pax_global_header00006660000000000000000000000064151120444350014511gustar00rootroot0000000000000052 comment=98b46935250a4b74005123abef4a28604c9a1365 pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/000077500000000000000000000000001511204443500204765ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/.codespell-ignore000066400000000000000000000001501511204443500237260ustar00rootroot00000000000000ba capela cas crasher datas endcode files' goin hda hist hve inport nd mmaped od ot parm sinc stdio uintpipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/.editorconfig000066400000000000000000000006301511204443500231520ustar00rootroot00000000000000# http://editorconfig.org root = true [*] indent_style = tab indent_size = 8 end_of_line = lf charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true # Use 2 spaces for meson files [*.build] indent_style = space indent_size = 2 [*.yml] indent_style = space indent_size = 2 [*.{conf,conf.in}] indent_style = space indent_size = 4 [*.{xml,xml.in}] indent_style = space indent_size = 2 pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/.gitattributes000066400000000000000000000000261511204443500233670ustar00rootroot00000000000000test/data/*.txt diff pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/.gitignore000066400000000000000000000011741511204443500224710ustar00rootroot00000000000000.* !.gitlab ABOUT-NLS *~ *.tar.gz *.tar.xz *.o cscope.out cscope.in.out cscope.po.out Makefile subprojects/lua* subprojects/wireplumber subprojects/media-session subprojects/packagecache subprojects/googletest* subprojects/gtest.wrap subprojects/libyaml.wrap subprojects/libyaml subprojects/libcamera subprojects/webrtc-audio-processing # Created by https://www.gitignore.io/api/vim ### Vim ### # Swap [._]*.s[a-v][a-z] [._]*.sw[a-p] [._]s[a-rt-v][a-z] [._]ss[a-gi-z] [._]sw[a-p] # Session Session.vim # Temporary .netrwhist *~ # Auto-generated tag files tags # Persistent undo [._]*.un~ # End of https://www.gitignore.io/api/vim pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/.gitlab-ci.yml000066400000000000000000000470631511204443500231440ustar00rootroot00000000000000# Create merge request pipelines for open merge requests, branch pipelines # otherwise. This allows MRs for new users to run CI, and prevents duplicate # pipelines for branches with open MRs. workflow: rules: - if: $CI_PIPELINE_SOURCE == "merge_request_event" - if: $CI_COMMIT_BRANCH && $CI_OPEN_MERGE_REQUESTS when: never - if: $CI_COMMIT_BRANCH stages: - container - container_coverity - build - analysis - pages variables: FDO_UPSTREAM_REPO: 'pipewire/pipewire' # ci-templates as of Mar 25th 2024 .templates_sha: &templates_sha ef5e4669b7500834a17ffe9277e15fbb6d977fff include: - project: 'freedesktop/ci-templates' ref: *templates_sha file: '/templates/fedora.yml' - project: 'freedesktop/ci-templates' ref: *templates_sha file: '/templates/ubuntu.yml' - project: 'freedesktop/ci-templates' ref: *templates_sha file: '/templates/alpine.yml' - project: 'freedesktop/ci-templates' ref: *templates_sha file: '/templates/debian.yml' .fedora: variables: # Update this tag when you want to trigger a rebuild FDO_DISTRIBUTION_TAG: '2025-10-22.0' FDO_DISTRIBUTION_VERSION: '42' FDO_DISTRIBUTION_PACKAGES: >- alsa-lib-devel avahi-devel bluez-libs-devel clang dbus-devel doxygen fdk-aac-free-devel file findutils gcc gcc-c++ git glib-devel graphviz gstreamer1-devel gstreamer1-plugins-base-devel jack-audio-connection-kit-devel libasan liblc3-devel libcanberra-devel libebur128-devel libffado-devel libldac-devel libmysofa-devel libsndfile-devel libubsan libusb1-devel lilv-devel libva-devel libX11-devel ModemManager-devel meson openssl-devel pulseaudio-libs-devel python3-docutils python3-pip sbc-devel ShellCheck SDL2-devel spandsp-devel systemd-devel vulkan-loader-devel webrtc-audio-processing-devel which valgrind ninja-build pkgconf pulseaudio-utils openal-soft readline-devel pandoc fftw-libs-single fftw-devel onnxruntime-devel # Uncommenting the following two lines and disabling the meson entry above # will re-enable use of Meson via pip but please consider using a newer distro # image first or making the build system compatible instead! This is because # using pip or another 3rd party repo defeats the point testing the particular # distro for regressions. # NOTE: If you do end up using pip3 for meson, be sure to also update the # build_meson_prerelease and build_meson_exact_release build instructions # to uninstall the pip3 version again and probably to not call dnf remove # FDO_DISTRIBUTION_EXEC: >- # pip3 install meson .ubuntu: variables: # Update this tag when you want to trigger a rebuild FDO_DISTRIBUTION_TAG: '2025-05-10.0' FDO_DISTRIBUTION_VERSION: '22.04' FDO_DISTRIBUTION_PACKAGES: >- debhelper-compat findutils git libapparmor-dev libasound2-dev libavcodec-dev libavfilter-dev libavformat-dev libdbus-1-dev libbluetooth-dev libglib2.0-dev libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev libsbc-dev libsdl2-dev libsnapd-glib-dev libudev-dev libva-dev libx11-dev meson ninja-build pkg-config python3-docutils systemd # Uncommenting the following three lines and disabling the meson entry above # will re-enable use of Meson via pip but please consider using a newer distro # image first or making the build system compatible instead! This is because # using pip or another 3rd party repo defeats the point testing the particular # distro for regressions. # python3-pip # FDO_DISTRIBUTION_EXEC: >- # pip3 install meson .debian: variables: # Update this tag when you want to trigger a rebuild BASE_TAG: '2025-08-10.0' FDO_DISTRIBUTION_VERSION: 'trixie' FDO_DISTRIBUTION_PACKAGES: >- build-essential dpkg-dev findutils git meson .debian-archictectures: parallel: matrix: - ARCH: - amd64 - arm64 - armhf - i386 - ppc64el - riscv64 - s390x .alpine: variables: # Update this tag when you want to trigger a rebuild FDO_DISTRIBUTION_TAG: '2025-03-25.0' FDO_DISTRIBUTION_VERSION: '3.20' FDO_DISTRIBUTION_PACKAGES: >- alsa-lib-dev avahi-dev bash bluez-dev gcc g++ dbus-dev doxygen elogind-dev eudev-dev fdk-aac-dev git glib-dev graphviz gst-plugins-base-dev gstreamer-dev jack-dev libfreeaptx-dev libusb-dev libx11-dev meson modemmanager-dev ncurses-dev pulseaudio-dev readline-dev sbc-dev vulkan-loader-dev xmltoman .coverity: variables: FDO_REPO_SUFFIX: 'coverity' FDO_BASE_IMAGE: registry.freedesktop.org/$FDO_UPSTREAM_REPO/fedora/$FDO_DISTRIBUTION_VERSION:$FDO_DISTRIBUTION_TAG FDO_DISTRIBUTION_PACKAGES: >- curl FDO_DISTRIBUTION_EXEC: >- mkdir -p /opt ; cd /opt ; curl -o /tmp/cov-analysis-linux64.tgz https://scan.coverity.com/download/cxx/linux64 --form project=$COVERITY_SCAN_PROJECT_NAME --form token=$COVERITY_SCAN_TOKEN ; tar xf /tmp/cov-analysis-linux64.tgz ; mv cov-analysis-linux64-* coverity ; rm /tmp/cov-analysis-linux64.tgz rules: - if: $COVERITY != null .not_coverity: rules: - if: $COVERITY == null .build: before_script: # setup the environment - export BUILD_ID="$CI_JOB_ID" - export PREFIX="$PWD/prefix-$BUILD_ID" - export BUILD_DIR="$PWD/build-$BUILD_ID" - export XDG_RUNTIME_DIR="$(mktemp -p $PWD -d xdg-runtime-XXXXXX)" - | if [ -n "$FDO_CI_CONCURRENT" ]; then COMPILE_ARGS="-j$FDO_CI_CONCURRENT" export COMPILE_ARGS fi script: - echo "Building with meson options $MESON_OPTIONS" - meson setup "$BUILD_DIR" --prefix="$PREFIX" $MESON_OPTIONS - meson compile -C "$BUILD_DIR" $COMPILE_ARGS - echo "Running tests with meson options $MESON_TEST_OPTIONS" - meson test -C "$BUILD_DIR" --no-rebuild $MESON_TEST_OPTIONS - meson install -C "$BUILD_DIR" --no-rebuild artifacts: name: pipewire-$CI_COMMIT_SHA when: always paths: - build-*/meson-logs container_ubuntu: extends: - .ubuntu - .fdo.container-build@ubuntu stage: container variables: GIT_STRATEGY: none # no need to pull the whole tree for rebuilding the image container_debian: extends: - .debian - .debian-archictectures - .fdo.container-build@debian stage: container variables: GIT_STRATEGY: none # no need to pull the whole tree for rebuilding the image FDO_DISTRIBUTION_TAG: "$BASE_TAG-$ARCH" FDO_DISTRIBUTION_EXEC: >- ./.gitlab/ci/setup-debian-cross-container.sh "$ARCH" container_fedora: extends: - .fedora - .fdo.container-build@fedora stage: container variables: GIT_STRATEGY: none # no need to pull the whole tree for rebuilding the image container_alpine: extends: - .alpine - .fdo.container-build@alpine stage: container variables: GIT_STRATEGY: none # no need to pull the whole tree for rebuilding the image container_coverity: extends: - .fedora - .coverity - .fdo.container-build@fedora stage: container_coverity variables: GIT_STRATEGY: none build_on_ubuntu: extends: - .ubuntu - .not_coverity - .fdo.distribution-image@ubuntu - .build stage: build needs: - job: container_ubuntu artifacts: false variables: MESON_OPTIONS: "-Dsession-managers=[] -Dsnap=enabled" build_on_debian: extends: - .debian - .debian-archictectures - .not_coverity - .fdo.distribution-image@debian - .build stage: build needs: - job: container_debian artifacts: false # ideally # parallel: # matrix: # - ARCH: "$ARCH" # however https://gitlab.com/gitlab-org/gitlab/-/issues/423553 # ("Expand variables in `needs:parallel:matrix`") variables: FDO_DISTRIBUTION_TAG: "$BASE_TAG-$ARCH" # see /.gitlab/ci/setup-debian-cross-container.sh for installed packages MESON_OPTIONS: >- --cross-file /opt/meson-$ARCH.cross -D c_args=['-UFASTPATH'] -D cpp_args=['-UFASTPATH'] -D auto_features=enabled -D session-managers=[] -D bluez5-backend-native-mm=enabled -D bluez5-codec-lc3plus=disabled -D bluez5-codec-ldac=disabled -D bluez5-codec-ldac-dec=disabled -D libcamera=disabled -D roc=disabled -D snap=disabled -D systemd-user-service=disabled -D systemd-system-service=disabled -D onnxruntime=disabled -D vulkan=enabled -D ffmpeg=enabled -D pw-cat-ffmpeg=enabled MESON_TEST_OPTIONS: >- --timeout-multiplier=2 .build_on_fedora: extends: - .fedora - .not_coverity - .fdo.distribution-image@fedora - .build stage: build needs: - job: container_fedora artifacts: false build_on_fedora: extends: - .build_on_fedora variables: MESON_OPTIONS: >- -Ddocs=enabled -Dman=enabled -Ddoc-prefix-value=/usr -Ddoc-sysconfdir-value=/etc -Dinstalled_tests=enabled -Dsystemd-system-service=enabled -Dbluez5-backend-hsphfpd=enabled -Daudiotestsrc=enabled -Dtest=enabled -Dvideotestsrc=enabled -Dvolume=enabled -Dvulkan=enabled -Dsdl2=enabled -Dsndfile=enabled -Dsession-managers=[] -Dsnap=disabled artifacts: name: pipewire-$CI_COMMIT_SHA when: always paths: - build-*/meson-logs - prefix-* build_on_fedora_html_docs: extends: - .build_on_fedora variables: MESON_OPTIONS: >- -Ddocs=enabled -Dman=enabled -Ddoc-prefix-value=/usr -Ddoc-sysconfdir-value=/etc -Dinstalled_tests=enabled -Dsystemd-system-service=enabled -Dbluez5-backend-hsphfpd=enabled -Daudiotestsrc=enabled -Dtest=enabled -Dvideotestsrc=enabled -Dvolume=enabled -Dvulkan=enabled -Dsdl2=enabled -Dsndfile=enabled -Dsession-managers=[] before_script: - git fetch origin 1.0 1.2 1.4 master - git branch -f 1.0 origin/1.0 - git clone -b 1.0 . branch-1.0 - git branch -f 1.2 origin/1.2 - git clone -b 1.2 . branch-1.2 - git branch -f 1.4 origin/1.4 - git clone -b 1.4 . branch-1.4 - git branch -f master origin/master - git clone -b master . branch-master - !reference [.build, before_script] script: - cd branch-1.0 - meson setup builddir $MESON_OPTIONS - meson compile -C builddir doc/pipewire-docs - cd .. - cd branch-1.2 - meson setup builddir $MESON_OPTIONS - meson compile -C builddir doc/pipewire-docs - cd .. - cd branch-1.4 - meson setup builddir $MESON_OPTIONS - meson compile -C builddir doc/pipewire-docs - cd .. - cd branch-master - meson setup builddir $MESON_OPTIONS - meson compile -C builddir doc/pipewire-docs artifacts: name: pipewire-$CI_COMMIT_SHA when: always paths: - branch-*/builddir/meson-logs - branch-*/builddir/doc/html rules: - !reference [pages, rules] build_on_alpine: extends: - .alpine - .not_coverity - .fdo.distribution-image@alpine - .build stage: build needs: - job: container_alpine artifacts: false variables: MESON_OPTIONS: "-Dsession-managers=[] -Dsnap=disabled -Dlogind=enabled -Dlogind-provider=libelogind" # build with all auto() options enabled build_all: extends: - .build_on_fedora variables: # Fedora doesn't have libfreeaptx, lc3plus, or roc # libcamera has no stable API, so let's not chase that target MESON_OPTIONS: >- -Dauto_features=enabled -Dbluez5-codec-aptx=disabled -Dbluez5-codec-lc3plus=disabled -Dbluez5-codec-ldac-dec=disabled -Droc=disabled -Dlibcamera=disabled -Dsession-managers=[] -Dc_args=['-UFASTPATH'] -Dcpp_args=['-UFASTPATH'] -Dsnap=disabled parallel: matrix: - CC: [gcc, clang] # build with all options on auto() or their default values build_with_no_commandline_options: extends: - .build_on_fedora variables: MESON_OPTIONS: "-Dsession-managers=[] -Dsnap=disabled" parallel: matrix: - CC: [gcc, clang] # build with a set of options enabled or disabled build_with_custom_options: extends: - .build_on_fedora parallel: matrix: - MESON_OPTION: [docs, installed_tests, systemd-system-service, bluez5-backend-hsphfpd, audiotestsrc, test, videotestsrc, volume, vulkan, sdl2, sndfile] MESON_OPTION_VALUE: [enabled, disabled] script: - echo "Building with -D$MESON_OPTION=$MESON_OPTION_VALUE" - meson setup "$BUILD_DIR" --prefix="$PREFIX" "-D$MESON_OPTION=$MESON_OPTION_VALUE" -Dsession-managers=[] - meson compile -C "$BUILD_DIR" $COMPILE_ARGS - meson test -C "$BUILD_DIR" --no-rebuild build_with_asan_ubsan: extends: - .build_on_fedora script: - echo "Building with ASan and UBSan" - meson setup "$BUILD_DIR" --prefix="$PREFIX" -D debug=true -D optimization=g -D b_sanitize=address,undefined -D session-managers=[] - meson compile -C "$BUILD_DIR" $COMPILE_ARGS - env UBSAN_OPTIONS=halt_on_error=1:abort_on_error=1:print_summary=1:print_stacktrace=1 ASAN_OPTIONS=halt_on_error=1:abort_on_error=1:print_summary=1 meson test -C "$BUILD_DIR" --no-rebuild # A release build with NDEBUG, all options on auto() but tests explicitly # enabled. This should show issues with tests failing due to different # optimization or relying on assert. build_release: extends: - .build_on_fedora variables: MESON_OPTIONS: "-Dtest=enabled -Dbuildtype=release -Db_ndebug=true -Dsession-managers=[] -Dsnap=disabled" parallel: matrix: - CC: [gcc, clang] build_session_managers: extends: - .build_on_fedora script: - echo "Building with meson options $MESON_OPTIONS" - meson setup "$BUILD_DIR" --prefix="$PREFIX" $MESON_OPTIONS - meson compile -C "$BUILD_DIR" $COMPILE_ARGS - meson install -C "$BUILD_DIR" --no-rebuild variables: MESON_OPTIONS: "-Dsession-managers=$SESSION_MANAGERS -Dsnap=disabled" parallel: matrix: - SESSION_MANAGERS: ["[]", "wireplumber", "media-session", "media-session,wireplumber", "wireplumber,media-session" ] allow_failure: true build_meson_prerelease: extends: - .build_on_fedora script: - dnf remove --assumeyes meson - pip3 install --upgrade --pre meson - echo "Building with meson options $MESON_OPTIONS" - meson setup "$BUILD_DIR" --prefix="$PREFIX" $MESON_OPTIONS - meson compile -C "$BUILD_DIR" $COMPILE_ARGS - meson install -C "$BUILD_DIR" --no-rebuild variables: MESON_OPTIONS: "-Dsession-managers=wireplumber,media-session -Dsnap=disabled" allow_failure: true build_meson_exact_release: extends: - .build_on_fedora script: - meson_version=$(head -n 5 meson.build | grep 'meson_version' | sed -e 's/.*\([0-9]\+\.[0-9]\+\.[0-9]\+\).*/\1/') - echo "Requiring meson version $meson_version" - test -n "$meson_version" || (echo "Meson version parser failed" && exit 1) - dnf remove --assumeyes meson # - pip3 uninstall --yes meson - pip3 install "meson==$meson_version" - echo "Building with meson options $MESON_OPTIONS" - meson setup "$BUILD_DIR" --prefix="$PREFIX" $MESON_OPTIONS - meson compile -C "$BUILD_DIR" $COMPILE_ARGS - meson install -C "$BUILD_DIR" --no-rebuild variables: MESON_OPTIONS: "-Dsession-managers=[] -Dsnap=disabled" valgrind: extends: - .build_on_fedora script: - echo "Building with meson options $MESON_OPTIONS" - meson setup "$BUILD_DIR" --prefix="$PREFIX" $MESON_OPTIONS - meson test -C "$BUILD_DIR" --setup=valgrind variables: MESON_OPTIONS: "-Dsession-managers=[]" build_with_coverity: extends: - .fedora - .coverity - .fdo.suffixed-image@fedora - .build stage: analysis needs: - job: container_coverity artifacts: false script: - export PATH=/opt/coverity/bin:$PATH - meson setup "$BUILD_DIR" --prefix="$PREFIX" -Ddocs=disabled -Dbluez5-backend-hsphfpd=enabled -Daudiotestsrc=enabled -Dtest=enabled -Dvideotestsrc=enabled -Dvolume=enabled -Dvulkan=enabled -Dsdl2=enabled -Dsndfile=enabled -Dsession-managers=[] - cov-configure --config coverity_conf.xml --comptype gcc --compiler cc --template --xml-option=append_arg@C:--ppp_translator --xml-option=append_arg@C:"replace/_sd_deprecated_\s+=/ =" --xml-option=append_arg@C:--ppp_translator --xml-option=append_arg@C:"replace/GLIB_(DEPRECATED|AVAILABLE)_ENUMERATOR_IN_\d_\d\d(_FOR\(\w+\)|)\s+=/ =" --xml-option=append_arg@C:--ppp_translator --xml-option=append_arg@C:"replace/(__has_builtin|_GLIBCXX_HAS_BUILTIN)\(\w+\)/1" - cov-build --dir cov-int --config coverity_conf.xml meson compile -C "$BUILD_DIR" $COMPILE_ARGS - tar czf cov-int.tar.gz cov-int - curl https://scan.coverity.com/builds?project=$COVERITY_SCAN_PROJECT_NAME --form token=$COVERITY_SCAN_TOKEN --form email=$GITLAB_USER_EMAIL --form file=@cov-int.tar.gz --form version="`git describe --tags`" --form description="`git describe --tags` / $CI_COMMIT_TITLE / $CI_COMMIT_REF_NAME:$CI_PIPELINE_ID " artifacts: name: pipewire-coverity-$CI_COMMIT_SHA when: always paths: - build-*/meson-logs - cov-int/build-log.txt shellcheck: extends: - .build_on_fedora stage: analysis variables: MESON_OPTIONS: >- -Dpipewire-v4l2=enabled -Dpipewire-jack=enabled script: - echo "Configuring with meson options $MESON_OPTIONS" - meson setup "$BUILD_DIR" --prefix="$PREFIX" $MESON_OPTIONS - shellcheck $(git ls-files '*.sh') - shellcheck $(grep -rl "#\!/.*bin/.*sh" "$BUILD_DIR") spellcheck: extends: - .build_on_fedora stage: analysis script: - git ls-files | grep -v .gitlab-ci.yml | xargs -d '\n' sed -i 's/Pipewire/PipeWire/g' - git diff --exit-code || (echo "Please fix the above spelling mistakes" && exit 1) doccheck: extends: - .build_on_fedora stage: analysis script: # Check that each pipewire module has a \subpage entry - git grep -h -o -e "\\\page page_module_\w\+" | cut -f2 -d ' ' > pipewire_module_pages - cat pipewire_module_pages - | for page in $(cat pipewire_module_pages); do git grep -q -e "\\\subpage $page" || (echo "\\page $page is missing \\subpage entry in doc/pipewire-modules.dox" && false) done check_missing_headers: extends: - .fedora - .not_coverity - .fdo.distribution-image@fedora stage: analysis needs: - job: build_on_fedora artifacts: true script: - export PREFIX=`find -name prefix-*` - ./.gitlab/ci/check_missing_headers.sh pages: extends: - .not_coverity stage: pages needs: - job: build_on_fedora_html_docs artifacts: true script: - mkdir public public/1.0 public/1.2 public/1.4 public/devel - cp -R branch-1.0/builddir/doc/html/* public/1.0/ - cp -R branch-1.2/builddir/doc/html/* public/1.2/ - cp -R branch-1.4/builddir/doc/html/* public/1.4/ - cp -R branch-master/builddir/doc/html/* public/devel/ - (cd public && ln -s 1.4/* .) artifacts: paths: - public rules: - if: $CI_COMMIT_BRANCH == 'master' pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/.gitlab/000077500000000000000000000000001511204443500220165ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/.gitlab/ci/000077500000000000000000000000001511204443500224115ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/.gitlab/ci/check_missing_headers.sh000077500000000000000000000010001511204443500272400ustar00rootroot00000000000000#!/bin/sh # This script will tell you if there are headers in the source tree # that have not been installed in $PREFIX LIST="" for i in $(find spa/include -name '*.h' | sed s#spa/include/##); do [ -f "$PREFIX/include/spa-0.2/$i" ] || LIST="$i $LIST" done for i in $(find src/pipewire -name '*.h' -a -not -name '*private.h' | sed s#src/##); do [ -f "$PREFIX/include/pipewire-0.3/$i" ] || LIST="$i $LIST" done for i in $LIST; do echo "$i not installed" done if [ "$LIST" != "" ]; then exit 1 fi exit 0 pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/.gitlab/ci/setup-debian-cross-container.sh000077500000000000000000000021011511204443500304310ustar00rootroot00000000000000#!/usr/bin/env bash set -ex packages=( # libapparmor-dev libasound2-dev libavahi-client-dev libavcodec-dev libavfilter-dev libavformat-dev libbluetooth-dev libcanberra-dev libdbus-1-dev libebur128-dev libfdk-aac-dev libffado-dev libfftw3-dev libfreeaptx-dev libglib2.0-dev libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev libjack-jackd2-dev liblc3-dev liblilv-dev libmysofa-dev libopus-dev libpulse-dev libreadline-dev libsbc-dev libsdl2-dev # libsnapd-glib-dev libsndfile1-dev libspandsp-dev libssl-dev libsystemd-dev libudev-dev libusb-1.0-0-dev libvulkan-dev libwebrtc-audio-processing-dev libx11-dev modemmanager-dev ) arch="$1" export DEBIAN_FRONTEND=noninteractive sed -i \ 's/^Components:.*$/Components: main contrib non-free non-free-firmware/' \ /etc/apt/sources.list.d/debian.sources dpkg --add-architecture "$arch" apt update -y pkgs=( "crossbuild-essential-$arch" ) for pkg in "${packages[@]}"; do pkgs+=( "$pkg:$arch" ) done apt install -y "${pkgs[@]}" meson env2mfile --cross --debarch "$arch" -o "/opt/meson-$arch.cross" pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/.gitlab/issue_templates/000077500000000000000000000000001511204443500252245ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/.gitlab/issue_templates/.gitkeep000066400000000000000000000000001511204443500266430ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/.gitlab/issue_templates/bluetooth issue.md000066400000000000000000000015461511204443500306720ustar00rootroot00000000000000 - PipeWire version (`pipewire --version`): - Distribution and distribution version (`PRETTY_NAME` from `/etc/os-release`): - Desktop Environment: - Kernel version (`uname -r`): - BlueZ version (`bluetoothctl --version`): - `lsusb`: ``` # paste the output of "lsusb" here ``` - Bluetooth devices: ``` # paste the output of "bluetoothctl devices" here ``` ## Description of Problem: ## How Reproducible: ### Steps to Reproduce: 1. 2. 3. ### Actual Results: ### Expected Results: # Additional Info (as attachments): - `pw-dump > pw-dump.log`: - Bluetooth debug log, see [here](https://gitlab.freedesktop.org/pipewire/pipewire/-/wikis/Troubleshooting#bluetooth): pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/.gitlab/issue_templates/issue.md000066400000000000000000000007521511204443500267020ustar00rootroot00000000000000 - PipeWire version (`pipewire --version`): - Distribution and distribution version (`PRETTY_NAME` from `/etc/os-release`): - Desktop Environment: - Kernel version (`uname -r`): ## Description of Problem: ## How Reproducible: ### Steps to Reproduce: 1. 2. 3. ### Actual Results: ### Expected Results: # Additional Info (as attachments): - `pw-dump > pw-dump.log`: pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/CODE_OF_CONDUCT.md000066400000000000000000000065001511204443500232760ustar00rootroot00000000000000# Contributor Covenant Code of Conduct ## Our Pledge In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to make participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. ## Our Standards Examples of behavior that contributes to creating a positive environment include: * Using welcoming and inclusive language * Being respectful of differing viewpoints and experiences * Gracefully accepting constructive criticism * Focusing on what is best for the community * Showing empathy towards other community members Examples of unacceptable behavior by participants include: * The use of sexualized language or imagery and unwelcome sexual attention or advances * Trolling, insulting/derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or electronic address, without explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. ## Scope This Code of Conduct applies within all project spaces, and it also applies when an individual is representing the project or its community in public spaces. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at pipewire-maintainers@lists.freedesktop.org. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html [homepage]: https://www.contributor-covenant.org For answers to common questions about this code of conduct, see https://www.contributor-covenant.org/faq pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/COPYING000066400000000000000000000023021511204443500215260ustar00rootroot00000000000000Copyright © 2018 Wim Taymans Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --- The above is the version of the MIT "Expat" License used by X.org: http://cgit.freedesktop.org/xorg/xserver/tree/COPYING pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/INSTALL.md000066400000000000000000000156071511204443500221370ustar00rootroot00000000000000## Building PipeWire uses a build tool called [*Meson*](https://mesonbuild.com) as a basis for its build process. It's a tool with some resemblance to Autotools and CMake. Meson again generates build files for a lower level build tool called [*Ninja*](https://ninja-build.org/), working in about the same level of abstraction as more familiar GNU Make does. Meson uses a user-specified build directory and all files produced by Meson are in that build directory. This build directory will be called `builddir` in this document. Generate the build files for Ninja: ``` $ meson setup builddir ``` For distribution-specific build dependencies, please check our [CI pipeline](https://gitlab.freedesktop.org/pipewire/pipewire/-/blob/master/.gitlab-ci.yml) (search for `FDO_DISTRIBUTION_PACKAGES`). Note that some dependencies are optional and depend on options passed to meson. Once this is done, the next step is to review the build options: ``` $ meson configure builddir ``` Define the installation prefix: ``` $ meson configure builddir -Dprefix=/usr # Default: /usr/local ``` PipeWire specific build options are listed in the "Project options" section. They are defined in `meson_options.txt`. Finally, invoke the build: ``` $ meson compile -C builddir ``` Just to avoid any confusion: `autogen.sh` is a script invoked by *Jhbuild*, which orchestrates multi-component builds. ## Running If you want to run PipeWire without installing it on your system, there is a script that you can run. This puts you in an environment in which PipeWire can be run from the build directory, and ALSA, PulseAudio and JACK applications will use the PipeWire emulation libraries automatically in this environment. You can get into this environment with: ``` $ ./pw-uninstalled.sh -b builddir ``` In most cases you would want to run the default pipewire daemon. Look below for how to make this daemon start automatically using systemd. If you want to run pipewire from the build directory, you can do this by doing: ``` cd builddir/ make run ``` This will use the default config file to configure and start the daemon. The default config will also start `pipewire-media-session`, a default example media session and `pipewire-pulse`, a PulseAudio compatible server. You can also enable more debugging with the `PIPEWIRE_DEBUG` and `WIREPLUMBER_DEBUG` environment variables like so: ``` cd builddir/ PIPEWIRE_DEBUG="D" WIREPLUMBER_DEBUG="D" make run ``` You might have to stop the pipewire service/socket that might have been started already, with: ``` systemctl --user stop pipewire.service \ pipewire.socket \ pipewire-media-session.service \ pipewire-pulse.service \ pipewire-pulse.socket ``` ## Installing PipeWire comes with quite a bit of libraries and tools, run: ``` meson install -C builddir ``` to install everything onto the system into the specified prefix. Depending on the configured installation prefix, the above command may need to be run with elevated privileges (e.g. with `sudo`). Some additional steps will have to be performed to integrate with the distribution as shown below. ### PipeWire daemon A correctly installed PipeWire system should have a pipewire process, a pipewire-media-session (or alternative) and an (optional) pipewire-pulse process running. PipeWire is usually started as a systemd unit using socket activation or as a service. Configuration of the PipeWire daemon can be found in `/usr/share/pipewire/pipewire.conf`. Please refer to the comments in the config file for more information about the configuration options. The daemon is started with: ``` systemctl --user start pipewire.service pipewire.socket ``` If you did not start the media-session in pipewire.conf, you will also need to start it like this: ``` systemctl --user start pipewire-media-session.service ``` To make it start on system startup: ``` systemctl --user enable pipewire-media-session.service ``` you can write ```enable --now``` to start service immediately. ### ALSA plugin The ALSA plugin is usually installed in: On Fedora: ``` /usr/lib64/alsa-lib/libasound_module_pcm_pipewire.so ``` On Ubuntu: ``` /usr/lib/x86_64-linux-gnu/alsa-lib/libasound_module_pcm_pipewire.so ``` There is also a config file installed in: ``` /usr/share/alsa/alsa.conf.d/50-pipewire.conf ``` The plugin will be picked up by alsa when the following files are in `/etc/alsa/conf.d/`: ``` /etc/alsa/conf.d/50-pipewire.conf -> /usr/share/alsa/alsa.conf.d/50-pipewire.conf /etc/alsa/conf.d/99-pipewire-default.conf ``` With this setup, `aplay -l` should list a pipewire device that can be used as a regular alsa device for playback and record. ### JACK emulation PipeWire reimplements the 3 libraries that JACK applications use to make them run on top of PipeWire. These libraries are found here: ``` /usr/lib64/pipewire-0.3/jack/libjacknet.so -> libjacknet.so.0 /usr/lib64/pipewire-0.3/jack/libjacknet.so.0 -> libjacknet.so.0.304.0 /usr/lib64/pipewire-0.3/jack/libjacknet.so.0.304.0 /usr/lib64/pipewire-0.3/jack/libjackserver.so -> libjackserver.so.0 /usr/lib64/pipewire-0.3/jack/libjackserver.so.0 -> libjackserver.so.0.304.0 /usr/lib64/pipewire-0.3/jack/libjackserver.so.0.304.0 /usr/lib64/pipewire-0.3/jack/libjack.so -> libjack.so.0 /usr/lib64/pipewire-0.3/jack/libjack.so.0 -> libjack.so.0.304.0 /usr/lib64/pipewire-0.3/jack/libjack.so.0.304.0 ``` The provided `pw-jack` script uses `LD_LIBRARY_PATH` to set the library search path to these replacement libraries. This allows you to run jack apps on both the real JACK server or on PipeWire with the script. It is also possible to completely replace the JACK libraries by adding a file `pipewire-jack-x86_64.conf` to `/etc/ld.so.conf.d/` with contents like: ``` /usr/lib64/pipewire-0.3/jack/ ``` Note that when JACK is replaced by PipeWire, the SPA JACK plugin (installed in `/usr/lib64/spa-0.2/jack/libspa-jack.so`) is not useful anymore and distributions should make them conflict. ### PulseAudio replacement PipeWire reimplements the PulseAudio server protocol as a small service that runs on top of PipeWire. The binary is normally placed here: ``` /usr/bin/pipewire-pulse ``` The server can be started with provided systemd activation files or from PipeWire itself. (See `/usr/share/pipewire/pipewire.conf`) ``` systemctl --user start pipewire-pulse.service pipewire-pulse.socket ``` You can also start additional PulseAudio servers listening on other sockets with the `-a` option. See `pipewire-pulse -h` for more info. ## Uninstalling To uninstall, run: ``` ninja -C builddir uninstall ``` Depending on the configured installation prefix, the above command may need to be run with elevated privileges (e.g. with `sudo`). Note that at the time of writing uninstallation only works with the same build directory that was used for installation. Meson stores the list of installed files in the build directory, and this list is necessary for uninstallation to work. pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/LICENSE000066400000000000000000000005351511204443500215060ustar00rootroot00000000000000All PipeWire source files are licensed under the MIT License. (see file COPYING for details) With the exception of: libspa-alsa.so in spa/plugins/alsa, which contains LGPL code from Pulseaudio and is thus licensed as LGPL. libjackserver.so which links against the GPL2 jack/control.h, which makes it GPL2 pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/Makefile.in000066400000000000000000000043261511204443500225500ustar00rootroot00000000000000VERSION = @VERSION@ TAG = @TAG@ SOURCE_ROOT = @SOURCE_ROOT@ BUILD_ROOT = @BUILD_ROOT@ all: ninja -C $(BUILD_ROOT) install: ninja -C $(BUILD_ROOT) install uninstall: ninja -C $(BUILD_ROOT) uninstall clean: ninja -C $(BUILD_ROOT) clean run: all SPA_PLUGIN_DIR=$(BUILD_ROOT)/spa/plugins \ SPA_DATA_DIR=$(SOURCE_ROOT)/spa/plugins \ PIPEWIRE_MODULE_DIR=$(BUILD_ROOT)/src/modules \ PATH=$(BUILD_ROOT)/src/examples:$(PATH) \ PIPEWIRE_CONFIG_DIR=$(BUILD_ROOT)/src/daemon \ ACP_PATHS_DIR=$(SOURCE_ROOT)/spa/plugins/alsa/mixer/paths \ ACP_PROFILES_DIR=$(SOURCE_ROOT)/spa/plugins/alsa/mixer/profile-sets \ PIPEWIRE_LOG_SYSTEMD=false \ $(DBG) $(BUILD_ROOT)/src/daemon/pipewire-uninstalled run-pulse: all SPA_PLUGIN_DIR=$(BUILD_ROOT)/spa/plugins \ SPA_DATA_DIR=$(SOURCE_ROOT)/spa/plugins \ PIPEWIRE_MODULE_DIR=$(BUILD_ROOT)/src/modules \ PIPEWIRE_CONFIG_DIR=$(BUILD_ROOT)/src/daemon \ ACP_PATHS_DIR=$(SOURCE_ROOT)/spa/plugins/alsa/mixer/paths \ ACP_PROFILES_DIR=$(SOURCE_ROOT)/spa/plugins/alsa/mixer/profile-sets \ PIPEWIRE_LOG_SYSTEMD=false \ $(DBG) $(BUILD_ROOT)/src/daemon/pipewire-pulse gdb: $(MAKE) run DBG=gdb valgrind: $(MAKE) run DBG="DISABLE_RTKIT=1 PIPEWIRE_DLCLOSE=false valgrind --trace-children=yes --leak-check=full" test: all ninja -C $(BUILD_ROOT) test benchmark: all ninja -C $(BUILD_ROOT) benchmark monitor: all SPA_PLUGIN_DIR=$(BUILD_ROOT)/spa/plugins \ SPA_DATA_DIR=$(SOURCE_ROOT)/spa/plugins \ PIPEWIRE_MODULE_DIR=$(BUILD_ROOT)/src/modules/ \ $(BUILD_ROOT)/src/tools/pw-mon cli: all SPA_PLUGIN_DIR=$(BUILD_ROOT)/spa/plugins \ SPA_DATA_DIR=$(SOURCE_ROOT)/spa/plugins \ PIPEWIRE_MODULE_DIR=$(BUILD_ROOT)/src/modules/ \ $(BUILD_ROOT)/src/tools/pw-cli shell: all ninja -C $(BUILD_ROOT) pw-uninstalled dist: all git archive --prefix=pipewire-$(VERSION)/ -o pipewire-$(VERSION).tar.gz $(TAG) rpm: dist rpmbuild -ta pipewire-$(VERSION).tar.gz publish: all git branch -D gh-pages 2>/dev/null || true && \ git branch -D draft 2>/dev/null || true && \ git checkout -b draft && \ git add -f $(BUILD_ROOT)/doc/html && \ git commit -anm "Deploy on gh-pages" && \ git subtree split --prefix $(BUILD_ROOT)/doc/html -b gh-pages && \ git push --force origin gh-pages:gh-pages && \ git checkout work 2>/dev/null pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/NEWS000066400000000000000000012015261511204443500212040ustar00rootroot00000000000000# PipeWire 1.5.84 (2025-11-27) This is the fourth 1.6 release candidate that is API and ABI compatible with previous 1.4.x, 1.2.x and 1.0.x releases. Changes since the last pre-release: ## Highlights - Capabilities wer added to improve negotiation over links. - The audio resampler now has a configurable window function to better tune the resampler quality. A kaiser and blackman window was added and the default parameters were tuned. - Various small fixes and improvements. ## PipeWire - Capabilities and PeerCapabilities were added to exchange key/value pairs between consumer and producer right after a link is made. This can be used to detect how the negotiation of formats and buffers should be done. ## Modules - Avoid segfaults in RTP source. (#4970) - The AVB module has seen some improvements. ## Pulse-server - @NONE@ can now be used to clear the default sink/source. ## SPA - Support longer convolver filenames and also support inline IRs. - The audio resampler window function is now selectable and configurable. A kaiser window and blackman window was added and the default qualities were tweaked to improve quality. - The filter-graph convolver latency is now set by default to something more sensible. (0 by default and N/2 for hilbert). (#4980) ## Bluetooth - Better xrun and error handling for iso streams. - The +CNUM reply was fixed. - The CIEC call status was fixed. (#1744) - Add BAP context metadata to improve compatibility. - Improve compatiblity with Creative Zen Hybrid Pro by releasing transports simultaneously. Older versions: # PipeWire 1.5.83 (2025-11-06) This is the third 1.6 release candidate that is API and ABI compatible with previous 1.4.x, 1.2.x and 1.0.x releases. Changes since the last pre-release: ## Highlights - Include the NEWS and updated version number. # PipeWire 1.5.82 (2025-11-06) This is the second 1.6 release candidate that is API and ABI compatible with previous 1.4.x, 1.2.x and 1.0.x releases. Changes since the last pre-release: ## Highlights - The max channel limit is now a compile time option. - The SAP and RTP module have seen some robustness improvements. - Add audio.layout propperty. - Cleanups to the code here and there. ## PipeWire - Handle Tags more like Latency with a NULL param when no ports are linked and some sort of (empty) Tag when the ports are linked. ## Modules - Improve the echo-cancel module to keep the streams more aligned and cause less latency. - Improve format parsing errors in most modules. - The RTP module now has extra code for better network robustness, including cases when network interfaces are not yet up and running, and multicast sockets are silently kicked out of IGMP groups. - The direct timestamp mode in the RTP module was effectively broken and is now fixed. - Add support for audio.layout. - Add multichannel support to ROC. ## SPA - Rework the maximum number of channel handling. Because this is a potential ABI break, it is now a compile time option with new functions to handle more than the previous 64 channels. - The 64 channel limit was removed from the noise shaper. - spa_strbuf is used in more places instead of custom snprintf code. - The volume ramp code was simplified. - The driver node now has properties to configure the clock. - The adapter will try to renegotiate when the driver changes. - Fix relaxed array parsing with od number of elements. (#4944) - audio.layout was added to set the channel positions to some predefined layouts. - Added more POD choice checks to ensure the right amount of values are present in the choice. - Fix __has_attribute usage. (#4962) - Thread RESET_ON_FORK is now disabled for JACK application so that forking will preserve any real-time thread priorities, like JACK. (#4966) - Fix some compilation issues. (#4960 and #4961). ## Pulse-server - Fix missing subscription events on device port changes. - Increase min.quantum to 256/48000. (#4875) ## GStreamer - Avoid overflow in clock time calculations. - Fix renegotiation. ## Docs - Swap the name and id of device.product # PipeWire 1.5.81 (2025-10-16) This is the first 1.6 release candidate that is API and ABI compatible with previous 1.4.x, 1.2.x and 1.0.x releases. In addition to all the changes backported to 1.4.x, this release also contains some new features: ## Highlights - The link negotiation code was refactored and improved. Applications now have more options for selecting the default values and restricting the available options. The default negotiation code will now attempt to better match the application suggested values. - The loop now has support for locking with priority inversion. Most of the code was updated to use the locks instead of invoke to get proper concurrent updates with the loop. The Thread loop functionality of locks, signal and wait was moved to the SPA loop. This guarantees better real-time behaviour because inter-thread synchronization does not have to pass eventfd/epoll. - The control stream parser was rewritten to be safe against concurrent updates while parsing, which can occur when parsing shared memory. It also has extra checks to avoid integer overflows and undefined behaviour. - MIDI 2.0 clip support was added to the tools. - Bluetooth ASHA (Audio Streaming for Hearing Aid) support was added. - The ALSA node setup was tweaked to provide low latency with the ALSA Firewire driver. - Better support for explicit sync. It is now possible to negotiate extra features to know if a consumer will signal the sync objects and implement a fallback using a reliable transport. - Many bug fixes and improvements. ## PipeWire - Avoid process calls in disconnect in pw-stream. (#3314) - Disable PipeWire services for root. - The link negotiation was refactored and improved. Drivers now always have a lower priority in deciding the final format. - Backwards compatibility with the v0 protocol was removed. - pw-stream and pw-filter will now refuse to queue a buffer that was not dequeued before. - Object properties will now be updated on the global as well. - The priority of config overrides is correct now. (#4816) - Async links now correctly report 1 extra quantum of latency. - node.exclusive and the new port.exclusive flag are now enforced by PipeWire itself. - A new timer-queue helper was added to schedule timeouts. - node.terminal and node.physical properties are now copied to the ports to make it possible to create virtual sources and sinks for JACK applications. - Port properties will now be dynamically updated when the node properties they depend on are updated. - Passive leaf nodes are now handled better. Now they will also run when the peer is active. (#4915) - Reliable transport has been added for output ports. This can be used in some cases if the producer wants to ensure buffers are consumed by a consumer. (#4885) - Context properties now support rlimit. properties to configure rlimits. (#4047) ## Modules - Close SyncObj fds. - module-combine-stream has better Latency reporting. - The JACK tunnel can now optionally connect ports. - module-loopback has better Latency reporting. - A Dolby Surround and Dolby Pro Logic II example filter config was added. - Filter-chain can now resample to a specific rate before running the filters. This is useful when the filter-graph needs to run at a specific rate. - Avahi-poll now uses the timer-queue to schedule timeouts. - Modules are ported to timer-queue instead of using timerfd directly for non-realtime timers. ## SPA - The loop now has support for locking with priority inversion. Most of the code was updated to use the locks instead of invoke to get proper concurrent updates with the loop. The Thread loop functionality of locks, signal and wait was moved to the SPA loop. - UMP to Midi 1.0 conversion was improved, some UMP events are now converted to multiple Midi 1.0 messages. (#4839) - The POD filter was refactored and improved. It is now possible to use the default value of the output by specifying an invalid input default value. - The POD parser was made safe for concurrent updates of the memory it is parsing. This is important when the POD is in shared memory and the parser should not access invalid memory. - Some hardcoded channel limits were removed and now use the global channel limit. More things can dynamically adapt to this global limit. The max number of channels was then bumped to 128. - The POD builder is safe to use on shared memory now and tries to avoid many integer overflows. - Most debug functions are safe to be used on shared memory. - User specified Commands and Events are now possible. - The SPA_IO_CLOCK_FLAG_DISCONT was added to spa_io_clock to signal a discont in the clock due to clock change. - AC3, DTS, EAC3, TRUEHD and MPEGH now have helper parser functions. - H265 was added as a video format. (#4674) - SPA_PARAM_PeerFormats was added to let a port know about its peer formats in order to better filter possible conversions. - More color matrices, transfer functions and color primaries. - The echo-canceler is enabled now. - Pro-Audio mode now uses 3 periods by default. This lowers the latency on some drivers (Firewire). The latency of Firewire is also reported correctly now. - The ALSA DLL bandwidth is configurable now. - The resampler now uses fixed point for the phases and is a little faster when updating adaptive rates. - The convolver is a little faster by swapping buffers instead of copying samples. - Latency and ProcessLatency support was added to filter-graph. (#4678) - Audio channel position support was added to filter-graph. - A new ffmpeg avfilter plugin was added to filter-graph. - A new ONNX filter was added to filter-graph. - A debug, pipe, zeroramp and noisegate filter was added to the filter-graph. (#4745) - The filter-graph lv2 plugin now supports options and state. - videoconvert was greatly improved. - The v4l2 plugin can negotiate DMABUF with modifiers. - Colorimetry information was added to v4l2 and libcamera. - Audioconvert can handle empty buffers more efficiently. - Improve the POD compare functions for Rectangle. - There is now a SPA_POD_PROP_FLAG_DROP flag to drop the property when the property is missing from one side. - A new FEATURE choice was added that is basically a flags choice with a FLAG_DROP property. - Metadata features were added. This is a way to negotiate new features for the metadata. (#4885) - DSD playback with pw-cat has been improved. - Compatibility and xrun prevention for the SOF driver has been improved. (#4489) - The filter-graph max plugin can now have 8 input channels. - Buffer Negotiation between the mixer port and the node ports is much improved. (#4918) - An offline AEC benchmark was added. - Channel positions are now read from HDMI ELD when possible. - Audioconvert and filter-graph now also support properties of Long and String types. ## ACP - It's possible to disable the pro-audio profile. - Support for Logitech Z407 PC Speakers was improved. - Support for Razer BlackShark v3. - Fix volume rounding down causing mute. (#4890) ## Tools - pw-cat can now play and record MIDI 2.0 Clips, which is the official format for storing MIDI 2.0 UMP data. pw-midi2play and pw-midi2record were added as aliases. - pw-cat can now upload sysex files. The pw-sysex alias was added for this. - The pw-link tool now has a -t option to list port latencies. It also has better monitor support. - pw-top can now clear the ERR column with the c key. - pw-cli now keeps the types of the variables it stores and avoid using wrongly typed variables that can crash things. It can now also list the available variables. - pw-dump can now output raw JSON and SPA JSON. - pw-dump has configurable indentation level. - pw-mididump can be forced to output MIDI 1.0 messages. - pw-profiler now uses doubles for extra precision. - pw-top now marks the async nodes with =. ## Bluetooth - Telephony improvements. - ASHA support was added. - Packet loss concealment was added. - Improved synchronisation between LE Audio streams in the same group. - Improved LE Audio device compatibility. - LC3-24kHz voice codec was added (used by Airpods) - LDAC decoding support added (requires separate decoder library) ## Pulse-server - The SUSPEND event is now correctly generated. fail-on-suspend is now implemented. - PA_STREAM_DONT_INHIBIT_AUTO_SUSPEND is now implemented. (#4255) (#4726) - RTP streams now have stream.properties for extra configuration. - Timed out streams are now destroyed instead of lingering. (#4901) - A new help and pipewire-pulse:list-modules core message was added. ## JACK - Port rename callbacks are now emitted correctly. - Use safe POD parsing for the control sequences. ## V4l2 - The wrapper now avoids a race while initializing PipeWire. (#4859) ## GStreamer - Colorimetry support was added. - Cursor metadata is now exposed as ROI metadata. - Many more updates. ## Docs - Document the client-node flow a bit more. # PipeWire 1.4.0 (2025-03-06) This is the 1.4 release that is API and ABI compatible with previous 1.2.x and 1.0.x releases. This release contains some of the bigger changes that happened since the 1.2 release last year, including: * client-rt.conf was removed, all clients now use client.conf and are given RT priority in the data threads. * UMP (aka MIDI2) support was added and is now the default format to carry MIDI1 and MIDI2 around in PipeWire. There are helper functions to convert between legacy MIDI and UMP. * The resampler can now precompute (at compile time) some common conversion filters. Delay reporting in the resampler was fixed and improved. * Bluetooth support for BAP broadcast links and support for hearing aids using ASHA was added. A new G722 codec was also added. Delay reporting and configuration in Bluetooth was improved. * The ALSA plugin now supports DSD playback when explicitly allowed with the alsa.formats property. * A PipeWire JACK control API was added. * A system service was added for pipewire-pulse. * Many documentation and translation updates. * Many of the SPA macros are converted to inline functions. All SPA inline functions are now also compiled into a libspa.so library to make it easier to access them from bindings. * The module-filter-chain graph code was moved to a separate filter-graph SPA plugin so that it becomes usable in more places. EBUR128, param_eq and dcblock plugins were added to filter-graph. The filter graph can now also use fftw for doing convolutions. The audioconvert plugin was optimized and support was added to audioconvert to insert extra filter-graphs in the processing pipeline. * New helper functions were added to parse JSON format descriptions. * The profiler now also includes the clock of the followers. * RISCV CPU support and assembler optimisations were added. * The clock used for logging timestamps can be configured now. * The JSON parser was split into core functions and helper. * Support for UCM split PCMs was added. Instead of alsa-lib splitting up PCMs, PipeWire can mark the PCMs with the correct metadata so that the session manager can use native PipeWire features to do this. * Support for webrtc2 was added to echo-cancel. * IEC958 codecs are now detected from the HDMI ELD data. * Conversion between floating point and 32 bits now preserve 25 bits of precision instead of 24 bits. * A new Telephony D-BUS API compatible with ofono was added. * The invoke queues are now more efficient and can be called from multiple threads concurrently. * Clock information in v4l2 was improved. * An ffmpeg based videoconvert plugin was added that can be used with the videoadapter. * The GStreamer elements have improved buffer pool handling and rate matching. * The combine-stream module can now also mix streams. * link-factory now checks that the port and node belong together. * The netjack-manager module has support for autoconnecting streams. * The native-protocol has support for abstract sockets. * The pulse server has support for blocking playback and capture in pulse.rules. * The corked state of stream is now reported correctly in pulse-server. * Fix backwards jumps in pulse-server. * Latency configuration support was added in loopback and raop-sink. * The ROC module has more configuration options. * The SAP module now only send updated SDP when something changed. * RTP source now has a standby mode where it idles when there is no data received. * Support for PTP clocking was added the RTP streams. * The VBAN receiver can now dynamically create streams when they are detected. * Error reporting when making links was improved. * Support for returning (canceling) a dequeued buffer in pw-stream. * Support for emiting events in pw-stream was added. * pw-cat now support stdin and stdout. ## Highlights (since the previous 1.3.83 release) - Small fixes and improvements. ## PipeWire - Fix some missing includes in metadata.h - Pass the current error in errno when a stream is in error (#4574) ## modules - Evaluate node rules before loading adapter follower to ensure properties are set correctly. (#4562) ## SPA - Avoid a use after free when building PODs. (#4445) - Take headroom into account when calculating resync. ## Bluetooth - Fix +CLCC parsing. ## GStreamer - Notify about default device changes in deviceprovider. - Copy frames between pools and avoid splitting video buffers. ## JACK - Add an option to disable the MIDI2 port flags. (#4584) # PipeWire 1.3.83 (2025-02-20) This is the third and hopefully last 1.4 release candidate that is (almost) API and (entirely) ABI compatible with previous 1.2.x and 1.0.x releases. We note that in the 1.3.x series, the API is slighty not backwards compatible because some methods previously used to accept void* as a parameter while they now require the correct type. We think this is however a good kind of API breakage and expect projects to patch their code to get things compiled with newer version (which will also compile for older versions). Note also that this is not an ABI break. ## Highlights - Handle JACK transport updates in a better way. - Fix a SAP regression when starting. - Fix regression in rate scaling. - Improve bluetooth source rate handling. - More small bugfixes and improvements. ## PipeWire - Handle JACK transport updates in a better way. (#4543) ## Modules - Check that the link factory port and nodes match. Deprecate the port.id when making links. - Improve profiler output by scaling the quantum with the node rate so that we don't end up with confusing information. (#4555) - Fix sending of the SAP SDP. Handle some SDP parsing errors. - Add some more options to the ROC source module. (#4516) ## SPA - Fix firewire quirks in udev rules. (#4528) - Fix a bug in the rate scaling in some cases that would make things run with the wrong samplerate. - Improve introspection of control types. ## Bluetooth - Use the G722 codec from Android instead of FFmpeg for ASHA. - Use the A2DP source rate as the graph rate. (#4555) - Specify the bluetooth source latency property in the rate of the stream to avoid conversions and rounding errors. # PipeWire 1.3.82 (2025-02-06) This is the second 1.4 release candidate that is API and ABI compatible with previous 1.2.x and 1.0.x releases. ## Highlights - Various pw-stream improvements: timing information fixes, avoid locking buffers in some cases and an improved drain event. - A new Telephony D-BUS API compatible with ofono. - Documentation fixes and updates. - More small fixes and improvements. ## PipeWire - Improve timing information when rate is unknown. - Avoid locked buffers in pw_stream in some cases. - Improve pw_stream drain event emission. - Improve manager socket handling. Applications can avoid hardcoding the sockets so that they will respect the config settings. ## modules - Fix header size calculation when using ipv6. (#4524) ## SPA - Optimize byteswapped s16 conversions. - Improve event handling for internal events. - Optimize negiotiation when in convert mode, prefer the format of the follower in adapter. - Fix EnumPortConfig for videoadapter without converter. - Fix libcamera property buffer size. ## Pulse-server - Add systemwide systemd files. ## JACK - Add a UMP example. - Use the new JackPortMIDI2 flag to mark UMP ports to JACK. ## Bluetooth - Support BAP hardware volume. - Add a Telephony DBUS API. ## GStreamer - Disable buffer pools for audio by default. ## Docs - Improve the module documentation. # PipeWire 1.3.81 (2025-01-23) This is the first 1.4 release candidate that is API and ABI compatible with previous 1.2.x and 1.0.x releases. In addition to all the changes backported to 1.2.x, this release also contains some new features: ## Highlights - UMP support was added with MIDI 1.0 and MIDI 2.0 support in the ALSA sequencer plugin. By default PipeWire will now use MIDI 2.0 in UMP messages to transport MIDI in the graph, with conversions to/from legacy MIDI where required. This requires UMP support in the kernel. - client-rt.conf is no longer supported. Custom changes made to this config should be moved to client.conf. Clients that try to load the client-rt.conf will emit a warning and be directed to client.conf automatically for backwards compatibility. - The module-filter-chain code was moved to a new filter-graph plugin. This made it possible to add filter-graph support directly in audioconvert. It is now possible to run up to 8 run-time swappable filter-graphs inside streams and nodes. This should make it easier to add effects to streams and device nodes. - Bluetooth support for BAP broadcast links and support for hearing aids using ASHA was added. - Many more bugfixes and improvements. ## PipeWire - Nodes are now only scheduled when ready to signal the driver. - Add slovenian translation. (#4156) - Link errors are handled better. - The videoadapter is now enabled by default but no videoconverter is loaded yet by default. - Streams now have support for ProcessLatency. - Streams now have a method to emit events. - The RequestProcess event and command can now pass around extra properties. - Local timestamps are now used for logging. - client-rt.conf is no longer supported. Custom changes made to this config should be moved to client.conf. Clients that try to load the client-rt.conf will emit a warning and be directed to cliert.conf automatically to preserve backwards compatibility. - pw_stream now has an API to return unused buffers. ## modules - module-combine-stream can now mix streams. - Links in error are now destroyed by link-factory. - The netjack2 driver can now also create streams that autoconnect when specified. (#4125) - Many updates and bugfixes to the RTP modules. - The netjack2 driver can now bind to a custom IP and port pair. (#4144) - The loopback module and module-raop have support for ProcessLatency, which can be used to query and update the latency. - The profiler module can now reduce the sampling rate. - The filter-chain was optimized some more. - The filter-chain gained some more plugins: param_eq, ebur128, dcblock. - Support for fftw based convolver was added. - Some module arguments can now be overridden. - The VBAN receiver now creates new streams per stream name. (#4400) - The RTP SAP module is now smarter with generating new SAP messages. - The RTP source can now be paused when no data is received. (#4456) ## tools - pw-cat can now stream most formats from stdin/stdout. - pw-profiler has a JSON dump option to dump the raw profiler data. - pw-cli now supports unload-module. (#4276) ## SPA - The resampler can precompute some common coeficients now at compile time. - UMP support was added with MIDI 1.0 and MIDI 2.0 support in the ALSA sequencer plugin. By default PipeWire will now use MIDI 2.0 in UMP messages to transport MIDI in the graph, with conversions to/from legacy MIDI where required. - Control types can now be negotiated. - Support for writing ALSA bind controls was added. - The ALSA sequencer now has better names for the ports. - The F32 to S32 conversion now uses 25 bits for an extra bit of precision. - libcamera controls can now be set in all cases. - The videoadapter has been improved and a dummy and ffmpeg based videoconverter plugin was added. - Negotiation was improved in audioadapter. First a passthrough format is tried. - Some JSON helper functions were added and some duplicate code removed or simplified. - Add support for RISC V CPU detection and add many optimizations in the audio converters. - Add an option to disable ALSA mixer path select. (#4311) - Fix a potential bug with the cleanup of the loop queues. - ALSA nodes now dynamically adjust the DLL bandwidth based on average measured variance. - The loop invoke queue was made more efficient and make it possible to invoke from multiple threads. - The filter-chain code was moved to a new filter-graph plugin. - Most function macros are now static inlined and can also be built into a libspa.so file. This should improve language bindings. - V4l2 clock information was improved. - Supported IEC958 codecs are now autodetected via ELD info. - Audioconvert was optimized some more. - Audioconvert can now include filter-graphs in its processing. - webrtc-audio-processing-2 is now supported in AEC. - The resampler now reports the delay and subsample delay. Also the delay is reported in the samplerate of the input. - The ALSA sequencer now handle kernels without UMP support. (#4507) ## Pulse-server - Add quirk to block clients from making record and playback streams. - The corked state is now set on stream to always report this state correctly to other clients. - Readiness notification was added to the pulse server with the PIPEWIRE_PULSE_NOTIFICATION_FD environment variable. (#4347) - The pulse.cmd config now supports conditions. - A bug in clearing the ringbuffer was fixed. (#4464) ## GStreamer - Support for the default devices was added to the deviceprovider. (#4268) - The graph clock is now used as the source for the GStreamer clock. - The sink now does some rate control. ## ALSA - The ALSA plugin now supports DSD when explicitly enabled. ## JACK - JACK now supports 2 new extension formats for OSC and UMP. - JACK clients can receive UMP MIDI1 or MIDI2 messages when using the new UMP port format extension. - JACK now reports the PipeWire version in the minor/micro/proto. - Implement more jackserver functions. ## Bluetooth - Support BAP broadcast links. - Support for ASHA was added. - Delay reporting in A2DP sources was improved. ## Examples - 2 new examples of pw-stream using spa_ringbuffer were added. ## Docs - Many updates to the man pages. - More documentation about thread safety of functions in stream and filters. (#4521) # PipeWire 1.2.7 (2024-11-26) This is a bugfix release that is API and ABI compatible with the previous 1.2.x and 1.0.x releases. ## Highlights - Backport support for lazy scheduling. - Handle the case where processing would stop when an ALSA driver is destroyed. - Add support for v4l2loopback in the v4l2 plugin. - Small bug fixes and improvements. ## PipeWire - Invalidate the proxy ID when removed. - Backport support for lazy scheduling. - Fix profiler stats for async nodes. - Fix EARLY_PROCESS again in pw-stream. (#3480) ## Modules - Fix a crasher issue when nodes are created in the wrong order in module-filter-chain. - Fix unmap bug in lv2 uri tables. - Add ratelimit to jack-tunnel xruns. - Remove hardcoded limit in filter-chain sofa plugin. - Handle the MTU size correctly in module-rtp and handle large MTUs. (#4396) - Fix JSON float parsing errors in equalizer module. (#4418) ## SPA - Fix crash in audiotestsrc when using spa-inspect (#4365). - Improve JSON float infinity checks. - Improve resampler performace a little. - Make audioconvert only output when there is something to output. - Fix regression in v4l2 port flags which would disable support for EXPBUF. - Handle the case where an ALSA driver is destroyed and the follower becomes a driver. Processing would stop. (#4401) - Add support for v4l2loopback in the v4l2 plugin. ## Pulse-server - Give a better error message when running out of fds. - Ensure positive latency reporting. ## GStreamer - Fix memory leak in deviceprovider. - Fix locking when emitting an error. ## Tools - Fix pw-dot link labels. # PipeWire 1.2.6 (2024-10-23) This is a bugfix release that is API and ABI compatible with the previous 1.2.x and 1.0.x releases. ## Highlights - The filter-chain param changes were not aggregated correctly, causing some param changes to be ignored. (#4331) - Clear the JACK io ports correctly when stopping to avoid crashes. (#4337) - Some more small fixes and improvements. ## PipeWire - Stream states are now updated based on the underlying node state. - Exported nodes now have their state change done synchronously so that the server can immediately start the driver and avoid some initial xruns. - Improve stream flush handling and improve the docs. - Don't send mix_info to destroyed ports to avoid some errors in the JACK clients. ## Modules - The filter-chain param changes were not aggregated correctly, causing some param changes to be ignored. (#4331) - The filter-chain now correctly optimizes unlinked nodes in all cases. ## SPA - ALSA PCM node properties are now no longer overwritten with card properties. (#4135) - Increase the adapter retry count to avoid xruns in some cases. (#4334) - Fix potential crash in cleanup of ALSA nodes. ## Bluetooth - Fix a crash with broadcast sinks. - Improve compatibility with Phonak hearing aids. - Don't exit when DBus goes down. ## JACK - Clear the io ports correctly when stopping to avoid crashes. (#4337) ## Docs - Backport docs from master. # PipeWire 1.0.9 (2024-10-22) This is a bugfix release that is API and ABI compatible with previous 1.0.x releases. ## Highlights - Fix an fd leak and confusion in the protocol that would cause leaks and wrong memory to be used. - Fix bug where the mixer would not be synced correctly after selecting a port, leaving the audio muted. (#4084) - Backport v4l2 systemd-logind support to avoid races when starting. (#3539 and #3960). - Other small fixed and improvements. ## PipeWire - Fix a bug where renegotiation would sometimes fail to deactivate a link. - Fix an fd leaks and confusion in the protocol. ## modules - Fix a use-after-free in the rt module when stopping a thread. ## SPA - Fix bug where the mixer would not be synced correctly after selecting a port, leaving the audio muted. (#4084) - Fix a compilation issue with empty initializers. (#4317) - Backport v4l2 systemd-logind support to avoid races when starting. (#3539 and #3960). - Fix a potential crash when cleaning ALSA nodes. ## JACK - align buffers to the max cpu alignment in order to allow more optimizations. # PipeWire 1.2.5 (2024-09-27) This is an important bugfix release that is API and ABI compatible with the previous 1.2.x and 1.0.x releases. ## Highlights - Fix an fd mismatch in the protocol in some cases that could lead to fd leaks and crashes. - Fix a bug where the mixer was not updated after setting the port, which would cause muted audio at boot or resume from suspend. - Fix a potential use-after-free in module-rt when stopping a thread. - Cached objects are now freed in the JACK API to avoid memory leaks. - Some more fixes and improvements. ## PipeWire - RequestProcess commands are now only sent after the node completes the state change to RUNNING. - More FreeBSD fixes. - Handle ACTIVE links going to < PAUSED as well. This improves renegotiation in some cases. - Fix an fd mismatch in the protocol in some cases that could lead to fd leaks and crashes. ## Modules - Many of the network modules can now also accept hostnames instead of IP addresses. - Fix a potential use-after-free in module-rt when stopping the thread. ## SPA - Support for elogind was added. - Some more errors are checked when converting JSON to POD. (#4313) - Fix a bug where the mixer was not updated after setting the port, which would cause muted audio at boot or resume from suspend. (#4084) ## JACK - The BBT transport handling was improved. Some fields were added to be able to handle the JACK semantics correctly. (#4314) - Buffers are now aligned according to the maximum CPU alignment instead of the hardcoded 16 bytes alignment. - Cached objects are now freed correctly. ## Doc - Some small doc updates. (#4272) # PipeWire 1.2.4 (2024-09-19) This is a bugfix release that is API and ABI compatible with the previous 1.2.x and 1.0.x releases. ## Highlights - Avoid a crash in cleanup of globals. (#4250) - Use systemd-logind to scan for new devices in v4l2. - Some more bugfixes and improvements. ## PipeWire - Avoid a crash in cleanup of globals. (#4250) - Improve RequestProcess dispatch. ## Tools - Improve float parsing. (#4234) ## SPA - Clear the ringbuffer when stopping in libcamera. - Use systemd-logind to scan for new devices in v4l2. (#3539, #3960) - Queue dropped first buffer in v4l2. - Unlink pcm devices when moving drivers to avoid broken pipe. ## JACK - Emit buffer_size callback in jack_activate() to improve compatibility with GStreamer. (#4260) # PipeWire 1.0.8 (2024-09-19) This is a small bugfix release that is API and ABI compatible with previous 1.0.x releases. ## Highlights - Backport support for explicit sync. - FFADO backport fixes. - Fix some races in JACK. - More small fixes and improvements. ## PipeWire - Add support for mandatory metadata and explicit sync metadata. - Fix RequestProcess again. - Include config.h to use malloc_trim() when cleaning nodes. - Avoid crash when destroying a global. (#4250) ## Modules - FFADO fixes: improve timing reporting, avoid some xruns, improve samplerate and period size handling, implement freewheeling. - Decrease memory usage of the profiler. ## Tools - Fix pw-dump metadata changes fix. (#4053) - Support large params in pw-cli. (#4166) ## SPA - Improve libcamera devices reporting to properly filter out duplicates in all cases. - Improve property reporting in v4l2. - Fix lost buffer in v4l2. ## Bluetooth - Improve compatibility with some devices. ## JACK - Fix some races when shutting down. - Fix rt-priority on the main thread when using custom thread create function. (#4099) ## ALSA - Handle format renegotiation. (#3856) # PipeWire 1.2.3 (2024-08-22) This is a bugfix release that is API and ABI compatible with the previous 1.2.x and 1.0.x releases. ## Highlights - Implement freewheeling support in the FFADO driver. Also improve buffersize and samplerate handling. - Improve some locking on spa_loop. Remove a possible deadlock when the queue was full. - Allocate more space for the libcamera devices string to properly deduplicate libcamera and v4l2 devices. - Some more bugfixes and improvements. ## PipeWire - Improve activation state changes and xrun detection some more. (#4182) - Avoid a memory leak when a link in error is destroyed. ## Modules - Improve samplerate and buffersize handling in FFADO driver so that it is possible to force a rate and buffer size. - Implement freewheeling support in the ffado driver. - Always set the server side clock.quantum-limit on nodes. This fixes a buffer size problem in Midi-bridge. (#4005) ## SPA - Improve some locking on spa_loop. Remove a possible deadlock when the queue was full. (#4114) - Allocate more space for the libcamera devices string to properly deduplicate libcamera and v4l2 devices. - Fix a potential race when enumerating v4l2 udev devices. (#3960) ## Bluetooth - Improve compatibility with some devices (Soundcore Motion 300). ## Tools - pw-cli can now handle arbitrarily large input and params. (#4166) - Avoid some compiler warnings in pw-top. # PipeWire 1.2.2 (2024-07-31) This is a bugfix release that is API and ABI compatible with the previous 1.2.x and 1.0.x releases. ## Highlights - Fix some more fallout of the async nodes rewrite. Fixes some crackling, xruns and possibly also some crashes in some cases. - Fix freewheeling timeouts in case of xruns. This fixes ardour export. - Fix event mixdown in JACK. Fixes qsynth and possibly other apps. - Some more small fixes and improvements. ## PipeWire - Add a new SPA_IO_CLOCK_FLAG_XRUN_RECOVER flag when the process function is called because of xrun recovery. - Properly stop nodes in all cases, this avoids spurious xruns and scheduling errors. (#4122) - Make sure async nodes receive an async link in all cases. Do the processing of source output ports slightly differently to make sure we don't cause latency for sources. (#4138) (#4133) - Fix some races when negotiating and starting nodes. (#4094) - Actually include the config.h header to use malloc_trim() to reduce memory usage in pulse-server. ## Modules - Avoid unloading some modules on stream errors because it is possible to recover from the error. (#4121) - Fix a (harmless) warning in module-rtp because of comparing samples and time. (#4095) ## SPA - Let the freewheel driver detect xrun recovery and handle the timeouts correctly. This fixes an issue with ardour export. - Remove the HDMI/AC3 profiles. they turn out to fail on some hardware with no way to detect this. - Signal the eventfd when the loop is full to make sure the other thread is woken up to process the queue. ## JACK - Don't check timestamps when mixing down events. The timestamps are only checked when writing new events with the public API. This fixes an issue where qsynth would not receive midi events anymore. - Fix the jack_get_time() function, it was returning nano instead of micro seconds. # PipeWire 1.2.1 (2024-07-12) This is a bugfix release that is API and ABI compatible with previous the previous 1.2.0 release and the 1.0.x releases. ## Highlights - Fix a regression in the node activation counters that would break audio when using KODI. - Fix a regression in ardour export because of mishandling of sync groups. - Fix a regression in KDE screen preview because of the new async scheduling. - Fix a regression in context.exec argument parsing that would break some existing scripts. - More small bug fixes and improvements. ## PipeWire - Fix a regression in the node activation counters that would break audio when using KODI. (#4087) - Fix a regression in ardour export because of mishandling of sync groups. (#4083) - Fix a regression in KDE screen preview because of the new async scheduling. Disable async for driver nodes. (#4092) - Slightly improve node shutdown to cause less xruns. - Fix a regression in context.exec argument parsing that would break some existing scripts. - Support custom thread create functions. ## Modules - Improve snapcast address parsing. (#4093) ## SPA - Fix multiple %f parsing in ACP for the new plug+a52 profiles. - Improve v4l2 param generation. Improve recovery when framesize or rates are unknown, support vivid. (#4063) ## JACK - Use the custom thread create function to correctly let module-rt kit manage threads so that we don't end up with priorities on the wrong threads. (#4099) ## GStreamer - Fix a crash when destroying a stream. # PipeWire 1.2.0 (2024-06-27) This is the 1.2 release that is API and ABI compatible with previous 1.1.x and 1.0.x releases. This release contains some of the bigger changes that happened since the 1.0 release last year, including: * Support for asynchronous processing has been implemented. Nodes can choose (or be forced) to be scheduled asynchronously. The graph will not wait for the output of the node to continue processing but it will use the output of the previous cycle (or silence) instead. This adds one cycle of latency but it can avoid having some nodes blocking the processing graph. Non realtime streams and filters now also use this asynchronous processing instead of their own slightly broken version. * The concept of node.sync-group was added. This groups nodes with overlapping sync-group together when one of them sets the node.sync = true. This is now used to make sure all nodes are scheduled together when JACK transport is started so that they all see the same transport time. * Config parsing errors are reported earlier and much better with line and column numbers where the parsing started to fail. * Add support for mandatory metadata when negotiating buffer parameters. This can be used to only negotiate extra buffer planes when certain metadata is negotiated. One use case is the explicit sync support that requires 2 extra fds for the timelines. * Explicit sync metadata and support was added. * Support was added for making and using multiple data-loops in the server and clients. Support for CPU affinity and priorities was added to the data-loops as well. * The log topic debug levels can now be changed at runtime with metadata. The log levels in the pulse server can be dynamically changed with a /core message. * The UCM conflicting devices patches were merged. * Add snapcast-discover module to stream to snapcast servers. * Rework how peers are linked and the counters are updated. Resume the peers when a node is unlinked and not yet processed. This should cause less occasional dropouts in the graph when reconnecting things. * Many GStreamer element updates. * Many more fixes and improvements. Enjoy the summer vacation! ## Highlights (since the previous 1.1.83 release) - Small fixes here and there. ## PipeWire - Compilation fixes after enabling -Werror=float-conversion ## Modules - The module-rtp-sap now propagates the cleanup.sec property to the rtp-source and the rtp-source now sets a property with the receiving status. - Fix for ROC 0.3, explicitly specify sender encoding. (#4070) - Some fixes to the RAOP sink module, including a format fix for 32 bit machines. ## Tools - Fix pw-cli monitoring code. ## SPA - Revert peer_enum_params again because it was not used and flawed. - Fix multichannel processing in webrtc AEC. ## GStreamer - Logging improvements. - Fix a race in the bufferpool activation. ## Bluetooth - Improvements to BAP broadcast code parsing. # PipeWire 1.1.83 (2024-06-17) This is the third and hopefully the last 1.2 release candidate that is API and ABI compatible with previous 1.0.x releases. Some last minute changes went in to clean up the node activation and scheduling that justify another pre-release. ## Highlights - Rework how peers are linked and the counters are updated. Resume the peers when a node is unlinked and not yet processed. This should cause less occasional dropouts in the graph when reconnecting things. - Improve xruns in module-ffado. - Many GStreamer element updates. - More fixes and improvements. ## PipeWire - Rework how peers are linked and the counters are updated. Resume the peers when a node is unlinked and not yet processed. This should cause less dropouts in the graph when reconnecting. (#4026) - Improve debug of xruns. - Evaluate node.rules and device.rules before loading the plugin so that extra properties can be passed to the plugin init function. ## Modules - Improve timing reporting in module-ffado some more. - Prealloc less memory in the profiler by default. - Improve xrun handling in module-ffado. ## Tools - Fix a crash in pw-link when a link fails. - Fix pw-dump update for metadata. (#4053) ## SPA - Improve handling of controls. (#4028) - Fix the string size in v4l2 to hold the device and vendor id. - Support meta_videotransform on buffers in v4l2. This can be used to signal that the buffer was rotated for example. - Add HDMI/AC3 profile to ALSA when supported. - Make it possible to disable the webrtc dependency ## GStreamer - Improve caps handling in the elements. - Set buffer duration when we can. - Post an element error when all the elements buffers are removed. (#1980) - Improve DMA_DRM caps selection. - Some refactoring work. - Improve state handling in the elements. ## JACK - Improve how links are activated. - Fix some races when freeing memory. ## Bluetooth - Support multiple BIS in the broadcast source. # PipeWire 1.1.82 (2024-05-24) This is the second 1.2 release candidate that is API and ABI compatible with previous 1.0.x releases. Not so many things needed to be fixed so this might already be the last prerelease if everything goes well... ## Highlights - Fix problem when moving nodes that could cause nodes to be scheduled wrongly and cause errors. (#4017) - Add snapcast-discover module to stream to snapcast servers. - Work around wrong kernel provided MTU for USB controllers. - Fix some spelling mistakes all over the codebase. - More small fixes and improvements. ## PipeWire - Remove the private cleanup.h header and use the public SPA version. - Fix problem when moving nodes that could cause nodes to be scheduled wrongly and cause errors. (#4017) ## Modules - Handle IPv6 in module-protocol-simple and support port allocation. - Add snapcast-discover module to stream to snapcast servers. ## Bluetooth - Work around wrong kernel provided MTU for USB controllers. # PipeWire 1.0.7 (2024-05-24) This is a small bugfix release that is API and ABI compatible with previous 1.0.x releases. ## Highlights - Fix a potential race/crash. - Fix some problems with negotiation of large integers and floats. - Fix JACK sysex MIDI event handling. - Some more smaller fixes and improvements. ## PipeWire - Fix a potential race when adding/removing a port to be scheduled. ## Modules - Fix FFADO default device handling. (#4023) ## SPA - Fix in integer overflow and float/double compare in POD. ## JACK - Copy larger MIDI events correctly. # PipeWire 1.1.81 (2024-05-16) This is the first 1.2 release candidate that is API and ABI compatible with previous 1.0.x releases. In addition to all the changes backported to 1.0.x, this release also contains some new features: ## Highlights - Support for asynchronous processing has been implemented. Nodes can choose (or be forced) to be scheduled asynchronously. The graph will not wait for the output of the node to continue processing but it will use the output of the previous cycle (or silence) instead. This adds one cycle of latency but it can avoid having some nodes blocking the processing graph. Non realtime streams and filters now also use this asynchronous processing instead of their own slightly broken version. - The concept of node.sync-group was added. This groups nodes with overlapping sync-group together when one of them sets the node.sync = true. This is now used to make sure all nodes are scheduled together when JACK transport is started so that they all see the same time. - Config parsing errors are reported earlier and much better with line and column numbers where the parsing started to fail. - Add support for mandatory metadata when negotiation buffer parameters. This can be used to only negotiate extra buffer planes when certain metadata is negotiated. One use case is the explicit sync support that requires 2 extra fds for the timelines. - Support was added for making and using multiple data-loops in the server and clients. Support for CPU affinity and priorities was added to the data-loops as well. - The log topic debug levels can now be changed at runtime with metadata. The log levels in the pulse server can be dynamically changed with a /core message. - The UCM conflicting devices patches were merged. ## PipeWire - snap support has been added. - Implement async processing. (#3509) - Support for explicit sync was added. - Config parsing errors are reported earlier and much better. - A -P option was added to provide extra properties to the context. This can be used to enable some features that use rules. - properties.rules was added to enhance properties based on some rules. This deprecates the vm.overrides. - Support was added for security-context. This makes it possible for a flatpak to request a socket with specific properties from pipewire to mount in the flatpak. The session manager can then assign permissions based on the connection properties. - Support for fixed arrays in pw_array was improved. - PipeWire server and clients can now use multiple threads to process the nodes in parallel. - device.rules and node.rules were added to update device and node properties based on rules. - device.param and node.param can now be used to configure params when devices and nodes are created. - Memory will now try to use MFD_NOEXEC_SEAL. - The driver id of a node is now placed in the properties. - A potential race was fixed when adding and removing ports to the scheduling lists. ## Modules - Priorities for the FFADO threads can be configured now. - The loopback module now has support for up and downmixing. - Extra properties can now be configured per native-connection socket. - The pulse-tunnel can now automatically reconnect when the connection is broken. - The RTP module now supports the PTP management protocol. - The RTP sender can now use a timer to send out multiple packets per quantum. - A new module was added for loading Parametric EQ. - The simple-protocol module now has per stream configurable properties and can also be used to interface with a snapcast server. - Support for local services was added to raop, rtp and pulse avahi discoverers. Support for IPv6 on local services was added to RAOP. ## SPA - Support for reporting JSON parsing errors has been added. - Some extra checks are added when iterating POD structures. - Port and profiles can now be hidden from ALSA nodes with api.acp.hidden-ports and api.acp.hidden-profiles properties. - The UCM conflicting devices patches were merged. - Profiles and Routes can now also be set by name. - Hires timestamps are now used when possible in IRQ based scheduling to get more accurate wakeup times. - udev can now be an optional dependency. - audioadapter now has an option to automatically configure its ports. - Camera rotation was added to the libcamera node. - invoke on loops can now be done from multiple threads at the same time. - Make sure we use CLOCK_MONOTONIC everywhere in the io_clock. - Vulkan bit and convert filters were added. - ALSA will now always read the HW ringbuffer pointer when followers are not on the same card. - Support for larger MIDI sysex messages was improved. Configuration of the client input and output pool was added. (#4005) ## Bluetooth - Support Google OPUS codec. - Support the LC3-SWB codec. - Support the AAC-ELD codec. - Broadcast source configuration support was added. ## pulseaudio-server - The GSettings schemas are now optionally installed. - Extensions were moved to the modules. - The log level of the pulse server can dynamically be changed with a core object message. - snap access control was added to pulse-server. - The old pacmd describe-module functionality is now implemented with a core message pipewire-pulse:describe-module. - An option was added to disable module loading and unloading. ## JACK - OSC messages can now also be placed in JACK MIDI and the translation layer will detect and tag the right PipeWire control message types. - A jack.other-connect-mode was added to limit the connections that an app can do to ports it doesn't own. - The way the transport is started and how the nodes are grouped together in the transport was improved using the new sync groups. (#3850) - Fix large MIDI messages handling. (#4005) ## ALSA - Fix format renegotiation. (#3858) - Handle period events better. (#3676) - Improve handling of the eventfd wakeups. ## GStreamer - The GStreamer elements can now negotiate and use DMABUF. ## Tools - The T flag is used in pw-top when the transport is running. - A new pw-container tool was added to start a new security context and run an application in it. - pw-dot handles properties with quotes better. Nodes are grouped with the node.link-group. - pw-link has a --wait option to wait for all links to be created. # PipeWire 1.0.6 (2024-05-09) This is a bugfix release that is API and ABI compatible with previous 1.0.x releases. ## Highlights - A bitfield race was fixed that could cause some crashes or undefined behaviour when moving nodes between drivers. - Fix to some invalid memory access in the pw-mon and pw-dump. - A regression in kodi with IEC958 formats playback was fixed. - A race in the ALSA plugin was fixed when updating the eventfd. - Improvements and fixes to module-combine-stream. - Negotiation was improved in pipewiresrc. - Some more small fixes and improvements. ## PipeWire - Context properties are now set early so that client properties can be matched with rules. - A bitfield race was fixed that could cause some crashes or undefined behaviour when moving nodes between drivers. ## Tools - Fix failure to hide properties in pw-mon. (#3997) - Fix some memleaks and a crash in pw-dump. (#4001) ## Modules - The combine-stream module now prevents resampling to avoid broken audio because of different samplerates. - Fix a potential double free in module-loopback when calculating the delay. (#3748) - The FFADO module now only starts when ports are negotiated to avoid startup races. (#3968) - The combine-stream module will now forward tags. ## SPA - Monitor volumes are now also clamped to the min/max volumes. (#3962) - V4l2 and libcamera now encodes the device ids into a JSON array. This is part of the deduplication code of devices. - A regression in kodi with IEC958 formats playback was fixed. ## Bluetooth - Improved buffer handling and queued data when stopping. ## ALSA - A race was fixed when updating the eventfd. (#3711) ## GStreamer - Handle some errors better instead of crashing. (#3994) - Fix a memleak in the stream params handling. - Negotiation was improved in pipewiresrc. # PipeWire 1.0.5 (2024-04-15) This is a bugfix release that is API and ABI compatible with previous 1.0.x releases. ## Highlights - pw_stream can now report timestamps on buffers and the expected amount of samples for the resampler. - The GStreamer element now has more correct timestamps using the new pw_stream timestamps as a fallback. - The FFADO module now handles suspend and resume better. - A regression in v4l2 was fixed when parsing malformed filters. - A potential memory/fd leak was fixed in client-node. - Many more small bugfixes and improvements. ## PipeWire - pw_stream now reports the expected resampler input or output size in the pw_time structure. (#3750) - pw_stream now also adds a time field to the buffer, which contains the time of the graph when the buffer was received in the stream. - Fix a compiler error when compiling with -Werror=shadow. (#3915) - The config parser will warn when invalid config is detected. ## Modules - The FFADO module now opens and closes when suspending. This fixes some problems when FFADO properties are changed while suspended. (#3558) - Filter-chain will now warn when invalid config is detected. - Echo-cancel will now handle manage the state of the echo-cancel plugin better, making sure run() is not called after deactivate(). - Fix some potential memory/fd leaks in client-node. ## SPA - Improve reading the bound ALSA controls. - The resampler can now also report the number of expected output samples. - The ALSA ACP device objects have some more properties like the card.id and alsa.components. (#3912) - Fix a potential string corruption when parsing JSON strings. - V4l2 now sets the latency on the port. (#3910) - alsa-udev now has an option to expose the device even if busy. (#3914) - Improve null-audio-sink channel handling. (#3931) - v4l2 will now drop the first frame because it often contains wrong timestamps or garbage. (#3910) - A regression in v4l2 was fixed where invalid/empty properties in the filter would make it error early. (#3959) ## GStreamer - The source now falls back to the new pw_buffer time for the timestamps. ## Docs - Sync with the master branch. # PipeWire 1.0.4 (2024-03-13) This is a bugfix release that is API and ABI compatible with previous 1.0.x releases. ## Highlights - Track memfd better to avoid inconsistent memory. Also make sure the mixer info is removed correctly in all cases on destroyed ports. - Correctly handle removed objects in the metadata. - Add an option to set the server and client priorities instead of using a hardcoded value of 88. - The FFADO module has been fixed. Audio and MIDI now works with the same latency as the JACK driver. This has now also been tested with a Focusrite Saffire Pro 14. - The JACK library has seen some important fixes. Some ardour crackling has been fixed when looping and multiple MIDI ports on a client should now work. - Small bugfixes and improvements. ## PipeWire - Track memfd better to avoid inconsistent memory. Also make sure the mixer info is removed correctly in all cases on destroyed ports. - Fix Props param emission again in pw_stream. (#3833) - Add MAPPABLE flag to buffer data to indicate that the fd can be mmapped directly. Use this on DMABUF from v4l2. (#3840) - Correctly handle removed object in the metadata. - FreeBSD build and compatibility fixes. - Add an option to set the server and client priorities instead of using a hardcoded value of 88. - Read config overrides in the right order. - Fix PIPEWIRE_QUANTUM rate handling in pw_stream and pw_filter. - Fix pw_context_parse_conf_section(), actually use the conf argument. - A new pw_stream_get_nsec() and pw_filter_get_nsec() function was added to get the current time of the stream/filter without having to assume a particular clock. - A new default.clock.quantum-floor property was added to configure the absolute lowest buffer-size. (#3908) ## docs - Many doc updates. ## tools - Make sure we always quit pw-cli when the server stops. (#3837) - pw-top now prints all drivers in batch mode. (#3899) ## modules - Don't destroy the client in protocol-simple on EAGAIN. - Handle IPv6 better in the RTP modules. Fix IPv6 SAP header parsing. (#3851) - The FFADO module has been fixed. Audio and MIDI now works with the same latency as the JACK driver. This has now also been tested with a Focusrite Saffire Pro 14. (#3558) ## pulse-server - Make sure the peer_name is filled to avoid protocol errors. ## SPA - Small resampler tweaks to improve stability of adaptive resampler. - Add ALSA option to control htimestamp autodisable. - Avoid some potential crashes in audioconvert when ports are removed. - Improve HDMI jack detection on some SOCs. - The audioconvert now has a monitor.passthrough option to pass the latency information on the monitor ports. (#3888) ## GStreamer - Don't use timeouts when autoconnect=false in pipewiresrc. (#3884) - pipewiresrc and pipewiresink can now be automatically selected as audio source and sink. - An invalid memory access was fixed when destroying the device provider. ## JACK - Remove properties correctly with the object id, not serial. - Improve sync with the data thread by pausing the core. Also improve handling of port io to avoid invalid buffer access. - Fix PIPEWIRE_QUANTUM rate handling. - Support multiple MIDI input ports per client. (#3901) - The output buffer size is now always correctly set. (#3892) ## ALSA - Handle errors from eventfd_create correctly. # PipeWire 1.0.3 (2024-02-02) This is a quick bugfix release that is API and ABI compatible with previous 1.0.x releases. ## Highlights - Fix ALSA version check. This should allow the alsa plugin to work again. - Some small fixes and improvements. ## PipeWire - Escape @DEFAULT_SINK@ in the conf files. ## Modules - Improve logging in module-pipe-tunnel. ## SPA - Always recheck rate matching in ALSA when moving drivers. This fixes a potential issue where the adaptive resampler would not be activated in some cases. ## ALSA - Fix version check. This should allow the alsa plugin to work again with version 1.0.2. # PipeWire 1.0.2 (2024-01-31) This is a bugfix release that is API and ABI compatible with previous 1.0.x releases. ## Highlights - Fix v4l2 enumeration with filter. This should fix negotiation in some GStreamer pipelines with capsfilter. Also probe for EXPBUF support before using it. - Fix max-latency property and Buffer param when dealing with small ALSA device buffers. This should fix stuttering with some AMD based soundcards. - More small cleanups an improvements. ## Modules - Improve netjack2 channel positions. - Improve RAOP module state after suspend/resume. (#3778) - Avoid crash in some LV2 plugins by configuring the Atom ports. (#3815) ## SPA - Bump libcamera requirements to 0.2.0. - Try to avoid unaligned load exceptions. (#3790) - Fix v4l2 enumeration with filter. (#1793) - Fix max-latency property and Buffer param when dealing with small ALSA device buffers. This should fix stuttering with some AMD based soundcards. (#3744,#3622) - Add a resync.ms option to node.driver to make it possible to resync fast to clock jumps. - Probe for EXPBUF support in v4l2 before using it. (#3821) ## pulse-server - Also emit change events when the port list change. ## Bluetooth - Log a more verbose explanation when other soundservers seem to be interfering with bluetooth. - Add quirks for Rockbox Brick. (#3786) - Add quirks for SoundCore mini2. (#2927) ## JACK - Improve check for the running state of clients. (#3794) # PipeWire 1.0.1 (2024-01-11) This is a bugfix release that is API and ABI compatible with previous 1.0.x releases. ## Highlights - Work around the buggy ALSA backend in libcanberra by forcing the pulse backend in module-x11-bell. - Fix a race in the device info updates in pulse-server. - Fix timing and rate matching in ALSA sequencer. - Improve timing information in JACK and from the ALSA driver. - More small fixes and improvements. ## PipeWire - Fix a build issue when examples where disabled. - Avoid some compiler warnings. - Avoid some bitfield data races. (#3706) ## Modules - Bump the PTP driver priority. (#3217) - Support the previous "allowed" permission in the access module. - Fix filename leak in module-filter-chain. - Work around the buggy ALSA backend in libcanberra by forcing the pulse backend in module-x11-bell. (#3688) - Fix a race in the device info updates in pulse-server. - Fix compatibility in RAOP. (#3698) ## SPA - Handle ALSA picth control errors correctly - Clamp buffer-frames correctly. (#3000) - Fix timing and rate matching in ALSA sequencer. (#3657) - Revert a commit that could result in current time in the future in the timing updates. - Improve adapter state checks. - Remove the timer from the ALSA pcm. - Fix timeout in freewheel driver. ## Pulse-server - Also handle active ports for monitor sources. - Fix zeroconf-publish format properties. ## JACK - Improve timing and transport calculations. - Handle -ENOENT from the core and don't error out. ## GStreamer - Handle node port removal in the device provider. (#3708) - Improve error handling while connecting. - Fix dts_offset. # PipeWire 1.0.0 (2023-11-26) The PipeWire project is immensely proud to announce the 1.0 release of PipeWire. It is API and ABI compatible with previous 0.3.x releases. "PipeWire represents the next evolution of audio handling for Linux, taking the best of both pro-audio (JACK) and desktop audio servers (PulseAudio) and linking them into a single, seamless, powerful new system." - Paul Davis, JACK and Ardour author "What exciting times! PipeWire 1.0 is the culmination of 15 years of Linux audio expertise, blending lessons from PulseAudio into a high-performance, flexible, and user-friendly foundation for audio and multimedia on Linux. I'm looking forward to the next decade of progress in the free software consumer and professional audio space!." - Arun Raghavan, PulseAudio developer/maintainer. "I'm thrilled to witness the first stable release of PipeWire after five years of collaboration with its remarkable community, pushing the boundaries of multimedia integration in the Linux ecosystem one step further.” - George Kiagiadakis, WirePlumber author "From the beginning of the libcamera project, we have always seen PipeWire as the solution to handle desktop and mobile integration and give a seamless multimedia integration to users while providing security features and resource sharing between applications." - Kieran Bingham, libcamera author Happy Holidays! ## Highlights - Fix a memfd/dmabuf leak when uploading buffers while shutting down. - Handle concurrent jack_port_get_buffer() calls because ardour seems to be doing this. - Improve time reporting (less jitter) in ALSA when using IRQ. - Many doc improvements. ## PipeWire - Respect PIPEWIRE_DLCLOSE everywhere, remove pw_in_valgrind(). - Remove a warning when a client tries to change ignored properties. ## Modules - Fix a memfd/dmabuf leak when uploading buffers while shutting down. - Fix a potential segfault when copying mix structures. (#3658) - Avoid races in setrlimit in module-rt. - Fix a memory leak in filter-chain. - Set rtp.ptime on senders, not receivers. - The ROC modules were ported to ROC 0.3 ## SPA - Improve time reporting (less jitter) in ALSA when using IRQ. (#3657) - Add latency param query in libcamera. - Fix some compiler warnings. - The EVL plugin was updated. ## Bluetooth - LC3 codec and compatibility improvements. ## Pulse server - Fix emission of events when a sink/source state changes. (#3660) ## JACK - Improve transport and time handling. Use unique ids to make consistent snapshots of the current time and transport. - Avoid enumerating port params that we are not going to use. - Optimize buffer reuse. - Handle concurrent jack_port_get_buffer() calls because ardour seems to be doing this. (#3632) ## Docs - Many doc improvements. - Add man pages for pw-dump, pw-loopback, modules, pipewire-pulse. - Manpages are now made with Doxygen. - Add docs for pulse-modules # PipeWire 0.3.85 (2023-11-16) This is the fifth (and last) 1.0 release candidate that is API and ABI compatible with previous 0.3.x releases. ## Highlights - Fix an issue where a link could end up paused while not negotiated. - Fix an infinite recursion issue when finding runnable nodes. - Support XDG base directories when loading ACP config. - Fix MIDI event recording preview in Ardour. - Many more small fixes, cleanups and improvements. ## PipeWire - Fix an issue where a link could end up paused while not negotiated. (#3619) - Fix an infinite recursion issue when finding runnable nodes by stopping the scan on feedback links around the driver. (#3621) - The system service now has better socket permissions. ## Modules - Add support for uclamp. This allows the scheduler to make better informed decisions about where tasks should be placed, and what pstate to set for the CPU it is running on. - Emit warnings when applications are not doing the right locking instead of crashing. - Improve media.name for RAOP sinks. (#3801) - Support pause/resume in pipe-tunnel. (#3197) - Remove time rlimit when probing for realtime to avoid SIGXCPU. ## SPA - Fix a bug where the resampler would be activated even when there is an ALSA pitch element. (#3628) - Improve resume from suspend in ALSA. (#3646) - Add option to expose ALSA controls as prop params. - Support XDG base directories when loading ACP config. This makes it possible to override the ACP config files. ## Bluetooth - Schedule nodes in the same ISO group together. - More BAP fixes and cleanups. ## JACK - Fix MIDI events from peer ports. This makes the MIDI event recording preview of Ardour work correctly. ## GStreamer - Fix some error handling in the source and sink. ## ALSA plugin - Improve poll descriptor handling. (#3648) ## Docs - Many improvements to the layout and organization. # PipeWire 0.3.84 (2023-11-02) This is the fourth 1.0 release candidate that is API and ABI compatible with previous 0.3.x releases. ## Highlights - Fix a regression with openal because the queued buffers in the stream were not reported correctly. - Fix a bug in port busy counters that could cause random silent links. - Fix a regression in echo-cancel because it was not reporting its streams as ASYNC. - Fix a JACK regression where not all ports were enumerated in all cases. - Many more fixes and improvements. ## PipeWire - pw_stream now reports the queued buffers more accurately. This fixes a regression when using openal. (#3592) - The port busy counters were not updated correctly in some cases. This could lead to negotiation errors and silent links. (#3547) - Ignore latency maximum when forcing rate/quantum. (#3613) - Nodes can now be added to multiple groups and link-groups. (#3612) ## Modules - The filter-chain now also handles notify port dependencies correctly. (#3596) - Filter-chain has support for new linear, clamp, recip, exp, log, mult, sine builtin plugins. - The echo-cancel module now correctly reports its playback and capture streams as ASYNC to avoid running out of buffers. (#3593) - It is now possible to specify an array of remote names to connect to with the native protocol. - module-rtp-sap and module-rtp-sink now try to bind to the specified interface. ## SPA - The alsa plugin now removes the runtime properties such as period-num, period-size and max-latency when suspended. (#3613) ## Bluetooth - BAP Locations/Context is now set on endpoints as required by new bluez. - Improve selection of BAP leader. ## JACK - Add a jack_set_sample_rate() extension function. - Make sure we get the info of all nodes/ports before completing the jack_client_open() operation so that we can enumerate the ports correctly in all cases. (#3618) ## GStreamer - Fix types of metadata in pipewiresink. - Also copy metadata in buffers in all cases. - Fix size allocation in bufferpool for compressed formats. - Don't stop streaming thread when unlinked. (#3620) ## ALSA - The ALSA plugin now handles NULL values from mmap_areas. (#3600) # PipeWire 0.3.83 (2023-10-19) This is the third 1.0 release candidate that is API and ABI compatible with previous 0.3.x releases. ## Highlights - A quantum change regression was fixed. - Use a 2 socket server now for the manager and the applications with (when wireplumber is updated) different permissions. - Reduce memory usage a little in audioconvert and use fewer buffers. - Some JACK deadlocks were fixed. - More bugfixes and improvements. ## PipeWire - Fix quantum change regression. (#3574) - Use a 2 socket server by default. One for the session-manager and one for applications. - Fix a potential use-after-free in node and device cleanup. (#3588) ## modules - Some hardcoded buffer size limits were removed. - Fix ASYNC flag on combined-streams. - Add support for on-demand combined-streams using metadata. ## SPA - alsa-udev will now ignore PCMs with the ACP_IGNORE udev environment variable. (#3570) - The audioadapter now uses at least 2 buffers when the follower is async. - The number of buffers used by plugins was tweaked a little. Most plugins now only ask 1 buffer. - Memory usage in audioconvert was reduced. - Fix some unaligned reads and writes and undefined left shifts reported by ASAN. (#3572) - Rework vulkan dependency checking. - Don't try to link ALSA devices when prepare fails. This fixes some crashes. - Fix a stall when the allowed codecs are changed in ALSA. - Improve ALSA rate control for sources to avoid xruns. (#3584) - Try to fix IEC958 TrueHD and DTS playback. (#2284) ## Bluetooth - Improve fallback SCO mtu when the kernel doesn't tell us. ## JACK - The fixed buffer size limit was removed. - Add an option to make input buffers writable (default true). - A potential deadlock was fixed when applications lock the process function. (#3585) - Use a separate thread to dispatch notifications to avoid deadlocks. (#3585) - Potentially fix silent export in ardour in some cases. (#3514) # PipeWire 0.3.82 (2023-10-13) This is the second 1.0 release candidate that is API and ABI compatible with previous 0.3.x releases. ## Highlights - Fix a regression in some devices when the Pro-Audio profile was selected. Only enable the IRQ based scheduling and device linking in specific safe cases. (#3556) - Improve rate switching. In some cases the graph rate would not switch correctly. (#2929) - Fix regression in alsa wakeups that would cause silence in VMs. - Fix a leak in the SBC codecs for SCO. - More improvements to the RAOP module. - Other small improvements and fixes. ## PipeWire - Improve client property checks. - Allow non-power-of-2 quantums when forced. - Improve rate switching. In some cases the graph rate would not switch correctly. (#2929) - The PIPEWIRE_QUANTUM env variable now forces the size and rate in the graph for the duration of the application. The softer PIPEWIRE_LATENCY and PIPEWIRE_RATE can still be used to merely suggest a maximum latency and a rate. ## modules - Remove the RTSP FLUSH request in RAOP because it does not seem necessary. - The RAOP module now uses the common RTP stream functions. - Add sockets option to protocol-native to make pipewire listen on multiple sockets. ## SPA - Clean up some of the log functions. - Add an option in ALSA to disable linking devices together. - Only link pcms together when 1 capture and 1 playback pcm. For more complex devices we can't be sure which ones can be linked. (#3556) - disable tsched only when using linked devices. - Add some extra checks in ALSA to avoid segfaults. (#3554) - Add Tag support to alsa-sink and alsa-source. - Use dynamic pod builder when we can. - Set priority.driver on midi-bridge to allow it as a fallback driver. (#3562) - Fix regression in alsa wakeups. (#3565) - The PTP clock can now be found from the interface in node-driver. ## pulse-server - Some small cleanups and internal improvements. - Add some memory debugging messages. - Add Tag messages to streams. ## Bluetooth - Fix a leak in the SBC codecs for SCO. ## JACK - Patch up midi events in the destination buffer instead of writing to the source buffer. (#3580) - Group all jack clients together to avoid transport issues. (#3562) ## ALSA-plugins - Add also.deny option to block alsa clients from opening the PCM. # PipeWire 0.3.81 (2023-10-06) This is the first 1.0 release candidate that is API and ABI compatible with previous 0.3.x releases. ## Highlights - jackdbus support is now enabled by default. - IRQ based scheduling in ALSA was improved and enabled by default for Pro-Audio profile. It will also link the pcms together to get lower latency. This now matches what JACK does and gives equal latency to PipeWire for Pro-Audio profiles. - Support both old and new versions of webrtc-audio-processing to make the transition easier. - Forced quantum changes by nodes or metadata will now also force a suspend and resume of the graph, like the rate changes to make sure all nodes adapt to the new quantum. This is important for Pro-Audio nodes that need to reconfigure the hardware to a new period in IRQ based scheduling. - Fix a regression in regex parsing. - Many bugfixes and improvements. ## PipeWire - jackdbus is by default enabled now. The idea is that when jackdbus is installed, the real libjack.so is in the path and we can become a real JACK client. - Forces quantum changes by nodes or metadata will now also force a suspend and resume in the graph, like the rate changes to make sure all nodes adapt to the new quantum. This is important for Pro-Audio nodes that need to reconfigure the hardware to a new period. - The stream now has an EARLY_PROCESS option that can be used to implement custom buffer fill levels. (#3480) - Fix a regression in regex parsing. (#3528) - Fix a bug in position reporting in the driver node. (#3189) (#3544) - Destroying a link will now recalculate the graph correctly. - Fix the rate comparison for finding the best rate in the graph. - Use malloc_trim() when available to release memory. (#1840) ## Tools - pw-cat now supports DFF DSD files. - pw-cli avoid some NULL derefs in some cases. ## Modules - The RAOP sink has seen some cleanups and improvements. It will now ask for feedback every 2 seconds to keep some devices alive. - A bug in filter-chain was fixed where it would fail to apply the gain when mixing just one source. - The filter-chain can now pass the stream volume to a control in the filter-chain graph. (#3434) - Improve volume handling in RAOP sink. ## Pulse-server - Some cleanup in the pending_stream handling. - Fix a regression in the event emission code where it failed to emit a changed event when a node was linked. (#3522) - Lower the realtime priority of pulseaudio clients. - Set pulse.module.id on the echo-cancel streams. (#3541) ## SPA - Support both old and new versions of webrtc-audio-processing to make the transition easier. - The ALSA driver now does the sync of all followers directly from the wakeup event. This results in more stable rate matching. - IRQ based scheduling in ALSA was improved and enabled by default for Pro-Audio profile. It will also link the pcms together to get lower latency. This now matches what JACK does and gives equal latency to PipeWire for Pro-Audio profiles. - GNU/Hurd support was added. - Some improvements to passthrough handling. ## Bluetooth - Improvements to the codec handling when PipeWire is used as Audio Gateway. - Adapt to new Bluez API for BAP devices. ## JACK - When the jack library is set in the default library path, avoid using LD_LIBRARY_PATH because this can cause confusion. - Handle clearing the latency on a port. - jack_property now always manages to actually change the metadata because it waits for a roundtrip before exiting. # PipeWire 0.3.80 (2023-09-14) This is a bugfix release that is API and ABI compatible with previous 0.3.x releases. ## Highlights - A new Tag param was added that allows arbitrary metadata to be transported out-of-band in the graph. - Vulkan DMA buf support was merged. - The echo-canceller was ported to webrtc-audio-processing-1. - Fix a regression in locating monitor sources by id in pulse-server. - Mixer io areas updates are now synchronized correctly with the data thread to avoid potential crashes. - Many more bugfixes and improvements. ## PipeWire - Handle driver nodes that refuse to change the quantum or rate. - A new Tag param was added that allows arbitrary metadata to be transported out-of-band in the graph. ## Modules - The pipe-tunnel source has been reworked to use a ringbuffer and rate adaption to keep the latency constant. It can now also function as a driver to reduce resampling. (#3478) ## Tools - pw-cat will now place media properties in Tag params. - pw-mon can now filter props and params. ## SPA - ALSA refuses to change quantum and rate when in IRQ mode. - ALSA will now be smarter in selecting the period size for batch devices and will make it depend on the samplerate. (#3444) - Vulkan DMA buf support was merged. - ALSA latency will now be reported in the time domain of the graph. - Add udev based autodetection for compress-offload devices. - The echo-canceller was ported to webrtc-audio-processing-1. - The v4l2 inotify code was rewritten to avoid a use-after-free and by using a separate watch (but same fd) for each device. (#3439) - The tag and latency handling was improved in audioadpter. - Don't use -Ofast on alpha because it can crash on denormalized values. (#3489) - The mixers now synchronize spa_io_buffers updates with the data thread to avoid crashes. - Handle NULL param updates. (#3504) ## Pulse-server - Fix a regression in locating monitor sources by id. (#3476) - Add support for use_system_clock_for_timing in module-pipe-sink. - Add support for checking module arguments. - Avoid some useless change events. ## Bluetooth - Ports are now marked as physical, which makes the bluetooth devices show up as hardware devices in Ardour and other JACK apps. (#3418) - Some fixes for LE audio support (#3479) ## JACK - Also emit unregister notify even when suppressed when creating the client. - The notify callbacks now match JACK2 behaviour more. - The mixer io areas are updated and handled safely now to avoid crashes. (#3506) # PipeWire 0.3.79 (2023-08-29) This is a quick bugfix release that is API and ABI compatible with previous 0.3.x releases. ## Highlights - Fix a regression in suspend that could cause silence. - Fix a regression in JACK port registration that could cause all kinds of JACK problems. (#3485) - Fix a typo in the neon sample conversion functions that could cause distortion. - Add BAP broadcast source and sink support. - pw-top now has a batch mode to dump the output to stdout. - Many more bugfixes and improvements. ## PipeWire - Fix a regression in shutdown where a node might not first suspend properly. This cause loss of sound in some cases. (#3378) - Failure to compile a regular expression in the config file will now be reported and ! can be used to negate the match. (#3460) - Fix a regression where some nodes might not set running in some cases. - Nodes are now suspended before the format is cleared, which might fix some crashes. ## Tools - pw-top now has a batch mode to dump the output to stdout. ## SPA - The queued samples in audioconvert are now correctly reported in the delay. (#3454) - Make it easier to add a custom profile in ACP. - Fix a typo in the neon sample conversion functions that could cause distortion. (#3463) - device.profile.pro=true is added for pro audio nodes. - An xrun counter was added to spa_io_clock to detect and track skipped data because of xruns. ## Pulse-server - Add alsa-sink and alsa-source modules. (#3456) ## Bluetooth - Fix a regression where only the BAP off profile is shown. - Add BAP broadcast source and sink support. ## JACK - Also emit a latency notify when the buffer size changes. - Fix a regression in JACK port registration. (#3485) - jack_port_tie() is now supported. ## ALSA - Improve property handling, support lists and ranges in addition to fixed values. (#3451) # PipeWire 0.3.78 (2023-08-22) This is a small bugfix release that is API and ABI compatible with previous 0.3.x releases. ## Highlights - An old regression was fixed with where some nodes would not run. - A regression was fixed where removed events would not be shown in some cases. This would result in duplicate entries in audio clients. - Fix an off-by-one in the vban audio receiver. Tweak the rate adaption a little. - ACP will now set a UCM verb before probing the pro-audio devices. - More bugfixes and improvements. ## PipeWire - An old regression was fixed with where some nodes would not run. (#3405) - Suspend was improved a little to avoid races when the session manager would suspend right when a driver was starting. ## Modules - module-rtp-sap does not use the deprecated inet_aton anymore. - Fix an off-by-one in the vban audio receiver. Tweak the rate adaption a little. (#3380) ## SPA - ACP will now set a UCM verb before probing the pro-audio devices. (#3407) - The mandatory flag will be set now on the video modifiers. - EVL was updated to Xenomai4 r46 and xbuf creation was improved. - An option was added to force colors in the log even when logging to !tty. - The return type of spa_pod_builder_control() was fixed. - inotify errors are handled better now. (#3439) ## pulse-server - A regression was fixed where removed events would not be shown in some cases. (#3414) ## Bluetooth - Improve compatibility with more devices, avoid reusing the same transport for different media-sink instances to avoid encoder resets. - Improve enumeration of codec profiles for BAP and A2DP. ## JACK - Ensure we can't iterate ports from a deactivated client. Also make sure the JACK clients with the node.always-process=false always show their ports. (#3416) ## GStreamer - A potential crash was fixed in the device provider when stopping. # PipeWire 0.3.77 (2023-08-04) This is a quick bugfix release that is API and ABI compatible with previous 0.3.x releases. ## Highlights - Fix a bug in ALSA source where the available number of samples was miscaluclated and resulted in xruns in some cases. - A new L permission was added to make it possible to force a link between nodes even when the nodes can't see each other. - The VBAN module now supports midi send and receive as well. - Many cleanups and small fixes. ## PipeWire - Global objects now only show permissions that apply to them. The permissions required to perform various API calls are documented. - A new L permission was added to make it possible to force a link between nodes even when the nodes can't see each other. - Config files need to end with .conf. - The client.api is added the to global properties of a node. ## modules - The VBAN module now supports midi send and receive as well. - Fix module-profiler alignment and make sure we don't overrun our buffers with many nodes. - Protect libcanberra calls with a mutex because it is not thread safe. (#2834) ## SPA - Support older compilers for spa_clear_ptr(). - Fix a bug in ALSA source where the available number of samples was miscaluclated and resulted in xruns. (#3395) - Don't set inotify on /dev but on the videoX devices directly. Setting inotify on /dev would cause a lot of spurious wakeups and lock contention in the fsnotify subsystem on some benchmarks. - Audioconvert now rate limits the warnings when it runs out of buffers. (#3384) ## pulse-server - Some bugs and inconsistencies were fixed in device lookup. - Improve subscribe event emission, detect changes to the sink or the monitor and send the right sink/source event. (#3388) ## JACK - The libjack.so now has a minor version of 3 and a micro version of the pipewire version. - JACK clients will now see portregistration from other jack clients when they activate/deactivate like real JACK. (#3260) ## bluetooth - Use some more autoptr cleanups, fix some leaks. # PipeWire 0.3.76 (2023-07-28) This is a quick bugfix release that is API and ABI compatible with previous 0.3.x releases. ## Highlights - Fix a regression that would cause the MPV pipewire backend to fail because of a spurious thread-loop signal. - Fix a crash when DBus is not found. - ALSA hires timestamps are now disabled by default. - Some more fixes and improvements. ## PipeWire - A new option was added to pw-thread-loop to signal when the thread starts. This is only used in module-rt to avoid regressions in mpv. (#3374) - Fix a compilation problem. - Stream flags now only set the properties when not already set. This fixes a regression with node autoconnect. (#3382) ## Tools - pw-cat will now stop when the stream is disconnected. (#2731) - Improve the pw-cat man page, mention that stdin/stdout handling is only on raw data. ## modules - module-rt will now not crash when dbus is not available but error out as before. - A new VBAN (vb-audio.com) sender and receiver was added. (#3380) ## SPA - Add an option in audioconvert to disable volume updates. (#3361) - ALSA hires timestamps are disabled by default because many drivers seem to give wrong timestamps and cause extra delay. ## bluetooth - LE Audio support is now enabled by default when liblc3 is available now that bluez has support for detecting the hardware features. # PipeWire 0.3.75 (2023-07-21) This is a bugfix release that is API and ABI compatible with previous 0.3.x releases. ## Highlights - Link permissions between nodes are now enforced. This avoids potential portal managed screencast nodes to link to the camera even though it was not assigned permissions to do so by the session manager. - Libcamera and v4l2 devices now have properties so that duplicates can be filtered out by the session manager. - A bug with draining was fixed where a buffer would be marked EMPTY and would not play when it contained drained samples. (#3365) - Many fixes and improvements. ## PipeWire - Permissions for links between nodes are now enforced. The link will now check that the owner clients of the nodes can see each other before allowing the link. This avoids screensharing clients to accidentally being linked to the camera nodes by the session manager. A side effect is that patchbay tools will no longer be able to link portal managed screencast nodes to the camera, for this we need a new permission for those patchbay clients. (wireplumber#218) - The stream.rules/filter.rules are now evaluated when connecting the stream/filter so that more properties can be matched. (#3355) - Move some internal events from the context to the nodes to better handle per-node threads in the future. - The thread-loop will now signal when the thread is started. ## modules - A timestamp workaround in module-raop was reverted because it does not work in all cases. Instead latency was increased to 1.5 seconds, which also makes the problematic device in question work. (#3247) - The profiler module was reworked a bit to use the new node realtime events. It should now also handle dynamically added and removed drivers. - The module-rt now does the rtkit calls from a separate thread so that it does not block the main thread. This could cause deadlocks during startup in some cases. (#3357) ## SPA - Atomic operation macros were move from internal pipewire API to public API. - The video-info structure now has a new SPA_VIDEO_FLAG_MODIFIER_FIXATION_REQUIRED flag to instruct the application to fixate the modifiers. This simplifies some logic in applications a lot. - The libcamera and v4l2 nodes now have properties to enumerate the device id they are using. This can be used to match v4l2 devices and libcamera devices and filter out duplicates. - A bug with draining was fixed where a buffer would be marked EMPTY and would not play when it contained drained samples. (#3365) # PipeWire 0.3.74 (2023-07-12) This is a quick bugfix release that is API and ABI compatible with previous 0.3.x releases. ## Highlights - Fix a critical bug where audio to bluetooth devices would cut out randomly. (#3316) - Improve RAOP compatibility. - Avoid crashes after an update. - Small fixes and improvements. ## PipeWire - Mix info on port is now created explicitly. - Remove the node as a driver peer when stopping. This caused some problem with playback on and other remote bluetooth devices. (#3316) - Work on avoiding crashes when loading new modules that use internal API with old libpipewire. This is typical after an update where the old library is still loaded by an application but when a new stream is created, updated modules are loaded. (#3243) ## Modules - The RTP source module now has an option to ignore the SSRC, which is useful to continue to receive the stream when the sender is restarted. - The native protocol will refuse to load twice now instead of silently ignoring the error. - module-raop is compatible with more devices. (#3247) ## SPA - plugins will now warn when running out of buffers. This is always a bad thing. - Merge scope based cleanup macros. - Add ratelimit function. # PipeWire 0.3.73 (2023-07-06) This is a bugfix release that is API and ABI compatible with previous 0.3.x releases. ## Highlights - Fixes an ALSA resume after suspend error. - Handle and disable seemingly wrong hires timestamps from ALSA. - Filter-chain now has loadable plugin modules. The LV2 and sofa plugins are moved to a separate .so file to make things more modular. - Rate changes in the graph should now be handled more gracefully by loopback and filter-chain. - A regression in the rtp-sap module was fixed where it would in some cases fail to start. - A potential crash in the peaks resampler was fixed. - Many cleanups and other small bug fixes. ## PipeWire - Fix a potential segfault when no fallback driver was set in the config. - Improve OPUS detection. - Add ASYNC flag to pw-filter and pw-stream when queue/dequeue is not called from the process function. This ensure we allocate an extra buffer. - Discard pending process callbacks when disconnecting. (#3314) - Cleanups and improvements to the debug environment variable parsing. - The graph rate was tweaked to better handle very low rates such as those requested by pavucontrol when it does the signal monitoring. ## Modules - An example filter module was added. - Filter-chain and loopback now disable the resamplers if no rate is specified and will always follow the graph rate. - Improve setup of filter-chain. The graph is now created when starting because this ensure the target graph rate is known. - Filter-chain can now link notify ports to control ports in the graph. - Filter-chain now has loadable plugin modules. The LV2 and sofa plugins are moved to a separate .so file. - A regression in the rtp-sap module was fixed where it would in some cases fail to start. - Module-rt now has options to disable rlimits, portal and rtkit. - module-raop-discover now has an options to set the latency. (#3247) ## Tools - pw-cat now supports overriding all stream properties. ## SPA - Disable rate negotiation when the resampler is disabled. We will always follow the graph rate. - Set device.icon property for UCM ports as well. - Improve ALSA recover when using hires timestamps. This fixes some problems after resume from suspend. (#3315) - ALSA will now warn and disable hires timestamp when they seem wrong. They can also be disabled manually with a property. - V4l2 will now gracefully handle ENOTTY when enumerating frame sizes and frame rates. (#3325) - A potential crash in the peaks resampler was fixed. (#3320) ## pulse-server - A client crash in pavucontrol is avoided by always setting a card name. - The graph rate is now taken correctly when using the FIX flags. (#3317) - An option was added to ignore the FIX flags of a stream. Also the documentation for those options was updated. (#3317) - module-raop-discover now support latency_msec. (#3247) ## Bluetooth - Remove an assert and issue a warning/recover instead when a buffer is too small. ## GStreamer - The device provider does locking when destroying the registry. # PipeWire 0.3.72 (2023-06-26) This is a bugfix release that is API and ABI compatible with previous 0.3.x releases. ## Highlights - Fix a critical bug that would refuse to update the samplerate or buffersize in JACK clients. (#3226) - A new module-netjack2-driver and module-netjack2-manager were added that are compatible with NETJACK2. This allows PipeWire to become a NETJACK2 manager or a driver between JACK2 or PipeWire servers. - Support was added for firewire devices with FFADO. This is untested for now and MIDI is not implemented yet. - The node scheduling was optimized some more. External drivers are now as efficient as in-server ones. This should improve performance of various drivers such as bluetooth and JACK based drivers. - Many, many bug fixes and a ton of improvements. ## PipeWire - pw-filter can now be used to write sinks and sources. - The node activation for drivers was changed. The driver now does not need to go to the server to start the processing cycle. This makes out-of-server drivers as efficient as in-server drivers. - Don't try to use drivers with 0 priority as fallback drivers. This avoids making the screencast driver a driver for audio. (#3219) - Improve xrun count reporting in pw-top and the profiler. Now each node has their own xrun counter updated when it fails to complete processing during the cycle. - pw-filter now also has support for TRIGGER. - A potential fd leak was found when fds were send to a zombie client. (#1840) - Fix a bug where monitor or capture streams were logged twice in the profiler. (#3278) - Remove stream hooks safely. (#3251) - A bug in serialization of container properties was fixed. This could result in truncated property values. (#3290) - The PIPEWIRE_AUTOCONNECT environment variable now always overrides the autoconnect settings of streams. (#3299) - Node, port and link destroy now avoids some useless work. - Port will now try to renegotiate a new format when idle. (#3266) ## Modules - The module-sap now is more compatible with AES67. - A new FFADO driver module was added. This is completely untested because of lack of hardware. Please test and report issues. - A new NETJACK2 driver and a NETJACK2 manager module were added. These should be drop in replacements for the JACK2 parts. - The RAOP discover module now tries harder to only list devices once. - The zeroconf discover module now tries harder to only list devices once. - The RAOP sink module now handles latency better and is compatible with some more devices. (#3247, #3282) - The loopback and filter-chain modules now always dequeue the last input buffer to avoid stuttering in some cases. (#3276) - The SPA node factory module can now also export nodes. This is used to export the PTP clock from the AES67 config file. - A bug in module-jack-tunnel was fixed that would cause stuttering and corrupted output in some cases. (#3255) - The resampler is now disabled in module-loopback and filter-chain when the samplerate is set to follow the graph rate. (#2969) - The way the mixer peer is sent to clients was improved. It is now also possible to let a remote node know about mixer port removes, which can avoid memory leaks and some code simplifications. ## SPA - Monitor ports now report latency correctly. - The ALSA plugin now uses htimestamp to get a more accurate ringbuffer position to estimate the clock skew. - The channelmixer now has min/max-volume settings to limit or fix the volume. - The ALSA plugin can now control the playback and capture rate of USB gadgets. This can avoid resampling and instead use the USB feedback to control the rate. - The ALSA output to multiple devices has been improved, some lockups are avoided when the device ringbuffer is full. - The compress-offload sink has improved negotiation. ## pulse-server - Only try to use GSettings when the schema exists. - @DEFAULT_SOURCE@, @DEFAULT_SINK@ and @DEFAULT_MONITOR@ are now correctly handled as targets in playback and capture streams. (#3284) - 2 new quirks are added to disable volume updates on sinks/sources. (#1517) - The virtual-sink and virtual-source modules were added. These are really example modules but actually also work and are useful on PulseAudio so implement them as well. - Fix initial stream volumes. (#3306) ## Bluetooth - Only register A2DP or BAP when we have codecs. - Include codec into the media.name ## JACK - Fix a critical bug that would refuse to update the samplerate or buffersize. (#3226) - Improve updates of samplerate/buffersize, delay the updates until the client is activated. (#3297) - Use the new mix-info updates to simplify the mixer setup and peer detection. ## GStreamer - Fill default strides instead of 0 on pipewire video buffers. (#3236) # PipeWire 0.3.71 (2023-05-17) This is a bugfix release that is API and ABI compatible with previous 0.3.x releases. ## Highlights - A new zero-latency jackdbus bridge was added. This works similar to what PulseAudio has to offer and creates a sink/source when jackdbus is started. It is however much more efficient and runs the complete PipeWire graph as a synchronous JACK client with no added latency. - Many performance improvements. Activation of remote nodes is more efficient, fewer eventfds are required on the clients, less callback overhead in performance critical paths and an optimized poll function was added. This was mainly driven by the jackdbus module to get the lowest possible overhead when running the graph. - The JACK notify callback implementation was reworked to emulate better what JACK does, improving compatibility with ardour7 and the JACK stress test. - More work on BAP devices. Device latency is now passed on to applications also for multi-device headsets, and channel allocation is handled better. - Many more improvements and bugfixes. ## PipeWire - Remove the hardcoded limit on io_areas. This is used to link nodes together and exchange buffers, it was limited to 2048 but now dynamically scales based on requirements. - Rate and quantum changes are now applied correctly in more cases. (#3159) - Updates to client-node to more efficiently process the driver. - The profiler information was improved to be more accurate. It should now work better for remote drivers. - Some potential memory map errors were fixed in the protocol because in some case with large messages, some fds were closed too soon. - pw-filter now implements the pw_filter_set_active() method. - A potential out-of-buffers case was fixed in capture pw-streams where buffers were not moved to the recycle queue when the node suspended. - Nodes are now always woken up with the eventfd. Previously there were some optimizations in the server to directly call into the node process function but that optimization is not necessary. Without this optimization it is now possible to run nodes in different threads. - pw-stream trigger is now implemented correctly in all cases. - Remote nodes now use one eventfd less because they get triggered with the node eventfd directly. - Monitor ports are now ignored in latency updates. - A potential race when reporting an error to a client was fixed. (#3192) - Fix a bug where always_process nodes would sometimes IDLE. (#3189) - Optimize peer activation. Nodes are now activated more efficiently and independent of the number of links. It also reduces the number of eventfds and memory in remote clients. - A bug in property serialization was fixed. Values with spaces would only serialize the first part of the value. ## Modules - Correctly handle the echo-canceler plugin init method fallback. The samplerate was not correctly configured. This is only a regression for people that have external echo-canceler plugins. - RAOP sink now only sets the volume on the remote end when the stream is recording. (#3175) - RAOP discover now tries to deduplicate entries from the same host. - A new zero-latency jackdbus bridge was added. This works similar to what pulseaudio has to offer and creates a sink/source when jackdbus is started. It is however much more efficient and runs the complete PipeWire graph as a synchronous JACK client. - The access module uses a more secure way to check the application executable. - module-combine-stream now has configurable delay and latency for each stream. This can be used to align sinks/sources with different latencies. - A potential crash in module-pulse-tunnel was fixed when shutting down. (#3199) - Module-rt will now clamp the nice value to the min allowed value to avoid errors from rtkit. (#3186) - Fix a bug with the session counters in module-rtp-sap. Also use the right format for L24. Improve the AES67 example config. - Improve some warning and info messages in module-rt. (#3194) - module-rtp-session should now do something when started without arguments. - A potential crash in module-rtp-session was fixed. (#3217) - module-filter-chain has better error reporting when a convolver fails to load. (#3223) ## SPA - Move some things around to avoid compiler warnings. (#3171) - Increase mixer ports. Reorganize some things and bump mixer input ports from 128 to 512. - Fix a potential crash when a node is scheduled before it completes the setup. - The JACK sink and source SPA plugins have seen some improvements. - Allow the peaks resampler still if we disabled resampling. - Perform more cleanup in audioadapter when in error. - An optimized non-cancellable loop implementation was added. - Callbacks were optimized with a _fast() varsion that doesn't check the version and method. When this check is performed earlier, it can be skipped in performance critical places. - Some of the callbacks and system methods are now using the fast function calls in critical paths. - A potential division by zero was fixed in the ALSA plugins. - Improve rate and quantum when starting audioconvert. - Make it possible to override node.driver in the SPA null-audio-driver. (#3220) ## pulse-server - The audio info parameter parsing was refactored and improved. - Fix some races with clients exiting when playing samples. - An option was added to change or disable the dbus name registration. (#2987) ## Bluetooth - Implement battery reporting using AT+XEVENT. - Disable hardware volume for 3M WorkTunes. - Implement BAP audio locations (channel positions) by using the new bluez properties. ## JACK - Fix some errors reported by JACK test.cpp. (#2638) - Add jack.show-midi option to show/hide midi ports. - Add jack.max-client-ports option. JACK also has a port limit and so PipeWire needs it as well to make the tests happy. - Call the shutdown callback only when the server stopped, not when there is a random error. (#3070) - Avoid registering the same port name twice. - Call port registration callbacks in activate/deactivate. - Improve jack_port_connected(). - Improve some error reporting. - The JACK headers were updated to a newer version. - JACK callbacks are now managed with an event queue to simulate more what JACK does. This avoids emitting callbacks when a method is blocking for a reply and causing deadlocks. (#3183) - Assign unique names to JACK clients. (#2833) - Fix a potential crash when the thread_utils was used after free. - Aliases are now not filled in by default to improve JACK compatibility. (#3154) # ALSA - The ALSA plugin will now wait for negotiation to complete or an error before _prepare() completes. This makes more applications deal correctly with the potential errors. # Docs - A new document about how scheduling is implemented was added. - Update the pw-cli man page. (#2988) - Document the SPA Pod serialization. - Document the PipeWire native protocol. # PipeWire 0.3.70 (2023-04-20) This is a quick bugfix release that is API and ABI compatible with previous 0.3.x releases. ## Highlights - Fix a regression in the scheduler that could keep some nodes IDLE. - Fix a regression in the biquad filters in filter-chain. - Fix a regression and potential crash in the ALSA mixer probing. - Fix a regression in pipewiresrc with timestamps that could cause cheese to record video with wrong timestamps. - Beamforming support was enabled in the echo-canceler. - pulse-tunnel and raop-sink will now proxy local volume changes to the remote end. - More bugfixes and improvements. ## PipeWire - Fix a bug in the graph scheduler where some nodes might stay IDLE in some cases (like when connecting the source of the echo-canceler to the sink). - pw-metadata can now be created from the factory with initial values for the metadata. (#3076) - Conditions were added to the pipewire config file to make it possible to configure the access module and the exec sections. - Support was added in pw-stream to intercept and override properties for the adapter. This can be used to implement custom volume control, for example. ## Tools - pw-metadata can now list all available metadata objects with the -l option. - A new pw-config tool was added to debug configuration file loading and parsing. ## Modules - The webrtc echo canceler now supports beamforming. You can provide the coordinates of the microphones and let webrtc perform beamforming on the captured samples to improve quality and remove noise. - Fix a regression in the filter-chain with biquad filters. (#3161) and improve error reporting. - The pulse-tunnel will now proxy the volume changes to the remote end. - The RAOP sink will now send volume parameters to control the volume remotely. (#2061) ## SPA - One ALSA commit was not correctly reverted and might cause crashes. - The ALSA sink and source now calculate the ALSA ringbuffer memory location more correctly which might improve compatibility with some hardware. - v4l2 now sets the values of the controls in the Props param. ## Pulse-server - The echo-canceler aec_args are now parsed like they would be under pulseaudio. ## Bluetooth - More work on synchronizing BAP devices. ## GStreamer - The GStreamer source can now renegotiate the format when it changes. - The GStreamer source now uses the BaseSrc clocking code to implement the clock and timing code. # PipeWire 0.3.69 (2023-04-13) This is a quick bugfix release that is API and ABI compatible with previous 0.3.x releases. ## Highlights - Reverted the UCM changes, they seem to cause regressions causing audio to be muted in some cases. - Fix a regression in the scheduler where a driver node might not be marked runnable in some cases, like when echo-cancel is used. (#3145) - Handle links from the driver to itself. This makes the midi bridge work again. (#3153) - ALSA rate matching for sources was fixed. It would previously wait too long for rate matching and then cause drift. This should reduce crackling and stuttering when capturing in low latency. - Fix the GStreamer clock to make cheese video recording work again. (#3149) - More fixes and improvements. ## PipeWire - Fix a regression in the scheduler where a driver node might not be marked runnable in some cases, like when echo-cancel is used. (#3145) - Handle links from the driver to itself. This makes the midi bridge work again. (#3153) - Some man pages were improved. - Fix a potential crash when thread-loop is destroyed before the loop. (#3150) ## Modules - A new raw biquad filter was added to filter-chain. You can manually set the 6 parameters and you can use this to create custom filters per sample rate. (#3139) - The echo-canceler now supports different channels for the capture and playback streams. ## SPA - A SB Audigy specific profile set was added to make better use of the controls. (#2934) - More ALSA IRQ based scheduling improvements. - ALSA rate matching for sources was fixed. It would previously wait too long for rate matching and then cause drift. This should reduce crackling and stuttering when capturing in low latency. - The echo-cancel plugin API has a new method to make it possible to have different channels for capture, source and playback. - Reverted the UCM changes, they seem to cause regressions causing audio to be muted in some cases. ## Bluetooth - Many more BAP fixes and improvements. Devices are now created as a set and can be combined into one device by the session manager. ## GStreamer - Fix the GStreamer clock to make cheese video recording work again. (#3149) # PipeWire 0.3.68 (2023-04-06) This is a bugfix release that is API and ABI compatible with previous 0.3.x releases. This release contains a huge number of changes, some of which might cause regressions. Please report anything that seems to fail after the upgrade. UCM devices in particular might have changed names, profiles and ports that might require changes in custom scripts. ## Highlights - Symbolic links to the pipewire binary are now used instead of recompiling the same binary multiple times. - Changes to the graph scheduler related to quantum/rate updates and calculation of the node states. Things should start and switch between quantums and rates more smoothly now and especially virtual devices should now only run when required. - A new RTP session module was added. This uses the Apple MIDI protocol to configure low-latency bidirectional MIDI (and with a PipeWire specific extension, also audio) between machines. OPUS encoding was added to the RTP formats. The SAP module was separated from the rtp-sink/source module to make it more usable. - A new runtime debug property was added to all streams and nodes to trigger a save of the raw samples to a wav file. Support for this has also been added to the echo-canceler to debug potential issues. - Module pulse-tunnel has improved rate matching and synchronization support. It should also not drift anymore for capture devices. - The link-factory now ignores by default the link.passive property. This means that tools like pw-link or jack clients and wireplumber can't make passive links anymore. The reason is that there is now much more advanced logic in PipeWire itself to handle passive links based on node and port properties. - The RAOP sink was ported to new OpenSSL functions. Digest passwords are handled correctly now and support for more devices was added. - The ACP code was updated with new PulseAudio UCM code: "Create multiple profiles per verb for conflicting devices". This might change the names of devices, profiles and ports so scripts might need to be updated. - Upmixing is disabled again by default. We now ship config files that distros can install to enable upmixing again. The reason being that PipeWire should not apply fancy DSP processing to audio by default. - Many cleanups and bugfixes, including some crashes and memory corruption bugs. ## PipeWire - Various FreeBSD compilation fixes. - Don't crash when calling _connect twice in stream/filter. (#3091) - Links are now installed instead of compiling the pipewire binary multiple times. - There is now a new core event bound_props that augments the bound_id event with the global properties. This can be used to get the global.serial among other global properties. It also makes it possible in the future to let the server allocate unique names or uuids. - Fix a bug where the server could go into an infinite reconfigure loop when the samplerate of a driver would change. - When a samplerate was forced, restore the previous best samplerate when the samplerate is no longer forced. (#2133) - Rework how the states of the nodes in the graph are calculated. A more refined algorithm is now used that only runs nodes that need to run. - Rework how the quantum change is applied to the graph. Drivers are now responsible for using the new updated rate/quantum before starting a new cycle. This avoids starting a cycle with an old quantum first. - pw-stream and pw-filter will now ensure that the Trigger event is called from the main thread. - node.force-rate=0 will now force the node.rate on the graph, forcefully switching the hardware into the new rate if possible. (#3026) - Additional checks were added to the thread-loop to check locking order. - Additional checks were added to pw-stream and pw-filter to check if methods are called from the right thread context. ## modules - A new RTP session module was added. This uses the Apple MIDI protocol to configure bidirectional MIDI (or audio) between machines. - SAP support was removed from module-rtp-source and module-rtp-sink and moved to a separate module. This makes it possible to use the RTP modules without SAP support as well. - The echo-cancel module now has support to save the signals to a wav file for debugging purposes. - The RTP modules now have support for the OPUS codec. - The RAOP module was ported to new openssl encryption functions and handles digest passwords correctly now. - module-raop-discover now has match rules to be able to select the streams and set properties. - Module pulse-tunnel has improved rate matching and synchronization support. (#3093) - Fix potential memory corruption and infinite loops because module-pulse-tunnel was unloaded from the wrong thread. - The link-factory now ignores by default the link.passive property. This means that tools like pw-link or jack clients and wireplumber can't make passive links anymore. The reason is that there is now much more advanced logic in PipeWire itself to handle passive links based on node and port properties. - module-echo-cancel will now clear its buffers after a suspend to avoid playing stray samples. - module-raop-sink will now handle 0 timing_port replies. (#3133) ## SPA - The adapter module now has support for saving the raw audio to a wav file for debugging purposes. - The ACP code was updated with new PulseAudio UCM code: "Create multiple profiles per verb for conflicting devices". This might change the names of devices, profiles and ports so scripts might need to be updated. - Upmixing was disabled again by default. We now ship config files that distros can install to enable upmixing again. (#3081) - audioadapter and audioconvert have seen improvements in the experimental non-DSP/passthrough mode. - Fix a potential race where the dummy drivers could fail to stop a timer and cause endless warnings in the logs. - The ALSA plugin has experimental support for IRQ based scheduling. This should decrease latency for some (mostly USB) drivers. This should bring latency within JACK latency. More work on this will be done before the 1.0 release later this year. - Audioconvert now has support for volume ramping. (#3046) - A new loop method was added the check if a thread is currently running the loop. - channelmix.disable and resample.disable now generate an error when true and channelmixing or resampling is required in the converter. ## Bluetooth - Fix a crash in some cases when a device was disconnected. - Support async transport state changes. This avoids some lockups when the bluetooth backend is having issues. (#3023) - Align BAP sinks. This improves synchronization between earpieces. ## ALSA - Improve properties in pw-top and pavucontrol. ## pulse-server - Improve error handling from pulse-tunnel. - Generate silence correctly for unsigned formats as well. - Review buffer params. The streams should now just work with 1 or 2 buffers. - module-rtp-send and module-rtp-recv now have support for the OPUS codec. # JACK - Make sure we don't call any callbacks anymore when deactivating. (#2781) ## GStreamer - Sort the device by priority in deviceprovider. (#3072) # PipeWire 0.3.67 (2023-03-09) This is a bugfix release that is API and ABI compatible with previous 0.3.x releases. ## Highlights - The loopback module and other couples streams will now not randomly fail in some cases. (#3028) - The RTP module now has support for sending and receiving MIDI as well. - The compress offload sink has seen many improvements. It now uses ioctls directly to bypass limitations of tinycompress (to be able to detect the available codecs, for example). - Pulse server compatibility was improved for some apps by improving the format parsing and FIX_ stream flag handling. - The min quantum in the pulse server was changed from 256/48000 to 128/48000 to fix some issues with games that expect 5ms or less of latency. - The Bluetooth plugin has seen many improvements in packet scheduling to attempt to reduce stuttering on some devices. - The ALSA plugin now handles some impossible cases better. This fixes recording in QEMU again. (#2971) ## PipeWire - SPDX tags were added to the code for copyright information. - The random number functions were made more usable. - The port property code was moved from the adapter to the port implementation itself to make it more useful and unified for the cases where no adapter is used (midi and video). - Fix a potential overflow in mixer areas. - Improve runnable state calculations of nodes. This is part of ongoing work to avoid running nodes that should not need to run. - The stream will now always call the process function when using trigger, even if there are no buffers. This avoids stalls of the processing graph in some cases. (#3028) - Links are now marked as passive by PipeWire itself so that the right thing happens in all cases. - Implement the in/out/true values for the node.passive property. Place a passive state on ports to make passive links on a port by port basis. ## Tools - pw-cat has seen improvements in the encoded file playback case. ## Modules - The rtp module has support for MIDI now. - DSCP is now configurable in the RTP module. - The loopback module doesn't randomly fail to work anymore. (#3028) ## SPA - The null-audio sink can now be given a format and it will return this instead of the default float ones. This makes it possible to make a null-sink that has a given format. - The compress offload sink has seen many improvements. It now no longer uses tinycompress to be able to detect the available codecs. - The ALSA plugin now handles some impossible cases better. (#2971) - Fix compilation on older compilers. (#3050) ## Pulse-server - The FIX_ flags are now implemented more correctly by fixating the stream to the format of the sink/source they ask to be connected to. There is now also an option to override the fixation based on rules. - Format parsing was improved and should now support all format strings supported by pulseaudio including upper and lower case variants and shortcuts. - Channelmap parsing was improved and should now reject invalid channelmaps as well as support the shortcuts supported by pulseaudio. - Escape codes in module arguments now work as it does in pulseaudio. (#3071) - The min quantum was changed from 256/48000 to 128/48000 to fix some issues with games that expect 5ms or less of latency. ## JACK - jack.passive-links can now be used to have a JACK client make passive links and the node.passive property is no longer used for this because it has a different function. - The qsynth rule was updated to the new node.passive features. It is now only passive on the output side. ## Bluetooth - BAP delay and transport latency are handled now. - A2DP and SCO can now use bigger buffers to improve quality when the reception is jittery. - The AT+BCC command is now implemented. - Packet encoding now happens ahead of time when possible to avoid delays before sending it. - Source should now always produce complete (padded) buffers to avoid sync problems. - Don't set unnecessary socket options. ## GStreamer - The pipewiresrc now has an autoconnect argument. - The metadata plane count is now handled correctly in more cases. - Stream errors are now handled correctly to stop the GStreamer elements. # PipeWire 0.3.66 (2023-02-16) This is a bugfix release that is API and ABI compatible with previous 0.3.x releases. ## Highlights - Fix a regression in the pulseaudio module-combine-stream because the new module-combine-stream was not installed. - PipeWire can now generate a limits.d config file with our recommended settings for priorities and memlock. - Modules, exec and objects can now be loaded depending on conditions. One example is the X11-bell module that can now be disabled with a custom property override. - Filter-chain has a new mysofa based spacializer plugin. - Support was added for different clocks that allow the RTP modules to work with a PTP clock, for example. - Many bugfixes and improvements. ## PipeWire - Avoid rate switches when the graph is idle. - The rate selection algorithm was improved. This ensures minimal performance and quality loss when resampling. - The default min.quantum was set to 32 again after it got erroneously changed to (the too low) 16 in version 0.3.45. - Fix compilation issues with rust bindings because of macros in defines. Work around it for now. (#2952) - Invalid file mappings are now refused (#2617 #2914 #3007) - Modules, exec and objects can now be loaded depending on conditions. One example is the X11-bell module that can now be disabled with a custom property override. - Filter now also supports _trigger_process() to drive the graph. - TID is now added to the journald log. - PipeWire generates and installs */etc/security/limits.d/25-pw-rlimits.conf* that by default contains project's recommended settings. Creation of the pipewire group is left to the distro or user ( `groupadd -r pipewire` ). See the rlimits-* Meson options for controlling this behavior. - Additionally there is now by default disabled Meson option that will install */etc/security/limits.d/20-pw-defaults.conf* with the current Linux default memlock value. Distros with only kernels >=5.16 or always using systemd v251 or newer do not need this. But all other builds should set the `-Dpam-defaults-install=true` Meson option to ensure that the memlock value is always large enough. Thanks to Rickie Schroeder for pointing out that the default Linux memlock value has been somewhat recently increased. ## modules - Install module-combine-stream. - RTP source now has support for custom channel names. - RTP source will now stop when inactive. - Filter-chain has a new mysofa based spacializer plugin. - The RTP modules can now use direct clock timestamps to send and receive packets. This makes it possible to synchronize sender and receiver with a PTP clock, for example. - Filter-chain now has an invert plugin to invert the polarity of a signal. (#3008) ## SPA - There is now an option to set the channels used for probing Pro Audio devices. This could unlock more samplerates for some devices when they are probed with fewer channels. (#2990) - Support was added for other clocks than the MONOTONIC clock in the driver nodes. This can be used to synchronize the graph to a PTP clock, for example. - The ALSA source has some more headroom when rate matching to avoid stuttering when following another driver. - libcamera controls are now mapped to standard PipeWire property values. - The channelmixer has seen some improvements. MONO and undefined channel layouts are now upmixed and downmixed more correctly. (#3010) ## Bluetooth - Many BAP support fixes. ## GStreamer - The gstreamer elements now support buffer video metadata so that strides are correctly handled. - pipewiresrc will now error out correctly in more cases. (#2935) ## JACK - The frame to/from time functions are improved to also work with negative time and frame offsets. # PipeWire 0.3.65 (2023-01-26) This is a bugfix release that is API and ABI compatible with previous 0.3.x releases. ## Highlights - Add back the deprecated symbols but make sure a deprecated warning is emitted for them. This fixes compilation issues in bindings. - Fix an error in the AVX code that could cause crackling in filter-chain when using the mixer. - The convolver in filter-chain can now select an IR from a list of IRs that best matches the current samplerate. Also resampling of the IR has been improved. - A new native module-combine-stream was added. You can use this to create a 5.1 device from 3 stereo soundcards, for example, or direct the output to multiple sinks at once. - Support for Bluetooth MIDI was added. This requires a wireplumber addition as well. - An ALSA plugin rule was added to tweak the buffer settings in Davinci Resolve so that it now runs with acceptable latency. (#1697) - Support for compress offload was added using tinycompress. This allows compressed formats to be decoded in hardware using ALSA on some devices. - Many more buffixes and improvements. ## PipeWire - Add back the deprecated symbols but make sure a deprecated warning is emitted for them. (#2952) - Fix a regression when running older servers and newer clients (such as flatpaks on older server) where the server would run clients too soon, causing crashes. (#2964) - Ensure that environment variables override any config values. ## Tools - pw-cli has received some improvements in the output. - pw-cat can now use ffmpeg to demux streams for compress offload. ## modules - The convolver IR volume is now preserved after resampling. - Adapter ports can now have a custom prefix. - module-rt now clamps the realtime priority to the user allowed one if it is within an acceptable range. Before it would fall back to RTKit immediately. - The module-echo-cancel can now have per stream channel layouts which makes it possible to link to specific audio ports on a device. (#2939) - Fix an error in the AVX code that could cause crackling in filter-chain when using the mixer. (#2965) - The convolver in filter-chain can now select an IR from a list of IRs that best matches the current sample-rate. - module-pipe-* now better matches the pulseaudio properties. (#2973) - A new combine-stream module was added to combine multiple sinks into one sink. It is also possible to merge multiple sources into one. - module-rtp-source now has match rules to select what SAP sessions to stream from. There were also improvements to the buffering and latency handling. - module-rtp-sink now handles multicast loopback correctly. - module-rtp-sink implements min-ptime and max-ptime to control the send packet latency. ## SPA - A new modifier flag was added to the video format parser helper to allow 0 (linear) as a valid modifier. (#2943) - Params includes were reorganized to make it more scalable. Many compressed audio formats were added. - The alsa pcm plugin now handles invalid values from the driver gracefully. (#2953) - Fix some potential stuttering cause by wrong scaling and overflow of the output buffers in audioconvert. (#2680) - Debug output is now also sent to the log instead of stdout. (#2923) - A debug context was added to debug macros to implement custom debug handling. This is used to redirect the debug of pods to the debug log instead of using some custom duplicated code. - Fix some warnings for potentially undefined shifts in format conversion. - Support for compress offload was added using tinycompress. This is mostly used on some embedded hardware where decoding of audio formats can be done in hardware. ## Bluetooth - Some fixes for LE audio were added. - Support for Bluetooth MIDI was added. This requires a wireplumber addition as well. - Reply OK to empty commands. - Improve compatibility with some devices that send stray \n such as the Sennheiser HD 350BT. (#2991) ## pulse-server - Devices with unsupported formats (by the pulseaudio API) are now also listed in the pulseaudio API (with invalid formats). - The native module-combine-stream is used for module-combine-sink. ## JACK - Make jack.merge-monitor default to true to better match the jack1/2 behaviour. Add an exception for mixxx, which is more usable with unmerged monitors. (#1760) ## ALSA - The property handling in the ALSA plugin was improved. alsa.properties and alsa.rules can now be added to the config file. - A rule was added to tweak the buffer settings in Davinci Resolve so that it can run with acceptable latency. (#1697) - ALSA volume will now also use cubic volumes, like pulseaudio. - The ALSA ctl plugin now also uses the client-rt.conf file. - A new alsa.volume-method was added to configure cubic or linear volume. This can be set per application using the rules. ## GStreamer - pipewiresrc will now advertise DMABUF support if the pipeline supports this. - pipewiresrc will now always be a live source unless told otherwise. # PipeWire 0.3.64 (2023-01-12) This is a bugfix release that is API and ABI compatible with previous 0.3.x releases. ## Highlights - Clear old buffer memory on ports to fix some SIGBUS errors. - It is now possible to assign custom port names to the ports from an adapter. This feature is helpful to those who use a multichannel interface with long-term connections. This way they can label each port with its designation, such as an instrument name or anything else to be displayed in a patchbay or DAW. - Fix some issues with node suspend and quantum and rate calculations. - Fix some regressions in pulse-tunnel and RTP-source adaptive resampling that could cause synchronization problems. - UCM devices now also have a Pro Audio profile. - NODE_TARGET (with the object.id) is now deprecated, use TARGET_OBJECT (with the object.serial, which is not reused and can avoid races). ## PipeWire - Clear all peer input port buffers when suspending. This fixes some SIGBUS errors when some plugins were using old memory. (#2914) - Fix a case where nodes that were not supposed to be suspended, were kept suspended on a rate change. (#2929) - Fix an error in the quantum and rate calculations that could cause nodes to run with wrong quantum and rates when multiple rates were allowed. (#2925) ## Tools - pw-dump will now sort dictionaries to make it easier to compare different outputs. - Improve output of pw-reserve. - pw-loopback uses TARGET_OBJECT so you will need to use the serial id (or better the name) as the target instead of the object id. ## modules - The filter-chain modules has seen some cleanups, refactoring and optimizations in the various DSP functions. - The ROC module now supports setting a custom samplerate. - ROC 0.2.X is now required. - The pulse tunnel and RTP source were not updating the rate field correctly which could cause synchronization problems. (#2891) - The filter-chain now supports an arbitrary number of control properties. (#2933) - It is now possible to assign custom port names to the ports from an adapter with the PW_KEY_NODE_CHANNELNAMES. - Support was added for capture and playback props in echo-cancel. (#2939) ## SPA - The ACP code now has an option to set the probe samplerate. (#1599) - UCM devices now also have a Pro Audio profile. - Filtering of Step ranges is now implemented. ## Pulse-Server - The channel-map is now set correctly on the echo-cancel module. - source_master and sink_master are now correctly handled in module echo-cancel. - Fix a regression in DRAIN where resuming after a DRAIN would fail. This caused problems for espeak. (#2928) - TARGET_OBJECT is now used to make it possible to use the indexes as a target. - ladspa-source and remap-source can now also link to monitors. ## ALSA - The ALSA plugin now handles the target.object correctly when set to -1. (#2893) ## V4L2 - The v4l2 replacement library now also follows symlinks. - Support for getting and setting controls was added. - Support for G_PARM was added. - The environment variable PIPEWIRE_V4L2_TARGET can be used to force an application onto a specific camera. ## Bluetooth - Fix compilation without ldac_abr. - Fix a missing brace in CIND reply. This could cause some devices to fail. - Fix configuration of the initial latency. ## GStreamer - The device provider now supports setting an fd so that it can connect to PipeWire sessions from the portal. - DMABuf support was re-enabled in gstpipewiresrc. # PipeWire 0.3.63 (2022-12-15) This is a quick bugfix release that is API and ABI compatible with previous 0.3.x releases. ## Highlights - Fix a critical bug that causes audio distortion in some cases when using AVX2. - Fix a crash in mpv caused by deinit of PipeWire. - Resample the convolver IR to match the graph samplerate for better results. - Many more small bugfixes and improvements. ## PipeWire - Fix a segfault in the PipeWire deinit code triggered by mpv in some cases. (#2881) - Fix docs about SPA_PLUGIN_DIR. - Always dlclose by default (even under valgrind). Add an option with PIPEWIRE_DLCLOSE to select alternative behaviour. - Improve PIPEWIRE_DEBUG category handling. ## modules - Resample the IR for the convolver when the IR samplerate and graph rate don't match. ## SPA - Handle spurious reads from timerfd gracefully. - Fix potential stack-use-after-scope when starting Audacity. - Fix distorted audio when using AVX2. (#2885) - Remove fallback to default channel map in channelmix. - Improve sorting of MIDI events, use the same order as Ardour. (#1816) - Enable LFE downmixing by default. (#2425) - Make IEC958/AC3 and IEC958/DTS work better by enforcing a fixed minimal buffering for the encoder to avoid stuttering. (#2650) ## Pulse-Server - Add a new pulse.cmd config section to execute pulse commands, currently only for loading modules. This removes the dependency on pactl. - Improve debug of messages. # PipeWire 0.3.62 (2022-12-09) This is a bugfix release that is API and ABI compatible with previous 0.3.x releases. ## Highlights - A regression in screensharing was fixed. It was caused by a race when activating links and driver nodes. - Video transform metadata was added so that cameras and screen sharing can report the video orientation and transformations. - Support for the PulseAudio module-gsettings was added to make paprefs work. - Support for bluetooth offloading was added. This allows for the bluetooth reception, decoding and playback to happen completely in hardware. This also requires some support in WirePlumber. - Many bugfixes and improvements. ## PipeWire - More work on stopping nodes in a more controlled way. - Fix a race in starting nodes and drivers. In some cases the driver node would already be started while the link to the peer node was not ready yet. This caused regressions in screen sharing. The driver is now only started after all the followers and links completed. - Fix a case where a slow capture stream would not recycle buffers anymore and stall. (#2874) - Fix a subtle bug in pw_loop_invoke that could cause callbacks to be delayed and cause crashes in some cases. - Fix a case where IPC was done from the data-thread and could cause crashes. ## Tools - Silence some expected errors in the pw-top output. ## modules - The filter-chain has seen some optimizations in the copy plugin and the convolver. - The zeroconf plugin will now only unpublish services from the server that was removed. - Fix a potential crash when stopping pw-loopback. - Some harmless errors were turned into info messages. - Fix some cases where pw_stream methods were called from the data-thread that could cause segfaults. (#2633) ## SPA - There is now a video transform metadata that indicates how a video frame was transformed (rotated/flipped). libcamera and the GStreamer elements now have support for this metadata. - The SPA volume plugin is now disabled from the default build. - Handle missing control info in libcamera. - Handle errors from loop better, don't call the callbacks on errors. - Somewhat improve performance in some audioconvert AVX2 code for format conversion. - Fix PortConfig and EnumPortConfig params in audioconvert and audioadapter to reflect what is actually going on instead of using hardcoded values. - Pass ignore-dB property correctly in all cases. - Probing is now done in 48KHz again. (#2857) ## Pulse-server - IPv4 addresses are now added first to the list and exposed first with zeroconf discover. - module-gsettings was added to make paprefs work. - The pulse.idle.timeout option was disabled by default and only enabled for selected apps (speech-dispatcher) because it caused some problems for other apps. (#2880) ## JACK - Only process valid ports. Could fix some crashes. (#2863) ## Bluetooth - Support was added for offloading bluetooth handling. Some hardware can receive, decode and play the bluetooth audio directly in hardware. # PipeWire 0.3.61 (2022-11-24) This is a bugfix release that is API and ABI compatible with previous 0.3.x releases. ## Highlights - Fix a bug in audioadapter that could cause crashes when switching bluetooth profiles. - Fix sound in QEMU, deadbeef and openal again. - libcamera plugin fixes, dynamic add and remove should now work with the next wireplumber version. - Fix a regression in pw-midiplay where the first buffer would not play and some events would be missing. - The network module now doesn't export other network sources anymore. - pulse-server now detects clients that keep underrunning for a long time and will pause them to save power. - Many more bugfixes and improvements. ## PipeWire - Optimize away some useless graph recalculations. - Increase alternative sample rates from 16 to 32. - FreeBSD and musl build fixes. - Silence some module loading errors when the error can be ignored. - Fix initial buffer requested size for pw-stream when operating in async mode. This also indirectly fixes the first buffer in pw-midiplay. (#2843) ## Modules - Set the network property on pulse-tunnel streams so that they are not exported anymore. (#2384) - Filter-chain has optimized mix functions now. ## SPA - Handle some errors in libcamera better. - Fix libcamera remove events. Fix the id allocation for devices. - Fix a bug in audioadapter where it would not renegotiate after a port reconfiguration, leading to crashes, especially when automatically switching profiles in bluetooth. (#2764) - Do ALSA probing in 44100Hz again. Some devices seem to fail otherwise for some unknown reason. (#2718) - Force playback start when the ALSA buffer is full. This fixes sound in QEMU. (#2830) - Support Digital 5.1 AC3 for Asus Xonar SE. - Improve format renegotiation in audioadapter. This makes the ALSA plugin work again for deadbeef. (#2832) - Fix latency reporting on adapter DSP ports. ## pulse-server - Fix a bug where openal based applications would hang. (#2821) - Improve zeroconf publish. Only publish on the address of the first running server. This avoids duplicate entries for IPv4 and IPv6. Add support for republish entries when new servers are started. - Add a pulse.idle.timeout option (default to 5 seconds) to pause streams that have been underrunning for this amount of time. Badly behaving clients will then not keep the graph and device busy so that devices can be suspended to save battery. This should give better default behaviour with speech-dispatcher. (#2839) ## JACK - Add an option to configure the filter character. - Fix connect_callbacks. It was only called once for output ports. (#2841) - Add option to set node.passive on jack clients. Make some quirks for qsynth to make it suspend and fade out better. # PipeWire 0.3.60 (2022-11-10) This is a bugfix release that is API and ABI compatible with previous 0.3.x releases. ## Highlights - The filter-chain now handles errors better and has fixes for many crasher bugs. - A new RTP module was added with a sender and receiver. It uses SAP to announce and consume RTP streams and is compatible with the PulseAudio RTP modules. - Many small bluetooth improvements and fixes. - The alsa plugin will now only start playback when there is data. This results in better sync and lower latency between capture and playback. - The v4l2 and libcamera plugins have seen a lot of improvements. They support control properties now. Also pw-v4l2 has seen many improvements and mostly passes the v4l2-compliance test now. - Many more bugfixes and improvements. ## PipeWire - Code cleanups, compiler warning fixes. - Add some extra checks to avoid scheduling an inactive node. - Rework the sequence of events to start and stop nodes. - Improve param enumeration. - An option was added to give priority to the Buffer params of the consumer. This makes it possible to use the default values of the consumer (instead of the producer) when capturing from a source. - The graph rate selection was improved to pick a rate closest to the requested one (instead of picking the default). ## Modules - Fix some crashes in filter-chain. (#2737) - X11 Bell module will now be loaded by default when available. - A new RTP module was added with a sender and receiver. It uses SAP to announce and consume RTP streams and is compatible with the PulseAudio RTP modules. - Improve RAOP compatibility. - The echo-cancel module now uses the resampler prefill option to align input and output samples without buffering. Better latency control when starting and stopping has been implemented. - The pulse tunnel will now write aligned samples to pulseaudio even when the ringbuffer wraps around. This fixes playback issues with multichannel sinks. - Add a delay option to module-loopback using a ringbuffer. - Implement echo-cancel params. - The filter-chain module has better error reporting. - The LADSPA search path was extended with some more common paths. - The echo-canceler input can now also be a monitor of a sink. This improves compatibility with some proton games that expect a real sink instead of a virtual one. ## Tools - Better error reporting in pw-link. - pw-top now also shows IEC958 passthrough formats and JPEG/H264 video formats. - pw-top refreshes the screen faster. - pw-top now prints the state of the node and shows less info for inactive nodes. - pw-dump now uses the new seq field in the spa_param_info to discard old param updates and avoid duplicate params in the output. ## Bluetooth - Add ModemManager support in the native backend. - Clean up GetManagedObjects handling. - Handle QoS from the endpoints in the codec. - Increase the socket buffer to have more control over the rate and QoS. - Simplify the packet flushing code. - Stop processing nodes before destroying them. - Fix timers when a source switches drivers. - Codecs can now share endpoints. This reduces the amount of endpoints and avoids problems with devices that can't handle a large amount of codec endpoints. - Report battery status to UPower for HFP AG. - Fix bitpool increase. ## SPA - The audioresampler now avoids clicks and pops between activating and deactivating the adaptive resampler when used by the stream API. - Use default locale to parse float parameters. - The upmix functions now have SSE optimizations. - Avoid recalculating the complete channelmix setup when only the volume changes. - The alsa plugin will now only start playback when there is data. This results in better sync and lower latency between capture and playback. - The ALSA MIDI sequencer will now pull data from the graph even when it did not output anything. Fixes some graph stalls with the sequencer in some cases. (#2775) - v4l2 and libcamera sources now recycle buffers when nothing is consuming them. This avoids stalling the graph. - libcamera now suggests a more appropriate frame size than the smallest poster frame. - Improve state changes in audioconvert. (#2764) - A new seq field was added to spa_param_info to keep track of pending param updates. - Support speaker output only on RealTek ALC4080. (#2744) - The v4l2 source now supports setting controls. - The libcamera plugin now supports enumerating and setting controls. - A new unit test for 6.1 channel mapping was added. (#2809) More debug info was added to audioconvert for the channel matrix. - Audioconvert will now also upmix a rear-center channel when needed. ## pulse-server - Add support for the RTP send and recv modules with the new native RTP module. - Add option to set latency for pulse-tunnel streams and module-zeroconf-discover. - The socket will now be given the same permissions as what pulseaudio did (0777). - Implement module-loopback latency_msec correctly with the new delay parameter. - sysfs.path is now filled with the same data as pulseaudio. - The manager now uses the new seq field in the spa_param_info. - Fix a bug where in some cases the read pointer would get out of sync and cause too large requests. (#2799) ## ALSA - The alsa plugin now reuses the stream in prepare which results in better performance. - Some deadlocks have been fixed in the ALSA plugin. - The ALSA plugin reports more accurate timing information in some cases. ## V4l2 - The v4l2 compatibility layer has received a lot of updates. - Improved node names and format enumeration. - Support for multiple /dev/videoX devices, each mapped to a unique PipeWire node. - Passes the v4l2-compliance test now with both the v4l2 and libcamera backend in PipeWire. - Improved mmap support for inline buffer memory. This makes it possible to consume PipeWire streams. - Negotiation works more reliably now. ## JACK - Implement jack_acquire_real_time_scheduling() and jack_drop_real_time_scheduling() by keeping the thread utils in a global state. - Fix jack_client_thread_id() to return NULL when the client is not active, just like jack1 and jack2. - An option was added to let the jack_set_buffer_size() function update the global metadata. A quirk was added so that jack_bufsize uses this new feature to make the buffer size settings persistent and global, just like jack. - jack_port_register() and jack_port_unregister() can be called on an active client so make this thread safe. (#2652) # PipeWire 0.3.59 (2022-09-30) This is a bugfix release that is API and ABI compatible with previous 0.3.x releases. ## Highlights - Fix possible wrong samplerate in loopback streams after suspend and rate switch. - module-filter-chain can now adapt to the graph samplerate. - Fix some potential stuttering and crackling in pulse-server. - Add Bluetooth LE support. This requires experimental kernel and bluez support. - The ALSA plugin has more options to control the buffer size. This can be used to work around high latency in davinci resolve. - Many bugfixes and improvements. ## PipeWire - Add audio capture example with volume meter. - Fix a case where a rate switch would not suspend all the nodes of the driver first. This could cause wrong samplerates in streams. - Fix a case where a node would be Paused while still added to the graph, causing potential crashes. (#2701) ## Modules - module-filter-chain and module-loopback now use the resample.prefill option to avoid buffering extra samples and causing unwanted latency when resampling is activated. - module-filter-chain can now adapt to the graph samplerate. - Improve module-raop to support the ALAC codec as raw PCM. - Improve RTSP parsing to improve compatibility. ## Tools - Fix 100% CPU in pw-cli monitor mode. (#2709) - spa-acp-tool can now be exited with ctrl-D. ## SPA - Various libcamera fixes and improvements. - Set stride on audioconvert output buffers. - Make sure we always place the last requested size from the resampler on the buffers in pw-stream. - Add resample.prefill option in the resampler to fill the history with 0 so that we don't have smaller buffers at the start. - Make sure that when an overflow corrupts a POD, that it will always stay corrupted. - Rate limit some ALSA warnings and reduce some unwanted warnings. - Don't recalculate the audioconverter state for each pause/play. (#2701) - Fix some POD parsing inconsistencies and potential overflows. - Add support for Asus Xonar SE. - Fix Flush command handling. It should not stop playback. (#2726) - Refactor the peaks function and add some unit tests and optimizations. - The channelmix has an optimized nXm converter and new unit tests. - Normalization in the channelmixer was fixed. ## pulse-server - The requested latency of record streams was reduced to fix some stuttering in Teamspeak. (#2702) - Tweak the max amount of bytes sent to a client. (#2711) (#2715) - Improve maxlength calculations, this fixes some crackling noise with high samplerate and channel counts in some players (audacious). ## Bluetooth - Merge Bluetooth LE support. - Make sure we are backward compatible with WirePlumber. - Fix some HFP and HSP AT command parsing. (#2463) - Use HFP by default over HSP. ## ALSA - Increase max number of periods. - The parameters handling was improved. There is now an option to set the buffer-bytes of the ALSA plugin. - PIPEWIRE_ALSA can now be used as an environment variable to restrict the plugin formats and buffer size. # PipeWire 0.3.58 (2022-09-15) This is a bugfix release that is API and ABI compatible with previous 0.3.x releases. ## Highlights - Fix a regression that could cause audio crackling. - Fix a regression in RTKit because rlimit was not set correctly. - JAVA sound applications will now alsa work with the pulseaudio-alsa plugin. - pw-top will now show the negotiated formats of devices and streams. - Fix some potential crashes when starting streams. - The ALSA plugin has had improved timing reporting and poll descriptor handling that should improve compatibility. - Many more improvements and bugfixes. ## PipeWire - Avoid scheduling nodes before they are added to the graph. This could avoid some crashes when scheduling nodes that were not completely started yet. (#2677) ## Tools - pw-top now also shows the negotiated formats of streams and devices. (#2566) - pw-top prints microseconds as "us" now to avoid unicode problems. ## Modules - Fix compilation with newer lv2. - Fix setting realtime priority with RTKit, it was not setting rlimit correctly and RTKit would refuse to change the priority. - Fix some playback problems with RAOP sink. (#2673) - Filter chain will now warn when a non-existing control property is used in the config file. (#2685) - Filter chain can now handle control port names with ":" in the name. (#2685) - The echo-cancel module and interface now has activate/deactivate functions to make it possible for plugins to reset their state. ## SPA - Make sure audioconvert uses the given channelmap and channels for the volumes, even when not negotiated yet. This makes it possible to change the volume before the node has been negotiated. - Refactor the peaks resampler. Fix an error in the SSE code. - Fix DSD min/max rates, avoid exposing impossible rates. - Set monitor port buffer size correctly. This could cause some crackling and hickups. (#2677) - Make ALSA sequencer port names unique. ## Pulse-server - Rework the capture buffer attributes to better match pulseaudio. This fixes a regression where opening pavucontrol could cause crackling. (#2671) - Implement TRIGGER and PREBUF methods. - Handle clients that send more than the requested amount of data. PipeWire will now also keep this as extra buffered data like PulseAudio. This fixes JAVA sound applications when they are running on top of the PulseAudio ALSA plugin. (#2626,#2674) - Update the requested amount of bytes more like PulseAudio. Fixes stuttering after resume with the GStreamer pulseaudio sink. (#2680) ## ALSA Plugin - More debug info was added. The time reporting was improved. - The poll descriptor handling was improved, avoiding some spurious wakeups. (#1697) # PipeWire 0.3.57 (2022-09-02) This is a bugfix release that is API and ABI compatible with previous 0.3.x releases. ## Highlights - Support masking of conf.d/ files. (#2629) - Use org.freedesktop.portal.Realtime when available. This does the correct PID/TID mappings to make realtime also work from flatpaks. - Fix rate adjustment logic in pulse-tunnel. This would cause increasing delays and hickups when using tunnels. (#2548) - Add OPUS as a new vendor codec. Add OPUS-A2DP spec. PipeWire can now send and receive OPUS data over bluetooth. - An AAC decoder was added so that PipeWire can now also function as an A2DP AAC receiver. - Fix some issues where the wrong samplerate was used. (#2614) - Fix rate match for sources. This fixes an error where follower sources would generate many resync warnings. - Many more bugfixes and improvements. ## PipeWire - Support masking of conf.d/ files. (#2629) - Add some more debug info to memfd. - Improve data-loop invoke method. Also flush pending items. (#2631) - Add a filter-chain systemd service file than can be used to start custom filters placed in ~/.conf/pipewire/filter-chain.d/ (#2553) - Improve triggered timestamps for remote nodes. - Fix some potential cross compilation problems due to wrong host_machine. - Check return values of pw_getrandom(). ## Tools - Updates to pw-cli manpages. (#2552) - Remove the pw-cli dump command. It is mostly implemented as part of wpctl status, pw-dump, pw-link, pw-top and others. - Clean up resource in pw-cat correctly on errors. (#2651) ## Modules - Fix compilation of AVB on big-endian. Enable AVB only on Linux. - Use org.freedesktop.portal.Realtime when available. This does the correct PID/TID mappings to make realtime also work from flatpaks. - Fix compilation of ROC module when headers are missing. (#2513) - Improve some error cleanup paths in protocol-native. Improve connect and disconnect. - Fix a potential crash in FFT unload in filter-chain. - Implement PIPEWIRE_NOTIFICATION_FD for notification when the socket is ready. - Try to use rtkit if set_nice() fails. - Fix rate adjustment logic in pulse-tunnel. This would cause increasing delays and hickups when using tunnels. (#2548) - Handle disconnect in pulse-tunnel. ## Bluetooth - Add OPUS as a new vendor codec. Add OPUS-A2DP spec. PipeWire can now send and receive OPUS data over bluetooth. - An AAC decoder was added so that PipeWire can now also function as an A2DP AAC receiver. ## SPA - Tweak the resampler window function some more. (#2574) - Improve format convert performance in some fallback cases. - Fix rounding in format conversion on ARM NEON. - Fix libcamera build error. (#2575) - Fix some issues where the wrong samplerate was used. (#2614) - Don't wait for more samples that can fit in the ringbuffer in ALSA. - Improve buffer size handling in audioconvert, scale the buffers based on the rate conversion and make things work with really large rate conversions as well. - Add more and better debug for ALSA devices. - Improve channel mix: Filter FC and LFE when copying from a different layout. Implement STEREO from FC. Avoid generating REAR from FC in PSD mode. - Fix rate match for sources. This fixes an error where follower sources would generate many resync warnings. - Improve ALSA format negotiation. If the ALSA node is not running and there was a previously configured format, close and reopen the device to enumerate and accept all possible formats again. (#2625). ## ALSA - The alsa plugin will now also save the volumes set with the control API. This saves the volumes set with alsa-mixer, for example. ## Pulse-server - Flatpak apps with devices=all (Zoom) will now be granted Manager permissions. - Small tweaks to the amount of data sent to clients to work around an issue in freerdp. ## JACK - Clean up the transport correctly when closing a client. (#2569) - Match context properties in addition to node properties for the jack client rules. (#2580) - Make sure to return an error when disconnected from the server. (#2606) - Fix thread cast problem in jack_client_thread_id(). - Increase jack_client_name_size() length and make sure we have space for the \0 byte. - JACK clients from the same application will be added to the same group so that they share the quantum and rate. # PipeWire 0.3.56 (2022-07-19) This is a quick bugfix release that is API and ABI compatible with previous 0.3.x releases. ## Highlights - A critical bug that could crash JACK apps was fixed. - Some more regressions in audiomixer were fixed. This should fix crackling and stuttering in some cases as well as some channel mapping regressions. - A bug in the alsa plugin was fixed that could cause stuttering in VMs. - Bluetooth sources should have improved latency and rate control. - Many more bugfixes and improvements. ## Modules - An experimental AVB module was added. It can expose PipeWire as an AVB entity and initiate (broken) streaming between entities. - module-loopback now handles the cases where the input and output channels are different without crashing or producing silence. - The filter-chain module now correctly calculates the output size without crashing in some cases. It also skips invalid ports instead of crashing. - Handle and report pthread errors better. ## SPA - The resampler qualities were tweaked a little. - A bug that would sometimes cut off the last part of a buffer was fixed in the alsa plugin. This could cause broken audio in VMs. (#2536) - Access to the alsa mixer and devices is now checked more thoroughly. (#2534) - The spa-resample tool can now also handle large downsampling rates without crashing. - Audioconverter now uses rounding for float to int conversions, which reduces distortions. Compilation of the c functions was separated and uses its own optimization flags now. Unit tests were added. (#2543) - Noise shaping was improved in audioconvert. A new Wannamaker 3 tap shaper was added. - Audioconvert now uses a pattern for generating keep alive noise. This should have much less energy and be even more inaudible. (#2540) - A channel mapping bug was fixed in audioconvert. Unit tests were added. - The dsp audio mixer would sometimes not mix enough and cause dropouts. (#2525) ## JACK - A critical bug in the mixer was fixed. It would cause most JACK apps to segfault at startup. ## Bluetooth - A new rate control algorithm was implemented for the sources. - The media role on HSP/HFP streams is now fixed. ## Pulse Server - Add the resampler delay to delay reporting as well. # PipeWire 0.3.55 (2022-07-12) This is a quick bugfix release that is API and ABI compatible with previous 0.3.x releases. ## Highlights - Fix some more critical bugs in the new audioconvert and the queueing in pw-stream that causes stuttering and hickups. - HFP hardware volumes are now saved and restored. - Format conversions and mixing was improved. - Small bug fixes and improvements. ## PipeWire - The queueing in pw-stream was improved with support for buffer prefetch in async mode. - Add a pw-filter unit test. ## tools - pw-midiplay should now work again after improvements in pw-stream. ## modules - The RAOP module was improved to support auth_setup. - The RAOP module should now handle timing packets better. - Add some more filter-chain examples. - The filter-chain now has a separate config file with the boilerplate settings. The examples are now just config snippets that can be dropped in .conf.d/ directories, such as the filter-chain.conf.d/ one. - Start suggesting to use target.object instead of node.target in docs and examples. ## SPA - Use the cosh window again for the resampler. It should now give better resampler quality. (#2483) - Rework the mixer functions. They were rewritten for higher precision and better performance. Add unit tests and benchmarks. - Improve format conversion for 32bits for avoid errors in clang because of undefined behaviour at extreme ranges. - Fix a bug in audioconvert where it would not consume the right amount of samples when the resampler was disabled. This could cause skipping and hickups. (#2519) - Fix bug in audioconvert where it would try to convert the input samples multiple times, causing strange artifacts when upmixing. - Be more strict about valid JSON floats. - device.vendor.id and device.product.id should now always show up in 0xXXXX format and should not be converted to floats in pw-dump anymore. - Add triangular dither, add unit tests for noise generation, add some more optimizations. ## Bluetooth - HFP and A2DP now expose different routes and thus can have different volumes. - HW Volumes for HFP are now synced better. Volume changes from HW buttons are now also saved. # PipeWire 0.3.54 (2022-07-07) This is a quick bugfix release that is API and ABI compatible with previous 0.3.x releases. ## Highlights - Some critical bugs in the new audioconvert were fixed. The old adapter had internal buffering that was abused in some places. - The bluetooth sources were rewritten using a ringbuffer to make them more reliable to jitter and remove old audioconvert behaviour. - Many improvements to the audio converter. - Native DSD128 and up is now supported by pw-dsdplay. ## tools - Support DSD128 to DSD512 as well by scaling the amount of samples to read per time slice. ## SPA - Format conversion is now generated with macros to remove duplication of code. - 24bits conversions were rewritten to use the generic conversion functions. - Temporary buffers in audioconvert are now made large enough in all cases. - Fix draining in audioconvert. This fixes speaker-test. - Fix the channel remapping. (#2502, #2490) - Audio conversion constants were tweaked to handle the maximum ranges and provide lossless conversion between 24bits and floats. - Vector code and C code are aligned and the unit tests are activated again. A new lossless conversion test was added. - Fix an underrun case where the adapter would not ask for more data. - Fix PROP_INFO for audioconvert. (#2488) - Use the blackman window again for the resampler, the cosh window has some bugs that can cause distortion in some cases. (#2483) - Add more unit tests for audioconvert. Add end-to-end conversion tests. - Don't leak memory in format converter. ## pulse-server - Card properties are now also added to sinks and sources, just like in pulseaudio. - Increase the maxlength size to at least 4 times the fragsize to avoid xruns. - Fix a race when setting default devices. ## Bluetooth - The source was rewritten to use a ringbuffer. This avoids regressions caused by audioconvert. # PipeWire 0.3.53 (2022-06-30) This is a bugfix release that is API and ABI compatible with previous 0.3.x releases. ## Highlights - The 44.1KHz samplerate was removed again from the defaults, it caused all kinds of problems with various hardware. - The ALSA plugin should now be able to deal with unsupported samplerates and fall back to the nearest supported one. - The rlimits performance tuning wiki page was updated. Please check you limits.conf file, the version on the wiki used to give all processes a -19 nice level instead of just the pipewire daemon. - The audioconvert plugin was rewritten to be more maintainable and faster. It also gained support for control ports and dithering with optional noise shaping. - An impossible buffering situation is avoided in pulse-server that would cause some applications (sunshine, ...) to stutter. ## PipeWire - 44.1KHz was removed from the allowed rates again. It caused all kinds of regressions due to driver bugs and timing issues on HDMI. ## modules - filter-chain now does some more error checking and reporting to avoid some crashes. - filter-chain now supports more channel layouts for input and output that does not need to match the plugin layout. - Format parsing is now more consistent in the modules. ## Tools - pw-cli can now also work without readline support. - pw-cat can now also read multichannel ulaw/alaw/u8/s8. ## SPA - The audioconvert plugin was rewritten. This should make it more maintainable. It also fixed some issues such as CPU spikes in some cases and crashes in others. The old plugins were removed, for a code reduction of some 6000 lines. - The audioconvert plugin now supports control ports, which can be enabled on nodes in the session manager. This makes it possible to control audioconvert properties using timed events or midi. - NoteOn 0-velocity MIDI events are no longer filtered out. This is a valid event, nodes that can't deal with it should fix it up themselves. The JACK layer still filters out these events by default but this can now be configured with a per-client property. - The running status on midi events is now disabled to match what JACK does. - The ALSA plugin will now deal with driver bugs when a driver announces support for a samplerate but then refuses to use it later. - The ALSA plugin has been optimized a little for sample IO. - V4L2 now doesn't error when there are no controls. - Error handling was improved in the audio converter. - The audioconvert plugin now supports rectangular dithering and noise shaping. - The audioconvert plugin can now insert additional inaudible noise that can be used to keep some amplifiers alive. (#705) - The audioconvert format conversion was changed so that it now produces the full 32 bits range in the C fallback conversion code as well. - The resampler window function was changed to a cosh() window function. (#2483) - Vendor and device id are now in hex. ## pulse-server - Tweak the record buffer attributes some more and make sure we don't end up in impossible buffering situations. Fixes an issue with distorted sound in sunshine. (#2447) - Fix a potential crash when updating the client property list. - Some properties on cards were aligned with pulseaudio. ## Wiki - Change "priority" to "nice" in the example limits.conf file. It was giving a -19 nice level to all processes, not just the pipewire daemon. # PipeWire 0.3.52 (2022-06-09) This is a bugfix release that is API and ABI compatible with previous 0.3.x releases. ## Highlights - Add 44.1KHz to allowed samplerates. The server can now switch by default between 48KHz and 44.1KHz. - Streams now allocate less resources. - Fix some bugs that could make the server crash. - Bluetooth now supports the LC3plus vendor codec. - Many bugfixes and improvements. ## PipeWire - Add 44.1KHz to allowed samplerates. - Avoid setting the locale. - Avoid use-after-free when destroying a node from spa-node-factory. - Avoid using reallocarray when not available. - Set port alias is not otherwise set. ## Modules - Improve filter-chain parsing and error reporting. Handle empty nodes. (#1950) - Handle destroy of globals and factory in most modules. (#565) - Add refcounts to client and resources to handle destroy of the protocol. (#565) - Handle global node.name in filter-chain and loopback again, use it to construct unique stream names. - Avoid a wrapped pw-node in the adapter. This reduces resources allocated for streams. - Fix a crash when module-x11-bell was unloaded. (#2392) - Add a new module-pipe-tunnel that can write/read data from a UNIX pipe. ## Tools - Fix DSD playback again in pw-cat. - Add -n option to pw-loopback to set node names. - Add -P option to pw-cat to pass properties to the stream. - Support stdin/stdout in pw-cat. (#2387) - pw-dump now also dumps object removal when monitoring. (#2426) ## SPA - Avoid duplicate param results in pw-dump for ports. - Avoid endless loops in audioconvert for badly behaving client. (#2359) - Scale max-error in alsa based on quantum and avoid logging a warning when starting. - Improve debug of failed format conversion. (#2383) - Handle offset in the audio dsp mixer inputs and clamp to the max buffer size. - Add option to disable locale support for JSON number conversion. - Add support for Astro A20 Gen2. - Fix some of the test sources, the flags were not set correctly. - Add camera location as property in libcamera and let the session manager Generate a localized description. - Fix some crashes due to wrong vargar types in v4l2 controls. (#2400) - Improve ALSA resync behaviour. (#2257) - Add support for Komplete Audio 6 MK2. - Improve loop cancel while iterating. - Try not to mix surround channels and AUX channels. Make card with many ports look better when not using the Pro Audio profile. - Vulkan filters were added. ## Bluetooth - Add LC3plus vendor codec. - Handle unsupported indicators better. - Ensure multiple devices on an adapter use different codecs because one endpoint can only be used by one device at a time. - Fix bitpool control as a follower. - Handle bluetooth errors better. - Speed up bluetooth connection by only waiting for the profiles supported by the adapter. - The dummy AVRCP player is disabled by default because it seems to break more devices than it fixes. ## pulse-server - Add initial stream latency property so that devices can be started with a reasonably accurate latency. - Fix ringbuffer underrun case. (#2366) - module-native-protocol-tcp now has a auth-anonymous option to give full access to the clients. - Report a node as being moved when it is still moving. This improves compatibility with pasystray. - Avoid overallocating message memory. - Don't export NETWORK nodes in zeroconf. (#2384) - Fix stride for TrueHD and DTSHD passthrough. (#2284) - Make sure we don't send too small audio fragments. Fixes capture from multiple tabs in Chrome. (#2418) - Rework module handling some more. - Use the new native module-pipe-tunnel for pipe-sink and pipe-source. - Implement the STREAM_MOVED message when a stream got moved. (#2407) - Fix a potential segfault when stopping the server and a TCP module as still loaded. ## ALSA - Add support for updating sw_params at runtime, mostly the min-avail param. - Capture and playback nodes are now assumed to use a different clock and will activate the adaptive resampler when linked. This assumption is removed in Pro Audio mode. This provide a better experience out of the box with most devices. ## JACK - Fix setting properties with PIPEWIRE_PROPS again. - Don't use 64 bits atomic operations for sync_timeout. (#1867) - Cleanup in error cases was improved, avoiding some crashes. (#2394) ## GStreamer - Fix pipewiresink in mode=provide. (#1980) - Share memory into a new buffer in pipewiresrc to avoid buffer corruption. - Fixes to the source and fd use. - It is now possible to set client properties as well. (#1573) # PipeWire 0.3.51 (2022-04-28) This is a bugfix release that is API and ABI compatible with previous 0.3.x releases. ## Highlights - Improved graph reconfiguration. - Extra configuration options for streams and filters with config rules and environment variable. - Improve module-pulse-tunnel latency, stability and error recovery. - pw-top, pw-cli and pw-link improvements. - Fix a channelmixer upmixing clipping issue. - The ROC module has seen many improvements. - Many more bugfixes and improvements. ## PipeWire - The graph reconfiguration code was reworked: * Moved nodes will update the new driver quantum correctly. (#2293) * Inactive nodes are ignored more. * Nodes that require a driver are now not scheduled anymore when they are passive (unused). (#2309) * Improved performance, the graph is reconfigured with a minimal amount of changes. - Method and event argument names were improved. - A linker garbage collection problem was fixed. (#2292) - Properties on threads are now implemented. Use common code to set thread name and add an option to set stack-size. - Streams and filters always want a driver now. This makes it possible to just link a playback stream to a capture stream without a driver and have it work. (#1761). - Streams and filters can now also have rules in the config file. - Streams, filters, JACK, ALSA and v4l2 now support PIPEWIRE_PROPS environment variable to override node properties. - Add config section extensions. This provides a way for modules to have specific config to override the default config. - Handle realloc errors better. - Improve stream and filter property updates. ## Modules - The pulse-tunnel modules has improved latency management and should now work a lot better. (#2230) - Module-loopback, module-echo-cancel, module-filter-chain unload the module when a stream is destroyed. (#1754) - Biquads in filter-chain now can have more gain (5->20 dB). - Documentation updates. Most Wiki content was moved to the source code inline comments. - Filter-chain now has a builtin delay line filter. (#2320) - Filter-chain can now parse the config key correctly in all cases. - The ROC sink and source saw many improvements. roc-source is now a stream by default that connects to the default sink. Both modules will try to set a graph rate. Both modules have an option to select the FEC mode. The ROC source has lower latency now. (#2331) - Handle realloc errors better. ## tools - pw-cat does not have --list-targets anymore, use one of the more advanced and less buggy tools such as wpctl or pw-cli to list sinks and sources. - pw-top has seen many improvements. * It now has some timeouts to reset the node values to their default state when unused. * The man page was improved. * Invalid timings and errors are displayed in a better way. - pw-cli and pw-link can now create links between all ports of given nodes. - pw-cat can now save to other file formats than wav, based on the extension of the filename. ## SPA - The resampler now uses a different internal method for draining. It can now also handle 0 size buffers as input without draining. - The channelmixer now uses the front channel averages for FC and LFE. This avoids clipping and results in much better upmixing. - ALSA should now work again on 32 bits. (#2271) - The JSON parser now converts escaped unicode correctly to UTF8. ## bluetooth - Codec switch improvements when the device is disconnected. (#2334) ## pulse-server - There is a new module-roc-sink-input module, the the PulseAudio equivalent. - The ROC source and sink-input module now have a much lower latency. - The ROC module now has an option to select FEC mode. - Playback and record rate adjustments should work now. (#1159) ## JACK - Remove some useless pthread attributes. This makes JACK work in QEMU with sandboxing enabled. (#2297) - The buffer_size callback is now only called when something has changed since the last process() callback or get_buffer_size() method. This fixes a GStreamer issue and is more in line with what JACK does. (#2324) - Fix a potential deadlock when the process thread is doing IPC and the IPC thread is blocking on the data thread. - Allocation errors in metadata are handled better. # PipeWire 0.3.50 (2022-04-13) This is a bugfix release that is API and ABI compatible with previous 0.3.x releases. ## Highlights - pw-stream can now report more timing information and can suggest the optimal number of samples to queue for playback. - pw-dot now works again.. - module-pulse-tunnel latency was improved. - WINE applications using the JACK backend should no longer crash. - The channelmixer defaults are improved and the muffled sound when playing back 5.1 and 7.1 material has been fixed. - Many fixes and improvements. ## PipeWire - pw-stream now places a suggested amount of samples in the pw-buffer for playback. This allows you to remove some places where spa_io_rate_match was needed to get this information. - pw-stream has new API to request a timing update. New fields are added in the timing info, such as number of buffered samples in the resampler and the number of queued and dequeued buffers. - pw-stream has support for double controls now. More controls are exposed such as the Rate control to do adaptive resampling. - The thread-utils object was moved to the context to avoid some concurrent use cases that caused crashes. (#2252) - Deactivating an exported node/stream will now remove the node from the data-thread immediately so that the process function will not be called anymore and resources can be safely freed. This could fix some of the last remaining crashes when streams are stopped. - PipeWire will now fail to load a module that tries to register the same export type twice instead of silently doing the wrong thing. (#2281) ## Modules - Many modules now use the NODE_WANT_DRIVER instead of the pipewire.dummy NODE_GROUP property. This makes it possible to use them with any other driver and can avoid resampling in some cases. - module-pulse-tunnel now uses an adaptive resampler to keep the latency under control. Latency should be much better than before and stay constant even when there are network delays. - There is now an option for packages to disable building the RTKit module, which is still built by default for backwards compatibility reasons. - A leak was fixed in filter-chain. (#2220) - Module node names are now made more unique with the pid. ## tools - pw-cat verbose output has been improved. - pw-link now has a man page. (#2263) - pw-reserve now has an -r option to make it issue a RequestRelease command on the owner of the device. This makes it possible to ask WirePlumber to close and release a device. - Fix pw-dot again. It didn't work anymore because of stray done events that were emitted to notify the client of object serials. (#2253) ## SPA - The channelmixer now has PSD upmixing enabled again. We used the simple upmixing in the previous release but that just sounds too awful to be a good default. (#861) and (#2219) - The channelmixer will not upmix FC and LFE anymore when upmixing is explicitly disabled. (#2266) - The channelmixer will only lowpass filter FC and LFE channels when they were upmixed. (#2280) - The defaults of the channelmixer were tweaked a little. There is now a little bit more bass in the LFE channel and more high frequencies in the FC channel when upmixing. Also the channel widening has been disabled by default. - Locale independent float parsing now uses a more portable solution with uselocale. - ALSA will now only allocate a buffer size big enough to hold 4 times the quantum limit instead of as large as possible. ## pulse-server - Internal cleanups in handling of modules. - A quirk to force s16 sample formats for teams-insider has been added. ## JACK - The data-loop is now started in activate and stopped in deactivate. This makes the data-loop respect any custom thread functions you configure. This also makes WINE apps using the JACK backend work. (#1495). - Port sorting was improved/fixed. (#2260) # PipeWire 0.3.49 (2022-03-29) This is a bugfix release that is API and ABI compatible with previous 0.3.x releases. ## Highlights - Sample rate switching should work again. - pw-dot can now use the output of pw-dump to render a graph. - Bluetooth A2DP streaming was improved that would reduce stuttering on some devices. - A JACK bug was fixed that would sometimes make it impossible to add more tracks in Ardour. (#1714) - Many bugfixes and improvements. ## PipeWire - Fix a potential crash when NULL params were configured. - Add some simple functional tests to avoid some recent regressions. Improve the test framework for this as well. - Improvements to the poll loop to avoid some use-after-free scenarios. - Fix samplerate switching again. - setlocale is not called anymore from the pipewire library. This should be called by the application. (#2223) - pw_init() and pw_deinit() can now be nested and called multiple times. - pw_stream will now report the resampler delay in the pw_time.queued field. ## modules - module-filter-chain now supports arbitrary many properties and will use property hints to assign them the right type. - The ROC modules now accept a sink/source_properties parameter. - The module-rt can now also be built without RT-Kit support. - module-echo-cancel can now use a fraction to specify the delay for more precise control. ## SPA - The channelmixer will now do upmixing by default and will not use normalization. It will also use a simple upmixing algorithm that duplicates channels by default. A more interesting upmix method is also available (PSD) but needs to be enabled manually. (#861) - Add SSE optimized (de)interleave functions for 32 bits samples with and without byteswap. - JSON parsing of empty strings will now give an invalid number instead of 0. - JSON numbers are now parsed and serialized in a locale independent way so that , and . are not mixed up. - The resampler will now report the resample delay and queued samples as the extra delay. ## tools - pw-cat will read more dsf files correctly and will not crash at the end. - pw-top now has a man page. - pw-dot can now use the output of pw-dump to render a graph. ## bluetooth - Improve interactions with oFono. - Fix recovery with slow connections. - Improve frame size of AptX-ll. - A2DP can now use any quantum and will flush packets in smaller chunks when needed to adapt. This improves stuttering in some cases. ## pulse-server - The server configuration can now be placed in pulse.properties section, which also makes it possible to have custom overrides. - Implement FIX_ flags for capture as well. - Small fixes and improvements in module loading. ## JACK - Clear the last error before executing a new action or else we could end up with error from a previous action. This causes some problems in Ardour where adding a track would fail after some time. (#1714) # PipeWire 0.3.48 (2022-03-03) This is a bugfix release that is API and ABI compatible with previous 0.3.x releases. ## Highlights - Fix IEC958 passthrough again. - Fix pulse-server crashes when playing a sample. - Support for more a more advanced upmixing algorithm. - filter-chain now supports arbitrary many ports. - Fix multichannel support in WINE. (with new WirePlumber). - Many bugfixes and improvements. ## PipeWire - The work queue is now created in the context so we can fail early and avoid further error checking in various places. - Fix a potential use after free with threaded loops. - The protocol now has a message footer. This is used to pass around global state such as the last registered object serial number. This can be used to detect when a client tries to bind to old (but reused) object ids. This avoids some races in the session manager but also when binding objects. - The zero-denormals CPU flag is now not touched anymore unless explicitly selected by the user. Denormals are avoided in filter-chain now in software. If the zero-denormals are now only configured in the data thread. This should fix issues with luajit. (#2160) - Configuration parsing will not actually fail on errors. - pw-top now correctly clips unicode characters. - Many places now use a dynamic POD builder to support arbitrary large property sets. - pw-stream now support PropInfo parameters so that they can announce custom properties. - Serial number are now also set on metadata and session-manager objects. ## SPA - audioadapter is now smarter when trying to fixate the format. It will use the PortConfig format to fill in any wildcards. This results in the least amount of conversions when the stream can handle it. It also is part of a fix (also requires a session manager fix) for WINE multichannel support. (#876). - Fix 5.1 to 2 channels mixing. It was using the volume of the stereo pair on all channels. - Fix some weird volume issues when a source is capturing and channelmixing. - Add stereo to 7.1 upmixing. - The channelmix parameters can be changed at runtime now. - Many improvements to the upmixing algorithms. Rear channels are now constructed from the ambient sound and can have delay and phase shift applied to them to improve specialization. The stereo channels can be filtered so that the dialog is more concentrated in the center channel. (#861) ## modules - Module X11 bell received cleanups and improvements. - The module now has a private method to schedule unload later. This simplifies cleanup in many modules. - module-filter-chain now handles arbitrary many ports and control ports. (#2179) - Fix a bug in RAOP where it was reading from the wrong port. (#2183) ## pulse-server - Nodes with the DONT_MOVE property should fail with -EINVAL when they are moved. - Fix a segfault when playing a sample. (#2151) - The _FIX flags in pulse-server also now ignore the configured sample format, just like pulseaudio does. (#876) - Fix IEC958 passthrough again. It got accidentally broken since 0.3.45 with a fix for another issue. (#1442) - Fix module-null-sink device.description. (#2166) ## Bluetooth - Don't try to connect HSP/HFP when no backend is available. # PipeWire 0.3.47 (2022-02-18) This is a bugfix release that is API and ABI compatible with previous 0.3.x releases. This is a quick emergency release to fix some severe problems with the previous release. ## Highlights - Fixes a bug in pulse-server that caused cached notifications to play multiple times. (#2142) - Removed check and warnings to catch leaked listeners on the proxy. This might access invalid memory and cause infinite loops in older wireplumber. # PipeWire 0.3.46 (2022-02-17) This is a bugfix release that is API and ABI compatible with previous 0.3.x releases. ## Highlights - Fix a critical bug in pipewire-pulse buffer size handling that made some apps (MuseScore, ... ) stutter. - Fix a critical bug where devices would not show when the kernel was compiled without VERBOSE_PROCSFS. - JACK clients will now use lock-quantum by default. This makes sure that all dynamic quantum changes are disabled while a JACK app is running. The only way to force a quantum chance is through a JACK app or with the metadata. - Almost all limits on number of ports, clients and nodes are removed. - A Dummy fallback sink is now automatically created when there are no other sinks. This avoids stalling browsers. - Sound sharing with Zoom should work better. A new WirePlumber release might be required. - Many more fixes and improvements. ## PipeWire - Update docs with new config overrides. - The rule matching logic was moved to config and code is now shared with pulse-server and JACK. - Add new Romanian translation. - When a quantum is forced with metadata, any node that asked to lock-quantum is ignored so that the quantum change can happen. - Fix a bug where a mixer was removed twice, leading to potential memory corruption. - The port limits on nodes and filters are now removed. Some code was simplified. - Fix a potential leak because listeners where removed while they could be emitted. - Improve context.exec and avoid zombie processes. ## Modules - The RAOP module now has a default latency of 2 seconds, like PulseAudio. - The echo-cancel module now uses the plugin loader to load the backends. This makes it possible to add custom, out of tree, echo cancel plugins. ## Tools - Improve help of pw-link. - Output to stdout and error to stderr. Use setlinebuf for stdout to improve piping between apps. (#2110) ## SPA - Improve removing sources when dispatching. Also improve performance now that a destroy loop can be removed. (#2114) - Fix an fd leak in the logger when logging to a file. - Improve loop enter/leave checks and support recursive loops. ## pulse-server - Clamp various buffer attributes to the max length. Fixes some issues with various applications. (#2100) - Module properties are now remapped correctly from their pulseaudio variant to the PipeWire ones. - Fix module index in introspect. Use the right index when loaded from our internal modules. (#2101) - Improve argument parsing and node.description. (#2086) - The sink-index should now be filled in correctly when playing a sample. (#2129) - module-always-sink is now implemented and loaded by default. (#1838) - Add support for loading some modules only once. - Module load and unload now does extra sync to make it appear synchronous, like in PulseAudio. This improves sounds sharing in Zoom. ## ALSA - Fix critial bug where alsa devices would not show when the kernel was compiled without VERBOSE_PROCFS. - Some corner cases were fixed in the ALSA timing code. When the capture node is follower, it will now not try to read too much data and xrun but it will instead produce a cycle of silence. - Various fixes and improvements to make ALSA devices resync to the driver more quickly and accurately. ## JACK - Add an option to name the default device as `system` to improve compatibility with some applications, - Use lock-quantum by default. This makes sure that all dynamic quantum changes are disabled while a JACK app is running. The only way to force a quantum chance is through a JACK app or with the metadata. - It is now possible to do IPC calls from the data thread. Note that this is a very bad idea but required for compatibility with JACK2. ## GStreamer - GStreamer sink will now set a default channelmap to make it possible to remap to the channel layout of the device. # PipeWire 0.3.45 (2022-02-03) This is a bugfix release that is API and ABI compatible with previous 0.3.x releases. ## Highlights - Zoom, telegram and other apps should be able to play sound again. - Implement a better way to force and lock JACK buffersize. - Default sink and source names and properties are improved. - The config loader can now load and merge fragments in conf.d directories for easier user configuration of config files. - Many small bug fixes and improvements. ## PipeWire - pw-cli can now also send Commands to nodes. This can be used to Suspend a device, for example. - The eventfd was removed from loops and invoke is now used to stop the loop, this saves an fd. - New Alpine CI target to test musl builds, various build fixes. - Add force-quantum and force-rate properties. - The config loader can now load and merge fragments in conf.d directories. (#207) - resource error methods can be called without a resource and then just log an error message. - link-factory can now also work from the config. (#2095) ## modules - module-simple-protocol has better argument parsing and can handle channelmap now. (#2068) It's also possible to configure latency and rate. - The native protocol now does extra checks for invalid data. (#2070) ## ALSA - TI2902 chips as found in various Behringer cards should have inputs again. - Better handling of busy devices in udev, retry when the inotify close event is emitted. ## SPA - plugins now handle alignment properly and only expect the max alignment required for the CPU. (#2074) ## Bluetooth - SBC-XQ is now enabled for the JBL Endurance RUN BT headset. - Support for non-hexadecimal XAPL version strings to improve compatibility. - Use HCI commands again to probe the adapter msbc capability. This improves compatibility with some adapters. (#2030) - Set the right startup volume. - Better A2DP source idle handling. - Fix a timer bug in SCO sink that could cause busy looping. ## pulse-server - A playback issue when the tlength > maxlength was fixed. (#2069) This affected Zoom and other applications. - The STREAM_BUFFER_ATTR command is now implemented. - Module names are improved. (#2076) - Many small fixes and improvements. - Fix a pavucontrol crash with invalid channels. (#1442) ## JACK - Use the new force-quantum and force-rate properties in the JACK API to switch quantum and ensure it can't change for the lifetime of the JACK app. (#2079) # PipeWire 0.3.44 (2022-01-27) This is a bugfix release that is API and ABI compatible with previous 0.3.x releases. ## Highlights - It is now possible to run a minimal PipeWire server without a session manager, enough to run JACK clients. - The maximum buffer size is now configurable and can be larger than the previously hardcoded limit of 8192 samples. When using high sample rates, the larger buffer size can avoid xruns. - The default maximum latency was reduced from 170ms to 42ms. This should improve overall latency for application that ask for a large latency, such as notifications. - Better JACK compatibility. Patchbays should now get less confused about ports appearing and disappearing. - Fix some bluetooth crashes. - Fix some races in ALSA device detection. - Many bug fixes and improvements all over the place. ## PipeWire - Bump the meson requirement to 0.59.0. - pw-top now reports correct times for filter-chain and loopback. - max-quantum is now also scaled with the rate. A new quantum-limit property was added as a hard limit for the quantum. This makes it possible to configure for larger than 8192 buffer sizes. Note than many JACK applications have a hardcoded 8192 limit. (#1931) - The max-quantum was reduced to 2048, This gives a 42ms default latency. (#1908) - pw-filter can now return a NULL buffer from _get_dsp_buffer(). - Add a PIPEWIRE_RATE and PIPEWIRE_QUANTUM env variable to set the graph rate and the graph quantum and rate respectively. - Fix a potential file descriptor leak in the connection. - A new minimal.conf file was added to demonstrate a static setup of a daemon that doesn't require a session manager and is able to run JACK applications. - Nice levels are now only changed on the servers, not the clients. - Add an option to suspend nodes when idle. - Make it possible to avoid quantum and rate changes with pw-metadata. This is essential in a locked down system. - Handle mixer port errors better and fail to create the link instead of silently not working. - Nodes that are moved to a driver now have all the linked nodes moved as well. This makes it possible to run some graphs without a driver, such as paplay -> zita-j2n. - pw-cli and pw-dump can now also list objects by name, serial and object.path using glob style pattern matching. ## modules - filter-chain can now also configure parameters by index. - Fix the client name of module-protocol-simple. (#2017) - module-rtkit was merged into module-rt. This makes it easier to ship a default config that works on more systems by default. - module-adapter can now configure the adapter node from the config. Previously, this was a task only performed by the session manager. - module-metadata can now also create metadata object from the config file. - The ROC module should now work again. (#2045) - An X11-bell module was added to handle X11 bell events. (#1668) - filter-chain and loopback modules now have better unique default names for the streams, which makes it possible to save and restore their volumes independently. (#1983) - module-echo-cancel now has properties to control the delay and buffer size. ## ALSA - The monitor names are now correctly parsed. - The default period size for batch devices is limited now to avoid large latency. - The unused min/max-latency properties were removed. - Internal latency is now also configurable with params at runtime. - The udev rule for TI2902 was removed because it causes problems. - Fix a race where some devices would sometimes be missing. (#2046) - Add some more timeouts to work around a race in udev device permission changes when switching VTs. ## SPA - Fix potential infinite loop in audioconvert. - The spa-resample tools can now also use optimized implementations. - Fix a potential crash in resampler. (#1994) - audioconvert can now also handle F64 formats. (#1990) - The channelmixer now does normalization by default to avoid clipping when downmixing is active. - The channelmixer will now generate LFE channels when the lfe_cutoff frequency is set, even when upmix is disabled. - The channelmixer will now always generate FC when the target has it. - Adapter now reports latency correctly, even after linking the monitor ports. - Reduce memory usage and preallocated memory in some of the audioconvert nodes. - Many properties are now exposed in adapter, such as the resample quality. - The resampler and channelmixer can now be disabled. ## V4L2 - pw-v4l2 now also works for ffplay. (#2029) - Take product names from udev now that the kernel returns a generic name. ## JACK - The jack pkgconfig file now has the `jack_implementation=pipewire` variable to be able to distinguish jack implementations. (#1666) - jconvolver now starts correctly again. (#1989) - The object.serial is now used for the port_id. This makes it easier to track old objects in the cache. - Add a dummy jacknet implementation. (#2043) - A bug in the port allocation was fixed that would make it impossible to allocate ports at some point. (#1714) ## Bluetooth - Bluetooth profiles are now saved properly by the session manager. - Improved profile detections, increased timeouts for slow devices. - Implement HFP call indicator for improved compatibility. - Handle the case where bluez does not set the adapter or address properties on the device instead of crashing. - Improved support for setting the profile from the session manager. ## pulse-server - Monitor sources now have the device.class=monitor for better compatibility. - Behaviour after seeking is improved. The algorithm for requesting bytes from the client was simplified and improved. (#1981) - module-ladspa-sink implements the control argument now. (#1987) - A potential memory leak in the message queue was fixed. (#1840) - Use the object.serial for the pulseaudio object index. The index is not supposed to be reused and this would cause problems with some clients. - Servers should now again be able to listen in IPv4. (#2047) - module-x11-bell was added. (#1668) - There is now support for per-application quirks and properties in the pipewire-pulse.conf file. Per-application latency and buffering properties can also be configured. - Fix a regression in telegram sounds not playing. # PipeWire 0.3.43 (2022-01-05) This is a bugfix release that is API and ABI compatible with previous 0.3.x releases. ## Highlights - Flatpak apps such as Ardour can now remove links again. - Many fixes to pulse-server. Memory usage should be improved. Some crashes are fixed. Underrun handling should work better. Better compatibility with GStreamer based applications after seeking. - Many of the samplerate and quantum changes bugs in previous releases were fixed. This fixes some issues where the microphone would fail to work. - Many more small fixes and improvements all over the place. ## PipeWire - Quantum and rate changes are now applied immediately when the driver is idle. This avoids setting the driver in the previous samplerate. (#1913) - Object destruction now does not need write permissions anymore. This restriction needs some more work. (#1920) - When we reposition, start a sync operation. This fixes a problem in Ardour when seeking. (#1907) - Require meson 0.56.0 - Make the align property in BUFFER_PARAM optional. We now only set this if the plugin has specific requirements and we default to the CPU largest alignment requirement. - pw-record will now also list monitors and streams as possible targets. ## modules - Improve LV2 plugin support in filter-chain. Add support for Worker and Options. - The loopback module now has a unique media.name to make it possible for the session manager to restore unique volume settings. ## SPA - Improve sample rate for EAC3 streams, some clients scale it while others don't so use some heuristics to make things work better. (#1902) - Allocate ports dynamically in audioconvert. This avoids using larger memory blocks of preallocated memory that confuses the allocator. (#1840) ## ALSA - Merge the latest pulseaudio UCM improvements. (#1849) - Fixes for selecting the sample rate. (#1892) - Improve latency on USB devices by scaling the period size based on the desired quantum when the device is opened. - Add api.alsa.period-num to configure the amount of periods to use in the device. Some devices have lower latency when a small value is forced. (#1473) - Allow multi-rate by default. In previous versions cards could only be opened in one samplerate to avoid bugs in pre 5.16 kernels. This however caused other problems so the default was removed. - Fix a bug where a card was not freed correctly. - Fix a bug in the alsa boundary check that could hang the alsa-plugin for a long time. - The ALSA plugin now has a parameter to configure the allowed samplerates. ## JACK - Improve handling of monitor nodes. ## Bluetooth - Codecs now have a priority. This should improve codec selection. ## pulse-server - The stream FIX_ flags are now implemented. (#1912) - Improve flushing and draining behaviour. Short samples will now play correctly. (#1549) - Fix a crash when enumerating the formats. (#1928) - Track quantum changes and update the amount of required buffering accordingly. This improves forced quantum handling. (#1930) - Improve handling of channels > 32. - Handle the case where a module is destroyed before it could be completely loaded. - Fixes some issues when samples were removed while they were playing. (#1953) - Fix some issues in module-zeroconf-publish. - Fix some memory leaks in module-roc. - Add command access control. This avoids execution of commands without proper authentication. # PipeWire 0.3.42 (2021-12-16) This is a bugfix release that is API and ABI compatible with previous 0.3.x releases. This is a quick emergency release to fix some severe problems with the previous release. ## Highlights - Fixes a bug in pulse-server underrun handling that broke qemu and orca. - A fix was added to pulse-server to handle quantum changes gracefully. - Fix module-echo-cancel again. - Fix a bug where the bluetooth headset capture was producing noise. # PipeWire 0.3.41 (2021-12-13) This is a bugfix release that is API and ABI compatible with previous 0.3.x releases. ## Highlights - Improved compatibility for flatpaks. Flatpaks with newer PipeWire version can connect to an older server in all cases. - A new RAOP module was added to stream to Apple Airplay devices. - OBS can now capture from the monitor devices again when using WirePlumber. - Improved JACK compatibility. Improved stability in Carla and Ardour when changing buffer size. Improved latency calculations and playback latency in Ardour. - Improved pulse-server handling of underruns and buffer size changes. - Many bugfixes and improvements. ## PipeWire - The systemd service files now have better names. - client.access permission checks are improved. - Fix some memory leaks in error paths. - Objects now have a global serial number that is unique for the lifetime of the server. - Make clock.rate, clock.allowed-rates and clock.quantum runtime tunable parameters with the settings metadata. - Add some additional memory checks in client-node to avoid sending invalid memory to clients. (#1859) - Improve buffer memory allocation. If one of the nodes is a remote node, ensure we only use memory that can be shared. - Version checks when binding to objects is removed. This means that newer clients can now bind to older servers, which is a typical case for a flatpak. - A bug in the latency calculations was fixed where it would in some cases report the wrong minimum latency. ## modules - module-echo-cancel has voice-detection enabled now. - module-raop-sink and module-raop-discover to stream audio to an Apple Airplay device. - module-filter-chain now has preliminary support for LV2 plugins. ## SPA - The audio resampler now has improved buffer size calculations. In some cases it was too small and would cause distortions. - More checks are done when doing volume changes so that the channelmap is correct. - Audioadapter now exposes most config options with params so that they can be adjusted at runtime. - The resampler can now calculate the expected input buffer size before receiving the first buffer, which avoids some confusion when starting streams. - Support was added for some 10bit video formats. - MONO channel handling was improved. - Most plugins now set a clock name and this is configurable where it makes sense. The clock.system.monotonic clock name is used for most plugins that use the system clock for timing. ## pulse-server - implement module-raop-discover - Use STREAM_CAPTURE_SINK property when capturing from a monitor source to better inform the session manager. This fixes some issues where OBS would capture from the microphone instead of the output monitor. - Limit the amount of cache messages to 16MB and don't add large memory blocks to the cache. This should fix some excessive memory usage that people reported. - Fix a potential memory leak when cleaning up a client. - Do some additional checks to avoid buffer overruns. - Improve recovery from underruns better. (#1857) This improves seeking in gnome-music. - Improve recovery when the quantum is forced larger that the stream configured latency. - The prebuf state is now handled correctly. ## JACK - A per type object cache is now implemented. This ensures that port objects remain valid for a longer time because many JACK applications inspect objects after they are destroyed. This improves catia/carla compatibility. - Recompute the latencies when the buffer-size changes. Fix some cases where we would end up with negative latencies. - Handle regcomp errors to avoid some crashes later. - Latency calculations are improved a lot. - More care is taken to not call a process callback while a buffer size change is pending. This fixes some crashes in Carla, which expect that all clients are paused when one handles the buffersize callback. - Loopback links to a client are now handled correctly and without latency. This fixes playback latency in ardour6 (#1839) ## ALSA - ALSA devices now keep track of the samplerate of the card and ensure that all PCM use the same rate. This is a workaround for a kernel bug that is fixed in 5.16. - Refactor the ALSA plugin a little. - The ALSA plugin now reports correct delay for a capture PCM. (#1697) - The ALSA nodes now expose all config options with params that can be changed at runtime. - The ALSA node has a configurable clock name. Adaptive resampling to match clock rates is avoided when the driver has the same clock name as the ALSA node. This can be used to link alsa devices together with a word clock. # PipeWire 0.3.40 (2021-11-11) This is a bugfix release that is API and ABI compatible with previous 0.3.x releases. ## Highlights - Producers and consumers can now incrementally negotiate a format by narrowing down the options. This can be used to select an optimal combination of format and modifiers. - Driver nodes such as the consumer of a headless compositor can now throttle the speed based on a new trigger_done event. - Headless compositors can now signal a damage event to consumers to start the processing of the graph. - Compatibility improvements in JACK. - Draining and resuming is now working correctly in pulse and alsa. - Many bugfixes and improvements. ## PipeWire - Many BSD fixes. - clang compilation fixes. - Fix map implementation on big-endian machines. - Improve tracking of param changes in pw-stream. - Add support for renegotiation. With this change, producer and consumer can incrementally renegotiate a format until it is fixed. This will be used to do complex negotiation of DRM modifiers. (#1732). - Add a trigger-done event in the stream. This can be used to know when processing of the complete graph has finished after issuing a trigger_process() and it can be used to throttle processing. - Add a RequestProcess node event and command. This can be used by non-driver nodes to suggest to a driver to start processing. One case is where a compositor can emit this event as a result of a screen update to let the headless compositor start an update. - Fix zeroconf sample format. - pw-mon outputs to stderr now and has colors. ## SPA - Fix compilation on ppc and armv7. - Fix port type check for ALSA seq midi ports so that they are not falsely listed as hardware. - Fix crash when running SSE code on unsupported HW. (#1775) - The libcamera plugin was rewritten. It now supports hotplug, format enumeration and an easier to read codebase. - Fix compatibility some more for cards with 64 channels. ## pulse-server - Flush data in pause in combine-sink to avoid stray audio fragments. - Fix a race where not all objects were removed correctly. - The latency calculations and setup was improved to more closely match pulseaudio behaviour. PULSE_LATENCY_MSEC should now resemble pulseaudio more closely. (#1769) - The drained reply is now sent only once and new data will be accepted once the drain completes. - Fix a potential crasher bug where the stream started processing before the setup was completed. - The server will now drop the client connections when the pipewire connection is lost. ## JACK - Rework the jack_port_get_buffer() method to return the same memory when called multiple times during the process() callback. This makes things work on a new Hydrogen. (#1748) - Add an option to disable showing the monitor ports. - JACK ports are now sorted per node/client and port_id. This should more closely match JACK behaviour and avoid random port order. (#1780) ## v4l2 - Fix v4l2 LD_PRELOAD script. - Make sure we destroy the proxy when the global is destroyed. ## ALSA - _prepare should exit the draining state. (#1467) - Fix the precision of the _delay function by taking into account the amount of queued samples are the correct samplerate. # PipeWire 0.3.39 (2021-10-21) This is a bugfix release that is API and ABI compatible with previous 0.3.x releases. ## Highlights - media-session is now moved into a separate module to speed up its deprecation in favour of WirePlumber. - There is now an LD_PRELOAD v4l2 emulation library to run some existing v4l2 applications on top of PipeWire. - Filter-chains should now flush out remaining samples when paused. There is now also the option to let a filter-chain drain so that long filters such as reverbs can fade out properly. - Stability and compatibility improvements in JACK apps. - Better Bluetooth compatibility with more devices. - libcamera plugin improvements. - Many bugfixes and improvements all over the map. ## PipeWire - Fix compilation on ARM. - Log topics are added to most modules. - Documentation updates. Many improvements to the layout. Reorganisation of the modules and groups. - Share a work queue for all links and nodes. This removes the need for a separate eventfd per link and per node. - Catch errors in the map implementation. - Add option to compile without dbus support. - Fix biquad frequency. It was using the wrong sample rate. - Fix a potential crash when destroying nodes, in some cases the node would not be deactivated properly. - Add some more helpers for dealing with properties and their values. - Implement flush and reset on virtual sinks/sources. - Make it possible to let virtual sinks/filter-chains run and drain after being idle. - Fix a bug where the quantum could exceed the maximum because it was scaled with the sample rate. - Fix channel_map parsing in module-zeroconf-discover so that the remote channel map is used. - pw-stream errors emitted on the proxy are reported but not fatal any more. They are usually used by the session manager to signal status to the client but otherwise does not really cause an error on the client. - Links now also store the output and input node id in the global properties so that applications can parse and use them regardless of how the link was made. (#1723) - pw-stream and pw-filter now have an event to notify commands. - The echo-cancel module can now operate on larger quantums. - pw-cat now uses the right metadata to find the default devices in --list-targets. ## media-session - Don't try to remix unpositioned streams when linking. This ensures that linking to Pro-Audio nodes does not remix the stream channels but links them as they are, one by one. - media-session is now moved to a separate module to accelerate its deprecation in favour of WirePlumber. ## SPA - Many libcamera improvements, handle MemFd buffers, handle errors gracefully. - Small improvements to make interface fall-backs easier to implement. - Add support to enable flush-to-zero and denormals-are-zero to avoid high CPU usage when dealing with denormals. - AUX13 channels are no longer reported as AUX12. (#1727) - Devices with more than 32 channels in Pro-Audio mode now only uses AUX channels. - Improve windowing function of the resampler to reduce aliasing and improve the quality. ## JACK - Port connect callbacks will not only be emitted after the port has negotiated buffers, which improves compatibility with applications that try to use the port right after the callback (jack_midi_latency_test). - Fix crash when midi ports were removed and being monitored, like in Ardour. ## pulse-server - The pulse tunnel will now use the specified format/rate/channels. - Improve lookup of default source and fall back to the monitors when no sources are available. - Mark some nodes as network nodes so that we can set the NETWORK flag correctly. ## GStreamer - The GStreamer element not releases the buffers in the stream again in all cases so that they can be reused by other streams. ## v4l2 - Add a v4l2 LD_PRELOAD library to emulate v4l2 system calls on top of PipeWire. This is tested with firefox and GStreamer and is known to not work with Chrome. ## Bluetooth - AAC compatibility improvements. - Disable hardware volume for "Tribit MAXSound Plus" and "SoundCore mini". - Add quirk to disable faststream. Disable faststream on "FiiO BTR3". - Add a dummy AVRCP player to improve compatibility with some devices. # PipeWire 0.3.38 (2021-09-30) This is a quick bugfix release that is API and ABI compatible with previous 0.3.x releases. ## Highlights - Topic based logging was added to improve debugging. - An off-by-one error was fixed in the audio resampler that could cause distortion when downsampling. - Various bluetooth compatibility improvements. - More fixes and improvements. ## PipeWire - module-pulse-tunnel now has better default latency to make it work better in more cases. There is also an option to configure the desired latency. - pw-cli now has readline support. - Topic based logging was added. Log lines can now be filtered by topic using wildcards. This should improve debugging. - The systemd service files should now have better descriptions. - Fix a crash in module-zeroconf-discover when unloading. - Fix a crash in filter-chain when using unaligned memory. ## ALSA - Sync the udev rules and profiles with pulseaudio. - Fix a memory leak. ## SPA plugins - An off-by-one error was fixed in the resampler that could cause distortion when downsampling. (#1646) ## Bluetooth - Avoid probing the native backend because it might block for DBus activation. This fixes some long startup times. - Fix the kernel version check, 5.14.x kernels should also support mSBC. - Fix FastStream microphone support in more cases. - Add workaround for Intel AX200. - SCO sink should now also work in follower mode. ## PulseAudio server - Make the service file require a session manager. # PipeWire 0.3.37 (2021-09-23) This is a quick bugfix release that is API and ABI compatible with previous 0.3.x releases. ## Highlights - Capture and playback is now avoided even more on unavailable devices. This should fix some issues where an unusable microphone was selected by default. It should now also again be possible to select an unavailable device as the default. - Native DSD audio playback is now supported. pw-cat can now also play DSF files with the -d option. - JACK stability improvements with buffer-size and samplerate changes in some apps. - Many cleanups and bugfixes all over the place. ## PipeWire - pw-metadata -d does not cause an infinite loop anymore. (#1622) - Increase some plugin buffer sizes to fix some issues with many channels. (#1620) - Protect the global plugin list with a lock. Make sure pw_init() is locked. Fixes some issues with concurrent ALSA plugin usage. ## media-session - Unavailable devices can be set as the default again. (#1624) - Do a better check if a device has available routes and avoid selecting devices with unavailable routes as default. - Media-session was moved to its own directory. It used to live in examples but it is past the example stage and it interferes with the build options for the real examples. ## Bluetooth - The hardware quirk database is now loaded by the plugin instead of the session manager. This makes it also work with wireplumber. ## ALSA - The ALSA mixer now handles device removal much better. (#1627) ## libcamera - Many fixes and improvement to the libcamera plugin. (#1513) ## pulse-server - Improve compatibility with pulseaudio module arguments. - Parse channel_map arguments in module-loopback. (#1486) ## JACK - Delay emitting the samplerate and buffersize callbacks until the client is active. This fixes some crashes with Carla and other JACK apps. # PipeWire 0.3.36 (2021-09-16) This is a quick bugfix release that is API and ABI compatible with previous 0.3.x releases. ## Highlights - A quick update with mostly only bugfixes and small improvements. - Capture and playback is now avoided on unavailable devices. This should fix some issues where an unusable microphone was selected by default. - MIDI output should not stop randomly now. - The GStreamer elements are much improved, cheese should work a lot better now. - Virtual sinks and sources should now always show up immediately. - JACK processing is now delayed until buffersize and samplerate are emitted. This should improve stability of many JACK apps. - JACK transport sync is now implemented correctly so that preroll in bitwig works. ## PipeWire - The module dir environment variable can now contain multiple paths. - Documentation now contains dot graphs of dependencies. (#1585) - config min/max/default quantum values are now scaled with the samplerate. - A potential crash was fixed where destroyed memory was still used by a node. This could cause crashes in cheese. ## pipewire-media-session - Only allow passthrough for passthrough formats (S/PDIF) for now. (#1587) - Improve bluetooth profile autoswitch. - Don't try to route audio to nodes with unavailable routes. ## ALSA - Pass the right AES bits to the alsa device when opening an S/PDIF stream. - Fix a bug in the MIDI bridge port management logic. When a port was added and immediately removed, output would stop. ## GStreamer - The GStreamer source now handles the flushing state correctly. - All blocking operations now have a 30 seconds timeout, to avoid infinite locks. ## Plugins - V4l2 Device formats and controls are now passed on the node, just like with audio devices. - audioconvert now also exposes the softMute property. ## JACK - Improve stability when changing buffer size and sample rate dynamically by pausing the processing until the application has handled the callback. - Improve handling of timebase master. When the master was moved to another driver, it did not attempt to become a new timebase master on the new driver. (#1589) - Implement transport sync to make preroll in bitwig work. (#1589) ## pulse-server - Fix an issue where virtual sinks/sources would not show up immediately. (#1588) # PipeWire 0.3.35 (2021-09-09) This is a bugfix release that is API and ABI compatible with previous 0.3.x releases. ## Highlights - S/PDIF passthrough over optical or HDMI is now implemented. - Some critical fixes to MIDI, draining of streams and various modules. - skypeforlinux should work better now after adding it to the quirks database. - Bluetooth codecs are now in separate plugins to make it easier to ship them. ## PipeWire - Drain was fixed in pw-stream. In some cases it would not clear the drain state correctly. Fixes the issue where speaker-test would only play one channel. - Loopback connections to a driver will now activate the driver. This fixes an issue where MIDI connections between devices or some applications (puredata) would not get any MIDI messages. (#1559) - The audiomixer can now mix more formats. Together with the passthrough improvements this can be used to avoid conversions to/from the DSP format in some cases. - Make sure we idle drivers when removing a node from it in all cases. JACK clients could keep a driver node busy. - Add new methods to accumulate object info. The old one was difficult to use when applications need to accumulate multiple changes. - A new interface to load modules has been added. Plugins can use this to ask the host (PipeWire) to load spa plugins. - Increase param buffer size to handle larger params. Nodes with a large number of channels would sometimes not have properties. (#1574) - Concurrent link negotiation that caused some links to not work, is now avoided. This fixes monitor ports in Ardour6. - Small tweaks to how the quantum and rate are handled when nodes move between drivers. Make node.lock-quantum work with node.latency ## PipeWire modules - The convolver plugin in filter-chain has been optimized some more. - The echo-cancel stream properties were improved so that it actually can remember the streams it links to. (#1557) - module-pulse-tunnel had the buffer attributes wrong and would cause high latency with older pulseaudio servers. (#1434) - module-roc had the properties configured wrongly, which would cause it to not work at all in most cases. (#1538) - There is now an example of a 7.1 virtual surround sink using the hesuvi impulse responses. - The convolver now supports dirac pulses as the IR. ## ALSA - UCM config is now cached per device, using up less memory. It also temporarily works around a problem in alsa-lib that is now being patched and rolled out. Should stop devices from disappearing when logging out and back in. (#1553) - Fix the MIDI clock rate matching. It was too sensitive to small changes and would spiral out of control and break MIDI rather quickly. ## pipewire-media-session - The media session can now save and restore IEC958 (S/PDIF) codecs for the sinks. - Passthrough of IEC958 (S/PDIF) content is now possible. If the client and the sink contain a compatible set of codecs, an exclusive connection can be made between client and sink to pass the encoded S/PDIF content directly to the device. - Use new introspection info update methods to suspend nodes in all cases. Sometimes, nodes would fail to suspend because the state info was not evaluated. - The media session can now work in non-DSP mode, which will try to avoid any audio conversions between client and device when possible. But, this will also disable compatibility with JACK applications. ## Bluetooth - Bluetooth codecs are now compiled into separate plugins which are dynamically loaded. This makes it possible to change the plugin implementation or ship plugins separately without having to recompile the bluetooth module. ## PulseAudio server - Delay stream create reply until the stream is linked to a sink/source. - The device-restore extension is now implemented. This makes it possible to configure the IEC958 (S/PDIF) codecs supported by the sink with pavucontrol. - skypeforlinux now uses the same quirks as teams to make the sinks show up in all cases. This fixes the issue of not being able to hear the remote end in skypeforlinux. ## JACK - Improve catia and carla compatibility by caching objects a little longer after being removed. (#1531) - JACK ports now notify the negotiated format correctly. - A potential deadlock was fixed when multiple threads would perform a call that would require a roundtrip. - Improve bufsize callback, it should not be called right after doing activate() but only when the buffersize changes later. - Add tweak to disable the process lock. Some older apps might not expect it. (#1576) ## Docs - man pages are now generated with rst2man. - DMA-BUF docs were updated. - Documentation updates. # PipeWire 0.3.34 (2021-08-26) This is a bugfix release that is API and ABI compatible with previous 0.3.x releases. ## Highlights - Fixes some critical issues with previous release. Such as devices not showing up and default devices being lost. - Support for consumer driver streams to make the producer v-sync to the consumer monitor in a headless compositor setup. - Improvements to routing of streams. - Bluetooth battery status support for head-set profile and using Apple extensions. aptX-LL and FastStream codec support was added. - Internal latency of ALSA devices can now be configured. - A fast convolver was added to the filter-chain to implement virtual surround sinks or reverbs. ## PipeWire - Add support for streams that are driver nodes for the graph. This was already possible for source streams but it is now also possible for playback streams. This can be used to let a producer v-sync to the consumer monitor in a headless compositor setup. (#1484) - State files are now stored in XDG_STATE_HOME instead of XDG_CONFIG_HOME. They will still be loaded from the config home if they are not in the new state home, to ease migration. - Set a driver on inactive nodes to make transport work in xjadeo. (#1491) - Fix parsing of filter-chain controls. - A new FFT based convolver was added to module-filter-chain. It uses a 0-latency 2 stage convolver with small FFT for the head and a large FFT for the tail of the convolution. A convolution can be used to implement IR based reverbs, HRIR surround sound or other convolution based operations. An example HRIR virtual surround sound sink has been added as well. - module-filter-chain was reworked a bit to support more config options for the plugins. - Endian conversion and alaw/ulaw formats are now supported for streams. - pw-cat will now suggest a samplerate for the graph. - SPA_PLUGIN_DIR can now search in multiple paths separated with a ':'. - Passthrough mode has been worked on and has been partially merged. S/PDIF definitions have been added and ALSA devices updated to report and configure S/PDIF formats. The session manager changes to fully configure and enable passthrough mode will hopefully be merged next time. - Fix a race in pw-stream where it would not always emit the right events. ## ALSA - Fix volume changed check. It was checking against the wrong value and this could cause rounding errors. - The ALSA plugin now also uses RT scheduling. - Fix the behringer UMC202 usb device id, it was using a generic TI chip ID that caused problems. - Fix USB devices that don't show up anymore. Use an ALSA workaround to fix this. (#1478) - Add a rule for the new firmware of Sennheiser GSX 1200. - ALSA sink and source can now use ProcessLatency param to configure the internal latency. The latencyOffsetNsec property is also exposed so that the latency can be adjusted in pavucontrol as well. ## media-session - Fix a critical issue where the default device was not remembered anymore when it was removed. - Fix the issue where some apps need to be restarted when nodes go away and reappear. - Improve routing of streams. Streams that have a specific target set will now be moved to the target when it appears instead of staying on the fallback. - Small memory leak fixes. - Try to switch back to the user selected profile after finishing a Bluetooth recording. ## Bluetooth - Add support for HF indicator 2 battery status. - Add support for XAPL battery status. - Set the Communication intended role for HFP profile. - Enable SBC-XQ by default if not disabled by quirks. - Fix some potential crashes due to excessive polling. - Add aptx-LL codec and enable duplex for aptx-LL devices. - Add FastStream codec. This is a codec that can use a duplex SBC channel. ## PulseAudio server - Suggests a samplerate for the graph. - Support for handling S/PDIF (IEC958) formats was added. This will start working when the session manager supports configuring streams and nodes in passthrough mode. - Be smarter when handling devices without a negotiated format yet so that they are visible as well. This makes virtual devices show up immediately. ## ALSA plugin - Now suggests a samplerate for the graph. ## JACK - The jack.pc file can only be generated with meson >= 0.59.0. When the jack-devel option is enabled, it will generate an error with older meson. - Small stability improvements when connecting/disconnecting in Ardour. # PipeWire 0.3.33 (2021-08-05) This is a bugfix release that is API and ABI compatible with previous 0.3.x releases. ## Highlights - Better support for virtual sinks/sources for Pro Audio profile. - Better DMA-BUF format modifier negotiation. - Support multiple sample rates in the graph. Not enabled by default yet. - Bluetooth can now automatically switch between headset and audio profile. - Documentation updates. - Many improvements and crasher fixes. ## PipeWire - Make AUX channels an official channel map, use this for the PRO audio profile so that we can name the channels. This make it possible to define virtual sources and sinks for Pro Audio devices in a more reliable way. - Fix scheduling of some virtual sinks/sources. (#1407) - Fix potential corruption of ringbuffer because of multiple concurrent writers. This might be the cause for many reported crashes. (#1451) - Don't place sockets in $HOME. (#1443) - Improve DMA-BUF negotiation. Add a flag to avoid fixation of a property so that producers can negotiate more efficiently. This is used to negotiate DMA-BUF modifiers, which should make more efficient use of the GPU. (#1084) - Add support for multiple sample rates. The graph can switch when IDLE to one of the supported rates. Add an option to lock the rate as well. This is not enabled by default yet because of driver bugs that need to be worked around first. - Add node.lock-quantum property that can be used to lock the quantum in place. - Improve latency reporting in the loopback module. - Make new client-node method to send the peer port id to the mixer. This can be used to know where the buffers entering the mixer are coming from. (#1471) ## Tools - pw-top should now also correctly show bluetooth devices. (#1540) ## media-session - Handle unset of the default node. - Added a module that can switch the bluetooth profile to headset profile when a stream wants to record from it. ## JACK - Only call the jack callbacks when the client is active. Some JACK applications don't expect callbacks before the client is active and crash (x42-dpl). (#1461) - Emit client unregister event. - Add per-client match rules in the config file to set app specific configuration and tweaks. (#1456) - Use peer_id to implement jack_port_get_buffer() from one of our peer ports to get the data before it enters the mixer. Makes the capture monitors work in Ardour6.8. (#1471) ## Bluetooth - Add some broken kernel versions to the mSBC blocklist - Avoid looping and consuming CPU when we can't write to the BT socket. - Use libfreeaptx instead of libopenaptx. - Fix rounding errors in HW volume conversion. ## PulseAudio server - implement module-switch-on-connect to emulate pulseaudio behaviour of new devices. Some desktop environments expect this behaviour and break otherwise. - Fix stream cleanup, make sure the stream is stopped before destroying it. Might be cause for some of the reported crashes. - Update message API to use the JSON format. ## Other - Many documentation updates. - Many cleanups and small improvements. - Support the latest libcamera version. (#1435) # PipeWire 0.3.32 (2021-07-20) This is a bugfix release that is API and ABI compatible with previous 0.3.x releases. ## Highlights - Real-time priority handling for threads was reworked. Freewheeling will now drop RT priorities to avoid being killed. - Problems with filter chains and echo-cancel being linked in a loop was fixed. - alsamixer should now be able to see the mixer controls again. - JACK has seen some latency reporting improvements that make Ardour report latencies correctly. - Many bugfixes and improvements. ## PipeWire - Fix a bug in the neon audio resampler code. - There is now a node.link-group property to relate linked streams. this can be used to track the dataflow with coupled streams. - Fix a crash when recalculating latency on a destroyed port. (#1371) - Filter chains and other modules that create streams can now also be added to the daemon config itself. (#1309) - Fix some potential deadlocks in timerfd. (#1377) - Feedback links are skipped when recalculating latency to avoid loops. - The dummy driver and null-sink now stop the timerfd when following another driver instead of generating useless graph wakeups. - rt.limit was increased to 2 seconds. Some applications got killed because they run lengthy code in the Real-Time thread. (#1344) - Fix s24_32 to float, it was not sign extending properly. (#1393) - The performance of the feedback loop check algorithm was improved a lot, making complex graphs start much much faster. - The zeroconf publish module now doesn't republish nodes every time the volume changes. (#1406) - A potential memory corruption error has been fixed in the loop that could cause random crashes. - Mempools can now be created from multiple threads at the same time. ## media-session - Loops in coupled streams are now avoided. (#1394) - Port changes for inactive profiles are ignored now by the default-route module. (#1403) ## ALSA - Make sure that alibpref is not part of the device node name because it is random. (#1362) - Fixed an off-by-one that could cause midi events to end up with a wrong timestamp and thus being discarded by some apps. (#1395) - Fix some memory leaks when destroying a card object. ## JACK - Fix some invalid cycle wakeups that could cause JACK application to run with a 0 buffer size. (#1386) - JACK can now use rtkit to manage realtime priorities on threads. - The Real-time priority is dropped when entering freewheel mode to make sure we don't get killed when using too much CPU. - jack_recompute_total_latencies() is now implemented, fixing the latency reporting in Ardour. (#1388) - Fix some overflows in time calculations. - Ensure frame_rate in position is never 0. - Graph callbacks are now emitted as well. ## Bluetooth - RTP payload type is now set correctly for aptX, LDAC and SBC, which should improve compatibility with devices that care about this. ## PulseAudio server - There is now a quirks database to deal with bad clients. The database is builtin but can be made external later. - Teams is now lied to and told all sink/sources use s16 samples to make it show all sinks/sources. - Firefox is forced to remove the DONT_MOVE flag on capture streams so that you can move firefox streams with other tools. - The UNDERFLOW warnings are now made into info log messages to not spam the log too much. Many application just let things underrun and PulseAudio did not warn about this either. (#910) ## ALSA plugin - The alsa plugin now uses the right metadata for finding the default source and sink, which makes the volume controls reappear. (#1384) ## Other - Cleanups in pulse-server and pipewire. - Documentation additions. # PipeWire 0.3.31 (2021-06-28) This is a bugfix release that is API and ABI compatible with previous 0.3.x releases. ## Highlights - Fixes for alsa-lib 1.2.5 - New pulseaudio modules: module-avahi-zeroconf, module-pipe-source, module-roc-sink, module-roc-source. - JACK has seen massive stability improvements. Locking and correctness wrt to callbacks has been reworked. Also thread priorities have improved. - Handle various crashes and lockups when running out of file descriptors. - Bluetooth now uses a hardware database to disable non-working features on listed devices. - Scheduling quantum and rate can now be changed dynamically with pw-metadata. - Many bugfixes and improvements. ## PipeWire - Improve cleanup of context in error cases. - There is now a pw-test framework for improved unit tests. - Improve property serialization to valid JSON. - Fix some macros to work with better with coverity. - Metadata permissions are checked now. Clients need the M permission on an object to be able to set metadata for it. - The core metadata object will now remove metadata for removed objects, the implementor does not need to worry about that anymore. - Audioadapter will now follow the rate of the graph with the resampler adjusting itself dynamically. - Core now has a metadata implementation helper. A context will expose a metadata with settings that can be changed at runtime. This can be used to change the loglevel or graph quantum and samplerate on the fly. - An infinite loop was fixed in the audio converter. - Handle out-of-fds more gracefully. Handle truncated control data by dropping the client connection. - Fix profiler crash with many streams. - Improve latency handling in pw-filter. There is now a default handler and a ProcessLatency parameter to simplify latency reporting. - Latency reporting was improved in devices and streams. - And example sink/source was added. ## ALSA - hardware mute and volume are now properties on the Route param to make things easier. - More fixes for alsa-ucm 1.2.5. ## Tools - spa-json-dump now properly encodes string and keys. - pw-dump now shows the correct subject of the metadata. ## PulseAudio server - Ensure the node.description is set, some applications crash otherwise (TeamSpeak). - Module loading and unloading was improved. - module-avahi-zeroconf was implemented. - module-pipe-source was implemented - module-roc-sink and module-roc-source was implemented. - The maximum amount of connections has been limited to 64, like pulseaudio. - Handle out-of-fds more gracefully. - Fix overflow of read/write pointers. - Source and sink state are now decoupled from the monitor state and will report IDLE when not playing anything. ## media-session - Port switching should now happen to/from the port that actually changed. ## JACK - The locking was reviewed. All callbacks are now emitted from the PipeWire thread with the lock released and the process function will be disabled for the duration of the callback. This ensures that no two callbacks are called at the same time. - Improve internal consistency and try to never call callbacks with invalid objects. - Monitor port can now be accessed with system:monitor_%d - client threads are now created with SCHED_FIFO and module-rt is used to create the other RT threads. This should avoid SIGKILL from RTKit in some cases. ## Bluetooth - Various bugfixes to improve connections to devices. - Handle delayed UUID connection. - There is now a hardware database that can disable features in listed devices. - Use libusb to detect availability of mSBC. ## ALSA - The virtual device name can now also contain a media role. # PipeWire 0.3.30 This is a bugfix release that is API and ABI compatible with previous 0.3.x releases. This is a quick emergency release to fix some severe problems with the previous release. ## Highlights - Recording from a monitor port should work again. - JACK applications should now be more stable again. - Freewheeling should not lock up anymore. - Fix lockups in many pulseaudio apps. - module-echo-cancel was implemented in pipewire-pulse - Many other stability fixes. ## PipeWire - Improve module path logic. - Improve logger formatting ## PulseAudio server - Make sure to pass 64 bits values for time on ARM 32 bits to avoid protocol errors. - Avoid a crash when unloading module-combine-sink. - Avoid overflow in requested bytes, resulting in stalled audio. - Implement module-echo-cancel. ## Bluetooth - Handle latency parameters instead of failing. ## JACK - Fix locking in many places to avoid deadlocks and crashes. - Fix port rename. - Stop freewheeling correctly instead of deadlocking. # PipeWire 0.3.29 This is a bugfix release that is API and ABI compatible with previous 0.3.x releases. ## Highlights - Latency reporting is now implemented. - Many documentation updates and cleanups. - module-combine-sink was added to PulseAudio server. - Better handling of multichannel input profiles. - Fix 100% volume issue when monitor suspends or profile changes in some cases. - Bugfixes and crashes ## PipeWire - A new module-rt was added to acquire real-time scheduling privileges without using RTKit. - Documentation fixes and updates. Docs are now using a custom theme. - There is now a MANDATORY flag on properties that influence how properties are filtered. - Filter-chain now parses the LADSPA_PATH correctly when it contains a colon separated list. - Move `#pipewire` IRC channel to oftc.net. - Fix an error where param changes were not emitted in all cases. - Implement Latency reporting. Latency values are propagated through the graph so that each node knows the latency to the output/input device. Synchronization in pw-stream has been updated to use this. - Some more upmix cases are added so that LFE, SIDE and REAR can be generated from a mono channel as well. - pw-stream and pw-filter will now emit the process event from the real-time thread in a safe way, potentially avoiding some of the harder to debug crashes. - Fix potential stack overflow with serialize_dict. - Add PIPEWIRE_NO_CONFIG to run without custom config files. - The WebRTC echo canceler was added. Next versions will integrate this better. ## PulseAudio server - module-combine-sink was implemented. - Fix some segfaults when DBus connections fail. - Support for listening on IPv6 was added. - Fix a bug where many flushes could result in requests for too much data from the client, causing sync, latency and garbled sound problems after many seeks. ## ALSA - Also probe input paths for multichannel mappings. This makes multichannel input ports show up in more cases. - Fix headphones/front volume issue on some cards. - Fix max volume issue when profile changes. - Fix issue with UCM local config that was not available when the device was opened in the server but the UCM was opened by the session manager. Fixes alsa 1.2.5 compatibility. ## JACK - Implement latency reporting with the new Latency params. # PipeWire 0.3.28 This is a bugfix release that is API and ABI compatible with previous 0.3.x releases. ## Highlights - Freewheeling was implemented. This makes it possible to export projects in ardour. - A new powerful filter-chain module was added that can be used to created all kinds of filter-chains from ladspa and builtin plugins. - Many more pulseaudio modules are now implemented: module-ladspa-sink, module-ladspa-source, module-pipe-sink, module-tunnel-sink, module-tunnel-source, module-zeroconf-discover - Fix a bug where devices would not appear after logout/login. - Fix a bug where the volume was reset to 0 and devices would have no audio. - Config files are now installed in the data dir, system overrides in /etc/pipewire and $HOME are checked first. ## PipeWire - Implement freewheeling for JACK clients - Add filter-chain module that can be used to construct arbitrary graphs from ladspa and builtin plugins. - Add new property to easily set algorithm params - Add module-pulse-tunnel to tunnel audio to and from a PulseAudio compatible server. - Add a avahi zeroconf discover module, create pulse-tunnel when PulseAudio devices are announced. - Config files are now installed in the data dir, system overrides in /etc/pipewire and $HOME are checked first. - Applications now have their monitor ports named with the "monitor" prefix to avoid confusion with the output ports. - LICENSE clarifications. ## GStreamer - fixes to the pipewiresink plugin. ## SPA plugins - Fix a bug where the volume was reset to 0 - Add events to dbus plugin. This can be used to detect dbus disconnects. ## Media-session - Handle dbus disconnect. - Handle device reservation errors. ## PulseAudio server - Implement module-ladspa-sink and a new PipeWire-only module-ladspa-source - Implement module-pipe-sink - Implement module-tunnel-sink and module-tunnel-source - Fix a bug with module argument parsing - Implement module-zeroconf-discover ## ALSA plugin - improve error handling PipeWire 0.3.27 This is a bugfix release that is API and ABI compatible with previous 0.3.x releases. - Highlights - Fix bug that caused bluetooth devices to stop working. - Fix session-manager crash when switching users caused by the DBus plugin cleanup errors. - Improve volume handling of monitor ports. - Fix GStreamer v4l2 support. - Implement module-remap-sink and module-remap-source in pipewire-pulse. - More fixes and improvements. - PipeWire - Move the loopback code into a module. Use this in pw-loopback and pipewire-pulse. Fix some cleanup crashes. - A dummy echo-cancel module was added. Later versions will include the webrtc echo-canceler. - State files don't have the X permission anymore. - Move i18n core into a private header file. - Stream can now advertise properties and receive property updates. - Fix an issue where the wrong index was used to address a port. It caused Bluetooth devices to stop working. - SPA plugins - Only do LFE filtering on channels we created. - Improve name and description of devices. - Improve cleanup in DBus connections and sources to avoid crash when destroying. - Improved volume handling. Hardware, Software and Monitor volumes are now properly separated and handled. - Support for S8 and S8P formats was added. - Tools - pw-cli can now also create Struct from JSON arrays. - Session-manager - The session manager can now also create passive links. This makes is possible to suspend effect chains together with the sinks when not in use. - Match rules now check the complete property value instead of only the start. - Handle multiple pending param enumerations, take only last result. This fixes some volume update issues. - GStreamer plugins - GStreamer plugins now advertise handling DMABUF explicitly. This is currently the only way to avoid a memcpy for v4l2 devices. - Device support - sync ACP with pulseaudio, merge upstream patch instead of our hack to workaround missing duplex devices. - V4l2 devices don't expose their fd anymore. Previously the fd and mmap offsets were passed to the client to access the buffer memory but that could create security issues. - Bluetooth - Don't unregister the profiles on shutdown because this can cause delay, just close the dbus connection. - Bluetooth devices now try to use the global samplerate from the graph. - PulseAudio server - Implement remap-sink and remap-source modules using the new loopback module. PipeWire 0.3.26 This is a bugfix release that is API and ABI compatible with previous 0.3.x releases. - Highlights - I18n support, with translations merged from PulseAudio. - New pw-link tool. - Many Bluetooth improvements, support for hardware volumes. - Support for 64 channel devices. - Stability fixes and improvements. - PipeWire improvements - The link factory can now also make links between nodes and ports by name so that it can be used in scripts. - Add module-protocol-simple that can stream raw audio on a socket. - Added i18n support. Merge PulseAudio translations for the ACP library so that we don't cause regressions. - Support more than 19 channels in the channel mixer. This makes all channels usable on 32 and 64 channel cards. - Detect if we're running in a VM and allow for tweaking some settings such as the max-quantum to make things work better in VMs. - Fix a potential crash when connecting a client and updating permissions. - Fix a potential crash when trying to link incompatible ports. - Lingering links in error will now be destroyed automatically. - Tools - Added new pw-link tool to list and monitor ports and to list, monitor, create and destroy links between them. - pw-cli can now also list params by name. - pw-dump now outputs Spa:String:JSON types in metadata as properly parsed and formatted JSON so that tools can parse the metadata values using a JSON parser. - Session-manager - Add logind support. The bluetooth monitor can only be started for one user at the time, so use logind detect active seats. - ALSA icon names were improved to match what PulseAudio does. - Improve the bluetooth icon name. Also use the device alias as the device description, like PulseAudio. - Device support - When devices become inaccessible, they are now removed from the PipeWire graph. - Fix datatype selection for buffers in v4l2 and libcamera. - Bluetooth - Various memory leaks and crashes are fixed. - Added support for AVRCP hardware volume. - Added support for HSP/HFP hardware volume. - PulseAudio server - Fix module-loopback connections to monitor ports. - Implement module-native-protocol-tcp. - Handle nodes and streams with > 32 channels. The PulseAudio API only supports up to 32 channels so only make those 32 first channels available with the PA API. - Implement module-simple-protocol-tcp. - Improve events emitted by the server. - Improvements to channels and channel_map properties on modules. one can imply the other and they should match when both given. - null-sink will now have their volume work correctly by default. - JACK - JACK development files can now optionally be installed. PipeWire 0.3.25 This is a bugfix release that is API and ABI compatible with previous 0.3.x releases. - Highlights - Many stability improvements. - Plug fd leak in flatpak detection - add pw-loopback tool and support module-loopback - volume restore for virtual sinks/sources or other sink/sources without hardware volume. - Fix cracks and pops in audio capture. - Many bluetooth improvements and compatibility fixes. - PipeWire improvements - Hex encode invalid SEC_LABEL properties to avoid generating invalid json. - Small fixes to how nodes are started to avoid crashes. - Make sure ports are only scheduled after being fully negotiated to avoid crashes. - Implement coverity into CI, fix some bugs detected by coverity. - Plug leak in flatpak detection. - Fix crash when removing globals in some cases. - Fix crash because the mixer info was not removed from a port in all cases. - Add PIPEWIRE_AUTOCONNECT environment variable to disable stream autoconnect. Also add a config option to disable autoconnect. - Improve wildcard in format helpers. - Add env variable to disable journald logging. - Tools - Add a new pw-loopback tool to loop a capture device to a playback device. - Display localized strings correctly in pw-top - Add some more options to pw-dot - Session-manager - When a new node is configured and some stream have this as the default target, move them to it. - Fix some crashes. - Implement volume restore on nodes without routes. This makes it possible to restore volume on purely software nodes like null-sinks. - Also try to suspend errored nodes so that they may leave the error state and be reused again. - Break endless link loops when something went wrong. - Device support - Fix monitor volumes, they are now separate from the hardware volume. - Fix cracks and pops in alsa capture caused by mismatch between resampler and capture source. - Add start-delay config option to alsa sink. - Ensure the PipeWire midi ports start from a higher number so that the lower port numbers are available to apps as before. - Bluetooth - source devices are now removed when idle - Support using pipewire as Audio Gateway. - LDAC encoding quality can be configured now - Implement codec switching for HFP - Implement codec switching with new device property. - Improved stability and compatibility - Autoconnect device profiles at startup - Add AAC bitrate mode configuration - Make it possible to use an A2DP source as an input device. You can then use your phone as an A2DP microphone, for example. - Remove battery reporting when RFCOMM connections is closed. - PulseAudio server - Add some workarounds for Blueman - Set correct errno values, fixes a hang in load-module of a non-existing module - Try to not send inconsistent information to clients. - Fix some crashes. - Add support for the new send-message API, use this to switch bluetooth codecs. - Fix draining by making sure we are started. - Handle 0 sink and source as the default sink/source. - Implement module-loopback - JACK - Fix some memory leaks when closing a client - Add self-connect config option to limit where clients can connect themselves. - Don't crash when apps call _port_get_buffer() on a port that is not their own but simply return NULL. This fixes a crash in Ardour6. - Improve client added/removed callbacks. Sometimes it would emit a client remove when there were still ports for the client. - make sure midi port names are stable across reboots. PipeWire 0.3.24 This is a bugfix release that is API and ABI compatible with previous 0.3.x releases. - Highlights - Many JACK midi improvements and device support. - Fixes in gnome-control-center default sink/source handling. - Many small performance improvements in alsa device handling and latency. There should also be less cracks/pops and xruns now. - Fixes for gnome-control-center default sink/source handling. - More bluetooth compatibility improvements. - PipeWire improvements - Implement simple upmixing. - Disable the resampler when not used. This improves latency and CPU usage. - Handle max-quantum on devices and try to not make the quantum larger than the device buffer size. - Improvements to how nodes and links are activated. It should now result in less xruns and cracks/pops. - meson uses the feature options everywhere now. - Handle volume remap in the channelmixer. This fixes the channels on multichannel devices. - Try to escape invalid JSON string characters. - Keep better track of changed parameters in audioconvert. - Improve config files, make arrays where needed. - Respect NO_COLOR where possible - Support in-place config file parsing to avoid allocations and improve startup performance. - There is now a config option to enable non-power-of-two quantums. - Preliminary support for upmixing and generating LFE channels. - Session-manager - Default nodes are not stored as JSON in the metadata. This is more readable and introspectable. - More default-nodes and default-routes improvements. Port switching should work better now. - Wait until all devices are scanned before linking clients. - Fixes some crashes. - Sinks (monitors) can now be set as default sources. - Device support - Fix startup timers for alsa devices. - Improve timers in alsa when quantum changes. It should cause less xruns and cracks. - Fix UCM setup of capture devices. - Only disable IRQ in alsa when not batch. For batch devices the hw pointers are updated each IRQ so we need to keep them enabled. This massively improves latency on USB batch devices to the same level as JACK (with small enough period size). - Bluetooth - Improvements to profile switches. - Improvements to volume handling. - Fixes for A2DP sources - Add support for battery status when available. - Many other small improvements. - PulseAudio server - Handle NULL in set_default_sink/source to clear the default. - Implement a workaround for gnome-control-center when setting the default sink/source. It also sets the target in stream-restore to the new default. This fixes moving streams in gnome-control-center. - Fix some races by replying to some requests after the operation completed. - Prefer formats of the extended format API. - Create a pid file on startup to improve compatibility with apps that look for it. - Capture streams can now be moved to monitors with pavucontrol. - Fixes for crashes. - JACK - jack clients can now connect to the 'default' server. - Move midi ports back to the midi client. - Only mark midi hardware ports as terminal/physical. - Use the same midi names as a2jmidid. - match system ports in get_ports. - Improve compatibility with some apps that require a fixed latency. - Beginnings of the libjackserver implementation. PipeWire 0.3.23 This is a bugfix release that is API and ABI compatible with previous 0.3.x releases. - Highlights - Fixes for some critical bugs in last release. - Fix bug where audio was not drained properly at the end of playback, causing repeating sound. - Profile and route switching was improved and should mimic more what pulseaudio did. - Various fixes for xruns in capture and playback. - Bluetooth now supports delay adjustment and various other improvements. - The pulseaudio server now correctly identifies AC3 and DTS streams and returns a not supported error instead of playing static. - Multichannel support was improved in the alsa plugin and the channel mixer. Channels should now play on the right speakers in all cases. - PipeWire improvements - Small fixes and improvements in JSON parsing and encoding. - Improvements to param handling in audioconverter. It would previously not always notify of changes. - Avoid updating some properties that we use internally such as the object id and the node.id. - log.level in the config files is now actually used. - the PIPEWIRE_LATENCY env variable should always override any application settings in filter/stream/jack. - The config file can now contain filer and stream properties to, for example, control the resampler, mixer and latency. - Add sandboxing to the systemd services - Various FreeBSD fixes. - Improve draining and a way to exit the drain state as well. - Many multichannel fixes. Channel remapping should now be correct. - Fix bug with repeating audio at the end of playback because the drain in the resampler was not draining all channels. - RTKit default rt.prio has been increased to 88. This will likely still be clamped to 20 until distros increase the max priority. - Session-manager - Don't try to switch to Pro Audio profile, this should be a user choice only. - Don't crash when metadata was disabled such as when not using the audio features of pipewire. - Rework the profile and route handling. - Add systemd unit files for the media-session - Device names should now also have sane names so that tab pactl completion works on them. - Device support - Fix ALSA format enumeration in more cases. Use the channels and rate as a filter. - Make sure the graph doesn't ever use buffers larger than the alsa device buffer size or we get xruns. - Tuning of the alsa device timeout handling and dynamic resampler. There should now not be any xruns when streams appear and disappear or when the quantum changes. - Fix bug in alsa device when reassigning to a new driver, in some cases the dynamic resampler was not activated and things would drift out of sync and fail. - Fixes in quantum changes for ALSA capture and how the resampler is drained and fed with the new samples. - Bluetooth - Delay adjustment has been implemented now. Bluetooth devices should now be more synchronized with video due to proper delay reporting. Because BT delays can be large, it can cause hickups in some players. - Fix volume in bluetooth devices. - Codec switch improvements. - PulseAudio server - Latency offset adjustment is now implemented and functional for bluetooth devices. It is not working for alsa devices yet. - Handle unsupported formats. Previously we would accept encoded formats and play noise. This fixes AC3 playback in vlc. - Move some of the configurable parameters to the config file. - Fix a fatal use after free when playing samples - Improve module handling. loaded modules now show up in the list of modules and can be unloaded. This also prepares the core for more module implementations later. - ALSA plugin - Fix drain with very large buffers, we need to manually start the stream before draining. - Fix the channel layout handling. - Improve compatibility with apps that expect the poll to only return when there is activity. - Fix drain for capture - JACK - Add a config option to shorten and filter client names - Increase the length of the client name size and make sure we don't exceed the allocated size. - We now include our own jack header files so we can build without depending on another jack-devel package. We don't yet install the headers or provide pkgconfig files. PipeWire 0.3.22 This is a bugfix release that is API and ABI compatible with previous 0.3.x releases. - Highlights - Per client config files replace the module-profiles. It's now possible to tweak settings and load custom modules. - Pro Audio card profile support. You can now select the Pro Audio profile and have raw device access with the maximum number of channels and no mixer controls. This is the usual setup for managing high end Pro Audio cards. - Many fixes and improvements in the JACK library to make devices look and integrate better. - Many bluetooth improvements. Playback should be more reliable and better synchronized. Support for the HFP HF profile. - Small fixes and improvements all over the map. - PipeWire improvements - Add support for restrictions requested by a client. This makes it possible to implement Flatpak policy for emulated PulseAudio clients as well. - Fix removal of params in objects. Previously they would not be removed from the cache. - Remove mlock warnings by default. There is an option to enable them again if you want to check if your system is optimized. - Remove LimitMEMLOCK lines from the service files. They can only lower the system settings and are thus not useful. - Implement per-client config files. Each pipewire client will now read a config file that you can use to configure the context of the client. - Implement state and config load/save in pipewire. This is used by the session manager or other apps. - Make an option to disable dbus support. - Add tool to convert pipewire config to JSON. - Session-manager - Give all permissions to Manager flatpak apps. In the future we will use the Permission store to remember user settings. - Improvements to default audio/sink handling. - Add option to configure device suspend time. - Small fixes in route handling. - Device support - Complain when ACP profile files are not found and use a fallback in order to get something working. - Add volume support to monitor ports. - Fix resume from suspend for ALSA in more cases. - ALSA ACP cards now have a Pro Audio profile that exposes the raw card devices. - Bluetooth - Enable A2DP delay reporting. This improves audio/video sync when playing audio over bluetooth. - Fix stuttering in A2DP source - Tweak buffer size and latency settings to avoid stuttering - More work on HSP and HFP support - Fix initial profile configuration - Add HFP HF support - PulseAudio server - Small tweaks in capture packet size to avoid crashes in some apps. - Detect Flatpak apps and requests the flatpak permissions from the session manager. This means that Flatpak pulseaudio apps will now run with reduced permissions. - ALSA plugin - Reduce min buffer size in the plugin for lower possible latency. - JACK - implement some missing methods to make qjackctl work again. - Use the context data thread instead of making our own. This fixes the issue where the data thread was not given RT priority correctly. - Pass extra jack flags around in port properties. This makes CV ports in carla work. - Many tweaks to the port names and aliases. Unwanted characters are filtered out, giving better names to jack apps. Default device names are now equal to those seen in pulseaudio apps. - Add an option to make a separate client for the monitor ports of a device. This makes it more usable in apps. - add support for system:playback_N and system:capture_N port names for apps that hardcode these port names. PipeWire 0.3.21 This is a bugfix release that is API and ABI compatible with previous 0.3.x releases. - Highlights - Many PulseAudio compatibility fixes. Handling of corked streams, the prebuf setting, seek modes and stream flags are now implemented correctly. - Ports and Profiles are now managed by the session manager and can save and restore previous settings. - ALSA device handling has been tweaked for maximum compatibility at the expense of latency. There are tuning options in the config file. - Improved Bluetooth support. HSP is disabled by default because it is old and deprecated and in some cases causes conflicts with the newer HFP profile. Codec switching is now implemented as well. - PipeWire accepts donations with liberapay now. - PipeWire improvements - Improve draining in pw-stream. - pw-stream now uses busy metadata by default. This makes sure that no writer can write to buffers when readers are still busy. - Fix handling of empty array/choice instead of failing. - Fix crashes when creating properties from empty strings. - Make it possible to pass an array to module-access access.allowed variables - Fix small bug in argument parsing in pw-cat - Session-manager - Restore route volumes in all cases, also when switching routes. - Use a default route volume for unknown routes instead of letting the system decide on a default. - Improve profile handling. Don't try to restore unavailable profiles. Implement the profile switching in the session manager now. - Fix handling of Virtual sources as defaults. - Handle port switching in the session manager. Implement save and restore of default ports per profile. - GStreamer - Fix a crash with zero SPA_PARAM_BUFFERS_size - Device support - v4l2-source will now respect the requested memory types. - ALSA buffering has been tweaked. USB devices should have less XRuns by default. Parameters can be tweaked to decrease the latency on capable devices. Also fix a case where a quantum change would cause an xrun. - Fix mute in bluetooth devices - bluetooth devices are not paused in idle anymore for improved compatibility. - Codec switching for bluetooth is implemented along with config options to select the codecs manually. - HSP for bluetooth is now disabled by default. Most devices support the newer HFP profile and some devices fail when both are available. - Reduce the amount of events the ALSA plugins emit by bundling them. - PulseAudio server - Implement the suspend command - Fixes volume in sample info - Fix playback of samples, sometimes samples would be clipped short. Also implement the target sink for the sample. - Use rate match to feed samples. This way the latency can be kept to a minimum. - Latency has been tuned some more, more closely emulating pulseaudio behaviour. - Improve default sink/source handling. Make sure all events are sent correctly when defaults change. - Handle underrun better without causing sync issues. Make sure to pause in corked state. - Implement rewind due to seeks, fixes GStreamer seeking. PipeWire 0.3.20 This is a bugfix release that is API and ABI compatible with previous 0.3.x releases. - Highlights - Latency was reduced in ALSA and PulseAudio and time reporting has improved a lot. - Bluetooth now has a native HFP backed, SBC XQ and mSBC support. - Many bugfixes and improvements, improved device support. - PipeWire improvements - pw-dump can now dump all objects such as Endpoints - pw-dump has a -m option to monitor changes - pw-dump can now dump metadata - pw-stream can now use the rate-match io to exactly produce the required number of samples for the current cycle. When using this feature, a stream can achieve the same low-latency as pw-filter. - spa-acp-tool can now load a custom profile-set and correctly parses the volume updates - There is now a nofail option when loading modules - The connection has been made reentrant to fix some strange random problems with metadata. - Turn some errors into warnings or simply info. - Executables are now built with PIE - S24OE formats should work now (MAudio FastTrack Pro) - Remove mlock warnings. Add support for mlockall with a config option. - Session-manager - There are now config files for bluez and v4l2 modules - Improve ALSA device and node properties - Bluetooth devices have better properties now. - The default device routing has been improved. - Device support - Port priorities are updated for UCM devices - ACP devices notify change in routes in all cases - There is now RW support in ALSA devices to increase compatibility. - Many improvements to Bluetooth. SBC XQ support can now be enabled with a config option. mSBC can be enabled with an option. - Bluetooth devices not expose Routes so that they look more like how PulseAudio handles them - Gracefully handle missing profile-sets - There is now a native HFP backend - Improve card names in some cases. - pause-on-idle is now disabled for ALSA devices. This can reduce pops and clicks when the device is stopped. - ALSA plugin - Use rate-match to reduce the latency - Implement a _delay() function to get smoother timestamps. - Fix property parsing. Fixes volume changes in alsamixer. - PulseAudio server - Use rate-match to reduce the latency. This also reduces the buffering in audioconvert and improves timestamp reporting. - Implement rate changes now that we have rate-match support. - pactl stats will now work - Fix excessive memory usage when a capture client doesn't read fast enough. PipeWire 0.3.19 This is a bugfix release that is API and ABI compatible with previous 0.3.x releases. - Highlights - Startup after login should be fixed now with inotify used to wait for permissions. - Channels should be mapped correctly now. - Many bluetooth improvements in LDAC, AptX-HD. AAC was also added. Headsets should work better now. - pipewire-libpulse was removed. It is now completely replaced by pipewire-pulse. - Fix a crasher bug in pipewire-pulse and some memory leaks. - Fix a bug with feedback loop that would cause 100% CPU. - A new pw-top tool to display real-time graph performance. - The example session manager now has config files. - The config file format was changed to use the SPA JSON tokenizer. This makes it more flexible and extensible. - PipeWire improvements - Fix debug of id in format channels - Audioconvert should now remap channels correctly in all cases. - Feedback loops were not scheduled correctly and would cause 100% CPU usage. - Small improvements to the profiler to also log incomplete graph status. - a new tool pw-top was added that prints real-time performance stats of the graph. - the rtkit module now sets the nice level to -11 - Session-manager - The session manager would sometimes link dont-reconnect nodes to another node, which would leak monitor streams in pipewire-pulse. - The session manager now has configuration files. Config files can also be placed in the user home directory to make custom configurations. - The session managers now creates unique device and node names for alsa and v4l2 devices. - Device support - Many improvements in Bluetooth codecs, LDAC stuttering, AptX-HD negotiation, LDAC ABR support - Bluetooth supports AAC audio now. - Many fixes to Bluetooth SCO transport used in headsets. - inotify support in device monitors - ACP was synced with the latest pulseaudio code - Fix a bug in enumeration of device ports. - PulseAudio server - seek flags and offset are now supported, making gstreamer pulse elements work better. - Fix a crasher bug in pipewire-pulse, we sometimes would write too much to the ringbuffer - Fix some memory leaks in error cases. - Fix handling of NULL string to locate default sink/source - JACK layer - Ports can also be found with the aliases now, making qjackctl work in more cases. PipeWire 0.3.18 This is a bugfix release that is API and ABI compatible with previous 0.3.x releases. - Highlights - More work in the PulseAudio server. It should be compatible with more applications. - Bluetooth now support extra codecs such as AptX/HD and LDAC. - Support for virtual sources and sink was improved a lot. - Added a new pw-dump tool to dump the objects in JSON formats and for filtering them with tools like jq. - Many more stability fixes and improvements. - PipeWire improvements - Silence some harmless warnings - pw-cli can now be used to set parameters. - Streams now perform the correct channel mapping when linked to non-standard multichannel devices. Previously channels would get swapped. - port, node and device params are now cached in the server. This avoids opening and closing devices whenever some client enumerates formats, which improves performance a lot, especially in cases where opening a device is slow. - Add a command to keep a device open during negotiation. This is used to enumerate and set a format while opening the device just once, improving performance. - The null-sink scheduling was fixed. - A memory corruption bug was fixed in format conversion, this could cause crashes, silent channels or other undefined behaviour. - There is now a simple JSON parser. - Session-manager - Settings files are now stored in JSON. With the json parser this is easier to parse and extend - Device support - Bluetooth now supports additional codecs: LDAC, AptX and AptX HD. LDAC is known to not work very well yet. - ALSA devices will now default to the max supported channels if nothing else is specified. This makes it possible to use 8+ channel cards with the alsa-pcm module, which is not supported with the default alsa-acp module. - Enable mSBC support in oFono. - Add an option to disable hardware mixers - ALSA now improves support for batch devices. - The udev rules had references to Pulseaudio removed in order to not create conflicts. - Fix a potential crash in bluetooth devices when disconnecting. - UCM cards now use HW volume when possible. - PulseAudio server - The id can now be used as the name to locate cards and devices - Report streams with planar formats as well - Better error reporting when stream create fails - module-null-sink can now handle channels, rate and channel_map properties - Add support for 3 types of virtual devices: source, sink and duplex. - set-port was fixed - Some buffer parameters were tweaked to improve performance, compatibility and stuttering with lower latency. - NULL can be used as a name for the device sink/source - Support lookup of monitor names - Set properties more like pulseaudio so that some clients (Teamspeak) don't crash anymore PipeWire 0.3.17 This is a bugfix release that is API and ABI compatible with previous 0.3.x releases. - Highlights - Fix crasher bug for kwin when screensharing stopped. - Massive improvements and compatibility fixes in the PulseAudio server. - The session manager now has a config directory in /etc/pipewire/media-session.d/ It will look for files there to activate session manager modules. Packagers can use this to only activate the audio modules when the PulseAudio server, libjack.so or the alsa modules are installed. - PipeWire improvements - We now clear hooks before adding them. Some application did not clear them and had random data for the destroy callback. - Return -ENOENT from unknown resources so apps can handle this better. It's a common problem when an app tries to introspect and object but it disappeared before the message reached the server. Apps should ignore this. - channelmap information is now passed with the volume settings. - DMABuf is not mmapp()ed anymore with the FLAG_MAP_BUFFERS in the stream or filter. This is because DMABuf usually requires more that just a simple mmap and is better left for the application. - increase the maximum number of ports for a client-node. - adapter and node-factory now support the linger option to keep the objects alive after the creating client disconnected. - Device support - ALSA now handles error in close(), like when unplugging a USB device. - Session-manager - The session manager is now handling DONT_RECONNECT streams without a target node. They get connected to a default node once and then fail to reconnect. - The session manager now exposes the stream setting as metadata. This makes it possible for other components, such as pulse-server to use this information. Information is stored as a json object for easier consumption. - The session manager now has a config directory in /etc/pipewire/media-session.d/ packagers can use this - PulseAudio server - Pulse server now acquire the dbus name. - Improvements in timing and compatibility with many apps. - The stream-restore extension is now implemented so that the event volume can be configured. - Many stability fixes and improvements. - Fix some issues with module-load/unload PipeWire 0.3.16 This is a bugfix release that is API and ABI compatible with previous 0.3.x releases. - Highlights - Fix screensharing for old 0.2 clients - Many pulse-server improvements. There is now a pipewire-pulse binary that is the preferred solution for PulseAudio compatibility. The replacement libpulse libraries are now deprecated. This also makes audio in Flatpak work. - PipeWire improvements - Fix cleanup of listeners everywhere. Force remove of listeners in _destroy to avoid crashes. - Add support for a journald logger module. - Various memory leak fixes - Silence some warnings that spammed the logs. - Fix flush in pw_stream. This fixes small glitches when switching streams in music players. - Various FreeBSD fixes and improvements. - Fix some crashes when destroying objects. - Device support - Reload the ALSA configuration when creating a node so that hotplugged devices work in all cases. - Fix memory leaks in ACP library. This also fixes issues where the mixer device was not closed. - Bluetooth now has support for the mSBC codec for SCO source and sink. - pulse-server - Many introspection and compatibility improvements. It should now be as good or better than the replacement library. - Implement sample cache to make notification events work. - JACK layer - handle errors when linking, fixes jack_connect hang when the ports were already linked. PipeWire 0.3.15 This is a bugfix release that is API and ABI compatible with previous 0.3.x releases. - Highlights - This is a quick update to fix critical issues with the 0.3.14 update, which broke screen sharing and accidentally enabled the experimental pulse-server. - Fix some compatibility issues in pulse-server with pavucontrol and fix an issue that would block the complete server. - PipeWire improvements - Permission checks for new clients are now done from a global context, which makes it possible to assign initial permissions to objects. - Handle EINTR everywhere - Fix an issue with the node state changes where a quick pause/play would hang a client. - Session manager improvements - Disable the bluez5 and pulse-bridge modules by default because they interfere with pulseaudio. These options should only be enabled if pulseaudio is removed or disabled in the system. - Fix an issue where the session manager could end up in infinite recursion while scanning for things to do. - The session manager will now always configure nodes to remix to the channel configuration of the device. This fixes the case where mono streams would only end up on one channel of a stereo device. - Device support - Initial merge of A2DP extra codec support using the new bluez5 API. - pulse-server - Create the runtime directory when it doesn't exist. - Don't ever block the server, use non-blocking IO everywhere. - Fill description of profiles with the name if not otherwise set, this fixes a crash in pavucontrol. - the connection debug category will now also debug pulse messages. - Respect the no_remix flag to make the control panel channel check work. - ALSA plugin - implement pause PipeWire 0.3.14 This is a bugfix release that is API and ABI compatible with previous 0.3.x releases. - Highlights - This release focuses on bugfixes and stability improvements. - A new experimental pulse-server module was added. This module implements the pulseaudio protocol on top of PipeWire and can be used to make flatpaks work with PipeWire. It looks like this might be a better way forward compared to the libpulse.so replacement library. - A2DP bluetooth was reworked. Playback should work a lot better now. Support was also added to automatically link an A2DP source to a playback device, which makes it possible to use PipeWire as a bluetooth receiver as well. - Improvements to the routing and volume restore features of the session manager. - PipeWire improvements - The channelmixer does not normalize volumes anymore. Volumes are only normalized for monitoring streams now. - Streams can actually start in the inactive state now. - The channelmixer can now also convert volume updates from one channel layout to another. This makes saved volumes work even when streams have different channel layouts. - Clients are only registered after the properties have been updated. - Links now have a new active state. - Drivers can now also specify a minimum quantum. This makes it possible for bluetooth devices to specify an optimum quantum for the given codec settings and MTU. - The amount of data sent over the socket was reduced by only sending the data that changed. - Client objects are now exposed after they uploaded their properties, which makes the new object more useful. - Tools improvements - pw-cat will now add metadata to the PipeWire streams. - Session manager improvements - Fix crashes when reading bad data in stored settings. - volume and routing is improved. Settings are now remembered per application or media-role. - The session manager remembers the last device used per stream - Fix a bug when moving streams where it could sometimes end up with linking a stream to multiple devices. - Use RTKit to set realtime priority on the data thread in the session manager. This improves performance of the pulse-server and bluetooth devices. - Add a new property to mark streams that want to capture from the monitor of the default sink. - NODE_TARGET can now also contain the node name. This avoids some lookups in the pulseaudio layer when selecting target nodes by name. - the -e and -d options are more usable now and can be used to add and remove modules from the default list of modules. - Device support - v4l2: add some workarounds for buggy drivers. Add Limited support for droidcam. - ACP: improve selection of default port and profiles. - ACP: add support for using the hardware mixer for more than 8 channel streams. - ACP: support the new port type and availability group found in PulseAudio. - A2DP bluetooth timings were reworked. Automatic linking of A2DP sources was added to make it possible for PipeWire to act as a bluetooth receiver. The code was reworked to allow other codecs such as APTX and LDAC in the future. - Try harder to recover from ALSA errors. - GStreamer improvements - Fix some crashes in the monitor that cause gnome-initial-setup to crash. - PulseAudio layer improvements - Many compatibility improvements. Improved playback in chrome. Fix a crash in firefox when the daemon is stopped. - Fix a leak in the formats. - Fix !ADJUST_LATENCY streams like paplay. - Make the device option in paplay work. - Fix volume/mute notifications, this makes plasma volume updates work again. - Do the conversion between PulseAudio cubic volumes and PipeWire linear volumes. Volume levels should behave now like they did with PulseAudio. - JACK layer improvements - Return an error when we run out of midi events. Some application rely on this behaviour. - ALSA plugin improvements - The ALSA plugin now also supports the node name in the playback_node and capture_node properties. PipeWire 0.3.13 This is a bugfix release that is API and ABI compatible with previous 0.3.x releases. - PipeWire improvements - Add pw-reserve tool to reserve or monitor a device on DBus. - Install spa-resample, a tool to resample a file. - Install spa-acp-tool, a tool to inspect the card profile. - Various fixes and improvements - Fix a bug in pw-stream where a capture stream could run out of buffers and become silent. - Rework the processing loops in the adapter and stream. There is now less latency in PulseAudio and ALSA layers. - Session manager improvements - Improve the device reservation code. We now try to acquire the device using the dbus device reservation API before we probe the device. This avoids conflicts with a running PulseAudio where devices would disappear (because they were locked by the other process). - Don't fail on invalid input from the config files. - Audio devices now have the same name as what PulseAudio would assign. - Device support - v4l2: try to use the format before enumerating the size and framerate. Some drivers don't check the format and might now work better. - v4l2: Fall back to MMAP when EXPBUF fails. Fix MMAP access, just export the fd and the mapoffset. This should make more devices work. - Fix crash in ALSA Card Profile (ACP) code. - ACP: fix selection of default profile. Prefer any possibly available profile over 'Off'. This makes some card at least start with something. - Fix soft volume. After setting the volume to 0, it would stay at 0 until pushed over the max volume. This should fix various volume related issues. - PulseAudio layer improvements - Rework the buffering and latency measurements and tweak the buffer attributes. This should make browsers and media players work better. This should also improve speechd performance. - JACK layer improvements - Fix compilation against newer JACK. PipeWire 0.3.12 This is a bugfix release that is API and ABI compatible with previous 0.3.x releases. - PipeWire improvements * the channelmap converter now handles unknown and strange channellayouts much better. * the resampler is now cleared correctly, avoiding clicks and pops at the start of sound. * Fixes for various crasher bugs. (paplay drain, vlc shutdown, pactl info, ...) * Fix a race condition in the node state changes that caused all kinds of sync and other issues (vlc, mpv, ...) * Improve the binary name property of applications * Fix the scheduling again of nodes that always need a driver such as the jack clients. - Session manager improvements * fix routing to default nodes. Sometimes nodes were not routed to the default node (bluetooth) - Device support * disable channelmap from ALSA by default. This is what PulseAudio does and thus provides better compatibility. * fix a bug in how the resampler was used in the ALSA source, causing distortion and errors when using low latency capture clients. (Discord, webrtc, ...) * Small bluetooth improvements. More work is needed for reliable bluetooth playback. - GStreamer plugins * the device provider now stops the processing loop before shutting down, which avoids crashes (gnome-initial-setup). - PulseAudio layer improvements * the buffer attributes were reworked to ensure compatibility with many more applications such as mpv and audacious. * the pulseaudio layer will now try hard to not hand out invalid channel maps to the application. (avoids crashes in gnome-volume-control). The channel map will now also look more like what PulseAudio does. * the @DEFAULT_SINK/SOURCE/MONITOR@ wildcards now work. This fixes the problem with volume keys when they are bound to scripts using pactl and the default sink/source wildcards. * the PIPEWIRE_LATENCY environment variable now works again * Fix some leaks of ports and port info. Also fix the leak of the context when the mainloop is stopped. * The sink/source format_info array is now filled up completely, this is actually not implemented yet in the real PulseAudio. - JACK layer improvements * jack now returns version 3.0.0 and has PipeWire in the version string so that apps can report this. PipeWire 0.3.11 This is a bugfix release that is API and ABI compatible with previous 0.3.x releases. - PipeWire improvements * Properly cleanup the mixer structures when a port is removed, this should fix client crashes related to port config changes and other random crashes. * Optimize the preferred formats in the audio converter. Higher quality formats with higher performance are chosen first. * Make sure the time reported by pw_stream is always increasing, even when the driver and clock changes. * There is now also a system service and socket that can be used to enable PipeWire systemwide. This is however not recommended and disabled by default. * Fix channelmixer 5.1 to stereo mix matrix. It was not reading the conversion matrix correctly and cause channels to be dropped. The channelmixer will now also normalize the volume, like what pulseaudio does. * The channelmixer will now just copy channels when no layout has been given. It has also optimized paths for this. This makes it possible for apps to request > 8 channels from the alsa plugin (ardour). * Port, Node and Link will now also emit an error on the resources in addition to updating the error in the info. This would make it easier to track negotiation errors in the session manager later. * many small fixes and cleanups. * Fix compatibility: + DOSBox: fix crash because of double free in pw_stream - Session manager improvements * The session manager will now try to configure the client to the channel configuration of the sink/source. It will only do this for downmixing, never for upmixing and also never when the client has the dont-remix property set. It will also renegotiate the channel layout when moving a stream to a new sink/source. * Configuration state is now saved in XDG_CONFIG_HOME. Previously it was saved in $HOME/.pipewire-media-session/ You can migrate the state by moving the files to $XDG_CONFIG_HOME/pipewire-media-session (or $HOME/.config/pipewire-media-session as a fallback when XDG_CONFIG_HOME is not set). - Device support * Bluetooth sources and sinks should work better now. * There is now also a new bluetooth backend using hsphfpd. * fix the ALSA UCM Off profile for alsa pcm devices * improve ALSA port and profile switching. The ACP device will now switch to the best port and profile when availability changes. - PulseAudio layer improvements * Implement some more callbacks. The pulse layer will now also notify applications of stream moved, started and latency changes. * Fix error code when an object was not found. We now return PA_ERR_NOENTITY instead of PA_ERR_INVALID. * Add some support for loading new null sinks. Applications such as pulseeffects use this. Note that pulseeffects does not yet work reliably but can start now. * Improve handling of profile and port updates, it should work much more reliable now. Apps should now also again receive volume updates from sinks/sources. * Fix compatibility: + openal-soft 1.20 + pavucontrol (checks PA_ERR_NOENTITY) - JACK layer improvements * improve default source and sink handling. It was not updated correctly in all cases. * add samplerate and period to the pw-jack wrapper to easily configure the desired samplerate and period for the app. - ALSA plugin improvements * Add a mixer entry in the alsa config file. * Implement support for planar types, rework the processing function to make it more robust. * refuse to load the alsa plugin when linked against 0.2. This catches some old apps linked against 0.2 that want to use the alsa plugin. * Fix compatibility: + linphone (ALSA SIGFPE when _status() is called before _prepare()). PipeWire 0.3.10 This is a bugfix release that is API and ABI compatible with previous 0.3.x releases. - Many improvements to the pulse layer. * GStreamer pulsesink element now works. * Fixes some segfaults. * Enable rtkit for client threads. * fixes capture of monitor stream by name * implement some more extensions, this makes paman work and removes some warnings. - Many improvements to the GStreamer elements * negotiation rework, avoid calling GStreamer methods from the PipeWire callbacks because they might block and cause deadlocks. * Add support for non-string property values. * improve stability after buffer and format renegotiation. * Rework the device provider. * pipewiresink can now provide a stream that can be consumed by apps like cheese. - Many improvements to the JACK layer: * Rework the buffer_size callbacks. Make sure we call the callback from a 'safe' thread and that we don't call the process callback while the application is handling the callback. This improves stability in apps like Carla when PipeWire dynamically changes the buffer size. * Improve compatibility with apps that call get_buffer_frames() with a 0 size (calfjackrack) * JACK can now create nodes that can be set as a sink/source in PulseAudio/ALSA apps (you can make an effects rack and set that as default sink for apps). - Added a group id property for nodes. This makes it possible to schedule nodes with the same driver even when they are otherwise not linked together. To make this work well a new flag needed to be added to nodes to signal when they are ready for processing. Together with the GStreamer fixes, this makes things like: gst-launch-1.0 -v pipewiresrc path=51 stream-properties="props,node.group=1" ! audio/x-raw ! pipewiresink stream-properties="props,node.group=1" work as expected with PipeWire managing the resampling to keep the clocks of the devices in sync. This can later also be used to force devices to be grouped together to create a JACK-like scheduling group. - Streams and filter now use PIPEWIRE_NODE and PIPEWIRE_LATENCY env variables as fallback. - ACP add per device port list. This makes UCM devices expose the right ports. - Fix some segfaults in ACP and UCM. - make pw-cat use the metadata to find default devices. - The media session can now save and load audio device Profiles and Routes (volumes), stream volumes and the default sink and sources. PipeWire 0.3.9 This is a bugfix release that is API and ABI compatible with previous 0.3.x releases. - Fix bad audio in chrome - Remove some errors that are not real errors. - Fix 100% cpu when disconnecting devices. - Improve pulseaudio introspection of formats - Fix JACK metadata handling, carla can now monitor the port it creates and insert midi. - Add a new permission bit (M) that is needed to be able to configure metadata on an object. Improve security of metadata some more, only allow metadata on objects that are visible to the client setting the metadata. - Add support for videocrop in the GStreamer elements. - Improve handling of the runtime directory for the server sockets. Add some reasonable fallback when XDG_RUNTIME_DIR is not set, as suggested in the spec. - Improve ALSA device names from ACP. - Fix various crasher bugs. One in the pulse layer, one in the session manager. - Make alsa plugin respect the PIPEWIRE_REMOTE env variable. - Various compile fixes. PipeWire 0.3.8 This is a bugfix release that is API and ABI compatible with previous 0.3.x releases. - Fix an embarrassing crasher in the JACK layer when metadata keys were removed. - Make it possible to add properties to jack clients with a PIPEWIRE_PROPS env variable. This can be used to make JACK nodes look like a device (like an effects rack). - Improvements in the session manager in how it links ports. Now it will try to link matching channels first and be more intelligent otherwise. The session manager will also configure the stream to the device port configuration when needed. - Add ofono backend for Bluetooth HeadSet support. - Improve default source and sink handling. They are now stored with their id, instead of name, in the metadata. This makes it work better with JACK because of JACK's limited name length. - Improve environment variables to make it possible to create and connect to servers other than "pipewire-0". Implement this in pulseaudio, JACK and alsa layers. - Add an alsa mixer plugin so that alsamixer works with PipeWire. It will configure the default source/sink volumes. - Fix capture devices. There was something wrong with how the resampler was used that caused corruption in the signal when the resampler was active. - We now ship alsa card paths, profile-sets configuration files and udev rules so that we don't have to rely on the pulseaudio ones. - Many build and stability fixes. PipeWire 0.3.7 This is a bugfix release that is API and ABI compatible with previous 0.3.x releases. - Improved PulseAudio compatibility. The alsa card profile code was reused from PulseAudio. Devices now support all profiles, ports, jack detection, UCM and hardware mixers that PulseAudio implements. There should not be (almost) any difference between PipeWire and PulseAudio in how it presents and manages devices. Other missing API pieces such as the default sink/source and move_stream are implemented now. At this point it should be possible to replace PulseAudio with the compatibility layer for those who want to try. - Many fixes and improvements to the GStreamer elements. pipewiresrc now has the ability to periodically resend the last frame. This makes it possible for use-cases like screensharing to only update the screen on changes while still keeping the client side encoder busy. PipeWire elements can now also share a connection between them. - Improvements to the bluetooth nodes. Dynamically adding and removing devices should work much smoother now. Many fixes and improvements to a2dp and sco nodes. - Reduced memory usage by using less pre-allocated memory where possible. JACK clients are especially using less memory. - Support for passive links is added again. These are links that don't cause the associated driver to become active. This makes it possible to have blocks of effects+sinks go to suspend as a group when not in use. - Both consumers and producers can now ask to renegotiate the format. This required some cleanups and improvements to how links and node states were handled. More work is needed to implement more use cases. - Important fixes to how memory is shared with clients. Memory was not correctly freed in all cases, which would result in reuse of the wrong memory. - Support for planar formats for audio and video was added. - Improved error handling in the session manager. - Metadata is now used to manage default audio source and sink devices. The session manager will try to link streams to the default device. Changing the default device will move streams to the new device. PulseAudio and JACK layers respect the default source/sinks. - Metadata is used to tag the desired output device for a stream and the session manager will move streams when the metadata changes. The PulseAudio layer uses this to implement the move_stream feature. - Many fixes to the security modules. The session manager now has a flatpak module that grants permissions to flatpak apps. The PulseAudio layer now respects the permissions of objects. Security related properties are made read-only now. Different access modules can now coexist. - The portal module has been split up in 2 parts: 1) a part living in the daemon that monitors the portal dbus owner and tags all clients from this PID. This part has to run in the daemon in order to securely tag the clients. 2) a part in the session manager that uses the permission store to manage the permissions of portal managed clients. PipeWire 0.3.6 This is a bugfix release that is API and ABI compatible with previous 0.3.x releases. - Extensive memory leak fixing and stress testing was done. A big leak in screen sharing with DMA-BUF was fixed. - Compile fixes - Stability improvements in jack and pulseaudio layers. - Added the old portal module to make the Camera portal work again. This will be moved to the session manager in future versions. - Improvements to the GStreamer source and sink shutdown. - Fix compatibility with v2 clients again when negotiating buffers. PipeWire 0.3.5 This is a bugfix release that is API and ABI compatible with previous 0.3.x releases. - Compiler fixes - Add pw-midiplay and pw-midirecord aliases - Add pw-mididump tool - Add pw-metadata tool to inspect, add and remove metadata for objects. - Docs updates, man pages - install alsa config files - Fix linked sink/source in pulseaudio - ratelimit graph processing warnings - improve buffer handling in GStreamer elements - Fix power usage by removing the queue for the alsa sequencer system announce messages. - Fix metadata clear() method dispatch. - Improve parameter enumeration, make it possible to detect missing parameters vs no-compatible parameters so that we can use defaults in the first case and error in the second case. - Fix cleanup of proxy objects. Stability improvements on plug/unplug in session manager. - Make it possible to set log level from config file - improve debug of param negotiation errors. Log the parameters to stderr/journal. - Make it possible to configure global logger implementation. - Fix NEON detection - JACK and PulseAudio compatibility improvements PipeWire 0.3.4 This is a bugfix release that is API and ABI compatible with previous 0.3.x releases. - A quick update with some important stability fixes. PipeWire 0.3.3 This is a bugfix release that is API and ABI compatible with previous 0.3.x releases. - NEON optimizations for audio conversion (32 and 64 bits) - rework of session manager implementation - Add option to disable modules in the session manager - Release midi hardware devices when suspended - various build fixes - Clean up options of various utils - Stability improvements - Mayor improvements in pulseaudio emulation. Improved timings and compatibility. - Implementation of drain and flush in pulse and alsa emulation. - Implement poll on file descriptors. - Improvement of metadata for jack emulation. - Fix memory and thread problems in jack emulation. - Simplification of state changes. Should make more use cases work in the jack emulation. - Improvements in the gstreamer elements. Removal of extra internal queue. pipewiresink can now be used to play audio. - Add pw-jack and pw-pulse scripts to run pulseaudio and jack applications with the right library path. PipeWire 0.3.2 This is a bugfix release that is API and ABI compatible with previous 0.3.x releases. - build fixes - Added support for data type negotiation. This makes it possible for a client to say that it can handle DMABuf and MemFd and then let the server select a compatible format. - Handle errors when enumerating parameters better. - Add support for rate, format, channels and period_bytes to the alsa config file to restrict what alsa apps can negotiate. - Fix JACK midi output. - Optimizations in common audio format conversions using AVX2. Small optimizations to plugins. - Change the vulkan compute example to an MIT licensed shader. - Remove some hardcoded defaults in the audio and video processing and use the values from the processing context. This also fixes the vulkan example. - Correct the documentation and defaults in the daemon config file. - Fix alsa and v4l2 buffer recycle. A paused client could cause the server to leak all buffers. - Remove some warnings that should be ignored. - Fix a crash in the bluez5 plugins. - Try to select higher quality formats first when negotiating a format with an audio device. - Fix an infinite loop in udev detection in some cases. - Add non-interactive mode to pw-cli. You can now just do "pw-cli ls Port" to get a listing of all ports. pw-cli will now also connect to the default server by default and has options to select a different server. - Allow the server to go up to the maximum quantum (8192 samples or ~=180ms) if a client explicitly wants this. PipeWire 0.3.1 This is a bugfix release that is API and ABI compatible with previous 0.3.x releases. - Don't load the rtkit module by default. It can cause a sigkill, which is not desirable for mutter, for example. Only enable this for the jack library for now. - Don't use pthread cancel by default because it uses a signal that might crash some apps. Only use it for the jack library because jack clients really expect this. - Build fixes for -Werror=suggest-attribute=format - improve error messages, don't report harmless errors and warnings. Try to send error messages to the proxy that started the operation or is the owner of the object. - pw-cat: midi improvement, add midi recording and dump in verbose mode - fix properties when loading spa-nodes from the config - Fix and update some examples - jack: check arguments and don't crash when invalid - Fix buffer memory upload. - jack: fix compatibility with zrythm. Fix timemaster install, improve sample_rate callback. Fix reposition handling. - fix crash in port after buffer negotiation error. - add support for control ports in pw_filter - fix cleanup of the metadata module - improve param enumeration. - Clear stream buffers when the format is cleared. - Add create-object command in the config file to create object from a factory. - Fix crash after the driver was not removed from unassigned nodes. Also properly pause inactive nodes. - Use "true" and "false" in properties when we are talking about a boolean. - pulseaudio: improve compatibility PipeWire 0.3.0 The 0.3 release is a major milestone in the development of PipeWire. It features a complete redesign of the scheduling mechanisms that make it possible to run a JACK compatibility layer with comparable performance to JACK2. The API has been reworked and is declared stable now. All development files and runtime paths are versioned so that future incompatible changes can be done without breaking existing applications. PipeWire 0.3 also includes a (now mandatory) session manager that populates and controls the PipeWire graph. This example session manager is very simple and not configurable. It is expected that future version will either switch to a more flexible session manager (like WirePlumber) or improve the configuration options of the example session manager. PipeWire 0.3 includes both PulseAudio, JACK and ALSA compatibility libraries that are known to support a wide range of applications. The ALSA library is pretty complete at this point. The JACK and mostly the PulseAudio compatibility libraries need more work. See the Wiki pages for the current compatibility problems. We do not yet encourage people to switch away from their existing audio solutions (PulseAudio or JACK) but we would love to hear from people who try it anyways. Future versions will mostly focus on improving compatibility further to make PipeWire a drop-in replacement. PipeWire comes with some GStreamer plugins to consume and produce data for PipeWire. The consumer (pipewiresrc) is working well in most cases. The sink (pipewiresink) is known to be somewhat problematic for now. PipeWire 0.2.97 Eighth pre-release for upcoming 0.3: - Build fixes - pw-cat improvement: Fix remote name, add midi support - add device subscribe params for completeness - jack and pulseaudio compatibility fixes - Fix a bug in resampler, add quality option, tweaked quality settings, tested now against https://src.infinitewave.ca/ testsignals and submitted results for publication. - Fix awkwardness in buffer negotiations, the default number of buffers was 4 and jack could only handle 2, causing corruption. Also implement negotiation of Step ranges. - Fix device reservation to work together with pulseaudio, previously we would block pulseaudio. PipeWire 0.2.96 Seventh pre-release for upcoming 0.3: - jack: improve compatibility - Fix unit test - Fix license of jack and alsa libs - Make start/stop more threadsafe - Fix rt-kit again, add params to configure things, increase default soft/hard limits to avoid being killed. - version 0 compatibility improvements, tested with firefox, cheese, GStreamer and chrome using compat layers. - Fix timing for gstreamer source - Require libspa in pkg-config file - Limit buffers to 16 to support old clients PipeWire 0.2.95 Sixth pre-release for upcoming 0.3: - Fix tests for big endian some more - Improve v2 compatibility mode: improve type negotiation and update_permissions - Workaround for firefox screen sharing PipeWire 0.2.94 Fifth pre-release for upcoming 0.3: - Fix man page names - Fix jack set_sync_timeout - Improve JACK compatibility with apps that cache buffer pointers. - Improve mlock failure warning message, add property to configure if mlock should be used. - Improve OBJECT_PATH in alsa objects - Install in versioned directory - Add pw-profiler tool - Improve pulseaudio compatibility wrt pa_operations - Thread safety fixes in remote nodes when activating/deactivating - Improve JACK names on duplicates - Add option to ignore failure when loading modules PipeWire 0.2.93 Fourth pre-release for upcoming 0.3: - Fix unit tests on 32 bits - Append -pw version to pulse and jack libs. This way we can install it next to the real libraries and use a symlink to enable it. - Improve jack support by killing threads with pthread_cancel. This then also remove the eventfd from the data-loop, making it maybe a little faster. - Fix jack_client_close() compatibility - Fix some segfaults in the session manager - Improve debug of protocol messages - Add examples options - Don't fail when alsa is not found - Fix some compiler warnings with a new spa_aprintf() helper. - Add pw-cat, the simple audio playback/record tool - Rename pipewire tools to pw- prefix - Add improve pw-cli object dump feature PipeWire 0.2.92 Third pre-release for upcoming 0.3: - Improve old version check some more - Fix unit tests on little/big endian - Fix compilation when CPU has no optimisations - Install jack and pulse libraries - Handle -EACCESS in flatpack access module PipeWire 0.2.91 It is mostly a bugfix release to make the new version install and run correctly in distros. - Install session manager, fix path to find the session manager - Fix alsa buffer reuse - Small fixes for crasher bugs - Implement pw_core_set_paused() to suspend/resume even processing. This can be used when using multiple connections to a daemon and one needs to pause one connection until the other one completes an action. Used by session managers. - Improve old version check PipeWire 0.2.90 This is the first pre-release of the 0.3 version. It consists of a major rewrite and is not API or ABI compatible with the 0.2 branch. PipeWire 0.2.7 This is mostly a bugfix release and is API/ABI compatible with previous 0.2 versions. Work is ongoing in the work branch that features a completely new scheduling method that will enable audio support. Some of these API changes are backported in this branch. - Add support for alsa-lib 1.1.9 which changed the include path - Improve error checking and reporting in the protocol - deviceprovider: fix probing without starting - add sentinel to some functions - compiler fixes for musl - Revert object tree permission checks that broke things, this is probably not a good idea (and the tree of objects is going to be removed later) PipeWire 0.2.6 - Improve error checking for threads - Fix some memory and fd leaks - Fix compilation with C++ compilers and clang - DISABLE_RTKIT should now not try to use dbus at all - Camera Portal fixes: - add Camera media.role - Rename module-flatpak to module-portal - Use the portal permissions store for camera checks - Actually use the passed fd in pipewiresrc - Make properties with "pipewire." prefix read-only - Add security label to client object - Enforce link permissions - Permissions of objects are now combined with parent permissions - Remove libv4l2 dependency, it is not used - Improve format negotiation in autolink #146 - Try to avoid list corruption with event emission #143 - Fix destroy of client-node memory corruption - Various small improvements PipeWire 0.2.5 - build fixes for systemd - Add cursor and bitmap metadata. This can be used to send a cursor sprite with the video stream. - permissions were set too strict for non-flatpak clients - Fix crash in loop caused by thread unsafe hook emission - Add more error checking for thread-loop - Small cleanups and bugfixes PipeWire 0.2.4 - Install man pages in right directory - Add systemd socket activation - Various memory leak and corruption fixes in properties, dbus and buffer mmapped memory. - Fix v4l2 crash on unplug - improve stream cleanup PipeWire 0.2.3 - Fix deviceprovider caps introspection - Refcounting fixes in pipewiresrc - Remove clock interpolation from stream - Improve clock in gstreamer elements - Remove spalib - Fix crash with pw_map - Add version number to hook list - Improve driver mode in gstreamer elements - add daemon options - add man pages PipeWire 0.2.2 - Increment API version and .so version PipeWire 0.2.1 - Various fixes to memory handling - Fixes for shutdown - v4l2 fix enumeration of frame intervals - Make the daemon stop when the setup commands fail - Improve safety of hooks - Update stream API to more future proof version - Add more options to stream API such as scheduling in the main thread and automatic mapping of buffers - Add version file and macros to check compile time and runtime versions of pipewire - Future proof some structs PipeWire 0.1.9 - Various build fixes - Do more permission checks - Add support for doing async connections. This can be used to make connections through the portal later. - Fix device creation from the GStreamer device monitor - v4l2 experiment with controls - move rtkit to a module to avoid dbus dependency - use dmabuf allocator in gstreamer elements - Add DSP module for pro audio cases, remove jack module. The idea is to make a replacement jack client library that talks pipewire directly instead of trying to emulate a jack server. - Various memory handling improvements pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/README.md000066400000000000000000000156521511204443500217660ustar00rootroot00000000000000# PipeWire [PipeWire](https://pipewire.org) is a server and user space API to deal with multimedia pipelines. This includes: - Making available sources of video (such as from a capture devices or application provided streams) and multiplexing this with clients. - Accessing sources of video for consumption. - Generating graphs for audio and video processing. Nodes in the graph can be implemented as separate processes, communicating with sockets and exchanging multimedia content using fd passing. ## Building and installation The preferred way to install PipeWire is to install it with your distribution package system. This ensures PipeWire is integrated into the rest of your system for the best experience. If you want to build and install PipeWire yourself, refer to [install](INSTALL.md) for instructions. ## Usage The most important purpose of PipeWire is to run your favorite apps. Some applications use the native PipeWire API, such as most compositors (gnome-shell, wayland, ...) to implement screen sharing. These apps will just work automatically. Most audio applications can use either ALSA, JACK or PulseAudio as a backend. PipeWire provides support for all 3 backends. Depending on how your distribution has configured things this should just work automatically or with the provided scripts shown below. PipeWire can use environment variables to control the behaviour of applications: * `PIPEWIRE_DEBUG=` to increase the debug level (or use one of `XEWIDT` for none, error, warnings, info, debug, or trace, respectively). * `PIPEWIRE_LOG=` to redirect log to filename * `PIPEWIRE_LOG_SYSTEMD=false` to disable logging to systemd journal * `PIPEWIRE_LATENCY=` to configure latency as a fraction. 10/1000 configures a 10ms latency. Usually this is expressed as a fraction of the samplerate, like 256/48000, which uses 256 samples at a samplerate of 48KHz for a latency of 5.33ms. This function does not attempt to configure the samplerate. * `PIPEWIRE_RATE=` to configure a rate for the graph. * `PIPEWIRE_QUANTUM=` to configure latency as a fraction and a samplerate. This function will force the graph samplerate to `denom` and force the specified `num` as the buffer size. * `PIPEWIRE_NODE=` to request a link to the specified node. The id can be a node.name or object.serial of the target node. ### Using tools `pw-cat` can be used to play and record audio and midi. Use `pw-cat -h` to get some more help. There are some aliases like `pw-play` and `pw-record` to make things easier: ``` $ pw-play /home/wim/data/01.\ Firepower.wav ``` ### Running JACK applications Depending on how the system was configured, you can either run PipeWire and JACK side-by-side or have PipeWire take over the functionality of JACK completely. In dual mode, JACK apps will by default use the JACK server. To direct a JACK app to PipeWire, you can use the `pw-jack` script like this: ``` $ pw-jack ``` If you replaced JACK with PipeWire completely, `pw-jack` does not have any effect and can be omitted. JACK applications will automatically use the buffer-size chosen by the server. You can force a maximum buffer size (latency) by setting the `PIPEWIRE_LATENCY` environment variable like so: ``` PIPEWIRE_LATENCY=128/48000 jack_simple_client ``` Requests the `jack_simple_client` to run with a buffer of 128 or less samples. ### Running PulseAudio applications PipeWire can run a PulseAudio compatible replacement server. You can't use both servers at the same time. Usually your package manager will make the server conflict so that you can only install one or the other. PulseAudio applications still use the regular PulseAudio client libraries and you don't need to do anything else than change the server implementation. A successful swap of the server can be verified by checking the output of ``` pactl info ``` It should include the string: ``` ... Server Name: PulseAudio (on PipeWire 0.3.x) ... ``` You can use pavucontrol to change profiles and ports, change volumes or redirect streams, just like with PulseAudio. ### Running ALSA applications If the PipeWire alsa module is installed, it can be seen with ``` $ aplay -L ``` ALSA applications can then use the `pipewire:` device to use PipeWire as the audio system. ### Running GStreamer applications PipeWire includes 2 GStreamer elements called `pipewiresrc` and `pipewiresink`. They can be used in pipelines such as this: ``` $ gst-launch-1.0 pipewiresrc ! videoconvert ! autovideosink ``` Or to play a beeping sound: ``` $ gst-launch-1.0 audiotestsrc ! pipewiresink ``` PipeWire provides a device monitor as well so that ``` $ gst-device-monitor-1.0 ``` shows the PipeWire devices and applications like cheese will automatically use the PipeWire video source when possible. ### Inspecting the PipeWire state To inspect and manipulate the PipeWire graph via GUI, you can use [Helvum](https://gitlab.freedesktop.org/ryuukyu/helvum). Alternatively, you can use use one of the excellent JACK tools, such as `Carla`, `catia`, `qjackctl`, ... However, you will not be able to see all features like the video ports. `pw-mon` dumps and monitors the state of the PipeWire daemon. `pw-dot` can dump a graph of the pipeline, check out the help for how to do this. `pw-top` monitors the real-time status of the graph. This is handy to find out what clients are running and how much DSP resources they use. `pw-dump` dumps the state of the PipeWire daemon in JSON format. This can be used to find out the properties and parameters of the objects in the PipeWire daemon. There is a more complicated tool to inspect the state of the server with `pw-cli`. This tool can be used interactively or it can execute single commands like this to get the server information: ``` $ pw-cli info 0 ``` ## Documentation Find tutorials and design documentation [here](doc/index.dox). The (incomplete) autogenerated API docs are [here](https://docs.pipewire.org). The Wiki can be found [here](https://gitlab.freedesktop.org/pipewire/pipewire/-/wikis/home) ## Contributing PipeWire is Free Software and is developed in the open. It is mostly licensed under the [MIT license](COPYING). Check [LICENSE](LICENSE) for more details about the exceptions. Contributors are encouraged to submit merge requests or file bugs on [gitlab](https://gitlab.freedesktop.org/pipewire). Join us on IRC at #pipewire on [OFTC](https://www.oftc.net/). We adhere to the Contributor Covenant for our [code of conduct](CODE_OF_CONDUCT.md). [Donate using Liberapay](https://liberapay.com/PipeWire/donate). ## Getting help You can ask for help on the IRC channel (see above). You can also ask questions by [raising](https://gitlab.freedesktop.org/pipewire/pipewire/-/issues/new) a gitlab issue. pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/autogen.sh000077500000000000000000000007071511204443500225030ustar00rootroot00000000000000#!/bin/sh # Only there to make jhbuild happy if [ -z "$MESON" ]; then MESON=$(which meson) fi if [ -z "$MESON" ]; then echo "error: Meson not found." echo "Install meson to configure and build PipeWire. If meson" \ "is already installed, set the environment variable MESON" \ "to the binary's path." exit 1; fi mkdir -p builddir $MESON setup "$@" builddir # use 'autogen.sh --reconfigure' to update ln -sf builddir/Makefile Makefile pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/doc/000077500000000000000000000000001511204443500212435ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/doc/Doxyfile.in000066400000000000000000000044021511204443500233560ustar00rootroot00000000000000PROJECT_NAME = PipeWire PROJECT_NUMBER = @PACKAGE_VERSION@ OUTPUT_DIRECTORY = "@output_directory@" FULL_PATH_NAMES = YES JAVADOC_AUTOBRIEF = YES TAB_SIZE = 8 OPTIMIZE_OUTPUT_FOR_C = YES EXTRACT_ALL = YES EXTRACT_STATIC = YES STRIP_FROM_PATH = @path_prefixes@ STRIP_FROM_INC_PATH = @path_prefixes@ SHOW_FILES = NO SHOW_INCLUDE_FILES = NO GENERATE_TODOLIST = NO GENERATE_TESTLIST = NO GENERATE_BUGLIST = NO GENERATE_DEPRECATEDLIST= NO QUIET = YES WARN_NO_PARAMDOC = YES HAVE_DOT = @HAVE_DOT@ INPUT = @inputs@ FILTER_PATTERNS = "*.c=@c_input_filter@" "*.h=@h_input_filter@" "*.md=@md_input_filter@" FILE_PATTERNS = "*.h" "*.c" RECURSIVE = YES EXAMPLE_PATH = "@top_srcdir@/src/examples" \ "@top_srcdir@/spa/examples" \ "@top_srcdir@/doc/examples" \ "@top_srcdir@/doc/dox" EXAMPLE_PATTERNS = "*.c" "*.inc" GENERATE_MAN = YES MAN_EXTENSION = 3 REFERENCED_BY_RELATION = NO REFERENCES_RELATION = NO IGNORE_PREFIX = pw_ \ PW_ \ spa_ \ SPA_ GENERATE_TREEVIEW = YES DISABLE_INDEX = NO SEARCHENGINE = YES GENERATE_LATEX = NO TOC_INCLUDE_HEADINGS = 0 LAYOUT_FILE = @layout@ MACRO_EXPANSION = YES EXPAND_ONLY_PREDEF = YES PREDEFINED = PA_C_DECL_BEGIN= \ PA_C_DECL_END= \ __USE_ISOC11 \ SPA_EXPORT \ SPA_PRINTF_FUNC \ SPA_DEPRECATED \ SPA_SENTINEL \ SPA_UNUSED \ SPA_NORETURN \ SPA_RESTRICT HTML_EXTRA_STYLESHEET = @cssfiles@ MAX_INITIALIZER_LINES = 1 SORT_MEMBER_DOCS = NO CALL_GRAPH = NO CALLER_GRAPH = NO CLASS_GRAPH = NO COLLABORATION_GRAPH = NO GROUP_GRAPHS = NO INCLUDED_BY_GRAPH = NO INCLUDE_GRAPH = NO GRAPHICAL_HIERARCHY = NO DIRECTORY_GRAPH = NO TEMPLATE_RELATIONS = NO # Fix up some apparent Doxygen mis-parsing EXCLUDE_SYMBOLS = "desc" "methods" "msgid_plural" "n" "name" "props" "utils" "start" pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/doc/DoxygenLayout.xml000066400000000000000000000215111511204443500246000ustar00rootroot00000000000000 pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/doc/custom.css000066400000000000000000000022511511204443500232670ustar00rootroot00000000000000html { /* --page-background-color: #729fcf; */ --primary-color: #729fcf; --primary-dark-color: #729fcf; --header-background: #729fcf; --header-foreground: rgba(255, 255, 255, 0.7); --font-family: 'Source Sans Pro', 'Source Sans', sans-serif; } @media (prefers-color-scheme: light) { html { --code-background: #f5f5f5; --code-foreground: #333333; --fragment-background: #f5f5f5; --fragment-foreground: #333333; --fragment-keyword: #c7254e; --fragment-link: #729fcf; } } #nav-tree .arrow { opacity: 1; padding-right: 0.25em; } .textblock h1 { font-size: 150%; border-bottom: 1px solid var(--page-foreground-color); margin-top: 1.5em; } .textblock h2 { font-size: 120%; margin-top: 1.5em; } .textblock h3, .textblock h4, .textblock h5, .textblock h6 { font-size: 100%; font-style: italic; font-size: medium; margin-top: 1.5em; } .textblock dl.section dd { margin-left: 2rem; } ul.multicol li { word-break: break-word; padding-left: 3em; text-indent: -3em; } ul.multicol li a.el { font-weight: normal; } div.contents div.toc li { word-break: break-word; padding-left: 2em; text-indent: -2em; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/doc/dox/000077500000000000000000000000001511204443500220355ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/doc/dox/api/000077500000000000000000000000001511204443500226065ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/doc/dox/api/index.dox000066400000000000000000000046661511204443500244450ustar00rootroot00000000000000/** \page page_api PipeWire API The PipeWire API consists of several parts: - The \ref pw_stream for a convenient way to send and receive data streams from/to PipeWire. - The \ref pw_filter for a convenient way to implement processing filters. - The \ref api_pw_core to access a PipeWire instance. This API is used by all clients that need to communicate with the \ref page_daemon and provides the necessary structs to interface with the daemon. - The \ref api_pw_impl is primarily used by the \ref page_daemon itself but also by the \ref page_session_manager and modules/extensions that need to build objects in the graph. - The \ref api_pw_util containing various utility functions and structures. - The \ref api_pw_ext for interfacing with certain extension modules. The APIs work through proxy objects, so that calling a method on an object invokes that same method on the remote side. Marshalling and de-marshalling is handled transparently by the \ref page_module_protocol_native. The below graph illustrates this approach: \dot digraph API { compound=true; node [shape="box"]; rankdir="RL"; subgraph cluster_daemon { rankdir="TB"; label="PipeWire daemon"; style="dashed"; impl_core [label="Core Impl. Object"]; impl_device [label="Device Impl. Object"]; impl_node [label="Node Impl. Object"]; } subgraph cluster_client { rankdir="TB"; label="PipeWire client"; style="dashed"; obj_core [label="Core Object"]; obj_device [label="Device Object"]; obj_node [label="Node Object"]; } obj_core -> impl_core; obj_device -> impl_device; obj_node -> impl_node; } \enddot It is common for clients to use both the \ref api_pw_core and the \ref api_pw_impl and both APIs are provided by the same library. - \subpage page_spa - \subpage page_client_impl - \subpage page_proxy - \subpage page_streams - \subpage page_thread_loop \addtogroup api_pw_core Core API The Core API to access a PipeWire instance. This API is used by all clients to communicate with the \ref page_daemon. If you are familiar with Wayland implementation, the Core API is roughly equivalent to libwayland-client. See: \ref page_api \addtogroup api_pw_impl Implementation API The implementation API provides the tools to build new objects and modules. If you are familiar with Wayland implementation, the Implementation API is roughly equivalent to libwayland-server. See: \ref page_api */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/doc/dox/api/spa-buffer.dox000066400000000000000000000071621511204443500253620ustar00rootroot00000000000000/** \page page_spa_buffer SPA Buffers > What is the array of `spa_data` in `spa_buffer`? A \ref spa_buffer "SPA Buffer" contains metadata and data. There can be many metadata items (headers, color info, cursor position, etc) in the buffer. The metadata items are stored in the metas array. In the same way, the buffer can contain multiple data blocks in the datas array. Each data block is, for example, a video plane or an audio channel. There are `n_datas` of those blocks. > What is the `void*` data pointer in `spa_data`? The data information either has a file descriptor or a data pointer. The type of the `spa_data` tells you what to expect. For a file descriptor, the data pointer can optionally be set when the FD is mapped into memory. Otherwise the user has to mmap the data themselves. Also associated with each `spa_data` is a chunk, which is read/write and contains the valid region in the `spa_data` (offset, size, stride and some flags). The reason why is this set up like this is that the metadata memory, the data and chunks can be directly transported in shared memory while the buffer structure can be negotiated separately (describing the shared memory). This way buffers can be shared but no process can destroy the structure of the buffers. * The buffer skeleton is placed in memory like below and can * be accessed as a regular structure. * * +==============================+ * | struct spa_buffer | * | uint32_t n_metas | number of metas * | uint32_t n_datas | number of datas * +-| struct spa_meta *metas | pointer to array of metas * +|-| struct spa_data *datas | pointer to array of datas * || +------------------------------+ * |+>| struct spa_meta | * | | uint32_t type | metadata * | | uint32_t size | size of metadata * +|--| void *data | pointer to metadata * || | ... | more spa_meta follow * || +------------------------------+ * |+->| struct spa_data | * | | uint32_t type | memory type * | | uint32_t flags | * | | int fd | fd of shared memory block * | | uint32_t mapoffset | offset in shared memory of data * | | uint32_t maxsize | size of data block * | +-| void *data | pointer to data * |+|-| struct spa_chunk *chunk | pointer to chunk * ||| | ... | more spa_data follow * ||| +==============================+ * VVV * * metadata, chunk and memory can either be placed right * after the skeleton (inlined) or in a separate piece of memory. * * vvv * ||| +==============================+ * +-->| meta data memory | metadata memory, 8 byte aligned * || | ... | * || +------------------------------+ * +->| struct spa_chunk | memory for n_datas chunks * | | uint32_t offset | * | | uint32_t size | * | | int32_t stride | * | | int32_t dummy | * | | ... chunks | * | +------------------------------+ * +>| data | memory for n_datas data, aligned * | ... blocks | according to alignments * +==============================+ Taken from [here](https://gitlab.freedesktop.org/pipewire/pipewire/-/blob/11f95fe11e07192cec19fddb4fafc708e023e49c/spa/include/spa/buffer/alloc.h). \addtogroup spa_buffer See: \ref page_spa_buffer */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/doc/dox/api/spa-design.dox000066400000000000000000000022171511204443500253560ustar00rootroot00000000000000/** \page page_spa_design SPA Design # Conventions ## Types Types are generally divided into two categories: - String types: They identify interfaces and highlevel object types. - Integer types: These are enumerations used in the parts where high performance/ease of use/low space overhead is needed. The SPA type is system is static and very simple but still allows you to make and introspect complex object type hierarchies. See the type system docs for more info. ## Error Codes SPA uses negative integers as errno style error codes. Functions that return an int result code generated an error when < 0. `spa_strerror()` can be used to get a string representation of the error code. SPA also has a way to encode asynchronous results. This is done by setting a high bit (bit 30, the `ASYNC_BIT`) in the result code and a sequence number in the lower bits. This result is normally identified as a positive success result code and the sequence number can later be matched to the completion event. ## Useful Macros SPA comes with some useful macros defined in `` and a number of utility functions, see \ref spa_utils. */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/doc/dox/api/spa-index.dox000066400000000000000000000052251511204443500252160ustar00rootroot00000000000000/** \page page_spa SPA (Simple Plugin API) \ref api_spa (Simple Plugin API) is an extensible API to implement all kinds of plugins. It is inspired by many other plugin APIs, mostly LV2 and GStreamer. SPA provides two parts: - A header-only API with no external dependencies. - A set of support libraries ("plugins") for commonly used functionality. The usual approach is that PipeWire and PipeWire clients can use the header-only functions to interact with the plugins. Those plugins are usually loaded at runtime (through `dlopen(3)`). # Motivation SPA was designed with the following goals in mind: - No dependencies, SPA is shipped as a set of header files that have no dependencies except for the standard C library. - Very efficient both in space and in time. - Very configurable and usable in many different environments. All aspects of the plugin environment can be configured and changed, like logging, poll loops, system calls, etc. - Consistent API. - Extensible; new API can be added with minimal effort, existing API can be updated and versioned. The original user of SPA is PipeWire, which uses SPA to implement the low-level multimedia processing plugins, device detection, mainloops, CPU detection, logging, among other things. SPA however can be used outside of PipeWire with minimal problems. # The SPA Header-Only API A very simple example on how SPA headers work are the \ref spa_utils, a set of utilities commonly required by C projects. SPA functions use the `spa_` namespace and are easy to identify. \code /* cc $(pkg-config --cflags libspa-0.2) -o spa-test spa-test.c */ #include #include int main(int argc, char **argv) { uint32_t val; if (spa_atoi32(argv[1], &val, 16)) printf("argv[1] is hex %#x\n", val); else printf("argv[1] is not a hex number\n"); return 0; } \endcode # SPA Plugins SPA plugins are shared libraries (`.so` files) that can be loaded at runtime. Each library provides one or more "factories", each of which may implement several "interfaces". Code that uses SPA plugins then uses those interfaces (through SPA header files) to interact with the plugin. For example, the PipeWire daemon can load the normal `printf`-based logger or a systemd journal-based logger. Both of those provide the \ref spa_log interface and once instantiated, PipeWire no longer has to differentiate between the two logging facilities. Please see \ref page_spa_plugins for the details on how to use SPA plugins. # Further details - \ref api_spa - \subpage page_spa_design - \subpage page_spa_plugins - \subpage page_spa_pod - \subpage page_spa_buffer \addtogroup api_spa See: \ref page_spa, \ref page_spa_design */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/doc/dox/api/spa-plugins.dox000066400000000000000000000255611511204443500255750ustar00rootroot00000000000000/** \page page_spa_plugins SPA Plugins \ref spa_handle "SPA plugins" are dynamically loadable objects that contain objects and interfaces that can be introspected and used at runtime in any application. This document introduces the basic concepts of SPA plugins. It first covers using the API and then talks about implementing new plugins. # Outline To use a plugin, the following steps are required: - **Load** the shared library. - **Enumerate** the available factories. - **Enumerate** the interfaces in each factory. - **Instantiate** the desired interface. - **Use** the interface-specific functions. In pseudo-code, loading a logger interface looks like this: \code{.py} handle = dlopen("$SPA_PLUGIN_DIR/support/libspa-support.so") factory_enumeration_func = dlsym(handle, SPA_HANDLE_FACTORY_ENUM_FUNC_NAME) spa_log *logger = NULL while True: factory = get_next_factory(factory_enumeration_func): if factory != SPA_NAME_SUPPORT_LOG: # continue interface_info = get_next_interface_info(factory) if info->type != SPA_TYPE_INTERFACE_Log: # continue interface = spa_load_interface(handle, interface_info->type) logger = (struct spa_log *)interface break spa_log_error(log, "This is an error message\n") \endcode SPA does not specify where plugins need to live, although plugins are normally installed in `/usr/lib64/spa-0.2/` or equivalent. Plugins and API are versioned and many versions can live on the same system. \note The directory the SPA plugins reside in is available through `pkg-config --variable plugindir libspa-0.2` The `spa-inspect` tool provides a CLI interface to inspect SPA plugins: \verbatim $ export SPA_PLUGIN_DIR=$(pkg-config --variable plugindir libspa-0.2) $ spa-inspect ${SPA_PLUGIN_DIR}/support/libspa-support.so ... factory version: 1 factory name: 'support.cpu' factory info: none factory interfaces: interface: 'Spa:Pointer:Interface:CPU' factory instance: interface: 'Spa:Pointer:Interface:CPU' skipping unknown interface factory version: 1 factory name: 'support.loop' factory info: none factory interfaces: interface: 'Spa:Pointer:Interface:Loop' interface: 'Spa:Pointer:Interface:LoopControl' interface: 'Spa:Pointer:Interface:LoopUtils' ... \endverbatim # Open A Plugin A plugin is opened with a platform specific API. In this example we use `dlopen()` as the method used on Linux. A plugin always consists of two parts, the vendor path and then the .so file. As an example we will load the "support/libspa-support.so" plugin. You will usually use some mapping between functionality and plugin path as we'll see later, instead of hardcoding the plugin name. To `dlopen` a plugin we then need to prefix the plugin path like this: \code{.c} #define SPA_PLUGIN_DIR /usr/lib64/spa-0.2/" void *hnd = dlopen(SPA_PLUGIN_DIR"/support/libspa-support.so", RTLD_NOW); \endcode The environment variable `SPA_PLUGIN_DIR` and `pkg-config` variable `plugindir` are usually used to find the location of the plugins. You will have to do some more work to construct the shared object path. The plugin must have exactly one public symbol, called `spa_handle_factory_enum`, which is defined with the macro `SPA_HANDLE_FACTORY_ENUM_FUNC_NAME` to get some compile time checks and avoid typos in the symbol name. We can get the symbol like so: \code{.c} spa_handle_factory_enum_func_t enum_func; enum_func = dlsym(hnd, SPA_HANDLE_FACTORY_ENUM_FUNC_NAME)); \endcode If this symbol is not available, the library is not a valid SPA plugin. # Enumerating Factories With the `enum_func` we can now enumerate all the factories in the plugin: \code{.c} uint32_t i; const struct spa_handle_factory *factory = NULL; for (i = 0;;) { if (enum_func(&factory, &i) <= 0) break; // check name and version, introspect interfaces, // do something with the factory. } \endcode A factory has a version, a name, some properties and a couple of functions that we can check and use. The main use of a factory is to create an actual new object from it. We can enumerate the interfaces that we will find on this new object with the `spa_handle_factory_enum_interface_info()` method. Interface types are simple strings that uniquely define the interface (see also the type system). The name of the factory is a well-known name that describes the functionality of the objects created from the factory. `` contains definitions for common functionality, for example: \code{.c} #define SPA_NAME_SUPPORT_CPU "support.cpu" // A CPU interface #define SPA_NAME_SUPPORT_LOG "support.log" // A Log interface #define SPA_NAME_SUPPORT_DBUS "support.dbus" // A DBUS interface \endcode Usually the name will be mapped to a specific plugin. This way an alternative compatible implementation can be made in a different library. # Making A Handle Once we have a suitable factory, we need to allocate memory for the object it can create. SPA usually does not allocate memory itself but relies on the application and the stack for storage. First get the size of the required memory: \code{.c} struct spa_dict *extra_params = NULL; size_t size = spa_handle_factory_get_size(factory, extra_params); \endcode Sometimes the memory can depend on the extra parameters given in `_get_size()`. Next we need to allocate the memory and initialize the object in it: \code{.c} handle = calloc(1, size); spa_handle_factory_init(factory, handle, NULL, // info NULL, // support 0 // n_support ); \endcode The info parameter should contain the same extra properties given in `spa_handle_factory_get_size()`. The support parameter is an array of `struct spa_support` items. They contain a string type and a pointer to extra support objects. This can be a logging API or a main loop API for example. Some plugins require certain support libraries to function. # Retrieving An Interface When a SPA handle is made, you can retrieve any of the interfaces that it provides: \code{.c} void *iface; spa_handle_get_interface(handle, SPA_NAME_SUPPORT_LOG, &iface); \endcode If this method succeeds, you can cast the `iface` variable to `struct spa_log *` and start using the log interface methods. \code{.c} struct spa_log *log = iface; spa_log_warn(log, "Hello World!\n"); \endcode # Clearing An Object After you are done with a handle you can clear it with `spa_handle_clear()` and you can unload the library with `dlclose()`. # SPA Interfaces We briefly talked about retrieving an interface from a plugin in the previous section. Now we will explore what an interface actually is and how to use it. When you retrieve an interface from a handle, you get a reference to a small structure that contains the type (string) of the interface, a version and a structure with a set of methods (and data) that are the implementation of the interface. Calling a method on the interface will just call the appropriate method in the implementation. Interfaces are defined in a header file (for example see `` for the logger API). It is a self contained definition that you can just use in your application after you `dlopen()` the plugin. Some interfaces also provide extra fields in the interface, like the log interface above that has the log level as a read/write parameter. See \ref spa_interface for some implementation details on interfaces. # SPA Events Some interfaces will also allow you to register a callback (a hook or listener) to be notified of events. This is usually when something changed internally in the interface and it wants to notify the registered listeners about this. For example, the `struct spa_node` interface has a method to register such an event handler like this: \code{.c} static void node_info(void *data, const struct spa_node_info *info) { printf("got node info!\n"); } static struct spa_node_events node_events = { SPA_VERSION_NODE_EVENTS, .info = node_info, }; struct spa_hook listener; spa_zero(listener); spa_node_add_listener(node, &listener, &node_event, my_data); \endcode You make a structure with pointers to the events you are interested in and then use `spa_node_add_listener()` to register a listener. The `struct spa_hook` is used by the interface to keep track of registered event listeners. Whenever the node information is changed, your `node_info` method will be called with `my_data` as the first data field. The events are usually also triggered when the listener is added, to enumerate the current state of the object. Events have a `version` field, set to `SPA_VERSION_NODE_EVENTS` in the above example. It should contain the version of the event structure you compiled with. When new events are added later, the version field will be checked and the new signal will be ignored for older versions. You can remove your listener with: \code{.c} spa_hook_remove(&listener); \endcode # API Results Some interfaces provide API that gives you a list or enumeration of objects/values. To avoid allocation overhead and ownership problems, SPA uses events to push results to the application. This makes it possible for the plugin to temporarily create complex objects on the stack and push this to the application without allocation or ownership problems. The application can look at the pushed result and keep/copy only what it wants to keep. ## Synchronous Results Here is an example of enumerating parameters on a node interface. First install a listener for the result: \code{.c} static void node_result(void *data, int seq, int res, uint32_t type, const void *result) { const struct spa_result_node_params *r = (const struct spa_result_node_params *) result; printf("got param:\n"); spa_debug_pod(0, NULL, r->param); } struct spa_hook listener = { 0 }; static const struct spa_node_events node_events = { SPA_VERSION_NODE_EVENTS, .result = node_result, }; spa_node_add_listener(node, &listener, &node_events, node); \endcode Then perform the `enum_param` method: \code{.c} int res = spa_node_enum_params(node, 0, SPA_PARAM_EnumFormat, 0, MAXINT, NULL); \endcode This triggers the result event handler with a 0 sequence number for each supported format. After this completes, remove the listener again: \code{.c} spa_hook_remove(&listener); \endcode ## Asynchronous Results Asynchronous results are pushed to the application in the same way as synchronous results, they are just pushed later. You can check that a result is asynchronous by the return value of the enum function: \code{.c} int res = spa_node_enum_params(node, 0, SPA_PARAM_EnumFormat, 0, MAXINT, NULL); if (SPA_RESULT_IS_ASYNC(res)) { // result will be received later ... } \endcode In the case of async results, the result callback will be called with the sequence number of the async result code, which can be obtained with: \code{.c} expected_seq = SPA_RESULT_ASYNC_SEQ(res); \endcode # Implementing A New Plugin ***FIXME*** \addtogroup spa_handle See: \ref page_spa_plugins */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/doc/dox/api/spa-pod.dox000066400000000000000000001141111511204443500246640ustar00rootroot00000000000000/** \page page_spa_pod SPA POD \ref spa_pod (plain old data) is a sort of data container. It is comparable to DBus Variant or LV2 Atom. A POD can express nested structures of objects (with properties), vectors, arrays, sequences and various primitives types. All information in the POD is laid out sequentially in memory and can be written directly to storage or exchanged between processes or threads without additional marshalling. Each POD is made of a 32 bits size followed by a 32 bits type field, followed by the POD contents. This makes it possible to skip over unknown POD types. The POD start is always aligned to 8 bytes. POD's can be efficiently constructed and parsed in real-time threads without requiring memory allocations. POD's use the SPA type system for the basic types and containers. See the SPA types for more info. # Types POD's can contain a number of basic SPA types: - `SPA_TYPE_None`: No value or a NULL pointer. - `SPA_TYPE_Bool`: A boolean value. - `SPA_TYPE_Id`: An enumerated value. - `SPA_TYPE_Int`, `SPA_TYPE_Long`, `SPA_TYPE_Float`, `SPA_TYPE_Double`: various numeral types, 32 and 64 bits. - `SPA_TYPE_String`: A string. - `SPA_TYPE_Bytes`: A byte array. - `SPA_TYPE_Rectangle`: A rectangle with width and height. - `SPA_TYPE_Fraction`: A fraction with numerator and denominator. - `SPA_TYPE_Bitmap`: An array of bits. Deprecated and unused. POD's can be grouped together in these container types: - `SPA_TYPE_Array`: An array of equal sized objects. - `SPA_TYPE_Struct`: A collection of types and objects. - `SPA_TYPE_Object`: An object with properties. - `SPA_TYPE_Sequence`: A timed sequence of POD's. POD's can also contain some extra types: - `SPA_TYPE_Pointer`: A typed pointer in memory. - `SPA_TYPE_Fd`: A file descriptor. - `SPA_TYPE_Choice`: A choice of values. - `SPA_TYPE_Pod`: A generic type for the POD itself. # Constructing A POD A POD is usually constructed with a `struct spa_pod_builder`. The builder needs to be initialized with a memory region to write into. It is also possible to dynamically grow the memory as needed. The most common way to construct a POD is on the stack. This does not require any memory allocations. The size of the POD can be estimated pretty easily and if the buffer is not large enough, an appropriate error will be generated. The code fragment below initializes a POD builder to write into the stack allocated buffer. \code{.c} uint8_t buffer[4096]; struct spa_pod_builder b; spa_pod_builder_init(&b, buffer, sizeof(buffer)); \endcode Next we need to write some object into the builder. Let's write a simple struct with an Int and Float in it. Structs are comparable to JSON arrays. \code{.c} struct spa_pod_frame f; spa_pod_builder_push_struct(&b, &f); \endcode First we open the struct container, the `struct spa_pod_frame` keeps track of the container context. Next we add some values to the container like this: \code{.c} spa_pod_builder_int(&b, 5); spa_pod_builder_float(&b, 3.1415f); \endcode Then we close the container by popping the frame again: \code{.c} struct spa_pod *pod; pod = spa_pod_builder_pop(&b, &f); \endcode `spa_pod_builder_pop()` returns a reference to the object we completed on the stack. ## Using varargs Builder We can also use the following construct to make POD objects: \code{.c} spa_pod_builder_push_struct(&b, &f); spa_pod_builder_add(&b, SPA_POD_Int(5), SPA_POD_Float(3.1415f)); pod = spa_pod_builder_pop(&b, &f); \endcode Or even shorter: \code{.c} pod = spa_pod_builder_add_struct(&b, SPA_POD_Int(5), SPA_POD_Float(3.1415f)); \endcode It's not possible to use the varargs builder to make a sequence or array, use the normal builder methods for that. ## Making Objects POD objects are containers for properties and are comparable to JSON objects. Start by pushing an object: \code{.c} spa_pod_builder_push_object(&b, &f, SPA_TYPE_OBJECT_Props, SPA_PARAM_Props); \endcode An object requires an object type (`SPA_TYPE_OBJECT_Props`) and a context ID (`SPA_PARAM_Props`). The object type defines the properties that can be added to the object and their meaning. The SPA type system allows you to make this connection (See the type system). Next we can push some properties in the object: \code{.c} spa_pod_builder_prop(&b, SPA_PROP_device, 0); spa_pod_builder_string(&b, "hw:0"); spa_pod_builder_prop(&b, SPA_PROP_frequency, 0); spa_pod_builder_float(&b, 440.0); \endcode As can be seen, we always need to push a prop (with key and flags) and then the associated value. For performance reasons it is a good idea to always push (and parse) the object keys in ascending order. Don't forget to pop the result when the object is finished: \code{.c} pod = spa_pod_builder_pop(&b, &f); \endcode There is a shortcut for making objects: \code{.c} pod = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_Props, SPA_PARAM_Props, SPA_PROP_device, SPA_POD_String("hw:0"), SPA_PROP_frequency, SPA_POD_Float(440.0f)); \endcode ## Choice Values It is possible to express ranges or enumerations of possible values for properties (and to some extend structs). This is achieved with choice values. Choice values are really just a choice type and an array of choice values (of the same type). Depending on the choice type, the array values are interpreted in different ways: - `SPA_CHOICE_None`: No choice, first value is current. - `SPA_CHOICE_Range`: Range: default, min, max. - `SPA_CHOICE_Step`: Range with step: default, min, max, step. - `SPA_CHOICE_Enum`: Enum: default, alternative,... - `SPA_CHOICE_Flags`: Bitmask of flags. Let's illustrate this with a props object that specifies a range of possible values for the frequency: \code{.c} struct spa_pod_frame f2; spa_pod_builder_push_object(&b, &f, SPA_TYPE_OBJECT_Props, SPA_PARAM_Props); spa_pod_builder_prop(&b, SPA_PROP_frequency, 0); spa_pod_builder_push_choice(&b, &f2, SPA_CHOICE_Range, 0); spa_pod_builder_float(&b, 440.0); // default spa_pod_builder_float(&b, 110.0); // min spa_pod_builder_float(&b, 880.0); // min pod = spa_pod_builder_pop(&b, &f2); pod = spa_pod_builder_pop(&b, &f); \endcode As you can see, first push the choice as a range, then the values. A range choice expects at least three values, the default value, minimum and maximum values. There is a shortcut for this as well using varargs: \code{.c} pod = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_Props, SPA_PARAM_Props, SPA_PROP_frequency, SPA_POD_CHOICE_RANGE_Float(440.0f, 110.0f, 880.0f)); \endcode ## Choice Examples This is a description of a possible `SPA_TYPE_OBJECT_Format` as used when enumerating allowed formats (`SPA_PARAM_EnumFormat`) in SPA objects: \code{.c} pod = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat, // specify the media type and subtype SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_audio), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), // audio/raw properties SPA_FORMAT_AUDIO_format, SPA_POD_CHOICE_ENUM_Id(4, // 4 values follow SPA_AUDIO_FORMAT_S16, // default SPA_AUDIO_FORMAT_S16, // alternative1 SPA_AUDIO_FORMAT_S32, // alternative2 SPA_AUDIO_FORMAT_F32 // alternative3 ), SPA_FORMAT_AUDIO_rate, SPA_POD_CHOICE_RANGE_Int( 44100, // default 8000, // min 192000 // max ), SPA_FORMAT_AUDIO_channels, SPA_POD_Int(2)); \endcode ## Fixate We can remove all choice values from the object with the `spa_pod_object_fixate()` method. This modifies the pod in-place and sets all choice properties to `SPA_CHOICE_None`, forcing the default value as the only available value in the choice. Running fixate on our previous example would result in an object equivalent to: \code{.c} pod = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat, // specify the media type and subtype SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_audio), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), // audio/raw properties SPA_FORMAT_AUDIO_format, SPA_POD_Id(SPA_AUDIO_FORMAT_S16), SPA_FORMAT_AUDIO_rate, SPA_POD_Int(44100), SPA_FORMAT_AUDIO_channels, SPA_POD_Int(2)); \endcode # Parsing A POD Parsing a POD usually consists of: - Validating if raw bytes + size can contain a valid POD. - Inspecting the type of a POD. - Looping over the items in an object or struct. - Getting data out of POD's. ## Validating Bytes Use `spa_pod_from_data()` to check if maxsize of bytes in data contain a POD at the size bytes starting at offset. This function checks that the POD size will fit and not overflow. \code{.c} struct spa_pod *pod; pod = spa_pod_from_data(data, maxsize, offset, size); \endcode ## Checking The Type Of POD Use one of `spa_pod_is_bool()`, `spa_pod_is_int()`, etc to check for the type of the pod. For simple (non-container) types, `spa_pod_get_bool()`, `spa_pod_get_int()` etc can be used to extract the value of the pod. `spa_pod_is_object_type()` can be used to check if the POD contains an object of the expected type. ## Struct Fields To iterate over the fields of a struct use: \code{.c} struct spa_pod *pod, *obj; SPA_POD_STRUCT_FOREACH(obj, pod) { printf("field type:%d\n", pod->type); } \endcode For parsing structs it is usually much easier to use the parser below. ## Object Properties To iterate over the properties in an object you can do: \code{.c} struct spa_pod_prop *prop; struct spa_pod_object *obj = (struct spa_pod_object*)pod; SPA_POD_OBJECT_FOREACH(pod, prop) { printf("prop key:%d\n", prop->key); } \endcode There is a function to retrieve the property for a certain key in the object. If the properties of the object are in ascending order, you can start searching from the previous key. \code{.c} struct spa_pod_prop *prop; prop = spa_pod_find_prop(obj, NULL, SPA_FORMAT_AUDIO_format); // .. use first prop prop = spa_pod_find_prop(obj, prop, SPA_FORMAT_AUDIO_rate); // .. use next prop \endcode ## Parser Similar to the builder, there is a parser object as well. If the fields in a struct are known, it is much easier to use the parser. Similarly, if the object type (and thus its keys) are known, the parser is easier. First initialize a `struct spa_pod_parser`: \code{.c} struct spa_pod_parser p; spa_pod_parser_pod(&p, obj); \endcode You can then enter containers such as objects or structs with a push operation: \code{.c} struct spa_pod_frame f; spa_pod_parser_push_struct(&p, &f); \endcode You need to store the context in a `struct spa_pod_frame` to be able to exit the container again later. You can then parse each field. The parser takes care of moving to the next field. \code{.c} uint32_t id, val; spa_pod_parser_get_id(&p, &id); spa_pod_parser_get_int(&p, &val); ... \endcode And finally exit the container again: \code{.c} spa_pod_parser_pop(&p, &f); \endcode ## Parser With Variable Arguments In most cases, parsing objects is easier with the variable argument functions. The parse function look like the mirror image of the builder functions. To parse a struct: \code{.c} spa_pod_parser_get_struct(&p, SPA_POD_Id(&id), SPA_POD_Int(&val)); \endcode To parse properties in an object: \code{.c} uint32_t id, type, subtype, format, rate, channels; spa_pod_parser_get_object(&p, SPA_TYPE_OBJECT_Format, &id, SPA_FORMAT_mediaType, SPA_POD_Id(&type), SPA_FORMAT_mediaSubtype, SPA_POD_Id(&subtype), SPA_FORMAT_AUDIO_format, SPA_POD_Id(&format), SPA_FORMAT_AUDIO_rate, SPA_POD_Int(&rate), SPA_FORMAT_AUDIO_channels, SPA_POD_Int(&channels)); \endcode When parsing objects it is possible to have optional fields. You can make a field optional be parsing it with the `SPA_POD_OPT_` prefix for the type. In the next example, the rate and channels fields are optional and when they are not present, the variables will not be changed. \code{.c} uint32_t id, type, subtype, format, rate = 0, channels = 0; spa_pod_parser_get_object(&p, SPA_TYPE_OBJECT_Format, &id, SPA_FORMAT_mediaType, SPA_POD_Id(&type), SPA_FORMAT_mediaSubtype, SPA_POD_Id(&subtype), SPA_FORMAT_AUDIO_format, SPA_POD_Id(&format), SPA_FORMAT_AUDIO_rate, SPA_POD_OPT_Int(&rate), SPA_FORMAT_AUDIO_channels, SPA_POD_OPT_Int(&channels)); \endcode It is not possible to parse a sequence or array with the parser. Use the iterator for this. ## Choice Values The parser will handle choice values as long as they are of type `none`. It will then parse the single value from the choice. When dealing with other choice values, it's possible to parse the property values into a `struct spa_pod` and then inspect the choice manually, if needed. Here is an example of parsing the format values as a POD: \code{.c} uint32_t id, type, subtype; struct spa_pod *format; spa_pod_parser_get_object(&p, SPA_TYPE_OBJECT_Format, &id, SPA_FORMAT_mediaType, SPA_POD_Id(&type), SPA_FORMAT_mediaSubtype, SPA_POD_Id(&subtype), SPA_FORMAT_AUDIO_format, SPA_POD_Pod(&format)); \endcode `spa_pod_get_values()` is a useful function. It returns a `struct spa_pod*` with and array of values. For invalid PODs it returns the POD and no values. For normal PODs it returns the POD and one value. For choice values it returns the choice type and an array of values. If the choice doesn't fit even a single value, the array will have no values. \code{.c} struct spa_pod *value; uint32_t n_vals, choice; value = spa_pod_get_values(pod, &n_vals, &choice); switch (choice) { case SPA_CHOICE_None: // one single value break; case SPA_CHOICE_Range: // array of values of type of pod, cast to right type // to iterate. uint32_t *v = SPA_POD_BODY(values); if (n_vals < 3) break; printf("default value: %u\n", v[0]); printf("min value: %u\n", v[1]); printf("max value: %u\n", v[2]); break; // ... default: break; } \endcode # Filter Given two POD objects of the same type (object, struct, ..) one can run a filter and generate a new POD that only contains values that are compatible with both input POD's. This is, for example, used to find a compatible format between two ports. As an example we can run a filter on two simple POD's: \code{.c} pod = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_audio), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), SPA_FORMAT_AUDIO_format, SPA_POD_CHOICE_ENUM_Id(4, // 4 values follow SPA_AUDIO_FORMAT_S16, // default SPA_AUDIO_FORMAT_S16, // alternative1 SPA_AUDIO_FORMAT_S32, // alternative2 SPA_AUDIO_FORMAT_F32 // alternative3 )); filter = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_audio), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), SPA_FORMAT_AUDIO_format, SPA_POD_CHOICE_ENUM_Id(3, // 3 values follow SPA_AUDIO_FORMAT_S16, // default SPA_AUDIO_FORMAT_S16, // alternative1 SPA_AUDIO_FORMAT_F64 // alternative2 )); struct spa_pod *result; if (spa_pod_filter(&b, &result, pod, filter) < 0) goto exit_error; \endcode Filter will contain a POD equivalent to: \code{.c} result = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_audio), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), SPA_FORMAT_AUDIO_format, SPA_AUDIO_FORMAT_S16); \endcode # POD Layout A POD always starts with a size/type pair of uint32_t in native endianness, followed by size in bytes of the payload data and padding. See \ref page_spa_pod for more details. The payload is always padded to 8 bytes so that a complete pod is always a multiple of 8 bytes. ``` 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | size | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | type | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | payload ... | . | ... padding . +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ ``` The total size of the POD is thus ROUND_UP_8(8 + size). # POD Types Here follows the layout of the POD types. ## None (1) Type 1 is the None type or the null pointer. It has a size of 0 and thus no payload. ``` 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | 0 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | 1 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ ``` ## Bool (2) Type 2 is the Bool type. I contains a true or false value. The value is stored in a int32, a value of 0 is false, any other value is true. ``` 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | 4 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | 2 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | value (int32) | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | padding | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ ``` ## Id (3) An id is stored as a uint32. The id refers to an index in a table where more information about the value can be found. This is typically a type table containing some well known ids. ``` 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | 4 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | 3 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | id (uint32) | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | padding | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ ``` ## Int (4) A 32 bit signed integer. ``` 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | 4 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | 4 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | value (int32) | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | padding | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ ``` ## Long (5) A 64 bit signed integer. ``` 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | 8 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | 5 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | value (int64) | + + | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ ``` ## Float (6) A 32 bit float value. ``` 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | 4 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | 6 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | value (float32) | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | padding | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ ``` ## Double (7) A 64 bit float value. ``` 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | 8 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | 7 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | value (float64) | + + | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ ``` ## String (8) A string. This does not have to be valid UTF8 but it is 0 terminated. The size field is set to the length of the string, including the 0 byte. ``` 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | size | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | 8 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | chars .... | . . | ... 0 | padding.. | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ ``` ## Bytes (9) A byte array. The size field is set to the number of bytes. ``` 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | size | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | 9 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | bytes .... | . . | | padding.. | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ ``` ## Rectangle (10) A Rectangle. ``` 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | 8 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | 10 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | width (uint32) | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | height (uint32) | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ ``` ## Fraction (11) A Fraction. ``` 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | 8 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | 11 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | num (uint32) | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | denom (uint32) | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ ``` ## Bitmap (12) A bitmap. Stored as bits in uint8. size is the number of bytes with bits. ``` 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | size | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | 12 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | bits (uint8) ... | . . | | padding.. | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ ``` ## Array (13) An array is an array of (basic) types. In principle the array can contain any type as long as each item in the array has the same child_size and child_type. ``` 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | size | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | 13 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | child_size | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | child_type | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | child1 (child_size bytes) ... | . . +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ . . +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | childN (child_size bytes) ... | . . +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ ``` We describe Array types with a shortcut like: ``` Array[Int](,,...) ``` ## Struct (14) Multiple PODs can be combined into a struct: ``` 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | size | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | 14 (Struct) | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | size1 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | type1 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | payload 1... | . | ... padding . +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ . . +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | sizeN | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | typeN | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | payloadN ... | . | ... padding . +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ ``` We describe Struct types with a shortcut like: ``` Struct( : , : , ...) ``` The type of a struct is 14 and the size the total sum in bytes of all PODs (with padding) inside the struct. ## Object (15) An object contains a set of of properties. ``` 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | size | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | 15 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | object_type | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | object_id | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | property1 | . . +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ . . +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | propertyN | . . +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ ``` object_type is one of the well defined object types. object_id is extra information about the context of the object. Each property is as follows: ``` 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | key (uint32) | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | flags (uint32) | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | POD value ... | . . +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ ``` Object are written with a shortcut as: ``` Object[type,id]( key1: , key2: , ...) ``` ## Sequence (16) A sequence is a series of times events. It is usually used for transporting MIDI and control updates. ``` 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | size | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | 16 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | unit | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | pad | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | control1 | . . +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ . . +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | controlN | . . +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ ``` The unit field and pad is currently set to 0. Each control look like: ``` 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | offset (uint32) | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | type (uint32) | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | POD value ... | . . +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ ``` - offset: the offset relative to the current graph clock time. - type: the type of control, see enum spa_control_type ## Pointer (17) A generic pointer to some memory region. Pointer types are usually not serialized. ``` 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | 16 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | 17 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | type | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | padding (must be 0) | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | native pointer value ... | . | .. padding . +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ ``` ## Fd (18) A file descriptor stored as int64. When serializing, the file descriptor is modified to contain the index of the fd in the message. ``` 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | 8 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | 18 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | fd (int64) ... | + + | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ ``` ## Choice (19) A choice contains an array of possible values. ``` 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | size | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | 19 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | type | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | flags | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | child_size | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | child_type | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | child1 (child_size bytes) ... | . . +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ . . +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | childN (child_size bytes) ... | . . +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ ``` - type: one of possible values, see enum spa_choice_type - None (0) : only child1 is an valid option - Range (1) : child1 is a default value, options are between child2 and child3 in the value array. - Step (2) : child1 is a default value, options are between child2 and child3, in steps of child4 in the value array. - Enum (3) : child1 is a default value, options are any value from the value array, preferred values come first. - Flags (4) : only child1 is a flag value. When filtering, the flags are AND-ed together. - flags: must be 0 ## Pod (20) The value id the POD itself. \addtogroup spa_pod See: \ref page_spa_pod */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/doc/dox/config/000077500000000000000000000000001511204443500233025ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/doc/dox/config/index.md000066400000000000000000000043371511204443500247420ustar00rootroot00000000000000\page page_config Configuration One of the design goals of PipeWire is to be able to closely control and configure all aspects of the processing graph. A fully configured PipeWire setup runs various pieces, each with their configuration options and files: - **pipewire**: The PipeWire main daemon that runs and coordinates the processing. - **pipewire-pulse**: The PipeWire PulseAudio replacement server. It also configures the properties of the PulseAudio clients connecting to it. - **wireplumber**: Most configuration of devices is performed by the session manager. It typically loads ALSA and other devices and configures the profiles, port volumes and more. The session manager also configures new clients and links them to the targets, as configured in the session manager policy. - **PipeWire clients**: Each native PipeWire client also loads a configuration file. Emulated JACK client also have separate configuration. # Configuration Settings Configuration of daemons: - \ref page_man_pipewire_conf_5 "PipeWire daemon configuration reference" - \ref page_man_pipewire-pulse_conf_5 "PipeWire Pulseaudio daemon configuration reference" - [WirePlumber daemon configuration](https://pipewire.pages.freedesktop.org/wireplumber/) Configuration of devices: - [WirePlumber configuration](https://pipewire.pages.freedesktop.org/wireplumber/daemon/configuration.html) - \ref page_man_pipewire-props_7 "Object property reference" - \subpage page_config_xref "Configuration Index" Configuration for client applications, either connecting via the native PipeWire interface, or the emulated ALSA, JACK, or PulseAudio interfaces: - \ref page_man_pipewire-client_conf_5 "PipeWire native and ALSA client configuration reference" - \ref page_man_pipewire-jack_conf_5 "PipeWire JACK client configuration reference" - \ref page_man_pipewire-pulse_conf_5 "PipeWire Pulseaudio client configuration reference" # Manual Pages - \subpage page_man_pipewire_conf_5 - \subpage page_man_pipewire-client_conf_5 - \subpage page_man_pipewire-pulse_conf_5 - \subpage page_man_pipewire-jack_conf_5 - \subpage page_man_pipewire-filter-chain_conf_5 - \subpage page_man_pipewire-props_7 - \subpage page_man_pipewire-pulse-modules_7 - \subpage page_man_libpipewire-modules_7 pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/doc/dox/config/libpipewire-modules.7.md000066400000000000000000000024241511204443500277540ustar00rootroot00000000000000\page page_man_libpipewire-modules_7 libpipewire-modules PipeWire modules # DESCRIPTION A PipeWire module is effectively a PipeWire client running inside `pipewire(1)` which can host multiple modules. Usually modules are loaded when they are listed in the configuration files. For example the default configuration file loads several modules: context.modules = [ ... # The native communication protocol. { name = libpipewire-module-protocol-native } # The profile module. Allows application to access profiler # and performance data. It provides an interface that is used # by pw-top and pw-profiler. { name = libpipewire-module-profiler } # Allows applications to create metadata objects. It creates # a factory for Metadata objects. { name = libpipewire-module-metadata } # Creates a factory for making devices that run in the # context of the PipeWire server. { name = libpipewire-module-spa-device-factory } ... ] # KNOWN MODULES $(LIBPIPEWIRE_MODULES) # AUTHORS The PipeWire Developers <$(PACKAGE_BUGREPORT)>; PipeWire is available from <$(PACKAGE_URL)> # SEE ALSO \ref page_man_pipewire_1 "pipewire(1)", \ref page_man_pipewire_conf_5 "pipewire.conf(5)" pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/doc/dox/config/pipewire-client.conf.5.md000066400000000000000000000161131511204443500300150ustar00rootroot00000000000000\page page_man_pipewire-client_conf_5 client.conf The PipeWire client configuration file. \tableofcontents # SYNOPSIS *$XDG_CONFIG_HOME/pipewire/client.conf* *$(PIPEWIRE_CONFIG_DIR)/client.conf* *$(PIPEWIRE_CONFDATADIR)/client.conf* *$(PIPEWIRE_CONFDATADIR)/client.conf.d/* *$(PIPEWIRE_CONFIG_DIR)/client.conf.d/* *$XDG_CONFIG_HOME/pipewire/client.conf.d/* # DESCRIPTION Configuration for PipeWire native clients, and for PipeWire's ALSA plugin. A PipeWire native client program selects the default config to load, and if nothing is specified, it usually loads `client.conf`. The configuration file format and lookup logic is the same as for \ref page_man_pipewire_conf_5 "pipewire.conf(5)". Drop-in configuration files `client.conf.d/*.conf` can be used, and are recommended. See \ref pipewire_conf__drop-in_configuration_files "pipewire.conf(5)". # CONFIGURATION FILE SECTIONS @IDX@ client.conf \par stream.properties Configures options for native client streams. \par stream.rules Configures rules for native client streams. \par alsa.properties ALSA client configuration. \par alsa.rules ALSA client match rules. In addition, the PipeWire context configuration sections may also be specified, see \ref page_man_pipewire_conf_5 "pipewire.conf(5)". # STREAM PROPERTIES @IDX@ client.conf stream.properties The client configuration files contain a stream.properties section that configures the options for client streams: ```css # ~/.config/pipewire/client.conf.d/custom.conf stream.properties = { #node.latency = 1024/48000 #node.autoconnect = true #resample.disable = false #resample.quality = 4 #resample.window = exp # blackman kaiser #resample.cutoff = 0.0 #resample.n-taps = 0 #resample.param.exp.A = 0.0 #resample.param.blackman.alpha = 0.0 #resample.param.kaiser.alpha = 0.0 #resample.param.kaiser.stopband-attenuation = 0.0 #resample.param.kaiser.transition-bandwidth = 0.0 #monitor.channel-volumes = false #channelmix.disable = false #channelmix.min-volume = 0.0 #channelmix.max-volume = 10.0 #channelmix.normalize = false #channelmix.lock-volume = false #channelmix.mix-lfe = true #channelmix.upmix = true #channelmix.upmix-method = psd # none, simple #channelmix.lfe-cutoff = 150.0 #channelmix.fc-cutoff = 12000.0 #channelmix.rear-delay = 12.0 #channelmix.stereo-widen = 0.0 #channelmix.hilbert-taps = 0 #dither.noise = 0 #dither.method = none # rectangular, triangular, triangular-hf, wannamaker3, shaped5 #debug.wav-path = "" } ``` Some of the properties refer to different aspects of the stream: * General stream properties to identify the stream. * General stream properties to classify the stream. * How it is going to be scheduled by the graph. * How it is going to be linked by the session manager. * How the internal processing will be done. * Properties to configure the media format. A list of object properties that can be applied to streams can be found in \ref props__common_node_properties "pipewire-props(7) Common Node Properties" and \ref props__audio_converter_properties "pipewire-props(7) Audio Adapter Properties" # STREAM RULES @IDX@ client.conf stream.rules You can add \ref pipewire_conf__match_rules "match rules, see pipewire(1)" to set properties for certain streams and filters. `stream.rules` and `filter.rules` provides an `update-props` action that takes an object with properties that are updated on the node object of the stream and filter. Add a `stream.rules` or `filter.rules` section in the config file like this: ```css # ~/.config/pipewire/client.conf.d/custom.conf stream.rules = [ { matches = [ { # all keys must match the value. ! negates. ~ starts regex. application.process.binary = "firefox" } ] actions = { update-props = { node.name = "My Name" } } } ] ``` Will set the node.name of Firefox to "My Name". # ALSA CLIENT PROPERTIES @IDX@ client.conf alsa.properties An `alsa.properties` section can be added to configure client applications that connect via the PipeWire ALSA plugin. ```css # ~/.config/pipewire/client.conf.d/custom.conf alsa.properties = { #alsa.deny = false #alsa.format = 0 #alsa.rate = 0 #alsa.channels = 0 #alsa.period-bytes = 0 #alsa.buffer-bytes = 0 #alsa.volume-method = cubic # linear, cubic } ``` @PAR@ client.conf alsa.deny Denies ALSA access for the client. Useful in rules or PIPEWIRE_ALSA environment variable. @PAR@ client.conf alsa.format The ALSA format to use for the client. This is an ALSA format name. default 0, which is to allow all formats. @PAR@ client.conf alsa.rate The samplerate to use for the client. The default is 0, which is to allow all rates. @PAR@ client.conf alsa.channels The number of channels for the client. The default is 0, which is to allow any number of channels. @PAR@ client.conf alsa.period-bytes The number of bytes per period. The default is 0 which is to allow any number of period bytes. @PAR@ client.conf alsa.buffer-bytes The number of bytes in the alsa buffer. The default is 0, which is to allow any number of bytes. @PAR@ client.conf alsa.volume-method = cubic | linear This controls the volume curve used on the ALSA mixer. Possible values are `cubic` and `linear`. The default is to use `cubic`. # ALSA CLIENT RULES @IDX@ client.conf alsa.rules It is possible to set ALSA client specific properties by using \ref pipewire_conf__match_rules "Match rules, see pipewire(1)". You can set any of the above ALSA properties or any of the `stream.properties`. ### Example ```css # ~/.config/pipewire/client.conf.d/custom.conf alsa.rules = [ { matches = [ { application.process.binary = "resolve" } ] actions = { update-props = { alsa.buffer-bytes = 131072 } } } ] ``` # ENVIRONMENT VARIABLES @IDX@ client-env See \ref page_man_pipewire_1 "pipewire(1)" for common environment variables. Many of these also apply to client applications. The environment variables also influence ALSA applications that are using PipeWire's ALSA plugin. @PAR@ client-env PIPEWIRE_ALSA \parblock This can be an object with properties from `alsa.properties` or `stream.properties` that will be used to construct the client and streams. For example: ``` PIPEWIRE_ALSA='{ alsa.buffer-bytes=16384 node.name=foo }' aplay ... ``` Starts aplay with custom properties. \endparblock @PAR@ client-env PIPEWIRE_NODE \parblock Instructs the ALSA client to link to a particular sink or source `object.serial` or `node.name`. For example: ``` PIPEWIRE_NODE=alsa_output.pci-0000_00_1b.0.analog-stereo aplay ... ``` Makes aplay play on the give audio sink. \endparblock # AUTHORS The PipeWire Developers <$(PACKAGE_BUGREPORT)>; PipeWire is available from <$(PACKAGE_URL)> # SEE ALSO \ref page_module_protocol_pulse "libpipewire-module-protocol-pulse(7)", \ref page_man_pipewire_conf_5 "pipewire.conf(5)", \ref page_man_pipewire-pulse_1 "pipewire-pulse(1)", \ref page_man_pipewire-pulse-modules_7 "pipewire-pulse-modules(7)" pipewire-filter-chain.conf.5.md000066400000000000000000000022331511204443500310230ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/doc/dox/config\page page_man_pipewire-filter-chain_conf_5 filter-chain.conf PipeWire example configuration for running audio filters. \tableofcontents # SYNOPSIS *$XDG_CONFIG_HOME/pipewire/filter-chain.conf* *$(PIPEWIRE_CONFIG_DIR)/filter-chain.conf* *$(PIPEWIRE_CONFDATADIR)/filter-chain.conf* *$(PIPEWIRE_CONFDATADIR)/filter-chain.conf.d/* *$(PIPEWIRE_CONFIG_DIR)/filter-chain.conf.d/* *$XDG_CONFIG_HOME/pipewire/filter-chain.conf.d/* # DESCRIPTION When \ref page_man_pipewire_1 "pipewire(1)" is run using this configuration file, `pipewire -c filter-chain.conf`, it starts a PipeWire client application that publishes nodes that apply various audio filters to their input. It is a normal PipeWire client application in all respects. Drop-in configuration files `filter-chain.conf.d/*.conf` can be used to modify the filter configuration, see \ref pipewire_conf__drop-in_configuration_files "pipewire.conf(5)". Some examples are in *$(PIPEWIRE_CONFDATADIR)/filter-chain/* # AUTHORS The PipeWire Developers <$(PACKAGE_BUGREPORT)>; PipeWire is available from <$(PACKAGE_URL)> # SEE ALSO \ref page_man_pipewire_1 "pipewire(1)", \ref page_man_pipewire_conf_5 "pipewire.conf(5)" pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/doc/dox/config/pipewire-jack.conf.5.md000066400000000000000000000310051511204443500274440ustar00rootroot00000000000000\page page_man_pipewire-jack_conf_5 jack.conf The PipeWire JACK client configuration file. \tableofcontents # SYNOPSIS *$XDG_CONFIG_HOME/pipewire/jack.conf* *$(PIPEWIRE_CONFIG_DIR)/jack.conf* *$(PIPEWIRE_CONFDATADIR)/jack.conf* *$(PIPEWIRE_CONFDATADIR)/jack.conf.d/* *$(PIPEWIRE_CONFIG_DIR)/jack.conf.d/* *$XDG_CONFIG_HOME/pipewire/jack.conf.d/* # DESCRIPTION Configuration for PipeWire JACK clients. The configuration file format and lookup logic is the same as for \ref page_man_pipewire_conf_5 "pipewire.conf(5)". Drop-in configuration files `jack.conf.d/*.conf` can be used, and are recommended. See \ref pipewire_conf__drop-in_configuration_files "pipewire.conf(5)". # CONFIGURATION FILE SECTIONS @IDX@ jack.conf \par jack.properties JACK client configuration. \par jack.rules JACK client match rules. In addition, the PipeWire context configuration sections may also be specified, see \ref page_man_pipewire_conf_5 "pipewire.conf(5)". # JACK PROPERTIES @IDX@ jack.conf jack.properties The configuration file can contain an extra JACK specific section called `jack.properties` like this: ```css # ~/.config/pipewire/jack.conf.d/custom.conf jack.properties = { #rt.prio = 88 #node.latency = 1024/48000 #node.lock-quantum = true #node.force-quantum = 0 #jack.show-monitor = true #jack.merge-monitor = true #jack.show-midi = true #jack.short-name = false #jack.filter-name = false #jack.filter-char = " " # # allow: Don't restrict self connect requests # fail-external: Fail self connect requests to external ports only # ignore-external: Ignore self connect requests to external ports only # fail-all: Fail all self connect requests # ignore-all: Ignore all self connect requests #jack.self-connect-mode = allow #jack.locked-process = true #jack.default-as-system = false #jack.fix-midi-events = true #jack.global-buffer-size = false #jack.passive-links = false #jack.max-client-ports = 768 #jack.fill-aliases = false #jack.writable-input = false #jack.flag-midi2 = false } ``` See `stream.properties` in \ref client_conf__stream_properties "pipewire-client.conf(5)" for an explanation of the generic node properties. It is also possible to have per-client settings, see Match Rules below. @PAR@ jack.conf rt.prio To limit the realtime priority that jack clients can acquire. @PAR@ jack.conf node.latency To force a specific minimum buffer size for the JACK applications, configure: ``` node.latency = 1024/48000 ``` This configures a buffer-size of 1024 samples at 48KHz. If the graph is running at a different sample rate, the buffer-size will be adjusted accordingly. @PAR@ jack.conf node.lock-quantum To make sure that no automatic quantum is changes while JACK applications are running, configure: ``` node.lock-quantum = true ``` The quantum can then only be changed by metadata or when an application is started with node.force-quantum. JACK Applications will also be able to use jack_set_buffersize() to override the quantum. @PAR@ jack.conf node.force-quantum To force the quantum to a certain value and avoid changes to it: ``` node.force-quantum = 1024 ``` The quantum can then only be changed by metadata or when an application is started with node.force-quantum (or JACK applications that use jack_set_buffersize() to override the quantum). @PAR@ jack.conf jack.show-monitor Show the Monitor client and its ports. @PAR@ jack.conf jack.merge-monitor \parblock Exposes the capture ports and monitor ports on the same JACK device client. This is how JACK presents monitor ports to the clients. The default is however *not* to merge them together because this results in more user friendly user interfaces, usually. An extra client with a `Monitor` suffix is created that contains the monitor ports. For example, this is (part of) the output of `jack_lsp` with the default setting (`jack.merge-monitor = false`): Compare: | `jack.merge-monitor = true` | `jack.merge-monitor = false` | |:--|:--| | Built-in Audio Analog Stereo:playback_FL | Built-in Audio Analog Stereo:playback_FL | Built-in Audio Analog Stereo:monitor_FL | Built-in Audio Analog Stereo Monitor:monitor_FL | Built-in Audio Analog Stereo:playback_FR | Built-in Audio Analog Stereo:playback_FR | Built-in Audio Analog Stereo:monitor_FR |Built-in Audio Analog Stereo Monitor:monitor_FR \endparblock @PAR@ jack.conf jack.show-midi Show the MIDI clients and their ports. @PAR@ jack.conf jack.short-name \parblock To use shorter names for the device client names use `jack.short-name = true`. Compare: | `jack.short-name = true` | `jack.short-name = false` | |:--|:--| | HDA Intel PCH:playback_FL | Built-in Audio Analog Stereo:playback_FL | HDA Intel PCH Monitor:monitor_FL | Built-in Audio Analog Stereo Monitor:monitor_FL | HDA Intel PCH:playback_FR | Built-in Audio Analog Stereo:playback_FR | HDA Intel PCH Monitor:monitor_FR |Built-in Audio Analog Stereo Monitor:monitor_FR \endparblock @PAR@ jack.conf jack.filter-name @PAR@ jack.conf jack.filter-char Will replace all special characters with `jack.filter-char`. For clients the special characters are ` ()[].:*$` and for ports they are ` ()[].*$`. Use this option when a client is not able to deal with the special characters. (and older version of PortAudio was known to use the client and port names as a regex, and thus failing when there are regex special characters in the name). @PAR@ jack.conf jack.self-connect-mode \parblock Restrict a client from making connections to and from itself. Possible values and their meaning are summarized as: | Value | Behavior |:--|:--| | `allow` | Don't restrict self connect requests. | `fail-external` | Fail self connect requests to external ports only. | `ignore-external` | Ignore self connect requests to external ports only. | `fail-all` | Fail all self connect requests. | `ignore-all` | Ignore all self connect requests. \endparblock @PAR@ jack.conf jack.locked-process Make sure the process and callbacks can not be called at the same time. This is the normal operation but it can be disabled in case a specific client can handle this. @PAR@ jack.conf jack.default-as-system \parblock Name the default source and sink as `system` and number the ports to maximize compatibility with JACK programs. | `jack.default-as-system = false` | `jack.default-as-system = true` | |:--|:--| | HDA Intel PCH:playback_FL | system:playback_1 | HDA Intel PCH Monitor:monitor_FL | system:monitor_1 | HDA Intel PCH:playback_FR | system:playback_2 | HDA Intel PCH Monitor:monitor_FR | system:monitor_2 \endparblock @PAR@ jack.conf jack.fix-midi-events Fix NoteOn events with a 0 velocity to NoteOff. This is standard behaviour in JACK and is thus enabled by default to maximize compatibility. Especially LV2 plugins do not allow NoteOn with 0 velocity. @PAR@ jack.conf jack.global-buffer-size When a client has this option, buffersize changes will be applied globally and permanently for all PipeWire clients using the metadata. @PAR@ jack.conf jack.passive-links Makes JACK clients make passive links. This option only works when the server link-factory was configured with the `allow.link.passive` option. @PAR@ jack.conf jack.max-client-ports Limit the number of allowed ports per client to this value. @PAR@ jack.conf jack.fill-aliases Automatically set the port alias1 and alias2 on the ports. @PAR@ jack.conf jack.writable-input \parblock Makes the input buffers writable. This is the default because some JACK clients write to the input buffer. This however can cause corruption in other clients when they are also reading from the buffer. Set this to true to avoid buffer corruption if you are only dealing with non-buggy clients. \endparblock @PAR@ jack.conf jack.flag-midi2 \parblock Use the new JACK MIDI2 port flag on MIDI2 (UMP) ports. This is disabled by default because most JACK apps don't know about this flag yet and refuse to show the port. Set this to true for applications that know how to handle MIDI2 ports. \endparblock # MATCH RULES @IDX@ jack.conf jack.rules `jack.rules` provides an `update-props` action that takes an object with properties that are updated on the client and node object of the jack client. Add a `jack.rules` section in the config file like this: ```css # ~/.config/pipewire/jack.conf.d/custom.conf jack.rules = [ { matches = [ { # all keys must match the value. ! negates. ~ starts regex. application.process.binary = "jack_simple_client" } ] actions = { update-props = { node.latency = 512/48000 } } } { matches = [ { client.name = "catia" } ] actions = { update-props = { jack.merge-monitor = true } } } ] ``` Will set the latency of jack_simple_client to 512/48000 and makes Catia see the monitor client merged with the playback client. # ENVIRONMENT VARIABLES @IDX@ jack-env See \ref page_man_pipewire_1 "pipewire(1)" for common environment variables. Many of these also apply to JACK client applications. Environment variables can be used to control the behavior of the PipeWire JACK client library. @PAR@ jack-env PIPEWIRE_NOJACK @PAR@ jack-env PIPEWIRE_INTERNAL When any of these variables is set, the JACK client library will refuse to open a client. The `PIPEWIRE_INTERNAL` variable is set by the PipeWire main daemon to avoid self connections. @PAR@ jack-env PIPEWIRE_PROPS Adds/overrides the properties specified in the `jack.conf` file. Check out the output of this: ``` > PIPEWIRE_PROPS='{ jack.short-name=true jack.merge-monitor=true }' jack_lsp ... HDA Intel PCH:playback_FL HDA Intel PCH:monitor_FL HDA Intel PCH:playback_FR HDA Intel PCH:monitor_FR ... ``` @PAR@ jack-env PIPEWIRE_LATENCY \parblock ``` PIPEWIRE_LATENCY=/ ``` A quick way to configure the maximum buffer-size for a client. It will run this client with the specified buffer-size (or smaller). `PIPEWIRE_LATENCY=256/48000 jack_lsp` is equivalent to `PIPEWIRE_PROPS='{ node.latency=256/48000 }' jack_lsp` A better way to start a jack session in a specific buffer-size is to force it with: ``` pw-metadata -n settings 0 clock.force-quantum ``` This always works immediately and the buffer size will not change until the quantum is changed back to 0. \endparblock @PAR@ jack-env PIPEWIRE_RATE \parblock ``` PIPEWIRE_RATE=1/ ``` A quick way to configure the rate of the graph. It will try to switch the samplerate of the graph. This can usually only be done with the graph is idle and the rate is part of the allowed sample rates. `PIPEWIRE_RATE=1/48000 jack_lsp` is equivalent to `PIPEWIRE_PROPS='{ node.rate=1/48000 }' jack_lsp` A better way to start a jack session in a specific rate is to force the rate with: ``` pw-metadata -n settings 0 clock.force-rate ``` This always works and the samplerate does not need to be in the allowed rates. The rate will also not change until it is set back to 0. \endparblock @PAR@ jack-env PIPEWIRE_QUANTUM \parblock ``` PIPEWIRE_QUANTUM=/ ``` Is similar to using `PIPEWIRE_LATENCY=/` and `PIPEWIRE_RATE=1/` (see above), except that it is not just a suggestion but it actively *forces* the graph to change the rate and quantum. It can be used to set both a buffersize and samplerate at the same time. When 2 applications force a quantum, the last one wins. When the winning app is stopped, the quantum of the previous app is restored. \endparblock @PAR@ jack-env PIPEWIRE_LINK_PASSIVE \parblock ``` PIPEWIRE_LINK_PASSIVE=true qjackctl ``` Make this client create passive links only. All links created by the client will be marked passive and will not keep the sink/source busy. You can use this to link filters to devices. When there is no client connected to the filter, only passive links remain between the filter and the device and the device will become idle and suspended. \endparblock @PAR@ jack-env PIPEWIRE_NODE \parblock ``` PIPEWIRE_NODE= ``` Will sort the ports so that only the ports of the node with are listed. You can use this to force an application to only deal with the ports of a certain node, for example when auto connecting. \endparblock # AUTHORS The PipeWire Developers <$(PACKAGE_BUGREPORT)>; PipeWire is available from <$(PACKAGE_URL)> # SEE ALSO \ref page_man_pw-jack_1 "pw-jack(1)", \ref page_man_pipewire_conf_5 "pipewire.conf(5)" pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/doc/dox/config/pipewire-props.7.md000066400000000000000000001542471511204443500267730ustar00rootroot00000000000000\page page_man_pipewire-props_7 pipewire-props PipeWire object property reference. \tableofcontents # DESCRIPTION PipeWire describes and configures audio and video elements with objects of the following main types: \par Node Audio or video sink/source endpoint \par Device Sound cards, bluetooth devices, cameras, etc. May have multiple nodes. \par Monitor Finding devices and handling hotplugging \par Port Audio/video endpoint in a node \par Link Connection between ports, that transporting audio/video between them. \par Client Application connected to PipeWire. All objects have *properties* ("props"), most of which can be set in configuration files or at runtime when the object is created. Some of the properties are "common properties" (for example `node.description`) and can be set on all objects of the given type. Other properties control settings of a specific kinds of device or node (ALSA, Bluetooth, ...), and have meaning only for those objects. # CUSTOMIZING PROPERTIES @IDX@ props Usually, all device properties are configured in the session manager configuration, see the session manager documentation. Application properties are configured in ``client.conf`` (for native PipeWire and ALSA applications), ``pipewire-pulse.conf`` (for Pulseaudio applications), and ``jack.conf`` (for JACK applications). In minimal PipeWire setups without a session manager, the device properties can be configured via \ref pipewire_conf__context_objects "context.objects in pipewire.conf(5)" when creating the devices. ## Examples Device configuration using WirePlumber (requires WirePlumber restart to apply). See [WirePlumber configuration](https://pipewire.pages.freedesktop.org/wireplumber/daemon/configuration.html) ```css # ~/.config/wireplumber/wireplumber.conf.d/custom-props.conf monitor.alsa.properties = { alsa.udev.expose-busy = true } monitor.alsa.rules = [ { matches = [ { device.name = "~alsa_card.pci-.*" } ], actions = { update-props = { api.alsa.soft-mixer = true } } }, { matches = [ { node.name = "alsa_output.pci-0000_03_00.1.hdmi-stereo-extra3" } ] actions = { update-props = { node.description = "Main Audio" } } } ] monitor.alsa-midi.properties = { api.alsa.seq.ump = true } monitor.bluez.properties = { bluez5.hfphsp-backend = ofono } monitor.bluez.rules = [ { matches = [ { device.name = "~bluez_card.*" } ], actions = { update-props = { bluez5.dummy-avrcp player = true } } } ] ``` Native client configuration (requires client application restart to apply). See \ref client_conf__stream_rules "pipewire-client.conf(5)" ```css # ~/.config/pipewire/client.conf/custom-props.conf stream.rules = [ { matches = [ { application.name = "pw-play" } ] actions = { update-props = { node.description = "Some pw-cat stream" } } } ] ``` Pulseaudio client configuration (requires \ref page_man_pipewire-pulse_1 "pipewire-pulse(1)" restart to apply). See \ref pipewire-pulse_conf__stream_rules "pipewire-pulse.conf(5)" ```css # ~/.config/pipewire/pipewire-pulse.conf/custom-props.conf stream.rules = [ { matches = [ { application.name = "paplay" } ] actions = { update-props = { node.description = "Some paplay stream" } } } ] ``` JACK client configuration (requires client restart to apply). See \ref jack_conf__match_rules "pipewire-jack.conf(5)" ```css # ~/.config/pipewire/jack.conf/custom-props.conf jack.rules = [ { matches = [ { client.name = "jack_delay" } ] actions = { update-props = { node.description = "Some JACK node" } } } ] ``` # COMMON DEVICE PROPERTIES @IDX@ props These are common properties for devices. @PAR@ device-prop device.name # string A (unique) name for the device. It can be used by command-line and other tools to identify the device. @PAR@ device-prop device.param.PARAM = { ... } # JSON \parblock Set value of a device \ref spa_param_type "Param" to a JSON value when the device is loaded. This works similarly as \ref page_man_pw-cli_1 "pw-cli(1)" `set-param` command. The `PARAM` should be replaced with the name of the Param to set, ie. for example `device.Param.Props = { ... }` to set `Props`. \endparblock @PAR@ device-prop device.plugged # integer \parblock \copydoc PW_KEY_DEVICE_PLUGGED \endparblock @PAR@ device-prop device.nick # string \parblock \copydoc PW_KEY_DEVICE_NICK \endparblock @PAR@ device-prop device.description # string \parblock \copydoc PW_KEY_DEVICE_DESCRIPTION \endparblock @PAR@ device-prop device.serial # string \parblock \copydoc PW_KEY_DEVICE_SERIAL \endparblock @PAR@ device-prop device.vendor.id # integer \parblock \copydoc PW_KEY_DEVICE_VENDOR_ID \endparblock @PAR@ device-prop device.vendor.name # string \parblock \copydoc PW_KEY_DEVICE_VENDOR_NAME \endparblock @PAR@ device-prop device.product.id # integer \parblock \copydoc PW_KEY_DEVICE_PRODUCT_ID \endparblock @PAR@ device-prop device.product.name # string \parblock \copydoc PW_KEY_DEVICE_PRODUCT_NAME \endparblock @PAR@ device-prop device.class # string \parblock \copydoc PW_KEY_DEVICE_CLASS \endparblock @PAR@ device-prop device.form-factor # string \parblock \copydoc PW_KEY_DEVICE_FORM_FACTOR \endparblock @PAR@ device-prop device.icon # string \parblock \copydoc PW_KEY_DEVICE_ICON \endparblock @PAR@ device-prop device.icon-name # string \parblock \copydoc PW_KEY_DEVICE_ICON_NAME \endparblock @PAR@ device-prop device.intended-roles # string \parblock \copydoc PW_KEY_DEVICE_INTENDED_ROLES \endparblock @PAR@ device-prop device.disabled = false # boolean Disable the creation of this device in session manager. There are other common `device.*` properties for technical purposes and not usually user-configurable. \see pw_keys in the API documentation for a full list. # COMMON NODE PROPERTIES @IDX@ props The properties here apply to general audio or video input/output streams, and other nodes such as sinks or sources corresponding to real or virtual devices. ## Identifying Properties @IDX@ props These contain properties to identify the node or to display the node in a GUI application. @PAR@ node-prop node.name # string A (unique) name for the node. This is usually set on sink and sources to identify them as targets for linking by the session manager. @PAR@ node-prop node.nick # string A short name for the node. @PAR@ node-prop node.description # string A human readable description of the node or stream. @PAR@ node-prop media.name A user readable media name, usually the artist and title. These are usually shown in user facing applications to inform the user about the current playing media. @PAR@ node-prop media.title A user readable stream title. @PAR@ node-prop media.artist A user readable stream artist @PAR@ node-prop media.copyright User readable stream copyright information @PAR@ node-prop media.software User readable stream generator software information @PAR@ node-prop media.language Stream language in POSIX format. Ex: `en_GB` @PAR@ node-prop media.filename File name for the stream @PAR@ node-prop media.icon Icon for the media, a base64 blob with PNG image data @PAR@ node-prop media.icon-name An XDG icon name for the media. Ex: `audio-x-mp3` @PAR@ node-prop media.comment Extra stream comment @PAR@ node-prop media.date Date of the media @PAR@ node-prop media.format User readable stream format information @PAR@ node-prop object.linger = false If the object should outlive its creator. @PAR@ node-prop device.id ID of the device the node belongs to. ## Classifying Properties @IDX@ props The classifying properties of a node are use for routing the signal to its destination and for configuring the settings. @PAR@ node-prop media.type The media type contains a broad category of the media that is being processed by the node. Possible values include "Audio", "Video", "Midi" @PAR@ node-prop media.category \parblock What kind of processing is done with the media. Possible values include: * Playback: media playback. * Capture: media capture. * Duplex: media capture and playback or media processing in general. * Monitor: a media monitor application. Does not actively change media data but monitors activity. * Manager: Will manage the media graph. \endparblock @PAR@ node-prop media.role \parblock The Use case of the media. Possible values include: * Movie: Movie playback with audio and video. * Music: Music listening. * Camera: Recording video from a camera. * Screen: Recording or sharing the desktop screen. * Communication: VOIP or other video chat application. * Game: Game. * Notification: System notification sounds. * DSP: Audio or Video filters and effect processing. * Production: Professional audio processing and production. * Accessibility: Audio and Visual aid for accessibility. * Test: Test program. \endparblock @PAR@ node-prop media.class \parblock The media class is to classify the stream function. Possible values include: * Video/Source: a producer of video, like a webcam. * Video/Sink: a consumer of video, like a display window. * Audio/Source: a source of audio samples like a microphone. * Audio/Sink: a sink for audio samples, like an audio card. * Audio/Duplex: a node that is both a sink and a source. * Stream/Output/Audio: a playback stream. * Stream/Input/Audio: a capture stream. The session manager assigns special meaning to the nodes based on the media.class. Sink or Source classes are used as targets for Stream classes, etc.. \endparblock ## Scheduling Properties @IDX@ props @PAR@ node-prop node.latency = 1024/48000 Sets a suggested latency on the node as a fraction. This is just a suggestion, the graph will try to configure this latency or less for the graph. It is however possible that the graph is forced to a higher latency. @PAR@ node-prop node.lock-quantum = false \parblock When this node is active, the quantum of the graph is locked and not allowed to change automatically. It can still be changed forcibly with metadata or when a node forces a quantum. JACK clients use this property to avoid unexpected quantum changes. \endparblock @PAR@ node-prop node.force-quantum = INTEGER \parblock While the node is active, force a quantum in the graph. The last node to be activated with this property wins. A value of 0 unforces the quantum. \endparblock @PAR@ node-prop node.rate = RATE Suggest a rate (samplerate) for the graph. The suggested rate will only be applied when doing so would not cause interruptions (devices are idle) and when the rate is in the list of allowed rates in the server. @PAR@ node-prop node.lock-rate = false When the node is active, the rate of the graph will not change automatically. It is still possible to force a rate change with metadata or with a node property. @PAR@ node-prop node.force-rate = RATE \parblock When the node is active, force a specific sample rate on the graph. The last node to activate with this property wins. A RATE of 0 means to force the rate in `node.rate` denominator. \endparblock @PAR@ node-prop node.always-process = false \parblock When the node is active, it will always be joined with a driver node, even when nothing is linked to the node. Setting this property to true also implies node.want-driver = true. This is the default for JACK nodes, that always need their process callback called. \endparblock @PAR@ node-prop node.want-driver = true The node wants to be linked to a driver so that it can start processing. This is the default for streams and filters since 0.3.51. Nodes that are not linked to anything will still be set to the idle state, unless node.always-process is set to true. @PAR@ node-prop node.pause-on-idle = false @PAR@ node-prop node.suspend-on-idle = false \parblock When the node is not linked anymore, it becomes idle. Normally idle nodes keep processing and are suspended by the session manager after some timeout. It is possible to immediately pause a node when idle with this property. When the session manager does not suspend nodes (or when there is no session manager), the node.suspend-on-idle property can be used instead. \endparblock @PAR@ node-prop node.loop.name = null @PAR@ node-prop node.loop.class = data.rt \parblock Add the node to a specific loop name or loop class. By default the node is added to the data.rt loop class. You can make more specific data loops and then assign the nodes to those. Other well known names are main-loop.0 and the main node.loop.class which runs the node data processing in the main loop. \endparblock @PAR@ node-prop priority.driver # integer \parblock The priority of choosing this device as the driver in the graph. The driver is selected from all linked devices by selecting the device with the highest priority. Normally, the session manager assigns higher priority to sources so that they become the driver in the graph. The reason for this is that adaptive resampling should be done on the sinks rather than the source to avoid signal distortion when capturing audio. \endparblock @PAR@ node-prop clock.name # string \parblock The name of the clock. This name is auto generated from the card index and stream direction. Devices with the same clock name will not use a resampler to align the clocks. This can be used to link devices together with a shared word clock. In Pro Audio mode, nodes from the same device are assumed to have the same clock and no resampling will happen when linked together. So, linking a capture port to a playback port will not use any adaptive resampling in Pro Audio mode. In Non Pro Audio profile, no such assumption is made and adaptive resampling is done in all cases by default. This can also be disabled by setting the same clock.name on the nodes. \endparblock ## Session Manager Properties @IDX@ props @PAR@ node-prop node.autoconnect = true Instructs the session manager to automatically connect this node to some other node, usually a sink or source. @PAR@ node-prop node.exclusive = false If this node wants to be linked exclusively to the sink/source. @PAR@ node-prop target.object = Where the node should link to, this can be a node.name or an object.serial. @PAR@ node-prop node.target = Where this node should be linked to. This can be a node.name or an object.id of a node. This property is deprecated, the target.object property should be used instead, which uses the more unique object.serial as a possible target. @PAR@ node-prop node.dont-reconnect = false \parblock When the node has a target configured and the target is destroyed, destroy the node as well. This property also inhibits that the node is moved to another sink/source. Note that if a stream should appear/disappear in sync with the target, a session manager (WirePlumber) script should be written instead. \endparblock @PAR@ node-prop node.dont-fallback = false If linking this node to its specified target does not succeed, session manager should not fall back to linking it to the default target. @PAR@ node-prop node.dont-move = false Whether the node target may be changed using metadata. @PAR@ node-prop node.passive = false \parblock This is a passive node and so it should not keep sinks/sources busy. This property makes the session manager create passive links to the sink/sources. If the node is not otherwise linked (via a non-passive link), the node and the sink it is linked to are idle (and eventually suspended). This is used for filter nodes that sit in front of sinks/sources and need to suspend together with the sink/source. \endparblock @PAR@ node-prop node.link-group = ID Add the node to a certain link group. Nodes from the same link group are not automatically linked to each other by the session manager. And example is a coupled stream where you don't want the output to link to the input streams, making a useless loop. @PAR@ node-prop stream.dont-remix = false Instruct the session manager to not remix the channels of a stream. Normally the stream channel configuration is changed to match the sink/source it is connected to. With this property set to true, the stream will keep its original channel layout and the session manager will link matching channels with the sink. @PAR@ node-prop priority.session # integer The priority for selecting this node as the default source or sink. @PAR@ node-prop session.suspend-timeout-seconds = 3 # float Timeout in seconds, after which an idle node is suspended. Value ``0`` means the node will not be suspended. @PAR@ node-prop state.restore-props = true Whether session manager should save state for this node. ## Format Properties Streams and also most device nodes can be configured in a certain format with properties. @PAR@ node-prop audio.rate = RATE Forces a samplerate on the node. @PAR@ node-prop audio.channels = INTEGER The number of audio channels to use. Must be a value between 1 and 64. @PAR@ node-prop audio.format = FORMAT \parblock Forces an audio format on the node. This is the format used internally in the node because the graph processing format is always float 32. Valid formats include: S16, S32, F32, F64, S16LE, S16BE, ... \endparblock @PAR@ node-prop audio.allowed-rates An array of allowed samplerates for the node. ex. "[ 44100 48000 ]" ## Other Properties @PAR@ node-prop node.param.PARAM = { ... } # JSON \parblock Set value of a node \ref spa_param_type "Param" to a JSON value when the device is loaded. This works similarly as \ref page_man_pw-cli_1 "pw-cli(1)" `set-param` command. The `PARAM` should be replaced with the name of the Param to set, ie. for example `node.param.Props = { ... }` to set `Props`. \endparblock @PAR@ node-prop node.disabled = false # boolean Disable the creation of this node in session manager. # AUDIO ADAPTER PROPERTIES @IDX@ props Most audio nodes (ALSA, Bluetooth, audio streams from applications, ...) have common properties for the audio adapter. The adapter performs sample format, sample rate and channel mixing operations. All properties listed below are node properties. ## Merger Parameters The merger is used as the input for a sink device node or a capture stream. It takes the various channels and merges them into a single stream for further processing. The merger will also provide the monitor ports of the input channels and can apply a software volume on the monitor signal. @PAR@ node-prop monitor.channel-volumes = false The volume of the input channels is applied to the volume of the monitor ports. Normally the monitor ports expose the raw unmodified signal on the input ports. ## Resampler Parameters Source, sinks, capture and playback streams contain a high quality adaptive resampler. It uses [sinc](https://ccrma.stanford.edu/~jos/resample/resample.pdf) based resampling with linear interpolation of filter banks to perform arbitrary resample factors. The resampler is activated in the following cases: * The hardware of a device node does not support the graph samplerate. Resampling will occur from the graph samplerate to the hardware samplerate. * The hardware clock of a device does not run at the same speed as the graph clock and adaptive resampling is required to match the clocks. * A stream does not have the same samplerate as the graph and needs to be resampled. * An application wants to activate adaptive resampling in a stream to make it match some other clock. PipeWire performs most of the sample conversions and resampling in the client (Or in the case of the PulseAudio server, in the pipewire-pulse server that creates the streams). This ensures all the conversions are offloaded to the clients and the server can deal with one single format for performance reasons. Below is an explanation of the options that can be tuned in the sample converter. @PAR@ node-prop resample.quality = 4 \parblock The quality of the resampler. from 0 to 14, the default is 4. Increasing the quality will result in better cutoff and less aliasing at the expense of (much) more CPU consumption. The default quality of 4 has been selected as a good compromise between quality and performance with no artifacts that are well below the audible range. See [Infinite Wave](https://src.infinitewave.ca/) for a comparison of the performance. \endparblock @PAR@ node-prop resample.disable = false Disable the resampler entirely. The node will only be able to negotiate with the graph when the samplerates are compatible. @PAR@ node-prop resample.window = exp The resampler window function to use. By default an exponential window function is used that gives a good balance between complexitiy and quality. You can also specify a blackman or kaiser window, both with different tradeoffs. @PAR@ node-prop resample.cutoff = 0.0 The resampler cutoff frequency. A value of 0.0 will use a predefined value based on the resampler quality. @PAR@ node-prop resample.n-taps = 0 The resampler number of taps. A value of 0 will use a predefined value based on the resampler quality or other window function parameters. @PAR@ node-prop resample.param.exp.A = 0.0 The A parameter for the exponential window function. A value of 0.0 will use a predefined value based on the quality when the exp window is in use. @PAR@ node-prop resample.param.blackman.alpha = 0.0 The alpha value of the blackman function. A value of 0.0 will use a predefined value based on the quality when the blackman window is in use. @PAR@ node-prop resample.param.kaiser.stopband-attenuation = 0.0 The kaiser window stopband attenuation parameter. A default value of 0.0 will use a predefined value based on the quality. @PAR@ node-prop resample.param.kaiser.transition-bandwidth = 0.0 The kaiser window transition bandwidth parameter. A default value of 0.0 will use a predefined value based on the quality. @PAR@ node-prop resample.param.kaiser.alpha = 0.0 The kaiser window alpha parameter. A default value of 0.0 will calculate an alpha value based on the stopband-attenuation and transition-bandwidth parameters. ## Channel Mixer Parameters Source, sinks, capture and playback streams can apply channel mixing on the incoming signal. Normally the channel mixer is not used for devices, the device channels are usually exposed as they are. This policy is usually enforced by the session manager, so we refer to its documentation there. Playback and capture streams are usually configured to the channel layout of the sink/source they connect to and will thus perform channel mixing. The channel mixer also implements a software volume. This volume adjustment is performed on the original channel layout. ex: A stereo playback stream that is up-mixed to 5.1 has 2 a left an right volume control. @PAR@ node-prop channelmix.disable = false Disables the channel mixer completely. The stream will only be able to link to compatible sources/sinks with the exact same channel layout. @PAR@ node-prop channelmix.min-volume = 0.0 @PAR@ node-prop channelmix.max-volume = 10.0 Gives the min and max volume values allowed. Any volume that is set will be clamped to these values. @PAR@ node-prop channelmix.normalize = false \parblock Makes sure that during such mixing & resampling original 0 dB level is preserved, so nothing sounds wildly quieter/louder. While this options prevents clipping, it can in some cases produce too low volume. Increase the volume in that case or disable normalization. \endparblock @PAR@ node-prop channelmix.lock-volumes = false Completely disable volume or mute changes. Defaults to false. @PAR@ node-prop channelmix.mix-lfe = true Mixes the low frequency effect channel into the front center or stereo pair. This might enhance the dynamic range of the signal if there is no subwoofer and the speakers can reproduce the low frequency signal. @PAR@ node-prop channelmix.upmix = true \parblock Enables up-mixing of the front center (FC) when the target has a FC channel. The sum of the stereo channels is used and an optional lowpass filter can be used (see `channelmix.fc-cutoff`). Also enabled up-mixing of LFE when `channelmix.lfe-cutoff` is set to something else than 0 and the target has an LFE channel. The LFE channel is produced by adding the stereo channels. If `channelmix.upmix` is true, the up-mixing of the rear channels is also enabled and controlled with the `channelmix-upmix-method` property. \endparblock @PAR@ node-prop channelmix.upmix-method = psd \parblock 3 methods are provided to produce the rear channels in a surround sound: 1. none. No rear channels are produced. 2. simple. Front channels are copied to the rear. This is fast but can produce phasing effects. 3. psd. The rear channels as produced from the front left and right ambient sound (the difference between the channels). A delay and optional phase shift are added to the rear signal to make the sound bigger. \endparblock @PAR@ node-prop channelmix.lfe-cutoff = 150 Apply a lowpass filter to the low frequency effects. The value is expressed in Hz. Typical subwoofers have a cutoff at around 150 and 200. The default value of 0 disables the feature. @PAR@ node-prop channelmix.fc-cutoff = 12000 \parblock Apply a lowpass filter to the front center frequency. The value is expressed in Hz. Since the front center contains the dialogs, a typical cutoff frequency is 12000 Hz. This option is only active when the up-mix is enabled. \endparblock @PAR@ node-prop channelmix.rear-delay = 12.0 \parblock Apply a delay in milliseconds when up-mixing the rear channels. This improves specialization of the sound. A typical delay of 12 milliseconds is the default. This is only active when the `psd` up-mix method is used. \endparblock @PAR@ node-prop channelmix.stereo-widen = 0.0 \parblock Subtracts some of the front center signal from the stereo channels. This moves the dialogs more to the center speaker and leaves the ambient sound in the stereo channels. This is only active when up-mix is enabled and a Front Center channel is mixed. \endparblock @PAR@ node-prop channelmix.hilbert-taps = 0 \parblock This option will apply a 90 degree phase shift to the rear channels to improve specialization. Taps needs to be between 15 and 255 with more accurate results (and more CPU consumption) for higher values. This is only active when the `psd` up-mix method is used. \endparblock @PAR@ node-prop dither.noise = 0 \parblock This option will add N bits of random data to the signal. When no dither.method is specified, the random data will flip between [-(1<<(N-1)), 0] every 1024 samples. With a dither.method, the dither noise is amplified with 1<<(N-1) bits. This can be used to keep some amplifiers alive during silent periods. One or two bits of noise is usually enough, otherwise the noise will become audible. This is usually used together with `session.suspend-timeout-seconds` to disable suspend in the session manager. Note that PipeWire uses floating point operations with 24 bits precission for all of the audio processing. Conversion to 24 bits integer sample formats is lossless and conversion to 32 bits integer sample formats are simply padded with 0 bits at the end. This means that the dither noise is always only in the 24 most significant bits. \endparblock @PAR@ node-prop dither.method = none \parblock Optional [dithering](https://en.wikipedia.org/wiki/Dither) can be done on the quantized output signal. There are 6 modes available: 1. none No dithering is done. 2. rectangular Dithering with a rectangular noise distribution. This adds random bits in the [-0.5, 0.5] range to the signal with even distribution. 3. triangular Dithering with a triangular noise distribution. This add random bits in the [-1.0, 1.0] range to the signal with triangular distribution around 0.0. 4. triangular-hf Dithering with a sloped triangular noise distribution. 5. wannamaker3 Additional noise shaping is performed on the sloped triangular dithering to move the noise to the more inaudible range. This is using the "F-Weighted" noise filter described by Wannamaker. 6. shaped5 Additional noise shaping is performed on the triangular dithering to move the noise to the more inaudible range. This is using the Lipshitz filter. Dithering is only useful for conversion to a format with less than 24 bits and will be disabled otherwise. \endparblock ## Debug Parameters @PAR@ node-prop debug.wav-path = "" Make the stream to also write the raw samples to a WAV file for debugging purposes. ## Other Parameters These control low-level technical features: @PAR@ node-prop clock.quantum-limit \ref pipewire_conf__default_clock_quantum-limit "See pipewire.conf(5)" @PAR@ node-prop resample.peaks = false # boolean Instead of actually resampling, produce peak amplitude values as output. This is used for volume monitoring, where it is set as a property of the "recording" stream. @PAR@ node-prop resample.prefill = false # boolean Prefill resampler buffers with silence. This affects the initial samples produced by the resampler. @PAR@ node-prop adapter.auto-port-config = null # JSON \parblock If specified, configure the ports of the node when it is created, instead of leaving that to the session manager to do. This is useful (only) for minimal configurations without a session manager. Value is SPA JSON of the form: ```json { mode = "none", # "none", "passthrough", "convert", "dsp" monitor = false, # boolean control = false, # boolean position = "preserve" # "unknown", "aux", "preserve" } ``` See \ref spa_param_port_config for the meaning. \endparblock # ALSA PROPERTIES @IDX@ props ## Monitor properties @PAR@ monitor-prop alsa.use-acp = true # boolean Use \ref monitor-prop__alsa_card_profiles "ALSA Card Profiles" (ACP) for device configuration. This autodetects available ALSA devices and configures port and hardware mixers. @PAR@ monitor-prop alsa.use-ucm # boolean Enable or disable UCM for all devices. Default: unset. @PAR@ monitor-prop alsa.udev.expose-busy # boolean Expose the ALSA card even if it is busy/in use. Default false. This can be useful when some of the PCMs are in use by other applications but the other free PCMs should still be exposed. ## Device properties @PAR@ device-prop api.alsa.path # string ALSA device path as can be used in snd_pcm_open() and snd_ctl_open(). @PAR@ device-prop api.alsa.use-ucm = true # boolean \parblock When ACP is enabled and a UCM configuration is available for a device, by default it is used instead of the ACP profiles. This option allows you to disable this and use the ACP profiles instead. This option does nothing if `alsa.use-acp` is set to `false`. \endparblock @PAR@ device-prop api.alsa.soft-mixer = false # boolean Setting this option to `true` will disable the hardware mixer for volume control and mute. All volume handling will then use software volume and mute, leaving the hardware mixer untouched. This can be interesting to work around bugs in the mixer detection or decibel reporting. The hardware mixer will still be used to mute unused audio paths in the device. Use `api.alsa.disable-mixer-path` to also disable mixer path selection. @PAR@ device-prop api.alsa.disable-mixer-path = false # boolean Setting this option to `true` will disable the hardware mixer path selection. The hardware mixer path is the configuration of the mixer depending on the jacks that are inserted in the card. If this is disabled, you will have to manually enable and disable mixer controls but it can be used to work around bugs in the mixer. The hardware mixer will still be used for volume and mute. Use `api.alsa.soft-mixer` to also disable hardware volume and mute. @PAR@ device-prop api.alsa.ignore-dB = false # boolean Setting this option to `true` will ignore the decibel setting configured by the driver. Use this when the driver reports wrong settings. @PAR@ device-prop device.profile-set # string This option can be used to select a custom ACP profile-set name for the device. This can be configured in UDev rules, but it can also be specified here. The default is to use "default.conf" unless there is a matching udev rule. @PAR@ device-prop device.profile # string The initial active profile name. The default is to start from the "Off" profile and then let session manager select the best profile based on its policy. @PAR@ device-prop api.acp.auto-profile = true # boolean Automatically select the best profile for the device. The session manager usually disables this, as it handles this task instead. This can be enabled in custom configurations without the session manager handling this. @PAR@ device-prop api.acp.auto-port = true # boolean Automatically select the highest priority port that is available ("port" is a PulseAudio/ACP term, the equivalent of a "Route" in PipeWire). The session manager usually disables this, as it handles this task instead. This can be enabled in custom configurations without the session manager handling this. @PAR@ device-prop api.acp.probe-rate # integer Sets the samplerate used for probing the ALSA devices and collecting the profiles and ports. @PAR@ device-prop api.acp.pro-channels # integer Sets the number of channels to use when probing the "Pro Audio" profile. Normally, the maximum amount of channels will be used but with this setting this can be reduced, which can make it possible to use other samplerates on some devices. @PAR@ device-prop api.alsa.split-enable # boolean \parblock \copydoc SPA_KEY_API_ALSA_SPLIT_ENABLE \endparblock @PAR@ device-prop api.acp.disable-pro-audio = false # boolean Disable the "Pro Audio" profile for this device. @PAR@ device-prop api.acp.use-eld-channels = false # boolean Use the channel count and mapping the connected HDMI device provides via ELD information. ## Node properties @PAR@ node-prop audio.channels # integer The number of audio channels to open the device with. Defaults depends on the profile of the device. @PAR@ node-prop audio.rate # integer The audio rate to open the device with. Default is 0, which means to open the device with a rate as close to the graph rate as possible. @PAR@ node-prop audio.format # string The audio format to open the device in. By default this is "UNKNOWN", which will open the device in the best possible bits (32/24/16/8..). You can force a format like S16_LE or S32_LE. @PAR@ node-prop audio.position # JSON array of strings The audio position of the channels in the device. This is auto detected based on the profile. You can configure an array of channel positions, like "[ FL, FR ]". @PAR@ node-prop audio.layout # string The audio layout of the channels in the device. You can use any of the predefined layouts, like "Stereo", "5.1" etc. @PAR@ node-prop audio.allowed-rates # JSON array of integers \parblock The allowed audio rates to open the device with. Default is "[ ]", which means the device can be opened in any supported rate. Only rates from the array will be used to open the device. When the graph is running with a rate not listed in the allowed-rates, the resampler will be used to resample to the nearest allowed rate. \endparblock @PAR@ node-prop api.alsa.period-size # integer The period size to open the device in. By default this is 0, which will open the device in the default period size to minimize latency. @PAR@ node-prop api.alsa.period-num # integer The amount of periods to use in the device. By default this is 0, which means to use as many as possible. @PAR@ node-prop api.alsa.headroom # integer The amount of extra space to keep in the ringbuffer. The default is 0. Higher values can be configured when the device read and write pointers are not accurately reported. @PAR@ node-prop api.alsa.start-delay = 0 # integer Some devices need some time before they can report accurate hardware pointer positions. In those cases, an extra start delay can be added to compensate for this startup delay. This sets the startup delay in samples. @PAR@ node-prop api.alsa.disable-mmap = false # boolean Disable mmap operation of the device and use the ALSA read/write API instead. Default is false, mmap is preferred. @PAR@ node-prop api.alsa.disable-batch # boolean Ignore the ALSA batch flag. If the batch flag is set, ALSA will need an extra period to update the read/write pointers. Ignore this flag from ALSA can reduce the latency. Default is false. @PAR@ node-prop api.alsa.use-chmap # boolean Use the driver provided channel map. Default is true when using UCM, false otherwise because many driver don't report this correctly. @PAR@ node-prop api.alsa.multi-rate # boolean Allow devices from the same card to be opened in multiple sample rates. Default is true. Some older drivers did not properly advertise the capabilities of the device and only really supported opening the device in one rate. @PAR@ node-prop api.alsa.htimestamp = false # boolean Use ALSA htimestamps in scheduling, instead of the system clock. Some ALSA drivers produce bad timestamps, so this is not enabled by default and will be disabled at runtime if it looks like the ALSA timestamps are bad. @PAR@ node-prop api.alsa.htimestamp.max-errors # integer Specify the number of consecutive errors before htimestamp is disabled. Setting this to 0 makes htimestamp never get disabled. @PAR@ node-prop api.alsa.disable-tsched = false # boolean Disable timer-based scheduling, and use IRQ for scheduling instead. The "Pro Audio" profile will usually enable this setting, if it is expected it works on the hardware. @PAR@ node-prop api.alsa.dll-bandwidth-max # double Sets the maximum bandwidth of the DLL (delay-locked loop) filter used to smooth out rate adjustments. The default value may be too responsive in some scenarios. For example, with UAC2 pitch control, the host reacts more slowly compared to local resampling, so using a lower bandwidth helps avoid oscillations or instability. @PAR@ node-prop api.alsa.auto-link = false # boolean Link follower PCM devices to the driver PCM device when using IRQ-based scheduling. The "Pro Audio" profile will usually enable this setting, if it is expected it works on the hardware. @PAR@ node-prop latency.internal.rate # integer Static set the device systemic latency, in samples at playback rate. @PAR@ node-prop latency.internal.ns # integer Static set the device systemic latency, in nanoseconds. @PAR@ node-prop api.alsa.path # string UNDOCUMENTED @PAR@ node-prop api.alsa.pcm.card # integer Card index to open. Usually determined from `api.alsa.path`. @PAR@ node-prop api.alsa.open.ucm # boolean Open device using UCM. @PAR@ node-prop api.alsa.bind-ctls # boolean UNDOCUMENTED @PAR@ node-prop api.alsa.bind-ctl.NAME # boolean UNDOCUMENTED @PAR@ node-prop iec958.codecs # JSON array of string Enable only specific IEC958 codecs. This can be used to disable some codecs the hardware supports. Available values: PCM, AC3, DTS, MPEG, MPEG2-AAC, EAC3, TRUEHD, DTSHD @PAR@ device-prop api.alsa.split.parent # boolean \parblock \copydoc SPA_KEY_API_ALSA_SPLIT_PARENT \endparblock @PAR@ node-prop api.alsa.split.position # JSON \parblock \copybrief SPA_KEY_API_ALSA_SPLIT_POSITION Informative property. \endparblock @PAR@ node-prop api.alsa.split.hw-position # JSON \parblock \copybrief SPA_KEY_API_ALSA_SPLIT_HW_POSITION Informative property. \endparblock @PAR@ node-prop api.alsa.dsd-lsb = false # boolean Use LSB bit order for DSD audio output. # ALSA MIDI PROPERTIES @IDX@ props ## Node properties For ALSA MIDI in Wireplumber, MIDI bridge node properties are configured in the monitor properties. @PAR@ monitor-prop api.alsa.seq.ump = true # boolean Use MIDI 2.0 if possible. @PAR@ monitor-prop api.alsa.seq.min-pool = 500 # integer UNDOCUMENTED @PAR@ monitor-prop api.alsa.seq.max-pool = 2000 # integer UNDOCUMENTED @PAR@ monitor-prop clock.name = "clock.system.monotonic" # string Clock to follow. @PAR@ monitor-prop api.alsa.path = "default" # string Sequencer device to use. @PAR@ monitor-prop api.alsa.disable-longname = true # boolean If card long name should not be passed to MIDI port. # BLUETOOTH PROPERTIES @IDX@ props ## Monitor properties The following are settings for the Bluetooth device monitor, not device or node properties: @PAR@ monitor-prop bluez5.roles = [ a2dp_sink a2dp_source bap_sink bap_source bap_bcast_sink bap_bcast_source hfp_hf hfp_ag ] # JSON array of string \parblock Enabled roles. Currently some headsets (Sony WH-1000XM3) are not working with both hsp_ag and hfp_ag enabled, so by default we enable only HFP. Supported roles: - `hsp_hs` (HSP Headset), - `hsp_ag` (HSP Audio Gateway), - `hfp_hf` (HFP Hands-Free), - `hfp_ag` (HFP Audio Gateway) - `a2dp_sink` (A2DP Audio Sink) - `a2dp_source` (A2DP Audio Source) - `bap_sink` (LE Audio Basic Audio Profile Sink) - `bap_source` (LE Audio Basic Audio Profile Source) - `bap_bcast_sink` (LE Audio Basic Audio Profile Broadcast Sink) - `bap_bcast_source` (LE Audio Basic Audio Profile Broadcast Source) \endparblock @PAR@ monitor-prop bluez5.codecs # JSON array of string Enabled A2DP codecs (default: all). Possible values: `sbc`, `sbc_xq`, `aac`, `aac_eld`, `aptx`, `aptx_hd`, `aptx_ll`, `aptx_ll_duplex`, `faststream`, `faststream_duplex`, `lc3plus_h3`, `ldac`, `opus_05`, `opus_05_51`, `opus_05_71`, `opus_05_duplex`, `opus_05_pro`, `opus_g`, `lc3`. @PAR@ monitor-prop bluez5.default.rate # integer Default audio rate. @PAR@ monitor-prop bluez5.default.channels # integer Default audio channels. @PAR@ monitor-prop bluez5.hfphsp-backend # integer HFP/HSP backend (default: native). Available values: any, none, hsphfpd, ofono, native @PAR@ monitor-prop bluez5.hfphsp-backend-native-modem # string @PAR@ monitor-prop bluez5.dummy-avrcp player # boolean Register dummy AVRCP player. Some devices have wrongly functioning volume or playback controls if this is not enabled. Default: false @PAR@ monitor-prop bluez5.enable-sbc-xq # boolean Override device quirk list and enable SBC-XQ for devices for which it is disabled. @PAR@ monitor-prop bluez5.enable-msbc # boolean Override device quirk list and enable MSBC for devices for which it is disabled. @PAR@ monitor-prop bluez5.enable-hw-volume # boolean Override device quirk list and enable hardware volume fo devices for which it is disabled. @PAR@ monitor-prop bluez5.hw-offload-sco # boolean \parblock HFP/HSP hardware offload SCO support (default: false). This feature requires a custom configuration that routes SCO audio to ALSA nodes, in a platform-specific way. See `tests/examples/bt-pinephone.lua` in WirePlumber for an example. Do not enable this setting if you don't know what all this means, as it won't work. \endparblock @PAR@ monitor-prop bluez5.a2dp.opus.pro.channels = 3 # integer PipeWire Opus Pro audio profile channel count. @PAR@ monitor-prop bluez5.a2dp.opus.pro.coupled-streams = 1 # integer PipeWire Opus Pro audio profile coupled stream count. @PAR@ monitor-prop bluez5.a2dp.opus.pro.locations = "FL,FR,LFE" # string PipeWire Opus Pro audio profile audio channel locations. @PAR@ monitor-prop bluez5.a2dp.opus.pro.max-bitrate = 600000 # integer PipeWire Opus Pro audio profile max bitrate. @PAR@ monitor-prop bluez5.a2dp.opus.pro.frame-dms = 50 # integer PipeWire Opus Pro audio profile frame duration (1/10 ms). @PAR@ monitor-prop bluez5.a2dp.opus.pro.bidi.channels = 1 # integer PipeWire Opus Pro audio profile duplex channels. @PAR@ monitor-prop bluez5.a2dp.opus.pro.bidi.coupled-streams = 0 # integer PipeWire Opus Pro audio profile duplex coupled stream count. @PAR@ monitor-prop bluez5.a2dp.opus.pro.bidi.locations = "FC" # string PipeWire Opus Pro audio profile duplex coupled channel locations. @PAR@ monitor-prop bluez5.a2dp.opus.pro.bidi.max-bitrate = 160000 # integer PipeWire Opus Pro audio profile duplex max bitrate. @PAR@ monitor-prop bluez5.a2dp.opus.pro.bidi.frame-dms = 400 # integer PipeWire Opus Pro audio profile duplex frame duration (1/10 ms). @PAR@ monitor-prop bluez5.bcast_source.config = [] # JSON \parblock Example: ``` bluez5.bcast_source.config = [ { "broadcast_code": "Børne House", "encryption: false, "sync_factor": 2, "bis": [ { # BIS configuration "qos_preset": "16_2_1", # QOS preset name from table Table 6.4 from BAP_v1.0.1. "audio_channel_allocation": 1, # audio channel allocation configuration for the BIS "metadata": [ # metadata configurations for the BIS { "type": 1, "value": [ 1, 1 ] } ] } ] } ] ``` \endparblock @PAR@ monitor-prop bluez5.bap-server-capabilities.rates # Array of integers Supported sampling frequencies for the LC3 codec (default: all). Possible values: `8000`, `16000`, `24000`, `32000`, `44100`, `48000` @PAR@ monitor-prop bluez5.bap-server-capabilities.durations # Array of doubles Supported frame durations for the LC3 codec (default: all). Possible values: `7.5`, `10` @PAR@ monitor-prop bluez5.bap-server-capabilities.channels # Array of integers Supported audio channel counts for the LC3 codec (default: [1, 2]). Possible values: `1`, `2`, `3`, `4`, `5`, `6`, `7`, `8` @PAR@ monitor-prop bluez5.bap-server-capabilities.framelen_min # integer Minimum number of octets supported per codec frame for the LC3 codec (default: 20). @PAR@ monitor-prop bluez5.bap-server-capabilities.framelen_max # integer Maximum number of octets supported per codec frame for the LC3 codec (default: 400). @PAR@ monitor-prop bluez5.bap-server-capabilities.max_frames # integer Maximum number of codec frames supported per SDU for the LC3 codec (default: 2). @PAR@ monitor-prop bluez5.bap-server-capabilities.sink.locations # JSON or integer Sink audio locations of the server, as channel positions or PACS bitmask. Example: `FL,FR` @PAR@ monitor-prop bluez5.bap-server-capabilities.sink.contexts # integer Available sink contexts PACS bitmask of the the server. @PAR@ monitor-prop bluez5.bap-server-capabilities.sink.supported-contexts # integer Supported sink contexts PACS bitmask of the the server. @PAR@ monitor-prop bluez5.bap-server-capabilities.source.locations # JSON or integer Source audio locations of the server, as channel positions or PACS bitmask. Example: `FL,FR` @PAR@ monitor-prop bluez5.bap-server-capabilities.source.contexts # integer Available source contexts PACS bitmask of the the server. @PAR@ monitor-prop bluez5.bap-server-capabilities.source.supported-contexts # integer Supported source contexts PACS bitmask of the the server. ## Device properties @PAR@ device-prop bluez5.auto-connect # boolean Auto-connect devices on start up. Disabled by default if the property is not specified. @PAR@ device-prop bluez5.hw-volume = [ hfp_ag hsp_ag a2dp_source ] # JSON array of string Profiles for which to enable hardware volume control. @PAR@ device-prop bluez5.profile # string Initial device profile. This usually has no effect as the session manager overrides it. @PAR@ device-prop bluez5.a2dp.ldac.quality = "auto" # string LDAC encoding quality Available values: - auto (Adaptive Bitrate, default) - hq (High Quality, 990/909kbps) - sq (Standard Quality, 660/606kbps) - mq (Mobile use Quality, 330/303kbps) @PAR@ device-prop bluez5.a2dp.aac.bitratemode = 0 # integer AAC variable bitrate mode. Available values: 0 (cbr, default), 1-5 (quality level) @PAR@ device-prop bluez5.a2dp.opus.pro.application = "audio" # string PipeWire Opus Pro Audio encoding mode: audio, voip, lowdelay @PAR@ device-prop bluez5.a2dp.opus.pro.bidi.application = "audio" # string PipeWire Opus Pro Audio duplex encoding mode: audio, voip, lowdelay @PAR@ device-prop bluez5.bap.cig = "auto" # integer, or 'auto' Set CIG ID for BAP unicast streams of the device. @PAR@ device-prop bluez5.bap.preset = "auto" # string BAP QoS preset name that needed to be used with vendor config. This property is experimental. Available: "48_2_1", ... as in the BAP specification. @PAR@ device-prop bluez5.bap.rtn # integer BAP QoS preset name that needed to be used with vendor config. This property is experimental. Default: as per QoS preset. @PAR@ device-prop bluez5.bap.latency # integer BAP QoS latency that needs to be applied for vendor defined preset This property is experimental. Default: as QoS preset. @PAR@ device-prop bluez5.bap.delay = 40000 # integer BAP QoS delay that needs to be applied for vendor defined preset This property is experimental. Default: as per QoS preset. @PAR@ device-prop bluez5.framing = false # boolean BAP QoS framing that needs to be applied for vendor defined preset This property is experimental. Default: as per QoS preset. ## Node properties @PAR@ node-prop bluez5.media-source-role # string \parblock Media source role for Bluetooth clients connecting to this instance. Available values: - playback: playing stream to speakers - input: appear as source node. \endparblock @PAR@ node-prop bluez5.decode-buffer.latency # integer Applies on media source nodes and defines the target amount of samples to be buffered on the output of the decoder. Default: 0, which means it is automatically determined. @PAR@ node-prop node.latency-offset-msec # string Applies only for BLE MIDI nodes. Latency adjustment to apply on the node. Larger values add a constant latency, but reduces timing jitter caused by Bluetooth transport. # PORT PROPERTIES @IDX@ props Port properties are usually not directly configurable via PipeWire configuration files, as they are determined by applications creating them. Below are some port properties may interesting for users: @PAR@ port-prop port.name # string \parblock \copydoc PW_KEY_PORT_NAME \endparblock @PAR@ port-prop port.alias # string \parblock \copydoc PW_KEY_PORT_ALIAS \endparblock \see pw_keys in the API documentation for a full list. # LINK PROPERTIES @IDX@ props Link properties are usually not directly configurable via PipeWire configuration files, as they are determined by applications creating them. \see pw_keys in the API documentation for a full list. # CLIENT PROPERTIES @IDX@ props Client properties are usually not directly configurable via PipeWire configuration files, as they are determined by the application connecting to PipeWire. Clients are however affected by the settings in \ref page_man_pipewire_conf_5 "pipewire.conf(5)" and session manager settings. \note Only the properties `pipewire.*` are safe to use for security purposes such as identifying applications and their capabilities, as clients can set and change other properties freely. Below are some client properties may interesting for users. @PAR@ client-prop application.name # string \parblock \copydoc PW_KEY_APP_NAME \endparblock @PAR@ client-prop application.process.id # integer \parblock \copydoc PW_KEY_APP_PROCESS_ID \endparblock @PAR@ client-prop pipewire.sec.pid # integer \parblock Client pid, set by protocol. Note that for PulseAudio applications, this is the PID of the `pipewire-pulse` process. \endparblock \see pw_keys in the API documentation for a full list. # RUNTIME SETTINGS @IDX@ props Objects such as devices and nodes also have *parameters* that can be modified after the object has been created. For example, the active device profile, channel volumes, and so on. For some objects, the *parameters* also allow changing some of the *properties*. The settings of most ALSA and virtual device parameters can be configured also at runtime. These settings are available in device *parameter* called `Props` in its `params` field. They can be seen e.g. using `pw-dump ` for an ALSA device: ```json { ... "Props": [ { ... "params": [ "audio.channels", 2, "audio.rate", 0, "audio.format", "UNKNOWN", "audio.position", "[ FL, FR ]", "audio.allowed-rates", "[ ]", "api.alsa.period-size", 0, "api.alsa.period-num", 0, "api.alsa.headroom", 0, "api.alsa.start-delay", 0, "api.alsa.disable-mmap", false, "api.alsa.disable-batch", false, "api.alsa.use-chmap", false, "api.alsa.multi-rate", true, "latency.internal.rate", 0, "latency.internal.ns", 0, "clock.name", "api.alsa.c-1" ] } ... ``` They generally have the same names and meaning as the corresponding properties. One or more `params` can be changed using \ref page_man_pw-cli_1 "pw-cli(1)": ``` pw-cli s Props '{ params = [ "api.alsa.headroom" 1024 ] }' ``` These settings are not saved and need to be reapplied for each session manager restart. # ALSA CARD PROFILES @IDX@ props The sound card profiles ("Analog stereo", "Analog stereo duplex", ...) except "Pro Audio" come from two sources: - UCM: ALSA Use Case Manager: the profile configuration system from ALSA. See https://github.com/alsa-project/alsa-ucm-conf/ - ACP ("Alsa Card Profiles"): Pulseaudio's profile system ported to PipeWire. See https://www.freedesktop.org/wiki/Software/PulseAudio/Backends/ALSA/Profiles/ See the above links on how to configure these systems. For ACP, PipeWire looks for the profile configuration files under - ~/.config/alsa-card-profile - /etc/alsa-card-profile - /usr/share/alsa-card-profile/mixer`. The `path` and `profile-set` files are in subdirectories `paths` and `profile-sets` of these directories. It is possible to override individual files locally by putting a modified copy into the ACP directories under `~/.config` or `/etc`. # OTHER OBJECT TYPES @IDX@ props Technically, PipeWire objects is what are manipulated by applications using the PipeWire API. The list of object types that are usually "exported" (i.e. appear in \ref page_man_pw-dump_1 "pw-dump(1)" output) is larger than considered above: - Node - Device - Port - Link - Client - Metadata - Module - Profiler - SecurityContext Monitors do not appear in this list; they are not usually exported, and technically also Device objects. They are considered above as a separate object type because they have configurable properties. Metadata objects are what is manipulated with \ref page_man_pw-metadata_1 "pw-metadata(1)" Modules can be loaded in configuration files, or by PipeWire applications. The Profiler and SecurityContext objects only provide corresponding PipeWire APIs. # INDEX {#pipewire-props__index} ## Monitor properties @SECREF@ monitor-prop ## Device properties @SECREF@ device-prop ## Node properties @SECREF@ node-prop ## Port properties @SECREF@ port-prop ## Client properties @SECREF@ client-prop # AUTHORS The PipeWire Developers <$(PACKAGE_BUGREPORT)>; PipeWire is available from <$(PACKAGE_URL)> # SEE ALSO \ref page_man_pipewire_conf_5 "pipewire.conf(5)" pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/doc/dox/config/pipewire-pulse-modules.7.md000066400000000000000000000005431511204443500304130ustar00rootroot00000000000000\page page_man_pipewire-pulse-modules_7 pipewire-pulse-modules PipeWire Pulseaudio modules # DESCRIPTION \include{doc} pulse-modules.inc # BUILT-IN MODULES $(PIPEWIRE_PULSE_MODULES) # AUTHORS The PipeWire Developers <$(PACKAGE_BUGREPORT)>; PipeWire is available from <$(PACKAGE_URL)> # SEE ALSO \ref page_man_pipewire-pulse_1 "pipewire-pulse(1)" pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/doc/dox/config/pipewire-pulse.conf.5.md000066400000000000000000000140501511204443500276650ustar00rootroot00000000000000\page page_man_pipewire-pulse_conf_5 pipewire-pulse.conf The PipeWire Pulseaudio server configuration file \tableofcontents # SYNOPSIS *$XDG_CONFIG_HOME/pipewire/pipewire-pulse.conf* *$(PIPEWIRE_CONFIG_DIR)/pipewire-pulse.conf* *$(PIPEWIRE_CONFDATADIR)/pipewire-pulse.conf* *$(PIPEWIRE_CONFDATADIR)/pipewire-pulse.conf.d/* *$(PIPEWIRE_CONFIG_DIR)/pipewire-pulse.conf.d/* *$XDG_CONFIG_HOME/pipewire/pipewire-pulse.conf.d/* # DESCRIPTION Configuration for PipeWire's PulseAudio-compatible daemon. The configuration file format and lookup logic is the same as for \ref page_man_pipewire_conf_5 "pipewire.conf(5)". Drop-in configuration files `pipewire-pulse.conf.d/*.conf` can be used, and are recommended. See \ref pipewire_conf__drop-in_configuration_files "pipewire.conf(5)". # CONFIGURATION FILE SECTIONS @IDX@ pipewire-pulse.conf \par stream.properties Dictionary. These properties configure the PipeWire Pulseaudio server properties. \par stream.rules Dictionary. These properties configure the PipeWire Pulseaudio server properties. \par pulse.properties Dictionary. These properties configure the PipeWire Pulseaudio server properties. \par pulse.cmd Array of dictionaries. A set of commands to be executed on startup. \par pulse.rules Array of dictionaries. A set of match rules and actions to apply to clients. See \ref page_module_protocol_pulse "libpipewire-module-protocol-pulse(7)" for the detailed description. In addition, the PipeWire context configuration sections may also be specified, see \ref page_man_pipewire_conf_5 "pipewire.conf(5)". # STREAM PROPERTIES @IDX@ pipewire-pulse.conf stream.properties The `stream.properties` section contains properties for streams created by the pipewire-pulse server. Available options are described in \ref client_conf__stream_properties "pipewire-client.conf(5) stream.properties". Some of these properties map to the PulseAudio `/etc/pulse/default.pa` config entries as follows: | PulseAudio | PipeWire | Notes | | ------------------------------ | --------------------- | -------------------- | | remixing-use-all-sink-channels | channelmix.upmix | | | remixing-produce-lfe | channelmix.lfe-cutoff | Set to > 0 to enable | | remixing-consume-lfe | channelmix.mix-lfe | | | lfe-crossover-freq | channelmix.lfe-cutoff | | ## Example ```css # ~/.config/pipewire/pipewire-pulse.conf.d/custom.conf stream.properties = { #node.latency = 1024/48000 #node.autoconnect = true #resample.disable = false #resample.quality = 4 #monitor.channel-volumes = false #channelmix.disable = false #channelmix.min-volume = 0.0 #channelmix.max-volume = 10.0 #channelmix.normalize = false #channelmix.mix-lfe = true #channelmix.upmix = true #channelmix.upmix-method = psd # none, simple #channelmix.lfe-cutoff = 150.0 #channelmix.fc-cutoff = 12000.0 #channelmix.rear-delay = 12.0 #channelmix.stereo-widen = 0.0 #channelmix.hilbert-taps = 0 #dither.noise = 0 #dither.method = none # rectangular, triangular, triangular-hf, wannamaker3, shaped5 #debug.wav-path = "" } ``` # STREAM RULES @IDX@ pipewire-pulse.conf stream.rules The `stream.rules` section works the same as \ref client_conf__stream_rules "pipewire-client.conf(5) stream.rules". # PULSEAUDIO PROPERTIES @IDX@ pipewire-pulse.conf pulse.properties For `pulse.properties` section, see \ref page_module_protocol_pulse "libpipewire-module-protocol-pulse(7)" for available options. # PULSEAUDIO RULES @IDX@ pipewire-pulse.conf pulse.rules For each client, a set of rules can be written in `pulse.rules` section to configure quirks of the client or to force some pulse specific stream configuration. The general look of this section is as follows and follows the layout of \ref pipewire_conf__match_rules "match rules, see pipewire(1)". See \ref page_module_protocol_pulse "libpipewire-module-protocol-pulse(7)" for available options. ## Example ```css # ~/.config/pipewire/pipewire-pulse.conf.d/custom.conf pulse.rules = [ { # skype does not want to use devices that don't have an S16 sample format. matches = [ { application.process.binary = "teams" } { application.process.binary = "teams-insiders" } { application.process.binary = "skypeforlinux" } ] actions = { quirks = [ force-s16-info ] } } { # speech dispatcher asks for too small latency and then underruns. matches = [ { application.name = "~speech-dispatcher*" } ] actions = { update-props = { pulse.min.req = 1024/48000 # 21ms pulse.min.quantum = 1024/48000 # 21ms } } } ] ``` # PULSEAUDIO COMMANDS @IDX@ pipewire-pulse.conf As part of the server startup procedure you can execute some additional commands with the `pulse.cmd` section in `pipewire-pulse.conf`. ```css # ~/.config/pipewire/pipewire-pulse.conf.d/custom.conf pulse.cmd = [ { cmd = "load-module" args = "module-always-sink" flags = [ ] } { cmd = "load-module" args = "module-switch-on-connect" } { cmd = "load-module" args = "module-gsettings" flags = [ "nofail" ] } ] ... ``` Additional commands can also be run via the \ref pipewire_conf__context_exec "context.exec section, see pipewire.conf(5)". Supported commands: @PAR@ pipewire-pulse.conf load-module Load the specified Pulseaudio module on startup, as if using **pactl(1)** to load the module. # PULSEAUDIO MODULES @IDX@ pipewire-pulse.conf For contents of section `pulse.modules`, see \ref page_man_pipewire-pulse-modules_7 "pipewire-pulse-modules(7)". # AUTHORS The PipeWire Developers <$(PACKAGE_BUGREPORT)>; PipeWire is available from <$(PACKAGE_URL)> # SEE ALSO \ref page_module_protocol_pulse "libpipewire-module-protocol-pulse(7)", \ref page_man_pipewire_conf_5 "pipewire.conf(5)", \ref page_man_pipewire-pulse_1 "pipewire-pulse(1)", \ref page_man_pipewire-pulse-modules_7 "pipewire-pulse-modules(7)" pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/doc/dox/config/pipewire.conf.5.md000066400000000000000000000536211511204443500265460ustar00rootroot00000000000000\page page_man_pipewire_conf_5 pipewire.conf The PipeWire server configuration file \tableofcontents # SYNOPSIS *$XDG_CONFIG_HOME/pipewire/pipewire.conf* *$(PIPEWIRE_CONFIG_DIR)/pipewire.conf* *$(PIPEWIRE_CONFDATADIR)/pipewire.conf* *$(PIPEWIRE_CONFDATADIR)/pipewire.conf.d/* *$(PIPEWIRE_CONFIG_DIR)/pipewire.conf.d/* *$XDG_CONFIG_HOME/pipewire/pipewire.conf.d/* # DESCRIPTION PipeWire is a service that facilitates sharing of multimedia content between devices and applications. On startup, the daemon reads a main configuration file to configure itself. It executes a series of commands listed in the config file. The config file is looked up in the order listed in the [SYNOPSIS](#synopsis). The environment variables `PIPEWIRE_CONFIG_DIR`, `PIPEWIRE_CONFIG_PREFIX` and `PIPEWIRE_CONFIG_NAME` can be used to specify an alternative config directory, subdirectory and file respectively. Other PipeWire configuration files generally follow the same lookup logic, replacing `pipewire.conf` with the name of the particular config file. # DROP-IN CONFIGURATION FILES @IDX@ pipewire.conf All `*.conf` files in the `pipewire.conf.d/` directories are loaded and merged into the configuration. Dictionary sections are merged, overriding properties if they already existed, and array sections are appended to. The drop-in files have same format as the main configuration file, but only contain the settings to be modified. As the `pipewire.conf` configuration file contains various parts that must be present for correct functioning, using drop-in files for configuration is recommended. ## Example A configuration file `~/.config/pipewire/pipewire.conf.d/custom.conf` to change the value of the `default.clock.min-quantum` setting in `pipewire.conf`: ``` # ~/.config/pipewire/pipewire.conf.d/custom.conf context.properties = { default.clock.min-quantum = 128 } ``` # CONFIGURATION FILE FORMAT @IDX@ pipewire.conf The configuration file is in "SPA" JSON format. The configuration file contains top-level keys, which are the sections. The value of a section is either a dictionary, `{ }`, or an array, `[ ]`. Section and dictionary item declarations have `KEY = VALUE` form, and are separated by whitespace. For example: ``` context.properties = { # top-level dictionary section key1 = value # a simple value key2 = { key1 = value1 key2 = value2 } # a dictionary with two entries key3 = [ value1 value2 ] # an array with two entries key4 = [ { k = v1 } { k = v2 } ] # an array of dictionaries } context.modules = [ # top-level array section value1 value2 ] ``` The configuration files can also be written in standard JSON syntax, but for easier manual editing, the relaxed "SPA" variant is allowed. In "SPA" JSON: - `:` to delimit keys and values can be substituted by `=` or a space. - \" around keys and string can be omitted as long as no special characters are used in the strings. - `,` to separate objects can be replaced with a whitespace character. - `#` can be used to start a comment until the line end # CONFIGURATION FILE SECTIONS @IDX@ pipewire.conf \par context.properties Dictionary. These properties configure the PipeWire instance. \par context.spa-libs Dictionary. Maps plugin features with globs to a spa library. \par context.modules Array of dictionaries. Each entry in the array is a dictionary with the *name* of the module to load, including optional *args* and *flags*. Most modules support being loaded multiple times. \par context.objects Array of dictionaries. Each entry in the array is a dictionary containing the *factory* to create an object from and optional extra arguments specific to that factory. \par context.exec \parblock Array of dictionaries. Each entry in the array is dictionary containing the *path* of a program to execute on startup and optional *args*. This array used to contain an entry to start the session manager but this mode of operation has since been demoted to development aid. Avoid starting a session manager in this way in production environment. \endparblock \par node.rules Array of dictionaries. Match rules for modifying node properties on the server. \par device.rules Array of dictionaries. Match rules for modifying device properties on the server. # CONTEXT PROPERTIES @IDX@ pipewire.conf context.properties Available PipeWire properties in `context.properties` and possible default values. @PAR@ pipewire.conf clock.power-of-two-quantum = true The quantum requests from the clients and the final graph quantum are rounded down to a power of two. A power of two quantum can be more efficient for many processing tasks. @PAR@ pipewire.conf context.data-loop.library.name.system The name of the shared library to use for the system functions for the data processing thread. This can typically be changed if the data thread is running on a realtime kernel such as EVL. @PAR@ pipewire.conf loop.rt-prio = -1 The priority of the data loops. The data loops are used to schedule the nodes in the graph. A value of -1 uses the default realtime priority from the module-rt. A value of 0 disables realtime scheduling for the data loops. @PAR@ pipewire.conf loop.class = [ data.rt .. ] An array of classes of the data loops. Normally nodes are assigned to a loop by name or by class. Nodes are by default assigned to the data.rt class so it is good to have a data loop of this class as well. @PAR@ pipewire.conf context.num-data-loops = 1 The number of data loops to create. By default 1 data-loop is created and all nodes are scheduled in this thread. A value of 0 disables the real-time data loops and schedules all nodes in the main thread. A value of -1 spawns as many data threads as there are cpu cores. @PAR@ pipewire.conf context.data-loops = [ ... ] This controls the data loops that will be created for the context. Is is an array of data loop specifications, one entry for each data loop to start: ``` # ~/.config/pipewire/pipewire.conf.d/custom.conf context.data-loops = [ { #library.name.system = support/libspa-support loop.rt-prio = -1 loop.class = [ data.rt .. ] thread.name = data-loop.0 thread.affinity = [ 0 1 ] } ... ] ``` A specific priority, classes and name can be given with loop.rt-prio, loop.class and thread.name respectively. It is also possible to pin the data loop to specific CPU cores with the thread.affinity property. @PAR@ pipewire.conf core.daemon = false Makes the PipeWire process, started with this config, a daemon process. This means that it will manage and schedule a graph for clients. You would also want to configure a core.name to give it a well known name. @PAR@ pipewire.conf core.name = pipewire-0 The name of the PipeWire context. This will also be the name of the PipeWire socket clients can connect to. @PAR@ pipewire.conf cpu.zero.denormals = false Configures the CPU to zero denormals automatically. This will be enabled for the data processing thread only, when enabled. @PAR@ pipewire.conf cpu.vm.name = null This will be set automatically when the context is created and will contain the name of the VM. It is typically used to write match rules to set extra properties. @PAR@ pipewire.conf default.clock.rate = 48000 The default clock rate determines the real time duration of the min/max/default quantums. You might want to change the quantums when you change the default clock rate to maintain the same duration for the quantums. @PAR@ pipewire.conf default.clock.allowed-rates = [ ] It is possible to specify up to 32 alternative sample rates. The graph sample rate will be switched when devices are idle. Note that this is not enabled by default for now because of various kernel and Bluetooth issues. Note that the min/max/default quantum values are scaled when the samplerate changes. @PAR@ pipewire.conf default.clock.min-quantum = 32 Default minimum quantum. @PAR@ pipewire.conf default.clock.max-quantum = 8192 Default maximum quantum. @PAR@ pipewire.conf default.clock.quantum = 1024 Default quantum used when no client specifies one. @PAR@ pipewire.conf default.clock.quantum-limit = 8192 Maximum quantum to reserve space for. This is the maximum buffer size used in the graph, regardless of the samplerate. @PAR@ pipewire.conf default.clock.quantum-floor = 4 Minimum quantum to reserve space for. This is the minimum buffer size used in the graph, regardless of the samplerate. @PAR@ pipewire.conf default.video.width Default video width @PAR@ pipewire.conf default.video.height Default video height @PAR@ pipewire.conf default.video.rate.num Default video rate numerator @PAR@ pipewire.conf default.video.rate.denom Default video rate denominator @PAR@ pipewire.conf library.name.system = support/libspa-support The name of the shared library to use for the system functions for the main thread. @PAR@ pipewire.conf link.max-buffers = 64 The maximum number of buffers to negotiate between nodes. Note that version < 3 clients can only support 16 buffers. More buffers is almost always worse than less, latency and memory wise. @PAR@ pipewire.conf log.level = 2 The default log level used by the process. @PAR@ pipewire.conf mem.allow-mlock = true Try to mlock the memory for the realtime processes. Locked memory will not be swapped out by the kernel and avoid hickups in the processing threads. @PAR@ pipewire.conf mem.warn-mlock = false Warn about failures to lock memory. @PAR@ pipewire.conf mem.mlock-all = false Try to mlock all current and future memory by the process. @PAR@ pipewire.conf rlimit.nofile = 4096 Try to set the max file descriptor number resource limit of the process. A value of -1 raises the limit to the system defined hard maximum value. The file resource limit is usually 1024 and should only be raised if the program does not use the select() system call. PipeWire does normally not use select(). @PAR@ pipewire.conf rlimit.*resource* = *value* Set resource limits. *resource* can be one of: as, core, cpu, data, fsize, locks, memlock, msgqueue, nice, nofile, nproc, rss, rtprio, rttime, sigpending or stack. See the documentation of setrlimit to get the meaning of these resources. A value of -1 will set the maximum allowed limit. @PAR@ pipewire.conf settings.check-quantum = false Check if the quantum in the settings metadata update is compatible with the configured limits. @PAR@ pipewire.conf settings.check-rate = false Check if the rate in the settings metadata update is compatible with the configured limits. @PAR@ pipewire.conf support.dbus = true Enable DBus support. This will enable DBus support in the various modules that require it. Disable this if you want to globally disable DBus support in the process. @PAR@ pipewire.conf vm.overrides = { default.clock.min-quantum = 1024 } Any property in the vm.overrides property object will override the property in the context.properties when PipeWire detects it is running in a VM. This is deprecated, use the context.properties.rules instead. @PAR@ pipewire.conf context.modules.allow-empty = false By default, a warning is logged when there are no context.modules loaded because this likely indicates there is a problem. Some applications might load the modules themselves and when they set this property to true, no warning will be logged. The context properties may also contain custom values. For example, the `context.modules` and `context.objects` sections can declare additional conditions that control whether a module or object is loaded depending on what properties are present. # SPA LIBRARIES @IDX@ pipewire.conf context.spa-libs SPA plugins are loaded based on their factory-name. This is a well known name that uniquely describes the features that the plugin should have. The `context.spa-libs` section provides a mapping between the factory-name and the plugin where the factory can be found. Factory names can contain a wildcard to group several related factories into one plugin. The plugin is loaded from the first matching factory-name. ## Example ``` # ~/.config/pipewire/pipewire.conf.d/custom.conf context.spa-libs = { audio.convert.* = audioconvert/libspa-audioconvert avb.* = avb/libspa-avb api.alsa.* = alsa/libspa-alsa api.v4l2.* = v4l2/libspa-v4l2 api.libcamera.* = libcamera/libspa-libcamera api.bluez5.* = bluez5/libspa-bluez5 api.vulkan.* = vulkan/libspa-vulkan api.jack.* = jack/libspa-jack support.* = support/libspa-support video.convert.* = videoconvert/libspa-videoconvert } ``` # MODULES @IDX@ pipewire.conf context.modules PipeWire modules to be loaded. See \ref page_man_libpipewire-modules_7 "libpipewire-modules(7)". ``` # ~/.config/pipewire/pipewire.conf.d/custom.conf context.modules = [ #{ name = MODULENAME # ( args = { KEY = VALUE ... } ) # ( flags = [ ( ifexists ) ( nofail ) ] ) # ( condition = [ { KEY = VALUE ... } ... ] ) #} # ] ``` \par name Name of module to be loaded \par args = { } Arguments passed to the module \par flags = [ ] Loading flags. `ifexists` to only load module if it exists, and `nofail` to not fail PipeWire startup if the module fails to load. \par condition = [ ] A \ref pipewire_conf__match_rules "match rule" `matches` condition. The module is loaded only if one of the expressions in the array matches to a context property. # CONTEXT OBJECTS @IDX@ pipewire.conf context.objects The `context.objects` section allows you to make some objects from factories (usually created by loading modules in `context.modules`). ``` # ~/.config/pipewire/pipewire.conf.d/custom.conf context.objects = [ #{ factory = # ( args = { = ... } ) # ( flags = [ ( nofail ) ] ) # ( condition = [ { = ... } ... ] ) #} ] ``` This section can be used to make nodes or links between nodes. \par factory Name of the factory to create the object. \par args = { } Arguments passed to the factory. \par flags = [ ] Flag `nofail` to not fail PipeWire startup if the object fails to load. \par condition = [ ] A \ref pipewire_conf__match_rules "match rule" `matches` condition. The object is created only if one of the expressions in the array matches to a context property. ## Example This fragment creates a new dummy driver node, but only if `core.daemon` property is true: ``` # ~/.config/pipewire/pipewire.conf.d/custom.conf context.objects = [ { factory = spa-node-factory args = { factory.name = support.node.driver node.name = Dummy-Driver node.group = pipewire.dummy priority.driver = 20000 }, condition = [ { core.daemon = true } ] } ] ``` # COMMAND EXECUTION @IDX@ pipewire.conf context.exec The `context.exec` section can be used to start arbitrary commands as part of the initialization of the PipeWire program. ``` # ~/.config/pipewire/pipewire.conf.d/custom.conf context.exec = [ #{ path = # ( args = "" | [ ... ] ) # ( condition = [ { = ... } ... ] ) #} ] ``` \par path Program to execute. \par args Arguments to the program. \par condition A \ref pipewire_conf__match_rules "match rule" `matches` condition. The object is created only if one of the expressions in the array matches to a context property. ## Example The following fragment executes a pactl command with the given arguments: ``` # ~/.config/pipewire/pipewire.conf.d/custom.conf context.exec = [ { path = "pactl" args = "load-module module-always-sink" } ] ``` # MATCH RULES @IDX@ pipewire.conf Some configuration file sections contain match rules. This makes it possible to perform some action when an object (usually a node or stream) is created/updated that matches certain properties. The general rules object follows the following pattern: ```css = [ { matches = [ # any of the following sets of properties are matched, if # any matches, the actions are executed { # = # all keys must match the value. ! negates. ~ starts regex. #application.process.binary = "teams" #application.name = "~speech-dispatcher.*" # Absence of property can be tested by comparing to null #pipewire.sec.flatpak = null } { # more matches here... } ... ] actions = { = ... } } ] ``` Match rules are an array of rules. A rule is always a JSON object with two keys: matches and actions. The matches key is used to define the conditions that need to be met for the rule to be evaluated as true, and the actions key is used to define the actions that are performed when the rule is evaluated as true. The matches key is always a JSON array of objects, where each object defines a condition that needs to be met. Each condition is a list of key-value pairs, where the key is the name of the property that is being matched, and the value is the value that the property needs to have. Within a condition, all the key-value pairs are combined with a logical AND, and all the conditions in the matches array are combined with a logical OR. The actions key is always a JSON object, where each key-value pair defines an action that is performed when the rule is evaluated as true. The action name is specific to the rule and is defined by the rule’s documentation, but most frequently you will see the update-props action, which is used to update the properties of the matched object. In the matches array, it is also possible to use regular expressions to match property values. For example, to match all nodes with a name that starts with my_, you can use the following condition: ```css matches = [ { node.name = "~my_.*" } ] ``` The ~ character signifies that the value is a regular expression. The exact syntax of the regular expressions is the POSIX extended regex syntax, as described in the regex (7) man page. In addition to regular expressions, you may also use the ! character to negate a condition. For example, to match all nodes with a name that does not start with my_, you can use the following condition: ```css matches = [ { node.name = "!~my_.*" } ] ``` The ! character can be used with or without a regular expression. For example, to match all nodes with a name that is not equal to my_node, you can use the following condition: ```css matches = [ { node.name = "!my_node" } ] ``` The null value has a special meaning; it checks if the property is not available (or unset). To check if a property is not set: ```css matches = [ { node.name = null } ] ``` To check the existence of a property, one can use the !null condition, for example: ```css matches = [ { node.name = "!null" } { node.name = !null # simplified syntax } ] ``` To handle the "null" string, one needs to escape the string. For example, to check if a property has the string value "null", use: ```css matches = [ { node.name = "null" } ] ``` To handle anything but the "null" string, use: ```css matches = [ { node.name = "!\"null\"" } { node.name = !"null" # simplified syntax } ] ``` # CONTEXT PROPERTIES RULES @IDX@ pipewire.conf context.properties.rules `context.properties.rules` can be used to dynamically update the properties based on other properties. A typical case is to update custom settings when running inside a VM. The `cpu.vm.name` is automatically set when running in a VM with the name of the VM. A match rule can be written to set custom properties like this: ```css # ~/.config/pipewire/pipewire.conf.d/custom.conf context.properties.rules = [ { matches = [ { cpu.vm.name = !null } ] actions = { update-props = { # These overrides are only applied when running in a vm. default.clock.min-quantum = 1024 } } } } ``` # NODE RULES @IDX@ pipewire.conf node.rules The node.rules are evaluated every time the properties on a node are set or updated. This can be used on the server side to override client set properties on arbitrary nodes. `node.rules` provides an `update-props` action that takes an object with properties that are updated on the node object. Add a `node.rules` section in the config file like this: ```css # ~/.config/pipewire/pipewire.conf.d/custom.conf node.rules = [ { matches = [ { # all keys must match the value. ! negates. ~ starts regex. client.name = "jack_simple_client" } ] actions = { update-props = { node.force-quantum = 512 } } } ] ``` Will set the `node.force-quantum` property of `jack_simple_client` to 512. # DEVICE RULES @IDX@ pipewire.conf device.rules The device.rules are evaluated every time the properties on a device are set or updated. This can be used on the server side to override client set properties on arbitrary devices. `device.rules` provides an `update-props` action that takes an object with properties that are updated on the device object. Add a `device.rules` section in the config file like this: ```css # ~/.config/pipewire/pipewire.conf.d/custom.conf device.rules = [ { matches = [ { # all keys must match the value. ! negates. ~ starts regex. device.name = ""v4l2_device.pci-0000_00_14.0-usb-0_1.2_1.0 } ] actions = { update-props = { device.description = "My Webcam" } } } ] ``` Will set the `device.description` property of the device with the given `device.name` to "My Webcam". # AUTHORS The PipeWire Developers <$(PACKAGE_BUGREPORT)>; PipeWire is available from <$(PACKAGE_URL)> # SEE ALSO \ref page_man_pipewire_1 "pipewire(1)", \ref page_man_pw-mon_1 "pw-mon(1)", \ref page_man_libpipewire-modules_7 "libpipewire-modules(7)" \ref page_man_pipewire-pulse_conf_5 "pipewire-pulse.conf(5)" \ref page_man_pipewire-client_conf_5 "pipewire-client.conf(5)" pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/doc/dox/config/xref.md000066400000000000000000000013631511204443500245730ustar00rootroot00000000000000\page page_config_xref Index \ref page_man_pipewire_conf_5 "pipewire.conf" @SECREF@ pipewire.conf \ref page_man_pipewire-pulse_conf_5 "pipewire-pulse.conf" @SECREF@ pipewire-pulse.conf \ref page_man_pipewire-client_conf_5 "client.conf" @SECREF@ client.conf \ref page_man_pipewire-jack_conf_5 "jack.conf" @SECREF@ jack.conf **Runtime settings** @SECREF@ pipewire-settings **Environment variables** @SECREF@ pipewire-env client-env jack-env pulse-env **Object properties** @SECREF@ props **Monitor properties** @SECREF@ monitor-prop **Device properties** @SECREF@ device-prop **Node properties** @SECREF@ node-prop **Port properties** @SECREF@ port-prop **Client properties** @SECREF@ client-prop \see pw_keys in API documentation. pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/doc/dox/index.dox000066400000000000000000000040301511204443500236550ustar00rootroot00000000000000/** \mainpage PipeWire PipeWire is low-level multimedia framework that provides: - Graph based processing. - Support for out-of-process processing graphs with minimal overhead. - Flexible and extensible media format negotiation and buffer allocation. - Hard real-time capable plugins. - Very low-latency for both audio and video processing. See \ref page_overview for an overview of PipeWire and \ref page_design for the design principles guiding PipeWire. # Documentation - \ref page_config - \ref page_programs - \ref page_modules - \ref page_pulse_modules See our [Wiki](https://gitlab.freedesktop.org/pipewire/pipewire/-/wikis/home) for more information on how to configure and use PipeWire. # Components PipeWire ships with the following components: - A \ref page_daemon that implements the IPC and graph processing. - An example \ref page_session_manager that manages objects in the \ref page_daemon. - A set of \ref page_programs to introspect and use the \ref page_daemon. - A \ref page_library to develop PipeWire applications and plugins (\ref page_tutorial "tutorial"). - The \ref page_spa used by both the \ref page_daemon and in the \ref page_library. # API Documentation See \ref page_api. # Resources - [PipeWire and AGL](https://wiki.automotivelinux.org/_media/pipewire_agl_20181206.pdf) - [LAC 2020 Paper](https://lac2020.sciencesconf.org//data/proceedings.pdf) and [Video](https://tube.aquilenet.fr/w/uy8PJyMnBrpBFNEZ9D48Uu) - [PipeWire Under The Hood](https://venam.nixers.net/blog/unix/2021/06/23/pipewire-under-the-hood.html) - [PipeWire: The Linux audio/video bus (LWN)](https://lwn.net/Articles/847412) - [PipeWire Wikipedia](https://en.wikipedia.org/wiki/PipeWire) - [Bluetooth, PipeWire and Whatsapp calls](https://gjhenrique.com/pipewire.html) - [Intoduction to PipeWire](https://bootlin.com/blog/an-introduction-to-pipewire/) - [A custom PipeWire node](https://bootlin.com/blog/a-custom-pipewire-node/) - [FOSDEM 2025 talk and slides](https://fosdem.org/2025/schedule/event/fosdem-2025-4749-pipewire-state-of-the-union/) */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/doc/dox/internals/000077500000000000000000000000001511204443500240345ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/doc/dox/internals/access.dox000066400000000000000000000117131511204443500260140ustar00rootroot00000000000000/** \page page_access Access Control This document explains how access control is designed and implemented. PipeWire implements per client permissions on the objects in the graph. Permissions include `R` (read), `W` (write), `X` (execute) and `M` (metadata). - `R`: An object with permission `R` is visible to the client. The client will receive registry events for the object and can interact with it. - `W`: An object with permission `W` can be modified. This is usually done through a method that modifies the state of the object. The `W` permission usually implies the `X` permission. - `X`: An object with permission `X` allows invoking methods on the object. Some of those methods will only query state, others will modify the object. As said above, modifying the object through one of these methods requires the `W` permission. - `M`: An object with `M` permission can be used as the subject in metadata. Clients with all permissions set are referred to as "ALL" in the documentation. # Use Cases ## New Clients Need Their Permissions Configured A new client is not allowed to communicate with the PipeWire daemon until it has been configured with permissions. ## Flatpaks Can't Modify Other Stream/Device Volumes An application running as Flatpak should not be able to modify the state of certain objects. Permissions of the relevant PipeWire objects should not have the `W` permission to avoid this. ## Flatpaks Can't Move Other Streams To Different Devices Streams are moved to another device by setting the `target.node` metadata on the node ID. By not setting the `M` bit on the other objects, this can be avoided. ## Application Should Be Restricted In What They Can See In general, applications should only be able to see the objects that they are allowed to see. For example, a web browser that was given access to a camera should not be able to see (and thus receive input data from) audio devices. ## "Manager" Applications Require Full Access Some applications require full access to the PipeWire graph, including moving streams between nodes (by setting metadata) and modifying properties (eg. volume). These applications must work even when running as Flatpak. # Design ## The PipeWire Daemon Immediately after a new client connects to the PipeWire daemon and updates its properties, the client will be registered and made visible to other clients. The PipeWire core will emit a `check_access` event in the \ref pw_context_events context for the the new client. The implementer of this event is responsible for assigning permissions to the client. Clients with permission `R` on the core object can continue communicating with the daemon. Clients without permission `R` on the core are suspended and are not able to send more messages. A suspended client can only resume processing after some other client sets the core permissions to `R`. This other client is usually a session manager, see e.g. \ref page_session_manager. ## The PipeWire Access Module The \ref page_module_access hooks into the `check_access` event that is emitted when a new client is registered. The module checks the permissions of the client and stores those in the \ref PW_KEY_ACCESS property on the client object. If this property is already set, the access module does nothing. If the property is not set it will go through a set of checks to determine the permissions for a client. See the \ref page_module_access documentation for details. Depending on the resolution, it grants permissions to the client as follows: - `"unrestricted"`: ALL permissions are set on the core object and the client will be able to resume. - any other value: No permissions are set on the core object and the client will be suspended. As detailed above, the client may be suspended. In that case the session manager or another client is required to configure permissions on the object for it to resume. ## The Session Manager The session manager listens for new clients to appear. It will use the \ref PW_KEY_ACCESS property to determine what to do. For clients that are not unrestricted, the session manager needs to set permissions on the client for the various PipeWire objects in the graph that it is allowed to interact with. To resume a client, the session manager needs to set permission `R` on the core object for the client. Permissions of objects for a client can be changed at any time by the session manager. Removing the client core permission `R` will suspend the client. The session manager needs to do additional checks to determine if the manager permissions can be given to the particular client and then configure ALL permissions on the client. Possible checks include permission store checks or ask the user if the application is allowed full access. Manager applications (ie. applications that need to modify the graph) will set the \ref PW_KEY_MEDIA_CATEGORY property in the client object to "Manager". For details on the pipewire-media-session implementation of access control, see \ref page_media_session. */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/doc/dox/internals/audio.dox000066400000000000000000000101211511204443500256440ustar00rootroot00000000000000/** \page page_audio Audio This document explains how Audio is implemented. # Use Cases ## Audio Devices Are Made Available As Processing Nodes/Ports Applications need to be able to see a port for each stream of an audio device. ## Audio Devices Can Be Plugged and Unplugged When devices are plugged and unplugged the associated nodes/ports need to be created and removed. ## Audio Port In Canonical Format It must be possible to make individual audio channels available as a single mono stream with a fixed format and samplerate. This makes it possible to link any of the audio ports together without doing conversions. ## Applications Can Connect To Audio Devices Applications can create ports that can connect to the audio ports so that data can be provided to or consumed from them. It should be possible to automatically connect an application to a sink/source when it requests this. ## Default Audio Sink and Sources It should be possible to mark a source or sink as the default source and sink so that applications are routed to them by default. It should be possible to change the default audio sink/source. ## Application Should Be Able To Move Between Sinks/Sources It should be possible to move an application from one device to another dynamically. ## Exclusive Access Application should be able to connect to a device in exclusive mode. This allows the application to negotiate a specific format with the device such as a compressed format. Exclusive access means that only one application can access the device because mixing is in general not possible when negotiating compressed formats. # Design ## SPA Audio devices are implemented with an \ref spa_device "SPA Device" object. This object is then responsible for controlling the \ref spa_node "SPA Nodes" that provide the audio ports to interface with the device. The nodes operate on the native audio formats supported by the device. This includes the sample rate as well as the number of channels and the audio format. ## Audio Adapter An SPA Node called the "adapter" is usually used with the SPA device node as the internal node. The function of the adapter is to convert the device native format to the required external format. This can include format or samplerate conversion but also channel remixing/remapping. The audio adapter is also responsible for exposing the audio channels as separate mono ports. This is called the DSP setup. The audio adapter can also be configured in passthrough mode when it will not do any conversions but simply pass through the port information of the internal node. This can be used to implement exclusive access. Setup of the different configurations of the adapter can be done with the PortConfig parameter. ## The Session Manager The session manager is responsible for creating nodes and ports for the various audio devices. It will need to wrap them into an audio adapter so that the specific configuration of the node can be decided by the policy mode. The session manager should create name and description for the devices and nodes. The session manager is responsible for assigning priorities to the nodes. At least \ref PW_KEY_PRIORITY_SESSION and \ref PW_KEY_PRIORITY_DRIVER need to be set. The session manager might need to work with other services to gain exclusive access to the device (eg. DBus). # Implementation ## PipeWire Media Session (alsa-monitor) PipeWire media session uses the \ref SPA_NAME_API_ALSA_ENUM_UDEV plugin for enumerating the ALSA devices. For each device it does: - Try to acquire the DBus device reservation object to gain exclusive access to the device. - Create an SPA device instance for the device and monitor this device instance. - For each node created by the device, create an adapter with an ALSA PCM node in the context of the PipeWire daemon. The session manager will also create suitable names and descriptions for the devices and nodes that it creates as well as assign session and driver priorities. The session manager has the option to add extra properties on the devices and nodes that it creates to control their behavior. This is implemented with match rules. */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/doc/dox/internals/daemon.dox000066400000000000000000000152701511204443500260200ustar00rootroot00000000000000/** \page page_daemon PipeWire Daemon The PipeWire daemon is the central process that manages data exchange between devices and clients. Typically general, users run one PipeWire daemon that listens for incoming connections and manages devices. Clients (including the \ref page_session_manager) are separate processes that talk to the daemon using the PipeWire socket (default: `$XDG_RUNTIME_DIR/pipewire-0`). This approach provides address-space separation between the privileged daemon and non-privileged clients. \dot digraph pw { compound=true; node [shape="box"]; subgraph cluster_pw { rankdir="TB"; label="PipeWire daemon"; style="dashed"; subgraph cluster_prot_native { label="pipewire-module-protocol-native"; style="solid"; socket [label="$XDG_RUNTIME_DIR/pipewire-0"]; mod_impl [label="module implementation"]; socket -> mod_impl; } core [label="PipeWire Core"]; alsa [label="PipeWire ALSA support"]; mod_impl -> core; core -> alsa; } kernel client1 [ label="Media Player" ]; client2 [ label="Audio Software" ]; sm [ label="Session Manager", style="dotted" ]; client1 -> socket; client2 -> socket; sm -> socket; alsa -> kernel; } \enddot As shown above, the protocol is handled by the \ref page_module_protocol_native. From PipeWire's point-of-view this module is just another module. # Configuration Files On startup, the daemon reads a configuration file to configure itself. It executes a series of commands listed in the config file. The lookup order for configuration files are: - `$XDG_CONFIG_HOME/pipewire/pipewire.conf` (usually `$HOME/.config/pipewire/pipewire.conf`) - `$sysconfdir/pipewire/pipewire.conf` (usually `/etc/pipewire/pipewire.conf`) - `$datadir/pipewire/pipewire.conf` (usually `/usr/share/pipewire/pipewire.conf`) The first configuration file found is loaded as the base configuration. Next, configuration sections (from files ending with a .conf extension) are collected in the directories in this order: - `$datadir/pipewire/pipewire.conf.d/` (usually `/usr/share/pipewire/pipewire.conf.d/`) - `$sysconfdir/pipewire/pipewire.conf.d/` (usually `/etc/pipewire/pipewire.conf.d/`) - `$XDG_CONFIG_HOME/pipewire/pipewire.conf.d/` (usually `$HOME/.config/pipewire/pipewire.conf.d/`) They are applied to the global configuration file. Properties are overwritten and array elements are appended. This makes it possible to make small custom customizations or additions to the main configuration file. The environment variables `PIPEWIRE_CONFIG_DIR`, `PIPEWIRE_CONFIG_PREFIX`, and `PIPEWIRE_CONFIG_NAME`. Can be used to specify an alternative configuration directory, subdirectory, and filename respectively. ## Configuration File Format PipeWire's configuration file format is JSON. In addition to true JSON PipeWire also understands a more compact JSON representation. Where `"` can be omitted around strings, no trailing commas are required and `:` or `=` can be used to separate object keys from their values. Also, `#` can be used to start a comment until the end of the line. The configuration file format is grouped into sections. A section is either a dictionary (`{}`) or an array (`[]`). Dictionary and array entries are separated by whitespace and may be simple value assignment, an array or a dictionary. For example: ``` # A dictionary section context.properties = { # Keys often have a dot notation core.daemon = true } # An array section containing three dictionary objects context.modules = [ # a dictionary object with one key assigned to a string { name = libpipewire-module-protocol-native } { name = libpipewire-module-profiler } # a dictionary object with two keys, one assigned to a string # the other one to an array of strings { name = libpipewire-module-portal flags = [ ifexists nofail ] } ] ``` Allowed configuration file sections are: - **context.properties** (dictionary): These properties configure the pipewire instance. - **context.spa-libs** (dictionary): Maps plugin features with globs to a spa library. - **context.modules** (array): Each entry in the array is a dictionary with the name of the module to load, including optional args and flags. Most modules support being loaded multiple times. - **context.objects** (array): Each entry in the array is a dictionary con‐ taining the factory to create an object from and optional extra argu‐ ments specific to that factory. - **context.exec** (array): Each entry in the array is dictionary containing the path of a program to execute on startup and optional args. This ar‐ ray usually contains an entry to start the session manager. # Logging The `PIPEWIRE_DEBUG` environment variable can be used to enable more debugging. This variable supports the following format: - `PIPEWIRE_DEBUG=[][,:][,:,...]` where the globs are shell globs to match on log topics and the levels are the respective log level to set for that topic. Globs are applied in order and a matching glob overrides an earlier glob for that category. A level without a glob prefix will set the global log level and is a more performant version of `*:`. For example, `PIPEWIRE_DEBUG=E,mod.*:D,mod.foo:X` enables global error messages, debugging on all modules but no messages on the foo module. - `` specifies the log level: + `X` or `0`: No logging is enabled. + `E` or `1`: Error logging is enabled. + `W` or `2`: Warnings are enabled. + `I` or `3`: Informational messages are enabled. + `D` or `4`: Debug messages are enabled. + `T` or `5`: Trace messages are enabled. These messages can be logged from the realtime threads. PipeWire uses a `category.topic` naming scheme, with the following categories: - `pw.*`: PipeWire internal topics. - `mod.*`: Module topics, for example `mod.foo` would usually refer to the `foo` module. - `ms.*`: Media session topics. - `ms.mod.*`: Media session modules, for example `ms.foo` would usually refer to the `media-session-foo` module. - `conn.*`: Connection specific topics such as printing raw messages sent over a communication socket. These are in a separate namespace as they are usually vastly more verbose than the normal debugging topics. This namespace must be explicitly enabled with a `conn.` glob. The behavior of the logging can be further controlled with the following environment variables: - `PIPEWIRE_LOG_SYSTEMD=false`: Disable logging to the systemd journal. - `PIPEWIRE_LOG=`: Redirect the log to the given filename. - `PIPEWIRE_LOG_LINE=false`: Don't log filename, function, and source code line. - `PIPEWIRE_LOG_COLOR=true/false/force`: Enable/disable color logging, and optionally force colors even when logging to a file. */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/doc/dox/internals/design.dox000066400000000000000000000053231511204443500260240ustar00rootroot00000000000000/** \page page_design Design A short overview of PipeWire's design. PipeWire is a media server that can run graphs of multimedia nodes. Nodes can run inside the server process or in separate processes, communicating with the server. PipeWire was designed to: - Be efficient for raw video using fd passing and audio with shared ringbuffers. - Be able to provide/consume/process media from any process. - Provide policy to restrict access to devices and streams. - Be easily extensible. Although an initial goal, the design is not limited to raw video only and should be able to handle compressed video and other media as well. PipeWire uses the \ref page_spa "SPA plugin API" for the nodes in the graph. SPA is designed for low-latency and efficient processing of any multimedia format. SPA also provides a number of helper utilities that are not available in the standard C library. Some of the application we intend to build: - v4l2 device provider: Provide controlled access to v4l2 devices and share one device between multiple processes. - gnome-shell video provider: GNOME Shell provides a node that gives the contents of the frame buffer for screen sharing or screen recording. - Audio server: Mix and playback multiple audio streams. The design is more like CRAS (Chromium audio server) than PulseAudio and with the added benefit that processing can be arranged in a graph. - Professional audio graph processing like JACK. - Media playback backend. # Protocol The native protocol and object model is similar to [Wayland](https://wayland.freedesktop.org) but with custom serialization/deserialization of messages. This is because the data structures in the messages are more complicated and not easily expressible in XML. See \ref page_module_protocol_native for details. See also \ref page_native_protocol for the documentation of the protocol messages. # Extensibility The functionality of the server is implemented and extended with modules and extensions. Modules are server side bits of logic that hook into various places to provide extra features. This mostly means controlling the processing graph in some way. See \ref page_modules for a list of current modules. Extensions are the client side version of the modules. Most extensions provide both a client side and server side init function. New interfaces or new object implementation can easily be added with modules/extensions. Some of the extensions that can be written: - Protocol extensions: A client/server side API (.h) together with protocol extensions and server/client side logic to implement a new object or interface. - A module to check security of method calls. - A module to automatically create, link or relink nodes. - A module to suspend idle nodes. */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/doc/dox/internals/dma-buf.dox000066400000000000000000000337541511204443500260770ustar00rootroot00000000000000/** \page page_dma_buf DMA-BUF Sharing PipeWire supports sharing Direct Memory Access buffers (DMA-BUFs) between clients via the \ref SPA_DATA_DmaBuf data type. However properly negotiating DMA-BUF support on both the producer and the consumer side require following a specific procedure. This page describes said procedure by using events and methods from the filter or stream API. Note: This article focuses mostly on DMA-BUF sharing from arbitrary devices, like discrete GPUs. For using DMA-BUFs created by v4l2 please refer to the corresponding paragraph. # Capability Negotiations The capability negotiation for DMA-BUFs is complicated by the fact that a usable and preferred optimal modifier for a given format can only be determined by the allocator. This allocator has to be invoked with the intersection of all supported modifiers for every client. As a result, the fixation of the modifier is delegated from PipeWire to the node responsible for allocating the buffers. ## pw_stream_connect The stream parameters should contain two \ref SPA_PARAM_EnumFormat objects for each format: one for DMA-BUFs, one for shared memory buffers as a fallback. Query the list of all supported modifiers from your graphics API of choice. Add a \ref SPA_FORMAT_VIDEO_modifier property to the first stream parameter with the flags `SPA_POD_PROP_FLAG_MANDATORY | SPA_POD_PROP_FLAG_DONT_FIXATE`. The value of the property should be set to a \ref SPA_CHOICE_Enum containing one `long` choice per supported modifier, plus `DRM_FORMAT_MOD_INVALID` if the graphics API supports modifier-less buffers. Note: When a producer is only supporting modifier-less buffers it can omit the \ref SPA_POD_PROP_FLAG_DONT_FIXATE (see param_changed hook, For producers). The second stream parameter should not contain any \ref SPA_FORMAT_VIDEO_modifier property. To prioritise DMA-BUFs place those \ref SPA_PARAM_EnumFormat containing modifiers first, when emitting them to PipeWire. ## param_changed Hook When the `param_changed` hook is called for a \ref SPA_PARAM_Format the client has to parse the `spa_pod` directly. Use `spa_pod_find_prop(param, NULL, SPA_FORMAT_VIDEO_modifier)` to check whether modifiers were negotiated. If they were negotiated, set the \ref SPA_PARAM_BUFFERS_dataType property to `1 << SPA_DATA_DmaBuf`. If they were not negotiated, fall back to shared memory by setting the \ref SPA_PARAM_BUFFERS_dataType property to `1 << SPA_DATA_MemFd`, `1 << SPA_DATA_MemPtr`, or both. While consumers only have to parse the resulting \ref SPA_PARAM_Format for any format related information, it's up to the producer to fixate onto a single format modifier pair. The producer is also responsible to check if all clients announce sufficient capabilities or fallback to shared memory buffers when possible. ### For Consumers Use \ref spa_format_video_raw_parse to get the format and modifier. ### For Producers Producers have to handle two cases when it comes to modifiers wrt. to the previous announced capabilities: Using only the modifier-less API, only the modifier-aware one, or supporting both. - modifier-less: In this case only the modifier `DRM_FORMAT_MOD_INVALID` was announced with the format. It is sufficient to check if the \ref SPA_PARAM_Format contains the modifier property as described above. If that is the case, use DMA-BUFs for screen-sharing, else fall back to SHM, if possible. - modifier-aware: In this case a list with all supported modifiers will be returned in the format. (using `DRM_FORMAT_MOD_INVALID` as the token for the modifier-less API). On the `param_changed` event check if the modifier key is present and has the flag \ref SPA_POD_PROP_FLAG_DONT_FIXATE attached to it. In this case, extract all modifiers from the list and do a test allocation with your allocator to choose the preferred modifier. Fixate on that \ref EnumFormat by announcing a \ref SPA_PARAM_EnumFormat with only one modifier in the \ref SPA_CHOICE_Enum and without the \ref SPA_POD_PROP_FLAG_DONT_FIXATE flag, followed by the previous announced \ref EnumFormat. This will retrigger the `param_changed` event with an \ref SPA_PARAM_Format as described below. If the \ref SPA_PARAM_Format contains a modifier key, without the flag \ref SPA_POD_PROP_FLAG_DONT_FIXATE, it should only contain one value in the \ref SPA_CHOICE_Enum. In this case announce the \ref SPA_PARAM_Buffers accordingly to the selected format and modifier. It is important to query the plane count of the used format modifier pair and set `SPA_PARAM_BUFFERS_blocks` accordingly. You might also want to add the option of adding explicit sync support to the buffers, as explained below. Note: When test allocating a buffer, collect all possible modifiers, while omitting `DRM_FORMAT_MOD_INVALID` from the \ref SPA_FORMAT_VIDEO_modifier property and pass them all to the graphics API. If the allocation fails and the list of possible modifiers contains `DRM_FORMAT_MOD_INVALID`, fall back to allocating without an explicit modifier if the graphics API allows it. ## add_buffer Hook This is relevant for producers. Allocate a DMA-BUF only using the negotiated format and modifier. ## on_event Hook This is relevant for consumers. Check the type of the dequeued buffer. If its \ref SPA_DATA_MemFd or \ref SPA_DATA_MemPtr use the fallback SHM import mechanism. If it's \ref SPA_DATA_DmaBuf get the DMA-BUF FDs (the plane count is encoded in the `n_datas` variable of the `spa_buffer` struct) and import them with the graphics API. Note: that the n_datas might also contain extra fds for things like sync_timelime metadata, you need to take this into account when persing the planes. Note: Some graphics APIs have separated functions for the modifier-less case (`DRM_FORMAT_MOD_INVALID`) or are omitting the modifier, since it might be used for error handling. ## Example Programs - \ref video-src-fixate.c "": \snippet{doc} video-src-fixate.c title - \ref video-play-fixate.c "": \snippet{doc} video-play-fixate.c title # DMA-BUF Mapping Warning It's important to make sure all consumers of the PipeWire stream are prepared to deal with DMA-BUFs. Most DMA-BUFs cannot be treated like shared memory in general because of the following issues: - DMA-BUFs can use hardware-specific tiling and compression as described by modifiers. Thus, a `mmap(3)` on the DMA-BUF FD will not give a linear view of the buffer contents. - DMA-BUFs need to be properly synchronized with the asynchronous reads and writes of the hardware. A `mmap(3)` call is not enough to guarantee proper synchronization. (Maybe add link to linux syscall doc??) - Blindly accessing the DMA-BUFs via `mmap(3)` can be extremely slow if the buffer has been allocated on discrete hardware. Consumers are better off using a proper graphics API (such as EGL, Vulkan or VA-API) to process the DMA-BUFs. # Size of DMA-BUFs When importing a DMA-BUF with a proper graphics API the size of a single buffer plane is no relevant property since it will be derived by the driver from the other properties. Therefore consumers should ignore the field `maxsize` of a `spa_data` and the field `size` of a `spa_chunk` struct. Producers are allowed to set both to 0. In cases where mapping a single plane is required the size should be obtained locally via the filedescriptor. # SPA param video format helpers SPA offers helper functions to parse and build a spa_pod object to/from the spa_video_info_* struct. The flags \ref SPA_VIDEO_FLAG_MODIFIER and \ref SPA_VIDEO_FLAG_MODIFIER_FIXATION_REQUIRED are used to indicate modifier usage with the format. `SPA_VIDEO_FLAG_MODIFIER` declares the parsed/provided spa_video_info_* struct contains valid modifier information. For legacy reasons `spa_format_video_*_build` will announce any modifier != 0 even when this flag is unused. `SPA_VIDEO_FLAG_MODIFIER_FIXATION_REQUIRED` is exclusive to the parse helpers and declares that the parsed spa_pod contains modifier information which needs to be fixated as described above. The list of available modifiers has to be parsed manually from the spa_pod object. - \ref spa_video_info_raw, \ref spa_format_video_raw_parse, \ref spa_format_video_raw_build - \ref spa_video_info_dsp, \ref spa_format_video_dsp_parse, \ref spa_format_video_dsp_build # v4l2 Another use case for streaming via DMA-BUFs are exporting a camera feed from v4l2 as DMA-BUFs. Those are located in the main memory where it is possible to mmap them. This should be done as follows: Neither producer nor consumer should announce a modifier, but both should include `1 << SPA_DATA_DmaBuf` in the `SPA_PARAM_BUFFERS_dataType` property. It's the the responsibility of the producer while the `add_buffer` event to choose DMA-BUF as the used buffer type even though no modifier is present, if it can guarantee, that the used buffer is mmapable. Note: For now v4l2 uses planar buffers without modifiers. This is the reason for this special case. # Explicit sync In addition to DMABUF, a set of synchronization primitives (a SyncObjTimeline) and associated metadata can be negotiated on the buffers. The explicit sync step is performed *after* the Format has been negotiated. ## Query support for explicit sync in the driver. You might first want to check that the drm render you are using is capable of explicit sync by checking support for DRM_CAP_SYNCOBJ and DRM_CAP_SYNCOBJ_TIMELINE before attempting to negotiate explicit sync. ## Provide space in the buffer for explicit sync Explicit sync requires two extra fds in the buffers and an extra \ref SPA_META_SyncTimeline metadata structure. The metadata structure will only be allocated when both sides support explicit sync. We can use this to make a fallback \ref SPA_PARAM_Buffers so that we can support both explicit sync and a fallback to implicit sync. So, first announce support for \ref SPA_META_SyncTimeline by adding the \ref SPA_TYPE_OBJECT_ParamMeta object to the stream: ``` params[n_params++] = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta, SPA_PARAM_META_type, SPA_POD_Id(SPA_META_SyncTimeline), SPA_PARAM_META_size, SPA_POD_Int(sizeof(struct spa_meta_sync_timeline))); ``` Next make a \ref SPA_PARAM_Buffers that depends on the negotiation of the SyncTimelime metadata: ``` spa_pod_builder_push_object(&b, &f, SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers); spa_pod_builder_add(&b, SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(8, 2, MAX_BUFFERS), SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(3), SPA_PARAM_BUFFERS_size, SPA_POD_Int(size), SPA_PARAM_BUFFERS_stride, SPA_POD_Int(data->stride), SPA_PARAM_BUFFERS_dataType, SPA_POD_CHOICE_FLAGS_Int((1<stride), SPA_PARAM_BUFFERS_dataType, SPA_POD_CHOICE_FLAGS_Int((1<datas[buf->n_datas - 2].fd, &acquire_handle); drmSyncobjFDToHandle(drm_fd, buf->datas[buf->n_datas - 1].fd, &release_handle); ``` ## Use the SPA_META_SyncTimeline when processing buffers The \ref struct spa_meta_sync_timeline contains 2 fields: the acquire_point and release_point. Producers will start a render operation on the DMABUF of the buffer and place the acquire_point in the \ref struct spa_meta_sync_timeline. When the rendering is complete, the producer should signal the acquire_point on the acquire SyncObjTimeline. Producers will also add a release_point on the release SyncObjTimeline. They are only allowed to reuse the buffer when the release_point has been signaled. Consumers use the acquire_point to wait for rendering to complete before processing the buffer. This can be offloaded to the hardware when submitting the rendering operation or it can be done explicitly with drmSyncobjTimelineWait() on the acquire SyncObjTimeline handle and the acquire_point of the metadata. Consumers should then also signal the release_point on the release SyncObjTimeline when they complete processing the buffer. This can be done in the hardware as part of the render pipeline or explicitly with drmSyncobjTimelineSignal() on the release handle and the release_point of the metadata. */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/doc/dox/internals/driver.dox000066400000000000000000000163241511204443500260510ustar00rootroot00000000000000/** \page page_driver Driver architecture and workflow This document explains how drivers are structured and how they operate. This is useful to know both for debugging and for writing new drivers. (For details about how the graph does scheduling, which is tied to the driver, see \ref page_scheduling ). # Clocks A driver is a node that starts graph cycles. Typically, this is accomplished by using a timer that periodically invokes a callback, or by an interrupt. Drivers use the monotonic system clock as the reference for timestamping. Note that "monotonic system clock" does not refer to the \c MONOTONIC_RAW clock in Linux, but rather, to the regular monotonic clock. Drivers may actually be run by a custom internal clock instead of the monotonic system clock. One example would be a sound card DAC's clock. Another would be a network adapter with a built in PHC. Or, the driver may be using a system clock other than the monotonic system clock. The driver then needs to perform some sort of timestamp translation and drift compensation from that internal clock to the monotonic clock, since it still needs to generate monotonic clock timestamps for the beginning cycle. (More on that below.) # Updates and graph cycle start Every time a driver starts a graph cycle, it must update the contents of the \ref spa_io_clock instance that is assigned to them through the \ref spa_node_methods::set_io callback. The fields of the struct must be updated as follows: - \ref spa_io_clock::nsec : Must be set to the time (according to the monotonic system clock) when the cycle that the driver is about to trigger started. To minimize jitter, it is usually a good idea to increment this by a fixed amount except for when the driver starts and when discontinuities occur in its clock. - \ref spa_io_clock::rate : Set to a value that can translate samples to nanoseconds. - \ref spa_io_clock::position : Current cycle position, in samples. This is the ideal position of the graph cycle (this is explained in greater detail further below). It is incremented by the duration (in samples) at the beginning of each cycle. If a discontinuity is experienced by the driver that results in a discontinuity in the position of the old and the current cycle, consider setting the \ref SPA_IO_CLOCK_FLAG_DISCONT flag to inform other nodes about this. - \ref spa_io_clock::duration : Duration of this new cycle, in samples. - \ref spa_io_clock::rate_diff : A decimal value that is set to whatever correction factor the driver applied to for a drift between an internal driver clock and the monotonic system clock. A value above 1.0 means that the internal driver clock is faster than the monotonic system clock, and vice versa. Always set this to 1.0 if the driver is directly using the monotonic clock. - \ref spa_io_clock::next_nsec : Must be set to the time (according to the monotonic system clock) when the cycle that comes after the current one is to be started. In some cases, this may actually be in the past relative to nsec, for example, when some internal driver clock experienced a discontinuity. Consider setting the \ref SPA_IO_CLOCK_FLAG_DISCONT flag in such a case. Just like with nsec, to minimize jitter, it is usually a good idea to increment this by a fixed amount except for when the driver starts and when discontinuities occur in its clock. The driver node signals the start of the graph cycle by calling \ref spa_node_call_ready with the \ref SPA_STATUS_HAVE_DATA and \ref SPA_STATUS_NEED_DATA flags passed to that function call. That call must happen inside the thread that runs the data loop assigned to the driver node. As mentioned above, the \ref spa_io_clock::position field is the _ideal_ position of the graph cycle, in samples. This contrasts with \ref spa_io_clock::nsec, which is the moment in monotonic clock time when the cycle _actually_ happens. This is an important distinction when driver is run by a clock that is different to the monotonic clock. In that case, the \ref spa_io_clock::nsec timestamps are adjusted to match the pace of that different clock (explained in the section below). In such a case, \ref spa_io_clock::position still is incremented by the duration in samples. This is important, since nodes and modules may use this field as an offset within their own internal ring buffers or similar structures, using the position field as an offset within said data structures. This requires the position field to advance in a continuous way. By incrementing by the duration, this requirement is met. # Using clocks other than the monotonic clock As mentioned earlier, the driver may be run by an internal clock that is different to the monotonic clock. If that other clock can be directly used for scheduling graph cycle initiations, then it is sufficient to compute the offset between that clock and the monotonic clock (that is, offset = monotonic_clock_time - other_clock_time) at each cycle and use that offset to translate that other clock's time to the monotonic clock time. This is accomplished by adding that offset to the \ref spa_io_clock::nsec and \ref spa_io_clock::next_nsec fields. For example, when the driver uses the realtime system clock instead of the monotonic system clock, then that realtime clock can still be used with \c timerfd to schedule callback invocations within the data loop. Then, computing the (monotonic_clock_time - realtime_clock_time) offset is sufficient, as mentioned, to be able to translate clock's time to monotonic time for \ref spa_io_clock::nsec and \ref spa_io_clock::next_nsec (which require monotonic clock timestamps). If however that other clock cannot be used for scheduling graph cycle initiations directly (for example, because the API of that clock has no functionality to trigger callbacks), then, in addition to the aforementioned offset, the driver has to use the monotonic clock for triggering callbacks (usually via \c timerfd) and adjust the time when callbacks are invoked such that they match the pace of that other clock. As an example (clock speed difference exaggerated for sake of clarity), suppose the other clock is twice as fast as the monotonic clock. Then the monotonic clock timestamps have to be calculated in a manner that halves the durations between said timestamps, and the \ref spa_io_clock::rate_diff field is set to 2.0. The dummy node driver uses a DLL for this purpose. It is fed the difference between the expected position (in samples) and the actual position (derived from the current time of the driver's internal clock), passes the delta between these two quantities into the DLL, and the DLL computes a correction factor (2.0 in the above example) which is used for scaling durations between \c timerfd timeouts. This forms a control loop, since the correction factor causes the durations between the timeouts to be adjusted such that the difference between the expected position and the actual position reaches zero. Keep in mind the notes above about \ref spa_io_clock::position being the ideal position of the graph cycle, meaning that even in this case, the duration it is incremented by is _not_ scaled by the correction factor; the duration in samples remains unchanged. (Other popular control loop mechanisms that are suitable alternatives to the DLL are PID controllers and Kalman filters.) */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/doc/dox/internals/index.dox000066400000000000000000000007411511204443500256610ustar00rootroot00000000000000/** \page page_internals Internals # Internals - \subpage page_design - \subpage page_audio - \subpage page_access - \subpage page_portal - \subpage page_midi - \subpage page_objects_design - \subpage page_library - \subpage page_dma_buf - \subpage page_scheduling - \subpage page_driver - \subpage page_latency - \subpage page_tag - \subpage page_native_protocol # Components - \subpage page_daemon - \subpage page_session_manager # Backends - \subpage page_pulseaudio */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/doc/dox/internals/latency.dox000066400000000000000000000277121511204443500262200ustar00rootroot00000000000000/** \page page_latency Latency support This document explains how the latency in the PipeWire graph is implemented. # Use Cases ## A node port has a latency Applications need to be able to query the latency of a port. Linked Nodes need to be informed of the latency of a port. ## dynamically update port latencies It needs to be possible to dynamically update the latency of a port and this should inform all linked ports/nodes of the updated latency. ## Linked nodes add latency to the signal When two nodes/ports have a latency, the signal is delayed by the sum of the nodes latencies. ## Calculate the signal delay upstream and downstream A node might need to know how much a signal was delayed since it arrived in the node. A node might need to know how much the signal will be delayed before it exists the graph. ## Detect latency mismatch When a signal travels through 2 different parts of the graph with different latencies and then eventually join, there is a latency mismatch. It should be possible to detect this mismatch. # Concepts ## Port Latency The fundamental object for implementing latency reporting in PipeWire is the Latency object. It consists of a direction (input/output) and min/max latency values. The latency values can express a latency relative to the graph quantum, the samplerate or in nanoseconds. There is a mininum and maximum latency value in the Latency object. The direction of the latency object determines how the latency object propagates. - SPA_DIRECTION_OUTPUT Latency objects move from output ports downstream and contain the latency from all nodes upstream. An output latency received on an input port should instruct the node to update the output latency on its output ports related to this input port. This corresponds to the JackCaptureLatency. - SPA_DIRECTION_INPUT Latency objects move from input ports upstream and contain the latency from all nodes downstream. An input latency received on an output port should instruct the node to update the input latency on its input ports related to this output port. This corresponds to the JackPlaybackLatency. PipeWire will automatically propagate Latency objects from ports to all linked ports in the graph. Output Latency objects on output ports are propagated to linked input ports and input Latency objects on input ports are propagated to linked output ports. If a port has links with multiple other ports, the Latency objects are merged by taking the min of the min values and the max of the max values of all latency objects on the other ports. This way, Output Latency always describes the aggragated total upstream latency of signal up to the port and Input latency describes the aggregated downstream latency of the signal from the port. ## Node ProcessLatency This is a per node property and applies to the latency introduced by the node logic itself. This mostly works if (almost) all data processing ports (input/output) participate in the same data processing with the same latency, which is a common use case. ProcessLatency is mostly used to easily calculate Output and Input Latency on ports. We can simply add the ProcessLatency to all latency values in the ports Latency objects to obtain the corresponding Latency object for the other ports. # Latency updates Latency params on the ports can be updated as needed. This can happen because some upstream or downstream latency changed or when the ProcessLatency of a node changes. When the ProcessLatency changes, the procedure to notify of latency changes is: - Take output latency on input port, add new ProcessLatency, set new latency on output port. This propagates the new latency downstream. - Take input latency on output port, add new ProcessLatency, set new latency on input port. This propagates the new latency upstream. PipeWire will automatically aggregate latency from links and propagate the new latencies down and upstream. # Async nodes When a node has the node.async property set to true, it will be considered an async node and will be scheduled differently, see scheduling.dox. A link between a port of an async node and another port (async or not) is called an async link and will have the link.async=true property. An async link will add 1 quantum of latency between the nodes it links. A special exception is made for the output ports of the driver node, which do not add latency. The Latency param will be updated with 1 extra quantum when they travel over an async link. # Examples ## A source node with a given ProcessLatency When we have a source with a ProcessLatency, for example, of 1024 samples: ``` +----------+ + Latency: [{ "direction": "output", "min-rate": 1024, "max-rate": 1024 } ] | FL ---+ Latency: [{ "direction": "input", "min-rate": 0, "max-rate": 0 } ] | source + | FR ---+ Latency: [{ "direction": "output", "min-rate": 1024, "max-rate": 1024 } ] +----------+ + Latency: [{ "direction": "input", "min-rate": 0, "max-rate": 0 } ] ^ | ProcessLatency: [ { "quantum": 0, "rate": 1024, "ns": 0 } ] ``` Both output ports have an output latency of 1024 samples and no input latency. ## A sink node with a given ProcessLatency When we have a sink with a ProcessLatency, for example, of 512 samples: ``` Latency: [{ "direction": "output", "min-rate": 0, "max-rate": 0 } ] Latency: [{ "direction": "input", "min-rate": 512, "max-rate": 512 } ] ^ | +----------+ +---- FL | | sink | <- ProcessLatency: [ { "quantum": 0, "rate": 512, "ns": 0 } ] +---- FR | | +----------+ v Latency: [{ "direction": "output", "min-rate": 0, "max-rate": 0 } ] Latency: [{ "direction": "input", "min-rate": 512, "max-rate": 512 } ] ``` Both input ports have an input latency of 512 samples and no output latency. ## A source and sink node linked together With the source and sink from above, if we link the FL channels, the input latency from the input port of the sink is propagated to the output port of the source and the output latency of the output port is propagated to the input port of the sink: ``` Latency: [{ "direction": "output", "min-rate": 1024, "max-rate": 1024 } ] Latency: [{ "direction": "input", "min-rate": 512, "max-rate": 512 } ] ^ | Latency: [{ "direction": "output", "min-rate": 1024, "max-rate": 1024 } ] | Latency: [{ "direction": "input", "min-rate": 512, "max-rate": 512 } ] | | +----------+v v+--------+ | FL ------------ FL | | source + | sink | | FR --+ FR | +----------+ | +--------+ v Latency: [{ "direction": "output", "min-rate": 1024, "max-rate": 1024 } ] Latency: [{ "direction": "input", "min-rate": 0, "max-rate": 0 } ] ``` ## Insert a latency node If we place a node with a 256 sample latency in the above source-sink graph: ``` Latency: [{ "direction": "output", "min-rate": 1024, "max-rate": 1024 } ] Latency: [{ "direction": "input", "min-rate": 768, "max-rate": 768 } ] ^ | Latency: [{ "direction": "output", "min-rate": 1024, "max-rate": 1024 } ] | Latency: [{ "direction": "input", "min-rate": 768, "max-rate": 768 } ] | ^ | | Latency: [{ "direction": "output", "min-rate": 1280, "max-rate": 1280 } ] | | Latency: [{ "direction": "input", "min-rate": 512, "max-rate": 512 } ] | | ^ | | | +----------+v v+--------+v +-------+ | FL ------------ FL FL --------- FL | | source + | node | ^ | sink | | . | . | . | +----------+ +--------+ | +-------+ v Latency: [{ "direction": "output", "min-rate": 1280, "max-rate": 1280 } ] Latency: [{ "direction": "input", "min-rate": 512, "max-rate": 512 } ] ``` See how the output latency propagates and is increased going downstream and the input latency is increased and traveling upstream. ## Link a port to two port with different latencies When we introduce a sink2 with different input latency and link this to the node FL output port, it will aggregate the two input latencies by taking the min of min and max of max. ``` Latency: [{ "direction": "output", "min-rate": 1024, "max-rate": 1024 } ] Latency: [{ "direction": "input", "min-rate": 768, "max-rate": 2304 } ] ^ | Latency: [{ "direction": "output", "min-rate": 1024, "max-rate": 1024 } ] | Latency: [{ "direction": "input", "min-rate": 768, "max-rate": 2304 } ] | ^ | | Latency: [{ "direction": "output", "min-rate": 1280, "max-rate": 1280 } ] | | Latency: [{ "direction": "input", "min-rate": 512, "max-rate": 2048 } ] | | ^ | | | Latency: [{ "direction": "output", "min-rate": 1280, "max-rate": 1280 } ] | | | Latency: [{ "direction": "input", "min-rate": 512, "max-rate": 512 } ] | | | ^ | | | | +----------+v v+--------+v v +-------+ | FL ------------ FL FL --------- FL | | source + | node | \ | sink | | . | . \ . | +----------+ +--------+ \ +-------+ \ \ +-------+ +--- FL | ^ | sink2 | | . . | +-------+ v Latency: [{ "direction": "output", "min-rate": 1280, "max-rate": 1280 } ] Latency: [{ "direction": "input", "min-rate": 2048, "max-rate": 2048 } ] ``` The source node now knows that its output signal will be delayed between 768 amd 2304 samples depending on the path in the graph. We also see that node.FL has different min/max-rate input latencies. This information can be used to insert a delay node to align the latencies again. For example, if we delay the signal between node.FL and FL.sink with 1536 samples, the latencies will be aligned again. ## An async output stream and sink node linked together The sink has 1 quantum of Input latency. The stream has no output latency. When the Input latency travels over the async link 1 quantum of latency is added and the Input latency on the stream is now 2 quanta. Similar for the stream Output latency that receives an additional 1 quantum of latency when it arrives in the sink over the async link. ``` Latency: [{ "direction": "output", "min-quantum": 0, "max-quantum": 0 } ] Latency: [{ "direction": "input", "min-quantum": 2, "max-quantum": 2 } ] ^ | Latency: [{ "direction": "output", "min-quantum": 1, "max-quantum": 1 } ] | Latency: [{ "direction": "input", "min-quantum": 1, "max-quantum": 1 } ] | | +----------+v v+--------+ | async FL ------------ FL | | stream + | sink | | FR --+ FR | +----------+ | +--------+ v Latency: [{ "direction": "output", "min-quantum": 0, "max-quantum": 0 } ] Latency: [{ "direction": "input", "min-quantum": 0, "max-quantum": 0 } ] ``` */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/doc/dox/internals/library.dox000066400000000000000000000200611511204443500262130ustar00rootroot00000000000000/** \page page_library PipeWire Library There are two main components that make up the PipeWire library: 1. An implementation of a graph based media processing engine. 2. An asynchronous IPC mechanism to manipulate and introspect a graph in another process. There is usually a daemon that implements the global graph and clients that operate on this graph. The IPC mechanism in PipeWire is inspired by Wayland in that it follows the same design principles of objects and methods/events along with how this API is presented to the user. PipeWire has a plugin architecture that allows new features to be added (or removed) by the user. Plugins can hook into many aspects of PipeWire and change the behaviour or number of features dynamically. # Principles The PipeWire API is an object oriented asynchronous protocol. All requests and replies are method invocations on some object. Objects are identified with a unique ID. Each object implements an interface and requests result in invocations of methods on the interface. The protocol is message based. A message sent by a client to the server is called a method. A message from the server to the client is called an event. Unlike Wayland, these messages are not (yet) described in an external protocol file but implemented directly in a protocol plugin. Protocol plugins can be added to add new objects or even protocols when required. Messages are encoded with \ref page_spa_pod, which make it possible to encode complex objects with right types. Events from the server can be a reply to a method or can be emitted when the server state changes. Upon connecting to a server, it will broadcast its state. Clients should listen for these state changes and cache them. There is no need (or mechanism) to query the state of the server. The server also has a registry object that, when listening to, will broadcast the presence of global objects and any changes in their state. State about objects can be obtained by binding to them and listening for state changes. # Versioning All interfaces have a version number. The maximum supported version number of an interface is advertised in the registry global event. A client asks for a specific version of an interface when it binds to them. It is the task of the server to adapt to the version of the client. Interfaces increase their version number when new methods or events are added. Methods or events should never be removed or changed for simplicity. # Proxies and Resources When a client connects to a PipeWire daemon, a new `struct pw_proxy` object is created with ID 0. The `struct pw_core` interface is assigned to the proxy. On the server side there is an equivalent `struct pw_resource` with ID 0. Whenever the client sends a message on the proxy (by calling a method on the interface of the proxy) it will transparently result in a callback on the resource with the same ID. Likewise if the server sends a message (an event) on a resource, it will result in an event on the client proxy with the same ID. PipeWire will notify a client when a resource ID (and thus also proxy ID) becomes unused. The client is responsible for destroying the proxy when it no longer wants to use it. # Interfaces ## struct pw_loop An abstraction for a `poll(2)` loop. It is usually part of one of: - `struct pw_main_loop`: A helper that can run and stop a `pw_loop`. - `struct pw_thread_loop`: A helper that can run and stop a `pw_loop` in a different thread. It also has some helper functions for various thread related synchronization issues. - `struct pw_data_loop`: A helper that can run and stop a `pw_loop` in a real-time thread along with some useful helper functions. ## struct pw_context The main context for PipeWire resources. It keeps track of the mainloop, loaded modules, the processing graph and proxies to remote PipeWire instances. An application has to select an implementation of a `struct pw_loop` when creating a context. The context has methods to create the various objects you can use to build a server or client application. ## struct pw_core A proxy to a remote PipeWire instance. This is used to send messages to a remote PipeWire daemon and to receive events from it. A core proxy can be used to receive errors from the remote daemon or to perform a roundtrip message to flush out pending requests. Other core methods and events are used internally for the object life cycle management. ## struct pw_registry A proxy to a PipeWire registry object. It emits events about the available objects on the server and can be used to bind to those objects in order to call methods or receive events from them. ## struct pw_module A proxy to a loadable module. Modules implement functionality such as provide new objects or policy. ## struct pw_factory A proxy to an object that can create other objects. ## struct pw_device A proxy to a device object. Device objects model a physical hardware or software device in the system and can create other objects such as nodes or other devices. ## struct pw_node A Proxy to a processing node in the graph. Nodes can have input and output ports and the ports can be linked together to form a graph. ## struct pw_port A Proxy to an input or output port of a node. They can be linked together to form a processing graph. ## struct pw_link A proxy to a link between in output and input port. A link negotiates a format and buffers between ports. A port can be linked to many other ports and PipeWire will manage mixing and duplicating the buffers. # High Level Helper Objects Some high level objects are implemented to make it easier to interface with a PipeWire graph. ## struct pw_filter A `struct pw_filter` allows you implement a processing filter that can be added to a PipeWire graph. It is comparable to a JACK client. ## struct pw_stream A `struct pw_stream` makes it easy to implement a playback or capture client for the graph. It takes care of format conversion and buffer sizes. It is comparable to Core Audio AudioQueue or a PulseAudio stream. # Security With the default native protocol, clients connect to PipeWire using a named socket. This results in a client socket that is used to send messages. For sandboxed clients, it is possible to get the client socket via other ways, like using the portal. In that case, a portal will do the connection for the client and then hands the connection socket to the client. All objects in PipeWire have per client permission bits, currently READ, WRITE, EXECUTE and METADATA. A client can not see an object unless it has READ permissions. Similarly, a client can only execute methods on an object when the EXECUTE bit is set and to modify the state of an object, the client needs WRITE permissions. A client (the portal after it makes a connection) can drop permissions on an object. Once dropped, it can never reacquire the permission. Clients with WRITE/EXECUTE permissions on another client can add and remove permissions for the client at will. Clients with MODIFY permissions on another object can set or remove metadata on that object. Clients that need permissions assigned to them can be started in blocked mode and resume when permissions are assigned to them by a session manager or portal, for example. PipeWire uses memfd (`memfd_create(2)`) or DMA-BUF for sharing media and data between clients. Clients can thus not look at other clients data unless they can see the objects and connect to them. # Implementation PipeWire also exposes an API to implement the server side objects in a graph. # Error Reporting Functions return either NULL with errno set or a negative int error code when an error occurs. Error codes are used from the SPA plugin library on which PipeWire is built. Some functions might return asynchronously. The error code for such functions is positive and SPA_RESULT_IS_ASYNC() will return true. SPA_RESULT_ASYNC_SEQ() can be used to get the unique sequence number associated with the async operation. The object returning the async result code will have some way to signal the completion of the async operation (with, for example, a callback). The sequence number can be used to see which operation completed. */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/doc/dox/internals/midi.dox000066400000000000000000000077061511204443500255040ustar00rootroot00000000000000/** \page page_midi MIDI Support This document explains how MIDI is implemented. # Use Cases ## MIDI Devices Are Made Available As Processing Nodes/Ports Applications need to be able to see a port for each stream of a MIDI device. ## MIDI Devices Can Be Plugged and Unplugged When devices are plugged and unplugged the associated nodes/ports need to be created and removed. ## Applications Can Connect To MIDI Devices Applications can create ports that can connect to the MIDI ports so that data can be provided to or consumed from them. ## Some MIDI Devices Are Sinks Or Sources For MIDI Data It should be possible to create a MIDI sink or source that routes the MIDI events to specific MIDI ports. One example of such a sink would be in front of a software MIDI renderer. An example of a MIDI source would be after a virtual keyboard or as a mix from many MIDI input devices. ## Applications Should Auto-connect To MIDI Sinks Or Sources An application should be able to be connected to a MIDI sink when it wants to play MIDI data. An application should be able to connect to a MIDI source when it wants to capture MIDI data. # Design ## SPA MIDI devices/streams are implemented with an \ref spa_node with generic control input and output Ports. These ports have a media type of `"application/control"` and the data transported over these ports are of type \ref spa_pod_sequence with the \ref spa_pod_control type set to \ref SPA_CONTROL_Midi. This means that every MIDI event is timestamped with the sample offset against the current graph clock cycle to get sample accurate midi events that can be aligned with the corresponding sample data. Since the MIDI events are embedded in the generic control stream, they can be interleaved with other control message types, such as property updates or OSC messages. As of 1.4, SPA_CONTROL_UMP (Universal Midi Packet) is the prefered format for the MIDI 1.0 and 2.0 messages in the \ref spa_pod_sequence. Conversion to SPA_CONTROL_Midi is performed for legacy applications. ## The PipeWire Daemon Nothing special is implemented for MIDI. Negotiation of formats happens between `"application/control"` media types and buffers are negotiated in the same way as any generic format. ## The Session Manager The session manager needs to create the MIDI nodes/ports for the available devices. This can either be done as a single node with ports per device/stream or as separate nodes created by a MIDI device monitor. The session manager needs to be aware of the various MIDI sinks and sources in order to route MIDI streams to them from applications that want this. # Implementation ## Session manager (Wireplumber) The session manager uses the \ref SPA_NAME_API_ALSA_SEQ_BRIDGE plugin for the MIDI features. This creates a single SPA Node with ports per MIDI client/stream. The media session will check the permissions on `/dev/snd/seq` before attempting to create this node. It will also use inotify to wait until the sequencer device node is accessible. Currently, the session manager does not try to link control messages automatically. ## JACK JACK assumes all `"application/control"` ports are MIDI ports. The control messages are converted to the JACK event format by filtering out the \ref SPA_CONTROL_Midi, \ref SPA_CONTROL_OSC and \ref SPA_CONTROL_UMP types. On output ports, the JACK event stream is converted to control messages in a similar way. Normally, all MIDI and UMP messages are converted to MIDI1 jack events unless the JACK port was created with an explcit "32 bit raw UMP" format or with the JackPortIsMIDI2 flag, in which case the raw UMP is passed to the JACK application directly. For output ports, the JACK events are assumed to be MIDI1 and converted to UMP unless the port has the "32 bit raw UMP" format or the JackPortIsMIDI2 flag, in which case the UMP messages are simply passed on. There is a 1 to 1 mapping between the JACK events and control messages so there is no information loss or need for complicated conversions. */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/doc/dox/internals/objects.dox000066400000000000000000000316331511204443500262070ustar00rootroot00000000000000/** \page page_objects_design Objects Design This document is a design reference on the various objects that exist in the PipeWire media and session management graphs. Explaining what these objects are, how they are meant to be used, and how they relate to other kinds of objects and concepts that exist in subsystems or other libraries. # The Media Graph The media graph represents and enables the media flow inside the PipeWire daemon and between the daemon and its clients. It consists of nodes, ports and links. ``` +------------+ +------------+ | | | | | +--------+ Link +--------+ | | Node | Port |--------| Port | Node | | +--------+ +--------+ | | | | | +------------+ +------------+ ``` ## Node A **node** is a media processing element. It consumes and/or produces buffers that contain data, such as audio or video. A node may operate entirely inside the PipeWire daemon or it may be operating in a client process. In the second case, media is transferred to/from that client using the PipeWire protocol. In an analogy to GStreamer, a _node_ is similar (but not equal) to a GStreamer _element_. ## Port A **port** is attached on a **node** and provides an interface for input or output of media on the node. A node may have multiple ports. A port always has a direction, input or output: - Input: it allows media input into the node (in other terms, it is a _sink_) - Output: it outputs media out of the node (in other terms, it is a _source_) In an analogy to GStreamer, a _port_ is similar (but not equal) to a GStreamer _pad_. ## Link A **link** connects two ports of opposite direction, making media flow from the output port to the input port. # The Session Management Graph The session management graph is a virtual, higher level representation of the media flow. It is created entirely by the session manager and it can affect the routing on the media graph only through the session manager's actions. The session management graph is useful to abstract the complexity of the actual media flow both for the target user and for the policy management codebase. ``` +---------------------+ +----------------------+ | | | | | +----------------+ Endpoint Link +----------------+ | | Endpoint |Endpoint Stream |-----------------|Endpoint Stream | Endpoint | | +----------------+ +----------------+ | | | | | +---------------------+ +----------------------+ ``` ## Endpoint An **endpoint** is a session management object that provides a representation of user conceivable places where media can be routed to/from. Examples of endpoints associated with hardware on a desktop-like system: - Laptop speakers. - USB webcam. - Bluetooth headset microphone. - Line out stereo jack port. Examples of endpoints associated with hardware in a car: - Speakers amplifier. - Front right seat microphone array. - Rear left seat headphones. - Bluetooth phone voice gateway. - Hardware FM radio device. Examples of endpoints associated with software: - Desktop screen capture source. - Media player application. - Camera application. In most cases an endpoint maps to a node on the media graph, but this is not always the case. An endpoint may be backed by several nodes or no nodes at all. Different endpoints may also be sharing nodes in some cases. An endpoint that does not map to any node may be useful to represent hardware that the session manager needs to be able to control, but there is no way to route media to/from that hardware through the PipeWire media graph. For example, in a car we may have a CD player device that is directly wired to the speakers amplifier and therefore audio flows between them without passing through the controlling CPU. However, it is useful for the session manager to be able to represent the *CD player endpoint* and the _endpoint link_ between it and the amplifier, so that it can apply audio policy that takes into account whether the CD player is playing or not. ### Target An **endpoint** may be grouping together targets that can be reached by following the same route and they are mutually exclusive with each other. For example, the speakers and the headphones jack on a laptop are usually mutually exclusive by hardware design (hardware mutes the speakers when the headphones are enabled) and they share the same ALSA PCM device, so audio still follows the same route to reach both. In this case, a session manager may choose to group these two targets into the same endpoint, using a parameter on the _endpoint_ object to allow the user to choose the target (if the hardware allows configuring this at all). ## Endpoint Stream An **endpoint stream** is attached to an **endpoint** and represents a logical path that can be taken to reach this endpoint, often associated with a _use case_. For example, the "Speakers amplifier" endpoint in a car might have the following streams: - _Music_: A path to play music; the implementation will output this to all speakers, using the volume that has been configured for the "Music" use case. - _Voice_: A path to play a voice message; such as a navigation message or feedback from a voice assistant, the implementation will output this to the front speakers only. Lowering the volume of the music (if any) on these speakers at the same time. - _Emergency_: A path to play an emergency situation sound (a beep, or equivalent); the implementation will output this on all speakers. Increasing the volume to a factory defined value if necessary (to ensure that it is audible) while muting audio from all other streams at the same time. In another example, a microphone that can be used for activating a voice assistant might have the following streams: - _Capture_: A path to capture directly from the microphone; this can be used by an application that listens for the assistant's wake-word in order to activate the full voice recognition engine. - _CaptureDelayed_: A path to capture with a constant delay (meaning that starting capturing now will actually capture something that was spoken a little earlier); this can be used by the full voice recognition engine, allowing it to start after the wake-word has been spoken while capturing audio that also includes the wake-word. Endpoint streams may be mutually exclusive or they may used simultaneously, depending on the implementation. Endpoint streams may be implemented in many ways: - By plugging additional nodes in the media graph that link to the device node (ex. a simple buffering node linked to an alsa source node could implement the _CaptureDelayed_ stream in the above microphone example). - By using a different device node (ex. different ALSA device on the same card) that has a special meaning for the hardware. - By triggering switches on the hardware (ex. modify ALSA controls on the same device). ## Endpoint Link An **endpoint link** connects two streams from two different endpoints, creating a logical representation of media flow between the endpoints. An **endpoint link** may be implemented by creating one or more _links_ in the underlying media graph, or it may be implemented by configuring hardware resources to enable media flow, in case the flow does not pass through the media graph. ### Constructing Constructing an **endpoint link** is done by asking the _endpoint stream_ objects to prepare it. First, the source stream is asked to provide linking information. When the information is retrieved, the sink stream is asked to use this information to prepare and to provide its own linking information. When this is done, the session manager is asked to create the link using the provided information. This mechanism allows stream implementations: - To prepare for linking, adjusting hardware paths if necessary. - To check for stream linking compatibility; not all streams can be connected to all others (ex. streams with media flow in the hardware cannot be linked to streams that are backed by nodes in the media graph). - To provide implementation specific information for linking; in the standard case this is going to be a list of _ports_ to be linked in the media graph, but in a hardware-flow case it can be any kind of hardware-specific detail. # Other Related Objects ## Device A **device** represents a handle to an underlying API that is used to create higher level objects, such as nodes, or other devices. Well-known devices include: | Device API | Description | | :--- | :--- | | alsa.pcm.device | A handle to an ALSA card (ex. `hw:0`, `hw:1`, etc). | | alsa.seq.device | A handle to an ALSA Midi device. | | v4l2.device | A handle to a V4L2 device (`/dev/video0`, `/dev/video1`, etc..). | | jack.device | A JACK client, allowing PipeWire to slave to JACK for audio input/output. | A device may have a _profile_, which allows the user to choose between multiple configurations that the device may be capable of having, or to simply turn the device _off_, which means that the handle is closed and not used by PipeWire. ## Session The **session** represents the session manager and can be used to expose global properties or methods that affect the session management. ### Default Endpoints The session is responsible for book-keeping the default device endpoints (one for each kind of device) that is to be used to link new clients when simulating a PulseAudio-like behavior, where the user can choose from the UI device preferences. For example, a system may have both "Speakers" and "HDMI" endpoints on the "Audio Output" category and the user may be offered to make a choice within the UI to select which endpoint they want to use by default for audio output. This preference is meant to be stored in the session object. ### Multiple Sessions It is not currently defined whether it is allowed to have multiple sessions or not and how the system should behave if this happens. # Mappings To Underlying Subsystem Objects ## ALSA UCM This is a ***proposal*** | ALSA / UCM | PipeWire | | :--- | :--- | | ALSA card | device | | UCM verb | device profile | | UCM device | endpoint (+ target, grouping conflicting devices into the same endpoint) | | UCM modifier | endpoint stream | | PCM stream | node | In UCM mode, an ALSA card is represented as a PipeWire device, with the available UCM verbs listed as profiles of the device. Activating a profile (ie. a verb) will create the necessary nodes for the available PCM streams and at the same time it will also create one endpoint for each UCM device. Optionally conflicting UCM devices can be grouped in the same endpoint, listing the conflicting options as targets of the endpoint. The available UCM modifiers for each UCM device will be added as streams, plus one "default" stream for accessing the device with no modifiers. ## ALSA Fallback | ALSA | PipeWire | | :--- | :--- | | card | device | | PCM stream | node + endpoint | In the case where UCM (or another similar mechanism) is not available, ALSA cards are represented as PipeWire devices with only two profiles on/off. When the on profile is activated, a node and an associated endpoint are created for every available PCM stream. Endpoints in this case have only one "default" stream, unless they are extended by the session manager to have software-backed streams. ## V4L2 ***FIXME*** | V4L2 | PipeWire | | :--- | :--- | | device | device + node | # Relationship To Other API's ## PulseAudio ### Mapping PipeWire Objects For Access By PulseAudio Clients | PipeWire | PulseAudio | | :--- | :--- | | device | card | | device profile | card profile | | endpoint (associated with a device) | sink / source | | endpoint (associated with a client) | sink-input / source-output | | endpoint target | port | | endpoint stream | N/A, PA clients will be limited to the default stream | ### Mapping PulseAudio Clients To PipeWire | PulseAudio | PipeWire | | :--- | :--- | | stream | client + node + endpoint (no targets, 1 default stream) | ## Jack Note: This section is about JACK clients connecting to PipeWire through the JACK compatibility library. The scenario where PipeWire connects to another JACK server as a client is out of scope here. ### Mapping PipeWire Objects For Access By JACK Clients | PipeWire | JACK | | :--- | :--- | | node | client | | port | port | | device | N/A | | endpoint | N/A | ### Mapping JACK Clients To PipeWire | JACK | PipeWire | | :--- | :--- | | client | client + node | | port | port | JACK clients do not create endpoints. A session manager should be JACK aware in order to anticipate direct node linking. */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/doc/dox/internals/portal.dox000066400000000000000000000144611511204443500260570ustar00rootroot00000000000000/** \page page_portal Portal Access Control This document explains how clients from the portal are handled. The portal is a DBus service that exposes interfaces to request access to the PipeWire daemon to perform a certain set of functions. The PipeWire daemon runs outside the sandbox, the portal is a way for clients inside the sandbox to connect to and use PipeWire. The PipeWire socket is not exposed in the sandbox. Instead, The portal connects to PipeWire on behalf of the client, informing PipeWire that this client is a portal-managed client. PipeWire can detect and enforce extra permission checks on the portal managed clients. Once such portal is the [camera portal](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.Camera.html) that provides a PipeWire session to stream video from a camera. # Use Cases ## New Portal Managed Clients Need Device Permissions Configured When a new client is detected, the available objects need to be scanned and permissions configured for each of them. Only the devices belonging to the media_roles given by the portal are considered. ## New Devices Need To Be Made Visible To Portal Managed Clients Newly created objects are made visible to a client when the client is allowed to interact with it. Only the devices belonging to the media_roles given by the portal are considered. ## Permissions For A Device Need To Be Revoked The session manager listens to changes in the permissions of devices and will remove the client permissions accordingly. Usually this is implemented by listening to the permission store DBus object. The desktop environment might provide a configuration panel where these permissions can be managed. # Design ## The Portal A sandboxed client cannot connect to PipeWire directly. Instead, it connects to the sandbox side of the portal which then connects the PipeWire daemon to configure the session. The portal then hands the file descriptor of the PipeWire connection to the client and the client can use this file descriptor to interface with the PipeWire session directly. When the portal connects, it will set the following properties on the client object: - `"pipewire.access.portal.is_portal" = true` for the connection of the portal itself (as opposed to a client managed by the portal). - `"pipewire.access.portal.app_id"` the [application ID](https://docs.flatpak.org/en/latest/conventions.html#application-ids) of the client. - `"pipewire.access.portal.media_roles"` media roles of the client. Currently only `"Camera"` is defined. Before returning the connection to a client, the portal configures minimal permissions on the client. No objects are initially visible. It is the task of the \ref page_session_manager to make the objects in the graph visible, depending on the client's `media_roles` (see also \ref PW_KEY_MEDIA_ROLE). ## The PipeWire Portal Module The PipeWire daemon uses the \ref page_module_portal to find the PID of the processes that owns the DBus name `org.freedesktop.portal.Desktop` (see the [XDG Desktop Portal](https://github.com/flatpak/xdg-desktop-portal)). Client connections from this PID are tagged as \ref PW_KEY_ACCESS `"portal"` (see \ref page_module_access). It will also set ALL permissions for this client so that it can resume. \dot digraph pw { compound=true; node [shape="box"]; rankdir="TB"; dbus [label="org.freedesktop.portal.Desktop"]; portal_access [label="PipeWire (mod: Portal Access)"]; portal [label="xdg-desktop-portal"]; dbus -> portal_access [arrowhead="dot"]; dbus -> portal [arrowhead="dot"]; portal_access -> portal [label="pipewire.access = portal"]; { rank="same"; portal_access; portal} } \enddot ## The Client A client can ask the portal for a connection to the PipeWire daemon. \dot digraph pw { compound=true; node [shape="box"]; rankdir="LR"; pw [label="PipeWire"]; portal [label="xdg-desktop-portal"]; client [label="client"]; client -> portal; portal -> pw [label="portal.is_portal=true", arrowhead="none"] {rank="min"; pw}; {rank="max"; client}; } \enddot The portal maintains an (unrestricted) connection to the PipeWire daemon with `"pipewire.access.portal.is_portal" = true` to identify the nodes the client needs access to. It then creates a new restricted connection for the client, tagged with additional information. \dot digraph pw { compound=true; node [shape="box"]; rankdir="LR"; pw [label="PipeWire"]; portal [label="xdg-desktop-portal"]; client [label="client"]; client -> portal [arrowhead="none"]; portal -> pw [label="portal.is_portal=true", arrowhead="none"] portal -> pw [label="portal.app_id = $appid"] {rank="min"; pw}; {rank="max"; client}; } \enddot The file descriptor for this restricted connection is passed back to the client which can now make use of the resources it has been permitted to access. \dot digraph pw { compound=true; node [shape="box"]; rankdir="LR"; pw [label="PipeWire"]; portal [label="xdg-desktop-portal"]; client [label="client"]; portal -> pw [label="portal.is_portal=true", arrowhead="none"] pw->client [label="restricted connection"]; {rank="min"; pw}; {rank="max"; client}; } \enddot ## The Session Manager The session manager listens for new clients to appear. It will use the \ref PW_KEY_ACCESS property to find portal connections. For client connections from the portal the session manager checks the requested `media_roles` and enables or disables access to the respective PipeWire objects. It might have to consult a database to decide what is allowed, for example the [org.freedesktop.impl.portal.PermissionStore](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.impl.portal.PermissionStore.html). \dot strict digraph pw { compound=true; node [shape="box"]; rankdir="LR"; portal [label="xdg-desktop-portal"]; client [label="client"]; subgraph { rankdir="TB"; permissions [label="PermissionStore"]; sm->permissions; sm [label="Session Manager"]; pw [label="PipeWire"]; sm -> pw [headlabel="allow $media.roles"]; pw -> sm; portal -> pw [label="portal.app_id = $appid"]; } client -> portal [arrowhead="none"]; {rank="min"; sm, pw}; {rank="max"; client}; } \enddot In the case of the [XDG Desktop Portal](https://github.com/flatpak/xdg-desktop-portal), the portal itself queries the PermissionStore directly. */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/doc/dox/internals/protocol.dox000066400000000000000000001303171511204443500264160ustar00rootroot00000000000000/** \page page_native_protocol Native Protocol PipeWire has a pluggable client/server IPC protocol. The reference implementation uses unix sockets and is implemented in \ref page_module_protocol_native. We document the messages here. \tableofcontents # Message header {#native-protocol-message-header} Each message on the unix socket contains a 16 bytes header and a variable length payload size: ``` 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Id | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | opcode | size | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | seq | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | n_fds | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | payload POD | . . +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | optional footer POD | . . +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ ``` These are four uint32 words to be read in native endianness. - Id: the message id this is the destination resource/proxy id - opcode: the opcode on the resource/proxy interface - size: the size of the payload and optional footer of the message - seq: an increasing sequence number for each message - n_fds: number of file descriptors in this message. The sender should send along with each message the fds that belong to the message. If there are more than the maximum number of fds in the message than can fit in one message, the message is split into multiple parts. A receiver needs to first read the header to know the size of the complete message. After collecting the complete message, it can start processing it. The payload is a single POD see \ref page_spa_pod for details. After the payload, there is an optional footer POD object. # Making a connection {#native-protocol-making-connection} First a connection is made to a unix domain socket. By default, the socket is named as "pipewire-0" and searched in the following directories: - getenv("PIPEWIRE_RUNTIME_DIR") - getenv("XDG_RUNTIME_DIR") - getenv("USERPROFILE") The client opens the socket and allocates a Core proxy with id 0 and a Client proxy with id 1. The proxy is nothing more than a way to identify the remote objects with an id. Usually they also contain the type and version of the remote object and some helpers (interfaces) to dispatch messages. The server will allocate a new Core resource with id 0. Resources are the counterpart of client proxies and also typically contain extra information and helpers (interfaces) to dispatch messages. The client sends the Core::Hello message on the socket to start the communication. The server binds the client resource with id 1 to the client. This means that the client object on the server will now have a resource with a client side proxy associated with it. When the client sends a message for a certain proxy, the server side resource with the same id for the client is found and the message is dispatched to the object associated with the resource. Similarly when a server sends a message on a resource, the client will find the proxy with the same id and dispatch the message to it's associated object. The client then sends client properties to the server. ``` client server |---------------------------------------->| | open socket | | | |---------------------------------------->| | Core::Hello(version) | | | |---------------------------------------->| | Client::UpdateProperties | . . ``` This completes the setup of the client. The newly connected client will appear in the registry at this point. # Core proxy/resource {#native-protocol-core} The core is always the object with Id 0. ## Core Methods (Id 0) ### Core::Hello (Opcode 1) The first message sent by a client is the Hello message and contains the version number of the client. ``` Struct( Int: version ) ``` The version is 3. ### Core::Sync (Opcode 2) The Sync message will result in a Done event from the server. When the Done event is received, the client can be sure that all operations before the Sync method have been completed. ``` Struct( Int: id Int: seq ) ``` - id: the id will be returned in the Done event. - seq: is usually generated automatically and will be returned in the Done event. ### Core::Pong (Opcode 3) Is sent from the client to the server when the server emits the Ping event. The id and seq should be copied from the Ping event. ``` Struct( Int: id Int: seq ) ``` ### Core::Error (Opcode 4) An error occurred in an object on the client. ``` Struct( Int: id Int: seq Int: res String: message ) ``` - id: The id of the proxy that is in error. - seq: a seq number from the failing request (if any) - res: a negative errno style error code - message: an error message ### Core::GetRegistry (Opcode 5) A client requests to bind to the registry object and list the available objects on the server. Like with all bindings, first the client allocates a new proxy id and puts this as the new_id field. Methods and Events can then be sent and received on the new_id (in the message Id field). ``` Struct( Int: version Int: new_id ) ``` - version: the version of the registry interface used on the client - new_id: the id of the new proxy with the registry interface After this method, the server will start sending Registry::Global events to the proxy with new_id. ``` client server | | | new proxy(new_id) | |---------------------------------------->| new resource(new_id) | Core::GetRegistry(version) | | | |<----------------------------------------| new_id | Registry::Global() | |<----------------------------------------| | Registry::Global() | . . ``` There is no explicit last Global event to signal that the last object has been received. The usual way of knowing this is to send a Core::Sync method right after the Core::GetRegistry method and to wait for the Core::Done event. ``` client server | | | new proxy(new_id) | |---------------------------------------->| new resource(new_id) | Core::GetRegistry(version) | |---------------------------------------->| | seq = Core::Sync | | | |<----------------------------------------| new_id | Registry::Global() | |<----------------------------------------| | Registry::Global() | . . |<----------------------------------------| | Core::Done(seq) | . . ``` ### Core::CreateObject (Opcode 6) Create a new object from a factory of a certain type. The client allocates a new_id for the proxy. The server will allocate a new resource with the same new_id and from then on, Methods and Events will be exchanged between the new object of the given type. ``` Struct( String: factory_name String: type Int: version Struct( Int: n_items (String: key String: value)* ): props Int: new_id ) ``` - factory_name: the name of a server factory object to use - type: the type of the object to create, this is also the type of the interface of the new_id proxy. - props: extra properties to create the object - new_id: the proxy id of the new object ### Core::Destroy (Opcode 7) Destroy an object. ``` Struct( Int: id ) ``` - id: the proxy id of the object to destroy. ## Core Events Core events are emitted by the server. ### Core::Info (Opcode 0) Emitted by the server upon connection with the more information about the server. ``` Struct( Int: id Int: cookie String: user_name String: host_name String: version String: name Long: change_mask Struct( Int: n_items (String: key String: value)* ): props ) ``` - id: the id of the server (0) - cookie: a unique cookie for this server - user_name: the name of the user running the server - host_name: the name of the host running the server - version: a version string of the server - name: the name of the server - change_mask: a set of bits with changes to the info - props: optional key/value properties, valid when change_mask has (1<<0) ### Core::Done (Opcode 1) Emitted as a result of a client Sync method. ``` Struct( Int: id Int: seq ) ``` id and seq are passed from the client Sync request. ### Core::Ping (Opcode 2) Emitted by the server when it wants to check if a client is alive or ensure that it has processed the previous events. ``` Struct( Int: id Int: seq ) ``` - id: the object id to ping - seq: usually automatically generated. The client should pass this in the Pong method reply. ### Core::Error (Opcode 3) The error event is sent out when a fatal (non-recoverable) error has occurred. The id argument is the proxy object where the error occurred, most often in response to a request to that object. The message is a brief description of the error, for (debugging) convenience. ``` Struct( Int: id Int: seq Int: res String: message ) ``` - id: The id of the resource that is in error. - seq: a seq number from the failing request (if any) - res: a negative errno style error code - message: an error message ### Core::RemoveId (Opcode 4) This event is used internally by the object ID management logic. When a client deletes an object, the server will send this event to acknowledge that it has seen the delete request. When the client receives this event, it will know that it can safely reuse the object ID. ``` Struct( Int: id ) ``` - id: a proxy id that was removed ### Core::BoundId (Opcode 5) This event is emitted when a local object ID is bound to a global ID. It is emitted before the global becomes visible in the registry. This event is deprecated, the BoundProps event should be used because it also contains extra properties. ``` Struct( Int: id Int: global_id ) ``` - id: a proxy id - global_id: the global_id as it will appear in the registry. ### Core::AddMem (Opcode 6) Memory is given to a client as fd of a certain memory type. Further references to this fd will be made with the per memory unique identifier id. ``` Struct( Int: id Id: type Fd: fd Int: flags ) ``` - id: a server allocated id for this memory - type: the memory type, see enum spa_data_type - fd: the index of the fd sent with this message - flags: extra flags ### Core::RemoveMem (Opcode 7) Remove memory for a client with the given id ``` Struct( Int: id ) ``` - id: the id of the memory to remove. This is the Id from AddMem. ### Core::BoundProps (Opcode 8) This event is emitted when a local object ID is bound to a global ID. It is emitted before the global becomes visible in the registry. ``` Struct( Int: id Int: global_id Struct( Int: n_items (String: key String: value)* ): props ) ``` - id: a proxy id - global_id: the global_id as it will appear in the registry. - props: the properties of the global # Registry proxy/resource {#native-protocol-registry} The registry is obtained with the GetRegistry method on the Core object. The Id depends on the new_id that was provided. ## Registry Methods ### Registry::Bind (Opcode 1) Bind to the global object with id and use the client proxy with new_id as the proxy. After this call, methods can be send to the remote global object and events can be received. ``` Struct( Int: id String: type Int: version Int: new_id ) ``` - id: the global_id to bind to - type: the type of the global id - version: the client version of the interface for type - new_id: the client proxy id for the global object ### Registry::Destroy (Opcode 2) Try to destroy the global object with id. This might fail when the client does not have permission. ``` Struct( Int: id ) ``` - id: the global id to destroy. ## Registry Events ### Registry::Global (Opcode 0) Notify a client about a new global object. ``` Struct( Int: id Int: permissions String: type Int: version Struct( Int: n_items (String: key String: value)* ): props ) ``` - id: the global id - permissions: permission bits - type: the type of object - version: the server version of the object - props: extra global properties ### Registry::GlobalRemove (Opcode 1) A global with id was removed ``` Struct( Int: id ) ``` - id: the global id that was removed. # PipeWire:Interface:Client {#native-protocol-client} The client object represents a client connect to the PipeWire server. Permissions of the client can be managed. The currently connected client always has the Client object with proxy id 1. ## Client Methods ### Client::Error (Opcode 1) Is used to send an error to a client. ``` Struct( Int: id Int: res String: error ) ``` - id: a client proxy id to send the error to - res: a negative errno style error code - error: an error message ### Client::UpdateProperties (Opcode 2) Is used to update the properties of a client. ``` Struct( Struct( Int: n_items (String: key String: value)* ): props ) ``` - props: properties to update on the client. ### Client::GetPermissions (Opcode 3) Get the currently configured permissions on the client. ``` Struct( Int: index Int: num ) ``` - index: the start index of the permissions to get - num: the number of permissions to get This method will result in at most num Permission Events. ### Client::UpdatePermissions (Opcode 4) Update the permissions of the global objects using the provided array with permissions ``` Struct( Int: n_permissions ( Int: id Int: permission )* ) ``` - n_permissions: number of permissions - id: the global id - permissions: the permissions for the global id ## Client Events ### Client::Info (Opcode 0) Get client information updates. This is emitted when binding to a client or when the client info is updated later. ``` Struct( Int: id Long: change_mask Struct( Int: n_items (String: key String: value)* ): props ) ``` - id: the global id of the client - change_mask: the changes emitted by this event - props: properties of this object, valid when change_mask has (1<<0) ### Client::Permissions (Opcode 1) Emitted as the reply of the GetPermissions method. ``` Struct( Int: index Struct( Int: n_permissions (Int: id Int: permission)* ) ) ``` - index: index of the first permission - n_permissions: the number of permission entries - id: the global id of the object - permissions: the permission for the given id # PipeWire:Interface:Device {#native-protocol-device} A device is an object that manages other devices or nodes. The usual flow is to bind to the Device object. This will result in an Info event. From the Info event one can find the available params to enumerate. ## Device methods ### Device::SubscribeParams (Opcode 1) Automatically emit Param events for the given ids when they are changed. ``` Struct( Array[Id]: ids ) ``` - ids: and array of param Id to subscribe to ### Device::EnumParams (Opcode 2) Enumerate the values of a param. This will result in Param events. ``` Struct( Int: seq Id: id Int: index Int: num Pod: filter ) ``` - seq: an automatically generated sequence number, will be copied into the reply - id: the param id to enumerate. - index: the first param index to retrieve - num: the number of params to retrieve - filter: an optional filter object for the param. ### Device::SetParam (Opcode 3) Set a parameter on the Device. ``` Struct( Id: id Int: flags Pod: param ) ``` - id: the param id to set. - flags: extra flags - param: the param object to set ## Device events ### Device::Info (Opcode 0) The info event is emitted when binding or when the device information changed. ``` Struct( Int: id Long: change_mask Struct( Int: n_items ( String: key String: value )* ): props Struct( Int: n_params ( Id: id Int: flags )* ): param_info ) ``` - id: the id of the global - change_mask: a bitmask of changed fields - props: extra properties, valid when change_mask is (1<<0) - param_info: info about the parameters, valid when change_mask is (1<<1) For each parameter, the id and current flags are given. - param_info.id : see enum spa_param_type - param_info.flags: struct spa_param_info.flags ### Device::Param (Opcode 1) Emitted as a result of EnumParams or SubscribeParams. ``` Struct( Int: seq Id: id Int: index Int: next Pod: param ) ``` - seq: the sequence number send by the client EnumParams or server generated in the SubscribeParams case. - id: the param id that is reported, see enum spa_param_type - index: the index of the parameter - next: the index of the next parameter - param: the parameter. The object type depends on the id # PipeWire:Interface:Factory {#native-protocol-factory} A factory is an object that allows one to create new objects. ## Factory methods A factory has no methods ## Factory events ### Factory::Info (Opcode 0) Info is emitted when binding to the factory global or when the information changed. ``` Struct( Int: id String: name String: type Int: version Long: change_mask Struct( Int: n_items ( String: key String: value )* ): props ) ``` - id: the global id of the factory - name: the name of the factory. This can be used as the name for Core::CreateObject - type: the object type produced by this factory - version: the version of the object interface - change_mask: bitfield of changed values. - props: optional properties of the factory, valid when change_mask is (1<<0) # PipeWire:Interface:Link {#native-protocol-link} A link is a connection between 2 ports. ## Link methods A link has no methods ## Link events ### Link::Info (Opcode 0) Info is emitted when binding to the link global or when the information changed. ``` Struct( Int: id Int: output_node_id Int: output_port_id Int: input_node_id Int: input_port_id Long: change_mask Int: state String: error Pod: format Struct( Int: n_items ( String: key String: value )* ): props ) ``` - id: the global id of the link - output_node_id: the global id of the output node - output_port_id: the global id of the output port - input_node_id: the global id of the input node - input_port_id: the global id of the input port - change_mask: bitfield of changed values. - state: the state of the link, valid when change_mask has (1<<0) - see enum pw_link_state for values - error: an optional error string - format: an optional format for the link, valid when change_mask has (1<<1) - props: optional properties of the link, valid when change_mask is (1<<2) # PipeWire:Interface:Module {#native-protocol-module} A Module provides dynamically loaded functionality ## Module methods A module has no methods ## Module events ### Module::Info (Opcode 0) Info is emitted when binding to the module global or when the information changed. ``` Struct( Int: id String: name String: filename String: args Long: change_mask Struct( Int: n_items ( String: key String: value )* ): props ) ``` - id: the global id of the module - name: the name of the module - filename: the filename of the module - args: arguments passed when loading the module - change_mask: bitfield of changed values. - props: optional properties of the module, valid when change_mask has (1<<0) # PipeWire:Interface:Node {#native-protocol-node} A Node is a processing element in the graph ## Node methods ### Node::SubscribeParams (Opcode 1) Automatically emit Param events for the given ids when they are changed. ``` Struct( Array[Id]: ids ) ``` - ids: and array of param Id to subscribe to ### Node::EnumParams (Opcode 2) Enumerate the values of a param. This will result in Param events. ``` Struct( Int: seq Id: id Int: index Int: num Pod: filter ) ``` - seq: an automatically generated sequence number, will be copied into the reply - id: the param id to enumerate. - index: the first param index to retrieve - num: the number of params to retrieve - filter: an optional filter object for the param. ### Node::SetParam (Opcode 3) Set a parameter on the Node. ``` Struct( Id: id Int: flags Pod: param ) ``` - id: the param id to set. - flags: extra flags - param: the param object to set ### Node::SendCommand (Opcode 4) Send a Command to the node. ``` Struct( Pod: command ) ``` - command: the command to send. See enum spa_node_command ## Node events ### Node::Info (Opcode 0) The info event is emitted when binding or when the node information changed. ``` Struct( Int: id Int: max_input_ports Int: max_output_ports Long: change_mask Int: n_input_ports Int: n_output_ports Id: state String: error Struct( Int: n_items ( String: key String: value )* ): props Struct( Int: n_params ( Int: id Int: flags )* ): param_info ) ``` - id: the id of the node global - max_input_port: the maximum input ports for the node - max_output_port: the maximum output ports for the node - change_mask: a bitmask of changed fields - n_input_port: the number of input ports, when change_mask has (1<<0) - n_output_port: the number of output ports, when change_mask has (1<<1) - state: the current node state, when change_mask has (1<<2) - See enum pw_node_state for values - error: an error message. - props: extra properties, valid when change_mask is (1<<3) - param_info: info about the parameters, valid when change_mask is (1<<4) For each parameter, the id and current flags are given. - param_info.id : see enum spa_param_type - param_info.flags: struct spa_param_info.flags ### Node::Param (Opcode 1) Emitted as a result of EnumParams or SubscribeParams. ``` Struct( Int: seq Id: id Int: index Int: next Pod: param ) ``` - seq: the sequence number send by the client EnumParams or server generated in the SubscribeParams case. - id: the param id that is reported, see enum spa_param_type - index: the index of the parameter - next: the index of the next parameter - param: the parameter. The object type depends on the id # PipeWire:Interface:Port {#native-protocol-port} A port is part of a node and allows links with other ports. ## Port methods ### Port::SubscribeParams (Opcode 1) Automatically emit Param events for the given ids when they are changed. ``` Struct( Array[Id]: ids ) ``` - ids: and array of param Id to subscribe to ### Port::EnumParams (Opcode 2) Enumerate the values of a param. This will result in Param events. ``` Struct( Int: seq Id: id Int: index Int: num Pod: filter ) ``` - seq: an automatically generated sequence number, will be copied into the reply - id: the param id to enumerate. - index: the first param index to retrieve - num: the number of params to retrieve - filter: an optional filter object for the param. ## Port events ### Port::Info (Opcode 0) The info event is emitted when binding or when the port information changed. ``` Struct( Int: id Int: direction Long: change_mask Struct( Int: n_items ( String: key String: value )* ): props Struct( Int: n_params ( Int: id Int: flags )* ): param_info ) ``` - id: the id of the port global - direction: the direction of the port, see enum pw_direction - change_mask: a bitmask of changed fields - props: extra properties, valid when change_mask is (1<<0) - param_info: info about the parameters, valid when change_mask is (1<<1) For each parameter, the id and current flags are given. - param_info.id : see enum spa_param_type - param_info.flags: struct spa_param_info.flags ### Port::Param (Opcode 1) Emitted as a result of EnumParams or SubscribeParams. ``` Struct( Int: seq Id: id Int: index Int: next Pod: param ) ``` - seq: the sequence number send by the client EnumParams or server generated in the SubscribeParams case. - id: the param id that is reported, see enum spa_param_type - index: the index of the parameter - next: the index of the next parameter # PipeWire:Interface:ClientNode {#native-protocol-clientnode} The ClientNode object is created from the `client-node` factory that is provided by the `libpipewire-module-client-node` module. It creates a server Node that can be controlled from a client. Processing will happen in the client. It is used by pw-stream and pw-filter to implement the PipeWire media processing nodes. To create a client-node, one must first connect to the server and then make a ClientNode proxy and do Core::CreateObject with the client-node factory. This will create a server side ClientNode resource that can be controlled with the proxy. After the proxy is set up, the conversation between client and server goes as follows: ``` client server | | |---------------------------------------->| | ClientNode::Update | send initial node configuration | | |<----------------------------------------| | Core::AddMem | memory for node activation |<----------------------------------------| | ClientNode::SetActivation | the node activation |<----------------------------------------| | ClientNode::Transport | the node transport |<----------------------------------------| | ClientNode::SetIO | clock IO | | |---------------------------------------->| | ClientNode::SetActive(true) | set the node active | | |<----------------------------------------| | ClientNode::SetParam | optional volume restore/settings |<----------------------------------------| | ClientNode::SetParam | optional PortConfig if needed |---------------------------------------->| | ClientNode::Update | Upload changed params | | |---------------------------------------->| | ClientNode::PortUpdate | config for each port . . |<----------------------------------------| | ClientNode::PortSetMixInfo | mixer inputs for each linked port |<----------------------------------------| | ClientNode::PortSetParam | PeerCapability of the ports |<----------------------------------------| | ClientNode::PortSetParam | Latency of the ports |<----------------------------------------| | ClientNode::SetActivation | activation of port peers |---------------------------------------->| | ClientNode::PortUpdate | Ack port updates . . |<----------------------------------------| | ClientNode::PortSetParam | formats of the ports |---------------------------------------->| | ClientNode::PortUpdate | Ack port format update . . |<----------------------------------------| | Core::AddMem | memory for the port buffers |<----------------------------------------| | ClientNode::PortUseBuffers | buffers for a port . . |<----------------------------------------| | Core::AddMem | memory for driver activation |<----------------------------------------| | ClientNode::SetActivation | the driver activation |<----------------------------------------| | ClientNode::SetIO | driver Position IO |<----------------------------------------| | Core::AddMem | memory for port IO |<----------------------------------------| | ClientNode::PortSetIO | buffers/async-buffers port IO . . |<----------------------------------------| | ClientNode::Command | Start command . . ``` ## ClientNode methods ### ClientNode::GetNode (Opcode 1) Get the node object associated with the client-node. This binds to the server side Node object. ``` Struct( Int: version Int: new_id ) ``` - version: the Node version to bind as - new_id: the proxy id ### ClientNode::Update (Opcode 2) Update the params and info of the node. ``` Struct( Int: change_mask Int: n_params ( Pod: param )* Struct( Int: max_input_ports Int: max_output_ports Long: change_mask Long: flags Int: n_items ( String: key String: value )* Int: n_params ( Id: id Int: flags )* ): info ) ``` - change_mask: a bitfield of changed items - n_params: number of update params, valid when change_mask has (1<<0) - param: an updated param - info: an updated `struct spa_node_info`, valid when change_mask has (1<<1) - max_input_ports: maximum input ports - max_output_ports: maximum output ports - change_mask: bitmask of changed items - flags: flags, see `struct spa_node_info`, when change_mask has (1<<0) - n_items: updated properties, valid when info.change_mask has (1<<1) - n_params: updated `struct spa_param_info`, valid when info.change_mask has (1<<2) ### ClientNode::PortUpdate (Opcode 3) Create, Update or destroy a node port. When the port is not known on the server, the port is created. When info is None, the port is destroyed. Otherwise, the port information is updated. ``` Struct( Int: direction Int: port_id Int: change_mask Int: n_params ( Pod: param )* Struct( Long: change_mask Long: flags Int: rate_num Int: rate_denom Int: n_items ( String: key String: value )* Int: n_params ( Id: id Int: flags )* ): info ) ``` - direction: the port direction - port_id: the port id - change_mask: a bitfield of changed items - n_params: number of updated params, valid when change_mask has (1<<0) - param: n_params updated params - info: an updated `struct spa_port_info`, valid when change_mask has (1<<1) - change_mask: bitmask of changed items - flags: flags, see `struct spa_port_info`, when change_mask has (1<<0) - rate_num: updated rate numerator - rate_denom: updated rate denominator, when info.change_mask has (1<<1) - n_items: updated properties, valid when info.change_mask has (1<<2) - n_params: updated `struct spa_param_info`, valid when info.change_mask has (1<<3) ### ClientNode::SetActive (Opcode 4) Set the node active or inactive. ``` Struct( Bool: active ) ``` - active: the new state of the node ### ClientNode::Event (Opcode 5) Emit an event on the node. ``` Struct( Pod: event ) ``` - event: the event to emit. See `enum spa_node_event`. ### ClientNode::PortBuffers (Opcode 6) This method is used by the client when it has allocated buffers for a port. It is usually called right after the UseBuffers event to let the server know about the the newly allocated buffer memory. ``` Struct( Int: direction Int: port_id Int: mix_id Int: n_buffers ( Int: n_datas ( Id: type Fd: memfd Int: flags Int: mapoffset Int: maxsize )* )* ) ``` - direction: the direction of the port - port_id: the port id - mix_id: the mix id of the port - n_buffer: the number of buffers - n_data: for each buffer the number of data planes - type: the plane memory type - memfd: the plane memfd - mapoffset: the start offset of where the buffer memory starts - maxsize: the maximum size of the memory. ## ClientNode events ### ClientNode::Transport (Opcode 0) The server will allocate the activation record and eventfd for the node and transfer this to the client with the Transport event. ``` Struct( Fd: readfd Fd: writefd Int: memfd Int: offset Int: size ) ``` - readfd: the eventfd to start processing - writefd: the eventfd to signal when the driver completes and profiling is enabled. - memfd: the index of the memfd of the activation record - offset: the offset in memfd of the start of the activation record - size: the size of the activation record The activation record is currently an internal data structure that is not yet ABI stable. The writefd is meant to wake up the server after the driver completes so that the profiler can collect the information. The profiler is active when the pw_node_activation::flags fields has PW_NODE_ACTIVATION_FLAG_PROFILER set. When the profiler is disabled (or when the node is not driving), this eventfd should not be signaled. ### ClientNode::SetParam (Opcode 1) Set a parameter on the Node. ``` Struct( Id: id Int: flags Pod: param ) ``` - id: the param id to set. - flags: extra flags - param: the param object to set ### ClientNode::SetIO (Opcode 2) Set an IO area on the node. ``` Struct( Id: id Int: memid Int: offset Int: size ) ``` - id: the io area id to set. - the memid to use, this is signaled with Core::AddMem - offset: the start offset in the memory area - the size of the io area ### ClientNode::Event (Opcode 3) Emit an event on the node. ``` Struct( Pod: event ) ``` - event: the event to emit. See `enum spa_node_event`. ### ClientNode::Command (Opcode 4) Send a command on the node. ``` Struct( Pod: command ) ``` - command: the command to send. See `enum spa_node_command`. ### ClientNode::AddPort (Opcode 5) Add a new port to the node ``` Struct( Int: direction Int: port_id Struct( Int: n_items ( String: key String: value )* ): props ) ``` - direction: the direction of the new port - port_id: the port id of the new port - props: optional extra properties for the port ### ClientNode::RemovePort (Opcode 6) Remove a port from the node ``` Struct( Int: direction Int: port_id ) ``` - direction: the direction of the port to remove - port_id: the port id of the port to remove ### ClientNode::PortSetParam (Opcode 7) Set a parameter on the Port of the node. ``` Struct( Int: direction Int: port_id Id: id Int: flags Pod: param ) ``` - direction: the direction of the port - port_id: the port id of the port - id: the param id to set. - flags: extra flags - param: the param object to set ### ClientNode::UseBuffers (Opcode 8) Use a set of buffers on the mixer port ``` Struct( Int: direction Int: port_id Int: mix_id Int: flags Int: n_buffers ( Int: memid Int: offset Int: size Int: n_metas ( Id: type Int: size )*: meta Int: n_datas ( Id: type Int: data Int: flags Int: mapoffset Int: maxsize )*: data )*: buffer ) ``` - direction: the direction of the port - port_id: the port id of the port - mix_id: the mixer id of the port - flags: extra flags - id: the param id to set. - flags: extra flags - SPA_NODE_BUFFERS_FLAG_ALLOC (1<<0) to let the client allocate buffers, in which case the PortBuffers method needs to be called with the allocated buffer memory. - n_buffers: the number of buffers - memid: the memory id of the buffer metadata and or data - offset: the offset in memid of the buffer - size: the size of the buffer metadata or data - n_metas: number of metadata. The buffer memory first contains this number of metadata parts of the given type and size - type: metadata type - size: metadata size, round up with 8 to get to the next metadata - n_data: the number of datablocks (planes) per buffer - type: the data type, this can be: - SPA_DATA_MemId to reference a memfd from Core:AddMem - SPA_DATA_MemPtr to reference this buffer memid - data: contains the memid or offset in the memid - flags: extra flags for the data - mapoffset: the offset in memfd - maxsize: the maxsize of the memory in memfd ### ClientNode::PortSetIO (Opcode 9) Set an IO area on a mixer port. ``` Struct( Int: direction Int: port_id Int: mix_id Id: id Int: memid Int: offset Int: size ) ``` - direction: the direction of the port - port_id: the port id of the port - mix_id: the mix id of the port - id: the IO area to set. See enum spa_io_type - memid: the memid of the io area, added with Core::AddMem - offset: the offset in the memid - size: the size of the IO area ### ClientNode::SetActivation (Opcode 10) Notify the client of the activation record of a peer node. This activation record should be triggered when this node finishes processing. ``` Struct( Int: node_id Fd: signalfd Int: memid Int: offset Int: size ) ``` - node_id: the node_id of the peer node - signalfd: the eventfd of the peer node - memid: the memid of the activation record of the peer from Core:AddMem - offset: the offset in memid - size: the size of the activation record ### ClientNode::PortSetMixInfo (Opcode 11) Notify the node of the peer of a mixer port. This can be used to track the peer ports of a node. ``` Struct( Int: direction Int: port_id Int: mix_id Int: peer_id Struct( Int: n_items ( String: key String: value )* ): props ) ``` - direction: the direction of the port - port_id: the port id of the port - mix_id: the mix id of the port - peer_id: the id of the peer port - props: optional properties # PipeWire:Interface:Metadata {#native-protocol-metadata} Metadata is a shared database of settings and properties. ## Metadata methods ### Metadata::SetProperty (Opcode 1) Set a property in the metadata store. ``` Struct( Int: subject String: key String: type String: value ) ``` - subject: the id of the object, this needs to be a valid global_id - key: a key - type: an optional type - value: a value ### Metadata::Clear (Opcode 2) Clear the metadata store. ``` Struct( None ) ``` ## Metadata events ### Metadata::Property (Opcode 0) A metadata key changed. This is also emitted when binding to the metadata. ``` Struct( Int: subject String: key String: type String: value ) ``` - subject: the id of the object, this is a valid global_id - key: a key - type: an optional type - value: a value # PipeWire:Interface:Profiler {#native-protocol-profiler} The profiler object allows one to receive profiler information of the pipewire graph. ## Profiler methods The profiler has no methods ## Profiler events ### Profiler::Profile (Opcode 0) ``` Struct( Pod: object ) ``` - object: a SPA_TYPE_OBJECT_Profiler object. See enum spa_profiler # Footer {#native-protocol-footer} The message footer contains additional messages, not directed to the destination object defined by the `Id` field. The footer consists of a single POD, immediately following the payload POD. The footer POD consists of a sequence of footer opcode Ids and footer payload Structs containing their arguments: ``` Struct( Id: opcode1, Struct { ... }, Id: opcode2, Struct { ... }, ... ) ``` The footer opcodes are separate for server-to-client (`core`) and client-to-server (`client`) directions. The message footer is processed before other parts of the message, including the object Id lookup. ## Core Generation (Footer Opcode 0) ``` Struct( Long: registry_generation, ) ``` Indicates to the client what is the current registry generation number of the \ref pw_context on the server side. The server shall include this footer in the next message it sends that follows the increment of the registry generation number. \see \ref native-protocol-registry-generation ## Client Generation (Footer Opcode 0) ``` Struct( Long: client_generation, ) ``` Indicates to the server what is the last registry generation number the client has processed. The client shall include this footer in the next message it sends, after it has processed an incoming message whose footer includes a registry generation update. \see \ref native-protocol-registry-generation # Registry generation {#native-protocol-registry-generation} The registry generation is a 64-bit integer in the PipeWire server \ref pw_context that increments by one when the server allocates a new global \ref PW_KEY_OBJECT_ID for an object. The server keeps track of each global *id* as a tuple ( *id*, *object generation* ) where *object generation* is the registry generation value when the *id* was allocated. When an object is destroyed, its *id* value may be reallocated to a different object. Because the protocol is asynchronous, the object *id* alone is not sufficient to uniquely identify objects. The server looks up objects based on a tuple ( *id*, *generation* ) as follows: 1. Look up the \ref pw_global based on the *id*. 2. The lookup fails if there is no global for the *id*, or if *generation* < *object generation*. The protocol message generally contains only the object *id*. The registry generation part is passed around as follows: 1. The server sends the current *registry generation* to clients in the protocol footer, if it has changed. 2. The clients keep track of the latest registry generation of the messages they have processed. This is the *client generation*. 3. Each client sends their *client generation* in the protocol footer of the next message to the server, if its value has changed. 4. The server keeps track for each client the *client generation* they have sent back. 5. In each protocol message received from client, the server considers each object *id* as tuple ( *id*, *client generation* ). This allows the server to know if the object *id* the client refers to was already destroyed, but the client has not yet processed the message indicating that the *id* is gone. The server indicates failed lookups of this type with error code ESTALE to the client. If a client has not sent any *client generation* updates to the server, then the server will not do any registry generation checks in object lookups. This is for backward compatibility only. The registry generation is an internal detail of the server implementation and the native protocol, and is not visible to clients in the PipeWire API. To identify objects uniquely, clients can use \ref PW_KEY_OBJECT_SERIAL, which are unique for objects and not reused, unlike \ref PW_KEY_OBJECT_ID \see \ref PW_KEY_OBJECT_SERIAL */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/doc/dox/internals/pulseaudio.dox000066400000000000000000000037061511204443500267300ustar00rootroot00000000000000/** \page page_pulseaudio PulseAudio Compatibility # Internals - Mapping Between ALSA and Streams This explains the mapping between alsa cards and streams and session manager objects. ## ALSA Cards An ALSA card is exposed as a PipeWire device. ## Streams Each ALSA PCM is opened and a node is created for each PCM stream. # Session Manager ## ALSA UCM The mapping of the PipeWire object hierarchy to the ALSA object hierarchy is the following: One PipeWire device is created for every ALSA card. - For each UCM verb, a node is created for the associated PCM devices. - For each UCM verb, an endpoint is created. In a first step: For each available combination of UCM device and modifier, a stream is created. Streams are marked with compatible other streams. Streams with the same modifier and mutually exclusive devices are grouped into one stream and the UCM devices are exposed on the endpoint as destinations. ## ALSA Fallback Each PCM stream (node) becomes an endpoint. The endpoint references the ALSA device ID. Each endpoint has one stream (for now) called HiFi Playback / HiFi Capture. More streams can be created depending on the format of the node. ## ALSA Pulse UCM Using the ALSA backend of PulseAudio we can create the following streams. ## ALSA Pulse Fallback The pulse ALSA backend will use the mixer controls and some probing to create the following nodes and endpoints. # PulseAudio PulseAudio uses the session manager API to construct cards with profiles and sink/source with ports. If an endpoint references a device, a card object is created for the device. Each endpoint becomes a sink/source. Each Stream in the endpoint becomes a profile on the PulseAudio card. Because only one profile is selected on the device, only one stream is visible on the endpoint. This clashes with the notion that multiple streams can be active at the same time but is a PulseAudio limitation. Each Endpoint destination becomes a port on the sink/source. */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/doc/dox/internals/scheduling.dox000066400000000000000000000423351511204443500267040ustar00rootroot00000000000000/** \page page_scheduling Graph Scheduling This document tries to explain how the PipeWire graph is scheduled. Graphs are constructed from linked nodes together with their ports. This results in a dependency graph between nodes. Special care is taken for loopback links so that the graph remains a directed graph. # Processing threads The server (and clients) has two processing threads: - A main thread that will do all IPC with clients and server and configure the nodes in the graph for processing. - One (or more) data processing threads that only do the data processing. The data processing threads are given realtime priority and are designed to run with as little overhead as possible. All of the node resources such as buffers, I/O areas and metadata will be set up in shared memory before the node is scheduled to run. This document describes the processing that happens in the data processing thread after the main thread has configured it. # Nodes Nodes are objects with 0 or more input and output ports. Each node also has: - an eventfd to signal the node that it can start processing - an activation record that lives in shared memory with memfd. ``` eventfd +-^---------+ | | in out | | +-v---------+ activation { status:OK, // bitmask of NEED_DATA, HAVE_DATA or OK pending:0, // number of unsatisfied dependencies needed to be able to run required:0 // number of dependencies with other nodes } ``` The activation record has the following information: - processing state and pending dependencies. As long as there are pending dependencies the node cannot be processed. This is the only relevant information for actually scheduling the graph and is shown in the above illustration. - Current status of the node and profiling info (TRIGGERED, AWAKE, FINISHED, timestamps when the node changed state). - Timing information, mostly for drivers when the processing started, the time, duration and rate (quantum) etc.. - Information about repositions (seek) and timebase owners. # Links When two nodes are linked together, the output node becomes a dependency for the input node. This means the input node can only start processing when the output node is finished. This dependency is reflected in the required counter in the activation record. In below illustration, B's required field is incremented with 1. The pending field is set to the required field when the graph is started. Node A will keep a list of all targets (B) that it is a dependency of. This dependency update is only performed when the link is ready (negotiated) and the nodes are ready to schedule (runnable). ``` eventfd eventfd +-^---------+ +-^---------+ | | link | | in A out ---------------------> in B out | | | | +-v---------+ +-v---------+ activation { target activation { status:OK, --------------------> status:OK, pending:0, pending:1, required:0 required:1 } } ``` Multiple links between A and B will only result in 1 target link between A and B. # Drivers The graph can only run if there is a driver node that is in some way linked to an active node. The driver is special because it will have to initiate the processing in the graph. It will use a timer or some sort of interrupt from hardware to start the cycle. Any node can also be a candidate for a driver (when the node.driver property is true). PipeWire will select the node with the highest priority.driver property as the driver. Nodes will be assigned to the driver node they will be scheduled with. Each node holds a reference to the driver and increments the required field of the driver. When a node is ready to be scheduled, the driver adds the node to its list of targets and increments the required field. ``` eventfd eventfd +-^---------+ +-^---------+ | | link | | in A out ---------------------> in B out | | | | +-v---------+ +-v---------+ activation { target activation { status:OK, --------------------> status:OK, pending:0, pending:0, required:1 required:2 } } | ^ ^ | | / / | | / / | | / / | | / / | | / / v | /-------------/ / activation { / status:OK, V---------------/ pending:0, required:2 } +-^---------+ | | | driver | | | +-v---------+ eventfd ``` As seen in the illustration above, the driver holds a link to each node it needs to schedule and each node holds a link to the driver. Some nodes hold a link to other nodes. It is possible that the driver is the same as a node in the graph (for example node A) but conceptually, the links above are still valid. The driver will then start processing the graph by emitting the ready signal. PipeWire will then: - Check the previous cycle. Did it complete? Mark xrun on unfinished nodes. - Perform reposition requests if any, timebase changes, etc.. - The pending counter of each follower node is set to the required field. - Update the cycle counter in the driver activation io. - It then loops over all targets of the driver and atomically decrements the required field of the activation record. When the required field is 0, the eventfd is signaled and the node can be scheduled. In our example above, nodes A and B will have their pending state decremented. Node A will be 0 and will be triggered first (node B has 2 pending dependencies to start with and will not be triggered yet). The driver itself also has 2 dependencies left and will not be triggered (completed) yet. ## Scheduling node A When the eventfd is signaled on a node, we say the node is triggered and it will be able to process data. It consumes the input on the input ports and produces more data on the output ports. After processing, node A goes through the list of targets and decrements each pending field (node A has a reference to B and the driver). In our above example, the driver is decremented (from 2 to 1) but is not yet triggered. Node B is decremented (from 1 to 0) and is triggered by writing to the eventfd. ## Scheduling node B Node B is scheduled and processes the input from node A. It then goes through the list of targets and decrements the pending fields. It decrements the pending field of the driver (from 1 to 0) and triggers the driver. ## Scheduling the driver The graph always completes after the driver is triggered and scheduled. All required fields from all the nodes in the target list of the driver are now 0. The driver calculates some stats about CPU time etc. # Async scheduling When a node has the node.async property set to true, it will be considered an async node and will be scheduled differently. Async nodes don't increment the pending counter of their peers and the upstream peers also don't increment the async node pending counters. Only the driver increments the pending counter to the async node. This means that the async nodes do not depend on any other node and also are not a dependency for other nodes. This also means that the async nodes can be scheduled as soon as the driver has started the graph. The completion of the async node does not influence the completion of the graph in any way and async nodes are therefore interesting when real-time performance can not be guaranteed, for example when the processing threads are not running with a real-time priority. A link between a port of an async node and another port (async or not) is called an async link and will have the link.async=true property. Because async nodes then run concurrently with other nodes, a method must be in place to avoid concurrent access to buffer data. This is done by sending a spa_io_async_buffers I/O to the (mixer) ports of an async link. The spa_io_async_buffers has 2 spa_io_buffer slots. The driver will increment a cycle counter for each cycle that it starts. Output ports will write to the spa_io_async_buffers (cycle+1)&1 slot and input ports will read from (cycle&1) slots. This way the async node will always consume the output of the previous cycle and will provide data for the next cycle. They will therefore always add 1 cycle of latency in the graph. A special exception is made for the output ports of the driver node. When the driver is started, the output port buffers are copied to the previous cycle spa_io_buffer slot. This way, the async nodes will immediately pick up the new data from the driver source. Because there are 2 buffers in flight on the spa_io_async_buffers I/O area, the link needs to negotiate at least 2 buffers for this to work. ## Example A, B, C are async nodes and have async links between their ports. The async link has the spa_io_async_buffers with 2 slots (named 0 and 1) below. All the slots are empty. ``` +--------+ +-------+ +-------+ | A | | B | | C | | 0 -( )-> 0 0 -( )-> 0 | | 1 ( ) 1 1 ( ) 1 | +--------+ +-------+ +-------+ ``` cycle 0: A produces a buffer AB0 on the output port in the (cycle+1)&1 slot (1). B consumes slot cycle&1 (0) with the empty buffer and produces BC0 in slot 1 C consumes slot cycle&1 (0) with the empty buffer ``` +--------+ +-------+ +-------+ | A | | B | | C | | (AB0) 0 -( )-> 0 ( ) 0 -( )-> 0 ( ) | | 1 (AB0) 1 1 (BC0) 1 | +--------+ +-------+ +-------+ ``` cycle 1: A produces a buffer AB1 on the output port in the (cycle+1)&1 slot (0). B consumes slot cycle&1 (1) with buffer AB0 and produces BC1 in slot 0 C consumes slot cycle&1 (1) with buffer BC0 ``` +--------+ +-------+ +-------+ | A | | B | | C | | (AB1) 0 -(AB1)-> 0 (AB0) 0 -(BC1)-> 0 (BC0) | | 1 (AB0) 1 1 (BC0) 1 | +--------+ +-------+ +-------+ ``` cycle 2: A produces a buffer AB2 on the output port in the (cycle+1)&1 slot (1). B consumes slot cycle&1 (0) with buffer AB1 and produces BC2 in slot 1 C consumes slot cycle&1 (0) with buffer BC1 ``` +--------+ +-------+ +-------+ | A | | B | | C | | (AB2) 0 -(AB1)-> 0 (AB1) 0 -(BC1)-> 0 (BC1) | | 1 (AB2) 1 1 (BC2) 1 | +--------+ +-------+ +-------+ ``` Each async link adds 1 cycle of latency to the chain. Notice how AB0 from cycle 0, produces BC1 in cycle 1, which arrives in node C at cycle 2. ## Latency reporting Because the latency is really introduced by the links, the additional cycle of latency is added when the SPA_PARAM_Latency is copied between the output and input ports of a link. It is possible for a sync node A to be linked to another sync node D and an async node B: ``` +--------+ +-------+ | A | | B | | (AB1) 0 -(AB1)-> 0 (AB0) 0 ... | 1 \(AB0) 1 1 +--------+ \ +-------+ \ \ +-------+ \ | D | -(AB1)-> 0 (AB1) | | | +-------+ ``` The output latency on A's output port is what A reports. When it is copied to the input port of B, 1 cycle is added and when it is copied to D, nothing is added. # Remote nodes For remote nodes, the eventfd and the activation are transferred from the server to the client. This means that writing to the remote client eventfd will wake the client directly without going to the server first. All remote clients also get the activation and eventfd of the peer and driver they are linked to and can directly trigger peers and drivers without going to the server first. ## Remote driver nodes Remote drivers start the graph cycle directly without going to the server first. After they complete (and only when the profiler is active), they will trigger an extra eventfd to signal the server that the graph completed. This is used by the server to generate the profiler info. # Lazy scheduling Normally, a driver will wake up the graph and all the followers need to process the data in sync. There are cases where: 1. the follower might not be ready to process the data 2. the driver rate is not ideal, the follower rate is better 3. the driver might not know when new data is available in the follower and might wake up the graph too often. In these cases, the driver and follower roles need to be reversed and a mechanism needs to be provided so that the follower can know when it is worth processing the graph. For notifying when the graph is ready to be processed, (non driver) nodes can send a RequestProcess event which will arrive as a RequestProcess command in the driver. The driver can then decide to run the graph or not. When the graph is started or partially controlled by RequestProcess events and commands we say we have lazy scheduling. The driver is not always scheduling according to its own rhythm but also depending on the follower. We cannot just enable lazy scheduling when no follower will emit RequestProcess events or when no driver will listen for RequestProcess commands. Two new node properties are defined: - node.supports-lazy = 0 | 1 | ... 0 means lazy scheduling as a driver is not supported >1 means lazy scheduling as a driver is supported with increasing preference - node.supports-request 0 means request events as a follower are not supported >1 means request events as a follower are supported with increasing preference We can only enable lazy scheduling when both the driver and (at least one) follower have the node.supports-lazy and node.supports-request properties respectively. Nodes can end up as a driver (is_driver()) and lazy scheduling can be enabled (is_lazy()), which results in the following cases: driver producer -> node.driver = true -> is_driving() && !is_lazy() -> calls trigger_process() to start the graph lazy producer -> node.driver = true -> node.supports-lazy = 1 -> is_driving() && is_lazy() -> listens for RequestProcess and calls trigger_process() to start the graph requesting producer -> node.supports-request = 1 -> !is_driving() && is_lazy() -> emits RequestProcess to suggest starting the graph follower producer -> !is_driving() && !is_lazy() driver consumer -> node.driver = true -> is_driving() && !is_lazy() -> calls trigger_process() to start the graph lazy consumer -> node.driver = true -> node.supports-lazy = 1 -> is_driving() && is_lazy() -> listens for RequestProcess and calls trigger_process() to start the graph requesting consumer -> node.supports-request = 1 -> !is_driving() && is_lazy() -> emits RequestProcess to suggest starting the graph follower consumer -> !is_driving() && !is_lazy() Some use cases: 1. Screensharing - driver producer, follower consumer - The producer starts the graph when a new frame is available. - The consumer consumes the new frames. -> throttles to the rate of the producer and idles when no frames are available. producer - node.driver = true consumer - node.driver = false -> producer selected as driver, consumer is a simple follower. lazy scheduling inactive (no lazy driver or no request follower) 2. headless server - requesting producer, (semi) lazy driver consumer - The producer emits RequestProcess when new frames are available. - The consumer requests new frames from the producer according to its refresh rate when there are RequestProcess commands. -> this throttles the framerate to the consumer but idles when there is no activity on the producer. producer - node.driver = true - node.supports-request = 1 consumer - node.driver = true - node.supports-lazy = 2 -> consumer is selected as driver (lazy > request) lazy scheduling active (1 lazy driver and at least 1 request follower) 3. frame encoder - lazy driver producer, requesting follower consumer - The consumer pulls a frame when it is ready to encode the next one. - The producer produces the next frame on demand. -> throttles the speed to the consumer without idle. producer - node.driver = true - node.supports-lazy = 1 consumer - node.driver = true - node.supports-request = 1 -> producer is selected as driver (lazy <= request) lazy scheduling active (1 lazy driver and at least 1 request follower) */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/doc/dox/internals/session-manager.dox000066400000000000000000000032451511204443500276470ustar00rootroot00000000000000/** \page page_session_manager PipeWire Session Manager The \ref page_daemon is primarily a framework that allows devices and applications to exchange data. It provides the mechanism to do so but the policy deciding which components can talk to each other and when is controlled by the session manager. As outlined in \ref page_objects_design, PipeWire provides a media graph consisting of devices, nodes and ports. The session manager is the one that decides on the links between those elements. Two prominent session managers currently exist: - [PipeWire Media Session](https://gitlab.freedesktop.org/pipewire/media-session), the example session manager. - [WirePlumber](https://gitlab.freedesktop.org/pipewire/wireplumber), a modular session manager based on GObject. [Documentation](https://pipewire.pages.freedesktop.org/wireplumber/) This page describes some of the requirements for session managers in general. # Client Management PipeWire provides a \ref page_access "permission system" to limit client's access to resources but only \ref page_module_access "basic permission handling". The session manager is expected to decide whether clients may access specific resources. # Device Management PipeWire's responsibility is to open devices, however the decision on which devices should be opened is the job of a session manager, including the configuration of those devices. # Endpoint Grouping An endpoint is, effectively, a group of nodes that are a logical unit that can consume or produce media data. For example, a Bluetooth speaker may present as several nodes but is only one logical unit to stream audio to. See \ref page_objects_design for details on Endpoints. */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/doc/dox/internals/tag.dox000066400000000000000000000050661511204443500253320ustar00rootroot00000000000000/** \page page_tag Tag support This document explains how stream specific metadata is transported in the PipeWire graph. The metadata is a dictionary of string key/value pairs with information such as the author, track, copyright, album information etc. # Use Cases ## A stream/node/port has some metadata Applications need to be able to query the Tag of a port/node/stream. Linked Nodes need to be informed of the upstream and downstream tags. ## dynamically update tags It needs to be possible to dynamically update the tags of a port/node/stream and this should inform all linked ports/nodes of the updated tags. ## Aggregate tags upstream and downstream A node might need to know all the upstream and downstream tags. Each node can add or remove metadata in the Tag param. A mixer node might need to combine the Tags of the two input streams and generate a combined tag. # Concepts ## Port Tags The fundamental object for implementing metadata reporting in PipeWire is the Tag object. It consists of a direction (input/output) and one or more generic dictionaries with string key/value pairs. The direction of the tag object determines how the object propagates in the graph. - SPA_DIRECTION_OUTPUT Tag objects move from output ports downstream and contain the metadata from all nodes upstream. An output tag received on an input port should instruct the node to update the output tag on its output ports related to this input port. - SPA_DIRECTION_INPUT Tag objects move from input ports upstream and contain the metadata from all nodes downstream. An input tag received on an output port should instruct the node to update the input tag on its input ports related to this output port. PipeWire will automatically propagate Tag objects from ports to all linked ports in the graph. Output Tag objects on output ports are propagated to linked input ports and input Tag objects on input ports are propagated to linked output ports. If a port has links with multiple other ports, the Tag objects are merged by appending the dictionaties to the Tag param. Intermediate nodes or sinks are allowed to take the multiple dictionaries in a Tag and combine them into one dictionary if they would like to do so. This way, Output Tag always describes the aggragated total upstream metadata of signal up to the port and Input tag describes the aggregated downstream metadata of the signal from the port. # Tag updates Tag params on the ports can be updated as needed. This can happen because some upstream or downstream Tag changed or when the metadata of a node/port/stream changes. */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/doc/dox/modules.dox000066400000000000000000000063441511204443500242300ustar00rootroot00000000000000/** \page page_modules Modules A PipeWire module is effectively a PipeWire client in an `.so` file that shares the \ref pw_context with the loading entity. Usually modules are loaded when they are listed in the configuration files. For example the default configuration file loads several modules: ``` context.modules = [ ... # The native communication protocol. { name = libpipewire-module-protocol-native } # The profile module. Allows application to access profiler # and performance data. It provides an interface that is used # by pw-top and pw-profiler. { name = libpipewire-module-profiler } # Allows applications to create metadata objects. It creates # a factory for Metadata objects. { name = libpipewire-module-metadata } # Creates a factory for making devices that run in the # context of the PipeWire server. { name = libpipewire-module-spa-device-factory } ... ] ``` The matching libraries are: ``` $ ls -1 /usr/lib64/pipewire-0.3/libpipewire-module* ... /usr/lib64/pipewire-0.3/libpipewire-module-metadata.so /usr/lib64/pipewire-0.3/libpipewire-module-profiler.so /usr/lib64/pipewire-0.3/libpipewire-module-protocol-native.so /usr/lib64/pipewire-0.3/libpipewire-module-spa-device-factory.so ... ``` A module's entry point is the `pipewire__module_init` function, see \ref PIPEWIRE_SYMBOL_MODULE_INIT. \code int pipewire__module_init(struct pw_impl_module *module, const char *args).` \endcode See the \ref page_module_example_sink and \ref page_module_example_source modules for a general oveview of how modules look like. List of known modules: - \subpage page_module_access - \subpage page_module_adapter - \subpage page_module_avb - \subpage page_module_client_device - \subpage page_module_client_node - \subpage page_module_combine_stream - \subpage page_module_echo_cancel - \subpage page_module_example_filter - \subpage page_module_example_sink - \subpage page_module_example_source - \subpage page_module_fallback_sink - \subpage page_module_ffado_driver - \subpage page_module_filter_chain - \subpage page_module_jackdbus_detect - \subpage page_module_jack_tunnel - \subpage page_module_link_factory - \subpage page_module_loopback - \subpage page_module_metadata - \subpage page_module_netjack2_driver - \subpage page_module_netjack2_manager - \subpage page_module_parametric_equalizer - \subpage page_module_pipe_tunnel - \subpage page_module_portal - \subpage page_module_profiler - \subpage page_module_protocol_native - \subpage page_module_protocol_pulse - \subpage page_module_protocol_simple - \subpage page_module_pulse_tunnel - \subpage page_module_raop_sink - \subpage page_module_raop_discover - \subpage page_module_roc_sink - \subpage page_module_roc_source - \subpage page_module_rtp_sap - \subpage page_module_rtp_sink - \subpage page_module_rtp_source - \subpage page_module_rtp_session - \subpage page_module_rt - \subpage page_module_spa_node - \subpage page_module_spa_node_factory - \subpage page_module_spa_device - \subpage page_module_spa_device_factory - \subpage page_module_session_manager - \subpage page_module_snapcast_discover - \subpage page_module_vban_recv - \subpage page_module_vban_send - \subpage page_module_x11_bell - \subpage page_module_zeroconf_discover */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/doc/dox/overview.dox000066400000000000000000000176031511204443500244260ustar00rootroot00000000000000/** \page page_overview Overview # Concepts ## The PipeWire Server PipeWire is a graph-based processing framework, that focuses on handling multimedia data (audio, video and MIDI mainly). A PipeWire graph is composed of nodes. Each node takes an arbitrary number of inputs called ports, does some processing over this multimedia data, and sends data out of its output ports. The edges in the graph are here called links. They are capable of connecting an output port to an input port. Nodes can have an arbitrary number of ports. A node with only output ports is often called a source, and a sink is a node that only possesses input ports. The PipeWire server provides the implementation of some of these nodes itself. Most importantly, it uses alsa-lib like any other ALSA client to expose statically configured ALSA devices as nodes. For example - a stereo ALSA PCM playback device can appear as a sink with two input ports: front-left and front-right or - a virtual ALSA device, to which clients which attempt to use ALSA directly connect, can appear as a source with two output ports: front-left and front right. Similar mechanisms exist to interface with and accommodate applications which use JACK or Pulseaudio. NOTE: `pw-jack` modifies the `LD_LIBRARY_PATH` environment variable so that applications will load PipeWire’s reimplementation of the JACK client libraries instead of JACK’s own libraries. This results in JACK clients being redirected to PipeWire. Other nodes are implemented by PipeWire clients. ## The PipeWire clients PipeWire clients can be any process. They can speak to the PipeWire server through a UNIX domain socket using the PipeWire native protocol. Besides implementing nodes, they may control the graph. ### Graph control The PipeWire server itself does not perform any management of the graph; context-dependent behaviour such as monitoring for new ALSA devices, and configuring them so that they appear as nodes, or linking nodes is not done automatically. It rather provides an API that allows spawning, linking and controlling these nodes. This API is then relied upon by clients to control the graph structure, without having to worry about the graph execution process. A recommended pattern that is often used is a single client be a daemon that deals with the session and policy management. Two implementations are known as of today: - pipewire-media-session, which was the first implementation of a session manager.c Today, it is used mainly in debugging scenarios. - WirePlumber, which takes a modular approach: It provides another, higher-level API compared to the PipeWire one, and runs Lua scripts that implement the management logic using the said API. It ships with default scripts and configuration that handle linking policies as well as monitoring and automatic spawning of ALSA, bluez, libcamera and v4l2 devices. The API is available for any process, not only from WirePlumber’s Lua scripts. ### Node implementation With the nodes which they implement, clients can send multimedia data into the graph or obtain multimedia data from the graph. A client can create multiple PipeWire nodes. That allows one to create more complex applications; a browser would for example be able to create a node per tab that requests the ability to play audio, letting the session manager handle the routing: This allows the user to route different tab sources to different sinks. Another example would be an application that requires many inputs. ## API Semantics The current state of the PipeWire server and its capabilities, and the PipeWire graph are exposed towards clients -- including introspection tools like `pw-dump` -- as a collection of objects, each of which has a specific type. These objects have associated parameters, and properties, methods, events, and permissions. Parameters of an object are data with a specific, well defined meaning, which can be modified and read-out in a controlled fashion through the PipeWire API. They are used to configure the object at run-time. Parameters are the key that allow WirePlumber to negotiate data formats and port configuration with nodes by providing information such as: - Multiple, supported sample rates - Channel count - Positions sample format - Available monitor ports Properties of an object are additional data which have been attached on the behalf of modules and of which the PipeWire server has no native understanding. Certain properties are, by convention, expected for specific object types. Each object type has a list of methods that it needs to implement. The session manager is responsible for defining the list of permissions each client has. Each permission entry is an object ID and four flags. The four flags are: - Read: the object can be seen and events can be received; - Write: the object can be modified, usually through methods (which requires the execute flag) - eXecute: methods can be called; - Metadata: metadata can be set on the object. - Link: any link can be made even to a port that is not visible by the owner of the port. ### Object types The following are the known types and their most important, specialized parameters and methods: #### Core The core is the heart of the PipeWire server. There can only be one core per server and it has the identifier zero. It represents global properties of the server. #### Clients A client object is the representation of an open connection with a client process with the server. #### Modules Modules are dynamic libraries that are loaded at run time in the clients and in the server and do arbitrary things, such as creating devices or provide methods to create links, nodes, etc. Modules in PipeWire can only be loaded in their own process. A client, for example, can not load a module in the server. #### Nodes Nodes are the core data processing entities in PipeWire. They may produce data (capture devices, signal generators, ...), consume data (playback devices, network endpoints, ...) or both (filters). Notes have a method `process`, which eats up data from input ports and provides data for each output port. #### Ports Ports are the entry and exit point of data for a Node. A port can either be used for input or output (but not both). For nodes that work with audio, one type of configuration is whether they have `dsp` ports or a `passthrough` port. In `dsp` mode, there is one port for channel of multichannel audio (so two ports for stereo audio, for example), and data is always in 32-bit floating point format. In `passthrough` mode, there is one port for multichannel data in a format that is negotiated between ports. #### Links Data flows between nodes when there is a Link between their ports. Links may be `"passive"` in which case the existence of the link does not automatically cause data to flow between those nodes (some link in the graph must be `"active"` for the graph to have data flow). #### Devices A device is a handle representing an underlying API, which is then used to create nodes or other devices. Examples of devices are ALSA PCM cards or V4L2 devices. A device has a profile, which allows one to configure them. #### Factories A factory is an object whose sole capability is to create other objects. Once a factory is created, it can only emit the type of object it declared. Those are most often delivered as a module: the module creates the factory and stays alive to keep it accessible for clients. ### Common parameters and methods Every object implement at least the add_listener method, that allows any client to register event listeners. Events are used through the PipeWire API to expose information about an object that might change over time (the state of a node for example). ## Context The PipeWire server and PipeWire clients use the PipeWire API through their respective `pw_context`, the so called PipeWire context. When a PipeWire context is created, it finds and parses a configuration file from the filesystem according to the rules of loading configuration files. */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/doc/dox/programs/000077500000000000000000000000001511204443500236675ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/doc/dox/programs/index.md000066400000000000000000000014241511204443500253210ustar00rootroot00000000000000\page page_programs Programs Manual pages: - \subpage page_man_pipewire_1 - \subpage page_man_pipewire-pulse_1 - \subpage page_man_pw-cat_1 - \subpage page_man_pw-cli_1 - \subpage page_man_pw-config_1 - \subpage page_man_pw-container_1 - \subpage page_man_pw-dot_1 - \subpage page_man_pw-dump_1 - \subpage page_man_pw-jack_1 - \subpage page_man_pw-link_1 - \subpage page_man_pw-loopback_1 - \subpage page_man_pw-metadata_1 - \subpage page_man_pw-mididump_1 - \subpage page_man_pw-mon_1 - \subpage page_man_pw-profiler_1 - \subpage page_man_pw-reserve_1 - \subpage page_man_pw-top_1 - \subpage page_man_pw-v4l2_1 - \subpage page_man_spa-acp-tool_1 - \subpage page_man_spa-inspect_1 - \subpage page_man_spa-json-dump_1 - \subpage page_man_spa-monitor_1 - \subpage page_man_spa-resample_1 pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/doc/dox/programs/pipewire-pulse.1.md000066400000000000000000000026461511204443500273320ustar00rootroot00000000000000\page page_man_pipewire-pulse_1 pipewire-pulse The PipeWire PulseAudio replacement # SYNOPSIS **pipewire-pulse** \[*options*\] # DESCRIPTION **pipewire-pulse** starts a PulseAudio-compatible daemon that integrates with the PipeWire media server, by running a pipewire process through a systemd service. This daemon is a drop-in replacement for the PulseAudio daemon. # OPTIONS \par -h | \--help Show help. \par -v | \--verbose Increase the verbosity by one level. This option may be specified multiple times. \par \--version Show version information. \par -c | \--config=FILE Load the given config file (Default: pipewire-pulse.conf). # ENVIRONMENT VARIABLES The generic \ref pipewire-env "pipewire(1) environment variables" are supported. In addition: @PAR@ pulse-env PULSE_RUNTIME_PATH @PAR@ pulse-env XDG_RUNTIME_DIR Directory where to create the native protocol pulseaudio socket. @PAR@ pulse-env PULSE_LATENCY_MSEC Extra buffering latency in milliseconds. This controls buffering logic in `libpulse` and may be set for PulseAudio client applications to adjust their buffering. (Setting it on the `pipewire-pulse` server has no effect.) # AUTHORS The PipeWire Developers <$(PACKAGE_BUGREPORT)>; PipeWire is available from <$(PACKAGE_URL)> # SEE ALSO \ref page_man_pipewire-pulse_conf_5 "pipewire-pulse.conf(5)", \ref page_man_pipewire_1 "pipewire(1)", \ref page_man_pipewire-pulse-modules_7 "pipewire-pulse-modules(7)" pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/doc/dox/programs/pipewire.1.md000066400000000000000000000160721511204443500262020ustar00rootroot00000000000000\page page_man_pipewire_1 pipewire The PipeWire media server \tableofcontents # SYNOPSIS **pipewire** \[*options*\] # DESCRIPTION PipeWire is a service that facilitates sharing of multimedia content between devices and applications. The **pipewire** daemon reads a config file that is further documented in \ref page_man_pipewire_conf_5 "pipewire.conf(5)" manual page. # OPTIONS \par -h | \--help Show help. \par -v | \--verbose Increase the verbosity by one level. This option may be specified multiple times. \par \--version Show version information. \par -c | \--config=FILE Load the given config file (Default: pipewire.conf). \par -P | \--properties=PROPS Add the given properties as a SPA JSON object to the context. # RUNTIME SETTINGS @IDX@ pipewire A PipeWire daemon will also expose a settings metadata object that can be used to change some settings at runtime. Normally these settings can bypass any of the restrictions listed in the config options above, such as quantum and samplerate values. The settings can be modified using \ref page_man_pw-metadata_1 "pw-metadata(1)": ``` pw-metadata -n settings # list settings pw-metadata -n settings 0 # list server settings pw-metadata -n settings 0 log.level 2 # modify a server setting ``` @PAR@ pipewire-settings log.level = INTEGER Change the log level of the PipeWire daemon. @PAR@ pipewire-settings clock.rate = INTEGER The default samplerate. @PAR@ pipewire-settings clock.allowed-rates = [ RATE1 RATE2... ] The allowed samplerates. @PAR@ pipewire-settings clock.force-rate = INTEGER \parblock Temporarily forces the graph to operate in a fixed sample rate. Both DSP processing and devices will switch to the new rate immediately. Running streams (PulseAudio, native and ALSA applications) will automatically resample to match the new rate. Set the value to 0 to allow the sample rate to vary again. \endparblock @PAR@ pipewire-settings clock.quantum = INTEGER The default quantum (buffer size). @PAR@ pipewire-settings clock.min-quantum = INTEGER Smallest quantum to be used. @PAR@ pipewire-settings clock.max-quantum = INTEGER Largest quantum to be used. @PAR@ pipewire-settings clock.force-quantum = INTEGER \parblock Temporarily force the graph to operate in a fixed quantum. Set the value to 0 to allow the quantum to vary again. \endparblock # ENVIRONMENT VARIABLES @IDX@ pipewire-env ## Socket directories @PAR@ pipewire-env PIPEWIRE_RUNTIME_DIR @PAR@ pipewire-env XDG_RUNTIME_DIR @PAR@ pipewire-env USERPROFILE Used to find the PipeWire socket on the server (and native clients). @PAR@ pipewire-env PIPEWIRE_CORE Name of the socket to make. @PAR@ pipewire-env PIPEWIRE_REMOTE Name of the socket to connect to. @PAR@ pipewire-env PIPEWIRE_DAEMON If set to true then the process becomes a new PipeWire server. ## Config directories, config file name and prefix @PAR@ pipewire-env PIPEWIRE_CONFIG_DIR @PAR@ pipewire-env XDG_CONFIG_HOME @PAR@ pipewire-env HOME Used to find the config file directories. @PAR@ pipewire-env PIPEWIRE_CONFIG_PREFIX @PAR@ pipewire-env PIPEWIRE_CONFIG_NAME Used to override the application provided config prefix and config name. @PAR@ pipewire-env PIPEWIRE_NO_CONFIG Enables (false) or disables (true) overriding on the default configuration. ## Context information As part of a client context, the following information is collected from environment variables and placed in the context properties: @PAR@ pipewire-env LANG The current language in `application.language`. @PAR@ pipewire-env XDG_SESSION_ID Set as the `application.process.session-id` property. @PAR@ pipewire-env DISPLAY Is set as the `window.x11.display` property. ## Modules @PAR@ pipewire-env PIPEWIRE_MODULE_DIR Sets the directory where to find PipeWire modules. @PAR@ pipewire-env SPA_SUPPORT_LIB The name of the SPA support lib to load. This can be used to switch to an alternative support library, for example, to run on the EVL realtime kernel. ## Logging options @PAR@ pipewire-env JOURNAL_STREAM Is used to parse the stream used for the journal. This is usually configured by systemd. @PAR@ pipewire-env PIPEWIRE_LOG_LINE Enables the logging of line numbers. Default true. @PAR@ pipewire-env PIPEWIRE_LOG_TIMESTAMP Logging timestamp type: "local", "monotonic", "realtime", "none". Default "local". @PAR@ pipewire-env PIPEWIRE_LOG Specifies a log file to use instead of the default logger. @PAR@ pipewire-env PIPEWIRE_LOG_SYSTEMD Enables the use of systemd for the logger, default true. ## Other settings @PAR@ pipewire-env PIPEWIRE_CPU Selects the CPU and flags. This is a bitmask of any of the \ref CPU flags @PAR@ pipewire-env PIPEWIRE_VM Selects the Virtual Machine PipeWire is running on. This can be any of the \ref CPU "VM" types. @PAR@ pipewire-env DISABLE_RTKIT Disables the use of RTKit or the Realtime Portal for realtime scheduling. @PAR@ pipewire-env NO_COLOR Disables the use of colors in the console output. ## Debugging options @PAR@ pipewire-env PIPEWIRE_DLCLOSE Enables (true) or disables (false) the use of dlclose when a shared library is no longer in use. When debugging, it might make sense to disable dlclose to be able to get debugging symbols from the object. ## Stream options @PAR@ pipewire-env PIPEWIRE_NODE Makes a stream connect to a specific `object.serial` or `node.name`. @PAR@ pipewire-env PIPEWIRE_PROPS Adds extra properties to a stream or filter. @PAR@ pipewire-env PIPEWIRE_QUANTUM Forces a specific rate and buffer-size for the stream or filter. @PAR@ pipewire-env PIPEWIRE_LATENCY Sets a specific latency for a stream or filter. This is only a suggestion but the configured latency will not be larger. @PAR@ pipewire-env PIPEWIRE_RATE Sets a rate for a stream or filter. This is only a suggestion. The rate will be switched when the graph is idle. @PAR@ pipewire-env PIPEWIRE_AUTOCONNECT Overrides the default stream autoconnect settings. ## Plugin options @PAR@ pipewire-env SPA_PLUGIN_DIR Is used to locate SPA plugins. @PAR@ pipewire-env SPA_DATA_DIR Is used to locate plugin specific config files. This is used by the bluetooth plugin currently to locate the quirks database. @PAR@ pipewire-env SPA_DEBUG Set the log level for SPA plugins. This is usually controlled by the `PIPEWIRE_DEBUG` variable when the plugins are managed by PipeWire but some standalone tools (like spa-inspect) uses this variable. @PAR@ pipewire-env ACP_BUILDDIR If set, the ACP profiles are loaded from the builddir. @PAR@ pipewire-env ACP_PATHS_DIR @PAR@ pipewire-env ACP_PROFILES_DIR Used to locate the ACP paths and profile directories respectively. @PAR@ pipewire-env LADSPA_PATH Comma separated list of directories where the ladspa plugins can be found. @PAR@ pipewire-env LIBJACK_PATH Directory where the jack1 or jack2 libjack.so can be found. # AUTHORS The PipeWire Developers <$(PACKAGE_BUGREPORT)>; PipeWire is available from <$(PACKAGE_URL)> # SEE ALSO \ref page_man_pw-top_1 "pw-top(1)", \ref page_man_pw-dump_1 "pw-dump(1)", \ref page_man_pw-mon_1 "pw-mon(1)", \ref page_man_pw-cat_1 "pw-cat(1)", \ref page_man_pw-cli_1 "pw-cli(1)", \ref page_man_libpipewire-modules_7 "libpipewire-modules(7)" pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/doc/dox/programs/pw-cat.1.md000066400000000000000000000136571511204443500255570ustar00rootroot00000000000000\page page_man_pw-cat_1 pw-cat Play and record media with PipeWire # SYNOPSIS **pw-cat** \[*options*\] \[*FILE* \| -\] **pw-play** \[*options*\] \[*FILE* \| -\] **pw-record** \[*options*\] \[*FILE* \| -\] **pw-midiplay** \[*options*\] \[*FILE* \| -\] **pw-midirecord** \[*options*\] \[*FILE* \| -\] **pw-midi2play** \[*options*\] \[*FILE* \| -\] **pw-midi2record** \[*options*\] \[*FILE* \| -\] **pw-dsdplay** \[*options*\] \[*FILE* \| -\] # DESCRIPTION **pw-cat** is a simple tool for playing back or capturing raw or encoded media files on a PipeWire server. It understands all audio file formats supported by `libsndfile` for PCM capture and playback. When capturing PCM, the filename extension is used to guess the file format with the WAV file format as the default. It understands standard MIDI files and MIDI 2.0 clip files for playback and recording. This tool will not render MIDI files, it will simply make the MIDI events available to the graph. You need a MIDI renderer such as qsynth, timidity or a hardware MIDI renderer to hear the MIDI. DSD playback is supported with the DSF file format. This tool will only work with native DSD capable hardware and will produce an error when no such hardware was found. When the *FILE* is - input and output will be raw data from STDIN and STDOUT respectively. # OPTIONS \par -h | \--help Show help. \par \--version Show version information. \par -v | \--verbose Verbose operation. \par -R | \--remote=NAME The name the *remote* instance to connect to. If left unspecified, a connection is made to the default PipeWire instance. \par -p | \--playback Playback mode. Read data from the specified file, and play it back. If the tool is called under the name **pw-play**, **pw-midiplay** or **pw-midi2play** this is the default. \par -r | \--record Recording mode. Capture data and write it to the specified file. If the tool is called under the name **pw-record**, **pw-midirecord** or **pw-midi2record** this is the default. \par -m | \--midi MIDI mode. *FILE* is a MIDI file. If the tool is called under the name **pw-midiplay** or **pw-midirecord** this is the default. Note that this program will *not* render the MIDI events into audible samples, it will simply provide the MIDI events in the graph. You need a separate MIDI renderer such as qsynth, timidity or a hardware renderer to hear the MIDI. \par -c | \--midi-clip MIDI 2.0 clip mode. *FILE* is a MIDI 2.0 clip file. If the tool is called under the name **pw-midi2play** or **pw-midi2record** this is the default. Note that this program will *not* render the MIDI events into audible samples, it will simply provide the MIDI events in the graph. You need a separate MIDI renderer such as qsynth, timidity or a hardware renderer to hear the MIDI. \par -d | \--dsd DSD mode. *FILE* is a DSF file. If the tool is called under the name **pw-dsdplay** this is the default. Note that this program will *not* render the DSD audio. You need a DSD capable device to play DSD content or this program will exit with an error. \par \--media-type=VALUE Set the media type property (default Audio/Midi depending on mode). The media type is used by the session manager to select a suitable target to link to. \par \--media-category=VALUE Set the media category property (default Playback/Capture depending on mode). The media type is used by the session manager to select a suitable target to link to. \par \--media-role=VALUE Set the media role property (default Music). The media type is used by the session manager to select a suitable target to link to. \par \--target=VALUE \parblock Set a node target (default auto). The value can be: - **auto**: Automatically select (Default) - **0**: Don't try to link this node - \: The object.serial or the node.name of a target node \endparblock \par \--latency=VALUE\[*units*\] \parblock Set the node latency (default 100ms) The latency determines the minimum amount of time it takes for a sample to travel from application to device (playback) and from device to application (capture). The latency determines the size of the buffers that the application will be able to fill. Lower latency means smaller buffers but higher overhead. Higher latency means larger buffers and lower overhead. Units can be **s** for seconds, **ms** for milliseconds, **us** for microseconds, **ns** for nanoseconds. If no units are given, the latency value is samples with the samplerate of the file. \endparblock \par -P | \--properties=VALUE Set extra stream properties as a JSON object. \par -q | \--quality=VALUE Resampler quality. When the samplerate of the source or destination file does not match the samplerate of the server, the data will be resampled. Higher quality uses more CPU. Values between 0 and 15 are allowed, the default quality is 4. \par \--rate=VALUE The sample rate, default 48000. \par \--channels=VALUE The number of channels, default 2. \par \--channel-map=VALUE The channelmap. Possible values include: **mono**, **stereo**, **surround-21**, **quad**, **surround-22**, **surround-40**, **surround-31**, **surround-41**, **surround-50**, **surround-51**, **surround-51r**, **surround-70**, **surround-71** or a comma separated list of channel names: **FL**, **FR**, **FC**, **LFE**, **SL**, **SR**, **FLC**, **FRC**, **RC**, **RL**, **RR**, **TC**, **TFL**, **TFC**, **TFR**, **TRL**, **TRC**, **TRR**, **RLC**, **RRC**, **FLW**, **FRW**, **LFE2**, **FLH**, **FCH**, **FRH**, **TFLC**, **TFRC**, **TSL**, **TSR**, **LLFR**, **RLFE**, **BC**, **BLC**, **BRC** \par \--format=VALUE The sample format to use. One of: **u8**, **s8**, **s16** (default), **s24**, **s32**, **f32**, **f64**. \par \--volume=VALUE The stream volume, default 1.000. Depending on the locale you have configured, "," or "." may be used as a decimal separator. Check with **locale** command. # AUTHORS The PipeWire Developers <$(PACKAGE_BUGREPORT)>; PipeWire is available from <$(PACKAGE_URL)> # SEE ALSO \ref page_man_pipewire_1 "pipewire(1)", \ref page_man_pw-mon_1 "pw-mon(1)", pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/doc/dox/programs/pw-cli.1.md000066400000000000000000000114211511204443500255420ustar00rootroot00000000000000\page page_man_pw-cli_1 pw-cli The PipeWire Command Line Interface # SYNOPSIS **pw-cli** \[*command*\] # DESCRIPTION Interact with a PipeWire instance. When a command is given, **pw-cli** will execute the command and exit When no command is given, **pw-cli** starts an interactive session with the default PipeWire instance *pipewire-0*. Connections to other, remote instances can be made. The current instance name is displayed at the prompt. Note that **pw-cli** also creates a local PipeWire instance. Some commands operate on the current (remote) instance and some on the local instance, such as module loading. Use the 'help' command to list the available commands. # GENERAL COMMANDS \par help | h Show a quick help on the commands available. It also lists the aliases for many commands. \par quit | q Exit from **pw-cli** \par list-vars List all currently known variables and their type. Some commands create objects that are identified with a variable. # MODULE MANAGEMENT Modules are loaded and unloaded in the local instance, thus the pw-cli binary itself and can add functionality or objects to the local instance. It is not possible in PipeWire to load modules in another instance. \par load-module *name* \[*arguments...*\] \parblock Load a module specified by its name and arguments in the local instance. For most modules it is OK to be loaded more than once. This command returns a module variable that can be used to unload the module. The local module is *not* visible in the remote instance. It is not possible in PipeWire to load modules in a remote instance. \endparblock \par unload-module *module-var* Unload a module, specified by its variable. # OBJECT INTROSPECTION \par list-objects List the objects of the current instance. Objects are listed with their *id*, *type* and *version*. \par info *id* | *all* Get information about a specific object or *all* objects. Requesting info about an object will also notify you of changes. # WORKING WITH REMOTES \par connect \[*remote-name*\] \parblock Connect to a remote instance and make this the new current instance. If no remote name is specified, a connection is made to the default remote instance, usually *pipewire-0*. The special remote name called *internal* can be used to connect to the local **pw-cli** PipeWire instance. This command returns a remote var that can be used to disconnect or switch remotes. \endparblock \par disconnect \[*remote-var*\] \parblock Disconnect from a *remote instance*. If no remote name is specified, the current instance is disconnected. \endparblock \par list-remotes List all *remote instances*. \par switch-remote \[*remote-var*\] \parblock Make the specified *remote* the current instance. If no remote name is specified, the first instance is made current. \endparblock # NODE MANAGEMENT \par create-node *factory-name* \[*properties...*\] \parblock Create a node from a factory in the current instance. Properties are key=value pairs separated by whitespace. This command returns a *node variable*. \endparblock \par export-node *node-id* \[*remote-var*\] Export a node from the local instance to the specified instance. When no instance is specified, the node will be exported to the current instance. # DEVICE MANAGEMENT \par create-device *factory-name* \[*properties...*\] \parblock Create a device from a factory in the current instance. Properties are key=value pairs separated by whitespace. This command returns a *device variable*. \endparblock # LINK MANAGEMENT \par create-link *node-id* *port-id* *node-id* *port-id* \[*properties...*\] \parblock Create a link between 2 nodes and ports. Port *ids* and Node *ids* can be set to `-` to automatically select a node or a port. Port *ids* can be `*` to automatically link matching ports ids in the nodes. Properties are key=value pairs separated by whitespace. This command returns one or more *link variables*. \endparblock # GLOBALS MANAGEMENT \par destroy *object-id* Destroy a global object. # PARAMETER MANAGEMENT \par enum-params *object-id* *param-id* \parblock Enumerate params of an object. *param-id* can also be given as the param short name. \endparblock \par set-param *object-id* *param-id* *param-json* \parblock Set param of an object. *param-id* can also be given as the param short name. \endparblock # PERMISSION MANAGEMENT \par permissions *client-id* *object-id* *permission* \parblock Set permissions for a client. *object-id* can be *-1* to set the default permissions. \endparblock \par get-permissions *client-id* Get permissions of a client. # COMMAND MANAGEMENT \par send-command *object-id* Send a command to an object. # AUTHORS The PipeWire Developers <$(PACKAGE_BUGREPORT)>; PipeWire is available from <$(PACKAGE_URL)> # SEE ALSO \ref page_man_pipewire_1 "pipewire(1)", \ref page_man_pw-mon_1 "pw-mon(1)", pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/doc/dox/programs/pw-config.1.md000066400000000000000000000044311511204443500262430ustar00rootroot00000000000000\page page_man_pw-config_1 pw-config Debug PipeWire Config parsing # SYNOPSIS **pw-config** \[*options*\] paths **pw-config** \[*options*\] list \[*SECTION*\] **pw-config** \[*options*\] merge *SECTION* # DESCRIPTION List config paths and config sections and display the parsed output. This tool can be used to get an overview of the config file that will be parsed by the PipeWire server and clients. # COMMON OPTIONS \par -h | \--help Show help. \par \--version Show version information. \par -n | \--name=NAME Config Name (default 'pipewire.conf') \par -p | \--prefix=PREFIX Config Prefix (default '') \par -L | \--no-newline Omit newlines after values \par -r | \--recurse Reformat config sections recursively \par -N | \--no-colors Disable color output \par -C | \-color\[=WHEN\] whether to enable color support. WHEN is *never*, *always*, or *auto* # LISTING PATHS Specify the paths command. It will display all the config files that will be parsed and in what order. # LISTING CONFIG SECTIONS Specify the list command with an optional *SECTION* to list the configuration fragments used for *SECTION*. Without a *SECTION*, all sections will be listed. Use the -r options to reformat the sections. # MERGING A CONFIG SECTION With the merge option and a *SECTION*, pw-config will merge all config files into a merged config section and dump the results. This will be the section used by the client or server. Use the -r options to reformat the sections. # EXAMPLES \par pw-config List all config files that will be used \par pw-config -n pipewire-pulse.conf List all config files that will be used by the PipeWire pulseaudio server. \par pw-config -n pipewire-pulse.conf list List all config sections used by the PipeWire pulseaudio server \par pw-config -n jack.conf list context.properties List the context.properties fragments used by the JACK clients \par pw-config -n jack.conf merge context.properties List the merged context.properties used by the JACK clients \par pw-config -n pipewire.conf -r merge context.modules List the merged context.modules used by the PipeWire server and reformat # AUTHORS The PipeWire Developers <$(PACKAGE_BUGREPORT)>; PipeWire is available from <$(PACKAGE_URL)> # SEE ALSO \ref page_man_pipewire_1 "pipewire(1)", \ref page_man_pw-dump_1 "pw-dump(1)", pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/doc/dox/programs/pw-container.1.md000066400000000000000000000040501511204443500267550ustar00rootroot00000000000000\page page_man_pw-container_1 pw-container The PipeWire container utility # SYNOPSIS **pw-container** \[*options*\] \[*PROGRAM*\] # DESCRIPTION Run a program in a new security context [1]. **pw-container** will create a new temporary unix socket and uses the SecurityContext extension API to create a server on this socket with the given properties. Clients created from this server socket will have the security properties attached to them. This can be used to simulate the behaviour of Flatpak or other containers. Without any arguments, **pw-container** simply creates the new socket and prints the address on stdout. Other PipeWire programs can then be run with `PIPEWIRE_REMOTE=` to connect through this security context. When *PROGRAM* is given, the `PIPEWIRE_REMOTE` env variable will be set and *PROGRAM* will be passed to system(). Argument to *PROGRAM* need to be properly quoted. # OPTIONS \par -P | \--properties=VALUE Set extra context properties as a JSON object. \par -r | \--remote=NAME The name the *remote* instance to connect to. If left unspecified, a connection is made to the default PipeWire instance. \par -h | \--help Show help. \par \--version Show version information. # EXIT STATUS If the security context was successfully created, **pw-container** does not exit until terminated with a signal. It exits with status 0 if terminated by SIGINT or SIGTERM in this case. Otherwise, it exits with nonzero exit status. # EXAMPLES **pw-container** 'pw-dump i 0' Run pw-dump of the Core object. Note the difference in the object permissions when running pw-dump with and without **pw-container**. **pw-container** 'pw-dump pw-dump' Run pw-dump of itself. Note the difference in the Client security tokens when running pw-dump with and without **pw-container**. # AUTHORS The PipeWire Developers <$(PACKAGE_BUGREPORT)>; PipeWire is available from <$(PACKAGE_URL)> # SEE ALSO [1] https://gitlab.freedesktop.org/wayland/wayland-protocols/-/blob/main/staging/security-context/security-context-v1.xml - Creating a security context pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/doc/dox/programs/pw-dot.1.md000066400000000000000000000021341511204443500255620ustar00rootroot00000000000000\page page_man_pw-dot_1 pw-dot The PipeWire dot graph dump # SYNOPSIS **pw-dot** \[*options*\] # DESCRIPTION Create a .dot file of the PipeWire graph. The .dot file can then be visualized with a tool like **dotty** or rendered to a PNG file with `dot -Tpng pw.dot -o pw.png`. # OPTIONS \par -r | \--remote=NAME The name the remote instance to connect to. If left unspecified, a connection is made to the default PipeWire instance. \par -h | \--help Show help. \par \--version Show version information. \par -a | \--all Show all object types. \par -s | \--smart Show linked objects only. \par -d | \--detail Show all object properties. \par -o FILE | \--output=FILE Output file name (Default pw.dot). Use - for stdout. \par -L | \--lr Lay the graph from left to right, instead of dot's default top to bottom. \par -9 | \--90 Lay the graph using 90-degree angles in edges. # AUTHORS The PipeWire Developers <$(PACKAGE_BUGREPORT)>; PipeWire is available from <$(PACKAGE_URL)> # SEE ALSO \ref page_man_pipewire_1 "pipewire(1)", \ref page_man_pw-cli_1 "pw-cli(1)", \ref page_man_pw-mon_1 "pw-mon(1)", pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/doc/dox/programs/pw-dump.1.md000066400000000000000000000016501511204443500257430ustar00rootroot00000000000000\page page_man_pw-dump_1 pw-dump The PipeWire state dumper # SYNOPSIS **pw-dump** \[*options*\] # DESCRIPTION The *pw-dump* program produces a representation of the current PipeWire state as JSON, including the information on nodes, devices, modules, ports, and other objects. # OPTIONS \par -h | \--help Show help. \par -r | \--remote=NAME The name of the *remote* instance to dump. If left unspecified, a connection is made to the default PipeWire instance. \par -m | \--monitor Monitor PipeWire state changes, and output JSON arrays describing changes. \par -N | \--no-colors Disable color output. \par -C | \--color=WHEN Whether to enable color support. WHEN is `never`, `always`, or `auto`. # AUTHORS The PipeWire Developers <$(PACKAGE_BUGREPORT)>; PipeWire is available from <$(PACKAGE_URL)> # SEE ALSO \ref page_man_pipewire_1 "pipewire(1)", \ref page_man_pw-cli_1 "pw-cli(1)", \ref page_man_pw-top_1 "pw-top(1)", pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/doc/dox/programs/pw-jack.1.md000066400000000000000000000020741511204443500257070ustar00rootroot00000000000000\page page_man_pw-jack_1 pw-jack Use PipeWire instead of JACK # SYNOPSIS **pw-jack** \[*options*\] *COMMAND* \[*ARGUMENTS...*\] # DESCRIPTION **pw-jack** modifies the `LD_LIBRARY_PATH` environment variable so that applications will load PipeWire's reimplementation of the JACK client libraries instead of JACK's own libraries. This results in JACK clients being redirected to PipeWire. If PipeWire's reimplementation of the JACK client libraries has been installed as a system-wide replacement for JACK's own libraries, then the whole system already behaves in that way, in which case **pw-jack** has no practical effect. # OPTIONS \par -h Show help. \par -r NAME The name of the remote instance to connect to. If left unspecified, a connection is made to the default PipeWire instance. \par -v Verbose operation. # EXAMPLES \par pw-jack sndfile-jackplay /usr/share/sounds/freedesktop/stereo/bell.oga # AUTHORS The PipeWire Developers <$(PACKAGE_BUGREPORT)>; PipeWire is available from <$(PACKAGE_URL)> # SEE ALSO \ref page_man_pipewire_1 "pipewire(1)", **jackd(1)**, pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/doc/dox/programs/pw-link.1.md000066400000000000000000000060121511204443500257300ustar00rootroot00000000000000\page page_man_pw-link_1 pw-link The PipeWire Link Command # SYNOPSIS **pw-link** \[*options*\] -o|-i|-l|-t \[*out-pattern*\] \[*in-pattern*\] **pw-link** \[*options*\] *output* *input* **pw-link** \[*options*\] -d *output* *input* **pw-link** \[*options*\] -d *link-id* # DESCRIPTION List, create and destroy links between PipeWire ports. # COMMON OPTIONS \par -r | \--remote=NAME The name the *remote* instance to monitor. If left unspecified, a connection is made to the default PipeWire instance. \par -h | \--help Show help. \par \--version Show version information. # LISTING PORTS AND LINKS Specify one of -o, -i or -l to list the matching optional input and output ports and their links. \par -o | \--output List output ports \par -i | \--input List input ports \par -l | \--links List links \par -t | \--latency List port latencies \par -m | \--monitor Monitor links and ports. **pw-link** will not exit but monitor and print new and destroyed ports or links. \par -I | \--id List IDs. Also list the unique link and port ids. \par -v | \--verbose Verbose port properties. Also list the port-object-path and the port-alias. # CONNECTING PORTS Without any list option (-i, -o or -l), the given ports will be linked. Valid port specifications are: *port-id* As obtained with the -I option when listing ports. *node-name:port-name* As obtained when listing ports. *port-object-path* As obtained from the first alternative name for the port when listing them with the -v option. *port-alias* As obtained from the second alternative name for the ports when listing them with the -v option. Extra options when linking can be given: \par -L | \--linger Linger. Will create a link that exists after **pw-link** is destroyed. This is the default behaviour, unless the -m option is given. \par -P | \--passive Passive link. A passive link will keep both nodes it links inactive unless another non-passive link is activating the nodes. You can use this to link a sink to a filter and have them both suspended when nothing else is linked to either of them. \par -p | \--props=PROPS Properties as JSON object. Give extra properties when creaing the link. # DISCONNECTING PORTS When the -d option is given, an existing link between port is destroyed. To disconnect port, a single *link-id*, as obtained when listing links with the -I option, or two port specifications can be given. See the connecting ports section for valid port specifications. \par -d | \--disconnect Disconnect ports # EXAMPLES **pw-link** -iol List all port and their links. **pw-link** -lm List all links and monitor changes until **pw-link** is stopped. **pw-link** paplay:output_FL alsa_output.pci-0000_00_1b.0.analog-stereo:playback_FL Link the given output port to the input port. **pw-link** -lI List links and their Id. **pw-link** -d 89 Destroy the link with id 89. # AUTHORS The PipeWire Developers <$(PACKAGE_BUGREPORT)>; PipeWire is available from <$(PACKAGE_URL)> # SEE ALSO \ref page_man_pipewire_1 "pipewire(1)", \ref page_man_pw-cli_1 "pw-cli(1)" pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/doc/dox/programs/pw-loopback.1.md000066400000000000000000000027731511204443500265770ustar00rootroot00000000000000\page page_man_pw-loopback_1 pw-loopback PipeWire loopback client # SYNOPSIS **pw-loopback** \[*options*\] # DESCRIPTION The *pw-loopback* program is a PipeWire client that uses the PipeWire loopback module to create loopback nodes, with configuration given via the command-line options. # OPTIONS \par -h | \--help Show help. \par -r | \--remote=NAME The name of the *remote* instance to connect to. If left unspecified, a connection is made to the default PipeWire instance. \par -n | \--name=NAME Name of the loopback node \par -g | \--group=NAME Name of the loopback node group \par -c | \--channels=NUMBER Number of channels to provide \par -m | \--channel-map=MAP Channel map (default `[ FL, FR ]`) \par -l | \--latency=LATENCY Desired latency in ms \par -d | \--delay=DELAY Added delay in seconds (floating point allowed) \par -C | \--capture=TARGET Target device to capture from \par -P | \--playback=TARGET Target device to play to \par -i | \--capture-props=PROPS Wanted properties of capture node (in JSON) \par -o | \--playback-props=PROPS Wanted properties of capture node (in JSON) # AUTHORS The PipeWire Developers <$(PACKAGE_BUGREPORT)>; PipeWire is available from <$(PACKAGE_URL)> # SEE ALSO \ref page_man_pipewire_1 "pipewire(1)", \ref page_man_pw-cat_1 "pw-cat(1)", **pactl(1)** Other ways to create loopback nodes are adding the loopback module in the configuration of a PipeWire daemon, or loading the loopback module using Pulseaudio commands (`pactl load-module module-loopback ...`). pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/doc/dox/programs/pw-metadata.1.md000066400000000000000000000032541511204443500265600ustar00rootroot00000000000000\page page_man_pw-metadata_1 pw-metadata The PipeWire metadata # SYNOPSIS **pw-metadata** \[*options*\] \[*id* \[*key* \[*value* \[*type* \] \] \] \] # DESCRIPTION Monitor, set and delete metadata on PipeWire objects. Metadata are key/type/value triplets attached to objects identified by *id*. The metadata is shared between all applications binding to the same metadata object. When an object is destroyed, all its metadata is automatically removed. When no *value* is given, **pw-metadata** will query and log the metadata matching the optional arguments *id* and *key*. Without any arguments, all metadata is displayed. When *value* is given, **pw-metadata** will set the metadata for *id* and *key* to *value* and an optional *type*. # OPTIONS \par -r | \--remote=NAME The name the remote instance to use. If left unspecified, a connection is made to the default PipeWire instance. \par -h | \--help Show help. \par \--version Show version information. \par -l | \--list List available metadata objects \par -m | \--monitor Keeps running and log the changes to the metadata. \par -d | \--delete Delete all metadata for *id* or for the specified *key* of object *id*. Without any option, all metadata is removed. \par -n | \--name Metadata name (Default: "default"). # EXAMPLES **pw-metadata** Show metadata in default name. **pw-metadata** -n settings 0 Display settings. **pw-metadata** -n settings 0 clock.quantum 1024 Change clock.quantum to 1024. # AUTHORS The PipeWire Developers <$(PACKAGE_BUGREPORT)>; PipeWire is available from <$(PACKAGE_URL)> # SEE ALSO \ref page_man_pipewire_1 "pipewire(1)", \ref page_man_pw-mon_1 "pw-mon(1)", \ref page_man_pw-cli_1 "pw-cli(1)", pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/doc/dox/programs/pw-mididump.1.md000066400000000000000000000014301511204443500266020ustar00rootroot00000000000000\page page_man_pw-mididump_1 pw-mididump The PipeWire MIDI dump # SYNOPSIS **pw-mididump** \[*options*\] \[*FILE*\] # DESCRIPTION Dump MIDI messages to stdout. When a MIDI file is given, the events inside the file are printed. When no file is given, **pw-mididump** creates a PipeWire MIDI input stream and will print all MIDI events received on the port to stdout. # OPTIONS \par -r | \--remote=NAME The name the remote instance to monitor. If left unspecified, a connection is made to the default PipeWire instance. \par -h | \--help Show help. \par \--version Show version information. # AUTHORS The PipeWire Developers <$(PACKAGE_BUGREPORT)>; PipeWire is available from <$(PACKAGE_URL)> # SEE ALSO \ref page_man_pipewire_1 "pipewire(1)", \ref page_man_pw-cat_1 "pw-cat(1)" pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/doc/dox/programs/pw-mon.1.md000066400000000000000000000012541511204443500255670ustar00rootroot00000000000000\page page_man_pw-mon_1 pw-mon The PipeWire monitor # SYNOPSIS **pw-mon** \[*options*\] # DESCRIPTION Monitor objects on the PipeWire instance. # OPTIONS \par -r | \--remote=NAME The name the *remote* instance to monitor. If left unspecified, a connection is made to the default PipeWire instance. \par -h | \--help Show help. \par \--version Show version information. \par -N | \--color=WHEN Whether to use color, one of 'never', 'always', or 'auto'. The default is 'auto'. **-N** is equivalent to **--color=never**. # AUTHORS The PipeWire Developers <$(PACKAGE_BUGREPORT)>; PipeWire is available from <$(PACKAGE_URL)> # SEE ALSO \ref page_man_pipewire_1 "pipewire(1)" pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/doc/dox/programs/pw-profiler.1.md000066400000000000000000000021271511204443500266200ustar00rootroot00000000000000\page page_man_pw-profiler_1 pw-profiler The PipeWire profiler # SYNOPSIS **pw-profiler** \[*options*\] # DESCRIPTION Start profiling a PipeWire instance. If the server has the profiler module loaded, this program will connect to it and log the profiler data. Profiler data contains times and durations when processing nodes and devices started and completed. When this program is stopped, a set of **gnuplot** files and a script to generate SVG files from the .plot files is generated, along with a .html file to visualize the profiling results in a browser. This function uses the same data used by *pw-top*. # OPTIONS \par -r | \--remote=NAME The name the remote instance to monitor. If left unspecified, a connection is made to the default PipeWire instance. \par -h | \--help Show help. \par \--version Show version information. \par -o | \--output=FILE Profiler output name (default "profiler.log"). # AUTHORS The PipeWire Developers <$(PACKAGE_BUGREPORT)>; PipeWire is available from <$(PACKAGE_URL)> # SEE ALSO \ref page_man_pipewire_1 "pipewire(1)", \ref page_man_pw-top_1 "pw-top(1)" pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/doc/dox/programs/pw-reserve.1.md000066400000000000000000000041331511204443500264500ustar00rootroot00000000000000\page page_man_pw-reserve_1 pw-reserve The PipeWire device reservation utility # SYNOPSIS **pw-reserve** \[*options*\] # DESCRIPTION Reserves a device using the DBus `org.freedesktop.ReserveDevice1` device reservation scheme [1], waiting until terminated by `SIGINT` or another signal. It can also request other applications to release a device. This can be used to make audio servers such as PipeWire, Pulseaudio, JACK, or other applications that respect the device reservation protocol, to ignore a device, or to release a sound device they are already using so that it can be used by other applications. # OPTIONS \par -r | \--release Request any client currently holding the device to release it, and try to reserve it after that. If this option is not given and the device is already in use, **pw-reserve** will exit with error status. \par -n NAME | \--name=NAME \parblock Name of the device to reserve. By convention, this is - AudioN: for ALSA card number N **pw-reserve** can reserve any device name, however PipeWire does not currently support other values than listed above. \endparblock \par -a NAME | \--appname=NAME Application name to use when reserving the device. \par -p PRIO | \--priority=PRIO Priority to use when reserving the device. \par -m | \--monitor Monitor reservations of a given device, instead of reserving it. \par -h | \--help Show help. \par \--version Show version information. # EXIT STATUS If the device reservation succeeds, **pw-reserve** does not exit until terminated with a signal. It exits with status 0 if terminated by SIGINT or SIGTERM in this case. Otherwise, it exits with nonzero exit status. # EXAMPLES **pw-reserve** -n Audio0 Reserve ALSA card 0, and exit with error if it is already reserved. **pw-reserve** -n Audio0 -r Reserve ALSA card 0, requesting any applications that have reserved the device to release it for us. # AUTHORS The PipeWire Developers <$(PACKAGE_BUGREPORT)>; PipeWire is available from <$(PACKAGE_URL)> # SEE ALSO [1] https://git.0pointer.net/reserve.git/tree/reserve.txt - A simple device reservation scheme with DBus pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/doc/dox/programs/pw-top.1.md000066400000000000000000000140041511204443500255750ustar00rootroot00000000000000\page page_man_pw-top_1 pw-top The PipeWire process viewer # SYNOPSIS **pw-top** \[*options*\] # DESCRIPTION The *pw-top* program provides a dynamic real-time view of the pipewire node and device statistics. A hierarchical view is shown of Driver nodes and follower nodes. The Driver nodes are actively using a timer to schedule dataflow in the followers. The followers of a driver node as shown below their driver with a + sign (or = for async nodes) in a tree-like representation. The columns presented are as follows: \par S \parblock Node status. - E = ERROR - C = CREATING - S = SUSPENDED - I = IDLE - R = RUNNING - t = RUNNING + transport starting - T = RUNNING + transport running \endparblock \par ID The ID of the pipewire node/device, as found in *pw-dump* and *pw-cli* \par QUANT \parblock The current quantum (for drivers) and the suggested quantum for follower nodes. The quantum by itself needs to be divided by the RATE column to calculate the duration of a scheduling period in fractions of a second. For a QUANT of 1024 and a RATE of 48000, the duration of one period in the graph is 1024/48000 or 21.3 milliseconds. Follower nodes can have a 0 QUANT field, which means that the node does not have a suggestion for the quantum and thus uses what the driver selected. The driver will use the lowest quantum of any of the followers. If none of the followers select a quantum, the default quantum in the pipewire configuration file will be used. The QUANT on the drivers usually translates directly into the number of audio samples processed per processing cycle of the graph. See also \endparblock \par RATE \parblock The current rate (for drivers) and the suggested rate for follower nodes. This is the rate at which the *graph* processes data and needs to be combined with the QUANT value to derive the duration of a processing cycle in the graph. Some nodes can have a 0 RATE, which means that they don\'t have any rate suggestion for the graph. Nodes that suggest a rate can make the graph switch rates if the graph is otherwise idle and the new rate is allowed as a possible graph rate (see the pipewire configuration file). The RATE on (audio) driver nodes usually also translates directly to the samplerate used by the device. Although some devices might not be able to operate at the given samplerate, in which case resampling will need to be done. The negotiated samplerate with the device and stream can be found in the FORMAT column. \endparblock \par WAIT \parblock The waiting time of a node is the elapsed time between when the node is ready to start processing and when it actually started processing. For Driver nodes, this is the time between when the node wakes up to start processing the graph and when the driver (and thus also the graph) completes a cycle. The WAIT time for driver is thus the elapsed time processing the graph. For follower nodes, it is the time spent between being woken up (when all dependencies of the node are satisfied) and when processing starts. The WAIT time for follower nodes is thus mostly caused by context switching. A value of \-\-- means that the node was not signaled. A value of +++ means that the node was signaled but not awake. \endparblock \par BUSY \parblock The processing time is started when the node starts processing until it completes and wakes up the next nodes in the graph. A value of \-\-- means that the node was not started. A value of +++ means that the node was started but did not complete. \endparblock \par W/Q \parblock Ratio of WAIT / QUANT. The W/Q time of the driver node is a good measure of the graph load. The running averages of the driver W/Q ratios are used as the DSP load in other (JACK) tools. Values of \-\-- and +++ are copied from the WAIT column. \endparblock \par B/Q \parblock Ratio of BUSY / QUANT This is a good measure of the load of a particular driver or follower node. Values of \-\-- and +++ are copied from the BUSY column. \endparblock \par ERR \parblock Total of Xruns and Errors Xruns for drivers are when the graph did not complete a cycle. This can be because a node in the graph also has an Xrun. It can also be caused when scheduling delays cause a deadline to be missed, causing a hardware Xrun. Xruns for followers are incremented when the node started processing but did not complete before the end of the graph cycle deadline. \endparblock \par FORMAT \parblock The format used by the driver node or the stream. This is the hardware format negotiated with the device or stream. If the stream of driver has a different rate than the graph, resampling will be done. For raw audio formats, the layout is \ \ \. For IEC958 passthrough audio formats, the layout is IEC958 \ \. For DSD formats, the layout is \ \. For Video formats, the layout is \ \x\. \endparblock \par NAME \parblock Name assigned to the device/node, as found in *pw-dump* node.name Names are prefixed by *+*/*=* when they are linked to a driver (entry above with no +/=) \endparblock # COMMANDS The following keys can be used in the interactive mode: \par q Quit \par c Clear the ERR counters. This does *not* clear the counters globally, it will only reset the counters in this instance of *pw-top*. # OPTIONS \par -h | \--help Show help. \par -b | \--batch-mode Run in non-interactive batch mode, similar to top\'s batch mode. \par -n | \--iterations=NUMBER Exit after NUMBER of batch iterations. Only used in batch mode. \par -r | \--remote=NAME The name the *remote* instance to monitor. If left unspecified, a connection is made to the default PipeWire instance. \par -V | \--version Show version information. # AUTHORS The PipeWire Developers <$(PACKAGE_BUGREPORT)>; PipeWire is available from <$(PACKAGE_URL)> # SEE ALSO \ref page_man_pipewire_1 "pipewire(1)", \ref page_man_pw-dump_1 "pw-dump(1)", \ref page_man_pw-cli_1 "pw-cli(1)", \ref page_man_pw-profiler_1 "pw-profiler(1)" pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/doc/dox/programs/pw-v4l2.1.md000066400000000000000000000014531511204443500255660ustar00rootroot00000000000000\page page_man_pw-v4l2_1 pw-v4l2 Use PipeWire instead of V4L2 # SYNOPSIS **pw-v4l2** \[*options*\] *COMMAND* \[*ARGUMENTS...*\] # DESCRIPTION **pw-v4l2** runs a command using a compatibility layer that maps PipeWire video devices to be visible to applications using V4L2. This is implemented by preloading a shared library via LD_PRELOAD, which translates library calls that try to access V4L2 devices. # OPTIONS \par -h Show help. \par -r NAME The name of the remote instance to connect to. If left unspecified, a connection is made to the default PipeWire instance. \par -v Verbose operation. # EXAMPLES **pw-v4l2** v4l2-ctl --list-devices # AUTHORS The PipeWire Developers <$(PACKAGE_BUGREPORT)>; PipeWire is available from <$(PACKAGE_URL)> # SEE ALSO \ref page_man_pipewire_1 "pipewire(1)", pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/doc/dox/programs/spa-acp-tool.1.md000066400000000000000000000030231511204443500266450ustar00rootroot00000000000000\page page_man_spa-acp-tool_1 spa-acp-tool The PipeWire ALSA profile debugging utility # SYNOPSIS **spa-acp-tool** \[*OPTIONS*\] \[*COMMAND*\] # DESCRIPTION Debug tool for exercising the ALSA card profile probing code, without running PipeWire. May be used to debug problems where PipeWire has incorrectly functioning ALSA card profiles. # OPTIONS \par -h | \--help Show help \par -v | \--verbose Increase verbosity by one level \par -c NUMBER | \--card NUMBER Select which card to probe \par -p | \--properties Additional properties to pass to ACP, e.g. `key=value ...`. # COMMANDS \par help | h Show available commands \par quit | q Quit \par card ID | c ID Probe card \par info | i List card info \par list | l List all objects \par list-verbose | lv List all data \par list-profiles [ID] | lpr [ID] List profiles \par set-profile ID | spr ID Activate a profile \par list-ports [ID] | lp [ID] List ports \par set-port ID | sp ID Activate a port \par list-devices [ID] | ld [ID] List available devices \par get-volume ID | gv ID Get volume from device \par set-volume ID VOL | v ID VOL Set volume on device \par inc-volume ID | v+ ID Increase volume on device \par dec-volume ID | v- ID Decrease volume on device \par get-mute ID | gm ID Get mute state from device \par set-mute ID VAL | sm ID VAL Set mute on device \par toggle-mute ID | m ID Toggle mute on device # AUTHORS The PipeWire Developers <$(PACKAGE_BUGREPORT)>; PipeWire is available from <$(PACKAGE_URL)> # SEE ALSO \ref page_man_pipewire_1 "pipewire(1)" pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/doc/dox/programs/spa-inspect.1.md000066400000000000000000000010071511204443500265740ustar00rootroot00000000000000\page page_man_spa-inspect_1 spa-inspect The PipeWire SPA plugin information utility # SYNOPSIS **spa-inspect** *FILE* # DESCRIPTION Displays information about a SPA plugin. Lists the SPA factories contained, and tries to instantiate them. # EXAMPLES **spa-inspect** $(SPA_PLUGINDIR)/bluez5/libspa-codec-bluez5-sbc.so Display information about a plugin. # AUTHORS The PipeWire Developers <$(PACKAGE_BUGREPORT)>; PipeWire is available from <$(PACKAGE_URL)> # SEE ALSO \ref page_man_pipewire_1 "pipewire(1)" pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/doc/dox/programs/spa-json-dump.1.md000066400000000000000000000006361511204443500270520ustar00rootroot00000000000000\page page_man_spa-json-dump_1 spa-json-dump SPA JSON to JSON converter # SYNOPSIS **spa-json** *[FILE]* # DESCRIPTION Reads a SPA JSON file or stdin, and outputs it as standard JSON. # EXAMPLES **spa-json-dump** $(PIPEWIRE_CONFDATADIR)/pipewire.conf # AUTHORS The PipeWire Developers <$(PACKAGE_BUGREPORT)>; PipeWire is available from <$(PACKAGE_URL)> # SEE ALSO \ref page_man_pipewire_1 "pipewire(1)" pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/doc/dox/programs/spa-monitor.1.md000066400000000000000000000007151511204443500266230ustar00rootroot00000000000000\page page_man_spa-monitor_1 spa-monitor The PipeWire SPA device debugging utility # SYNOPSIS **spa-monitor** *FILE* # DESCRIPTION Load a SPA plugin and instantiate a device from it. This is only useful for debugging device plugins. # EXAMPLES **spa-monitor** $(SPA_PLUGINDIR)/jack/libspa-jack.so # AUTHORS The PipeWire Developers <$(PACKAGE_BUGREPORT)>; PipeWire is available from <$(PACKAGE_URL)> # SEE ALSO \ref page_man_pipewire_1 "pipewire(1)" pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/doc/dox/programs/spa-resample.1.md000066400000000000000000000015361511204443500267460ustar00rootroot00000000000000\page page_man_spa-resample_1 spa-resample The PipeWire resampler debugging utility # SYNOPSIS **spa-resample** \[*OPTIONS*\] *INFILE* *OUTFILE* # DESCRIPTION Use the PipeWire resampler to resample input file to output file, following the given options. This is useful only for testing the resampler. # OPTIONS \par -r RATE | \--rate=RATE Output sample rate. \par -f FORMAT | \--format=FORMAT Output sample format (s8 | s16 | s32 | f32 | f64). \par -q QUALITY | \--quality=QUALITY Resampler output quality (0-14). \par -c FLAGS | \--cpuflags=FLAGS See \ref spa_cpu "spa/support/cpu.h". \par -h Show help. \par -v Verbose operation. # EXAMPLES **spa-resample** -r 48000 -f s32 in.wav out.wav # AUTHORS The PipeWire Developers <$(PACKAGE_BUGREPORT)>; PipeWire is available from <$(PACKAGE_URL)> # SEE ALSO \ref page_man_pipewire_1 "pipewire(1)" pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/doc/dox/pulse-modules.dox000066400000000000000000000030111511204443500253420ustar00rootroot00000000000000/** \page page_pulse_modules Pulseaudio Modules \include{doc} pulse-modules.inc # List of built-in modules: - \subpage page_pulse_module_alsa_sink - \subpage page_pulse_module_alsa_source - \subpage page_pulse_module_always_sink - \subpage page_pulse_module_combine_sink - \subpage page_pulse_module_device_manager - \subpage page_pulse_module_device_restore - \subpage page_pulse_module_echo_cancel - \subpage page_pulse_module_gsettings - \subpage page_pulse_module_jackdbus_detect - \subpage page_pulse_module_ladspa_sink - \subpage page_pulse_module_ladspa_source - \subpage page_pulse_module_loopback - \subpage page_pulse_module_native_protocol_tcp - \subpage page_pulse_module_null_sink - \subpage page_pulse_module_pipe_sink - \subpage page_pulse_module_pipe_source - \subpage page_pulse_module_raop_discover - \subpage page_pulse_module_remap_sink - \subpage page_pulse_module_remap_source - \subpage page_pulse_module_roc_sink - \subpage page_pulse_module_roc_sink_input - \subpage page_pulse_module_roc_source - \subpage page_pulse_module_rtp_recv - \subpage page_pulse_module_rtp_send - \subpage page_pulse_module_simple_protocol_tcp - \subpage page_pulse_module_stream_restore - \subpage page_pulse_module_switch_on_connect - \subpage page_pulse_module_tunnel_sink - \subpage page_pulse_module_tunnel_source - \subpage page_pulse_module_virtual_sink - \subpage page_pulse_module_virtual_source - \subpage page_pulse_module_x11_bell - \subpage page_pulse_module_zeroconf_discover - \subpage page_pulse_module_zeroconf_publish */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/doc/dox/pulse-modules.inc000066400000000000000000000110211511204443500253210ustar00rootroot00000000000000PipeWire's Pulseaudio emulation implements several Pulseaudio modules. It only supports its own built-in modules, and cannot load external modules written for Pulseaudio. # Loading modules The built-in modules can be loaded using Pulseaudio client programs, for example `pactl load-module `. They can also added to `pipewire-pulse.conf`, typically by a drop-in file in `~/.config/pipewire/pipewire-pulse.conf.d/` containing the module name and its arguments ``` # ~/.config/pipewire/pipewire-pulse.conf.d/custom.conf pulse.cmd = [ { cmd = "load-module" args = "module-null-sink sink_name=foo" flags = [ ] } ] ``` To list all modules currently loaded, with their arguments: ``` pactl list modules ``` For a short list of loaded modules: ``` pactl list modules short ``` Modules may be unloaded using either the module-name or index number: ``` pactl load-module pactl unload-module ``` # Common module options Most modules that create streams/devices support the following properties: ## sink_name, source_name Name for the sink (resp. source). Allowed characters in the name are a-z, A-Z, numbers, period (.) and underscore (_). The length must be 1-128 characters. ## format The sample format. The supported audio formats are: ### PCM - u8: unsigned 8-bit integer - aLaw: A-law encoded 8-bit integer - uLaw: μ-law encoded 8-bit integer - s16le: signed 16-bit little-endian integer - s16be: signed 16-bit big-endian integer - s16, s16ne: native-endian aliases for s16le or s16be - s16re: reverse-endian alias for s16le or s16be - float32le: 32-bit little-endian float - float32be: 32-bit big-endian float - float32, float32ne: native-endian aliases for float32le or float32be - float32re: reverse-endian alias for float32le or float32be - s32le: signed 32-bit little-endian integer - s32be: signed 32-bit big-endian integer - s32, s32ne: native-endian aliases for s32le or s32be - s32re: reverse-endian alias for s32le or s32be - s24le: signed 24-bit little-endian integer (note: ALSA calls this "S24_3LE") - s24be: signed 24-bit big-endian integer (note: ALSA calls this "S24_3BE") - s24, s24ne: native-endian aliases for s24le or s24be - s24re: reverse-endian alias for s24le or s24be - s24-32le: signed 24-bit little-endian integer, packed into a 32-bit integer so that the 8 most significant bits are ignored (note: ALSA calls this "S24_LE") - s24-32be: signed 24-bit big-endian integer, packed into a 32-bit integer so that the 8 most significant bits are ignored (note: ALSA calls this "S24_BE") - s24-32, s24-32ne: native-endian aliases for s24-32le or s24-32be - s24-32re: reverse-endian alias for s24-32le or s24-32be ### Compressed audio formats Below is a list of all supported compressed formats. The code at the beginning of each line is used whenever a textual identifier for a format is needed (for example in configuration files or on the command line). The formats whose identifier ends with -iec61937 have to be wrapped in IEC 61937 frames, which makes the compressed audio behave more like normal PCM audio. - ac3-iec61937: Dolby Digital (DD / AC-3 / A/52) - eac3-iec61937: Dolby Digital Plus (DD+ / E-AC-3) - mpeg-iec61937: MPEG-1 or MPEG-2 Part 3 (not MPEG-2 AAC) - dts-iec61937: DTS - mpeg2-aac-iec61937: MPEG-2 AAC (supported since PulseAudio 4.0) - truehd-iec61937: Dolby TrueHD (added in PulseAudio 13.0, but doesn't work yet in practice) - dtshd-iec61937: DTS-HD Master Audio (added in PulseAudio 13.0, but doesn't work yet in practice) - pcm: PCM (not a compressed format, but listed here, because pcm is one of the recognized encoding identifiers) - any: (special identifier for indicating that any encoding can be used) ## rate The sample rate. ##channels Number of audio channels. ## channel_map A channel map. A list of comma-separated channel names. The currently defined channel names are: `left`, `right`, `mono`, `center`, `front-left`, `front-right`, `front-center`, `rear-center`, `rear-left`, `rear-right`, `lfe`, `subwoofer`, `front-left-of-center`, `front-right-of-center`, `side-left`, `side-right`, `aux0`, `aux1` to `aux15`, `top-center`, `top-front-left`, `top-front-right`, `top-front-center`, `top-rear-left`, `top-rear-right`, `top-rear-center` ## sink_properties, source_properties Set additional properties of the sink/source. For example, you can set the description directly when the module is loaded by setting this parameter. ``` load-module module-alsa-sink sink_name=headphones sink_properties=device.description=Headphones ``` pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/doc/dox/tutorial/000077500000000000000000000000001511204443500237005ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/doc/dox/tutorial/index.dox000066400000000000000000000007661511204443500255340ustar00rootroot00000000000000/** \page page_tutorial API Tutorial Welcome to the PipeWire API tutorial. The goal is to learn PipeWire API step-by-step with simple short examples. - \subpage page_tutorial1 - \subpage page_tutorial2 - \subpage page_tutorial3 - \subpage page_tutorial4 - \subpage page_tutorial5 - \subpage page_tutorial6 - \subpage page_tutorial7 # More Example Programs - \ref audio-src.c "": \snippet{doc} audio-src.c title - \ref video-play.c "": \snippet{doc} video-play.c title - \subpage page_examples */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/doc/dox/tutorial/tutorial1.dox000066400000000000000000000023741511204443500263460ustar00rootroot00000000000000/** \page page_tutorial1 Tutorial - Part 1: Getting Started \ref page_tutorial "Index" | \ref page_tutorial2 In this tutorial we show the basics of a simple PipeWire application. Use this tutorial to get started and help you set up your development environment. # Initialization Let get started with the simplest application. \snippet tutorial1.c code Before you can use any PipeWire functions, you need to call `pw_init()`. # Compilation PipeWire provides a pkg-config file named `libpipewire-0.3` (note: the version suffix may change with future releases of PipeWire). To compile the simple test application, copy it into a test1.c file and use pkg-config to provide the required dependencies: gcc -Wall test1.c -o test1 $(pkg-config --cflags --libs libpipewire-0.3) then run it with: # ./test1 Compiled with libpipewire 0.3.5 Linked with libpipewire 0.3.5 # Use your build system's pkg-config support to integrate it into your project. For example, a minimal [meson.build](https://mesonbuild.com/) entry would look like this: project('test1', ['c']) pipewire_dep = dependency('libpipewire-0.3') executable('test1', 'test1.c', dependencies: [pipewire_dep]) \ref page_tutorial "Index" | \ref page_tutorial2 */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/doc/dox/tutorial/tutorial2.dox000066400000000000000000000103511511204443500263410ustar00rootroot00000000000000/** \page page_tutorial2 Tutorial - Part 2: Enumerating Objects \ref page_tutorial1 | \ref page_tutorial "Index" | \ref page_tutorial3 In this tutorial we show how to connect to a PipeWire daemon and enumerate the objects that it has. Let take a look at the following application to start. \snippet tutorial2.c code To compile the simple test application, copy it into a tutorial2.c file and use: gcc -Wall tutorial2.c -o tutorial2 $(pkg-config --cflags --libs libpipewire-0.3) Let's break this down: First we need to initialize the PipeWire library with `pw_init()` as we saw in the previous tutorial. This will load and configure the right modules and setup logging and other tasks. \code{.c} ... pw_init(&argc, &argv); ... \endcode Next we need to create one of the `struct pw_loop` wrappers. PipeWire ships with 2 types of mainloop implementations. We will use the `struct pw_main_loop` implementation, we will see later how we can use the `struct pw_thread_loop` implementation as well. The mainloop is an abstraction of a big poll loop, waiting for events to occur and things to do. Most of the PipeWire work will actually be performed in the context of this loop and so we need to make one first. We then need to make a new context object with the loop. This context object will manage the resources for us and will make it possible for us to connect to a PipeWire daemon: \code{.c} struct pw_main_loop *loop; struct pw_context *context; loop = pw_main_loop_new(NULL /* properties */); context = pw_context_new(pw_main_loop_get_loop(loop), NULL /* properties */, 0 /* user_data size */); \endcode It is possible to give extra properties when making the mainloop or context to tweak its features and functionality. It is also possible to add extra data to the allocated objects for your user data. It will stay alive for as long as the object is alive. We will use this feature later. A real implementation would also need to check if the allocation succeeded and do some error handling, but we leave that out to make the code easier to read. With the context we can now connect to the PipeWire daemon: \code{.c} struct pw_core *core; core = pw_context_connect(context, NULL /* properties */, 0 /* user_data size */); \endcode This creates a socket between the client and the server and makes a proxy object (with ID 0) for the core. Don't forget to check the result here, a NULL value means that the connection failed. At this point we can send messages to the server and receive events. For now we're not going to handle events on this core proxy but we're going to handle them on the registry object. \code{.c} struct pw_registry *registry; struct spa_hook registry_listener; registry = pw_core_get_registry(core, PW_VERSION_REGISTRY, 0 /* user_data size */); spa_zero(registry_listener); pw_registry_add_listener(registry, ®istry_listener, ®istry_events, NULL); \endcode From the core we get the registry proxy object and when we use `pw_registry_add_listener()` to listen for events. We need a small `struct spa_hook` to keep track of the listener and a reference to the `struct pw_registry_events` that contains the events we want to listen to. This is how we define the event handler and the function to handle the events: \code{.c} static const struct pw_registry_events registry_events = { PW_VERSION_REGISTRY_EVENTS, .global = registry_event_global, }; static void registry_event_global(void *data, uint32_t id, uint32_t permissions, const char *type, uint32_t version, const struct spa_dict *props) { printf("object: id:%u type:%s/%d\n", id, type, version); } \endcode Now that everything is set up we can start the mainloop and let the communication between client and server continue: \code{.c} pw_main_loop_run(loop); \endcode Since we don't call `pw_main_loop_quit()` anywhere, this loop will continue forever. In the next tutorial we'll see how we can nicely exit our application after we received all server objects. \ref page_tutorial1 | \ref page_tutorial "Index" | \ref page_tutorial3 */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/doc/dox/tutorial/tutorial3.dox000066400000000000000000000067571511204443500263610ustar00rootroot00000000000000/** \page page_tutorial3 Tutorial - Part 3: Forcing A Roundtrip \ref page_tutorial2 | \ref page_tutorial "Index" | \ref page_tutorial4 In this tutorial we show how to force a roundtrip to the server to make sure an action completed. We'll change our example from \ref page_tutorial2 "Tutorial 2" slightly and add the extra code to implement the roundtrip. Let's take the following small method first: \snippet tutorial3.c roundtrip Let's take a look at what this method does. \code{.c} struct spa_hook core_listener; pw_core_add_listener(core, &core_listener, &core_events, &d); \endcode First of all we add a listener for the events of the core object. We are only interested in the `done` event in this tutorial. This is the event handler: \code{.c} static void on_core_done(void *data, uint32_t id, int seq) { struct roundtrip_data *d = data; if (id == PW_ID_CORE && seq == d->pending) pw_main_loop_quit(d->loop); } \endcode When the done event is received for an object with id `PW_ID_CORE` and a certain sequence number `seq`, this function will call `pw_main_loop_quit()`. Next we do: \code{.c} d.pending = pw_core_sync(core, PW_ID_CORE, 0); \endcode This triggers the `sync` method on the core object with id `PW_ID_CORE` and sequence number 0. Because this is a method on a proxy object, it will be executed asynchronously and the return value will reflect this. PipeWire uses the return values of the underlying SPA (Simple Plugin API) helper objects (See also \ref page_spa_design ). Because all messages on the PipeWire server are handled sequentially, the sync method will be executed after all previous methods are completed. The PipeWire server will emit a `done` event with the same ID and the return value of the original `pw_core_sync()` method in the sequence number. We then run the mainloop to send the messages to the server and receive the events: \code{.c} pw_main_loop_run(loop); \endcode When we get the done event, we can compare it to the sync method and then we know that we did a complete roundtrip and there are no more pending methods on the server. We can quit the mainloop and remove the listener: \code{.c} spa_hook_remove(&core_listener); \endcode If we add this roundtrip method to our code and call it instead of the `pw_main_loop_run()` we will exit the program after all previous methods are finished. This means that the `pw_core_get_registry()` call completed and thus that we also received all events for the globals on the server. \snippet tutorial3.c code To compile the simple test application, copy it into a tutorial3.c file and use: gcc -Wall tutorial3.c -o tutorial3 $(pkg-config --cflags --libs libpipewire-0.3) Now that our program completes, we can take a look at how we can destroy the objects we created. Let's destroy each of them in reverse order that we created them: \code{.c} pw_proxy_destroy((struct pw_proxy*)registry); \endcode The registry is a proxy and can be destroyed with the generic proxy destroy method. After destroying the object, you should not use it anymore. It is an error to destroy an object more than once. We can disconnect from the server with: \code{.c} pw_core_disconnect(core); \endcode This will also destroy the core proxy object and will remove the proxies that might have been created on this connection. We can finally destroy our context and mainloop to conclude this tutorial: \code{.c} pw_context_destroy(context); pw_main_loop_destroy(loop); \endcode \ref page_tutorial2 | \ref page_tutorial "Index" | \ref page_tutorial4 */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/doc/dox/tutorial/tutorial4.dox000066400000000000000000000124411511204443500263450ustar00rootroot00000000000000/** \page page_tutorial4 Tutorial - Part 4: Playing A Tone \ref page_tutorial3 | \ref page_tutorial "Index" | \ref page_tutorial5 In this tutorial we show how to use a stream to play a tone. Let's take a look at the code before we break it down: \snippet tutorial4.c code Save as tutorial4.c and compile with: gcc -Wall tutorial4.c -o tutorial4 -lm $(pkg-config --cflags --libs libpipewire-0.3) We start with the usual boilerplate, `pw_init()` and a `pw_main_loop_new()`. We're going to store our objects in a structure so that we can pass them around in callbacks later. \code{.c} struct data { struct pw_main_loop *loop; struct pw_stream *stream; double accumulator; }; int main(int argc, char *argv[]) { struct data data = { 0, }; pw_init(&argc, &argv); data.loop = pw_main_loop_new(NULL); \endcode Next we create a stream object. It takes the mainloop as first argument and a stream name as the second. Next we provide some properties for the stream and a callback + data. \code{.c} data.stream = pw_stream_new_simple( pw_main_loop_get_loop(data.loop), "audio-src", pw_properties_new( PW_KEY_MEDIA_TYPE, "Audio", PW_KEY_MEDIA_CATEGORY, "Playback", PW_KEY_MEDIA_ROLE, "Music", NULL), &stream_events, &data); \endcode We are using `pw_stream_new_simple()` but there is also a `pw_stream_new()` that takes an existing `struct pw_core` as the first argument and that requires you to add the event handle manually, for more control. The `pw_stream_new_simple()` is, as the name implies, easier to use because it creates a `struct pw_context` and `struct pw_core` automatically. In the properties we need to give as much information about the stream as we can so that the session manager can make good decisions about how and where to route this stream. There are three important properties to configure: - `PW_KEY_MEDIA_TYPE`: The media type; like Audio, Video, MIDI. - `PW_KEY_MEDIA_CATEGORY`: The category; like Playback, Capture, Duplex, Monitor. - `PW_KEY_MEDIA_ROLE`: The media role; like Movie, Music, Camera, Screen, Communication, Game, Notification, DSP, Production, Accessibility, Test. The properties are owned by the stream and freed when the stream is destroyed later. This is the event structure that we use to listen for events: \code{.c} static const struct pw_stream_events stream_events = { PW_VERSION_STREAM_EVENTS, .process = on_process, }; \endcode We are for the moment only interested now in the `process` event. This event is called whenever we need to produce more data. We'll see how that function is implemented but first we need to setup the format of the stream: \code{.c} const struct spa_pod *params[1]; uint8_t buffer[1024]; struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); #define DEFAULT_RATE 44100 #define DEFAULT_CHANNELS 2 params[0] = spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat, &SPA_AUDIO_INFO_RAW_INIT( .format = SPA_AUDIO_FORMAT_S16, .channels = DEFAULT_CHANNELS, .rate = DEFAULT_RATE )); \endcode This is using a `struct spa_pod_builder` to make a `struct spa_pod *` object in the buffer array on the stack. The parameter is of type `SPA_PARAM_EnumFormat` which means that it enumerates the possible formats for this stream. We have only one, a Signed 16 bit stereo format at 44.1KHz. We use `spa_format_audio_raw_build()` which is a helper function to make the param with the builder. See \ref page_spa_pod for more information about how to make these POD objects. Now we're ready to connect the stream and run the main loop: \code{.c} pw_stream_connect(data.stream, PW_DIRECTION_OUTPUT, PW_ID_ANY, PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_MAP_BUFFERS | PW_STREAM_FLAG_RT_PROCESS, params, 1); pw_main_loop_run(data.loop); \endcode To connect we specify that we have a `PW_DIRECTION_OUTPUT` stream. The third argument is always `PW_ID_ANY`. Next we set some flags: - `PW_STREAM_FLAG_AUTOCONNECT`: Automatically connect this stream. This instructs the session manager to link us to some consumer. - `PW_STREAM_FLAG_MAP_BUFFERS`: mmap the buffers for us so we can access the memory. If you don't set these flags you have either work with the fd or mmap yourself. - `PW_STREAM_FLAG_RT_PROCESS`: Run the process function in the realtime thread. Only use this if the process function only uses functions that are realtime safe, this means no allocation or file access or any locking. And last we pass the extra parameters for our stream. Here we only have the allowed formats (`SPA_PARAM_EnumFormat`). Running the mainloop will then start processing and will result in our `process` callback to be called. Let's have a look at that function now. The main program flow of the process function is: - `pw_stream_dequeue_buffer()` to obtain a buffer to write into. - Get pointers in buffer memory to write to. - Write data into buffer. - Adjust buffer with number of written bytes, offset, stride. - `pw_stream_queue_buffer()` to queue the buffer for playback. \snippet tutorial4.c on_process Check out the docs for \ref page_spa_buffer for more information about how to work with buffers. Try to change the number of channels, samplerate or format; the stream will automatically convert to the format on the server. \ref page_tutorial3 | \ref page_tutorial "Index" | \ref page_tutorial5 */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/doc/dox/tutorial/tutorial5.dox000066400000000000000000000162031511204443500263460ustar00rootroot00000000000000/** \page page_tutorial5 Tutorial - Part 5: Capturing Video Frames \ref page_tutorial4 | \ref page_tutorial "Index" | \ref page_tutorial6 In this tutorial we show how to use a stream to capture a stream of video frames. Even though we are now working with a different media type and we are capturing instead of playback, you will see that this example is very similar to \ref page_tutorial4. Let's take a look at the code before we break it down: \snippet tutorial5.c code Save as tutorial5.c and compile with: gcc -Wall tutorial5.c -o tutorial5 -lm $(pkg-config --cflags --libs libpipewire-0.3) Most of the application is structured like \ref page_tutorial4. We create a stream object with different properties to make it a Camera Video Capture stream. \code{.c} props = pw_properties_new(PW_KEY_MEDIA_TYPE, "Video", PW_KEY_MEDIA_CATEGORY, "Capture", PW_KEY_MEDIA_ROLE, "Camera", NULL); if (argc > 1) pw_properties_set(props, PW_KEY_TARGET_OBJECT, argv[1]); data.stream = pw_stream_new_simple( pw_main_loop_get_loop(data.loop), "video-capture", props, &stream_events, &data); \endcode We also optionally allow the user to pass the name of the target node where the session manager is supposed to connect the node. The user may also give the value of the unique target node serial (`PW_KEY_OBJECT_SERIAL`) as the value. In addition to the `process` event, we are also going to listen to a new event, `param_changed`: \code{.c} static const struct pw_stream_events stream_events = { PW_VERSION_STREAM_EVENTS, .param_changed = on_param_changed, .process = on_process, }; \endcode Because we capture a stream of a wide range of different video formats and resolutions, we have to describe our accepted formats in a different way: \code{.c} const struct spa_pod *params[1]; uint8_t buffer[1024]; struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); params[0] = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), SPA_FORMAT_VIDEO_format, SPA_POD_CHOICE_ENUM_Id(7, SPA_VIDEO_FORMAT_RGB, SPA_VIDEO_FORMAT_RGB, SPA_VIDEO_FORMAT_RGBA, SPA_VIDEO_FORMAT_RGBx, SPA_VIDEO_FORMAT_BGRx, SPA_VIDEO_FORMAT_YUY2, SPA_VIDEO_FORMAT_I420), SPA_FORMAT_VIDEO_size, SPA_POD_CHOICE_RANGE_Rectangle( &SPA_RECTANGLE(320, 240), &SPA_RECTANGLE(1, 1), &SPA_RECTANGLE(4096, 4096)), SPA_FORMAT_VIDEO_framerate, SPA_POD_CHOICE_RANGE_Fraction( &SPA_FRACTION(25, 1), &SPA_FRACTION(0, 1), &SPA_FRACTION(1000, 1))); \endcode This is using a `struct spa_pod_builder` to make a `struct spa_pod *` object in the buffer array on the stack. The parameter is of type `SPA_PARAM_EnumFormat` which means that it enumerates the possible formats for this stream. In this example we use the builder to create some `CHOICE` entries for the format properties. We have an enumeration of formats, we need to first give the amount of enumerations that follow, then the default (preferred) value, followed by alternatives in order of preference: \code{.c} SPA_FORMAT_VIDEO_format, SPA_POD_CHOICE_ENUM_Id(7, SPA_VIDEO_FORMAT_RGB, /* default */ SPA_VIDEO_FORMAT_RGB, /* alternative 1 */ SPA_VIDEO_FORMAT_RGBA, /* alternative 2 */ SPA_VIDEO_FORMAT_RGBx, /* .. etc.. */ SPA_VIDEO_FORMAT_BGRx, SPA_VIDEO_FORMAT_YUY2, SPA_VIDEO_FORMAT_I420), \endcode We also have a `RANGE` of values for the size. We need to give a default (preferred) size and then a min and max value: \code{.c} SPA_FORMAT_VIDEO_size, SPA_POD_CHOICE_RANGE_Rectangle( &SPA_RECTANGLE(320, 240), /* default */ &SPA_RECTANGLE(1, 1), /* min */ &SPA_RECTANGLE(4096, 4096)), /* max */ \endcode We have something similar for the framerate. Note that there are other video parameters that we don't specify here. This means that we don't have any restrictions for their values. See \ref page_spa_pod for more information about how to make these POD objects. Now we're ready to connect the stream and run the main loop: \code{.c} pw_stream_connect(data.stream, PW_DIRECTION_INPUT, PW_ID_ANY, PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_MAP_BUFFERS, params, 1); pw_main_loop_run(data.loop); \endcode To connect we specify that we have a `PW_DIRECTION_INPUT` stream. The third argument is always `PW_ID_ANY`. We're setting the `PW_STREAM_FLAG_AUTOCONNECT` flag to make an automatic connection to a suitable camera and `PW_STREAM_FLAG_MAP_BUFFERS` to let the stream mmap the data for us. And last we pass the extra parameters for our stream. Here we only have the allowed formats (`SPA_PARAM_EnumFormat`). Running the mainloop will start the connection and negotiation process. First our `param_changed` event will be called with the format that was negotiated between our stream and the camera. This is always something that is compatible with what we enumerated in the EnumFormat param when we connected. Let's take a look at how we can parse the format in the `param_changed` event: \code{.c} static void on_param_changed(void *userdata, uint32_t id, const struct spa_pod *param) { struct data *data = userdata; if (param == NULL || id != SPA_PARAM_Format) return; \endcode First check if there is a param. A NULL param means that it is cleared. The ID of the param tells you what param it is. We are only interested in Format param (`SPA_PARAM_Format`). We can parse the media type and subtype as below and ensure that it is of the right type. In our example this will always be true but when your EnumFormat contains different media types or subtypes, this is how you can parse them: \code{.c} if (spa_format_parse(param, &data->format.media_type, &data->format.media_subtype) < 0) return; if (data->format.media_type != SPA_MEDIA_TYPE_video || data->format.media_subtype != SPA_MEDIA_SUBTYPE_raw) return; \endcode For the `video/raw` media type/subtype there is a utility function to parse out the values into a `struct spa_video_info`. This makes it easier to deal with. \code{.c} if (spa_format_video_raw_parse(param, &data->format.info.raw) < 0) return; printf("got video format:\n"); printf(" format: %d (%s)\n", data->format.info.raw.format, spa_debug_type_find_name(spa_type_video_format, data->format.info.raw.format)); printf(" size: %dx%d\n", data->format.info.raw.size.width, data->format.info.raw.size.height); printf(" framerate: %d/%d\n", data->format.info.raw.framerate.num, data->format.info.raw.framerate.denom); /** prepare to render video of this size */ } \endcode In this example we dump the video size and parameters but in a real playback or capture application you might want to set up the screen or encoder to deal with the format. After negotiation, the process function is called for each new frame. Check out \ref page_tutorial4 for another example. \snippet tutorial5.c on_process In a real playback application, one would do something with the data, like copy it to the screen or encode it into a file. \ref page_tutorial4 | \ref page_tutorial "Index" | \ref page_tutorial6 */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/doc/dox/tutorial/tutorial6.dox000066400000000000000000000040641511204443500263510ustar00rootroot00000000000000/** \page page_tutorial6 Tutorial - Part 6: Binding Objects \ref page_tutorial5 | \ref page_tutorial "Index" | \ref page_tutorial7 In this tutorial we show how to bind to an object so that we can receive events and call methods on the object. Let take a look at the following application to start. \snippet tutorial6.c code To compile the simple test application, copy it into a tutorial6.c file and use: gcc -Wall tutorial6.c -o tutorial6 $(pkg-config --cflags --libs libpipewire-0.3) Most of this is the same as \ref page_tutorial2 where we simply enumerated all objects on the server. Instead of just printing the object id and some other properties, in this example we also bind to the object. We use the `pw_registry_bind()` method on our registry object like this: \snippet tutorial6.c registry_event_global We bind to the first client object that we see. This gives us a pointer to a `struct pw_proxy` that we can also cast to a `struct pw_client`. On the proxy we can call methods and listen for events. PipeWire will automatically serialize the method calls and events between client and server for us. We can now listen for events by adding a listener. We're going to listen to the info event on the client object that is emitted right after we bind to it or when it changes. This is not very different from the registry listener we added before: \snippet tutorial6.c client_info \code{.c} static void registry_event_global(void *_data, uint32_t id, uint32_t permissions, const char *type, uint32_t version, const struct spa_dict *props) { /* ... */ pw_client_add_listener(data->client, &data->client_listener, &client_events, data); /* ... */ } \endcode We're also quitting the mainloop after we get the info to nicely stop our tutorial application. When we stop the application, don't forget to destroy all proxies that you created. Otherwise, they will be leaked: \code{.c} /* ... */ pw_proxy_destroy((struct pw_proxy *)data.client); /* ... */ return 0; } \endcode \ref page_tutorial5 | \ref page_tutorial "Index" | \ref page_tutorial7 */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/doc/dox/tutorial/tutorial7.dox000066400000000000000000000167171511204443500263620ustar00rootroot00000000000000/** \page page_tutorial7 Tutorial - Part 7: Creating an Audio DSP Filter \ref page_tutorial6 | \ref page_tutorial "Index" In this tutorial we show how to use \ref pw_filter "pw_filter" to create a real-time audio processing filter. This is useful for implementing audio effects, equalizers, analyzers, and other DSP applications. Let's take a look at the code before we break it down: \snippet tutorial7.c code Save as tutorial7.c and compile with: gcc -Wall tutorial7.c -o tutorial7 -lm $(pkg-config --cflags --libs libpipewire-0.3) ## Overview Unlike \ref pw_stream "pw_stream" which is designed for applications that produce or consume audio data, \ref pw_filter "pw_filter" is designed for applications that process existing audio streams. Filters have both input and output ports and operate in the DSP domain using 32-bit floating point samples. ## Setting up the Filter We start with the usual boilerplate and define our data structure: \code{.c} struct data { struct pw_main_loop *loop; struct pw_filter *filter; struct port *in_port; struct port *out_port; }; \endcode The filter object manages both input and output ports. Each port represents an audio channel that can be connected to other applications. ## Creating the Filter \code{.c} data.filter = pw_filter_new_simple( pw_main_loop_get_loop(data.loop), "audio-filter", pw_properties_new( PW_KEY_MEDIA_TYPE, "Audio", PW_KEY_MEDIA_CATEGORY, "Filter", PW_KEY_MEDIA_ROLE, "DSP", NULL), &filter_events, &data); \endcode We use `pw_filter_new_simple()` which automatically manages the core connection for us. The properties are important: - `PW_KEY_MEDIA_TYPE`: "Audio" indicates this is an audio filter - `PW_KEY_MEDIA_CATEGORY`: "Filter" tells the session manager this processes audio - `PW_KEY_MEDIA_ROLE`: "DSP" indicates this is for audio processing ## Adding Ports Next we add input and output ports: \code{.c} data.in_port = pw_filter_add_port(data.filter, PW_DIRECTION_INPUT, PW_FILTER_PORT_FLAG_MAP_BUFFERS, sizeof(struct port), pw_properties_new( PW_KEY_FORMAT_DSP, "32 bit float mono audio", PW_KEY_PORT_NAME, "input", NULL), NULL, 0); data.out_port = pw_filter_add_port(data.filter, PW_DIRECTION_OUTPUT, PW_FILTER_PORT_FLAG_MAP_BUFFERS, sizeof(struct port), pw_properties_new( PW_KEY_FORMAT_DSP, "32 bit float mono audio", PW_KEY_PORT_NAME, "output", NULL), NULL, 0); \endcode Key points about filter ports: - `PW_DIRECTION_INPUT` and `PW_DIRECTION_OUTPUT` specify the port direction - `PW_FILTER_PORT_FLAG_MAP_BUFFERS` allows direct memory access to buffers - `PW_KEY_FORMAT_DSP` indicates this uses 32-bit float DSP format - DSP ports work with normalized floating-point samples (typically -1.0 to 1.0) ## Setting Process Latency \code{.c} params[n_params++] = spa_process_latency_build(&b, SPA_PARAM_ProcessLatency, &SPA_PROCESS_LATENCY_INFO_INIT( .ns = 10 * SPA_NSEC_PER_MSEC )); \endcode This tells PipeWire that our filter adds 10 milliseconds of processing latency. This information helps the audio system maintain proper timing and latency compensation throughout the audio graph. ## Connecting the Filter \code{.c} if (pw_filter_connect(data.filter, PW_FILTER_FLAG_RT_PROCESS, params, n_params) < 0) { fprintf(stderr, "can't connect\n"); return -1; } \endcode The `PW_FILTER_FLAG_RT_PROCESS` flag ensures our process callback runs in the real-time audio thread. This is crucial for low-latency audio processing but means our process function must be real-time safe (no allocations, file I/O, or blocking operations). ## The Process Callback The heart of the filter is the process callback: \snippet tutorial7.c on_process The process function is called for each audio buffer and works as follows: 1. Get the number of samples to process from `position->clock.duration` 2. Get input and output buffer pointers using `pw_filter_get_dsp_buffer()` 3. Process the audio data (here we just copy input to output) 4. The framework handles queueing the processed buffers ### Key Points about DSP Processing: - **Float Format**: DSP buffers use 32-bit float samples, typically normalized to [-1.0, 1.0] - **Real-time Safe**: The process function runs in the audio thread and must be real-time safe - **Buffer Management**: `pw_filter_get_dsp_buffer()` handles the buffer lifecycle automatically - **Sample-accurate**: Processing happens at the audio sample rate with precise timing ## Advanced Usage This example shows a simple passthrough, but you can implement any audio processing: \code{.c} /* Example: Simple volume control */ for (uint32_t i = 0; i < n_samples; i++) { out[i] = in[i] * 0.5f; // Reduce volume by half } /* Example: Simple high-pass filter */ static float last_sample = 0.0f; float alpha = 0.99f; for (uint32_t i = 0; i < n_samples; i++) { out[i] = alpha * (out[i] + in[i] - last_sample); last_sample = in[i]; } \endcode ## Comparison with pw_stream | Feature | pw_stream | pw_filter | |---------|-----------|-----------| | **Use case** | Audio playback/recording | Audio processing/effects | | **Data format** | Various (S16, S32, etc.) | 32-bit float DSP | | **Ports** | Single direction | Input and output | | **Buffer management** | Manual queue/dequeue | Automatic via get_dsp_buffer | | **Typical apps** | Media players, recorders | Equalizers, effects, analyzers | ## Connecting and Linking the Filter ### Manual Linking Options Filters require manual connection by design. You can connect them using: #### Using pw-link command line: \code{.sh} # List output ports (sources) pw-link -o # List input ports (sinks) pw-link -i # List existing connections pw-link -l # Connect a source to filter input pw-link "source_app:output_FL" "audio-filter:input" # Connect filter output to sink pw-link "audio-filter:output" "sink_app:input_FL" \endcode ### Understanding Filter Auto-Connection Behavior **Important**: Unlike audio sources and sinks, filters are **not automatically connected** by WirePlumber. This is by design because filters are meant to be explicitly inserted into audio chains where needed. **Why filters don't auto-connect**: - Filters process existing audio streams rather than generate/consume them - Auto-connecting filters could create unwanted audio processing - Filters typically require specific placement in the audio graph - Manual connection gives users control over when/where effects are applied ### Testing the Filter The filter requires manual connection to test. Here's the recommended workflow: 1. **Start an audio source** (e.g., `pw-play music.wav`) 2. **Run your filter** (`./tutorial7`) 3. **Check available ports**: ```sh # List output ports pw-link -o | grep -E "(pw-play|audio-filter)" # List input ports pw-link -i | grep -E "(audio-filter|playback)" ``` 4. **Connect the audio chain manually**: ```sh # Connect source -> filter -> sink pw-link "pw-play:output_FL" "audio-filter:input" pw-link "audio-filter:output" "alsa_output.pci-0000_00_1f.3.analog-stereo:playback_FL" ``` You should hear the audio pass through your filter. Modify the process function to add effects like volume changes, filtering, or other audio processing. **Alternative: Use a patchbay tool** - **Helvum**: `flatpak install flathub org.pipewire.Helvum` - **qpwgraph**: Available in most Linux distributions - **Carla**: Full-featured audio plugin host These tools provide graphical interfaces for connecting PipeWire nodes and are ideal for experimenting with filter placement. \ref page_tutorial6 | \ref page_tutorial "Index" */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/doc/doxygen-awesome.css000066400000000000000000002256111511204443500250770ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT */ /** Doxygen Awesome https://github.com/jothepro/doxygen-awesome-css Copyright (c) 2021 - 2025 jothepro */ html { /* primary theme color. This will affect the entire websites color scheme: links, arrows, labels, ... */ --primary-color: #1779c4; --primary-dark-color: #335c80; --primary-light-color: #70b1e9; --on-primary-color: #ffffff; --link-color: var(--primary-color); /* page base colors */ --page-background-color: #ffffff; --page-foreground-color: #2f4153; --page-secondary-foreground-color: #6f7e8e; /* color for all separators on the website: hr, borders, ... */ --separator-color: #dedede; /* border radius for all rounded components. Will affect many components, like dropdowns, memitems, codeblocks, ... */ --border-radius-large: 10px; --border-radius-small: 5px; --border-radius-medium: 8px; /* default spacings. Most components reference these values for spacing, to provide uniform spacing on the page. */ --spacing-small: 5px; --spacing-medium: 10px; --spacing-large: 16px; --spacing-xlarge: 20px; /* default box shadow used for raising an element above the normal content. Used in dropdowns, search result, ... */ --box-shadow: 0 2px 8px 0 rgba(0,0,0,.075); --odd-color: rgba(0,0,0,.028); /* font-families. will affect all text on the website * font-family: the normal font for text, headlines, menus * font-family-monospace: used for preformatted text in memtitle, code, fragments */ --font-family: -apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif; --font-family-monospace: ui-monospace,SFMono-Regular,SF Mono,Menlo,Consolas,Liberation Mono,monospace; /* font sizes */ --page-font-size: 15.6px; --navigation-font-size: 14.4px; --toc-font-size: 13.4px; --code-font-size: 14px; /* affects code, fragment */ --title-font-size: 22px; /* content text properties. These only affect the page content, not the navigation or any other ui elements */ --content-line-height: 27px; /* The content is centered and constraint in it's width. To make the content fill the whole page, set the variable to auto.*/ --content-maxwidth: 1050px; --table-line-height: 24px; --toc-sticky-top: var(--spacing-medium); --toc-width: 200px; --toc-max-height: calc(100vh - 2 * var(--spacing-medium) - 85px); /* colors for various content boxes: @warning, @note, @deprecated @bug */ --warning-color: #faf3d8; --warning-color-dark: #f3a600; --warning-color-darker: #5f4204; --note-color: #e4f3ff; --note-color-dark: #1879C4; --note-color-darker: #274a5c; --todo-color: #e4dafd; --todo-color-dark: #5b2bdd; --todo-color-darker: #2a0d72; --deprecated-color: #ecf0f3; --deprecated-color-dark: #5b6269; --deprecated-color-darker: #43454a; --bug-color: #f8d1cc; --bug-color-dark: #b61825; --bug-color-darker: #75070f; --invariant-color: #d8f1e3; --invariant-color-dark: #44b86f; --invariant-color-darker: #265532; /* blockquote colors */ --blockquote-background: #f8f9fa; --blockquote-foreground: #636568; /* table colors */ --tablehead-background: #f1f1f1; --tablehead-foreground: var(--page-foreground-color); /* menu-display: block | none * Visibility of the top navigation on screens >= 768px. On smaller screen the menu is always visible. * `GENERATE_TREEVIEW` MUST be enabled! */ --menu-display: block; --menu-focus-foreground: var(--on-primary-color); --menu-focus-background: var(--primary-color); --menu-selected-background: rgba(0,0,0,.05); --header-background: var(--page-background-color); --header-foreground: var(--page-foreground-color); /* searchbar colors */ --searchbar-background: var(--side-nav-background); --searchbar-foreground: var(--page-foreground-color); /* searchbar size * (`searchbar-width` is only applied on screens >= 768px. * on smaller screens the searchbar will always fill the entire screen width) */ --searchbar-height: 33px; --searchbar-width: 210px; --searchbar-border-radius: var(--searchbar-height); /* code block colors */ --code-background: #f5f5f5; --code-foreground: var(--page-foreground-color); /* fragment colors */ --fragment-background: #F8F9FA; --fragment-foreground: #37474F; --fragment-keyword: #bb6bb2; --fragment-keywordtype: #8258b3; --fragment-keywordflow: #d67c3b; --fragment-token: #438a59; --fragment-comment: #969696; --fragment-link: #5383d6; --fragment-preprocessor: #46aaa5; --fragment-linenumber-color: #797979; --fragment-linenumber-background: #f4f4f5; --fragment-linenumber-border: #e3e5e7; --fragment-lineheight: 20px; /* sidebar navigation (treeview) colors */ --side-nav-background: #fbfbfb; --side-nav-foreground: var(--page-foreground-color); --side-nav-arrow-opacity: 0; --side-nav-arrow-hover-opacity: 0.9; --toc-background: var(--side-nav-background); --toc-foreground: var(--side-nav-foreground); /* height of an item in any tree / collapsible table */ --tree-item-height: 30px; --memname-font-size: var(--code-font-size); --memtitle-font-size: 18px; --webkit-scrollbar-size: 7px; --webkit-scrollbar-padding: 4px; --webkit-scrollbar-color: var(--separator-color); --animation-duration: .12s } @media screen and (max-width: 767px) { html { --page-font-size: 16px; --navigation-font-size: 16px; --toc-font-size: 15px; --code-font-size: 15px; /* affects code, fragment */ --title-font-size: 22px; } } @media (prefers-color-scheme: dark) { html:not(.light-mode) { color-scheme: dark; --primary-color: #1982d2; --primary-dark-color: #86a9c4; --primary-light-color: #4779ac; --box-shadow: 0 2px 8px 0 rgba(0,0,0,.35); --odd-color: rgba(100,100,100,.06); --menu-selected-background: rgba(0,0,0,.4); --page-background-color: #1C1D1F; --page-foreground-color: #d2dbde; --page-secondary-foreground-color: #859399; --separator-color: #38393b; --side-nav-background: #252628; --code-background: #2a2c2f; --tablehead-background: #2a2c2f; --blockquote-background: #222325; --blockquote-foreground: #7e8c92; --warning-color: #3b2e04; --warning-color-dark: #f1b602; --warning-color-darker: #ceb670; --note-color: #163750; --note-color-dark: #1982D2; --note-color-darker: #dcf0fa; --todo-color: #2a2536; --todo-color-dark: #7661b3; --todo-color-darker: #ae9ed6; --deprecated-color: #2e323b; --deprecated-color-dark: #738396; --deprecated-color-darker: #abb0bd; --bug-color: #2e1917; --bug-color-dark: #ad2617; --bug-color-darker: #f5b1aa; --invariant-color: #303a35; --invariant-color-dark: #76ce96; --invariant-color-darker: #cceed5; --fragment-background: #282c34; --fragment-foreground: #dbe4eb; --fragment-keyword: #cc99cd; --fragment-keywordtype: #ab99cd; --fragment-keywordflow: #e08000; --fragment-token: #7ec699; --fragment-comment: #999999; --fragment-link: #98c0e3; --fragment-preprocessor: #65cabe; --fragment-linenumber-color: #cccccc; --fragment-linenumber-background: #35393c; --fragment-linenumber-border: #1f1f1f; } } /* dark mode variables are defined twice, to support both the dark-mode without and with doxygen-awesome-darkmode-toggle.js */ html.dark-mode { color-scheme: dark; --primary-color: #1982d2; --primary-dark-color: #86a9c4; --primary-light-color: #4779ac; --box-shadow: 0 2px 8px 0 rgba(0,0,0,.30); --odd-color: rgba(100,100,100,.06); --menu-selected-background: rgba(0,0,0,.4); --page-background-color: #1C1D1F; --page-foreground-color: #d2dbde; --page-secondary-foreground-color: #859399; --separator-color: #38393b; --side-nav-background: #252628; --code-background: #2a2c2f; --tablehead-background: #2a2c2f; --blockquote-background: #222325; --blockquote-foreground: #7e8c92; --warning-color: #3b2e04; --warning-color-dark: #f1b602; --warning-color-darker: #ceb670; --note-color: #163750; --note-color-dark: #1982D2; --note-color-darker: #dcf0fa; --todo-color: #2a2536; --todo-color-dark: #7661b3; --todo-color-darker: #ae9ed6; --deprecated-color: #2e323b; --deprecated-color-dark: #738396; --deprecated-color-darker: #abb0bd; --bug-color: #2e1917; --bug-color-dark: #ad2617; --bug-color-darker: #f5b1aa; --invariant-color: #303a35; --invariant-color-dark: #76ce96; --invariant-color-darker: #cceed5; --fragment-background: #282c34; --fragment-foreground: #dbe4eb; --fragment-keyword: #cc99cd; --fragment-keywordtype: #ab99cd; --fragment-keywordflow: #e08000; --fragment-token: #7ec699; --fragment-comment: #999999; --fragment-link: #98c0e3; --fragment-preprocessor: #65cabe; --fragment-linenumber-color: #cccccc; --fragment-linenumber-background: #35393c; --fragment-linenumber-border: #1f1f1f; } body { color: var(--page-foreground-color); background-color: var(--page-background-color); font-size: var(--page-font-size); } body, table, div, p, dl, #nav-tree .label, #nav-tree a, .title, .sm-dox a, .sm-dox a:hover, .sm-dox a:focus, #projectname, .SelectItem, #MSearchField, .navpath li.navelem a, .navpath li.navelem a:hover, p.reference, p.definition, div.toc li, div.toc h3, #page-nav ul.page-outline li a { font-family: var(--font-family); } h1, h2, h3, h4, h5 { margin-top: 1em; font-weight: 600; line-height: initial; } p, div, table, dl, p.reference, p.definition { font-size: var(--page-font-size); } p.reference, p.definition { color: var(--page-secondary-foreground-color); } a:link, a:visited, a:hover, a:focus, a:active { color: var(--link-color) !important; font-weight: 500; background: none; } a:hover { text-decoration: underline; } a.anchor { scroll-margin-top: var(--spacing-large); display: block; } /* Title and top navigation */ #top { background: var(--header-background); border-bottom: 1px solid var(--separator-color); position: relative; z-index: 99; } @media screen and (min-width: 768px) { #top { display: flex; flex-wrap: wrap; justify-content: space-between; align-items: center; } } #main-nav { flex-grow: 5; padding: var(--spacing-small) var(--spacing-medium); border-bottom: 0; } #titlearea { width: auto; padding: var(--spacing-medium) var(--spacing-large); background: none; color: var(--header-foreground); border-bottom: none; } @media screen and (max-width: 767px) { #titlearea { padding-bottom: var(--spacing-small); } } #titlearea table tbody tr { height: auto !important; } #projectname { font-size: var(--title-font-size); font-weight: 600; } #projectnumber { font-family: inherit; font-size: 60%; } #projectbrief { font-family: inherit; font-size: 80%; } #projectlogo { vertical-align: middle; } #projectlogo img { max-height: calc(var(--title-font-size) * 2); margin-right: var(--spacing-small); } .sm-dox, .tabs, .tabs2, .tabs3 { background: none; padding: 0; } .tabs, .tabs2, .tabs3 { border-bottom: 1px solid var(--separator-color); margin-bottom: -1px; } .main-menu-btn-icon, .main-menu-btn-icon:before, .main-menu-btn-icon:after { background: var(--page-secondary-foreground-color); } @media screen and (max-width: 767px) { .sm-dox a span.sub-arrow { background: var(--code-background); } #main-menu a.has-submenu span.sub-arrow { color: var(--page-secondary-foreground-color); border-radius: var(--border-radius-medium); } #main-menu a.has-submenu:hover span.sub-arrow { color: var(--page-foreground-color); } } @media screen and (min-width: 768px) { .sm-dox li, .tablist li { display: var(--menu-display); } .sm-dox a span.sub-arrow { top: 15px; right: 10px; box-sizing: content-box; padding: 0; margin: 0; display: inline-block; width: 5px; height: 5px; transform: rotate(45deg); border-width: 0; border-right: 2px solid var(--header-foreground); border-bottom: 2px solid var(--header-foreground); background: none; } .sm-dox a:hover span.sub-arrow { border-color: var(--menu-focus-foreground); background: none; } .sm-dox ul a span.sub-arrow { transform: rotate(-45deg); border-width: 0; border-right: 2px solid var(--header-foreground); border-bottom: 2px solid var(--header-foreground); } .sm-dox ul a:hover span.sub-arrow { border-color: var(--menu-focus-foreground); background: none; } } .sm-dox ul { background: var(--page-background-color); box-shadow: var(--box-shadow); border: 1px solid var(--separator-color); border-radius: var(--border-radius-medium) !important; padding: var(--spacing-small); animation: ease-out 150ms slideInMenu; } @keyframes slideInMenu { from { opacity: 0; transform: translate(0px, -2px); } to { opacity: 1; transform: translate(0px, 0px); } } .sm-dox ul a { color: var(--page-foreground-color) !important; background: none; font-size: var(--navigation-font-size); } .sm-dox>li>ul:after { border-bottom-color: var(--page-background-color) !important; } .sm-dox>li>ul:before { border-bottom-color: var(--separator-color) !important; } .sm-dox ul a:hover, .sm-dox ul a:active, .sm-dox ul a:focus { font-size: var(--navigation-font-size) !important; color: var(--menu-focus-foreground) !important; text-shadow: none; background-color: var(--menu-focus-background); border-radius: var(--border-radius-small) !important; } .sm-dox a, .sm-dox a:focus, .tablist li, .tablist li a, .tablist li.current a { text-shadow: none; background: transparent; background-image: none !important; color: var(--header-foreground) !important; font-weight: normal; font-size: var(--navigation-font-size); border-radius: var(--border-radius-small) !important; } .sm-dox a:focus { outline: auto; } .sm-dox a:hover, .sm-dox a:active, .tablist li a:hover { text-shadow: none; font-weight: normal; background: var(--menu-focus-background); color: var(--menu-focus-foreground) !important; border-radius: var(--border-radius-small) !important; font-size: var(--navigation-font-size); } .tablist li.current { border-radius: var(--border-radius-small); background: var(--menu-selected-background); } .tablist li { margin: var(--spacing-small) 0 var(--spacing-small) var(--spacing-small); } .tablist a { padding: 0 var(--spacing-large); } /* Search box */ #MSearchBox { height: var(--searchbar-height); background: var(--searchbar-background); border-radius: var(--searchbar-border-radius); border: 1px solid var(--separator-color); overflow: hidden; width: var(--searchbar-width); position: relative; box-shadow: none; display: block; margin-top: 0; margin-right: 0; } @media (min-width: 768px) { .sm-dox li { padding: 0; } } /* until Doxygen 1.9.4 */ .left img#MSearchSelect { left: 0; user-select: none; padding-left: 8px; } /* Doxygen 1.9.5 */ .left span#MSearchSelect { left: 0; user-select: none; margin-left: 8px; padding: 0; } .left #MSearchSelect[src$=".png"] { padding-left: 0 } /* Doxygen 1.14.0 */ .search-icon::before { background: none; top: 5px; } .search-icon::after { background: none; top: 12px; } .SelectionMark { user-select: none; } .tabs .left #MSearchSelect { padding-left: 0; } .tabs #MSearchBox { position: absolute; right: var(--spacing-medium); } @media screen and (max-width: 767px) { .tabs #MSearchBox { position: relative; right: 0; margin-left: var(--spacing-medium); margin-top: 0; } } #MSearchSelectWindow, #MSearchResultsWindow { z-index: 9999; } #MSearchBox.MSearchBoxActive { border-color: var(--primary-color); box-shadow: inset 0 0 0 1px var(--primary-color); } #main-menu > li:last-child { margin-right: 0; } @media screen and (max-width: 767px) { #main-menu > li:last-child { height: 50px; } } #MSearchField { font-size: var(--navigation-font-size); height: calc(var(--searchbar-height) - 2px); background: transparent; width: calc(var(--searchbar-width) - 64px); } .MSearchBoxActive #MSearchField { color: var(--searchbar-foreground); } #MSearchSelect { top: calc(calc(var(--searchbar-height) / 2) - 11px); } #MSearchBox span.left, #MSearchBox span.right { background: none; background-image: none; } #MSearchBox span.right { padding-top: calc(calc(var(--searchbar-height) / 2) - 12px); position: absolute; right: var(--spacing-small); } .tabs #MSearchBox span.right { top: calc(calc(var(--searchbar-height) / 2) - 12px); } @keyframes slideInSearchResults { from { opacity: 0; transform: translate(0, 15px); } to { opacity: 1; transform: translate(0, 20px); } } #MSearchResultsWindow { left: auto !important; right: var(--spacing-medium); border-radius: var(--border-radius-large); border: 1px solid var(--separator-color); transform: translate(0, 20px); box-shadow: var(--box-shadow); animation: ease-out 280ms slideInSearchResults; background: var(--page-background-color); } iframe#MSearchResults { margin: 4px; } iframe { color-scheme: normal; } @media (prefers-color-scheme: dark) { html:not(.light-mode) iframe#MSearchResults { filter: invert() hue-rotate(180deg); } } html.dark-mode iframe#MSearchResults { filter: invert() hue-rotate(180deg); } #MSearchResults .SRPage { background-color: transparent; } #MSearchResults .SRPage .SREntry { font-size: 10pt; padding: var(--spacing-small) var(--spacing-medium); } #MSearchSelectWindow { border: 1px solid var(--separator-color); border-radius: var(--border-radius-medium); box-shadow: var(--box-shadow); background: var(--page-background-color); padding-top: var(--spacing-small); padding-bottom: var(--spacing-small); } #MSearchSelectWindow a.SelectItem { font-size: var(--navigation-font-size); line-height: var(--content-line-height); margin: 0 var(--spacing-small); border-radius: var(--border-radius-small); color: var(--page-foreground-color) !important; font-weight: normal; } #MSearchSelectWindow a.SelectItem:hover { background: var(--menu-focus-background); color: var(--menu-focus-foreground) !important; } @media screen and (max-width: 767px) { #MSearchBox { margin-top: var(--spacing-medium); margin-bottom: var(--spacing-medium); width: calc(100vw - 30px); } #main-menu > li:last-child { float: none !important; } #MSearchField { width: calc(100vw - 110px); } @keyframes slideInSearchResultsMobile { from { opacity: 0; transform: translate(0, 15px); } to { opacity: 1; transform: translate(0, 20px); } } #MSearchResultsWindow { left: var(--spacing-medium) !important; right: var(--spacing-medium); overflow: auto; transform: translate(0, 20px); animation: ease-out 280ms slideInSearchResultsMobile; width: auto !important; } /* * Overwrites for fixing the searchbox on mobile in doxygen 1.9.2 */ label.main-menu-btn ~ #searchBoxPos1 { top: 3px !important; right: 6px !important; left: 45px; display: flex; } label.main-menu-btn ~ #searchBoxPos1 > #MSearchBox { margin-top: 0; margin-bottom: 0; flex-grow: 2; float: left; } } /* Tree view */ #side-nav { min-width: 8px; max-width: 50vw; } #nav-tree, #top { border-right: 1px solid var(--separator-color); } @media screen and (max-width: 767px) { #side-nav { display: none; } #doc-content { margin-left: 0 !important; } #top { border-right: none; } } #nav-tree { background: var(--side-nav-background); margin-right: -1px; padding: 0; } #nav-tree .label { font-size: var(--navigation-font-size); line-height: var(--tree-item-height); } #nav-tree span.label a:hover { background: none; } #nav-tree .item { height: var(--tree-item-height); line-height: var(--tree-item-height); overflow: hidden; text-overflow: ellipsis; margin: 0; padding: 0; } #nav-tree-contents { margin: 0; } #main-menu > li:last-child { height: auto; } #nav-tree .item > a:focus { outline: none; } #nav-sync { bottom: var(--spacing-medium); right: var(--spacing-medium) !important; top: auto !important; user-select: none; } div.nav-sync-icon { border: 1px solid var(--separator-color); border-radius: var(--border-radius-medium); background: var(--page-background-color); width: 30px; height: 20px; } div.nav-sync-icon:hover { background: var(--page-background-color); } span.sync-icon-left, div.nav-sync-icon:hover span.sync-icon-left { border-left: 2px solid var(--primary-color); border-top: 2px solid var(--primary-color); top: 5px; left: 6px; } span.sync-icon-right, div.nav-sync-icon:hover span.sync-icon-right { border-right: 2px solid var(--primary-color); border-bottom: 2px solid var(--primary-color); top: 5px; left: initial; right: 6px; } div.nav-sync-icon.active::after, div.nav-sync-icon.active:hover::after { border-top: 2px solid var(--primary-color); top: 9px; left: 6px; width: 19px; } #nav-tree .selected { text-shadow: none; background-image: none; background-color: transparent; position: relative; color: var(--primary-color) !important; font-weight: 500; } #nav-tree .selected::after { content: ""; position: absolute; top: 1px; bottom: 1px; left: 0; width: 4px; border-radius: 0 var(--border-radius-small) var(--border-radius-small) 0; background: var(--primary-color); } #nav-tree a { color: var(--side-nav-foreground) !important; font-weight: normal; } #nav-tree a:focus { outline-style: auto; } #nav-tree .arrow { opacity: var(--side-nav-arrow-opacity); background: none; } #nav-tree span.arrowhead { margin: 0 0 1px 2px; } span.arrowhead { border-color: var(--primary-light-color); } .selected span.arrowhead { border-color: var(--primary-color); } #nav-tree-contents > ul > li:first-child > div > a { opacity: 0; pointer-events: none; } .contents .arrow { color: inherit; cursor: pointer; font-size: 45%; vertical-align: middle; margin-right: 2px; font-family: serif; height: auto; padding-bottom: 4px; } #nav-tree div.item:hover .arrow, #nav-tree a:focus .arrow { opacity: var(--side-nav-arrow-hover-opacity); } #nav-tree .selected a { color: var(--primary-color) !important; font-weight: bolder; font-weight: 600; } .ui-resizable-e { background: none; } .ui-resizable-e:hover { background: var(--separator-color); } /* Contents */ div.header { border-bottom: 1px solid var(--separator-color); background: none; background-image: none; } @media screen and (min-width: 1000px) { #doc-content > div > div.contents, .PageDoc > div.contents { display: flex; flex-direction: row-reverse; flex-wrap: nowrap; align-items: flex-start; } div.contents .textblock { min-width: 200px; flex-grow: 1; } } div.contents, div.header .title, div.header .summary { max-width: var(--content-maxwidth); } div.contents, div.header .title { line-height: initial; margin: calc(var(--spacing-medium) + .2em) auto var(--spacing-medium) auto; } div.header .summary { margin: var(--spacing-medium) auto 0 auto; } div.headertitle { padding: 0; } div.header .title { font-weight: 600; font-size: 225%; padding: var(--spacing-medium) var(--spacing-xlarge); word-break: break-word; } div.header .summary { width: auto; display: block; float: none; padding: 0 var(--spacing-large); } td.memSeparator { border-color: var(--separator-color); } span.mlabel { background: var(--primary-color); color: var(--on-primary-color); border: none; padding: 4px 9px; border-radius: var(--border-radius-large); margin-right: var(--spacing-medium); } span.mlabel:last-of-type { margin-right: 2px; } div.contents { padding: 0 var(--spacing-xlarge); } div.contents p, div.contents li { line-height: var(--content-line-height); } div.contents div.dyncontent { margin: var(--spacing-medium) 0; } @media screen and (max-width: 767px) { div.contents { padding: 0 var(--spacing-large); } div.header .title { padding: var(--spacing-medium) var(--spacing-large); } } @media (prefers-color-scheme: dark) { html:not(.light-mode) div.contents div.dyncontent img, html:not(.light-mode) div.contents center img, html:not(.light-mode) div.contents > table img, html:not(.light-mode) div.contents div.dyncontent iframe, html:not(.light-mode) div.contents center iframe, html:not(.light-mode) div.contents table iframe, html:not(.light-mode) div.contents .dotgraph iframe { filter: brightness(89%) hue-rotate(180deg) invert(); } } html.dark-mode div.contents div.dyncontent img, html.dark-mode div.contents center img, html.dark-mode div.contents > table img, html.dark-mode div.contents div.dyncontent iframe, html.dark-mode div.contents center iframe, html.dark-mode div.contents table iframe, html.dark-mode div.contents .dotgraph iframe { filter: brightness(89%) hue-rotate(180deg) invert(); } td h2.groupheader, h2.groupheader { border-bottom: 0px; color: var(--page-foreground-color); box-shadow: 100px 0 var(--page-background-color), -100px 0 var(--page-background-color), 100px 0.75px var(--separator-color), -100px 0.75px var(--separator-color), 500px 0 var(--page-background-color), -500px 0 var(--page-background-color), 500px 0.75px var(--separator-color), -500px 0.75px var(--separator-color), 900px 0 var(--page-background-color), -900px 0 var(--page-background-color), 900px 0.75px var(--separator-color), -900px 0.75px var(--separator-color), 1400px 0 var(--page-background-color), -1400px 0 var(--page-background-color), 1400px 0.75px var(--separator-color), -1400px 0.75px var(--separator-color), 1900px 0 var(--page-background-color), -1900px 0 var(--page-background-color), 1900px 0.75px var(--separator-color), -1900px 0.75px var(--separator-color); } blockquote { margin: 0 var(--spacing-medium) 0 var(--spacing-medium); padding: var(--spacing-small) var(--spacing-large); background: var(--blockquote-background); color: var(--blockquote-foreground); border-left: 0; overflow: visible; border-radius: var(--border-radius-medium); overflow: visible; position: relative; } blockquote::before, blockquote::after { font-weight: bold; font-family: serif; font-size: 360%; opacity: .15; position: absolute; } blockquote::before { content: "“"; left: -10px; top: 4px; } blockquote::after { content: "”"; right: -8px; bottom: -25px; } blockquote p { margin: var(--spacing-small) 0 var(--spacing-medium) 0; } .paramname, .paramname em { font-weight: 600; color: var(--primary-dark-color); } .paramname > code { border: 0; } table.params .paramname { font-weight: 600; font-family: var(--font-family-monospace); font-size: var(--code-font-size); padding-right: var(--spacing-small); line-height: var(--table-line-height); } h1.glow, h2.glow, h3.glow, h4.glow, h5.glow, h6.glow { text-shadow: 0 0 15px var(--primary-light-color); } .alphachar a { color: var(--page-foreground-color); } .dotgraph { max-width: 100%; overflow-x: scroll; } .dotgraph .caption { position: sticky; left: 0; } /* Wrap Graphviz graphs with the `interactive_dotgraph` class if `INTERACTIVE_SVG = YES` */ .interactive_dotgraph .dotgraph iframe { max-width: 100%; } /* Table of Contents */ div.contents .toc { max-height: var(--toc-max-height); min-width: var(--toc-width); border: 0; border-left: 1px solid var(--separator-color); border-radius: 0; background-color: var(--page-background-color); box-shadow: none; position: sticky; top: var(--toc-sticky-top); padding: 0 var(--spacing-large); margin: var(--spacing-small) 0 var(--spacing-large) var(--spacing-large); } div.toc h3 { color: var(--toc-foreground); font-size: var(--navigation-font-size); margin: var(--spacing-large) 0 var(--spacing-medium) 0; } div.toc li { padding: 0; background: none; line-height: var(--toc-font-size); margin: var(--toc-font-size) 0 0 0; } div.toc li::before { display: none; } div.toc ul { margin-top: 0 } div.toc li a { font-size: var(--toc-font-size); color: var(--page-foreground-color) !important; text-decoration: none; } div.toc li a:hover, div.toc li a.active { color: var(--primary-color) !important; } div.toc li a.aboveActive { color: var(--page-secondary-foreground-color) !important; } @media screen and (max-width: 999px) { div.contents .toc { max-height: 45vh; float: none; width: auto; margin: 0 0 var(--spacing-medium) 0; position: relative; top: 0; position: relative; border: 1px solid var(--separator-color); border-radius: var(--border-radius-medium); background-color: var(--toc-background); box-shadow: var(--box-shadow); } div.contents .toc.interactive { max-height: calc(var(--navigation-font-size) + 2 * var(--spacing-large)); overflow: hidden; } div.contents .toc > h3 { -webkit-tap-highlight-color: transparent; cursor: pointer; position: sticky; top: 0; background-color: var(--toc-background); margin: 0; padding: var(--spacing-large) 0; display: block; } div.contents .toc.interactive > h3::before { content: ""; width: 0; height: 0; border-left: 4px solid transparent; border-right: 4px solid transparent; border-top: 5px solid var(--primary-color); display: inline-block; margin-right: var(--spacing-small); margin-bottom: calc(var(--navigation-font-size) / 4); transform: rotate(-90deg); transition: transform var(--animation-duration) ease-out; } div.contents .toc.interactive.open > h3::before { transform: rotate(0deg); } div.contents .toc.interactive.open { max-height: 45vh; overflow: auto; transition: max-height 0.2s ease-in-out; } div.contents .toc a, div.contents .toc a.active { color: var(--primary-color) !important; } div.contents .toc a:hover { text-decoration: underline; } } /* Page Outline (Doxygen >= 1.14.0) */ #page-nav { background: var(--page-background-color); border-left: 1px solid var(--separator-color); } #page-nav #page-nav-resize-handle { background: var(--separator-color); } #page-nav #page-nav-resize-handle::after { border-left: 1px solid var(--primary-color); border-right: 1px solid var(--primary-color); } #page-nav #page-nav-tree #page-nav-contents { top: var(--spacing-large); } #page-nav ul.page-outline { margin: 0; padding: 0; } #page-nav ul.page-outline li a { font-size: var(--toc-font-size) !important; color: var(--page-secondary-foreground-color) !important; display: inline-block; line-height: calc(2 * var(--toc-font-size)); } #page-nav ul.page-outline li a a.anchorlink { display: none; } #page-nav ul.page-outline li.vis ~ * a { color: var(--page-foreground-color) !important; } #page-nav ul.page-outline li.vis:not(.vis ~ .vis) a, #page-nav ul.page-outline li a:hover { color: var(--primary-color) !important; } #page-nav ul.page-outline .vis { background: var(--page-background-color); position: relative; } #page-nav ul.page-outline .vis::after { content: ""; position: absolute; top: 0; bottom: 0; left: 0; width: 4px; background: var(--page-secondary-foreground-color); } #page-nav ul.page-outline .vis:not(.vis ~ .vis)::after { top: 1px; border-top-right-radius: var(--border-radius-small); } #page-nav ul.page-outline .vis:not(:has(~ .vis))::after { bottom: 1px; border-bottom-right-radius: var(--border-radius-small); } #page-nav ul.page-outline .arrow { display: inline-block; } #page-nav ul.page-outline .arrow span { display: none; } @media screen and (max-width: 767px) { #container { grid-template-columns: initial !important; } #page-nav { display: none; } } /* Code & Fragments */ code, div.fragment, pre.fragment, span.tt { border: 1px solid var(--separator-color); overflow: hidden; } code, span.tt { display: inline; background: var(--code-background); color: var(--code-foreground); padding: 2px 6px; border-radius: var(--border-radius-small); } div.fragment, pre.fragment { border-radius: var(--border-radius-medium); margin: var(--spacing-medium) 0; padding: calc(var(--spacing-large) - (var(--spacing-large) / 6)) var(--spacing-large); background: var(--fragment-background); color: var(--fragment-foreground); overflow-x: auto; } @media screen and (max-width: 767px) { div.fragment, pre.fragment { border-top-right-radius: 0; border-bottom-right-radius: 0; border-right: 0; } .contents > div.fragment, .textblock > div.fragment, .textblock > pre.fragment, .textblock > .tabbed > ul > li > div.fragment, .textblock > .tabbed > ul > li > pre.fragment, .contents > .doxygen-awesome-fragment-wrapper > div.fragment, .textblock > .doxygen-awesome-fragment-wrapper > div.fragment, .textblock > .doxygen-awesome-fragment-wrapper > pre.fragment, .textblock > .tabbed > ul > li > .doxygen-awesome-fragment-wrapper > div.fragment, .textblock > .tabbed > ul > li > .doxygen-awesome-fragment-wrapper > pre.fragment { margin: var(--spacing-medium) calc(0px - var(--spacing-large)); border-radius: 0; border-left: 0; } .textblock li > .fragment, .textblock li > .doxygen-awesome-fragment-wrapper > .fragment { margin: var(--spacing-medium) calc(0px - var(--spacing-large)); } .memdoc li > .fragment, .memdoc li > .doxygen-awesome-fragment-wrapper > .fragment { margin: var(--spacing-medium) calc(0px - var(--spacing-medium)); } .textblock ul, .memdoc ul { overflow: initial; } .memdoc > div.fragment, .memdoc > pre.fragment, dl dd > div.fragment, dl dd pre.fragment, .memdoc > .doxygen-awesome-fragment-wrapper > div.fragment, .memdoc > .doxygen-awesome-fragment-wrapper > pre.fragment, dl dd > .doxygen-awesome-fragment-wrapper > div.fragment, dl dd .doxygen-awesome-fragment-wrapper > pre.fragment { margin: var(--spacing-medium) calc(0px - var(--spacing-medium)); border-radius: 0; border-left: 0; } } code, code a, pre.fragment, div.fragment, div.fragment .line, div.fragment span, div.fragment .line a, div.fragment .line span, span.tt { font-family: var(--font-family-monospace); font-size: var(--code-font-size) !important; } div.line:after { margin-right: var(--spacing-medium); } div.fragment .line, pre.fragment { white-space: pre; word-wrap: initial; line-height: var(--fragment-lineheight); } div.fragment span.keyword { color: var(--fragment-keyword); } div.fragment span.keywordtype { color: var(--fragment-keywordtype); } div.fragment span.keywordflow { color: var(--fragment-keywordflow); } div.fragment span.stringliteral { color: var(--fragment-token) } div.fragment span.comment { color: var(--fragment-comment); } div.fragment a.code { color: var(--fragment-link) !important; } div.fragment span.preprocessor { color: var(--fragment-preprocessor); } div.fragment span.lineno { display: inline-block; width: 27px; border-right: none; background: var(--fragment-linenumber-background); color: var(--fragment-linenumber-color); } div.fragment span.lineno a { background: none; color: var(--fragment-link) !important; } div.fragment > .line:first-child .lineno { box-shadow: -999999px 0px 0 999999px var(--fragment-linenumber-background), -999998px 0px 0 999999px var(--fragment-linenumber-border); background-color: var(--fragment-linenumber-background) !important; } div.line { border-radius: var(--border-radius-small); } div.line.glow { background-color: var(--primary-light-color); box-shadow: none; } /* dl warning, attention, note, deprecated, bug, ... */ dl { line-height: calc(1.65 * var(--page-font-size)); } dl.bug dt a, dl.deprecated dt a, dl.todo dt a { font-weight: bold !important; } dl.warning, dl.attention, dl.note, dl.deprecated, dl.bug, dl.invariant, dl.pre, dl.post, dl.todo, dl.remark { padding: var(--spacing-medium); margin: var(--spacing-medium) 0; color: var(--page-background-color); overflow: hidden; margin-left: 0; border-radius: var(--border-radius-small); } dl.section dd { margin-bottom: 2px; } dl.warning, dl.attention { background: var(--warning-color); border-left: 8px solid var(--warning-color-dark); color: var(--warning-color-darker); } dl.warning dt, dl.attention dt { color: var(--warning-color-dark); } dl.note, dl.remark { background: var(--note-color); border-left: 8px solid var(--note-color-dark); color: var(--note-color-darker); } dl.note dt, dl.remark dt { color: var(--note-color-dark); } dl.todo { background: var(--todo-color); border-left: 8px solid var(--todo-color-dark); color: var(--todo-color-darker); } dl.todo dt a { color: var(--todo-color-dark) !important; } dl.bug dt a { color: var(--todo-color-dark) !important; } dl.bug { background: var(--bug-color); border-left: 8px solid var(--bug-color-dark); color: var(--bug-color-darker); } dl.bug dt a { color: var(--bug-color-dark) !important; } dl.deprecated { background: var(--deprecated-color); border-left: 8px solid var(--deprecated-color-dark); color: var(--deprecated-color-darker); } dl.deprecated dt a { color: var(--deprecated-color-dark) !important; } dl.section dd, dl.bug dd, dl.deprecated dd, dl.todo dd { margin-inline-start: 0px; } dl.invariant, dl.pre, dl.post { background: var(--invariant-color); border-left: 8px solid var(--invariant-color-dark); color: var(--invariant-color-darker); } dl.invariant dt, dl.pre dt, dl.post dt { color: var(--invariant-color-dark); } /* memitem */ div.memdoc, div.memproto, h2.memtitle { box-shadow: none; background-image: none; border: none; } div.memdoc { padding: 0 var(--spacing-medium); background: var(--page-background-color); } h2.memtitle, div.memitem { border: 1px solid var(--separator-color); box-shadow: var(--box-shadow); } h2.memtitle { box-shadow: 0px var(--spacing-medium) 0 -1px var(--fragment-background), var(--box-shadow); } div.memitem { transition: none; } div.memproto, h2.memtitle { background: var(--fragment-background); } h2.memtitle { font-weight: 500; font-size: var(--memtitle-font-size); font-family: var(--font-family-monospace); border-bottom: none; border-top-left-radius: var(--border-radius-medium); border-top-right-radius: var(--border-radius-medium); word-break: break-all; position: relative; } h2.memtitle:after { content: ""; display: block; background: var(--fragment-background); height: var(--spacing-medium); bottom: calc(0px - var(--spacing-medium)); left: 0; right: -14px; position: absolute; border-top-right-radius: var(--border-radius-medium); } h2.memtitle > span.permalink { font-size: inherit; } h2.memtitle > span.permalink > a { text-decoration: none; padding-left: 3px; margin-right: -4px; user-select: none; display: inline-block; margin-top: -6px; } h2.memtitle > span.permalink > a:hover { color: var(--primary-dark-color) !important; } a:target + h2.memtitle, a:target + h2.memtitle + div.memitem { border-color: var(--primary-light-color); } div.memitem { border-top-right-radius: var(--border-radius-medium); border-bottom-right-radius: var(--border-radius-medium); border-bottom-left-radius: var(--border-radius-medium); border-top-left-radius: 0; overflow: hidden; display: block !important; } div.memdoc { border-radius: 0; } div.memproto { border-radius: 0 var(--border-radius-small) 0 0; overflow: auto; border-bottom: 1px solid var(--separator-color); padding: var(--spacing-medium); margin-bottom: -1px; } div.memtitle { border-top-right-radius: var(--border-radius-medium); border-top-left-radius: var(--border-radius-medium); } div.memproto table.memname { font-family: var(--font-family-monospace); color: var(--page-foreground-color); font-size: var(--memname-font-size); text-shadow: none; } div.memproto div.memtemplate { font-family: var(--font-family-monospace); color: var(--primary-dark-color); font-size: var(--memname-font-size); margin-left: 2px; text-shadow: none; } table.mlabels, table.mlabels > tbody { display: block; } td.mlabels-left { width: auto; } td.mlabels-right { margin-top: 3px; position: sticky; left: 0; } table.mlabels > tbody > tr:first-child { display: flex; justify-content: space-between; flex-wrap: wrap; } .memname, .memitem span.mlabels { margin: 0 } /* reflist */ dl.reflist { box-shadow: var(--box-shadow); border-radius: var(--border-radius-medium); border: 1px solid var(--separator-color); overflow: hidden; padding: 0; } dl.reflist dt, dl.reflist dd { box-shadow: none; text-shadow: none; background-image: none; border: none; padding: 12px; } dl.reflist dt { font-weight: 500; border-radius: 0; background: var(--code-background); border-bottom: 1px solid var(--separator-color); color: var(--page-foreground-color) } dl.reflist dd { background: none; } /* Table */ .contents table:not(.memberdecls):not(.mlabels):not(.fieldtable):not(.memname), .contents table:not(.memberdecls):not(.mlabels):not(.fieldtable):not(.memname) tbody { display: inline-block; max-width: 100%; } .contents > table:not(.memberdecls):not(.mlabels):not(.fieldtable):not(.memname):not(.classindex) { margin-left: calc(0px - var(--spacing-large)); margin-right: calc(0px - var(--spacing-large)); max-width: calc(100% + 2 * var(--spacing-large)); } table.fieldtable, table.markdownTable tbody, table.doxtable tbody { border: none; margin: var(--spacing-medium) 0; box-shadow: 0 0 0 1px var(--separator-color); border-radius: var(--border-radius-small); } table.markdownTable, table.doxtable, table.fieldtable { padding: 1px; } table.doxtable caption { display: block; } table.fieldtable { border-collapse: collapse; width: 100%; } th.markdownTableHeadLeft, th.markdownTableHeadRight, th.markdownTableHeadCenter, th.markdownTableHeadNone, table.doxtable th { background: var(--tablehead-background); color: var(--tablehead-foreground); font-weight: 600; font-size: var(--page-font-size); } th.markdownTableHeadLeft:first-child, th.markdownTableHeadRight:first-child, th.markdownTableHeadCenter:first-child, th.markdownTableHeadNone:first-child, table.doxtable tr th:first-child { border-top-left-radius: var(--border-radius-small); } th.markdownTableHeadLeft:last-child, th.markdownTableHeadRight:last-child, th.markdownTableHeadCenter:last-child, th.markdownTableHeadNone:last-child, table.doxtable tr th:last-child { border-top-right-radius: var(--border-radius-small); } table.markdownTable td, table.markdownTable th, table.fieldtable td, table.fieldtable th, table.doxtable td, table.doxtable th { border: 1px solid var(--separator-color); padding: var(--spacing-small) var(--spacing-medium); } table.markdownTable td:last-child, table.markdownTable th:last-child, table.fieldtable td:last-child, table.fieldtable th:last-child, table.doxtable td:last-child, table.doxtable th:last-child { border-right: none; } table.markdownTable td:first-child, table.markdownTable th:first-child, table.fieldtable td:first-child, table.fieldtable th:first-child, table.doxtable td:first-child, table.doxtable th:first-child { border-left: none; } table.markdownTable tr:first-child td, table.markdownTable tr:first-child th, table.fieldtable tr:first-child td, table.fieldtable tr:first-child th, table.doxtable tr:first-child td, table.doxtable tr:first-child th { border-top: none; } table.markdownTable tr:last-child td, table.markdownTable tr:last-child th, table.fieldtable tr:last-child td, table.fieldtable tr:last-child th, table.doxtable tr:last-child td, table.doxtable tr:last-child th { border-bottom: none; } table.markdownTable tr, table.doxtable tr { border-bottom: 1px solid var(--separator-color); } table.markdownTable tr:last-child, table.doxtable tr:last-child { border-bottom: none; } .full_width_table table:not(.memberdecls):not(.mlabels):not(.fieldtable):not(.memname) { display: block; } .full_width_table table:not(.memberdecls):not(.mlabels):not(.fieldtable):not(.memname) tbody { display: table; width: 100%; } table.fieldtable th { font-size: var(--page-font-size); font-weight: 600; background-image: none; background-color: var(--tablehead-background); color: var(--tablehead-foreground); } table.fieldtable td.fieldtype, .fieldtable td.fieldname, .fieldtable td.fieldinit, .fieldtable td.fielddoc, .fieldtable th { border-bottom: 1px solid var(--separator-color); border-right: 1px solid var(--separator-color); } table.fieldtable tr:last-child td:first-child { border-bottom-left-radius: var(--border-radius-small); } table.fieldtable tr:last-child td:last-child { border-bottom-right-radius: var(--border-radius-small); } .memberdecls td.glow, .fieldtable tr.glow { background-color: var(--primary-light-color); box-shadow: none; } table.memberdecls { display: block; -webkit-tap-highlight-color: transparent; } table.memberdecls tr[class^='memitem'] { font-family: var(--font-family-monospace); font-size: var(--code-font-size); } table.memberdecls tr[class^='memitem'] .memTemplParams { font-family: var(--font-family-monospace); font-size: var(--code-font-size); color: var(--primary-dark-color); white-space: normal; } table.memberdecls tr.heading + tr[class^='memitem'] td.memItemLeft, table.memberdecls tr.heading + tr[class^='memitem'] td.memItemRight, table.memberdecls td.memItemLeft, table.memberdecls td.memItemRight, table.memberdecls .memTemplItemLeft, table.memberdecls .memTemplItemRight, table.memberdecls .memTemplParams { transition: none; padding-top: var(--spacing-small); padding-bottom: var(--spacing-small); border-top: 1px solid var(--separator-color); border-bottom: 1px solid var(--separator-color); background-color: var(--fragment-background); } @media screen and (min-width: 768px) { tr.heading + tr[class^='memitem'] td.memItemRight, tr.groupHeader + tr[class^='memitem'] td.memItemRight, tr.inherit_header + tr[class^='memitem'] td.memItemRight { border-top-right-radius: var(--border-radius-small); } table.memberdecls tr:last-child td.memItemRight, table.memberdecls tr:last-child td.mdescRight, table.memberdecls tr[class^='memitem']:has(+ tr.groupHeader) td.memItemRight, table.memberdecls tr[class^='memitem']:has(+ tr.inherit_header) td.memItemRight, table.memberdecls tr[class^='memdesc']:has(+ tr.groupHeader) td.mdescRight, table.memberdecls tr[class^='memdesc']:has(+ tr.inherit_header) td.mdescRight { border-bottom-right-radius: var(--border-radius-small); } table.memberdecls tr:last-child td.memItemLeft, table.memberdecls tr:last-child td.mdescLeft, table.memberdecls tr[class^='memitem']:has(+ tr.groupHeader) td.memItemLeft, table.memberdecls tr[class^='memitem']:has(+ tr.inherit_header) td.memItemLeft, table.memberdecls tr[class^='memdesc']:has(+ tr.groupHeader) td.mdescLeft, table.memberdecls tr[class^='memdesc']:has(+ tr.inherit_header) td.mdescLeft { border-bottom-left-radius: var(--border-radius-small); } tr.heading + tr[class^='memitem'] td.memItemLeft, tr.groupHeader + tr[class^='memitem'] td.memItemLeft, tr.inherit_header + tr[class^='memitem'] td.memItemLeft { border-top-left-radius: var(--border-radius-small); } } table.memname td.memname { font-size: var(--memname-font-size); } table.memberdecls .memTemplItemLeft, table.memberdecls .template .memItemLeft, table.memberdecls .memTemplItemRight, table.memberdecls .template .memItemRight { padding-top: 2px; } table.memberdecls .memTemplParams { border-bottom: 0; border-left: 1px solid var(--separator-color); border-right: 1px solid var(--separator-color); border-radius: var(--border-radius-small) var(--border-radius-small) 0 0; padding-bottom: var(--spacing-small); } table.memberdecls .memTemplItemLeft, table.memberdecls .template .memItemLeft { border-radius: 0 0 0 var(--border-radius-small); border-left: 1px solid var(--separator-color); border-top: 0; } table.memberdecls .memTemplItemRight, table.memberdecls .template .memItemRight { border-radius: 0 0 var(--border-radius-small) 0; border-right: 1px solid var(--separator-color); padding-left: 0; border-top: 0; } table.memberdecls .memItemLeft { border-radius: var(--border-radius-small) 0 0 var(--border-radius-small); border-left: 1px solid var(--separator-color); padding-left: var(--spacing-medium); padding-right: 0; } table.memberdecls .memItemRight { border-radius: 0 var(--border-radius-small) var(--border-radius-small) 0; border-right: 1px solid var(--separator-color); padding-right: var(--spacing-medium); padding-left: 0; } table.memberdecls .mdescLeft, table.memberdecls .mdescRight { background: none; color: var(--page-foreground-color); padding: var(--spacing-small) 0; border: 0; } table.memberdecls [class^="memdesc"] { box-shadow: none; } table.memberdecls .memItemLeft, table.memberdecls .memTemplItemLeft { padding-right: var(--spacing-medium); } table.memberdecls .memSeparator { background: var(--page-background-color); height: var(--spacing-large); border: 0; transition: none; } table.memberdecls .groupheader { margin-bottom: var(--spacing-large); } table.memberdecls .inherit_header td { padding: 0 0 var(--spacing-medium) 0; text-indent: -12px; color: var(--page-secondary-foreground-color); } table.memberdecls span.dynarrow { left: 10px; } table.memberdecls img[src="closed.png"], table.memberdecls img[src="open.png"], div.dynheader img[src="open.png"], div.dynheader img[src="closed.png"] { width: 0; height: 0; border-left: 4px solid transparent; border-right: 4px solid transparent; border-top: 5px solid var(--primary-color); margin-top: 8px; display: block; float: left; margin-left: -10px; transition: transform var(--animation-duration) ease-out; } tr.heading + tr[class^='memitem'] td.memItemLeft, tr.groupHeader + tr[class^='memitem'] td.memItemLeft, tr.inherit_header + tr[class^='memitem'] td.memItemLeft, tr.heading + tr[class^='memitem'] td.memItemRight, tr.groupHeader + tr[class^='memitem'] td.memItemRight, tr.inherit_header + tr[class^='memitem'] td.memItemRight { border-top: 1px solid var(--separator-color); } table.memberdecls img { margin-right: 10px; } table.memberdecls img[src="closed.png"], div.dynheader img[src="closed.png"] { transform: rotate(-90deg); } .compoundTemplParams { font-family: var(--font-family-monospace); color: var(--primary-dark-color); font-size: var(--code-font-size); } @media screen and (max-width: 767px) { table.memberdecls .memItemLeft, table.memberdecls .memItemRight, table.memberdecls .mdescLeft, table.memberdecls .mdescRight, table.memberdecls .memTemplItemLeft, table.memberdecls .memTemplItemRight, table.memberdecls .memTemplParams, table.memberdecls .template .memItemLeft, table.memberdecls .template .memItemRight, table.memberdecls .template .memParams { display: block; text-align: left; padding-left: var(--spacing-large); margin: 0 calc(0px - var(--spacing-large)) 0 calc(0px - var(--spacing-large)); border-right: none; border-left: none; border-radius: 0; white-space: normal; } table.memberdecls .memItemLeft, table.memberdecls .mdescLeft, table.memberdecls .memTemplItemLeft, table.memberdecls .template .memItemLeft { border-bottom: 0 !important; padding-bottom: 0 !important; } table.memberdecls .memTemplItemLeft, table.memberdecls .template .memItemLeft { padding-top: 0; } table.memberdecls .mdescLeft { margin-bottom: calc(0px - var(--page-font-size)); } table.memberdecls .memItemRight, table.memberdecls .mdescRight, table.memberdecls .memTemplItemRight, table.memberdecls .template .memItemRight { border-top: 0 !important; padding-top: 0 !important; padding-right: var(--spacing-large); padding-bottom: var(--spacing-medium); overflow-x: auto; } table.memberdecls tr[class^='memitem']:not(.inherit) { display: block; width: calc(100vw - 2 * var(--spacing-large)); } table.memberdecls .mdescRight { color: var(--page-foreground-color); } table.memberdecls tr.inherit { visibility: hidden; } table.memberdecls tr[style="display: table-row;"] { display: block !important; visibility: visible; width: calc(100vw - 2 * var(--spacing-large)); animation: fade .5s; } @keyframes fade { 0% { opacity: 0; max-height: 0; } 100% { opacity: 1; max-height: 200px; } } tr.heading + tr[class^='memitem'] td.memItemRight, tr.groupHeader + tr[class^='memitem'] td.memItemRight, tr.inherit_header + tr[class^='memitem'] td.memItemRight { border-top-right-radius: 0; } table.memberdecls tr:last-child td.memItemRight, table.memberdecls tr:last-child td.mdescRight, table.memberdecls tr[class^='memitem']:has(+ tr.groupHeader) td.memItemRight, table.memberdecls tr[class^='memitem']:has(+ tr.inherit_header) td.memItemRight, table.memberdecls tr[class^='memdesc']:has(+ tr.groupHeader) td.mdescRight, table.memberdecls tr[class^='memdesc']:has(+ tr.inherit_header) td.mdescRight { border-bottom-right-radius: 0; } table.memberdecls tr:last-child td.memItemLeft, table.memberdecls tr:last-child td.mdescLeft, table.memberdecls tr[class^='memitem']:has(+ tr.groupHeader) td.memItemLeft, table.memberdecls tr[class^='memitem']:has(+ tr.inherit_header) td.memItemLeft, table.memberdecls tr[class^='memdesc']:has(+ tr.groupHeader) td.mdescLeft, table.memberdecls tr[class^='memdesc']:has(+ tr.inherit_header) td.mdescLeft { border-bottom-left-radius: 0; } tr.heading + tr[class^='memitem'] td.memItemLeft, tr.groupHeader + tr[class^='memitem'] td.memItemLeft, tr.inherit_header + tr[class^='memitem'] td.memItemLeft { border-top-left-radius: 0; } } /* Horizontal Rule */ hr { margin-top: var(--spacing-large); margin-bottom: var(--spacing-large); height: 1px; background-color: var(--separator-color); border: 0; } .contents hr { box-shadow: 100px 0 var(--separator-color), -100px 0 var(--separator-color), 500px 0 var(--separator-color), -500px 0 var(--separator-color), 900px 0 var(--separator-color), -900px 0 var(--separator-color), 1400px 0 var(--separator-color), -1400px 0 var(--separator-color), 1900px 0 var(--separator-color), -1900px 0 var(--separator-color); } .contents img, .contents .center, .contents center, .contents div.image object { max-width: 100%; overflow: auto; } @media screen and (max-width: 767px) { .contents .dyncontent > .center, .contents > center { margin-left: calc(0px - var(--spacing-large)); margin-right: calc(0px - var(--spacing-large)); max-width: calc(100% + 2 * var(--spacing-large)); } } /* Directories */ div.directory { border-top: 1px solid var(--separator-color); border-bottom: 1px solid var(--separator-color); width: auto; } table.directory { font-family: var(--font-family); font-size: var(--page-font-size); font-weight: normal; width: 100%; } table.directory td.entry, table.directory td.desc { padding: calc(var(--spacing-small) / 2) var(--spacing-small); line-height: var(--table-line-height); } table.directory tr.even td:last-child { border-radius: 0 var(--border-radius-small) var(--border-radius-small) 0; } table.directory tr.even td:first-child { border-radius: var(--border-radius-small) 0 0 var(--border-radius-small); } table.directory tr.even:last-child td:last-child { border-radius: 0 var(--border-radius-small) 0 0; } table.directory tr.even:last-child td:first-child { border-radius: var(--border-radius-small) 0 0 0; } table.directory td.desc { min-width: 250px; } table.directory tr.even { background-color: var(--odd-color); } table.directory tr.odd { background-color: transparent; } .icona { width: auto; height: auto; margin: 0 var(--spacing-small); } .icon { background: var(--primary-color); border-radius: var(--border-radius-small); font-size: var(--page-font-size); padding: calc(var(--page-font-size) / 5); line-height: var(--page-font-size); transform: scale(0.8); height: auto; width: var(--page-font-size); user-select: none; } .iconfopen, .icondoc, .iconfclosed { background-position: center; margin-bottom: 0; height: var(--table-line-height); } .icondoc { filter: saturate(0.2); } @media screen and (max-width: 767px) { div.directory { margin-left: calc(0px - var(--spacing-large)); margin-right: calc(0px - var(--spacing-large)); } } @media (prefers-color-scheme: dark) { html:not(.light-mode) .iconfopen, html:not(.light-mode) .iconfclosed { filter: hue-rotate(180deg) invert(); } } html.dark-mode .iconfopen, html.dark-mode .iconfclosed { filter: hue-rotate(180deg) invert(); } /* Class list */ .classindex dl.odd { background: var(--odd-color); border-radius: var(--border-radius-small); } .classindex dl.even { background-color: transparent; } /* Class Index Doxygen 1.8 */ table.classindex { margin-left: 0; margin-right: 0; width: 100%; } table.classindex table div.ah { background-image: none; background-color: initial; border-color: var(--separator-color); color: var(--page-foreground-color); box-shadow: var(--box-shadow); border-radius: var(--border-radius-large); padding: var(--spacing-small); } div.qindex { background-color: var(--odd-color); border-radius: var(--border-radius-small); border: 1px solid var(--separator-color); padding: var(--spacing-small) 0; } /* Footer and nav-path */ #nav-path { width: 100%; } #nav-path ul { background-image: none; background: var(--page-background-color); border: none; border-top: 1px solid var(--separator-color); border-bottom: 0; font-size: var(--navigation-font-size); } img.footer { width: 60px; } .navpath li.footer { color: var(--page-secondary-foreground-color); } address.footer { color: var(--page-secondary-foreground-color); margin-bottom: var(--spacing-large); } #nav-path li.navelem { background-image: none; display: flex; align-items: center; } .navpath li.navelem a { text-shadow: none; display: inline-block; color: var(--primary-color) !important; } .navpath li.navelem a:hover { text-shadow: none; } .navpath li.navelem b { color: var(--primary-dark-color); font-weight: 500; } li.navelem { padding: 0; margin-left: -8px; } li.navelem:first-child { margin-left: var(--spacing-large); } li.navelem:first-child:before { display: none; } #nav-path ul { padding-left: 0; } #nav-path li.navelem:has(.el):after { content: ''; border: 5px solid var(--page-background-color); border-bottom-color: transparent; border-right-color: transparent; border-top-color: transparent; transform: translateY(-1px) scaleY(4.2); z-index: 10; margin-left: 6px; } #nav-path li.navelem:not(:has(.el)):after { background: var(--page-background-color); box-shadow: 1px -1px 0 1px var(--separator-color); border-radius: 0 var(--border-radius-medium) 0 50px; } #nav-path li.navelem:not(:has(.el)) { margin-left: 0; } #nav-path li.navelem:not(:has(.el)):hover, #nav-path li.navelem:not(:has(.el)):hover:after { background-color: var(--separator-color); } #nav-path li.navelem:has(.el):before { content: ''; border: 5px solid var(--separator-color); border-bottom-color: transparent; border-right-color: transparent; border-top-color: transparent; transform: translateY(-1px) scaleY(3.2); margin-right: var(--spacing-small); } .navpath li.navelem a:hover { color: var(--primary-color); } /* Scrollbars for Webkit */ #nav-tree::-webkit-scrollbar, div.fragment::-webkit-scrollbar, pre.fragment::-webkit-scrollbar, div.memproto::-webkit-scrollbar, .contents center::-webkit-scrollbar, .contents .center::-webkit-scrollbar, .contents table:not(.memberdecls):not(.mlabels):not(.fieldtable):not(.memname) tbody::-webkit-scrollbar, div.contents .toc::-webkit-scrollbar, .contents .dotgraph::-webkit-scrollbar, .contents .tabs-overview-container::-webkit-scrollbar { background: transparent; width: calc(var(--webkit-scrollbar-size) + var(--webkit-scrollbar-padding) + var(--webkit-scrollbar-padding)); height: calc(var(--webkit-scrollbar-size) + var(--webkit-scrollbar-padding) + var(--webkit-scrollbar-padding)); } #nav-tree::-webkit-scrollbar-thumb, div.fragment::-webkit-scrollbar-thumb, pre.fragment::-webkit-scrollbar-thumb, div.memproto::-webkit-scrollbar-thumb, .contents center::-webkit-scrollbar-thumb, .contents .center::-webkit-scrollbar-thumb, .contents table:not(.memberdecls):not(.mlabels):not(.fieldtable):not(.memname) tbody::-webkit-scrollbar-thumb, div.contents .toc::-webkit-scrollbar-thumb, .contents .dotgraph::-webkit-scrollbar-thumb, .contents .tabs-overview-container::-webkit-scrollbar-thumb { background-color: transparent; border: var(--webkit-scrollbar-padding) solid transparent; border-radius: calc(var(--webkit-scrollbar-padding) + var(--webkit-scrollbar-padding)); background-clip: padding-box; } #nav-tree:hover::-webkit-scrollbar-thumb, div.fragment:hover::-webkit-scrollbar-thumb, pre.fragment:hover::-webkit-scrollbar-thumb, div.memproto:hover::-webkit-scrollbar-thumb, .contents center:hover::-webkit-scrollbar-thumb, .contents .center:hover::-webkit-scrollbar-thumb, .contents table:not(.memberdecls):not(.mlabels):not(.fieldtable):not(.memname) tbody:hover::-webkit-scrollbar-thumb, div.contents .toc:hover::-webkit-scrollbar-thumb, .contents .dotgraph:hover::-webkit-scrollbar-thumb, .contents .tabs-overview-container:hover::-webkit-scrollbar-thumb { background-color: var(--webkit-scrollbar-color); } #nav-tree::-webkit-scrollbar-track, div.fragment::-webkit-scrollbar-track, pre.fragment::-webkit-scrollbar-track, div.memproto::-webkit-scrollbar-track, .contents center::-webkit-scrollbar-track, .contents .center::-webkit-scrollbar-track, .contents table:not(.memberdecls):not(.mlabels):not(.fieldtable):not(.memname) tbody::-webkit-scrollbar-track, div.contents .toc::-webkit-scrollbar-track, .contents .dotgraph::-webkit-scrollbar-track, .contents .tabs-overview-container::-webkit-scrollbar-track { background: transparent; } #nav-tree::-webkit-scrollbar-corner { background-color: var(--side-nav-background); } #nav-tree, div.fragment, pre.fragment, div.memproto, .contents center, .contents .center, .contents table:not(.memberdecls):not(.mlabels):not(.fieldtable):not(.memname) tbody, div.contents .toc { overflow-x: auto; overflow-x: overlay; } #nav-tree { overflow-x: auto; overflow-y: auto; overflow-y: overlay; } /* Scrollbars for Firefox */ #nav-tree, div.fragment, pre.fragment, div.memproto, .contents center, .contents .center, .contents table:not(.memberdecls):not(.mlabels):not(.fieldtable):not(.memname) tbody, div.contents .toc, .contents .dotgraph, .contents .tabs-overview-container { scrollbar-width: thin; } /* Optional Dark mode toggle button */ doxygen-awesome-dark-mode-toggle { display: inline-block; margin: 0 0 0 var(--spacing-small); padding: 0; width: var(--searchbar-height); height: var(--searchbar-height); background: none; border: none; border-radius: var(--searchbar-border-radius); vertical-align: middle; text-align: center; line-height: var(--searchbar-height); font-size: 22px; display: flex; align-items: center; justify-content: center; user-select: none; cursor: pointer; } doxygen-awesome-dark-mode-toggle > svg { transition: transform var(--animation-duration) ease-in-out; } doxygen-awesome-dark-mode-toggle:active > svg { transform: scale(.5); } doxygen-awesome-dark-mode-toggle:hover { background-color: rgba(0,0,0,.03); } html.dark-mode doxygen-awesome-dark-mode-toggle:hover { background-color: rgba(0,0,0,.18); } /* Optional fragment copy button */ .doxygen-awesome-fragment-wrapper { position: relative; } doxygen-awesome-fragment-copy-button { opacity: 0; background: var(--fragment-background); width: 28px; height: 28px; position: absolute; right: calc(var(--spacing-large) - (var(--spacing-large) / 2.5)); top: calc(var(--spacing-large) - (var(--spacing-large) / 2.5)); border: 1px solid var(--fragment-foreground); cursor: pointer; border-radius: var(--border-radius-small); display: flex; justify-content: center; align-items: center; } .doxygen-awesome-fragment-wrapper:hover doxygen-awesome-fragment-copy-button, doxygen-awesome-fragment-copy-button.success { opacity: .28; } doxygen-awesome-fragment-copy-button:hover, doxygen-awesome-fragment-copy-button.success { opacity: 1 !important; } doxygen-awesome-fragment-copy-button:active:not([class~=success]) svg { transform: scale(.91); } doxygen-awesome-fragment-copy-button svg { fill: var(--fragment-foreground); width: 18px; height: 18px; } doxygen-awesome-fragment-copy-button.success svg { fill: rgb(14, 168, 14); } doxygen-awesome-fragment-copy-button.success { border-color: rgb(14, 168, 14); } @media screen and (max-width: 767px) { .textblock > .doxygen-awesome-fragment-wrapper > doxygen-awesome-fragment-copy-button, .textblock li > .doxygen-awesome-fragment-wrapper > doxygen-awesome-fragment-copy-button, .memdoc li > .doxygen-awesome-fragment-wrapper > doxygen-awesome-fragment-copy-button, .memdoc > .doxygen-awesome-fragment-wrapper > doxygen-awesome-fragment-copy-button, dl dd > .doxygen-awesome-fragment-wrapper > doxygen-awesome-fragment-copy-button { right: 0; } } /* Optional paragraph link button */ a.anchorlink { font-size: 90%; margin-left: var(--spacing-small); color: var(--page-foreground-color) !important; text-decoration: none; opacity: .15; display: none; transition: opacity var(--animation-duration) ease-in-out, color var(--animation-duration) ease-in-out; } a.anchorlink svg { fill: var(--page-foreground-color); } h3 a.anchorlink svg, h4 a.anchorlink svg { margin-bottom: -3px; margin-top: -4px; } a.anchorlink:hover { opacity: .45; } h2:hover a.anchorlink, h1:hover a.anchorlink, h3:hover a.anchorlink, h4:hover a.anchorlink { display: inline-block; } /* Optional tab feature */ .tabbed > ul { padding-inline-start: 0px; margin: 0; padding: var(--spacing-small) 0; } .tabbed > ul > li { display: none; } .tabbed > ul > li.selected { display: block; } .tabs-overview-container { overflow-x: auto; display: block; overflow-y: visible; } .tabs-overview { border-bottom: 1px solid var(--separator-color); display: flex; flex-direction: row; } @media screen and (max-width: 767px) { .tabs-overview-container { margin: 0 calc(0px - var(--spacing-large)); } .tabs-overview { padding: 0 var(--spacing-large) } } .tabs-overview button.tab-button { color: var(--page-foreground-color); margin: 0; border: none; background: transparent; padding: calc(var(--spacing-large) / 2) 0; display: inline-block; font-size: var(--page-font-size); cursor: pointer; box-shadow: 0 1px 0 0 var(--separator-color); position: relative; -webkit-tap-highlight-color: transparent; } .tabs-overview button.tab-button .tab-title::before { display: block; content: attr(title); font-weight: 600; height: 0; overflow: hidden; visibility: hidden; } .tabs-overview button.tab-button .tab-title { float: left; white-space: nowrap; font-weight: normal; font-family: var(--font-family); padding: calc(var(--spacing-large) / 2) var(--spacing-large); border-radius: var(--border-radius-medium); transition: background-color var(--animation-duration) ease-in-out, font-weight var(--animation-duration) ease-in-out; } .tabs-overview button.tab-button:not(:last-child) .tab-title { box-shadow: 8px 0 0 -7px var(--separator-color); } .tabs-overview button.tab-button:hover .tab-title { background: var(--separator-color); box-shadow: none; } .tabs-overview button.tab-button.active .tab-title { font-weight: 600; } .tabs-overview button.tab-button::after { content: ''; display: block; position: absolute; left: 0; bottom: 0; right: 0; height: 0; width: 0%; margin: 0 auto; border-radius: var(--border-radius-small) var(--border-radius-small) 0 0; background-color: var(--primary-color); transition: width var(--animation-duration) ease-in-out, height var(--animation-duration) ease-in-out; } .tabs-overview button.tab-button.active::after { width: 100%; box-sizing: border-box; height: 3px; } /* Navigation Buttons */ .section_buttons:not(:empty) { margin-top: calc(var(--spacing-large) * 3); } .section_buttons table.markdownTable { display: block; width: 100%; } .section_buttons table.markdownTable tbody { display: table !important; width: 100%; box-shadow: none; border-spacing: 10px; } .section_buttons table.markdownTable td { padding: 0; } .section_buttons table.markdownTable th { display: none; } .section_buttons table.markdownTable tr.markdownTableHead { border: none; } .section_buttons tr th, .section_buttons tr td { background: none; border: none; padding: var(--spacing-large) 0 var(--spacing-small); } .section_buttons a { display: inline-block; border: 1px solid var(--separator-color); border-radius: var(--border-radius-medium); color: var(--page-secondary-foreground-color) !important; text-decoration: none; transition: color var(--animation-duration) ease-in-out, background-color var(--animation-duration) ease-in-out; } .section_buttons a:hover { color: var(--page-foreground-color) !important; background-color: var(--odd-color); } .section_buttons tr td.markdownTableBodyLeft a { padding: var(--spacing-medium) var(--spacing-large) var(--spacing-medium) calc(var(--spacing-large) / 2); } .section_buttons tr td.markdownTableBodyRight a { padding: var(--spacing-medium) calc(var(--spacing-large) / 2) var(--spacing-medium) var(--spacing-large); } .section_buttons tr td.markdownTableBodyLeft a::before, .section_buttons tr td.markdownTableBodyRight a::after { color: var(--page-secondary-foreground-color) !important; display: inline-block; transition: color .08s ease-in-out, transform .09s ease-in-out; } .section_buttons tr td.markdownTableBodyLeft a::before { content: '〈'; padding-right: var(--spacing-large); } .section_buttons tr td.markdownTableBodyRight a::after { content: '〉'; padding-left: var(--spacing-large); } .section_buttons tr td.markdownTableBodyLeft a:hover::before { color: var(--page-foreground-color) !important; transform: translateX(-3px); } .section_buttons tr td.markdownTableBodyRight a:hover::after { color: var(--page-foreground-color) !important; transform: translateX(3px); } @media screen and (max-width: 450px) { .section_buttons a { width: 100%; box-sizing: border-box; } .section_buttons tr td:nth-of-type(1).markdownTableBodyLeft a { border-radius: var(--border-radius-medium) 0 0 var(--border-radius-medium); border-right: none; } .section_buttons tr td:nth-of-type(2).markdownTableBodyRight a { border-radius: 0 var(--border-radius-medium) var(--border-radius-medium) 0; } } /* Bordered image */ html.dark-mode .darkmode_inverted_image img, /* < doxygen 1.9.3 */ html.dark-mode .darkmode_inverted_image object[type="image/svg+xml"] /* doxygen 1.9.3 */ { filter: brightness(89%) hue-rotate(180deg) invert(); } .bordered_image { border-radius: var(--border-radius-small); border: 1px solid var(--separator-color); display: inline-block; overflow: hidden; } .bordered_image:empty { border: none; } html.dark-mode .bordered_image img, /* < doxygen 1.9.3 */ html.dark-mode .bordered_image object[type="image/svg+xml"] /* doxygen 1.9.3 */ { border-radius: var(--border-radius-small); } /* Button */ .primary-button { display: inline-block; cursor: pointer; background: var(--primary-color); color: var(--page-background-color) !important; border-radius: var(--border-radius-medium); padding: var(--spacing-small) var(--spacing-medium); text-decoration: none; } .primary-button:hover { background: var(--primary-dark-color); }pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/doc/examples.dox.in000066400000000000000000000001301511204443500241740ustar00rootroot00000000000000/** \page page_examples List of example programs @example_ref@ @example_doxygen@ */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/doc/examples/000077500000000000000000000000001511204443500230615ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/doc/examples/tutorial1.c000066400000000000000000000005061511204443500251520ustar00rootroot00000000000000/* [title] \ref page_tutorial1 [title] */ /* [code] */ #include int main(int argc, char *argv[]) { pw_init(&argc, &argv); fprintf(stdout, "Compiled with libpipewire %s\n" "Linked with libpipewire %s\n", pw_get_headers_version(), pw_get_library_version()); return 0; } /* [code] */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/doc/examples/tutorial2.c000066400000000000000000000024121511204443500251510ustar00rootroot00000000000000/* [title] \ref page_tutorial2 [title] */ /* [code] */ #include static void registry_event_global(void *data, uint32_t id, uint32_t permissions, const char *type, uint32_t version, const struct spa_dict *props) { printf("object: id:%u type:%s/%d\n", id, type, version); } static const struct pw_registry_events registry_events = { PW_VERSION_REGISTRY_EVENTS, .global = registry_event_global, }; int main(int argc, char *argv[]) { struct pw_main_loop *loop; struct pw_context *context; struct pw_core *core; struct pw_registry *registry; struct spa_hook registry_listener; pw_init(&argc, &argv); loop = pw_main_loop_new(NULL /* properties */); context = pw_context_new(pw_main_loop_get_loop(loop), NULL /* properties */, 0 /* user_data size */); core = pw_context_connect(context, NULL /* properties */, 0 /* user_data size */); registry = pw_core_get_registry(core, PW_VERSION_REGISTRY, 0 /* user_data size */); spa_zero(registry_listener); pw_registry_add_listener(registry, ®istry_listener, ®istry_events, NULL); pw_main_loop_run(loop); pw_proxy_destroy((struct pw_proxy*)registry); pw_core_disconnect(core); pw_context_destroy(context); pw_main_loop_destroy(loop); return 0; } /* [code] */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/doc/examples/tutorial3.c000066400000000000000000000037721511204443500251640ustar00rootroot00000000000000/* [title] \ref page_tutorial3 [title] */ /* [code] */ #include /* [roundtrip] */ struct roundtrip_data { int pending; struct pw_main_loop *loop; }; static void on_core_done(void *data, uint32_t id, int seq) { struct roundtrip_data *d = data; if (id == PW_ID_CORE && seq == d->pending) pw_main_loop_quit(d->loop); } static void roundtrip(struct pw_core *core, struct pw_main_loop *loop) { static const struct pw_core_events core_events = { PW_VERSION_CORE_EVENTS, .done = on_core_done, }; struct roundtrip_data d = { .loop = loop }; struct spa_hook core_listener; int err; pw_core_add_listener(core, &core_listener, &core_events, &d); d.pending = pw_core_sync(core, PW_ID_CORE, 0); if ((err = pw_main_loop_run(loop)) < 0) printf("main_loop_run error:%d!\n", err); spa_hook_remove(&core_listener); } /* [roundtrip] */ static void registry_event_global(void *data, uint32_t id, uint32_t permissions, const char *type, uint32_t version, const struct spa_dict *props) { printf("object: id:%u type:%s/%d\n", id, type, version); } static const struct pw_registry_events registry_events = { PW_VERSION_REGISTRY_EVENTS, .global = registry_event_global, }; int main(int argc, char *argv[]) { struct pw_main_loop *loop; struct pw_context *context; struct pw_core *core; struct pw_registry *registry; struct spa_hook registry_listener; pw_init(&argc, &argv); loop = pw_main_loop_new(NULL /* properties */); context = pw_context_new(pw_main_loop_get_loop(loop), NULL /* properties */, 0 /* user_data size */); core = pw_context_connect(context, NULL /* properties */, 0 /* user_data size */); registry = pw_core_get_registry(core, PW_VERSION_REGISTRY, 0 /* user_data size */); pw_registry_add_listener(registry, ®istry_listener, ®istry_events, NULL); roundtrip(core, loop); pw_proxy_destroy((struct pw_proxy*)registry); pw_core_disconnect(core); pw_context_destroy(context); pw_main_loop_destroy(loop); return 0; } /* [code] */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/doc/examples/tutorial4.c000066400000000000000000000053751511204443500251660ustar00rootroot00000000000000/* [title] \ref page_tutorial4 [title] */ /* [code] */ #include #include #include #define M_PI_M2 ( M_PI + M_PI ) #define DEFAULT_RATE 44100 #define DEFAULT_CHANNELS 2 #define DEFAULT_VOLUME 0.7 struct data { struct pw_main_loop *loop; struct pw_stream *stream; double accumulator; }; /* [on_process] */ static void on_process(void *userdata) { struct data *data = userdata; struct pw_buffer *b; struct spa_buffer *buf; int i, c, n_frames, stride; int16_t *dst, val; if ((b = pw_stream_dequeue_buffer(data->stream)) == NULL) { pw_log_warn("out of buffers: %m"); return; } buf = b->buffer; if ((dst = buf->datas[0].data) == NULL) return; stride = sizeof(int16_t) * DEFAULT_CHANNELS; n_frames = buf->datas[0].maxsize / stride; if (b->requested) n_frames = SPA_MIN(b->requested, n_frames); for (i = 0; i < n_frames; i++) { data->accumulator += M_PI_M2 * 440 / DEFAULT_RATE; if (data->accumulator >= M_PI_M2) data->accumulator -= M_PI_M2; /* sin() gives a value between -1.0 and 1.0, we first apply * the volume and then scale with 32767.0 to get a 16 bits value * between [-32767 32767]. * Another common method to convert a double to * 16 bits is to multiple by 32768.0 and then clamp to * [-32768 32767] to get the full 16 bits range. */ val = sin(data->accumulator) * DEFAULT_VOLUME * 32767.0; for (c = 0; c < DEFAULT_CHANNELS; c++) *dst++ = val; } buf->datas[0].chunk->offset = 0; buf->datas[0].chunk->stride = stride; buf->datas[0].chunk->size = n_frames * stride; pw_stream_queue_buffer(data->stream, b); } /* [on_process] */ static const struct pw_stream_events stream_events = { PW_VERSION_STREAM_EVENTS, .process = on_process, }; int main(int argc, char *argv[]) { struct data data = { 0, }; const struct spa_pod *params[1]; uint8_t buffer[1024]; struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); pw_init(&argc, &argv); data.loop = pw_main_loop_new(NULL); data.stream = pw_stream_new_simple( pw_main_loop_get_loop(data.loop), "audio-src", pw_properties_new( PW_KEY_MEDIA_TYPE, "Audio", PW_KEY_MEDIA_CATEGORY, "Playback", PW_KEY_MEDIA_ROLE, "Music", NULL), &stream_events, &data); params[0] = spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat, &SPA_AUDIO_INFO_RAW_INIT( .format = SPA_AUDIO_FORMAT_S16, .channels = DEFAULT_CHANNELS, .rate = DEFAULT_RATE )); pw_stream_connect(data.stream, PW_DIRECTION_OUTPUT, PW_ID_ANY, PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_MAP_BUFFERS | PW_STREAM_FLAG_RT_PROCESS, params, 1); pw_main_loop_run(data.loop); pw_stream_destroy(data.stream); pw_main_loop_destroy(data.loop); return 0; } /* [code] */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/doc/examples/tutorial5.c000066400000000000000000000067511511204443500251660ustar00rootroot00000000000000/* [title] \ref page_tutorial5 [title] */ /* [code] */ #include #include #include #include struct data { struct pw_main_loop *loop; struct pw_stream *stream; struct spa_video_info format; }; /* [on_process] */ static void on_process(void *userdata) { struct data *data = userdata; struct pw_buffer *b; struct spa_buffer *buf; if ((b = pw_stream_dequeue_buffer(data->stream)) == NULL) { pw_log_warn("out of buffers: %m"); return; } buf = b->buffer; if (buf->datas[0].data == NULL) return; /** copy frame data to screen */ printf("got a frame of size %d\n", buf->datas[0].chunk->size); pw_stream_queue_buffer(data->stream, b); } /* [on_process] */ static void on_param_changed(void *userdata, uint32_t id, const struct spa_pod *param) { struct data *data = userdata; if (param == NULL || id != SPA_PARAM_Format) return; if (spa_format_parse(param, &data->format.media_type, &data->format.media_subtype) < 0) return; if (data->format.media_type != SPA_MEDIA_TYPE_video || data->format.media_subtype != SPA_MEDIA_SUBTYPE_raw) return; if (spa_format_video_raw_parse(param, &data->format.info.raw) < 0) return; printf("got video format:\n"); printf(" format: %d (%s)\n", data->format.info.raw.format, spa_debug_type_find_name(spa_type_video_format, data->format.info.raw.format)); printf(" size: %dx%d\n", data->format.info.raw.size.width, data->format.info.raw.size.height); printf(" framerate: %d/%d\n", data->format.info.raw.framerate.num, data->format.info.raw.framerate.denom); /** prepare to render video of this size */ } static const struct pw_stream_events stream_events = { PW_VERSION_STREAM_EVENTS, .param_changed = on_param_changed, .process = on_process, }; int main(int argc, char *argv[]) { struct data data = { 0, }; const struct spa_pod *params[1]; uint8_t buffer[1024]; struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); struct pw_properties *props; pw_init(&argc, &argv); data.loop = pw_main_loop_new(NULL); props = pw_properties_new(PW_KEY_MEDIA_TYPE, "Video", PW_KEY_MEDIA_CATEGORY, "Capture", PW_KEY_MEDIA_ROLE, "Camera", NULL); if (argc > 1) pw_properties_set(props, PW_KEY_TARGET_OBJECT, argv[1]); data.stream = pw_stream_new_simple( pw_main_loop_get_loop(data.loop), "video-capture", props, &stream_events, &data); params[0] = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), SPA_FORMAT_VIDEO_format, SPA_POD_CHOICE_ENUM_Id(7, SPA_VIDEO_FORMAT_RGB, SPA_VIDEO_FORMAT_RGB, SPA_VIDEO_FORMAT_RGBA, SPA_VIDEO_FORMAT_RGBx, SPA_VIDEO_FORMAT_BGRx, SPA_VIDEO_FORMAT_YUY2, SPA_VIDEO_FORMAT_I420), SPA_FORMAT_VIDEO_size, SPA_POD_CHOICE_RANGE_Rectangle( &SPA_RECTANGLE(320, 240), &SPA_RECTANGLE(1, 1), &SPA_RECTANGLE(4096, 4096)), SPA_FORMAT_VIDEO_framerate, SPA_POD_CHOICE_RANGE_Fraction( &SPA_FRACTION(25, 1), &SPA_FRACTION(0, 1), &SPA_FRACTION(1000, 1))); pw_stream_connect(data.stream, PW_DIRECTION_INPUT, PW_ID_ANY, PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_MAP_BUFFERS, params, 1); pw_main_loop_run(data.loop); pw_stream_destroy(data.stream); pw_main_loop_destroy(data.loop); return 0; } /* [code] */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/doc/examples/tutorial6.c000066400000000000000000000043631511204443500251640ustar00rootroot00000000000000/* [title] \ref page_tutorial6 [title] */ /* [code] */ #include struct data { struct pw_main_loop *loop; struct pw_context *context; struct pw_core *core; struct pw_registry *registry; struct spa_hook registry_listener; struct pw_client *client; struct spa_hook client_listener; }; /* [client_info] */ static void client_info(void *object, const struct pw_client_info *info) { struct data *data = object; const struct spa_dict_item *item; printf("client: id:%u\n", info->id); printf("\tprops:\n"); spa_dict_for_each(item, info->props) printf("\t\t%s: \"%s\"\n", item->key, item->value); pw_main_loop_quit(data->loop); } static const struct pw_client_events client_events = { PW_VERSION_CLIENT_EVENTS, .info = client_info, }; /* [client_info] */ /* [registry_event_global] */ static void registry_event_global(void *_data, uint32_t id, uint32_t permissions, const char *type, uint32_t version, const struct spa_dict *props) { struct data *data = _data; if (data->client != NULL) return; if (strcmp(type, PW_TYPE_INTERFACE_Client) == 0) { data->client = pw_registry_bind(data->registry, id, type, PW_VERSION_CLIENT, 0); pw_client_add_listener(data->client, &data->client_listener, &client_events, data); } } /* [registry_event_global] */ static const struct pw_registry_events registry_events = { PW_VERSION_REGISTRY_EVENTS, .global = registry_event_global, }; int main(int argc, char *argv[]) { struct data data; spa_zero(data); pw_init(&argc, &argv); data.loop = pw_main_loop_new(NULL /* properties */ ); data.context = pw_context_new(pw_main_loop_get_loop(data.loop), NULL /* properties */ , 0 /* user_data size */ ); data.core = pw_context_connect(data.context, NULL /* properties */ , 0 /* user_data size */ ); data.registry = pw_core_get_registry(data.core, PW_VERSION_REGISTRY, 0 /* user_data size */ ); pw_registry_add_listener(data.registry, &data.registry_listener, ®istry_events, &data); pw_main_loop_run(data.loop); pw_proxy_destroy((struct pw_proxy *)data.client); pw_proxy_destroy((struct pw_proxy *)data.registry); pw_core_disconnect(data.core); pw_context_destroy(data.context); pw_main_loop_destroy(data.loop); return 0; } /* [code] */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/doc/examples/tutorial7.c000066400000000000000000000073451511204443500251700ustar00rootroot00000000000000/* [title] \ref page_tutorial7 [title] */ /* [code] */ #include #include #include #include #include #include #include #include struct data; struct port { struct data *data; }; struct data { struct pw_main_loop *loop; struct pw_filter *filter; struct port *in_port; struct port *out_port; }; /* [on_process] */ static void on_process(void *userdata, struct spa_io_position *position) { struct data *data = userdata; float *in, *out; uint32_t n_samples = position->clock.duration; pw_log_trace("do process %d", n_samples); in = pw_filter_get_dsp_buffer(data->in_port, n_samples); out = pw_filter_get_dsp_buffer(data->out_port, n_samples); if (in == NULL || out == NULL) return; /* Simple passthrough - copy input to output. * Here you could implement any audio processing: * - Filters (lowpass, highpass, bandpass) * - Effects (reverb, delay, distortion) * - Dynamic processing (compressor, limiter) * - Equalization * - etc. */ memcpy(out, in, n_samples * sizeof(float)); } /* [on_process] */ static const struct pw_filter_events filter_events = { PW_VERSION_FILTER_EVENTS, .process = on_process, }; static void do_quit(void *userdata, int signal_number) { struct data *data = userdata; pw_main_loop_quit(data->loop); } int main(int argc, char *argv[]) { struct data data = { 0, }; const struct spa_pod *params[1]; uint32_t n_params = 0; uint8_t buffer[1024]; struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); pw_init(&argc, &argv); /* make a main loop. If you already have another main loop, you can add * the fd of this pipewire mainloop to it. */ data.loop = pw_main_loop_new(NULL); pw_loop_add_signal(pw_main_loop_get_loop(data.loop), SIGINT, do_quit, &data); pw_loop_add_signal(pw_main_loop_get_loop(data.loop), SIGTERM, do_quit, &data); /* Create a simple filter, the simple filter manages the core and remote * objects for you if you don't need to deal with them. * * Pass your events and a user_data pointer as the last arguments. This * will inform you about the filter state. The most important event * you need to listen to is the process event where you need to process * the data. */ data.filter = pw_filter_new_simple( pw_main_loop_get_loop(data.loop), "audio-filter", pw_properties_new( PW_KEY_MEDIA_TYPE, "Audio", PW_KEY_MEDIA_CATEGORY, "Filter", PW_KEY_MEDIA_ROLE, "DSP", NULL), &filter_events, &data); /* make an audio DSP input port */ data.in_port = pw_filter_add_port(data.filter, PW_DIRECTION_INPUT, PW_FILTER_PORT_FLAG_MAP_BUFFERS, sizeof(struct port), pw_properties_new( PW_KEY_FORMAT_DSP, "32 bit float mono audio", PW_KEY_PORT_NAME, "input", NULL), NULL, 0); /* make an audio DSP output port */ data.out_port = pw_filter_add_port(data.filter, PW_DIRECTION_OUTPUT, PW_FILTER_PORT_FLAG_MAP_BUFFERS, sizeof(struct port), pw_properties_new( PW_KEY_FORMAT_DSP, "32 bit float mono audio", PW_KEY_PORT_NAME, "output", NULL), NULL, 0); /* Set processing latency information */ params[n_params++] = spa_process_latency_build(&b, SPA_PARAM_ProcessLatency, &SPA_PROCESS_LATENCY_INFO_INIT( .ns = 10 * SPA_NSEC_PER_MSEC )); /* Now connect this filter. We ask that our process function is * called in a realtime thread. */ if (pw_filter_connect(data.filter, PW_FILTER_FLAG_RT_PROCESS, params, n_params) < 0) { fprintf(stderr, "can't connect\n"); return -1; } /* and wait while we let things run */ pw_main_loop_run(data.loop); pw_filter_destroy(data.filter); pw_main_loop_destroy(data.loop); pw_deinit(); return 0; } /* [code] */pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/doc/input-filter-h.sh000077500000000000000000000033511511204443500244530ustar00rootroot00000000000000#!/usr/bin/env bash # # Doxygen input filter, which tries to fix documentation of callback # method macros. # # This is used for .h files. # FILENAME="$1" # Add \ingroup commands for the file, for each \addgroup in it BASEFILE=$(echo "$FILENAME" | sed -e 's@.*src/pipewire/@pipewire/@; s@.*spa/include/spa/@spa/@; s@.*src/test/@test/@;') printf "/** \\\\file\n\`%s\`\n" "$BASEFILE" sed -n -e '/.*\\addtogroup [a-zA-Z0-9_].*/ { s/.*addtogroup /\\ingroup /; p; }' < "$FILENAME" | sort | uniq echo " */" # Add \sa and \copydoc for (struct *methods) callback macros. # #define pw_core_add_listener(...) pw_core_method(c,add_listener,...) -> add \sa and \copydoc # #define spa_system_read(...) spa_system_method_r(c,read,...) -> add \sa and \copydoc # # Also: # Ensure all macros are included (also those defined inside a struct), # by adding /** \ingroup XXX */ before each definition. # Also ensure all opaque structs get included. # Strip SPA_FORMAT_ARG_FUNC(1) etc. things that confuse doxygen sed -e 's@^\(#define .*[[:space:]]\)\(.*_method\)\((.,[[:space:]]*\)\([a-z_]\+\)\(.*)[[:space:]]*\)$@\1\2\3\4\5 /**< \\copydoc \2s.\4\n\n\\sa \2s.\4 */@;' \ -e 's@^\(#define .*[[:space:]]\)\(.*_method\)\(_[rvs](.,[[:space:]]*\)\([a-z_]\+\)\(.*)[[:space:]]*\)$@\1\2\3\4\5 /**< \\copydoc \2s.\4\n\n\\sa \2s.\4 */@;' \ -e '/\\addtogroup/ { h; s@.*\\addtogroup \(.*\).*@/** \\ingroup \1 */@; x; }' \ -e '/#define \(PW\|SPA\)_[A-Z].*[^\\][ ]*$/ { x; p; x; }' \ -e 's@^\([ ]*struct \)\([a-zA-Z0-9_]*\)\(;.*\)$@/** \\struct \2 */\n\1\2\3@;' \ -e 's@^[ ]*SPA_FORMAT_ARG_FUNC([0-9, ]*)@@;' \ -e 's@[ ]*SPA_PRINTF_FUNC([0-9, ]*);@;@;' \ -e 's@^[ ]*SPA_WARN_UNUSED_RESULT@ @;' \ -e 's@ SPA_SENTINEL;@;@;' \ -e 's@ SPA_UNUSED,@,@;' \ < "$FILENAME" pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/doc/input-filter-md.py000077500000000000000000000110021511204443500246320ustar00rootroot00000000000000#!/usr/bin/env python3 # -*- mode: python; coding: utf-8; eval: (blacken-mode); -*- r"""input-filter-md.py FILENAME input-filter-md.py --index FILENAMES... Doxygen .md input filter that adds extended syntax. With --index, generates an index file. Assumes BUILD_DIR environment variable is set. @PAR@
(...) Adds an index item and expands to \anchor \par (...) @SECREF@
Expands to \secreflist \refitem ... ... \endsecreflist containing all index items from the specified section. # Section title @IDX@
[] Adds the section title to the index, and expands to an anchor # Section title {#key} The index keys can be used in \ref and have format {section}__{name} where the parts are converted to lowercase and _ replaces non-alphanumerics. """ import sys import re import os def index_key(section, name): key = f"{section}__{name}".lower() return re.sub(r"[^A-Za-z0-9_-]", "_", key) BUILD_DIR = os.environ["BUILD_DIR"] PAR_RE = r"^@PAR@\s+([^\s]*)[ \t]+(\S+)(.*)$" IDX_RE = r"^(#+)(.*)@IDX@[ \t]+(\S+)([ \t]+\S+)?[ \t]*$" SECREF_RE = r"^@SECREF@[ \t]+([^\n]*)[ \t]*$" def main(args): fn = args[0] with open(fn, "r") as f: text = f.read() def par(m): section = m.group(1) name = m.group(2) rest = m.group(3).strip() key = index_key(section, name) return f"\\anchor {key}\n\\par {name} {rest}" def idx(m): level = m.group(1) title = name = m.group(2).strip() section = m.group(3) alt = m.group(4) if title == title.upper(): name = name.capitalize() key = index_key(section, name) text = f"{level} {title} {{#{key}}}" if alt and alt.strip(): alt_key = index_key(section, alt.strip()) if alt_key != key: text += f"\n\\anchor {alt_key}" return text def secref(m): import os import json secs = m.group(1).split() with open(os.path.join(BUILD_DIR, "index.json"), "r") as f: index = json.load(f) items = {} for sec in secs: if sec not in index: print(f"{fn}: no index '{sec}'", file=sys.stderr) else: for name, key in index[sec].items(): if name in items: pkey, psec = items.pop(name) nname = f"{name} ({sec})" items[nname] = (key, sec) if pkey is not None: pname = f"{name} ({psec})" items[pname] = (pkey, psec) items[name] = (None, None) else: items[name] = (key, sec) text = [r"\secreflist"] for name, (key, sec) in sorted(items.items()): if key is not None: text.append(rf'\refitem {key} "{name}"') text.append(r"\endsecreflist") text = "\n".join(text) return f"{text}\n" text = re.sub(PAR_RE, par, text, flags=re.M) text = re.sub(IDX_RE, idx, text, flags=re.M) text = re.sub(SECREF_RE, secref, text, flags=re.M) print(text) def main_index(args): import json sections = {} for fn in set(args): with open(fn, "r") as f: load_index(sections, f.read()) result = {} for section, items in sections.items(): for name in items: key = index_key(section, name) result.setdefault(section, {})[name] = key with open(os.path.join(BUILD_DIR, "index.json"), "w") as f: json.dump(result, f) def load_index(sections, text): def par(m): section = m.group(1) name = m.group(2) sections.setdefault(section, []).append(name) return "" def idx(m): name = m.group(2).strip() section = m.group(3) alt = m.group(4) if name == name.upper(): name = name.capitalize() sections.setdefault(section, []).append(name) if alt and alt.strip(): sections.setdefault(section, []).append(alt.strip()) return "" text = re.sub(PAR_RE, par, text, flags=re.M) text = re.sub(IDX_RE, idx, text, flags=re.M) if __name__ == "__main__": if len(sys.argv) >= 2 and sys.argv[1] == "--index": main_index(sys.argv[2:]) elif len(sys.argv) == 2: main(sys.argv[1:]) else: print(__doc__.strip()) sys.exit(1) pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/doc/input-filter.py000077500000000000000000000027231511204443500242460ustar00rootroot00000000000000#!/usr/bin/env python3 # -*- mode: python; coding: utf-8; eval: (blacken-mode); -*- r""" Doxygen input filter that: - adds \privatesection to all files - removes macros - parses pulse_module_options and substitutes it into @pulse_module_options@ This is used for .c files, and causes Doxygen to not include any symbols from them, unless they also appeared in a header file. The Pulse module option parsing is used in documentation of Pulseaudio modules. """ import sys import re import os def main(): fn = sys.argv[1] with open(fn, "r") as f: text = f.read() text = re.sub("#define.*", "", text) if "@pulse_module_options@" in text: m = re.search( r"static const char[* ]*const pulse_module_options\s+=\s+(.*?\")\s*;\s*$", text, re.M | re.S, ) if m: res = [] for line in m.group(1).splitlines(): m = re.match(r"\s*\"\s*([a-z0-9_]+)\s*=\s*(.*)\"\s*$", line) if m: name = m.group(1) value = m.group(2).strip().strip("<>") res.append(f"- `{name}`: {value}") res = "\n * ".join(res) text = text.replace("@pulse_module_options@", res) if os.path.basename(fn).startswith("module-") and fn.endswith(".c"): text = re.sub(r"^ \* ##", r" * #", text, flags=re.M) print("/** \\privatesection */") print(text) if __name__ == "__main__": main() pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/doc/man-fixup.py000077500000000000000000000055151511204443500235320ustar00rootroot00000000000000#!/usr/bin/python3 # -*- mode: python; coding: utf-8; eval: (blacken-mode); -*- r""" Fetch right Doxygen man file, replace dummy parts, and fixup nroff """ import argparse import re import sys from subprocess import call from pathlib import Path def main(): p = argparse.ArgumentParser(description=__doc__.strip()) p.add_argument("htmldir", type=Path) p.add_argument("page") p.add_argument("name") p.add_argument("section") p.add_argument("version") args = p.parse_args() page, name, section, version = args.page, args.name, args.section, args.version mandir = args.htmldir / ".." / "man" / "man3" fn = mandir / f"{page}.3" # Doxygen < 1.9.7 names .md file output differently... if not fn.exists(): page2 = page.replace("page_man_", "md_doc_dox_programs_").replace("-", "_") fn = mandir / f"{page2}.3" if not fn.exists(): page2 = page.replace("page_man_", "md_doc_dox_config_").replace("-", "_") fn = mandir / f"{page2}.3" else: page2 = None try: with open(fn, "r") as f: text = f.read() except: print(f"ERROR: man file {fn} missing!", file=sys.stderr) call(["ls", "-R", str(args.htmldir / ".." / "man")], stdout=sys.stderr) raise text = text.replace(page, name) if page2 is not None: text = text.replace(page2, name) # Replace bad nroff header text = re.sub( r"^(\.TH[^\n]*)\n", rf'.TH "{name}" {section} "{version}" "PipeWire" \\" -*- nroff -*-\n', text, ) # Fixup name field (can't be done in Doxygen, otherwise HTML looks bac) text = re.sub( rf"^\.SH NAME\s*\n{name} \\- {name}\s*\n\.PP\n *", rf".SH NAME\n{name} \\- ", text, count=1, flags=re.M, ) # Add DESCRIPTION section if missing and NAME field has extra stuff if not re.search(r"^\.SH DESCRIPTION\s*\n", text): text = re.sub( r"^(.SH NAME\s*\n[^\.].*\n)\.PP\s*\n([^\.\n ]+)", r"\1.SH DESCRIPTION\n.PP\n\2", text, count=1, flags=re.M, ) # Upcase titles def upcase(m): return m.group(0).upper() text = re.sub(r"^\.SH .*?$", upcase, text, flags=re.M) # Replace PW_KEY_*, SPA_KEY_* by their values def pw_key(m): key = m.group(0) key = key.replace("PW_KEY_", "").lower().replace("_", ".") if key in ("protocol", "access", "client.access") or key.startswith("sec."): return f"pipewire.{key}" return key def spa_key(m): key = m.group(0) return key.replace("SPA_KEY_", "").lower().replace("_", ".") text = re.sub(r"PW_KEY_[A-Z_]+", pw_key, text, flags=re.S) text = re.sub(r"SPA_KEY_[A-Z_]+", spa_key, text, flags=re.S) print(text) if __name__ == "__main__": main() pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/doc/meson.build000066400000000000000000000232721511204443500234130ustar00rootroot00000000000000fs = import('fs') doxyfile_conf = configuration_data() doxyfile_conf.set('PACKAGE_NAME', meson.project_name()) doxyfile_conf.set('PACKAGE_VERSION', meson.project_version()) doxyfile_conf.set('top_srcdir', meson.project_source_root()) doxyfile_conf.set('top_builddir', meson.project_build_root()) doxyfile_conf.set('output_directory', meson.current_build_dir()) doc_prefix_value = get_option('doc-prefix-value') doc_sysconfdir_value = get_option('doc-sysconfdir-value') if doc_prefix_value == '' and doc_sysconfdir_value == '' doc_spa_plugindir = spa_plugindir doc_pipewire_configdir = pipewire_configdir doc_pipewire_confdatadir = pipewire_confdatadir else if doc_prefix_value == '' doc_prefix_value = get_option('prefix') endif if doc_sysconfdir_value == '' doc_sysconfdir_value = get_option('sysconfdir') endif doc_spa_plugindir = doc_prefix_value / get_option('libdir') / spa_name doc_pipewire_configdir = doc_prefix_value / doc_sysconfdir_value / 'pipewire' doc_pipewire_confdatadir = doc_prefix_value / get_option('datadir') / 'pipewire' endif doxygen_env = environment() doxygen_env.set('PACKAGE_NAME', meson.project_name()) doxygen_env.set('PACKAGE_VERSION', meson.project_version()) doxygen_env.set('PACKAGE_URL', 'https://pipewire.org') doxygen_env.set('PACKAGE_BUGREPORT', 'https://gitlab.freedesktop.org/pipewire/pipewire/issues') doxygen_env.set('PIPEWIRE_CONFIG_DIR', doc_pipewire_configdir) doxygen_env.set('PIPEWIRE_CONFDATADIR', doc_pipewire_confdatadir) doxygen_env.set('SPA_PLUGINDIR', doc_spa_plugindir) doxygen_env.set('BUILD_DIR', meson.current_build_dir()) dot_found = find_program('dot', required: false).found() summary({'dot (used with doxygen)': dot_found}, bool_yn: true, section: 'Optional programs') if dot_found doxyfile_conf.set('HAVE_DOT', 'YES') else doxyfile_conf.set('HAVE_DOT', 'NO') endif # Note: order here is how doxygen will expose the pages in the sidebar # tree.dox should be first to determine the ordering. extra_docs = [ 'tree.dox', 'dox/index.dox', 'dox/overview.dox', 'dox/modules.dox', 'dox/pulse-modules.dox', 'dox/programs/index.md', 'dox/config/index.md', 'dox/config/xref.md', 'dox/internals/index.dox', 'dox/internals/design.dox', 'dox/internals/access.dox', 'dox/internals/latency.dox', 'dox/internals/tag.dox', 'dox/internals/midi.dox', 'dox/internals/portal.dox', 'dox/internals/daemon.dox', 'dox/internals/library.dox', 'dox/internals/session-manager.dox', 'dox/internals/objects.dox', 'dox/internals/audio.dox', 'dox/internals/scheduling.dox', 'dox/internals/driver.dox', 'dox/internals/protocol.dox', 'dox/internals/pulseaudio.dox', 'dox/internals/dma-buf.dox', 'dox/tutorial/index.dox', 'dox/tutorial/tutorial1.dox', 'dox/tutorial/tutorial2.dox', 'dox/tutorial/tutorial3.dox', 'dox/tutorial/tutorial4.dox', 'dox/tutorial/tutorial5.dox', 'dox/tutorial/tutorial6.dox', 'dox/tutorial/tutorial7.dox', 'dox/api/index.dox', 'dox/api/spa-index.dox', 'dox/api/spa-plugins.dox', 'dox/api/spa-design.dox', 'dox/api/spa-pod.dox', 'dox/api/spa-buffer.dox', ] manpage_docs = [ 'dox/config/pipewire-pulse.conf.5.md', 'dox/config/pipewire.conf.5.md', 'dox/config/pipewire-client.conf.5.md', 'dox/config/pipewire-jack.conf.5.md', 'dox/config/pipewire-props.7.md', 'dox/config/pipewire-filter-chain.conf.5.md', 'dox/config/pipewire-pulse-modules.7.md', 'dox/config/libpipewire-modules.7.md', 'dox/programs/pipewire-pulse.1.md', 'dox/programs/pipewire.1.md', 'dox/programs/pw-cat.1.md', 'dox/programs/pw-cli.1.md', 'dox/programs/pw-config.1.md', 'dox/programs/pw-container.1.md', 'dox/programs/pw-dot.1.md', 'dox/programs/pw-dump.1.md', 'dox/programs/pw-jack.1.md', 'dox/programs/pw-link.1.md', 'dox/programs/pw-loopback.1.md', 'dox/programs/pw-metadata.1.md', 'dox/programs/pw-mididump.1.md', 'dox/programs/pw-mon.1.md', 'dox/programs/pw-profiler.1.md', 'dox/programs/pw-reserve.1.md', 'dox/programs/pw-top.1.md', 'dox/programs/pw-v4l2.1.md', 'dox/programs/spa-acp-tool.1.md', 'dox/programs/spa-inspect.1.md', 'dox/programs/spa-json-dump.1.md', 'dox/programs/spa-monitor.1.md', 'dox/programs/spa-resample.1.md', ] manpages = [] foreach m : manpage_docs name = fs.stem(fs.name(m)) pagepart = name.replace('.', '_') manpages += [[name, f'page_man_@pagepart@']] extra_docs += m endforeach inputs = [] foreach extra : extra_docs inputs += meson.project_source_root() / 'doc' / extra endforeach foreach h : pipewire_headers inputs += meson.project_source_root() / 'src' / 'pipewire' / h endforeach foreach h : pipewire_ext_headers inputs += meson.project_source_root() / 'src' / 'pipewire' / 'extensions' / h endforeach foreach h : pipewire_ext_sm_headers inputs += meson.project_source_root() / 'src' / 'pipewire' / 'extensions' / h endforeach foreach h : pipewire_sources inputs += meson.project_source_root() / 'src' / 'pipewire' / h endforeach foreach h : module_sources inputs += meson.project_source_root() / 'src' / 'modules' / h endforeach foreach h : pipewire_module_protocol_pulse_sources inputs += meson.project_source_root() / 'src' / 'modules' / h endforeach input_dirs = [ meson.project_source_root() / 'spa' / 'include' / 'spa' ] path_prefixes = [ meson.project_source_root() / 'src', meson.project_source_root() / 'spa' / 'include', meson.project_source_root(), ] cssfiles = [ meson.project_source_root() / 'doc' / 'doxygen-awesome.css', meson.project_source_root() / 'doc' / 'custom.css' ] # Example files (in order from simple to esoteric) example_files = [ 'tutorial1.c', 'tutorial2.c', 'tutorial3.c', 'tutorial4.c', 'tutorial5.c', 'tutorial6.c', 'tutorial7.c', ] example_dep_files = [] foreach h : example_files example_dep_files += ['examples/' + h] endforeach foreach h : examples example_files += [h + '.c'] example_dep_files += ['../src/examples/' + h + '.c'] endforeach foreach h : spa_examples example_files += ['spa/examples/' + h + '.c'] example_dep_files += ['../spa/examples/' + h + '.c'] endforeach example_doxygen = [] example_ref = [] foreach h : example_files example_doxygen += ['\\example ' + h, '\\snippet{doc} ' + h + ' title', '
', '\\snippet{doc} ' + h + ' doc'] example_ref += ['- \\ref ' + h + ' "": \snippet{doc} ' + h + ' title'] endforeach examples_dox_conf = configuration_data() examples_dox_conf.set('example_doxygen', '\n'.join(example_doxygen)) examples_dox_conf.set('example_ref', '\n'.join(example_ref)) examples_dox = configure_file(input: 'examples.dox.in', output: 'examples.dox', configuration: examples_dox_conf) input_dirs += [ 'doc/examples.dox' ] module_manpage_list = [] foreach m : module_sources name = fs.stem(m) pagepart = name.replace('-', '_') module_manpage_list += f'\\ref page_@pagepart@ "libpipewire-@name@(7)"' manpages += [[f'libpipewire-@name@.7', f'page_@pagepart@']] endforeach doxygen_env.set('LIBPIPEWIRE_MODULES', '
  • ' + '
  • '.join(module_manpage_list) + '
') pulse_module_manpage_list = [] foreach m : pipewire_module_protocol_pulse_sources name = fs.stem(fs.name(m)) if m.contains('/modules/') and name.startswith('module-') pagepart = name.replace('-', '_') pulse_module_manpage_list += f'\\ref page_pulse_@pagepart@ "pipewire-pulse-@name@(7)"' manpages += [[f'pipewire-pulse-@name@.7', f'page_pulse_@pagepart@']] endif endforeach doxygen_env.set('PIPEWIRE_PULSE_MODULES', '
  • ' + '
  • '.join(pulse_module_manpage_list) + '
') doxygen_layout = meson.project_source_root() / 'doc' / 'DoxygenLayout.xml' doxygen_filter_c = meson.project_source_root() / 'doc' / 'input-filter.py' doxygen_filter_h = meson.project_source_root() / 'doc' / 'input-filter-h.sh' doxygen_filter_md = meson.project_source_root() / 'doc' / 'input-filter-md.py' doxyfile_conf.set('inputs', ' '.join(inputs + input_dirs)) doxyfile_conf.set('cssfiles', ' '.join(cssfiles)) doxyfile_conf.set('layout', doxygen_layout) doxyfile_conf.set('path_prefixes', ' '.join(path_prefixes)) doxyfile_conf.set('c_input_filter', doxygen_filter_c) doxyfile_conf.set('h_input_filter', doxygen_filter_h) doxyfile_conf.set('md_input_filter', doxygen_filter_md) doxyfile = configure_file(input: 'Doxyfile.in', output: 'Doxyfile', configuration: doxyfile_conf) docdir = get_option('docdir') if docdir == '' docdir = pipewire_datadir / 'doc' / meson.project_name() endif index_json = custom_target('index.json', command: [ doxygen_filter_md, '--index', '@INPUT@' ], input: extra_docs + manpage_docs, output: 'index.json', env: doxygen_env ) html_target = custom_target('pipewire-docs', input: [ doxyfile, doxygen_layout, example_dep_files, examples_dox, doxygen_filter_c, doxygen_filter_h, index_json ] + inputs + cssfiles, output: [ 'html' ], command: [ doxygen, doxyfile ], env: doxygen_env, install: install_docs, install_tag: 'doc', install_dir: docdir) man_fixup = files('man-fixup.py') manfiles = [] foreach m : manpages file = m.get(0) page = m.get(1) name = fs.stem(file) section = file.split('.').get(-1) manfiles += custom_target(file, command : [ python, man_fixup, '@INPUT@', page, name, section, meson.project_version() ], output : file, input : html_target, depend_files : [ man_fixup ], capture : true, install : install_man, install_tag: 'man', install_dir : get_option('mandir') / 'man' + section ) endforeach pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/doc/tree.dox000066400000000000000000000051231511204443500227170ustar00rootroot00000000000000/** This determines the ordering of items in Doxygen sidebar. \defgroup api_pw_core Core API \brief PipeWire Core API \{ \addtogroup pw_pipewire \addtogroup pw_main_loop \addtogroup pw_context \addtogroup pw_client \addtogroup pw_core \addtogroup pw_device \addtogroup pw_factory \addtogroup pw_link \addtogroup pw_loop \addtogroup pw_module \addtogroup pw_node \addtogroup pw_permission \addtogroup pw_port \addtogroup pw_proxy \addtogroup pw_registry \addtogroup pw_type \addtogroup pw_keys \} \defgroup api_pw_impl Implementation API \brief PipeWire Object Implementation API \{ \addtogroup pw_impl_client \addtogroup pw_impl_core \addtogroup pw_impl_device \addtogroup pw_impl_factory \addtogroup pw_impl_link \addtogroup pw_impl_metadata \addtogroup pw_impl_module \addtogroup pw_impl_node \addtogroup pw_impl_port \addtogroup pw_buffers \addtogroup pw_control \addtogroup pw_data_loop \addtogroup pw_global \addtogroup pw_protocol \addtogroup pw_resource \addtogroup pw_thread_loop \addtogroup pw_timer_queue \addtogroup pw_work_queue \} \defgroup api_pw_util Utilities \brief PipeWire Utilities \{ \addtogroup pw_array \addtogroup pw_conf \addtogroup pw_gettext \addtogroup pw_log \addtogroup pw_map \addtogroup pw_memblock \addtogroup pw_properties \addtogroup pw_thread \addtogroup pw_utils \} \defgroup api_pw_ext Extensions \brief PipeWire Extensions \{ \addtogroup pw_client_node \addtogroup pw_metadata \addtogroup pw_profiler \addtogroup pw_protocol_native \addtogroup pw_session_manager \} \defgroup api_spa SPA \brief Simple Plugin API \{ \addtogroup spa_buffer \addtogroup spa_control \addtogroup spa_debug \addtogroup spa_device \addtogroup spa_graph \addtogroup spa_node \addtogroup spa_param \addtogroup spa_pod \defgroup spa_utils Utilities Utility data structures, macros, etc. \{ \addtogroup spa_ansi \addtogroup spa_utils_defs \addtogroup spa_dict \addtogroup spa_list \addtogroup spa_hooks \addtogroup spa_interfaces \addtogroup spa_json \addtogroup spa_json_pod \addtogroup spa_keys \addtogroup spa_names \addtogroup spa_result \addtogroup spa_ringbuffer \addtogroup spa_string \addtogroup spa_types \} \defgroup spa_support Support Support interfaces provided by host \{ \addtogroup spa_cpu \addtogroup spa_dbus \addtogroup spa_i18n \addtogroup spa_log \addtogroup spa_loop \addtogroup spa_handle \addtogroup spa_plugin_loader \addtogroup spa_system \addtogroup spa_thread \} \} \defgroup pw_stream Stream \{ \} \defgroup pw_filter Filter \{ \} \page page_overview \page page_config \page page_programs \page page_modules \page page_pulse_modules \page page_internals \page page_api \page page_tutorial */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/include/000077500000000000000000000000001511204443500221215ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/include/valgrind/000077500000000000000000000000001511204443500237275ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/include/valgrind/memcheck.h000066400000000000000000000343521511204443500256630ustar00rootroot00000000000000 /* ---------------------------------------------------------------- Notice that the following BSD-style license applies to this one file (memcheck.h) only. The rest of Valgrind is licensed under the terms of the GNU General Public License, version 2, unless otherwise indicated. See the COPYING file in the source distribution for details. ---------------------------------------------------------------- This file is part of MemCheck, a heavyweight Valgrind tool for detecting memory errors. Copyright (C) 2000-2013 Julian Seward. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are 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. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 3. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 4. The name of the author may not be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS 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 AUTHOR 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. ---------------------------------------------------------------- Notice that the above BSD-style license applies to this one file (memcheck.h) only. The entire rest of Valgrind is licensed under the terms of the GNU General Public License, version 2. See the COPYING file in the source distribution for details. ---------------------------------------------------------------- */ #ifndef __MEMCHECK_H #define __MEMCHECK_H /* This file is for inclusion into client (your!) code. You can use these macros to manipulate and query memory permissions inside your own programs. See comment near the top of valgrind.h on how to use them. */ #include "valgrind.h" /* !! ABIWARNING !! ABIWARNING !! ABIWARNING !! ABIWARNING !! This enum comprises an ABI exported by Valgrind to programs which use client requests. DO NOT CHANGE THE ORDER OF THESE ENTRIES, NOR DELETE ANY -- add new ones at the end. */ typedef enum { VG_USERREQ__MAKE_MEM_NOACCESS = VG_USERREQ_TOOL_BASE('M','C'), VG_USERREQ__MAKE_MEM_UNDEFINED, VG_USERREQ__MAKE_MEM_DEFINED, VG_USERREQ__DISCARD, VG_USERREQ__CHECK_MEM_IS_ADDRESSABLE, VG_USERREQ__CHECK_MEM_IS_DEFINED, VG_USERREQ__DO_LEAK_CHECK, VG_USERREQ__COUNT_LEAKS, VG_USERREQ__GET_VBITS, VG_USERREQ__SET_VBITS, VG_USERREQ__CREATE_BLOCK, VG_USERREQ__MAKE_MEM_DEFINED_IF_ADDRESSABLE, /* Not next to VG_USERREQ__COUNT_LEAKS because it was added later. */ VG_USERREQ__COUNT_LEAK_BLOCKS, VG_USERREQ__ENABLE_ADDR_ERROR_REPORTING_IN_RANGE, VG_USERREQ__DISABLE_ADDR_ERROR_REPORTING_IN_RANGE, /* This is just for memcheck's internal use - don't use it */ _VG_USERREQ__MEMCHECK_RECORD_OVERLAP_ERROR = VG_USERREQ_TOOL_BASE('M','C') + 256 } Vg_MemCheckClientRequest; /* Client-code macros to manipulate the state of memory. */ /* Mark memory at _qzz_addr as unaddressable for _qzz_len bytes. */ #define VALGRIND_MAKE_MEM_NOACCESS(_qzz_addr,_qzz_len) \ VALGRIND_DO_CLIENT_REQUEST_EXPR(0 /* default return */, \ VG_USERREQ__MAKE_MEM_NOACCESS, \ (_qzz_addr), (_qzz_len), 0, 0, 0) /* Similarly, mark memory at _qzz_addr as addressable but undefined for _qzz_len bytes. */ #define VALGRIND_MAKE_MEM_UNDEFINED(_qzz_addr,_qzz_len) \ VALGRIND_DO_CLIENT_REQUEST_EXPR(0 /* default return */, \ VG_USERREQ__MAKE_MEM_UNDEFINED, \ (_qzz_addr), (_qzz_len), 0, 0, 0) /* Similarly, mark memory at _qzz_addr as addressable and defined for _qzz_len bytes. */ #define VALGRIND_MAKE_MEM_DEFINED(_qzz_addr,_qzz_len) \ VALGRIND_DO_CLIENT_REQUEST_EXPR(0 /* default return */, \ VG_USERREQ__MAKE_MEM_DEFINED, \ (_qzz_addr), (_qzz_len), 0, 0, 0) /* Similar to VALGRIND_MAKE_MEM_DEFINED except that addressability is not altered: bytes which are addressable are marked as defined, but those which are not addressable are left unchanged. */ #define VALGRIND_MAKE_MEM_DEFINED_IF_ADDRESSABLE(_qzz_addr,_qzz_len) \ VALGRIND_DO_CLIENT_REQUEST_EXPR(0 /* default return */, \ VG_USERREQ__MAKE_MEM_DEFINED_IF_ADDRESSABLE, \ (_qzz_addr), (_qzz_len), 0, 0, 0) /* Create a block-description handle. The description is an ascii string which is included in any messages pertaining to addresses within the specified memory range. Has no other effect on the properties of the memory range. */ #define VALGRIND_CREATE_BLOCK(_qzz_addr,_qzz_len, _qzz_desc) \ VALGRIND_DO_CLIENT_REQUEST_EXPR(0 /* default return */, \ VG_USERREQ__CREATE_BLOCK, \ (_qzz_addr), (_qzz_len), (_qzz_desc), \ 0, 0) /* Discard a block-description-handle. Returns 1 for an invalid handle, 0 for a valid handle. */ #define VALGRIND_DISCARD(_qzz_blkindex) \ VALGRIND_DO_CLIENT_REQUEST_EXPR(0 /* default return */, \ VG_USERREQ__DISCARD, \ 0, (_qzz_blkindex), 0, 0, 0) /* Client-code macros to check the state of memory. */ /* Check that memory at _qzz_addr is addressable for _qzz_len bytes. If suitable addressability is not established, Valgrind prints an error message and returns the address of the first offending byte. Otherwise it returns zero. */ #define VALGRIND_CHECK_MEM_IS_ADDRESSABLE(_qzz_addr,_qzz_len) \ VALGRIND_DO_CLIENT_REQUEST_EXPR(0, \ VG_USERREQ__CHECK_MEM_IS_ADDRESSABLE, \ (_qzz_addr), (_qzz_len), 0, 0, 0) /* Check that memory at _qzz_addr is addressable and defined for _qzz_len bytes. If suitable addressability and definedness are not established, Valgrind prints an error message and returns the address of the first offending byte. Otherwise it returns zero. */ #define VALGRIND_CHECK_MEM_IS_DEFINED(_qzz_addr,_qzz_len) \ VALGRIND_DO_CLIENT_REQUEST_EXPR(0, \ VG_USERREQ__CHECK_MEM_IS_DEFINED, \ (_qzz_addr), (_qzz_len), 0, 0, 0) /* Use this macro to force the definedness and addressability of an lvalue to be checked. If suitable addressability and definedness are not established, Valgrind prints an error message and returns the address of the first offending byte. Otherwise it returns zero. */ #define VALGRIND_CHECK_VALUE_IS_DEFINED(__lvalue) \ VALGRIND_CHECK_MEM_IS_DEFINED( \ (volatile unsigned char *)&(__lvalue), \ (unsigned long)(sizeof (__lvalue))) /* Do a full memory leak check (like --leak-check=full) mid-execution. */ #define VALGRIND_DO_LEAK_CHECK \ VALGRIND_DO_CLIENT_REQUEST_STMT(VG_USERREQ__DO_LEAK_CHECK, \ 0, 0, 0, 0, 0) /* Same as VALGRIND_DO_LEAK_CHECK but only showing the entries for which there was an increase in leaked bytes or leaked nr of blocks since the previous leak search. */ #define VALGRIND_DO_ADDED_LEAK_CHECK \ VALGRIND_DO_CLIENT_REQUEST_STMT(VG_USERREQ__DO_LEAK_CHECK, \ 0, 1, 0, 0, 0) /* Same as VALGRIND_DO_ADDED_LEAK_CHECK but showing entries with increased or decreased leaked bytes/blocks since previous leak search. */ #define VALGRIND_DO_CHANGED_LEAK_CHECK \ VALGRIND_DO_CLIENT_REQUEST_STMT(VG_USERREQ__DO_LEAK_CHECK, \ 0, 2, 0, 0, 0) /* Do a summary memory leak check (like --leak-check=summary) mid-execution. */ #define VALGRIND_DO_QUICK_LEAK_CHECK \ VALGRIND_DO_CLIENT_REQUEST_STMT(VG_USERREQ__DO_LEAK_CHECK, \ 1, 0, 0, 0, 0) /* Return number of leaked, dubious, reachable and suppressed bytes found by all previous leak checks. They must be lvalues. */ #define VALGRIND_COUNT_LEAKS(leaked, dubious, reachable, suppressed) \ /* For safety on 64-bit platforms we assign the results to private unsigned long variables, then assign these to the lvalues the user specified, which works no matter what type 'leaked', 'dubious', etc are. We also initialise '_qzz_leaked', etc because VG_USERREQ__COUNT_LEAKS doesn't mark the values returned as defined. */ \ { \ unsigned long _qzz_leaked = 0, _qzz_dubious = 0; \ unsigned long _qzz_reachable = 0, _qzz_suppressed = 0; \ VALGRIND_DO_CLIENT_REQUEST_STMT( \ VG_USERREQ__COUNT_LEAKS, \ &_qzz_leaked, &_qzz_dubious, \ &_qzz_reachable, &_qzz_suppressed, 0); \ leaked = _qzz_leaked; \ dubious = _qzz_dubious; \ reachable = _qzz_reachable; \ suppressed = _qzz_suppressed; \ } /* Return number of leaked, dubious, reachable and suppressed bytes found by all previous leak checks. They must be lvalues. */ #define VALGRIND_COUNT_LEAK_BLOCKS(leaked, dubious, reachable, suppressed) \ /* For safety on 64-bit platforms we assign the results to private unsigned long variables, then assign these to the lvalues the user specified, which works no matter what type 'leaked', 'dubious', etc are. We also initialise '_qzz_leaked', etc because VG_USERREQ__COUNT_LEAKS doesn't mark the values returned as defined. */ \ { \ unsigned long _qzz_leaked = 0, _qzz_dubious = 0; \ unsigned long _qzz_reachable = 0, _qzz_suppressed = 0; \ VALGRIND_DO_CLIENT_REQUEST_STMT( \ VG_USERREQ__COUNT_LEAK_BLOCKS, \ &_qzz_leaked, &_qzz_dubious, \ &_qzz_reachable, &_qzz_suppressed, 0); \ leaked = _qzz_leaked; \ dubious = _qzz_dubious; \ reachable = _qzz_reachable; \ suppressed = _qzz_suppressed; \ } /* Get the validity data for addresses [zza..zza+zznbytes-1] and copy it into the provided zzvbits array. Return values: 0 if not running on valgrind 1 success 2 [previously indicated unaligned arrays; these are now allowed] 3 if any parts of zzsrc/zzvbits are not addressable. The metadata is not copied in cases 0, 2 or 3 so it should be impossible to segfault your system by using this call. */ #define VALGRIND_GET_VBITS(zza,zzvbits,zznbytes) \ (unsigned)VALGRIND_DO_CLIENT_REQUEST_EXPR(0, \ VG_USERREQ__GET_VBITS, \ (const char*)(zza), \ (char*)(zzvbits), \ (zznbytes), 0, 0) /* Set the validity data for addresses [zza..zza+zznbytes-1], copying it from the provided zzvbits array. Return values: 0 if not running on valgrind 1 success 2 [previously indicated unaligned arrays; these are now allowed] 3 if any parts of zza/zzvbits are not addressable. The metadata is not copied in cases 0, 2 or 3 so it should be impossible to segfault your system by using this call. */ #define VALGRIND_SET_VBITS(zza,zzvbits,zznbytes) \ (unsigned)VALGRIND_DO_CLIENT_REQUEST_EXPR(0, \ VG_USERREQ__SET_VBITS, \ (const char*)(zza), \ (const char*)(zzvbits), \ (zznbytes), 0, 0 ) /* Disable and re-enable reporting of addressing errors in the specified address range. */ #define VALGRIND_DISABLE_ADDR_ERROR_REPORTING_IN_RANGE(_qzz_addr,_qzz_len) \ VALGRIND_DO_CLIENT_REQUEST_EXPR(0 /* default return */, \ VG_USERREQ__DISABLE_ADDR_ERROR_REPORTING_IN_RANGE, \ (_qzz_addr), (_qzz_len), 0, 0, 0) #define VALGRIND_ENABLE_ADDR_ERROR_REPORTING_IN_RANGE(_qzz_addr,_qzz_len) \ VALGRIND_DO_CLIENT_REQUEST_EXPR(0 /* default return */, \ VG_USERREQ__ENABLE_ADDR_ERROR_REPORTING_IN_RANGE, \ (_qzz_addr), (_qzz_len), 0, 0, 0) #endif pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/include/valgrind/valgrind.h000066400000000000000000013751271511204443500257260ustar00rootroot00000000000000/* -*- c -*- ---------------------------------------------------------------- Notice that the following BSD-style license applies to this one file (valgrind.h) only. The rest of Valgrind is licensed under the terms of the GNU General Public License, version 2, unless otherwise indicated. See the COPYING file in the source distribution for details. ---------------------------------------------------------------- This file is part of Valgrind, a dynamic binary instrumentation framework. Copyright (C) 2000-2017 Julian Seward. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are 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. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 3. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 4. The name of the author may not be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS 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 AUTHOR 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. ---------------------------------------------------------------- Notice that the above BSD-style license applies to this one file (valgrind.h) only. The entire rest of Valgrind is licensed under the terms of the GNU General Public License, version 2. See the COPYING file in the source distribution for details. ---------------------------------------------------------------- */ /* This file is for inclusion into client (your!) code. You can use these macros to manipulate and query Valgrind's execution inside your own programs. The resulting executables will still run without Valgrind, just a little bit more slowly than they otherwise would, but otherwise unchanged. When not running on valgrind, each client request consumes very few (eg. 7) instructions, so the resulting performance loss is negligible unless you plan to execute client requests millions of times per second. Nevertheless, if that is still a problem, you can compile with the NVALGRIND symbol defined (gcc -DNVALGRIND) so that client requests are not even compiled in. */ #ifndef __VALGRIND_H #define __VALGRIND_H /* ------------------------------------------------------------------ */ /* VERSION NUMBER OF VALGRIND */ /* ------------------------------------------------------------------ */ /* Specify Valgrind's version number, so that user code can conditionally compile based on our version number. Note that these were introduced at version 3.6 and so do not exist in version 3.5 or earlier. The recommended way to use them to check for "version X.Y or later" is (eg) #if defined(__VALGRIND_MAJOR__) && defined(__VALGRIND_MINOR__) \ && (__VALGRIND_MAJOR__ > 3 \ || (__VALGRIND_MAJOR__ == 3 && __VALGRIND_MINOR__ >= 6)) */ #define __VALGRIND_MAJOR__ 3 #define __VALGRIND_MINOR__ 15 #include /* Nb: this file might be included in a file compiled with -ansi. So we can't use C++ style "//" comments nor the "asm" keyword (instead use "__asm__"). */ /* Derive some tags indicating what the target platform is. Note that in this file we're using the compiler's CPP symbols for identifying architectures, which are different to the ones we use within the rest of Valgrind. Note, __powerpc__ is active for both 32 and 64-bit PPC, whereas __powerpc64__ is only active for the latter (on Linux, that is). Misc note: how to find out what's predefined in gcc by default: gcc -Wp,-dM somefile.c */ #undef PLAT_x86_darwin #undef PLAT_amd64_darwin #undef PLAT_x86_win32 #undef PLAT_amd64_win64 #undef PLAT_x86_linux #undef PLAT_amd64_linux #undef PLAT_ppc32_linux #undef PLAT_ppc64be_linux #undef PLAT_ppc64le_linux #undef PLAT_arm_linux #undef PLAT_arm64_linux #undef PLAT_s390x_linux #undef PLAT_mips32_linux #undef PLAT_mips64_linux #undef PLAT_x86_solaris #undef PLAT_amd64_solaris #if defined(__APPLE__) && defined(__i386__) # define PLAT_x86_darwin 1 #elif defined(__APPLE__) && defined(__x86_64__) # define PLAT_amd64_darwin 1 #elif (defined(__MINGW32__) && !defined(__MINGW64__)) \ || defined(__CYGWIN32__) \ || (defined(_WIN32) && defined(_M_IX86)) # define PLAT_x86_win32 1 #elif defined(__MINGW64__) \ || (defined(_WIN64) && defined(_M_X64)) # define PLAT_amd64_win64 1 #elif defined(__linux__) && defined(__i386__) # define PLAT_x86_linux 1 #elif defined(__linux__) && defined(__x86_64__) && !defined(__ILP32__) # define PLAT_amd64_linux 1 #elif defined(__linux__) && defined(__powerpc__) && !defined(__powerpc64__) # define PLAT_ppc32_linux 1 #elif defined(__linux__) && defined(__powerpc__) && defined(__powerpc64__) && _CALL_ELF != 2 /* Big Endian uses ELF version 1 */ # define PLAT_ppc64be_linux 1 #elif defined(__linux__) && defined(__powerpc__) && defined(__powerpc64__) && _CALL_ELF == 2 /* Little Endian uses ELF version 2 */ # define PLAT_ppc64le_linux 1 #elif defined(__linux__) && defined(__arm__) && !defined(__aarch64__) # define PLAT_arm_linux 1 #elif defined(__linux__) && defined(__aarch64__) && !defined(__arm__) # define PLAT_arm64_linux 1 #elif defined(__linux__) && defined(__s390__) && defined(__s390x__) # define PLAT_s390x_linux 1 #elif defined(__linux__) && defined(__mips__) && (__mips==64) # define PLAT_mips64_linux 1 #elif defined(__linux__) && defined(__mips__) && (__mips!=64) # define PLAT_mips32_linux 1 #elif defined(__sun) && defined(__i386__) # define PLAT_x86_solaris 1 #elif defined(__sun) && defined(__x86_64__) # define PLAT_amd64_solaris 1 #else /* If we're not compiling for our target platform, don't generate any inline asms. */ # if !defined(NVALGRIND) # define NVALGRIND 1 # endif #endif /* ------------------------------------------------------------------ */ /* ARCHITECTURE SPECIFICS for SPECIAL INSTRUCTIONS. There is nothing */ /* in here of use to end-users -- skip to the next section. */ /* ------------------------------------------------------------------ */ /* * VALGRIND_DO_CLIENT_REQUEST(): a statement that invokes a Valgrind client * request. Accepts both pointers and integers as arguments. * * VALGRIND_DO_CLIENT_REQUEST_STMT(): a statement that invokes a Valgrind * client request that does not return a value. * VALGRIND_DO_CLIENT_REQUEST_EXPR(): a C expression that invokes a Valgrind * client request and whose value equals the client request result. Accepts * both pointers and integers as arguments. Note that such calls are not * necessarily pure functions -- they may have side effects. */ #define VALGRIND_DO_CLIENT_REQUEST(_zzq_rlval, _zzq_default, \ _zzq_request, _zzq_arg1, _zzq_arg2, \ _zzq_arg3, _zzq_arg4, _zzq_arg5) \ do { (_zzq_rlval) = VALGRIND_DO_CLIENT_REQUEST_EXPR((_zzq_default), \ (_zzq_request), (_zzq_arg1), (_zzq_arg2), \ (_zzq_arg3), (_zzq_arg4), (_zzq_arg5)); } while (0) #define VALGRIND_DO_CLIENT_REQUEST_STMT(_zzq_request, _zzq_arg1, \ _zzq_arg2, _zzq_arg3, _zzq_arg4, _zzq_arg5) \ do { (void) VALGRIND_DO_CLIENT_REQUEST_EXPR(0, \ (_zzq_request), (_zzq_arg1), (_zzq_arg2), \ (_zzq_arg3), (_zzq_arg4), (_zzq_arg5)); } while (0) #if defined(NVALGRIND) /* Define NVALGRIND to completely remove the Valgrind magic sequence from the compiled code (analogous to NDEBUG's effects on assert()) */ #define VALGRIND_DO_CLIENT_REQUEST_EXPR( \ _zzq_default, _zzq_request, \ _zzq_arg1, _zzq_arg2, _zzq_arg3, _zzq_arg4, _zzq_arg5) \ (_zzq_default) #else /* ! NVALGRIND */ /* The following defines the magic code sequences which the JITter spots and handles magically. Don't look too closely at them as they will rot your brain. The assembly code sequences for all architectures is in this one file. This is because this file must be stand-alone, and we don't want to have multiple files. For VALGRIND_DO_CLIENT_REQUEST, we must ensure that the default value gets put in the return slot, so that everything works when this is executed not under Valgrind. Args are passed in a memory block, and so there's no intrinsic limit to the number that could be passed, but it's currently five. The macro args are: _zzq_rlval result lvalue _zzq_default default value (result returned when running on real CPU) _zzq_request request code _zzq_arg1..5 request params The other two macros are used to support function wrapping, and are a lot simpler. VALGRIND_GET_NR_CONTEXT returns the value of the guest's NRADDR pseudo-register and whatever other information is needed to safely run the call original from the wrapper: on ppc64-linux, the R2 value at the divert point is also needed. This information is abstracted into a user-visible type, OrigFn. VALGRIND_CALL_NOREDIR_* behaves the same as the following on the guest, but guarantees that the branch instruction will not be redirected: x86: call *%eax, amd64: call *%rax, ppc32/ppc64: branch-and-link-to-r11. VALGRIND_CALL_NOREDIR is just text, not a complete inline asm, since it needs to be combined with more magic inline asm stuff to be useful. */ /* ----------------- x86-{linux,darwin,solaris} ---------------- */ #if defined(PLAT_x86_linux) || defined(PLAT_x86_darwin) \ || (defined(PLAT_x86_win32) && defined(__GNUC__)) \ || defined(PLAT_x86_solaris) typedef struct { unsigned int nraddr; /* where's the code? */ } OrigFn; #define __SPECIAL_INSTRUCTION_PREAMBLE \ "roll $3, %%edi ; roll $13, %%edi\n\t" \ "roll $29, %%edi ; roll $19, %%edi\n\t" #define VALGRIND_DO_CLIENT_REQUEST_EXPR( \ _zzq_default, _zzq_request, \ _zzq_arg1, _zzq_arg2, _zzq_arg3, _zzq_arg4, _zzq_arg5) \ __extension__ \ ({volatile unsigned int _zzq_args[6]; \ volatile unsigned int _zzq_result; \ _zzq_args[0] = (unsigned int)(_zzq_request); \ _zzq_args[1] = (unsigned int)(_zzq_arg1); \ _zzq_args[2] = (unsigned int)(_zzq_arg2); \ _zzq_args[3] = (unsigned int)(_zzq_arg3); \ _zzq_args[4] = (unsigned int)(_zzq_arg4); \ _zzq_args[5] = (unsigned int)(_zzq_arg5); \ __asm__ volatile(__SPECIAL_INSTRUCTION_PREAMBLE \ /* %EDX = client_request ( %EAX ) */ \ "xchgl %%ebx,%%ebx" \ : "=d" (_zzq_result) \ : "a" (&_zzq_args[0]), "0" (_zzq_default) \ : "cc", "memory" \ ); \ _zzq_result; \ }) #define VALGRIND_GET_NR_CONTEXT(_zzq_rlval) \ { volatile OrigFn* _zzq_orig = &(_zzq_rlval); \ volatile unsigned int __addr; \ __asm__ volatile(__SPECIAL_INSTRUCTION_PREAMBLE \ /* %EAX = guest_NRADDR */ \ "xchgl %%ecx,%%ecx" \ : "=a" (__addr) \ : \ : "cc", "memory" \ ); \ _zzq_orig->nraddr = __addr; \ } #define VALGRIND_CALL_NOREDIR_EAX \ __SPECIAL_INSTRUCTION_PREAMBLE \ /* call-noredir *%EAX */ \ "xchgl %%edx,%%edx\n\t" #define VALGRIND_VEX_INJECT_IR() \ do { \ __asm__ volatile(__SPECIAL_INSTRUCTION_PREAMBLE \ "xchgl %%edi,%%edi\n\t" \ : : : "cc", "memory" \ ); \ } while (0) #endif /* PLAT_x86_linux || PLAT_x86_darwin || (PLAT_x86_win32 && __GNUC__) || PLAT_x86_solaris */ /* ------------------------- x86-Win32 ------------------------- */ #if defined(PLAT_x86_win32) && !defined(__GNUC__) typedef struct { unsigned int nraddr; /* where's the code? */ } OrigFn; #if defined(_MSC_VER) #define __SPECIAL_INSTRUCTION_PREAMBLE \ __asm rol edi, 3 __asm rol edi, 13 \ __asm rol edi, 29 __asm rol edi, 19 #define VALGRIND_DO_CLIENT_REQUEST_EXPR( \ _zzq_default, _zzq_request, \ _zzq_arg1, _zzq_arg2, _zzq_arg3, _zzq_arg4, _zzq_arg5) \ valgrind_do_client_request_expr((uintptr_t)(_zzq_default), \ (uintptr_t)(_zzq_request), (uintptr_t)(_zzq_arg1), \ (uintptr_t)(_zzq_arg2), (uintptr_t)(_zzq_arg3), \ (uintptr_t)(_zzq_arg4), (uintptr_t)(_zzq_arg5)) static __inline uintptr_t valgrind_do_client_request_expr(uintptr_t _zzq_default, uintptr_t _zzq_request, uintptr_t _zzq_arg1, uintptr_t _zzq_arg2, uintptr_t _zzq_arg3, uintptr_t _zzq_arg4, uintptr_t _zzq_arg5) { volatile uintptr_t _zzq_args[6]; volatile unsigned int _zzq_result; _zzq_args[0] = (uintptr_t)(_zzq_request); _zzq_args[1] = (uintptr_t)(_zzq_arg1); _zzq_args[2] = (uintptr_t)(_zzq_arg2); _zzq_args[3] = (uintptr_t)(_zzq_arg3); _zzq_args[4] = (uintptr_t)(_zzq_arg4); _zzq_args[5] = (uintptr_t)(_zzq_arg5); __asm { __asm lea eax, _zzq_args __asm mov edx, _zzq_default __SPECIAL_INSTRUCTION_PREAMBLE /* %EDX = client_request ( %EAX ) */ __asm xchg ebx,ebx __asm mov _zzq_result, edx } return _zzq_result; } #define VALGRIND_GET_NR_CONTEXT(_zzq_rlval) \ { volatile OrigFn* _zzq_orig = &(_zzq_rlval); \ volatile unsigned int __addr; \ __asm { __SPECIAL_INSTRUCTION_PREAMBLE \ /* %EAX = guest_NRADDR */ \ __asm xchg ecx,ecx \ __asm mov __addr, eax \ } \ _zzq_orig->nraddr = __addr; \ } #define VALGRIND_CALL_NOREDIR_EAX ERROR #define VALGRIND_VEX_INJECT_IR() \ do { \ __asm { __SPECIAL_INSTRUCTION_PREAMBLE \ __asm xchg edi,edi \ } \ } while (0) #else #error Unsupported compiler. #endif #endif /* PLAT_x86_win32 */ /* ----------------- amd64-{linux,darwin,solaris} --------------- */ #if defined(PLAT_amd64_linux) || defined(PLAT_amd64_darwin) \ || defined(PLAT_amd64_solaris) \ || (defined(PLAT_amd64_win64) && defined(__GNUC__)) typedef struct { unsigned long int nraddr; /* where's the code? */ } OrigFn; #define __SPECIAL_INSTRUCTION_PREAMBLE \ "rolq $3, %%rdi ; rolq $13, %%rdi\n\t" \ "rolq $61, %%rdi ; rolq $51, %%rdi\n\t" #define VALGRIND_DO_CLIENT_REQUEST_EXPR( \ _zzq_default, _zzq_request, \ _zzq_arg1, _zzq_arg2, _zzq_arg3, _zzq_arg4, _zzq_arg5) \ __extension__ \ ({ volatile unsigned long int _zzq_args[6]; \ volatile unsigned long int _zzq_result; \ _zzq_args[0] = (unsigned long int)(_zzq_request); \ _zzq_args[1] = (unsigned long int)(_zzq_arg1); \ _zzq_args[2] = (unsigned long int)(_zzq_arg2); \ _zzq_args[3] = (unsigned long int)(_zzq_arg3); \ _zzq_args[4] = (unsigned long int)(_zzq_arg4); \ _zzq_args[5] = (unsigned long int)(_zzq_arg5); \ __asm__ volatile(__SPECIAL_INSTRUCTION_PREAMBLE \ /* %RDX = client_request ( %RAX ) */ \ "xchgq %%rbx,%%rbx" \ : "=d" (_zzq_result) \ : "a" (&_zzq_args[0]), "0" (_zzq_default) \ : "cc", "memory" \ ); \ _zzq_result; \ }) #define VALGRIND_GET_NR_CONTEXT(_zzq_rlval) \ { volatile OrigFn* _zzq_orig = &(_zzq_rlval); \ volatile unsigned long int __addr; \ __asm__ volatile(__SPECIAL_INSTRUCTION_PREAMBLE \ /* %RAX = guest_NRADDR */ \ "xchgq %%rcx,%%rcx" \ : "=a" (__addr) \ : \ : "cc", "memory" \ ); \ _zzq_orig->nraddr = __addr; \ } #define VALGRIND_CALL_NOREDIR_RAX \ __SPECIAL_INSTRUCTION_PREAMBLE \ /* call-noredir *%RAX */ \ "xchgq %%rdx,%%rdx\n\t" #define VALGRIND_VEX_INJECT_IR() \ do { \ __asm__ volatile(__SPECIAL_INSTRUCTION_PREAMBLE \ "xchgq %%rdi,%%rdi\n\t" \ : : : "cc", "memory" \ ); \ } while (0) #endif /* PLAT_amd64_linux || PLAT_amd64_darwin || PLAT_amd64_solaris */ /* ------------------------- amd64-Win64 ------------------------- */ #if defined(PLAT_amd64_win64) && !defined(__GNUC__) #error Unsupported compiler. #endif /* PLAT_amd64_win64 */ /* ------------------------ ppc32-linux ------------------------ */ #if defined(PLAT_ppc32_linux) typedef struct { unsigned int nraddr; /* where's the code? */ } OrigFn; #define __SPECIAL_INSTRUCTION_PREAMBLE \ "rlwinm 0,0,3,0,31 ; rlwinm 0,0,13,0,31\n\t" \ "rlwinm 0,0,29,0,31 ; rlwinm 0,0,19,0,31\n\t" #define VALGRIND_DO_CLIENT_REQUEST_EXPR( \ _zzq_default, _zzq_request, \ _zzq_arg1, _zzq_arg2, _zzq_arg3, _zzq_arg4, _zzq_arg5) \ \ __extension__ \ ({ unsigned int _zzq_args[6]; \ unsigned int _zzq_result; \ unsigned int* _zzq_ptr; \ _zzq_args[0] = (unsigned int)(_zzq_request); \ _zzq_args[1] = (unsigned int)(_zzq_arg1); \ _zzq_args[2] = (unsigned int)(_zzq_arg2); \ _zzq_args[3] = (unsigned int)(_zzq_arg3); \ _zzq_args[4] = (unsigned int)(_zzq_arg4); \ _zzq_args[5] = (unsigned int)(_zzq_arg5); \ _zzq_ptr = _zzq_args; \ __asm__ volatile("mr 3,%1\n\t" /*default*/ \ "mr 4,%2\n\t" /*ptr*/ \ __SPECIAL_INSTRUCTION_PREAMBLE \ /* %R3 = client_request ( %R4 ) */ \ "or 1,1,1\n\t" \ "mr %0,3" /*result*/ \ : "=b" (_zzq_result) \ : "b" (_zzq_default), "b" (_zzq_ptr) \ : "cc", "memory", "r3", "r4"); \ _zzq_result; \ }) #define VALGRIND_GET_NR_CONTEXT(_zzq_rlval) \ { volatile OrigFn* _zzq_orig = &(_zzq_rlval); \ unsigned int __addr; \ __asm__ volatile(__SPECIAL_INSTRUCTION_PREAMBLE \ /* %R3 = guest_NRADDR */ \ "or 2,2,2\n\t" \ "mr %0,3" \ : "=b" (__addr) \ : \ : "cc", "memory", "r3" \ ); \ _zzq_orig->nraddr = __addr; \ } #define VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ __SPECIAL_INSTRUCTION_PREAMBLE \ /* branch-and-link-to-noredir *%R11 */ \ "or 3,3,3\n\t" #define VALGRIND_VEX_INJECT_IR() \ do { \ __asm__ volatile(__SPECIAL_INSTRUCTION_PREAMBLE \ "or 5,5,5\n\t" \ ); \ } while (0) #endif /* PLAT_ppc32_linux */ /* ------------------------ ppc64-linux ------------------------ */ #if defined(PLAT_ppc64be_linux) typedef struct { unsigned long int nraddr; /* where's the code? */ unsigned long int r2; /* what tocptr do we need? */ } OrigFn; #define __SPECIAL_INSTRUCTION_PREAMBLE \ "rotldi 0,0,3 ; rotldi 0,0,13\n\t" \ "rotldi 0,0,61 ; rotldi 0,0,51\n\t" #define VALGRIND_DO_CLIENT_REQUEST_EXPR( \ _zzq_default, _zzq_request, \ _zzq_arg1, _zzq_arg2, _zzq_arg3, _zzq_arg4, _zzq_arg5) \ \ __extension__ \ ({ unsigned long int _zzq_args[6]; \ unsigned long int _zzq_result; \ unsigned long int* _zzq_ptr; \ _zzq_args[0] = (unsigned long int)(_zzq_request); \ _zzq_args[1] = (unsigned long int)(_zzq_arg1); \ _zzq_args[2] = (unsigned long int)(_zzq_arg2); \ _zzq_args[3] = (unsigned long int)(_zzq_arg3); \ _zzq_args[4] = (unsigned long int)(_zzq_arg4); \ _zzq_args[5] = (unsigned long int)(_zzq_arg5); \ _zzq_ptr = _zzq_args; \ __asm__ volatile("mr 3,%1\n\t" /*default*/ \ "mr 4,%2\n\t" /*ptr*/ \ __SPECIAL_INSTRUCTION_PREAMBLE \ /* %R3 = client_request ( %R4 ) */ \ "or 1,1,1\n\t" \ "mr %0,3" /*result*/ \ : "=b" (_zzq_result) \ : "b" (_zzq_default), "b" (_zzq_ptr) \ : "cc", "memory", "r3", "r4"); \ _zzq_result; \ }) #define VALGRIND_GET_NR_CONTEXT(_zzq_rlval) \ { volatile OrigFn* _zzq_orig = &(_zzq_rlval); \ unsigned long int __addr; \ __asm__ volatile(__SPECIAL_INSTRUCTION_PREAMBLE \ /* %R3 = guest_NRADDR */ \ "or 2,2,2\n\t" \ "mr %0,3" \ : "=b" (__addr) \ : \ : "cc", "memory", "r3" \ ); \ _zzq_orig->nraddr = __addr; \ __asm__ volatile(__SPECIAL_INSTRUCTION_PREAMBLE \ /* %R3 = guest_NRADDR_GPR2 */ \ "or 4,4,4\n\t" \ "mr %0,3" \ : "=b" (__addr) \ : \ : "cc", "memory", "r3" \ ); \ _zzq_orig->r2 = __addr; \ } #define VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ __SPECIAL_INSTRUCTION_PREAMBLE \ /* branch-and-link-to-noredir *%R11 */ \ "or 3,3,3\n\t" #define VALGRIND_VEX_INJECT_IR() \ do { \ __asm__ volatile(__SPECIAL_INSTRUCTION_PREAMBLE \ "or 5,5,5\n\t" \ ); \ } while (0) #endif /* PLAT_ppc64be_linux */ #if defined(PLAT_ppc64le_linux) typedef struct { unsigned long int nraddr; /* where's the code? */ unsigned long int r2; /* what tocptr do we need? */ } OrigFn; #define __SPECIAL_INSTRUCTION_PREAMBLE \ "rotldi 0,0,3 ; rotldi 0,0,13\n\t" \ "rotldi 0,0,61 ; rotldi 0,0,51\n\t" #define VALGRIND_DO_CLIENT_REQUEST_EXPR( \ _zzq_default, _zzq_request, \ _zzq_arg1, _zzq_arg2, _zzq_arg3, _zzq_arg4, _zzq_arg5) \ \ __extension__ \ ({ unsigned long int _zzq_args[6]; \ unsigned long int _zzq_result; \ unsigned long int* _zzq_ptr; \ _zzq_args[0] = (unsigned long int)(_zzq_request); \ _zzq_args[1] = (unsigned long int)(_zzq_arg1); \ _zzq_args[2] = (unsigned long int)(_zzq_arg2); \ _zzq_args[3] = (unsigned long int)(_zzq_arg3); \ _zzq_args[4] = (unsigned long int)(_zzq_arg4); \ _zzq_args[5] = (unsigned long int)(_zzq_arg5); \ _zzq_ptr = _zzq_args; \ __asm__ volatile("mr 3,%1\n\t" /*default*/ \ "mr 4,%2\n\t" /*ptr*/ \ __SPECIAL_INSTRUCTION_PREAMBLE \ /* %R3 = client_request ( %R4 ) */ \ "or 1,1,1\n\t" \ "mr %0,3" /*result*/ \ : "=b" (_zzq_result) \ : "b" (_zzq_default), "b" (_zzq_ptr) \ : "cc", "memory", "r3", "r4"); \ _zzq_result; \ }) #define VALGRIND_GET_NR_CONTEXT(_zzq_rlval) \ { volatile OrigFn* _zzq_orig = &(_zzq_rlval); \ unsigned long int __addr; \ __asm__ volatile(__SPECIAL_INSTRUCTION_PREAMBLE \ /* %R3 = guest_NRADDR */ \ "or 2,2,2\n\t" \ "mr %0,3" \ : "=b" (__addr) \ : \ : "cc", "memory", "r3" \ ); \ _zzq_orig->nraddr = __addr; \ __asm__ volatile(__SPECIAL_INSTRUCTION_PREAMBLE \ /* %R3 = guest_NRADDR_GPR2 */ \ "or 4,4,4\n\t" \ "mr %0,3" \ : "=b" (__addr) \ : \ : "cc", "memory", "r3" \ ); \ _zzq_orig->r2 = __addr; \ } #define VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R12 \ __SPECIAL_INSTRUCTION_PREAMBLE \ /* branch-and-link-to-noredir *%R12 */ \ "or 3,3,3\n\t" #define VALGRIND_VEX_INJECT_IR() \ do { \ __asm__ volatile(__SPECIAL_INSTRUCTION_PREAMBLE \ "or 5,5,5\n\t" \ ); \ } while (0) #endif /* PLAT_ppc64le_linux */ /* ------------------------- arm-linux ------------------------- */ #if defined(PLAT_arm_linux) typedef struct { unsigned int nraddr; /* where's the code? */ } OrigFn; #define __SPECIAL_INSTRUCTION_PREAMBLE \ "mov r12, r12, ror #3 ; mov r12, r12, ror #13 \n\t" \ "mov r12, r12, ror #29 ; mov r12, r12, ror #19 \n\t" #define VALGRIND_DO_CLIENT_REQUEST_EXPR( \ _zzq_default, _zzq_request, \ _zzq_arg1, _zzq_arg2, _zzq_arg3, _zzq_arg4, _zzq_arg5) \ \ __extension__ \ ({volatile unsigned int _zzq_args[6]; \ volatile unsigned int _zzq_result; \ _zzq_args[0] = (unsigned int)(_zzq_request); \ _zzq_args[1] = (unsigned int)(_zzq_arg1); \ _zzq_args[2] = (unsigned int)(_zzq_arg2); \ _zzq_args[3] = (unsigned int)(_zzq_arg3); \ _zzq_args[4] = (unsigned int)(_zzq_arg4); \ _zzq_args[5] = (unsigned int)(_zzq_arg5); \ __asm__ volatile("mov r3, %1\n\t" /*default*/ \ "mov r4, %2\n\t" /*ptr*/ \ __SPECIAL_INSTRUCTION_PREAMBLE \ /* R3 = client_request ( R4 ) */ \ "orr r10, r10, r10\n\t" \ "mov %0, r3" /*result*/ \ : "=r" (_zzq_result) \ : "r" (_zzq_default), "r" (&_zzq_args[0]) \ : "cc","memory", "r3", "r4"); \ _zzq_result; \ }) #define VALGRIND_GET_NR_CONTEXT(_zzq_rlval) \ { volatile OrigFn* _zzq_orig = &(_zzq_rlval); \ unsigned int __addr; \ __asm__ volatile(__SPECIAL_INSTRUCTION_PREAMBLE \ /* R3 = guest_NRADDR */ \ "orr r11, r11, r11\n\t" \ "mov %0, r3" \ : "=r" (__addr) \ : \ : "cc", "memory", "r3" \ ); \ _zzq_orig->nraddr = __addr; \ } #define VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R4 \ __SPECIAL_INSTRUCTION_PREAMBLE \ /* branch-and-link-to-noredir *%R4 */ \ "orr r12, r12, r12\n\t" #define VALGRIND_VEX_INJECT_IR() \ do { \ __asm__ volatile(__SPECIAL_INSTRUCTION_PREAMBLE \ "orr r9, r9, r9\n\t" \ : : : "cc", "memory" \ ); \ } while (0) #endif /* PLAT_arm_linux */ /* ------------------------ arm64-linux ------------------------- */ #if defined(PLAT_arm64_linux) typedef struct { unsigned long int nraddr; /* where's the code? */ } OrigFn; #define __SPECIAL_INSTRUCTION_PREAMBLE \ "ror x12, x12, #3 ; ror x12, x12, #13 \n\t" \ "ror x12, x12, #51 ; ror x12, x12, #61 \n\t" #define VALGRIND_DO_CLIENT_REQUEST_EXPR( \ _zzq_default, _zzq_request, \ _zzq_arg1, _zzq_arg2, _zzq_arg3, _zzq_arg4, _zzq_arg5) \ \ __extension__ \ ({volatile unsigned long int _zzq_args[6]; \ volatile unsigned long int _zzq_result; \ _zzq_args[0] = (unsigned long int)(_zzq_request); \ _zzq_args[1] = (unsigned long int)(_zzq_arg1); \ _zzq_args[2] = (unsigned long int)(_zzq_arg2); \ _zzq_args[3] = (unsigned long int)(_zzq_arg3); \ _zzq_args[4] = (unsigned long int)(_zzq_arg4); \ _zzq_args[5] = (unsigned long int)(_zzq_arg5); \ __asm__ volatile("mov x3, %1\n\t" /*default*/ \ "mov x4, %2\n\t" /*ptr*/ \ __SPECIAL_INSTRUCTION_PREAMBLE \ /* X3 = client_request ( X4 ) */ \ "orr x10, x10, x10\n\t" \ "mov %0, x3" /*result*/ \ : "=r" (_zzq_result) \ : "r" ((unsigned long int)(_zzq_default)), \ "r" (&_zzq_args[0]) \ : "cc","memory", "x3", "x4"); \ _zzq_result; \ }) #define VALGRIND_GET_NR_CONTEXT(_zzq_rlval) \ { volatile OrigFn* _zzq_orig = &(_zzq_rlval); \ unsigned long int __addr; \ __asm__ volatile(__SPECIAL_INSTRUCTION_PREAMBLE \ /* X3 = guest_NRADDR */ \ "orr x11, x11, x11\n\t" \ "mov %0, x3" \ : "=r" (__addr) \ : \ : "cc", "memory", "x3" \ ); \ _zzq_orig->nraddr = __addr; \ } #define VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_X8 \ __SPECIAL_INSTRUCTION_PREAMBLE \ /* branch-and-link-to-noredir X8 */ \ "orr x12, x12, x12\n\t" #define VALGRIND_VEX_INJECT_IR() \ do { \ __asm__ volatile(__SPECIAL_INSTRUCTION_PREAMBLE \ "orr x9, x9, x9\n\t" \ : : : "cc", "memory" \ ); \ } while (0) #endif /* PLAT_arm64_linux */ /* ------------------------ s390x-linux ------------------------ */ #if defined(PLAT_s390x_linux) typedef struct { unsigned long int nraddr; /* where's the code? */ } OrigFn; /* __SPECIAL_INSTRUCTION_PREAMBLE will be used to identify Valgrind specific * code. This detection is implemented in platform specific toIR.c * (e.g. VEX/priv/guest_s390_decoder.c). */ #define __SPECIAL_INSTRUCTION_PREAMBLE \ "lr 15,15\n\t" \ "lr 1,1\n\t" \ "lr 2,2\n\t" \ "lr 3,3\n\t" #define __CLIENT_REQUEST_CODE "lr 2,2\n\t" #define __GET_NR_CONTEXT_CODE "lr 3,3\n\t" #define __CALL_NO_REDIR_CODE "lr 4,4\n\t" #define __VEX_INJECT_IR_CODE "lr 5,5\n\t" #define VALGRIND_DO_CLIENT_REQUEST_EXPR( \ _zzq_default, _zzq_request, \ _zzq_arg1, _zzq_arg2, _zzq_arg3, _zzq_arg4, _zzq_arg5) \ __extension__ \ ({volatile unsigned long int _zzq_args[6]; \ volatile unsigned long int _zzq_result; \ _zzq_args[0] = (unsigned long int)(_zzq_request); \ _zzq_args[1] = (unsigned long int)(_zzq_arg1); \ _zzq_args[2] = (unsigned long int)(_zzq_arg2); \ _zzq_args[3] = (unsigned long int)(_zzq_arg3); \ _zzq_args[4] = (unsigned long int)(_zzq_arg4); \ _zzq_args[5] = (unsigned long int)(_zzq_arg5); \ __asm__ volatile(/* r2 = args */ \ "lgr 2,%1\n\t" \ /* r3 = default */ \ "lgr 3,%2\n\t" \ __SPECIAL_INSTRUCTION_PREAMBLE \ __CLIENT_REQUEST_CODE \ /* results = r3 */ \ "lgr %0, 3\n\t" \ : "=d" (_zzq_result) \ : "a" (&_zzq_args[0]), "0" (_zzq_default) \ : "cc", "2", "3", "memory" \ ); \ _zzq_result; \ }) #define VALGRIND_GET_NR_CONTEXT(_zzq_rlval) \ { volatile OrigFn* _zzq_orig = &(_zzq_rlval); \ volatile unsigned long int __addr; \ __asm__ volatile(__SPECIAL_INSTRUCTION_PREAMBLE \ __GET_NR_CONTEXT_CODE \ "lgr %0, 3\n\t" \ : "=a" (__addr) \ : \ : "cc", "3", "memory" \ ); \ _zzq_orig->nraddr = __addr; \ } #define VALGRIND_CALL_NOREDIR_R1 \ __SPECIAL_INSTRUCTION_PREAMBLE \ __CALL_NO_REDIR_CODE #define VALGRIND_VEX_INJECT_IR() \ do { \ __asm__ volatile(__SPECIAL_INSTRUCTION_PREAMBLE \ __VEX_INJECT_IR_CODE); \ } while (0) #endif /* PLAT_s390x_linux */ /* ------------------------- mips32-linux ---------------- */ #if defined(PLAT_mips32_linux) typedef struct { unsigned int nraddr; /* where's the code? */ } OrigFn; /* .word 0x342 * .word 0x742 * .word 0xC2 * .word 0x4C2*/ #define __SPECIAL_INSTRUCTION_PREAMBLE \ "srl $0, $0, 13\n\t" \ "srl $0, $0, 29\n\t" \ "srl $0, $0, 3\n\t" \ "srl $0, $0, 19\n\t" #define VALGRIND_DO_CLIENT_REQUEST_EXPR( \ _zzq_default, _zzq_request, \ _zzq_arg1, _zzq_arg2, _zzq_arg3, _zzq_arg4, _zzq_arg5) \ __extension__ \ ({ volatile unsigned int _zzq_args[6]; \ volatile unsigned int _zzq_result; \ _zzq_args[0] = (unsigned int)(_zzq_request); \ _zzq_args[1] = (unsigned int)(_zzq_arg1); \ _zzq_args[2] = (unsigned int)(_zzq_arg2); \ _zzq_args[3] = (unsigned int)(_zzq_arg3); \ _zzq_args[4] = (unsigned int)(_zzq_arg4); \ _zzq_args[5] = (unsigned int)(_zzq_arg5); \ __asm__ volatile("move $11, %1\n\t" /*default*/ \ "move $12, %2\n\t" /*ptr*/ \ __SPECIAL_INSTRUCTION_PREAMBLE \ /* T3 = client_request ( T4 ) */ \ "or $13, $13, $13\n\t" \ "move %0, $11\n\t" /*result*/ \ : "=r" (_zzq_result) \ : "r" (_zzq_default), "r" (&_zzq_args[0]) \ : "$11", "$12", "memory"); \ _zzq_result; \ }) #define VALGRIND_GET_NR_CONTEXT(_zzq_rlval) \ { volatile OrigFn* _zzq_orig = &(_zzq_rlval); \ volatile unsigned int __addr; \ __asm__ volatile(__SPECIAL_INSTRUCTION_PREAMBLE \ /* %t9 = guest_NRADDR */ \ "or $14, $14, $14\n\t" \ "move %0, $11" /*result*/ \ : "=r" (__addr) \ : \ : "$11" \ ); \ _zzq_orig->nraddr = __addr; \ } #define VALGRIND_CALL_NOREDIR_T9 \ __SPECIAL_INSTRUCTION_PREAMBLE \ /* call-noredir *%t9 */ \ "or $15, $15, $15\n\t" #define VALGRIND_VEX_INJECT_IR() \ do { \ __asm__ volatile(__SPECIAL_INSTRUCTION_PREAMBLE \ "or $11, $11, $11\n\t" \ ); \ } while (0) #endif /* PLAT_mips32_linux */ /* ------------------------- mips64-linux ---------------- */ #if defined(PLAT_mips64_linux) typedef struct { unsigned long nraddr; /* where's the code? */ } OrigFn; /* dsll $0,$0, 3 * dsll $0,$0, 13 * dsll $0,$0, 29 * dsll $0,$0, 19*/ #define __SPECIAL_INSTRUCTION_PREAMBLE \ "dsll $0,$0, 3 ; dsll $0,$0,13\n\t" \ "dsll $0,$0,29 ; dsll $0,$0,19\n\t" #define VALGRIND_DO_CLIENT_REQUEST_EXPR( \ _zzq_default, _zzq_request, \ _zzq_arg1, _zzq_arg2, _zzq_arg3, _zzq_arg4, _zzq_arg5) \ __extension__ \ ({ volatile unsigned long int _zzq_args[6]; \ volatile unsigned long int _zzq_result; \ _zzq_args[0] = (unsigned long int)(_zzq_request); \ _zzq_args[1] = (unsigned long int)(_zzq_arg1); \ _zzq_args[2] = (unsigned long int)(_zzq_arg2); \ _zzq_args[3] = (unsigned long int)(_zzq_arg3); \ _zzq_args[4] = (unsigned long int)(_zzq_arg4); \ _zzq_args[5] = (unsigned long int)(_zzq_arg5); \ __asm__ volatile("move $11, %1\n\t" /*default*/ \ "move $12, %2\n\t" /*ptr*/ \ __SPECIAL_INSTRUCTION_PREAMBLE \ /* $11 = client_request ( $12 ) */ \ "or $13, $13, $13\n\t" \ "move %0, $11\n\t" /*result*/ \ : "=r" (_zzq_result) \ : "r" (_zzq_default), "r" (&_zzq_args[0]) \ : "$11", "$12", "memory"); \ _zzq_result; \ }) #define VALGRIND_GET_NR_CONTEXT(_zzq_rlval) \ { volatile OrigFn* _zzq_orig = &(_zzq_rlval); \ volatile unsigned long int __addr; \ __asm__ volatile(__SPECIAL_INSTRUCTION_PREAMBLE \ /* $11 = guest_NRADDR */ \ "or $14, $14, $14\n\t" \ "move %0, $11" /*result*/ \ : "=r" (__addr) \ : \ : "$11"); \ _zzq_orig->nraddr = __addr; \ } #define VALGRIND_CALL_NOREDIR_T9 \ __SPECIAL_INSTRUCTION_PREAMBLE \ /* call-noredir $25 */ \ "or $15, $15, $15\n\t" #define VALGRIND_VEX_INJECT_IR() \ do { \ __asm__ volatile(__SPECIAL_INSTRUCTION_PREAMBLE \ "or $11, $11, $11\n\t" \ ); \ } while (0) #endif /* PLAT_mips64_linux */ /* Insert assembly code for other platforms here... */ #endif /* NVALGRIND */ /* ------------------------------------------------------------------ */ /* PLATFORM SPECIFICS for FUNCTION WRAPPING. This is all very */ /* ugly. It's the least-worst tradeoff I can think of. */ /* ------------------------------------------------------------------ */ /* This section defines magic (a.k.a appalling-hack) macros for doing guaranteed-no-redirection macros, so as to get from function wrappers to the functions they are wrapping. The whole point is to construct standard call sequences, but to do the call itself with a special no-redirect call pseudo-instruction that the JIT understands and handles specially. This section is long and repetitious, and I can't see a way to make it shorter. The naming scheme is as follows: CALL_FN_{W,v}_{v,W,WW,WWW,WWWW,5W,6W,7W,etc} 'W' stands for "word" and 'v' for "void". Hence there are different macros for calling arity 0, 1, 2, 3, 4, etc, functions, and for each, the possibility of returning a word-typed result, or no result. */ /* Use these to write the name of your wrapper. NOTE: duplicates VG_WRAP_FUNCTION_Z{U,Z} in pub_tool_redir.h. NOTE also: inserts the default behaviour equivalence class tag "0000" into the name. See pub_tool_redir.h for details -- normally you don't need to think about this, though. */ /* Use an extra level of macroisation so as to ensure the soname/fnname args are fully macro-expanded before pasting them together. */ #define VG_CONCAT4(_aa,_bb,_cc,_dd) _aa##_bb##_cc##_dd #define I_WRAP_SONAME_FNNAME_ZU(soname,fnname) \ VG_CONCAT4(_vgw00000ZU_,soname,_,fnname) #define I_WRAP_SONAME_FNNAME_ZZ(soname,fnname) \ VG_CONCAT4(_vgw00000ZZ_,soname,_,fnname) /* Use this macro from within a wrapper function to collect the context (address and possibly other info) of the original function. Once you have that you can then use it in one of the CALL_FN_ macros. The type of the argument _lval is OrigFn. */ #define VALGRIND_GET_ORIG_FN(_lval) VALGRIND_GET_NR_CONTEXT(_lval) /* Also provide end-user facilities for function replacement, rather than wrapping. A replacement function differs from a wrapper in that it has no way to get hold of the original function being called, and hence no way to call onwards to it. In a replacement function, VALGRIND_GET_ORIG_FN always returns zero. */ #define I_REPLACE_SONAME_FNNAME_ZU(soname,fnname) \ VG_CONCAT4(_vgr00000ZU_,soname,_,fnname) #define I_REPLACE_SONAME_FNNAME_ZZ(soname,fnname) \ VG_CONCAT4(_vgr00000ZZ_,soname,_,fnname) /* Derivatives of the main macros below, for calling functions returning void. */ #define CALL_FN_v_v(fnptr) \ do { volatile unsigned long _junk; \ CALL_FN_W_v(_junk,fnptr); } while (0) #define CALL_FN_v_W(fnptr, arg1) \ do { volatile unsigned long _junk; \ CALL_FN_W_W(_junk,fnptr,arg1); } while (0) #define CALL_FN_v_WW(fnptr, arg1,arg2) \ do { volatile unsigned long _junk; \ CALL_FN_W_WW(_junk,fnptr,arg1,arg2); } while (0) #define CALL_FN_v_WWW(fnptr, arg1,arg2,arg3) \ do { volatile unsigned long _junk; \ CALL_FN_W_WWW(_junk,fnptr,arg1,arg2,arg3); } while (0) #define CALL_FN_v_WWWW(fnptr, arg1,arg2,arg3,arg4) \ do { volatile unsigned long _junk; \ CALL_FN_W_WWWW(_junk,fnptr,arg1,arg2,arg3,arg4); } while (0) #define CALL_FN_v_5W(fnptr, arg1,arg2,arg3,arg4,arg5) \ do { volatile unsigned long _junk; \ CALL_FN_W_5W(_junk,fnptr,arg1,arg2,arg3,arg4,arg5); } while (0) #define CALL_FN_v_6W(fnptr, arg1,arg2,arg3,arg4,arg5,arg6) \ do { volatile unsigned long _junk; \ CALL_FN_W_6W(_junk,fnptr,arg1,arg2,arg3,arg4,arg5,arg6); } while (0) #define CALL_FN_v_7W(fnptr, arg1,arg2,arg3,arg4,arg5,arg6,arg7) \ do { volatile unsigned long _junk; \ CALL_FN_W_7W(_junk,fnptr,arg1,arg2,arg3,arg4,arg5,arg6,arg7); } while (0) /* ----------------- x86-{linux,darwin,solaris} ---------------- */ #if defined(PLAT_x86_linux) || defined(PLAT_x86_darwin) \ || defined(PLAT_x86_solaris) /* These regs are trashed by the hidden call. No need to mention eax as gcc can already see that, plus causes gcc to bomb. */ #define __CALLER_SAVED_REGS /*"eax"*/ "ecx", "edx" /* Macros to save and align the stack before making a function call and restore it afterwards as gcc may not keep the stack pointer aligned if it doesn't realise calls are being made to other functions. */ #define VALGRIND_ALIGN_STACK \ "movl %%esp,%%edi\n\t" \ "andl $0xfffffff0,%%esp\n\t" #define VALGRIND_RESTORE_STACK \ "movl %%edi,%%esp\n\t" /* These CALL_FN_ macros assume that on x86-linux, sizeof(unsigned long) == 4. */ #define CALL_FN_W_v(lval, orig) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[1]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "movl (%%eax), %%eax\n\t" /* target->%eax */ \ VALGRIND_CALL_NOREDIR_EAX \ VALGRIND_RESTORE_STACK \ : /*out*/ "=a" (_res) \ : /*in*/ "a" (&_argvec[0]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "edi" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_W(lval, orig, arg1) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[2]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)(arg1); \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "subl $12, %%esp\n\t" \ "pushl 4(%%eax)\n\t" \ "movl (%%eax), %%eax\n\t" /* target->%eax */ \ VALGRIND_CALL_NOREDIR_EAX \ VALGRIND_RESTORE_STACK \ : /*out*/ "=a" (_res) \ : /*in*/ "a" (&_argvec[0]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "edi" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_WW(lval, orig, arg1,arg2) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[3]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)(arg1); \ _argvec[2] = (unsigned long)(arg2); \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "subl $8, %%esp\n\t" \ "pushl 8(%%eax)\n\t" \ "pushl 4(%%eax)\n\t" \ "movl (%%eax), %%eax\n\t" /* target->%eax */ \ VALGRIND_CALL_NOREDIR_EAX \ VALGRIND_RESTORE_STACK \ : /*out*/ "=a" (_res) \ : /*in*/ "a" (&_argvec[0]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "edi" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_WWW(lval, orig, arg1,arg2,arg3) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[4]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)(arg1); \ _argvec[2] = (unsigned long)(arg2); \ _argvec[3] = (unsigned long)(arg3); \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "subl $4, %%esp\n\t" \ "pushl 12(%%eax)\n\t" \ "pushl 8(%%eax)\n\t" \ "pushl 4(%%eax)\n\t" \ "movl (%%eax), %%eax\n\t" /* target->%eax */ \ VALGRIND_CALL_NOREDIR_EAX \ VALGRIND_RESTORE_STACK \ : /*out*/ "=a" (_res) \ : /*in*/ "a" (&_argvec[0]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "edi" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_WWWW(lval, orig, arg1,arg2,arg3,arg4) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[5]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)(arg1); \ _argvec[2] = (unsigned long)(arg2); \ _argvec[3] = (unsigned long)(arg3); \ _argvec[4] = (unsigned long)(arg4); \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "pushl 16(%%eax)\n\t" \ "pushl 12(%%eax)\n\t" \ "pushl 8(%%eax)\n\t" \ "pushl 4(%%eax)\n\t" \ "movl (%%eax), %%eax\n\t" /* target->%eax */ \ VALGRIND_CALL_NOREDIR_EAX \ VALGRIND_RESTORE_STACK \ : /*out*/ "=a" (_res) \ : /*in*/ "a" (&_argvec[0]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "edi" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_5W(lval, orig, arg1,arg2,arg3,arg4,arg5) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[6]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)(arg1); \ _argvec[2] = (unsigned long)(arg2); \ _argvec[3] = (unsigned long)(arg3); \ _argvec[4] = (unsigned long)(arg4); \ _argvec[5] = (unsigned long)(arg5); \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "subl $12, %%esp\n\t" \ "pushl 20(%%eax)\n\t" \ "pushl 16(%%eax)\n\t" \ "pushl 12(%%eax)\n\t" \ "pushl 8(%%eax)\n\t" \ "pushl 4(%%eax)\n\t" \ "movl (%%eax), %%eax\n\t" /* target->%eax */ \ VALGRIND_CALL_NOREDIR_EAX \ VALGRIND_RESTORE_STACK \ : /*out*/ "=a" (_res) \ : /*in*/ "a" (&_argvec[0]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "edi" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_6W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[7]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)(arg1); \ _argvec[2] = (unsigned long)(arg2); \ _argvec[3] = (unsigned long)(arg3); \ _argvec[4] = (unsigned long)(arg4); \ _argvec[5] = (unsigned long)(arg5); \ _argvec[6] = (unsigned long)(arg6); \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "subl $8, %%esp\n\t" \ "pushl 24(%%eax)\n\t" \ "pushl 20(%%eax)\n\t" \ "pushl 16(%%eax)\n\t" \ "pushl 12(%%eax)\n\t" \ "pushl 8(%%eax)\n\t" \ "pushl 4(%%eax)\n\t" \ "movl (%%eax), %%eax\n\t" /* target->%eax */ \ VALGRIND_CALL_NOREDIR_EAX \ VALGRIND_RESTORE_STACK \ : /*out*/ "=a" (_res) \ : /*in*/ "a" (&_argvec[0]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "edi" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_7W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ arg7) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[8]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)(arg1); \ _argvec[2] = (unsigned long)(arg2); \ _argvec[3] = (unsigned long)(arg3); \ _argvec[4] = (unsigned long)(arg4); \ _argvec[5] = (unsigned long)(arg5); \ _argvec[6] = (unsigned long)(arg6); \ _argvec[7] = (unsigned long)(arg7); \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "subl $4, %%esp\n\t" \ "pushl 28(%%eax)\n\t" \ "pushl 24(%%eax)\n\t" \ "pushl 20(%%eax)\n\t" \ "pushl 16(%%eax)\n\t" \ "pushl 12(%%eax)\n\t" \ "pushl 8(%%eax)\n\t" \ "pushl 4(%%eax)\n\t" \ "movl (%%eax), %%eax\n\t" /* target->%eax */ \ VALGRIND_CALL_NOREDIR_EAX \ VALGRIND_RESTORE_STACK \ : /*out*/ "=a" (_res) \ : /*in*/ "a" (&_argvec[0]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "edi" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_8W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ arg7,arg8) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[9]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)(arg1); \ _argvec[2] = (unsigned long)(arg2); \ _argvec[3] = (unsigned long)(arg3); \ _argvec[4] = (unsigned long)(arg4); \ _argvec[5] = (unsigned long)(arg5); \ _argvec[6] = (unsigned long)(arg6); \ _argvec[7] = (unsigned long)(arg7); \ _argvec[8] = (unsigned long)(arg8); \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "pushl 32(%%eax)\n\t" \ "pushl 28(%%eax)\n\t" \ "pushl 24(%%eax)\n\t" \ "pushl 20(%%eax)\n\t" \ "pushl 16(%%eax)\n\t" \ "pushl 12(%%eax)\n\t" \ "pushl 8(%%eax)\n\t" \ "pushl 4(%%eax)\n\t" \ "movl (%%eax), %%eax\n\t" /* target->%eax */ \ VALGRIND_CALL_NOREDIR_EAX \ VALGRIND_RESTORE_STACK \ : /*out*/ "=a" (_res) \ : /*in*/ "a" (&_argvec[0]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "edi" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_9W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ arg7,arg8,arg9) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[10]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)(arg1); \ _argvec[2] = (unsigned long)(arg2); \ _argvec[3] = (unsigned long)(arg3); \ _argvec[4] = (unsigned long)(arg4); \ _argvec[5] = (unsigned long)(arg5); \ _argvec[6] = (unsigned long)(arg6); \ _argvec[7] = (unsigned long)(arg7); \ _argvec[8] = (unsigned long)(arg8); \ _argvec[9] = (unsigned long)(arg9); \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "subl $12, %%esp\n\t" \ "pushl 36(%%eax)\n\t" \ "pushl 32(%%eax)\n\t" \ "pushl 28(%%eax)\n\t" \ "pushl 24(%%eax)\n\t" \ "pushl 20(%%eax)\n\t" \ "pushl 16(%%eax)\n\t" \ "pushl 12(%%eax)\n\t" \ "pushl 8(%%eax)\n\t" \ "pushl 4(%%eax)\n\t" \ "movl (%%eax), %%eax\n\t" /* target->%eax */ \ VALGRIND_CALL_NOREDIR_EAX \ VALGRIND_RESTORE_STACK \ : /*out*/ "=a" (_res) \ : /*in*/ "a" (&_argvec[0]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "edi" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_10W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ arg7,arg8,arg9,arg10) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[11]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)(arg1); \ _argvec[2] = (unsigned long)(arg2); \ _argvec[3] = (unsigned long)(arg3); \ _argvec[4] = (unsigned long)(arg4); \ _argvec[5] = (unsigned long)(arg5); \ _argvec[6] = (unsigned long)(arg6); \ _argvec[7] = (unsigned long)(arg7); \ _argvec[8] = (unsigned long)(arg8); \ _argvec[9] = (unsigned long)(arg9); \ _argvec[10] = (unsigned long)(arg10); \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "subl $8, %%esp\n\t" \ "pushl 40(%%eax)\n\t" \ "pushl 36(%%eax)\n\t" \ "pushl 32(%%eax)\n\t" \ "pushl 28(%%eax)\n\t" \ "pushl 24(%%eax)\n\t" \ "pushl 20(%%eax)\n\t" \ "pushl 16(%%eax)\n\t" \ "pushl 12(%%eax)\n\t" \ "pushl 8(%%eax)\n\t" \ "pushl 4(%%eax)\n\t" \ "movl (%%eax), %%eax\n\t" /* target->%eax */ \ VALGRIND_CALL_NOREDIR_EAX \ VALGRIND_RESTORE_STACK \ : /*out*/ "=a" (_res) \ : /*in*/ "a" (&_argvec[0]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "edi" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_11W(lval, orig, arg1,arg2,arg3,arg4,arg5, \ arg6,arg7,arg8,arg9,arg10, \ arg11) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[12]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)(arg1); \ _argvec[2] = (unsigned long)(arg2); \ _argvec[3] = (unsigned long)(arg3); \ _argvec[4] = (unsigned long)(arg4); \ _argvec[5] = (unsigned long)(arg5); \ _argvec[6] = (unsigned long)(arg6); \ _argvec[7] = (unsigned long)(arg7); \ _argvec[8] = (unsigned long)(arg8); \ _argvec[9] = (unsigned long)(arg9); \ _argvec[10] = (unsigned long)(arg10); \ _argvec[11] = (unsigned long)(arg11); \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "subl $4, %%esp\n\t" \ "pushl 44(%%eax)\n\t" \ "pushl 40(%%eax)\n\t" \ "pushl 36(%%eax)\n\t" \ "pushl 32(%%eax)\n\t" \ "pushl 28(%%eax)\n\t" \ "pushl 24(%%eax)\n\t" \ "pushl 20(%%eax)\n\t" \ "pushl 16(%%eax)\n\t" \ "pushl 12(%%eax)\n\t" \ "pushl 8(%%eax)\n\t" \ "pushl 4(%%eax)\n\t" \ "movl (%%eax), %%eax\n\t" /* target->%eax */ \ VALGRIND_CALL_NOREDIR_EAX \ VALGRIND_RESTORE_STACK \ : /*out*/ "=a" (_res) \ : /*in*/ "a" (&_argvec[0]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "edi" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_12W(lval, orig, arg1,arg2,arg3,arg4,arg5, \ arg6,arg7,arg8,arg9,arg10, \ arg11,arg12) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[13]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)(arg1); \ _argvec[2] = (unsigned long)(arg2); \ _argvec[3] = (unsigned long)(arg3); \ _argvec[4] = (unsigned long)(arg4); \ _argvec[5] = (unsigned long)(arg5); \ _argvec[6] = (unsigned long)(arg6); \ _argvec[7] = (unsigned long)(arg7); \ _argvec[8] = (unsigned long)(arg8); \ _argvec[9] = (unsigned long)(arg9); \ _argvec[10] = (unsigned long)(arg10); \ _argvec[11] = (unsigned long)(arg11); \ _argvec[12] = (unsigned long)(arg12); \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "pushl 48(%%eax)\n\t" \ "pushl 44(%%eax)\n\t" \ "pushl 40(%%eax)\n\t" \ "pushl 36(%%eax)\n\t" \ "pushl 32(%%eax)\n\t" \ "pushl 28(%%eax)\n\t" \ "pushl 24(%%eax)\n\t" \ "pushl 20(%%eax)\n\t" \ "pushl 16(%%eax)\n\t" \ "pushl 12(%%eax)\n\t" \ "pushl 8(%%eax)\n\t" \ "pushl 4(%%eax)\n\t" \ "movl (%%eax), %%eax\n\t" /* target->%eax */ \ VALGRIND_CALL_NOREDIR_EAX \ VALGRIND_RESTORE_STACK \ : /*out*/ "=a" (_res) \ : /*in*/ "a" (&_argvec[0]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "edi" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #endif /* PLAT_x86_linux || PLAT_x86_darwin || PLAT_x86_solaris */ /* ---------------- amd64-{linux,darwin,solaris} --------------- */ #if defined(PLAT_amd64_linux) || defined(PLAT_amd64_darwin) \ || defined(PLAT_amd64_solaris) /* ARGREGS: rdi rsi rdx rcx r8 r9 (the rest on stack in R-to-L order) */ /* These regs are trashed by the hidden call. */ #define __CALLER_SAVED_REGS /*"rax",*/ "rcx", "rdx", "rsi", \ "rdi", "r8", "r9", "r10", "r11" /* This is all pretty complex. It's so as to make stack unwinding work reliably. See bug 243270. The basic problem is the sub and add of 128 of %rsp in all of the following macros. If gcc believes the CFA is in %rsp, then unwinding may fail, because what's at the CFA is not what gcc "expected" when it constructs the CFIs for the places where the macros are instantiated. But we can't just add a CFI annotation to increase the CFA offset by 128, to match the sub of 128 from %rsp, because we don't know whether gcc has chosen %rsp as the CFA at that point, or whether it has chosen some other register (eg, %rbp). In the latter case, adding a CFI annotation to change the CFA offset is simply wrong. So the solution is to get hold of the CFA using __builtin_dwarf_cfa(), put it in a known register, and add a CFI annotation to say what the register is. We choose %rbp for this (perhaps perversely), because: (1) %rbp is already subject to unwinding. If a new register was chosen then the unwinder would have to unwind it in all stack traces, which is expensive, and (2) %rbp is already subject to precise exception updates in the JIT. If a new register was chosen, we'd have to have precise exceptions for it too, which reduces performance of the generated code. However .. one extra complication. We can't just whack the result of __builtin_dwarf_cfa() into %rbp and then add %rbp to the list of trashed registers at the end of the inline assembly fragments; gcc won't allow %rbp to appear in that list. Hence instead we need to stash %rbp in %r15 for the duration of the asm, and say that %r15 is trashed instead. gcc seems happy to go with that. Oh .. and this all needs to be conditionalised so that it is unchanged from before this commit, when compiled with older gccs that don't support __builtin_dwarf_cfa. Furthermore, since this header file is freestanding, it has to be independent of config.h, and so the following conditionalisation cannot depend on configure time checks. Although it's not clear from 'defined(__GNUC__) && defined(__GCC_HAVE_DWARF2_CFI_ASM)', this expression excludes Darwin. .cfi directives in Darwin assembly appear to be completely different and I haven't investigated how they work. For even more entertainment value, note we have to use the completely undocumented __builtin_dwarf_cfa(), which appears to really compute the CFA, whereas __builtin_frame_address(0) claims to but actually doesn't. See https://bugs.kde.org/show_bug.cgi?id=243270#c47 */ #if defined(__GNUC__) && defined(__GCC_HAVE_DWARF2_CFI_ASM) # define __FRAME_POINTER \ ,"r"(__builtin_dwarf_cfa()) # define VALGRIND_CFI_PROLOGUE \ "movq %%rbp, %%r15\n\t" \ "movq %2, %%rbp\n\t" \ ".cfi_remember_state\n\t" \ ".cfi_def_cfa rbp, 0\n\t" # define VALGRIND_CFI_EPILOGUE \ "movq %%r15, %%rbp\n\t" \ ".cfi_restore_state\n\t" #else # define __FRAME_POINTER # define VALGRIND_CFI_PROLOGUE # define VALGRIND_CFI_EPILOGUE #endif /* Macros to save and align the stack before making a function call and restore it afterwards as gcc may not keep the stack pointer aligned if it doesn't realise calls are being made to other functions. */ #define VALGRIND_ALIGN_STACK \ "movq %%rsp,%%r14\n\t" \ "andq $0xfffffffffffffff0,%%rsp\n\t" #define VALGRIND_RESTORE_STACK \ "movq %%r14,%%rsp\n\t" /* These CALL_FN_ macros assume that on amd64-linux, sizeof(unsigned long) == 8. */ /* NB 9 Sept 07. There is a nasty kludge here in all these CALL_FN_ macros. In order not to trash the stack redzone, we need to drop %rsp by 128 before the hidden call, and restore afterwards. The nastiness is that it is only by luck that the stack still appears to be unwindable during the hidden call - since then the behaviour of any routine using this macro does not match what the CFI data says. Sigh. Why is this important? Imagine that a wrapper has a stack allocated local, and passes to the hidden call, a pointer to it. Because gcc does not know about the hidden call, it may allocate that local in the redzone. Unfortunately the hidden call may then trash it before it comes to use it. So we must step clear of the redzone, for the duration of the hidden call, to make it safe. Probably the same problem afflicts the other redzone-style ABIs too (ppc64-linux); but for those, the stack is self describing (none of this CFI nonsense) so at least messing with the stack pointer doesn't give a danger of non-unwindable stack. */ #define CALL_FN_W_v(lval, orig) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[1]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ __asm__ volatile( \ VALGRIND_CFI_PROLOGUE \ VALGRIND_ALIGN_STACK \ "subq $128,%%rsp\n\t" \ "movq (%%rax), %%rax\n\t" /* target->%rax */ \ VALGRIND_CALL_NOREDIR_RAX \ VALGRIND_RESTORE_STACK \ VALGRIND_CFI_EPILOGUE \ : /*out*/ "=a" (_res) \ : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r14", "r15" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_W(lval, orig, arg1) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[2]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)(arg1); \ __asm__ volatile( \ VALGRIND_CFI_PROLOGUE \ VALGRIND_ALIGN_STACK \ "subq $128,%%rsp\n\t" \ "movq 8(%%rax), %%rdi\n\t" \ "movq (%%rax), %%rax\n\t" /* target->%rax */ \ VALGRIND_CALL_NOREDIR_RAX \ VALGRIND_RESTORE_STACK \ VALGRIND_CFI_EPILOGUE \ : /*out*/ "=a" (_res) \ : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r14", "r15" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_WW(lval, orig, arg1,arg2) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[3]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)(arg1); \ _argvec[2] = (unsigned long)(arg2); \ __asm__ volatile( \ VALGRIND_CFI_PROLOGUE \ VALGRIND_ALIGN_STACK \ "subq $128,%%rsp\n\t" \ "movq 16(%%rax), %%rsi\n\t" \ "movq 8(%%rax), %%rdi\n\t" \ "movq (%%rax), %%rax\n\t" /* target->%rax */ \ VALGRIND_CALL_NOREDIR_RAX \ VALGRIND_RESTORE_STACK \ VALGRIND_CFI_EPILOGUE \ : /*out*/ "=a" (_res) \ : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r14", "r15" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_WWW(lval, orig, arg1,arg2,arg3) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[4]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)(arg1); \ _argvec[2] = (unsigned long)(arg2); \ _argvec[3] = (unsigned long)(arg3); \ __asm__ volatile( \ VALGRIND_CFI_PROLOGUE \ VALGRIND_ALIGN_STACK \ "subq $128,%%rsp\n\t" \ "movq 24(%%rax), %%rdx\n\t" \ "movq 16(%%rax), %%rsi\n\t" \ "movq 8(%%rax), %%rdi\n\t" \ "movq (%%rax), %%rax\n\t" /* target->%rax */ \ VALGRIND_CALL_NOREDIR_RAX \ VALGRIND_RESTORE_STACK \ VALGRIND_CFI_EPILOGUE \ : /*out*/ "=a" (_res) \ : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r14", "r15" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_WWWW(lval, orig, arg1,arg2,arg3,arg4) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[5]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)(arg1); \ _argvec[2] = (unsigned long)(arg2); \ _argvec[3] = (unsigned long)(arg3); \ _argvec[4] = (unsigned long)(arg4); \ __asm__ volatile( \ VALGRIND_CFI_PROLOGUE \ VALGRIND_ALIGN_STACK \ "subq $128,%%rsp\n\t" \ "movq 32(%%rax), %%rcx\n\t" \ "movq 24(%%rax), %%rdx\n\t" \ "movq 16(%%rax), %%rsi\n\t" \ "movq 8(%%rax), %%rdi\n\t" \ "movq (%%rax), %%rax\n\t" /* target->%rax */ \ VALGRIND_CALL_NOREDIR_RAX \ VALGRIND_RESTORE_STACK \ VALGRIND_CFI_EPILOGUE \ : /*out*/ "=a" (_res) \ : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r14", "r15" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_5W(lval, orig, arg1,arg2,arg3,arg4,arg5) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[6]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)(arg1); \ _argvec[2] = (unsigned long)(arg2); \ _argvec[3] = (unsigned long)(arg3); \ _argvec[4] = (unsigned long)(arg4); \ _argvec[5] = (unsigned long)(arg5); \ __asm__ volatile( \ VALGRIND_CFI_PROLOGUE \ VALGRIND_ALIGN_STACK \ "subq $128,%%rsp\n\t" \ "movq 40(%%rax), %%r8\n\t" \ "movq 32(%%rax), %%rcx\n\t" \ "movq 24(%%rax), %%rdx\n\t" \ "movq 16(%%rax), %%rsi\n\t" \ "movq 8(%%rax), %%rdi\n\t" \ "movq (%%rax), %%rax\n\t" /* target->%rax */ \ VALGRIND_CALL_NOREDIR_RAX \ VALGRIND_RESTORE_STACK \ VALGRIND_CFI_EPILOGUE \ : /*out*/ "=a" (_res) \ : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r14", "r15" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_6W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[7]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)(arg1); \ _argvec[2] = (unsigned long)(arg2); \ _argvec[3] = (unsigned long)(arg3); \ _argvec[4] = (unsigned long)(arg4); \ _argvec[5] = (unsigned long)(arg5); \ _argvec[6] = (unsigned long)(arg6); \ __asm__ volatile( \ VALGRIND_CFI_PROLOGUE \ VALGRIND_ALIGN_STACK \ "subq $128,%%rsp\n\t" \ "movq 48(%%rax), %%r9\n\t" \ "movq 40(%%rax), %%r8\n\t" \ "movq 32(%%rax), %%rcx\n\t" \ "movq 24(%%rax), %%rdx\n\t" \ "movq 16(%%rax), %%rsi\n\t" \ "movq 8(%%rax), %%rdi\n\t" \ "movq (%%rax), %%rax\n\t" /* target->%rax */ \ VALGRIND_CALL_NOREDIR_RAX \ VALGRIND_RESTORE_STACK \ VALGRIND_CFI_EPILOGUE \ : /*out*/ "=a" (_res) \ : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r14", "r15" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_7W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ arg7) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[8]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)(arg1); \ _argvec[2] = (unsigned long)(arg2); \ _argvec[3] = (unsigned long)(arg3); \ _argvec[4] = (unsigned long)(arg4); \ _argvec[5] = (unsigned long)(arg5); \ _argvec[6] = (unsigned long)(arg6); \ _argvec[7] = (unsigned long)(arg7); \ __asm__ volatile( \ VALGRIND_CFI_PROLOGUE \ VALGRIND_ALIGN_STACK \ "subq $136,%%rsp\n\t" \ "pushq 56(%%rax)\n\t" \ "movq 48(%%rax), %%r9\n\t" \ "movq 40(%%rax), %%r8\n\t" \ "movq 32(%%rax), %%rcx\n\t" \ "movq 24(%%rax), %%rdx\n\t" \ "movq 16(%%rax), %%rsi\n\t" \ "movq 8(%%rax), %%rdi\n\t" \ "movq (%%rax), %%rax\n\t" /* target->%rax */ \ VALGRIND_CALL_NOREDIR_RAX \ VALGRIND_RESTORE_STACK \ VALGRIND_CFI_EPILOGUE \ : /*out*/ "=a" (_res) \ : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r14", "r15" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_8W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ arg7,arg8) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[9]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)(arg1); \ _argvec[2] = (unsigned long)(arg2); \ _argvec[3] = (unsigned long)(arg3); \ _argvec[4] = (unsigned long)(arg4); \ _argvec[5] = (unsigned long)(arg5); \ _argvec[6] = (unsigned long)(arg6); \ _argvec[7] = (unsigned long)(arg7); \ _argvec[8] = (unsigned long)(arg8); \ __asm__ volatile( \ VALGRIND_CFI_PROLOGUE \ VALGRIND_ALIGN_STACK \ "subq $128,%%rsp\n\t" \ "pushq 64(%%rax)\n\t" \ "pushq 56(%%rax)\n\t" \ "movq 48(%%rax), %%r9\n\t" \ "movq 40(%%rax), %%r8\n\t" \ "movq 32(%%rax), %%rcx\n\t" \ "movq 24(%%rax), %%rdx\n\t" \ "movq 16(%%rax), %%rsi\n\t" \ "movq 8(%%rax), %%rdi\n\t" \ "movq (%%rax), %%rax\n\t" /* target->%rax */ \ VALGRIND_CALL_NOREDIR_RAX \ VALGRIND_RESTORE_STACK \ VALGRIND_CFI_EPILOGUE \ : /*out*/ "=a" (_res) \ : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r14", "r15" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_9W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ arg7,arg8,arg9) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[10]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)(arg1); \ _argvec[2] = (unsigned long)(arg2); \ _argvec[3] = (unsigned long)(arg3); \ _argvec[4] = (unsigned long)(arg4); \ _argvec[5] = (unsigned long)(arg5); \ _argvec[6] = (unsigned long)(arg6); \ _argvec[7] = (unsigned long)(arg7); \ _argvec[8] = (unsigned long)(arg8); \ _argvec[9] = (unsigned long)(arg9); \ __asm__ volatile( \ VALGRIND_CFI_PROLOGUE \ VALGRIND_ALIGN_STACK \ "subq $136,%%rsp\n\t" \ "pushq 72(%%rax)\n\t" \ "pushq 64(%%rax)\n\t" \ "pushq 56(%%rax)\n\t" \ "movq 48(%%rax), %%r9\n\t" \ "movq 40(%%rax), %%r8\n\t" \ "movq 32(%%rax), %%rcx\n\t" \ "movq 24(%%rax), %%rdx\n\t" \ "movq 16(%%rax), %%rsi\n\t" \ "movq 8(%%rax), %%rdi\n\t" \ "movq (%%rax), %%rax\n\t" /* target->%rax */ \ VALGRIND_CALL_NOREDIR_RAX \ VALGRIND_RESTORE_STACK \ VALGRIND_CFI_EPILOGUE \ : /*out*/ "=a" (_res) \ : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r14", "r15" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_10W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ arg7,arg8,arg9,arg10) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[11]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)(arg1); \ _argvec[2] = (unsigned long)(arg2); \ _argvec[3] = (unsigned long)(arg3); \ _argvec[4] = (unsigned long)(arg4); \ _argvec[5] = (unsigned long)(arg5); \ _argvec[6] = (unsigned long)(arg6); \ _argvec[7] = (unsigned long)(arg7); \ _argvec[8] = (unsigned long)(arg8); \ _argvec[9] = (unsigned long)(arg9); \ _argvec[10] = (unsigned long)(arg10); \ __asm__ volatile( \ VALGRIND_CFI_PROLOGUE \ VALGRIND_ALIGN_STACK \ "subq $128,%%rsp\n\t" \ "pushq 80(%%rax)\n\t" \ "pushq 72(%%rax)\n\t" \ "pushq 64(%%rax)\n\t" \ "pushq 56(%%rax)\n\t" \ "movq 48(%%rax), %%r9\n\t" \ "movq 40(%%rax), %%r8\n\t" \ "movq 32(%%rax), %%rcx\n\t" \ "movq 24(%%rax), %%rdx\n\t" \ "movq 16(%%rax), %%rsi\n\t" \ "movq 8(%%rax), %%rdi\n\t" \ "movq (%%rax), %%rax\n\t" /* target->%rax */ \ VALGRIND_CALL_NOREDIR_RAX \ VALGRIND_RESTORE_STACK \ VALGRIND_CFI_EPILOGUE \ : /*out*/ "=a" (_res) \ : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r14", "r15" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_11W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ arg7,arg8,arg9,arg10,arg11) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[12]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)(arg1); \ _argvec[2] = (unsigned long)(arg2); \ _argvec[3] = (unsigned long)(arg3); \ _argvec[4] = (unsigned long)(arg4); \ _argvec[5] = (unsigned long)(arg5); \ _argvec[6] = (unsigned long)(arg6); \ _argvec[7] = (unsigned long)(arg7); \ _argvec[8] = (unsigned long)(arg8); \ _argvec[9] = (unsigned long)(arg9); \ _argvec[10] = (unsigned long)(arg10); \ _argvec[11] = (unsigned long)(arg11); \ __asm__ volatile( \ VALGRIND_CFI_PROLOGUE \ VALGRIND_ALIGN_STACK \ "subq $136,%%rsp\n\t" \ "pushq 88(%%rax)\n\t" \ "pushq 80(%%rax)\n\t" \ "pushq 72(%%rax)\n\t" \ "pushq 64(%%rax)\n\t" \ "pushq 56(%%rax)\n\t" \ "movq 48(%%rax), %%r9\n\t" \ "movq 40(%%rax), %%r8\n\t" \ "movq 32(%%rax), %%rcx\n\t" \ "movq 24(%%rax), %%rdx\n\t" \ "movq 16(%%rax), %%rsi\n\t" \ "movq 8(%%rax), %%rdi\n\t" \ "movq (%%rax), %%rax\n\t" /* target->%rax */ \ VALGRIND_CALL_NOREDIR_RAX \ VALGRIND_RESTORE_STACK \ VALGRIND_CFI_EPILOGUE \ : /*out*/ "=a" (_res) \ : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r14", "r15" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_12W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ arg7,arg8,arg9,arg10,arg11,arg12) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[13]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)(arg1); \ _argvec[2] = (unsigned long)(arg2); \ _argvec[3] = (unsigned long)(arg3); \ _argvec[4] = (unsigned long)(arg4); \ _argvec[5] = (unsigned long)(arg5); \ _argvec[6] = (unsigned long)(arg6); \ _argvec[7] = (unsigned long)(arg7); \ _argvec[8] = (unsigned long)(arg8); \ _argvec[9] = (unsigned long)(arg9); \ _argvec[10] = (unsigned long)(arg10); \ _argvec[11] = (unsigned long)(arg11); \ _argvec[12] = (unsigned long)(arg12); \ __asm__ volatile( \ VALGRIND_CFI_PROLOGUE \ VALGRIND_ALIGN_STACK \ "subq $128,%%rsp\n\t" \ "pushq 96(%%rax)\n\t" \ "pushq 88(%%rax)\n\t" \ "pushq 80(%%rax)\n\t" \ "pushq 72(%%rax)\n\t" \ "pushq 64(%%rax)\n\t" \ "pushq 56(%%rax)\n\t" \ "movq 48(%%rax), %%r9\n\t" \ "movq 40(%%rax), %%r8\n\t" \ "movq 32(%%rax), %%rcx\n\t" \ "movq 24(%%rax), %%rdx\n\t" \ "movq 16(%%rax), %%rsi\n\t" \ "movq 8(%%rax), %%rdi\n\t" \ "movq (%%rax), %%rax\n\t" /* target->%rax */ \ VALGRIND_CALL_NOREDIR_RAX \ VALGRIND_RESTORE_STACK \ VALGRIND_CFI_EPILOGUE \ : /*out*/ "=a" (_res) \ : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r14", "r15" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #endif /* PLAT_amd64_linux || PLAT_amd64_darwin || PLAT_amd64_solaris */ /* ------------------------ ppc32-linux ------------------------ */ #if defined(PLAT_ppc32_linux) /* This is useful for finding out about the on-stack stuff: extern int f9 ( int,int,int,int,int,int,int,int,int ); extern int f10 ( int,int,int,int,int,int,int,int,int,int ); extern int f11 ( int,int,int,int,int,int,int,int,int,int,int ); extern int f12 ( int,int,int,int,int,int,int,int,int,int,int,int ); int g9 ( void ) { return f9(11,22,33,44,55,66,77,88,99); } int g10 ( void ) { return f10(11,22,33,44,55,66,77,88,99,110); } int g11 ( void ) { return f11(11,22,33,44,55,66,77,88,99,110,121); } int g12 ( void ) { return f12(11,22,33,44,55,66,77,88,99,110,121,132); } */ /* ARGREGS: r3 r4 r5 r6 r7 r8 r9 r10 (the rest on stack somewhere) */ /* These regs are trashed by the hidden call. */ #define __CALLER_SAVED_REGS \ "lr", "ctr", "xer", \ "cr0", "cr1", "cr2", "cr3", "cr4", "cr5", "cr6", "cr7", \ "r0", "r2", "r3", "r4", "r5", "r6", "r7", "r8", "r9", "r10", \ "r11", "r12", "r13" /* Macros to save and align the stack before making a function call and restore it afterwards as gcc may not keep the stack pointer aligned if it doesn't realise calls are being made to other functions. */ #define VALGRIND_ALIGN_STACK \ "mr 28,1\n\t" \ "rlwinm 1,1,0,0,27\n\t" #define VALGRIND_RESTORE_STACK \ "mr 1,28\n\t" /* These CALL_FN_ macros assume that on ppc32-linux, sizeof(unsigned long) == 4. */ #define CALL_FN_W_v(lval, orig) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[1]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "mr 11,%1\n\t" \ "lwz 11,0(11)\n\t" /* target->r11 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ VALGRIND_RESTORE_STACK \ "mr %0,3" \ : /*out*/ "=r" (_res) \ : /*in*/ "r" (&_argvec[0]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_W(lval, orig, arg1) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[2]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)arg1; \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "mr 11,%1\n\t" \ "lwz 3,4(11)\n\t" /* arg1->r3 */ \ "lwz 11,0(11)\n\t" /* target->r11 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ VALGRIND_RESTORE_STACK \ "mr %0,3" \ : /*out*/ "=r" (_res) \ : /*in*/ "r" (&_argvec[0]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_WW(lval, orig, arg1,arg2) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[3]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)arg1; \ _argvec[2] = (unsigned long)arg2; \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "mr 11,%1\n\t" \ "lwz 3,4(11)\n\t" /* arg1->r3 */ \ "lwz 4,8(11)\n\t" \ "lwz 11,0(11)\n\t" /* target->r11 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ VALGRIND_RESTORE_STACK \ "mr %0,3" \ : /*out*/ "=r" (_res) \ : /*in*/ "r" (&_argvec[0]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_WWW(lval, orig, arg1,arg2,arg3) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[4]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)arg1; \ _argvec[2] = (unsigned long)arg2; \ _argvec[3] = (unsigned long)arg3; \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "mr 11,%1\n\t" \ "lwz 3,4(11)\n\t" /* arg1->r3 */ \ "lwz 4,8(11)\n\t" \ "lwz 5,12(11)\n\t" \ "lwz 11,0(11)\n\t" /* target->r11 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ VALGRIND_RESTORE_STACK \ "mr %0,3" \ : /*out*/ "=r" (_res) \ : /*in*/ "r" (&_argvec[0]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_WWWW(lval, orig, arg1,arg2,arg3,arg4) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[5]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)arg1; \ _argvec[2] = (unsigned long)arg2; \ _argvec[3] = (unsigned long)arg3; \ _argvec[4] = (unsigned long)arg4; \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "mr 11,%1\n\t" \ "lwz 3,4(11)\n\t" /* arg1->r3 */ \ "lwz 4,8(11)\n\t" \ "lwz 5,12(11)\n\t" \ "lwz 6,16(11)\n\t" /* arg4->r6 */ \ "lwz 11,0(11)\n\t" /* target->r11 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ VALGRIND_RESTORE_STACK \ "mr %0,3" \ : /*out*/ "=r" (_res) \ : /*in*/ "r" (&_argvec[0]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_5W(lval, orig, arg1,arg2,arg3,arg4,arg5) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[6]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)arg1; \ _argvec[2] = (unsigned long)arg2; \ _argvec[3] = (unsigned long)arg3; \ _argvec[4] = (unsigned long)arg4; \ _argvec[5] = (unsigned long)arg5; \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "mr 11,%1\n\t" \ "lwz 3,4(11)\n\t" /* arg1->r3 */ \ "lwz 4,8(11)\n\t" \ "lwz 5,12(11)\n\t" \ "lwz 6,16(11)\n\t" /* arg4->r6 */ \ "lwz 7,20(11)\n\t" \ "lwz 11,0(11)\n\t" /* target->r11 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ VALGRIND_RESTORE_STACK \ "mr %0,3" \ : /*out*/ "=r" (_res) \ : /*in*/ "r" (&_argvec[0]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_6W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[7]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)arg1; \ _argvec[2] = (unsigned long)arg2; \ _argvec[3] = (unsigned long)arg3; \ _argvec[4] = (unsigned long)arg4; \ _argvec[5] = (unsigned long)arg5; \ _argvec[6] = (unsigned long)arg6; \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "mr 11,%1\n\t" \ "lwz 3,4(11)\n\t" /* arg1->r3 */ \ "lwz 4,8(11)\n\t" \ "lwz 5,12(11)\n\t" \ "lwz 6,16(11)\n\t" /* arg4->r6 */ \ "lwz 7,20(11)\n\t" \ "lwz 8,24(11)\n\t" \ "lwz 11,0(11)\n\t" /* target->r11 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ VALGRIND_RESTORE_STACK \ "mr %0,3" \ : /*out*/ "=r" (_res) \ : /*in*/ "r" (&_argvec[0]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_7W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ arg7) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[8]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)arg1; \ _argvec[2] = (unsigned long)arg2; \ _argvec[3] = (unsigned long)arg3; \ _argvec[4] = (unsigned long)arg4; \ _argvec[5] = (unsigned long)arg5; \ _argvec[6] = (unsigned long)arg6; \ _argvec[7] = (unsigned long)arg7; \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "mr 11,%1\n\t" \ "lwz 3,4(11)\n\t" /* arg1->r3 */ \ "lwz 4,8(11)\n\t" \ "lwz 5,12(11)\n\t" \ "lwz 6,16(11)\n\t" /* arg4->r6 */ \ "lwz 7,20(11)\n\t" \ "lwz 8,24(11)\n\t" \ "lwz 9,28(11)\n\t" \ "lwz 11,0(11)\n\t" /* target->r11 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ VALGRIND_RESTORE_STACK \ "mr %0,3" \ : /*out*/ "=r" (_res) \ : /*in*/ "r" (&_argvec[0]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_8W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ arg7,arg8) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[9]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)arg1; \ _argvec[2] = (unsigned long)arg2; \ _argvec[3] = (unsigned long)arg3; \ _argvec[4] = (unsigned long)arg4; \ _argvec[5] = (unsigned long)arg5; \ _argvec[6] = (unsigned long)arg6; \ _argvec[7] = (unsigned long)arg7; \ _argvec[8] = (unsigned long)arg8; \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "mr 11,%1\n\t" \ "lwz 3,4(11)\n\t" /* arg1->r3 */ \ "lwz 4,8(11)\n\t" \ "lwz 5,12(11)\n\t" \ "lwz 6,16(11)\n\t" /* arg4->r6 */ \ "lwz 7,20(11)\n\t" \ "lwz 8,24(11)\n\t" \ "lwz 9,28(11)\n\t" \ "lwz 10,32(11)\n\t" /* arg8->r10 */ \ "lwz 11,0(11)\n\t" /* target->r11 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ VALGRIND_RESTORE_STACK \ "mr %0,3" \ : /*out*/ "=r" (_res) \ : /*in*/ "r" (&_argvec[0]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_9W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ arg7,arg8,arg9) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[10]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)arg1; \ _argvec[2] = (unsigned long)arg2; \ _argvec[3] = (unsigned long)arg3; \ _argvec[4] = (unsigned long)arg4; \ _argvec[5] = (unsigned long)arg5; \ _argvec[6] = (unsigned long)arg6; \ _argvec[7] = (unsigned long)arg7; \ _argvec[8] = (unsigned long)arg8; \ _argvec[9] = (unsigned long)arg9; \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "mr 11,%1\n\t" \ "addi 1,1,-16\n\t" \ /* arg9 */ \ "lwz 3,36(11)\n\t" \ "stw 3,8(1)\n\t" \ /* args1-8 */ \ "lwz 3,4(11)\n\t" /* arg1->r3 */ \ "lwz 4,8(11)\n\t" \ "lwz 5,12(11)\n\t" \ "lwz 6,16(11)\n\t" /* arg4->r6 */ \ "lwz 7,20(11)\n\t" \ "lwz 8,24(11)\n\t" \ "lwz 9,28(11)\n\t" \ "lwz 10,32(11)\n\t" /* arg8->r10 */ \ "lwz 11,0(11)\n\t" /* target->r11 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ VALGRIND_RESTORE_STACK \ "mr %0,3" \ : /*out*/ "=r" (_res) \ : /*in*/ "r" (&_argvec[0]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_10W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ arg7,arg8,arg9,arg10) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[11]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)arg1; \ _argvec[2] = (unsigned long)arg2; \ _argvec[3] = (unsigned long)arg3; \ _argvec[4] = (unsigned long)arg4; \ _argvec[5] = (unsigned long)arg5; \ _argvec[6] = (unsigned long)arg6; \ _argvec[7] = (unsigned long)arg7; \ _argvec[8] = (unsigned long)arg8; \ _argvec[9] = (unsigned long)arg9; \ _argvec[10] = (unsigned long)arg10; \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "mr 11,%1\n\t" \ "addi 1,1,-16\n\t" \ /* arg10 */ \ "lwz 3,40(11)\n\t" \ "stw 3,12(1)\n\t" \ /* arg9 */ \ "lwz 3,36(11)\n\t" \ "stw 3,8(1)\n\t" \ /* args1-8 */ \ "lwz 3,4(11)\n\t" /* arg1->r3 */ \ "lwz 4,8(11)\n\t" \ "lwz 5,12(11)\n\t" \ "lwz 6,16(11)\n\t" /* arg4->r6 */ \ "lwz 7,20(11)\n\t" \ "lwz 8,24(11)\n\t" \ "lwz 9,28(11)\n\t" \ "lwz 10,32(11)\n\t" /* arg8->r10 */ \ "lwz 11,0(11)\n\t" /* target->r11 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ VALGRIND_RESTORE_STACK \ "mr %0,3" \ : /*out*/ "=r" (_res) \ : /*in*/ "r" (&_argvec[0]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_11W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ arg7,arg8,arg9,arg10,arg11) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[12]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)arg1; \ _argvec[2] = (unsigned long)arg2; \ _argvec[3] = (unsigned long)arg3; \ _argvec[4] = (unsigned long)arg4; \ _argvec[5] = (unsigned long)arg5; \ _argvec[6] = (unsigned long)arg6; \ _argvec[7] = (unsigned long)arg7; \ _argvec[8] = (unsigned long)arg8; \ _argvec[9] = (unsigned long)arg9; \ _argvec[10] = (unsigned long)arg10; \ _argvec[11] = (unsigned long)arg11; \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "mr 11,%1\n\t" \ "addi 1,1,-32\n\t" \ /* arg11 */ \ "lwz 3,44(11)\n\t" \ "stw 3,16(1)\n\t" \ /* arg10 */ \ "lwz 3,40(11)\n\t" \ "stw 3,12(1)\n\t" \ /* arg9 */ \ "lwz 3,36(11)\n\t" \ "stw 3,8(1)\n\t" \ /* args1-8 */ \ "lwz 3,4(11)\n\t" /* arg1->r3 */ \ "lwz 4,8(11)\n\t" \ "lwz 5,12(11)\n\t" \ "lwz 6,16(11)\n\t" /* arg4->r6 */ \ "lwz 7,20(11)\n\t" \ "lwz 8,24(11)\n\t" \ "lwz 9,28(11)\n\t" \ "lwz 10,32(11)\n\t" /* arg8->r10 */ \ "lwz 11,0(11)\n\t" /* target->r11 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ VALGRIND_RESTORE_STACK \ "mr %0,3" \ : /*out*/ "=r" (_res) \ : /*in*/ "r" (&_argvec[0]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_12W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ arg7,arg8,arg9,arg10,arg11,arg12) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[13]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)arg1; \ _argvec[2] = (unsigned long)arg2; \ _argvec[3] = (unsigned long)arg3; \ _argvec[4] = (unsigned long)arg4; \ _argvec[5] = (unsigned long)arg5; \ _argvec[6] = (unsigned long)arg6; \ _argvec[7] = (unsigned long)arg7; \ _argvec[8] = (unsigned long)arg8; \ _argvec[9] = (unsigned long)arg9; \ _argvec[10] = (unsigned long)arg10; \ _argvec[11] = (unsigned long)arg11; \ _argvec[12] = (unsigned long)arg12; \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "mr 11,%1\n\t" \ "addi 1,1,-32\n\t" \ /* arg12 */ \ "lwz 3,48(11)\n\t" \ "stw 3,20(1)\n\t" \ /* arg11 */ \ "lwz 3,44(11)\n\t" \ "stw 3,16(1)\n\t" \ /* arg10 */ \ "lwz 3,40(11)\n\t" \ "stw 3,12(1)\n\t" \ /* arg9 */ \ "lwz 3,36(11)\n\t" \ "stw 3,8(1)\n\t" \ /* args1-8 */ \ "lwz 3,4(11)\n\t" /* arg1->r3 */ \ "lwz 4,8(11)\n\t" \ "lwz 5,12(11)\n\t" \ "lwz 6,16(11)\n\t" /* arg4->r6 */ \ "lwz 7,20(11)\n\t" \ "lwz 8,24(11)\n\t" \ "lwz 9,28(11)\n\t" \ "lwz 10,32(11)\n\t" /* arg8->r10 */ \ "lwz 11,0(11)\n\t" /* target->r11 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ VALGRIND_RESTORE_STACK \ "mr %0,3" \ : /*out*/ "=r" (_res) \ : /*in*/ "r" (&_argvec[0]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #endif /* PLAT_ppc32_linux */ /* ------------------------ ppc64-linux ------------------------ */ #if defined(PLAT_ppc64be_linux) /* ARGREGS: r3 r4 r5 r6 r7 r8 r9 r10 (the rest on stack somewhere) */ /* These regs are trashed by the hidden call. */ #define __CALLER_SAVED_REGS \ "lr", "ctr", "xer", \ "cr0", "cr1", "cr2", "cr3", "cr4", "cr5", "cr6", "cr7", \ "r0", "r3", "r4", "r5", "r6", "r7", "r8", "r9", "r10", \ "r11", "r12", "r13" /* Macros to save and align the stack before making a function call and restore it afterwards as gcc may not keep the stack pointer aligned if it doesn't realise calls are being made to other functions. */ #define VALGRIND_ALIGN_STACK \ "mr 28,1\n\t" \ "rldicr 1,1,0,59\n\t" #define VALGRIND_RESTORE_STACK \ "mr 1,28\n\t" /* These CALL_FN_ macros assume that on ppc64-linux, sizeof(unsigned long) == 8. */ #define CALL_FN_W_v(lval, orig) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[3+0]; \ volatile unsigned long _res; \ /* _argvec[0] holds current r2 across the call */ \ _argvec[1] = (unsigned long)_orig.r2; \ _argvec[2] = (unsigned long)_orig.nraddr; \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "mr 11,%1\n\t" \ "std 2,-16(11)\n\t" /* save tocptr */ \ "ld 2,-8(11)\n\t" /* use nraddr's tocptr */ \ "ld 11, 0(11)\n\t" /* target->r11 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ "mr 11,%1\n\t" \ "mr %0,3\n\t" \ "ld 2,-16(11)\n\t" /* restore tocptr */ \ VALGRIND_RESTORE_STACK \ : /*out*/ "=r" (_res) \ : /*in*/ "r" (&_argvec[2]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_W(lval, orig, arg1) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[3+1]; \ volatile unsigned long _res; \ /* _argvec[0] holds current r2 across the call */ \ _argvec[1] = (unsigned long)_orig.r2; \ _argvec[2] = (unsigned long)_orig.nraddr; \ _argvec[2+1] = (unsigned long)arg1; \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "mr 11,%1\n\t" \ "std 2,-16(11)\n\t" /* save tocptr */ \ "ld 2,-8(11)\n\t" /* use nraddr's tocptr */ \ "ld 3, 8(11)\n\t" /* arg1->r3 */ \ "ld 11, 0(11)\n\t" /* target->r11 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ "mr 11,%1\n\t" \ "mr %0,3\n\t" \ "ld 2,-16(11)\n\t" /* restore tocptr */ \ VALGRIND_RESTORE_STACK \ : /*out*/ "=r" (_res) \ : /*in*/ "r" (&_argvec[2]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_WW(lval, orig, arg1,arg2) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[3+2]; \ volatile unsigned long _res; \ /* _argvec[0] holds current r2 across the call */ \ _argvec[1] = (unsigned long)_orig.r2; \ _argvec[2] = (unsigned long)_orig.nraddr; \ _argvec[2+1] = (unsigned long)arg1; \ _argvec[2+2] = (unsigned long)arg2; \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "mr 11,%1\n\t" \ "std 2,-16(11)\n\t" /* save tocptr */ \ "ld 2,-8(11)\n\t" /* use nraddr's tocptr */ \ "ld 3, 8(11)\n\t" /* arg1->r3 */ \ "ld 4, 16(11)\n\t" /* arg2->r4 */ \ "ld 11, 0(11)\n\t" /* target->r11 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ "mr 11,%1\n\t" \ "mr %0,3\n\t" \ "ld 2,-16(11)\n\t" /* restore tocptr */ \ VALGRIND_RESTORE_STACK \ : /*out*/ "=r" (_res) \ : /*in*/ "r" (&_argvec[2]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_WWW(lval, orig, arg1,arg2,arg3) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[3+3]; \ volatile unsigned long _res; \ /* _argvec[0] holds current r2 across the call */ \ _argvec[1] = (unsigned long)_orig.r2; \ _argvec[2] = (unsigned long)_orig.nraddr; \ _argvec[2+1] = (unsigned long)arg1; \ _argvec[2+2] = (unsigned long)arg2; \ _argvec[2+3] = (unsigned long)arg3; \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "mr 11,%1\n\t" \ "std 2,-16(11)\n\t" /* save tocptr */ \ "ld 2,-8(11)\n\t" /* use nraddr's tocptr */ \ "ld 3, 8(11)\n\t" /* arg1->r3 */ \ "ld 4, 16(11)\n\t" /* arg2->r4 */ \ "ld 5, 24(11)\n\t" /* arg3->r5 */ \ "ld 11, 0(11)\n\t" /* target->r11 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ "mr 11,%1\n\t" \ "mr %0,3\n\t" \ "ld 2,-16(11)\n\t" /* restore tocptr */ \ VALGRIND_RESTORE_STACK \ : /*out*/ "=r" (_res) \ : /*in*/ "r" (&_argvec[2]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_WWWW(lval, orig, arg1,arg2,arg3,arg4) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[3+4]; \ volatile unsigned long _res; \ /* _argvec[0] holds current r2 across the call */ \ _argvec[1] = (unsigned long)_orig.r2; \ _argvec[2] = (unsigned long)_orig.nraddr; \ _argvec[2+1] = (unsigned long)arg1; \ _argvec[2+2] = (unsigned long)arg2; \ _argvec[2+3] = (unsigned long)arg3; \ _argvec[2+4] = (unsigned long)arg4; \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "mr 11,%1\n\t" \ "std 2,-16(11)\n\t" /* save tocptr */ \ "ld 2,-8(11)\n\t" /* use nraddr's tocptr */ \ "ld 3, 8(11)\n\t" /* arg1->r3 */ \ "ld 4, 16(11)\n\t" /* arg2->r4 */ \ "ld 5, 24(11)\n\t" /* arg3->r5 */ \ "ld 6, 32(11)\n\t" /* arg4->r6 */ \ "ld 11, 0(11)\n\t" /* target->r11 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ "mr 11,%1\n\t" \ "mr %0,3\n\t" \ "ld 2,-16(11)\n\t" /* restore tocptr */ \ VALGRIND_RESTORE_STACK \ : /*out*/ "=r" (_res) \ : /*in*/ "r" (&_argvec[2]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_5W(lval, orig, arg1,arg2,arg3,arg4,arg5) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[3+5]; \ volatile unsigned long _res; \ /* _argvec[0] holds current r2 across the call */ \ _argvec[1] = (unsigned long)_orig.r2; \ _argvec[2] = (unsigned long)_orig.nraddr; \ _argvec[2+1] = (unsigned long)arg1; \ _argvec[2+2] = (unsigned long)arg2; \ _argvec[2+3] = (unsigned long)arg3; \ _argvec[2+4] = (unsigned long)arg4; \ _argvec[2+5] = (unsigned long)arg5; \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "mr 11,%1\n\t" \ "std 2,-16(11)\n\t" /* save tocptr */ \ "ld 2,-8(11)\n\t" /* use nraddr's tocptr */ \ "ld 3, 8(11)\n\t" /* arg1->r3 */ \ "ld 4, 16(11)\n\t" /* arg2->r4 */ \ "ld 5, 24(11)\n\t" /* arg3->r5 */ \ "ld 6, 32(11)\n\t" /* arg4->r6 */ \ "ld 7, 40(11)\n\t" /* arg5->r7 */ \ "ld 11, 0(11)\n\t" /* target->r11 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ "mr 11,%1\n\t" \ "mr %0,3\n\t" \ "ld 2,-16(11)\n\t" /* restore tocptr */ \ VALGRIND_RESTORE_STACK \ : /*out*/ "=r" (_res) \ : /*in*/ "r" (&_argvec[2]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_6W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[3+6]; \ volatile unsigned long _res; \ /* _argvec[0] holds current r2 across the call */ \ _argvec[1] = (unsigned long)_orig.r2; \ _argvec[2] = (unsigned long)_orig.nraddr; \ _argvec[2+1] = (unsigned long)arg1; \ _argvec[2+2] = (unsigned long)arg2; \ _argvec[2+3] = (unsigned long)arg3; \ _argvec[2+4] = (unsigned long)arg4; \ _argvec[2+5] = (unsigned long)arg5; \ _argvec[2+6] = (unsigned long)arg6; \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "mr 11,%1\n\t" \ "std 2,-16(11)\n\t" /* save tocptr */ \ "ld 2,-8(11)\n\t" /* use nraddr's tocptr */ \ "ld 3, 8(11)\n\t" /* arg1->r3 */ \ "ld 4, 16(11)\n\t" /* arg2->r4 */ \ "ld 5, 24(11)\n\t" /* arg3->r5 */ \ "ld 6, 32(11)\n\t" /* arg4->r6 */ \ "ld 7, 40(11)\n\t" /* arg5->r7 */ \ "ld 8, 48(11)\n\t" /* arg6->r8 */ \ "ld 11, 0(11)\n\t" /* target->r11 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ "mr 11,%1\n\t" \ "mr %0,3\n\t" \ "ld 2,-16(11)\n\t" /* restore tocptr */ \ VALGRIND_RESTORE_STACK \ : /*out*/ "=r" (_res) \ : /*in*/ "r" (&_argvec[2]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_7W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ arg7) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[3+7]; \ volatile unsigned long _res; \ /* _argvec[0] holds current r2 across the call */ \ _argvec[1] = (unsigned long)_orig.r2; \ _argvec[2] = (unsigned long)_orig.nraddr; \ _argvec[2+1] = (unsigned long)arg1; \ _argvec[2+2] = (unsigned long)arg2; \ _argvec[2+3] = (unsigned long)arg3; \ _argvec[2+4] = (unsigned long)arg4; \ _argvec[2+5] = (unsigned long)arg5; \ _argvec[2+6] = (unsigned long)arg6; \ _argvec[2+7] = (unsigned long)arg7; \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "mr 11,%1\n\t" \ "std 2,-16(11)\n\t" /* save tocptr */ \ "ld 2,-8(11)\n\t" /* use nraddr's tocptr */ \ "ld 3, 8(11)\n\t" /* arg1->r3 */ \ "ld 4, 16(11)\n\t" /* arg2->r4 */ \ "ld 5, 24(11)\n\t" /* arg3->r5 */ \ "ld 6, 32(11)\n\t" /* arg4->r6 */ \ "ld 7, 40(11)\n\t" /* arg5->r7 */ \ "ld 8, 48(11)\n\t" /* arg6->r8 */ \ "ld 9, 56(11)\n\t" /* arg7->r9 */ \ "ld 11, 0(11)\n\t" /* target->r11 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ "mr 11,%1\n\t" \ "mr %0,3\n\t" \ "ld 2,-16(11)\n\t" /* restore tocptr */ \ VALGRIND_RESTORE_STACK \ : /*out*/ "=r" (_res) \ : /*in*/ "r" (&_argvec[2]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_8W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ arg7,arg8) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[3+8]; \ volatile unsigned long _res; \ /* _argvec[0] holds current r2 across the call */ \ _argvec[1] = (unsigned long)_orig.r2; \ _argvec[2] = (unsigned long)_orig.nraddr; \ _argvec[2+1] = (unsigned long)arg1; \ _argvec[2+2] = (unsigned long)arg2; \ _argvec[2+3] = (unsigned long)arg3; \ _argvec[2+4] = (unsigned long)arg4; \ _argvec[2+5] = (unsigned long)arg5; \ _argvec[2+6] = (unsigned long)arg6; \ _argvec[2+7] = (unsigned long)arg7; \ _argvec[2+8] = (unsigned long)arg8; \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "mr 11,%1\n\t" \ "std 2,-16(11)\n\t" /* save tocptr */ \ "ld 2,-8(11)\n\t" /* use nraddr's tocptr */ \ "ld 3, 8(11)\n\t" /* arg1->r3 */ \ "ld 4, 16(11)\n\t" /* arg2->r4 */ \ "ld 5, 24(11)\n\t" /* arg3->r5 */ \ "ld 6, 32(11)\n\t" /* arg4->r6 */ \ "ld 7, 40(11)\n\t" /* arg5->r7 */ \ "ld 8, 48(11)\n\t" /* arg6->r8 */ \ "ld 9, 56(11)\n\t" /* arg7->r9 */ \ "ld 10, 64(11)\n\t" /* arg8->r10 */ \ "ld 11, 0(11)\n\t" /* target->r11 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ "mr 11,%1\n\t" \ "mr %0,3\n\t" \ "ld 2,-16(11)\n\t" /* restore tocptr */ \ VALGRIND_RESTORE_STACK \ : /*out*/ "=r" (_res) \ : /*in*/ "r" (&_argvec[2]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_9W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ arg7,arg8,arg9) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[3+9]; \ volatile unsigned long _res; \ /* _argvec[0] holds current r2 across the call */ \ _argvec[1] = (unsigned long)_orig.r2; \ _argvec[2] = (unsigned long)_orig.nraddr; \ _argvec[2+1] = (unsigned long)arg1; \ _argvec[2+2] = (unsigned long)arg2; \ _argvec[2+3] = (unsigned long)arg3; \ _argvec[2+4] = (unsigned long)arg4; \ _argvec[2+5] = (unsigned long)arg5; \ _argvec[2+6] = (unsigned long)arg6; \ _argvec[2+7] = (unsigned long)arg7; \ _argvec[2+8] = (unsigned long)arg8; \ _argvec[2+9] = (unsigned long)arg9; \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "mr 11,%1\n\t" \ "std 2,-16(11)\n\t" /* save tocptr */ \ "ld 2,-8(11)\n\t" /* use nraddr's tocptr */ \ "addi 1,1,-128\n\t" /* expand stack frame */ \ /* arg9 */ \ "ld 3,72(11)\n\t" \ "std 3,112(1)\n\t" \ /* args1-8 */ \ "ld 3, 8(11)\n\t" /* arg1->r3 */ \ "ld 4, 16(11)\n\t" /* arg2->r4 */ \ "ld 5, 24(11)\n\t" /* arg3->r5 */ \ "ld 6, 32(11)\n\t" /* arg4->r6 */ \ "ld 7, 40(11)\n\t" /* arg5->r7 */ \ "ld 8, 48(11)\n\t" /* arg6->r8 */ \ "ld 9, 56(11)\n\t" /* arg7->r9 */ \ "ld 10, 64(11)\n\t" /* arg8->r10 */ \ "ld 11, 0(11)\n\t" /* target->r11 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ "mr 11,%1\n\t" \ "mr %0,3\n\t" \ "ld 2,-16(11)\n\t" /* restore tocptr */ \ VALGRIND_RESTORE_STACK \ : /*out*/ "=r" (_res) \ : /*in*/ "r" (&_argvec[2]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_10W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ arg7,arg8,arg9,arg10) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[3+10]; \ volatile unsigned long _res; \ /* _argvec[0] holds current r2 across the call */ \ _argvec[1] = (unsigned long)_orig.r2; \ _argvec[2] = (unsigned long)_orig.nraddr; \ _argvec[2+1] = (unsigned long)arg1; \ _argvec[2+2] = (unsigned long)arg2; \ _argvec[2+3] = (unsigned long)arg3; \ _argvec[2+4] = (unsigned long)arg4; \ _argvec[2+5] = (unsigned long)arg5; \ _argvec[2+6] = (unsigned long)arg6; \ _argvec[2+7] = (unsigned long)arg7; \ _argvec[2+8] = (unsigned long)arg8; \ _argvec[2+9] = (unsigned long)arg9; \ _argvec[2+10] = (unsigned long)arg10; \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "mr 11,%1\n\t" \ "std 2,-16(11)\n\t" /* save tocptr */ \ "ld 2,-8(11)\n\t" /* use nraddr's tocptr */ \ "addi 1,1,-128\n\t" /* expand stack frame */ \ /* arg10 */ \ "ld 3,80(11)\n\t" \ "std 3,120(1)\n\t" \ /* arg9 */ \ "ld 3,72(11)\n\t" \ "std 3,112(1)\n\t" \ /* args1-8 */ \ "ld 3, 8(11)\n\t" /* arg1->r3 */ \ "ld 4, 16(11)\n\t" /* arg2->r4 */ \ "ld 5, 24(11)\n\t" /* arg3->r5 */ \ "ld 6, 32(11)\n\t" /* arg4->r6 */ \ "ld 7, 40(11)\n\t" /* arg5->r7 */ \ "ld 8, 48(11)\n\t" /* arg6->r8 */ \ "ld 9, 56(11)\n\t" /* arg7->r9 */ \ "ld 10, 64(11)\n\t" /* arg8->r10 */ \ "ld 11, 0(11)\n\t" /* target->r11 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ "mr 11,%1\n\t" \ "mr %0,3\n\t" \ "ld 2,-16(11)\n\t" /* restore tocptr */ \ VALGRIND_RESTORE_STACK \ : /*out*/ "=r" (_res) \ : /*in*/ "r" (&_argvec[2]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_11W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ arg7,arg8,arg9,arg10,arg11) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[3+11]; \ volatile unsigned long _res; \ /* _argvec[0] holds current r2 across the call */ \ _argvec[1] = (unsigned long)_orig.r2; \ _argvec[2] = (unsigned long)_orig.nraddr; \ _argvec[2+1] = (unsigned long)arg1; \ _argvec[2+2] = (unsigned long)arg2; \ _argvec[2+3] = (unsigned long)arg3; \ _argvec[2+4] = (unsigned long)arg4; \ _argvec[2+5] = (unsigned long)arg5; \ _argvec[2+6] = (unsigned long)arg6; \ _argvec[2+7] = (unsigned long)arg7; \ _argvec[2+8] = (unsigned long)arg8; \ _argvec[2+9] = (unsigned long)arg9; \ _argvec[2+10] = (unsigned long)arg10; \ _argvec[2+11] = (unsigned long)arg11; \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "mr 11,%1\n\t" \ "std 2,-16(11)\n\t" /* save tocptr */ \ "ld 2,-8(11)\n\t" /* use nraddr's tocptr */ \ "addi 1,1,-144\n\t" /* expand stack frame */ \ /* arg11 */ \ "ld 3,88(11)\n\t" \ "std 3,128(1)\n\t" \ /* arg10 */ \ "ld 3,80(11)\n\t" \ "std 3,120(1)\n\t" \ /* arg9 */ \ "ld 3,72(11)\n\t" \ "std 3,112(1)\n\t" \ /* args1-8 */ \ "ld 3, 8(11)\n\t" /* arg1->r3 */ \ "ld 4, 16(11)\n\t" /* arg2->r4 */ \ "ld 5, 24(11)\n\t" /* arg3->r5 */ \ "ld 6, 32(11)\n\t" /* arg4->r6 */ \ "ld 7, 40(11)\n\t" /* arg5->r7 */ \ "ld 8, 48(11)\n\t" /* arg6->r8 */ \ "ld 9, 56(11)\n\t" /* arg7->r9 */ \ "ld 10, 64(11)\n\t" /* arg8->r10 */ \ "ld 11, 0(11)\n\t" /* target->r11 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ "mr 11,%1\n\t" \ "mr %0,3\n\t" \ "ld 2,-16(11)\n\t" /* restore tocptr */ \ VALGRIND_RESTORE_STACK \ : /*out*/ "=r" (_res) \ : /*in*/ "r" (&_argvec[2]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_12W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ arg7,arg8,arg9,arg10,arg11,arg12) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[3+12]; \ volatile unsigned long _res; \ /* _argvec[0] holds current r2 across the call */ \ _argvec[1] = (unsigned long)_orig.r2; \ _argvec[2] = (unsigned long)_orig.nraddr; \ _argvec[2+1] = (unsigned long)arg1; \ _argvec[2+2] = (unsigned long)arg2; \ _argvec[2+3] = (unsigned long)arg3; \ _argvec[2+4] = (unsigned long)arg4; \ _argvec[2+5] = (unsigned long)arg5; \ _argvec[2+6] = (unsigned long)arg6; \ _argvec[2+7] = (unsigned long)arg7; \ _argvec[2+8] = (unsigned long)arg8; \ _argvec[2+9] = (unsigned long)arg9; \ _argvec[2+10] = (unsigned long)arg10; \ _argvec[2+11] = (unsigned long)arg11; \ _argvec[2+12] = (unsigned long)arg12; \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "mr 11,%1\n\t" \ "std 2,-16(11)\n\t" /* save tocptr */ \ "ld 2,-8(11)\n\t" /* use nraddr's tocptr */ \ "addi 1,1,-144\n\t" /* expand stack frame */ \ /* arg12 */ \ "ld 3,96(11)\n\t" \ "std 3,136(1)\n\t" \ /* arg11 */ \ "ld 3,88(11)\n\t" \ "std 3,128(1)\n\t" \ /* arg10 */ \ "ld 3,80(11)\n\t" \ "std 3,120(1)\n\t" \ /* arg9 */ \ "ld 3,72(11)\n\t" \ "std 3,112(1)\n\t" \ /* args1-8 */ \ "ld 3, 8(11)\n\t" /* arg1->r3 */ \ "ld 4, 16(11)\n\t" /* arg2->r4 */ \ "ld 5, 24(11)\n\t" /* arg3->r5 */ \ "ld 6, 32(11)\n\t" /* arg4->r6 */ \ "ld 7, 40(11)\n\t" /* arg5->r7 */ \ "ld 8, 48(11)\n\t" /* arg6->r8 */ \ "ld 9, 56(11)\n\t" /* arg7->r9 */ \ "ld 10, 64(11)\n\t" /* arg8->r10 */ \ "ld 11, 0(11)\n\t" /* target->r11 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ "mr 11,%1\n\t" \ "mr %0,3\n\t" \ "ld 2,-16(11)\n\t" /* restore tocptr */ \ VALGRIND_RESTORE_STACK \ : /*out*/ "=r" (_res) \ : /*in*/ "r" (&_argvec[2]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #endif /* PLAT_ppc64be_linux */ /* ------------------------- ppc64le-linux ----------------------- */ #if defined(PLAT_ppc64le_linux) /* ARGREGS: r3 r4 r5 r6 r7 r8 r9 r10 (the rest on stack somewhere) */ /* These regs are trashed by the hidden call. */ #define __CALLER_SAVED_REGS \ "lr", "ctr", "xer", \ "cr0", "cr1", "cr2", "cr3", "cr4", "cr5", "cr6", "cr7", \ "r0", "r3", "r4", "r5", "r6", "r7", "r8", "r9", "r10", \ "r11", "r12", "r13" /* Macros to save and align the stack before making a function call and restore it afterwards as gcc may not keep the stack pointer aligned if it doesn't realise calls are being made to other functions. */ #define VALGRIND_ALIGN_STACK \ "mr 28,1\n\t" \ "rldicr 1,1,0,59\n\t" #define VALGRIND_RESTORE_STACK \ "mr 1,28\n\t" /* These CALL_FN_ macros assume that on ppc64-linux, sizeof(unsigned long) == 8. */ #define CALL_FN_W_v(lval, orig) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[3+0]; \ volatile unsigned long _res; \ /* _argvec[0] holds current r2 across the call */ \ _argvec[1] = (unsigned long)_orig.r2; \ _argvec[2] = (unsigned long)_orig.nraddr; \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "mr 12,%1\n\t" \ "std 2,-16(12)\n\t" /* save tocptr */ \ "ld 2,-8(12)\n\t" /* use nraddr's tocptr */ \ "ld 12, 0(12)\n\t" /* target->r12 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R12 \ "mr 12,%1\n\t" \ "mr %0,3\n\t" \ "ld 2,-16(12)\n\t" /* restore tocptr */ \ VALGRIND_RESTORE_STACK \ : /*out*/ "=r" (_res) \ : /*in*/ "r" (&_argvec[2]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_W(lval, orig, arg1) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[3+1]; \ volatile unsigned long _res; \ /* _argvec[0] holds current r2 across the call */ \ _argvec[1] = (unsigned long)_orig.r2; \ _argvec[2] = (unsigned long)_orig.nraddr; \ _argvec[2+1] = (unsigned long)arg1; \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "mr 12,%1\n\t" \ "std 2,-16(12)\n\t" /* save tocptr */ \ "ld 2,-8(12)\n\t" /* use nraddr's tocptr */ \ "ld 3, 8(12)\n\t" /* arg1->r3 */ \ "ld 12, 0(12)\n\t" /* target->r12 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R12 \ "mr 12,%1\n\t" \ "mr %0,3\n\t" \ "ld 2,-16(12)\n\t" /* restore tocptr */ \ VALGRIND_RESTORE_STACK \ : /*out*/ "=r" (_res) \ : /*in*/ "r" (&_argvec[2]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_WW(lval, orig, arg1,arg2) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[3+2]; \ volatile unsigned long _res; \ /* _argvec[0] holds current r2 across the call */ \ _argvec[1] = (unsigned long)_orig.r2; \ _argvec[2] = (unsigned long)_orig.nraddr; \ _argvec[2+1] = (unsigned long)arg1; \ _argvec[2+2] = (unsigned long)arg2; \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "mr 12,%1\n\t" \ "std 2,-16(12)\n\t" /* save tocptr */ \ "ld 2,-8(12)\n\t" /* use nraddr's tocptr */ \ "ld 3, 8(12)\n\t" /* arg1->r3 */ \ "ld 4, 16(12)\n\t" /* arg2->r4 */ \ "ld 12, 0(12)\n\t" /* target->r12 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R12 \ "mr 12,%1\n\t" \ "mr %0,3\n\t" \ "ld 2,-16(12)\n\t" /* restore tocptr */ \ VALGRIND_RESTORE_STACK \ : /*out*/ "=r" (_res) \ : /*in*/ "r" (&_argvec[2]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_WWW(lval, orig, arg1,arg2,arg3) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[3+3]; \ volatile unsigned long _res; \ /* _argvec[0] holds current r2 across the call */ \ _argvec[1] = (unsigned long)_orig.r2; \ _argvec[2] = (unsigned long)_orig.nraddr; \ _argvec[2+1] = (unsigned long)arg1; \ _argvec[2+2] = (unsigned long)arg2; \ _argvec[2+3] = (unsigned long)arg3; \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "mr 12,%1\n\t" \ "std 2,-16(12)\n\t" /* save tocptr */ \ "ld 2,-8(12)\n\t" /* use nraddr's tocptr */ \ "ld 3, 8(12)\n\t" /* arg1->r3 */ \ "ld 4, 16(12)\n\t" /* arg2->r4 */ \ "ld 5, 24(12)\n\t" /* arg3->r5 */ \ "ld 12, 0(12)\n\t" /* target->r12 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R12 \ "mr 12,%1\n\t" \ "mr %0,3\n\t" \ "ld 2,-16(12)\n\t" /* restore tocptr */ \ VALGRIND_RESTORE_STACK \ : /*out*/ "=r" (_res) \ : /*in*/ "r" (&_argvec[2]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_WWWW(lval, orig, arg1,arg2,arg3,arg4) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[3+4]; \ volatile unsigned long _res; \ /* _argvec[0] holds current r2 across the call */ \ _argvec[1] = (unsigned long)_orig.r2; \ _argvec[2] = (unsigned long)_orig.nraddr; \ _argvec[2+1] = (unsigned long)arg1; \ _argvec[2+2] = (unsigned long)arg2; \ _argvec[2+3] = (unsigned long)arg3; \ _argvec[2+4] = (unsigned long)arg4; \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "mr 12,%1\n\t" \ "std 2,-16(12)\n\t" /* save tocptr */ \ "ld 2,-8(12)\n\t" /* use nraddr's tocptr */ \ "ld 3, 8(12)\n\t" /* arg1->r3 */ \ "ld 4, 16(12)\n\t" /* arg2->r4 */ \ "ld 5, 24(12)\n\t" /* arg3->r5 */ \ "ld 6, 32(12)\n\t" /* arg4->r6 */ \ "ld 12, 0(12)\n\t" /* target->r12 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R12 \ "mr 12,%1\n\t" \ "mr %0,3\n\t" \ "ld 2,-16(12)\n\t" /* restore tocptr */ \ VALGRIND_RESTORE_STACK \ : /*out*/ "=r" (_res) \ : /*in*/ "r" (&_argvec[2]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_5W(lval, orig, arg1,arg2,arg3,arg4,arg5) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[3+5]; \ volatile unsigned long _res; \ /* _argvec[0] holds current r2 across the call */ \ _argvec[1] = (unsigned long)_orig.r2; \ _argvec[2] = (unsigned long)_orig.nraddr; \ _argvec[2+1] = (unsigned long)arg1; \ _argvec[2+2] = (unsigned long)arg2; \ _argvec[2+3] = (unsigned long)arg3; \ _argvec[2+4] = (unsigned long)arg4; \ _argvec[2+5] = (unsigned long)arg5; \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "mr 12,%1\n\t" \ "std 2,-16(12)\n\t" /* save tocptr */ \ "ld 2,-8(12)\n\t" /* use nraddr's tocptr */ \ "ld 3, 8(12)\n\t" /* arg1->r3 */ \ "ld 4, 16(12)\n\t" /* arg2->r4 */ \ "ld 5, 24(12)\n\t" /* arg3->r5 */ \ "ld 6, 32(12)\n\t" /* arg4->r6 */ \ "ld 7, 40(12)\n\t" /* arg5->r7 */ \ "ld 12, 0(12)\n\t" /* target->r12 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R12 \ "mr 12,%1\n\t" \ "mr %0,3\n\t" \ "ld 2,-16(12)\n\t" /* restore tocptr */ \ VALGRIND_RESTORE_STACK \ : /*out*/ "=r" (_res) \ : /*in*/ "r" (&_argvec[2]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_6W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[3+6]; \ volatile unsigned long _res; \ /* _argvec[0] holds current r2 across the call */ \ _argvec[1] = (unsigned long)_orig.r2; \ _argvec[2] = (unsigned long)_orig.nraddr; \ _argvec[2+1] = (unsigned long)arg1; \ _argvec[2+2] = (unsigned long)arg2; \ _argvec[2+3] = (unsigned long)arg3; \ _argvec[2+4] = (unsigned long)arg4; \ _argvec[2+5] = (unsigned long)arg5; \ _argvec[2+6] = (unsigned long)arg6; \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "mr 12,%1\n\t" \ "std 2,-16(12)\n\t" /* save tocptr */ \ "ld 2,-8(12)\n\t" /* use nraddr's tocptr */ \ "ld 3, 8(12)\n\t" /* arg1->r3 */ \ "ld 4, 16(12)\n\t" /* arg2->r4 */ \ "ld 5, 24(12)\n\t" /* arg3->r5 */ \ "ld 6, 32(12)\n\t" /* arg4->r6 */ \ "ld 7, 40(12)\n\t" /* arg5->r7 */ \ "ld 8, 48(12)\n\t" /* arg6->r8 */ \ "ld 12, 0(12)\n\t" /* target->r12 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R12 \ "mr 12,%1\n\t" \ "mr %0,3\n\t" \ "ld 2,-16(12)\n\t" /* restore tocptr */ \ VALGRIND_RESTORE_STACK \ : /*out*/ "=r" (_res) \ : /*in*/ "r" (&_argvec[2]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_7W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ arg7) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[3+7]; \ volatile unsigned long _res; \ /* _argvec[0] holds current r2 across the call */ \ _argvec[1] = (unsigned long)_orig.r2; \ _argvec[2] = (unsigned long)_orig.nraddr; \ _argvec[2+1] = (unsigned long)arg1; \ _argvec[2+2] = (unsigned long)arg2; \ _argvec[2+3] = (unsigned long)arg3; \ _argvec[2+4] = (unsigned long)arg4; \ _argvec[2+5] = (unsigned long)arg5; \ _argvec[2+6] = (unsigned long)arg6; \ _argvec[2+7] = (unsigned long)arg7; \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "mr 12,%1\n\t" \ "std 2,-16(12)\n\t" /* save tocptr */ \ "ld 2,-8(12)\n\t" /* use nraddr's tocptr */ \ "ld 3, 8(12)\n\t" /* arg1->r3 */ \ "ld 4, 16(12)\n\t" /* arg2->r4 */ \ "ld 5, 24(12)\n\t" /* arg3->r5 */ \ "ld 6, 32(12)\n\t" /* arg4->r6 */ \ "ld 7, 40(12)\n\t" /* arg5->r7 */ \ "ld 8, 48(12)\n\t" /* arg6->r8 */ \ "ld 9, 56(12)\n\t" /* arg7->r9 */ \ "ld 12, 0(12)\n\t" /* target->r12 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R12 \ "mr 12,%1\n\t" \ "mr %0,3\n\t" \ "ld 2,-16(12)\n\t" /* restore tocptr */ \ VALGRIND_RESTORE_STACK \ : /*out*/ "=r" (_res) \ : /*in*/ "r" (&_argvec[2]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_8W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ arg7,arg8) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[3+8]; \ volatile unsigned long _res; \ /* _argvec[0] holds current r2 across the call */ \ _argvec[1] = (unsigned long)_orig.r2; \ _argvec[2] = (unsigned long)_orig.nraddr; \ _argvec[2+1] = (unsigned long)arg1; \ _argvec[2+2] = (unsigned long)arg2; \ _argvec[2+3] = (unsigned long)arg3; \ _argvec[2+4] = (unsigned long)arg4; \ _argvec[2+5] = (unsigned long)arg5; \ _argvec[2+6] = (unsigned long)arg6; \ _argvec[2+7] = (unsigned long)arg7; \ _argvec[2+8] = (unsigned long)arg8; \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "mr 12,%1\n\t" \ "std 2,-16(12)\n\t" /* save tocptr */ \ "ld 2,-8(12)\n\t" /* use nraddr's tocptr */ \ "ld 3, 8(12)\n\t" /* arg1->r3 */ \ "ld 4, 16(12)\n\t" /* arg2->r4 */ \ "ld 5, 24(12)\n\t" /* arg3->r5 */ \ "ld 6, 32(12)\n\t" /* arg4->r6 */ \ "ld 7, 40(12)\n\t" /* arg5->r7 */ \ "ld 8, 48(12)\n\t" /* arg6->r8 */ \ "ld 9, 56(12)\n\t" /* arg7->r9 */ \ "ld 10, 64(12)\n\t" /* arg8->r10 */ \ "ld 12, 0(12)\n\t" /* target->r12 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R12 \ "mr 12,%1\n\t" \ "mr %0,3\n\t" \ "ld 2,-16(12)\n\t" /* restore tocptr */ \ VALGRIND_RESTORE_STACK \ : /*out*/ "=r" (_res) \ : /*in*/ "r" (&_argvec[2]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_9W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ arg7,arg8,arg9) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[3+9]; \ volatile unsigned long _res; \ /* _argvec[0] holds current r2 across the call */ \ _argvec[1] = (unsigned long)_orig.r2; \ _argvec[2] = (unsigned long)_orig.nraddr; \ _argvec[2+1] = (unsigned long)arg1; \ _argvec[2+2] = (unsigned long)arg2; \ _argvec[2+3] = (unsigned long)arg3; \ _argvec[2+4] = (unsigned long)arg4; \ _argvec[2+5] = (unsigned long)arg5; \ _argvec[2+6] = (unsigned long)arg6; \ _argvec[2+7] = (unsigned long)arg7; \ _argvec[2+8] = (unsigned long)arg8; \ _argvec[2+9] = (unsigned long)arg9; \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "mr 12,%1\n\t" \ "std 2,-16(12)\n\t" /* save tocptr */ \ "ld 2,-8(12)\n\t" /* use nraddr's tocptr */ \ "addi 1,1,-128\n\t" /* expand stack frame */ \ /* arg9 */ \ "ld 3,72(12)\n\t" \ "std 3,96(1)\n\t" \ /* args1-8 */ \ "ld 3, 8(12)\n\t" /* arg1->r3 */ \ "ld 4, 16(12)\n\t" /* arg2->r4 */ \ "ld 5, 24(12)\n\t" /* arg3->r5 */ \ "ld 6, 32(12)\n\t" /* arg4->r6 */ \ "ld 7, 40(12)\n\t" /* arg5->r7 */ \ "ld 8, 48(12)\n\t" /* arg6->r8 */ \ "ld 9, 56(12)\n\t" /* arg7->r9 */ \ "ld 10, 64(12)\n\t" /* arg8->r10 */ \ "ld 12, 0(12)\n\t" /* target->r12 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R12 \ "mr 12,%1\n\t" \ "mr %0,3\n\t" \ "ld 2,-16(12)\n\t" /* restore tocptr */ \ VALGRIND_RESTORE_STACK \ : /*out*/ "=r" (_res) \ : /*in*/ "r" (&_argvec[2]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_10W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ arg7,arg8,arg9,arg10) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[3+10]; \ volatile unsigned long _res; \ /* _argvec[0] holds current r2 across the call */ \ _argvec[1] = (unsigned long)_orig.r2; \ _argvec[2] = (unsigned long)_orig.nraddr; \ _argvec[2+1] = (unsigned long)arg1; \ _argvec[2+2] = (unsigned long)arg2; \ _argvec[2+3] = (unsigned long)arg3; \ _argvec[2+4] = (unsigned long)arg4; \ _argvec[2+5] = (unsigned long)arg5; \ _argvec[2+6] = (unsigned long)arg6; \ _argvec[2+7] = (unsigned long)arg7; \ _argvec[2+8] = (unsigned long)arg8; \ _argvec[2+9] = (unsigned long)arg9; \ _argvec[2+10] = (unsigned long)arg10; \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "mr 12,%1\n\t" \ "std 2,-16(12)\n\t" /* save tocptr */ \ "ld 2,-8(12)\n\t" /* use nraddr's tocptr */ \ "addi 1,1,-128\n\t" /* expand stack frame */ \ /* arg10 */ \ "ld 3,80(12)\n\t" \ "std 3,104(1)\n\t" \ /* arg9 */ \ "ld 3,72(12)\n\t" \ "std 3,96(1)\n\t" \ /* args1-8 */ \ "ld 3, 8(12)\n\t" /* arg1->r3 */ \ "ld 4, 16(12)\n\t" /* arg2->r4 */ \ "ld 5, 24(12)\n\t" /* arg3->r5 */ \ "ld 6, 32(12)\n\t" /* arg4->r6 */ \ "ld 7, 40(12)\n\t" /* arg5->r7 */ \ "ld 8, 48(12)\n\t" /* arg6->r8 */ \ "ld 9, 56(12)\n\t" /* arg7->r9 */ \ "ld 10, 64(12)\n\t" /* arg8->r10 */ \ "ld 12, 0(12)\n\t" /* target->r12 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R12 \ "mr 12,%1\n\t" \ "mr %0,3\n\t" \ "ld 2,-16(12)\n\t" /* restore tocptr */ \ VALGRIND_RESTORE_STACK \ : /*out*/ "=r" (_res) \ : /*in*/ "r" (&_argvec[2]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_11W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ arg7,arg8,arg9,arg10,arg11) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[3+11]; \ volatile unsigned long _res; \ /* _argvec[0] holds current r2 across the call */ \ _argvec[1] = (unsigned long)_orig.r2; \ _argvec[2] = (unsigned long)_orig.nraddr; \ _argvec[2+1] = (unsigned long)arg1; \ _argvec[2+2] = (unsigned long)arg2; \ _argvec[2+3] = (unsigned long)arg3; \ _argvec[2+4] = (unsigned long)arg4; \ _argvec[2+5] = (unsigned long)arg5; \ _argvec[2+6] = (unsigned long)arg6; \ _argvec[2+7] = (unsigned long)arg7; \ _argvec[2+8] = (unsigned long)arg8; \ _argvec[2+9] = (unsigned long)arg9; \ _argvec[2+10] = (unsigned long)arg10; \ _argvec[2+11] = (unsigned long)arg11; \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "mr 12,%1\n\t" \ "std 2,-16(12)\n\t" /* save tocptr */ \ "ld 2,-8(12)\n\t" /* use nraddr's tocptr */ \ "addi 1,1,-144\n\t" /* expand stack frame */ \ /* arg11 */ \ "ld 3,88(12)\n\t" \ "std 3,112(1)\n\t" \ /* arg10 */ \ "ld 3,80(12)\n\t" \ "std 3,104(1)\n\t" \ /* arg9 */ \ "ld 3,72(12)\n\t" \ "std 3,96(1)\n\t" \ /* args1-8 */ \ "ld 3, 8(12)\n\t" /* arg1->r3 */ \ "ld 4, 16(12)\n\t" /* arg2->r4 */ \ "ld 5, 24(12)\n\t" /* arg3->r5 */ \ "ld 6, 32(12)\n\t" /* arg4->r6 */ \ "ld 7, 40(12)\n\t" /* arg5->r7 */ \ "ld 8, 48(12)\n\t" /* arg6->r8 */ \ "ld 9, 56(12)\n\t" /* arg7->r9 */ \ "ld 10, 64(12)\n\t" /* arg8->r10 */ \ "ld 12, 0(12)\n\t" /* target->r12 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R12 \ "mr 12,%1\n\t" \ "mr %0,3\n\t" \ "ld 2,-16(12)\n\t" /* restore tocptr */ \ VALGRIND_RESTORE_STACK \ : /*out*/ "=r" (_res) \ : /*in*/ "r" (&_argvec[2]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_12W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ arg7,arg8,arg9,arg10,arg11,arg12) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[3+12]; \ volatile unsigned long _res; \ /* _argvec[0] holds current r2 across the call */ \ _argvec[1] = (unsigned long)_orig.r2; \ _argvec[2] = (unsigned long)_orig.nraddr; \ _argvec[2+1] = (unsigned long)arg1; \ _argvec[2+2] = (unsigned long)arg2; \ _argvec[2+3] = (unsigned long)arg3; \ _argvec[2+4] = (unsigned long)arg4; \ _argvec[2+5] = (unsigned long)arg5; \ _argvec[2+6] = (unsigned long)arg6; \ _argvec[2+7] = (unsigned long)arg7; \ _argvec[2+8] = (unsigned long)arg8; \ _argvec[2+9] = (unsigned long)arg9; \ _argvec[2+10] = (unsigned long)arg10; \ _argvec[2+11] = (unsigned long)arg11; \ _argvec[2+12] = (unsigned long)arg12; \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "mr 12,%1\n\t" \ "std 2,-16(12)\n\t" /* save tocptr */ \ "ld 2,-8(12)\n\t" /* use nraddr's tocptr */ \ "addi 1,1,-144\n\t" /* expand stack frame */ \ /* arg12 */ \ "ld 3,96(12)\n\t" \ "std 3,120(1)\n\t" \ /* arg11 */ \ "ld 3,88(12)\n\t" \ "std 3,112(1)\n\t" \ /* arg10 */ \ "ld 3,80(12)\n\t" \ "std 3,104(1)\n\t" \ /* arg9 */ \ "ld 3,72(12)\n\t" \ "std 3,96(1)\n\t" \ /* args1-8 */ \ "ld 3, 8(12)\n\t" /* arg1->r3 */ \ "ld 4, 16(12)\n\t" /* arg2->r4 */ \ "ld 5, 24(12)\n\t" /* arg3->r5 */ \ "ld 6, 32(12)\n\t" /* arg4->r6 */ \ "ld 7, 40(12)\n\t" /* arg5->r7 */ \ "ld 8, 48(12)\n\t" /* arg6->r8 */ \ "ld 9, 56(12)\n\t" /* arg7->r9 */ \ "ld 10, 64(12)\n\t" /* arg8->r10 */ \ "ld 12, 0(12)\n\t" /* target->r12 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R12 \ "mr 12,%1\n\t" \ "mr %0,3\n\t" \ "ld 2,-16(12)\n\t" /* restore tocptr */ \ VALGRIND_RESTORE_STACK \ : /*out*/ "=r" (_res) \ : /*in*/ "r" (&_argvec[2]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #endif /* PLAT_ppc64le_linux */ /* ------------------------- arm-linux ------------------------- */ #if defined(PLAT_arm_linux) /* These regs are trashed by the hidden call. */ #define __CALLER_SAVED_REGS "r0", "r1", "r2", "r3","r4", "r12", "r14" /* Macros to save and align the stack before making a function call and restore it afterwards as gcc may not keep the stack pointer aligned if it doesn't realise calls are being made to other functions. */ /* This is a bit tricky. We store the original stack pointer in r10 as it is callee-saves. gcc doesn't allow the use of r11 for some reason. Also, we can't directly "bic" the stack pointer in thumb mode since r13 isn't an allowed register number in that context. So use r4 as a temporary, since that is about to get trashed anyway, just after each use of this macro. Side effect is we need to be very careful about any future changes, since VALGRIND_ALIGN_STACK simply assumes r4 is usable. */ #define VALGRIND_ALIGN_STACK \ "mov r10, sp\n\t" \ "mov r4, sp\n\t" \ "bic r4, r4, #7\n\t" \ "mov sp, r4\n\t" #define VALGRIND_RESTORE_STACK \ "mov sp, r10\n\t" /* These CALL_FN_ macros assume that on arm-linux, sizeof(unsigned long) == 4. */ #define CALL_FN_W_v(lval, orig) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[1]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "ldr r4, [%1] \n\t" /* target->r4 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R4 \ VALGRIND_RESTORE_STACK \ "mov %0, r0\n" \ : /*out*/ "=r" (_res) \ : /*in*/ "0" (&_argvec[0]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r10" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_W(lval, orig, arg1) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[2]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)(arg1); \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "ldr r0, [%1, #4] \n\t" \ "ldr r4, [%1] \n\t" /* target->r4 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R4 \ VALGRIND_RESTORE_STACK \ "mov %0, r0\n" \ : /*out*/ "=r" (_res) \ : /*in*/ "0" (&_argvec[0]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r10" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_WW(lval, orig, arg1,arg2) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[3]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)(arg1); \ _argvec[2] = (unsigned long)(arg2); \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "ldr r0, [%1, #4] \n\t" \ "ldr r1, [%1, #8] \n\t" \ "ldr r4, [%1] \n\t" /* target->r4 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R4 \ VALGRIND_RESTORE_STACK \ "mov %0, r0\n" \ : /*out*/ "=r" (_res) \ : /*in*/ "0" (&_argvec[0]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r10" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_WWW(lval, orig, arg1,arg2,arg3) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[4]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)(arg1); \ _argvec[2] = (unsigned long)(arg2); \ _argvec[3] = (unsigned long)(arg3); \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "ldr r0, [%1, #4] \n\t" \ "ldr r1, [%1, #8] \n\t" \ "ldr r2, [%1, #12] \n\t" \ "ldr r4, [%1] \n\t" /* target->r4 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R4 \ VALGRIND_RESTORE_STACK \ "mov %0, r0\n" \ : /*out*/ "=r" (_res) \ : /*in*/ "0" (&_argvec[0]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r10" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_WWWW(lval, orig, arg1,arg2,arg3,arg4) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[5]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)(arg1); \ _argvec[2] = (unsigned long)(arg2); \ _argvec[3] = (unsigned long)(arg3); \ _argvec[4] = (unsigned long)(arg4); \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "ldr r0, [%1, #4] \n\t" \ "ldr r1, [%1, #8] \n\t" \ "ldr r2, [%1, #12] \n\t" \ "ldr r3, [%1, #16] \n\t" \ "ldr r4, [%1] \n\t" /* target->r4 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R4 \ VALGRIND_RESTORE_STACK \ "mov %0, r0" \ : /*out*/ "=r" (_res) \ : /*in*/ "0" (&_argvec[0]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r10" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_5W(lval, orig, arg1,arg2,arg3,arg4,arg5) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[6]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)(arg1); \ _argvec[2] = (unsigned long)(arg2); \ _argvec[3] = (unsigned long)(arg3); \ _argvec[4] = (unsigned long)(arg4); \ _argvec[5] = (unsigned long)(arg5); \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "sub sp, sp, #4 \n\t" \ "ldr r0, [%1, #20] \n\t" \ "push {r0} \n\t" \ "ldr r0, [%1, #4] \n\t" \ "ldr r1, [%1, #8] \n\t" \ "ldr r2, [%1, #12] \n\t" \ "ldr r3, [%1, #16] \n\t" \ "ldr r4, [%1] \n\t" /* target->r4 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R4 \ VALGRIND_RESTORE_STACK \ "mov %0, r0" \ : /*out*/ "=r" (_res) \ : /*in*/ "0" (&_argvec[0]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r10" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_6W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[7]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)(arg1); \ _argvec[2] = (unsigned long)(arg2); \ _argvec[3] = (unsigned long)(arg3); \ _argvec[4] = (unsigned long)(arg4); \ _argvec[5] = (unsigned long)(arg5); \ _argvec[6] = (unsigned long)(arg6); \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "ldr r0, [%1, #20] \n\t" \ "ldr r1, [%1, #24] \n\t" \ "push {r0, r1} \n\t" \ "ldr r0, [%1, #4] \n\t" \ "ldr r1, [%1, #8] \n\t" \ "ldr r2, [%1, #12] \n\t" \ "ldr r3, [%1, #16] \n\t" \ "ldr r4, [%1] \n\t" /* target->r4 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R4 \ VALGRIND_RESTORE_STACK \ "mov %0, r0" \ : /*out*/ "=r" (_res) \ : /*in*/ "0" (&_argvec[0]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r10" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_7W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ arg7) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[8]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)(arg1); \ _argvec[2] = (unsigned long)(arg2); \ _argvec[3] = (unsigned long)(arg3); \ _argvec[4] = (unsigned long)(arg4); \ _argvec[5] = (unsigned long)(arg5); \ _argvec[6] = (unsigned long)(arg6); \ _argvec[7] = (unsigned long)(arg7); \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "sub sp, sp, #4 \n\t" \ "ldr r0, [%1, #20] \n\t" \ "ldr r1, [%1, #24] \n\t" \ "ldr r2, [%1, #28] \n\t" \ "push {r0, r1, r2} \n\t" \ "ldr r0, [%1, #4] \n\t" \ "ldr r1, [%1, #8] \n\t" \ "ldr r2, [%1, #12] \n\t" \ "ldr r3, [%1, #16] \n\t" \ "ldr r4, [%1] \n\t" /* target->r4 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R4 \ VALGRIND_RESTORE_STACK \ "mov %0, r0" \ : /*out*/ "=r" (_res) \ : /*in*/ "0" (&_argvec[0]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r10" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_8W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ arg7,arg8) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[9]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)(arg1); \ _argvec[2] = (unsigned long)(arg2); \ _argvec[3] = (unsigned long)(arg3); \ _argvec[4] = (unsigned long)(arg4); \ _argvec[5] = (unsigned long)(arg5); \ _argvec[6] = (unsigned long)(arg6); \ _argvec[7] = (unsigned long)(arg7); \ _argvec[8] = (unsigned long)(arg8); \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "ldr r0, [%1, #20] \n\t" \ "ldr r1, [%1, #24] \n\t" \ "ldr r2, [%1, #28] \n\t" \ "ldr r3, [%1, #32] \n\t" \ "push {r0, r1, r2, r3} \n\t" \ "ldr r0, [%1, #4] \n\t" \ "ldr r1, [%1, #8] \n\t" \ "ldr r2, [%1, #12] \n\t" \ "ldr r3, [%1, #16] \n\t" \ "ldr r4, [%1] \n\t" /* target->r4 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R4 \ VALGRIND_RESTORE_STACK \ "mov %0, r0" \ : /*out*/ "=r" (_res) \ : /*in*/ "0" (&_argvec[0]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r10" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_9W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ arg7,arg8,arg9) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[10]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)(arg1); \ _argvec[2] = (unsigned long)(arg2); \ _argvec[3] = (unsigned long)(arg3); \ _argvec[4] = (unsigned long)(arg4); \ _argvec[5] = (unsigned long)(arg5); \ _argvec[6] = (unsigned long)(arg6); \ _argvec[7] = (unsigned long)(arg7); \ _argvec[8] = (unsigned long)(arg8); \ _argvec[9] = (unsigned long)(arg9); \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "sub sp, sp, #4 \n\t" \ "ldr r0, [%1, #20] \n\t" \ "ldr r1, [%1, #24] \n\t" \ "ldr r2, [%1, #28] \n\t" \ "ldr r3, [%1, #32] \n\t" \ "ldr r4, [%1, #36] \n\t" \ "push {r0, r1, r2, r3, r4} \n\t" \ "ldr r0, [%1, #4] \n\t" \ "ldr r1, [%1, #8] \n\t" \ "ldr r2, [%1, #12] \n\t" \ "ldr r3, [%1, #16] \n\t" \ "ldr r4, [%1] \n\t" /* target->r4 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R4 \ VALGRIND_RESTORE_STACK \ "mov %0, r0" \ : /*out*/ "=r" (_res) \ : /*in*/ "0" (&_argvec[0]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r10" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_10W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ arg7,arg8,arg9,arg10) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[11]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)(arg1); \ _argvec[2] = (unsigned long)(arg2); \ _argvec[3] = (unsigned long)(arg3); \ _argvec[4] = (unsigned long)(arg4); \ _argvec[5] = (unsigned long)(arg5); \ _argvec[6] = (unsigned long)(arg6); \ _argvec[7] = (unsigned long)(arg7); \ _argvec[8] = (unsigned long)(arg8); \ _argvec[9] = (unsigned long)(arg9); \ _argvec[10] = (unsigned long)(arg10); \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "ldr r0, [%1, #40] \n\t" \ "push {r0} \n\t" \ "ldr r0, [%1, #20] \n\t" \ "ldr r1, [%1, #24] \n\t" \ "ldr r2, [%1, #28] \n\t" \ "ldr r3, [%1, #32] \n\t" \ "ldr r4, [%1, #36] \n\t" \ "push {r0, r1, r2, r3, r4} \n\t" \ "ldr r0, [%1, #4] \n\t" \ "ldr r1, [%1, #8] \n\t" \ "ldr r2, [%1, #12] \n\t" \ "ldr r3, [%1, #16] \n\t" \ "ldr r4, [%1] \n\t" /* target->r4 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R4 \ VALGRIND_RESTORE_STACK \ "mov %0, r0" \ : /*out*/ "=r" (_res) \ : /*in*/ "0" (&_argvec[0]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r10" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_11W(lval, orig, arg1,arg2,arg3,arg4,arg5, \ arg6,arg7,arg8,arg9,arg10, \ arg11) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[12]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)(arg1); \ _argvec[2] = (unsigned long)(arg2); \ _argvec[3] = (unsigned long)(arg3); \ _argvec[4] = (unsigned long)(arg4); \ _argvec[5] = (unsigned long)(arg5); \ _argvec[6] = (unsigned long)(arg6); \ _argvec[7] = (unsigned long)(arg7); \ _argvec[8] = (unsigned long)(arg8); \ _argvec[9] = (unsigned long)(arg9); \ _argvec[10] = (unsigned long)(arg10); \ _argvec[11] = (unsigned long)(arg11); \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "sub sp, sp, #4 \n\t" \ "ldr r0, [%1, #40] \n\t" \ "ldr r1, [%1, #44] \n\t" \ "push {r0, r1} \n\t" \ "ldr r0, [%1, #20] \n\t" \ "ldr r1, [%1, #24] \n\t" \ "ldr r2, [%1, #28] \n\t" \ "ldr r3, [%1, #32] \n\t" \ "ldr r4, [%1, #36] \n\t" \ "push {r0, r1, r2, r3, r4} \n\t" \ "ldr r0, [%1, #4] \n\t" \ "ldr r1, [%1, #8] \n\t" \ "ldr r2, [%1, #12] \n\t" \ "ldr r3, [%1, #16] \n\t" \ "ldr r4, [%1] \n\t" /* target->r4 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R4 \ VALGRIND_RESTORE_STACK \ "mov %0, r0" \ : /*out*/ "=r" (_res) \ : /*in*/ "0" (&_argvec[0]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r10" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_12W(lval, orig, arg1,arg2,arg3,arg4,arg5, \ arg6,arg7,arg8,arg9,arg10, \ arg11,arg12) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[13]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)(arg1); \ _argvec[2] = (unsigned long)(arg2); \ _argvec[3] = (unsigned long)(arg3); \ _argvec[4] = (unsigned long)(arg4); \ _argvec[5] = (unsigned long)(arg5); \ _argvec[6] = (unsigned long)(arg6); \ _argvec[7] = (unsigned long)(arg7); \ _argvec[8] = (unsigned long)(arg8); \ _argvec[9] = (unsigned long)(arg9); \ _argvec[10] = (unsigned long)(arg10); \ _argvec[11] = (unsigned long)(arg11); \ _argvec[12] = (unsigned long)(arg12); \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "ldr r0, [%1, #40] \n\t" \ "ldr r1, [%1, #44] \n\t" \ "ldr r2, [%1, #48] \n\t" \ "push {r0, r1, r2} \n\t" \ "ldr r0, [%1, #20] \n\t" \ "ldr r1, [%1, #24] \n\t" \ "ldr r2, [%1, #28] \n\t" \ "ldr r3, [%1, #32] \n\t" \ "ldr r4, [%1, #36] \n\t" \ "push {r0, r1, r2, r3, r4} \n\t" \ "ldr r0, [%1, #4] \n\t" \ "ldr r1, [%1, #8] \n\t" \ "ldr r2, [%1, #12] \n\t" \ "ldr r3, [%1, #16] \n\t" \ "ldr r4, [%1] \n\t" /* target->r4 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R4 \ VALGRIND_RESTORE_STACK \ "mov %0, r0" \ : /*out*/ "=r" (_res) \ : /*in*/ "0" (&_argvec[0]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r10" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #endif /* PLAT_arm_linux */ /* ------------------------ arm64-linux ------------------------ */ #if defined(PLAT_arm64_linux) /* These regs are trashed by the hidden call. */ #define __CALLER_SAVED_REGS \ "x0", "x1", "x2", "x3","x4", "x5", "x6", "x7", "x8", "x9", \ "x10", "x11", "x12", "x13", "x14", "x15", "x16", "x17", \ "x18", "x19", "x20", "x30", \ "v0", "v1", "v2", "v3", "v4", "v5", "v6", "v7", "v8", "v9", \ "v10", "v11", "v12", "v13", "v14", "v15", "v16", "v17", \ "v18", "v19", "v20", "v21", "v22", "v23", "v24", "v25", \ "v26", "v27", "v28", "v29", "v30", "v31" /* x21 is callee-saved, so we can use it to save and restore SP around the hidden call. */ #define VALGRIND_ALIGN_STACK \ "mov x21, sp\n\t" \ "bic sp, x21, #15\n\t" #define VALGRIND_RESTORE_STACK \ "mov sp, x21\n\t" /* These CALL_FN_ macros assume that on arm64-linux, sizeof(unsigned long) == 8. */ #define CALL_FN_W_v(lval, orig) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[1]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "ldr x8, [%1] \n\t" /* target->x8 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_X8 \ VALGRIND_RESTORE_STACK \ "mov %0, x0\n" \ : /*out*/ "=r" (_res) \ : /*in*/ "0" (&_argvec[0]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "x21" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_W(lval, orig, arg1) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[2]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)(arg1); \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "ldr x0, [%1, #8] \n\t" \ "ldr x8, [%1] \n\t" /* target->x8 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_X8 \ VALGRIND_RESTORE_STACK \ "mov %0, x0\n" \ : /*out*/ "=r" (_res) \ : /*in*/ "0" (&_argvec[0]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "x21" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_WW(lval, orig, arg1,arg2) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[3]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)(arg1); \ _argvec[2] = (unsigned long)(arg2); \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "ldr x0, [%1, #8] \n\t" \ "ldr x1, [%1, #16] \n\t" \ "ldr x8, [%1] \n\t" /* target->x8 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_X8 \ VALGRIND_RESTORE_STACK \ "mov %0, x0\n" \ : /*out*/ "=r" (_res) \ : /*in*/ "0" (&_argvec[0]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "x21" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_WWW(lval, orig, arg1,arg2,arg3) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[4]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)(arg1); \ _argvec[2] = (unsigned long)(arg2); \ _argvec[3] = (unsigned long)(arg3); \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "ldr x0, [%1, #8] \n\t" \ "ldr x1, [%1, #16] \n\t" \ "ldr x2, [%1, #24] \n\t" \ "ldr x8, [%1] \n\t" /* target->x8 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_X8 \ VALGRIND_RESTORE_STACK \ "mov %0, x0\n" \ : /*out*/ "=r" (_res) \ : /*in*/ "0" (&_argvec[0]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "x21" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_WWWW(lval, orig, arg1,arg2,arg3,arg4) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[5]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)(arg1); \ _argvec[2] = (unsigned long)(arg2); \ _argvec[3] = (unsigned long)(arg3); \ _argvec[4] = (unsigned long)(arg4); \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "ldr x0, [%1, #8] \n\t" \ "ldr x1, [%1, #16] \n\t" \ "ldr x2, [%1, #24] \n\t" \ "ldr x3, [%1, #32] \n\t" \ "ldr x8, [%1] \n\t" /* target->x8 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_X8 \ VALGRIND_RESTORE_STACK \ "mov %0, x0" \ : /*out*/ "=r" (_res) \ : /*in*/ "0" (&_argvec[0]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "x21" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_5W(lval, orig, arg1,arg2,arg3,arg4,arg5) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[6]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)(arg1); \ _argvec[2] = (unsigned long)(arg2); \ _argvec[3] = (unsigned long)(arg3); \ _argvec[4] = (unsigned long)(arg4); \ _argvec[5] = (unsigned long)(arg5); \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "ldr x0, [%1, #8] \n\t" \ "ldr x1, [%1, #16] \n\t" \ "ldr x2, [%1, #24] \n\t" \ "ldr x3, [%1, #32] \n\t" \ "ldr x4, [%1, #40] \n\t" \ "ldr x8, [%1] \n\t" /* target->x8 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_X8 \ VALGRIND_RESTORE_STACK \ "mov %0, x0" \ : /*out*/ "=r" (_res) \ : /*in*/ "0" (&_argvec[0]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "x21" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_6W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[7]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)(arg1); \ _argvec[2] = (unsigned long)(arg2); \ _argvec[3] = (unsigned long)(arg3); \ _argvec[4] = (unsigned long)(arg4); \ _argvec[5] = (unsigned long)(arg5); \ _argvec[6] = (unsigned long)(arg6); \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "ldr x0, [%1, #8] \n\t" \ "ldr x1, [%1, #16] \n\t" \ "ldr x2, [%1, #24] \n\t" \ "ldr x3, [%1, #32] \n\t" \ "ldr x4, [%1, #40] \n\t" \ "ldr x5, [%1, #48] \n\t" \ "ldr x8, [%1] \n\t" /* target->x8 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_X8 \ VALGRIND_RESTORE_STACK \ "mov %0, x0" \ : /*out*/ "=r" (_res) \ : /*in*/ "0" (&_argvec[0]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "x21" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_7W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ arg7) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[8]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)(arg1); \ _argvec[2] = (unsigned long)(arg2); \ _argvec[3] = (unsigned long)(arg3); \ _argvec[4] = (unsigned long)(arg4); \ _argvec[5] = (unsigned long)(arg5); \ _argvec[6] = (unsigned long)(arg6); \ _argvec[7] = (unsigned long)(arg7); \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "ldr x0, [%1, #8] \n\t" \ "ldr x1, [%1, #16] \n\t" \ "ldr x2, [%1, #24] \n\t" \ "ldr x3, [%1, #32] \n\t" \ "ldr x4, [%1, #40] \n\t" \ "ldr x5, [%1, #48] \n\t" \ "ldr x6, [%1, #56] \n\t" \ "ldr x8, [%1] \n\t" /* target->x8 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_X8 \ VALGRIND_RESTORE_STACK \ "mov %0, x0" \ : /*out*/ "=r" (_res) \ : /*in*/ "0" (&_argvec[0]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "x21" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_8W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ arg7,arg8) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[9]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)(arg1); \ _argvec[2] = (unsigned long)(arg2); \ _argvec[3] = (unsigned long)(arg3); \ _argvec[4] = (unsigned long)(arg4); \ _argvec[5] = (unsigned long)(arg5); \ _argvec[6] = (unsigned long)(arg6); \ _argvec[7] = (unsigned long)(arg7); \ _argvec[8] = (unsigned long)(arg8); \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "ldr x0, [%1, #8] \n\t" \ "ldr x1, [%1, #16] \n\t" \ "ldr x2, [%1, #24] \n\t" \ "ldr x3, [%1, #32] \n\t" \ "ldr x4, [%1, #40] \n\t" \ "ldr x5, [%1, #48] \n\t" \ "ldr x6, [%1, #56] \n\t" \ "ldr x7, [%1, #64] \n\t" \ "ldr x8, [%1] \n\t" /* target->x8 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_X8 \ VALGRIND_RESTORE_STACK \ "mov %0, x0" \ : /*out*/ "=r" (_res) \ : /*in*/ "0" (&_argvec[0]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "x21" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_9W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ arg7,arg8,arg9) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[10]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)(arg1); \ _argvec[2] = (unsigned long)(arg2); \ _argvec[3] = (unsigned long)(arg3); \ _argvec[4] = (unsigned long)(arg4); \ _argvec[5] = (unsigned long)(arg5); \ _argvec[6] = (unsigned long)(arg6); \ _argvec[7] = (unsigned long)(arg7); \ _argvec[8] = (unsigned long)(arg8); \ _argvec[9] = (unsigned long)(arg9); \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "sub sp, sp, #0x20 \n\t" \ "ldr x0, [%1, #8] \n\t" \ "ldr x1, [%1, #16] \n\t" \ "ldr x2, [%1, #24] \n\t" \ "ldr x3, [%1, #32] \n\t" \ "ldr x4, [%1, #40] \n\t" \ "ldr x5, [%1, #48] \n\t" \ "ldr x6, [%1, #56] \n\t" \ "ldr x7, [%1, #64] \n\t" \ "ldr x8, [%1, #72] \n\t" \ "str x8, [sp, #0] \n\t" \ "ldr x8, [%1] \n\t" /* target->x8 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_X8 \ VALGRIND_RESTORE_STACK \ "mov %0, x0" \ : /*out*/ "=r" (_res) \ : /*in*/ "0" (&_argvec[0]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "x21" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_10W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ arg7,arg8,arg9,arg10) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[11]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)(arg1); \ _argvec[2] = (unsigned long)(arg2); \ _argvec[3] = (unsigned long)(arg3); \ _argvec[4] = (unsigned long)(arg4); \ _argvec[5] = (unsigned long)(arg5); \ _argvec[6] = (unsigned long)(arg6); \ _argvec[7] = (unsigned long)(arg7); \ _argvec[8] = (unsigned long)(arg8); \ _argvec[9] = (unsigned long)(arg9); \ _argvec[10] = (unsigned long)(arg10); \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "sub sp, sp, #0x20 \n\t" \ "ldr x0, [%1, #8] \n\t" \ "ldr x1, [%1, #16] \n\t" \ "ldr x2, [%1, #24] \n\t" \ "ldr x3, [%1, #32] \n\t" \ "ldr x4, [%1, #40] \n\t" \ "ldr x5, [%1, #48] \n\t" \ "ldr x6, [%1, #56] \n\t" \ "ldr x7, [%1, #64] \n\t" \ "ldr x8, [%1, #72] \n\t" \ "str x8, [sp, #0] \n\t" \ "ldr x8, [%1, #80] \n\t" \ "str x8, [sp, #8] \n\t" \ "ldr x8, [%1] \n\t" /* target->x8 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_X8 \ VALGRIND_RESTORE_STACK \ "mov %0, x0" \ : /*out*/ "=r" (_res) \ : /*in*/ "0" (&_argvec[0]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "x21" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_11W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ arg7,arg8,arg9,arg10,arg11) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[12]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)(arg1); \ _argvec[2] = (unsigned long)(arg2); \ _argvec[3] = (unsigned long)(arg3); \ _argvec[4] = (unsigned long)(arg4); \ _argvec[5] = (unsigned long)(arg5); \ _argvec[6] = (unsigned long)(arg6); \ _argvec[7] = (unsigned long)(arg7); \ _argvec[8] = (unsigned long)(arg8); \ _argvec[9] = (unsigned long)(arg9); \ _argvec[10] = (unsigned long)(arg10); \ _argvec[11] = (unsigned long)(arg11); \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "sub sp, sp, #0x30 \n\t" \ "ldr x0, [%1, #8] \n\t" \ "ldr x1, [%1, #16] \n\t" \ "ldr x2, [%1, #24] \n\t" \ "ldr x3, [%1, #32] \n\t" \ "ldr x4, [%1, #40] \n\t" \ "ldr x5, [%1, #48] \n\t" \ "ldr x6, [%1, #56] \n\t" \ "ldr x7, [%1, #64] \n\t" \ "ldr x8, [%1, #72] \n\t" \ "str x8, [sp, #0] \n\t" \ "ldr x8, [%1, #80] \n\t" \ "str x8, [sp, #8] \n\t" \ "ldr x8, [%1, #88] \n\t" \ "str x8, [sp, #16] \n\t" \ "ldr x8, [%1] \n\t" /* target->x8 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_X8 \ VALGRIND_RESTORE_STACK \ "mov %0, x0" \ : /*out*/ "=r" (_res) \ : /*in*/ "0" (&_argvec[0]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "x21" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_12W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ arg7,arg8,arg9,arg10,arg11, \ arg12) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[13]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)(arg1); \ _argvec[2] = (unsigned long)(arg2); \ _argvec[3] = (unsigned long)(arg3); \ _argvec[4] = (unsigned long)(arg4); \ _argvec[5] = (unsigned long)(arg5); \ _argvec[6] = (unsigned long)(arg6); \ _argvec[7] = (unsigned long)(arg7); \ _argvec[8] = (unsigned long)(arg8); \ _argvec[9] = (unsigned long)(arg9); \ _argvec[10] = (unsigned long)(arg10); \ _argvec[11] = (unsigned long)(arg11); \ _argvec[12] = (unsigned long)(arg12); \ __asm__ volatile( \ VALGRIND_ALIGN_STACK \ "sub sp, sp, #0x30 \n\t" \ "ldr x0, [%1, #8] \n\t" \ "ldr x1, [%1, #16] \n\t" \ "ldr x2, [%1, #24] \n\t" \ "ldr x3, [%1, #32] \n\t" \ "ldr x4, [%1, #40] \n\t" \ "ldr x5, [%1, #48] \n\t" \ "ldr x6, [%1, #56] \n\t" \ "ldr x7, [%1, #64] \n\t" \ "ldr x8, [%1, #72] \n\t" \ "str x8, [sp, #0] \n\t" \ "ldr x8, [%1, #80] \n\t" \ "str x8, [sp, #8] \n\t" \ "ldr x8, [%1, #88] \n\t" \ "str x8, [sp, #16] \n\t" \ "ldr x8, [%1, #96] \n\t" \ "str x8, [sp, #24] \n\t" \ "ldr x8, [%1] \n\t" /* target->x8 */ \ VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_X8 \ VALGRIND_RESTORE_STACK \ "mov %0, x0" \ : /*out*/ "=r" (_res) \ : /*in*/ "0" (&_argvec[0]) \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "x21" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #endif /* PLAT_arm64_linux */ /* ------------------------- s390x-linux ------------------------- */ #if defined(PLAT_s390x_linux) /* Similar workaround as amd64 (see above), but we use r11 as frame pointer and save the old r11 in r7. r11 might be used for argvec, therefore we copy argvec in r1 since r1 is clobbered after the call anyway. */ #if defined(__GNUC__) && defined(__GCC_HAVE_DWARF2_CFI_ASM) # define __FRAME_POINTER \ ,"d"(__builtin_dwarf_cfa()) # define VALGRIND_CFI_PROLOGUE \ ".cfi_remember_state\n\t" \ "lgr 1,%1\n\t" /* copy the argvec pointer in r1 */ \ "lgr 7,11\n\t" \ "lgr 11,%2\n\t" \ ".cfi_def_cfa r11, 0\n\t" # define VALGRIND_CFI_EPILOGUE \ "lgr 11, 7\n\t" \ ".cfi_restore_state\n\t" #else # define __FRAME_POINTER # define VALGRIND_CFI_PROLOGUE \ "lgr 1,%1\n\t" # define VALGRIND_CFI_EPILOGUE #endif /* Nb: On s390 the stack pointer is properly aligned *at all times* according to the s390 GCC maintainer. (The ABI specification is not precise in this regard.) Therefore, VALGRIND_ALIGN_STACK and VALGRIND_RESTORE_STACK are not defined here. */ /* These regs are trashed by the hidden call. Note that we overwrite r14 in s390_irgen_noredir (VEX/priv/guest_s390_irgen.c) to give the function a proper return address. All others are ABI defined call clobbers. */ #define __CALLER_SAVED_REGS "0","1","2","3","4","5","14", \ "f0","f1","f2","f3","f4","f5","f6","f7" /* Nb: Although r11 is modified in the asm snippets below (inside VALGRIND_CFI_PROLOGUE) it is not listed in the clobber section, for two reasons: (1) r11 is restored in VALGRIND_CFI_EPILOGUE, so effectively it is not modified (2) GCC will complain that r11 cannot appear inside a clobber section, when compiled with -O -fno-omit-frame-pointer */ #define CALL_FN_W_v(lval, orig) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[1]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ __asm__ volatile( \ VALGRIND_CFI_PROLOGUE \ "aghi 15,-160\n\t" \ "lg 1, 0(1)\n\t" /* target->r1 */ \ VALGRIND_CALL_NOREDIR_R1 \ "lgr %0, 2\n\t" \ "aghi 15,160\n\t" \ VALGRIND_CFI_EPILOGUE \ : /*out*/ "=d" (_res) \ : /*in*/ "d" (&_argvec[0]) __FRAME_POINTER \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS,"7" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) /* The call abi has the arguments in r2-r6 and stack */ #define CALL_FN_W_W(lval, orig, arg1) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[2]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)arg1; \ __asm__ volatile( \ VALGRIND_CFI_PROLOGUE \ "aghi 15,-160\n\t" \ "lg 2, 8(1)\n\t" \ "lg 1, 0(1)\n\t" \ VALGRIND_CALL_NOREDIR_R1 \ "lgr %0, 2\n\t" \ "aghi 15,160\n\t" \ VALGRIND_CFI_EPILOGUE \ : /*out*/ "=d" (_res) \ : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS,"7" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_WW(lval, orig, arg1, arg2) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[3]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)arg1; \ _argvec[2] = (unsigned long)arg2; \ __asm__ volatile( \ VALGRIND_CFI_PROLOGUE \ "aghi 15,-160\n\t" \ "lg 2, 8(1)\n\t" \ "lg 3,16(1)\n\t" \ "lg 1, 0(1)\n\t" \ VALGRIND_CALL_NOREDIR_R1 \ "lgr %0, 2\n\t" \ "aghi 15,160\n\t" \ VALGRIND_CFI_EPILOGUE \ : /*out*/ "=d" (_res) \ : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS,"7" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_WWW(lval, orig, arg1, arg2, arg3) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[4]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)arg1; \ _argvec[2] = (unsigned long)arg2; \ _argvec[3] = (unsigned long)arg3; \ __asm__ volatile( \ VALGRIND_CFI_PROLOGUE \ "aghi 15,-160\n\t" \ "lg 2, 8(1)\n\t" \ "lg 3,16(1)\n\t" \ "lg 4,24(1)\n\t" \ "lg 1, 0(1)\n\t" \ VALGRIND_CALL_NOREDIR_R1 \ "lgr %0, 2\n\t" \ "aghi 15,160\n\t" \ VALGRIND_CFI_EPILOGUE \ : /*out*/ "=d" (_res) \ : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS,"7" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_WWWW(lval, orig, arg1, arg2, arg3, arg4) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[5]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)arg1; \ _argvec[2] = (unsigned long)arg2; \ _argvec[3] = (unsigned long)arg3; \ _argvec[4] = (unsigned long)arg4; \ __asm__ volatile( \ VALGRIND_CFI_PROLOGUE \ "aghi 15,-160\n\t" \ "lg 2, 8(1)\n\t" \ "lg 3,16(1)\n\t" \ "lg 4,24(1)\n\t" \ "lg 5,32(1)\n\t" \ "lg 1, 0(1)\n\t" \ VALGRIND_CALL_NOREDIR_R1 \ "lgr %0, 2\n\t" \ "aghi 15,160\n\t" \ VALGRIND_CFI_EPILOGUE \ : /*out*/ "=d" (_res) \ : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS,"7" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_5W(lval, orig, arg1, arg2, arg3, arg4, arg5) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[6]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)arg1; \ _argvec[2] = (unsigned long)arg2; \ _argvec[3] = (unsigned long)arg3; \ _argvec[4] = (unsigned long)arg4; \ _argvec[5] = (unsigned long)arg5; \ __asm__ volatile( \ VALGRIND_CFI_PROLOGUE \ "aghi 15,-160\n\t" \ "lg 2, 8(1)\n\t" \ "lg 3,16(1)\n\t" \ "lg 4,24(1)\n\t" \ "lg 5,32(1)\n\t" \ "lg 6,40(1)\n\t" \ "lg 1, 0(1)\n\t" \ VALGRIND_CALL_NOREDIR_R1 \ "lgr %0, 2\n\t" \ "aghi 15,160\n\t" \ VALGRIND_CFI_EPILOGUE \ : /*out*/ "=d" (_res) \ : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS,"6","7" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_6W(lval, orig, arg1, arg2, arg3, arg4, arg5, \ arg6) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[7]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)arg1; \ _argvec[2] = (unsigned long)arg2; \ _argvec[3] = (unsigned long)arg3; \ _argvec[4] = (unsigned long)arg4; \ _argvec[5] = (unsigned long)arg5; \ _argvec[6] = (unsigned long)arg6; \ __asm__ volatile( \ VALGRIND_CFI_PROLOGUE \ "aghi 15,-168\n\t" \ "lg 2, 8(1)\n\t" \ "lg 3,16(1)\n\t" \ "lg 4,24(1)\n\t" \ "lg 5,32(1)\n\t" \ "lg 6,40(1)\n\t" \ "mvc 160(8,15), 48(1)\n\t" \ "lg 1, 0(1)\n\t" \ VALGRIND_CALL_NOREDIR_R1 \ "lgr %0, 2\n\t" \ "aghi 15,168\n\t" \ VALGRIND_CFI_EPILOGUE \ : /*out*/ "=d" (_res) \ : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS,"6","7" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_7W(lval, orig, arg1, arg2, arg3, arg4, arg5, \ arg6, arg7) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[8]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)arg1; \ _argvec[2] = (unsigned long)arg2; \ _argvec[3] = (unsigned long)arg3; \ _argvec[4] = (unsigned long)arg4; \ _argvec[5] = (unsigned long)arg5; \ _argvec[6] = (unsigned long)arg6; \ _argvec[7] = (unsigned long)arg7; \ __asm__ volatile( \ VALGRIND_CFI_PROLOGUE \ "aghi 15,-176\n\t" \ "lg 2, 8(1)\n\t" \ "lg 3,16(1)\n\t" \ "lg 4,24(1)\n\t" \ "lg 5,32(1)\n\t" \ "lg 6,40(1)\n\t" \ "mvc 160(8,15), 48(1)\n\t" \ "mvc 168(8,15), 56(1)\n\t" \ "lg 1, 0(1)\n\t" \ VALGRIND_CALL_NOREDIR_R1 \ "lgr %0, 2\n\t" \ "aghi 15,176\n\t" \ VALGRIND_CFI_EPILOGUE \ : /*out*/ "=d" (_res) \ : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS,"6","7" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_8W(lval, orig, arg1, arg2, arg3, arg4, arg5, \ arg6, arg7 ,arg8) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[9]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)arg1; \ _argvec[2] = (unsigned long)arg2; \ _argvec[3] = (unsigned long)arg3; \ _argvec[4] = (unsigned long)arg4; \ _argvec[5] = (unsigned long)arg5; \ _argvec[6] = (unsigned long)arg6; \ _argvec[7] = (unsigned long)arg7; \ _argvec[8] = (unsigned long)arg8; \ __asm__ volatile( \ VALGRIND_CFI_PROLOGUE \ "aghi 15,-184\n\t" \ "lg 2, 8(1)\n\t" \ "lg 3,16(1)\n\t" \ "lg 4,24(1)\n\t" \ "lg 5,32(1)\n\t" \ "lg 6,40(1)\n\t" \ "mvc 160(8,15), 48(1)\n\t" \ "mvc 168(8,15), 56(1)\n\t" \ "mvc 176(8,15), 64(1)\n\t" \ "lg 1, 0(1)\n\t" \ VALGRIND_CALL_NOREDIR_R1 \ "lgr %0, 2\n\t" \ "aghi 15,184\n\t" \ VALGRIND_CFI_EPILOGUE \ : /*out*/ "=d" (_res) \ : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS,"6","7" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_9W(lval, orig, arg1, arg2, arg3, arg4, arg5, \ arg6, arg7 ,arg8, arg9) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[10]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)arg1; \ _argvec[2] = (unsigned long)arg2; \ _argvec[3] = (unsigned long)arg3; \ _argvec[4] = (unsigned long)arg4; \ _argvec[5] = (unsigned long)arg5; \ _argvec[6] = (unsigned long)arg6; \ _argvec[7] = (unsigned long)arg7; \ _argvec[8] = (unsigned long)arg8; \ _argvec[9] = (unsigned long)arg9; \ __asm__ volatile( \ VALGRIND_CFI_PROLOGUE \ "aghi 15,-192\n\t" \ "lg 2, 8(1)\n\t" \ "lg 3,16(1)\n\t" \ "lg 4,24(1)\n\t" \ "lg 5,32(1)\n\t" \ "lg 6,40(1)\n\t" \ "mvc 160(8,15), 48(1)\n\t" \ "mvc 168(8,15), 56(1)\n\t" \ "mvc 176(8,15), 64(1)\n\t" \ "mvc 184(8,15), 72(1)\n\t" \ "lg 1, 0(1)\n\t" \ VALGRIND_CALL_NOREDIR_R1 \ "lgr %0, 2\n\t" \ "aghi 15,192\n\t" \ VALGRIND_CFI_EPILOGUE \ : /*out*/ "=d" (_res) \ : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS,"6","7" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_10W(lval, orig, arg1, arg2, arg3, arg4, arg5, \ arg6, arg7 ,arg8, arg9, arg10) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[11]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)arg1; \ _argvec[2] = (unsigned long)arg2; \ _argvec[3] = (unsigned long)arg3; \ _argvec[4] = (unsigned long)arg4; \ _argvec[5] = (unsigned long)arg5; \ _argvec[6] = (unsigned long)arg6; \ _argvec[7] = (unsigned long)arg7; \ _argvec[8] = (unsigned long)arg8; \ _argvec[9] = (unsigned long)arg9; \ _argvec[10] = (unsigned long)arg10; \ __asm__ volatile( \ VALGRIND_CFI_PROLOGUE \ "aghi 15,-200\n\t" \ "lg 2, 8(1)\n\t" \ "lg 3,16(1)\n\t" \ "lg 4,24(1)\n\t" \ "lg 5,32(1)\n\t" \ "lg 6,40(1)\n\t" \ "mvc 160(8,15), 48(1)\n\t" \ "mvc 168(8,15), 56(1)\n\t" \ "mvc 176(8,15), 64(1)\n\t" \ "mvc 184(8,15), 72(1)\n\t" \ "mvc 192(8,15), 80(1)\n\t" \ "lg 1, 0(1)\n\t" \ VALGRIND_CALL_NOREDIR_R1 \ "lgr %0, 2\n\t" \ "aghi 15,200\n\t" \ VALGRIND_CFI_EPILOGUE \ : /*out*/ "=d" (_res) \ : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS,"6","7" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_11W(lval, orig, arg1, arg2, arg3, arg4, arg5, \ arg6, arg7 ,arg8, arg9, arg10, arg11) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[12]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)arg1; \ _argvec[2] = (unsigned long)arg2; \ _argvec[3] = (unsigned long)arg3; \ _argvec[4] = (unsigned long)arg4; \ _argvec[5] = (unsigned long)arg5; \ _argvec[6] = (unsigned long)arg6; \ _argvec[7] = (unsigned long)arg7; \ _argvec[8] = (unsigned long)arg8; \ _argvec[9] = (unsigned long)arg9; \ _argvec[10] = (unsigned long)arg10; \ _argvec[11] = (unsigned long)arg11; \ __asm__ volatile( \ VALGRIND_CFI_PROLOGUE \ "aghi 15,-208\n\t" \ "lg 2, 8(1)\n\t" \ "lg 3,16(1)\n\t" \ "lg 4,24(1)\n\t" \ "lg 5,32(1)\n\t" \ "lg 6,40(1)\n\t" \ "mvc 160(8,15), 48(1)\n\t" \ "mvc 168(8,15), 56(1)\n\t" \ "mvc 176(8,15), 64(1)\n\t" \ "mvc 184(8,15), 72(1)\n\t" \ "mvc 192(8,15), 80(1)\n\t" \ "mvc 200(8,15), 88(1)\n\t" \ "lg 1, 0(1)\n\t" \ VALGRIND_CALL_NOREDIR_R1 \ "lgr %0, 2\n\t" \ "aghi 15,208\n\t" \ VALGRIND_CFI_EPILOGUE \ : /*out*/ "=d" (_res) \ : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS,"6","7" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_12W(lval, orig, arg1, arg2, arg3, arg4, arg5, \ arg6, arg7 ,arg8, arg9, arg10, arg11, arg12)\ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[13]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)arg1; \ _argvec[2] = (unsigned long)arg2; \ _argvec[3] = (unsigned long)arg3; \ _argvec[4] = (unsigned long)arg4; \ _argvec[5] = (unsigned long)arg5; \ _argvec[6] = (unsigned long)arg6; \ _argvec[7] = (unsigned long)arg7; \ _argvec[8] = (unsigned long)arg8; \ _argvec[9] = (unsigned long)arg9; \ _argvec[10] = (unsigned long)arg10; \ _argvec[11] = (unsigned long)arg11; \ _argvec[12] = (unsigned long)arg12; \ __asm__ volatile( \ VALGRIND_CFI_PROLOGUE \ "aghi 15,-216\n\t" \ "lg 2, 8(1)\n\t" \ "lg 3,16(1)\n\t" \ "lg 4,24(1)\n\t" \ "lg 5,32(1)\n\t" \ "lg 6,40(1)\n\t" \ "mvc 160(8,15), 48(1)\n\t" \ "mvc 168(8,15), 56(1)\n\t" \ "mvc 176(8,15), 64(1)\n\t" \ "mvc 184(8,15), 72(1)\n\t" \ "mvc 192(8,15), 80(1)\n\t" \ "mvc 200(8,15), 88(1)\n\t" \ "mvc 208(8,15), 96(1)\n\t" \ "lg 1, 0(1)\n\t" \ VALGRIND_CALL_NOREDIR_R1 \ "lgr %0, 2\n\t" \ "aghi 15,216\n\t" \ VALGRIND_CFI_EPILOGUE \ : /*out*/ "=d" (_res) \ : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \ : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS,"6","7" \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #endif /* PLAT_s390x_linux */ /* ------------------------- mips32-linux ----------------------- */ #if defined(PLAT_mips32_linux) /* These regs are trashed by the hidden call. */ #define __CALLER_SAVED_REGS "$2", "$3", "$4", "$5", "$6", \ "$7", "$8", "$9", "$10", "$11", "$12", "$13", "$14", "$15", "$24", \ "$25", "$31" /* These CALL_FN_ macros assume that on mips-linux, sizeof(unsigned long) == 4. */ #define CALL_FN_W_v(lval, orig) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[1]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ __asm__ volatile( \ "subu $29, $29, 8 \n\t" \ "sw $28, 0($29) \n\t" \ "sw $31, 4($29) \n\t" \ "subu $29, $29, 16 \n\t" \ "lw $25, 0(%1) \n\t" /* target->t9 */ \ VALGRIND_CALL_NOREDIR_T9 \ "addu $29, $29, 16\n\t" \ "lw $28, 0($29) \n\t" \ "lw $31, 4($29) \n\t" \ "addu $29, $29, 8 \n\t" \ "move %0, $2\n" \ : /*out*/ "=r" (_res) \ : /*in*/ "0" (&_argvec[0]) \ : /*trash*/ "memory", __CALLER_SAVED_REGS \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_W(lval, orig, arg1) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[2]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)(arg1); \ __asm__ volatile( \ "subu $29, $29, 8 \n\t" \ "sw $28, 0($29) \n\t" \ "sw $31, 4($29) \n\t" \ "subu $29, $29, 16 \n\t" \ "lw $4, 4(%1) \n\t" /* arg1*/ \ "lw $25, 0(%1) \n\t" /* target->t9 */ \ VALGRIND_CALL_NOREDIR_T9 \ "addu $29, $29, 16 \n\t" \ "lw $28, 0($29) \n\t" \ "lw $31, 4($29) \n\t" \ "addu $29, $29, 8 \n\t" \ "move %0, $2\n" \ : /*out*/ "=r" (_res) \ : /*in*/ "0" (&_argvec[0]) \ : /*trash*/ "memory", __CALLER_SAVED_REGS \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_WW(lval, orig, arg1,arg2) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[3]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)(arg1); \ _argvec[2] = (unsigned long)(arg2); \ __asm__ volatile( \ "subu $29, $29, 8 \n\t" \ "sw $28, 0($29) \n\t" \ "sw $31, 4($29) \n\t" \ "subu $29, $29, 16 \n\t" \ "lw $4, 4(%1) \n\t" \ "lw $5, 8(%1) \n\t" \ "lw $25, 0(%1) \n\t" /* target->t9 */ \ VALGRIND_CALL_NOREDIR_T9 \ "addu $29, $29, 16 \n\t" \ "lw $28, 0($29) \n\t" \ "lw $31, 4($29) \n\t" \ "addu $29, $29, 8 \n\t" \ "move %0, $2\n" \ : /*out*/ "=r" (_res) \ : /*in*/ "0" (&_argvec[0]) \ : /*trash*/ "memory", __CALLER_SAVED_REGS \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_WWW(lval, orig, arg1,arg2,arg3) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[4]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)(arg1); \ _argvec[2] = (unsigned long)(arg2); \ _argvec[3] = (unsigned long)(arg3); \ __asm__ volatile( \ "subu $29, $29, 8 \n\t" \ "sw $28, 0($29) \n\t" \ "sw $31, 4($29) \n\t" \ "subu $29, $29, 16 \n\t" \ "lw $4, 4(%1) \n\t" \ "lw $5, 8(%1) \n\t" \ "lw $6, 12(%1) \n\t" \ "lw $25, 0(%1) \n\t" /* target->t9 */ \ VALGRIND_CALL_NOREDIR_T9 \ "addu $29, $29, 16 \n\t" \ "lw $28, 0($29) \n\t" \ "lw $31, 4($29) \n\t" \ "addu $29, $29, 8 \n\t" \ "move %0, $2\n" \ : /*out*/ "=r" (_res) \ : /*in*/ "0" (&_argvec[0]) \ : /*trash*/ "memory", __CALLER_SAVED_REGS \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_WWWW(lval, orig, arg1,arg2,arg3,arg4) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[5]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)(arg1); \ _argvec[2] = (unsigned long)(arg2); \ _argvec[3] = (unsigned long)(arg3); \ _argvec[4] = (unsigned long)(arg4); \ __asm__ volatile( \ "subu $29, $29, 8 \n\t" \ "sw $28, 0($29) \n\t" \ "sw $31, 4($29) \n\t" \ "subu $29, $29, 16 \n\t" \ "lw $4, 4(%1) \n\t" \ "lw $5, 8(%1) \n\t" \ "lw $6, 12(%1) \n\t" \ "lw $7, 16(%1) \n\t" \ "lw $25, 0(%1) \n\t" /* target->t9 */ \ VALGRIND_CALL_NOREDIR_T9 \ "addu $29, $29, 16 \n\t" \ "lw $28, 0($29) \n\t" \ "lw $31, 4($29) \n\t" \ "addu $29, $29, 8 \n\t" \ "move %0, $2\n" \ : /*out*/ "=r" (_res) \ : /*in*/ "0" (&_argvec[0]) \ : /*trash*/ "memory", __CALLER_SAVED_REGS \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_5W(lval, orig, arg1,arg2,arg3,arg4,arg5) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[6]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)(arg1); \ _argvec[2] = (unsigned long)(arg2); \ _argvec[3] = (unsigned long)(arg3); \ _argvec[4] = (unsigned long)(arg4); \ _argvec[5] = (unsigned long)(arg5); \ __asm__ volatile( \ "subu $29, $29, 8 \n\t" \ "sw $28, 0($29) \n\t" \ "sw $31, 4($29) \n\t" \ "lw $4, 20(%1) \n\t" \ "subu $29, $29, 24\n\t" \ "sw $4, 16($29) \n\t" \ "lw $4, 4(%1) \n\t" \ "lw $5, 8(%1) \n\t" \ "lw $6, 12(%1) \n\t" \ "lw $7, 16(%1) \n\t" \ "lw $25, 0(%1) \n\t" /* target->t9 */ \ VALGRIND_CALL_NOREDIR_T9 \ "addu $29, $29, 24 \n\t" \ "lw $28, 0($29) \n\t" \ "lw $31, 4($29) \n\t" \ "addu $29, $29, 8 \n\t" \ "move %0, $2\n" \ : /*out*/ "=r" (_res) \ : /*in*/ "0" (&_argvec[0]) \ : /*trash*/ "memory", __CALLER_SAVED_REGS \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_6W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[7]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)(arg1); \ _argvec[2] = (unsigned long)(arg2); \ _argvec[3] = (unsigned long)(arg3); \ _argvec[4] = (unsigned long)(arg4); \ _argvec[5] = (unsigned long)(arg5); \ _argvec[6] = (unsigned long)(arg6); \ __asm__ volatile( \ "subu $29, $29, 8 \n\t" \ "sw $28, 0($29) \n\t" \ "sw $31, 4($29) \n\t" \ "lw $4, 20(%1) \n\t" \ "subu $29, $29, 32\n\t" \ "sw $4, 16($29) \n\t" \ "lw $4, 24(%1) \n\t" \ "nop\n\t" \ "sw $4, 20($29) \n\t" \ "lw $4, 4(%1) \n\t" \ "lw $5, 8(%1) \n\t" \ "lw $6, 12(%1) \n\t" \ "lw $7, 16(%1) \n\t" \ "lw $25, 0(%1) \n\t" /* target->t9 */ \ VALGRIND_CALL_NOREDIR_T9 \ "addu $29, $29, 32 \n\t" \ "lw $28, 0($29) \n\t" \ "lw $31, 4($29) \n\t" \ "addu $29, $29, 8 \n\t" \ "move %0, $2\n" \ : /*out*/ "=r" (_res) \ : /*in*/ "0" (&_argvec[0]) \ : /*trash*/ "memory", __CALLER_SAVED_REGS \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_7W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ arg7) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[8]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)(arg1); \ _argvec[2] = (unsigned long)(arg2); \ _argvec[3] = (unsigned long)(arg3); \ _argvec[4] = (unsigned long)(arg4); \ _argvec[5] = (unsigned long)(arg5); \ _argvec[6] = (unsigned long)(arg6); \ _argvec[7] = (unsigned long)(arg7); \ __asm__ volatile( \ "subu $29, $29, 8 \n\t" \ "sw $28, 0($29) \n\t" \ "sw $31, 4($29) \n\t" \ "lw $4, 20(%1) \n\t" \ "subu $29, $29, 32\n\t" \ "sw $4, 16($29) \n\t" \ "lw $4, 24(%1) \n\t" \ "sw $4, 20($29) \n\t" \ "lw $4, 28(%1) \n\t" \ "sw $4, 24($29) \n\t" \ "lw $4, 4(%1) \n\t" \ "lw $5, 8(%1) \n\t" \ "lw $6, 12(%1) \n\t" \ "lw $7, 16(%1) \n\t" \ "lw $25, 0(%1) \n\t" /* target->t9 */ \ VALGRIND_CALL_NOREDIR_T9 \ "addu $29, $29, 32 \n\t" \ "lw $28, 0($29) \n\t" \ "lw $31, 4($29) \n\t" \ "addu $29, $29, 8 \n\t" \ "move %0, $2\n" \ : /*out*/ "=r" (_res) \ : /*in*/ "0" (&_argvec[0]) \ : /*trash*/ "memory", __CALLER_SAVED_REGS \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_8W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ arg7,arg8) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[9]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)(arg1); \ _argvec[2] = (unsigned long)(arg2); \ _argvec[3] = (unsigned long)(arg3); \ _argvec[4] = (unsigned long)(arg4); \ _argvec[5] = (unsigned long)(arg5); \ _argvec[6] = (unsigned long)(arg6); \ _argvec[7] = (unsigned long)(arg7); \ _argvec[8] = (unsigned long)(arg8); \ __asm__ volatile( \ "subu $29, $29, 8 \n\t" \ "sw $28, 0($29) \n\t" \ "sw $31, 4($29) \n\t" \ "lw $4, 20(%1) \n\t" \ "subu $29, $29, 40\n\t" \ "sw $4, 16($29) \n\t" \ "lw $4, 24(%1) \n\t" \ "sw $4, 20($29) \n\t" \ "lw $4, 28(%1) \n\t" \ "sw $4, 24($29) \n\t" \ "lw $4, 32(%1) \n\t" \ "sw $4, 28($29) \n\t" \ "lw $4, 4(%1) \n\t" \ "lw $5, 8(%1) \n\t" \ "lw $6, 12(%1) \n\t" \ "lw $7, 16(%1) \n\t" \ "lw $25, 0(%1) \n\t" /* target->t9 */ \ VALGRIND_CALL_NOREDIR_T9 \ "addu $29, $29, 40 \n\t" \ "lw $28, 0($29) \n\t" \ "lw $31, 4($29) \n\t" \ "addu $29, $29, 8 \n\t" \ "move %0, $2\n" \ : /*out*/ "=r" (_res) \ : /*in*/ "0" (&_argvec[0]) \ : /*trash*/ "memory", __CALLER_SAVED_REGS \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_9W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ arg7,arg8,arg9) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[10]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)(arg1); \ _argvec[2] = (unsigned long)(arg2); \ _argvec[3] = (unsigned long)(arg3); \ _argvec[4] = (unsigned long)(arg4); \ _argvec[5] = (unsigned long)(arg5); \ _argvec[6] = (unsigned long)(arg6); \ _argvec[7] = (unsigned long)(arg7); \ _argvec[8] = (unsigned long)(arg8); \ _argvec[9] = (unsigned long)(arg9); \ __asm__ volatile( \ "subu $29, $29, 8 \n\t" \ "sw $28, 0($29) \n\t" \ "sw $31, 4($29) \n\t" \ "lw $4, 20(%1) \n\t" \ "subu $29, $29, 40\n\t" \ "sw $4, 16($29) \n\t" \ "lw $4, 24(%1) \n\t" \ "sw $4, 20($29) \n\t" \ "lw $4, 28(%1) \n\t" \ "sw $4, 24($29) \n\t" \ "lw $4, 32(%1) \n\t" \ "sw $4, 28($29) \n\t" \ "lw $4, 36(%1) \n\t" \ "sw $4, 32($29) \n\t" \ "lw $4, 4(%1) \n\t" \ "lw $5, 8(%1) \n\t" \ "lw $6, 12(%1) \n\t" \ "lw $7, 16(%1) \n\t" \ "lw $25, 0(%1) \n\t" /* target->t9 */ \ VALGRIND_CALL_NOREDIR_T9 \ "addu $29, $29, 40 \n\t" \ "lw $28, 0($29) \n\t" \ "lw $31, 4($29) \n\t" \ "addu $29, $29, 8 \n\t" \ "move %0, $2\n" \ : /*out*/ "=r" (_res) \ : /*in*/ "0" (&_argvec[0]) \ : /*trash*/ "memory", __CALLER_SAVED_REGS \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_10W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ arg7,arg8,arg9,arg10) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[11]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)(arg1); \ _argvec[2] = (unsigned long)(arg2); \ _argvec[3] = (unsigned long)(arg3); \ _argvec[4] = (unsigned long)(arg4); \ _argvec[5] = (unsigned long)(arg5); \ _argvec[6] = (unsigned long)(arg6); \ _argvec[7] = (unsigned long)(arg7); \ _argvec[8] = (unsigned long)(arg8); \ _argvec[9] = (unsigned long)(arg9); \ _argvec[10] = (unsigned long)(arg10); \ __asm__ volatile( \ "subu $29, $29, 8 \n\t" \ "sw $28, 0($29) \n\t" \ "sw $31, 4($29) \n\t" \ "lw $4, 20(%1) \n\t" \ "subu $29, $29, 48\n\t" \ "sw $4, 16($29) \n\t" \ "lw $4, 24(%1) \n\t" \ "sw $4, 20($29) \n\t" \ "lw $4, 28(%1) \n\t" \ "sw $4, 24($29) \n\t" \ "lw $4, 32(%1) \n\t" \ "sw $4, 28($29) \n\t" \ "lw $4, 36(%1) \n\t" \ "sw $4, 32($29) \n\t" \ "lw $4, 40(%1) \n\t" \ "sw $4, 36($29) \n\t" \ "lw $4, 4(%1) \n\t" \ "lw $5, 8(%1) \n\t" \ "lw $6, 12(%1) \n\t" \ "lw $7, 16(%1) \n\t" \ "lw $25, 0(%1) \n\t" /* target->t9 */ \ VALGRIND_CALL_NOREDIR_T9 \ "addu $29, $29, 48 \n\t" \ "lw $28, 0($29) \n\t" \ "lw $31, 4($29) \n\t" \ "addu $29, $29, 8 \n\t" \ "move %0, $2\n" \ : /*out*/ "=r" (_res) \ : /*in*/ "0" (&_argvec[0]) \ : /*trash*/ "memory", __CALLER_SAVED_REGS \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_11W(lval, orig, arg1,arg2,arg3,arg4,arg5, \ arg6,arg7,arg8,arg9,arg10, \ arg11) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[12]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)(arg1); \ _argvec[2] = (unsigned long)(arg2); \ _argvec[3] = (unsigned long)(arg3); \ _argvec[4] = (unsigned long)(arg4); \ _argvec[5] = (unsigned long)(arg5); \ _argvec[6] = (unsigned long)(arg6); \ _argvec[7] = (unsigned long)(arg7); \ _argvec[8] = (unsigned long)(arg8); \ _argvec[9] = (unsigned long)(arg9); \ _argvec[10] = (unsigned long)(arg10); \ _argvec[11] = (unsigned long)(arg11); \ __asm__ volatile( \ "subu $29, $29, 8 \n\t" \ "sw $28, 0($29) \n\t" \ "sw $31, 4($29) \n\t" \ "lw $4, 20(%1) \n\t" \ "subu $29, $29, 48\n\t" \ "sw $4, 16($29) \n\t" \ "lw $4, 24(%1) \n\t" \ "sw $4, 20($29) \n\t" \ "lw $4, 28(%1) \n\t" \ "sw $4, 24($29) \n\t" \ "lw $4, 32(%1) \n\t" \ "sw $4, 28($29) \n\t" \ "lw $4, 36(%1) \n\t" \ "sw $4, 32($29) \n\t" \ "lw $4, 40(%1) \n\t" \ "sw $4, 36($29) \n\t" \ "lw $4, 44(%1) \n\t" \ "sw $4, 40($29) \n\t" \ "lw $4, 4(%1) \n\t" \ "lw $5, 8(%1) \n\t" \ "lw $6, 12(%1) \n\t" \ "lw $7, 16(%1) \n\t" \ "lw $25, 0(%1) \n\t" /* target->t9 */ \ VALGRIND_CALL_NOREDIR_T9 \ "addu $29, $29, 48 \n\t" \ "lw $28, 0($29) \n\t" \ "lw $31, 4($29) \n\t" \ "addu $29, $29, 8 \n\t" \ "move %0, $2\n" \ : /*out*/ "=r" (_res) \ : /*in*/ "0" (&_argvec[0]) \ : /*trash*/ "memory", __CALLER_SAVED_REGS \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #define CALL_FN_W_12W(lval, orig, arg1,arg2,arg3,arg4,arg5, \ arg6,arg7,arg8,arg9,arg10, \ arg11,arg12) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long _argvec[13]; \ volatile unsigned long _res; \ _argvec[0] = (unsigned long)_orig.nraddr; \ _argvec[1] = (unsigned long)(arg1); \ _argvec[2] = (unsigned long)(arg2); \ _argvec[3] = (unsigned long)(arg3); \ _argvec[4] = (unsigned long)(arg4); \ _argvec[5] = (unsigned long)(arg5); \ _argvec[6] = (unsigned long)(arg6); \ _argvec[7] = (unsigned long)(arg7); \ _argvec[8] = (unsigned long)(arg8); \ _argvec[9] = (unsigned long)(arg9); \ _argvec[10] = (unsigned long)(arg10); \ _argvec[11] = (unsigned long)(arg11); \ _argvec[12] = (unsigned long)(arg12); \ __asm__ volatile( \ "subu $29, $29, 8 \n\t" \ "sw $28, 0($29) \n\t" \ "sw $31, 4($29) \n\t" \ "lw $4, 20(%1) \n\t" \ "subu $29, $29, 56\n\t" \ "sw $4, 16($29) \n\t" \ "lw $4, 24(%1) \n\t" \ "sw $4, 20($29) \n\t" \ "lw $4, 28(%1) \n\t" \ "sw $4, 24($29) \n\t" \ "lw $4, 32(%1) \n\t" \ "sw $4, 28($29) \n\t" \ "lw $4, 36(%1) \n\t" \ "sw $4, 32($29) \n\t" \ "lw $4, 40(%1) \n\t" \ "sw $4, 36($29) \n\t" \ "lw $4, 44(%1) \n\t" \ "sw $4, 40($29) \n\t" \ "lw $4, 48(%1) \n\t" \ "sw $4, 44($29) \n\t" \ "lw $4, 4(%1) \n\t" \ "lw $5, 8(%1) \n\t" \ "lw $6, 12(%1) \n\t" \ "lw $7, 16(%1) \n\t" \ "lw $25, 0(%1) \n\t" /* target->t9 */ \ VALGRIND_CALL_NOREDIR_T9 \ "addu $29, $29, 56 \n\t" \ "lw $28, 0($29) \n\t" \ "lw $31, 4($29) \n\t" \ "addu $29, $29, 8 \n\t" \ "move %0, $2\n" \ : /*out*/ "=r" (_res) \ : /*in*/ "r" (&_argvec[0]) \ : /*trash*/ "memory", __CALLER_SAVED_REGS \ ); \ lval = (__typeof__(lval)) _res; \ } while (0) #endif /* PLAT_mips32_linux */ /* ------------------------- mips64-linux ------------------------- */ #if defined(PLAT_mips64_linux) /* These regs are trashed by the hidden call. */ #define __CALLER_SAVED_REGS "$2", "$3", "$4", "$5", "$6", \ "$7", "$8", "$9", "$10", "$11", "$12", "$13", "$14", "$15", "$24", \ "$25", "$31" /* These CALL_FN_ macros assume that on mips64-linux, sizeof(long long) == 8. */ #define MIPS64_LONG2REG_CAST(x) ((long long)(long)x) #define CALL_FN_W_v(lval, orig) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long long _argvec[1]; \ volatile unsigned long long _res; \ _argvec[0] = MIPS64_LONG2REG_CAST(_orig.nraddr); \ __asm__ volatile( \ "ld $25, 0(%1)\n\t" /* target->t9 */ \ VALGRIND_CALL_NOREDIR_T9 \ "move %0, $2\n" \ : /*out*/ "=r" (_res) \ : /*in*/ "0" (&_argvec[0]) \ : /*trash*/ "memory", __CALLER_SAVED_REGS \ ); \ lval = (__typeof__(lval)) (long)_res; \ } while (0) #define CALL_FN_W_W(lval, orig, arg1) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long long _argvec[2]; \ volatile unsigned long long _res; \ _argvec[0] = MIPS64_LONG2REG_CAST(_orig.nraddr); \ _argvec[1] = MIPS64_LONG2REG_CAST(arg1); \ __asm__ volatile( \ "ld $4, 8(%1)\n\t" /* arg1*/ \ "ld $25, 0(%1)\n\t" /* target->t9 */ \ VALGRIND_CALL_NOREDIR_T9 \ "move %0, $2\n" \ : /*out*/ "=r" (_res) \ : /*in*/ "r" (&_argvec[0]) \ : /*trash*/ "memory", __CALLER_SAVED_REGS \ ); \ lval = (__typeof__(lval)) (long)_res; \ } while (0) #define CALL_FN_W_WW(lval, orig, arg1,arg2) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long long _argvec[3]; \ volatile unsigned long long _res; \ _argvec[0] = _orig.nraddr; \ _argvec[1] = MIPS64_LONG2REG_CAST(arg1); \ _argvec[2] = MIPS64_LONG2REG_CAST(arg2); \ __asm__ volatile( \ "ld $4, 8(%1)\n\t" \ "ld $5, 16(%1)\n\t" \ "ld $25, 0(%1)\n\t" /* target->t9 */ \ VALGRIND_CALL_NOREDIR_T9 \ "move %0, $2\n" \ : /*out*/ "=r" (_res) \ : /*in*/ "r" (&_argvec[0]) \ : /*trash*/ "memory", __CALLER_SAVED_REGS \ ); \ lval = (__typeof__(lval)) (long)_res; \ } while (0) #define CALL_FN_W_WWW(lval, orig, arg1,arg2,arg3) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long long _argvec[4]; \ volatile unsigned long long _res; \ _argvec[0] = _orig.nraddr; \ _argvec[1] = MIPS64_LONG2REG_CAST(arg1); \ _argvec[2] = MIPS64_LONG2REG_CAST(arg2); \ _argvec[3] = MIPS64_LONG2REG_CAST(arg3); \ __asm__ volatile( \ "ld $4, 8(%1)\n\t" \ "ld $5, 16(%1)\n\t" \ "ld $6, 24(%1)\n\t" \ "ld $25, 0(%1)\n\t" /* target->t9 */ \ VALGRIND_CALL_NOREDIR_T9 \ "move %0, $2\n" \ : /*out*/ "=r" (_res) \ : /*in*/ "r" (&_argvec[0]) \ : /*trash*/ "memory", __CALLER_SAVED_REGS \ ); \ lval = (__typeof__(lval)) (long)_res; \ } while (0) #define CALL_FN_W_WWWW(lval, orig, arg1,arg2,arg3,arg4) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long long _argvec[5]; \ volatile unsigned long long _res; \ _argvec[0] = MIPS64_LONG2REG_CAST(_orig.nraddr); \ _argvec[1] = MIPS64_LONG2REG_CAST(arg1); \ _argvec[2] = MIPS64_LONG2REG_CAST(arg2); \ _argvec[3] = MIPS64_LONG2REG_CAST(arg3); \ _argvec[4] = MIPS64_LONG2REG_CAST(arg4); \ __asm__ volatile( \ "ld $4, 8(%1)\n\t" \ "ld $5, 16(%1)\n\t" \ "ld $6, 24(%1)\n\t" \ "ld $7, 32(%1)\n\t" \ "ld $25, 0(%1)\n\t" /* target->t9 */ \ VALGRIND_CALL_NOREDIR_T9 \ "move %0, $2\n" \ : /*out*/ "=r" (_res) \ : /*in*/ "r" (&_argvec[0]) \ : /*trash*/ "memory", __CALLER_SAVED_REGS \ ); \ lval = (__typeof__(lval)) (long)_res; \ } while (0) #define CALL_FN_W_5W(lval, orig, arg1,arg2,arg3,arg4,arg5) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long long _argvec[6]; \ volatile unsigned long long _res; \ _argvec[0] = MIPS64_LONG2REG_CAST(_orig.nraddr); \ _argvec[1] = MIPS64_LONG2REG_CAST(arg1); \ _argvec[2] = MIPS64_LONG2REG_CAST(arg2); \ _argvec[3] = MIPS64_LONG2REG_CAST(arg3); \ _argvec[4] = MIPS64_LONG2REG_CAST(arg4); \ _argvec[5] = MIPS64_LONG2REG_CAST(arg5); \ __asm__ volatile( \ "ld $4, 8(%1)\n\t" \ "ld $5, 16(%1)\n\t" \ "ld $6, 24(%1)\n\t" \ "ld $7, 32(%1)\n\t" \ "ld $8, 40(%1)\n\t" \ "ld $25, 0(%1)\n\t" /* target->t9 */ \ VALGRIND_CALL_NOREDIR_T9 \ "move %0, $2\n" \ : /*out*/ "=r" (_res) \ : /*in*/ "r" (&_argvec[0]) \ : /*trash*/ "memory", __CALLER_SAVED_REGS \ ); \ lval = (__typeof__(lval)) (long)_res; \ } while (0) #define CALL_FN_W_6W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long long _argvec[7]; \ volatile unsigned long long _res; \ _argvec[0] = MIPS64_LONG2REG_CAST(_orig.nraddr); \ _argvec[1] = MIPS64_LONG2REG_CAST(arg1); \ _argvec[2] = MIPS64_LONG2REG_CAST(arg2); \ _argvec[3] = MIPS64_LONG2REG_CAST(arg3); \ _argvec[4] = MIPS64_LONG2REG_CAST(arg4); \ _argvec[5] = MIPS64_LONG2REG_CAST(arg5); \ _argvec[6] = MIPS64_LONG2REG_CAST(arg6); \ __asm__ volatile( \ "ld $4, 8(%1)\n\t" \ "ld $5, 16(%1)\n\t" \ "ld $6, 24(%1)\n\t" \ "ld $7, 32(%1)\n\t" \ "ld $8, 40(%1)\n\t" \ "ld $9, 48(%1)\n\t" \ "ld $25, 0(%1)\n\t" /* target->t9 */ \ VALGRIND_CALL_NOREDIR_T9 \ "move %0, $2\n" \ : /*out*/ "=r" (_res) \ : /*in*/ "r" (&_argvec[0]) \ : /*trash*/ "memory", __CALLER_SAVED_REGS \ ); \ lval = (__typeof__(lval)) (long)_res; \ } while (0) #define CALL_FN_W_7W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ arg7) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long long _argvec[8]; \ volatile unsigned long long _res; \ _argvec[0] = MIPS64_LONG2REG_CAST(_orig.nraddr); \ _argvec[1] = MIPS64_LONG2REG_CAST(arg1); \ _argvec[2] = MIPS64_LONG2REG_CAST(arg2); \ _argvec[3] = MIPS64_LONG2REG_CAST(arg3); \ _argvec[4] = MIPS64_LONG2REG_CAST(arg4); \ _argvec[5] = MIPS64_LONG2REG_CAST(arg5); \ _argvec[6] = MIPS64_LONG2REG_CAST(arg6); \ _argvec[7] = MIPS64_LONG2REG_CAST(arg7); \ __asm__ volatile( \ "ld $4, 8(%1)\n\t" \ "ld $5, 16(%1)\n\t" \ "ld $6, 24(%1)\n\t" \ "ld $7, 32(%1)\n\t" \ "ld $8, 40(%1)\n\t" \ "ld $9, 48(%1)\n\t" \ "ld $10, 56(%1)\n\t" \ "ld $25, 0(%1) \n\t" /* target->t9 */ \ VALGRIND_CALL_NOREDIR_T9 \ "move %0, $2\n" \ : /*out*/ "=r" (_res) \ : /*in*/ "r" (&_argvec[0]) \ : /*trash*/ "memory", __CALLER_SAVED_REGS \ ); \ lval = (__typeof__(lval)) (long)_res; \ } while (0) #define CALL_FN_W_8W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ arg7,arg8) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long long _argvec[9]; \ volatile unsigned long long _res; \ _argvec[0] = MIPS64_LONG2REG_CAST(_orig.nraddr); \ _argvec[1] = MIPS64_LONG2REG_CAST(arg1); \ _argvec[2] = MIPS64_LONG2REG_CAST(arg2); \ _argvec[3] = MIPS64_LONG2REG_CAST(arg3); \ _argvec[4] = MIPS64_LONG2REG_CAST(arg4); \ _argvec[5] = MIPS64_LONG2REG_CAST(arg5); \ _argvec[6] = MIPS64_LONG2REG_CAST(arg6); \ _argvec[7] = MIPS64_LONG2REG_CAST(arg7); \ _argvec[8] = MIPS64_LONG2REG_CAST(arg8); \ __asm__ volatile( \ "ld $4, 8(%1)\n\t" \ "ld $5, 16(%1)\n\t" \ "ld $6, 24(%1)\n\t" \ "ld $7, 32(%1)\n\t" \ "ld $8, 40(%1)\n\t" \ "ld $9, 48(%1)\n\t" \ "ld $10, 56(%1)\n\t" \ "ld $11, 64(%1)\n\t" \ "ld $25, 0(%1) \n\t" /* target->t9 */ \ VALGRIND_CALL_NOREDIR_T9 \ "move %0, $2\n" \ : /*out*/ "=r" (_res) \ : /*in*/ "r" (&_argvec[0]) \ : /*trash*/ "memory", __CALLER_SAVED_REGS \ ); \ lval = (__typeof__(lval)) (long)_res; \ } while (0) #define CALL_FN_W_9W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ arg7,arg8,arg9) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long long _argvec[10]; \ volatile unsigned long long _res; \ _argvec[0] = MIPS64_LONG2REG_CAST(_orig.nraddr); \ _argvec[1] = MIPS64_LONG2REG_CAST(arg1); \ _argvec[2] = MIPS64_LONG2REG_CAST(arg2); \ _argvec[3] = MIPS64_LONG2REG_CAST(arg3); \ _argvec[4] = MIPS64_LONG2REG_CAST(arg4); \ _argvec[5] = MIPS64_LONG2REG_CAST(arg5); \ _argvec[6] = MIPS64_LONG2REG_CAST(arg6); \ _argvec[7] = MIPS64_LONG2REG_CAST(arg7); \ _argvec[8] = MIPS64_LONG2REG_CAST(arg8); \ _argvec[9] = MIPS64_LONG2REG_CAST(arg9); \ __asm__ volatile( \ "dsubu $29, $29, 8\n\t" \ "ld $4, 72(%1)\n\t" \ "sd $4, 0($29)\n\t" \ "ld $4, 8(%1)\n\t" \ "ld $5, 16(%1)\n\t" \ "ld $6, 24(%1)\n\t" \ "ld $7, 32(%1)\n\t" \ "ld $8, 40(%1)\n\t" \ "ld $9, 48(%1)\n\t" \ "ld $10, 56(%1)\n\t" \ "ld $11, 64(%1)\n\t" \ "ld $25, 0(%1)\n\t" /* target->t9 */ \ VALGRIND_CALL_NOREDIR_T9 \ "daddu $29, $29, 8\n\t" \ "move %0, $2\n" \ : /*out*/ "=r" (_res) \ : /*in*/ "r" (&_argvec[0]) \ : /*trash*/ "memory", __CALLER_SAVED_REGS \ ); \ lval = (__typeof__(lval)) (long)_res; \ } while (0) #define CALL_FN_W_10W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ arg7,arg8,arg9,arg10) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long long _argvec[11]; \ volatile unsigned long long _res; \ _argvec[0] = MIPS64_LONG2REG_CAST(_orig.nraddr); \ _argvec[1] = MIPS64_LONG2REG_CAST(arg1); \ _argvec[2] = MIPS64_LONG2REG_CAST(arg2); \ _argvec[3] = MIPS64_LONG2REG_CAST(arg3); \ _argvec[4] = MIPS64_LONG2REG_CAST(arg4); \ _argvec[5] = MIPS64_LONG2REG_CAST(arg5); \ _argvec[6] = MIPS64_LONG2REG_CAST(arg6); \ _argvec[7] = MIPS64_LONG2REG_CAST(arg7); \ _argvec[8] = MIPS64_LONG2REG_CAST(arg8); \ _argvec[9] = MIPS64_LONG2REG_CAST(arg9); \ _argvec[10] = MIPS64_LONG2REG_CAST(arg10); \ __asm__ volatile( \ "dsubu $29, $29, 16\n\t" \ "ld $4, 72(%1)\n\t" \ "sd $4, 0($29)\n\t" \ "ld $4, 80(%1)\n\t" \ "sd $4, 8($29)\n\t" \ "ld $4, 8(%1)\n\t" \ "ld $5, 16(%1)\n\t" \ "ld $6, 24(%1)\n\t" \ "ld $7, 32(%1)\n\t" \ "ld $8, 40(%1)\n\t" \ "ld $9, 48(%1)\n\t" \ "ld $10, 56(%1)\n\t" \ "ld $11, 64(%1)\n\t" \ "ld $25, 0(%1)\n\t" /* target->t9 */ \ VALGRIND_CALL_NOREDIR_T9 \ "daddu $29, $29, 16\n\t" \ "move %0, $2\n" \ : /*out*/ "=r" (_res) \ : /*in*/ "r" (&_argvec[0]) \ : /*trash*/ "memory", __CALLER_SAVED_REGS \ ); \ lval = (__typeof__(lval)) (long)_res; \ } while (0) #define CALL_FN_W_11W(lval, orig, arg1,arg2,arg3,arg4,arg5, \ arg6,arg7,arg8,arg9,arg10, \ arg11) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long long _argvec[12]; \ volatile unsigned long long _res; \ _argvec[0] = MIPS64_LONG2REG_CAST(_orig.nraddr); \ _argvec[1] = MIPS64_LONG2REG_CAST(arg1); \ _argvec[2] = MIPS64_LONG2REG_CAST(arg2); \ _argvec[3] = MIPS64_LONG2REG_CAST(arg3); \ _argvec[4] = MIPS64_LONG2REG_CAST(arg4); \ _argvec[5] = MIPS64_LONG2REG_CAST(arg5); \ _argvec[6] = MIPS64_LONG2REG_CAST(arg6); \ _argvec[7] = MIPS64_LONG2REG_CAST(arg7); \ _argvec[8] = MIPS64_LONG2REG_CAST(arg8); \ _argvec[9] = MIPS64_LONG2REG_CAST(arg9); \ _argvec[10] = MIPS64_LONG2REG_CAST(arg10); \ _argvec[11] = MIPS64_LONG2REG_CAST(arg11); \ __asm__ volatile( \ "dsubu $29, $29, 24\n\t" \ "ld $4, 72(%1)\n\t" \ "sd $4, 0($29)\n\t" \ "ld $4, 80(%1)\n\t" \ "sd $4, 8($29)\n\t" \ "ld $4, 88(%1)\n\t" \ "sd $4, 16($29)\n\t" \ "ld $4, 8(%1)\n\t" \ "ld $5, 16(%1)\n\t" \ "ld $6, 24(%1)\n\t" \ "ld $7, 32(%1)\n\t" \ "ld $8, 40(%1)\n\t" \ "ld $9, 48(%1)\n\t" \ "ld $10, 56(%1)\n\t" \ "ld $11, 64(%1)\n\t" \ "ld $25, 0(%1)\n\t" /* target->t9 */ \ VALGRIND_CALL_NOREDIR_T9 \ "daddu $29, $29, 24\n\t" \ "move %0, $2\n" \ : /*out*/ "=r" (_res) \ : /*in*/ "r" (&_argvec[0]) \ : /*trash*/ "memory", __CALLER_SAVED_REGS \ ); \ lval = (__typeof__(lval)) (long)_res; \ } while (0) #define CALL_FN_W_12W(lval, orig, arg1,arg2,arg3,arg4,arg5, \ arg6,arg7,arg8,arg9,arg10, \ arg11,arg12) \ do { \ volatile OrigFn _orig = (orig); \ volatile unsigned long long _argvec[13]; \ volatile unsigned long long _res; \ _argvec[0] = MIPS64_LONG2REG_CAST(_orig.nraddr); \ _argvec[1] = MIPS64_LONG2REG_CAST(arg1); \ _argvec[2] = MIPS64_LONG2REG_CAST(arg2); \ _argvec[3] = MIPS64_LONG2REG_CAST(arg3); \ _argvec[4] = MIPS64_LONG2REG_CAST(arg4); \ _argvec[5] = MIPS64_LONG2REG_CAST(arg5); \ _argvec[6] = MIPS64_LONG2REG_CAST(arg6); \ _argvec[7] = MIPS64_LONG2REG_CAST(arg7); \ _argvec[8] = MIPS64_LONG2REG_CAST(arg8); \ _argvec[9] = MIPS64_LONG2REG_CAST(arg9); \ _argvec[10] = MIPS64_LONG2REG_CAST(arg10); \ _argvec[11] = MIPS64_LONG2REG_CAST(arg11); \ _argvec[12] = MIPS64_LONG2REG_CAST(arg12); \ __asm__ volatile( \ "dsubu $29, $29, 32\n\t" \ "ld $4, 72(%1)\n\t" \ "sd $4, 0($29)\n\t" \ "ld $4, 80(%1)\n\t" \ "sd $4, 8($29)\n\t" \ "ld $4, 88(%1)\n\t" \ "sd $4, 16($29)\n\t" \ "ld $4, 96(%1)\n\t" \ "sd $4, 24($29)\n\t" \ "ld $4, 8(%1)\n\t" \ "ld $5, 16(%1)\n\t" \ "ld $6, 24(%1)\n\t" \ "ld $7, 32(%1)\n\t" \ "ld $8, 40(%1)\n\t" \ "ld $9, 48(%1)\n\t" \ "ld $10, 56(%1)\n\t" \ "ld $11, 64(%1)\n\t" \ "ld $25, 0(%1)\n\t" /* target->t9 */ \ VALGRIND_CALL_NOREDIR_T9 \ "daddu $29, $29, 32\n\t" \ "move %0, $2\n" \ : /*out*/ "=r" (_res) \ : /*in*/ "r" (&_argvec[0]) \ : /*trash*/ "memory", __CALLER_SAVED_REGS \ ); \ lval = (__typeof__(lval)) (long)_res; \ } while (0) #endif /* PLAT_mips64_linux */ /* ------------------------------------------------------------------ */ /* ARCHITECTURE INDEPENDENT MACROS for CLIENT REQUESTS. */ /* */ /* ------------------------------------------------------------------ */ /* Some request codes. There are many more of these, but most are not exposed to end-user view. These are the public ones, all of the form 0x1000 + small_number. Core ones are in the range 0x00000000--0x0000ffff. The non-public ones start at 0x2000. */ /* These macros are used by tools -- they must be public, but don't embed them into other programs. */ #define VG_USERREQ_TOOL_BASE(a,b) \ ((unsigned int)(((a)&0xff) << 24 | ((b)&0xff) << 16)) #define VG_IS_TOOL_USERREQ(a, b, v) \ (VG_USERREQ_TOOL_BASE(a,b) == ((v) & 0xffff0000)) /* !! ABIWARNING !! ABIWARNING !! ABIWARNING !! ABIWARNING !! This enum comprises an ABI exported by Valgrind to programs which use client requests. DO NOT CHANGE THE NUMERIC VALUES OF THESE ENTRIES, NOR DELETE ANY -- add new ones at the end of the most relevant group. */ typedef enum { VG_USERREQ__RUNNING_ON_VALGRIND = 0x1001, VG_USERREQ__DISCARD_TRANSLATIONS = 0x1002, /* These allow any function to be called from the simulated CPU but run on the real CPU. Nb: the first arg passed to the function is always the ThreadId of the running thread! So CLIENT_CALL0 actually requires a 1 arg function, etc. */ VG_USERREQ__CLIENT_CALL0 = 0x1101, VG_USERREQ__CLIENT_CALL1 = 0x1102, VG_USERREQ__CLIENT_CALL2 = 0x1103, VG_USERREQ__CLIENT_CALL3 = 0x1104, /* Can be useful in regression testing suites -- eg. can send Valgrind's output to /dev/null and still count errors. */ VG_USERREQ__COUNT_ERRORS = 0x1201, /* Allows the client program and/or gdbserver to execute a monitor command. */ VG_USERREQ__GDB_MONITOR_COMMAND = 0x1202, /* These are useful and can be interpreted by any tool that tracks malloc() et al, by using vg_replace_malloc.c. */ VG_USERREQ__MALLOCLIKE_BLOCK = 0x1301, VG_USERREQ__RESIZEINPLACE_BLOCK = 0x130b, VG_USERREQ__FREELIKE_BLOCK = 0x1302, /* Memory pool support. */ VG_USERREQ__CREATE_MEMPOOL = 0x1303, VG_USERREQ__DESTROY_MEMPOOL = 0x1304, VG_USERREQ__MEMPOOL_ALLOC = 0x1305, VG_USERREQ__MEMPOOL_FREE = 0x1306, VG_USERREQ__MEMPOOL_TRIM = 0x1307, VG_USERREQ__MOVE_MEMPOOL = 0x1308, VG_USERREQ__MEMPOOL_CHANGE = 0x1309, VG_USERREQ__MEMPOOL_EXISTS = 0x130a, /* Allow printfs to valgrind log. */ /* The first two pass the va_list argument by value, which assumes it is the same size as or smaller than a UWord, which generally isn't the case. Hence are deprecated. The second two pass the vargs by reference and so are immune to this problem. */ /* both :: char* fmt, va_list vargs (DEPRECATED) */ VG_USERREQ__PRINTF = 0x1401, VG_USERREQ__PRINTF_BACKTRACE = 0x1402, /* both :: char* fmt, va_list* vargs */ VG_USERREQ__PRINTF_VALIST_BY_REF = 0x1403, VG_USERREQ__PRINTF_BACKTRACE_VALIST_BY_REF = 0x1404, /* Stack support. */ VG_USERREQ__STACK_REGISTER = 0x1501, VG_USERREQ__STACK_DEREGISTER = 0x1502, VG_USERREQ__STACK_CHANGE = 0x1503, /* Wine support */ VG_USERREQ__LOAD_PDB_DEBUGINFO = 0x1601, /* Querying of debug info. */ VG_USERREQ__MAP_IP_TO_SRCLOC = 0x1701, /* Disable/enable error reporting level. Takes a single Word arg which is the delta to this thread's error disablement indicator. Hence 1 disables or further disables errors, and -1 moves back towards enablement. Other values are not allowed. */ VG_USERREQ__CHANGE_ERR_DISABLEMENT = 0x1801, /* Some requests used for Valgrind internal, such as self-test or self-hosting. */ /* Initialise IR injection */ VG_USERREQ__VEX_INIT_FOR_IRI = 0x1901, /* Used by Inner Valgrind to inform Outer Valgrind where to find the list of inner guest threads */ VG_USERREQ__INNER_THREADS = 0x1902 } Vg_ClientRequest; #if !defined(__GNUC__) # define __extension__ /* */ #endif /* Returns the number of Valgrinds this code is running under. That is, 0 if running natively, 1 if running under Valgrind, 2 if running under Valgrind which is running under another Valgrind, etc. */ #define RUNNING_ON_VALGRIND \ (unsigned)VALGRIND_DO_CLIENT_REQUEST_EXPR(0 /* if not */, \ VG_USERREQ__RUNNING_ON_VALGRIND, \ 0, 0, 0, 0, 0) \ /* Discard translation of code in the range [_qzz_addr .. _qzz_addr + _qzz_len - 1]. Useful if you are debugging a JITter or some such, since it provides a way to make sure valgrind will retranslate the invalidated area. Returns no value. */ #define VALGRIND_DISCARD_TRANSLATIONS(_qzz_addr,_qzz_len) \ VALGRIND_DO_CLIENT_REQUEST_STMT(VG_USERREQ__DISCARD_TRANSLATIONS, \ _qzz_addr, _qzz_len, 0, 0, 0) #define VALGRIND_INNER_THREADS(_qzz_addr) \ VALGRIND_DO_CLIENT_REQUEST_STMT(VG_USERREQ__INNER_THREADS, \ _qzz_addr, 0, 0, 0, 0) /* These requests are for getting Valgrind itself to print something. Possibly with a backtrace. This is a really ugly hack. The return value is the number of characters printed, excluding the "**** " part at the start and the backtrace (if present). */ #if defined(__GNUC__) || defined(__INTEL_COMPILER) && !defined(_MSC_VER) /* Modern GCC will optimize the static routine out if unused, and unused attribute will shut down warnings about it. */ static int VALGRIND_PRINTF(const char *format, ...) __attribute__((format(__printf__, 1, 2), __unused__)); #endif static int #if defined(_MSC_VER) __inline #endif VALGRIND_PRINTF(const char *format, ...) { #if defined(NVALGRIND) (void)format; return 0; #else /* NVALGRIND */ #if defined(_MSC_VER) || defined(__MINGW64__) uintptr_t _qzz_res; #else unsigned long _qzz_res; #endif va_list vargs; va_start(vargs, format); #if defined(_MSC_VER) || defined(__MINGW64__) _qzz_res = VALGRIND_DO_CLIENT_REQUEST_EXPR(0, VG_USERREQ__PRINTF_VALIST_BY_REF, (uintptr_t)format, (uintptr_t)&vargs, 0, 0, 0); #else _qzz_res = VALGRIND_DO_CLIENT_REQUEST_EXPR(0, VG_USERREQ__PRINTF_VALIST_BY_REF, (unsigned long)format, (unsigned long)&vargs, 0, 0, 0); #endif va_end(vargs); return (int)_qzz_res; #endif /* NVALGRIND */ } #if defined(__GNUC__) || defined(__INTEL_COMPILER) && !defined(_MSC_VER) static int VALGRIND_PRINTF_BACKTRACE(const char *format, ...) __attribute__((format(__printf__, 1, 2), __unused__)); #endif static int #if defined(_MSC_VER) __inline #endif VALGRIND_PRINTF_BACKTRACE(const char *format, ...) { #if defined(NVALGRIND) (void)format; return 0; #else /* NVALGRIND */ #if defined(_MSC_VER) || defined(__MINGW64__) uintptr_t _qzz_res; #else unsigned long _qzz_res; #endif va_list vargs; va_start(vargs, format); #if defined(_MSC_VER) || defined(__MINGW64__) _qzz_res = VALGRIND_DO_CLIENT_REQUEST_EXPR(0, VG_USERREQ__PRINTF_BACKTRACE_VALIST_BY_REF, (uintptr_t)format, (uintptr_t)&vargs, 0, 0, 0); #else _qzz_res = VALGRIND_DO_CLIENT_REQUEST_EXPR(0, VG_USERREQ__PRINTF_BACKTRACE_VALIST_BY_REF, (unsigned long)format, (unsigned long)&vargs, 0, 0, 0); #endif va_end(vargs); return (int)_qzz_res; #endif /* NVALGRIND */ } /* These requests allow control to move from the simulated CPU to the real CPU, calling an arbitrary function. Note that the current ThreadId is inserted as the first argument. So this call: VALGRIND_NON_SIMD_CALL2(f, arg1, arg2) requires f to have this signature: Word f(Word tid, Word arg1, Word arg2) where "Word" is a word-sized type. Note that these client requests are not entirely reliable. For example, if you call a function with them that subsequently calls printf(), there's a high chance Valgrind will crash. Generally, your prospects of these working are made higher if the called function does not refer to any global variables, and does not refer to any libc or other functions (printf et al). Any kind of entanglement with libc or dynamic linking is likely to have a bad outcome, for tricky reasons which we've grappled with a lot in the past. */ #define VALGRIND_NON_SIMD_CALL0(_qyy_fn) \ VALGRIND_DO_CLIENT_REQUEST_EXPR(0 /* default return */, \ VG_USERREQ__CLIENT_CALL0, \ _qyy_fn, \ 0, 0, 0, 0) #define VALGRIND_NON_SIMD_CALL1(_qyy_fn, _qyy_arg1) \ VALGRIND_DO_CLIENT_REQUEST_EXPR(0 /* default return */, \ VG_USERREQ__CLIENT_CALL1, \ _qyy_fn, \ _qyy_arg1, 0, 0, 0) #define VALGRIND_NON_SIMD_CALL2(_qyy_fn, _qyy_arg1, _qyy_arg2) \ VALGRIND_DO_CLIENT_REQUEST_EXPR(0 /* default return */, \ VG_USERREQ__CLIENT_CALL2, \ _qyy_fn, \ _qyy_arg1, _qyy_arg2, 0, 0) #define VALGRIND_NON_SIMD_CALL3(_qyy_fn, _qyy_arg1, _qyy_arg2, _qyy_arg3) \ VALGRIND_DO_CLIENT_REQUEST_EXPR(0 /* default return */, \ VG_USERREQ__CLIENT_CALL3, \ _qyy_fn, \ _qyy_arg1, _qyy_arg2, \ _qyy_arg3, 0) /* Counts the number of errors that have been recorded by a tool. Nb: the tool must record the errors with VG_(maybe_record_error)() or VG_(unique_error)() for them to be counted. */ #define VALGRIND_COUNT_ERRORS \ (unsigned)VALGRIND_DO_CLIENT_REQUEST_EXPR( \ 0 /* default return */, \ VG_USERREQ__COUNT_ERRORS, \ 0, 0, 0, 0, 0) /* Several Valgrind tools (Memcheck, Massif, Helgrind, DRD) rely on knowing when heap blocks are allocated in order to give accurate results. This happens automatically for the standard allocator functions such as malloc(), calloc(), realloc(), memalign(), new, new[], free(), delete, delete[], etc. But if your program uses a custom allocator, this doesn't automatically happen, and Valgrind will not do as well. For example, if you allocate superblocks with mmap() and then allocates chunks of the superblocks, all Valgrind's observations will be at the mmap() level and it won't know that the chunks should be considered separate entities. In Memcheck's case, that means you probably won't get heap block overrun detection (because there won't be redzones marked as unaddressable) and you definitely won't get any leak detection. The following client requests allow a custom allocator to be annotated so that it can be handled accurately by Valgrind. VALGRIND_MALLOCLIKE_BLOCK marks a region of memory as having been allocated by a malloc()-like function. For Memcheck (an illustrative case), this does two things: - It records that the block has been allocated. This means any addresses within the block mentioned in error messages will be identified as belonging to the block. It also means that if the block isn't freed it will be detected by the leak checker. - It marks the block as being addressable and undefined (if 'is_zeroed' is not set), or addressable and defined (if 'is_zeroed' is set). This controls how accesses to the block by the program are handled. 'addr' is the start of the usable block (ie. after any redzone), 'sizeB' is its size. 'rzB' is the redzone size if the allocator can apply redzones -- these are blocks of padding at the start and end of each block. Adding redzones is recommended as it makes it much more likely Valgrind will spot block overruns. `is_zeroed' indicates if the memory is zeroed (or filled with another predictable value), as is the case for calloc(). VALGRIND_MALLOCLIKE_BLOCK should be put immediately after the point where a heap block -- that will be used by the client program -- is allocated. It's best to put it at the outermost level of the allocator if possible; for example, if you have a function my_alloc() which calls internal_alloc(), and the client request is put inside internal_alloc(), stack traces relating to the heap block will contain entries for both my_alloc() and internal_alloc(), which is probably not what you want. For Memcheck users: if you use VALGRIND_MALLOCLIKE_BLOCK to carve out custom blocks from within a heap block, B, that has been allocated with malloc/calloc/new/etc, then block B will be *ignored* during leak-checking -- the custom blocks will take precedence. VALGRIND_FREELIKE_BLOCK is the partner to VALGRIND_MALLOCLIKE_BLOCK. For Memcheck, it does two things: - It records that the block has been deallocated. This assumes that the block was annotated as having been allocated via VALGRIND_MALLOCLIKE_BLOCK. Otherwise, an error will be issued. - It marks the block as being unaddressable. VALGRIND_FREELIKE_BLOCK should be put immediately after the point where a heap block is deallocated. VALGRIND_RESIZEINPLACE_BLOCK informs a tool about reallocation. For Memcheck, it does four things: - It records that the size of a block has been changed. This assumes that the block was annotated as having been allocated via VALGRIND_MALLOCLIKE_BLOCK. Otherwise, an error will be issued. - If the block shrunk, it marks the freed memory as being unaddressable. - If the block grew, it marks the new area as undefined and defines a red zone past the end of the new block. - The V-bits of the overlap between the old and the new block are preserved. VALGRIND_RESIZEINPLACE_BLOCK should be put after allocation of the new block and before deallocation of the old block. In many cases, these three client requests will not be enough to get your allocator working well with Memcheck. More specifically, if your allocator writes to freed blocks in any way then a VALGRIND_MAKE_MEM_UNDEFINED call will be necessary to mark the memory as addressable just before the zeroing occurs, otherwise you'll get a lot of invalid write errors. For example, you'll need to do this if your allocator recycles freed blocks, but it zeroes them before handing them back out (via VALGRIND_MALLOCLIKE_BLOCK). Alternatively, if your allocator reuses freed blocks for allocator-internal data structures, VALGRIND_MAKE_MEM_UNDEFINED calls will also be necessary. Really, what's happening is a blurring of the lines between the client program and the allocator... after VALGRIND_FREELIKE_BLOCK is called, the memory should be considered unaddressable to the client program, but the allocator knows more than the rest of the client program and so may be able to safely access it. Extra client requests are necessary for Valgrind to understand the distinction between the allocator and the rest of the program. Ignored if addr == 0. */ #define VALGRIND_MALLOCLIKE_BLOCK(addr, sizeB, rzB, is_zeroed) \ VALGRIND_DO_CLIENT_REQUEST_STMT(VG_USERREQ__MALLOCLIKE_BLOCK, \ addr, sizeB, rzB, is_zeroed, 0) /* See the comment for VALGRIND_MALLOCLIKE_BLOCK for details. Ignored if addr == 0. */ #define VALGRIND_RESIZEINPLACE_BLOCK(addr, oldSizeB, newSizeB, rzB) \ VALGRIND_DO_CLIENT_REQUEST_STMT(VG_USERREQ__RESIZEINPLACE_BLOCK, \ addr, oldSizeB, newSizeB, rzB, 0) /* See the comment for VALGRIND_MALLOCLIKE_BLOCK for details. Ignored if addr == 0. */ #define VALGRIND_FREELIKE_BLOCK(addr, rzB) \ VALGRIND_DO_CLIENT_REQUEST_STMT(VG_USERREQ__FREELIKE_BLOCK, \ addr, rzB, 0, 0, 0) /* Create a memory pool. */ #define VALGRIND_CREATE_MEMPOOL(pool, rzB, is_zeroed) \ VALGRIND_DO_CLIENT_REQUEST_STMT(VG_USERREQ__CREATE_MEMPOOL, \ pool, rzB, is_zeroed, 0, 0) /* Create a memory pool with some flags specifying extended behaviour. When flags is zero, the behaviour is identical to VALGRIND_CREATE_MEMPOOL. The flag VALGRIND_MEMPOOL_METAPOOL specifies that the pieces of memory associated with the pool using VALGRIND_MEMPOOL_ALLOC will be used by the application as superblocks to dole out MALLOC_LIKE blocks using VALGRIND_MALLOCLIKE_BLOCK. In other words, a meta pool is a "2 levels" pool : first level is the blocks described by VALGRIND_MEMPOOL_ALLOC. The second level blocks are described using VALGRIND_MALLOCLIKE_BLOCK. Note that the association between the pool and the second level blocks is implicit : second level blocks will be located inside first level blocks. It is necessary to use the VALGRIND_MEMPOOL_METAPOOL flag for such 2 levels pools, as otherwise valgrind will detect overlapping memory blocks, and will abort execution (e.g. during leak search). Such a meta pool can also be marked as an 'auto free' pool using the flag VALGRIND_MEMPOOL_AUTO_FREE, which must be OR-ed together with the VALGRIND_MEMPOOL_METAPOOL. For an 'auto free' pool, VALGRIND_MEMPOOL_FREE will automatically free the second level blocks that are contained inside the first level block freed with VALGRIND_MEMPOOL_FREE. In other words, calling VALGRIND_MEMPOOL_FREE will cause implicit calls to VALGRIND_FREELIKE_BLOCK for all the second level blocks included in the first level block. Note: it is an error to use the VALGRIND_MEMPOOL_AUTO_FREE flag without the VALGRIND_MEMPOOL_METAPOOL flag. */ #define VALGRIND_MEMPOOL_AUTO_FREE 1 #define VALGRIND_MEMPOOL_METAPOOL 2 #define VALGRIND_CREATE_MEMPOOL_EXT(pool, rzB, is_zeroed, flags) \ VALGRIND_DO_CLIENT_REQUEST_STMT(VG_USERREQ__CREATE_MEMPOOL, \ pool, rzB, is_zeroed, flags, 0) /* Destroy a memory pool. */ #define VALGRIND_DESTROY_MEMPOOL(pool) \ VALGRIND_DO_CLIENT_REQUEST_STMT(VG_USERREQ__DESTROY_MEMPOOL, \ pool, 0, 0, 0, 0) /* Associate a piece of memory with a memory pool. */ #define VALGRIND_MEMPOOL_ALLOC(pool, addr, size) \ VALGRIND_DO_CLIENT_REQUEST_STMT(VG_USERREQ__MEMPOOL_ALLOC, \ pool, addr, size, 0, 0) /* Disassociate a piece of memory from a memory pool. */ #define VALGRIND_MEMPOOL_FREE(pool, addr) \ VALGRIND_DO_CLIENT_REQUEST_STMT(VG_USERREQ__MEMPOOL_FREE, \ pool, addr, 0, 0, 0) /* Disassociate any pieces outside a particular range. */ #define VALGRIND_MEMPOOL_TRIM(pool, addr, size) \ VALGRIND_DO_CLIENT_REQUEST_STMT(VG_USERREQ__MEMPOOL_TRIM, \ pool, addr, size, 0, 0) /* Resize and/or move a piece associated with a memory pool. */ #define VALGRIND_MOVE_MEMPOOL(poolA, poolB) \ VALGRIND_DO_CLIENT_REQUEST_STMT(VG_USERREQ__MOVE_MEMPOOL, \ poolA, poolB, 0, 0, 0) /* Resize and/or move a piece associated with a memory pool. */ #define VALGRIND_MEMPOOL_CHANGE(pool, addrA, addrB, size) \ VALGRIND_DO_CLIENT_REQUEST_STMT(VG_USERREQ__MEMPOOL_CHANGE, \ pool, addrA, addrB, size, 0) /* Return 1 if a mempool exists, else 0. */ #define VALGRIND_MEMPOOL_EXISTS(pool) \ (unsigned)VALGRIND_DO_CLIENT_REQUEST_EXPR(0, \ VG_USERREQ__MEMPOOL_EXISTS, \ pool, 0, 0, 0, 0) /* Mark a piece of memory as being a stack. Returns a stack id. start is the lowest addressable stack byte, end is the highest addressable stack byte. */ #define VALGRIND_STACK_REGISTER(start, end) \ (unsigned)VALGRIND_DO_CLIENT_REQUEST_EXPR(0, \ VG_USERREQ__STACK_REGISTER, \ start, end, 0, 0, 0) /* Unmark the piece of memory associated with a stack id as being a stack. */ #define VALGRIND_STACK_DEREGISTER(id) \ VALGRIND_DO_CLIENT_REQUEST_STMT(VG_USERREQ__STACK_DEREGISTER, \ id, 0, 0, 0, 0) /* Change the start and end address of the stack id. start is the new lowest addressable stack byte, end is the new highest addressable stack byte. */ #define VALGRIND_STACK_CHANGE(id, start, end) \ VALGRIND_DO_CLIENT_REQUEST_STMT(VG_USERREQ__STACK_CHANGE, \ id, start, end, 0, 0) /* Load PDB debug info for Wine PE image_map. */ #define VALGRIND_LOAD_PDB_DEBUGINFO(fd, ptr, total_size, delta) \ VALGRIND_DO_CLIENT_REQUEST_STMT(VG_USERREQ__LOAD_PDB_DEBUGINFO, \ fd, ptr, total_size, delta, 0) /* Map a code address to a source file name and line number. buf64 must point to a 64-byte buffer in the caller's address space. The result will be dumped in there and is guaranteed to be zero terminated. If no info is found, the first byte is set to zero. */ #define VALGRIND_MAP_IP_TO_SRCLOC(addr, buf64) \ (unsigned)VALGRIND_DO_CLIENT_REQUEST_EXPR(0, \ VG_USERREQ__MAP_IP_TO_SRCLOC, \ addr, buf64, 0, 0, 0) /* Disable error reporting for this thread. Behaves in a stack like way, so you can safely call this multiple times provided that VALGRIND_ENABLE_ERROR_REPORTING is called the same number of times to re-enable reporting. The first call of this macro disables reporting. Subsequent calls have no effect except to increase the number of VALGRIND_ENABLE_ERROR_REPORTING calls needed to re-enable reporting. Child threads do not inherit this setting from their parents -- they are always created with reporting enabled. */ #define VALGRIND_DISABLE_ERROR_REPORTING \ VALGRIND_DO_CLIENT_REQUEST_STMT(VG_USERREQ__CHANGE_ERR_DISABLEMENT, \ 1, 0, 0, 0, 0) /* Re-enable error reporting, as per comments on VALGRIND_DISABLE_ERROR_REPORTING. */ #define VALGRIND_ENABLE_ERROR_REPORTING \ VALGRIND_DO_CLIENT_REQUEST_STMT(VG_USERREQ__CHANGE_ERR_DISABLEMENT, \ -1, 0, 0, 0, 0) /* Execute a monitor command from the client program. If a connection is opened with GDB, the output will be sent according to the output mode set for vgdb. If no connection is opened, output will go to the log output. Returns 1 if command not recognised, 0 otherwise. */ #define VALGRIND_MONITOR_COMMAND(command) \ VALGRIND_DO_CLIENT_REQUEST_EXPR(0, VG_USERREQ__GDB_MONITOR_COMMAND, \ command, 0, 0, 0, 0) #undef PLAT_x86_darwin #undef PLAT_amd64_darwin #undef PLAT_x86_win32 #undef PLAT_amd64_win64 #undef PLAT_x86_linux #undef PLAT_amd64_linux #undef PLAT_ppc32_linux #undef PLAT_ppc64be_linux #undef PLAT_ppc64le_linux #undef PLAT_arm_linux #undef PLAT_s390x_linux #undef PLAT_mips32_linux #undef PLAT_mips64_linux #undef PLAT_x86_solaris #undef PLAT_amd64_solaris #endif /* __VALGRIND_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/meson.build000066400000000000000000000601171511204443500226450ustar00rootroot00000000000000project('pipewire', ['c' ], version : '1.5.84', license : [ 'MIT', 'LGPL-2.1-or-later', 'GPL-2.0-only' ], meson_version : '>= 0.61.1', default_options : [ 'warning_level=3', 'c_std=gnu11', 'cpp_std=c++20', 'b_pie=true', #'b_sanitize=address,undefined', 'buildtype=debugoptimized' ]) pipewire_version = meson.project_version() version_arr = pipewire_version.split('.') pipewire_version_major = version_arr[0] pipewire_version_minor = version_arr[1] pipewire_version_micro = version_arr[2] if version_arr.length() == 4 pipewire_version_nano = version_arr[3] else pipewire_version_nano = 0 endif spaversion = '0.2' apiversion = '0.3' soversion = 0 libversion_minor = pipewire_version_major.to_int() * 1000 + pipewire_version_minor.to_int() * 100 + pipewire_version_micro.to_int() libversion = '@0@.@1@.0'.format(soversion, libversion_minor) # LADI/jack # 3, for PipeWire being the third JACK implementation, after JACK1 and jackdmp/JACK2) jack_version_major = 3 jack_version_minor = libversion_minor # libjack[server] version has 0 for major (for compatibility with other implementations), # 3 for minor, and "1000*major + 100*minor + micro" as micro version (the minor libpipewire soversion number) libjackversion = '@0@.@1@.@2@'.format(soversion, jack_version_major, jack_version_minor) # jack[server] version has 3 for major # and pipewire's "1000*major + 100*minor + micro" as minor version jackversion = '@0@.@1@.@2@'.format(jack_version_major, jack_version_minor, 0) pipewire_name = 'pipewire-@0@'.format(apiversion) spa_name = 'spa-@0@'.format(spaversion) prefix = get_option('prefix') pipewire_bindir = prefix / get_option('bindir') pipewire_datadir = prefix / get_option('datadir') pipewire_libdir = prefix / get_option('libdir') pipewire_libexecdir = prefix / get_option('libexecdir') pipewire_localedir = prefix / get_option('localedir') pipewire_sysconfdir = prefix / get_option('sysconfdir') pipewire_configdir = pipewire_sysconfdir / 'pipewire' pipewire_confdatadir = pipewire_datadir / 'pipewire' modules_install_dir = pipewire_libdir / pipewire_name cc = meson.get_compiler('c') cc_native = meson.get_compiler('c', native: true) if cc.has_header('features.h') and cc.get_define('__GLIBC__', prefix: '#include ') != '' # glibc ld.so interprets ${LIB} in a library loading path with an # appropriate value for the current architecture, typically something # like lib, lib64 or lib/x86_64-linux-gnu. # This allows the same pw-jack script to work for both 32- and 64-bit # applications on biarch/multiarch distributions, by setting something # like LD_LIBRARY_PATH='/usr/${LIB}/pipewire-0.3/jack'. # Note that ${LIB} is a special token expanded by the runtime linker, # not an environment variable, and must be passed through literally. modules_install_dir_dlopen = prefix / '${LIB}' / pipewire_name else modules_install_dir_dlopen = modules_install_dir endif spa_plugindir = pipewire_libdir / spa_name spa_datadir = pipewire_datadir / spa_name alsadatadir = pipewire_datadir / 'alsa-card-profile' / 'mixer' pipewire_headers_dir = pipewire_name / 'pipewire' pkgconfig = import('pkgconfig') common_flags = [ '-fvisibility=hidden', '-fno-strict-aliasing', '-fno-strict-overflow', '-Werror=suggest-attribute=format', '-Wsign-compare', '-Wpointer-arith', '-Wpointer-sign', '-Werror=format', '-Wno-error=format-overflow', # avoid some "‘%s’ directive argument is null" '-Wformat-security', '-Wimplicit-fallthrough', '-Wmissing-braces', '-Wtype-limits', '-Wvariadic-macros', '-Wmaybe-uninitialized', '-Wno-missing-field-initializers', '-Wno-unused-parameter', '-Wno-pedantic', '-Wdeprecated-declarations', '-Wunused-result', '-Werror=return-type', '-Werror=float-conversion', '-Werror=constant-conversion', ] cc_flags = common_flags + [ '-D_GNU_SOURCE', '-DFASTPATH', # '-DSPA_DEBUG_MEMCPY', '-Werror=implicit-function-declaration', '-Werror=incompatible-pointer-types', '-Werror=int-conversion', '-Werror=old-style-declaration', '-Werror=old-style-definition', '-Werror=missing-parameter-type', '-Werror=strict-prototypes', '-DSPA_AUDIO_MAX_CHANNELS=128u', ] add_project_arguments(cc.get_supported_arguments(cc_flags), language: 'c') add_project_arguments(cc_native.get_supported_arguments(cc_flags), language: 'c', native: true) have_cpp = add_languages('cpp', native: false, required : false) if have_cpp cxx = meson.get_compiler('cpp') cxx_flags = common_flags + [ '-Wno-c99-designator' ] add_project_arguments(cxx.get_supported_arguments(cxx_flags), language: 'cpp') endif have_sse = false have_sse2 = false have_ssse3 = false have_sse41 = false have_fma = false have_avx = false have_avx2 = false if host_machine.cpu_family() in ['x86', 'x86_64'] sse_args = '-msse' sse2_args = '-msse2' ssse3_args = '-mssse3' sse41_args = '-msse4.1' fma_args = '-mfma' avx_args = '-mavx' avx2_args = '-mavx2' have_sse = cc.has_argument(sse_args) have_sse2 = cc.has_argument(sse2_args) have_ssse3 = cc.has_argument(ssse3_args) have_sse41 = cc.has_argument(sse41_args) have_fma = cc.has_argument(fma_args) have_avx = cc.has_argument(avx_args) have_avx2 = cc.has_argument(avx2_args) endif have_neon = false if host_machine.cpu_family() == 'aarch64' if cc.compiles(''' #include int main () { float *s; asm volatile( " ld1 { v0.4s }, [%[s]], #16\n" " fcvtzs v0.4s, v0.4s, #31\n" : [s] "+r" (s) : :); } ''', name : 'aarch64 Neon Support') neon_args = [] have_neon = true endif elif cc.has_argument('-mfpu=neon') if cc.compiles(''' #include int main () { float *s; asm volatile( " vld1.32 { q0 }, [%[s]]!\n" " vcvt.s32.f32 q0, q0, #31\n" : [s] "+r" (s) : :); } ''', args: '-mfpu=neon', name : 'arm Neon Support') neon_args = ['-mfpu=neon'] have_neon = true endif endif have_rvv = false if host_machine.cpu_family() == 'riscv64' if cc.compiles(''' int main() { __asm__ __volatile__ ( ".option arch, +v\nvsetivli zero, 0, e8, m1, ta, ma" ); } ''', name : 'riscv64 V Support') have_rvv = true endif endif libatomic = cc.find_library('atomic', required : false) test_8_byte_atomic = ''' #include int main(void) { int64_t eight; __atomic_fetch_add(&eight, 123, __ATOMIC_SEQ_CST); return 0; } ''' # We currently assume that libatomic is unnecessary for 4-byte atomic # operations on any reasonable architecture. if cc.links( test_8_byte_atomic, name : '8-byte __atomic_fetch_add without libatomic') atomic_dep = dependency('', required: false) elif cc.links( test_8_byte_atomic, dependencies : libatomic, name : '8-byte __atomic_fetch_add with libatomic') atomic_dep = libatomic else error('8-byte atomic operations are required') endif versiondata = configuration_data() versiondata.set('PIPEWIRE_VERSION_MAJOR', pipewire_version_major) versiondata.set('PIPEWIRE_VERSION_MINOR', pipewire_version_minor) versiondata.set('PIPEWIRE_VERSION_MICRO', pipewire_version_micro) versiondata.set('PIPEWIRE_VERSION_NANO', pipewire_version_nano) versiondata.set_quoted('PIPEWIRE_API_VERSION', apiversion) cdata = configuration_data() cdata.set_quoted('PREFIX', prefix) cdata.set_quoted('PIPEWIRE_CONFDATADIR', pipewire_confdatadir) cdata.set_quoted('LOCALEDIR', pipewire_localedir) cdata.set_quoted('LIBDIR', pipewire_libdir) cdata.set_quoted('GETTEXT_PACKAGE', meson.project_name()) cdata.set_quoted('PACKAGE', 'pipewire') cdata.set_quoted('PACKAGE_NAME', 'PipeWire') cdata.set_quoted('PACKAGE_STRING', 'PipeWire @0@'.format(pipewire_version)) cdata.set_quoted('PACKAGE_TARNAME', 'pipewire') cdata.set_quoted('PACKAGE_URL', 'https://pipewire.org') cdata.set_quoted('PACKAGE_VERSION', pipewire_version) cdata.set_quoted('MODULEDIR', modules_install_dir) cdata.set_quoted('PIPEWIRE_CONFIG_DIR', pipewire_configdir) cdata.set_quoted('PLUGINDIR', spa_plugindir) cdata.set_quoted('SPADATADIR', spa_datadir) cdata.set_quoted('PA_ALSA_DATA_DIR', alsadatadir) cdata.set('RTPRIO_SERVER', get_option('rtprio-server')) cdata.set('RTPRIO_CLIENT', get_option('rtprio-client')) if host_machine.endian() == 'big' cdata.set('WORDS_BIGENDIAN', 1) endif check_headers = [ ['sys/auxv.h', 'HAVE_SYS_AUXV_H'], ['sys/mount.h', 'HAVE_SYS_MOUNT_H'], ['sys/param.h', 'HAVE_SYS_PARAM_H'], ['sys/random.h', 'HAVE_SYS_RANDOM_H'], ['sys/vfs.h', 'HAVE_SYS_VFS_H'], ['pwd.h', 'HAVE_PWD_H'], ['grp.h', 'HAVE_GRP_H'], ] foreach h : check_headers cdata.set(h.get(1), cc.has_header(h.get(0))) endforeach cdata.set('HAVE_PIDFD_OPEN', cc.get_define('SYS_pidfd_open', prefix: '#include ') != '') systemd_dep = dependency('libsystemd', required: get_option('libsystemd')) summary({'libsystemd': systemd_dep.found()}, bool_yn: true) cdata.set('HAVE_SYSTEMD', systemd_dep.found()) logind_dep = dependency(get_option('logind-provider'), required: get_option('logind')) summary({'logind': logind_dep.found()}, bool_yn: true) cdata.set('HAVE_LOGIND', logind_dep.found()) selinux_dep = dependency('libselinux', required: get_option('selinux')) summary({'libselinux': selinux_dep.found()}, bool_yn: true) cdata.set('HAVE_SELINUX', selinux_dep.found()) configinc = include_directories('.') includes_inc = include_directories('include') pipewire_inc = include_directories('src') makedata = configuration_data() makedata.set('BUILD_ROOT', meson.project_build_root()) makedata.set('SOURCE_ROOT', meson.project_source_root()) makedata.set('VERSION', pipewire_version) if version_arr.length() == 4 makedata.set('TAG', 'HEAD') else makedata.set('TAG', pipewire_version) endif configure_file(input : 'Makefile.in', output : 'Makefile', configuration : makedata) # Find dependencies mathlib = cc.find_library('m', required : false) mathlib_native = cc_native.find_library('m', required : false) rt_lib = cc.find_library('rt', required : false) # clock_gettime dl_lib = cc.find_library('dl', required : false) pthread_lib = dependency('threads') dbus_dep = dependency('dbus-1', required : get_option('dbus')) summary({'dbus (Bluetooth, rt, portal, pw-reserve)': dbus_dep.found()}, bool_yn: true, section: 'Misc dependencies') cdata.set('HAVE_DBUS', dbus_dep.found()) sdl_dep = dependency('sdl2', required : get_option('sdl2')) summary({'SDL2 (video examples)': sdl_dep.found()}, bool_yn: true, section: 'Misc dependencies') drm_dep = dependency('libdrm', required : false) fftw_dep = dependency('fftw3f', required : get_option('fftw')) summary({'fftw3f (filter-chain convolver)': fftw_dep.found()}, bool_yn: true, section: 'Misc dependencies') cdata.set('HAVE_FFTW', fftw_dep.found()) if get_option('readline').disabled() readline_dep = dependency('', required: false) else readline_dep = dependency('readline', required : false) if not readline_dep.found() readline_dep = cc.find_library('readline', required : get_option('readline')) endif endif # Both the FFmpeg SPA plugin and the pw-cat FFmpeg integration use libavcodec. # But only the latter also needs libavformat and libavutil. # Search for these libraries here, globally, so both of these subprojects can reuse the results. pw_cat_ffmpeg = get_option('pw-cat-ffmpeg') ffmpeg = get_option('ffmpeg') if pw_cat_ffmpeg.allowed() or ffmpeg.allowed() # libswscale is only used by videoconvert. FFmpeg might however be used for # compressed audio (both for decoding said compressed audio and for parsing # it in pw-cat). If users only care about audio, then libswscale would still # become a requirement if its required flag is defined only by FFmpeg options. # Make the videoconvert option a factor in swscale_dep as well to avoid this. videoconvert = get_option('videoconvert') avcodec_dep = dependency('libavcodec', required: pw_cat_ffmpeg.enabled() or ffmpeg.enabled()) avformat_dep = dependency('libavformat', required: pw_cat_ffmpeg.enabled()) avfilter_dep = dependency('libavfilter', required: ffmpeg.enabled()) avutil_dep = dependency('libavutil', required: pw_cat_ffmpeg.enabled() or ffmpeg.enabled()) swscale_dep = dependency('libswscale', required: (pw_cat_ffmpeg.enabled() or ffmpeg.enabled()) and videoconvert.enabled()) else avcodec_dep = dependency('', required: false) avformat_dep = dependency('', required: false) avfilter_dep = dependency('', required: false) avutil_dep = dependency('', required: false) swscale_dep = dependency('', required: false) endif cdata.set('HAVE_PW_CAT_FFMPEG_INTEGRATION', pw_cat_ffmpeg.allowed()) opus_dep = dependency('opus', required : get_option('opus')) summary({'opus (Bluetooth, RTP)': opus_dep.found()}, bool_yn: true, section: 'Misc dependencies') cdata.set('HAVE_OPUS', opus_dep.found()) summary({'readline (for pw-cli)': readline_dep.found()}, bool_yn: true, section: 'Misc dependencies') cdata.set('HAVE_READLINE', readline_dep.found()) ncurses_dep = dependency('ncursesw', required : false) sndfile_dep = dependency('sndfile', version : '>= 1.0.20', required : get_option('sndfile')) summary({'sndfile': sndfile_dep.found()}, bool_yn: true, section: 'pw-cat/pw-play/pw-dump/filter-chain') cdata.set('HAVE_SNDFILE', sndfile_dep.found()) pulseaudio_dep = dependency('libpulse', required : get_option('libpulse')) summary({'libpulse': pulseaudio_dep.found()}, bool_yn: true, section: 'Streaming between daemons') avahi_dep = dependency('avahi-client', required : get_option('avahi')) summary({'Avahi DNS-SD (Zeroconf)': avahi_dep.found()}, bool_yn: true, section: 'Streaming between daemons') x11_dep = dependency('x11-xcb', required : get_option('x11')) summary({'X11 (x11-bell)': x11_dep.found()}, bool_yn: true, section: 'Misc dependencies') xfixes_dep = dependency('xfixes', required : get_option('x11-xfixes'), version: '>= 6') cdata.set('HAVE_XFIXES_6', xfixes_dep.found()) canberra_dep = dependency('libcanberra', required : get_option('libcanberra')) summary({'libcanberra (x11-bell)': canberra_dep.found()}, bool_yn: true, section: 'Misc dependencies') libusb_dep = dependency('libusb-1.0', required : get_option('libusb')) summary({'libusb (Bluetooth quirks)': libusb_dep.found()}, bool_yn: true, section: 'Backend') cdata.set('HAVE_LIBUSB', libusb_dep.found()) glib2_dep = dependency('glib-2.0', required : get_option('flatpak')) summary({'GLib-2.0 (Flatpak support)': glib2_dep.found()}, bool_yn: true, section: 'Misc dependencies') flatpak_support = glib2_dep.found() cdata.set('HAVE_GLIB2', flatpak_support) gsettings_gio_dep = dependency('gio-2.0', version : '>= 2.26.0', required : get_option('gsettings')) summary({'GIO (GSettings)': gsettings_gio_dep.found()}, bool_yn: true, section: 'Misc dependencies') if not gsettings_gio_dep.found() and get_option('gsettings-pulse-schema').enabled() error('`gsettings-pulse-schema` is enabled but `gio` was not found.') endif gst_option = get_option('gstreamer') gst_deps_def = { 'glib-2.0': {'version': '>=2.32.0'}, 'gobject-2.0': {}, 'gmodule-2.0': {}, 'gio-2.0': {}, 'gio-unix-2.0': {}, 'gstreamer-1.0': {'version': '>= 1.10.0'}, 'gstreamer-base-1.0': {}, 'gstreamer-video-1.0': {}, 'gstreamer-audio-1.0': {}, 'gstreamer-allocators-1.0': {}, } gst_dep = [] gst_dma_drm_found = false gst_shm_allocator_found = false foreach depname, kwargs: gst_deps_def dep = dependency(depname, required: gst_option, kwargs: kwargs) summary({depname: dep.found()}, bool_yn: true, section: 'GStreamer modules') if not dep.found() # Beware, there's logic below depending on the array clear here! gst_dep = [] if get_option('gstreamer-device-provider').enabled() error('`gstreamer-device-provider` is enabled but `@0@` was not found.'.format(depname)) endif break endif gst_dep += [dep] if depname == 'gstreamer-allocators-1.0' and dep.version().version_compare('>= 1.23.1') gst_dma_drm_found = true gst_shm_allocator_found = true endif endforeach summary({'gstreamer SHM allocator': gst_shm_allocator_found}, bool_yn: true, section: 'Backend') cdata.set('HAVE_GSTREAMER_SHM_ALLOCATOR', gst_shm_allocator_found) # This code relies on the array being empty if any dependency was not found gst_dp_found = gst_dep.length() > 0 summary({'gstreamer-device-provider': gst_dp_found}, bool_yn: true, section: 'Backend') cdata.set('HAVE_GSTREAMER_DEVICE_PROVIDER', get_option('gstreamer-device-provider').allowed()) summary({'gstreamer DMA_DRM support': gst_dma_drm_found}, bool_yn: true, section: 'Backend') cdata.set('HAVE_GSTREAMER_DMA_DRM', gst_dma_drm_found) if get_option('echo-cancel-webrtc').disabled() webrtc_dep = dependency('', required: false) summary({'WebRTC Echo Canceling': webrtc_dep.found()}, bool_yn: false, section: 'Misc dependencies') else webrtc_dep = dependency('webrtc-audio-processing-2', version : ['>= 2.0' ], required : false) cdata.set('HAVE_WEBRTC2', webrtc_dep.found()) if webrtc_dep.found() summary({'WebRTC Echo Canceling >= 2.0': webrtc_dep.found()}, bool_yn: true, section: 'Misc dependencies') else webrtc_dep = dependency('webrtc-audio-processing-1', version : ['>= 1.2' ], required : false) cdata.set('HAVE_WEBRTC1', webrtc_dep.found()) if webrtc_dep.found() summary({'WebRTC Echo Canceling >= 1.2': webrtc_dep.found()}, bool_yn: true, section: 'Misc dependencies') else webrtc_dep = dependency('webrtc-audio-processing', version : ['>= 0.2', '< 1.0'], required : false) cdata.set('HAVE_WEBRTC', webrtc_dep.found()) if webrtc_dep.found() summary({'WebRTC Echo Canceling < 1.0': webrtc_dep.found()}, bool_yn: true, section: 'Misc dependencies') else # If deps are not found on the system but it's enabled, try to fallback to the subproject webrtc_dep = dependency('webrtc-audio-processing-2', version : ['>= 2.0' ], required : get_option('echo-cancel-webrtc')) cdata.set('HAVE_WEBRTC2', webrtc_dep.found()) summary({'WebRTC Echo Canceling > 2.0': webrtc_dep.found()}, bool_yn: true, section: 'Misc dependencies') endif endif endif endif # On FreeBSD and MidnightBSD, epoll-shim library is required for eventfd() and timerfd() epoll_shim_dep = (host_machine.system() == 'freebsd' or host_machine.system() == 'midnightbsd' ? dependency('epoll-shim', required: true) : dependency('', required: false)) libinotify_dep = (host_machine.system() == 'freebsd' or host_machine.system() == 'midnightbsd' ? dependency('libinotify', required: true) : dependency('', required: false)) # On FreeBSD and MidnightBSD, libintl library is required for gettext libintl_dep = cc.find_library('intl', required: false) if not libintl_dep.found() libintl_dep = dependency('intl', required: false) endif summary({'intl support': libintl_dep.found()}, bool_yn: true) need_alsa = get_option('pipewire-alsa').enabled() or 'media-session' in get_option('session-managers') alsa_dep = dependency('alsa', version : '>=1.2.6', required: need_alsa) summary({'pipewire-alsa': alsa_dep.found()}, bool_yn: true) if host_machine.system() == 'freebsd' or host_machine.system() == 'midnightbsd' # On FreeBSD and MidnightBSD the OpenSSL library may come from base or a package. # Check for a package first and fallback to the base library if we can't find it via pkgconfig openssl_lib = dependency('openssl', required: false) if not openssl_lib.found() openssl_lib = declare_dependency(link_args : [ '-lssl', '-lcrypto']) endif else openssl_lib = dependency('openssl', required: get_option('raop')) endif summary({'OpenSSL (for raop-sink)': openssl_lib.found()}, bool_yn: true) libffado_dep = dependency('libffado', required: get_option('libffado')) summary({'ffado': libffado_dep.found()}, bool_yn: true) glib2_snap_dep = dependency('glib-2.0', required : get_option('snap')) gio2_snap_dep = dependency('gio-2.0', required : get_option('snap')) apparmor_snap_dep = dependency('libapparmor', required : get_option('snap')) if dependency('snapd-glib-2', required: false).found() snap_dep = dependency('snapd-glib-2', required : get_option('snap')) else snap_dep = dependency('snapd-glib', required : get_option('snap')) endif if snap_dep.found() and glib2_snap_dep.found() and gio2_snap_dep.found() and apparmor_snap_dep.found() cdata.set('HAVE_SNAP', true) snap_deps = [glib2_snap_dep, gio2_snap_dep, snap_dep, apparmor_snap_dep] endif summary({'GLib-2.0 (Snap support)': glib2_snap_dep.found()}, bool_yn: true, section: 'Misc dependencies') summary({'Gio-2.0 (Snap support)': gio2_snap_dep.found()}, bool_yn: true, section: 'Misc dependencies') summary({'Apparmor (Snap support)': apparmor_snap_dep.found()}, bool_yn: true, section: 'Misc dependencies') summary({'Snapd-glib (Snap support)': snap_dep.found()}, bool_yn: true, section: 'Misc dependencies') check_functions = [ ['gettid', '#include ', ['-D_GNU_SOURCE'], []], ['memfd_create', '#include ', ['-D_GNU_SOURCE'], []], ['getrandom', '#include \n#include ', ['-D_GNU_SOURCE'], []], ['random_r', '#include ', ['-D_GNU_SOURCE'], []], ['reallocarray', '#include ', ['-D_GNU_SOURCE'], []], ['sigabbrev_np', '#include ', ['-D_GNU_SOURCE'], []], ['XSetIOErrorExitHandler', '#include ', [], [x11_dep]], ['malloc_trim', '#include ', [], []], ['malloc_info', '#include ', [], []], ] foreach f : check_functions cdata.set('HAVE_' + f.get(0).to_upper(), cc.has_function(f.get(0), prefix: f.get(1), args: f.get(2), dependencies: f.get(3))) endforeach installed_tests_metadir = pipewire_datadir / 'installed-tests' / pipewire_name installed_tests_execdir = pipewire_libexecdir / 'installed-tests' / pipewire_name installed_tests_enabled = get_option('installed_tests').allowed() installed_tests_template = files('template.test.in') if get_option('tests').allowed() gstack = find_program('gstack', required : false) cdata.set('HAVE_GSTACK', gstack.found()) endif subdir('po') subdir('spa') subdir('src') if get_option('tests').allowed() subdir('test') endif configure_file(output : 'config.h', configuration : cdata) if get_option('pipewire-jack').allowed() subdir('pipewire-jack') endif if get_option('pipewire-v4l2').allowed() subdir('pipewire-v4l2') endif if alsa_dep.found() subdir('pipewire-alsa/alsa-plugins') subdir('pipewire-alsa/conf') subdir('pipewire-alsa/tests') endif generate_docs = get_option('man').enabled() or get_option('docs').enabled() if get_option('man').allowed() or get_option('docs').allowed() doxygen = find_program('doxygen', required : generate_docs, version : '>=1.9') pymod = import('python') python = pymod.find_installation('python3', required: generate_docs) generate_docs = doxygen.found() and python.found() endif install_docs = get_option('docs').require(generate_docs).allowed() install_man = get_option('man').require(generate_docs).allowed() summary({'Documentation ': install_docs}, bool_yn: true) summary({'Man pages ': install_man}, bool_yn: true) if generate_docs subdir('doc') endif setenv = find_program('pw-uninstalled.sh') run_target('pw-uninstalled', command : [setenv, '-b@0@'.format(meson.project_build_root()), '-v@0@'.format(pipewire_version)] ) devenv = environment() builddir = meson.project_build_root() srcdir = meson.project_source_root() devenv.set('PIPEWIRE_CONFIG_DIR', pipewire_dep.get_variable('confdatadir')) devenv.set('PIPEWIRE_MODULE_DIR', pipewire_dep.get_variable('moduledir')) devenv.set('SPA_PLUGIN_DIR', spa_dep.get_variable('plugindir')) devenv.set('SPA_DATA_DIR', spa_dep.get_variable('datadir')) devenv.set('ACP_PATHS_DIR', srcdir / 'spa' / 'plugins' / 'alsa' / 'mixer' / 'paths') devenv.set('ACP_PROFILES_DIR', srcdir / 'spa' / 'plugins' / 'alsa' / 'mixer' / 'profile-sets') devenv.prepend('GST_PLUGIN_PATH', builddir / 'src'/ 'gst') devenv.prepend('ALSA_PLUGIN_DIR', builddir / 'pipewire-alsa' / 'alsa-plugins') devenv.prepend('LD_LIBRARY_PATH', builddir / 'pipewire-jack' / 'src') devenv.set('PIPEWIRE_LOG_SYSTEMD', 'false') devenv.set('PW_UNINSTALLED', '1') devenv.set('PW_BUILDDIR', meson.project_build_root()) meson.add_devenv(devenv) pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/meson_options.txt000066400000000000000000000323221511204443500241350ustar00rootroot00000000000000option('docdir', type : 'string', description : 'Directory for installing documentation to (defaults to pipewire_datadir/doc/meson.project_name() )') option('docs', description: 'Documentation', type: 'feature', value: 'disabled') option('man', description: 'Manual pages', type: 'feature', value: 'disabled') option('examples', description: 'Build examples', type: 'feature', value: 'enabled') option('tests', description: 'Build tests', type: 'feature', value: 'enabled', yield : true) option('installed_tests', description: 'Install manual and automated test executables', type: 'feature', value: 'disabled') option('gstreamer', description: 'Build GStreamer plugins', type: 'feature', value: 'auto') option('gstreamer-device-provider', description: 'Build GStreamer device provider plugin', type: 'feature', value: 'auto') option('libsystemd', description: 'Enable code that depends on libsystemd', type: 'feature', value: 'auto') option('logind', description: 'Enable logind integration', type: 'feature', value: 'auto') option('logind-provider', description: 'Provider for logind integration', type: 'combo', choices: ['libelogind', 'libsystemd'], value: 'libsystemd') option('systemd-system-service', description: 'Install systemd system service file', type: 'feature', value: 'disabled') option('systemd-user-service', description: 'Install systemd user service file', type: 'feature', value: 'auto') option('selinux', description: 'Enable SELinux integration', type: 'feature', value: 'auto') option('pipewire-alsa', description: 'Enable pipewire-alsa integration', type: 'feature', value: 'auto') option('pipewire-jack', description: 'Enable pipewire-jack integration', type: 'feature', value: 'enabled') option('pipewire-v4l2', description: 'Enable pipewire-v4l2 integration', type: 'feature', value: 'enabled') option('jack-devel', description: 'Install jack development files', type: 'boolean', value: false) option('libjack-path', description: 'Where to install the libjack.so library', type: 'string') option('libv4l2-path', description: 'Where to install the libpw-v4l2.so library', type: 'string') option('spa-plugins', description: 'Enable spa plugins integration', type: 'feature', value: 'enabled') option('alsa', description: 'Enable alsa spa plugin integration', type: 'feature', value: 'auto') option('audiomixer', description: 'Enable audiomixer spa plugin integration', type: 'feature', value: 'enabled') option('audioconvert', description: 'Enable audioconvert spa plugin integration', type: 'feature', value: 'enabled') option('resampler-precomp-tuples', description: 'Array of "inrate,outrate[,quality]" tuples to precompute resampler coefficients for', type: 'array', value: [ '32000,44100', '32000,48000', '48000,44100', '44100,48000' ]) option('bluez5', description: 'Enable bluez5 spa plugin integration', type: 'feature', value: 'auto') option('bluez5-backend-hsp-native', description: 'Enable HSP in native backend in bluez5 spa plugin', type: 'feature', value: 'enabled') option('bluez5-backend-hfp-native', description: 'Enable HFP in native backend in bluez5 spa plugin', type: 'feature', value: 'enabled') option('bluez5-backend-native-mm', description: 'Enable ModemManager in native backend in bluez5 spa plugin', type: 'feature', value: 'disabled') option('bluez5-backend-ofono', description: 'Enable oFono HFP backend in bluez5 spa plugin (no dependency on oFono)', type: 'feature', value: 'enabled') option('bluez5-backend-hsphfpd', description: 'Enable hsphfpd backend in bluez5 spa plugin (no dependency on hsphfpd)', type: 'feature', value: 'enabled') option('bluez5-codec-aptx', description: 'Enable AptX Qualcomm open source codec implementation', type: 'feature', value: 'auto') option('bluez5-codec-ldac', description: 'Enable LDAC Sony open source codec implementation', type: 'feature', value: 'auto') option('bluez5-codec-ldac-dec', description: 'Enable LDAC Sony open source codec decoding', type: 'feature', value: 'auto') option('bluez5-codec-aac', description: 'Enable Fraunhofer FDK AAC open source codec implementation', type: 'feature', value: 'auto') option('bluez5-codec-lc3plus', description: 'Enable LC3plus open source codec implementation', type: 'feature', value: 'auto') option('bluez5-codec-opus', description: 'Enable Opus open source codec implementation', type: 'feature', value: 'auto') option('bluez5-codec-lc3', description: 'Enable LC3 open source codec implementation', type: 'feature', value: 'auto') option('bluez5-codec-g722', description: 'Enable G722 open source codec implementation', type: 'feature', value: 'auto') option('bluez5-plc-spandsp', description: 'Enable SpanDSP for packet loss concealment', type: 'feature', value: 'auto') option('control', description: 'Enable control spa plugin integration', type: 'feature', value: 'enabled') option('audiotestsrc', description: 'Enable audiotestsrc spa plugin integration', type: 'feature', value: 'enabled') option('ffmpeg', description: 'Enable ffmpeg spa plugin integration', type: 'feature', value: 'disabled') option('jack', description: 'Enable jack spa plugin integration', type: 'feature', value: 'auto') option('support', description: 'Enable support spa plugin integration', type: 'feature', value: 'enabled') option('evl', description: 'Enable EVL support spa plugin integration', type: 'feature', value: 'disabled') option('test', description: 'Enable test spa plugin integration', type: 'feature', value: 'disabled') option('v4l2', description: 'Enable v4l2 spa plugin integration', type: 'feature', value: 'auto') option('dbus', description: 'Enable code that depends on dbus', type: 'feature', value: 'enabled') option('libcamera', description: 'Enable libcamera spa plugin integration', type: 'feature', value: 'auto') option('videoconvert', description: 'Enable videoconvert spa plugin integration', type: 'feature', value: 'enabled') option('videotestsrc', description: 'Enable videotestsrc spa plugin integration', type: 'feature', value: 'enabled') option('volume', description: 'Build the legacy volume spa plugin', type: 'feature', value: 'disabled') option('vulkan', description: 'Enable vulkan spa plugin integration', type: 'feature', value: 'disabled') option('pw-cat', description: 'Build pw-cat/pw-play/pw-record', type: 'feature', value: 'auto') option('pw-cat-ffmpeg', description: 'Enable FFmpeg integration in pw-cat/pw-play/pw-record', type: 'feature', value: 'disabled') option('udev', description: 'Enable Udev integration', type: 'feature', value: 'auto') option('udevrulesdir', type : 'string', description : 'Directory for udev rules (defaults to /lib/udev/rules.d)') option('systemd-system-unit-dir', type : 'string', description : 'Directory for system systemd units (defaults to /usr/lib/systemd/system)') option('systemd-user-unit-dir', type : 'string', description : 'Directory for user systemd units (defaults to /usr/lib/systemd/user)') option('sdl2', description: 'Enable code that depends on SDL 2', type: 'feature', value: 'auto') option('sndfile', description: 'Enable code that depends on libsndfile', type: 'feature', value: 'auto') option('libmysofa', description: 'Enable code that depends on libmysofa', type: 'feature', value: 'auto') option('libpulse', description: 'Enable code that depends on libpulse', type: 'feature', value: 'auto') option('roc', description: 'Enable code that depends on roc toolkit', type: 'feature', value: 'auto') option('avahi', description: 'Enable code that depends on avahi', type: 'feature', value: 'auto') option('echo-cancel-webrtc', description : 'Enable WebRTC-based echo canceller', type : 'feature', value : 'auto') option('libusb', description: 'Enable code that depends on libusb', type: 'feature', value: 'auto') option('session-managers', description : 'Session managers to build (can be [] for none or an absolute path)', type : 'array', value : ['wireplumber']) option('raop', description: 'Enable module for Remote Audio Output Protocol', type: 'feature', value: 'auto') option('lv2', description: 'Enable loading of LV2 plugins', type: 'feature', value: 'auto') option('x11', description: 'Enable code that depends on X11', type: 'feature', value: 'auto') option('x11-xfixes', description: 'Enable code that depends on XFixes', type: 'feature', value: 'auto') option('libcanberra', description: 'Enable code that depends on libcanberra', type: 'feature', value: 'auto') option('legacy-rtkit', description: 'Build legacy rtkit module', type: 'boolean', value: true) option('avb', description: 'Enable AVB code', type: 'feature', value: 'auto') option('flatpak', description: 'Enable Flatpak support', type: 'feature', value: 'enabled') option('readline', description: 'Enable code that depends on libreadline', type: 'feature', value: 'auto') option('gsettings', description: 'Enable code that depends on gsettings', type: 'feature', value: 'auto') option('compress-offload', description: 'Enable ALSA Compress-Offload support', type: 'feature', value: 'auto') option('pam-defaults-install', description: 'Install limits.d file modifying defaults for all PAM users. Only for old kernels/systemd!', type: 'boolean', value: false) option('pam-memlock-default', description : 'The default memlock value for any PAM user in kilobytes. Multiples of 64 recommended.', type : 'integer', min: 640, value: 8192) option('rlimits-install', description: 'Install PAM limits.d file. Voids all following rlimits-* options, if false', type: 'boolean', value: true) option('rlimits-match', description : 'PAM match rule for the generated limits.d file. @ denotes matching a group.', type : 'string', value: '@pipewire') option('rtprio-server', description : 'PipeWire server realtime priority', type : 'integer', min: 11, max: 99, value: 88) option('rtprio-client', description : 'PipeWire clients realtime priority', type : 'integer', min: 11, max: 99, value: 83) option('rlimits-rtprio', description : 'RR and FIFO scheduler priority permitted for realtime threads of the matching user(s)', type : 'integer', min: 11, max: 99, value: 95) option('rlimits-memlock', description : 'kB of memory each process of the user matched by the rule can lock. Can be unlimited .', type : 'string', value: '4194304') option('rlimits-nice', description : 'Not niceness permitted for non-realtime threads of the matching user(s)', type : 'integer', min: -20, max: -1, value: -19) option('opus', description: 'Enable code that depends on opus', type: 'feature', value: 'auto') option('libffado', description: 'Enable code that depends on libffado', type: 'feature', value: 'auto') option('gsettings-pulse-schema', description: 'Install gsettings schema for pulseaudio', type: 'feature', value: 'auto') option('snap', description : 'Enable Snap permissions support.', type : 'feature', value : 'auto') option('doc-prefix-value', description : 'Installation prefix to show in documentation instead of the actual value.', type : 'string', value : '') option('doc-sysconfdir-value', description : 'Sysconf data directory to show in documentation instead of the actual value.', type : 'string', value : '') option('ebur128', description: 'Enable code that depends on ebur128', type: 'feature', value: 'auto') option('fftw', description: 'Enable code that depends on fftw', type: 'feature', value: 'auto') option('onnxruntime', description: 'Enable code that depends on onnxruntime', type: 'feature', value: 'auto') pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/pipewire-alsa/000077500000000000000000000000001511204443500232405ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/pipewire-alsa/alsa-plugins/000077500000000000000000000000001511204443500256375ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/pipewire-alsa/alsa-plugins/ctl_pipewire.c000066400000000000000000001017061511204443500304760ustar00rootroot00000000000000/* CTL - PipeWire plugin */ /* SPDX-FileCopyrightText: Copyright © 2020 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #include #include #include #include #include PW_LOG_TOPIC_STATIC(alsa_log_topic, "alsa.ctl"); #define PW_LOG_TOPIC_DEFAULT alsa_log_topic #define DEFAULT_VOLUME_METHOD "cubic" #define VOLUME_MIN ((uint32_t) 0U) #define VOLUME_MAX ((uint32_t) 0x10000U) #define MAX_CHANNELS SPA_AUDIO_MAX_CHANNELS struct volume { uint32_t channels; long values[MAX_CHANNELS]; }; typedef struct { snd_ctl_ext_t ext; struct pw_properties *props; struct spa_system *system; struct pw_thread_loop *mainloop; struct pw_context *context; struct pw_core *core; struct spa_hook core_listener; struct pw_registry *registry; struct spa_hook registry_listener; struct pw_metadata *metadata; struct spa_hook metadata_listener; int fd; int last_seq; int pending_seq; int error; char default_sink[1024]; int sink_muted; struct volume sink_volume; char default_source[1024]; int source_muted; struct volume source_volume; int subscribed; #define VOLUME_METHOD_LINEAR (0) #define VOLUME_METHOD_CUBIC (1) int volume_method; #define UPDATE_SINK_VOL (1<<0) #define UPDATE_SINK_MUTE (1<<1) #define UPDATE_SOURCE_VOL (1<<2) #define UPDATE_SOURCE_MUTE (1<<3) int updated; struct spa_list globals; } snd_ctl_pipewire_t; static inline uint32_t volume_from_linear(float vol, int method) { if (vol <= 0.0f) vol = 0.0f; switch (method) { case VOLUME_METHOD_CUBIC: vol = cbrtf(vol); break; } return SPA_CLAMP((uint64_t)lroundf(vol * VOLUME_MAX), VOLUME_MIN, VOLUME_MAX); } static inline float volume_to_linear(uint32_t vol, int method) { float v = ((float)vol) / VOLUME_MAX; switch (method) { case VOLUME_METHOD_CUBIC: v = v * v * v; break; } return v; } struct global; struct global_info { const char *type; uint32_t version; const void *events; pw_destroy_t destroy; int (*init) (struct global *g); }; struct global { struct spa_list link; snd_ctl_pipewire_t *ctl; const struct global_info *ginfo; uint32_t id; uint32_t permissions; struct pw_properties *props; struct pw_proxy *proxy; struct spa_hook proxy_listener; struct spa_hook object_listener; union { struct { #define NODE_FLAG_SINK (1<<0) #define NODE_FLAG_SOURCE (1<<1) #define NODE_FLAG_DEVICE_VOLUME (1<<2) #define NODE_FLAG_DEVICE_MUTE (1<<3) uint32_t flags; uint32_t device_id; uint32_t profile_device_id; int priority; float volume; bool mute; struct volume channel_volume; } node; struct { uint32_t active_route_output; uint32_t active_route_input; } device; }; }; #define SOURCE_VOL_NAME "Capture Volume" #define SOURCE_MUTE_NAME "Capture Switch" #define SINK_VOL_NAME "Master Playback Volume" #define SINK_MUTE_NAME "Master Playback Switch" static void do_resync(snd_ctl_pipewire_t *ctl) { ctl->pending_seq = pw_core_sync(ctl->core, PW_ID_CORE, ctl->pending_seq); } static int wait_resync(snd_ctl_pipewire_t *ctl) { int res; do_resync(ctl); while (true) { pw_thread_loop_wait(ctl->mainloop); res = ctl->error; if (res < 0) { ctl->error = 0; return res; } if (ctl->pending_seq == ctl->last_seq) break; } return 0; } static struct global *find_global(snd_ctl_pipewire_t *ctl, uint32_t id, const char *name, const char *type) { struct global *g; uint32_t name_id = name ? (uint32_t)atoi(name) : SPA_ID_INVALID; const char *str; spa_list_for_each(g, &ctl->globals, link) { if ((g->id == id || g->id == name_id) && (type == NULL || spa_streq(g->ginfo->type, type))) return g; if (name != NULL && name[0] != '\0' && (str = pw_properties_get(g->props, PW_KEY_NODE_NAME)) != NULL && spa_streq(name, str)) return g; } return NULL; } static struct global *find_best_node(snd_ctl_pipewire_t *ctl, uint32_t flags) { struct global *g, *best = NULL; spa_list_for_each(g, &ctl->globals, link) { if ((spa_streq(g->ginfo->type, PW_TYPE_INTERFACE_Node)) && (flags == 0 || (g->node.flags & flags) == flags) && (best == NULL || best->node.priority < g->node.priority)) best = g; } return best; } static inline int poll_activate(snd_ctl_pipewire_t *ctl) { spa_system_eventfd_write(ctl->system, ctl->fd, 1); return 1; } static inline int poll_deactivate(snd_ctl_pipewire_t *ctl) { uint64_t val; spa_system_eventfd_read(ctl->system, ctl->fd, &val); return 1; } static bool volume_equal(struct volume *a, struct volume *b) { if (a == b) return true; if (a->channels != b->channels) return false; return memcmp(a->values, b->values, sizeof(float) * a->channels) == 0; } static int pipewire_update_volume(snd_ctl_pipewire_t * ctl) { bool changed = false; struct global *g; if (ctl->default_sink[0] == '\0') g = find_best_node(ctl, NODE_FLAG_SINK); else g = find_global(ctl, SPA_ID_INVALID, ctl->default_sink, PW_TYPE_INTERFACE_Node); if (g) { if (!!ctl->sink_muted != !!g->node.mute) { ctl->sink_muted = g->node.mute; ctl->updated |= UPDATE_SINK_MUTE; changed = true; } if (!volume_equal(&ctl->sink_volume, &g->node.channel_volume)) { ctl->sink_volume = g->node.channel_volume; ctl->updated |= UPDATE_SINK_VOL; changed = true; } } if (ctl->default_source[0] == '\0') g = find_best_node(ctl, NODE_FLAG_SOURCE); else g = find_global(ctl, SPA_ID_INVALID, ctl->default_source, PW_TYPE_INTERFACE_Node); if (g) { if (!!ctl->source_muted != !!g->node.mute) { ctl->source_muted = g->node.mute; ctl->updated |= UPDATE_SOURCE_MUTE; changed = true; } if (!volume_equal(&ctl->source_volume, &g->node.channel_volume)) { ctl->source_volume = g->node.channel_volume; ctl->updated |= UPDATE_SOURCE_VOL; changed = true; } } if (changed) poll_activate(ctl); return 0; } static int pipewire_elem_count(snd_ctl_ext_t * ext) { snd_ctl_pipewire_t *ctl = ext->private_data; int count = 0, err; assert(ctl); if (!ctl->mainloop) return -EBADFD; pw_thread_loop_lock(ctl->mainloop); err = ctl->error; if (err < 0) { ctl->error = 0; count = err; goto finish; } err = pipewire_update_volume(ctl); if (err < 0) { count = err; goto finish; } if (ctl->default_source[0] != '\0') count += 2; if (ctl->default_sink[0] != '\0') count += 2; finish: pw_thread_loop_unlock(ctl->mainloop); return count; } static int pipewire_elem_list(snd_ctl_ext_t * ext, unsigned int offset, snd_ctl_elem_id_t * id) { snd_ctl_pipewire_t *ctl = ext->private_data; int err; assert(ctl); if (!ctl->mainloop) return -EBADFD; snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_MIXER); pw_thread_loop_lock(ctl->mainloop); err = ctl->error; if (err < 0) { ctl->error = 0; goto finish; } if (ctl->default_source[0] != '\0') { if (offset == 0) snd_ctl_elem_id_set_name(id, SOURCE_VOL_NAME); else if (offset == 1) snd_ctl_elem_id_set_name(id, SOURCE_MUTE_NAME); } else offset += 2; err = 0; finish: pw_thread_loop_unlock(ctl->mainloop); if (err >= 0) { if (offset == 2) snd_ctl_elem_id_set_name(id, SINK_VOL_NAME); else if (offset == 3) snd_ctl_elem_id_set_name(id, SINK_MUTE_NAME); } return err; } static snd_ctl_ext_key_t pipewire_find_elem(snd_ctl_ext_t * ext, const snd_ctl_elem_id_t * id) { const char *name; unsigned int numid; numid = snd_ctl_elem_id_get_numid(id); if (numid > 0 && numid <= 4) return numid - 1; name = snd_ctl_elem_id_get_name(id); if (spa_streq(name, SOURCE_VOL_NAME)) return 0; if (spa_streq(name, SOURCE_MUTE_NAME)) return 1; if (spa_streq(name, SINK_VOL_NAME)) return 2; if (spa_streq(name, SINK_MUTE_NAME)) return 3; return SND_CTL_EXT_KEY_NOT_FOUND; } static int pipewire_get_attribute(snd_ctl_ext_t * ext, snd_ctl_ext_key_t key, int *type, unsigned int *acc, unsigned int *count) { snd_ctl_pipewire_t *ctl = ext->private_data; int err = 0; if (key > 3) return -EINVAL; assert(ctl); if (!ctl->mainloop) return -EBADFD; pw_thread_loop_lock(ctl->mainloop); err = ctl->error; if (err < 0) { ctl->error = 0; goto finish; } err = pipewire_update_volume(ctl); if (err < 0) goto finish; if (key & 1) *type = SND_CTL_ELEM_TYPE_BOOLEAN; else *type = SND_CTL_ELEM_TYPE_INTEGER; *acc = SND_CTL_EXT_ACCESS_READWRITE; if (key == 0) *count = ctl->source_volume.channels; else if (key == 2) *count = ctl->sink_volume.channels; else *count = 1; finish: pw_thread_loop_unlock(ctl->mainloop); return err; } static int pipewire_get_integer_info(snd_ctl_ext_t * ext, snd_ctl_ext_key_t key, long *imin, long *imax, long *istep) { *istep = 1; *imin = VOLUME_MIN; *imax = VOLUME_MAX; return 0; } static int pipewire_read_integer(snd_ctl_ext_t * ext, snd_ctl_ext_key_t key, long *value) { snd_ctl_pipewire_t *ctl = ext->private_data; int err = 0; uint32_t i; struct volume *vol = NULL; assert(ctl); if (!ctl->mainloop) return -EBADFD; pw_thread_loop_lock(ctl->mainloop); err = ctl->error; if (err < 0) { ctl->error = 0; goto finish; } err = pipewire_update_volume(ctl); if (err < 0) goto finish; switch (key) { case 0: vol = &ctl->source_volume; break; case 1: *value = !ctl->source_muted; break; case 2: vol = &ctl->sink_volume; break; case 3: *value = !ctl->sink_muted; break; default: err = -EINVAL; goto finish; } if (vol) { for (i = 0; i < vol->channels; i++) value[i] = vol->values[i]; } finish: pw_thread_loop_unlock(ctl->mainloop); return err; } static struct spa_pod *build_volume_mute(struct spa_pod_builder *b, struct volume *volume, int *mute, int volume_method) { struct spa_pod_frame f[1]; spa_pod_builder_push_object(b, &f[0], SPA_TYPE_OBJECT_Props, SPA_PARAM_Props); if (volume) { float volumes[MAX_CHANNELS]; uint32_t i, n_volumes = 0; n_volumes = volume->channels; for (i = 0; i < n_volumes; i++) volumes[i] = volume_to_linear(volume->values[i], volume_method); spa_pod_builder_prop(b, SPA_PROP_channelVolumes, 0); spa_pod_builder_array(b, sizeof(float), SPA_TYPE_Float, n_volumes, volumes); } if (mute) { spa_pod_builder_prop(b, SPA_PROP_mute, 0); spa_pod_builder_bool(b, *mute ? true : false); } return spa_pod_builder_pop(b, &f[0]); } static int set_volume_mute(snd_ctl_pipewire_t *ctl, const char *name, struct volume *volume, int *mute) { struct global *g, *dg = NULL; uint32_t id = SPA_ID_INVALID, device_id = SPA_ID_INVALID; char buf[1024]; struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buf, sizeof(buf)); struct spa_pod_frame f[2]; struct spa_pod *param; g = find_global(ctl, SPA_ID_INVALID, name, PW_TYPE_INTERFACE_Node); if (g == NULL) return -EINVAL; if (SPA_FLAG_IS_SET(g->node.flags, NODE_FLAG_DEVICE_VOLUME) && (dg = find_global(ctl, g->node.device_id, NULL, PW_TYPE_INTERFACE_Device)) != NULL) { if (g->node.flags & NODE_FLAG_SINK) id = dg->device.active_route_output; else if (g->node.flags & NODE_FLAG_SOURCE) id = dg->device.active_route_input; device_id = g->node.profile_device_id; } pw_log_debug("id %d device_id %d flags:%08x", id, device_id, g->node.flags); if (id != SPA_ID_INVALID && device_id != SPA_ID_INVALID && dg != NULL) { if (!SPA_FLAG_IS_SET(dg->permissions, PW_PERM_W | PW_PERM_X)) return -EPERM; spa_pod_builder_push_object(&b, &f[0], SPA_TYPE_OBJECT_ParamRoute, SPA_PARAM_Route); spa_pod_builder_add(&b, SPA_PARAM_ROUTE_index, SPA_POD_Int(id), SPA_PARAM_ROUTE_device, SPA_POD_Int(device_id), SPA_PARAM_ROUTE_save, SPA_POD_Bool(true), 0); spa_pod_builder_prop(&b, SPA_PARAM_ROUTE_props, 0); build_volume_mute(&b, volume, mute, ctl->volume_method); param = spa_pod_builder_pop(&b, &f[0]); pw_log_debug("set device %d mute/volume for node %d", dg->id, g->id); pw_device_set_param((struct pw_device*)dg->proxy, SPA_PARAM_Route, 0, param); } else { if (!SPA_FLAG_IS_SET(g->permissions, PW_PERM_W | PW_PERM_X)) return -EPERM; param = build_volume_mute(&b, volume, mute, ctl->volume_method); pw_log_debug("set node %d mute/volume", g->id); pw_node_set_param((struct pw_node*)g->proxy, SPA_PARAM_Props, 0, param); } return 0; } static int pipewire_write_integer(snd_ctl_ext_t * ext, snd_ctl_ext_key_t key, long *value) { snd_ctl_pipewire_t *ctl = ext->private_data; int err = 0; uint32_t i; struct volume *vol = NULL; assert(ctl); if (!ctl->mainloop) return -EBADFD; pw_thread_loop_lock(ctl->mainloop); err = ctl->error; if (err < 0) { ctl->error = 0; goto finish; } err = pipewire_update_volume(ctl); if (err < 0) goto finish; switch (key) { case 0: vol = &ctl->source_volume; break; case 1: if (!!ctl->source_muted == !*value) goto finish; ctl->source_muted = !*value; break; case 2: vol = &ctl->sink_volume; break; case 3: if (!!ctl->sink_muted == !*value) goto finish; ctl->sink_muted = !*value; break; default: err = -EINVAL; goto finish; } if (vol) { for (i = 0; i < vol->channels; i++) if (value[i] != vol->values[i]) break; if (i == vol->channels) goto finish; for (i = 0; i < vol->channels; i++) vol->values[i] = value[i]; if (key == 0) err = set_volume_mute(ctl, ctl->default_source, vol, NULL); else err = set_volume_mute(ctl, ctl->default_sink, vol, NULL); } else { if (key == 1) err = set_volume_mute(ctl, ctl->default_source, NULL, &ctl->source_muted); else err = set_volume_mute(ctl, ctl->default_sink, NULL, &ctl->sink_muted); } if (err < 0) goto finish; err = wait_resync(ctl); if (err < 0) goto finish; err = 1; finish: pw_thread_loop_unlock(ctl->mainloop); return err; } static void pipewire_subscribe_events(snd_ctl_ext_t * ext, int subscribe) { snd_ctl_pipewire_t *ctl = ext->private_data; assert(ctl); if (!ctl->mainloop) return; pw_thread_loop_lock(ctl->mainloop); ctl->subscribed = !!(subscribe & SND_CTL_EVENT_MASK_VALUE); pw_thread_loop_unlock(ctl->mainloop); } static int pipewire_read_event(snd_ctl_ext_t * ext, snd_ctl_elem_id_t * id, unsigned int *event_mask) { snd_ctl_pipewire_t *ctl = ext->private_data; int offset; int err; assert(ctl); if (!ctl->mainloop) return -EBADFD; pw_thread_loop_lock(ctl->mainloop); err = ctl->error; if (err < 0) { ctl->error = 0; goto finish; } if (!ctl->updated || !ctl->subscribed) { err = -EAGAIN; goto finish; } if (ctl->default_source[0] != '\0') offset = 2; else offset = 0; if (ctl->updated & UPDATE_SOURCE_VOL) { pipewire_elem_list(ext, 0, id); ctl->updated &= ~UPDATE_SOURCE_VOL; } else if (ctl->updated & UPDATE_SOURCE_MUTE) { pipewire_elem_list(ext, 1, id); ctl->updated &= ~UPDATE_SOURCE_MUTE; } else if (ctl->updated & UPDATE_SINK_VOL) { pipewire_elem_list(ext, offset + 0, id); ctl->updated &= ~UPDATE_SINK_VOL; } else if (ctl->updated & UPDATE_SINK_MUTE) { pipewire_elem_list(ext, offset + 1, id); ctl->updated &= ~UPDATE_SINK_MUTE; } *event_mask = SND_CTL_EVENT_MASK_VALUE; err = 1; finish: if (!ctl->updated) poll_deactivate(ctl); pw_thread_loop_unlock(ctl->mainloop); return err; } static int pipewire_ctl_poll_revents(snd_ctl_ext_t * ext, struct pollfd *pfd, unsigned int nfds, unsigned short *revents) { snd_ctl_pipewire_t *ctl = ext->private_data; int err = 0; assert(ctl); if (!ctl->mainloop) return -EBADFD; pw_thread_loop_lock(ctl->mainloop); err = ctl->error; if (err < 0) { ctl->error = 0; goto finish; } if (ctl->updated) *revents = POLLIN; else *revents = 0; err = 0; finish: pw_thread_loop_unlock(ctl->mainloop); return err; } static void snd_ctl_pipewire_free(snd_ctl_pipewire_t *ctl) { if (ctl == NULL) return; pw_log_debug("%p:", ctl); if (ctl->mainloop) pw_thread_loop_stop(ctl->mainloop); if (ctl->registry) pw_proxy_destroy((struct pw_proxy*)ctl->registry); if (ctl->context) pw_context_destroy(ctl->context); if (ctl->fd >= 0) spa_system_close(ctl->system, ctl->fd); if (ctl->mainloop) pw_thread_loop_destroy(ctl->mainloop); pw_properties_free(ctl->props); free(ctl); } static void pipewire_close(snd_ctl_ext_t * ext) { snd_ctl_pipewire_t *ctl = ext->private_data; snd_ctl_pipewire_free(ctl); } static const snd_ctl_ext_callback_t pipewire_ext_callback = { .elem_count = pipewire_elem_count, .elem_list = pipewire_elem_list, .find_elem = pipewire_find_elem, .get_attribute = pipewire_get_attribute, .get_integer_info = pipewire_get_integer_info, .read_integer = pipewire_read_integer, .write_integer = pipewire_write_integer, .subscribe_events = pipewire_subscribe_events, .read_event = pipewire_read_event, .poll_revents = pipewire_ctl_poll_revents, .close = pipewire_close, }; /** device */ static void device_event_info(void *data, const struct pw_device_info *info) { struct global *g = data; snd_ctl_pipewire_t *ctl = g->ctl; uint32_t n; pw_log_debug("info"); if (info->change_mask & PW_DEVICE_CHANGE_MASK_PARAMS) { for (n = 0; n < info->n_params; n++) { if (!(info->params[n].flags & SPA_PARAM_INFO_READ)) continue; switch (info->params[n].id) { case SPA_PARAM_Route: pw_device_enum_params((struct pw_device*)g->proxy, 0, info->params[n].id, 0, -1, NULL); break; default: break; } } } do_resync(ctl); } static void parse_props(struct global *g, const struct spa_pod *param, bool device) { struct spa_pod_prop *prop; struct spa_pod_object *obj = (struct spa_pod_object *) param; snd_ctl_pipewire_t *ctl = g->ctl; SPA_POD_OBJECT_FOREACH(obj, prop) { switch (prop->key) { case SPA_PROP_volume: if (spa_pod_get_float(&prop->value, &g->node.volume) < 0) continue; pw_log_debug("update node %d volume", g->id); SPA_FLAG_UPDATE(g->node.flags, NODE_FLAG_DEVICE_VOLUME, device); break; case SPA_PROP_mute: if (spa_pod_get_bool(&prop->value, &g->node.mute) < 0) continue; SPA_FLAG_UPDATE(g->node.flags, NODE_FLAG_DEVICE_MUTE, device); pw_log_debug("update node %d mute", g->id); break; case SPA_PROP_channelVolumes: { float volumes[MAX_CHANNELS]; uint32_t n_volumes, i; n_volumes = spa_pod_copy_array(&prop->value, SPA_TYPE_Float, volumes, SPA_N_ELEMENTS(volumes)); g->node.channel_volume.channels = n_volumes; for (i = 0; i < n_volumes; i++) g->node.channel_volume.values[i] = volume_from_linear(volumes[i], ctl->volume_method); SPA_FLAG_UPDATE(g->node.flags, NODE_FLAG_DEVICE_VOLUME, device); pw_log_debug("update node %d channelVolumes", g->id); break; } default: break; } } } static struct global *find_node_for_route(snd_ctl_pipewire_t *ctl, uint32_t card, uint32_t device) { struct global *n; spa_list_for_each(n, &ctl->globals, link) { if (spa_streq(n->ginfo->type, PW_TYPE_INTERFACE_Node) && (n->node.device_id == card) && (n->node.profile_device_id == device)) return n; } return NULL; } static void device_event_param(void *data, int seq, uint32_t id, uint32_t index, uint32_t next, const struct spa_pod *param) { struct global *g = data; snd_ctl_pipewire_t *ctl = g->ctl; pw_log_debug("param %d", id); switch (id) { case SPA_PARAM_Route: { uint32_t idx, device; enum spa_direction direction; struct spa_pod *props = NULL; struct global *ng; if (spa_pod_parse_object(param, SPA_TYPE_OBJECT_ParamRoute, NULL, SPA_PARAM_ROUTE_index, SPA_POD_Int(&idx), SPA_PARAM_ROUTE_direction, SPA_POD_Id(&direction), SPA_PARAM_ROUTE_device, SPA_POD_Int(&device), SPA_PARAM_ROUTE_props, SPA_POD_OPT_Pod(&props)) < 0) { pw_log_warn("device %d: can't parse route", g->id); return; } if (direction == SPA_DIRECTION_OUTPUT) g->device.active_route_output = idx; else g->device.active_route_input = idx; pw_log_debug("device %d: active %s route %d", g->id, direction == SPA_DIRECTION_OUTPUT ? "output" : "input", idx); ng = find_node_for_route(ctl, g->id, device); if (props && ng) parse_props(ng, props, true); break; } default: break; } } static const struct pw_device_events device_events = { PW_VERSION_DEVICE_EVENTS, .info = device_event_info, .param = device_event_param, }; static const struct global_info device_info = { .type = PW_TYPE_INTERFACE_Device, .version = PW_VERSION_DEVICE, .events = &device_events, }; /** node */ static void node_event_info(void *data, const struct pw_node_info *info) { struct global *g = data; snd_ctl_pipewire_t *ctl = g->ctl; const char *str; uint32_t i; pw_log_debug("update %d %"PRIu64, g->id, info->change_mask); if (info->change_mask & PW_NODE_CHANGE_MASK_PROPS && info->props) { if ((str = spa_dict_lookup(info->props, "card.profile.device"))) g->node.profile_device_id = atoi(str); else g->node.profile_device_id = SPA_ID_INVALID; if ((str = spa_dict_lookup(info->props, PW_KEY_DEVICE_ID))) g->node.device_id = atoi(str); else g->node.device_id = SPA_ID_INVALID; if ((str = spa_dict_lookup(info->props, PW_KEY_PRIORITY_SESSION))) g->node.priority = atoi(str); if ((str = spa_dict_lookup(info->props, PW_KEY_MEDIA_CLASS))) { if (spa_streq(str, "Audio/Sink")) g->node.flags |= NODE_FLAG_SINK; else if (spa_streq(str, "Audio/Source")) g->node.flags |= NODE_FLAG_SOURCE; } } if (info->change_mask & PW_NODE_CHANGE_MASK_PARAMS) { for (i = 0; i < info->n_params; i++) { if (!(info->params[i].flags & SPA_PARAM_INFO_READ)) continue; switch (info->params[i].id) { case SPA_PARAM_Props: pw_node_enum_params((struct pw_node*)g->proxy, 0, info->params[i].id, 0, -1, NULL); break; default: break; } } } do_resync(ctl); } static void node_event_param(void *data, int seq, uint32_t id, uint32_t index, uint32_t next, const struct spa_pod *param) { struct global *g = data; pw_log_debug("update param %d %d", g->id, id); switch (id) { case SPA_PARAM_Props: if (!SPA_FLAG_IS_SET(g->node.flags, NODE_FLAG_DEVICE_VOLUME | NODE_FLAG_DEVICE_MUTE)) parse_props(g, param, false); break; default: break; } } static const struct pw_node_events node_events = { PW_VERSION_NODE_EVENTS, .info = node_event_info, .param = node_event_param, }; static const struct global_info node_info = { .type = PW_TYPE_INTERFACE_Node, .version = PW_VERSION_NODE, .events = &node_events, }; /** metadata */ static int metadata_property(void *data, uint32_t subject, const char *key, const char *type, const char *value) { struct global *g = data; snd_ctl_pipewire_t *ctl = g->ctl; if (subject == PW_ID_CORE) { if (key == NULL || spa_streq(key, "default.audio.sink")) { if (value == NULL || spa_json_str_object_find(value, strlen(value), "name", ctl->default_sink, sizeof(ctl->default_sink)) < 0) ctl->default_sink[0] = '\0'; pw_log_debug("found default sink: %s", ctl->default_sink); } if (key == NULL || spa_streq(key, "default.audio.source")) { if (value == NULL || spa_json_str_object_find(value, strlen(value), "name", ctl->default_source, sizeof(ctl->default_source)) < 0) ctl->default_source[0] = '\0'; pw_log_debug("found default source: %s", ctl->default_source); } } return 0; } static int metadata_init(struct global *g) { snd_ctl_pipewire_t *ctl = g->ctl; ctl->metadata = (struct pw_metadata*)g->proxy; return 0; } static const struct pw_metadata_events metadata_events = { PW_VERSION_METADATA_EVENTS, .property = metadata_property, }; static const struct global_info metadata_info = { .type = PW_TYPE_INTERFACE_Metadata, .version = PW_VERSION_METADATA, .events = &metadata_events, .init = metadata_init }; /** proxy */ static void proxy_removed(void *data) { struct global *g = data; pw_proxy_destroy(g->proxy); } static void proxy_destroy(void *data) { struct global *g = data; spa_list_remove(&g->link); g->proxy = NULL; pw_properties_free(g->props); } static const struct pw_proxy_events proxy_events = { PW_VERSION_PROXY_EVENTS, .removed = proxy_removed, .destroy = proxy_destroy }; static void registry_event_global(void *data, uint32_t id, uint32_t permissions, const char *type, uint32_t version, const struct spa_dict *props) { snd_ctl_pipewire_t *ctl = data; const struct global_info *info = NULL; struct pw_proxy *proxy; const char *str; pw_log_debug("got %d %s", id, type); if (spa_streq(type, PW_TYPE_INTERFACE_Device)) { if (props == NULL || ((str = spa_dict_lookup(props, PW_KEY_MEDIA_CLASS)) == NULL) || (!spa_streq(str, "Audio/Device"))) return; pw_log_debug("found device %d", id); info = &device_info; } else if (spa_streq(type, PW_TYPE_INTERFACE_Node)) { if (props == NULL || ((str = spa_dict_lookup(props, PW_KEY_MEDIA_CLASS)) == NULL) || ((!spa_streq(str, "Audio/Sink")) && (!spa_streq(str, "Audio/Source")))) return; pw_log_debug("found node %d type:%s", id, str); info = &node_info; } else if (spa_streq(type, PW_TYPE_INTERFACE_Metadata)) { if (props == NULL || ((str = spa_dict_lookup(props, PW_KEY_METADATA_NAME)) == NULL) || (!spa_streq(str, "default"))) return; if (ctl->metadata != NULL) return; info = &metadata_info; } if (info) { struct global *g; proxy = pw_registry_bind(ctl->registry, id, info->type, info->version, sizeof(struct global)); g = pw_proxy_get_user_data(proxy); g->ctl = ctl; g->ginfo = info; g->id = id; g->permissions = permissions; g->props = props ? pw_properties_new_dict(props) : NULL; g->proxy = proxy; spa_list_append(&ctl->globals, &g->link); pw_proxy_add_listener(proxy, &g->proxy_listener, &proxy_events, g); if (info->events) { pw_proxy_add_object_listener(proxy, &g->object_listener, info->events, g); } if (info->init) info->init(g); do_resync(ctl); } } static void registry_event_global_remove(void *data, uint32_t id) { snd_ctl_pipewire_t *ctl = data; struct global *g; const char *name; if ((g = find_global(ctl, id, NULL, NULL)) == NULL) return; if (spa_streq(g->ginfo->type, PW_TYPE_INTERFACE_Node)) { if ((name = pw_properties_get(g->props, PW_KEY_NODE_NAME)) == NULL) return; if (spa_streq(name, ctl->default_sink)) ctl->default_sink[0] = '\0'; if (spa_streq(name, ctl->default_source)) ctl->default_source[0] = '\0'; } pw_proxy_destroy(g->proxy); } static const struct pw_registry_events registry_events = { PW_VERSION_REGISTRY_EVENTS, .global = registry_event_global, .global_remove = registry_event_global_remove, }; static void on_core_error(void *data, uint32_t id, int seq, int res, const char *message) { snd_ctl_pipewire_t *ctl = data; pw_log_warn("%p: error id:%u seq:%d res:%d (%s): %s", ctl, id, seq, res, spa_strerror(res), message); if (id == PW_ID_CORE) { switch (res) { case -ENOENT: break; default: ctl->error = res; if (ctl->fd != -1) poll_activate(ctl); } } pw_thread_loop_signal(ctl->mainloop, false); } static void on_core_done(void *data, uint32_t id, int seq) { snd_ctl_pipewire_t *ctl = data; pw_log_debug("done %d %d %d", id, seq, ctl->pending_seq); if (id != PW_ID_CORE) return; ctl->last_seq = seq; if (seq == ctl->pending_seq) { pipewire_update_volume(ctl); pw_thread_loop_signal(ctl->mainloop, false); } } static const struct pw_core_events core_events = { PW_VERSION_CORE_EVENTS, .error = on_core_error, .done = on_core_done, }; static int execute_match(void *data, const char *location, const char *action, const char *val, size_t len) { snd_ctl_pipewire_t *ctl = data; if (spa_streq(action, "update-props")) pw_properties_update_string(ctl->props, val, len); return 1; } SPA_EXPORT SND_CTL_PLUGIN_DEFINE_FUNC(pipewire) { snd_config_iterator_t i, next; const char *server = NULL; const char *device = NULL; const char *source = NULL; const char *sink = NULL; const char *fallback_name = NULL; int err; const char *str; snd_ctl_pipewire_t *ctl; struct pw_loop *loop; pw_init(NULL, NULL); PW_LOG_TOPIC_INIT(alsa_log_topic); snd_config_for_each(i, next, conf) { snd_config_t *n = snd_config_iterator_entry(i); const char *id; if (snd_config_get_id(n, &id) < 0) continue; if (spa_streq(id, "comment") || spa_streq(id, "type") || spa_streq(id, "hint")) continue; if (spa_streq(id, "server")) { if (snd_config_get_string(n, &server) < 0) { SNDERR("Invalid type for %s", id); return -EINVAL; } else if (!*server) { server = NULL; } continue; } if (spa_streq(id, "device")) { if (snd_config_get_string(n, &device) < 0) { SNDERR("Invalid type for %s", id); return -EINVAL; } else if (!*device) { device = NULL; } continue; } if (spa_streq(id, "source")) { if (snd_config_get_string(n, &source) < 0) { SNDERR("Invalid type for %s", id); return -EINVAL; } else if (!*source) { source = NULL; } continue; } if (spa_streq(id, "sink")) { if (snd_config_get_string(n, &sink) < 0) { SNDERR("Invalid type for %s", id); return -EINVAL; } else if (!*sink) { sink = NULL; } continue; } if (spa_streq(id, "fallback")) { if (snd_config_get_string(n, &fallback_name) < 0) { SNDERR("Invalid value for %s", id); return -EINVAL; } continue; } SNDERR("Unknown field %s", id); return -EINVAL; } if (fallback_name && name && spa_streq(name, fallback_name)) fallback_name = NULL; /* no fallback for the same name */ ctl = calloc(1, sizeof(*ctl)); if (!ctl) return -ENOMEM; spa_list_init(&ctl->globals); if (source == NULL) source = device; if (source != NULL) snprintf(ctl->default_source, sizeof(ctl->default_source), "%s", source); if (sink == NULL) sink = device; if (sink != NULL) snprintf(ctl->default_sink, sizeof(ctl->default_sink), "%s", sink); ctl->mainloop = pw_thread_loop_new("alsa-pipewire", NULL); if (ctl->mainloop == NULL) { err = -errno; goto error; } loop = pw_thread_loop_get_loop(ctl->mainloop); ctl->system = loop->system; ctl->fd = spa_system_eventfd_create(ctl->system, SPA_FD_CLOEXEC | SPA_FD_NONBLOCK); if (ctl->fd < 0) { err = ctl->fd; goto error; } ctl->context = pw_context_new(loop, pw_properties_new( PW_KEY_CLIENT_API, "alsa", NULL), 0); if (ctl->context == NULL) { err = -errno; goto error; } ctl->props = pw_properties_new(NULL, NULL); if (ctl->props == NULL) { err = -errno; goto error; } if (server) pw_properties_set(ctl->props, PW_KEY_REMOTE_NAME, server); pw_context_conf_update_props(ctl->context, "alsa.properties", ctl->props); pw_context_conf_section_match_rules(ctl->context, "alsa.rules", &pw_context_get_properties(ctl->context)->dict, execute_match, ctl); if (pw_properties_get(ctl->props, PW_KEY_APP_NAME) == NULL) pw_properties_setf(ctl->props, PW_KEY_APP_NAME, "PipeWire ALSA [%s]", pw_get_prgname()); str = getenv("PIPEWIRE_ALSA"); if (str != NULL) pw_properties_update_string(ctl->props, str, strlen(str)); if ((str = pw_properties_get(ctl->props, "alsa.volume-method")) == NULL) str = DEFAULT_VOLUME_METHOD; if (spa_streq(str, "cubic")) ctl->volume_method = VOLUME_METHOD_CUBIC; else if (spa_streq(str, "linear")) ctl->volume_method = VOLUME_METHOD_LINEAR; else { ctl->volume_method = VOLUME_METHOD_CUBIC; SNDERR("unknown alsa.volume-method %s, using cubic", str); } if ((err = pw_thread_loop_start(ctl->mainloop)) < 0) goto error; pw_thread_loop_lock(ctl->mainloop); ctl->core = pw_context_connect(ctl->context, pw_properties_copy(ctl->props), 0); if (ctl->core == NULL) { err = -errno; goto error_unlock; } pw_core_add_listener(ctl->core, &ctl->core_listener, &core_events, ctl); ctl->registry = pw_core_get_registry(ctl->core, PW_VERSION_REGISTRY, 0); if (ctl->registry == NULL) { err = -errno; goto error_unlock; } pw_registry_add_listener(ctl->registry, &ctl->registry_listener, ®istry_events, ctl); err = wait_resync(ctl); if (err < 0) goto error_unlock; pw_thread_loop_unlock(ctl->mainloop); ctl->ext.version = SND_CTL_EXT_VERSION; ctl->ext.card_idx = 0; strncpy(ctl->ext.id, "pipewire", sizeof(ctl->ext.id) - 1); strncpy(ctl->ext.driver, "PW plugin", sizeof(ctl->ext.driver) - 1); strncpy(ctl->ext.name, "PipeWire", sizeof(ctl->ext.name) - 1); strncpy(ctl->ext.longname, "PipeWire", sizeof(ctl->ext.longname) - 1); strncpy(ctl->ext.mixername, "PipeWire", sizeof(ctl->ext.mixername) - 1); ctl->ext.poll_fd = ctl->fd; ctl->ext.callback = &pipewire_ext_callback; ctl->ext.private_data = ctl; err = snd_ctl_ext_create(&ctl->ext, name, mode); if (err < 0) goto error; *handlep = ctl->ext.handle; return 0; error_unlock: pw_thread_loop_unlock(ctl->mainloop); error: snd_ctl_pipewire_free(ctl); pw_log_error("error %d (%s)", err, spa_strerror(err)); if (fallback_name) return snd_ctl_open_fallback(handlep, root, fallback_name, name, mode); return err; } SPA_EXPORT SND_CTL_PLUGIN_SYMBOL(pipewire); pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/pipewire-alsa/alsa-plugins/meson.build000066400000000000000000000014221511204443500300000ustar00rootroot00000000000000pipewire_alsa_plugin_pcm_sources = [ 'pcm_pipewire.c', ] pipewire_alsa_plugin_ctl_sources = [ 'ctl_pipewire.c', ] pipewire_alsa_plugin_c_args = [ '-DPIC', ] pipewire_alsa_pcm_plugin = shared_library('asound_module_pcm_pipewire', pipewire_alsa_plugin_pcm_sources, c_args : pipewire_alsa_plugin_c_args, include_directories : [configinc], dependencies : [pipewire_dep, alsa_dep], install : true, install_dir : pipewire_libdir / 'alsa-lib', ) pipewire_alsa_ctl_plugin = shared_library('asound_module_ctl_pipewire', pipewire_alsa_plugin_ctl_sources, c_args : pipewire_alsa_plugin_c_args, include_directories : [configinc], dependencies : [pipewire_dep, alsa_dep, mathlib], install : true, install_dir : pipewire_libdir / 'alsa-lib', ) pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/pipewire-alsa/alsa-plugins/pcm_pipewire.c000066400000000000000000001244251511204443500304760ustar00rootroot00000000000000/* PCM - PipeWire plugin */ /* SPDX-FileCopyrightText: Copyright © 2017 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #define __USE_GNU #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include PW_LOG_TOPIC_STATIC(alsa_log_topic, "alsa.pcm"); #define PW_LOG_TOPIC_DEFAULT alsa_log_topic #define MIN_BUFFERS 2u #define MAX_BUFFERS 64u #define MAX_RATE (48000*8) #define MAX_CHANNELS SPA_AUDIO_MAX_CHANNELS #define MIN_PERIOD 64 #define MIN_PERIOD_BYTES (128) #define MAX_PERIOD_BYTES (2*1024*1024) #define MIN_BUFFER_BYTES (2*MIN_PERIOD_BYTES) #define MAX_BUFFER_BYTES (2*MAX_PERIOD_BYTES) typedef struct { snd_pcm_ioplug_t io; snd_output_t *output; FILE *log_file; int fd; int error; unsigned int activated:1; /* PipeWire is activated? */ unsigned int drained:1; unsigned int draining:1; unsigned int xrun_detected:1; unsigned int hw_params_changed:1; unsigned int negotiated:1; snd_pcm_uframes_t hw_ptr; snd_pcm_uframes_t boundary; snd_pcm_uframes_t min_avail; unsigned int sample_bits; uint32_t blocks; uint32_t stride; struct spa_system *system; struct pw_thread_loop *main_loop; struct pw_properties *props; struct pw_context *context; struct pw_core *core; struct spa_hook core_listener; struct pw_stream *stream; struct spa_hook stream_listener; int64_t delay; uint64_t transferred; uint64_t buffered; int64_t now; uintptr_t seq; struct spa_audio_info requested; struct spa_audio_info format; } snd_pcm_pipewire_t; static int snd_pcm_pipewire_stop(snd_pcm_ioplug_t *io); static int update_active(snd_pcm_ioplug_t *io) { snd_pcm_pipewire_t *pw = io->private_data; snd_pcm_sframes_t avail; bool active; uint64_t val; avail = snd_pcm_ioplug_avail(io, pw->hw_ptr, io->appl_ptr); if (pw->error > 0) { active = true; } else if (io->state == SND_PCM_STATE_DRAINING) { active = pw->drained; } else if (avail >= 0 && avail < (snd_pcm_sframes_t)pw->min_avail) { active = false; } else if (avail >= (snd_pcm_sframes_t)pw->min_avail) { active = true; } else { active = false; } pw_log_trace("%p: avail:%lu min-avail:%lu state:%s hw:%lu appl:%lu active:%d state:%s", pw, avail, pw->min_avail, snd_pcm_state_name(io->state), pw->hw_ptr, io->appl_ptr, active, snd_pcm_state_name(io->state)); if (active) spa_system_eventfd_write(pw->system, io->poll_fd, 1); else spa_system_eventfd_read(pw->system, io->poll_fd, &val); return active; } static void snd_pcm_pipewire_free(snd_pcm_pipewire_t *pw) { if (pw == NULL) return; pw_log_debug("%p: free", pw); if (pw->main_loop) pw_thread_loop_stop(pw->main_loop); if (pw->stream) pw_stream_destroy(pw->stream); if (pw->context) pw_context_destroy(pw->context); if (pw->fd >= 0) spa_system_close(pw->system, pw->fd); if (pw->main_loop) pw_thread_loop_destroy(pw->main_loop); pw_properties_free(pw->props); snd_output_close(pw->output); fclose(pw->log_file); free(pw); } static int snd_pcm_pipewire_close(snd_pcm_ioplug_t *io) { snd_pcm_pipewire_t *pw = io->private_data; pw_log_debug("%p: close", pw); snd_pcm_pipewire_free(pw); return 0; } static int snd_pcm_pipewire_poll_revents(snd_pcm_ioplug_t *io, struct pollfd *pfds, unsigned int nfds, unsigned short *revents) { snd_pcm_pipewire_t *pw = io->private_data; assert(pfds && nfds == 1 && revents); if (pw->error < 0) return pw->error; *revents = pfds[0].revents & ~(POLLIN | POLLOUT); if (pfds[0].revents & POLLIN && update_active(io)) *revents |= (io->stream == SND_PCM_STREAM_PLAYBACK) ? POLLOUT : POLLIN; pw_log_trace_fp("poll %d", *revents); return 0; } static snd_pcm_sframes_t snd_pcm_pipewire_pointer(snd_pcm_ioplug_t *io) { snd_pcm_pipewire_t *pw = io->private_data; if (pw->xrun_detected) return -EPIPE; if (pw->error < 0) return pw->error; if (io->buffer_size == 0) return 0; #ifdef SND_PCM_IOPLUG_FLAG_BOUNDARY_WA return pw->hw_ptr; #else return pw->hw_ptr % io->buffer_size; #endif } static int snd_pcm_pipewire_delay(snd_pcm_ioplug_t *io, snd_pcm_sframes_t *delayp) { snd_pcm_pipewire_t *pw = io->private_data; uintptr_t seq1, seq2; int64_t elapsed = 0, delay, now, avail; int64_t diff; do { seq1 = SPA_SEQ_READ(pw->seq); delay = pw->delay + pw->transferred; now = pw->now; if (io->stream == SND_PCM_STREAM_PLAYBACK) avail = snd_pcm_ioplug_hw_avail(io, pw->hw_ptr, io->appl_ptr); else avail = snd_pcm_ioplug_avail(io, pw->hw_ptr, io->appl_ptr); seq2 = SPA_SEQ_READ(pw->seq); } while (!SPA_SEQ_READ_SUCCESS(seq1, seq2)); if (now != 0 && (io->state == SND_PCM_STATE_RUNNING || io->state == SND_PCM_STATE_DRAINING)) { diff = pw_stream_get_nsec(pw->stream) - now; elapsed = (io->rate * diff) / SPA_NSEC_PER_SEC; if (io->stream == SND_PCM_STREAM_PLAYBACK) delay -= SPA_MIN(elapsed, delay); else delay += SPA_MIN(elapsed, (int64_t)io->buffer_size); } *delayp = delay + avail; pw_log_trace("avail:%"PRIi64" filled %"PRIi64" elapsed:%"PRIi64" delay:%ld hw:%lu appl:%lu", avail, delay, elapsed, *delayp, pw->hw_ptr, io->appl_ptr); return 0; } static snd_pcm_uframes_t snd_pcm_pipewire_process(snd_pcm_pipewire_t *pw, struct pw_buffer *b, snd_pcm_uframes_t *hw_avail,snd_pcm_uframes_t want) { snd_pcm_ioplug_t *io = &pw->io; snd_pcm_channel_area_t *pwareas; snd_pcm_uframes_t xfer = 0; snd_pcm_uframes_t nframes; unsigned int channel; struct spa_data *d; void *ptr; uint32_t bl, offset, size; d = b->buffer->datas; pwareas = alloca(io->channels * sizeof(snd_pcm_channel_area_t)); for (bl = 0; bl < pw->blocks; bl++) { if (io->stream == SND_PCM_STREAM_PLAYBACK) { size = SPA_MIN(d[bl].maxsize, pw->min_avail * pw->stride); } else { offset = SPA_MIN(d[bl].chunk->offset, d[bl].maxsize); size = SPA_MIN(d[bl].chunk->size, d[bl].maxsize - offset); } want = SPA_MIN(want, size / pw->stride); } nframes = SPA_MIN(want, *hw_avail); if (pw->blocks == 1) { if (io->stream == SND_PCM_STREAM_PLAYBACK) { d[0].chunk->size = want * pw->stride; d[0].chunk->offset = offset = 0; } else { offset = SPA_MIN(d[0].chunk->offset, d[0].maxsize); } ptr = SPA_PTROFF(d[0].data, offset, void); for (channel = 0; channel < io->channels; channel++) { pwareas[channel].addr = ptr; pwareas[channel].first = channel * pw->sample_bits; pwareas[channel].step = io->channels * pw->sample_bits; } } else { for (channel = 0; channel < io->channels; channel++) { if (io->stream == SND_PCM_STREAM_PLAYBACK) { d[channel].chunk->size = want * pw->stride; d[channel].chunk->offset = offset = 0; } else { offset = SPA_MIN(d[channel].chunk->offset, d[channel].maxsize); } ptr = SPA_PTROFF(d[channel].data, offset, void); pwareas[channel].addr = ptr; pwareas[channel].first = 0; pwareas[channel].step = pw->sample_bits; } } if (io->state == SND_PCM_STATE_RUNNING || io->state == SND_PCM_STATE_DRAINING) { snd_pcm_uframes_t hw_ptr = pw->hw_ptr; xfer = nframes; if (xfer > 0) { const snd_pcm_channel_area_t *areas = snd_pcm_ioplug_mmap_areas(io); if (areas != NULL) { const snd_pcm_uframes_t offset = hw_ptr % io->buffer_size; if (io->stream == SND_PCM_STREAM_PLAYBACK) snd_pcm_areas_copy_wrap(pwareas, 0, nframes, areas, offset, io->buffer_size, io->channels, xfer, io->format); else snd_pcm_areas_copy_wrap(areas, offset, io->buffer_size, pwareas, 0, nframes, io->channels, xfer, io->format); } hw_ptr += xfer; if (hw_ptr >= pw->boundary) hw_ptr -= pw->boundary; pw->hw_ptr = hw_ptr; *hw_avail -= xfer; } } /* check if requested frames were copied */ if (xfer < want) { /* always fill the not yet written PipeWire buffer with silence */ if (io->stream == SND_PCM_STREAM_PLAYBACK) { const snd_pcm_uframes_t frames = want - xfer; snd_pcm_areas_silence(pwareas, xfer, io->channels, frames, io->format); xfer += frames; } if (io->state == SND_PCM_STATE_RUNNING || io->state == SND_PCM_STATE_DRAINING) { /* report Xrun to user application */ pw->xrun_detected = true; } } return xfer; } static void on_stream_param_changed(void *data, uint32_t id, const struct spa_pod *param) { snd_pcm_pipewire_t *pw = data; snd_pcm_ioplug_t *io = &pw->io; const struct spa_pod *params[4]; uint32_t n_params = 0; uint8_t buffer[4096]; struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); uint32_t buffers, size; if (param == NULL || id != SPA_PARAM_Format) return; if (spa_format_audio_parse(param, &pw->format) < 0) { pw->error = -EINVAL; } else { switch (pw->format.media_subtype) { case SPA_MEDIA_SUBTYPE_raw: break; case SPA_MEDIA_SUBTYPE_dsd: if (pw->format.info.dsd.interleave != pw->requested.info.dsd.interleave || pw->format.info.dsd.bitorder != pw->requested.info.dsd.bitorder) { pw->error = -EINVAL; } break; } } if (pw->error < 0) { pw_thread_loop_signal(pw->main_loop, false); return; } io->period_size = pw->min_avail; buffers = SPA_CLAMP(io->buffer_size / io->period_size, MIN_BUFFERS, MAX_BUFFERS); size = io->period_size * pw->stride; pw_log_info("%p: buffer_size:%lu period_size:%lu buffers:%u size:%u min_avail:%lu", pw, io->buffer_size, io->period_size, buffers, size, pw->min_avail); params[n_params++] = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers, SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(buffers, MIN_BUFFERS, MAX_BUFFERS), SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(pw->blocks), SPA_PARAM_BUFFERS_size, SPA_POD_CHOICE_RANGE_Int(size, size, INT_MAX), SPA_PARAM_BUFFERS_stride, SPA_POD_Int(pw->stride)); pw_stream_update_params(pw->stream, params, n_params); pw->negotiated = true; pw_thread_loop_signal(pw->main_loop, false); } static void on_stream_state_changed(void *data, enum pw_stream_state old, enum pw_stream_state state, const char *error) { snd_pcm_pipewire_t *pw = data; if (state == PW_STREAM_STATE_ERROR) { pw_log_warn("%s", error); pw->error = -errno; update_active(&pw->io); } } static void on_stream_drained(void *data) { snd_pcm_pipewire_t *pw = data; pw->drained = true; pw->draining = false; pw_log_debug("%p: drained", pw); pw_thread_loop_signal(pw->main_loop, false); } static void on_stream_process(void *data) { snd_pcm_pipewire_t *pw = data; snd_pcm_ioplug_t *io = &pw->io; struct pw_buffer *b; snd_pcm_uframes_t hw_avail, before, want, xfer; struct pw_time pwt; int64_t delay; pw_stream_get_time_n(pw->stream, &pwt, sizeof(pwt)); delay = pwt.delay; if (pwt.rate.num != 0) delay = delay * io->rate * pwt.rate.num / pwt.rate.denom; before = hw_avail = snd_pcm_ioplug_hw_avail(io, pw->hw_ptr, io->appl_ptr); if (pw->drained) goto done; b = pw_stream_dequeue_buffer(pw->stream); if (b == NULL) return; want = b->requested ? b->requested : hw_avail; SPA_SEQ_WRITE(pw->seq); if (pw->now != pwt.now) { pw->transferred = pw->buffered; pw->buffered = 0; } xfer = snd_pcm_pipewire_process(pw, b, &hw_avail, want); pw->delay = delay; /* the buffer is now queued in the stream and consumed */ if (io->stream == SND_PCM_STREAM_PLAYBACK) pw->transferred += xfer; /* more then requested data transferred, use them in next iteration */ pw->buffered = (want == 0 || pw->transferred < want) ? 0 : (pw->transferred % want); pw->now = pwt.now; SPA_SEQ_WRITE(pw->seq); pw_log_trace("%p: avail-before:%lu avail:%lu want:%lu xfer:%lu hw:%lu appl:%lu", pw, before, hw_avail, want, xfer, pw->hw_ptr, io->appl_ptr); pw_stream_queue_buffer(pw->stream, b); if (io->state == SND_PCM_STATE_DRAINING && !pw->draining && hw_avail == 0) { if (io->stream == SND_PCM_STREAM_CAPTURE) { on_stream_drained (pw); /* since pw_stream does not call drained() for capture */ } else { pw_stream_flush(pw->stream, true); pw->draining = true; pw->drained = false; } } done: update_active(io); } static const struct pw_stream_events stream_events = { PW_VERSION_STREAM_EVENTS, .param_changed = on_stream_param_changed, .state_changed = on_stream_state_changed, .process = on_stream_process, .drained = on_stream_drained, }; static int pipewire_start(snd_pcm_pipewire_t *pw) { if (!pw->activated && pw->stream != NULL) { pw_stream_set_active(pw->stream, true); pw->activated = true; } return 0; } static int snd_pcm_pipewire_drain(snd_pcm_ioplug_t *io) { int res; snd_pcm_pipewire_t *pw = io->private_data; pw_thread_loop_lock(pw->main_loop); pw_log_debug("%p: drain", pw); pw->drained = false; pw->draining = false; pipewire_start(pw); while (!pw->drained && pw->error >= 0 && pw->activated) { pw_thread_loop_wait(pw->main_loop); } res = pw->error; pw_thread_loop_unlock(pw->main_loop); return res; } static int snd_pcm_pipewire_prepare(snd_pcm_ioplug_t *io) { snd_pcm_pipewire_t *pw = io->private_data; snd_pcm_sw_params_t *swparams; const struct spa_pod *params[1]; uint8_t buffer[1024]; struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); uint32_t min_period; pw_thread_loop_lock(pw->main_loop); snd_pcm_sw_params_alloca(&swparams); if (snd_pcm_sw_params_current(io->pcm, swparams) == 0) { int event; snd_pcm_sw_params_get_period_event(swparams, &event); if (event) pw->min_avail = io->period_size; else snd_pcm_sw_params_get_avail_min(swparams, &pw->min_avail); snd_pcm_sw_params_get_boundary(swparams, &pw->boundary); snd_pcm_sw_params_dump(swparams, pw->output); fflush(pw->log_file); } else { pw->min_avail = io->period_size; pw->boundary = io->buffer_size; } min_period = (MIN_PERIOD * io->rate / 48000); pw->min_avail = SPA_MAX(pw->min_avail, min_period); pw_log_debug("%p: prepare error:%d stream:%p buffer-size:%lu " "period-size:%lu min-avail:%ld", pw, pw->error, pw->stream, io->buffer_size, io->period_size, pw->min_avail); if (pw->error >= 0 && pw->stream != NULL && !pw->hw_params_changed) goto done; pw->hw_params_changed = false; pw_properties_setf(pw->props, PW_KEY_NODE_LATENCY, "%lu/%u", pw->min_avail, io->rate); pw_properties_setf(pw->props, PW_KEY_NODE_RATE, "1/%u", io->rate); params[0] = spa_format_audio_build(&b, SPA_PARAM_EnumFormat, &pw->format); if (pw->stream != NULL) { pw_stream_set_active(pw->stream, false); pw_stream_update_properties(pw->stream, &pw->props->dict); pw_stream_update_params(pw->stream, params, 1); pw_stream_set_active(pw->stream, true); goto done; } pw->stream = pw_stream_new(pw->core, NULL, pw_properties_copy(pw->props)); if (pw->stream == NULL) goto error; pw_stream_add_listener(pw->stream, &pw->stream_listener, &stream_events, pw); pw->error = 0; pw->negotiated = false; pw_stream_connect(pw->stream, io->stream == SND_PCM_STREAM_PLAYBACK ? PW_DIRECTION_OUTPUT : PW_DIRECTION_INPUT, PW_ID_ANY, PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_MAP_BUFFERS | PW_STREAM_FLAG_RT_PROCESS, params, 1); done: pw->hw_ptr = 0; pw->now = 0; pw->xrun_detected = false; pw->drained = false; pw->draining = false; while (!pw->negotiated && pw->error >= 0) pw_thread_loop_wait(pw->main_loop); if (pw->error < 0) goto error; pw_thread_loop_unlock(pw->main_loop); return 0; error: pw_thread_loop_unlock(pw->main_loop); return pw->error < 0 ? pw->error : -ENOMEM; } static int snd_pcm_pipewire_start(snd_pcm_ioplug_t *io) { snd_pcm_pipewire_t *pw = io->private_data; pw_thread_loop_lock(pw->main_loop); pw_log_debug("%p: start", pw); pipewire_start(pw); pw_thread_loop_unlock(pw->main_loop); return 0; } static int snd_pcm_pipewire_stop(snd_pcm_ioplug_t *io) { snd_pcm_pipewire_t *pw = io->private_data; pw_log_debug("%p: stop", pw); update_active(io); pw_thread_loop_lock(pw->main_loop); if (pw->activated && pw->stream != NULL) { pw_stream_set_active(pw->stream, false); pw->activated = false; pw_thread_loop_signal(pw->main_loop, false); } pw_thread_loop_unlock(pw->main_loop); return 0; } static int snd_pcm_pipewire_pause(snd_pcm_ioplug_t * io, int enable) { pw_log_debug("%p: pause", io); if (enable) snd_pcm_pipewire_stop(io); else snd_pcm_pipewire_start(io); return 0; } #if __BYTE_ORDER == __BIG_ENDIAN #define _FORMAT_LE(p, fmt) p ? SPA_AUDIO_FORMAT_UNKNOWN : SPA_AUDIO_FORMAT_ ## fmt ## _OE #define _FORMAT_BE(p, fmt) p ? SPA_AUDIO_FORMAT_ ## fmt ## P : SPA_AUDIO_FORMAT_ ## fmt #elif __BYTE_ORDER == __LITTLE_ENDIAN #define _FORMAT_LE(p, fmt) p ? SPA_AUDIO_FORMAT_ ## fmt ## P : SPA_AUDIO_FORMAT_ ## fmt #define _FORMAT_BE(p, fmt) p ? SPA_AUDIO_FORMAT_UNKNOWN : SPA_AUDIO_FORMAT_ ## fmt ## _OE #endif static int set_default_channels(uint32_t channels, uint32_t position[MAX_CHANNELS]) { switch (channels) { case 8: position[6] = SPA_AUDIO_CHANNEL_SL; position[7] = SPA_AUDIO_CHANNEL_SR; SPA_FALLTHROUGH case 6: position[5] = SPA_AUDIO_CHANNEL_LFE; SPA_FALLTHROUGH case 5: position[4] = SPA_AUDIO_CHANNEL_FC; SPA_FALLTHROUGH case 4: position[2] = SPA_AUDIO_CHANNEL_RL; position[3] = SPA_AUDIO_CHANNEL_RR; SPA_FALLTHROUGH case 2: position[0] = SPA_AUDIO_CHANNEL_FL; position[1] = SPA_AUDIO_CHANNEL_FR; return 1; case 1: position[0] = SPA_AUDIO_CHANNEL_MONO; return 1; default: return 0; } } static int snd_pcm_pipewire_hw_params(snd_pcm_ioplug_t * io, snd_pcm_hw_params_t * params) { snd_pcm_pipewire_t *pw = io->private_data; bool planar; const char *fmt_str = NULL; snd_pcm_hw_params_dump(params, pw->output); fflush(pw->log_file); pw_log_debug("%p: hw_params buffer_size:%lu period_size:%lu", pw, io->buffer_size, io->period_size); switch(io->access) { case SND_PCM_ACCESS_MMAP_INTERLEAVED: case SND_PCM_ACCESS_RW_INTERLEAVED: planar = false; break; case SND_PCM_ACCESS_MMAP_NONINTERLEAVED: case SND_PCM_ACCESS_RW_NONINTERLEAVED: planar = true; break; default: SNDERR("PipeWire: invalid access: %d\n", io->access); return -EINVAL; } pw->requested.media_type = SPA_MEDIA_TYPE_audio; switch(io->format) { case SND_PCM_FORMAT_U8: pw->requested.media_subtype = SPA_MEDIA_SUBTYPE_raw; pw->requested.info.raw.format = planar ? SPA_AUDIO_FORMAT_U8P : SPA_AUDIO_FORMAT_U8; break; case SND_PCM_FORMAT_S16_LE: pw->requested.media_subtype = SPA_MEDIA_SUBTYPE_raw; pw->requested.info.raw.format = _FORMAT_LE(planar, S16); break; case SND_PCM_FORMAT_S16_BE: pw->requested.media_subtype = SPA_MEDIA_SUBTYPE_raw; pw->requested.info.raw.format = _FORMAT_BE(planar, S16); break; case SND_PCM_FORMAT_S24_LE: pw->requested.media_subtype = SPA_MEDIA_SUBTYPE_raw; pw->requested.info.raw.format = _FORMAT_LE(planar, S24_32); break; case SND_PCM_FORMAT_S24_BE: pw->requested.media_subtype = SPA_MEDIA_SUBTYPE_raw; pw->requested.info.raw.format = _FORMAT_BE(planar, S24_32); break; case SND_PCM_FORMAT_S32_LE: pw->requested.media_subtype = SPA_MEDIA_SUBTYPE_raw; pw->requested.info.raw.format = _FORMAT_LE(planar, S32); break; case SND_PCM_FORMAT_S32_BE: pw->requested.media_subtype = SPA_MEDIA_SUBTYPE_raw; pw->requested.info.raw.format = _FORMAT_BE(planar, S32); break; case SND_PCM_FORMAT_S24_3LE: pw->requested.media_subtype = SPA_MEDIA_SUBTYPE_raw; pw->requested.info.raw.format = _FORMAT_LE(planar, S24); break; case SND_PCM_FORMAT_S24_3BE: pw->requested.media_subtype = SPA_MEDIA_SUBTYPE_raw; pw->requested.info.raw.format = _FORMAT_BE(planar, S24); break; case SND_PCM_FORMAT_FLOAT_LE: pw->requested.media_subtype = SPA_MEDIA_SUBTYPE_raw; pw->requested.info.raw.format = _FORMAT_LE(planar, F32); break; case SND_PCM_FORMAT_FLOAT_BE: pw->requested.media_subtype = SPA_MEDIA_SUBTYPE_raw; pw->requested.info.raw.format = _FORMAT_BE(planar, F32); break; case SND_PCM_FORMAT_DSD_U32_BE: pw->requested.media_subtype = SPA_MEDIA_SUBTYPE_dsd; pw->requested.info.dsd.interleave = 4; break; case SND_PCM_FORMAT_DSD_U32_LE: pw->requested.media_subtype = SPA_MEDIA_SUBTYPE_dsd; pw->requested.info.dsd.interleave = -4; break; case SND_PCM_FORMAT_DSD_U16_BE: pw->requested.media_subtype = SPA_MEDIA_SUBTYPE_dsd; pw->requested.info.dsd.interleave = 2; break; case SND_PCM_FORMAT_DSD_U16_LE: pw->requested.media_subtype = SPA_MEDIA_SUBTYPE_dsd; pw->requested.info.dsd.interleave = -2; break; case SND_PCM_FORMAT_DSD_U8: pw->requested.media_subtype = SPA_MEDIA_SUBTYPE_dsd; pw->requested.info.dsd.interleave = 1; break; default: SNDERR("PipeWire: invalid format: %d\n", io->format); return -EINVAL; } switch (pw->requested.media_subtype) { case SPA_MEDIA_SUBTYPE_raw: pw->requested.info.raw.channels = io->channels; pw->requested.info.raw.rate = io->rate; set_default_channels(io->channels, pw->requested.info.raw.position); fmt_str = spa_type_audio_format_to_short_name(pw->requested.info.raw.format); pw->format = pw->requested; break; case SPA_MEDIA_SUBTYPE_dsd: pw->requested.info.dsd.bitorder = SPA_PARAM_BITORDER_msb; pw->requested.info.dsd.channels = io->channels; pw->requested.info.dsd.rate = io->rate * SPA_ABS(pw->requested.info.dsd.interleave); set_default_channels(io->channels, pw->requested.info.dsd.position); pw->format = pw->requested; /* we need to let the server decide these values */ pw->format.info.dsd.bitorder = 0; pw->format.info.dsd.interleave = 0; fmt_str = "DSD"; break; default: return -EIO; } pw->sample_bits = snd_pcm_format_physical_width(io->format); if (planar) { pw->blocks = io->channels; pw->stride = pw->sample_bits / 8; } else { pw->blocks = 1; pw->stride = (io->channels * pw->sample_bits) / 8; } pw->hw_params_changed = true; pw_log_info("%p: format:%s channels:%d rate:%d stride:%d blocks:%d", pw, fmt_str, io->channels, io->rate, pw->stride, pw->blocks); return 0; } static int snd_pcm_pipewire_sw_params(snd_pcm_ioplug_t * io, snd_pcm_sw_params_t * sw_params) { snd_pcm_pipewire_t *pw = io->private_data; pw_thread_loop_lock(pw->main_loop); if (pw->stream) { snd_pcm_uframes_t min_avail; snd_pcm_sw_params_get_avail_min( sw_params, &min_avail); snd_pcm_sw_params_get_boundary(sw_params, &pw->boundary); if (min_avail != pw->min_avail) { char latency[64]; struct spa_dict_item item[1]; uint32_t min_period = (MIN_PERIOD * io->rate / 48000); pw->min_avail = SPA_MAX(min_avail, min_period); spa_scnprintf(latency, sizeof(latency), "%lu/%u", pw->min_avail, io->rate); item[0] = SPA_DICT_ITEM_INIT(PW_KEY_NODE_LATENCY, latency); pw_log_debug("%p: sw_params update props %p %ld", pw, pw->stream, pw->min_avail); pw_stream_update_properties(pw->stream, &SPA_DICT_INIT(item, 1)); } } else { pw_log_debug("%p: sw_params pre-prepare noop", pw); } pw_thread_loop_unlock(pw->main_loop); return 0; } struct chmap_info { enum snd_pcm_chmap_position pos; enum spa_audio_channel channel; }; static const struct chmap_info chmap_info[] = { [SND_CHMAP_UNKNOWN] = { SND_CHMAP_UNKNOWN, SPA_AUDIO_CHANNEL_UNKNOWN }, [SND_CHMAP_NA] = { SND_CHMAP_NA, SPA_AUDIO_CHANNEL_NA }, [SND_CHMAP_MONO] = { SND_CHMAP_MONO, SPA_AUDIO_CHANNEL_MONO }, [SND_CHMAP_FL] = { SND_CHMAP_FL, SPA_AUDIO_CHANNEL_FL }, [SND_CHMAP_FR] = { SND_CHMAP_FR, SPA_AUDIO_CHANNEL_FR }, [SND_CHMAP_RL] = { SND_CHMAP_RL, SPA_AUDIO_CHANNEL_RL }, [SND_CHMAP_RR] = { SND_CHMAP_RR, SPA_AUDIO_CHANNEL_RR }, [SND_CHMAP_FC] = { SND_CHMAP_FC, SPA_AUDIO_CHANNEL_FC }, [SND_CHMAP_LFE] = { SND_CHMAP_LFE, SPA_AUDIO_CHANNEL_LFE }, [SND_CHMAP_SL] = { SND_CHMAP_SL, SPA_AUDIO_CHANNEL_SL }, [SND_CHMAP_SR] = { SND_CHMAP_SR, SPA_AUDIO_CHANNEL_SR }, [SND_CHMAP_RC] = { SND_CHMAP_RC, SPA_AUDIO_CHANNEL_RC }, [SND_CHMAP_FLC] = { SND_CHMAP_FLC, SPA_AUDIO_CHANNEL_FLC }, [SND_CHMAP_FRC] = { SND_CHMAP_FRC, SPA_AUDIO_CHANNEL_FRC }, [SND_CHMAP_RLC] = { SND_CHMAP_RLC, SPA_AUDIO_CHANNEL_RLC }, [SND_CHMAP_RRC] = { SND_CHMAP_RRC, SPA_AUDIO_CHANNEL_RRC }, [SND_CHMAP_FLW] = { SND_CHMAP_FLW, SPA_AUDIO_CHANNEL_FLW }, [SND_CHMAP_FRW] = { SND_CHMAP_FRW, SPA_AUDIO_CHANNEL_FRW }, [SND_CHMAP_FLH] = { SND_CHMAP_FLH, SPA_AUDIO_CHANNEL_FLH }, [SND_CHMAP_FCH] = { SND_CHMAP_FCH, SPA_AUDIO_CHANNEL_FCH }, [SND_CHMAP_FRH] = { SND_CHMAP_FRH, SPA_AUDIO_CHANNEL_FRH }, [SND_CHMAP_TC] = { SND_CHMAP_TC, SPA_AUDIO_CHANNEL_TC }, [SND_CHMAP_TFL] = { SND_CHMAP_TFL, SPA_AUDIO_CHANNEL_TFL }, [SND_CHMAP_TFR] = { SND_CHMAP_TFR, SPA_AUDIO_CHANNEL_TFR }, [SND_CHMAP_TFC] = { SND_CHMAP_TFC, SPA_AUDIO_CHANNEL_TFC }, [SND_CHMAP_TRL] = { SND_CHMAP_TRL, SPA_AUDIO_CHANNEL_TRL }, [SND_CHMAP_TRR] = { SND_CHMAP_TRR, SPA_AUDIO_CHANNEL_TRR }, [SND_CHMAP_TRC] = { SND_CHMAP_TRC, SPA_AUDIO_CHANNEL_TRC }, [SND_CHMAP_TFLC] = { SND_CHMAP_TFLC, SPA_AUDIO_CHANNEL_TFLC }, [SND_CHMAP_TFRC] = { SND_CHMAP_TFRC, SPA_AUDIO_CHANNEL_TFRC }, [SND_CHMAP_TSL] = { SND_CHMAP_TSL, SPA_AUDIO_CHANNEL_TSL }, [SND_CHMAP_TSR] = { SND_CHMAP_TSR, SPA_AUDIO_CHANNEL_TSR }, [SND_CHMAP_LLFE] = { SND_CHMAP_LLFE, SPA_AUDIO_CHANNEL_LLFE }, [SND_CHMAP_RLFE] = { SND_CHMAP_RLFE, SPA_AUDIO_CHANNEL_RLFE }, [SND_CHMAP_BC] = { SND_CHMAP_BC, SPA_AUDIO_CHANNEL_BC }, [SND_CHMAP_BLC] = { SND_CHMAP_BLC, SPA_AUDIO_CHANNEL_BLC }, [SND_CHMAP_BRC] = { SND_CHMAP_BRC, SPA_AUDIO_CHANNEL_BRC }, }; static enum snd_pcm_chmap_position channel_to_chmap(enum spa_audio_channel channel) { SPA_FOR_EACH_ELEMENT_VAR(chmap_info, info) if (info->channel == channel) return info->pos; return SND_CHMAP_UNKNOWN; } static enum spa_audio_channel chmap_to_channel(enum snd_pcm_chmap_position pos) { if (pos >= SPA_N_ELEMENTS(chmap_info)) return SPA_AUDIO_CHANNEL_UNKNOWN; return chmap_info[pos].channel; } static int snd_pcm_pipewire_set_chmap(snd_pcm_ioplug_t * io, const snd_pcm_chmap_t * map) { snd_pcm_pipewire_t *pw = io->private_data; unsigned int i; uint32_t *position; switch (pw->requested.media_subtype) { case SPA_MEDIA_SUBTYPE_raw: pw->requested.info.raw.channels = map->channels; position = pw->requested.info.raw.position; break; case SPA_MEDIA_SUBTYPE_dsd: pw->requested.info.dsd.channels = map->channels; position = pw->requested.info.dsd.position; break; default: return -EINVAL; } if (map->channels > MAX_CHANNELS) return -ENOTSUP; for (i = 0; i < map->channels; i++) { char buf[8]; position[i] = chmap_to_channel(map->pos[i]); pw_log_debug("map %d: %s / %s", i, snd_pcm_chmap_name(map->pos[i]), spa_type_audio_channel_make_short_name(position[i], buf, sizeof(buf), "UNK")); } return 1; } static snd_pcm_chmap_t * snd_pcm_pipewire_get_chmap(snd_pcm_ioplug_t * io) { snd_pcm_pipewire_t *pw = io->private_data; snd_pcm_chmap_t *map; uint32_t i, channels, *position; switch (pw->requested.media_subtype) { case SPA_MEDIA_SUBTYPE_raw: channels = pw->requested.info.raw.channels; position = pw->requested.info.raw.position; break; case SPA_MEDIA_SUBTYPE_dsd: channels = pw->requested.info.dsd.channels; position = pw->requested.info.dsd.position; break; default: return NULL; } map = calloc(1, sizeof(snd_pcm_chmap_t) + channels * sizeof(unsigned int)); map->channels = channels; for (i = 0; i < channels; i++) map->pos[i] = channel_to_chmap(position[i]); return map; } static void make_map(snd_pcm_chmap_query_t **maps, int index, int channels, ...) { va_list args; int i; maps[index] = malloc(sizeof(snd_pcm_chmap_query_t) + (channels * sizeof(unsigned int))); maps[index]->type = SND_CHMAP_TYPE_FIXED; maps[index]->map.channels = channels; va_start(args, channels); for (i = 0; i < channels; i++) maps[index]->map.pos[i] = va_arg(args, int); va_end(args); } static snd_pcm_chmap_query_t **snd_pcm_pipewire_query_chmaps(snd_pcm_ioplug_t *io) { snd_pcm_chmap_query_t **maps; maps = calloc(7, sizeof(*maps)); make_map(maps, 0, 1, SND_CHMAP_MONO); make_map(maps, 1, 2, SND_CHMAP_FL, SND_CHMAP_FR); make_map(maps, 2, 4, SND_CHMAP_FL, SND_CHMAP_FR, SND_CHMAP_RL, SND_CHMAP_RR); make_map(maps, 3, 5, SND_CHMAP_FL, SND_CHMAP_FR, SND_CHMAP_RL, SND_CHMAP_RR, SND_CHMAP_FC); make_map(maps, 4, 6, SND_CHMAP_FL, SND_CHMAP_FR, SND_CHMAP_RL, SND_CHMAP_RR, SND_CHMAP_FC, SND_CHMAP_LFE); make_map(maps, 5, 8, SND_CHMAP_FL, SND_CHMAP_FR, SND_CHMAP_RL, SND_CHMAP_RR, SND_CHMAP_FC, SND_CHMAP_LFE, SND_CHMAP_SL, SND_CHMAP_SR); return maps; } static snd_pcm_ioplug_callback_t pipewire_pcm_callback = { .close = snd_pcm_pipewire_close, .start = snd_pcm_pipewire_start, .stop = snd_pcm_pipewire_stop, .pause = snd_pcm_pipewire_pause, .pointer = snd_pcm_pipewire_pointer, .delay = snd_pcm_pipewire_delay, .drain = snd_pcm_pipewire_drain, .prepare = snd_pcm_pipewire_prepare, .poll_revents = snd_pcm_pipewire_poll_revents, .hw_params = snd_pcm_pipewire_hw_params, .sw_params = snd_pcm_pipewire_sw_params, .set_chmap = snd_pcm_pipewire_set_chmap, .get_chmap = snd_pcm_pipewire_get_chmap, .query_chmaps = snd_pcm_pipewire_query_chmaps, }; #define MAX_VALS 64 struct param_info { const char *prop; int key; #define TYPE_LIST 0 #define TYPE_MIN_MAX 1 int type; unsigned int vals[MAX_VALS]; unsigned int n_vals; int (*collect) (const char *str, int len, unsigned int *val); }; static int collect_access(const char *str, int len, unsigned int *val) { char key[64]; if (spa_json_parse_stringn(str, len, key, sizeof(key)) <= 0) return -EINVAL; if (strcasecmp(key, "MMAP_INTERLEAVED") == 0) *val = SND_PCM_ACCESS_MMAP_INTERLEAVED; else if (strcasecmp(key, "MMAP_NONINTERLEAVED") == 0) *val = SND_PCM_ACCESS_MMAP_NONINTERLEAVED; else if (strcasecmp(key, "RW_INTERLEAVED") == 0) *val = SND_PCM_ACCESS_RW_INTERLEAVED; else if (strcasecmp(key, "RW_NONINTERLEAVED") == 0) *val = SND_PCM_ACCESS_RW_NONINTERLEAVED; else return -EINVAL; return 0; } static int collect_format(const char *str, int len, unsigned int *val) { char key[64]; snd_pcm_format_t fmt; if (spa_json_parse_stringn(str, len, key, sizeof(key)) < 0) return -EINVAL; fmt = snd_pcm_format_value(key); if (fmt != SND_PCM_FORMAT_UNKNOWN) *val = fmt; else return -EINVAL; return 0; } static int collect_int(const char *str, int len, unsigned int *val) { int v; if (spa_json_parse_int(str, len, &v) > 0) *val = v; else return -EINVAL; return 0; } struct param_info infos[] = { { "alsa.access", SND_PCM_IOPLUG_HW_ACCESS, TYPE_LIST, { SND_PCM_ACCESS_MMAP_INTERLEAVED, SND_PCM_ACCESS_MMAP_NONINTERLEAVED, SND_PCM_ACCESS_RW_INTERLEAVED, SND_PCM_ACCESS_RW_NONINTERLEAVED }, 4, collect_access }, { "alsa.format", SND_PCM_IOPLUG_HW_FORMAT, TYPE_LIST, { #if __BYTE_ORDER == __LITTLE_ENDIAN SND_PCM_FORMAT_FLOAT_LE, SND_PCM_FORMAT_S32_LE, SND_PCM_FORMAT_S24_LE, SND_PCM_FORMAT_S24_3LE, SND_PCM_FORMAT_S24_3BE, SND_PCM_FORMAT_S16_LE, #elif __BYTE_ORDER == __BIG_ENDIAN SND_PCM_FORMAT_FLOAT_BE, SND_PCM_FORMAT_S32_BE, SND_PCM_FORMAT_S24_BE, SND_PCM_FORMAT_S24_3LE, SND_PCM_FORMAT_S24_3BE, SND_PCM_FORMAT_S16_BE, #endif SND_PCM_FORMAT_U8, /* we don't add DSD formats here, use alsa.formats to * force this. Because we can't convert to/from DSD, enabling this * might fail when the system has no native DSD * SND_PCM_FORMAT_DSD_U32_BE, * SND_PCM_FORMAT_DSD_U32_LE, * SND_PCM_FORMAT_DSD_U16_BE, * SND_PCM_FORMAT_DSD_U16_LE, * SND_PCM_FORMAT_DSD_U8 */ }, 7, collect_format }, { "alsa.rate", SND_PCM_IOPLUG_HW_RATE, TYPE_MIN_MAX, { 1, MAX_RATE }, 2, collect_int }, { "alsa.channels", SND_PCM_IOPLUG_HW_CHANNELS, TYPE_MIN_MAX, { 1, MAX_CHANNELS }, 2, collect_int }, { "alsa.buffer-bytes", SND_PCM_IOPLUG_HW_BUFFER_BYTES, TYPE_MIN_MAX, { MIN_BUFFER_BYTES, MAX_BUFFER_BYTES }, 2, collect_int }, { "alsa.period-bytes", SND_PCM_IOPLUG_HW_PERIOD_BYTES, TYPE_MIN_MAX, { MIN_PERIOD_BYTES, MAX_PERIOD_BYTES }, 2, collect_int }, { "alsa.periods", SND_PCM_IOPLUG_HW_PERIODS, TYPE_MIN_MAX, { MIN_BUFFERS, 1024 }, 2, collect_int }, }; static struct param_info *param_info_by_key(int key) { SPA_FOR_EACH_ELEMENT_VAR(infos, p) { if (p->key == key) return p; } return NULL; } static int parse_value(const char *str, struct param_info *info) { struct spa_json it[2]; unsigned int v; const char *val; int len; if ((len = spa_json_begin(&it[0], str, strlen(str), &val)) <= 0) return -EINVAL; if (spa_json_is_array(val, len)) { info->type = TYPE_LIST; info->n_vals = 0; spa_json_enter(&it[0], &it[1]); while ((len = spa_json_next(&it[1], &val)) > 0 && info->n_vals < MAX_VALS) { if (info->collect(val, len, &v) < 0) continue; info->vals[info->n_vals++] = v; } } else if (spa_json_is_object(val, len)) { char key[64]; info->type = TYPE_MIN_MAX; info->n_vals = 2; spa_json_enter(&it[0], &it[1]); while ((len = spa_json_object_next(&it[1], key, sizeof(key), &val)) > 0) { if (info->collect(val, len, &v) < 0) continue; if (spa_streq(key, "min")) info->vals[0] = v; else if (spa_streq(key, "max")) info->vals[1] = v; } } else if (info->collect(val, len, &v) >= 0) { info->type = TYPE_LIST; info->vals[0] = v; info->n_vals = 1; } return 0; } static int set_constraint(snd_pcm_pipewire_t *pw, int key) { struct param_info *p = param_info_by_key(key), info; const char *str; int err; if (p == NULL) return -EINVAL; info = *p; str = pw_properties_get(pw->props, p->prop); if (str != NULL) parse_value(str, &info); switch (info.type) { case TYPE_LIST: pw_log_info("%s: list %d", p->prop, info.n_vals); err = snd_pcm_ioplug_set_param_list(&pw->io, key, info.n_vals, info.vals); break; case TYPE_MIN_MAX: pw_log_info("%s: min:%u max:%u", p->prop, info.vals[0], info.vals[1]); err = snd_pcm_ioplug_set_param_minmax(&pw->io, key, info.vals[0], info.vals[1]); break; default: return -EIO; } if (err < 0) pw_log_warn("Can't set param %s: %s", info.prop, snd_strerror(err)); return err; } static int pipewire_set_hw_constraint(snd_pcm_pipewire_t *pw) { int err; if ((err = set_constraint(pw, SND_PCM_IOPLUG_HW_ACCESS)) < 0 || (err = set_constraint(pw, SND_PCM_IOPLUG_HW_FORMAT)) < 0 || (err = set_constraint(pw, SND_PCM_IOPLUG_HW_RATE)) < 0 || (err = set_constraint(pw, SND_PCM_IOPLUG_HW_CHANNELS)) < 0 || (err = set_constraint(pw, SND_PCM_IOPLUG_HW_PERIOD_BYTES)) < 0 || (err = set_constraint(pw, SND_PCM_IOPLUG_HW_BUFFER_BYTES)) < 0 || (err = set_constraint(pw, SND_PCM_IOPLUG_HW_PERIODS)) < 0) return err; return 0; } static void on_core_error(void *data, uint32_t id, int seq, int res, const char *message) { snd_pcm_pipewire_t *pw = data; pw_log_warn("%p: error id:%u seq:%d res:%d (%s): %s", pw, id, seq, res, spa_strerror(res), message); if (id == PW_ID_CORE) { pw->error = res; if (pw->fd != -1) update_active(&pw->io); } pw_thread_loop_signal(pw->main_loop, false); } static const struct pw_core_events core_events = { PW_VERSION_CORE_EVENTS, .error = on_core_error, }; static ssize_t log_write(void *cookie, const char *buf, size_t size) { int len; while (size > 0) { len = strcspn(buf, "\n"); if (len > 0) pw_log_debug("%.*s", (int)len, buf); buf += len + 1; size -= len + 1; } return size; } static cookie_io_functions_t io_funcs = { .write = log_write, }; static int execute_match(void *data, const char *location, const char *action, const char *val, size_t len) { snd_pcm_pipewire_t *pw = data; if (spa_streq(action, "update-props")) pw_properties_update_string(pw->props, val, len); return 1; } static int snd_pcm_pipewire_open(snd_pcm_t **pcmp, struct pw_properties *props, snd_pcm_stream_t stream, int mode) { snd_pcm_pipewire_t *pw; int err; const char *str, *node_name = NULL; struct pw_loop *loop; assert(pcmp); pw = calloc(1, sizeof(*pw)); if (!pw) return -ENOMEM; pw->props = props; pw->fd = -1; pw->io.poll_fd = -1; pw->log_file = fopencookie(pw, "w", io_funcs); if (pw->log_file == NULL) { pw_log_error("can't create log file: %m"); err = -errno; goto error; } if ((err = snd_output_stdio_attach(&pw->output, pw->log_file, 0)) < 0) { pw_log_error("can't attach log file: %s", snd_strerror(err)); goto error; } pw->main_loop = pw_thread_loop_new("alsa-pipewire", NULL); if (pw->main_loop == NULL) { err = -errno; goto error; } loop = pw_thread_loop_get_loop(pw->main_loop); pw->system = loop->system; if ((pw->context = pw_context_new(loop, pw_properties_new( PW_KEY_CLIENT_API, "alsa", NULL), 0)) == NULL) { err = -errno; goto error; } pw_context_conf_update_props(pw->context, "alsa.properties", pw->props); pw_context_conf_section_match_rules(pw->context, "alsa.rules", &pw_context_get_properties(pw->context)->dict, execute_match, pw); if (pw_properties_get(pw->props, PW_KEY_APP_NAME) == NULL) pw_properties_setf(pw->props, PW_KEY_APP_NAME, "PipeWire ALSA [%s]", pw_get_prgname()); if (pw_properties_get(pw->props, PW_KEY_NODE_NAME) == NULL) pw_properties_setf(pw->props, PW_KEY_NODE_NAME, "alsa_%s.%s", stream == SND_PCM_STREAM_PLAYBACK ? "playback" : "capture", pw_get_prgname()); if (pw_properties_get(pw->props, PW_KEY_NODE_DESCRIPTION) == NULL) pw_properties_setf(pw->props, PW_KEY_NODE_DESCRIPTION, "ALSA %s [%s]", stream == SND_PCM_STREAM_PLAYBACK ? "Playback" : "Capture", pw_get_prgname()); if (pw_properties_get(pw->props, PW_KEY_MEDIA_NAME) == NULL) pw_properties_setf(pw->props, PW_KEY_MEDIA_NAME, "ALSA %s", stream == SND_PCM_STREAM_PLAYBACK ? "Playback" : "Capture"); if (pw_properties_get(pw->props, PW_KEY_MEDIA_TYPE) == NULL) pw_properties_set(pw->props, PW_KEY_MEDIA_TYPE, "Audio"); if (pw_properties_get(pw->props, PW_KEY_MEDIA_CATEGORY) == NULL) pw_properties_set(pw->props, PW_KEY_MEDIA_CATEGORY, stream == SND_PCM_STREAM_PLAYBACK ? "Playback" : "Capture"); str = getenv("PIPEWIRE_ALSA"); if (str != NULL) pw_properties_update_string(pw->props, str, strlen(str)); if ((str = pw_properties_get(pw->props, "alsa.deny")) != NULL && spa_atob(str)) { err = -EACCES; goto error; } str = getenv("PIPEWIRE_NODE"); if (str != NULL && str[0]) pw_properties_set(pw->props, PW_KEY_TARGET_OBJECT, str); node_name = pw_properties_get(pw->props, PW_KEY_NODE_NAME); if (pw_properties_get(pw->props, PW_KEY_MEDIA_NAME) == NULL) pw_properties_set(pw->props, PW_KEY_MEDIA_NAME, node_name); if ((err = pw_thread_loop_start(pw->main_loop)) < 0) goto error; pw_thread_loop_lock(pw->main_loop); pw->core = pw_context_connect(pw->context, pw_properties_copy(pw->props), 0); if (pw->core == NULL) { err = -errno; pw_thread_loop_unlock(pw->main_loop); goto error; } pw_core_add_listener(pw->core, &pw->core_listener, &core_events, pw); pw_thread_loop_unlock(pw->main_loop); pw->fd = spa_system_eventfd_create(pw->system, SPA_FD_CLOEXEC | SPA_FD_NONBLOCK); if (pw->fd < 0) { err = pw->fd; goto error; } pw->io.version = SND_PCM_IOPLUG_VERSION; pw->io.name = "ALSA <-> PipeWire PCM I/O Plugin"; pw->io.callback = &pipewire_pcm_callback; pw->io.private_data = pw; pw->io.poll_fd = pw->fd; pw->io.poll_events = POLLIN; pw->io.mmap_rw = 1; #ifdef SND_PCM_IOPLUG_FLAG_BOUNDARY_WA pw->io.flags = SND_PCM_IOPLUG_FLAG_BOUNDARY_WA; #else #warning hw_ptr updates of buffer_size will not be recognized by the ALSA library. Consider to update your ALSA library. #endif pw->io.flags |= SND_PCM_IOPLUG_FLAG_MONOTONIC; if ((err = snd_pcm_ioplug_create(&pw->io, node_name, stream, mode)) < 0) goto error; if ((err = pipewire_set_hw_constraint(pw)) < 0) goto error; pw_log_debug("%p: opened name:%s stream:%s mode:%d", pw, node_name, snd_pcm_stream_name(pw->io.stream), mode); *pcmp = pw->io.pcm; return 0; error: pw_log_debug("%p: failed to open %s :%s", pw, node_name, spa_strerror(err)); snd_pcm_pipewire_free(pw); return err; } SPA_EXPORT SND_PCM_PLUGIN_DEFINE_FUNC(pipewire) { snd_config_iterator_t i, next; struct pw_properties *props; const char *str; long val; int err; pw_init(NULL, NULL); if (spa_strstartswith(pw_get_library_version(), "0.2")) return -ENOTSUP; props = pw_properties_new(NULL, NULL); if (props == NULL) return -errno; PW_LOG_TOPIC_INIT(alsa_log_topic); snd_config_for_each(i, next, conf) { snd_config_t *n = snd_config_iterator_entry(i); const char *id; if (snd_config_get_id(n, &id) < 0) continue; if (spa_streq(id, "comment") || spa_streq(id, "type") || spa_streq(id, "hint")) continue; if (spa_streq(id, "name")) { if (snd_config_get_string(n, &str) == 0) pw_properties_set(props, PW_KEY_NODE_NAME, str); continue; } if (spa_streq(id, "server")) { if (snd_config_get_string(n, &str) == 0) pw_properties_set(props, PW_KEY_REMOTE_NAME, str); continue; } if (spa_streq(id, "playback_node")) { if (stream == SND_PCM_STREAM_PLAYBACK && snd_config_get_string(n, &str) == 0) if (str != NULL && !spa_streq(str, "-1")) pw_properties_set(props, PW_KEY_TARGET_OBJECT, str); continue; } if (spa_streq(id, "capture_node")) { if (stream == SND_PCM_STREAM_CAPTURE && snd_config_get_string(n, &str) == 0) if (str != NULL && !spa_streq(str, "-1")) pw_properties_set(props, PW_KEY_TARGET_OBJECT, str); continue; } if (spa_streq(id, "role")) { if (snd_config_get_string(n, &str) == 0) if (str != NULL && *str) pw_properties_set(props, PW_KEY_MEDIA_ROLE, str); continue; } if (spa_streq(id, "exclusive")) { if (snd_config_get_bool(n)) pw_properties_set(props, PW_KEY_NODE_EXCLUSIVE, "true"); continue; } if (spa_streq(id, "rate")) { if (snd_config_get_integer(n, &val) == 0) { if (val != 0) pw_properties_setf(props, "alsa.rate", "%ld", val); } else { SNDERR("%s: invalid type", id); } continue; } if (spa_streq(id, "format")) { if (snd_config_get_string(n, &str) == 0) { if (str != NULL && *str) pw_properties_set(props, "alsa.format", str); } else { SNDERR("%s: invalid type", id); } continue; } if (spa_streq(id, "channels")) { if (snd_config_get_integer(n, &val) == 0) { if (val != 0) pw_properties_setf(props, "alsa.channels", "%ld", val); } else { SNDERR("%s: invalid type", id); } continue; } if (spa_streq(id, "period_bytes")) { if (snd_config_get_integer(n, &val) == 0) { if (val != 0) pw_properties_setf(props, "alsa.period-bytes", "%ld", val); } else { SNDERR("%s: invalid type", id); } continue; } if (spa_streq(id, "buffer_bytes")) { long val; if (snd_config_get_integer(n, &val) == 0) { if (val != 0) pw_properties_setf(props, "alsa.buffer-bytes", "%ld", val); } else { SNDERR("%s: invalid type", id); } continue; } SNDERR("Unknown field %s", id); pw_properties_free(props); return -EINVAL; } err = snd_pcm_pipewire_open(pcmp, props, stream, mode); return err; } SPA_EXPORT SND_PCM_PLUGIN_SYMBOL(pipewire); pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/pipewire-alsa/conf/000077500000000000000000000000001511204443500241655ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/pipewire-alsa/conf/50-pipewire.conf000066400000000000000000000034621511204443500271070ustar00rootroot00000000000000# Add a specific named PipeWire pcm defaults.pipewire.server "pipewire-0" defaults.pipewire.node "-1" defaults.pipewire.exclusive false defaults.pipewire.role "" defaults.pipewire.rate 0 defaults.pipewire.format "" defaults.pipewire.channels 0 defaults.pipewire.period_bytes 0 defaults.pipewire.buffer_bytes 0 pcm.pipewire { @args [ SERVER NODE EXCLUSIVE ROLE RATE FORMAT CHANNELS PERIOD_BYTES BUFFER_BYTES ] @args.SERVER { type string default { @func refer name defaults.pipewire.server } } @args.NODE { type string default { @func refer name defaults.pipewire.node } } @args.EXCLUSIVE { type integer default { @func refer name defaults.pipewire.exclusive } } @args.ROLE { type string default { @func refer name defaults.pipewire.role } } @args.RATE { type integer default { @func refer name defaults.pipewire.rate } } @args.FORMAT { type string default { @func refer name defaults.pipewire.format } } @args.CHANNELS { type integer default { @func refer name defaults.pipewire.channels } } @args.PERIOD_BYTES { type integer default { @func refer name defaults.pipewire.period_bytes } } @args.BUFFER_BYTES { type integer default { @func refer name defaults.pipewire.buffer_bytes } } type pipewire server $SERVER playback_node $NODE capture_node $NODE exclusive $EXCLUSIVE role $ROLE rate $RATE format $FORMAT channels $CHANNELS period_bytes $PERIOD_BYTES buffer_bytes $BUFFER_BYTES hint { show on description "PipeWire Sound Server" } } ctl.pipewire { @args.SERVER { type string default { @func refer name defaults.pipewire.server } } type pipewire server $SERVER } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/pipewire-alsa/conf/99-pipewire-default.conf000066400000000000000000000003421511204443500305400ustar00rootroot00000000000000pcm.!default { type pipewire playback_node "-1" capture_node "-1" hint { show on description "Default ALSA Output (currently PipeWire Media Server)" } } ctl.!default { type pipewire } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/pipewire-alsa/conf/meson.build000066400000000000000000000002271511204443500263300ustar00rootroot00000000000000alsaconfdir = pipewire_datadir / 'alsa' / 'alsa.conf.d' install_data(['50-pipewire.conf', '99-pipewire-default.conf'], install_dir: alsaconfdir, ) pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/pipewire-alsa/tests/000077500000000000000000000000001511204443500244025ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/pipewire-alsa/tests/meson.build000066400000000000000000000011541511204443500265450ustar00rootroot00000000000000test_apps = [ [ 'test-pipewire-alsa-stress', [alsa_dep, pthread_lib] ], ] foreach a : test_apps executable('pw-' + a[0], a[0] + '.c', dependencies : a[1], include_directories: [includes_inc], install : installed_tests_enabled, install_dir : installed_tests_execdir ) if installed_tests_enabled test_conf = configuration_data() test_conf.set('exec', installed_tests_execdir / 'pw-' + a[0]) configure_file( input: installed_tests_template, output: 'pw-' + a[0] + '.test', install_dir: installed_tests_metadir, configuration: test_conf ) endif endforeach test-pipewire-alsa-stress.c000066400000000000000000000056021511204443500315320ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/pipewire-alsa/tests/* PipeWire */ /* SPDX-FileCopyrightText: Copyright © 2021 Axis Communications AB */ /* SPDX-License-Identifier: MIT */ /* [title] Stress test using pipewire-alsa. [title] */ #include #include #include #include #define DEFAULT_PCM "pipewire" #define DEFAULT_RATE 44100 #define DEFAULT_CHANNELS 2 #define N_THREADS 20 static void * thread_func(void *data) { snd_pcm_t *pcm = NULL; snd_pcm_hw_params_t *params; int res; long n = (long)data; unsigned int sample_rate = DEFAULT_RATE; res = snd_pcm_open(&pcm, DEFAULT_PCM, SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK); if (res < 0) { fprintf(stderr, "open failed: %s\n", snd_strerror(res)); pcm = NULL; goto fail; } printf("opened %ld\n", n); snd_pcm_hw_params_alloca(¶ms); res = snd_pcm_hw_params_any(pcm, params); if (res < 0) { fprintf(stderr, "params_any failed: %s\n", snd_strerror(res)); goto fail; } res = snd_pcm_hw_params_set_access(pcm, params, SND_PCM_ACCESS_RW_INTERLEAVED); if (res < 0) { fprintf(stderr, "set_access failed: %s\n", snd_strerror(res)); goto fail; } res = snd_pcm_hw_params_set_format(pcm, params, SND_PCM_FORMAT_S32_LE); if (res < 0) { fprintf(stderr, "set_format failed: %s\n", snd_strerror(res)); goto fail; } res = snd_pcm_hw_params_set_rate_near(pcm, params, &sample_rate, 0); if (res < 0) { fprintf(stderr, "set_rate_near failed: %s\n", snd_strerror(res)); goto fail; } res = snd_pcm_hw_params_set_channels(pcm, params, DEFAULT_CHANNELS); if (res < 0) { fprintf(stderr, "set_channels failed: %s\n", snd_strerror(res)); goto fail; } res = snd_pcm_hw_params(pcm, params); if (res < 0) { fprintf(stderr, "params failed: %s\n", snd_strerror(res)); goto fail; } res = snd_pcm_prepare(pcm); if (res < 0) { fprintf(stderr, "prepare failed: %s (%d)\n", snd_strerror(res), res); goto fail; } printf("prepared %ld\n", n); res = snd_pcm_close(pcm); if (res < 0) { fprintf(stderr, "close failed: %s\n", snd_strerror(res)); pcm = NULL; goto fail; } printf("closed %ld\n", n); return NULL; fail: if (pcm != NULL) { res = snd_pcm_close(pcm); if (res < 0) { fprintf(stderr, "close failed: %s\n", snd_strerror(res)); } } exit(EXIT_FAILURE); } int main(int argc, char *argv[]) { pthread_t t[N_THREADS] = { 0 }; long n; int s; /* avoid rtkit in this test */ setenv("PIPEWIRE_CONFIG_NAME", "client.conf", false); while (true) { for (n=0; n < N_THREADS; n++) { if ((s = pthread_create(&(t[n]), NULL, thread_func, (void *)n)) != 0) { fprintf(stderr, "pthread_create: %s\n", strerror(s)); exit(EXIT_FAILURE); } printf("created %ld\n", n); } for (n=0; n < N_THREADS; n++) { if (t[n] != 0 && (s = pthread_join(t[n], NULL)) != 0) { fprintf(stderr, "pthread_join: %s\n", strerror(s)); exit(EXIT_FAILURE); } printf("joined %ld\n", n); t[n] = 0; } } return EXIT_SUCCESS; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/pipewire-jack/000077500000000000000000000000001511204443500232305ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/pipewire-jack/examples/000077500000000000000000000000001511204443500250465ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/pipewire-jack/examples/ump-source.c000066400000000000000000000057201511204443500273150ustar00rootroot00000000000000/* PipeWire */ /* SPDX-FileCopyrightText: Copyright © 2019 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #include #include #include #define MAX_BUFFERS 64 struct data { const char *path; jack_client_t *client; const char *client_name; jack_port_t *out_port; int cycle; uint64_t position; uint64_t next_sample; uint64_t period; }; static int process (jack_nframes_t nframes, void *arg) { struct data *d = (struct data*)arg; void *buf; uint32_t event[2]; buf = jack_port_get_buffer (d->out_port, nframes); jack_midi_clear_buffer(buf); while (d->position >= d->next_sample && d->position + nframes > d->next_sample) { uint64_t pos = d->position - d->next_sample; if (d->cycle == 0) { /* MIDI 2.0 note on, channel 0, middle C, max velocity, no attribute */ event[0] = 0x40903c00; event[1] = 0xffff0000; } else { /* MIDI 2.0 note off, channel 0, middle C, max velocity, no attribute */ event[0] = 0x40803c00; event[1] = 0xffff0000; } d->cycle ^= 1; jack_midi_event_write(buf, pos, (const jack_midi_data_t *) event, sizeof(event)); d->next_sample += d->period; } d->position += nframes; return 0; } int main(int argc, char *argv[]) { struct data data = { 0, }; jack_options_t options = JackNullOption; jack_status_t status; data.client = jack_client_open ("ump-source", options, &status); if (data.client == NULL) { fprintf (stderr, "jack_client_open() failed, " "status = 0x%2.0x\n", status); if (status & JackServerFailed) { fprintf (stderr, "Unable to connect to JACK server\n"); } exit (1); } if (status & JackServerStarted) { fprintf (stderr, "JACK server started\n"); } if (status & JackNameNotUnique) { data.client_name = jack_get_client_name(data.client); fprintf (stderr, "unique name `%s' assigned\n", data.client_name); } /* send 2 events per second */ data.period = jack_get_sample_rate(data.client) / 2; jack_set_process_callback (data.client, process, &data); /* the UMP port type allows both sending and receiving of UMP * messages, which can contain MIDI 1.0 and MIDI 2.0 messages. */ data.out_port = jack_port_register (data.client, "output", JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput | JackPortIsMIDI2, 0); if (data.out_port == NULL) { fprintf(stderr, "no more JACK ports available\n"); exit (1); } if (jack_activate (data.client)) { fprintf (stderr, "cannot activate client"); exit (1); } while (1) { sleep (1); } jack_client_close (data.client); return 0; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/pipewire-jack/examples/video-dsp-play.c000066400000000000000000000107111511204443500300470ustar00rootroot00000000000000/* PipeWire */ /* SPDX-FileCopyrightText: Copyright © 2019 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #include #include #include #include #define MAX_BUFFERS 64 #define JACK_DEFAULT_VIDEO_TYPE "32 bit float RGBA video" #define CLAMP(v,low,high) \ ({ \ __typeof__(v) _v = (v); \ __typeof__(low) _low = (low); \ __typeof__(high) _high = (high); \ (_v < _low) ? _low : (_v > _high) ? _high : _v; \ }) struct pixel { float r, g, b, a; }; struct data { const char *path; SDL_Renderer *renderer; SDL_Window *window; SDL_Texture *texture; SDL_Texture *cursor; jack_client_t *client; const char *client_name; jack_port_t *in_port; jack_image_size_t size; int counter; SDL_Rect rect; SDL_Rect cursor_rect; }; static void handle_events(struct data *data) { SDL_Event event; while (SDL_PollEvent(&event)) { switch (event.type) { case SDL_QUIT: exit(0); break; } } } static int process (jack_nframes_t nframes, void *arg) { struct data *data = (struct data*)arg; void *sdata, *ddata; int sstride, dstride; uint32_t i, j; uint8_t *src, *dst; sdata = jack_port_get_buffer (data->in_port, nframes); handle_events(data); if (SDL_LockTexture(data->texture, NULL, &ddata, &dstride) < 0) { fprintf(stderr, "Couldn't lock texture: %s\n", SDL_GetError()); goto done; } /* copy video image in texture */ sstride = data->size.stride; src = sdata; dst = ddata; for (i = 0; i < data->size.height; i++) { struct pixel *p = (struct pixel *) src; for (j = 0; j < data->size.width; j++) { dst[j * 4 + 0] = CLAMP(lrintf(p[j].r * 255.0f), 0, 255); dst[j * 4 + 1] = CLAMP(lrintf(p[j].g * 255.0f), 0, 255); dst[j * 4 + 2] = CLAMP(lrintf(p[j].b * 255.0f), 0, 255); dst[j * 4 + 3] = CLAMP(lrintf(p[j].a * 255.0f), 0, 255); } src += sstride; dst += dstride; } SDL_UnlockTexture(data->texture); SDL_RenderClear(data->renderer); SDL_RenderCopy(data->renderer, data->texture, &data->rect, NULL); SDL_RenderPresent(data->renderer); done: return 0; } int main(int argc, char *argv[]) { struct data data = { 0, }; jack_options_t options = JackNullOption; jack_status_t status; int res; data.client = jack_client_open ("video-dsp-play", options, &status); if (data.client == NULL) { fprintf (stderr, "jack_client_open() failed, " "status = 0x%2.0x\n", status); if (status & JackServerFailed) { fprintf (stderr, "Unable to connect to JACK server\n"); } exit (1); } if (status & JackServerStarted) { fprintf (stderr, "JACK server started\n"); } if (status & JackNameNotUnique) { data.client_name = jack_get_client_name(data.client); fprintf (stderr, "unique name `%s' assigned\n", data.client_name); } jack_set_process_callback (data.client, process, &data); if ((res = jack_get_video_image_size(data.client, &data.size)) < 0) { fprintf(stderr, "can't get video size: %d %s\n", res, strerror(-res)); return -1; } if (SDL_Init(SDL_INIT_VIDEO) < 0) { fprintf(stderr, "can't initialize SDL: %s\n", SDL_GetError()); return -1; } if (SDL_CreateWindowAndRenderer (data.size.width, data.size.height, SDL_WINDOW_RESIZABLE, &data.window, &data.renderer)) { fprintf(stderr, "can't create window: %s\n", SDL_GetError()); return -1; } data.texture = SDL_CreateTexture(data.renderer, SDL_PIXELFORMAT_RGBA32, SDL_TEXTUREACCESS_STREAMING, data.size.width, data.size.height); data.rect.x = 0; data.rect.y = 0; data.rect.w = data.size.width; data.rect.h = data.size.height; data.in_port = jack_port_register (data.client, "input", JACK_DEFAULT_VIDEO_TYPE, JackPortIsInput, 0); if (data.in_port == NULL) { fprintf(stderr, "no more JACK ports available\n"); exit (1); } if (jack_activate (data.client)) { fprintf (stderr, "cannot activate client"); exit (1); } while (1) { sleep (1); } jack_client_close (data.client); SDL_DestroyTexture(data.texture); SDL_DestroyRenderer(data.renderer); SDL_DestroyWindow(data.window); return 0; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/pipewire-jack/jack/000077500000000000000000000000001511204443500241405ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/pipewire-jack/jack/control.h000066400000000000000000000421011511204443500257670ustar00rootroot00000000000000/* -*- Mode: C ; c-basic-offset: 4 -*- */ /* JACK control API Copyright (C) 2008 Nedko Arnaudov Copyright (C) 2008 GRAME This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; version 2 of the License. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ /** * @file jack/control.h * @ingroup publicheader * @brief JACK control API * */ #ifndef JACKCTL_H__2EEDAD78_DF4C_4B26_83B7_4FF1A446A47E__INCLUDED #define JACKCTL_H__2EEDAD78_DF4C_4B26_83B7_4FF1A446A47E__INCLUDED #include #include #include #if !defined(sun) && !defined(__sun__) #include #endif /** Parameter types, intentionally similar to jack_driver_param_type_t */ typedef enum { JackParamInt = 1, /**< @brief value type is a signed integer */ JackParamUInt, /**< @brief value type is an unsigned integer */ JackParamChar, /**< @brief value type is a char */ JackParamString, /**< @brief value type is a string with max size of ::JACK_PARAM_STRING_MAX+1 chars */ JackParamBool, /**< @brief value type is a boolean */ } jackctl_param_type_t; /** Driver types */ typedef enum { JackMaster = 1, /**< @brief master driver */ JackSlave /**< @brief slave driver */ } jackctl_driver_type_t; /** @brief Max value that jackctl_param_type_t type can have */ #define JACK_PARAM_MAX (JackParamBool + 1) /** @brief Max length of string parameter value, excluding terminating null char */ #define JACK_PARAM_STRING_MAX 127 /** @brief Type for parameter value */ /* intentionally similar to jack_driver_param_value_t */ union jackctl_parameter_value { uint32_t ui; /**< @brief member used for ::JackParamUInt */ int32_t i; /**< @brief member used for ::JackParamInt */ char c; /**< @brief member used for ::JackParamChar */ char str[JACK_PARAM_STRING_MAX + 1]; /**< @brief member used for ::JackParamString */ bool b; /**< @brief member used for ::JackParamBool */ }; /** opaque type for server object */ typedef struct jackctl_server jackctl_server_t; /** opaque type for driver object */ typedef struct jackctl_driver jackctl_driver_t; /** opaque type for internal client object */ typedef struct jackctl_internal jackctl_internal_t; /** opaque type for parameter object */ typedef struct jackctl_parameter jackctl_parameter_t; /** opaque type for sigmask object */ typedef struct jackctl_sigmask jackctl_sigmask_t; #ifdef __cplusplus extern "C" { #endif #if 0 } /* Adjust editor indent */ #endif /** * @defgroup ControlAPI The API for starting and controlling a JACK server * @{ */ /** * Call this function to setup process signal handling. As a general * rule, it is required for proper operation for the server object. * * @param flags signals setup flags, use 0 for none. Currently no * flags are defined * * @return the configurated signal set. */ jackctl_sigmask_t * jackctl_setup_signals( unsigned int flags); /** * Call this function to wait on a signal set. * * @param signals signals set to wait on */ void jackctl_wait_signals( jackctl_sigmask_t * signals); /** * \bold THIS FUNCTION IS DEPRECATED AND SHOULD NOT BE USED IN * NEW JACK PROJECTS * * @deprecated Please use jackctl_server_create2(). */ jackctl_server_t * jackctl_server_create( bool (* on_device_acquire)(const char * device_name), void (* on_device_release)(const char * device_name)); /** * Call this function to create server object. * * @param on_device_acquire - Optional callback to be called before device is acquired. If false is returned, device usage will fail * @param on_device_release - Optional callback to be called after device is released. * @param on_device_reservation_loop - Optional callback to be called when looping/idling the reservation. * * @return server object handle, NULL if creation of server object * failed. Successfully created server object must be destroyed with * paired call to ::jackctl_server_destroy */ jackctl_server_t * jackctl_server_create2( bool (* on_device_acquire)(const char * device_name), void (* on_device_release)(const char * device_name), void (* on_device_reservation_loop)(void)); /** * Call this function to destroy server object. * * @param server server object handle to destroy */ void jackctl_server_destroy( jackctl_server_t * server); /** * Call this function to open JACK server * * @param server server object handle * @param driver driver to use * * @return success status: true - success, false - fail */ bool jackctl_server_open( jackctl_server_t * server, jackctl_driver_t * driver); /** * Call this function to start JACK server * * @param server server object handle * * @return success status: true - success, false - fail */ bool jackctl_server_start( jackctl_server_t * server); /** * Call this function to stop JACK server * * @param server server object handle * * @return success status: true - success, false - fail */ bool jackctl_server_stop( jackctl_server_t * server); /** * Call this function to close JACK server * * @param server server object handle * * @return success status: true - success, false - fail */ bool jackctl_server_close( jackctl_server_t * server); /** * Call this function to get list of available drivers. List node data * pointers is a driver object handle (::jackctl_driver_t). * * @param server server object handle to get drivers for * * @return Single linked list of driver object handles. Must not be * modified. Always same for same server object. */ const JSList * jackctl_server_get_drivers_list( jackctl_server_t * server); /** * Call this function to get list of server parameters. List node data * pointers is a parameter object handle (::jackctl_parameter_t). * * @param server server object handle to get parameters for * * @return Single linked list of parameter object handles. Must not be * modified. Always same for same server object. */ const JSList * jackctl_server_get_parameters( jackctl_server_t * server); /** * Call this function to get list of available internal clients. List node data * pointers is a internal client object handle (::jackctl_internal_t). * * @param server server object handle to get internal clients for * * @return Single linked list of internal client object handles. Must not be * modified. Always same for same server object. */ const JSList * jackctl_server_get_internals_list( jackctl_server_t * server); /** * Call this function to load one internal client. * (can be used when the server is running) * * @param server server object handle * @param internal internal to use * * @return success status: true - success, false - fail */ bool jackctl_server_load_internal( jackctl_server_t * server, jackctl_internal_t * internal); /** * Call this function to unload one internal client. * (can be used when the server is running) * * @param server server object handle * @param internal internal to unload * * @return success status: true - success, false - fail */ bool jackctl_server_unload_internal( jackctl_server_t * server, jackctl_internal_t * internal); /** * Call this function to load a session file. * (can be used when the server is running) * * @param server server object handle * @param file the session file to load, containing a list of * internal clients and connections to be made. * * @return success status: true - success, false - fail */ bool jackctl_server_load_session_file( jackctl_server_t * server_ptr, const char * file); /** * Call this function to add a slave in the driver slave list. * (cannot be used when the server is running that is between * jackctl_server_start and jackctl_server_stop) * * @param server server object handle * @param driver driver to add in the driver slave list. * * @return success status: true - success, false - fail */ bool jackctl_server_add_slave(jackctl_server_t * server, jackctl_driver_t * driver); /** * Call this function to remove a slave from the driver slave list. * (cannot be used when the server is running that is between * jackctl_server_start and jackctl_server_stop) * * @param server server object handle * @param driver driver to remove from the driver slave list. * * @return success status: true - success, false - fail */ bool jackctl_server_remove_slave(jackctl_server_t * server, jackctl_driver_t * driver); /** * Call this function to switch master driver. * * @param server server object handle * @param driver driver to switch to * * @return success status: true - success, false - fail */ bool jackctl_server_switch_master(jackctl_server_t * server, jackctl_driver_t * driver); /** * Call this function to get name of driver. * * @param driver driver object handle to get name of * * @return driver name. Must not be modified. Always same for same * driver object. */ const char * jackctl_driver_get_name( jackctl_driver_t * driver); /** * Call this function to get type of driver. * * @param driver driver object handle to get name of * * @return driver type. Must not be modified. Always same for same * driver object. */ jackctl_driver_type_t jackctl_driver_get_type( jackctl_driver_t * driver); /** * Call this function to get list of driver parameters. List node data * pointers is a parameter object handle (::jackctl_parameter_t). * * @param driver driver object handle to get parameters for * * @return Single linked list of parameter object handles. Must not be * modified. Always same for same driver object. */ const JSList * jackctl_driver_get_parameters( jackctl_driver_t * driver); /** * Call this function to parse parameters for a driver. * * @param driver driver object handle * @param argc parameter list len * @param argv parameter list, as an array of char* * * @return success status: true - success, false - fail */ int jackctl_driver_params_parse( jackctl_driver_t * driver, int argc, char* argv[]); /** * Call this function to get name of internal client. * * @param internal internal object handle to get name of * * @return internal name. Must not be modified. Always same for same * internal object. */ const char * jackctl_internal_get_name( jackctl_internal_t * internal); /** * Call this function to get list of internal parameters. List node data * pointers is a parameter object handle (::jackctl_parameter_t). * * @param internal internal object handle to get parameters for * * @return Single linked list of parameter object handles. Must not be * modified. Always same for same internal object. */ const JSList * jackctl_internal_get_parameters( jackctl_internal_t * internal); /** * Call this function to get parameter name. * * @param parameter parameter object handle to get name of * * @return parameter name. Must not be modified. Always same for same * parameter object. */ const char * jackctl_parameter_get_name( jackctl_parameter_t * parameter); /** * Call this function to get parameter short description. * * @param parameter parameter object handle to get short description of * * @return parameter short description. Must not be modified. Always * same for same parameter object. */ const char * jackctl_parameter_get_short_description( jackctl_parameter_t * parameter); /** * Call this function to get parameter long description. * * @param parameter parameter object handle to get long description of * * @return parameter long description. Must not be modified. Always * same for same parameter object. */ const char * jackctl_parameter_get_long_description( jackctl_parameter_t * parameter); /** * Call this function to get parameter type. * * @param parameter parameter object handle to get type of * * @return parameter type. Always same for same parameter object. */ jackctl_param_type_t jackctl_parameter_get_type( jackctl_parameter_t * parameter); /** * Call this function to get parameter character. * * @param parameter parameter object handle to get character of * * @return character. */ char jackctl_parameter_get_id( jackctl_parameter_t * parameter); /** * Call this function to check whether parameter has been set, or its * default value is being used. * * @param parameter parameter object handle to check * * @return true - parameter is set, false - parameter is using default * value. */ bool jackctl_parameter_is_set( jackctl_parameter_t * parameter); /** * Call this function to reset parameter to its default value. * * @param parameter parameter object handle to reset value of * * @return success status: true - success, false - fail */ bool jackctl_parameter_reset( jackctl_parameter_t * parameter); /** * Call this function to get parameter value. * * @param parameter parameter object handle to get value of * * @return parameter value. */ union jackctl_parameter_value jackctl_parameter_get_value( jackctl_parameter_t * parameter); /** * Call this function to set parameter value. * * @param parameter parameter object handle to get value of * @param value_ptr pointer to variable containing parameter value * * @return success status: true - success, false - fail */ bool jackctl_parameter_set_value( jackctl_parameter_t * parameter, const union jackctl_parameter_value * value_ptr); /** * Call this function to get parameter default value. * * @param parameter parameter object handle to get default value of * * @return parameter default value. */ union jackctl_parameter_value jackctl_parameter_get_default_value( jackctl_parameter_t * parameter); /** * Call this function check whether parameter has range constraint. * * @param parameter object handle of parameter to check * * @return whether parameter has range constraint. */ bool jackctl_parameter_has_range_constraint( jackctl_parameter_t * parameter); /** * Call this function check whether parameter has enumeration constraint. * * @param parameter object handle of parameter to check * * @return whether parameter has enumeration constraint. */ bool jackctl_parameter_has_enum_constraint( jackctl_parameter_t * parameter); /** * Call this function get how many enumeration values parameter has. * * @param parameter object handle of parameter * * @return number of enumeration values */ uint32_t jackctl_parameter_get_enum_constraints_count( jackctl_parameter_t * parameter); /** * Call this function to get parameter enumeration value. * * @param parameter object handle of parameter * @param index index of parameter enumeration value * * @return enumeration value. */ union jackctl_parameter_value jackctl_parameter_get_enum_constraint_value( jackctl_parameter_t * parameter, uint32_t index); /** * Call this function to get parameter enumeration value description. * * @param parameter object handle of parameter * @param index index of parameter enumeration value * * @return enumeration value description. */ const char * jackctl_parameter_get_enum_constraint_description( jackctl_parameter_t * parameter, uint32_t index); /** * Call this function to get parameter range. * * @param parameter object handle of parameter * @param min_ptr pointer to variable receiving parameter minimum value * @param max_ptr pointer to variable receiving parameter maximum value */ void jackctl_parameter_get_range_constraint( jackctl_parameter_t * parameter, union jackctl_parameter_value * min_ptr, union jackctl_parameter_value * max_ptr); /** * Call this function to check whether parameter constraint is strict, * i.e. whether supplying non-matching value will not work for sure. * * @param parameter parameter object handle to check * * @return whether parameter constraint is strict. */ bool jackctl_parameter_constraint_is_strict( jackctl_parameter_t * parameter); /** * Call this function to check whether parameter has fake values, * i.e. values have no user meaningful meaning and only value * description is meaningful to user. * * @param parameter parameter object handle to check * * @return whether parameter constraint is strict. */ bool jackctl_parameter_constraint_is_fake_value( jackctl_parameter_t * parameter); /** * Call this function to log an error message. * * @param format string */ void jack_error( const char *format, ...); /** * Call this function to log an information message. * * @param format string */ void jack_info( const char *format, ...); /** * Call this function to log an information message but only when * verbose mode is enabled. * * @param format string */ void jack_log( const char *format, ...); /**@}*/ #if 0 { /* Adjust editor indent */ #endif #ifdef __cplusplus } /* extern "C" */ #endif #endif /* #ifndef JACKCTL_H__2EEDAD78_DF4C_4B26_83B7_4FF1A446A47E__INCLUDED */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/pipewire-jack/jack/intclient.h000066400000000000000000000113411511204443500263020ustar00rootroot00000000000000/* * Copyright (C) 2004 Jack O'Quin * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * */ #ifndef __jack_intclient_h__ #define __jack_intclient_h__ #ifdef __cplusplus extern "C" { #endif #include /** * Get an internal client's name. This is useful when @ref * JackUseExactName was not specified on jack_internal_client_load() * and @ref JackNameNotUnique status was returned. In that case, the * actual name will differ from the @a client_name requested. * * @param client requesting JACK client's handle. * * @param intclient handle returned from jack_internal_client_load() * or jack_internal_client_handle(). * * @return NULL if unsuccessful, otherwise pointer to the internal * client name obtained from the heap via malloc(). The caller should * jack_free() this storage when no longer needed. */ char *jack_get_internal_client_name (jack_client_t *client, jack_intclient_t intclient); /** * Return the @ref jack_intclient_t handle for an internal client * running in the JACK server. * * @param client requesting JACK client's handle. * * @param client_name for the internal client of no more than * jack_client_name_size() characters. The name scope is local to the * current server. * * @param status (if non-NULL) an address for JACK to return * information from this operation. This status word is formed by * OR-ing together the relevant @ref JackStatus bits. * * @return Opaque internal client handle if successful. If 0, the * internal client was not found, and @a *status includes the @ref * JackNoSuchClient and @ref JackFailure bits. */ jack_intclient_t jack_internal_client_handle (jack_client_t *client, const char *client_name, jack_status_t *status); /** * Load an internal client into the JACK server. * * Internal clients run inside the JACK server process. They can use * most of the same functions as external clients. Each internal * client is built as a shared object module, which must declare * jack_initialize() and jack_finish() entry points called at load and * unload times. See @ref inprocess.c for an example. * * @param client loading JACK client's handle. * * @param client_name of at most jack_client_name_size() characters * for the internal client to load. The name scope is local to the * current server. * * @param options formed by OR-ing together @ref JackOptions bits. * Only the @ref JackLoadOptions bits are valid. * * @param status (if non-NULL) an address for JACK to return * information from the load operation. This status word is formed by * OR-ing together the relevant @ref JackStatus bits. * * Optional parameters: depending on corresponding [@a options * bits] additional parameters may follow @a status (in this order). * * @arg [@ref JackLoadName] (char *) load_name is the shared * object file from which to load the new internal client (otherwise * use the @a client_name). * * @arg [@ref JackLoadInit] (char *) load_init an arbitrary * string passed to the internal client's jack_initialize() routine * (otherwise NULL), of no more than @ref JACK_LOAD_INIT_LIMIT bytes. * * @return Opaque internal client handle if successful. If this is 0, * the load operation failed, the internal client was not loaded, and * @a *status includes the @ref JackFailure bit. */ jack_intclient_t jack_internal_client_load (jack_client_t *client, const char *client_name, jack_options_t options, jack_status_t *status, ...); /** * Unload an internal client from a JACK server. This calls the * intclient's jack_finish() entry point then removes it. See @ref * inprocess.c for an example. * * @param client unloading JACK client's handle. * * @param intclient handle returned from jack_internal_client_load() or * jack_internal_client_handle(). * * @return 0 if successful, otherwise @ref JackStatus bits. */ jack_status_t jack_internal_client_unload (jack_client_t *client, jack_intclient_t intclient); #ifdef __cplusplus } #endif #endif /* __jack_intclient_h__ */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/pipewire-jack/jack/jack.h000066400000000000000000001553651511204443500252400ustar00rootroot00000000000000/* Copyright (C) 2001 Paul Davis Copyright (C) 2004 Jack O'Quin This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #ifndef __jack_h__ #define __jack_h__ #ifdef __cplusplus extern "C" { #endif #include #include #include /** * Note: More documentation can be found in jack/types.h. */ /************************************************************* * NOTE: JACK_WEAK_EXPORT ***MUST*** be used on every function * added to the JACK API after the 0.116.2 release. * * Functions that predate this release are marked with * JACK_WEAK_OPTIONAL_EXPORT which can be defined at compile * time in a variety of ways. The default definition is empty, * so that these symbols get normal linkage. If you wish to * use all JACK symbols with weak linkage, include * before jack.h. *************************************************************/ #include /** * Call this function to get version of the JACK, in form of several numbers * * @param major_ptr pointer to variable receiving major version of JACK. * * @param minor_ptr pointer to variable receiving minor version of JACK. * * @param major_ptr pointer to variable receiving micro version of JACK. * * @param major_ptr pointer to variable receiving protocol version of JACK. * */ void jack_get_version( int *major_ptr, int *minor_ptr, int *micro_ptr, int *proto_ptr) JACK_OPTIONAL_WEAK_EXPORT; /** * Call this function to get version of the JACK, in form of a string * * @return Human readable string describing JACK version being used. * */ const char * jack_get_version_string(void) JACK_OPTIONAL_WEAK_EXPORT; /** * @defgroup ClientFunctions Creating & manipulating clients * @{ */ /** * Open an external client session with a JACK server. This interface * is more complex but more powerful than jack_client_new(). With it, * clients may choose which of several servers to connect, and control * whether and how to start the server automatically, if it was not * already running. There is also an option for JACK to generate a * unique client name, when necessary. * * @param client_name of at most jack_client_name_size() characters. * The name scope is local to each server. Unless forbidden by the * @ref JackUseExactName option, the server will modify this name to * create a unique variant, if needed. * * @param options formed by OR-ing together @ref JackOptions bits. * Only the @ref JackOpenOptions bits are allowed. * * @param status (if non-NULL) an address for JACK to return * information from the open operation. This status word is formed by * OR-ing together the relevant @ref JackStatus bits. * * * Optional parameters: depending on corresponding [@a options * bits] additional parameters may follow @a status (in this order). * * @arg [@ref JackServerName] (char *) server_name selects * from among several possible concurrent server instances. Server * names are unique to each user. If unspecified, use "default" * unless \$JACK_DEFAULT_SERVER is defined in the process environment. * * @return Opaque client handle if successful. If this is NULL, the * open operation failed, @a *status includes @ref JackFailure and the * caller is not a JACK client. */ jack_client_t * jack_client_open (const char *client_name, jack_options_t options, jack_status_t *status, ...) JACK_OPTIONAL_WEAK_EXPORT; /** * \bold THIS FUNCTION IS DEPRECATED AND SHOULD NOT BE USED IN * NEW JACK CLIENTS * * @deprecated Please use jack_client_open(). */ jack_client_t * jack_client_new (const char *client_name) JACK_OPTIONAL_WEAK_DEPRECATED_EXPORT; /** * Disconnects an external client from a JACK server. * * @return 0 on success, otherwise a non-zero error code */ int jack_client_close (jack_client_t *client) JACK_OPTIONAL_WEAK_EXPORT; /** * @return the maximum number of characters in a JACK client name * including the final NULL character. This value is a constant. */ int jack_client_name_size (void) JACK_OPTIONAL_WEAK_EXPORT; /** * @return pointer to actual client name. This is useful when @ref * JackUseExactName is not specified on open and @ref * JackNameNotUnique status was returned. In that case, the actual * name will differ from the @a client_name requested. */ char * jack_get_client_name (jack_client_t *client) JACK_OPTIONAL_WEAK_EXPORT; /** * Get the session ID for a client name. * * The session manager needs this to reassociate a client name to the session_id. * * The caller is responsible for calling jack_free(3) on any non-NULL * returned value. */ char *jack_get_uuid_for_client_name (jack_client_t *client, const char *client_name) JACK_WEAK_EXPORT; /** * Get the client name for a session_id. * * In order to snapshot the graph connections, the session manager needs to map * session_ids to client names. * * The caller is responsible for calling jack_free(3) on any non-NULL * returned value. */ char *jack_get_client_name_by_uuid (jack_client_t *client, const char *client_uuid ) JACK_WEAK_EXPORT; /** * Load an internal client into the Jack server. * * Internal clients run inside the JACK server process. They can use * most of the same functions as external clients. Each internal * client must declare jack_initialize() and jack_finish() entry * points, called at load and unload times. See inprocess.c for an * example of how to write an internal client. * * @deprecated Please use jack_internal_client_load(). * * @param client_name of at most jack_client_name_size() characters. * * @param load_name of a shared object file containing the code for * the new client. * * @param load_init an arbitrary string passed to the jack_initialize() * routine of the new client (may be NULL). * * @return 0 if successful. */ int jack_internal_client_new (const char *client_name, const char *load_name, const char *load_init) JACK_OPTIONAL_WEAK_DEPRECATED_EXPORT; /** * Remove an internal client from a JACK server. * * @deprecated Please use jack_internal_client_unload(). */ void jack_internal_client_close (const char *client_name) JACK_OPTIONAL_WEAK_DEPRECATED_EXPORT; /** * Tell the Jack server that the program is ready to start processing * audio. * * @return 0 on success, otherwise a non-zero error code */ int jack_activate (jack_client_t *client) JACK_OPTIONAL_WEAK_EXPORT; /** * Tell the Jack server to remove this @a client from the process * graph. Also, disconnect all ports belonging to it, since inactive * clients have no port connections. * * @return 0 on success, otherwise a non-zero error code */ int jack_deactivate (jack_client_t *client) JACK_OPTIONAL_WEAK_EXPORT; /** * @return pid of client. If not available, 0 will be returned. */ int jack_get_client_pid (const char *name) JACK_OPTIONAL_WEAK_EXPORT; /** * @return the pthread ID of the thread running the JACK client side * real-time code. */ jack_native_thread_t jack_client_thread_id (jack_client_t *client) JACK_OPTIONAL_WEAK_EXPORT; /**@}*/ /** * @param client pointer to JACK client structure. * * Check if the JACK subsystem is running with -R (--realtime). * * @return 1 if JACK is running realtime, 0 otherwise */ int jack_is_realtime (jack_client_t *client) JACK_OPTIONAL_WEAK_EXPORT; /** * @defgroup NonCallbackAPI The non-callback API * @{ */ /** * \bold THIS FUNCTION IS DEPRECATED AND SHOULD NOT BE USED IN * NEW JACK CLIENTS. * * @deprecated Please use jack_cycle_wait() and jack_cycle_signal() functions. */ jack_nframes_t jack_thread_wait (jack_client_t *client, int status) JACK_OPTIONAL_WEAK_EXPORT; /** * Wait until this JACK client should process data. * * @param client - pointer to a JACK client structure * * @return the number of frames of data to process */ jack_nframes_t jack_cycle_wait (jack_client_t* client) JACK_OPTIONAL_WEAK_EXPORT; /** * Signal next clients in the graph. * * @param client - pointer to a JACK client structure * @param status - if non-zero, calling thread should exit */ void jack_cycle_signal (jack_client_t* client, int status) JACK_OPTIONAL_WEAK_EXPORT; /** * Tell the Jack server to call @a thread_callback in the RT thread. * Typical use are in conjunction with @a jack_cycle_wait and @a jack_cycle_signal functions. * The code in the supplied function must be suitable for real-time * execution. That means that it cannot call functions that might * block for a long time. This includes malloc, free, printf, * pthread_mutex_lock, sleep, wait, poll, select, pthread_join, * pthread_cond_wait, etc, etc. See * http://jackit.sourceforge.net/docs/design/design.html#SECTION00411000000000000000 * for more information. * * NOTE: this function cannot be called while the client is activated * (after jack_activate has been called.) * * @return 0 on success, otherwise a non-zero error code. */ int jack_set_process_thread(jack_client_t* client, JackThreadCallback thread_callback, void *arg) JACK_OPTIONAL_WEAK_EXPORT; /**@}*/ /** * @defgroup ClientCallbacks Setting Client Callbacks * @{ */ /** * Tell JACK to call @a thread_init_callback once just after * the creation of the thread in which all other callbacks * will be handled. * * The code in the supplied function does not need to be * suitable for real-time execution. * * NOTE: this function cannot be called while the client is activated * (after jack_activate has been called.) * * @return 0 on success, otherwise a non-zero error code, causing JACK * to remove that client from the process() graph. */ int jack_set_thread_init_callback (jack_client_t *client, JackThreadInitCallback thread_init_callback, void *arg) JACK_OPTIONAL_WEAK_EXPORT; /** * @param client pointer to JACK client structure. * @param function The jack_shutdown function pointer. * @param arg The arguments for the jack_shutdown function. * * Register a function (and argument) to be called if and when the * JACK server shuts down the client thread. The function must * be written as if it were an asynchronous POSIX signal * handler --- use only async-safe functions, and remember that it * is executed from another thread. A typical function might * set a flag or write to a pipe so that the rest of the * application knows that the JACK client thread has shut * down. * * NOTE: clients do not need to call this. It exists only * to help more complex clients understand what is going * on. It should be called before jack_client_activate(). * * NOTE: if a client calls this AND jack_on_info_shutdown(), then * in case of a client thread shutdown, the callback * passed to this function will not be called, and the one passed to * jack_on_info_shutdown() will. * * NOTE: application should typically signal another thread to correctly * finish cleanup, that is by calling "jack_client_close" * (since "jack_client_close" cannot be called directly in the context * of the thread that calls the shutdown callback). */ void jack_on_shutdown (jack_client_t *client, JackShutdownCallback shutdown_callback, void *arg) JACK_OPTIONAL_WEAK_EXPORT; /** * @param client pointer to JACK client structure. * @param function The jack_info_shutdown function pointer. * @param arg The arguments for the jack_info_shutdown function. * * Register a function (and argument) to be called if and when the * JACK server shuts down the client thread. The function must * be written as if it were an asynchronous POSIX signal * handler --- use only async-safe functions, and remember that it * is executed from another thread. A typical function might * set a flag or write to a pipe so that the rest of the * application knows that the JACK client thread has shut * down. * * NOTE: clients do not need to call this. It exists only * to help more complex clients understand what is going * on. It should be called before jack_client_activate(). * * NOTE: if a client calls this AND jack_on_shutdown(), then * in case of a client thread shutdown, the callback passed to * jack_on_info_shutdown() will be called. * * NOTE: application should typically signal another thread to correctly * finish cleanup, that is by calling "jack_client_close" * (since "jack_client_close" cannot be called directly in the context * of the thread that calls the shutdown callback). */ void jack_on_info_shutdown (jack_client_t *client, JackInfoShutdownCallback shutdown_callback, void *arg) JACK_WEAK_EXPORT; /** * Tell the Jack server to call @a process_callback whenever there is * work be done, passing @a arg as the second argument. * * The code in the supplied function must be suitable for real-time * execution. That means that it cannot call functions that might * block for a long time. This includes malloc, free, printf, * pthread_mutex_lock, sleep, wait, poll, select, pthread_join, * pthread_cond_wait, etc, etc. See * http://jackit.sourceforge.net/docs/design/design.html#SECTION00411000000000000000 * for more information. * * NOTE: this function cannot be called while the client is activated * (after jack_activate has been called.) * * @return 0 on success, otherwise a non-zero error code. */ int jack_set_process_callback (jack_client_t *client, JackProcessCallback process_callback, void *arg) JACK_OPTIONAL_WEAK_EXPORT; /** * Tell the Jack server to call @a freewheel_callback * whenever we enter or leave "freewheel" mode, passing @a * arg as the second argument. The first argument to the * callback will be non-zero if JACK is entering freewheel * mode, and zero otherwise. * * All "notification events" are received in a separated non RT thread, * the code in the supplied function does not need to be * suitable for real-time execution. * * NOTE: this function cannot be called while the client is activated * (after jack_activate has been called.) * * @return 0 on success, otherwise a non-zero error code. */ int jack_set_freewheel_callback (jack_client_t *client, JackFreewheelCallback freewheel_callback, void *arg) JACK_OPTIONAL_WEAK_EXPORT; /** * Tell JACK to call @a bufsize_callback whenever the size of the * buffer that will be passed to the @a process_callback is about to * change. Clients that depend on knowing the buffer size must supply * a @a bufsize_callback before activating themselves. * * All "notification events" are received in a separated non RT thread, * the code in the supplied function does not need to be * suitable for real-time execution. * * NOTE: this function cannot be called while the client is activated * (after jack_activate has been called.) * * @param client pointer to JACK client structure. * @param bufsize_callback function to call when the buffer size changes. * @param arg argument for @a bufsize_callback. * * @return 0 on success, otherwise a non-zero error code */ int jack_set_buffer_size_callback (jack_client_t *client, JackBufferSizeCallback bufsize_callback, void *arg) JACK_OPTIONAL_WEAK_EXPORT; /** * Tell the Jack server to call @a srate_callback whenever the system * sample rate changes. * * All "notification events" are received in a separated non RT thread, * the code in the supplied function does not need to be * suitable for real-time execution. * * NOTE: this function cannot be called while the client is activated * (after jack_activate has been called.) * * @return 0 on success, otherwise a non-zero error code */ int jack_set_sample_rate_callback (jack_client_t *client, JackSampleRateCallback srate_callback, void *arg) JACK_OPTIONAL_WEAK_EXPORT; /** * Tell the JACK server to call @a client_registration_callback whenever a * client is registered or unregistered, passing @a arg as a parameter. * * All "notification events" are received in a separated non RT thread, * the code in the supplied function does not need to be * suitable for real-time execution. * * NOTE: this function cannot be called while the client is activated * (after jack_activate has been called.) * * @return 0 on success, otherwise a non-zero error code */ int jack_set_client_registration_callback (jack_client_t *client, JackClientRegistrationCallback registration_callback, void *arg) JACK_OPTIONAL_WEAK_EXPORT; /** * Tell the JACK server to call @a registration_callback whenever a * port is registered or unregistered, passing @a arg as a parameter. * * All "notification events" are received in a separated non RT thread, * the code in the supplied function does not need to be * suitable for real-time execution. * * NOTE: this function cannot be called while the client is activated * (after jack_activate has been called.) * * @return 0 on success, otherwise a non-zero error code */ int jack_set_port_registration_callback (jack_client_t *client, JackPortRegistrationCallback registration_callback, void *arg) JACK_OPTIONAL_WEAK_EXPORT; /** * Tell the JACK server to call @a connect_callback whenever a * port is connected or disconnected, passing @a arg as a parameter. * * All "notification events" are received in a separated non RT thread, * the code in the supplied function does not need to be * suitable for real-time execution. * * NOTE: this function cannot be called while the client is activated * (after jack_activate has been called.) * * @return 0 on success, otherwise a non-zero error code */ int jack_set_port_connect_callback (jack_client_t *client, JackPortConnectCallback connect_callback, void *arg) JACK_OPTIONAL_WEAK_EXPORT; /** * Tell the JACK server to call @a rename_callback whenever a * port is renamed, passing @a arg as a parameter. * * All "notification events" are received in a separated non RT thread, * the code in the supplied function does not need to be * suitable for real-time execution. * * NOTE: this function cannot be called while the client is activated * (after jack_activate has been called.) * * @return 0 on success, otherwise a non-zero error code */ int jack_set_port_rename_callback (jack_client_t *client, JackPortRenameCallback rename_callback, void *arg) JACK_OPTIONAL_WEAK_EXPORT; /** * Tell the JACK server to call @a graph_callback whenever the * processing graph is reordered, passing @a arg as a parameter. * * All "notification events" are received in a separated non RT thread, * the code in the supplied function does not need to be * suitable for real-time execution. * * NOTE: this function cannot be called while the client is activated * (after jack_activate has been called.) * * @return 0 on success, otherwise a non-zero error code */ int jack_set_graph_order_callback (jack_client_t *client, JackGraphOrderCallback graph_callback, void *) JACK_OPTIONAL_WEAK_EXPORT; /** * Tell the JACK server to call @a xrun_callback whenever there is a * xrun, passing @a arg as a parameter. * * All "notification events" are received in a separated non RT thread, * the code in the supplied function does not need to be * suitable for real-time execution. * * NOTE: this function cannot be called while the client is activated * (after jack_activate has been called.) * * @return 0 on success, otherwise a non-zero error code */ int jack_set_xrun_callback (jack_client_t *client, JackXRunCallback xrun_callback, void *arg) JACK_OPTIONAL_WEAK_EXPORT; /** * Tell the Jack server to call @a latency_callback whenever it * is necessary to recompute the latencies for some or all * Jack ports. * * @a latency_callback will be called twice each time it is * needed, once being passed JackCaptureLatency and once * JackPlaybackLatency. See @ref LatencyFunctions for * the definition of each type of latency and related functions. * * IMPORTANT: Most JACK clients do NOT need to register a latency * callback. * * Clients that meet any of the following conditions do NOT * need to register a latency callback: * * - have only input ports * - have only output ports * - their output is totally unrelated to their input * - their output is not delayed relative to their input * (i.e. data that arrives in a given process() * callback is processed and output again in the * same callback) * * Clients NOT registering a latency callback MUST also * satisfy this condition: * * - have no multiple distinct internal signal pathways * * This means that if your client has more than 1 input and * output port, and considers them always "correlated" * (e.g. as a stereo pair), then there is only 1 (e.g. stereo) * signal pathway through the client. This would be true, * for example, of a stereo FX rack client that has a * left/right input pair and a left/right output pair. * * However, this is somewhat a matter of perspective. The * same FX rack client could be connected so that its * two input ports were connected to entirely separate * sources. Under these conditions, the fact that the client * does not register a latency callback MAY result * in port latency values being incorrect. * * Clients that do not meet any of those conditions SHOULD * register a latency callback. * * Another case is when a client wants to use * @ref jack_port_get_latency_range(), which only returns meaningful * values when ports get connected and latency values change. * * See the documentation for @ref jack_port_set_latency_range() * on how the callback should operate. Remember that the @a mode * argument given to the latency callback will need to be * passed into @ref jack_port_set_latency_range() * * @return 0 on success, otherwise a non-zero error code */ int jack_set_latency_callback (jack_client_t *client, JackLatencyCallback latency_callback, void *) JACK_WEAK_EXPORT; /**@}*/ /** * @defgroup ServerClientControl Controlling & querying JACK server operation * @{ */ /** * Start/Stop JACK's "freewheel" mode. * * When in "freewheel" mode, JACK no longer waits for * any external event to begin the start of the next process * cycle. * * As a result, freewheel mode causes "faster than realtime" * execution of a JACK graph. If possessed, real-time * scheduling is dropped when entering freewheel mode, and * if appropriate it is reacquired when stopping. * * IMPORTANT: on systems using capabilities to provide real-time * scheduling (i.e. Linux kernel 2.4), if onoff is zero, this function * must be called from the thread that originally called jack_activate(). * This restriction does not apply to other systems (e.g. Linux kernel 2.6 * or OS X). * * @param client pointer to JACK client structure * @param onoff if non-zero, freewheel mode starts. Otherwise * freewheel mode ends. * * @return 0 on success, otherwise a non-zero error code. */ int jack_set_freewheel(jack_client_t* client, int onoff) JACK_OPTIONAL_WEAK_EXPORT; /** * Change the buffer size passed to the @a process_callback. * * This operation stops the JACK engine process cycle, then calls all * registered @a bufsize_callback functions before restarting the * process cycle. This will cause a gap in the audio flow, so it * should only be done at appropriate stopping points. * * @see jack_set_buffer_size_callback() * * @param client pointer to JACK client structure. * @param nframes new buffer size. Must be a power of two. * * @return 0 on success, otherwise a non-zero error code */ int jack_set_buffer_size (jack_client_t *client, jack_nframes_t nframes) JACK_OPTIONAL_WEAK_EXPORT; /** * @return the sample rate of the jack system, as set by the user when * jackd was started. */ jack_nframes_t jack_get_sample_rate (jack_client_t *) JACK_OPTIONAL_WEAK_EXPORT; /** * @return the current maximum size that will ever be passed to the @a * process_callback. It should only be used *before* the client has * been activated. This size may change, clients that depend on it * must register a @a bufsize_callback so they will be notified if it * does. * * @see jack_set_buffer_size_callback() */ jack_nframes_t jack_get_buffer_size (jack_client_t *) JACK_OPTIONAL_WEAK_EXPORT; /** * Old-style interface to become the timebase for the entire JACK * subsystem. * * @deprecated This function still exists for compatibility with the * earlier transport interface, but it does nothing. Instead, see * transport.h and use jack_set_timebase_callback(). * * @return ENOSYS, function not implemented. */ int jack_engine_takeover_timebase (jack_client_t *) JACK_OPTIONAL_WEAK_DEPRECATED_EXPORT; /** * @return the current CPU load estimated by JACK. This is a running * average of the time it takes to execute a full process cycle for * all clients as a percentage of the real time available per cycle * determined by the buffer size and sample rate. */ float jack_cpu_load (jack_client_t *client) JACK_OPTIONAL_WEAK_EXPORT; /**@}*/ /** * @defgroup PortFunctions Creating & manipulating ports * @{ */ /** * Create a new port for the client. This is an object used for moving * data of any type in or out of the client. Ports may be connected * in various ways. * * Each port has a short name. The port's full name contains the name * of the client concatenated with a colon (:) followed by its short * name. The jack_port_name_size() is the maximum length of this full * name. Exceeding that will cause the port registration to fail and * return NULL. * * The @a port_name must be unique among all ports owned by this client. * If the name is not unique, the registration will fail. * * All ports have a type, which may be any non-NULL and non-zero * length string, passed as an argument. Some port types are built * into the JACK API, currently only JACK_DEFAULT_AUDIO_TYPE. * * @param client pointer to JACK client structure. * @param port_name non-empty short name for the new port (not * including the leading @a "client_name:"). Must be unique. * @param port_type port type name. If longer than * jack_port_type_size(), only that many characters are significant. * @param flags @ref JackPortFlags bit mask. * @param buffer_size must be non-zero if this is not a built-in @a * port_type. Otherwise, it is ignored. * * @return jack_port_t pointer on success, otherwise NULL. */ jack_port_t * jack_port_register (jack_client_t *client, const char *port_name, const char *port_type, unsigned long flags, unsigned long buffer_size) JACK_OPTIONAL_WEAK_EXPORT; /** * Remove the port from the client, disconnecting any existing * connections. * * @return 0 on success, otherwise a non-zero error code */ int jack_port_unregister (jack_client_t *client, jack_port_t *port) JACK_OPTIONAL_WEAK_EXPORT; /** * This returns a pointer to the memory area associated with the * specified port. For an output port, it will be a memory area * that can be written to; for an input port, it will be an area * containing the data from the port's connection(s), or * zero-filled. if there are multiple inbound connections, the data * will be mixed appropriately. * * FOR OUTPUT PORTS ONLY : DEPRECATED in Jack 2.0 !! * --------------------------------------------------- * You may cache the value returned, but only between calls to * your "blocksize" callback. For this reason alone, you should * either never cache the return value or ensure you have * a "blocksize" callback and be sure to invalidate the cached * address from there. * * Caching output ports is DEPRECATED in Jack 2.0, due to some new optimization (like "pipelining"). * Port buffers have to be retrieved in each callback for proper functioning. */ void * jack_port_get_buffer (jack_port_t *port, jack_nframes_t) JACK_OPTIONAL_WEAK_EXPORT; /** * @return the UUID of the jack_port_t * * @see jack_uuid_to_string() to convert into a string representation */ jack_uuid_t jack_port_uuid (const jack_port_t *port) JACK_OPTIONAL_WEAK_EXPORT; /** * @return the full name of the jack_port_t (including the @a * "client_name:" prefix). * * @see jack_port_name_size(). */ const char * jack_port_name (const jack_port_t *port) JACK_OPTIONAL_WEAK_EXPORT; /** * @return the short name of the jack_port_t (not including the @a * "client_name:" prefix). * * @see jack_port_name_size(). */ const char * jack_port_short_name (const jack_port_t *port) JACK_OPTIONAL_WEAK_EXPORT; /** * @return the @ref JackPortFlags of the jack_port_t. */ int jack_port_flags (const jack_port_t *port) JACK_OPTIONAL_WEAK_EXPORT; /** * @return the @a port type, at most jack_port_type_size() characters * including a final NULL. */ const char * jack_port_type (const jack_port_t *port) JACK_OPTIONAL_WEAK_EXPORT; /** * @return the @a port type id. */ jack_port_type_id_t jack_port_type_id (const jack_port_t *port) JACK_OPTIONAL_WEAK_EXPORT; /** * @return TRUE if the jack_port_t belongs to the jack_client_t. */ int jack_port_is_mine (const jack_client_t *client, const jack_port_t *port) JACK_OPTIONAL_WEAK_EXPORT; /** * @return number of connections to or from @a port. * * @pre The calling client must own @a port. */ int jack_port_connected (const jack_port_t *port) JACK_OPTIONAL_WEAK_EXPORT; /** * @return TRUE if the locally-owned @a port is @b directly connected * to the @a port_name. * * @see jack_port_name_size() */ int jack_port_connected_to (const jack_port_t *port, const char *port_name) JACK_OPTIONAL_WEAK_EXPORT; /** * @return a null-terminated array of full port names to which the @a * port is connected. If none, returns NULL. * * The caller is responsible for calling jack_free() on any non-NULL * returned value. * * @param port locally owned jack_port_t pointer. * * @see jack_port_name_size(), jack_port_get_all_connections() */ const char ** jack_port_get_connections (const jack_port_t *port) JACK_OPTIONAL_WEAK_EXPORT; /** * @return a null-terminated array of full port names to which the @a * port is connected. If none, returns NULL. * * The caller is responsible for calling jack_free() on any non-NULL * returned value. * * This differs from jack_port_get_connections() in two important * respects: * * 1) You may not call this function from code that is * executed in response to a JACK event. For example, * you cannot use it in a GraphReordered handler. * * 2) You need not be the owner of the port to get information * about its connections. * * @see jack_port_name_size() */ const char ** jack_port_get_all_connections (const jack_client_t *client, const jack_port_t *port) JACK_OPTIONAL_WEAK_EXPORT; /** * * @deprecated This function will be removed from a future version * of JACK. Do not use it. There is no replacement. It has * turned out to serve essentially no purpose in real-life * JACK clients. */ int jack_port_tie (jack_port_t *src, jack_port_t *dst) JACK_OPTIONAL_WEAK_DEPRECATED_EXPORT; /** * * @deprecated This function will be removed from a future version * of JACK. Do not use it. There is no replacement. It has * turned out to serve essentially no purpose in real-life * JACK clients. */ int jack_port_untie (jack_port_t *port) JACK_OPTIONAL_WEAK_DEPRECATED_EXPORT; /** * \bold THIS FUNCTION IS DEPRECATED AND SHOULD NOT BE USED IN * NEW JACK CLIENTS * * Modify a port's short name. May be called at any time. If the * resulting full name (including the @a "client_name:" prefix) is * longer than jack_port_name_size(), it will be truncated. * * @return 0 on success, otherwise a non-zero error code. */ int jack_port_set_name (jack_port_t *port, const char *port_name) JACK_OPTIONAL_WEAK_DEPRECATED_EXPORT; /** * Modify a port's short name. May NOT be called from a callback handling a server event. * If the resulting full name (including the @a "client_name:" prefix) is * longer than jack_port_name_size(), it will be truncated. * * @return 0 on success, otherwise a non-zero error code. * * This differs from jack_port_set_name() by triggering PortRename notifications to * clients that have registered a port rename handler. */ int jack_port_rename (jack_client_t* client, jack_port_t *port, const char *port_name) JACK_OPTIONAL_WEAK_EXPORT; /** * Set @a alias as an alias for @a port. May be called at any time. * If the alias is longer than jack_port_name_size(), it will be truncated. * * After a successful call, and until JACK exits or * @function jack_port_unset_alias() is called, @alias may be * used as a alternate name for the port. * * Ports can have up to two aliases - if both are already * set, this function will return an error. * * @return 0 on success, otherwise a non-zero error code. */ int jack_port_set_alias (jack_port_t *port, const char *alias) JACK_OPTIONAL_WEAK_EXPORT; /** * Remove @a alias as an alias for @a port. May be called at any time. * * After a successful call, @a alias can no longer be * used as a alternate name for the port. * * @return 0 on success, otherwise a non-zero error code. */ int jack_port_unset_alias (jack_port_t *port, const char *alias) JACK_OPTIONAL_WEAK_EXPORT; /** * Get any aliases known for @port. * * @return the number of aliases discovered for the port */ int jack_port_get_aliases (const jack_port_t *port, char* const aliases[2]) JACK_OPTIONAL_WEAK_EXPORT; /** * If @ref JackPortCanMonitor is set for this @a port, turn input * monitoring on or off. Otherwise, do nothing. */ int jack_port_request_monitor (jack_port_t *port, int onoff) JACK_OPTIONAL_WEAK_EXPORT; /** * If @ref JackPortCanMonitor is set for this @a port_name, turn input * monitoring on or off. Otherwise, do nothing. * * @return 0 on success, otherwise a non-zero error code. * * @see jack_port_name_size() */ int jack_port_request_monitor_by_name (jack_client_t *client, const char *port_name, int onoff) JACK_OPTIONAL_WEAK_EXPORT; /** * If @ref JackPortCanMonitor is set for a port, this function turns * on input monitoring if it was off, and turns it off if only one * request has been made to turn it on. Otherwise it does nothing. * * @return 0 on success, otherwise a non-zero error code */ int jack_port_ensure_monitor (jack_port_t *port, int onoff) JACK_OPTIONAL_WEAK_EXPORT; /** * @return TRUE if input monitoring has been requested for @a port. */ int jack_port_monitoring_input (jack_port_t *port) JACK_OPTIONAL_WEAK_EXPORT; /** * Establish a connection between two ports. * * When a connection exists, data written to the source port will * be available to be read at the destination port. * * @pre The port types must be identical. * * @pre The @ref JackPortFlags of the @a source_port must include @ref * JackPortIsOutput. * * @pre The @ref JackPortFlags of the @a destination_port must include * @ref JackPortIsInput. * * @return 0 on success, EEXIST if the connection is already made, * otherwise a non-zero error code */ int jack_connect (jack_client_t *client, const char *source_port, const char *destination_port) JACK_OPTIONAL_WEAK_EXPORT; /** * Remove a connection between two ports. * * @pre The port types must be identical. * * @pre The @ref JackPortFlags of the @a source_port must include @ref * JackPortIsOutput. * * @pre The @ref JackPortFlags of the @a destination_port must include * @ref JackPortIsInput. * * @return 0 on success, otherwise a non-zero error code */ int jack_disconnect (jack_client_t *client, const char *source_port, const char *destination_port) JACK_OPTIONAL_WEAK_EXPORT; /** * Perform the same function as jack_disconnect() using port handles * rather than names. This avoids the name lookup inherent in the * name-based version. * * Clients connecting their own ports are likely to use this function, * while generic connection clients (e.g. patchbays) would use * jack_disconnect(). */ int jack_port_disconnect (jack_client_t *client, jack_port_t *port) JACK_OPTIONAL_WEAK_EXPORT; /** * @return the maximum number of characters in a full JACK port name * including the final NULL character. This value is a constant. * * A port's full name contains the owning client name concatenated * with a colon (:) followed by its short name and a NULL * character. */ int jack_port_name_size(void) JACK_OPTIONAL_WEAK_EXPORT; /** * @return the maximum number of characters in a JACK port type name * including the final NULL character. This value is a constant. */ int jack_port_type_size(void) JACK_OPTIONAL_WEAK_EXPORT; /** * @return the buffersize of a port of type @arg port_type. * * this function may only be called in a buffer_size callback. */ size_t jack_port_type_get_buffer_size (jack_client_t *client, const char *port_type) JACK_WEAK_EXPORT; /**@}*/ /** * @defgroup LatencyFunctions Managing and determining latency * @{ * * The purpose of JACK's latency API is to allow clients to * easily answer two questions: * * - How long has it been since the data read from a port arrived * at the edge of the JACK graph (either via a physical port * or being synthesized from scratch)? * * - How long will it be before the data written to a port arrives * at the edge of a JACK graph? * To help answering these two questions, all JACK ports have two * latency values associated with them, both measured in frames: * * capture latency: how long since the data read from * the buffer of a port arrived at * a port marked with JackPortIsTerminal. * The data will have come from the "outside * world" if the terminal port is also * marked with JackPortIsPhysical, or * will have been synthesized by the client * that owns the terminal port. * * playback latency: how long until the data * written to the buffer of port will reach a port * marked with JackPortIsTerminal. * * Both latencies might potentially have more than one value * because there may be multiple pathways to/from a given port * and a terminal port. Latency is therefore generally * expressed a min/max pair. * * In most common setups, the minimum and maximum latency * are the same, but this design accommodates more complex * routing, and allows applications (and thus users) to * detect cases where routing is creating an anomalous * situation that may either need fixing or more * sophisticated handling by clients that care about * latency. * * See also @ref jack_set_latency_callback for details on how * clients that add latency to the signal path should interact * with JACK to ensure that the correct latency figures are * used. */ /** * The port latency is zero by default. Clients that control * physical hardware with non-zero latency should call this * to set the latency to its correct value. Note that the value * should include any systemic latency present "outside" the * physical hardware controlled by the client. For example, * for a client controlling a digital audio interface connected * to an external digital converter, the latency setting should * include both buffering by the audio interface *and* the converter. * * @deprecated This method will be removed in the next major * release of JACK. It should not be used in new code, and should * be replaced by a latency callback that calls @ref * jack_port_set_latency_range(). */ void jack_port_set_latency (jack_port_t *port, jack_nframes_t) JACK_OPTIONAL_WEAK_DEPRECATED_EXPORT; /** * return the latency range defined by @a mode for * @a port, in frames. * * See @ref LatencyFunctions for the definition of each latency value. * * This function is best used from callbacks, specifically the latency callback. * Before a port is connected, this returns the default latency: zero. * Therefore it only makes sense to call jack_port_get_latency_range() when * the port is connected, and that gets signalled by the latency callback. * See @ref jack_set_latency_callback() for details. */ void jack_port_get_latency_range (jack_port_t *port, jack_latency_callback_mode_t mode, jack_latency_range_t *range) JACK_WEAK_EXPORT; /** * set the minimum and maximum latencies defined by * @a mode for @a port, in frames. * * See @ref LatencyFunctions for the definition of each latency value. * * This function should ONLY be used inside a latency * callback. The client should determine the current * value of the latency using @ref jack_port_get_latency_range() * (called using the same mode as @a mode) * and then add some number of frames to that reflects * latency added by the client. * * How much latency a client adds will vary * dramatically. For most clients, the answer is zero * and there is no reason for them to register a latency * callback and thus they should never call this * function. * * More complex clients that take an input signal, * transform it in some way and output the result but * not during the same process() callback will * generally know a single constant value to add * to the value returned by @ref jack_port_get_latency_range(). * * Such clients would register a latency callback (see * @ref jack_set_latency_callback) and must know what input * ports feed which output ports as part of their * internal state. Their latency callback will update * the ports' latency values appropriately. * * A pseudo-code example will help. The @a mode argument to the latency * callback will determine whether playback or capture * latency is being set. The callback will use * @ref jack_port_set_latency_range() as follows: * * \code * jack_latency_range_t range; * if (mode == JackPlaybackLatency) { * foreach input_port in (all self-registered port) { * jack_port_get_latency_range (port_feeding_input_port, JackPlaybackLatency, &range); * range.min += min_delay_added_as_signal_flows_from port_feeding to input_port; * range.max += max_delay_added_as_signal_flows_from port_feeding to input_port; * jack_port_set_latency_range (input_port, JackPlaybackLatency, &range); * } * } else if (mode == JackCaptureLatency) { * foreach output_port in (all self-registered port) { * jack_port_get_latency_range (port_fed_by_output_port, JackCaptureLatency, &range); * range.min += min_delay_added_as_signal_flows_from_output_port_to_fed_by_port; * range.max += max_delay_added_as_signal_flows_from_output_port_to_fed_by_port; * jack_port_set_latency_range (output_port, JackCaptureLatency, &range); * } * } * \endcode * * In this relatively simple pseudo-code example, it is assumed that * each input port or output is connected to only 1 output or input * port respectively. * * If a port is connected to more than 1 other port, then the * range.min and range.max values passed to @ref * jack_port_set_latency_range() should reflect the minimum and * maximum values across all connected ports. * * See the description of @ref jack_set_latency_callback for more * information. */ void jack_port_set_latency_range (jack_port_t *port, jack_latency_callback_mode_t mode, jack_latency_range_t *range) JACK_WEAK_EXPORT; /** * Request a complete recomputation of all port latencies. This * can be called by a client that has just changed the internal * latency of its port using jack_port_set_latency * and wants to ensure that all signal pathways in the graph * are updated with respect to the values that will be returned * by jack_port_get_total_latency. It allows a client * to change multiple port latencies without triggering a * recompute for each change. * * @return zero for successful execution of the request. non-zero * otherwise. */ int jack_recompute_total_latencies (jack_client_t *client) JACK_OPTIONAL_WEAK_EXPORT; /** * @return the time (in frames) between data being available or * delivered at/to a port, and the time at which it arrived at or is * delivered to the "other side" of the port. E.g. for a physical * audio output port, this is the time between writing to the port and * when the signal will leave the connector. For a physical audio * input port, this is the time between the sound arriving at the * connector and the corresponding frames being readable from the * port. * * @deprecated This method will be removed in the next major * release of JACK. It should not be used in new code, and should * be replaced by jack_port_get_latency_range() in any existing * use cases. */ jack_nframes_t jack_port_get_latency (jack_port_t *port) JACK_OPTIONAL_WEAK_DEPRECATED_EXPORT; /** * The maximum of the sum of the latencies in every * connection path that can be drawn between the port and other * ports with the @ref JackPortIsTerminal flag set. * * @deprecated This method will be removed in the next major * release of JACK. It should not be used in new code, and should * be replaced by jack_port_get_latency_range() in any existing * use cases. */ jack_nframes_t jack_port_get_total_latency (jack_client_t *client, jack_port_t *port) JACK_OPTIONAL_WEAK_DEPRECATED_EXPORT; /** * Request a complete recomputation of a port's total latency. This * can be called by a client that has just changed the internal * latency of its port using jack_port_set_latency * and wants to ensure that all signal pathways in the graph * are updated with respect to the values that will be returned * by jack_port_get_total_latency. * * @return zero for successful execution of the request. non-zero * otherwise. * * @deprecated This method will be removed in the next major * release of JACK. It should not be used in new code, and should * be replaced by jack_recompute_total_latencies() in any existing * use cases. */ int jack_recompute_total_latency (jack_client_t*, jack_port_t* port) JACK_OPTIONAL_WEAK_DEPRECATED_EXPORT; /**@}*/ /** * @defgroup PortSearching Looking up ports * @{ */ /** * @param port_name_pattern A regular expression used to select * ports by name. If NULL or of zero length, no selection based * on name will be carried out. * @param type_name_pattern A regular expression used to select * ports by type. If NULL or of zero length, no selection based * on type will be carried out. * @param flags A value used to select ports by their flags. * If zero, no selection based on flags will be carried out. * * @return a NULL-terminated array of ports that match the specified * arguments. The caller is responsible for calling jack_free() any * non-NULL returned value. * * @see jack_port_name_size(), jack_port_type_size() */ const char ** jack_get_ports (jack_client_t *client, const char *port_name_pattern, const char *type_name_pattern, unsigned long flags) JACK_OPTIONAL_WEAK_EXPORT; /** * @return address of the jack_port_t named @a port_name. * * @see jack_port_name_size() */ jack_port_t * jack_port_by_name (jack_client_t *client, const char *port_name) JACK_OPTIONAL_WEAK_EXPORT; /** * @return address of the jack_port_t of a @a port_id. */ jack_port_t * jack_port_by_id (jack_client_t *client, jack_port_id_t port_id) JACK_OPTIONAL_WEAK_EXPORT; /**@}*/ /** * @defgroup TimeFunctions Handling time * @{ * * JACK time is in units of 'frames', according to the current sample rate. * The absolute value of frame times is meaningless, frame times have meaning * only relative to each other. */ /** * @return the estimated time in frames that has passed since the JACK * server began the current process cycle. */ jack_nframes_t jack_frames_since_cycle_start (const jack_client_t *) JACK_OPTIONAL_WEAK_EXPORT; /** * @return the estimated current time in frames. * This function is intended for use in other threads (not the process * callback). The return value can be compared with the value of * jack_last_frame_time to relate time in other threads to JACK time. */ jack_nframes_t jack_frame_time (const jack_client_t *) JACK_OPTIONAL_WEAK_EXPORT; /** * @return the precise time at the start of the current process cycle. * This function may only be used from the process callback, and can * be used to interpret timestamps generated by jack_frame_time() in * other threads with respect to the current process cycle. * * This is the only jack time function that returns exact time: * when used during the process callback it always returns the same * value (until the next process callback, where it will return * that value + nframes, etc). The return value is guaranteed to be * monotonic and linear in this fashion unless an xrun occurs. * If an xrun occurs, clients must check this value again, as time * may have advanced in a non-linear way (e.g. cycles may have been skipped). */ jack_nframes_t jack_last_frame_time (const jack_client_t *client) JACK_OPTIONAL_WEAK_EXPORT; /** * This function may only be used from the process callback. * It provides the internal cycle timing information as used by * most of the other time related functions. This allows the * caller to map between frame counts and microseconds with full * precision (i.e. without rounding frame times to integers), * and also provides e.g. the microseconds time of the start of * the current cycle directly (it has to be computed otherwise). * * If the return value is zero, the following information is * provided in the variables pointed to by the arguments: * * current_frames: the frame time counter at the start of the * current cycle, same as jack_last_frame_time(). * current_usecs: the microseconds time at the start of the * current cycle. * next_usecs: the microseconds time of the start of the next * next cycle as computed by the DLL. * period_usecs: the current best estimate of the period time in * microseconds. * * NOTES: * * Because of the types used, all the returned values except period_usecs * are unsigned. In computations mapping between frames and microseconds * *signed* differences are required. The easiest way is to compute those * separately and assign them to the appropriate signed variables, * int32_t for frames and int64_t for usecs. See the implementation of * jack_frames_to_time() and Jack_time_to_frames() for an example. * * Unless there was an xrun, skipped cycles, or the current cycle is the * first after freewheeling or starting Jack, the value of current_usecs * will always be the value of next_usecs of the previous cycle. * * The value of period_usecs will in general NOT be exactly equal to * the difference of next_usecs and current_usecs. This is because to * ensure stability of the DLL and continuity of the mapping, a fraction * of the loop error must be included in next_usecs. For an accurate * mapping between frames and microseconds, the difference of next_usecs * and current_usecs should be used, and not period_usecs. * * @return zero if OK, non-zero otherwise. */ int jack_get_cycle_times(const jack_client_t *client, jack_nframes_t *current_frames, jack_time_t *current_usecs, jack_time_t *next_usecs, float *period_usecs) JACK_OPTIONAL_WEAK_EXPORT; /** * @return the estimated time in microseconds of the specified frame time */ jack_time_t jack_frames_to_time(const jack_client_t *client, jack_nframes_t) JACK_OPTIONAL_WEAK_EXPORT; /** * @return the estimated time in frames for the specified system time. */ jack_nframes_t jack_time_to_frames(const jack_client_t *client, jack_time_t) JACK_OPTIONAL_WEAK_EXPORT; /** * @return return JACK's current system time in microseconds, * using the JACK clock source. * * The value returned is guaranteed to be monotonic, but not linear. */ jack_time_t jack_get_time(void) JACK_OPTIONAL_WEAK_EXPORT; /**@}*/ /** * @defgroup ErrorOutput Controlling error/information output * @{ */ /** * Display JACK error message. * * Set via jack_set_error_function(), otherwise a JACK-provided * default will print @a msg (plus a newline) to stderr. * * @param msg error message text (no newline at end). */ extern void (*jack_error_callback)(const char *msg) JACK_OPTIONAL_WEAK_EXPORT; /** * Set the @ref jack_error_callback for error message display. * Set it to NULL to restore default_jack_error_callback function. * * The JACK library provides two built-in callbacks for this purpose: * default_jack_error_callback() and silent_jack_error_callback(). */ void jack_set_error_function (void (*func)(const char *)) JACK_OPTIONAL_WEAK_EXPORT; /** * Display JACK info message. * * Set via jack_set_info_function(), otherwise a JACK-provided * default will print @a msg (plus a newline) to stdout. * * @param msg info message text (no newline at end). */ extern void (*jack_info_callback)(const char *msg) JACK_OPTIONAL_WEAK_EXPORT; /** * Set the @ref jack_info_callback for info message display. * Set it to NULL to restore default_jack_info_callback function. * * The JACK library provides two built-in callbacks for this purpose: * default_jack_info_callback() and silent_jack_info_callback(). */ void jack_set_info_function (void (*func)(const char *)) JACK_OPTIONAL_WEAK_EXPORT; /**@}*/ /** * The free function to be used on memory returned by jack_port_get_connections, * jack_port_get_all_connections, jack_get_ports and jack_get_internal_client_name functions. * This is MANDATORY on Windows when otherwise all nasty runtime version related crashes can occur. * Developers are strongly encouraged to use this function instead of the standard "free" function in new code. * * @param ptr the memory pointer to be deallocated. */ void jack_free(void* ptr) JACK_OPTIONAL_WEAK_EXPORT; #ifdef __cplusplus } #endif #endif /* __jack_h__ */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/pipewire-jack/jack/jslist.h000066400000000000000000000126431511204443500256270ustar00rootroot00000000000000/* Based on gslist.c from glib-1.2.9 (LGPL). Adaption to JACK, Copyright (C) 2002 Kai Vehmanen. - replaced use of gtypes with normal ANSI C types - glib's memory allocation routines replaced with malloc/free calls This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #ifndef __jack_jslist_h__ #define __jack_jslist_h__ #include #include #ifdef sun #define __inline__ #endif typedef struct _JSList JSList; typedef int (*JCompareFunc) (void* a, void* b); struct _JSList { void *data; JSList *next; }; static __inline__ JSList* jack_slist_alloc (void) { JSList *new_list; new_list = (JSList*)malloc(sizeof(JSList)); if (new_list) { new_list->data = NULL; new_list->next = NULL; } return new_list; } static __inline__ JSList* jack_slist_prepend (JSList* list, void* data) { JSList *new_list; new_list = (JSList*)malloc(sizeof(JSList)); if (new_list) { new_list->data = data; new_list->next = list; } return new_list; } #define jack_slist_next(slist) ((slist) ? (((JSList *)(slist))->next) : NULL) static __inline__ JSList* jack_slist_last (JSList *list) { if (list) { while (list->next) list = list->next; } return list; } static __inline__ JSList* jack_slist_remove_link (JSList *list, JSList *link) { JSList *tmp; JSList *prev; prev = NULL; tmp = list; while (tmp) { if (tmp == link) { if (prev) prev->next = tmp->next; if (list == tmp) list = list->next; tmp->next = NULL; break; } prev = tmp; tmp = tmp->next; } return list; } static __inline__ void jack_slist_free (JSList *list) { while (list) { JSList *next = list->next; free(list); list = next; } } static __inline__ void jack_slist_free_1 (JSList *list) { if (list) { free(list); } } static __inline__ JSList* jack_slist_remove (JSList *list, void *data) { JSList *tmp; JSList *prev; prev = NULL; tmp = list; while (tmp) { if (tmp->data == data) { if (prev) prev->next = tmp->next; if (list == tmp) list = list->next; tmp->next = NULL; jack_slist_free (tmp); break; } prev = tmp; tmp = tmp->next; } return list; } static __inline__ unsigned int jack_slist_length (JSList *list) { unsigned int length; length = 0; while (list) { length++; list = list->next; } return length; } static __inline__ JSList* jack_slist_find (JSList *list, void *data) { while (list) { if (list->data == data) break; list = list->next; } return list; } static __inline__ JSList* jack_slist_copy (JSList *list) { JSList *new_list = NULL; if (list) { JSList *last; new_list = jack_slist_alloc (); new_list->data = list->data; last = new_list; list = list->next; while (list) { last->next = jack_slist_alloc (); last = last->next; last->data = list->data; list = list->next; } } return new_list; } static __inline__ JSList* jack_slist_append (JSList *list, void *data) { JSList *new_list; JSList *last; new_list = jack_slist_alloc (); new_list->data = data; if (list) { last = jack_slist_last (list); last->next = new_list; return list; } else return new_list; } static __inline__ JSList* jack_slist_sort_merge (JSList *l1, JSList *l2, JCompareFunc compare_func) { JSList list, *l; l = &list; while (l1 && l2) { if (compare_func(l1->data, l2->data) < 0) { l = l->next = l1; l1 = l1->next; } else { l = l->next = l2; l2 = l2->next; } } l->next = l1 ? l1 : l2; return list.next; } static __inline__ JSList* jack_slist_sort (JSList *list, JCompareFunc compare_func) { JSList *l1, *l2; if (!list) return NULL; if (!list->next) return list; l1 = list; l2 = list->next; while ((l2 = l2->next) != NULL) { if ((l2 = l2->next) == NULL) break; l1 = l1->next; } l2 = l1->next; l1->next = NULL; return jack_slist_sort_merge (jack_slist_sort (list, compare_func), jack_slist_sort (l2, compare_func), compare_func); } #endif /* __jack_jslist_h__ */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/pipewire-jack/jack/metadata.h000066400000000000000000000257761511204443500261120ustar00rootroot00000000000000/* Copyright (C) 2011-2014 David Robillard Copyright (C) 2013 Paul Davis This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ /** * @file jack/metadata.h * @ingroup publicheader * @brief JACK Metadata API * */ #ifndef __jack_metadata_h__ #define __jack_metadata_h__ #include #ifdef __cplusplus extern "C" { #endif /** * @defgroup Metadata Metadata API. * @{ */ /** * A single property (key:value pair). * * Although there is no semantics imposed on metadata keys and values, it is * much less useful to use it to associate highly structured data with a port * (or client), since this then implies the need for some (presumably * library-based) code to parse the structure and be able to use it. * * The real goal of the metadata API is to be able to tag ports (and clients) * with small amounts of data that is outside of the core JACK API but * nevertheless useful. */ typedef struct { /** The key of this property (URI string). */ const char* key; /** The property value (null-terminated string). */ const char* data; /** * Type of data, either a MIME type or URI. * * If type is NULL or empty, the data is assumed to be a UTF-8 encoded * string (text/plain). The data is a null-terminated string regardless of * type, so values can always be copied, but clients should not try to * interpret values of an unknown type. * * Example values: * - image/png;base64 (base64 encoded PNG image) * - http://www.w3.org/2001/XMLSchema#int (integer) * * Official types are preferred, but clients may use any syntactically * valid MIME type (which start with a type and slash, like "text/..."). * If a URI type is used, it must be a complete absolute URI * (which start with a scheme and colon, like "http:"). */ const char* type; } jack_property_t; /** * Set a property on @p subject. * * See the above documentation for rules about @p subject and @p key. * @param subject The subject to set the property on. * @param key The key of the property. * @param value The value of the property. * @param type The type of the property. See the discussion of * types in the definition of jack_property_t above. * @return 0 on success. */ int jack_set_property(jack_client_t*, jack_uuid_t subject, const char* key, const char* value, const char* type); /** * Get a property on @p subject. * * @param subject The subject to get the property from. * @param key The key of the property. * @param value Set to the value of the property if found, or NULL otherwise. * The caller must free this value with jack_free(). * @param type The type of the property if set, or NULL. See the discussion * of types in the definition of jack_property_t above. * If non-null, the caller must free this value with jack_free(). * * @return 0 on success, -1 if the @p subject has no @p key property. */ int jack_get_property(jack_uuid_t subject, const char* key, char** value, char** type); /** * A description of a subject (a set of properties). */ typedef struct { jack_uuid_t subject; /**< Subject being described. */ uint32_t property_cnt; /**< Number of elements in "properties". */ jack_property_t* properties; /**< Array of properties. */ uint32_t property_size; /**< Private, do not use. */ } jack_description_t; /** * Free a description. * * @param desc a jack_description_t whose associated memory will all be released * @param free_description_itself if non-zero, then @param desc will also be passed to free() */ void jack_free_description (jack_description_t* desc, int free_description_itself); /** * Get a description of @p subject. * @param subject The subject to get all properties of. * @param desc Set to the description of subject if found, or NULL otherwise. * The caller must free this value with jack_free_description(). * @return the number of properties, -1 if no @p subject with any properties exists. */ int jack_get_properties (jack_uuid_t subject, jack_description_t* desc); /** * Get descriptions for all subjects with metadata. * @param descs Set to an array of descriptions. * The caller must free each of these with jack_free_description(), * and the array itself with jack_free(). * @return the number of descriptions, or -1 on error. */ int jack_get_all_properties (jack_description_t** descs); /** * Remove a single property on a subject. * * @param client The JACK client making the request to remove the property. * @param subject The subject to remove the property from. * @param key The key of the property to be removed. * * @return 0 on success, -1 otherwise */ int jack_remove_property (jack_client_t* client, jack_uuid_t subject, const char* key); /** * Remove all properties on a subject. * * @param client The JACK client making the request to remove some properties. * @param subject The subject to remove all properties from. * * @return a count of the number of properties removed, or -1 on error. */ int jack_remove_properties (jack_client_t* client, jack_uuid_t subject); /** * Remove all properties. * * WARNING!! This deletes all metadata managed by a running JACK server. * Data lost cannot be recovered (though it can be recreated by new calls * to jack_set_property()). * * @param client The JACK client making the request to remove all properties * * @return 0 on success, -1 otherwise */ int jack_remove_all_properties (jack_client_t* client); typedef enum { PropertyCreated, PropertyChanged, PropertyDeleted } jack_property_change_t; /** * Prototype for the client supplied function that is called by the * engine anytime a property or properties have been modified. * * Note that when the key is empty, it means all properties have been * modified. This is often used to indicate that the removal of all keys. * * @param subject The subject the change relates to, this can be either a client or port * @param key The key of the modified property (URI string) * @param change Wherever the key has been created, changed or deleted * @param arg pointer to a client supplied structure */ typedef void (*JackPropertyChangeCallback)(jack_uuid_t subject, const char* key, jack_property_change_t change, void* arg); /** * Arrange for @p client to call @p callback whenever a property is created, * changed or deleted. * * @param client the JACK client making the request * @param callback the function to be invoked when a property change occurs * @param arg the argument to be passed to @param callback when it is invoked * * @return 0 success, -1 otherwise. */ int jack_set_property_change_callback (jack_client_t* client, JackPropertyChangeCallback callback, void* arg); /** * A value that identifies what the hardware port is connected to (an external * device of some kind). Possible values might be "E-Piano" or "Master 2 Track". */ extern const char* JACK_METADATA_CONNECTED; /** * The supported event types of an event port. * * This is a kludge around Jack only supporting MIDI, particularly for OSC. * This property is a comma-separated list of event types, currently "MIDI" or * "OSC". If this contains "OSC", the port may carry OSC bundles (first byte * '#') or OSC messages (first byte '/'). Note that the "status byte" of both * OSC events is not a valid MIDI status byte, so MIDI clients that check the * status byte will gracefully ignore OSC messages if the user makes an * inappropriate connection. */ extern const char* JACK_METADATA_EVENT_TYPES; /** * A value that should be shown when attempting to identify the * specific hardware outputs of a client. Typical values might be * "ADAT1", "S/PDIF L" or "MADI 43". */ extern const char* JACK_METADATA_HARDWARE; /** * A value with a MIME type of "image/png;base64" that is an encoding of an * NxN (with 32 < N <= 128) image to be used when displaying a visual * representation of that client or port. */ extern const char* JACK_METADATA_ICON_LARGE; /** * The name of the icon for the subject (typically client). * * This is used for looking up icons on the system, possibly with many sizes or * themes. Icons should be searched for according to the freedesktop Icon * * Theme Specification: * https://specifications.freedesktop.org/icon-theme-spec/icon-theme-spec-latest.html */ extern const char* JACK_METADATA_ICON_NAME; /** * A value with a MIME type of "image/png;base64" that is an encoding of an * NxN (with N <=32) image to be used when displaying a visual representation * of that client or port. */ extern const char* JACK_METADATA_ICON_SMALL; /** * Order for a port. * * This is used to specify the best order to show ports in user interfaces. * The value MUST be an integer. There are no other requirements, so there may * be gaps in the orders for several ports. Applications should compare the * orders of ports to determine their relative order, but must not assign any * other relevance to order values. * * It is encouraged to use http://www.w3.org/2001/XMLSchema#int as the type. */ extern const char* JACK_METADATA_ORDER; /** * A value that should be shown to the user when displaying a port to the user, * unless the user has explicitly overridden that a request to show the port * name, or some other key value. */ extern const char* JACK_METADATA_PRETTY_NAME; /** */ extern const char* JACK_METADATA_PORT_GROUP; /** * The type of an audio signal. * * This property allows audio ports to be tagged with a "meaning". The value * is a simple string. Currently, the only type is "CV", for "control voltage" * ports. Hosts SHOULD be take care to not treat CV ports as audible and send * their output directly to speakers. In particular, CV ports are not * necessarily periodic at all and may have very high DC. */ extern const char* JACK_METADATA_SIGNAL_TYPE; /** * @} */ #ifdef __cplusplus } /* namespace */ #endif #endif /* __jack_metadata_h__ */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/pipewire-jack/jack/midiport.h000066400000000000000000000153101511204443500261400ustar00rootroot00000000000000/* Copyright (C) 2004 Ian Esten This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #ifndef __JACK_MIDIPORT_H #define __JACK_MIDIPORT_H #ifdef __cplusplus extern "C" { #endif #include #include #include /** Type for raw event data contained in @ref jack_midi_event_t. */ typedef unsigned char jack_midi_data_t; /** A Jack MIDI event. */ typedef struct _jack_midi_event { jack_nframes_t time; /**< Sample index at which event is valid */ size_t size; /**< Number of bytes of data in \a buffer */ jack_midi_data_t *buffer; /**< Raw MIDI data */ } jack_midi_event_t; /** * @defgroup MIDIAPI Reading and writing MIDI data * @{ */ /** Get number of events in a port buffer. * * @param port_buffer Port buffer from which to retrieve event. * @return number of events inside @a port_buffer */ uint32_t jack_midi_get_event_count(void* port_buffer) JACK_OPTIONAL_WEAK_EXPORT; /** Get a MIDI event from an event port buffer. * * Jack MIDI is normalised, the MIDI event returned by this function is * guaranteed to be a complete MIDI event (the status byte will always be * present, and no realtime events will interspersed with the event). * * This rule does not apply to System Exclusive MIDI messages * since they can be of arbitrary length. * To maintain smooth realtime operation such events CAN be delivered * as multiple, non-normalised events. * The maximum size of one event "chunk" depends on the MIDI backend in use. * For example the midiseq driver will create chunks of 256 bytes. * The first SysEx "chunked" event starts with 0xF0 and the last * delivered chunk ends with 0xF7. * To receive the full SysEx message, a caller of jack_midi_event_get() * must concatenate chunks until a chunk ends with 0xF7. * * @param event Event structure to store retrieved event in. * @param port_buffer Port buffer from which to retrieve event. * @param event_index Index of event to retrieve. * @return 0 on success, ENODATA if buffer is empty. */ int jack_midi_event_get(jack_midi_event_t *event, void *port_buffer, uint32_t event_index) JACK_OPTIONAL_WEAK_EXPORT; /** Clear an event buffer. * * This should be called at the beginning of each process cycle before calling * @ref jack_midi_event_reserve or @ref jack_midi_event_write. This * function may not be called on an input port's buffer. * * @param port_buffer Port buffer to clear (must be an output port buffer). */ void jack_midi_clear_buffer(void *port_buffer) JACK_OPTIONAL_WEAK_EXPORT; /** Reset an event buffer (from data allocated outside of JACK). * * This should be called at the beginning of each process cycle before calling * @ref jack_midi_event_reserve or @ref jack_midi_event_write. This * function may not be called on an input port's buffer. * * @deprecated Please use jack_midi_clear_buffer(). * * @param port_buffer Port buffer to reset. */ void jack_midi_reset_buffer(void *port_buffer) JACK_OPTIONAL_WEAK_DEPRECATED_EXPORT; /** Get the size of the largest event that can be stored by the port. * * This function returns the current space available, taking into account * events already stored in the port. * * @param port_buffer Port buffer to check size of. */ size_t jack_midi_max_event_size(void* port_buffer) JACK_OPTIONAL_WEAK_EXPORT; /** Allocate space for an event to be written to an event port buffer. * * Clients are to write the actual event data to be written starting at the * pointer returned by this function. Clients must not write more than * @a data_size bytes into this buffer. Clients must write normalised * MIDI data to the port - no running status and no (1-byte) realtime * messages interspersed with other messages (realtime messages are fine * when they occur on their own, like other messages). * * Events must be written in order, sorted by their sample offsets. * JACK will not sort the events for you, and will refuse to store * out-of-order events. * * @param port_buffer Buffer to write event to. * @param time Sample offset of event. * @param data_size Length of event's raw data in bytes. * @return Pointer to the beginning of the reserved event's data buffer, or * NULL on error (ie not enough space). */ jack_midi_data_t* jack_midi_event_reserve(void *port_buffer, jack_nframes_t time, size_t data_size) JACK_OPTIONAL_WEAK_EXPORT; /** Write an event into an event port buffer. * * This function is simply a wrapper for @ref jack_midi_event_reserve * which writes the event data into the space reserved in the buffer. * * Clients must not write more than * @a data_size bytes into this buffer. Clients must write normalised * MIDI data to the port - no running status and no (1-byte) realtime * messages interspersed with other messages (realtime messages are fine * when they occur on their own, like other messages). * * Events must be written in order, sorted by their sample offsets. * JACK will not sort the events for you, and will refuse to store * out-of-order events. * * @param port_buffer Buffer to write event to. * @param time Sample offset of event. * @param data Message data to be written. * @param data_size Length of @a data in bytes. * @return 0 on success, ENOBUFS if there's not enough space in buffer for event. */ int jack_midi_event_write(void *port_buffer, jack_nframes_t time, const jack_midi_data_t *data, size_t data_size) JACK_OPTIONAL_WEAK_EXPORT; /** Get the number of events that could not be written to @a port_buffer. * * This function returning a non-zero value implies @a port_buffer is full. * Currently the only way this can happen is if events are lost on port mixdown. * * @param port_buffer Port to receive count for. * @returns Number of events that could not be written to @a port_buffer. */ uint32_t jack_midi_get_lost_event_count(void *port_buffer) JACK_OPTIONAL_WEAK_EXPORT; /**@}*/ #ifdef __cplusplus } #endif #endif /* __JACK_MIDIPORT_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/pipewire-jack/jack/net.h000066400000000000000000000357521511204443500251130ustar00rootroot00000000000000/* Copyright (C) 2009-2010 Grame This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #ifndef __net_h__ #define __net_h__ #ifdef __cplusplus extern "C" { #endif #include #include #include #define DEFAULT_MULTICAST_IP "225.3.19.154" #define DEFAULT_PORT 19000 #define DEFAULT_MTU 1500 #define MASTER_NAME_SIZE 256 // Possible error codes #define NO_ERROR 0 #define SOCKET_ERROR -1 #define SYNC_PACKET_ERROR -2 #define DATA_PACKET_ERROR -3 #define RESTART_CB_API 1 enum JackNetEncoder { JackFloatEncoder = 0, // samples are transmitted as float JackIntEncoder = 1, // samples are transmitted as 16 bits integer JackCeltEncoder = 2, // samples are transmitted using CELT codec (http://www.celt-codec.org/) JackOpusEncoder = 3, // samples are transmitted using OPUS codec (http://www.opus-codec.org/) }; typedef struct { int audio_input; // from master or to slave (-1 to take master audio physical inputs) int audio_output; // to master or from slave (-1 to take master audio physical outputs) int midi_input; // from master or to slave (-1 to take master MIDI physical inputs) int midi_output; // to master or from slave (-1 to take master MIDI physical outputs) int mtu; // network Maximum Transmission Unit int time_out; // in second, -1 means infinite int encoder; // encoder type (one of JackNetEncoder) int kbps; // KB per second for CELT or OPUS codec int latency; // network latency in number of buffers } jack_slave_t; typedef struct { int audio_input; // master audio physical outputs (-1 to take slave wanted audio inputs) int audio_output; // master audio physical inputs (-1 to take slave wanted audio outputs) int midi_input; // master MIDI physical outputs (-1 to take slave wanted MIDI inputs) int midi_output; // master MIDI physical inputs (-1 to take slave wanted MIDI outputs) jack_nframes_t buffer_size; // master buffer size jack_nframes_t sample_rate; // master sample rate char master_name[MASTER_NAME_SIZE]; // master machine name int time_out; // in second, -1 means infinite int partial_cycle; // if 'true', partial buffers will be used } jack_master_t; /** * jack_net_slave_t is an opaque type. You may only access it using the * API provided. */ typedef struct _jack_net_slave jack_net_slave_t; /** * Open a network connection with the master machine. * * @param ip the multicast address of the master * @param port the connection port * @param name the JACK client name * @param request a connection request structure * @param result a connection result structure * * @return Opaque net handle if successful or NULL in case of error. */ jack_net_slave_t* jack_net_slave_open(const char* ip, int port, const char* name, jack_slave_t* request, jack_master_t* result); /** * Close the network connection with the master machine. * * @param net the network connection to be closed * * @return 0 on success, otherwise a non-zero error code */ int jack_net_slave_close(jack_net_slave_t* net); /** * Prototype for Process callback. * * @param nframes buffer size * @param audio_input number of audio inputs * @param audio_input_buffer an array of audio input buffers (from master) * @param midi_input number of MIDI inputs * @param midi_input_buffer an array of MIDI input buffers (from master) * @param audio_output number of audio outputs * @param audio_output_buffer an array of audio output buffers (to master) * @param midi_output number of MIDI outputs * @param midi_output_buffer an array of MIDI output buffers (to master) * @param arg pointer to a client supplied structure supplied by jack_set_net_process_callback() * * @return zero on success, non-zero on error */ typedef int (* JackNetSlaveProcessCallback) (jack_nframes_t buffer_size, int audio_input, float** audio_input_buffer, int midi_input, void** midi_input_buffer, int audio_output, float** audio_output_buffer, int midi_output, void** midi_output_buffer, void* data); /** * Set network process callback. * * @param net the network connection * @param net_callback the process callback * @param arg pointer to a client supplied structure * * @return 0 on success, otherwise a non-zero error code */ int jack_set_net_slave_process_callback(jack_net_slave_t * net, JackNetSlaveProcessCallback net_callback, void *arg); /** * Start processing thread, the net_callback will start to be called. * * @param net the network connection * * @return 0 on success, otherwise a non-zero error code */ int jack_net_slave_activate(jack_net_slave_t* net); /** * Stop processing thread. * * @param net the network connection * * @return 0 on success, otherwise a non-zero error code */ int jack_net_slave_deactivate(jack_net_slave_t* net); /** * Test if slave is still active. * * @param net the network connection * * @return a boolean */ int jack_net_slave_is_active(jack_net_slave_t* net); /** * Prototype for BufferSize callback. * * @param nframes buffer size * @param arg pointer to a client supplied structure supplied by jack_set_net_buffer_size_callback() * * @return zero on success, non-zero on error */ typedef int (*JackNetSlaveBufferSizeCallback)(jack_nframes_t nframes, void *arg); /** * Set network buffer size callback. * * @param net the network connection * @param bufsize_callback the buffer size callback * @param arg pointer to a client supplied structure * * @return 0 on success, otherwise a non-zero error code */ int jack_set_net_slave_buffer_size_callback(jack_net_slave_t *net, JackNetSlaveBufferSizeCallback bufsize_callback, void *arg); /** * Prototype for SampleRate callback. * * @param nframes sample rate * @param arg pointer to a client supplied structure supplied by jack_set_net_sample_rate_callback() * * @return zero on success, non-zero on error */ typedef int (*JackNetSlaveSampleRateCallback)(jack_nframes_t nframes, void *arg); /** * Set network sample rate callback. * * @param net the network connection * @param samplerate_callback the sample rate callback * @param arg pointer to a client supplied structure * * @return 0 on success, otherwise a non-zero error code */ int jack_set_net_slave_sample_rate_callback(jack_net_slave_t *net, JackNetSlaveSampleRateCallback samplerate_callback, void *arg); /** * Prototype for server Shutdown callback (if not set, the client will just restart, waiting for an available master again). * * @param arg pointer to a client supplied structure supplied by jack_set_net_shutdown_callback() */ typedef void (*JackNetSlaveShutdownCallback)(void* arg); /** * Set network shutdown callback. * * @param net the network connection * @param shutdown_callback the shutdown callback * @param arg pointer to a client supplied structure * * @return 0 on success, otherwise a non-zero error code */ int jack_set_net_slave_shutdown_callback(jack_net_slave_t *net, JackNetSlaveShutdownCallback shutdown_callback, void *arg) JACK_OPTIONAL_WEAK_DEPRECATED_EXPORT; /** * Prototype for server Restart callback : this is the new preferable way to be notified when the master has disappeared. * The client may want to retry connecting a certain number of time (which will be done using the time_out value given in jack_net_slave_open) * by returning 0. Otherwise returning a non-zero error code will definitively close the connection * (and jack_net_slave_is_active will later on return false). * If both Shutdown and Restart are supplied, Restart callback will be used. * * @param arg pointer to a client supplied structure supplied by jack_set_net_restart_callback() * * @return 0 on success, otherwise a non-zero error code */ typedef int (*JackNetSlaveRestartCallback)(void* arg); /** * Set network restart callback. * * @param net the network connection * @param restart_callback the shutdown callback * @param arg pointer to a client supplied structure * * @return 0 on success, otherwise a non-zero error code */ int jack_set_net_slave_restart_callback(jack_net_slave_t *net, JackNetSlaveRestartCallback restart_callback, void *arg) JACK_OPTIONAL_WEAK_EXPORT; /** * Prototype for server Error callback. * * @param error_code an error code (see "Possible error codes") * @param arg pointer to a client supplied structure supplied by jack_set_net_error_callback() */ typedef void (*JackNetSlaveErrorCallback) (int error_code, void* arg); /** * Set error restart callback. * * @param net the network connection * @param error_callback the error callback * @param arg pointer to a client supplied structure * * @return 0 on success, otherwise a non-zero error code */ int jack_set_net_slave_error_callback(jack_net_slave_t *net, JackNetSlaveErrorCallback error_callback, void *arg) JACK_OPTIONAL_WEAK_EXPORT; /** * jack_net_master_t is an opaque type, you may only access it using the API provided. */ typedef struct _jack_net_master jack_net_master_t; /** * Open a network connection with the slave machine. * * @param ip the multicast address of the master * @param port the connection port * @param request a connection request structure * @param result a connection result structure * * @return Opaque net handle if successful or NULL in case of error. */ jack_net_master_t* jack_net_master_open(const char* ip, int port, jack_master_t* request, jack_slave_t* result); /** * Close the network connection with the slave machine. * * @param net the network connection to be closed * * @return 0 on success, otherwise a non-zero error code */ int jack_net_master_close(jack_net_master_t* net); /** * Receive sync and data from the network (complete buffer). * * @param net the network connection * @param audio_input number of audio inputs * @param audio_input_buffer an array of audio input buffers * @param midi_input number of MIDI inputs * @param midi_input_buffer an array of MIDI input buffers * * @return zero on success, non-zero on error */ int jack_net_master_recv(jack_net_master_t* net, int audio_input, float** audio_input_buffer, int midi_input, void** midi_input_buffer); /** * Receive sync and data from the network (incomplete buffer). * * @param net the network connection * @param audio_input number of audio inputs * @param audio_input_buffer an array of audio input buffers * @param midi_input number of MIDI inputs * @param midi_input_buffer an array of MIDI input buffers * @param frames the number of frames to receive * * @return zero on success, non-zero on error */ int jack_net_master_recv_slice(jack_net_master_t* net, int audio_input, float** audio_input_buffer, int midi_input, void** midi_input_buffer, int frames); /** * Send sync and data to the network (complete buffer). * * @param net the network connection * @param audio_output number of audio outputs * @param audio_output_buffer an array of audio output buffers * @param midi_output number of MIDI outputs * @param midi_output_buffer an array of MIDI output buffers * * @return zero on success, non-zero on error */ int jack_net_master_send(jack_net_master_t* net, int audio_output, float** audio_output_buffer, int midi_output, void** midi_output_buffer); /** * Send sync and data to the network (incomplete buffer). * * @param net the network connection * @param audio_output number of audio outputs * @param audio_output_buffer an array of audio output buffers * @param midi_output number of MIDI outputs * @param midi_output_buffer an array of MIDI output buffers * @param frames the number of frames to send * * @return zero on success, non-zero on error */ int jack_net_master_send_slice(jack_net_master_t* net, int audio_output, float** audio_output_buffer, int midi_output, void** midi_output_buffer, int frames); // Experimental Adapter API /** * jack_adapter_t is an opaque type, you may only access it using the API provided. */ typedef struct _jack_adapter jack_adapter_t; /** * Create an adapter. * * @param input number of audio inputs * @param output of audio outputs * @param host_buffer_size the host buffer size in frames * @param host_sample_rate the host buffer sample rate * @param adapted_buffer_size the adapted buffer size in frames * @param adapted_sample_rate the adapted buffer sample rate * * @return 0 on success, otherwise a non-zero error code */ jack_adapter_t* jack_create_adapter(int input, int output, jack_nframes_t host_buffer_size, jack_nframes_t host_sample_rate, jack_nframes_t adapted_buffer_size, jack_nframes_t adapted_sample_rate); /** * Destroy an adapter. * * @param adapter the adapter to be destroyed * * @return 0 on success, otherwise a non-zero error code */ int jack_destroy_adapter(jack_adapter_t* adapter); /** * Flush internal state of an adapter. * * @param adapter the adapter to be flushed * * @return 0 on success, otherwise a non-zero error code */ void jack_flush_adapter(jack_adapter_t* adapter); /** * Push input to and pull output from adapter ringbuffer. * * @param adapter the adapter * @param input an array of audio input buffers * @param output an array of audio output buffers * @param frames number of frames * * @return 0 on success, otherwise a non-zero error code */ int jack_adapter_push_and_pull(jack_adapter_t* adapter, float** input, float** output, unsigned int frames); /** * Pull input from and push output to adapter ringbuffer. * * @param adapter the adapter * @param input an array of audio input buffers * @param output an array of audio output buffers * @param frames number of frames * * @return 0 on success, otherwise a non-zero error code */ int jack_adapter_pull_and_push(jack_adapter_t* adapter, float** input, float** output, unsigned int frames); #ifdef __cplusplus } #endif #endif /* __net_h__ */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/pipewire-jack/jack/ringbuffer.h000066400000000000000000000174251511204443500264530ustar00rootroot00000000000000/* Copyright (C) 2000 Paul Davis Copyright (C) 2003 Rohan Drape This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #ifndef _RINGBUFFER_H #define _RINGBUFFER_H #ifdef __cplusplus extern "C" { #endif #include /** @file ringbuffer.h * * A set of library functions to make lock-free ringbuffers available * to JACK clients. The `capture_client.c' (in the example_clients * directory) is a fully functioning user of this API. * * The key attribute of a ringbuffer is that it can be safely accessed * by two threads simultaneously -- one reading from the buffer and * the other writing to it -- without using any synchronization or * mutual exclusion primitives. For this to work correctly, there can * only be a single reader and a single writer thread. Their * identities cannot be interchanged. */ typedef struct { char *buf; size_t len; } jack_ringbuffer_data_t ; typedef struct { char *buf; size_t write_ptr; size_t read_ptr; size_t size; size_t size_mask; int mlocked; } jack_ringbuffer_t ; /** * Allocates a ringbuffer data structure of a specified size. The * caller must arrange for a call to jack_ringbuffer_free() to release * the memory associated with the ringbuffer. * * @param sz the ringbuffer size in bytes. * * @return a pointer to a new jack_ringbuffer_t, if successful; NULL * otherwise. */ jack_ringbuffer_t *jack_ringbuffer_create(size_t sz); /** * Frees the ringbuffer data structure allocated by an earlier call to * jack_ringbuffer_create(). * * @param rb a pointer to the ringbuffer structure. */ void jack_ringbuffer_free(jack_ringbuffer_t *rb); /** * Fill a data structure with a description of the current readable * data held in the ringbuffer. This description is returned in a two * element array of jack_ringbuffer_data_t. Two elements are needed * because the data to be read may be split across the end of the * ringbuffer. * * The first element will always contain a valid @a len field, which * may be zero or greater. If the @a len field is non-zero, then data * can be read in a contiguous fashion using the address given in the * corresponding @a buf field. * * If the second element has a non-zero @a len field, then a second * contiguous stretch of data can be read from the address given in * its corresponding @a buf field. * * @param rb a pointer to the ringbuffer structure. * @param vec a pointer to a 2 element array of jack_ringbuffer_data_t. * */ void jack_ringbuffer_get_read_vector(const jack_ringbuffer_t *rb, jack_ringbuffer_data_t *vec); /** * Fill a data structure with a description of the current writable * space in the ringbuffer. The description is returned in a two * element array of jack_ringbuffer_data_t. Two elements are needed * because the space available for writing may be split across the end * of the ringbuffer. * * The first element will always contain a valid @a len field, which * may be zero or greater. If the @a len field is non-zero, then data * can be written in a contiguous fashion using the address given in * the corresponding @a buf field. * * If the second element has a non-zero @a len field, then a second * contiguous stretch of data can be written to the address given in * the corresponding @a buf field. * * @param rb a pointer to the ringbuffer structure. * @param vec a pointer to a 2 element array of jack_ringbuffer_data_t. */ void jack_ringbuffer_get_write_vector(const jack_ringbuffer_t *rb, jack_ringbuffer_data_t *vec); /** * Read data from the ringbuffer. * * @param rb a pointer to the ringbuffer structure. * @param dest a pointer to a buffer where data read from the * ringbuffer will go. * @param cnt the number of bytes to read. * * @return the number of bytes read, which may range from 0 to cnt. */ size_t jack_ringbuffer_read(jack_ringbuffer_t *rb, char *dest, size_t cnt); /** * Read data from the ringbuffer. Opposed to jack_ringbuffer_read() * this function does not move the read pointer. Thus it's * a convenient way to inspect data in the ringbuffer in a * continuous fashion. The price is that the data is copied * into a user provided buffer. For "raw" non-copy inspection * of the data in the ringbuffer use jack_ringbuffer_get_read_vector(). * * @param rb a pointer to the ringbuffer structure. * @param dest a pointer to a buffer where data read from the * ringbuffer will go. * @param cnt the number of bytes to read. * * @return the number of bytes read, which may range from 0 to cnt. */ size_t jack_ringbuffer_peek(jack_ringbuffer_t *rb, char *dest, size_t cnt); /** * Advance the read pointer. * * After data have been read from the ringbuffer using the pointers * returned by jack_ringbuffer_get_read_vector(), use this function to * advance the buffer pointers, making that space available for future * write operations. * * @param rb a pointer to the ringbuffer structure. * @param cnt the number of bytes read. */ void jack_ringbuffer_read_advance(jack_ringbuffer_t *rb, size_t cnt); /** * Return the number of bytes available for reading. * * @param rb a pointer to the ringbuffer structure. * * @return the number of bytes available to read. */ size_t jack_ringbuffer_read_space(const jack_ringbuffer_t *rb); /** * Lock a ringbuffer data block into memory. * * Uses the mlock() system call. This is not a realtime operation. * * @param rb a pointer to the ringbuffer structure. */ int jack_ringbuffer_mlock(jack_ringbuffer_t *rb); /** * Reset the read and write pointers, making an empty buffer. * * This is not thread safe. * * @param rb a pointer to the ringbuffer structure. */ void jack_ringbuffer_reset(jack_ringbuffer_t *rb); /** * Reset the internal "available" size, and read and write pointers, making an empty buffer. * * This is not thread safe. * * @param rb a pointer to the ringbuffer structure. * @param sz the new size, that must be less than allocated size. */ void jack_ringbuffer_reset_size (jack_ringbuffer_t * rb, size_t sz); /** * Write data into the ringbuffer. * * @param rb a pointer to the ringbuffer structure. * @param src a pointer to the data to be written to the ringbuffer. * @param cnt the number of bytes to write. * * @return the number of bytes write, which may range from 0 to cnt */ size_t jack_ringbuffer_write(jack_ringbuffer_t *rb, const char *src, size_t cnt); /** * Advance the write pointer. * * After data have been written the ringbuffer using the pointers * returned by jack_ringbuffer_get_write_vector(), use this function * to advance the buffer pointer, making the data available for future * read operations. * * @param rb a pointer to the ringbuffer structure. * @param cnt the number of bytes written. */ void jack_ringbuffer_write_advance(jack_ringbuffer_t *rb, size_t cnt); /** * Return the number of bytes available for writing. * * @param rb a pointer to the ringbuffer structure. * * @return the amount of free space (in bytes) available for writing. */ size_t jack_ringbuffer_write_space(const jack_ringbuffer_t *rb); #ifdef __cplusplus } #endif #endif pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/pipewire-jack/jack/session.h000066400000000000000000000227471511204443500260100ustar00rootroot00000000000000/* Copyright (C) 2001 Paul Davis Copyright (C) 2004 Jack O'Quin Copyright (C) 2010 Torben Hohn This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #ifndef __jack_session_h__ #define __jack_session_h__ #ifdef __cplusplus extern "C" { #endif #include #include /** * @defgroup SessionClientFunctions Session API for clients. * * @deprecated Use of JACK-Session is currently deprecated and unsupported. * JACK developers recommend the use of NSM instead. * See https://new-session-manager.jackaudio.org/ * @{ */ /** * Session event type. * * If a client can't save templates, i might just do a normal save. * * There is no "quit without saving" event because a client might refuse to * quit when it has unsaved data, but other clients may have already quit. * This results in too much confusion, so it is unsupported. */ enum JackSessionEventType { /** * Save the session completely. * * The client may save references to data outside the provided directory, * but it must do so by creating a link inside the provided directory and * referring to that in any save files. The client must not refer to data * files outside the provided directory directly in save files, because * this makes it impossible for the session manager to create a session * archive for distribution or archival. */ JackSessionSave = 1, /** * Save the session completely, then quit. * * The rules for saving are exactly the same as for JackSessionSave. */ JackSessionSaveAndQuit = 2, /** * Save a session template. * * A session template is a "skeleton" of the session, but without any data. * Clients must save a session that, when restored, will create the same * ports as a full save would have. However, the actual data contained in * the session may not be saved (e.g. a DAW would create the necessary * tracks, but not save the actual recorded data). */ JackSessionSaveTemplate = 3 }; typedef enum JackSessionEventType jack_session_event_type_t; /** * @ref jack_session_flags_t bits */ enum JackSessionFlags { /** * An error occurred while saving. */ JackSessionSaveError = 0x01, /** * Client needs to be run in a terminal. */ JackSessionNeedTerminal = 0x02 }; /** * Session flags. */ typedef enum JackSessionFlags jack_session_flags_t; struct _jack_session_event { /** * The type of this session event. */ jack_session_event_type_t type; /** * Session directory path, with trailing separator. * * This directory is exclusive to the client; when saving the client may * create any files it likes in this directory. */ const char *session_dir; /** * Client UUID which must be passed to jack_client_open on session load. * * The client can specify this in the returned command line, or save it * in a state file within the session directory. */ const char *client_uuid; /** * Reply (set by client): the command line needed to restore the client. * * This is a platform dependent command line. It must contain * ${SESSION_DIR} instead of the actual session directory path. More * generally, just as in session files, clients should not include any * paths outside the session directory here as this makes * archival/distribution impossible. * * This field is set to NULL by Jack when the event is delivered to the * client. The client must set to allocated memory that is safe to * free(). This memory will be freed by jack_session_event_free. */ char *command_line; /** * Reply (set by client): Session flags. */ jack_session_flags_t flags; /** * Future flags. Set to zero for now. */ uint32_t future; }; typedef struct _jack_session_event jack_session_event_t; /** * Prototype for the client supplied function that is called * whenever a session notification is sent via jack_session_notify(). * * Ownership of the memory of @a event is passed to the application. * It must be freed using jack_session_event_free when it's not used anymore. * * The client must promptly call jack_session_reply for this event. * * @deprecated Use of JACK-Session is currently deprecated and unsupported. * JACK developers recommend the use of NSM instead. * See https://github.com/linuxaudio/new-session-manager * * @param event The event structure. * @param arg Pointer to a client supplied structure. */ typedef void (*JackSessionCallback)(jack_session_event_t *event, void *arg); /** * Tell the JACK server to call @a session_callback when a session event * is to be delivered. * * setting more than one session_callback per process is probably a design * error. if you have a multiclient application its more sensible to create * a jack_client with only a session callback set. * * @deprecated Use of JACK-Session is currently deprecated and unsupported. * JACK developers recommend the use of NSM instead. * See https://github.com/linuxaudio/new-session-manager * * @return 0 on success, otherwise a non-zero error code */ int jack_set_session_callback (jack_client_t *client, JackSessionCallback session_callback, void *arg) JACK_OPTIONAL_WEAK_DEPRECATED_EXPORT; /** * Reply to a session event. * * This can either be called directly from the callback, or later from a * different thread. For example, it is possible to push the event through a * queue and execute the save code from the GUI thread. * * @deprecated Use of JACK-Session is currently deprecated and unsupported. * JACK developers recommend the use of NSM instead. * See https://github.com/linuxaudio/new-session-manager * * @return 0 on success, otherwise a non-zero error code */ int jack_session_reply (jack_client_t *client, jack_session_event_t *event) JACK_OPTIONAL_WEAK_DEPRECATED_EXPORT; /** * Free memory used by a jack_session_event_t. * * This also frees the memory used by the command_line pointer, if its non NULL. * * @deprecated Use of JACK-Session is currently deprecated and unsupported. * JACK developers recommend the use of NSM instead. * See https://github.com/linuxaudio/new-session-manager */ void jack_session_event_free (jack_session_event_t *event) JACK_OPTIONAL_WEAK_DEPRECATED_EXPORT; /** * Get the assigned uuid for client. * Safe to call from callback and all other threads. * * The caller is responsible for calling jack_free(3) on any non-NULL * returned value. */ char *jack_client_get_uuid (jack_client_t *client) JACK_WEAK_EXPORT; /** * @} */ /** * @defgroup JackSessionManagerAPI API for a session manager. * * @{ */ typedef struct { const char *uuid; const char *client_name; const char *command; jack_session_flags_t flags; } jack_session_command_t; /** * Send an event to all clients listening for session callbacks. * * The returned strings of the clients are accumulated and returned as an array * of jack_session_command_t. its terminated by ret[i].uuid == NULL target == * NULL means send to all interested clients. otherwise a clientname */ jack_session_command_t *jack_session_notify ( jack_client_t* client, const char *target, jack_session_event_type_t type, const char *path) JACK_WEAK_EXPORT; /** * Free the memory allocated by a session command. * * @deprecated Use of JACK-Session is currently deprecated and unsupported. * JACK developers recommend the use of NSM instead. * See https://github.com/linuxaudio/new-session-manager */ void jack_session_commands_free (jack_session_command_t *cmds) JACK_OPTIONAL_WEAK_DEPRECATED_EXPORT; /** * Reserve a client name and associate it with a UUID. * * When a client later calls jack_client_open() and specifies the UUID, jackd * will assign the reserved name. This allows a session manager to know in * advance under which client name its managed clients will appear. * * @return 0 on success, otherwise a non-zero error code */ int jack_reserve_client_name (jack_client_t *client, const char *name, const char *uuid) JACK_WEAK_EXPORT; /** * Find out whether a client has set up a session callback. * * @deprecated Use of JACK-Session is currently deprecated and unsupported. * JACK developers recommend the use of NSM instead. * See https://github.com/linuxaudio/new-session-manager * * @return 0 when the client has no session callback, 1 when it has one. * -1 on error. */ int jack_client_has_session_callback (jack_client_t *client, const char *client_name) JACK_OPTIONAL_WEAK_DEPRECATED_EXPORT; /** * @} */ #ifdef __cplusplus } #endif #endif pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/pipewire-jack/jack/statistics.h000066400000000000000000000034541511204443500265110ustar00rootroot00000000000000/* * Copyright (C) 2004 Rui Nuno Capela, Lee Revell * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this program; if not, write to the Free * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. * */ #ifndef __statistics_h__ #define __statistics_h__ #ifdef __cplusplus extern "C" { #endif #include /** * @return the maximum delay reported by the backend since * startup or reset. When compared to the period size in usecs, this * can be used to estimate the ideal period size for a given setup. */ float jack_get_max_delayed_usecs (jack_client_t *client); /** * @return the delay in microseconds due to the most recent XRUN * occurrence. This probably only makes sense when called from a @ref * JackXRunCallback defined using jack_set_xrun_callback(). */ float jack_get_xrun_delayed_usecs (jack_client_t *client); /** * Reset the maximum delay counter. This would be useful * to estimate the effect that a change to the configuration of a running * system (e.g. toggling kernel preemption) has on the delay * experienced by JACK, without having to restart the JACK engine. */ void jack_reset_max_delayed_usecs (jack_client_t *client); #ifdef __cplusplus } #endif #endif /* __statistics_h__ */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/pipewire-jack/jack/systemdeps.h000066400000000000000000000116071511204443500265160ustar00rootroot00000000000000/* Copyright (C) 2004-2012 Grame This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #ifndef __jack_systemdeps_h__ #define __jack_systemdeps_h__ #ifndef POST_PACKED_STRUCTURE #ifdef __GNUC__ /* POST_PACKED_STRUCTURE needs to be a macro which expands into a compiler directive. The directive must tell the compiler to arrange the preceding structure declaration so that it is packed on byte-boundaries rather than use the natural alignment of the processor and/or compiler. */ #define PRE_PACKED_STRUCTURE #define POST_PACKED_STRUCTURE __attribute__((__packed__)) #else #ifdef _MSC_VER #define PRE_PACKED_STRUCTURE1 __pragma(pack(push,1)) #define PRE_PACKED_STRUCTURE PRE_PACKED_STRUCTURE1 /* PRE_PACKED_STRUCTURE needs to be a macro which expands into a compiler directive. The directive must tell the compiler to arrange the following structure declaration so that it is packed on byte-boundaries rather than use the natural alignment of the processor and/or compiler. */ #define POST_PACKED_STRUCTURE ;__pragma(pack(pop)) /* and POST_PACKED_STRUCTURE needs to be a macro which restores the packing to its previous setting */ #else #define PRE_PACKED_STRUCTURE #define POST_PACKED_STRUCTURE #endif /* _MSC_VER */ #endif /* __GNUC__ */ #endif #if defined(_WIN32) && !defined(__CYGWIN__) && !defined(GNU_WIN32) #ifdef __MINGW32__ # include // mingw gives warning if we include windows.h before winsock2.h #endif #include #ifdef _MSC_VER /* Microsoft compiler */ #define __inline__ inline #if (!defined(int8_t) && !defined(_STDINT_H)) #define __int8_t_defined typedef INT8 int8_t; typedef UINT8 uint8_t; typedef INT16 int16_t; typedef UINT16 uint16_t; typedef INT32 int32_t; typedef UINT32 uint32_t; typedef INT64 int64_t; typedef UINT64 uint64_t; #endif #elif __MINGW32__ /* MINGW */ #include #include #else /* other compilers ...*/ #include #include #include #endif #if !defined(_PTHREAD_H) && !defined(PTHREAD_WIN32) /** * to make jack API independent of different thread implementations, * we define jack_native_thread_t to HANDLE here. */ typedef HANDLE jack_native_thread_t; #else #ifdef PTHREAD_WIN32 // Added by JE - 10-10-2011 #include // Makes sure we #include the ptw32 version ! #endif /** * to make jack API independent of different thread implementations, * we define jack_native_thread_t to pthread_t here. */ typedef pthread_t jack_native_thread_t; #endif #endif /* _WIN32 && !__CYGWIN__ && !GNU_WIN32 */ #if defined(__APPLE__) || defined(__linux__) || defined(__sun__) || defined(sun) || defined(__unix__) || defined(__CYGWIN__) || defined(GNU_WIN32) #if defined(__CYGWIN__) || defined(GNU_WIN32) #include #endif #include #include #include /** * to make jack API independent of different thread implementations, * we define jack_native_thread_t to pthread_t here. */ typedef pthread_t jack_native_thread_t; #endif /* __APPLE__ || __linux__ || __sun__ || sun */ #if (defined(__arm__) || defined(__aarch64__) || defined(__mips__) || defined(__ppc__) || defined(__powerpc__)) && !defined(__APPLE__) #undef POST_PACKED_STRUCTURE #define POST_PACKED_STRUCTURE #endif /* __arm__ || __aarch64__ || __mips__ || __ppc__ || __powerpc__ */ /** define JACK_LIB_EXPORT, useful for internal clients */ #if defined(_WIN32) #define JACK_LIB_EXPORT __declspec(dllexport) #elif defined(__GNUC__) #define JACK_LIB_EXPORT __attribute__((visibility("default"))) #else #define JACK_LIB_EXPORT #endif #endif /* __jack_systemdeps_h__ */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/pipewire-jack/jack/thread.h000066400000000000000000000121321511204443500255570ustar00rootroot00000000000000/* Copyright (C) 2004 Paul Davis This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #ifndef __jack_thread_h__ #define __jack_thread_h__ #ifdef __cplusplus extern "C" { #endif #include #include /* use 512KB stack per thread - the default is way too high to be feasible * with mlockall() on many systems */ #define THREAD_STACK 524288 /** @file thread.h * * Library functions to standardize thread creation for JACK and its * clients. These interfaces hide some system variations in the * handling of realtime scheduling and associated privileges. */ /** * @defgroup ClientThreads Creating and managing client threads * @{ */ /** * @returns if JACK is running with realtime scheduling, this returns * the priority that any JACK-created client threads will run at. * Otherwise returns -1. */ int jack_client_real_time_priority (jack_client_t*) JACK_OPTIONAL_WEAK_EXPORT; /** * @returns if JACK is running with realtime scheduling, this returns * the maximum priority that a JACK client thread should use if the thread * is subject to realtime scheduling. Otherwise returns -1. */ int jack_client_max_real_time_priority (jack_client_t*) JACK_OPTIONAL_WEAK_EXPORT; /** * Attempt to enable realtime scheduling for a thread. On some * systems that may require special privileges. * * @param thread POSIX thread ID. * @param priority requested thread priority. * * @returns 0, if successful; EPERM, if the calling process lacks * required realtime privileges; otherwise some other error number. */ int jack_acquire_real_time_scheduling (jack_native_thread_t thread, int priority) JACK_OPTIONAL_WEAK_EXPORT; /** * Create a thread for JACK or one of its clients. The thread is * created executing @a start_routine with @a arg as its sole * argument. * * @param client the JACK client for whom the thread is being created. May be * NULL if the client is being created within the JACK server. * @param thread place to return POSIX thread ID. * @param priority thread priority, if realtime. * @param realtime true for the thread to use realtime scheduling. On * some systems that may require special privileges. * @param start_routine function the thread calls when it starts. * @param arg parameter passed to the @a start_routine. * * @returns 0, if successful; otherwise some error number. */ int jack_client_create_thread (jack_client_t* client, jack_native_thread_t *thread, int priority, int realtime, /* boolean */ void *(*start_routine)(void*), void *arg) JACK_OPTIONAL_WEAK_EXPORT; /** * Drop realtime scheduling for a thread. * * @param thread POSIX thread ID. * * @returns 0, if successful; otherwise an error number. */ int jack_drop_real_time_scheduling (jack_native_thread_t thread) JACK_OPTIONAL_WEAK_EXPORT; /** * Stop the thread, waiting for the thread handler to terminate. * * @param thread POSIX thread ID. * * @returns 0, if successful; otherwise an error number. */ int jack_client_stop_thread(jack_client_t* client, jack_native_thread_t thread) JACK_OPTIONAL_WEAK_EXPORT; /** * Kill the thread. * * @param thread POSIX thread ID. * * @returns 0, if successful; otherwise an error number. */ int jack_client_kill_thread(jack_client_t* client, jack_native_thread_t thread) JACK_OPTIONAL_WEAK_EXPORT; #ifndef _WIN32 typedef int (*jack_thread_creator_t)(pthread_t*, const pthread_attr_t*, void* (*function)(void*), void* arg); /** * This function can be used in very very specialized cases * where it is necessary that client threads created by JACK * are created by something other than pthread_create(). After * it is used, any threads that JACK needs for the client will * will be created by calling the function passed to this * function. * * No normal application/client should consider calling this. * The specific case for which it was created involves running * win32/x86 plugins under Wine on Linux, where it is necessary * that all threads that might call win32 functions are known * to Wine. * * Set it to NULL to restore thread creation function. * * @param creator a function that creates a new thread * */ void jack_set_thread_creator (jack_thread_creator_t creator) JACK_OPTIONAL_WEAK_EXPORT; #endif /**@}*/ #ifdef __cplusplus } #endif #endif /* __jack_thread_h__ */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/pipewire-jack/jack/transport.h000066400000000000000000000207121511204443500263470ustar00rootroot00000000000000/* Copyright (C) 2002 Paul Davis Copyright (C) 2003 Jack O'Quin This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #ifndef __jack_transport_h__ #define __jack_transport_h__ #ifdef __cplusplus extern "C" { #endif #include #include /** * @defgroup TransportControl Transport and Timebase control * @{ */ /** * Called by the timebase master to release itself from that * responsibility. * * If the timebase master releases the timebase or leaves the JACK * graph for any reason, the JACK engine takes over at the start of * the next process cycle. The transport state does not change. If * rolling, it continues to play, with frame numbers as the only * available position information. * * @see jack_set_timebase_callback * * @param client the JACK client structure. * * @return 0 on success, otherwise a non-zero error code. */ int jack_release_timebase (jack_client_t *client) JACK_OPTIONAL_WEAK_EXPORT; /** * Register (or unregister) as a slow-sync client, one that cannot * respond immediately to transport position changes. * * The @a sync_callback will be invoked at the first available * opportunity after its registration is complete. If the client is * currently active this will be the following process cycle, * otherwise it will be the first cycle after calling jack_activate(). * After that, it runs according to the ::JackSyncCallback rules. * Clients that don't set a @a sync_callback are assumed to be ready * immediately any time the transport wants to start. * * @param client the JACK client structure. * @param sync_callback is a realtime function that returns TRUE when * the client is ready. Setting @a sync_callback to NULL declares that * this client no longer requires slow-sync processing. * @param arg an argument for the @a sync_callback function. * * @return 0 on success, otherwise a non-zero error code. */ int jack_set_sync_callback (jack_client_t *client, JackSyncCallback sync_callback, void *arg) JACK_OPTIONAL_WEAK_EXPORT; /** * Set the timeout value for slow-sync clients. * * This timeout prevents unresponsive slow-sync clients from * completely halting the transport mechanism. The default is two * seconds. When the timeout expires, the transport starts rolling, * even if some slow-sync clients are still unready. The @a * sync_callbacks of these clients continue being invoked, giving them * a chance to catch up. * * @see jack_set_sync_callback * * @param client the JACK client structure. * @param timeout is delay (in microseconds) before the timeout expires. * * @return 0 on success, otherwise a non-zero error code. */ int jack_set_sync_timeout (jack_client_t *client, jack_time_t timeout) JACK_OPTIONAL_WEAK_EXPORT; /** * Register as timebase master for the JACK subsystem. * * The timebase master registers a callback that updates extended * position information such as beats or timecode whenever necessary. * Without this extended information, there is no need for this * function. * * There is never more than one master at a time. When a new client * takes over, the former @a timebase_callback is no longer called. * Taking over the timebase may be done conditionally, so it fails if * there was a master already. * * @param client the JACK client structure. * @param conditional non-zero for a conditional request. * @param timebase_callback is a realtime function that returns * position information. * @param arg an argument for the @a timebase_callback function. * * @return * - 0 on success; * - EBUSY if a conditional request fails because there was already a * timebase master; * - other non-zero error code. */ int jack_set_timebase_callback (jack_client_t *client, int conditional, JackTimebaseCallback timebase_callback, void *arg) JACK_OPTIONAL_WEAK_EXPORT; /** * Reposition the transport to a new frame number. * * May be called at any time by any client. The new position takes * effect in two process cycles. If there are slow-sync clients and * the transport is already rolling, it will enter the * ::JackTransportStarting state and begin invoking their @a * sync_callbacks until ready. This function is realtime-safe. * * @see jack_transport_reposition, jack_set_sync_callback * * @param client the JACK client structure. * @param frame frame number of new transport position. * * @return 0 if valid request, non-zero otherwise. */ int jack_transport_locate (jack_client_t *client, jack_nframes_t frame) JACK_OPTIONAL_WEAK_EXPORT; /** * Query the current transport state and position. * * This function is realtime-safe, and can be called from any thread. * If called from the process thread, @a pos corresponds to the first * frame of the current cycle and the state returned is valid for the * entire cycle. * * @param client the JACK client structure. * @param pos pointer to structure for returning current transport * position; @a pos->valid will show which fields contain valid data. * If @a pos is NULL, do not return position information. * * @return Current transport state. */ jack_transport_state_t jack_transport_query (const jack_client_t *client, jack_position_t *pos) JACK_OPTIONAL_WEAK_EXPORT; /** * Return an estimate of the current transport frame, * including any time elapsed since the last transport * positional update. * * @param client the JACK client structure */ jack_nframes_t jack_get_current_transport_frame (const jack_client_t *client) JACK_OPTIONAL_WEAK_EXPORT; /** * Request a new transport position. * * May be called at any time by any client. The new position takes * effect in two process cycles. If there are slow-sync clients and * the transport is already rolling, it will enter the * ::JackTransportStarting state and begin invoking their @a * sync_callbacks until ready. This function is realtime-safe. * * @see jack_transport_locate, jack_set_sync_callback * * @param client the JACK client structure. * @param pos requested new transport position. * * @return 0 if valid request, EINVAL if position structure rejected. */ int jack_transport_reposition (jack_client_t *client, const jack_position_t *pos) JACK_OPTIONAL_WEAK_EXPORT; /** * Start the JACK transport rolling. * * Any client can make this request at any time. It takes effect no * sooner than the next process cycle, perhaps later if there are * slow-sync clients. This function is realtime-safe. * * @see jack_set_sync_callback * * @param client the JACK client structure. */ void jack_transport_start (jack_client_t *client) JACK_OPTIONAL_WEAK_EXPORT; /** * Stop the JACK transport. * * Any client can make this request at any time. It takes effect on * the next process cycle. This function is realtime-safe. * * @param client the JACK client structure. */ void jack_transport_stop (jack_client_t *client) JACK_OPTIONAL_WEAK_EXPORT; /** * Gets the current transport info structure (deprecated). * * @param client the JACK client structure. * @param tinfo current transport info structure. The "valid" field * describes which fields contain valid data. * * @deprecated This is for compatibility with the earlier transport * interface. Use jack_transport_query(), instead. * * @pre Must be called from the process thread. */ void jack_get_transport_info (jack_client_t *client, jack_transport_info_t *tinfo) JACK_OPTIONAL_WEAK_EXPORT; /** * Set the transport info structure (deprecated). * * @deprecated This function still exists for compatibility with the * earlier transport interface, but it does nothing. Instead, define * a ::JackTimebaseCallback. */ void jack_set_transport_info (jack_client_t *client, jack_transport_info_t *tinfo) JACK_OPTIONAL_WEAK_EXPORT; /**@}*/ #ifdef __cplusplus } #endif #endif /* __jack_transport_h__ */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/pipewire-jack/jack/types.h000066400000000000000000000624131511204443500254630ustar00rootroot00000000000000/* Copyright (C) 2001 Paul Davis Copyright (C) 2004 Jack O'Quin This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #ifndef __jack_types_h__ #define __jack_types_h__ #include typedef uint64_t jack_uuid_t; typedef int32_t jack_shmsize_t; /** * Type used to represent sample frame counts. */ typedef uint32_t jack_nframes_t; /** * Maximum value that can be stored in jack_nframes_t */ #define JACK_MAX_FRAMES (4294967295U) /* This should be UINT32_MAX, but C++ has a problem with that. */ /** * Type used to represent the value of free running * monotonic clock with units of microseconds. */ typedef uint64_t jack_time_t; /** * Maximum size of @a load_init string passed to an internal client * jack_initialize() function via jack_internal_client_load(). */ #define JACK_LOAD_INIT_LIMIT 1024 /** * jack_intclient_t is an opaque type representing a loaded internal * client. You may only access it using the API provided in @ref * intclient.h "". */ typedef uint64_t jack_intclient_t; /** * jack_port_t is an opaque type. You may only access it using the * API provided. */ typedef struct _jack_port jack_port_t; /** * jack_client_t is an opaque type. You may only access it using the * API provided. */ typedef struct _jack_client jack_client_t; /** * Ports have unique ids. A port registration callback is the only * place you ever need to know their value. */ typedef uint32_t jack_port_id_t; typedef uint32_t jack_port_type_id_t; /** * @ref jack_options_t bits */ enum JackOptions { /** * Null value to use when no option bits are needed. */ JackNullOption = 0x00, /** * Do not automatically start the JACK server when it is not * already running. This option is always selected if * \$JACK_NO_START_SERVER is defined in the calling process * environment. */ JackNoStartServer = 0x01, /** * Use the exact client name requested. Otherwise, JACK * automatically generates a unique one, if needed. */ JackUseExactName = 0x02, /** * Open with optional (char *) server_name parameter. */ JackServerName = 0x04, /** * Load internal client from optional (char *) * load_name. Otherwise use the @a client_name. */ JackLoadName = 0x08, /** * Pass optional (char *) load_init string to the * jack_initialize() entry point of an internal client. */ JackLoadInit = 0x10, /** * pass a SessionID Token this allows the sessionmanager to identify the client again. */ JackSessionID = 0x20 }; /** Valid options for opening an external client. */ #define JackOpenOptions (JackSessionID|JackServerName|JackNoStartServer|JackUseExactName) /** Valid options for loading an internal client. */ #define JackLoadOptions (JackLoadInit|JackLoadName|JackUseExactName) /** * Options for several JACK operations, formed by OR-ing together the * relevant @ref JackOptions bits. */ typedef enum JackOptions jack_options_t; /** * @ref jack_status_t bits */ enum JackStatus { /** * Overall operation failed. */ JackFailure = 0x01, /** * The operation contained an invalid or unsupported option. */ JackInvalidOption = 0x02, /** * The desired client name was not unique. With the @ref * JackUseExactName option this situation is fatal. Otherwise, * the name was modified by appending a dash and a two-digit * number in the range "-01" to "-99". The * jack_get_client_name() function will return the exact string * that was used. If the specified @a client_name plus these * extra characters would be too long, the open fails instead. */ JackNameNotUnique = 0x04, /** * The JACK server was started as a result of this operation. * Otherwise, it was running already. In either case the caller * is now connected to jackd, so there is no race condition. * When the server shuts down, the client will find out. */ JackServerStarted = 0x08, /** * Unable to connect to the JACK server. */ JackServerFailed = 0x10, /** * Communication error with the JACK server. */ JackServerError = 0x20, /** * Requested client does not exist. */ JackNoSuchClient = 0x40, /** * Unable to load internal client */ JackLoadFailure = 0x80, /** * Unable to initialize client */ JackInitFailure = 0x100, /** * Unable to access shared memory */ JackShmFailure = 0x200, /** * Client's protocol version does not match */ JackVersionError = 0x400, /** * Backend error */ JackBackendError = 0x800, /** * Client zombified failure */ JackClientZombie = 0x1000 }; /** * Status word returned from several JACK operations, formed by * OR-ing together the relevant @ref JackStatus bits. */ typedef enum JackStatus jack_status_t; /** * @ref jack_latency_callback_mode_t */ enum JackLatencyCallbackMode { /** * Latency Callback for Capture Latency. * Input Ports have their latency value setup. * In the Callback the client needs to set the latency of the output ports */ JackCaptureLatency, /** * Latency Callback for Playback Latency. * Output Ports have their latency value setup. * In the Callback the client needs to set the latency of the input ports */ JackPlaybackLatency }; /** * Type of Latency Callback (Capture or Playback) */ typedef enum JackLatencyCallbackMode jack_latency_callback_mode_t; /** * Prototype for the client supplied function that is called * by the engine when port latencies need to be recalculated * * @param mode playback or capture latency * @param arg pointer to a client supplied data * * @return zero on success, non-zero on error */ typedef void (*JackLatencyCallback)(jack_latency_callback_mode_t mode, void *arg); /** * the new latency API operates on Ranges. */ PRE_PACKED_STRUCTURE struct _jack_latency_range { /** * minimum latency */ jack_nframes_t min; /** * maximum latency */ jack_nframes_t max; } POST_PACKED_STRUCTURE; typedef struct _jack_latency_range jack_latency_range_t; /** * Prototype for the client supplied function that is called * by the engine anytime there is work to be done. * * @pre nframes == jack_get_buffer_size() * @pre nframes == pow(2,x) * * @param nframes number of frames to process * @param arg pointer to a client supplied structure * * @return zero on success, non-zero on error */ typedef int (*JackProcessCallback)(jack_nframes_t nframes, void *arg); /** * Prototype for the client thread routine called * by the engine when the client is inserted in the graph. * * @param arg pointer to a client supplied structure * */ typedef void *(*JackThreadCallback)(void* arg); /** * Prototype for the client supplied function that is called * once after the creation of the thread in which other * callbacks will be made. Special thread characteristics * can be set from this callback, for example. This is a * highly specialized callback and most clients will not * and should not use it. * * @param arg pointer to a client supplied structure * * @return void */ typedef void (*JackThreadInitCallback)(void *arg); /** * Prototype for the client supplied function that is called * whenever the processing graph is reordered. * * @param arg pointer to a client supplied structure * * @return zero on success, non-zero on error */ typedef int (*JackGraphOrderCallback)(void *arg); /** * Prototype for the client-supplied function that is called whenever * an xrun has occurred. * * @see jack_get_xrun_delayed_usecs() * * @param arg pointer to a client supplied structure * * @return zero on success, non-zero on error */ typedef int (*JackXRunCallback)(void *arg); /** * Prototype for the @a bufsize_callback that is invoked whenever the * JACK engine buffer size changes. Although this function is called * in the JACK process thread, the normal process cycle is suspended * during its operation, causing a gap in the audio flow. So, the @a * bufsize_callback can allocate storage, touch memory not previously * referenced, and perform other operations that are not realtime * safe. * * @param nframes buffer size * @param arg pointer supplied by jack_set_buffer_size_callback(). * * @return zero on success, non-zero on error */ typedef int (*JackBufferSizeCallback)(jack_nframes_t nframes, void *arg); /** * Prototype for the client supplied function that is called * when the engine sample rate changes. * * @param nframes new engine sample rate * @param arg pointer to a client supplied structure * * @return zero on success, non-zero on error */ typedef int (*JackSampleRateCallback)(jack_nframes_t nframes, void *arg); /** * Prototype for the client supplied function that is called * whenever a port is registered or unregistered. * * @param port the ID of the port * @param arg pointer to a client supplied data * @param register non-zero if the port is being registered, * zero if the port is being unregistered */ typedef void (*JackPortRegistrationCallback)(jack_port_id_t port, int /* register */, void *arg); /** * Prototype for the client supplied function that is called * whenever a client is registered or unregistered. * * @param name a null-terminated string containing the client name * @param register non-zero if the client is being registered, * zero if the client is being unregistered * @param arg pointer to a client supplied structure */ typedef void (*JackClientRegistrationCallback)(const char* name, int /* register */, void *arg); /** * Prototype for the client supplied function that is called * whenever a port is connected or disconnected. * * @param a one of two ports connected or disconnected * @param b one of two ports connected or disconnected * @param connect non-zero if ports were connected * zero if ports were disconnected * @param arg pointer to a client supplied data */ typedef void (*JackPortConnectCallback)(jack_port_id_t a, jack_port_id_t b, int connect, void* arg); /** * Prototype for the client supplied function that is called * whenever the port name has been changed. * * @param port the port that has been renamed * @param new_name the new name * @param arg pointer to a client supplied structure */ typedef void (*JackPortRenameCallback)(jack_port_id_t port, const char* old_name, const char* new_name, void *arg); /** * Prototype for the client supplied function that is called * whenever jackd starts or stops freewheeling. * * @param starting non-zero if we start starting to freewheel, zero otherwise * @param arg pointer to a client supplied structure */ typedef void (*JackFreewheelCallback)(int starting, void *arg); /** * Prototype for the client supplied function that is called * whenever jackd is shutdown. Note that after server shutdown, * the client pointer is *not* deallocated by libjack, * the application is responsible to properly use jack_client_close() * to release client resources. Warning: jack_client_close() cannot be * safely used inside the shutdown callback and has to be called outside of * the callback context. * * @param arg pointer to a client supplied structure */ typedef void (*JackShutdownCallback)(void *arg); /** * Prototype for the client supplied function that is called * whenever jackd is shutdown. Note that after server shutdown, * the client pointer is *not* deallocated by libjack, * the application is responsible to properly use jack_client_close() * to release client resources. Warning: jack_client_close() cannot be * safely used inside the shutdown callback and has to be called outside of * the callback context. * @param code a status word, formed by OR-ing together the relevant @ref JackStatus bits. * @param reason a string describing the shutdown reason (backend failure, server crash... etc...). * Note that this string will not be available anymore after the callback returns, so possibly copy it. * @param arg pointer to a client supplied structure */ typedef void (*JackInfoShutdownCallback)(jack_status_t code, const char* reason, void *arg); /** * Used for the type argument of jack_port_register() for default * audio ports and midi ports. */ #define JACK_DEFAULT_AUDIO_TYPE "32 bit float mono audio" #define JACK_DEFAULT_MIDI_TYPE "8 bit raw midi" /** * For convenience, use this typedef if you want to be able to change * between float and double. You may want to typedef sample_t to * jack_default_audio_sample_t in your application. */ typedef float jack_default_audio_sample_t; /** * A port has a set of flags that are formed by AND-ing together the * desired values from the list below. The flags "JackPortIsInput" and * "JackPortIsOutput" are mutually exclusive and it is an error to use * them both. */ enum JackPortFlags { /** * if JackPortIsInput is set, then the port can receive * data. */ JackPortIsInput = 0x1, /** * if JackPortIsOutput is set, then data can be read from * the port. */ JackPortIsOutput = 0x2, /** * if JackPortIsPhysical is set, then the port corresponds * to some kind of physical I/O connector. */ JackPortIsPhysical = 0x4, /** * if JackPortCanMonitor is set, then a call to * jack_port_request_monitor() makes sense. * * Precisely what this means is dependent on the client. A typical * result of it being called with TRUE as the second argument is * that data that would be available from an output port (with * JackPortIsPhysical set) is sent to a physical output connector * as well, so that it can be heard/seen/whatever. * * Clients that do not control physical interfaces * should never create ports with this bit set. */ JackPortCanMonitor = 0x8, /** * JackPortIsTerminal means: * * for an input port: the data received by the port * will not be passed on or made * available at any other port * * for an output port: the data available at the port * does not originate from any other port * * Audio synthesizers, I/O hardware interface clients, HDR * systems are examples of clients that would set this flag for * their ports. */ JackPortIsTerminal = 0x10, /** * if JackPortIsCV is set, then the port buffer represents audio-rate * control data rather than audio. * * Clients SHOULD prevent connections between Audio and CV ports. * * To make the ports more meaningful, clients can add meta-data to them. * It is recommended to set these 2 in particular: * - http://lv2plug.in/ns/lv2core#minimum * - http://lv2plug.in/ns/lv2core#maximum */ JackPortIsCV = 0x20, /** * if JackPortIsMIDI2 is set, then the port expects to receive MIDI2 data. * * JACK will automatically convert MIDI1 data into MIDI2 for this port. * * for ports without this flag JACK will convert MIDI2 into MIDI1 * as much possible, some events might be skipped. */ JackPortIsMIDI2 = 0x20, }; /** * Transport states. */ typedef enum { /* the order matters for binary compatibility */ JackTransportStopped = 0, /**< Transport halted */ JackTransportRolling = 1, /**< Transport playing */ JackTransportLooping = 2, /**< For OLD_TRANSPORT, now ignored */ JackTransportStarting = 3, /**< Waiting for sync ready */ JackTransportNetStarting = 4, /**< Waiting for sync ready on the network*/ } jack_transport_state_t; typedef uint64_t jack_unique_t; /**< Unique ID (opaque) */ /** * Optional struct jack_position_t fields. */ typedef enum { JackPositionBBT = 0x10, /**< Bar, Beat, Tick */ JackPositionTimecode = 0x20, /**< External timecode */ JackBBTFrameOffset = 0x40, /**< Frame offset of BBT information */ JackAudioVideoRatio = 0x80, /**< audio frames per video frame */ JackVideoFrameOffset = 0x100, /**< frame offset of first video frame */ JackTickDouble = 0x200, /**< double-resolution tick */ } jack_position_bits_t; /** all valid position bits */ #define JACK_POSITION_MASK (JackPositionBBT|JackPositionTimecode) #define EXTENDED_TIME_INFO /** transport tick_double member is available for use */ #define JACK_TICK_DOUBLE PRE_PACKED_STRUCTURE struct _jack_position { /* these four cannot be set from clients: the server sets them */ jack_unique_t unique_1; /**< unique ID */ jack_time_t usecs; /**< monotonic, free-rolling */ jack_nframes_t frame_rate; /**< current frame rate (per second) */ jack_nframes_t frame; /**< frame number, always present */ jack_position_bits_t valid; /**< which other fields are valid */ /* JackPositionBBT fields: */ int32_t bar; /**< current bar */ int32_t beat; /**< current beat-within-bar */ int32_t tick; /**< current tick-within-beat */ double bar_start_tick; float beats_per_bar; /**< time signature "numerator" */ float beat_type; /**< time signature "denominator" */ double ticks_per_beat; double beats_per_minute; /* JackPositionTimecode fields: (EXPERIMENTAL: could change) */ double frame_time; /**< current time in seconds */ double next_time; /**< next sequential frame_time (unless repositioned) */ /* JackBBTFrameOffset fields: */ jack_nframes_t bbt_offset; /**< frame offset for the BBT fields (the given bar, beat, and tick values actually refer to a time frame_offset frames before the start of the cycle), should be assumed to be 0 if JackBBTFrameOffset is not set. If JackBBTFrameOffset is set and this value is zero, the BBT time refers to the first frame of this cycle. If the value is positive, the BBT time refers to a frame that many frames before the start of the cycle. */ /* JACK video positional data (experimental) */ float audio_frames_per_video_frame; /**< number of audio frames per video frame. Should be assumed zero if JackAudioVideoRatio is not set. If JackAudioVideoRatio is set and the value is zero, no video data exists within the JACK graph */ jack_nframes_t video_offset; /**< audio frame at which the first video frame in this cycle occurs. Should be assumed to be 0 if JackVideoFrameOffset is not set. If JackVideoFrameOffset is set, but the value is zero, there is no video frame within this cycle. */ /* JACK extra transport fields */ double tick_double; /**< current tick-within-beat in double resolution. Should be assumed zero if JackTickDouble is not set. Since older versions of JACK do not expose this variable, the macro JACK_TICK_DOUBLE is provided, which can be used as build-time detection. */ /* For binary compatibility, new fields should be allocated from * this padding area with new valid bits controlling access, so * the existing structure size and offsets are preserved. */ int32_t padding[5]; /* When (unique_1 == unique_2) the contents are consistent. */ jack_unique_t unique_2; /**< unique ID */ } POST_PACKED_STRUCTURE; typedef struct _jack_position jack_position_t; /** * Prototype for the @a sync_callback defined by slow-sync clients. * When the client is active, this callback is invoked just before * process() in the same thread. This occurs once after registration, * then subsequently whenever some client requests a new position, or * the transport enters the ::JackTransportStarting state. This * realtime function must not wait. * * The transport @a state will be: * * - ::JackTransportStopped when a new position is requested; * - ::JackTransportStarting when the transport is waiting to start; * - ::JackTransportRolling when the timeout has expired, and the * position is now a moving target. * * @param state current transport state. * @param pos new transport position. * @param arg the argument supplied by jack_set_sync_callback(). * * @return TRUE (non-zero) when ready to roll. */ typedef int (*JackSyncCallback)(jack_transport_state_t state, jack_position_t *pos, void *arg); /** * Prototype for the @a timebase_callback used to provide extended * position information. Its output affects all of the following * process cycle. This realtime function must not wait. * * This function is called immediately after process() in the same * thread whenever the transport is rolling, or when any client has * requested a new position in the previous cycle. The first cycle * after jack_set_timebase_callback() is also treated as a new * position, or the first cycle after jack_activate() if the client * had been inactive. * * The timebase master may not use its @a pos argument to set @a * pos->frame. To change position, use jack_transport_reposition() or * jack_transport_locate(). These functions are realtime-safe, the @a * timebase_callback can call them directly. * * @param state current transport state. * @param nframes number of frames in current period. * @param pos address of the position structure for the next cycle; @a * pos->frame will be its frame number. If @a new_pos is FALSE, this * structure contains extended position information from the current * cycle. If TRUE, it contains whatever was set by the requester. * The @a timebase_callback's task is to update the extended * information here. * @param new_pos TRUE (non-zero) for a newly requested @a pos, or for * the first cycle after the @a timebase_callback is defined. * @param arg the argument supplied by jack_set_timebase_callback(). */ typedef void (*JackTimebaseCallback)(jack_transport_state_t state, jack_nframes_t nframes, jack_position_t *pos, int new_pos, void *arg); /********************************************************************* * The following interfaces are DEPRECATED. They are only provided * for compatibility with the earlier JACK transport implementation. *********************************************************************/ /** * Optional struct jack_transport_info_t fields. * * @see jack_position_bits_t. */ typedef enum { JackTransportState = 0x1, /**< Transport state */ JackTransportPosition = 0x2, /**< Frame number */ JackTransportLoop = 0x4, /**< Loop boundaries (ignored) */ JackTransportSMPTE = 0x8, /**< SMPTE (ignored) */ JackTransportBBT = 0x10 /**< Bar, Beat, Tick */ } jack_transport_bits_t; /** * Deprecated struct for transport position information. * * @deprecated This is for compatibility with the earlier transport * interface. Use the jack_position_t struct, instead. */ typedef struct { /* these two cannot be set from clients: the server sets them */ jack_nframes_t frame_rate; /**< current frame rate (per second) */ jack_time_t usecs; /**< monotonic, free-rolling */ jack_transport_bits_t valid; /**< which fields are legal to read */ jack_transport_state_t transport_state; jack_nframes_t frame; jack_nframes_t loop_start; jack_nframes_t loop_end; long smpte_offset; /**< SMPTE offset (from frame 0) */ float smpte_frame_rate; /**< 29.97, 30, 24 etc. */ int bar; int beat; int tick; double bar_start_tick; float beats_per_bar; float beat_type; double ticks_per_beat; double beats_per_minute; } jack_transport_info_t; #endif /* __jack_types_h__ */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/pipewire-jack/jack/uuid.h000066400000000000000000000031511511204443500252570ustar00rootroot00000000000000/* Copyright (C) 2013 Paul Davis This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #ifndef __jack_uuid_h__ #define __jack_uuid_h__ #include #ifdef __cplusplus extern "C" { #endif #define JACK_UUID_SIZE 36 #define JACK_UUID_STRING_SIZE (JACK_UUID_SIZE+1) /* includes trailing null */ #define JACK_UUID_EMPTY_INITIALIZER 0 extern jack_uuid_t jack_client_uuid_generate (void); extern jack_uuid_t jack_port_uuid_generate (uint32_t port_id); extern uint32_t jack_uuid_to_index (jack_uuid_t); extern int jack_uuid_compare (jack_uuid_t, jack_uuid_t); extern void jack_uuid_copy (jack_uuid_t* dst, jack_uuid_t src); extern void jack_uuid_clear (jack_uuid_t*); extern int jack_uuid_parse (const char *buf, jack_uuid_t*); extern void jack_uuid_unparse (jack_uuid_t, char buf[JACK_UUID_STRING_SIZE]); extern int jack_uuid_empty (jack_uuid_t); #ifdef __cplusplus } /* namespace */ #endif #endif /* __jack_uuid_h__ */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/pipewire-jack/jack/weakjack.h000066400000000000000000000123721511204443500260760ustar00rootroot00000000000000/* Copyright (C) 2010 Paul Davis This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #ifndef __weakjack_h__ #define __weakjack_h__ /** * @defgroup WeakLinkage Managing support for newer/older versions of JACK * @{ One challenge faced by developers is that of taking * advantage of new features introduced in new versions * of [ JACK ] while still supporting older versions of * the system. Normally, if an application uses a new * feature in a library/API, it is unable to run on * earlier versions of the library/API that do not * support that feature. Such applications would either * fail to launch or crash when an attempt to use the * feature was made. This problem cane be solved using * weakly-linked symbols. * * When a symbol in a framework is defined as weakly * linked, the symbol does not have to be present at * runtime for a process to continue running. The static * linker identifies a weakly linked symbol as such in * any code module that references the symbol. The * dynamic linker uses this same information at runtime * to determine whether a process can continue * running. If a weakly linked symbol is not present in * the framework, the code module can continue to run as * long as it does not reference the symbol. However, if * the symbol is present, the code can use it normally. * * (adapted from: http://developer.apple.com/library/mac/#documentation/MacOSX/Conceptual/BPFrameworks/Concepts/WeakLinking.html) * * A concrete example will help. Suppose that someone uses a version * of a JACK client we'll call "Jill". Jill was linked against a version * of JACK that contains a newer part of the API (say, jack_set_latency_callback()) * and would like to use it if it is available. * * When Jill is run on a system that has a suitably "new" version of * JACK, this function will be available entirely normally. But if Jill * is run on a system with an old version of JACK, the function isn't * available. * * With normal symbol linkage, this would create a startup error whenever * someone tries to run Jill with the "old" version of JACK. However, functions * added to JACK after version 0.116.2 are all declared to have "weak" linkage * which means that their absence doesn't cause an error during program * startup. Instead, Jill can test whether or not the symbol jack_set_latency_callback * is null or not. If its null, it means that the JACK installed on this machine * is too old to support this function. If it's not null, then Jill can use it * just like any other function in the API. For example: * * \code * if (jack_set_latency_callback) { * jack_set_latency_callback (jill_client, jill_latency_callback, arg); * } * \endcode * * However, there are clients that may want to use this approach to parts of the * the JACK API that predate 0.116.2. For example, they might want to see if even * really old basic parts of the API like jack_client_open() exist at runtime. * * Such clients should include before any other JACK header. * This will make the \b entire JACK API be subject to weak linkage, so that any * and all functions can be checked for existence at runtime. It is important * to understand that very few clients need to do this - if you use this * feature you should have a clear reason to do so. * * */ #ifdef __APPLE__ #define WEAK_ATTRIBUTE weak_import #else #define WEAK_ATTRIBUTE __weak__ #endif #ifndef JACK_OPTIONAL_WEAK_EXPORT /* JACK_OPTIONAL_WEAK_EXPORT needs to be a macro which expands into a compiler directive. If non-null, the directive must tell the compiler to arrange for weak linkage of the symbol it used with. For this to work fully may require linker arguments for the client as well. */ #ifdef __GNUC__ #define JACK_OPTIONAL_WEAK_EXPORT __attribute__((WEAK_ATTRIBUTE)) #else /* Add other things here for non-gcc platforms */ #endif #endif #ifndef JACK_OPTIONAL_WEAK_DEPRECATED_EXPORT /* JACK_OPTIONAL_WEAK_DEPRECATED_EXPORT needs to be a macro which expands into a compiler directive. If non-null, the directive must tell the compiler to arrange for weak linkage of the symbol it is used with AND optionally to mark the symbol as deprecated. For this to work fully may require linker arguments for the client as well. */ #ifdef __GNUC__ #define JACK_OPTIONAL_WEAK_DEPRECATED_EXPORT __attribute__((WEAK_ATTRIBUTE,__deprecated__)) #else /* Add other things here for non-gcc platforms */ #endif #endif /**@}*/ #endif /* weakjack */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/pipewire-jack/jack/weakmacros.h000066400000000000000000000053171511204443500264530ustar00rootroot00000000000000/* Copyright (C) 2010 Paul Davis This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #ifndef __weakmacros_h__ #define __weakmacros_h__ /************************************************************* * NOTE: JACK_WEAK_EXPORT ***MUST*** be used on every function * added to the JACK API after the 0.116.2 release. * * Functions that predate this release are marked with * JACK_WEAK_OPTIONAL_EXPORT which can be defined at compile * time in a variety of ways. The default definition is empty, * so that these symbols get normal linkage. If you wish to * use all JACK symbols with weak linkage, include * before jack.h. *************************************************************/ #ifdef __APPLE__ #define WEAK_ATTRIBUTE weak_import #else #define WEAK_ATTRIBUTE __weak__ #endif #ifndef JACK_WEAK_EXPORT #ifdef __GNUC__ /* JACK_WEAK_EXPORT needs to be a macro which expands into a compiler directive. If non-null, the directive must tell the compiler to arrange for weak linkage of the symbol it used with. For this to work full may require linker arguments in the client as well. */ #ifdef _WIN32 /* Not working with __declspec(dllexport) so normal linking Linking with JackWeakAPI.cpp will be the preferred way. */ #define JACK_WEAK_EXPORT #else #define JACK_WEAK_EXPORT __attribute__((WEAK_ATTRIBUTE)) #endif #else /* Add other things here for non-gcc platforms */ #ifdef _WIN32 #define JACK_WEAK_EXPORT #endif #endif #endif #ifndef JACK_WEAK_EXPORT #define JACK_WEAK_EXPORT #endif #ifndef JACK_OPTIONAL_WEAK_EXPORT #define JACK_OPTIONAL_WEAK_EXPORT #endif #ifndef JACK_OPTIONAL_WEAK_DEPRECATED_EXPORT #ifdef __GNUC__ #define JACK_OPTIONAL_WEAK_DEPRECATED_EXPORT __attribute__((__deprecated__)) #else /* Add other things here for non-gcc platforms */ #ifdef _WIN32 #define JACK_OPTIONAL_WEAK_DEPRECATED_EXPORT #endif #endif /* __GNUC__ */ #ifndef JACK_OPTIONAL_WEAK_DEPRECATED_EXPORT #define JACK_OPTIONAL_WEAK_DEPRECATED_EXPORT #endif #endif #endif /* __weakmacros_h__ */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/pipewire-jack/meson.build000066400000000000000000000002641511204443500253740ustar00rootroot00000000000000jack_inc = include_directories('.') if get_option('jack-devel') == true install_subdir('jack', install_dir: get_option('includedir'), strip_directory: false) endif subdir('src') pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/pipewire-jack/src/000077500000000000000000000000001511204443500240175ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/pipewire-jack/src/control.c000066400000000000000000000226011511204443500256440ustar00rootroot00000000000000/* PipeWire */ /* SPDX-FileCopyrightText: Copyright © 2021 Florian Hülsmann */ /* SPDX-License-Identifier: MIT */ #include "config.h" #include #include #include #include #include #include struct jackctl_sigmask { sigset_t signals; }; struct jackctl_sigmask sigmask; SPA_EXPORT jackctl_sigmask_t * jackctl_setup_signals(unsigned int flags) { // stub pw_log_warn("not implemented %d", flags); sigemptyset(&sigmask.signals); return &sigmask; } SPA_EXPORT void jackctl_wait_signals(jackctl_sigmask_t * signals) { // stub pw_log_warn("not implemented %p", signals); } SPA_EXPORT jackctl_server_t * jackctl_server_create( bool (* on_device_acquire)(const char * device_name), void (* on_device_release)(const char * device_name)) { pw_log_error("deprecated"); return jackctl_server_create2(on_device_acquire, on_device_release, NULL); } struct jackctl_server { // stub JSList * empty; JSList * drivers; }; struct jackctl_driver { // stub }; SPA_EXPORT jackctl_server_t * jackctl_server_create2( bool (* on_device_acquire)(const char * device_name), void (* on_device_release)(const char * device_name), void (* on_device_reservation_loop)(void)) { // stub pw_log_warn("not implemented %p %p %p", on_device_acquire, on_device_release, on_device_reservation_loop); // setup server jackctl_server_t * server; server = (jackctl_server_t *)malloc(sizeof(jackctl_server_t)); if (server == NULL) { return NULL; } server->empty = NULL; server->drivers = NULL; // setup dummy (default) driver jackctl_driver_t * dummy; dummy = (jackctl_driver_t *)malloc(sizeof(jackctl_driver_t)); if (dummy == NULL) { free(server); return NULL; } server->drivers = jack_slist_append (server->drivers, dummy); return server; } SPA_EXPORT void jackctl_server_destroy(jackctl_server_t * server) { // stub pw_log_warn("%p: not implemented", server); if (server) { if (server->drivers) { free(server->drivers->data); } jack_slist_free(server->empty); jack_slist_free(server->drivers); free(server); } } SPA_EXPORT bool jackctl_server_open(jackctl_server_t * server, jackctl_driver_t * driver) { // stub pw_log_warn("%p: not implemented %p", server, driver); return true; } SPA_EXPORT bool jackctl_server_start(jackctl_server_t * server) { // stub pw_log_warn("%p: not implemented", server); return true; } SPA_EXPORT bool jackctl_server_stop(jackctl_server_t * server) { // stub pw_log_warn("%p: not implemented", server); return true; } SPA_EXPORT bool jackctl_server_close(jackctl_server_t * server) { // stub pw_log_warn("%p: not implemented", server); return true; } SPA_EXPORT const JSList * jackctl_server_get_drivers_list(jackctl_server_t * server) { // stub pw_log_warn("%p: not implemented", server); if (server == NULL) { pw_log_warn("server == NULL"); return NULL; } return server->drivers; } SPA_EXPORT const JSList * jackctl_server_get_parameters(jackctl_server_t * server) { // stub pw_log_warn("%p: not implemented", server); if (server == NULL) { return NULL; } return server->empty; } SPA_EXPORT const JSList * jackctl_server_get_internals_list(jackctl_server_t * server) { // stub pw_log_warn("%p: not implemented", server); if (server == NULL) { return NULL; } return server->empty; } SPA_EXPORT bool jackctl_server_load_internal(jackctl_server_t * server, jackctl_internal_t * internal) { // stub pw_log_warn("%p: not implemented %p", server, internal); return true; } SPA_EXPORT bool jackctl_server_unload_internal(jackctl_server_t * server, jackctl_internal_t * internal) { // stub pw_log_warn("%p: not implemented %p", server, internal); return true; } SPA_EXPORT bool jackctl_server_load_session_file(jackctl_server_t * server_ptr, const char * file) { // stub pw_log_warn("%p: not implemented %s", server_ptr, file); return false; } SPA_EXPORT bool jackctl_server_add_slave(jackctl_server_t * server, jackctl_driver_t * driver) { // stub pw_log_warn("%p: not implemented %p", server, driver); return false; } SPA_EXPORT bool jackctl_server_remove_slave(jackctl_server_t * server, jackctl_driver_t * driver) { // stub pw_log_warn("%p: not implemented %p", server, driver); return false; } SPA_EXPORT bool jackctl_server_switch_master(jackctl_server_t * server, jackctl_driver_t * driver) { // stub pw_log_warn("%p: not implemented %p", server, driver); return false; } SPA_EXPORT const char * jackctl_driver_get_name(jackctl_driver_t * driver) { // stub pw_log_warn("%p: not implemented", driver); return "dummy"; } SPA_EXPORT jackctl_driver_type_t jackctl_driver_get_type(jackctl_driver_t * driver) { // stub pw_log_warn("%p: not implemented", driver); return (jackctl_driver_type_t)0; } SPA_EXPORT const JSList * jackctl_driver_get_parameters(jackctl_driver_t * driver) { // stub pw_log_warn("%p: not implemented", driver); return NULL; } SPA_EXPORT int jackctl_driver_params_parse(jackctl_driver_t * driver, int argc, char* argv[]) { // stub pw_log_warn("%p: not implemented %d %p", driver, argc, argv); return 1; } SPA_EXPORT const char * jackctl_internal_get_name(jackctl_internal_t * internal) { // stub pw_log_warn("not implemented %p", internal); return "pipewire-jack-stub"; } SPA_EXPORT const JSList * jackctl_internal_get_parameters(jackctl_internal_t * internal) { // stub pw_log_warn("not implemented %p", internal); return NULL; } SPA_EXPORT const char * jackctl_parameter_get_name(jackctl_parameter_t * parameter) { // stub pw_log_warn("%p: not implemented", parameter); return "pipewire-jack-stub"; } SPA_EXPORT const char * jackctl_parameter_get_short_description(jackctl_parameter_t * parameter) { // stub pw_log_warn("%p: not implemented", parameter); return "pipewire-jack-stub"; } SPA_EXPORT const char * jackctl_parameter_get_long_description(jackctl_parameter_t * parameter) { // stub pw_log_warn("%p: not implemented", parameter); return "pipewire-jack-stub"; } SPA_EXPORT jackctl_param_type_t jackctl_parameter_get_type(jackctl_parameter_t * parameter) { // stub pw_log_warn("%p: not implemented", parameter); return (jackctl_param_type_t)0; } SPA_EXPORT char jackctl_parameter_get_id(jackctl_parameter_t * parameter) { // stub pw_log_warn("%p: not implemented", parameter); return 0; } SPA_EXPORT bool jackctl_parameter_is_set(jackctl_parameter_t * parameter) { // stub pw_log_warn("%p: not implemented", parameter); return false; } SPA_EXPORT bool jackctl_parameter_reset(jackctl_parameter_t * parameter) { // stub pw_log_warn("%p: not implemented", parameter); return false; } SPA_EXPORT union jackctl_parameter_value jackctl_parameter_get_value(jackctl_parameter_t * parameter) { // stub pw_log_warn("%p: not implemented", parameter); union jackctl_parameter_value value; memset(&value, 0, sizeof(value)); return value; } SPA_EXPORT bool jackctl_parameter_set_value( jackctl_parameter_t * parameter, const union jackctl_parameter_value * value_ptr) { // stub pw_log_warn("%p: not implemented", parameter); return false; } SPA_EXPORT union jackctl_parameter_value jackctl_parameter_get_default_value(jackctl_parameter_t * parameter) { // stub pw_log_warn("%p: not implemented", parameter); union jackctl_parameter_value value; memset(&value, 0, sizeof(value)); return value; } SPA_EXPORT bool jackctl_parameter_has_range_constraint(jackctl_parameter_t * parameter) { // stub pw_log_warn("%p: not implemented", parameter); return false; } SPA_EXPORT bool jackctl_parameter_has_enum_constraint(jackctl_parameter_t * parameter) { // stub pw_log_warn("%p: not implemented", parameter); return false; } SPA_EXPORT uint32_t jackctl_parameter_get_enum_constraints_count(jackctl_parameter_t * parameter) { // stub pw_log_warn("%p: not implemented", parameter); return 0; } SPA_EXPORT union jackctl_parameter_value jackctl_parameter_get_enum_constraint_value( jackctl_parameter_t * parameter, uint32_t index) { // stub pw_log_warn("%p: not implemented %d", parameter, index); union jackctl_parameter_value value; memset(&value, 0, sizeof(value)); return value; } SPA_EXPORT const char * jackctl_parameter_get_enum_constraint_description( jackctl_parameter_t * parameter, uint32_t index) { // stub pw_log_warn("%p: not implemented %d", parameter, index); return "pipewire-jack-stub"; } SPA_EXPORT void jackctl_parameter_get_range_constraint( jackctl_parameter_t * parameter, union jackctl_parameter_value * min_ptr, union jackctl_parameter_value * max_ptr) { // stub pw_log_warn("%p: not implemented %p %p", parameter, min_ptr, max_ptr); } SPA_EXPORT bool jackctl_parameter_constraint_is_strict(jackctl_parameter_t * parameter) { // stub pw_log_warn("not implemented %p", parameter); return false; } SPA_EXPORT bool jackctl_parameter_constraint_is_fake_value(jackctl_parameter_t * parameter) { // stub pw_log_warn("not implemented %p", parameter); return false; } SPA_EXPORT SPA_PRINTF_FUNC(1, 2) void jack_error(const char *format, ...) { va_list args; va_start(args, format); pw_log_logv(SPA_LOG_LEVEL_ERROR, "", 0, "", format, args); va_end(args); } SPA_EXPORT SPA_PRINTF_FUNC(1, 2) void jack_info(const char *format, ...) { va_list args; va_start(args, format); pw_log_logv(SPA_LOG_LEVEL_INFO, "", 0, "", format, args); va_end(args); } SPA_EXPORT SPA_PRINTF_FUNC(1, 2) void jack_log(const char *format, ...) { va_list args; va_start(args, format); pw_log_logv(SPA_LOG_LEVEL_DEBUG, "", 0, "", format, args); va_end(args); } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/pipewire-jack/src/dummy.c000066400000000000000000000005541511204443500253220ustar00rootroot00000000000000/* PipeWire */ /* SPDX-FileCopyrightText: Copyright © 2020 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include "config.h" #include #include #include #include #include #include static void reg(void) __attribute__ ((constructor)); static void reg(void) { pw_init(NULL, NULL); } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/pipewire-jack/src/export.c000066400000000000000000000020741511204443500255070ustar00rootroot00000000000000/* PipeWire */ /* SPDX-FileCopyrightText: Copyright © 2020 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #define JACK_METADATA_PREFIX "http://jackaudio.org/metadata/" SPA_EXPORT const char *JACK_METADATA_CONNECTED = JACK_METADATA_PREFIX "connected"; SPA_EXPORT const char *JACK_METADATA_EVENT_TYPES = JACK_METADATA_PREFIX "event-types"; SPA_EXPORT const char *JACK_METADATA_HARDWARE = JACK_METADATA_PREFIX "hardware"; SPA_EXPORT const char *JACK_METADATA_ICON_LARGE = JACK_METADATA_PREFIX "icon-large"; SPA_EXPORT const char *JACK_METADATA_ICON_NAME = JACK_METADATA_PREFIX "icon-name"; SPA_EXPORT const char *JACK_METADATA_ICON_SMALL = JACK_METADATA_PREFIX "icon-small"; SPA_EXPORT const char *JACK_METADATA_ORDER = JACK_METADATA_PREFIX "order"; SPA_EXPORT const char *JACK_METADATA_PORT_GROUP = JACK_METADATA_PREFIX "port-group"; SPA_EXPORT const char *JACK_METADATA_PRETTY_NAME = JACK_METADATA_PREFIX "pretty-name"; SPA_EXPORT const char *JACK_METADATA_SIGNAL_TYPE = JACK_METADATA_PREFIX "signal-type"; #undef JACK_METADATA_PREFIX pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/pipewire-jack/src/meson.build000066400000000000000000000064751511204443500261750ustar00rootroot00000000000000pipewire_jack_sources = [ 'export.c', 'pipewire-jack.c', 'ringbuffer.c', 'uuid.c', ] pipewire_jackserver_sources = pipewire_jack_sources pipewire_jackserver_sources += [ 'control.c', ] pipewire_net_sources = [ 'net.c', ] pipewire_jack_c_args = [ '-DPIC', ] libjack_path = get_option('libjack-path') if libjack_path == '' libjack_path = modules_install_dir / 'jack' libjack_path_dlopen = modules_install_dir_dlopen / 'jack' libjack_path_enable = '' elif libjack_path == get_option('libdir') or libjack_path == pipewire_libdir libjack_path = pipewire_libdir libjack_path_dlopen = libjack_path libjack_path_enable = '#' else libjack_path_dlopen = libjack_path libjack_path_enable = '' endif tools_config = configuration_data() tools_config.set('LIBJACK_PATH', libjack_path_dlopen) tools_config.set('LIBJACK_PATH_ENABLE', libjack_path_enable) configure_file(input : 'pw-jack.in', output : 'pw-jack', configuration : tools_config, install_dir : pipewire_bindir) pipewire_jack = shared_library('jack', pipewire_jack_sources, soversion : soversion, version : libjackversion, c_args : pipewire_jack_c_args, include_directories : [configinc, jack_inc], dependencies : [pipewire_dep, mathlib], install : true, install_dir : libjack_path, ) pipewire_jackserver = shared_library('jackserver', pipewire_jackserver_sources, soversion : soversion, version : libjackversion, c_args : pipewire_jack_c_args, include_directories : [configinc, jack_inc], dependencies : [pipewire_dep, mathlib], install : true, install_dir : libjack_path, ) pipewire_jacknet = shared_library('jacknet', pipewire_net_sources, soversion : soversion, version : libjackversion, c_args : pipewire_jack_c_args, include_directories : [configinc, jack_inc], dependencies : [pipewire_dep, mathlib], install : true, install_dir : libjack_path, ) if get_option('jack-devel') == true if meson.version().version_compare('<0.59.0') error( ''' Before version 0.59.0 Meson creates a wrong jack pkg-config file. For that reason this is now an error. Please update Meson, if you want to have JACK development files. ''') endif pkgconfig.generate(filebase : 'jack', libraries : [pipewire_jack], name : 'jack', description : 'PipeWire JACK API', version : jackversion, extra_cflags : '-D_REENTRANT', unescaped_variables: ['server_libs=-L${libdir} -ljackserver', 'jack_implementation=pipewire']) pkgconfig.generate(filebase : 'jackserver', libraries : [pipewire_jackserver], name : 'jackserver', description : 'PipeWire JACK Control API', version : jackversion, unescaped_variables: ['jack_implementation=pipewire']) endif if sdl_dep.found() executable('video-dsp-play', '../examples/video-dsp-play.c', include_directories : [jack_inc], install : installed_tests_enabled, install_dir : installed_tests_execdir / 'examples' / 'jack', dependencies : [sdl_dep, mathlib], link_with: pipewire_jack, ) endif executable('ump-source', '../examples/ump-source.c', include_directories : [jack_inc], install : installed_tests_enabled, install_dir : installed_tests_execdir / 'examples' / 'jack', dependencies : [mathlib], link_with: pipewire_jack, ) pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/pipewire-jack/src/metadata.c000066400000000000000000000227511511204443500257520ustar00rootroot00000000000000/* PipeWire */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include "config.h" #include #include #include #include #include #include #include #include static jack_description_t *find_description(jack_uuid_t subject) { jack_description_t *desc; pw_array_for_each(desc, &globals.descriptions) { if (jack_uuid_compare(desc->subject, subject) == 0) return desc; } return NULL; } static void set_property(jack_property_t *prop, const char *key, const char *value, const char *type) { prop->key = strdup(key); prop->data = strdup(value); prop->type = strdup(type); } static void clear_property(jack_property_t *prop) { free((char*)prop->key); free((char*)prop->data); free((char*)prop->type); } static jack_property_t *copy_properties(jack_property_t *src, uint32_t cnt) { jack_property_t *dst; uint32_t i; dst = malloc(sizeof(jack_property_t) * cnt); if (dst != NULL) { for (i = 0; i < cnt; i++) set_property(&dst[i], src[i].key, src[i].data, src[i].type); } return dst; } static int copy_description(jack_description_t *dst, jack_description_t *src) { dst->properties = copy_properties(src->properties, src->property_cnt); if (dst->properties == NULL) return -errno; jack_uuid_copy(&dst->subject, src->subject); dst->property_cnt = src->property_cnt; dst->property_size = src->property_size; return dst->property_cnt; } static jack_description_t *add_description(jack_uuid_t subject) { jack_description_t *desc; desc = pw_array_add(&globals.descriptions, sizeof(*desc)); if (desc != NULL) { spa_zero(*desc); jack_uuid_copy(&desc->subject, subject); } return desc; } static void remove_description(jack_description_t *desc) { jack_free_description(desc, false); pw_array_remove(&globals.descriptions, desc); } static jack_property_t *find_property(jack_description_t *desc, const char *key) { uint32_t i; for (i = 0; i < desc->property_cnt; i++) { jack_property_t *prop = &desc->properties[i]; if (spa_streq(prop->key, key)) return prop; } return NULL; } static jack_property_t *add_property(jack_description_t *desc, const char *key, const char *value, const char *type) { jack_property_t *prop; void *np; size_t ns; if (desc->property_cnt == desc->property_size) { ns = desc->property_size > 0 ? desc->property_size * 2 : 8; np = pw_reallocarray(desc->properties, ns, sizeof(*prop)); if (np == NULL) return NULL; desc->property_size = ns; desc->properties = np; } prop = &desc->properties[desc->property_cnt++]; set_property(prop, key, value, type); return prop; } static void remove_property(jack_description_t *desc, jack_property_t *prop) { clear_property(prop); desc->property_cnt--; memmove(desc->properties, SPA_PTROFF(prop, sizeof(*prop), void), SPA_PTRDIFF(SPA_PTROFF(desc->properties, sizeof(*prop) * desc->property_cnt, void), prop)); if (desc->property_cnt == 0) remove_description(desc); } static int change_property(jack_property_t *prop, const char *value, const char *type) { int changed = 0; if (!spa_streq(prop->data, value)) { free((char*)prop->data); prop->data = strdup(value); changed++; } if (!spa_streq(prop->type, type)) { free((char*)prop->type); prop->type = strdup(type); changed++; } return changed; } static int update_property(struct client *c, jack_uuid_t subject, const char* key, const char* type, const char* value) { jack_property_change_t change; jack_description_t *desc; int changed = 0; pthread_mutex_lock(&globals.lock); desc = find_description(subject); if (key == NULL) { if (desc != NULL) { remove_description(desc); change = PropertyDeleted; changed++; } } else { jack_property_t *prop; prop = desc ? find_property(desc, key) : NULL; if (value == NULL || type == NULL) { if (prop != NULL) { remove_property(desc, prop); change = PropertyDeleted; changed++; } } else if (prop == NULL) { if (desc == NULL) desc = add_description(subject); if (desc == NULL) { changed = -errno; pw_log_warn("add_description failed: %m"); } else if (add_property(desc, key, value, type) == NULL) { changed = -errno; pw_log_warn("add_property failed: %m"); } else { change = PropertyCreated; changed++; } } else { changed = change_property(prop, value, type); change = PropertyChanged; } } pthread_mutex_unlock(&globals.lock); if (c->property_callback && changed > 0) { pw_log_info("emit %"PRIu64" %s", (uint64_t)subject, key); c->property_callback(subject, key, change, c->property_arg); } return changed; } SPA_EXPORT int jack_set_property(jack_client_t*client, jack_uuid_t subject, const char* key, const char* value, const char* type) { struct client *c = (struct client *) client; struct object *o; uint32_t serial; int res = -1; spa_return_val_if_fail(c != NULL, -EINVAL); spa_return_val_if_fail(key != NULL, -EINVAL); spa_return_val_if_fail(value != NULL, -EINVAL); pw_thread_loop_lock(c->context.loop); if (c->metadata == NULL) goto done; if (subject & (1<<30)) goto done; serial = jack_uuid_to_index(subject); if ((o = find_by_serial(c, serial)) == NULL) goto done; if (type == NULL) type = ""; pw_log_info("set id:%u (%"PRIu64") '%s' to '%s@%s'", o->id, subject, key, value, type); if (update_property(c, subject, key, type, value)) pw_metadata_set_property(c->metadata->proxy, o->id, key, type, value); res = do_sync(c); done: pw_thread_loop_unlock(c->context.loop); return res; } SPA_EXPORT int jack_get_property(jack_uuid_t subject, const char* key, char** value, char** type) { jack_description_t *desc; jack_property_t *prop; int res = -1; pthread_mutex_lock(&globals.lock); desc = find_description(subject); if (desc == NULL) goto done; prop = find_property(desc, key); if (prop == NULL) goto done; *value = strdup(prop->data); *type = strdup(prop->type); res = 0; pw_log_debug("subject:%"PRIu64" key:'%s' value:'%s' type:'%s'", subject, key, *value, *type); done: pthread_mutex_unlock(&globals.lock); return res; } SPA_EXPORT void jack_free_description (jack_description_t* desc, int free_description_itself) { uint32_t n; for (n = 0; n < desc->property_cnt; ++n) clear_property(&desc->properties[n]); free(desc->properties); if (free_description_itself) free(desc); } SPA_EXPORT int jack_get_properties (jack_uuid_t subject, jack_description_t* desc) { jack_description_t *d; int res = -1; spa_return_val_if_fail(desc != NULL, -EINVAL); pthread_mutex_lock(&globals.lock); d = find_description(subject); if (d == NULL) goto done; res = copy_description(desc, d); done: pthread_mutex_unlock(&globals.lock); return res; } SPA_EXPORT int jack_get_all_properties (jack_description_t** result) { uint32_t i; jack_description_t *dst, *src; struct pw_array *descriptions; uint32_t len; pthread_mutex_lock(&globals.lock); descriptions = &globals.descriptions; len = pw_array_get_len(descriptions, jack_description_t); src = descriptions->data; dst = malloc(descriptions->size); for (i = 0; i < len; i++) copy_description(&dst[i], &src[i]); *result = dst; pthread_mutex_unlock(&globals.lock); return len; } SPA_EXPORT int jack_remove_property (jack_client_t* client, jack_uuid_t subject, const char* key) { struct client *c = (struct client *) client; struct object *o; uint32_t serial; int res = -1; spa_return_val_if_fail(c != NULL, -EINVAL); spa_return_val_if_fail(key != NULL, -EINVAL); pw_thread_loop_lock(c->context.loop); if (c->metadata == NULL) goto done; if (subject & (1<<30)) goto done; serial = jack_uuid_to_index(subject); if ((o = find_by_serial(c, serial)) == NULL) goto done; pw_log_info("remove id:%u (%"PRIu64") '%s'", o->id, subject, key); pw_metadata_set_property(c->metadata->proxy, o->id, key, NULL, NULL); res = do_sync(c); done: pw_thread_loop_unlock(c->context.loop); return res; } SPA_EXPORT int jack_remove_properties (jack_client_t* client, jack_uuid_t subject) { struct client *c = (struct client *) client; struct object *o; uint32_t serial; int res = -1; spa_return_val_if_fail(c != NULL, -EINVAL); pw_thread_loop_lock(c->context.loop); if (c->metadata == NULL) goto done; if (subject & (1<<30)) goto done; serial = jack_uuid_to_index(subject); if ((o = find_by_serial(c, serial)) == NULL) goto done; pw_log_info("remove id:%u (%"PRIu64")", o->id, subject); pw_metadata_set_property(c->metadata->proxy, o->id, NULL, NULL, NULL); res = do_sync(c); done: pw_thread_loop_unlock(c->context.loop); return res; } SPA_EXPORT int jack_remove_all_properties (jack_client_t* client) { int res; struct client *c = (struct client *) client; spa_return_val_if_fail(c != NULL, -EINVAL); pw_thread_loop_lock(c->context.loop); pw_metadata_clear(c->metadata->proxy); res = do_sync(c); pw_thread_loop_unlock(c->context.loop); return res; } SPA_EXPORT int jack_set_property_change_callback (jack_client_t* client, JackPropertyChangeCallback callback, void* arg) { struct client *c = (struct client *) client; spa_return_val_if_fail(c != NULL, -EINVAL); pw_thread_loop_lock(c->context.loop); c->property_callback = callback; c->property_arg = arg; pw_thread_loop_unlock(c->context.loop); return 0; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/pipewire-jack/src/net.c000066400000000000000000000065261511204443500247620ustar00rootroot00000000000000/* PipeWire */ /* SPDX-FileCopyrightText: Copyright © 2022 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include "config.h" #include #include #include #include #include SPA_EXPORT jack_net_slave_t* jack_net_slave_open(const char* ip, int port, const char* name, jack_slave_t* request, jack_master_t* result) { return NULL; } SPA_EXPORT int jack_net_slave_close(jack_net_slave_t* net) { return ENOTSUP; } SPA_EXPORT int jack_set_net_slave_process_callback(jack_net_slave_t * net, JackNetSlaveProcessCallback net_callback, void *arg) { return ENOTSUP; } SPA_EXPORT int jack_net_slave_activate(jack_net_slave_t* net) { return ENOTSUP; } SPA_EXPORT int jack_net_slave_deactivate(jack_net_slave_t* net) { return ENOTSUP; } SPA_EXPORT int jack_net_slave_is_active(jack_net_slave_t* net) { return false; } SPA_EXPORT int jack_set_net_slave_buffer_size_callback(jack_net_slave_t *net, JackNetSlaveBufferSizeCallback bufsize_callback, void *arg) { return ENOTSUP; } SPA_EXPORT int jack_set_net_slave_sample_rate_callback(jack_net_slave_t *net, JackNetSlaveSampleRateCallback samplerate_callback, void *arg) { return ENOTSUP; } SPA_EXPORT int jack_set_net_slave_shutdown_callback(jack_net_slave_t *net, JackNetSlaveShutdownCallback shutdown_callback, void *arg) { return ENOTSUP; } SPA_EXPORT int jack_set_net_slave_restart_callback(jack_net_slave_t *net, JackNetSlaveRestartCallback restart_callback, void *arg) { return ENOTSUP; } SPA_EXPORT int jack_set_net_slave_error_callback(jack_net_slave_t *net, JackNetSlaveErrorCallback error_callback, void *arg) { return ENOTSUP; } SPA_EXPORT jack_net_master_t* jack_net_master_open(const char* ip, int port, jack_master_t* request, jack_slave_t* result) { return NULL; } SPA_EXPORT int jack_net_master_close(jack_net_master_t* net) { return ENOTSUP; } SPA_EXPORT int jack_net_master_recv(jack_net_master_t* net, int audio_input, float** audio_input_buffer, int midi_input, void** midi_input_buffer) { return ENOTSUP; } SPA_EXPORT int jack_net_master_recv_slice(jack_net_master_t* net, int audio_input, float** audio_input_buffer, int midi_input, void** midi_input_buffer, int frames) { return ENOTSUP; } SPA_EXPORT int jack_net_master_send(jack_net_master_t* net, int audio_output, float** audio_output_buffer, int midi_output, void** midi_output_buffer) { return ENOTSUP; } SPA_EXPORT int jack_net_master_send_slice(jack_net_master_t* net, int audio_output, float** audio_output_buffer, int midi_output, void** midi_output_buffer, int frames) { return ENOTSUP; } SPA_EXPORT jack_adapter_t* jack_create_adapter(int input, int output, jack_nframes_t host_buffer_size, jack_nframes_t host_sample_rate, jack_nframes_t adapted_buffer_size, jack_nframes_t adapted_sample_rate) { return NULL; } SPA_EXPORT int jack_destroy_adapter(jack_adapter_t* adapter) { return ENOTSUP; } SPA_EXPORT void jack_flush_adapter(jack_adapter_t* adapter) { } SPA_EXPORT int jack_adapter_push_and_pull(jack_adapter_t* adapter, float** input, float** output, unsigned int frames) { return ENOTSUP; } SPA_EXPORT int jack_adapter_pull_and_push(jack_adapter_t* adapter, float** input, float** output, unsigned int frames) { return ENOTSUP; } pipewire-jack-extensions.h000066400000000000000000000017761511204443500310530ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/pipewire-jack/src/* PipeWire JACK extensions */ /* SPDX-FileCopyrightText: Copyright © 2020 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef PIPEWIRE_JACK_EXTENSIONS_H #define PIPEWIRE_JACK_EXTENSIONS_H #include #ifdef __cplusplus extern "C" { #endif /** 1.0 gamma, full range HDR 0.0 -> 1.0, pre-multiplied * alpha, BT.2020 primaries, progressive */ #define JACK_DEFAULT_VIDEO_TYPE "32 bit float RGBA video" typedef struct jack_image_size { uint32_t width; uint32_t height; uint32_t stride; uint32_t flags; } jack_image_size_t; int jack_get_video_image_size(jack_client_t *client, jack_image_size_t *size); int jack_set_sample_rate (jack_client_t *client, jack_nframes_t nframes); /* raw OSC message */ #define JACK_DEFAULT_OSC_TYPE "8 bit raw OSC" /* MIDI 2.0 UMP type. This contains raw UMP data, which can have MIDI 1.0 or * MIDI 2.0 packets. The data is an array of 32 bit ints. */ #define JACK_DEFAULT_UMP_TYPE "32 bit raw UMP" #ifdef __cplusplus } #endif #endif /* PIPEWIRE_JACK_EXTENSIONS_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/pipewire-jack/src/pipewire-jack.c000066400000000000000000006330301511204443500267220ustar00rootroot00000000000000/* PipeWire */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-FileCopyrightText: Copyright © 2024 Nedko Arnaudov */ /* SPDX-License-Identifier: MIT */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "pipewire/extensions/client-node.h" #include "pipewire/extensions/metadata.h" #include "pipewire-jack-extensions.h" /* use 512KB stack per thread - the default is way too high to be feasible * with mlockall() on many systems */ #define THREAD_STACK 524288 #define DEFAULT_RT_MAX RTPRIO_CLIENT #define JACK_CLIENT_NAME_SIZE 256 #define JACK_PORT_NAME_SIZE 256 #define JACK_PORT_TYPE_SIZE 32 #define MONITOR_EXT " Monitor" #define MAX_MIX 1024 #define MAX_CLIENT_PORTS 768 #define MAX_ALIGN 32 #define MAX_BUFFERS 2 #define MAX_BUFFER_DATAS 1u #define REAL_JACK_PORT_NAME_SIZE (JACK_CLIENT_NAME_SIZE + JACK_PORT_NAME_SIZE) PW_LOG_TOPIC_STATIC(jack_log_topic, "jack"); #define PW_LOG_TOPIC_DEFAULT jack_log_topic #define TYPE_ID_AUDIO 0 #define TYPE_ID_VIDEO 1 #define TYPE_ID_MIDI 2 #define TYPE_ID_OSC 3 #define TYPE_ID_UMP 4 #define TYPE_ID_OTHER 5 #define TYPE_ID_IS_EVENT(t) ((t) >= TYPE_ID_MIDI && (t) <= TYPE_ID_UMP) #define TYPE_ID_CAN_OSC(t) ((t) == TYPE_ID_MIDI || (t) == TYPE_ID_OSC) #define TYPE_ID_IS_HIDDEN(t) ((t) >= TYPE_ID_OTHER) #define TYPE_ID_IS_COMPATIBLE(a,b)(((a) == (b)) || (TYPE_ID_IS_EVENT(a) && TYPE_ID_IS_EVENT(b))) #define SELF_CONNECT_ALLOW 0 #define SELF_CONNECT_FAIL_EXT -1 #define SELF_CONNECT_IGNORE_EXT 1 #define SELF_CONNECT_FAIL_ALL -2 #define SELF_CONNECT_IGNORE_ALL 2 #define OTHER_CONNECT_ALLOW 1 #define OTHER_CONNECT_FAIL -1 #define OTHER_CONNECT_IGNORE 0 #define NOTIFY_BUFFER_SIZE (1u<<13) #define NOTIFY_BUFFER_MASK (NOTIFY_BUFFER_SIZE-1) struct notify { #define NOTIFY_ACTIVE_FLAG (1<<0) #define NOTIFY_TYPE_NONE ((0<<4)|NOTIFY_ACTIVE_FLAG) #define NOTIFY_TYPE_REGISTRATION ((1<<4)) #define NOTIFY_TYPE_PORTREGISTRATION ((2<<4)|NOTIFY_ACTIVE_FLAG) #define NOTIFY_TYPE_CONNECT ((3<<4)|NOTIFY_ACTIVE_FLAG) #define NOTIFY_TYPE_BUFFER_FRAMES ((4<<4)|NOTIFY_ACTIVE_FLAG) #define NOTIFY_TYPE_SAMPLE_RATE ((5<<4)|NOTIFY_ACTIVE_FLAG) #define NOTIFY_TYPE_FREEWHEEL ((6<<4)|NOTIFY_ACTIVE_FLAG) #define NOTIFY_TYPE_SHUTDOWN ((7<<4)|NOTIFY_ACTIVE_FLAG) #define NOTIFY_TYPE_LATENCY ((8<<4)|NOTIFY_ACTIVE_FLAG) #define NOTIFY_TYPE_TOTAL_LATENCY ((9<<4)|NOTIFY_ACTIVE_FLAG) #define NOTIFY_TYPE_PORT_RENAME ((10<<4)|NOTIFY_ACTIVE_FLAG) int type; struct object *object; int arg1; const char *msg; }; struct client; struct port; struct globals { jack_thread_creator_t creator; pthread_mutex_t lock; struct pw_array descriptions; struct spa_list free_objects; struct spa_thread_utils *thread_utils; uint32_t max_frames; }; static struct globals globals; static bool mlock_warned = false; #define MIDI_SCRATCH_FRAMES 8192 static thread_local float midi_scratch[MIDI_SCRATCH_FRAMES]; #define OBJECT_CHUNK 8 #define RECYCLE_THRESHOLD 128 typedef void (*mix_func) (float *dst, float *src[], uint32_t n_src, bool aligned, uint32_t n_samples); struct object { struct spa_list link; struct client *client; #define INTERFACE_Invalid 0 #define INTERFACE_Port 1 #define INTERFACE_Node 2 #define INTERFACE_Link 3 #define INTERFACE_Client 4 uint32_t type; uint32_t id; uint32_t serial; union { struct { char name[1024]; int32_t pid; } pwclient; struct { char name[JACK_CLIENT_NAME_SIZE+1]; char node_name[512]; int32_t priority; uint32_t client_id; unsigned is_jack:1; unsigned is_running:1; } node; struct { uint32_t src; uint32_t dst; uint32_t src_serial; uint32_t dst_serial; bool src_ours; bool dst_ours; struct port *our_input; struct port *our_output; } port_link; struct { unsigned long flags; char old_name[REAL_JACK_PORT_NAME_SIZE+1]; char name[REAL_JACK_PORT_NAME_SIZE+1]; char alias1[REAL_JACK_PORT_NAME_SIZE+1]; char alias2[REAL_JACK_PORT_NAME_SIZE+1]; char system[REAL_JACK_PORT_NAME_SIZE+1]; uint32_t system_id; uint32_t type_id; uint32_t node_id; uint32_t monitor_requests; int32_t priority; struct port *port; bool is_monitor; struct object *node; struct spa_latency_info latency[2]; } port; }; struct pw_proxy *proxy; struct spa_hook proxy_listener; struct spa_hook object_listener; int registered; unsigned int visible; unsigned int removing:1; unsigned int removed:1; unsigned int to_free:1; }; struct midi_buffer { #define MIDI_BUFFER_MAGIC 0x900df00d uint32_t magic; int32_t buffer_size; uint32_t nframes; int32_t write_pos; uint32_t event_count; uint32_t lost_events; }; #define MIDI_INLINE_MAX 4 struct midi_event { uint16_t time; uint16_t size; union { uint32_t byte_offset; uint8_t inline_data[MIDI_INLINE_MAX]; }; }; struct buffer { struct spa_list link; #define BUFFER_FLAG_OUT (1<<0) #define BUFFER_FLAG_MAPPED (1<<1) uint32_t flags; uint32_t id; struct spa_data datas[MAX_BUFFER_DATAS]; uint32_t n_datas; struct pw_memmap *mem[MAX_BUFFER_DATAS+1]; uint32_t n_mem; }; struct mix_info { struct spa_pod_parser parser; struct spa_pod_frame frame; struct spa_pod_control control; const void *control_body; }; struct mix { struct spa_list link; struct spa_list port_link; uint32_t id; uint32_t peer_id; struct port *port; struct port *peer_port; struct spa_io_buffers *io[2]; struct spa_list queue; struct buffer buffers[MAX_BUFFERS]; uint32_t n_buffers; struct mix_info mix_info; unsigned int to_free:1; }; struct port { bool valid; struct spa_list link; struct client *client; enum spa_direction direction; uint32_t port_id; struct object *object; struct pw_properties *props; struct spa_port_info info; #define IDX_EnumFormat 0 #define IDX_Buffers 1 #define IDX_IO 2 #define IDX_Format 3 #define IDX_Latency 4 #define N_PORT_PARAMS 5 struct spa_param_info params[N_PORT_PARAMS]; struct spa_io_buffers io[2]; struct spa_list mix; uint32_t n_mix; struct mix *global_mix; struct port *tied; unsigned int empty_out:1; unsigned int zeroed:1; unsigned int to_free:1; void *(*get_buffer) (struct port *p, jack_nframes_t frames); float *emptyptr; float empty[]; }; struct link { struct spa_list link; struct spa_list target_link; struct client *client; uint32_t node_id; struct pw_memmap *mem; struct pw_node_activation *activation; int signalfd; void (*trigger) (struct link *l, uint64_t nsec); }; struct context { struct pw_loop *l; struct pw_thread_loop *loop; /* thread_lock protects all below */ struct pw_context *context; struct pw_loop *nl; struct pw_thread_loop *notify; struct spa_thread_utils *old_thread_utils; struct spa_thread_utils thread_utils; pthread_mutex_t lock; /* protects map and lists below, in addition to thread_lock */ struct spa_list objects; uint32_t free_count; }; #define GET_DIRECTION(f) ((f) & JackPortIsInput ? SPA_DIRECTION_INPUT : SPA_DIRECTION_OUTPUT) #define GET_PORT(c,d,p) (pw_map_lookup(&c->ports[d], p)) struct metadata { struct pw_metadata *proxy; struct spa_hook proxy_listener; struct spa_hook listener; char default_audio_sink[1024]; char default_audio_source[1024]; }; struct frame_times { uint64_t frames; uint64_t nsec; uint64_t next_nsec; uint32_t buffer_frames; uint32_t sample_rate; double rate_diff; }; struct client { char name[JACK_CLIENT_NAME_SIZE+1]; struct context context; char *server_name; char *load_name; /* load module name */ char *load_init; /* initialization string */ jack_uuid_t session_id; /* requested session_id */ struct pw_loop *l; struct pw_data_loop *loop; struct pw_properties *props; struct pw_core *core; struct spa_hook core_listener; struct pw_mempool *pool; int pending_sync; int last_sync; int last_res; struct spa_node_info info; struct pw_registry *registry; struct spa_hook registry_listener; struct pw_client_node *node; struct spa_hook node_listener; struct spa_hook proxy_listener; struct metadata *metadata; struct metadata *settings; uint32_t node_id; uint32_t serial; struct object *object; struct spa_source *socket_source; struct spa_source *notify_source; void *notify_buffer; struct spa_ringbuffer notify_ring; JackThreadCallback thread_callback; void *thread_arg; JackThreadInitCallback thread_init_callback; void *thread_init_arg; JackShutdownCallback shutdown_callback; void *shutdown_arg; JackInfoShutdownCallback info_shutdown_callback; void *info_shutdown_arg; JackProcessCallback process_callback; void *process_arg; JackFreewheelCallback freewheel_callback; void *freewheel_arg; JackBufferSizeCallback bufsize_callback; void *bufsize_arg; JackSampleRateCallback srate_callback; void *srate_arg; JackClientRegistrationCallback registration_callback; void *registration_arg; JackPortRegistrationCallback portregistration_callback; void *portregistration_arg; JackPortConnectCallback connect_callback; void *connect_arg; JackPortRenameCallback rename_callback; void *rename_arg; JackGraphOrderCallback graph_callback; void *graph_arg; JackXRunCallback xrun_callback; void *xrun_arg; JackLatencyCallback latency_callback; void *latency_arg; JackSyncCallback sync_callback; void *sync_arg; JackTimebaseCallback timebase_callback; void *timebase_arg; JackPropertyChangeCallback property_callback; void *property_arg; struct spa_io_position *position; uint32_t sample_rate; uint32_t buffer_frames; struct spa_fraction latency; struct spa_list mix; struct spa_list free_mix; struct spa_list free_ports; struct pw_map ports[2]; uint32_t n_ports; struct spa_list links; uint32_t driver_id; struct pw_node_activation *driver_activation; struct pw_memmap *mem; struct pw_node_activation *activation; uint32_t xrun_count; struct { struct spa_io_position *position; struct pw_node_activation *driver_activation; struct spa_list target_links; unsigned int prepared:1; unsigned int first:1; unsigned int thread_entered:1; } rt; pthread_mutex_t rt_lock; unsigned int rt_locked:1; unsigned int data_locked:1; unsigned int started:1; unsigned int active:1; unsigned int destroyed:1; unsigned int has_transport:1; unsigned int allow_mlock:1; unsigned int warn_mlock:1; unsigned int timeowner_conditional:1; unsigned int show_monitor:1; unsigned int show_midi:1; unsigned int merge_monitor:1; unsigned int short_name:1; unsigned int filter_name:1; unsigned int freewheeling:1; unsigned int locked_process:1; unsigned int default_as_system:1; int self_connect_mode; int other_connect_mode; int rt_max; unsigned int fix_midi_events:1; unsigned int global_buffer_size:1; unsigned int global_sample_rate:1; unsigned int passive_links:1; unsigned int pending_callbacks:1; int frozen_callbacks; char filter_char; uint32_t max_ports; unsigned int fill_aliases:1; unsigned int writable_input:1; unsigned int async:1; unsigned int flag_midi2:1; uint32_t max_frames; uint32_t max_align; mix_func mix_function; jack_position_t jack_position; jack_transport_state_t jack_state; struct frame_times jack_times; }; #define return_val_if_fail(expr, val) \ ({ \ if (SPA_UNLIKELY(!(expr))) { \ pw_log_warn("'%s' failed at %s:%u %s()", \ #expr , __FILE__, __LINE__, __func__); \ return (val); \ } \ }) #define return_if_fail(expr) \ ({ \ if (SPA_UNLIKELY(!(expr))) { \ pw_log_warn("'%s' failed at %s:%u %s()", \ #expr , __FILE__, __LINE__, __func__); \ return; \ } \ }) static int do_sync(struct client *client); static struct object *find_by_serial(struct client *c, uint32_t serial); #include "metadata.c" int pw_jack_match_rules(const char *rules, size_t size, const struct spa_dict *props, int (*matched) (void *data, const char *action, const char *val, int len), void *data); static struct object * alloc_object(struct client *c, int type) { struct object *o; int i; pthread_mutex_lock(&globals.lock); if (spa_list_is_empty(&globals.free_objects)) { o = calloc(OBJECT_CHUNK, sizeof(struct object)); if (o == NULL) { pthread_mutex_unlock(&globals.lock); return NULL; } o[0].to_free = true; for (i = 0; i < OBJECT_CHUNK; i++) spa_list_append(&globals.free_objects, &o[i].link); } o = spa_list_first(&globals.free_objects, struct object, link); spa_list_remove(&o->link); pthread_mutex_unlock(&globals.lock); o->client = c; o->removed = false; o->type = type; pw_log_debug("%p: object:%p type:%d", c, o, type); return o; } static void recycle_objects(struct client *c, uint32_t remain) { struct object *o, *t; pthread_mutex_lock(&globals.lock); spa_list_for_each_safe(o, t, &c->context.objects, link) { pw_log_debug("%p: recycle object:%p remived:%d type:%d id:%u/%u %u/%u", c, o, o->removed, o->type, o->id, o->serial, c->context.free_count, remain); if (o->removed) { spa_list_remove(&o->link); memset(o, 0, sizeof(struct object)); spa_list_append(&globals.free_objects, &o->link); if (--c->context.free_count == remain) break; } } pthread_mutex_unlock(&globals.lock); } /* JACK clients expect the objects to hang around after * they are unregistered and freed. We mark the object removed and * move it to the end of the queue. */ static void free_object(struct client *c, struct object *o) { pw_log_debug("%p: object:%p type:%d %u/%u", c, o, o->type, c->context.free_count, RECYCLE_THRESHOLD); pthread_mutex_lock(&c->context.lock); spa_list_remove(&o->link); o->removed = true; o->id = SPA_ID_INVALID; spa_list_append(&c->context.objects, &o->link); if (++c->context.free_count >= RECYCLE_THRESHOLD) recycle_objects(c, RECYCLE_THRESHOLD / 2); pthread_mutex_unlock(&c->context.lock); } static inline struct object *port_to_object(const jack_port_t *port) { return (struct object*)port; } static inline jack_port_t *object_to_port(struct object *o) { return (jack_port_t*)o; } struct io_info { struct mix *mix; void *data; size_t size; }; static int do_mix_set_io(struct spa_loop *loop, bool async, uint32_t seq, const void *data, size_t size, void *user_data) { const struct io_info *info = data; struct port *port = info->mix->port; if (info->data) { if (info->size >= sizeof(struct spa_io_async_buffers)) { struct spa_io_async_buffers *ab = info->data; info->mix->io[0] = &ab->buffers[port->direction]; info->mix->io[1] = &ab->buffers[port->direction^1]; } else if (info->size >= sizeof(struct spa_io_buffers)) { info->mix->io[0] = info->data; info->mix->io[1] = info->data; } else { info->mix->io[0] = NULL; info->mix->io[1] = NULL; } if (port->n_mix++ == 0 && port->global_mix != NULL) { port->global_mix->io[0] = &port->io[0]; port->global_mix->io[1] = &port->io[1]; } } else { info->mix->io[0] = NULL; info->mix->io[1] = NULL; if (port->n_mix > 0 && --port->n_mix == 0 && port->global_mix != NULL) { port->global_mix->io[0] = NULL; port->global_mix->io[1] = NULL; } } return 0; } static inline void mix_set_io(struct mix *mix, void *data, size_t size) { struct io_info info = { .mix = mix, .data = data, .size = size }; pw_loop_locked(mix->port->client->loop->loop, do_mix_set_io, SPA_ID_INVALID, &info, sizeof(info), NULL); } static void init_mix(struct mix *mix, uint32_t mix_id, struct port *port, uint32_t peer_id) { pw_log_debug("create %p mix:%d peer:%d", port, mix_id, peer_id); mix->id = mix_id; mix->peer_id = peer_id; mix->port = port; mix->peer_port = NULL; mix->io[0] = mix->io[1] = NULL; mix->n_buffers = 0; spa_list_init(&mix->queue); if (mix_id == SPA_ID_INVALID) { port->global_mix = mix; if (port->n_mix > 0) mix_set_io(port->global_mix, &port->io, sizeof(port->io)); } } static struct mix *find_mix_peer(struct client *c, uint32_t peer_id) { struct mix *mix; spa_list_for_each(mix, &c->mix, link) { if (mix->peer_id == peer_id) return mix; } return NULL; } static struct mix *find_port_peer(struct port *port, uint32_t peer_id) { struct mix *mix; spa_list_for_each(mix, &port->mix, port_link) { pw_log_trace("%p %d %d", port, mix->peer_id, peer_id); if (mix->peer_id == peer_id) return mix; } return NULL; } static struct mix *find_mix(struct client *c, struct port *port, uint32_t mix_id) { struct mix *mix; spa_list_for_each(mix, &port->mix, port_link) { if (mix->id == mix_id) return mix; } return NULL; } static struct mix *create_mix(struct client *c, struct port *port, uint32_t mix_id, uint32_t peer_id) { struct mix *mix; uint32_t i; if (spa_list_is_empty(&c->free_mix)) { mix = calloc(OBJECT_CHUNK, sizeof(struct mix)); if (mix == NULL) return NULL; mix[0].to_free = true; for (i = 0; i < OBJECT_CHUNK; i++) spa_list_append(&c->free_mix, &mix[i].link); } mix = spa_list_first(&c->free_mix, struct mix, link); spa_list_remove(&mix->link); spa_list_append(&c->mix, &mix->link); spa_list_append(&port->mix, &mix->port_link); init_mix(mix, mix_id, port, peer_id); return mix; } static int clear_buffers(struct client *c, struct mix *mix) { struct port *port = mix->port; struct buffer *b; uint32_t i, j; pw_log_debug("%p: port %p clear buffers", c, port); for (i = 0; i < mix->n_buffers; i++) { b = &mix->buffers[i]; for (j = 0; j < b->n_mem; j++) pw_memmap_free(b->mem[j]); b->n_mem = 0; } mix->n_buffers = 0; spa_list_init(&mix->queue); return 0; } static void free_mix(struct client *c, struct mix *mix) { struct port *port = mix->port; clear_buffers(c, mix); spa_list_remove(&mix->port_link); if (mix->id == SPA_ID_INVALID) port->global_mix = NULL; spa_list_remove(&mix->link); spa_list_append(&c->free_mix, &mix->link); } static struct port * alloc_port(struct client *c, enum spa_direction direction) { struct port *p; struct object *o; uint32_t i, port_size; if (c->n_ports >= c->max_ports) { errno = ENOSPC; return NULL; } if (spa_list_is_empty(&c->free_ports)) { port_size = sizeof(struct port) + (c->max_frames * sizeof(float)) + c->max_align; p = calloc(OBJECT_CHUNK, port_size); if (p == NULL) return NULL; p[0].to_free = true; for (i = 0; i < OBJECT_CHUNK; i++) { struct port *t = SPA_PTROFF(p, port_size * i, struct port); spa_list_append(&c->free_ports, &t->link); } } p = spa_list_first(&c->free_ports, struct port, link); spa_list_remove(&p->link); o = alloc_object(c, INTERFACE_Port); if (o == NULL) return NULL; o->id = SPA_ID_INVALID; o->port.node_id = c->node_id; o->port.port = p; o->port.latency[SPA_DIRECTION_INPUT] = SPA_LATENCY_INFO(SPA_DIRECTION_INPUT); o->port.latency[SPA_DIRECTION_OUTPUT] = SPA_LATENCY_INFO(SPA_DIRECTION_OUTPUT); p->valid = true; p->zeroed = false; p->client = c; p->object = o; spa_list_init(&p->mix); p->props = pw_properties_new(NULL, NULL); p->direction = direction; p->emptyptr = SPA_PTR_ALIGN(p->empty, c->max_align, float); p->port_id = pw_map_insert_new(&c->ports[direction], p); c->n_ports++; pthread_mutex_lock(&c->context.lock); spa_list_append(&c->context.objects, &o->link); pthread_mutex_unlock(&c->context.lock); return p; } static void free_port(struct client *c, struct port *p, bool free) { struct mix *m; spa_list_consume(m, &p->mix, port_link) free_mix(c, m); c->n_ports--; pw_map_remove(&c->ports[p->direction], p->port_id); pw_properties_free(p->props); spa_list_append(&c->free_ports, &p->link); if (free) free_object(c, p->object); else p->object->removing = true; } static struct object *find_node(struct client *c, const char *name) { struct object *o; spa_list_for_each(o, &c->context.objects, link) { if (o->removing || o->removed || o->type != INTERFACE_Node) continue; if (spa_streq(o->node.name, name)) return o; } return NULL; } static bool is_port_default(struct client *c, struct object *o) { struct object *ot; if (c->metadata == NULL) return false; if ((ot = o->port.node) != NULL && (spa_streq(ot->node.node_name, c->metadata->default_audio_source) || spa_streq(ot->node.node_name, c->metadata->default_audio_sink))) return true; return false; } static inline bool client_port_visible(struct client *c, struct object *o) { if (o->port.port != NULL && o->port.port->client == c) return true; return o->visible; } static struct object *find_port_by_name(struct client *c, const char *name) { struct object *o; spa_list_for_each(o, &c->context.objects, link) { if (o->type != INTERFACE_Port || o->removed || (!client_port_visible(c, o))) continue; if (spa_streq(o->port.name, name) || spa_streq(o->port.alias1, name) || spa_streq(o->port.alias2, name)) return o; if (is_port_default(c, o) && spa_streq(o->port.system, name)) return o; } return NULL; } static struct object *find_by_id(struct client *c, uint32_t id) { struct object *o; spa_list_for_each(o, &c->context.objects, link) { if (o->id == id) return o; } return NULL; } static struct object *find_by_serial(struct client *c, uint32_t serial) { struct object *o; spa_list_for_each(o, &c->context.objects, link) { if (o->serial == serial) return o; } return NULL; } static struct object *find_id(struct client *c, uint32_t id, bool valid) { struct object *o = find_by_id(c, id); if (o != NULL && (!valid || o->client == c)) return o; return NULL; } static struct object *find_type(struct client *c, uint32_t id, uint32_t type, bool valid) { struct object *o = find_id(c, id, valid); if (o != NULL && o->type == type) return o; return NULL; } static struct object *find_client(struct client *c, uint32_t client_id) { return find_type(c, client_id, INTERFACE_Client, false); } static struct object *find_link(struct client *c, uint32_t src, uint32_t dst) { struct object *l; spa_list_for_each(l, &c->context.objects, link) { if (l->type != INTERFACE_Link || l->removed) continue; if (l->port_link.src == src && l->port_link.dst == dst) { return l; } } return NULL; } #if defined (__SSE__) #include static void mix_sse(float *dst, float *src[], uint32_t n_src, bool aligned, uint32_t n_samples) { uint32_t i, n, unrolled; __m128 in[1]; if (SPA_IS_ALIGNED(dst, 16) && aligned) unrolled = n_samples & ~3; else unrolled = 0; for (n = 0; n < unrolled; n += 4) { in[0] = _mm_load_ps(&src[0][n]); for (i = 1; i < n_src; i++) in[0] = _mm_add_ps(in[0], _mm_load_ps(&src[i][n])); _mm_store_ps(&dst[n], in[0]); } for (; n < n_samples; n++) { in[0] = _mm_load_ss(&src[0][n]); for (i = 1; i < n_src; i++) in[0] = _mm_add_ss(in[0], _mm_load_ss(&src[i][n])); _mm_store_ss(&dst[n], in[0]); } } #endif static void mix_c(float *dst, float *src[], uint32_t n_src, bool aligned, uint32_t n_samples) { uint32_t n, i; for (n = 0; n < n_samples; n++) { float t = src[0][n]; for (i = 1; i < n_src; i++) t += src[i][n]; dst[n] = t; } } SPA_EXPORT void jack_get_version(int *major_ptr, int *minor_ptr, int *micro_ptr, int *proto_ptr) { if (major_ptr) *major_ptr = 3; if (minor_ptr) *minor_ptr = PW_MAJOR; if (micro_ptr) *micro_ptr = PW_MINOR; if (proto_ptr) *proto_ptr = PW_MICRO; } #define do_callback_expr(c,expr,callback,do_emit,...) \ ({ \ if (c->callback && do_emit) { \ pw_thread_loop_unlock(c->context.loop); \ if (c->locked_process) \ pthread_mutex_lock(&c->rt_lock); \ (expr); \ pw_log_debug("emit " #callback); \ c->callback(__VA_ARGS__); \ if (c->locked_process) \ pthread_mutex_unlock(&c->rt_lock); \ pw_thread_loop_lock(c->context.loop); \ } else { \ (expr); \ pw_log_debug("skip " #callback \ " cb:%p do_emit:%d", c->callback, \ do_emit); \ } \ }) #define do_callback(c,callback,do_emit,...) do_callback_expr(c,(void)0,callback,do_emit,__VA_ARGS__) #define do_rt_callback_res(c,callback,...) \ ({ \ int res = 0; \ if (c->callback) { \ if (pthread_mutex_trylock(&c->rt_lock) == 0) { \ c->rt_locked = true; \ res = c->callback(__VA_ARGS__); \ c->rt_locked = false; \ pthread_mutex_unlock(&c->rt_lock); \ } else { \ pw_log_debug("skip " #callback \ " cb:%p", c->callback); \ } \ } \ res; \ }) SPA_EXPORT const char * jack_get_version_string(void) { static char name[1024]; int major, minor, micro, proto; jack_get_version(&major, &minor, µ, &proto); snprintf(name, sizeof(name), "%d.%d.%d.%d (using PipeWire %s)", major, minor, micro, proto, pw_get_library_version()); return name; } #define freeze_callbacks(c) \ ({ \ (c)->frozen_callbacks++; \ }) #define check_callbacks(c) \ ({ \ if ((c)->frozen_callbacks == 0 && (c)->pending_callbacks) \ pw_loop_signal_event((c)->context.nl, (c)->notify_source); \ }) #define thaw_callbacks(c) \ ({ \ (c)->frozen_callbacks--; \ check_callbacks(c); \ }) static void on_notify_event(void *data, uint64_t count) { struct client *c = data; struct object *o; int32_t avail; uint32_t index; struct notify *notify; bool do_graph = false, do_recompute_capture = false, do_recompute_playback = false; pw_thread_loop_lock(c->context.loop); if (c->frozen_callbacks != 0 || !c->pending_callbacks) goto done; pw_log_debug("%p: enter active:%u", c, c->active); c->pending_callbacks = false; freeze_callbacks(c); avail = spa_ringbuffer_get_read_index(&c->notify_ring, &index); while (avail > 0) { notify = SPA_PTROFF(c->notify_buffer, index & NOTIFY_BUFFER_MASK, struct notify); o = notify->object; pw_log_debug("%p: dequeue notify index:%08x %p type:%d %p arg1:%d", c, index, notify, notify->type, o, notify->arg1); switch (notify->type) { case NOTIFY_TYPE_REGISTRATION: if (o->registered == notify->arg1) break; pw_log_debug("%p: node %u %s %u", c, o->serial, o->node.name, notify->arg1); do_callback(c, registration_callback, true, o->node.name, notify->arg1, c->registration_arg); break; case NOTIFY_TYPE_PORTREGISTRATION: if (o->registered == notify->arg1) break; pw_log_debug("%p: port %u %s %u", c, o->serial, o->port.name, notify->arg1); do_callback(c, portregistration_callback, c->active, o->serial, notify->arg1, c->portregistration_arg); break; case NOTIFY_TYPE_PORT_RENAME: if (o->registered != notify->arg1) break; pw_log_debug("%p: port rename %u %s->%s", c, o->serial, o->port.old_name, o->port.name); do_callback(c, rename_callback, c->active, o->serial, o->port.old_name, o->port.name, c->rename_arg); break; case NOTIFY_TYPE_CONNECT: if (o->registered == notify->arg1) break; pw_log_debug("%p: link %u %u -> %u %u", c, o->serial, o->port_link.src_serial, o->port_link.dst, notify->arg1); do_callback(c, connect_callback, c->active, o->port_link.src_serial, o->port_link.dst_serial, notify->arg1, c->connect_arg); do_graph = true; do_recompute_capture = do_recompute_playback = true; break; case NOTIFY_TYPE_BUFFER_FRAMES: pw_log_debug("%p: buffer frames %d -> %d", c, c->buffer_frames, notify->arg1); if (c->buffer_frames != (uint32_t)notify->arg1) { do_callback_expr(c, c->buffer_frames = notify->arg1, bufsize_callback, c->active, notify->arg1, c->bufsize_arg); do_recompute_capture = do_recompute_playback = true; } break; case NOTIFY_TYPE_SAMPLE_RATE: pw_log_debug("%p: sample rate %d -> %d", c, c->sample_rate, notify->arg1); if (c->sample_rate != (uint32_t)notify->arg1) { do_callback_expr(c, c->sample_rate = notify->arg1, srate_callback, c->active, notify->arg1, c->srate_arg); } break; case NOTIFY_TYPE_FREEWHEEL: pw_log_debug("%p: freewheel %d", c, notify->arg1); do_callback(c, freewheel_callback, c->active, notify->arg1, c->freewheel_arg); break; case NOTIFY_TYPE_SHUTDOWN: pw_log_debug("%p: shutdown %d %s", c, notify->arg1, notify->msg); if (c->info_shutdown_callback) do_callback(c, info_shutdown_callback, c->active, notify->arg1, notify->msg, c->info_shutdown_arg); else do_callback(c, shutdown_callback, c->active, c->shutdown_arg); break; case NOTIFY_TYPE_LATENCY: pw_log_debug("%p: latency %d", c, notify->arg1); if (notify->arg1 == JackCaptureLatency) do_recompute_capture = true; else if (notify->arg1 == JackPlaybackLatency) do_recompute_playback = true; break; case NOTIFY_TYPE_TOTAL_LATENCY: pw_log_debug("%p: total latency", c); do_recompute_capture = do_recompute_playback = true; break; default: break; } if (o != NULL) { o->registered = notify->arg1; if (notify->arg1 == 0 && o->removing) { o->removing = false; free_object(c, o); } } avail -= sizeof(struct notify); index += sizeof(struct notify); spa_ringbuffer_read_update(&c->notify_ring, index); } if (do_recompute_capture) do_callback(c, latency_callback, c->active, JackCaptureLatency, c->latency_arg); if (do_recompute_playback) do_callback(c, latency_callback, c->active, JackPlaybackLatency, c->latency_arg); if (do_graph) do_callback(c, graph_callback, c->active, c->graph_arg); thaw_callbacks(c); done: pw_log_debug("%p: leave", c); pw_thread_loop_unlock(c->context.loop); } static int queue_notify(struct client *c, int type, struct object *o, int arg1, const char *msg) { int32_t filled; uint32_t index; struct notify *notify; bool emit = false; int res = 0; switch (type) { case NOTIFY_TYPE_REGISTRATION: emit = c->registration_callback != NULL && o != NULL; break; case NOTIFY_TYPE_PORTREGISTRATION: emit = c->portregistration_callback != NULL && o != NULL; o->visible = arg1; break; case NOTIFY_TYPE_PORT_RENAME: emit = c->rename_callback != NULL && o != NULL; break; case NOTIFY_TYPE_CONNECT: emit = c->connect_callback != NULL && o != NULL; break; case NOTIFY_TYPE_BUFFER_FRAMES: emit = c->bufsize_callback != NULL; break; case NOTIFY_TYPE_SAMPLE_RATE: emit = c->srate_callback != NULL; break; case NOTIFY_TYPE_FREEWHEEL: emit = c->freewheel_callback != NULL; break; case NOTIFY_TYPE_SHUTDOWN: emit = c->info_shutdown_callback != NULL || c->shutdown_callback != NULL; break; case NOTIFY_TYPE_LATENCY: case NOTIFY_TYPE_TOTAL_LATENCY: emit = c->latency_callback != NULL; break; default: break; } if (!emit || ((type & NOTIFY_ACTIVE_FLAG) && !c->active)) { switch (type) { case NOTIFY_TYPE_BUFFER_FRAMES: if (!emit) { c->buffer_frames = arg1; queue_notify(c, NOTIFY_TYPE_TOTAL_LATENCY, NULL, 0, NULL); } break; case NOTIFY_TYPE_SAMPLE_RATE: if (!emit) c->sample_rate = arg1; break; } pw_log_debug("%p: skip notify %08x active:%d emit:%d", c, type, c->active, emit); if (o != NULL) { o->registered = arg1; if (arg1 == 0 && o->removing) { o->removing = false; free_object(c, o); } } return res; } pthread_mutex_lock(&c->context.lock); filled = spa_ringbuffer_get_write_index(&c->notify_ring, &index); if (filled < 0 || filled + sizeof(struct notify) > NOTIFY_BUFFER_SIZE) { pw_log_warn("%p: notify queue full %d", c, type); res = -ENOSPC; goto done; } notify = SPA_PTROFF(c->notify_buffer, index & NOTIFY_BUFFER_MASK, struct notify); notify->type = type; notify->object = o; notify->arg1 = arg1; notify->msg = msg; pw_log_debug("%p: queue notify index:%08x %p type:%d %p arg1:%d msg:%s", c, index, notify, notify->type, o, notify->arg1, notify->msg); index += sizeof(struct notify); spa_ringbuffer_write_update(&c->notify_ring, index); c->pending_callbacks = true; check_callbacks(c); done: pthread_mutex_unlock(&c->context.lock); return res; } static void on_sync_reply(void *data, uint32_t id, int seq) { struct client *client = data; if (id != PW_ID_CORE) return; client->last_sync = seq; if (client->pending_sync == seq) pw_thread_loop_signal(client->context.loop, false); } static void on_error(void *data, uint32_t id, int seq, int res, const char *message) { struct client *client = data; pw_log_warn("%p: error id:%u seq:%d res:%d (%s): %s", client, id, seq, res, spa_strerror(res), message); if (id == PW_ID_CORE) { /* This happens when we did something on a proxy that * was destroyed on the server already */ if (res == -ENOENT) return; client->last_res = res; if (res == -EPIPE && !client->destroyed) { queue_notify(client, NOTIFY_TYPE_SHUTDOWN, NULL, JackFailure | JackServerError, "JACK server has been closed"); } } pw_thread_loop_signal(client->context.loop, false); } static const struct pw_core_events core_events = { PW_VERSION_CORE_EVENTS, .done = on_sync_reply, .error = on_error, }; static int do_sync(struct client *client) { bool in_data_thread = pw_data_loop_in_thread(client->loop); if (pw_thread_loop_in_thread(client->context.loop)) { pw_log_warn("sync requested from callback"); return 0; } if (client->last_res == -EPIPE) return -EPIPE; client->last_res = 0; client->pending_sync = pw_proxy_sync((struct pw_proxy*)client->core, client->pending_sync); if (client->pending_sync < 0) return client->pending_sync; while (true) { if (in_data_thread) { if (client->rt_locked) pthread_mutex_unlock(&client->rt_lock); client->data_locked = true; } pw_thread_loop_wait(client->context.loop); if (in_data_thread) { client->data_locked = false; if (client->rt_locked) pthread_mutex_lock(&client->rt_lock); } if (client->last_res < 0) return client->last_res; if (client->pending_sync == client->last_sync) break; } return 0; } static void on_node_removed(void *data) { struct client *client = data; pw_proxy_destroy((struct pw_proxy*)client->node); } static void on_node_destroy(void *data) { struct client *client = data; client->node = NULL; spa_hook_remove(&client->proxy_listener); spa_hook_remove(&client->node_listener); } static void on_node_bound_props(void *data, uint32_t global_id, const struct spa_dict *props) { struct client *client = data; client->node_id = global_id; if (props) pw_properties_update(client->props, props); } static const struct pw_proxy_events node_proxy_events = { PW_VERSION_PROXY_EVENTS, .removed = on_node_removed, .destroy = on_node_destroy, .bound_props = on_node_bound_props, }; static struct link *find_activation(struct spa_list *links, uint32_t node_id) { struct link *l; spa_list_for_each(l, links, link) { if (l->node_id == node_id) return l; } return NULL; } static void client_remove_source(struct client *c) { if (c->socket_source) { pw_loop_destroy_source(c->l, c->socket_source); c->socket_source = NULL; } } static inline void queue_buffer(struct client *c, struct mix *mix, uint32_t id) { struct buffer *b; b = &mix->buffers[id]; if (SPA_FLAG_IS_SET(b->flags, BUFFER_FLAG_OUT)) { pw_log_trace_fp("%p: port %p: recycle buffer %d", c, mix->port, id); spa_list_append(&mix->queue, &b->link); SPA_FLAG_CLEAR(b->flags, BUFFER_FLAG_OUT); } } static inline struct buffer *dequeue_buffer(struct client *c, struct mix *mix) { struct buffer *b; if (SPA_UNLIKELY(spa_list_is_empty(&mix->queue))) return NULL; b = spa_list_first(&mix->queue, struct buffer, link); spa_list_remove(&b->link); SPA_FLAG_SET(b->flags, BUFFER_FLAG_OUT); pw_log_trace_fp("%p: port %p: dequeue buffer %d", c, mix->port, b->id); return b; } static inline bool is_osc(jack_midi_event_t *ev) { return ev->size >= 1 && (ev->buffer[0] == '#' || ev->buffer[0] == '/'); } static size_t convert_from_event(void *midi, void *buffer, size_t size, uint32_t type) { struct spa_pod_builder b = { 0, }; uint32_t i, count; struct spa_pod_frame f; uint32_t event_type; switch (type) { case TYPE_ID_MIDI: case TYPE_ID_OSC: /* we handle MIDI as OSC, check below */ event_type = SPA_CONTROL_OSC; break; case TYPE_ID_UMP: event_type = SPA_CONTROL_UMP; break; default: return 0; } count = jack_midi_get_event_count(midi); spa_pod_builder_init(&b, buffer, size); spa_pod_builder_push_sequence(&b, &f, 0); for (i = 0; i < count; i++) { jack_midi_event_t ev; jack_midi_event_get(&ev, midi, i); if (type != TYPE_ID_MIDI || is_osc(&ev)) { /* no midi port or it's OSC */ spa_pod_builder_control(&b, ev.time, event_type); spa_pod_builder_bytes(&b, ev.buffer, ev.size); } else { /* midi port and it's not OSC, convert to UMP */ uint8_t *data = ev.buffer; size_t size = ev.size; uint64_t state = 0; while (size > 0) { uint32_t ump[4]; int ump_size = spa_ump_from_midi(&data, &size, ump, sizeof(ump), 0, &state); if (ump_size <= 0) break; spa_pod_builder_control(&b, ev.time, SPA_CONTROL_UMP); spa_pod_builder_bytes(&b, ump, ump_size); } } } spa_pod_builder_pop(&b, &f); return b.state.offset; } static inline int event_compare(uint8_t s1, uint8_t s2) { /* 11 (controller) > 12 (program change) > * 8 (note off) > 9 (note on) > 10 (aftertouch) > * 13 (channel pressure) > 14 (pitch bend) */ static int priotab[] = { 5,4,3,7,6,2,1,0 }; if ((s1 & 0xf) != (s2 & 0xf)) return 0; return priotab[(s2>>4) & 7] - priotab[(s1>>4) & 7]; } static inline int event_sort(struct spa_pod_control *a, const void *abody, struct spa_pod_control *b, const void *bbody) { if (a->offset < b->offset) return -1; if (a->offset > b->offset) return 1; if (a->type != b->type) return 0; switch(a->type) { case SPA_CONTROL_Midi: { const uint8_t *sa = abody, *sb = bbody; if (SPA_POD_BODY_SIZE(&a->value) < 1 || SPA_POD_BODY_SIZE(&b->value) < 1) return 0; return event_compare(sa[0], sb[0]); } case SPA_CONTROL_UMP: { const uint32_t *sa = abody, *sb = bbody; if (SPA_POD_BODY_SIZE(&a->value) < 4 || SPA_POD_BODY_SIZE(&b->value) < 4) return 0; if (((sa[0] >> 28) != 2 && (sa[0] >> 28) != 4) || ((sb[0] >> 28) != 2 && (sb[0] >> 28) != 4)) return 0; return event_compare(sa[0] >> 16, sb[0] >> 16); } default: return 0; } } static inline void fix_midi_event(uint8_t *data, size_t size) { /* fixup NoteOn with vel 0 */ if (size > 2 && (data[0] & 0xF0) == 0x90 && data[2] == 0x00) { data[0] = 0x80 + (data[0] & 0x0F); data[2] = 0x40; } } static inline jack_midi_data_t* midi_event_reserve(void *port_buffer, jack_nframes_t time, size_t data_size) { struct midi_buffer *mb = port_buffer; uint8_t *res = NULL; /* Check if data_size is >0 and there is enough space in the buffer for the event. */ if (SPA_UNLIKELY(data_size <= 0)) { pw_log_warn("midi %p: data_size:%zd", port_buffer, data_size); } else if (SPA_UNLIKELY(jack_midi_max_event_size (port_buffer) < data_size)) { pw_log_warn("midi %p: event too large: data_size:%zd", port_buffer, data_size); } else { struct midi_event *events = SPA_PTROFF(mb, sizeof(*mb), struct midi_event); struct midi_event *ev = &events[mb->event_count]; ev->time = time; ev->size = data_size; if (SPA_LIKELY(data_size <= MIDI_INLINE_MAX)) { res = ev->inline_data; } else { mb->write_pos += data_size; ev->byte_offset = mb->buffer_size - mb->write_pos; res = SPA_PTROFF(mb, ev->byte_offset, uint8_t); } mb->event_count += 1; } return res; } static inline int midi_event_append(void *port_buffer, const jack_midi_data_t *data, size_t data_size) { struct midi_buffer *mb = port_buffer; struct midi_event *events = SPA_PTROFF(mb, sizeof(*mb), struct midi_event); struct midi_event *ev; size_t old_size; uint8_t *old, *buf; ev = &events[--mb->event_count]; mb->write_pos -= ev->size; old_size = ev->size; if (old_size <= MIDI_INLINE_MAX) old = ev->inline_data; else old = SPA_PTROFF(mb, ev->byte_offset, uint8_t); buf = midi_event_reserve(port_buffer, ev->time, old_size + data_size); if (SPA_UNLIKELY(buf == NULL)) return -ENOBUFS; memmove(buf, old, old_size); memcpy(buf+old_size, data, data_size); return 0; } static inline int midi_event_write(void *port_buffer, jack_nframes_t time, const jack_midi_data_t *data, size_t data_size, bool fix) { jack_midi_data_t *retbuf = midi_event_reserve (port_buffer, time, data_size); if (SPA_UNLIKELY(retbuf == NULL)) return -ENOBUFS; memcpy (retbuf, data, data_size); if (fix) fix_midi_event(retbuf, data_size); return 0; } static void convert_to_event(struct mix_info **mix, uint32_t n_mix, void *midi, bool fix, uint32_t type) { uint32_t i; int res = 0; bool in_sysex = false; while (true) { struct mix_info *next = NULL; uint32_t next_index = 0; struct spa_pod_control *control; size_t size; uint8_t *data; uint64_t state = 0; for (i = 0; i < n_mix; i++) { struct mix_info *m = mix[i]; if (next == NULL || event_sort(&m->control, m->control_body, &next->control, next->control_body) <= 0) { next = m; next_index = i; } } if (SPA_UNLIKELY(next == NULL)) break; control = &next->control; data = (uint8_t*)next->control_body; size = SPA_POD_BODY_SIZE(&control->value); switch(control->type) { case SPA_CONTROL_OSC: if (!TYPE_ID_CAN_OSC(type)) break; SPA_FALLTHROUGH; case SPA_CONTROL_Midi: { if (type == TYPE_ID_UMP) { while (size > 0) { uint32_t ump[4]; int ump_size = spa_ump_from_midi(&data, &size, ump, sizeof(ump), 0, &state); if (ump_size <= 0) break; if ((res = midi_event_write(midi, control->offset, (uint8_t*)ump, ump_size, false)) < 0) break; } } else { res = midi_event_write(midi, control->offset, data, size, fix); } if (res < 0) pw_log_warn("midi %p: can't write event: %s", midi, spa_strerror(res)); break; } case SPA_CONTROL_UMP: { if (type == TYPE_ID_MIDI) { uint8_t ev[32]; const uint32_t *d = (uint32_t*)data; while (size > 0) { bool was_sysex = in_sysex; int ev_size = spa_ump_to_midi(&d, &size, ev, sizeof(ev), &state); if (ev_size <= 0) break; if (!in_sysex && ev[0] == 0xf0) in_sysex = true; if (in_sysex && ev[ev_size-1] == 0xf7) in_sysex = false; if (was_sysex) res = midi_event_append(midi, ev, ev_size); else res = midi_event_write(midi, control->offset, ev, ev_size, fix); if (res < 0) break; } } else if (type == TYPE_ID_UMP) { res = midi_event_write(midi, control->offset, data, size, fix); } if (res < 0) pw_log_warn("midi %p: can't write event: %s", midi, spa_strerror(res)); break; } } if (spa_pod_parser_get_control_body(&next->parser, &next->control, &next->control_body) < 0) { spa_pod_parser_pop(&next->parser, &next->frame); mix[next_index] = mix[--n_mix]; } } } static inline void *get_buffer_output(struct port *p, uint32_t frames, uint32_t stride, struct buffer **buf) { struct mix *mix; struct client *c = p->client; void *ptr = NULL; struct buffer *b; struct spa_data *d; struct spa_io_buffers *io; uint32_t cycle = p->client->rt.position->clock.cycle & 1; if (frames == 0 || !p->valid) return NULL; if (SPA_UNLIKELY((mix = p->global_mix) == NULL)) return NULL; pw_log_trace_fp("%p: port %s %d get buffer %d n_buffers:%d io:%p", c, p->object->port.name, p->port_id, frames, mix->n_buffers, mix->io); if (SPA_UNLIKELY((io = mix->io[cycle]) == NULL || mix->n_buffers == 0)) return NULL; if (io->status == SPA_STATUS_HAVE_DATA && io->buffer_id < mix->n_buffers) { b = &mix->buffers[io->buffer_id]; d = &b->datas[0]; } else { if (mix->n_buffers == 1) { b = &mix->buffers[0]; } else { if (io->buffer_id < mix->n_buffers) queue_buffer(c, mix, io->buffer_id); b = dequeue_buffer(c, mix); if (SPA_UNLIKELY(b == NULL)) { pw_log_warn("port %p: out of buffers %d", p, mix->n_buffers); io->buffer_id = SPA_ID_INVALID; return NULL; } } d = &b->datas[0]; d->chunk->offset = 0; d->chunk->size = c->buffer_frames * sizeof(float); d->chunk->stride = stride; io->buffer_id = b->id; io->status = SPA_STATUS_HAVE_DATA; } ptr = d->data; if (buf) *buf = b; return ptr; } static inline void process_empty(struct port *p, uint32_t frames) { struct client *c = p->client; void *ptr, *src = p->emptyptr; struct port *tied = p->tied; uint32_t type = p->object->port.type_id; if (SPA_UNLIKELY(tied != NULL)) { if ((src = tied->get_buffer(tied, frames)) == NULL) src = p->emptyptr; } switch (type) { case TYPE_ID_AUDIO: ptr = get_buffer_output(p, frames, sizeof(float), NULL); if (SPA_LIKELY(ptr != NULL)) memcpy(ptr, src, frames * sizeof(float)); break; case TYPE_ID_MIDI: case TYPE_ID_OSC: case TYPE_ID_UMP: { struct buffer *b; ptr = get_buffer_output(p, c->max_frames, 1, &b); if (SPA_LIKELY(ptr != NULL)) { /* first build the complete pod in scratch memory, then copy it * to the target buffer. This makes it possible for multiple threads * to do this concurrently */ b->datas[0].chunk->size = convert_from_event(src, midi_scratch, MIDI_SCRATCH_FRAMES * sizeof(float), type); memcpy(ptr, midi_scratch, b->datas[0].chunk->size); } break; } default: pw_log_warn("port %p: unhandled format %d", p, p->object->port.type_id); break; } } static void prepare_output(struct port *p, uint32_t frames, uint32_t cycle) { struct mix *mix; struct spa_io_buffers *io; if (SPA_UNLIKELY(p->empty_out || p->tied)) process_empty(p, frames); if (p->global_mix == NULL || (io = p->global_mix->io[cycle]) == NULL) return; spa_list_for_each(mix, &p->mix, port_link) { if (SPA_LIKELY(mix->io[cycle] != NULL)) *mix->io[cycle] = *io; } } static void complete_process(struct client *c, uint32_t frames) { struct port *p; struct mix *mix; union pw_map_item *item; uint32_t cycle = c->rt.position->clock.cycle & 1; pw_array_for_each(item, &c->ports[SPA_DIRECTION_OUTPUT].items) { if (pw_map_item_is_free(item)) continue; p = item->data; if (!p->valid) continue; prepare_output(p, frames, cycle); p->io[cycle].status = SPA_STATUS_NEED_DATA; } pw_array_for_each(item, &c->ports[SPA_DIRECTION_INPUT].items) { if (pw_map_item_is_free(item)) continue; p = item->data; if (!p->valid) continue; spa_list_for_each(mix, &p->mix, port_link) { if (SPA_LIKELY(mix->io[cycle] != NULL)) mix->io[cycle]->status = SPA_STATUS_NEED_DATA; } } } static inline void debug_position(struct client *c, jack_position_t *p) { #define pw_log_custom pw_log_trace_fp pw_log_custom("usecs: %"PRIu64, p->usecs); pw_log_custom("frame_rate: %u", p->frame_rate); pw_log_custom("frame: %u", p->frame); pw_log_custom("valid: %08x", p->valid); if (p->valid & JackPositionBBT) { pw_log_custom("BBT"); pw_log_custom(" bar: %u", p->bar); pw_log_custom(" beat: %u", p->beat); pw_log_custom(" tick: %u", p->tick); pw_log_custom(" bar_start_tick: %f", p->bar_start_tick); pw_log_custom(" beats_per_bar: %f", p->beats_per_bar); pw_log_custom(" beat_type: %f", p->beat_type); pw_log_custom(" ticks_per_beat: %f", p->ticks_per_beat); pw_log_custom(" beats_per_minute: %f", p->beats_per_minute); } if (p->valid & JackPositionTimecode) { pw_log_custom("Timecode:"); pw_log_custom(" frame_time: %f", p->frame_time); pw_log_custom(" next_time: %f", p->next_time); } if (p->valid & JackBBTFrameOffset) { pw_log_custom("BBTFrameOffset:"); pw_log_custom(" bbt_offset: %u", p->bbt_offset); } if (p->valid & JackAudioVideoRatio) { pw_log_custom("AudioVideoRatio:"); pw_log_custom(" audio_frames_per_video_frame: %f", p->audio_frames_per_video_frame); } if (p->valid & JackVideoFrameOffset) { pw_log_custom("JackVideoFrameOffset:"); pw_log_custom(" video_offset: %u", p->video_offset); } #undef pw_log_custom } static inline void jack_to_position(jack_position_t *s, struct pw_node_activation *a) { struct spa_io_segment *d = &a->segment; if (s->valid & JackPositionBBT) { d->bar.flags = SPA_IO_SEGMENT_BAR_FLAG_VALID; if (s->valid & JackBBTFrameOffset) d->bar.offset = s->bbt_offset; else d->bar.offset = 0; d->bar.signature_num = s->beats_per_bar; d->bar.signature_denom = s->beat_type; d->bar.ticks_per_beat = s->ticks_per_beat; d->bar.bar_start_tick = s->bar_start_tick; d->bar.bpm = s->beats_per_minute; d->bar.beat = s->bar * s->beats_per_bar + (s->beat-1) + (s->tick / s->ticks_per_beat); } } static inline jack_transport_state_t position_to_jack(struct pw_node_activation *a, jack_position_t *d, struct frame_times *t) { struct spa_io_position *s = &a->position; jack_transport_state_t state; struct spa_io_segment *seg = &s->segments[0]; uint64_t running; switch (s->state) { default: case SPA_IO_POSITION_STATE_STOPPED: state = JackTransportStopped; break; case SPA_IO_POSITION_STATE_STARTING: state = JackTransportStarting; break; case SPA_IO_POSITION_STATE_RUNNING: if (seg->flags & SPA_IO_SEGMENT_FLAG_LOOPING) state = JackTransportLooping; else state = JackTransportRolling; break; } if (SPA_UNLIKELY(d == NULL)) return state; d->unique_1++; t->frames = s->clock.position; t->nsec = s->clock.nsec; d->usecs = t->nsec / SPA_NSEC_PER_USEC; t->next_nsec = s->clock.next_nsec; t->rate_diff = s->clock.rate_diff; t->buffer_frames = s->clock.duration; d->frame_rate = t->sample_rate = s->clock.rate.denom; if ((int64_t)s->clock.position < s->offset) { d->frame = seg->position; } else { running = s->clock.position - s->offset; if (running >= seg->start && (seg->duration == 0 || running < seg->start + seg->duration)) d->frame = (unsigned int)((running - seg->start) * seg->rate + seg->position); else d->frame = seg->position; } d->valid = 0; if (a->segment_owner[0] && SPA_FLAG_IS_SET(seg->bar.flags, SPA_IO_SEGMENT_BAR_FLAG_VALID)) { double abs_beat; long beats; d->valid |= JackPositionBBT; d->bbt_offset = seg->bar.offset; if (seg->bar.offset) d->valid |= JackBBTFrameOffset; d->beats_per_bar = seg->bar.signature_num; d->beat_type = seg->bar.signature_denom; d->ticks_per_beat = seg->bar.ticks_per_beat; d->bar_start_tick = seg->bar.bar_start_tick; d->beats_per_minute = seg->bar.bpm; abs_beat = seg->bar.beat; d->bar = (int32_t) (abs_beat / d->beats_per_bar); beats = (long int) (d->bar * d->beats_per_bar); d->beat = (int32_t) (abs_beat - beats); beats += d->beat; d->tick = (int32_t) ((abs_beat - beats) * d->ticks_per_beat); d->beat++; } d->unique_2 = d->unique_1; return state; } static inline int check_buffer_frames(struct client *c, struct spa_io_position *pos) { uint32_t buffer_frames = pos->clock.duration; if (SPA_UNLIKELY(buffer_frames != c->buffer_frames)) { pw_log_info("%p: bufferframes old:%d new:%d cb:%p", c, c->buffer_frames, buffer_frames, c->bufsize_callback); if (c->buffer_frames != (uint32_t)-1) queue_notify(c, NOTIFY_TYPE_BUFFER_FRAMES, NULL, buffer_frames, NULL); else c->buffer_frames = buffer_frames; } return c->buffer_frames == buffer_frames; } static inline int check_sample_rate(struct client *c, struct spa_io_position *pos) { uint32_t sample_rate = pos->clock.rate.denom; if (SPA_UNLIKELY(sample_rate != c->sample_rate)) { pw_log_info("%p: sample_rate old:%d new:%d cb:%p", c, c->sample_rate, sample_rate, c->srate_callback); if (c->sample_rate != (uint32_t)-1) queue_notify(c, NOTIFY_TYPE_SAMPLE_RATE, NULL, sample_rate, NULL); else c->sample_rate = sample_rate; } return c->sample_rate == sample_rate; } static inline uint32_t cycle_run(struct client *c) { uint64_t cmd; int fd = c->socket_source->fd; struct spa_io_position *pos = c->rt.position; struct pw_node_activation *activation = c->activation; struct pw_node_activation *driver = c->rt.driver_activation; while (true) { if (SPA_UNLIKELY(read(fd, &cmd, sizeof(cmd)) != sizeof(cmd))) { if (errno == EINTR) continue; if (errno == EWOULDBLOCK || errno == EAGAIN) return 0; pw_log_warn("%p: read failed %m", c); } break; } if (SPA_UNLIKELY(cmd > 1)) { pw_log_info("%p: missed %"PRIu64" wakeups", c, cmd - 1); activation->xrun_count += cmd - 1; activation->xrun_time = activation->awake_time; activation->xrun_delay = 0; activation->max_delay = SPA_MAX(activation->max_delay, 0u); } if (!SPA_ATOMIC_CAS(activation->status, PW_NODE_ACTIVATION_TRIGGERED, PW_NODE_ACTIVATION_AWAKE)) return 0; activation->awake_time = get_time_ns(c->l->system); if (SPA_UNLIKELY(c->rt.first)) { if (c->thread_init_callback) c->thread_init_callback(c->thread_init_arg); c->rt.first = false; } if (SPA_UNLIKELY(pos == NULL)) { pw_log_error("%p: missing position", c); return 0; } if (check_buffer_frames(c, pos) == 0) return 0; if (check_sample_rate(c, pos) == 0) return 0; if (SPA_LIKELY(driver)) { c->jack_state = position_to_jack(driver, &c->jack_position, &c->jack_times); if (SPA_UNLIKELY(activation->pending_sync)) { if (c->sync_callback == NULL || c->sync_callback(c->jack_state, &c->jack_position, c->sync_arg)) activation->pending_sync = false; } if (SPA_UNLIKELY(c->xrun_count != driver->xrun_count && c->xrun_count != 0 && c->xrun_callback)) c->xrun_callback(c->xrun_arg); c->xrun_count = driver->xrun_count; } pw_log_trace_fp("%p: wait %"PRIu64" frames:%d rate:%d pos:%d delay:%"PRIi64" corr:%f", c, activation->awake_time, c->buffer_frames, c->sample_rate, c->jack_position.frame, pos->clock.delay, pos->clock.rate_diff); return c->buffer_frames; } static inline uint32_t cycle_wait(struct client *c) { int res; uint32_t nframes; do { res = pw_data_loop_wait(c->loop, -1); if (SPA_UNLIKELY(res <= 0)) { pw_log_warn("%p: wait error %m", c); return 0; } nframes = cycle_run(c); } while (!nframes); return nframes; } static void trigger_link_v1(struct link *l, uint64_t nsec) { struct client *c = l->client; struct pw_node_activation *a = l->activation; struct pw_node_activation_state *state = &a->state[0]; uint64_t cmd = 1; pw_log_trace_fp("%p: link %p-%d %p %d/%d", c, l, l->node_id, state, state->pending, state->required); if (pw_node_activation_state_dec(state)) { if (SPA_ATOMIC_CAS(a->status, PW_NODE_ACTIVATION_NOT_TRIGGERED, PW_NODE_ACTIVATION_TRIGGERED)) { a->signal_time = nsec; pw_log_trace_fp("%p: signal %p %p", c, l, state); if (SPA_UNLIKELY(write(l->signalfd, &cmd, sizeof(cmd)) != sizeof(cmd))) pw_log_warn("%p: write failed %m", c); } } } static void trigger_link_v0(struct link *l, uint64_t nsec) { struct client *c = l->client; struct pw_node_activation *a = l->activation; struct pw_node_activation_state *state = &a->state[0]; uint64_t cmd = 1; pw_log_trace_fp("%p: link %p-%d %p %d/%d", c, l, l->node_id, state, state->pending, state->required); if (pw_node_activation_state_dec(state)) { SPA_ATOMIC_STORE(a->status, PW_NODE_ACTIVATION_TRIGGERED); a->signal_time = nsec; pw_log_trace_fp("%p: signal %p %p", c, l, state); if (SPA_UNLIKELY(write(l->signalfd, &cmd, sizeof(cmd)) != sizeof(cmd))) pw_log_warn("%p: write failed %m", c); } } static inline void deactivate_link(struct client *c, struct link *l, uint64_t trigger) { if (!c->async && trigger != 0) l->trigger(l, trigger); } static inline void signal_sync(struct client *c) { uint64_t nsec; struct link *l; struct pw_node_activation *activation = c->activation; int old_status; complete_process(c, c->buffer_frames); nsec = get_time_ns(c->l->system); old_status = SPA_ATOMIC_XCHG(activation->status, PW_NODE_ACTIVATION_FINISHED); activation->finish_time = nsec; if (c->async || old_status != PW_NODE_ACTIVATION_AWAKE) return; spa_list_for_each(l, &c->rt.target_links, target_link) l->trigger(l, nsec); } static inline void cycle_signal(struct client *c, int status) { struct pw_node_activation *driver = c->rt.driver_activation; struct pw_node_activation *activation = c->activation; if (SPA_LIKELY(status == 0)) { if (c->timebase_callback && driver && driver->segment_owner[0] == c->node_id) { if (activation->pending_new_pos || c->jack_state == JackTransportRolling || c->jack_state == JackTransportLooping) { c->timebase_callback(c->jack_state, c->buffer_frames, &c->jack_position, activation->pending_new_pos, c->timebase_arg); activation->pending_new_pos = false; debug_position(c, &c->jack_position); jack_to_position(&c->jack_position, activation); } } } signal_sync(c); } static void on_rtsocket_condition(void *data, int fd, uint32_t mask) { struct client *c = data; if (SPA_UNLIKELY(mask & (SPA_IO_ERR | SPA_IO_HUP))) { pw_log_warn("%p: got error", c); client_remove_source(c); return; } if (SPA_UNLIKELY(c->thread_callback)) { if (!c->rt.thread_entered) { c->rt.thread_entered = true; c->thread_callback(c->thread_arg); } } else if (SPA_LIKELY(mask & SPA_IO_IN)) { uint32_t buffer_frames; int status = 0; buffer_frames = cycle_run(c); if (buffer_frames > 0) status = do_rt_callback_res(c, process_callback, buffer_frames, c->process_arg); cycle_signal(c, status); } } static void free_link(struct link *link) { pw_log_debug("free link %p", link); pw_memmap_free(link->mem); close(link->signalfd); free(link); } static int do_clean_transport(struct spa_loop *loop, bool async, uint32_t seq, const void *data, size_t size, void *user_data) { struct client *c = user_data; struct link *l; pw_log_debug("%p: clean transport", c); client_remove_source(c); spa_list_consume(l, &c->rt.target_links, target_link) spa_list_remove(&l->target_link); return 0; } static void clean_transport(struct client *c) { struct link *l; if (!c->has_transport) return; /* We assume the data-loop is unlocked now and can process our * clean function. This is reasonable, the cleanup function is run when * closing the client, which should join the data-thread. */ pw_data_loop_invoke(c->loop, do_clean_transport, 1, NULL, 0, true, c); spa_list_consume(l, &c->links, link) { spa_list_remove(&l->link); free_link(l); } c->has_transport = false; } static int client_node_transport(void *data, int readfd, int writefd, uint32_t mem_id, uint32_t offset, uint32_t size) { struct client *c = (struct client *) data; clean_transport(c); c->mem = pw_mempool_map_id(c->pool, mem_id, PW_MEMMAP_FLAG_READWRITE, offset, size, NULL); if (c->mem == NULL) { pw_log_debug("%p: can't map activation: %m", c); return -errno; } c->activation = c->mem->ptr; pw_log_debug("%p: create client transport with fds %d %d for node %u", c, readfd, writefd, c->node_id); c->activation->client_version = PW_VERSION_NODE_ACTIVATION; close(writefd); c->socket_source = pw_loop_add_io(c->l, readfd, SPA_IO_ERR | SPA_IO_HUP, true, on_rtsocket_condition, c); c->has_transport = true; c->position = &c->activation->position; pw_thread_loop_signal(c->context.loop, false); return 0; } static int client_node_set_param(void *data, uint32_t id, uint32_t flags, const struct spa_pod *param) { struct client *c = (struct client *) data; pw_proxy_error((struct pw_proxy*)c->node, -ENOTSUP, "not supported"); return -ENOTSUP; } static int install_timeowner(struct client *c) { struct pw_node_activation *a; uint32_t owner; if (!c->timebase_callback) return 0; if ((a = c->driver_activation) == NULL) return -EIO; pw_log_debug("%p: activation %p", c, a); /* was ok */ owner = SPA_ATOMIC_LOAD(a->segment_owner[0]); if (owner == c->node_id) return 0; /* try to become owner */ if (c->timeowner_conditional) { if (!SPA_ATOMIC_CAS(a->segment_owner[0], 0, c->node_id)) { pw_log_debug("%p: owner:%u id:%u", c, owner, c->node_id); return -EBUSY; } } else { SPA_ATOMIC_STORE(a->segment_owner[0], c->node_id); } pw_log_debug("%p: timebase installed for id:%u", c, c->node_id); return 0; } static int do_update_driver_activation(struct spa_loop *loop, bool async, uint32_t seq, const void *data, size_t size, void *user_data) { struct client *c = user_data; c->rt.position = c->position; c->rt.driver_activation = c->driver_activation; if (c->position) { pw_log_debug("%p: driver:%d clock:%s", c, c->driver_id, c->position->clock.name); check_sample_rate(c, c->position); check_buffer_frames(c, c->position); } return 0; } static int update_driver_activation(struct client *c) { jack_client_t *client = (jack_client_t*)c; struct link *link; bool freewheeling; pw_log_debug("%p: driver %d", c, c->driver_id); freewheeling = SPA_FLAG_IS_SET(c->position->clock.flags, SPA_IO_CLOCK_FLAG_FREEWHEEL); if (c->freewheeling != freewheeling) { jack_native_thread_t thr = jack_client_thread_id(client); c->freewheeling = freewheeling; if (freewheeling && thr) { jack_drop_real_time_scheduling(thr); } queue_notify(c, NOTIFY_TYPE_FREEWHEEL, NULL, freewheeling, NULL); if (!freewheeling && thr) { jack_acquire_real_time_scheduling(thr, jack_client_real_time_priority(client)); } } link = find_activation(&c->links, c->driver_id); c->driver_activation = link ? link->activation : NULL; pw_data_loop_invoke(c->loop, do_update_driver_activation, SPA_ID_INVALID, NULL, 0, false, c); install_timeowner(c); return 0; } static int do_memmap_free(struct spa_loop *loop, bool async, uint32_t seq, const void *data, size_t size, void *user_data) { struct client *c = user_data; struct pw_memmap *mm = *((struct pw_memmap **)data); pw_log_trace("memmap %p free", mm); pw_memmap_free(mm); pw_core_set_paused(c->core, false); return 0; } static int do_queue_memmap_free(struct spa_loop *loop, bool async, uint32_t seq, const void *data, size_t size, void *user_data) { struct client *c = user_data; pw_loop_invoke(c->context.l, do_memmap_free, 0, data, size, false, c); return 0; } static void queue_memmap_free(struct client *c, struct pw_memmap *mem) { if (mem != NULL) { mem->tag[0] = SPA_ID_INVALID; pw_core_set_paused(c->core, true); pw_data_loop_invoke(c->loop, do_queue_memmap_free, SPA_ID_INVALID, &mem, sizeof(&mem), false, c); } } static int client_node_set_io(void *data, uint32_t id, uint32_t mem_id, uint32_t offset, uint32_t size) { struct client *c = (struct client *) data; struct pw_memmap *old, *mm; void *ptr; uint32_t tag[5] = { c->node_id, id, }; old = pw_mempool_find_tag(c->pool, tag, sizeof(tag)); if (mem_id == SPA_ID_INVALID) { mm = ptr = NULL; } else { mm = pw_mempool_map_id(c->pool, mem_id, PW_MEMMAP_FLAG_READWRITE, offset, size, tag); if (mm == NULL) { pw_log_warn("%p: can't map memory id %u: %m", c, mem_id); return -errno; } ptr = mm->ptr; } pw_log_debug("%p: set io %s %p", c, spa_debug_type_find_name(spa_type_io, id), ptr); switch (id) { case SPA_IO_Position: c->position = ptr; c->driver_id = ptr ? c->position->clock.id : SPA_ID_INVALID; update_driver_activation(c); c->activation->active_driver_id = c->driver_id; queue_memmap_free(c, old); old = NULL; break; default: break; } pw_memmap_free(old); return 0; } static int client_node_event(void *data, const struct spa_event *event) { return -ENOTSUP; } static int do_prepare_client(struct spa_loop *loop, bool async, uint32_t seq, const void *data, size_t size, void *user_data) { struct client *c = user_data; pw_log_debug("%p prepared:%d ", c, c->rt.prepared); if (c->rt.prepared) return 0; SPA_ATOMIC_STORE(c->activation->status, PW_NODE_ACTIVATION_FINISHED); pw_loop_update_io(c->l, c->socket_source, SPA_IO_IN | SPA_IO_ERR | SPA_IO_HUP); c->rt.first = true; c->rt.thread_entered = false; c->rt.prepared = true; return 0; } static int do_unprepare_client(struct spa_loop *loop, bool async, uint32_t seq, const void *data, size_t size, void *user_data) { struct client *c = user_data; int old_state; uint64_t trigger = 0; struct link *l; pw_log_debug("%p prepared:%d ", c, c->rt.prepared); old_state = SPA_ATOMIC_XCHG(c->activation->status, PW_NODE_ACTIVATION_INACTIVE); if (old_state != PW_NODE_ACTIVATION_FINISHED) trigger = get_time_ns(c->l->system); if (!c->rt.prepared) return 0; spa_list_for_each(l, &c->rt.target_links, target_link) { if (!c->async && trigger != 0) l->trigger(l, trigger); } pw_loop_update_io(c->l, c->socket_source, SPA_IO_ERR | SPA_IO_HUP); c->rt.prepared = false; return 0; } static int client_node_command(void *data, const struct spa_command *command) { struct client *c = (struct client *) data; pw_log_debug("%p: got command %d", c, SPA_COMMAND_TYPE(command)); switch (SPA_NODE_COMMAND_ID(command)) { case SPA_NODE_COMMAND_Suspend: case SPA_NODE_COMMAND_Pause: if (c->started) { pw_loop_locked(c->loop->loop, do_unprepare_client, SPA_ID_INVALID, NULL, 0, c); c->started = false; } break; case SPA_NODE_COMMAND_Start: if (!c->started) { pw_loop_locked(c->loop->loop, do_prepare_client, SPA_ID_INVALID, NULL, 0, c); c->started = true; } break; default: pw_log_warn("%p: unhandled node command %d", c, SPA_COMMAND_TYPE(command)); pw_proxy_errorf((struct pw_proxy*)c->node, -ENOTSUP, "unhandled command %d", SPA_COMMAND_TYPE(command)); } return 0; } static int client_node_add_port(void *data, enum spa_direction direction, uint32_t port_id, const struct spa_dict *props) { struct client *c = (struct client *) data; pw_proxy_error((struct pw_proxy*)c->node, -ENOTSUP, "add port not supported"); return -ENOTSUP; } static int client_node_remove_port(void *data, enum spa_direction direction, uint32_t port_id) { struct client *c = (struct client *) data; pw_proxy_error((struct pw_proxy*)c->node, -ENOTSUP, "remove port not supported"); return -ENOTSUP; } static int param_enum_format(struct client *c, struct port *p, struct spa_pod **param, struct spa_pod_builder *b) { switch (p->object->port.type_id) { case TYPE_ID_AUDIO: *param = spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_audio), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_dsp), SPA_FORMAT_AUDIO_format, SPA_POD_Id(SPA_AUDIO_FORMAT_DSP_F32)); break; case TYPE_ID_UMP: case TYPE_ID_OSC: case TYPE_ID_MIDI: *param = spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_application), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_control)); break; case TYPE_ID_VIDEO: *param = spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_dsp), SPA_FORMAT_VIDEO_format, SPA_POD_Id(SPA_VIDEO_FORMAT_DSP_F32)); break; default: return -EINVAL; } return 1; } static int param_format(struct client *c, struct port *p, struct spa_pod **param, struct spa_pod_builder *b) { switch (p->object->port.type_id) { case TYPE_ID_AUDIO: *param = spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_Format, SPA_PARAM_Format, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_audio), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_dsp), SPA_FORMAT_AUDIO_format, SPA_POD_Id(SPA_AUDIO_FORMAT_DSP_F32)); break; case TYPE_ID_MIDI: case TYPE_ID_OSC: case TYPE_ID_UMP: *param = spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_Format, SPA_PARAM_Format, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_application), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_control)); break; case TYPE_ID_VIDEO: *param = spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_Format, SPA_PARAM_Format, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_dsp), SPA_FORMAT_VIDEO_format, SPA_POD_Id(SPA_VIDEO_FORMAT_DSP_F32)); break; default: return -EINVAL; } return 1; } static int param_buffers(struct client *c, struct port *p, struct spa_pod **param, struct spa_pod_builder *b) { switch (p->object->port.type_id) { case TYPE_ID_AUDIO: case TYPE_ID_MIDI: case TYPE_ID_OSC: case TYPE_ID_UMP: *param = spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers, SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(1, 1, MAX_BUFFERS), SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(1), SPA_PARAM_BUFFERS_size, SPA_POD_CHOICE_STEP_Int( c->max_frames * sizeof(float), sizeof(float), INT32_MAX, sizeof(float)), SPA_PARAM_BUFFERS_stride, SPA_POD_Int(p->object->port.type_id == TYPE_ID_AUDIO ? sizeof(float) : 1)); break; case TYPE_ID_VIDEO: *param = spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers, SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(1, 1, MAX_BUFFERS), SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(1), SPA_PARAM_BUFFERS_size, SPA_POD_CHOICE_RANGE_Int( 320 * 240 * 4 * 4, 0, INT32_MAX), SPA_PARAM_BUFFERS_stride, SPA_POD_CHOICE_RANGE_Int(4, 4, INT32_MAX)); break; default: return -EINVAL; } return 1; } static int param_io(struct client *c, struct port *p, struct spa_pod **param, struct spa_pod_builder *b) { *param = spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_ParamIO, SPA_PARAM_IO, SPA_PARAM_IO_id, SPA_POD_Id(SPA_IO_Buffers), SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_buffers))); return 1; } static int param_io_async(struct client *c, struct port *p, struct spa_pod **param, struct spa_pod_builder *b) { *param = spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_ParamIO, SPA_PARAM_IO, SPA_PARAM_IO_id, SPA_POD_Id(SPA_IO_AsyncBuffers), SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_async_buffers))); return 1; } static int param_latency(struct client *c, struct port *p, struct spa_pod **param, struct spa_pod_builder *b) { *param = spa_latency_build(b, SPA_PARAM_Latency, &p->object->port.latency[p->direction]); return 1; } static int param_latency_other(struct client *c, struct port *p, struct spa_pod **param, struct spa_pod_builder *b) { *param = spa_latency_build(b, SPA_PARAM_Latency, &p->object->port.latency[SPA_DIRECTION_REVERSE(p->direction)]); return 1; } /* called from thread-loop */ static int port_set_format(struct client *c, struct port *p, uint32_t flags, const struct spa_pod *param) { struct spa_pod *params[7]; uint8_t buffer[4096]; struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); if (param == NULL) { struct mix *mix; pw_log_debug("%p: port %p clear format", c, p); spa_list_for_each(mix, &p->mix, port_link) clear_buffers(c, mix); p->params[IDX_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); } else { struct spa_audio_info info = { 0 }; if (spa_format_parse(param, &info.media_type, &info.media_subtype) < 0) return -EINVAL; switch (info.media_type) { case SPA_MEDIA_TYPE_audio: { if (info.media_subtype != SPA_MEDIA_SUBTYPE_dsp) return -EINVAL; if (spa_format_audio_dsp_parse(param, &info.info.dsp) < 0) return -EINVAL; if (info.info.dsp.format != SPA_AUDIO_FORMAT_DSP_F32) return -EINVAL; break; } case SPA_MEDIA_TYPE_application: if (info.media_subtype != SPA_MEDIA_SUBTYPE_control) return -EINVAL; break; case SPA_MEDIA_TYPE_video: { struct spa_video_info vinfo = { 0 }; if (info.media_subtype != SPA_MEDIA_SUBTYPE_dsp) return -EINVAL; if (spa_format_video_dsp_parse(param, &vinfo.info.dsp) < 0) return -EINVAL; if (vinfo.info.dsp.format != SPA_VIDEO_FORMAT_DSP_F32) return -EINVAL; break; } default: return -EINVAL; } p->params[IDX_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_READWRITE); } pw_log_debug("port %s: update", p->object->port.name); p->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; param_enum_format(c, p, ¶ms[0], &b); param_format(c, p, ¶ms[1], &b); param_buffers(c, p, ¶ms[2], &b); param_io(c, p, ¶ms[3], &b); param_io_async(c, p, ¶ms[4], &b); param_latency(c, p, ¶ms[5], &b); param_latency_other(c, p, ¶ms[6], &b); pw_client_node_port_update(c->node, p->direction, p->port_id, PW_CLIENT_NODE_PORT_UPDATE_PARAMS | PW_CLIENT_NODE_PORT_UPDATE_INFO, SPA_N_ELEMENTS(params), (const struct spa_pod **) params, &p->info); p->info.change_mask = 0; return 0; } /* called from thread-loop */ static void port_update_latency(struct port *p) { struct client *c = p->client; struct spa_pod *params[7]; uint8_t buffer[4096]; struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); param_enum_format(c, p, ¶ms[0], &b); param_format(c, p, ¶ms[1], &b); param_buffers(c, p, ¶ms[2], &b); param_io(c, p, ¶ms[3], &b); param_io_async(c, p, ¶ms[4], &b); param_latency(c, p, ¶ms[5], &b); param_latency_other(c, p, ¶ms[6], &b); pw_log_debug("port %s: update", p->object->port.name); p->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; p->params[IDX_Latency].flags ^= SPA_PARAM_INFO_SERIAL; pw_client_node_port_update(c->node, p->direction, p->port_id, PW_CLIENT_NODE_PORT_UPDATE_PARAMS | PW_CLIENT_NODE_PORT_UPDATE_INFO, SPA_N_ELEMENTS(params), (const struct spa_pod **) params, &p->info); p->info.change_mask = 0; } static void port_check_latency(struct port *p, const struct spa_latency_info *latency) { struct spa_latency_info *current; struct client *c = p->client; struct object *o = p->object; current = &o->port.latency[latency->direction]; if (spa_latency_info_compare(current, latency) == 0) return; *current = *latency; pw_log_info("%p: %s update %s latency %f-%f %d-%d %"PRIu64"-%"PRIu64, c, o->port.name, latency->direction == SPA_DIRECTION_INPUT ? "playback" : "capture", latency->min_quantum, latency->max_quantum, latency->min_rate, latency->max_rate, latency->min_ns, latency->max_ns); port_update_latency(p); } /* called from thread-loop */ static void default_latency(struct client *c, enum spa_direction direction, struct spa_latency_info *latency) { enum spa_direction other; union pw_map_item *item; struct port *p; other = SPA_DIRECTION_REVERSE(direction); spa_latency_info_combine_start(latency, direction); pw_array_for_each(item, &c->ports[other].items) { if (pw_map_item_is_free(item)) continue; p = item->data; spa_latency_info_combine(latency, &p->object->port.latency[direction]); } spa_latency_info_combine_finish(latency); } /* called from thread-loop */ static void default_latency_callback(jack_latency_callback_mode_t mode, struct client *c) { struct spa_latency_info latency; union pw_map_item *item; enum spa_direction direction; struct port *p; if (mode == JackPlaybackLatency) direction = SPA_DIRECTION_INPUT; else direction = SPA_DIRECTION_OUTPUT; default_latency(c, direction, &latency); pw_array_for_each(item, &c->ports[direction].items) { if (pw_map_item_is_free(item)) continue; p = item->data; port_check_latency(p, &latency); } } /* called from thread-loop */ static int port_set_latency(struct client *c, struct port *p, uint32_t flags, const struct spa_pod *param) { struct spa_latency_info info; jack_latency_callback_mode_t mode; struct spa_latency_info *current; int res; if (param == NULL) info = SPA_LATENCY_INFO(SPA_DIRECTION_REVERSE(p->direction)); else if ((res = spa_latency_parse(param, &info)) < 0) return res; if (info.direction == p->direction) return 0; current = &p->object->port.latency[info.direction]; if (spa_latency_info_compare(current, &info) == 0) return 0; *current = info; pw_log_info("port %s: set %s latency %f-%f %d-%d %"PRIu64"-%"PRIu64, p->object->port.name, info.direction == SPA_DIRECTION_INPUT ? "playback" : "capture", info.min_quantum, info.max_quantum, info.min_rate, info.max_rate, info.min_ns, info.max_ns); if (info.direction == SPA_DIRECTION_INPUT) mode = JackPlaybackLatency; else mode = JackCaptureLatency; if (c->latency_callback) queue_notify(c, NOTIFY_TYPE_LATENCY, NULL, mode, NULL); else default_latency_callback(mode, c); port_update_latency(p); return 0; } /* called from thread-loop */ static int client_node_port_set_param(void *data, enum spa_direction direction, uint32_t port_id, uint32_t id, uint32_t flags, const struct spa_pod *param) { struct client *c = (struct client *) data; struct port *p = GET_PORT(c, direction, port_id); if (p == NULL || !p->valid) return -EINVAL; pw_log_info("client %p: port %s %d.%d id:%d (%s) %p", c, p->object->port.name, direction, port_id, id, spa_debug_type_find_name(spa_type_param, id), param); switch (id) { case SPA_PARAM_Format: return port_set_format(c, p, flags, param); break; case SPA_PARAM_Latency: return port_set_latency(c, p, flags, param); default: break; } return 0; } static void midi_init_buffer(void *data, uint32_t max_frames, uint32_t nframes) { struct midi_buffer *mb = data; mb->magic = MIDI_BUFFER_MAGIC; mb->buffer_size = max_frames * sizeof(float); mb->nframes = nframes; mb->write_pos = 0; mb->event_count = 0; mb->lost_events = 0; } static inline void *init_buffer(struct port *p, uint32_t nframes) { struct client *c = p->client; void *data = p->emptyptr; if (p->zeroed) return data; if (TYPE_ID_IS_EVENT(p->object->port.type_id)) { struct midi_buffer *mb = data; midi_init_buffer(data, c->max_frames, nframes); pw_log_debug("port %p: init midi buffer size:%d frames:%d", p, mb->buffer_size, nframes); } else memset(data, 0, c->max_frames * sizeof(float)); p->zeroed = true; return data; } static int client_node_port_use_buffers(void *data, enum spa_direction direction, uint32_t port_id, uint32_t mix_id, uint32_t flags, uint32_t n_buffers, struct pw_client_node_buffer *buffers) { struct client *c = (struct client *) data; struct port *p = GET_PORT(c, direction, port_id); struct buffer *b; uint32_t i, j, fl; int res; struct mix *mix; if (p == NULL || !p->valid) { res = -EINVAL; goto done; } if ((mix = find_mix(c, p, mix_id)) == NULL) { res = -ENOMEM; goto done; } pw_log_debug("%p: port %p %d %d.%d use_buffers %d", c, p, direction, port_id, mix_id, n_buffers); if (n_buffers > MAX_BUFFERS) { pw_log_error("%p: too many buffers %u > %u", c, n_buffers, MAX_BUFFERS); res = -ENOSPC; goto done; } fl = PW_MEMMAP_FLAG_READ; /* Make the buffer writable when output. Some apps write to the input buffer * so we want to make them writable as well if the option is selected. * We can't use a PRIVATE mapping here because then we might not see changes * in the buffer by other apps (see mmap man page). */ if (direction == SPA_DIRECTION_OUTPUT || (p->object->port.type_id != TYPE_ID_VIDEO && c->writable_input)) fl |= PW_MEMMAP_FLAG_WRITE; /* clear previous buffers */ clear_buffers(c, mix); for (i = 0; i < n_buffers; i++) { off_t offset; struct spa_buffer *buf; struct pw_memmap *mm; mm = pw_mempool_map_id(c->pool, buffers[i].mem_id, fl, buffers[i].offset, buffers[i].size, NULL); if (mm == NULL) { pw_log_warn("%p: can't map memory id %u: %m", c, buffers[i].mem_id); continue; } buf = buffers[i].buffer; b = &mix->buffers[i]; b->id = i; b->flags = 0; b->n_mem = 0; b->mem[b->n_mem++] = mm; pw_log_debug("%p: add buffer id:%u offset:%u size:%u map:%p ptr:%p", c, buffers[i].mem_id, buffers[i].offset, buffers[i].size, mm, mm->ptr); offset = 0; for (j = 0; j < buf->n_metas; j++) { struct spa_meta *m = &buf->metas[j]; offset += SPA_ROUND_UP_N(m->size, 8); } b->n_datas = SPA_MIN(buf->n_datas, MAX_BUFFER_DATAS); for (j = 0; j < b->n_datas; j++) { struct spa_data *d = &b->datas[j]; memcpy(d, &buf->datas[j], sizeof(struct spa_data)); d->chunk = SPA_PTROFF(mm->ptr, offset + sizeof(struct spa_chunk) * j, struct spa_chunk); if (d->type == SPA_DATA_MemId) { uint32_t mem_id = SPA_PTR_TO_UINT32(d->data); struct pw_memblock *bm; struct pw_memmap *bmm; bm = pw_mempool_find_id(c->pool, mem_id); if (bm == NULL) { pw_log_error("%p: unknown buffer mem %u", c, mem_id); res = -ENODEV; goto done; } d->fd = bm->fd; d->type = bm->type; d->data = NULL; bmm = pw_memblock_map(bm, fl, d->mapoffset, d->maxsize, NULL); if (bmm == NULL) { res = -errno; pw_log_error("%p: failed to map buffer mem %m", c); d->data = NULL; goto done; } b->mem[b->n_mem++] = bmm; d->data = bmm->ptr; pw_log_debug("%p: data %d %u -> fd %d %d", c, j, bm->id, bm->fd, d->maxsize); } else if (d->type == SPA_DATA_MemPtr) { int offs = SPA_PTR_TO_INT(d->data); d->data = SPA_PTROFF(mm->ptr, offs, void); d->fd = -1; pw_log_debug("%p: data %d %u -> mem %p %d", c, j, b->id, d->data, d->maxsize); } else { pw_log_warn("unknown buffer data type %d", d->type); } if (c->allow_mlock && mlock(d->data, d->maxsize) < 0) { if (errno != ENOMEM || !mlock_warned) { pw_log(c->warn_mlock ? SPA_LOG_LEVEL_WARN : SPA_LOG_LEVEL_DEBUG, "%p: Failed to mlock memory %p %u: %s", c, d->data, d->maxsize, errno == ENOMEM ? "This is not a problem but for best performance, " "consider increasing RLIMIT_MEMLOCK" : strerror(errno)); mlock_warned |= errno == ENOMEM; } } } SPA_FLAG_SET(b->flags, BUFFER_FLAG_OUT); if (direction == SPA_DIRECTION_OUTPUT) queue_buffer(c, mix, b->id); } pw_log_debug("%p: have %d buffers", c, n_buffers); mix->n_buffers = n_buffers; res = 0; done: if (res < 0) pw_proxy_errorf((struct pw_proxy*)c->node, res, "port_use_buffers(%u:%u:%u): %s", direction, port_id, mix_id, spa_strerror(res)); return res; } static int client_node_port_set_io(void *data, enum spa_direction direction, uint32_t port_id, uint32_t mix_id, uint32_t id, uint32_t mem_id, uint32_t offset, uint32_t size) { struct client *c = (struct client *) data; struct port *p = GET_PORT(c, direction, port_id); struct pw_memmap *mm, *old; struct mix *mix; uint32_t tag[5] = { c->node_id, direction, port_id, mix_id, id }; void *ptr; int res = 0; if (p == NULL || !p->valid) { res = -EINVAL; goto exit; } if ((mix = find_mix(c, p, mix_id)) == NULL) { res = -ENOMEM; goto exit; } old = pw_mempool_find_tag(c->pool, tag, sizeof(tag)); if (mem_id == SPA_ID_INVALID) { mm = ptr = NULL; size = 0; } else { mm = pw_mempool_map_id(c->pool, mem_id, PW_MEMMAP_FLAG_READWRITE, offset, size, tag); if (mm == NULL) { pw_log_warn("%p: can't map memory id %u: %m", c, mem_id); res = -EINVAL; goto exit_free; } ptr = mm->ptr; } pw_log_debug("%p: port %p mix:%d set io:%s id:%u ptr:%p", c, p, mix_id, spa_debug_type_find_name(spa_type_io, id), id, ptr); switch (id) { case SPA_IO_Buffers: case SPA_IO_AsyncBuffers: mix_set_io(mix, ptr, size); queue_memmap_free(c, old); old = NULL; break; default: break; } exit_free: pw_memmap_free(old); exit: if (res < 0) pw_proxy_errorf((struct pw_proxy*)c->node, res, "port_set_io(%u:%u:%u %u): %s", direction, port_id, mix_id, id, spa_strerror(res)); return res; } static int do_add_link(struct spa_loop *loop, bool async, uint32_t seq, const void *data, size_t size, void *user_data) { struct link *link = user_data; struct client *c = link->client; pw_log_trace("link %p", link); spa_list_append(&c->rt.target_links, &link->target_link); return 0; } static int do_remove_link(struct spa_loop *loop, bool async, uint32_t seq, const void *data, size_t size, void *user_data) { struct link *link = user_data; struct client *c = link->client; pw_log_trace("link %p", link); spa_list_remove(&link->target_link); if (c->rt.prepared) { int old_state = SPA_ATOMIC_LOAD(c->activation->status); uint64_t trigger = 0; if (old_state != PW_NODE_ACTIVATION_FINISHED) trigger = get_time_ns(c->l->system); deactivate_link(c, link, trigger); } return 0; } static int do_free_link(struct spa_loop *loop, bool async, uint32_t seq, const void *data, size_t size, void *user_data) { struct client *c = user_data; struct link *l = *((struct link **)data); free_link(l); pw_core_set_paused(c->core, false); return 0; } static int do_queue_free_link(struct spa_loop *loop, bool async, uint32_t seq, const void *data, size_t size, void *user_data) { struct client *c = user_data; pw_loop_invoke(c->context.l, do_free_link, 0, data, size, false, c); return 0; } static void queue_free_link(struct client *c, struct link *l) { pw_core_set_paused(c->core, true); pw_data_loop_invoke(c->loop, do_queue_free_link, SPA_ID_INVALID, &l, sizeof(&l), false, c); } static int client_node_set_activation(void *data, uint32_t node_id, int signalfd, uint32_t mem_id, uint32_t offset, uint32_t size) { struct client *c = (struct client *) data; struct pw_memmap *mm; struct link *link; void *ptr; int res = 0; if (mem_id == SPA_ID_INVALID) { mm = ptr = NULL; size = 0; } else { mm = pw_mempool_map_id(c->pool, mem_id, PW_MEMMAP_FLAG_READWRITE, offset, size, NULL); if (mm == NULL) { pw_log_warn("%p: can't map memory id %u: %m", c, mem_id); res = -EINVAL; goto exit; } ptr = mm->ptr; } if (c->node_id == node_id) { pw_log_debug("%p: our activation %u: %u %u %u %p", c, node_id, mem_id, offset, size, ptr); } else { pw_log_debug("%p: set activation %u: %u %u %u %p", c, node_id, mem_id, offset, size, ptr); } if (ptr) { link = calloc(1, sizeof(struct link)); if (link == NULL) { res = -errno; goto exit; } link->client = c; link->node_id = node_id; link->mem = mm; link->activation = ptr; link->signalfd = signalfd; link->trigger = link->activation->server_version < 1 ? trigger_link_v0 : trigger_link_v1; spa_list_append(&c->links, &link->link); pw_loop_locked(c->loop->loop, do_add_link, SPA_ID_INVALID, NULL, 0, link); } else { link = find_activation(&c->links, node_id); if (link == NULL) { res = -EINVAL; goto exit; } spa_list_remove(&link->link); pw_loop_locked(c->loop->loop, do_remove_link, SPA_ID_INVALID, NULL, 0, link); queue_free_link(c, link); } if (c->driver_id == node_id) update_driver_activation(c); exit: if (res < 0) pw_proxy_errorf((struct pw_proxy*)c->node, res, "set_activation(%u): %s", node_id, spa_strerror(res)); return res; } static int client_node_port_set_mix_info(void *data, enum spa_direction direction, uint32_t port_id, uint32_t mix_id, uint32_t peer_id, const struct spa_dict *props) { struct client *c = (struct client *) data; struct port *p = GET_PORT(c, direction, port_id); struct mix *mix; int res = 0; if (p == NULL || !p->valid) { res = peer_id == SPA_ID_INVALID ? 0 : -EINVAL; goto exit; } mix = find_mix(c, p, mix_id); pw_log_debug("%p: port %p mix:%d peer_id:%u info:%p", c, p, mix_id, peer_id, props); if (peer_id == SPA_ID_INVALID) { if (mix == NULL) { res = -ENOENT; goto exit; } free_mix(c, mix); } else { if (mix != NULL) { res = -EEXIST; goto exit; } mix = create_mix(c, p, mix_id, peer_id); } exit: if (res < 0) pw_proxy_errorf((struct pw_proxy*)c->node, res, "set_mix_info(%u:%u:%u %u): %s", direction, port_id, mix_id, peer_id, spa_strerror(res)); return res; } static const struct pw_client_node_events client_node_events = { PW_VERSION_CLIENT_NODE_EVENTS, .transport = client_node_transport, .set_param = client_node_set_param, .set_io = client_node_set_io, .event = client_node_event, .command = client_node_command, .add_port = client_node_add_port, .remove_port = client_node_remove_port, .port_set_param = client_node_port_set_param, .port_use_buffers = client_node_port_use_buffers, .port_set_io = client_node_port_set_io, .set_activation = client_node_set_activation, .port_set_mix_info = client_node_port_set_mix_info, }; #define CHECK(expression,label) \ do { \ if ((errno = expression) != 0) { \ res = -errno; \ pw_log_error(#expression ": %s", strerror(errno)); \ goto label; \ } \ } while(false); static struct spa_thread *impl_create(void *object, const struct spa_dict *props, void *(*start)(void*), void *arg) { struct client *c = (struct client *) object; struct spa_dict_item *items; struct spa_dict copy; char creator_ptr[64]; pw_log_info("create thread"); if (globals.creator != NULL) { uint32_t i, n_items = props ? props->n_items : 0; items = alloca((n_items + 1) * sizeof(*items)); for (i = 0; i < n_items; i++) items[i] = props->items[i]; snprintf(creator_ptr, sizeof(creator_ptr), "pointer:%p", globals.creator); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_THREAD_CREATOR, creator_ptr); copy = SPA_DICT_INIT(items, n_items); props = © } return spa_thread_utils_create(c->context.old_thread_utils, props, start, arg); } static int impl_join(void *object, struct spa_thread *thread, void **retval) { struct client *c = (struct client *) object; pw_log_info("join thread"); return spa_thread_utils_join(c->context.old_thread_utils, thread, retval); } static int impl_acquire_rt(void *object, struct spa_thread *thread, int priority) { struct client *c = (struct client *) object; return spa_thread_utils_acquire_rt(c->context.old_thread_utils, thread, priority); } static int impl_drop_rt(void *object, struct spa_thread *thread) { struct client *c = (struct client *) object; return spa_thread_utils_drop_rt(c->context.old_thread_utils, thread); } static struct spa_thread_utils_methods thread_utils_impl = { SPA_VERSION_THREAD_UTILS_METHODS, .create = impl_create, .join = impl_join, .acquire_rt = impl_acquire_rt, .drop_rt = impl_drop_rt, }; static jack_port_type_id_t string_to_type(const char *port_type) { if (spa_streq(JACK_DEFAULT_AUDIO_TYPE, port_type)) return TYPE_ID_AUDIO; else if (spa_streq(JACK_DEFAULT_VIDEO_TYPE, port_type)) return TYPE_ID_VIDEO; else if (spa_streq(JACK_DEFAULT_MIDI_TYPE, port_type)) return TYPE_ID_MIDI; else if (spa_streq(JACK_DEFAULT_OSC_TYPE, port_type)) return TYPE_ID_OSC; else if (spa_streq(JACK_DEFAULT_UMP_TYPE, port_type)) return TYPE_ID_UMP; else if (spa_streq("other", port_type)) return TYPE_ID_OTHER; else return SPA_ID_INVALID; } static const char* type_to_string(jack_port_type_id_t type_id) { switch(type_id) { case TYPE_ID_AUDIO: return JACK_DEFAULT_AUDIO_TYPE; case TYPE_ID_VIDEO: return JACK_DEFAULT_VIDEO_TYPE; case TYPE_ID_MIDI: case TYPE_ID_OSC: case TYPE_ID_UMP: /* all returned as MIDI */ return JACK_DEFAULT_MIDI_TYPE; case TYPE_ID_OTHER: return "other"; default: return NULL; } } static bool type_is_dsp(jack_port_type_id_t type_id) { switch(type_id) { case TYPE_ID_AUDIO: case TYPE_ID_MIDI: case TYPE_ID_VIDEO: case TYPE_ID_OSC: case TYPE_ID_UMP: return true; default: return false; } } static jack_uuid_t client_make_uuid(uint32_t id, bool monitor) { jack_uuid_t uuid = 0x2; /* JackUUIDClient */ uuid = (uuid << 32) | (id + 1); if (monitor) uuid |= (1 << 30); pw_log_debug("uuid %d -> %"PRIu64, id, uuid); return uuid; } static int metadata_property(void *data, uint32_t id, const char *key, const char *type, const char *value) { struct client *c = (struct client *) data; struct object *o; jack_uuid_t uuid; pw_log_debug("set id:%u key:'%s' value:'%s' type:'%s'", id, key, value, type); if (id == PW_ID_CORE) { if (key == NULL || spa_streq(key, "default.audio.sink")) { if (value != NULL) { if (spa_json_str_object_find(value, strlen(value), "name", c->metadata->default_audio_sink, sizeof(c->metadata->default_audio_sink)) < 0) value = NULL; } if (value == NULL) c->metadata->default_audio_sink[0] = '\0'; } if (key == NULL || spa_streq(key, "default.audio.source")) { if (value != NULL) { if (spa_json_str_object_find(value, strlen(value), "name", c->metadata->default_audio_source, sizeof(c->metadata->default_audio_source)) < 0) value = NULL; } if (value == NULL) c->metadata->default_audio_source[0] = '\0'; } } else { if ((o = find_id(c, id, true)) == NULL) return -EINVAL; switch (o->type) { case INTERFACE_Node: uuid = client_make_uuid(o->serial, false); break; case INTERFACE_Port: uuid = jack_port_uuid_generate(o->serial); break; default: return -EINVAL; } update_property(c, uuid, key, type, value); } return 0; } static const struct pw_metadata_events metadata_events = { PW_VERSION_METADATA_EVENTS, .property = metadata_property }; static void metadata_proxy_removed(void *data) { struct client *c = data; pw_proxy_destroy((struct pw_proxy*)c->metadata->proxy); } static void metadata_proxy_destroy(void *data) { struct client *c = data; spa_hook_remove(&c->metadata->proxy_listener); spa_hook_remove(&c->metadata->listener); c->metadata = NULL; } static const struct pw_proxy_events metadata_proxy_events = { PW_VERSION_PROXY_EVENTS, .removed = metadata_proxy_removed, .destroy = metadata_proxy_destroy, }; static void settings_proxy_removed(void *data) { struct client *c = data; pw_proxy_destroy((struct pw_proxy*)c->settings->proxy); } static void settings_proxy_destroy(void *data) { struct client *c = data; spa_hook_remove(&c->settings->proxy_listener); c->settings = NULL; } static const struct pw_proxy_events settings_proxy_events = { PW_VERSION_PROXY_EVENTS, .removed = settings_proxy_removed, .destroy = settings_proxy_destroy, }; static void proxy_removed(void *data) { struct object *o = data; pw_proxy_destroy(o->proxy); } static void proxy_destroy(void *data) { struct object *o = data; spa_hook_remove(&o->proxy_listener); spa_hook_remove(&o->object_listener); o->proxy = NULL; } static const struct pw_proxy_events proxy_events = { PW_VERSION_PROXY_EVENTS, .removed = proxy_removed, .destroy = proxy_destroy, }; static bool node_is_active(struct client *c, struct object *n) { return !n->node.is_jack || (c->node_id == n->id ? c->active : n->node.is_running); } static void node_info(void *data, const struct pw_node_info *info) { struct object *n = data; struct client *c = n->client; bool active; if (info->change_mask & PW_NODE_CHANGE_MASK_PROPS) { /* JACK clients always need ALWAYS_PROCESS=true or else they don't * conform to the JACK API. We would try to hide the ports of * PAUSED JACK clients, for example, even if they are active. */ const char *str = spa_dict_lookup(info->props, PW_KEY_NODE_ALWAYS_PROCESS); n->node.is_jack = str ? spa_atob(str) : false; } n->node.is_running = info->state == PW_NODE_STATE_RUNNING; active = node_is_active(c, n); pw_log_debug("DSP node %d %08"PRIx64" jack:%u state change %s running:%d", info->id, info->change_mask, n->node.is_jack, pw_node_state_as_string(info->state), n->node.is_running); if (info->change_mask & PW_NODE_CHANGE_MASK_STATE) { struct object *p, *l; spa_list_for_each(p, &c->context.objects, link) { if (p->type != INTERFACE_Port || p->removed || p->port.node_id != info->id) continue; if (active) queue_notify(c, NOTIFY_TYPE_PORTREGISTRATION, p, 1, NULL); else { spa_list_for_each(l, &c->context.objects, link) { if (l->type != INTERFACE_Link || l->removed || (l->port_link.src_serial != p->serial && l->port_link.dst_serial != p->serial)) continue; queue_notify(c, NOTIFY_TYPE_CONNECT, l, 0, NULL); } queue_notify(c, NOTIFY_TYPE_PORTREGISTRATION, p, 0, NULL); } } } } static const struct pw_node_events node_events = { PW_VERSION_NODE_EVENTS, .info = node_info, }; #define FILTER_NAME " ()[].:*$" #define FILTER_PORT " ()[].*$" static void filter_name(char *str, const char *filter, char filter_char) { char *p; for (p = str; *p; p++) { if (strchr(filter, *p) != NULL) *p = filter_char; } } static int update_port_name(struct object *o, const char *name) { struct object *ot = o->port.node, *op; struct client *c = o->client; char tmp[REAL_JACK_PORT_NAME_SIZE+1]; char port_name[REAL_JACK_PORT_NAME_SIZE+1]; if (o->port.is_monitor && !c->merge_monitor) snprintf(tmp, sizeof(tmp), "%.*s%s:%s", (int)(JACK_CLIENT_NAME_SIZE-(sizeof(MONITOR_EXT)-1)), ot->node.name, MONITOR_EXT, name); else snprintf(tmp, sizeof(tmp), "%s:%s", ot->node.name, name); if (c->filter_name) filter_name(tmp, FILTER_PORT, c->filter_char); op = find_port_by_name(c, tmp); if (op != NULL && op != o) snprintf(port_name, sizeof(port_name), "%.*s-%u", (int)(sizeof(tmp)-11), tmp, o->serial); else snprintf(port_name, sizeof(port_name), "%s", tmp); if (spa_streq(port_name, o->port.name)) return 0; strcpy(o->port.old_name, o->port.name); strcpy(o->port.name, port_name); return 1; } static void port_info(void *data, const struct pw_port_info *info) { struct object *o = data; struct client *c = o->client; if (info->change_mask & PW_PORT_CHANGE_MASK_PROPS) { const char *str = spa_dict_lookup(info->props, PW_KEY_PORT_NAME); if (str != NULL) { if (update_port_name(o, str) > 0) { pw_log_info("%p: port rename %u %s->%s", c, o->serial, o->port.old_name, o->port.name); queue_notify(c, NOTIFY_TYPE_PORT_RENAME, o, 1, NULL); } } } } static void port_param(void *data, int seq, uint32_t id, uint32_t index, uint32_t next, const struct spa_pod *param) { struct object *o = data; switch (id) { case SPA_PARAM_Latency: { struct spa_latency_info info; if (spa_latency_parse(param, &info) < 0) return; o->port.latency[info.direction] = info; break; } default: break; } } static const struct pw_port_events port_events = { PW_VERSION_PORT_EVENTS, .info = port_info, .param = port_param, }; static void registry_event_global(void *data, uint32_t id, uint32_t permissions, const char *type, uint32_t version, const struct spa_dict *props) { struct client *c = (struct client *) data; struct object *o, *ot; const char *str; bool do_emit = true, do_sync = false; uint32_t serial; const char *app; if (props == NULL) return; str = spa_dict_lookup(props, PW_KEY_OBJECT_SERIAL); if (!spa_atou32(str, &serial, 0)) serial = SPA_ID_INVALID; pw_log_debug("new %s id:%u serial:%u", type, id, serial); if (spa_streq(type, PW_TYPE_INTERFACE_Client)) { app = spa_dict_lookup(props, PW_KEY_APP_NAME); if ((str = spa_dict_lookup(props, PW_KEY_SEC_PID)) != NULL) { pw_log_debug("%p: pid of \"%s\" is \"%s\"", c, app, str); } else { pw_log_debug("%p: pid of \"%s\" is unknown", c, app); } o = alloc_object(c, INTERFACE_Client); if (o == NULL) goto exit; o->pwclient.pid = (int32_t)atoi(str); snprintf(o->pwclient.name, sizeof(o->pwclient.name), "%s", app); pw_log_debug("%p: add pw client %d (%s) pid %llu", c, id, app, (unsigned long long)o->pwclient.pid); pthread_mutex_lock(&c->context.lock); spa_list_append(&c->context.objects, &o->link); pthread_mutex_unlock(&c->context.lock); } else if (spa_streq(type, PW_TYPE_INTERFACE_Node)) { const char *node_name; char tmp[JACK_CLIENT_NAME_SIZE+1]; o = alloc_object(c, INTERFACE_Node); if (o == NULL) goto exit; if ((str = spa_dict_lookup(props, PW_KEY_CLIENT_ID)) != NULL) o->node.client_id = atoi(str); node_name = spa_dict_lookup(props, PW_KEY_NODE_NAME); snprintf(o->node.node_name, sizeof(o->node.node_name), "%s", node_name); app = spa_dict_lookup(props, PW_KEY_APP_NAME); if (c->short_name) { str = spa_dict_lookup(props, PW_KEY_NODE_NICK); if (str == NULL) str = spa_dict_lookup(props, PW_KEY_NODE_DESCRIPTION); } else { str = spa_dict_lookup(props, PW_KEY_NODE_DESCRIPTION); if (str == NULL) str = spa_dict_lookup(props, PW_KEY_NODE_NICK); } if (str == NULL) str = node_name; if (str == NULL) str = spa_dict_lookup(props, PW_KEY_OBJECT_PATH); if (str == NULL) str = "node"; if (app && !spa_streq(app, str)) snprintf(tmp, sizeof(tmp), "%s/%s", app, str); else snprintf(tmp, sizeof(tmp), "%s", str); if (c->filter_name) filter_name(tmp, FILTER_NAME, c->filter_char); ot = find_node(c, tmp); if (ot != NULL && o->node.client_id != ot->node.client_id) { snprintf(o->node.name, sizeof(o->node.name), "%.*s-%d", (int)(sizeof(tmp)-11), tmp, id); } else { do_emit = ot == NULL; snprintf(o->node.name, sizeof(o->node.name), "%s", tmp); } if (id == c->node_id) { pw_log_debug("%p: add our node %d", c, id); snprintf(c->name, sizeof(c->name), "%s", o->node.name); c->object = o; c->serial = serial; } if ((str = spa_dict_lookup(props, PW_KEY_PRIORITY_SESSION)) != NULL) o->node.priority = pw_properties_parse_int(str); if ((str = spa_dict_lookup(props, PW_KEY_CLIENT_API)) != NULL) o->node.is_jack = spa_streq(str, "jack"); pw_log_debug("%p: add node %d", c, id); if (o->node.is_jack) { o->proxy = pw_registry_bind(c->registry, id, type, PW_VERSION_NODE, 0); if (o->proxy) { pw_proxy_add_listener(o->proxy, &o->proxy_listener, &proxy_events, o); pw_proxy_add_object_listener(o->proxy, &o->object_listener, &node_events, o); do_sync = true; } } pthread_mutex_lock(&c->context.lock); spa_list_append(&c->context.objects, &o->link); pthread_mutex_unlock(&c->context.lock); } else if (spa_streq(type, PW_TYPE_INTERFACE_Port)) { const struct spa_dict_item *item; unsigned long flags = 0; jack_port_type_id_t type_id; uint32_t node_id; bool is_monitor = false; char tmp[REAL_JACK_PORT_NAME_SIZE+1]; const char *name; if ((str = spa_dict_lookup(props, PW_KEY_FORMAT_DSP)) == NULL) str = "other"; if ((type_id = string_to_type(str)) == SPA_ID_INVALID) goto exit; if ((str = spa_dict_lookup(props, PW_KEY_NODE_ID)) == NULL) goto exit; node_id = atoi(str); if ((str = spa_dict_lookup(props, PW_KEY_PORT_EXTRA)) != NULL && spa_strstartswith(str, "jack:flags:")) flags = atoi(str+11); if ((name = spa_dict_lookup(props, PW_KEY_PORT_NAME)) == NULL) goto exit; if (type_id == TYPE_ID_UMP && c->flag_midi2) flags |= JackPortIsMIDI2; spa_dict_for_each(item, props) { if (spa_streq(item->key, PW_KEY_PORT_DIRECTION)) { if (spa_streq(item->value, "in")) flags |= JackPortIsInput; else if (spa_streq(item->value, "out")) flags |= JackPortIsOutput; } else if (spa_streq(item->key, PW_KEY_PORT_PHYSICAL)) { if (pw_properties_parse_bool(item->value)) flags |= JackPortIsPhysical; } else if (spa_streq(item->key, PW_KEY_PORT_TERMINAL)) { if (pw_properties_parse_bool(item->value)) flags |= JackPortIsTerminal; } else if (spa_streq(item->key, PW_KEY_PORT_CONTROL)) { if (pw_properties_parse_bool(item->value)) type_id = TYPE_ID_MIDI; } else if (spa_streq(item->key, PW_KEY_PORT_MONITOR)) { is_monitor = pw_properties_parse_bool(item->value); } } if (is_monitor && !c->show_monitor) goto exit; if (TYPE_ID_IS_EVENT(type_id) && !c->show_midi) goto exit; o = NULL; if (node_id == c->node_id) { snprintf(tmp, sizeof(tmp), "%s:%s", c->name, name); o = find_port_by_name(c, tmp); if (o != NULL) pw_log_info("%p: %s found our port %p", c, tmp, o); } if (o == NULL) { if ((ot = find_type(c, node_id, INTERFACE_Node, true)) == NULL) goto exit; o = alloc_object(c, INTERFACE_Port); if (o == NULL) goto exit; o->port.system_id = 0; o->port.priority = ot->node.priority; o->port.node = ot; o->port.latency[SPA_DIRECTION_INPUT] = SPA_LATENCY_INFO(SPA_DIRECTION_INPUT); o->port.latency[SPA_DIRECTION_OUTPUT] = SPA_LATENCY_INFO(SPA_DIRECTION_OUTPUT); o->port.type_id = type_id; do_emit = node_is_active(c, ot); o->proxy = pw_registry_bind(c->registry, id, type, PW_VERSION_PORT, 0); if (o->proxy) { uint32_t ids[1] = { SPA_PARAM_Latency }; pw_proxy_add_listener(o->proxy, &o->proxy_listener, &proxy_events, o); pw_proxy_add_object_listener(o->proxy, &o->object_listener, &port_events, o); if (type_is_dsp(type_id)) pw_port_subscribe_params((struct pw_port*)o->proxy, ids, 1); do_sync = true; } pthread_mutex_lock(&c->context.lock); spa_list_append(&c->context.objects, &o->link); pthread_mutex_unlock(&c->context.lock); } o->port.flags = flags; o->port.node_id = node_id; o->port.is_monitor = is_monitor; if (c->fill_aliases) { if ((str = spa_dict_lookup(props, PW_KEY_OBJECT_PATH)) != NULL) snprintf(o->port.alias1, sizeof(o->port.alias1), "%s", str); if ((str = spa_dict_lookup(props, PW_KEY_PORT_ALIAS)) != NULL) snprintf(o->port.alias2, sizeof(o->port.alias2), "%s", str); } if ((str = spa_dict_lookup(props, PW_KEY_PORT_ID)) != NULL) { o->port.system_id = atoi(str); snprintf(o->port.system, sizeof(o->port.system), "system:%s_%d", flags & JackPortIsInput ? "playback" : is_monitor ? "monitor" : "capture", o->port.system_id+1); } if (node_id != c->node_id) update_port_name(o, name); pw_log_debug("%p: %p add port %d name:%s %d", c, o, id, o->port.name, type_id); } else if (spa_streq(type, PW_TYPE_INTERFACE_Link)) { struct object *p; o = alloc_object(c, INTERFACE_Link); if (o == NULL) goto exit; pthread_mutex_lock(&c->context.lock); spa_list_append(&c->context.objects, &o->link); pthread_mutex_unlock(&c->context.lock); if ((str = spa_dict_lookup(props, PW_KEY_LINK_OUTPUT_PORT)) == NULL) goto exit_free; o->port_link.src = pw_properties_parse_int(str); if ((p = find_type(c, o->port_link.src, INTERFACE_Port, true)) == NULL) goto exit_free; o->port_link.src_serial = p->serial; o->port_link.src_ours = p->port.port != NULL && p->port.port->client == c; if (o->port_link.src_ours) o->port_link.our_output = p->port.port; if ((str = spa_dict_lookup(props, PW_KEY_LINK_INPUT_PORT)) == NULL) goto exit_free; o->port_link.dst = pw_properties_parse_int(str); if ((p = find_type(c, o->port_link.dst, INTERFACE_Port, true)) == NULL) goto exit_free; o->port_link.dst_serial = p->serial; o->port_link.dst_ours = p->port.port != NULL && p->port.port->client == c; if (o->port_link.dst_ours) o->port_link.our_input = p->port.port; if (o->port_link.our_input != NULL && o->port_link.our_output != NULL) { struct mix *mix; mix = find_port_peer(o->port_link.our_output, o->port_link.dst); if (mix != NULL) mix->peer_port = o->port_link.our_input; mix = find_port_peer(o->port_link.our_input, o->port_link.src); if (mix != NULL) mix->peer_port = o->port_link.our_output; } pw_log_debug("%p: add link %d %u/%u->%u/%u", c, id, o->port_link.src, o->port_link.src_serial, o->port_link.dst, o->port_link.dst_serial); } else if (spa_streq(type, PW_TYPE_INTERFACE_Metadata)) { struct pw_proxy *proxy; if (c->metadata != NULL) goto exit; if ((str = spa_dict_lookup(props, PW_KEY_METADATA_NAME)) == NULL) goto exit; if (spa_streq(str, "default")) { proxy = pw_registry_bind(c->registry, id, type, PW_VERSION_METADATA, sizeof(struct metadata)); c->metadata = pw_proxy_get_user_data(proxy); c->metadata->proxy = (struct pw_metadata*)proxy; c->metadata->default_audio_sink[0] = '\0'; c->metadata->default_audio_source[0] = '\0'; pw_proxy_add_listener(proxy, &c->metadata->proxy_listener, &metadata_proxy_events, c); pw_metadata_add_listener(c->metadata->proxy, &c->metadata->listener, &metadata_events, c); do_sync = true; } else if (spa_streq(str, "settings")) { proxy = pw_registry_bind(c->registry, id, type, PW_VERSION_METADATA, sizeof(struct metadata)); c->settings = pw_proxy_get_user_data(proxy); c->settings->proxy = (struct pw_metadata*)proxy; pw_proxy_add_listener(proxy, &c->settings->proxy_listener, &settings_proxy_events, c); do_sync = true; } goto exit; } else { goto exit; } o->id = id; o->serial = serial; switch (o->type) { case INTERFACE_Node: pw_log_info("%p: client added \"%s\" emit:%d", c, o->node.name, do_emit); if (do_emit) queue_notify(c, NOTIFY_TYPE_REGISTRATION, o, 1, NULL); break; case INTERFACE_Port: pw_log_info("%p: port added %u/%u \"%s\" emit:%d", c, o->id, o->serial, o->port.name, do_emit); if (do_emit) queue_notify(c, NOTIFY_TYPE_PORTREGISTRATION, o, 1, NULL); break; case INTERFACE_Link: pw_log_info("%p: link %u %u/%u -> %u/%u added", c, o->id, o->port_link.src, o->port_link.src_serial, o->port_link.dst, o->port_link.dst_serial); if (do_emit) queue_notify(c, NOTIFY_TYPE_CONNECT, o, 1, NULL); break; } exit: if (do_sync) c->pending_sync = pw_proxy_sync((struct pw_proxy*)c->core, c->pending_sync); return; exit_free: free_object(c, o); return; } static void registry_event_global_remove(void *data, uint32_t id) { struct client *c = (struct client *) data; struct object *o; pw_log_debug("%p: removed: %u", c, id); if ((o = find_id(c, id, true)) == NULL) return; if (o->proxy) { pw_proxy_destroy(o->proxy); o->proxy = NULL; } o->removing = true; switch (o->type) { case INTERFACE_Client: free_object(c, o); break; case INTERFACE_Node: if (c->metadata) { if (spa_streq(o->node.node_name, c->metadata->default_audio_sink)) c->metadata->default_audio_sink[0] = '\0'; if (spa_streq(o->node.node_name, c->metadata->default_audio_source)) c->metadata->default_audio_source[0] = '\0'; } if (find_node(c, o->node.name) == NULL) { pw_log_info("%p: client %u removed \"%s\"", c, o->id, o->node.name); queue_notify(c, NOTIFY_TYPE_REGISTRATION, o, 0, NULL); } else { free_object(c, o); } break; case INTERFACE_Port: pw_log_info("%p: port %u/%u removed \"%s\"", c, o->id, o->serial, o->port.name); queue_notify(c, NOTIFY_TYPE_PORTREGISTRATION, o, 0, NULL); break; case INTERFACE_Link: if (find_type(c, o->port_link.src, INTERFACE_Port, true) != NULL && find_type(c, o->port_link.dst, INTERFACE_Port, true) != NULL) { pw_log_info("%p: link %u %u/%u -> %u/%u removed", c, o->id, o->port_link.src, o->port_link.src_serial, o->port_link.dst, o->port_link.dst_serial); queue_notify(c, NOTIFY_TYPE_CONNECT, o, 0, NULL); } else { pw_log_warn("unlink between unknown ports %d and %d", o->port_link.src, o->port_link.dst); free_object(c, o); } break; } return; } static const struct pw_registry_events registry_events = { PW_VERSION_REGISTRY_EVENTS, .global = registry_event_global, .global_remove = registry_event_global_remove, }; static void varargs_parse (struct client *c, jack_options_t options, va_list ap) { if ((options & JackServerName)) c->server_name = va_arg(ap, char *); if ((options & JackLoadName)) c->load_name = va_arg(ap, char *); if ((options & JackLoadInit)) c->load_init = va_arg(ap, char *); if ((options & JackSessionID)) { char *sid = va_arg(ap, char *); if (sid) { const long long id = atoll(sid); if (id > 0) c->session_id = id; } } } static int execute_match(void *data, const char *location, const char *action, const char *val, size_t len) { struct client *client = data; if (spa_streq(action, "update-props")) pw_properties_update_string(client->props, val, len); return 1; } static struct client * g_first_client; SPA_EXPORT jack_client_t * jack_client_open (const char *client_name, jack_options_t options, jack_status_t *status_ptr, ...) { struct client *client; const struct spa_support *support; uint32_t n_support; const char *str; struct spa_cpu *cpu_iface; const struct pw_properties *props; va_list ap; jack_status_t status; if (getenv("PIPEWIRE_NOJACK") != NULL || getenv("PIPEWIRE_INTERNAL") != NULL || spa_strstartswith(pw_get_library_version(), "0.2")) goto disabled; return_val_if_fail(client_name != NULL, NULL); client = calloc(1, sizeof(struct client)); if (client == NULL) goto disabled; pw_log_info("%p: open '%s' options:%d", client, client_name, options); va_start(ap, status_ptr); varargs_parse(client, options, ap); va_end(ap); snprintf(client->name, sizeof(client->name), "pw-%s", client_name); pthread_mutex_init(&client->context.lock, NULL); spa_list_init(&client->context.objects); client->node_id = SPA_ID_INVALID; client->buffer_frames = (uint32_t)-1; client->sample_rate = (uint32_t)-1; client->latency = SPA_FRACTION(-1, -1); spa_list_init(&client->mix); spa_list_init(&client->free_mix); spa_list_init(&client->free_ports); pw_map_init(&client->ports[SPA_DIRECTION_INPUT], 32, 32); pw_map_init(&client->ports[SPA_DIRECTION_OUTPUT], 32, 32); spa_list_init(&client->links); client->driver_id = SPA_ID_INVALID; spa_list_init(&client->rt.target_links); pthread_mutex_init(&client->rt_lock, NULL); if (client->server_name != NULL && spa_streq(client->server_name, "default")) client->server_name = NULL; client->props = pw_properties_new( PW_KEY_LOOP_CANCEL, "true", SPA_KEY_THREAD_RESET_ON_FORK, "false", PW_KEY_REMOTE_NAME, client->server_name, PW_KEY_CLIENT_NAME, client_name, PW_KEY_CLIENT_API, "jack", PW_KEY_CONFIG_NAME, "jack.conf", NULL); if (client->props == NULL) goto no_props; client->context.loop = pw_thread_loop_new(client->name, NULL); if (client->context.loop == NULL) goto no_props; client->context.l = pw_thread_loop_get_loop(client->context.loop); client->context.context = pw_context_new( client->context.l, pw_properties_copy(client->props), 0); if (client->context.context == NULL) goto no_props; client->context.notify = pw_thread_loop_new(client->name, NULL); if (client->context.notify == NULL) goto no_props; client->context.nl = pw_thread_loop_get_loop(client->context.notify); globals.max_frames = client->max_frames = client->context.context->settings.clock_quantum_limit; client->notify_source = pw_loop_add_event(client->context.nl, on_notify_event, client); client->notify_buffer = calloc(1, NOTIFY_BUFFER_SIZE + sizeof(struct notify)); spa_ringbuffer_init(&client->notify_ring); pw_context_conf_update_props(client->context.context, "jack.properties", client->props); props = pw_context_get_properties(client->context.context); client->allow_mlock = pw_properties_get_bool(props, "mem.allow-mlock", true); client->warn_mlock = pw_properties_get_bool(props, "mem.warn-mlock", false); pw_context_conf_section_match_rules(client->context.context, "jack.rules", &props->dict, execute_match, client); support = pw_context_get_support(client->context.context, &n_support); client->mix_function = mix_c; cpu_iface = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_CPU); if (cpu_iface) { #if defined (__SSE__) uint32_t flags = spa_cpu_get_flags(cpu_iface); if (flags & SPA_CPU_FLAG_SSE) client->mix_function = mix_sse; #endif client->max_align = spa_cpu_get_max_align(cpu_iface); } else { client->max_align = MAX_ALIGN; } client->context.old_thread_utils = pw_context_get_object(client->context.context, SPA_TYPE_INTERFACE_ThreadUtils); if (client->context.old_thread_utils == NULL) client->context.old_thread_utils = pw_thread_utils_get(); globals.thread_utils = client->context.old_thread_utils; client->context.thread_utils.iface = SPA_INTERFACE_INIT( SPA_TYPE_INTERFACE_ThreadUtils, SPA_VERSION_THREAD_UTILS, &thread_utils_impl, client); client->loop = pw_context_get_data_loop(client->context.context); client->l = pw_data_loop_get_loop(client->loop); pw_data_loop_stop(client->loop); pw_context_set_object(client->context.context, SPA_TYPE_INTERFACE_ThreadUtils, &client->context.thread_utils); pw_thread_loop_start(client->context.loop); pw_thread_loop_lock(client->context.loop); client->core = pw_context_connect(client->context.context, pw_properties_copy(client->props), 0); if (client->core == NULL) goto server_failed; client->pool = pw_core_get_mempool(client->core); pw_core_add_listener(client->core, &client->core_listener, &core_events, client); client->registry = pw_core_get_registry(client->core, PW_VERSION_REGISTRY, 0); pw_registry_add_listener(client->registry, &client->registry_listener, ®istry_events, client); if ((str = getenv("PIPEWIRE_PROPS")) != NULL) pw_properties_update_string(client->props, str, strlen(str)); if ((str = getenv("PIPEWIRE_QUANTUM")) != NULL) { struct spa_fraction q; if (sscanf(str, "%u/%u", &q.num, &q.denom) == 2 && q.denom != 0) { pw_properties_setf(client->props, PW_KEY_NODE_FORCE_RATE, "%u", q.denom); pw_properties_setf(client->props, PW_KEY_NODE_FORCE_QUANTUM, "%u", q.num); } else { pw_log_warn("invalid PIPEWIRE_QUANTUM: %s", str); } } if ((str = getenv("PIPEWIRE_LATENCY")) != NULL) pw_properties_set(client->props, PW_KEY_NODE_LATENCY, str); if ((str = getenv("PIPEWIRE_RATE")) != NULL) pw_properties_set(client->props, PW_KEY_NODE_RATE, str); if ((str = getenv("PIPEWIRE_LINK_PASSIVE")) != NULL) pw_properties_set(client->props, "jack.passive-links", str); if ((str = pw_properties_get(client->props, PW_KEY_NODE_LATENCY)) != NULL) { uint32_t num, denom; if (sscanf(str, "%u/%u", &num, &denom) == 2 && denom != 0) { client->latency = SPA_FRACTION(num, denom); } } if (pw_properties_get(client->props, PW_KEY_NODE_NAME) == NULL) pw_properties_set(client->props, PW_KEY_NODE_NAME, client_name); if (pw_properties_get(client->props, PW_KEY_NODE_GROUP) == NULL) pw_properties_setf(client->props, PW_KEY_NODE_GROUP, "group.dsp.0"); if (pw_properties_get(client->props, PW_KEY_NODE_DESCRIPTION) == NULL) pw_properties_set(client->props, PW_KEY_NODE_DESCRIPTION, client_name); if (pw_properties_get(client->props, PW_KEY_MEDIA_TYPE) == NULL) pw_properties_set(client->props, PW_KEY_MEDIA_TYPE, "Audio"); if (pw_properties_get(client->props, PW_KEY_MEDIA_CATEGORY) == NULL) pw_properties_set(client->props, PW_KEY_MEDIA_CATEGORY, "Duplex"); if (pw_properties_get(client->props, PW_KEY_MEDIA_ROLE) == NULL) pw_properties_set(client->props, PW_KEY_MEDIA_ROLE, "DSP"); if (pw_properties_get(client->props, PW_KEY_NODE_ALWAYS_PROCESS) == NULL) pw_properties_set(client->props, PW_KEY_NODE_ALWAYS_PROCESS, "true"); if (pw_properties_get(client->props, PW_KEY_NODE_LOCK_QUANTUM) == NULL) pw_properties_set(client->props, PW_KEY_NODE_LOCK_QUANTUM, "true"); pw_properties_set(client->props, PW_KEY_NODE_TRANSPORT_SYNC, "true"); client->node = pw_core_create_object(client->core, "client-node", PW_TYPE_INTERFACE_ClientNode, PW_VERSION_CLIENT_NODE, &client->props->dict, 0); if (client->node == NULL) goto init_failed; pw_client_node_add_listener(client->node, &client->node_listener, &client_node_events, client); pw_proxy_add_listener((struct pw_proxy*)client->node, &client->proxy_listener, &node_proxy_events, client); client->info = SPA_NODE_INFO_INIT(); client->info.max_input_ports = UINT32_MAX; client->info.max_output_ports = UINT32_MAX; client->info.change_mask = SPA_NODE_CHANGE_MASK_FLAGS | SPA_NODE_CHANGE_MASK_PROPS; client->info.flags = SPA_NODE_FLAG_RT; client->info.props = &client->props->dict; pw_client_node_update(client->node, PW_CLIENT_NODE_UPDATE_INFO, 0, NULL, &client->info); client->info.change_mask = 0; client->show_monitor = pw_properties_get_bool(client->props, "jack.show-monitor", true); client->show_midi = pw_properties_get_bool(client->props, "jack.show-midi", true); client->merge_monitor = pw_properties_get_bool(client->props, "jack.merge-monitor", true); client->short_name = pw_properties_get_bool(client->props, "jack.short-name", false); client->filter_name = pw_properties_get_bool(client->props, "jack.filter-name", false); client->passive_links = pw_properties_get_bool(client->props, "jack.passive-links", false); client->filter_char = ' '; if ((str = pw_properties_get(client->props, "jack.filter-char")) != NULL && str[0] != '\0') client->filter_char = str[0]; client->locked_process = pw_properties_get_bool(client->props, "jack.locked-process", true); client->default_as_system = pw_properties_get_bool(client->props, "jack.default-as-system", false); client->fix_midi_events = pw_properties_get_bool(client->props, "jack.fix-midi-events", true); client->global_buffer_size = pw_properties_get_bool(client->props, "jack.global-buffer-size", false); client->global_sample_rate = pw_properties_get_bool(client->props, "jack.global-sample-rate", false); client->max_ports = pw_properties_get_uint32(client->props, "jack.max-client-ports", MAX_CLIENT_PORTS); client->fill_aliases = pw_properties_get_bool(client->props, "jack.fill-aliases", false); client->writable_input = pw_properties_get_bool(client->props, "jack.writable-input", true); client->async = pw_properties_get_bool(client->props, PW_KEY_NODE_ASYNC, false); client->flag_midi2 = pw_properties_get_bool(client->props, "jack.flag-midi2", false); client->self_connect_mode = SELF_CONNECT_ALLOW; if ((str = pw_properties_get(client->props, "jack.self-connect-mode")) != NULL) { if (spa_streq(str, "fail-external")) client->self_connect_mode = SELF_CONNECT_FAIL_EXT; else if (spa_streq(str, "ignore-external")) client->self_connect_mode = SELF_CONNECT_IGNORE_EXT; else if (spa_streq(str, "fail-all")) client->self_connect_mode = SELF_CONNECT_FAIL_ALL; else if (spa_streq(str, "ignore-all")) client->self_connect_mode = SELF_CONNECT_IGNORE_ALL; } client->other_connect_mode = OTHER_CONNECT_ALLOW; if ((str = pw_properties_get(client->props, "jack.other-connect-mode")) != NULL) { if (spa_streq(str, "fail")) client->other_connect_mode = OTHER_CONNECT_FAIL; else if (spa_streq(str, "ignore")) client->other_connect_mode = OTHER_CONNECT_IGNORE; } client->rt_max = pw_properties_get_int32(client->props, "rt.prio", DEFAULT_RT_MAX); status = 0; if (status_ptr) *status_ptr = status; client->pending_sync = pw_proxy_sync((struct pw_proxy*)client->core, client->pending_sync); while (true) { pw_thread_loop_wait(client->context.loop); if (client->last_res < 0) goto init_failed; if (client->pending_sync == client->last_sync) break; } if (!spa_streq(client->name, client_name)) { status |= JackNameNotUnique; if (status_ptr) *status_ptr = status; if (options & JackUseExactName) goto exit_unlock; } pw_thread_loop_unlock(client->context.loop); if (g_first_client == NULL) g_first_client = client; pw_thread_loop_start(client->context.notify); pw_log_info("%p: opened", client); return (jack_client_t *)client; no_props: status = JackFailure | JackInitFailure; if (status_ptr) *status_ptr = status; goto exit; init_failed: status = JackFailure | JackInitFailure; if (status_ptr) *status_ptr = status; goto exit_unlock; server_failed: status = JackFailure | JackServerFailed; if (status_ptr) *status_ptr = status; goto exit_unlock; exit_unlock: pw_thread_loop_unlock(client->context.loop); exit: pw_log_info("%p: error %d", client, status); jack_client_close((jack_client_t *) client); return NULL; disabled: pw_log_warn("JACK is disabled"); status = JackFailure | JackInitFailure; if (status_ptr) *status_ptr = status; return NULL; } SPA_EXPORT jack_client_t * jack_client_new (const char *client_name) { jack_options_t options = JackUseExactName; jack_status_t status; if (getenv("JACK_START_SERVER") == NULL) options |= JackNoStartServer; return jack_client_open(client_name, options, &status, NULL); } SPA_EXPORT int jack_client_close (jack_client_t *client) { struct client *c = (struct client *) client; struct object *o; union pw_map_item *item; struct mix *m, *tm; struct port *p, *tp; int res; return_val_if_fail(c != NULL, -EINVAL); pw_log_info("%p: close", client); if (g_first_client == c) g_first_client = NULL; c->destroyed = true; res = jack_deactivate(client); clean_transport(c); if (c->context.loop) { pw_loop_invoke(c->context.l, NULL, 0, NULL, 0, false, c); pw_thread_loop_stop(c->context.loop); } if (c->context.notify) { queue_notify(c, NOTIFY_TYPE_REGISTRATION, c->object, 0, NULL); pw_loop_invoke(c->context.nl, NULL, 0, NULL, 0, false, c); pw_thread_loop_stop(c->context.notify); } if (c->registry) { spa_hook_remove(&c->registry_listener); pw_proxy_destroy((struct pw_proxy*)c->registry); } if (c->metadata && c->metadata->proxy) { pw_proxy_destroy((struct pw_proxy*)c->metadata->proxy); } if (c->settings && c->settings->proxy) { pw_proxy_destroy((struct pw_proxy*)c->settings->proxy); } if (c->core) { spa_hook_remove(&c->core_listener); pw_core_disconnect(c->core); } globals.thread_utils = pw_thread_utils_get(); if (c->context.context) pw_context_destroy(c->context.context); if (c->notify_source) pw_loop_destroy_source(c->context.nl, c->notify_source); free(c->notify_buffer); if (c->context.loop) pw_thread_loop_destroy(c->context.loop); if (c->context.notify) pw_thread_loop_destroy(c->context.notify); pw_log_debug("%p: free", client); pw_array_for_each(item, &c->ports[SPA_DIRECTION_OUTPUT].items) { if (pw_map_item_is_free(item)) continue; free_port(c, item->data, false); } pw_array_for_each(item, &c->ports[SPA_DIRECTION_INPUT].items) { if (pw_map_item_is_free(item)) continue; free_port(c, item->data, false); } pthread_mutex_lock(&globals.lock); spa_list_consume(o, &c->context.objects, link) { bool to_free = o->to_free; spa_list_remove(&o->link); memset(o, 0, sizeof(struct object)); o->to_free = to_free; spa_list_append(&globals.free_objects, &o->link); } pthread_mutex_unlock(&globals.lock); spa_list_for_each_safe(m, tm, &c->free_mix, link) { if (!m->to_free) spa_list_remove(&m->link); } spa_list_consume(m, &c->free_mix, link) { spa_list_remove(&m->link); free(m); } spa_list_for_each_safe(p, tp, &c->free_ports, link) { if (!p->to_free) spa_list_remove(&p->link); } spa_list_consume(p, &c->free_ports, link) { spa_list_remove(&p->link); free(p); } pw_map_clear(&c->ports[SPA_DIRECTION_INPUT]); pw_map_clear(&c->ports[SPA_DIRECTION_OUTPUT]); pthread_mutex_destroy(&c->context.lock); pthread_mutex_destroy(&c->rt_lock); pw_properties_free(c->props); free(c); return res; } SPA_EXPORT jack_intclient_t jack_internal_client_handle (jack_client_t *client, const char *client_name, jack_status_t *status) { struct client *c = (struct client *) client; return_val_if_fail(c != NULL, 0); if (status) *status = JackNoSuchClient | JackFailure; return 0; } SPA_EXPORT jack_intclient_t jack_internal_client_load (jack_client_t *client, const char *client_name, jack_options_t options, jack_status_t *status, ...) { struct client *c = (struct client *) client; return_val_if_fail(c != NULL, 0); if (status) *status = JackNoSuchClient | JackFailure; return 0; } SPA_EXPORT jack_status_t jack_internal_client_unload (jack_client_t *client, jack_intclient_t intclient) { struct client *c = (struct client *) client; return_val_if_fail(c != NULL, 0); return JackFailure | JackNoSuchClient; } SPA_EXPORT char *jack_get_internal_client_name (jack_client_t *client, jack_intclient_t intclient) { struct client *c = (struct client *) client; return_val_if_fail(c != NULL, NULL); return strdup(c->name); } SPA_EXPORT int jack_client_name_size (void) { /* The JACK API specifies that this value includes the final NULL character. */ pw_log_trace("%d", JACK_CLIENT_NAME_SIZE+1); return JACK_CLIENT_NAME_SIZE+1; } SPA_EXPORT char * jack_get_client_name (jack_client_t *client) { struct client *c = (struct client *) client; return_val_if_fail(c != NULL, NULL); return c->name; } SPA_EXPORT char *jack_get_uuid_for_client_name (jack_client_t *client, const char *client_name) { struct client *c = (struct client *) client; struct object *o; char *uuid = NULL; bool monitor; return_val_if_fail(c != NULL, NULL); return_val_if_fail(client_name != NULL, NULL); monitor = spa_strendswith(client_name, MONITOR_EXT); pthread_mutex_lock(&c->context.lock); spa_list_for_each(o, &c->context.objects, link) { if (o->type != INTERFACE_Node) continue; if (spa_streq(o->node.name, client_name) || (monitor && spa_strneq(o->node.name, client_name, strlen(client_name) - strlen(MONITOR_EXT)))) { uuid = spa_aprintf( "%" PRIu64, client_make_uuid(o->serial, monitor)); break; } } pw_log_debug("%p: name %s -> %s", client, client_name, uuid); pthread_mutex_unlock(&c->context.lock); return uuid; } SPA_EXPORT char *jack_get_client_name_by_uuid (jack_client_t *client, const char *client_uuid ) { struct client *c = (struct client *) client; struct object *o; jack_uuid_t uuid; char *name = NULL; bool monitor; return_val_if_fail(c != NULL, NULL); return_val_if_fail(client_uuid != NULL, NULL); if (jack_uuid_parse(client_uuid, &uuid) < 0) return NULL; monitor = uuid & (1 << 30); pthread_mutex_lock(&c->context.lock); spa_list_for_each(o, &c->context.objects, link) { if (o->type != INTERFACE_Node) continue; if (client_make_uuid(o->serial, monitor) == uuid) { pw_log_debug("%p: uuid %s (%"PRIu64")-> %s", client, client_uuid, uuid, o->node.name); name = spa_aprintf("%s%s", o->node.name, monitor ? MONITOR_EXT : ""); break; } } pthread_mutex_unlock(&c->context.lock); return name; } SPA_EXPORT int jack_internal_client_new (const char *client_name, const char *load_name, const char *load_init) { pw_log_warn("not implemented %s %s %s", client_name, load_name, load_init); return -ENOTSUP; } SPA_EXPORT void jack_internal_client_close (const char *client_name) { pw_log_warn("not implemented %s", client_name); } static int do_activate(struct client *c) { int res; pw_client_node_set_active(c->node, true); res = do_sync(c); return res; } static int do_emit_buffer_size(struct spa_loop *loop, bool async, uint32_t seq, const void *data, size_t size, void *user_data) { struct client *c = user_data; c->buffer_frames = c->rt.position->clock.duration; pw_log_debug("%p: emit buffersize %d", c, c->buffer_frames); c->bufsize_callback(c->buffer_frames, c->bufsize_arg); return 0; } SPA_EXPORT int jack_activate (jack_client_t *client) { struct client *c = (struct client *) client; struct object *o; int res = 0; return_val_if_fail(c != NULL, -EINVAL); pw_log_info("%p: active:%d", c, c->active); if (c->active) return 0; pw_thread_loop_lock(c->context.loop); freeze_callbacks(c); /* reemit buffer_frames */ c->buffer_frames = 0; pw_data_loop_start(c->loop); c->active = true; if ((res = do_activate(c)) < 0) goto done; c->activation->pending_new_pos = true; c->activation->pending_sync = true; spa_list_for_each(o, &c->context.objects, link) { if (o->type != INTERFACE_Port || o->port.port == NULL || o->port.port->client != c || !o->port.port->valid) continue; o->registered = 0; queue_notify(c, NOTIFY_TYPE_PORTREGISTRATION, o, 1, NULL); } done: if (res < 0) { c->active = false; pw_data_loop_stop(c->loop); } else if (SPA_LIKELY(c->bufsize_callback != NULL)) { pw_thread_loop_unlock(c->context.loop); pw_data_loop_invoke(c->loop, do_emit_buffer_size, SPA_ID_INVALID, NULL, 0, true, c); pw_thread_loop_lock(c->context.loop); } pw_log_debug("%p: activate result:%d", c, res); thaw_callbacks(c); pw_thread_loop_unlock(c->context.loop); return res; } SPA_EXPORT int jack_deactivate (jack_client_t *client) { struct object *o; struct client *c = (struct client *) client; int res; return_val_if_fail(c != NULL, -EINVAL); pw_log_info("%p: active:%d", c, c->active); if (!c->active) return 0; pw_thread_loop_lock(c->context.loop); freeze_callbacks(c); pw_data_loop_stop(c->loop); pw_client_node_set_active(c->node, false); spa_list_for_each(o, &c->context.objects, link) { if (o->type != INTERFACE_Link || o->removed) continue; if (o->port_link.src_ours || o->port_link.dst_ours) pw_registry_destroy(c->registry, o->id); } spa_list_for_each(o, &c->context.objects, link) { if (o->type != INTERFACE_Port || o->port.port == NULL || o->port.port->client != c || !o->port.port->valid) continue; queue_notify(c, NOTIFY_TYPE_PORTREGISTRATION, o, 0, NULL); } c->activation->pending_new_pos = false; c->activation->pending_sync = false; c->active = false; res = do_sync(c); thaw_callbacks(c); pw_thread_loop_unlock(c->context.loop); return res; } SPA_EXPORT int jack_get_client_pid (const char *name) { struct object *on, *oc; if (g_first_client == NULL) return 0; on = find_node(g_first_client, name); if (on == NULL) { pw_log_warn("unknown (jack-client) node \"%s\"", name); return 0; } oc = find_client(g_first_client, on->node.client_id); if (oc == NULL) { pw_log_warn("unknown (pw) client %d", (int)on->node.client_id); return 0; } pw_log_info("pid %d (%s)", (int)oc->pwclient.pid, oc->pwclient.name); return (int)oc->pwclient.pid; } SPA_EXPORT jack_native_thread_t jack_client_thread_id (jack_client_t *client) { struct client *c = (struct client *) client; return_val_if_fail(c != NULL, (pthread_t){0}); return (jack_native_thread_t)pw_data_loop_get_thread(c->loop); } SPA_EXPORT int jack_is_realtime (jack_client_t *client) { struct client *c = (struct client *) client; return_val_if_fail(c != NULL, 0); return !c->freewheeling; } SPA_EXPORT jack_nframes_t jack_thread_wait (jack_client_t *client, int status) { pw_log_error("%p: jack_thread_wait: deprecated, use jack_cycle_wait/jack_cycle_signal", client); return 0; } SPA_EXPORT jack_nframes_t jack_cycle_wait (jack_client_t* client) { struct client *c = (struct client *) client; jack_nframes_t res; return_val_if_fail(c != NULL, 0); res = cycle_wait(c); pw_log_trace("%p: result:%d", c, res); return res; } SPA_EXPORT void jack_cycle_signal (jack_client_t* client, int status) { struct client *c = (struct client *) client; return_if_fail(c != NULL); pw_log_trace("%p: status:%d", c, status); cycle_signal(c, status); } SPA_EXPORT int jack_set_process_thread(jack_client_t* client, JackThreadCallback thread_callback, void *arg) { struct client *c = (struct client *) client; return_val_if_fail(c != NULL, -EINVAL); if (c->active) { pw_log_error("%p: can't set callback on active client", c); return -EIO; } else if (c->process_callback) { pw_log_error("%p: process callback was already set", c); return -EIO; } pw_log_debug("%p: %p %p", c, thread_callback, arg); c->thread_callback = thread_callback; c->thread_arg = arg; return 0; } SPA_EXPORT int jack_set_thread_init_callback (jack_client_t *client, JackThreadInitCallback thread_init_callback, void *arg) { struct client *c = (struct client *) client; return_val_if_fail(c != NULL, -EINVAL); pw_log_debug("%p: %p %p", c, thread_init_callback, arg); c->thread_init_callback = thread_init_callback; c->thread_init_arg = arg; return 0; } SPA_EXPORT void jack_on_shutdown (jack_client_t *client, JackShutdownCallback shutdown_callback, void *arg) { struct client *c = (struct client *) client; return_if_fail(c != NULL); if (c->active) { pw_log_error("%p: can't set callback on active client", c); } else { pw_log_debug("%p: %p %p", c, shutdown_callback, arg); c->shutdown_callback = shutdown_callback; c->shutdown_arg = arg; } } SPA_EXPORT void jack_on_info_shutdown (jack_client_t *client, JackInfoShutdownCallback shutdown_callback, void *arg) { struct client *c = (struct client *) client; return_if_fail(c != NULL); if (c->active) { pw_log_error("%p: can't set callback on active client", c); } else { pw_log_debug("%p: %p %p", c, shutdown_callback, arg); c->info_shutdown_callback = shutdown_callback; c->info_shutdown_arg = arg; } } SPA_EXPORT int jack_set_process_callback (jack_client_t *client, JackProcessCallback process_callback, void *arg) { struct client *c = (struct client *) client; return_val_if_fail(c != NULL, -EINVAL); if (c->active) { pw_log_error("%p: can't set callback on active client", c); return -EIO; } else if (c->thread_callback) { pw_log_error("%p: thread callback was already set", c); return -EIO; } pw_log_debug("%p: %p %p", c, process_callback, arg); c->process_callback = process_callback; c->process_arg = arg; return 0; } SPA_EXPORT int jack_set_freewheel_callback (jack_client_t *client, JackFreewheelCallback freewheel_callback, void *arg) { struct client *c = (struct client *) client; return_val_if_fail(c != NULL, -EINVAL); if (c->active) { pw_log_error("%p: can't set callback on active client", c); return -EIO; } pw_log_debug("%p: %p %p", c, freewheel_callback, arg); c->freewheel_callback = freewheel_callback; c->freewheel_arg = arg; return 0; } SPA_EXPORT int jack_set_buffer_size_callback (jack_client_t *client, JackBufferSizeCallback bufsize_callback, void *arg) { struct client *c = (struct client *) client; return_val_if_fail(c != NULL, -EINVAL); if (c->active) { pw_log_error("%p: can't set callback on active client", c); return -EIO; } pw_log_debug("%p: %p %p", c, bufsize_callback, arg); c->bufsize_callback = bufsize_callback; c->bufsize_arg = arg; return 0; } SPA_EXPORT int jack_set_sample_rate_callback (jack_client_t *client, JackSampleRateCallback srate_callback, void *arg) { struct client *c = (struct client *) client; return_val_if_fail(c != NULL, -EINVAL); if (c->active) { pw_log_error("%p: can't set callback on active client", c); return -EIO; } pw_log_debug("%p: %p %p", c, srate_callback, arg); c->srate_callback = srate_callback; c->srate_arg = arg; if (c->srate_callback && c->sample_rate != (uint32_t)-1) c->srate_callback(c->sample_rate, c->srate_arg); return 0; } SPA_EXPORT int jack_set_client_registration_callback (jack_client_t *client, JackClientRegistrationCallback registration_callback, void *arg) { struct client *c = (struct client *) client; return_val_if_fail(c != NULL, -EINVAL); if (c->active) { pw_log_error("%p: can't set callback on active client", c); return -EIO; } pw_log_debug("%p: %p %p", c, registration_callback, arg); c->registration_callback = registration_callback; c->registration_arg = arg; return 0; } SPA_EXPORT int jack_set_port_registration_callback (jack_client_t *client, JackPortRegistrationCallback registration_callback, void *arg) { struct client *c = (struct client *) client; return_val_if_fail(c != NULL, -EINVAL); if (c->active) { pw_log_error("%p: can't set callback on active client", c); return -EIO; } pw_log_debug("%p: %p %p", c, registration_callback, arg); c->portregistration_callback = registration_callback; c->portregistration_arg = arg; return 0; } SPA_EXPORT int jack_set_port_connect_callback (jack_client_t *client, JackPortConnectCallback connect_callback, void *arg) { struct client *c = (struct client *) client; return_val_if_fail(c != NULL, -EINVAL); if (c->active) { pw_log_error("%p: can't set callback on active client", c); return -EIO; } pw_log_debug("%p: %p %p", c, connect_callback, arg); c->connect_callback = connect_callback; c->connect_arg = arg; return 0; } SPA_EXPORT int jack_set_port_rename_callback (jack_client_t *client, JackPortRenameCallback rename_callback, void *arg) { struct client *c = (struct client *) client; return_val_if_fail(c != NULL, -EINVAL); if (c->active) { pw_log_error("%p: can't set callback on active client", c); return -EIO; } pw_log_debug("%p: %p %p", c, rename_callback, arg); c->rename_callback = rename_callback; c->rename_arg = arg; return 0; } SPA_EXPORT int jack_set_graph_order_callback (jack_client_t *client, JackGraphOrderCallback graph_callback, void *data) { struct client *c = (struct client *) client; return_val_if_fail(c != NULL, -EINVAL); if (c->active) { pw_log_error("%p: can't set callback on active client", c); return -EIO; } pw_log_debug("%p: %p %p", c, graph_callback, data); c->graph_callback = graph_callback; c->graph_arg = data; return 0; } SPA_EXPORT int jack_set_xrun_callback (jack_client_t *client, JackXRunCallback xrun_callback, void *arg) { struct client *c = (struct client *) client; return_val_if_fail(c != NULL, -EINVAL); if (c->active) { pw_log_error("%p: can't set callback on active client", c); return -EIO; } pw_log_debug("%p: %p %p", c, xrun_callback, arg); c->xrun_callback = xrun_callback; c->xrun_arg = arg; return 0; } SPA_EXPORT int jack_set_latency_callback (jack_client_t *client, JackLatencyCallback latency_callback, void *data) { struct client *c = (struct client *) client; return_val_if_fail(c != NULL, -EINVAL); if (c->active) { pw_log_error("%p: can't set callback on active client", c); return -EIO; } pw_log_debug("%p: %p %p", c, latency_callback, data); c->latency_callback = latency_callback; c->latency_arg = data; return 0; } SPA_EXPORT int jack_set_freewheel(jack_client_t* client, int onoff) { struct client *c = (struct client *) client; const char *str; pw_log_info("%p: freewheel %d", client, onoff); pw_thread_loop_lock(c->context.loop); str = pw_properties_get(c->props, PW_KEY_NODE_GROUP); if (str != NULL) { char *p = strstr(str, ",pipewire.freewheel"); if (p == NULL) p = strstr(str, "pipewire.freewheel"); if (p == NULL && onoff) pw_properties_setf(c->props, PW_KEY_NODE_GROUP, "%s,pipewire.freewheel", str); else if (p != NULL && !onoff) { pw_log_info("%s %d %s %.*s", p, (int)(p - str), str, (int)(p - str), str); pw_properties_setf(c->props, PW_KEY_NODE_GROUP, "%.*s", (int)(p - str), str); } } else { pw_properties_set(c->props, PW_KEY_NODE_GROUP, onoff ? "pipewire.freewheel" : ""); } c->info.change_mask |= SPA_NODE_CHANGE_MASK_PROPS; c->info.props = &c->props->dict; pw_client_node_update(c->node, PW_CLIENT_NODE_UPDATE_INFO, 0, NULL, &c->info); c->info.change_mask = 0; pw_thread_loop_unlock(c->context.loop); return 0; } SPA_EXPORT int jack_set_buffer_size (jack_client_t *client, jack_nframes_t nframes) { struct client *c = (struct client *) client; return_val_if_fail(c != NULL, -EINVAL); pw_log_info("%p: buffer-size %u", client, nframes); pw_thread_loop_lock(c->context.loop); if (c->global_buffer_size && c->settings && c->settings->proxy) { char val[256]; snprintf(val, sizeof(val), "%u", nframes == 1 ? 0: nframes); pw_metadata_set_property(c->settings->proxy, 0, "clock.force-quantum", "", val); } else { pw_properties_setf(c->props, PW_KEY_NODE_FORCE_QUANTUM, "%u", nframes); c->info.change_mask |= SPA_NODE_CHANGE_MASK_PROPS; c->info.props = &c->props->dict; pw_client_node_update(c->node, PW_CLIENT_NODE_UPDATE_INFO, 0, NULL, &c->info); c->info.change_mask = 0; } pw_thread_loop_unlock(c->context.loop); return 0; } SPA_EXPORT int jack_set_sample_rate (jack_client_t *client, jack_nframes_t nframes) { struct client *c = (struct client *) client; return_val_if_fail(c != NULL, -EINVAL); pw_log_info("%p: sample-size %u", client, nframes); pw_thread_loop_lock(c->context.loop); if (c->global_sample_rate && c->settings && c->settings->proxy) { char val[256]; snprintf(val, sizeof(val), "%u", nframes); pw_metadata_set_property(c->settings->proxy, 0, "clock.force-rate", "", val); } else { pw_properties_setf(c->props, PW_KEY_NODE_FORCE_RATE, "%u", nframes); c->info.change_mask |= SPA_NODE_CHANGE_MASK_PROPS; c->info.props = &c->props->dict; pw_client_node_update(c->node, PW_CLIENT_NODE_UPDATE_INFO, 0, NULL, &c->info); c->info.change_mask = 0; } pw_thread_loop_unlock(c->context.loop); return 0; } SPA_EXPORT jack_nframes_t jack_get_sample_rate (jack_client_t *client) { struct client *c = (struct client *) client; jack_nframes_t res = -1; return_val_if_fail(c != NULL, 0); if (!c->active) res = c->latency.denom; if (c->active || res == (uint32_t)-1) { res = c->sample_rate; if (res == (uint32_t)-1) { if (c->rt.position) res = c->rt.position->clock.rate.denom; else if (c->position) res = c->position->clock.rate.denom; } } c->sample_rate = res; pw_log_trace_fp("sample_rate: %u", res); return res; } SPA_EXPORT jack_nframes_t jack_get_buffer_size (jack_client_t *client) { struct client *c = (struct client *) client; jack_nframes_t res = -1; return_val_if_fail(c != NULL, 0); if (!c->active) res = c->latency.num; if (c->active || res == (uint32_t)-1) { res = c->buffer_frames; if (res == (uint32_t)-1) { if (c->rt.position) res = c->rt.position->clock.duration; else if (c->position) res = c->position->clock.duration; } } c->buffer_frames = res; pw_log_debug("buffer_frames: %u", res); return res; } SPA_EXPORT int jack_engine_takeover_timebase (jack_client_t *client) { pw_log_error("%p: deprecated", client); return 0; } SPA_EXPORT float jack_cpu_load (jack_client_t *client) { struct client *c = (struct client *) client; float res = 0.0f; return_val_if_fail(c != NULL, 0.0); if (c->driver_activation) res = c->driver_activation->cpu_load[0] * 100.0f; pw_log_trace("%p: cpu load %f", client, res); return res; } #include "statistics.c" static void *get_buffer_input_float(struct port *p, jack_nframes_t frames); static void *get_buffer_input_midi(struct port *p, jack_nframes_t frames); static void *get_buffer_input_empty(struct port *p, jack_nframes_t frames); static void *get_buffer_output_float(struct port *p, jack_nframes_t frames); static void *get_buffer_output_midi(struct port *p, jack_nframes_t frames); static void *get_buffer_output_empty(struct port *p, jack_nframes_t frames); SPA_EXPORT jack_port_t * jack_port_register (jack_client_t *client, const char *port_name, const char *port_type, unsigned long flags, unsigned long buffer_frames) { struct client *c = (struct client *) client; enum spa_direction direction; struct object *o; jack_port_type_id_t type_id; uint8_t buffer[1024]; struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); struct spa_pod *params[7]; uint32_t n_params = 0; struct port *p; int res, len; char name[REAL_JACK_PORT_NAME_SIZE+1]; return_val_if_fail(c != NULL, NULL); return_val_if_fail(port_name != NULL && strlen(port_name) != 0, NULL); return_val_if_fail(port_type != NULL, NULL); pw_log_info("%p: port register \"%s:%s\" \"%s\" %08lx %ld", c, c->name, port_name, port_type, flags, buffer_frames); if (flags & JackPortIsInput) direction = PW_DIRECTION_INPUT; else if (flags & JackPortIsOutput) direction = PW_DIRECTION_OUTPUT; else { pw_log_warn("invalid port flags %lu for %s", flags, port_name); return NULL; } if ((type_id = string_to_type(port_type)) == SPA_ID_INVALID) { pw_log_warn("unknown port type %s", port_type); return NULL; } if (type_id == TYPE_ID_MIDI && (flags & JackPortIsMIDI2)) type_id = TYPE_ID_UMP; len = snprintf(name, sizeof(name), "%s:%s", c->name, port_name); if (len < 0 || (size_t)len >= sizeof(name)) { pw_log_warn("%p: name \"%s:%s\" too long", c, c->name, port_name); return NULL; } pthread_mutex_lock(&c->context.lock); o = find_port_by_name(c, name); pthread_mutex_unlock(&c->context.lock); if (o != NULL) { pw_log_warn("%p: name \"%s\" already exists", c, name); return NULL; } if ((p = alloc_port(c, direction)) == NULL) { pw_log_warn("can't allocate port %s: %m", port_name); return NULL; } o = p->object; o->port.flags = flags; strcpy(o->port.name, name); o->port.type_id = type_id; init_buffer(p, c->max_frames); if (direction == SPA_DIRECTION_INPUT) { switch (type_id) { case TYPE_ID_AUDIO: case TYPE_ID_VIDEO: p->get_buffer = get_buffer_input_float; break; case TYPE_ID_MIDI: case TYPE_ID_OSC: case TYPE_ID_UMP: p->get_buffer = get_buffer_input_midi; break; default: p->get_buffer = get_buffer_input_empty; break; } } else { switch (type_id) { case TYPE_ID_AUDIO: case TYPE_ID_VIDEO: p->get_buffer = get_buffer_output_float; break; case TYPE_ID_MIDI: case TYPE_ID_OSC: case TYPE_ID_UMP: p->get_buffer = get_buffer_output_midi; break; default: p->get_buffer = get_buffer_output_empty; break; } } pw_log_debug("%p: port %p", c, p); spa_list_init(&p->mix); pw_properties_set(p->props, PW_KEY_FORMAT_DSP, type_to_string(type_id)); pw_properties_set(p->props, PW_KEY_PORT_NAME, port_name); if (flags > 0x1f) { pw_properties_setf(p->props, PW_KEY_PORT_EXTRA, "jack:flags:%lu", flags & ~0x1f); } if (flags & JackPortIsPhysical) pw_properties_set(p->props, PW_KEY_PORT_PHYSICAL, "true"); if (flags & JackPortIsTerminal) pw_properties_set(p->props, PW_KEY_PORT_TERMINAL, "true"); p->info = SPA_PORT_INFO_INIT(); p->info.change_mask |= SPA_PORT_CHANGE_MASK_FLAGS; p->info.flags = SPA_PORT_FLAG_NO_REF; p->info.change_mask |= SPA_PORT_CHANGE_MASK_PROPS; p->info.props = &p->props->dict; p->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; p->params[IDX_EnumFormat] = SPA_PARAM_INFO(SPA_PARAM_EnumFormat, SPA_PARAM_INFO_READ); p->params[IDX_Buffers] = SPA_PARAM_INFO(SPA_PARAM_Buffers, SPA_PARAM_INFO_READ); p->params[IDX_IO] = SPA_PARAM_INFO(SPA_PARAM_IO, SPA_PARAM_INFO_READ); p->params[IDX_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); p->params[IDX_Latency] = SPA_PARAM_INFO(SPA_PARAM_Latency, SPA_PARAM_INFO_READWRITE); p->info.params = p->params; p->info.n_params = N_PORT_PARAMS; param_enum_format(c, p, ¶ms[n_params++], &b); param_buffers(c, p, ¶ms[n_params++], &b); param_io(c, p, ¶ms[n_params++], &b); param_io_async(c, p, ¶ms[n_params++], &b); param_latency(c, p, ¶ms[n_params++], &b); param_latency_other(c, p, ¶ms[n_params++], &b); pw_thread_loop_lock(c->context.loop); if (create_mix(c, p, SPA_ID_INVALID, SPA_ID_INVALID) == NULL) { res = -errno; pw_log_warn("can't create mix for port %s: %m", port_name); pw_thread_loop_unlock(c->context.loop); goto error_free; } freeze_callbacks(c); pw_client_node_port_update(c->node, direction, p->port_id, PW_CLIENT_NODE_PORT_UPDATE_PARAMS | PW_CLIENT_NODE_PORT_UPDATE_INFO, n_params, (const struct spa_pod **) params, &p->info); p->info.change_mask = 0; res = do_sync(c); thaw_callbacks(c); pw_log_debug("%p: port %p done", c, p); pw_thread_loop_unlock(c->context.loop); if (res < 0) { pw_log_warn("can't create port %s: %s", port_name, spa_strerror(res)); goto error_free; } return object_to_port(o); error_free: free_port(c, p, true); return NULL; } static int do_free_port(struct spa_loop *loop, bool async, uint32_t seq, const void *data, size_t size, void *user_data) { struct port *p = user_data; struct client *c = p->client; free_port(c, p, !c->active); return 0; } static int do_invalidate_port(struct spa_loop *loop, bool async, uint32_t seq, const void *data, size_t size, void *user_data) { struct port *p = user_data; struct client *c = p->client; p->valid = false; pw_loop_invoke(c->context.l, do_free_port, 0, NULL, 0, false, p); return 0; } SPA_EXPORT int jack_port_unregister (jack_client_t *client, jack_port_t *port) { struct client *c = (struct client *) client; struct object *o = port_to_object(port); struct port *p; int res; return_val_if_fail(c != NULL, -EINVAL); return_val_if_fail(o != NULL, -EINVAL); pw_thread_loop_lock(c->context.loop); freeze_callbacks(c); p = o->port.port; if (o->type != INTERFACE_Port || p == NULL || !p->valid || o->client != c) { pw_log_error("%p: invalid port %p", client, port); res = -EINVAL; goto done; } pw_data_loop_invoke(c->loop, do_invalidate_port, 1, NULL, 0, false, p); pw_log_info("%p: port %p unregister \"%s\"", client, port, o->port.name); pw_client_node_port_update(c->node, p->direction, p->port_id, 0, 0, NULL, NULL); res = do_sync(c); if (res < 0) { pw_log_warn("can't unregister port %s: %s", o->port.name, spa_strerror(res)); } done: thaw_callbacks(c); pw_thread_loop_unlock(c->context.loop); return res; } static struct buffer *get_mix_buffer(struct client *c, struct mix *mix, jack_nframes_t frames) { struct spa_io_buffers *io; uint32_t cycle = c->rt.position->clock.cycle & 1; if (mix->peer_port != NULL) prepare_output(mix->peer_port, frames, cycle); io = mix->io[cycle]; if (io == NULL || io->status != SPA_STATUS_HAVE_DATA || io->buffer_id >= mix->n_buffers) return NULL; return &mix->buffers[io->buffer_id]; } static inline void *get_buffer_data(struct buffer *b, jack_nframes_t frames) { struct spa_data *d; uint32_t offset, size; d = &b->datas[0]; offset = SPA_MIN(d->chunk->offset, d->maxsize); size = SPA_MIN(d->chunk->size, d->maxsize - offset); if (size / sizeof(float) < frames) return NULL; return SPA_PTROFF(d->data, offset, void); } static void *get_buffer_input_float(struct port *p, jack_nframes_t frames) { struct mix *mix; struct buffer *b; void *ptr = NULL; float *mix_ptr[MAX_MIX], *np; uint32_t n_ptr = 0; bool ptr_aligned = true; struct client *c = p->client; spa_list_for_each(mix, &p->mix, port_link) { if (mix->id == SPA_ID_INVALID) continue; pw_log_trace_fp("%p: port %s mix %d.%d get buffer %d", c, p->object->port.name, p->port_id, mix->id, frames); if ((b = get_mix_buffer(c, mix, frames)) == NULL) continue; if ((np = get_buffer_data(b, frames)) == NULL) continue; if (!SPA_IS_ALIGNED(np, 16)) ptr_aligned = false; mix_ptr[n_ptr++] = np; if (n_ptr == MAX_MIX) break; } if (n_ptr == 1) { ptr = mix_ptr[0]; } else if (n_ptr > 1) { ptr = p->emptyptr; c->mix_function(ptr, mix_ptr, n_ptr, ptr_aligned, frames); p->zeroed = false; } if (ptr == NULL) ptr = init_buffer(p, frames); return ptr; } static void *get_buffer_input_midi(struct port *p, jack_nframes_t frames) { struct mix *mix; void *ptr = p->emptyptr; struct midi_buffer *mb = (struct midi_buffer*)midi_scratch; struct mix_info *mix_info[MAX_MIX]; uint32_t n_mix_info = 0; spa_list_for_each(mix, &p->mix, port_link) { struct spa_data *d; struct buffer *b; struct mix_info *mi = &mix->mix_info; struct spa_pod_sequence seq; const void *seq_body; if (mix->id == SPA_ID_INVALID) continue; pw_log_trace_fp("%p: port %p mix %d.%d get buffer %d", p->client, p, p->port_id, mix->id, frames); if ((b = get_mix_buffer(p->client, mix, frames)) == NULL) continue; d = &b->datas[0]; spa_pod_parser_init_from_data(&mi->parser, d->data, d->maxsize, d->chunk->offset, d->chunk->size); if (spa_pod_parser_push_sequence_body(&mi->parser, &mi->frame, &seq, &seq_body) < 0) continue; if (spa_pod_parser_get_control_body(&mi->parser, &mi->control, &mi->control_body) < 0) continue; mix_info[n_mix_info++] = mi; if (n_mix_info == MAX_MIX) break; } midi_init_buffer(mb, MIDI_SCRATCH_FRAMES, frames); /* first convert to a thread local scratch buffer, then memcpy into * the per port buffer. This makes it possible to call this function concurrently * but also have different pointers per port */ convert_to_event(mix_info, n_mix_info, mb, p->client->fix_midi_events, p->object->port.type_id); memcpy(ptr, mb, sizeof(struct midi_buffer) + (mb->event_count * sizeof(struct midi_event))); if (mb->write_pos > 0) { size_t offs = mb->buffer_size - mb->write_pos; memcpy(SPA_PTROFF(ptr, offs, void), SPA_PTROFF(mb, offs, void), mb->write_pos); } return ptr; } static void *get_buffer_output_float(struct port *p, jack_nframes_t frames) { void *ptr; ptr = get_buffer_output(p, frames, sizeof(float), NULL); if (SPA_UNLIKELY(p->empty_out = (ptr == NULL))) ptr = p->emptyptr; return ptr; } static void *get_buffer_output_midi(struct port *p, jack_nframes_t frames) { p->empty_out = true; return p->emptyptr; } static void *get_buffer_output_empty(struct port *p, jack_nframes_t frames) { p->empty_out = true; return p->emptyptr; } static void *get_buffer_input_empty(struct port *p, jack_nframes_t frames) { return init_buffer(p, frames); } SPA_EXPORT void * jack_port_get_buffer (jack_port_t *port, jack_nframes_t frames) { struct object *o = port_to_object(port); struct port *p = NULL; void *ptr = NULL; struct client *c; return_val_if_fail(o != NULL, NULL); c = o->client; if (o->type != INTERFACE_Port || c == NULL) goto done; if (frames > c->max_frames) goto done; if ((p = o->port.port) == NULL) { struct mix *mix; struct buffer *b; if ((mix = find_mix_peer(c, o->id)) == NULL) goto done; pw_log_trace("peer mix: %p %d", mix, mix->peer_id); if ((b = get_mix_buffer(c, mix, frames)) == NULL) goto done; if (TYPE_ID_IS_EVENT(o->port.type_id)) { struct mix_info *mix_info[1], mi; struct spa_data *d; struct spa_pod_sequence seq; const void *seq_body; ptr = midi_scratch; midi_init_buffer(ptr, MIDI_SCRATCH_FRAMES, frames); d = &b->datas[0]; spa_pod_parser_init_from_data(&mi.parser, d->data, d->maxsize, d->chunk->offset, d->chunk->size); if (spa_pod_parser_push_sequence_body(&mi.parser, &mi.frame, &seq, &seq_body) < 0) goto done; if (spa_pod_parser_get_control_body(&mi.parser, &mi.control, &mi.control_body) < 0) goto done; mix_info[0] = &mi; convert_to_event(mix_info, 1, ptr, c->fix_midi_events, o->port.type_id); } else { ptr = get_buffer_data(b, frames); } } else if (p->valid) { ptr = p->get_buffer(p, frames); } done: pw_log_trace_fp("%p: port:%p buffer:%p frames:%d", c, p, ptr, frames); return ptr; } SPA_EXPORT jack_uuid_t jack_port_uuid (const jack_port_t *port) { struct object *o = port_to_object(port); return_val_if_fail(o != NULL, 0); return jack_port_uuid_generate(o->serial); } static const char *port_name(struct object *o) { const char *name; struct client *c = o->client; if (c == NULL) return NULL; if (c->default_as_system && is_port_default(c, o)) name = o->port.system; else name = o->port.name; return name; } SPA_EXPORT const char * jack_port_name (const jack_port_t *port) { struct object *o = port_to_object(port); return_val_if_fail(o != NULL, NULL); if (o->type != INTERFACE_Port) return NULL; return port_name(o); } SPA_EXPORT const char * jack_port_short_name (const jack_port_t *port) { struct object *o = port_to_object(port); return_val_if_fail(o != NULL, NULL); if (o->type != INTERFACE_Port) return NULL; return strchr(port_name(o), ':') + 1; } SPA_EXPORT int jack_port_flags (const jack_port_t *port) { struct object *o = port_to_object(port); return_val_if_fail(o != NULL, 0); if (o->type != INTERFACE_Port) return 0; return o->port.flags; } SPA_EXPORT const char * jack_port_type (const jack_port_t *port) { struct object *o = port_to_object(port); return_val_if_fail(o != NULL, NULL); if (o->type != INTERFACE_Port) return NULL; return type_to_string(o->port.type_id); } SPA_EXPORT jack_port_type_id_t jack_port_type_id (const jack_port_t *port) { struct object *o = port_to_object(port); return_val_if_fail(o != NULL, 0); if (o->type != INTERFACE_Port) return TYPE_ID_OTHER; return o->port.type_id; } SPA_EXPORT int jack_port_is_mine (const jack_client_t *client, const jack_port_t *port) { struct object *o = port_to_object(port); return_val_if_fail(o != NULL, 0); return o->type == INTERFACE_Port && o->port.port != NULL && o->port.port->client == (struct client*)client; } SPA_EXPORT int jack_port_connected (const jack_port_t *port) { struct object *o = port_to_object(port); struct client *c; struct object *l; int res = 0; return_val_if_fail(o != NULL, 0); if (o->type != INTERFACE_Port || o->client == NULL) return 0; c = o->client; pthread_mutex_lock(&c->context.lock); spa_list_for_each(l, &c->context.objects, link) { if (l->type != INTERFACE_Link || l->removed) continue; if (l->port_link.src_serial == o->serial || l->port_link.dst_serial == o->serial) res++; } pthread_mutex_unlock(&c->context.lock); pw_log_debug("%p: id:%u/%u res:%d", port, o->id, o->serial, res); return res; } SPA_EXPORT int jack_port_connected_to (const jack_port_t *port, const char *port_name) { struct object *o = port_to_object(port); struct client *c; struct object *p, *l; int res = 0; return_val_if_fail(o != NULL, 0); return_val_if_fail(port_name != NULL, 0); if (o->type != INTERFACE_Port || o->client == NULL) return 0; c = o->client; pthread_mutex_lock(&c->context.lock); p = find_port_by_name(c, port_name); if (p == NULL) goto exit; if (GET_DIRECTION(p->port.flags) == GET_DIRECTION(o->port.flags)) goto exit; if (p->port.flags & JackPortIsOutput) { l = p; p = o; o = l; } if ((l = find_link(c, o->id, p->id)) != NULL) res = 1; exit: pthread_mutex_unlock(&c->context.lock); pw_log_debug("%p: id:%u/%u name:%s res:%d", port, o->id, o->serial, port_name, res); return res; } SPA_EXPORT const char ** jack_port_get_connections (const jack_port_t *port) { struct object *o = port_to_object(port); return_val_if_fail(o != NULL, NULL); if (o->type != INTERFACE_Port || o->client == NULL) return NULL; return jack_port_get_all_connections((jack_client_t *)o->client, port); } SPA_EXPORT const char ** jack_port_get_all_connections (const jack_client_t *client, const jack_port_t *port) { struct client *c = (struct client *) client; struct object *o = port_to_object(port); struct object *p, *l; const char **res; int count = 0; struct pw_array tmp; return_val_if_fail(c != NULL, NULL); return_val_if_fail(o != NULL, NULL); pw_array_init(&tmp, sizeof(void*) * 32); pthread_mutex_lock(&c->context.lock); spa_list_for_each(l, &c->context.objects, link) { if (l->type != INTERFACE_Link || l->removed) continue; if (l->port_link.src_serial == o->serial) p = find_type(c, l->port_link.dst, INTERFACE_Port, true); else if (l->port_link.dst_serial == o->serial) p = find_type(c, l->port_link.src, INTERFACE_Port, true); else continue; if (p == NULL) continue; pw_array_add_ptr(&tmp, (void*)port_name(p)); count++; } pthread_mutex_unlock(&c->context.lock); if (count == 0) { pw_array_clear(&tmp); res = NULL; } else { pw_array_add_ptr(&tmp, NULL); res = tmp.data; } return res; } SPA_EXPORT int jack_port_tie (jack_port_t *src, jack_port_t *dst) { struct object *s = port_to_object(src); struct object *d = port_to_object(dst); struct port *sp, *dp; sp = s->port.port; dp = d->port.port; if (sp == NULL || !sp->valid || dp == NULL || !dp->valid || sp->client != dp->client) return -EINVAL; dp->tied = sp; return 0; } SPA_EXPORT int jack_port_untie (jack_port_t *port) { struct object *o = port_to_object(port); struct port *p; p = o->port.port; if (p == NULL || !p->valid) return -EINVAL; p->tied = NULL; return 0; } SPA_EXPORT int jack_port_set_name (jack_port_t *port, const char *port_name) { pw_log_warn("deprecated"); return 0; } SPA_EXPORT int jack_port_rename (jack_client_t* client, jack_port_t *port, const char *port_name) { struct client *c = (struct client *) client; struct object *o = port_to_object(port); struct port *p; int res = 0; return_val_if_fail(c != NULL, -EINVAL); return_val_if_fail(o != NULL, -EINVAL); return_val_if_fail(port_name != NULL, -EINVAL); pw_thread_loop_lock(c->context.loop); pw_log_info("%p: port rename %p %s -> %s:%s", client, port, o->port.name, c->name, port_name); p = o->port.port; if (p == NULL || !p->valid) { res = -EINVAL; goto done; } pw_properties_set(p->props, PW_KEY_PORT_NAME, port_name); snprintf(o->port.name, sizeof(o->port.name), "%s:%s", c->name, port_name); p->info.change_mask |= SPA_PORT_CHANGE_MASK_PROPS; p->info.props = &p->props->dict; pw_client_node_port_update(c->node, p->direction, p->port_id, PW_CLIENT_NODE_PORT_UPDATE_INFO, 0, NULL, &p->info); p->info.change_mask = 0; done: pw_thread_loop_unlock(c->context.loop); return res; } SPA_EXPORT int jack_port_set_alias (jack_port_t *port, const char *alias) { struct object *o = port_to_object(port); struct client *c; struct port *p; const char *key; int res = 0; return_val_if_fail(o != NULL, -EINVAL); return_val_if_fail(alias != NULL, -EINVAL); c = o->client; if (o->type != INTERFACE_Port || c == NULL) return -EINVAL; pw_thread_loop_lock(c->context.loop); p = o->port.port; if (p == NULL || !p->valid) { res = -EINVAL; goto done; } if (o->port.alias1[0] == '\0') { key = PW_KEY_OBJECT_PATH; snprintf(o->port.alias1, sizeof(o->port.alias1), "%s", alias); } else if (o->port.alias2[0] == '\0') { key = PW_KEY_PORT_ALIAS; snprintf(o->port.alias2, sizeof(o->port.alias2), "%s", alias); } else { res = -1; goto done; } pw_properties_set(p->props, key, alias); p->info.change_mask |= SPA_PORT_CHANGE_MASK_PROPS; p->info.props = &p->props->dict; pw_client_node_port_update(c->node, p->direction, p->port_id, PW_CLIENT_NODE_PORT_UPDATE_INFO, 0, NULL, &p->info); p->info.change_mask = 0; done: pw_thread_loop_unlock(c->context.loop); return res; } SPA_EXPORT int jack_port_unset_alias (jack_port_t *port, const char *alias) { struct object *o = port_to_object(port); struct client *c; struct port *p; const char *key; int res = 0; return_val_if_fail(o != NULL, -EINVAL); return_val_if_fail(alias != NULL, -EINVAL); c = o->client; if (o->type != INTERFACE_Port || c == NULL) return -EINVAL; pw_thread_loop_lock(c->context.loop); p = o->port.port; if (p == NULL || !p->valid) { res = -EINVAL; goto done; } if (spa_streq(o->port.alias1, alias)) key = PW_KEY_OBJECT_PATH; else if (spa_streq(o->port.alias2, alias)) key = PW_KEY_PORT_ALIAS; else { res = -1; goto done; } pw_properties_set(p->props, key, NULL); p->info.change_mask |= SPA_PORT_CHANGE_MASK_PROPS; p->info.props = &p->props->dict; pw_client_node_port_update(c->node, p->direction, p->port_id, PW_CLIENT_NODE_PORT_UPDATE_INFO, 0, NULL, &p->info); p->info.change_mask = 0; done: pw_thread_loop_unlock(c->context.loop); return res; } SPA_EXPORT int jack_port_get_aliases (const jack_port_t *port, char* const aliases[2]) { struct object *o = port_to_object(port); int res = 0; return_val_if_fail(o != NULL, -EINVAL); return_val_if_fail(aliases != NULL, -EINVAL); return_val_if_fail(aliases[0] != NULL, -EINVAL); return_val_if_fail(aliases[1] != NULL, -EINVAL); if (o->port.alias1[0] != '\0') { snprintf(aliases[0], REAL_JACK_PORT_NAME_SIZE+1, "%s", o->port.alias1); res++; } if (o->port.alias2[0] != '\0') { snprintf(aliases[1], REAL_JACK_PORT_NAME_SIZE+1, "%s", o->port.alias2); res++; } return res; } SPA_EXPORT int jack_port_request_monitor (jack_port_t *port, int onoff) { struct object *o = port_to_object(port); return_val_if_fail(o != NULL, -EINVAL); if (onoff) o->port.monitor_requests++; else if (o->port.monitor_requests > 0) o->port.monitor_requests--; return 0; } SPA_EXPORT int jack_port_request_monitor_by_name (jack_client_t *client, const char *port_name, int onoff) { struct client *c = (struct client *) client; struct object *p; return_val_if_fail(c != NULL, -EINVAL); return_val_if_fail(port_name != NULL, -EINVAL); pthread_mutex_lock(&c->context.lock); p = find_port_by_name(c, port_name); pthread_mutex_unlock(&c->context.lock); if (p == NULL) { pw_log_error("%p: jack_port_request_monitor_by_name called" " with an incorrect port %s", client, port_name); return -1; } return jack_port_request_monitor(object_to_port(p), onoff); } SPA_EXPORT int jack_port_ensure_monitor (jack_port_t *port, int onoff) { struct object *o = port_to_object(port); return_val_if_fail(o != NULL, -EINVAL); if (onoff) { if (o->port.monitor_requests == 0) o->port.monitor_requests++; } else { if (o->port.monitor_requests > 0) o->port.monitor_requests = 0; } return 0; } SPA_EXPORT int jack_port_monitoring_input (jack_port_t *port) { struct object *o = port_to_object(port); return_val_if_fail(o != NULL, -EINVAL); return o->port.monitor_requests > 0; } static void link_proxy_error(void *data, int seq, int res, const char *message) { int *link_res = data; *link_res = res; } static const struct pw_proxy_events link_proxy_events = { PW_VERSION_PROXY_EVENTS, .error = link_proxy_error, }; static int check_connect(struct client *c, struct object *src, struct object *dst) { int src_self, dst_self, sum; src_self = src->port.node_id == c->node_id ? 1 : 0; dst_self = dst->port.node_id == c->node_id ? 1 : 0; sum = src_self + dst_self; pw_log_debug("sum %d %d", sum, c->self_connect_mode); /* check for other connection first */ if (sum == 0) return c->other_connect_mode; if (c->self_connect_mode == SELF_CONNECT_ALLOW) return 1; /* internal connection */ if (sum == 2 && (c->self_connect_mode == SELF_CONNECT_FAIL_EXT || c->self_connect_mode == SELF_CONNECT_IGNORE_EXT)) return 1; /* failure -> -1 */ if (c->self_connect_mode < 0) return -1; /* ignore -> 0 */ return 0; } SPA_EXPORT int jack_connect (jack_client_t *client, const char *source_port, const char *destination_port) { struct client *c = (struct client *) client; struct object *src, *dst; struct spa_dict props; struct spa_dict_item items[6]; struct pw_proxy *proxy; struct spa_hook listener; char val[4][16]; int res, link_res = 0; return_val_if_fail(c != NULL, EINVAL); return_val_if_fail(source_port != NULL, EINVAL); return_val_if_fail(destination_port != NULL, EINVAL); pw_log_info("%p: connect %s %s", client, source_port, destination_port); pw_thread_loop_lock(c->context.loop); freeze_callbacks(c); src = find_port_by_name(c, source_port); dst = find_port_by_name(c, destination_port); if (src == NULL || dst == NULL || !(src->port.flags & JackPortIsOutput) || !(dst->port.flags & JackPortIsInput) || !TYPE_ID_IS_COMPATIBLE(src->port.type_id, dst->port.type_id)) { res = -EINVAL; goto exit; } if ((res = check_connect(c, src, dst)) != 1) goto exit; snprintf(val[0], sizeof(val[0]), "%d", src->port.node_id); snprintf(val[1], sizeof(val[1]), "%d", src->id); snprintf(val[2], sizeof(val[2]), "%d", dst->port.node_id); snprintf(val[3], sizeof(val[3]), "%d", dst->id); props = SPA_DICT_INIT(items, 0); items[props.n_items++] = SPA_DICT_ITEM_INIT(PW_KEY_LINK_OUTPUT_NODE, val[0]); items[props.n_items++] = SPA_DICT_ITEM_INIT(PW_KEY_LINK_OUTPUT_PORT, val[1]); items[props.n_items++] = SPA_DICT_ITEM_INIT(PW_KEY_LINK_INPUT_NODE, val[2]); items[props.n_items++] = SPA_DICT_ITEM_INIT(PW_KEY_LINK_INPUT_PORT, val[3]); items[props.n_items++] = SPA_DICT_ITEM_INIT(PW_KEY_OBJECT_LINGER, "true"); if (c->passive_links) items[props.n_items++] = SPA_DICT_ITEM_INIT(PW_KEY_LINK_PASSIVE, "true"); proxy = pw_core_create_object(c->core, "link-factory", PW_TYPE_INTERFACE_Link, PW_VERSION_LINK, &props, 0); if (proxy == NULL) { res = -errno; goto exit; } spa_zero(listener); pw_proxy_add_listener(proxy, &listener, &link_proxy_events, &link_res); res = do_sync(c); spa_hook_remove(&listener); if (link_res < 0) res = link_res; pw_proxy_destroy(proxy); exit: pw_log_debug("%p: connect %s %s done %d", client, source_port, destination_port, res); thaw_callbacks(c); pw_thread_loop_unlock(c->context.loop); return -res; } SPA_EXPORT int jack_disconnect (jack_client_t *client, const char *source_port, const char *destination_port) { struct client *c = (struct client *) client; struct object *src, *dst, *l; int res; return_val_if_fail(c != NULL, -EINVAL); return_val_if_fail(source_port != NULL, -EINVAL); return_val_if_fail(destination_port != NULL, -EINVAL); pw_log_info("%p: disconnect %s %s", client, source_port, destination_port); pw_thread_loop_lock(c->context.loop); freeze_callbacks(c); src = find_port_by_name(c, source_port); dst = find_port_by_name(c, destination_port); pw_log_debug("%p: %d %d", client, src->id, dst->id); if (src == NULL || dst == NULL || !(src->port.flags & JackPortIsOutput) || !(dst->port.flags & JackPortIsInput)) { res = -EINVAL; goto exit; } if ((res = check_connect(c, src, dst)) != 1) goto exit; if ((l = find_link(c, src->id, dst->id)) == NULL) { res = -ENOENT; goto exit; } pw_registry_destroy(c->registry, l->id); res = do_sync(c); exit: thaw_callbacks(c); pw_thread_loop_unlock(c->context.loop); return -res; } SPA_EXPORT int jack_port_disconnect (jack_client_t *client, jack_port_t *port) { struct client *c = (struct client *) client; struct object *o = port_to_object(port); struct object *l; int res; return_val_if_fail(c != NULL, -EINVAL); return_val_if_fail(o != NULL, -EINVAL); pw_log_debug("%p: disconnect %p", client, port); pw_thread_loop_lock(c->context.loop); freeze_callbacks(c); spa_list_for_each(l, &c->context.objects, link) { if (l->type != INTERFACE_Link || l->removed) continue; if (l->port_link.src_serial == o->serial || l->port_link.dst_serial == o->serial) { pw_registry_destroy(c->registry, l->id); } } res = do_sync(c); thaw_callbacks(c); pw_thread_loop_unlock(c->context.loop); return -res; } SPA_EXPORT int jack_port_name_size(void) { return REAL_JACK_PORT_NAME_SIZE+1; } SPA_EXPORT int jack_port_type_size(void) { return JACK_PORT_TYPE_SIZE+1; } SPA_EXPORT size_t jack_port_type_get_buffer_size (jack_client_t *client, const char *port_type) { struct client *c = (struct client *) client; return_val_if_fail(client != NULL, 0); return_val_if_fail(port_type != NULL, 0); if (spa_streq(JACK_DEFAULT_AUDIO_TYPE, port_type)) return jack_get_buffer_size(client) * sizeof(float); else if (spa_streq(JACK_DEFAULT_MIDI_TYPE, port_type)) return c->max_frames * sizeof(float); else if (spa_streq(JACK_DEFAULT_OSC_TYPE, port_type)) return c->max_frames * sizeof(float); else if (spa_streq(JACK_DEFAULT_UMP_TYPE, port_type)) return c->max_frames * sizeof(float); else if (spa_streq(JACK_DEFAULT_VIDEO_TYPE, port_type)) return 320 * 240 * 4 * sizeof(float); else return 0; } SPA_EXPORT void jack_port_set_latency (jack_port_t *port, jack_nframes_t frames) { struct object *o = port_to_object(port); struct client *c; jack_latency_range_t range = { frames, frames }; return_if_fail(o != NULL); c = o->client; pw_log_debug("%p: %s set latency %d", c, o->port.name, frames); if (o->port.flags & JackPortIsOutput) { jack_port_set_latency_range(port, JackCaptureLatency, &range); } if (o->port.flags & JackPortIsInput) { jack_port_set_latency_range(port, JackPlaybackLatency, &range); } } SPA_EXPORT void jack_port_get_latency_range (jack_port_t *port, jack_latency_callback_mode_t mode, jack_latency_range_t *range) { struct object *o = port_to_object(port); struct client *c; jack_nframes_t nframes, rate; int direction; struct spa_latency_info *info; int64_t min, max; return_if_fail(o != NULL); c = o->client; if (o->type != INTERFACE_Port || c == NULL) { range->min = range->max = 0; return; } if (mode == JackCaptureLatency) direction = SPA_DIRECTION_OUTPUT; else direction = SPA_DIRECTION_INPUT; nframes = jack_get_buffer_size((jack_client_t*)c); rate = jack_get_sample_rate((jack_client_t*)c); info = &o->port.latency[direction]; min = (int64_t)(info->min_quantum * nframes) + info->min_rate + (info->min_ns * (int64_t)rate) / (int64_t)SPA_NSEC_PER_SEC; max = (int64_t)(info->max_quantum * nframes) + info->max_rate + (info->max_ns * (int64_t)rate) / (int64_t)SPA_NSEC_PER_SEC; range->min = SPA_MAX(min, 0); range->max = SPA_MAX(max, 0); pw_log_debug("%p: %s get %d latency range %d %d", c, o->port.name, mode, range->min, range->max); } static int do_port_check_latency(struct spa_loop *loop, bool async, uint32_t seq, const void *data, size_t size, void *user_data) { struct port *p = user_data; const struct spa_latency_info *latency = data; port_check_latency(p, latency); return 0; } SPA_EXPORT void jack_port_set_latency_range (jack_port_t *port, jack_latency_callback_mode_t mode, jack_latency_range_t *range) { struct object *o = port_to_object(port); struct client *c; enum spa_direction direction; struct spa_latency_info latency; jack_nframes_t nframes; struct port *p; return_if_fail(o != NULL); if (o->type != INTERFACE_Port || o->client == NULL) return; c = o->client; if (mode == JackCaptureLatency) direction = SPA_DIRECTION_OUTPUT; else direction = SPA_DIRECTION_INPUT; pw_log_info("%p: %s set %d latency range %d %d", c, o->port.name, mode, range->min, range->max); latency = SPA_LATENCY_INFO(direction); nframes = jack_get_buffer_size((jack_client_t*)c); if (nframes == 0) nframes = 1; latency.min_rate = range->min; if (latency.min_rate >= (int32_t)nframes) { latency.min_quantum = latency.min_rate / nframes; latency.min_rate %= nframes; } latency.max_rate = range->max; if (latency.max_rate >= (int32_t)nframes) { latency.max_quantum = latency.max_rate / nframes; latency.max_rate %= nframes; } if ((p = o->port.port) == NULL) return; pw_loop_invoke(c->context.l, do_port_check_latency, 0, &latency, sizeof(latency), false, p); } SPA_EXPORT int jack_recompute_total_latencies (jack_client_t *client) { struct client *c = (struct client *) client; return queue_notify(c, NOTIFY_TYPE_TOTAL_LATENCY, NULL, 0, NULL); } static jack_nframes_t port_get_latency (jack_port_t *port) { struct object *o = port_to_object(port); jack_latency_range_t range = { 0, 0 }; return_val_if_fail(o != NULL, 0); if (o->port.flags & JackPortIsOutput) { jack_port_get_latency_range(port, JackCaptureLatency, &range); } if (o->port.flags & JackPortIsInput) { jack_port_get_latency_range(port, JackPlaybackLatency, &range); } return (range.min + range.max) / 2; } SPA_EXPORT jack_nframes_t jack_port_get_latency (jack_port_t *port) { return port_get_latency(port); } SPA_EXPORT jack_nframes_t jack_port_get_total_latency (jack_client_t *client, jack_port_t *port) { return port_get_latency(port); } SPA_EXPORT int jack_recompute_total_latency (jack_client_t *client, jack_port_t* port) { pw_log_warn("%p: not implemented %p", client, port); return 0; } static int port_compare_func(const void *v1, const void *v2) { const struct object *const*o1 = v1, *const*o2 = v2; struct client *c = (*o1)->client; int res; bool is_cap1, is_cap2, is_def1 = false, is_def2 = false; is_cap1 = ((*o1)->port.flags & JackPortIsOutput) == JackPortIsOutput && !(*o1)->port.is_monitor; is_cap2 = ((*o2)->port.flags & JackPortIsOutput) == JackPortIsOutput && !(*o2)->port.is_monitor; if (c->metadata) { struct object *ot1, *ot2; ot1 = (*o1)->port.node; if (is_cap1) is_def1 = ot1 != NULL && spa_streq(ot1->node.node_name, c->metadata->default_audio_source); else if (!is_cap1) is_def1 = ot1 != NULL && spa_streq(ot1->node.node_name, c->metadata->default_audio_sink); ot2 = (*o2)->port.node; if (is_cap2) is_def2 = ot2 != NULL && spa_streq(ot2->node.node_name, c->metadata->default_audio_source); else if (!is_cap2) is_def2 = ot2 != NULL && spa_streq(ot2->node.node_name, c->metadata->default_audio_sink); } if ((*o1)->port.type_id != (*o2)->port.type_id) res = (*o1)->port.type_id - (*o2)->port.type_id; else if ((is_cap1 || is_cap2) && is_cap1 != is_cap2) res = is_cap2 - is_cap1; else if ((is_def1 || is_def2) && is_def1 != is_def2) res = is_def2 - is_def1; else if ((*o1)->port.priority != (*o2)->port.priority) res = (*o2)->port.priority - (*o1)->port.priority; else if ((res = (*o1)->port.node_id - (*o2)->port.node_id) == 0) { if ((*o1)->port.is_monitor != (*o2)->port.is_monitor) res = (*o1)->port.is_monitor - (*o2)->port.is_monitor; if (res == 0) res = (*o1)->port.system_id - (*o2)->port.system_id; if (res == 0) res = (*o1)->serial - (*o2)->serial; } pw_log_debug("port %s<->%s type:%d<->%d def:%d<->%d prio:%d<->%d id:%d<->%d res:%d", (*o1)->port.name, (*o2)->port.name, (*o1)->port.type_id, (*o2)->port.type_id, is_def1, is_def2, (*o1)->port.priority, (*o2)->port.priority, (*o1)->serial, (*o2)->serial, res); return res; } SPA_EXPORT const char ** jack_get_ports (jack_client_t *client, const char *port_name_pattern, const char *type_name_pattern, unsigned long flags) { struct client *c = (struct client *) client; const char **res; struct object *o; struct pw_array tmp; const char *str; uint32_t i, count; int r; regex_t port_regex, type_regex; return_val_if_fail(c != NULL, NULL); str = getenv("PIPEWIRE_NODE"); if (port_name_pattern && port_name_pattern[0]) { if ((r = regcomp(&port_regex, port_name_pattern, REG_EXTENDED | REG_NOSUB)) != 0) { pw_log_error("can't compile regex %s: %d", port_name_pattern, r); return NULL; } } if (type_name_pattern && type_name_pattern[0]) { if ((r = regcomp(&type_regex, type_name_pattern, REG_EXTENDED | REG_NOSUB)) != 0) { pw_log_error("can't compile regex %s: %d", type_name_pattern, r); return NULL; } } pw_log_debug("%p: ports target:%s name:\"%s\" type:\"%s\" flags:%08lx", c, str, port_name_pattern, type_name_pattern, flags); pthread_mutex_lock(&c->context.lock); pw_array_init(&tmp, sizeof(void*) * 32); count = 0; spa_list_for_each(o, &c->context.objects, link) { if (o->type != INTERFACE_Port || o->removed || !o->visible) continue; pw_log_debug("%p: check port type:%d flags:%08lx name:\"%s\"", c, o->port.type_id, o->port.flags, o->port.name); if (TYPE_ID_IS_HIDDEN(o->port.type_id)) continue; if (!SPA_FLAG_IS_SET(o->port.flags, flags)) continue; if (str != NULL && o->port.node != NULL) { if (!spa_strstartswith(o->port.name, str) && o->port.node->serial != atoll(str)) continue; } if (port_name_pattern && port_name_pattern[0]) { bool match; match = regexec(&port_regex, o->port.name, 0, NULL, 0) == 0; if (!match && is_port_default(c, o)) match = regexec(&port_regex, o->port.system, 0, NULL, 0) == 0; if (!match) continue; } if (type_name_pattern && type_name_pattern[0]) { if (regexec(&type_regex, type_to_string(o->port.type_id), 0, NULL, 0) == REG_NOMATCH) continue; } pw_log_debug("%p: port \"%s\" prio:%d matches (%d)", c, o->port.name, o->port.priority, count); pw_array_add_ptr(&tmp, o); count++; } pthread_mutex_unlock(&c->context.lock); if (count > 0) { qsort(tmp.data, count, sizeof(struct object *), port_compare_func); pw_array_add_ptr(&tmp, NULL); res = tmp.data; for (i = 0; i < count; i++) res[i] = port_name((struct object*)res[i]); } else { pw_array_clear(&tmp); res = NULL; } if (port_name_pattern && port_name_pattern[0]) regfree(&port_regex); if (type_name_pattern && type_name_pattern[0]) regfree(&type_regex); return res; } SPA_EXPORT jack_port_t * jack_port_by_name (jack_client_t *client, const char *port_name) { struct client *c = (struct client *) client; struct object *res; return_val_if_fail(c != NULL, NULL); pthread_mutex_lock(&c->context.lock); res = find_port_by_name(c, port_name); pthread_mutex_unlock(&c->context.lock); if (res == NULL) pw_log_info("%p: port \"%s\" not found", c, port_name); return object_to_port(res); } SPA_EXPORT jack_port_t * jack_port_by_id (jack_client_t *client, jack_port_id_t port_id) { struct client *c = (struct client *) client; struct object *res = NULL; return_val_if_fail(c != NULL, NULL); pthread_mutex_lock(&c->context.lock); res = find_by_serial(c, port_id); if (res && res->type != INTERFACE_Port) res = NULL; pw_log_debug("%p: port %d -> %p", c, port_id, res); pthread_mutex_unlock(&c->context.lock); if (res == NULL) pw_log_info("%p: port %d not found", c, port_id); return object_to_port(res); } static inline void get_frame_times(struct client *c, struct frame_times *times) { jack_unique_t u1; uint32_t count = 0; do { u1 = c->jack_position.unique_1; *times = c->jack_times; if (++count == 10) { pw_log_warn("could not get snapshot %" PRIu64 " %" PRIu64, u1, c->jack_position.unique_2); break; } } while (u1 != c->jack_position.unique_2); } SPA_EXPORT jack_nframes_t jack_frames_since_cycle_start (const jack_client_t *client) { struct client *c = (struct client *) client; struct frame_times times; int64_t diff; return_val_if_fail(c != NULL, 0); get_frame_times(c, ×); diff = get_time_ns(c->l->system) - times.nsec; return (jack_nframes_t) floor(((double)times.sample_rate * diff) / SPA_NSEC_PER_SEC); } SPA_EXPORT jack_nframes_t jack_frame_time (const jack_client_t *client) { return jack_time_to_frames(client, jack_get_time()); } SPA_EXPORT jack_nframes_t jack_last_frame_time (const jack_client_t *client) { struct client *c = (struct client *) client; struct frame_times times; return_val_if_fail(c != NULL, 0); get_frame_times(c, ×); return times.frames; } SPA_EXPORT int jack_get_cycle_times(const jack_client_t *client, jack_nframes_t *current_frames, jack_time_t *current_usecs, jack_time_t *next_usecs, float *period_usecs) { struct client *c = (struct client *) client; struct frame_times times; return_val_if_fail(c != NULL, -EINVAL); get_frame_times(c, ×); if (times.sample_rate == 0 || times.rate_diff == 0.0) return -1; *current_frames = times.frames; *next_usecs = times.next_nsec / SPA_NSEC_PER_USEC; *period_usecs = (float)(times.buffer_frames * SPA_USEC_PER_SEC / (times.sample_rate * times.rate_diff)); *current_usecs = *next_usecs - (jack_time_t)*period_usecs; pw_log_trace("%p: %d %"PRIu64" %"PRIu64" %f", c, *current_frames, *current_usecs, *next_usecs, *period_usecs); return 0; } SPA_EXPORT jack_time_t jack_frames_to_time(const jack_client_t *client, jack_nframes_t frames) { struct client *c = (struct client *) client; struct frame_times times; return_val_if_fail(c != NULL, -EINVAL); get_frame_times(c, ×); if (times.buffer_frames == 0 || times.sample_rate == 0 || times.rate_diff == 0.0) return 0; uint32_t nf = (uint32_t)times.frames; uint64_t nw = times.next_nsec/SPA_NSEC_PER_USEC; uint64_t dp = (uint64_t)(times.buffer_frames * (float)SPA_USEC_PER_SEC / (times.sample_rate * times.rate_diff)); uint64_t w = nw - dp; int32_t df = frames - nf; return w + (int64_t)rint((double) df * (double) dp / times.buffer_frames); } SPA_EXPORT jack_nframes_t jack_time_to_frames(const jack_client_t *client, jack_time_t usecs) { struct client *c = (struct client *) client; struct frame_times times; return_val_if_fail(c != NULL, -EINVAL); get_frame_times(c, ×); if (times.sample_rate == 0 || times.rate_diff == 0.0) return 0; uint32_t nf = (uint32_t)times.frames; uint64_t nw = times.next_nsec/SPA_NSEC_PER_USEC; uint64_t dp = (uint64_t)(times.buffer_frames * (float)SPA_USEC_PER_SEC / (times.sample_rate * times.rate_diff)); uint64_t w = nw - dp; int64_t du = usecs - w; return nf + (int32_t)rint((double)du / (double)dp * times.buffer_frames); } SPA_EXPORT jack_time_t jack_get_time(void) { struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); return SPA_TIMESPEC_TO_USEC(&ts); } SPA_EXPORT void default_jack_error_callback(const char *desc) { pw_log_error("pw jack error: %s",desc); } SPA_EXPORT void silent_jack_error_callback(const char *desc) { } SPA_EXPORT void (*jack_error_callback)(const char *msg); SPA_EXPORT void jack_set_error_function (void (*func)(const char *)) { jack_error_callback = (func == NULL) ? &default_jack_error_callback : func; } SPA_EXPORT void default_jack_info_callback(const char *desc) { pw_log_info("pw jack info: %s", desc); } SPA_EXPORT void silent_jack_info_callback(const char *desc) { } SPA_EXPORT void (*jack_info_callback)(const char *msg); SPA_EXPORT void jack_set_info_function (void (*func)(const char *)) { jack_info_callback = (func == NULL) ? &default_jack_info_callback : func; } SPA_EXPORT void jack_free(void* ptr) { free(ptr); } SPA_EXPORT int jack_release_timebase (jack_client_t *client) { struct client *c = (struct client *) client; struct pw_node_activation *a; return_val_if_fail(c != NULL, -EINVAL); if ((a = c->driver_activation) == NULL) return -EIO; if (!SPA_ATOMIC_CAS(a->segment_owner[0], c->node_id, 0)) return -EINVAL; c->timebase_callback = NULL; c->timebase_arg = NULL; c->activation->pending_new_pos = false; return 0; } SPA_EXPORT int jack_set_sync_callback (jack_client_t *client, JackSyncCallback sync_callback, void *arg) { int res = 0; struct client *c = (struct client *) client; return_val_if_fail(c != NULL, -EINVAL); pw_thread_loop_lock(c->context.loop); freeze_callbacks(c); c->sync_callback = sync_callback; c->sync_arg = arg; if ((res = do_activate(c)) < 0) goto done; c->activation->pending_sync = true; done: thaw_callbacks(c); pw_thread_loop_unlock(c->context.loop); return res; } SPA_EXPORT int jack_set_sync_timeout (jack_client_t *client, jack_time_t timeout) { int res = 0; struct client *c = (struct client *) client; struct pw_node_activation *a; return_val_if_fail(c != NULL, -EINVAL); pw_thread_loop_lock(c->context.loop); if ((a = c->activation) == NULL) res = -EIO; else a->sync_timeout = timeout; pw_thread_loop_unlock(c->context.loop); return res; } SPA_EXPORT int jack_set_timebase_callback (jack_client_t *client, int conditional, JackTimebaseCallback timebase_callback, void *arg) { int res = 0; struct client *c = (struct client *) client; return_val_if_fail(c != NULL, -EINVAL); return_val_if_fail(timebase_callback != NULL, -EINVAL); pw_thread_loop_lock(c->context.loop); freeze_callbacks(c); c->timebase_callback = timebase_callback; c->timebase_arg = arg; c->timeowner_conditional = conditional; install_timeowner(c); pw_log_debug("%p: timebase set id:%u", c, c->node_id); if ((res = do_activate(c)) < 0) goto done; c->activation->pending_new_pos = true; done: thaw_callbacks(c); pw_thread_loop_unlock(c->context.loop); return res; } SPA_EXPORT int jack_transport_locate (jack_client_t *client, jack_nframes_t frame) { jack_position_t pos; pos.frame = frame; pos.valid = (jack_position_bits_t)0; return jack_transport_reposition(client, &pos); } SPA_EXPORT jack_transport_state_t jack_transport_query (const jack_client_t *client, jack_position_t *pos) { struct client *c = (struct client *) client; jack_transport_state_t state; jack_unique_t u1; uint32_t count = 0; return_val_if_fail(c != NULL, JackTransportStopped); do { u1 = c->jack_position.unique_1; state = c->jack_state; if (pos != NULL) *pos = c->jack_position; if (++count == 10) { pw_log_warn("could not get snapshot %" PRIu64 " %" PRIu64, u1, c->jack_position.unique_2); break; } } while (u1 != c->jack_position.unique_2); return state; } SPA_EXPORT jack_nframes_t jack_get_current_transport_frame (const jack_client_t *client) { struct client *c = (struct client *) client; jack_transport_state_t state; jack_nframes_t res; jack_position_t pos; return_val_if_fail(c != NULL, -EINVAL); state = jack_transport_query(client, &pos); res = pos.frame; if (state == JackTransportRolling) { float usecs = get_time_ns(c->l->system)/1000 - pos.usecs; res += (jack_nframes_t)floor((((float) pos.frame_rate) / 1000000.0f) * usecs); } return res; } SPA_EXPORT int jack_transport_reposition (jack_client_t *client, const jack_position_t *pos) { struct client *c = (struct client *) client; struct pw_node_activation *a, *na; return_val_if_fail(c != NULL, -EINVAL); a = c->rt.driver_activation; na = c->activation; if (!a || !na) return -EIO; if (pos->valid & ~(JackPositionBBT|JackPositionTimecode)) return -EINVAL; pw_log_debug("frame:%u", pos->frame); spa_zero(na->reposition); na->reposition.flags = 0; na->reposition.start = 0; na->reposition.duration = 0; na->reposition.position = pos->frame; na->reposition.rate = 1.0; SPA_ATOMIC_STORE(a->reposition_owner, c->node_id); return 0; } static void update_command(struct client *c, uint32_t command) { struct pw_node_activation *a = c->rt.driver_activation; if (!a) return; SPA_ATOMIC_STORE(a->command, command); } static int transport_update(struct client* c, int active) { pw_log_info("%p: transport %d", c, active); pw_thread_loop_lock(c->context.loop); pw_properties_set(c->props, PW_KEY_NODE_SYNC, "true"); pw_properties_set(c->props, PW_KEY_NODE_TRANSPORT, active ? "true" : "false"); c->info.change_mask |= SPA_NODE_CHANGE_MASK_PROPS; c->info.props = &c->props->dict; pw_client_node_update(c->node, PW_CLIENT_NODE_UPDATE_INFO, 0, NULL, &c->info); c->info.change_mask = 0; pw_properties_set(c->props, PW_KEY_NODE_TRANSPORT, NULL); pw_thread_loop_unlock(c->context.loop); return 0; } SPA_EXPORT void jack_transport_start (jack_client_t *client) { struct client *c = (struct client *) client; return_if_fail(c != NULL); if (c->activation->server_version < 1) update_command(c, PW_NODE_ACTIVATION_COMMAND_START); else transport_update(c, true); } SPA_EXPORT void jack_transport_stop (jack_client_t *client) { struct client *c = (struct client *) client; return_if_fail(c != NULL); if (c->activation->server_version < 1) update_command(c, PW_NODE_ACTIVATION_COMMAND_STOP); else transport_update(c, false); } SPA_EXPORT void jack_get_transport_info (jack_client_t *client, jack_transport_info_t *tinfo) { pw_log_error("%p: deprecated", client); if (tinfo) memset(tinfo, 0, sizeof(jack_transport_info_t)); } SPA_EXPORT void jack_set_transport_info (jack_client_t *client, jack_transport_info_t *tinfo) { pw_log_error("%p: deprecated", client); if (tinfo) memset(tinfo, 0, sizeof(jack_transport_info_t)); } SPA_EXPORT int jack_set_session_callback (jack_client_t *client, JackSessionCallback session_callback, void *arg) { struct client *c = (struct client *) client; return_val_if_fail(c != NULL, -EINVAL); if (c->active) { pw_log_error("%p: can't set callback on active client", c); return -EIO; } pw_log_warn("%p: not implemented", client); return -ENOTSUP; } SPA_EXPORT int jack_session_reply (jack_client_t *client, jack_session_event_t *event) { pw_log_warn("%p: not implemented", client); return -ENOTSUP; } SPA_EXPORT void jack_session_event_free (jack_session_event_t *event) { if (event) { free((void *)event->session_dir); free((void *)event->client_uuid); free(event->command_line); free(event); } } SPA_EXPORT char *jack_client_get_uuid (jack_client_t *client) { struct client *c = (struct client *) client; return_val_if_fail(c != NULL, NULL); return spa_aprintf("%"PRIu64, client_make_uuid(c->serial, false)); } SPA_EXPORT jack_session_command_t *jack_session_notify ( jack_client_t* client, const char *target, jack_session_event_type_t type, const char *path) { struct client *c = (struct client *) client; jack_session_command_t *cmds; return_val_if_fail(c != NULL, NULL); pw_log_warn("not implemented"); cmds = calloc(1, sizeof(jack_session_command_t)); return cmds; } SPA_EXPORT void jack_session_commands_free (jack_session_command_t *cmds) { int i; if (cmds == NULL) return; for (i = 0; cmds[i].uuid != NULL; i++) { free((char*)cmds[i].client_name); free((char*)cmds[i].command); free((char*)cmds[i].uuid); } free(cmds); } SPA_EXPORT int jack_reserve_client_name (jack_client_t *client, const char *name, const char *uuid) { struct client *c = (struct client *) client; return_val_if_fail(c != NULL, -1); pw_log_warn("not implemented"); return 0; } SPA_EXPORT int jack_client_has_session_callback (jack_client_t *client, const char *client_name) { struct client *c = (struct client *) client; return_val_if_fail(c != NULL, -1); return 0; } SPA_EXPORT int jack_client_real_time_priority (jack_client_t * client) { return jack_client_max_real_time_priority(client) - 5; } SPA_EXPORT int jack_client_max_real_time_priority (jack_client_t *client) { struct client *c = (struct client *) client; int min, max; return_val_if_fail(c != NULL, -1); spa_thread_utils_get_rt_range(&c->context.thread_utils, NULL, &min, &max); return SPA_MIN(max, c->rt_max) - 1; } SPA_EXPORT int jack_acquire_real_time_scheduling (jack_native_thread_t thread, int priority) { struct spa_thread *t = (struct spa_thread*)thread; pw_log_info("acquire %p", t); return_val_if_fail(globals.thread_utils != NULL, -1); return_val_if_fail(t != NULL, -1); return spa_thread_utils_acquire_rt(globals.thread_utils, t, priority); } SPA_EXPORT int jack_drop_real_time_scheduling (jack_native_thread_t thread) { struct spa_thread *t = (struct spa_thread*)thread; pw_log_info("drop %p", t); return_val_if_fail(globals.thread_utils != NULL, -1); return_val_if_fail(t != NULL, -1); return spa_thread_utils_drop_rt(globals.thread_utils, t); } /** * Create a thread for JACK or one of its clients. The thread is * created executing @a start_routine with @a arg as its sole * argument. * * @param client the JACK client for whom the thread is being created. May be * NULL if the client is being created within the JACK server. * @param thread place to return POSIX thread ID. * @param priority thread priority, if realtime. * @param realtime true for the thread to use realtime scheduling. On * some systems that may require special privileges. * @param start_routine function the thread calls when it starts. * @param arg parameter passed to the @a start_routine. * * @returns 0, if successful; otherwise some error number. */ SPA_EXPORT int jack_client_create_thread (jack_client_t* client, jack_native_thread_t *thread, int priority, int realtime, /* boolean */ void *(*start_routine)(void*), void *arg) { struct client *c = (struct client *) client; int res = 0; struct spa_thread *thr; return_val_if_fail(client != NULL, -EINVAL); return_val_if_fail(thread != NULL, -EINVAL); return_val_if_fail(start_routine != NULL, -EINVAL); pw_log_info("client %p: create thread rt:%d prio:%d", client, realtime, priority); thr = spa_thread_utils_create(&c->context.thread_utils, NULL, start_routine, arg); if (thr == NULL) res = -errno; *thread = (pthread_t)thr; if (res != 0) { pw_log_warn("client %p: create RT thread failed: %s", client, strerror(res)); } else if (realtime) { /* Try to acquire RT scheduling, we don't fail here but the * function will emit a warning. Real JACK fails here. */ jack_acquire_real_time_scheduling(*thread, priority); } return res; } SPA_EXPORT int jack_client_stop_thread(jack_client_t* client, jack_native_thread_t thread) { struct client *c = (struct client *) client; void* status; if (thread == (jack_native_thread_t)NULL) return -EINVAL; return_val_if_fail(client != NULL, -EINVAL); pw_log_debug("join thread %p", (void *) thread); spa_thread_utils_join(&c->context.thread_utils, (struct spa_thread*)thread, &status); pw_log_debug("stopped thread %p", (void *) thread); return 0; } SPA_EXPORT int jack_client_kill_thread(jack_client_t* client, jack_native_thread_t thread) { struct client *c = (struct client *) client; void* status; if (thread == (jack_native_thread_t)NULL) return -EINVAL; return_val_if_fail(client != NULL, -EINVAL); pw_log_debug("cancel thread %p", (void *) thread); pthread_cancel(thread); pw_log_debug("join thread %p", (void *) thread); spa_thread_utils_join(&c->context.thread_utils, (struct spa_thread*)thread, &status); pw_log_debug("stopped thread %p", (void *) thread); return 0; } SPA_EXPORT void jack_set_thread_creator (jack_thread_creator_t creator) { globals.creator = creator; } static inline uint8_t * midi_event_data (void* port_buffer, const struct midi_event* event) { if (SPA_LIKELY(event->size <= MIDI_INLINE_MAX)) return (uint8_t *)event->inline_data; else return SPA_PTROFF(port_buffer, event->byte_offset, uint8_t); } SPA_EXPORT uint32_t jack_midi_get_event_count(void* port_buffer) { struct midi_buffer *mb = port_buffer; if (mb == NULL || mb->magic != MIDI_BUFFER_MAGIC) return 0; return mb->event_count; } SPA_EXPORT int jack_midi_event_get(jack_midi_event_t *event, void *port_buffer, uint32_t event_index) { struct midi_buffer *mb = port_buffer; struct midi_event *ev = SPA_PTROFF(mb, sizeof(*mb), struct midi_event); if (mb == NULL || mb->magic != MIDI_BUFFER_MAGIC) return -EINVAL; if (event_index >= mb->event_count) return -ENOBUFS; ev += event_index; event->time = ev->time; event->size = ev->size; event->buffer = midi_event_data (port_buffer, ev); return 0; } SPA_EXPORT void jack_midi_clear_buffer(void *port_buffer) { struct midi_buffer *mb = port_buffer; if (mb == NULL || mb->magic != MIDI_BUFFER_MAGIC) return; mb->event_count = 0; mb->write_pos = 0; mb->lost_events = 0; } SPA_EXPORT void jack_midi_reset_buffer(void *port_buffer) { midi_init_buffer(port_buffer, globals.max_frames, globals.max_frames); } SPA_EXPORT size_t jack_midi_max_event_size(void* port_buffer) { struct midi_buffer *mb = port_buffer; size_t buffer_size; if (mb == NULL || mb->magic != MIDI_BUFFER_MAGIC) return 0; buffer_size = mb->buffer_size; /* (event_count + 1) below accounts for jack_midi_port_internal_event_t * which would be needed to store the next event */ size_t used_size = sizeof(struct midi_buffer) + mb->write_pos + ((mb->event_count + 1) * sizeof(struct midi_event)); if (SPA_UNLIKELY(used_size > buffer_size)) { return 0; } else if (SPA_LIKELY((buffer_size - used_size) < MIDI_INLINE_MAX)) { return MIDI_INLINE_MAX; } else { return buffer_size - used_size; } } static inline int midi_buffer_check(void *port_buffer, jack_nframes_t time) { struct midi_buffer *mb = port_buffer; struct midi_event *events; if (SPA_UNLIKELY(mb == NULL)) { pw_log_warn("port buffer is NULL"); return -EINVAL; } if (SPA_UNLIKELY(mb->magic != MIDI_BUFFER_MAGIC)) { pw_log_warn("port buffer is invalid"); return -EINVAL; } if (SPA_UNLIKELY(time >= mb->nframes)) { pw_log_warn("midi %p: time:%d frames:%d", port_buffer, time, mb->nframes); return -EINVAL; } events = SPA_PTROFF(mb, sizeof(*mb), struct midi_event); if (SPA_UNLIKELY(mb->event_count > 0 && time < events[mb->event_count - 1].time)) { pw_log_warn("midi %p: time:%d ev:%d", port_buffer, time, mb->event_count); return -EINVAL; } return 0; } SPA_EXPORT jack_midi_data_t* jack_midi_event_reserve(void *port_buffer, jack_nframes_t time, size_t data_size) { struct midi_buffer *mb = port_buffer; jack_midi_data_t *res; if (midi_buffer_check(port_buffer, time) < 0) goto failed; res = midi_event_reserve(port_buffer, time, data_size); if (res != NULL) return res; failed: mb->lost_events++; return NULL; } SPA_EXPORT int jack_midi_event_write(void *port_buffer, jack_nframes_t time, const jack_midi_data_t *data, size_t data_size) { jack_midi_data_t *ptr; int res; if ((res = midi_buffer_check(port_buffer, time)) < 0) return res; if ((ptr = midi_event_reserve(port_buffer, time, data_size)) == NULL) return -ENOBUFS; memcpy (ptr, data, data_size); return 0; } SPA_EXPORT uint32_t jack_midi_get_lost_event_count(void *port_buffer) { struct midi_buffer *mb = port_buffer; if (mb == NULL || mb->magic != MIDI_BUFFER_MAGIC) return 0; return mb->lost_events; } /** extensions */ SPA_EXPORT int jack_get_video_image_size(jack_client_t *client, jack_image_size_t *size) { struct client *c = (struct client *) client; struct pw_node_activation *a; return_val_if_fail(c != NULL, 0); a = c->rt.driver_activation; if (SPA_UNLIKELY(a == NULL)) a = c->activation; if (SPA_UNLIKELY(a == NULL)) return -EIO; if (SPA_UNLIKELY(!(a->position.video.flags & SPA_IO_VIDEO_SIZE_VALID))) return -EIO; size->width = a->position.video.size.width; size->height = a->position.video.size.height; size->stride = a->position.video.stride; size->flags = 0; return size->stride * size->height; } static void reg(void) __attribute__ ((constructor)); static void reg(void) { pw_init(NULL, NULL); PW_LOG_TOPIC_INIT(jack_log_topic); pthread_mutex_init(&globals.lock, NULL); pw_array_init(&globals.descriptions, 16); spa_list_init(&globals.free_objects); } static void unreg(void) __attribute__ ((destructor)); static void unreg(void) { struct object *o, *to; pthread_mutex_lock(&globals.lock); spa_list_for_each_safe(o, to, &globals.free_objects, link) { if (!o->to_free) spa_list_remove(&o->link); } spa_list_consume(o, &globals.free_objects, link) { spa_list_remove(&o->link); free(o); } pthread_mutex_unlock(&globals.lock); pw_deinit(); } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/pipewire-jack/src/pw-jack.in000077500000000000000000000025371511204443500257150ustar00rootroot00000000000000#!/bin/sh # This file is part of PipeWire. # SPDX-FileCopyrightText: Copyright © 2020 Wim Taymans # SPDX-License-Identifier: MIT DEFAULT_SAMPLERATE=48000 while getopts 'hr:vs:p:' param ; do case $param in r) PIPEWIRE_REMOTE="$OPTARG" export PIPEWIRE_REMOTE ;; v) if [ -z "$PIPEWIRE_DEBUG" ]; then PIPEWIRE_DEBUG=3 else PIPEWIRE_DEBUG=$(( PIPEWIRE_DEBUG + 1 )) fi export PIPEWIRE_DEBUG ;; s) SAMPLERATE="$OPTARG" ;; p) PERIOD="$OPTARG" ;; *) echo "$0 - run JACK applications on PipeWire" echo " " echo "$0 [options] application [arguments]" echo " " echo "options:" echo " -h show brief help" echo " -r remote daemon name" echo " -v verbose debug info" echo " -s samplerate (default \"$DEFAULT_SAMPLERATE\")" echo " -p period in samples" exit 0 ;; esac done shift $(( OPTIND - 1 )) if [ -n "$PERIOD" ]; then if [ -n "$SAMPLERATE" ]; then PIPEWIRE_QUANTUM="$PERIOD/$SAMPLERATE" else PIPEWIRE_QUANTUM="$PERIOD/$DEFAULT_SAMPLERATE" fi export PIPEWIRE_QUANTUM fi # shellcheck disable=SC2016 # ${LIB} is interpreted by ld.so, not the shell @LIBJACK_PATH_ENABLE@LD_LIBRARY_PATH='@LIBJACK_PATH@'"${LD_LIBRARY_PATH+":$LD_LIBRARY_PATH"}" @LIBJACK_PATH_ENABLE@export LD_LIBRARY_PATH exec "$@" pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/pipewire-jack/src/ringbuffer.c000066400000000000000000000124661511204443500263250ustar00rootroot00000000000000/* PipeWire */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include "config.h" #include #include #include #include #include #include SPA_EXPORT jack_ringbuffer_t *jack_ringbuffer_create(size_t sz) { size_t power_of_two; jack_ringbuffer_t *rb; rb = calloc(1, sizeof(jack_ringbuffer_t)); if (rb == NULL) return NULL; for (power_of_two = 1; 1u << power_of_two < sz; power_of_two++); rb->size = 1 << power_of_two; rb->size_mask = rb->size - 1; if ((rb->buf = calloc(1, rb->size)) == NULL) { free (rb); return NULL; } rb->mlocked = 0; return rb; } SPA_EXPORT void jack_ringbuffer_free(jack_ringbuffer_t *rb) { #ifdef USE_MLOCK if (rb->mlocked) munlock (rb->buf, rb->size); #endif /* USE_MLOCK */ free (rb->buf); free (rb); } SPA_EXPORT void jack_ringbuffer_get_read_vector(const jack_ringbuffer_t *rb, jack_ringbuffer_data_t *vec) { size_t free_cnt; size_t cnt2; size_t w, r; w = rb->write_ptr; r = rb->read_ptr; if (w > r) free_cnt = w - r; else free_cnt = (w - r + rb->size) & rb->size_mask; cnt2 = r + free_cnt; if (cnt2 > rb->size) { vec[0].buf = &(rb->buf[r]); vec[0].len = rb->size - r; vec[1].buf = rb->buf; vec[1].len = cnt2 & rb->size_mask; } else { vec[0].buf = &(rb->buf[r]); vec[0].len = free_cnt; vec[1].len = 0; } } SPA_EXPORT void jack_ringbuffer_get_write_vector(const jack_ringbuffer_t *rb, jack_ringbuffer_data_t *vec) { size_t free_cnt; size_t cnt2; size_t w, r; w = rb->write_ptr; r = rb->read_ptr; if (w > r) free_cnt = ((r - w + rb->size) & rb->size_mask) - 1; else if (w < r) free_cnt = (r - w) - 1; else free_cnt = rb->size - 1; cnt2 = w + free_cnt; if (cnt2 > rb->size) { vec[0].buf = &(rb->buf[w]); vec[0].len = rb->size - w; vec[1].buf = rb->buf; vec[1].len = cnt2 & rb->size_mask; } else { vec[0].buf = &(rb->buf[w]); vec[0].len = free_cnt; vec[1].len = 0; } } SPA_EXPORT size_t jack_ringbuffer_read(jack_ringbuffer_t *rb, char *dest, size_t cnt) { size_t free_cnt; size_t cnt2; size_t to_read; size_t n1, n2; if ((free_cnt = jack_ringbuffer_read_space (rb)) == 0) return 0; to_read = cnt > free_cnt ? free_cnt : cnt; cnt2 = rb->read_ptr + to_read; if (cnt2 > rb->size) { n1 = rb->size - rb->read_ptr; n2 = cnt2 & rb->size_mask; } else { n1 = to_read; n2 = 0; } memcpy (dest, &(rb->buf[rb->read_ptr]), n1); rb->read_ptr = (rb->read_ptr + n1) & rb->size_mask; if (n2) { memcpy (dest + n1, &(rb->buf[rb->read_ptr]), n2); rb->read_ptr = (rb->read_ptr + n2) & rb->size_mask; } return to_read; } SPA_EXPORT size_t jack_ringbuffer_peek(jack_ringbuffer_t *rb, char *dest, size_t cnt) { size_t free_cnt; size_t cnt2; size_t to_read; size_t n1, n2; size_t tmp_read_ptr; tmp_read_ptr = rb->read_ptr; if ((free_cnt = jack_ringbuffer_read_space (rb)) == 0) return 0; to_read = cnt > free_cnt ? free_cnt : cnt; cnt2 = tmp_read_ptr + to_read; if (cnt2 > rb->size) { n1 = rb->size - tmp_read_ptr; n2 = cnt2 & rb->size_mask; } else { n1 = to_read; n2 = 0; } memcpy (dest, &(rb->buf[tmp_read_ptr]), n1); tmp_read_ptr = (tmp_read_ptr + n1) & rb->size_mask; if (n2) memcpy (dest + n1, &(rb->buf[tmp_read_ptr]), n2); return to_read; } SPA_EXPORT void jack_ringbuffer_read_advance(jack_ringbuffer_t *rb, size_t cnt) { size_t tmp = (rb->read_ptr + cnt) & rb->size_mask; rb->read_ptr = tmp; } SPA_EXPORT size_t jack_ringbuffer_read_space(const jack_ringbuffer_t *rb) { size_t w, r; w = rb->write_ptr; r = rb->read_ptr; if (w > r) return w - r; else return (w - r + rb->size) & rb->size_mask; } SPA_EXPORT int jack_ringbuffer_mlock(jack_ringbuffer_t *rb) { #ifdef USE_MLOCK if (mlock (rb->buf, rb->size)) return -1; #endif /* USE_MLOCK */ rb->mlocked = 1; return 0; } SPA_EXPORT void jack_ringbuffer_reset(jack_ringbuffer_t *rb) { rb->read_ptr = 0; rb->write_ptr = 0; memset(rb->buf, 0, rb->size); } SPA_EXPORT void jack_ringbuffer_reset_size (jack_ringbuffer_t * rb, size_t sz) { rb->size = sz; rb->size_mask = rb->size - 1; rb->read_ptr = 0; rb->write_ptr = 0; } SPA_EXPORT size_t jack_ringbuffer_write(jack_ringbuffer_t *rb, const char *src, size_t cnt) { size_t free_cnt; size_t cnt2; size_t to_write; size_t n1, n2; if ((free_cnt = jack_ringbuffer_write_space (rb)) == 0) return 0; to_write = cnt > free_cnt ? free_cnt : cnt; cnt2 = rb->write_ptr + to_write; if (cnt2 > rb->size) { n1 = rb->size - rb->write_ptr; n2 = cnt2 & rb->size_mask; } else { n1 = to_write; n2 = 0; } memcpy (&(rb->buf[rb->write_ptr]), src, n1); rb->write_ptr = (rb->write_ptr + n1) & rb->size_mask; if (n2) { memcpy (&(rb->buf[rb->write_ptr]), src + n1, n2); rb->write_ptr = (rb->write_ptr + n2) & rb->size_mask; } return to_write; } SPA_EXPORT void jack_ringbuffer_write_advance(jack_ringbuffer_t *rb, size_t cnt) { size_t tmp = (rb->write_ptr + cnt) & rb->size_mask; rb->write_ptr = tmp; } SPA_EXPORT size_t jack_ringbuffer_write_space(const jack_ringbuffer_t *rb) { size_t w, r; w = rb->write_ptr; r = rb->read_ptr; if (w > r) return ((r - w + rb->size) & rb->size_mask) - 1; else if (w < r) return (r - w) - 1; else return rb->size - 1; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/pipewire-jack/src/statistics.c000066400000000000000000000020151511204443500263530ustar00rootroot00000000000000/* PipeWire */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include SPA_EXPORT float jack_get_max_delayed_usecs (jack_client_t *client) { struct client *c = (struct client *) client; float res = 0.0f; spa_return_val_if_fail(c != NULL, 0.0); if (c->driver_activation) res = (float)c->driver_activation->max_delay / SPA_USEC_PER_SEC; pw_log_trace("%p: max delay %f", client, res); return res; } SPA_EXPORT float jack_get_xrun_delayed_usecs (jack_client_t *client) { struct client *c = (struct client *) client; float res = 0.0f; spa_return_val_if_fail(c != NULL, 0.0); if (c->driver_activation) res = (float)c->driver_activation->xrun_delay / SPA_USEC_PER_SEC; pw_log_trace("%p: xrun delay %f", client, res); return res; } SPA_EXPORT void jack_reset_max_delayed_usecs (jack_client_t *client) { struct client *c = (struct client *) client; spa_return_if_fail(c != NULL); if (c->driver_activation) c->driver_activation->max_delay = 0; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/pipewire-jack/src/uuid.c000066400000000000000000000032661511204443500251400ustar00rootroot00000000000000/* PipeWire */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include "config.h" #include #include #include #include #include SPA_EXPORT jack_uuid_t jack_client_uuid_generate (void) { static uint32_t uuid_cnt = 0; jack_uuid_t uuid = 0x2; /* JackUUIDClient */; uuid = (uuid << 32) | ++uuid_cnt; pw_log_debug("uuid %"PRIu64, uuid); return uuid; } SPA_EXPORT jack_uuid_t jack_port_uuid_generate (uint32_t port_id) { jack_uuid_t uuid = 0x1; /* JackUUIDPort */ uuid = (uuid << 32) | (port_id + 1); pw_log_debug("uuid %d -> %"PRIu64, port_id, uuid); return uuid; } SPA_EXPORT uint32_t jack_uuid_to_index (jack_uuid_t id) { return (id & 0xffffff) - 1; } SPA_EXPORT int jack_uuid_compare (jack_uuid_t id1, jack_uuid_t id2) { if (id1 == id2) return 0; if (id1 < id2) return -1; return 1; } SPA_EXPORT void jack_uuid_copy (jack_uuid_t* dst, jack_uuid_t src) { spa_return_if_fail(dst != NULL); *dst = src; } SPA_EXPORT void jack_uuid_clear (jack_uuid_t *id) { spa_return_if_fail(id != NULL); *id = 0; } SPA_EXPORT int jack_uuid_parse (const char *buf, jack_uuid_t *id) { spa_return_val_if_fail(buf != NULL, -EINVAL); spa_return_val_if_fail(id != NULL, -EINVAL); if (sscanf (buf, "%" PRIu64, id) == 1) { if (*id < (0x1LL << 32)) { /* has not type bits set - not legal */ return -1; } return 0; } return -1; } SPA_EXPORT void jack_uuid_unparse (jack_uuid_t id, char buf[JACK_UUID_STRING_SIZE]) { spa_return_if_fail(buf != NULL); snprintf (buf, JACK_UUID_STRING_SIZE, "%" PRIu64, id); } SPA_EXPORT int jack_uuid_empty (jack_uuid_t id) { return id == 0; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/pipewire-v4l2/000077500000000000000000000000001511204443500231075ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/pipewire-v4l2/meson.build000066400000000000000000000000161511204443500252460ustar00rootroot00000000000000subdir('src') pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/pipewire-v4l2/src/000077500000000000000000000000001511204443500236765ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/pipewire-v4l2/src/meson.build000066400000000000000000000024151511204443500260420ustar00rootroot00000000000000pipewire_v4l2_sources = [ 'pipewire-v4l2.c', 'v4l2-func.c', ] pipewire_v4l2_c_args = [ # Meson enables large file support unconditionally, which redirect file # operations to 64-bit versions. This results in some symbols being # renamed, for instance open() being renamed to open64(). As the V4L2 # adaptation wrapper needs to provide both 32-bit and 64-bit versions of # file operations, disable transparent large file support. '-U_FILE_OFFSET_BITS', '-D_FILE_OFFSET_BITS=32', '-D_LARGEFILE64_SOURCE', '-U_TIME_BITS', '-fvisibility=hidden', ] libv4l2_path = get_option('libv4l2-path') if libv4l2_path == '' libv4l2_path = modules_install_dir / 'v4l2' libv4l2_path_dlopen = modules_install_dir_dlopen / 'v4l2' else libv4l2_path_dlopen = libv4l2_path endif tools_config = configuration_data() tools_config.set('LIBV4L2_PATH', libv4l2_path_dlopen) configure_file(input : 'pw-v4l2.in', output : 'pw-v4l2', configuration : tools_config, install_dir : pipewire_bindir) pipewire_v4l2 = shared_library('pw-v4l2', pipewire_v4l2_sources, c_args : pipewire_v4l2_c_args, include_directories : [configinc], dependencies : [pipewire_dep, mathlib, dl_lib], install : true, install_dir : libv4l2_path, ) pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/pipewire-v4l2/src/pipewire-v4l2.c000066400000000000000000002003021511204443500264500ustar00rootroot00000000000000/* PipeWire */ /* SPDX-FileCopyrightText: Copyright © 2021 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "pipewire-v4l2.h" #include #include #include #include #include #include #include #include PW_LOG_TOPIC_STATIC(v4l2_log_topic, "v4l2"); #define PW_LOG_TOPIC_DEFAULT v4l2_log_topic #define MIN_BUFFERS 2u #define MAX_BUFFERS 32u #define DEFAULT_TIMEOUT 30 #define DEFAULT_DRIVER "PipeWire" #define DEFAULT_CARD "PipeWire Camera" #define DEFAULT_BUS_INFO "PipeWire" #define MAX_DEV 32 struct file_map { void *addr; struct file *file; }; struct fd_map { int fd; #define FD_MAP_DUP (1<<0) uint32_t flags; struct file *file; }; struct globals { struct fops old_fops; pthread_mutex_t lock; struct pw_array fd_maps; struct pw_array file_maps; uint32_t dev_map[MAX_DEV]; }; static struct globals globals; struct global; struct buffer_map { void *addr; uint32_t id; }; struct buffer { struct v4l2_buffer v4l2; struct pw_buffer *buf; uint32_t id; }; struct file { int ref; uint32_t dev_id; uint32_t serial; struct pw_properties *props; struct pw_thread_loop *loop; struct pw_loop *l; struct pw_context *context; struct pw_core *core; struct spa_hook core_listener; int last_seq; int pending_seq; int error; struct pw_registry *registry; struct spa_hook registry_listener; struct spa_list globals; struct global *node; struct pw_stream *stream; struct spa_hook stream_listener; enum v4l2_priority priority; struct v4l2_format v4l2_format; uint32_t reqbufs; int reqbufs_fd; struct buffer buffers[MAX_BUFFERS]; uint32_t n_buffers; uint32_t size; uint32_t sequence; struct pw_array buffer_maps; uint32_t last_fourcc; unsigned int running:1; unsigned int closed:1; int fd; }; struct global_info { const char *type; uint32_t version; const void *events; pw_destroy_t destroy; int (*init) (struct global *g); }; struct global { struct spa_list link; struct file *file; const struct global_info *ginfo; uint32_t id; uint32_t permissions; struct pw_properties *props; struct pw_proxy *proxy; struct spa_hook proxy_listener; struct spa_hook object_listener; int changed; void *info; struct spa_list pending_list; struct spa_list param_list; union { struct { #define NODE_FLAG_SOURCE (1<<0) #define NODE_FLAG_SINK (1<<0) uint32_t flags; uint32_t device_id; int priority; } node; }; }; struct param { struct spa_list link; uint32_t id; int32_t seq; struct spa_pod *param; }; static uint32_t clear_params(struct spa_list *param_list, uint32_t id) { struct param *p, *t; uint32_t count = 0; spa_list_for_each_safe(p, t, param_list, link) { if (id == SPA_ID_INVALID || p->id == id) { spa_list_remove(&p->link); free(p); count++; } } return count; } static struct param *add_param(struct spa_list *params, int seq, uint32_t id, const struct spa_pod *param) { struct param *p; if (id == SPA_ID_INVALID) { if (param == NULL || !spa_pod_is_object(param)) { errno = EINVAL; return NULL; } id = SPA_POD_OBJECT_ID(param); } p = malloc(sizeof(*p) + (param != NULL ? SPA_POD_SIZE(param) : 0)); if (p == NULL) return NULL; p->id = id; p->seq = seq; if (param != NULL) { p->param = SPA_PTROFF(p, sizeof(*p), struct spa_pod); memcpy(p->param, param, SPA_POD_SIZE(param)); } else { clear_params(params, id); p->param = NULL; } spa_list_append(params, &p->link); return p; } static void update_params(struct file *file) { struct param *p, *t; struct global *node; struct pw_node_info *info; uint32_t i; if ((node = file->node) == NULL) return; if ((info = node->info) == NULL) return; for (i = 0; i < info->n_params; i++) { spa_list_for_each_safe(p, t, &node->pending_list, link) { if (p->id == info->params[i].id && p->seq != info->params[i].seq && p->param != NULL) { spa_list_remove(&p->link); free(p); } } } spa_list_consume(p, &node->pending_list, link) { spa_list_remove(&p->link); if (p->param == NULL) { clear_params(&node->param_list, p->id); free(p); } else { spa_list_append(&node->param_list, &p->link); } } } static struct file *make_file(void) { struct file *file; file = calloc(1, sizeof(*file)); if (file == NULL) return NULL; file->ref = 1; file->fd = -1; file->reqbufs_fd = -1; file->priority = V4L2_PRIORITY_DEFAULT; spa_list_init(&file->globals); pw_array_init(&file->buffer_maps, sizeof(struct buffer_map) * MAX_BUFFERS); return file; } static void free_file(struct file *file) { pw_log_info("file:%d", file->fd); if (file->loop) pw_thread_loop_stop(file->loop); if (file->registry) { spa_hook_remove(&file->registry_listener); pw_proxy_destroy((struct pw_proxy*)file->registry); } if (file->stream) { spa_hook_remove(&file->stream_listener); pw_stream_destroy(file->stream); } if (file->core) { spa_hook_remove(&file->core_listener); pw_core_disconnect(file->core); } if (file->context) pw_context_destroy(file->context); if (file->fd != -1) spa_system_close(file->l->system, file->fd); if (file->loop) pw_thread_loop_destroy(file->loop); pw_array_clear(&file->buffer_maps); free(file); } static void unref_file(struct file *file) { pw_log_debug("file:%d ref:%d", file->fd, file->ref); if (SPA_ATOMIC_DEC(file->ref) <= 0) free_file(file); } static int add_fd_map(int fd, struct file *file, uint32_t flags) { struct fd_map *map; pthread_mutex_lock(&globals.lock); map = pw_array_add(&globals.fd_maps, sizeof(*map)); if (map != NULL) { map->fd = fd; map->flags = flags; map->file = file; SPA_ATOMIC_INC(file->ref); pw_log_debug("fd:%d -> file:%d ref:%d", fd, file->fd, file->ref); } pthread_mutex_unlock(&globals.lock); return 0; } static uint32_t find_dev_for_serial(uint32_t serial) { uint32_t i, res = SPA_ID_INVALID; pthread_mutex_lock(&globals.lock); for (i = 0; i < SPA_N_ELEMENTS(globals.dev_map); i++) { if (globals.dev_map[i] == serial) { res = i; break; } } pthread_mutex_unlock(&globals.lock); return res; } static bool add_dev_for_serial(uint32_t dev, uint32_t serial) { pthread_mutex_lock(&globals.lock); globals.dev_map[dev] = serial; pthread_mutex_unlock(&globals.lock); return true; } /* must be called with `globals.lock` held */ static struct fd_map *find_fd_map_unlocked(int fd) { struct fd_map *map; pw_array_for_each(map, &globals.fd_maps) { if (map->fd == fd) { SPA_ATOMIC_INC(map->file->ref); pw_log_debug("fd:%d find:%d ref:%d", map->fd, fd, map->file->ref); return map; } } return NULL; } static struct file *find_file(int fd, uint32_t *flags) { pthread_mutex_lock(&globals.lock); struct fd_map *map = find_fd_map_unlocked(fd); struct file *file = NULL; if (map != NULL) { file = map->file; *flags = map->flags; } pthread_mutex_unlock(&globals.lock); return file; } static struct file *find_file_by_dev(uint32_t dev) { struct fd_map *map = NULL, *tmp; struct file *file = NULL; pthread_mutex_lock(&globals.lock); pw_array_for_each(tmp, &globals.fd_maps) { if (tmp->file->dev_id == dev) { if (tmp->file->closed) tmp->file->fd = tmp->fd; SPA_ATOMIC_INC(tmp->file->ref); map = tmp; pw_log_debug("dev:%d find:%d ref:%d", tmp->file->dev_id, dev, tmp->file->ref); break; } } if (map != NULL) file = map->file; pthread_mutex_unlock(&globals.lock); return file; } static struct file *remove_fd_map(int fd) { pthread_mutex_lock(&globals.lock); struct fd_map *map = find_fd_map_unlocked(fd); struct file *file = NULL; if (map != NULL) { file = map->file; pw_log_debug("fd:%d find:%d", map->fd, fd); pw_array_remove(&globals.fd_maps, map); } pthread_mutex_unlock(&globals.lock); if (file != NULL) unref_file(file); return file; } static int add_file_map(struct file *file, void *addr) { struct file_map *map; pthread_mutex_lock(&globals.lock); map = pw_array_add(&globals.file_maps, sizeof(*map)); if (map != NULL) { map->addr = addr; map->file = file; } pthread_mutex_unlock(&globals.lock); return 0; } /* must be called with `globals.lock` held */ static struct file_map *find_file_map_unlocked(void *addr) { struct file_map *map; pw_array_for_each(map, &globals.file_maps) { if (map->addr == addr) return map; } return NULL; } static struct file *remove_file_map(void *addr) { pthread_mutex_lock(&globals.lock); struct file_map *map = find_file_map_unlocked(addr); struct file *file = NULL; if (map != NULL) { file = map->file; pw_array_remove(&globals.file_maps, map); } pthread_mutex_unlock(&globals.lock); return file; } static int add_buffer_map(struct file *file, void *addr, uint32_t id) { struct buffer_map *map; map = pw_array_add(&file->buffer_maps, sizeof(*map)); if (map != NULL) { map->addr = addr; map->id = id; } return 0; } static struct buffer_map *find_buffer_map(struct file *file, void *addr) { struct buffer_map *map; pw_array_for_each(map, &file->buffer_maps) { if (map->addr == addr) return map; } return NULL; } static void remove_buffer_map(struct file *file, struct buffer_map *map) { pw_array_remove(&file->buffer_maps, map); } static void do_resync(struct file *file) { file->pending_seq = pw_core_sync(file->core, PW_ID_CORE, file->pending_seq); } static int wait_resync(struct file *file) { int res; do_resync(file); while (true) { pw_thread_loop_wait(file->loop); res = file->error; if (res < 0) { file->error = 0; return res; } if (file->pending_seq == file->last_seq) break; } return 0; } static void on_sync_reply(void *data, uint32_t id, int seq) { struct file *file = data; if (id != PW_ID_CORE) return; file->last_seq = seq; if (file->pending_seq == seq) { update_params(file); pw_thread_loop_signal(file->loop, false); } } static void on_error(void *data, uint32_t id, int seq, int res, const char *message) { struct file *file = data; pw_log_warn("file:%d: error id:%u seq:%d res:%d (%s): %s", file->fd, id, seq, res, spa_strerror(res), message); if (id == PW_ID_CORE) { switch (res) { case -ENOENT: break; default: file->error = res; } } pw_thread_loop_signal(file->loop, false); } static const struct pw_core_events core_events = { PW_VERSION_CORE_EVENTS, .done = on_sync_reply, .error = on_error, }; /** node */ static void node_event_info(void *object, const struct pw_node_info *info) { struct global *g = object; struct file *file = g->file; const char *str; uint32_t i; info = g->info = pw_node_info_merge(g->info, info, g->changed == 0); if (info == NULL) return; pw_log_debug("update %d %"PRIu64, g->id, info->change_mask); if (info->change_mask & PW_NODE_CHANGE_MASK_PROPS && info->props) { if ((str = spa_dict_lookup(info->props, PW_KEY_DEVICE_ID))) g->node.device_id = atoi(str); else g->node.device_id = SPA_ID_INVALID; if ((str = spa_dict_lookup(info->props, PW_KEY_PRIORITY_SESSION))) g->node.priority = atoi(str); if ((str = spa_dict_lookup(info->props, PW_KEY_MEDIA_CLASS))) { if (spa_streq(str, "Video/Sink")) g->node.flags |= NODE_FLAG_SINK; else if (spa_streq(str, "Video/Source")) g->node.flags |= NODE_FLAG_SOURCE; } } if (info->change_mask & PW_NODE_CHANGE_MASK_PARAMS) { for (i = 0; i < info->n_params; i++) { uint32_t id = info->params[i].id; int res; if (info->params[i].user == 0) continue; info->params[i].user = 0; add_param(&g->pending_list, info->params[i].seq, id, NULL); if (!(info->params[i].flags & SPA_PARAM_INFO_READ)) continue; res = pw_node_enum_params((struct pw_node*)g->proxy, ++info->params[i].seq, id, 0, -1, NULL); if (SPA_RESULT_IS_ASYNC(res)) info->params[i].seq = res; } } do_resync(file); } static void node_event_param(void *object, int seq, uint32_t id, uint32_t index, uint32_t next, const struct spa_pod *param) { struct global *g = object; pw_log_debug("update param %d %d %d", g->id, id, seq); add_param(&g->pending_list, seq, id, param); } static const struct pw_node_events node_events = { PW_VERSION_NODE_EVENTS, .info = node_event_info, .param = node_event_param, }; static const struct global_info node_info = { .type = PW_TYPE_INTERFACE_Node, .version = PW_VERSION_NODE, .events = &node_events, }; /** proxy */ static void proxy_removed(void *data) { struct global *g = data; pw_proxy_destroy(g->proxy); } static void proxy_destroy(void *data) { struct global *g = data; spa_list_remove(&g->link); g->proxy = NULL; if (g->file) g->file->node = NULL; clear_params(&g->param_list, SPA_ID_INVALID); clear_params(&g->pending_list, SPA_ID_INVALID); } static const struct pw_proxy_events proxy_events = { PW_VERSION_PROXY_EVENTS, .removed = proxy_removed, .destroy = proxy_destroy }; static void registry_event_global(void *data, uint32_t id, uint32_t permissions, const char *type, uint32_t version, const struct spa_dict *props) { struct file *file = data; const struct global_info *info = NULL; struct pw_proxy *proxy; const char *str; uint32_t serial = SPA_ID_INVALID, dev, req_serial; if (spa_streq(type, PW_TYPE_INTERFACE_Node)) { if (file->node != NULL) return; pw_log_info("got %d %s", id, type); if (props == NULL) return; if (((str = spa_dict_lookup(props, PW_KEY_MEDIA_CLASS)) == NULL) || ((!spa_streq(str, "Video/Sink")) && (!spa_streq(str, "Video/Source")))) return; if (((str = spa_dict_lookup(props, PW_KEY_OBJECT_SERIAL)) == NULL) || !spa_atou32(str, &serial, 10)) return; if ((str = getenv("PIPEWIRE_V4L2_TARGET")) != NULL && spa_atou32(str, &req_serial, 10) && req_serial != serial) return; dev = find_dev_for_serial(serial); if (dev != SPA_ID_INVALID && dev != file->dev_id) return; pw_log_info("found node:%d serial:%d type:%s", id, serial, str); info = &node_info; } if (info) { struct global *g; proxy = pw_registry_bind(file->registry, id, info->type, info->version, sizeof(struct global)); g = pw_proxy_get_user_data(proxy); g->file = file; g->ginfo = info; g->id = id; g->permissions = permissions; g->props = props ? pw_properties_new_dict(props) : NULL; g->proxy = proxy; spa_list_init(&g->pending_list); spa_list_init(&g->param_list); spa_list_append(&file->globals, &g->link); pw_proxy_add_listener(proxy, &g->proxy_listener, &proxy_events, g); if (info->events) { pw_proxy_add_object_listener(proxy, &g->object_listener, info->events, g); } if (info->init) info->init(g); file->serial = serial; file->node = g; do_resync(file); } } static struct global *find_global(struct file *file, uint32_t id) { struct global *g; spa_list_for_each(g, &file->globals, link) { if (g->id == id) return g; } return NULL; } static void registry_event_global_remove(void *data, uint32_t id) { struct file *file = data; struct global *g; if ((g = find_global(file, id)) == NULL) return; pw_proxy_destroy(g->proxy); } static const struct pw_registry_events registry_events = { PW_VERSION_REGISTRY_EVENTS, .global = registry_event_global, .global_remove = registry_event_global_remove, }; static int do_dup(int oldfd, uint32_t flags) { int res; struct file *file; uint32_t fl; res = globals.old_fops.dup(oldfd); if (res < 0) return res; if ((file = find_file(oldfd, &fl)) != NULL) { add_fd_map(res, file, flags | fl); unref_file(file); pw_log_info("fd:%d %08x -> %d (%s)", oldfd, flags, res, strerror(res < 0 ? errno : 0)); } return res; } static int v4l2_dup(int oldfd) { return do_dup(oldfd, FD_MAP_DUP); } /* Deferred PipeWire init (called on first device access) */ static void pipewire_init_func(void) { pw_init(NULL, NULL); PW_LOG_TOPIC_INIT(v4l2_log_topic); } static void ensure_pipewire_init(void) { static pthread_once_t pipewire_once = PTHREAD_ONCE_INIT; pthread_once(&pipewire_once, pipewire_init_func); } static int v4l2_openat(int dirfd, const char *path, int oflag, mode_t mode) { int res, flags; struct file *file; bool passthrough = true; uint32_t dev_id = SPA_ID_INVALID; char *real_path; real_path = realpath(path, NULL); if (!real_path) real_path = (char *)path; if (spa_strstartswith(real_path, "/dev/video")) { if (spa_atou32(real_path+10, &dev_id, 10) && dev_id < MAX_DEV) passthrough = false; } if (real_path && real_path != path) free(real_path); if (passthrough) return globals.old_fops.openat(dirfd, path, oflag, mode); ensure_pipewire_init(); pw_log_info("path:%s oflag:%d mode:%d", path, oflag, mode); if ((file = find_file_by_dev(dev_id)) != NULL) { res = do_dup(file->fd, 0); unref_file(file); if (res < 0) return res; if (fcntl(res, F_SETFL, oflag) < 0) pw_log_warn("fd:%d failed to set flags: %m", res); return res; } if ((file = make_file()) == NULL) goto error; file->dev_id = dev_id; file->props = pw_properties_new( PW_KEY_CLIENT_API, "v4l2", NULL); file->loop = pw_thread_loop_new("v4l2", NULL); if (file->loop == NULL) goto error; file->l = pw_thread_loop_get_loop(file->loop); file->context = pw_context_new(file->l, pw_properties_copy(file->props), 0); if (file->context == NULL) goto error; pw_thread_loop_start(file->loop); pw_thread_loop_lock(file->loop); file->core = pw_context_connect(file->context, pw_properties_copy(file->props), 0); if (file->core == NULL) goto error_unlock; pw_core_add_listener(file->core, &file->core_listener, &core_events, file); file->registry = pw_core_get_registry(file->core, PW_VERSION_REGISTRY, 0); if (file->registry == NULL) goto error_unlock; pw_registry_add_listener(file->registry, &file->registry_listener, ®istry_events, file); res = wait_resync(file); if (res < 0) { errno = -res; goto error_unlock; } if (file->node == NULL) { errno = ENOENT; goto error_unlock; } pw_thread_loop_unlock(file->loop); flags = SPA_FD_CLOEXEC; if (oflag & O_NONBLOCK) flags |= SPA_FD_NONBLOCK; res = spa_system_eventfd_create(file->l->system, flags); if (res < 0) goto error; file->fd = res; pw_log_info("path:%s oflag:%d mode:%d -> %d (%s)", path, oflag, mode, res, strerror(res < 0 ? errno : 0)); add_fd_map(res, file, 0); add_dev_for_serial(file->dev_id, file->serial); unref_file(file); return res; error_unlock: pw_thread_loop_unlock(file->loop); error: res = -errno; if (file) free_file(file); pw_log_info("path:%s oflag:%d mode:%d -> %d (%s)", path, oflag, mode, -1, spa_strerror(res)); errno = -res; return -1; } static int v4l2_close(int fd) { struct file *file; if ((file = remove_fd_map(fd)) == NULL) return globals.old_fops.close(fd); pw_log_info("fd:%d file:%d", fd, file->fd); if (fd != file->fd) spa_system_close(file->l->system, fd); file->closed = true; unref_file(file); return 0; } #define KERNEL_VERSION(a, b, c) (((a) << 16) + ((b) << 8) + (c)) static int vidioc_querycap(struct file *file, struct v4l2_capability *arg) { int res = 0; const char *card = NULL, *bus_info = NULL; struct pw_node_info *info; if (file->node == NULL) return -EIO; info = file->node->info; if (info != NULL && info->props != NULL) { card = spa_dict_lookup(info->props, PW_KEY_NODE_DESCRIPTION); bus_info = spa_dict_lookup(info->props, SPA_KEY_API_V4L2_CAP_BUS_INFO); } if (card == NULL) card = DEFAULT_CARD; spa_scnprintf((char*)arg->driver, sizeof(arg->driver), "%s", DEFAULT_DRIVER); spa_scnprintf((char*)arg->card, sizeof(arg->card), "%s", card); if (bus_info == NULL) spa_scnprintf((char*)arg->bus_info, sizeof(arg->bus_info), "platform:%s-%d", DEFAULT_BUS_INFO, file->node->id); else spa_scnprintf((char*)arg->bus_info, sizeof(arg->bus_info), "%s", bus_info); arg->version = KERNEL_VERSION(5, 2, 0); arg->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING | V4L2_CAP_EXT_PIX_FORMAT; arg->capabilities = arg->device_caps | V4L2_CAP_DEVICE_CAPS; memset(arg->reserved, 0, sizeof(arg->reserved)); pw_log_info("file:%d -> %d (%s)", file->fd, res, spa_strerror(res)); return res; } struct format_info { uint32_t fourcc; uint32_t media_type; uint32_t media_subtype; uint32_t format; uint32_t bpp; const char *desc; }; #define MAKE_FORMAT(fcc,mt,mst,bpp,fmt,...) \ { V4L2_PIX_FMT_ ## fcc, SPA_MEDIA_TYPE_ ## mt, SPA_MEDIA_SUBTYPE_ ## mst, SPA_VIDEO_FORMAT_ ## fmt, bpp, __VA_ARGS__ } static const struct format_info format_info[] = { /* RGB formats */ MAKE_FORMAT(RGB332, video, raw, 4, UNKNOWN, "8-bit RGB 3-3-2"), MAKE_FORMAT(ARGB555, video, raw, 4, UNKNOWN), MAKE_FORMAT(XRGB555, video, raw, 4, RGB15, "16-bit XRGB 1-5-5-5"), MAKE_FORMAT(ARGB555X, video, raw, 4, UNKNOWN), MAKE_FORMAT(XRGB555X, video, raw, 4, BGR15, "16-bit XRGB 1-5-5-5 BE"), MAKE_FORMAT(RGB565, video, raw, 4, RGB16, "16-bit RGB 5-6-5"), MAKE_FORMAT(RGB565X, video, raw, 4, UNKNOWN), MAKE_FORMAT(BGR666, video, raw, 4, UNKNOWN), MAKE_FORMAT(BGR24, video, raw, 4, BGR), MAKE_FORMAT(RGB24, video, raw, 4, RGB), MAKE_FORMAT(ABGR32, video, raw, 4, BGRA), MAKE_FORMAT(XBGR32, video, raw, 4, BGRx), MAKE_FORMAT(ARGB32, video, raw, 4, ARGB), MAKE_FORMAT(XRGB32, video, raw, 4, xRGB), /* Deprecated Packed RGB Image Formats (alpha ambiguity) */ MAKE_FORMAT(RGB444, video, raw, 2, UNKNOWN), MAKE_FORMAT(RGB555, video, raw, 2, RGB15), MAKE_FORMAT(RGB555X, video, raw, 2, BGR15), MAKE_FORMAT(BGR32, video, raw, 4, BGRx), MAKE_FORMAT(RGB32, video, raw, 4, xRGB), /* Grey formats */ MAKE_FORMAT(GREY, video, raw, 1, GRAY8), MAKE_FORMAT(Y4, video, raw, 1, UNKNOWN), MAKE_FORMAT(Y6, video, raw, 1, UNKNOWN), MAKE_FORMAT(Y10, video, raw, 2, UNKNOWN), MAKE_FORMAT(Y12, video, raw, 2, UNKNOWN), MAKE_FORMAT(Y16, video, raw, 2, GRAY16_LE), #ifdef V4L2_PIX_FMT_Y16_BE MAKE_FORMAT(Y16_BE, video, raw, 2, GRAY16_BE), #endif MAKE_FORMAT(Y10BPACK, video, raw, 2, UNKNOWN), /* Palette formats */ MAKE_FORMAT(PAL8, video, raw, 1, UNKNOWN), /* Chrominance formats */ MAKE_FORMAT(UV8, video, raw, 2, UNKNOWN), /* Luminance+Chrominance formats */ MAKE_FORMAT(YVU410, video, raw, 1, YVU9, "Planar YVU 4:1:0"), MAKE_FORMAT(YVU420, video, raw, 1, YV12, "Planar YVU 4:2:0"), MAKE_FORMAT(YVU420M, video, raw, 1, UNKNOWN), MAKE_FORMAT(YUYV, video, raw, 2, YUY2, "YUYV 4:2:2"), MAKE_FORMAT(YYUV, video, raw, 2, UNKNOWN), MAKE_FORMAT(YVYU, video, raw, 2, YVYU, "YVYU 4:2:2"), MAKE_FORMAT(UYVY, video, raw, 2, UYVY, "UYVY 4:2:2"), MAKE_FORMAT(VYUY, video, raw, 2, UNKNOWN), MAKE_FORMAT(YUV422P, video, raw, 1, Y42B), MAKE_FORMAT(YUV411P, video, raw, 1, Y41B), MAKE_FORMAT(Y41P, video, raw, 1, UNKNOWN), MAKE_FORMAT(YUV444, video, raw, 1, UNKNOWN), MAKE_FORMAT(YUV555, video, raw, 1, UNKNOWN), MAKE_FORMAT(YUV565, video, raw, 1, UNKNOWN), MAKE_FORMAT(YUV32, video, raw, 1, UNKNOWN), MAKE_FORMAT(YUV410, video, raw, 1, YUV9), MAKE_FORMAT(YUV420, video, raw, 1, I420, "Planar YUV 4:2:0"), MAKE_FORMAT(YUV420M, video, raw, 1, I420, "Planar YUV 4:2:0 (N-C)"), MAKE_FORMAT(HI240, video, raw, 1, UNKNOWN), MAKE_FORMAT(HM12, video, raw, 1, UNKNOWN), MAKE_FORMAT(M420, video, raw, 1, UNKNOWN), /* two planes -- one Y, one Cr + Cb interleaved */ MAKE_FORMAT(NV12, video, raw, 1, NV12, "Y/CbCr 4:2:0"), MAKE_FORMAT(NV12M, video, raw, 1, NV12, "Y/CbCr 4:2:0 (N-C)"), MAKE_FORMAT(NV12MT, video, raw, 1, NV12_64Z32, "Y/CbCr 4:2:0 (64x32 MB, N-C)"), MAKE_FORMAT(NV12MT_16X16, video, raw, 1, UNKNOWN), MAKE_FORMAT(NV21, video, raw, 1, NV21, "Y/CrCb 4:2:0"), MAKE_FORMAT(NV21M, video, raw, 1, NV21, "Y/CrCb 4:2:0 (N-C)"), MAKE_FORMAT(NV16, video, raw, 1, NV16, "Y/CbCr 4:2:2"), MAKE_FORMAT(NV16M, video, raw, 1, NV16, "Y/CbCr 4:2:2 (N-C)"), MAKE_FORMAT(NV61, video, raw, 1, NV61, "Y/CrCb 4:2:2"), MAKE_FORMAT(NV61M, video, raw, 1, NV61, "Y/CrCb 4:2:2 (N-C)"), MAKE_FORMAT(NV24, video, raw, 1, NV24, "Y/CbCr 4:4:4"), MAKE_FORMAT(NV42, video, raw, 1, UNKNOWN), /* Bayer formats - see http://www.siliconimaging.com/RGB%20Bayer.htm */ MAKE_FORMAT(SBGGR8, video, bayer, 1, UNKNOWN), MAKE_FORMAT(SGBRG8, video, bayer, 1, UNKNOWN), MAKE_FORMAT(SGRBG8, video, bayer, 1, UNKNOWN), MAKE_FORMAT(SRGGB8, video, bayer, 1, UNKNOWN), /* compressed formats */ MAKE_FORMAT(MJPEG, video, mjpg, 1, ENCODED, "Motion-JPEG"), MAKE_FORMAT(JPEG, video, mjpg, 1, ENCODED, "JFIF JPEG"), MAKE_FORMAT(PJPG, video, mjpg, 1, ENCODED, "GSPCA PJPG"), MAKE_FORMAT(DV, video, dv, 1, ENCODED), MAKE_FORMAT(MPEG, video, mpegts, 1, ENCODED), MAKE_FORMAT(H264, video, h264, 1, ENCODED, "H.264"), MAKE_FORMAT(H264_NO_SC, video, h264, 1, ENCODED, "H.264 (No Start Codes)"), MAKE_FORMAT(H264_MVC, video, h264, 1, ENCODED, "H.264 MVC"), MAKE_FORMAT(H263, video, h263, 1, ENCODED), MAKE_FORMAT(MPEG1, video, mpeg1, 1, ENCODED), MAKE_FORMAT(MPEG2, video, mpeg2, 1, ENCODED), MAKE_FORMAT(MPEG4, video, mpeg4, 1, ENCODED), MAKE_FORMAT(XVID, video, xvid, 1, ENCODED), MAKE_FORMAT(VC1_ANNEX_G, video, vc1, 1, ENCODED), MAKE_FORMAT(VC1_ANNEX_L, video, vc1, 1, ENCODED), MAKE_FORMAT(VP8, video, vp8, 1, ENCODED), /* Vendor-specific formats */ MAKE_FORMAT(WNVA, video, raw, 1, UNKNOWN), MAKE_FORMAT(SN9C10X, video, raw, 1, UNKNOWN), MAKE_FORMAT(PWC1, video, raw, 1, UNKNOWN), MAKE_FORMAT(PWC2, video, raw, 1, UNKNOWN), }; static const struct format_info *format_info_from_media_type(uint32_t type, uint32_t subtype, uint32_t format) { SPA_FOR_EACH_ELEMENT_VAR(format_info, i) if ((i->media_type == type) && (i->media_subtype == subtype) && (format == 0 || i->format == format)) return i; return NULL; } static const struct format_info *format_info_from_fourcc(uint32_t fourcc) { SPA_FOR_EACH_ELEMENT_VAR(format_info, i) if (i->fourcc == fourcc) return i; return NULL; } static int format_to_info(const struct v4l2_format *arg, struct spa_video_info *info) { const struct format_info *fi; pw_log_info("type: %u", arg->type); pw_log_info("width: %u", arg->fmt.pix.width); pw_log_info("height: %u", arg->fmt.pix.height); pw_log_info("fmt: %.4s", (char*)&arg->fmt.pix.pixelformat); if (arg->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) return -EINVAL; fi = format_info_from_fourcc(arg->fmt.pix.pixelformat); if (fi == NULL) return -EINVAL; spa_zero(*info); info->media_type = fi->media_type; info->media_subtype = fi->media_subtype; switch (info->media_subtype) { case SPA_MEDIA_SUBTYPE_raw: info->info.raw.format = fi->format; info->info.raw.size.width = arg->fmt.pix.width; info->info.raw.size.height = arg->fmt.pix.height; break; case SPA_MEDIA_SUBTYPE_h264: info->info.h264.size.width = arg->fmt.pix.width; info->info.h264.size.height = arg->fmt.pix.height; break; case SPA_MEDIA_SUBTYPE_mjpg: case SPA_MEDIA_SUBTYPE_jpeg: info->info.mjpg.size.width = arg->fmt.pix.width; info->info.mjpg.size.height = arg->fmt.pix.height; break; default: return -EINVAL; } return 0; } static struct spa_pod *info_to_param(struct spa_pod_builder *builder, uint32_t id, struct spa_video_info *info) { struct spa_pod *pod; if (info->media_type != SPA_MEDIA_TYPE_video) return NULL; switch (info->media_subtype) { case SPA_MEDIA_SUBTYPE_raw: pod = spa_format_video_raw_build(builder, id, &info->info.raw); break; case SPA_MEDIA_SUBTYPE_mjpg: case SPA_MEDIA_SUBTYPE_jpeg: pod = spa_format_video_mjpg_build(builder, id, &info->info.mjpg); break; case SPA_MEDIA_SUBTYPE_h264: pod = spa_format_video_h264_build(builder, id, &info->info.h264); break; default: return NULL; } return pod; } static struct spa_pod *fmt_to_param(struct spa_pod_builder *builder, uint32_t id, const struct v4l2_format *fmt) { struct spa_video_info info; if (format_to_info(fmt, &info) < 0) return NULL; return info_to_param(builder, id, &info); } static int param_to_info(const struct spa_pod *param, struct spa_video_info *info) { int res; spa_zero(*info); if (spa_format_parse(param, &info->media_type, &info->media_subtype) < 0) return -EINVAL; if (info->media_type != SPA_MEDIA_TYPE_video) return -EINVAL; switch (info->media_subtype) { case SPA_MEDIA_SUBTYPE_raw: res = spa_format_video_raw_parse(param, &info->info.raw); break; case SPA_MEDIA_SUBTYPE_h264: res = spa_format_video_h264_parse(param, &info->info.h264); break; case SPA_MEDIA_SUBTYPE_mjpg: case SPA_MEDIA_SUBTYPE_jpeg: res = spa_format_video_mjpg_parse(param, &info->info.mjpg); break; default: return -EINVAL; } return res; } static int info_to_fmt(const struct spa_video_info *info, struct v4l2_format *fmt) { const struct format_info *fi; uint32_t format; if (info->media_type != SPA_MEDIA_TYPE_video) return -EINVAL; if (info->media_subtype == SPA_MEDIA_SUBTYPE_raw) { format = info->info.raw.format; } else { format = SPA_VIDEO_FORMAT_ENCODED; } fi = format_info_from_media_type(info->media_type, info->media_subtype, format); if (fi == NULL) return -EINVAL; spa_zero(*fmt); fmt->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; fmt->fmt.pix.pixelformat = fi->fourcc; fmt->fmt.pix.field = V4L2_FIELD_NONE; switch (info->media_subtype) { case SPA_MEDIA_SUBTYPE_raw: fmt->fmt.pix.width = info->info.raw.size.width; fmt->fmt.pix.height = info->info.raw.size.height; fmt->fmt.pix.colorspace = V4L2_COLORSPACE_SRGB; break; case SPA_MEDIA_SUBTYPE_mjpg: case SPA_MEDIA_SUBTYPE_jpeg: fmt->fmt.pix.width = info->info.mjpg.size.width; fmt->fmt.pix.height = info->info.mjpg.size.height; fmt->fmt.pix.colorspace = V4L2_COLORSPACE_JPEG; break; case SPA_MEDIA_SUBTYPE_h264: fmt->fmt.pix.width = info->info.h264.size.width; fmt->fmt.pix.height = info->info.h264.size.height; fmt->fmt.pix.colorspace = V4L2_COLORSPACE_SRGB; break; default: return -EINVAL; } if (fmt->fmt.pix.width == 0 || fmt->fmt.pix.height == 0) return -EINVAL; fmt->fmt.pix.bytesperline = SPA_ROUND_UP_N(fmt->fmt.pix.width, 4) * fi->bpp; fmt->fmt.pix.sizeimage = fmt->fmt.pix.bytesperline * SPA_ROUND_UP_N(fmt->fmt.pix.height, 2); return 0; } static int param_to_fmt(const struct spa_pod *param, struct v4l2_format *fmt) { struct spa_video_info info; struct spa_pod *copy; int res; copy = spa_pod_copy(param); spa_pod_fixate(copy); res = param_to_info(copy, &info); free(copy); if (res < 0) return -EINVAL; if (info_to_fmt(&info, fmt) < 0) return -EINVAL; return 0; } static void on_stream_param_changed(void *data, uint32_t id, const struct spa_pod *param) { struct file *file = data; const struct spa_pod *params[4]; uint32_t n_params = 0; uint8_t buffer[4096]; struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); uint32_t buffers, size, stride; struct v4l2_format fmt; if (param == NULL || id != SPA_PARAM_Format) return; if (param_to_fmt(param, &fmt) < 0) return; file->v4l2_format = fmt; buffers = SPA_CLAMP(file->reqbufs, 1u, MAX_BUFFERS); size = fmt.fmt.pix.sizeimage; stride = fmt.fmt.pix.bytesperline; params[n_params++] = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers, SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(buffers, 1, MAX_BUFFERS), SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(1), SPA_PARAM_BUFFERS_size, SPA_POD_CHOICE_RANGE_Int(size, 0, INT_MAX), SPA_PARAM_BUFFERS_stride, SPA_POD_CHOICE_RANGE_Int(stride, 0, INT_MAX), SPA_PARAM_BUFFERS_dataType, SPA_POD_CHOICE_FLAGS_Int((1<stream, params, n_params); } static void on_stream_state_changed(void *data, enum pw_stream_state old, enum pw_stream_state state, const char *error) { struct file *file = data; pw_log_info("file:%d: state %s", file->fd, pw_stream_state_as_string(state)); switch (state) { case PW_STREAM_STATE_ERROR: break; case PW_STREAM_STATE_UNCONNECTED: break; case PW_STREAM_STATE_CONNECTING: case PW_STREAM_STATE_PAUSED: case PW_STREAM_STATE_STREAMING: break; } pw_thread_loop_signal(file->loop, false); } static void on_stream_add_buffer(void *data, struct pw_buffer *b) { struct file *file = data; uint32_t id = file->n_buffers; struct buffer *buf = &file->buffers[id]; struct v4l2_buffer vb; struct spa_data *d = &b->buffer->datas[0]; file->size = d->maxsize; pw_log_info("file:%d: id:%d fd:%"PRIi64" size:%u offset:%u", file->fd, id, d->fd, file->size, id * file->size); spa_zero(vb); vb.index = id; vb.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; vb.flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; vb.memory = V4L2_MEMORY_MMAP; vb.m.offset = id * file->size; vb.length = file->size; buf->v4l2 = vb; buf->id = id; buf->buf = b; b->user_data = buf; file->n_buffers++; } static void on_stream_remove_buffer(void *data, struct pw_buffer *b) { struct file *file = data; file->n_buffers--; } static void on_stream_process(void *data) { struct file *file = data; pw_log_debug("file:%d", file->fd); spa_system_eventfd_write(file->l->system, file->fd, 1); } static const struct pw_stream_events stream_events = { PW_VERSION_STREAM_EVENTS, .param_changed = on_stream_param_changed, .state_changed = on_stream_state_changed, .add_buffer = on_stream_add_buffer, .remove_buffer = on_stream_remove_buffer, .process = on_stream_process, }; static int vidioc_enum_framesizes(struct file *file, struct v4l2_frmsizeenum *arg) { uint32_t count = 0; struct global *g = file->node; struct param *p; bool found = false; pw_log_info("index: %u", arg->index); pw_log_info("format: %.4s", (char*)&arg->pixel_format); pw_thread_loop_lock(file->loop); spa_list_for_each(p, &g->param_list, link) { const struct format_info *fi; uint32_t media_type, media_subtype, format; if (p->id != SPA_PARAM_EnumFormat || p->param == NULL) continue; if (spa_format_parse(p->param, &media_type, &media_subtype) < 0) continue; if (media_type != SPA_MEDIA_TYPE_video) continue; if (media_subtype == SPA_MEDIA_SUBTYPE_raw) { if (spa_pod_parse_object(p->param, SPA_TYPE_OBJECT_Format, NULL, SPA_FORMAT_VIDEO_format, SPA_POD_Id(&format)) < 0) continue; } else { format = SPA_VIDEO_FORMAT_ENCODED; } fi = format_info_from_media_type(media_type, media_subtype, format); if (fi == NULL) continue; if (fi->fourcc != arg->pixel_format) continue; const struct spa_pod_prop *size_prop = spa_pod_find_prop(p->param, NULL, SPA_FORMAT_VIDEO_size); if (!size_prop) continue; uint32_t n_sizes, choice; const struct spa_pod *size_pods = spa_pod_get_values(&size_prop->value, &n_sizes, &choice); if (!size_pods || n_sizes <= 0) continue; const struct spa_rectangle *sizes = SPA_POD_BODY_CONST(size_pods); if (size_pods->type != SPA_TYPE_Rectangle || size_pods->size != sizeof(*sizes)) continue; switch (choice) { case SPA_CHOICE_Enum: n_sizes -= 1; sizes += 1; SPA_FALLTHROUGH; case SPA_CHOICE_None: arg->type = V4L2_FRMSIZE_TYPE_DISCRETE; for (size_t i = 0; i < n_sizes; i++, count++) { arg->discrete.width = sizes[i].width; arg->discrete.height = sizes[i].height; pw_log_debug("count:%u %.4s %ux%u", count, (char*)&fi->fourcc, arg->discrete.width, arg->discrete.height); if (count == arg->index) { found = true; break; } } break; case SPA_CHOICE_Range: if (n_sizes < 3) continue; arg->type = V4L2_FRMSIZE_TYPE_CONTINUOUS; arg->stepwise.min_width = sizes[1].width; arg->stepwise.min_height = sizes[1].height; arg->stepwise.max_width = sizes[2].width; arg->stepwise.max_height = sizes[2].height; arg->stepwise.step_width = arg->stepwise.step_height = 1; pw_log_debug("count:%u %.4s (%ux%u)-(%ux%u)", count, (char*)&fi->fourcc, arg->stepwise.min_width, arg->stepwise.min_height, arg->stepwise.max_width, arg->stepwise.max_height); if (count == arg->index) { found = true; break; } count++; break; case SPA_CHOICE_Step: if (n_sizes < 4) continue; arg->type = V4L2_FRMSIZE_TYPE_CONTINUOUS; arg->stepwise.min_width = sizes[1].width; arg->stepwise.min_height = sizes[1].height; arg->stepwise.max_width = sizes[2].width; arg->stepwise.max_height = sizes[2].height; arg->stepwise.step_width = sizes[3].width; arg->stepwise.step_height = sizes[3].height; pw_log_debug("count:%u %.4s (%ux%u)-(%ux%u)/(+%u,%u)", count, (char*)&fi->fourcc, arg->stepwise.min_width, arg->stepwise.min_height, arg->stepwise.min_width, arg->stepwise.min_height, arg->stepwise.step_width, arg->stepwise.step_height); if (count == arg->index) { found = true; break; } count++; break; default: continue; } if (found) break; } pw_thread_loop_unlock(file->loop); if (!found) return -EINVAL; switch (arg->type) { case V4L2_FRMSIZE_TYPE_DISCRETE: pw_log_info("type: discrete"); pw_log_info("width: %u", arg->discrete.width); pw_log_info("height: %u", arg->discrete.height); break; case V4L2_FRMSIZE_TYPE_CONTINUOUS: case V4L2_FRMSIZE_TYPE_STEPWISE: pw_log_info("type: stepwise"); pw_log_info("min-width: %u", arg->stepwise.min_width); pw_log_info("max-width: %u", arg->stepwise.max_width); pw_log_info("step-width: %u", arg->stepwise.step_width); pw_log_info("min-height: %u", arg->stepwise.min_height); pw_log_info("max-height: %u", arg->stepwise.max_height); pw_log_info("step-height: %u", arg->stepwise.step_height); break; } memset(arg->reserved, 0, sizeof(arg->reserved)); return 0; } static int vidioc_enum_fmt(struct file *file, struct v4l2_fmtdesc *arg) { uint32_t count = 0, last_fourcc = 0; struct global *g = file->node; struct param *p; bool found = false; pw_log_info("index: %u", arg->index); pw_log_info("type: %u", arg->type); if (arg->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) return -EINVAL; pw_thread_loop_lock(file->loop); spa_list_for_each(p, &g->param_list, link) { const struct format_info *fi; uint32_t media_type, media_subtype, format; if (p->id != SPA_PARAM_EnumFormat || p->param == NULL) continue; if (spa_format_parse(p->param, &media_type, &media_subtype) < 0) continue; if (media_type != SPA_MEDIA_TYPE_video) continue; if (media_subtype == SPA_MEDIA_SUBTYPE_raw) { if (spa_pod_parse_object(p->param, SPA_TYPE_OBJECT_Format, NULL, SPA_FORMAT_VIDEO_format, SPA_POD_Id(&format)) < 0) continue; } else { format = SPA_VIDEO_FORMAT_ENCODED; } fi = format_info_from_media_type(media_type, media_subtype, format); if (fi == NULL) continue; if (fi->fourcc == last_fourcc) continue; pw_log_info("count:%d fourcc:%.4s last:%.4s", count, (char*)&fi->fourcc, (char*)&last_fourcc); arg->flags = fi->format == SPA_VIDEO_FORMAT_ENCODED ? V4L2_FMT_FLAG_COMPRESSED : 0; arg->pixelformat = fi->fourcc; snprintf((char*)arg->description, sizeof(arg->description), "%s", fi->desc ? fi->desc : "Unknown"); last_fourcc = fi->fourcc; if (count == arg->index) { found = true; break; } count++; } pw_thread_loop_unlock(file->loop); if (!found) return -EINVAL; pw_log_info("format: %.4s", (char*)&arg->pixelformat); pw_log_info("flags: %u", arg->type); memset(arg->reserved, 0, sizeof(arg->reserved)); return 0; } static int vidioc_g_fmt(struct file *file, struct v4l2_format *arg) { struct param *p; struct global *g = file->node; int res; if (arg->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) return -EINVAL; pw_thread_loop_lock(file->loop); if (file->v4l2_format.fmt.pix.pixelformat != 0) { *arg = file->v4l2_format; } else { struct v4l2_format tmp; bool found = false; spa_list_for_each(p, &g->param_list, link) { if (p->id != SPA_PARAM_EnumFormat || p->param == NULL) continue; if (param_to_fmt(p->param, &tmp) < 0) continue; found = true; break; } if (!found) { res = -EINVAL; goto exit_unlock; } *arg = file->v4l2_format = tmp; } res = 0; exit_unlock: pw_thread_loop_unlock(file->loop); return res; } static int score_diff(struct v4l2_format *fmt, struct v4l2_format *tmp) { int score = 0, w, h; if (fmt->fmt.pix.pixelformat != tmp->fmt.pix.pixelformat) score += 20000; w = SPA_ABS((int)fmt->fmt.pix.width - (int)tmp->fmt.pix.width); h = SPA_ABS((int)fmt->fmt.pix.height - (int)tmp->fmt.pix.height); return score + (w*w) + (h*h); } static int try_format(struct file *file, struct v4l2_format *fmt) { struct param *p; struct global *g = file->node; struct v4l2_format best_fmt = *fmt; int best = -1; pw_log_info("in: type: %u", fmt->type); if (fmt->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) return -EINVAL; pw_log_info("in: format: %.4s", (char*)&fmt->fmt.pix.pixelformat); pw_log_info("in: width: %u", fmt->fmt.pix.width); pw_log_info("in: height: %u", fmt->fmt.pix.height); pw_log_info("in: field: %u", fmt->fmt.pix.field); spa_list_for_each(p, &g->param_list, link) { struct v4l2_format tmp; int score; if (p->param == NULL) continue; if (p->id != SPA_PARAM_EnumFormat && p->id != SPA_PARAM_Format) continue; if (param_to_fmt(p->param, &tmp) < 0) continue; score = score_diff(fmt, &tmp); pw_log_debug("check: type: %u", tmp.type); pw_log_debug("check: format: %.4s", (char*)&tmp.fmt.pix.pixelformat); pw_log_debug("check: width: %u", tmp.fmt.pix.width); pw_log_debug("check: height: %u", tmp.fmt.pix.height); pw_log_debug("check: score: %d best:%d", score, best); if (p->id == SPA_PARAM_Format) { best_fmt = tmp; break; } if (best == -1 || score < best) { best = score; best_fmt = tmp; } } *fmt = best_fmt; pw_log_info("out: format: %.4s", (char*)&fmt->fmt.pix.pixelformat); pw_log_info("out: width: %u", fmt->fmt.pix.width); pw_log_info("out: height: %u", fmt->fmt.pix.height); pw_log_info("out: field: %u", fmt->fmt.pix.field); pw_log_info("out: size: %u", fmt->fmt.pix.sizeimage); return 0; } static int disconnect_stream(struct file *file) { if (file->stream != NULL) { pw_log_info("file:%d disconnect", file->fd); pw_stream_destroy(file->stream); file->stream = NULL; file->n_buffers = 0; } return 0; } static int connect_stream(struct file *file) { int res; struct global *g = file->node; struct timespec abstime; const char *error = NULL; uint8_t buffer[1024]; struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); const struct spa_pod *params[1]; struct pw_properties *props; params[0] = fmt_to_param(&b, SPA_PARAM_EnumFormat, &file->v4l2_format); if (params[0] == NULL) { res = -EINVAL; goto exit; } disconnect_stream(file); props = pw_properties_new(NULL, NULL); if (props == NULL) { res = -errno; goto exit; } pw_properties_set(props, PW_KEY_CLIENT_API, "v4l2"); pw_properties_setf(props, PW_KEY_APP_NAME, "%s", pw_get_prgname()); if (pw_properties_get(props, PW_KEY_MEDIA_TYPE) == NULL) pw_properties_set(props, PW_KEY_MEDIA_TYPE, "Video"); if (pw_properties_get(props, PW_KEY_MEDIA_CATEGORY) == NULL) pw_properties_set(props, PW_KEY_MEDIA_CATEGORY, "Capture"); file->stream = pw_stream_new(file->core, "v4l2 capture", props); if (file->stream == NULL) { res = -errno; goto exit; } pw_stream_add_listener(file->stream, &file->stream_listener, &stream_events, file); file->error = 0; pw_stream_connect(file->stream, PW_DIRECTION_INPUT, g->id, PW_STREAM_FLAG_DONT_RECONNECT | PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_RT_PROCESS, params, 1); pw_thread_loop_get_time (file->loop, &abstime, DEFAULT_TIMEOUT * SPA_NSEC_PER_SEC); while (true) { enum pw_stream_state state = pw_stream_get_state(file->stream, &error); if (state == PW_STREAM_STATE_STREAMING) break; if (state == PW_STREAM_STATE_ERROR) { res = -errno; goto exit; } if (file->error < 0) { res = file->error; goto exit; } if (pw_thread_loop_timed_wait_full(file->loop, &abstime) < 0) { res = -ETIMEDOUT; goto exit; } } /* pause stream */ res = pw_stream_set_active(file->stream, false); exit: return res; } static int vidioc_s_fmt(struct file *file, struct v4l2_format *arg) { int res; pw_thread_loop_lock(file->loop); if ((res = try_format(file, arg)) < 0) goto exit_unlock; file->v4l2_format = *arg; exit_unlock: pw_thread_loop_unlock(file->loop); return res; } static int vidioc_try_fmt(struct file *file, struct v4l2_format *arg) { int res; pw_thread_loop_lock(file->loop); res = try_format(file, arg); pw_thread_loop_unlock(file->loop); return res; } static int vidioc_g_parm(struct file *file, struct v4l2_streamparm *arg) { if (arg->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) return -EINVAL; struct param *p; pw_thread_loop_lock(file->loop); bool found = false; struct spa_video_info info; int num = 0, denom = 0; spa_list_for_each(p, &file->node->param_list, link) { if (p->id != SPA_PARAM_EnumFormat || p->param == NULL) continue; if (param_to_info(p->param, &info) < 0) continue; switch (info.media_subtype) { case SPA_MEDIA_SUBTYPE_raw: num = info.info.raw.framerate.num; denom = info.info.raw.framerate.denom; break; case SPA_MEDIA_SUBTYPE_mjpg: num = info.info.mjpg.framerate.num; denom = info.info.mjpg.framerate.denom; break; case SPA_MEDIA_SUBTYPE_h264: num = info.info.h264.framerate.num; denom = info.info.h264.framerate.denom; break; } if (num == 0 || denom == 0) continue; found = true; break; } if (!found) { pw_thread_loop_unlock(file->loop); return -EINVAL; } pw_thread_loop_unlock(file->loop); spa_zero(*arg); arg->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; arg->parm.capture.capability = V4L2_CAP_TIMEPERFRAME; arg->parm.capture.capturemode = 0; arg->parm.capture.extendedmode = 0; arg->parm.capture.readbuffers = 0; arg->parm.capture.timeperframe.numerator = denom; arg->parm.capture.timeperframe.denominator = num; pw_log_info("VIDIOC_G_PARM frametime: %d/%d", num, denom); return 0; } // TODO: implement setting parameters static int vidioc_s_parm(struct file *file, struct v4l2_streamparm *arg) { pw_log_warn("VIDIOC_S_PARM is unimplemented, returning current value"); vidioc_g_parm(file, arg); return 0; } static int vidioc_enuminput(struct file *file, struct v4l2_input *arg) { uint32_t index = arg->index; spa_zero(*arg); arg->index = index; switch (arg->index) { case 0: spa_scnprintf((char*)arg->name, sizeof(arg->name), "%s", DEFAULT_CARD); arg->type = V4L2_INPUT_TYPE_CAMERA; break; default: return -EINVAL; } return 0; } static int vidioc_g_input(struct file *file, int *arg) { *arg = 0; return 0; } static int vidioc_s_input(struct file *file, int *arg) { if (*arg != 0) return -EINVAL; return 0; } static int vidioc_g_priority(struct file *file, enum v4l2_priority *arg) { *arg = file->priority; pw_log_info("file:%d prio:%d", file->fd, *arg); return 0; } static int vidioc_s_priority(struct file *file, int fd, enum v4l2_priority *arg) { if (*arg > V4L2_PRIORITY_RECORD) return -EINVAL; if (file->fd != fd && file->priority > *arg) return -EINVAL; pw_log_info("file:%d (%d) prio:%d", file->fd, fd, *arg); file->priority = *arg; return 0; } static int vidioc_reqbufs(struct file *file, int fd, struct v4l2_requestbuffers *arg) { int res; pw_log_info("count: %u", arg->count); pw_log_info("type: %u", arg->type); pw_log_info("memory: %u", arg->memory); #ifdef V4L2_MEMORY_FLAG_NON_COHERENT pw_log_info("flags: %08x", arg->flags); #endif if (arg->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) return -EINVAL; if (arg->memory != V4L2_MEMORY_MMAP) return -EINVAL; pw_thread_loop_lock(file->loop); if (file->n_buffers > 0 && file->reqbufs_fd != fd) { pw_log_info("%u fd:%d != %d", file->n_buffers, file->reqbufs_fd, fd); res = -EBUSY; goto exit_unlock; } if (arg->count == 0) { if (pw_array_get_len(&file->buffer_maps, struct buffer_map) != 0) { pw_log_info("fd:%d have maps", fd); res = -EBUSY; goto exit_unlock; } if (file->running) { pw_log_info("fd:%d running", fd); res = -EBUSY; goto exit_unlock; } res = disconnect_stream(file); file->reqbufs = 0; file->reqbufs_fd = -1; } else { file->reqbufs = arg->count; if ((res = connect_stream(file)) < 0) goto exit_unlock; arg->count = file->n_buffers; file->reqbufs_fd = fd; } #ifdef V4L2_MEMORY_FLAG_NON_COHERENT arg->flags = 0; #endif #ifdef V4L2_BUF_CAP_SUPPORTS_MMAP arg->capabilities = V4L2_BUF_CAP_SUPPORTS_MMAP; #endif memset(arg->reserved, 0, sizeof(arg->reserved)); pw_log_info("result count: %u", arg->count); exit_unlock: if (res < 0) pw_log_info("error : %s", spa_strerror(res)); pw_thread_loop_unlock(file->loop); return res; } static int vidioc_querybuf(struct file *file, struct v4l2_buffer *arg) { int res; if (arg->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) return -EINVAL; pw_thread_loop_lock(file->loop); if (arg->index >= file->n_buffers) { res = -EINVAL; goto exit_unlock; } *arg = file->buffers[arg->index].v4l2; res = 0; exit_unlock: pw_thread_loop_unlock(file->loop); return res; } static int vidioc_qbuf(struct file *file, struct v4l2_buffer *arg) { int res = 0; struct buffer *buf; if (arg->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) return -EINVAL; if (arg->memory != V4L2_MEMORY_MMAP) return -EINVAL; pw_thread_loop_lock(file->loop); if (arg->index >= file->n_buffers) { res = -EINVAL; goto exit; } buf = &file->buffers[arg->index]; if (SPA_FLAG_IS_SET(buf->v4l2.flags, V4L2_BUF_FLAG_QUEUED)) { res = -EINVAL; goto exit; } SPA_FLAG_SET(buf->v4l2.flags, V4L2_BUF_FLAG_QUEUED); arg->flags = buf->v4l2.flags; pw_stream_queue_buffer(file->stream, buf->buf); exit: pw_log_debug("file:%d %d -> %d (%s)", file->fd, arg->index, res, spa_strerror(res)); pw_thread_loop_unlock(file->loop); return res; } static int vidioc_dqbuf(struct file *file, int fd, struct v4l2_buffer *arg) { int res = 0; struct pw_buffer *b; struct buffer *buf; uint64_t val; struct spa_data *d; struct timespec ts; if (arg->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) return -EINVAL; if (arg->memory != V4L2_MEMORY_MMAP) return -EINVAL; pw_log_debug("file:%d (%d) %d", file->fd, fd, arg->index); pw_thread_loop_lock(file->loop); if (arg->index >= file->n_buffers) { res = -EINVAL; goto exit_unlock; } while (true) { if (!file->running) { res = -EINVAL; goto exit_unlock; } b = pw_stream_dequeue_buffer(file->stream); if (b != NULL) break; pw_thread_loop_unlock(file->loop); res = spa_system_eventfd_read(file->l->system, fd, &val); pw_thread_loop_lock(file->loop); if (res < 0) goto exit_unlock; } buf = b->user_data; d = &buf->buf->buffer->datas[0]; SPA_FLAG_CLEAR(buf->v4l2.flags, V4L2_BUF_FLAG_QUEUED); SPA_FLAG_UPDATE(buf->v4l2.flags, V4L2_BUF_FLAG_ERROR, SPA_FLAG_IS_SET(d->chunk->flags, SPA_CHUNK_FLAG_CORRUPTED)); SPA_FLAG_SET(buf->v4l2.flags, V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC); clock_gettime(CLOCK_MONOTONIC, &ts); buf->v4l2.timestamp.tv_sec = ts.tv_sec; buf->v4l2.timestamp.tv_usec = ts.tv_nsec / 1000; buf->v4l2.field = V4L2_FIELD_NONE; buf->v4l2.bytesused = d->chunk->size; buf->v4l2.sequence = file->sequence++; *arg = buf->v4l2; exit_unlock: pw_log_debug("file:%d (%d) %d -> %d (%s)", file->fd, fd, arg->index, res, spa_strerror(res)); pw_thread_loop_unlock(file->loop); return res; } static int vidioc_streamon(struct file *file, int *arg) { int res; if (*arg != V4L2_BUF_TYPE_VIDEO_CAPTURE) return -EINVAL; pw_thread_loop_lock(file->loop); if (file->n_buffers == 0) { res = -EINVAL; goto exit_unlock; } if (file->running) { res = 0; goto exit_unlock; } res = pw_stream_set_active(file->stream, true); if (res >= 0) file->running = true; exit_unlock: pw_thread_loop_unlock(file->loop); pw_log_info("file:%d -> %d (%s)", file->fd, res, spa_strerror(res)); return res; } static int vidioc_streamoff(struct file *file, int *arg) { int res; uint32_t i; if (*arg != V4L2_BUF_TYPE_VIDEO_CAPTURE) return -EINVAL; pw_thread_loop_lock(file->loop); for (i = 0; i < file->n_buffers; i++) { struct buffer *buf = &file->buffers[i]; SPA_FLAG_CLEAR(buf->v4l2.flags, V4L2_BUF_FLAG_QUEUED); } if (!file->running) { res = 0; goto exit_unlock; } res = pw_stream_set_active(file->stream, false); file->running = false; file->sequence = 0; exit_unlock: pw_thread_loop_unlock(file->loop); pw_log_info("file:%d -> %d (%s)", file->fd, res, spa_strerror(res)); return res; } // Copied from spa/plugins/v4l2/v4l2-utils.c // TODO: unify with source static struct { uint32_t v4l2_id; uint32_t spa_id; } control_map[] = { { V4L2_CID_BRIGHTNESS, SPA_PROP_brightness }, { V4L2_CID_CONTRAST, SPA_PROP_contrast }, { V4L2_CID_SATURATION, SPA_PROP_saturation }, { V4L2_CID_HUE, SPA_PROP_hue }, { V4L2_CID_GAMMA, SPA_PROP_gamma }, { V4L2_CID_EXPOSURE_ABSOLUTE, SPA_PROP_exposure }, { V4L2_CID_GAIN, SPA_PROP_gain }, { V4L2_CID_SHARPNESS, SPA_PROP_sharpness }, }; static uint32_t prop_id_to_control(uint32_t prop_id) { SPA_FOR_EACH_ELEMENT_VAR(control_map, c) { if (c->spa_id == prop_id) return c->v4l2_id; } if (prop_id >= SPA_PROP_START_CUSTOM) return prop_id - SPA_PROP_START_CUSTOM; return SPA_ID_INVALID; } static int vidioc_queryctrl(struct file *file, struct v4l2_queryctrl *arg) { struct param *p; bool found = false, next = false; memset(arg->reserved, 0, sizeof(arg->reserved)); // TODO: V4L2_CTRL_FLAG_NEXT_COMPOUND if (arg->id & V4L2_CTRL_FLAG_NEXT_CTRL) { pw_log_debug("VIDIOC_QUERYCTRL: 0x%08" PRIx32 " | V4L2_CTRL_FLAG_NEXT_CTRL", arg->id); arg->id = arg->id & ~V4L2_CTRL_FLAG_NEXT_CTRL & ~V4L2_CTRL_FLAG_NEXT_COMPOUND; next = true; } pw_log_debug("VIDIOC_QUERYCTRL: 0x%08" PRIx32, arg->id); if (file->node == NULL) return -EIO; pw_thread_loop_lock(file->loop); // FIXME: place found item into a variable. Will fix unordered ctrls spa_list_for_each(p, &file->node->param_list, link) { uint32_t prop_id, ctrl_id, n_vals, choice = SPA_ID_INVALID; const char *prop_description; const struct spa_pod *type, *pod; if (p->id != SPA_PARAM_PropInfo || p->param == NULL) continue; if (spa_pod_parse_object(p->param, SPA_TYPE_OBJECT_PropInfo, NULL, SPA_PROP_INFO_id, SPA_POD_Id(&prop_id), SPA_PROP_INFO_description, SPA_POD_String(&prop_description)) < 0) continue; if ((ctrl_id = prop_id_to_control(prop_id)) == SPA_ID_INVALID) continue; if ((next && ctrl_id > arg->id) || (!next && ctrl_id == arg->id)) { if (spa_pod_parse_object(p->param, SPA_TYPE_OBJECT_PropInfo, NULL, SPA_PROP_INFO_type, SPA_POD_PodChoice(&type)) < 0) continue; // TODO: support setting controls arg->flags = V4L2_CTRL_FLAG_READ_ONLY; spa_scnprintf((char*)arg->name, sizeof(arg->name), "%s", prop_description); // check type and populate range pod = spa_pod_get_values(type, &n_vals, &choice); if (spa_pod_is_int(pod)) { if (n_vals < 4) break; arg->type = V4L2_CTRL_TYPE_INTEGER; int *v = SPA_POD_BODY(pod); arg->default_value = v[0]; arg->minimum = v[1]; arg->maximum = v[2]; arg->step = v[3]; } else if (spa_pod_is_bool(pod) && n_vals > 0) { arg->type = V4L2_CTRL_TYPE_BOOLEAN; arg->default_value = SPA_POD_VALUE(struct spa_pod_bool, pod); arg->minimum = 0; arg->maximum = 1; arg->step = 1; } else { break; } arg->id = ctrl_id; found = true; pw_log_debug("ctrl 0x%08" PRIx32 " ok", arg->id); break; } } pw_thread_loop_unlock(file->loop); if (!found) { pw_log_info("not found ctrl 0x%08" PRIx32, arg->id); return -EINVAL; } return 0; } static int vidioc_g_ctrl(struct file *file, struct v4l2_control *arg) { struct param *p; bool found = false; pw_log_debug("VIDIOC_G_CTRL: 0x%08" PRIx32, arg->id); if (file->node == NULL) return -EIO; pw_thread_loop_lock(file->loop); spa_list_for_each(p, &file->node->param_list, link) { uint32_t prop_id, ctrl_id, n_vals, choice = SPA_ID_INVALID; const char *prop_description; const struct spa_pod *type, *pod; if (p->id != SPA_PARAM_PropInfo || p->param == NULL) continue; if (spa_pod_parse_object(p->param, SPA_TYPE_OBJECT_PropInfo, NULL, SPA_PROP_INFO_id, SPA_POD_Id(&prop_id), SPA_PROP_INFO_description, SPA_POD_String(&prop_description)) < 0) continue; if ((ctrl_id = prop_id_to_control(prop_id)) == SPA_ID_INVALID) continue; if (spa_pod_parse_object(p->param, SPA_TYPE_OBJECT_PropInfo, NULL, SPA_PROP_INFO_type, SPA_POD_PodChoice(&type)) < 0) continue; if (ctrl_id == arg->id) { // TODO: support getting true ctrl values instead of defaults pod = spa_pod_get_values(type, &n_vals, &choice); if (spa_pod_is_int(pod)) { if (n_vals < 4) break; int *v = SPA_POD_BODY(pod); arg->value = v[0]; } else if (spa_pod_is_bool(pod) && n_vals > 0) { arg->value = SPA_POD_VALUE(struct spa_pod_bool, pod); } else { break; } found = true; pw_log_debug("ctrl 0x%08" PRIx32 " ok", arg->id); break; } } pw_thread_loop_unlock(file->loop); if (!found) { pw_log_info("not found ctrl 0x%08" PRIx32, arg->id); return -EINVAL; } return 0; } static int vidioc_s_ctrl(struct file *file, struct v4l2_control *arg) { struct param *p; bool found = false; pw_log_info("VIDIOC_S_CTRL: 0x%08" PRIx32 " 0x%08" PRIx32, arg->id, arg->value); if (file->node == NULL) return -EIO; pw_thread_loop_lock(file->loop); spa_list_for_each(p, &file->node->param_list, link) { uint32_t prop_id, ctrl_id, n_vals, choice = SPA_ID_INVALID; const char *prop_description; const struct spa_pod *type, *pod; if (p->id != SPA_PARAM_PropInfo || p->param == NULL) continue; if (spa_pod_parse_object(p->param, SPA_TYPE_OBJECT_PropInfo, NULL, SPA_PROP_INFO_id, SPA_POD_Id(&prop_id), SPA_PROP_INFO_description, SPA_POD_String(&prop_description)) < 0) continue; if ((ctrl_id = prop_id_to_control(prop_id)) == SPA_ID_INVALID) continue; if (spa_pod_parse_object(p->param, SPA_TYPE_OBJECT_PropInfo, NULL, SPA_PROP_INFO_type, SPA_POD_PodChoice(&type)) < 0) continue; if (ctrl_id == arg->id) { char buf[1024]; struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buf, sizeof(buf)); struct spa_pod_frame f[1]; struct spa_pod *param; pod = spa_pod_get_values(type, &n_vals, &choice); if (n_vals < 1) break; spa_pod_builder_push_object(&b, &f[0], SPA_TYPE_OBJECT_Props, SPA_PARAM_Props); if (spa_pod_is_int(pod)) { spa_pod_builder_add(&b, prop_id, SPA_POD_Int(arg->value), 0); } else if (spa_pod_is_bool(pod)) { spa_pod_builder_add(&b, prop_id, SPA_POD_Bool(arg->value), 0); } else { // TODO: float and other formats pw_log_info("unknown type"); break; } param = spa_pod_builder_pop(&b, &f[0]); pw_node_set_param((struct pw_node*)file->node->proxy, SPA_PARAM_Props, 0, param); found = true; pw_log_info("ctrl 0x%08" PRIx32 " set ok", arg->id); break; } } pw_thread_loop_unlock(file->loop); if (!found) { pw_log_info("not found ctrl 0x%08" PRIx32, arg->id); return -EINVAL; } return 0; } static int v4l2_ioctl(int fd, unsigned long int request, void *arg) { int res; struct file *file; uint32_t flags; if ((file = find_file(fd, &flags)) == NULL) return globals.old_fops.ioctl(fd, request, arg); #if defined(__FreeBSD__) || defined(__MidnightBSD__) if (arg == NULL && (request & IOC_DIRMASK != IOC_VOID)) { #else if (arg == NULL && (_IOC_DIR(request) & (_IOC_WRITE | _IOC_READ))) { #endif res = -EFAULT; goto done; } if (flags & FD_MAP_DUP) fd = file->fd; switch (request & 0xffffffff) { case VIDIOC_QUERYCAP: res = vidioc_querycap(file, (struct v4l2_capability *)arg); break; case VIDIOC_ENUM_FRAMESIZES: res = vidioc_enum_framesizes(file, (struct v4l2_frmsizeenum *)arg); break; case VIDIOC_ENUM_FMT: res = vidioc_enum_fmt(file, (struct v4l2_fmtdesc *)arg); break; case VIDIOC_G_FMT: res = vidioc_g_fmt(file, (struct v4l2_format *)arg); break; case VIDIOC_S_FMT: res = vidioc_s_fmt(file, (struct v4l2_format *)arg); break; case VIDIOC_TRY_FMT: res = vidioc_try_fmt(file, (struct v4l2_format *)arg); break; case VIDIOC_G_PARM: res = vidioc_g_parm(file, (struct v4l2_streamparm *)arg); break; case VIDIOC_S_PARM: res = vidioc_s_parm(file, (struct v4l2_streamparm *)arg); break; case VIDIOC_ENUMINPUT: res = vidioc_enuminput(file, (struct v4l2_input *)arg); break; case VIDIOC_G_INPUT: res = vidioc_g_input(file, (int *)arg); break; case VIDIOC_S_INPUT: res = vidioc_s_input(file, (int *)arg); break; case VIDIOC_G_PRIORITY: res = vidioc_g_priority(file, (enum v4l2_priority *)arg); break; case VIDIOC_S_PRIORITY: res = vidioc_s_priority(file, fd, (enum v4l2_priority *)arg); break; case VIDIOC_REQBUFS: res = vidioc_reqbufs(file, fd, (struct v4l2_requestbuffers *)arg); break; case VIDIOC_QUERYBUF: res = vidioc_querybuf(file, (struct v4l2_buffer *)arg); break; case VIDIOC_QBUF: res = vidioc_qbuf(file, (struct v4l2_buffer *)arg); break; case VIDIOC_DQBUF: res = vidioc_dqbuf(file, fd, (struct v4l2_buffer *)arg); break; case VIDIOC_STREAMON: res = vidioc_streamon(file, (int *)arg); break; case VIDIOC_STREAMOFF: res = vidioc_streamoff(file, (int *)arg); break; // TODO: VIDIOC_QUERY_EXT_CTRL case VIDIOC_QUERYCTRL: res = vidioc_queryctrl(file, (struct v4l2_queryctrl *)arg); break; case VIDIOC_G_CTRL: res = vidioc_g_ctrl(file, (struct v4l2_control *)arg); break; case VIDIOC_S_CTRL: res = vidioc_s_ctrl(file, (struct v4l2_control *)arg); break; default: res = -ENOTTY; break; } done: if (res < 0) { errno = -res; res = -1; } pw_log_debug("file:%d fd:%d request:%lx nr:%d arg:%p -> %d (%s)", file->fd, fd, request, (int)_IOC_NR(request), arg, res, strerror(res < 0 ? errno : 0)); unref_file(file); return res; } static void *v4l2_mmap(void *addr, size_t length, int prot, int flags, int fd, off64_t offset) { void *res; struct file *file; off64_t id; struct pw_map_range range; struct buffer *buf; struct spa_data *data; uint32_t fl; if ((file = find_file(fd, &fl)) == NULL) return globals.old_fops.mmap(addr, length, prot, flags, fd, offset); pw_thread_loop_lock(file->loop); if (file->size == 0) { errno = EIO; res = MAP_FAILED; goto error_unlock; } id = offset / file->size; if ((id * file->size) != offset || file->size != length) { errno = EINVAL; res = MAP_FAILED; goto error_unlock; } buf = &file->buffers[id]; data = &buf->buf->buffer->datas[0]; pw_map_range_init(&range, data->mapoffset, data->maxsize, 1024); if (!SPA_FLAG_IS_SET(data->flags, SPA_DATA_FLAG_READABLE)) prot &= ~PROT_READ; if (!SPA_FLAG_IS_SET(data->flags, SPA_DATA_FLAG_WRITABLE)) prot &= ~PROT_WRITE; if (data->data == NULL) res = globals.old_fops.mmap(addr, range.size, prot, flags, data->fd, range.offset); else res = data->data; add_file_map(file, res); add_buffer_map(file, res, id); SPA_FLAG_SET(buf->v4l2.flags, V4L2_BUF_FLAG_MAPPED); pw_log_info("file:%d addr:%p length:%zu prot:%d flags:%d fd:%"PRIi64 " offset:%"PRIi64" (%u - %u) -> %p (%s)" , file->fd, addr, length, prot, flags, data->fd, offset, range.offset, range.size, res, strerror(res == MAP_FAILED ? errno : 0)); error_unlock: pw_thread_loop_unlock(file->loop); unref_file(file); return res; } static int v4l2_munmap(void *addr, size_t length) { int res; struct buffer_map *bmap; struct buffer *buf; struct file *file; struct spa_data *data; if ((file = remove_file_map(addr)) == NULL) return globals.old_fops.munmap(addr, length); pw_thread_loop_lock(file->loop); bmap = find_buffer_map(file, addr); if (bmap == NULL) { res = -EINVAL; goto exit_unlock; } buf = &file->buffers[bmap->id]; data = &buf->buf->buffer->datas[0]; if (data->data == NULL) res = globals.old_fops.munmap(addr, length); else res = 0; pw_log_info("addr:%p length:%zu -> %d (%s)", addr, length, res, strerror(res < 0 ? errno : 0)); buf->v4l2.flags &= ~V4L2_BUF_FLAG_MAPPED; remove_buffer_map(file, bmap); exit_unlock: pw_thread_loop_unlock(file->loop); return res; } const struct fops fops = { .openat = v4l2_openat, .dup = v4l2_dup, .close = v4l2_close, .ioctl = v4l2_ioctl, .mmap = v4l2_mmap, .munmap = v4l2_munmap, }; static void initialize(void) { globals.old_fops.openat = dlsym(RTLD_NEXT, "openat64"); globals.old_fops.dup = dlsym(RTLD_NEXT, "dup"); globals.old_fops.close = dlsym(RTLD_NEXT, "close"); globals.old_fops.ioctl = dlsym(RTLD_NEXT, "ioctl"); globals.old_fops.mmap = dlsym(RTLD_NEXT, "mmap64"); globals.old_fops.munmap = dlsym(RTLD_NEXT, "munmap"); /* NOTE: * We avoid calling pw_init() here (constructor/early init path) because * that can deadlock in certain host processes (e.g. Zoom >= 5.0) when * the preload causes PipeWire initialisation to run too early. * * PipeWire initialisation (pw_init + PW_LOG_TOPIC_INIT) is deferred * to ensure it runs on-demand in the first actual V4L2 open call. */ pthread_mutex_init(&globals.lock, NULL); pw_array_init(&globals.file_maps, 1024); pw_array_init(&globals.fd_maps, 256); } const struct fops *get_fops(void) { static pthread_once_t initialized = PTHREAD_ONCE_INIT; pthread_once(&initialized, initialize); return &fops; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/pipewire-v4l2/src/pipewire-v4l2.h000066400000000000000000000007761511204443500264720ustar00rootroot00000000000000/* PipeWire */ /* SPDX-FileCopyrightText: Copyright © 2021 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include struct fops { int (*openat)(int dirfd, const char *path, int oflag, mode_t mode); int (*dup)(int oldfd); int (*close)(int fd); int (*ioctl)(int fd, unsigned long request, void *arg); void *(*mmap)(void *addr, size_t length, int prot, int flags, int fd, off64_t offset); int (*munmap)(void *addr, size_t length); }; const struct fops *get_fops(void); pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/pipewire-v4l2/src/pw-v4l2.in000077500000000000000000000021761511204443500254520ustar00rootroot00000000000000#!/bin/sh # This file is part of PipeWire. # SPDX-FileCopyrightText: Copyright © 2021 Wim Taymans # SPDX-License-Identifier: MIT while getopts 'hr:vs:p:' param ; do case $param in r) PIPEWIRE_REMOTE="$OPTARG" export PIPEWIRE_REMOTE ;; v) if [ -z "$PIPEWIRE_DEBUG" ]; then PIPEWIRE_DEBUG=3 else PIPEWIRE_DEBUG=$(( PIPEWIRE_DEBUG + 1 )) fi export PIPEWIRE_DEBUG ;; *) echo "$0 - run v4l2 applications on PipeWire" echo " " echo "$0 [options] application [arguments]" echo " " echo "options:" echo " -h show brief help" echo " -r remote daemon name" echo " -v verbose debug info" exit 0 ;; esac done shift $(( OPTIND - 1 )) if [ "$PW_UNINSTALLED" = 1 ] ; then PW_V4L2_LD_PRELOAD="$PW_BUILDDIR"'/pipewire-v4l2/src/libpw-v4l2.so' else # shellcheck disable=SC2016 # ${LIB} is interpreted by ld.so, not the shell PW_V4L2_LD_PRELOAD='@LIBV4L2_PATH@/libpw-v4l2.so' fi if [ "$LD_PRELOAD" = "" ] ; then LD_PRELOAD="$PW_V4L2_LD_PRELOAD" else LD_PRELOAD="$LD_PRELOAD $PW_V4L2_LD_PRELOAD" fi export LD_PRELOAD exec "$@" pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/pipewire-v4l2/src/v4l2-func.c000066400000000000000000000060261511204443500255660ustar00rootroot00000000000000/* PipeWire */ /* SPDX-FileCopyrightText: Copyright © 2021 Wim Taymans */ /* SPDX-License-Identifier: MIT */ /* * We need to export open* etc., but _FORTIFY_SOURCE defines conflicting * always_inline versions. Disable _FORTIFY_SOURCE for this file, so we * can define our overrides. */ #ifdef _FORTIFY_SOURCE #undef _FORTIFY_SOURCE #endif #include #include #include #include #include #include #include #include #include "pipewire-v4l2.h" #define SPA_EXPORT __attribute__((visibility("default"))) #define extract_va_arg(type, arg, last) \ { \ va_list ap; \ va_start(ap, last); \ arg = va_arg(ap, type); \ va_end(ap); \ } static inline bool needs_mode(int flags) { return (flags & O_CREAT) || ((flags & O_TMPFILE) == O_TMPFILE); } SPA_EXPORT int open(const char *path, int oflag, ...) { mode_t mode = 0; if (needs_mode(oflag)) extract_va_arg(mode_t, mode, oflag); return get_fops()->openat(AT_FDCWD, path, oflag, mode); } /* _FORTIFY_SOURCE redirects open to __open_2 */ SPA_EXPORT int __open_2(const char *path, int oflag) { assert(!needs_mode(oflag)); return open(path, oflag); } #ifndef open64 SPA_EXPORT int open64(const char *path, int oflag, ...) { mode_t mode = 0; if (needs_mode(oflag)) extract_va_arg(mode_t, mode, oflag); return get_fops()->openat(AT_FDCWD, path, oflag | O_LARGEFILE, mode); } SPA_EXPORT int __open64_2(const char *path, int oflag) { assert(!needs_mode(oflag)); return open64(path, oflag); } #endif SPA_EXPORT int openat(int dirfd, const char *path, int oflag, ...) { mode_t mode = 0; if (needs_mode(oflag)) extract_va_arg(mode_t, mode, oflag); return get_fops()->openat(dirfd, path, oflag, mode); } SPA_EXPORT int __openat_2(int dirfd, const char *path, int oflag) { assert(!needs_mode(oflag)); return openat(dirfd, path, oflag); } #ifndef openat64 SPA_EXPORT int openat64(int dirfd, const char *path, int oflag, ...) { mode_t mode = 0; if (needs_mode(oflag)) extract_va_arg(mode_t, mode, oflag); return get_fops()->openat(dirfd, path, oflag | O_LARGEFILE, mode); } SPA_EXPORT int __openat64_2(int dirfd, const char *path, int oflag) { assert(!needs_mode(oflag)); return openat64(dirfd, path, oflag); } #endif SPA_EXPORT int dup(int oldfd) { return get_fops()->dup(oldfd); } SPA_EXPORT int close(int fd) { return get_fops()->close(fd); } SPA_EXPORT void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset) { return get_fops()->mmap(addr, length, prot, flags, fd, offset); } #ifndef mmap64 SPA_EXPORT void *mmap64(void *addr, size_t length, int prot, int flags, int fd, off64_t offset) { return get_fops()->mmap(addr, length, prot, flags, fd, offset); } #endif SPA_EXPORT int munmap(void *addr, size_t length) { return get_fops()->munmap(addr, length); } SPA_EXPORT int ioctl(int fd, unsigned long int request, ...) { void *arg; extract_va_arg(void *, arg, request); return get_fops()->ioctl(fd, request, arg); } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/po/000077500000000000000000000000001511204443500211145ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/po/LINGUAS000066400000000000000000000002721511204443500221420ustar00rootroot00000000000000af ar as be bg bn_IN ca cs da de_CH de el es fi fr gl gu he hi hr hu id it ja ka kk kn ko lt ml mr my nl nn oc or pa pl pt_BR pt ro ru sk sl sr@latin sr sv ta te tr uk zh_CN zh_TW eo si pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/po/POTFILES.in000066400000000000000000000013341511204443500226720ustar00rootroot00000000000000src/daemon/pipewire.c src/daemon/pipewire.desktop.in src/modules/module-protocol-pulse/modules/module-tunnel-sink.c src/modules/module-protocol-pulse/modules/module-tunnel-source.c src/modules/module-fallback-sink.c src/modules/module-pulse-tunnel.c src/modules/module-zeroconf-discover.c src/tools/pw-cat.c src/tools/pw-cat.c src/tools/pw-cli.c src/tools/pw-dot.c src/tools/pw-dump.c src/tools/pw-link.c src/tools/pw-loopback.c src/tools/pw-metadata.c src/tools/pw-mididump.c src/tools/pw-mon.c src/tools/pw-profiler.c src/tools/pw-top.c spa/plugins/alsa/acp/acp.c spa/plugins/alsa/acp/alsa-mixer.c spa/plugins/alsa/acp/alsa-util.c spa/plugins/alsa/acp/channelmap.h spa/plugins/alsa/acp/compat.c spa/plugins/bluez5/bluez5-device.c pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/po/POTFILES.skip000066400000000000000000000001421511204443500232260ustar00rootroot00000000000000src/daemon/systemd/system/pipewire-pulse.service.in src/daemon/systemd/system/pipewire.service.in pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/po/af.po000066400000000000000000000363061511204443500220520ustar00rootroot00000000000000# Afrikaans translation of PipeWire. # This file is distributed under the same license as the PipeWire package. # F Wolff , 2019. msgid "" msgstr "" "Project-Id-Version: master\n" "Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/pipewire/" "issues/new\n" "POT-Creation-Date: 2021-04-18 16:54+0800\n" "PO-Revision-Date: 2019-01-09 12:17+0200\n" "Last-Translator: F Wolff \n" "Language-Team: translate-discuss-af@lists.sourceforge.net\n" "Language: af\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Generator: Virtaal 0.7.1\n" #: src/daemon/pipewire.c:43 #, c-format msgid "" "%s [options]\n" " -h, --help Show this help\n" " --version Show version\n" " -c, --config Load config (Default %s)\n" msgstr "" #: src/daemon/pipewire.desktop.in:4 msgid "PipeWire Media System" msgstr "" #: src/daemon/pipewire.desktop.in:5 msgid "Start the PipeWire Media System" msgstr "" #: src/examples/media-session/alsa-monitor.c:526 #: spa/plugins/alsa/acp/compat.c:187 msgid "Built-in Audio" msgstr "Ingeboude oudio" #: src/examples/media-session/alsa-monitor.c:530 #: spa/plugins/alsa/acp/compat.c:192 msgid "Modem" msgstr "Modem" #: src/examples/media-session/alsa-monitor.c:539 msgid "Unknown device" msgstr "" #: src/tools/pw-cat.c:991 #, c-format msgid "" "%s [options] \n" " -h, --help Show this help\n" " --version Show version\n" " -v, --verbose Enable verbose operations\n" "\n" msgstr "" #: src/tools/pw-cat.c:998 #, c-format msgid "" " -R, --remote Remote daemon name\n" " --media-type Set media type (default %s)\n" " --media-category Set media category (default %s)\n" " --media-role Set media role (default %s)\n" " --target Set node target (default %s)\n" " 0 means don't link\n" " --latency Set node latency (default %s)\n" " Xunit (unit = s, ms, us, ns)\n" " or direct samples (256)\n" " the rate is the one of the source " "file\n" " --list-targets List available targets for --target\n" "\n" msgstr "" #: src/tools/pw-cat.c:1016 #, c-format msgid "" " --rate Sample rate (req. for rec) (default " "%u)\n" " --channels Number of channels (req. for rec) " "(default %u)\n" " --channel-map Channel map\n" " one of: \"stereo\", " "\"surround-51\",... or\n" " comma separated list of channel " "names: eg. \"FL,FR\"\n" " --format Sample format %s (req. for rec) " "(default %s)\n" " --volume Stream volume 0-1.0 (default %.3f)\n" " -q --quality Resampler quality (0 - 15) (default " "%d)\n" "\n" msgstr "" #: src/tools/pw-cat.c:1033 msgid "" " -p, --playback Playback mode\n" " -r, --record Recording mode\n" " -m, --midi Midi mode\n" "\n" msgstr "" #: src/tools/pw-cli.c:2932 #, c-format msgid "" "%s [options] [command]\n" " -h, --help Show this help\n" " --version Show version\n" " -d, --daemon Start as daemon (Default false)\n" " -r, --remote Remote daemon name\n" "\n" msgstr "" #: spa/plugins/alsa/acp/acp.c:290 msgid "Pro Audio" msgstr "" #: spa/plugins/alsa/acp/acp.c:411 spa/plugins/alsa/acp/alsa-mixer.c:4704 #: spa/plugins/bluez5/bluez5-device.c:1000 msgid "Off" msgstr "Af" #: spa/plugins/alsa/acp/channelmap.h:466 msgid "(invalid)" msgstr "(ongeldig)" #: spa/plugins/alsa/acp/alsa-mixer.c:2709 msgid "Input" msgstr "Toevoer" #: spa/plugins/alsa/acp/alsa-mixer.c:2710 msgid "Docking Station Input" msgstr "Dokstasietoevoer" #: spa/plugins/alsa/acp/alsa-mixer.c:2711 msgid "Docking Station Microphone" msgstr "Dokstasiemikrofoon" #: spa/plugins/alsa/acp/alsa-mixer.c:2712 msgid "Docking Station Line In" msgstr "Dokstasie se lyn in" #: spa/plugins/alsa/acp/alsa-mixer.c:2713 #: spa/plugins/alsa/acp/alsa-mixer.c:2804 msgid "Line In" msgstr "Lyn in" #: spa/plugins/alsa/acp/alsa-mixer.c:2714 #: spa/plugins/alsa/acp/alsa-mixer.c:2798 #: spa/plugins/bluez5/bluez5-device.c:1145 msgid "Microphone" msgstr "Mikrofoon" #: spa/plugins/alsa/acp/alsa-mixer.c:2715 #: spa/plugins/alsa/acp/alsa-mixer.c:2799 msgid "Front Microphone" msgstr "Mikrofoon voor" #: spa/plugins/alsa/acp/alsa-mixer.c:2716 #: spa/plugins/alsa/acp/alsa-mixer.c:2800 msgid "Rear Microphone" msgstr "Mikrofoon agter" #: spa/plugins/alsa/acp/alsa-mixer.c:2717 msgid "External Microphone" msgstr "Eksterne mikrofoon" #: spa/plugins/alsa/acp/alsa-mixer.c:2718 #: spa/plugins/alsa/acp/alsa-mixer.c:2802 msgid "Internal Microphone" msgstr "Interne mikrofoon" #: spa/plugins/alsa/acp/alsa-mixer.c:2719 #: spa/plugins/alsa/acp/alsa-mixer.c:2805 msgid "Radio" msgstr "Radio" #: spa/plugins/alsa/acp/alsa-mixer.c:2720 #: spa/plugins/alsa/acp/alsa-mixer.c:2806 msgid "Video" msgstr "Video" #: spa/plugins/alsa/acp/alsa-mixer.c:2721 msgid "Automatic Gain Control" msgstr "Outomatiese versterkingkontrole" #: spa/plugins/alsa/acp/alsa-mixer.c:2722 msgid "No Automatic Gain Control" msgstr "Geen outomatiese versterkingkontrole" #: spa/plugins/alsa/acp/alsa-mixer.c:2723 msgid "Boost" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2724 msgid "No Boost" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2725 msgid "Amplifier" msgstr "Versterker" #: spa/plugins/alsa/acp/alsa-mixer.c:2726 msgid "No Amplifier" msgstr "Geen versterker" #: spa/plugins/alsa/acp/alsa-mixer.c:2727 msgid "Bass Boost" msgstr "Basversterker" #: spa/plugins/alsa/acp/alsa-mixer.c:2728 msgid "No Bass Boost" msgstr "Geen basversterker" #: spa/plugins/alsa/acp/alsa-mixer.c:2729 #: spa/plugins/bluez5/bluez5-device.c:1150 msgid "Speaker" msgstr "Luidspreker" #: spa/plugins/alsa/acp/alsa-mixer.c:2730 #: spa/plugins/alsa/acp/alsa-mixer.c:2808 msgid "Headphones" msgstr "Oorfone" #: spa/plugins/alsa/acp/alsa-mixer.c:2797 msgid "Analog Input" msgstr "Analoë toevoer" #: spa/plugins/alsa/acp/alsa-mixer.c:2801 msgid "Dock Microphone" msgstr "Dokmikrofoon" #: spa/plugins/alsa/acp/alsa-mixer.c:2803 msgid "Headset Microphone" msgstr "Kopstukmikrofoon" #: spa/plugins/alsa/acp/alsa-mixer.c:2807 msgid "Analog Output" msgstr "Analoë afvoer" #: spa/plugins/alsa/acp/alsa-mixer.c:2809 #, fuzzy msgid "Headphones 2" msgstr "Oorfone" #: spa/plugins/alsa/acp/alsa-mixer.c:2810 msgid "Headphones Mono Output" msgstr "Oorfone, mono-afvoer" #: spa/plugins/alsa/acp/alsa-mixer.c:2811 msgid "Line Out" msgstr "Lyn uit" #: spa/plugins/alsa/acp/alsa-mixer.c:2812 msgid "Analog Mono Output" msgstr "Analoë mono-afvoer" #: spa/plugins/alsa/acp/alsa-mixer.c:2813 msgid "Speakers" msgstr "Luidsprekers" #: spa/plugins/alsa/acp/alsa-mixer.c:2814 msgid "HDMI / DisplayPort" msgstr "HDMI / DisplayPort" #: spa/plugins/alsa/acp/alsa-mixer.c:2815 msgid "Digital Output (S/PDIF)" msgstr "Digitale afvoer (S/PDIF)" #: spa/plugins/alsa/acp/alsa-mixer.c:2816 msgid "Digital Input (S/PDIF)" msgstr "Digitale toevoer (S/PDIF)" #: spa/plugins/alsa/acp/alsa-mixer.c:2817 msgid "Multichannel Input" msgstr "Multikanaaltoevoer" #: spa/plugins/alsa/acp/alsa-mixer.c:2818 msgid "Multichannel Output" msgstr "Multikanaalafvoer" #: spa/plugins/alsa/acp/alsa-mixer.c:2819 msgid "Game Output" msgstr "Speletjieafvoer" #: spa/plugins/alsa/acp/alsa-mixer.c:2820 #: spa/plugins/alsa/acp/alsa-mixer.c:2821 msgid "Chat Output" msgstr "Geselsafvoer" #: spa/plugins/alsa/acp/alsa-mixer.c:2822 #, fuzzy msgid "Chat Input" msgstr "Geselsafvoer" #: spa/plugins/alsa/acp/alsa-mixer.c:2823 #, fuzzy msgid "Virtual Surround 7.1" msgstr "Analoë omringklank 7.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4527 msgid "Analog Mono" msgstr "Analoë mono" #: spa/plugins/alsa/acp/alsa-mixer.c:4528 #, fuzzy msgid "Analog Mono (Left)" msgstr "Analoë mono" #: spa/plugins/alsa/acp/alsa-mixer.c:4529 #, fuzzy msgid "Analog Mono (Right)" msgstr "Analoë mono" #. Note: Not translated to "Analog Stereo Input", because the source #. * name gets "Input" appended to it automatically, so adding "Input" #. * here would lead to the source name to become "Analog Stereo Input #. * Input". The same logic applies to analog-stereo-output, #. * multichannel-input and multichannel-output. #: spa/plugins/alsa/acp/alsa-mixer.c:4530 #: spa/plugins/alsa/acp/alsa-mixer.c:4538 #: spa/plugins/alsa/acp/alsa-mixer.c:4539 msgid "Analog Stereo" msgstr "Analoë stereo" #: spa/plugins/alsa/acp/alsa-mixer.c:4531 msgid "Mono" msgstr "Mono" #: spa/plugins/alsa/acp/alsa-mixer.c:4532 msgid "Stereo" msgstr "Stereo" #: spa/plugins/alsa/acp/alsa-mixer.c:4540 #: spa/plugins/alsa/acp/alsa-mixer.c:4698 #: spa/plugins/bluez5/bluez5-device.c:1135 msgid "Headset" msgstr "Kopstuk" #: spa/plugins/alsa/acp/alsa-mixer.c:4541 #: spa/plugins/alsa/acp/alsa-mixer.c:4699 #, fuzzy msgid "Speakerphone" msgstr "Luidspreker" #: spa/plugins/alsa/acp/alsa-mixer.c:4542 #: spa/plugins/alsa/acp/alsa-mixer.c:4543 msgid "Multichannel" msgstr "Multikanaal" #: spa/plugins/alsa/acp/alsa-mixer.c:4544 msgid "Analog Surround 2.1" msgstr "Analoë omringklank 2.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4545 msgid "Analog Surround 3.0" msgstr "Analoë omringklank 3.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4546 msgid "Analog Surround 3.1" msgstr "Analoë omringklank 3.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4547 msgid "Analog Surround 4.0" msgstr "Analoë omringklank 4.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4548 msgid "Analog Surround 4.1" msgstr "Analoë omringklank 4.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4549 msgid "Analog Surround 5.0" msgstr "Analoë omringklank 5.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4550 msgid "Analog Surround 5.1" msgstr "Analoë omringklank 5.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4551 msgid "Analog Surround 6.0" msgstr "Analoë omringklank 6.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4552 msgid "Analog Surround 6.1" msgstr "Analoë omringklank 6.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4553 msgid "Analog Surround 7.0" msgstr "Analoë omringklank 7.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4554 msgid "Analog Surround 7.1" msgstr "Analoë omringklank 7.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4555 msgid "Digital Stereo (IEC958)" msgstr "Digitale stereo (IEC958)" #: spa/plugins/alsa/acp/alsa-mixer.c:4556 msgid "Digital Surround 4.0 (IEC958/AC3)" msgstr "Digitale omringklank 4.0 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4557 msgid "Digital Surround 5.1 (IEC958/AC3)" msgstr "Digitale omringklank 5.1 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4558 msgid "Digital Surround 5.1 (IEC958/DTS)" msgstr "Digitale omringklank 5.1 (IEC958/DTS)" #: spa/plugins/alsa/acp/alsa-mixer.c:4559 msgid "Digital Stereo (HDMI)" msgstr "Digitale stereo (HDMI)" #: spa/plugins/alsa/acp/alsa-mixer.c:4560 msgid "Digital Surround 5.1 (HDMI)" msgstr "Digitale omringklank 5.1 (HDMI)" #: spa/plugins/alsa/acp/alsa-mixer.c:4561 msgid "Chat" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4562 msgid "Game" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4696 msgid "Analog Mono Duplex" msgstr "Analoë monodupleks" #: spa/plugins/alsa/acp/alsa-mixer.c:4697 msgid "Analog Stereo Duplex" msgstr "Analoë stereodupleks" #: spa/plugins/alsa/acp/alsa-mixer.c:4700 msgid "Digital Stereo Duplex (IEC958)" msgstr "Digitale stereodupleks (IEC958)" #: spa/plugins/alsa/acp/alsa-mixer.c:4701 msgid "Multichannel Duplex" msgstr "Multikanaaldupleks" #: spa/plugins/alsa/acp/alsa-mixer.c:4702 msgid "Stereo Duplex" msgstr "Stereodupleks" #: spa/plugins/alsa/acp/alsa-mixer.c:4703 msgid "Mono Chat + 7.1 Surround" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4806 #, c-format msgid "%s Output" msgstr "%s-afvoer" #: spa/plugins/alsa/acp/alsa-mixer.c:4813 #, c-format msgid "%s Input" msgstr "%s-toevoer" #: spa/plugins/alsa/acp/alsa-util.c:1175 spa/plugins/alsa/acp/alsa-util.c:1269 #, c-format msgid "" "snd_pcm_avail() returned a value that is exceptionally large: %lu byte (%lu " "ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_avail() returned a value that is exceptionally large: %lu bytes (%lu " "ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" msgstr[1] "" #: spa/plugins/alsa/acp/alsa-util.c:1241 #, c-format msgid "" "snd_pcm_delay() returned a value that is exceptionally large: %li byte (%s" "%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_delay() returned a value that is exceptionally large: %li bytes (%s" "%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" msgstr[1] "" #: spa/plugins/alsa/acp/alsa-util.c:1288 #, c-format msgid "" "snd_pcm_avail_delay() returned strange values: delay %lu is less than avail " "%lu.\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr "" #: spa/plugins/alsa/acp/alsa-util.c:1331 #, c-format msgid "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu byte " "(%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu bytes " "(%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" msgstr[1] "" #: spa/plugins/bluez5/bluez5-device.c:1010 msgid "Audio Gateway (A2DP Source & HSP/HFP AG)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1033 #, c-format msgid "High Fidelity Playback (A2DP Sink, codec %s)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1035 #, c-format msgid "High Fidelity Duplex (A2DP Source/Sink, codec %s)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1041 msgid "High Fidelity Playback (A2DP Sink)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1043 msgid "High Fidelity Duplex (A2DP Source/Sink)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1070 #, c-format msgid "Headset Head Unit (HSP/HFP, codec %s)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1074 msgid "Headset Head Unit (HSP/HFP)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1140 msgid "Handsfree" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1155 msgid "Headphone" msgstr "Oorfone" #: spa/plugins/bluez5/bluez5-device.c:1160 msgid "Portable" msgstr "Draagbaar" #: spa/plugins/bluez5/bluez5-device.c:1165 msgid "Car" msgstr "Kar" #: spa/plugins/bluez5/bluez5-device.c:1170 msgid "HiFi" msgstr "Hoëtroustel" #: spa/plugins/bluez5/bluez5-device.c:1175 msgid "Phone" msgstr "Foon" #: spa/plugins/bluez5/bluez5-device.c:1181 #, fuzzy msgid "Bluetooth" msgstr "Bluetooth-toevoer" pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/po/ar.po000066400000000000000000000671061511204443500220700ustar00rootroot00000000000000# Arabic translation of PipeWire # This file is distributed under the same license as the pipewire package. # # SPDX-FileCopyrightText: 2025 r msgid "" msgstr "" "Project-Id-Version: pipewire\n" "Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/pipewire/issues/" "new\n" "POT-Creation-Date: 2024-02-25 03:43+0300\n" "PO-Revision-Date: 2025-06-22 13:09+0200\n" "Last-Translator: r \n" "Language-Team: Arabic \n" "Language: ar\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && " "n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;\n" "X-Generator: Lokalize 25.04.0\n" #: src/daemon/pipewire.c:26 #, c-format msgid "" "%s [options]\n" " -h, --help Show this help\n" " --version Show version\n" " -c, --config Load config (Default %s)\n" msgstr "" "%s [خيارات]\n" " -h, --help إظهار هذه المساعدة\n" " --version إظهار الإصدار\n" " -c, --config تحميل التضبيط (الافتراضي %s)\n" #: src/daemon/pipewire.desktop.in:4 msgid "PipeWire Media System" msgstr "نظام الوسائط بايب‌واير" #: src/daemon/pipewire.desktop.in:5 msgid "Start the PipeWire Media System" msgstr "ابدأ نظام الوسائط بايب‌واير" #: src/modules/module-protocol-pulse/modules/module-tunnel-sink.c:159 #: src/modules/module-protocol-pulse/modules/module-tunnel-source.c:159 #, c-format msgid "Tunnel to %s%s%s" msgstr "نفّق إلى %s%s%s" #: src/modules/module-fallback-sink.c:40 msgid "Dummy Output" msgstr "إخراج وهمي" #: src/modules/module-pulse-tunnel.c:774 #, c-format msgid "Tunnel for %s@%s" msgstr "نفّق ل %s@%s" #: src/modules/module-zeroconf-discover.c:315 msgid "Unknown device" msgstr "جهاز غير معروف" #: src/modules/module-zeroconf-discover.c:327 #, c-format msgid "%s on %s@%s" msgstr "%s على %s@%s" #: src/modules/module-zeroconf-discover.c:331 #, c-format msgid "%s on %s" msgstr "%s على %s" #: src/tools/pw-cat.c:991 #, c-format msgid "" "%s [options] [|-]\n" " -h, --help Show this help\n" " --version Show version\n" " -v, --verbose Enable verbose operations\n" "\n" msgstr "" "%s [خيارات] [|-]\n" " -h, --help إظهار هذه المساعدة\n" " --version إظهار الإصدار\n" " -v, --verbose تمكين العمليات المطولة\n" "\n" #: src/tools/pw-cat.c:998 #, c-format msgid "" " -R, --remote Remote daemon name\n" " --media-type Set media type (default %s)\n" " --media-category Set media category (default %s)\n" " --media-role Set media role (default %s)\n" " --target Set node target serial or name " "(default %s)\n" " 0 means don't link\n" " --latency Set node latency (default %s)\n" " Xunit (unit = s, ms, us, ns)\n" " or direct samples (256)\n" " the rate is the one of the source " "file\n" " -P --properties Set node properties\n" "\n" msgstr "" "-R, --remote اسم العملية الخفية البعيدة\n" " --media-type تعيين نوع الوسائط (الافتراضي %s)\n" " --media-category تعيين فئة الوسائط (الافتراضي %s)\n" " --media-role تعيين دور الوسائط (الافتراضي %s)\n" " --target تعيين المسلسل أو اسم هدف العُقدة " "\n (الافتراضي %s)" " 0 يعني عدم الربط\n" " --latency تعيين زمن انتقال العُقدة (الافتراضي %s" ")\n" " وحدة X (الوحدة = s, ms, us, ns)\n" " أو عينات مباشرة (256)\n" " المعدل هو معدل ملف المصدر\n" " -P --properties تعيين خصائص العُقدة\n" "\n" #: src/tools/pw-cat.c:1016 #, c-format msgid "" " --rate Sample rate (req. for rec) (default " "%u)\n" " --channels Number of channels (req. for rec) " "(default %u)\n" " --channel-map Channel map\n" " one of: \"stereo\", " "\"surround-51\",... or\n" " comma separated list of channel " "names: eg. \"FL,FR\"\n" " --format Sample format %s (req. for rec) " "(default %s)\n" " --volume Stream volume 0-1.0 (default %.3f)\n" " -q --quality Resampler quality (0 - 15) (default " "%d)\n" "\n" msgstr "" "--rate معدل العينة (مطلوب للتسجيل) (الافتراضي %u)\n" " --channels عدد القنوات (مطلوب للتسجيل) (الافتراضي" " %u)\n" " --channel-map خريطة القنوات\n" " أحد الخيارات: \"مِجساميّ\", \"محيط" "ي-51\",... أو\n" " قائمة بأسماء القنوات مفصولة بفاصلة" ": مثال \"FL,FR\"\n" " --format تنسيق العينة %s (مطلوب للتسجيل) (الافت" "راضي %s)\n" " --volume حجم البث 0-1.0 (الافتراضي %.3f)\n" " -q --quality جودة إعادة التشكيل (0 - 15) (الافتراضي" " %d)\n" "\n" #: src/tools/pw-cat.c:1033 msgid "" " -p, --playback Playback mode\n" " -r, --record Recording mode\n" " -m, --midi Midi mode\n" " -d, --dsd DSD mode\n" " -o, --encoded Encoded mode\n" "\n" msgstr "" "-p, --playback وضع التشغيل\n" " -r, --record وضع التسجيل\n" " -m, --midi وضع Midi\n" " -d, --dsd وضع DSD\n" " -o, --encoded الوضع المرمّز\n" "\n" #: src/tools/pw-cli.c:2252 #, c-format msgid "" "%s [options] [command]\n" " -h, --help Show this help\n" " --version Show version\n" " -d, --daemon Start as daemon (Default false)\n" " -r, --remote Remote daemon name\n" " -m, --monitor Monitor activity\n" "\n" msgstr "" "%s [خيارات] [أمر]\n" " -h, --help إظهار هذه المساعدة\n" " --version إظهار الإصدار\n" " -d, --daemon بدء كخفي (افتراضي خطأ)\n" " -r, --remote اسم الخفي البعيد\n" " -m, --monitor مراقبة النشاط\n" "\n" #: spa/plugins/alsa/acp/acp.c:327 msgid "Pro Audio" msgstr "صوت احترافي" #: spa/plugins/alsa/acp/acp.c:488 spa/plugins/alsa/acp/alsa-mixer.c:4633 #: spa/plugins/bluez5/bluez5-device.c:1701 msgid "Off" msgstr "معطل" #: spa/plugins/alsa/acp/alsa-mixer.c:2652 msgid "Input" msgstr "الإدخال" #: spa/plugins/alsa/acp/alsa-mixer.c:2653 msgid "Docking Station Input" msgstr "إدخال محطة الإرساء" #: spa/plugins/alsa/acp/alsa-mixer.c:2654 msgid "Docking Station Microphone" msgstr "ميكروفون محطة الإرساء" #: spa/plugins/alsa/acp/alsa-mixer.c:2655 msgid "Docking Station Line In" msgstr "مدخل خطي محط الارساء" #: spa/plugins/alsa/acp/alsa-mixer.c:2656 #: spa/plugins/alsa/acp/alsa-mixer.c:2747 msgid "Line In" msgstr "مدخل خطي" #: spa/plugins/alsa/acp/alsa-mixer.c:2657 #: spa/plugins/alsa/acp/alsa-mixer.c:2741 #: spa/plugins/bluez5/bluez5-device.c:1989 msgid "Microphone" msgstr "ميكروفون" #: spa/plugins/alsa/acp/alsa-mixer.c:2658 #: spa/plugins/alsa/acp/alsa-mixer.c:2742 msgid "Front Microphone" msgstr "ميكروفون أمامي" #: spa/plugins/alsa/acp/alsa-mixer.c:2659 #: spa/plugins/alsa/acp/alsa-mixer.c:2743 msgid "Rear Microphone" msgstr "ميكروفون خلفي" #: spa/plugins/alsa/acp/alsa-mixer.c:2660 msgid "External Microphone" msgstr "ميكروفون خارجي" #: spa/plugins/alsa/acp/alsa-mixer.c:2661 #: spa/plugins/alsa/acp/alsa-mixer.c:2745 msgid "Internal Microphone" msgstr "ميكروفون داخلي" #: spa/plugins/alsa/acp/alsa-mixer.c:2662 #: spa/plugins/alsa/acp/alsa-mixer.c:2748 msgid "Radio" msgstr "راديو" #: spa/plugins/alsa/acp/alsa-mixer.c:2663 #: spa/plugins/alsa/acp/alsa-mixer.c:2749 msgid "Video" msgstr "مرئيّ" #: spa/plugins/alsa/acp/alsa-mixer.c:2664 msgid "Automatic Gain Control" msgstr "التحكم التلقائي في الكسب" #: spa/plugins/alsa/acp/alsa-mixer.c:2665 msgid "No Automatic Gain Control" msgstr "دون التحكم التلقائي في الكسب" #: spa/plugins/alsa/acp/alsa-mixer.c:2666 msgid "Boost" msgstr "تعزيز" #: spa/plugins/alsa/acp/alsa-mixer.c:2667 msgid "No Boost" msgstr "دون تعزيز" #: spa/plugins/alsa/acp/alsa-mixer.c:2668 msgid "Amplifier" msgstr "" "" "مُضَخِّم" #: spa/plugins/alsa/acp/alsa-mixer.c:2669 msgid "No Amplifier" msgstr "دون مُضَخِّم" #: spa/plugins/alsa/acp/alsa-mixer.c:2670 msgid "Bass Boost" msgstr "تعزيز القرار" #: spa/plugins/alsa/acp/alsa-mixer.c:2671 msgid "No Bass Boost" msgstr "دون تعزيز القرار" #: spa/plugins/alsa/acp/alsa-mixer.c:2672 #: spa/plugins/bluez5/bluez5-device.c:1995 msgid "Speaker" msgstr "مكبر صوت" #: spa/plugins/alsa/acp/alsa-mixer.c:2673 #: spa/plugins/alsa/acp/alsa-mixer.c:2751 msgid "Headphones" msgstr "سماعات الأذن" #: spa/plugins/alsa/acp/alsa-mixer.c:2740 msgid "Analog Input" msgstr "إدخال التناظري" #: spa/plugins/alsa/acp/alsa-mixer.c:2744 msgid "Dock Microphone" msgstr "ميكروفون محطة الارساء" #: spa/plugins/alsa/acp/alsa-mixer.c:2746 msgid "Headset Microphone" msgstr "ميكروفون سماعة الرأس" #: spa/plugins/alsa/acp/alsa-mixer.c:2750 msgid "Analog Output" msgstr "إخراج تناظري" #: spa/plugins/alsa/acp/alsa-mixer.c:2752 msgid "Headphones 2" msgstr "سماعات الأذن 2" #: spa/plugins/alsa/acp/alsa-mixer.c:2753 msgid "Headphones Mono Output" msgstr "سماعات الأذن أحادية الإخراج" #: spa/plugins/alsa/acp/alsa-mixer.c:2754 msgid "Line Out" msgstr "مخرج خطي" #: spa/plugins/alsa/acp/alsa-mixer.c:2755 msgid "Analog Mono Output" msgstr "إخراج أحادي تناظري" #: spa/plugins/alsa/acp/alsa-mixer.c:2756 msgid "Speakers" msgstr "مكبرات الصوت" #: spa/plugins/alsa/acp/alsa-mixer.c:2757 msgid "HDMI / DisplayPort" msgstr "HDMI / ديسبلاي بورت" #: spa/plugins/alsa/acp/alsa-mixer.c:2758 msgid "Digital Output (S/PDIF)" msgstr "إخراج الرقمي (S/PDIF)" #: spa/plugins/alsa/acp/alsa-mixer.c:2759 msgid "Digital Input (S/PDIF)" msgstr "إدخال الرقمي (S/PDIF)" #: spa/plugins/alsa/acp/alsa-mixer.c:2760 msgid "Multichannel Input" msgstr "إدخال متعدد القنوات" #: spa/plugins/alsa/acp/alsa-mixer.c:2761 msgid "Multichannel Output" msgstr "إخراج متعدد القنوات" #: spa/plugins/alsa/acp/alsa-mixer.c:2762 msgid "Game Output" msgstr "إخراج اللعبة" #: spa/plugins/alsa/acp/alsa-mixer.c:2763 #: spa/plugins/alsa/acp/alsa-mixer.c:2764 msgid "Chat Output" msgstr "إخراج المحادثة" #: spa/plugins/alsa/acp/alsa-mixer.c:2765 msgid "Chat Input" msgstr "إدخال المحادثة" #: spa/plugins/alsa/acp/alsa-mixer.c:2766 msgid "Virtual Surround 7.1" msgstr "محيطي تخيلي 7.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4456 msgid "Analog Mono" msgstr "تناظري أحادي" #: spa/plugins/alsa/acp/alsa-mixer.c:4457 msgid "Analog Mono (Left)" msgstr "تناظري أحادي (يسار)" #: spa/plugins/alsa/acp/alsa-mixer.c:4458 msgid "Analog Mono (Right)" msgstr "تناظري أحادي (يمين)" #. Note: Not translated to "Analog Stereo Input", because the source #. * name gets "Input" appended to it automatically, so adding "Input" #. * here would lead to the source name to become "Analog Stereo Input #. * Input". The same logic applies to analog-stereo-output, #. * multichannel-input and multichannel-output. #: spa/plugins/alsa/acp/alsa-mixer.c:4459 #: spa/plugins/alsa/acp/alsa-mixer.c:4467 #: spa/plugins/alsa/acp/alsa-mixer.c:4468 msgid "Analog Stereo" msgstr "مِجْساميّ تناظري" #: spa/plugins/alsa/acp/alsa-mixer.c:4460 msgid "Mono" msgstr "أحادي" #: spa/plugins/alsa/acp/alsa-mixer.c:4461 msgid "Stereo" msgstr "مِجْساميّ" #: spa/plugins/alsa/acp/alsa-mixer.c:4469 #: spa/plugins/alsa/acp/alsa-mixer.c:4627 #: spa/plugins/bluez5/bluez5-device.c:1977 msgid "Headset" msgstr "سماعة الرأس" #: spa/plugins/alsa/acp/alsa-mixer.c:4470 #: spa/plugins/alsa/acp/alsa-mixer.c:4628 msgid "Speakerphone" msgstr "مكبر صوت الجوّال" #: spa/plugins/alsa/acp/alsa-mixer.c:4471 #: spa/plugins/alsa/acp/alsa-mixer.c:4472 msgid "Multichannel" msgstr "متعدد القنوات" #: spa/plugins/alsa/acp/alsa-mixer.c:4473 msgid "Analog Surround 2.1" msgstr "تناظري محيطي 2.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4474 msgid "Analog Surround 3.0" msgstr "تناظري محيطي 3.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4475 msgid "Analog Surround 3.1" msgstr "تناظري محيطي 3.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4476 msgid "Analog Surround 4.0" msgstr "تناظري محيطي 4.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4477 msgid "Analog Surround 4.1" msgstr "تناظري محيطي 4.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4478 msgid "Analog Surround 5.0" msgstr "تناظري محيطي 5.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4479 msgid "Analog Surround 5.1" msgstr "تناظري محيطي 5.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4480 msgid "Analog Surround 6.0" msgstr "تناظري محيطي 6.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4481 msgid "Analog Surround 6.1" msgstr "تناظري محيطي 6.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4482 msgid "Analog Surround 7.0" msgstr "تناظري محيطي 7.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4483 msgid "Analog Surround 7.1" msgstr "تناظري محيطي 7.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4484 msgid "Digital Stereo (IEC958)" msgstr "مِجْساميّ رقمي (IEC958)" #: spa/plugins/alsa/acp/alsa-mixer.c:4485 msgid "Digital Surround 4.0 (IEC958/AC3)" msgstr "محيطي رقمي 4.0 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4486 msgid "Digital Surround 5.1 (IEC958/AC3)" msgstr "محيطي رقمي 5.1 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4487 msgid "Digital Surround 5.1 (IEC958/DTS)" msgstr "محيطي رقمي 5.1 (IEC958/DTS)" #: spa/plugins/alsa/acp/alsa-mixer.c:4488 msgid "Digital Stereo (HDMI)" msgstr "مِجْساميّ رقمي (HDMI)" #: spa/plugins/alsa/acp/alsa-mixer.c:4489 msgid "Digital Surround 5.1 (HDMI)" msgstr "محيطي رقمي 5.1 (HDMI)" #: spa/plugins/alsa/acp/alsa-mixer.c:4490 msgid "Chat" msgstr "محادثة" #: spa/plugins/alsa/acp/alsa-mixer.c:4491 msgid "Game" msgstr "لعبة" #: spa/plugins/alsa/acp/alsa-mixer.c:4625 msgid "Analog Mono Duplex" msgstr "تناظري أحادي مزدوج" #: spa/plugins/alsa/acp/alsa-mixer.c:4626 msgid "Analog Stereo Duplex" msgstr "مِجْساميّ تناظري مزدوج" #: spa/plugins/alsa/acp/alsa-mixer.c:4629 msgid "Digital Stereo Duplex (IEC958)" msgstr "مِجْساميّ رقمي مزدوج (IEC958)" #: spa/plugins/alsa/acp/alsa-mixer.c:4630 msgid "Multichannel Duplex" msgstr "مزدوج متعدد القنوات" #: spa/plugins/alsa/acp/alsa-mixer.c:4631 msgid "Stereo Duplex" msgstr "مِجساميّ مزدوج" #: spa/plugins/alsa/acp/alsa-mixer.c:4632 msgid "Mono Chat + 7.1 Surround" msgstr "محادثة أحادية + محيطي 7.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4733 #, c-format msgid "%s Output" msgstr "إخراج %s" #: spa/plugins/alsa/acp/alsa-mixer.c:4741 #, c-format msgid "%s Input" msgstr "إدخال %s" #: spa/plugins/alsa/acp/alsa-util.c:1220 spa/plugins/alsa/acp/alsa-util.c:1314 #, c-format msgid "" "snd_pcm_avail() returned a value that is exceptionally large: %lu byte (%lu " "ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_avail() returned a value that is exceptionally large: %lu bytes (%lu " "ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "أرجعت الدالة snd_pcm_avail() قيمة كبيرة بشكل استثنائي: %lu بايت " "(%lu مللي ثانية).\n" "من المرجح أن يكون هذا خطأ في برنامج تشغيل ALSA '%s'. يُرجى الإبلاغ عن هذه المش" "كلة " "لمطوري ALSA." msgstr[1] "" "أرجعت الدالة snd_pcm_avail() قيمة كبيرة بشكل استثنائي: %lu بايتات " "(%lu مللي ثانية).\n" "من المرجح أن يكون هذا خطأ في برنامج تشغيل ALSA '%s'. يُرجى الإبلاغ عن هذه المش" "كلة " "لمطوري ALSA." msgstr[2] "" "أرجعت الدالة snd_pcm_avail() قيمة كبيرة بشكل استثنائي: %lu بايتات " "(%lu مللي ثانية).\n" "من المرجح أن يكون هذا خطأ في برنامج تشغيل ALSA '%s'. يُرجى الإبلاغ عن هذه المش" "كلة " "لمطوري ALSA." msgstr[3] "" "أرجعت الدالة snd_pcm_avail() قيمة كبيرة بشكل استثنائي: %lu بايتات " "(%lu مللي ثانية).\n" "من المرجح أن يكون هذا خطأ في برنامج تشغيل ALSA '%s'. يُرجى الإبلاغ عن هذه المش" "كلة " "لمطوري ALSA." msgstr[4] "" "أرجعت الدالة snd_pcm_avail() قيمة كبيرة بشكل استثنائي: %lu بايتات " "(%lu مللي ثانية).\n" "من المرجح أن يكون هذا خطأ في برنامج تشغيل ALSA '%s'. يُرجى الإبلاغ عن هذه المش" "كلة " "لمطوري ALSA." msgstr[5] "" "أرجعت الدالة snd_pcm_avail() قيمة كبيرة بشكل استثنائي: %lu بايتات " "(%lu مللي ثانية).\n" "من المرجح أن يكون هذا خطأ في برنامج تشغيل ALSA '%s'. يُرجى الإبلاغ عن هذه المش" "كلة " "لمطوري ALSA." #: spa/plugins/alsa/acp/alsa-util.c:1286 #, c-format msgid "" "snd_pcm_delay() returned a value that is exceptionally large: %li byte " "(%s%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_delay() returned a value that is exceptionally large: %li bytes " "(%s%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "أرجعت الدالة snd_pcm_delay() قيمة كبيرة بشكل استثنائي: %li بايت " "(%s%lu مللي ثانية).\n" "من المرجح أن يكون هذا خطأ في برنامج تشغيل ALSA '%s'. يُرجى الإبلاغ عن هذه المش" "كلة " "لمطوري ALSA." msgstr[1] "" "أرجعت الدالة snd_pcm_delay() قيمة كبيرة بشكل استثنائي: %li بايتات " "(%s%lu مللي ثانية).\n" "من المرجح أن يكون هذا خطأ في برنامج تشغيل ALSA '%s'. يُرجى الإبلاغ عن هذه المش" "كلة " "لمطوري ALSA." msgstr[2] "" "أرجعت الدالة snd_pcm_delay() قيمة كبيرة بشكل استثنائي: %li بايتات " "(%s%lu مللي ثانية).\n" "من المرجح أن يكون هذا خطأ في برنامج تشغيل ALSA '%s'. يُرجى الإبلاغ عن هذه المش" "كلة " "لمطوري ALSA." msgstr[3] "" "أرجعت الدالة snd_pcm_delay() قيمة كبيرة بشكل استثنائي: %li بايتات " "(%s%lu مللي ثانية).\n" "من المرجح أن يكون هذا خطأ في برنامج تشغيل ALSA '%s'. يُرجى الإبلاغ عن هذه المش" "كلة " "لمطوري ALSA." msgstr[4] "" "أرجعت الدالة snd_pcm_delay() قيمة كبيرة بشكل استثنائي: %li بايتات " "(%s%lu مللي ثانية).\n" "من المرجح أن يكون هذا خطأ في برنامج تشغيل ALSA '%s'. يُرجى الإبلاغ عن هذه المش" "كلة " "لمطوري ALSA." msgstr[5] "" "أرجعت الدالة snd_pcm_delay() قيمة كبيرة بشكل استثنائي: %li بايتات " "(%s%lu مللي ثانية).\n" "من المرجح أن يكون هذا خطأ في برنامج تشغيل ALSA '%s'. يُرجى الإبلاغ عن هذه المش" "كلة " "لمطوري ALSA." #: spa/plugins/alsa/acp/alsa-util.c:1333 #, c-format msgid "" "snd_pcm_avail_delay() returned strange values: delay %lu is less than avail " "%lu.\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr "" "أرجعت الدالة snd_pcm_avail_delay() قيمًا غريبة: التأخير %lu أقل من avail " "(%lu مللي ثانية).\n" "من المرجح أن يكون هذا خطأ في برنامج تشغيل ALSA '%s'. يُرجى الإبلاغ عن هذه المش" "كلة " "لمطوري ALSA." #: spa/plugins/alsa/acp/alsa-util.c:1376 #, c-format msgid "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu byte " "(%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu bytes " "(%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "أرجعت الدالة snd_pcm_mmap_begin() قيمة كبيرة بشكل استثنائي: %lu بايت " "(%lu مللي ثانية).\n" "من المرجح أن يكون هذا خطأ في برنامج تشغيل ALSA '%s'. يُرجى الإبلاغ عن هذه المش" "كلة " "لمطوري ALSA." msgstr[1] "" "أرجعت الدالة snd_pcm_mmap_begin() قيمة كبيرة بشكل استثنائي: %lu بايتات " "(%lu مللي ثانية).\n" "من المرجح أن يكون هذا خطأ في برنامج تشغيل ALSA '%s'. يُرجى الإبلاغ عن هذه المش" "كلة " "لمطوري ALSA." msgstr[2] "" "أرجعت الدالة snd_pcm_mmap_begin() قيمة كبيرة بشكل استثنائي: %lu بايتات " "(%lu مللي ثانية).\n" "من المرجح أن يكون هذا خطأ في برنامج تشغيل ALSA '%s'. يُرجى الإبلاغ عن هذه المش" "كلة " "لمطوري ALSA." msgstr[3] "" "أرجعت الدالة snd_pcm_mmap_begin() قيمة كبيرة بشكل استثنائي: %lu بايتات " "(%lu مللي ثانية).\n" "من المرجح أن يكون هذا خطأ في برنامج تشغيل ALSA '%s'. يُرجى الإبلاغ عن هذه المش" "كلة " "لمطوري ALSA." msgstr[4] "" "أرجعت الدالة snd_pcm_mmap_begin() قيمة كبيرة بشكل استثنائي: %lu بايتات " "(%lu مللي ثانية).\n" "من المرجح أن يكون هذا خطأ في برنامج تشغيل ALSA '%s'. يُرجى الإبلاغ عن هذه المش" "كلة " "لمطوري ALSA." msgstr[5] "" "أرجعت الدالة snd_pcm_mmap_begin() قيمة كبيرة بشكل استثنائي: %lu بايتات " "(%lu مللي ثانية).\n" "من المرجح أن يكون هذا خطأ في برنامج تشغيل ALSA '%s'. يُرجى الإبلاغ عن هذه المش" "كلة " "لمطوري ALSA." #: spa/plugins/alsa/acp/channelmap.h:457 msgid "(invalid)" msgstr "(غير صالح)" #: spa/plugins/alsa/acp/compat.c:193 msgid "Built-in Audio" msgstr "صوت مدمج" #: spa/plugins/alsa/acp/compat.c:198 msgid "Modem" msgstr "مودم" #: spa/plugins/bluez5/bluez5-device.c:1712 msgid "Audio Gateway (A2DP Source & HSP/HFP AG)" msgstr "بوابة الصوت (مصدر A2DP و HSP/HFP AG)" #: spa/plugins/bluez5/bluez5-device.c:1760 #, c-format msgid "High Fidelity Playback (A2DP Sink, codec %s)" msgstr "تشغيل عالي الدقة (A2DP Sink, codec %s)" #: spa/plugins/bluez5/bluez5-device.c:1763 #, c-format msgid "High Fidelity Duplex (A2DP Source/Sink, codec %s)" msgstr "مزدوج عالي الدقة (مصدر A2DP/Sink, codec %s)" #: spa/plugins/bluez5/bluez5-device.c:1771 msgid "High Fidelity Playback (A2DP Sink)" msgstr "تشغيل عالي الدقة (A2DP Sink)" #: spa/plugins/bluez5/bluez5-device.c:1773 msgid "High Fidelity Duplex (A2DP Source/Sink)" msgstr "مزدوج عالي الدقة (مصدر A2DP/Sink)" #: spa/plugins/bluez5/bluez5-device.c:1823 #, c-format msgid "High Fidelity Playback (BAP Sink, codec %s)" msgstr "تشغيل عالي الدقة (BAP Sink, codec %s)" #: spa/plugins/bluez5/bluez5-device.c:1828 #, c-format msgid "High Fidelity Input (BAP Source, codec %s)" msgstr "إدخال عالي الدقة (مصدر BAP, codec %s)" #: spa/plugins/bluez5/bluez5-device.c:1832 #, c-format msgid "High Fidelity Duplex (BAP Source/Sink, codec %s)" msgstr "مزدوج عالي الدقة (مصدر BAP/Sink, codec %s)" #: spa/plugins/bluez5/bluez5-device.c:1841 msgid "High Fidelity Playback (BAP Sink)" msgstr "تشغيل عالي الدقة (BAP Sink)" #: spa/plugins/bluez5/bluez5-device.c:1845 msgid "High Fidelity Input (BAP Source)" msgstr "إدخال عالي الدقة (مصدر BAP)" #: spa/plugins/bluez5/bluez5-device.c:1848 msgid "High Fidelity Duplex (BAP Source/Sink)" msgstr "مزدوج عالي الدقة (مصدر BAP/Sink)" #: spa/plugins/bluez5/bluez5-device.c:1897 #, c-format msgid "Headset Head Unit (HSP/HFP, codec %s)" msgstr "وحدة رأس سماعة الرأس (HSP/HFP, codec %s)" #: spa/plugins/bluez5/bluez5-device.c:1978 #: spa/plugins/bluez5/bluez5-device.c:1983 #: spa/plugins/bluez5/bluez5-device.c:1990 #: spa/plugins/bluez5/bluez5-device.c:1996 #: spa/plugins/bluez5/bluez5-device.c:2002 #: spa/plugins/bluez5/bluez5-device.c:2008 #: spa/plugins/bluez5/bluez5-device.c:2014 #: spa/plugins/bluez5/bluez5-device.c:2020 #: spa/plugins/bluez5/bluez5-device.c:2026 msgid "Handsfree" msgstr "دون استخدام اليدين" #: spa/plugins/bluez5/bluez5-device.c:1984 msgid "Handsfree (HFP)" msgstr "دون استخدام اليدين (HFP)" #: spa/plugins/bluez5/bluez5-device.c:2001 msgid "Headphone" msgstr "سماعة الأذن" #: spa/plugins/bluez5/bluez5-device.c:2007 msgid "Portable" msgstr "محمول" #: spa/plugins/bluez5/bluez5-device.c:2013 msgid "Car" msgstr "سيارة" #: spa/plugins/bluez5/bluez5-device.c:2019 msgid "HiFi" msgstr "HiFi" #: spa/plugins/bluez5/bluez5-device.c:2025 msgid "Phone" msgstr "جوّال" #: spa/plugins/bluez5/bluez5-device.c:2032 msgid "Bluetooth" msgstr "بلوتوث" #: spa/plugins/bluez5/bluez5-device.c:2033 msgid "Bluetooth (HFP)" msgstr "بلوتوث (HFP)" pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/po/as.po000066400000000000000000000464151511204443500220710ustar00rootroot00000000000000# translation of pipewire.master-tx.as.po to Assamese # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # Amitakhya Phukan , 2009, 2012. # msgid "" msgstr "" "Project-Id-Version: pipewire.master-tx.as\n" "Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/pipewire/" "issues/new\n" "POT-Creation-Date: 2021-04-18 16:54+0800\n" "PO-Revision-Date: 2012-01-30 09:52+0000\n" "Last-Translator: Amitakhya Phukan \n" "Language-Team: Assamese <>\n" "Language: as\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Generator: Lokalize 0.2\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" #: src/daemon/pipewire.c:43 #, c-format msgid "" "%s [options]\n" " -h, --help Show this help\n" " --version Show version\n" " -c, --config Load config (Default %s)\n" msgstr "" #: src/daemon/pipewire.desktop.in:4 msgid "PipeWire Media System" msgstr "" #: src/daemon/pipewire.desktop.in:5 msgid "Start the PipeWire Media System" msgstr "" #: src/examples/media-session/alsa-monitor.c:526 #: spa/plugins/alsa/acp/compat.c:187 msgid "Built-in Audio" msgstr "আভ্যন্তৰীণ অ'ডিঅ'" #: src/examples/media-session/alsa-monitor.c:530 #: spa/plugins/alsa/acp/compat.c:192 msgid "Modem" msgstr "মোডেম" #: src/examples/media-session/alsa-monitor.c:539 msgid "Unknown device" msgstr "" #: src/tools/pw-cat.c:991 #, c-format msgid "" "%s [options] \n" " -h, --help Show this help\n" " --version Show version\n" " -v, --verbose Enable verbose operations\n" "\n" msgstr "" #: src/tools/pw-cat.c:998 #, c-format msgid "" " -R, --remote Remote daemon name\n" " --media-type Set media type (default %s)\n" " --media-category Set media category (default %s)\n" " --media-role Set media role (default %s)\n" " --target Set node target (default %s)\n" " 0 means don't link\n" " --latency Set node latency (default %s)\n" " Xunit (unit = s, ms, us, ns)\n" " or direct samples (256)\n" " the rate is the one of the source " "file\n" " --list-targets List available targets for --target\n" "\n" msgstr "" #: src/tools/pw-cat.c:1016 #, c-format msgid "" " --rate Sample rate (req. for rec) (default " "%u)\n" " --channels Number of channels (req. for rec) " "(default %u)\n" " --channel-map Channel map\n" " one of: \"stereo\", " "\"surround-51\",... or\n" " comma separated list of channel " "names: eg. \"FL,FR\"\n" " --format Sample format %s (req. for rec) " "(default %s)\n" " --volume Stream volume 0-1.0 (default %.3f)\n" " -q --quality Resampler quality (0 - 15) (default " "%d)\n" "\n" msgstr "" #: src/tools/pw-cat.c:1033 msgid "" " -p, --playback Playback mode\n" " -r, --record Recording mode\n" " -m, --midi Midi mode\n" "\n" msgstr "" #: src/tools/pw-cli.c:2932 #, c-format msgid "" "%s [options] [command]\n" " -h, --help Show this help\n" " --version Show version\n" " -d, --daemon Start as daemon (Default false)\n" " -r, --remote Remote daemon name\n" "\n" msgstr "" #: spa/plugins/alsa/acp/acp.c:290 msgid "Pro Audio" msgstr "" #: spa/plugins/alsa/acp/acp.c:411 spa/plugins/alsa/acp/alsa-mixer.c:4704 #: spa/plugins/bluez5/bluez5-device.c:1000 msgid "Off" msgstr "বন্ধ" #: spa/plugins/alsa/acp/channelmap.h:466 msgid "(invalid)" msgstr "(অবৈধ)" #: spa/plugins/alsa/acp/alsa-mixer.c:2709 msgid "Input" msgstr "নিবেশ" #: spa/plugins/alsa/acp/alsa-mixer.c:2710 msgid "Docking Station Input" msgstr "ডকিং স্টেছনৰ পৰা নিবেশ" #: spa/plugins/alsa/acp/alsa-mixer.c:2711 #, fuzzy msgid "Docking Station Microphone" msgstr "ডকিং স্টেছনৰ মাইক্ৰোফোন" #: spa/plugins/alsa/acp/alsa-mixer.c:2712 #, fuzzy msgid "Docking Station Line In" msgstr "ডকিং স্টেছনৰ পৰা নিবেশ" #: spa/plugins/alsa/acp/alsa-mixer.c:2713 #: spa/plugins/alsa/acp/alsa-mixer.c:2804 msgid "Line In" msgstr "লাইন ইন" #: spa/plugins/alsa/acp/alsa-mixer.c:2714 #: spa/plugins/alsa/acp/alsa-mixer.c:2798 #: spa/plugins/bluez5/bluez5-device.c:1145 msgid "Microphone" msgstr "মাইক্ৰোফোন" #: spa/plugins/alsa/acp/alsa-mixer.c:2715 #: spa/plugins/alsa/acp/alsa-mixer.c:2799 #, fuzzy msgid "Front Microphone" msgstr "ডকিং স্টেছনৰ মাইক্ৰোফোন" #: spa/plugins/alsa/acp/alsa-mixer.c:2716 #: spa/plugins/alsa/acp/alsa-mixer.c:2800 #, fuzzy msgid "Rear Microphone" msgstr "মাইক্ৰোফোন" #: spa/plugins/alsa/acp/alsa-mixer.c:2717 msgid "External Microphone" msgstr "বহিস্থিত মাইক্ৰোফোন" #: spa/plugins/alsa/acp/alsa-mixer.c:2718 #: spa/plugins/alsa/acp/alsa-mixer.c:2802 msgid "Internal Microphone" msgstr "অভ্যন্তৰীণ মাইক্ৰোফোন" #: spa/plugins/alsa/acp/alsa-mixer.c:2719 #: spa/plugins/alsa/acp/alsa-mixer.c:2805 msgid "Radio" msgstr "ৰেডিঅ'" #: spa/plugins/alsa/acp/alsa-mixer.c:2720 #: spa/plugins/alsa/acp/alsa-mixer.c:2806 msgid "Video" msgstr "ভিডিঅ'" #: spa/plugins/alsa/acp/alsa-mixer.c:2721 msgid "Automatic Gain Control" msgstr "স্বয়ংক্ৰিয় গেইন নিয়ন্ত্ৰণ" #: spa/plugins/alsa/acp/alsa-mixer.c:2722 msgid "No Automatic Gain Control" msgstr "স্বয়ংক্ৰিয় গেইন নিয়ন্ত্ৰণ প্ৰয়োগ কৰা ন'হ'ব" #: spa/plugins/alsa/acp/alsa-mixer.c:2723 msgid "Boost" msgstr "বুস্ট" #: spa/plugins/alsa/acp/alsa-mixer.c:2724 msgid "No Boost" msgstr "বুস্ট প্ৰয়োগ কৰা ন'হ'ব" #: spa/plugins/alsa/acp/alsa-mixer.c:2725 msgid "Amplifier" msgstr "বিবৰ্ধক" #: spa/plugins/alsa/acp/alsa-mixer.c:2726 msgid "No Amplifier" msgstr "বিবৰ্ধন প্ৰয়োগ কৰা ন'হ'ব" #: spa/plugins/alsa/acp/alsa-mixer.c:2727 #, fuzzy msgid "Bass Boost" msgstr "বুস্ট" #: spa/plugins/alsa/acp/alsa-mixer.c:2728 #, fuzzy msgid "No Bass Boost" msgstr "বুস্ট প্ৰয়োগ কৰা ন'হ'ব" #: spa/plugins/alsa/acp/alsa-mixer.c:2729 #: spa/plugins/bluez5/bluez5-device.c:1150 msgid "Speaker" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2730 #: spa/plugins/alsa/acp/alsa-mixer.c:2808 #, fuzzy msgid "Headphones" msgstr "এনালগ হেড ফোন" #: spa/plugins/alsa/acp/alsa-mixer.c:2797 msgid "Analog Input" msgstr "এনালগ নিবেশ" #: spa/plugins/alsa/acp/alsa-mixer.c:2801 msgid "Dock Microphone" msgstr "ডকিং স্টেছনৰ মাইক্ৰোফোন" #: spa/plugins/alsa/acp/alsa-mixer.c:2803 #, fuzzy msgid "Headset Microphone" msgstr "মাইক্ৰোফোন" #: spa/plugins/alsa/acp/alsa-mixer.c:2807 msgid "Analog Output" msgstr "এনালগ নিৰ্গম" #: spa/plugins/alsa/acp/alsa-mixer.c:2809 #, fuzzy msgid "Headphones 2" msgstr "এনালগ হেড ফোন" #: spa/plugins/alsa/acp/alsa-mixer.c:2810 #, fuzzy msgid "Headphones Mono Output" msgstr "এনালগ মোনো নিৰ্গম" #: spa/plugins/alsa/acp/alsa-mixer.c:2811 #, fuzzy msgid "Line Out" msgstr "লাইন ইন" #: spa/plugins/alsa/acp/alsa-mixer.c:2812 msgid "Analog Mono Output" msgstr "এনালগ মোনো নিৰ্গম" #: spa/plugins/alsa/acp/alsa-mixer.c:2813 #, fuzzy msgid "Speakers" msgstr "এনালগ স্টিৰিঅ'" #: spa/plugins/alsa/acp/alsa-mixer.c:2814 msgid "HDMI / DisplayPort" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2815 #, fuzzy msgid "Digital Output (S/PDIF)" msgstr "ডিজিটেল স্টিৰিঅ' (HDMI)" #: spa/plugins/alsa/acp/alsa-mixer.c:2816 #, fuzzy msgid "Digital Input (S/PDIF)" msgstr "ডিজিটেল স্টিৰিঅ' (HDMI)" #: spa/plugins/alsa/acp/alsa-mixer.c:2817 msgid "Multichannel Input" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2818 #, fuzzy msgid "Multichannel Output" msgstr "Null ফলাফল" #: spa/plugins/alsa/acp/alsa-mixer.c:2819 #, fuzzy msgid "Game Output" msgstr "Null ফলাফল" #: spa/plugins/alsa/acp/alsa-mixer.c:2820 #: spa/plugins/alsa/acp/alsa-mixer.c:2821 #, fuzzy msgid "Chat Output" msgstr "Null ফলাফল" #: spa/plugins/alsa/acp/alsa-mixer.c:2822 #, fuzzy msgid "Chat Input" msgstr "নিবেশ" #: spa/plugins/alsa/acp/alsa-mixer.c:2823 #, fuzzy msgid "Virtual Surround 7.1" msgstr "এনালগ ছাৰাউন্ড ৭.১" #: spa/plugins/alsa/acp/alsa-mixer.c:4527 msgid "Analog Mono" msgstr "এনালগ মোনো" #: spa/plugins/alsa/acp/alsa-mixer.c:4528 #, fuzzy msgid "Analog Mono (Left)" msgstr "এনালগ মোনো" #: spa/plugins/alsa/acp/alsa-mixer.c:4529 #, fuzzy msgid "Analog Mono (Right)" msgstr "এনালগ মোনো" #. Note: Not translated to "Analog Stereo Input", because the source #. * name gets "Input" appended to it automatically, so adding "Input" #. * here would lead to the source name to become "Analog Stereo Input #. * Input". The same logic applies to analog-stereo-output, #. * multichannel-input and multichannel-output. #: spa/plugins/alsa/acp/alsa-mixer.c:4530 #: spa/plugins/alsa/acp/alsa-mixer.c:4538 #: spa/plugins/alsa/acp/alsa-mixer.c:4539 msgid "Analog Stereo" msgstr "এনালগ স্টিৰিঅ'" #: spa/plugins/alsa/acp/alsa-mixer.c:4531 msgid "Mono" msgstr "মোনো" #: spa/plugins/alsa/acp/alsa-mixer.c:4532 msgid "Stereo" msgstr "স্টিৰিঅ'" #: spa/plugins/alsa/acp/alsa-mixer.c:4540 #: spa/plugins/alsa/acp/alsa-mixer.c:4698 #: spa/plugins/bluez5/bluez5-device.c:1135 msgid "Headset" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4541 #: spa/plugins/alsa/acp/alsa-mixer.c:4699 #, fuzzy msgid "Speakerphone" msgstr "এনালগ স্টিৰিঅ'" #: spa/plugins/alsa/acp/alsa-mixer.c:4542 #: spa/plugins/alsa/acp/alsa-mixer.c:4543 msgid "Multichannel" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4544 msgid "Analog Surround 2.1" msgstr "এনালগ ছাৰাউন্ড ২.১" #: spa/plugins/alsa/acp/alsa-mixer.c:4545 msgid "Analog Surround 3.0" msgstr "এনালগ ছাৰাউন্ড ৩.০" #: spa/plugins/alsa/acp/alsa-mixer.c:4546 msgid "Analog Surround 3.1" msgstr "এনালগ ছাৰাউন্ড ৩.১" #: spa/plugins/alsa/acp/alsa-mixer.c:4547 msgid "Analog Surround 4.0" msgstr "এনালগ ছাৰাউন্ড ৪.০" #: spa/plugins/alsa/acp/alsa-mixer.c:4548 msgid "Analog Surround 4.1" msgstr "এনালগ ছাৰাউন্ড ৪.১" #: spa/plugins/alsa/acp/alsa-mixer.c:4549 msgid "Analog Surround 5.0" msgstr "এনালগ ছাৰাউন্ড ৫.০" #: spa/plugins/alsa/acp/alsa-mixer.c:4550 msgid "Analog Surround 5.1" msgstr "এনালগ ছাৰাউন্ড ৫.১" #: spa/plugins/alsa/acp/alsa-mixer.c:4551 msgid "Analog Surround 6.0" msgstr "এনালগ ছাৰাউন্ড ৬.০" #: spa/plugins/alsa/acp/alsa-mixer.c:4552 msgid "Analog Surround 6.1" msgstr "এনালগ ছাৰাউন্ড ৬.১" #: spa/plugins/alsa/acp/alsa-mixer.c:4553 msgid "Analog Surround 7.0" msgstr "এনালগ ছাৰাউন্ড ৭.০" #: spa/plugins/alsa/acp/alsa-mixer.c:4554 msgid "Analog Surround 7.1" msgstr "এনালগ ছাৰাউন্ড ৭.১" #: spa/plugins/alsa/acp/alsa-mixer.c:4555 msgid "Digital Stereo (IEC958)" msgstr "ডিজিটেল স্টিৰিঅ' (IEC958)" #: spa/plugins/alsa/acp/alsa-mixer.c:4556 msgid "Digital Surround 4.0 (IEC958/AC3)" msgstr "ডিজিটেল ছাৰাউন্ড ৪.০ (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4557 msgid "Digital Surround 5.1 (IEC958/AC3)" msgstr "ডিজিটেল ছাৰাউন্ড ৫.১ (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4558 #, fuzzy msgid "Digital Surround 5.1 (IEC958/DTS)" msgstr "ডিজিটেল ছাৰাউন্ড ৫.১ (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4559 msgid "Digital Stereo (HDMI)" msgstr "ডিজিটেল স্টিৰিঅ' (HDMI)" #: spa/plugins/alsa/acp/alsa-mixer.c:4560 #, fuzzy msgid "Digital Surround 5.1 (HDMI)" msgstr "ডিজিটেল ছাৰাউন্ড ৫.১ (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4561 msgid "Chat" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4562 msgid "Game" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4696 msgid "Analog Mono Duplex" msgstr "এনালগ মোনো ডুপ্লেক্স" #: spa/plugins/alsa/acp/alsa-mixer.c:4697 msgid "Analog Stereo Duplex" msgstr "এনালগ স্টিৰিঅ' ডুপ্লেক্স" #: spa/plugins/alsa/acp/alsa-mixer.c:4700 msgid "Digital Stereo Duplex (IEC958)" msgstr "ডিজিটেল স্টিৰিঅ' ডুপ্লেক্স (IEC958)" #: spa/plugins/alsa/acp/alsa-mixer.c:4701 msgid "Multichannel Duplex" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4702 #, fuzzy msgid "Stereo Duplex" msgstr "এনালগ স্টিৰিঅ' ডুপ্লেক্স" #: spa/plugins/alsa/acp/alsa-mixer.c:4703 msgid "Mono Chat + 7.1 Surround" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4806 #, fuzzy, c-format msgid "%s Output" msgstr "Null ফলাফল" #: spa/plugins/alsa/acp/alsa-mixer.c:4813 #, fuzzy, c-format msgid "%s Input" msgstr "নিবেশ" #: spa/plugins/alsa/acp/alsa-util.c:1175 spa/plugins/alsa/acp/alsa-util.c:1269 #, fuzzy, c-format msgid "" "snd_pcm_avail() returned a value that is exceptionally large: %lu byte (%lu " "ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_avail() returned a value that is exceptionally large: %lu bytes (%lu " "ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "snd_pcm_avail() এ এটা বৰ ডাঙৰ মান ঘূৰালে: %lu bytes (%lu ms) ।\n" "অতি সম্ভৱ এইটো ALSA চালক '%s' ৰ এটা বাগ । অনুগ্ৰহ কৰি এই সমস্যা ALSA বিকাশকক " "জনাওক ।" msgstr[1] "" "snd_pcm_avail() এ এটা বৰ ডাঙৰ মান ঘূৰালে: %lu bytes (%lu ms) ।\n" "অতি সম্ভৱ এইটো ALSA চালক '%s' ৰ এটা বাগ । অনুগ্ৰহ কৰি এই সমস্যা ALSA বিকাশকক " "জনাওক ।" #: spa/plugins/alsa/acp/alsa-util.c:1241 #, fuzzy, c-format msgid "" "snd_pcm_delay() returned a value that is exceptionally large: %li byte (%s" "%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_delay() returned a value that is exceptionally large: %li bytes (%s" "%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "snd_pcm_delay() এ এটা বৰ ডাঙৰ মান ঘূৰালে: %li bytes (%s%lu ms) ।\n" "অতি সম্ভৱ এইটো ALSA চালক '%s' ৰ এটা বাগ । অনুগ্ৰহ কৰি এই সমস্যা ALSA বিকাশকক " "জনাওক ।" msgstr[1] "" "snd_pcm_delay() এ এটা বৰ ডাঙৰ মান ঘূৰালে: %li bytes (%s%lu ms) ।\n" "অতি সম্ভৱ এইটো ALSA চালক '%s' ৰ এটা বাগ । অনুগ্ৰহ কৰি এই সমস্যা ALSA বিকাশকক " "জনাওক ।" #: spa/plugins/alsa/acp/alsa-util.c:1288 #, fuzzy, c-format msgid "" "snd_pcm_avail_delay() returned strange values: delay %lu is less than avail " "%lu.\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr "" "snd_pcm_avail() এ এটা বৰ ডাঙৰ মান ঘূৰালে: %lu bytes (%lu ms) ।\n" "অতি সম্ভৱ এইটো ALSA চালক '%s' ৰ এটা বাগ । অনুগ্ৰহ কৰি এই সমস্যা ALSA বিকাশকক " "জনাওক ।" #: spa/plugins/alsa/acp/alsa-util.c:1331 #, fuzzy, c-format msgid "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu byte " "(%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu bytes " "(%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "snd_pcm_mmap_begin() এ এটা বৰ ডাঙৰ মান ঘূৰালে: %lu bytes (%lu ms) ।\n" "অতি সম্ভৱ এইটো ALSA চালক '%s' ৰ এটা বাগ । অনুগ্ৰহ কৰি এই সমস্যা ALSA বিকাশকক " "জনাওক ।" msgstr[1] "" "snd_pcm_mmap_begin() এ এটা বৰ ডাঙৰ মান ঘূৰালে: %lu bytes (%lu ms) ।\n" "অতি সম্ভৱ এইটো ALSA চালক '%s' ৰ এটা বাগ । অনুগ্ৰহ কৰি এই সমস্যা ALSA বিকাশকক " "জনাওক ।" #: spa/plugins/bluez5/bluez5-device.c:1010 msgid "Audio Gateway (A2DP Source & HSP/HFP AG)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1033 #, c-format msgid "High Fidelity Playback (A2DP Sink, codec %s)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1035 #, c-format msgid "High Fidelity Duplex (A2DP Source/Sink, codec %s)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1041 msgid "High Fidelity Playback (A2DP Sink)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1043 msgid "High Fidelity Duplex (A2DP Source/Sink)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1070 #, c-format msgid "Headset Head Unit (HSP/HFP, codec %s)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1074 msgid "Headset Head Unit (HSP/HFP)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1140 msgid "Handsfree" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1155 #, fuzzy msgid "Headphone" msgstr "এনালগ হেড ফোন" #: spa/plugins/bluez5/bluez5-device.c:1160 msgid "Portable" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1165 msgid "Car" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1170 msgid "HiFi" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1175 msgid "Phone" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1181 msgid "Bluetooth" msgstr "" pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/po/be.po000066400000000000000000000565531511204443500220600ustar00rootroot00000000000000# Belarusian tanslation of PipeWire # Copyright (C) 2016 PipeWire's COPYRIGHT HOLDER # This file is distributed under the same license as the PipeWire package. # # # Viktar Vaŭčkievič , 2016, 2023. # msgid "" msgstr "" "Project-Id-Version: PipeWire\n" "Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/pipewire/" "issues/new\n" "POT-Creation-Date: 2021-04-18 16:54+0800\n" "PO-Revision-Date: 2023-03-11 01:14+0300\n" "Last-Translator: Viktar Vaŭčkievič \n" "Language-Team: Belarusian <>\n" "Language: be\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && " "n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" "X-Generator: Gtranslator 42.0\n" #: src/daemon/pipewire.c:43 #, c-format msgid "" "%s [options]\n" " -h, --help Show this help\n" " --version Show version\n" " -c, --config Load config (Default %s)\n" msgstr "" "%s [параметры]\n" " -h, --help Паказаць гэту даведку\n" " --version Паказаць версію\n" " -c, --config Загрузіць канфігурацыю (Агаданая " "%s)\n" #: src/daemon/pipewire.desktop.in:4 msgid "PipeWire Media System" msgstr "Медыйная сістэма PipeWire" #: src/daemon/pipewire.desktop.in:5 msgid "Start the PipeWire Media System" msgstr "Старт медыйнай сістэмы PipeWire" #: src/examples/media-session/alsa-monitor.c:526 #: spa/plugins/alsa/acp/compat.c:187 msgid "Built-in Audio" msgstr "Убудаванае аўдыя" #: src/examples/media-session/alsa-monitor.c:530 #: spa/plugins/alsa/acp/compat.c:192 msgid "Modem" msgstr "Мадэм" #: src/examples/media-session/alsa-monitor.c:539 msgid "Unknown device" msgstr "Невядомая прылада" #: src/tools/pw-cat.c:991 #, c-format msgid "" "%s [options] \n" " -h, --help Show this help\n" " --version Show version\n" " -v, --verbose Enable verbose operations\n" "\n" msgstr "" "%s [параметры] \n" " -h, --help Паказаць гэту даведку\n" " --version Паказаць версію\n" " -v, --verbose Уключыць падрабязнасці\n" "\n" #: src/tools/pw-cat.c:998 #, c-format msgid "" " -R, --remote Remote daemon name\n" " --media-type Set media type (default %s)\n" " --media-category Set media category (default %s)\n" " --media-role Set media role (default %s)\n" " --target Set node target (default %s)\n" " 0 means don't link\n" " --latency Set node latency (default %s)\n" " Xunit (unit = s, ms, us, ns)\n" " or direct samples (256)\n" " the rate is the one of the source " "file\n" " --list-targets List available targets for --target\n" "\n" msgstr "" " -R, --remote Назва адаленага сэрвіса\n" " --media-type Задаць тып медыя (агаданы %s)\n" " --media-category Задаць катэгорыю медыя (агаданая " "%s)\n" " --media-role Задаць ролю медыя (агаданая %s)\n" " --target Задаць мэтавы вузел (агаданы %s)\n" " 0 азначае не спасылацца\n" " --latency Задаць затрымку вузла (агаданая %s)\n" " Xадзінка (адзінка = s, ms, us, " "ns)\n" " ці наўпрост сэмплы (256)\n" " з частатой аднаго з уваходных " "файлаў\n" " --list-targets Ліставаць наяўныя мэты для --target\n" "\n" #: src/tools/pw-cat.c:1016 #, c-format msgid "" " --rate Sample rate (req. for rec) (default " "%u)\n" " --channels Number of channels (req. for rec) " "(default %u)\n" " --channel-map Channel map\n" " one of: \"stereo\", " "\"surround-51\",... or\n" " comma separated list of channel " "names: eg. \"FL,FR\"\n" " --format Sample format %s (req. for rec) " "(default %s)\n" " --volume Stream volume 0-1.0 (default %.3f)\n" " -q --quality Resampler quality (0 - 15) (default " "%d)\n" "\n" msgstr "" " --rate Частата сэмплаў (для запісу) " "(агаданая %u)\n" " --channels Колькасць каналаў (для запісу) " "(агаданая %u)\n" " --channel-map Мапа каналаў\n" " адзін з: «stereo», " "«surround-51»,... ці\n" " назвы каналаў праз коску: пр. " "«FL,FR»\n" " --format Фармат сэмплаў %s (req. for rec) " "(агаданы %s)\n" " --volume Гучнасць патоку 0-1.0 (агаданая " "%.3f)\n" " -q --quality Якасць перадыскрэтызацыі (0 - 15) " "(агаданая %d)\n" "\n" #: src/tools/pw-cat.c:1033 msgid "" " -p, --playback Playback mode\n" " -r, --record Recording mode\n" " -m, --midi Midi mode\n" "\n" msgstr "" " -p, --playback Рэжым прайгравальніка\n" " -r, --record Рэжым запісу\n" " -m, --midi Midi-рэжым\n" "\n" #: src/tools/pw-cli.c:2932 #, c-format msgid "" "%s [options] [command]\n" " -h, --help Show this help\n" " --version Show version\n" " -d, --daemon Start as daemon (Default false)\n" " -r, --remote Remote daemon name\n" "\n" msgstr "" "%s [параметры] [каманда]\n" " -h, --help Паказаць гэту даведку\n" " --version Паказаць версію\n" " -d, --daemon Запусціць як фонавы сэрвіс (Default " "false)\n" " -r, --remote Назва адаленага сэрвіса\n" "\n" #: spa/plugins/alsa/acp/acp.c:290 msgid "Pro Audio" msgstr "Pro Audio" #: spa/plugins/alsa/acp/acp.c:411 spa/plugins/alsa/acp/alsa-mixer.c:4704 #: spa/plugins/bluez5/bluez5-device.c:1000 msgid "Off" msgstr "Адключаны" #: spa/plugins/alsa/acp/channelmap.h:466 msgid "(invalid)" msgstr "(некарэктнае)" #: spa/plugins/alsa/acp/alsa-mixer.c:2709 msgid "Input" msgstr "Уваход" #: spa/plugins/alsa/acp/alsa-mixer.c:2710 msgid "Docking Station Input" msgstr "Уваход док-станцыі" #: spa/plugins/alsa/acp/alsa-mixer.c:2711 msgid "Docking Station Microphone" msgstr "Мікрафон док-станцыі" #: spa/plugins/alsa/acp/alsa-mixer.c:2712 msgid "Docking Station Line In" msgstr "Лінейны ўваход док-станцыі" #: spa/plugins/alsa/acp/alsa-mixer.c:2713 #: spa/plugins/alsa/acp/alsa-mixer.c:2804 msgid "Line In" msgstr "Лінейны ўваход" #: spa/plugins/alsa/acp/alsa-mixer.c:2714 #: spa/plugins/alsa/acp/alsa-mixer.c:2798 #: spa/plugins/bluez5/bluez5-device.c:1145 msgid "Microphone" msgstr "Мікрафон" #: spa/plugins/alsa/acp/alsa-mixer.c:2715 #: spa/plugins/alsa/acp/alsa-mixer.c:2799 msgid "Front Microphone" msgstr "Пярэдні мікрафон" #: spa/plugins/alsa/acp/alsa-mixer.c:2716 #: spa/plugins/alsa/acp/alsa-mixer.c:2800 msgid "Rear Microphone" msgstr "Задні мікрафон" #: spa/plugins/alsa/acp/alsa-mixer.c:2717 msgid "External Microphone" msgstr "Знешні мікрафон" #: spa/plugins/alsa/acp/alsa-mixer.c:2718 #: spa/plugins/alsa/acp/alsa-mixer.c:2802 msgid "Internal Microphone" msgstr "Убудаваны мікрафон" #: spa/plugins/alsa/acp/alsa-mixer.c:2719 #: spa/plugins/alsa/acp/alsa-mixer.c:2805 msgid "Radio" msgstr "Радыё" #: spa/plugins/alsa/acp/alsa-mixer.c:2720 #: spa/plugins/alsa/acp/alsa-mixer.c:2806 msgid "Video" msgstr "Відэа" #: spa/plugins/alsa/acp/alsa-mixer.c:2721 msgid "Automatic Gain Control" msgstr "Аўтаматычнае рэгуляванне ўзмацнення" #: spa/plugins/alsa/acp/alsa-mixer.c:2722 msgid "No Automatic Gain Control" msgstr "Без аўтаматычнага рэгулявання ўзмацнення" #: spa/plugins/alsa/acp/alsa-mixer.c:2723 msgid "Boost" msgstr "Узмацненне" #: spa/plugins/alsa/acp/alsa-mixer.c:2724 msgid "No Boost" msgstr "Без ўзмацнення" #: spa/plugins/alsa/acp/alsa-mixer.c:2725 msgid "Amplifier" msgstr "Узмацняльнік" #: spa/plugins/alsa/acp/alsa-mixer.c:2726 msgid "No Amplifier" msgstr "Без ўзмацняльніка" #: spa/plugins/alsa/acp/alsa-mixer.c:2727 msgid "Bass Boost" msgstr "Узмацненне басоў" #: spa/plugins/alsa/acp/alsa-mixer.c:2728 msgid "No Bass Boost" msgstr "Без ўзмацнення басоў" #: spa/plugins/alsa/acp/alsa-mixer.c:2729 #: spa/plugins/bluez5/bluez5-device.c:1150 msgid "Speaker" msgstr "Дынамік" #: spa/plugins/alsa/acp/alsa-mixer.c:2730 #: spa/plugins/alsa/acp/alsa-mixer.c:2808 msgid "Headphones" msgstr "Навушнікі" #: spa/plugins/alsa/acp/alsa-mixer.c:2797 msgid "Analog Input" msgstr "Аналагавы ўваход" #: spa/plugins/alsa/acp/alsa-mixer.c:2801 msgid "Dock Microphone" msgstr "Мікрафон док-станцыі" #: spa/plugins/alsa/acp/alsa-mixer.c:2803 msgid "Headset Microphone" msgstr "Мікрафон гарнітуры" #: spa/plugins/alsa/acp/alsa-mixer.c:2807 msgid "Analog Output" msgstr "Аналагавы выхад" #: spa/plugins/alsa/acp/alsa-mixer.c:2809 msgid "Headphones 2" msgstr "Навушнікі 2" #: spa/plugins/alsa/acp/alsa-mixer.c:2810 msgid "Headphones Mono Output" msgstr "Навушнікавы монавыхад" #: spa/plugins/alsa/acp/alsa-mixer.c:2811 msgid "Line Out" msgstr "Лінейны выхад" #: spa/plugins/alsa/acp/alsa-mixer.c:2812 msgid "Analog Mono Output" msgstr "Аналагавы монавыхад" #: spa/plugins/alsa/acp/alsa-mixer.c:2813 msgid "Speakers" msgstr "Дынамікі" #: spa/plugins/alsa/acp/alsa-mixer.c:2814 msgid "HDMI / DisplayPort" msgstr "HDMI / DisplayPort" #: spa/plugins/alsa/acp/alsa-mixer.c:2815 msgid "Digital Output (S/PDIF)" msgstr "Лічбавы выхад (S/PDIF)" #: spa/plugins/alsa/acp/alsa-mixer.c:2816 msgid "Digital Input (S/PDIF)" msgstr "Лічбавы ўваход (S/PDIF)" #: spa/plugins/alsa/acp/alsa-mixer.c:2817 msgid "Multichannel Input" msgstr "Шматканальны ўваход" #: spa/plugins/alsa/acp/alsa-mixer.c:2818 msgid "Multichannel Output" msgstr "Шматканальны выхад" #: spa/plugins/alsa/acp/alsa-mixer.c:2819 msgid "Game Output" msgstr "Гульнявы выхад" #: spa/plugins/alsa/acp/alsa-mixer.c:2820 #: spa/plugins/alsa/acp/alsa-mixer.c:2821 msgid "Chat Output" msgstr "Чатавы выхад" #: spa/plugins/alsa/acp/alsa-mixer.c:2822 msgid "Chat Input" msgstr "Чатавы уваход" #: spa/plugins/alsa/acp/alsa-mixer.c:2823 msgid "Virtual Surround 7.1" msgstr "Віртуальны абʼёмны 7.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4527 msgid "Analog Mono" msgstr "Аналагавы мона" #: spa/plugins/alsa/acp/alsa-mixer.c:4528 msgid "Analog Mono (Left)" msgstr "Аналагавы мона (левы)" #: spa/plugins/alsa/acp/alsa-mixer.c:4529 msgid "Analog Mono (Right)" msgstr "Аналагавы мона (правы)" #. Note: Not translated to "Analog Stereo Input", because the source #. * name gets "Input" appended to it automatically, so adding "Input" #. * here would lead to the source name to become "Analog Stereo Input #. * Input". The same logic applies to analog-stereo-output, #. * multichannel-input and multichannel-output. #: spa/plugins/alsa/acp/alsa-mixer.c:4530 #: spa/plugins/alsa/acp/alsa-mixer.c:4538 #: spa/plugins/alsa/acp/alsa-mixer.c:4539 msgid "Analog Stereo" msgstr "Аналагавы стэрэа" #: spa/plugins/alsa/acp/alsa-mixer.c:4531 msgid "Mono" msgstr "Мона" #: spa/plugins/alsa/acp/alsa-mixer.c:4532 msgid "Stereo" msgstr "Стэрэа" #: spa/plugins/alsa/acp/alsa-mixer.c:4540 #: spa/plugins/alsa/acp/alsa-mixer.c:4698 #: spa/plugins/bluez5/bluez5-device.c:1135 msgid "Headset" msgstr "Гарнітура" #: spa/plugins/alsa/acp/alsa-mixer.c:4541 #: spa/plugins/alsa/acp/alsa-mixer.c:4699 msgid "Speakerphone" msgstr "Дынамік" #: spa/plugins/alsa/acp/alsa-mixer.c:4542 #: spa/plugins/alsa/acp/alsa-mixer.c:4543 msgid "Multichannel" msgstr "Шматканальны" #: spa/plugins/alsa/acp/alsa-mixer.c:4544 msgid "Analog Surround 2.1" msgstr "Аналагавы абʼёмны 2.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4545 msgid "Analog Surround 3.0" msgstr "Аналагавы абʼёмны 3.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4546 msgid "Analog Surround 3.1" msgstr "Аналагавы абʼёмны 3.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4547 msgid "Analog Surround 4.0" msgstr "Аналагавы абʼёмны 4.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4548 msgid "Analog Surround 4.1" msgstr "Аналагавы абʼёмны 4.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4549 msgid "Analog Surround 5.0" msgstr "Аналагавы абʼёмны 5.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4550 msgid "Analog Surround 5.1" msgstr "Аналагавы абʼёмны 5.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4551 msgid "Analog Surround 6.0" msgstr "Аналагавы абʼёмны 6.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4552 msgid "Analog Surround 6.1" msgstr "Аналагавы абʼёмны 6.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4553 msgid "Analog Surround 7.0" msgstr "Аналагавы абʼёмны 7.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4554 msgid "Analog Surround 7.1" msgstr "Аналагавы абʼёмны 7.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4555 msgid "Digital Stereo (IEC958)" msgstr "Лічбавы стэрэа (IEC958)" #: spa/plugins/alsa/acp/alsa-mixer.c:4556 msgid "Digital Surround 4.0 (IEC958/AC3)" msgstr "Лічбавы абʼёмны 4.0 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4557 msgid "Digital Surround 5.1 (IEC958/AC3)" msgstr "Лічбавы абʼёмны 5.1 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4558 msgid "Digital Surround 5.1 (IEC958/DTS)" msgstr "Лічбавы абʼёмны 5.1 (IEC958/DTS)" #: spa/plugins/alsa/acp/alsa-mixer.c:4559 msgid "Digital Stereo (HDMI)" msgstr "Лічбавы стэрэа (HDMI)" #: spa/plugins/alsa/acp/alsa-mixer.c:4560 msgid "Digital Surround 5.1 (HDMI)" msgstr "Лічбавы абʼёмны 5.1 (HDMI)" #: spa/plugins/alsa/acp/alsa-mixer.c:4561 msgid "Chat" msgstr "Чат" #: spa/plugins/alsa/acp/alsa-mixer.c:4562 msgid "Game" msgstr "Гульня" #: spa/plugins/alsa/acp/alsa-mixer.c:4696 msgid "Analog Mono Duplex" msgstr "Аналагавы мона дуплекс" #: spa/plugins/alsa/acp/alsa-mixer.c:4697 msgid "Analog Stereo Duplex" msgstr "Аналагавы стэрэа дуплекс" #: spa/plugins/alsa/acp/alsa-mixer.c:4700 msgid "Digital Stereo Duplex (IEC958)" msgstr "Лічбавы стэрэа дуплекс (IEC958)" #: spa/plugins/alsa/acp/alsa-mixer.c:4701 msgid "Multichannel Duplex" msgstr "Шматканальны дуплекс" #: spa/plugins/alsa/acp/alsa-mixer.c:4702 msgid "Stereo Duplex" msgstr "Стэрэа дуплекс" #: spa/plugins/alsa/acp/alsa-mixer.c:4703 msgid "Mono Chat + 7.1 Surround" msgstr "Чатавы мона + Аб'ёмны 7.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4806 #, c-format msgid "%s Output" msgstr "%s выхад" #: spa/plugins/alsa/acp/alsa-mixer.c:4813 #, c-format msgid "%s Input" msgstr "%s уваход" #: spa/plugins/alsa/acp/alsa-util.c:1175 spa/plugins/alsa/acp/alsa-util.c:1269 #, c-format msgid "" "snd_pcm_avail() returned a value that is exceptionally large: %lu byte (%lu " "ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_avail() returned a value that is exceptionally large: %lu bytes (%lu " "ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "Выклік snd_pcm_avail() вярнуў выключна вялікае значэнне: %lu байт (%lu мс).\n" "Хутчэй за ўсё, гэта памылка ў ALSA-драйверы «%s». Калі ласка, паведаміце аб " "гэтым распрацоўнікам ALSA." msgstr[1] "" "Выклік snd_pcm_avail() вярнуў выключна вялікае значэнне: %lu байта (%lu " "мс).\n" "Хутчэй за ўсё, гэта памылка ў ALSA-драйверы «%s». Калі ласка, паведаміце аб " "гэтым распрацоўнікам ALSA." msgstr[2] "" "Выклік snd_pcm_avail() вярнуў выключна вялікае значэнне: %lu байтаў (%lu " "мс).\n" "Хутчэй за ўсё, гэта памылка ў ALSA-драйверы «%s». Калі ласка, паведаміце аб " "гэтым распрацоўнікам ALSA." #: spa/plugins/alsa/acp/alsa-util.c:1241 #, c-format msgid "" "snd_pcm_delay() returned a value that is exceptionally large: %li byte " "(%s%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_delay() returned a value that is exceptionally large: %li bytes " "(%s%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "Выклік snd_pcm_delay() вярнуў выключна вялікае значэнне: %li байт (%s%lu " "мс).\n" "Хутчэй за ўсё, гэта памылка ў ALSA-драйверы «%s». Калі ласка, паведаміце аб " "гэтым распрацоўнікам ALSA." msgstr[1] "" "Выклік snd_pcm_delay() вярнуў выключна вялікае значэнне: %li байта (%s%lu " "мс).\n" "Хутчэй за ўсё, гэта памылка ў ALSA-драйверы «%s». Калі ласка, паведаміце аб " "гэтым распрацоўнікам ALSA." msgstr[2] "" "Выклік snd_pcm_delay() вярнуў выключна вялікае значэнне: %li байтаў (%s%lu " "мс).\n" "Хутчэй за ўсё, гэта памылка ў ALSA-драйверы «%s». Калі ласка, паведаміце аб " "гэтым распрацоўнікам ALSA." #: spa/plugins/alsa/acp/alsa-util.c:1288 #, c-format msgid "" "snd_pcm_avail_delay() returned strange values: delay %lu is less than avail " "%lu.\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr "" "Выклік snd_pcm_avail_delay() вярнуў дзіўнае значэнне: затрымка %lu меншая за " "даступную %lu.\n" "Хутчэй за ўсё, гэта памылка ў ALSA-драйверы «%s». Калі ласка, паведаміце аб " "гэтым распрацоўнікам ALSA." #: spa/plugins/alsa/acp/alsa-util.c:1331 #, c-format msgid "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu byte " "(%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu bytes " "(%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "Выклік snd_pcm_mmap_begin() вярнуў выключна вялікае значэнне: %lu байт (%lu " "мс).\n" "Хутчэй за ўсё, гэта памылка ў ALSA-драйверы «%s». Калі ласка, паведаміце аб " "гэтым распрацоўнікам ALSA." msgstr[1] "" "Выклік snd_pcm_mmap_begin() вярнуў выключна вялікае значэнне: %lu байта (%lu " "мс).\n" "Хутчэй за ўсё, гэта памылка ў ALSA-драйверы «%s». Калі ласка, паведаміце аб " "гэтым распрацоўнікам ALSA." msgstr[2] "" "Выклік snd_pcm_mmap_begin() вярнуў выключна вялікае значэнне: %lu байтаў " "(%lu мс).\n" "Хутчэй за ўсё, гэта памылка ў ALSA-драйверы «%s». Калі ласка, паведаміце аб " "гэтым распрацоўнікам ALSA." #: spa/plugins/bluez5/bluez5-device.c:1010 msgid "Audio Gateway (A2DP Source & HSP/HFP AG)" msgstr "Аўдыяшлюз (A2DP-крыніца і HSP/HFP AG)" #: spa/plugins/bluez5/bluez5-device.c:1033 #, c-format msgid "High Fidelity Playback (A2DP Sink, codec %s)" msgstr "Высокадакладнае прайграванне (A2DP-прыёмнік, кодэк %s)" #: spa/plugins/bluez5/bluez5-device.c:1035 #, c-format msgid "High Fidelity Duplex (A2DP Source/Sink, codec %s)" msgstr "Высокадакладны дуплекс (A2DP-крыніца/прыёмнік, кодэк %s)" #: spa/plugins/bluez5/bluez5-device.c:1041 msgid "High Fidelity Playback (A2DP Sink)" msgstr "Высокадакладнае прайграванне (A2DP-прыёмнік)" #: spa/plugins/bluez5/bluez5-device.c:1043 msgid "High Fidelity Duplex (A2DP Source/Sink)" msgstr "Высокадакладны дуплекс (A2DP-крыніца/прыёмнік)" #: spa/plugins/bluez5/bluez5-device.c:1070 #, c-format msgid "Headset Head Unit (HSP/HFP, codec %s)" msgstr "Навушнікі гарнітуры (HSP/HFP, кодэк %s)" #: spa/plugins/bluez5/bluez5-device.c:1074 msgid "Headset Head Unit (HSP/HFP)" msgstr "Навушнікі гарнітуры (HSP/HFP)" #: spa/plugins/bluez5/bluez5-device.c:1140 msgid "Handsfree" msgstr "Хэндс-фры" #: spa/plugins/bluez5/bluez5-device.c:1155 msgid "Headphone" msgstr "Навушнік" #: spa/plugins/bluez5/bluez5-device.c:1160 msgid "Portable" msgstr "Партатыўная сістэма" #: spa/plugins/bluez5/bluez5-device.c:1165 msgid "Car" msgstr "Аўтамабільная сістэма" #: spa/plugins/bluez5/bluez5-device.c:1170 msgid "HiFi" msgstr "Hi-Fi" #: spa/plugins/bluez5/bluez5-device.c:1175 msgid "Phone" msgstr "Тэлефон" #: spa/plugins/bluez5/bluez5-device.c:1181 msgid "Bluetooth" msgstr "Bluetooth" pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/po/bg.po000066400000000000000000000576211511204443500220570ustar00rootroot00000000000000# Bulgarian translation of pipewire po-file. # Copyright (C) 2023 Alexander Shopov # Alexander Shopov , 2023. # This file is licensed under the same terms as pipewire. # msgid "" msgstr "" "Project-Id-Version: pipewire 1.0.0.\n" "Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/pipewire/" "issues/new\n" "POT-Creation-Date: 2022-06-30 12:50+0200\n" "PO-Revision-Date: 2023-12-09 15:17+0100\n" "Last-Translator: Alexander Shopov \n" "Language-Team: Bulgarian \n" "Language: bg\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #: src/daemon/pipewire.c:46 #, c-format msgid "" "%s [options]\n" " -h, --help Show this help\n" " --version Show version\n" " -c, --config Load config (Default %s)\n" msgstr "" "%s [ОПЦИЯ…]\n" "\n" " -h --help Извеждане на тази помощ\n" " -V --version Извеждане на версията\n" " -c, --config Зареждане на настройки (стандартно „%s“)\n" #: src/modules/module-protocol-pulse/modules/module-tunnel-sink.c:180 #: src/modules/module-protocol-pulse/modules/module-tunnel-source.c:180 #, c-format msgid "Tunnel to %s/%s" msgstr "Тунелиране към „%s/%s“" #: src/modules/module-fallback-sink.c:51 msgid "Dummy Output" msgstr "Фиктивен изход" #: src/modules/module-pulse-tunnel.c:662 #, c-format msgid "Tunnel for %s@%s" msgstr "Тунелиране за %s@%s" #: src/modules/module-zeroconf-discover.c:332 msgid "Unknown device" msgstr "Непознато устройство" #: src/modules/module-zeroconf-discover.c:344 #, c-format msgid "%s on %s@%s" msgstr "„%s“ на „%s@%s“" #: src/modules/module-zeroconf-discover.c:348 #, c-format msgid "%s on %s" msgstr "„%s“ на „%s“" #: src/tools/pw-cat.c:784 #, c-format msgid "" "%s [options] [|-]\n" " -h, --help Show this help\n" " --version Show version\n" " -v, --verbose Enable verbose operations\n" "\n" msgstr "" "%s [ОПЦИЯ…] [ФАЙЛ|-]\n" "\n" " -h, --help Извеждане на тази помощ\n" " --version Извеждане на версията\n" " -v, --verbose Включване на подробен режим\n" #: src/tools/pw-cat.c:791 #, c-format msgid "" " -R, --remote Remote daemon name\n" " --media-type Set media type (default %s)\n" " --media-category Set media category (default %s)\n" " --media-role Set media role (default %s)\n" " --target Set node target (default %s)\n" " 0 means don't link\n" " --latency Set node latency (default %s)\n" " Xunit (unit = s, ms, us, ns)\n" " or direct samples (256)\n" " the rate is the one of the source " "file\n" " -P --properties Set node properties\n" "\n" msgstr "" " -R, --remote Име на отдалечения демон\n" " --media-type Вид на медията (стандартно: „%s“)\n" " --media-category Категория на медията (стандартно: „%s“)\n" " --media-role Роля на медията (стандартно: „%s“)\n" " --target Цел на възела (стандартно: „%s“)\n" " „0“ означава без свързване\n" " --latency Латентност на възела (стандартно: „%s“)\n" " Xединица (единица е една от „s“/„ms“/„us“/„ns“)\n" " или пряко отчитане (256)\n" " честотата е тази на изходния файл\n" #: src/tools/pw-cat.c:809 #, c-format msgid "" " --rate Sample rate (req. for rec) (default " "%u)\n" " --channels Number of channels (req. for rec) " "(default %u)\n" " --channel-map Channel map\n" " one of: \"stereo\", " "\"surround-51\",... or\n" " comma separated list of channel " "names: eg. \"FL,FR\"\n" " --format Sample format %s (req. for rec) " "(default %s)\n" " --volume Stream volume 0-1.0 (default %.3f)\n" " -q --quality Resampler quality (0 - 15) (default " "%d)\n" "\n" msgstr "" " --rate Честота на отчитане (задължително при запис)\n" " (стандартно: „%u“)\n" " --channels Брой канали (задължително при запис)\n" " (стандартно: „%u“)\n" " --channel-map Карта на каналите:\n" " едно от: „stereo“, „surround-51“, … или\n" " списък с разделител „,“, напр. „FL,FR“\n" " --format Формат на отчѐта %s (задължително при запис)\n" " (стандартно: „%s“)\n" " --volume Сила на звука на потока 0-1.0 (стандартно: %.3f)\n" " -q --quality Качество при ново отчитане (0 - 15) (стандартно: %d)\n" #: src/tools/pw-cat.c:826 msgid "" " -p, --playback Playback mode\n" " -r, --record Recording mode\n" " -m, --midi Midi mode\n" " -d, --dsd DSD mode\n" "\n" msgstr "" " -p, --playback Режим за изпълнение\n" " -r, --record Режим за запис\n" " -m, --midi Режим за Midi\n" " -d, --dsd Режим за DSD\n" "\n" #: src/tools/pw-cli.c:2250 #, c-format msgid "" "%s [options] [command]\n" " -h, --help Show this help\n" " --version Show version\n" " -d, --daemon Start as daemon (Default false)\n" " -r, --remote Remote daemon name\n" "\n" msgstr "" "%s [ОПЦИЯ…] [КОМАНДА]\n" " -h, --help Извеждане на тази помощ\n" " --version Извеждане на версията\n" " -d, --daemon Стартиране като демон (стандартно: не)\n" " -r, --remote Име на отдалечения демон\n" #: spa/plugins/alsa/acp/acp.c:321 msgid "Pro Audio" msgstr "Професионално аудио" #: spa/plugins/alsa/acp/acp.c:444 spa/plugins/alsa/acp/alsa-mixer.c:4648 #: spa/plugins/bluez5/bluez5-device.c:1236 msgid "Off" msgstr "Изключено" #: spa/plugins/alsa/acp/alsa-mixer.c:2652 msgid "Input" msgstr "Вход" #: spa/plugins/alsa/acp/alsa-mixer.c:2653 msgid "Docking Station Input" msgstr "Вход на станцията за скачане" #: spa/plugins/alsa/acp/alsa-mixer.c:2654 msgid "Docking Station Microphone" msgstr "Микрофон на станцията за скачане" #: spa/plugins/alsa/acp/alsa-mixer.c:2655 msgid "Docking Station Line In" msgstr "Вход на станцията за скачане" #: spa/plugins/alsa/acp/alsa-mixer.c:2656 #: spa/plugins/alsa/acp/alsa-mixer.c:2747 msgid "Line In" msgstr "Вход" #: spa/plugins/alsa/acp/alsa-mixer.c:2657 #: spa/plugins/alsa/acp/alsa-mixer.c:2741 #: spa/plugins/bluez5/bluez5-device.c:1454 msgid "Microphone" msgstr "Микрофон" #: spa/plugins/alsa/acp/alsa-mixer.c:2658 #: spa/plugins/alsa/acp/alsa-mixer.c:2742 msgid "Front Microphone" msgstr "Преден микрофон" #: spa/plugins/alsa/acp/alsa-mixer.c:2659 #: spa/plugins/alsa/acp/alsa-mixer.c:2743 msgid "Rear Microphone" msgstr "Заден микрофон" #: spa/plugins/alsa/acp/alsa-mixer.c:2660 msgid "External Microphone" msgstr "Външен микрофон" #: spa/plugins/alsa/acp/alsa-mixer.c:2661 #: spa/plugins/alsa/acp/alsa-mixer.c:2745 msgid "Internal Microphone" msgstr "Вътрешен микрофон" #: spa/plugins/alsa/acp/alsa-mixer.c:2662 #: spa/plugins/alsa/acp/alsa-mixer.c:2748 msgid "Radio" msgstr "Радио" #: spa/plugins/alsa/acp/alsa-mixer.c:2663 #: spa/plugins/alsa/acp/alsa-mixer.c:2749 msgid "Video" msgstr "Видео" #: spa/plugins/alsa/acp/alsa-mixer.c:2664 msgid "Automatic Gain Control" msgstr "Автоматично усилване" #: spa/plugins/alsa/acp/alsa-mixer.c:2665 msgid "No Automatic Gain Control" msgstr "Без автоматично усилване" #: spa/plugins/alsa/acp/alsa-mixer.c:2666 msgid "Boost" msgstr "Подсилване" #: spa/plugins/alsa/acp/alsa-mixer.c:2667 msgid "No Boost" msgstr "Без подсилване" #: spa/plugins/alsa/acp/alsa-mixer.c:2668 msgid "Amplifier" msgstr "Усилвател" #: spa/plugins/alsa/acp/alsa-mixer.c:2669 msgid "No Amplifier" msgstr "Без усилвател" #: spa/plugins/alsa/acp/alsa-mixer.c:2670 msgid "Bass Boost" msgstr "Усилване на басите" #: spa/plugins/alsa/acp/alsa-mixer.c:2671 msgid "No Bass Boost" msgstr "Без усилване на басите" #: spa/plugins/alsa/acp/alsa-mixer.c:2672 #: spa/plugins/bluez5/bluez5-device.c:1460 msgid "Speaker" msgstr "Високоговорител" #: spa/plugins/alsa/acp/alsa-mixer.c:2673 #: spa/plugins/alsa/acp/alsa-mixer.c:2751 msgid "Headphones" msgstr "Слушалки" #: spa/plugins/alsa/acp/alsa-mixer.c:2740 msgid "Analog Input" msgstr "Аналогов вход" #: spa/plugins/alsa/acp/alsa-mixer.c:2744 msgid "Dock Microphone" msgstr "Микрофон за скачане" #: spa/plugins/alsa/acp/alsa-mixer.c:2746 msgid "Headset Microphone" msgstr "Микрофон на слушалките" #: spa/plugins/alsa/acp/alsa-mixer.c:2750 msgid "Analog Output" msgstr "Аналогов изход" #: spa/plugins/alsa/acp/alsa-mixer.c:2752 msgid "Headphones 2" msgstr "Слушалки 2" #: spa/plugins/alsa/acp/alsa-mixer.c:2753 msgid "Headphones Mono Output" msgstr "Моно изход на слушалките" #: spa/plugins/alsa/acp/alsa-mixer.c:2754 msgid "Line Out" msgstr "Изход" #: spa/plugins/alsa/acp/alsa-mixer.c:2755 msgid "Analog Mono Output" msgstr "Аналогов моно изход" #: spa/plugins/alsa/acp/alsa-mixer.c:2756 msgid "Speakers" msgstr "Тонколони" #: spa/plugins/alsa/acp/alsa-mixer.c:2757 msgid "HDMI / DisplayPort" msgstr "HDMI/DisplayPort" #: spa/plugins/alsa/acp/alsa-mixer.c:2758 msgid "Digital Output (S/PDIF)" msgstr "Цифров изход (S/PDIF)" #: spa/plugins/alsa/acp/alsa-mixer.c:2759 msgid "Digital Input (S/PDIF)" msgstr "Цифров вход (S/PDIF)" #: spa/plugins/alsa/acp/alsa-mixer.c:2760 msgid "Multichannel Input" msgstr "Многоканален вход" #: spa/plugins/alsa/acp/alsa-mixer.c:2761 msgid "Multichannel Output" msgstr "Многоканален изход" #: spa/plugins/alsa/acp/alsa-mixer.c:2762 msgid "Game Output" msgstr "Изход за игри" #: spa/plugins/alsa/acp/alsa-mixer.c:2763 #: spa/plugins/alsa/acp/alsa-mixer.c:2764 msgid "Chat Output" msgstr "Изход за разговори" #: spa/plugins/alsa/acp/alsa-mixer.c:2765 msgid "Chat Input" msgstr "Вход за разговори" #: spa/plugins/alsa/acp/alsa-mixer.c:2766 msgid "Virtual Surround 7.1" msgstr "Виртуален съраунд 7.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4471 msgid "Analog Mono" msgstr "Аналогово моно" #: spa/plugins/alsa/acp/alsa-mixer.c:4472 msgid "Analog Mono (Left)" msgstr "Аналогово моно (отляво)" #: spa/plugins/alsa/acp/alsa-mixer.c:4473 msgid "Analog Mono (Right)" msgstr "Аналогово моно (отдясно)" #. Note: Not translated to "Analog Stereo Input", because the source #. * name gets "Input" appended to it automatically, so adding "Input" #. * here would lead to the source name to become "Analog Stereo Input #. * Input". The same logic applies to analog-stereo-output, #. * multichannel-input and multichannel-output. #: spa/plugins/alsa/acp/alsa-mixer.c:4474 #: spa/plugins/alsa/acp/alsa-mixer.c:4482 #: spa/plugins/alsa/acp/alsa-mixer.c:4483 msgid "Analog Stereo" msgstr "Аналогово стерео" #: spa/plugins/alsa/acp/alsa-mixer.c:4475 msgid "Mono" msgstr "Моно" #: spa/plugins/alsa/acp/alsa-mixer.c:4476 msgid "Stereo" msgstr "Стерео" #: spa/plugins/alsa/acp/alsa-mixer.c:4484 #: spa/plugins/alsa/acp/alsa-mixer.c:4642 #: spa/plugins/bluez5/bluez5-device.c:1442 msgid "Headset" msgstr "Слушалки с микрофон" #: spa/plugins/alsa/acp/alsa-mixer.c:4485 #: spa/plugins/alsa/acp/alsa-mixer.c:4643 msgid "Speakerphone" msgstr "Високоговорител на телефон" #: spa/plugins/alsa/acp/alsa-mixer.c:4486 #: spa/plugins/alsa/acp/alsa-mixer.c:4487 msgid "Multichannel" msgstr "Многоканален" #: spa/plugins/alsa/acp/alsa-mixer.c:4488 msgid "Analog Surround 2.1" msgstr "Аналогов съраунд 2.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4489 msgid "Analog Surround 3.0" msgstr "Аналогов съраунд 3.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4490 msgid "Analog Surround 3.1" msgstr "Аналогов съраунд 3.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4491 msgid "Analog Surround 4.0" msgstr "Аналогов съраунд 4.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4492 msgid "Analog Surround 4.1" msgstr "Аналогов съраунд 4.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4493 msgid "Analog Surround 5.0" msgstr "Аналогов съраунд 5.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4494 msgid "Analog Surround 5.1" msgstr "Аналогов съраунд 5.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4495 msgid "Analog Surround 6.0" msgstr "Аналогов съраунд 6.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4496 msgid "Analog Surround 6.1" msgstr "Аналогов съраунд 6.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4497 msgid "Analog Surround 7.0" msgstr "Аналогов съраунд 7.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4498 msgid "Analog Surround 7.1" msgstr "Аналогов съраунд 7.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4499 msgid "Digital Stereo (IEC958)" msgstr "Цифрово стерео (IEC958)" #: spa/plugins/alsa/acp/alsa-mixer.c:4500 msgid "Digital Surround 4.0 (IEC958/AC3)" msgstr "Цифров съраунд 4.0 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4501 msgid "Digital Surround 5.1 (IEC958/AC3)" msgstr "Цифров съраунд 5.1 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4502 msgid "Digital Surround 5.1 (IEC958/DTS)" msgstr "Цифров съраунд 5.1 (IEC958/DTS)" #: spa/plugins/alsa/acp/alsa-mixer.c:4503 msgid "Digital Stereo (HDMI)" msgstr "Цифрово стерео (HDMI)" #: spa/plugins/alsa/acp/alsa-mixer.c:4504 msgid "Digital Surround 5.1 (HDMI)" msgstr "Цифров съраунд 5.1 (HDMI)" #: spa/plugins/alsa/acp/alsa-mixer.c:4505 msgid "Chat" msgstr "Разговор" #: spa/plugins/alsa/acp/alsa-mixer.c:4506 msgid "Game" msgstr "Игра" #: spa/plugins/alsa/acp/alsa-mixer.c:4640 msgid "Analog Mono Duplex" msgstr "Двупосочно аналогово моно" #: spa/plugins/alsa/acp/alsa-mixer.c:4641 msgid "Analog Stereo Duplex" msgstr "Двупосочно аналогово стерео" #: spa/plugins/alsa/acp/alsa-mixer.c:4644 msgid "Digital Stereo Duplex (IEC958)" msgstr "Двупосочно цифрово стерео (IEC958)" #: spa/plugins/alsa/acp/alsa-mixer.c:4645 msgid "Multichannel Duplex" msgstr "Двупосочна многоканална връзка" #: spa/plugins/alsa/acp/alsa-mixer.c:4646 msgid "Stereo Duplex" msgstr "Двупосочно стерео" #: spa/plugins/alsa/acp/alsa-mixer.c:4647 msgid "Mono Chat + 7.1 Surround" msgstr "Моно разговор + съраунд 7.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4754 #, c-format msgid "%s Output" msgstr "Изход %s" #: spa/plugins/alsa/acp/alsa-mixer.c:4761 #, c-format msgid "%s Input" msgstr "Вход %s" #: spa/plugins/alsa/acp/alsa-util.c:1187 spa/plugins/alsa/acp/alsa-util.c:1281 #, c-format msgid "" "snd_pcm_avail() returned a value that is exceptionally large: %lu byte (%lu " "ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_avail() returned a value that is exceptionally large: %lu bytes (%lu " "ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "„snd_pcm_avail()“ върна неочаквано голяма стойност: %lu байт (%lu ms).\n" "Най-вероятно се дължи на грешка в драйвера на ALSA „%s“. Молим да докладвате " "това на разработчиците на ALSA." msgstr[1] "" "„snd_pcm_avail()“ върна неочаквано голяма стойност: %lu байта (%lu ms).\n" "Най-вероятно се дължи на грешка в драйвера на ALSA „%s“. Молим да докладвате " "това на разработчиците на ALSA." #: spa/plugins/alsa/acp/alsa-util.c:1253 #, c-format msgid "" "snd_pcm_delay() returned a value that is exceptionally large: %li byte " "(%s%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_delay() returned a value that is exceptionally large: %li bytes " "(%s%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "„snd_pcm_delay()“ върна неочаквано голяма стойност: %li байт (%s%lu ms).\n" "Най-вероятно се дължи на грешка в драйвера на ALSA „%s“. Молим да докладвате " "това на разработчиците на ALSA." msgstr[1] "" "„snd_pcm_delay()“ върна неочаквано голяма стойност: %li байта (%s%lu ms).\n" "Най-вероятно се дължи на грешка в драйвера на ALSA „%s“. Молим да докладвате " "това на разработчиците на ALSA.<" #: spa/plugins/alsa/acp/alsa-util.c:1300 #, c-format msgid "" "snd_pcm_avail_delay() returned strange values: delay %lu is less than avail " "%lu.\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr "" "„snd_pcm_avail_delay()“ върна неочаквана стойност: забавянето %lu е по-малко " "от наличното %lu.\n" "Най-вероятно се дължи на грешка в драйвера на ALSA „%s“. Молим да докладвате " "това на разработчиците на ALSA." #: spa/plugins/alsa/acp/alsa-util.c:1343 #, c-format msgid "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu byte " "(%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu bytes " "(%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "„snd_pcm_mmap_begin()“ върна неочаквано голяма стойност: %lu байт (%lu ms).\n" "Най-вероятно се дължи на грешка в драйвера на ALSA „%s“. Молим да докладвате " "това на разработчиците на ALSA." msgstr[1] "" "„snd_pcm_mmap_begin()“ върна неочаквано голяма стойност: %lu байта (%lu " "ms).\n" "Най-вероятно се дължи на грешка в драйвера на ALSA „%s“. Молим да докладвате " "това на разработчиците на ALSA." #: spa/plugins/alsa/acp/channelmap.h:457 msgid "(invalid)" msgstr "(неправилно)" #: spa/plugins/alsa/acp/compat.c:189 msgid "Built-in Audio" msgstr "Вградено аудио" #: spa/plugins/alsa/acp/compat.c:194 msgid "Modem" msgstr "Модем" #: spa/plugins/bluez5/bluez5-device.c:1247 msgid "Audio Gateway (A2DP Source & HSP/HFP AG)" msgstr "Аудио шлюз (източник A2DP и HSP/HFP AG)" #: spa/plugins/bluez5/bluez5-device.c:1272 #, c-format msgid "High Fidelity Playback (A2DP Sink, codec %s)" msgstr "Изпълнение с висока точност (елемент-приемник A2DP, кодер „%s“)" #: spa/plugins/bluez5/bluez5-device.c:1275 #, c-format msgid "High Fidelity Duplex (A2DP Source/Sink, codec %s)" msgstr "" "Двупосочна връзка с висока точност (елемент-източник/приемник A2DP, кодер " "„%s“)" #: spa/plugins/bluez5/bluez5-device.c:1283 msgid "High Fidelity Playback (A2DP Sink)" msgstr "Изпълнение с висока точност (елемент-приемник A2DP)" #: spa/plugins/bluez5/bluez5-device.c:1285 msgid "High Fidelity Duplex (A2DP Source/Sink)" msgstr "Двупосочна връзка с висока точност (елемент-източник/приемник A2DP)" #: spa/plugins/bluez5/bluez5-device.c:1322 #, c-format msgid "High Fidelity Playback (BAP Sink, codec %s)" msgstr "Изпълнение с висока точност (елемент-приемник BAP, кодер „%s“)" #: spa/plugins/bluez5/bluez5-device.c:1326 #, c-format msgid "High Fidelity Input (BAP Source, codec %s)" msgstr "Вход с висока точност (елемент-източник BAP, кодер „%s“)" #: spa/plugins/bluez5/bluez5-device.c:1330 #, c-format msgid "High Fidelity Duplex (BAP Source/Sink, codec %s)" msgstr "" "Двупосочна връзка с висока точност (елемент-източник/приемник BAP, кодер " "„%s“)" #: spa/plugins/bluez5/bluez5-device.c:1359 #, c-format msgid "Headset Head Unit (HSP/HFP, codec %s)" msgstr "Слушалки с микрофон (HSP/HFP, кодер „%s“)<" #: spa/plugins/bluez5/bluez5-device.c:1364 msgid "Headset Head Unit (HSP/HFP)" msgstr "Слушалки с микрофон (HSP/HFP)" #: spa/plugins/bluez5/bluez5-device.c:1443 #: spa/plugins/bluez5/bluez5-device.c:1448 #: spa/plugins/bluez5/bluez5-device.c:1455 #: spa/plugins/bluez5/bluez5-device.c:1461 #: spa/plugins/bluez5/bluez5-device.c:1467 #: spa/plugins/bluez5/bluez5-device.c:1473 #: spa/plugins/bluez5/bluez5-device.c:1479 #: spa/plugins/bluez5/bluez5-device.c:1485 #: spa/plugins/bluez5/bluez5-device.c:1491 msgid "Handsfree" msgstr "Слушалка за свободни ръце" #: spa/plugins/bluez5/bluez5-device.c:1449 msgid "Handsfree (HFP)" msgstr "Слушалка за свободни ръце (HFP)" #: spa/plugins/bluez5/bluez5-device.c:1466 msgid "Headphone" msgstr "Слушалки" #: spa/plugins/bluez5/bluez5-device.c:1472 msgid "Portable" msgstr "Преносимо" #: spa/plugins/bluez5/bluez5-device.c:1478 msgid "Car" msgstr "Кола̀" #: spa/plugins/bluez5/bluez5-device.c:1484 msgid "HiFi" msgstr "HiFi" #: spa/plugins/bluez5/bluez5-device.c:1490 msgid "Phone" msgstr "Телефон" #: spa/plugins/bluez5/bluez5-device.c:1497 msgid "Bluetooth" msgstr "Bluetooth" #: spa/plugins/bluez5/bluez5-device.c:1498 msgid "Bluetooth (HFP)" msgstr "Bluetooth (HFP)" pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/po/bn_IN.po000066400000000000000000000500461511204443500224460ustar00rootroot00000000000000# translation of pipewire.master-tx.bn_IN.po to Bengali INDIA # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # Runa Bhattacharjee , 2009, 2012. # msgid "" msgstr "" "Project-Id-Version: pipewire.master-tx.bn_IN\n" "Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/pipewire/" "issues/new\n" "POT-Creation-Date: 2021-04-18 16:54+0800\n" "PO-Revision-Date: 2012-01-30 09:52+0000\n" "Last-Translator: Runa Bhattacharjee \n" "Language-Team: Bengali INDIA \n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Generator: KBabel 1.11.4\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #: src/daemon/pipewire.c:43 #, c-format msgid "" "%s [options]\n" " -h, --help Show this help\n" " --version Show version\n" " -c, --config Load config (Default %s)\n" msgstr "" #: src/daemon/pipewire.desktop.in:4 msgid "PipeWire Media System" msgstr "" #: src/daemon/pipewire.desktop.in:5 msgid "Start the PipeWire Media System" msgstr "" #: src/examples/media-session/alsa-monitor.c:526 #: spa/plugins/alsa/acp/compat.c:187 msgid "Built-in Audio" msgstr "অভ্যন্তরীণ অডিও" #: src/examples/media-session/alsa-monitor.c:530 #: spa/plugins/alsa/acp/compat.c:192 msgid "Modem" msgstr "মোডেম" #: src/examples/media-session/alsa-monitor.c:539 msgid "Unknown device" msgstr "" #: src/tools/pw-cat.c:991 #, c-format msgid "" "%s [options] \n" " -h, --help Show this help\n" " --version Show version\n" " -v, --verbose Enable verbose operations\n" "\n" msgstr "" #: src/tools/pw-cat.c:998 #, c-format msgid "" " -R, --remote Remote daemon name\n" " --media-type Set media type (default %s)\n" " --media-category Set media category (default %s)\n" " --media-role Set media role (default %s)\n" " --target Set node target (default %s)\n" " 0 means don't link\n" " --latency Set node latency (default %s)\n" " Xunit (unit = s, ms, us, ns)\n" " or direct samples (256)\n" " the rate is the one of the source " "file\n" " --list-targets List available targets for --target\n" "\n" msgstr "" #: src/tools/pw-cat.c:1016 #, c-format msgid "" " --rate Sample rate (req. for rec) (default " "%u)\n" " --channels Number of channels (req. for rec) " "(default %u)\n" " --channel-map Channel map\n" " one of: \"stereo\", " "\"surround-51\",... or\n" " comma separated list of channel " "names: eg. \"FL,FR\"\n" " --format Sample format %s (req. for rec) " "(default %s)\n" " --volume Stream volume 0-1.0 (default %.3f)\n" " -q --quality Resampler quality (0 - 15) (default " "%d)\n" "\n" msgstr "" #: src/tools/pw-cat.c:1033 msgid "" " -p, --playback Playback mode\n" " -r, --record Recording mode\n" " -m, --midi Midi mode\n" "\n" msgstr "" #: src/tools/pw-cli.c:2932 #, c-format msgid "" "%s [options] [command]\n" " -h, --help Show this help\n" " --version Show version\n" " -d, --daemon Start as daemon (Default false)\n" " -r, --remote Remote daemon name\n" "\n" msgstr "" #: spa/plugins/alsa/acp/acp.c:290 msgid "Pro Audio" msgstr "" #: spa/plugins/alsa/acp/acp.c:411 spa/plugins/alsa/acp/alsa-mixer.c:4704 #: spa/plugins/bluez5/bluez5-device.c:1000 msgid "Off" msgstr "বন্ধ" #: spa/plugins/alsa/acp/channelmap.h:466 msgid "(invalid)" msgstr "(অবৈধ)" #: spa/plugins/alsa/acp/alsa-mixer.c:2709 msgid "Input" msgstr "ইনপুট" #: spa/plugins/alsa/acp/alsa-mixer.c:2710 msgid "Docking Station Input" msgstr "ডকিং স্টেশন থেকে ইনপুট" #: spa/plugins/alsa/acp/alsa-mixer.c:2711 #, fuzzy msgid "Docking Station Microphone" msgstr "ডকিং স্টেশনের মাইক্রোফোন" #: spa/plugins/alsa/acp/alsa-mixer.c:2712 #, fuzzy msgid "Docking Station Line In" msgstr "ডকিং স্টেশন থেকে ইনপুট" #: spa/plugins/alsa/acp/alsa-mixer.c:2713 #: spa/plugins/alsa/acp/alsa-mixer.c:2804 msgid "Line In" msgstr "লাইন-ইন" #: spa/plugins/alsa/acp/alsa-mixer.c:2714 #: spa/plugins/alsa/acp/alsa-mixer.c:2798 #: spa/plugins/bluez5/bluez5-device.c:1145 msgid "Microphone" msgstr "মাইক্রোফোন" #: spa/plugins/alsa/acp/alsa-mixer.c:2715 #: spa/plugins/alsa/acp/alsa-mixer.c:2799 #, fuzzy msgid "Front Microphone" msgstr "ডকিং স্টেশনের মাইক্রোফোন" #: spa/plugins/alsa/acp/alsa-mixer.c:2716 #: spa/plugins/alsa/acp/alsa-mixer.c:2800 #, fuzzy msgid "Rear Microphone" msgstr "মাইক্রোফোন" #: spa/plugins/alsa/acp/alsa-mixer.c:2717 msgid "External Microphone" msgstr "বহিস্থিত মাইক্রোফোন" #: spa/plugins/alsa/acp/alsa-mixer.c:2718 #: spa/plugins/alsa/acp/alsa-mixer.c:2802 msgid "Internal Microphone" msgstr "অভ্যন্তরীণ মাইক্রোফোন" #: spa/plugins/alsa/acp/alsa-mixer.c:2719 #: spa/plugins/alsa/acp/alsa-mixer.c:2805 msgid "Radio" msgstr "রেডিও" #: spa/plugins/alsa/acp/alsa-mixer.c:2720 #: spa/plugins/alsa/acp/alsa-mixer.c:2806 msgid "Video" msgstr "ভিডিও" #: spa/plugins/alsa/acp/alsa-mixer.c:2721 msgid "Automatic Gain Control" msgstr "স্বয়ংক্রিয় গেইন নিয়ন্ত্রণ" #: spa/plugins/alsa/acp/alsa-mixer.c:2722 msgid "No Automatic Gain Control" msgstr "স্বয়ংক্রিয় গেইন নিয়ন্ত্রণ প্রয়োগ করা হবে না" #: spa/plugins/alsa/acp/alsa-mixer.c:2723 msgid "Boost" msgstr "বুস্ট" #: spa/plugins/alsa/acp/alsa-mixer.c:2724 msgid "No Boost" msgstr "বুস্ট প্রয়োগ করা হবে না" #: spa/plugins/alsa/acp/alsa-mixer.c:2725 msgid "Amplifier" msgstr "বিবর্ধক" #: spa/plugins/alsa/acp/alsa-mixer.c:2726 msgid "No Amplifier" msgstr "বিবর্ধন প্রয়োগ করা হবে না" #: spa/plugins/alsa/acp/alsa-mixer.c:2727 #, fuzzy msgid "Bass Boost" msgstr "বুস্ট" #: spa/plugins/alsa/acp/alsa-mixer.c:2728 #, fuzzy msgid "No Bass Boost" msgstr "বুস্ট প্রয়োগ করা হবে না" #: spa/plugins/alsa/acp/alsa-mixer.c:2729 #: spa/plugins/bluez5/bluez5-device.c:1150 msgid "Speaker" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2730 #: spa/plugins/alsa/acp/alsa-mixer.c:2808 msgid "Headphones" msgstr "অ্যানালগ হেড-ফোন" #: spa/plugins/alsa/acp/alsa-mixer.c:2797 msgid "Analog Input" msgstr "অ্যানালগ ইনপুট" #: spa/plugins/alsa/acp/alsa-mixer.c:2801 msgid "Dock Microphone" msgstr "ডকিং স্টেশনের মাইক্রোফোন" #: spa/plugins/alsa/acp/alsa-mixer.c:2803 #, fuzzy msgid "Headset Microphone" msgstr "মাইক্রোফোন" #: spa/plugins/alsa/acp/alsa-mixer.c:2807 msgid "Analog Output" msgstr "অ্যানালগ আউটপুট" #: spa/plugins/alsa/acp/alsa-mixer.c:2809 #, fuzzy msgid "Headphones 2" msgstr "অ্যানালগ হেড-ফোন" #: spa/plugins/alsa/acp/alsa-mixer.c:2810 #, fuzzy msgid "Headphones Mono Output" msgstr "অ্যানালগ মোনো আউটপুট" #: spa/plugins/alsa/acp/alsa-mixer.c:2811 #, fuzzy msgid "Line Out" msgstr "লাইন-ইন" #: spa/plugins/alsa/acp/alsa-mixer.c:2812 msgid "Analog Mono Output" msgstr "অ্যানালগ মোনো আউটপুট" #: spa/plugins/alsa/acp/alsa-mixer.c:2813 #, fuzzy msgid "Speakers" msgstr "অ্যানালগ স্টিরিও" #: spa/plugins/alsa/acp/alsa-mixer.c:2814 msgid "HDMI / DisplayPort" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2815 #, fuzzy msgid "Digital Output (S/PDIF)" msgstr "ডিজিট্যাল স্টিরিও (HDMI)" #: spa/plugins/alsa/acp/alsa-mixer.c:2816 #, fuzzy msgid "Digital Input (S/PDIF)" msgstr "ডিজিট্যাল স্টিরিও (HDMI)" #: spa/plugins/alsa/acp/alsa-mixer.c:2817 msgid "Multichannel Input" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2818 #, fuzzy msgid "Multichannel Output" msgstr "Null ফলাফল" #: spa/plugins/alsa/acp/alsa-mixer.c:2819 #, fuzzy msgid "Game Output" msgstr "Null ফলাফল" #: spa/plugins/alsa/acp/alsa-mixer.c:2820 #: spa/plugins/alsa/acp/alsa-mixer.c:2821 #, fuzzy msgid "Chat Output" msgstr "Null ফলাফল" #: spa/plugins/alsa/acp/alsa-mixer.c:2822 #, fuzzy msgid "Chat Input" msgstr "ইনপুট" #: spa/plugins/alsa/acp/alsa-mixer.c:2823 #, fuzzy msgid "Virtual Surround 7.1" msgstr "অ্যানালগ সারাউন্ড ৭.১" #: spa/plugins/alsa/acp/alsa-mixer.c:4527 msgid "Analog Mono" msgstr "অ্যানালগ মোনো" #: spa/plugins/alsa/acp/alsa-mixer.c:4528 #, fuzzy msgid "Analog Mono (Left)" msgstr "অ্যানালগ মোনো" #: spa/plugins/alsa/acp/alsa-mixer.c:4529 #, fuzzy msgid "Analog Mono (Right)" msgstr "অ্যানালগ মোনো" #. Note: Not translated to "Analog Stereo Input", because the source #. * name gets "Input" appended to it automatically, so adding "Input" #. * here would lead to the source name to become "Analog Stereo Input #. * Input". The same logic applies to analog-stereo-output, #. * multichannel-input and multichannel-output. #: spa/plugins/alsa/acp/alsa-mixer.c:4530 #: spa/plugins/alsa/acp/alsa-mixer.c:4538 #: spa/plugins/alsa/acp/alsa-mixer.c:4539 msgid "Analog Stereo" msgstr "অ্যানালগ স্টিরিও" #: spa/plugins/alsa/acp/alsa-mixer.c:4531 msgid "Mono" msgstr "মোনো" #: spa/plugins/alsa/acp/alsa-mixer.c:4532 msgid "Stereo" msgstr "স্টিরিও" #: spa/plugins/alsa/acp/alsa-mixer.c:4540 #: spa/plugins/alsa/acp/alsa-mixer.c:4698 #: spa/plugins/bluez5/bluez5-device.c:1135 msgid "Headset" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4541 #: spa/plugins/alsa/acp/alsa-mixer.c:4699 #, fuzzy msgid "Speakerphone" msgstr "অ্যানালগ স্টিরিও" #: spa/plugins/alsa/acp/alsa-mixer.c:4542 #: spa/plugins/alsa/acp/alsa-mixer.c:4543 msgid "Multichannel" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4544 msgid "Analog Surround 2.1" msgstr "অ্যানালগ সারাউন্ড ২.১" #: spa/plugins/alsa/acp/alsa-mixer.c:4545 msgid "Analog Surround 3.0" msgstr "অ্যানালগ সারাউন্ড ৩.০" #: spa/plugins/alsa/acp/alsa-mixer.c:4546 msgid "Analog Surround 3.1" msgstr "অ্যানালগ সারাউন্ড ৩.১" #: spa/plugins/alsa/acp/alsa-mixer.c:4547 msgid "Analog Surround 4.0" msgstr "অ্যানালগ সারাউন্ড ৪.০" #: spa/plugins/alsa/acp/alsa-mixer.c:4548 msgid "Analog Surround 4.1" msgstr "অ্যানালগ সারাউন্ড ৪.১" #: spa/plugins/alsa/acp/alsa-mixer.c:4549 msgid "Analog Surround 5.0" msgstr "অ্যানালগ সারাউন্ড ৫.০" #: spa/plugins/alsa/acp/alsa-mixer.c:4550 msgid "Analog Surround 5.1" msgstr "অ্যানালগ সারাউন্ড ৫.১" #: spa/plugins/alsa/acp/alsa-mixer.c:4551 msgid "Analog Surround 6.0" msgstr "অ্যানালগ সারাউন্ড ৬.০" #: spa/plugins/alsa/acp/alsa-mixer.c:4552 msgid "Analog Surround 6.1" msgstr "অ্যানালগ সারাউন্ড ৬.১" #: spa/plugins/alsa/acp/alsa-mixer.c:4553 msgid "Analog Surround 7.0" msgstr "অ্যানালগ সারাউন্ড ৭.০" #: spa/plugins/alsa/acp/alsa-mixer.c:4554 msgid "Analog Surround 7.1" msgstr "অ্যানালগ সারাউন্ড ৭.১" #: spa/plugins/alsa/acp/alsa-mixer.c:4555 msgid "Digital Stereo (IEC958)" msgstr "ডিজিট্যাল স্টিরিও (IEC958)" #: spa/plugins/alsa/acp/alsa-mixer.c:4556 msgid "Digital Surround 4.0 (IEC958/AC3)" msgstr "ডিজিট্যাল সারাউন্ড ৪.০ (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4557 msgid "Digital Surround 5.1 (IEC958/AC3)" msgstr "ডিজিট্যাল সারাউন্ড ৫.১ (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4558 #, fuzzy msgid "Digital Surround 5.1 (IEC958/DTS)" msgstr "ডিজিট্যাল সারাউন্ড ৫.১ (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4559 msgid "Digital Stereo (HDMI)" msgstr "ডিজিট্যাল স্টিরিও (HDMI)" #: spa/plugins/alsa/acp/alsa-mixer.c:4560 #, fuzzy msgid "Digital Surround 5.1 (HDMI)" msgstr "ডিজিট্যাল সারাউন্ড ৫.১ (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4561 msgid "Chat" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4562 msgid "Game" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4696 msgid "Analog Mono Duplex" msgstr "অ্যানালগ মোনো ডুপ্লে" #: spa/plugins/alsa/acp/alsa-mixer.c:4697 msgid "Analog Stereo Duplex" msgstr "অ্যানালগ স্টিরিও ডুপ্লে" #: spa/plugins/alsa/acp/alsa-mixer.c:4700 msgid "Digital Stereo Duplex (IEC958)" msgstr "ডিজিট্যাল স্টিরিও ডুপ্লে (IEC958)" #: spa/plugins/alsa/acp/alsa-mixer.c:4701 msgid "Multichannel Duplex" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4702 #, fuzzy msgid "Stereo Duplex" msgstr "অ্যানালগ স্টিরিও ডুপ্লে" #: spa/plugins/alsa/acp/alsa-mixer.c:4703 msgid "Mono Chat + 7.1 Surround" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4806 #, fuzzy, c-format msgid "%s Output" msgstr "Null ফলাফল" #: spa/plugins/alsa/acp/alsa-mixer.c:4813 #, fuzzy, c-format msgid "%s Input" msgstr "ইনপুট" #: spa/plugins/alsa/acp/alsa-util.c:1175 spa/plugins/alsa/acp/alsa-util.c:1269 #, fuzzy, c-format msgid "" "snd_pcm_avail() returned a value that is exceptionally large: %lu byte (%lu " "ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_avail() returned a value that is exceptionally large: %lu bytes (%lu " "ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "snd_pcm_avail() থেকে প্রাপ্ত মান অত্যাধিক বড়: %lu বাইট (%lu ms)।\n" "সম্ভবত এটি ALSA ড্রাইভার '%s'-র একটি বাগ। অনুগ্রহ করে এই সমস্যা সম্বন্ধে ALSA " "ডিভেলপরদের সূচিত করুন।" msgstr[1] "" "snd_pcm_avail() থেকে প্রাপ্ত মান অত্যাধিক বড়: %lu বাইট (%lu ms)।\n" "সম্ভবত এটি ALSA ড্রাইভার '%s'-র একটি বাগ। অনুগ্রহ করে এই সমস্যা সম্বন্ধে ALSA " "ডিভেলপরদের সূচিত করুন।" #: spa/plugins/alsa/acp/alsa-util.c:1241 #, fuzzy, c-format msgid "" "snd_pcm_delay() returned a value that is exceptionally large: %li byte (%s" "%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_delay() returned a value that is exceptionally large: %li bytes (%s" "%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "snd_pcm_delay() থেকে প্রাপ্ত মান অত্যাধিক বড়: %li বাইট (%s%lu ms)।\n" "সম্ভবত এটি ALSA ড্রাইভার '%s'-র একটি বাগ। অনুগ্রহ করে এই সমস্যা সম্বন্ধে ALSA " "ডিভেলপরদের সূচিত করুন।" msgstr[1] "" "snd_pcm_delay() থেকে প্রাপ্ত মান অত্যাধিক বড়: %li বাইট (%s%lu ms)।\n" "সম্ভবত এটি ALSA ড্রাইভার '%s'-র একটি বাগ। অনুগ্রহ করে এই সমস্যা সম্বন্ধে ALSA " "ডিভেলপরদের সূচিত করুন।" #: spa/plugins/alsa/acp/alsa-util.c:1288 #, fuzzy, c-format msgid "" "snd_pcm_avail_delay() returned strange values: delay %lu is less than avail " "%lu.\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr "" "snd_pcm_avail() থেকে প্রাপ্ত মান অত্যাধিক বড়: %lu বাইট (%lu ms)।\n" "সম্ভবত এটি ALSA ড্রাইভার '%s'-র একটি বাগ। অনুগ্রহ করে এই সমস্যা সম্বন্ধে ALSA " "ডিভেলপরদের সূচিত করুন।" #: spa/plugins/alsa/acp/alsa-util.c:1331 #, fuzzy, c-format msgid "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu byte " "(%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu bytes " "(%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "snd_pcm_mmap_begin() থেকে প্রাপ্ত মান অত্যাধিক বড়: %lu বাইট (%lu ms)।\n" "সম্ভবত এটি ALSA ড্রাইভার '%s'-র একটি বাগ। অনুগ্রহ করে এই সমস্যা সম্বন্ধে ALSA " "ডিভেলপরদের সূচিত করুন।" msgstr[1] "" "snd_pcm_mmap_begin() থেকে প্রাপ্ত মান অত্যাধিক বড়: %lu বাইট (%lu ms)।\n" "সম্ভবত এটি ALSA ড্রাইভার '%s'-র একটি বাগ। অনুগ্রহ করে এই সমস্যা সম্বন্ধে ALSA " "ডিভেলপরদের সূচিত করুন।" #: spa/plugins/bluez5/bluez5-device.c:1010 msgid "Audio Gateway (A2DP Source & HSP/HFP AG)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1033 #, c-format msgid "High Fidelity Playback (A2DP Sink, codec %s)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1035 #, c-format msgid "High Fidelity Duplex (A2DP Source/Sink, codec %s)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1041 msgid "High Fidelity Playback (A2DP Sink)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1043 msgid "High Fidelity Duplex (A2DP Source/Sink)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1070 #, c-format msgid "Headset Head Unit (HSP/HFP, codec %s)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1074 msgid "Headset Head Unit (HSP/HFP)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1140 msgid "Handsfree" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1155 #, fuzzy msgid "Headphone" msgstr "অ্যানালগ হেড-ফোন" #: spa/plugins/bluez5/bluez5-device.c:1160 msgid "Portable" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1165 msgid "Car" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1170 msgid "HiFi" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1175 msgid "Phone" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1181 msgid "Bluetooth" msgstr "" pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/po/ca.po000066400000000000000000000575561511204443500220610ustar00rootroot00000000000000# Catalan translation of pipewire by Softcatalà # Copyright (C) 2008 Free Software Foundation # This file is distributed under the same license as the pipewire package. # # Xavier Conde Rueda , 2008. # Agustí Grau , 2009. # Judith Pintó Subirada # Jordi Mas i Herǹandez, , 2022-2025 # # This file is translated according to the glossary and style guide of # Softcatalà. If you plan to modify this file, please read first the page # of the Catalan translation team for the Fedora project at: # http://www.softcatala.org/projectes/fedora/ # and contact the previous translator. # # Aquest fitxer s'ha de traduir d'acord amb el recull de termes i la guia # d'estil de Softcatalà. Si voleu modificar aquest fitxer, llegiu si # us plau la pàgina de catalanització del projecte Fedora a: # https://www.softcatala.org/projectes/fedora/ # i contacteu l'anterior traductor/a. # Josep Torné Llavall , 2009, 2012. # Robert Antoni Buj Gelonch , 2016. #zanata # Wim Taymans , 2016. #zanata # Robert Antoni Buj Gelonch , 2017. #zanata # Robert Antoni Buj Gelonch , 2019. #zanata # msgid "" msgstr "" "Project-Id-Version: pipewire\n" "Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/pipewire/-/issues\n" "POT-Creation-Date: 2025-07-20 15:32+0000\n" "PO-Revision-Date: 2025-06-20 21:46+0200\n" "Last-Translator: Jordi Mas i Herǹandez, ,\n" "Language-Team: Catalan \n" "Language: ca\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Generator: Poedit 3.2.2\n" #: src/daemon/pipewire.c:29 #, c-format msgid "" "%s [options]\n" " -h, --help Show this help\n" " -v, --verbose Increase verbosity by one level\n" " --version Show version\n" " -c, --config Load config (Default %s)\n" " -P --properties Set context properties\n" msgstr "" "%s [opcions]\n" " -h, --help Mostra aquesta ajuda\n" " -v, --verbose Augmenta la verbositat en un nivell\n" " --version Mostra la versió\n" " -c, --config Carrega la configuració (predeterminada %s)\n" " -P --properties Estableix les propietats del context\n" "\n" #: src/daemon/pipewire.desktop.in:3 msgid "PipeWire Media System" msgstr "Sistema multimèdia PipeWire" #: src/daemon/pipewire.desktop.in:4 msgid "Start the PipeWire Media System" msgstr "Inicia el sistema multimèdia PipeWire" #: src/modules/module-protocol-pulse/modules/module-tunnel-sink.c:159 #: src/modules/module-protocol-pulse/modules/module-tunnel-source.c:159 #, c-format msgid "Tunnel to %s%s%s" msgstr "Túnel cap a %s%s%s" #: src/modules/module-fallback-sink.c:40 msgid "Dummy Output" msgstr "Sortida fictícia" #: src/modules/module-pulse-tunnel.c:760 #, c-format msgid "Tunnel for %s@%s" msgstr "Túnel per a %s@%s" #: src/modules/module-zeroconf-discover.c:320 msgid "Unknown device" msgstr "Dispositiu desconegut" #: src/modules/module-zeroconf-discover.c:332 #, c-format msgid "%s on %s@%s" msgstr "%s en %s@%s" #: src/modules/module-zeroconf-discover.c:336 #, c-format msgid "%s on %s" msgstr "%s en %s" #: src/tools/pw-cat.c:1044 #, c-format msgid "" "%s [options] [|-]\n" " -h, --help Show this help\n" " --version Show version\n" " -v, --verbose Enable verbose operations\n" "\n" msgstr "" "%s [opcions] [|-]\n" " -h, --help Mostra aquesta ajuda\n" " --version Mostra la versió\n" " -v, --verbose Habilita les operacions detallades\n" #: src/tools/pw-cat.c:1051 #, c-format msgid "" " -R, --remote Remote daemon name\n" " --media-type Set media type (default %s)\n" " --media-category Set media category (default %s)\n" " --media-role Set media role (default %s)\n" " --target Set node target serial or name (default %s)\n" " 0 means don't link\n" " --latency Set node latency (default %s)\n" " Xunit (unit = s, ms, us, ns)\n" " or direct samples (256)\n" " the rate is the one of the source file\n" " -P --properties Set node properties\n" "\n" msgstr "" " -R, --remote Nom del dimoni remot\n" " --media-type Estableix el tipus de mitjà (per defecte %s)\n" " --media-category Estableix la categoria del mitjà (per defecte %s)\n" " --media-role Estableix el rol del mitjà (per defecte %s)\n" " --target Estableix l'objectiu sèrie o el nom del node (per defecte %s)\n" " 0 vol dir que no enllaça\n" " --latency Estableix latència del node (per defecte %s)\n" " Xunit (unitat = s, ms, us, ns)\n" " o mostres directes (256)\n" " la taxa és la del fitxer d'origen\n" " -P --properties Estableix les propietats del node\n" "\n" #: src/tools/pw-cat.c:1069 #, c-format msgid "" " --rate Sample rate (req. for rec) (default %u)\n" " --channels Number of channels (req. for rec) (default %u)\n" " --channel-map Channel map\n" " one of: \"stereo\", \"surround-51\",... or\n" " comma separated list of channel names: eg. \"FL,FR\"\n" " --format Sample format %s (req. for rec) (default %s)\n" " --volume Stream volume 0-1.0 (default %.3f)\n" " -q --quality Resampler quality (0 - 15) (default %d)\n" " -a, --raw RAW mode\n" "\n" msgstr "" " --rate Freqüència de mostreig (req. per a rec) (predeterminat %u)\n" " --channels Nombre de canals (req. per a rec) (predeterminat %u)\n" " --channel-map Mapa de canals\n" " un dels següents: \"stereo\", \"surround-51\",... o\n" " llista separada per comes dels noms dels canals: per exemple. «FL,FR»\n" " --format Format de mostra %s (req. per a rec) (predeterminat %s)\n" " --volume Volum del flux 0-1.0 (predeterminat %.3f)\n" " -q --quality Qualitat de remostrador (0 - 15) (predeterminat %d)\n" " -a, --raw Mode RAW\n" "\n" #: src/tools/pw-cat.c:1087 msgid "" " -p, --playback Playback mode\n" " -r, --record Recording mode\n" " -m, --midi Midi mode\n" " -d, --dsd DSD mode\n" " -o, --encoded Encoded mode\n" " -s, --sysex SysEx mode\n" "\n" msgstr "" " -p, --playback Mode de reproducció\n" " -r, --record Mode d'enregistrament\n" " -m, --midi Mode MIDI\n" " -d, --dsd Mode DSD\n" " -o, --encoded Mode codificat\n" " -s, --sysex Mode SysEx\n" "\n" #: src/tools/pw-cli.c:2386 #, c-format msgid "" "%s [options] [command]\n" " -h, --help Show this help\n" " --version Show version\n" " -d, --daemon Start as daemon (Default false)\n" " -r, --remote Remote daemon name\n" " -m, --monitor Monitor activity\n" "\n" msgstr "" "%s [opcions] [ordre]\n" " -h, --help Mostra aquesta ajuda\n" " --version Mostra la versió\n" " -d, --daemon Inicia com a dimoni (fals per defecte)\n" " -r, --remote Nom del dimoni remot\n" " -m, --monitor Monitor d'activitat\n" "\n" #: spa/plugins/alsa/acp/acp.c:351 msgid "Pro Audio" msgstr "Pro Audio" #: spa/plugins/alsa/acp/acp.c:520 spa/plugins/alsa/acp/alsa-mixer.c:4635 #: spa/plugins/bluez5/bluez5-device.c:1974 msgid "Off" msgstr "Inactiu" #: spa/plugins/alsa/acp/acp.c:603 #, c-format msgid "%s [ALSA UCM error]" msgstr "%s [Error d'ALSA UCM]" #: spa/plugins/alsa/acp/alsa-mixer.c:2652 msgid "Input" msgstr "Entrada" #: spa/plugins/alsa/acp/alsa-mixer.c:2653 msgid "Docking Station Input" msgstr "Entrada de l'estació d'acoblament" #: spa/plugins/alsa/acp/alsa-mixer.c:2654 msgid "Docking Station Microphone" msgstr "Micròfon de l'estació d'acoblament" #: spa/plugins/alsa/acp/alsa-mixer.c:2655 msgid "Docking Station Line In" msgstr "Entrada de línia de l'estació d'acoblament" #: spa/plugins/alsa/acp/alsa-mixer.c:2656 #: spa/plugins/alsa/acp/alsa-mixer.c:2747 msgid "Line In" msgstr "Entrada de línia" #: spa/plugins/alsa/acp/alsa-mixer.c:2657 #: spa/plugins/alsa/acp/alsa-mixer.c:2741 #: spa/plugins/bluez5/bluez5-device.c:2372 msgid "Microphone" msgstr "Micròfon" #: spa/plugins/alsa/acp/alsa-mixer.c:2658 #: spa/plugins/alsa/acp/alsa-mixer.c:2742 msgid "Front Microphone" msgstr "Micròfon frontal" #: spa/plugins/alsa/acp/alsa-mixer.c:2659 #: spa/plugins/alsa/acp/alsa-mixer.c:2743 msgid "Rear Microphone" msgstr "Micròfon posterior" #: spa/plugins/alsa/acp/alsa-mixer.c:2660 msgid "External Microphone" msgstr "Micròfon extern" #: spa/plugins/alsa/acp/alsa-mixer.c:2661 #: spa/plugins/alsa/acp/alsa-mixer.c:2745 msgid "Internal Microphone" msgstr "Micròfon intern" #: spa/plugins/alsa/acp/alsa-mixer.c:2662 #: spa/plugins/alsa/acp/alsa-mixer.c:2748 msgid "Radio" msgstr "Ràdio" #: spa/plugins/alsa/acp/alsa-mixer.c:2663 #: spa/plugins/alsa/acp/alsa-mixer.c:2749 msgid "Video" msgstr "Vídeo" #: spa/plugins/alsa/acp/alsa-mixer.c:2664 msgid "Automatic Gain Control" msgstr "Control de guany automàtic" #: spa/plugins/alsa/acp/alsa-mixer.c:2665 msgid "No Automatic Gain Control" msgstr "Sense control de guany automàtic" #: spa/plugins/alsa/acp/alsa-mixer.c:2666 msgid "Boost" msgstr "Accentuació" #: spa/plugins/alsa/acp/alsa-mixer.c:2667 msgid "No Boost" msgstr "Sense accentuació" #: spa/plugins/alsa/acp/alsa-mixer.c:2668 msgid "Amplifier" msgstr "Amplificador" #: spa/plugins/alsa/acp/alsa-mixer.c:2669 msgid "No Amplifier" msgstr "Sense amplificador" #: spa/plugins/alsa/acp/alsa-mixer.c:2670 msgid "Bass Boost" msgstr "Sense accentuació dels baixos" #: spa/plugins/alsa/acp/alsa-mixer.c:2671 msgid "No Bass Boost" msgstr "Sense accentuació dels baixos" #: spa/plugins/alsa/acp/alsa-mixer.c:2672 #: spa/plugins/bluez5/bluez5-device.c:2378 msgid "Speaker" msgstr "Altaveu" #. Don't call it "headset", the HF one has the mic #: spa/plugins/alsa/acp/alsa-mixer.c:2673 #: spa/plugins/alsa/acp/alsa-mixer.c:2751 #: spa/plugins/bluez5/bluez5-device.c:2384 #: spa/plugins/bluez5/bluez5-device.c:2451 msgid "Headphones" msgstr "Auriculars" #: spa/plugins/alsa/acp/alsa-mixer.c:2740 msgid "Analog Input" msgstr "Entrada analògica" #: spa/plugins/alsa/acp/alsa-mixer.c:2744 msgid "Dock Microphone" msgstr "Micròfon de l'acoblador" #: spa/plugins/alsa/acp/alsa-mixer.c:2746 msgid "Headset Microphone" msgstr "Micròfon d'auriculars" #: spa/plugins/alsa/acp/alsa-mixer.c:2750 msgid "Analog Output" msgstr "Sortida analògica" #: spa/plugins/alsa/acp/alsa-mixer.c:2752 msgid "Headphones 2" msgstr "Auriculars 2" #: spa/plugins/alsa/acp/alsa-mixer.c:2753 msgid "Headphones Mono Output" msgstr "Sortida mono dels auriculars" #: spa/plugins/alsa/acp/alsa-mixer.c:2754 msgid "Line Out" msgstr "Sortida de línia" #: spa/plugins/alsa/acp/alsa-mixer.c:2755 msgid "Analog Mono Output" msgstr "Sortida mono analògica" #: spa/plugins/alsa/acp/alsa-mixer.c:2756 msgid "Speakers" msgstr "Altaveus" #: spa/plugins/alsa/acp/alsa-mixer.c:2757 msgid "HDMI / DisplayPort" msgstr "HDMI / DisplayPort" #: spa/plugins/alsa/acp/alsa-mixer.c:2758 msgid "Digital Output (S/PDIF)" msgstr "Sortida digital (S/PDIF)" #: spa/plugins/alsa/acp/alsa-mixer.c:2759 msgid "Digital Input (S/PDIF)" msgstr "Entrada digital (S/PDIF)" #: spa/plugins/alsa/acp/alsa-mixer.c:2760 msgid "Multichannel Input" msgstr "Entrada multicanal" #: spa/plugins/alsa/acp/alsa-mixer.c:2761 msgid "Multichannel Output" msgstr "Sortida multicanal" #: spa/plugins/alsa/acp/alsa-mixer.c:2762 msgid "Game Output" msgstr "Sortida del joc" #: spa/plugins/alsa/acp/alsa-mixer.c:2763 #: spa/plugins/alsa/acp/alsa-mixer.c:2764 msgid "Chat Output" msgstr "Sortida del xat" #: spa/plugins/alsa/acp/alsa-mixer.c:2765 msgid "Chat Input" msgstr "Entrada del xat" #: spa/plugins/alsa/acp/alsa-mixer.c:2766 msgid "Virtual Surround 7.1" msgstr "Envoltant virtual 7.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4458 msgid "Analog Mono" msgstr "Mono analògic" #: spa/plugins/alsa/acp/alsa-mixer.c:4459 msgid "Analog Mono (Left)" msgstr "Mono analògic (esquerra)" #: spa/plugins/alsa/acp/alsa-mixer.c:4460 msgid "Analog Mono (Right)" msgstr "Mono analògic (dreta)" #. Note: Not translated to "Analog Stereo Input", because the source #. * name gets "Input" appended to it automatically, so adding "Input" #. * here would lead to the source name to become "Analog Stereo Input #. * Input". The same logic applies to analog-stereo-output, #. * multichannel-input and multichannel-output. #: spa/plugins/alsa/acp/alsa-mixer.c:4461 #: spa/plugins/alsa/acp/alsa-mixer.c:4469 #: spa/plugins/alsa/acp/alsa-mixer.c:4470 msgid "Analog Stereo" msgstr "Estèreo analògic" #: spa/plugins/alsa/acp/alsa-mixer.c:4462 msgid "Mono" msgstr "Mono" #: spa/plugins/alsa/acp/alsa-mixer.c:4463 msgid "Stereo" msgstr "Estèreo" #: spa/plugins/alsa/acp/alsa-mixer.c:4471 #: spa/plugins/alsa/acp/alsa-mixer.c:4629 #: spa/plugins/bluez5/bluez5-device.c:2360 msgid "Headset" msgstr "Auriculars" #: spa/plugins/alsa/acp/alsa-mixer.c:4472 #: spa/plugins/alsa/acp/alsa-mixer.c:4630 msgid "Speakerphone" msgstr "Altaveu del telèfon" #: spa/plugins/alsa/acp/alsa-mixer.c:4473 #: spa/plugins/alsa/acp/alsa-mixer.c:4474 msgid "Multichannel" msgstr "Multicanal" #: spa/plugins/alsa/acp/alsa-mixer.c:4475 msgid "Analog Surround 2.1" msgstr "Envoltant analògic 2.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4476 msgid "Analog Surround 3.0" msgstr "Envoltant analògic 3.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4477 msgid "Analog Surround 3.1" msgstr "Envoltant analògic 3.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4478 msgid "Analog Surround 4.0" msgstr "Envoltant analògic 4.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4479 msgid "Analog Surround 4.1" msgstr "Envoltant analògic 4.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4480 msgid "Analog Surround 5.0" msgstr "Envoltant analògic 5.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4481 msgid "Analog Surround 5.1" msgstr "Envoltant analògic 5.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4482 msgid "Analog Surround 6.0" msgstr "Envoltant analògic 6.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4483 msgid "Analog Surround 6.1" msgstr "Envoltant analògic 6.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4484 msgid "Analog Surround 7.0" msgstr "Envoltant analògic 7.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4485 msgid "Analog Surround 7.1" msgstr "Envoltant analògic 7.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4486 msgid "Digital Stereo (IEC958)" msgstr "Estèreo digital (IEC958)" #: spa/plugins/alsa/acp/alsa-mixer.c:4487 msgid "Digital Surround 4.0 (IEC958/AC3)" msgstr "Envoltant digital 4.0 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4488 msgid "Digital Surround 5.1 (IEC958/AC3)" msgstr "Envoltant digital 5.1 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4489 msgid "Digital Surround 5.1 (IEC958/DTS)" msgstr "Envoltant digital 5.1 (IEC958/DTS)" #: spa/plugins/alsa/acp/alsa-mixer.c:4490 msgid "Digital Stereo (HDMI)" msgstr "Estèreo digital (HDMI)" #: spa/plugins/alsa/acp/alsa-mixer.c:4491 msgid "Digital Surround 5.1 (HDMI)" msgstr "Envoltant digital 5.1 (HDMI)" #: spa/plugins/alsa/acp/alsa-mixer.c:4492 msgid "Chat" msgstr "Xat" #: spa/plugins/alsa/acp/alsa-mixer.c:4493 msgid "Game" msgstr "Joc" #: spa/plugins/alsa/acp/alsa-mixer.c:4627 msgid "Analog Mono Duplex" msgstr "Dúplex mono analògic" #: spa/plugins/alsa/acp/alsa-mixer.c:4628 msgid "Analog Stereo Duplex" msgstr "Dúplex estèreo analògic" #: spa/plugins/alsa/acp/alsa-mixer.c:4631 msgid "Digital Stereo Duplex (IEC958)" msgstr "Dúplex estèreo digital (IEC958)" #: spa/plugins/alsa/acp/alsa-mixer.c:4632 msgid "Multichannel Duplex" msgstr "Dúplex Multicanal" #: spa/plugins/alsa/acp/alsa-mixer.c:4633 msgid "Stereo Duplex" msgstr "Dúplex estèreo" #: spa/plugins/alsa/acp/alsa-mixer.c:4634 msgid "Mono Chat + 7.1 Surround" msgstr "Xat mono + 7.1 envoltant" #: spa/plugins/alsa/acp/alsa-mixer.c:4735 #, c-format msgid "%s Output" msgstr "Sortida %s" #: spa/plugins/alsa/acp/alsa-mixer.c:4743 #, c-format msgid "%s Input" msgstr "Entrada %s" #: spa/plugins/alsa/acp/alsa-util.c:1233 spa/plugins/alsa/acp/alsa-util.c:1327 #, c-format msgid "" "snd_pcm_avail() returned a value that is exceptionally large: %lu byte (%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue to the ALSA developers." msgid_plural "" "snd_pcm_avail() returned a value that is exceptionally large: %lu bytes (%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue to the ALSA developers." msgstr[0] "" "snd_pcm_avail() ha retornat un valor excepcionalment gran: %lu byte (%lu ms).\n" "Probablement es tracta d'un error del controlador de l'ALSA «%s». Informeu d'aquest incident als desenvolupadors de l'ALSA." msgstr[1] "" "snd_pcm_avail() ha retornat un valor excepcionalment gran: %lu bytes (%lu ms).\n" "Probablement es tracta d'un error del controlador de l'ALSA «%s». Informeu d'aquest incident als desenvolupadors de l'ALSA." #: spa/plugins/alsa/acp/alsa-util.c:1299 #, c-format msgid "" "snd_pcm_delay() returned a value that is exceptionally large: %li byte (%s%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue to the ALSA developers." msgid_plural "" "snd_pcm_delay() returned a value that is exceptionally large: %li bytes (%s%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue to the ALSA developers." msgstr[0] "" "snd_pcm_delay() ha retornat un valor excepcionalment gran: %li byte (%s%lu ms).\n" "Probablement es tracta d'un error del controlador de l'ALSA «%s». Informeu d'aquest incident als desenvolupadors de l'ALSA." msgstr[1] "" "snd_pcm_delay() ha retornat un valor excepcionalment gran: %li bytes (%s%lu ms).\n" "Probablement es tracta d'un error del controlador de l'ALSA «%s». Informeu d'aquest incident als desenvolupadors de l'ALSA." #: spa/plugins/alsa/acp/alsa-util.c:1346 #, c-format msgid "" "snd_pcm_avail_delay() returned strange values: delay %lu is less than avail %lu.\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue to the ALSA developers." msgstr "" "snd_pcm_avail() ha retornat un valor excepcionalment gran: %lu bytes (%lu ms).\n" "Probablement es tracta d'un error del controlador d'ALSA «%s». Informeu d'aquest problema als desenvolupadors d'ALSA." #: spa/plugins/alsa/acp/alsa-util.c:1389 #, c-format msgid "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu byte (%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue to the ALSA developers." msgid_plural "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu bytes (%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue to the ALSA developers." msgstr[0] "" "snd_pcm_mmap_begin() ha retornat un valor excepcionalment gran: %lu byte (%lu ms).\n" "Probablement es tracta d'un error del controlador de l'ALSA «%s». Informeu d'aquest incident als desenvolupadors de l'ALSA." msgstr[1] "" "snd_pcm_mmap_begin() ha retornat un valor excepcionalment gran: %lu bytes (%lu ms).\n" "Probablement es tracta d'un error del controlador de l'ALSA «%s». Informeu d'aquest incident als desenvolupadors de l'ALSA." #: spa/plugins/alsa/acp/channelmap.h:457 msgid "(invalid)" msgstr "(incorrecte)" #: spa/plugins/alsa/acp/compat.c:194 msgid "Built-in Audio" msgstr "Àudio intern" #: spa/plugins/alsa/acp/compat.c:199 msgid "Modem" msgstr "Mòdem" #: spa/plugins/bluez5/bluez5-device.c:1985 msgid "Audio Gateway (A2DP Source & HSP/HFP AG)" msgstr "Passarel·la d'àudio (A2DP Source & HSP/HFP AG)" #: spa/plugins/bluez5/bluez5-device.c:2014 msgid "Audio Streaming for Hearing Aids (ASHA Sink)" msgstr "Retransmissió d'àudio per als audiòfons (ASHA Sink)" #: spa/plugins/bluez5/bluez5-device.c:2057 #, c-format msgid "High Fidelity Playback (A2DP Sink, codec %s)" msgstr "Reproducció d'alta fidelitat (Sink A2DP, còdec %s)" #: spa/plugins/bluez5/bluez5-device.c:2060 #, c-format msgid "High Fidelity Duplex (A2DP Source/Sink, codec %s)" msgstr "Dúplex d'alta fidelitat (A2DP Source/Sink, còdec %s)" #: spa/plugins/bluez5/bluez5-device.c:2068 msgid "High Fidelity Playback (A2DP Sink)" msgstr "Reproducció d'alta fidelitat (A2DP Sink)" #: spa/plugins/bluez5/bluez5-device.c:2070 msgid "High Fidelity Duplex (A2DP Source/Sink)" msgstr "Dúplex d'alta fidelitat (A2DP Source/Sink)" #: spa/plugins/bluez5/bluez5-device.c:2144 #, c-format msgid "High Fidelity Playback (BAP Sink, codec %s)" msgstr "Reproducció d'alta fidelitat (sortida BAP, còdec %s)" #: spa/plugins/bluez5/bluez5-device.c:2149 #, c-format msgid "High Fidelity Input (BAP Source, codec %s)" msgstr "Entrada d'alta fidelitat (font A2DP, còdec %s)" #: spa/plugins/bluez5/bluez5-device.c:2153 #, c-format msgid "High Fidelity Duplex (BAP Source/Sink, codec %s)" msgstr "Dúplex d'alta fidelitat (BAP Source/Sink, còdec %s)" #: spa/plugins/bluez5/bluez5-device.c:2162 msgid "High Fidelity Playback (BAP Sink)" msgstr "Reproducció d'alta fidelitat (Sortida BAP)" #: spa/plugins/bluez5/bluez5-device.c:2166 msgid "High Fidelity Input (BAP Source)" msgstr "Entrada d'alta fidelitat (Font BAP)" #: spa/plugins/bluez5/bluez5-device.c:2169 msgid "High Fidelity Duplex (BAP Source/Sink)" msgstr "Dúplex d'alta fidelitat (BAP Source/Sink)" #: spa/plugins/bluez5/bluez5-device.c:2209 #, c-format msgid "Headset Head Unit (HSP/HFP, codec %s)" msgstr "Unitat d'auriculars pel cap (HSP/HFP, còdec %s)" #: spa/plugins/bluez5/bluez5-device.c:2361 #: spa/plugins/bluez5/bluez5-device.c:2366 #: spa/plugins/bluez5/bluez5-device.c:2373 #: spa/plugins/bluez5/bluez5-device.c:2379 #: spa/plugins/bluez5/bluez5-device.c:2385 #: spa/plugins/bluez5/bluez5-device.c:2391 #: spa/plugins/bluez5/bluez5-device.c:2397 #: spa/plugins/bluez5/bluez5-device.c:2403 #: spa/plugins/bluez5/bluez5-device.c:2409 msgid "Handsfree" msgstr "Mans lliures" #: spa/plugins/bluez5/bluez5-device.c:2367 msgid "Handsfree (HFP)" msgstr "Mans lliures (HFP)" #: spa/plugins/bluez5/bluez5-device.c:2390 msgid "Portable" msgstr "Portable" #: spa/plugins/bluez5/bluez5-device.c:2396 msgid "Car" msgstr "Cotxe" #: spa/plugins/bluez5/bluez5-device.c:2402 msgid "HiFi" msgstr "HiFi" #: spa/plugins/bluez5/bluez5-device.c:2408 msgid "Phone" msgstr "Telèfon" #: spa/plugins/bluez5/bluez5-device.c:2415 msgid "Bluetooth" msgstr "Bluetooth" #: spa/plugins/bluez5/bluez5-device.c:2416 msgid "Bluetooth (HFP)" msgstr "Bluetooth (HFP)" pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/po/cs.po000066400000000000000000000553231511204443500220710ustar00rootroot00000000000000# Czech translation of pipewire. # Copyright (C) 2008, 2009 the author(s) of pipewire. # This file is distributed under the same license as the pipewire package. # Petr Kovar , 2008, 2009, 2012. # Daniel Rusek , 2018. # Marek Černocký , 2018. # msgid "" msgstr "" "Project-Id-Version: pipewire.master-tx\n" "Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/pipewire/-/" "issues\n" "POT-Creation-Date: 2022-09-15 15:26+0000\n" "PO-Revision-Date: 2022-10-21 16:44+0200\n" "Last-Translator: Daniel Rusek \n" "Language-Team: čeština \n" "Language: cs\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n" "X-Generator: Poedit 3.1.1\n" #: src/daemon/pipewire.c:46 #, c-format msgid "" "%s [options]\n" " -h, --help Show this help\n" " --version Show version\n" " -c, --config Load config (Default %s)\n" msgstr "" "%s [volby]\n" " -h, --help Zobrazit tuto nápovědu\n" " --version Zobrazit verzi\n" " -c, --config Načíst konfiguraci (výchozí je %s)\n" #: src/daemon/pipewire.desktop.in:4 msgid "PipeWire Media System" msgstr "Multimediální systém PipeWire" #: src/daemon/pipewire.desktop.in:5 msgid "Start the PipeWire Media System" msgstr "Spustit multimediální systém PipeWire" #: src/modules/module-protocol-pulse/modules/module-tunnel-sink.c:180 #: src/modules/module-protocol-pulse/modules/module-tunnel-source.c:180 #, c-format msgid "Tunnel to %s/%s" msgstr "Tunel do %s/%s" #: src/modules/module-fallback-sink.c:51 #| msgid "Game Output" msgid "Dummy Output" msgstr "Předstíraný výstup" #: src/modules/module-pulse-tunnel.c:662 #, c-format msgid "Tunnel for %s@%s" msgstr "Tunel pro %s@%s" #: src/modules/module-zeroconf-discover.c:332 msgid "Unknown device" msgstr "Neznámé zařízení" #: src/modules/module-zeroconf-discover.c:344 #, c-format msgid "%s on %s@%s" msgstr "%s na %s@%s" #: src/modules/module-zeroconf-discover.c:348 #, c-format msgid "%s on %s" msgstr "%s na %s" #: src/tools/pw-cat.c:784 #, c-format msgid "" "%s [options] [|-]\n" " -h, --help Show this help\n" " --version Show version\n" " -v, --verbose Enable verbose operations\n" "\n" msgstr "" "%s [volby] [|-]\n" " -h, --help Zobrazit tuto nápovědu\n" " --version Zobrazit verzi\n" " -v, --verbose Povolit podrobné operace\n" "\n" #: src/tools/pw-cat.c:791 #, c-format msgid "" " -R, --remote Remote daemon name\n" " --media-type Set media type (default %s)\n" " --media-category Set media category (default %s)\n" " --media-role Set media role (default %s)\n" " --target Set node target (default %s)\n" " 0 means don't link\n" " --latency Set node latency (default %s)\n" " Xunit (unit = s, ms, us, ns)\n" " or direct samples (256)\n" " the rate is the one of the source " "file\n" " -P --properties Set node properties\n" "\n" msgstr "" " -R, --remote Název vzdáleného démonu\n" " --media-type Nastavit typ médií (výchozí je %s)\n" " --media-category Nastavit kategorii médií (výchozí je " "%s)\n" " --media-role Nastavit roli médií (výchozí je %s)\n" " --target Nastavit cíl uzlu (výchozí je %s)\n" " 0 znamená nepropojovat\n" " --latency Nastavit latenci uzlu (výchozí je " "%s)\n" " Xjednotka (jednotka = s, ms, us, " "ns)\n" " nebo přímé vzorky (256)\n" " frekvence je stejná jako u " "zdrojového souboru\n" " -P --properties Nastavit vlastnosti uzlu\n" "\n" #: src/tools/pw-cat.c:809 #, c-format msgid "" " --rate Sample rate (req. for rec) (default " "%u)\n" " --channels Number of channels (req. for rec) " "(default %u)\n" " --channel-map Channel map\n" " one of: \"stereo\", " "\"surround-51\",... or\n" " comma separated list of channel " "names: eg. \"FL,FR\"\n" " --format Sample format %s (req. for rec) " "(default %s)\n" " --volume Stream volume 0-1.0 (default %.3f)\n" " -q --quality Resampler quality (0 - 15) (default " "%d)\n" "\n" msgstr "" " --rate Vzorkovací frekvence (vyžad. pro " "rec) (výchozí je %u)\n" " --channels Počet kanálů (vyžad. pro rec) " "(výchozí je %u)\n" " --channel-map Mapa kanálů\n" " jedno z: \"stereo\", " "\"surround-51\",... nebo\n" " čárkami oddělený seznam názvů " "kanálů: např. \"FL,FR\"\n" " --format Formát vzorku %s (vyžad. pro rec) " "(výchozí je %s)\n" " --volume Hlasitost streamu 0-1.0 (výchozí je " "%.3f)\n" " -q --quality Kvalita resampleru (0 - 15) (výchozí " "je %d)\n" "\n" #: src/tools/pw-cat.c:826 msgid "" " -p, --playback Playback mode\n" " -r, --record Recording mode\n" " -m, --midi Midi mode\n" " -d, --dsd DSD mode\n" "\n" msgstr "" " -p, --playback Playback mód\n" " -r, --record Recording mód\n" " -m, --midi Midi mód\n" " -d, --dsd DSD mód\n" "\n" #: src/tools/pw-cli.c:2255 #, c-format msgid "" "%s [options] [command]\n" " -h, --help Show this help\n" " --version Show version\n" " -d, --daemon Start as daemon (Default false)\n" " -r, --remote Remote daemon name\n" "\n" msgstr "" "%s [volby] [příkaz]\n" " -h, --help Zobrazit tuto nápovědu\n" " --version Zobrazit verzi\n" " -d, --daemon Spustit jako démon (výchozí je " "false)\n" " -r, --remote Název vzdáleného démonu\n" "\n" #: spa/plugins/alsa/acp/acp.c:321 msgid "Pro Audio" msgstr "Pro Audio" #: spa/plugins/alsa/acp/acp.c:444 spa/plugins/alsa/acp/alsa-mixer.c:4648 #: spa/plugins/bluez5/bluez5-device.c:1236 msgid "Off" msgstr "Vypnuto" #: spa/plugins/alsa/acp/alsa-mixer.c:2652 msgid "Input" msgstr "Vstup" #: spa/plugins/alsa/acp/alsa-mixer.c:2653 msgid "Docking Station Input" msgstr "Vstup dokovací stanice" #: spa/plugins/alsa/acp/alsa-mixer.c:2654 msgid "Docking Station Microphone" msgstr "Mikrofon dokovací stanice" #: spa/plugins/alsa/acp/alsa-mixer.c:2655 msgid "Docking Station Line In" msgstr "Linkový vstup dokovací stanice" #: spa/plugins/alsa/acp/alsa-mixer.c:2656 #: spa/plugins/alsa/acp/alsa-mixer.c:2747 msgid "Line In" msgstr "Linkový vstup" #: spa/plugins/alsa/acp/alsa-mixer.c:2657 #: spa/plugins/alsa/acp/alsa-mixer.c:2741 #: spa/plugins/bluez5/bluez5-device.c:1454 msgid "Microphone" msgstr "Mikrofon" #: spa/plugins/alsa/acp/alsa-mixer.c:2658 #: spa/plugins/alsa/acp/alsa-mixer.c:2742 msgid "Front Microphone" msgstr "Přední mikrofon" #: spa/plugins/alsa/acp/alsa-mixer.c:2659 #: spa/plugins/alsa/acp/alsa-mixer.c:2743 msgid "Rear Microphone" msgstr "Zadní mikrofon" #: spa/plugins/alsa/acp/alsa-mixer.c:2660 msgid "External Microphone" msgstr "Externí mikrofon" #: spa/plugins/alsa/acp/alsa-mixer.c:2661 #: spa/plugins/alsa/acp/alsa-mixer.c:2745 msgid "Internal Microphone" msgstr "Interní mikrofon" #: spa/plugins/alsa/acp/alsa-mixer.c:2662 #: spa/plugins/alsa/acp/alsa-mixer.c:2748 msgid "Radio" msgstr "Rádio" #: spa/plugins/alsa/acp/alsa-mixer.c:2663 #: spa/plugins/alsa/acp/alsa-mixer.c:2749 msgid "Video" msgstr "Obraz" #: spa/plugins/alsa/acp/alsa-mixer.c:2664 msgid "Automatic Gain Control" msgstr "Automatické řízení zesílení" #: spa/plugins/alsa/acp/alsa-mixer.c:2665 msgid "No Automatic Gain Control" msgstr "Bez automatického řízení zesílení" #: spa/plugins/alsa/acp/alsa-mixer.c:2666 msgid "Boost" msgstr "Zdůraznění" #: spa/plugins/alsa/acp/alsa-mixer.c:2667 msgid "No Boost" msgstr "Bez zdůraznění" #: spa/plugins/alsa/acp/alsa-mixer.c:2668 msgid "Amplifier" msgstr "Zesilovač" #: spa/plugins/alsa/acp/alsa-mixer.c:2669 msgid "No Amplifier" msgstr "Bez zesilovače" #: spa/plugins/alsa/acp/alsa-mixer.c:2670 msgid "Bass Boost" msgstr "Zdůraznění basů" #: spa/plugins/alsa/acp/alsa-mixer.c:2671 msgid "No Bass Boost" msgstr "Bez zdůraznění basů" #: spa/plugins/alsa/acp/alsa-mixer.c:2672 #: spa/plugins/bluez5/bluez5-device.c:1460 msgid "Speaker" msgstr "Reproduktor" #: spa/plugins/alsa/acp/alsa-mixer.c:2673 #: spa/plugins/alsa/acp/alsa-mixer.c:2751 msgid "Headphones" msgstr "Sluchátka" #: spa/plugins/alsa/acp/alsa-mixer.c:2740 msgid "Analog Input" msgstr "Analogový vstup" #: spa/plugins/alsa/acp/alsa-mixer.c:2744 msgid "Dock Microphone" msgstr "Dokovací mikrofon" #: spa/plugins/alsa/acp/alsa-mixer.c:2746 msgid "Headset Microphone" msgstr "Mikrofon náhlavní soupravy" #: spa/plugins/alsa/acp/alsa-mixer.c:2750 msgid "Analog Output" msgstr "Analogový výstup" #: spa/plugins/alsa/acp/alsa-mixer.c:2752 msgid "Headphones 2" msgstr "Sluchátka 2" #: spa/plugins/alsa/acp/alsa-mixer.c:2753 msgid "Headphones Mono Output" msgstr "Sluchátkový výstup mono" #: spa/plugins/alsa/acp/alsa-mixer.c:2754 msgid "Line Out" msgstr "Linkový výstup" #: spa/plugins/alsa/acp/alsa-mixer.c:2755 msgid "Analog Mono Output" msgstr "Analogový výstup mono" #: spa/plugins/alsa/acp/alsa-mixer.c:2756 msgid "Speakers" msgstr "Reproduktory" #: spa/plugins/alsa/acp/alsa-mixer.c:2757 msgid "HDMI / DisplayPort" msgstr "HDMI / DisplayPort" #: spa/plugins/alsa/acp/alsa-mixer.c:2758 msgid "Digital Output (S/PDIF)" msgstr "Digitální výstup (S/PDIF)" #: spa/plugins/alsa/acp/alsa-mixer.c:2759 msgid "Digital Input (S/PDIF)" msgstr "Digitální vstup (S/PDIF)" #: spa/plugins/alsa/acp/alsa-mixer.c:2760 msgid "Multichannel Input" msgstr "Vícekanálový vstup" #: spa/plugins/alsa/acp/alsa-mixer.c:2761 msgid "Multichannel Output" msgstr "Vícekanálový výstup" #: spa/plugins/alsa/acp/alsa-mixer.c:2762 msgid "Game Output" msgstr "Herní výstup" #: spa/plugins/alsa/acp/alsa-mixer.c:2763 #: spa/plugins/alsa/acp/alsa-mixer.c:2764 msgid "Chat Output" msgstr "Komunikační výstup" #: spa/plugins/alsa/acp/alsa-mixer.c:2765 msgid "Chat Input" msgstr "Komunikační vstup" #: spa/plugins/alsa/acp/alsa-mixer.c:2766 msgid "Virtual Surround 7.1" msgstr "Virtuální surround 7.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4471 msgid "Analog Mono" msgstr "Analogové mono" #: spa/plugins/alsa/acp/alsa-mixer.c:4472 msgid "Analog Mono (Left)" msgstr "Analogové mono (levé)" #: spa/plugins/alsa/acp/alsa-mixer.c:4473 msgid "Analog Mono (Right)" msgstr "Analogové mono (pravé)" #. Note: Not translated to "Analog Stereo Input", because the source #. * name gets "Input" appended to it automatically, so adding "Input" #. * here would lead to the source name to become "Analog Stereo Input #. * Input". The same logic applies to analog-stereo-output, #. * multichannel-input and multichannel-output. #: spa/plugins/alsa/acp/alsa-mixer.c:4474 #: spa/plugins/alsa/acp/alsa-mixer.c:4482 #: spa/plugins/alsa/acp/alsa-mixer.c:4483 msgid "Analog Stereo" msgstr "Analogové stereo" #: spa/plugins/alsa/acp/alsa-mixer.c:4475 msgid "Mono" msgstr "Mono" #: spa/plugins/alsa/acp/alsa-mixer.c:4476 msgid "Stereo" msgstr "Stereo" #: spa/plugins/alsa/acp/alsa-mixer.c:4484 #: spa/plugins/alsa/acp/alsa-mixer.c:4642 #: spa/plugins/bluez5/bluez5-device.c:1442 msgid "Headset" msgstr "Náhlavní souprava" #: spa/plugins/alsa/acp/alsa-mixer.c:4485 #: spa/plugins/alsa/acp/alsa-mixer.c:4643 msgid "Speakerphone" msgstr "Hlasitý odposlech" #: spa/plugins/alsa/acp/alsa-mixer.c:4486 #: spa/plugins/alsa/acp/alsa-mixer.c:4487 msgid "Multichannel" msgstr "Více kanálů" #: spa/plugins/alsa/acp/alsa-mixer.c:4488 msgid "Analog Surround 2.1" msgstr "Analogový Surround 2.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4489 msgid "Analog Surround 3.0" msgstr "Analogový Surround 3.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4490 msgid "Analog Surround 3.1" msgstr "Analogový Surround 3.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4491 msgid "Analog Surround 4.0" msgstr "Analogový Surround 4.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4492 msgid "Analog Surround 4.1" msgstr "Analogový Surround 4.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4493 msgid "Analog Surround 5.0" msgstr "Analogový Surround 5.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4494 msgid "Analog Surround 5.1" msgstr "Analogový Surround 5.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4495 msgid "Analog Surround 6.0" msgstr "Analogový Surround 6.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4496 msgid "Analog Surround 6.1" msgstr "Analogový Surround 6.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4497 msgid "Analog Surround 7.0" msgstr "Analogový Surround 7.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4498 msgid "Analog Surround 7.1" msgstr "Analogový Surround 7.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4499 msgid "Digital Stereo (IEC958)" msgstr "Digitální stereo (IEC958)" #: spa/plugins/alsa/acp/alsa-mixer.c:4500 msgid "Digital Surround 4.0 (IEC958/AC3)" msgstr "Digitální Surround 4.0 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4501 msgid "Digital Surround 5.1 (IEC958/AC3)" msgstr "Digitální Surround 5.1 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4502 msgid "Digital Surround 5.1 (IEC958/DTS)" msgstr "Digitální Surround 5.1 (IEC958/DTS)" #: spa/plugins/alsa/acp/alsa-mixer.c:4503 msgid "Digital Stereo (HDMI)" msgstr "Digitální stereo (HDMI)" #: spa/plugins/alsa/acp/alsa-mixer.c:4504 msgid "Digital Surround 5.1 (HDMI)" msgstr "Digitální Surround 5.1 (HDMI)" #: spa/plugins/alsa/acp/alsa-mixer.c:4505 msgid "Chat" msgstr "Chat" #: spa/plugins/alsa/acp/alsa-mixer.c:4506 msgid "Game" msgstr "Hra" #: spa/plugins/alsa/acp/alsa-mixer.c:4640 msgid "Analog Mono Duplex" msgstr "Analogové duplexní mono" #: spa/plugins/alsa/acp/alsa-mixer.c:4641 msgid "Analog Stereo Duplex" msgstr "Analogové duplexní stereo" #: spa/plugins/alsa/acp/alsa-mixer.c:4644 msgid "Digital Stereo Duplex (IEC958)" msgstr "Digitální duplexní stereo (IEC958)" #: spa/plugins/alsa/acp/alsa-mixer.c:4645 msgid "Multichannel Duplex" msgstr "Vícekanálový duplex" #: spa/plugins/alsa/acp/alsa-mixer.c:4646 msgid "Stereo Duplex" msgstr "Duplexní stereo" #: spa/plugins/alsa/acp/alsa-mixer.c:4647 msgid "Mono Chat + 7.1 Surround" msgstr "Mono Chat + 7.1 Surround" #: spa/plugins/alsa/acp/alsa-mixer.c:4754 #, c-format msgid "%s Output" msgstr "Výstup %s" #: spa/plugins/alsa/acp/alsa-mixer.c:4761 #, c-format msgid "%s Input" msgstr "Vstup %s" #: spa/plugins/alsa/acp/alsa-util.c:1187 spa/plugins/alsa/acp/alsa-util.c:1281 #, c-format msgid "" "snd_pcm_avail() returned a value that is exceptionally large: %lu byte (%lu " "ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_avail() returned a value that is exceptionally large: %lu bytes (%lu " "ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "Volání snd_pcm_avail() vrátilo hodnotu, která je nezvykle vysoká: %lu bajtů " "(%lu ms).\n" "S největší pravděpodobností se jedná o chybu v ovladači ALSA „%s“. Nahlaste " "prosím tento problém vývojářům ALSA." msgstr[1] "" "Volání snd_pcm_avail() vrátilo hodnotu, která je nezvykle vysoká: %lu bajtů " "(%lu ms).\n" "S největší pravděpodobností se jedná o chybu v ovladači ALSA „%s“. Nahlaste " "prosím tento problém vývojářům ALSA." msgstr[2] "" "Volání snd_pcm_avail() vrátilo hodnotu, která je nezvykle vysoká: %lu bajtů " "(%lu ms).\n" "S největší pravděpodobností se jedná o chybu v ovladači ALSA „%s“. Nahlaste " "prosím tento problém vývojářům ALSA." #: spa/plugins/alsa/acp/alsa-util.c:1253 #, c-format msgid "" "snd_pcm_delay() returned a value that is exceptionally large: %li byte " "(%s%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_delay() returned a value that is exceptionally large: %li bytes " "(%s%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "Volání snd_pcm_delay() vrátilo hodnotu, která je nezvykle vysoká: %li bajtů " "(%s%lu ms).\n" "S největší pravděpodobností se jedná o chybu v ovladači ALSA „%s“. Nahlaste " "prosím tento problém vývojářům ALSA." msgstr[1] "" "Volání snd_pcm_delay() vrátilo hodnotu, která je nezvykle vysoká: %li bajtů " "(%s%lu ms).\n" "S největší pravděpodobností se jedná o chybu v ovladači ALSA „%s“. Nahlaste " "prosím tento problém vývojářům ALSA." msgstr[2] "" "Volání snd_pcm_delay() vrátilo hodnotu, která je nezvykle vysoká: %li bajtů " "(%s%lu ms).\n" "S největší pravděpodobností se jedná o chybu v ovladači ALSA „%s“. Nahlaste " "prosím tento problém vývojářům ALSA." #: spa/plugins/alsa/acp/alsa-util.c:1300 #, c-format msgid "" "snd_pcm_avail_delay() returned strange values: delay %lu is less than avail " "%lu.\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr "" "Volání snd_pcm_delay() vrátilo hodnotu, která je podivná: zpoždění %lu je " "menší než možné %lu.\n" "S největší pravděpodobností se jedná o chybu v ovladači ALSA „%s“. Nahlaste " "prosím tento problém vývojářům ALSA." #: spa/plugins/alsa/acp/alsa-util.c:1343 #, c-format msgid "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu byte " "(%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu bytes " "(%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "Volání snd_pcm_mmap_begin() vrátilo hodnotu, která je nezvykle vysoká: %lu " "bajtů (%lu ms).\n" "S největší pravděpodobností se jedná o chybu v ovladači ALSA „%s“. Nahlaste " "prosím tento problém vývojářům ALSA." msgstr[1] "" "Volání snd_pcm_mmap_begin() vrátilo hodnotu, která je nezvykle vysoká: %lu " "bajtů (%lu ms).\n" "S největší pravděpodobností se jedná o chybu v ovladači ALSA „%s“. Nahlaste " "prosím tento problém vývojářům ALSA." msgstr[2] "" "Volání snd_pcm_mmap_begin() vrátilo hodnotu, která je nezvykle vysoká: %lu " "bajtů (%lu ms).\n" "S největší pravděpodobností se jedná o chybu v ovladači ALSA „%s“. Nahlaste " "prosím tento problém vývojářům ALSA." #: spa/plugins/alsa/acp/channelmap.h:457 msgid "(invalid)" msgstr "(neplatné)" #: spa/plugins/alsa/acp/compat.c:189 msgid "Built-in Audio" msgstr "Vnitřní zvukový systém" #: spa/plugins/alsa/acp/compat.c:194 msgid "Modem" msgstr "Modem" #: spa/plugins/bluez5/bluez5-device.c:1247 msgid "Audio Gateway (A2DP Source & HSP/HFP AG)" msgstr "Zvuková brána (A2DP Source & HSP/HFP AG)" #: spa/plugins/bluez5/bluez5-device.c:1272 #, c-format msgid "High Fidelity Playback (A2DP Sink, codec %s)" msgstr "High Fidelity Playback (A2DP Sink, kodek %s)" #: spa/plugins/bluez5/bluez5-device.c:1275 #, c-format msgid "High Fidelity Duplex (A2DP Source/Sink, codec %s)" msgstr "High Fidelity Duplex (A2DP Source/Sink, kodek %s)" #: spa/plugins/bluez5/bluez5-device.c:1283 msgid "High Fidelity Playback (A2DP Sink)" msgstr "High Fidelity Playback (A2DP Sink)" #: spa/plugins/bluez5/bluez5-device.c:1285 msgid "High Fidelity Duplex (A2DP Source/Sink)" msgstr "High Fidelity Duplex (A2DP Source/Sink)" #: spa/plugins/bluez5/bluez5-device.c:1322 #, c-format #| msgid "High Fidelity Playback (A2DP Sink, codec %s)" msgid "High Fidelity Playback (BAP Sink, codec %s)" msgstr "High Fidelity Playback (BAP Sink, kodek %s)" #: spa/plugins/bluez5/bluez5-device.c:1326 #, c-format #| msgid "High Fidelity Duplex (A2DP Source/Sink, codec %s)" msgid "High Fidelity Input (BAP Source, codec %s)" msgstr "High Fidelity Input (BAP Source, kodek %s)" #: spa/plugins/bluez5/bluez5-device.c:1330 #, c-format #| msgid "High Fidelity Duplex (A2DP Source/Sink, codec %s)" msgid "High Fidelity Duplex (BAP Source/Sink, codec %s)" msgstr "High Fidelity Duplex (BAP Source/Sink, kodek %s)" #: spa/plugins/bluez5/bluez5-device.c:1359 #, c-format msgid "Headset Head Unit (HSP/HFP, codec %s)" msgstr "Jednotka náhlavní soupravy (HSP/HFP, kodek %s)" #: spa/plugins/bluez5/bluez5-device.c:1364 msgid "Headset Head Unit (HSP/HFP)" msgstr "Jednotka náhlavní soupravy (HSP/HFP)" #: spa/plugins/bluez5/bluez5-device.c:1443 #: spa/plugins/bluez5/bluez5-device.c:1448 #: spa/plugins/bluez5/bluez5-device.c:1455 #: spa/plugins/bluez5/bluez5-device.c:1461 #: spa/plugins/bluez5/bluez5-device.c:1467 #: spa/plugins/bluez5/bluez5-device.c:1473 #: spa/plugins/bluez5/bluez5-device.c:1479 #: spa/plugins/bluez5/bluez5-device.c:1485 #: spa/plugins/bluez5/bluez5-device.c:1491 msgid "Handsfree" msgstr "Handsfree" #: spa/plugins/bluez5/bluez5-device.c:1449 #| msgid "Handsfree" msgid "Handsfree (HFP)" msgstr "Handsfree (HFP)" #: spa/plugins/bluez5/bluez5-device.c:1466 msgid "Headphone" msgstr "Sluchátko" #: spa/plugins/bluez5/bluez5-device.c:1472 msgid "Portable" msgstr "Přenosné zařízení" #: spa/plugins/bluez5/bluez5-device.c:1478 msgid "Car" msgstr "Auto" #: spa/plugins/bluez5/bluez5-device.c:1484 msgid "HiFi" msgstr "HiFi" #: spa/plugins/bluez5/bluez5-device.c:1490 msgid "Phone" msgstr "Telefon" #: spa/plugins/bluez5/bluez5-device.c:1497 msgid "Bluetooth" msgstr "Bluetooth" #: spa/plugins/bluez5/bluez5-device.c:1498 #| msgid "Bluetooth" msgid "Bluetooth (HFP)" msgstr "Bluetooth (HFP)" pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/po/da.po000066400000000000000000000464251511204443500220530ustar00rootroot00000000000000# Danish translation for PipeWire. # Copyright (C) 2019 PipeWire's COPYRIGHT HOLDER # This file is distributed under the same license as the PipeWire package. # scootergrisen, 2019, 2021. msgid "" msgstr "" "Project-Id-Version: PipeWire master\n" "Report-Msgid-Bugs-To: " "https://gitlab.freedesktop.org/pipewire/pipewire/issues/new\n" "POT-Creation-Date: 2021-04-18 16:54+0800\n" "PO-Revision-Date: 2021-04-19 20:26+0200\n" "Last-Translator: scootergrisen\n" "Language-Team: Danish\n" "Language: da\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #: src/daemon/pipewire.c:43 #, c-format msgid "" "%s [options]\n" " -h, --help Show this help\n" " --version Show version\n" " -c, --config Load config (Default %s)\n" msgstr "" "%s [tilvalg]\n" " -h, --help Vis denne hjælp\n" " --version Vis version\n" " -c, --config Indlæs konfiguration (standard %s)\n" #: src/daemon/pipewire.desktop.in:4 msgid "PipeWire Media System" msgstr "PipeWire-mediesystem" #: src/daemon/pipewire.desktop.in:5 msgid "Start the PipeWire Media System" msgstr "Start PipeWire-mediesystemet" #: src/examples/media-session/alsa-monitor.c:526 #: spa/plugins/alsa/acp/compat.c:187 msgid "Built-in Audio" msgstr "Indbygget lyd" #: src/examples/media-session/alsa-monitor.c:530 #: spa/plugins/alsa/acp/compat.c:192 msgid "Modem" msgstr "Modem" #: src/examples/media-session/alsa-monitor.c:539 msgid "Unknown device" msgstr "Ukendt enhed" #: src/tools/pw-cat.c:991 #, c-format msgid "" "%s [options] \n" " -h, --help Show this help\n" " --version Show version\n" " -v, --verbose Enable verbose operations\n" "\n" msgstr "" "%s [tilvalg] \n" " -h, --help Vis denne hjælp\n" " --version Vis version\n" " -v, --verbose Aktivér uddybende handlinger\n" "\n" #: src/tools/pw-cat.c:998 #, c-format msgid "" " -R, --remote Remote daemon name\n" " --media-type Set media type (default %s)\n" " --media-category Set media category (default %s)\n" " --media-role Set media role (default %s)\n" " --target Set node target (default %s)\n" " 0 means don't link\n" " --latency Set node latency (default %s)\n" " Xunit (unit = s, ms, us, ns)\n" " or direct samples (256)\n" " the rate is the one of the source " "file\n" " --list-targets List available targets for --target\n" "\n" msgstr "" " -R, --remote Navn for fjerndæmon\n" " --media-type Indstil medietype (standard %s)\n" " --media-category Indstil mediekategori (standard %s)\n" " --media-role Indstil medierolle (default %s)\n" " --target Indstil mål for knudepunkt (standard " "%s)\n" " 0 betyder ingen henvisning\n" " --latency Indstil latens for knudepunkt " "(standard %s)\n" " Xenhed (enhed = s, ms, us, ns)\n" " eller direkte datapunkter (256)\n" " hastigheden stammer fra kildefilen\n" " --list-targets Vis tilgængelige mål for --target\n" "\n" #: src/tools/pw-cat.c:1016 #, c-format msgid "" " --rate Sample rate (req. for rec) (default " "%u)\n" " --channels Number of channels (req. for rec) " "(default %u)\n" " --channel-map Channel map\n" " one of: \"stereo\", " "\"surround-51\",... or\n" " comma separated list of channel " "names: eg. \"FL,FR\"\n" " --format Sample format %s (req. for rec) " "(default %s)\n" " --volume Stream volume 0-1.0 (default %.3f)\n" " -q --quality Resampler quality (0 - 15) (default " "%d)\n" "\n" msgstr "" " --rate Målefrekvens (kræves ved optagelse) " "(standard %u)\n" " --channels Antal kanaler (kræves ved optagelse) " "(standard %u)\n" " --channel-map Kanalkort\n" " en af: \"stereo\", \"surround-51\", " "... eller\n" " kommasepareret liste over " "kanalnavne: f.eks. \"FL,FR\"\n" " --format Måleformat %s (kræves ved optagelse) " "(standard %s)\n" " --volume Lydstyrke for stream 0-1.0 (standard " "%.3f)\n" " -q --quality Kvalitet for resampler (0-15) " "(standard %d)\n" "\n" #: src/tools/pw-cat.c:1033 msgid "" " -p, --playback Playback mode\n" " -r, --record Recording mode\n" " -m, --midi Midi mode\n" "\n" msgstr "" " -p, --playback Afspilningstilstand\n" " -r, --record Optagelsestilstand\n" " -m, --midi Midi-tilstand\n" "\n" #: src/tools/pw-cli.c:2932 #, c-format msgid "" "%s [options] [command]\n" " -h, --help Show this help\n" " --version Show version\n" " -d, --daemon Start as daemon (Default false)\n" " -r, --remote Remote daemon name\n" "\n" msgstr "" "%s [tilvalg] [kommando]\n" " -h, --help Vis denne hjælp\n" " --version Vis version\n" " -d, --daemon Start som dæmon (standard false)\n" " -r, --remote Navn for fjerndæmon\n" "\n" #: spa/plugins/alsa/acp/acp.c:290 msgid "Pro Audio" msgstr "Pro Audio" #: spa/plugins/alsa/acp/acp.c:411 spa/plugins/alsa/acp/alsa-mixer.c:4704 #: spa/plugins/bluez5/bluez5-device.c:1000 msgid "Off" msgstr "Fra" #: spa/plugins/alsa/acp/channelmap.h:466 msgid "(invalid)" msgstr "(ugyldig)" #: spa/plugins/alsa/acp/alsa-mixer.c:2709 msgid "Input" msgstr "Input" #: spa/plugins/alsa/acp/alsa-mixer.c:2710 msgid "Docking Station Input" msgstr "Input for dokingstation" #: spa/plugins/alsa/acp/alsa-mixer.c:2711 msgid "Docking Station Microphone" msgstr "Mikrofon for dokingstation" #: spa/plugins/alsa/acp/alsa-mixer.c:2712 msgid "Docking Station Line In" msgstr "Line in for dokingstation" #: spa/plugins/alsa/acp/alsa-mixer.c:2713 #: spa/plugins/alsa/acp/alsa-mixer.c:2804 msgid "Line In" msgstr "Line in" #: spa/plugins/alsa/acp/alsa-mixer.c:2714 #: spa/plugins/alsa/acp/alsa-mixer.c:2798 #: spa/plugins/bluez5/bluez5-device.c:1145 msgid "Microphone" msgstr "Mikrofon" #: spa/plugins/alsa/acp/alsa-mixer.c:2715 #: spa/plugins/alsa/acp/alsa-mixer.c:2799 msgid "Front Microphone" msgstr "Mikrofon foran" #: spa/plugins/alsa/acp/alsa-mixer.c:2716 #: spa/plugins/alsa/acp/alsa-mixer.c:2800 msgid "Rear Microphone" msgstr "Mikrofon bagved" #: spa/plugins/alsa/acp/alsa-mixer.c:2717 msgid "External Microphone" msgstr "Ekstern mikrofon" #: spa/plugins/alsa/acp/alsa-mixer.c:2718 #: spa/plugins/alsa/acp/alsa-mixer.c:2802 msgid "Internal Microphone" msgstr "Intern mikrofon" #: spa/plugins/alsa/acp/alsa-mixer.c:2719 #: spa/plugins/alsa/acp/alsa-mixer.c:2805 msgid "Radio" msgstr "Radio" #: spa/plugins/alsa/acp/alsa-mixer.c:2720 #: spa/plugins/alsa/acp/alsa-mixer.c:2806 msgid "Video" msgstr "Video" #: spa/plugins/alsa/acp/alsa-mixer.c:2721 msgid "Automatic Gain Control" msgstr "Automatisk styring af gain" #: spa/plugins/alsa/acp/alsa-mixer.c:2722 msgid "No Automatic Gain Control" msgstr "Ingen automatisk styring af gain" #: spa/plugins/alsa/acp/alsa-mixer.c:2723 msgid "Boost" msgstr "Boost" #: spa/plugins/alsa/acp/alsa-mixer.c:2724 msgid "No Boost" msgstr "Ingen boost" #: spa/plugins/alsa/acp/alsa-mixer.c:2725 msgid "Amplifier" msgstr "Forstærker" #: spa/plugins/alsa/acp/alsa-mixer.c:2726 msgid "No Amplifier" msgstr "Ingen forstærker" #: spa/plugins/alsa/acp/alsa-mixer.c:2727 msgid "Bass Boost" msgstr "Bas boost" #: spa/plugins/alsa/acp/alsa-mixer.c:2728 msgid "No Bass Boost" msgstr "Ingen bas boost" #: spa/plugins/alsa/acp/alsa-mixer.c:2729 #: spa/plugins/bluez5/bluez5-device.c:1150 msgid "Speaker" msgstr "Højttaler" #: spa/plugins/alsa/acp/alsa-mixer.c:2730 #: spa/plugins/alsa/acp/alsa-mixer.c:2808 msgid "Headphones" msgstr "Hovedtelefoner" #: spa/plugins/alsa/acp/alsa-mixer.c:2797 msgid "Analog Input" msgstr "Analog input" #: spa/plugins/alsa/acp/alsa-mixer.c:2801 msgid "Dock Microphone" msgstr "Dokmikrofon" #: spa/plugins/alsa/acp/alsa-mixer.c:2803 msgid "Headset Microphone" msgstr "Mikrofon for headset" #: spa/plugins/alsa/acp/alsa-mixer.c:2807 msgid "Analog Output" msgstr "Analog output" #: spa/plugins/alsa/acp/alsa-mixer.c:2809 msgid "Headphones 2" msgstr "Hovedtelefoner 2" #: spa/plugins/alsa/acp/alsa-mixer.c:2810 msgid "Headphones Mono Output" msgstr "Monooutput for hovedtelefoner" #: spa/plugins/alsa/acp/alsa-mixer.c:2811 msgid "Line Out" msgstr "Line out" #: spa/plugins/alsa/acp/alsa-mixer.c:2812 msgid "Analog Mono Output" msgstr "Analog monooutput" #: spa/plugins/alsa/acp/alsa-mixer.c:2813 msgid "Speakers" msgstr "Højttalere" #: spa/plugins/alsa/acp/alsa-mixer.c:2814 msgid "HDMI / DisplayPort" msgstr "HDMI/DisplayPort" #: spa/plugins/alsa/acp/alsa-mixer.c:2815 msgid "Digital Output (S/PDIF)" msgstr "Digital output (S/PDIF)" #: spa/plugins/alsa/acp/alsa-mixer.c:2816 msgid "Digital Input (S/PDIF)" msgstr "Digital input (S/PDIF)" #: spa/plugins/alsa/acp/alsa-mixer.c:2817 msgid "Multichannel Input" msgstr "Multikanal input" #: spa/plugins/alsa/acp/alsa-mixer.c:2818 msgid "Multichannel Output" msgstr "Multikanal output" #: spa/plugins/alsa/acp/alsa-mixer.c:2819 msgid "Game Output" msgstr "Spilouput" #: spa/plugins/alsa/acp/alsa-mixer.c:2820 #: spa/plugins/alsa/acp/alsa-mixer.c:2821 msgid "Chat Output" msgstr "Chatoutput" #: spa/plugins/alsa/acp/alsa-mixer.c:2822 msgid "Chat Input" msgstr "Chatinput" #: spa/plugins/alsa/acp/alsa-mixer.c:2823 msgid "Virtual Surround 7.1" msgstr "Virtuel surround 7.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4527 msgid "Analog Mono" msgstr "Analog mono" #: spa/plugins/alsa/acp/alsa-mixer.c:4528 msgid "Analog Mono (Left)" msgstr "Analog mono (venstre)" #: spa/plugins/alsa/acp/alsa-mixer.c:4529 msgid "Analog Mono (Right)" msgstr "Analog mono (højre)" #. Note: Not translated to "Analog Stereo Input", because the source #. * name gets "Input" appended to it automatically, so adding "Input" #. * here would lead to the source name to become "Analog Stereo Input #. * Input". The same logic applies to analog-stereo-output, #. * multichannel-input and multichannel-output. #: spa/plugins/alsa/acp/alsa-mixer.c:4530 #: spa/plugins/alsa/acp/alsa-mixer.c:4538 #: spa/plugins/alsa/acp/alsa-mixer.c:4539 msgid "Analog Stereo" msgstr "Analog stereo" #: spa/plugins/alsa/acp/alsa-mixer.c:4531 msgid "Mono" msgstr "Mono" #: spa/plugins/alsa/acp/alsa-mixer.c:4532 msgid "Stereo" msgstr "Stereo" #: spa/plugins/alsa/acp/alsa-mixer.c:4540 #: spa/plugins/alsa/acp/alsa-mixer.c:4698 #: spa/plugins/bluez5/bluez5-device.c:1135 msgid "Headset" msgstr "Headset" #: spa/plugins/alsa/acp/alsa-mixer.c:4541 #: spa/plugins/alsa/acp/alsa-mixer.c:4699 msgid "Speakerphone" msgstr "Højttalertelefon" #: spa/plugins/alsa/acp/alsa-mixer.c:4542 #: spa/plugins/alsa/acp/alsa-mixer.c:4543 msgid "Multichannel" msgstr "Multikanal" #: spa/plugins/alsa/acp/alsa-mixer.c:4544 msgid "Analog Surround 2.1" msgstr "Analog surround 2.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4545 msgid "Analog Surround 3.0" msgstr "Analog surround 3.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4546 msgid "Analog Surround 3.1" msgstr "Analog surround 3.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4547 msgid "Analog Surround 4.0" msgstr "Analog surround 4.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4548 msgid "Analog Surround 4.1" msgstr "Analog surround 4.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4549 msgid "Analog Surround 5.0" msgstr "Analog surround 5.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4550 msgid "Analog Surround 5.1" msgstr "Analog surround 5.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4551 msgid "Analog Surround 6.0" msgstr "Analog surround 6.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4552 msgid "Analog Surround 6.1" msgstr "Analog surround 6.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4553 msgid "Analog Surround 7.0" msgstr "Analog surround 7.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4554 msgid "Analog Surround 7.1" msgstr "Analog surround 7.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4555 msgid "Digital Stereo (IEC958)" msgstr "Digital stereo (IEC958)" #: spa/plugins/alsa/acp/alsa-mixer.c:4556 msgid "Digital Surround 4.0 (IEC958/AC3)" msgstr "Digital surround 4.0 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4557 msgid "Digital Surround 5.1 (IEC958/AC3)" msgstr "Digital surround 5.1 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4558 msgid "Digital Surround 5.1 (IEC958/DTS)" msgstr "Digital surround 5.1 (IEC958/DTS)" #: spa/plugins/alsa/acp/alsa-mixer.c:4559 msgid "Digital Stereo (HDMI)" msgstr "Digital stereo (HDMI)" #: spa/plugins/alsa/acp/alsa-mixer.c:4560 msgid "Digital Surround 5.1 (HDMI)" msgstr "Digital surround 5.1 (HDMI)" #: spa/plugins/alsa/acp/alsa-mixer.c:4561 msgid "Chat" msgstr "Chat" #: spa/plugins/alsa/acp/alsa-mixer.c:4562 msgid "Game" msgstr "Spil" #: spa/plugins/alsa/acp/alsa-mixer.c:4696 msgid "Analog Mono Duplex" msgstr "Analog mono dupleks" #: spa/plugins/alsa/acp/alsa-mixer.c:4697 msgid "Analog Stereo Duplex" msgstr "Analog stereo dupleks" #: spa/plugins/alsa/acp/alsa-mixer.c:4700 msgid "Digital Stereo Duplex (IEC958)" msgstr "Digital stereo dupleks (IEC958)" #: spa/plugins/alsa/acp/alsa-mixer.c:4701 msgid "Multichannel Duplex" msgstr "Multikanal dupleks" #: spa/plugins/alsa/acp/alsa-mixer.c:4702 msgid "Stereo Duplex" msgstr "Stereo dupleks" #: spa/plugins/alsa/acp/alsa-mixer.c:4703 msgid "Mono Chat + 7.1 Surround" msgstr "Monochat + 7.1 surround" #: spa/plugins/alsa/acp/alsa-mixer.c:4806 #, c-format msgid "%s Output" msgstr "%s-output" #: spa/plugins/alsa/acp/alsa-mixer.c:4813 #, c-format msgid "%s Input" msgstr "%s-input" #: spa/plugins/alsa/acp/alsa-util.c:1175 spa/plugins/alsa/acp/alsa-util.c:1269 #, c-format msgid "" "snd_pcm_avail() returned a value that is exceptionally large: %lu byte (%lu " "ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_avail() returned a value that is exceptionally large: %lu bytes (%lu " "ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "snd_pcm_avail() returnerede en værdi som er usædvanlig stor: %lu byte (%lu " "ms).\n" "Det er højst sandsynligt en fejl i ALSA-driveren '%s'. Rapportér det " "venligst til ALSA-udviklerne." msgstr[1] "" "snd_pcm_avail() returnerede en værdi som er usædvanlig stor: %lu bytes (%lu " "ms).\n" "Det er højst sandsynligt en fejl i ALSA-driveren '%s'. Rapportér det " "venligst til ALSA-udviklerne." #: spa/plugins/alsa/acp/alsa-util.c:1241 #, c-format msgid "" "snd_pcm_delay() returned a value that is exceptionally large: %li byte (%s" "%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_delay() returned a value that is exceptionally large: %li bytes (%s" "%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "snd_pcm_delay() returnerede en værdi som er usædvanlig stor: %li byte (%s%lu " "ms).\n" "Det er højst sandsynligt en fejl i ALSA-driveren '%s'. Rapportér det " "venligst til ALSA-udviklerne." msgstr[1] "" "snd_pcm_delay() returnerede en værdi som er usædvanlig stor: %li bytes (%s" "%lu ms).\n" "Det er højst sandsynligt en fejl i ALSA-driveren '%s'. Rapportér det " "venligst til ALSA-udviklerne." #: spa/plugins/alsa/acp/alsa-util.c:1288 #, c-format msgid "" "snd_pcm_avail_delay() returned strange values: delay %lu is less than avail " "%lu.\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr "" "snd_pcm_avail_delay() returnerede underlige værdier: forsinkelsen %lu er " "mindre end tilgængelige %lu.\n" "Det er højst sandsynligt en fejl i ALSA-driveren '%s'. Rapportér det " "venligst til ALSA-udviklerne." #: spa/plugins/alsa/acp/alsa-util.c:1331 #, c-format msgid "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu byte " "(%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu bytes " "(%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "snd_pcm_mmap_begin() returnerede en værdi som er usædvanlig stor: %lu byte " "(%lu ms).\n" "Det er højst sandsynligt en fejl i ALSA-driveren '%s'. Rapportér det " "venligst til ALSA-udviklerne." msgstr[1] "" "snd_pcm_mmap_begin() returnerede en værdi som er usædvanlig stor: %lu bytes " "(%lu ms).\n" "Det er højst sandsynligt en fejl i ALSA-driveren '%s'. Rapportér det " "venligst til ALSA-udviklerne." #: spa/plugins/bluez5/bluez5-device.c:1010 msgid "Audio Gateway (A2DP Source & HSP/HFP AG)" msgstr "Lydgateway (A2DP-kilde og HSP/HFP AG)" #: spa/plugins/bluez5/bluez5-device.c:1033 #, c-format msgid "High Fidelity Playback (A2DP Sink, codec %s)" msgstr "High fidelity afspilning (A2DP-sink, codec %s)" #: spa/plugins/bluez5/bluez5-device.c:1035 #, c-format msgid "High Fidelity Duplex (A2DP Source/Sink, codec %s)" msgstr "High fidelity dupleks (A2DP-kilde/-sink, codec %s)" #: spa/plugins/bluez5/bluez5-device.c:1041 msgid "High Fidelity Playback (A2DP Sink)" msgstr "High fidelity afspilning (A2DP-sink)" #: spa/plugins/bluez5/bluez5-device.c:1043 msgid "High Fidelity Duplex (A2DP Source/Sink)" msgstr "High fidelity dupleks (A2DP-kilde/-sink)" #: spa/plugins/bluez5/bluez5-device.c:1070 #, c-format msgid "Headset Head Unit (HSP/HFP, codec %s)" msgstr "Hovedenhed for headset (HSP/HFP, codec %s)" #: spa/plugins/bluez5/bluez5-device.c:1074 msgid "Headset Head Unit (HSP/HFP)" msgstr "Hovedenhed for headset (HSP/HFP)" #: spa/plugins/bluez5/bluez5-device.c:1140 msgid "Handsfree" msgstr "Håndfri" #: spa/plugins/bluez5/bluez5-device.c:1155 msgid "Headphone" msgstr "Hovedtelefon" #: spa/plugins/bluez5/bluez5-device.c:1160 msgid "Portable" msgstr "Bærbar" #: spa/plugins/bluez5/bluez5-device.c:1165 msgid "Car" msgstr "Bil" #: spa/plugins/bluez5/bluez5-device.c:1170 msgid "HiFi" msgstr "HiFi" #: spa/plugins/bluez5/bluez5-device.c:1175 msgid "Phone" msgstr "Telefon" #: spa/plugins/bluez5/bluez5-device.c:1181 msgid "Bluetooth" msgstr "Bluetooth" pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/po/de.po000066400000000000000000000553421511204443500220550ustar00rootroot00000000000000# translation of pipewire.master-tx.de.po to # German translation of pipewire # Copyright (C) 2008 pipewire # This file is distributed under the same license as the pipewire package. # # # Fabian Affolter , 2008-2009. # Micha Pietsch , 2008, 2009. # Hedda Peters , 2009, 2012. # Mario Blättermann , 2016. # Jürgen Benvenuti , 2024. # Christian Kirbach , 2024. # msgid "" msgstr "" "Project-Id-Version: pipewire.master-tx.de\n" "Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/pipewire/-/" "issues\n" "POT-Creation-Date: 2024-09-27 03:27+0000\n" "PO-Revision-Date: 2024-09-27 11:25+0200\n" "Last-Translator: Christian Kirbach \n" "Language-Team: German \n" "Language: de\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" "X-Generator: Poedit 3.4.4\n" #: src/daemon/pipewire.c:29 #, c-format msgid "" "%s [options]\n" " -h, --help Show this help\n" " -v, --verbose Increase verbosity by one level\n" " --version Show version\n" " -c, --config Load config (Default %s)\n" " -P --properties Set context properties\n" msgstr "" "%s [Optionen]\n" " -h, --help Diese Hilfe ausgeben\n" " -v, --verbose Ausführlichere Ausgaben\n" " --version Version anzeigen\n" " -c, --config Konfiguration laden (Voreinstellung " "%s)\n" " -P --properties Kontext-Eigenschaften festlegen\n" #: src/daemon/pipewire.desktop.in:4 msgid "PipeWire Media System" msgstr "PipeWire-Mediensystem" #: src/daemon/pipewire.desktop.in:5 msgid "Start the PipeWire Media System" msgstr "Das PipeWire-Mediensystem starten" #: src/modules/module-protocol-pulse/modules/module-tunnel-sink.c:159 #: src/modules/module-protocol-pulse/modules/module-tunnel-source.c:159 #, c-format msgid "Tunnel to %s%s%s" msgstr "Tunnel zu %s%s%s" #: src/modules/module-fallback-sink.c:40 msgid "Dummy Output" msgstr "Schein-Ausgabe" #: src/modules/module-pulse-tunnel.c:777 #, c-format msgid "Tunnel for %s@%s" msgstr "Tunnel für %s@%s" #: src/modules/module-zeroconf-discover.c:320 msgid "Unknown device" msgstr "Unbekanntes Gerät" #: src/modules/module-zeroconf-discover.c:332 #, c-format msgid "%s on %s@%s" msgstr "%s auf %s@%s" #: src/modules/module-zeroconf-discover.c:336 #, c-format msgid "%s on %s" msgstr "%s auf %s" #: src/tools/pw-cat.c:973 #, c-format msgid "" "%s [options] [|-]\n" " -h, --help Show this help\n" " --version Show version\n" " -v, --verbose Enable verbose operations\n" "\n" msgstr "" "%s [Optionen] [|-]\n" " -h, --help Diese Hilfe ausgeben\n" " --version Version anzeigen\n" " -v, --verbose Ausführliche Vorgänge einschalten\n" "\n" "\n" #: src/tools/pw-cat.c:980 #, c-format msgid "" " -R, --remote Remote daemon name\n" " --media-type Set media type (default %s)\n" " --media-category Set media category (default %s)\n" " --media-role Set media role (default %s)\n" " --target Set node target serial or name " "(default %s)\n" " 0 means don't link\n" " --latency Set node latency (default %s)\n" " Xunit (unit = s, ms, us, ns)\n" " or direct samples (256)\n" " the rate is the one of the source " "file\n" " -P --properties Set node properties\n" "\n" msgstr "" " -R, --remote Name des entfernten Daemon\n" " --media-type Medientyp festlegen (Vorgabe %s)\n" " --media-category Medienkategorie festlegen (Vorgabe " "%s)\n" " --media-role Medienrolle festlegen (Vorgabe %s)\n" " --target Seriennummer oder Name des " "Knotenziels festlegen (Vorgabe %s)\n" " 0 bedeutet keine Verbindung\n" " --latency Knotenlatenz festlegen (Vorgabe %s)\n" " Xunit (unit = s, ms, us, ns)\n" " oder direkte Abtastung (256)\n" " die Rate entspricht der " "Quelldatei\n" " -P --properties Knoteneigenschaften festlegen\n" "\n" #: src/tools/pw-cat.c:998 #, c-format msgid "" " --rate Sample rate (req. for rec) (default " "%u)\n" " --channels Number of channels (req. for rec) " "(default %u)\n" " --channel-map Channel map\n" " one of: \"stereo\", " "\"surround-51\",... or\n" " comma separated list of channel " "names: eg. \"FL,FR\"\n" " --format Sample format %s (req. for rec) " "(default %s)\n" " --volume Stream volume 0-1.0 (default %.3f)\n" " -q --quality Resampler quality (0 - 15) (default " "%d)\n" " -a, --raw RAW mode\n" "\n" msgstr "" " --rate Abtastrate (notw. für Aufzeichn.) " "(Vorgabe %u)\n" " --channels Anzahl der Kanäle (notw. für " "Aufzeichn.) (Vorgabe %u)\n" " --channel-map Kanalabbildung\n" " eines von: »stereo«, " "»surround-51«, … oder\n" " eine mit Kommata getrennte Liste " "mit Kanalnamen: z.B. »FL,FR«\n" " --format Abtastformat %s (notw. für " "Aufzeichn.) (Vorgabe %s)\n" " --volume Strom-Lautstärke 0-1.0 (Vorgabe " "%.3f)\n" " -q --quality Qualität der Neu-Abtastung (0 - 15) " "(Vorgabe %d)\n" " -a, --raw RAW-Modus\n" "\n" #: src/tools/pw-cat.c:1016 msgid "" " -p, --playback Playback mode\n" " -r, --record Recording mode\n" " -m, --midi Midi mode\n" " -d, --dsd DSD mode\n" " -o, --encoded Encoded mode\n" "\n" msgstr "" " -p, --playback Wiedergabe-Modus\n" " -r, --record Aufnahme-Modus\n" " -m, --midi Midi-Modus\n" " -d, --dsd DSD-Modus\n" " -o, --encoded Codieren-Modus\n" "\n" #: src/tools/pw-cli.c:2318 #, c-format msgid "" "%s [options] [command]\n" " -h, --help Show this help\n" " --version Show version\n" " -d, --daemon Start as daemon (Default false)\n" " -r, --remote Remote daemon name\n" " -m, --monitor Monitor activity\n" "\n" msgstr "" "%s [Optionen] [Befehl]\n" " -h, --help Diese Hilfe ausgeben\n" " --version Version anzeigen\n" " -d, --daemon Als Daemon starten (Vorgabe: nein)\n" " -r, --remote Name des entfernten Daemon\n" " -m, --monitor Aktivitäten überwachen\n" "\n" #: spa/plugins/alsa/acp/acp.c:327 msgid "Pro Audio" msgstr "Pro Audio" #: spa/plugins/alsa/acp/acp.c:487 spa/plugins/alsa/acp/alsa-mixer.c:4633 #: spa/plugins/bluez5/bluez5-device.c:1696 msgid "Off" msgstr "Aus" #: spa/plugins/alsa/acp/alsa-mixer.c:2652 msgid "Input" msgstr "Eingabe" #: spa/plugins/alsa/acp/alsa-mixer.c:2653 msgid "Docking Station Input" msgstr "Eingabe über Docking-Station" #: spa/plugins/alsa/acp/alsa-mixer.c:2654 msgid "Docking Station Microphone" msgstr "Mikrofon der Docking-Station" #: spa/plugins/alsa/acp/alsa-mixer.c:2655 msgid "Docking Station Line In" msgstr "Line-Eingang der Docking-Station" #: spa/plugins/alsa/acp/alsa-mixer.c:2656 #: spa/plugins/alsa/acp/alsa-mixer.c:2747 msgid "Line In" msgstr "Line-Eingang" #: spa/plugins/alsa/acp/alsa-mixer.c:2657 #: spa/plugins/alsa/acp/alsa-mixer.c:2741 #: spa/plugins/bluez5/bluez5-device.c:1984 msgid "Microphone" msgstr "Mikrofon" #: spa/plugins/alsa/acp/alsa-mixer.c:2658 #: spa/plugins/alsa/acp/alsa-mixer.c:2742 msgid "Front Microphone" msgstr "Vorderes Mikrofon" #: spa/plugins/alsa/acp/alsa-mixer.c:2659 #: spa/plugins/alsa/acp/alsa-mixer.c:2743 msgid "Rear Microphone" msgstr "Rückwärtiges Mikrofon" #: spa/plugins/alsa/acp/alsa-mixer.c:2660 msgid "External Microphone" msgstr "Externes Mikrofon" #: spa/plugins/alsa/acp/alsa-mixer.c:2661 #: spa/plugins/alsa/acp/alsa-mixer.c:2745 msgid "Internal Microphone" msgstr "Internes Mikrofon" #: spa/plugins/alsa/acp/alsa-mixer.c:2662 #: spa/plugins/alsa/acp/alsa-mixer.c:2748 msgid "Radio" msgstr "Radio" #: spa/plugins/alsa/acp/alsa-mixer.c:2663 #: spa/plugins/alsa/acp/alsa-mixer.c:2749 msgid "Video" msgstr "Video" #: spa/plugins/alsa/acp/alsa-mixer.c:2664 msgid "Automatic Gain Control" msgstr "Automatische Verstärkungsregelung" #: spa/plugins/alsa/acp/alsa-mixer.c:2665 msgid "No Automatic Gain Control" msgstr "Keine automatische Verstärkungsregelung" #: spa/plugins/alsa/acp/alsa-mixer.c:2666 msgid "Boost" msgstr "Boost" #: spa/plugins/alsa/acp/alsa-mixer.c:2667 msgid "No Boost" msgstr "Kein Boost" #: spa/plugins/alsa/acp/alsa-mixer.c:2668 msgid "Amplifier" msgstr "Verstärker" #: spa/plugins/alsa/acp/alsa-mixer.c:2669 msgid "No Amplifier" msgstr "Kein Verstärker" #: spa/plugins/alsa/acp/alsa-mixer.c:2670 msgid "Bass Boost" msgstr "Bassverstärkung" #: spa/plugins/alsa/acp/alsa-mixer.c:2671 msgid "No Bass Boost" msgstr "Keine Bassverstärkung" #: spa/plugins/alsa/acp/alsa-mixer.c:2672 #: spa/plugins/bluez5/bluez5-device.c:1990 msgid "Speaker" msgstr "Lautsprecher" #: spa/plugins/alsa/acp/alsa-mixer.c:2673 #: spa/plugins/alsa/acp/alsa-mixer.c:2751 msgid "Headphones" msgstr "Kopfhörer" #: spa/plugins/alsa/acp/alsa-mixer.c:2740 msgid "Analog Input" msgstr "Analoger Eingang" #: spa/plugins/alsa/acp/alsa-mixer.c:2744 msgid "Dock Microphone" msgstr "Mikrofon der Docking-Station" #: spa/plugins/alsa/acp/alsa-mixer.c:2746 msgid "Headset Microphone" msgstr "Mikrofon am Sprechkopfhörer" #: spa/plugins/alsa/acp/alsa-mixer.c:2750 msgid "Analog Output" msgstr "Analoge Ausgabe" #: spa/plugins/alsa/acp/alsa-mixer.c:2752 msgid "Headphones 2" msgstr "Kopfhörer 2" #: spa/plugins/alsa/acp/alsa-mixer.c:2753 msgid "Headphones Mono Output" msgstr "Kopfhörer Mono-Ausgabe" #: spa/plugins/alsa/acp/alsa-mixer.c:2754 msgid "Line Out" msgstr "Line-Ausgang" #: spa/plugins/alsa/acp/alsa-mixer.c:2755 msgid "Analog Mono Output" msgstr "Analoge Mono-Ausgabe" #: spa/plugins/alsa/acp/alsa-mixer.c:2756 msgid "Speakers" msgstr "Lautsprecher" #: spa/plugins/alsa/acp/alsa-mixer.c:2757 msgid "HDMI / DisplayPort" msgstr "HDMI / DisplayPort" #: spa/plugins/alsa/acp/alsa-mixer.c:2758 msgid "Digital Output (S/PDIF)" msgstr "Digitalausgang (S/PDIF)" #: spa/plugins/alsa/acp/alsa-mixer.c:2759 msgid "Digital Input (S/PDIF)" msgstr "Digitaleingang (S/PDIF)" #: spa/plugins/alsa/acp/alsa-mixer.c:2760 msgid "Multichannel Input" msgstr "Mehrkanal-Eingang" #: spa/plugins/alsa/acp/alsa-mixer.c:2761 msgid "Multichannel Output" msgstr "Mehrkanal-Wiedergabe" #: spa/plugins/alsa/acp/alsa-mixer.c:2762 msgid "Game Output" msgstr "Spiel-Ausgang" #: spa/plugins/alsa/acp/alsa-mixer.c:2763 #: spa/plugins/alsa/acp/alsa-mixer.c:2764 msgid "Chat Output" msgstr "Unterhaltungs-Ausgabe" #: spa/plugins/alsa/acp/alsa-mixer.c:2765 msgid "Chat Input" msgstr "Unterhaltungs-Eingang" #: spa/plugins/alsa/acp/alsa-mixer.c:2766 msgid "Virtual Surround 7.1" msgstr "Virtuelles 7.1 Surround" #: spa/plugins/alsa/acp/alsa-mixer.c:4456 msgid "Analog Mono" msgstr "Analoges Mono" #: spa/plugins/alsa/acp/alsa-mixer.c:4457 msgid "Analog Mono (Left)" msgstr "Analoges Mono (links)" #: spa/plugins/alsa/acp/alsa-mixer.c:4458 msgid "Analog Mono (Right)" msgstr "Analoges Mono (rechts)" #. Note: Not translated to "Analog Stereo Input", because the source #. * name gets "Input" appended to it automatically, so adding "Input" #. * here would lead to the source name to become "Analog Stereo Input #. * Input". The same logic applies to analog-stereo-output, #. * multichannel-input and multichannel-output. #: spa/plugins/alsa/acp/alsa-mixer.c:4459 #: spa/plugins/alsa/acp/alsa-mixer.c:4467 #: spa/plugins/alsa/acp/alsa-mixer.c:4468 msgid "Analog Stereo" msgstr "Analoges Stereo" #: spa/plugins/alsa/acp/alsa-mixer.c:4460 msgid "Mono" msgstr "Mono" #: spa/plugins/alsa/acp/alsa-mixer.c:4461 msgid "Stereo" msgstr "Stereo" #: spa/plugins/alsa/acp/alsa-mixer.c:4469 #: spa/plugins/alsa/acp/alsa-mixer.c:4627 #: spa/plugins/bluez5/bluez5-device.c:1972 msgid "Headset" msgstr "Sprechkopfhörer" #: spa/plugins/alsa/acp/alsa-mixer.c:4470 #: spa/plugins/alsa/acp/alsa-mixer.c:4628 msgid "Speakerphone" msgstr "Lautsprechertelefon" #: spa/plugins/alsa/acp/alsa-mixer.c:4471 #: spa/plugins/alsa/acp/alsa-mixer.c:4472 msgid "Multichannel" msgstr "Mehrkanal" #: spa/plugins/alsa/acp/alsa-mixer.c:4473 msgid "Analog Surround 2.1" msgstr "Analog Surround 2.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4474 msgid "Analog Surround 3.0" msgstr "Analog Surround 3.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4475 msgid "Analog Surround 3.1" msgstr "Analog Surround 3.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4476 msgid "Analog Surround 4.0" msgstr "Analog Surround 4.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4477 msgid "Analog Surround 4.1" msgstr "Analog Surround 4.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4478 msgid "Analog Surround 5.0" msgstr "Analog Surround 5.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4479 msgid "Analog Surround 5.1" msgstr "Analog Surround 5.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4480 msgid "Analog Surround 6.0" msgstr "Analog Surround 6.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4481 msgid "Analog Surround 6.1" msgstr "Analog Surround 6.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4482 msgid "Analog Surround 7.0" msgstr "Analog Surround 7.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4483 msgid "Analog Surround 7.1" msgstr "Analog Surround 7.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4484 msgid "Digital Stereo (IEC958)" msgstr "Digitales Stereo (IEC958)" #: spa/plugins/alsa/acp/alsa-mixer.c:4485 msgid "Digital Surround 4.0 (IEC958/AC3)" msgstr "Digital Surround 4.0 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4486 msgid "Digital Surround 5.1 (IEC958/AC3)" msgstr "Digital Surround 5.1 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4487 msgid "Digital Surround 5.1 (IEC958/DTS)" msgstr "Digital Surround 5.1 (IEC958/DTS)" #: spa/plugins/alsa/acp/alsa-mixer.c:4488 msgid "Digital Stereo (HDMI)" msgstr "Digitales Stereo (HDMI)" #: spa/plugins/alsa/acp/alsa-mixer.c:4489 msgid "Digital Surround 5.1 (HDMI)" msgstr "Digital Surround 5.1 (HDMI)" #: spa/plugins/alsa/acp/alsa-mixer.c:4490 msgid "Chat" msgstr "Unterhaltung" #: spa/plugins/alsa/acp/alsa-mixer.c:4491 msgid "Game" msgstr "Spiel" #: spa/plugins/alsa/acp/alsa-mixer.c:4625 msgid "Analog Mono Duplex" msgstr "Analoges Mono Duplex" #: spa/plugins/alsa/acp/alsa-mixer.c:4626 msgid "Analog Stereo Duplex" msgstr "Analoges Stereo Duplex" #: spa/plugins/alsa/acp/alsa-mixer.c:4629 msgid "Digital Stereo Duplex (IEC958)" msgstr "Digitales Stereo Duplex (IEC958)" #: spa/plugins/alsa/acp/alsa-mixer.c:4630 msgid "Multichannel Duplex" msgstr "Mehrkanal-Duplex" #: spa/plugins/alsa/acp/alsa-mixer.c:4631 msgid "Stereo Duplex" msgstr "Stereo Duplex" #: spa/plugins/alsa/acp/alsa-mixer.c:4632 msgid "Mono Chat + 7.1 Surround" msgstr "Mono-Unterhaltung + 7.1 Surround" #: spa/plugins/alsa/acp/alsa-mixer.c:4733 #, c-format msgid "%s Output" msgstr "%s-Ausgabe" #: spa/plugins/alsa/acp/alsa-mixer.c:4741 #, c-format msgid "%s Input" msgstr "%s-Eingang" #: spa/plugins/alsa/acp/alsa-util.c:1231 spa/plugins/alsa/acp/alsa-util.c:1325 #, c-format msgid "" "snd_pcm_avail() returned a value that is exceptionally large: %lu byte (%lu " "ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_avail() returned a value that is exceptionally large: %lu bytes (%lu " "ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "snd_pcm_avail() gab einen Wert zurück, der außerordentlich groß ist: %lu " "Byte (%lu ms).\n" "Dies ist wahrscheinlich ein Fehler im ALSA-Treiber »%s«. Bitte melden Sie " "dieses Problem den ALSA-Entwicklern." msgstr[1] "" "snd_pcm_avail() gab einen Wert zurück, der außerordentlich groß ist: %lu " "Bytes (%lu ms).\n" "Dies ist wahrscheinlich ein Fehler im ALSA-Treiber »%s«. Bitte melden Sie " "dieses Problem den ALSA-Entwicklern." #: spa/plugins/alsa/acp/alsa-util.c:1297 #, c-format msgid "" "snd_pcm_delay() returned a value that is exceptionally large: %li byte " "(%s%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_delay() returned a value that is exceptionally large: %li bytes " "(%s%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "snd_pcm_delay() gab einen Wert zurück, der außerordentlich groß ist: %li " "Byte (%s%lu ms).\n" "Dies ist wahrscheinlich ein Fehler im ALSA-Treiber »%s«. Bitte melden Sie " "dieses Problem den ALSA-Entwicklern." msgstr[1] "" "snd_pcm_delay() gab einen Wert zurück, der außerordentlich groß ist: %li " "Bytes (%s%lu ms).\n" "Dies ist wahrscheinlich ein Fehler im ALSA-Treiber »%s«. Bitte melden Sie " "dieses Problem den ALSA-Entwicklern." #: spa/plugins/alsa/acp/alsa-util.c:1344 #, c-format msgid "" "snd_pcm_avail_delay() returned strange values: delay %lu is less than avail " "%lu.\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr "" "snd_pcm_avail_delay() gibt einen ungewöhnlichen Wert zurück: Verzögerung %lu " "ist kleiner als das verfügbare %lu.\n" "Dies ist wahrscheinlich ein Fehler im ALSA-Treiber »%s«. Bitte melden Sie " "dieses Problem den ALSA-Entwicklern." #: spa/plugins/alsa/acp/alsa-util.c:1387 #, c-format msgid "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu byte " "(%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu bytes " "(%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "snd_pcm_mmap_begin() gab einen Wert zurück, der außerordentlich groß ist: " "%lu Byte (%lu ms).\n" "Dies ist wahrscheinlich ein Fehler im ALSA-Treiber »%s«. Bitte melden Sie " "dieses Problem den ALSA-Entwicklern." msgstr[1] "" "snd_pcm_mmap_begin() gab einen Wert zurück, der außerordentlich groß ist: " "%lu Byte (%lu ms).\n" "Dies ist wahrscheinlich ein Fehler im ALSA-Treiber »%s«. Bitte melden Sie " "dieses Problem den ALSA-Entwicklern." #: spa/plugins/alsa/acp/channelmap.h:457 msgid "(invalid)" msgstr "(ungültig)" #: spa/plugins/alsa/acp/compat.c:193 msgid "Built-in Audio" msgstr "Internes Audio" #: spa/plugins/alsa/acp/compat.c:198 msgid "Modem" msgstr "Modem" #: spa/plugins/bluez5/bluez5-device.c:1707 msgid "Audio Gateway (A2DP Source & HSP/HFP AG)" msgstr "Audio-Gateway (A2DP-Quelle und HSP/HFP AG)" #: spa/plugins/bluez5/bluez5-device.c:1755 #, c-format msgid "High Fidelity Playback (A2DP Sink, codec %s)" msgstr "High Fidelity-Wiedergabe (A2DP-Senke, Codec %s)" #: spa/plugins/bluez5/bluez5-device.c:1758 #, c-format msgid "High Fidelity Duplex (A2DP Source/Sink, codec %s)" msgstr "High Fidelity Duplex (A2DP-Quelle/-Senke, Codec %s)" #: spa/plugins/bluez5/bluez5-device.c:1766 msgid "High Fidelity Playback (A2DP Sink)" msgstr "High Fidelity-Wiedergabe (A2DP-Senke)" #: spa/plugins/bluez5/bluez5-device.c:1768 msgid "High Fidelity Duplex (A2DP Source/Sink)" msgstr "High Fidelity Duplex (A2DP-Quelle/-Senke)" #: spa/plugins/bluez5/bluez5-device.c:1818 #, c-format msgid "High Fidelity Playback (BAP Sink, codec %s)" msgstr "High Fidelity-Wiedergabe (BAP-Senke, Codec %s)" #: spa/plugins/bluez5/bluez5-device.c:1823 #, c-format msgid "High Fidelity Input (BAP Source, codec %s)" msgstr "High Fidelity-Eingang (BAP-Quelle, Codec %s)" #: spa/plugins/bluez5/bluez5-device.c:1827 #, c-format msgid "High Fidelity Duplex (BAP Source/Sink, codec %s)" msgstr "High Fidelity Duplex (BAP-Quelle/-Senke, Codec %s)" #: spa/plugins/bluez5/bluez5-device.c:1836 msgid "High Fidelity Playback (BAP Sink)" msgstr "High Fidelity-Wiedergabe (BAP-Senke)" #: spa/plugins/bluez5/bluez5-device.c:1840 msgid "High Fidelity Input (BAP Source)" msgstr "High Fidelity-Eingang (BAP-Quelle)" #: spa/plugins/bluez5/bluez5-device.c:1843 msgid "High Fidelity Duplex (BAP Source/Sink)" msgstr "High Fidelity Duplex (BAP-Quelle/-Senke)" #: spa/plugins/bluez5/bluez5-device.c:1892 #, c-format msgid "Headset Head Unit (HSP/HFP, codec %s)" msgstr "Sprechkopfhörer-Einheit (HSP/HFP, Codec %s)" #: spa/plugins/bluez5/bluez5-device.c:1973 #: spa/plugins/bluez5/bluez5-device.c:1978 #: spa/plugins/bluez5/bluez5-device.c:1985 #: spa/plugins/bluez5/bluez5-device.c:1991 #: spa/plugins/bluez5/bluez5-device.c:1997 #: spa/plugins/bluez5/bluez5-device.c:2003 #: spa/plugins/bluez5/bluez5-device.c:2009 #: spa/plugins/bluez5/bluez5-device.c:2015 #: spa/plugins/bluez5/bluez5-device.c:2021 msgid "Handsfree" msgstr "Freisprech" #: spa/plugins/bluez5/bluez5-device.c:1979 msgid "Handsfree (HFP)" msgstr "Freisprech (HFP)" #: spa/plugins/bluez5/bluez5-device.c:1996 msgid "Headphone" msgstr "Kopfhörer" #: spa/plugins/bluez5/bluez5-device.c:2002 msgid "Portable" msgstr "Tragbar" #: spa/plugins/bluez5/bluez5-device.c:2008 msgid "Car" msgstr "Auto" #: spa/plugins/bluez5/bluez5-device.c:2014 msgid "HiFi" msgstr "HiFi" #: spa/plugins/bluez5/bluez5-device.c:2020 msgid "Phone" msgstr "Telefon" #: spa/plugins/bluez5/bluez5-device.c:2027 msgid "Bluetooth" msgstr "Bluetooth" #: spa/plugins/bluez5/bluez5-device.c:2028 msgid "Bluetooth (HFP)" msgstr "Bluetooth (HFP)" #~ msgid "Headset Head Unit (HSP/HFP)" #~ msgstr "Kopfhörer-Garnitur (HSP/HFP)"pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/po/de_CH.po000066400000000000000000000411751511204443500224260ustar00rootroot00000000000000# German translation of pipewire # Copyright (C) 2008 pipewire # This file is distributed under the same license as the pipewire package. # # Micha Pietsch , 2008, 2009. # Fabian Affolter , 2008-2009, 2012. # msgid "" msgstr "" "Project-Id-Version: pipewire\n" "Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/pipewire/" "issues/new\n" "POT-Creation-Date: 2021-04-18 16:54+0800\n" "PO-Revision-Date: 2012-01-30 09:53+0000\n" "Last-Translator: Fabian Affolter \n" "Language-Team: German \n" "Language: de\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Poedit-Language: German\n" #: src/daemon/pipewire.c:43 #, c-format msgid "" "%s [options]\n" " -h, --help Show this help\n" " --version Show version\n" " -c, --config Load config (Default %s)\n" msgstr "" #: src/daemon/pipewire.desktop.in:4 msgid "PipeWire Media System" msgstr "" #: src/daemon/pipewire.desktop.in:5 msgid "Start the PipeWire Media System" msgstr "" #: src/examples/media-session/alsa-monitor.c:526 #: spa/plugins/alsa/acp/compat.c:187 msgid "Built-in Audio" msgstr "Internes Audio" #: src/examples/media-session/alsa-monitor.c:530 #: spa/plugins/alsa/acp/compat.c:192 msgid "Modem" msgstr "Modem" #: src/examples/media-session/alsa-monitor.c:539 msgid "Unknown device" msgstr "" #: src/tools/pw-cat.c:991 #, c-format msgid "" "%s [options] \n" " -h, --help Show this help\n" " --version Show version\n" " -v, --verbose Enable verbose operations\n" "\n" msgstr "" #: src/tools/pw-cat.c:998 #, c-format msgid "" " -R, --remote Remote daemon name\n" " --media-type Set media type (default %s)\n" " --media-category Set media category (default %s)\n" " --media-role Set media role (default %s)\n" " --target Set node target (default %s)\n" " 0 means don't link\n" " --latency Set node latency (default %s)\n" " Xunit (unit = s, ms, us, ns)\n" " or direct samples (256)\n" " the rate is the one of the source " "file\n" " --list-targets List available targets for --target\n" "\n" msgstr "" #: src/tools/pw-cat.c:1016 #, c-format msgid "" " --rate Sample rate (req. for rec) (default " "%u)\n" " --channels Number of channels (req. for rec) " "(default %u)\n" " --channel-map Channel map\n" " one of: \"stereo\", " "\"surround-51\",... or\n" " comma separated list of channel " "names: eg. \"FL,FR\"\n" " --format Sample format %s (req. for rec) " "(default %s)\n" " --volume Stream volume 0-1.0 (default %.3f)\n" " -q --quality Resampler quality (0 - 15) (default " "%d)\n" "\n" msgstr "" #: src/tools/pw-cat.c:1033 msgid "" " -p, --playback Playback mode\n" " -r, --record Recording mode\n" " -m, --midi Midi mode\n" "\n" msgstr "" #: src/tools/pw-cli.c:2932 #, c-format msgid "" "%s [options] [command]\n" " -h, --help Show this help\n" " --version Show version\n" " -d, --daemon Start as daemon (Default false)\n" " -r, --remote Remote daemon name\n" "\n" msgstr "" #: spa/plugins/alsa/acp/acp.c:290 msgid "Pro Audio" msgstr "" #: spa/plugins/alsa/acp/acp.c:411 spa/plugins/alsa/acp/alsa-mixer.c:4704 #: spa/plugins/bluez5/bluez5-device.c:1000 msgid "Off" msgstr "Aus" #: spa/plugins/alsa/acp/channelmap.h:466 msgid "(invalid)" msgstr "(ungültig)" #: spa/plugins/alsa/acp/alsa-mixer.c:2709 #, fuzzy msgid "Input" msgstr "Eingang %s" #: spa/plugins/alsa/acp/alsa-mixer.c:2710 msgid "Docking Station Input" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2711 #, fuzzy msgid "Docking Station Microphone" msgstr "Internes Audio" #: spa/plugins/alsa/acp/alsa-mixer.c:2712 msgid "Docking Station Line In" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2713 #: spa/plugins/alsa/acp/alsa-mixer.c:2804 msgid "Line In" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2714 #: spa/plugins/alsa/acp/alsa-mixer.c:2798 #: spa/plugins/bluez5/bluez5-device.c:1145 msgid "Microphone" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2715 #: spa/plugins/alsa/acp/alsa-mixer.c:2799 #, fuzzy msgid "Front Microphone" msgstr "Internes Audio" #: spa/plugins/alsa/acp/alsa-mixer.c:2716 #: spa/plugins/alsa/acp/alsa-mixer.c:2800 #, fuzzy msgid "Rear Microphone" msgstr "Internes Audio" #: spa/plugins/alsa/acp/alsa-mixer.c:2717 msgid "External Microphone" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2718 #: spa/plugins/alsa/acp/alsa-mixer.c:2802 #, fuzzy msgid "Internal Microphone" msgstr "Internes Audio" #: spa/plugins/alsa/acp/alsa-mixer.c:2719 #: spa/plugins/alsa/acp/alsa-mixer.c:2805 msgid "Radio" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2720 #: spa/plugins/alsa/acp/alsa-mixer.c:2806 msgid "Video" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2721 msgid "Automatic Gain Control" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2722 msgid "No Automatic Gain Control" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2723 msgid "Boost" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2724 msgid "No Boost" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2725 msgid "Amplifier" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2726 msgid "No Amplifier" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2727 msgid "Bass Boost" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2728 msgid "No Bass Boost" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2729 #: spa/plugins/bluez5/bluez5-device.c:1150 msgid "Speaker" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2730 #: spa/plugins/alsa/acp/alsa-mixer.c:2808 #, fuzzy msgid "Headphones" msgstr "Analog Mono" #: spa/plugins/alsa/acp/alsa-mixer.c:2797 #, fuzzy msgid "Analog Input" msgstr "Analog Mono" #: spa/plugins/alsa/acp/alsa-mixer.c:2801 msgid "Dock Microphone" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2803 #, fuzzy msgid "Headset Microphone" msgstr "Internes Audio" #: spa/plugins/alsa/acp/alsa-mixer.c:2807 #, fuzzy msgid "Analog Output" msgstr "Ausgang %s" #: spa/plugins/alsa/acp/alsa-mixer.c:2809 #, fuzzy msgid "Headphones 2" msgstr "Analog Mono" #: spa/plugins/alsa/acp/alsa-mixer.c:2810 #, fuzzy msgid "Headphones Mono Output" msgstr "Analog Mono" #: spa/plugins/alsa/acp/alsa-mixer.c:2811 msgid "Line Out" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2812 #, fuzzy msgid "Analog Mono Output" msgstr "Analog Mono" #: spa/plugins/alsa/acp/alsa-mixer.c:2813 #, fuzzy msgid "Speakers" msgstr "Analog Stereo" #: spa/plugins/alsa/acp/alsa-mixer.c:2814 msgid "HDMI / DisplayPort" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2815 #, fuzzy msgid "Digital Output (S/PDIF)" msgstr "Digital Stereo (HDMI)" #: spa/plugins/alsa/acp/alsa-mixer.c:2816 #, fuzzy msgid "Digital Input (S/PDIF)" msgstr "Digital Stereo (HDMI)" #: spa/plugins/alsa/acp/alsa-mixer.c:2817 msgid "Multichannel Input" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2818 #, fuzzy msgid "Multichannel Output" msgstr "Ausgang %s" #: spa/plugins/alsa/acp/alsa-mixer.c:2819 #, fuzzy msgid "Game Output" msgstr "Ausgang %s" #: spa/plugins/alsa/acp/alsa-mixer.c:2820 #: spa/plugins/alsa/acp/alsa-mixer.c:2821 #, fuzzy msgid "Chat Output" msgstr "Ausgang %s" #: spa/plugins/alsa/acp/alsa-mixer.c:2822 #, fuzzy msgid "Chat Input" msgstr "Eingang %s" #: spa/plugins/alsa/acp/alsa-mixer.c:2823 #, fuzzy msgid "Virtual Surround 7.1" msgstr "Analog Surround 7.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4527 msgid "Analog Mono" msgstr "Analog Mono" #: spa/plugins/alsa/acp/alsa-mixer.c:4528 #, fuzzy msgid "Analog Mono (Left)" msgstr "Analog Mono" #: spa/plugins/alsa/acp/alsa-mixer.c:4529 #, fuzzy msgid "Analog Mono (Right)" msgstr "Analog Mono" #. Note: Not translated to "Analog Stereo Input", because the source #. * name gets "Input" appended to it automatically, so adding "Input" #. * here would lead to the source name to become "Analog Stereo Input #. * Input". The same logic applies to analog-stereo-output, #. * multichannel-input and multichannel-output. #: spa/plugins/alsa/acp/alsa-mixer.c:4530 #: spa/plugins/alsa/acp/alsa-mixer.c:4538 #: spa/plugins/alsa/acp/alsa-mixer.c:4539 msgid "Analog Stereo" msgstr "Analog Stereo" #: spa/plugins/alsa/acp/alsa-mixer.c:4531 msgid "Mono" msgstr "Mono" #: spa/plugins/alsa/acp/alsa-mixer.c:4532 msgid "Stereo" msgstr "Stereo" #: spa/plugins/alsa/acp/alsa-mixer.c:4540 #: spa/plugins/alsa/acp/alsa-mixer.c:4698 #: spa/plugins/bluez5/bluez5-device.c:1135 msgid "Headset" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4541 #: spa/plugins/alsa/acp/alsa-mixer.c:4699 #, fuzzy msgid "Speakerphone" msgstr "Analog Stereo" #: spa/plugins/alsa/acp/alsa-mixer.c:4542 #: spa/plugins/alsa/acp/alsa-mixer.c:4543 msgid "Multichannel" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4544 #, fuzzy msgid "Analog Surround 2.1" msgstr "Analog Surround 4.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4545 #, fuzzy msgid "Analog Surround 3.0" msgstr "Analog Surround 4.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4546 #, fuzzy msgid "Analog Surround 3.1" msgstr "Analog Surround 4.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4547 msgid "Analog Surround 4.0" msgstr "Analog Surround 4.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4548 msgid "Analog Surround 4.1" msgstr "Analog Surround 4.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4549 msgid "Analog Surround 5.0" msgstr "Analog Surround 5.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4550 msgid "Analog Surround 5.1" msgstr "Analog Surround 5.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4551 #, fuzzy msgid "Analog Surround 6.0" msgstr "Analog Surround 4.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4552 #, fuzzy msgid "Analog Surround 6.1" msgstr "Analog Surround 4.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4553 #, fuzzy msgid "Analog Surround 7.0" msgstr "Analog Surround 4.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4554 msgid "Analog Surround 7.1" msgstr "Analog Surround 7.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4555 msgid "Digital Stereo (IEC958)" msgstr "Digital Stereo (IEC958)" #: spa/plugins/alsa/acp/alsa-mixer.c:4556 msgid "Digital Surround 4.0 (IEC958/AC3)" msgstr "Digital Surround 4.0 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4557 msgid "Digital Surround 5.1 (IEC958/AC3)" msgstr "Digital Surround 5.1 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4558 #, fuzzy msgid "Digital Surround 5.1 (IEC958/DTS)" msgstr "Digital Surround 5.1 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4559 msgid "Digital Stereo (HDMI)" msgstr "Digital Stereo (HDMI)" #: spa/plugins/alsa/acp/alsa-mixer.c:4560 #, fuzzy msgid "Digital Surround 5.1 (HDMI)" msgstr "Digital Surround 5.1 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4561 msgid "Chat" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4562 msgid "Game" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4696 #, fuzzy msgid "Analog Mono Duplex" msgstr "Analog Mono" #: spa/plugins/alsa/acp/alsa-mixer.c:4697 #, fuzzy msgid "Analog Stereo Duplex" msgstr "Analog Stereo" #: spa/plugins/alsa/acp/alsa-mixer.c:4700 #, fuzzy msgid "Digital Stereo Duplex (IEC958)" msgstr "Digital Stereo (IEC958)" #: spa/plugins/alsa/acp/alsa-mixer.c:4701 msgid "Multichannel Duplex" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4702 #, fuzzy msgid "Stereo Duplex" msgstr "Analog Stereo" #: spa/plugins/alsa/acp/alsa-mixer.c:4703 msgid "Mono Chat + 7.1 Surround" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4806 #, fuzzy, c-format msgid "%s Output" msgstr "Ausgang %s" #: spa/plugins/alsa/acp/alsa-mixer.c:4813 #, fuzzy, c-format msgid "%s Input" msgstr "Eingang %s" #: spa/plugins/alsa/acp/alsa-util.c:1175 spa/plugins/alsa/acp/alsa-util.c:1269 #, fuzzy, c-format msgid "" "snd_pcm_avail() returned a value that is exceptionally large: %lu byte (%lu " "ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_avail() returned a value that is exceptionally large: %lu bytes (%lu " "ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "snd_pcm_avail() gibt einen Wert zurück, welche außerordentlich groß ist: %lu " "bytes (%lu ms).\n" "Dies ist wahrscheinlich ein Fehler im ALSA-Treiber '%s'. Bitte melden Sie " "diesen Punkt den ALSA-Entwicklern." msgstr[1] "" "snd_pcm_avail() gibt einen Wert zurück, welche außerordentlich groß ist: %lu " "bytes (%lu ms).\n" "Dies ist wahrscheinlich ein Fehler im ALSA-Treiber '%s'. Bitte melden Sie " "diesen Punkt den ALSA-Entwicklern." #: spa/plugins/alsa/acp/alsa-util.c:1241 #, fuzzy, c-format msgid "" "snd_pcm_delay() returned a value that is exceptionally large: %li byte (%s" "%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_delay() returned a value that is exceptionally large: %li bytes (%s" "%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "snd_pcm_delay() gibt einen Wert zurück, welche außerordentlich groß ist: %li " "bytes (%s%lu ms).\n" "Dies ist wahrscheinlich ein Fehler im ALSA-Treiber '%s'. Bitte melden Sie " "diesen Punkt den ALSA-Entwicklern." msgstr[1] "" "snd_pcm_delay() gibt einen Wert zurück, welche außerordentlich groß ist: %li " "bytes (%s%lu ms).\n" "Dies ist wahrscheinlich ein Fehler im ALSA-Treiber '%s'. Bitte melden Sie " "diesen Punkt den ALSA-Entwicklern." #: spa/plugins/alsa/acp/alsa-util.c:1288 #, fuzzy, c-format msgid "" "snd_pcm_avail_delay() returned strange values: delay %lu is less than avail " "%lu.\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr "" "snd_pcm_avail() gibt einen Wert zurück, welche außerordentlich groß ist: %lu " "bytes (%lu ms).\n" "Dies ist wahrscheinlich ein Fehler im ALSA-Treiber '%s'. Bitte melden Sie " "diesen Punkt den ALSA-Entwicklern." #: spa/plugins/alsa/acp/alsa-util.c:1331 #, fuzzy, c-format msgid "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu byte " "(%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu bytes " "(%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "snd_pcm_mmap_begin() gibt einen Wert zurück, welche außerordentlich groß " "ist: %lu bytes (%lu ms).\n" "Dies ist wahrscheinlich ein Fehler im ALSA-Treiber '%s'. Bitte melden Sie " "diesen Punkt den ALSA-Entwicklern." msgstr[1] "" "snd_pcm_mmap_begin() gibt einen Wert zurück, welche außerordentlich groß " "ist: %lu bytes (%lu ms).\n" "Dies ist wahrscheinlich ein Fehler im ALSA-Treiber '%s'. Bitte melden Sie " "diesen Punkt den ALSA-Entwicklern." #: spa/plugins/bluez5/bluez5-device.c:1010 msgid "Audio Gateway (A2DP Source & HSP/HFP AG)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1033 #, c-format msgid "High Fidelity Playback (A2DP Sink, codec %s)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1035 #, c-format msgid "High Fidelity Duplex (A2DP Source/Sink, codec %s)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1041 msgid "High Fidelity Playback (A2DP Sink)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1043 msgid "High Fidelity Duplex (A2DP Source/Sink)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1070 #, c-format msgid "Headset Head Unit (HSP/HFP, codec %s)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1074 msgid "Headset Head Unit (HSP/HFP)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1140 msgid "Handsfree" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1155 #, fuzzy msgid "Headphone" msgstr "Analog Mono" #: spa/plugins/bluez5/bluez5-device.c:1160 msgid "Portable" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1165 msgid "Car" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1170 msgid "HiFi" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1175 msgid "Phone" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1181 msgid "Bluetooth" msgstr "" pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/po/el.po000066400000000000000000000472001511204443500220570ustar00rootroot00000000000000# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # Dimitris Glezos , 2008. # Thalia Papoutsaki , 2009, 2012. # Dimitris Spingos (Δημήτρης Σπίγγος) , 2013, 2014. msgid "" msgstr "" "Project-Id-Version: el\n" "Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/pipewire/" "issues/new\n" "POT-Creation-Date: 2021-04-18 16:54+0800\n" "PO-Revision-Date: 2014-05-08 09:53+0300\n" "Last-Translator: Dimitris Spingos (Δημήτρης Σπίγγος) \n" "Language-Team: team@lists.gnome.gr\n" "Language: el\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Generator: Virtaal 0.7.0\n" #: src/daemon/pipewire.c:43 #, c-format msgid "" "%s [options]\n" " -h, --help Show this help\n" " --version Show version\n" " -c, --config Load config (Default %s)\n" msgstr "" #: src/daemon/pipewire.desktop.in:4 msgid "PipeWire Media System" msgstr "" #: src/daemon/pipewire.desktop.in:5 msgid "Start the PipeWire Media System" msgstr "" #: src/examples/media-session/alsa-monitor.c:526 #: spa/plugins/alsa/acp/compat.c:187 msgid "Built-in Audio" msgstr "Εσωτερικός ήχος" #: src/examples/media-session/alsa-monitor.c:530 #: spa/plugins/alsa/acp/compat.c:192 msgid "Modem" msgstr "Μόντεμ" #: src/examples/media-session/alsa-monitor.c:539 msgid "Unknown device" msgstr "" #: src/tools/pw-cat.c:991 #, c-format msgid "" "%s [options] \n" " -h, --help Show this help\n" " --version Show version\n" " -v, --verbose Enable verbose operations\n" "\n" msgstr "" #: src/tools/pw-cat.c:998 #, c-format msgid "" " -R, --remote Remote daemon name\n" " --media-type Set media type (default %s)\n" " --media-category Set media category (default %s)\n" " --media-role Set media role (default %s)\n" " --target Set node target (default %s)\n" " 0 means don't link\n" " --latency Set node latency (default %s)\n" " Xunit (unit = s, ms, us, ns)\n" " or direct samples (256)\n" " the rate is the one of the source " "file\n" " --list-targets List available targets for --target\n" "\n" msgstr "" #: src/tools/pw-cat.c:1016 #, c-format msgid "" " --rate Sample rate (req. for rec) (default " "%u)\n" " --channels Number of channels (req. for rec) " "(default %u)\n" " --channel-map Channel map\n" " one of: \"stereo\", " "\"surround-51\",... or\n" " comma separated list of channel " "names: eg. \"FL,FR\"\n" " --format Sample format %s (req. for rec) " "(default %s)\n" " --volume Stream volume 0-1.0 (default %.3f)\n" " -q --quality Resampler quality (0 - 15) (default " "%d)\n" "\n" msgstr "" #: src/tools/pw-cat.c:1033 msgid "" " -p, --playback Playback mode\n" " -r, --record Recording mode\n" " -m, --midi Midi mode\n" "\n" msgstr "" #: src/tools/pw-cli.c:2932 #, c-format msgid "" "%s [options] [command]\n" " -h, --help Show this help\n" " --version Show version\n" " -d, --daemon Start as daemon (Default false)\n" " -r, --remote Remote daemon name\n" "\n" msgstr "" #: spa/plugins/alsa/acp/acp.c:290 msgid "Pro Audio" msgstr "" #: spa/plugins/alsa/acp/acp.c:411 spa/plugins/alsa/acp/alsa-mixer.c:4704 #: spa/plugins/bluez5/bluez5-device.c:1000 msgid "Off" msgstr "Ανενεργό" #: spa/plugins/alsa/acp/channelmap.h:466 msgid "(invalid)" msgstr "(άκυρο)" #: spa/plugins/alsa/acp/alsa-mixer.c:2709 msgid "Input" msgstr "Εισαγωγή" #: spa/plugins/alsa/acp/alsa-mixer.c:2710 msgid "Docking Station Input" msgstr "Είσοδος σταθμού αγκύρωσης" #: spa/plugins/alsa/acp/alsa-mixer.c:2711 msgid "Docking Station Microphone" msgstr "Μικρόφωνο σταθμού αγκύρωσης" #: spa/plugins/alsa/acp/alsa-mixer.c:2712 msgid "Docking Station Line In" msgstr "Γραμμή εισόδου σταθμού αγκύρωσης" #: spa/plugins/alsa/acp/alsa-mixer.c:2713 #: spa/plugins/alsa/acp/alsa-mixer.c:2804 msgid "Line In" msgstr "Γραμμή εισόδου" #: spa/plugins/alsa/acp/alsa-mixer.c:2714 #: spa/plugins/alsa/acp/alsa-mixer.c:2798 #: spa/plugins/bluez5/bluez5-device.c:1145 msgid "Microphone" msgstr "Μικρόφωνο" #: spa/plugins/alsa/acp/alsa-mixer.c:2715 #: spa/plugins/alsa/acp/alsa-mixer.c:2799 msgid "Front Microphone" msgstr "Μπροστινό μικρόφωνο" #: spa/plugins/alsa/acp/alsa-mixer.c:2716 #: spa/plugins/alsa/acp/alsa-mixer.c:2800 msgid "Rear Microphone" msgstr "Πίσω μικρόφωνο" #: spa/plugins/alsa/acp/alsa-mixer.c:2717 msgid "External Microphone" msgstr "Εξωτερικό μικρόφωνο" #: spa/plugins/alsa/acp/alsa-mixer.c:2718 #: spa/plugins/alsa/acp/alsa-mixer.c:2802 msgid "Internal Microphone" msgstr "Εσωτερικό μικρόφωνο" #: spa/plugins/alsa/acp/alsa-mixer.c:2719 #: spa/plugins/alsa/acp/alsa-mixer.c:2805 msgid "Radio" msgstr "Ραδιόφωνο" #: spa/plugins/alsa/acp/alsa-mixer.c:2720 #: spa/plugins/alsa/acp/alsa-mixer.c:2806 msgid "Video" msgstr "Βίντεο" #: spa/plugins/alsa/acp/alsa-mixer.c:2721 msgid "Automatic Gain Control" msgstr "Αυτόματος έλεγχος απολαβής" #: spa/plugins/alsa/acp/alsa-mixer.c:2722 msgid "No Automatic Gain Control" msgstr "Χωρίς αυτόματο έλεγχο απολαβής" #: spa/plugins/alsa/acp/alsa-mixer.c:2723 msgid "Boost" msgstr "Ενίσχυση" #: spa/plugins/alsa/acp/alsa-mixer.c:2724 msgid "No Boost" msgstr "Χωρίς ενίσχυση" #: spa/plugins/alsa/acp/alsa-mixer.c:2725 msgid "Amplifier" msgstr "Ενισχυτή" #: spa/plugins/alsa/acp/alsa-mixer.c:2726 msgid "No Amplifier" msgstr "Χωρίς ενισχυτή" #: spa/plugins/alsa/acp/alsa-mixer.c:2727 msgid "Bass Boost" msgstr "Ενίσχυση μπάσων" #: spa/plugins/alsa/acp/alsa-mixer.c:2728 msgid "No Bass Boost" msgstr "Χωρίς ενίσχυση μπάσων" #: spa/plugins/alsa/acp/alsa-mixer.c:2729 #: spa/plugins/bluez5/bluez5-device.c:1150 msgid "Speaker" msgstr "Ηχείο" #: spa/plugins/alsa/acp/alsa-mixer.c:2730 #: spa/plugins/alsa/acp/alsa-mixer.c:2808 msgid "Headphones" msgstr "Ακουστικά" #: spa/plugins/alsa/acp/alsa-mixer.c:2797 msgid "Analog Input" msgstr "Αναλογική είσοδος" #: spa/plugins/alsa/acp/alsa-mixer.c:2801 msgid "Dock Microphone" msgstr "Μικρόφωνο σταθμού αγκύρωσης" #: spa/plugins/alsa/acp/alsa-mixer.c:2803 msgid "Headset Microphone" msgstr "Μικρόφωνο ακουστικού" #: spa/plugins/alsa/acp/alsa-mixer.c:2807 msgid "Analog Output" msgstr "Αναλογική έξοδος" #: spa/plugins/alsa/acp/alsa-mixer.c:2809 #, fuzzy msgid "Headphones 2" msgstr "Ακουστικά" #: spa/plugins/alsa/acp/alsa-mixer.c:2810 #, fuzzy msgid "Headphones Mono Output" msgstr "Αναλογική μονοφωνική έξοδος" #: spa/plugins/alsa/acp/alsa-mixer.c:2811 msgid "Line Out" msgstr "Γραμμή εξόδου" #: spa/plugins/alsa/acp/alsa-mixer.c:2812 msgid "Analog Mono Output" msgstr "Αναλογική μονοφωνική έξοδος" #: spa/plugins/alsa/acp/alsa-mixer.c:2813 msgid "Speakers" msgstr "Ηχεία" #: spa/plugins/alsa/acp/alsa-mixer.c:2814 msgid "HDMI / DisplayPort" msgstr "HDMI / Θύρα εμφάνισης" #: spa/plugins/alsa/acp/alsa-mixer.c:2815 msgid "Digital Output (S/PDIF)" msgstr "Ψηφιακή έξοδος (S/PDIF)" #: spa/plugins/alsa/acp/alsa-mixer.c:2816 msgid "Digital Input (S/PDIF)" msgstr "Ψηφιακή είσοδος (S/PDIF)" #: spa/plugins/alsa/acp/alsa-mixer.c:2817 #, fuzzy msgid "Multichannel Input" msgstr "Αναλογική 4κάναλη είσοδος" #: spa/plugins/alsa/acp/alsa-mixer.c:2818 #, fuzzy msgid "Multichannel Output" msgstr "Μηδενική έξοδος" #: spa/plugins/alsa/acp/alsa-mixer.c:2819 #, fuzzy msgid "Game Output" msgstr "Έξοδος %s" #: spa/plugins/alsa/acp/alsa-mixer.c:2820 #: spa/plugins/alsa/acp/alsa-mixer.c:2821 #, fuzzy msgid "Chat Output" msgstr "Έξοδος %s" #: spa/plugins/alsa/acp/alsa-mixer.c:2822 #, fuzzy msgid "Chat Input" msgstr "Είσοδος %s" #: spa/plugins/alsa/acp/alsa-mixer.c:2823 #, fuzzy msgid "Virtual Surround 7.1" msgstr "Εικονικός περιφερειακός ήχος δέκτη" #: spa/plugins/alsa/acp/alsa-mixer.c:4527 msgid "Analog Mono" msgstr "Αναλογικό μονοφωνικό" #: spa/plugins/alsa/acp/alsa-mixer.c:4528 #, fuzzy msgid "Analog Mono (Left)" msgstr "Αναλογικό μονοφωνικό" #: spa/plugins/alsa/acp/alsa-mixer.c:4529 #, fuzzy msgid "Analog Mono (Right)" msgstr "Αναλογικό μονοφωνικό" #. Note: Not translated to "Analog Stereo Input", because the source #. * name gets "Input" appended to it automatically, so adding "Input" #. * here would lead to the source name to become "Analog Stereo Input #. * Input". The same logic applies to analog-stereo-output, #. * multichannel-input and multichannel-output. #: spa/plugins/alsa/acp/alsa-mixer.c:4530 #: spa/plugins/alsa/acp/alsa-mixer.c:4538 #: spa/plugins/alsa/acp/alsa-mixer.c:4539 msgid "Analog Stereo" msgstr "Αναλογικό στερεοφωνικό" #: spa/plugins/alsa/acp/alsa-mixer.c:4531 msgid "Mono" msgstr "Μονοφωνικό" #: spa/plugins/alsa/acp/alsa-mixer.c:4532 msgid "Stereo" msgstr "Στερεοφωνικό" #: spa/plugins/alsa/acp/alsa-mixer.c:4540 #: spa/plugins/alsa/acp/alsa-mixer.c:4698 #: spa/plugins/bluez5/bluez5-device.c:1135 msgid "Headset" msgstr "Ακουστικά" #: spa/plugins/alsa/acp/alsa-mixer.c:4541 #: spa/plugins/alsa/acp/alsa-mixer.c:4699 #, fuzzy msgid "Speakerphone" msgstr "Ηχείο" #: spa/plugins/alsa/acp/alsa-mixer.c:4542 #: spa/plugins/alsa/acp/alsa-mixer.c:4543 msgid "Multichannel" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4544 msgid "Analog Surround 2.1" msgstr "Αναλογικός περιφερειακός ήχος 2.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4545 msgid "Analog Surround 3.0" msgstr "Αναλογικός περιφερειακός ήχος 3.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4546 msgid "Analog Surround 3.1" msgstr "Αναλογικός περιφερειακός ήχος 3.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4547 msgid "Analog Surround 4.0" msgstr "Αναλογικός περιφερειακός ήχος 4.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4548 msgid "Analog Surround 4.1" msgstr "Αναλογικός περιφερειακός ήχος 4.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4549 msgid "Analog Surround 5.0" msgstr "Αναλογικός περιφερειακός ήχος 5.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4550 msgid "Analog Surround 5.1" msgstr "Αναλογικός περιφερειακός ήχος 5.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4551 msgid "Analog Surround 6.0" msgstr "Αναλογικός περιφερειακός ήχος 6.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4552 msgid "Analog Surround 6.1" msgstr "Αναλογικός περιφερειακός ήχος 6.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4553 msgid "Analog Surround 7.0" msgstr "Αναλογικός περιφερειακός ήχος 7.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4554 msgid "Analog Surround 7.1" msgstr "Αναλογικός περιφερειακός ήχος 7.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4555 msgid "Digital Stereo (IEC958)" msgstr "Ψηφιακό στερεοφωνικό (IEC958)" #: spa/plugins/alsa/acp/alsa-mixer.c:4556 msgid "Digital Surround 4.0 (IEC958/AC3)" msgstr "Ψηφιακός περιφερειακός ήχος 4.0 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4557 msgid "Digital Surround 5.1 (IEC958/AC3)" msgstr "Ψηφιακός περιφερειακός ήχος 5.1 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4558 msgid "Digital Surround 5.1 (IEC958/DTS)" msgstr "Ψηφιακός περιφερειακός ήχος 5.1 (IEC958/DTS)" #: spa/plugins/alsa/acp/alsa-mixer.c:4559 msgid "Digital Stereo (HDMI)" msgstr "Ψηφιακός στερεοφωνικός (HDMI)" #: spa/plugins/alsa/acp/alsa-mixer.c:4560 msgid "Digital Surround 5.1 (HDMI)" msgstr "Ψηφιακός περιφερειακός ήχος 5.1 (HDMI)" #: spa/plugins/alsa/acp/alsa-mixer.c:4561 msgid "Chat" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4562 msgid "Game" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4696 msgid "Analog Mono Duplex" msgstr "Αναλογικός μονοφωνικός αμφίδρομος" #: spa/plugins/alsa/acp/alsa-mixer.c:4697 msgid "Analog Stereo Duplex" msgstr "Αναλογικός στερεοφωνικός αμφίδρομος" #: spa/plugins/alsa/acp/alsa-mixer.c:4700 msgid "Digital Stereo Duplex (IEC958)" msgstr "Ψηφιακός στερεοφωνικός αμφίδρομος (IEC958)" #: spa/plugins/alsa/acp/alsa-mixer.c:4701 msgid "Multichannel Duplex" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4702 #, fuzzy msgid "Stereo Duplex" msgstr "Αναλογικός στερεοφωνικός αμφίδρομος" #: spa/plugins/alsa/acp/alsa-mixer.c:4703 msgid "Mono Chat + 7.1 Surround" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4806 #, c-format msgid "%s Output" msgstr "Έξοδος %s" #: spa/plugins/alsa/acp/alsa-mixer.c:4813 #, c-format msgid "%s Input" msgstr "Είσοδος %s" #: spa/plugins/alsa/acp/alsa-util.c:1175 spa/plugins/alsa/acp/alsa-util.c:1269 #, fuzzy, c-format msgid "" "snd_pcm_avail() returned a value that is exceptionally large: %lu byte (%lu " "ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_avail() returned a value that is exceptionally large: %lu bytes (%lu " "ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "Το snd_pcm_avail() επέστρεψε μια τιμή που είναι πολύ μεγάλη: %lu ψηφιολέξεις " "(%lu ms).\n" "Το πιθανότερο αυτό είναι ένα σφάλμα στον οδηγό ALSA '%s'. Παρακαλούμε, " "αναφέρτε αυτό το θέμα στους προγραμματιστές ALSA." msgstr[1] "" "Το snd_pcm_avail() επέστρεψε μια τιμή που είναι πολύ μεγάλη: %lu ψηφιολέξεις " "(%lu ms).\n" "Το πιθανότερο αυτό είναι ένα σφάλμα στον οδηγό ALSA '%s'. Παρακαλούμε, " "αναφέρτε αυτό το θέμα στους προγραμματιστές ALSA." #: spa/plugins/alsa/acp/alsa-util.c:1241 #, fuzzy, c-format msgid "" "snd_pcm_delay() returned a value that is exceptionally large: %li byte (%s" "%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_delay() returned a value that is exceptionally large: %li bytes (%s" "%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "Το snd_pcm_delay() επέστρεψε μια τιμή που είναι πολύ μεγάλη: %li ψηφιολέξεις " "(%s%lu ms).\n" "Το πιθανότερο αυτό είναι ένα σφάλμα στον οδηγό ALSA '%s'. Παρακαλούμε, " "αναφέρτε αυτό το θέμα στους προγραμματιστές ALSA." msgstr[1] "" "Το snd_pcm_delay() επέστρεψε μια τιμή που είναι πολύ μεγάλη: %li ψηφιολέξεις " "(%s%lu ms).\n" "Το πιθανότερο αυτό είναι ένα σφάλμα στον οδηγό ALSA '%s'. Παρακαλούμε, " "αναφέρτε αυτό το θέμα στους προγραμματιστές ALSA." #: spa/plugins/alsa/acp/alsa-util.c:1288 #, c-format msgid "" "snd_pcm_avail_delay() returned strange values: delay %lu is less than avail " "%lu.\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr "" "Το snd_pcm_avail_delay() επέστρεψε περίεργες τιμές: η καθυστέρηση %lu είναι " "μικρότερη από το avail %lu.\n" "Το πιθανότερο αυτό είναι ένα σφάλμα στον οδηγό ALSA '%s'. Παρακαλούμε, " "αναφέρτε αυτό το θέμα στους προγραμματιστές ALSA." #: spa/plugins/alsa/acp/alsa-util.c:1331 #, fuzzy, c-format msgid "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu byte " "(%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu bytes " "(%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "Το snd_pcm_mmap_begin() επέστρεψε μια τιμή που είναι πολύ μεγάλη: %lu " "ψηφιολέξεις (%lu ms).\n" "Το πιθανότερο αυτό είναι ένα σφάλμα στον οδηγό ALSA '%s'. Παρακαλούμε, " "αναφέρτε αυτό το θέμα στους προγραμματιστές ALSA." msgstr[1] "" "Το snd_pcm_mmap_begin() επέστρεψε μια τιμή που είναι πολύ μεγάλη: %lu " "ψηφιολέξεις (%lu ms).\n" "Το πιθανότερο αυτό είναι ένα σφάλμα στον οδηγό ALSA '%s'. Παρακαλούμε, " "αναφέρτε αυτό το θέμα στους προγραμματιστές ALSA." #: spa/plugins/bluez5/bluez5-device.c:1010 msgid "Audio Gateway (A2DP Source & HSP/HFP AG)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1033 #, c-format msgid "High Fidelity Playback (A2DP Sink, codec %s)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1035 #, c-format msgid "High Fidelity Duplex (A2DP Source/Sink, codec %s)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1041 msgid "High Fidelity Playback (A2DP Sink)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1043 msgid "High Fidelity Duplex (A2DP Source/Sink)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1070 #, c-format msgid "Headset Head Unit (HSP/HFP, codec %s)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1074 msgid "Headset Head Unit (HSP/HFP)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1140 msgid "Handsfree" msgstr "Ανοιχτής ακρόασης" #: spa/plugins/bluez5/bluez5-device.c:1155 msgid "Headphone" msgstr "Ακουστικό" #: spa/plugins/bluez5/bluez5-device.c:1160 msgid "Portable" msgstr "Φορητό" #: spa/plugins/bluez5/bluez5-device.c:1165 msgid "Car" msgstr "Αυτοκίνητο" #: spa/plugins/bluez5/bluez5-device.c:1170 msgid "HiFi" msgstr "Υψηλή πιστότητα" #: spa/plugins/bluez5/bluez5-device.c:1175 msgid "Phone" msgstr "Τηλέφωνο" #: spa/plugins/bluez5/bluez5-device.c:1181 #, fuzzy msgid "Bluetooth" msgstr "Είσοδος μπλουτούθ" pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/po/eo.po000066400000000000000000000363011511204443500220620ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the pipewire package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: pipewire\n" "Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/pipewire/" "issues/new\n" "POT-Creation-Date: 2021-04-18 16:54+0800\n" "PO-Revision-Date: 2021-02-05 01:40+0000\n" "Last-Translator: Carmen Bianca Bakker \n" "Language-Team: Esperanto \n" "Language: eo\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" "X-Generator: Weblate 4.4.2\n" #: src/daemon/pipewire.c:43 #, c-format msgid "" "%s [options]\n" " -h, --help Show this help\n" " --version Show version\n" " -c, --config Load config (Default %s)\n" msgstr "" #: src/daemon/pipewire.desktop.in:4 msgid "PipeWire Media System" msgstr "" #: src/daemon/pipewire.desktop.in:5 msgid "Start the PipeWire Media System" msgstr "" #: src/examples/media-session/alsa-monitor.c:526 #: spa/plugins/alsa/acp/compat.c:187 msgid "Built-in Audio" msgstr "Integrita sono" #: src/examples/media-session/alsa-monitor.c:530 #: spa/plugins/alsa/acp/compat.c:192 msgid "Modem" msgstr "" #: src/examples/media-session/alsa-monitor.c:539 msgid "Unknown device" msgstr "" #: src/tools/pw-cat.c:991 #, c-format msgid "" "%s [options] \n" " -h, --help Show this help\n" " --version Show version\n" " -v, --verbose Enable verbose operations\n" "\n" msgstr "" #: src/tools/pw-cat.c:998 #, c-format msgid "" " -R, --remote Remote daemon name\n" " --media-type Set media type (default %s)\n" " --media-category Set media category (default %s)\n" " --media-role Set media role (default %s)\n" " --target Set node target (default %s)\n" " 0 means don't link\n" " --latency Set node latency (default %s)\n" " Xunit (unit = s, ms, us, ns)\n" " or direct samples (256)\n" " the rate is the one of the source " "file\n" " --list-targets List available targets for --target\n" "\n" msgstr "" #: src/tools/pw-cat.c:1016 #, c-format msgid "" " --rate Sample rate (req. for rec) (default " "%u)\n" " --channels Number of channels (req. for rec) " "(default %u)\n" " --channel-map Channel map\n" " one of: \"stereo\", " "\"surround-51\",... or\n" " comma separated list of channel " "names: eg. \"FL,FR\"\n" " --format Sample format %s (req. for rec) " "(default %s)\n" " --volume Stream volume 0-1.0 (default %.3f)\n" " -q --quality Resampler quality (0 - 15) (default " "%d)\n" "\n" msgstr "" #: src/tools/pw-cat.c:1033 msgid "" " -p, --playback Playback mode\n" " -r, --record Recording mode\n" " -m, --midi Midi mode\n" "\n" msgstr "" #: src/tools/pw-cli.c:2932 #, c-format msgid "" "%s [options] [command]\n" " -h, --help Show this help\n" " --version Show version\n" " -d, --daemon Start as daemon (Default false)\n" " -r, --remote Remote daemon name\n" "\n" msgstr "" #: spa/plugins/alsa/acp/acp.c:290 msgid "Pro Audio" msgstr "" #: spa/plugins/alsa/acp/acp.c:411 spa/plugins/alsa/acp/alsa-mixer.c:4704 #: spa/plugins/bluez5/bluez5-device.c:1000 msgid "Off" msgstr "Malŝaltita" #: spa/plugins/alsa/acp/channelmap.h:466 msgid "(invalid)" msgstr "(nevalida)" #: spa/plugins/alsa/acp/alsa-mixer.c:2709 msgid "Input" msgstr "Enigo" #: spa/plugins/alsa/acp/alsa-mixer.c:2710 msgid "Docking Station Input" msgstr "Doka enigo" #: spa/plugins/alsa/acp/alsa-mixer.c:2711 msgid "Docking Station Microphone" msgstr "Doka mikrofono" #: spa/plugins/alsa/acp/alsa-mixer.c:2712 msgid "Docking Station Line In" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2713 #: spa/plugins/alsa/acp/alsa-mixer.c:2804 msgid "Line In" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2714 #: spa/plugins/alsa/acp/alsa-mixer.c:2798 #: spa/plugins/bluez5/bluez5-device.c:1145 msgid "Microphone" msgstr "Mikrofono" #: spa/plugins/alsa/acp/alsa-mixer.c:2715 #: spa/plugins/alsa/acp/alsa-mixer.c:2799 msgid "Front Microphone" msgstr "Antaŭa mikrofono" #: spa/plugins/alsa/acp/alsa-mixer.c:2716 #: spa/plugins/alsa/acp/alsa-mixer.c:2800 msgid "Rear Microphone" msgstr "Malantaŭa mikrofono" #: spa/plugins/alsa/acp/alsa-mixer.c:2717 msgid "External Microphone" msgstr "Ekstera mikrofono" #: spa/plugins/alsa/acp/alsa-mixer.c:2718 #: spa/plugins/alsa/acp/alsa-mixer.c:2802 msgid "Internal Microphone" msgstr "Interna mikrofono" #: spa/plugins/alsa/acp/alsa-mixer.c:2719 #: spa/plugins/alsa/acp/alsa-mixer.c:2805 msgid "Radio" msgstr "Radio" #: spa/plugins/alsa/acp/alsa-mixer.c:2720 #: spa/plugins/alsa/acp/alsa-mixer.c:2806 msgid "Video" msgstr "Video" #: spa/plugins/alsa/acp/alsa-mixer.c:2721 msgid "Automatic Gain Control" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2722 msgid "No Automatic Gain Control" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2723 msgid "Boost" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2724 msgid "No Boost" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2725 msgid "Amplifier" msgstr "Amplifilo" #: spa/plugins/alsa/acp/alsa-mixer.c:2726 msgid "No Amplifier" msgstr "Neniu amplifilo" #: spa/plugins/alsa/acp/alsa-mixer.c:2727 msgid "Bass Boost" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2728 msgid "No Bass Boost" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2729 #: spa/plugins/bluez5/bluez5-device.c:1150 msgid "Speaker" msgstr "Laŭtparolilo" #: spa/plugins/alsa/acp/alsa-mixer.c:2730 #: spa/plugins/alsa/acp/alsa-mixer.c:2808 msgid "Headphones" msgstr "Kapaŭskultilo" #: spa/plugins/alsa/acp/alsa-mixer.c:2797 msgid "Analog Input" msgstr "Analoga enigo" #: spa/plugins/alsa/acp/alsa-mixer.c:2801 msgid "Dock Microphone" msgstr "Doka mikrofono" #: spa/plugins/alsa/acp/alsa-mixer.c:2803 msgid "Headset Microphone" msgstr "Kaptelefona mikrofono" #: spa/plugins/alsa/acp/alsa-mixer.c:2807 msgid "Analog Output" msgstr "Analoga eligo" #: spa/plugins/alsa/acp/alsa-mixer.c:2809 msgid "Headphones 2" msgstr "Kapaŭskultilo 2" #: spa/plugins/alsa/acp/alsa-mixer.c:2810 msgid "Headphones Mono Output" msgstr "Kapaŭskultila mono-eligo" #: spa/plugins/alsa/acp/alsa-mixer.c:2811 msgid "Line Out" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2812 msgid "Analog Mono Output" msgstr "Analoga mono-eligo" #: spa/plugins/alsa/acp/alsa-mixer.c:2813 msgid "Speakers" msgstr "Laŭtparoliloj" #: spa/plugins/alsa/acp/alsa-mixer.c:2814 msgid "HDMI / DisplayPort" msgstr "HDMI / DisplayPort" #: spa/plugins/alsa/acp/alsa-mixer.c:2815 msgid "Digital Output (S/PDIF)" msgstr "Cifera eligo (S/PDIF)" #: spa/plugins/alsa/acp/alsa-mixer.c:2816 msgid "Digital Input (S/PDIF)" msgstr "Cifera enigo (S/PDIF)" #: spa/plugins/alsa/acp/alsa-mixer.c:2817 msgid "Multichannel Input" msgstr "Plurkanala enigo" #: spa/plugins/alsa/acp/alsa-mixer.c:2818 msgid "Multichannel Output" msgstr "Plurkanala eligo" #: spa/plugins/alsa/acp/alsa-mixer.c:2819 msgid "Game Output" msgstr "Lud-eligo" #: spa/plugins/alsa/acp/alsa-mixer.c:2820 #: spa/plugins/alsa/acp/alsa-mixer.c:2821 msgid "Chat Output" msgstr "Babil-eligo" #: spa/plugins/alsa/acp/alsa-mixer.c:2822 msgid "Chat Input" msgstr "Babil-enigo" #: spa/plugins/alsa/acp/alsa-mixer.c:2823 msgid "Virtual Surround 7.1" msgstr "Virtuala ĉirkaŭa sono 7.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4527 msgid "Analog Mono" msgstr "Analoga mono" #: spa/plugins/alsa/acp/alsa-mixer.c:4528 msgid "Analog Mono (Left)" msgstr "Analoga mono (maldekstra)" #: spa/plugins/alsa/acp/alsa-mixer.c:4529 msgid "Analog Mono (Right)" msgstr "Analoga mono (dekstra)" #. Note: Not translated to "Analog Stereo Input", because the source #. * name gets "Input" appended to it automatically, so adding "Input" #. * here would lead to the source name to become "Analog Stereo Input #. * Input". The same logic applies to analog-stereo-output, #. * multichannel-input and multichannel-output. #: spa/plugins/alsa/acp/alsa-mixer.c:4530 #: spa/plugins/alsa/acp/alsa-mixer.c:4538 #: spa/plugins/alsa/acp/alsa-mixer.c:4539 msgid "Analog Stereo" msgstr "Analoga stereo" #: spa/plugins/alsa/acp/alsa-mixer.c:4531 msgid "Mono" msgstr "Mono" #: spa/plugins/alsa/acp/alsa-mixer.c:4532 msgid "Stereo" msgstr "Stereo" #: spa/plugins/alsa/acp/alsa-mixer.c:4540 #: spa/plugins/alsa/acp/alsa-mixer.c:4698 #: spa/plugins/bluez5/bluez5-device.c:1135 msgid "Headset" msgstr "Kaptelefono" #: spa/plugins/alsa/acp/alsa-mixer.c:4541 #: spa/plugins/alsa/acp/alsa-mixer.c:4699 msgid "Speakerphone" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4542 #: spa/plugins/alsa/acp/alsa-mixer.c:4543 msgid "Multichannel" msgstr "Plurkanala" #: spa/plugins/alsa/acp/alsa-mixer.c:4544 msgid "Analog Surround 2.1" msgstr "Analoga ĉirkaŭa sono 2.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4545 msgid "Analog Surround 3.0" msgstr "Analoga ĉirkaŭa sono 3.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4546 msgid "Analog Surround 3.1" msgstr "Analoga ĉirkaŭa sono 3.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4547 msgid "Analog Surround 4.0" msgstr "Analoga ĉirkaŭa sono 4.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4548 msgid "Analog Surround 4.1" msgstr "Analoga ĉirkaŭa sono 4.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4549 msgid "Analog Surround 5.0" msgstr "Analoga ĉirkaŭa sono 5.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4550 msgid "Analog Surround 5.1" msgstr "Analoga ĉirkaŭa sono 5.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4551 msgid "Analog Surround 6.0" msgstr "Analoga ĉirkaŭa sono 6.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4552 msgid "Analog Surround 6.1" msgstr "Analoga ĉirkaŭa sono 6.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4553 msgid "Analog Surround 7.0" msgstr "Analoga ĉirkaŭa sono 7.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4554 msgid "Analog Surround 7.1" msgstr "Analoga ĉirkaŭa sono 7.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4555 msgid "Digital Stereo (IEC958)" msgstr "Cifera stereo (IEC958)" #: spa/plugins/alsa/acp/alsa-mixer.c:4556 msgid "Digital Surround 4.0 (IEC958/AC3)" msgstr "Cifera ĉirkaŭa sono 4.0 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4557 msgid "Digital Surround 5.1 (IEC958/AC3)" msgstr "Cifera ĉirkaŭa sono 5.1 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4558 msgid "Digital Surround 5.1 (IEC958/DTS)" msgstr "Cifera ĉirkaŭa sono 5.1 (IEC958/DTS)" #: spa/plugins/alsa/acp/alsa-mixer.c:4559 msgid "Digital Stereo (HDMI)" msgstr "Cifera stereo (HDMI)" #: spa/plugins/alsa/acp/alsa-mixer.c:4560 msgid "Digital Surround 5.1 (HDMI)" msgstr "Cifera ĉirkaŭa sono 5.1 (HDMI)" #: spa/plugins/alsa/acp/alsa-mixer.c:4561 msgid "Chat" msgstr "Babilo" #: spa/plugins/alsa/acp/alsa-mixer.c:4562 msgid "Game" msgstr "Ludo" #: spa/plugins/alsa/acp/alsa-mixer.c:4696 msgid "Analog Mono Duplex" msgstr "Analoga mono-dupleksa" #: spa/plugins/alsa/acp/alsa-mixer.c:4697 msgid "Analog Stereo Duplex" msgstr "Analoga stereo-dupleksa" #: spa/plugins/alsa/acp/alsa-mixer.c:4700 msgid "Digital Stereo Duplex (IEC958)" msgstr "Cifera stereo-dupleksa (IEC958)" #: spa/plugins/alsa/acp/alsa-mixer.c:4701 msgid "Multichannel Duplex" msgstr "Plurkanala dupleksa" #: spa/plugins/alsa/acp/alsa-mixer.c:4702 msgid "Stereo Duplex" msgstr "Stereo-dupleksa" #: spa/plugins/alsa/acp/alsa-mixer.c:4703 msgid "Mono Chat + 7.1 Surround" msgstr "Mono-babilo + 7.1 ĉirkaŭa sono" #: spa/plugins/alsa/acp/alsa-mixer.c:4806 #, c-format msgid "%s Output" msgstr "%s-eligo" #: spa/plugins/alsa/acp/alsa-mixer.c:4813 #, c-format msgid "%s Input" msgstr "%s-enigo" #: spa/plugins/alsa/acp/alsa-util.c:1175 spa/plugins/alsa/acp/alsa-util.c:1269 #, c-format msgid "" "snd_pcm_avail() returned a value that is exceptionally large: %lu byte (%lu " "ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_avail() returned a value that is exceptionally large: %lu bytes (%lu " "ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" msgstr[1] "" #: spa/plugins/alsa/acp/alsa-util.c:1241 #, c-format msgid "" "snd_pcm_delay() returned a value that is exceptionally large: %li byte (%s" "%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_delay() returned a value that is exceptionally large: %li bytes (%s" "%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" msgstr[1] "" #: spa/plugins/alsa/acp/alsa-util.c:1288 #, c-format msgid "" "snd_pcm_avail_delay() returned strange values: delay %lu is less than avail " "%lu.\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr "" #: spa/plugins/alsa/acp/alsa-util.c:1331 #, c-format msgid "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu byte " "(%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu bytes " "(%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" msgstr[1] "" #: spa/plugins/bluez5/bluez5-device.c:1010 msgid "Audio Gateway (A2DP Source & HSP/HFP AG)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1033 #, c-format msgid "High Fidelity Playback (A2DP Sink, codec %s)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1035 #, c-format msgid "High Fidelity Duplex (A2DP Source/Sink, codec %s)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1041 msgid "High Fidelity Playback (A2DP Sink)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1043 msgid "High Fidelity Duplex (A2DP Source/Sink)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1070 #, c-format msgid "Headset Head Unit (HSP/HFP, codec %s)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1074 msgid "Headset Head Unit (HSP/HFP)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1140 msgid "Handsfree" msgstr "Libermana" #: spa/plugins/bluez5/bluez5-device.c:1155 msgid "Headphone" msgstr "Kapaŭskultilo" #: spa/plugins/bluez5/bluez5-device.c:1160 msgid "Portable" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1165 msgid "Car" msgstr "Aŭto" #: spa/plugins/bluez5/bluez5-device.c:1170 msgid "HiFi" msgstr "HiFi" #: spa/plugins/bluez5/bluez5-device.c:1175 msgid "Phone" msgstr "Telefono" #: spa/plugins/bluez5/bluez5-device.c:1181 msgid "Bluetooth" msgstr "Bludento" pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/po/es.po000066400000000000000000000430111511204443500220620ustar00rootroot00000000000000# Fedora Spanish translation of PipeWire. # This file is distributed under the same license as the PipeWire Package. # # Domingo Becker , 2009. # Héctor Daniel Cabrera , 2009. # Fernando Gonzalez Blanco , 2009, 2012. # Alberto Castillo , 2016. #zanata # Gladys Guerrero Lozano , 2016. #zanata # Máximo Castañeda Riloba , 2016. #zanata # Wim Taymans , 2016. #zanata msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/pipewire/" "issues/new\n" "POT-Creation-Date: 2021-04-18 16:54+0800\n" "PO-Revision-Date: 2020-10-01 15:30+0000\n" "Last-Translator: Emilio Herrera \n" "Language-Team: Spanish \n" "Language: es\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" "X-Generator: Weblate 4.2.2\n" "X-Poedit-Language: Spanish\n" #: src/daemon/pipewire.c:43 #, c-format msgid "" "%s [options]\n" " -h, --help Show this help\n" " --version Show version\n" " -c, --config Load config (Default %s)\n" msgstr "" #: src/daemon/pipewire.desktop.in:4 msgid "PipeWire Media System" msgstr "" #: src/daemon/pipewire.desktop.in:5 msgid "Start the PipeWire Media System" msgstr "" #: src/examples/media-session/alsa-monitor.c:526 #: spa/plugins/alsa/acp/compat.c:187 msgid "Built-in Audio" msgstr "Audio Interno" #: src/examples/media-session/alsa-monitor.c:530 #: spa/plugins/alsa/acp/compat.c:192 msgid "Modem" msgstr "Módem" #: src/examples/media-session/alsa-monitor.c:539 msgid "Unknown device" msgstr "" #: src/tools/pw-cat.c:991 #, c-format msgid "" "%s [options] \n" " -h, --help Show this help\n" " --version Show version\n" " -v, --verbose Enable verbose operations\n" "\n" msgstr "" #: src/tools/pw-cat.c:998 #, c-format msgid "" " -R, --remote Remote daemon name\n" " --media-type Set media type (default %s)\n" " --media-category Set media category (default %s)\n" " --media-role Set media role (default %s)\n" " --target Set node target (default %s)\n" " 0 means don't link\n" " --latency Set node latency (default %s)\n" " Xunit (unit = s, ms, us, ns)\n" " or direct samples (256)\n" " the rate is the one of the source " "file\n" " --list-targets List available targets for --target\n" "\n" msgstr "" #: src/tools/pw-cat.c:1016 #, c-format msgid "" " --rate Sample rate (req. for rec) (default " "%u)\n" " --channels Number of channels (req. for rec) " "(default %u)\n" " --channel-map Channel map\n" " one of: \"stereo\", " "\"surround-51\",... or\n" " comma separated list of channel " "names: eg. \"FL,FR\"\n" " --format Sample format %s (req. for rec) " "(default %s)\n" " --volume Stream volume 0-1.0 (default %.3f)\n" " -q --quality Resampler quality (0 - 15) (default " "%d)\n" "\n" msgstr "" #: src/tools/pw-cat.c:1033 msgid "" " -p, --playback Playback mode\n" " -r, --record Recording mode\n" " -m, --midi Midi mode\n" "\n" msgstr "" #: src/tools/pw-cli.c:2932 #, c-format msgid "" "%s [options] [command]\n" " -h, --help Show this help\n" " --version Show version\n" " -d, --daemon Start as daemon (Default false)\n" " -r, --remote Remote daemon name\n" "\n" msgstr "" #: spa/plugins/alsa/acp/acp.c:290 msgid "Pro Audio" msgstr "" #: spa/plugins/alsa/acp/acp.c:411 spa/plugins/alsa/acp/alsa-mixer.c:4704 #: spa/plugins/bluez5/bluez5-device.c:1000 msgid "Off" msgstr "Apagado" #: spa/plugins/alsa/acp/channelmap.h:466 msgid "(invalid)" msgstr "(inválido)" #: spa/plugins/alsa/acp/alsa-mixer.c:2709 msgid "Input" msgstr "Entrada" #: spa/plugins/alsa/acp/alsa-mixer.c:2710 msgid "Docking Station Input" msgstr "Entrada de estación de acoplamiento" #: spa/plugins/alsa/acp/alsa-mixer.c:2711 msgid "Docking Station Microphone" msgstr "Micrófono de estación de acoplamiento" #: spa/plugins/alsa/acp/alsa-mixer.c:2712 msgid "Docking Station Line In" msgstr "Línea de entrada de estación de acoplamiento" #: spa/plugins/alsa/acp/alsa-mixer.c:2713 #: spa/plugins/alsa/acp/alsa-mixer.c:2804 msgid "Line In" msgstr "Línea de entrada" #: spa/plugins/alsa/acp/alsa-mixer.c:2714 #: spa/plugins/alsa/acp/alsa-mixer.c:2798 #: spa/plugins/bluez5/bluez5-device.c:1145 msgid "Microphone" msgstr "Micrófono" #: spa/plugins/alsa/acp/alsa-mixer.c:2715 #: spa/plugins/alsa/acp/alsa-mixer.c:2799 msgid "Front Microphone" msgstr "Micrófono frontal" #: spa/plugins/alsa/acp/alsa-mixer.c:2716 #: spa/plugins/alsa/acp/alsa-mixer.c:2800 msgid "Rear Microphone" msgstr "Micrófono trasero" #: spa/plugins/alsa/acp/alsa-mixer.c:2717 msgid "External Microphone" msgstr "Micrófono externo" #: spa/plugins/alsa/acp/alsa-mixer.c:2718 #: spa/plugins/alsa/acp/alsa-mixer.c:2802 msgid "Internal Microphone" msgstr "Micrófono interno" #: spa/plugins/alsa/acp/alsa-mixer.c:2719 #: spa/plugins/alsa/acp/alsa-mixer.c:2805 msgid "Radio" msgstr "Radio" #: spa/plugins/alsa/acp/alsa-mixer.c:2720 #: spa/plugins/alsa/acp/alsa-mixer.c:2806 msgid "Video" msgstr "Vídeo" #: spa/plugins/alsa/acp/alsa-mixer.c:2721 msgid "Automatic Gain Control" msgstr "Control automático de ganancia" #: spa/plugins/alsa/acp/alsa-mixer.c:2722 msgid "No Automatic Gain Control" msgstr "Sin control automático de ganancia" #: spa/plugins/alsa/acp/alsa-mixer.c:2723 msgid "Boost" msgstr "Incremento de ganancia" #: spa/plugins/alsa/acp/alsa-mixer.c:2724 msgid "No Boost" msgstr "Sin incremento de ganancia" #: spa/plugins/alsa/acp/alsa-mixer.c:2725 msgid "Amplifier" msgstr "Amplificador" #: spa/plugins/alsa/acp/alsa-mixer.c:2726 msgid "No Amplifier" msgstr "Sin amplificador" #: spa/plugins/alsa/acp/alsa-mixer.c:2727 msgid "Bass Boost" msgstr "Potenciador de graves" #: spa/plugins/alsa/acp/alsa-mixer.c:2728 msgid "No Bass Boost" msgstr "Sin potenciador de graves" #: spa/plugins/alsa/acp/alsa-mixer.c:2729 #: spa/plugins/bluez5/bluez5-device.c:1150 msgid "Speaker" msgstr "Altavoz" #: spa/plugins/alsa/acp/alsa-mixer.c:2730 #: spa/plugins/alsa/acp/alsa-mixer.c:2808 msgid "Headphones" msgstr "Auriculares" #: spa/plugins/alsa/acp/alsa-mixer.c:2797 msgid "Analog Input" msgstr "Entrada analógica" #: spa/plugins/alsa/acp/alsa-mixer.c:2801 msgid "Dock Microphone" msgstr "Micrófono de estación de acoplamiento" #: spa/plugins/alsa/acp/alsa-mixer.c:2803 msgid "Headset Microphone" msgstr "Micrófono acoplado a auriculares" #: spa/plugins/alsa/acp/alsa-mixer.c:2807 msgid "Analog Output" msgstr "Salida analógica" #: spa/plugins/alsa/acp/alsa-mixer.c:2809 #, fuzzy msgid "Headphones 2" msgstr "Auriculares" #: spa/plugins/alsa/acp/alsa-mixer.c:2810 #, fuzzy msgid "Headphones Mono Output" msgstr "Salida mono analógica " #: spa/plugins/alsa/acp/alsa-mixer.c:2811 msgid "Line Out" msgstr "Línea de salida" #: spa/plugins/alsa/acp/alsa-mixer.c:2812 msgid "Analog Mono Output" msgstr "Salida mono analógica " #: spa/plugins/alsa/acp/alsa-mixer.c:2813 msgid "Speakers" msgstr "Altavoces" #: spa/plugins/alsa/acp/alsa-mixer.c:2814 msgid "HDMI / DisplayPort" msgstr "HDMI / DisplayPort" #: spa/plugins/alsa/acp/alsa-mixer.c:2815 msgid "Digital Output (S/PDIF)" msgstr "Salida digital (S/PDIF)" #: spa/plugins/alsa/acp/alsa-mixer.c:2816 msgid "Digital Input (S/PDIF)" msgstr "Entrada digital (S/PDIF)" #: spa/plugins/alsa/acp/alsa-mixer.c:2817 #, fuzzy msgid "Multichannel Input" msgstr "Multicanal" #: spa/plugins/alsa/acp/alsa-mixer.c:2818 #, fuzzy msgid "Multichannel Output" msgstr "Multicanal" #: spa/plugins/alsa/acp/alsa-mixer.c:2819 #, fuzzy msgid "Game Output" msgstr "Salida %s" #: spa/plugins/alsa/acp/alsa-mixer.c:2820 #: spa/plugins/alsa/acp/alsa-mixer.c:2821 #, fuzzy msgid "Chat Output" msgstr "Salida %s" #: spa/plugins/alsa/acp/alsa-mixer.c:2822 #, fuzzy msgid "Chat Input" msgstr "Entrada %s" #: spa/plugins/alsa/acp/alsa-mixer.c:2823 #, fuzzy msgid "Virtual Surround 7.1" msgstr "Destino envolvente virtual" #: spa/plugins/alsa/acp/alsa-mixer.c:4527 msgid "Analog Mono" msgstr "Mono analógico" #: spa/plugins/alsa/acp/alsa-mixer.c:4528 #, fuzzy msgid "Analog Mono (Left)" msgstr "Mono analógico" #: spa/plugins/alsa/acp/alsa-mixer.c:4529 #, fuzzy msgid "Analog Mono (Right)" msgstr "Mono analógico" #. Note: Not translated to "Analog Stereo Input", because the source #. * name gets "Input" appended to it automatically, so adding "Input" #. * here would lead to the source name to become "Analog Stereo Input #. * Input". The same logic applies to analog-stereo-output, #. * multichannel-input and multichannel-output. #: spa/plugins/alsa/acp/alsa-mixer.c:4530 #: spa/plugins/alsa/acp/alsa-mixer.c:4538 #: spa/plugins/alsa/acp/alsa-mixer.c:4539 msgid "Analog Stereo" msgstr "Estéreo analógico" #: spa/plugins/alsa/acp/alsa-mixer.c:4531 msgid "Mono" msgstr "Mono" #: spa/plugins/alsa/acp/alsa-mixer.c:4532 msgid "Stereo" msgstr "Estéreo" #: spa/plugins/alsa/acp/alsa-mixer.c:4540 #: spa/plugins/alsa/acp/alsa-mixer.c:4698 #: spa/plugins/bluez5/bluez5-device.c:1135 msgid "Headset" msgstr "Auriculares" #: spa/plugins/alsa/acp/alsa-mixer.c:4541 #: spa/plugins/alsa/acp/alsa-mixer.c:4699 #, fuzzy msgid "Speakerphone" msgstr "Altavoz" #: spa/plugins/alsa/acp/alsa-mixer.c:4542 #: spa/plugins/alsa/acp/alsa-mixer.c:4543 msgid "Multichannel" msgstr "Multicanal" #: spa/plugins/alsa/acp/alsa-mixer.c:4544 msgid "Analog Surround 2.1" msgstr "Envolvente analógico 2.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4545 msgid "Analog Surround 3.0" msgstr "Envolvente analógico 3.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4546 msgid "Analog Surround 3.1" msgstr "Envolvente analógico 3.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4547 msgid "Analog Surround 4.0" msgstr "Envolvente analógico 4.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4548 msgid "Analog Surround 4.1" msgstr "Envolvente análogico 4.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4549 msgid "Analog Surround 5.0" msgstr "Envolvente analógico 5.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4550 msgid "Analog Surround 5.1" msgstr "Envolvente analógico 5.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4551 msgid "Analog Surround 6.0" msgstr "Envolvente analógico 6.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4552 msgid "Analog Surround 6.1" msgstr "Envolvente analógico 6.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4553 msgid "Analog Surround 7.0" msgstr "Envolvente analógico 7.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4554 msgid "Analog Surround 7.1" msgstr "Envolvente analógico 7.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4555 msgid "Digital Stereo (IEC958)" msgstr "Estéreo digital (IEC958)" #: spa/plugins/alsa/acp/alsa-mixer.c:4556 msgid "Digital Surround 4.0 (IEC958/AC3)" msgstr "Envolvente digital 4.0 (IEC9588/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4557 msgid "Digital Surround 5.1 (IEC958/AC3)" msgstr "Envolvente digital 5.1 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4558 msgid "Digital Surround 5.1 (IEC958/DTS)" msgstr "Envolvente digital 5.1 (IEC958/DTS)" #: spa/plugins/alsa/acp/alsa-mixer.c:4559 msgid "Digital Stereo (HDMI)" msgstr "Estéreo digital (HDMI)" #: spa/plugins/alsa/acp/alsa-mixer.c:4560 msgid "Digital Surround 5.1 (HDMI)" msgstr "Envolvente digital 5.1 (HDMI)" #: spa/plugins/alsa/acp/alsa-mixer.c:4561 msgid "Chat" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4562 msgid "Game" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4696 msgid "Analog Mono Duplex" msgstr "Mono analógico dúplex" #: spa/plugins/alsa/acp/alsa-mixer.c:4697 msgid "Analog Stereo Duplex" msgstr "Estéreo analógico dúplex" #: spa/plugins/alsa/acp/alsa-mixer.c:4700 msgid "Digital Stereo Duplex (IEC958)" msgstr "Estéreo digital dúplex (IEC958)" #: spa/plugins/alsa/acp/alsa-mixer.c:4701 #, fuzzy msgid "Multichannel Duplex" msgstr "Multicanal" #: spa/plugins/alsa/acp/alsa-mixer.c:4702 #, fuzzy msgid "Stereo Duplex" msgstr "Estéreo analógico dúplex" #: spa/plugins/alsa/acp/alsa-mixer.c:4703 msgid "Mono Chat + 7.1 Surround" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4806 #, c-format msgid "%s Output" msgstr "Salida %s" #: spa/plugins/alsa/acp/alsa-mixer.c:4813 #, c-format msgid "%s Input" msgstr "Entrada %s" #: spa/plugins/alsa/acp/alsa-util.c:1175 spa/plugins/alsa/acp/alsa-util.c:1269 #, fuzzy, c-format msgid "" "snd_pcm_avail() returned a value that is exceptionally large: %lu byte (%lu " "ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_avail() returned a value that is exceptionally large: %lu bytes (%lu " "ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "snd_pcm_avail() devolvió un valor que es excepcionalmente grande: %lu bytes " "(%lu ms).\n" "Lo más probable es que sea un error del controlador ALSA '%s'. Por favor, " "informe de esto a los desarrolladores de ALSA." msgstr[1] "" "snd_pcm_avail() devolvió un valor que es excepcionalmente grande: %lu bytes " "(%lu ms).\n" "Lo más probable es que sea un error del controlador ALSA '%s'. Por favor, " "informe de esto a los desarrolladores de ALSA." #: spa/plugins/alsa/acp/alsa-util.c:1241 #, fuzzy, c-format msgid "" "snd_pcm_delay() returned a value that is exceptionally large: %li byte (%s" "%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_delay() returned a value that is exceptionally large: %li bytes (%s" "%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "snd_pcm_delay() devolvió un valor que es excepcionalmente grande: %li bytes " "(%s%lu ms).\n" "Lo más probable es que sea un error del controlador ALSA '%s'. Por favor, " "informe de esto a los desarrolladores de ALSA." msgstr[1] "" "snd_pcm_delay() devolvió un valor que es excepcionalmente grande: %li bytes " "(%s%lu ms).\n" "Lo más probable es que sea un error del controlador ALSA '%s'. Por favor, " "informe de esto a los desarrolladores de ALSA." #: spa/plugins/alsa/acp/alsa-util.c:1288 #, c-format msgid "" "snd_pcm_avail_delay() returned strange values: delay %lu is less than avail " "%lu.\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr "" "snd_pcm_avail_delay() devolvió valores raros: %lu es menor que los " "disponibles %lu.\n" "Lo más probable es que sea un error del controlador ALSA '%s'. Por favor, " "informe de esto a los desarrolladores de ALSA." #: spa/plugins/alsa/acp/alsa-util.c:1331 #, fuzzy, c-format msgid "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu byte " "(%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu bytes " "(%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "snd_pcm_mmap_begin() devolvió un valor que es excepcionalmente grande: %lu " "bytes (%lu ms).\n" "Lo más probable es que sea un error del controlador ALSA '%s'. Por favor, " "informe de esto a los desarrolladores de ALSA." msgstr[1] "" "snd_pcm_mmap_begin() devolvió un valor que es excepcionalmente grande: %lu " "bytes (%lu ms).\n" "Lo más probable es que sea un error del controlador ALSA '%s'. Por favor, " "informe de esto a los desarrolladores de ALSA." #: spa/plugins/bluez5/bluez5-device.c:1010 msgid "Audio Gateway (A2DP Source & HSP/HFP AG)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1033 #, c-format msgid "High Fidelity Playback (A2DP Sink, codec %s)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1035 #, c-format msgid "High Fidelity Duplex (A2DP Source/Sink, codec %s)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1041 msgid "High Fidelity Playback (A2DP Sink)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1043 msgid "High Fidelity Duplex (A2DP Source/Sink)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1070 #, c-format msgid "Headset Head Unit (HSP/HFP, codec %s)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1074 msgid "Headset Head Unit (HSP/HFP)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1140 msgid "Handsfree" msgstr "Manos libres" #: spa/plugins/bluez5/bluez5-device.c:1155 msgid "Headphone" msgstr "Auriculares" #: spa/plugins/bluez5/bluez5-device.c:1160 msgid "Portable" msgstr "Portátil" #: spa/plugins/bluez5/bluez5-device.c:1165 msgid "Car" msgstr "Coche" #: spa/plugins/bluez5/bluez5-device.c:1170 msgid "HiFi" msgstr "Alta fidelidad" #: spa/plugins/bluez5/bluez5-device.c:1175 msgid "Phone" msgstr "Teléfono" #: spa/plugins/bluez5/bluez5-device.c:1181 #, fuzzy msgid "Bluetooth" msgstr "Entrada bluetooth" pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/po/fi.po000066400000000000000000000546271511204443500220700ustar00rootroot00000000000000# pipewire translation to Finnish (fi). # Copyright (C) 2008 Timo Jyrinki # This file is distributed under the same license as the pipewire package. # Timo Jyrinki , 2008. # Ville-Pekka Vainio , 2009, 2012. # msgid "" msgstr "" "Project-Id-Version: git trunk\n" "Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/pipewire/issues/new\n" "POT-Creation-Date: 2024-10-12 11:50+0300\n" "PO-Revision-Date: 2024-10-12 12:04+0300\n" "Last-Translator: Pauli Virtanen \n" "Language-Team: Finnish \n" "Language: fi\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" "X-Generator: Weblate 4.5.1\n" #: src/daemon/pipewire.c:29 #, c-format msgid "" "%s [options]\n" " -h, --help Show this help\n" " -v, --verbose Increase verbosity by one level\n" " --version Show version\n" " -c, --config Load config (Default %s)\n" " -P --properties Set context properties\n" msgstr "" "%s [valinnat]\n" " -h, --help Näytä tämä ohje\n" " -v, --verbose Lisää viestien yksityiskohtaisuutta\n" " --version Näytä versio\n" " -c, --config Lataa asetukset (oletus %s)\n" " -P, --properties Aseta kontekstin ominaisuudet\n" #: src/daemon/pipewire.desktop.in:4 msgid "PipeWire Media System" msgstr "PipeWire-mediajärjestelmä" #: src/daemon/pipewire.desktop.in:5 msgid "Start the PipeWire Media System" msgstr "Käynnistä PipeWire-mediajärjestelmä" #: src/modules/module-protocol-pulse/modules/module-tunnel-sink.c:159 #: src/modules/module-protocol-pulse/modules/module-tunnel-source.c:159 #, c-format msgid "Tunnel to %s%s%s" msgstr "Tunneli: %s%s%s" #: src/modules/module-fallback-sink.c:40 msgid "Dummy Output" msgstr "Valeulostulo" #: src/modules/module-pulse-tunnel.c:777 #, c-format msgid "Tunnel for %s@%s" msgstr "Tunneli: %s@%s" #: src/modules/module-zeroconf-discover.c:320 msgid "Unknown device" msgstr "Tuntematon laite" #: src/modules/module-zeroconf-discover.c:332 #, c-format msgid "%s on %s@%s" msgstr "%s koneella %s@%s" #: src/modules/module-zeroconf-discover.c:336 #, c-format msgid "%s on %s" msgstr "%s koneella %s" #: src/tools/pw-cat.c:973 #, c-format msgid "" "%s [options] [|-]\n" " -h, --help Show this help\n" " --version Show version\n" " -v, --verbose Enable verbose operations\n" "\n" msgstr "" "%s [valinnat] [|-]\n" " -h, --help Näytä tämä ohje\n" " --version Näytä versio\n" " -v, --verbose Näytä lisää tietoja\n" "\n" #: src/tools/pw-cat.c:980 #, c-format msgid "" " -R, --remote Remote daemon name\n" " --media-type Set media type (default %s)\n" " --media-category Set media category (default %s)\n" " --media-role Set media role (default %s)\n" " --target Set node target serial or name " "(default %s)\n" " 0 means don't link\n" " --latency Set node latency (default %s)\n" " Xunit (unit = s, ms, us, ns)\n" " or direct samples (256)\n" " the rate is the one of the source " "file\n" " -P --properties Set node properties\n" "\n" msgstr "" " -R, --remote Vastapään taustaprosessin nimi\n" " --media-type Aseta mediatyyppi (oletus %s)\n" " --media-category Aseta medialuokka (oletus %s)\n" " --media-role Aseta mediarooli (oletus %s)\n" " --target Aseta kohteen numero/nimi (oletus %s)\n" " 0 tarkoittaa: ei linkkiä\n" " --latency Aseta solmun viive (oletus %s)\n" " Xyksikkö (yksikkö = s, ms, us, ns)\n" " tai näytteiden lukumäärä (256)\n" " näytetaajuus on tiedoston mukainen\n" " -P --properties Aseta solmun ominaisuudet\n" "\n" #: src/tools/pw-cat.c:998 #, c-format msgid "" " --rate Sample rate (req. for rec) (default " "%u)\n" " --channels Number of channels (req. for rec) " "(default %u)\n" " --channel-map Channel map\n" " one of: \"stereo\", " "\"surround-51\",... or\n" " comma separated list of channel " "names: eg. \"FL,FR\"\n" " --format Sample format %s (req. for rec) " "(default %s)\n" " --volume Stream volume 0-1.0 (default %.3f)\n" " -q --quality Resampler quality (0 - 15) (default " "%d)\n" " -a, --raw RAW mode\n" "\n" msgstr "" " --rate Näytetaajuus (pakoll. nauhoit.) (oletus %u)\n" " --channels Kanavien määrä (pakoll. nauhoit.) (oletus %u)\n" " --channel-map Kanavakartta\n" " vaihtoehdot: \"stereo\", \"surround-51\",... tai\n" " pilkulla erotetut kanavien nimet: esim. \"FL,FR\"\n" " --format Näytemuoto %s (pakoll. nauhoit.) (oletus %s)\n" " --volume Vuon äänenvoimakkuus 0-1.0 (oletus %.3f)\n" " -q --quality Resamplerin laatu (0 - 15) (oletus %d)\n" " -a --raw Muotoilemattoman äänidatan tila\n" "\n" #: src/tools/pw-cat.c:1016 msgid "" " -p, --playback Playback mode\n" " -r, --record Recording mode\n" " -m, --midi Midi mode\n" " -d, --dsd DSD mode\n" " -o, --encoded Encoded mode\n" "\n" msgstr "" " -p, --playback Toisto\n" " -r, --record Nauhoitus\n" " -m, --midi MIDI-tila\n" " -d, --dsd DSD-tila\n" " -o, --encoded Koodatun audion tila\n" "\n" #: src/tools/pw-cli.c:2318 #, c-format msgid "" "%s [options] [command]\n" " -h, --help Show this help\n" " --version Show version\n" " -d, --daemon Start as daemon (Default false)\n" " -r, --remote Remote daemon name\n" " -m, --monitor Monitor activity\n" "\n" msgstr "" "%s [valinnat] [komento]\n" " -h, --help Näytä tämä ohje\n" " --version Näytä versio\n" " -d, --daemon Käynnistä taustaprosessina (oletus: ei)\n" " -r, --remote Taustaprosessin nimi\n" " -m, --monitor Seuraa tapahtumia\n" "\n" #: spa/plugins/alsa/acp/acp.c:327 msgid "Pro Audio" msgstr "Pro-audio" #: spa/plugins/alsa/acp/acp.c:487 spa/plugins/alsa/acp/alsa-mixer.c:4633 #: spa/plugins/bluez5/bluez5-device.c:1696 msgid "Off" msgstr "Poissa" #: spa/plugins/alsa/acp/alsa-mixer.c:2652 msgid "Input" msgstr "Sisääntulo" #: spa/plugins/alsa/acp/alsa-mixer.c:2653 msgid "Docking Station Input" msgstr "Telakan sisääntulo" #: spa/plugins/alsa/acp/alsa-mixer.c:2654 msgid "Docking Station Microphone" msgstr "Telakan mikrofoni" #: spa/plugins/alsa/acp/alsa-mixer.c:2655 msgid "Docking Station Line In" msgstr "Telakan linjasisääntulo" #: spa/plugins/alsa/acp/alsa-mixer.c:2656 #: spa/plugins/alsa/acp/alsa-mixer.c:2747 msgid "Line In" msgstr "Linjasisääntulo" #: spa/plugins/alsa/acp/alsa-mixer.c:2657 #: spa/plugins/alsa/acp/alsa-mixer.c:2741 #: spa/plugins/bluez5/bluez5-device.c:1984 msgid "Microphone" msgstr "Mikrofoni" #: spa/plugins/alsa/acp/alsa-mixer.c:2658 #: spa/plugins/alsa/acp/alsa-mixer.c:2742 msgid "Front Microphone" msgstr "Etumikrofoni" #: spa/plugins/alsa/acp/alsa-mixer.c:2659 #: spa/plugins/alsa/acp/alsa-mixer.c:2743 msgid "Rear Microphone" msgstr "Takamikrofoni" #: spa/plugins/alsa/acp/alsa-mixer.c:2660 msgid "External Microphone" msgstr "Ulkoinen mikrofoni" #: spa/plugins/alsa/acp/alsa-mixer.c:2661 #: spa/plugins/alsa/acp/alsa-mixer.c:2745 msgid "Internal Microphone" msgstr "Sisäinen mikrofoni" #: spa/plugins/alsa/acp/alsa-mixer.c:2662 #: spa/plugins/alsa/acp/alsa-mixer.c:2748 msgid "Radio" msgstr "Radio" #: spa/plugins/alsa/acp/alsa-mixer.c:2663 #: spa/plugins/alsa/acp/alsa-mixer.c:2749 msgid "Video" msgstr "Video" #: spa/plugins/alsa/acp/alsa-mixer.c:2664 msgid "Automatic Gain Control" msgstr "Automaattinen äänenvoimakkuuden säätö" #: spa/plugins/alsa/acp/alsa-mixer.c:2665 msgid "No Automatic Gain Control" msgstr "Ei automaattista äänenvoimakkuuden säätöä" #: spa/plugins/alsa/acp/alsa-mixer.c:2666 msgid "Boost" msgstr "Vahvistus" #: spa/plugins/alsa/acp/alsa-mixer.c:2667 msgid "No Boost" msgstr "Ei vahvistusta" #: spa/plugins/alsa/acp/alsa-mixer.c:2668 msgid "Amplifier" msgstr "Vahvistin" #: spa/plugins/alsa/acp/alsa-mixer.c:2669 msgid "No Amplifier" msgstr "Ei vahvistinta" #: spa/plugins/alsa/acp/alsa-mixer.c:2670 msgid "Bass Boost" msgstr "Bassonvahvistus" #: spa/plugins/alsa/acp/alsa-mixer.c:2671 msgid "No Bass Boost" msgstr "Ei basson vahvistusta" #: spa/plugins/alsa/acp/alsa-mixer.c:2672 #: spa/plugins/bluez5/bluez5-device.c:1990 msgid "Speaker" msgstr "Kaiutin" #: spa/plugins/alsa/acp/alsa-mixer.c:2673 #: spa/plugins/alsa/acp/alsa-mixer.c:2751 msgid "Headphones" msgstr "Kuulokkeet" #: spa/plugins/alsa/acp/alsa-mixer.c:2740 msgid "Analog Input" msgstr "Analoginen sisääntulo" #: spa/plugins/alsa/acp/alsa-mixer.c:2744 msgid "Dock Microphone" msgstr "Telakan mikrofoni" #: spa/plugins/alsa/acp/alsa-mixer.c:2746 msgid "Headset Microphone" msgstr "Kuulokkeiden mikrofoni" #: spa/plugins/alsa/acp/alsa-mixer.c:2750 msgid "Analog Output" msgstr "Analoginen ulostulo" #: spa/plugins/alsa/acp/alsa-mixer.c:2752 msgid "Headphones 2" msgstr "Kuulokkeet 2" #: spa/plugins/alsa/acp/alsa-mixer.c:2753 msgid "Headphones Mono Output" msgstr "Kuulokkeiden monoulostulo" #: spa/plugins/alsa/acp/alsa-mixer.c:2754 msgid "Line Out" msgstr "Linjaulostulo" #: spa/plugins/alsa/acp/alsa-mixer.c:2755 msgid "Analog Mono Output" msgstr "Analoginen monoulostulo" #: spa/plugins/alsa/acp/alsa-mixer.c:2756 msgid "Speakers" msgstr "Kaiuttimet" #: spa/plugins/alsa/acp/alsa-mixer.c:2757 msgid "HDMI / DisplayPort" msgstr "HDMI / DisplayPort" #: spa/plugins/alsa/acp/alsa-mixer.c:2758 msgid "Digital Output (S/PDIF)" msgstr "Digitaalinen ulostulo (S/PDIF)" #: spa/plugins/alsa/acp/alsa-mixer.c:2759 msgid "Digital Input (S/PDIF)" msgstr "Digitaalinen sisääntulo (S/PDIF)" #: spa/plugins/alsa/acp/alsa-mixer.c:2760 msgid "Multichannel Input" msgstr "Monikanavainen sisääntulo" #: spa/plugins/alsa/acp/alsa-mixer.c:2761 msgid "Multichannel Output" msgstr "Monikanavainen ulostulo" #: spa/plugins/alsa/acp/alsa-mixer.c:2762 msgid "Game Output" msgstr "Peli-ulostulo" #: spa/plugins/alsa/acp/alsa-mixer.c:2763 #: spa/plugins/alsa/acp/alsa-mixer.c:2764 msgid "Chat Output" msgstr "Puhe-ulostulo" #: spa/plugins/alsa/acp/alsa-mixer.c:2765 msgid "Chat Input" msgstr "Puhe-sisääntulo" #: spa/plugins/alsa/acp/alsa-mixer.c:2766 msgid "Virtual Surround 7.1" msgstr "Virtuaalinen tilaääni 7.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4456 msgid "Analog Mono" msgstr "Analoginen mono" #: spa/plugins/alsa/acp/alsa-mixer.c:4457 msgid "Analog Mono (Left)" msgstr "Analoginen mono (vasen)" #: spa/plugins/alsa/acp/alsa-mixer.c:4458 msgid "Analog Mono (Right)" msgstr "Analoginen mono (oikea)" #. Note: Not translated to "Analog Stereo Input", because the source #. * name gets "Input" appended to it automatically, so adding "Input" #. * here would lead to the source name to become "Analog Stereo Input #. * Input". The same logic applies to analog-stereo-output, #. * multichannel-input and multichannel-output. #: spa/plugins/alsa/acp/alsa-mixer.c:4459 #: spa/plugins/alsa/acp/alsa-mixer.c:4467 #: spa/plugins/alsa/acp/alsa-mixer.c:4468 msgid "Analog Stereo" msgstr "Analoginen stereo" #: spa/plugins/alsa/acp/alsa-mixer.c:4460 msgid "Mono" msgstr "Mono" #: spa/plugins/alsa/acp/alsa-mixer.c:4461 msgid "Stereo" msgstr "Stereo" #: spa/plugins/alsa/acp/alsa-mixer.c:4469 #: spa/plugins/alsa/acp/alsa-mixer.c:4627 #: spa/plugins/bluez5/bluez5-device.c:1972 msgid "Headset" msgstr "Kuulokkeet" #: spa/plugins/alsa/acp/alsa-mixer.c:4470 #: spa/plugins/alsa/acp/alsa-mixer.c:4628 msgid "Speakerphone" msgstr "Kaiutinpuhelin" #: spa/plugins/alsa/acp/alsa-mixer.c:4471 #: spa/plugins/alsa/acp/alsa-mixer.c:4472 msgid "Multichannel" msgstr "Monikanavainen" #: spa/plugins/alsa/acp/alsa-mixer.c:4473 msgid "Analog Surround 2.1" msgstr "Analoginen tilaääni 2.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4474 msgid "Analog Surround 3.0" msgstr "Analoginen tilaääni 3.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4475 msgid "Analog Surround 3.1" msgstr "Analoginen tilaääni 3.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4476 msgid "Analog Surround 4.0" msgstr "Analoginen tilaääni 4.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4477 msgid "Analog Surround 4.1" msgstr "Analoginen tilaääni 4.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4478 msgid "Analog Surround 5.0" msgstr "Analoginen tilaääni 5.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4479 msgid "Analog Surround 5.1" msgstr "Analoginen tilaääni 5.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4480 msgid "Analog Surround 6.0" msgstr "Analoginen tilaääni 6.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4481 msgid "Analog Surround 6.1" msgstr "Analoginen tilaääni 6.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4482 msgid "Analog Surround 7.0" msgstr "Analoginen tilaääni 7.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4483 msgid "Analog Surround 7.1" msgstr "Analoginen tilaääni 7.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4484 msgid "Digital Stereo (IEC958)" msgstr "Digitaalinen stereo (IEC958)" #: spa/plugins/alsa/acp/alsa-mixer.c:4485 msgid "Digital Surround 4.0 (IEC958/AC3)" msgstr "Digitaalinen tilaääni 4.0 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4486 msgid "Digital Surround 5.1 (IEC958/AC3)" msgstr "Digitaalinen tilaääni 5.1 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4487 msgid "Digital Surround 5.1 (IEC958/DTS)" msgstr "Digitaalinen tilaääni 5.1 (IEC958/DTS)" #: spa/plugins/alsa/acp/alsa-mixer.c:4488 msgid "Digital Stereo (HDMI)" msgstr "Digitaalinen stereo (HDMI)" #: spa/plugins/alsa/acp/alsa-mixer.c:4489 msgid "Digital Surround 5.1 (HDMI)" msgstr "Digitaalinen tilaääni 5.1 (HDMI)" #: spa/plugins/alsa/acp/alsa-mixer.c:4490 msgid "Chat" msgstr "Puhe" #: spa/plugins/alsa/acp/alsa-mixer.c:4491 msgid "Game" msgstr "Peli" #: spa/plugins/alsa/acp/alsa-mixer.c:4625 msgid "Analog Mono Duplex" msgstr "Analoginen mono, molempisuuntainen" #: spa/plugins/alsa/acp/alsa-mixer.c:4626 msgid "Analog Stereo Duplex" msgstr "Analoginen stereo, molempisuuntainen" #: spa/plugins/alsa/acp/alsa-mixer.c:4629 msgid "Digital Stereo Duplex (IEC958)" msgstr "Digitaalinen stereo, molempisuuntainen (IEC958)" #: spa/plugins/alsa/acp/alsa-mixer.c:4630 msgid "Multichannel Duplex" msgstr "Monikanavainen, molempisuuntainen" #: spa/plugins/alsa/acp/alsa-mixer.c:4631 msgid "Stereo Duplex" msgstr "Stereo, molempisuuntainen" #: spa/plugins/alsa/acp/alsa-mixer.c:4632 msgid "Mono Chat + 7.1 Surround" msgstr "Mono-puhe + 7.1 tilaääni" #: spa/plugins/alsa/acp/alsa-mixer.c:4733 #, c-format msgid "%s Output" msgstr "%s, ulostulo" #: spa/plugins/alsa/acp/alsa-mixer.c:4741 #, c-format msgid "%s Input" msgstr "%s, sisääntulo" #: spa/plugins/alsa/acp/alsa-util.c:1231 spa/plugins/alsa/acp/alsa-util.c:1325 #, c-format msgid "" "snd_pcm_avail() returned a value that is exceptionally large: %lu byte (%lu " "ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_avail() returned a value that is exceptionally large: %lu bytes (%lu " "ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "snd_pcm_avail() palautti poikkeuksellisen suuren arvon: %lu tavu (%lu ms).\n" "Tämä on todennäköisesti ohjelmavirhe ALSA-ajurissa ”%s”. Ilmoita tästä " "ongelmasta ALSA-kehittäjille." msgstr[1] "" "snd_pcm_avail() palautti poikkeuksellisen suuren arvon: %lu tavua (%lu ms).\n" "Tämä on todennäköisesti ohjelmavirhe ALSA-ajurissa ”%s”. Ilmoita tästä " "ongelmasta ALSA-kehittäjille." #: spa/plugins/alsa/acp/alsa-util.c:1297 #, c-format msgid "" "snd_pcm_delay() returned a value that is exceptionally large: %li byte " "(%s%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_delay() returned a value that is exceptionally large: %li bytes " "(%s%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "snd_pcm_delay() palautti poikkeuksellisen suuren arvon: %li tavu (%s%lu " "ms).\n" "Tämä on todennäköisesti ohjelmavirhe ALSA-ajurissa ”%s”. Ilmoita tästä " "ongelmasta ALSA-kehittäjille." msgstr[1] "" "snd_pcm_delay() palautti poikkeuksellisen suuren arvon: %li tavua (%s%lu " "ms).\n" "Tämä on todennäköisesti ohjelmavirhe ALSA-ajurissa ”%s”. Ilmoita tästä " "ongelmasta ALSA-kehittäjille." #: spa/plugins/alsa/acp/alsa-util.c:1344 #, c-format msgid "" "snd_pcm_avail_delay() returned strange values: delay %lu is less than avail " "%lu.\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr "" "snd_pcm_avail_delay() palautti poikkeuksellisia arvoja: %lu on vähemmän kuin " "saatavissa oleva %lu.\n" "Tämä on todennäköisesti ohjelmavirhe ALSA-ajurissa ”%s”. Ilmoita tästä " "ongelmasta ALSA-kehittäjille." #: spa/plugins/alsa/acp/alsa-util.c:1387 #, c-format msgid "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu byte " "(%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu bytes " "(%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "snd_pcm_mmap_begin() palautti poikkeuksellisen suuren arvon: %lu tavu (%lu " "ms).\n" "Tämä on todennäköisesti ohjelmavirhe ALSA-ajurissa ”%s”. Ilmoita tästä " "ongelmasta ALSA-kehittäjille." msgstr[1] "" "snd_pcm_mmap_begin() palautti poikkeuksellisen suuren arvon: %lu tavua (%lu " "ms).\n" "Tämä on todennäköisesti ohjelmavirhe ALSA-ajurissa ”%s”. Ilmoita tästä " "ongelmasta ALSA-kehittäjille." #: spa/plugins/alsa/acp/channelmap.h:457 msgid "(invalid)" msgstr "(virheellinen)" #: spa/plugins/alsa/acp/compat.c:193 msgid "Built-in Audio" msgstr "Sisäinen äänentoisto" #: spa/plugins/alsa/acp/compat.c:198 msgid "Modem" msgstr "Modeemi" #: spa/plugins/bluez5/bluez5-device.c:1707 msgid "Audio Gateway (A2DP Source & HSP/HFP AG)" msgstr "Ääniyhdyskäytävä (A2DP-lähde & HSP/HFP AG)" #: spa/plugins/bluez5/bluez5-device.c:1755 #, c-format msgid "High Fidelity Playback (A2DP Sink, codec %s)" msgstr "Korkealaatuinen toisto (A2DP-kohde, %s-koodekki)" #: spa/plugins/bluez5/bluez5-device.c:1758 #, c-format msgid "High Fidelity Duplex (A2DP Source/Sink, codec %s)" msgstr "Korkealaatuinen molempisuuntainen (A2DP-lähde/kohde, %s-koodekki)" #: spa/plugins/bluez5/bluez5-device.c:1766 msgid "High Fidelity Playback (A2DP Sink)" msgstr "Korkealaatuinen toisto (A2DP-kohde)" #: spa/plugins/bluez5/bluez5-device.c:1768 msgid "High Fidelity Duplex (A2DP Source/Sink)" msgstr "Korkealaatuinen molempisuuntainen (A2DP-lähde/kohde)" #: spa/plugins/bluez5/bluez5-device.c:1818 #, c-format msgid "High Fidelity Playback (BAP Sink, codec %s)" msgstr "Korkealaatuinen toisto (BAP-kohde, %s-koodekki)" #: spa/plugins/bluez5/bluez5-device.c:1823 #, c-format msgid "High Fidelity Input (BAP Source, codec %s)" msgstr "Korkealaatuinen sisääntulo (BAP-lähde, %s-koodekki)" #: spa/plugins/bluez5/bluez5-device.c:1827 #, c-format msgid "High Fidelity Duplex (BAP Source/Sink, codec %s)" msgstr "Korkealaatuinen molempisuuntainen (BAP-lähde/kohde, %s-koodekki)" #: spa/plugins/bluez5/bluez5-device.c:1836 msgid "High Fidelity Playback (BAP Sink)" msgstr "Korkealaatuinen toisto (BAP-kohde)" #: spa/plugins/bluez5/bluez5-device.c:1840 msgid "High Fidelity Input (BAP Source)" msgstr "Korkealaatuinen sisääntulo (BAP-lähde)" #: spa/plugins/bluez5/bluez5-device.c:1843 msgid "High Fidelity Duplex (BAP Source/Sink)" msgstr "Korkealaatuinen molempisuuntainen (BAP-lähde/kohde)" #: spa/plugins/bluez5/bluez5-device.c:1892 #, c-format msgid "Headset Head Unit (HSP/HFP, codec %s)" msgstr "Kuulokemikrofoni (HSP/HFP, %s-koodekki)" #: spa/plugins/bluez5/bluez5-device.c:1973 #: spa/plugins/bluez5/bluez5-device.c:1978 #: spa/plugins/bluez5/bluez5-device.c:1985 #: spa/plugins/bluez5/bluez5-device.c:1991 #: spa/plugins/bluez5/bluez5-device.c:1997 #: spa/plugins/bluez5/bluez5-device.c:2003 #: spa/plugins/bluez5/bluez5-device.c:2009 #: spa/plugins/bluez5/bluez5-device.c:2015 #: spa/plugins/bluez5/bluez5-device.c:2021 msgid "Handsfree" msgstr "Kuulokemikrofoni" #: spa/plugins/bluez5/bluez5-device.c:1979 msgid "Handsfree (HFP)" msgstr "Kuulokemikrofoni (HFP)" #: spa/plugins/bluez5/bluez5-device.c:1996 msgid "Headphone" msgstr "Kuulokkeet" #: spa/plugins/bluez5/bluez5-device.c:2002 msgid "Portable" msgstr "Kannettava" #: spa/plugins/bluez5/bluez5-device.c:2008 msgid "Car" msgstr "Auto" #: spa/plugins/bluez5/bluez5-device.c:2014 msgid "HiFi" msgstr "Hi-Fi" #: spa/plugins/bluez5/bluez5-device.c:2020 msgid "Phone" msgstr "Puhelin" #: spa/plugins/bluez5/bluez5-device.c:2027 msgid "Bluetooth" msgstr "Bluetooth" #: spa/plugins/bluez5/bluez5-device.c:2028 msgid "Bluetooth (HFP)" msgstr "Bluetooth (HFP)" pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/po/fr.po000066400000000000000000000432441511204443500220720ustar00rootroot00000000000000# French translation of pipewire. # Copyright (C) 2006-2008 Lennart Poettering # This file is distributed under the same license as the pipewire package. # # # Robert-André Mauchin , 2008. # Michaël Ughetto , 2008. # Pablo Martin-Gomez , 2008. # Corentin Perard , 2009. # Thomas Canniot , 2009, 2012. # Sam Friedmann , 2016. #zanata # Wim Taymans , 2016. #zanata # Edouard Duliege , 2017. #zanata msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/pipewire/" "issues/new\n" "POT-Creation-Date: 2021-04-18 16:54+0800\n" "PO-Revision-Date: 2020-12-13 17:35+0000\n" "Last-Translator: Julien Humbert \n" "Language-Team: French \n" "Language: fr\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n > 1;\n" "X-Generator: Weblate 4.3.2\n" #: src/daemon/pipewire.c:43 #, c-format msgid "" "%s [options]\n" " -h, --help Show this help\n" " --version Show version\n" " -c, --config Load config (Default %s)\n" msgstr "" #: src/daemon/pipewire.desktop.in:4 msgid "PipeWire Media System" msgstr "" #: src/daemon/pipewire.desktop.in:5 msgid "Start the PipeWire Media System" msgstr "" #: src/examples/media-session/alsa-monitor.c:526 #: spa/plugins/alsa/acp/compat.c:187 msgid "Built-in Audio" msgstr "Audio interne" #: src/examples/media-session/alsa-monitor.c:530 #: spa/plugins/alsa/acp/compat.c:192 msgid "Modem" msgstr "Modem" #: src/examples/media-session/alsa-monitor.c:539 msgid "Unknown device" msgstr "" #: src/tools/pw-cat.c:991 #, c-format msgid "" "%s [options] \n" " -h, --help Show this help\n" " --version Show version\n" " -v, --verbose Enable verbose operations\n" "\n" msgstr "" #: src/tools/pw-cat.c:998 #, c-format msgid "" " -R, --remote Remote daemon name\n" " --media-type Set media type (default %s)\n" " --media-category Set media category (default %s)\n" " --media-role Set media role (default %s)\n" " --target Set node target (default %s)\n" " 0 means don't link\n" " --latency Set node latency (default %s)\n" " Xunit (unit = s, ms, us, ns)\n" " or direct samples (256)\n" " the rate is the one of the source " "file\n" " --list-targets List available targets for --target\n" "\n" msgstr "" #: src/tools/pw-cat.c:1016 #, c-format msgid "" " --rate Sample rate (req. for rec) (default " "%u)\n" " --channels Number of channels (req. for rec) " "(default %u)\n" " --channel-map Channel map\n" " one of: \"stereo\", " "\"surround-51\",... or\n" " comma separated list of channel " "names: eg. \"FL,FR\"\n" " --format Sample format %s (req. for rec) " "(default %s)\n" " --volume Stream volume 0-1.0 (default %.3f)\n" " -q --quality Resampler quality (0 - 15) (default " "%d)\n" "\n" msgstr "" #: src/tools/pw-cat.c:1033 msgid "" " -p, --playback Playback mode\n" " -r, --record Recording mode\n" " -m, --midi Midi mode\n" "\n" msgstr "" #: src/tools/pw-cli.c:2932 #, c-format msgid "" "%s [options] [command]\n" " -h, --help Show this help\n" " --version Show version\n" " -d, --daemon Start as daemon (Default false)\n" " -r, --remote Remote daemon name\n" "\n" msgstr "" #: spa/plugins/alsa/acp/acp.c:290 msgid "Pro Audio" msgstr "" #: spa/plugins/alsa/acp/acp.c:411 spa/plugins/alsa/acp/alsa-mixer.c:4704 #: spa/plugins/bluez5/bluez5-device.c:1000 msgid "Off" msgstr "Éteint" #: spa/plugins/alsa/acp/channelmap.h:466 msgid "(invalid)" msgstr "(invalide)" #: spa/plugins/alsa/acp/alsa-mixer.c:2709 msgid "Input" msgstr "Entrée" #: spa/plugins/alsa/acp/alsa-mixer.c:2710 msgid "Docking Station Input" msgstr "Entrée de la station d’accueil" #: spa/plugins/alsa/acp/alsa-mixer.c:2711 msgid "Docking Station Microphone" msgstr "Microphone de la station d’accueil" #: spa/plugins/alsa/acp/alsa-mixer.c:2712 msgid "Docking Station Line In" msgstr "Entrée ligne de la station d’accueil" #: spa/plugins/alsa/acp/alsa-mixer.c:2713 #: spa/plugins/alsa/acp/alsa-mixer.c:2804 msgid "Line In" msgstr "Entrée ligne" #: spa/plugins/alsa/acp/alsa-mixer.c:2714 #: spa/plugins/alsa/acp/alsa-mixer.c:2798 #: spa/plugins/bluez5/bluez5-device.c:1145 msgid "Microphone" msgstr "Microphone" #: spa/plugins/alsa/acp/alsa-mixer.c:2715 #: spa/plugins/alsa/acp/alsa-mixer.c:2799 msgid "Front Microphone" msgstr "Microphone avant" #: spa/plugins/alsa/acp/alsa-mixer.c:2716 #: spa/plugins/alsa/acp/alsa-mixer.c:2800 msgid "Rear Microphone" msgstr "Microphone arrière" #: spa/plugins/alsa/acp/alsa-mixer.c:2717 msgid "External Microphone" msgstr "Microphone externe" #: spa/plugins/alsa/acp/alsa-mixer.c:2718 #: spa/plugins/alsa/acp/alsa-mixer.c:2802 msgid "Internal Microphone" msgstr "Microphone interne" #: spa/plugins/alsa/acp/alsa-mixer.c:2719 #: spa/plugins/alsa/acp/alsa-mixer.c:2805 msgid "Radio" msgstr "Radio" #: spa/plugins/alsa/acp/alsa-mixer.c:2720 #: spa/plugins/alsa/acp/alsa-mixer.c:2806 msgid "Video" msgstr "Vidéo" #: spa/plugins/alsa/acp/alsa-mixer.c:2721 msgid "Automatic Gain Control" msgstr "Contrôle automatique du gain" #: spa/plugins/alsa/acp/alsa-mixer.c:2722 msgid "No Automatic Gain Control" msgstr "Pas de contrôle automatique du gain" #: spa/plugins/alsa/acp/alsa-mixer.c:2723 msgid "Boost" msgstr "Boost" #: spa/plugins/alsa/acp/alsa-mixer.c:2724 msgid "No Boost" msgstr "Pas de boost" #: spa/plugins/alsa/acp/alsa-mixer.c:2725 msgid "Amplifier" msgstr "Amplificateur" #: spa/plugins/alsa/acp/alsa-mixer.c:2726 msgid "No Amplifier" msgstr "Pas d’amplificateur" #: spa/plugins/alsa/acp/alsa-mixer.c:2727 msgid "Bass Boost" msgstr "Booster de basses" #: spa/plugins/alsa/acp/alsa-mixer.c:2728 msgid "No Bass Boost" msgstr "Pas de booster de basses" #: spa/plugins/alsa/acp/alsa-mixer.c:2729 #: spa/plugins/bluez5/bluez5-device.c:1150 msgid "Speaker" msgstr "Haut-parleur" #: spa/plugins/alsa/acp/alsa-mixer.c:2730 #: spa/plugins/alsa/acp/alsa-mixer.c:2808 msgid "Headphones" msgstr "Casque audio" #: spa/plugins/alsa/acp/alsa-mixer.c:2797 msgid "Analog Input" msgstr "Entrée analogique" #: spa/plugins/alsa/acp/alsa-mixer.c:2801 msgid "Dock Microphone" msgstr "Microphone de la station d’accueil" #: spa/plugins/alsa/acp/alsa-mixer.c:2803 msgid "Headset Microphone" msgstr "Microphone casque" #: spa/plugins/alsa/acp/alsa-mixer.c:2807 msgid "Analog Output" msgstr "Sortie analogique" #: spa/plugins/alsa/acp/alsa-mixer.c:2809 #, fuzzy msgid "Headphones 2" msgstr "Casque audio" #: spa/plugins/alsa/acp/alsa-mixer.c:2810 #, fuzzy msgid "Headphones Mono Output" msgstr "Sortie mono analogique" #: spa/plugins/alsa/acp/alsa-mixer.c:2811 msgid "Line Out" msgstr "Sortie ligne" #: spa/plugins/alsa/acp/alsa-mixer.c:2812 msgid "Analog Mono Output" msgstr "Sortie mono analogique" #: spa/plugins/alsa/acp/alsa-mixer.c:2813 msgid "Speakers" msgstr "Haut-parleurs" #: spa/plugins/alsa/acp/alsa-mixer.c:2814 msgid "HDMI / DisplayPort" msgstr "HDMI / DisplayPort" #: spa/plugins/alsa/acp/alsa-mixer.c:2815 msgid "Digital Output (S/PDIF)" msgstr "Sortie numérique (S/PDIF)" #: spa/plugins/alsa/acp/alsa-mixer.c:2816 msgid "Digital Input (S/PDIF)" msgstr "Entrée numérique (S/PDIF)" #: spa/plugins/alsa/acp/alsa-mixer.c:2817 #, fuzzy msgid "Multichannel Input" msgstr "Multicanal" #: spa/plugins/alsa/acp/alsa-mixer.c:2818 #, fuzzy msgid "Multichannel Output" msgstr "Multicanal" #: spa/plugins/alsa/acp/alsa-mixer.c:2819 #, fuzzy msgid "Game Output" msgstr "Sortie %s" #: spa/plugins/alsa/acp/alsa-mixer.c:2820 #: spa/plugins/alsa/acp/alsa-mixer.c:2821 #, fuzzy msgid "Chat Output" msgstr "Sortie %s" #: spa/plugins/alsa/acp/alsa-mixer.c:2822 #, fuzzy msgid "Chat Input" msgstr "Entrée %s" #: spa/plugins/alsa/acp/alsa-mixer.c:2823 #, fuzzy msgid "Virtual Surround 7.1" msgstr "Destination surround virtuelle" #: spa/plugins/alsa/acp/alsa-mixer.c:4527 msgid "Analog Mono" msgstr "Mono analogique" #: spa/plugins/alsa/acp/alsa-mixer.c:4528 #, fuzzy msgid "Analog Mono (Left)" msgstr "Mono analogique" #: spa/plugins/alsa/acp/alsa-mixer.c:4529 #, fuzzy msgid "Analog Mono (Right)" msgstr "Mono analogique" #. Note: Not translated to "Analog Stereo Input", because the source #. * name gets "Input" appended to it automatically, so adding "Input" #. * here would lead to the source name to become "Analog Stereo Input #. * Input". The same logic applies to analog-stereo-output, #. * multichannel-input and multichannel-output. #: spa/plugins/alsa/acp/alsa-mixer.c:4530 #: spa/plugins/alsa/acp/alsa-mixer.c:4538 #: spa/plugins/alsa/acp/alsa-mixer.c:4539 msgid "Analog Stereo" msgstr "Stéréo analogique" #: spa/plugins/alsa/acp/alsa-mixer.c:4531 msgid "Mono" msgstr "Mono" #: spa/plugins/alsa/acp/alsa-mixer.c:4532 msgid "Stereo" msgstr "Stéréo" #: spa/plugins/alsa/acp/alsa-mixer.c:4540 #: spa/plugins/alsa/acp/alsa-mixer.c:4698 #: spa/plugins/bluez5/bluez5-device.c:1135 msgid "Headset" msgstr "Casque" #: spa/plugins/alsa/acp/alsa-mixer.c:4541 #: spa/plugins/alsa/acp/alsa-mixer.c:4699 #, fuzzy msgid "Speakerphone" msgstr "Haut-parleur" #: spa/plugins/alsa/acp/alsa-mixer.c:4542 #: spa/plugins/alsa/acp/alsa-mixer.c:4543 msgid "Multichannel" msgstr "Multicanal" #: spa/plugins/alsa/acp/alsa-mixer.c:4544 msgid "Analog Surround 2.1" msgstr "Surround analogique 2.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4545 msgid "Analog Surround 3.0" msgstr "Surround analogique 3.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4546 msgid "Analog Surround 3.1" msgstr "Surround analogique 3.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4547 msgid "Analog Surround 4.0" msgstr "Surround analogique 4.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4548 msgid "Analog Surround 4.1" msgstr "Surround analogique 4.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4549 msgid "Analog Surround 5.0" msgstr "Surround analogique 5.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4550 msgid "Analog Surround 5.1" msgstr "Surround analogique 5.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4551 msgid "Analog Surround 6.0" msgstr "Surround analogique 6.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4552 msgid "Analog Surround 6.1" msgstr "Surround analogique 6.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4553 msgid "Analog Surround 7.0" msgstr "Surround analogique 7.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4554 msgid "Analog Surround 7.1" msgstr "Surround analogique 7.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4555 msgid "Digital Stereo (IEC958)" msgstr "Stéréo numérique (IEC958)" #: spa/plugins/alsa/acp/alsa-mixer.c:4556 msgid "Digital Surround 4.0 (IEC958/AC3)" msgstr "Surround numérique 4.0 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4557 msgid "Digital Surround 5.1 (IEC958/AC3)" msgstr "Surround numérique 5.1 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4558 msgid "Digital Surround 5.1 (IEC958/DTS)" msgstr "Surround numérique 5.1 (IEC958/DTS)" #: spa/plugins/alsa/acp/alsa-mixer.c:4559 msgid "Digital Stereo (HDMI)" msgstr "Stéréo numérique (HDMI)" #: spa/plugins/alsa/acp/alsa-mixer.c:4560 msgid "Digital Surround 5.1 (HDMI)" msgstr "Surround numérique 5.1 (HDMI)" #: spa/plugins/alsa/acp/alsa-mixer.c:4561 msgid "Chat" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4562 msgid "Game" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4696 msgid "Analog Mono Duplex" msgstr "Duplex Mono analogique" #: spa/plugins/alsa/acp/alsa-mixer.c:4697 msgid "Analog Stereo Duplex" msgstr "Duplex stéréo analogique" #: spa/plugins/alsa/acp/alsa-mixer.c:4700 msgid "Digital Stereo Duplex (IEC958)" msgstr "Duplex stéréo numérique (IEC958)" #: spa/plugins/alsa/acp/alsa-mixer.c:4701 #, fuzzy msgid "Multichannel Duplex" msgstr "Multicanal" #: spa/plugins/alsa/acp/alsa-mixer.c:4702 #, fuzzy msgid "Stereo Duplex" msgstr "Duplex stéréo analogique" #: spa/plugins/alsa/acp/alsa-mixer.c:4703 msgid "Mono Chat + 7.1 Surround" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4806 #, c-format msgid "%s Output" msgstr "Sortie %s" #: spa/plugins/alsa/acp/alsa-mixer.c:4813 #, c-format msgid "%s Input" msgstr "Entrée %s" #: spa/plugins/alsa/acp/alsa-util.c:1175 spa/plugins/alsa/acp/alsa-util.c:1269 #, fuzzy, c-format msgid "" "snd_pcm_avail() returned a value that is exceptionally large: %lu byte (%lu " "ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_avail() returned a value that is exceptionally large: %lu bytes (%lu " "ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "snd_pcm_avail() a retourné une valeur qui est exceptionnellement large : %lu " "octets (%lu ms).\n" "Il s’agit très probablement d’un bogue dans le pilote ALSA « %s ». Veuillez " "rapporter ce problème aux développeurs d’ALSA." msgstr[1] "" "snd_pcm_avail() a retourné une valeur qui est exceptionnellement large : %lu " "octets (%lu ms).\n" "Il s’agit très probablement d’un bogue dans le pilote ALSA « %s ». Veuillez " "rapporter ce problème aux développeurs d’ALSA." #: spa/plugins/alsa/acp/alsa-util.c:1241 #, fuzzy, c-format msgid "" "snd_pcm_delay() returned a value that is exceptionally large: %li byte (%s" "%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_delay() returned a value that is exceptionally large: %li bytes (%s" "%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "snd_pcm_delay() a retourné une valeur qui est exceptionnellement large : %li " "octets (%s%lu ms).\n" "Il s’agit très probablement d’un bogue dans le pilote ALSA « %s ». Veuillez " "rapporter ce problème aux développeurs d’ALSA." msgstr[1] "" "snd_pcm_delay() a retourné une valeur qui est exceptionnellement large : %li " "octets (%s%lu ms).\n" "Il s’agit très probablement d’un bogue dans le pilote ALSA « %s ». Veuillez " "rapporter ce problème aux développeurs d’ALSA." #: spa/plugins/alsa/acp/alsa-util.c:1288 #, c-format msgid "" "snd_pcm_avail_delay() returned strange values: delay %lu is less than avail " "%lu.\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr "" "snd_pcm_avail_delay() a retourné des valeurs inhabituelles : le délai %lu " "est inférieur au %lu disponible.\n" "Il s’agit très probablement d’un bogue dans le pilote ALSA « %s ». Veuillez " "rapporter ce problème aux développeurs d’ALSA." #: spa/plugins/alsa/acp/alsa-util.c:1331 #, fuzzy, c-format msgid "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu byte " "(%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu bytes " "(%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "snd_pcm_mmap_begin() a retourné une valeur qui est exceptionnellement " "large : %lu octets (%lu·ms).\n" "Il s’agit très probablement d’un bogue dans le pilote ALSA « %s ». Veuillez " "rapporter ce problème aux développeurs d’ALSA." msgstr[1] "" "snd_pcm_mmap_begin() a retourné une valeur qui est exceptionnellement " "large : %lu octets (%lu·ms).\n" "Il s’agit très probablement d’un bogue dans le pilote ALSA « %s ». Veuillez " "rapporter ce problème aux développeurs d’ALSA." #: spa/plugins/bluez5/bluez5-device.c:1010 msgid "Audio Gateway (A2DP Source & HSP/HFP AG)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1033 #, c-format msgid "High Fidelity Playback (A2DP Sink, codec %s)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1035 #, c-format msgid "High Fidelity Duplex (A2DP Source/Sink, codec %s)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1041 msgid "High Fidelity Playback (A2DP Sink)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1043 msgid "High Fidelity Duplex (A2DP Source/Sink)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1070 #, c-format msgid "Headset Head Unit (HSP/HFP, codec %s)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1074 msgid "Headset Head Unit (HSP/HFP)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1140 msgid "Handsfree" msgstr "Mains-libres" #: spa/plugins/bluez5/bluez5-device.c:1155 msgid "Headphone" msgstr "Écouteurs" #: spa/plugins/bluez5/bluez5-device.c:1160 msgid "Portable" msgstr "Portable" #: spa/plugins/bluez5/bluez5-device.c:1165 msgid "Car" msgstr "Voiture" #: spa/plugins/bluez5/bluez5-device.c:1170 msgid "HiFi" msgstr "HiFi" #: spa/plugins/bluez5/bluez5-device.c:1175 msgid "Phone" msgstr "Téléphone" #: spa/plugins/bluez5/bluez5-device.c:1181 #, fuzzy msgid "Bluetooth" msgstr "Entrée Bluetooth" pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/po/gl.po000066400000000000000000000527021511204443500220640ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # Translators: # bassball93 , 2011. # mbouzada , 2011. # Marcos Lans , 2018. # Fran Dieguez , 2012-2022. # msgid "" msgstr "" "Project-Id-Version: PipeWire\n" "Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/pipewire/-/" "issues\n" "POT-Creation-Date: 2022-07-10 03:27+0000\n" "PO-Revision-Date: 2022-08-23 09:47+0200\n" "Last-Translator: Fran Dieguez \n" "Language-Team: Galician >\n" "Language: gl\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" "X-Generator: Gtranslator 40.0\n" "X-DL-Team: gl\n" "X-DL-Module: PipeWire\n" "X-DL-Branch: master\n" "X-DL-Domain: po\n" "X-DL-State: Translating\n" #: src/daemon/pipewire.c:46 #, c-format msgid "" "%s [options]\n" " -h, --help Show this help\n" " --version Show version\n" " -c, --config Load config (Default %s)\n" msgstr "" "%s [opcións]\n" " -h, --help Mostra esta axuda\n" " --version Mostrar versión\n" " -c, --config Cargar configuración (Predeterminado " "%s)\n" #: src/daemon/pipewire.desktop.in:4 msgid "PipeWire Media System" msgstr "Sistema multimedia PipeWire" #: src/daemon/pipewire.desktop.in:5 msgid "Start the PipeWire Media System" msgstr "Iniciar o Sistema multimedia PipeWire" #: src/modules/module-protocol-pulse/modules/module-tunnel-sink.c:180 #: src/modules/module-protocol-pulse/modules/module-tunnel-source.c:180 #, c-format msgid "Tunnel to %s/%s" msgstr "Túnel a %s/%s" #: src/modules/module-fallback-sink.c:51 #| msgid "Game Output" msgid "Dummy Output" msgstr "Saída de proba" #: src/modules/module-pulse-tunnel.c:648 #, c-format msgid "Tunnel for %s@%s" msgstr "Túnel para %s@%s" #: src/modules/module-zeroconf-discover.c:332 msgid "Unknown device" msgstr "Dispositivo descoñecido" #: src/modules/module-zeroconf-discover.c:344 #, c-format msgid "%s on %s@%s" msgstr "%s en %s@%s" #: src/modules/module-zeroconf-discover.c:348 #, c-format msgid "%s on %s" msgstr "%s en %s" #: src/tools/pw-cat.c:784 #, c-format msgid "" "%s [options] [|-]\n" " -h, --help Show this help\n" " --version Show version\n" " -v, --verbose Enable verbose operations\n" "\n" msgstr "" "%s [opcións] [|-]\n" " -h, --help Mostrar esta axuda\n" " --version Mostrar versión\n" " -v, --verbose Activar operacións verbosas\n" "\n" #: src/tools/pw-cat.c:791 #, c-format msgid "" " -R, --remote Remote daemon name\n" " --media-type Set media type (default %s)\n" " --media-category Set media category (default %s)\n" " --media-role Set media role (default %s)\n" " --target Set node target (default %s)\n" " 0 means don't link\n" " --latency Set node latency (default %s)\n" " Xunit (unit = s, ms, us, ns)\n" " or direct samples (256)\n" " the rate is the one of the source " "file\n" " -P --properties Set node properties\n" "\n" msgstr "" " -R, --remote Nome do daemon remoto\n" " --media-type Estabelecer o tipo de medio (por " "omisión %s)\n" " --media-category Estabelecer a categoría multimedia " "(por omisión %s)\n" " --media-role Estabelecer o rol multimedia (por " "omisión %s)\n" " --target Estabelecer o nodo obxectivo (por " "omisión %s)\n" " 0 significa non ligar\n" " --latency Estabelecer a latencia do nodo (por " "omisión %s)\n" " Xunit (unidade = s, ms, us, ns)\n" " ou mostras directas samples (256)\n" " a taxa é un dos ficheiros de " "orixe\n" " -P --properties Estabelecer as propiedades do nodo\n" "\n" #: src/tools/pw-cat.c:809 #, c-format msgid "" " --rate Sample rate (req. for rec) (default " "%u)\n" " --channels Number of channels (req. for rec) " "(default %u)\n" " --channel-map Channel map\n" " one of: \"stereo\", " "\"surround-51\",... or\n" " comma separated list of channel " "names: eg. \"FL,FR\"\n" " --format Sample format %s (req. for rec) " "(default %s)\n" " --volume Stream volume 0-1.0 (default %.3f)\n" " -q --quality Resampler quality (0 - 15) (default " "%d)\n" "\n" msgstr "" " --rate Taxa de mostreo (solicitudes por " "segundo) (por omisión %u)\n" " --channels Número de canles (solicitudes por " "segundo) (por omisión %u)\n" " --channel-map Mapa de canles\n" " un de: \"stereo\", " "\"surround-51\",... or\n" " lista separada por comas dos " "nomes das canles: p.ex. \"FL,FR\"\n" " --format Formato de mostras %s (solicitudes " "por segundo) (por omisión %s)\n" " --volume Volume do fluxo 0-1.0 (por omisión " "%.3f)\n" " -q --quality Calidade do remostreador (0 - 15) " "(por omisión %d)\n" "\n" #: src/tools/pw-cat.c:826 msgid "" " -p, --playback Playback mode\n" " -r, --record Recording mode\n" " -m, --midi Midi mode\n" " -d, --dsd DSD mode\n" "\n" msgstr "" " -p, --playback Modo de reprodución\n" " -r, --record Modo de grabación\n" " -m, --midi Modo MIDI\n" " -d, --dsd Modo DSD\n" "\n" #: src/tools/pw-cli.c:3165 #, c-format msgid "" "%s [options] [command]\n" " -h, --help Show this help\n" " --version Show version\n" " -d, --daemon Start as daemon (Default false)\n" " -r, --remote Remote daemon name\n" "\n" msgstr "" "%s [opcións] [orde]\n" " -h, --help Mostrar esta axuda\n" " --version Mostrar versión\n" " -d, --daemon Iniciar como demonio (Por omisión " "falso)\n" " -r, --remote Modo de demonio remoto\n" "\n" #: spa/plugins/alsa/acp/acp.c:321 msgid "Pro Audio" msgstr "Pro Audio" #: spa/plugins/alsa/acp/acp.c:446 spa/plugins/alsa/acp/alsa-mixer.c:4648 #: spa/plugins/bluez5/bluez5-device.c:1185 msgid "Off" msgstr "Apagado" #: spa/plugins/alsa/acp/alsa-mixer.c:2652 msgid "Input" msgstr "Entrada" #: spa/plugins/alsa/acp/alsa-mixer.c:2653 msgid "Docking Station Input" msgstr "Entrada de estación acoplada (Docking Station)" #: spa/plugins/alsa/acp/alsa-mixer.c:2654 msgid "Docking Station Microphone" msgstr "Micrófono da estación acoplada (Docking Station)" #: spa/plugins/alsa/acp/alsa-mixer.c:2655 msgid "Docking Station Line In" msgstr "Entrada de estación acoplada (Docking Station)" #: spa/plugins/alsa/acp/alsa-mixer.c:2656 #: spa/plugins/alsa/acp/alsa-mixer.c:2747 msgid "Line In" msgstr "Liña de entrada" #: spa/plugins/alsa/acp/alsa-mixer.c:2657 #: spa/plugins/alsa/acp/alsa-mixer.c:2741 #: spa/plugins/bluez5/bluez5-device.c:1357 msgid "Microphone" msgstr "Micrófono" #: spa/plugins/alsa/acp/alsa-mixer.c:2658 #: spa/plugins/alsa/acp/alsa-mixer.c:2742 msgid "Front Microphone" msgstr "Micrófono frontal" #: spa/plugins/alsa/acp/alsa-mixer.c:2659 #: spa/plugins/alsa/acp/alsa-mixer.c:2743 msgid "Rear Microphone" msgstr "Micrófono traseiro" #: spa/plugins/alsa/acp/alsa-mixer.c:2660 msgid "External Microphone" msgstr "Micrófono externo" #: spa/plugins/alsa/acp/alsa-mixer.c:2661 #: spa/plugins/alsa/acp/alsa-mixer.c:2745 msgid "Internal Microphone" msgstr "Micrófono interno" #: spa/plugins/alsa/acp/alsa-mixer.c:2662 #: spa/plugins/alsa/acp/alsa-mixer.c:2748 msgid "Radio" msgstr "Radio" #: spa/plugins/alsa/acp/alsa-mixer.c:2663 #: spa/plugins/alsa/acp/alsa-mixer.c:2749 msgid "Video" msgstr "Vídeo" #: spa/plugins/alsa/acp/alsa-mixer.c:2664 msgid "Automatic Gain Control" msgstr "Control automático de ganancia" #: spa/plugins/alsa/acp/alsa-mixer.c:2665 msgid "No Automatic Gain Control" msgstr "Sen control automático de ganancia" #: spa/plugins/alsa/acp/alsa-mixer.c:2666 msgid "Boost" msgstr "Enfatizador" #: spa/plugins/alsa/acp/alsa-mixer.c:2667 msgid "No Boost" msgstr "Sen enfatizador" #: spa/plugins/alsa/acp/alsa-mixer.c:2668 msgid "Amplifier" msgstr "Amplificador" #: spa/plugins/alsa/acp/alsa-mixer.c:2669 msgid "No Amplifier" msgstr "Sen amplificador" #: spa/plugins/alsa/acp/alsa-mixer.c:2670 msgid "Bass Boost" msgstr "Enfatizador baixo" #: spa/plugins/alsa/acp/alsa-mixer.c:2671 msgid "No Bass Boost" msgstr "Sen enfatizador baixo" #: spa/plugins/alsa/acp/alsa-mixer.c:2672 #: spa/plugins/bluez5/bluez5-device.c:1363 msgid "Speaker" msgstr "Altofalante" #: spa/plugins/alsa/acp/alsa-mixer.c:2673 #: spa/plugins/alsa/acp/alsa-mixer.c:2751 msgid "Headphones" msgstr "Auriculares" #: spa/plugins/alsa/acp/alsa-mixer.c:2740 msgid "Analog Input" msgstr "Entrada analóxica" #: spa/plugins/alsa/acp/alsa-mixer.c:2744 msgid "Dock Microphone" msgstr "Micrófono do acople" #: spa/plugins/alsa/acp/alsa-mixer.c:2746 msgid "Headset Microphone" msgstr "Micrófono con auricular" #: spa/plugins/alsa/acp/alsa-mixer.c:2750 msgid "Analog Output" msgstr "Saída analóxica" #: spa/plugins/alsa/acp/alsa-mixer.c:2752 msgid "Headphones 2" msgstr "Auriculares 2" #: spa/plugins/alsa/acp/alsa-mixer.c:2753 msgid "Headphones Mono Output" msgstr "Saída monoaural para auriculares" #: spa/plugins/alsa/acp/alsa-mixer.c:2754 msgid "Line Out" msgstr "Liña de saída" #: spa/plugins/alsa/acp/alsa-mixer.c:2755 msgid "Analog Mono Output" msgstr "Saída monoaural analóxica" #: spa/plugins/alsa/acp/alsa-mixer.c:2756 msgid "Speakers" msgstr "Altofalantes" #: spa/plugins/alsa/acp/alsa-mixer.c:2757 msgid "HDMI / DisplayPort" msgstr "HDMI / DisplayPort" #: spa/plugins/alsa/acp/alsa-mixer.c:2758 msgid "Digital Output (S/PDIF)" msgstr "Saída dixital (S/PDIF)" #: spa/plugins/alsa/acp/alsa-mixer.c:2759 msgid "Digital Input (S/PDIF)" msgstr "Entrada dixital (S/PDIF)" #: spa/plugins/alsa/acp/alsa-mixer.c:2760 msgid "Multichannel Input" msgstr "Entrada multicanle" #: spa/plugins/alsa/acp/alsa-mixer.c:2761 msgid "Multichannel Output" msgstr "Saída multicanle" #: spa/plugins/alsa/acp/alsa-mixer.c:2762 msgid "Game Output" msgstr "Saída do xogo" #: spa/plugins/alsa/acp/alsa-mixer.c:2763 #: spa/plugins/alsa/acp/alsa-mixer.c:2764 msgid "Chat Output" msgstr "Saída do chat" #: spa/plugins/alsa/acp/alsa-mixer.c:2765 msgid "Chat Input" msgstr "Entrada de chat" #: spa/plugins/alsa/acp/alsa-mixer.c:2766 msgid "Virtual Surround 7.1" msgstr "Envolvente virtual 7.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4471 msgid "Analog Mono" msgstr "Monoaural analóxico" #: spa/plugins/alsa/acp/alsa-mixer.c:4472 msgid "Analog Mono (Left)" msgstr "Monoaural analóxico (Esquerda)" #: spa/plugins/alsa/acp/alsa-mixer.c:4473 msgid "Analog Mono (Right)" msgstr "Monoaural analóxico (Dereita)" #. Note: Not translated to "Analog Stereo Input", because the source #. * name gets "Input" appended to it automatically, so adding "Input" #. * here would lead to the source name to become "Analog Stereo Input #. * Input". The same logic applies to analog-stereo-output, #. * multichannel-input and multichannel-output. #: spa/plugins/alsa/acp/alsa-mixer.c:4474 #: spa/plugins/alsa/acp/alsa-mixer.c:4482 #: spa/plugins/alsa/acp/alsa-mixer.c:4483 msgid "Analog Stereo" msgstr "Estéreo analóxico" #: spa/plugins/alsa/acp/alsa-mixer.c:4475 msgid "Mono" msgstr "Mono" #: spa/plugins/alsa/acp/alsa-mixer.c:4476 msgid "Stereo" msgstr "Estéreo" #: spa/plugins/alsa/acp/alsa-mixer.c:4484 #: spa/plugins/alsa/acp/alsa-mixer.c:4642 #: spa/plugins/bluez5/bluez5-device.c:1345 msgid "Headset" msgstr "Auriculares con micro" #: spa/plugins/alsa/acp/alsa-mixer.c:4485 #: spa/plugins/alsa/acp/alsa-mixer.c:4643 msgid "Speakerphone" msgstr "Altofalante" #: spa/plugins/alsa/acp/alsa-mixer.c:4486 #: spa/plugins/alsa/acp/alsa-mixer.c:4487 msgid "Multichannel" msgstr "Multicanle" #: spa/plugins/alsa/acp/alsa-mixer.c:4488 msgid "Analog Surround 2.1" msgstr "Envolvente analóxico 2.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4489 msgid "Analog Surround 3.0" msgstr "Envolvente analóxico 3.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4490 msgid "Analog Surround 3.1" msgstr "Envolvente analóxico 3.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4491 msgid "Analog Surround 4.0" msgstr "Envolvente analóxico 4.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4492 msgid "Analog Surround 4.1" msgstr "Envolvente analóxico 4.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4493 msgid "Analog Surround 5.0" msgstr "Envolvente analóxico 5.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4494 msgid "Analog Surround 5.1" msgstr "Envolvente analóxico 5.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4495 msgid "Analog Surround 6.0" msgstr "Envolvente analóxico 6.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4496 msgid "Analog Surround 6.1" msgstr "Envolvente analóxico 6.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4497 msgid "Analog Surround 7.0" msgstr "Envolvente analóxico 7.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4498 msgid "Analog Surround 7.1" msgstr "Envolvente analóxico 7.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4499 msgid "Digital Stereo (IEC958)" msgstr "Estéreo dixital (IEC958)" #: spa/plugins/alsa/acp/alsa-mixer.c:4500 msgid "Digital Surround 4.0 (IEC958/AC3)" msgstr "Envolvente dixital 4.0 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4501 msgid "Digital Surround 5.1 (IEC958/AC3)" msgstr "Envolvente dixital 5.1 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4502 msgid "Digital Surround 5.1 (IEC958/DTS)" msgstr "Envolvente dixital 5.1 (IEC958/ACDTS)" #: spa/plugins/alsa/acp/alsa-mixer.c:4503 msgid "Digital Stereo (HDMI)" msgstr "Estéreo dixital (HDMI)" #: spa/plugins/alsa/acp/alsa-mixer.c:4504 msgid "Digital Surround 5.1 (HDMI)" msgstr "Envolvente dixital 5.1 (HDMI)" #: spa/plugins/alsa/acp/alsa-mixer.c:4505 msgid "Chat" msgstr "Chat" #: spa/plugins/alsa/acp/alsa-mixer.c:4506 msgid "Game" msgstr "Xogo" #: spa/plugins/alsa/acp/alsa-mixer.c:4640 msgid "Analog Mono Duplex" msgstr "Monoaural analóxico dúplex" #: spa/plugins/alsa/acp/alsa-mixer.c:4641 msgid "Analog Stereo Duplex" msgstr "Estéreo analóxico dúplex" #: spa/plugins/alsa/acp/alsa-mixer.c:4644 msgid "Digital Stereo Duplex (IEC958)" msgstr "Estéreo dixital dúplex (IEC958)" #: spa/plugins/alsa/acp/alsa-mixer.c:4645 msgid "Multichannel Duplex" msgstr "Dúplex multicanle" #: spa/plugins/alsa/acp/alsa-mixer.c:4646 msgid "Stereo Duplex" msgstr "Dúplex estéreo" #: spa/plugins/alsa/acp/alsa-mixer.c:4647 msgid "Mono Chat + 7.1 Surround" msgstr "Chat mono + envolvente 7.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4754 #, c-format msgid "%s Output" msgstr "Saída %s" #: spa/plugins/alsa/acp/alsa-mixer.c:4761 #, c-format msgid "%s Input" msgstr "Entrada %s" #: spa/plugins/alsa/acp/alsa-util.c:1173 spa/plugins/alsa/acp/alsa-util.c:1267 #, c-format msgid "" "snd_pcm_avail() returned a value that is exceptionally large: %lu byte (%lu " "ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_avail() returned a value that is exceptionally large: %lu bytes (%lu " "ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "snd_pcm_avail() devolveu un valor que é excepcionalmente grande: %lu bytes " "(%lu ms).\n" "O máis probábel é que sexa un erro do controlador ALSA «%s». Informe disto " "aos desenvolvedores de ALSA." msgstr[1] "" "snd_pcm_avail() devolveu un valor que é excepcionalmente grande: %lu bytes " "(%lu ms).\n" "O máis probábel é que sexa un erro do controlador ALSA «%s». Informe disto " "aos desenvolvedores de ALSA." #: spa/plugins/alsa/acp/alsa-util.c:1239 #, c-format msgid "" "snd_pcm_delay() returned a value that is exceptionally large: %li byte " "(%s%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_delay() returned a value that is exceptionally large: %li bytes " "(%s%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "snd_pcm_delay() devolveu un valor que é excepcionalmente grande: %li bytes " "(%s%lu ms).\n" "O máis probábel é que sexa un erro do controlador ALSA «%s». Informe disto " "aos desenvolvedores de ALSA." msgstr[1] "" "snd_pcm_delay() devolveu un valor que é excepcionalmente grande: %li bytes " "(%s%lu ms).\n" "O máis probábel é que sexa un erro do controlador ALSA «%s». Informe disto " "aos desenvolvedores de ALSA." #: spa/plugins/alsa/acp/alsa-util.c:1286 #, c-format msgid "" "snd_pcm_avail_delay() returned strange values: delay %lu is less than avail " "%lu.\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr "" "snd_pcm_avail_delay() devolveu valores estraños: o atraso de %lu é menor que " "o dispoñíbel %lu.\n" "O máis probábel é que sexa un erro do controlador ALSA «%s». Informe disto " "aos desenvolvedores de ALSA." #: spa/plugins/alsa/acp/alsa-util.c:1329 #, c-format msgid "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu byte " "(%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu bytes " "(%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "snd_pcm_mmap_begin() devolveu un valor que é excepcionalmente grande: %lu " "bytes (%lu ms).\n" "O máis probábel é que sexa un erro do controlador ALSA «%s». Informe disto " "aos desenvolvedores de ALSA." msgstr[1] "" "snd_pcm_mmap_begin() devolveu un valor que é excepcionalmente grande: %lu " "bytes (%lu ms).\n" "O máis probábel é que sexa un erro do controlador ALSA «%s». Informe disto " "aos desenvolvedores de ALSA." #: spa/plugins/alsa/acp/channelmap.h:457 msgid "(invalid)" msgstr "(incorrecto)" #: spa/plugins/alsa/acp/compat.c:189 msgid "Built-in Audio" msgstr "Audio interno" #: spa/plugins/alsa/acp/compat.c:194 msgid "Modem" msgstr "Módem" #: spa/plugins/bluez5/bluez5-device.c:1196 msgid "Audio Gateway (A2DP Source & HSP/HFP AG)" msgstr "Porta de enlace de son (Orixe A2DP e HSP/HFP AG)" #: spa/plugins/bluez5/bluez5-device.c:1221 #, c-format msgid "High Fidelity Playback (A2DP Sink, codec %s)" msgstr "Reprodución de alta fidelidade (Sumideiro A2DP, códec %s)" #: spa/plugins/bluez5/bluez5-device.c:1224 #, c-format msgid "High Fidelity Duplex (A2DP Source/Sink, codec %s)" msgstr "Dúplex de alta fidelidade (Orixe/sumideiro A2DP, códec %s)" #: spa/plugins/bluez5/bluez5-device.c:1232 msgid "High Fidelity Playback (A2DP Sink)" msgstr "Reprodución de alta fidelidade (Sumideiro A2DP)" #: spa/plugins/bluez5/bluez5-device.c:1234 msgid "High Fidelity Duplex (A2DP Source/Sink)" msgstr "Dúplex de alta fidelidade (Orixe/sumideiro A2DP)" #: spa/plugins/bluez5/bluez5-device.c:1262 #, c-format msgid "Headset Head Unit (HSP/HFP, codec %s)" msgstr "Unidade de auriculares de cabeza (HSP/HFP, códec %s)" #: spa/plugins/bluez5/bluez5-device.c:1267 msgid "Headset Head Unit (HSP/HFP)" msgstr "Unidade de auriculares de cabeza (HSP/HFP)" #: spa/plugins/bluez5/bluez5-device.c:1346 #: spa/plugins/bluez5/bluez5-device.c:1351 #: spa/plugins/bluez5/bluez5-device.c:1358 #: spa/plugins/bluez5/bluez5-device.c:1364 #: spa/plugins/bluez5/bluez5-device.c:1370 #: spa/plugins/bluez5/bluez5-device.c:1376 #: spa/plugins/bluez5/bluez5-device.c:1382 #: spa/plugins/bluez5/bluez5-device.c:1388 #: spa/plugins/bluez5/bluez5-device.c:1394 msgid "Handsfree" msgstr "Sen mans" #: spa/plugins/bluez5/bluez5-device.c:1352 #| msgid "Handsfree" msgid "Handsfree (HFP)" msgstr "Sen mans (HFP)" #: spa/plugins/bluez5/bluez5-device.c:1369 msgid "Headphone" msgstr "Auriculares" #: spa/plugins/bluez5/bluez5-device.c:1375 msgid "Portable" msgstr "Portátil" #: spa/plugins/bluez5/bluez5-device.c:1381 msgid "Car" msgstr "Automóbil" #: spa/plugins/bluez5/bluez5-device.c:1387 msgid "HiFi" msgstr "HiFi" #: spa/plugins/bluez5/bluez5-device.c:1393 msgid "Phone" msgstr "Teléfono" #: spa/plugins/bluez5/bluez5-device.c:1400 msgid "Bluetooth" msgstr "Bluetooth" #: spa/plugins/bluez5/bluez5-device.c:1401 msgid "Bluetooth (HFP)" msgstr "Bluetooth (HFP)" pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/po/gu.po000066400000000000000000000471571511204443500221050ustar00rootroot00000000000000# translation of PipeWire.po to Gujarati # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # Sweta Kothari , 2009, 2012. # msgid "" msgstr "" "Project-Id-Version: PipeWire\n" "Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/pipewire/" "issues/new\n" "POT-Creation-Date: 2021-04-18 16:54+0800\n" "PO-Revision-Date: 2012-01-30 09:53+0000\n" "Last-Translator: Sweta Kothari \n" "Language-Team: Gujarati\n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Generator: KBabel 1.11.4\n" "Plural-Forms: nplurals=2; plural=(n!=1);\n" #: src/daemon/pipewire.c:43 #, c-format msgid "" "%s [options]\n" " -h, --help Show this help\n" " --version Show version\n" " -c, --config Load config (Default %s)\n" msgstr "" #: src/daemon/pipewire.desktop.in:4 msgid "PipeWire Media System" msgstr "" #: src/daemon/pipewire.desktop.in:5 msgid "Start the PipeWire Media System" msgstr "" #: src/examples/media-session/alsa-monitor.c:526 #: spa/plugins/alsa/acp/compat.c:187 msgid "Built-in Audio" msgstr "આંતરિક ઓડિયો" #: src/examples/media-session/alsa-monitor.c:530 #: spa/plugins/alsa/acp/compat.c:192 msgid "Modem" msgstr "મોડેમ" #: src/examples/media-session/alsa-monitor.c:539 msgid "Unknown device" msgstr "" #: src/tools/pw-cat.c:991 #, c-format msgid "" "%s [options] \n" " -h, --help Show this help\n" " --version Show version\n" " -v, --verbose Enable verbose operations\n" "\n" msgstr "" #: src/tools/pw-cat.c:998 #, c-format msgid "" " -R, --remote Remote daemon name\n" " --media-type Set media type (default %s)\n" " --media-category Set media category (default %s)\n" " --media-role Set media role (default %s)\n" " --target Set node target (default %s)\n" " 0 means don't link\n" " --latency Set node latency (default %s)\n" " Xunit (unit = s, ms, us, ns)\n" " or direct samples (256)\n" " the rate is the one of the source " "file\n" " --list-targets List available targets for --target\n" "\n" msgstr "" #: src/tools/pw-cat.c:1016 #, c-format msgid "" " --rate Sample rate (req. for rec) (default " "%u)\n" " --channels Number of channels (req. for rec) " "(default %u)\n" " --channel-map Channel map\n" " one of: \"stereo\", " "\"surround-51\",... or\n" " comma separated list of channel " "names: eg. \"FL,FR\"\n" " --format Sample format %s (req. for rec) " "(default %s)\n" " --volume Stream volume 0-1.0 (default %.3f)\n" " -q --quality Resampler quality (0 - 15) (default " "%d)\n" "\n" msgstr "" #: src/tools/pw-cat.c:1033 msgid "" " -p, --playback Playback mode\n" " -r, --record Recording mode\n" " -m, --midi Midi mode\n" "\n" msgstr "" #: src/tools/pw-cli.c:2932 #, c-format msgid "" "%s [options] [command]\n" " -h, --help Show this help\n" " --version Show version\n" " -d, --daemon Start as daemon (Default false)\n" " -r, --remote Remote daemon name\n" "\n" msgstr "" #: spa/plugins/alsa/acp/acp.c:290 msgid "Pro Audio" msgstr "" #: spa/plugins/alsa/acp/acp.c:411 spa/plugins/alsa/acp/alsa-mixer.c:4704 #: spa/plugins/bluez5/bluez5-device.c:1000 msgid "Off" msgstr "બંધ" #: spa/plugins/alsa/acp/channelmap.h:466 msgid "(invalid)" msgstr "(અયોગ્ય)" #: spa/plugins/alsa/acp/alsa-mixer.c:2709 msgid "Input" msgstr "ઇનપુટ" #: spa/plugins/alsa/acp/alsa-mixer.c:2710 msgid "Docking Station Input" msgstr "ડોકિંગ સ્ટેશન ઇનપુટ" #: spa/plugins/alsa/acp/alsa-mixer.c:2711 #, fuzzy msgid "Docking Station Microphone" msgstr "ડોકિંગ સ્ટેશન માઇક્રોફોન" #: spa/plugins/alsa/acp/alsa-mixer.c:2712 #, fuzzy msgid "Docking Station Line In" msgstr "ડોકિંગ સ્ટેશન ઇનપુટ" #: spa/plugins/alsa/acp/alsa-mixer.c:2713 #: spa/plugins/alsa/acp/alsa-mixer.c:2804 msgid "Line In" msgstr "લાઇન-ઇન" #: spa/plugins/alsa/acp/alsa-mixer.c:2714 #: spa/plugins/alsa/acp/alsa-mixer.c:2798 #: spa/plugins/bluez5/bluez5-device.c:1145 msgid "Microphone" msgstr "માઇક્રોફોન" #: spa/plugins/alsa/acp/alsa-mixer.c:2715 #: spa/plugins/alsa/acp/alsa-mixer.c:2799 #, fuzzy msgid "Front Microphone" msgstr "ડોકિંગ સ્ટેશન માઇક્રોફોન" #: spa/plugins/alsa/acp/alsa-mixer.c:2716 #: spa/plugins/alsa/acp/alsa-mixer.c:2800 #, fuzzy msgid "Rear Microphone" msgstr "માઇક્રોફોન" #: spa/plugins/alsa/acp/alsa-mixer.c:2717 msgid "External Microphone" msgstr "બહારનાં માઇક્રોફોન" #: spa/plugins/alsa/acp/alsa-mixer.c:2718 #: spa/plugins/alsa/acp/alsa-mixer.c:2802 msgid "Internal Microphone" msgstr "આંતરિક માઇક્રોફોન" #: spa/plugins/alsa/acp/alsa-mixer.c:2719 #: spa/plugins/alsa/acp/alsa-mixer.c:2805 msgid "Radio" msgstr "રેડિયો" #: spa/plugins/alsa/acp/alsa-mixer.c:2720 #: spa/plugins/alsa/acp/alsa-mixer.c:2806 msgid "Video" msgstr "વિડિયો" #: spa/plugins/alsa/acp/alsa-mixer.c:2721 msgid "Automatic Gain Control" msgstr "Automatic Gain Control" #: spa/plugins/alsa/acp/alsa-mixer.c:2722 msgid "No Automatic Gain Control" msgstr "Automatic Gain Control નથી" #: spa/plugins/alsa/acp/alsa-mixer.c:2723 msgid "Boost" msgstr "બુસ્ટ" #: spa/plugins/alsa/acp/alsa-mixer.c:2724 msgid "No Boost" msgstr "બુસ્ટ નથી" #: spa/plugins/alsa/acp/alsa-mixer.c:2725 msgid "Amplifier" msgstr "પરિવર્ધક" #: spa/plugins/alsa/acp/alsa-mixer.c:2726 msgid "No Amplifier" msgstr "પરિવર્ધક નથી" #: spa/plugins/alsa/acp/alsa-mixer.c:2727 #, fuzzy msgid "Bass Boost" msgstr "બુસ્ટ" #: spa/plugins/alsa/acp/alsa-mixer.c:2728 #, fuzzy msgid "No Bass Boost" msgstr "બુસ્ટ નથી" #: spa/plugins/alsa/acp/alsa-mixer.c:2729 #: spa/plugins/bluez5/bluez5-device.c:1150 msgid "Speaker" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2730 #: spa/plugins/alsa/acp/alsa-mixer.c:2808 msgid "Headphones" msgstr "ઍનલૉગ હૅડફોનો" #: spa/plugins/alsa/acp/alsa-mixer.c:2797 msgid "Analog Input" msgstr "ઍનલૉગ ઇનપુટ" #: spa/plugins/alsa/acp/alsa-mixer.c:2801 msgid "Dock Microphone" msgstr "ડોકિંગ સ્ટેશન માઇક્રોફોન" #: spa/plugins/alsa/acp/alsa-mixer.c:2803 #, fuzzy msgid "Headset Microphone" msgstr "માઇક્રોફોન" #: spa/plugins/alsa/acp/alsa-mixer.c:2807 msgid "Analog Output" msgstr "ઍનલૉગ આઉટપુટ" #: spa/plugins/alsa/acp/alsa-mixer.c:2809 #, fuzzy msgid "Headphones 2" msgstr "ઍનલૉગ હૅડફોનો" #: spa/plugins/alsa/acp/alsa-mixer.c:2810 #, fuzzy msgid "Headphones Mono Output" msgstr "ઍનલૉગ મોનો આઉટપુટ" #: spa/plugins/alsa/acp/alsa-mixer.c:2811 #, fuzzy msgid "Line Out" msgstr "લાઇન-ઇન" #: spa/plugins/alsa/acp/alsa-mixer.c:2812 msgid "Analog Mono Output" msgstr "ઍનલૉગ મોનો આઉટપુટ" #: spa/plugins/alsa/acp/alsa-mixer.c:2813 #, fuzzy msgid "Speakers" msgstr "ઍનલૉગ સ્ટેરિઓ" #: spa/plugins/alsa/acp/alsa-mixer.c:2814 msgid "HDMI / DisplayPort" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2815 #, fuzzy msgid "Digital Output (S/PDIF)" msgstr "ડિજિટલ સ્ટેરિઓ (HDMI)" #: spa/plugins/alsa/acp/alsa-mixer.c:2816 #, fuzzy msgid "Digital Input (S/PDIF)" msgstr "ડિજિટલ સ્ટેરિઓ (HDMI)" #: spa/plugins/alsa/acp/alsa-mixer.c:2817 msgid "Multichannel Input" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2818 #, fuzzy msgid "Multichannel Output" msgstr "શૂન્ય આઉટપુટ" #: spa/plugins/alsa/acp/alsa-mixer.c:2819 #, fuzzy msgid "Game Output" msgstr "શૂન્ય આઉટપુટ" #: spa/plugins/alsa/acp/alsa-mixer.c:2820 #: spa/plugins/alsa/acp/alsa-mixer.c:2821 #, fuzzy msgid "Chat Output" msgstr "શૂન્ય આઉટપુટ" #: spa/plugins/alsa/acp/alsa-mixer.c:2822 #, fuzzy msgid "Chat Input" msgstr "ઇનપુટ" #: spa/plugins/alsa/acp/alsa-mixer.c:2823 #, fuzzy msgid "Virtual Surround 7.1" msgstr "ઍનલૉગ સરાઉન્ડ 7.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4527 msgid "Analog Mono" msgstr "ઍનલૉગ મોનો" #: spa/plugins/alsa/acp/alsa-mixer.c:4528 #, fuzzy msgid "Analog Mono (Left)" msgstr "ઍનલૉગ મોનો" #: spa/plugins/alsa/acp/alsa-mixer.c:4529 #, fuzzy msgid "Analog Mono (Right)" msgstr "ઍનલૉગ મોનો" #. Note: Not translated to "Analog Stereo Input", because the source #. * name gets "Input" appended to it automatically, so adding "Input" #. * here would lead to the source name to become "Analog Stereo Input #. * Input". The same logic applies to analog-stereo-output, #. * multichannel-input and multichannel-output. #: spa/plugins/alsa/acp/alsa-mixer.c:4530 #: spa/plugins/alsa/acp/alsa-mixer.c:4538 #: spa/plugins/alsa/acp/alsa-mixer.c:4539 msgid "Analog Stereo" msgstr "ઍનલૉગ સ્ટેરિઓ" #: spa/plugins/alsa/acp/alsa-mixer.c:4531 msgid "Mono" msgstr "મોનો" #: spa/plugins/alsa/acp/alsa-mixer.c:4532 msgid "Stereo" msgstr "સ્ટેરિઓ" #: spa/plugins/alsa/acp/alsa-mixer.c:4540 #: spa/plugins/alsa/acp/alsa-mixer.c:4698 #: spa/plugins/bluez5/bluez5-device.c:1135 msgid "Headset" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4541 #: spa/plugins/alsa/acp/alsa-mixer.c:4699 #, fuzzy msgid "Speakerphone" msgstr "ઍનલૉગ સ્ટેરિઓ" #: spa/plugins/alsa/acp/alsa-mixer.c:4542 #: spa/plugins/alsa/acp/alsa-mixer.c:4543 msgid "Multichannel" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4544 msgid "Analog Surround 2.1" msgstr "ઍનલૉગ સરાઉન્ડ 2.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4545 msgid "Analog Surround 3.0" msgstr "ઍનલૉગ સરાઉન્ડ 3.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4546 msgid "Analog Surround 3.1" msgstr "ઍનલૉગ સરાઉન્ડ 3.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4547 msgid "Analog Surround 4.0" msgstr "ઍનલૉગ સરાઉન્ડ 4.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4548 msgid "Analog Surround 4.1" msgstr "ઍનલૉગ સરાઉન્ડ 4.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4549 msgid "Analog Surround 5.0" msgstr "ઍનલૉગ સરાઉન્ડ 5.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4550 msgid "Analog Surround 5.1" msgstr "ઍનલૉગ સરાઉન્ડ 5.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4551 msgid "Analog Surround 6.0" msgstr "ઍનલૉગ સરાઉન્ડ 6.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4552 msgid "Analog Surround 6.1" msgstr "ઍનલૉગ સરાઉન્ડ 6.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4553 msgid "Analog Surround 7.0" msgstr "ઍનલૉગ સરાઉન્ડ 7.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4554 msgid "Analog Surround 7.1" msgstr "ઍનલૉગ સરાઉન્ડ 7.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4555 msgid "Digital Stereo (IEC958)" msgstr "ડિજિટલ સ્ટેરિઓ (IEC958)" #: spa/plugins/alsa/acp/alsa-mixer.c:4556 msgid "Digital Surround 4.0 (IEC958/AC3)" msgstr "ડિજિટલ સરાઉન્ડ 4.0 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4557 msgid "Digital Surround 5.1 (IEC958/AC3)" msgstr "ડિજિટલ સરાઉન્ડ 5.1 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4558 #, fuzzy msgid "Digital Surround 5.1 (IEC958/DTS)" msgstr "ડિજિટલ સરાઉન્ડ 5.1 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4559 msgid "Digital Stereo (HDMI)" msgstr "ડિજિટલ સ્ટેરિઓ (HDMI)" #: spa/plugins/alsa/acp/alsa-mixer.c:4560 #, fuzzy msgid "Digital Surround 5.1 (HDMI)" msgstr "ડિજિટલ સરાઉન્ડ 5.1 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4561 msgid "Chat" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4562 msgid "Game" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4696 msgid "Analog Mono Duplex" msgstr "ઍનલૉગ મોનો ડુપ્લેક્ષ" #: spa/plugins/alsa/acp/alsa-mixer.c:4697 msgid "Analog Stereo Duplex" msgstr "ઍનલૉગ સ્ટેરિઓ ડુપ્લેક્ષ" #: spa/plugins/alsa/acp/alsa-mixer.c:4700 msgid "Digital Stereo Duplex (IEC958)" msgstr "ડિજિટલ સ્ટેરિઓ ડુપ્લેક્ષ (IEC958)" #: spa/plugins/alsa/acp/alsa-mixer.c:4701 msgid "Multichannel Duplex" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4702 #, fuzzy msgid "Stereo Duplex" msgstr "ઍનલૉગ સ્ટેરિઓ ડુપ્લેક્ષ" #: spa/plugins/alsa/acp/alsa-mixer.c:4703 msgid "Mono Chat + 7.1 Surround" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4806 #, fuzzy, c-format msgid "%s Output" msgstr "શૂન્ય આઉટપુટ" #: spa/plugins/alsa/acp/alsa-mixer.c:4813 #, fuzzy, c-format msgid "%s Input" msgstr "ઇનપુટ" #: spa/plugins/alsa/acp/alsa-util.c:1175 spa/plugins/alsa/acp/alsa-util.c:1269 #, fuzzy, c-format msgid "" "snd_pcm_avail() returned a value that is exceptionally large: %lu byte (%lu " "ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_avail() returned a value that is exceptionally large: %lu bytes (%lu " "ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "snd_pcm_avail() કિંમતને પાછુ મળેલ છે કે જે અપવાદ રીતે વિશાળ છે: %lu bytes (%lu ms).\n" "ALSA ડ્રાઇવર '%s' માં મોટેભાગે આ ભૂલ જેવુ છે. ALSA ડેવલ્પરોમાં આ સમસ્યાને મહેરબાની કરીને " "અહેવાલ કરો." msgstr[1] "" "snd_pcm_avail() કિંમતને પાછુ મળેલ છે કે જે અપવાદ રીતે વિશાળ છે: %lu bytes (%lu ms).\n" "ALSA ડ્રાઇવર '%s' માં મોટેભાગે આ ભૂલ જેવુ છે. ALSA ડેવલ્પરોમાં આ સમસ્યાને મહેરબાની કરીને " "અહેવાલ કરો." #: spa/plugins/alsa/acp/alsa-util.c:1241 #, fuzzy, c-format msgid "" "snd_pcm_delay() returned a value that is exceptionally large: %li byte (%s" "%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_delay() returned a value that is exceptionally large: %li bytes (%s" "%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "snd_pcm_delay() કિંમતને પાછુ મળેલ છે કે જે અપવાદ રીતે વિશાળ છે: %li bytes (%s%lu " "ms).\n" "ALSA ડ્રાઇવર '%s' માં મોટેભાગે આ ભૂલ જેવુ છે. ALSA ડેવલ્પરોમાં આ સમસ્યાને મહેરબાની કરીને " "અહેવાલ કરો." msgstr[1] "" "snd_pcm_delay() કિંમતને પાછુ મળેલ છે કે જે અપવાદ રીતે વિશાળ છે: %li bytes (%s%lu " "ms).\n" "ALSA ડ્રાઇવર '%s' માં મોટેભાગે આ ભૂલ જેવુ છે. ALSA ડેવલ્પરોમાં આ સમસ્યાને મહેરબાની કરીને " "અહેવાલ કરો." #: spa/plugins/alsa/acp/alsa-util.c:1288 #, fuzzy, c-format msgid "" "snd_pcm_avail_delay() returned strange values: delay %lu is less than avail " "%lu.\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr "" "snd_pcm_avail() કિંમતને પાછુ મળેલ છે કે જે અપવાદ રીતે વિશાળ છે: %lu bytes (%lu ms).\n" "ALSA ડ્રાઇવર '%s' માં મોટેભાગે આ ભૂલ જેવુ છે. ALSA ડેવલ્પરોમાં આ સમસ્યાને મહેરબાની કરીને " "અહેવાલ કરો." #: spa/plugins/alsa/acp/alsa-util.c:1331 #, fuzzy, c-format msgid "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu byte " "(%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu bytes " "(%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "snd_pcm_mmap_begin() કિંમતને પાછુ મળેલ છે કે જે અપવાદ રીતે વિશાળ છે: %lu બાઇટો (%lu " "ms).\n" "ALSA ડ્રાઇવર '%s' માં મોટેભાગે આ ભૂલ જેવુ છે. ALSA ડેવલ્પરોમાં આ સમસ્યાને મહેરબાની કરીને " "અહેવાલ કરો." msgstr[1] "" "snd_pcm_mmap_begin() કિંમતને પાછુ મળેલ છે કે જે અપવાદ રીતે વિશાળ છે: %lu બાઇટો (%lu " "ms).\n" "ALSA ડ્રાઇવર '%s' માં મોટેભાગે આ ભૂલ જેવુ છે. ALSA ડેવલ્પરોમાં આ સમસ્યાને મહેરબાની કરીને " "અહેવાલ કરો." #: spa/plugins/bluez5/bluez5-device.c:1010 msgid "Audio Gateway (A2DP Source & HSP/HFP AG)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1033 #, c-format msgid "High Fidelity Playback (A2DP Sink, codec %s)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1035 #, c-format msgid "High Fidelity Duplex (A2DP Source/Sink, codec %s)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1041 msgid "High Fidelity Playback (A2DP Sink)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1043 msgid "High Fidelity Duplex (A2DP Source/Sink)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1070 #, c-format msgid "Headset Head Unit (HSP/HFP, codec %s)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1074 msgid "Headset Head Unit (HSP/HFP)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1140 msgid "Handsfree" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1155 #, fuzzy msgid "Headphone" msgstr "ઍનલૉગ હૅડફોનો" #: spa/plugins/bluez5/bluez5-device.c:1160 msgid "Portable" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1165 msgid "Car" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1170 msgid "HiFi" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1175 msgid "Phone" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1181 msgid "Bluetooth" msgstr "" pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/po/he.po000066400000000000000000000371511511204443500220570ustar00rootroot00000000000000# # Elad , 2012. # msgid "" msgstr "" "Project-Id-Version: pipewire\n" "Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/pipewire/" "issues/new\n" "POT-Creation-Date: 2021-04-18 16:54+0800\n" "PO-Revision-Date: 2021-03-02 14:40+0000\n" "Last-Translator: Yaron Shahrabani \n" "Language-Team: Hebrew \n" "Language: he\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Generator: Weblate 4.4.2\n" "X-Poedit-Language: Hebrew\n" "X-Poedit-Country: Israel\n" #: src/daemon/pipewire.c:43 #, c-format msgid "" "%s [options]\n" " -h, --help Show this help\n" " --version Show version\n" " -c, --config Load config (Default %s)\n" msgstr "" #: src/daemon/pipewire.desktop.in:4 msgid "PipeWire Media System" msgstr "" #: src/daemon/pipewire.desktop.in:5 msgid "Start the PipeWire Media System" msgstr "" #: src/examples/media-session/alsa-monitor.c:526 #: spa/plugins/alsa/acp/compat.c:187 msgid "Built-in Audio" msgstr "צליל פנימי" #: src/examples/media-session/alsa-monitor.c:530 #: spa/plugins/alsa/acp/compat.c:192 msgid "Modem" msgstr "מודם" #: src/examples/media-session/alsa-monitor.c:539 msgid "Unknown device" msgstr "" #: src/tools/pw-cat.c:991 #, c-format msgid "" "%s [options] \n" " -h, --help Show this help\n" " --version Show version\n" " -v, --verbose Enable verbose operations\n" "\n" msgstr "" #: src/tools/pw-cat.c:998 #, c-format msgid "" " -R, --remote Remote daemon name\n" " --media-type Set media type (default %s)\n" " --media-category Set media category (default %s)\n" " --media-role Set media role (default %s)\n" " --target Set node target (default %s)\n" " 0 means don't link\n" " --latency Set node latency (default %s)\n" " Xunit (unit = s, ms, us, ns)\n" " or direct samples (256)\n" " the rate is the one of the source " "file\n" " --list-targets List available targets for --target\n" "\n" msgstr "" #: src/tools/pw-cat.c:1016 #, c-format msgid "" " --rate Sample rate (req. for rec) (default " "%u)\n" " --channels Number of channels (req. for rec) " "(default %u)\n" " --channel-map Channel map\n" " one of: \"stereo\", " "\"surround-51\",... or\n" " comma separated list of channel " "names: eg. \"FL,FR\"\n" " --format Sample format %s (req. for rec) " "(default %s)\n" " --volume Stream volume 0-1.0 (default %.3f)\n" " -q --quality Resampler quality (0 - 15) (default " "%d)\n" "\n" msgstr "" #: src/tools/pw-cat.c:1033 msgid "" " -p, --playback Playback mode\n" " -r, --record Recording mode\n" " -m, --midi Midi mode\n" "\n" msgstr "" #: src/tools/pw-cli.c:2932 #, c-format msgid "" "%s [options] [command]\n" " -h, --help Show this help\n" " --version Show version\n" " -d, --daemon Start as daemon (Default false)\n" " -r, --remote Remote daemon name\n" "\n" msgstr "" #: spa/plugins/alsa/acp/acp.c:290 msgid "Pro Audio" msgstr "" #: spa/plugins/alsa/acp/acp.c:411 spa/plugins/alsa/acp/alsa-mixer.c:4704 #: spa/plugins/bluez5/bluez5-device.c:1000 msgid "Off" msgstr "מכובה" #: spa/plugins/alsa/acp/channelmap.h:466 msgid "(invalid)" msgstr "(לא תקף)" #: spa/plugins/alsa/acp/alsa-mixer.c:2709 msgid "Input" msgstr "קלט" #: spa/plugins/alsa/acp/alsa-mixer.c:2710 msgid "Docking Station Input" msgstr "קלט מתחנת עגינה" #: spa/plugins/alsa/acp/alsa-mixer.c:2711 msgid "Docking Station Microphone" msgstr "מיקרופון מתחנת עגינה" #: spa/plugins/alsa/acp/alsa-mixer.c:2712 msgid "Docking Station Line In" msgstr "קו נכנס מתחנת עגינה" #: spa/plugins/alsa/acp/alsa-mixer.c:2713 #: spa/plugins/alsa/acp/alsa-mixer.c:2804 msgid "Line In" msgstr "קו נכנס" #: spa/plugins/alsa/acp/alsa-mixer.c:2714 #: spa/plugins/alsa/acp/alsa-mixer.c:2798 #: spa/plugins/bluez5/bluez5-device.c:1145 msgid "Microphone" msgstr "מיקרופון" #: spa/plugins/alsa/acp/alsa-mixer.c:2715 #: spa/plugins/alsa/acp/alsa-mixer.c:2799 msgid "Front Microphone" msgstr "מיקרופון קדמי" #: spa/plugins/alsa/acp/alsa-mixer.c:2716 #: spa/plugins/alsa/acp/alsa-mixer.c:2800 msgid "Rear Microphone" msgstr "מיקרופון אחורי" #: spa/plugins/alsa/acp/alsa-mixer.c:2717 msgid "External Microphone" msgstr "מיקרופון חיצוני" #: spa/plugins/alsa/acp/alsa-mixer.c:2718 #: spa/plugins/alsa/acp/alsa-mixer.c:2802 msgid "Internal Microphone" msgstr "מיקרופון פנימי" #: spa/plugins/alsa/acp/alsa-mixer.c:2719 #: spa/plugins/alsa/acp/alsa-mixer.c:2805 msgid "Radio" msgstr "רדיו" #: spa/plugins/alsa/acp/alsa-mixer.c:2720 #: spa/plugins/alsa/acp/alsa-mixer.c:2806 msgid "Video" msgstr "וידאו" #: spa/plugins/alsa/acp/alsa-mixer.c:2721 msgid "Automatic Gain Control" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2722 msgid "No Automatic Gain Control" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2723 msgid "Boost" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2724 msgid "No Boost" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2725 msgid "Amplifier" msgstr "מגבר" #: spa/plugins/alsa/acp/alsa-mixer.c:2726 msgid "No Amplifier" msgstr "אין מגבר" #: spa/plugins/alsa/acp/alsa-mixer.c:2727 msgid "Bass Boost" msgstr "הגברת באסים" #: spa/plugins/alsa/acp/alsa-mixer.c:2728 msgid "No Bass Boost" msgstr "ללא הגברת באסים" #: spa/plugins/alsa/acp/alsa-mixer.c:2729 #: spa/plugins/bluez5/bluez5-device.c:1150 msgid "Speaker" msgstr "רמקול" #: spa/plugins/alsa/acp/alsa-mixer.c:2730 #: spa/plugins/alsa/acp/alsa-mixer.c:2808 msgid "Headphones" msgstr "אוזניות אנלוגיות" #: spa/plugins/alsa/acp/alsa-mixer.c:2797 msgid "Analog Input" msgstr "קלט אנלוגי" #: spa/plugins/alsa/acp/alsa-mixer.c:2801 msgid "Dock Microphone" msgstr "מיקרופון של תחנת עגינה" #: spa/plugins/alsa/acp/alsa-mixer.c:2803 msgid "Headset Microphone" msgstr "מיקרופון באוזניות" #: spa/plugins/alsa/acp/alsa-mixer.c:2807 msgid "Analog Output" msgstr "פלט אנלוגי" #: spa/plugins/alsa/acp/alsa-mixer.c:2809 msgid "Headphones 2" msgstr "אוזניות 2" #: spa/plugins/alsa/acp/alsa-mixer.c:2810 msgid "Headphones Mono Output" msgstr "פלט מונו לאוזניות" #: spa/plugins/alsa/acp/alsa-mixer.c:2811 msgid "Line Out" msgstr "קו יוצא" #: spa/plugins/alsa/acp/alsa-mixer.c:2812 msgid "Analog Mono Output" msgstr "פלט מונו אנלוגי" #: spa/plugins/alsa/acp/alsa-mixer.c:2813 msgid "Speakers" msgstr "רמקולים" #: spa/plugins/alsa/acp/alsa-mixer.c:2814 msgid "HDMI / DisplayPort" msgstr "HDMI / DisplayPort" #: spa/plugins/alsa/acp/alsa-mixer.c:2815 msgid "Digital Output (S/PDIF)" msgstr "פלט דיגיטלי (S/PDIF)" #: spa/plugins/alsa/acp/alsa-mixer.c:2816 msgid "Digital Input (S/PDIF)" msgstr "קלט דיגיטלי (S/PDIF)" #: spa/plugins/alsa/acp/alsa-mixer.c:2817 msgid "Multichannel Input" msgstr "קלט רב־ערוצי" #: spa/plugins/alsa/acp/alsa-mixer.c:2818 msgid "Multichannel Output" msgstr "פלט רב־ערוצי" #: spa/plugins/alsa/acp/alsa-mixer.c:2819 msgid "Game Output" msgstr "פלט משחק" #: spa/plugins/alsa/acp/alsa-mixer.c:2820 #: spa/plugins/alsa/acp/alsa-mixer.c:2821 msgid "Chat Output" msgstr "פלט צ׳אט" #: spa/plugins/alsa/acp/alsa-mixer.c:2822 msgid "Chat Input" msgstr "קלט צ׳אט" #: spa/plugins/alsa/acp/alsa-mixer.c:2823 msgid "Virtual Surround 7.1" msgstr "סראונד וירטואלי 7.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4527 msgid "Analog Mono" msgstr "מונו אנלוגי" #: spa/plugins/alsa/acp/alsa-mixer.c:4528 msgid "Analog Mono (Left)" msgstr "מונו אנלוגי (שמאל)" #: spa/plugins/alsa/acp/alsa-mixer.c:4529 msgid "Analog Mono (Right)" msgstr "מונו אנלוגי (ימין)" #. Note: Not translated to "Analog Stereo Input", because the source #. * name gets "Input" appended to it automatically, so adding "Input" #. * here would lead to the source name to become "Analog Stereo Input #. * Input". The same logic applies to analog-stereo-output, #. * multichannel-input and multichannel-output. #: spa/plugins/alsa/acp/alsa-mixer.c:4530 #: spa/plugins/alsa/acp/alsa-mixer.c:4538 #: spa/plugins/alsa/acp/alsa-mixer.c:4539 msgid "Analog Stereo" msgstr "סטריאו אנלוגי" #: spa/plugins/alsa/acp/alsa-mixer.c:4531 msgid "Mono" msgstr "מונו" #: spa/plugins/alsa/acp/alsa-mixer.c:4532 msgid "Stereo" msgstr "סטריאו" #: spa/plugins/alsa/acp/alsa-mixer.c:4540 #: spa/plugins/alsa/acp/alsa-mixer.c:4698 #: spa/plugins/bluez5/bluez5-device.c:1135 msgid "Headset" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4541 #: spa/plugins/alsa/acp/alsa-mixer.c:4699 msgid "Speakerphone" msgstr "דיבורית לחדר ישיבות" #: spa/plugins/alsa/acp/alsa-mixer.c:4542 #: spa/plugins/alsa/acp/alsa-mixer.c:4543 msgid "Multichannel" msgstr "ערוצים מרובים" #: spa/plugins/alsa/acp/alsa-mixer.c:4544 msgid "Analog Surround 2.1" msgstr "סראונד אנלוגי 2.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4545 msgid "Analog Surround 3.0" msgstr "סראונד אנלוגי 3.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4546 msgid "Analog Surround 3.1" msgstr "סראונד אנלוגי 3.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4547 msgid "Analog Surround 4.0" msgstr "סראונד אנלוגי 4.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4548 msgid "Analog Surround 4.1" msgstr "סראונד אנלוגי 4.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4549 msgid "Analog Surround 5.0" msgstr "סראונד אנלוגי 5.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4550 msgid "Analog Surround 5.1" msgstr "סראונד אנלוגי 5.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4551 msgid "Analog Surround 6.0" msgstr "סראונד אנלוגי 6.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4552 msgid "Analog Surround 6.1" msgstr "סראונד אנלוגי 6.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4553 msgid "Analog Surround 7.0" msgstr "סראונד אנלוגי 7.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4554 msgid "Analog Surround 7.1" msgstr "סראונד אנלוגי 7.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4555 msgid "Digital Stereo (IEC958)" msgstr "סטריאו דיגיטלי (IEC958)" #: spa/plugins/alsa/acp/alsa-mixer.c:4556 msgid "Digital Surround 4.0 (IEC958/AC3)" msgstr "סראונד דיגיטלי 4.0 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4557 msgid "Digital Surround 5.1 (IEC958/AC3)" msgstr "סראונד דיגיטלי 5.1 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4558 msgid "Digital Surround 5.1 (IEC958/DTS)" msgstr "סראונד דיגיטלי 5.1 (IEC958/DTS)" #: spa/plugins/alsa/acp/alsa-mixer.c:4559 msgid "Digital Stereo (HDMI)" msgstr "סטריאו דיגיטלי (HDMI)" #: spa/plugins/alsa/acp/alsa-mixer.c:4560 msgid "Digital Surround 5.1 (HDMI)" msgstr "סראונד דיגיטלי 5.1 (HDMI)" #: spa/plugins/alsa/acp/alsa-mixer.c:4561 msgid "Chat" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4562 msgid "Game" msgstr "משחק" #: spa/plugins/alsa/acp/alsa-mixer.c:4696 msgid "Analog Mono Duplex" msgstr "מונו אנלוגי משולב" #: spa/plugins/alsa/acp/alsa-mixer.c:4697 msgid "Analog Stereo Duplex" msgstr "סטריאו אנלוגי משולב" #: spa/plugins/alsa/acp/alsa-mixer.c:4700 msgid "Digital Stereo Duplex (IEC958)" msgstr "סטריאו דיגיטלי משולב (IEC958)" #: spa/plugins/alsa/acp/alsa-mixer.c:4701 msgid "Multichannel Duplex" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4702 msgid "Stereo Duplex" msgstr "סטריאו משולב" #: spa/plugins/alsa/acp/alsa-mixer.c:4703 msgid "Mono Chat + 7.1 Surround" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4806 #, c-format msgid "%s Output" msgstr "פלט %s" #: spa/plugins/alsa/acp/alsa-mixer.c:4813 #, c-format msgid "%s Input" msgstr "קלט %s" #: spa/plugins/alsa/acp/alsa-util.c:1175 spa/plugins/alsa/acp/alsa-util.c:1269 #, c-format msgid "" "snd_pcm_avail() returned a value that is exceptionally large: %lu byte (%lu " "ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_avail() returned a value that is exceptionally large: %lu bytes (%lu " "ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" msgstr[1] "" #: spa/plugins/alsa/acp/alsa-util.c:1241 #, c-format msgid "" "snd_pcm_delay() returned a value that is exceptionally large: %li byte (%s" "%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_delay() returned a value that is exceptionally large: %li bytes (%s" "%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" msgstr[1] "" #: spa/plugins/alsa/acp/alsa-util.c:1288 #, c-format msgid "" "snd_pcm_avail_delay() returned strange values: delay %lu is less than avail " "%lu.\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr "" #: spa/plugins/alsa/acp/alsa-util.c:1331 #, c-format msgid "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu byte " "(%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu bytes " "(%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" msgstr[1] "" #: spa/plugins/bluez5/bluez5-device.c:1010 msgid "Audio Gateway (A2DP Source & HSP/HFP AG)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1033 #, c-format msgid "High Fidelity Playback (A2DP Sink, codec %s)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1035 #, c-format msgid "High Fidelity Duplex (A2DP Source/Sink, codec %s)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1041 msgid "High Fidelity Playback (A2DP Sink)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1043 msgid "High Fidelity Duplex (A2DP Source/Sink)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1070 #, c-format msgid "Headset Head Unit (HSP/HFP, codec %s)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1074 msgid "Headset Head Unit (HSP/HFP)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1140 msgid "Handsfree" msgstr "דיבורית" #: spa/plugins/bluez5/bluez5-device.c:1155 msgid "Headphone" msgstr "אוזניה" #: spa/plugins/bluez5/bluez5-device.c:1160 msgid "Portable" msgstr "נייד" #: spa/plugins/bluez5/bluez5-device.c:1165 msgid "Car" msgstr "מכונית" #: spa/plugins/bluez5/bluez5-device.c:1170 msgid "HiFi" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1175 msgid "Phone" msgstr "טלפון" #: spa/plugins/bluez5/bluez5-device.c:1181 msgid "Bluetooth" msgstr "" pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/po/hi.po000066400000000000000000000473731511204443500220720ustar00rootroot00000000000000# translation of pipewire.master-tx.po to Hindi # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # Rajesh Ranjan , 2009, 2012. # msgid "" msgstr "" "Project-Id-Version: pipewire.master-tx\n" "Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/pipewire/" "issues/new\n" "POT-Creation-Date: 2021-04-18 16:54+0800\n" "PO-Revision-Date: 2012-01-30 09:54+0000\n" "Last-Translator: Rajesh Ranjan \n" "Language-Team: Hindi \n" "Language: hi\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Generator: KBabel 1.11.4\n" "Plural-Forms: nplurals=2; plural=(n!=1);\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n" #: src/daemon/pipewire.c:43 #, c-format msgid "" "%s [options]\n" " -h, --help Show this help\n" " --version Show version\n" " -c, --config Load config (Default %s)\n" msgstr "" #: src/daemon/pipewire.desktop.in:4 msgid "PipeWire Media System" msgstr "" #: src/daemon/pipewire.desktop.in:5 msgid "Start the PipeWire Media System" msgstr "" #: src/examples/media-session/alsa-monitor.c:526 #: spa/plugins/alsa/acp/compat.c:187 msgid "Built-in Audio" msgstr "आंतरिक ऑडियो" #: src/examples/media-session/alsa-monitor.c:530 #: spa/plugins/alsa/acp/compat.c:192 msgid "Modem" msgstr "मॉडेम" #: src/examples/media-session/alsa-monitor.c:539 msgid "Unknown device" msgstr "" #: src/tools/pw-cat.c:991 #, c-format msgid "" "%s [options] \n" " -h, --help Show this help\n" " --version Show version\n" " -v, --verbose Enable verbose operations\n" "\n" msgstr "" #: src/tools/pw-cat.c:998 #, c-format msgid "" " -R, --remote Remote daemon name\n" " --media-type Set media type (default %s)\n" " --media-category Set media category (default %s)\n" " --media-role Set media role (default %s)\n" " --target Set node target (default %s)\n" " 0 means don't link\n" " --latency Set node latency (default %s)\n" " Xunit (unit = s, ms, us, ns)\n" " or direct samples (256)\n" " the rate is the one of the source " "file\n" " --list-targets List available targets for --target\n" "\n" msgstr "" #: src/tools/pw-cat.c:1016 #, c-format msgid "" " --rate Sample rate (req. for rec) (default " "%u)\n" " --channels Number of channels (req. for rec) " "(default %u)\n" " --channel-map Channel map\n" " one of: \"stereo\", " "\"surround-51\",... or\n" " comma separated list of channel " "names: eg. \"FL,FR\"\n" " --format Sample format %s (req. for rec) " "(default %s)\n" " --volume Stream volume 0-1.0 (default %.3f)\n" " -q --quality Resampler quality (0 - 15) (default " "%d)\n" "\n" msgstr "" #: src/tools/pw-cat.c:1033 msgid "" " -p, --playback Playback mode\n" " -r, --record Recording mode\n" " -m, --midi Midi mode\n" "\n" msgstr "" #: src/tools/pw-cli.c:2932 #, c-format msgid "" "%s [options] [command]\n" " -h, --help Show this help\n" " --version Show version\n" " -d, --daemon Start as daemon (Default false)\n" " -r, --remote Remote daemon name\n" "\n" msgstr "" #: spa/plugins/alsa/acp/acp.c:290 msgid "Pro Audio" msgstr "" #: spa/plugins/alsa/acp/acp.c:411 spa/plugins/alsa/acp/alsa-mixer.c:4704 #: spa/plugins/bluez5/bluez5-device.c:1000 msgid "Off" msgstr "बंद" #: spa/plugins/alsa/acp/channelmap.h:466 msgid "(invalid)" msgstr "(अवैध)" #: spa/plugins/alsa/acp/alsa-mixer.c:2709 msgid "Input" msgstr "इनपुट" #: spa/plugins/alsa/acp/alsa-mixer.c:2710 msgid "Docking Station Input" msgstr "डॉकिंग स्टेशन इनपुट" #: spa/plugins/alsa/acp/alsa-mixer.c:2711 #, fuzzy msgid "Docking Station Microphone" msgstr "डॉकिंग स्टेशन माइक्रोफोन" #: spa/plugins/alsa/acp/alsa-mixer.c:2712 #, fuzzy msgid "Docking Station Line In" msgstr "डॉकिंग स्टेशन इनपुट" #: spa/plugins/alsa/acp/alsa-mixer.c:2713 #: spa/plugins/alsa/acp/alsa-mixer.c:2804 msgid "Line In" msgstr "लाइन इन" #: spa/plugins/alsa/acp/alsa-mixer.c:2714 #: spa/plugins/alsa/acp/alsa-mixer.c:2798 #: spa/plugins/bluez5/bluez5-device.c:1145 msgid "Microphone" msgstr "माइक्रोफोन" #: spa/plugins/alsa/acp/alsa-mixer.c:2715 #: spa/plugins/alsa/acp/alsa-mixer.c:2799 #, fuzzy msgid "Front Microphone" msgstr "डॉकिंग स्टेशन माइक्रोफोन" #: spa/plugins/alsa/acp/alsa-mixer.c:2716 #: spa/plugins/alsa/acp/alsa-mixer.c:2800 #, fuzzy msgid "Rear Microphone" msgstr "माइक्रोफोन" #: spa/plugins/alsa/acp/alsa-mixer.c:2717 msgid "External Microphone" msgstr "बाहरी माइक्रोफोन" #: spa/plugins/alsa/acp/alsa-mixer.c:2718 #: spa/plugins/alsa/acp/alsa-mixer.c:2802 msgid "Internal Microphone" msgstr "आंतरिक माइक्रोफोन" #: spa/plugins/alsa/acp/alsa-mixer.c:2719 #: spa/plugins/alsa/acp/alsa-mixer.c:2805 msgid "Radio" msgstr "रेडियो" #: spa/plugins/alsa/acp/alsa-mixer.c:2720 #: spa/plugins/alsa/acp/alsa-mixer.c:2806 msgid "Video" msgstr "वीडियो" #: spa/plugins/alsa/acp/alsa-mixer.c:2721 msgid "Automatic Gain Control" msgstr "स्वचालित प्राप्ति नियंत्रण" #: spa/plugins/alsa/acp/alsa-mixer.c:2722 msgid "No Automatic Gain Control" msgstr "कोई स्वचालित प्राप्ति नियंत्रण नहीं" #: spa/plugins/alsa/acp/alsa-mixer.c:2723 msgid "Boost" msgstr "बूस्ट" #: spa/plugins/alsa/acp/alsa-mixer.c:2724 msgid "No Boost" msgstr "कोई बढ़ावा नहीं" #: spa/plugins/alsa/acp/alsa-mixer.c:2725 msgid "Amplifier" msgstr "एंप्लीफायर" #: spa/plugins/alsa/acp/alsa-mixer.c:2726 msgid "No Amplifier" msgstr "कोई एंप्लीफायर नहीं" #: spa/plugins/alsa/acp/alsa-mixer.c:2727 #, fuzzy msgid "Bass Boost" msgstr "बूस्ट" #: spa/plugins/alsa/acp/alsa-mixer.c:2728 #, fuzzy msgid "No Bass Boost" msgstr "कोई बढ़ावा नहीं" #: spa/plugins/alsa/acp/alsa-mixer.c:2729 #: spa/plugins/bluez5/bluez5-device.c:1150 msgid "Speaker" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2730 #: spa/plugins/alsa/acp/alsa-mixer.c:2808 msgid "Headphones" msgstr "एनालॉग हेडफोन" #: spa/plugins/alsa/acp/alsa-mixer.c:2797 msgid "Analog Input" msgstr "एनालॉग इनपुट" #: spa/plugins/alsa/acp/alsa-mixer.c:2801 msgid "Dock Microphone" msgstr "डॉकिंग स्टेशन माइक्रोफोन" #: spa/plugins/alsa/acp/alsa-mixer.c:2803 #, fuzzy msgid "Headset Microphone" msgstr "माइक्रोफोन" #: spa/plugins/alsa/acp/alsa-mixer.c:2807 msgid "Analog Output" msgstr "एनालॉग आउटपुट" #: spa/plugins/alsa/acp/alsa-mixer.c:2809 #, fuzzy msgid "Headphones 2" msgstr "एनालॉग हेडफोन" #: spa/plugins/alsa/acp/alsa-mixer.c:2810 #, fuzzy msgid "Headphones Mono Output" msgstr "एनालॉग एकल आउटपुट" #: spa/plugins/alsa/acp/alsa-mixer.c:2811 #, fuzzy msgid "Line Out" msgstr "लाइन इन" #: spa/plugins/alsa/acp/alsa-mixer.c:2812 msgid "Analog Mono Output" msgstr "एनालॉग एकल आउटपुट" #: spa/plugins/alsa/acp/alsa-mixer.c:2813 #, fuzzy msgid "Speakers" msgstr "एनालॉग स्टीरियो" #: spa/plugins/alsa/acp/alsa-mixer.c:2814 msgid "HDMI / DisplayPort" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2815 #, fuzzy msgid "Digital Output (S/PDIF)" msgstr "डिजिटल सेटअप (HDMI)" #: spa/plugins/alsa/acp/alsa-mixer.c:2816 #, fuzzy msgid "Digital Input (S/PDIF)" msgstr "डिजिटल सेटअप (HDMI)" #: spa/plugins/alsa/acp/alsa-mixer.c:2817 msgid "Multichannel Input" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2818 #, fuzzy msgid "Multichannel Output" msgstr "रिक्त आउटपुट" #: spa/plugins/alsa/acp/alsa-mixer.c:2819 #, fuzzy msgid "Game Output" msgstr "रिक्त आउटपुट" #: spa/plugins/alsa/acp/alsa-mixer.c:2820 #: spa/plugins/alsa/acp/alsa-mixer.c:2821 #, fuzzy msgid "Chat Output" msgstr "रिक्त आउटपुट" #: spa/plugins/alsa/acp/alsa-mixer.c:2822 #, fuzzy msgid "Chat Input" msgstr "इनपुट" #: spa/plugins/alsa/acp/alsa-mixer.c:2823 #, fuzzy msgid "Virtual Surround 7.1" msgstr "एनालॉग सर्राउंड 7.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4527 msgid "Analog Mono" msgstr "एनालॉग मोनो" #: spa/plugins/alsa/acp/alsa-mixer.c:4528 #, fuzzy msgid "Analog Mono (Left)" msgstr "एनालॉग मोनो" #: spa/plugins/alsa/acp/alsa-mixer.c:4529 #, fuzzy msgid "Analog Mono (Right)" msgstr "एनालॉग मोनो" #. Note: Not translated to "Analog Stereo Input", because the source #. * name gets "Input" appended to it automatically, so adding "Input" #. * here would lead to the source name to become "Analog Stereo Input #. * Input". The same logic applies to analog-stereo-output, #. * multichannel-input and multichannel-output. #: spa/plugins/alsa/acp/alsa-mixer.c:4530 #: spa/plugins/alsa/acp/alsa-mixer.c:4538 #: spa/plugins/alsa/acp/alsa-mixer.c:4539 msgid "Analog Stereo" msgstr "एनालॉग स्टीरियो" #: spa/plugins/alsa/acp/alsa-mixer.c:4531 msgid "Mono" msgstr "मोनो" #: spa/plugins/alsa/acp/alsa-mixer.c:4532 msgid "Stereo" msgstr "स्टीरियो" #: spa/plugins/alsa/acp/alsa-mixer.c:4540 #: spa/plugins/alsa/acp/alsa-mixer.c:4698 #: spa/plugins/bluez5/bluez5-device.c:1135 msgid "Headset" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4541 #: spa/plugins/alsa/acp/alsa-mixer.c:4699 #, fuzzy msgid "Speakerphone" msgstr "एनालॉग स्टीरियो" #: spa/plugins/alsa/acp/alsa-mixer.c:4542 #: spa/plugins/alsa/acp/alsa-mixer.c:4543 msgid "Multichannel" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4544 msgid "Analog Surround 2.1" msgstr "एनालॉग सर्राउंड 2.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4545 msgid "Analog Surround 3.0" msgstr "एनालॉग सर्राउंड 3.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4546 msgid "Analog Surround 3.1" msgstr "एनालॉग सर्राउंड 3.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4547 msgid "Analog Surround 4.0" msgstr "एनालॉग सर्राउंड 4.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4548 msgid "Analog Surround 4.1" msgstr "एनालॉग सर्राउंड 4.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4549 msgid "Analog Surround 5.0" msgstr "एनालॉग सर्राउंड 5.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4550 msgid "Analog Surround 5.1" msgstr "एनालॉग सर्राउंड 5.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4551 msgid "Analog Surround 6.0" msgstr "एनालॉग सर्राउंड 6.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4552 msgid "Analog Surround 6.1" msgstr "एनालॉग सर्राउंड 6.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4553 msgid "Analog Surround 7.0" msgstr "एनालॉग सर्राउंड 7.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4554 msgid "Analog Surround 7.1" msgstr "एनालॉग सर्राउंड 7.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4555 msgid "Digital Stereo (IEC958)" msgstr "डिजिटल स्टीरियो (IEC958)" #: spa/plugins/alsa/acp/alsa-mixer.c:4556 msgid "Digital Surround 4.0 (IEC958/AC3)" msgstr "डिजिटल सर्राउंड 4.0 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4557 msgid "Digital Surround 5.1 (IEC958/AC3)" msgstr "डिजिटल सर्राउंड 5.1 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4558 #, fuzzy msgid "Digital Surround 5.1 (IEC958/DTS)" msgstr "डिजिटल सर्राउंड 5.1 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4559 msgid "Digital Stereo (HDMI)" msgstr "डिजिटल सेटअप (HDMI)" #: spa/plugins/alsa/acp/alsa-mixer.c:4560 #, fuzzy msgid "Digital Surround 5.1 (HDMI)" msgstr "डिजिटल सर्राउंड 5.1 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4561 msgid "Chat" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4562 msgid "Game" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4696 msgid "Analog Mono Duplex" msgstr "एनालॉग एकल डुप्लेक्स" #: spa/plugins/alsa/acp/alsa-mixer.c:4697 msgid "Analog Stereo Duplex" msgstr "एनालॉग स्टीरियो डुप्लेक्स" #: spa/plugins/alsa/acp/alsa-mixer.c:4700 msgid "Digital Stereo Duplex (IEC958)" msgstr "डिजिटल स्टीरियो डुप्लेक्स (IEC958)" #: spa/plugins/alsa/acp/alsa-mixer.c:4701 msgid "Multichannel Duplex" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4702 #, fuzzy msgid "Stereo Duplex" msgstr "एनालॉग स्टीरियो डुप्लेक्स" #: spa/plugins/alsa/acp/alsa-mixer.c:4703 msgid "Mono Chat + 7.1 Surround" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4806 #, fuzzy, c-format msgid "%s Output" msgstr "रिक्त आउटपुट" #: spa/plugins/alsa/acp/alsa-mixer.c:4813 #, fuzzy, c-format msgid "%s Input" msgstr "इनपुट" #: spa/plugins/alsa/acp/alsa-util.c:1175 spa/plugins/alsa/acp/alsa-util.c:1269 #, fuzzy, c-format msgid "" "snd_pcm_avail() returned a value that is exceptionally large: %lu byte (%lu " "ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_avail() returned a value that is exceptionally large: %lu bytes (%lu " "ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "snd_pcm_avail() ने एक मान दिया जो अप्रत्याशित रूप से बड़ा है: %lu बाइट (%lu ms).\n" "अधिक संभव है कि यह ALSA ड्राइवर '%s' में एक बग है. इस मुद्दे को ALSA डेवलेपर को रिपोर्ट " "करें." msgstr[1] "" "snd_pcm_avail() ने एक मान दिया जो अप्रत्याशित रूप से बड़ा है: %lu बाइट (%lu ms).\n" "अधिक संभव है कि यह ALSA ड्राइवर '%s' में एक बग है. इस मुद्दे को ALSA डेवलेपर को रिपोर्ट " "करें." #: spa/plugins/alsa/acp/alsa-util.c:1241 #, fuzzy, c-format msgid "" "snd_pcm_delay() returned a value that is exceptionally large: %li byte (%s" "%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_delay() returned a value that is exceptionally large: %li bytes (%s" "%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "snd_pcm_delay() ने एक मान दिया जो अप्रत्याशित रूप से बड़ा है: %li बाइट (%s%lu ms).\n" "अधिक संभव है कि यह ALSA ड्राइवर '%s' में एक बग है. इस मुद्दे को ALSA डेवलेपर को रिपोर्ट " "करें." msgstr[1] "" "snd_pcm_delay() ने एक मान दिया जो अप्रत्याशित रूप से बड़ा है: %li बाइट (%s%lu ms).\n" "अधिक संभव है कि यह ALSA ड्राइवर '%s' में एक बग है. इस मुद्दे को ALSA डेवलेपर को रिपोर्ट " "करें." #: spa/plugins/alsa/acp/alsa-util.c:1288 #, fuzzy, c-format msgid "" "snd_pcm_avail_delay() returned strange values: delay %lu is less than avail " "%lu.\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr "" "snd_pcm_avail() ने एक मान दिया जो अप्रत्याशित रूप से बड़ा है: %lu बाइट (%lu ms).\n" "अधिक संभव है कि यह ALSA ड्राइवर '%s' में एक बग है. इस मुद्दे को ALSA डेवलेपर को रिपोर्ट " "करें." #: spa/plugins/alsa/acp/alsa-util.c:1331 #, fuzzy, c-format msgid "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu byte " "(%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu bytes " "(%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "snd_pcm_mmap_begin() ने एक मान दिया जो अप्रत्याशित रूप से बड़ा है: %lu बाइट (%lu " "ms).\n" "अधिक संभव है कि यह ALSA ड्राइवर '%s' में एक बग है. इस मुद्दे को ALSA डेवलेपर को रिपोर्ट " "करें." msgstr[1] "" "snd_pcm_mmap_begin() ने एक मान दिया जो अप्रत्याशित रूप से बड़ा है: %lu बाइट (%lu " "ms).\n" "अधिक संभव है कि यह ALSA ड्राइवर '%s' में एक बग है. इस मुद्दे को ALSA डेवलेपर को रिपोर्ट " "करें." #: spa/plugins/bluez5/bluez5-device.c:1010 msgid "Audio Gateway (A2DP Source & HSP/HFP AG)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1033 #, c-format msgid "High Fidelity Playback (A2DP Sink, codec %s)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1035 #, c-format msgid "High Fidelity Duplex (A2DP Source/Sink, codec %s)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1041 msgid "High Fidelity Playback (A2DP Sink)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1043 msgid "High Fidelity Duplex (A2DP Source/Sink)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1070 #, c-format msgid "Headset Head Unit (HSP/HFP, codec %s)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1074 msgid "Headset Head Unit (HSP/HFP)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1140 msgid "Handsfree" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1155 #, fuzzy msgid "Headphone" msgstr "एनालॉग हेडफोन" #: spa/plugins/bluez5/bluez5-device.c:1160 msgid "Portable" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1165 msgid "Car" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1170 msgid "HiFi" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1175 msgid "Phone" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1181 msgid "Bluetooth" msgstr "" pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/po/hr.po000066400000000000000000000537051511204443500220770ustar00rootroot00000000000000# Croatian translation for pipewire # Copyright (c) 2010 Rosetta Contributors and Canonical Ltd 2010 # This file is distributed under the same license as the pipewire package. # gogo , 2017. # msgid "" msgstr "" "Project-Id-Version: pipewire\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-10-01 14:01+0200\n" "PO-Revision-Date: 2022-10-01 14:12+0200\n" "Last-Translator: gogo \n" "Language-Team: Croatian \n" "Language: hr\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && " "n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" "X-Generator: Poedit 3.0.1\n" "X-Launchpad-Export-Date: 2017-04-20 21:04+0000\n" #: src/daemon/pipewire.c:46 #, c-format msgid "" "%s [options]\n" " -h, --help Show this help\n" " --version Show version\n" " -c, --config Load config (Default %s)\n" msgstr "" "%s [mogućnosti]\n" " -h, --help Prikaži ovu pomoć\n" " --version Prikaži inačicu\n" " -c, --config Učitaj podešavanje (Zadano %s)\n" #: src/modules/module-protocol-pulse/modules/module-tunnel-sink.c:180 #: src/modules/module-protocol-pulse/modules/module-tunnel-source.c:180 #, c-format msgid "Tunnel to %s/%s" msgstr "Tunel do %s/%s" #: src/modules/module-fallback-sink.c:51 msgid "Dummy Output" msgstr "Lažni izlaz" #: src/modules/module-pulse-tunnel.c:662 #, c-format msgid "Tunnel for %s@%s" msgstr "Tunel za %s@%s" #: src/modules/module-zeroconf-discover.c:332 msgid "Unknown device" msgstr "Nepoznat uređaj" #: src/modules/module-zeroconf-discover.c:344 #, c-format msgid "%s on %s@%s" msgstr "%s na %s@%s" #: src/modules/module-zeroconf-discover.c:348 #, c-format msgid "%s on %s" msgstr "%s na %s" #: src/tools/pw-cat.c:784 #, c-format msgid "" "%s [options] [|-]\n" " -h, --help Show this help\n" " --version Show version\n" " -v, --verbose Enable verbose operations\n" "\n" msgstr "" "%s [mogućnosti] [|-]\n" " -h, --help Prikaži ovu pomoć\n" " --version Prikaži inačicu\n" " -v, --verbose Omogući opširnije radnje\n" "\n" #: src/tools/pw-cat.c:791 #, c-format msgid "" " -R, --remote Remote daemon name\n" " --media-type Set media type (default %s)\n" " --media-category Set media category (default %s)\n" " --media-role Set media role (default %s)\n" " --target Set node target (default %s)\n" " 0 means don't link\n" " --latency Set node latency (default %s)\n" " Xunit (unit = s, ms, us, ns)\n" " or direct samples (256)\n" " the rate is the one of the source " "file\n" " -P --properties Set node properties\n" "\n" msgstr "" " -R, --remote Naziv udaljenog pozadinskog " "programa\n" " --media-type Postavi vrstu medija (zadano je %s)\n" " --media-category Postavi kategoriju medija (zadano je " "%s)\n" " --media-role Postavi namjenu medija (zadano je " "%s)\n" " --target Postavi odredište čvora (zadano je " "%s)\n" " 0 znači bez povezivanja\n" " --latency Postavi latenciju čvora (zadano je " "%s)\n" " Xunit (jedinica = s, ms, us, ns)\n" " ili izravne uzorke (256)\n" " frekvencija je jednaka izvornoj " "datoteci\n" " -P --properties Postavi svojstva čvora\n" "\n" #: src/tools/pw-cat.c:809 #, c-format msgid "" " --rate Sample rate (req. for rec) (default " "%u)\n" " --channels Number of channels (req. for rec) " "(default %u)\n" " --channel-map Channel map\n" " one of: \"stereo\", " "\"surround-51\",... or\n" " comma separated list of channel " "names: eg. \"FL,FR\"\n" " --format Sample format %s (req. for rec) " "(default %s)\n" " --volume Stream volume 0-1.0 (default %.3f)\n" " -q --quality Resampler quality (0 - 15) (default " "%d)\n" "\n" msgstr "" " --rate Frekvencija (potrebno za snimanje) " "(zadano je %u)\n" " --channels Broj kanala (potrebno za snimanje) " "(zadano je %u)\n" " --channel-map Broj kanala\n" " jedan od: \"stereo\", " "\"surround-51\",... ili\n" " zarezom odvojen popis naziva " "kanala: npr. \"FL,FR\"\n" " --format Format %s (potrebno za snimanje) " "(zadano je %s)\n" " --volume Glasnoća zvuka strujanja 0-1.0 " "(zadano je %.3f)\n" " -q --quality Kvaliteta normalizacije zvuka (0 - " "15) (zadano je %d)\n" "\n" #: src/tools/pw-cat.c:826 msgid "" " -p, --playback Playback mode\n" " -r, --record Recording mode\n" " -m, --midi Midi mode\n" " -d, --dsd DSD mode\n" "\n" msgstr "" " -p, --playback Način reprodukcije\n" " -r, --record Način snimanja\n" " -m, --midi Midi način\n" " -d, --dsd DSD način\n" "\n" #: src/tools/pw-cli.c:2250 #, c-format msgid "" "%s [options] [command]\n" " -h, --help Show this help\n" " --version Show version\n" " -d, --daemon Start as daemon (Default false)\n" " -r, --remote Remote daemon name\n" "\n" msgstr "" "%s [mogućnosti] [naredba]\n" " -h, --help Prikaži ovu pomoć\n" " --version Prikaži inačicu\n" " -d, --daemon Pokreni kao pozadinski program " "(Zadano je laž)\n" " -r, --remote Naziv udaljenog pozadinskog " "programa\n" "\n" #: spa/plugins/alsa/acp/acp.c:321 msgid "Pro Audio" msgstr "Pro Audio" #: spa/plugins/alsa/acp/acp.c:444 spa/plugins/alsa/acp/alsa-mixer.c:4648 #: spa/plugins/bluez5/bluez5-device.c:1236 msgid "Off" msgstr "Isključeno" #: spa/plugins/alsa/acp/alsa-mixer.c:2652 msgid "Input" msgstr "Ulaz" #: spa/plugins/alsa/acp/alsa-mixer.c:2653 msgid "Docking Station Input" msgstr "Ulaz priključne stanice" #: spa/plugins/alsa/acp/alsa-mixer.c:2654 msgid "Docking Station Microphone" msgstr "Mikrofon priključne stanice" #: spa/plugins/alsa/acp/alsa-mixer.c:2655 msgid "Docking Station Line In" msgstr "Ulaz priključne stanice" #: spa/plugins/alsa/acp/alsa-mixer.c:2656 #: spa/plugins/alsa/acp/alsa-mixer.c:2747 msgid "Line In" msgstr "Ulaz" #: spa/plugins/alsa/acp/alsa-mixer.c:2657 #: spa/plugins/alsa/acp/alsa-mixer.c:2741 #: spa/plugins/bluez5/bluez5-device.c:1454 msgid "Microphone" msgstr "Mikrofon" #: spa/plugins/alsa/acp/alsa-mixer.c:2658 #: spa/plugins/alsa/acp/alsa-mixer.c:2742 msgid "Front Microphone" msgstr "Prednji mikrofon" #: spa/plugins/alsa/acp/alsa-mixer.c:2659 #: spa/plugins/alsa/acp/alsa-mixer.c:2743 msgid "Rear Microphone" msgstr "Stražnji mikrofon" #: spa/plugins/alsa/acp/alsa-mixer.c:2660 msgid "External Microphone" msgstr "Vanjski mikrofon" #: spa/plugins/alsa/acp/alsa-mixer.c:2661 #: spa/plugins/alsa/acp/alsa-mixer.c:2745 msgid "Internal Microphone" msgstr "Unutarnji mikrofon" #: spa/plugins/alsa/acp/alsa-mixer.c:2662 #: spa/plugins/alsa/acp/alsa-mixer.c:2748 msgid "Radio" msgstr "Radio" #: spa/plugins/alsa/acp/alsa-mixer.c:2663 #: spa/plugins/alsa/acp/alsa-mixer.c:2749 msgid "Video" msgstr "Video" #: spa/plugins/alsa/acp/alsa-mixer.c:2664 msgid "Automatic Gain Control" msgstr "Automatska kontrola pojačanja" #: spa/plugins/alsa/acp/alsa-mixer.c:2665 msgid "No Automatic Gain Control" msgstr "Bez automatske kontrole pojačanja" #: spa/plugins/alsa/acp/alsa-mixer.c:2666 msgid "Boost" msgstr "Pojačanje" #: spa/plugins/alsa/acp/alsa-mixer.c:2667 msgid "No Boost" msgstr "Bez pojačanja" #: spa/plugins/alsa/acp/alsa-mixer.c:2668 msgid "Amplifier" msgstr "Pojačalo" #: spa/plugins/alsa/acp/alsa-mixer.c:2669 msgid "No Amplifier" msgstr "Bez pojačala" #: spa/plugins/alsa/acp/alsa-mixer.c:2670 msgid "Bass Boost" msgstr "Pojačanje basa" #: spa/plugins/alsa/acp/alsa-mixer.c:2671 msgid "No Bass Boost" msgstr "Bez pojačanja basa" #: spa/plugins/alsa/acp/alsa-mixer.c:2672 #: spa/plugins/bluez5/bluez5-device.c:1460 msgid "Speaker" msgstr "Zvučnik" #: spa/plugins/alsa/acp/alsa-mixer.c:2673 #: spa/plugins/alsa/acp/alsa-mixer.c:2751 msgid "Headphones" msgstr "Slušalice" #: spa/plugins/alsa/acp/alsa-mixer.c:2740 msgid "Analog Input" msgstr "Analogni ulaz" #: spa/plugins/alsa/acp/alsa-mixer.c:2744 msgid "Dock Microphone" msgstr "Ugrađeni mikrofon" #: spa/plugins/alsa/acp/alsa-mixer.c:2746 msgid "Headset Microphone" msgstr "Mikrofon sa slušalicama" #: spa/plugins/alsa/acp/alsa-mixer.c:2750 msgid "Analog Output" msgstr "Analogni izlaz" #: spa/plugins/alsa/acp/alsa-mixer.c:2752 msgid "Headphones 2" msgstr "Slušalice 2" #: spa/plugins/alsa/acp/alsa-mixer.c:2753 msgid "Headphones Mono Output" msgstr "Mono izlaz za slušalice" #: spa/plugins/alsa/acp/alsa-mixer.c:2754 msgid "Line Out" msgstr "Izlaz" #: spa/plugins/alsa/acp/alsa-mixer.c:2755 msgid "Analog Mono Output" msgstr "Analogni mono izlaz" #: spa/plugins/alsa/acp/alsa-mixer.c:2756 msgid "Speakers" msgstr "Zvučnici" #: spa/plugins/alsa/acp/alsa-mixer.c:2757 msgid "HDMI / DisplayPort" msgstr "HDMI / DisplayPort" #: spa/plugins/alsa/acp/alsa-mixer.c:2758 msgid "Digital Output (S/PDIF)" msgstr "Digitalni izlaz (S/PDIF)" #: spa/plugins/alsa/acp/alsa-mixer.c:2759 msgid "Digital Input (S/PDIF)" msgstr "Digitalni ulaz (S/PDIF)" #: spa/plugins/alsa/acp/alsa-mixer.c:2760 msgid "Multichannel Input" msgstr "Višekanalni ulaz" #: spa/plugins/alsa/acp/alsa-mixer.c:2761 msgid "Multichannel Output" msgstr "Višekanalni izlaz" #: spa/plugins/alsa/acp/alsa-mixer.c:2762 msgid "Game Output" msgstr "Izlaz za igre" #: spa/plugins/alsa/acp/alsa-mixer.c:2763 #: spa/plugins/alsa/acp/alsa-mixer.c:2764 msgid "Chat Output" msgstr "Izlaz razgovora" #: spa/plugins/alsa/acp/alsa-mixer.c:2765 msgid "Chat Input" msgstr "Ulaz razgovora" #: spa/plugins/alsa/acp/alsa-mixer.c:2766 msgid "Virtual Surround 7.1" msgstr "Virtalni surround 7.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4471 msgid "Analog Mono" msgstr "Analogni mono" #: spa/plugins/alsa/acp/alsa-mixer.c:4472 msgid "Analog Mono (Left)" msgstr "Analogni mono (lijevi)" #: spa/plugins/alsa/acp/alsa-mixer.c:4473 msgid "Analog Mono (Right)" msgstr "Analogni mono (desni)" #. Note: Not translated to "Analog Stereo Input", because the source #. * name gets "Input" appended to it automatically, so adding "Input" #. * here would lead to the source name to become "Analog Stereo Input #. * Input". The same logic applies to analog-stereo-output, #. * multichannel-input and multichannel-output. #: spa/plugins/alsa/acp/alsa-mixer.c:4474 #: spa/plugins/alsa/acp/alsa-mixer.c:4482 #: spa/plugins/alsa/acp/alsa-mixer.c:4483 msgid "Analog Stereo" msgstr "Analogni stereo" #: spa/plugins/alsa/acp/alsa-mixer.c:4475 msgid "Mono" msgstr "Mono" #: spa/plugins/alsa/acp/alsa-mixer.c:4476 msgid "Stereo" msgstr "Stereo" #: spa/plugins/alsa/acp/alsa-mixer.c:4484 #: spa/plugins/alsa/acp/alsa-mixer.c:4642 #: spa/plugins/bluez5/bluez5-device.c:1442 msgid "Headset" msgstr "Slušalice s mikrofonom" #: spa/plugins/alsa/acp/alsa-mixer.c:4485 #: spa/plugins/alsa/acp/alsa-mixer.c:4643 msgid "Speakerphone" msgstr "Zvučnik" #: spa/plugins/alsa/acp/alsa-mixer.c:4486 #: spa/plugins/alsa/acp/alsa-mixer.c:4487 msgid "Multichannel" msgstr "Višekanalni" #: spa/plugins/alsa/acp/alsa-mixer.c:4488 msgid "Analog Surround 2.1" msgstr "Analogni surround 2.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4489 msgid "Analog Surround 3.0" msgstr "Analogni surround 3.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4490 msgid "Analog Surround 3.1" msgstr "Analogni surround 3.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4491 msgid "Analog Surround 4.0" msgstr "Analogni surround 4.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4492 msgid "Analog Surround 4.1" msgstr "Analogni surround 4.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4493 msgid "Analog Surround 5.0" msgstr "Analogni surround 5.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4494 msgid "Analog Surround 5.1" msgstr "Analogni surround 5.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4495 msgid "Analog Surround 6.0" msgstr "Analogni surround 6.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4496 msgid "Analog Surround 6.1" msgstr "Analogni surround 6.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4497 msgid "Analog Surround 7.0" msgstr "Analogni surround 7.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4498 msgid "Analog Surround 7.1" msgstr "Analogni surround 7.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4499 msgid "Digital Stereo (IEC958)" msgstr "Digitalni stereo (IEC958)" #: spa/plugins/alsa/acp/alsa-mixer.c:4500 msgid "Digital Surround 4.0 (IEC958/AC3)" msgstr "Digitalni surround 4.0 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4501 msgid "Digital Surround 5.1 (IEC958/AC3)" msgstr "Digitalni surround 5.1 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4502 msgid "Digital Surround 5.1 (IEC958/DTS)" msgstr "Digitalni surround 5.1 (IEC958/DTS)" #: spa/plugins/alsa/acp/alsa-mixer.c:4503 msgid "Digital Stereo (HDMI)" msgstr "Digitalni stereo (HDMI)" #: spa/plugins/alsa/acp/alsa-mixer.c:4504 msgid "Digital Surround 5.1 (HDMI)" msgstr "Digitalni surround 5.1 (HDMI)" #: spa/plugins/alsa/acp/alsa-mixer.c:4505 msgid "Chat" msgstr "Razgovor" #: spa/plugins/alsa/acp/alsa-mixer.c:4506 msgid "Game" msgstr "Igra" #: spa/plugins/alsa/acp/alsa-mixer.c:4640 msgid "Analog Mono Duplex" msgstr "Analogni mono obostrani" #: spa/plugins/alsa/acp/alsa-mixer.c:4641 msgid "Analog Stereo Duplex" msgstr "Analogni stereo obostrani" #: spa/plugins/alsa/acp/alsa-mixer.c:4644 msgid "Digital Stereo Duplex (IEC958)" msgstr "Digitalni stereo obostrani (IEC958)" #: spa/plugins/alsa/acp/alsa-mixer.c:4645 msgid "Multichannel Duplex" msgstr "Višekanalni obostrani" #: spa/plugins/alsa/acp/alsa-mixer.c:4646 msgid "Stereo Duplex" msgstr "Stereo obostrani" #: spa/plugins/alsa/acp/alsa-mixer.c:4647 msgid "Mono Chat + 7.1 Surround" msgstr "Mono razgovor + 7.1 Surround" #: spa/plugins/alsa/acp/alsa-mixer.c:4754 #, c-format msgid "%s Output" msgstr "%s izlaz" #: spa/plugins/alsa/acp/alsa-mixer.c:4761 #, c-format msgid "%s Input" msgstr "%s ulaz" #: spa/plugins/alsa/acp/alsa-util.c:1187 #: spa/plugins/alsa/acp/alsa-util.c:1281 #, c-format msgid "" "snd_pcm_avail() returned a value that is exceptionally large: %lu byte (%lu " "ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_avail() returned a value that is exceptionally large: %lu bytes (%lu " "ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "snd_pcm_avail() je vratio vrijednost koja je iznimno velika: %lu bajt (%lu " "ms).\n" "Najvjerojatnije je ovo greška ALSA upravljačkog programa '%s'. Prijavite " "problem ALSA razvijateljima." msgstr[1] "" "snd_pcm_avail() je vratio vrijednost koja je iznimno velika: %lu bajta (%lu " "ms).\n" "Najvjerojatnije je ovo greška ALSA upravljačkog programa '%s'. Prijavite " "problem ALSA razvijateljima." msgstr[2] "" "snd_pcm_avail() je vratio vrijednost koja je iznimno velika: %lu bajta (%lu " "ms).\n" "Najvjerojatnije je ovo greška ALSA upravljačkog programa '%s'. Prijavite " "problem ALSA razvijateljima." #: spa/plugins/alsa/acp/alsa-util.c:1253 #, c-format msgid "" "snd_pcm_delay() returned a value that is exceptionally large: %li byte " "(%s%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_delay() returned a value that is exceptionally large: %li bytes " "(%s%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "snd_pcm_delay() je vratio vrijednost koja je iznimno velika: %li bajt (%s%lu " "ms).\n" "Najvjerojatnije je ovo greška ALSA upravljačkog programa '%s'. Prijavite " "problem ALSA razvijateljima." msgstr[1] "" "snd_pcm_delay() je vratio vrijednost koja je iznimno velika: %li bajta " "(%s%lu ms).\n" "Najvjerojatnije je ovo greška ALSA upravljačkog programa '%s'. Prijavite " "problem ALSA razvijateljima." msgstr[2] "" "snd_pcm_delay() je vratio vrijednost koja je iznimno velika: %li bajta " "(%s%lu ms).\n" "Najvjerojatnije je ovo greška ALSA upravljačkog programa '%s'. Prijavite " "problem ALSA razvijateljima." #: spa/plugins/alsa/acp/alsa-util.c:1300 #, c-format msgid "" "snd_pcm_avail_delay() returned strange values: delay %lu is less than avail " "%lu.\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr "" "snd_pcm_avail_delay() je vratio nepoznate vrijednosti: kašnjenje %lu je " "manje od %lu.\n" "Najvjerojatnije je ovo greška ALSA upravljačkog programa '%s'. Prijavite " "problem ALSA razvijateljima." #: spa/plugins/alsa/acp/alsa-util.c:1343 #, c-format msgid "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu byte " "(%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu bytes " "(%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "snd_pcm_mmap_begin() je vratio vrijednost koja je iznimno velika: %lu bajt " "(%lu ms).\n" "Najvjerojatnije je ovo greška ALSA upravljačkog programa '%s'. Prijavite " "problem ALSA razvijateljima." msgstr[1] "" "snd_pcm_mmap_begin() je vratio vrijednost koja je iznimno velika: %lu bajta " "(%lu ms).\n" "Najvjerojatnije je ovo greška ALSA upravljačkog programa '%s'. Prijavite " "problem ALSA razvijateljima." msgstr[2] "" "snd_pcm_mmap_begin() je vratio vrijednost koja je iznimno velika: %lu bajta " "(%lu ms).\n" "Najvjerojatnije je ovo greška ALSA upravljačkog programa '%s'. Prijavite " "problem ALSA razvijateljima." #: spa/plugins/alsa/acp/channelmap.h:457 msgid "(invalid)" msgstr "(neispravno)" #: spa/plugins/alsa/acp/compat.c:189 msgid "Built-in Audio" msgstr "Ugrađeni zvuk" #: spa/plugins/alsa/acp/compat.c:194 msgid "Modem" msgstr "Modem" #: spa/plugins/bluez5/bluez5-device.c:1247 msgid "Audio Gateway (A2DP Source & HSP/HFP AG)" msgstr "Zvučni pristupnik (A2DP izvor i HSP/HFP AG)" #: spa/plugins/bluez5/bluez5-device.c:1272 #, c-format msgid "High Fidelity Playback (A2DP Sink, codec %s)" msgstr "Reprodukcija visoke autentičnosti (A2DP slivnik, kôdek %s)" #: spa/plugins/bluez5/bluez5-device.c:1275 #, c-format msgid "High Fidelity Duplex (A2DP Source/Sink, codec %s)" msgstr "Telefonija visoke autentičnosti (A2DP slivnik, kôdek %s)" #: spa/plugins/bluez5/bluez5-device.c:1283 msgid "High Fidelity Playback (A2DP Sink)" msgstr "Reprodukcija visoke autentičnosti (A2DP slivnik)" #: spa/plugins/bluez5/bluez5-device.c:1285 msgid "High Fidelity Duplex (A2DP Source/Sink)" msgstr "Telefonija visoke autentičnosti (A2DP izvor/slivnik)" #: spa/plugins/bluez5/bluez5-device.c:1322 #, c-format msgid "High Fidelity Playback (BAP Sink, codec %s)" msgstr "Reprodukcija visoke autentičnosti (BAP slivnik, kôdek %s)" #: spa/plugins/bluez5/bluez5-device.c:1326 #, c-format msgid "High Fidelity Input (BAP Source, codec %s)" msgstr "Ulaz visoke autentičnosti (BAP izvor, kôdek %s)" #: spa/plugins/bluez5/bluez5-device.c:1330 #, c-format msgid "High Fidelity Duplex (BAP Source/Sink, codec %s)" msgstr "Telefonija visoke autentičnosti (BAP izvor/slivnik, kôdek %s)" #: spa/plugins/bluez5/bluez5-device.c:1359 #, c-format msgid "Headset Head Unit (HSP/HFP, codec %s)" msgstr "Jedinica slušalice s mikrofonom (HSP/HFP, kôdek %s)" #: spa/plugins/bluez5/bluez5-device.c:1364 msgid "Headset Head Unit (HSP/HFP)" msgstr "Jedinica slušalice s mikrofonom (HSP/HFP)" #: spa/plugins/bluez5/bluez5-device.c:1443 #: spa/plugins/bluez5/bluez5-device.c:1448 #: spa/plugins/bluez5/bluez5-device.c:1455 #: spa/plugins/bluez5/bluez5-device.c:1461 #: spa/plugins/bluez5/bluez5-device.c:1467 #: spa/plugins/bluez5/bluez5-device.c:1473 #: spa/plugins/bluez5/bluez5-device.c:1479 #: spa/plugins/bluez5/bluez5-device.c:1485 #: spa/plugins/bluez5/bluez5-device.c:1491 msgid "Handsfree" msgstr "Bez-ruku" #: spa/plugins/bluez5/bluez5-device.c:1449 msgid "Handsfree (HFP)" msgstr "Bez-ruku (HFP)" #: spa/plugins/bluez5/bluez5-device.c:1466 msgid "Headphone" msgstr "Slušalica" #: spa/plugins/bluez5/bluez5-device.c:1472 msgid "Portable" msgstr "Prijenosnik" #: spa/plugins/bluez5/bluez5-device.c:1478 msgid "Car" msgstr "Automobil" #: spa/plugins/bluez5/bluez5-device.c:1484 msgid "HiFi" msgstr "HiFi" #: spa/plugins/bluez5/bluez5-device.c:1490 msgid "Phone" msgstr "Telefon" #: spa/plugins/bluez5/bluez5-device.c:1497 msgid "Bluetooth" msgstr "Bluetooth" #: spa/plugins/bluez5/bluez5-device.c:1498 msgid "Bluetooth (HFP)" msgstr "Bluetooth (HFP)" #~ msgid "PipeWire Media System" #~ msgstr "PipeWire medijski sustav" #~ msgid "Start the PipeWire Media System" #~ msgstr "Pokreni PipeWire medijski sustav" pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/po/hu.po000066400000000000000000000550501511204443500220750ustar00rootroot00000000000000# Hungarian translation for PipeWire. # Copyright (C) 2012, 2016, 2022. Free Software Foundation, Inc. # This file is distributed under the same license as the PipeWire package. # # KAMI , 2012. # Gabor Kelemen , 2016. # Balázs Úr , 2016, 2022. msgid "" msgstr "" "Project-Id-Version: PipeWire master\n" "Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/pipewire/-/" "issues\n" "POT-Creation-Date: 2022-09-15 15:26+0000\n" "PO-Revision-Date: 2022-09-21 22:35+0200\n" "Last-Translator: Balázs Úr \n" "Language-Team: Hungarian \n" "Language: hu\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" "X-Generator: Lokalize 19.12.3\n" #: src/daemon/pipewire.c:46 #, c-format msgid "" "%s [options]\n" " -h, --help Show this help\n" " --version Show version\n" " -c, --config Load config (Default %s)\n" msgstr "" "%s [kapcsolók]\n" " -h, --help Ezen súgó megjelenítése\n" " --version Verzió megjelenítése\n" " -c, --config Beállítás betöltése (alapérték: %s)\n" #: src/daemon/pipewire.desktop.in:4 msgid "PipeWire Media System" msgstr "PipeWire médiarendszer" #: src/daemon/pipewire.desktop.in:5 msgid "Start the PipeWire Media System" msgstr "A PipeWire médiarendszer indítása" #: src/modules/module-protocol-pulse/modules/module-tunnel-sink.c:180 #: src/modules/module-protocol-pulse/modules/module-tunnel-source.c:180 #, c-format msgid "Tunnel to %s/%s" msgstr "Alagút ide: %s/%s" #: src/modules/module-fallback-sink.c:51 msgid "Dummy Output" msgstr "Üres kimenet" #: src/modules/module-pulse-tunnel.c:662 #, c-format msgid "Tunnel for %s@%s" msgstr "Alagút ehhez: %s@%s" #: src/modules/module-zeroconf-discover.c:332 msgid "Unknown device" msgstr "Ismeretlen eszköz" #: src/modules/module-zeroconf-discover.c:344 #, c-format msgid "%s on %s@%s" msgstr "%s ezen: %s@%s" #: src/modules/module-zeroconf-discover.c:348 #, c-format msgid "%s on %s" msgstr "%s ezen: %s" #: src/tools/pw-cat.c:784 #, c-format msgid "" "%s [options] [|-]\n" " -h, --help Show this help\n" " --version Show version\n" " -v, --verbose Enable verbose operations\n" "\n" msgstr "" "%s [kapcsolók] [|-]\n" " -h, --help Ezen súgó megjelenítése\n" " --version Verzió megjelenítése\n" " -v, --verbose Részletes műveletek engedélyezése\n" "\n" #: src/tools/pw-cat.c:791 #, c-format msgid "" " -R, --remote Remote daemon name\n" " --media-type Set media type (default %s)\n" " --media-category Set media category (default %s)\n" " --media-role Set media role (default %s)\n" " --target Set node target (default %s)\n" " 0 means don't link\n" " --latency Set node latency (default %s)\n" " Xunit (unit = s, ms, us, ns)\n" " or direct samples (256)\n" " the rate is the one of the source " "file\n" " -P --properties Set node properties\n" "\n" msgstr "" " -R, --remote Távoli démon neve\n" " --media-type Médiatípus beállítása (alapérték: " "%s)\n" " --media-category Médiakategória beállítása\n" " (alapérték: %s)\n" " --media-role Médiaszerep beállítása (alapérték: " "%s)\n" " --target Csomópont céljának beállítása\n" " (alapérték: %s), a 0 azt jelenti,\n" " hogy ne linkeljen\n" " --latency Csomópont késleltetésének " "beállítása\n" " (alapérték: %s)\n" " Xegység (egység = s, ms, us, ns)\n" " vagy közvetlen minták (256)\n" " a gyakoriság a forrásfájl egyike\n" " -P --properties Csomópont tulajdonságainak " "beállítása\n" "\n" #: src/tools/pw-cat.c:809 #, c-format msgid "" " --rate Sample rate (req. for rec) (default " "%u)\n" " --channels Number of channels (req. for rec) " "(default %u)\n" " --channel-map Channel map\n" " one of: \"stereo\", " "\"surround-51\",... or\n" " comma separated list of channel " "names: eg. \"FL,FR\"\n" " --format Sample format %s (req. for rec) " "(default %s)\n" " --volume Stream volume 0-1.0 (default %.3f)\n" " -q --quality Resampler quality (0 - 15) (default " "%d)\n" "\n" msgstr "" " --rate Mintavételi gyakoriság (kötelező a\n" " rögzítéshez) (alapérték: %u)\n" " --channels Csatornák száma (kötelező a\n" " rögzítéshez) (alapérték: %u)\n" " --channel-map Csatornaleképezés\n" " ezek egyike: „stereo”, " "„surround-51”\n" " stb. vagy csatornanevek vesszővel\n" " tagolt listája, például: „FL,FR”\n" " --format Mintavételi formátum: %s (kötelező " "a\n" " rögzítéshez) (alapérték: %s)\n" " --volume Adatfolyam hangereje 0-1.0\n" " (alapérték: %.3f)\n" " -q --quality Újramintavételezési minőség (0-15)\n" " (alapérték: %d)\n" "\n" #: src/tools/pw-cat.c:826 msgid "" " -p, --playback Playback mode\n" " -r, --record Recording mode\n" " -m, --midi Midi mode\n" " -d, --dsd DSD mode\n" "\n" msgstr "" " -p, --playback Lejátszási mód\n" " -r, --record Rögzítési mód\n" " -m, --midi Midi mód\n" " -d, --dsd DSD mód\n" "\n" #: src/tools/pw-cli.c:2255 #, c-format msgid "" "%s [options] [command]\n" " -h, --help Show this help\n" " --version Show version\n" " -d, --daemon Start as daemon (Default false)\n" " -r, --remote Remote daemon name\n" "\n" msgstr "" "%s [kapcsolók] [parancs]\n" " -h, --help Ezen súgó megjelenítése\n" " --version Verzió megjelenítése\n" " -d, --daemon Indítás démonként (alapérték: " "hamis)\n" " -r, --remote Távoli démon neve\n" "\n" #: spa/plugins/alsa/acp/acp.c:321 msgid "Pro Audio" msgstr "Pro Audio" #: spa/plugins/alsa/acp/acp.c:444 spa/plugins/alsa/acp/alsa-mixer.c:4648 #: spa/plugins/bluez5/bluez5-device.c:1236 msgid "Off" msgstr "Ki" #: spa/plugins/alsa/acp/alsa-mixer.c:2652 msgid "Input" msgstr "Bemenet" #: spa/plugins/alsa/acp/alsa-mixer.c:2653 msgid "Docking Station Input" msgstr "Dokkolóállomás bemenet" #: spa/plugins/alsa/acp/alsa-mixer.c:2654 msgid "Docking Station Microphone" msgstr "Dokkolóállomás mikrofon" #: spa/plugins/alsa/acp/alsa-mixer.c:2655 msgid "Docking Station Line In" msgstr "Dokkolóállomás vonalbemenet" #: spa/plugins/alsa/acp/alsa-mixer.c:2656 #: spa/plugins/alsa/acp/alsa-mixer.c:2747 msgid "Line In" msgstr "Vonalbemenet" #: spa/plugins/alsa/acp/alsa-mixer.c:2657 #: spa/plugins/alsa/acp/alsa-mixer.c:2741 #: spa/plugins/bluez5/bluez5-device.c:1454 msgid "Microphone" msgstr "Mikrofon" #: spa/plugins/alsa/acp/alsa-mixer.c:2658 #: spa/plugins/alsa/acp/alsa-mixer.c:2742 msgid "Front Microphone" msgstr "Elülső mikrofon" #: spa/plugins/alsa/acp/alsa-mixer.c:2659 #: spa/plugins/alsa/acp/alsa-mixer.c:2743 msgid "Rear Microphone" msgstr "Hátsó mikrofon" #: spa/plugins/alsa/acp/alsa-mixer.c:2660 msgid "External Microphone" msgstr "Külső mikrofon" #: spa/plugins/alsa/acp/alsa-mixer.c:2661 #: spa/plugins/alsa/acp/alsa-mixer.c:2745 msgid "Internal Microphone" msgstr "Belső mikrofon" #: spa/plugins/alsa/acp/alsa-mixer.c:2662 #: spa/plugins/alsa/acp/alsa-mixer.c:2748 msgid "Radio" msgstr "Rádió" #: spa/plugins/alsa/acp/alsa-mixer.c:2663 #: spa/plugins/alsa/acp/alsa-mixer.c:2749 msgid "Video" msgstr "Videó" #: spa/plugins/alsa/acp/alsa-mixer.c:2664 msgid "Automatic Gain Control" msgstr "Automatikus erősítésszabályzás" #: spa/plugins/alsa/acp/alsa-mixer.c:2665 msgid "No Automatic Gain Control" msgstr "Nincs automatikus erősítésszabályzás" #: spa/plugins/alsa/acp/alsa-mixer.c:2666 msgid "Boost" msgstr "Erősítés" #: spa/plugins/alsa/acp/alsa-mixer.c:2667 msgid "No Boost" msgstr "Nincs erősítés" #: spa/plugins/alsa/acp/alsa-mixer.c:2668 msgid "Amplifier" msgstr "Erősítő" #: spa/plugins/alsa/acp/alsa-mixer.c:2669 msgid "No Amplifier" msgstr "Nincs erősítő" #: spa/plugins/alsa/acp/alsa-mixer.c:2670 msgid "Bass Boost" msgstr "Basszuskiemelés" #: spa/plugins/alsa/acp/alsa-mixer.c:2671 msgid "No Bass Boost" msgstr "Nincs basszuskiemelés" #: spa/plugins/alsa/acp/alsa-mixer.c:2672 #: spa/plugins/bluez5/bluez5-device.c:1460 msgid "Speaker" msgstr "Hangszóró" #: spa/plugins/alsa/acp/alsa-mixer.c:2673 #: spa/plugins/alsa/acp/alsa-mixer.c:2751 msgid "Headphones" msgstr "Fejhallgató" #: spa/plugins/alsa/acp/alsa-mixer.c:2740 msgid "Analog Input" msgstr "Analóg bemenet" #: spa/plugins/alsa/acp/alsa-mixer.c:2744 msgid "Dock Microphone" msgstr "Dokkolóállomás mikrofon" #: spa/plugins/alsa/acp/alsa-mixer.c:2746 msgid "Headset Microphone" msgstr "Fejhallgató mikrofon" #: spa/plugins/alsa/acp/alsa-mixer.c:2750 msgid "Analog Output" msgstr "Analóg kimenet" #: spa/plugins/alsa/acp/alsa-mixer.c:2752 msgid "Headphones 2" msgstr "2. fejhallgató" #: spa/plugins/alsa/acp/alsa-mixer.c:2753 msgid "Headphones Mono Output" msgstr "Fejhallató monó kimenet" #: spa/plugins/alsa/acp/alsa-mixer.c:2754 msgid "Line Out" msgstr "Vonalkimenet" #: spa/plugins/alsa/acp/alsa-mixer.c:2755 msgid "Analog Mono Output" msgstr "Analóg monó kimenet" #: spa/plugins/alsa/acp/alsa-mixer.c:2756 msgid "Speakers" msgstr "Hangszórók" #: spa/plugins/alsa/acp/alsa-mixer.c:2757 msgid "HDMI / DisplayPort" msgstr "HDMI / DisplayPort" #: spa/plugins/alsa/acp/alsa-mixer.c:2758 msgid "Digital Output (S/PDIF)" msgstr "Digitális kimenet (S/PDIF)" #: spa/plugins/alsa/acp/alsa-mixer.c:2759 msgid "Digital Input (S/PDIF)" msgstr "Digitális bemenet (S/PDIF)" #: spa/plugins/alsa/acp/alsa-mixer.c:2760 msgid "Multichannel Input" msgstr "Többcsatornás bemenet" #: spa/plugins/alsa/acp/alsa-mixer.c:2761 msgid "Multichannel Output" msgstr "Többcsatornás kimenet" #: spa/plugins/alsa/acp/alsa-mixer.c:2762 msgid "Game Output" msgstr "Játék kimenet" #: spa/plugins/alsa/acp/alsa-mixer.c:2763 #: spa/plugins/alsa/acp/alsa-mixer.c:2764 msgid "Chat Output" msgstr "Csevegés kimenet" #: spa/plugins/alsa/acp/alsa-mixer.c:2765 msgid "Chat Input" msgstr "Csevegés bemenet" #: spa/plugins/alsa/acp/alsa-mixer.c:2766 msgid "Virtual Surround 7.1" msgstr "Virtuális térhatás 7.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4471 msgid "Analog Mono" msgstr "Analóg monó" #: spa/plugins/alsa/acp/alsa-mixer.c:4472 msgid "Analog Mono (Left)" msgstr "Analóg monó (bal)" #: spa/plugins/alsa/acp/alsa-mixer.c:4473 msgid "Analog Mono (Right)" msgstr "Analóg monó (jobb)" #. Note: Not translated to "Analog Stereo Input", because the source #. * name gets "Input" appended to it automatically, so adding "Input" #. * here would lead to the source name to become "Analog Stereo Input #. * Input". The same logic applies to analog-stereo-output, #. * multichannel-input and multichannel-output. #: spa/plugins/alsa/acp/alsa-mixer.c:4474 #: spa/plugins/alsa/acp/alsa-mixer.c:4482 #: spa/plugins/alsa/acp/alsa-mixer.c:4483 msgid "Analog Stereo" msgstr "Analóg sztereó" #: spa/plugins/alsa/acp/alsa-mixer.c:4475 msgid "Mono" msgstr "Monó" #: spa/plugins/alsa/acp/alsa-mixer.c:4476 msgid "Stereo" msgstr "Sztereó" #: spa/plugins/alsa/acp/alsa-mixer.c:4484 #: spa/plugins/alsa/acp/alsa-mixer.c:4642 #: spa/plugins/bluez5/bluez5-device.c:1442 msgid "Headset" msgstr "Fejhallgató" #: spa/plugins/alsa/acp/alsa-mixer.c:4485 #: spa/plugins/alsa/acp/alsa-mixer.c:4643 msgid "Speakerphone" msgstr "Mikrofonos fejhallgató" #: spa/plugins/alsa/acp/alsa-mixer.c:4486 #: spa/plugins/alsa/acp/alsa-mixer.c:4487 msgid "Multichannel" msgstr "Többcsatornás" #: spa/plugins/alsa/acp/alsa-mixer.c:4488 msgid "Analog Surround 2.1" msgstr "Analóg térhatású 2.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4489 msgid "Analog Surround 3.0" msgstr "Analóg térhatású 3.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4490 msgid "Analog Surround 3.1" msgstr "Analóg térhatású 3.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4491 msgid "Analog Surround 4.0" msgstr "Analóg térhatású 4.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4492 msgid "Analog Surround 4.1" msgstr "Analóg térhatású 4.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4493 msgid "Analog Surround 5.0" msgstr "Analóg térhatású 5.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4494 msgid "Analog Surround 5.1" msgstr "Analóg térhatású 5.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4495 msgid "Analog Surround 6.0" msgstr "Analóg térhatású 6.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4496 msgid "Analog Surround 6.1" msgstr "Analóg térhatású 6.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4497 msgid "Analog Surround 7.0" msgstr "Analóg térhatású 7.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4498 msgid "Analog Surround 7.1" msgstr "Analóg térhatású 7.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4499 msgid "Digital Stereo (IEC958)" msgstr "Digitális sztereó (IEC958)" #: spa/plugins/alsa/acp/alsa-mixer.c:4500 msgid "Digital Surround 4.0 (IEC958/AC3)" msgstr "Digitális térhatású 4.0 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4501 msgid "Digital Surround 5.1 (IEC958/AC3)" msgstr "Digitális térhatású 5.1 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4502 msgid "Digital Surround 5.1 (IEC958/DTS)" msgstr "Digitális térhatású 5.1 (IEC958/DTS)" #: spa/plugins/alsa/acp/alsa-mixer.c:4503 msgid "Digital Stereo (HDMI)" msgstr "Digitális sztereó (HDMI)" #: spa/plugins/alsa/acp/alsa-mixer.c:4504 msgid "Digital Surround 5.1 (HDMI)" msgstr "Digitális térhatású 5.1 (HDMI)" #: spa/plugins/alsa/acp/alsa-mixer.c:4505 msgid "Chat" msgstr "Csevegés" #: spa/plugins/alsa/acp/alsa-mixer.c:4506 msgid "Game" msgstr "Játék" #: spa/plugins/alsa/acp/alsa-mixer.c:4640 msgid "Analog Mono Duplex" msgstr "Analóg monó kétirányú" #: spa/plugins/alsa/acp/alsa-mixer.c:4641 msgid "Analog Stereo Duplex" msgstr "Analóg sztereó kétirányú" #: spa/plugins/alsa/acp/alsa-mixer.c:4644 msgid "Digital Stereo Duplex (IEC958)" msgstr "Digitális sztereó kétirányú (IEC958)" #: spa/plugins/alsa/acp/alsa-mixer.c:4645 msgid "Multichannel Duplex" msgstr "Többcsatornás kétirányú" #: spa/plugins/alsa/acp/alsa-mixer.c:4646 msgid "Stereo Duplex" msgstr "Sztereó kétirányú" #: spa/plugins/alsa/acp/alsa-mixer.c:4647 msgid "Mono Chat + 7.1 Surround" msgstr "Monó csevegés + 7.1 térhatású" #: spa/plugins/alsa/acp/alsa-mixer.c:4754 #, c-format msgid "%s Output" msgstr "%s kimenet" #: spa/plugins/alsa/acp/alsa-mixer.c:4761 #, c-format msgid "%s Input" msgstr "%s bemenet" #: spa/plugins/alsa/acp/alsa-util.c:1187 spa/plugins/alsa/acp/alsa-util.c:1281 #, c-format msgid "" "snd_pcm_avail() returned a value that is exceptionally large: %lu byte (%lu " "ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_avail() returned a value that is exceptionally large: %lu bytes (%lu " "ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "Az „snd_pcm_avail()” függvény különlegesen nagy értéket adott vissza: %lu " "bájt (%lu ms).\n" "Ez valószínűleg egy hiba eredménye az ALSA „%s” illesztőprogramban. Jelentse " "ezt a problémát az ALSA fejlesztői felé." msgstr[1] "" "Az „snd_pcm_avail()” függvény különlegesen nagy értéket adott vissza: %lu " "bájt (%lu ms).\n" "Ez valószínűleg egy hiba eredménye az ALSA „%s” illesztőprogramban. Jelentse " "ezt a problémát az ALSA fejlesztői felé." #: spa/plugins/alsa/acp/alsa-util.c:1253 #, c-format msgid "" "snd_pcm_delay() returned a value that is exceptionally large: %li byte (%s" "%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_delay() returned a value that is exceptionally large: %li bytes (%s" "%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "Az „snd_pcm_delay()” függvény különlegesen nagy értéket adott vissza: %li " "bájt (%s%lu ms).\n" "Ez valószínűleg egy hiba eredménye az ALSA „%s” illesztőprogramban. Jelentse " "ezt a problémát az ALSA fejlesztői felé." msgstr[1] "" "Az „snd_pcm_delay()” függvény különlegesen nagy értéket adott vissza: %li " "bájt (%s%lu ms).\n" "Ez valószínűleg egy hiba eredménye az ALSA „%s” illesztőprogramban. Jelentse " "ezt a problémát az ALSA fejlesztői felé." #: spa/plugins/alsa/acp/alsa-util.c:1300 #, c-format msgid "" "snd_pcm_avail_delay() returned strange values: delay %lu is less than avail " "%lu.\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr "" "Az „snd_pcm_avail_delay()” függvény furcsa értékeket adott vissza: a " "késleltetés (%lu) kisebb, mint az elérhető %lu.\n" "Ez valószínűleg egy hiba eredménye az ALSA „%s” illesztőprogramban. Jelentse " "ezt a problémát az ALSA fejlesztői felé." #: spa/plugins/alsa/acp/alsa-util.c:1343 #, c-format msgid "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu byte " "(%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu bytes " "(%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "Az „snd_pcm_mmap_begin()” függvény különlegesen nagy értéket adott vissza: " "%lu bájt (%lu ms).\n" "Ez valószínűleg egy hiba eredménye az ALSA „%s” illesztőprogramban. Jelentse " "ezt a problémát az ALSA fejlesztői felé." msgstr[1] "" "Az „snd_pcm_mmap_begin()” függvény különlegesen nagy értéket adott vissza: " "%lu bájt (%lu ms).\n" "Ez valószínűleg egy hiba eredménye az ALSA „%s” illesztőprogramban. Jelentse " "ezt a problémát az ALSA fejlesztői felé." #: spa/plugins/alsa/acp/channelmap.h:457 msgid "(invalid)" msgstr "(érvénytelen)" #: spa/plugins/alsa/acp/compat.c:189 msgid "Built-in Audio" msgstr "Beépített hangforrás" #: spa/plugins/alsa/acp/compat.c:194 msgid "Modem" msgstr "Modem" #: spa/plugins/bluez5/bluez5-device.c:1247 msgid "Audio Gateway (A2DP Source & HSP/HFP AG)" msgstr "Hang átjáró (A2DP forrás és HSP/HFP AG)" #: spa/plugins/bluez5/bluez5-device.c:1272 #, c-format msgid "High Fidelity Playback (A2DP Sink, codec %s)" msgstr "Magas hűségű lejátszás (A2DP fogadó, %s kodek)" #: spa/plugins/bluez5/bluez5-device.c:1275 #, c-format msgid "High Fidelity Duplex (A2DP Source/Sink, codec %s)" msgstr "Magas hűségű kétirányú (A2DP forrás/fogadó, %s kodek)" #: spa/plugins/bluez5/bluez5-device.c:1283 msgid "High Fidelity Playback (A2DP Sink)" msgstr "Magas hűségű lejátszás (A2DP fogadó)" #: spa/plugins/bluez5/bluez5-device.c:1285 msgid "High Fidelity Duplex (A2DP Source/Sink)" msgstr "Magas hűségű kétirányú (A2DP forrás/fogadó)" #: spa/plugins/bluez5/bluez5-device.c:1322 #, c-format msgid "High Fidelity Playback (BAP Sink, codec %s)" msgstr "Magas hűségű lejátszás (BAP fogadó, %s kodek)" #: spa/plugins/bluez5/bluez5-device.c:1326 #, c-format msgid "High Fidelity Input (BAP Source, codec %s)" msgstr "Magas hűségű bemenet (BAP forrás, %s kodek)" #: spa/plugins/bluez5/bluez5-device.c:1330 #, c-format msgid "High Fidelity Duplex (BAP Source/Sink, codec %s)" msgstr "Magas hűségű kétirányú (BAP forrás/fogadó, %s kodek)" #: spa/plugins/bluez5/bluez5-device.c:1359 #, c-format msgid "Headset Head Unit (HSP/HFP, codec %s)" msgstr "Fejhallgató fejegység (HSP/HFP, %s kodek)" #: spa/plugins/bluez5/bluez5-device.c:1364 msgid "Headset Head Unit (HSP/HFP)" msgstr "Fejhallgató fejegység (HSP/HFP)" #: spa/plugins/bluez5/bluez5-device.c:1443 #: spa/plugins/bluez5/bluez5-device.c:1448 #: spa/plugins/bluez5/bluez5-device.c:1455 #: spa/plugins/bluez5/bluez5-device.c:1461 #: spa/plugins/bluez5/bluez5-device.c:1467 #: spa/plugins/bluez5/bluez5-device.c:1473 #: spa/plugins/bluez5/bluez5-device.c:1479 #: spa/plugins/bluez5/bluez5-device.c:1485 #: spa/plugins/bluez5/bluez5-device.c:1491 msgid "Handsfree" msgstr "Kihangosító" #: spa/plugins/bluez5/bluez5-device.c:1449 msgid "Handsfree (HFP)" msgstr "Kihangosító (HFP)" #: spa/plugins/bluez5/bluez5-device.c:1466 msgid "Headphone" msgstr "Fejhallgató" #: spa/plugins/bluez5/bluez5-device.c:1472 msgid "Portable" msgstr "Hordozható" #: spa/plugins/bluez5/bluez5-device.c:1478 msgid "Car" msgstr "Autó" #: spa/plugins/bluez5/bluez5-device.c:1484 msgid "HiFi" msgstr "Hi-Fi" #: spa/plugins/bluez5/bluez5-device.c:1490 msgid "Phone" msgstr "Telefon" #: spa/plugins/bluez5/bluez5-device.c:1497 msgid "Bluetooth" msgstr "Bluetooth" #: spa/plugins/bluez5/bluez5-device.c:1498 msgid "Bluetooth (HFP)" msgstr "Bluetooth (HFP)" pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/po/id.po000066400000000000000000000530111511204443500220500ustar00rootroot00000000000000# Indonesian translation of pipewire # Copyright (C) 2011 THE pipewire'S COPYRIGHT HOLDER # This file is distributed under the same license as the pipewire package. # # Translators: # Andika Triwidada , 2011, 2012, 2018, 2021, 2024. msgid "" msgstr "" "Project-Id-Version: PipeWire master\n" "Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/pipewire/" "issues/new\n" "POT-Creation-Date: 2024-02-25 03:43+0300\n" "PO-Revision-Date: 2024-11-03 14:23+0700\n" "Last-Translator: Andika Triwidada \n" "Language-Team: Indonesia \n" "Language: id\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" "X-Generator: Poedit 3.5\n" #: src/daemon/pipewire.c:26 #, c-format msgid "" "%s [options]\n" " -h, --help Show this help\n" " --version Show version\n" " -c, --config Load config (Default %s)\n" msgstr "" "%s [opsi]\n" " -h, --help Tampilkan bantuan ini\n" " --version Tampilkan versi\n" " -c, --config Muat konfig (Baku %s)\n" #: src/daemon/pipewire.desktop.in:4 msgid "PipeWire Media System" msgstr "Sistem Media PipeWire" #: src/daemon/pipewire.desktop.in:5 msgid "Start the PipeWire Media System" msgstr "Memulai Sistem Media PipeWire" #: src/modules/module-protocol-pulse/modules/module-tunnel-sink.c:159 #: src/modules/module-protocol-pulse/modules/module-tunnel-source.c:159 #, c-format msgid "Tunnel to %s%s%s" msgstr "Tunnel ke %s%s%s" #: src/modules/module-fallback-sink.c:40 msgid "Dummy Output" msgstr "Keluaran Dummy" #: src/modules/module-pulse-tunnel.c:774 #, c-format msgid "Tunnel for %s@%s" msgstr "Tunnel untuk %s@%s" #: src/modules/module-zeroconf-discover.c:315 msgid "Unknown device" msgstr "Perangkat tak dikenal" #: src/modules/module-zeroconf-discover.c:327 #, c-format msgid "%s on %s@%s" msgstr "%s pada %s@%s" #: src/modules/module-zeroconf-discover.c:331 #, c-format msgid "%s on %s" msgstr "%s pada %s" #: src/tools/pw-cat.c:991 #, c-format msgid "" "%s [options] [|-]\n" " -h, --help Show this help\n" " --version Show version\n" " -v, --verbose Enable verbose operations\n" "\n" msgstr "" "%s [opsi] [|-]\n" " -h, --help Tampilkan bantuan ini\n" " --version Tampilkan versi\n" " -v, --verbose Fungsikan pesan rinci\n" "\n" #: src/tools/pw-cat.c:998 #, c-format msgid "" " -R, --remote Remote daemon name\n" " --media-type Set media type (default %s)\n" " --media-category Set media category (default %s)\n" " --media-role Set media role (default %s)\n" " --target Set node target serial or name " "(default %s)\n" " 0 means don't link\n" " --latency Set node latency (default %s)\n" " Xunit (unit = s, ms, us, ns)\n" " or direct samples (256)\n" " the rate is the one of the source " "file\n" " -P --properties Set node properties\n" "\n" msgstr "" " -R, --remote Nama daemon remote\n" " --media-type Atur tipe media (baku %s)\n" " --media-category Atur kategori media (baku %s)\n" " --media-role Atur peran media (baku %s)\n" " --target Atur target simpul (baku %s)\n" " 0 berarti jangan tautkan\n" " --latency Atur latensi simpul (baku %s)\n" " Xunit (unit = s, ms, us, ns)\n" " or cuplikan langsung (256)\n" " laju adalah satu dari berkas " "sumber\n" " -P --properties Atur properti simpul\n" "\n" #: src/tools/pw-cat.c:1016 #, c-format msgid "" " --rate Sample rate (req. for rec) (default " "%u)\n" " --channels Number of channels (req. for rec) " "(default %u)\n" " --channel-map Channel map\n" " one of: \"stereo\", " "\"surround-51\",... or\n" " comma separated list of channel " "names: eg. \"FL,FR\"\n" " --format Sample format %s (req. for rec) " "(default %s)\n" " --volume Stream volume 0-1.0 (default %.3f)\n" " -q --quality Resampler quality (0 - 15) (default " "%d)\n" "\n" msgstr "" " --rate Laju cuplik (req. for rec) (baku " "%u)\n" " --channels Cacah kanal (req. for rec) (baku " "%u)\n" " --channel-map Peta kanal\n" " satu dari: \"stereo\", " "\"surround-51\",... atau\n" " daftar dipisah koma dari nama " "kanal: mis. \"FL,FR\"\n" " --format Format cuplikan %s (perlu untuk " "rekam) (baku %s)\n" " --volume Volume stream 0-1.0 (baku %.3f)\n" " -q --quality Kualitas resampler (0 - 15) (baku " "%d)\n" "\n" #: src/tools/pw-cat.c:1033 msgid "" " -p, --playback Playback mode\n" " -r, --record Recording mode\n" " -m, --midi Midi mode\n" " -d, --dsd DSD mode\n" " -o, --encoded Encoded mode\n" "\n" msgstr "" " -p, --playback Mode main ulang\n" " -r, --record Mode perekaman\n" " -m, --midi Mode midi\n" " -d, --dsd Mode DSD\n" " -o, --encoded Mode di-enkode\n" "\n" #: src/tools/pw-cli.c:2252 #, c-format msgid "" "%s [options] [command]\n" " -h, --help Show this help\n" " --version Show version\n" " -d, --daemon Start as daemon (Default false)\n" " -r, --remote Remote daemon name\n" " -m, --monitor Monitor activity\n" "\n" msgstr "" "%s [opsi] [perintah]\n" " -h, --help Tampilkan bantuan ini\n" " --version Tampilkan versi\n" " -d, --daemon Mulai sebagai daemon (Baku = false)\n" " -r, --remote Nama daemon remote\n" " -m, --monitor Monitor activitas\n" "\n" #: spa/plugins/alsa/acp/acp.c:327 msgid "Pro Audio" msgstr "Pro Audio" #: spa/plugins/alsa/acp/acp.c:488 spa/plugins/alsa/acp/alsa-mixer.c:4633 #: spa/plugins/bluez5/bluez5-device.c:1701 msgid "Off" msgstr "Mati" #: spa/plugins/alsa/acp/alsa-mixer.c:2652 msgid "Input" msgstr "Masukan" #: spa/plugins/alsa/acp/alsa-mixer.c:2653 msgid "Docking Station Input" msgstr "Masukan Docking Station" #: spa/plugins/alsa/acp/alsa-mixer.c:2654 msgid "Docking Station Microphone" msgstr "Mikrofon Docking Station" #: spa/plugins/alsa/acp/alsa-mixer.c:2655 msgid "Docking Station Line In" msgstr "Docking Station Line In" #: spa/plugins/alsa/acp/alsa-mixer.c:2656 #: spa/plugins/alsa/acp/alsa-mixer.c:2747 msgid "Line In" msgstr "Line In" #: spa/plugins/alsa/acp/alsa-mixer.c:2657 #: spa/plugins/alsa/acp/alsa-mixer.c:2741 #: spa/plugins/bluez5/bluez5-device.c:1989 msgid "Microphone" msgstr "Mikrofon" #: spa/plugins/alsa/acp/alsa-mixer.c:2658 #: spa/plugins/alsa/acp/alsa-mixer.c:2742 msgid "Front Microphone" msgstr "Mikrofon Depan" #: spa/plugins/alsa/acp/alsa-mixer.c:2659 #: spa/plugins/alsa/acp/alsa-mixer.c:2743 msgid "Rear Microphone" msgstr "Mikrofon Belakang" #: spa/plugins/alsa/acp/alsa-mixer.c:2660 msgid "External Microphone" msgstr "Mikrofon Eksternal" #: spa/plugins/alsa/acp/alsa-mixer.c:2661 #: spa/plugins/alsa/acp/alsa-mixer.c:2745 msgid "Internal Microphone" msgstr "Mikrofon Internal" #: spa/plugins/alsa/acp/alsa-mixer.c:2662 #: spa/plugins/alsa/acp/alsa-mixer.c:2748 msgid "Radio" msgstr "Radio" #: spa/plugins/alsa/acp/alsa-mixer.c:2663 #: spa/plugins/alsa/acp/alsa-mixer.c:2749 msgid "Video" msgstr "Video" #: spa/plugins/alsa/acp/alsa-mixer.c:2664 msgid "Automatic Gain Control" msgstr "Kendali Penguatan Otomatis (AGC)" #: spa/plugins/alsa/acp/alsa-mixer.c:2665 msgid "No Automatic Gain Control" msgstr "Tanpa Kendali Penguatan Otomatis (AGC)" #: spa/plugins/alsa/acp/alsa-mixer.c:2666 msgid "Boost" msgstr "Boost" #: spa/plugins/alsa/acp/alsa-mixer.c:2667 msgid "No Boost" msgstr "Tanpa Boost" #: spa/plugins/alsa/acp/alsa-mixer.c:2668 msgid "Amplifier" msgstr "Penguat" #: spa/plugins/alsa/acp/alsa-mixer.c:2669 msgid "No Amplifier" msgstr "Tanpa Penguat" #: spa/plugins/alsa/acp/alsa-mixer.c:2670 msgid "Bass Boost" msgstr "Boost Bass" #: spa/plugins/alsa/acp/alsa-mixer.c:2671 msgid "No Bass Boost" msgstr "Tanpa Boost Bass" #: spa/plugins/alsa/acp/alsa-mixer.c:2672 #: spa/plugins/bluez5/bluez5-device.c:1995 msgid "Speaker" msgstr "Speaker" #: spa/plugins/alsa/acp/alsa-mixer.c:2673 #: spa/plugins/alsa/acp/alsa-mixer.c:2751 msgid "Headphones" msgstr "Headphone" #: spa/plugins/alsa/acp/alsa-mixer.c:2740 msgid "Analog Input" msgstr "Masukan Analog" #: spa/plugins/alsa/acp/alsa-mixer.c:2744 msgid "Dock Microphone" msgstr "Mikrofon Dok" #: spa/plugins/alsa/acp/alsa-mixer.c:2746 msgid "Headset Microphone" msgstr "Mikrofon Headset" #: spa/plugins/alsa/acp/alsa-mixer.c:2750 msgid "Analog Output" msgstr "Keluaran Analog" #: spa/plugins/alsa/acp/alsa-mixer.c:2752 msgid "Headphones 2" msgstr "Headphone 2" #: spa/plugins/alsa/acp/alsa-mixer.c:2753 msgid "Headphones Mono Output" msgstr "Keluaran Mono Headphone" #: spa/plugins/alsa/acp/alsa-mixer.c:2754 msgid "Line Out" msgstr "Line Out" #: spa/plugins/alsa/acp/alsa-mixer.c:2755 msgid "Analog Mono Output" msgstr "Keluaran Mono Analog" #: spa/plugins/alsa/acp/alsa-mixer.c:2756 msgid "Speakers" msgstr "Speaker" #: spa/plugins/alsa/acp/alsa-mixer.c:2757 msgid "HDMI / DisplayPort" msgstr "HDMI / DisplayPort" #: spa/plugins/alsa/acp/alsa-mixer.c:2758 msgid "Digital Output (S/PDIF)" msgstr "Keluaran Digital (S/PDIF)" #: spa/plugins/alsa/acp/alsa-mixer.c:2759 msgid "Digital Input (S/PDIF)" msgstr "Masukan Digital (S/PDIF)" #: spa/plugins/alsa/acp/alsa-mixer.c:2760 msgid "Multichannel Input" msgstr "Masukan Multikanal" #: spa/plugins/alsa/acp/alsa-mixer.c:2761 msgid "Multichannel Output" msgstr "Keluaran Multikanal" #: spa/plugins/alsa/acp/alsa-mixer.c:2762 msgid "Game Output" msgstr "Keluaran Permainan" #: spa/plugins/alsa/acp/alsa-mixer.c:2763 #: spa/plugins/alsa/acp/alsa-mixer.c:2764 msgid "Chat Output" msgstr "Keluaran Obrolan" #: spa/plugins/alsa/acp/alsa-mixer.c:2765 msgid "Chat Input" msgstr "Masukan Obrolan" #: spa/plugins/alsa/acp/alsa-mixer.c:2766 msgid "Virtual Surround 7.1" msgstr "Virtual Surround 7.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4456 msgid "Analog Mono" msgstr "Analog Mono" #: spa/plugins/alsa/acp/alsa-mixer.c:4457 msgid "Analog Mono (Left)" msgstr "Analog Mono (Kiri)" #: spa/plugins/alsa/acp/alsa-mixer.c:4458 msgid "Analog Mono (Right)" msgstr "Analog Mono (Kanan)" #. Note: Not translated to "Analog Stereo Input", because the source #. * name gets "Input" appended to it automatically, so adding "Input" #. * here would lead to the source name to become "Analog Stereo Input #. * Input". The same logic applies to analog-stereo-output, #. * multichannel-input and multichannel-output. #: spa/plugins/alsa/acp/alsa-mixer.c:4459 #: spa/plugins/alsa/acp/alsa-mixer.c:4467 #: spa/plugins/alsa/acp/alsa-mixer.c:4468 msgid "Analog Stereo" msgstr "Analog Stereo" #: spa/plugins/alsa/acp/alsa-mixer.c:4460 msgid "Mono" msgstr "Mono" #: spa/plugins/alsa/acp/alsa-mixer.c:4461 msgid "Stereo" msgstr "Stereo" #: spa/plugins/alsa/acp/alsa-mixer.c:4469 #: spa/plugins/alsa/acp/alsa-mixer.c:4627 #: spa/plugins/bluez5/bluez5-device.c:1977 msgid "Headset" msgstr "Headset" #: spa/plugins/alsa/acp/alsa-mixer.c:4470 #: spa/plugins/alsa/acp/alsa-mixer.c:4628 msgid "Speakerphone" msgstr "Speakerphone" #: spa/plugins/alsa/acp/alsa-mixer.c:4471 #: spa/plugins/alsa/acp/alsa-mixer.c:4472 msgid "Multichannel" msgstr "Multikanal" #: spa/plugins/alsa/acp/alsa-mixer.c:4473 msgid "Analog Surround 2.1" msgstr "Analog Surround 2.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4474 msgid "Analog Surround 3.0" msgstr "Analog Surround 3.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4475 msgid "Analog Surround 3.1" msgstr "Analog Surround 3.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4476 msgid "Analog Surround 4.0" msgstr "Analog Surround 4.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4477 msgid "Analog Surround 4.1" msgstr "Analog Surround 4.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4478 msgid "Analog Surround 5.0" msgstr "Analog Surround 5.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4479 msgid "Analog Surround 5.1" msgstr "Analog Surround 5.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4480 msgid "Analog Surround 6.0" msgstr "Analog Surround 6.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4481 msgid "Analog Surround 6.1" msgstr "Analog Surround 6.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4482 msgid "Analog Surround 7.0" msgstr "Analog Surround 7.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4483 msgid "Analog Surround 7.1" msgstr "Analog Surround 7.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4484 msgid "Digital Stereo (IEC958)" msgstr "Digital Stereo (IEC958)" #: spa/plugins/alsa/acp/alsa-mixer.c:4485 msgid "Digital Surround 4.0 (IEC958/AC3)" msgstr "Digital Surround 4.0 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4486 msgid "Digital Surround 5.1 (IEC958/AC3)" msgstr "Digital Surround 5.1 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4487 msgid "Digital Surround 5.1 (IEC958/DTS)" msgstr "Surround 5.1 Digital (IEC958/DTS)" #: spa/plugins/alsa/acp/alsa-mixer.c:4488 msgid "Digital Stereo (HDMI)" msgstr "Digital Stereo (HDMI)" #: spa/plugins/alsa/acp/alsa-mixer.c:4489 msgid "Digital Surround 5.1 (HDMI)" msgstr "Surround 5.1 Digital (HDMI)" #: spa/plugins/alsa/acp/alsa-mixer.c:4490 msgid "Chat" msgstr "Obrolan" #: spa/plugins/alsa/acp/alsa-mixer.c:4491 msgid "Game" msgstr "Permainan" #: spa/plugins/alsa/acp/alsa-mixer.c:4625 msgid "Analog Mono Duplex" msgstr "Dupleks Mono Analog" #: spa/plugins/alsa/acp/alsa-mixer.c:4626 msgid "Analog Stereo Duplex" msgstr "Dupleks Stereo Analog" #: spa/plugins/alsa/acp/alsa-mixer.c:4629 msgid "Digital Stereo Duplex (IEC958)" msgstr "Dupleks Stereo Digital (IEC958)" #: spa/plugins/alsa/acp/alsa-mixer.c:4630 msgid "Multichannel Duplex" msgstr "Dupleks Multikanal" #: spa/plugins/alsa/acp/alsa-mixer.c:4631 msgid "Stereo Duplex" msgstr "Dupleks Stereo" #: spa/plugins/alsa/acp/alsa-mixer.c:4632 msgid "Mono Chat + 7.1 Surround" msgstr "Mono Chat + 7.1 Surround" #: spa/plugins/alsa/acp/alsa-mixer.c:4733 #, c-format msgid "%s Output" msgstr "Keluaran %s" #: spa/plugins/alsa/acp/alsa-mixer.c:4741 #, c-format msgid "%s Input" msgstr "Masukan %s" #: spa/plugins/alsa/acp/alsa-util.c:1220 spa/plugins/alsa/acp/alsa-util.c:1314 #, c-format msgid "" "snd_pcm_avail() returned a value that is exceptionally large: %lu byte (%lu " "ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_avail() returned a value that is exceptionally large: %lu bytes (%lu " "ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "snd_pcm_avail() mengembalikan nilai yang luar biasa besar: %lu byte (%lu " "ms).\n" "Sangat mungkin ini adalah kutu pada driver ALSA '%s'. Silakan laporkan hal " "ini ke para pengembang ALSA." #: spa/plugins/alsa/acp/alsa-util.c:1286 #, c-format msgid "" "snd_pcm_delay() returned a value that is exceptionally large: %li byte " "(%s%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_delay() returned a value that is exceptionally large: %li bytes " "(%s%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "snd_pcm_delay() mengembalikan nilai yang luar biasa besar: %li byte (%s%lu " "ms).\n" "Sangat mungkin ini adalah kutu pada driver ALSA '%s'. Silakan laporkan hal " "ini ke para pengembang ALSA." #: spa/plugins/alsa/acp/alsa-util.c:1333 #, c-format msgid "" "snd_pcm_avail_delay() returned strange values: delay %lu is less than avail " "%lu.\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr "" "snd_pcm_avail_delay() mengembalikan nilai yang aneh: tundaan %lu kurang dari " "yang tersedia %lu.\n" "Paling mungkin ini adalah kutu dalam penggerak ALSA '%s'. Harap laporkan " "kasus ini ke para pengembang ALSA." #: spa/plugins/alsa/acp/alsa-util.c:1376 #, c-format msgid "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu byte " "(%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu bytes " "(%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "snd_pcm_mmap_begin() mengembalikan nilai yang luar biasa besar: %lu byte " "(%lu ms).\n" "Sangat mungkin ini adalah kutu pada driver ALSA '%s'. Silakan laporkan hal " "ini ke para pengembang ALSA." #: spa/plugins/alsa/acp/channelmap.h:457 msgid "(invalid)" msgstr "(tak valid)" #: spa/plugins/alsa/acp/compat.c:193 msgid "Built-in Audio" msgstr "Audio Bawaan" #: spa/plugins/alsa/acp/compat.c:198 msgid "Modem" msgstr "Modem" #: spa/plugins/bluez5/bluez5-device.c:1712 msgid "Audio Gateway (A2DP Source & HSP/HFP AG)" msgstr "Audio Gateway (Sumber A2DP & HSP/HFP AG)" #: spa/plugins/bluez5/bluez5-device.c:1760 #, c-format msgid "High Fidelity Playback (A2DP Sink, codec %s)" msgstr "Putar High Fidelity (Muara A2DP, kodek %s)" #: spa/plugins/bluez5/bluez5-device.c:1763 #, c-format msgid "High Fidelity Duplex (A2DP Source/Sink, codec %s)" msgstr "Dupleks High Fidelity (Sumber/Muara A2DP, kodek %s)" #: spa/plugins/bluez5/bluez5-device.c:1771 msgid "High Fidelity Playback (A2DP Sink)" msgstr "Putar High Fidelity (Muara A2DP)" #: spa/plugins/bluez5/bluez5-device.c:1773 msgid "High Fidelity Duplex (A2DP Source/Sink)" msgstr "Dupleks High Fidelity (Sumber/Muara A2DP)" #: spa/plugins/bluez5/bluez5-device.c:1823 #, c-format msgid "High Fidelity Playback (BAP Sink, codec %s)" msgstr "Putar High Fidelity (Muara BAP, kodek %s)" #: spa/plugins/bluez5/bluez5-device.c:1828 #, c-format msgid "High Fidelity Input (BAP Source, codec %s)" msgstr "Masukan High Fidelity (Sumber BAP, kodek %s)" #: spa/plugins/bluez5/bluez5-device.c:1832 #, c-format msgid "High Fidelity Duplex (BAP Source/Sink, codec %s)" msgstr "Dupleks High Fidelity (Sumber/Muara BAP, kodek %s)" #: spa/plugins/bluez5/bluez5-device.c:1841 msgid "High Fidelity Playback (BAP Sink)" msgstr "Putar High Fidelity (Muara BAP)" #: spa/plugins/bluez5/bluez5-device.c:1845 msgid "High Fidelity Input (BAP Source)" msgstr "Masukan High Fidelity (Sumber BAP)" #: spa/plugins/bluez5/bluez5-device.c:1848 msgid "High Fidelity Duplex (BAP Source/Sink)" msgstr "Dupleks High Fidelity (Sumber/Muara BAP)" #: spa/plugins/bluez5/bluez5-device.c:1897 #, c-format msgid "Headset Head Unit (HSP/HFP, codec %s)" msgstr "Headset Head Unit (HSP/HFP, kodek %s)" #: spa/plugins/bluez5/bluez5-device.c:1978 #: spa/plugins/bluez5/bluez5-device.c:1983 #: spa/plugins/bluez5/bluez5-device.c:1990 #: spa/plugins/bluez5/bluez5-device.c:1996 #: spa/plugins/bluez5/bluez5-device.c:2002 #: spa/plugins/bluez5/bluez5-device.c:2008 #: spa/plugins/bluez5/bluez5-device.c:2014 #: spa/plugins/bluez5/bluez5-device.c:2020 #: spa/plugins/bluez5/bluez5-device.c:2026 msgid "Handsfree" msgstr "Handsfree" #: spa/plugins/bluez5/bluez5-device.c:1984 msgid "Handsfree (HFP)" msgstr "Handsfree (HFP)" #: spa/plugins/bluez5/bluez5-device.c:2001 msgid "Headphone" msgstr "Headphone" #: spa/plugins/bluez5/bluez5-device.c:2007 msgid "Portable" msgstr "Portabel" #: spa/plugins/bluez5/bluez5-device.c:2013 msgid "Car" msgstr "Mobil" #: spa/plugins/bluez5/bluez5-device.c:2019 msgid "HiFi" msgstr "HiFi" #: spa/plugins/bluez5/bluez5-device.c:2025 msgid "Phone" msgstr "Telepon" #: spa/plugins/bluez5/bluez5-device.c:2032 msgid "Bluetooth" msgstr "Bluetooth" #: spa/plugins/bluez5/bluez5-device.c:2033 msgid "Bluetooth (HFP)" msgstr "Bluetooth (HFP)" #, c-format #~ msgid "" #~ "%s [options]\n" #~ " -h, --help Show this help\n" #~ " -v, --verbose Increase verbosity by one level\n" #~ " --version Show version\n" #~ " -c, --config Load config (Default %s)\n" #~ " -P --properties Set context properties\n" #~ msgstr "" #~ "%s [opsi]\n" #~ " -h, --help Tampilkan bantuan ini\n" #~ " -v, --verbose Tingkatkan kerincian satu aras\n" #~ " --version Tampilkan versi\n" #~ " -c, --config Muat konfig (Baku %s)\n" #~ " -P --properties Atur properti konteks\n" pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/po/it.po000066400000000000000000000424321511204443500220750ustar00rootroot00000000000000# Italian translation for PipeWire. # Copyright (C) 2008, 2009, 2012, 2015, 2019 The Free Software Foundation, Inc # This file is distributed under the same license as the pipewire package. # # Luca Ferretti , 2008, 2009. # mario_santagiuliana , 2009. # Milo Casagrande , 2009, 2012, 2015, 2019. # Albano Battistella ,2021 msgid "" msgstr "" "Project-Id-Version: pipewire\n" "Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/pipewire/" "issues/new\n" "POT-Creation-Date: 2021-04-18 16:54+0800\n" "PO-Revision-Date: 2021-05-09 13:29+0100\n" "Last-Translator: Albano Battistella \n" "Language-Team: Italian <>" "pipewire/pipewire/it/>\n" "Language: it\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" "X-Generator: Weblate 4.2.2\n" #: src/daemon/pipewire.c:43 #, c-format msgid "" "%s [options]\n" " -h, --help Show this help\n" " --version Show version\n" " -c, --config Load config (Default %s)\n" msgstr "" #: src/daemon/pipewire.desktop.in:4 msgid "PipeWire Media System" msgstr "Sistema multimediale PipeWire" #: src/daemon/pipewire.desktop.in:5 msgid "Start the PipeWire Media System" msgstr "Avvia il sistema multimediale PipeWire" #: src/examples/media-session/alsa-monitor.c:526 #: spa/plugins/alsa/acp/compat.c:187 msgid "Built-in Audio" msgstr "Audio interno" #: src/examples/media-session/alsa-monitor.c:530 #: spa/plugins/alsa/acp/compat.c:192 msgid "Modem" msgstr "Modem" #: src/examples/media-session/alsa-monitor.c:539 msgid "Unknown device" msgstr "Dispositivo sconosciuto" #: src/tools/pw-cat.c:991 #, c-format msgid "" "%s [options] \n" " -h, --help Show this help\n" " --version Show version\n" " -v, --verbose Enable verbose operations\n" "\n" msgstr "" #: src/tools/pw-cat.c:998 #, c-format msgid "" " -R, --remote Remote daemon name\n" " --media-type Set media type (default %s)\n" " --media-category Set media category (default %s)\n" " --media-role Set media role (default %s)\n" " --target Set node target (default %s)\n" " 0 means don't link\n" " --latency Set node latency (default %s)\n" " Xunit (unit = s, ms, us, ns)\n" " or direct samples (256)\n" " the rate is the one of the source " "file\n" " --list-targets List available targets for --target\n" "\n" msgstr "" #: src/tools/pw-cat.c:1016 #, c-format msgid "" " --rate Sample rate (req. for rec) (default " "%u)\n" " --channels Number of channels (req. for rec) " "(default %u)\n" " --channel-map Channel map\n" " one of: \"stereo\", " "\"surround-51\",... or\n" " comma separated list of channel " "names: eg. \"FL,FR\"\n" " --format Sample format %s (req. for rec) " "(default %s)\n" " --volume Stream volume 0-1.0 (default %.3f)\n" " -q --quality Resampler quality (0 - 15) (default " "%d)\n" "\n" msgstr "" #: src/tools/pw-cat.c:1033 msgid "" " -p, --playback Playback mode\n" " -r, --record Recording mode\n" " -m, --midi Midi mode\n" "\n" msgstr "" #: src/tools/pw-cli.c:2932 #, c-format msgid "" "%s [options] [command]\n" " -h, --help Show this help\n" " --version Show version\n" " -d, --daemon Start as daemon (Default false)\n" " -r, --remote Remote daemon name\n" "\n" msgstr "" #: spa/plugins/alsa/acp/acp.c:290 msgid "Pro Audio" msgstr "Audio Professionale" #: spa/plugins/alsa/acp/acp.c:411 spa/plugins/alsa/acp/alsa-mixer.c:4704 #: spa/plugins/bluez5/bluez5-device.c:1000 msgid "Off" msgstr "Spento" #: spa/plugins/alsa/acp/channelmap.h:466 msgid "(invalid)" msgstr "(non valido)" #: spa/plugins/alsa/acp/alsa-mixer.c:2709 msgid "Input" msgstr "Ingresso" #: spa/plugins/alsa/acp/alsa-mixer.c:2710 msgid "Docking Station Input" msgstr "Ingresso docking station" #: spa/plugins/alsa/acp/alsa-mixer.c:2711 msgid "Docking Station Microphone" msgstr "Microfono della docking station" #: spa/plugins/alsa/acp/alsa-mixer.c:2712 msgid "Docking Station Line In" msgstr "Linea di ingresso nella docking station" #: spa/plugins/alsa/acp/alsa-mixer.c:2713 #: spa/plugins/alsa/acp/alsa-mixer.c:2804 msgid "Line In" msgstr "Linea di ingresso" #: spa/plugins/alsa/acp/alsa-mixer.c:2714 #: spa/plugins/alsa/acp/alsa-mixer.c:2798 #: spa/plugins/bluez5/bluez5-device.c:1145 msgid "Microphone" msgstr "Microfono" #: spa/plugins/alsa/acp/alsa-mixer.c:2715 #: spa/plugins/alsa/acp/alsa-mixer.c:2799 msgid "Front Microphone" msgstr "Microfono anteriore" #: spa/plugins/alsa/acp/alsa-mixer.c:2716 #: spa/plugins/alsa/acp/alsa-mixer.c:2800 msgid "Rear Microphone" msgstr "Microfono posteriore" #: spa/plugins/alsa/acp/alsa-mixer.c:2717 msgid "External Microphone" msgstr "Microfono esterno" #: spa/plugins/alsa/acp/alsa-mixer.c:2718 #: spa/plugins/alsa/acp/alsa-mixer.c:2802 msgid "Internal Microphone" msgstr "Microfono interno" #: spa/plugins/alsa/acp/alsa-mixer.c:2719 #: spa/plugins/alsa/acp/alsa-mixer.c:2805 msgid "Radio" msgstr "Radio" #: spa/plugins/alsa/acp/alsa-mixer.c:2720 #: spa/plugins/alsa/acp/alsa-mixer.c:2806 msgid "Video" msgstr "Video" #: spa/plugins/alsa/acp/alsa-mixer.c:2721 msgid "Automatic Gain Control" msgstr "Controllo automatico del guadagno" #: spa/plugins/alsa/acp/alsa-mixer.c:2722 msgid "No Automatic Gain Control" msgstr "Nessun controllo automatico del guadagno" #: spa/plugins/alsa/acp/alsa-mixer.c:2723 msgid "Boost" msgstr "Boost" #: spa/plugins/alsa/acp/alsa-mixer.c:2724 msgid "No Boost" msgstr "Nessun boost" #: spa/plugins/alsa/acp/alsa-mixer.c:2725 msgid "Amplifier" msgstr "Amplificatore" #: spa/plugins/alsa/acp/alsa-mixer.c:2726 msgid "No Amplifier" msgstr "Nessun amplificatore" #: spa/plugins/alsa/acp/alsa-mixer.c:2727 msgid "Bass Boost" msgstr "Incremento bassi" #: spa/plugins/alsa/acp/alsa-mixer.c:2728 msgid "No Bass Boost" msgstr "Nessun incremento bassi" #: spa/plugins/alsa/acp/alsa-mixer.c:2729 #: spa/plugins/bluez5/bluez5-device.c:1150 msgid "Speaker" msgstr "Altoparlante" #: spa/plugins/alsa/acp/alsa-mixer.c:2730 #: spa/plugins/alsa/acp/alsa-mixer.c:2808 msgid "Headphones" msgstr "Cuffie analogiche" #: spa/plugins/alsa/acp/alsa-mixer.c:2797 msgid "Analog Input" msgstr "Ingresso analogico" #: spa/plugins/alsa/acp/alsa-mixer.c:2801 msgid "Dock Microphone" msgstr "Microfono docking station" #: spa/plugins/alsa/acp/alsa-mixer.c:2803 msgid "Headset Microphone" msgstr "Microfono auricolare" #: spa/plugins/alsa/acp/alsa-mixer.c:2807 msgid "Analog Output" msgstr "Uscita analogica" #: spa/plugins/alsa/acp/alsa-mixer.c:2809 msgid "Headphones 2" msgstr "Cuffie 2" #: spa/plugins/alsa/acp/alsa-mixer.c:2810 msgid "Headphones Mono Output" msgstr "Uscita mono cuffie" #: spa/plugins/alsa/acp/alsa-mixer.c:2811 msgid "Line Out" msgstr "Linea di uscita" #: spa/plugins/alsa/acp/alsa-mixer.c:2812 msgid "Analog Mono Output" msgstr "Uscita mono analogica" #: spa/plugins/alsa/acp/alsa-mixer.c:2813 msgid "Speakers" msgstr "Altoparlanti" #: spa/plugins/alsa/acp/alsa-mixer.c:2814 msgid "HDMI / DisplayPort" msgstr "HDMI / DisplayPort" #: spa/plugins/alsa/acp/alsa-mixer.c:2815 msgid "Digital Output (S/PDIF)" msgstr "Uscita digitale (S/PDIF)" #: spa/plugins/alsa/acp/alsa-mixer.c:2816 msgid "Digital Input (S/PDIF)" msgstr "Ingresso digitale (S/PDIF)" #: spa/plugins/alsa/acp/alsa-mixer.c:2817 msgid "Multichannel Input" msgstr "Ingresso multi canale" #: spa/plugins/alsa/acp/alsa-mixer.c:2818 msgid "Multichannel Output" msgstr "Uscita multi canale" #: spa/plugins/alsa/acp/alsa-mixer.c:2819 msgid "Game Output" msgstr "Uscita gioco" #: spa/plugins/alsa/acp/alsa-mixer.c:2820 #: spa/plugins/alsa/acp/alsa-mixer.c:2821 msgid "Chat Output" msgstr "Uscita conversazione" #: spa/plugins/alsa/acp/alsa-mixer.c:2822 msgid "Chat Input" msgstr "Ingresso Chat" #: spa/plugins/alsa/acp/alsa-mixer.c:2823 msgid "Virtual Surround 7.1" msgstr "Virtual Sorround 7.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4527 msgid "Analog Mono" msgstr "Mono analogico" #: spa/plugins/alsa/acp/alsa-mixer.c:4528 msgid "Analog Mono (Left)" msgstr "Mono analogico (Sinistra)" #: spa/plugins/alsa/acp/alsa-mixer.c:4529 msgid "Analog Mono (Right)" msgstr "Mono analogico (Destra)" #. Note: Not translated to "Analog Stereo Input", because the source #. * name gets "Input" appended to it automatically, so adding "Input" #. * here would lead to the source name to become "Analog Stereo Input #. * Input". The same logic applies to analog-stereo-output, #. * multichannel-input and multichannel-output. #: spa/plugins/alsa/acp/alsa-mixer.c:4530 #: spa/plugins/alsa/acp/alsa-mixer.c:4538 #: spa/plugins/alsa/acp/alsa-mixer.c:4539 msgid "Analog Stereo" msgstr "Stereo analogico" #: spa/plugins/alsa/acp/alsa-mixer.c:4531 msgid "Mono" msgstr "Mono" #: spa/plugins/alsa/acp/alsa-mixer.c:4532 msgid "Stereo" msgstr "Stereo" #: spa/plugins/alsa/acp/alsa-mixer.c:4540 #: spa/plugins/alsa/acp/alsa-mixer.c:4698 #: spa/plugins/bluez5/bluez5-device.c:1135 msgid "Headset" msgstr "Cuffie con microfono" #: spa/plugins/alsa/acp/alsa-mixer.c:4541 #: spa/plugins/alsa/acp/alsa-mixer.c:4699 msgid "Speakerphone" msgstr "Vivavoce" #: spa/plugins/alsa/acp/alsa-mixer.c:4542 #: spa/plugins/alsa/acp/alsa-mixer.c:4543 msgid "Multichannel" msgstr "Multicanale" #: spa/plugins/alsa/acp/alsa-mixer.c:4544 msgid "Analog Surround 2.1" msgstr "Surround analogico 2.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4545 msgid "Analog Surround 3.0" msgstr "Surround analogico 3.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4546 msgid "Analog Surround 3.1" msgstr "Surround analogico 3.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4547 msgid "Analog Surround 4.0" msgstr "Surround analogico 4.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4548 msgid "Analog Surround 4.1" msgstr "Surround analogico 4.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4549 msgid "Analog Surround 5.0" msgstr "Surround analogico 5.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4550 msgid "Analog Surround 5.1" msgstr "Surround analogico 5.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4551 msgid "Analog Surround 6.0" msgstr "Surround analogico 6.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4552 msgid "Analog Surround 6.1" msgstr "Surround analogico 6.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4553 msgid "Analog Surround 7.0" msgstr "Surround analogico 7.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4554 msgid "Analog Surround 7.1" msgstr "Surround analogico 7.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4555 msgid "Digital Stereo (IEC958)" msgstr "Stereo digitale (IEC958)" #: spa/plugins/alsa/acp/alsa-mixer.c:4556 msgid "Digital Surround 4.0 (IEC958/AC3)" msgstr "Surround digitale 4.0 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4557 msgid "Digital Surround 5.1 (IEC958/AC3)" msgstr "Surround digitale 5.1 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4558 msgid "Digital Surround 5.1 (IEC958/DTS)" msgstr "Surround digitale 5.1 (IEC958/DTS)" #: spa/plugins/alsa/acp/alsa-mixer.c:4559 msgid "Digital Stereo (HDMI)" msgstr "Stereo digitale (HDMI)" #: spa/plugins/alsa/acp/alsa-mixer.c:4560 msgid "Digital Surround 5.1 (HDMI)" msgstr "Surround digitale 5.1 (HDMI)" #: spa/plugins/alsa/acp/alsa-mixer.c:4561 msgid "Chat" msgstr "Chat" #: spa/plugins/alsa/acp/alsa-mixer.c:4562 msgid "Game" msgstr "Gioco" #: spa/plugins/alsa/acp/alsa-mixer.c:4696 msgid "Analog Mono Duplex" msgstr "Duplex mono analogico" #: spa/plugins/alsa/acp/alsa-mixer.c:4697 msgid "Analog Stereo Duplex" msgstr "Duplex stereo analogico" #: spa/plugins/alsa/acp/alsa-mixer.c:4700 msgid "Digital Stereo Duplex (IEC958)" msgstr "Duplex stereo digitale (IEC958)" #: spa/plugins/alsa/acp/alsa-mixer.c:4701 msgid "Multichannel Duplex" msgstr "Duplex multicanale" #: spa/plugins/alsa/acp/alsa-mixer.c:4702 msgid "Stereo Duplex" msgstr "Duplex stereo" #: spa/plugins/alsa/acp/alsa-mixer.c:4703 msgid "Mono Chat + 7.1 Surround" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4806 #, c-format msgid "%s Output" msgstr "Uscita «%s»" #: spa/plugins/alsa/acp/alsa-mixer.c:4813 #, c-format msgid "%s Input" msgstr "Ingresso «%s»" #: spa/plugins/alsa/acp/alsa-util.c:1175 spa/plugins/alsa/acp/alsa-util.c:1269 #, c-format msgid "" "snd_pcm_avail() returned a value that is exceptionally large: %lu byte (%lu " "ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_avail() returned a value that is exceptionally large: %lu bytes (%lu " "ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "snd_pcm_avail() ha restituito un valore molto grande: %lu byte (%lu ms).\n" "Molto probabilmente si tratta di un bug nel driver ALSA «%s». Segnalare " "questo problema ai suoi sviluppatori." msgstr[1] "" "snd_pcm_avail() ha restituito un valore molto grande: %lu byte (%lu ms).\n" "Molto probabilmente si tratta di un bug nel driver ALSA «%s». Segnalare " "questo problema ai suoi sviluppatori." #: spa/plugins/alsa/acp/alsa-util.c:1241 #, c-format msgid "" "snd_pcm_delay() returned a value that is exceptionally large: %li byte (%s" "%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_delay() returned a value that is exceptionally large: %li bytes (%s" "%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "snd_pcm_delay() ha restituito un valore molto grande: %li byte (%s%lu ms).\n" "Molto probabilmente si tratta di un bug nel driver ALSA «%s». Segnalare " "questo problema ai suoi sviluppatori." msgstr[1] "" "snd_pcm_delay() ha restituito un valore molto grande: %li byte (%s%lu ms).\n" "Molto probabilmente si tratta di un bug nel driver ALSA «%s». Segnalare " "questo problema ai suoi sviluppatori." #: spa/plugins/alsa/acp/alsa-util.c:1288 #, c-format msgid "" "snd_pcm_avail_delay() returned strange values: delay %lu is less than avail " "%lu.\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr "" "snd_pcm_avail() ha restituito dei valori strani: delay %lu è minore di avail " "%lu.\n" "Molto probabilmente si tratta di un bug nel driver ALSA «%s». Segnalare " "questo problema ai suoi sviluppatori." #: spa/plugins/alsa/acp/alsa-util.c:1331 #, c-format msgid "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu byte " "(%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu bytes " "(%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "snd_pcm_mmap_begin() ha restituito un valore molto grande: %lu byte (%lu " "ms).\n" "Molto probabilmente si tratta di un bug nel driver ALSA «%s». Segnalare " "questo problema ai suoi sviluppatori." msgstr[1] "" "snd_pcm_mmap_begin() ha restituito un valore molto grande: %lu byte (%lu " "ms).\n" "Molto probabilmente si tratta di un bug nel driver ALSA «%s». Segnalare " "questo problema ai suoi sviluppatori." #: spa/plugins/bluez5/bluez5-device.c:1010 msgid "Audio Gateway (A2DP Source & HSP/HFP AG)" msgstr "Gateway Audio (Sorgente A2DP & HSP/HFP AG)" #: spa/plugins/bluez5/bluez5-device.c:1033 #, c-format msgid "High Fidelity Playback (A2DP Sink, codec %s)" msgstr "Riproduzione ad alta fedeltà (A2DP Sink, codec %s)" #: spa/plugins/bluez5/bluez5-device.c:1035 #, c-format msgid "High Fidelity Duplex (A2DP Source/Sink, codec %s)" msgstr "Duplex ad alta fedeltà (Sorgente/Sink, A2DP codec %s)" #: spa/plugins/bluez5/bluez5-device.c:1041 msgid "High Fidelity Playback (A2DP Sink)" msgstr "Riproduzione ad alta fedeltà (A2DP Sink)" #: spa/plugins/bluez5/bluez5-device.c:1043 msgid "High Fidelity Duplex (A2DP Source/Sink)" msgstr "Duplex ad alta fedeltà (A2DP Source/Sink)" #: spa/plugins/bluez5/bluez5-device.c:1070 #, c-format msgid "Headset Head Unit (HSP/HFP, codec %s)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1074 msgid "Headset Head Unit (HSP/HFP)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1140 msgid "Handsfree" msgstr "Vivavoce" #: spa/plugins/bluez5/bluez5-device.c:1155 msgid "Headphone" msgstr "Cuffie" #: spa/plugins/bluez5/bluez5-device.c:1160 msgid "Portable" msgstr "Portabile" #: spa/plugins/bluez5/bluez5-device.c:1165 msgid "Car" msgstr "Automobile" #: spa/plugins/bluez5/bluez5-device.c:1170 msgid "HiFi" msgstr "HiFi" #: spa/plugins/bluez5/bluez5-device.c:1175 msgid "Phone" msgstr "Telefono" #: spa/plugins/bluez5/bluez5-device.c:1181 msgid "Bluetooth" msgstr "Bluetooth" pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/po/ja.po000066400000000000000000000421461511204443500220550ustar00rootroot00000000000000# translation of ja.po to Japanese # PipeWire # Copyright (C) 2009. # This file is distributed under the same license as the PACKAGE package. # # Hyu_gabaru Ryu_ichi , 2009. # Kiyoto Hashida , 2009, 2012. # Kenzo Moriguchi , 2016. #zanata # Ooyama Yosiyuki , 2016. #zanata # Wim Taymans , 2016. #zanata msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/pipewire/" "issues/new\n" "POT-Creation-Date: 2021-04-18 16:54+0800\n" "PO-Revision-Date: 2016-03-15 08:25+0000\n" "Last-Translator: Kenzo Moriguchi \n" "Language-Team: Japanese \n" "Language: ja\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Generator: Zanata 4.6.2\n" "Plural-Forms: Plural-Forms: nplurals=1; plural=0;\n" #: src/daemon/pipewire.c:43 #, c-format msgid "" "%s [options]\n" " -h, --help Show this help\n" " --version Show version\n" " -c, --config Load config (Default %s)\n" msgstr "" #: src/daemon/pipewire.desktop.in:4 msgid "PipeWire Media System" msgstr "" #: src/daemon/pipewire.desktop.in:5 msgid "Start the PipeWire Media System" msgstr "" #: src/examples/media-session/alsa-monitor.c:526 #: spa/plugins/alsa/acp/compat.c:187 msgid "Built-in Audio" msgstr "内部オーディオ" #: src/examples/media-session/alsa-monitor.c:530 #: spa/plugins/alsa/acp/compat.c:192 msgid "Modem" msgstr "モデム" #: src/examples/media-session/alsa-monitor.c:539 msgid "Unknown device" msgstr "" #: src/tools/pw-cat.c:991 #, c-format msgid "" "%s [options] \n" " -h, --help Show this help\n" " --version Show version\n" " -v, --verbose Enable verbose operations\n" "\n" msgstr "" #: src/tools/pw-cat.c:998 #, c-format msgid "" " -R, --remote Remote daemon name\n" " --media-type Set media type (default %s)\n" " --media-category Set media category (default %s)\n" " --media-role Set media role (default %s)\n" " --target Set node target (default %s)\n" " 0 means don't link\n" " --latency Set node latency (default %s)\n" " Xunit (unit = s, ms, us, ns)\n" " or direct samples (256)\n" " the rate is the one of the source " "file\n" " --list-targets List available targets for --target\n" "\n" msgstr "" #: src/tools/pw-cat.c:1016 #, c-format msgid "" " --rate Sample rate (req. for rec) (default " "%u)\n" " --channels Number of channels (req. for rec) " "(default %u)\n" " --channel-map Channel map\n" " one of: \"stereo\", " "\"surround-51\",... or\n" " comma separated list of channel " "names: eg. \"FL,FR\"\n" " --format Sample format %s (req. for rec) " "(default %s)\n" " --volume Stream volume 0-1.0 (default %.3f)\n" " -q --quality Resampler quality (0 - 15) (default " "%d)\n" "\n" msgstr "" #: src/tools/pw-cat.c:1033 msgid "" " -p, --playback Playback mode\n" " -r, --record Recording mode\n" " -m, --midi Midi mode\n" "\n" msgstr "" #: src/tools/pw-cli.c:2932 #, c-format msgid "" "%s [options] [command]\n" " -h, --help Show this help\n" " --version Show version\n" " -d, --daemon Start as daemon (Default false)\n" " -r, --remote Remote daemon name\n" "\n" msgstr "" #: spa/plugins/alsa/acp/acp.c:290 msgid "Pro Audio" msgstr "" #: spa/plugins/alsa/acp/acp.c:411 spa/plugins/alsa/acp/alsa-mixer.c:4704 #: spa/plugins/bluez5/bluez5-device.c:1000 msgid "Off" msgstr "オフ" #: spa/plugins/alsa/acp/channelmap.h:466 msgid "(invalid)" msgstr "無効)" #: spa/plugins/alsa/acp/alsa-mixer.c:2709 msgid "Input" msgstr "入力" #: spa/plugins/alsa/acp/alsa-mixer.c:2710 msgid "Docking Station Input" msgstr "ドッキングステーション入力" #: spa/plugins/alsa/acp/alsa-mixer.c:2711 msgid "Docking Station Microphone" msgstr "ドッキングステーションマイクロフォン" #: spa/plugins/alsa/acp/alsa-mixer.c:2712 msgid "Docking Station Line In" msgstr "ドッキングステーションライン入力" #: spa/plugins/alsa/acp/alsa-mixer.c:2713 #: spa/plugins/alsa/acp/alsa-mixer.c:2804 msgid "Line In" msgstr "ラインイン" #: spa/plugins/alsa/acp/alsa-mixer.c:2714 #: spa/plugins/alsa/acp/alsa-mixer.c:2798 #: spa/plugins/bluez5/bluez5-device.c:1145 msgid "Microphone" msgstr "マイクロフォン" #: spa/plugins/alsa/acp/alsa-mixer.c:2715 #: spa/plugins/alsa/acp/alsa-mixer.c:2799 msgid "Front Microphone" msgstr "フロントマイクロフォン" #: spa/plugins/alsa/acp/alsa-mixer.c:2716 #: spa/plugins/alsa/acp/alsa-mixer.c:2800 msgid "Rear Microphone" msgstr "リアマイクロフォン" #: spa/plugins/alsa/acp/alsa-mixer.c:2717 msgid "External Microphone" msgstr "外部マイクロフォン" #: spa/plugins/alsa/acp/alsa-mixer.c:2718 #: spa/plugins/alsa/acp/alsa-mixer.c:2802 msgid "Internal Microphone" msgstr "内部マイクロフォン" #: spa/plugins/alsa/acp/alsa-mixer.c:2719 #: spa/plugins/alsa/acp/alsa-mixer.c:2805 msgid "Radio" msgstr "ラジオ" #: spa/plugins/alsa/acp/alsa-mixer.c:2720 #: spa/plugins/alsa/acp/alsa-mixer.c:2806 msgid "Video" msgstr "ビデオ" #: spa/plugins/alsa/acp/alsa-mixer.c:2721 msgid "Automatic Gain Control" msgstr "自動ゲイン制御" #: spa/plugins/alsa/acp/alsa-mixer.c:2722 msgid "No Automatic Gain Control" msgstr "自動ゲイン制御なし" #: spa/plugins/alsa/acp/alsa-mixer.c:2723 msgid "Boost" msgstr "ブースト" #: spa/plugins/alsa/acp/alsa-mixer.c:2724 msgid "No Boost" msgstr "ブーストなし" #: spa/plugins/alsa/acp/alsa-mixer.c:2725 msgid "Amplifier" msgstr "アンプ" #: spa/plugins/alsa/acp/alsa-mixer.c:2726 msgid "No Amplifier" msgstr "アンプなし" #: spa/plugins/alsa/acp/alsa-mixer.c:2727 msgid "Bass Boost" msgstr "低音ブースト" #: spa/plugins/alsa/acp/alsa-mixer.c:2728 msgid "No Bass Boost" msgstr "低音ブーストなし" #: spa/plugins/alsa/acp/alsa-mixer.c:2729 #: spa/plugins/bluez5/bluez5-device.c:1150 msgid "Speaker" msgstr "スピーカー" #: spa/plugins/alsa/acp/alsa-mixer.c:2730 #: spa/plugins/alsa/acp/alsa-mixer.c:2808 msgid "Headphones" msgstr "アナログヘッドフォン" #: spa/plugins/alsa/acp/alsa-mixer.c:2797 msgid "Analog Input" msgstr "アナログ入力" #: spa/plugins/alsa/acp/alsa-mixer.c:2801 msgid "Dock Microphone" msgstr "ドッキングステーションマイクロフォン" #: spa/plugins/alsa/acp/alsa-mixer.c:2803 msgid "Headset Microphone" msgstr "ヘッドセットマイクロフォン" #: spa/plugins/alsa/acp/alsa-mixer.c:2807 msgid "Analog Output" msgstr "アナログ出力" #: spa/plugins/alsa/acp/alsa-mixer.c:2809 #, fuzzy msgid "Headphones 2" msgstr "アナログヘッドフォン" #: spa/plugins/alsa/acp/alsa-mixer.c:2810 #, fuzzy msgid "Headphones Mono Output" msgstr "アナログモノ出力" #: spa/plugins/alsa/acp/alsa-mixer.c:2811 msgid "Line Out" msgstr "ライン出力" #: spa/plugins/alsa/acp/alsa-mixer.c:2812 msgid "Analog Mono Output" msgstr "アナログモノ出力" #: spa/plugins/alsa/acp/alsa-mixer.c:2813 msgid "Speakers" msgstr "スピーカー" #: spa/plugins/alsa/acp/alsa-mixer.c:2814 msgid "HDMI / DisplayPort" msgstr "HDMI / DisplayPort" #: spa/plugins/alsa/acp/alsa-mixer.c:2815 msgid "Digital Output (S/PDIF)" msgstr "デジタル出力 (S/PDIF)" #: spa/plugins/alsa/acp/alsa-mixer.c:2816 msgid "Digital Input (S/PDIF)" msgstr "デジタル入力 (S/PDIF)" #: spa/plugins/alsa/acp/alsa-mixer.c:2817 #, fuzzy msgid "Multichannel Input" msgstr "マルチチャネル" #: spa/plugins/alsa/acp/alsa-mixer.c:2818 #, fuzzy msgid "Multichannel Output" msgstr "マルチチャネル" #: spa/plugins/alsa/acp/alsa-mixer.c:2819 #, fuzzy msgid "Game Output" msgstr "%s 出力" #: spa/plugins/alsa/acp/alsa-mixer.c:2820 #: spa/plugins/alsa/acp/alsa-mixer.c:2821 #, fuzzy msgid "Chat Output" msgstr "%s 出力" #: spa/plugins/alsa/acp/alsa-mixer.c:2822 #, fuzzy msgid "Chat Input" msgstr "%s 入力" #: spa/plugins/alsa/acp/alsa-mixer.c:2823 #, fuzzy msgid "Virtual Surround 7.1" msgstr "仮想サラウンドシンク" #: spa/plugins/alsa/acp/alsa-mixer.c:4527 msgid "Analog Mono" msgstr "アナログモノ" #: spa/plugins/alsa/acp/alsa-mixer.c:4528 #, fuzzy msgid "Analog Mono (Left)" msgstr "アナログモノ" #: spa/plugins/alsa/acp/alsa-mixer.c:4529 #, fuzzy msgid "Analog Mono (Right)" msgstr "アナログモノ" #. Note: Not translated to "Analog Stereo Input", because the source #. * name gets "Input" appended to it automatically, so adding "Input" #. * here would lead to the source name to become "Analog Stereo Input #. * Input". The same logic applies to analog-stereo-output, #. * multichannel-input and multichannel-output. #: spa/plugins/alsa/acp/alsa-mixer.c:4530 #: spa/plugins/alsa/acp/alsa-mixer.c:4538 #: spa/plugins/alsa/acp/alsa-mixer.c:4539 msgid "Analog Stereo" msgstr "アナログステレオ" #: spa/plugins/alsa/acp/alsa-mixer.c:4531 msgid "Mono" msgstr "モノ" #: spa/plugins/alsa/acp/alsa-mixer.c:4532 msgid "Stereo" msgstr "ステレオ" #: spa/plugins/alsa/acp/alsa-mixer.c:4540 #: spa/plugins/alsa/acp/alsa-mixer.c:4698 #: spa/plugins/bluez5/bluez5-device.c:1135 msgid "Headset" msgstr "ヘッドセット" #: spa/plugins/alsa/acp/alsa-mixer.c:4541 #: spa/plugins/alsa/acp/alsa-mixer.c:4699 #, fuzzy msgid "Speakerphone" msgstr "スピーカー" #: spa/plugins/alsa/acp/alsa-mixer.c:4542 #: spa/plugins/alsa/acp/alsa-mixer.c:4543 msgid "Multichannel" msgstr "マルチチャネル" #: spa/plugins/alsa/acp/alsa-mixer.c:4544 msgid "Analog Surround 2.1" msgstr "アナログサラウンド 2.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4545 msgid "Analog Surround 3.0" msgstr "アナログサラウンド 3.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4546 msgid "Analog Surround 3.1" msgstr "アナログサラウンド 3.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4547 msgid "Analog Surround 4.0" msgstr "アナログサラウンド 4.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4548 msgid "Analog Surround 4.1" msgstr "アナログサラウンド 4.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4549 msgid "Analog Surround 5.0" msgstr "アナログサラウンド 5.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4550 msgid "Analog Surround 5.1" msgstr "アナログサラウンド 5.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4551 msgid "Analog Surround 6.0" msgstr "アナログサラウンド 6.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4552 msgid "Analog Surround 6.1" msgstr "アナログサラウンド 6.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4553 msgid "Analog Surround 7.0" msgstr "アナログサラウンド 7.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4554 msgid "Analog Surround 7.1" msgstr "アナログサラウンド 7.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4555 msgid "Digital Stereo (IEC958)" msgstr "デジタルステレオ (IEC958)" #: spa/plugins/alsa/acp/alsa-mixer.c:4556 msgid "Digital Surround 4.0 (IEC958/AC3)" msgstr "デジタルサラウンド 4.0 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4557 msgid "Digital Surround 5.1 (IEC958/AC3)" msgstr "デジタルサラウンド 5.1 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4558 msgid "Digital Surround 5.1 (IEC958/DTS)" msgstr "デジタルサラウンド 5.1 (IEC958/DTS)" #: spa/plugins/alsa/acp/alsa-mixer.c:4559 msgid "Digital Stereo (HDMI)" msgstr "デジタルステレオ (HDMI)" #: spa/plugins/alsa/acp/alsa-mixer.c:4560 msgid "Digital Surround 5.1 (HDMI)" msgstr "デジタルサラウンド 5.1 (HDMI)" #: spa/plugins/alsa/acp/alsa-mixer.c:4561 msgid "Chat" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4562 msgid "Game" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4696 msgid "Analog Mono Duplex" msgstr "アナログモノデュプレックス" #: spa/plugins/alsa/acp/alsa-mixer.c:4697 msgid "Analog Stereo Duplex" msgstr "アナログステレオデュプレックス" #: spa/plugins/alsa/acp/alsa-mixer.c:4700 msgid "Digital Stereo Duplex (IEC958)" msgstr "デジタルステレオデュプレックス (IEC958)" #: spa/plugins/alsa/acp/alsa-mixer.c:4701 #, fuzzy msgid "Multichannel Duplex" msgstr "マルチチャネル" #: spa/plugins/alsa/acp/alsa-mixer.c:4702 #, fuzzy msgid "Stereo Duplex" msgstr "アナログステレオデュプレックス" #: spa/plugins/alsa/acp/alsa-mixer.c:4703 msgid "Mono Chat + 7.1 Surround" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4806 #, c-format msgid "%s Output" msgstr "%s 出力" #: spa/plugins/alsa/acp/alsa-mixer.c:4813 #, c-format msgid "%s Input" msgstr "%s 入力" #: spa/plugins/alsa/acp/alsa-util.c:1175 spa/plugins/alsa/acp/alsa-util.c:1269 #, fuzzy, c-format msgid "" "snd_pcm_avail() returned a value that is exceptionally large: %lu byte (%lu " "ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_avail() returned a value that is exceptionally large: %lu bytes (%lu " "ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "snd_pcm_avail() は 例外的に大きな値を返しました: %lu バイト(%lu ms)。\n" "これは多分、ALSA ドライバー '%s' 内のバグです。この問題は ALSA 開発者宛に報告" "を提出して下さい。" #: spa/plugins/alsa/acp/alsa-util.c:1241 #, fuzzy, c-format msgid "" "snd_pcm_delay() returned a value that is exceptionally large: %li byte (%s" "%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_delay() returned a value that is exceptionally large: %li bytes (%s" "%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "snd_pcm_delay() は 例外的に大きな値を返しました: %li バイト(%s%lu ms)。\n" "これは多分、ALSA ドライバー '%s' 内のバグです。この問題は ALSA 開発者宛に報告" "を提出して下さい。" #: spa/plugins/alsa/acp/alsa-util.c:1288 #, c-format msgid "" "snd_pcm_avail_delay() returned strange values: delay %lu is less than avail " "%lu.\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr "" "snd_pcm_avail_delay() がおかしな値を返しました: 遅延 %lu は有効な値 %lu 未満" "です。\n" "これは多分、ALSA ドライバー '%s' 内のバグです。この問題は ALSA 開発者宛に報告" "を提出して下さい。" #: spa/plugins/alsa/acp/alsa-util.c:1331 #, fuzzy, c-format msgid "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu byte " "(%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu bytes " "(%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "snd_pcm_mmap_begin() は 例外的に大きな値を返しました: %lu バイト(%lu " "ms)。\n" "これは多分、ALSA ドライバー '%s' 内のバグです。この問題は ALSA 開発者宛に報告" "を提出して下さい。" #: spa/plugins/bluez5/bluez5-device.c:1010 msgid "Audio Gateway (A2DP Source & HSP/HFP AG)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1033 #, c-format msgid "High Fidelity Playback (A2DP Sink, codec %s)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1035 #, c-format msgid "High Fidelity Duplex (A2DP Source/Sink, codec %s)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1041 msgid "High Fidelity Playback (A2DP Sink)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1043 msgid "High Fidelity Duplex (A2DP Source/Sink)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1070 #, c-format msgid "Headset Head Unit (HSP/HFP, codec %s)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1074 msgid "Headset Head Unit (HSP/HFP)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1140 msgid "Handsfree" msgstr "ハンズフリー" #: spa/plugins/bluez5/bluez5-device.c:1155 msgid "Headphone" msgstr "ヘッドフォン" #: spa/plugins/bluez5/bluez5-device.c:1160 msgid "Portable" msgstr "ポータブル" #: spa/plugins/bluez5/bluez5-device.c:1165 msgid "Car" msgstr "車" #: spa/plugins/bluez5/bluez5-device.c:1170 msgid "HiFi" msgstr "HiFi" #: spa/plugins/bluez5/bluez5-device.c:1175 msgid "Phone" msgstr "電話" #: spa/plugins/bluez5/bluez5-device.c:1181 #, fuzzy msgid "Bluetooth" msgstr "Bluetooth 入力" pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/po/ka.po000066400000000000000000000702641511204443500220600ustar00rootroot00000000000000# Georgian translation of pipewire # Copyright (C) 2023 pipewire's authors # This file is distributed under the same license as the pipewire package. # Temuri Doghonadze , 2023. # msgid "" msgstr "" "Project-Id-Version: pipewire\n" "Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/pipewire/" "issues/new\n" "POT-Creation-Date: 2023-08-07 22:01+0200\n" "PO-Revision-Date: 2023-08-07 22:06+0200\n" "Last-Translator: Temuri Doghonadze \n" "Language-Team: Georgian <(nothing)>\n" "Language: ka\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Generator: Poedit 3.3.2\n" #: src/daemon/pipewire.c:26 #, c-format msgid "" "%s [options]\n" " -h, --help Show this help\n" " --version Show version\n" " -c, --config Load config (Default %s)\n" msgstr "" "%s [პარამეტრები]\n" " -h, --help ამ დახმარების ჩვენება\n" " --version ვერსიის ჩვენება\n" " -c, --config ჩატვირთვის კონფიგურაცია (ნაგულისხმები %s)\n" #: src/daemon/pipewire.desktop.in:4 msgid "PipeWire Media System" msgstr "PipeWire Media System" #: src/daemon/pipewire.desktop.in:5 msgid "Start the PipeWire Media System" msgstr "PipeWire Media System-ის გაშვება" #: src/modules/module-protocol-pulse/modules/module-tunnel-sink.c:141 #: src/modules/module-protocol-pulse/modules/module-tunnel-source.c:141 #, c-format msgid "Tunnel to %s%s%s" msgstr "გვირაბი %s%s%s-მდე" #: src/modules/module-fallback-sink.c:31 msgid "Dummy Output" msgstr "ნულოვანი გამოყვანა" #: src/modules/module-pulse-tunnel.c:847 #, c-format msgid "Tunnel for %s@%s" msgstr "გვირაბი %s@%s-სთვის" #: src/modules/module-zeroconf-discover.c:311 msgid "Unknown device" msgstr "უცნობი მოწყობილობა" #: src/modules/module-zeroconf-discover.c:323 #, c-format msgid "%s on %s@%s" msgstr "%s %s@%s -ზე" #: src/modules/module-zeroconf-discover.c:327 #, c-format msgid "%s on %s" msgstr "%s %s-ზე" #: src/tools/pw-cat.c:979 #, c-format msgid "" "%s [options] [|-]\n" " -h, --help Show this help\n" " --version Show version\n" " -v, --verbose Enable verbose operations\n" "\n" msgstr "" "%s [პარამეტრები] [<ფაილი>|-]\n" " -h, --help ამ დახმარების ჩვენება\n" " --version ვერსიის ჩვენება\n" " -v, --verbose დამატებითი შეტყობინებების გამოტანა\n" "\n" #: src/tools/pw-cat.c:986 #, c-format msgid "" " -R, --remote Remote daemon name\n" " --media-type Set media type (default %s)\n" " --media-category Set media category (default %s)\n" " --media-role Set media role (default %s)\n" " --target Set node target serial or name " "(default %s)\n" " 0 means don't link\n" " --latency Set node latency (default %s)\n" " Xunit (unit = s, ms, us, ns)\n" " or direct samples (256)\n" " the rate is the one of the source " "file\n" " -P --properties Set node properties\n" "\n" msgstr "" " -R, --remote დაშორებული დემონის სახელი\n" " --media-type მედიის ტიპის დაყენება (ნაგულისხმები %s)\n" " --media-category მედია კატეგორიის დაყენება (ნაგულისხმები %s)\n" " --media-role მედიის როლის დაყენება (ნაგულისხმები %s)\n" " --target კვანძის სამიზნის დაყენება (ნაგულისხმები %s)\n" " 0 ნიშნავს არ მიბმა\n" " --latency კვანძის შეყოვნების დაყენება (ნაგულისხმები %s)\n" " Xunit (ერთეული = s, ms, us, ns)\n" " ან პირდაპირი ნიმუშები (256)\n" " მაჩვენებელი არის ერთ-ერთი წყაროს " "ფაილი\n" " -P --properties კვანძის თვისებების დაყენება\n" #: src/tools/pw-cat.c:1004 #, c-format msgid "" " --rate Sample rate (req. for rec) (default " "%u)\n" " --channels Number of channels (req. for rec) " "(default %u)\n" " --channel-map Channel map\n" " one of: \"stereo\", " "\"surround-51\",... or\n" " comma separated list of channel " "names: eg. \"FL,FR\"\n" " --format Sample format %s (req. for rec) " "(default %s)\n" " --volume Stream volume 0-1.0 (default %.3f)\n" " -q --quality Resampler quality (0 - 15) (default " "%d)\n" "\n" msgstr "" " --rate სემპლის_სიჩქარე (მოთხოვნილება rec.) (ნაგულისხმები %u)\n" " --channels არხების რაოდენობა (მოთხოვნილი ჩანაწერისთვის) (ნაგულისხმები " "%u)\n" " --channel-map არხის რუკა\n" " ერთ-ერთი: \"stereo\", " "\"surround-51\",... ან\n" " მძიმით გამოყოფილი არხის " "სახელების სია: მაგ. \"FL, FR\"\n" " --format ნიმუშის ფორმატი %s (მოთხოვნილება rec.) " "(ნაგულისხმები %s)\n" " --volume ნაკადის მოცულობა 0-1.0 (ნაგულისხმები %.3f)\n" " -q --quality Resampler ხარისხი (0 - 15) " "(ნაგულისხმები %d)\n" #: src/tools/pw-cat.c:1021 msgid "" " -p, --playback Playback mode\n" " -r, --record Recording mode\n" " -m, --midi Midi mode\n" " -d, --dsd DSD mode\n" " -o, --encoded Encoded mode\n" "\n" msgstr "" " -p, --playback დაკვრის რეჟიმი\n" " -r, --record ჩაწერის რეჟიმი\n" " -m, --midi Midi რეჟიმი\n" " -d, --dsd DSD რეჟიმი\n" " -o, --encoded დაშიფრული რეჟიმი\n" "\n" #: src/tools/pw-cli.c:2220 #, c-format msgid "" "%s [options] [command]\n" " -h, --help Show this help\n" " --version Show version\n" " -d, --daemon Start as daemon (Default false)\n" " -r, --remote Remote daemon name\n" " -m, --monitor Monitor activity\n" "\n" msgstr "" "%s [პარამეტრები] [ბრძანება]\n" " -h, --help ამ დახმარების ჩვენება\n" " --version ვერსიის ჩვენება\n" " -d, --daemon დემონის სახით გაშვება (ნაგულისხმევად " "გამორთულია)\n" " -r, --remote დაშორებული დემონის სახელი\n" " -m, --monitor აქტივობის მონიტორინგი\n" "\n" #: spa/plugins/alsa/acp/acp.c:325 msgid "Pro Audio" msgstr "Pro Audio" #: spa/plugins/alsa/acp/acp.c:449 spa/plugins/alsa/acp/alsa-mixer.c:4648 #: spa/plugins/bluez5/bluez5-device.c:1586 msgid "Off" msgstr "გამორთულია" #: spa/plugins/alsa/acp/alsa-mixer.c:2652 msgid "Input" msgstr "შეყვანა" #: spa/plugins/alsa/acp/alsa-mixer.c:2653 msgid "Docking Station Input" msgstr "Docking Station-ის შეყვანა" #: spa/plugins/alsa/acp/alsa-mixer.c:2654 msgid "Docking Station Microphone" msgstr "Docking Station-ის მიკროფონი" #: spa/plugins/alsa/acp/alsa-mixer.c:2655 msgid "Docking Station Line In" msgstr "Docking Station-ის Line In პორტი" #: spa/plugins/alsa/acp/alsa-mixer.c:2656 #: spa/plugins/alsa/acp/alsa-mixer.c:2747 msgid "Line In" msgstr "Line In" #: spa/plugins/alsa/acp/alsa-mixer.c:2657 #: spa/plugins/alsa/acp/alsa-mixer.c:2741 #: spa/plugins/bluez5/bluez5-device.c:1831 msgid "Microphone" msgstr "მიკროფონი" #: spa/plugins/alsa/acp/alsa-mixer.c:2658 #: spa/plugins/alsa/acp/alsa-mixer.c:2742 msgid "Front Microphone" msgstr "წინა მიკროფონი" #: spa/plugins/alsa/acp/alsa-mixer.c:2659 #: spa/plugins/alsa/acp/alsa-mixer.c:2743 msgid "Rear Microphone" msgstr "უკანა მიკფოფონი" #: spa/plugins/alsa/acp/alsa-mixer.c:2660 msgid "External Microphone" msgstr "გარე მიკროფონი" #: spa/plugins/alsa/acp/alsa-mixer.c:2661 #: spa/plugins/alsa/acp/alsa-mixer.c:2745 msgid "Internal Microphone" msgstr "შიდა მიკროფონი" #: spa/plugins/alsa/acp/alsa-mixer.c:2662 #: spa/plugins/alsa/acp/alsa-mixer.c:2748 msgid "Radio" msgstr "რადიო" #: spa/plugins/alsa/acp/alsa-mixer.c:2663 #: spa/plugins/alsa/acp/alsa-mixer.c:2749 msgid "Video" msgstr "ვიდეო" #: spa/plugins/alsa/acp/alsa-mixer.c:2664 msgid "Automatic Gain Control" msgstr "ხმის მომატების ავტომატური კონტროლი" #: spa/plugins/alsa/acp/alsa-mixer.c:2665 msgid "No Automatic Gain Control" msgstr "ხმის მომატების ავტომატური კონტროლის გამორთვა" #: spa/plugins/alsa/acp/alsa-mixer.c:2666 msgid "Boost" msgstr "გაძლიერება" #: spa/plugins/alsa/acp/alsa-mixer.c:2667 msgid "No Boost" msgstr "გაძლიერების გარეშე" #: spa/plugins/alsa/acp/alsa-mixer.c:2668 msgid "Amplifier" msgstr "გამაძლიერებელი" #: spa/plugins/alsa/acp/alsa-mixer.c:2669 msgid "No Amplifier" msgstr "გამაძლიერებლის გარეშე" #: spa/plugins/alsa/acp/alsa-mixer.c:2670 msgid "Bass Boost" msgstr "Bass-ის გაძლიერება" #: spa/plugins/alsa/acp/alsa-mixer.c:2671 msgid "No Bass Boost" msgstr "Bass-ის გაძლიერების გარეშე" #: spa/plugins/alsa/acp/alsa-mixer.c:2672 #: spa/plugins/bluez5/bluez5-device.c:1837 msgid "Speaker" msgstr "დინამიკი" #: spa/plugins/alsa/acp/alsa-mixer.c:2673 #: spa/plugins/alsa/acp/alsa-mixer.c:2751 msgid "Headphones" msgstr "ყურსაცვამები" #: spa/plugins/alsa/acp/alsa-mixer.c:2740 msgid "Analog Input" msgstr "ანალოგური შეყვანა" #: spa/plugins/alsa/acp/alsa-mixer.c:2744 msgid "Dock Microphone" msgstr "მისამაგრებელი მიკროფონი" #: spa/plugins/alsa/acp/alsa-mixer.c:2746 msgid "Headset Microphone" msgstr "ყურსაცვამის მიროფონი" #: spa/plugins/alsa/acp/alsa-mixer.c:2750 msgid "Analog Output" msgstr "ანალოგური გამოტანა" #: spa/plugins/alsa/acp/alsa-mixer.c:2752 msgid "Headphones 2" msgstr "ყურსაცვამები 2" #: spa/plugins/alsa/acp/alsa-mixer.c:2753 msgid "Headphones Mono Output" msgstr "ყურსაცვამები მონო" #: spa/plugins/alsa/acp/alsa-mixer.c:2754 msgid "Line Out" msgstr "ხაზოვანი გამოყვანა" #: spa/plugins/alsa/acp/alsa-mixer.c:2755 msgid "Analog Mono Output" msgstr "ანალოგური მონო გამოყვანა" #: spa/plugins/alsa/acp/alsa-mixer.c:2756 msgid "Speakers" msgstr "დინამიკები" #: spa/plugins/alsa/acp/alsa-mixer.c:2757 msgid "HDMI / DisplayPort" msgstr "HDMI / DisplayPort" #: spa/plugins/alsa/acp/alsa-mixer.c:2758 msgid "Digital Output (S/PDIF)" msgstr "ციფრული გამოყვანა (S/PDIF)" #: spa/plugins/alsa/acp/alsa-mixer.c:2759 msgid "Digital Input (S/PDIF)" msgstr "ციფრული შეტანა (S/PDIF)" #: spa/plugins/alsa/acp/alsa-mixer.c:2760 msgid "Multichannel Input" msgstr "მრავალარხიანი შეყვანა" #: spa/plugins/alsa/acp/alsa-mixer.c:2761 msgid "Multichannel Output" msgstr "მრავალარხიანი გამოყვანა" #: spa/plugins/alsa/acp/alsa-mixer.c:2762 msgid "Game Output" msgstr "თამაშის გამოყვანა" #: spa/plugins/alsa/acp/alsa-mixer.c:2763 #: spa/plugins/alsa/acp/alsa-mixer.c:2764 msgid "Chat Output" msgstr "ჩატის გამოყვანა" #: spa/plugins/alsa/acp/alsa-mixer.c:2765 msgid "Chat Input" msgstr "ჩატის შეყვანა" #: spa/plugins/alsa/acp/alsa-mixer.c:2766 msgid "Virtual Surround 7.1" msgstr "ვირტუალური სივრცითი ხმა 7.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4471 msgid "Analog Mono" msgstr "ანალოგური მონო" #: spa/plugins/alsa/acp/alsa-mixer.c:4472 msgid "Analog Mono (Left)" msgstr "ანალოგური მონო (მარცხენა)" #: spa/plugins/alsa/acp/alsa-mixer.c:4473 msgid "Analog Mono (Right)" msgstr "ანალოგური მონო (მარჯვენა)" #. Note: Not translated to "Analog Stereo Input", because the source #. * name gets "Input" appended to it automatically, so adding "Input" #. * here would lead to the source name to become "Analog Stereo Input #. * Input". The same logic applies to analog-stereo-output, #. * multichannel-input and multichannel-output. #: spa/plugins/alsa/acp/alsa-mixer.c:4474 #: spa/plugins/alsa/acp/alsa-mixer.c:4482 #: spa/plugins/alsa/acp/alsa-mixer.c:4483 msgid "Analog Stereo" msgstr "ანალოგური სტერეო" #: spa/plugins/alsa/acp/alsa-mixer.c:4475 msgid "Mono" msgstr "მონო" #: spa/plugins/alsa/acp/alsa-mixer.c:4476 msgid "Stereo" msgstr "სტერეო" #: spa/plugins/alsa/acp/alsa-mixer.c:4484 #: spa/plugins/alsa/acp/alsa-mixer.c:4642 #: spa/plugins/bluez5/bluez5-device.c:1819 msgid "Headset" msgstr "ყურსაცვამები & მიკროფონი" #: spa/plugins/alsa/acp/alsa-mixer.c:4485 #: spa/plugins/alsa/acp/alsa-mixer.c:4643 msgid "Speakerphone" msgstr "სამაგიდო დინამიკი" #: spa/plugins/alsa/acp/alsa-mixer.c:4486 #: spa/plugins/alsa/acp/alsa-mixer.c:4487 msgid "Multichannel" msgstr "მრავალარხიანი" #: spa/plugins/alsa/acp/alsa-mixer.c:4488 msgid "Analog Surround 2.1" msgstr "ანალოგური სივრცითი 2.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4489 msgid "Analog Surround 3.0" msgstr "ანალოგური სივრცითი 3.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4490 msgid "Analog Surround 3.1" msgstr "ანალოგური სივრცითი 3.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4491 msgid "Analog Surround 4.0" msgstr "ანალოგური სივრცითი 4.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4492 msgid "Analog Surround 4.1" msgstr "ანალოგური სივრცითი 4.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4493 msgid "Analog Surround 5.0" msgstr "ანალოგური სივრცითი 5.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4494 msgid "Analog Surround 5.1" msgstr "ანალოგური სივრცითი 5.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4495 msgid "Analog Surround 6.0" msgstr "ანალოგური სივრცითი 6.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4496 msgid "Analog Surround 6.1" msgstr "ანალოგური სივრცითი 6.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4497 msgid "Analog Surround 7.0" msgstr "ანალოგური სივრცითი 7.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4498 msgid "Analog Surround 7.1" msgstr "ანალოგური სივრცითი 7.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4499 msgid "Digital Stereo (IEC958)" msgstr "ციფრული სტერეო (IEC958)" #: spa/plugins/alsa/acp/alsa-mixer.c:4500 msgid "Digital Surround 4.0 (IEC958/AC3)" msgstr "ციფრული სივრცითი 4.0 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4501 msgid "Digital Surround 5.1 (IEC958/AC3)" msgstr "ციფრული სივრცითი 5.1 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4502 msgid "Digital Surround 5.1 (IEC958/DTS)" msgstr "ციფრული სივრცითი 5.1 (IEC958/DTS)" #: spa/plugins/alsa/acp/alsa-mixer.c:4503 msgid "Digital Stereo (HDMI)" msgstr "ციფრული სტერეო (HDMI)" #: spa/plugins/alsa/acp/alsa-mixer.c:4504 msgid "Digital Surround 5.1 (HDMI)" msgstr "ციფრული სივრცითი 5.1 (HDMI)" #: spa/plugins/alsa/acp/alsa-mixer.c:4505 msgid "Chat" msgstr "ჩატი" #: spa/plugins/alsa/acp/alsa-mixer.c:4506 msgid "Game" msgstr "თამაში" #: spa/plugins/alsa/acp/alsa-mixer.c:4640 msgid "Analog Mono Duplex" msgstr "ანალოგური მონო დუპლექსი" #: spa/plugins/alsa/acp/alsa-mixer.c:4641 msgid "Analog Stereo Duplex" msgstr "ანალოგური სტერეო დუპლექსი" #: spa/plugins/alsa/acp/alsa-mixer.c:4644 msgid "Digital Stereo Duplex (IEC958)" msgstr "ციფრული სტერეო დუპლექსი (IEC958)" #: spa/plugins/alsa/acp/alsa-mixer.c:4645 msgid "Multichannel Duplex" msgstr "მრავალარხიანი დუპლექსი" #: spa/plugins/alsa/acp/alsa-mixer.c:4646 msgid "Stereo Duplex" msgstr "სტერეო დუპლექსი" #: spa/plugins/alsa/acp/alsa-mixer.c:4647 msgid "Mono Chat + 7.1 Surround" msgstr "მონო ჩატი + 7.1 სივრცითი" #: spa/plugins/alsa/acp/alsa-mixer.c:4748 #, c-format msgid "%s Output" msgstr "%s გამოყვანა" #: spa/plugins/alsa/acp/alsa-mixer.c:4756 #, c-format msgid "%s Input" msgstr "%s შეყვანა" #: spa/plugins/alsa/acp/alsa-util.c:1187 spa/plugins/alsa/acp/alsa-util.c:1281 #, c-format msgid "" "snd_pcm_avail() returned a value that is exceptionally large: %lu byte (%lu " "ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_avail() returned a value that is exceptionally large: %lu bytes (%lu " "ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "snd_pcm_avail()-ის მიერ დაბრუნებული მნიშვნელობა არაჩვეულებრივად დიდია: %lu " "ბაიტი (%lu მწმ).\n" "ყველაზე ხშირად ეს ALSA-ს დრაივერის (%s) შეცდომის გამო ხდება. დაუკავშირდით " "ALSA-ის პროგრამისტებს." msgstr[1] "" "snd_pcm_avail()-ის მიერ დაბრუნებული მნიშვნელობა არაჩვეულებრივად დიდია: %lu " "ბაიტი (%lu მწმ).\n" "ყველაზე ხშირად ეს ALSA-ს დრაივერის (%s) შეცდომის გამო ხდება. დაუკავშირდით " "ALSA-ის პროგრამისტებს." #: spa/plugins/alsa/acp/alsa-util.c:1253 #, c-format msgid "" "snd_pcm_delay() returned a value that is exceptionally large: %li byte " "(%s%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_delay() returned a value that is exceptionally large: %li bytes " "(%s%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "snd_pcm_delay()-ის მიერ დაბრუნებული მნიშვნელობა არაჩვეულებრივად დიდია: %li " "ბაიტი (%s%lu მწმ).\n" "ყველაზე ხშირად ეს ALSA-ს დრაივერის (%s) შეცდომის გამო ხდება. დაუკავშირდით " "ALSA-ის პროგრამისტებს." msgstr[1] "" "snd_pcm_delay()-ის მიერ დაბრუნებული მნიშვნელობა არაჩვეულებრივად დიდია: %li " "ბაიტი (%s%lu მწმ).\n" "ყველაზე ხშირად ეს ALSA-ს დრაივერის (%s) შეცდომის გამო ხდება. დაუკავშირდით " "ALSA-ის პროგრამისტებს." #: spa/plugins/alsa/acp/alsa-util.c:1300 #, c-format msgid "" "snd_pcm_avail_delay() returned strange values: delay %lu is less than avail " "%lu.\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr "" "snd_pcm_avail_delay()-ის მიერ დაბრუნებული მნიშვნელობები უცნაურია: დაყოვნება " "%lu უფრო მცირეა, ვიდრე ხელმისაწვდომი დრო %lu.\n" "ყველაზე ხშირად ეს ALSA-ს დრაივერის (%s) შეცდომის გამო ხდება. დაუკავშირდით " "ALSA-ის პროგრამისტებს." #: spa/plugins/alsa/acp/alsa-util.c:1343 #, c-format msgid "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu byte " "(%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu bytes " "(%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "snd_pcm_mmap_begin()-ის მიერ დაბრუნებული მნიშვნელობა არაჩვეულებრივად დიდია: " "%lu ბაიტი (%lu მწმ).\n" "ყველაზე ხშირად ეს ALSA-ს დრაივერის (%s) შეცდომის გამო ხდება. დაუკავშირდით " "ALSA-ის პროგრამისტებს." msgstr[1] "" "snd_pcm_mmap_begin()-ის მიერ დაბრუნებული მნიშვნელობა არაჩვეულებრივად დიდია: " "%lu ბაიტი (%lu მწმ).\n" "ყველაზე ხშირად ეს ALSA-ს დრაივერის (%s) შეცდომის გამო ხდება. დაუკავშირდით " "ALSA-ის პროგრამისტებს." #: spa/plugins/alsa/acp/channelmap.h:457 msgid "(invalid)" msgstr "(არასწორი)" #: spa/plugins/alsa/acp/compat.c:189 msgid "Built-in Audio" msgstr "ჩაშენებული აუდიო" #: spa/plugins/alsa/acp/compat.c:194 msgid "Modem" msgstr "მოდემი" #: spa/plugins/bluez5/bluez5-device.c:1597 msgid "Audio Gateway (A2DP Source & HSP/HFP AG)" msgstr "Audio Gateway (A2DP წყარო & HSP/HFP AG)" #: spa/plugins/bluez5/bluez5-device.c:1622 #, c-format msgid "High Fidelity Playback (A2DP Sink, codec %s)" msgstr "მაღალი ხარისხის ხმა (A2DP Sink, კოდეკი %s)" #: spa/plugins/bluez5/bluez5-device.c:1625 #, c-format msgid "High Fidelity Duplex (A2DP Source/Sink, codec %s)" msgstr "მაღალი ხარისხის დუპლექსი (A2DP წყარო/Sink, კოდეკი %s)" #: spa/plugins/bluez5/bluez5-device.c:1633 msgid "High Fidelity Playback (A2DP Sink)" msgstr "მაღალი ხარისხის ხმა (A2DP Sink)" #: spa/plugins/bluez5/bluez5-device.c:1635 msgid "High Fidelity Duplex (A2DP Source/Sink)" msgstr "მაღალი ხარისხის დუპლექსი(A2DP წყარო/Sink)" #: spa/plugins/bluez5/bluez5-device.c:1677 #, c-format msgid "High Fidelity Playback (BAP Sink, codec %s)" msgstr "მაღალი ხარისხის დაკვრა (BAP Sink, კოდეკი %s)" #: spa/plugins/bluez5/bluez5-device.c:1681 #, c-format msgid "High Fidelity Input (BAP Source, codec %s)" msgstr "მაღალი ხარისხის შეყვანა (BAP წყარო, კოდეკი %s)" #: spa/plugins/bluez5/bluez5-device.c:1685 #, c-format msgid "High Fidelity Duplex (BAP Source/Sink, codec %s)" msgstr "მაღალი ხარისხის დუპლექსი (BAP წყარო/Sink, კოდეკი %s)" #: spa/plugins/bluez5/bluez5-device.c:1693 msgid "High Fidelity Playback (BAP Sink)" msgstr "მაღალი ხარისხის დაკვრა (BAP Sink)" #: spa/plugins/bluez5/bluez5-device.c:1696 msgid "High Fidelity Input (BAP Source)" msgstr "მაღალი ხარისხის შეყვანა (BAP წყარო)" #: spa/plugins/bluez5/bluez5-device.c:1699 msgid "High Fidelity Duplex (BAP Source/Sink)" msgstr "მაღალი ხარისხის დუპლექსი (BAP წყარო/Sink)" #: spa/plugins/bluez5/bluez5-device.c:1735 #, c-format msgid "Headset Head Unit (HSP/HFP, codec %s)" msgstr "Headset Head Unit (HSP/HFP, კოდეკი %s)" #: spa/plugins/bluez5/bluez5-device.c:1740 msgid "Headset Head Unit (HSP/HFP)" msgstr "Headset Head Unit (HSP/HFP)" #: spa/plugins/bluez5/bluez5-device.c:1820 #: spa/plugins/bluez5/bluez5-device.c:1825 #: spa/plugins/bluez5/bluez5-device.c:1832 #: spa/plugins/bluez5/bluez5-device.c:1838 #: spa/plugins/bluez5/bluez5-device.c:1844 #: spa/plugins/bluez5/bluez5-device.c:1850 #: spa/plugins/bluez5/bluez5-device.c:1856 #: spa/plugins/bluez5/bluez5-device.c:1862 #: spa/plugins/bluez5/bluez5-device.c:1868 msgid "Handsfree" msgstr "ხელის გარეშე სამართავი" #: spa/plugins/bluez5/bluez5-device.c:1826 msgid "Handsfree (HFP)" msgstr "ხელის გარეშე სამართავი (HFP)" #: spa/plugins/bluez5/bluez5-device.c:1843 msgid "Headphone" msgstr "ყურსაცვამი" #: spa/plugins/bluez5/bluez5-device.c:1849 msgid "Portable" msgstr "გადატანადი" #: spa/plugins/bluez5/bluez5-device.c:1855 msgid "Car" msgstr "მანქანა" #: spa/plugins/bluez5/bluez5-device.c:1861 msgid "HiFi" msgstr "HiFi" #: spa/plugins/bluez5/bluez5-device.c:1867 msgid "Phone" msgstr "ტელეფონი" #: spa/plugins/bluez5/bluez5-device.c:1874 msgid "Bluetooth" msgstr "ლურჯკბილა" #: spa/plugins/bluez5/bluez5-device.c:1875 msgid "Bluetooth (HFP)" msgstr "ბლუთუზი (HFP)" pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/po/kk.po000066400000000000000000000404161511204443500220660ustar00rootroot00000000000000# Kazakh translation of pipewire. # Copyright (C) 2020 The pipewire authors. # This file is distributed under the same license as the pipewire package. # Baurzhan Muftakhidinov , 2020. # msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/pipewire/" "issues/new\n" "POT-Creation-Date: 2021-04-18 16:54+0800\n" "PO-Revision-Date: 2020-06-30 08:04+0500\n" "Last-Translator: Baurzhan Muftakhidinov \n" "Language-Team: \n" "Language: kk\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Generator: Poedit 2.3.1\n" #: src/daemon/pipewire.c:43 #, c-format msgid "" "%s [options]\n" " -h, --help Show this help\n" " --version Show version\n" " -c, --config Load config (Default %s)\n" msgstr "" #: src/daemon/pipewire.desktop.in:4 msgid "PipeWire Media System" msgstr "" #: src/daemon/pipewire.desktop.in:5 msgid "Start the PipeWire Media System" msgstr "" #: src/examples/media-session/alsa-monitor.c:526 #: spa/plugins/alsa/acp/compat.c:187 msgid "Built-in Audio" msgstr "Құрамындағы аудио" #: src/examples/media-session/alsa-monitor.c:530 #: spa/plugins/alsa/acp/compat.c:192 msgid "Modem" msgstr "Модем" #: src/examples/media-session/alsa-monitor.c:539 msgid "Unknown device" msgstr "" #: src/tools/pw-cat.c:991 #, c-format msgid "" "%s [options] \n" " -h, --help Show this help\n" " --version Show version\n" " -v, --verbose Enable verbose operations\n" "\n" msgstr "" #: src/tools/pw-cat.c:998 #, c-format msgid "" " -R, --remote Remote daemon name\n" " --media-type Set media type (default %s)\n" " --media-category Set media category (default %s)\n" " --media-role Set media role (default %s)\n" " --target Set node target (default %s)\n" " 0 means don't link\n" " --latency Set node latency (default %s)\n" " Xunit (unit = s, ms, us, ns)\n" " or direct samples (256)\n" " the rate is the one of the source " "file\n" " --list-targets List available targets for --target\n" "\n" msgstr "" #: src/tools/pw-cat.c:1016 #, c-format msgid "" " --rate Sample rate (req. for rec) (default " "%u)\n" " --channels Number of channels (req. for rec) " "(default %u)\n" " --channel-map Channel map\n" " one of: \"stereo\", " "\"surround-51\",... or\n" " comma separated list of channel " "names: eg. \"FL,FR\"\n" " --format Sample format %s (req. for rec) " "(default %s)\n" " --volume Stream volume 0-1.0 (default %.3f)\n" " -q --quality Resampler quality (0 - 15) (default " "%d)\n" "\n" msgstr "" #: src/tools/pw-cat.c:1033 msgid "" " -p, --playback Playback mode\n" " -r, --record Recording mode\n" " -m, --midi Midi mode\n" "\n" msgstr "" #: src/tools/pw-cli.c:2932 #, c-format msgid "" "%s [options] [command]\n" " -h, --help Show this help\n" " --version Show version\n" " -d, --daemon Start as daemon (Default false)\n" " -r, --remote Remote daemon name\n" "\n" msgstr "" #: spa/plugins/alsa/acp/acp.c:290 msgid "Pro Audio" msgstr "" #: spa/plugins/alsa/acp/acp.c:411 spa/plugins/alsa/acp/alsa-mixer.c:4704 #: spa/plugins/bluez5/bluez5-device.c:1000 msgid "Off" msgstr "Сөнд." #: spa/plugins/alsa/acp/channelmap.h:466 msgid "(invalid)" msgstr "(жарамсыз)" #: spa/plugins/alsa/acp/alsa-mixer.c:2709 msgid "Input" msgstr "Кіріс" #: spa/plugins/alsa/acp/alsa-mixer.c:2710 msgid "Docking Station Input" msgstr "Док-станция кірісі" #: spa/plugins/alsa/acp/alsa-mixer.c:2711 msgid "Docking Station Microphone" msgstr "Док-станция микрофоны" #: spa/plugins/alsa/acp/alsa-mixer.c:2712 msgid "Docking Station Line In" msgstr "Док-станцияның сызықтық кірісі" #: spa/plugins/alsa/acp/alsa-mixer.c:2713 #: spa/plugins/alsa/acp/alsa-mixer.c:2804 msgid "Line In" msgstr "Сызықтық кіріс" #: spa/plugins/alsa/acp/alsa-mixer.c:2714 #: spa/plugins/alsa/acp/alsa-mixer.c:2798 #: spa/plugins/bluez5/bluez5-device.c:1145 msgid "Microphone" msgstr "Микрофон" #: spa/plugins/alsa/acp/alsa-mixer.c:2715 #: spa/plugins/alsa/acp/alsa-mixer.c:2799 msgid "Front Microphone" msgstr "Алдыңғы микрофон" #: spa/plugins/alsa/acp/alsa-mixer.c:2716 #: spa/plugins/alsa/acp/alsa-mixer.c:2800 msgid "Rear Microphone" msgstr "Артқы микрофон" #: spa/plugins/alsa/acp/alsa-mixer.c:2717 msgid "External Microphone" msgstr "Сыртқы микрофон" #: spa/plugins/alsa/acp/alsa-mixer.c:2718 #: spa/plugins/alsa/acp/alsa-mixer.c:2802 msgid "Internal Microphone" msgstr "Ішкі микрофон" #: spa/plugins/alsa/acp/alsa-mixer.c:2719 #: spa/plugins/alsa/acp/alsa-mixer.c:2805 msgid "Radio" msgstr "Радио" #: spa/plugins/alsa/acp/alsa-mixer.c:2720 #: spa/plugins/alsa/acp/alsa-mixer.c:2806 msgid "Video" msgstr "Видео" #: spa/plugins/alsa/acp/alsa-mixer.c:2721 msgid "Automatic Gain Control" msgstr "Күшейтуді автореттеу" #: spa/plugins/alsa/acp/alsa-mixer.c:2722 msgid "No Automatic Gain Control" msgstr "Күшейтуді автореттеу жоқ" #: spa/plugins/alsa/acp/alsa-mixer.c:2723 msgid "Boost" msgstr "Күшейту" #: spa/plugins/alsa/acp/alsa-mixer.c:2724 msgid "No Boost" msgstr "Күшейту жоқ" #: spa/plugins/alsa/acp/alsa-mixer.c:2725 msgid "Amplifier" msgstr "Күшейткіш" #: spa/plugins/alsa/acp/alsa-mixer.c:2726 msgid "No Amplifier" msgstr "Күшейткіш жоқ" #: spa/plugins/alsa/acp/alsa-mixer.c:2727 msgid "Bass Boost" msgstr "Бас күшейту" #: spa/plugins/alsa/acp/alsa-mixer.c:2728 msgid "No Bass Boost" msgstr "Бас күшейту жоқ" #: spa/plugins/alsa/acp/alsa-mixer.c:2729 #: spa/plugins/bluez5/bluez5-device.c:1150 msgid "Speaker" msgstr "Динамик" #: spa/plugins/alsa/acp/alsa-mixer.c:2730 #: spa/plugins/alsa/acp/alsa-mixer.c:2808 msgid "Headphones" msgstr "Құлаққаптар" #: spa/plugins/alsa/acp/alsa-mixer.c:2797 msgid "Analog Input" msgstr "Аналогтық кіріс" #: spa/plugins/alsa/acp/alsa-mixer.c:2801 msgid "Dock Microphone" msgstr "Док-станция микрофоны" #: spa/plugins/alsa/acp/alsa-mixer.c:2803 msgid "Headset Microphone" msgstr "Гарнитура микрофоны" #: spa/plugins/alsa/acp/alsa-mixer.c:2807 msgid "Analog Output" msgstr "Аналогтық шығыс" #: spa/plugins/alsa/acp/alsa-mixer.c:2809 #, fuzzy msgid "Headphones 2" msgstr "Құлаққаптар" #: spa/plugins/alsa/acp/alsa-mixer.c:2810 msgid "Headphones Mono Output" msgstr "Құлаққаптардың моно шығысы" #: spa/plugins/alsa/acp/alsa-mixer.c:2811 msgid "Line Out" msgstr "Сызықтық шығыс" #: spa/plugins/alsa/acp/alsa-mixer.c:2812 msgid "Analog Mono Output" msgstr "Аналогтық моно шығысы" #: spa/plugins/alsa/acp/alsa-mixer.c:2813 msgid "Speakers" msgstr "Динамиктер" #: spa/plugins/alsa/acp/alsa-mixer.c:2814 msgid "HDMI / DisplayPort" msgstr "HDMI / DisplayPort" #: spa/plugins/alsa/acp/alsa-mixer.c:2815 msgid "Digital Output (S/PDIF)" msgstr "Цифрлық шығыс (S/PDIF)" #: spa/plugins/alsa/acp/alsa-mixer.c:2816 msgid "Digital Input (S/PDIF)" msgstr "Цифрлық кіріс (S/PDIF)" #: spa/plugins/alsa/acp/alsa-mixer.c:2817 msgid "Multichannel Input" msgstr "Көпарналы кіріс" #: spa/plugins/alsa/acp/alsa-mixer.c:2818 msgid "Multichannel Output" msgstr "Көпарналы шығыс" #: spa/plugins/alsa/acp/alsa-mixer.c:2819 msgid "Game Output" msgstr "Ойын шығысы" #: spa/plugins/alsa/acp/alsa-mixer.c:2820 #: spa/plugins/alsa/acp/alsa-mixer.c:2821 msgid "Chat Output" msgstr "Чат шығысы" #: spa/plugins/alsa/acp/alsa-mixer.c:2822 #, fuzzy msgid "Chat Input" msgstr "Чат шығысы" #: spa/plugins/alsa/acp/alsa-mixer.c:2823 #, fuzzy msgid "Virtual Surround 7.1" msgstr "Виртуалды көлемді аудиоқабылдағыш" #: spa/plugins/alsa/acp/alsa-mixer.c:4527 msgid "Analog Mono" msgstr "Аналогтық моно" #: spa/plugins/alsa/acp/alsa-mixer.c:4528 #, fuzzy msgid "Analog Mono (Left)" msgstr "Аналогтық моно" #: spa/plugins/alsa/acp/alsa-mixer.c:4529 #, fuzzy msgid "Analog Mono (Right)" msgstr "Аналогтық моно" #. Note: Not translated to "Analog Stereo Input", because the source #. * name gets "Input" appended to it automatically, so adding "Input" #. * here would lead to the source name to become "Analog Stereo Input #. * Input". The same logic applies to analog-stereo-output, #. * multichannel-input and multichannel-output. #: spa/plugins/alsa/acp/alsa-mixer.c:4530 #: spa/plugins/alsa/acp/alsa-mixer.c:4538 #: spa/plugins/alsa/acp/alsa-mixer.c:4539 msgid "Analog Stereo" msgstr "Аналогтық стерео" #: spa/plugins/alsa/acp/alsa-mixer.c:4531 msgid "Mono" msgstr "Моно" #: spa/plugins/alsa/acp/alsa-mixer.c:4532 msgid "Stereo" msgstr "Стерео" #: spa/plugins/alsa/acp/alsa-mixer.c:4540 #: spa/plugins/alsa/acp/alsa-mixer.c:4698 #: spa/plugins/bluez5/bluez5-device.c:1135 msgid "Headset" msgstr "Гарнитура" #: spa/plugins/alsa/acp/alsa-mixer.c:4541 #: spa/plugins/alsa/acp/alsa-mixer.c:4699 #, fuzzy msgid "Speakerphone" msgstr "Динамик" #: spa/plugins/alsa/acp/alsa-mixer.c:4542 #: spa/plugins/alsa/acp/alsa-mixer.c:4543 msgid "Multichannel" msgstr "Көпарналы" #: spa/plugins/alsa/acp/alsa-mixer.c:4544 msgid "Analog Surround 2.1" msgstr "Аналогтық көлемді 2.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4545 msgid "Analog Surround 3.0" msgstr "Аналогтық көлемді 3.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4546 msgid "Analog Surround 3.1" msgstr "Аналогтық көлемді 3.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4547 msgid "Analog Surround 4.0" msgstr "Аналогтық көлемді 4.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4548 msgid "Analog Surround 4.1" msgstr "Аналогтық көлемді 4.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4549 msgid "Analog Surround 5.0" msgstr "Аналогтық көлемді 5.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4550 msgid "Analog Surround 5.1" msgstr "Аналогтық көлемді 5.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4551 msgid "Analog Surround 6.0" msgstr "Аналогтық көлемді 6.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4552 msgid "Analog Surround 6.1" msgstr "Аналогтық көлемді 6.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4553 msgid "Analog Surround 7.0" msgstr "Аналогтық көлемді 7.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4554 msgid "Analog Surround 7.1" msgstr "Аналогтық көлемді 7.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4555 msgid "Digital Stereo (IEC958)" msgstr "Цифрлық стерео (IEC958)" #: spa/plugins/alsa/acp/alsa-mixer.c:4556 msgid "Digital Surround 4.0 (IEC958/AC3)" msgstr "Цифрлық көлемді 4.0 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4557 msgid "Digital Surround 5.1 (IEC958/AC3)" msgstr "Цифрлық көлемді 5.1 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4558 msgid "Digital Surround 5.1 (IEC958/DTS)" msgstr "Цифрлық көлемді 5.1 (IEC958/DTS)" #: spa/plugins/alsa/acp/alsa-mixer.c:4559 msgid "Digital Stereo (HDMI)" msgstr "Цифрлық стерео (HDMI)" #: spa/plugins/alsa/acp/alsa-mixer.c:4560 msgid "Digital Surround 5.1 (HDMI)" msgstr "Цифрлық көлемді 5.1 (HDMI)" #: spa/plugins/alsa/acp/alsa-mixer.c:4561 msgid "Chat" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4562 msgid "Game" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4696 msgid "Analog Mono Duplex" msgstr "Аналогтық моно дуплекс" #: spa/plugins/alsa/acp/alsa-mixer.c:4697 msgid "Analog Stereo Duplex" msgstr "Аналогтық стерео дуплекс" #: spa/plugins/alsa/acp/alsa-mixer.c:4700 msgid "Digital Stereo Duplex (IEC958)" msgstr "Цифрлық стерео дуплекс (IEC958)" #: spa/plugins/alsa/acp/alsa-mixer.c:4701 msgid "Multichannel Duplex" msgstr "Көпарналы дуплекс" #: spa/plugins/alsa/acp/alsa-mixer.c:4702 msgid "Stereo Duplex" msgstr "Стерео дуплекс" #: spa/plugins/alsa/acp/alsa-mixer.c:4703 msgid "Mono Chat + 7.1 Surround" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4806 #, c-format msgid "%s Output" msgstr "%s шығысы" #: spa/plugins/alsa/acp/alsa-mixer.c:4813 #, c-format msgid "%s Input" msgstr "%s кірісі" #: spa/plugins/alsa/acp/alsa-util.c:1175 spa/plugins/alsa/acp/alsa-util.c:1269 #, c-format msgid "" "snd_pcm_avail() returned a value that is exceptionally large: %lu byte (%lu " "ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_avail() returned a value that is exceptionally large: %lu bytes (%lu " "ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" msgstr[1] "" #: spa/plugins/alsa/acp/alsa-util.c:1241 #, c-format msgid "" "snd_pcm_delay() returned a value that is exceptionally large: %li byte (%s" "%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_delay() returned a value that is exceptionally large: %li bytes (%s" "%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" msgstr[1] "" #: spa/plugins/alsa/acp/alsa-util.c:1288 #, c-format msgid "" "snd_pcm_avail_delay() returned strange values: delay %lu is less than avail " "%lu.\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr "" #: spa/plugins/alsa/acp/alsa-util.c:1331 #, c-format msgid "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu byte " "(%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu bytes " "(%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" msgstr[1] "" #: spa/plugins/bluez5/bluez5-device.c:1010 msgid "Audio Gateway (A2DP Source & HSP/HFP AG)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1033 #, c-format msgid "High Fidelity Playback (A2DP Sink, codec %s)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1035 #, c-format msgid "High Fidelity Duplex (A2DP Source/Sink, codec %s)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1041 msgid "High Fidelity Playback (A2DP Sink)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1043 msgid "High Fidelity Duplex (A2DP Source/Sink)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1070 #, c-format msgid "Headset Head Unit (HSP/HFP, codec %s)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1074 msgid "Headset Head Unit (HSP/HFP)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1140 msgid "Handsfree" msgstr "Хендс-фри" #: spa/plugins/bluez5/bluez5-device.c:1155 msgid "Headphone" msgstr "Құлаққап" #: spa/plugins/bluez5/bluez5-device.c:1160 msgid "Portable" msgstr "Портативті динамик" #: spa/plugins/bluez5/bluez5-device.c:1165 msgid "Car" msgstr "Автомобильдік динамик" #: spa/plugins/bluez5/bluez5-device.c:1170 msgid "HiFi" msgstr "HiFi" #: spa/plugins/bluez5/bluez5-device.c:1175 msgid "Phone" msgstr "Телефон" #: spa/plugins/bluez5/bluez5-device.c:1181 msgid "Bluetooth" msgstr "Bluetooth" pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/po/kn.po000066400000000000000000000504211511204443500220660ustar00rootroot00000000000000# translation of pipewire.master-tx.kn.po to Kannada # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # Shankar Prasad , 2009, 2012. # msgid "" msgstr "" "Project-Id-Version: pipewire.master-tx.kn\n" "Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/pipewire/" "issues/new\n" "POT-Creation-Date: 2021-04-18 16:54+0800\n" "PO-Revision-Date: 2012-01-30 09:54+0000\n" "Last-Translator: Shankar Prasad \n" "Language-Team: Kannada \n" "Language: kn\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Generator: Lokalize 1.0\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #: src/daemon/pipewire.c:43 #, c-format msgid "" "%s [options]\n" " -h, --help Show this help\n" " --version Show version\n" " -c, --config Load config (Default %s)\n" msgstr "" #: src/daemon/pipewire.desktop.in:4 msgid "PipeWire Media System" msgstr "" #: src/daemon/pipewire.desktop.in:5 msgid "Start the PipeWire Media System" msgstr "" #: src/examples/media-session/alsa-monitor.c:526 #: spa/plugins/alsa/acp/compat.c:187 msgid "Built-in Audio" msgstr "ಆಂತರಿಕ ಆಡಿಯೊ" #: src/examples/media-session/alsa-monitor.c:530 #: spa/plugins/alsa/acp/compat.c:192 msgid "Modem" msgstr "ಮಾಡೆಮ್" #: src/examples/media-session/alsa-monitor.c:539 msgid "Unknown device" msgstr "" #: src/tools/pw-cat.c:991 #, c-format msgid "" "%s [options] \n" " -h, --help Show this help\n" " --version Show version\n" " -v, --verbose Enable verbose operations\n" "\n" msgstr "" #: src/tools/pw-cat.c:998 #, c-format msgid "" " -R, --remote Remote daemon name\n" " --media-type Set media type (default %s)\n" " --media-category Set media category (default %s)\n" " --media-role Set media role (default %s)\n" " --target Set node target (default %s)\n" " 0 means don't link\n" " --latency Set node latency (default %s)\n" " Xunit (unit = s, ms, us, ns)\n" " or direct samples (256)\n" " the rate is the one of the source " "file\n" " --list-targets List available targets for --target\n" "\n" msgstr "" #: src/tools/pw-cat.c:1016 #, c-format msgid "" " --rate Sample rate (req. for rec) (default " "%u)\n" " --channels Number of channels (req. for rec) " "(default %u)\n" " --channel-map Channel map\n" " one of: \"stereo\", " "\"surround-51\",... or\n" " comma separated list of channel " "names: eg. \"FL,FR\"\n" " --format Sample format %s (req. for rec) " "(default %s)\n" " --volume Stream volume 0-1.0 (default %.3f)\n" " -q --quality Resampler quality (0 - 15) (default " "%d)\n" "\n" msgstr "" #: src/tools/pw-cat.c:1033 msgid "" " -p, --playback Playback mode\n" " -r, --record Recording mode\n" " -m, --midi Midi mode\n" "\n" msgstr "" #: src/tools/pw-cli.c:2932 #, c-format msgid "" "%s [options] [command]\n" " -h, --help Show this help\n" " --version Show version\n" " -d, --daemon Start as daemon (Default false)\n" " -r, --remote Remote daemon name\n" "\n" msgstr "" #: spa/plugins/alsa/acp/acp.c:290 msgid "Pro Audio" msgstr "" #: spa/plugins/alsa/acp/acp.c:411 spa/plugins/alsa/acp/alsa-mixer.c:4704 #: spa/plugins/bluez5/bluez5-device.c:1000 msgid "Off" msgstr "ಜಡ" #: spa/plugins/alsa/acp/channelmap.h:466 msgid "(invalid)" msgstr "(ಅಮಾನ್ಯ)" #: spa/plugins/alsa/acp/alsa-mixer.c:2709 msgid "Input" msgstr "ಇನ್‌ಪುಟ್" #: spa/plugins/alsa/acp/alsa-mixer.c:2710 msgid "Docking Station Input" msgstr "ಡಾಕಿಂಗ್ ಸ್ಟೇಶನ್ ಇನ್‌ಪುಟ್" #: spa/plugins/alsa/acp/alsa-mixer.c:2711 #, fuzzy msgid "Docking Station Microphone" msgstr "ಡಾಕಿಂಗ್ ಸ್ಟೇಶನ್ ಮೈಕ್ರೊಫೋನ್" #: spa/plugins/alsa/acp/alsa-mixer.c:2712 #, fuzzy msgid "Docking Station Line In" msgstr "ಡಾಕಿಂಗ್ ಸ್ಟೇಶನ್ ಇನ್‌ಪುಟ್" #: spa/plugins/alsa/acp/alsa-mixer.c:2713 #: spa/plugins/alsa/acp/alsa-mixer.c:2804 msgid "Line In" msgstr "ಲೈನ್-ಇನ್" #: spa/plugins/alsa/acp/alsa-mixer.c:2714 #: spa/plugins/alsa/acp/alsa-mixer.c:2798 #: spa/plugins/bluez5/bluez5-device.c:1145 msgid "Microphone" msgstr "ಮೈಕ್ರೊಫೋನ್" #: spa/plugins/alsa/acp/alsa-mixer.c:2715 #: spa/plugins/alsa/acp/alsa-mixer.c:2799 #, fuzzy msgid "Front Microphone" msgstr "ಡಾಕಿಂಗ್ ಸ್ಟೇಶನ್ ಮೈಕ್ರೊಫೋನ್" #: spa/plugins/alsa/acp/alsa-mixer.c:2716 #: spa/plugins/alsa/acp/alsa-mixer.c:2800 #, fuzzy msgid "Rear Microphone" msgstr "ಮೈಕ್ರೊಫೋನ್" #: spa/plugins/alsa/acp/alsa-mixer.c:2717 msgid "External Microphone" msgstr "ಬಾಹ್ಯ ಮೈಕ್ರೊಫೋನ್" #: spa/plugins/alsa/acp/alsa-mixer.c:2718 #: spa/plugins/alsa/acp/alsa-mixer.c:2802 msgid "Internal Microphone" msgstr "ಆಂತರಿಕ ಮೈಕ್ರೊಫೋನ್" #: spa/plugins/alsa/acp/alsa-mixer.c:2719 #: spa/plugins/alsa/acp/alsa-mixer.c:2805 msgid "Radio" msgstr "ರೇಡಿಯೊ" #: spa/plugins/alsa/acp/alsa-mixer.c:2720 #: spa/plugins/alsa/acp/alsa-mixer.c:2806 msgid "Video" msgstr "ವೀಡಿಯೊ" #: spa/plugins/alsa/acp/alsa-mixer.c:2721 msgid "Automatic Gain Control" msgstr "ಆಟೊಮ್ಯಾಟಿಕ್ ಗೇನ್ ಕಂಟ್ರೋಲ್" #: spa/plugins/alsa/acp/alsa-mixer.c:2722 msgid "No Automatic Gain Control" msgstr "ಯಾವುದೆ ಆಟೊಮ್ಯಾಟಿಕ್ ಗೇನ್ ಕಂಟ್ರೋಲ್ ಇಲ್ಲ" #: spa/plugins/alsa/acp/alsa-mixer.c:2723 msgid "Boost" msgstr "ಬೂಸ್ಟ್" #: spa/plugins/alsa/acp/alsa-mixer.c:2724 msgid "No Boost" msgstr "ಯಾವುದೆ ಬೂಸ್ಟ್ ಇಲ್ಲ" #: spa/plugins/alsa/acp/alsa-mixer.c:2725 msgid "Amplifier" msgstr "ಆಂಪ್ಲಿಫಯರ್" #: spa/plugins/alsa/acp/alsa-mixer.c:2726 msgid "No Amplifier" msgstr "ಯಾವುದೆ ಆಂಪ್ಲಿಫಯರ್ ಇಲ್ಲ" #: spa/plugins/alsa/acp/alsa-mixer.c:2727 #, fuzzy msgid "Bass Boost" msgstr "ಬೂಸ್ಟ್" #: spa/plugins/alsa/acp/alsa-mixer.c:2728 #, fuzzy msgid "No Bass Boost" msgstr "ಯಾವುದೆ ಬೂಸ್ಟ್ ಇಲ್ಲ" #: spa/plugins/alsa/acp/alsa-mixer.c:2729 #: spa/plugins/bluez5/bluez5-device.c:1150 msgid "Speaker" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2730 #: spa/plugins/alsa/acp/alsa-mixer.c:2808 msgid "Headphones" msgstr "ಅನಲಾಗ್ ಹೆಡ್‌ಫೋನ್‌ಗಳು" #: spa/plugins/alsa/acp/alsa-mixer.c:2797 msgid "Analog Input" msgstr "ಅನಲಾಗ್ ಇನ್‌ಪುಟ್" #: spa/plugins/alsa/acp/alsa-mixer.c:2801 msgid "Dock Microphone" msgstr "ಡಾಕಿಂಗ್ ಸ್ಟೇಶನ್ ಮೈಕ್ರೊಫೋನ್" #: spa/plugins/alsa/acp/alsa-mixer.c:2803 #, fuzzy msgid "Headset Microphone" msgstr "ಮೈಕ್ರೊಫೋನ್" #: spa/plugins/alsa/acp/alsa-mixer.c:2807 msgid "Analog Output" msgstr "ಅನಲಾಗ್ ಔಟ್‌ಪುಟ್" #: spa/plugins/alsa/acp/alsa-mixer.c:2809 #, fuzzy msgid "Headphones 2" msgstr "ಅನಲಾಗ್ ಹೆಡ್‌ಫೋನ್‌ಗಳು" #: spa/plugins/alsa/acp/alsa-mixer.c:2810 #, fuzzy msgid "Headphones Mono Output" msgstr "ಅನಲಾಗ್ ಮೊನೊ ಔಟ್‌ಪುಟ್" #: spa/plugins/alsa/acp/alsa-mixer.c:2811 #, fuzzy msgid "Line Out" msgstr "ಲೈನ್-ಇನ್" #: spa/plugins/alsa/acp/alsa-mixer.c:2812 msgid "Analog Mono Output" msgstr "ಅನಲಾಗ್ ಮೊನೊ ಔಟ್‌ಪುಟ್" #: spa/plugins/alsa/acp/alsa-mixer.c:2813 #, fuzzy msgid "Speakers" msgstr "ಅನಲಾಗ್ ಸ್ಟೀರಿಯೋ" #: spa/plugins/alsa/acp/alsa-mixer.c:2814 msgid "HDMI / DisplayPort" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2815 #, fuzzy msgid "Digital Output (S/PDIF)" msgstr "ಡಿಜಿಟಲ್ ಸ್ಟೀರಿಯೊ (HDMI)" #: spa/plugins/alsa/acp/alsa-mixer.c:2816 #, fuzzy msgid "Digital Input (S/PDIF)" msgstr "ಡಿಜಿಟಲ್ ಸ್ಟೀರಿಯೊ (HDMI)" #: spa/plugins/alsa/acp/alsa-mixer.c:2817 msgid "Multichannel Input" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2818 #, fuzzy msgid "Multichannel Output" msgstr "ಶೂನ್ಯ ಔಟ್‌ಪುಟ್" #: spa/plugins/alsa/acp/alsa-mixer.c:2819 #, fuzzy msgid "Game Output" msgstr "ಶೂನ್ಯ ಔಟ್‌ಪುಟ್" #: spa/plugins/alsa/acp/alsa-mixer.c:2820 #: spa/plugins/alsa/acp/alsa-mixer.c:2821 #, fuzzy msgid "Chat Output" msgstr "ಶೂನ್ಯ ಔಟ್‌ಪುಟ್" #: spa/plugins/alsa/acp/alsa-mixer.c:2822 #, fuzzy msgid "Chat Input" msgstr "ಇನ್‌ಪುಟ್" #: spa/plugins/alsa/acp/alsa-mixer.c:2823 #, fuzzy msgid "Virtual Surround 7.1" msgstr "ಅನಲಾಗ್ ಸರೌಂಡ್‌ 7.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4527 msgid "Analog Mono" msgstr "ಅನಲಾಗ್ ಮೊನೊ" #: spa/plugins/alsa/acp/alsa-mixer.c:4528 #, fuzzy msgid "Analog Mono (Left)" msgstr "ಅನಲಾಗ್ ಮೊನೊ" #: spa/plugins/alsa/acp/alsa-mixer.c:4529 #, fuzzy msgid "Analog Mono (Right)" msgstr "ಅನಲಾಗ್ ಮೊನೊ" #. Note: Not translated to "Analog Stereo Input", because the source #. * name gets "Input" appended to it automatically, so adding "Input" #. * here would lead to the source name to become "Analog Stereo Input #. * Input". The same logic applies to analog-stereo-output, #. * multichannel-input and multichannel-output. #: spa/plugins/alsa/acp/alsa-mixer.c:4530 #: spa/plugins/alsa/acp/alsa-mixer.c:4538 #: spa/plugins/alsa/acp/alsa-mixer.c:4539 msgid "Analog Stereo" msgstr "ಅನಲಾಗ್ ಸ್ಟೀರಿಯೋ" #: spa/plugins/alsa/acp/alsa-mixer.c:4531 msgid "Mono" msgstr "ಮೊನೊ" #: spa/plugins/alsa/acp/alsa-mixer.c:4532 msgid "Stereo" msgstr "ಸ್ಟೀರಿಯೋ" #: spa/plugins/alsa/acp/alsa-mixer.c:4540 #: spa/plugins/alsa/acp/alsa-mixer.c:4698 #: spa/plugins/bluez5/bluez5-device.c:1135 msgid "Headset" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4541 #: spa/plugins/alsa/acp/alsa-mixer.c:4699 #, fuzzy msgid "Speakerphone" msgstr "ಅನಲಾಗ್ ಸ್ಟೀರಿಯೋ" #: spa/plugins/alsa/acp/alsa-mixer.c:4542 #: spa/plugins/alsa/acp/alsa-mixer.c:4543 msgid "Multichannel" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4544 msgid "Analog Surround 2.1" msgstr "ಅನಲಾಗ್ ಸರೌಂಡ್‌ 2.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4545 msgid "Analog Surround 3.0" msgstr "ಅನಲಾಗ್ ಸರೌಂಡ್‌ 3.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4546 msgid "Analog Surround 3.1" msgstr "ಅನಲಾಗ್ ಸರೌಂಡ್‌ 3.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4547 msgid "Analog Surround 4.0" msgstr "ಅನಲಾಗ್ ಸರೌಂಡ್‌ 4.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4548 msgid "Analog Surround 4.1" msgstr "ಅನಲಾಗ್ ಸರೌಂಡ್‌ 4.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4549 msgid "Analog Surround 5.0" msgstr "ಅನಲಾಗ್ ಸರೌಂಡ್‌ 5.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4550 msgid "Analog Surround 5.1" msgstr "ಅನಲಾಗ್ ಸರೌಂಡ್‌ 5.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4551 msgid "Analog Surround 6.0" msgstr "ಅನಲಾಗ್ ಸರೌಂಡ್‌ 6.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4552 msgid "Analog Surround 6.1" msgstr "ಅನಲಾಗ್ ಸರೌಂಡ್‌ 6.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4553 msgid "Analog Surround 7.0" msgstr "ಅನಲಾಗ್ ಸರೌಂಡ್‌ 7.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4554 msgid "Analog Surround 7.1" msgstr "ಅನಲಾಗ್ ಸರೌಂಡ್‌ 7.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4555 msgid "Digital Stereo (IEC958)" msgstr "ಡಿಜಿಟಲ್ ಸ್ಟೀರಿಯೊ (IEC958)" #: spa/plugins/alsa/acp/alsa-mixer.c:4556 msgid "Digital Surround 4.0 (IEC958/AC3)" msgstr "ಡಿಜಿಟಲ್ ಸರೌಂಡ್ 4.0 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4557 msgid "Digital Surround 5.1 (IEC958/AC3)" msgstr "ಡಿಜಿಟಲ್ ಸರೌಂಡ್ 5.1 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4558 #, fuzzy msgid "Digital Surround 5.1 (IEC958/DTS)" msgstr "ಡಿಜಿಟಲ್ ಸರೌಂಡ್ 5.1 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4559 msgid "Digital Stereo (HDMI)" msgstr "ಡಿಜಿಟಲ್ ಸ್ಟೀರಿಯೊ (HDMI)" #: spa/plugins/alsa/acp/alsa-mixer.c:4560 #, fuzzy msgid "Digital Surround 5.1 (HDMI)" msgstr "ಡಿಜಿಟಲ್ ಸರೌಂಡ್ 5.1 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4561 msgid "Chat" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4562 msgid "Game" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4696 msgid "Analog Mono Duplex" msgstr "ಅನಲಾಗ್ ಮೊನೊ ಡ್ಯೂಪ್ಲೆಕ್ಸ್" #: spa/plugins/alsa/acp/alsa-mixer.c:4697 msgid "Analog Stereo Duplex" msgstr "ಅನಲಾಗ್ ಸ್ಟೀರಿಯೊ ಡ್ಯೂಪ್ಲೆಕ್ಸ್" #: spa/plugins/alsa/acp/alsa-mixer.c:4700 msgid "Digital Stereo Duplex (IEC958)" msgstr "ಅನಲಾಗ್ ಸ್ಟೀರಿಯೊ ಡ್ಯೂಪ್ಲೆಕ್ಸ್ (IEC958)" #: spa/plugins/alsa/acp/alsa-mixer.c:4701 msgid "Multichannel Duplex" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4702 #, fuzzy msgid "Stereo Duplex" msgstr "ಅನಲಾಗ್ ಸ್ಟೀರಿಯೊ ಡ್ಯೂಪ್ಲೆಕ್ಸ್" #: spa/plugins/alsa/acp/alsa-mixer.c:4703 msgid "Mono Chat + 7.1 Surround" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4806 #, fuzzy, c-format msgid "%s Output" msgstr "ಶೂನ್ಯ ಔಟ್‌ಪುಟ್" #: spa/plugins/alsa/acp/alsa-mixer.c:4813 #, fuzzy, c-format msgid "%s Input" msgstr "ಇನ್‌ಪುಟ್" #: spa/plugins/alsa/acp/alsa-util.c:1175 spa/plugins/alsa/acp/alsa-util.c:1269 #, fuzzy, c-format msgid "" "snd_pcm_avail() returned a value that is exceptionally large: %lu byte (%lu " "ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_avail() returned a value that is exceptionally large: %lu bytes (%lu " "ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "snd_pcm_avail() ದಿಂದ ಅತ್ಯಂತ ದೊಡ್ಡದಾದ ಮೌಲ್ಯವು ಮರಳಿದೆ: %lu ಬೈಟ್‌ಗಳು (%lu ms).\n" "ಇದಕ್ಕೆ ALSA ಚಾಲಕ '%s' ದಲ್ಲಿನ ಒಂದು ದೋಷದ ಕಾರಣವಿರಬಹುದು. ದಯವಿಟ್ಟುಈ ತೊಂದರೆಯನ್ನು ALSA " "ವಿಕಸನಗಾರರ ಗಮನಕ್ಕೆ ತನ್ನಿ." msgstr[1] "" "snd_pcm_avail() ದಿಂದ ಅತ್ಯಂತ ದೊಡ್ಡದಾದ ಮೌಲ್ಯವು ಮರಳಿದೆ: %lu ಬೈಟ್‌ಗಳು (%lu ms).\n" "ಇದಕ್ಕೆ ALSA ಚಾಲಕ '%s' ದಲ್ಲಿನ ಒಂದು ದೋಷದ ಕಾರಣವಿರಬಹುದು. ದಯವಿಟ್ಟುಈ ತೊಂದರೆಯನ್ನು ALSA " "ವಿಕಸನಗಾರರ ಗಮನಕ್ಕೆ ತನ್ನಿ." #: spa/plugins/alsa/acp/alsa-util.c:1241 #, fuzzy, c-format msgid "" "snd_pcm_delay() returned a value that is exceptionally large: %li byte (%s" "%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_delay() returned a value that is exceptionally large: %li bytes (%s" "%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "snd_pcm_delay() ದಿಂದ ಅತ್ಯಂತ ದೊಡ್ಡದಾದ ಮೌಲ್ಯವು ಮರಳಿದೆ: %li ಬೈಟ್‌ಗಳು (%s%lu ms).\n" "ಇದಕ್ಕೆ ALSA ಚಾಲಕ '%s' ದಲ್ಲಿನ ಒಂದು ದೋಷದ ಕಾರಣವಿರಬಹುದು. ದಯವಿಟ್ಟುಈ ತೊಂದರೆಯನ್ನು ALSA " "ವಿಕಸನಗಾರರ ಗಮನಕ್ಕೆ ತನ್ನಿ." msgstr[1] "" "snd_pcm_delay() ದಿಂದ ಅತ್ಯಂತ ದೊಡ್ಡದಾದ ಮೌಲ್ಯವು ಮರಳಿದೆ: %li ಬೈಟ್‌ಗಳು (%s%lu ms).\n" "ಇದಕ್ಕೆ ALSA ಚಾಲಕ '%s' ದಲ್ಲಿನ ಒಂದು ದೋಷದ ಕಾರಣವಿರಬಹುದು. ದಯವಿಟ್ಟುಈ ತೊಂದರೆಯನ್ನು ALSA " "ವಿಕಸನಗಾರರ ಗಮನಕ್ಕೆ ತನ್ನಿ." #: spa/plugins/alsa/acp/alsa-util.c:1288 #, fuzzy, c-format msgid "" "snd_pcm_avail_delay() returned strange values: delay %lu is less than avail " "%lu.\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr "" "snd_pcm_avail() ದಿಂದ ಅತ್ಯಂತ ದೊಡ್ಡದಾದ ಮೌಲ್ಯವು ಮರಳಿದೆ: %lu ಬೈಟ್‌ಗಳು (%lu ms).\n" "ಇದಕ್ಕೆ ALSA ಚಾಲಕ '%s' ದಲ್ಲಿನ ಒಂದು ದೋಷದ ಕಾರಣವಿರಬಹುದು. ದಯವಿಟ್ಟುಈ ತೊಂದರೆಯನ್ನು ALSA " "ವಿಕಸನಗಾರರ ಗಮನಕ್ಕೆ ತನ್ನಿ." #: spa/plugins/alsa/acp/alsa-util.c:1331 #, fuzzy, c-format msgid "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu byte " "(%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu bytes " "(%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "snd_pcm_mmap_begin() ದಿಂದ ಅತ್ಯಂತ ದೊಡ್ಡದಾದ ಮೌಲ್ಯವು ಮರಳಿದೆ: %lu ಬೈಟ್‌ಗಳು (%lu ms).\n" "ಇದಕ್ಕೆ ALSA ಚಾಲಕ '%s' ದಲ್ಲಿನ ಒಂದು ದೋಷದ ಕಾರಣವಿರಬಹುದು. ದಯವಿಟ್ಟುಈ ತೊಂದರೆಯನ್ನು ALSA " "ವಿಕಸನಗಾರರ ಗಮನಕ್ಕೆ ತನ್ನಿ." msgstr[1] "" "snd_pcm_mmap_begin() ದಿಂದ ಅತ್ಯಂತ ದೊಡ್ಡದಾದ ಮೌಲ್ಯವು ಮರಳಿದೆ: %lu ಬೈಟ್‌ಗಳು (%lu ms).\n" "ಇದಕ್ಕೆ ALSA ಚಾಲಕ '%s' ದಲ್ಲಿನ ಒಂದು ದೋಷದ ಕಾರಣವಿರಬಹುದು. ದಯವಿಟ್ಟುಈ ತೊಂದರೆಯನ್ನು ALSA " "ವಿಕಸನಗಾರರ ಗಮನಕ್ಕೆ ತನ್ನಿ." #: spa/plugins/bluez5/bluez5-device.c:1010 msgid "Audio Gateway (A2DP Source & HSP/HFP AG)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1033 #, c-format msgid "High Fidelity Playback (A2DP Sink, codec %s)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1035 #, c-format msgid "High Fidelity Duplex (A2DP Source/Sink, codec %s)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1041 msgid "High Fidelity Playback (A2DP Sink)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1043 msgid "High Fidelity Duplex (A2DP Source/Sink)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1070 #, c-format msgid "Headset Head Unit (HSP/HFP, codec %s)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1074 msgid "Headset Head Unit (HSP/HFP)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1140 msgid "Handsfree" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1155 #, fuzzy msgid "Headphone" msgstr "ಅನಲಾಗ್ ಹೆಡ್‌ಫೋನ್‌ಗಳು" #: spa/plugins/bluez5/bluez5-device.c:1160 msgid "Portable" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1165 msgid "Car" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1170 msgid "HiFi" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1175 msgid "Phone" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1181 msgid "Bluetooth" msgstr "" pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/po/ko.po000066400000000000000000000405761511204443500221010ustar00rootroot00000000000000# eukim , 2013. #zanata # KimJeongYeon , 2017. # Sangchul Lee , 2018. msgid "" msgstr "" "Project-Id-Version: pipewire\n" "Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/pipewire/" "issues/new\n" "POT-Creation-Date: 2021-04-18 16:54+0800\n" "PO-Revision-Date: 2018-06-21 15:10+0900\n" "Last-Translator: Sangchul Lee \n" "Language-Team: Korean\n" "Language: ko\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Generator: Poedit 1.8.7.1\n" "Plural-Forms: nplurals=1; plural=0;\n" #: src/daemon/pipewire.c:43 #, c-format msgid "" "%s [options]\n" " -h, --help Show this help\n" " --version Show version\n" " -c, --config Load config (Default %s)\n" msgstr "" #: src/daemon/pipewire.desktop.in:4 msgid "PipeWire Media System" msgstr "" #: src/daemon/pipewire.desktop.in:5 msgid "Start the PipeWire Media System" msgstr "" #: src/examples/media-session/alsa-monitor.c:526 #: spa/plugins/alsa/acp/compat.c:187 msgid "Built-in Audio" msgstr "내장 오디오 " #: src/examples/media-session/alsa-monitor.c:530 #: spa/plugins/alsa/acp/compat.c:192 msgid "Modem" msgstr "모뎀 " #: src/examples/media-session/alsa-monitor.c:539 msgid "Unknown device" msgstr "" #: src/tools/pw-cat.c:991 #, c-format msgid "" "%s [options] \n" " -h, --help Show this help\n" " --version Show version\n" " -v, --verbose Enable verbose operations\n" "\n" msgstr "" #: src/tools/pw-cat.c:998 #, c-format msgid "" " -R, --remote Remote daemon name\n" " --media-type Set media type (default %s)\n" " --media-category Set media category (default %s)\n" " --media-role Set media role (default %s)\n" " --target Set node target (default %s)\n" " 0 means don't link\n" " --latency Set node latency (default %s)\n" " Xunit (unit = s, ms, us, ns)\n" " or direct samples (256)\n" " the rate is the one of the source " "file\n" " --list-targets List available targets for --target\n" "\n" msgstr "" #: src/tools/pw-cat.c:1016 #, c-format msgid "" " --rate Sample rate (req. for rec) (default " "%u)\n" " --channels Number of channels (req. for rec) " "(default %u)\n" " --channel-map Channel map\n" " one of: \"stereo\", " "\"surround-51\",... or\n" " comma separated list of channel " "names: eg. \"FL,FR\"\n" " --format Sample format %s (req. for rec) " "(default %s)\n" " --volume Stream volume 0-1.0 (default %.3f)\n" " -q --quality Resampler quality (0 - 15) (default " "%d)\n" "\n" msgstr "" #: src/tools/pw-cat.c:1033 msgid "" " -p, --playback Playback mode\n" " -r, --record Recording mode\n" " -m, --midi Midi mode\n" "\n" msgstr "" #: src/tools/pw-cli.c:2932 #, c-format msgid "" "%s [options] [command]\n" " -h, --help Show this help\n" " --version Show version\n" " -d, --daemon Start as daemon (Default false)\n" " -r, --remote Remote daemon name\n" "\n" msgstr "" #: spa/plugins/alsa/acp/acp.c:290 msgid "Pro Audio" msgstr "" #: spa/plugins/alsa/acp/acp.c:411 spa/plugins/alsa/acp/alsa-mixer.c:4704 #: spa/plugins/bluez5/bluez5-device.c:1000 msgid "Off" msgstr "끄기 " #: spa/plugins/alsa/acp/channelmap.h:466 msgid "(invalid)" msgstr "(잘못됨)" #: spa/plugins/alsa/acp/alsa-mixer.c:2709 msgid "Input" msgstr "입력" #: spa/plugins/alsa/acp/alsa-mixer.c:2710 msgid "Docking Station Input" msgstr "도킹 스테이션 입력" #: spa/plugins/alsa/acp/alsa-mixer.c:2711 msgid "Docking Station Microphone" msgstr "도킹 스테이션 마이크" #: spa/plugins/alsa/acp/alsa-mixer.c:2712 msgid "Docking Station Line In" msgstr "도킹 스테이션 라인 입력 " #: spa/plugins/alsa/acp/alsa-mixer.c:2713 #: spa/plugins/alsa/acp/alsa-mixer.c:2804 msgid "Line In" msgstr "라인 입력 " #: spa/plugins/alsa/acp/alsa-mixer.c:2714 #: spa/plugins/alsa/acp/alsa-mixer.c:2798 #: spa/plugins/bluez5/bluez5-device.c:1145 msgid "Microphone" msgstr "마이크" #: spa/plugins/alsa/acp/alsa-mixer.c:2715 #: spa/plugins/alsa/acp/alsa-mixer.c:2799 msgid "Front Microphone" msgstr "전면 마이크 " #: spa/plugins/alsa/acp/alsa-mixer.c:2716 #: spa/plugins/alsa/acp/alsa-mixer.c:2800 msgid "Rear Microphone" msgstr "후면 마이크 " #: spa/plugins/alsa/acp/alsa-mixer.c:2717 msgid "External Microphone" msgstr "외부 마이크 " #: spa/plugins/alsa/acp/alsa-mixer.c:2718 #: spa/plugins/alsa/acp/alsa-mixer.c:2802 msgid "Internal Microphone" msgstr "내부 마이크 " #: spa/plugins/alsa/acp/alsa-mixer.c:2719 #: spa/plugins/alsa/acp/alsa-mixer.c:2805 msgid "Radio" msgstr "라디오 " #: spa/plugins/alsa/acp/alsa-mixer.c:2720 #: spa/plugins/alsa/acp/alsa-mixer.c:2806 msgid "Video" msgstr "비디오 " #: spa/plugins/alsa/acp/alsa-mixer.c:2721 msgid "Automatic Gain Control" msgstr "자동 게인 컨트롤" #: spa/plugins/alsa/acp/alsa-mixer.c:2722 msgid "No Automatic Gain Control" msgstr "자동 게인 컨트롤 없음" #: spa/plugins/alsa/acp/alsa-mixer.c:2723 msgid "Boost" msgstr "부스트" #: spa/plugins/alsa/acp/alsa-mixer.c:2724 msgid "No Boost" msgstr "부스트 없음" #: spa/plugins/alsa/acp/alsa-mixer.c:2725 msgid "Amplifier" msgstr "증폭" #: spa/plugins/alsa/acp/alsa-mixer.c:2726 msgid "No Amplifier" msgstr "증폭 없음" #: spa/plugins/alsa/acp/alsa-mixer.c:2727 msgid "Bass Boost" msgstr "베이스 부스트" #: spa/plugins/alsa/acp/alsa-mixer.c:2728 msgid "No Bass Boost" msgstr "베이스 부스트 없음" #: spa/plugins/alsa/acp/alsa-mixer.c:2729 #: spa/plugins/bluez5/bluez5-device.c:1150 msgid "Speaker" msgstr "스피커" #: spa/plugins/alsa/acp/alsa-mixer.c:2730 #: spa/plugins/alsa/acp/alsa-mixer.c:2808 msgid "Headphones" msgstr "헤드폰" #: spa/plugins/alsa/acp/alsa-mixer.c:2797 msgid "Analog Input" msgstr "아날로그 입력" #: spa/plugins/alsa/acp/alsa-mixer.c:2801 msgid "Dock Microphone" msgstr "도킹 스테이션 마이크 " #: spa/plugins/alsa/acp/alsa-mixer.c:2803 #, fuzzy msgid "Headset Microphone" msgstr "후면 마이크 " #: spa/plugins/alsa/acp/alsa-mixer.c:2807 msgid "Analog Output" msgstr "아날로그 출력" #: spa/plugins/alsa/acp/alsa-mixer.c:2809 #, fuzzy msgid "Headphones 2" msgstr "헤드폰" #: spa/plugins/alsa/acp/alsa-mixer.c:2810 #, fuzzy msgid "Headphones Mono Output" msgstr "아날로그 모노 출력" #: spa/plugins/alsa/acp/alsa-mixer.c:2811 msgid "Line Out" msgstr "라인 출력 " #: spa/plugins/alsa/acp/alsa-mixer.c:2812 msgid "Analog Mono Output" msgstr "아날로그 모노 출력" #: spa/plugins/alsa/acp/alsa-mixer.c:2813 msgid "Speakers" msgstr "스피커" #: spa/plugins/alsa/acp/alsa-mixer.c:2814 msgid "HDMI / DisplayPort" msgstr "HDMI / DisplayPort" #: spa/plugins/alsa/acp/alsa-mixer.c:2815 msgid "Digital Output (S/PDIF)" msgstr "디지털 출력 (S/PDIF)" #: spa/plugins/alsa/acp/alsa-mixer.c:2816 msgid "Digital Input (S/PDIF)" msgstr "디지털 입력 (S/PDIF)" #: spa/plugins/alsa/acp/alsa-mixer.c:2817 #, fuzzy msgid "Multichannel Input" msgstr "아날로그 4-채널 입력" #: spa/plugins/alsa/acp/alsa-mixer.c:2818 #, fuzzy msgid "Multichannel Output" msgstr "빈 출력" #: spa/plugins/alsa/acp/alsa-mixer.c:2819 #, fuzzy msgid "Game Output" msgstr "%s 출력" #: spa/plugins/alsa/acp/alsa-mixer.c:2820 #: spa/plugins/alsa/acp/alsa-mixer.c:2821 #, fuzzy msgid "Chat Output" msgstr "%s 출력" #: spa/plugins/alsa/acp/alsa-mixer.c:2822 #, fuzzy msgid "Chat Input" msgstr "%s 입력" #: spa/plugins/alsa/acp/alsa-mixer.c:2823 #, fuzzy msgid "Virtual Surround 7.1" msgstr "가상 서라운드 싱크 " #: spa/plugins/alsa/acp/alsa-mixer.c:4527 msgid "Analog Mono" msgstr "아날로그 모노 " #: spa/plugins/alsa/acp/alsa-mixer.c:4528 #, fuzzy msgid "Analog Mono (Left)" msgstr "아날로그 모노 " #: spa/plugins/alsa/acp/alsa-mixer.c:4529 #, fuzzy msgid "Analog Mono (Right)" msgstr "아날로그 모노 " #. Note: Not translated to "Analog Stereo Input", because the source #. * name gets "Input" appended to it automatically, so adding "Input" #. * here would lead to the source name to become "Analog Stereo Input #. * Input". The same logic applies to analog-stereo-output, #. * multichannel-input and multichannel-output. #: spa/plugins/alsa/acp/alsa-mixer.c:4530 #: spa/plugins/alsa/acp/alsa-mixer.c:4538 #: spa/plugins/alsa/acp/alsa-mixer.c:4539 msgid "Analog Stereo" msgstr "아날로그 스테레오 " #: spa/plugins/alsa/acp/alsa-mixer.c:4531 msgid "Mono" msgstr "모노" #: spa/plugins/alsa/acp/alsa-mixer.c:4532 msgid "Stereo" msgstr "스테레오" #: spa/plugins/alsa/acp/alsa-mixer.c:4540 #: spa/plugins/alsa/acp/alsa-mixer.c:4698 #: spa/plugins/bluez5/bluez5-device.c:1135 msgid "Headset" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4541 #: spa/plugins/alsa/acp/alsa-mixer.c:4699 #, fuzzy msgid "Speakerphone" msgstr "스피커" #: spa/plugins/alsa/acp/alsa-mixer.c:4542 #: spa/plugins/alsa/acp/alsa-mixer.c:4543 msgid "Multichannel" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4544 msgid "Analog Surround 2.1" msgstr "아날로그 서라운드 2.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4545 msgid "Analog Surround 3.0" msgstr "아날로그 서라운드 3.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4546 msgid "Analog Surround 3.1" msgstr "아날로그 서라운드 3.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4547 msgid "Analog Surround 4.0" msgstr "아날로그 서라운드 4.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4548 msgid "Analog Surround 4.1" msgstr "아날로그 서라운드 4.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4549 msgid "Analog Surround 5.0" msgstr "아날로그 서라운드 5.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4550 msgid "Analog Surround 5.1" msgstr "아날로그 서라운드 5.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4551 msgid "Analog Surround 6.0" msgstr "아날로그 서라운드 6.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4552 msgid "Analog Surround 6.1" msgstr "아날로그 서라운드 6.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4553 msgid "Analog Surround 7.0" msgstr "아날로그 서라운드 7.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4554 msgid "Analog Surround 7.1" msgstr "아날로그 서라운드 7.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4555 msgid "Digital Stereo (IEC958)" msgstr "디지털 스테레오 (IEC958)" #: spa/plugins/alsa/acp/alsa-mixer.c:4556 msgid "Digital Surround 4.0 (IEC958/AC3)" msgstr "디지털 서라운드 4.0 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4557 msgid "Digital Surround 5.1 (IEC958/AC3)" msgstr "디지털 서라운드 5.1 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4558 msgid "Digital Surround 5.1 (IEC958/DTS)" msgstr "디지털 서라운드 5.1 (IEC958/DTS)" #: spa/plugins/alsa/acp/alsa-mixer.c:4559 msgid "Digital Stereo (HDMI)" msgstr "디지털 스테레오 (HDMI)" #: spa/plugins/alsa/acp/alsa-mixer.c:4560 msgid "Digital Surround 5.1 (HDMI)" msgstr "디지털 서라운드 5.1 (HDMI)" #: spa/plugins/alsa/acp/alsa-mixer.c:4561 msgid "Chat" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4562 msgid "Game" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4696 msgid "Analog Mono Duplex" msgstr "아날로그 양방향 모노" #: spa/plugins/alsa/acp/alsa-mixer.c:4697 msgid "Analog Stereo Duplex" msgstr "아날로그 양방향 스테레오" #: spa/plugins/alsa/acp/alsa-mixer.c:4700 msgid "Digital Stereo Duplex (IEC958)" msgstr "아날로그 양방향 스테레오 (IEC958)" #: spa/plugins/alsa/acp/alsa-mixer.c:4701 msgid "Multichannel Duplex" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4702 #, fuzzy msgid "Stereo Duplex" msgstr "아날로그 양방향 스테레오" #: spa/plugins/alsa/acp/alsa-mixer.c:4703 msgid "Mono Chat + 7.1 Surround" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4806 #, c-format msgid "%s Output" msgstr "%s 출력" #: spa/plugins/alsa/acp/alsa-mixer.c:4813 #, c-format msgid "%s Input" msgstr "%s 입력" #: spa/plugins/alsa/acp/alsa-util.c:1175 spa/plugins/alsa/acp/alsa-util.c:1269 #, fuzzy, c-format msgid "" "snd_pcm_avail() returned a value that is exceptionally large: %lu byte (%lu " "ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_avail() returned a value that is exceptionally large: %lu bytes (%lu " "ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "snd_pcm_avail()이 %lu 바이트 (%lu ms)의 매우 큰 값을 반환했습니다.\n" "ALSA 드라이버 '%s'의 오류일 수 있습니다. ALSA 개발자에게 이 문제를 보고해주시" "기 바랍니다." #: spa/plugins/alsa/acp/alsa-util.c:1241 #, fuzzy, c-format msgid "" "snd_pcm_delay() returned a value that is exceptionally large: %li byte (%s" "%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_delay() returned a value that is exceptionally large: %li bytes (%s" "%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "snd_pcm_delay()가 %li 바이트 (%s%lu ms)의 매우 큰 값을 반환했습니다.\n" "ALSA 드라이버 '%s'의 오류일 수 있습니다. ALSA 개발자에게 이 문제를 보고해주시" "기 바랍니다." #: spa/plugins/alsa/acp/alsa-util.c:1288 #, c-format msgid "" "snd_pcm_avail_delay() returned strange values: delay %lu is less than avail " "%lu.\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr "" "snd_pcm_avail_delay()가 이상한 값을 반환했습니다: 지연 시간 %lu은 사용 가능" "한 시간 %lu 보다 작습니다.\n" "ALSA 드라이버 '%s'의 오류일 수 있습니다. ALSA 개발자에게 이 문제를 보고해 주" "시기 바랍니다." #: spa/plugins/alsa/acp/alsa-util.c:1331 #, fuzzy, c-format msgid "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu byte " "(%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu bytes " "(%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "snd_pcm_mmap_begin()이 %lu 바이트 (%lu ms)의 매우 큰 값을 반환했습니다.\n" "ALSA 드라이버 '%s'의 오류일 수 있습니다. ALSA 개발자에게 이 문제를 보고해 주" "시기 바랍니다." #: spa/plugins/bluez5/bluez5-device.c:1010 msgid "Audio Gateway (A2DP Source & HSP/HFP AG)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1033 #, c-format msgid "High Fidelity Playback (A2DP Sink, codec %s)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1035 #, c-format msgid "High Fidelity Duplex (A2DP Source/Sink, codec %s)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1041 msgid "High Fidelity Playback (A2DP Sink)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1043 msgid "High Fidelity Duplex (A2DP Source/Sink)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1070 #, c-format msgid "Headset Head Unit (HSP/HFP, codec %s)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1074 msgid "Headset Head Unit (HSP/HFP)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1140 #, fuzzy msgid "Handsfree" msgstr "핸즈프리 게이트웨이" #: spa/plugins/bluez5/bluez5-device.c:1155 #, fuzzy msgid "Headphone" msgstr "헤드폰" #: spa/plugins/bluez5/bluez5-device.c:1160 msgid "Portable" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1165 msgid "Car" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1170 msgid "HiFi" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1175 msgid "Phone" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1181 #, fuzzy msgid "Bluetooth" msgstr "블루투스 출력 " pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/po/lt.po000066400000000000000000000432171511204443500221020ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # Moo, 2017-2019 # msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/pipewire/" "issues/new\n" "POT-Creation-Date: 2021-04-18 16:54+0800\n" "PO-Revision-Date: 2019-09-01 16:15+0300\n" "Last-Translator: Moo\n" "Language-Team: \n" "Language: lt\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Generator: Poedit 2.2.1\n" "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && (n" "%100<10 || n%100>=20) ? 1 : 2);\n" #: src/daemon/pipewire.c:43 #, c-format msgid "" "%s [options]\n" " -h, --help Show this help\n" " --version Show version\n" " -c, --config Load config (Default %s)\n" msgstr "" #: src/daemon/pipewire.desktop.in:4 msgid "PipeWire Media System" msgstr "" #: src/daemon/pipewire.desktop.in:5 msgid "Start the PipeWire Media System" msgstr "" #: src/examples/media-session/alsa-monitor.c:526 #: spa/plugins/alsa/acp/compat.c:187 msgid "Built-in Audio" msgstr "Įtaisytas garsas" #: src/examples/media-session/alsa-monitor.c:530 #: spa/plugins/alsa/acp/compat.c:192 msgid "Modem" msgstr "Modemas" #: src/examples/media-session/alsa-monitor.c:539 msgid "Unknown device" msgstr "" #: src/tools/pw-cat.c:991 #, c-format msgid "" "%s [options] \n" " -h, --help Show this help\n" " --version Show version\n" " -v, --verbose Enable verbose operations\n" "\n" msgstr "" #: src/tools/pw-cat.c:998 #, c-format msgid "" " -R, --remote Remote daemon name\n" " --media-type Set media type (default %s)\n" " --media-category Set media category (default %s)\n" " --media-role Set media role (default %s)\n" " --target Set node target (default %s)\n" " 0 means don't link\n" " --latency Set node latency (default %s)\n" " Xunit (unit = s, ms, us, ns)\n" " or direct samples (256)\n" " the rate is the one of the source " "file\n" " --list-targets List available targets for --target\n" "\n" msgstr "" #: src/tools/pw-cat.c:1016 #, c-format msgid "" " --rate Sample rate (req. for rec) (default " "%u)\n" " --channels Number of channels (req. for rec) " "(default %u)\n" " --channel-map Channel map\n" " one of: \"stereo\", " "\"surround-51\",... or\n" " comma separated list of channel " "names: eg. \"FL,FR\"\n" " --format Sample format %s (req. for rec) " "(default %s)\n" " --volume Stream volume 0-1.0 (default %.3f)\n" " -q --quality Resampler quality (0 - 15) (default " "%d)\n" "\n" msgstr "" #: src/tools/pw-cat.c:1033 msgid "" " -p, --playback Playback mode\n" " -r, --record Recording mode\n" " -m, --midi Midi mode\n" "\n" msgstr "" #: src/tools/pw-cli.c:2932 #, c-format msgid "" "%s [options] [command]\n" " -h, --help Show this help\n" " --version Show version\n" " -d, --daemon Start as daemon (Default false)\n" " -r, --remote Remote daemon name\n" "\n" msgstr "" #: spa/plugins/alsa/acp/acp.c:290 msgid "Pro Audio" msgstr "" #: spa/plugins/alsa/acp/acp.c:411 spa/plugins/alsa/acp/alsa-mixer.c:4704 #: spa/plugins/bluez5/bluez5-device.c:1000 msgid "Off" msgstr "Išjungta" #: spa/plugins/alsa/acp/channelmap.h:466 msgid "(invalid)" msgstr "(neteisinga)" #: spa/plugins/alsa/acp/alsa-mixer.c:2709 msgid "Input" msgstr "Įvestis" #: spa/plugins/alsa/acp/alsa-mixer.c:2710 msgid "Docking Station Input" msgstr "Sujungimo stoties įvestis" #: spa/plugins/alsa/acp/alsa-mixer.c:2711 msgid "Docking Station Microphone" msgstr "Sujungimo stoties mikrofonas" #: spa/plugins/alsa/acp/alsa-mixer.c:2712 msgid "Docking Station Line In" msgstr "Sujungimo stoties įvadinė linija" #: spa/plugins/alsa/acp/alsa-mixer.c:2713 #: spa/plugins/alsa/acp/alsa-mixer.c:2804 msgid "Line In" msgstr "Įvadinė linija" #: spa/plugins/alsa/acp/alsa-mixer.c:2714 #: spa/plugins/alsa/acp/alsa-mixer.c:2798 #: spa/plugins/bluez5/bluez5-device.c:1145 msgid "Microphone" msgstr "Mikrofonas" #: spa/plugins/alsa/acp/alsa-mixer.c:2715 #: spa/plugins/alsa/acp/alsa-mixer.c:2799 msgid "Front Microphone" msgstr "Priekinis mikrofonas" #: spa/plugins/alsa/acp/alsa-mixer.c:2716 #: spa/plugins/alsa/acp/alsa-mixer.c:2800 msgid "Rear Microphone" msgstr "Galinis mikrofonas" #: spa/plugins/alsa/acp/alsa-mixer.c:2717 msgid "External Microphone" msgstr "Išorinis mikrofonas" #: spa/plugins/alsa/acp/alsa-mixer.c:2718 #: spa/plugins/alsa/acp/alsa-mixer.c:2802 msgid "Internal Microphone" msgstr "Vidinis mikrofonas" #: spa/plugins/alsa/acp/alsa-mixer.c:2719 #: spa/plugins/alsa/acp/alsa-mixer.c:2805 msgid "Radio" msgstr "Radijas" #: spa/plugins/alsa/acp/alsa-mixer.c:2720 #: spa/plugins/alsa/acp/alsa-mixer.c:2806 msgid "Video" msgstr "Vaizdas" #: spa/plugins/alsa/acp/alsa-mixer.c:2721 msgid "Automatic Gain Control" msgstr "Automatinis stiprinimo reguliavimas" #: spa/plugins/alsa/acp/alsa-mixer.c:2722 msgid "No Automatic Gain Control" msgstr "Be automatinio stiprinimo reguliavimo" #: spa/plugins/alsa/acp/alsa-mixer.c:2723 msgid "Boost" msgstr "Pastiprinimas" #: spa/plugins/alsa/acp/alsa-mixer.c:2724 msgid "No Boost" msgstr "Be pastiprinimo" #: spa/plugins/alsa/acp/alsa-mixer.c:2725 msgid "Amplifier" msgstr "Stiprintuvas" #: spa/plugins/alsa/acp/alsa-mixer.c:2726 msgid "No Amplifier" msgstr "Be stiprintuvo" #: spa/plugins/alsa/acp/alsa-mixer.c:2727 msgid "Bass Boost" msgstr "Žemų tonų pastiprinimas" #: spa/plugins/alsa/acp/alsa-mixer.c:2728 msgid "No Bass Boost" msgstr "Be žemų tonų pastiprinimo" #: spa/plugins/alsa/acp/alsa-mixer.c:2729 #: spa/plugins/bluez5/bluez5-device.c:1150 msgid "Speaker" msgstr "Garsiakalbis" #: spa/plugins/alsa/acp/alsa-mixer.c:2730 #: spa/plugins/alsa/acp/alsa-mixer.c:2808 msgid "Headphones" msgstr "Ausinės" #: spa/plugins/alsa/acp/alsa-mixer.c:2797 msgid "Analog Input" msgstr "Analoginė įvestis" #: spa/plugins/alsa/acp/alsa-mixer.c:2801 msgid "Dock Microphone" msgstr "Doko mikrofonas" #: spa/plugins/alsa/acp/alsa-mixer.c:2803 msgid "Headset Microphone" msgstr "Ausinių mikrofonas" #: spa/plugins/alsa/acp/alsa-mixer.c:2807 msgid "Analog Output" msgstr "Analoginė išvestis" #: spa/plugins/alsa/acp/alsa-mixer.c:2809 #, fuzzy msgid "Headphones 2" msgstr "Ausinės" #: spa/plugins/alsa/acp/alsa-mixer.c:2810 msgid "Headphones Mono Output" msgstr "Ausinių mono išvestis" #: spa/plugins/alsa/acp/alsa-mixer.c:2811 msgid "Line Out" msgstr "Išvadinė linija" #: spa/plugins/alsa/acp/alsa-mixer.c:2812 msgid "Analog Mono Output" msgstr "Analoginė mono išvestis" #: spa/plugins/alsa/acp/alsa-mixer.c:2813 msgid "Speakers" msgstr "Garsiakalbiai" #: spa/plugins/alsa/acp/alsa-mixer.c:2814 msgid "HDMI / DisplayPort" msgstr "HDMI / DisplayPort" #: spa/plugins/alsa/acp/alsa-mixer.c:2815 msgid "Digital Output (S/PDIF)" msgstr "Skaitmeninė išvestis (S/PDIF)" #: spa/plugins/alsa/acp/alsa-mixer.c:2816 msgid "Digital Input (S/PDIF)" msgstr "Skaitmeninė įvestis (S/PDIF)" #: spa/plugins/alsa/acp/alsa-mixer.c:2817 msgid "Multichannel Input" msgstr "Daugiakanalė įvestis" #: spa/plugins/alsa/acp/alsa-mixer.c:2818 msgid "Multichannel Output" msgstr "Daugiakanalė išvestis" #: spa/plugins/alsa/acp/alsa-mixer.c:2819 msgid "Game Output" msgstr "Žaidimo išvestis" #: spa/plugins/alsa/acp/alsa-mixer.c:2820 #: spa/plugins/alsa/acp/alsa-mixer.c:2821 msgid "Chat Output" msgstr "Pokalbio išvestis" #: spa/plugins/alsa/acp/alsa-mixer.c:2822 #, fuzzy msgid "Chat Input" msgstr "Pokalbio išvestis" #: spa/plugins/alsa/acp/alsa-mixer.c:2823 #, fuzzy msgid "Virtual Surround 7.1" msgstr "Virtualus erdvinis rinktuvas" #: spa/plugins/alsa/acp/alsa-mixer.c:4527 msgid "Analog Mono" msgstr "Analoginė mono" #: spa/plugins/alsa/acp/alsa-mixer.c:4528 #, fuzzy msgid "Analog Mono (Left)" msgstr "Analoginė mono" #: spa/plugins/alsa/acp/alsa-mixer.c:4529 #, fuzzy msgid "Analog Mono (Right)" msgstr "Analoginė mono" #. Note: Not translated to "Analog Stereo Input", because the source #. * name gets "Input" appended to it automatically, so adding "Input" #. * here would lead to the source name to become "Analog Stereo Input #. * Input". The same logic applies to analog-stereo-output, #. * multichannel-input and multichannel-output. #: spa/plugins/alsa/acp/alsa-mixer.c:4530 #: spa/plugins/alsa/acp/alsa-mixer.c:4538 #: spa/plugins/alsa/acp/alsa-mixer.c:4539 msgid "Analog Stereo" msgstr "Analoginė stereo" #: spa/plugins/alsa/acp/alsa-mixer.c:4531 msgid "Mono" msgstr "Mono" #: spa/plugins/alsa/acp/alsa-mixer.c:4532 msgid "Stereo" msgstr "Stereo" #: spa/plugins/alsa/acp/alsa-mixer.c:4540 #: spa/plugins/alsa/acp/alsa-mixer.c:4698 #: spa/plugins/bluez5/bluez5-device.c:1135 msgid "Headset" msgstr "Ausinės su mikrofonu" #: spa/plugins/alsa/acp/alsa-mixer.c:4541 #: spa/plugins/alsa/acp/alsa-mixer.c:4699 #, fuzzy msgid "Speakerphone" msgstr "Garsiakalbis" #: spa/plugins/alsa/acp/alsa-mixer.c:4542 #: spa/plugins/alsa/acp/alsa-mixer.c:4543 msgid "Multichannel" msgstr "Daugiakanalė" #: spa/plugins/alsa/acp/alsa-mixer.c:4544 msgid "Analog Surround 2.1" msgstr "Analoginė erdvinė 2.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4545 msgid "Analog Surround 3.0" msgstr "Analoginė erdvinė 3.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4546 msgid "Analog Surround 3.1" msgstr "Analoginė erdvinė 3.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4547 msgid "Analog Surround 4.0" msgstr "Analoginė erdvinė 4.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4548 msgid "Analog Surround 4.1" msgstr "Analoginė erdvinė 4.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4549 msgid "Analog Surround 5.0" msgstr "Analoginė erdvinė 5.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4550 msgid "Analog Surround 5.1" msgstr "Analoginė erdvinė 5.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4551 msgid "Analog Surround 6.0" msgstr "Analoginė erdvinė 6.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4552 msgid "Analog Surround 6.1" msgstr "Analoginė erdvinė 6.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4553 msgid "Analog Surround 7.0" msgstr "Analoginė erdvinė 7.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4554 msgid "Analog Surround 7.1" msgstr "Analoginė erdvinė 7.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4555 msgid "Digital Stereo (IEC958)" msgstr "Skaitmeninė stereo (IEC958)" #: spa/plugins/alsa/acp/alsa-mixer.c:4556 msgid "Digital Surround 4.0 (IEC958/AC3)" msgstr "Skaitmeninė erdvinė 4.0 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4557 msgid "Digital Surround 5.1 (IEC958/AC3)" msgstr "Skaitmeninė erdvinė 5.1 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4558 msgid "Digital Surround 5.1 (IEC958/DTS)" msgstr "Skaitmeninė erdvinė 5.1 (IEC958/DTS)" #: spa/plugins/alsa/acp/alsa-mixer.c:4559 msgid "Digital Stereo (HDMI)" msgstr "Skaitmeninė stereo (HDMI)" #: spa/plugins/alsa/acp/alsa-mixer.c:4560 msgid "Digital Surround 5.1 (HDMI)" msgstr "Skaitmeninė erdvinė 5.1 (HDMI)" #: spa/plugins/alsa/acp/alsa-mixer.c:4561 msgid "Chat" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4562 msgid "Game" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4696 msgid "Analog Mono Duplex" msgstr "Analoginė dvipusė mono" #: spa/plugins/alsa/acp/alsa-mixer.c:4697 msgid "Analog Stereo Duplex" msgstr "Analoginė dvipusė stereo" #: spa/plugins/alsa/acp/alsa-mixer.c:4700 msgid "Digital Stereo Duplex (IEC958)" msgstr "Skaitmeninė dvipusė stereo (IEC958)" #: spa/plugins/alsa/acp/alsa-mixer.c:4701 msgid "Multichannel Duplex" msgstr "Daugiakanalė dvipusė" #: spa/plugins/alsa/acp/alsa-mixer.c:4702 msgid "Stereo Duplex" msgstr "Dvipusė stereo" #: spa/plugins/alsa/acp/alsa-mixer.c:4703 msgid "Mono Chat + 7.1 Surround" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4806 #, c-format msgid "%s Output" msgstr "%s išvestis" #: spa/plugins/alsa/acp/alsa-mixer.c:4813 #, c-format msgid "%s Input" msgstr "%s įvestis" #: spa/plugins/alsa/acp/alsa-util.c:1175 spa/plugins/alsa/acp/alsa-util.c:1269 #, c-format msgid "" "snd_pcm_avail() returned a value that is exceptionally large: %lu byte (%lu " "ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_avail() returned a value that is exceptionally large: %lu bytes (%lu " "ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "snd_pcm_avail() grąžino reikšmę, kuri yra išskirtinai didelė: %lu baitas " "(%lu ms).\n" "Greičiausiai, tai yra klaida ALSA \"%s\" tvarkyklėje. Prašome apie šią " "klaidą pranešti ALSA kūrėjams." msgstr[1] "" "snd_pcm_avail() grąžino reikšmę, kuri yra išskirtinai didelė: %lu baitai " "(%lu ms).\n" "Greičiausiai, tai yra klaida ALSA \"%s\" tvarkyklėje. Prašome apie šią " "klaidą pranešti ALSA kūrėjams." msgstr[2] "" "snd_pcm_avail() grąžino reikšmę, kuri yra išskirtinai didelė: %lu baitų (%lu " "ms).\n" "Greičiausiai, tai yra klaida ALSA \"%s\" tvarkyklėje. Prašome apie šią " "klaidą pranešti ALSA kūrėjams." #: spa/plugins/alsa/acp/alsa-util.c:1241 #, c-format msgid "" "snd_pcm_delay() returned a value that is exceptionally large: %li byte (%s" "%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_delay() returned a value that is exceptionally large: %li bytes (%s" "%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "snd_pcm_delay() grąžino reikšmę, kuri yra išskirtinai didelė: %li baitas (%s" "%lu ms).\n" "Greičiausiai, tai yra klaida ALSA \"%s\" tvarkyklėje. Prašome apie šią " "klaidą pranešti ALSA kūrėjams." msgstr[1] "" "snd_pcm_delay() grąžino reikšmę, kuri yra išskirtinai didelė: %li baitai (%s" "%lu ms).\n" "Greičiausiai, tai yra klaida ALSA \"%s\" tvarkyklėje. Prašome apie šią " "klaidą pranešti ALSA kūrėjams." msgstr[2] "" "snd_pcm_delay() grąžino reikšmę, kuri yra išskirtinai didelė: %li baitų (%s" "%lu ms).\n" "Greičiausiai, tai yra klaida ALSA \"%s\" tvarkyklėje. Prašome apie šią " "klaidą pranešti ALSA kūrėjams." #: spa/plugins/alsa/acp/alsa-util.c:1288 #, c-format msgid "" "snd_pcm_avail_delay() returned strange values: delay %lu is less than avail " "%lu.\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr "" "snd_pcm_avail_delay() grąžino keistas reikšmes: delsa %lu yra mažesnė, nei " "prieinama %lu.\n" "Greičiausiai, tai yra klaida ALSA \"'%s\" tvarkyklėje. Prašome apie šią " "klaidą pranešti ALSA kūrėjams." #: spa/plugins/alsa/acp/alsa-util.c:1331 #, c-format msgid "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu byte " "(%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu bytes " "(%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "snd_pcm_mmap_begin() grąžino reikšmę, kuri yra išskirtinai didelė: %lu " "baitas (%lu ms).\n" "Greičiausiai, tai yra klaida ALSA \"%s\" tvarkyklėje. Prašome apie šią " "klaidą pranešti ALSA kūrėjams." msgstr[1] "" "snd_pcm_mmap_begin() grąžino reikšmę, kuri yra išskirtinai didelė: %lu " "baitai (%lu ms).\n" "Greičiausiai, tai yra klaida ALSA \"%s\" tvarkyklėje. Prašome apie šią " "klaidą pranešti ALSA kūrėjams." msgstr[2] "" "snd_pcm_mmap_begin() grąžino reikšmę, kuri yra išskirtinai didelė: %lu baitų " "(%lu ms).\n" "Greičiausiai, tai yra klaida ALSA \"%s\" tvarkyklėje. Prašome apie šią " "klaidą pranešti ALSA kūrėjams." #: spa/plugins/bluez5/bluez5-device.c:1010 msgid "Audio Gateway (A2DP Source & HSP/HFP AG)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1033 #, c-format msgid "High Fidelity Playback (A2DP Sink, codec %s)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1035 #, c-format msgid "High Fidelity Duplex (A2DP Source/Sink, codec %s)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1041 msgid "High Fidelity Playback (A2DP Sink)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1043 msgid "High Fidelity Duplex (A2DP Source/Sink)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1070 #, c-format msgid "Headset Head Unit (HSP/HFP, codec %s)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1074 msgid "Headset Head Unit (HSP/HFP)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1140 msgid "Handsfree" msgstr "Laisvų rankų įranga" #: spa/plugins/bluez5/bluez5-device.c:1155 msgid "Headphone" msgstr "Ausinė" #: spa/plugins/bluez5/bluez5-device.c:1160 msgid "Portable" msgstr "Portatyvi sistema" #: spa/plugins/bluez5/bluez5-device.c:1165 msgid "Car" msgstr "Automobilis" #: spa/plugins/bluez5/bluez5-device.c:1170 msgid "HiFi" msgstr "HiFi" #: spa/plugins/bluez5/bluez5-device.c:1175 msgid "Phone" msgstr "Telefonas" #: spa/plugins/bluez5/bluez5-device.c:1181 #, fuzzy msgid "Bluetooth" msgstr "Bluetooth įvestis" pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/po/meson.build000066400000000000000000000005231511204443500232560ustar00rootroot00000000000000i18n = import('i18n') i18n.gettext( meson.project_name(), preset: 'glib', # Page width is set to 90 characters in order to avoid bad wrapping of the # bug reporting address. args: ['--msgid-bugs-address=https://gitlab.freedesktop.org/pipewire/pipewire/issues/new', '--width=90'], ) po_dir = meson.current_source_dir() pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/po/ml.po000066400000000000000000000500631511204443500220700ustar00rootroot00000000000000# # <>, YEAR, 2012. # msgid "" msgstr "" "Project-Id-Version: pipewire.master-tx.ml\n" "Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/pipewire/" "issues/new\n" "POT-Creation-Date: 2021-04-18 16:54+0800\n" "PO-Revision-Date: 2012-01-30 09:41+0000\n" "Last-Translator: \n" "Language-Team: \n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Generator: KBabel 1.11.4\n" #: src/daemon/pipewire.c:43 #, c-format msgid "" "%s [options]\n" " -h, --help Show this help\n" " --version Show version\n" " -c, --config Load config (Default %s)\n" msgstr "" #: src/daemon/pipewire.desktop.in:4 msgid "PipeWire Media System" msgstr "" #: src/daemon/pipewire.desktop.in:5 msgid "Start the PipeWire Media System" msgstr "" #: src/examples/media-session/alsa-monitor.c:526 #: spa/plugins/alsa/acp/compat.c:187 msgid "Built-in Audio" msgstr "ഇന്റേര്‍ണല്‍ ഓഡിയോ" #: src/examples/media-session/alsa-monitor.c:530 #: spa/plugins/alsa/acp/compat.c:192 msgid "Modem" msgstr "മോഡം" #: src/examples/media-session/alsa-monitor.c:539 msgid "Unknown device" msgstr "" #: src/tools/pw-cat.c:991 #, c-format msgid "" "%s [options] \n" " -h, --help Show this help\n" " --version Show version\n" " -v, --verbose Enable verbose operations\n" "\n" msgstr "" #: src/tools/pw-cat.c:998 #, c-format msgid "" " -R, --remote Remote daemon name\n" " --media-type Set media type (default %s)\n" " --media-category Set media category (default %s)\n" " --media-role Set media role (default %s)\n" " --target Set node target (default %s)\n" " 0 means don't link\n" " --latency Set node latency (default %s)\n" " Xunit (unit = s, ms, us, ns)\n" " or direct samples (256)\n" " the rate is the one of the source " "file\n" " --list-targets List available targets for --target\n" "\n" msgstr "" #: src/tools/pw-cat.c:1016 #, c-format msgid "" " --rate Sample rate (req. for rec) (default " "%u)\n" " --channels Number of channels (req. for rec) " "(default %u)\n" " --channel-map Channel map\n" " one of: \"stereo\", " "\"surround-51\",... or\n" " comma separated list of channel " "names: eg. \"FL,FR\"\n" " --format Sample format %s (req. for rec) " "(default %s)\n" " --volume Stream volume 0-1.0 (default %.3f)\n" " -q --quality Resampler quality (0 - 15) (default " "%d)\n" "\n" msgstr "" #: src/tools/pw-cat.c:1033 msgid "" " -p, --playback Playback mode\n" " -r, --record Recording mode\n" " -m, --midi Midi mode\n" "\n" msgstr "" #: src/tools/pw-cli.c:2932 #, c-format msgid "" "%s [options] [command]\n" " -h, --help Show this help\n" " --version Show version\n" " -d, --daemon Start as daemon (Default false)\n" " -r, --remote Remote daemon name\n" "\n" msgstr "" #: spa/plugins/alsa/acp/acp.c:290 msgid "Pro Audio" msgstr "" #: spa/plugins/alsa/acp/acp.c:411 spa/plugins/alsa/acp/alsa-mixer.c:4704 #: spa/plugins/bluez5/bluez5-device.c:1000 msgid "Off" msgstr "ഓഫ്" #: spa/plugins/alsa/acp/channelmap.h:466 msgid "(invalid)" msgstr "(അസാധു)" #: spa/plugins/alsa/acp/alsa-mixer.c:2709 msgid "Input" msgstr "ഇന്‍പുട്ട്" #: spa/plugins/alsa/acp/alsa-mixer.c:2710 msgid "Docking Station Input" msgstr "ഡോക്കിങ് സ്റ്റേഷന്‍ ഇന്‍പുട്ട്" #: spa/plugins/alsa/acp/alsa-mixer.c:2711 #, fuzzy msgid "Docking Station Microphone" msgstr "ഡോക്കിങ് സ്റ്റേഷന്‍ മൈക്രോഫോണ്‍" #: spa/plugins/alsa/acp/alsa-mixer.c:2712 #, fuzzy msgid "Docking Station Line In" msgstr "ഡോക്കിങ് സ്റ്റേഷന്‍ ഇന്‍പുട്ട്" #: spa/plugins/alsa/acp/alsa-mixer.c:2713 #: spa/plugins/alsa/acp/alsa-mixer.c:2804 msgid "Line In" msgstr "അനലോഗ് ലൈന്‍-ഇന്‍" #: spa/plugins/alsa/acp/alsa-mixer.c:2714 #: spa/plugins/alsa/acp/alsa-mixer.c:2798 #: spa/plugins/bluez5/bluez5-device.c:1145 msgid "Microphone" msgstr "മൈക്രോഫോണ്‍" #: spa/plugins/alsa/acp/alsa-mixer.c:2715 #: spa/plugins/alsa/acp/alsa-mixer.c:2799 #, fuzzy msgid "Front Microphone" msgstr "ഡോക്കിങ് സ്റ്റേഷന്‍ മൈക്രോഫോണ്‍" #: spa/plugins/alsa/acp/alsa-mixer.c:2716 #: spa/plugins/alsa/acp/alsa-mixer.c:2800 #, fuzzy msgid "Rear Microphone" msgstr "മൈക്രോഫോണ്‍" #: spa/plugins/alsa/acp/alsa-mixer.c:2717 msgid "External Microphone" msgstr "എക്സ്റ്റേണല്‍ മൈക്രോഫോണ്‍" #: spa/plugins/alsa/acp/alsa-mixer.c:2718 #: spa/plugins/alsa/acp/alsa-mixer.c:2802 msgid "Internal Microphone" msgstr "ഇന്റേണല്‍ മൈക്രോഫോണ്‍" #: spa/plugins/alsa/acp/alsa-mixer.c:2719 #: spa/plugins/alsa/acp/alsa-mixer.c:2805 msgid "Radio" msgstr "റേഡിയോ" #: spa/plugins/alsa/acp/alsa-mixer.c:2720 #: spa/plugins/alsa/acp/alsa-mixer.c:2806 msgid "Video" msgstr "വീഡിയോ" #: spa/plugins/alsa/acp/alsa-mixer.c:2721 msgid "Automatic Gain Control" msgstr "ഓട്ടോമാറ്റിക് ഗെയിന്‍ കണ്ട്രോള്‍" #: spa/plugins/alsa/acp/alsa-mixer.c:2722 msgid "No Automatic Gain Control" msgstr "ഓട്ടോമാറ്റിക് ഗെയിന്‍ കണ്ട്രോള്‍ ലഭ്യമല്ല" #: spa/plugins/alsa/acp/alsa-mixer.c:2723 msgid "Boost" msgstr "ബൂസ്റ്റ്" #: spa/plugins/alsa/acp/alsa-mixer.c:2724 msgid "No Boost" msgstr "ബൂസ്റ്റ് ലഭ്യമല്ല" #: spa/plugins/alsa/acp/alsa-mixer.c:2725 msgid "Amplifier" msgstr "ആംപ്ലിഫയര്‍" #: spa/plugins/alsa/acp/alsa-mixer.c:2726 msgid "No Amplifier" msgstr "ആംപ്ലിഫയര്‍ ലഭ്യമല്ല" #: spa/plugins/alsa/acp/alsa-mixer.c:2727 #, fuzzy msgid "Bass Boost" msgstr "ബൂസ്റ്റ്" #: spa/plugins/alsa/acp/alsa-mixer.c:2728 #, fuzzy msgid "No Bass Boost" msgstr "ബൂസ്റ്റ് ലഭ്യമല്ല" #: spa/plugins/alsa/acp/alsa-mixer.c:2729 #: spa/plugins/bluez5/bluez5-device.c:1150 msgid "Speaker" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2730 #: spa/plugins/alsa/acp/alsa-mixer.c:2808 msgid "Headphones" msgstr "അനലോഗ് ഹെഡ്ഫോണുകള്‍" #: spa/plugins/alsa/acp/alsa-mixer.c:2797 msgid "Analog Input" msgstr "അനലോഗ് ഇന്‍പുട്ട്" #: spa/plugins/alsa/acp/alsa-mixer.c:2801 msgid "Dock Microphone" msgstr "ഡോക്കിങ് സ്റ്റേഷന്‍ മൈക്രോഫോണ്‍" #: spa/plugins/alsa/acp/alsa-mixer.c:2803 #, fuzzy msgid "Headset Microphone" msgstr "മൈക്രോഫോണ്‍" #: spa/plugins/alsa/acp/alsa-mixer.c:2807 msgid "Analog Output" msgstr "അനലോഗ് ഔട്ട്പുട്ട്" #: spa/plugins/alsa/acp/alsa-mixer.c:2809 #, fuzzy msgid "Headphones 2" msgstr "അനലോഗ് ഹെഡ്ഫോണുകള്‍" #: spa/plugins/alsa/acp/alsa-mixer.c:2810 #, fuzzy msgid "Headphones Mono Output" msgstr "അനലോഗ് മോണോ ഔട്ട്പുട്ട്" #: spa/plugins/alsa/acp/alsa-mixer.c:2811 #, fuzzy msgid "Line Out" msgstr "അനലോഗ് ലൈന്‍-ഇന്‍" #: spa/plugins/alsa/acp/alsa-mixer.c:2812 msgid "Analog Mono Output" msgstr "അനലോഗ് മോണോ ഔട്ട്പുട്ട്" #: spa/plugins/alsa/acp/alsa-mixer.c:2813 #, fuzzy msgid "Speakers" msgstr "അനലോഗ് സ്റ്റീരിയോ" #: spa/plugins/alsa/acp/alsa-mixer.c:2814 msgid "HDMI / DisplayPort" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2815 #, fuzzy msgid "Digital Output (S/PDIF)" msgstr "ഡിജിറ്റല്‍ സ്റ്റീരിയോ (HDMI)" #: spa/plugins/alsa/acp/alsa-mixer.c:2816 #, fuzzy msgid "Digital Input (S/PDIF)" msgstr "ഡിജിറ്റല്‍ സ്റ്റീരിയോ (HDMI)" #: spa/plugins/alsa/acp/alsa-mixer.c:2817 msgid "Multichannel Input" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2818 #, fuzzy msgid "Multichannel Output" msgstr "നള്‍ ഔട്ട്പുട്ട്" #: spa/plugins/alsa/acp/alsa-mixer.c:2819 #, fuzzy msgid "Game Output" msgstr "നള്‍ ഔട്ട്പുട്ട്" #: spa/plugins/alsa/acp/alsa-mixer.c:2820 #: spa/plugins/alsa/acp/alsa-mixer.c:2821 #, fuzzy msgid "Chat Output" msgstr "നള്‍ ഔട്ട്പുട്ട്" #: spa/plugins/alsa/acp/alsa-mixer.c:2822 #, fuzzy msgid "Chat Input" msgstr "ഇന്‍പുട്ട്" #: spa/plugins/alsa/acp/alsa-mixer.c:2823 #, fuzzy msgid "Virtual Surround 7.1" msgstr "അനലോഗ് സറൌണ്ട് 7.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4527 msgid "Analog Mono" msgstr "അനലോഗ് മോണോ" #: spa/plugins/alsa/acp/alsa-mixer.c:4528 #, fuzzy msgid "Analog Mono (Left)" msgstr "അനലോഗ് മോണോ" #: spa/plugins/alsa/acp/alsa-mixer.c:4529 #, fuzzy msgid "Analog Mono (Right)" msgstr "അനലോഗ് മോണോ" #. Note: Not translated to "Analog Stereo Input", because the source #. * name gets "Input" appended to it automatically, so adding "Input" #. * here would lead to the source name to become "Analog Stereo Input #. * Input". The same logic applies to analog-stereo-output, #. * multichannel-input and multichannel-output. #: spa/plugins/alsa/acp/alsa-mixer.c:4530 #: spa/plugins/alsa/acp/alsa-mixer.c:4538 #: spa/plugins/alsa/acp/alsa-mixer.c:4539 msgid "Analog Stereo" msgstr "അനലോഗ് സ്റ്റീരിയോ" #: spa/plugins/alsa/acp/alsa-mixer.c:4531 msgid "Mono" msgstr "മോണോ" #: spa/plugins/alsa/acp/alsa-mixer.c:4532 msgid "Stereo" msgstr "സ്റ്റീരിയോ" #: spa/plugins/alsa/acp/alsa-mixer.c:4540 #: spa/plugins/alsa/acp/alsa-mixer.c:4698 #: spa/plugins/bluez5/bluez5-device.c:1135 msgid "Headset" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4541 #: spa/plugins/alsa/acp/alsa-mixer.c:4699 #, fuzzy msgid "Speakerphone" msgstr "അനലോഗ് സ്റ്റീരിയോ" #: spa/plugins/alsa/acp/alsa-mixer.c:4542 #: spa/plugins/alsa/acp/alsa-mixer.c:4543 msgid "Multichannel" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4544 msgid "Analog Surround 2.1" msgstr "അനലോഗ് സറൌണ്ട് 2.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4545 msgid "Analog Surround 3.0" msgstr "അനലോഗ് സറൌണ്ട് 3.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4546 msgid "Analog Surround 3.1" msgstr "അനലോഗ് സറൌണ്ട് 3.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4547 msgid "Analog Surround 4.0" msgstr "അനലോഗ് സറൌണ്ട് 4.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4548 msgid "Analog Surround 4.1" msgstr "അനലോഗ് സറൌണ്ട് 4.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4549 msgid "Analog Surround 5.0" msgstr "അനലോഗ് സറൌണ്ട് 5.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4550 msgid "Analog Surround 5.1" msgstr "അനലോഗ് സറൌണ്ട് 5.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4551 msgid "Analog Surround 6.0" msgstr "അനലോഗ് സറൌണ്ട് 6.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4552 msgid "Analog Surround 6.1" msgstr "അനലോഗ് സറൌണ്ട് 6.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4553 msgid "Analog Surround 7.0" msgstr "അനലോഗ് സറൌണ്ട് 7.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4554 msgid "Analog Surround 7.1" msgstr "അനലോഗ് സറൌണ്ട് 7.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4555 msgid "Digital Stereo (IEC958)" msgstr "ഡിജിറ്റല്‍ സ്റ്റീരിയോ (IEC958) " #: spa/plugins/alsa/acp/alsa-mixer.c:4556 msgid "Digital Surround 4.0 (IEC958/AC3)" msgstr "ഡിജിറ്റല്‍ സറൌണ്ട് 4.0 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4557 msgid "Digital Surround 5.1 (IEC958/AC3)" msgstr "ഡിജിറ്റല്‍ സറൌണ്ട് 5.1 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4558 #, fuzzy msgid "Digital Surround 5.1 (IEC958/DTS)" msgstr "ഡിജിറ്റല്‍ സറൌണ്ട് 5.1 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4559 msgid "Digital Stereo (HDMI)" msgstr "ഡിജിറ്റല്‍ സ്റ്റീരിയോ (HDMI)" #: spa/plugins/alsa/acp/alsa-mixer.c:4560 #, fuzzy msgid "Digital Surround 5.1 (HDMI)" msgstr "ഡിജിറ്റല്‍ സറൌണ്ട് 5.1 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4561 msgid "Chat" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4562 msgid "Game" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4696 msgid "Analog Mono Duplex" msgstr "അനലോഗ് മോണോ ഡ്യൂപ്ലെക്സ്" #: spa/plugins/alsa/acp/alsa-mixer.c:4697 msgid "Analog Stereo Duplex" msgstr "അനലോഗ് സ്റ്റീരിയോ ഡ്യൂപ്ലെക്സ്" #: spa/plugins/alsa/acp/alsa-mixer.c:4700 msgid "Digital Stereo Duplex (IEC958)" msgstr "ഡിജിറ്റല്‍ സ്റ്റീരിയോ ഡ്യൂപ്ലെക്സ് (IEC958)" #: spa/plugins/alsa/acp/alsa-mixer.c:4701 msgid "Multichannel Duplex" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4702 #, fuzzy msgid "Stereo Duplex" msgstr "അനലോഗ് സ്റ്റീരിയോ ഡ്യൂപ്ലെക്സ്" #: spa/plugins/alsa/acp/alsa-mixer.c:4703 msgid "Mono Chat + 7.1 Surround" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4806 #, fuzzy, c-format msgid "%s Output" msgstr "നള്‍ ഔട്ട്പുട്ട്" #: spa/plugins/alsa/acp/alsa-mixer.c:4813 #, fuzzy, c-format msgid "%s Input" msgstr "ഇന്‍പുട്ട്" #: spa/plugins/alsa/acp/alsa-util.c:1175 spa/plugins/alsa/acp/alsa-util.c:1269 #, fuzzy, c-format msgid "" "snd_pcm_avail() returned a value that is exceptionally large: %lu byte (%lu " "ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_avail() returned a value that is exceptionally large: %lu bytes (%lu " "ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "snd_pcm_avail() നല്‍കിയ മൂല്ല്യം വളരെ വലുതാണു്: %lu ബൈറ്റുകള്‍ (%lu ms).\n" "ഇതു് ALSA ഡ്രൈവര്‍ '%s'-ലുള്ള ഒരു ബഗാവാം. ദയവായി ഈ പ്രശ്നം ALSA ഡവലപ്പര്‍സിനെ അറിയിക്കുക." msgstr[1] "" "snd_pcm_avail() നല്‍കിയ മൂല്ല്യം വളരെ വലുതാണു്: %lu ബൈറ്റുകള്‍ (%lu ms).\n" "ഇതു് ALSA ഡ്രൈവര്‍ '%s'-ലുള്ള ഒരു ബഗാവാം. ദയവായി ഈ പ്രശ്നം ALSA ഡവലപ്പര്‍സിനെ അറിയിക്കുക." #: spa/plugins/alsa/acp/alsa-util.c:1241 #, fuzzy, c-format msgid "" "snd_pcm_delay() returned a value that is exceptionally large: %li byte (%s" "%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_delay() returned a value that is exceptionally large: %li bytes (%s" "%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "snd_pcm_delay() നല്‍കിയ മൂല്ല്യം വളരെ വലുതാണു്: %li ബൈറ്റുകള്‍ (%s%lu ms).\n" "ഇതു് ALSA ഡ്രൈവര്‍ '%s'-ലുള്ള ഒരു ബഗാവാം. ദയവായി ഈ പ്രശ്നം ALSA ഡവലപ്പര്‍സിനെ അറിയിക്കുക." msgstr[1] "" "snd_pcm_delay() നല്‍കിയ മൂല്ല്യം വളരെ വലുതാണു്: %li ബൈറ്റുകള്‍ (%s%lu ms).\n" "ഇതു് ALSA ഡ്രൈവര്‍ '%s'-ലുള്ള ഒരു ബഗാവാം. ദയവായി ഈ പ്രശ്നം ALSA ഡവലപ്പര്‍സിനെ അറിയിക്കുക." #: spa/plugins/alsa/acp/alsa-util.c:1288 #, fuzzy, c-format msgid "" "snd_pcm_avail_delay() returned strange values: delay %lu is less than avail " "%lu.\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr "" "snd_pcm_avail() നല്‍കിയ മൂല്ല്യം വളരെ വലുതാണു്: %lu ബൈറ്റുകള്‍ (%lu ms).\n" "ഇതു് ALSA ഡ്രൈവര്‍ '%s'-ലുള്ള ഒരു ബഗാവാം. ദയവായി ഈ പ്രശ്നം ALSA ഡവലപ്പര്‍സിനെ അറിയിക്കുക." #: spa/plugins/alsa/acp/alsa-util.c:1331 #, fuzzy, c-format msgid "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu byte " "(%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu bytes " "(%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "snd_pcm_mmap_begin() നല്‍കിയ മൂല്ല്യം വളരെ വലുതാണു്: %lu ബൈറ്റുകള്‍(%lu ms).\n" "ഇതു് ALSA ഡ്രൈവര്‍ '%s'-ലുള്ള ഒരു ബഗാവാം. ദയവായി ഈ പ്രശ്നം ALSA ഡവലപ്പര്‍സിനെ അറിയിക്കുക." msgstr[1] "" "snd_pcm_mmap_begin() നല്‍കിയ മൂല്ല്യം വളരെ വലുതാണു്: %lu ബൈറ്റുകള്‍(%lu ms).\n" "ഇതു് ALSA ഡ്രൈവര്‍ '%s'-ലുള്ള ഒരു ബഗാവാം. ദയവായി ഈ പ്രശ്നം ALSA ഡവലപ്പര്‍സിനെ അറിയിക്കുക." #: spa/plugins/bluez5/bluez5-device.c:1010 msgid "Audio Gateway (A2DP Source & HSP/HFP AG)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1033 #, c-format msgid "High Fidelity Playback (A2DP Sink, codec %s)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1035 #, c-format msgid "High Fidelity Duplex (A2DP Source/Sink, codec %s)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1041 msgid "High Fidelity Playback (A2DP Sink)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1043 msgid "High Fidelity Duplex (A2DP Source/Sink)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1070 #, c-format msgid "Headset Head Unit (HSP/HFP, codec %s)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1074 msgid "Headset Head Unit (HSP/HFP)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1140 msgid "Handsfree" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1155 #, fuzzy msgid "Headphone" msgstr "അനലോഗ് ഹെഡ്ഫോണുകള്‍" #: spa/plugins/bluez5/bluez5-device.c:1160 msgid "Portable" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1165 msgid "Car" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1170 msgid "HiFi" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1175 msgid "Phone" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1181 msgid "Bluetooth" msgstr "" pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/po/mr.po000066400000000000000000000467211511204443500221040ustar00rootroot00000000000000# translation of pipewire.master-tx.po to Marathi # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # Sandeep Shedmake , 2009, 2012. # msgid "" msgstr "" "Project-Id-Version: pipewire.master-tx\n" "Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/pipewire/" "issues/new\n" "POT-Creation-Date: 2021-04-18 16:54+0800\n" "PO-Revision-Date: 2012-01-30 09:54+0000\n" "Last-Translator: Sandeep Shedmake \n" "Language-Team: Marathi \n" "Language: mr\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Generator: KBabel 1.11.4\n" "Plural-Forms: nplurals=2; plural=(n!=1);\n" #: src/daemon/pipewire.c:43 #, c-format msgid "" "%s [options]\n" " -h, --help Show this help\n" " --version Show version\n" " -c, --config Load config (Default %s)\n" msgstr "" #: src/daemon/pipewire.desktop.in:4 msgid "PipeWire Media System" msgstr "" #: src/daemon/pipewire.desktop.in:5 msgid "Start the PipeWire Media System" msgstr "" #: src/examples/media-session/alsa-monitor.c:526 #: spa/plugins/alsa/acp/compat.c:187 msgid "Built-in Audio" msgstr "आंतरीक ऑडिओ" #: src/examples/media-session/alsa-monitor.c:530 #: spa/plugins/alsa/acp/compat.c:192 msgid "Modem" msgstr "मोडेम" #: src/examples/media-session/alsa-monitor.c:539 msgid "Unknown device" msgstr "" #: src/tools/pw-cat.c:991 #, c-format msgid "" "%s [options] \n" " -h, --help Show this help\n" " --version Show version\n" " -v, --verbose Enable verbose operations\n" "\n" msgstr "" #: src/tools/pw-cat.c:998 #, c-format msgid "" " -R, --remote Remote daemon name\n" " --media-type Set media type (default %s)\n" " --media-category Set media category (default %s)\n" " --media-role Set media role (default %s)\n" " --target Set node target (default %s)\n" " 0 means don't link\n" " --latency Set node latency (default %s)\n" " Xunit (unit = s, ms, us, ns)\n" " or direct samples (256)\n" " the rate is the one of the source " "file\n" " --list-targets List available targets for --target\n" "\n" msgstr "" #: src/tools/pw-cat.c:1016 #, c-format msgid "" " --rate Sample rate (req. for rec) (default " "%u)\n" " --channels Number of channels (req. for rec) " "(default %u)\n" " --channel-map Channel map\n" " one of: \"stereo\", " "\"surround-51\",... or\n" " comma separated list of channel " "names: eg. \"FL,FR\"\n" " --format Sample format %s (req. for rec) " "(default %s)\n" " --volume Stream volume 0-1.0 (default %.3f)\n" " -q --quality Resampler quality (0 - 15) (default " "%d)\n" "\n" msgstr "" #: src/tools/pw-cat.c:1033 msgid "" " -p, --playback Playback mode\n" " -r, --record Recording mode\n" " -m, --midi Midi mode\n" "\n" msgstr "" #: src/tools/pw-cli.c:2932 #, c-format msgid "" "%s [options] [command]\n" " -h, --help Show this help\n" " --version Show version\n" " -d, --daemon Start as daemon (Default false)\n" " -r, --remote Remote daemon name\n" "\n" msgstr "" #: spa/plugins/alsa/acp/acp.c:290 msgid "Pro Audio" msgstr "" #: spa/plugins/alsa/acp/acp.c:411 spa/plugins/alsa/acp/alsa-mixer.c:4704 #: spa/plugins/bluez5/bluez5-device.c:1000 msgid "Off" msgstr "बंद करा" #: spa/plugins/alsa/acp/channelmap.h:466 msgid "(invalid)" msgstr "(अवैध)" #: spa/plugins/alsa/acp/alsa-mixer.c:2709 msgid "Input" msgstr "इंपुट" #: spa/plugins/alsa/acp/alsa-mixer.c:2710 msgid "Docking Station Input" msgstr "डॉकिंग स्टेशन इंपुट" #: spa/plugins/alsa/acp/alsa-mixer.c:2711 #, fuzzy msgid "Docking Station Microphone" msgstr "डॉकिंग स्टेशन माइक्रोफोन" #: spa/plugins/alsa/acp/alsa-mixer.c:2712 #, fuzzy msgid "Docking Station Line In" msgstr "डॉकिंग स्टेशन इंपुट" #: spa/plugins/alsa/acp/alsa-mixer.c:2713 #: spa/plugins/alsa/acp/alsa-mixer.c:2804 msgid "Line In" msgstr "लाइन-इन" #: spa/plugins/alsa/acp/alsa-mixer.c:2714 #: spa/plugins/alsa/acp/alsa-mixer.c:2798 #: spa/plugins/bluez5/bluez5-device.c:1145 msgid "Microphone" msgstr "माइक्रोफोन" #: spa/plugins/alsa/acp/alsa-mixer.c:2715 #: spa/plugins/alsa/acp/alsa-mixer.c:2799 #, fuzzy msgid "Front Microphone" msgstr "डॉकिंग स्टेशन माइक्रोफोन" #: spa/plugins/alsa/acp/alsa-mixer.c:2716 #: spa/plugins/alsa/acp/alsa-mixer.c:2800 #, fuzzy msgid "Rear Microphone" msgstr "माइक्रोफोन" #: spa/plugins/alsa/acp/alsa-mixer.c:2717 msgid "External Microphone" msgstr "बाहेरील माइक्रोफोन" #: spa/plugins/alsa/acp/alsa-mixer.c:2718 #: spa/plugins/alsa/acp/alsa-mixer.c:2802 msgid "Internal Microphone" msgstr "आंतरीक माइक्रोफोन" #: spa/plugins/alsa/acp/alsa-mixer.c:2719 #: spa/plugins/alsa/acp/alsa-mixer.c:2805 msgid "Radio" msgstr "रेडिओ" #: spa/plugins/alsa/acp/alsa-mixer.c:2720 #: spa/plugins/alsa/acp/alsa-mixer.c:2806 msgid "Video" msgstr "विडिओ" #: spa/plugins/alsa/acp/alsa-mixer.c:2721 msgid "Automatic Gain Control" msgstr "स्वयं गैन कंट्रोल" #: spa/plugins/alsa/acp/alsa-mixer.c:2722 msgid "No Automatic Gain Control" msgstr "स्वयं गैन कंट्रोल अशक्य" #: spa/plugins/alsa/acp/alsa-mixer.c:2723 msgid "Boost" msgstr "बूस्ट" #: spa/plugins/alsa/acp/alsa-mixer.c:2724 msgid "No Boost" msgstr "बूस्ट अशक्य" #: spa/plugins/alsa/acp/alsa-mixer.c:2725 msgid "Amplifier" msgstr "ऍमप्लिफायर" #: spa/plugins/alsa/acp/alsa-mixer.c:2726 msgid "No Amplifier" msgstr "ऍमप्लिफायर अशक्य" #: spa/plugins/alsa/acp/alsa-mixer.c:2727 #, fuzzy msgid "Bass Boost" msgstr "बूस्ट" #: spa/plugins/alsa/acp/alsa-mixer.c:2728 #, fuzzy msgid "No Bass Boost" msgstr "बूस्ट अशक्य" #: spa/plugins/alsa/acp/alsa-mixer.c:2729 #: spa/plugins/bluez5/bluez5-device.c:1150 msgid "Speaker" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2730 #: spa/plugins/alsa/acp/alsa-mixer.c:2808 msgid "Headphones" msgstr "ऍनलॉग हेडफोन्स्" #: spa/plugins/alsa/acp/alsa-mixer.c:2797 msgid "Analog Input" msgstr "ऍनलॉग इंपुट" #: spa/plugins/alsa/acp/alsa-mixer.c:2801 msgid "Dock Microphone" msgstr "डॉकिंग स्टेशन माइक्रोफोन" #: spa/plugins/alsa/acp/alsa-mixer.c:2803 #, fuzzy msgid "Headset Microphone" msgstr "माइक्रोफोन" #: spa/plugins/alsa/acp/alsa-mixer.c:2807 msgid "Analog Output" msgstr "ऍनलॉग आऊटपुट" #: spa/plugins/alsa/acp/alsa-mixer.c:2809 #, fuzzy msgid "Headphones 2" msgstr "ऍनलॉग हेडफोन्स्" #: spa/plugins/alsa/acp/alsa-mixer.c:2810 #, fuzzy msgid "Headphones Mono Output" msgstr "ऍनलॉग मोनो आऊटपुट" #: spa/plugins/alsa/acp/alsa-mixer.c:2811 #, fuzzy msgid "Line Out" msgstr "लाइन-इन" #: spa/plugins/alsa/acp/alsa-mixer.c:2812 msgid "Analog Mono Output" msgstr "ऍनलॉग मोनो आऊटपुट" #: spa/plugins/alsa/acp/alsa-mixer.c:2813 #, fuzzy msgid "Speakers" msgstr "ऍनलॉग स्टिरीओ" #: spa/plugins/alsa/acp/alsa-mixer.c:2814 msgid "HDMI / DisplayPort" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2815 #, fuzzy msgid "Digital Output (S/PDIF)" msgstr "डिजीटल स्टिरीओ (HDMI)" #: spa/plugins/alsa/acp/alsa-mixer.c:2816 #, fuzzy msgid "Digital Input (S/PDIF)" msgstr "डिजीटल स्टिरीओ (HDMI)" #: spa/plugins/alsa/acp/alsa-mixer.c:2817 msgid "Multichannel Input" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2818 #, fuzzy msgid "Multichannel Output" msgstr "Null आऊटपुट" #: spa/plugins/alsa/acp/alsa-mixer.c:2819 #, fuzzy msgid "Game Output" msgstr "Null आऊटपुट" #: spa/plugins/alsa/acp/alsa-mixer.c:2820 #: spa/plugins/alsa/acp/alsa-mixer.c:2821 #, fuzzy msgid "Chat Output" msgstr "Null आऊटपुट" #: spa/plugins/alsa/acp/alsa-mixer.c:2822 #, fuzzy msgid "Chat Input" msgstr "इंपुट" #: spa/plugins/alsa/acp/alsa-mixer.c:2823 #, fuzzy msgid "Virtual Surround 7.1" msgstr "ऍनलॉग सर्राउंड 7.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4527 msgid "Analog Mono" msgstr "ऍनलॉग मोनो" #: spa/plugins/alsa/acp/alsa-mixer.c:4528 #, fuzzy msgid "Analog Mono (Left)" msgstr "ऍनलॉग मोनो" #: spa/plugins/alsa/acp/alsa-mixer.c:4529 #, fuzzy msgid "Analog Mono (Right)" msgstr "ऍनलॉग मोनो" #. Note: Not translated to "Analog Stereo Input", because the source #. * name gets "Input" appended to it automatically, so adding "Input" #. * here would lead to the source name to become "Analog Stereo Input #. * Input". The same logic applies to analog-stereo-output, #. * multichannel-input and multichannel-output. #: spa/plugins/alsa/acp/alsa-mixer.c:4530 #: spa/plugins/alsa/acp/alsa-mixer.c:4538 #: spa/plugins/alsa/acp/alsa-mixer.c:4539 msgid "Analog Stereo" msgstr "ऍनलॉग स्टिरीओ" #: spa/plugins/alsa/acp/alsa-mixer.c:4531 msgid "Mono" msgstr "मोनो" #: spa/plugins/alsa/acp/alsa-mixer.c:4532 msgid "Stereo" msgstr "स्टिरीओ" #: spa/plugins/alsa/acp/alsa-mixer.c:4540 #: spa/plugins/alsa/acp/alsa-mixer.c:4698 #: spa/plugins/bluez5/bluez5-device.c:1135 msgid "Headset" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4541 #: spa/plugins/alsa/acp/alsa-mixer.c:4699 #, fuzzy msgid "Speakerphone" msgstr "ऍनलॉग स्टिरीओ" #: spa/plugins/alsa/acp/alsa-mixer.c:4542 #: spa/plugins/alsa/acp/alsa-mixer.c:4543 msgid "Multichannel" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4544 msgid "Analog Surround 2.1" msgstr "ऍनलॉग सर्राउंड 2.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4545 msgid "Analog Surround 3.0" msgstr "ऍनलॉग सर्राउंड 3.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4546 msgid "Analog Surround 3.1" msgstr "ऍनलॉग सर्राउंड 3.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4547 msgid "Analog Surround 4.0" msgstr "ऍनलॉग सर्राउंड 4.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4548 msgid "Analog Surround 4.1" msgstr "ऍनलॉग सर्राउंड 4.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4549 msgid "Analog Surround 5.0" msgstr "ऍनलॉग सर्राउंड 5.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4550 msgid "Analog Surround 5.1" msgstr "ऍनलॉग सर्राउंड 5.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4551 msgid "Analog Surround 6.0" msgstr "ऍनलॉग सर्राउंड 6.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4552 msgid "Analog Surround 6.1" msgstr "ऍनलॉग सर्राउंड 6.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4553 msgid "Analog Surround 7.0" msgstr "ऍनलॉग सर्राउंड 7.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4554 msgid "Analog Surround 7.1" msgstr "ऍनलॉग सर्राउंड 7.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4555 msgid "Digital Stereo (IEC958)" msgstr "डिजीटल स्टिरीओ (IEC958)" #: spa/plugins/alsa/acp/alsa-mixer.c:4556 msgid "Digital Surround 4.0 (IEC958/AC3)" msgstr "डिजीटल सर्राउंड 4.0 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4557 msgid "Digital Surround 5.1 (IEC958/AC3)" msgstr "डिजीटल सर्राउंड 5.1 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4558 #, fuzzy msgid "Digital Surround 5.1 (IEC958/DTS)" msgstr "डिजीटल सर्राउंड 5.1 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4559 msgid "Digital Stereo (HDMI)" msgstr "डिजीटल स्टिरीओ (HDMI)" #: spa/plugins/alsa/acp/alsa-mixer.c:4560 #, fuzzy msgid "Digital Surround 5.1 (HDMI)" msgstr "डिजीटल सर्राउंड 5.1 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4561 msgid "Chat" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4562 msgid "Game" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4696 msgid "Analog Mono Duplex" msgstr "ऍनलॉग मोनो ड्युप्लेक्स्" #: spa/plugins/alsa/acp/alsa-mixer.c:4697 msgid "Analog Stereo Duplex" msgstr "ऍनलॉग स्टिरीओ ड्युप्लेक्स्" #: spa/plugins/alsa/acp/alsa-mixer.c:4700 msgid "Digital Stereo Duplex (IEC958)" msgstr "डिजीटल स्टिरीओ ड्युप्लेक्स् (IEC958)" #: spa/plugins/alsa/acp/alsa-mixer.c:4701 msgid "Multichannel Duplex" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4702 #, fuzzy msgid "Stereo Duplex" msgstr "ऍनलॉग स्टिरीओ ड्युप्लेक्स्" #: spa/plugins/alsa/acp/alsa-mixer.c:4703 msgid "Mono Chat + 7.1 Surround" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4806 #, fuzzy, c-format msgid "%s Output" msgstr "Null आऊटपुट" #: spa/plugins/alsa/acp/alsa-mixer.c:4813 #, fuzzy, c-format msgid "%s Input" msgstr "इंपुट" #: spa/plugins/alsa/acp/alsa-util.c:1175 spa/plugins/alsa/acp/alsa-util.c:1269 #, fuzzy, c-format msgid "" "snd_pcm_avail() returned a value that is exceptionally large: %lu byte (%lu " "ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_avail() returned a value that is exceptionally large: %lu bytes (%lu " "ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "snd_pcm_avail() ने अपेक्षा पेक्षा मोठे मूल्य पूरवले: %lu बाईटस् (%lu ms).\n" "हे सहसा ALSA ड्राइवर '%s' अंतर्गत बग अशू शकते. कृपया या अडचणीस ALSA डेव्हलपर करीता " "कळवा." msgstr[1] "" "snd_pcm_avail() ने अपेक्षा पेक्षा मोठे मूल्य पूरवले: %lu बाईटस् (%lu ms).\n" "हे सहसा ALSA ड्राइवर '%s' अंतर्गत बग अशू शकते. कृपया या अडचणीस ALSA डेव्हलपर करीता " "कळवा." #: spa/plugins/alsa/acp/alsa-util.c:1241 #, fuzzy, c-format msgid "" "snd_pcm_delay() returned a value that is exceptionally large: %li byte (%s" "%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_delay() returned a value that is exceptionally large: %li bytes (%s" "%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "snd_pcm_delay() ने अपेक्षा पेक्षा मोठे मूल्य पूरवले: %li बाईटस् (%s% lu ms).\n" "हे सहसा ALSA ड्राइवर '%s' अंतर्गत बग अशू शकते. कृपया या अडचणीस ALSA डेव्हलपर करीता " "कळवा." msgstr[1] "" "snd_pcm_delay() ने अपेक्षा पेक्षा मोठे मूल्य पूरवले: %li बाईटस् (%s% lu ms).\n" "हे सहसा ALSA ड्राइवर '%s' अंतर्गत बग अशू शकते. कृपया या अडचणीस ALSA डेव्हलपर करीता " "कळवा." #: spa/plugins/alsa/acp/alsa-util.c:1288 #, fuzzy, c-format msgid "" "snd_pcm_avail_delay() returned strange values: delay %lu is less than avail " "%lu.\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr "" "snd_pcm_avail() ने अपेक्षा पेक्षा मोठे मूल्य पूरवले: %lu बाईटस् (%lu ms).\n" "हे सहसा ALSA ड्राइवर '%s' अंतर्गत बग अशू शकते. कृपया या अडचणीस ALSA डेव्हलपर करीता " "कळवा." #: spa/plugins/alsa/acp/alsa-util.c:1331 #, fuzzy, c-format msgid "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu byte " "(%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu bytes " "(%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "snd_pcm_nmap_begin() ने अपेक्षा पेक्षा मोठे मूल्य पूरवले: %lu बाईटस् (%lu ms).\n" "हे सहसा ALSA ड्राइवर '%s' अंतर्गत बग अशू शकते. कृपया या अडचणीस ALSA डेव्हलपर करीता " "कळवा." msgstr[1] "" "snd_pcm_nmap_begin() ने अपेक्षा पेक्षा मोठे मूल्य पूरवले: %lu बाईटस् (%lu ms).\n" "हे सहसा ALSA ड्राइवर '%s' अंतर्गत बग अशू शकते. कृपया या अडचणीस ALSA डेव्हलपर करीता " "कळवा." #: spa/plugins/bluez5/bluez5-device.c:1010 msgid "Audio Gateway (A2DP Source & HSP/HFP AG)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1033 #, c-format msgid "High Fidelity Playback (A2DP Sink, codec %s)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1035 #, c-format msgid "High Fidelity Duplex (A2DP Source/Sink, codec %s)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1041 msgid "High Fidelity Playback (A2DP Sink)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1043 msgid "High Fidelity Duplex (A2DP Source/Sink)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1070 #, c-format msgid "Headset Head Unit (HSP/HFP, codec %s)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1074 msgid "Headset Head Unit (HSP/HFP)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1140 msgid "Handsfree" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1155 #, fuzzy msgid "Headphone" msgstr "ऍनलॉग हेडफोन्स्" #: spa/plugins/bluez5/bluez5-device.c:1160 msgid "Portable" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1165 msgid "Car" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1170 msgid "HiFi" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1175 msgid "Phone" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1181 msgid "Bluetooth" msgstr "" pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/po/my.po000066400000000000000000000600001511204443500220750ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: pipewire-master\n" "Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/pipewire/-/" "issues\n" "POT-Creation-Date: 2021-08-26 03:31+0000\n" "PO-Revision-Date: 2025-10-02 11:36+0630\n" "Last-Translator: zayar lwin \n" "Language-Team: lw1nzayar@yandex.com\n" "Language: my\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" "X-Generator: Poedit 3.7\n" #: src/daemon/pipewire.c:45 #, c-format msgid "" "%s [options]\n" " -h, --help Show this help\n" " --version Show version\n" " -c, --config Load config (Default %s)\n" msgstr "" "%s [options]\r\n" " -h, --help Show this help\r\n" " --version Show version\r\n" " -c, --config Load config (Default %s)\r\n" "\n" #: src/daemon/pipewire.desktop.in:4 msgid "PipeWire Media System" msgstr "ပိုက်ဝိုင်ယာ မီဒီယာစနစ်" #: src/daemon/pipewire.desktop.in:5 msgid "Start the PipeWire Media System" msgstr "ပိုက်ဝိုင်ယာ မီဒီယာစနစ် စတင်ရန်" #: src/examples/media-session/alsa-monitor.c:588 #: spa/plugins/alsa/acp/compat.c:189 msgid "Built-in Audio" msgstr "မူလပါရှိပြီးအသံကိရိယာ" #: src/examples/media-session/alsa-monitor.c:592 #: spa/plugins/alsa/acp/compat.c:194 msgid "Modem" msgstr "ဆ.သ.ရ-စက်" #: src/examples/media-session/alsa-monitor.c:601 #: src/modules/module-zeroconf-discover.c:296 msgid "Unknown device" msgstr "မသိ" #: src/modules/module-protocol-pulse/modules/module-tunnel-sink.c:173 #: src/modules/module-protocol-pulse/modules/module-tunnel-source.c:173 #, c-format msgid "Tunnel to %s/%s" msgstr "%s/%sသို့ လုံခြုံစွာဒေတာပို့ဆောင်" #: src/modules/module-pulse-tunnel.c:534 #, c-format msgid "Tunnel for %s@%s" msgstr "%s@%sအတွက် လုံခြုံစွာဒေတာပို့ဆောင်" #: src/modules/module-zeroconf-discover.c:308 #, c-format msgid "%s on %s@%s" msgstr "%s on %s@%s" #: src/modules/module-zeroconf-discover.c:312 #, c-format msgid "%s on %s" msgstr "%s on %s" #: src/tools/pw-cat.c:1016 #, c-format msgid "" "%s [options] \n" " -h, --help Show this help\n" " --version Show version\n" " -v, --verbose Enable verbose operations\n" "\n" msgstr "" "%s [options] \r\n" " -h, --help Show this help\r\n" " --version Show version\r\n" " -v, --verbose Enable verbose operations\r\n" "\r\n" #: src/tools/pw-cat.c:1023 #, c-format msgid "" " -R, --remote Remote daemon name\n" " --media-type Set media type (default %s)\n" " --media-category Set media category (default %s)\n" " --media-role Set media role (default %s)\n" " --target Set node target (default %s)\n" " 0 means don't link\n" " --latency Set node latency (default %s)\n" " Xunit (unit = s, ms, us, ns)\n" " or direct samples (256)\n" " the rate is the one of the source " "file\n" " --list-targets List available targets for --target\n" "\n" msgstr "" " -R, --remote Remote daemon name\r\n" " --media-type Set media type (default %s)\r\n" " --media-category Set media category (default %s)\r\n" " --media-role Set media role (default %s)\r\n" " --target Set node target (default %s)\r\n" " 0 means don't link\r\n" " --latency Set node latency (default %s)\r\n" " Xunit (unit = s, ms, us, ns)\r\n" " or direct samples (256)\r\n" " the rate is the one of the source " "file\r\n" " --list-targets List available targets for --" "target\r\n" "\r\n" #: src/tools/pw-cat.c:1041 #, c-format msgid "" " --rate Sample rate (req. for rec) (default " "%u)\n" " --channels Number of channels (req. for rec) " "(default %u)\n" " --channel-map Channel map\n" " one of: \"stereo\", " "\"surround-51\",... or\n" " comma separated list of channel " "names: eg. \"FL,FR\"\n" " --format Sample format %s (req. for rec) " "(default %s)\n" " --volume Stream volume 0-1.0 (default %.3f)\n" " -q --quality Resampler quality (0 - 15) (default " "%d)\n" "\n" msgstr "" " --rate Sample rate (req. for rec) (default " "%u)\r\n" " --channels Number of channels (req. for rec) " "(default %u)\r\n" " --channel-map Channel map\r\n" " one of: \"stereo\", " "\"surround-51\",... or\r\n" " comma separated list of channel " "names: eg. \"FL,FR\"\r\n" " --format Sample format %s (req. for rec) " "(default %s)\r\n" " --volume Stream volume 0-1.0 (default %.3f)" "\r\n" " -q --quality Resampler quality (0 - 15) (default " "%d)\r\n" "\r\n" #: src/tools/pw-cat.c:1058 msgid "" " -p, --playback Playback mode\n" " -r, --record Recording mode\n" " -m, --midi Midi mode\n" "\n" msgstr "" " -p, --playback Playback mode\r\n" " -r, --record Recording mode\r\n" " -m, --midi Midi mode\r\n" "\r\n" #: src/tools/pw-cli.c:2954 #, c-format msgid "" "%s [options] [command]\n" " -h, --help Show this help\n" " --version Show version\n" " -d, --daemon Start as daemon (Default false)\n" " -r, --remote Remote daemon name\n" "\n" msgstr "" "%s [options] [command]\r\n" " -h, --help Show this help\r\n" " --version Show version\r\n" " -d, --daemon Start as daemon (Default false)\r\n" " -r, --remote Remote daemon name\r\n" "\r\n" #: spa/plugins/alsa/acp/acp.c:306 msgid "Pro Audio" msgstr "ပရို အော်ဒီယို" #: spa/plugins/alsa/acp/acp.c:429 spa/plugins/alsa/acp/alsa-mixer.c:4648 #: spa/plugins/bluez5/bluez5-device.c:1128 msgid "Off" msgstr "ပိတ်" #: spa/plugins/alsa/acp/alsa-mixer.c:2652 msgid "Input" msgstr "အသံသွင်းမှု" #: spa/plugins/alsa/acp/alsa-mixer.c:2653 msgid "Docking Station Input" msgstr "ချိတ်ဆက်စခန်း အသံသွင်းမှု" #: spa/plugins/alsa/acp/alsa-mixer.c:2654 msgid "Docking Station Microphone" msgstr "ချိတ်ဆက်စခန်း မိုက်ခရိုဖုန်း" #: spa/plugins/alsa/acp/alsa-mixer.c:2655 msgid "Docking Station Line In" msgstr "ချိတ်ဆက်စခန်း အသံလက်ခံကြိုး" #: spa/plugins/alsa/acp/alsa-mixer.c:2656 #: spa/plugins/alsa/acp/alsa-mixer.c:2747 msgid "Line In" msgstr "အသံလက်ခံကြိုး" #: spa/plugins/alsa/acp/alsa-mixer.c:2657 #: spa/plugins/alsa/acp/alsa-mixer.c:2741 #: spa/plugins/bluez5/bluez5-device.c:1283 msgid "Microphone" msgstr "မိုက်ခရိုဖုန်း" #: spa/plugins/alsa/acp/alsa-mixer.c:2658 #: spa/plugins/alsa/acp/alsa-mixer.c:2742 msgid "Front Microphone" msgstr "မျက်နှာချင်းဆိုင်မိုက်ခရိုဖုန်း" #: spa/plugins/alsa/acp/alsa-mixer.c:2659 #: spa/plugins/alsa/acp/alsa-mixer.c:2743 msgid "Rear Microphone" msgstr "နောက်ဘက်မိုက်ခရိုဖုန်း" #: spa/plugins/alsa/acp/alsa-mixer.c:2660 msgid "External Microphone" msgstr "အပြင်မိုက်ခရိုဖုန်း" #: spa/plugins/alsa/acp/alsa-mixer.c:2661 #: spa/plugins/alsa/acp/alsa-mixer.c:2745 msgid "Internal Microphone" msgstr "အတွင်းမိုက်ခရိုဖုန်း" #: spa/plugins/alsa/acp/alsa-mixer.c:2662 #: spa/plugins/alsa/acp/alsa-mixer.c:2748 msgid "Radio" msgstr "ရေဒီယို" #: spa/plugins/alsa/acp/alsa-mixer.c:2663 #: spa/plugins/alsa/acp/alsa-mixer.c:2749 msgid "Video" msgstr "ဗီဒီယို" #: spa/plugins/alsa/acp/alsa-mixer.c:2664 msgid "Automatic Gain Control" msgstr "အလိုအလျောက်gainထိန်းချုပ်မည်" #: spa/plugins/alsa/acp/alsa-mixer.c:2665 msgid "No Automatic Gain Control" msgstr "အလိုအလျောက်gainမထိန်းချုပ်ပါ" #: spa/plugins/alsa/acp/alsa-mixer.c:2666 msgid "Boost" msgstr "အားတိုးသည်" #: spa/plugins/alsa/acp/alsa-mixer.c:2667 msgid "No Boost" msgstr "အားမတိုးပါ" #: spa/plugins/alsa/acp/alsa-mixer.c:2668 msgid "Amplifier" msgstr "ချဲ့စက်" #: spa/plugins/alsa/acp/alsa-mixer.c:2669 msgid "No Amplifier" msgstr "ချဲ့စက်မရှိပါ" #: spa/plugins/alsa/acp/alsa-mixer.c:2670 msgid "Bass Boost" msgstr "Bassတင်ထားသည်" #: spa/plugins/alsa/acp/alsa-mixer.c:2671 msgid "No Bass Boost" msgstr "Bassမတင်ထားပါ" #: spa/plugins/alsa/acp/alsa-mixer.c:2672 #: spa/plugins/bluez5/bluez5-device.c:1288 msgid "Speaker" msgstr "စပီကာ" #: spa/plugins/alsa/acp/alsa-mixer.c:2673 #: spa/plugins/alsa/acp/alsa-mixer.c:2751 msgid "Headphones" msgstr "နားကပ်စပီကာများ" #: spa/plugins/alsa/acp/alsa-mixer.c:2740 msgid "Analog Input" msgstr "အန်နလော့ အသံသွင်းမှု" #: spa/plugins/alsa/acp/alsa-mixer.c:2744 msgid "Dock Microphone" msgstr "မိုက်ခရိုဖုန်းချိတ်ရန်" #: spa/plugins/alsa/acp/alsa-mixer.c:2746 msgid "Headset Microphone" msgstr "မိုက်ပါနားကြပ်၏မိုက်" #: spa/plugins/alsa/acp/alsa-mixer.c:2750 msgid "Analog Output" msgstr "အန်နလော့ အသံထွက်မှု" #: spa/plugins/alsa/acp/alsa-mixer.c:2752 msgid "Headphones 2" msgstr "နားကပ်စပီကာများ ၂" #: spa/plugins/alsa/acp/alsa-mixer.c:2753 msgid "Headphones Mono Output" msgstr "နားကပ်စပီကာများ မိုနိုအသံထွက်မှု" #: spa/plugins/alsa/acp/alsa-mixer.c:2754 msgid "Line Out" msgstr "အသံပို့ဆောင်ကြိုး" #: spa/plugins/alsa/acp/alsa-mixer.c:2755 msgid "Analog Mono Output" msgstr "အန်နလော့ မိုနို အသံထွက်မှု" #: spa/plugins/alsa/acp/alsa-mixer.c:2756 msgid "Speakers" msgstr "စပီကာများ" #: spa/plugins/alsa/acp/alsa-mixer.c:2757 msgid "HDMI / DisplayPort" msgstr "HDMI / DisplayPort" #: spa/plugins/alsa/acp/alsa-mixer.c:2758 msgid "Digital Output (S/PDIF)" msgstr "ဒီဂျစ်တယ် အသံထွက်မှု (S/PDIF)" #: spa/plugins/alsa/acp/alsa-mixer.c:2759 msgid "Digital Input (S/PDIF)" msgstr "ဒီဂျစ်တယ် အသံသွင်းမှု (S/PDIF)" #: spa/plugins/alsa/acp/alsa-mixer.c:2760 msgid "Multichannel Input" msgstr "လိုင်းစုံ အသံသွင်းမှု" #: spa/plugins/alsa/acp/alsa-mixer.c:2761 msgid "Multichannel Output" msgstr "လိုင်းစုံ အသံထုတ်မှု" #: spa/plugins/alsa/acp/alsa-mixer.c:2762 msgid "Game Output" msgstr "ဂိမ်းအသံထွက်မှု" #: spa/plugins/alsa/acp/alsa-mixer.c:2763 #: spa/plugins/alsa/acp/alsa-mixer.c:2764 msgid "Chat Output" msgstr "စကားပြောဆို-အသံထွက်မှု" #: spa/plugins/alsa/acp/alsa-mixer.c:2765 msgid "Chat Input" msgstr "စကားပြောဆို-အသံထွက်မှု" #: spa/plugins/alsa/acp/alsa-mixer.c:2766 msgid "Virtual Surround 7.1" msgstr "ပတ်ပတ်လည်အယောင်သံ ၇.၁" #: spa/plugins/alsa/acp/alsa-mixer.c:4471 msgid "Analog Mono" msgstr "အန်နလော့ မိုနို" #: spa/plugins/alsa/acp/alsa-mixer.c:4472 msgid "Analog Mono (Left)" msgstr "အန်နလော့ မိုနို (ဘယ်)" #: spa/plugins/alsa/acp/alsa-mixer.c:4473 msgid "Analog Mono (Right)" msgstr "အန်နလော့ မိုနို (ညာ)" #. Note: Not translated to "Analog Stereo Input", because the source #. * name gets "Input" appended to it automatically, so adding "Input" #. * here would lead to the source name to become "Analog Stereo Input #. * Input". The same logic applies to analog-stereo-output, #. * multichannel-input and multichannel-output. #: spa/plugins/alsa/acp/alsa-mixer.c:4474 #: spa/plugins/alsa/acp/alsa-mixer.c:4482 #: spa/plugins/alsa/acp/alsa-mixer.c:4483 msgid "Analog Stereo" msgstr "အန်နလော့ စတယ်ရီယို" #: spa/plugins/alsa/acp/alsa-mixer.c:4475 msgid "Mono" msgstr "မိုနို" #: spa/plugins/alsa/acp/alsa-mixer.c:4476 msgid "Stereo" msgstr "စတယ်ရီယို" #: spa/plugins/alsa/acp/alsa-mixer.c:4484 #: spa/plugins/alsa/acp/alsa-mixer.c:4642 #: spa/plugins/bluez5/bluez5-device.c:1273 msgid "Headset" msgstr "မိုက်ပါနားကြပ်" #: spa/plugins/alsa/acp/alsa-mixer.c:4485 #: spa/plugins/alsa/acp/alsa-mixer.c:4643 msgid "Speakerphone" msgstr "စပီကာဖုန်း" #: spa/plugins/alsa/acp/alsa-mixer.c:4486 #: spa/plugins/alsa/acp/alsa-mixer.c:4487 msgid "Multichannel" msgstr "လိုင်းစုံ" #: spa/plugins/alsa/acp/alsa-mixer.c:4488 msgid "Analog Surround 2.1" msgstr "အန်နလော့ ပတ်ပတ်လည် ၂.၁" #: spa/plugins/alsa/acp/alsa-mixer.c:4489 msgid "Analog Surround 3.0" msgstr "အန်နလော့ ပတ်ပတ်လည် ၃.၀" #: spa/plugins/alsa/acp/alsa-mixer.c:4490 msgid "Analog Surround 3.1" msgstr "အန်နလော့ ပတ်ပတ်လည် ၃.၁" #: spa/plugins/alsa/acp/alsa-mixer.c:4491 msgid "Analog Surround 4.0" msgstr "အန်နလော့ ပတ်ပတ်လည် ၄.၀" #: spa/plugins/alsa/acp/alsa-mixer.c:4492 msgid "Analog Surround 4.1" msgstr "အန်နလော့ ပတ်ပတ်လည် ၄.၁" #: spa/plugins/alsa/acp/alsa-mixer.c:4493 msgid "Analog Surround 5.0" msgstr "အန်နလော့ ပတ်ပတ်လည် ၅.၀" #: spa/plugins/alsa/acp/alsa-mixer.c:4494 msgid "Analog Surround 5.1" msgstr "အန်နလော့ ပတ်ပတ်လည် ၅.၁" #: spa/plugins/alsa/acp/alsa-mixer.c:4495 msgid "Analog Surround 6.0" msgstr "အန်နလော့ ပတ်ပတ်လည် ၆.၀" #: spa/plugins/alsa/acp/alsa-mixer.c:4496 msgid "Analog Surround 6.1" msgstr "အန်နလော့ ပတ်ပတ်လည် ၆.၁" #: spa/plugins/alsa/acp/alsa-mixer.c:4497 msgid "Analog Surround 7.0" msgstr "အန်နလော့ ပတ်ပတ်လည် ၇.၀" #: spa/plugins/alsa/acp/alsa-mixer.c:4498 msgid "Analog Surround 7.1" msgstr "အန်နလော့ ပတ်ပတ်လည် ၇.၁" #: spa/plugins/alsa/acp/alsa-mixer.c:4499 msgid "Digital Stereo (IEC958)" msgstr "ဒီဂျစ်တယ်စတယ်ရီယို (IEC958)" #: spa/plugins/alsa/acp/alsa-mixer.c:4500 msgid "Digital Surround 4.0 (IEC958/AC3)" msgstr "ဒီဂျစ်တယ် ပတ်ပတ်လည် ၄.၀ (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4501 msgid "Digital Surround 5.1 (IEC958/AC3)" msgstr "ဒီဂျစ်တယ် ပတ်ပတ်လည် ၅.၁ (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4502 msgid "Digital Surround 5.1 (IEC958/DTS)" msgstr "ဒီဂျစ်တယ် ပတ်ပတ်လည် ၅.၁ (IEC958/DTS)" #: spa/plugins/alsa/acp/alsa-mixer.c:4503 msgid "Digital Stereo (HDMI)" msgstr "ဒီဂျစ်တယ်စတယ်ရီယို (HDMI)" #: spa/plugins/alsa/acp/alsa-mixer.c:4504 msgid "Digital Surround 5.1 (HDMI)" msgstr "ဒီဂျစ်တယ် ပတ်ပတ်လည် ၅.၁ (HDMI)" #: spa/plugins/alsa/acp/alsa-mixer.c:4505 msgid "Chat" msgstr "စကားပြောဆို" #: spa/plugins/alsa/acp/alsa-mixer.c:4506 msgid "Game" msgstr "ဂိမ်း" #: spa/plugins/alsa/acp/alsa-mixer.c:4640 msgid "Analog Mono Duplex" msgstr "အန်နလော့ မိုနို ဒူပလက်စ်" #: spa/plugins/alsa/acp/alsa-mixer.c:4641 msgid "Analog Stereo Duplex" msgstr "အန်နလော့ စတယ်ရီယို ဒူပလက်စ်" #: spa/plugins/alsa/acp/alsa-mixer.c:4644 msgid "Digital Stereo Duplex (IEC958)" msgstr "ဒီဂျစ်တယ်စတယ်ရီယို ဒူပလက်စ်" #: spa/plugins/alsa/acp/alsa-mixer.c:4645 msgid "Multichannel Duplex" msgstr "လိုင်းစုံ ဒူပလက်စ်" #: spa/plugins/alsa/acp/alsa-mixer.c:4646 msgid "Stereo Duplex" msgstr "စတယ်ရီယို ဒူပလက်စ်" #: spa/plugins/alsa/acp/alsa-mixer.c:4647 msgid "Mono Chat + 7.1 Surround" msgstr "မိုနို စကားပြောဆိုသံ + ၇.၁ ပတ်ပတ်လည်" #: spa/plugins/alsa/acp/alsa-mixer.c:4750 #, c-format msgid "%s Output" msgstr "%s အသံထုတ်" #: spa/plugins/alsa/acp/alsa-mixer.c:4757 #, c-format msgid "%s Input" msgstr "%s အသံသွင်း" #: spa/plugins/alsa/acp/alsa-util.c:1173 spa/plugins/alsa/acp/alsa-util.c:1267 #, c-format msgid "" "snd_pcm_avail() returned a value that is exceptionally large: %lu byte (%lu " "ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_avail() returned a value that is exceptionally large: %lu bytes (%lu " "ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "snd_pcm_avail() မှ ပြန်ပေးသည့်တန်ဖိုးသည် အလွန်ကြီးမားနေသည်- %lu ဘိုက် (%lu ms)။\n" "ဖြစ်နိုင်ခြေအများဆုံးမှာ ALSA ဒရိုက်ဗာ ‘%s’ တွင် ပရိုဂရမ်အမှားတစ်ခုရှိနေခြင်းဖြစ်သည်။ ဤပြဿနာအား " "ALSA ရေးသားသူများထံသို့ သတင်းပို့ပေးပါ။" #: spa/plugins/alsa/acp/alsa-util.c:1239 #, c-format msgid "" "snd_pcm_delay() returned a value that is exceptionally large: %li byte " "(%s%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_delay() returned a value that is exceptionally large: %li bytes " "(%s%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "snd_pcm_delay() မှ ပြန်ပေးသည့်တန်ဖိုးသည် အလွန်ကြီးမားနေသည်- %li ဘိုက် (%s%lu ms)။\n" "ဖြစ်နိုင်ခြေအများဆုံးမှာ ALSA ဒရိုက်ဗာ ‘%s’ တွင် ပရိုဂရမ်အမှားတစ်ခုရှိနေခြင်းဖြစ်သည်။ ဤပြဿနာအား " "ALSA ရေးသားသူများထံသို့ သတင်းပို့ပေးပါ။" #: spa/plugins/alsa/acp/alsa-util.c:1286 #, c-format msgid "" "snd_pcm_avail_delay() returned strange values: delay %lu is less than avail " "%lu.\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr "" "snd_pcm_avail_delay() မှ ထူးဆန်းသောတန်ဖိုးများ ပြန်ပေးခဲ့သည်- နှောင့်နှေးမှု %lu သည် ရနိုင်မှု %lu " "ထက် နည်းနေပါသည်။\n" "ဖြစ်နိုင်ခြေအများဆုံးမှာ ALSA ဒရိုက်ဘာ '%s’ တွင် ပရိုဂရမ်အမှားတစ်ခုရှိနေခြင်းဖြစ်သည်။ ဤပြဿနာအား " "ALSA ရေးသားသူများထံသို့ သတင်းပို့ပေးပါ။" #: spa/plugins/alsa/acp/alsa-util.c:1329 #, c-format msgid "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu byte " "(%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu bytes " "(%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "snd_pcm_mmap_begin() မှ ပြန်ပေးသည့်တန်ဖိုးသည် အလွန်ကြီးမားနေသည်- %lu ဘိုက် (%lu ms)။\n" "ဖြစ်နိုင်ခြေအများဆုံးမှာ ALSA ဒရိုက်ဗာ ‘%s’ တွင် ပရိုဂရမ်အမှားတစ်ခုရှိနေခြင်းဖြစ်သည်။ ဤပြဿနာအား " "ALSA ရေးသားသူများထံသို့ သတင်းပို့ပေးပါ။" #: spa/plugins/alsa/acp/channelmap.h:466 msgid "(invalid)" msgstr "(မမှန်ကန်)" #: spa/plugins/bluez5/bluez5-device.c:1138 msgid "Audio Gateway (A2DP Source & HSP/HFP AG)" msgstr "အသံ ဂိတ်ဝေး (A2DP Source & HSP/HFP AG)" #: spa/plugins/bluez5/bluez5-device.c:1161 #, c-format msgid "High Fidelity Playback (A2DP Sink, codec %s)" msgstr "High Fidelity Playback (A2DP Sink, codec %s)" #: spa/plugins/bluez5/bluez5-device.c:1163 #, c-format msgid "High Fidelity Duplex (A2DP Source/Sink, codec %s)" msgstr "High Fidelity Duplex (A2DP Source/Sink, codec %s)" #: spa/plugins/bluez5/bluez5-device.c:1169 msgid "High Fidelity Playback (A2DP Sink)" msgstr "High Fidelity Playback (A2DP Sink)" #: spa/plugins/bluez5/bluez5-device.c:1171 msgid "High Fidelity Duplex (A2DP Source/Sink)" msgstr "High Fidelity Duplex (A2DP Source/Sink)" #: spa/plugins/bluez5/bluez5-device.c:1198 #, c-format msgid "Headset Head Unit (HSP/HFP, codec %s)" msgstr "မိုက်ပါနားကြပ်ခေါင်းယူနစ် (HSP/HFP၊ codec %s)" #: spa/plugins/bluez5/bluez5-device.c:1202 msgid "Headset Head Unit (HSP/HFP)" msgstr "မိုက်ပါနားကြပ်ခေါင်းယူနစ် (HSP/HFP)" #: spa/plugins/bluez5/bluez5-device.c:1278 msgid "Handsfree" msgstr "လက်မလို" #: spa/plugins/bluez5/bluez5-device.c:1293 msgid "Headphone" msgstr "နားကပ်စပီကာ" #: spa/plugins/bluez5/bluez5-device.c:1298 msgid "Portable" msgstr "သယ်ရလွယ်ကူ" #: spa/plugins/bluez5/bluez5-device.c:1303 msgid "Car" msgstr "ကား" #: spa/plugins/bluez5/bluez5-device.c:1308 msgid "HiFi" msgstr "ဟိုင်ဖိုင်" #: spa/plugins/bluez5/bluez5-device.c:1313 msgid "Phone" msgstr "ဖုန်း" #: spa/plugins/bluez5/bluez5-device.c:1319 msgid "Bluetooth" msgstr "ဘလူးတု" pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/po/nl.po000066400000000000000000000416601511204443500220740ustar00rootroot00000000000000# Dutch translation of pipewire.master-tx. # Copyright (C) 2009 THE pipewire.master-tx'S COPYRIGHT HOLDER # This file is distributed under the same license as the pipewire.master-tx package. # Geert Warrink , 2009. # Reinout van Schouwen , 2009, 2012. # msgid "" msgstr "" "Project-Id-Version: pipewire.master-tx\n" "Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/pipewire/" "issues/new\n" "POT-Creation-Date: 2021-04-18 16:54+0800\n" "PO-Revision-Date: 2020-07-11 20:27+0000\n" "Last-Translator: Geert Warrink \n" "Language-Team: Dutch \n" "Language: nl\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: \n" "Plural-Forms: nplurals=2; plural=n != 1;\n" "X-Generator: Weblate 4.1.1\n" #: src/daemon/pipewire.c:43 #, c-format msgid "" "%s [options]\n" " -h, --help Show this help\n" " --version Show version\n" " -c, --config Load config (Default %s)\n" msgstr "" #: src/daemon/pipewire.desktop.in:4 msgid "PipeWire Media System" msgstr "" #: src/daemon/pipewire.desktop.in:5 msgid "Start the PipeWire Media System" msgstr "" #: src/examples/media-session/alsa-monitor.c:526 #: spa/plugins/alsa/acp/compat.c:187 msgid "Built-in Audio" msgstr "Intern geluid" #: src/examples/media-session/alsa-monitor.c:530 #: spa/plugins/alsa/acp/compat.c:192 msgid "Modem" msgstr "Modem" #: src/examples/media-session/alsa-monitor.c:539 msgid "Unknown device" msgstr "Onbekend apparaat" #: src/tools/pw-cat.c:991 #, c-format msgid "" "%s [options] \n" " -h, --help Show this help\n" " --version Show version\n" " -v, --verbose Enable verbose operations\n" "\n" msgstr "" #: src/tools/pw-cat.c:998 #, c-format msgid "" " -R, --remote Remote daemon name\n" " --media-type Set media type (default %s)\n" " --media-category Set media category (default %s)\n" " --media-role Set media role (default %s)\n" " --target Set node target (default %s)\n" " 0 means don't link\n" " --latency Set node latency (default %s)\n" " Xunit (unit = s, ms, us, ns)\n" " or direct samples (256)\n" " the rate is the one of the source " "file\n" " --list-targets List available targets for --target\n" "\n" msgstr "" #: src/tools/pw-cat.c:1016 #, c-format msgid "" " --rate Sample rate (req. for rec) (default " "%u)\n" " --channels Number of channels (req. for rec) " "(default %u)\n" " --channel-map Channel map\n" " one of: \"stereo\", " "\"surround-51\",... or\n" " comma separated list of channel " "names: eg. \"FL,FR\"\n" " --format Sample format %s (req. for rec) " "(default %s)\n" " --volume Stream volume 0-1.0 (default %.3f)\n" " -q --quality Resampler quality (0 - 15) (default " "%d)\n" "\n" msgstr "" #: src/tools/pw-cat.c:1033 msgid "" " -p, --playback Playback mode\n" " -r, --record Recording mode\n" " -m, --midi Midi mode\n" "\n" msgstr "" #: src/tools/pw-cli.c:2932 #, c-format msgid "" "%s [options] [command]\n" " -h, --help Show this help\n" " --version Show version\n" " -d, --daemon Start as daemon (Default false)\n" " -r, --remote Remote daemon name\n" "\n" msgstr "" #: spa/plugins/alsa/acp/acp.c:290 msgid "Pro Audio" msgstr "" #: spa/plugins/alsa/acp/acp.c:411 spa/plugins/alsa/acp/alsa-mixer.c:4704 #: spa/plugins/bluez5/bluez5-device.c:1000 msgid "Off" msgstr "Uit" #: spa/plugins/alsa/acp/channelmap.h:466 msgid "(invalid)" msgstr "(ongeldig)" #: spa/plugins/alsa/acp/alsa-mixer.c:2709 msgid "Input" msgstr "Invoer" #: spa/plugins/alsa/acp/alsa-mixer.c:2710 msgid "Docking Station Input" msgstr "Docking station-invoer" #: spa/plugins/alsa/acp/alsa-mixer.c:2711 msgid "Docking Station Microphone" msgstr "Docking station-microfoon" #: spa/plugins/alsa/acp/alsa-mixer.c:2712 msgid "Docking Station Line In" msgstr "Docking station-Lijn-in" #: spa/plugins/alsa/acp/alsa-mixer.c:2713 #: spa/plugins/alsa/acp/alsa-mixer.c:2804 msgid "Line In" msgstr "Lijn-in" #: spa/plugins/alsa/acp/alsa-mixer.c:2714 #: spa/plugins/alsa/acp/alsa-mixer.c:2798 #: spa/plugins/bluez5/bluez5-device.c:1145 msgid "Microphone" msgstr "Microfoon" #: spa/plugins/alsa/acp/alsa-mixer.c:2715 #: spa/plugins/alsa/acp/alsa-mixer.c:2799 msgid "Front Microphone" msgstr "Microfoon vooraan" #: spa/plugins/alsa/acp/alsa-mixer.c:2716 #: spa/plugins/alsa/acp/alsa-mixer.c:2800 msgid "Rear Microphone" msgstr "Microfoon achteraan" #: spa/plugins/alsa/acp/alsa-mixer.c:2717 msgid "External Microphone" msgstr "Externe microfoon" #: spa/plugins/alsa/acp/alsa-mixer.c:2718 #: spa/plugins/alsa/acp/alsa-mixer.c:2802 msgid "Internal Microphone" msgstr "Interne microfoon" #: spa/plugins/alsa/acp/alsa-mixer.c:2719 #: spa/plugins/alsa/acp/alsa-mixer.c:2805 msgid "Radio" msgstr "Radio" #: spa/plugins/alsa/acp/alsa-mixer.c:2720 #: spa/plugins/alsa/acp/alsa-mixer.c:2806 msgid "Video" msgstr "Video" #: spa/plugins/alsa/acp/alsa-mixer.c:2721 msgid "Automatic Gain Control" msgstr "Automatische gain-controle" #: spa/plugins/alsa/acp/alsa-mixer.c:2722 msgid "No Automatic Gain Control" msgstr "Geen automatische gain-controle" #: spa/plugins/alsa/acp/alsa-mixer.c:2723 msgid "Boost" msgstr "Boostversterking" #: spa/plugins/alsa/acp/alsa-mixer.c:2724 msgid "No Boost" msgstr "Geen boostversterking" #: spa/plugins/alsa/acp/alsa-mixer.c:2725 msgid "Amplifier" msgstr "Versterker" #: spa/plugins/alsa/acp/alsa-mixer.c:2726 msgid "No Amplifier" msgstr "Geen versterker" #: spa/plugins/alsa/acp/alsa-mixer.c:2727 msgid "Bass Boost" msgstr "Boostversterking" #: spa/plugins/alsa/acp/alsa-mixer.c:2728 msgid "No Bass Boost" msgstr "Geen boostversterking" #: spa/plugins/alsa/acp/alsa-mixer.c:2729 #: spa/plugins/bluez5/bluez5-device.c:1150 msgid "Speaker" msgstr "Luidspreker" #: spa/plugins/alsa/acp/alsa-mixer.c:2730 #: spa/plugins/alsa/acp/alsa-mixer.c:2808 msgid "Headphones" msgstr "Analoge koptelefoon" #: spa/plugins/alsa/acp/alsa-mixer.c:2797 msgid "Analog Input" msgstr "Analoge invoer" #: spa/plugins/alsa/acp/alsa-mixer.c:2801 msgid "Dock Microphone" msgstr "Docking station-microfoon" #: spa/plugins/alsa/acp/alsa-mixer.c:2803 #, fuzzy msgid "Headset Microphone" msgstr "Microfoon" #: spa/plugins/alsa/acp/alsa-mixer.c:2807 msgid "Analog Output" msgstr "Analoge output" #: spa/plugins/alsa/acp/alsa-mixer.c:2809 msgid "Headphones 2" msgstr "Analoge koptelefoon 2" #: spa/plugins/alsa/acp/alsa-mixer.c:2810 msgid "Headphones Mono Output" msgstr "Analoge mono-uitvoer" #: spa/plugins/alsa/acp/alsa-mixer.c:2811 msgid "Line Out" msgstr "Lijn-uit" #: spa/plugins/alsa/acp/alsa-mixer.c:2812 msgid "Analog Mono Output" msgstr "Analoge mono-uitvoer" #: spa/plugins/alsa/acp/alsa-mixer.c:2813 msgid "Speakers" msgstr "Luidsprekers" #: spa/plugins/alsa/acp/alsa-mixer.c:2814 msgid "HDMI / DisplayPort" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2815 msgid "Digital Output (S/PDIF)" msgstr "Digitaal stereo (HDMI)" #: spa/plugins/alsa/acp/alsa-mixer.c:2816 msgid "Digital Input (S/PDIF)" msgstr "Digitale invoer (S/PDIF)" #: spa/plugins/alsa/acp/alsa-mixer.c:2817 msgid "Multichannel Input" msgstr "Multikanaal invoer" #: spa/plugins/alsa/acp/alsa-mixer.c:2818 msgid "Multichannel Output" msgstr "Multikanaal uitvoer" #: spa/plugins/alsa/acp/alsa-mixer.c:2819 msgid "Game Output" msgstr "Spel uitvoer" #: spa/plugins/alsa/acp/alsa-mixer.c:2820 #: spa/plugins/alsa/acp/alsa-mixer.c:2821 #, fuzzy msgid "Chat Output" msgstr "Null-uitvoer" #: spa/plugins/alsa/acp/alsa-mixer.c:2822 #, fuzzy msgid "Chat Input" msgstr "Invoer" #: spa/plugins/alsa/acp/alsa-mixer.c:2823 msgid "Virtual Surround 7.1" msgstr "Virtueel surround 7.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4527 msgid "Analog Mono" msgstr "Analoog mono" #: spa/plugins/alsa/acp/alsa-mixer.c:4528 msgid "Analog Mono (Left)" msgstr "Analoog mono (Links)" #: spa/plugins/alsa/acp/alsa-mixer.c:4529 msgid "Analog Mono (Right)" msgstr "Analoog mono (Rechts)" #. Note: Not translated to "Analog Stereo Input", because the source #. * name gets "Input" appended to it automatically, so adding "Input" #. * here would lead to the source name to become "Analog Stereo Input #. * Input". The same logic applies to analog-stereo-output, #. * multichannel-input and multichannel-output. #: spa/plugins/alsa/acp/alsa-mixer.c:4530 #: spa/plugins/alsa/acp/alsa-mixer.c:4538 #: spa/plugins/alsa/acp/alsa-mixer.c:4539 msgid "Analog Stereo" msgstr "Analoog stereo" #: spa/plugins/alsa/acp/alsa-mixer.c:4531 msgid "Mono" msgstr "Mono" #: spa/plugins/alsa/acp/alsa-mixer.c:4532 msgid "Stereo" msgstr "Stereo" #: spa/plugins/alsa/acp/alsa-mixer.c:4540 #: spa/plugins/alsa/acp/alsa-mixer.c:4698 #: spa/plugins/bluez5/bluez5-device.c:1135 msgid "Headset" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4541 #: spa/plugins/alsa/acp/alsa-mixer.c:4699 msgid "Speakerphone" msgstr "Luidspreker" #: spa/plugins/alsa/acp/alsa-mixer.c:4542 #: spa/plugins/alsa/acp/alsa-mixer.c:4543 msgid "Multichannel" msgstr "Multikanaal" #: spa/plugins/alsa/acp/alsa-mixer.c:4544 msgid "Analog Surround 2.1" msgstr "Analoog surround 2.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4545 msgid "Analog Surround 3.0" msgstr "Analoog surround 3.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4546 msgid "Analog Surround 3.1" msgstr "Analoog surround 3.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4547 msgid "Analog Surround 4.0" msgstr "Analoog surround 4.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4548 msgid "Analog Surround 4.1" msgstr "Analoog surround 4.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4549 msgid "Analog Surround 5.0" msgstr "Analoog surround 5.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4550 msgid "Analog Surround 5.1" msgstr "Analoog surround 5.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4551 msgid "Analog Surround 6.0" msgstr "Analoog surround 6.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4552 msgid "Analog Surround 6.1" msgstr "Analoog surround 6.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4553 msgid "Analog Surround 7.0" msgstr "Analoog surround 7.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4554 msgid "Analog Surround 7.1" msgstr "Analoog surround 7.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4555 msgid "Digital Stereo (IEC958)" msgstr "Digitaal stereo (IEC958)" #: spa/plugins/alsa/acp/alsa-mixer.c:4556 msgid "Digital Surround 4.0 (IEC958/AC3)" msgstr "Digitaal surround 4.0 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4557 msgid "Digital Surround 5.1 (IEC958/AC3)" msgstr "Digitaal surround 5.1 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4558 msgid "Digital Surround 5.1 (IEC958/DTS)" msgstr "Digitaal surround 5.1 (IEC958/DTS)" #: spa/plugins/alsa/acp/alsa-mixer.c:4559 msgid "Digital Stereo (HDMI)" msgstr "Digitaal stereo (HDMI)" #: spa/plugins/alsa/acp/alsa-mixer.c:4560 msgid "Digital Surround 5.1 (HDMI)" msgstr "Digitaal surround 5.1 (HDMI)" #: spa/plugins/alsa/acp/alsa-mixer.c:4561 msgid "Chat" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4562 msgid "Game" msgstr "Spel" #: spa/plugins/alsa/acp/alsa-mixer.c:4696 msgid "Analog Mono Duplex" msgstr "Analoog mono duplex" #: spa/plugins/alsa/acp/alsa-mixer.c:4697 msgid "Analog Stereo Duplex" msgstr "Analoog stereo duplex" #: spa/plugins/alsa/acp/alsa-mixer.c:4700 msgid "Digital Stereo Duplex (IEC958)" msgstr "Digitaal stereo duplex (IEC958)" #: spa/plugins/alsa/acp/alsa-mixer.c:4701 msgid "Multichannel Duplex" msgstr "Multikanaal-duplex" #: spa/plugins/alsa/acp/alsa-mixer.c:4702 msgid "Stereo Duplex" msgstr "Stereo duplex" #: spa/plugins/alsa/acp/alsa-mixer.c:4703 msgid "Mono Chat + 7.1 Surround" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4806 #, c-format msgid "%s Output" msgstr "%s uitvoer" #: spa/plugins/alsa/acp/alsa-mixer.c:4813 #, c-format msgid "%s Input" msgstr "%s invoer" #: spa/plugins/alsa/acp/alsa-util.c:1175 spa/plugins/alsa/acp/alsa-util.c:1269 #, fuzzy, c-format msgid "" "snd_pcm_avail() returned a value that is exceptionally large: %lu byte (%lu " "ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_avail() returned a value that is exceptionally large: %lu bytes (%lu " "ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "snd_pcm_avail() gaf een waarde terug die uitzonderlijk groot is: %lu bytes " "(%lu ms).\n" "Waarschijnlijk is dit een fout in het ALSA-stuurprogramma ‘%s’. Meld dit " "probleem alstublieft aan de ALSA-ontwikkelaars." msgstr[1] "" "snd_pcm_avail() gaf een waarde terug die uitzonderlijk groot is: %lu bytes " "(%lu ms).\n" "Waarschijnlijk is dit een fout in het ALSA-stuurprogramma ‘%s’. Meld dit " "probleem alstublieft aan de ALSA-ontwikkelaars." #: spa/plugins/alsa/acp/alsa-util.c:1241 #, fuzzy, c-format msgid "" "snd_pcm_delay() returned a value that is exceptionally large: %li byte (%s" "%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_delay() returned a value that is exceptionally large: %li bytes (%s" "%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "snd_pcm_delay() gaf een waarde terug die uitzonderlijk groot is: %li bytes " "(%s%lu ms).\n" "Waarschijnlijk is dit een fout in het ALSA-stuurprogramma ‘%s’. Meld dit " "probleem alstublieft aan de ALSA-ontwikkelaars." msgstr[1] "" "snd_pcm_delay() gaf een waarde terug die uitzonderlijk groot is: %li bytes " "(%s%lu ms).\n" "Waarschijnlijk is dit een fout in het ALSA-stuurprogramma ‘%s’. Meld dit " "probleem alstublieft aan de ALSA-ontwikkelaars." #: spa/plugins/alsa/acp/alsa-util.c:1288 #, c-format msgid "" "snd_pcm_avail_delay() returned strange values: delay %lu is less than avail " "%lu.\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr "" "snd_pcm_avail_delay() gaf vreemde waardes terug: vertraging %lu is minder " "dan %lu.\n" "Waarschijnlijk is dit een fout in het ALSA-stuurprogramma '%s'. Meld dit " "probleem aan de ALSA-ontwikkelaars." #: spa/plugins/alsa/acp/alsa-util.c:1331 #, fuzzy, c-format msgid "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu byte " "(%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu bytes " "(%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "snd_pcm_mmap_begin() gaf een waarde terug die uitzonderlijk groot is: %lu " "bytes (%lu ms).\n" "Waarschijnlijk is dit een fout in het ALSA-stuurprogramma ‘%s’. Meld dit " "probleem alstublieft aan de ALSA-ontwikkelaars." msgstr[1] "" "snd_pcm_mmap_begin() gaf een waarde terug die uitzonderlijk groot is: %lu " "bytes (%lu ms).\n" "Waarschijnlijk is dit een fout in het ALSA-stuurprogramma ‘%s’. Meld dit " "probleem alstublieft aan de ALSA-ontwikkelaars." #: spa/plugins/bluez5/bluez5-device.c:1010 msgid "Audio Gateway (A2DP Source & HSP/HFP AG)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1033 #, c-format msgid "High Fidelity Playback (A2DP Sink, codec %s)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1035 #, c-format msgid "High Fidelity Duplex (A2DP Source/Sink, codec %s)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1041 msgid "High Fidelity Playback (A2DP Sink)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1043 msgid "High Fidelity Duplex (A2DP Source/Sink)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1070 #, c-format msgid "Headset Head Unit (HSP/HFP, codec %s)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1074 msgid "Headset Head Unit (HSP/HFP)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1140 msgid "Handsfree" msgstr "Handenvrij" #: spa/plugins/bluez5/bluez5-device.c:1155 msgid "Headphone" msgstr "Koptelefoon" #: spa/plugins/bluez5/bluez5-device.c:1160 msgid "Portable" msgstr "Draagbaar" #: spa/plugins/bluez5/bluez5-device.c:1165 msgid "Car" msgstr "Auto" #: spa/plugins/bluez5/bluez5-device.c:1170 msgid "HiFi" msgstr "HiFi" #: spa/plugins/bluez5/bluez5-device.c:1175 msgid "Phone" msgstr "Telefoon" #: spa/plugins/bluez5/bluez5-device.c:1181 msgid "Bluetooth" msgstr "Bluetooth" pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/po/nn.po000066400000000000000000000462211511204443500220740ustar00rootroot00000000000000# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # Karl Ove Hufthammer , 2017. # Nicolai Syvertsen 2021. # msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/pipewire/" "issues/new\n" "POT-Creation-Date: 2021-04-20 16:34+0200\n" "PO-Revision-Date: 2021-02-07 15:40+0000\n" "Last-Translator: Karl Ove Hufthammer \n" "Language-Team: Norwegian Nynorsk \n" "Language: nn\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" "X-Generator: Weblate 4.4.2\n" #: src/daemon/pipewire.c:43 #, c-format msgid "" "%s [options]\n" " -h, --help Show this help\n" " --version Show version\n" " -c, --config Load config (Default %s)\n" msgstr "" #: src/daemon/pipewire.desktop.in:4 msgid "PipeWire Media System" msgstr "PipeWire mediasystem" #: src/daemon/pipewire.desktop.in:5 msgid "Start the PipeWire Media System" msgstr "Start opp PipeWire mediasystem" #: src/examples/media-session/alsa-monitor.c:585 #: spa/plugins/alsa/acp/compat.c:187 msgid "Built-in Audio" msgstr "Innebygd lyd" #: src/examples/media-session/alsa-monitor.c:589 #: spa/plugins/alsa/acp/compat.c:192 msgid "Modem" msgstr "Modem" #: src/examples/media-session/alsa-monitor.c:598 msgid "Unknown device" msgstr "Ukjend einhet" #: src/tools/pw-cat.c:991 #, c-format msgid "" "%s [options] \n" " -h, --help Show this help\n" " --version Show version\n" " -v, --verbose Enable verbose operations\n" "\n" msgstr "" "%s [val] \n" " -h, --help Vis denne hjelpen\n" " --version Vis versjon\n" " -v, --verbose Slå på utdypene handling\n" "\n" #: src/tools/pw-cat.c:998 #, c-format msgid "" " -R, --remote Remote daemon name\n" " --media-type Set media type (default %s)\n" " --media-category Set media category (default %s)\n" " --media-role Set media role (default %s)\n" " --target Set node target (default %s)\n" " 0 means don't link\n" " --latency Set node latency (default %s)\n" " Xunit (unit = s, ms, us, ns)\n" " or direct samples (256)\n" " the rate is the one of the source " "file\n" " --list-targets List available targets for --target\n" "\n" msgstr "" " -R, --remote Navn på fjerntjenar\n" " --media-type Set medietype (standard %s)\n" " --media-category Set mediekategori (standard %s)\n" " --media-role Set medierolle (standard %s)\n" " --target Set nodemål (standard %s)\n" " 0 betyr ikke tilknytt\n" " --latency Set nodelatens (standard %s)\n" " Xunit (unit = s, ms, us, ns)\n" " eller direkte i datapunkter (256)\n" " hastigheiten kjem frå kildefilen " "file\n" " --list-targets Vis tilgjengeleg mål for --target\n" "\n" #: src/tools/pw-cat.c:1016 #, c-format msgid "" " --rate Sample rate (req. for rec) (default " "%u)\n" " --channels Number of channels (req. for rec) " "(default %u)\n" " --channel-map Channel map\n" " one of: \"stereo\", " "\"surround-51\",... or\n" " comma separated list of channel " "names: eg. \"FL,FR\"\n" " --format Sample format %s (req. for rec) " "(default %s)\n" " --volume Stream volume 0-1.0 (default %.3f)\n" " -q --quality Resampler quality (0 - 15) (default " "%d)\n" "\n" msgstr "" " --rate Målehastigheit (krevjast for opptak) (default " "%u)\n" " --channels Antall kanalar (krevjast for opptak) " "(default %u)\n" " --channel-map Kanaloppsett\n" " ein av: \"stereo\", " "\"surround-51\",... or\n" " kommaskild liste av kanalnavn " ": t.d. \"FL,FR\"\n" " --format Format for målingar %s (krevjast for opptak) " "(default %s)\n" " --volume Lydnivå for straum 0-1.0 (standard %.3f)\n" " -q --quality Kvaltet for gjenutvalg (0 - 15) (standard " "%d)\n" "\n" #: src/tools/pw-cat.c:1033 msgid "" " -p, --playback Playback mode\n" " -r, --record Recording mode\n" " -m, --midi Midi mode\n" "\n" msgstr "" " -p, --playback Avspillingsmodus\n" " -r, --record Opptaksmodus\n" " -m, --midi Midi-modus\n" "\n" #: src/tools/pw-cli.c:2941 #, c-format msgid "" "%s [options] [command]\n" " -h, --help Show this help\n" " --version Show version\n" " -d, --daemon Start as daemon (Default false)\n" " -r, --remote Remote daemon name\n" "\n" msgstr "" "%s [val] [kommando]\n" " -h, --help Vis denne hjelp\n" " --version Vis versjon\n" " -d, --daemon Start som tjenar (standard er false)\n" " -r, --remote Navn på fjerntjenar\n" "\n" #: spa/plugins/alsa/acp/acp.c:290 msgid "Pro Audio" msgstr "Profflyd" #: spa/plugins/alsa/acp/acp.c:411 spa/plugins/alsa/acp/alsa-mixer.c:4704 #: spa/plugins/bluez5/bluez5-device.c:1000 msgid "Off" msgstr "Av" #: spa/plugins/alsa/acp/channelmap.h:466 msgid "(invalid)" msgstr "(ugyldig)" #: spa/plugins/alsa/acp/alsa-mixer.c:2709 msgid "Input" msgstr "Lyd inn" #: spa/plugins/alsa/acp/alsa-mixer.c:2710 msgid "Docking Station Input" msgstr "Lyd inn frå dokkingstasjon" #: spa/plugins/alsa/acp/alsa-mixer.c:2711 msgid "Docking Station Microphone" msgstr "Mikrofon på dokkingstasjon" #: spa/plugins/alsa/acp/alsa-mixer.c:2712 msgid "Docking Station Line In" msgstr "Linje inn på dokkingstasjon" #: spa/plugins/alsa/acp/alsa-mixer.c:2713 #: spa/plugins/alsa/acp/alsa-mixer.c:2804 msgid "Line In" msgstr "Linje inn" #: spa/plugins/alsa/acp/alsa-mixer.c:2714 #: spa/plugins/alsa/acp/alsa-mixer.c:2798 #: spa/plugins/bluez5/bluez5-device.c:1145 msgid "Microphone" msgstr "Mikrofon" #: spa/plugins/alsa/acp/alsa-mixer.c:2715 #: spa/plugins/alsa/acp/alsa-mixer.c:2799 msgid "Front Microphone" msgstr "Frontmikrofon" #: spa/plugins/alsa/acp/alsa-mixer.c:2716 #: spa/plugins/alsa/acp/alsa-mixer.c:2800 msgid "Rear Microphone" msgstr "Bakmikrofon" #: spa/plugins/alsa/acp/alsa-mixer.c:2717 msgid "External Microphone" msgstr "Ekstern mikrofon" #: spa/plugins/alsa/acp/alsa-mixer.c:2718 #: spa/plugins/alsa/acp/alsa-mixer.c:2802 msgid "Internal Microphone" msgstr "Intern mikrofonen" #: spa/plugins/alsa/acp/alsa-mixer.c:2719 #: spa/plugins/alsa/acp/alsa-mixer.c:2805 msgid "Radio" msgstr "Radio" #: spa/plugins/alsa/acp/alsa-mixer.c:2720 #: spa/plugins/alsa/acp/alsa-mixer.c:2806 msgid "Video" msgstr "Video" #: spa/plugins/alsa/acp/alsa-mixer.c:2721 msgid "Automatic Gain Control" msgstr "Automatisk lydnivåstyring (AGC)" #: spa/plugins/alsa/acp/alsa-mixer.c:2722 msgid "No Automatic Gain Control" msgstr "Inga automatisk lydnivåstyring (AGC)" #: spa/plugins/alsa/acp/alsa-mixer.c:2723 msgid "Boost" msgstr "Lydforsterking" #: spa/plugins/alsa/acp/alsa-mixer.c:2724 msgid "No Boost" msgstr "Inga lydforsterking" #: spa/plugins/alsa/acp/alsa-mixer.c:2725 msgid "Amplifier" msgstr "Forsterkar" #: spa/plugins/alsa/acp/alsa-mixer.c:2726 msgid "No Amplifier" msgstr "Ingen forsterkar" #: spa/plugins/alsa/acp/alsa-mixer.c:2727 msgid "Bass Boost" msgstr "Bassforsterking" #: spa/plugins/alsa/acp/alsa-mixer.c:2728 msgid "No Bass Boost" msgstr "Inga bassforsterking" #: spa/plugins/alsa/acp/alsa-mixer.c:2729 #: spa/plugins/bluez5/bluez5-device.c:1150 msgid "Speaker" msgstr "Høgtalar" #: spa/plugins/alsa/acp/alsa-mixer.c:2730 #: spa/plugins/alsa/acp/alsa-mixer.c:2808 msgid "Headphones" msgstr "Hovudtelefonar" #: spa/plugins/alsa/acp/alsa-mixer.c:2797 msgid "Analog Input" msgstr "Analog innlyd" #: spa/plugins/alsa/acp/alsa-mixer.c:2801 msgid "Dock Microphone" msgstr "Mikrofon på dokkingstasjon" #: spa/plugins/alsa/acp/alsa-mixer.c:2803 msgid "Headset Microphone" msgstr "Mikrofon på hovudsett" #: spa/plugins/alsa/acp/alsa-mixer.c:2807 msgid "Analog Output" msgstr "Analog utlyd" #: spa/plugins/alsa/acp/alsa-mixer.c:2809 msgid "Headphones 2" msgstr "Hovudtelefonar 2" #: spa/plugins/alsa/acp/alsa-mixer.c:2810 msgid "Headphones Mono Output" msgstr "Hovudtelefonar monolyd" #: spa/plugins/alsa/acp/alsa-mixer.c:2811 msgid "Line Out" msgstr "Linje ut" #: spa/plugins/alsa/acp/alsa-mixer.c:2812 msgid "Analog Mono Output" msgstr "Analog mono-utlyd" #: spa/plugins/alsa/acp/alsa-mixer.c:2813 msgid "Speakers" msgstr "Høgtalarar" #: spa/plugins/alsa/acp/alsa-mixer.c:2814 msgid "HDMI / DisplayPort" msgstr "HDMI/DisplayPort" #: spa/plugins/alsa/acp/alsa-mixer.c:2815 msgid "Digital Output (S/PDIF)" msgstr "Digital utlyd (S/PDIF)" #: spa/plugins/alsa/acp/alsa-mixer.c:2816 msgid "Digital Input (S/PDIF)" msgstr "Digital innlyd (S/PDIF)" #: spa/plugins/alsa/acp/alsa-mixer.c:2817 msgid "Multichannel Input" msgstr "Multikanals innlyd" #: spa/plugins/alsa/acp/alsa-mixer.c:2818 msgid "Multichannel Output" msgstr "Multikanals utlyd" #: spa/plugins/alsa/acp/alsa-mixer.c:2819 msgid "Game Output" msgstr "Spellyd" #: spa/plugins/alsa/acp/alsa-mixer.c:2820 #: spa/plugins/alsa/acp/alsa-mixer.c:2821 msgid "Chat Output" msgstr "Nettprat utlyd" #: spa/plugins/alsa/acp/alsa-mixer.c:2822 msgid "Chat Input" msgstr "Nettprat innlyd" #: spa/plugins/alsa/acp/alsa-mixer.c:2823 msgid "Virtual Surround 7.1" msgstr "Virtuell kringlyd 7.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4527 msgid "Analog Mono" msgstr "Analog mono" #: spa/plugins/alsa/acp/alsa-mixer.c:4528 msgid "Analog Mono (Left)" msgstr "Analog mono (venstre)" #: spa/plugins/alsa/acp/alsa-mixer.c:4529 msgid "Analog Mono (Right)" msgstr "Analog mono (høgre)" #. Note: Not translated to "Analog Stereo Input", because the source #. * name gets "Input" appended to it automatically, so adding "Input" #. * here would lead to the source name to become "Analog Stereo Input #. * Input". The same logic applies to analog-stereo-output, #. * multichannel-input and multichannel-output. #: spa/plugins/alsa/acp/alsa-mixer.c:4530 #: spa/plugins/alsa/acp/alsa-mixer.c:4538 #: spa/plugins/alsa/acp/alsa-mixer.c:4539 msgid "Analog Stereo" msgstr "Analog stereo" #: spa/plugins/alsa/acp/alsa-mixer.c:4531 msgid "Mono" msgstr "Mono" #: spa/plugins/alsa/acp/alsa-mixer.c:4532 msgid "Stereo" msgstr "Stereo" #: spa/plugins/alsa/acp/alsa-mixer.c:4540 #: spa/plugins/alsa/acp/alsa-mixer.c:4698 #: spa/plugins/bluez5/bluez5-device.c:1135 msgid "Headset" msgstr "Hovudsett" #: spa/plugins/alsa/acp/alsa-mixer.c:4541 #: spa/plugins/alsa/acp/alsa-mixer.c:4699 msgid "Speakerphone" msgstr "Høgtalartelefon" #: spa/plugins/alsa/acp/alsa-mixer.c:4542 #: spa/plugins/alsa/acp/alsa-mixer.c:4543 msgid "Multichannel" msgstr "Multikanals" #: spa/plugins/alsa/acp/alsa-mixer.c:4544 msgid "Analog Surround 2.1" msgstr "Analog kringlyd 2.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4545 msgid "Analog Surround 3.0" msgstr "Analog kringlyd 3.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4546 msgid "Analog Surround 3.1" msgstr "Analog kringlyd 3.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4547 msgid "Analog Surround 4.0" msgstr "Analog kringlyd 4.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4548 msgid "Analog Surround 4.1" msgstr "Analog kringlyd 4.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4549 msgid "Analog Surround 5.0" msgstr "Analog kringlyd 5.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4550 msgid "Analog Surround 5.1" msgstr "Analog kringlyd 5.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4551 msgid "Analog Surround 6.0" msgstr "Analog kringlyd 6.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4552 msgid "Analog Surround 6.1" msgstr "Analog kringlyd 6.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4553 msgid "Analog Surround 7.0" msgstr "Analog kringlyd 7.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4554 msgid "Analog Surround 7.1" msgstr "Analog kringlyd 7.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4555 msgid "Digital Stereo (IEC958)" msgstr "Digital stereo (IEC958)" #: spa/plugins/alsa/acp/alsa-mixer.c:4556 msgid "Digital Surround 4.0 (IEC958/AC3)" msgstr "Digital kringlyd 4.0 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4557 msgid "Digital Surround 5.1 (IEC958/AC3)" msgstr "Digital kringlyd 5.1 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4558 msgid "Digital Surround 5.1 (IEC958/DTS)" msgstr "Digital kringlyd 5.1 (IEC958/DTS)" #: spa/plugins/alsa/acp/alsa-mixer.c:4559 msgid "Digital Stereo (HDMI)" msgstr "Digital stereo (HDMI)" #: spa/plugins/alsa/acp/alsa-mixer.c:4560 msgid "Digital Surround 5.1 (HDMI)" msgstr "Digital kringlyd 5.1 (HDMI)" #: spa/plugins/alsa/acp/alsa-mixer.c:4561 msgid "Chat" msgstr "Nettprat" #: spa/plugins/alsa/acp/alsa-mixer.c:4562 msgid "Game" msgstr "Spel" #: spa/plugins/alsa/acp/alsa-mixer.c:4696 msgid "Analog Mono Duplex" msgstr "Analog mono dupleks" #: spa/plugins/alsa/acp/alsa-mixer.c:4697 msgid "Analog Stereo Duplex" msgstr "Analog stereo dupleks" #: spa/plugins/alsa/acp/alsa-mixer.c:4700 msgid "Digital Stereo Duplex (IEC958)" msgstr "Digital stereo duplex (IEC958)" #: spa/plugins/alsa/acp/alsa-mixer.c:4701 msgid "Multichannel Duplex" msgstr "Multikanals dupleks" #: spa/plugins/alsa/acp/alsa-mixer.c:4702 msgid "Stereo Duplex" msgstr "Stereo dupleks" #: spa/plugins/alsa/acp/alsa-mixer.c:4703 msgid "Mono Chat + 7.1 Surround" msgstr "Mono-nettprat + 7.1-kringlyd" #: spa/plugins/alsa/acp/alsa-mixer.c:4806 #, c-format msgid "%s Output" msgstr "%s utlyd" #: spa/plugins/alsa/acp/alsa-mixer.c:4813 #, c-format msgid "%s Input" msgstr "%s innlyd" #: spa/plugins/alsa/acp/alsa-util.c:1175 spa/plugins/alsa/acp/alsa-util.c:1269 #, c-format msgid "" "snd_pcm_avail() returned a value that is exceptionally large: %lu byte (%lu " "ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_avail() returned a value that is exceptionally large: %lu bytes (%lu " "ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "snd_pcm_avail() gav ein verdi som er uvanleg stor: %lu byte (%lu ms).\n" "Dette kjem truleg av ein feil i ALSA-drivaren «%s». Meld frå om problemet " "til ALSA-utviklarane." msgstr[1] "" "snd_pcm_avail() gav ein verdi som er uvanleg stor: %lu byte (%lu ms).\n" "Dette kjem truleg av ein feil i ALSA-drivaren «%s». Meld frå om problemet " "til ALSA-utviklarane." #: spa/plugins/alsa/acp/alsa-util.c:1241 #, c-format msgid "" "snd_pcm_delay() returned a value that is exceptionally large: %li byte (%s" "%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_delay() returned a value that is exceptionally large: %li bytes (%s" "%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "snd_pcm_delay() gav ein verdi som er uvanleg stor: %li byte (%s%lu ms).\n" "Dette kjem truleg av ein feil i ALSA-drivaren «%s». Meld frå om problemet " "til ALSA-utviklarane." msgstr[1] "" "snd_pcm_delay() gav ein verdi som er uvanleg stor: %li byte (%s%lu ms).\n" "Dette kjem truleg av ein feil i ALSA-drivaren «%s». Meld frå om problemet " "til ALSA-utviklarane." #: spa/plugins/alsa/acp/alsa-util.c:1288 #, c-format msgid "" "snd_pcm_avail_delay() returned strange values: delay %lu is less than avail " "%lu.\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr "" "snd_pcm_avail_delay() gav ein merkeleg verdi: delay %lu er mindre enn " "avail %lu.\n" "Dette kjem truleg av ein feil i ALSA-drivaren «%s». Meld frå om problemet " "til ALSA-utviklarane." #: spa/plugins/alsa/acp/alsa-util.c:1331 #, c-format msgid "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu byte " "(%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu bytes " "(%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "snd_pcm_mmap_begin() gav ein verdi som er uvanleg stor: %lu byte (%lu ms).\n" "Dette kjem truleg av ein feil i ALSA-drivaren «%s». Meld frå om problemet " "til ALSA-utviklarane." msgstr[1] "" "snd_pcm_mmap_begin() gav ein verdi som er uvanleg stor: %lu byte (%lu ms).\n" "Dette kjem truleg av ein feil i ALSA-drivaren «%s». Meld frå om problemet " "til ALSA-utviklarane." #: spa/plugins/bluez5/bluez5-device.c:1010 msgid "Audio Gateway (A2DP Source & HSP/HFP AG)" msgstr "Inngangsport for lyd (A2DP-kilde & HSP/HFP AG)" #: spa/plugins/bluez5/bluez5-device.c:1033 #, c-format msgid "High Fidelity Playback (A2DP Sink, codec %s)" msgstr "Avspilling med naturtru lydattgjeving (A2DP Sink, codec %s)" #: spa/plugins/bluez5/bluez5-device.c:1035 #, c-format msgid "High Fidelity Duplex (A2DP Source/Sink, codec %s)" msgstr "Toveis lyd med naturtru attgjeving (A2DP Source/Sink, codec %s)" #: spa/plugins/bluez5/bluez5-device.c:1041 msgid "High Fidelity Playback (A2DP Sink)" msgstr "Avspilling med naturtru lydattgjeving (A2DP Sink)" #: spa/plugins/bluez5/bluez5-device.c:1043 msgid "High Fidelity Duplex (A2DP Source/Sink)" msgstr "Toveis lyd med naturtru attgjeving (A2DP Source/Sink)" #: spa/plugins/bluez5/bluez5-device.c:1070 #, c-format msgid "Headset Head Unit (HSP/HFP, codec %s)" msgstr "Hodesett (HSP/HFP, codec %s)" #: spa/plugins/bluez5/bluez5-device.c:1074 msgid "Headset Head Unit (HSP/HFP)" msgstr "Hodesett (HSP/HFP)" #: spa/plugins/bluez5/bluez5-device.c:1140 msgid "Handsfree" msgstr "Handfri" #: spa/plugins/bluez5/bluez5-device.c:1155 msgid "Headphone" msgstr "Hovudtelefonar" #: spa/plugins/bluez5/bluez5-device.c:1160 msgid "Portable" msgstr "Portabel" #: spa/plugins/bluez5/bluez5-device.c:1165 msgid "Car" msgstr "Bil" #: spa/plugins/bluez5/bluez5-device.c:1170 msgid "HiFi" msgstr "Hi-fi" #: spa/plugins/bluez5/bluez5-device.c:1175 msgid "Phone" msgstr "Telefon" #: spa/plugins/bluez5/bluez5-device.c:1181 msgid "Bluetooth" msgstr "Bluetooth" pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/po/oc.po000066400000000000000000000544411511204443500220650ustar00rootroot00000000000000# Occitan translation of pipewire. # Copyright (C) 2006-2008 Lennart Poettering # This file is distributed under the same license as the pipewire package. # Robert-André Mauchin , 2008. # Michaël Ughetto , 2008. # Pablo Martin-Gomez , 2008. # Corentin Perard , 2009. # Thomas Canniot , 2009, 2012. # Cédric Valmary (Tot en Òc) , 2015. # Cédric Valmary (totenoc.eu) , 2016. # Quentin PAGÈS, 2023.-2024 msgid "" msgstr "" "Project-Id-Version: pipewire trunk\n" "Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/pipewire/" "issues/new\n" "POT-Creation-Date: 2024-02-25 03:43+0300\n" "PO-Revision-Date: 2024-06-24 11:53+0200\n" "Last-Translator: Quentin PAGÈS\n" "Language-Team: Tot En Òc\n" "Language: oc\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n" "X-Generator: Poedit 3.4.3\n" "X-Launchpad-Export-Date: 2016-10-12 20:12+0000\n" #: src/daemon/pipewire.c:26 #, c-format msgid "" "%s [options]\n" " -h, --help Show this help\n" " --version Show version\n" " -c, --config Load config (Default %s)\n" msgstr "" "%s [opcions]\n" " -h, --help Afichar aquesta ajuda\n" " --version Afichar la version\n" " -c, --config Cargar la conf. (Defaut %s)\n" #: src/daemon/pipewire.desktop.in:4 msgid "PipeWire Media System" msgstr "Sistèma mèdia PipeWire" #: src/daemon/pipewire.desktop.in:5 msgid "Start the PipeWire Media System" msgstr "Aviar lo sistèma mèdia PipeWire" #: src/modules/module-protocol-pulse/modules/module-tunnel-sink.c:159 #: src/modules/module-protocol-pulse/modules/module-tunnel-source.c:159 #, c-format msgid "Tunnel to %s%s%s" msgstr "Tunèl cap a %s%s%s" #: src/modules/module-fallback-sink.c:40 msgid "Dummy Output" msgstr "Sortida factícia" #: src/modules/module-pulse-tunnel.c:774 #, c-format msgid "Tunnel for %s@%s" msgstr "Tunèl per %s@%s" #: src/modules/module-zeroconf-discover.c:315 msgid "Unknown device" msgstr "Periferic desconegut" #: src/modules/module-zeroconf-discover.c:327 #, c-format msgid "%s on %s@%s" msgstr "%s sus %s@%s" #: src/modules/module-zeroconf-discover.c:331 #, c-format msgid "%s on %s" msgstr "%s sus %s" #: src/tools/pw-cat.c:991 #, c-format msgid "" "%s [options] [|-]\n" " -h, --help Show this help\n" " --version Show version\n" " -v, --verbose Enable verbose operations\n" "\n" msgstr "" "%s [opcions] [|-]\n" " -h, --help Afichar aquesta ajuda\n" " --version Afichar la version\n" " -v, --verbose Activar las operacions verbosas\n" "\n" #: src/tools/pw-cat.c:998 #, c-format msgid "" " -R, --remote Remote daemon name\n" " --media-type Set media type (default %s)\n" " --media-category Set media category (default %s)\n" " --media-role Set media role (default %s)\n" " --target Set node target serial or name " "(default %s)\n" " 0 means don't link\n" " --latency Set node latency (default %s)\n" " Xunit (unit = s, ms, us, ns)\n" " or direct samples (256)\n" " the rate is the one of the source " "file\n" " -P --properties Set node properties\n" "\n" msgstr "" " -R, --remote Nom del demòni distant\n" " --media-type Definir lo tipe de mèdia (per defaut " "%s)\n" " --media-category Definir la categoria de mèdia (per " "defaut %s)\n" " --media-role Definir lo ròtle del mèdia (per " "defaut %s)\n" " --target Definir lo numèro de seria o lo nom " "de la cibla del nos (per defaut %s)\n" " 0 significa ligar pas\n" " --latency Definir la laténcia del nos (per " "defaut %s)\n" " Xunit (unit = s, ms, us, ns)\n" " o escandalhatge dirècte (256)\n" " lo taus es çò del fichièr font\n" " -P --properties Definir las proprietats del nos\n" "\n" #: src/tools/pw-cat.c:1016 #, c-format msgid "" " --rate Sample rate (req. for rec) (default " "%u)\n" " --channels Number of channels (req. for rec) " "(default %u)\n" " --channel-map Channel map\n" " one of: \"stereo\", " "\"surround-51\",... or\n" " comma separated list of channel " "names: eg. \"FL,FR\"\n" " --format Sample format %s (req. for rec) " "(default %s)\n" " --volume Stream volume 0-1.0 (default %.3f)\n" " -q --quality Resampler quality (0 - 15) (default " "%d)\n" "\n" msgstr "" " --rate Taus d'escandalhatge (req. per rec) " "(per defaut %u)\n" " --channels Nombre de canals (req. per rec) (per " "defaut %u)\n" " --channel-map Mapa de canal\n" " un de : \"stereo\", " "\"surround-51\",... o\n" " lista de nom de canal separats " "per de virgula : ex. \"FL,FR\"\n" " --format Format d'escandalhatge %s (req. per " "rec) (per defaut %s)\n" " --volume Volum del flux 0-1.0 (per defaut " "%.3f)\n" " -q --quality Qualitat del aus reescandalhatge (0 " "- 15) (per defaut %d)\n" "\n" #: src/tools/pw-cat.c:1033 msgid "" " -p, --playback Playback mode\n" " -r, --record Recording mode\n" " -m, --midi Midi mode\n" " -d, --dsd DSD mode\n" " -o, --encoded Encoded mode\n" "\n" msgstr "" " -p, --playback Mòde lectura\n" " -r, --record Mòde enregistrament\n" " -m, --midi Mòde Midi\n" " -d, --dsd Mòde DSD\n" " -o, --encoded %òde encodat\n" "\n" "\n" #: src/tools/pw-cli.c:2252 #, c-format msgid "" "%s [options] [command]\n" " -h, --help Show this help\n" " --version Show version\n" " -d, --daemon Start as daemon (Default false)\n" " -r, --remote Remote daemon name\n" " -m, --monitor Monitor activity\n" "\n" msgstr "" "%s [opcions] [comanda]\n" " -h, --help Afichar aquesta ajuda\n" " --version Afichar la version\n" " -d, --daemon Aviar coma demòni (Per defaut " "false)\n" " -r, --remote Nom del demòni distant\n" " -m, --monitor Susvelhar l’activitat\n" "\n" "\n" #: spa/plugins/alsa/acp/acp.c:327 msgid "Pro Audio" msgstr "Àudio pro" #: spa/plugins/alsa/acp/acp.c:488 spa/plugins/alsa/acp/alsa-mixer.c:4633 #: spa/plugins/bluez5/bluez5-device.c:1701 msgid "Off" msgstr "Atudat" #: spa/plugins/alsa/acp/alsa-mixer.c:2652 msgid "Input" msgstr "Entrada" #: spa/plugins/alsa/acp/alsa-mixer.c:2653 msgid "Docking Station Input" msgstr "Entrada de l'estacion d'acuèlh" #: spa/plugins/alsa/acp/alsa-mixer.c:2654 msgid "Docking Station Microphone" msgstr "Microfòn de l'estacion d'acuèlh" #: spa/plugins/alsa/acp/alsa-mixer.c:2655 msgid "Docking Station Line In" msgstr "Entrada linha de l'estacion d'acuèlh" #: spa/plugins/alsa/acp/alsa-mixer.c:2656 #: spa/plugins/alsa/acp/alsa-mixer.c:2747 msgid "Line In" msgstr "Entrada linha" #: spa/plugins/alsa/acp/alsa-mixer.c:2657 #: spa/plugins/alsa/acp/alsa-mixer.c:2741 #: spa/plugins/bluez5/bluez5-device.c:1989 msgid "Microphone" msgstr "Microfòn" #: spa/plugins/alsa/acp/alsa-mixer.c:2658 #: spa/plugins/alsa/acp/alsa-mixer.c:2742 msgid "Front Microphone" msgstr "Microfòn avant" #: spa/plugins/alsa/acp/alsa-mixer.c:2659 #: spa/plugins/alsa/acp/alsa-mixer.c:2743 msgid "Rear Microphone" msgstr "Microfòn arrièr" #: spa/plugins/alsa/acp/alsa-mixer.c:2660 msgid "External Microphone" msgstr "Microfòn extèrne" #: spa/plugins/alsa/acp/alsa-mixer.c:2661 #: spa/plugins/alsa/acp/alsa-mixer.c:2745 msgid "Internal Microphone" msgstr "Microfòn intèrne" #: spa/plugins/alsa/acp/alsa-mixer.c:2662 #: spa/plugins/alsa/acp/alsa-mixer.c:2748 msgid "Radio" msgstr "Ràdio" #: spa/plugins/alsa/acp/alsa-mixer.c:2663 #: spa/plugins/alsa/acp/alsa-mixer.c:2749 msgid "Video" msgstr "Vidèo" #: spa/plugins/alsa/acp/alsa-mixer.c:2664 msgid "Automatic Gain Control" msgstr "Contraròtle automatic del ganh" #: spa/plugins/alsa/acp/alsa-mixer.c:2665 msgid "No Automatic Gain Control" msgstr "Cap de contraròtle automatic del ganh" #: spa/plugins/alsa/acp/alsa-mixer.c:2666 msgid "Boost" msgstr "Boost" #: spa/plugins/alsa/acp/alsa-mixer.c:2667 msgid "No Boost" msgstr "Sens boost" #: spa/plugins/alsa/acp/alsa-mixer.c:2668 msgid "Amplifier" msgstr "Amplificador" #: spa/plugins/alsa/acp/alsa-mixer.c:2669 msgid "No Amplifier" msgstr "Cap d'amplificador" #: spa/plugins/alsa/acp/alsa-mixer.c:2670 msgid "Bass Boost" msgstr "Amplificacion bassas" #: spa/plugins/alsa/acp/alsa-mixer.c:2671 msgid "No Bass Boost" msgstr "Cap d'amplificacion de las bassas" #: spa/plugins/alsa/acp/alsa-mixer.c:2672 #: spa/plugins/bluez5/bluez5-device.c:1995 msgid "Speaker" msgstr "Nautparlaire" #: spa/plugins/alsa/acp/alsa-mixer.c:2673 #: spa/plugins/alsa/acp/alsa-mixer.c:2751 msgid "Headphones" msgstr "Escotadors" #: spa/plugins/alsa/acp/alsa-mixer.c:2740 msgid "Analog Input" msgstr "Entrada analogica" #: spa/plugins/alsa/acp/alsa-mixer.c:2744 msgid "Dock Microphone" msgstr "Microfòn de l'estacion d'acuèlh" #: spa/plugins/alsa/acp/alsa-mixer.c:2746 msgid "Headset Microphone" msgstr "Micro-casc" #: spa/plugins/alsa/acp/alsa-mixer.c:2750 msgid "Analog Output" msgstr "Sortida analogica" #: spa/plugins/alsa/acp/alsa-mixer.c:2752 msgid "Headphones 2" msgstr "Casc àudio 2" #: spa/plugins/alsa/acp/alsa-mixer.c:2753 msgid "Headphones Mono Output" msgstr "Sortida casc àudio analogica mono" #: spa/plugins/alsa/acp/alsa-mixer.c:2754 msgid "Line Out" msgstr "Sortida linha" #: spa/plugins/alsa/acp/alsa-mixer.c:2755 msgid "Analog Mono Output" msgstr "Sortida analogica mono" #: spa/plugins/alsa/acp/alsa-mixer.c:2756 msgid "Speakers" msgstr "Nauts parlaires" #: spa/plugins/alsa/acp/alsa-mixer.c:2757 msgid "HDMI / DisplayPort" msgstr "HDMI / DisplayPort" #: spa/plugins/alsa/acp/alsa-mixer.c:2758 msgid "Digital Output (S/PDIF)" msgstr "Sortida numerica (S/PDIF)" #: spa/plugins/alsa/acp/alsa-mixer.c:2759 msgid "Digital Input (S/PDIF)" msgstr "Entrada numerica (S/PDIF)" #: spa/plugins/alsa/acp/alsa-mixer.c:2760 msgid "Multichannel Input" msgstr "Entrada multicanal" #: spa/plugins/alsa/acp/alsa-mixer.c:2761 msgid "Multichannel Output" msgstr "Sortida multicanal" #: spa/plugins/alsa/acp/alsa-mixer.c:2762 msgid "Game Output" msgstr "Sortida jòc" #: spa/plugins/alsa/acp/alsa-mixer.c:2763 #: spa/plugins/alsa/acp/alsa-mixer.c:2764 msgid "Chat Output" msgstr "Sortida messatjariá" #: spa/plugins/alsa/acp/alsa-mixer.c:2765 msgid "Chat Input" msgstr "Entrada messatjariá" #: spa/plugins/alsa/acp/alsa-mixer.c:2766 msgid "Virtual Surround 7.1" msgstr "Surround 7.1 virtual" #: spa/plugins/alsa/acp/alsa-mixer.c:4456 msgid "Analog Mono" msgstr "Mono analogic" #: spa/plugins/alsa/acp/alsa-mixer.c:4457 msgid "Analog Mono (Left)" msgstr "Mono analogic (esquèrra)" #: spa/plugins/alsa/acp/alsa-mixer.c:4458 msgid "Analog Mono (Right)" msgstr "Mono analogic (drecha)" #. Note: Not translated to "Analog Stereo Input", because the source #. * name gets "Input" appended to it automatically, so adding "Input" #. * here would lead to the source name to become "Analog Stereo Input #. * Input". The same logic applies to analog-stereo-output, #. * multichannel-input and multichannel-output. #: spa/plugins/alsa/acp/alsa-mixer.c:4459 #: spa/plugins/alsa/acp/alsa-mixer.c:4467 #: spa/plugins/alsa/acp/alsa-mixer.c:4468 msgid "Analog Stereo" msgstr "Estereo analogic" #: spa/plugins/alsa/acp/alsa-mixer.c:4460 msgid "Mono" msgstr "Mono" #: spa/plugins/alsa/acp/alsa-mixer.c:4461 msgid "Stereo" msgstr "Estereo" #: spa/plugins/alsa/acp/alsa-mixer.c:4469 #: spa/plugins/alsa/acp/alsa-mixer.c:4627 #: spa/plugins/bluez5/bluez5-device.c:1977 msgid "Headset" msgstr "Casc àudio" #: spa/plugins/alsa/acp/alsa-mixer.c:4470 #: spa/plugins/alsa/acp/alsa-mixer.c:4628 msgid "Speakerphone" msgstr "Nautparlaire" #: spa/plugins/alsa/acp/alsa-mixer.c:4471 #: spa/plugins/alsa/acp/alsa-mixer.c:4472 msgid "Multichannel" msgstr "Multicanal" #: spa/plugins/alsa/acp/alsa-mixer.c:4473 msgid "Analog Surround 2.1" msgstr "Surround analogic 2.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4474 msgid "Analog Surround 3.0" msgstr "Surround analogic 3.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4475 msgid "Analog Surround 3.1" msgstr "Surround analogic 3.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4476 msgid "Analog Surround 4.0" msgstr "Surround analogic 4.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4477 msgid "Analog Surround 4.1" msgstr "Surround analogic 4.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4478 msgid "Analog Surround 5.0" msgstr "Surround analogic 5.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4479 msgid "Analog Surround 5.1" msgstr "Surround analogic 5.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4480 msgid "Analog Surround 6.0" msgstr "Surround analogic 6.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4481 msgid "Analog Surround 6.1" msgstr "Surround analogic 6.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4482 msgid "Analog Surround 7.0" msgstr "Surround analogic 7.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4483 msgid "Analog Surround 7.1" msgstr "Surround analogic 7.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4484 msgid "Digital Stereo (IEC958)" msgstr "Estereo numeric (IEC958)" #: spa/plugins/alsa/acp/alsa-mixer.c:4485 msgid "Digital Surround 4.0 (IEC958/AC3)" msgstr "Surround numeric 4.0 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4486 msgid "Digital Surround 5.1 (IEC958/AC3)" msgstr "Surround numeric 5.1 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4487 msgid "Digital Surround 5.1 (IEC958/DTS)" msgstr "Digital Surround 5.1 (IEC958/DTS)" #: spa/plugins/alsa/acp/alsa-mixer.c:4488 msgid "Digital Stereo (HDMI)" msgstr "Estereo numeric (HDMI)" #: spa/plugins/alsa/acp/alsa-mixer.c:4489 msgid "Digital Surround 5.1 (HDMI)" msgstr "Digital Surround 5.1 (HDMI)" #: spa/plugins/alsa/acp/alsa-mixer.c:4490 msgid "Chat" msgstr "Messatjariá instantanèa" #: spa/plugins/alsa/acp/alsa-mixer.c:4491 msgid "Game" msgstr "Jòc" #: spa/plugins/alsa/acp/alsa-mixer.c:4625 msgid "Analog Mono Duplex" msgstr "Duplèx Mono analogic" #: spa/plugins/alsa/acp/alsa-mixer.c:4626 msgid "Analog Stereo Duplex" msgstr "Duplèx esterèo analogic" #: spa/plugins/alsa/acp/alsa-mixer.c:4629 msgid "Digital Stereo Duplex (IEC958)" msgstr "Duplèx estèreo numeric (IEC958)" #: spa/plugins/alsa/acp/alsa-mixer.c:4630 msgid "Multichannel Duplex" msgstr "Duplèx multicanal" #: spa/plugins/alsa/acp/alsa-mixer.c:4631 msgid "Stereo Duplex" msgstr "Duplèx estereo" #: spa/plugins/alsa/acp/alsa-mixer.c:4632 msgid "Mono Chat + 7.1 Surround" msgstr "Messatjariá mono + Surround 7.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4733 #, c-format msgid "%s Output" msgstr "Sortida %s" #: spa/plugins/alsa/acp/alsa-mixer.c:4741 #, c-format msgid "%s Input" msgstr "Entrada %s" #: spa/plugins/alsa/acp/alsa-util.c:1220 spa/plugins/alsa/acp/alsa-util.c:1314 #, c-format msgid "" "snd_pcm_avail() returned a value that is exceptionally large: %lu byte (%lu " "ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_avail() returned a value that is exceptionally large: %lu bytes (%lu " "ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "snd_pcm_avail() a tornat una valor qu'es excepcionalament larga : %lu octet " "(%lu ms).\n" "Es fòrt probablament un bug dins lo pilòt ALSA « %s ». Senhalatz-lo als " "desvolopaires d’ALSA." msgstr[1] "" "snd_pcm_avail() a tornat una valor qu'es excepcionalament larga : %lu octets " "(%lu ms).\n" "Es fòrt probablament un bug dins lo pilòt ALSA « %s ». Senhalatz-lo als " "desvolopaires d’ALSA." #: spa/plugins/alsa/acp/alsa-util.c:1286 #, c-format msgid "" "snd_pcm_delay() returned a value that is exceptionally large: %li byte (%s" "%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_delay() returned a value that is exceptionally large: %li bytes (%s" "%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "snd_pcm_delay() a tornat una valor qu'es excepcionalament larga : %li octet " "%s%lu ms).\n" "Es fòrt probablament un bug dins lo pilòt ALSA « %s ». Senhalatz-lo als " "desvolopaires d’ALSA." msgstr[1] "" "snd_pcm_delay() a tornat una valor qu'es excepcionalament larga : %li octets " "%s%lu ms).\n" "Es fòrt probablament un bug dins lo pilòt ALSA « %s ». Senhalatz-lo als " "desvolopaires d’ALSA." #: spa/plugins/alsa/acp/alsa-util.c:1333 #, c-format msgid "" "snd_pcm_avail_delay() returned strange values: delay %lu is less than avail " "%lu.\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr "" "snd_pcm_avail_delay() a tornat de resultats anormals : lo relambi %lu es mai " "pichon que %lu.\n" "Es fòrt probablament un bug dins lo pilòt ALSA « %s ». Senhalatz-lo als " "desvolopaires d’ALSA." #: spa/plugins/alsa/acp/alsa-util.c:1376 #, c-format msgid "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu byte " "(%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu bytes " "(%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "snd_pcm_mmap_begin() a tornat una valor qu'es excepcionalament larga : %lu " "octet (%lu ms).\n" "Es fòrt probablament un bug dins lo pilòt ALSA « %s ». Senhalatz-lo als " "desvolopaires d’ALSA." msgstr[1] "" "snd_pcm_mmap_begin() a tornat una valor qu'es excepcionalament larga : %lu " "octet (%lu ms).\n" "Es fòrt probablament un bug dins lo pilòt ALSA « %s ». Senhalatz-lo als " "desvolopaires d’ALSA." #: spa/plugins/alsa/acp/channelmap.h:457 msgid "(invalid)" msgstr "(invalid)" #: spa/plugins/alsa/acp/compat.c:193 msgid "Built-in Audio" msgstr "Àudio integrat" #: spa/plugins/alsa/acp/compat.c:198 msgid "Modem" msgstr "Modèm" #: spa/plugins/bluez5/bluez5-device.c:1712 msgid "Audio Gateway (A2DP Source & HSP/HFP AG)" msgstr "Palanca àudio (Font A2DP & HSP/HFP AG)" #: spa/plugins/bluez5/bluez5-device.c:1760 #, c-format msgid "High Fidelity Playback (A2DP Sink, codec %s)" msgstr "Lectura nauta fidelitat (A2DP Sink, codec %s)" #: spa/plugins/bluez5/bluez5-device.c:1763 #, c-format msgid "High Fidelity Duplex (A2DP Source/Sink, codec %s)" msgstr "Duplèx nauta fidelitat (A2DP Source/Sink, codec %s)" #: spa/plugins/bluez5/bluez5-device.c:1771 msgid "High Fidelity Playback (A2DP Sink)" msgstr "Lectura nauta fidelitat (A2DP Sink)" #: spa/plugins/bluez5/bluez5-device.c:1773 msgid "High Fidelity Duplex (A2DP Source/Sink)" msgstr "Duplèx nauta fidelitat (A2DP Source/Sink)" #: spa/plugins/bluez5/bluez5-device.c:1823 #, c-format msgid "High Fidelity Playback (BAP Sink, codec %s)" msgstr "Lectura nauta fidelitat (A2DP Sink, codec %s)" #: spa/plugins/bluez5/bluez5-device.c:1828 #, c-format msgid "High Fidelity Input (BAP Source, codec %s)" msgstr "Duplèx nauta fidelitat (Font BAP, codec %s)" #: spa/plugins/bluez5/bluez5-device.c:1832 #, c-format msgid "High Fidelity Duplex (BAP Source/Sink, codec %s)" msgstr "Duplèx nauta fidelitat (Font BAP/Sink, codec %s)" #: spa/plugins/bluez5/bluez5-device.c:1841 msgid "High Fidelity Playback (BAP Sink)" msgstr "Lectura nauta fidelitat (receptor BAP)" #: spa/plugins/bluez5/bluez5-device.c:1845 msgid "High Fidelity Input (BAP Source)" msgstr "Duplèx nauta fidelitat (Font BAP)" #: spa/plugins/bluez5/bluez5-device.c:1848 msgid "High Fidelity Duplex (BAP Source/Sink)" msgstr "Duplèx nauta fidelitat (Font/Receptor BAP)" #: spa/plugins/bluez5/bluez5-device.c:1897 #, c-format msgid "Headset Head Unit (HSP/HFP, codec %s)" msgstr "Controlador de casc (HSP/HFP, codec %s)" #: spa/plugins/bluez5/bluez5-device.c:1978 #: spa/plugins/bluez5/bluez5-device.c:1983 #: spa/plugins/bluez5/bluez5-device.c:1990 #: spa/plugins/bluez5/bluez5-device.c:1996 #: spa/plugins/bluez5/bluez5-device.c:2002 #: spa/plugins/bluez5/bluez5-device.c:2008 #: spa/plugins/bluez5/bluez5-device.c:2014 #: spa/plugins/bluez5/bluez5-device.c:2020 #: spa/plugins/bluez5/bluez5-device.c:2026 msgid "Handsfree" msgstr "Mans liuras" #: spa/plugins/bluez5/bluez5-device.c:1984 msgid "Handsfree (HFP)" msgstr "Mans liuras (HFP)" #: spa/plugins/bluez5/bluez5-device.c:2001 msgid "Headphone" msgstr "Escotador" #: spa/plugins/bluez5/bluez5-device.c:2007 msgid "Portable" msgstr "Portable" #: spa/plugins/bluez5/bluez5-device.c:2013 msgid "Car" msgstr "Telefòn de veitura" #: spa/plugins/bluez5/bluez5-device.c:2019 msgid "HiFi" msgstr "HiFi" #: spa/plugins/bluez5/bluez5-device.c:2025 msgid "Phone" msgstr "Telefòn" #: spa/plugins/bluez5/bluez5-device.c:2032 msgid "Bluetooth" msgstr "Bluetooth" #: spa/plugins/bluez5/bluez5-device.c:2033 msgid "Bluetooth (HFP)" msgstr "Bluetooth (HFP)"pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/po/or.po000066400000000000000000000454111511204443500221010ustar00rootroot00000000000000# translation of pipewire.master-tx.or.po to Oriya # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # Manoj Kumar Giri , 2009, 2012. # msgid "" msgstr "" "Project-Id-Version: pipewire.master-tx.or\n" "Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/pipewire/" "issues/new\n" "POT-Creation-Date: 2021-04-18 16:54+0800\n" "PO-Revision-Date: 2012-01-30 09:55+0000\n" "Last-Translator: Manoj Kumar Giri \n" "Language-Team: Oriya \n" "Language: or\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Generator: KBabel 1.11.4\n" "Plural-Forms: nplurals=2; plural=(n!=1);\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n" #: src/daemon/pipewire.c:43 #, c-format msgid "" "%s [options]\n" " -h, --help Show this help\n" " --version Show version\n" " -c, --config Load config (Default %s)\n" msgstr "" #: src/daemon/pipewire.desktop.in:4 msgid "PipeWire Media System" msgstr "" #: src/daemon/pipewire.desktop.in:5 msgid "Start the PipeWire Media System" msgstr "" #: src/examples/media-session/alsa-monitor.c:526 #: spa/plugins/alsa/acp/compat.c:187 msgid "Built-in Audio" msgstr "ଆଭ୍ୟନ୍ତରୀଣ ଧ୍ୱନି" #: src/examples/media-session/alsa-monitor.c:530 #: spa/plugins/alsa/acp/compat.c:192 msgid "Modem" msgstr "ମଡେମ" #: src/examples/media-session/alsa-monitor.c:539 msgid "Unknown device" msgstr "" #: src/tools/pw-cat.c:991 #, c-format msgid "" "%s [options] \n" " -h, --help Show this help\n" " --version Show version\n" " -v, --verbose Enable verbose operations\n" "\n" msgstr "" #: src/tools/pw-cat.c:998 #, c-format msgid "" " -R, --remote Remote daemon name\n" " --media-type Set media type (default %s)\n" " --media-category Set media category (default %s)\n" " --media-role Set media role (default %s)\n" " --target Set node target (default %s)\n" " 0 means don't link\n" " --latency Set node latency (default %s)\n" " Xunit (unit = s, ms, us, ns)\n" " or direct samples (256)\n" " the rate is the one of the source " "file\n" " --list-targets List available targets for --target\n" "\n" msgstr "" #: src/tools/pw-cat.c:1016 #, c-format msgid "" " --rate Sample rate (req. for rec) (default " "%u)\n" " --channels Number of channels (req. for rec) " "(default %u)\n" " --channel-map Channel map\n" " one of: \"stereo\", " "\"surround-51\",... or\n" " comma separated list of channel " "names: eg. \"FL,FR\"\n" " --format Sample format %s (req. for rec) " "(default %s)\n" " --volume Stream volume 0-1.0 (default %.3f)\n" " -q --quality Resampler quality (0 - 15) (default " "%d)\n" "\n" msgstr "" #: src/tools/pw-cat.c:1033 msgid "" " -p, --playback Playback mode\n" " -r, --record Recording mode\n" " -m, --midi Midi mode\n" "\n" msgstr "" #: src/tools/pw-cli.c:2932 #, c-format msgid "" "%s [options] [command]\n" " -h, --help Show this help\n" " --version Show version\n" " -d, --daemon Start as daemon (Default false)\n" " -r, --remote Remote daemon name\n" "\n" msgstr "" #: spa/plugins/alsa/acp/acp.c:290 msgid "Pro Audio" msgstr "" #: spa/plugins/alsa/acp/acp.c:411 spa/plugins/alsa/acp/alsa-mixer.c:4704 #: spa/plugins/bluez5/bluez5-device.c:1000 msgid "Off" msgstr "ଅଫ" #: spa/plugins/alsa/acp/channelmap.h:466 msgid "(invalid)" msgstr "(ଅବୈଧ)" #: spa/plugins/alsa/acp/alsa-mixer.c:2709 msgid "Input" msgstr "ନିବେଶ" #: spa/plugins/alsa/acp/alsa-mixer.c:2710 msgid "Docking Station Input" msgstr "ଡକିଙ୍ଗ ଷ୍ଟେସନ ନିବେଶ" #: spa/plugins/alsa/acp/alsa-mixer.c:2711 #, fuzzy msgid "Docking Station Microphone" msgstr "ଡକିଙ୍ଗ ଷ୍ଟେସନ ମାଇକ୍ରୋଫୋନ" #: spa/plugins/alsa/acp/alsa-mixer.c:2712 #, fuzzy msgid "Docking Station Line In" msgstr "ଡକିଙ୍ଗ ଷ୍ଟେସନ ନିବେଶ" #: spa/plugins/alsa/acp/alsa-mixer.c:2713 #: spa/plugins/alsa/acp/alsa-mixer.c:2804 msgid "Line In" msgstr "ଲାଇନ-ଇନ" #: spa/plugins/alsa/acp/alsa-mixer.c:2714 #: spa/plugins/alsa/acp/alsa-mixer.c:2798 #: spa/plugins/bluez5/bluez5-device.c:1145 msgid "Microphone" msgstr "ମାଇକ୍ରୋଫୋନ" #: spa/plugins/alsa/acp/alsa-mixer.c:2715 #: spa/plugins/alsa/acp/alsa-mixer.c:2799 #, fuzzy msgid "Front Microphone" msgstr "ଡକିଙ୍ଗ ଷ୍ଟେସନ ମାଇକ୍ରୋଫୋନ" #: spa/plugins/alsa/acp/alsa-mixer.c:2716 #: spa/plugins/alsa/acp/alsa-mixer.c:2800 #, fuzzy msgid "Rear Microphone" msgstr "ମାଇକ୍ରୋଫୋନ" #: spa/plugins/alsa/acp/alsa-mixer.c:2717 msgid "External Microphone" msgstr "ବାହ୍ୟ ମାଇକ୍ରୋଫୋନ" #: spa/plugins/alsa/acp/alsa-mixer.c:2718 #: spa/plugins/alsa/acp/alsa-mixer.c:2802 msgid "Internal Microphone" msgstr "ଆଭ୍ୟନ୍ତରୀଣ ମାଇକ୍ରୋଫୋନ" #: spa/plugins/alsa/acp/alsa-mixer.c:2719 #: spa/plugins/alsa/acp/alsa-mixer.c:2805 msgid "Radio" msgstr "ରେଡିଓ" #: spa/plugins/alsa/acp/alsa-mixer.c:2720 #: spa/plugins/alsa/acp/alsa-mixer.c:2806 msgid "Video" msgstr "ଭିଡ଼ିଓ" #: spa/plugins/alsa/acp/alsa-mixer.c:2721 msgid "Automatic Gain Control" msgstr "ସ୍ୱୟଂଚାଳିତ ଲାଭ ନିୟନ୍ତ୍ରଣ" #: spa/plugins/alsa/acp/alsa-mixer.c:2722 msgid "No Automatic Gain Control" msgstr "କୌଣସି ସ୍ୱୟଂଚାଳିତ ଲାଭ ନିୟନ୍ତ୍ରଣ ନାହିଁ" #: spa/plugins/alsa/acp/alsa-mixer.c:2723 msgid "Boost" msgstr "ବୃଦ୍ଧି" #: spa/plugins/alsa/acp/alsa-mixer.c:2724 msgid "No Boost" msgstr "ବୃଦ୍ଧି ନାହିଁ" #: spa/plugins/alsa/acp/alsa-mixer.c:2725 msgid "Amplifier" msgstr "ଏମ୍ପ୍ଲିଫାୟର" #: spa/plugins/alsa/acp/alsa-mixer.c:2726 msgid "No Amplifier" msgstr "ଏମ୍ପ୍ଲିଫାୟର ନାହିଁ" #: spa/plugins/alsa/acp/alsa-mixer.c:2727 #, fuzzy msgid "Bass Boost" msgstr "ବୃଦ୍ଧି" #: spa/plugins/alsa/acp/alsa-mixer.c:2728 #, fuzzy msgid "No Bass Boost" msgstr "ବୃଦ୍ଧି ନାହିଁ" #: spa/plugins/alsa/acp/alsa-mixer.c:2729 #: spa/plugins/bluez5/bluez5-device.c:1150 msgid "Speaker" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2730 #: spa/plugins/alsa/acp/alsa-mixer.c:2808 msgid "Headphones" msgstr "ଏନାଲୋଗ ହେଡ଼ଫୋନଗୁଡ଼ିକ" #: spa/plugins/alsa/acp/alsa-mixer.c:2797 msgid "Analog Input" msgstr "ଏନାଲୋଗ ନିବେଶ" #: spa/plugins/alsa/acp/alsa-mixer.c:2801 msgid "Dock Microphone" msgstr "ଡକିଙ୍ଗ ଷ୍ଟେସନ ମାଇକ୍ରୋଫୋନ" #: spa/plugins/alsa/acp/alsa-mixer.c:2803 #, fuzzy msgid "Headset Microphone" msgstr "ମାଇକ୍ରୋଫୋନ" #: spa/plugins/alsa/acp/alsa-mixer.c:2807 msgid "Analog Output" msgstr "ଏନାଲୋଗ ଫଳାଫଳ" #: spa/plugins/alsa/acp/alsa-mixer.c:2809 #, fuzzy msgid "Headphones 2" msgstr "ଏନାଲୋଗ ହେଡ଼ଫୋନଗୁଡ଼ିକ" #: spa/plugins/alsa/acp/alsa-mixer.c:2810 #, fuzzy msgid "Headphones Mono Output" msgstr "ଏନାଲୋଗ ମୋନୋ ଫଳାଫଳ" #: spa/plugins/alsa/acp/alsa-mixer.c:2811 #, fuzzy msgid "Line Out" msgstr "ଲାଇନ-ଇନ" #: spa/plugins/alsa/acp/alsa-mixer.c:2812 msgid "Analog Mono Output" msgstr "ଏନାଲୋଗ ମୋନୋ ଫଳାଫଳ" #: spa/plugins/alsa/acp/alsa-mixer.c:2813 #, fuzzy msgid "Speakers" msgstr "ଏନାଲୋଗ ଷ୍ଟେରିଓ" #: spa/plugins/alsa/acp/alsa-mixer.c:2814 msgid "HDMI / DisplayPort" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2815 #, fuzzy msgid "Digital Output (S/PDIF)" msgstr "ଡିଜିଟାଲ ଷ୍ଟେରିଓ (HDMI)" #: spa/plugins/alsa/acp/alsa-mixer.c:2816 #, fuzzy msgid "Digital Input (S/PDIF)" msgstr "ଡିଜିଟାଲ ଷ୍ଟେରିଓ (HDMI)" #: spa/plugins/alsa/acp/alsa-mixer.c:2817 msgid "Multichannel Input" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2818 #, fuzzy msgid "Multichannel Output" msgstr "ଶୂନ୍ୟ ଫଳାଫଳ" #: spa/plugins/alsa/acp/alsa-mixer.c:2819 #, fuzzy msgid "Game Output" msgstr "ଶୂନ୍ୟ ଫଳାଫଳ" #: spa/plugins/alsa/acp/alsa-mixer.c:2820 #: spa/plugins/alsa/acp/alsa-mixer.c:2821 #, fuzzy msgid "Chat Output" msgstr "ଶୂନ୍ୟ ଫଳାଫଳ" #: spa/plugins/alsa/acp/alsa-mixer.c:2822 #, fuzzy msgid "Chat Input" msgstr "ନିବେଶ" #: spa/plugins/alsa/acp/alsa-mixer.c:2823 #, fuzzy msgid "Virtual Surround 7.1" msgstr "ଏନାଲୋଗ ଚତୁଃ ପାର୍ଶ୍ୱ 7.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4527 msgid "Analog Mono" msgstr "ଏନାଲୋଗ ମୋନୋ" #: spa/plugins/alsa/acp/alsa-mixer.c:4528 #, fuzzy msgid "Analog Mono (Left)" msgstr "ଏନାଲୋଗ ମୋନୋ" #: spa/plugins/alsa/acp/alsa-mixer.c:4529 #, fuzzy msgid "Analog Mono (Right)" msgstr "ଏନାଲୋଗ ମୋନୋ" #. Note: Not translated to "Analog Stereo Input", because the source #. * name gets "Input" appended to it automatically, so adding "Input" #. * here would lead to the source name to become "Analog Stereo Input #. * Input". The same logic applies to analog-stereo-output, #. * multichannel-input and multichannel-output. #: spa/plugins/alsa/acp/alsa-mixer.c:4530 #: spa/plugins/alsa/acp/alsa-mixer.c:4538 #: spa/plugins/alsa/acp/alsa-mixer.c:4539 msgid "Analog Stereo" msgstr "ଏନାଲୋଗ ଷ୍ଟେରିଓ" #: spa/plugins/alsa/acp/alsa-mixer.c:4531 msgid "Mono" msgstr "ମୋନୋ" #: spa/plugins/alsa/acp/alsa-mixer.c:4532 msgid "Stereo" msgstr "ଷ୍ଟେରିଓ" #: spa/plugins/alsa/acp/alsa-mixer.c:4540 #: spa/plugins/alsa/acp/alsa-mixer.c:4698 #: spa/plugins/bluez5/bluez5-device.c:1135 msgid "Headset" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4541 #: spa/plugins/alsa/acp/alsa-mixer.c:4699 #, fuzzy msgid "Speakerphone" msgstr "ଏନାଲୋଗ ଷ୍ଟେରିଓ" #: spa/plugins/alsa/acp/alsa-mixer.c:4542 #: spa/plugins/alsa/acp/alsa-mixer.c:4543 msgid "Multichannel" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4544 msgid "Analog Surround 2.1" msgstr "ଏନାଲୋଗ ଚତୁଃ ପାର୍ଶ୍ୱ 2.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4545 msgid "Analog Surround 3.0" msgstr "ଏନାଲୋଗ ଚତୁଃ ପାର୍ଶ୍ୱ 3.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4546 msgid "Analog Surround 3.1" msgstr "ଏନାଲୋଗ ଚତୁଃ ପାର୍ଶ୍ୱ 3.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4547 msgid "Analog Surround 4.0" msgstr "ଏନାଲୋଗ ଚତୁଃ ପାର୍ଶ୍ୱ 4.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4548 msgid "Analog Surround 4.1" msgstr "ଏନାଲୋଗ ଚତୁଃ ପାର୍ଶ୍ୱ 4.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4549 msgid "Analog Surround 5.0" msgstr "ଏନାଲୋଗ ଚତୁଃ ପାର୍ଶ୍ୱ 5.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4550 msgid "Analog Surround 5.1" msgstr "ଏନାଲୋଗ ଚତୁଃ ପାର୍ଶ୍ୱ 5.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4551 msgid "Analog Surround 6.0" msgstr "ଏନାଲୋଗ ଚତୁଃ ପାର୍ଶ୍ୱ 6.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4552 msgid "Analog Surround 6.1" msgstr "ଏନାଲୋଗ ଚତୁଃ ପାର୍ଶ୍ୱ 6.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4553 msgid "Analog Surround 7.0" msgstr "ଏନାଲୋଗ ଚତୁଃ ପାର୍ଶ୍ୱ 7.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4554 msgid "Analog Surround 7.1" msgstr "ଏନାଲୋଗ ଚତୁଃ ପାର୍ଶ୍ୱ 7.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4555 msgid "Digital Stereo (IEC958)" msgstr "ଡିଜିଟାଲ ଷ୍ଟେରିଓ (IEC958)" #: spa/plugins/alsa/acp/alsa-mixer.c:4556 msgid "Digital Surround 4.0 (IEC958/AC3)" msgstr "ଡିଜିଟାଲ ଚତୁଃ ପାର୍ଶ୍ୱ 4.0 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4557 msgid "Digital Surround 5.1 (IEC958/AC3)" msgstr "ଡିଜିଟାଲ ଚତୁଃ ପାର୍ଶ୍ୱ 5.1 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4558 #, fuzzy msgid "Digital Surround 5.1 (IEC958/DTS)" msgstr "ଡିଜିଟାଲ ଚତୁଃ ପାର୍ଶ୍ୱ 5.1 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4559 msgid "Digital Stereo (HDMI)" msgstr "ଡିଜିଟାଲ ଷ୍ଟେରିଓ (HDMI)" #: spa/plugins/alsa/acp/alsa-mixer.c:4560 #, fuzzy msgid "Digital Surround 5.1 (HDMI)" msgstr "ଡିଜିଟାଲ ଚତୁଃ ପାର୍ଶ୍ୱ 5.1 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4561 msgid "Chat" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4562 msgid "Game" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4696 msgid "Analog Mono Duplex" msgstr "ଏନାଲୋଗ ମୋନୋ ଡ଼ୁପ୍ଲେକ୍ସ" #: spa/plugins/alsa/acp/alsa-mixer.c:4697 msgid "Analog Stereo Duplex" msgstr "ଏନାଲୋଗ ଷ୍ଟେରିଓ ଡ଼ୁପ୍ଲେକ୍ସ" #: spa/plugins/alsa/acp/alsa-mixer.c:4700 msgid "Digital Stereo Duplex (IEC958)" msgstr "ଡିଜିଟାଲ ଷ୍ଟେରିଓ ଡ଼ୁପ୍ଲେକ୍ସ (IEC958)" #: spa/plugins/alsa/acp/alsa-mixer.c:4701 msgid "Multichannel Duplex" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4702 #, fuzzy msgid "Stereo Duplex" msgstr "ଏନାଲୋଗ ଷ୍ଟେରିଓ ଡ଼ୁପ୍ଲେକ୍ସ" #: spa/plugins/alsa/acp/alsa-mixer.c:4703 msgid "Mono Chat + 7.1 Surround" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4806 #, fuzzy, c-format msgid "%s Output" msgstr "ଶୂନ୍ୟ ଫଳାଫଳ" #: spa/plugins/alsa/acp/alsa-mixer.c:4813 #, fuzzy, c-format msgid "%s Input" msgstr "ନିବେଶ" #: spa/plugins/alsa/acp/alsa-util.c:1175 spa/plugins/alsa/acp/alsa-util.c:1269 #, fuzzy, c-format msgid "" "snd_pcm_avail() returned a value that is exceptionally large: %lu byte (%lu " "ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_avail() returned a value that is exceptionally large: %lu bytes (%lu " "ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "snd_pcm_avail() returned a value that is exceptionally large: %lu bytes (%lu " "ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[1] "" "snd_pcm_avail() returned a value that is exceptionally large: %lu bytes (%lu " "ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." #: spa/plugins/alsa/acp/alsa-util.c:1241 #, fuzzy, c-format msgid "" "snd_pcm_delay() returned a value that is exceptionally large: %li byte (%s" "%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_delay() returned a value that is exceptionally large: %li bytes (%s" "%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "snd_pcm_delay() returned a value that is exceptionally large: %li bytes (%s" "%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[1] "" "snd_pcm_delay() returned a value that is exceptionally large: %li bytes (%s" "%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." #: spa/plugins/alsa/acp/alsa-util.c:1288 #, fuzzy, c-format msgid "" "snd_pcm_avail_delay() returned strange values: delay %lu is less than avail " "%lu.\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr "" "snd_pcm_avail() returned a value that is exceptionally large: %lu bytes (%lu " "ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." #: spa/plugins/alsa/acp/alsa-util.c:1331 #, fuzzy, c-format msgid "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu byte " "(%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu bytes " "(%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu bytes " "(%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[1] "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu bytes " "(%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." #: spa/plugins/bluez5/bluez5-device.c:1010 msgid "Audio Gateway (A2DP Source & HSP/HFP AG)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1033 #, c-format msgid "High Fidelity Playback (A2DP Sink, codec %s)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1035 #, c-format msgid "High Fidelity Duplex (A2DP Source/Sink, codec %s)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1041 msgid "High Fidelity Playback (A2DP Sink)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1043 msgid "High Fidelity Duplex (A2DP Source/Sink)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1070 #, c-format msgid "Headset Head Unit (HSP/HFP, codec %s)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1074 msgid "Headset Head Unit (HSP/HFP)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1140 msgid "Handsfree" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1155 #, fuzzy msgid "Headphone" msgstr "ଏନାଲୋଗ ହେଡ଼ଫୋନଗୁଡ଼ିକ" #: spa/plugins/bluez5/bluez5-device.c:1160 msgid "Portable" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1165 msgid "Car" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1170 msgid "HiFi" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1175 msgid "Phone" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1181 msgid "Bluetooth" msgstr "" pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/po/pa.po000066400000000000000000000466461511204443500220740ustar00rootroot00000000000000# translation of pipewire.master-tx.pa.po to Punjabi # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # # Amanpreet Singh Alam , 2008. # A S Alam , 2009. # Jaswinder Singh , 2009, 2012. # msgid "" msgstr "" "Project-Id-Version: pipewire.master-tx.pa\n" "Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/pipewire/" "issues/new\n" "POT-Creation-Date: 2021-04-18 16:54+0800\n" "PO-Revision-Date: 2012-01-30 09:55+0000\n" "Last-Translator: Jaswinder Singh \n" "Language-Team: Punjabi/Panjabi \n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Generator: Lokalize 1.0\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" #: src/daemon/pipewire.c:43 #, c-format msgid "" "%s [options]\n" " -h, --help Show this help\n" " --version Show version\n" " -c, --config Load config (Default %s)\n" msgstr "" #: src/daemon/pipewire.desktop.in:4 msgid "PipeWire Media System" msgstr "" #: src/daemon/pipewire.desktop.in:5 msgid "Start the PipeWire Media System" msgstr "" #: src/examples/media-session/alsa-monitor.c:526 #: spa/plugins/alsa/acp/compat.c:187 msgid "Built-in Audio" msgstr "ਅੰਦਰੂਨੀ ਆਡੀਓ" #: src/examples/media-session/alsa-monitor.c:530 #: spa/plugins/alsa/acp/compat.c:192 msgid "Modem" msgstr "ਮਾਡਮ" #: src/examples/media-session/alsa-monitor.c:539 msgid "Unknown device" msgstr "" #: src/tools/pw-cat.c:991 #, c-format msgid "" "%s [options] \n" " -h, --help Show this help\n" " --version Show version\n" " -v, --verbose Enable verbose operations\n" "\n" msgstr "" #: src/tools/pw-cat.c:998 #, c-format msgid "" " -R, --remote Remote daemon name\n" " --media-type Set media type (default %s)\n" " --media-category Set media category (default %s)\n" " --media-role Set media role (default %s)\n" " --target Set node target (default %s)\n" " 0 means don't link\n" " --latency Set node latency (default %s)\n" " Xunit (unit = s, ms, us, ns)\n" " or direct samples (256)\n" " the rate is the one of the source " "file\n" " --list-targets List available targets for --target\n" "\n" msgstr "" #: src/tools/pw-cat.c:1016 #, c-format msgid "" " --rate Sample rate (req. for rec) (default " "%u)\n" " --channels Number of channels (req. for rec) " "(default %u)\n" " --channel-map Channel map\n" " one of: \"stereo\", " "\"surround-51\",... or\n" " comma separated list of channel " "names: eg. \"FL,FR\"\n" " --format Sample format %s (req. for rec) " "(default %s)\n" " --volume Stream volume 0-1.0 (default %.3f)\n" " -q --quality Resampler quality (0 - 15) (default " "%d)\n" "\n" msgstr "" #: src/tools/pw-cat.c:1033 msgid "" " -p, --playback Playback mode\n" " -r, --record Recording mode\n" " -m, --midi Midi mode\n" "\n" msgstr "" #: src/tools/pw-cli.c:2932 #, c-format msgid "" "%s [options] [command]\n" " -h, --help Show this help\n" " --version Show version\n" " -d, --daemon Start as daemon (Default false)\n" " -r, --remote Remote daemon name\n" "\n" msgstr "" #: spa/plugins/alsa/acp/acp.c:290 msgid "Pro Audio" msgstr "" #: spa/plugins/alsa/acp/acp.c:411 spa/plugins/alsa/acp/alsa-mixer.c:4704 #: spa/plugins/bluez5/bluez5-device.c:1000 msgid "Off" msgstr "ਬੰਦ" #: spa/plugins/alsa/acp/channelmap.h:466 msgid "(invalid)" msgstr "(ਅਢੁੱਕਵਾਂ)" #: spa/plugins/alsa/acp/alsa-mixer.c:2709 msgid "Input" msgstr "ਇੰਪੁੱਟ" #: spa/plugins/alsa/acp/alsa-mixer.c:2710 msgid "Docking Station Input" msgstr "ਡੌਕਿੰਗ ਸਟੇਸ਼ਨ ਇੰਪੁੱਟ" #: spa/plugins/alsa/acp/alsa-mixer.c:2711 #, fuzzy msgid "Docking Station Microphone" msgstr "ਡੌਕਿੰਗ ਸਟੇਸ਼ਨ ਮਾਈਕਰੋਫੋਨ" #: spa/plugins/alsa/acp/alsa-mixer.c:2712 #, fuzzy msgid "Docking Station Line In" msgstr "ਡੌਕਿੰਗ ਸਟੇਸ਼ਨ ਇੰਪੁੱਟ" #: spa/plugins/alsa/acp/alsa-mixer.c:2713 #: spa/plugins/alsa/acp/alsa-mixer.c:2804 msgid "Line In" msgstr "ਲਾਈਨ-ਇਨ" #: spa/plugins/alsa/acp/alsa-mixer.c:2714 #: spa/plugins/alsa/acp/alsa-mixer.c:2798 #: spa/plugins/bluez5/bluez5-device.c:1145 msgid "Microphone" msgstr "ਮਾਈਕਰੋਫੋਨ" #: spa/plugins/alsa/acp/alsa-mixer.c:2715 #: spa/plugins/alsa/acp/alsa-mixer.c:2799 #, fuzzy msgid "Front Microphone" msgstr "ਡੌਕਿੰਗ ਸਟੇਸ਼ਨ ਮਾਈਕਰੋਫੋਨ" #: spa/plugins/alsa/acp/alsa-mixer.c:2716 #: spa/plugins/alsa/acp/alsa-mixer.c:2800 #, fuzzy msgid "Rear Microphone" msgstr "ਮਾਈਕਰੋਫੋਨ" #: spa/plugins/alsa/acp/alsa-mixer.c:2717 msgid "External Microphone" msgstr "ਬਾਹਰੀ ਮਾਈਕਰੋਫੋਨ" #: spa/plugins/alsa/acp/alsa-mixer.c:2718 #: spa/plugins/alsa/acp/alsa-mixer.c:2802 msgid "Internal Microphone" msgstr "ਅੰਦਰੂਨੀ ਮਾਈਕਰੋਫੋਨ" #: spa/plugins/alsa/acp/alsa-mixer.c:2719 #: spa/plugins/alsa/acp/alsa-mixer.c:2805 msgid "Radio" msgstr "ਰੇਡੀਓ" #: spa/plugins/alsa/acp/alsa-mixer.c:2720 #: spa/plugins/alsa/acp/alsa-mixer.c:2806 msgid "Video" msgstr "ਵੀਡੀਓ" #: spa/plugins/alsa/acp/alsa-mixer.c:2721 msgid "Automatic Gain Control" msgstr "ਆਟੋਮੈਟਿਕ ਗੇਨ ਕੰਟਰੋਲ" #: spa/plugins/alsa/acp/alsa-mixer.c:2722 msgid "No Automatic Gain Control" msgstr "ਕੋਈ ਆਟੋਮੈਟਿਕ ਗੇਨ ਕੰਟਰੋਲ ਨਹੀਂ" #: spa/plugins/alsa/acp/alsa-mixer.c:2723 msgid "Boost" msgstr "ਬੂਸਟ" #: spa/plugins/alsa/acp/alsa-mixer.c:2724 msgid "No Boost" msgstr "ਕੋਈ ਬੂਸਟ ਨਹੀਂ" #: spa/plugins/alsa/acp/alsa-mixer.c:2725 msgid "Amplifier" msgstr "ਐਂਪਲੀਫਾਇਰ" #: spa/plugins/alsa/acp/alsa-mixer.c:2726 msgid "No Amplifier" msgstr "ਕੋਈ ਐਂਪਲੀਫਾਇਰ ਨਹੀਂ" #: spa/plugins/alsa/acp/alsa-mixer.c:2727 #, fuzzy msgid "Bass Boost" msgstr "ਬੂਸਟ" #: spa/plugins/alsa/acp/alsa-mixer.c:2728 #, fuzzy msgid "No Bass Boost" msgstr "ਕੋਈ ਬੂਸਟ ਨਹੀਂ" #: spa/plugins/alsa/acp/alsa-mixer.c:2729 #: spa/plugins/bluez5/bluez5-device.c:1150 msgid "Speaker" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2730 #: spa/plugins/alsa/acp/alsa-mixer.c:2808 msgid "Headphones" msgstr "ਐਨਾਲਾਗ ਹੈੱਡਫੋਨ" #: spa/plugins/alsa/acp/alsa-mixer.c:2797 msgid "Analog Input" msgstr "ਐਨਾਲਾਗ ਇੰਪੁੱਟ" #: spa/plugins/alsa/acp/alsa-mixer.c:2801 msgid "Dock Microphone" msgstr "ਡੌਕਿੰਗ ਸਟੇਸ਼ਨ ਮਾਈਕਰੋਫੋਨ" #: spa/plugins/alsa/acp/alsa-mixer.c:2803 #, fuzzy msgid "Headset Microphone" msgstr "ਮਾਈਕਰੋਫੋਨ" #: spa/plugins/alsa/acp/alsa-mixer.c:2807 msgid "Analog Output" msgstr "ਐਨਾਲਾਗ ਆਉਟਪੁੱਟ" #: spa/plugins/alsa/acp/alsa-mixer.c:2809 #, fuzzy msgid "Headphones 2" msgstr "ਐਨਾਲਾਗ ਹੈੱਡਫੋਨ" #: spa/plugins/alsa/acp/alsa-mixer.c:2810 #, fuzzy msgid "Headphones Mono Output" msgstr "ਐਨਾਲਾਗ ਮੋਨੋ ਆਊਟਪੁੱਟ" #: spa/plugins/alsa/acp/alsa-mixer.c:2811 #, fuzzy msgid "Line Out" msgstr "ਲਾਈਨ-ਇਨ" #: spa/plugins/alsa/acp/alsa-mixer.c:2812 msgid "Analog Mono Output" msgstr "ਐਨਾਲਾਗ ਮੋਨੋ ਆਊਟਪੁੱਟ" #: spa/plugins/alsa/acp/alsa-mixer.c:2813 #, fuzzy msgid "Speakers" msgstr "ਐਨਾਲਾਗ ਸਟੀਰੀਓ" #: spa/plugins/alsa/acp/alsa-mixer.c:2814 msgid "HDMI / DisplayPort" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2815 #, fuzzy msgid "Digital Output (S/PDIF)" msgstr "ਡਿਜ਼ੀਟਲ ਸਟੀਰੀਓ (HDMI)" #: spa/plugins/alsa/acp/alsa-mixer.c:2816 #, fuzzy msgid "Digital Input (S/PDIF)" msgstr "ਡਿਜ਼ੀਟਲ ਸਟੀਰੀਓ (HDMI)" #: spa/plugins/alsa/acp/alsa-mixer.c:2817 msgid "Multichannel Input" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2818 #, fuzzy msgid "Multichannel Output" msgstr "ਜ਼ੀਰੋ (Null) ਆਉਟਪੁੱਟ" #: spa/plugins/alsa/acp/alsa-mixer.c:2819 #, fuzzy msgid "Game Output" msgstr "ਜ਼ੀਰੋ (Null) ਆਉਟਪੁੱਟ" #: spa/plugins/alsa/acp/alsa-mixer.c:2820 #: spa/plugins/alsa/acp/alsa-mixer.c:2821 #, fuzzy msgid "Chat Output" msgstr "ਜ਼ੀਰੋ (Null) ਆਉਟਪੁੱਟ" #: spa/plugins/alsa/acp/alsa-mixer.c:2822 #, fuzzy msgid "Chat Input" msgstr "ਇੰਪੁੱਟ" #: spa/plugins/alsa/acp/alsa-mixer.c:2823 #, fuzzy msgid "Virtual Surround 7.1" msgstr "ਐਨਾਲਾਗ ਸਰਾਊਂਡ 7.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4527 msgid "Analog Mono" msgstr "ਐਨਾਲਾਗ ਮੋਨੋ" #: spa/plugins/alsa/acp/alsa-mixer.c:4528 #, fuzzy msgid "Analog Mono (Left)" msgstr "ਐਨਾਲਾਗ ਮੋਨੋ" #: spa/plugins/alsa/acp/alsa-mixer.c:4529 #, fuzzy msgid "Analog Mono (Right)" msgstr "ਐਨਾਲਾਗ ਮੋਨੋ" #. Note: Not translated to "Analog Stereo Input", because the source #. * name gets "Input" appended to it automatically, so adding "Input" #. * here would lead to the source name to become "Analog Stereo Input #. * Input". The same logic applies to analog-stereo-output, #. * multichannel-input and multichannel-output. #: spa/plugins/alsa/acp/alsa-mixer.c:4530 #: spa/plugins/alsa/acp/alsa-mixer.c:4538 #: spa/plugins/alsa/acp/alsa-mixer.c:4539 msgid "Analog Stereo" msgstr "ਐਨਾਲਾਗ ਸਟੀਰੀਓ" #: spa/plugins/alsa/acp/alsa-mixer.c:4531 msgid "Mono" msgstr "ਮੋਨੋ" #: spa/plugins/alsa/acp/alsa-mixer.c:4532 msgid "Stereo" msgstr "ਸਟੀਰੀਓ" #: spa/plugins/alsa/acp/alsa-mixer.c:4540 #: spa/plugins/alsa/acp/alsa-mixer.c:4698 #: spa/plugins/bluez5/bluez5-device.c:1135 msgid "Headset" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4541 #: spa/plugins/alsa/acp/alsa-mixer.c:4699 #, fuzzy msgid "Speakerphone" msgstr "ਐਨਾਲਾਗ ਸਟੀਰੀਓ" #: spa/plugins/alsa/acp/alsa-mixer.c:4542 #: spa/plugins/alsa/acp/alsa-mixer.c:4543 msgid "Multichannel" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4544 msgid "Analog Surround 2.1" msgstr "ਐਨਾਲਾਗ ਸਰਾਊਂਡ 2.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4545 msgid "Analog Surround 3.0" msgstr "ਐਨਾਲਾਗ ਸਰਾਊਂਡ 3.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4546 msgid "Analog Surround 3.1" msgstr "ਐਨਾਲਾਗ ਸਰਾਊਂਡ 3.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4547 msgid "Analog Surround 4.0" msgstr "ਐਨਾਲਾਗ ਸਰਾਊਂਡ 4.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4548 msgid "Analog Surround 4.1" msgstr "ਐਨਾਲਾਗ ਸਰਾਊਂਡ 4.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4549 msgid "Analog Surround 5.0" msgstr "ਐਨਾਲਾਗ ਸਰਾਊਂਡ 5.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4550 msgid "Analog Surround 5.1" msgstr "ਐਨਾਲਾਗ ਸਰਾਊਂਡ 5.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4551 msgid "Analog Surround 6.0" msgstr "ਐਨਾਲਾਗ ਸਰਾਊਂਡ 6.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4552 msgid "Analog Surround 6.1" msgstr "ਐਨਾਲਾਗ ਸਰਾਊਂਡ 6.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4553 msgid "Analog Surround 7.0" msgstr "ਐਨਾਲਾਗ ਸਰਾਊਂਡ 7.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4554 msgid "Analog Surround 7.1" msgstr "ਐਨਾਲਾਗ ਸਰਾਊਂਡ 7.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4555 msgid "Digital Stereo (IEC958)" msgstr "ਡਿਜ਼ੀਟਲ ਸਟੀਰੀਓ (IEC958)" #: spa/plugins/alsa/acp/alsa-mixer.c:4556 msgid "Digital Surround 4.0 (IEC958/AC3)" msgstr "ਡਿਜ਼ੀਟਲ ਸਰਾਊਂਡ 4.0 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4557 msgid "Digital Surround 5.1 (IEC958/AC3)" msgstr "ਡਿਜ਼ੀਟਲ ਸਰਾਊਂਡ 5.1 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4558 #, fuzzy msgid "Digital Surround 5.1 (IEC958/DTS)" msgstr "ਡਿਜ਼ੀਟਲ ਸਰਾਊਂਡ 5.1 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4559 msgid "Digital Stereo (HDMI)" msgstr "ਡਿਜ਼ੀਟਲ ਸਟੀਰੀਓ (HDMI)" #: spa/plugins/alsa/acp/alsa-mixer.c:4560 #, fuzzy msgid "Digital Surround 5.1 (HDMI)" msgstr "ਡਿਜ਼ੀਟਲ ਸਰਾਊਂਡ 5.1 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4561 msgid "Chat" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4562 msgid "Game" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4696 msgid "Analog Mono Duplex" msgstr "ਐਨਾਲਾਗ ਮੋਨੋ ਡੁਪਲੈਕਸ" #: spa/plugins/alsa/acp/alsa-mixer.c:4697 msgid "Analog Stereo Duplex" msgstr "ਐਨਾਲਾਗ ਸਟੀਰੀਓ ਡੁਪਲੈਕਸ" #: spa/plugins/alsa/acp/alsa-mixer.c:4700 msgid "Digital Stereo Duplex (IEC958)" msgstr "ਡਿਜ਼ੀਟਲ ਸਟੀਰੀਓ ਡੁਪਲੈਕਸ (IEC958)" #: spa/plugins/alsa/acp/alsa-mixer.c:4701 msgid "Multichannel Duplex" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4702 #, fuzzy msgid "Stereo Duplex" msgstr "ਐਨਾਲਾਗ ਸਟੀਰੀਓ ਡੁਪਲੈਕਸ" #: spa/plugins/alsa/acp/alsa-mixer.c:4703 msgid "Mono Chat + 7.1 Surround" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4806 #, fuzzy, c-format msgid "%s Output" msgstr "ਜ਼ੀਰੋ (Null) ਆਉਟਪੁੱਟ" #: spa/plugins/alsa/acp/alsa-mixer.c:4813 #, fuzzy, c-format msgid "%s Input" msgstr "ਇੰਪੁੱਟ" #: spa/plugins/alsa/acp/alsa-util.c:1175 spa/plugins/alsa/acp/alsa-util.c:1269 #, fuzzy, c-format msgid "" "snd_pcm_avail() returned a value that is exceptionally large: %lu byte (%lu " "ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_avail() returned a value that is exceptionally large: %lu bytes (%lu " "ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "snd_pcm_avail() ਤੋਂ ਇੱਕ ਮੁੱਲ ਮਿਲਿਆ ਹੈ, ਜੋ ਬਹੁਤ ਵੱਡਾ ਹੈ: %lu ਬਾਈਟ (%lu ms)।\n" "ਇਹ ALSA ਡਰਾਈਵਰ '%s' ਵਿਚਲਾ ਬੱਗ ਲੱਗਦਾ ਹੈ। ਇਸ ਮੁੱਦੇ ਦੀ ALSA ਡਿਵੈਲਪਰਾਂ ਨੂੰ ਰਿਪੋਰਟ ਦਿਓ ਜੀ।" msgstr[1] "" "snd_pcm_avail() ਤੋਂ ਇੱਕ ਮੁੱਲ ਮਿਲਿਆ ਹੈ, ਜੋ ਬਹੁਤ ਵੱਡਾ ਹੈ: %lu ਬਾਈਟ (%lu ms)।\n" "ਇਹ ALSA ਡਰਾਈਵਰ '%s' ਵਿਚਲਾ ਬੱਗ ਲੱਗਦਾ ਹੈ। ਇਸ ਮੁੱਦੇ ਦੀ ALSA ਡਿਵੈਲਪਰਾਂ ਨੂੰ ਰਿਪੋਰਟ ਦਿਓ ਜੀ।" #: spa/plugins/alsa/acp/alsa-util.c:1241 #, fuzzy, c-format msgid "" "snd_pcm_delay() returned a value that is exceptionally large: %li byte (%s" "%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_delay() returned a value that is exceptionally large: %li bytes (%s" "%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "snd_pcm_delay() ਤੋਂ ਇੱਕ ਮੁੱਲ ਮਿਲਿਆ ਹੈ, ਜੋ ਬਹੁਤ ਵੱਡਾ ਹੈ: %li ਬਾਈਟ (%s%lu ms)।\n" "ਇਹ ALSA ਡਰਾਈਵਰ '%s' ਵਿਚਲਾ ਬੱਗ ਲੱਗਦਾ ਹੈ। ਇਸ ਮੁੱਦੇ ਦੀ ALSA ਡਿਵੈਲਪਰਾਂ ਨੂੰ ਰਿਪੋਰਟ ਦਿਓ ਜੀ।" msgstr[1] "" "snd_pcm_delay() ਤੋਂ ਇੱਕ ਮੁੱਲ ਮਿਲਿਆ ਹੈ, ਜੋ ਬਹੁਤ ਵੱਡਾ ਹੈ: %li ਬਾਈਟ (%s%lu ms)।\n" "ਇਹ ALSA ਡਰਾਈਵਰ '%s' ਵਿਚਲਾ ਬੱਗ ਲੱਗਦਾ ਹੈ। ਇਸ ਮੁੱਦੇ ਦੀ ALSA ਡਿਵੈਲਪਰਾਂ ਨੂੰ ਰਿਪੋਰਟ ਦਿਓ ਜੀ।" #: spa/plugins/alsa/acp/alsa-util.c:1288 #, fuzzy, c-format msgid "" "snd_pcm_avail_delay() returned strange values: delay %lu is less than avail " "%lu.\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr "" "snd_pcm_avail() ਤੋਂ ਇੱਕ ਮੁੱਲ ਮਿਲਿਆ ਹੈ, ਜੋ ਬਹੁਤ ਵੱਡਾ ਹੈ: %lu ਬਾਈਟ (%lu ms)।\n" "ਇਹ ALSA ਡਰਾਈਵਰ '%s' ਵਿਚਲਾ ਬੱਗ ਲੱਗਦਾ ਹੈ। ਇਸ ਮੁੱਦੇ ਦੀ ALSA ਡਿਵੈਲਪਰਾਂ ਨੂੰ ਰਿਪੋਰਟ ਦਿਓ ਜੀ।" #: spa/plugins/alsa/acp/alsa-util.c:1331 #, fuzzy, c-format msgid "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu byte " "(%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu bytes " "(%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "snd_pcm_mmap_begin() ਤੋਂ ਇੱਕ ਮੁੱਲ ਮਿਲਿਆ ਹੈ, ਜੋ ਬਹੁਤ ਵੱਡਾ ਹੈ: %lu ਬਾਈਟ (%lu ms)।\n" "ਇਹ ALSA ਡਰਾਈਵਰ '%s' ਵਿਚਲਾ ਬੱਗ ਲੱਗਦਾ ਹੈ। ਇਸ ਮੁੱਦੇ ਦੀ ALSA ਡਿਵੈਲਪਰਾਂ ਨੂੰ ਰਿਪੋਰਟ ਦਿਓ ਜੀ।" msgstr[1] "" "snd_pcm_mmap_begin() ਤੋਂ ਇੱਕ ਮੁੱਲ ਮਿਲਿਆ ਹੈ, ਜੋ ਬਹੁਤ ਵੱਡਾ ਹੈ: %lu ਬਾਈਟ (%lu ms)।\n" "ਇਹ ALSA ਡਰਾਈਵਰ '%s' ਵਿਚਲਾ ਬੱਗ ਲੱਗਦਾ ਹੈ। ਇਸ ਮੁੱਦੇ ਦੀ ALSA ਡਿਵੈਲਪਰਾਂ ਨੂੰ ਰਿਪੋਰਟ ਦਿਓ ਜੀ।" #: spa/plugins/bluez5/bluez5-device.c:1010 msgid "Audio Gateway (A2DP Source & HSP/HFP AG)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1033 #, c-format msgid "High Fidelity Playback (A2DP Sink, codec %s)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1035 #, c-format msgid "High Fidelity Duplex (A2DP Source/Sink, codec %s)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1041 msgid "High Fidelity Playback (A2DP Sink)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1043 msgid "High Fidelity Duplex (A2DP Source/Sink)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1070 #, c-format msgid "Headset Head Unit (HSP/HFP, codec %s)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1074 msgid "Headset Head Unit (HSP/HFP)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1140 msgid "Handsfree" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1155 #, fuzzy msgid "Headphone" msgstr "ਐਨਾਲਾਗ ਹੈੱਡਫੋਨ" #: spa/plugins/bluez5/bluez5-device.c:1160 msgid "Portable" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1165 msgid "Car" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1170 msgid "HiFi" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1175 msgid "Phone" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1181 msgid "Bluetooth" msgstr "" pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/po/pipewire.pot000066400000000000000000000367361511204443500235030ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the pipewire package. # FIRST AUTHOR , YEAR. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: pipewire\n" "Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/pipewire/issues/new\n" "POT-Creation-Date: 2024-02-25 03:43+0300\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=CHARSET\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n" #: src/daemon/pipewire.c:26 #, c-format msgid "" "%s [options]\n" " -h, --help Show this help\n" " --version Show version\n" " -c, --config Load config (Default %s)\n" msgstr "" #: src/daemon/pipewire.desktop.in:4 msgid "PipeWire Media System" msgstr "" #: src/daemon/pipewire.desktop.in:5 msgid "Start the PipeWire Media System" msgstr "" #: src/modules/module-protocol-pulse/modules/module-tunnel-sink.c:159 #: src/modules/module-protocol-pulse/modules/module-tunnel-source.c:159 #, c-format msgid "Tunnel to %s%s%s" msgstr "" #: src/modules/module-fallback-sink.c:40 msgid "Dummy Output" msgstr "" #: src/modules/module-pulse-tunnel.c:774 #, c-format msgid "Tunnel for %s@%s" msgstr "" #: src/modules/module-zeroconf-discover.c:315 msgid "Unknown device" msgstr "" #: src/modules/module-zeroconf-discover.c:327 #, c-format msgid "%s on %s@%s" msgstr "" #: src/modules/module-zeroconf-discover.c:331 #, c-format msgid "%s on %s" msgstr "" #: src/tools/pw-cat.c:991 #, c-format msgid "" "%s [options] [|-]\n" " -h, --help Show this help\n" " --version Show version\n" " -v, --verbose Enable verbose operations\n" "\n" msgstr "" #: src/tools/pw-cat.c:998 #, c-format msgid "" " -R, --remote Remote daemon name\n" " --media-type Set media type (default %s)\n" " --media-category Set media category (default %s)\n" " --media-role Set media role (default %s)\n" " --target Set node target serial or name " "(default %s)\n" " 0 means don't link\n" " --latency Set node latency (default %s)\n" " Xunit (unit = s, ms, us, ns)\n" " or direct samples (256)\n" " the rate is the one of the source " "file\n" " -P --properties Set node properties\n" "\n" msgstr "" #: src/tools/pw-cat.c:1016 #, c-format msgid "" " --rate Sample rate (req. for rec) (default " "%u)\n" " --channels Number of channels (req. for rec) " "(default %u)\n" " --channel-map Channel map\n" " one of: \"stereo\", " "\"surround-51\",... or\n" " comma separated list of channel " "names: eg. \"FL,FR\"\n" " --format Sample format %s (req. for rec) " "(default %s)\n" " --volume Stream volume 0-1.0 (default %.3f)\n" " -q --quality Resampler quality (0 - 15) (default " "%d)\n" "\n" msgstr "" #: src/tools/pw-cat.c:1033 msgid "" " -p, --playback Playback mode\n" " -r, --record Recording mode\n" " -m, --midi Midi mode\n" " -d, --dsd DSD mode\n" " -o, --encoded Encoded mode\n" "\n" msgstr "" #: src/tools/pw-cli.c:2252 #, c-format msgid "" "%s [options] [command]\n" " -h, --help Show this help\n" " --version Show version\n" " -d, --daemon Start as daemon (Default false)\n" " -r, --remote Remote daemon name\n" " -m, --monitor Monitor activity\n" "\n" msgstr "" #: spa/plugins/alsa/acp/acp.c:327 msgid "Pro Audio" msgstr "" #: spa/plugins/alsa/acp/acp.c:488 spa/plugins/alsa/acp/alsa-mixer.c:4633 #: spa/plugins/bluez5/bluez5-device.c:1701 msgid "Off" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2652 msgid "Input" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2653 msgid "Docking Station Input" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2654 msgid "Docking Station Microphone" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2655 msgid "Docking Station Line In" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2656 #: spa/plugins/alsa/acp/alsa-mixer.c:2747 msgid "Line In" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2657 #: spa/plugins/alsa/acp/alsa-mixer.c:2741 #: spa/plugins/bluez5/bluez5-device.c:1989 msgid "Microphone" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2658 #: spa/plugins/alsa/acp/alsa-mixer.c:2742 msgid "Front Microphone" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2659 #: spa/plugins/alsa/acp/alsa-mixer.c:2743 msgid "Rear Microphone" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2660 msgid "External Microphone" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2661 #: spa/plugins/alsa/acp/alsa-mixer.c:2745 msgid "Internal Microphone" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2662 #: spa/plugins/alsa/acp/alsa-mixer.c:2748 msgid "Radio" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2663 #: spa/plugins/alsa/acp/alsa-mixer.c:2749 msgid "Video" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2664 msgid "Automatic Gain Control" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2665 msgid "No Automatic Gain Control" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2666 msgid "Boost" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2667 msgid "No Boost" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2668 msgid "Amplifier" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2669 msgid "No Amplifier" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2670 msgid "Bass Boost" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2671 msgid "No Bass Boost" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2672 #: spa/plugins/bluez5/bluez5-device.c:1995 msgid "Speaker" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2673 #: spa/plugins/alsa/acp/alsa-mixer.c:2751 msgid "Headphones" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2740 msgid "Analog Input" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2744 msgid "Dock Microphone" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2746 msgid "Headset Microphone" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2750 msgid "Analog Output" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2752 msgid "Headphones 2" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2753 msgid "Headphones Mono Output" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2754 msgid "Line Out" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2755 msgid "Analog Mono Output" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2756 msgid "Speakers" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2757 msgid "HDMI / DisplayPort" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2758 msgid "Digital Output (S/PDIF)" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2759 msgid "Digital Input (S/PDIF)" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2760 msgid "Multichannel Input" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2761 msgid "Multichannel Output" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2762 msgid "Game Output" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2763 #: spa/plugins/alsa/acp/alsa-mixer.c:2764 msgid "Chat Output" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2765 msgid "Chat Input" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2766 msgid "Virtual Surround 7.1" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4456 msgid "Analog Mono" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4457 msgid "Analog Mono (Left)" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4458 msgid "Analog Mono (Right)" msgstr "" #. Note: Not translated to "Analog Stereo Input", because the source #. * name gets "Input" appended to it automatically, so adding "Input" #. * here would lead to the source name to become "Analog Stereo Input #. * Input". The same logic applies to analog-stereo-output, #. * multichannel-input and multichannel-output. #: spa/plugins/alsa/acp/alsa-mixer.c:4459 #: spa/plugins/alsa/acp/alsa-mixer.c:4467 #: spa/plugins/alsa/acp/alsa-mixer.c:4468 msgid "Analog Stereo" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4460 msgid "Mono" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4461 msgid "Stereo" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4469 #: spa/plugins/alsa/acp/alsa-mixer.c:4627 #: spa/plugins/bluez5/bluez5-device.c:1977 msgid "Headset" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4470 #: spa/plugins/alsa/acp/alsa-mixer.c:4628 msgid "Speakerphone" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4471 #: spa/plugins/alsa/acp/alsa-mixer.c:4472 msgid "Multichannel" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4473 msgid "Analog Surround 2.1" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4474 msgid "Analog Surround 3.0" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4475 msgid "Analog Surround 3.1" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4476 msgid "Analog Surround 4.0" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4477 msgid "Analog Surround 4.1" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4478 msgid "Analog Surround 5.0" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4479 msgid "Analog Surround 5.1" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4480 msgid "Analog Surround 6.0" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4481 msgid "Analog Surround 6.1" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4482 msgid "Analog Surround 7.0" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4483 msgid "Analog Surround 7.1" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4484 msgid "Digital Stereo (IEC958)" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4485 msgid "Digital Surround 4.0 (IEC958/AC3)" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4486 msgid "Digital Surround 5.1 (IEC958/AC3)" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4487 msgid "Digital Surround 5.1 (IEC958/DTS)" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4488 msgid "Digital Stereo (HDMI)" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4489 msgid "Digital Surround 5.1 (HDMI)" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4490 msgid "Chat" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4491 msgid "Game" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4625 msgid "Analog Mono Duplex" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4626 msgid "Analog Stereo Duplex" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4629 msgid "Digital Stereo Duplex (IEC958)" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4630 msgid "Multichannel Duplex" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4631 msgid "Stereo Duplex" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4632 msgid "Mono Chat + 7.1 Surround" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4733 #, c-format msgid "%s Output" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4741 #, c-format msgid "%s Input" msgstr "" #: spa/plugins/alsa/acp/alsa-util.c:1220 spa/plugins/alsa/acp/alsa-util.c:1314 #, c-format msgid "" "snd_pcm_avail() returned a value that is exceptionally large: %lu byte (%lu " "ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_avail() returned a value that is exceptionally large: %lu bytes (%lu " "ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" msgstr[1] "" #: spa/plugins/alsa/acp/alsa-util.c:1286 #, c-format msgid "" "snd_pcm_delay() returned a value that is exceptionally large: %li byte " "(%s%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_delay() returned a value that is exceptionally large: %li bytes " "(%s%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" msgstr[1] "" #: spa/plugins/alsa/acp/alsa-util.c:1333 #, c-format msgid "" "snd_pcm_avail_delay() returned strange values: delay %lu is less than avail " "%lu.\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr "" #: spa/plugins/alsa/acp/alsa-util.c:1376 #, c-format msgid "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu byte " "(%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu bytes " "(%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" msgstr[1] "" #: spa/plugins/alsa/acp/channelmap.h:457 msgid "(invalid)" msgstr "" #: spa/plugins/alsa/acp/compat.c:193 msgid "Built-in Audio" msgstr "" #: spa/plugins/alsa/acp/compat.c:198 msgid "Modem" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1712 msgid "Audio Gateway (A2DP Source & HSP/HFP AG)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1760 #, c-format msgid "High Fidelity Playback (A2DP Sink, codec %s)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1763 #, c-format msgid "High Fidelity Duplex (A2DP Source/Sink, codec %s)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1771 msgid "High Fidelity Playback (A2DP Sink)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1773 msgid "High Fidelity Duplex (A2DP Source/Sink)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1823 #, c-format msgid "High Fidelity Playback (BAP Sink, codec %s)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1828 #, c-format msgid "High Fidelity Input (BAP Source, codec %s)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1832 #, c-format msgid "High Fidelity Duplex (BAP Source/Sink, codec %s)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1841 msgid "High Fidelity Playback (BAP Sink)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1845 msgid "High Fidelity Input (BAP Source)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1848 msgid "High Fidelity Duplex (BAP Source/Sink)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1897 #, c-format msgid "Headset Head Unit (HSP/HFP, codec %s)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1978 #: spa/plugins/bluez5/bluez5-device.c:1983 #: spa/plugins/bluez5/bluez5-device.c:1990 #: spa/plugins/bluez5/bluez5-device.c:1996 #: spa/plugins/bluez5/bluez5-device.c:2002 #: spa/plugins/bluez5/bluez5-device.c:2008 #: spa/plugins/bluez5/bluez5-device.c:2014 #: spa/plugins/bluez5/bluez5-device.c:2020 #: spa/plugins/bluez5/bluez5-device.c:2026 msgid "Handsfree" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1984 msgid "Handsfree (HFP)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:2001 msgid "Headphone" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:2007 msgid "Portable" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:2013 msgid "Car" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:2019 msgid "HiFi" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:2025 msgid "Phone" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:2032 msgid "Bluetooth" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:2033 msgid "Bluetooth (HFP)" msgstr "" pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/po/pl.po000066400000000000000000000606641511204443500221030ustar00rootroot00000000000000# Polish translation for pipewire. # Copyright © 2008-2025 the pipewire authors. # This file is distributed under the same license as the pipewire package. # Piotr Drąg , 2008, 2012-2025. # msgid "" msgstr "" "Project-Id-Version: pipewire\n" "Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/pipewire/-/" "issues\n" "POT-Creation-Date: 2025-09-13 03:33+0000\n" "PO-Revision-Date: 2025-09-13 11:47+0200\n" "Last-Translator: Piotr Drąg \n" "Language-Team: Polish \n" "Language: pl\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 " "|| n%100>=20) ? 1 : 2;\n" #: src/daemon/pipewire.c:29 #, c-format msgid "" "%s [options]\n" " -h, --help Show this help\n" " -v, --verbose Increase verbosity by one level\n" " --version Show version\n" " -c, --config Load config (Default %s)\n" " -P --properties Set context properties\n" msgstr "" "%s [opcje]\n" " -h, --help Wyświetla tę pomoc\n" " -v, --verbose Zwiększa liczbę wyświetlanych\n" " komunikatów o jeden poziom\n" " --version Wyświetla wersję\n" " -c, --config Wczytuje konfigurację (domyślnie " "%s)\n" " -P --properties Ustawia właściwości kontekstu\n" #: src/daemon/pipewire.desktop.in:3 msgid "PipeWire Media System" msgstr "System multimediów PipeWire" #: src/daemon/pipewire.desktop.in:4 msgid "Start the PipeWire Media System" msgstr "Uruchomienie systemu multimediów PipeWire" #: src/modules/module-protocol-pulse/modules/module-tunnel-sink.c:159 #: src/modules/module-protocol-pulse/modules/module-tunnel-source.c:159 #, c-format msgid "Tunnel to %s%s%s" msgstr "Tunel do %s%s%s" #: src/modules/module-fallback-sink.c:40 msgid "Dummy Output" msgstr "Głuche wyjście" #: src/modules/module-pulse-tunnel.c:760 #, c-format msgid "Tunnel for %s@%s" msgstr "Tunel dla %s@%s" #: src/modules/module-zeroconf-discover.c:320 msgid "Unknown device" msgstr "Nieznane urządzenie" #: src/modules/module-zeroconf-discover.c:332 #, c-format msgid "%s on %s@%s" msgstr "%s na %s@%s" #: src/modules/module-zeroconf-discover.c:336 #, c-format msgid "%s on %s" msgstr "%s na %s" #: src/tools/pw-cat.c:1084 #, c-format msgid "" "%s [options] [|-]\n" " -h, --help Show this help\n" " --version Show version\n" " -v, --verbose Enable verbose operations\n" "\n" msgstr "" "%s [opcje] [|-]\n" " -h, --help Wyświetla tę pomoc\n" " --version Wyświetla wersję\n" " -v, --verbose Wyświetla więcej komunikatów\n" "\n" #: src/tools/pw-cat.c:1091 #, c-format msgid "" " -R, --remote Remote daemon name\n" " --media-type Set media type (default %s)\n" " --media-category Set media category (default %s)\n" " --media-role Set media role (default %s)\n" " --target Set node target serial or name " "(default %s)\n" " 0 means don't link\n" " --latency Set node latency (default %s)\n" " Xunit (unit = s, ms, us, ns)\n" " or direct samples (256)\n" " the rate is the one of the source " "file\n" " -P --properties Set node properties\n" "\n" msgstr "" " -R, --remote Nazwa zdalnej usługi\n" " --media-type Ustawia typ multimediów (domyślnie " "%s)\n" " --media-category Ustawia kategorię multimediów " "(domyślnie %s)\n" " --media-role Ustawia rolę multimediów (domyślnie " "%s)\n" " --target Ustawia docelowy numer seryjny\n" " lub nazwę węzła (domyślnie %s)\n" " 0 oznacza brak wiązania\n" " --latency Ustawia opóźnienie węzła (domyślnie " "%s)\n" " Xjednostka (jednostka = s, ms, us, " "ns)\n" " lub bezpośrednie próbki (256)\n" " częstotliwość jest z pliku " "źródłowego\n" " -P --properties Ustawia właściwości węzła\n" "\n" #: src/tools/pw-cat.c:1109 #, c-format msgid "" " --rate Sample rate (req. for rec) (default " "%u)\n" " --channels Number of channels (req. for rec) " "(default %u)\n" " --channel-map Channel map\n" " one of: \"stereo\", " "\"surround-51\",... or\n" " comma separated list of channel " "names: eg. \"FL,FR\"\n" " --format Sample format %s (req. for rec) " "(default %s)\n" " --volume Stream volume 0-1.0 (default %.3f)\n" " -q --quality Resampler quality (0 - 15) (default " "%d)\n" " -a, --raw RAW mode\n" " -M, --force-midi Force midi format, one of \"midi\" " "or \"ump\", (default ump)\n" " -n, --sample-count COUNT Stop after COUNT samples\n" "\n" msgstr "" " --rate Częstotliwość próbki (wymagane do " "nagrywania) (domyślnie %u)\n" " --channels Liczba kanałów (wymagane do " "nagrywania) (domyślnie %u)\n" " --channel-map Mapa kanałów\n" " jedna z: „stereo”, " "„surround-51”… lub\n" " lista nazw kanałów rozdzielonych " "przecinkami: np. „FL,FR”\n" " --format Format próbki %s (wymagane do " "nagrywania) (domyślnie %s)\n" " --volume Głośność potoku w zakresie 0-1,0 " "(domyślnie %.3f)\n" " -q --quality Jakość resamplera od 0 do 15 " "(domyślnie %d)\n" " -a, --raw Tryb RAW\n" " -M, --force-midi Wymusza format MIDI, można użyć " "„midi”\n" " albo „ump” (domyślnie ump)\n" " -n, --sample-count LICZBA Zatrzymuje po LICZBIE próbek\n" "\n" #: src/tools/pw-cat.c:1129 msgid "" " -p, --playback Playback mode\n" " -r, --record Recording mode\n" " -m, --midi Midi mode\n" " -d, --dsd DSD mode\n" " -o, --encoded Encoded mode\n" " -s, --sysex SysEx mode\n" " -c, --midi-clip MIDI clip mode\n" "\n" msgstr "" " -p, --playback Tryb odtwarzania\n" " -r, --record Tryb nagrywania\n" " -m, --midi Tryb MIDI\n" " -d, --dsd Tryb DSD\n" " -o, --encoded Tryb zakodowany\n" " -s, --sysex Tryb SysEx\n" " -c, --midi-clip Tryb klipu MIDI\n" "\n" #: src/tools/pw-cli.c:2386 #, c-format msgid "" "%s [options] [command]\n" " -h, --help Show this help\n" " --version Show version\n" " -d, --daemon Start as daemon (Default false)\n" " -r, --remote Remote daemon name\n" " -m, --monitor Monitor activity\n" "\n" msgstr "" "%s [opcje] [polecenie]\n" " -h, --help Wyświetla tę pomoc\n" " --version Wyświetla wersję\n" " -d, --daemon Uruchamia jako usługę (domyślnie " "tego nie robi)\n" " -r, --remote Nazwa zdalnej usługi\n" " -m, --monitor Monitoruje aktywność\n" "\n" #: spa/plugins/alsa/acp/acp.c:351 msgid "Pro Audio" msgstr "Dźwięk w zastosowaniach profesjonalnych" #: spa/plugins/alsa/acp/acp.c:527 spa/plugins/alsa/acp/alsa-mixer.c:4635 #: spa/plugins/bluez5/bluez5-device.c:1974 msgid "Off" msgstr "Wyłączone" #: spa/plugins/alsa/acp/acp.c:610 #, c-format msgid "%s [ALSA UCM error]" msgstr "%s [błąd UCM biblioteki ALSA]" #: spa/plugins/alsa/acp/alsa-mixer.c:2652 msgid "Input" msgstr "Wejście" #: spa/plugins/alsa/acp/alsa-mixer.c:2653 msgid "Docking Station Input" msgstr "Wejście stacji dokującej" #: spa/plugins/alsa/acp/alsa-mixer.c:2654 msgid "Docking Station Microphone" msgstr "Mikrofon stacji dokującej" #: spa/plugins/alsa/acp/alsa-mixer.c:2655 msgid "Docking Station Line In" msgstr "Wejście liniowe stacji dokującej" #: spa/plugins/alsa/acp/alsa-mixer.c:2656 #: spa/plugins/alsa/acp/alsa-mixer.c:2747 msgid "Line In" msgstr "Wejście liniowe" #: spa/plugins/alsa/acp/alsa-mixer.c:2657 #: spa/plugins/alsa/acp/alsa-mixer.c:2741 #: spa/plugins/bluez5/bluez5-device.c:2372 msgid "Microphone" msgstr "Mikrofon" #: spa/plugins/alsa/acp/alsa-mixer.c:2658 #: spa/plugins/alsa/acp/alsa-mixer.c:2742 msgid "Front Microphone" msgstr "Przedni mikrofon" #: spa/plugins/alsa/acp/alsa-mixer.c:2659 #: spa/plugins/alsa/acp/alsa-mixer.c:2743 msgid "Rear Microphone" msgstr "Tylny mikrofon" #: spa/plugins/alsa/acp/alsa-mixer.c:2660 msgid "External Microphone" msgstr "Zewnętrzny mikrofon" #: spa/plugins/alsa/acp/alsa-mixer.c:2661 #: spa/plugins/alsa/acp/alsa-mixer.c:2745 msgid "Internal Microphone" msgstr "Wewnętrzny mikrofon" #: spa/plugins/alsa/acp/alsa-mixer.c:2662 #: spa/plugins/alsa/acp/alsa-mixer.c:2748 msgid "Radio" msgstr "Radio" #: spa/plugins/alsa/acp/alsa-mixer.c:2663 #: spa/plugins/alsa/acp/alsa-mixer.c:2749 msgid "Video" msgstr "Wideo" #: spa/plugins/alsa/acp/alsa-mixer.c:2664 msgid "Automatic Gain Control" msgstr "Automatyczne sterowanie natężeniem" #: spa/plugins/alsa/acp/alsa-mixer.c:2665 msgid "No Automatic Gain Control" msgstr "Brak automatycznego sterowania natężeniem" #: spa/plugins/alsa/acp/alsa-mixer.c:2666 msgid "Boost" msgstr "Podbicie" #: spa/plugins/alsa/acp/alsa-mixer.c:2667 msgid "No Boost" msgstr "Brak podbicia" #: spa/plugins/alsa/acp/alsa-mixer.c:2668 msgid "Amplifier" msgstr "Amplituner" #: spa/plugins/alsa/acp/alsa-mixer.c:2669 msgid "No Amplifier" msgstr "Brak amplitunera" #: spa/plugins/alsa/acp/alsa-mixer.c:2670 msgid "Bass Boost" msgstr "Podbicie basów" #: spa/plugins/alsa/acp/alsa-mixer.c:2671 msgid "No Bass Boost" msgstr "Brak podbicia basów" #: spa/plugins/alsa/acp/alsa-mixer.c:2672 #: spa/plugins/bluez5/bluez5-device.c:2378 msgid "Speaker" msgstr "Głośnik" #. Don't call it "headset", the HF one has the mic #: spa/plugins/alsa/acp/alsa-mixer.c:2673 #: spa/plugins/alsa/acp/alsa-mixer.c:2751 #: spa/plugins/bluez5/bluez5-device.c:2384 #: spa/plugins/bluez5/bluez5-device.c:2451 msgid "Headphones" msgstr "Słuchawki" #: spa/plugins/alsa/acp/alsa-mixer.c:2740 msgid "Analog Input" msgstr "Wejście analogowe" #: spa/plugins/alsa/acp/alsa-mixer.c:2744 msgid "Dock Microphone" msgstr "Mikrofon stacji dokującej" #: spa/plugins/alsa/acp/alsa-mixer.c:2746 msgid "Headset Microphone" msgstr "Mikrofon na słuchawkach" #: spa/plugins/alsa/acp/alsa-mixer.c:2750 msgid "Analog Output" msgstr "Wyjście analogowe" #: spa/plugins/alsa/acp/alsa-mixer.c:2752 msgid "Headphones 2" msgstr "Słuchawki 2" #: spa/plugins/alsa/acp/alsa-mixer.c:2753 msgid "Headphones Mono Output" msgstr "Wyjście mono słuchawek" #: spa/plugins/alsa/acp/alsa-mixer.c:2754 msgid "Line Out" msgstr "Wyjście liniowe" #: spa/plugins/alsa/acp/alsa-mixer.c:2755 msgid "Analog Mono Output" msgstr "Analogowe wyjście mono" #: spa/plugins/alsa/acp/alsa-mixer.c:2756 msgid "Speakers" msgstr "Głośniki" #: spa/plugins/alsa/acp/alsa-mixer.c:2757 msgid "HDMI / DisplayPort" msgstr "HDMI/DisplayPort" #: spa/plugins/alsa/acp/alsa-mixer.c:2758 msgid "Digital Output (S/PDIF)" msgstr "Wyjście cyfrowe (S/PDIF)" #: spa/plugins/alsa/acp/alsa-mixer.c:2759 msgid "Digital Input (S/PDIF)" msgstr "Wejście cyfrowe (S/PDIF)" #: spa/plugins/alsa/acp/alsa-mixer.c:2760 msgid "Multichannel Input" msgstr "Wejście wielokanałowe" #: spa/plugins/alsa/acp/alsa-mixer.c:2761 msgid "Multichannel Output" msgstr "Wyjście wielokanałowe" #: spa/plugins/alsa/acp/alsa-mixer.c:2762 msgid "Game Output" msgstr "Wyjście gry" #: spa/plugins/alsa/acp/alsa-mixer.c:2763 #: spa/plugins/alsa/acp/alsa-mixer.c:2764 msgid "Chat Output" msgstr "Wyjście rozmowy" #: spa/plugins/alsa/acp/alsa-mixer.c:2765 msgid "Chat Input" msgstr "Wejście rozmowy" #: spa/plugins/alsa/acp/alsa-mixer.c:2766 msgid "Virtual Surround 7.1" msgstr "Wirtualne przestrzenne 7.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4458 msgid "Analog Mono" msgstr "Analogowe mono" #: spa/plugins/alsa/acp/alsa-mixer.c:4459 msgid "Analog Mono (Left)" msgstr "Analogowe mono (lewy)" #: spa/plugins/alsa/acp/alsa-mixer.c:4460 msgid "Analog Mono (Right)" msgstr "Analogowe mono (prawy)" #. Note: Not translated to "Analog Stereo Input", because the source #. * name gets "Input" appended to it automatically, so adding "Input" #. * here would lead to the source name to become "Analog Stereo Input #. * Input". The same logic applies to analog-stereo-output, #. * multichannel-input and multichannel-output. #: spa/plugins/alsa/acp/alsa-mixer.c:4461 #: spa/plugins/alsa/acp/alsa-mixer.c:4469 #: spa/plugins/alsa/acp/alsa-mixer.c:4470 msgid "Analog Stereo" msgstr "Analogowe stereo" #: spa/plugins/alsa/acp/alsa-mixer.c:4462 msgid "Mono" msgstr "Mono" #: spa/plugins/alsa/acp/alsa-mixer.c:4463 msgid "Stereo" msgstr "Stereo" #: spa/plugins/alsa/acp/alsa-mixer.c:4471 #: spa/plugins/alsa/acp/alsa-mixer.c:4629 #: spa/plugins/bluez5/bluez5-device.c:2360 msgid "Headset" msgstr "Słuchawki z mikrofonem" #: spa/plugins/alsa/acp/alsa-mixer.c:4472 #: spa/plugins/alsa/acp/alsa-mixer.c:4630 msgid "Speakerphone" msgstr "Telefon głośnomówiący" #: spa/plugins/alsa/acp/alsa-mixer.c:4473 #: spa/plugins/alsa/acp/alsa-mixer.c:4474 msgid "Multichannel" msgstr "Wielokanałowe" #: spa/plugins/alsa/acp/alsa-mixer.c:4475 msgid "Analog Surround 2.1" msgstr "Analogowe przestrzenne 2.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4476 msgid "Analog Surround 3.0" msgstr "Analogowe przestrzenne 3.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4477 msgid "Analog Surround 3.1" msgstr "Analogowe przestrzenne 3.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4478 msgid "Analog Surround 4.0" msgstr "Analogowe przestrzenne 4.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4479 msgid "Analog Surround 4.1" msgstr "Analogowe przestrzenne 4.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4480 msgid "Analog Surround 5.0" msgstr "Analogowe przestrzenne 5.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4481 msgid "Analog Surround 5.1" msgstr "Analogowe przestrzenne 5.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4482 msgid "Analog Surround 6.0" msgstr "Analogowe przestrzenne 6.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4483 msgid "Analog Surround 6.1" msgstr "Analogowe przestrzenne 6.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4484 msgid "Analog Surround 7.0" msgstr "Analogowe przestrzenne 7.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4485 msgid "Analog Surround 7.1" msgstr "Analogowe przestrzenne 7.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4486 msgid "Digital Stereo (IEC958)" msgstr "Cyfrowe stereo (IEC958)" #: spa/plugins/alsa/acp/alsa-mixer.c:4487 msgid "Digital Surround 4.0 (IEC958/AC3)" msgstr "Cyfrowe przestrzenne 4.0 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4488 msgid "Digital Surround 5.1 (IEC958/AC3)" msgstr "Cyfrowe przestrzenne 5.1 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4489 msgid "Digital Surround 5.1 (IEC958/DTS)" msgstr "Cyfrowe przestrzenne 5.1 (IEC958/DTS)" #: spa/plugins/alsa/acp/alsa-mixer.c:4490 msgid "Digital Stereo (HDMI)" msgstr "Cyfrowe stereo (HDMI)" #: spa/plugins/alsa/acp/alsa-mixer.c:4491 msgid "Digital Surround 5.1 (HDMI)" msgstr "Cyfrowe przestrzenne 5.1 (HDMI)" #: spa/plugins/alsa/acp/alsa-mixer.c:4492 msgid "Chat" msgstr "Rozmowa" #: spa/plugins/alsa/acp/alsa-mixer.c:4493 msgid "Game" msgstr "Gra" #: spa/plugins/alsa/acp/alsa-mixer.c:4627 msgid "Analog Mono Duplex" msgstr "Analogowy dupleks mono" #: spa/plugins/alsa/acp/alsa-mixer.c:4628 msgid "Analog Stereo Duplex" msgstr "Analogowy dupleks stereo" #: spa/plugins/alsa/acp/alsa-mixer.c:4631 msgid "Digital Stereo Duplex (IEC958)" msgstr "Cyfrowy dupleks stereo (IEC958)" #: spa/plugins/alsa/acp/alsa-mixer.c:4632 msgid "Multichannel Duplex" msgstr "Dupleks wielokanałowy" #: spa/plugins/alsa/acp/alsa-mixer.c:4633 msgid "Stereo Duplex" msgstr "Dupleks stereo" #: spa/plugins/alsa/acp/alsa-mixer.c:4634 msgid "Mono Chat + 7.1 Surround" msgstr "Rozmowa mono + przestrzenne 7.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4735 #, c-format msgid "%s Output" msgstr "Wyjście %s" #: spa/plugins/alsa/acp/alsa-mixer.c:4743 #, c-format msgid "%s Input" msgstr "Wejście %s" #: spa/plugins/alsa/acp/alsa-util.c:1233 spa/plugins/alsa/acp/alsa-util.c:1327 #, c-format msgid "" "snd_pcm_avail() returned a value that is exceptionally large: %lu byte (%lu " "ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_avail() returned a value that is exceptionally large: %lu bytes (%lu " "ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "snd_pcm_avail() zwróciło wyjątkowo dużą wartość: %lu bajt (%lu ms).\n" "Prawdopodobnie jest to błąd sterownika ALSA „%s”. Proszę zgłosić ten problem " "programistom usługi ALSA." msgstr[1] "" "snd_pcm_avail() zwróciło wyjątkowo dużą wartość: %lu bajty (%lu ms).\n" "Prawdopodobnie jest to błąd sterownika ALSA „%s”. Proszę zgłosić ten problem " "programistom usługi ALSA." msgstr[2] "" "snd_pcm_avail() zwróciło wyjątkowo dużą wartość: %lu bajtów (%lu ms).\n" "Prawdopodobnie jest to błąd sterownika ALSA „%s”. Proszę zgłosić ten problem " "programistom usługi ALSA." #: spa/plugins/alsa/acp/alsa-util.c:1299 #, c-format msgid "" "snd_pcm_delay() returned a value that is exceptionally large: %li byte " "(%s%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_delay() returned a value that is exceptionally large: %li bytes " "(%s%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "snd_pcm_delay() zwróciło wyjątkowo dużą wartość: %li bajt (%s%lu ms).\n" "Prawdopodobnie jest to błąd sterownika ALSA „%s”. Proszę zgłosić ten problem " "programistom usługi ALSA." msgstr[1] "" "snd_pcm_delay() zwróciło wyjątkowo dużą wartość: %li bajty (%s%lu ms).\n" "Prawdopodobnie jest to błąd sterownika ALSA „%s”. Proszę zgłosić ten problem " "programistom usługi ALSA." msgstr[2] "" "snd_pcm_delay() zwróciło wyjątkowo dużą wartość: %li bajtów (%s%lu ms).\n" "Prawdopodobnie jest to błąd sterownika ALSA „%s”. Proszę zgłosić ten problem " "programistom usługi ALSA." #: spa/plugins/alsa/acp/alsa-util.c:1346 #, c-format msgid "" "snd_pcm_avail_delay() returned strange values: delay %lu is less than avail " "%lu.\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr "" "snd_pcm_avail_delay() zwróciło dziwne wartości: opóźnienie %lu jest mniejsze " "niż korzyść %lu.\n" "Prawdopodobnie jest to błąd sterownika ALSA „%s”. Proszę zgłosić ten problem " "programistom usługi ALSA." #: spa/plugins/alsa/acp/alsa-util.c:1389 #, c-format msgid "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu byte " "(%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu bytes " "(%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "snd_pcm_mmap_begin() zwróciło wyjątkowo dużą wartość: %lu bajt (%lu ms).\n" "Prawdopodobnie jest to błąd sterownika ALSA „%s”. Proszę zgłosić ten problem " "programistom usługi ALSA." msgstr[1] "" "snd_pcm_mmap_begin() zwróciło wyjątkowo dużą wartość: %lu bajty (%lu ms).\n" "Prawdopodobnie jest to błąd sterownika ALSA „%s”. Proszę zgłosić ten problem " "programistom usługi ALSA." msgstr[2] "" "snd_pcm_mmap_begin() zwróciło wyjątkowo dużą wartość: %lu bajtów (%lu ms).\n" "Prawdopodobnie jest to błąd sterownika ALSA „%s”. Proszę zgłosić ten problem " "programistom usługi ALSA." #: spa/plugins/alsa/acp/channelmap.h:457 msgid "(invalid)" msgstr "(nieprawidłowe)" #: spa/plugins/alsa/acp/compat.c:194 msgid "Built-in Audio" msgstr "Wbudowany dźwięk" #: spa/plugins/alsa/acp/compat.c:199 msgid "Modem" msgstr "Modem" #: spa/plugins/bluez5/bluez5-device.c:1985 msgid "Audio Gateway (A2DP Source & HSP/HFP AG)" msgstr "Bramka dźwięku (źródło A2DP i AG HSP/HFP)" #: spa/plugins/bluez5/bluez5-device.c:2014 msgid "Audio Streaming for Hearing Aids (ASHA Sink)" msgstr "Przesyłanie dźwięku do aparatów słuchowych (odpływ ASHA)" #: spa/plugins/bluez5/bluez5-device.c:2057 #, c-format msgid "High Fidelity Playback (A2DP Sink, codec %s)" msgstr "Odtwarzanie o wysokiej dokładności (odpływ A2DP, kodek %s)" #: spa/plugins/bluez5/bluez5-device.c:2060 #, c-format msgid "High Fidelity Duplex (A2DP Source/Sink, codec %s)" msgstr "Dupleks o wysokiej dokładności (źródło/odpływ A2DP, kodek %s)" #: spa/plugins/bluez5/bluez5-device.c:2068 msgid "High Fidelity Playback (A2DP Sink)" msgstr "Odtwarzanie o wysokiej dokładności (odpływ A2DP)" #: spa/plugins/bluez5/bluez5-device.c:2070 msgid "High Fidelity Duplex (A2DP Source/Sink)" msgstr "Dupleks o wysokiej dokładności (źródło/odpływ A2DP)" #: spa/plugins/bluez5/bluez5-device.c:2144 #, c-format msgid "High Fidelity Playback (BAP Sink, codec %s)" msgstr "Odtwarzanie o wysokiej dokładności (odpływ BAP, kodek %s)" #: spa/plugins/bluez5/bluez5-device.c:2149 #, c-format msgid "High Fidelity Input (BAP Source, codec %s)" msgstr "Wejście o wysokiej dokładności (źródło BAP, kodek %s)" #: spa/plugins/bluez5/bluez5-device.c:2153 #, c-format msgid "High Fidelity Duplex (BAP Source/Sink, codec %s)" msgstr "Dupleks o wysokiej dokładności (źródło/odpływ BAP, kodek %s)" #: spa/plugins/bluez5/bluez5-device.c:2162 msgid "High Fidelity Playback (BAP Sink)" msgstr "Odtwarzanie o wysokiej dokładności (odpływ BAP)" #: spa/plugins/bluez5/bluez5-device.c:2166 msgid "High Fidelity Input (BAP Source)" msgstr "Wejście o wysokiej dokładności (źródło BAP)" #: spa/plugins/bluez5/bluez5-device.c:2169 msgid "High Fidelity Duplex (BAP Source/Sink)" msgstr "Dupleks o wysokiej dokładności (źródło/odpływ BAP)" #: spa/plugins/bluez5/bluez5-device.c:2209 #, c-format msgid "Headset Head Unit (HSP/HFP, codec %s)" msgstr "Jednostka główna słuchawek z mikrofonem (HSP/HFP, kodek %s)" #: spa/plugins/bluez5/bluez5-device.c:2361 #: spa/plugins/bluez5/bluez5-device.c:2366 #: spa/plugins/bluez5/bluez5-device.c:2373 #: spa/plugins/bluez5/bluez5-device.c:2379 #: spa/plugins/bluez5/bluez5-device.c:2385 #: spa/plugins/bluez5/bluez5-device.c:2391 #: spa/plugins/bluez5/bluez5-device.c:2397 #: spa/plugins/bluez5/bluez5-device.c:2403 #: spa/plugins/bluez5/bluez5-device.c:2409 msgid "Handsfree" msgstr "Zestaw głośnomówiący" #: spa/plugins/bluez5/bluez5-device.c:2367 msgid "Handsfree (HFP)" msgstr "Zestaw głośnomówiący (HFP)" #: spa/plugins/bluez5/bluez5-device.c:2390 msgid "Portable" msgstr "Przenośne" #: spa/plugins/bluez5/bluez5-device.c:2396 msgid "Car" msgstr "Samochód" #: spa/plugins/bluez5/bluez5-device.c:2402 msgid "HiFi" msgstr "HiFi" #: spa/plugins/bluez5/bluez5-device.c:2408 msgid "Phone" msgstr "Telefon" #: spa/plugins/bluez5/bluez5-device.c:2415 msgid "Bluetooth" msgstr "Bluetooth" #: spa/plugins/bluez5/bluez5-device.c:2416 msgid "Bluetooth (HFP)" msgstr "Bluetooth (HFP)" pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/po/pt.po000066400000000000000000000507241511204443500221070ustar00rootroot00000000000000# Portuguese translation for pipewire. # Copyright © 2008-2022 the pipewire authors. # This file is distributed under the same license as the pipewire package. # Rui Gouveia , 2012. # Rui Gouveia , 2012, 2015. # Pedro Albuquerque , 2015. # Juliano de Souza Camargo , 2020. # Hugo Carvalho , 2021 2022. # msgid "" msgstr "" "Project-Id-Version: pipewire\n" "Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/pipewire/" "issues/new\n" "POT-Creation-Date: 2021-11-17 15:06+0100\n" "PO-Revision-Date: 2022-03-30 19:37+0100\n" "Last-Translator: Hugo Carvalho \n" "Language-Team: Portuguese \n" "Language: pt\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Generator: Poedit 3.0.1\n" #: src/daemon/pipewire.c:45 #, c-format msgid "" "%s [options]\n" " -h, --help Show this help\n" " --version Show version\n" " -c, --config Load config (Default %s)\n" msgstr "" "%s [options]\n" " -h, --help Mostra esta ajuda\n" " --version Mostra a versão\n" " -c, --config Carrega uma configuração (Padrão " "%s)\n" #: src/modules/module-protocol-pulse/modules/module-tunnel-sink.c:185 #: src/modules/module-protocol-pulse/modules/module-tunnel-source.c:185 #, c-format msgid "Tunnel to %s/%s" msgstr "Túnel para %s/%s" #: src/modules/module-pulse-tunnel.c:536 #, c-format msgid "Tunnel for %s@%s" msgstr "Túnel para %s@%s" #: src/modules/module-zeroconf-discover.c:332 msgid "Unknown device" msgstr "Dispositivo desconhecido" #: src/modules/module-zeroconf-discover.c:344 #, c-format msgid "%s on %s@%s" msgstr "%s em %s@%s" #: src/modules/module-zeroconf-discover.c:348 #, c-format msgid "%s on %s" msgstr "%s em %s" #: src/tools/pw-cat.c:1058 #, c-format msgid "" "%s [options] \n" " -h, --help Show this help\n" " --version Show version\n" " -v, --verbose Enable verbose operations\n" "\n" msgstr "" "%s [options] \n" " -h, --help Mostra esta ajuda\n" " --version Mostra a versão\n" " -v, --verbose Ativa operações descritivas\n" "\n" #: src/tools/pw-cat.c:1065 #, c-format msgid "" " -R, --remote Remote daemon name\n" " --media-type Set media type (default %s)\n" " --media-category Set media category (default %s)\n" " --media-role Set media role (default %s)\n" " --target Set node target (default %s)\n" " 0 means don't link\n" " --latency Set node latency (default %s)\n" " Xunit (unit = s, ms, us, ns)\n" " or direct samples (256)\n" " the rate is the one of the source " "file\n" " --list-targets List available targets for --target\n" "\n" msgstr "" " -R, --remote Nome do daemon remoto\n" " --media-type Define o tipo de média (padrão: %s)\n" " --media-category Define a categoria de média (padrão: " "%s)\n" " --media-role Define o papel de média (padrão: " "%s)\n" " --target Define o alvo do nó (padrão: %s)\n" " 0 significa não vincular\n" " --latency Define a latência do nó (padrão: " "%s)\n" " Xunit (unidade = s, ms, us, ns)\n" " ou amostras diretas (256)\n" " a taxa é um dos ficheiros fontes\n" " --list-targets Lista alvos disponíveis para --" "target\n" "\n" #: src/tools/pw-cat.c:1083 #, c-format msgid "" " --rate Sample rate (req. for rec) (default " "%u)\n" " --channels Number of channels (req. for rec) " "(default %u)\n" " --channel-map Channel map\n" " one of: \"stereo\", " "\"surround-51\",... or\n" " comma separated list of channel " "names: eg. \"FL,FR\"\n" " --format Sample format %s (req. for rec) " "(default %s)\n" " --volume Stream volume 0-1.0 (default %.3f)\n" " -q --quality Resampler quality (0 - 15) (default " "%d)\n" "\n" msgstr "" " --rate Taxa de amostra (req. por rec) " "(padrão: %u)\n" " --channels Número de canais (req. por rec) " "(padrão: %u)\n" " --channel-map Mapa de canal\n" " um de : “stereo”, " "“surround-51”,... ou\n" " lista separada por vírgulas de " "nomes de\n" " canal: e.x.: “FL,FR”\n" " --format Formato da amostra %s (req. por rec) " "(padrão: %s)\n" " --volume Volume do fluxo 0-1.0 (padrão: " "%.3f)\n" " -q --quality Qualidade da reamostra (0 - 15) " "(padrão: %d)\n" "\n" #: src/tools/pw-cat.c:1100 msgid "" " -p, --playback Playback mode\n" " -r, --record Recording mode\n" " -m, --midi Midi mode\n" " -d, --dsd DSD mode\n" "\n" msgstr "" " -p, --playback Modo de reprodução\n" " -r, --record Modo de gravação\n" " -m, --midi Modo midi\n" " -d, --dsd Modo DSD\n" "\n" #: src/tools/pw-cli.c:3018 #, c-format msgid "" "%s [options] [command]\n" " -h, --help Show this help\n" " --version Show version\n" " -d, --daemon Start as daemon (Default false)\n" " -r, --remote Remote daemon name\n" "\n" msgstr "" "%s [options] [command]\n" " -h, --help Mostra esta ajuda\n" " --version Mostra a versão\n" " -d, --daemon Inicia como daemon (falso por " "padrão)\n" " -r, --remote Nome do daemon remoto\n" "\n" #: spa/plugins/alsa/acp/acp.c:321 msgid "Pro Audio" msgstr "Pró áudio" #: spa/plugins/alsa/acp/acp.c:444 spa/plugins/alsa/acp/alsa-mixer.c:4648 #: spa/plugins/bluez5/bluez5-device.c:1145 msgid "Off" msgstr "Desligado" #: spa/plugins/alsa/acp/alsa-mixer.c:2652 msgid "Input" msgstr "Entrada" #: spa/plugins/alsa/acp/alsa-mixer.c:2653 msgid "Docking Station Input" msgstr "Base de entrada da estação" #: spa/plugins/alsa/acp/alsa-mixer.c:2654 msgid "Docking Station Microphone" msgstr "Base de microfone da estação" #: spa/plugins/alsa/acp/alsa-mixer.c:2655 msgid "Docking Station Line In" msgstr "Base de linha de entrada da estação" #: spa/plugins/alsa/acp/alsa-mixer.c:2656 #: spa/plugins/alsa/acp/alsa-mixer.c:2747 msgid "Line In" msgstr "Linha de entrada" #: spa/plugins/alsa/acp/alsa-mixer.c:2657 #: spa/plugins/alsa/acp/alsa-mixer.c:2741 #: spa/plugins/bluez5/bluez5-device.c:1302 msgid "Microphone" msgstr "Microfone" #: spa/plugins/alsa/acp/alsa-mixer.c:2658 #: spa/plugins/alsa/acp/alsa-mixer.c:2742 msgid "Front Microphone" msgstr "Microfone frontal" #: spa/plugins/alsa/acp/alsa-mixer.c:2659 #: spa/plugins/alsa/acp/alsa-mixer.c:2743 msgid "Rear Microphone" msgstr "Microfone traseiro" #: spa/plugins/alsa/acp/alsa-mixer.c:2660 msgid "External Microphone" msgstr "Microfone externo" #: spa/plugins/alsa/acp/alsa-mixer.c:2661 #: spa/plugins/alsa/acp/alsa-mixer.c:2745 msgid "Internal Microphone" msgstr "Microfone interno" #: spa/plugins/alsa/acp/alsa-mixer.c:2662 #: spa/plugins/alsa/acp/alsa-mixer.c:2748 msgid "Radio" msgstr "Rádio" #: spa/plugins/alsa/acp/alsa-mixer.c:2663 #: spa/plugins/alsa/acp/alsa-mixer.c:2749 msgid "Video" msgstr "Vídeo" #: spa/plugins/alsa/acp/alsa-mixer.c:2664 msgid "Automatic Gain Control" msgstr "Controlo automático de aumento" #: spa/plugins/alsa/acp/alsa-mixer.c:2665 msgid "No Automatic Gain Control" msgstr "Sem controlo automático de aumento" #: spa/plugins/alsa/acp/alsa-mixer.c:2666 msgid "Boost" msgstr "Aumentar" #: spa/plugins/alsa/acp/alsa-mixer.c:2667 msgid "No Boost" msgstr "Não aumentar" #: spa/plugins/alsa/acp/alsa-mixer.c:2668 msgid "Amplifier" msgstr "Amplificador" #: spa/plugins/alsa/acp/alsa-mixer.c:2669 msgid "No Amplifier" msgstr "Sem amplificador" #: spa/plugins/alsa/acp/alsa-mixer.c:2670 msgid "Bass Boost" msgstr "Aumentar graves" #: spa/plugins/alsa/acp/alsa-mixer.c:2671 msgid "No Bass Boost" msgstr "Não aumentar graves" #: spa/plugins/alsa/acp/alsa-mixer.c:2672 #: spa/plugins/bluez5/bluez5-device.c:1307 msgid "Speaker" msgstr "Coluna" #: spa/plugins/alsa/acp/alsa-mixer.c:2673 #: spa/plugins/alsa/acp/alsa-mixer.c:2751 msgid "Headphones" msgstr "Auscultadores" #: spa/plugins/alsa/acp/alsa-mixer.c:2740 msgid "Analog Input" msgstr "Entrada analógica" #: spa/plugins/alsa/acp/alsa-mixer.c:2744 msgid "Dock Microphone" msgstr "Microfone de base" #: spa/plugins/alsa/acp/alsa-mixer.c:2746 msgid "Headset Microphone" msgstr "Microfone de auscultadores" #: spa/plugins/alsa/acp/alsa-mixer.c:2750 msgid "Analog Output" msgstr "Saída analógica" #: spa/plugins/alsa/acp/alsa-mixer.c:2752 msgid "Headphones 2" msgstr "Auscultadores 2" #: spa/plugins/alsa/acp/alsa-mixer.c:2753 msgid "Headphones Mono Output" msgstr "Saída analógica auscultadores" #: spa/plugins/alsa/acp/alsa-mixer.c:2754 msgid "Line Out" msgstr "Linha de saída" #: spa/plugins/alsa/acp/alsa-mixer.c:2755 msgid "Analog Mono Output" msgstr "Saída mono analógica" #: spa/plugins/alsa/acp/alsa-mixer.c:2756 msgid "Speakers" msgstr "Colunas" #: spa/plugins/alsa/acp/alsa-mixer.c:2757 msgid "HDMI / DisplayPort" msgstr "HDMI / DisplayPort" #: spa/plugins/alsa/acp/alsa-mixer.c:2758 msgid "Digital Output (S/PDIF)" msgstr "Saída digital (S/PDIF)" #: spa/plugins/alsa/acp/alsa-mixer.c:2759 msgid "Digital Input (S/PDIF)" msgstr "Entrada digital (S/PDIF)" #: spa/plugins/alsa/acp/alsa-mixer.c:2760 msgid "Multichannel Input" msgstr "Entrada multicanal" #: spa/plugins/alsa/acp/alsa-mixer.c:2761 msgid "Multichannel Output" msgstr "Saída multicanal" #: spa/plugins/alsa/acp/alsa-mixer.c:2762 msgid "Game Output" msgstr "Saída de jogo" #: spa/plugins/alsa/acp/alsa-mixer.c:2763 #: spa/plugins/alsa/acp/alsa-mixer.c:2764 msgid "Chat Output" msgstr "Saída de conversa" #: spa/plugins/alsa/acp/alsa-mixer.c:2765 msgid "Chat Input" msgstr "Entrada de conversa" #: spa/plugins/alsa/acp/alsa-mixer.c:2766 msgid "Virtual Surround 7.1" msgstr "Surround Virtual 7.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4471 msgid "Analog Mono" msgstr "Mono Analógico" #: spa/plugins/alsa/acp/alsa-mixer.c:4472 msgid "Analog Mono (Left)" msgstr "Mono Analógico (Esquerda)" #: spa/plugins/alsa/acp/alsa-mixer.c:4473 msgid "Analog Mono (Right)" msgstr "Mono Analógico (Direita)" #. Note: Not translated to "Analog Stereo Input", because the source #. * name gets "Input" appended to it automatically, so adding "Input" #. * here would lead to the source name to become "Analog Stereo Input #. * Input". The same logic applies to analog-stereo-output, #. * multichannel-input and multichannel-output. #: spa/plugins/alsa/acp/alsa-mixer.c:4474 #: spa/plugins/alsa/acp/alsa-mixer.c:4482 #: spa/plugins/alsa/acp/alsa-mixer.c:4483 msgid "Analog Stereo" msgstr "Estéreo Analógico" #: spa/plugins/alsa/acp/alsa-mixer.c:4475 msgid "Mono" msgstr "Mono" #: spa/plugins/alsa/acp/alsa-mixer.c:4476 msgid "Stereo" msgstr "Estéreo" #: spa/plugins/alsa/acp/alsa-mixer.c:4484 #: spa/plugins/alsa/acp/alsa-mixer.c:4642 #: spa/plugins/bluez5/bluez5-device.c:1292 msgid "Headset" msgstr "Auscultadores" #: spa/plugins/alsa/acp/alsa-mixer.c:4485 #: spa/plugins/alsa/acp/alsa-mixer.c:4643 msgid "Speakerphone" msgstr "Coluna" #: spa/plugins/alsa/acp/alsa-mixer.c:4486 #: spa/plugins/alsa/acp/alsa-mixer.c:4487 msgid "Multichannel" msgstr "Multicanal" #: spa/plugins/alsa/acp/alsa-mixer.c:4488 msgid "Analog Surround 2.1" msgstr "Surround 2.1 analógico" #: spa/plugins/alsa/acp/alsa-mixer.c:4489 msgid "Analog Surround 3.0" msgstr "Surround 3.0 analógico" #: spa/plugins/alsa/acp/alsa-mixer.c:4490 msgid "Analog Surround 3.1" msgstr "Surround 3.1 analógico" #: spa/plugins/alsa/acp/alsa-mixer.c:4491 msgid "Analog Surround 4.0" msgstr "Surround 4.0 analógico" #: spa/plugins/alsa/acp/alsa-mixer.c:4492 msgid "Analog Surround 4.1" msgstr "Surround 4.1 analógico" #: spa/plugins/alsa/acp/alsa-mixer.c:4493 msgid "Analog Surround 5.0" msgstr "Surround 5.0 analógico" #: spa/plugins/alsa/acp/alsa-mixer.c:4494 msgid "Analog Surround 5.1" msgstr "Surround 5.1 analógico" #: spa/plugins/alsa/acp/alsa-mixer.c:4495 msgid "Analog Surround 6.0" msgstr "Surround 6.0 analógico" #: spa/plugins/alsa/acp/alsa-mixer.c:4496 msgid "Analog Surround 6.1" msgstr "Surround 6.1 analógico" #: spa/plugins/alsa/acp/alsa-mixer.c:4497 msgid "Analog Surround 7.0" msgstr "Surround 7.0 analógico" #: spa/plugins/alsa/acp/alsa-mixer.c:4498 msgid "Analog Surround 7.1" msgstr "Surround 7.1 analógico" #: spa/plugins/alsa/acp/alsa-mixer.c:4499 msgid "Digital Stereo (IEC958)" msgstr "Estéreo Digital (IEC958)" #: spa/plugins/alsa/acp/alsa-mixer.c:4500 msgid "Digital Surround 4.0 (IEC958/AC3)" msgstr "Surround Digital 4.0 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4501 msgid "Digital Surround 5.1 (IEC958/AC3)" msgstr "Surround Digital 5.1 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4502 msgid "Digital Surround 5.1 (IEC958/DTS)" msgstr "Surround Digital 5.1 (IEC958/DTS)" #: spa/plugins/alsa/acp/alsa-mixer.c:4503 msgid "Digital Stereo (HDMI)" msgstr "Estéreo Digital (HDMI)" #: spa/plugins/alsa/acp/alsa-mixer.c:4504 msgid "Digital Surround 5.1 (HDMI)" msgstr "Surround 5.1 (IEC958/AC3) digital" #: spa/plugins/alsa/acp/alsa-mixer.c:4505 msgid "Chat" msgstr "Conversa" #: spa/plugins/alsa/acp/alsa-mixer.c:4506 msgid "Game" msgstr "Jogo" #: spa/plugins/alsa/acp/alsa-mixer.c:4640 msgid "Analog Mono Duplex" msgstr "Mono duplex analógico" #: spa/plugins/alsa/acp/alsa-mixer.c:4641 msgid "Analog Stereo Duplex" msgstr "Estéreo duplex analógico" #: spa/plugins/alsa/acp/alsa-mixer.c:4644 msgid "Digital Stereo Duplex (IEC958)" msgstr "Estéreo duplex digital (IEC958)" #: spa/plugins/alsa/acp/alsa-mixer.c:4645 msgid "Multichannel Duplex" msgstr "Duplex multicanal" #: spa/plugins/alsa/acp/alsa-mixer.c:4646 msgid "Stereo Duplex" msgstr "Duplex estéreo" #: spa/plugins/alsa/acp/alsa-mixer.c:4647 msgid "Mono Chat + 7.1 Surround" msgstr "Mono Chat + 7.1 Surround" #: spa/plugins/alsa/acp/alsa-mixer.c:4750 #, c-format msgid "%s Output" msgstr "Saída %s" #: spa/plugins/alsa/acp/alsa-mixer.c:4757 #, c-format msgid "%s Input" msgstr "Entrada %s" #: spa/plugins/alsa/acp/alsa-util.c:1173 spa/plugins/alsa/acp/alsa-util.c:1267 #, c-format msgid "" "snd_pcm_avail() returned a value that is exceptionally large: %lu byte (%lu " "ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_avail() returned a value that is exceptionally large: %lu bytes (%lu " "ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "snd_pcm_avail() retornou um valor excecionalmente elevado: %lu byte (%lu " "ms).\n" "Provavelmente isto é um erro no controlador ALSA '%s'. Por favor, reporte " "este problema aos programadores do ALSA." msgstr[1] "" "snd_pcm_avail() retornou um valor excecionalmente elevado: %lu bytes (%lu " "ms).\n" "Provavelmente isto é um erro no controlador ALSA '%s'. Por favor, reporte " "este problema aos programadores do ALSA." #: spa/plugins/alsa/acp/alsa-util.c:1239 #, c-format msgid "" "snd_pcm_delay() returned a value that is exceptionally large: %li byte (%s" "%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_delay() returned a value that is exceptionally large: %li bytes (%s" "%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "snd_pcm_delay() retornou um valor excecionalmente elevado: %li byte (%s%lu " "ms).\n" "Provavelmente isto é um erro no driver ALSA '%s'. Por favor, reporte este " "problema aos programadores do ALSA." msgstr[1] "" "snd_pcm_delay() retornou um valor excecionalmente elevado: %li bytes (%s%lu " "ms).\n" "Provavelmente isto é um erro no driver ALSA '%s'. Por favor, reporte este " "problema aos programadores do ALSA." #: spa/plugins/alsa/acp/alsa-util.c:1286 #, c-format msgid "" "snd_pcm_avail_delay() returned strange values: delay %lu is less than avail " "%lu.\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr "" "snd_pcm_avail_delay() retornou um valor excecionalmente elevado: %lu bytes " "(%lu ms).\n" "Provavelmente isto é um erro no controlador ALSA \"%s\". Por favor, reporte " "este problema aos programadores do ALSA." #: spa/plugins/alsa/acp/alsa-util.c:1329 #, c-format msgid "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu byte " "(%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu bytes " "(%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "snd_pcm_mmap_begin() retornou um valor excecionalmente elevado: %lu byte " "(%lu ms).\n" "Provavelmente isto é um erro no driver ALSA '%s'. Por favor, reporte este " "problema aos programadores do ALSA." msgstr[1] "" "snd_pcm_mmap_begin() retornou um valor excecionalmente elevado: %lu bytes " "(%lu ms).\n" "Provavelmente isto é um erro no driver ALSA '%s'. Por favor, reporte este " "problema aos programadores do ALSA." #: spa/plugins/alsa/acp/channelmap.h:464 msgid "(invalid)" msgstr "(inválido)" #: spa/plugins/alsa/acp/compat.c:189 msgid "Built-in Audio" msgstr "Áudio interno" #: spa/plugins/alsa/acp/compat.c:194 msgid "Modem" msgstr "Modem" #: spa/plugins/bluez5/bluez5-device.c:1155 msgid "Audio Gateway (A2DP Source & HSP/HFP AG)" msgstr "Porta de áudio (A2DP Fonte & HSP/HFP AG)" #: spa/plugins/bluez5/bluez5-device.c:1178 #, c-format msgid "High Fidelity Playback (A2DP Sink, codec %s)" msgstr "Reprodução de Alta Fidelidade (A2DP Sink, codec %s)" #: spa/plugins/bluez5/bluez5-device.c:1181 #, c-format msgid "High Fidelity Duplex (A2DP Source/Sink, codec %s)" msgstr "Duplex de Alta Fidelidade (A2DP Fonte/Sink, codec %s)" #: spa/plugins/bluez5/bluez5-device.c:1188 msgid "High Fidelity Playback (A2DP Sink)" msgstr "Reprodução de Alta Fidelidade (A2DP Sink)" #: spa/plugins/bluez5/bluez5-device.c:1190 msgid "High Fidelity Duplex (A2DP Source/Sink)" msgstr "Duplex de Alta Fidelidade (A2DP Fonte/Sink)" #: spa/plugins/bluez5/bluez5-device.c:1217 #, c-format msgid "Headset Head Unit (HSP/HFP, codec %s)" msgstr "Unidade de Auscultadores (HSP/HFP, codec %s)" #: spa/plugins/bluez5/bluez5-device.c:1221 msgid "Headset Head Unit (HSP/HFP)" msgstr "Unidade de Auscultadores (HSP/HFP)" #: spa/plugins/bluez5/bluez5-device.c:1297 msgid "Handsfree" msgstr "Mãos livres" #: spa/plugins/bluez5/bluez5-device.c:1312 msgid "Headphone" msgstr "Auscultadores" #: spa/plugins/bluez5/bluez5-device.c:1317 msgid "Portable" msgstr "Portátil" #: spa/plugins/bluez5/bluez5-device.c:1322 msgid "Car" msgstr "Carro" #: spa/plugins/bluez5/bluez5-device.c:1327 msgid "HiFi" msgstr "HiFi" #: spa/plugins/bluez5/bluez5-device.c:1332 msgid "Phone" msgstr "Telefone" #: spa/plugins/bluez5/bluez5-device.c:1338 msgid "Bluetooth" msgstr "Bluetooth" #~ msgid "PipeWire Media System" #~ msgstr "Sistema de Multimédia PipeWire" #~ msgid "Start the PipeWire Media System" #~ msgstr "Iniciar o Sistema de Multimédia PipeWire" pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/po/pt_BR.po000066400000000000000000000547171511204443500225000ustar00rootroot00000000000000# Brazilian Portuguese translation for pipewire # Copyright (C) 2022 Rafael Fontenelle # This file is distributed under the same license as the pipewire package. # Fabian Affolter , 2008. # Igor Pires Soares , 2009, 2012. # Rafael Fontenelle , 2013-2021. # Matheus Barbosa , 2022. # msgid "" msgstr "" "Project-Id-Version: pipewire\n" "Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/pipewire/-/" "issues\n" "POT-Creation-Date: 2022-09-30 03:27+0000\n" "PO-Revision-Date: 2022-01-25 19:49-0300\n" "Last-Translator: Matheus Barbosa \n" "Language-Team: Brazilian Portuguese \n" "Language: pt_BR\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n > 1)\n" "X-Generator: Gtranslator 40.0\n" #: src/daemon/pipewire.c:46 #, c-format msgid "" "%s [options]\n" " -h, --help Show this help\n" " --version Show version\n" " -c, --config Load config (Default %s)\n" msgstr "" "%s [opções]\n" " -h, --help Mostra esta ajuda\n" " --version Mostra a versão\n" " -c, --config Carrega uma configuração (Padrão: " "%s)\n" #: src/daemon/pipewire.desktop.in:4 msgid "PipeWire Media System" msgstr "Sistema de Mídia PipeWire" #: src/daemon/pipewire.desktop.in:5 msgid "Start the PipeWire Media System" msgstr "Inicia o Sistema de Mídia PipeWire" #: src/modules/module-protocol-pulse/modules/module-tunnel-sink.c:180 #: src/modules/module-protocol-pulse/modules/module-tunnel-source.c:180 #, c-format msgid "Tunnel to %s/%s" msgstr "Túnel para %s/%s" #: src/modules/module-fallback-sink.c:51 msgid "Dummy Output" msgstr "Saída de falsa" #: src/modules/module-pulse-tunnel.c:662 #, c-format msgid "Tunnel for %s@%s" msgstr "Túnel para %s@%s" #: src/modules/module-zeroconf-discover.c:332 msgid "Unknown device" msgstr "Dispositivo desconhecido" #: src/modules/module-zeroconf-discover.c:344 #, c-format msgid "%s on %s@%s" msgstr "%s em %s@%s" #: src/modules/module-zeroconf-discover.c:348 #, c-format msgid "%s on %s" msgstr "%s em %s" #: src/tools/pw-cat.c:784 #, c-format msgid "" "%s [options] [|-]\n" " -h, --help Show this help\n" " --version Show version\n" " -v, --verbose Enable verbose operations\n" "\n" msgstr "" "%s [opções] [|-]\n" " -h, --help Mostra esta ajuda\n" " --version Mostra a versão\n" " -v, --verbose Habilita operações verbosas\n" "\n" #: src/tools/pw-cat.c:791 #, c-format msgid "" " -R, --remote Remote daemon name\n" " --media-type Set media type (default %s)\n" " --media-category Set media category (default %s)\n" " --media-role Set media role (default %s)\n" " --target Set node target (default %s)\n" " 0 means don't link\n" " --latency Set node latency (default %s)\n" " Xunit (unit = s, ms, us, ns)\n" " or direct samples (256)\n" " the rate is the one of the source " "file\n" " -P --properties Set node properties\n" "\n" msgstr "" " -R, --remote Nome do daemon remoto\n" " --media-type Define o tipo de mídia (padrão: %s)\n" " --media-category Define a categoria de mídia (padrão: " "%s)\n" " --media-role Define o papel de mídia (padrão: " "%s)\n" " --target Define o alvo do nó (padrão: %s)\n" " 0 significa não vincular\n" " --latency Define a latência do nó (padrão: " "%s)\n" " Xunit (unidade = s, ms, us, ns)\n" " ou amostras diretas (256)\n" " a taxa é um dos arquivos fontes\n" " --properties Define as propriedades do nó\n" "\n" #: src/tools/pw-cat.c:809 #, c-format msgid "" " --rate Sample rate (req. for rec) (default " "%u)\n" " --channels Number of channels (req. for rec) " "(default %u)\n" " --channel-map Channel map\n" " one of: \"stereo\", " "\"surround-51\",... or\n" " comma separated list of channel " "names: eg. \"FL,FR\"\n" " --format Sample format %s (req. for rec) " "(default %s)\n" " --volume Stream volume 0-1.0 (default %.3f)\n" " -q --quality Resampler quality (0 - 15) (default " "%d)\n" "\n" msgstr "" " --rate Taxa de amostra (req. por rec) " "(padrão: %u)\n" " --channels Número de canais (req. por rec) " "(padrão: %u)\n" " --channel-map Mapa de canal\n" " um de : “stereo”, " "“surround-51”,... ou\n" " lista separada por vírgulas de " "nomes de\n" " canal: e.x.: “FL,FR”\n" " --format Formata da amostra %s (req. por rec) " "(padrão: %s)\n" " --volume Volume do fluxo 0-1.0 (padrão: " "%.3f)\n" " -q --quality Qualidade da reamostra (0 - 15) " "(padrão: %d)\n" "\n" #: src/tools/pw-cat.c:826 msgid "" " -p, --playback Playback mode\n" " -r, --record Recording mode\n" " -m, --midi Midi mode\n" " -d, --dsd DSD mode\n" "\n" msgstr "" " -p, --playback Modo de reprodução\n" " -r, --record Modo de gravação\n" " -m, --midi Modo Midi\n" " -d, --dsd Modo DSD\n" "\n" #: src/tools/pw-cli.c:2255 #, c-format msgid "" "%s [options] [command]\n" " -h, --help Show this help\n" " --version Show version\n" " -d, --daemon Start as daemon (Default false)\n" " -r, --remote Remote daemon name\n" "\n" msgstr "" "%s [opções] [comando]\n" " -h, --help Mostra esta ajuda\n" " --version Mostra a versão\n" " -d, --daemon Inicia como daemon (Padrão: false)\n" " -r, --remote Nome do daemon remoto\n" "\n" #: spa/plugins/alsa/acp/acp.c:321 msgid "Pro Audio" msgstr "Pro Audio" #: spa/plugins/alsa/acp/acp.c:444 spa/plugins/alsa/acp/alsa-mixer.c:4648 #: spa/plugins/bluez5/bluez5-device.c:1236 msgid "Off" msgstr "Desligado" #: spa/plugins/alsa/acp/alsa-mixer.c:2652 msgid "Input" msgstr "Entrada" #: spa/plugins/alsa/acp/alsa-mixer.c:2653 msgid "Docking Station Input" msgstr "Entrada da base de encaixe" #: spa/plugins/alsa/acp/alsa-mixer.c:2654 msgid "Docking Station Microphone" msgstr "Microfone de estação de base de encaixe" #: spa/plugins/alsa/acp/alsa-mixer.c:2655 msgid "Docking Station Line In" msgstr "Entrada de linha de estação de base de encaixe" #: spa/plugins/alsa/acp/alsa-mixer.c:2656 #: spa/plugins/alsa/acp/alsa-mixer.c:2747 msgid "Line In" msgstr "Entrada de linha" #: spa/plugins/alsa/acp/alsa-mixer.c:2657 #: spa/plugins/alsa/acp/alsa-mixer.c:2741 #: spa/plugins/bluez5/bluez5-device.c:1454 msgid "Microphone" msgstr "Microfone" #: spa/plugins/alsa/acp/alsa-mixer.c:2658 #: spa/plugins/alsa/acp/alsa-mixer.c:2742 msgid "Front Microphone" msgstr "Microfone frontal" #: spa/plugins/alsa/acp/alsa-mixer.c:2659 #: spa/plugins/alsa/acp/alsa-mixer.c:2743 msgid "Rear Microphone" msgstr "Microfone traseiro" #: spa/plugins/alsa/acp/alsa-mixer.c:2660 msgid "External Microphone" msgstr "Microfone externo" #: spa/plugins/alsa/acp/alsa-mixer.c:2661 #: spa/plugins/alsa/acp/alsa-mixer.c:2745 msgid "Internal Microphone" msgstr "Microfone interno" #: spa/plugins/alsa/acp/alsa-mixer.c:2662 #: spa/plugins/alsa/acp/alsa-mixer.c:2748 msgid "Radio" msgstr "Rádio" #: spa/plugins/alsa/acp/alsa-mixer.c:2663 #: spa/plugins/alsa/acp/alsa-mixer.c:2749 msgid "Video" msgstr "Vídeo" # https://pt.wikipedia.org/wiki/Controle_autom%C3%A1tico_de_ganho #: spa/plugins/alsa/acp/alsa-mixer.c:2664 msgid "Automatic Gain Control" msgstr "Controle automático de ganho" # https://pt.wikipedia.org/wiki/Controle_autom%C3%A1tico_de_ganho #: spa/plugins/alsa/acp/alsa-mixer.c:2665 msgid "No Automatic Gain Control" msgstr "Sem controle automático de ganho" # Este contexto de Boost é "reforço" no áudio, e não "impulso". #: spa/plugins/alsa/acp/alsa-mixer.c:2666 msgid "Boost" msgstr "Reforço" # Este contexto de Boost é "reforço" no áudio, e não "impulso". #: spa/plugins/alsa/acp/alsa-mixer.c:2667 msgid "No Boost" msgstr "Sem reforço" #: spa/plugins/alsa/acp/alsa-mixer.c:2668 msgid "Amplifier" msgstr "Amplificador" #: spa/plugins/alsa/acp/alsa-mixer.c:2669 msgid "No Amplifier" msgstr "Sem amplificador" # Este contexto de Boost é "reforço" no áudio, e não "impulso". #: spa/plugins/alsa/acp/alsa-mixer.c:2670 msgid "Bass Boost" msgstr "Reforço de graves" # Este contexto de Boost é "reforço" no áudio, e não "impulso". #: spa/plugins/alsa/acp/alsa-mixer.c:2671 msgid "No Bass Boost" msgstr "Sem reforço de graves" #: spa/plugins/alsa/acp/alsa-mixer.c:2672 #: spa/plugins/bluez5/bluez5-device.c:1460 msgid "Speaker" msgstr "Auto-falante" #: spa/plugins/alsa/acp/alsa-mixer.c:2673 #: spa/plugins/alsa/acp/alsa-mixer.c:2751 msgid "Headphones" msgstr "Fones de ouvido" #: spa/plugins/alsa/acp/alsa-mixer.c:2740 msgid "Analog Input" msgstr "Entrada analógica" #: spa/plugins/alsa/acp/alsa-mixer.c:2744 msgid "Dock Microphone" msgstr "Microfone de base de encaixe" #: spa/plugins/alsa/acp/alsa-mixer.c:2746 msgid "Headset Microphone" msgstr "Microfone de headset" #: spa/plugins/alsa/acp/alsa-mixer.c:2750 msgid "Analog Output" msgstr "Saída analógica" #: spa/plugins/alsa/acp/alsa-mixer.c:2752 msgid "Headphones 2" msgstr "Fones de ouvido 2" #: spa/plugins/alsa/acp/alsa-mixer.c:2753 msgid "Headphones Mono Output" msgstr "Saída analógica fones de ouvido" #: spa/plugins/alsa/acp/alsa-mixer.c:2754 msgid "Line Out" msgstr "Saída de linha" #: spa/plugins/alsa/acp/alsa-mixer.c:2755 msgid "Analog Mono Output" msgstr "Saída analógica monofônica" #: spa/plugins/alsa/acp/alsa-mixer.c:2756 msgid "Speakers" msgstr "Alto-falantes" #: spa/plugins/alsa/acp/alsa-mixer.c:2757 msgid "HDMI / DisplayPort" msgstr "HDMI / DisplayPort" #: spa/plugins/alsa/acp/alsa-mixer.c:2758 msgid "Digital Output (S/PDIF)" msgstr "Saída digital (S/PDIF)" #: spa/plugins/alsa/acp/alsa-mixer.c:2759 msgid "Digital Input (S/PDIF)" msgstr "Entrada digital (S/PDIF)" #: spa/plugins/alsa/acp/alsa-mixer.c:2760 msgid "Multichannel Input" msgstr "Entrada multicanal" #: spa/plugins/alsa/acp/alsa-mixer.c:2761 msgid "Multichannel Output" msgstr "Saída multicanal" #: spa/plugins/alsa/acp/alsa-mixer.c:2762 msgid "Game Output" msgstr "Saída de jogo" #: spa/plugins/alsa/acp/alsa-mixer.c:2763 #: spa/plugins/alsa/acp/alsa-mixer.c:2764 msgid "Chat Output" msgstr "Saída de bate-papo" #: spa/plugins/alsa/acp/alsa-mixer.c:2765 msgid "Chat Input" msgstr "Entrada de bate-papo" #: spa/plugins/alsa/acp/alsa-mixer.c:2766 msgid "Virtual Surround 7.1" msgstr "Surround virtual 7.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4471 msgid "Analog Mono" msgstr "Monofônico analógico" #: spa/plugins/alsa/acp/alsa-mixer.c:4472 msgid "Analog Mono (Left)" msgstr "Monofônico analógico (Esquerdo)" #: spa/plugins/alsa/acp/alsa-mixer.c:4473 msgid "Analog Mono (Right)" msgstr "Monofônico analógico (Direito)" #. Note: Not translated to "Analog Stereo Input", because the source #. * name gets "Input" appended to it automatically, so adding "Input" #. * here would lead to the source name to become "Analog Stereo Input #. * Input". The same logic applies to analog-stereo-output, #. * multichannel-input and multichannel-output. #: spa/plugins/alsa/acp/alsa-mixer.c:4474 #: spa/plugins/alsa/acp/alsa-mixer.c:4482 #: spa/plugins/alsa/acp/alsa-mixer.c:4483 msgid "Analog Stereo" msgstr "Estéreo analógico" #: spa/plugins/alsa/acp/alsa-mixer.c:4475 msgid "Mono" msgstr "Mono" #: spa/plugins/alsa/acp/alsa-mixer.c:4476 msgid "Stereo" msgstr "Estéreo" # Fone de ouvido não se encaixa como tradução aqui, pois há ou pode haver microfone junto. #: spa/plugins/alsa/acp/alsa-mixer.c:4484 #: spa/plugins/alsa/acp/alsa-mixer.c:4642 #: spa/plugins/bluez5/bluez5-device.c:1442 msgid "Headset" msgstr "Headset" #: spa/plugins/alsa/acp/alsa-mixer.c:4485 #: spa/plugins/alsa/acp/alsa-mixer.c:4643 msgid "Speakerphone" msgstr "Viva voz" #: spa/plugins/alsa/acp/alsa-mixer.c:4486 #: spa/plugins/alsa/acp/alsa-mixer.c:4487 msgid "Multichannel" msgstr "Multicanal" #: spa/plugins/alsa/acp/alsa-mixer.c:4488 msgid "Analog Surround 2.1" msgstr "Surround analógico 2.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4489 msgid "Analog Surround 3.0" msgstr "Surround analógico 3.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4490 msgid "Analog Surround 3.1" msgstr "Surround analógico 3.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4491 msgid "Analog Surround 4.0" msgstr "Surround analógico 4.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4492 msgid "Analog Surround 4.1" msgstr "Surround analógico 4.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4493 msgid "Analog Surround 5.0" msgstr "Surround analógico 5.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4494 msgid "Analog Surround 5.1" msgstr "Surround analógico 5.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4495 msgid "Analog Surround 6.0" msgstr "Surround analógico 6.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4496 msgid "Analog Surround 6.1" msgstr "Surround analógico 6.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4497 msgid "Analog Surround 7.0" msgstr "Surround analógico 7.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4498 msgid "Analog Surround 7.1" msgstr "Surround analógico 7.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4499 msgid "Digital Stereo (IEC958)" msgstr "Estéreo digital (IEC958)" #: spa/plugins/alsa/acp/alsa-mixer.c:4500 msgid "Digital Surround 4.0 (IEC958/AC3)" msgstr "Surround digital 4.0 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4501 msgid "Digital Surround 5.1 (IEC958/AC3)" msgstr "Surround digital 5.1 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4502 msgid "Digital Surround 5.1 (IEC958/DTS)" msgstr "Surround digital 5.1 (IEC958/DTS)" #: spa/plugins/alsa/acp/alsa-mixer.c:4503 msgid "Digital Stereo (HDMI)" msgstr "Estéreo digital (HDMI)" #: spa/plugins/alsa/acp/alsa-mixer.c:4504 msgid "Digital Surround 5.1 (HDMI)" msgstr "Surround digital 5.1 (HDMI)" #: spa/plugins/alsa/acp/alsa-mixer.c:4505 msgid "Chat" msgstr "Bate-papo" #: spa/plugins/alsa/acp/alsa-mixer.c:4506 msgid "Game" msgstr "Jogo" #: spa/plugins/alsa/acp/alsa-mixer.c:4640 msgid "Analog Mono Duplex" msgstr "Duplex monofônico analógico" #: spa/plugins/alsa/acp/alsa-mixer.c:4641 msgid "Analog Stereo Duplex" msgstr "Duplex estéreo analógico" #: spa/plugins/alsa/acp/alsa-mixer.c:4644 msgid "Digital Stereo Duplex (IEC958)" msgstr "Duplex estéreo digital (IEC958)" #: spa/plugins/alsa/acp/alsa-mixer.c:4645 msgid "Multichannel Duplex" msgstr "Duplex multicanal" #: spa/plugins/alsa/acp/alsa-mixer.c:4646 msgid "Stereo Duplex" msgstr "Duplex estéreo" #: spa/plugins/alsa/acp/alsa-mixer.c:4647 msgid "Mono Chat + 7.1 Surround" msgstr "Bate-papo monofônico + surround 7.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4754 #, c-format msgid "%s Output" msgstr "Saída %s" #: spa/plugins/alsa/acp/alsa-mixer.c:4761 #, c-format msgid "%s Input" msgstr "Entrada %s" #: spa/plugins/alsa/acp/alsa-util.c:1187 spa/plugins/alsa/acp/alsa-util.c:1281 #, c-format msgid "" "snd_pcm_avail() returned a value that is exceptionally large: %lu byte (%lu " "ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_avail() returned a value that is exceptionally large: %lu bytes (%lu " "ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "snd_pcm_avail() retornou um valor que é excepcionalmente grande: %lu byte " "(%lu ms).\n" "É mais provável que isso seja um erro no driver “%s” do ALSA. Por favor, " "relate esse problema aos desenvolvedores do ALSA." msgstr[1] "" "snd_pcm_avail() retornou um valor que é excepcionalmente grande: %lu bytes " "(%lu ms).\n" "É mais provável que isso seja um erro no driver “%s” do ALSA. Por favor, " "relate esse problema aos desenvolvedores do ALSA." #: spa/plugins/alsa/acp/alsa-util.c:1253 #, c-format msgid "" "snd_pcm_delay() returned a value that is exceptionally large: %li byte (%s" "%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_delay() returned a value that is exceptionally large: %li bytes (%s" "%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "snd_pcm_delay() retornou um valor que é excepcionalmente grande: %li byte (%s" "%lu ms).\n" "É mais provável que isso seja um erro no driver “%s” do ALSA. Por favor, " "relate esse problema aos desenvolvedores do ALSA." msgstr[1] "" "snd_pcm_delay() retornou um valor que é excepcionalmente grande: %li bytes " "(%s%lu ms).\n" "É mais provável que isso seja um erro no driver “%s” do ALSA. Por favor, " "relate esse problema aos desenvolvedores do ALSA." #: spa/plugins/alsa/acp/alsa-util.c:1300 #, c-format msgid "" "snd_pcm_avail_delay() returned strange values: delay %lu is less than avail " "%lu.\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr "" "snd_pcm_avail() retornou um valor estranho: o atraso de %lu é menor do que " "(%lu ms).\n" "É mais provável que isso seja um erro no driver “%s” do ALSA. Por favor, " "relate esse problema aos desenvolvedores do ALSA." #: spa/plugins/alsa/acp/alsa-util.c:1343 #, c-format msgid "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu byte " "(%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu bytes " "(%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "snd_pcm_mmap_begin() retornou um valor que é excepcionalmente grande: %lu " "byte (%lu ms).\n" "É mais provável que isso seja um erro no driver “%s” do ALSA. Por favor, " "relate esse problema aos desenvolvedores do ALSA." msgstr[1] "" "snd_pcm_mmap_begin() retornou um valor que é excepcionalmente grande: %lu " "bytes (%lu ms).\n" "É mais provável que isso seja um erro no driver “%s” do ALSA. Por favor, " "relate esse problema aos desenvolvedores do ALSA." #: spa/plugins/alsa/acp/channelmap.h:457 msgid "(invalid)" msgstr "(inválido)" #: spa/plugins/alsa/acp/compat.c:189 msgid "Built-in Audio" msgstr "Áudio interno" #: spa/plugins/alsa/acp/compat.c:194 msgid "Modem" msgstr "Modem" #: spa/plugins/bluez5/bluez5-device.c:1247 msgid "Audio Gateway (A2DP Source & HSP/HFP AG)" msgstr "Gateway de áudio (fonte A2DP & HSP/HFP AG)" #: spa/plugins/bluez5/bluez5-device.c:1272 #, c-format msgid "High Fidelity Playback (A2DP Sink, codec %s)" msgstr "Reprodução de alta-fidelidade (destino A2DP, codec %s)" #: spa/plugins/bluez5/bluez5-device.c:1275 #, c-format msgid "High Fidelity Duplex (A2DP Source/Sink, codec %s)" msgstr "Duplex de alta-fidelidade (fonte/destino A2DP, codec %s)" #: spa/plugins/bluez5/bluez5-device.c:1283 msgid "High Fidelity Playback (A2DP Sink)" msgstr "Reprodução de alta-fidelidade (destino A2DP)" #: spa/plugins/bluez5/bluez5-device.c:1285 msgid "High Fidelity Duplex (A2DP Source/Sink)" msgstr "Duplex de alta-fidelidade (fonte/destino A2DP)" #: spa/plugins/bluez5/bluez5-device.c:1322 #, c-format msgid "High Fidelity Playback (BAP Sink, codec %s)" msgstr "Reprodução de alta-fidelidade (destino BAP, codec %s)" #: spa/plugins/bluez5/bluez5-device.c:1326 #, c-format msgid "High Fidelity Input (BAP Source, codec %s)" msgstr "Entrada de alta-fidelidade (fonte BAP, codec %s)" #: spa/plugins/bluez5/bluez5-device.c:1330 #, c-format msgid "High Fidelity Duplex (BAP Source/Sink, codec %s)" msgstr "Duplex de alta-fidelidade (fonte/destino BAP, codec %s)" #: spa/plugins/bluez5/bluez5-device.c:1359 #, c-format msgid "Headset Head Unit (HSP/HFP, codec %s)" msgstr "Unidade de headset (HSP/HFP, codec %s)" #: spa/plugins/bluez5/bluez5-device.c:1364 msgid "Headset Head Unit (HSP/HFP)" msgstr "Unidade de headset (HSP/HFP)" # Supostamente relacionado a HFP, hands-free profile, mas não encontrei tradução comum #: spa/plugins/bluez5/bluez5-device.c:1443 #: spa/plugins/bluez5/bluez5-device.c:1448 #: spa/plugins/bluez5/bluez5-device.c:1455 #: spa/plugins/bluez5/bluez5-device.c:1461 #: spa/plugins/bluez5/bluez5-device.c:1467 #: spa/plugins/bluez5/bluez5-device.c:1473 #: spa/plugins/bluez5/bluez5-device.c:1479 #: spa/plugins/bluez5/bluez5-device.c:1485 #: spa/plugins/bluez5/bluez5-device.c:1491 msgid "Handsfree" msgstr "Handsfree" # Supostamente relacionado a HFP, hands-free profile, mas não encontrei tradução comum #: spa/plugins/bluez5/bluez5-device.c:1449 msgid "Handsfree (HFP)" msgstr "Handsfree (HFP)" #: spa/plugins/bluez5/bluez5-device.c:1466 msgid "Headphone" msgstr "Fones de ouvido" #: spa/plugins/bluez5/bluez5-device.c:1472 msgid "Portable" msgstr "Portátil" #: spa/plugins/bluez5/bluez5-device.c:1478 msgid "Car" msgstr "Carro" #: spa/plugins/bluez5/bluez5-device.c:1484 msgid "HiFi" msgstr "HiFi" #: spa/plugins/bluez5/bluez5-device.c:1490 msgid "Phone" msgstr "Telefone" #: spa/plugins/bluez5/bluez5-device.c:1497 msgid "Bluetooth" msgstr "Bluetooth" #: spa/plugins/bluez5/bluez5-device.c:1498 msgid "Bluetooth (HFP)" msgstr "Bluetooth (HFP)" pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/po/ro.po000066400000000000000000000513751511204443500221070ustar00rootroot00000000000000# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the pipewire package. # # Sergiu Bivol , 2022. msgid "" msgstr "" "Project-Id-Version: pipewire\n" "Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/pipewire/issues/" "new\n" "POT-Creation-Date: 2021-11-17 15:06+0100\n" "PO-Revision-Date: 2022-02-05 12:06+0000\n" "Last-Translator: Sergiu Bivol \n" "Language-Team: Romanian\n" "Language: ro\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=n==1 ? 0 : (n==0 || (n%100 > 0 && n%100 <" " 20)) ? 1 : 2;\n" "X-Generator: Lokalize 21.12.2\n" #: src/daemon/pipewire.c:45 #, c-format msgid "" "%s [options]\n" " -h, --help Show this help\n" " --version Show version\n" " -c, --config Load config (Default %s)\n" msgstr "" "%s [opțiuni]\n" " -h, --help Arată acest ajutor\n" " --version Arată versiunea\n" " -c, --config Încarcă configurare (implicit %s)\n" #: src/modules/module-protocol-pulse/modules/module-tunnel-sink.c:185 #: src/modules/module-protocol-pulse/modules/module-tunnel-source.c:185 #, c-format msgid "Tunnel to %s/%s" msgstr "Tunelează spre %s/%s" #: src/modules/module-pulse-tunnel.c:536 #, c-format msgid "Tunnel for %s@%s" msgstr "Tunelează pentru %s@%s" #: src/modules/module-zeroconf-discover.c:332 msgid "Unknown device" msgstr "Dispozitiv necunoscut" #: src/modules/module-zeroconf-discover.c:344 #, c-format msgid "%s on %s@%s" msgstr "%s pe %s@%s" #: src/modules/module-zeroconf-discover.c:348 #, c-format msgid "%s on %s" msgstr "%s pe %s" #: src/tools/pw-cat.c:1058 #, c-format msgid "" "%s [options] \n" " -h, --help Show this help\n" " --version Show version\n" " -v, --verbose Enable verbose operations\n" "\n" msgstr "" "%s [opțiuni] \n" " -h, --help Arată acest ajutor\n" " --version Arată versiunea\n" " -v, --verbose Activează operații verboase\n" "\n" #: src/tools/pw-cat.c:1065 #, c-format msgid "" " -R, --remote Remote daemon name\n" " --media-type Set media type (default %s)\n" " --media-category Set media category (default %s)\n" " --media-role Set media role (default %s)\n" " --target Set node target (default %s)\n" " 0 means don't link\n" " --latency Set node latency (default %s)\n" " Xunit (unit = s, ms, us, ns)\n" " or direct samples (256)\n" " the rate is the one of the source " "file\n" " --list-targets List available targets for --target\n" "\n" msgstr "" " -R, --remote Denumirea demonului distant\n" " --media-type Stabilește tipul mediului (implicit " "%s)\n" " --media-category Stabilește categoria mediului" " (implicit %s)\n" " --media-role Stabilește rolul mediului (implicit " "%s)\n" " --target Stabilește ținta nodului (implicit " "%s)\n" " 0 înseamnă „nu lega”\n" " --latency Stabilește latența nodului (implicit " "%s)\n" " Xunit (unitate = s, ms, us, ns)\n" " sau mostre directe (256)\n" " rata este cea a fișierului sursă\n" " --list-targets Enumeră țintele disponibile pentru" " --target\n" "\n" #: src/tools/pw-cat.c:1083 #, c-format msgid "" " --rate Sample rate (req. for rec) (default " "%u)\n" " --channels Number of channels (req. for rec) " "(default %u)\n" " --channel-map Channel map\n" " one of: \"stereo\", " "\"surround-51\",... or\n" " comma separated list of channel " "names: eg. \"FL,FR\"\n" " --format Sample format %s (req. for rec) " "(default %s)\n" " --volume Stream volume 0-1.0 (default %.3f)\n" " -q --quality Resampler quality (0 - 15) (default " "%d)\n" "\n" msgstr "" " --rate Rată de eșantionare (nec. pt." " înregistrare) (implicit %u)\n" " --channels Număr de canale (nec. pt." " înregistrare) (implicit %u)\n" " --channel-map Hartă canale\n" " una dintre: \"stereo\"," " \"surround-51\",... sau listă\n" " separată prin virgulă cu denumiri" " de canale: de ex. \"FL,FR\"\n" " --format Format de eșantionare %s (nec. pt." " înregistrare) (implicit %s)\n" " --volume Volum flux 0-1.0 (implicit %.3f)\n" " -q --quality Calitate de re-eșantionare (0 - 15)" " (implicit %d)\n" "\n" #: src/tools/pw-cat.c:1100 msgid "" " -p, --playback Playback mode\n" " -r, --record Recording mode\n" " -m, --midi Midi mode\n" " -d, --dsd DSD mode\n" "\n" msgstr "" " -p, --playback Regim de redare\n" " -r, --record Regim de înregistrare\n" " -m, --midi Regim MIDI\n" " -d, --dsd regim DSD\n" "\n" #: src/tools/pw-cli.c:3018 #, c-format msgid "" "%s [options] [command]\n" " -h, --help Show this help\n" " --version Show version\n" " -d, --daemon Start as daemon (Default false)\n" " -r, --remote Remote daemon name\n" "\n" msgstr "" "%s [opțiuni] [comandă]\n" " -h, --help Arată acest ajutor\n" " --version Arată versiunea\n" " -d, --daemon Pornește ca demon (implicit fals)\n" " -r, --remote Denumire demon distant\n" "\n" #: spa/plugins/alsa/acp/acp.c:321 msgid "Pro Audio" msgstr "Pro Audio" #: spa/plugins/alsa/acp/acp.c:444 spa/plugins/alsa/acp/alsa-mixer.c:4648 #: spa/plugins/bluez5/bluez5-device.c:1145 msgid "Off" msgstr "Oprit" #: spa/plugins/alsa/acp/alsa-mixer.c:2652 msgid "Input" msgstr "Intrare" #: spa/plugins/alsa/acp/alsa-mixer.c:2653 msgid "Docking Station Input" msgstr "Intrare stație de andocare" #: spa/plugins/alsa/acp/alsa-mixer.c:2654 msgid "Docking Station Microphone" msgstr "Microfon stație de andocare" #: spa/plugins/alsa/acp/alsa-mixer.c:2655 msgid "Docking Station Line In" msgstr "Linie de intrare stație de andocare" #: spa/plugins/alsa/acp/alsa-mixer.c:2656 #: spa/plugins/alsa/acp/alsa-mixer.c:2747 msgid "Line In" msgstr "Linie de intrare" #: spa/plugins/alsa/acp/alsa-mixer.c:2657 #: spa/plugins/alsa/acp/alsa-mixer.c:2741 #: spa/plugins/bluez5/bluez5-device.c:1302 msgid "Microphone" msgstr "Microfon" #: spa/plugins/alsa/acp/alsa-mixer.c:2658 #: spa/plugins/alsa/acp/alsa-mixer.c:2742 msgid "Front Microphone" msgstr "Microfon frontal" #: spa/plugins/alsa/acp/alsa-mixer.c:2659 #: spa/plugins/alsa/acp/alsa-mixer.c:2743 msgid "Rear Microphone" msgstr "Microfon spate" #: spa/plugins/alsa/acp/alsa-mixer.c:2660 msgid "External Microphone" msgstr "Microfon extern" #: spa/plugins/alsa/acp/alsa-mixer.c:2661 #: spa/plugins/alsa/acp/alsa-mixer.c:2745 msgid "Internal Microphone" msgstr "Microfon intern" #: spa/plugins/alsa/acp/alsa-mixer.c:2662 #: spa/plugins/alsa/acp/alsa-mixer.c:2748 msgid "Radio" msgstr "Radio" #: spa/plugins/alsa/acp/alsa-mixer.c:2663 #: spa/plugins/alsa/acp/alsa-mixer.c:2749 msgid "Video" msgstr "Video" #: spa/plugins/alsa/acp/alsa-mixer.c:2664 msgid "Automatic Gain Control" msgstr "Control automat al nivelului de intrare" #: spa/plugins/alsa/acp/alsa-mixer.c:2665 msgid "No Automatic Gain Control" msgstr "Fără control automat al nivelului de intrare" #: spa/plugins/alsa/acp/alsa-mixer.c:2666 msgid "Boost" msgstr "Stimulare" #: spa/plugins/alsa/acp/alsa-mixer.c:2667 msgid "No Boost" msgstr "Fără stimulare" #: spa/plugins/alsa/acp/alsa-mixer.c:2668 msgid "Amplifier" msgstr "Amplificator" #: spa/plugins/alsa/acp/alsa-mixer.c:2669 msgid "No Amplifier" msgstr "Fără amplificator" #: spa/plugins/alsa/acp/alsa-mixer.c:2670 msgid "Bass Boost" msgstr "Stimulare bas" #: spa/plugins/alsa/acp/alsa-mixer.c:2671 msgid "No Bass Boost" msgstr "Fără stimulare bas" #: spa/plugins/alsa/acp/alsa-mixer.c:2672 #: spa/plugins/bluez5/bluez5-device.c:1307 msgid "Speaker" msgstr "Difuzor" #: spa/plugins/alsa/acp/alsa-mixer.c:2673 #: spa/plugins/alsa/acp/alsa-mixer.c:2751 msgid "Headphones" msgstr "Căști" #: spa/plugins/alsa/acp/alsa-mixer.c:2740 msgid "Analog Input" msgstr "Intrare analogică" #: spa/plugins/alsa/acp/alsa-mixer.c:2744 msgid "Dock Microphone" msgstr "Microfon andocare" #: spa/plugins/alsa/acp/alsa-mixer.c:2746 msgid "Headset Microphone" msgstr "Microfon căști" #: spa/plugins/alsa/acp/alsa-mixer.c:2750 msgid "Analog Output" msgstr "Ieșire analogică" #: spa/plugins/alsa/acp/alsa-mixer.c:2752 msgid "Headphones 2" msgstr "Căști 2" #: spa/plugins/alsa/acp/alsa-mixer.c:2753 msgid "Headphones Mono Output" msgstr "Ieșire mono căști" #: spa/plugins/alsa/acp/alsa-mixer.c:2754 msgid "Line Out" msgstr "Linie de ieșire" #: spa/plugins/alsa/acp/alsa-mixer.c:2755 msgid "Analog Mono Output" msgstr "Ieșire mono analogic" #: spa/plugins/alsa/acp/alsa-mixer.c:2756 msgid "Speakers" msgstr "Difuzoare" #: spa/plugins/alsa/acp/alsa-mixer.c:2757 msgid "HDMI / DisplayPort" msgstr "HDMI / DisplayPort" #: spa/plugins/alsa/acp/alsa-mixer.c:2758 msgid "Digital Output (S/PDIF)" msgstr "Ieșire digitală (S/PDIF)" #: spa/plugins/alsa/acp/alsa-mixer.c:2759 msgid "Digital Input (S/PDIF)" msgstr "Intrare digitală (S/PDIF)" #: spa/plugins/alsa/acp/alsa-mixer.c:2760 msgid "Multichannel Input" msgstr "Intrare multicanal" #: spa/plugins/alsa/acp/alsa-mixer.c:2761 msgid "Multichannel Output" msgstr "Ieșire multicanal" #: spa/plugins/alsa/acp/alsa-mixer.c:2762 msgid "Game Output" msgstr "Ieșire joc" #: spa/plugins/alsa/acp/alsa-mixer.c:2763 #: spa/plugins/alsa/acp/alsa-mixer.c:2764 msgid "Chat Output" msgstr "Ieșire discuție" #: spa/plugins/alsa/acp/alsa-mixer.c:2765 msgid "Chat Input" msgstr "Intrare discuție" #: spa/plugins/alsa/acp/alsa-mixer.c:2766 msgid "Virtual Surround 7.1" msgstr "surround virtual 7.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4471 msgid "Analog Mono" msgstr "mono analogic" #: spa/plugins/alsa/acp/alsa-mixer.c:4472 msgid "Analog Mono (Left)" msgstr "mono analogic (stânga)" #: spa/plugins/alsa/acp/alsa-mixer.c:4473 msgid "Analog Mono (Right)" msgstr "mono analogic (dreapta)" #. Note: Not translated to "Analog Stereo Input", because the source #. * name gets "Input" appended to it automatically, so adding "Input" #. * here would lead to the source name to become "Analog Stereo Input #. * Input". The same logic applies to analog-stereo-output, #. * multichannel-input and multichannel-output. #: spa/plugins/alsa/acp/alsa-mixer.c:4474 #: spa/plugins/alsa/acp/alsa-mixer.c:4482 #: spa/plugins/alsa/acp/alsa-mixer.c:4483 msgid "Analog Stereo" msgstr "stereo analogic" #: spa/plugins/alsa/acp/alsa-mixer.c:4475 msgid "Mono" msgstr "Mono" #: spa/plugins/alsa/acp/alsa-mixer.c:4476 msgid "Stereo" msgstr "Stereo" #: spa/plugins/alsa/acp/alsa-mixer.c:4484 #: spa/plugins/alsa/acp/alsa-mixer.c:4642 #: spa/plugins/bluez5/bluez5-device.c:1292 msgid "Headset" msgstr "Set cu cască" #: spa/plugins/alsa/acp/alsa-mixer.c:4485 #: spa/plugins/alsa/acp/alsa-mixer.c:4643 msgid "Speakerphone" msgstr "Difuzor" #: spa/plugins/alsa/acp/alsa-mixer.c:4486 #: spa/plugins/alsa/acp/alsa-mixer.c:4487 msgid "Multichannel" msgstr "multicanal" #: spa/plugins/alsa/acp/alsa-mixer.c:4488 msgid "Analog Surround 2.1" msgstr "surround analogic 2.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4489 msgid "Analog Surround 3.0" msgstr "surround analogic 3.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4490 msgid "Analog Surround 3.1" msgstr "surround analogic 3.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4491 msgid "Analog Surround 4.0" msgstr "surround analogic 4.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4492 msgid "Analog Surround 4.1" msgstr "surround analogic 4.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4493 msgid "Analog Surround 5.0" msgstr "surround analogic 5.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4494 msgid "Analog Surround 5.1" msgstr "surround analogic 5.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4495 msgid "Analog Surround 6.0" msgstr "surround analogic 6.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4496 msgid "Analog Surround 6.1" msgstr "surround analogic 6.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4497 msgid "Analog Surround 7.0" msgstr "surround analogic 7.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4498 msgid "Analog Surround 7.1" msgstr "surround analogic 7.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4499 msgid "Digital Stereo (IEC958)" msgstr "stereo digital (IEC958)" #: spa/plugins/alsa/acp/alsa-mixer.c:4500 msgid "Digital Surround 4.0 (IEC958/AC3)" msgstr "surround digital 4.0 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4501 msgid "Digital Surround 5.1 (IEC958/AC3)" msgstr "surround digital 5.1 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4502 msgid "Digital Surround 5.1 (IEC958/DTS)" msgstr "surround digital 5.1 (IEC958/DTS)" #: spa/plugins/alsa/acp/alsa-mixer.c:4503 msgid "Digital Stereo (HDMI)" msgstr "stereo digital (HDMI)" #: spa/plugins/alsa/acp/alsa-mixer.c:4504 msgid "Digital Surround 5.1 (HDMI)" msgstr "surround digital 5.1 (HDMI)" #: spa/plugins/alsa/acp/alsa-mixer.c:4505 msgid "Chat" msgstr "Discuție" #: spa/plugins/alsa/acp/alsa-mixer.c:4506 msgid "Game" msgstr "joc" #: spa/plugins/alsa/acp/alsa-mixer.c:4640 msgid "Analog Mono Duplex" msgstr "Duplex mono analogic" #: spa/plugins/alsa/acp/alsa-mixer.c:4641 msgid "Analog Stereo Duplex" msgstr "Duplex stereo analogic" #: spa/plugins/alsa/acp/alsa-mixer.c:4644 msgid "Digital Stereo Duplex (IEC958)" msgstr "Duplex stereo digital (IEC958)" #: spa/plugins/alsa/acp/alsa-mixer.c:4645 msgid "Multichannel Duplex" msgstr "Duplex multicanal" #: spa/plugins/alsa/acp/alsa-mixer.c:4646 msgid "Stereo Duplex" msgstr "Duplex stereo" #: spa/plugins/alsa/acp/alsa-mixer.c:4647 msgid "Mono Chat + 7.1 Surround" msgstr "Discuție mono + Surround 7.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4750 #, c-format msgid "%s Output" msgstr "Ieșire %s" #: spa/plugins/alsa/acp/alsa-mixer.c:4757 #, c-format msgid "%s Input" msgstr "Intrare %s" #: spa/plugins/alsa/acp/alsa-util.c:1173 #: spa/plugins/alsa/acp/alsa-util.c:1267 #, c-format msgid "" "snd_pcm_avail() returned a value that is exceptionally large: %lu byte (%lu " "ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_avail() returned a value that is exceptionally large: %lu bytes (%lu " "ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "snd_pcm_avail() a întors o valoare excepțional de mare: %lu octet (%lu ms).\n" "Cel mai probabil, acesta e un defect în driver-ul ALSA „%s”. Raportați" " această problemă dezvoltatorilor ALSA." msgstr[1] "" "snd_pcm_avail() a întors o valoare excepțional de mare: %lu octeți (%lu ms).\n" "Cel mai probabil, acesta e un defect în driver-ul ALSA „%s”. Raportați" " această problemă dezvoltatorilor ALSA." msgstr[2] "" "snd_pcm_avail() a întors o valoare excepțional de mare: %lu de octeți (%lu" " ms).\n" "Cel mai probabil, acesta e un defect în driver-ul ALSA „%s”. Raportați" " această problemă dezvoltatorilor ALSA." #: spa/plugins/alsa/acp/alsa-util.c:1239 #, c-format msgid "" "snd_pcm_delay() returned a value that is exceptionally large: %li byte (%s" "%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_delay() returned a value that is exceptionally large: %li bytes (%s" "%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "snd_pcm_delay() a întors o valoare excepțional de mare: %li octet (%s%lu" " ms).\n" "Cel mai probabil, acesta e un defect în driver-ul ALSA „%s”. Raportați" " această problemă dezvoltatorilor ALSA." msgstr[1] "" "snd_pcm_delay() a întors o valoare excepțional de mare: %li octeți (%s%lu" " ms).\n" "Cel mai probabil, acesta e un defect în driver-ul ALSA „%s”. Raportați" " această problemă dezvoltatorilor ALSA." msgstr[2] "" "snd_pcm_delay() a întors o valoare excepțional de mare: %li de octeți (%s%lu" " ms).\n" "Cel mai probabil, acesta e un defect în driver-ul ALSA „%s”. Raportați" " această problemă dezvoltatorilor ALSA." #: spa/plugins/alsa/acp/alsa-util.c:1286 #, c-format msgid "" "snd_pcm_avail_delay() returned strange values: delay %lu is less than avail " "%lu.\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr "" "snd_pcm_avail_delay() a întors valori stranii: întârzierea %lu e mai mică" " decât disponibilul %lu.\n" "Cel mai probabil, acesta e un defect în driver-ul ALSA „%s”. Raportați" " această problemă dezvoltatorilor ALSA." #: spa/plugins/alsa/acp/alsa-util.c:1329 #, c-format msgid "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu byte " "(%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu bytes " "(%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "snd_pcm_mmap_begin() a întors o valoare excepțional de mare: %lu octet (%lu" " ms).\n" "Cel mai probabil, acesta e un defect în driver-ul ALSA „%s”. Raportați" " această problemă dezvoltatorilor ALSA." msgstr[1] "" "snd_pcm_mmap_begin() a întors o valoare excepțional de mare: %lu octeți (%lu" " ms).\n" "Cel mai probabil, acesta e un defect în driver-ul ALSA „%s”. Raportați" " această problemă dezvoltatorilor ALSA." msgstr[2] "" "snd_pcm_mmap_begin() a întors o valoare excepțional de mare: %lu de octeți (" "%lu ms).\n" "Cel mai probabil, acesta e un defect în driver-ul ALSA „%s”. Raportați" " această problemă dezvoltatorilor ALSA." #: spa/plugins/alsa/acp/channelmap.h:464 msgid "(invalid)" msgstr "(nevalid)" #: spa/plugins/alsa/acp/compat.c:189 msgid "Built-in Audio" msgstr "Audio încorporat" #: spa/plugins/alsa/acp/compat.c:194 msgid "Modem" msgstr "Modem" #: spa/plugins/bluez5/bluez5-device.c:1155 msgid "Audio Gateway (A2DP Source & HSP/HFP AG)" msgstr "Poartă de acces audio (sursă A2DP și HSP/HFP AG)" #: spa/plugins/bluez5/bluez5-device.c:1178 #, c-format msgid "High Fidelity Playback (A2DP Sink, codec %s)" msgstr "Redare de fidelitate înaltă (chiuvetă A2DP, codec %s)" #: spa/plugins/bluez5/bluez5-device.c:1181 #, c-format msgid "High Fidelity Duplex (A2DP Source/Sink, codec %s)" msgstr "Duplex de fidelitate înaltă (sursă/chiuvetă A2DP, codec %s)" #: spa/plugins/bluez5/bluez5-device.c:1188 msgid "High Fidelity Playback (A2DP Sink)" msgstr "Redare de fidelitate înaltă (chiuvetă A2DP)" #: spa/plugins/bluez5/bluez5-device.c:1190 msgid "High Fidelity Duplex (A2DP Source/Sink)" msgstr "Duplex de fidelitate înaltă (sursă/chiuvetă A2DP)" #: spa/plugins/bluez5/bluez5-device.c:1217 #, c-format msgid "Headset Head Unit (HSP/HFP, codec %s)" msgstr "Bază pentru set cu cască (HSP/HFP, codec %s)" #: spa/plugins/bluez5/bluez5-device.c:1221 msgid "Headset Head Unit (HSP/HFP)" msgstr "Bază pentru set cu cască (HSP/HFP)" #: spa/plugins/bluez5/bluez5-device.c:1297 msgid "Handsfree" msgstr "Mâini libere" #: spa/plugins/bluez5/bluez5-device.c:1312 msgid "Headphone" msgstr "Cască" #: spa/plugins/bluez5/bluez5-device.c:1317 msgid "Portable" msgstr "Portabil" #: spa/plugins/bluez5/bluez5-device.c:1322 msgid "Car" msgstr "Mașină" #: spa/plugins/bluez5/bluez5-device.c:1327 msgid "HiFi" msgstr "HiFi" #: spa/plugins/bluez5/bluez5-device.c:1332 msgid "Phone" msgstr "Telefon" #: spa/plugins/bluez5/bluez5-device.c:1338 msgid "Bluetooth" msgstr "Bluetooth" pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/po/ru.po000066400000000000000000000651171511204443500221140ustar00rootroot00000000000000# Russian translation of pipewire. # Copyright (C) 2010 pipewire # This file is distributed under the same license as the pipewire package. # # Leonid Kurbatov , 2010, 2012. # Alexander Potashev , 2014, 2019. # Victor Ryzhykh , 2021. msgid "" msgstr "" "Project-Id-Version: pipewire\n" "Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/pipewire/-/" "issues\n" "POT-Creation-Date: 2023-05-28 10:45+0000\n" "PO-Revision-Date: 2023-11-28 14:30+0300\n" "Last-Translator: Aleksandr Melman \n" "Language-Team: ru\n" "Language: ru\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && " "n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" "X-Generator: Poedit 3.4.1\n" #: src/daemon/pipewire.c:26 #, c-format msgid "" "%s [options]\n" " -h, --help Show this help\n" " --version Show version\n" " -c, --config Load config (Default %s)\n" msgstr "" "%s [параметры]\n" " -h, --help Показать справку\n" " --version Показать версию\n" " -c, --config Загрузить конфигурацию (По умолчанию " "%s)\n" #: src/daemon/pipewire.desktop.in:4 msgid "PipeWire Media System" msgstr "Мультимедийная система PipeWire" #: src/daemon/pipewire.desktop.in:5 msgid "Start the PipeWire Media System" msgstr "Запустить PipeWire" #: src/modules/module-protocol-pulse/modules/module-tunnel-sink.c:141 #: src/modules/module-protocol-pulse/modules/module-tunnel-source.c:141 #, c-format msgid "Tunnel to %s%s%s" msgstr "Туннель на %s%s%s" #: src/modules/module-fallback-sink.c:31 msgid "Dummy Output" msgstr "Холостой выход" #: src/modules/module-pulse-tunnel.c:844 #, c-format msgid "Tunnel for %s@%s" msgstr "Туннель для %s@%s" #: src/modules/module-zeroconf-discover.c:315 msgid "Unknown device" msgstr "Неизвестное устройство" #: src/modules/module-zeroconf-discover.c:327 #, c-format msgid "%s on %s@%s" msgstr "%s на %s@%s" #: src/modules/module-zeroconf-discover.c:331 #, c-format msgid "%s on %s" msgstr "%s по %s" #: src/tools/pw-cat.c:974 #, c-format msgid "" "%s [options] [|-]\n" " -h, --help Show this help\n" " --version Show version\n" " -v, --verbose Enable verbose operations\n" "\n" msgstr "" "%s [параметры] [|-]\n" " -h, --help Показать справку\n" " --version Показать версию\n" " -v, --verbose Включить показ подробной информации\n" "\n" #: src/tools/pw-cat.c:981 #, c-format msgid "" " -R, --remote Remote daemon name\n" " --media-type Set media type (default %s)\n" " --media-category Set media category (default %s)\n" " --media-role Set media role (default %s)\n" " --target Set node target serial or name " "(default %s)\n" " 0 means don't link\n" " --latency Set node latency (default %s)\n" " Xunit (unit = s, ms, us, ns)\n" " or direct samples (256)\n" " the rate is the one of the source " "file\n" " -P --properties Set node properties\n" "\n" msgstr "" " -R, --remote Имя удаленного фоновой службы\n" " --media-type Задать тип мультимедиа (по умолчанию " "%s)\n" " --media-category Задать категорию мультимедиа (по " "умолчанию %s)\n" " --media-role Задать роль мультимедиа (по " "умолчанию %s)\n" " --target Задать серийный номер или имя " "целевого узла (по умолчанию %s)\n" " 0 значит не связывать\n" " --latency Задать задежку узла (по умолчанию " "%s)\n" " Xединица (единица = s, ms, us, " "ns)\n" " or direct samples (256)\n" " частота такая же как у источника " "file\n" " -P --properties Задать свойства узла\n" "\n" #: src/tools/pw-cat.c:999 #, c-format msgid "" " --rate Sample rate (req. for rec) (default " "%u)\n" " --channels Number of channels (req. for rec) " "(default %u)\n" " --channel-map Channel map\n" " one of: \"stereo\", " "\"surround-51\",... or\n" " comma separated list of channel " "names: eg. \"FL,FR\"\n" " --format Sample format %s (req. for rec) " "(default %s)\n" " --volume Stream volume 0-1.0 (default %.3f)\n" " -q --quality Resampler quality (0 - 15) (default " "%d)\n" "\n" msgstr "" " --rate Частота дискретизации (необходимо " "для записи) (По умолчанию %u)\n" " --channels Количество каналов (необходимо для " "записи) (default %u)\n" " --channel-map Карта каналов\n" " одно из: \"stereo\", " "\"surround-51\",... or\n" " список каналов через запятую " "names: eg. \"FL,FR\"\n" " --format Формат %s (необходимо для записи) " "(default %s)\n" " --volume Громкость 0-1.0 (по умолчанию %.3f)\n" " -q --quality Качество ресэмплера (0 - 15) (по " "умолчанию %d)\n" "\n" #: src/tools/pw-cat.c:1016 msgid "" " -p, --playback Playback mode\n" " -r, --record Recording mode\n" " -m, --midi Midi mode\n" " -d, --dsd DSD mode\n" " -o, --encoded Encoded mode\n" "\n" msgstr "" " -p, --playback Режим проигрывания\n" " -r, --record Режим записи\n" " -m, --midi Режим MIDI\n" " -d, --dsd Режим DSD\n" " -o, --encoded Режим кодирования\n" "\n" #: src/tools/pw-cli.c:2220 #, c-format msgid "" "%s [options] [command]\n" " -h, --help Show this help\n" " --version Show version\n" " -d, --daemon Start as daemon (Default false)\n" " -r, --remote Remote daemon name\n" " -m, --monitor Monitor activity\n" "\n" msgstr "" "%s [параметры] [команда]\n" " -h, --help Показать справку\n" " --version Показать версию\n" " -d, --daemon Запустить в режиме фоновой службы " "(По умолчанию false)\n" " -r, --remote Имя удаленного фоновой службы\n" " -m, --monitor Контроль активности\n" "\n" #: spa/plugins/alsa/acp/acp.c:303 msgid "Pro Audio" msgstr "Pro Audio" #: spa/plugins/alsa/acp/acp.c:427 spa/plugins/alsa/acp/alsa-mixer.c:4648 #: spa/plugins/bluez5/bluez5-device.c:1586 msgid "Off" msgstr "Выключено" #: spa/plugins/alsa/acp/alsa-mixer.c:2652 msgid "Input" msgstr "Вход" #: spa/plugins/alsa/acp/alsa-mixer.c:2653 msgid "Docking Station Input" msgstr "Вход док-станции" #: spa/plugins/alsa/acp/alsa-mixer.c:2654 msgid "Docking Station Microphone" msgstr "Микрофон док-станции" #: spa/plugins/alsa/acp/alsa-mixer.c:2655 msgid "Docking Station Line In" msgstr "Линейный вход док-станции" #: spa/plugins/alsa/acp/alsa-mixer.c:2656 #: spa/plugins/alsa/acp/alsa-mixer.c:2747 msgid "Line In" msgstr "Линейный вход" #: spa/plugins/alsa/acp/alsa-mixer.c:2657 #: spa/plugins/alsa/acp/alsa-mixer.c:2741 #: spa/plugins/bluez5/bluez5-device.c:1831 msgid "Microphone" msgstr "Микрофон" #: spa/plugins/alsa/acp/alsa-mixer.c:2658 #: spa/plugins/alsa/acp/alsa-mixer.c:2742 msgid "Front Microphone" msgstr "Фронтальный микрофон" #: spa/plugins/alsa/acp/alsa-mixer.c:2659 #: spa/plugins/alsa/acp/alsa-mixer.c:2743 msgid "Rear Microphone" msgstr "Тыловой микрофон" #: spa/plugins/alsa/acp/alsa-mixer.c:2660 msgid "External Microphone" msgstr "Внешний микрофон" #: spa/plugins/alsa/acp/alsa-mixer.c:2661 #: spa/plugins/alsa/acp/alsa-mixer.c:2745 msgid "Internal Microphone" msgstr "Встроенный микрофон" #: spa/plugins/alsa/acp/alsa-mixer.c:2662 #: spa/plugins/alsa/acp/alsa-mixer.c:2748 msgid "Radio" msgstr "Радио" #: spa/plugins/alsa/acp/alsa-mixer.c:2663 #: spa/plugins/alsa/acp/alsa-mixer.c:2749 msgid "Video" msgstr "Видео" #: spa/plugins/alsa/acp/alsa-mixer.c:2664 msgid "Automatic Gain Control" msgstr "Автоматическая регулировка усиления" #: spa/plugins/alsa/acp/alsa-mixer.c:2665 msgid "No Automatic Gain Control" msgstr "Нет автоматической регулировки усиления" #: spa/plugins/alsa/acp/alsa-mixer.c:2666 msgid "Boost" msgstr "Усиление" #: spa/plugins/alsa/acp/alsa-mixer.c:2667 msgid "No Boost" msgstr "Нет усиления" #: spa/plugins/alsa/acp/alsa-mixer.c:2668 msgid "Amplifier" msgstr "Усилитель" #: spa/plugins/alsa/acp/alsa-mixer.c:2669 msgid "No Amplifier" msgstr "Нет усилителя" #: spa/plugins/alsa/acp/alsa-mixer.c:2670 msgid "Bass Boost" msgstr "Усиление басов" #: spa/plugins/alsa/acp/alsa-mixer.c:2671 msgid "No Bass Boost" msgstr "Нет усиления басов" #: spa/plugins/alsa/acp/alsa-mixer.c:2672 #: spa/plugins/bluez5/bluez5-device.c:1837 msgid "Speaker" msgstr "Динамик" #: spa/plugins/alsa/acp/alsa-mixer.c:2673 #: spa/plugins/alsa/acp/alsa-mixer.c:2751 msgid "Headphones" msgstr "Наушники" #: spa/plugins/alsa/acp/alsa-mixer.c:2740 msgid "Analog Input" msgstr "Аналоговый вход" #: spa/plugins/alsa/acp/alsa-mixer.c:2744 msgid "Dock Microphone" msgstr "Микрофон док-станции" #: spa/plugins/alsa/acp/alsa-mixer.c:2746 msgid "Headset Microphone" msgstr "Микрофон гарнитуры" #: spa/plugins/alsa/acp/alsa-mixer.c:2750 msgid "Analog Output" msgstr "Аналоговый выход" #: spa/plugins/alsa/acp/alsa-mixer.c:2752 msgid "Headphones 2" msgstr "Наушники 2" #: spa/plugins/alsa/acp/alsa-mixer.c:2753 msgid "Headphones Mono Output" msgstr "Моно-выход наушников" #: spa/plugins/alsa/acp/alsa-mixer.c:2754 msgid "Line Out" msgstr "Линейный выход" #: spa/plugins/alsa/acp/alsa-mixer.c:2755 msgid "Analog Mono Output" msgstr "Аналоговый моно-выход" #: spa/plugins/alsa/acp/alsa-mixer.c:2756 msgid "Speakers" msgstr "Динамики" #: spa/plugins/alsa/acp/alsa-mixer.c:2757 msgid "HDMI / DisplayPort" msgstr "HDMI / DisplayPort" #: spa/plugins/alsa/acp/alsa-mixer.c:2758 msgid "Digital Output (S/PDIF)" msgstr "Цифровой выход (S/PDIF)" #: spa/plugins/alsa/acp/alsa-mixer.c:2759 msgid "Digital Input (S/PDIF)" msgstr "Цифровой вход (S/PDIF)" #: spa/plugins/alsa/acp/alsa-mixer.c:2760 msgid "Multichannel Input" msgstr "Многоканальный вход" #: spa/plugins/alsa/acp/alsa-mixer.c:2761 msgid "Multichannel Output" msgstr "Многоканальный выход" #: spa/plugins/alsa/acp/alsa-mixer.c:2762 msgid "Game Output" msgstr "Игровой выход" #: spa/plugins/alsa/acp/alsa-mixer.c:2763 #: spa/plugins/alsa/acp/alsa-mixer.c:2764 msgid "Chat Output" msgstr "Разговорный выход" #: spa/plugins/alsa/acp/alsa-mixer.c:2765 msgid "Chat Input" msgstr "Разговорный вход" #: spa/plugins/alsa/acp/alsa-mixer.c:2766 msgid "Virtual Surround 7.1" msgstr "Виртуальный объёмный звук 7.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4471 msgid "Analog Mono" msgstr "Аналоговый моно" #: spa/plugins/alsa/acp/alsa-mixer.c:4472 msgid "Analog Mono (Left)" msgstr "Аналоговый моно (левый)" #: spa/plugins/alsa/acp/alsa-mixer.c:4473 msgid "Analog Mono (Right)" msgstr "Аналоговый моно (правый)" #. Note: Not translated to "Analog Stereo Input", because the source #. * name gets "Input" appended to it automatically, so adding "Input" #. * here would lead to the source name to become "Analog Stereo Input #. * Input". The same logic applies to analog-stereo-output, #. * multichannel-input and multichannel-output. #: spa/plugins/alsa/acp/alsa-mixer.c:4474 #: spa/plugins/alsa/acp/alsa-mixer.c:4482 #: spa/plugins/alsa/acp/alsa-mixer.c:4483 msgid "Analog Stereo" msgstr "Аналоговый стерео" #: spa/plugins/alsa/acp/alsa-mixer.c:4475 msgid "Mono" msgstr "Моно" #: spa/plugins/alsa/acp/alsa-mixer.c:4476 msgid "Stereo" msgstr "Стерео" #: spa/plugins/alsa/acp/alsa-mixer.c:4484 #: spa/plugins/alsa/acp/alsa-mixer.c:4642 #: spa/plugins/bluez5/bluez5-device.c:1819 msgid "Headset" msgstr "Гарнитура" #: spa/plugins/alsa/acp/alsa-mixer.c:4485 #: spa/plugins/alsa/acp/alsa-mixer.c:4643 msgid "Speakerphone" msgstr "Спикерфон" #: spa/plugins/alsa/acp/alsa-mixer.c:4486 #: spa/plugins/alsa/acp/alsa-mixer.c:4487 msgid "Multichannel" msgstr "Многоканальный" #: spa/plugins/alsa/acp/alsa-mixer.c:4488 msgid "Analog Surround 2.1" msgstr "Аналоговый объёмный 2.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4489 msgid "Analog Surround 3.0" msgstr "Аналоговый объёмный 3.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4490 msgid "Analog Surround 3.1" msgstr "Аналоговый объёмный 3.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4491 msgid "Analog Surround 4.0" msgstr "Аналоговый объёмный 4.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4492 msgid "Analog Surround 4.1" msgstr "Аналоговый объёмный 4.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4493 msgid "Analog Surround 5.0" msgstr "Аналоговый объёмный 5.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4494 msgid "Analog Surround 5.1" msgstr "Аналоговый объёмный 5.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4495 msgid "Analog Surround 6.0" msgstr "Аналоговый объёмный 6.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4496 msgid "Analog Surround 6.1" msgstr "Аналоговый объёмный 6.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4497 msgid "Analog Surround 7.0" msgstr "Аналоговый объёмный 7.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4498 msgid "Analog Surround 7.1" msgstr "Аналоговый объёмный 7.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4499 msgid "Digital Stereo (IEC958)" msgstr "Цифровой стерео (IEC958)" #: spa/plugins/alsa/acp/alsa-mixer.c:4500 msgid "Digital Surround 4.0 (IEC958/AC3)" msgstr "Цифровой объёмный 4.0 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4501 msgid "Digital Surround 5.1 (IEC958/AC3)" msgstr "Цифровой объёмный 5.1 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4502 msgid "Digital Surround 5.1 (IEC958/DTS)" msgstr "Цифровой объёмный 5.1 (IEC958/DTS)" #: spa/plugins/alsa/acp/alsa-mixer.c:4503 msgid "Digital Stereo (HDMI)" msgstr "Цифровой стерео (HDMI)" #: spa/plugins/alsa/acp/alsa-mixer.c:4504 msgid "Digital Surround 5.1 (HDMI)" msgstr "Цифровой объёмный 5.1 (HDMI)" #: spa/plugins/alsa/acp/alsa-mixer.c:4505 msgid "Chat" msgstr "Чат" #: spa/plugins/alsa/acp/alsa-mixer.c:4506 msgid "Game" msgstr "Игра" #: spa/plugins/alsa/acp/alsa-mixer.c:4640 msgid "Analog Mono Duplex" msgstr "Аналоговый моно дуплекс" #: spa/plugins/alsa/acp/alsa-mixer.c:4641 msgid "Analog Stereo Duplex" msgstr "Аналоговый стерео дуплекс" #: spa/plugins/alsa/acp/alsa-mixer.c:4644 msgid "Digital Stereo Duplex (IEC958)" msgstr "Цифровой стерео дуплекс (IEC958)" #: spa/plugins/alsa/acp/alsa-mixer.c:4645 msgid "Multichannel Duplex" msgstr "Многоканальный дуплекс" #: spa/plugins/alsa/acp/alsa-mixer.c:4646 msgid "Stereo Duplex" msgstr "Стерео дуплекс" #: spa/plugins/alsa/acp/alsa-mixer.c:4647 msgid "Mono Chat + 7.1 Surround" msgstr "Моно чат + 7.1 Surround" #: spa/plugins/alsa/acp/alsa-mixer.c:4748 #, c-format msgid "%s Output" msgstr "%s выход" #: spa/plugins/alsa/acp/alsa-mixer.c:4756 #, c-format msgid "%s Input" msgstr "%s вход" #: spa/plugins/alsa/acp/alsa-util.c:1187 spa/plugins/alsa/acp/alsa-util.c:1281 #, c-format msgid "" "snd_pcm_avail() returned a value that is exceptionally large: %lu byte (%lu " "ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_avail() returned a value that is exceptionally large: %lu bytes (%lu " "ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "snd_pcm_avail() возвращает значение, которое является исключительно большим: " "%lu байт (%lu мс).\n" "Вероятно, это ошибка в драйвере ALSA «%s». Пожалуйста, сообщите об этой " "проблеме разработчикам ALSA." msgstr[1] "" "snd_pcm_avail() возвращает значение, которое является исключительно большим: " "%lu байта (%lu мс).\n" "Вероятно, это ошибка в драйвере ALSA «%s». Пожалуйста, сообщите об этой " "проблеме разработчикам ALSA." msgstr[2] "" "snd_pcm_avail() возвращает значение, которое является исключительно большим: " "%lu байт (%lu мс).\n" "Вероятно, это ошибка в драйвере ALSA «%s». Пожалуйста, сообщите об этой " "проблеме разработчикам ALSA." #: spa/plugins/alsa/acp/alsa-util.c:1253 #, c-format msgid "" "snd_pcm_delay() returned a value that is exceptionally large: %li byte " "(%s%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_delay() returned a value that is exceptionally large: %li bytes " "(%s%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "snd_pcm_delay() возвращает значение, которое является исключительно большим: " "%li байт (%s%lu мс).\n" "Вероятно, это ошибка в драйвере ALSA «%s». Пожалуйста, сообщите об этой " "проблеме разработчикам ALSA." msgstr[1] "" "snd_pcm_delay() возвращает значение, которое является исключительно большим: " "%li байта (%s%lu мс).\n" "Вероятно, это ошибка в драйвере ALSA «%s». Пожалуйста, сообщите об этой " "проблеме разработчикам ALSA." msgstr[2] "" "snd_pcm_delay() возвращает значение, которое является исключительно большим: " "%li байт (%s%lu мс).\n" "Вероятно, это ошибка в драйвере ALSA «%s». Пожалуйста, сообщите об этой " "проблеме разработчикам ALSA." #: spa/plugins/alsa/acp/alsa-util.c:1300 #, c-format msgid "" "snd_pcm_avail_delay() returned strange values: delay %lu is less than avail " "%lu.\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr "" "snd_pcm_avail_delay() возвращает странное значение: задержка %lu меньше " "доступных %lu.\n" "Вероятно, это ошибка в драйвере ALSA «%s». Пожалуйста, сообщите об этой " "проблеме разработчикам ALSA." #: spa/plugins/alsa/acp/alsa-util.c:1343 #, c-format msgid "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu byte " "(%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu bytes " "(%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "snd_pcm_mmap_begin() возвращает значение, которое является исключительно " "большим: %lu байт (%lu мс).\n" "Вероятно, это ошибка в драйвере ALSA «%s». Пожалуйста, сообщите об этой " "проблеме разработчикам ALSA." msgstr[1] "" "snd_pcm_mmap_begin() возвращает значение, которое является исключительно " "большим: %lu байта (%lu мс).\n" "Вероятно, это ошибка в драйвере ALSA «%s». Пожалуйста, сообщите об этой " "проблеме разработчикам ALSA." msgstr[2] "" "snd_pcm_mmap_begin() возвращает значение, которое является исключительно " "большим: %lu байт (%lu мс).\n" "Вероятно, это ошибка в драйвере ALSA «%s». Пожалуйста, сообщите об этой " "проблеме разработчикам ALSA." #: spa/plugins/alsa/acp/channelmap.h:457 msgid "(invalid)" msgstr "(недействительно)" #: spa/plugins/alsa/acp/compat.c:189 msgid "Built-in Audio" msgstr "Встроенное аудио" #: spa/plugins/alsa/acp/compat.c:194 msgid "Modem" msgstr "Модем" #: spa/plugins/bluez5/bluez5-device.c:1597 msgid "Audio Gateway (A2DP Source & HSP/HFP AG)" msgstr "Адаптер аудиогарнитуры (приёмник A2DP и HSP/HFP AG)" #: spa/plugins/bluez5/bluez5-device.c:1622 #, c-format msgid "High Fidelity Playback (A2DP Sink, codec %s)" msgstr "Воспроизведение высокого качества (приёмник A2DP, кодек %s)" #: spa/plugins/bluez5/bluez5-device.c:1625 #, c-format msgid "High Fidelity Duplex (A2DP Source/Sink, codec %s)" msgstr "Дуплекс высокого качества (источник / приёмник A2DP, кодек %s)" #: spa/plugins/bluez5/bluez5-device.c:1633 msgid "High Fidelity Playback (A2DP Sink)" msgstr "Воспроизведение высокого качества (приёмник A2DP)" #: spa/plugins/bluez5/bluez5-device.c:1635 msgid "High Fidelity Duplex (A2DP Source/Sink)" msgstr "Дуплекс высокого качества (источник / приёмник A2DP)" #: spa/plugins/bluez5/bluez5-device.c:1677 #, c-format msgid "High Fidelity Playback (BAP Sink, codec %s)" msgstr "Воспроизведение высокого качества (приёмник BAP, кодек %s)" #: spa/plugins/bluez5/bluez5-device.c:1681 #, c-format msgid "High Fidelity Input (BAP Source, codec %s)" msgstr "Вход высокого качества (источник BAP, кодек %s)" #: spa/plugins/bluez5/bluez5-device.c:1685 #, c-format msgid "High Fidelity Duplex (BAP Source/Sink, codec %s)" msgstr "Дуплекс высокого качества (источник / приёмник BAP, кодек %s)" #: spa/plugins/bluez5/bluez5-device.c:1693 msgid "High Fidelity Playback (BAP Sink)" msgstr "Воспроизведение высокого качества (приёмник BAP)" #: spa/plugins/bluez5/bluez5-device.c:1696 msgid "High Fidelity Input (BAP Source)" msgstr "Вход высокого качества (источник BAP)" #: spa/plugins/bluez5/bluez5-device.c:1699 msgid "High Fidelity Duplex (BAP Source/Sink)" msgstr "Дуплекс высокого качества (источник / приёмник BAP)" #: spa/plugins/bluez5/bluez5-device.c:1735 #, c-format msgid "Headset Head Unit (HSP/HFP, codec %s)" msgstr "Гарнитура (HSP/HFP, кодек %s)" #: spa/plugins/bluez5/bluez5-device.c:1740 msgid "Headset Head Unit (HSP/HFP)" msgstr "Гарнитура (HSP/HFP)" #: spa/plugins/bluez5/bluez5-device.c:1820 #: spa/plugins/bluez5/bluez5-device.c:1825 #: spa/plugins/bluez5/bluez5-device.c:1832 #: spa/plugins/bluez5/bluez5-device.c:1838 #: spa/plugins/bluez5/bluez5-device.c:1844 #: spa/plugins/bluez5/bluez5-device.c:1850 #: spa/plugins/bluez5/bluez5-device.c:1856 #: spa/plugins/bluez5/bluez5-device.c:1862 #: spa/plugins/bluez5/bluez5-device.c:1868 msgid "Handsfree" msgstr "Handsfree" #: spa/plugins/bluez5/bluez5-device.c:1826 msgid "Handsfree (HFP)" msgstr "Handsfree (HFP)" #: spa/plugins/bluez5/bluez5-device.c:1843 msgid "Headphone" msgstr "Наушники" #: spa/plugins/bluez5/bluez5-device.c:1849 msgid "Portable" msgstr "Портативное устройство" #: spa/plugins/bluez5/bluez5-device.c:1855 msgid "Car" msgstr "Автомобильное устройство" #: spa/plugins/bluez5/bluez5-device.c:1861 msgid "HiFi" msgstr "Hi-Fi" #: spa/plugins/bluez5/bluez5-device.c:1867 msgid "Phone" msgstr "Телефон" #: spa/plugins/bluez5/bluez5-device.c:1874 msgid "Bluetooth" msgstr "Bluetooth" #: spa/plugins/bluez5/bluez5-device.c:1875 msgid "Bluetooth (HFP)" msgstr "Bluetooth (HFP)" pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/po/si.po000066400000000000000000000334741511204443500221020ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the pipewire package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: pipewire\n" "Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/pipewire/" "issues/new\n" "POT-Creation-Date: 2021-04-18 16:54+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: Automatically generated\n" "Language-Team: none\n" "Language: si\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n > 1;\n" #: src/daemon/pipewire.c:43 #, c-format msgid "" "%s [options]\n" " -h, --help Show this help\n" " --version Show version\n" " -c, --config Load config (Default %s)\n" msgstr "" #: src/daemon/pipewire.desktop.in:4 msgid "PipeWire Media System" msgstr "" #: src/daemon/pipewire.desktop.in:5 msgid "Start the PipeWire Media System" msgstr "" #: src/examples/media-session/alsa-monitor.c:526 #: spa/plugins/alsa/acp/compat.c:187 msgid "Built-in Audio" msgstr "" #: src/examples/media-session/alsa-monitor.c:530 #: spa/plugins/alsa/acp/compat.c:192 msgid "Modem" msgstr "" #: src/examples/media-session/alsa-monitor.c:539 msgid "Unknown device" msgstr "" #: src/tools/pw-cat.c:991 #, c-format msgid "" "%s [options] \n" " -h, --help Show this help\n" " --version Show version\n" " -v, --verbose Enable verbose operations\n" "\n" msgstr "" #: src/tools/pw-cat.c:998 #, c-format msgid "" " -R, --remote Remote daemon name\n" " --media-type Set media type (default %s)\n" " --media-category Set media category (default %s)\n" " --media-role Set media role (default %s)\n" " --target Set node target (default %s)\n" " 0 means don't link\n" " --latency Set node latency (default %s)\n" " Xunit (unit = s, ms, us, ns)\n" " or direct samples (256)\n" " the rate is the one of the source " "file\n" " --list-targets List available targets for --target\n" "\n" msgstr "" #: src/tools/pw-cat.c:1016 #, c-format msgid "" " --rate Sample rate (req. for rec) (default " "%u)\n" " --channels Number of channels (req. for rec) " "(default %u)\n" " --channel-map Channel map\n" " one of: \"stereo\", " "\"surround-51\",... or\n" " comma separated list of channel " "names: eg. \"FL,FR\"\n" " --format Sample format %s (req. for rec) " "(default %s)\n" " --volume Stream volume 0-1.0 (default %.3f)\n" " -q --quality Resampler quality (0 - 15) (default " "%d)\n" "\n" msgstr "" #: src/tools/pw-cat.c:1033 msgid "" " -p, --playback Playback mode\n" " -r, --record Recording mode\n" " -m, --midi Midi mode\n" "\n" msgstr "" #: src/tools/pw-cli.c:2932 #, c-format msgid "" "%s [options] [command]\n" " -h, --help Show this help\n" " --version Show version\n" " -d, --daemon Start as daemon (Default false)\n" " -r, --remote Remote daemon name\n" "\n" msgstr "" #: spa/plugins/alsa/acp/acp.c:290 msgid "Pro Audio" msgstr "" #: spa/plugins/alsa/acp/acp.c:411 spa/plugins/alsa/acp/alsa-mixer.c:4704 #: spa/plugins/bluez5/bluez5-device.c:1000 msgid "Off" msgstr "" #: spa/plugins/alsa/acp/channelmap.h:466 msgid "(invalid)" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2709 msgid "Input" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2710 msgid "Docking Station Input" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2711 msgid "Docking Station Microphone" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2712 msgid "Docking Station Line In" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2713 #: spa/plugins/alsa/acp/alsa-mixer.c:2804 msgid "Line In" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2714 #: spa/plugins/alsa/acp/alsa-mixer.c:2798 #: spa/plugins/bluez5/bluez5-device.c:1145 msgid "Microphone" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2715 #: spa/plugins/alsa/acp/alsa-mixer.c:2799 msgid "Front Microphone" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2716 #: spa/plugins/alsa/acp/alsa-mixer.c:2800 msgid "Rear Microphone" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2717 msgid "External Microphone" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2718 #: spa/plugins/alsa/acp/alsa-mixer.c:2802 msgid "Internal Microphone" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2719 #: spa/plugins/alsa/acp/alsa-mixer.c:2805 msgid "Radio" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2720 #: spa/plugins/alsa/acp/alsa-mixer.c:2806 msgid "Video" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2721 msgid "Automatic Gain Control" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2722 msgid "No Automatic Gain Control" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2723 msgid "Boost" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2724 msgid "No Boost" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2725 msgid "Amplifier" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2726 msgid "No Amplifier" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2727 msgid "Bass Boost" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2728 msgid "No Bass Boost" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2729 #: spa/plugins/bluez5/bluez5-device.c:1150 msgid "Speaker" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2730 #: spa/plugins/alsa/acp/alsa-mixer.c:2808 msgid "Headphones" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2797 msgid "Analog Input" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2801 msgid "Dock Microphone" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2803 msgid "Headset Microphone" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2807 msgid "Analog Output" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2809 msgid "Headphones 2" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2810 msgid "Headphones Mono Output" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2811 msgid "Line Out" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2812 msgid "Analog Mono Output" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2813 msgid "Speakers" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2814 msgid "HDMI / DisplayPort" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2815 msgid "Digital Output (S/PDIF)" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2816 msgid "Digital Input (S/PDIF)" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2817 msgid "Multichannel Input" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2818 msgid "Multichannel Output" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2819 msgid "Game Output" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2820 #: spa/plugins/alsa/acp/alsa-mixer.c:2821 msgid "Chat Output" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2822 msgid "Chat Input" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2823 msgid "Virtual Surround 7.1" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4527 msgid "Analog Mono" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4528 msgid "Analog Mono (Left)" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4529 msgid "Analog Mono (Right)" msgstr "" #. Note: Not translated to "Analog Stereo Input", because the source #. * name gets "Input" appended to it automatically, so adding "Input" #. * here would lead to the source name to become "Analog Stereo Input #. * Input". The same logic applies to analog-stereo-output, #. * multichannel-input and multichannel-output. #: spa/plugins/alsa/acp/alsa-mixer.c:4530 #: spa/plugins/alsa/acp/alsa-mixer.c:4538 #: spa/plugins/alsa/acp/alsa-mixer.c:4539 msgid "Analog Stereo" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4531 msgid "Mono" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4532 msgid "Stereo" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4540 #: spa/plugins/alsa/acp/alsa-mixer.c:4698 #: spa/plugins/bluez5/bluez5-device.c:1135 msgid "Headset" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4541 #: spa/plugins/alsa/acp/alsa-mixer.c:4699 msgid "Speakerphone" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4542 #: spa/plugins/alsa/acp/alsa-mixer.c:4543 msgid "Multichannel" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4544 msgid "Analog Surround 2.1" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4545 msgid "Analog Surround 3.0" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4546 msgid "Analog Surround 3.1" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4547 msgid "Analog Surround 4.0" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4548 msgid "Analog Surround 4.1" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4549 msgid "Analog Surround 5.0" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4550 msgid "Analog Surround 5.1" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4551 msgid "Analog Surround 6.0" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4552 msgid "Analog Surround 6.1" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4553 msgid "Analog Surround 7.0" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4554 msgid "Analog Surround 7.1" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4555 msgid "Digital Stereo (IEC958)" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4556 msgid "Digital Surround 4.0 (IEC958/AC3)" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4557 msgid "Digital Surround 5.1 (IEC958/AC3)" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4558 msgid "Digital Surround 5.1 (IEC958/DTS)" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4559 msgid "Digital Stereo (HDMI)" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4560 msgid "Digital Surround 5.1 (HDMI)" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4561 msgid "Chat" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4562 msgid "Game" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4696 msgid "Analog Mono Duplex" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4697 msgid "Analog Stereo Duplex" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4700 msgid "Digital Stereo Duplex (IEC958)" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4701 msgid "Multichannel Duplex" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4702 msgid "Stereo Duplex" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4703 msgid "Mono Chat + 7.1 Surround" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4806 #, c-format msgid "%s Output" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4813 #, c-format msgid "%s Input" msgstr "" #: spa/plugins/alsa/acp/alsa-util.c:1175 spa/plugins/alsa/acp/alsa-util.c:1269 #, c-format msgid "" "snd_pcm_avail() returned a value that is exceptionally large: %lu byte (%lu " "ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_avail() returned a value that is exceptionally large: %lu bytes (%lu " "ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" msgstr[1] "" #: spa/plugins/alsa/acp/alsa-util.c:1241 #, c-format msgid "" "snd_pcm_delay() returned a value that is exceptionally large: %li byte (%s" "%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_delay() returned a value that is exceptionally large: %li bytes (%s" "%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" msgstr[1] "" #: spa/plugins/alsa/acp/alsa-util.c:1288 #, c-format msgid "" "snd_pcm_avail_delay() returned strange values: delay %lu is less than avail " "%lu.\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr "" #: spa/plugins/alsa/acp/alsa-util.c:1331 #, c-format msgid "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu byte " "(%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu bytes " "(%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" msgstr[1] "" #: spa/plugins/bluez5/bluez5-device.c:1010 msgid "Audio Gateway (A2DP Source & HSP/HFP AG)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1033 #, c-format msgid "High Fidelity Playback (A2DP Sink, codec %s)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1035 #, c-format msgid "High Fidelity Duplex (A2DP Source/Sink, codec %s)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1041 msgid "High Fidelity Playback (A2DP Sink)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1043 msgid "High Fidelity Duplex (A2DP Source/Sink)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1070 #, c-format msgid "Headset Head Unit (HSP/HFP, codec %s)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1074 msgid "Headset Head Unit (HSP/HFP)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1140 msgid "Handsfree" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1155 msgid "Headphone" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1160 msgid "Portable" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1165 msgid "Car" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1170 msgid "HiFi" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1175 msgid "Phone" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1181 msgid "Bluetooth" msgstr "" pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/po/sk.po000066400000000000000000000372561511204443500221060ustar00rootroot00000000000000# Slovak translation for PipeWire. # Copyright (C) 2014 PipeWire's COPYRIGHT HOLDER # This file is distributed under the same license as the PipeWire package. # Dušan Kazik , 2014, 2015. # msgid "" msgstr "" "Project-Id-Version: PipeWire master\n" "Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/pipewire/" "issues/new\n" "POT-Creation-Date: 2021-04-18 16:54+0800\n" "PO-Revision-Date: 2020-11-25 08:35+0000\n" "Last-Translator: Dusan Kazik \n" "Language-Team: Slovak \n" "Language: sk\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=(n==1) ? 1 : (n>=2 && n<=4) ? 2 : 0;\n" "X-Generator: Weblate 4.3.2\n" #: src/daemon/pipewire.c:43 #, c-format msgid "" "%s [options]\n" " -h, --help Show this help\n" " --version Show version\n" " -c, --config Load config (Default %s)\n" msgstr "" #: src/daemon/pipewire.desktop.in:4 msgid "PipeWire Media System" msgstr "" #: src/daemon/pipewire.desktop.in:5 msgid "Start the PipeWire Media System" msgstr "" #: src/examples/media-session/alsa-monitor.c:526 #: spa/plugins/alsa/acp/compat.c:187 msgid "Built-in Audio" msgstr "Vstavaný zvuk" #: src/examples/media-session/alsa-monitor.c:530 #: spa/plugins/alsa/acp/compat.c:192 msgid "Modem" msgstr "Modem" #: src/examples/media-session/alsa-monitor.c:539 msgid "Unknown device" msgstr "" #: src/tools/pw-cat.c:991 #, c-format msgid "" "%s [options] \n" " -h, --help Show this help\n" " --version Show version\n" " -v, --verbose Enable verbose operations\n" "\n" msgstr "" #: src/tools/pw-cat.c:998 #, c-format msgid "" " -R, --remote Remote daemon name\n" " --media-type Set media type (default %s)\n" " --media-category Set media category (default %s)\n" " --media-role Set media role (default %s)\n" " --target Set node target (default %s)\n" " 0 means don't link\n" " --latency Set node latency (default %s)\n" " Xunit (unit = s, ms, us, ns)\n" " or direct samples (256)\n" " the rate is the one of the source " "file\n" " --list-targets List available targets for --target\n" "\n" msgstr "" #: src/tools/pw-cat.c:1016 #, c-format msgid "" " --rate Sample rate (req. for rec) (default " "%u)\n" " --channels Number of channels (req. for rec) " "(default %u)\n" " --channel-map Channel map\n" " one of: \"stereo\", " "\"surround-51\",... or\n" " comma separated list of channel " "names: eg. \"FL,FR\"\n" " --format Sample format %s (req. for rec) " "(default %s)\n" " --volume Stream volume 0-1.0 (default %.3f)\n" " -q --quality Resampler quality (0 - 15) (default " "%d)\n" "\n" msgstr "" #: src/tools/pw-cat.c:1033 msgid "" " -p, --playback Playback mode\n" " -r, --record Recording mode\n" " -m, --midi Midi mode\n" "\n" msgstr "" #: src/tools/pw-cli.c:2932 #, c-format msgid "" "%s [options] [command]\n" " -h, --help Show this help\n" " --version Show version\n" " -d, --daemon Start as daemon (Default false)\n" " -r, --remote Remote daemon name\n" "\n" msgstr "" #: spa/plugins/alsa/acp/acp.c:290 msgid "Pro Audio" msgstr "" #: spa/plugins/alsa/acp/acp.c:411 spa/plugins/alsa/acp/alsa-mixer.c:4704 #: spa/plugins/bluez5/bluez5-device.c:1000 msgid "Off" msgstr "Vypnuté" #: spa/plugins/alsa/acp/channelmap.h:466 msgid "(invalid)" msgstr "(neplatné)" #: spa/plugins/alsa/acp/alsa-mixer.c:2709 msgid "Input" msgstr "Vstup" #: spa/plugins/alsa/acp/alsa-mixer.c:2710 msgid "Docking Station Input" msgstr "Vstup dokovacej stanice" #: spa/plugins/alsa/acp/alsa-mixer.c:2711 msgid "Docking Station Microphone" msgstr "Mikrofón dokovacej stanice" #: spa/plugins/alsa/acp/alsa-mixer.c:2712 msgid "Docking Station Line In" msgstr "Vstupná linka dokovacej stanice" #: spa/plugins/alsa/acp/alsa-mixer.c:2713 #: spa/plugins/alsa/acp/alsa-mixer.c:2804 msgid "Line In" msgstr "Vstupná linka" #: spa/plugins/alsa/acp/alsa-mixer.c:2714 #: spa/plugins/alsa/acp/alsa-mixer.c:2798 #: spa/plugins/bluez5/bluez5-device.c:1145 msgid "Microphone" msgstr "Mikrofón" #: spa/plugins/alsa/acp/alsa-mixer.c:2715 #: spa/plugins/alsa/acp/alsa-mixer.c:2799 msgid "Front Microphone" msgstr "Predný mikrofón" #: spa/plugins/alsa/acp/alsa-mixer.c:2716 #: spa/plugins/alsa/acp/alsa-mixer.c:2800 msgid "Rear Microphone" msgstr "Zadný mikrofón" #: spa/plugins/alsa/acp/alsa-mixer.c:2717 msgid "External Microphone" msgstr "Externý mikrofón" #: spa/plugins/alsa/acp/alsa-mixer.c:2718 #: spa/plugins/alsa/acp/alsa-mixer.c:2802 msgid "Internal Microphone" msgstr "Vstavaný mikrofón" #: spa/plugins/alsa/acp/alsa-mixer.c:2719 #: spa/plugins/alsa/acp/alsa-mixer.c:2805 msgid "Radio" msgstr "Rádio" #: spa/plugins/alsa/acp/alsa-mixer.c:2720 #: spa/plugins/alsa/acp/alsa-mixer.c:2806 msgid "Video" msgstr "Video" #: spa/plugins/alsa/acp/alsa-mixer.c:2721 msgid "Automatic Gain Control" msgstr "Automatické ovládanie zosilnenia" #: spa/plugins/alsa/acp/alsa-mixer.c:2722 msgid "No Automatic Gain Control" msgstr "Bez automatického ovládania zosilnenia" #: spa/plugins/alsa/acp/alsa-mixer.c:2723 msgid "Boost" msgstr "Zosilnenie" #: spa/plugins/alsa/acp/alsa-mixer.c:2724 msgid "No Boost" msgstr "Bez zosilnenia" #: spa/plugins/alsa/acp/alsa-mixer.c:2725 msgid "Amplifier" msgstr "Zosilňovač" #: spa/plugins/alsa/acp/alsa-mixer.c:2726 msgid "No Amplifier" msgstr "Bez zosilňovača" #: spa/plugins/alsa/acp/alsa-mixer.c:2727 msgid "Bass Boost" msgstr "Zosilnenie basov" #: spa/plugins/alsa/acp/alsa-mixer.c:2728 msgid "No Bass Boost" msgstr "Bez zosilnenia basov" #: spa/plugins/alsa/acp/alsa-mixer.c:2729 #: spa/plugins/bluez5/bluez5-device.c:1150 msgid "Speaker" msgstr "Reproduktor" #: spa/plugins/alsa/acp/alsa-mixer.c:2730 #: spa/plugins/alsa/acp/alsa-mixer.c:2808 msgid "Headphones" msgstr "Slúchadlá" #: spa/plugins/alsa/acp/alsa-mixer.c:2797 msgid "Analog Input" msgstr "Analógový vstup" #: spa/plugins/alsa/acp/alsa-mixer.c:2801 msgid "Dock Microphone" msgstr "Mikrofón doku" #: spa/plugins/alsa/acp/alsa-mixer.c:2803 msgid "Headset Microphone" msgstr "Mikrofón headsetu" #: spa/plugins/alsa/acp/alsa-mixer.c:2807 msgid "Analog Output" msgstr "Analógový výstup" #: spa/plugins/alsa/acp/alsa-mixer.c:2809 #, fuzzy msgid "Headphones 2" msgstr "Slúchadlá" #: spa/plugins/alsa/acp/alsa-mixer.c:2810 #, fuzzy msgid "Headphones Mono Output" msgstr "Analógový mono výstup" #: spa/plugins/alsa/acp/alsa-mixer.c:2811 msgid "Line Out" msgstr "Výstupná linka" #: spa/plugins/alsa/acp/alsa-mixer.c:2812 msgid "Analog Mono Output" msgstr "Analógový mono výstup" #: spa/plugins/alsa/acp/alsa-mixer.c:2813 msgid "Speakers" msgstr "Reproduktory" #: spa/plugins/alsa/acp/alsa-mixer.c:2814 msgid "HDMI / DisplayPort" msgstr "HDMI / DisplayPort" #: spa/plugins/alsa/acp/alsa-mixer.c:2815 msgid "Digital Output (S/PDIF)" msgstr "Digitálny výstup (S/PDIF)" #: spa/plugins/alsa/acp/alsa-mixer.c:2816 msgid "Digital Input (S/PDIF)" msgstr "Digitálny vstup (S/PDIF)" #: spa/plugins/alsa/acp/alsa-mixer.c:2817 msgid "Multichannel Input" msgstr "Viackanálový vstup" #: spa/plugins/alsa/acp/alsa-mixer.c:2818 msgid "Multichannel Output" msgstr "Viackanálový výstup" #: spa/plugins/alsa/acp/alsa-mixer.c:2819 #, fuzzy msgid "Game Output" msgstr "%s výstup" #: spa/plugins/alsa/acp/alsa-mixer.c:2820 #: spa/plugins/alsa/acp/alsa-mixer.c:2821 #, fuzzy msgid "Chat Output" msgstr "%s výstup" #: spa/plugins/alsa/acp/alsa-mixer.c:2822 #, fuzzy msgid "Chat Input" msgstr "%s vstup" #: spa/plugins/alsa/acp/alsa-mixer.c:2823 #, fuzzy msgid "Virtual Surround 7.1" msgstr "Virtuálny priestorový cieľ" #: spa/plugins/alsa/acp/alsa-mixer.c:4527 msgid "Analog Mono" msgstr "Analógový mono" #: spa/plugins/alsa/acp/alsa-mixer.c:4528 #, fuzzy msgid "Analog Mono (Left)" msgstr "Analógový mono" #: spa/plugins/alsa/acp/alsa-mixer.c:4529 #, fuzzy msgid "Analog Mono (Right)" msgstr "Analógový mono" #. Note: Not translated to "Analog Stereo Input", because the source #. * name gets "Input" appended to it automatically, so adding "Input" #. * here would lead to the source name to become "Analog Stereo Input #. * Input". The same logic applies to analog-stereo-output, #. * multichannel-input and multichannel-output. #: spa/plugins/alsa/acp/alsa-mixer.c:4530 #: spa/plugins/alsa/acp/alsa-mixer.c:4538 #: spa/plugins/alsa/acp/alsa-mixer.c:4539 msgid "Analog Stereo" msgstr "Analógový stereo" #: spa/plugins/alsa/acp/alsa-mixer.c:4531 msgid "Mono" msgstr "Mono" #: spa/plugins/alsa/acp/alsa-mixer.c:4532 msgid "Stereo" msgstr "Stereo" #: spa/plugins/alsa/acp/alsa-mixer.c:4540 #: spa/plugins/alsa/acp/alsa-mixer.c:4698 #: spa/plugins/bluez5/bluez5-device.c:1135 msgid "Headset" msgstr "Headset" #: spa/plugins/alsa/acp/alsa-mixer.c:4541 #: spa/plugins/alsa/acp/alsa-mixer.c:4699 #, fuzzy msgid "Speakerphone" msgstr "Reproduktor" #: spa/plugins/alsa/acp/alsa-mixer.c:4542 #: spa/plugins/alsa/acp/alsa-mixer.c:4543 msgid "Multichannel" msgstr "Viackanálový" #: spa/plugins/alsa/acp/alsa-mixer.c:4544 msgid "Analog Surround 2.1" msgstr "Analógový priestorový 2.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4545 msgid "Analog Surround 3.0" msgstr "Analógový priestorový 3.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4546 msgid "Analog Surround 3.1" msgstr "Analógový priestorový 3.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4547 msgid "Analog Surround 4.0" msgstr "Analógový priestorový 4.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4548 msgid "Analog Surround 4.1" msgstr "Analógový priestorový 4.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4549 msgid "Analog Surround 5.0" msgstr "Analógový priestorový 5.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4550 msgid "Analog Surround 5.1" msgstr "Analógový priestorový 5.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4551 msgid "Analog Surround 6.0" msgstr "Analógový priestorový 6.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4552 msgid "Analog Surround 6.1" msgstr "Analógový priestorový 6.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4553 msgid "Analog Surround 7.0" msgstr "Analógový priestorový 7.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4554 msgid "Analog Surround 7.1" msgstr "Analógový priestorový 7.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4555 msgid "Digital Stereo (IEC958)" msgstr "Digitálne stereo (IEC958)" #: spa/plugins/alsa/acp/alsa-mixer.c:4556 msgid "Digital Surround 4.0 (IEC958/AC3)" msgstr "Digitálny priestorový 4.0 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4557 msgid "Digital Surround 5.1 (IEC958/AC3)" msgstr "Digitálny priestorový 5.1 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4558 msgid "Digital Surround 5.1 (IEC958/DTS)" msgstr "Digitálny priestorový 5.1 (IEC958/DTS)" #: spa/plugins/alsa/acp/alsa-mixer.c:4559 msgid "Digital Stereo (HDMI)" msgstr "Digitálny stereo (HDMI)" #: spa/plugins/alsa/acp/alsa-mixer.c:4560 msgid "Digital Surround 5.1 (HDMI)" msgstr "Digitálny priestorový 5.1 (HDMI)" #: spa/plugins/alsa/acp/alsa-mixer.c:4561 msgid "Chat" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4562 msgid "Game" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4696 msgid "Analog Mono Duplex" msgstr "Obojsmerný analógový mono" #: spa/plugins/alsa/acp/alsa-mixer.c:4697 msgid "Analog Stereo Duplex" msgstr "Obojsmerný analógový stereo" #: spa/plugins/alsa/acp/alsa-mixer.c:4700 msgid "Digital Stereo Duplex (IEC958)" msgstr "Obojsmerný digitálny stereo (IEC958)" #: spa/plugins/alsa/acp/alsa-mixer.c:4701 msgid "Multichannel Duplex" msgstr "Obojsmerný viackanálový" #: spa/plugins/alsa/acp/alsa-mixer.c:4702 #, fuzzy msgid "Stereo Duplex" msgstr "Obojsmerný analógový stereo" #: spa/plugins/alsa/acp/alsa-mixer.c:4703 msgid "Mono Chat + 7.1 Surround" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4806 #, c-format msgid "%s Output" msgstr "%s výstup" #: spa/plugins/alsa/acp/alsa-mixer.c:4813 #, c-format msgid "%s Input" msgstr "%s vstup" #: spa/plugins/alsa/acp/alsa-util.c:1175 spa/plugins/alsa/acp/alsa-util.c:1269 #, c-format msgid "" "snd_pcm_avail() returned a value that is exceptionally large: %lu byte (%lu " "ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_avail() returned a value that is exceptionally large: %lu bytes (%lu " "ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" msgstr[1] "" msgstr[2] "" #: spa/plugins/alsa/acp/alsa-util.c:1241 #, c-format msgid "" "snd_pcm_delay() returned a value that is exceptionally large: %li byte (%s" "%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_delay() returned a value that is exceptionally large: %li bytes (%s" "%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" msgstr[1] "" msgstr[2] "" #: spa/plugins/alsa/acp/alsa-util.c:1288 #, c-format msgid "" "snd_pcm_avail_delay() returned strange values: delay %lu is less than avail " "%lu.\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr "" #: spa/plugins/alsa/acp/alsa-util.c:1331 #, c-format msgid "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu byte " "(%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu bytes " "(%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" msgstr[1] "" msgstr[2] "" #: spa/plugins/bluez5/bluez5-device.c:1010 msgid "Audio Gateway (A2DP Source & HSP/HFP AG)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1033 #, c-format msgid "High Fidelity Playback (A2DP Sink, codec %s)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1035 #, c-format msgid "High Fidelity Duplex (A2DP Source/Sink, codec %s)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1041 msgid "High Fidelity Playback (A2DP Sink)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1043 msgid "High Fidelity Duplex (A2DP Source/Sink)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1070 #, c-format msgid "Headset Head Unit (HSP/HFP, codec %s)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1074 msgid "Headset Head Unit (HSP/HFP)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1140 msgid "Handsfree" msgstr "Handsfree" #: spa/plugins/bluez5/bluez5-device.c:1155 msgid "Headphone" msgstr "Slúchadlo" #: spa/plugins/bluez5/bluez5-device.c:1160 msgid "Portable" msgstr "Prenosné" #: spa/plugins/bluez5/bluez5-device.c:1165 msgid "Car" msgstr "Automobil" #: spa/plugins/bluez5/bluez5-device.c:1170 msgid "HiFi" msgstr "HiFi" #: spa/plugins/bluez5/bluez5-device.c:1175 msgid "Phone" msgstr "Telefón" #: spa/plugins/bluez5/bluez5-device.c:1181 #, fuzzy msgid "Bluetooth" msgstr "Vstup cez Bluetooth" pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/po/sl.po000066400000000000000000000605741511204443500221060ustar00rootroot00000000000000# Slovenian translation for PipeWire. # Copyright (C) 2024 PipeWire's COPYRIGHT HOLDER # This file is distributed under the same license as the PipeWire package. # # Martin , 2024, 2025. # msgid "" msgstr "" "Project-Id-Version: PipeWire master\n" "Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/pipewire/-/" "issues\n" "POT-Creation-Date: 2025-11-04 03:33+0000\n" "PO-Revision-Date: 2025-11-08 15:23+0100\n" "Last-Translator: Martin Srebotnjak \n" "Language-Team: Slovenian \n" "Language: sl\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=4; plural=(n%100==1 ? 0 : n%100==2 ? 1 : n%100>=3 && n" "%100<=4 ? 2 : 3);\n" "X-Generator: Poedit 2.2.1\n" #: src/daemon/pipewire.c:29 #, c-format msgid "" "%s [options]\n" " -h, --help Show this help\n" " -v, --verbose Increase verbosity by one level\n" " --version Show version\n" " -c, --config Load config (Default %s)\n" " -P --properties Set context properties\n" msgstr "" "%s [možnosti]\n" " -h, --help Pokaži to pomoč\n" " -v, --verbose Povečaj opisnost za eno raven\n" " --version Pokaži različico\n" " -c, --config Naloži prilagoditev config (privzeto " "%s)\n" " -P --properties Določi lastnosti konteksta\n" #: src/daemon/pipewire.desktop.in:3 msgid "PipeWire Media System" msgstr "Medijski sistem PipeWire" #: src/daemon/pipewire.desktop.in:4 msgid "Start the PipeWire Media System" msgstr "Zaženite medijski sistem PipeWire" #: src/modules/module-protocol-pulse/modules/module-tunnel-sink.c:159 #: src/modules/module-protocol-pulse/modules/module-tunnel-source.c:159 #, c-format msgid "Tunnel to %s%s%s" msgstr "Prehod do %s%s%s" #: src/modules/module-fallback-sink.c:40 msgid "Dummy Output" msgstr "Lažni izhod" #: src/modules/module-pulse-tunnel.c:761 #, c-format msgid "Tunnel for %s@%s" msgstr "Prehod za %s@%s" #: src/modules/module-zeroconf-discover.c:320 msgid "Unknown device" msgstr "Neznana naprava" #: src/modules/module-zeroconf-discover.c:332 #, c-format msgid "%s on %s@%s" msgstr "%s na %s@%s" #: src/modules/module-zeroconf-discover.c:336 #, c-format msgid "%s on %s" msgstr "%s na %s" #: src/tools/pw-cat.c:1096 #, c-format msgid "" "%s [options] [|-]\n" " -h, --help Show this help\n" " --version Show version\n" " -v, --verbose Enable verbose operations\n" "\n" msgstr "" "%s [možnosti] [|-]\n" " -h, --help Pokaži to pomoč\n" " --version Pokaži različico\n" " -v, --verbose Omogoči podrobno opisane operacije\n" "\n" "\n" #: src/tools/pw-cat.c:1103 #, c-format msgid "" " -R, --remote Remote daemon name\n" " --media-type Set media type (default %s)\n" " --media-category Set media category (default %s)\n" " --media-role Set media role (default %s)\n" " --target Set node target serial or name " "(default %s)\n" " 0 means don't link\n" " --latency Set node latency (default %s)\n" " Xunit (unit = s, ms, us, ns)\n" " or direct samples (256)\n" " the rate is the one of the source " "file\n" " -P --properties Set node properties\n" "\n" msgstr "" " -R, --remote Ime oddaljenega demona\n" " --media-type Nastavitev vrste medija (privzeto " "%s)\n" " --media-category Nastavi kategorijo predstavnosti " "(privzeto %s)\n" " --media-role Nastavi vlogo predstavnosti " "(privzeto %s)\n" " --target Nastavi serijsko ali ime ciljnega " "vozlišča (privzeto %s)\n" " 0 pomeni, da se ne poveže\n" " --latency Nastavi zakasnitev vozlišča " "(privzeto %s)\n" " Xunit (enota = s, ms, us, ns)\n" " ali neposredni vzorci (256)\n" " Hitrost je enaka tisti v izvornih " "datotekah\n" " -P --properties Nastavi lastnosti vozlišča\n" "\n" #: src/tools/pw-cat.c:1121 #, c-format msgid "" " --rate Sample rate (req. for rec) (default " "%u)\n" " --channels Number of channels (req. for rec) " "(default %u)\n" " --channel-map Channel map\n" " one of: \"stereo\", " "\"surround-51\",... or\n" " comma separated list of channel " "names: eg. \"FL,FR\"\n" " --format Sample format %s (req. for rec) " "(default %s)\n" " --volume Stream volume 0-1.0 (default %.3f)\n" " -q --quality Resampler quality (0 - 15) (default " "%d)\n" " -a, --raw RAW mode\n" " -M, --force-midi Force midi format, one of \"midi\" " "or \"ump\", (default ump)\n" " -n, --sample-count COUNT Stop after COUNT samples\n" "\n" msgstr "" " --rate Mera vzorčenja (zaht. za rec) " "(privzeto %u)\n" " --channels Število kanalov (zaht. za snemanje) " "(privzeto %u)\n" " --channel-map Preslikava kanalov\n" " Ena izmed: \"stereo\", " "\"surround-51\",... ali\n" " seznam imen kanalov, ločen z " "vejico: npr. \"FL,FR\"\n" " --format Vzorčne oblike zapisa %s (zahtevano " "za rec) (privzeto %s)\n" " --volume Glasnost toka 0-1.0 (privzeto %.3f)\n" " -q --quality Kakovost prevzorčenja (0 - 15) " "(privzeto %d)\n" " -a, --raw neobdelan način (RAW)\n" " -M, --force-midi Vsili zapis midi, eden izmed \"midi" "\" ali \"ump\" (privzeto ump)\n" " -n, --sample-count ŠTEVEC Ustavi po ŠTEVEC vzorcih\n" "\n" #: src/tools/pw-cat.c:1141 msgid "" " -p, --playback Playback mode\n" " -r, --record Recording mode\n" " -m, --midi Midi mode\n" " -d, --dsd DSD mode\n" " -o, --encoded Encoded mode\n" " -s, --sysex SysEx mode\n" " -c, --midi-clip MIDI clip mode\n" "\n" msgstr "" " -p, --playback Način predvajanja\n" " -r, --record Način snemanja\n" " -m, --midi Midi način\n" " -d, --dsd Način DSD\n" " -o, --encoded Kodiran način\n" " -s, --sysex Način SysEx\n" " -c, --midi-clip Način posnetka MIDI\n" "\n" #: src/tools/pw-cli.c:2386 #, c-format msgid "" "%s [options] [command]\n" " -h, --help Show this help\n" " --version Show version\n" " -d, --daemon Start as daemon (Default false)\n" " -r, --remote Remote daemon name\n" " -m, --monitor Monitor activity\n" "\n" msgstr "" "%s [možnosti] [ukaz]\n" " -h, --help Pokaži to pomoč\n" " --version Pokaži različico\n" " -d, --daemon Začni kot zaledni proces (privzeto " "false)\n" " -r, --remote Ime oddaljenega zalednega procesa\n" " -m, --monitor Spremljaj dejavnosti\n" "\n" #: spa/plugins/alsa/acp/acp.c:361 msgid "Pro Audio" msgstr "Profesionalni zvok" #: spa/plugins/alsa/acp/acp.c:537 spa/plugins/alsa/acp/alsa-mixer.c:4699 #: spa/plugins/bluez5/bluez5-device.c:1976 msgid "Off" msgstr "Izklopljeno" #: spa/plugins/alsa/acp/acp.c:620 #, c-format msgid "%s [ALSA UCM error]" msgstr "%s [napaka ALSA UCM]" #: spa/plugins/alsa/acp/alsa-mixer.c:2721 msgid "Input" msgstr "Vhod" #: spa/plugins/alsa/acp/alsa-mixer.c:2722 msgid "Docking Station Input" msgstr "Vhod priklopne postaje" #: spa/plugins/alsa/acp/alsa-mixer.c:2723 msgid "Docking Station Microphone" msgstr "Mikrofon priklopne postaje" #: spa/plugins/alsa/acp/alsa-mixer.c:2724 msgid "Docking Station Line In" msgstr "Linijski vhod priklopne postaje" #: spa/plugins/alsa/acp/alsa-mixer.c:2725 #: spa/plugins/alsa/acp/alsa-mixer.c:2816 msgid "Line In" msgstr "Linijski vhod" #: spa/plugins/alsa/acp/alsa-mixer.c:2726 #: spa/plugins/alsa/acp/alsa-mixer.c:2810 #: spa/plugins/bluez5/bluez5-device.c:2374 msgid "Microphone" msgstr "Mikrofon" #: spa/plugins/alsa/acp/alsa-mixer.c:2727 #: spa/plugins/alsa/acp/alsa-mixer.c:2811 msgid "Front Microphone" msgstr "Sprednji mikrofon" #: spa/plugins/alsa/acp/alsa-mixer.c:2728 #: spa/plugins/alsa/acp/alsa-mixer.c:2812 msgid "Rear Microphone" msgstr "Zadnji mikrofon" #: spa/plugins/alsa/acp/alsa-mixer.c:2729 msgid "External Microphone" msgstr "Zunanji mikrofon" #: spa/plugins/alsa/acp/alsa-mixer.c:2730 #: spa/plugins/alsa/acp/alsa-mixer.c:2814 msgid "Internal Microphone" msgstr "Notranji mikrofon" #: spa/plugins/alsa/acp/alsa-mixer.c:2731 #: spa/plugins/alsa/acp/alsa-mixer.c:2817 msgid "Radio" msgstr "Radio" #: spa/plugins/alsa/acp/alsa-mixer.c:2732 #: spa/plugins/alsa/acp/alsa-mixer.c:2818 msgid "Video" msgstr "Video" #: spa/plugins/alsa/acp/alsa-mixer.c:2733 msgid "Automatic Gain Control" msgstr "Samodejni nadzor ojačanja" #: spa/plugins/alsa/acp/alsa-mixer.c:2734 msgid "No Automatic Gain Control" msgstr "Brez samodejnega nadzora ojačanja" #: spa/plugins/alsa/acp/alsa-mixer.c:2735 msgid "Boost" msgstr "Ojačitev" #: spa/plugins/alsa/acp/alsa-mixer.c:2736 msgid "No Boost" msgstr "Brez ojačitve" #: spa/plugins/alsa/acp/alsa-mixer.c:2737 msgid "Amplifier" msgstr "Ojačevalnik" #: spa/plugins/alsa/acp/alsa-mixer.c:2738 msgid "No Amplifier" msgstr "Brez ojačevalnika" #: spa/plugins/alsa/acp/alsa-mixer.c:2739 msgid "Bass Boost" msgstr "Ojačitev nizkih tonov" #: spa/plugins/alsa/acp/alsa-mixer.c:2740 msgid "No Bass Boost" msgstr "Brez ojačitve nizkih tonov" #: spa/plugins/alsa/acp/alsa-mixer.c:2741 #: spa/plugins/bluez5/bluez5-device.c:2380 msgid "Speaker" msgstr "Zvočnik" #. Don't call it "headset", the HF one has the mic #: spa/plugins/alsa/acp/alsa-mixer.c:2742 #: spa/plugins/alsa/acp/alsa-mixer.c:2820 #: spa/plugins/bluez5/bluez5-device.c:2386 #: spa/plugins/bluez5/bluez5-device.c:2453 msgid "Headphones" msgstr "Slušalke" #: spa/plugins/alsa/acp/alsa-mixer.c:2809 msgid "Analog Input" msgstr "Analogni vhod" #: spa/plugins/alsa/acp/alsa-mixer.c:2813 msgid "Dock Microphone" msgstr "Priklopni mikrofon" #: spa/plugins/alsa/acp/alsa-mixer.c:2815 msgid "Headset Microphone" msgstr "Mikrofon s slušalkami" #: spa/plugins/alsa/acp/alsa-mixer.c:2819 msgid "Analog Output" msgstr "Analogni izhod" #: spa/plugins/alsa/acp/alsa-mixer.c:2821 msgid "Headphones 2" msgstr "Slušalke 2" #: spa/plugins/alsa/acp/alsa-mixer.c:2822 msgid "Headphones Mono Output" msgstr "Mono izhod slušalk" #: spa/plugins/alsa/acp/alsa-mixer.c:2823 msgid "Line Out" msgstr "Linijsk izhod" #: spa/plugins/alsa/acp/alsa-mixer.c:2824 msgid "Analog Mono Output" msgstr "Analogni mono izhod" #: spa/plugins/alsa/acp/alsa-mixer.c:2825 msgid "Speakers" msgstr "Govorniki" #: spa/plugins/alsa/acp/alsa-mixer.c:2826 msgid "HDMI / DisplayPort" msgstr "HDMI / DisplayPort" #: spa/plugins/alsa/acp/alsa-mixer.c:2827 msgid "Digital Output (S/PDIF)" msgstr "Digitalni izhod (S/PDIF)" #: spa/plugins/alsa/acp/alsa-mixer.c:2828 msgid "Digital Input (S/PDIF)" msgstr "Digitalni vhod (S/PDIF)" #: spa/plugins/alsa/acp/alsa-mixer.c:2829 msgid "Multichannel Input" msgstr "Večkanalni vhod" #: spa/plugins/alsa/acp/alsa-mixer.c:2830 msgid "Multichannel Output" msgstr "Večkanalni izhod" #: spa/plugins/alsa/acp/alsa-mixer.c:2831 msgid "Game Output" msgstr "Vhod igre" #: spa/plugins/alsa/acp/alsa-mixer.c:2832 #: spa/plugins/alsa/acp/alsa-mixer.c:2833 msgid "Chat Output" msgstr "Izhod klepeta" #: spa/plugins/alsa/acp/alsa-mixer.c:2834 msgid "Chat Input" msgstr "Vhod klepeta" #: spa/plugins/alsa/acp/alsa-mixer.c:2835 msgid "Virtual Surround 7.1" msgstr "Navidezni prostorski zvok 7.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4522 msgid "Analog Mono" msgstr "Analogni mono" #: spa/plugins/alsa/acp/alsa-mixer.c:4523 msgid "Analog Mono (Left)" msgstr "Analogni mono (levo)" #: spa/plugins/alsa/acp/alsa-mixer.c:4524 msgid "Analog Mono (Right)" msgstr "Analogni mono (desno)" #. Note: Not translated to "Analog Stereo Input", because the source #. * name gets "Input" appended to it automatically, so adding "Input" #. * here would lead to the source name to become "Analog Stereo Input #. * Input". The same logic applies to analog-stereo-output, #. * multichannel-input and multichannel-output. #: spa/plugins/alsa/acp/alsa-mixer.c:4525 #: spa/plugins/alsa/acp/alsa-mixer.c:4533 #: spa/plugins/alsa/acp/alsa-mixer.c:4534 msgid "Analog Stereo" msgstr "Analogni stereo" #: spa/plugins/alsa/acp/alsa-mixer.c:4526 msgid "Mono" msgstr "Mono" #: spa/plugins/alsa/acp/alsa-mixer.c:4527 msgid "Stereo" msgstr "Stereo" #: spa/plugins/alsa/acp/alsa-mixer.c:4535 #: spa/plugins/alsa/acp/alsa-mixer.c:4693 #: spa/plugins/bluez5/bluez5-device.c:2362 msgid "Headset" msgstr "Slušalka" #: spa/plugins/alsa/acp/alsa-mixer.c:4536 #: spa/plugins/alsa/acp/alsa-mixer.c:4694 msgid "Speakerphone" msgstr "Zvočnik telefona" #: spa/plugins/alsa/acp/alsa-mixer.c:4537 #: spa/plugins/alsa/acp/alsa-mixer.c:4538 msgid "Multichannel" msgstr "Večkanalno" #: spa/plugins/alsa/acp/alsa-mixer.c:4539 msgid "Analog Surround 2.1" msgstr "Analogni prostorski zvok 2.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4540 msgid "Analog Surround 3.0" msgstr "Analogni prostorski zvok 3.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4541 msgid "Analog Surround 3.1" msgstr "Analogni prostorski zvok 3.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4542 msgid "Analog Surround 4.0" msgstr "Analogni prostorski zvok 4.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4543 msgid "Analog Surround 4.1" msgstr "Analogni prostorski zvok 4.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4544 msgid "Analog Surround 5.0" msgstr "Analogni prostorski zvok 5.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4545 msgid "Analog Surround 5.1" msgstr "Analogni prostorski zvok 5.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4546 msgid "Analog Surround 6.0" msgstr "Analogni prostorski zvok 6.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4547 msgid "Analog Surround 6.1" msgstr "Analogni prostorski zvok 6.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4548 msgid "Analog Surround 7.0" msgstr "Analogni prostorski zvok 7.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4549 msgid "Analog Surround 7.1" msgstr "Analogni prostorski zvok 7.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4550 msgid "Digital Stereo (IEC958)" msgstr "Digitalni stereo (IEC958)" #: spa/plugins/alsa/acp/alsa-mixer.c:4551 msgid "Digital Surround 4.0 (IEC958/AC3)" msgstr "Digitalni prostorski zvok 4.0 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4552 msgid "Digital Surround 5.1 (IEC958/AC3)" msgstr "Digitalni prostorski zvok 5.1 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4553 msgid "Digital Surround 5.1 (IEC958/DTS)" msgstr "Digitalni prostorski zvok 5.1 (IEC958/DTS)" #: spa/plugins/alsa/acp/alsa-mixer.c:4554 msgid "Digital Stereo (HDMI)" msgstr "Digitalni stereo (HDMI)" #: spa/plugins/alsa/acp/alsa-mixer.c:4555 msgid "Digital Surround 5.1 (HDMI)" msgstr "Digitalni prostorski zvok 5.1 (HDMI)" #: spa/plugins/alsa/acp/alsa-mixer.c:4556 msgid "Chat" msgstr "Klepet" #: spa/plugins/alsa/acp/alsa-mixer.c:4557 msgid "Game" msgstr "Igra" #: spa/plugins/alsa/acp/alsa-mixer.c:4691 msgid "Analog Mono Duplex" msgstr "Analogni mono dupleks" #: spa/plugins/alsa/acp/alsa-mixer.c:4692 msgid "Analog Stereo Duplex" msgstr "Analogni stereo dupleks" #: spa/plugins/alsa/acp/alsa-mixer.c:4695 msgid "Digital Stereo Duplex (IEC958)" msgstr "Digitalni stereo dupleks (IEC958)" #: spa/plugins/alsa/acp/alsa-mixer.c:4696 msgid "Multichannel Duplex" msgstr "Večkanalni dupleks" #: spa/plugins/alsa/acp/alsa-mixer.c:4697 msgid "Stereo Duplex" msgstr "Stereo dupleks" #: spa/plugins/alsa/acp/alsa-mixer.c:4698 msgid "Mono Chat + 7.1 Surround" msgstr "Mono klepet + prostorski zvok 7.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4799 #, c-format msgid "%s Output" msgstr "Izhod %s" #: spa/plugins/alsa/acp/alsa-mixer.c:4807 #, c-format msgid "%s Input" msgstr "Vhod %s" #: spa/plugins/alsa/acp/alsa-util.c:1233 spa/plugins/alsa/acp/alsa-util.c:1327 #, c-format msgid "" "snd_pcm_avail() returned a value that is exceptionally large: %lu byte (%lu " "ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_avail() returned a value that is exceptionally large: %lu bytes (%lu " "ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "snd_pcm_avail() je vrnil vrednost, ki je izjemno velika: %lu bajt (%lu ms).\n" "Najverjetneje je to napaka v gonilniku ALSA »%s«. O tej težavi obvestite " "razvijalce ALSA." msgstr[1] "" "snd_pcm_avail() je vrnil vrednost, ki je izjemno velika: %lu bajta (%lu " "ms).\n" "Najverjetneje je to napaka v gonilniku ALSA »%s«. O tej težavi obvestite " "razvijalce ALSA." msgstr[2] "" "snd_pcm_avail() je vrnil vrednost, ki je izjemno velika: %lu bajti (%lu " "ms).\n" "Najverjetneje je to napaka v gonilniku ALSA »%s«. O tej težavi obvestite " "razvijalce ALSA." msgstr[3] "" "snd_pcm_avail() je vrnil vrednost, ki je izjemno velika: %lu bajtov (%lu " "ms).\n" "Najverjetneje je to napaka v gonilniku ALSA »%s«. O tej težavi obvestite " "razvijalce ALSA." #: spa/plugins/alsa/acp/alsa-util.c:1299 #, c-format msgid "" "snd_pcm_delay() returned a value that is exceptionally large: %li byte (%s" "%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_delay() returned a value that is exceptionally large: %li bytes (%s" "%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "snd_pcm_delay() je vrnil vrednost, ki je izjemno velika: %li bajt (%s%lu " "ms).\n" "Najverjetneje je to napaka v gonilniku ALSA »%s«. O tej težavi obvestite " "razvijalce ALSA." msgstr[1] "" "snd_pcm_delay() je vrnil vrednost, ki je izjemno velika: %li bajta (%s%lu " "ms).\n" "Najverjetneje je to napaka v gonilniku ALSA »%s«. O tej težavi obvestite " "razvijalce ALSA." msgstr[2] "" "snd_pcm_delay() je vrnil vrednost, ki je izjemno velika: %li bajti (%s%lu " "ms).\n" "Najverjetneje je to napaka v gonilniku ALSA »%s«. O tej težavi obvestite " "razvijalce ALSA." msgstr[3] "" "snd_pcm_delay() je vrnil vrednost, ki je izjemno velika: %li bajtov (%s%lu " "ms).\n" "Najverjetneje je to napaka v gonilniku ALSA »%s«. O tej težavi obvestite " "razvijalce ALSA." #: spa/plugins/alsa/acp/alsa-util.c:1346 #, c-format msgid "" "snd_pcm_avail_delay() returned strange values: delay %lu is less than avail " "%lu.\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr "" "snd_pcm_avail_delay() je vrnil nenavadne vrednosti: zakasnitev %lu je manjša " "kot razpoložljiva %lu.\n" "Najverjetneje je to napaka v gonilniku ALSA »%s«. O tej težavi obvestite " "razvijalce ALSA." #: spa/plugins/alsa/acp/alsa-util.c:1389 #, c-format msgid "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu byte " "(%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu bytes " "(%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "snd_pcm_mmap_begin() je vrnil vrednost, ki je izjemno velika: %lu bajt (%lu " "ms).\n" "Najverjetneje je to napaka v gonilniku ALSA »%s«. O tej težavi obvestite " "razvijalce ALSA." msgstr[1] "" "snd_pcm_mmap_begin() je vrnil vrednost, ki je izjemno velika: %lu bajta (%lu " "ms).\n" "Najverjetneje je to napaka v gonilniku ALSA »%s«. O tej težavi obvestite " "razvijalce ALSA." msgstr[2] "" "snd_pcm_mmap_begin() je vrnil vrednost, ki je izjemno velika: %lu bajti (%lu " "ms).\n" "Najverjetneje je to napaka v gonilniku ALSA »%s«. O tej težavi obvestite " "razvijalce ALSA." msgstr[3] "" "snd_pcm_mmap_begin() je vrnil vrednost, ki je izjemno velika: %lu bajtov " "(%lu ms).\n" "Najverjetneje je to napaka v gonilniku ALSA »%s«. O tej težavi obvestite " "razvijalce ALSA." #: spa/plugins/alsa/acp/channelmap.h:460 msgid "(invalid)" msgstr "(neveljavno)" #: spa/plugins/alsa/acp/compat.c:194 msgid "Built-in Audio" msgstr "Vgrajen zvok" #: spa/plugins/alsa/acp/compat.c:199 msgid "Modem" msgstr "Modem" #: spa/plugins/bluez5/bluez5-device.c:1987 msgid "Audio Gateway (A2DP Source & HSP/HFP AG)" msgstr "Zvožni prehod (vir A2DP in HSP/HFP AG)" #: spa/plugins/bluez5/bluez5-device.c:2016 msgid "Audio Streaming for Hearing Aids (ASHA Sink)" msgstr "Pretakanje zvoka za slušne aparate (ponor ASHA)" #: spa/plugins/bluez5/bluez5-device.c:2059 #, c-format msgid "High Fidelity Playback (A2DP Sink, codec %s)" msgstr "Predvajanje visoke ločljivosti (ponor A2DP, kodek %s)" #: spa/plugins/bluez5/bluez5-device.c:2062 #, c-format msgid "High Fidelity Duplex (A2DP Source/Sink, codec %s)" msgstr "Dupleks visoke ločljivosti (vir/ponor A2DP, kodek %s)" #: spa/plugins/bluez5/bluez5-device.c:2070 msgid "High Fidelity Playback (A2DP Sink)" msgstr "Predvajanje visoke ločljivosti (ponor A2DP)" #: spa/plugins/bluez5/bluez5-device.c:2072 msgid "High Fidelity Duplex (A2DP Source/Sink)" msgstr "Dupleks visoke ločljivosti (vir/ponor A2DP)" #: spa/plugins/bluez5/bluez5-device.c:2146 #, c-format msgid "High Fidelity Playback (BAP Sink, codec %s)" msgstr "Predvajanje visoke ločljivosti (ponor BAP, kodek %s)" #: spa/plugins/bluez5/bluez5-device.c:2151 #, c-format msgid "High Fidelity Input (BAP Source, codec %s)" msgstr "Vhod visoke ločljivosti (vir BAP, kodek %s)" #: spa/plugins/bluez5/bluez5-device.c:2155 #, c-format msgid "High Fidelity Duplex (BAP Source/Sink, codec %s)" msgstr "Dupleks visoke ločljivosti (vir/ponor BAP, kodek %s)" #: spa/plugins/bluez5/bluez5-device.c:2164 msgid "High Fidelity Playback (BAP Sink)" msgstr "Predvajanje visoke ločljivosti (ponor BAP)" #: spa/plugins/bluez5/bluez5-device.c:2168 msgid "High Fidelity Input (BAP Source)" msgstr "Vhod visoke ločljivosti (vir BAP)" #: spa/plugins/bluez5/bluez5-device.c:2171 msgid "High Fidelity Duplex (BAP Source/Sink)" msgstr "Dupleks visoke ločljivosti (vir/ponor BAP)" #: spa/plugins/bluez5/bluez5-device.c:2211 #, c-format msgid "Headset Head Unit (HSP/HFP, codec %s)" msgstr "Naglavna enota slušalk (HSP/HFP, kodek %s)" #: spa/plugins/bluez5/bluez5-device.c:2363 #: spa/plugins/bluez5/bluez5-device.c:2368 #: spa/plugins/bluez5/bluez5-device.c:2375 #: spa/plugins/bluez5/bluez5-device.c:2381 #: spa/plugins/bluez5/bluez5-device.c:2387 #: spa/plugins/bluez5/bluez5-device.c:2393 #: spa/plugins/bluez5/bluez5-device.c:2399 #: spa/plugins/bluez5/bluez5-device.c:2405 #: spa/plugins/bluez5/bluez5-device.c:2411 msgid "Handsfree" msgstr "Prostoročno telefoniranje" #: spa/plugins/bluez5/bluez5-device.c:2369 msgid "Handsfree (HFP)" msgstr "Prostoročno telefoniranje (HFP)" #: spa/plugins/bluez5/bluez5-device.c:2392 msgid "Portable" msgstr "Prenosna naprava" #: spa/plugins/bluez5/bluez5-device.c:2398 msgid "Car" msgstr "Avtomobil" #: spa/plugins/bluez5/bluez5-device.c:2404 msgid "HiFi" msgstr "HiFi" #: spa/plugins/bluez5/bluez5-device.c:2410 msgid "Phone" msgstr "Telefon" #: spa/plugins/bluez5/bluez5-device.c:2417 msgid "Bluetooth" msgstr "Bluetooth" #: spa/plugins/bluez5/bluez5-device.c:2418 msgid "Bluetooth Handsfree" msgstr "Bluetooth - prostoročno" pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/po/sr.po000066400000000000000000000473761511204443500221210ustar00rootroot00000000000000# Serbian translations for pipewire # Copyright (C) 2006 Lennart Poettering # This file is distributed under the same license as the pipewire package. # Igor Miletic (Игор Милетић) , 2009. # Miloš Komarčević , 2009, 2012. # msgid "" msgstr "" "Project-Id-Version: pipewire\n" "Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/pipewire/" "issues/new\n" "POT-Creation-Date: 2021-04-18 16:54+0800\n" "PO-Revision-Date: 2012-01-30 09:55+0000\n" "Last-Translator: Miloš Komarčević \n" "Language-Team: Serbian (sr) \n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" "%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" #: src/daemon/pipewire.c:43 #, c-format msgid "" "%s [options]\n" " -h, --help Show this help\n" " --version Show version\n" " -c, --config Load config (Default %s)\n" msgstr "" #: src/daemon/pipewire.desktop.in:4 msgid "PipeWire Media System" msgstr "" #: src/daemon/pipewire.desktop.in:5 msgid "Start the PipeWire Media System" msgstr "" #: src/examples/media-session/alsa-monitor.c:526 #: spa/plugins/alsa/acp/compat.c:187 msgid "Built-in Audio" msgstr "Унутрашњи звук" #: src/examples/media-session/alsa-monitor.c:530 #: spa/plugins/alsa/acp/compat.c:192 msgid "Modem" msgstr "Модем" #: src/examples/media-session/alsa-monitor.c:539 msgid "Unknown device" msgstr "" #: src/tools/pw-cat.c:991 #, c-format msgid "" "%s [options] \n" " -h, --help Show this help\n" " --version Show version\n" " -v, --verbose Enable verbose operations\n" "\n" msgstr "" #: src/tools/pw-cat.c:998 #, c-format msgid "" " -R, --remote Remote daemon name\n" " --media-type Set media type (default %s)\n" " --media-category Set media category (default %s)\n" " --media-role Set media role (default %s)\n" " --target Set node target (default %s)\n" " 0 means don't link\n" " --latency Set node latency (default %s)\n" " Xunit (unit = s, ms, us, ns)\n" " or direct samples (256)\n" " the rate is the one of the source " "file\n" " --list-targets List available targets for --target\n" "\n" msgstr "" #: src/tools/pw-cat.c:1016 #, c-format msgid "" " --rate Sample rate (req. for rec) (default " "%u)\n" " --channels Number of channels (req. for rec) " "(default %u)\n" " --channel-map Channel map\n" " one of: \"stereo\", " "\"surround-51\",... or\n" " comma separated list of channel " "names: eg. \"FL,FR\"\n" " --format Sample format %s (req. for rec) " "(default %s)\n" " --volume Stream volume 0-1.0 (default %.3f)\n" " -q --quality Resampler quality (0 - 15) (default " "%d)\n" "\n" msgstr "" #: src/tools/pw-cat.c:1033 msgid "" " -p, --playback Playback mode\n" " -r, --record Recording mode\n" " -m, --midi Midi mode\n" "\n" msgstr "" #: src/tools/pw-cli.c:2932 #, c-format msgid "" "%s [options] [command]\n" " -h, --help Show this help\n" " --version Show version\n" " -d, --daemon Start as daemon (Default false)\n" " -r, --remote Remote daemon name\n" "\n" msgstr "" #: spa/plugins/alsa/acp/acp.c:290 msgid "Pro Audio" msgstr "" #: spa/plugins/alsa/acp/acp.c:411 spa/plugins/alsa/acp/alsa-mixer.c:4704 #: spa/plugins/bluez5/bluez5-device.c:1000 msgid "Off" msgstr "Искључено" #: spa/plugins/alsa/acp/channelmap.h:466 msgid "(invalid)" msgstr "(неисправно)" #: spa/plugins/alsa/acp/alsa-mixer.c:2709 msgid "Input" msgstr "Улаз" #: spa/plugins/alsa/acp/alsa-mixer.c:2710 msgid "Docking Station Input" msgstr "Улаз прикључне станице" #: spa/plugins/alsa/acp/alsa-mixer.c:2711 #, fuzzy msgid "Docking Station Microphone" msgstr "Микрофон прикључне станице" #: spa/plugins/alsa/acp/alsa-mixer.c:2712 #, fuzzy msgid "Docking Station Line In" msgstr "Улаз прикључне станице" #: spa/plugins/alsa/acp/alsa-mixer.c:2713 #: spa/plugins/alsa/acp/alsa-mixer.c:2804 msgid "Line In" msgstr "Линија у" #: spa/plugins/alsa/acp/alsa-mixer.c:2714 #: spa/plugins/alsa/acp/alsa-mixer.c:2798 #: spa/plugins/bluez5/bluez5-device.c:1145 msgid "Microphone" msgstr "Микрофон" #: spa/plugins/alsa/acp/alsa-mixer.c:2715 #: spa/plugins/alsa/acp/alsa-mixer.c:2799 #, fuzzy msgid "Front Microphone" msgstr "Микрофон прикључне станице" #: spa/plugins/alsa/acp/alsa-mixer.c:2716 #: spa/plugins/alsa/acp/alsa-mixer.c:2800 #, fuzzy msgid "Rear Microphone" msgstr "Микрофон" #: spa/plugins/alsa/acp/alsa-mixer.c:2717 msgid "External Microphone" msgstr "Спољни микрофон" #: spa/plugins/alsa/acp/alsa-mixer.c:2718 #: spa/plugins/alsa/acp/alsa-mixer.c:2802 msgid "Internal Microphone" msgstr "Унутрашњи микрофон" #: spa/plugins/alsa/acp/alsa-mixer.c:2719 #: spa/plugins/alsa/acp/alsa-mixer.c:2805 msgid "Radio" msgstr "Радио" #: spa/plugins/alsa/acp/alsa-mixer.c:2720 #: spa/plugins/alsa/acp/alsa-mixer.c:2806 msgid "Video" msgstr "Видео" #: spa/plugins/alsa/acp/alsa-mixer.c:2721 msgid "Automatic Gain Control" msgstr "Самостална контрола појачања" #: spa/plugins/alsa/acp/alsa-mixer.c:2722 msgid "No Automatic Gain Control" msgstr "Без самосталне контроле појачања" #: spa/plugins/alsa/acp/alsa-mixer.c:2723 msgid "Boost" msgstr "Подизање" #: spa/plugins/alsa/acp/alsa-mixer.c:2724 msgid "No Boost" msgstr "Без подизања" #: spa/plugins/alsa/acp/alsa-mixer.c:2725 msgid "Amplifier" msgstr "Појачало" #: spa/plugins/alsa/acp/alsa-mixer.c:2726 msgid "No Amplifier" msgstr "Без појачала" #: spa/plugins/alsa/acp/alsa-mixer.c:2727 #, fuzzy msgid "Bass Boost" msgstr "Подизање" #: spa/plugins/alsa/acp/alsa-mixer.c:2728 #, fuzzy msgid "No Bass Boost" msgstr "Без подизања" #: spa/plugins/alsa/acp/alsa-mixer.c:2729 #: spa/plugins/bluez5/bluez5-device.c:1150 msgid "Speaker" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2730 #: spa/plugins/alsa/acp/alsa-mixer.c:2808 msgid "Headphones" msgstr "Аналогне слушалице" #: spa/plugins/alsa/acp/alsa-mixer.c:2797 msgid "Analog Input" msgstr "Аналогни улаз" #: spa/plugins/alsa/acp/alsa-mixer.c:2801 msgid "Dock Microphone" msgstr "Микрофон прикључне станице" #: spa/plugins/alsa/acp/alsa-mixer.c:2803 #, fuzzy msgid "Headset Microphone" msgstr "Микрофон" #: spa/plugins/alsa/acp/alsa-mixer.c:2807 msgid "Analog Output" msgstr "Аналогни излаз" #: spa/plugins/alsa/acp/alsa-mixer.c:2809 #, fuzzy msgid "Headphones 2" msgstr "Аналогне слушалице" #: spa/plugins/alsa/acp/alsa-mixer.c:2810 #, fuzzy msgid "Headphones Mono Output" msgstr "Аналогни моно излаз" #: spa/plugins/alsa/acp/alsa-mixer.c:2811 #, fuzzy msgid "Line Out" msgstr "Линија у" #: spa/plugins/alsa/acp/alsa-mixer.c:2812 msgid "Analog Mono Output" msgstr "Аналогни моно излаз" #: spa/plugins/alsa/acp/alsa-mixer.c:2813 #, fuzzy msgid "Speakers" msgstr "Аналогни стерео" #: spa/plugins/alsa/acp/alsa-mixer.c:2814 msgid "HDMI / DisplayPort" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2815 #, fuzzy msgid "Digital Output (S/PDIF)" msgstr "Дигитални стерео (HDMI)" #: spa/plugins/alsa/acp/alsa-mixer.c:2816 #, fuzzy msgid "Digital Input (S/PDIF)" msgstr "Дигитални стерео (HDMI)" #: spa/plugins/alsa/acp/alsa-mixer.c:2817 msgid "Multichannel Input" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2818 #, fuzzy msgid "Multichannel Output" msgstr "Празан излаз" #: spa/plugins/alsa/acp/alsa-mixer.c:2819 #, fuzzy msgid "Game Output" msgstr "Празан излаз" #: spa/plugins/alsa/acp/alsa-mixer.c:2820 #: spa/plugins/alsa/acp/alsa-mixer.c:2821 #, fuzzy msgid "Chat Output" msgstr "Празан излаз" #: spa/plugins/alsa/acp/alsa-mixer.c:2822 #, fuzzy msgid "Chat Input" msgstr "Улаз" #: spa/plugins/alsa/acp/alsa-mixer.c:2823 #, fuzzy msgid "Virtual Surround 7.1" msgstr "Аналогни окружујући 7.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4527 msgid "Analog Mono" msgstr "Аналогни моно" #: spa/plugins/alsa/acp/alsa-mixer.c:4528 #, fuzzy msgid "Analog Mono (Left)" msgstr "Аналогни моно" #: spa/plugins/alsa/acp/alsa-mixer.c:4529 #, fuzzy msgid "Analog Mono (Right)" msgstr "Аналогни моно" #. Note: Not translated to "Analog Stereo Input", because the source #. * name gets "Input" appended to it automatically, so adding "Input" #. * here would lead to the source name to become "Analog Stereo Input #. * Input". The same logic applies to analog-stereo-output, #. * multichannel-input and multichannel-output. #: spa/plugins/alsa/acp/alsa-mixer.c:4530 #: spa/plugins/alsa/acp/alsa-mixer.c:4538 #: spa/plugins/alsa/acp/alsa-mixer.c:4539 msgid "Analog Stereo" msgstr "Аналогни стерео" #: spa/plugins/alsa/acp/alsa-mixer.c:4531 msgid "Mono" msgstr "Моно" #: spa/plugins/alsa/acp/alsa-mixer.c:4532 msgid "Stereo" msgstr "Стерео" #: spa/plugins/alsa/acp/alsa-mixer.c:4540 #: spa/plugins/alsa/acp/alsa-mixer.c:4698 #: spa/plugins/bluez5/bluez5-device.c:1135 msgid "Headset" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4541 #: spa/plugins/alsa/acp/alsa-mixer.c:4699 #, fuzzy msgid "Speakerphone" msgstr "Аналогни стерео" #: spa/plugins/alsa/acp/alsa-mixer.c:4542 #: spa/plugins/alsa/acp/alsa-mixer.c:4543 msgid "Multichannel" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4544 msgid "Analog Surround 2.1" msgstr "Аналогни окружујући 2.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4545 msgid "Analog Surround 3.0" msgstr "Аналогни окружујући 3.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4546 msgid "Analog Surround 3.1" msgstr "Аналогни окружујући 3.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4547 msgid "Analog Surround 4.0" msgstr "Аналогни окружујући 4.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4548 msgid "Analog Surround 4.1" msgstr "Аналогни окружујући 4.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4549 msgid "Analog Surround 5.0" msgstr "Аналогни окружујући 5.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4550 msgid "Analog Surround 5.1" msgstr "Аналогни окружујући 5.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4551 msgid "Analog Surround 6.0" msgstr "Аналогни окружујући 6.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4552 msgid "Analog Surround 6.1" msgstr "Аналогни окружујући 6.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4553 msgid "Analog Surround 7.0" msgstr "Аналогни окружујући 7.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4554 msgid "Analog Surround 7.1" msgstr "Аналогни окружујући 7.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4555 msgid "Digital Stereo (IEC958)" msgstr "Дигитални стерео (IEC958)" #: spa/plugins/alsa/acp/alsa-mixer.c:4556 msgid "Digital Surround 4.0 (IEC958/AC3)" msgstr "Дигитални окружујући 4.0 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4557 msgid "Digital Surround 5.1 (IEC958/AC3)" msgstr "Дигитални окружујући 5.1 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4558 #, fuzzy msgid "Digital Surround 5.1 (IEC958/DTS)" msgstr "Дигитални окружујући 5.1 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4559 msgid "Digital Stereo (HDMI)" msgstr "Дигитални стерео (HDMI)" #: spa/plugins/alsa/acp/alsa-mixer.c:4560 #, fuzzy msgid "Digital Surround 5.1 (HDMI)" msgstr "Дигитални окружујући 5.1 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4561 msgid "Chat" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4562 msgid "Game" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4696 msgid "Analog Mono Duplex" msgstr "Двосмерни аналогни моно" #: spa/plugins/alsa/acp/alsa-mixer.c:4697 msgid "Analog Stereo Duplex" msgstr "Двосмерни аналогни стерео" #: spa/plugins/alsa/acp/alsa-mixer.c:4700 msgid "Digital Stereo Duplex (IEC958)" msgstr "Двосмерни дигитални стерео (IEC958)" #: spa/plugins/alsa/acp/alsa-mixer.c:4701 msgid "Multichannel Duplex" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4702 #, fuzzy msgid "Stereo Duplex" msgstr "Двосмерни аналогни стерео" #: spa/plugins/alsa/acp/alsa-mixer.c:4703 msgid "Mono Chat + 7.1 Surround" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4806 #, fuzzy, c-format msgid "%s Output" msgstr "Празан излаз" #: spa/plugins/alsa/acp/alsa-mixer.c:4813 #, fuzzy, c-format msgid "%s Input" msgstr "Улаз" #: spa/plugins/alsa/acp/alsa-util.c:1175 spa/plugins/alsa/acp/alsa-util.c:1269 #, fuzzy, c-format msgid "" "snd_pcm_avail() returned a value that is exceptionally large: %lu byte (%lu " "ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_avail() returned a value that is exceptionally large: %lu bytes (%lu " "ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "snd_pcm_avail() је вратио вредност која је необично велика: %lu бајтова (%lu " "ms).\n" "Ово је највероватније грешка у „%s“ ALSA управљачком програму. Пријавите " "овај проблем ALSA програмерима." msgstr[1] "" "snd_pcm_avail() је вратио вредност која је необично велика: %lu бајтова (%lu " "ms).\n" "Ово је највероватније грешка у „%s“ ALSA управљачком програму. Пријавите " "овај проблем ALSA програмерима." msgstr[2] "" "snd_pcm_avail() је вратио вредност која је необично велика: %lu бајтова (%lu " "ms).\n" "Ово је највероватније грешка у „%s“ ALSA управљачком програму. Пријавите " "овај проблем ALSA програмерима." #: spa/plugins/alsa/acp/alsa-util.c:1241 #, fuzzy, c-format msgid "" "snd_pcm_delay() returned a value that is exceptionally large: %li byte (%s" "%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_delay() returned a value that is exceptionally large: %li bytes (%s" "%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "snd_pcm_delay() је вратио вредност која је необично велика: %li бајтова (%s" "%lu ms).\n" "Ово је највероватније грешка у „%s“ ALSA управљачком програму. Пријавите " "овај проблем ALSA програмерима." msgstr[1] "" "snd_pcm_delay() је вратио вредност која је необично велика: %li бајтова (%s" "%lu ms).\n" "Ово је највероватније грешка у „%s“ ALSA управљачком програму. Пријавите " "овај проблем ALSA програмерима." msgstr[2] "" "snd_pcm_delay() је вратио вредност која је необично велика: %li бајтова (%s" "%lu ms).\n" "Ово је највероватније грешка у „%s“ ALSA управљачком програму. Пријавите " "овај проблем ALSA програмерима." #: spa/plugins/alsa/acp/alsa-util.c:1288 #, fuzzy, c-format msgid "" "snd_pcm_avail_delay() returned strange values: delay %lu is less than avail " "%lu.\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr "" "snd_pcm_avail() је вратио вредност која је необично велика: %lu бајтова (%lu " "ms).\n" "Ово је највероватније грешка у „%s“ ALSA управљачком програму. Пријавите " "овај проблем ALSA програмерима." #: spa/plugins/alsa/acp/alsa-util.c:1331 #, fuzzy, c-format msgid "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu byte " "(%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu bytes " "(%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "snd_pcm_mmap_begin() је вратио вредност која је необично велика: %lu " "бајтова (%lu ms).\n" "Ово је највероватније грешка у „%s“ ALSA управљачком програму. Пријавите " "овај проблем ALSA програмерима." msgstr[1] "" "snd_pcm_mmap_begin() је вратио вредност која је необично велика: %lu " "бајтова (%lu ms).\n" "Ово је највероватније грешка у „%s“ ALSA управљачком програму. Пријавите " "овај проблем ALSA програмерима." msgstr[2] "" "snd_pcm_mmap_begin() је вратио вредност која је необично велика: %lu " "бајтова (%lu ms).\n" "Ово је највероватније грешка у „%s“ ALSA управљачком програму. Пријавите " "овај проблем ALSA програмерима." #: spa/plugins/bluez5/bluez5-device.c:1010 msgid "Audio Gateway (A2DP Source & HSP/HFP AG)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1033 #, c-format msgid "High Fidelity Playback (A2DP Sink, codec %s)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1035 #, c-format msgid "High Fidelity Duplex (A2DP Source/Sink, codec %s)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1041 msgid "High Fidelity Playback (A2DP Sink)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1043 msgid "High Fidelity Duplex (A2DP Source/Sink)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1070 #, c-format msgid "Headset Head Unit (HSP/HFP, codec %s)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1074 msgid "Headset Head Unit (HSP/HFP)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1140 msgid "Handsfree" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1155 #, fuzzy msgid "Headphone" msgstr "Аналогне слушалице" #: spa/plugins/bluez5/bluez5-device.c:1160 msgid "Portable" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1165 msgid "Car" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1170 msgid "HiFi" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1175 msgid "Phone" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1181 msgid "Bluetooth" msgstr "" pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/po/sr@latin.po000066400000000000000000000432441511204443500232370ustar00rootroot00000000000000# Serbian(Latin) translations for pipewire # Copyright (C) 2006 Lennart Poettering # This file is distributed under the same license as the pipewire package. # Igor Miletic (Igor Miletić) , 2009. # Miloš Komarčević , 2009, 2012. # msgid "" msgstr "" "Project-Id-Version: pipewire\n" "Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/pipewire/" "issues/new\n" "POT-Creation-Date: 2021-04-18 16:54+0800\n" "PO-Revision-Date: 2012-01-30 09:55+0000\n" "Last-Translator: Miloš Komarčević \n" "Language-Team: Serbian (sr) \n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" "%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" #: src/daemon/pipewire.c:43 #, c-format msgid "" "%s [options]\n" " -h, --help Show this help\n" " --version Show version\n" " -c, --config Load config (Default %s)\n" msgstr "" #: src/daemon/pipewire.desktop.in:4 msgid "PipeWire Media System" msgstr "" #: src/daemon/pipewire.desktop.in:5 msgid "Start the PipeWire Media System" msgstr "" #: src/examples/media-session/alsa-monitor.c:526 #: spa/plugins/alsa/acp/compat.c:187 msgid "Built-in Audio" msgstr "Unutrašnji zvuk" #: src/examples/media-session/alsa-monitor.c:530 #: spa/plugins/alsa/acp/compat.c:192 msgid "Modem" msgstr "Modem" #: src/examples/media-session/alsa-monitor.c:539 msgid "Unknown device" msgstr "" #: src/tools/pw-cat.c:991 #, c-format msgid "" "%s [options] \n" " -h, --help Show this help\n" " --version Show version\n" " -v, --verbose Enable verbose operations\n" "\n" msgstr "" #: src/tools/pw-cat.c:998 #, c-format msgid "" " -R, --remote Remote daemon name\n" " --media-type Set media type (default %s)\n" " --media-category Set media category (default %s)\n" " --media-role Set media role (default %s)\n" " --target Set node target (default %s)\n" " 0 means don't link\n" " --latency Set node latency (default %s)\n" " Xunit (unit = s, ms, us, ns)\n" " or direct samples (256)\n" " the rate is the one of the source " "file\n" " --list-targets List available targets for --target\n" "\n" msgstr "" #: src/tools/pw-cat.c:1016 #, c-format msgid "" " --rate Sample rate (req. for rec) (default " "%u)\n" " --channels Number of channels (req. for rec) " "(default %u)\n" " --channel-map Channel map\n" " one of: \"stereo\", " "\"surround-51\",... or\n" " comma separated list of channel " "names: eg. \"FL,FR\"\n" " --format Sample format %s (req. for rec) " "(default %s)\n" " --volume Stream volume 0-1.0 (default %.3f)\n" " -q --quality Resampler quality (0 - 15) (default " "%d)\n" "\n" msgstr "" #: src/tools/pw-cat.c:1033 msgid "" " -p, --playback Playback mode\n" " -r, --record Recording mode\n" " -m, --midi Midi mode\n" "\n" msgstr "" #: src/tools/pw-cli.c:2932 #, c-format msgid "" "%s [options] [command]\n" " -h, --help Show this help\n" " --version Show version\n" " -d, --daemon Start as daemon (Default false)\n" " -r, --remote Remote daemon name\n" "\n" msgstr "" #: spa/plugins/alsa/acp/acp.c:290 msgid "Pro Audio" msgstr "" #: spa/plugins/alsa/acp/acp.c:411 spa/plugins/alsa/acp/alsa-mixer.c:4704 #: spa/plugins/bluez5/bluez5-device.c:1000 msgid "Off" msgstr "Isključeno" #: spa/plugins/alsa/acp/channelmap.h:466 msgid "(invalid)" msgstr "(neispravno)" #: spa/plugins/alsa/acp/alsa-mixer.c:2709 msgid "Input" msgstr "Ulaz" #: spa/plugins/alsa/acp/alsa-mixer.c:2710 msgid "Docking Station Input" msgstr "Ulaz priključne stanice" #: spa/plugins/alsa/acp/alsa-mixer.c:2711 #, fuzzy msgid "Docking Station Microphone" msgstr "Mikrofon priključne stanice" #: spa/plugins/alsa/acp/alsa-mixer.c:2712 #, fuzzy msgid "Docking Station Line In" msgstr "Ulaz priključne stanice" #: spa/plugins/alsa/acp/alsa-mixer.c:2713 #: spa/plugins/alsa/acp/alsa-mixer.c:2804 msgid "Line In" msgstr "Linija u" #: spa/plugins/alsa/acp/alsa-mixer.c:2714 #: spa/plugins/alsa/acp/alsa-mixer.c:2798 #: spa/plugins/bluez5/bluez5-device.c:1145 msgid "Microphone" msgstr "Mikrofon" #: spa/plugins/alsa/acp/alsa-mixer.c:2715 #: spa/plugins/alsa/acp/alsa-mixer.c:2799 #, fuzzy msgid "Front Microphone" msgstr "Mikrofon priključne stanice" #: spa/plugins/alsa/acp/alsa-mixer.c:2716 #: spa/plugins/alsa/acp/alsa-mixer.c:2800 #, fuzzy msgid "Rear Microphone" msgstr "Mikrofon" #: spa/plugins/alsa/acp/alsa-mixer.c:2717 msgid "External Microphone" msgstr "Spoljni mikrofon" #: spa/plugins/alsa/acp/alsa-mixer.c:2718 #: spa/plugins/alsa/acp/alsa-mixer.c:2802 msgid "Internal Microphone" msgstr "Unutrašnji mikrofon" #: spa/plugins/alsa/acp/alsa-mixer.c:2719 #: spa/plugins/alsa/acp/alsa-mixer.c:2805 msgid "Radio" msgstr "Radio" #: spa/plugins/alsa/acp/alsa-mixer.c:2720 #: spa/plugins/alsa/acp/alsa-mixer.c:2806 msgid "Video" msgstr "Video" #: spa/plugins/alsa/acp/alsa-mixer.c:2721 msgid "Automatic Gain Control" msgstr "Samostalna kontrola pojačanja" #: spa/plugins/alsa/acp/alsa-mixer.c:2722 msgid "No Automatic Gain Control" msgstr "Bez samostalne kontrole pojačanja" #: spa/plugins/alsa/acp/alsa-mixer.c:2723 msgid "Boost" msgstr "Podizanje" #: spa/plugins/alsa/acp/alsa-mixer.c:2724 msgid "No Boost" msgstr "Bez podizanja" #: spa/plugins/alsa/acp/alsa-mixer.c:2725 msgid "Amplifier" msgstr "Pojačalo" #: spa/plugins/alsa/acp/alsa-mixer.c:2726 msgid "No Amplifier" msgstr "Bez pojačala" #: spa/plugins/alsa/acp/alsa-mixer.c:2727 #, fuzzy msgid "Bass Boost" msgstr "Podizanje" #: spa/plugins/alsa/acp/alsa-mixer.c:2728 #, fuzzy msgid "No Bass Boost" msgstr "Bez podizanja" #: spa/plugins/alsa/acp/alsa-mixer.c:2729 #: spa/plugins/bluez5/bluez5-device.c:1150 msgid "Speaker" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2730 #: spa/plugins/alsa/acp/alsa-mixer.c:2808 msgid "Headphones" msgstr "Analogne slušalice" #: spa/plugins/alsa/acp/alsa-mixer.c:2797 msgid "Analog Input" msgstr "Analogni ulaz" #: spa/plugins/alsa/acp/alsa-mixer.c:2801 msgid "Dock Microphone" msgstr "Mikrofon priključne stanice" #: spa/plugins/alsa/acp/alsa-mixer.c:2803 #, fuzzy msgid "Headset Microphone" msgstr "Mikrofon" #: spa/plugins/alsa/acp/alsa-mixer.c:2807 msgid "Analog Output" msgstr "Analogni izlaz" #: spa/plugins/alsa/acp/alsa-mixer.c:2809 #, fuzzy msgid "Headphones 2" msgstr "Analogne slušalice" #: spa/plugins/alsa/acp/alsa-mixer.c:2810 #, fuzzy msgid "Headphones Mono Output" msgstr "Analogni mono izlaz" #: spa/plugins/alsa/acp/alsa-mixer.c:2811 #, fuzzy msgid "Line Out" msgstr "Linija u" #: spa/plugins/alsa/acp/alsa-mixer.c:2812 msgid "Analog Mono Output" msgstr "Analogni mono izlaz" #: spa/plugins/alsa/acp/alsa-mixer.c:2813 #, fuzzy msgid "Speakers" msgstr "Analogni stereo" #: spa/plugins/alsa/acp/alsa-mixer.c:2814 msgid "HDMI / DisplayPort" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2815 #, fuzzy msgid "Digital Output (S/PDIF)" msgstr "Digitalni stereo (HDMI)" #: spa/plugins/alsa/acp/alsa-mixer.c:2816 #, fuzzy msgid "Digital Input (S/PDIF)" msgstr "Digitalni stereo (HDMI)" #: spa/plugins/alsa/acp/alsa-mixer.c:2817 msgid "Multichannel Input" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2818 #, fuzzy msgid "Multichannel Output" msgstr "Prazan izlaz" #: spa/plugins/alsa/acp/alsa-mixer.c:2819 #, fuzzy msgid "Game Output" msgstr "Prazan izlaz" #: spa/plugins/alsa/acp/alsa-mixer.c:2820 #: spa/plugins/alsa/acp/alsa-mixer.c:2821 #, fuzzy msgid "Chat Output" msgstr "Prazan izlaz" #: spa/plugins/alsa/acp/alsa-mixer.c:2822 #, fuzzy msgid "Chat Input" msgstr "Ulaz" #: spa/plugins/alsa/acp/alsa-mixer.c:2823 #, fuzzy msgid "Virtual Surround 7.1" msgstr "Analogni okružujući 7.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4527 msgid "Analog Mono" msgstr "Analogni mono" #: spa/plugins/alsa/acp/alsa-mixer.c:4528 #, fuzzy msgid "Analog Mono (Left)" msgstr "Analogni mono" #: spa/plugins/alsa/acp/alsa-mixer.c:4529 #, fuzzy msgid "Analog Mono (Right)" msgstr "Analogni mono" #. Note: Not translated to "Analog Stereo Input", because the source #. * name gets "Input" appended to it automatically, so adding "Input" #. * here would lead to the source name to become "Analog Stereo Input #. * Input". The same logic applies to analog-stereo-output, #. * multichannel-input and multichannel-output. #: spa/plugins/alsa/acp/alsa-mixer.c:4530 #: spa/plugins/alsa/acp/alsa-mixer.c:4538 #: spa/plugins/alsa/acp/alsa-mixer.c:4539 msgid "Analog Stereo" msgstr "Analogni stereo" #: spa/plugins/alsa/acp/alsa-mixer.c:4531 msgid "Mono" msgstr "Mono" #: spa/plugins/alsa/acp/alsa-mixer.c:4532 msgid "Stereo" msgstr "Stereo" #: spa/plugins/alsa/acp/alsa-mixer.c:4540 #: spa/plugins/alsa/acp/alsa-mixer.c:4698 #: spa/plugins/bluez5/bluez5-device.c:1135 msgid "Headset" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4541 #: spa/plugins/alsa/acp/alsa-mixer.c:4699 #, fuzzy msgid "Speakerphone" msgstr "Analogni stereo" #: spa/plugins/alsa/acp/alsa-mixer.c:4542 #: spa/plugins/alsa/acp/alsa-mixer.c:4543 msgid "Multichannel" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4544 msgid "Analog Surround 2.1" msgstr "Analogni okružujući 2.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4545 msgid "Analog Surround 3.0" msgstr "Analogni okružujući 3.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4546 msgid "Analog Surround 3.1" msgstr "Analogni okružujući 3.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4547 msgid "Analog Surround 4.0" msgstr "Analogni okružujući 4.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4548 msgid "Analog Surround 4.1" msgstr "Analogni okružujući 4.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4549 msgid "Analog Surround 5.0" msgstr "Analogni okružujući 5.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4550 msgid "Analog Surround 5.1" msgstr "Analogni okružujući 5.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4551 msgid "Analog Surround 6.0" msgstr "Analogni okružujući 6.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4552 msgid "Analog Surround 6.1" msgstr "Analogni okružujući 6.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4553 msgid "Analog Surround 7.0" msgstr "Analogni okružujući 7.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4554 msgid "Analog Surround 7.1" msgstr "Analogni okružujući 7.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4555 msgid "Digital Stereo (IEC958)" msgstr "Digitalni stereo (IEC958)" #: spa/plugins/alsa/acp/alsa-mixer.c:4556 msgid "Digital Surround 4.0 (IEC958/AC3)" msgstr "Digitalni okružujući 4.0 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4557 msgid "Digital Surround 5.1 (IEC958/AC3)" msgstr "Digitalni okružujući 5.1 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4558 #, fuzzy msgid "Digital Surround 5.1 (IEC958/DTS)" msgstr "Digitalni okružujući 5.1 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4559 msgid "Digital Stereo (HDMI)" msgstr "Digitalni stereo (HDMI)" #: spa/plugins/alsa/acp/alsa-mixer.c:4560 #, fuzzy msgid "Digital Surround 5.1 (HDMI)" msgstr "Digitalni okružujući 5.1 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4561 msgid "Chat" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4562 msgid "Game" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4696 msgid "Analog Mono Duplex" msgstr "Dvosmerni analogni mono" #: spa/plugins/alsa/acp/alsa-mixer.c:4697 msgid "Analog Stereo Duplex" msgstr "Dvosmerni analogni stereo" #: spa/plugins/alsa/acp/alsa-mixer.c:4700 msgid "Digital Stereo Duplex (IEC958)" msgstr "Dvosmerni digitalni stereo (IEC958)" #: spa/plugins/alsa/acp/alsa-mixer.c:4701 msgid "Multichannel Duplex" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4702 #, fuzzy msgid "Stereo Duplex" msgstr "Dvosmerni analogni stereo" #: spa/plugins/alsa/acp/alsa-mixer.c:4703 msgid "Mono Chat + 7.1 Surround" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4806 #, fuzzy, c-format msgid "%s Output" msgstr "Prazan izlaz" #: spa/plugins/alsa/acp/alsa-mixer.c:4813 #, fuzzy, c-format msgid "%s Input" msgstr "Ulaz" #: spa/plugins/alsa/acp/alsa-util.c:1175 spa/plugins/alsa/acp/alsa-util.c:1269 #, fuzzy, c-format msgid "" "snd_pcm_avail() returned a value that is exceptionally large: %lu byte (%lu " "ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_avail() returned a value that is exceptionally large: %lu bytes (%lu " "ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "snd_pcm_avail() je vratio vrednost koja je neobično velika: %lu bajtova (%lu " "ms).\n" "Ovo je najverovatnije greška u „%s“ ALSA upravljačkom programu. Prijavite " "ovaj problem ALSA programerima." msgstr[1] "" "snd_pcm_avail() je vratio vrednost koja je neobično velika: %lu bajtova (%lu " "ms).\n" "Ovo je najverovatnije greška u „%s“ ALSA upravljačkom programu. Prijavite " "ovaj problem ALSA programerima." msgstr[2] "" "snd_pcm_avail() je vratio vrednost koja je neobično velika: %lu bajtova (%lu " "ms).\n" "Ovo je najverovatnije greška u „%s“ ALSA upravljačkom programu. Prijavite " "ovaj problem ALSA programerima." #: spa/plugins/alsa/acp/alsa-util.c:1241 #, fuzzy, c-format msgid "" "snd_pcm_delay() returned a value that is exceptionally large: %li byte (%s" "%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_delay() returned a value that is exceptionally large: %li bytes (%s" "%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "snd_pcm_delay() je vratio vrednost koja je neobično velika: %li bajtova (%s" "%lu ms).\n" "Ovo je najverovatnije greška u „%s“ ALSA upravljačkom programu. Prijavite " "ovaj problem ALSA programerima." msgstr[1] "" "snd_pcm_delay() je vratio vrednost koja je neobično velika: %li bajtova (%s" "%lu ms).\n" "Ovo je najverovatnije greška u „%s“ ALSA upravljačkom programu. Prijavite " "ovaj problem ALSA programerima." msgstr[2] "" "snd_pcm_delay() je vratio vrednost koja je neobično velika: %li bajtova (%s" "%lu ms).\n" "Ovo je najverovatnije greška u „%s“ ALSA upravljačkom programu. Prijavite " "ovaj problem ALSA programerima." #: spa/plugins/alsa/acp/alsa-util.c:1288 #, fuzzy, c-format msgid "" "snd_pcm_avail_delay() returned strange values: delay %lu is less than avail " "%lu.\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr "" "snd_pcm_avail() je vratio vrednost koja je neobično velika: %lu bajtova (%lu " "ms).\n" "Ovo je najverovatnije greška u „%s“ ALSA upravljačkom programu. Prijavite " "ovaj problem ALSA programerima." #: spa/plugins/alsa/acp/alsa-util.c:1331 #, fuzzy, c-format msgid "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu byte " "(%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu bytes " "(%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "snd_pcm_mmap_begin() je vratio vrednost koja je neobično velika: %lu " "bajtova (%lu ms).\n" "Ovo je najverovatnije greška u „%s“ ALSA upravljačkom programu. Prijavite " "ovaj problem ALSA programerima." msgstr[1] "" "snd_pcm_mmap_begin() je vratio vrednost koja je neobično velika: %lu " "bajtova (%lu ms).\n" "Ovo je najverovatnije greška u „%s“ ALSA upravljačkom programu. Prijavite " "ovaj problem ALSA programerima." msgstr[2] "" "snd_pcm_mmap_begin() je vratio vrednost koja je neobično velika: %lu " "bajtova (%lu ms).\n" "Ovo je najverovatnije greška u „%s“ ALSA upravljačkom programu. Prijavite " "ovaj problem ALSA programerima." #: spa/plugins/bluez5/bluez5-device.c:1010 msgid "Audio Gateway (A2DP Source & HSP/HFP AG)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1033 #, c-format msgid "High Fidelity Playback (A2DP Sink, codec %s)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1035 #, c-format msgid "High Fidelity Duplex (A2DP Source/Sink, codec %s)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1041 msgid "High Fidelity Playback (A2DP Sink)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1043 msgid "High Fidelity Duplex (A2DP Source/Sink)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1070 #, c-format msgid "Headset Head Unit (HSP/HFP, codec %s)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1074 msgid "Headset Head Unit (HSP/HFP)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1140 msgid "Handsfree" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1155 #, fuzzy msgid "Headphone" msgstr "Analogne slušalice" #: spa/plugins/bluez5/bluez5-device.c:1160 msgid "Portable" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1165 msgid "Car" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1170 msgid "HiFi" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1175 msgid "Phone" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1181 msgid "Bluetooth" msgstr "" pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/po/sv.po000066400000000000000000000552211511204443500221110ustar00rootroot00000000000000# Swedish translation for pipewire. # Copyright © 2008-2025 Free Software Foundation, Inc. # This file is distributed under the same license as the pipewire package. # Daniel Nylander , 2008, 2012. # Josef Andersson , 2014, 2017. # Anders Jonsson , 2021, 2022, 2023, 2024, 2025. # # Termer: # input/output: ingång/utgång (det handlar om ljud) # latency: latens # delay: fördröjning # boost: öka # gain: förstärkning # channel map: kanalmappning # passthrough: genomströmning # och en hel del termer som inte översätts inom ljuddomänen, ex. surround. msgid "" msgstr "" "Project-Id-Version: pipewire\n" "Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/pipewire/-/" "issues\n" "POT-Creation-Date: 2025-04-16 15:31+0000\n" "PO-Revision-Date: 2025-04-22 10:43+0200\n" "Last-Translator: Anders Jonsson \n" "Language-Team: Swedish \n" "Language: sv\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" "X-Generator: Poedit 3.5\n" #: src/daemon/pipewire.c:29 #, c-format msgid "" "%s [options]\n" " -h, --help Show this help\n" " -v, --verbose Increase verbosity by one level\n" " --version Show version\n" " -c, --config Load config (Default %s)\n" " -P --properties Set context properties\n" msgstr "" "%s [flaggor]\n" " -h, --help Visa denna hjälp\n" " -v, --verbose Öka utförligheten en nivå\n" " --version Visa version\n" " -c, --config Läs in konfig (standard %s)\n" " -P --properties Ställ in kontextegenskaper\n" #: src/daemon/pipewire.desktop.in:4 msgid "PipeWire Media System" msgstr "PipeWire mediasystem" #: src/daemon/pipewire.desktop.in:5 msgid "Start the PipeWire Media System" msgstr "Starta mediasystemet PipeWire" #: src/modules/module-protocol-pulse/modules/module-tunnel-sink.c:159 #: src/modules/module-protocol-pulse/modules/module-tunnel-source.c:159 #, c-format msgid "Tunnel to %s%s%s" msgstr "Tunnel till %s%s%s" #: src/modules/module-fallback-sink.c:40 msgid "Dummy Output" msgstr "Attrapputgång" #: src/modules/module-pulse-tunnel.c:760 #, c-format msgid "Tunnel for %s@%s" msgstr "Tunnel för %s@%s" #: src/modules/module-zeroconf-discover.c:320 msgid "Unknown device" msgstr "Okänd enhet" #: src/modules/module-zeroconf-discover.c:332 #, c-format msgid "%s on %s@%s" msgstr "%s på %s@%s" #: src/modules/module-zeroconf-discover.c:336 #, c-format msgid "%s on %s" msgstr "%s på %s" #: src/tools/pw-cat.c:973 #, c-format msgid "" "%s [options] [|-]\n" " -h, --help Show this help\n" " --version Show version\n" " -v, --verbose Enable verbose operations\n" "\n" msgstr "" "%s [flaggor] [|-]\n" " -h, --help Visa denna hjälp\n" " --version Visa version\n" " -v, --verbose Aktivera utförliga operationer\n" "\n" #: src/tools/pw-cat.c:980 #, c-format msgid "" " -R, --remote Remote daemon name\n" " --media-type Set media type (default %s)\n" " --media-category Set media category (default %s)\n" " --media-role Set media role (default %s)\n" " --target Set node target serial or name " "(default %s)\n" " 0 means don't link\n" " --latency Set node latency (default %s)\n" " Xunit (unit = s, ms, us, ns)\n" " or direct samples (256)\n" " the rate is the one of the source " "file\n" " -P --properties Set node properties\n" "\n" msgstr "" " -R, --remote Fjärrdemonnamn\n" " --media-type Sätt mediatyp (standard %s)\n" " --media-category Sätt mediakategori (standard %s)\n" " --media-role Sätt mediaroll (standard %s)\n" " --target Sätt nodmåls serienummer eller namn\n" " (standard %s), 0 betyder länka " "inte\n" " --latency Sätt nodlatens (standard %s)\n" " Xenhet (enhet = s, ms, us, ns)\n" " eller direkta samplar (256)\n" " hastigheten är källfilens\n" " -P --properties Sätt nodegenskaper\n" "\n" #: src/tools/pw-cat.c:998 #, c-format msgid "" " --rate Sample rate (req. for rec) (default " "%u)\n" " --channels Number of channels (req. for rec) " "(default %u)\n" " --channel-map Channel map\n" " one of: \"stereo\", " "\"surround-51\",... or\n" " comma separated list of channel " "names: eg. \"FL,FR\"\n" " --format Sample format %s (req. for rec) " "(default %s)\n" " --volume Stream volume 0-1.0 (default %.3f)\n" " -q --quality Resampler quality (0 - 15) (default " "%d)\n" " -a, --raw RAW mode\n" "\n" msgstr "" " --rate Samplingsfrekvens (krävs för insp.) " "(standard %u)\n" " --channels Antal kanaler (krävs för insp.) " "(standard %u)\n" " --channel-map Kanalmappning\n" " en av: \"stereo\", " "\"surround-51\",... eller\n" " kommaseparerad lista av " "kanalnamn: t.ex. \"FL,FR\"\n" " --format Samplingsformat %s (krävs för insp.) " "(standard %s)\n" " --volume Strömvolym 0-1.0 (standard %.3f)\n" " -q --quality Omsamplarkvalitet (0 - 15) (standard " "%d)\n" " -a, --raw RAW-läge\n" "\n" #: src/tools/pw-cat.c:1016 msgid "" " -p, --playback Playback mode\n" " -r, --record Recording mode\n" " -m, --midi Midi mode\n" " -d, --dsd DSD mode\n" " -o, --encoded Encoded mode\n" "\n" msgstr "" " -p, --playback Uppspelningsläge\n" " -r, --record Inspelningsläge\n" " -m, --midi Midiläge\n" " -d, --dsd DSD-läge\n" " -o, --encoded Kodat läge\n" "\n" #: src/tools/pw-cli.c:2306 #, c-format msgid "" "%s [options] [command]\n" " -h, --help Show this help\n" " --version Show version\n" " -d, --daemon Start as daemon (Default false)\n" " -r, --remote Remote daemon name\n" " -m, --monitor Monitor activity\n" "\n" msgstr "" "%s [flaggor] [kommando]\n" " -h, --help Visa denna hjälp\n" " --version Show version\n" " -d, --daemon Starta som demon (standard false)\n" " -r, --remote Fjärrdemonnamn\n" " -m, --monitor Övervaka aktivitet\n" "\n" #: spa/plugins/alsa/acp/acp.c:350 msgid "Pro Audio" msgstr "Professionellt ljud" #: spa/plugins/alsa/acp/acp.c:511 spa/plugins/alsa/acp/alsa-mixer.c:4635 #: spa/plugins/bluez5/bluez5-device.c:1802 msgid "Off" msgstr "Av" #: spa/plugins/alsa/acp/acp.c:593 #, c-format msgid "%s [ALSA UCM error]" msgstr "%s [ALSA UCM-fel]" #: spa/plugins/alsa/acp/alsa-mixer.c:2652 msgid "Input" msgstr "Ingång" #: spa/plugins/alsa/acp/alsa-mixer.c:2653 msgid "Docking Station Input" msgstr "Ingång för dockningsstation" #: spa/plugins/alsa/acp/alsa-mixer.c:2654 msgid "Docking Station Microphone" msgstr "Mikrofon för dockningsstation" #: spa/plugins/alsa/acp/alsa-mixer.c:2655 msgid "Docking Station Line In" msgstr "Linje in för dockningsstation" #: spa/plugins/alsa/acp/alsa-mixer.c:2656 #: spa/plugins/alsa/acp/alsa-mixer.c:2747 msgid "Line In" msgstr "Linje in" #: spa/plugins/alsa/acp/alsa-mixer.c:2657 #: spa/plugins/alsa/acp/alsa-mixer.c:2741 #: spa/plugins/bluez5/bluez5-device.c:2146 msgid "Microphone" msgstr "Mikrofon" #: spa/plugins/alsa/acp/alsa-mixer.c:2658 #: spa/plugins/alsa/acp/alsa-mixer.c:2742 msgid "Front Microphone" msgstr "Frontmikrofon" #: spa/plugins/alsa/acp/alsa-mixer.c:2659 #: spa/plugins/alsa/acp/alsa-mixer.c:2743 msgid "Rear Microphone" msgstr "Bakre mikrofon" #: spa/plugins/alsa/acp/alsa-mixer.c:2660 msgid "External Microphone" msgstr "Extern mikrofon" #: spa/plugins/alsa/acp/alsa-mixer.c:2661 #: spa/plugins/alsa/acp/alsa-mixer.c:2745 msgid "Internal Microphone" msgstr "Intern mikrofon" #: spa/plugins/alsa/acp/alsa-mixer.c:2662 #: spa/plugins/alsa/acp/alsa-mixer.c:2748 msgid "Radio" msgstr "Radio" #: spa/plugins/alsa/acp/alsa-mixer.c:2663 #: spa/plugins/alsa/acp/alsa-mixer.c:2749 msgid "Video" msgstr "Video" #: spa/plugins/alsa/acp/alsa-mixer.c:2664 msgid "Automatic Gain Control" msgstr "Automatisk förstärkningskontroll" #: spa/plugins/alsa/acp/alsa-mixer.c:2665 msgid "No Automatic Gain Control" msgstr "Ingen automatisk förstärkningskontroll" #: spa/plugins/alsa/acp/alsa-mixer.c:2666 msgid "Boost" msgstr "Ökning" #: spa/plugins/alsa/acp/alsa-mixer.c:2667 msgid "No Boost" msgstr "Ingen ökning" #: spa/plugins/alsa/acp/alsa-mixer.c:2668 msgid "Amplifier" msgstr "Förstärkare" #: spa/plugins/alsa/acp/alsa-mixer.c:2669 msgid "No Amplifier" msgstr "Ingen förstärkare" #: spa/plugins/alsa/acp/alsa-mixer.c:2670 msgid "Bass Boost" msgstr "Basökning" #: spa/plugins/alsa/acp/alsa-mixer.c:2671 msgid "No Bass Boost" msgstr "Ingen basökning" #: spa/plugins/alsa/acp/alsa-mixer.c:2672 #: spa/plugins/bluez5/bluez5-device.c:2152 msgid "Speaker" msgstr "Högtalare" #: spa/plugins/alsa/acp/alsa-mixer.c:2673 #: spa/plugins/alsa/acp/alsa-mixer.c:2751 msgid "Headphones" msgstr "Hörlurar" #: spa/plugins/alsa/acp/alsa-mixer.c:2740 msgid "Analog Input" msgstr "Analog ingång" #: spa/plugins/alsa/acp/alsa-mixer.c:2744 msgid "Dock Microphone" msgstr "Dockmikrofon" #: spa/plugins/alsa/acp/alsa-mixer.c:2746 msgid "Headset Microphone" msgstr "Headset-mikrofon" #: spa/plugins/alsa/acp/alsa-mixer.c:2750 msgid "Analog Output" msgstr "Analog utgång" #: spa/plugins/alsa/acp/alsa-mixer.c:2752 msgid "Headphones 2" msgstr "Hörlurar 2" #: spa/plugins/alsa/acp/alsa-mixer.c:2753 msgid "Headphones Mono Output" msgstr "Monoutgång för hörlurar" #: spa/plugins/alsa/acp/alsa-mixer.c:2754 msgid "Line Out" msgstr "Linje ut" #: spa/plugins/alsa/acp/alsa-mixer.c:2755 msgid "Analog Mono Output" msgstr "Analog monoutgång" #: spa/plugins/alsa/acp/alsa-mixer.c:2756 msgid "Speakers" msgstr "Högtalare" #: spa/plugins/alsa/acp/alsa-mixer.c:2757 msgid "HDMI / DisplayPort" msgstr "HDMI / DisplayPort" #: spa/plugins/alsa/acp/alsa-mixer.c:2758 msgid "Digital Output (S/PDIF)" msgstr "Digital utgång (S/PDIF)" #: spa/plugins/alsa/acp/alsa-mixer.c:2759 msgid "Digital Input (S/PDIF)" msgstr "Digital ingång (S/PDIF)" #: spa/plugins/alsa/acp/alsa-mixer.c:2760 msgid "Multichannel Input" msgstr "Multikanalingång" #: spa/plugins/alsa/acp/alsa-mixer.c:2761 msgid "Multichannel Output" msgstr "Multikanalutgång" #: spa/plugins/alsa/acp/alsa-mixer.c:2762 msgid "Game Output" msgstr "Spelutgång" #: spa/plugins/alsa/acp/alsa-mixer.c:2763 #: spa/plugins/alsa/acp/alsa-mixer.c:2764 msgid "Chat Output" msgstr "Chatt-utgång" #: spa/plugins/alsa/acp/alsa-mixer.c:2765 msgid "Chat Input" msgstr "Chatt-ingång" #: spa/plugins/alsa/acp/alsa-mixer.c:2766 msgid "Virtual Surround 7.1" msgstr "Virtual surround 7.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4458 msgid "Analog Mono" msgstr "Analog mono" #: spa/plugins/alsa/acp/alsa-mixer.c:4459 msgid "Analog Mono (Left)" msgstr "Analog mono (vänster)" #: spa/plugins/alsa/acp/alsa-mixer.c:4460 msgid "Analog Mono (Right)" msgstr "Analog mono (höger)" #. Note: Not translated to "Analog Stereo Input", because the source #. * name gets "Input" appended to it automatically, so adding "Input" #. * here would lead to the source name to become "Analog Stereo Input #. * Input". The same logic applies to analog-stereo-output, #. * multichannel-input and multichannel-output. #: spa/plugins/alsa/acp/alsa-mixer.c:4461 #: spa/plugins/alsa/acp/alsa-mixer.c:4469 #: spa/plugins/alsa/acp/alsa-mixer.c:4470 msgid "Analog Stereo" msgstr "Analog stereo" #: spa/plugins/alsa/acp/alsa-mixer.c:4462 msgid "Mono" msgstr "Mono" #: spa/plugins/alsa/acp/alsa-mixer.c:4463 msgid "Stereo" msgstr "Stereo" #: spa/plugins/alsa/acp/alsa-mixer.c:4471 #: spa/plugins/alsa/acp/alsa-mixer.c:4629 #: spa/plugins/bluez5/bluez5-device.c:2134 msgid "Headset" msgstr "Headset" #: spa/plugins/alsa/acp/alsa-mixer.c:4472 #: spa/plugins/alsa/acp/alsa-mixer.c:4630 msgid "Speakerphone" msgstr "Högtalartelefon" #: spa/plugins/alsa/acp/alsa-mixer.c:4473 #: spa/plugins/alsa/acp/alsa-mixer.c:4474 msgid "Multichannel" msgstr "Multikanal" #: spa/plugins/alsa/acp/alsa-mixer.c:4475 msgid "Analog Surround 2.1" msgstr "Analog surround 2.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4476 msgid "Analog Surround 3.0" msgstr "Analog surround 3.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4477 msgid "Analog Surround 3.1" msgstr "Analog surround 3.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4478 msgid "Analog Surround 4.0" msgstr "Analog surround 4.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4479 msgid "Analog Surround 4.1" msgstr "Analog surround 4.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4480 msgid "Analog Surround 5.0" msgstr "Analog surround 5.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4481 msgid "Analog Surround 5.1" msgstr "Analog surround 5.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4482 msgid "Analog Surround 6.0" msgstr "Analog surround 6.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4483 msgid "Analog Surround 6.1" msgstr "Analog surround 6.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4484 msgid "Analog Surround 7.0" msgstr "Analog surround 7.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4485 msgid "Analog Surround 7.1" msgstr "Analog surround 7.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4486 msgid "Digital Stereo (IEC958)" msgstr "Digital stereo (IEC958)" #: spa/plugins/alsa/acp/alsa-mixer.c:4487 msgid "Digital Surround 4.0 (IEC958/AC3)" msgstr "Digital surround 4.0 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4488 msgid "Digital Surround 5.1 (IEC958/AC3)" msgstr "Digital surround 5.1 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4489 msgid "Digital Surround 5.1 (IEC958/DTS)" msgstr "Digital surround 5.1 (IEC958/DTS)" #: spa/plugins/alsa/acp/alsa-mixer.c:4490 msgid "Digital Stereo (HDMI)" msgstr "Digital stereo (HDMI)" #: spa/plugins/alsa/acp/alsa-mixer.c:4491 msgid "Digital Surround 5.1 (HDMI)" msgstr "Digital surround 5.1 (HDMI)" #: spa/plugins/alsa/acp/alsa-mixer.c:4492 msgid "Chat" msgstr "Chatt" #: spa/plugins/alsa/acp/alsa-mixer.c:4493 msgid "Game" msgstr "Spel" #: spa/plugins/alsa/acp/alsa-mixer.c:4627 msgid "Analog Mono Duplex" msgstr "Analog mono duplex" #: spa/plugins/alsa/acp/alsa-mixer.c:4628 msgid "Analog Stereo Duplex" msgstr "Analog stereo duplex" #: spa/plugins/alsa/acp/alsa-mixer.c:4631 msgid "Digital Stereo Duplex (IEC958)" msgstr "Digital stereo duplex (IEC958)" #: spa/plugins/alsa/acp/alsa-mixer.c:4632 msgid "Multichannel Duplex" msgstr "Multikanalduplex" #: spa/plugins/alsa/acp/alsa-mixer.c:4633 msgid "Stereo Duplex" msgstr "Stereo duplex" #: spa/plugins/alsa/acp/alsa-mixer.c:4634 msgid "Mono Chat + 7.1 Surround" msgstr "Mono Chatt + 7.1 Surround" #: spa/plugins/alsa/acp/alsa-mixer.c:4735 #, c-format msgid "%s Output" msgstr "%s-utgång" #: spa/plugins/alsa/acp/alsa-mixer.c:4743 #, c-format msgid "%s Input" msgstr "%s-ingång" #: spa/plugins/alsa/acp/alsa-util.c:1233 spa/plugins/alsa/acp/alsa-util.c:1327 #, c-format msgid "" "snd_pcm_avail() returned a value that is exceptionally large: %lu byte (%lu " "ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_avail() returned a value that is exceptionally large: %lu bytes (%lu " "ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "snd_pcm_avail() returnerade ett värde som är exceptionellt stort: %lu byte " "(%lu ms).\n" "Förmodligen är detta ett fel i ALSA-drivrutinen ”%s”. Vänligen rapportera " "problemet till ALSA-utvecklarna." msgstr[1] "" "snd_pcm_avail() returnerade ett värde som är exceptionellt stort: %lu byte " "(%lu ms).\n" "Förmodligen är detta ett fel i ALSA-drivrutinen ”%s”. Vänligen rapportera " "problemet till ALSA-utvecklarna." #: spa/plugins/alsa/acp/alsa-util.c:1299 #, c-format msgid "" "snd_pcm_delay() returned a value that is exceptionally large: %li byte " "(%s%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_delay() returned a value that is exceptionally large: %li bytes " "(%s%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "snd_pcm_delay() returnerade ett värde som är exceptionellt stort: %li byte " "(%s%lu ms).\n" "Förmodligen är detta ett fel i ALSA-drivrutinen ”%s”. Vänligen rapportera " "problemet till ALSA-utvecklarna." msgstr[1] "" "snd_pcm_delay() returnerade ett värde som är exceptionellt stort: %li byte " "(%s%lu ms).\n" "Förmodligen är detta ett fel i ALSA-drivrutinen ”%s”. Vänligen rapportera " "problemet till ALSA-utvecklarna." #: spa/plugins/alsa/acp/alsa-util.c:1346 #, c-format msgid "" "snd_pcm_avail_delay() returned strange values: delay %lu is less than avail " "%lu.\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr "" "snd_pcm_avail_delay() returnerade konstiga värden: fördröjningen %lu är " "mindre än tillgängliga %lu.\n" "Förmodligen är detta ett fel i ALSA-drivrutinen ”%s”. Vänligen rapportera " "problemet till ALSA-utvecklarna." #: spa/plugins/alsa/acp/alsa-util.c:1389 #, c-format msgid "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu byte " "(%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu bytes " "(%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "snd_pcm_mmap_begin() returnerade ett värde som är exceptionellt stort: %lu " "byte (%lu ms).\n" "Förmodligen är detta ett fel i ALSA-drivrutinen ”%s”. Vänligen rapportera " "problemet till ALSA-utvecklarna." msgstr[1] "" "snd_pcm_mmap_begin() returnerade ett värde som är exceptionellt stort: %lu " "byte (%lu ms).\n" "Förmodligen är detta ett fel i ALSA-drivrutinen ”%s”. Vänligen rapportera " "problemet till ALSA-utvecklarna." #: spa/plugins/alsa/acp/channelmap.h:457 msgid "(invalid)" msgstr "(ogiltig)" #: spa/plugins/alsa/acp/compat.c:193 msgid "Built-in Audio" msgstr "Inbyggt ljud" #: spa/plugins/alsa/acp/compat.c:198 msgid "Modem" msgstr "Modem" #: spa/plugins/bluez5/bluez5-device.c:1813 msgid "Audio Gateway (A2DP Source & HSP/HFP AG)" msgstr "Audio gateway (A2DP-källa & HSP/HFP AG)" #: spa/plugins/bluez5/bluez5-device.c:1841 msgid "Audio Streaming for Hearing Aids (ASHA Sink)" msgstr "Ljudströmning för hörhjälpmedel (ASHA-utgång)" #: spa/plugins/bluez5/bluez5-device.c:1881 #, c-format msgid "High Fidelity Playback (A2DP Sink, codec %s)" msgstr "High fidelity-uppspelning (A2DP-utgång, kodek %s)" #: spa/plugins/bluez5/bluez5-device.c:1884 #, c-format msgid "High Fidelity Duplex (A2DP Source/Sink, codec %s)" msgstr "High fidelity duplex (A2DP-källa/utgång, kodek %s)" #: spa/plugins/bluez5/bluez5-device.c:1892 msgid "High Fidelity Playback (A2DP Sink)" msgstr "High fidelity-uppspelning (A2DP-utgång)" #: spa/plugins/bluez5/bluez5-device.c:1894 msgid "High Fidelity Duplex (A2DP Source/Sink)" msgstr "High fidelity duplex (A2DP-källa/utgång)" #: spa/plugins/bluez5/bluez5-device.c:1944 #, c-format msgid "High Fidelity Playback (BAP Sink, codec %s)" msgstr "High fidelity-uppspelning (BAP-utgång, kodek %s)" #: spa/plugins/bluez5/bluez5-device.c:1949 #, c-format msgid "High Fidelity Input (BAP Source, codec %s)" msgstr "High fidelity-ingång (BAP-källa, kodek %s)" #: spa/plugins/bluez5/bluez5-device.c:1953 #, c-format msgid "High Fidelity Duplex (BAP Source/Sink, codec %s)" msgstr "High fidelity duplex (BAP-källa/utgång, kodek %s)" #: spa/plugins/bluez5/bluez5-device.c:1962 msgid "High Fidelity Playback (BAP Sink)" msgstr "High fidelity-uppspelning (BAP-utgång)" #: spa/plugins/bluez5/bluez5-device.c:1966 msgid "High Fidelity Input (BAP Source)" msgstr "High fidelity-ingång (BAP-källa)" #: spa/plugins/bluez5/bluez5-device.c:1969 msgid "High Fidelity Duplex (BAP Source/Sink)" msgstr "High fidelity duplex (BAP-källa/utgång)" #: spa/plugins/bluez5/bluez5-device.c:2015 #, c-format msgid "Headset Head Unit (HSP/HFP, codec %s)" msgstr "Headset-huvudenhet (HSP/HFP, kodek %s)" #: spa/plugins/bluez5/bluez5-device.c:2135 #: spa/plugins/bluez5/bluez5-device.c:2140 #: spa/plugins/bluez5/bluez5-device.c:2147 #: spa/plugins/bluez5/bluez5-device.c:2153 #: spa/plugins/bluez5/bluez5-device.c:2159 #: spa/plugins/bluez5/bluez5-device.c:2165 #: spa/plugins/bluez5/bluez5-device.c:2171 #: spa/plugins/bluez5/bluez5-device.c:2177 #: spa/plugins/bluez5/bluez5-device.c:2183 msgid "Handsfree" msgstr "Handsfree" #: spa/plugins/bluez5/bluez5-device.c:2141 msgid "Handsfree (HFP)" msgstr "Handsfree (HFP)" #: spa/plugins/bluez5/bluez5-device.c:2158 msgid "Headphone" msgstr "Hörlurar" #: spa/plugins/bluez5/bluez5-device.c:2164 msgid "Portable" msgstr "Bärbar" #: spa/plugins/bluez5/bluez5-device.c:2170 msgid "Car" msgstr "Bil" #: spa/plugins/bluez5/bluez5-device.c:2176 msgid "HiFi" msgstr "HiFi" #: spa/plugins/bluez5/bluez5-device.c:2182 msgid "Phone" msgstr "Telefon" #: spa/plugins/bluez5/bluez5-device.c:2189 msgid "Bluetooth" msgstr "Bluetooth" #: spa/plugins/bluez5/bluez5-device.c:2190 msgid "Bluetooth (HFP)" msgstr "Bluetooth (HFP)" pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/po/ta.po000066400000000000000000000442041511204443500220640ustar00rootroot00000000000000# translation of pipewire.master-tx.ta.po to Tamil # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # I. Felix , 2009, 2012. # msgid "" msgstr "" "Project-Id-Version: pipewire.master-tx.ta\n" "Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/pipewire/" "issues/new\n" "POT-Creation-Date: 2021-04-18 16:54+0800\n" "PO-Revision-Date: 2012-01-30 09:56+0000\n" "Last-Translator: I. Felix \n" "Language-Team: Tamil \n" "Language: ta\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Generator: KBabel 1.11.4\n" "Plural-Forms: nplurals=2; plural=(n!=1);\\n\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n" #: src/daemon/pipewire.c:43 #, c-format msgid "" "%s [options]\n" " -h, --help Show this help\n" " --version Show version\n" " -c, --config Load config (Default %s)\n" msgstr "" #: src/daemon/pipewire.desktop.in:4 msgid "PipeWire Media System" msgstr "" #: src/daemon/pipewire.desktop.in:5 msgid "Start the PipeWire Media System" msgstr "" #: src/examples/media-session/alsa-monitor.c:526 #: spa/plugins/alsa/acp/compat.c:187 msgid "Built-in Audio" msgstr "உட்புற ஆடியோ" #: src/examples/media-session/alsa-monitor.c:530 #: spa/plugins/alsa/acp/compat.c:192 msgid "Modem" msgstr "மாதிரி" #: src/examples/media-session/alsa-monitor.c:539 msgid "Unknown device" msgstr "" #: src/tools/pw-cat.c:991 #, c-format msgid "" "%s [options] \n" " -h, --help Show this help\n" " --version Show version\n" " -v, --verbose Enable verbose operations\n" "\n" msgstr "" #: src/tools/pw-cat.c:998 #, c-format msgid "" " -R, --remote Remote daemon name\n" " --media-type Set media type (default %s)\n" " --media-category Set media category (default %s)\n" " --media-role Set media role (default %s)\n" " --target Set node target (default %s)\n" " 0 means don't link\n" " --latency Set node latency (default %s)\n" " Xunit (unit = s, ms, us, ns)\n" " or direct samples (256)\n" " the rate is the one of the source " "file\n" " --list-targets List available targets for --target\n" "\n" msgstr "" #: src/tools/pw-cat.c:1016 #, c-format msgid "" " --rate Sample rate (req. for rec) (default " "%u)\n" " --channels Number of channels (req. for rec) " "(default %u)\n" " --channel-map Channel map\n" " one of: \"stereo\", " "\"surround-51\",... or\n" " comma separated list of channel " "names: eg. \"FL,FR\"\n" " --format Sample format %s (req. for rec) " "(default %s)\n" " --volume Stream volume 0-1.0 (default %.3f)\n" " -q --quality Resampler quality (0 - 15) (default " "%d)\n" "\n" msgstr "" #: src/tools/pw-cat.c:1033 msgid "" " -p, --playback Playback mode\n" " -r, --record Recording mode\n" " -m, --midi Midi mode\n" "\n" msgstr "" #: src/tools/pw-cli.c:2932 #, c-format msgid "" "%s [options] [command]\n" " -h, --help Show this help\n" " --version Show version\n" " -d, --daemon Start as daemon (Default false)\n" " -r, --remote Remote daemon name\n" "\n" msgstr "" #: spa/plugins/alsa/acp/acp.c:290 msgid "Pro Audio" msgstr "" #: spa/plugins/alsa/acp/acp.c:411 spa/plugins/alsa/acp/alsa-mixer.c:4704 #: spa/plugins/bluez5/bluez5-device.c:1000 msgid "Off" msgstr "ஆஃப்" #: spa/plugins/alsa/acp/channelmap.h:466 msgid "(invalid)" msgstr "(தவறான)" #: spa/plugins/alsa/acp/alsa-mixer.c:2709 msgid "Input" msgstr "உள்ளீடு" #: spa/plugins/alsa/acp/alsa-mixer.c:2710 msgid "Docking Station Input" msgstr "டாக்கிங் ஸ்டேஷன் உள்ளீடு" #: spa/plugins/alsa/acp/alsa-mixer.c:2711 #, fuzzy msgid "Docking Station Microphone" msgstr "டாக்கிங் ஸ்டேஷன் மைக்ரோஃபோன்" #: spa/plugins/alsa/acp/alsa-mixer.c:2712 #, fuzzy msgid "Docking Station Line In" msgstr "டாக்கிங் ஸ்டேஷன் உள்ளீடு" #: spa/plugins/alsa/acp/alsa-mixer.c:2713 #: spa/plugins/alsa/acp/alsa-mixer.c:2804 msgid "Line In" msgstr "லைன்இன்" #: spa/plugins/alsa/acp/alsa-mixer.c:2714 #: spa/plugins/alsa/acp/alsa-mixer.c:2798 #: spa/plugins/bluez5/bluez5-device.c:1145 msgid "Microphone" msgstr "மைக்ரோஃபோன்" #: spa/plugins/alsa/acp/alsa-mixer.c:2715 #: spa/plugins/alsa/acp/alsa-mixer.c:2799 #, fuzzy msgid "Front Microphone" msgstr "டாக்கிங் ஸ்டேஷன் மைக்ரோஃபோன்" #: spa/plugins/alsa/acp/alsa-mixer.c:2716 #: spa/plugins/alsa/acp/alsa-mixer.c:2800 #, fuzzy msgid "Rear Microphone" msgstr "மைக்ரோஃபோன்" #: spa/plugins/alsa/acp/alsa-mixer.c:2717 msgid "External Microphone" msgstr "வெளியார்ந்த மைக்ரோஃபோன்" #: spa/plugins/alsa/acp/alsa-mixer.c:2718 #: spa/plugins/alsa/acp/alsa-mixer.c:2802 msgid "Internal Microphone" msgstr "உட்புற மைக்ரோஃபோன்" #: spa/plugins/alsa/acp/alsa-mixer.c:2719 #: spa/plugins/alsa/acp/alsa-mixer.c:2805 msgid "Radio" msgstr "ரேடியோ" #: spa/plugins/alsa/acp/alsa-mixer.c:2720 #: spa/plugins/alsa/acp/alsa-mixer.c:2806 msgid "Video" msgstr "வீடியோ" #: spa/plugins/alsa/acp/alsa-mixer.c:2721 msgid "Automatic Gain Control" msgstr "தானியக்க லாப கட்டுப்பாடு" #: spa/plugins/alsa/acp/alsa-mixer.c:2722 msgid "No Automatic Gain Control" msgstr "தானியக்க லாப கட்டுப்பாடு எதுவுமில்லை" #: spa/plugins/alsa/acp/alsa-mixer.c:2723 msgid "Boost" msgstr "பூஸ்ட்" #: spa/plugins/alsa/acp/alsa-mixer.c:2724 msgid "No Boost" msgstr "பூஸ்ட் இல்லை" #: spa/plugins/alsa/acp/alsa-mixer.c:2725 msgid "Amplifier" msgstr "ஆம்பிளிஃபையர்" #: spa/plugins/alsa/acp/alsa-mixer.c:2726 msgid "No Amplifier" msgstr "ஆம்ப்ளிஃபையர் இல்லை" #: spa/plugins/alsa/acp/alsa-mixer.c:2727 #, fuzzy msgid "Bass Boost" msgstr "பூஸ்ட்" #: spa/plugins/alsa/acp/alsa-mixer.c:2728 #, fuzzy msgid "No Bass Boost" msgstr "பூஸ்ட் இல்லை" #: spa/plugins/alsa/acp/alsa-mixer.c:2729 #: spa/plugins/bluez5/bluez5-device.c:1150 msgid "Speaker" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2730 #: spa/plugins/alsa/acp/alsa-mixer.c:2808 msgid "Headphones" msgstr "அனலாக் ஹெட்ஃபோன்கள்" #: spa/plugins/alsa/acp/alsa-mixer.c:2797 msgid "Analog Input" msgstr "அனலாக் உள்ளிடு" #: spa/plugins/alsa/acp/alsa-mixer.c:2801 msgid "Dock Microphone" msgstr "டாக்கிங் ஸ்டேஷன் மைக்ரோஃபோன்" #: spa/plugins/alsa/acp/alsa-mixer.c:2803 #, fuzzy msgid "Headset Microphone" msgstr "மைக்ரோஃபோன்" #: spa/plugins/alsa/acp/alsa-mixer.c:2807 msgid "Analog Output" msgstr "அனலாக் வெளிப்பாடு" #: spa/plugins/alsa/acp/alsa-mixer.c:2809 #, fuzzy msgid "Headphones 2" msgstr "அனலாக் ஹெட்ஃபோன்கள்" #: spa/plugins/alsa/acp/alsa-mixer.c:2810 #, fuzzy msgid "Headphones Mono Output" msgstr "அனலாக் மோனோ வெளிப்பாடு" #: spa/plugins/alsa/acp/alsa-mixer.c:2811 #, fuzzy msgid "Line Out" msgstr "லைன்இன்" #: spa/plugins/alsa/acp/alsa-mixer.c:2812 msgid "Analog Mono Output" msgstr "அனலாக் மோனோ வெளிப்பாடு" #: spa/plugins/alsa/acp/alsa-mixer.c:2813 #, fuzzy msgid "Speakers" msgstr "அனலாக் ஸ்டிரியோ" #: spa/plugins/alsa/acp/alsa-mixer.c:2814 msgid "HDMI / DisplayPort" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2815 #, fuzzy msgid "Digital Output (S/PDIF)" msgstr "Digital Stereo (HDMI)" #: spa/plugins/alsa/acp/alsa-mixer.c:2816 #, fuzzy msgid "Digital Input (S/PDIF)" msgstr "Digital Stereo (HDMI)" #: spa/plugins/alsa/acp/alsa-mixer.c:2817 msgid "Multichannel Input" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2818 #, fuzzy msgid "Multichannel Output" msgstr "பூஜ்ஜிய வெளிப்பாடு" #: spa/plugins/alsa/acp/alsa-mixer.c:2819 #, fuzzy msgid "Game Output" msgstr "பூஜ்ஜிய வெளிப்பாடு" #: spa/plugins/alsa/acp/alsa-mixer.c:2820 #: spa/plugins/alsa/acp/alsa-mixer.c:2821 #, fuzzy msgid "Chat Output" msgstr "பூஜ்ஜிய வெளிப்பாடு" #: spa/plugins/alsa/acp/alsa-mixer.c:2822 #, fuzzy msgid "Chat Input" msgstr "உள்ளீடு" #: spa/plugins/alsa/acp/alsa-mixer.c:2823 #, fuzzy msgid "Virtual Surround 7.1" msgstr "Analog Surround 7.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4527 msgid "Analog Mono" msgstr "அனலாக் மோனோ" #: spa/plugins/alsa/acp/alsa-mixer.c:4528 #, fuzzy msgid "Analog Mono (Left)" msgstr "அனலாக் மோனோ" #: spa/plugins/alsa/acp/alsa-mixer.c:4529 #, fuzzy msgid "Analog Mono (Right)" msgstr "அனலாக் மோனோ" #. Note: Not translated to "Analog Stereo Input", because the source #. * name gets "Input" appended to it automatically, so adding "Input" #. * here would lead to the source name to become "Analog Stereo Input #. * Input". The same logic applies to analog-stereo-output, #. * multichannel-input and multichannel-output. #: spa/plugins/alsa/acp/alsa-mixer.c:4530 #: spa/plugins/alsa/acp/alsa-mixer.c:4538 #: spa/plugins/alsa/acp/alsa-mixer.c:4539 msgid "Analog Stereo" msgstr "அனலாக் ஸ்டிரியோ" #: spa/plugins/alsa/acp/alsa-mixer.c:4531 msgid "Mono" msgstr "மோனோ" #: spa/plugins/alsa/acp/alsa-mixer.c:4532 msgid "Stereo" msgstr "ஸ்டிரியோ" #: spa/plugins/alsa/acp/alsa-mixer.c:4540 #: spa/plugins/alsa/acp/alsa-mixer.c:4698 #: spa/plugins/bluez5/bluez5-device.c:1135 msgid "Headset" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4541 #: spa/plugins/alsa/acp/alsa-mixer.c:4699 #, fuzzy msgid "Speakerphone" msgstr "அனலாக் ஸ்டிரியோ" #: spa/plugins/alsa/acp/alsa-mixer.c:4542 #: spa/plugins/alsa/acp/alsa-mixer.c:4543 msgid "Multichannel" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4544 msgid "Analog Surround 2.1" msgstr "Analog Surround 2.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4545 msgid "Analog Surround 3.0" msgstr "Analog Surround 3.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4546 msgid "Analog Surround 3.1" msgstr "Analog Surround 3.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4547 msgid "Analog Surround 4.0" msgstr "Analog Surround 4.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4548 msgid "Analog Surround 4.1" msgstr "Analog Surround 4.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4549 msgid "Analog Surround 5.0" msgstr "Analog Surround 5.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4550 msgid "Analog Surround 5.1" msgstr "Analog Surround 5.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4551 msgid "Analog Surround 6.0" msgstr "Analog Surround 6.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4552 msgid "Analog Surround 6.1" msgstr "Analog Surround 6.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4553 msgid "Analog Surround 7.0" msgstr "Analog Surround 7.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4554 msgid "Analog Surround 7.1" msgstr "Analog Surround 7.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4555 msgid "Digital Stereo (IEC958)" msgstr "Digital Stereo (IEC958)" #: spa/plugins/alsa/acp/alsa-mixer.c:4556 msgid "Digital Surround 4.0 (IEC958/AC3)" msgstr "Digital Surround 4.0 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4557 msgid "Digital Surround 5.1 (IEC958/AC3)" msgstr "Digital Surround 5.1 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4558 #, fuzzy msgid "Digital Surround 5.1 (IEC958/DTS)" msgstr "Digital Surround 5.1 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4559 msgid "Digital Stereo (HDMI)" msgstr "Digital Stereo (HDMI)" #: spa/plugins/alsa/acp/alsa-mixer.c:4560 #, fuzzy msgid "Digital Surround 5.1 (HDMI)" msgstr "Digital Surround 5.1 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4561 msgid "Chat" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4562 msgid "Game" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4696 msgid "Analog Mono Duplex" msgstr "Analog Mono Duplex" #: spa/plugins/alsa/acp/alsa-mixer.c:4697 msgid "Analog Stereo Duplex" msgstr "Analog Stereo Duplex" #: spa/plugins/alsa/acp/alsa-mixer.c:4700 msgid "Digital Stereo Duplex (IEC958)" msgstr "Digital Stereo Duplex (IEC958)" #: spa/plugins/alsa/acp/alsa-mixer.c:4701 msgid "Multichannel Duplex" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4702 #, fuzzy msgid "Stereo Duplex" msgstr "Analog Stereo Duplex" #: spa/plugins/alsa/acp/alsa-mixer.c:4703 msgid "Mono Chat + 7.1 Surround" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4806 #, fuzzy, c-format msgid "%s Output" msgstr "பூஜ்ஜிய வெளிப்பாடு" #: spa/plugins/alsa/acp/alsa-mixer.c:4813 #, fuzzy, c-format msgid "%s Input" msgstr "உள்ளீடு" #: spa/plugins/alsa/acp/alsa-util.c:1175 spa/plugins/alsa/acp/alsa-util.c:1269 #, fuzzy, c-format msgid "" "snd_pcm_avail() returned a value that is exceptionally large: %lu byte (%lu " "ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_avail() returned a value that is exceptionally large: %lu bytes (%lu " "ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "snd_pcm_avail() returned a value that is exceptionally large: %lu bytes (%lu " "ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[1] "" "snd_pcm_avail() returned a value that is exceptionally large: %lu bytes (%lu " "ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." #: spa/plugins/alsa/acp/alsa-util.c:1241 #, fuzzy, c-format msgid "" "snd_pcm_delay() returned a value that is exceptionally large: %li byte (%s" "%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_delay() returned a value that is exceptionally large: %li bytes (%s" "%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "snd_pcm_delay() returned a value that is exceptionally large: %li bytes (%s" "%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[1] "" "snd_pcm_delay() returned a value that is exceptionally large: %li bytes (%s" "%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." #: spa/plugins/alsa/acp/alsa-util.c:1288 #, fuzzy, c-format msgid "" "snd_pcm_avail_delay() returned strange values: delay %lu is less than avail " "%lu.\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr "" "snd_pcm_avail() returned a value that is exceptionally large: %lu bytes (%lu " "ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." #: spa/plugins/alsa/acp/alsa-util.c:1331 #, fuzzy, c-format msgid "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu byte " "(%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu bytes " "(%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu bytes " "(%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[1] "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu bytes " "(%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." #: spa/plugins/bluez5/bluez5-device.c:1010 msgid "Audio Gateway (A2DP Source & HSP/HFP AG)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1033 #, c-format msgid "High Fidelity Playback (A2DP Sink, codec %s)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1035 #, c-format msgid "High Fidelity Duplex (A2DP Source/Sink, codec %s)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1041 msgid "High Fidelity Playback (A2DP Sink)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1043 msgid "High Fidelity Duplex (A2DP Source/Sink)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1070 #, c-format msgid "Headset Head Unit (HSP/HFP, codec %s)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1074 msgid "Headset Head Unit (HSP/HFP)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1140 msgid "Handsfree" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1155 #, fuzzy msgid "Headphone" msgstr "அனலாக் ஹெட்ஃபோன்கள்" #: spa/plugins/bluez5/bluez5-device.c:1160 msgid "Portable" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1165 msgid "Car" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1170 msgid "HiFi" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1175 msgid "Phone" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1181 msgid "Bluetooth" msgstr "" pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/po/te.po000066400000000000000000000476121511204443500220760ustar00rootroot00000000000000# translation of pipewire.master-tx.te.po to Telugu # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # Krishna Babu K , 2009, 2012. # msgid "" msgstr "" "Project-Id-Version: pipewire.master-tx.te\n" "Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/pipewire/" "issues/new\n" "POT-Creation-Date: 2021-04-18 16:54+0800\n" "PO-Revision-Date: 2012-01-30 09:56+0000\n" "Last-Translator: Krishna Babu K \n" "Language-Team: Telugu \n" "Language: te\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Generator: KBabel 1.11.4\n" "Plural-Forms: nplurals=2; plural=(n!=1);\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n" #: src/daemon/pipewire.c:43 #, c-format msgid "" "%s [options]\n" " -h, --help Show this help\n" " --version Show version\n" " -c, --config Load config (Default %s)\n" msgstr "" #: src/daemon/pipewire.desktop.in:4 msgid "PipeWire Media System" msgstr "" #: src/daemon/pipewire.desktop.in:5 msgid "Start the PipeWire Media System" msgstr "" #: src/examples/media-session/alsa-monitor.c:526 #: spa/plugins/alsa/acp/compat.c:187 msgid "Built-in Audio" msgstr "అంతర్గత ఆడియో" #: src/examples/media-session/alsa-monitor.c:530 #: spa/plugins/alsa/acp/compat.c:192 msgid "Modem" msgstr "మోడెమ్" #: src/examples/media-session/alsa-monitor.c:539 msgid "Unknown device" msgstr "" #: src/tools/pw-cat.c:991 #, c-format msgid "" "%s [options] \n" " -h, --help Show this help\n" " --version Show version\n" " -v, --verbose Enable verbose operations\n" "\n" msgstr "" #: src/tools/pw-cat.c:998 #, c-format msgid "" " -R, --remote Remote daemon name\n" " --media-type Set media type (default %s)\n" " --media-category Set media category (default %s)\n" " --media-role Set media role (default %s)\n" " --target Set node target (default %s)\n" " 0 means don't link\n" " --latency Set node latency (default %s)\n" " Xunit (unit = s, ms, us, ns)\n" " or direct samples (256)\n" " the rate is the one of the source " "file\n" " --list-targets List available targets for --target\n" "\n" msgstr "" #: src/tools/pw-cat.c:1016 #, c-format msgid "" " --rate Sample rate (req. for rec) (default " "%u)\n" " --channels Number of channels (req. for rec) " "(default %u)\n" " --channel-map Channel map\n" " one of: \"stereo\", " "\"surround-51\",... or\n" " comma separated list of channel " "names: eg. \"FL,FR\"\n" " --format Sample format %s (req. for rec) " "(default %s)\n" " --volume Stream volume 0-1.0 (default %.3f)\n" " -q --quality Resampler quality (0 - 15) (default " "%d)\n" "\n" msgstr "" #: src/tools/pw-cat.c:1033 msgid "" " -p, --playback Playback mode\n" " -r, --record Recording mode\n" " -m, --midi Midi mode\n" "\n" msgstr "" #: src/tools/pw-cli.c:2932 #, c-format msgid "" "%s [options] [command]\n" " -h, --help Show this help\n" " --version Show version\n" " -d, --daemon Start as daemon (Default false)\n" " -r, --remote Remote daemon name\n" "\n" msgstr "" #: spa/plugins/alsa/acp/acp.c:290 msgid "Pro Audio" msgstr "" #: spa/plugins/alsa/acp/acp.c:411 spa/plugins/alsa/acp/alsa-mixer.c:4704 #: spa/plugins/bluez5/bluez5-device.c:1000 msgid "Off" msgstr "ఆఫ్" #: spa/plugins/alsa/acp/channelmap.h:466 msgid "(invalid)" msgstr "(చెల్లని)" #: spa/plugins/alsa/acp/alsa-mixer.c:2709 msgid "Input" msgstr "ఇన్పుట్" #: spa/plugins/alsa/acp/alsa-mixer.c:2710 msgid "Docking Station Input" msgstr "డాకింగ్ స్టేషన్ ఇన్పుట్" #: spa/plugins/alsa/acp/alsa-mixer.c:2711 #, fuzzy msgid "Docking Station Microphone" msgstr "డాకింగ్ స్టేషన్ మైక్రోఫోన్" #: spa/plugins/alsa/acp/alsa-mixer.c:2712 #, fuzzy msgid "Docking Station Line In" msgstr "డాకింగ్ స్టేషన్ ఇన్పుట్" #: spa/plugins/alsa/acp/alsa-mixer.c:2713 #: spa/plugins/alsa/acp/alsa-mixer.c:2804 msgid "Line In" msgstr "లైన్-యిన్" #: spa/plugins/alsa/acp/alsa-mixer.c:2714 #: spa/plugins/alsa/acp/alsa-mixer.c:2798 #: spa/plugins/bluez5/bluez5-device.c:1145 msgid "Microphone" msgstr "మైక్రోఫోన్" #: spa/plugins/alsa/acp/alsa-mixer.c:2715 #: spa/plugins/alsa/acp/alsa-mixer.c:2799 #, fuzzy msgid "Front Microphone" msgstr "డాకింగ్ స్టేషన్ మైక్రోఫోన్" #: spa/plugins/alsa/acp/alsa-mixer.c:2716 #: spa/plugins/alsa/acp/alsa-mixer.c:2800 #, fuzzy msgid "Rear Microphone" msgstr "మైక్రోఫోన్" #: spa/plugins/alsa/acp/alsa-mixer.c:2717 msgid "External Microphone" msgstr "బహిర్గత మైక్రోఫోన్" #: spa/plugins/alsa/acp/alsa-mixer.c:2718 #: spa/plugins/alsa/acp/alsa-mixer.c:2802 msgid "Internal Microphone" msgstr "అంతర్గత మైక్రోఫోన్" #: spa/plugins/alsa/acp/alsa-mixer.c:2719 #: spa/plugins/alsa/acp/alsa-mixer.c:2805 msgid "Radio" msgstr "రేడియో" #: spa/plugins/alsa/acp/alsa-mixer.c:2720 #: spa/plugins/alsa/acp/alsa-mixer.c:2806 msgid "Video" msgstr "వీడియో" #: spa/plugins/alsa/acp/alsa-mixer.c:2721 msgid "Automatic Gain Control" msgstr "స్వయంచాలకంగా పొందు నియంత్రణ" #: spa/plugins/alsa/acp/alsa-mixer.c:2722 msgid "No Automatic Gain Control" msgstr "స్వయంచాలకంగా పొందు ఏ నియంత్రణ లేదు" #: spa/plugins/alsa/acp/alsa-mixer.c:2723 msgid "Boost" msgstr "బూస్ట్" #: spa/plugins/alsa/acp/alsa-mixer.c:2724 msgid "No Boost" msgstr "బూస్ట్ లేదు" #: spa/plugins/alsa/acp/alsa-mixer.c:2725 msgid "Amplifier" msgstr "ఎంప్లిఫైర్" #: spa/plugins/alsa/acp/alsa-mixer.c:2726 msgid "No Amplifier" msgstr "ఎంప్లిఫైర్ లేదు" #: spa/plugins/alsa/acp/alsa-mixer.c:2727 #, fuzzy msgid "Bass Boost" msgstr "బూస్ట్" #: spa/plugins/alsa/acp/alsa-mixer.c:2728 #, fuzzy msgid "No Bass Boost" msgstr "బూస్ట్ లేదు" #: spa/plugins/alsa/acp/alsa-mixer.c:2729 #: spa/plugins/bluez5/bluez5-device.c:1150 msgid "Speaker" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2730 #: spa/plugins/alsa/acp/alsa-mixer.c:2808 msgid "Headphones" msgstr "ఎనలాగ్ హెడ్‌ఫోన్స్" #: spa/plugins/alsa/acp/alsa-mixer.c:2797 msgid "Analog Input" msgstr "ఎనలాగ్ యిన్పుట్" #: spa/plugins/alsa/acp/alsa-mixer.c:2801 msgid "Dock Microphone" msgstr "డాకింగ్ స్టేషన్ మైక్రోఫోన్" #: spa/plugins/alsa/acp/alsa-mixer.c:2803 #, fuzzy msgid "Headset Microphone" msgstr "మైక్రోఫోన్" #: spa/plugins/alsa/acp/alsa-mixer.c:2807 msgid "Analog Output" msgstr "ఎనలాగ్ అవుట్పుట్" #: spa/plugins/alsa/acp/alsa-mixer.c:2809 #, fuzzy msgid "Headphones 2" msgstr "ఎనలాగ్ హెడ్‌ఫోన్స్" #: spa/plugins/alsa/acp/alsa-mixer.c:2810 #, fuzzy msgid "Headphones Mono Output" msgstr "ఎనలాగ్ మోనో అవుట్పుట్" #: spa/plugins/alsa/acp/alsa-mixer.c:2811 #, fuzzy msgid "Line Out" msgstr "లైన్-యిన్" #: spa/plugins/alsa/acp/alsa-mixer.c:2812 msgid "Analog Mono Output" msgstr "ఎనలాగ్ మోనో అవుట్పుట్" #: spa/plugins/alsa/acp/alsa-mixer.c:2813 #, fuzzy msgid "Speakers" msgstr "ఎనలాగ్ స్టీరియో" #: spa/plugins/alsa/acp/alsa-mixer.c:2814 msgid "HDMI / DisplayPort" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2815 #, fuzzy msgid "Digital Output (S/PDIF)" msgstr "డిజిటల్ స్టీరియో (HDMI)" #: spa/plugins/alsa/acp/alsa-mixer.c:2816 #, fuzzy msgid "Digital Input (S/PDIF)" msgstr "డిజిటల్ స్టీరియో (HDMI)" #: spa/plugins/alsa/acp/alsa-mixer.c:2817 msgid "Multichannel Input" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:2818 #, fuzzy msgid "Multichannel Output" msgstr "Null అవుట్పుట్" #: spa/plugins/alsa/acp/alsa-mixer.c:2819 #, fuzzy msgid "Game Output" msgstr "Null అవుట్పుట్" #: spa/plugins/alsa/acp/alsa-mixer.c:2820 #: spa/plugins/alsa/acp/alsa-mixer.c:2821 #, fuzzy msgid "Chat Output" msgstr "Null అవుట్పుట్" #: spa/plugins/alsa/acp/alsa-mixer.c:2822 #, fuzzy msgid "Chat Input" msgstr "ఇన్పుట్" #: spa/plugins/alsa/acp/alsa-mixer.c:2823 #, fuzzy msgid "Virtual Surround 7.1" msgstr "ఎనలాగ్ సరౌండ్ 7.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4527 msgid "Analog Mono" msgstr "ఎనలాగ్ మోనో" #: spa/plugins/alsa/acp/alsa-mixer.c:4528 #, fuzzy msgid "Analog Mono (Left)" msgstr "ఎనలాగ్ మోనో" #: spa/plugins/alsa/acp/alsa-mixer.c:4529 #, fuzzy msgid "Analog Mono (Right)" msgstr "ఎనలాగ్ మోనో" #. Note: Not translated to "Analog Stereo Input", because the source #. * name gets "Input" appended to it automatically, so adding "Input" #. * here would lead to the source name to become "Analog Stereo Input #. * Input". The same logic applies to analog-stereo-output, #. * multichannel-input and multichannel-output. #: spa/plugins/alsa/acp/alsa-mixer.c:4530 #: spa/plugins/alsa/acp/alsa-mixer.c:4538 #: spa/plugins/alsa/acp/alsa-mixer.c:4539 msgid "Analog Stereo" msgstr "ఎనలాగ్ స్టీరియో" #: spa/plugins/alsa/acp/alsa-mixer.c:4531 msgid "Mono" msgstr "మోనో" #: spa/plugins/alsa/acp/alsa-mixer.c:4532 msgid "Stereo" msgstr "స్టీరియో" #: spa/plugins/alsa/acp/alsa-mixer.c:4540 #: spa/plugins/alsa/acp/alsa-mixer.c:4698 #: spa/plugins/bluez5/bluez5-device.c:1135 msgid "Headset" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4541 #: spa/plugins/alsa/acp/alsa-mixer.c:4699 #, fuzzy msgid "Speakerphone" msgstr "ఎనలాగ్ స్టీరియో" #: spa/plugins/alsa/acp/alsa-mixer.c:4542 #: spa/plugins/alsa/acp/alsa-mixer.c:4543 msgid "Multichannel" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4544 msgid "Analog Surround 2.1" msgstr "ఎనలాగ్ సరౌండ్ 2.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4545 msgid "Analog Surround 3.0" msgstr "ఎనలాగ్ సరౌండ్ 3.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4546 msgid "Analog Surround 3.1" msgstr "ఎనలాగ్ సరౌండ్ 3.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4547 msgid "Analog Surround 4.0" msgstr "ఎనలాగ్ సరౌండ్ 4.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4548 msgid "Analog Surround 4.1" msgstr "ఎనలాగ్ సరౌండ్ 4.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4549 msgid "Analog Surround 5.0" msgstr "ఎనలాగ్ సరౌండ్ 5.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4550 msgid "Analog Surround 5.1" msgstr "ఎనలాగ్ సరౌండ్ 5.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4551 msgid "Analog Surround 6.0" msgstr "ఎనలాగ్ సరౌండ్ 6.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4552 msgid "Analog Surround 6.1" msgstr "ఎనలాగ్ సరౌండ్ 6.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4553 msgid "Analog Surround 7.0" msgstr "ఎనలాగ్ సరౌండ్ 7.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4554 msgid "Analog Surround 7.1" msgstr "ఎనలాగ్ సరౌండ్ 7.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4555 msgid "Digital Stereo (IEC958)" msgstr "డిజిటల్ స్టీరియో (IEC958)" #: spa/plugins/alsa/acp/alsa-mixer.c:4556 msgid "Digital Surround 4.0 (IEC958/AC3)" msgstr "డిజిటల్ సరౌండ్ 4.0 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4557 msgid "Digital Surround 5.1 (IEC958/AC3)" msgstr "డిజిటల్ సరౌండ్ 5.1 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4558 #, fuzzy msgid "Digital Surround 5.1 (IEC958/DTS)" msgstr "డిజిటల్ సరౌండ్ 5.1 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4559 msgid "Digital Stereo (HDMI)" msgstr "డిజిటల్ స్టీరియో (HDMI)" #: spa/plugins/alsa/acp/alsa-mixer.c:4560 #, fuzzy msgid "Digital Surround 5.1 (HDMI)" msgstr "డిజిటల్ సరౌండ్ 5.1 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4561 msgid "Chat" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4562 msgid "Game" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4696 msgid "Analog Mono Duplex" msgstr "ఎనలాగ్ మోనో డుప్లెక్స్" #: spa/plugins/alsa/acp/alsa-mixer.c:4697 msgid "Analog Stereo Duplex" msgstr "ఎనలాగ్ స్టీరియో డుప్లెక్స్" #: spa/plugins/alsa/acp/alsa-mixer.c:4700 msgid "Digital Stereo Duplex (IEC958)" msgstr "డిజిటల్ స్టీరియో డుప్లెక్స్ (IEC958)" #: spa/plugins/alsa/acp/alsa-mixer.c:4701 msgid "Multichannel Duplex" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4702 #, fuzzy msgid "Stereo Duplex" msgstr "ఎనలాగ్ స్టీరియో డుప్లెక్స్" #: spa/plugins/alsa/acp/alsa-mixer.c:4703 msgid "Mono Chat + 7.1 Surround" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4806 #, fuzzy, c-format msgid "%s Output" msgstr "Null అవుట్పుట్" #: spa/plugins/alsa/acp/alsa-mixer.c:4813 #, fuzzy, c-format msgid "%s Input" msgstr "ఇన్పుట్" #: spa/plugins/alsa/acp/alsa-util.c:1175 spa/plugins/alsa/acp/alsa-util.c:1269 #, fuzzy, c-format msgid "" "snd_pcm_avail() returned a value that is exceptionally large: %lu byte (%lu " "ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_avail() returned a value that is exceptionally large: %lu bytes (%lu " "ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "snd_pcm_avail() అనునది పెద్ద విలువను యిచ్చినది: %lu bytes (%lu ms).\n" "సాదారణంగా యిది ALSA డ్రైవర్ '%s' నందలి బగ్ కావచ్చును. దయచేసి దీనిని ALSA అభివృద్ది కారులకు " "నివేదించుము." msgstr[1] "" "snd_pcm_avail() అనునది పెద్ద విలువను యిచ్చినది: %lu bytes (%lu ms).\n" "సాదారణంగా యిది ALSA డ్రైవర్ '%s' నందలి బగ్ కావచ్చును. దయచేసి దీనిని ALSA అభివృద్ది కారులకు " "నివేదించుము." #: spa/plugins/alsa/acp/alsa-util.c:1241 #, fuzzy, c-format msgid "" "snd_pcm_delay() returned a value that is exceptionally large: %li byte (%s" "%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_delay() returned a value that is exceptionally large: %li bytes (%s" "%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "snd_pcm_delay() అనునది పెద్ద విలువను యిచ్చినది: %li bytes (%s%lu ms).\n" "సాదారణంగా యిది ALSA డ్రైవర్ '%s' నందు బగ్ కావచ్చును . దయచేసి దీనిని ALSA అభివృద్దికారులక " "నివేదించుము." msgstr[1] "" "snd_pcm_delay() అనునది పెద్ద విలువను యిచ్చినది: %li bytes (%s%lu ms).\n" "సాదారణంగా యిది ALSA డ్రైవర్ '%s' నందు బగ్ కావచ్చును . దయచేసి దీనిని ALSA అభివృద్దికారులక " "నివేదించుము." #: spa/plugins/alsa/acp/alsa-util.c:1288 #, fuzzy, c-format msgid "" "snd_pcm_avail_delay() returned strange values: delay %lu is less than avail " "%lu.\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr "" "snd_pcm_avail() అనునది పెద్ద విలువను యిచ్చినది: %lu bytes (%lu ms).\n" "సాదారణంగా యిది ALSA డ్రైవర్ '%s' నందలి బగ్ కావచ్చును. దయచేసి దీనిని ALSA అభివృద్ది కారులకు " "నివేదించుము." #: spa/plugins/alsa/acp/alsa-util.c:1331 #, fuzzy, c-format msgid "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu byte " "(%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu bytes " "(%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "snd_pcm_mmap_begin() అనునది పెద్ద విలువను యిచ్చినది: %lu bytes (%lu ms).\n" "సాదారణంగా యిది ALSA డ్రైవర్ '%s'నందలి బగ్ కావచ్చును. దయచేసి దీనిని ALSA అభివృద్దికారులను నివేదించండి." msgstr[1] "" "snd_pcm_mmap_begin() అనునది పెద్ద విలువను యిచ్చినది: %lu bytes (%lu ms).\n" "సాదారణంగా యిది ALSA డ్రైవర్ '%s'నందలి బగ్ కావచ్చును. దయచేసి దీనిని ALSA అభివృద్దికారులను నివేదించండి." #: spa/plugins/bluez5/bluez5-device.c:1010 msgid "Audio Gateway (A2DP Source & HSP/HFP AG)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1033 #, c-format msgid "High Fidelity Playback (A2DP Sink, codec %s)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1035 #, c-format msgid "High Fidelity Duplex (A2DP Source/Sink, codec %s)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1041 msgid "High Fidelity Playback (A2DP Sink)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1043 msgid "High Fidelity Duplex (A2DP Source/Sink)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1070 #, c-format msgid "Headset Head Unit (HSP/HFP, codec %s)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1074 msgid "Headset Head Unit (HSP/HFP)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1140 msgid "Handsfree" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1155 #, fuzzy msgid "Headphone" msgstr "ఎనలాగ్ హెడ్‌ఫోన్స్" #: spa/plugins/bluez5/bluez5-device.c:1160 msgid "Portable" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1165 msgid "Car" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1170 msgid "HiFi" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1175 msgid "Phone" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1181 msgid "Bluetooth" msgstr "" pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/po/tr.po000066400000000000000000000555431511204443500221150ustar00rootroot00000000000000# Turkish translation for PipeWire. # Copyright (C) 2014-2025 PipeWire's COPYRIGHT HOLDER # This file is distributed under the same license as the PipeWire package. # # Necdet Yücel , 2014. # Kaan Özdinçer , 2014. # Muhammet Kara , 2015, 2016, 2017. # Oğuz Ersen , 2021-2022. # Sabri Ünal , 2024, 2025. # msgid "" msgstr "" "Project-Id-Version: PipeWire master\n" "Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/pipewire/-/" "issues\n" "POT-Creation-Date: 2025-10-24 15:37+0000\n" "PO-Revision-Date: 2025-10-24 20:15+0300\n" "Last-Translator: Sabri Ünal \n" "Language-Team: Türkçe \n" "Language: tr\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" "X-Generator: Poedit 3.8\n" #: src/daemon/pipewire.c:29 #, c-format msgid "" "%s [options]\n" " -h, --help Show this help\n" " -v, --verbose Increase verbosity by one level\n" " --version Show version\n" " -c, --config Load config (Default %s)\n" " -P --properties Set context properties\n" msgstr "" "%s [seçenekler]\n" " -h, --help Bu yardımı göster\n" " -v, --verbose Ayrıntı düzeyini bir düzey artır\n" " --version Sürümü göster\n" " -c, --config Yapılandırmayı yükle (Öntanımlı %s)\n" " -P --properties Bağlam özelliklerini ayarla\n" #: src/daemon/pipewire.desktop.in:3 msgid "PipeWire Media System" msgstr "PipeWire Ortam Sistemi" #: src/daemon/pipewire.desktop.in:4 msgid "Start the PipeWire Media System" msgstr "PipeWire Ortam Sistemini Başlat" #: src/modules/module-protocol-pulse/modules/module-tunnel-sink.c:159 #: src/modules/module-protocol-pulse/modules/module-tunnel-source.c:159 #, c-format msgid "Tunnel to %s%s%s" msgstr "%s%s%s tüneli" #: src/modules/module-fallback-sink.c:40 msgid "Dummy Output" msgstr "Temsili Çıkış" #: src/modules/module-pulse-tunnel.c:760 #, c-format msgid "Tunnel for %s@%s" msgstr "%s@%s için tünel" #: src/modules/module-zeroconf-discover.c:320 msgid "Unknown device" msgstr "Bilinmeyen aygıt" #: src/modules/module-zeroconf-discover.c:332 #, c-format msgid "%s on %s@%s" msgstr "%s, %s@%s" #: src/modules/module-zeroconf-discover.c:336 #, c-format msgid "%s on %s" msgstr "%s, %s" #: src/tools/pw-cat.c:1096 #, c-format msgid "" "%s [options] [|-]\n" " -h, --help Show this help\n" " --version Show version\n" " -v, --verbose Enable verbose operations\n" "\n" msgstr "" "%s [seçenekler] [|-]\n" " -h, --help Bu yardımı göster\n" " --version Sürümü göster\n" " -v, --verbose Ayrıntılı işlemleri etkinleştir\n" "\n" #: src/tools/pw-cat.c:1103 #, c-format msgid "" " -R, --remote Remote daemon name\n" " --media-type Set media type (default %s)\n" " --media-category Set media category (default %s)\n" " --media-role Set media role (default %s)\n" " --target Set node target serial or name " "(default %s)\n" " 0 means don't link\n" " --latency Set node latency (default %s)\n" " Xunit (unit = s, ms, us, ns)\n" " or direct samples (256)\n" " the rate is the one of the source " "file\n" " -P --properties Set node properties\n" "\n" msgstr "" " -R, --remote Uzak art alan hizmeti adı\n" " --media-type Ortam türünü ayarla (öntanımlı %s)\n" " --media-category Ortam kategorisini ayarla (öntanımlı " "%s)\n" " --media-role Ortam rolünü ayarla (öntanımlı %s)\n" " --target Düğüm hedefi seri ya da adını ayarla " "(öntanımlı %s)\n" " 0, bağlanmayacağı anlamına gelir\n" " --latency Düğüm gecikmesini ayarla (öntanımlı " "%s)\n" " Xbirim (birim = s, ms, us, ns)\n" " veya doğrudan örneklemeler (256)\n" " oran kaynak dosyadan biridir\n" " -P --properties Düğüm özelliklerini ayarla\n" "\n" #: src/tools/pw-cat.c:1121 #, c-format msgid "" " --rate Sample rate (req. for rec) (default " "%u)\n" " --channels Number of channels (req. for rec) " "(default %u)\n" " --channel-map Channel map\n" " one of: \"stereo\", " "\"surround-51\",... or\n" " comma separated list of channel " "names: eg. \"FL,FR\"\n" " --format Sample format %s (req. for rec) " "(default %s)\n" " --volume Stream volume 0-1.0 (default %.3f)\n" " -q --quality Resampler quality (0 - 15) (default " "%d)\n" " -a, --raw RAW mode\n" " -M, --force-midi Force midi format, one of \"midi\" " "or \"ump\", (default ump)\n" " -n, --sample-count COUNT Stop after COUNT samples\n" "\n" msgstr "" " --rate Örnekleme oranı (kayıt için gerekli) " "(öntanımlı %u)\n" " --channels Kanal sayısı (kayıt için gerekli) " "(öntanımlı %u)\n" " --channel-map Kanal haritası\n" " şunlardan biri: \"stereo\", " "\"surround-51\",... veya\n" " kanal adlarının virgülle " "ayrılmış listesi: örn. \"FL,FR\"\n" " --format Örnekleme biçimi %s (kayıt için " "gerekli) (öntanımlı %s)\n" " --volume Akış ses seviyesi 0-1.0 (öntanımlı " "%.3f)\n" " -q --quality Yeniden örnekleyici kalitesi (0 - " "15) (öntanımlı %d)\n" " -a, --raw HAM kipi\n" " -M, --force-midi Midi biçimini zorla, ikisinden " "birisi \"midi\" ya da\"ump\", (öntanımlı ump)\n" " -n, --sample-count COUNT COUNT örnekleme sonrası dur\n" "\n" #: src/tools/pw-cat.c:1141 msgid "" " -p, --playback Playback mode\n" " -r, --record Recording mode\n" " -m, --midi Midi mode\n" " -d, --dsd DSD mode\n" " -o, --encoded Encoded mode\n" " -s, --sysex SysEx mode\n" " -c, --midi-clip MIDI clip mode\n" "\n" msgstr "" " -p, --playback Çalma kipi\n" " -r, --record Kayıt kipi\n" " -m, --midi Midi kipi\n" " -d, --dsd DSD kipi\n" " -o, --encoded Kodlanmış kip\n" " -s, --sysex SysEx kipi\n" " -c, --midi-clip MIDI klip kipi\n" "\n" #: src/tools/pw-cli.c:2386 #, c-format msgid "" "%s [options] [command]\n" " -h, --help Show this help\n" " --version Show version\n" " -d, --daemon Start as daemon (Default false)\n" " -r, --remote Remote daemon name\n" " -m, --monitor Monitor activity\n" "\n" msgstr "" "%s [seçenekler] [komut]\n" " -h, --help Bu yardımı göster\n" " --version Sürümü göster\n" " -d, --daemon Art alan hizmeti olarak başlat " "(Öntanımlı olarak yanlış)\n" " -r, --remote Uzak arka plan programı adı\n" " -m, --monitor Etkinliği izle\n" #: spa/plugins/alsa/acp/acp.c:361 msgid "Pro Audio" msgstr "Profesyonel Ses" #: spa/plugins/alsa/acp/acp.c:537 spa/plugins/alsa/acp/alsa-mixer.c:4699 #: spa/plugins/bluez5/bluez5-device.c:1976 msgid "Off" msgstr "Kapalı" #: spa/plugins/alsa/acp/acp.c:620 #, c-format msgid "%s [ALSA UCM error]" msgstr "%s [ALSA UCM hatası]" #: spa/plugins/alsa/acp/alsa-mixer.c:2721 msgid "Input" msgstr "Giriş" #: spa/plugins/alsa/acp/alsa-mixer.c:2722 msgid "Docking Station Input" msgstr "Yerleştirme İstasyonu Girişi" #: spa/plugins/alsa/acp/alsa-mixer.c:2723 msgid "Docking Station Microphone" msgstr "Yerleştirme İstasyonu Mikrofonu" #: spa/plugins/alsa/acp/alsa-mixer.c:2724 msgid "Docking Station Line In" msgstr "Yerleştirme İstasyonu Hat Girişi" #: spa/plugins/alsa/acp/alsa-mixer.c:2725 #: spa/plugins/alsa/acp/alsa-mixer.c:2816 msgid "Line In" msgstr "Hat Girişi" #: spa/plugins/alsa/acp/alsa-mixer.c:2726 #: spa/plugins/alsa/acp/alsa-mixer.c:2810 #: spa/plugins/bluez5/bluez5-device.c:2374 msgid "Microphone" msgstr "Mikrofon" #: spa/plugins/alsa/acp/alsa-mixer.c:2727 #: spa/plugins/alsa/acp/alsa-mixer.c:2811 msgid "Front Microphone" msgstr "Ön Mikrofon" #: spa/plugins/alsa/acp/alsa-mixer.c:2728 #: spa/plugins/alsa/acp/alsa-mixer.c:2812 msgid "Rear Microphone" msgstr "Arka Mikrofon" #: spa/plugins/alsa/acp/alsa-mixer.c:2729 msgid "External Microphone" msgstr "Harici Mikrofon" #: spa/plugins/alsa/acp/alsa-mixer.c:2730 #: spa/plugins/alsa/acp/alsa-mixer.c:2814 msgid "Internal Microphone" msgstr "Dahili Mikrofon" #: spa/plugins/alsa/acp/alsa-mixer.c:2731 #: spa/plugins/alsa/acp/alsa-mixer.c:2817 msgid "Radio" msgstr "Radyo" #: spa/plugins/alsa/acp/alsa-mixer.c:2732 #: spa/plugins/alsa/acp/alsa-mixer.c:2818 msgid "Video" msgstr "Video" #: spa/plugins/alsa/acp/alsa-mixer.c:2733 msgid "Automatic Gain Control" msgstr "Otomatik Kazanç Denetimi" #: spa/plugins/alsa/acp/alsa-mixer.c:2734 msgid "No Automatic Gain Control" msgstr "Otomatik Kazanç Denetimi Yok" #: spa/plugins/alsa/acp/alsa-mixer.c:2735 msgid "Boost" msgstr "Artır" #: spa/plugins/alsa/acp/alsa-mixer.c:2736 msgid "No Boost" msgstr "Artırma Yok" #: spa/plugins/alsa/acp/alsa-mixer.c:2737 msgid "Amplifier" msgstr "Yükseltici" #: spa/plugins/alsa/acp/alsa-mixer.c:2738 msgid "No Amplifier" msgstr "Yükseltici Yok" #: spa/plugins/alsa/acp/alsa-mixer.c:2739 msgid "Bass Boost" msgstr "Bas Artır" #: spa/plugins/alsa/acp/alsa-mixer.c:2740 msgid "No Bass Boost" msgstr "Bas Artırma Yok" #: spa/plugins/alsa/acp/alsa-mixer.c:2741 #: spa/plugins/bluez5/bluez5-device.c:2380 msgid "Speaker" msgstr "Hoparlör" #. Don't call it "headset", the HF one has the mic #: spa/plugins/alsa/acp/alsa-mixer.c:2742 #: spa/plugins/alsa/acp/alsa-mixer.c:2820 #: spa/plugins/bluez5/bluez5-device.c:2386 #: spa/plugins/bluez5/bluez5-device.c:2453 msgid "Headphones" msgstr "Kulaklık" #: spa/plugins/alsa/acp/alsa-mixer.c:2809 msgid "Analog Input" msgstr "Analog Giriş" #: spa/plugins/alsa/acp/alsa-mixer.c:2813 msgid "Dock Microphone" msgstr "Yapışık Mikrofon" #: spa/plugins/alsa/acp/alsa-mixer.c:2815 msgid "Headset Microphone" msgstr "Mikrofonlu Kulaklık" #: spa/plugins/alsa/acp/alsa-mixer.c:2819 msgid "Analog Output" msgstr "Analog Çıkış" #: spa/plugins/alsa/acp/alsa-mixer.c:2821 msgid "Headphones 2" msgstr "Kulaklık 2" #: spa/plugins/alsa/acp/alsa-mixer.c:2822 msgid "Headphones Mono Output" msgstr "Kulaklık Tek Kanallı Çıkış" #: spa/plugins/alsa/acp/alsa-mixer.c:2823 msgid "Line Out" msgstr "Hat Çıkışı" #: spa/plugins/alsa/acp/alsa-mixer.c:2824 msgid "Analog Mono Output" msgstr "Analog Tek Kanallı Çıkış" #: spa/plugins/alsa/acp/alsa-mixer.c:2825 msgid "Speakers" msgstr "Hoparlörler" #: spa/plugins/alsa/acp/alsa-mixer.c:2826 msgid "HDMI / DisplayPort" msgstr "HDMI / DisplayPort" #: spa/plugins/alsa/acp/alsa-mixer.c:2827 msgid "Digital Output (S/PDIF)" msgstr "Sayısal Çıkış (S/PDIF)" #: spa/plugins/alsa/acp/alsa-mixer.c:2828 msgid "Digital Input (S/PDIF)" msgstr "Sayısal Giriş (S/PDIF)" #: spa/plugins/alsa/acp/alsa-mixer.c:2829 msgid "Multichannel Input" msgstr "Çok Kanallı Giriş" #: spa/plugins/alsa/acp/alsa-mixer.c:2830 msgid "Multichannel Output" msgstr "Çok Kanallı Çıkış" #: spa/plugins/alsa/acp/alsa-mixer.c:2831 msgid "Game Output" msgstr "Oyun Çıkışı" #: spa/plugins/alsa/acp/alsa-mixer.c:2832 #: spa/plugins/alsa/acp/alsa-mixer.c:2833 msgid "Chat Output" msgstr "Sohbet Çıkışı" #: spa/plugins/alsa/acp/alsa-mixer.c:2834 msgid "Chat Input" msgstr "Sohbet Girişi" #: spa/plugins/alsa/acp/alsa-mixer.c:2835 msgid "Virtual Surround 7.1" msgstr "Sanal Çevresel Ses 7.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4522 msgid "Analog Mono" msgstr "Analog Tek Kanallı" #: spa/plugins/alsa/acp/alsa-mixer.c:4523 msgid "Analog Mono (Left)" msgstr "Analog Tek Kanallı (Sol)" #: spa/plugins/alsa/acp/alsa-mixer.c:4524 msgid "Analog Mono (Right)" msgstr "Analog Tek Kanallı (Sağ)" #. Note: Not translated to "Analog Stereo Input", because the source #. * name gets "Input" appended to it automatically, so adding "Input" #. * here would lead to the source name to become "Analog Stereo Input #. * Input". The same logic applies to analog-stereo-output, #. * multichannel-input and multichannel-output. #: spa/plugins/alsa/acp/alsa-mixer.c:4525 #: spa/plugins/alsa/acp/alsa-mixer.c:4533 #: spa/plugins/alsa/acp/alsa-mixer.c:4534 msgid "Analog Stereo" msgstr "Analog Stereo" #: spa/plugins/alsa/acp/alsa-mixer.c:4526 msgid "Mono" msgstr "Tek Kanallı" #: spa/plugins/alsa/acp/alsa-mixer.c:4527 msgid "Stereo" msgstr "Stereo" #: spa/plugins/alsa/acp/alsa-mixer.c:4535 #: spa/plugins/alsa/acp/alsa-mixer.c:4693 #: spa/plugins/bluez5/bluez5-device.c:2362 msgid "Headset" msgstr "Kulaklık" #: spa/plugins/alsa/acp/alsa-mixer.c:4536 #: spa/plugins/alsa/acp/alsa-mixer.c:4694 msgid "Speakerphone" msgstr "Hoparlör" #: spa/plugins/alsa/acp/alsa-mixer.c:4537 #: spa/plugins/alsa/acp/alsa-mixer.c:4538 msgid "Multichannel" msgstr "Çok kanallı" #: spa/plugins/alsa/acp/alsa-mixer.c:4539 msgid "Analog Surround 2.1" msgstr "Analog Çevresel Ses 2.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4540 msgid "Analog Surround 3.0" msgstr "Analog Çevresel Ses 3.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4541 msgid "Analog Surround 3.1" msgstr "Analog Çevresel Ses 3.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4542 msgid "Analog Surround 4.0" msgstr "Analog Çevresel Ses 4.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4543 msgid "Analog Surround 4.1" msgstr "Analog Çevresel Ses 4.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4544 msgid "Analog Surround 5.0" msgstr "Analog Çevresel Ses 5.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4545 msgid "Analog Surround 5.1" msgstr "Analog Çevresel Ses 5.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4546 msgid "Analog Surround 6.0" msgstr "Analog Çevresel Ses 6.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4547 msgid "Analog Surround 6.1" msgstr "Analog Çevresel Ses 6.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4548 msgid "Analog Surround 7.0" msgstr "Analog Çevresel Ses 7.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4549 msgid "Analog Surround 7.1" msgstr "Analog Çevresel Ses 7.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4550 msgid "Digital Stereo (IEC958)" msgstr "Sayısal Stereo (IEC958)" #: spa/plugins/alsa/acp/alsa-mixer.c:4551 msgid "Digital Surround 4.0 (IEC958/AC3)" msgstr "Sayısal Çevresel Ses 4.0 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4552 msgid "Digital Surround 5.1 (IEC958/AC3)" msgstr "Sayısal Çevresel Ses 5.1 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4553 msgid "Digital Surround 5.1 (IEC958/DTS)" msgstr "Sayısal Çevresel Ses 5.1 (IEC958/DTS)" #: spa/plugins/alsa/acp/alsa-mixer.c:4554 msgid "Digital Stereo (HDMI)" msgstr "Sayısal Stereo (HDMI)" #: spa/plugins/alsa/acp/alsa-mixer.c:4555 msgid "Digital Surround 5.1 (HDMI)" msgstr "Sayısal Çevresel Ses 5.1 (HDMI)" #: spa/plugins/alsa/acp/alsa-mixer.c:4556 msgid "Chat" msgstr "Sohbet" #: spa/plugins/alsa/acp/alsa-mixer.c:4557 msgid "Game" msgstr "Oyun" #: spa/plugins/alsa/acp/alsa-mixer.c:4691 msgid "Analog Mono Duplex" msgstr "Analog Tek Kanallı İkili" #: spa/plugins/alsa/acp/alsa-mixer.c:4692 msgid "Analog Stereo Duplex" msgstr "Analog İkili Stereo" #: spa/plugins/alsa/acp/alsa-mixer.c:4695 msgid "Digital Stereo Duplex (IEC958)" msgstr "Sayısal İkili Stereo (IEC958)" #: spa/plugins/alsa/acp/alsa-mixer.c:4696 msgid "Multichannel Duplex" msgstr "Çok Kanallı İkili" #: spa/plugins/alsa/acp/alsa-mixer.c:4697 msgid "Stereo Duplex" msgstr "İkili Stereo" #: spa/plugins/alsa/acp/alsa-mixer.c:4698 msgid "Mono Chat + 7.1 Surround" msgstr "Tek Kanallı Sohbet + 7.1 Çevresel Ses" #: spa/plugins/alsa/acp/alsa-mixer.c:4799 #, c-format msgid "%s Output" msgstr "%s Çıkışı" #: spa/plugins/alsa/acp/alsa-mixer.c:4807 #, c-format msgid "%s Input" msgstr "%s Girişi" #: spa/plugins/alsa/acp/alsa-util.c:1233 spa/plugins/alsa/acp/alsa-util.c:1327 #, c-format msgid "" "snd_pcm_avail() returned a value that is exceptionally large: %lu byte (%lu " "ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_avail() returned a value that is exceptionally large: %lu bytes (%lu " "ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "snd_pcm_avail() son derece büyük bir değer döndürdü: %lu bayt (%lu ms).\n" "Büyük ihtimalle bu bir ALSA sürücüsü '%s' hatasıdır. Lütfen bu sorunu ALSA " "geliştiricilerine bildirin." #: spa/plugins/alsa/acp/alsa-util.c:1299 #, c-format msgid "" "snd_pcm_delay() returned a value that is exceptionally large: %li byte " "(%s%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_delay() returned a value that is exceptionally large: %li bytes " "(%s%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "snd_pcm_delay() son derece büyük bir değer döndürdü: %li bayt (%s%lu ms).\n" "Büyük ihtimalle bu bir ALSA sürücüsü '%s' hatasıdır. Lütfen bu sorunu ALSA " "geliştiricilerine bildirin." #: spa/plugins/alsa/acp/alsa-util.c:1346 #, c-format msgid "" "snd_pcm_avail_delay() returned strange values: delay %lu is less than avail " "%lu.\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr "" "snd_pcm_avail_delay() garip değerler döndü: gecikme %lu kazançtan %lu daha " "azdır.\n" "Büyük ihtimalle bu bir ALSA sürücüsü '%s' hatasıdır. Lütfen bu sorunu ALSA " "geliştiricilerine bildirin." #: spa/plugins/alsa/acp/alsa-util.c:1389 #, c-format msgid "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu byte " "(%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu bytes " "(%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "snd_pcm_mmap_begin() son derece büyük bir değer döndürdü: %lu bayt (%lu " "ms).\n" "Büyük ihtimalle bu bir ALSA sürücüsü '%s' hatasıdır. Lütfen bu sorunu ALSA " "geliştiricilerine bildirin." #: spa/plugins/alsa/acp/channelmap.h:460 msgid "(invalid)" msgstr "(geçersiz)" #: spa/plugins/alsa/acp/compat.c:194 msgid "Built-in Audio" msgstr "Dahili Ses" #: spa/plugins/alsa/acp/compat.c:199 msgid "Modem" msgstr "Modem" #: spa/plugins/bluez5/bluez5-device.c:1987 msgid "Audio Gateway (A2DP Source & HSP/HFP AG)" msgstr "Ses Geçidi (A2DP Kaynak & HSP/HFP AG)" #: spa/plugins/bluez5/bluez5-device.c:2016 msgid "Audio Streaming for Hearing Aids (ASHA Sink)" msgstr "İşitme Aygıtları İçin Ses Akışı (ASHA Alıcı)" #: spa/plugins/bluez5/bluez5-device.c:2059 #, c-format msgid "High Fidelity Playback (A2DP Sink, codec %s)" msgstr "Yüksek Kaliteli Çalma (A2DP Alıcı, çözücü %s)" #: spa/plugins/bluez5/bluez5-device.c:2062 #, c-format msgid "High Fidelity Duplex (A2DP Source/Sink, codec %s)" msgstr "Yüksek Kaliteli İkili (A2DP Kaynak/Alıcı, çözücü %s)" #: spa/plugins/bluez5/bluez5-device.c:2070 msgid "High Fidelity Playback (A2DP Sink)" msgstr "Yüksek Kaliteli Çalma (A2DP Alıcı)" #: spa/plugins/bluez5/bluez5-device.c:2072 msgid "High Fidelity Duplex (A2DP Source/Sink)" msgstr "Yüksek Kaliteli İkili (A2DP Kaynak/Alıcı)" #: spa/plugins/bluez5/bluez5-device.c:2146 #, c-format msgid "High Fidelity Playback (BAP Sink, codec %s)" msgstr "Yüksek Kaliteli Çalma (BAP Alıcı, çözücü %s)" #: spa/plugins/bluez5/bluez5-device.c:2151 #, c-format msgid "High Fidelity Input (BAP Source, codec %s)" msgstr "Yüksek Kaliteli Giriş (BAP Kaynak, çözücü %s)" #: spa/plugins/bluez5/bluez5-device.c:2155 #, c-format msgid "High Fidelity Duplex (BAP Source/Sink, codec %s)" msgstr "Yüksek Kaliteli İkili (BAP Kaynak/Alıcı, çözücü %s)" #: spa/plugins/bluez5/bluez5-device.c:2164 msgid "High Fidelity Playback (BAP Sink)" msgstr "Yüksek Kaliteli Çalma (BAP Alıcı)" #: spa/plugins/bluez5/bluez5-device.c:2168 msgid "High Fidelity Input (BAP Source)" msgstr "Yüksek Kaliteli Giriş (BAP Kaynak)" #: spa/plugins/bluez5/bluez5-device.c:2171 msgid "High Fidelity Duplex (BAP Source/Sink)" msgstr "Yüksek Kaliteli İkili (BAP Kaynak/Alıcı)" #: spa/plugins/bluez5/bluez5-device.c:2211 #, c-format msgid "Headset Head Unit (HSP/HFP, codec %s)" msgstr "Kulaklık Ana Birimi (HSP/HFP, çözücü %s)" #: spa/plugins/bluez5/bluez5-device.c:2363 #: spa/plugins/bluez5/bluez5-device.c:2368 #: spa/plugins/bluez5/bluez5-device.c:2375 #: spa/plugins/bluez5/bluez5-device.c:2381 #: spa/plugins/bluez5/bluez5-device.c:2387 #: spa/plugins/bluez5/bluez5-device.c:2393 #: spa/plugins/bluez5/bluez5-device.c:2399 #: spa/plugins/bluez5/bluez5-device.c:2405 #: spa/plugins/bluez5/bluez5-device.c:2411 msgid "Handsfree" msgstr "Ahizesiz" #: spa/plugins/bluez5/bluez5-device.c:2369 msgid "Handsfree (HFP)" msgstr "Ahizesiz (HFP)" #: spa/plugins/bluez5/bluez5-device.c:2392 msgid "Portable" msgstr "Taşınabilir" #: spa/plugins/bluez5/bluez5-device.c:2398 msgid "Car" msgstr "Araba" #: spa/plugins/bluez5/bluez5-device.c:2404 msgid "HiFi" msgstr "Yüksek Kalite" #: spa/plugins/bluez5/bluez5-device.c:2410 msgid "Phone" msgstr "Telefon" #: spa/plugins/bluez5/bluez5-device.c:2417 msgid "Bluetooth" msgstr "Bluetooth" #: spa/plugins/bluez5/bluez5-device.c:2418 msgid "Bluetooth (HFP)" msgstr "Bluetooth (HFP)" pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/po/uk.po000066400000000000000000000706131511204443500221020ustar00rootroot00000000000000# Copyright (C) 2009 Free Software Foundation, Inc. # This file is distributed under the same license as the pipewire package. # # Yuri Chornoivan , 2009-2021, 2022, 2023. msgid "" msgstr "" "Project-Id-Version: pipewire\n" "Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/pipewire/-/issu" "es\n" "POT-Creation-Date: 2023-02-06 15:27+0000\n" "PO-Revision-Date: 2023-02-11 17:42+0200\n" "Last-Translator: Yuri Chornoivan \n" "Language-Team: Ukrainian \n" "Language: uk\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<" "=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" "X-Generator: Lokalize 20.12.0\n" #: src/daemon/pipewire.c:46 #, c-format msgid "" "%s [options]\n" " -h, --help Show this help\n" " --version Show version\n" " -c, --config Load config (Default %s)\n" msgstr "" "%s [параметри]\n" " -h, --help вивести довідку\n" " --version вивести дані щодо версії\n" " -c, --config завантажити налаштування (типово, " "%s)\n" #: src/daemon/pipewire.desktop.in:4 msgid "PipeWire Media System" msgstr "Мультимедійна система PipeWire" #: src/daemon/pipewire.desktop.in:5 msgid "Start the PipeWire Media System" msgstr "Запустити мультимедійну систему PipeWire" #: src/modules/module-protocol-pulse/modules/module-tunnel-sink.c:179 #: src/modules/module-protocol-pulse/modules/module-tunnel-source.c:179 #, c-format msgid "Tunnel to %s/%s" msgstr "Тунель до %s/%s" #: src/modules/module-fallback-sink.c:51 msgid "Dummy Output" msgstr "Фіктивний вихід" #: src/modules/module-pulse-tunnel.c:695 #, c-format msgid "Tunnel for %s@%s" msgstr "Тунель для %s@%s" #: src/modules/module-zeroconf-discover.c:335 msgid "Unknown device" msgstr "Невідомий пристрій" #: src/modules/module-zeroconf-discover.c:347 #, c-format msgid "%s on %s@%s" msgstr "%s на %s@%s" #: src/modules/module-zeroconf-discover.c:351 #, c-format msgid "%s on %s" msgstr "%s на %s" #: src/tools/pw-cat.c:940 #, c-format msgid "" "%s [options] [|-]\n" " -h, --help Show this help\n" " --version Show version\n" " -v, --verbose Enable verbose operations\n" "\n" msgstr "" "%s [параметри] [<файл>|-]\n" " -h, --help вивести довідку\n" " --version вивести дані щодо версії\n" " -v, --verbose ввімкнути відображення докладної " "інформації\n" "\n" #: src/tools/pw-cat.c:947 #, c-format #| msgid "" #| " -R, --remote Remote daemon name\n" #| " --media-type Set media type (default %s)\n" #| " --media-category Set media category (default %s)\n" #| " --media-role Set media role (default %s)\n" #| " --target Set node target (default %s)\n" #| " 0 means don't link\n" #| " --latency Set node latency (default %s)\n" #| " Xunit (unit = s, ms, us, ns)\n" #| " or direct samples (256)\n" #| " the rate is the one of the " #| "source file\n" #| " -P --properties Set node properties\n" #| "\n" msgid "" " -R, --remote Remote daemon name\n" " --media-type Set media type (default %s)\n" " --media-category Set media category (default %s)\n" " --media-role Set media role (default %s)\n" " --target Set node target serial or name " "(default %s)\n" " 0 means don't link\n" " --latency Set node latency (default %s)\n" " Xunit (unit = s, ms, us, ns)\n" " or direct samples (256)\n" " the rate is the one of the source " "file\n" " -P --properties Set node properties\n" "\n" msgstr "" " -R, --remote назва віддаленої фонової служби\n" " --media-type встановити тип мультимедіа (типово, " "%s)\n" " --media-category встановити категорію мультимедіа " "(типово, %s)\n" " --media-role встановити роль мультимедіа (типово, " "%s)\n" " --target встановити назву або серійний номер" " цілі вузла (типово, %s)\n" " 0 — не пов'язувати\n" " --latency встановити затримку вузла (типово, " "%s)\n" " Xодиниця (одиниця = s, ms, us, " "ns)\n" " або безпосередні семпли (256)\n" " частота — частота з файла джерела\n" " -P --properties встановити властивості вузла\n" "\n" #: src/tools/pw-cat.c:965 #, c-format msgid "" " --rate Sample rate (req. for rec) (default " "%u)\n" " --channels Number of channels (req. for rec) " "(default %u)\n" " --channel-map Channel map\n" " one of: \"stereo\", " "\"surround-51\",... or\n" " comma separated list of channel " "names: eg. \"FL,FR\"\n" " --format Sample format %s (req. for rec) " "(default %s)\n" " --volume Stream volume 0-1.0 (default %.3f)\n" " -q --quality Resampler quality (0 - 15) (default " "%d)\n" "\n" msgstr "" " --rate частота дискретизації (потрібна для " "запису) (типово, %u)\n" " --channels кількість каналів (потрібна для " "запису) (типово, %u)\n" " --channel-map карта каналів\n" " одне з таких значень: \"stereo" "\", \"surround-51\",... або\n" " список каналів, відокремлених " "комами; приклад: \"FL,FR\"\n" " --format формат семплу %s (потрібен для " "запису) (типово, %s)\n" " --volume гучність потоку 0-1.0 (типово, " "%.3f)\n" " -q --quality якість зміни дискретизації (0 - 15) " "(типово, %d)\n" "\n" #: src/tools/pw-cat.c:982 #| msgid "" #| " -p, --playback Playback mode\n" #| " -r, --record Recording mode\n" #| " -m, --midi Midi mode\n" #| " -d, --dsd DSD mode\n" #| "\n" msgid "" " -p, --playback Playback mode\n" " -r, --record Recording mode\n" " -m, --midi Midi mode\n" " -d, --dsd DSD mode\n" " -o, --encoded\t\t\t Encoded mode\n" "\n" msgstr "" " -p, --playback режим відтворення\n" " -r, --record режим запису\n" " -m, --midi режим MIDI\n" " -d, --dsd режим DSD\n" " -o, --encoded\t\t\t закодований режим\n" "\n" #: src/tools/pw-cli.c:2236 #, c-format #| msgid "" #| "%s [options] [command]\n" #| " -h, --help Show this help\n" #| " --version Show version\n" #| " -d, --daemon Start as daemon (Default false)\n" #| " -r, --remote Remote daemon name\n" #| "\n" msgid "" "%s [options] [command]\n" " -h, --help Show this help\n" " --version Show version\n" " -d, --daemon Start as daemon (Default false)\n" " -r, --remote Remote daemon name\n" " -m, --monitor Monitor activity\n" "\n" msgstr "" "%s [параметри] [команда]\n" " -h, --help вивести довідку\n" " --version вивести дані щодо версії\n" " -d, --daemon запустити як фонову службу (типово, " "false)\n" " -r, --remote назва віддаленої фонової служби\n" " -m, --monitor спостерігати за діями\n" "\n" #: spa/plugins/alsa/acp/acp.c:323 msgid "Pro Audio" msgstr "Професійний звук" #: spa/plugins/alsa/acp/acp.c:447 spa/plugins/alsa/acp/alsa-mixer.c:4648 #: spa/plugins/bluez5/bluez5-device.c:1303 msgid "Off" msgstr "Вимкнено" #: spa/plugins/alsa/acp/alsa-mixer.c:2652 msgid "Input" msgstr "Вхід" #: spa/plugins/alsa/acp/alsa-mixer.c:2653 msgid "Docking Station Input" msgstr "Вхідний канал док-станції" #: spa/plugins/alsa/acp/alsa-mixer.c:2654 msgid "Docking Station Microphone" msgstr "Мікрофон док-станції" #: spa/plugins/alsa/acp/alsa-mixer.c:2655 msgid "Docking Station Line In" msgstr "Лінійний вхід док-станції" #: spa/plugins/alsa/acp/alsa-mixer.c:2656 #: spa/plugins/alsa/acp/alsa-mixer.c:2747 msgid "Line In" msgstr "Лінійний вхід" #: spa/plugins/alsa/acp/alsa-mixer.c:2657 #: spa/plugins/alsa/acp/alsa-mixer.c:2741 #: spa/plugins/bluez5/bluez5-device.c:1536 msgid "Microphone" msgstr "Мікрофон" #: spa/plugins/alsa/acp/alsa-mixer.c:2658 #: spa/plugins/alsa/acp/alsa-mixer.c:2742 msgid "Front Microphone" msgstr "Передній мікрофон" #: spa/plugins/alsa/acp/alsa-mixer.c:2659 #: spa/plugins/alsa/acp/alsa-mixer.c:2743 msgid "Rear Microphone" msgstr "Задній мікрофон" #: spa/plugins/alsa/acp/alsa-mixer.c:2660 msgid "External Microphone" msgstr "Зовнішній мікрофон" #: spa/plugins/alsa/acp/alsa-mixer.c:2661 #: spa/plugins/alsa/acp/alsa-mixer.c:2745 msgid "Internal Microphone" msgstr "Вбудований мікрофон" #: spa/plugins/alsa/acp/alsa-mixer.c:2662 #: spa/plugins/alsa/acp/alsa-mixer.c:2748 msgid "Radio" msgstr "Радіо" #: spa/plugins/alsa/acp/alsa-mixer.c:2663 #: spa/plugins/alsa/acp/alsa-mixer.c:2749 msgid "Video" msgstr "Відео" #: spa/plugins/alsa/acp/alsa-mixer.c:2664 msgid "Automatic Gain Control" msgstr "Автоматичне керування підсиленням" #: spa/plugins/alsa/acp/alsa-mixer.c:2665 msgid "No Automatic Gain Control" msgstr "Без автоматичного керування підсиленням" #: spa/plugins/alsa/acp/alsa-mixer.c:2666 msgid "Boost" msgstr "Підсилення" #: spa/plugins/alsa/acp/alsa-mixer.c:2667 msgid "No Boost" msgstr "Без пісилення" #: spa/plugins/alsa/acp/alsa-mixer.c:2668 msgid "Amplifier" msgstr "Підсилювач" #: spa/plugins/alsa/acp/alsa-mixer.c:2669 msgid "No Amplifier" msgstr "Без підсилювача" #: spa/plugins/alsa/acp/alsa-mixer.c:2670 msgid "Bass Boost" msgstr "Підсилення басів" #: spa/plugins/alsa/acp/alsa-mixer.c:2671 msgid "No Bass Boost" msgstr "Без підсилення" #: spa/plugins/alsa/acp/alsa-mixer.c:2672 #: spa/plugins/bluez5/bluez5-device.c:1542 msgid "Speaker" msgstr "Динамік" #: spa/plugins/alsa/acp/alsa-mixer.c:2673 #: spa/plugins/alsa/acp/alsa-mixer.c:2751 msgid "Headphones" msgstr "Аналогові навушники" #: spa/plugins/alsa/acp/alsa-mixer.c:2740 msgid "Analog Input" msgstr "Аналогових вхід" #: spa/plugins/alsa/acp/alsa-mixer.c:2744 msgid "Dock Microphone" msgstr "Мікрофон док-станції" #: spa/plugins/alsa/acp/alsa-mixer.c:2746 msgid "Headset Microphone" msgstr "Мікрофон гарнітури" #: spa/plugins/alsa/acp/alsa-mixer.c:2750 msgid "Analog Output" msgstr "Аналогове відтворення" #: spa/plugins/alsa/acp/alsa-mixer.c:2752 msgid "Headphones 2" msgstr "Навушники 2" #: spa/plugins/alsa/acp/alsa-mixer.c:2753 msgid "Headphones Mono Output" msgstr "Моно-вихід навушників" #: spa/plugins/alsa/acp/alsa-mixer.c:2754 msgid "Line Out" msgstr "Лінійний вихід" #: spa/plugins/alsa/acp/alsa-mixer.c:2755 msgid "Analog Mono Output" msgstr "Аналоговий моно-вихід" #: spa/plugins/alsa/acp/alsa-mixer.c:2756 msgid "Speakers" msgstr "Акустичні колонки" #: spa/plugins/alsa/acp/alsa-mixer.c:2757 msgid "HDMI / DisplayPort" msgstr "HDMI / DisplayPort" #: spa/plugins/alsa/acp/alsa-mixer.c:2758 msgid "Digital Output (S/PDIF)" msgstr "Цифровий вихід (S/PDIF)" #: spa/plugins/alsa/acp/alsa-mixer.c:2759 msgid "Digital Input (S/PDIF)" msgstr "Цифровий вхід (S/PDIF)" #: spa/plugins/alsa/acp/alsa-mixer.c:2760 msgid "Multichannel Input" msgstr "Багатоканальний вхід" #: spa/plugins/alsa/acp/alsa-mixer.c:2761 msgid "Multichannel Output" msgstr "Багатоканальний вихід" #: spa/plugins/alsa/acp/alsa-mixer.c:2762 msgid "Game Output" msgstr "Ігровий вихід" #: spa/plugins/alsa/acp/alsa-mixer.c:2763 #: spa/plugins/alsa/acp/alsa-mixer.c:2764 msgid "Chat Output" msgstr "Вихід спілкування" #: spa/plugins/alsa/acp/alsa-mixer.c:2765 msgid "Chat Input" msgstr "Вхід спілкування" #: spa/plugins/alsa/acp/alsa-mixer.c:2766 msgid "Virtual Surround 7.1" msgstr "Віртуальний об'ємний звук 7.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4471 msgid "Analog Mono" msgstr "Аналогове моно" #: spa/plugins/alsa/acp/alsa-mixer.c:4472 msgid "Analog Mono (Left)" msgstr "Аналогове моно (лівий)" #: spa/plugins/alsa/acp/alsa-mixer.c:4473 msgid "Analog Mono (Right)" msgstr "Аналогове моно (правий)" #. Note: Not translated to "Analog Stereo Input", because the source #. * name gets "Input" appended to it automatically, so adding "Input" #. * here would lead to the source name to become "Analog Stereo Input #. * Input". The same logic applies to analog-stereo-output, #. * multichannel-input and multichannel-output. #: spa/plugins/alsa/acp/alsa-mixer.c:4474 #: spa/plugins/alsa/acp/alsa-mixer.c:4482 #: spa/plugins/alsa/acp/alsa-mixer.c:4483 msgid "Analog Stereo" msgstr "Аналогове стерео" #: spa/plugins/alsa/acp/alsa-mixer.c:4475 msgid "Mono" msgstr "Моно" #: spa/plugins/alsa/acp/alsa-mixer.c:4476 msgid "Stereo" msgstr "Стерео" #: spa/plugins/alsa/acp/alsa-mixer.c:4484 #: spa/plugins/alsa/acp/alsa-mixer.c:4642 #: spa/plugins/bluez5/bluez5-device.c:1524 msgid "Headset" msgstr "Гарнітура" #: spa/plugins/alsa/acp/alsa-mixer.c:4485 #: spa/plugins/alsa/acp/alsa-mixer.c:4643 msgid "Speakerphone" msgstr "Гучномовець" #: spa/plugins/alsa/acp/alsa-mixer.c:4486 #: spa/plugins/alsa/acp/alsa-mixer.c:4487 msgid "Multichannel" msgstr "Багатоканальний" #: spa/plugins/alsa/acp/alsa-mixer.c:4488 msgid "Analog Surround 2.1" msgstr "Аналоговий об'ємний 2.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4489 msgid "Analog Surround 3.0" msgstr "Аналоговий об'ємний 3.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4490 msgid "Analog Surround 3.1" msgstr "Аналоговий об'ємний 3.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4491 msgid "Analog Surround 4.0" msgstr "Аналоговий об'ємний 4.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4492 msgid "Analog Surround 4.1" msgstr "Аналоговий об'ємний 4.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4493 msgid "Analog Surround 5.0" msgstr "Аналоговий об'ємний 5.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4494 msgid "Analog Surround 5.1" msgstr "Аналоговий об'ємний 5.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4495 msgid "Analog Surround 6.0" msgstr "Аналоговий об'ємний 6.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4496 msgid "Analog Surround 6.1" msgstr "Аналоговий об'ємний 6.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4497 msgid "Analog Surround 7.0" msgstr "Аналоговий об'ємний 7.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4498 msgid "Analog Surround 7.1" msgstr "Аналоговий об'ємний 7.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4499 msgid "Digital Stereo (IEC958)" msgstr "Цифрове стерео (IEC958)" #: spa/plugins/alsa/acp/alsa-mixer.c:4500 msgid "Digital Surround 4.0 (IEC958/AC3)" msgstr "Цифровий об’ємний 4.0 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4501 msgid "Digital Surround 5.1 (IEC958/AC3)" msgstr "Цифровий об’ємний 5.1 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4502 msgid "Digital Surround 5.1 (IEC958/DTS)" msgstr "Цифровий об’ємний 5.1 (IEC958/DTS)" #: spa/plugins/alsa/acp/alsa-mixer.c:4503 msgid "Digital Stereo (HDMI)" msgstr "Цифровий стерео (HDMI)" #: spa/plugins/alsa/acp/alsa-mixer.c:4504 msgid "Digital Surround 5.1 (HDMI)" msgstr "Цифровий об’ємний 5.1 (HDMI)" #: spa/plugins/alsa/acp/alsa-mixer.c:4505 msgid "Chat" msgstr "Чат" #: spa/plugins/alsa/acp/alsa-mixer.c:4506 msgid "Game" msgstr "Гра" #: spa/plugins/alsa/acp/alsa-mixer.c:4640 msgid "Analog Mono Duplex" msgstr "Аналогове двобічне моно" #: spa/plugins/alsa/acp/alsa-mixer.c:4641 msgid "Analog Stereo Duplex" msgstr "Аналогове двобічне стерео" #: spa/plugins/alsa/acp/alsa-mixer.c:4644 msgid "Digital Stereo Duplex (IEC958)" msgstr "Цифрове двобічне стерео (IEC958)" #: spa/plugins/alsa/acp/alsa-mixer.c:4645 msgid "Multichannel Duplex" msgstr "Багатоканальний двобічний" #: spa/plugins/alsa/acp/alsa-mixer.c:4646 msgid "Stereo Duplex" msgstr "Двобічне стерео" #: spa/plugins/alsa/acp/alsa-mixer.c:4647 msgid "Mono Chat + 7.1 Surround" msgstr "Моно, спілкування + об'ємний 7.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4754 #, c-format msgid "%s Output" msgstr "%s-вихід" #: spa/plugins/alsa/acp/alsa-mixer.c:4761 #, c-format msgid "%s Input" msgstr "%s-вхід" #: spa/plugins/alsa/acp/alsa-util.c:1187 spa/plugins/alsa/acp/alsa-util.c:1281 #, c-format msgid "" "snd_pcm_avail() returned a value that is exceptionally large: %lu byte (%lu " "ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_avail() returned a value that is exceptionally large: %lu bytes (%lu " "ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "Функція snd_pcm_avail() повернула винятково велике значення: %lu байт (%lu " "мс).\n" "Ймовірно, ви натрапили на помилку у драйвері ALSA «%s». Будь ласка, " "повідомте про цю помилку розробникам ALSA." msgstr[1] "" "Функція snd_pcm_avail() повернула винятково велике значення: %lu байти (%lu " "мс).\n" "Ймовірно, ви натрапили на помилку у драйвері ALSA «%s». Будь ласка, " "повідомте про цю помилку розробникам ALSA." msgstr[2] "" "Функція snd_pcm_avail() повернула винятково велике значення: %lu байтів (%lu " "мс).\n" "Ймовірно, ви натрапили на помилку у драйвері ALSA «%s». Будь ласка, " "повідомте про цю помилку розробникам ALSA." #: spa/plugins/alsa/acp/alsa-util.c:1253 #, c-format msgid "" "snd_pcm_delay() returned a value that is exceptionally large: %li byte (%s" "%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_delay() returned a value that is exceptionally large: %li bytes (%s" "%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "Функція snd_pcm_delay() повернула винятково велике значення: %li байт (%s%lu " "мс).\n" "Ймовірно, ви натрапили на помилку у драйвері ALSA «%s». Будь ласка, " "повідомте про цю помилку розробникам ALSA." msgstr[1] "" "Функція snd_pcm_delay() повернула винятково велике значення: %li байти (%s" "%lu мс).\n" "Ймовірно, ви натрапили на помилку у драйвері ALSA «%s». Будь ласка, " "повідомте про цю помилку розробникам ALSA." msgstr[2] "" "Функція snd_pcm_delay() повернула винятково велике значення: %li байтів (%s" "%lu мс).\n" "Ймовірно, ви натрапили на помилку у драйвері ALSA «%s». Будь ласка, " "повідомте про цю помилку розробникам ALSA." #: spa/plugins/alsa/acp/alsa-util.c:1300 #, c-format msgid "" "snd_pcm_avail_delay() returned strange values: delay %lu is less than avail " "%lu.\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr "" "snd_pcm_avail_delay() повернуто дивні значення: затримка %lu є меншою за " "доступну, %lu.\n" "Ймовірно, це пов’язано з помилкою у драйвері ALSA «%s». Будь ласка, " "повідомте про цю помилку розробникам ALSA." #: spa/plugins/alsa/acp/alsa-util.c:1343 #, c-format msgid "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu byte " "(%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu bytes " "(%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "Функція snd_pcm_mmap_begin() повернула винятково велике значення: %lu байт " "(%lu мс).\n" "Ймовірно, ви натрапили на помилку у драйвері ALSA «%s». Будь ласка, " "повідомте про цю помилку розробникам ALSA." msgstr[1] "" "Функція snd_pcm_mmap_begin() повернула винятково велике значення: %lu байти " "(%lu мс).\n" "Ймовірно, ви натрапили на помилку у драйвері ALSA «%s». Будь ласка, " "повідомте про цю помилку розробникам ALSA." msgstr[2] "" "Функція snd_pcm_mmap_begin() повернула винятково велике значення: %lu байтів " "(%lu мс).\n" "Ймовірно, ви натрапили на помилку у драйвері ALSA «%s». Будь ласка, " "повідомте про цю помилку розробникам ALSA." #: spa/plugins/alsa/acp/channelmap.h:457 msgid "(invalid)" msgstr "(некоректний)" #: spa/plugins/alsa/acp/compat.c:189 msgid "Built-in Audio" msgstr "Вбудоване аудіо" #: spa/plugins/alsa/acp/compat.c:194 msgid "Modem" msgstr "Модем" #: spa/plugins/bluez5/bluez5-device.c:1314 msgid "Audio Gateway (A2DP Source & HSP/HFP AG)" msgstr "Звуковий шлюз (джерело A2DP і HSP/HFP AG)" #: spa/plugins/bluez5/bluez5-device.c:1339 #, c-format msgid "High Fidelity Playback (A2DP Sink, codec %s)" msgstr "Високоточне відтворення (приймач A2DP, кодек %s)" #: spa/plugins/bluez5/bluez5-device.c:1342 #, c-format msgid "High Fidelity Duplex (A2DP Source/Sink, codec %s)" msgstr "Двобічний високоточний обмін (джерело/приймач A2DP, кодек %s)" #: spa/plugins/bluez5/bluez5-device.c:1350 msgid "High Fidelity Playback (A2DP Sink)" msgstr "Високоточне відтворення (приймач A2DP)" #: spa/plugins/bluez5/bluez5-device.c:1352 msgid "High Fidelity Duplex (A2DP Source/Sink)" msgstr "Двобічний високоточний обмін (джерело/приймач A2DP)" #: spa/plugins/bluez5/bluez5-device.c:1391 #, c-format #| msgid "High Fidelity Playback (A2DP Sink, codec %s)" msgid "High Fidelity Playback (BAP Sink, codec %s)" msgstr "Високоточне відтворення (приймач BAP, кодек %s)" #: spa/plugins/bluez5/bluez5-device.c:1395 #, c-format #| msgid "High Fidelity Duplex (A2DP Source/Sink, codec %s)" msgid "High Fidelity Input (BAP Source, codec %s)" msgstr "Двобічний високоточний вхід (джерело BAP, кодек %s)" #: spa/plugins/bluez5/bluez5-device.c:1399 #, c-format #| msgid "High Fidelity Duplex (A2DP Source/Sink, codec %s)" msgid "High Fidelity Duplex (BAP Source/Sink, codec %s)" msgstr "Двобічний високоточний обмін (джерело/приймач BAP, кодек %s)" #: spa/plugins/bluez5/bluez5-device.c:1407 #| msgid "High Fidelity Playback (A2DP Sink)" msgid "High Fidelity Playback (BAP Sink)" msgstr "Високоточне відтворення (приймач BAP)" #: spa/plugins/bluez5/bluez5-device.c:1410 #| msgid "High Fidelity Duplex (A2DP Source/Sink)" msgid "High Fidelity Input (BAP Source)" msgstr "Двобічний високоточний вхід (джерело BAP)" #: spa/plugins/bluez5/bluez5-device.c:1413 #| msgid "High Fidelity Duplex (A2DP Source/Sink)" msgid "High Fidelity Duplex (BAP Source/Sink)" msgstr "Двобічний високоточний обмін (джерело/приймач BAP)" #: spa/plugins/bluez5/bluez5-device.c:1441 #, c-format msgid "Headset Head Unit (HSP/HFP, codec %s)" msgstr "Головний модуль гарнітури (HSP/HFP, кодек %s)" #: spa/plugins/bluez5/bluez5-device.c:1446 msgid "Headset Head Unit (HSP/HFP)" msgstr "Головний модуль гарнітури (HSP/HFP)" #: spa/plugins/bluez5/bluez5-device.c:1525 #: spa/plugins/bluez5/bluez5-device.c:1530 #: spa/plugins/bluez5/bluez5-device.c:1537 #: spa/plugins/bluez5/bluez5-device.c:1543 #: spa/plugins/bluez5/bluez5-device.c:1549 #: spa/plugins/bluez5/bluez5-device.c:1555 #: spa/plugins/bluez5/bluez5-device.c:1561 #: spa/plugins/bluez5/bluez5-device.c:1567 #: spa/plugins/bluez5/bluez5-device.c:1573 msgid "Handsfree" msgstr "Hands-Free пристрій" #: spa/plugins/bluez5/bluez5-device.c:1531 #| msgid "Handsfree" msgid "Handsfree (HFP)" msgstr "Hands-Free пристрій (HFP)" #: spa/plugins/bluez5/bluez5-device.c:1548 msgid "Headphone" msgstr "Навушники" #: spa/plugins/bluez5/bluez5-device.c:1554 msgid "Portable" msgstr "Портативна акустика" #: spa/plugins/bluez5/bluez5-device.c:1560 msgid "Car" msgstr "Автомобільна акустика" #: spa/plugins/bluez5/bluez5-device.c:1566 msgid "HiFi" msgstr "Hi-Fi" #: spa/plugins/bluez5/bluez5-device.c:1572 msgid "Phone" msgstr "Телефон" #: spa/plugins/bluez5/bluez5-device.c:1579 msgid "Bluetooth" msgstr "Bluetooth" #: spa/plugins/bluez5/bluez5-device.c:1580 #| msgid "Bluetooth" msgid "Bluetooth (HFP)" msgstr "Bluetooth (HFP)" pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/po/zh_CN.po000066400000000000000000000543501511204443500224640ustar00rootroot00000000000000# Simplified Chinese translation for PipeWire. # Copyright (C) 2008 PULSEAUDIO COPYRIGHT HOLDER # This file is distributed under the same license as the pipewire package. # 闫丰刚 , 2008, 2009. # Leah Liu , 2009, 2012. # Cheng-Chia Tseng , 2010, 2012. # Frank Hill , 2015. # Mingye Wang (Arthur2e5) , 2015. # lumingzh , 2024-2025. # msgid "" msgstr "" "Project-Id-Version: pipewire.master-tx\n" "Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/pipewire/-/" "issues\n" "POT-Creation-Date: 2025-11-25 15:35+0000\n" "PO-Revision-Date: 2025-11-26 10:19+0800\n" "Last-Translator: lumingzh \n" "Language-Team: Chinese (China) \n" "Language: zh_CN\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Launchpad-Export-Date: 2016-03-22 13:23+0000\n" "X-Generator: Gtranslator 49.0\n" "Plural-Forms: nplurals=1; plural=0;\n" #: src/daemon/pipewire.c:29 #, c-format msgid "" "%s [options]\n" " -h, --help Show this help\n" " -v, --verbose Increase verbosity by one level\n" " --version Show version\n" " -c, --config Load config (Default %s)\n" " -P --properties Set context properties\n" msgstr "" "%s [选项]\n" " -h, --help 显示此帮助信息\n" " -v, --verbose 增加一级的详尽程度\n" " --version 显示版本\n" " -c, --config 加载配置 (默认 %s)\n" " -P --properties 设置上下文属性\n" #: src/daemon/pipewire.desktop.in:3 msgid "PipeWire Media System" msgstr "PipeWire 多媒体系统" #: src/daemon/pipewire.desktop.in:4 msgid "Start the PipeWire Media System" msgstr "启动 PipeWire 多媒体系统" #: src/modules/module-protocol-pulse/modules/module-tunnel-sink.c:159 #: src/modules/module-protocol-pulse/modules/module-tunnel-source.c:159 #, c-format msgid "Tunnel to %s%s%s" msgstr "至 %s%s%s 的隧道" #: src/modules/module-fallback-sink.c:40 msgid "Dummy Output" msgstr "虚拟输出" #: src/modules/module-pulse-tunnel.c:761 #, c-format msgid "Tunnel for %s@%s" msgstr "用于 %s@%s 的隧道" #: src/modules/module-zeroconf-discover.c:320 msgid "Unknown device" msgstr "未知设备" #: src/modules/module-zeroconf-discover.c:332 #, c-format msgid "%s on %s@%s" msgstr "%2$s@%3$s 上的 %1$s" #: src/modules/module-zeroconf-discover.c:336 #, c-format msgid "%s on %s" msgstr "%2$s 上的 %1$s" #: src/tools/pw-cat.c:1088 #, c-format msgid "" "%s [options] [|-]\n" " -h, --help Show this help\n" " --version Show version\n" " -v, --verbose Enable verbose operations\n" "\n" msgstr "" "%s [选项] [<文件>|-]\n" " -h, --help 显示此帮助信息\n" " --version 显示版本\n" " -v, --verbose 输出详细操作\n" "\n" #: src/tools/pw-cat.c:1095 #, c-format msgid "" " -R, --remote Remote daemon name\n" " --media-type Set media type (default %s)\n" " --media-category Set media category (default %s)\n" " --media-role Set media role (default %s)\n" " --target Set node target serial or name " "(default %s)\n" " 0 means don't link\n" " --latency Set node latency (default %s)\n" " Xunit (unit = s, ms, us, ns)\n" " or direct samples (256)\n" " the rate is the one of the source " "file\n" " -P --properties Set node properties\n" "\n" msgstr "" " -R, --remote 远程守护程序名\n" " --media-type 设置媒体类型 (默认 %s)\n" " --media-category 设置媒体类别 (默认 %s)\n" " --media-role 设置媒体角色 (默认 %s)\n" " --target 设置节点目标序列或名称 (默认 %s)\n" " 设为 0 则不链接节点\n" " --latency 设置节点延迟 (默认 %s)\n" " 时间 (单位可为 s, ms, us, ns)\n" " 或样本数 (如256)\n" " 对应的采样率则是媒体源文件采样率的" "其一\n" " -P --properties 设置节点属性\n" "\n" #: src/tools/pw-cat.c:1113 #, c-format msgid "" " --rate Sample rate (req. for rec) (default " "%u)\n" " --channels Number of channels (req. for rec) " "(default %u)\n" " --channel-map Channel map\n" " one of: \"Stereo\", \"5.1\",... " "or\n" " comma separated list of channel " "names: eg. \"FL,FR\"\n" " --format Sample format %s (req. for rec) " "(default %s)\n" " --volume Stream volume 0-1.0 (default %.3f)\n" " -q --quality Resampler quality (0 - 15) (default " "%d)\n" " -a, --raw RAW mode\n" " -M, --force-midi Force midi format, one of \"midi\" " "or \"ump\", (default ump)\n" " -n, --sample-count COUNT Stop after COUNT samples\n" "\n" msgstr "" " --rate 采样率 (录制模式需要) (默认 %u)\n" " --channels 通道数 (录制模式需要) (默认 %u)\n" " --channel-map 通道映射\n" " \"stereo\", \"5.1\",... 中的其一" "或\n" " 以英文逗号分隔的通道名列表: 如 " "\"FL,FR\"\n" " --format 采样格式 %s (录制模式需要) (默认 " "%s)\n" " --volume 媒体流音量 0-1.0 (默认 %.3f)\n" " -q --quality 重采样质量 (0 - 15) (默认 %d)\n" " -a, --raw 原生模式\n" " -M, --force-midi 强制 midi 格式,\"midi\" 或 \"ump\" " "其一 (默认 ump)\n" " -n, --sample-count COUNT 计数采样后停止\n" "\n" #: src/tools/pw-cat.c:1133 msgid "" " -p, --playback Playback mode\n" " -r, --record Recording mode\n" " -m, --midi Midi mode\n" " -d, --dsd DSD mode\n" " -o, --encoded Encoded mode\n" " -s, --sysex SysEx mode\n" " -c, --midi-clip MIDI clip mode\n" "\n" msgstr "" " -p, --playback 回放模式\n" " -r, --record 录制模式\n" " -m, --midi Midi 模式\n" " -d, --dsd DSD 模式\n" " -o, --encoded 编码模式\n" " -s, --sysex SysEx 模式\n" " -c, --midi-clip MIDI 剪辑模式\n" "\n" #: src/tools/pw-cli.c:2386 #, c-format msgid "" "%s [options] [command]\n" " -h, --help Show this help\n" " --version Show version\n" " -d, --daemon Start as daemon (Default false)\n" " -r, --remote Remote daemon name\n" " -m, --monitor Monitor activity\n" "\n" msgstr "" "%s [选项] [命令]\n" " -h, --help 显示此帮助信息\n" " --version 显示版本\n" " -d, --daemon 以守护程序方式启动 (默认关闭)\n" " -m, --monitor 监视器活动\n" "\n" #: spa/plugins/alsa/acp/acp.c:361 msgid "Pro Audio" msgstr "专业音频" #: spa/plugins/alsa/acp/acp.c:537 spa/plugins/alsa/acp/alsa-mixer.c:4699 #: spa/plugins/bluez5/bluez5-device.c:1976 msgid "Off" msgstr "关" #: spa/plugins/alsa/acp/acp.c:620 #, c-format msgid "%s [ALSA UCM error]" msgstr "%s [ALSA UCM 错误]" #: spa/plugins/alsa/acp/alsa-mixer.c:2721 msgid "Input" msgstr "输入" #: spa/plugins/alsa/acp/alsa-mixer.c:2722 msgid "Docking Station Input" msgstr "扩展坞输入" #: spa/plugins/alsa/acp/alsa-mixer.c:2723 msgid "Docking Station Microphone" msgstr "扩展坞话筒" #: spa/plugins/alsa/acp/alsa-mixer.c:2724 msgid "Docking Station Line In" msgstr "扩展坞线输入" #: spa/plugins/alsa/acp/alsa-mixer.c:2725 #: spa/plugins/alsa/acp/alsa-mixer.c:2816 msgid "Line In" msgstr "输入插孔" #: spa/plugins/alsa/acp/alsa-mixer.c:2726 #: spa/plugins/alsa/acp/alsa-mixer.c:2810 #: spa/plugins/bluez5/bluez5-device.c:2374 msgid "Microphone" msgstr "话筒" #: spa/plugins/alsa/acp/alsa-mixer.c:2727 #: spa/plugins/alsa/acp/alsa-mixer.c:2811 msgid "Front Microphone" msgstr "前麦克风" #: spa/plugins/alsa/acp/alsa-mixer.c:2728 #: spa/plugins/alsa/acp/alsa-mixer.c:2812 msgid "Rear Microphone" msgstr "后麦克风" #: spa/plugins/alsa/acp/alsa-mixer.c:2729 msgid "External Microphone" msgstr "外部话筒" #: spa/plugins/alsa/acp/alsa-mixer.c:2730 #: spa/plugins/alsa/acp/alsa-mixer.c:2814 msgid "Internal Microphone" msgstr "内部话筒" #: spa/plugins/alsa/acp/alsa-mixer.c:2731 #: spa/plugins/alsa/acp/alsa-mixer.c:2817 msgid "Radio" msgstr "无线电" #: spa/plugins/alsa/acp/alsa-mixer.c:2732 #: spa/plugins/alsa/acp/alsa-mixer.c:2818 msgid "Video" msgstr "视频" #: spa/plugins/alsa/acp/alsa-mixer.c:2733 msgid "Automatic Gain Control" msgstr "自动增益控制" #: spa/plugins/alsa/acp/alsa-mixer.c:2734 msgid "No Automatic Gain Control" msgstr "无自动增益控制" #: spa/plugins/alsa/acp/alsa-mixer.c:2735 msgid "Boost" msgstr "增强" #: spa/plugins/alsa/acp/alsa-mixer.c:2736 msgid "No Boost" msgstr "无增强" #: spa/plugins/alsa/acp/alsa-mixer.c:2737 msgid "Amplifier" msgstr "功放" #: spa/plugins/alsa/acp/alsa-mixer.c:2738 msgid "No Amplifier" msgstr "无功放" #: spa/plugins/alsa/acp/alsa-mixer.c:2739 msgid "Bass Boost" msgstr "重低音增强" #: spa/plugins/alsa/acp/alsa-mixer.c:2740 msgid "No Bass Boost" msgstr "无重低音增强" #: spa/plugins/alsa/acp/alsa-mixer.c:2741 #: spa/plugins/bluez5/bluez5-device.c:2380 msgid "Speaker" msgstr "扬声器" #. Don't call it "headset", the HF one has the mic #: spa/plugins/alsa/acp/alsa-mixer.c:2742 #: spa/plugins/alsa/acp/alsa-mixer.c:2820 #: spa/plugins/bluez5/bluez5-device.c:2386 #: spa/plugins/bluez5/bluez5-device.c:2453 msgid "Headphones" msgstr "模拟耳机" #: spa/plugins/alsa/acp/alsa-mixer.c:2809 msgid "Analog Input" msgstr "模拟输入" #: spa/plugins/alsa/acp/alsa-mixer.c:2813 msgid "Dock Microphone" msgstr "扩展坞麦克风" #: spa/plugins/alsa/acp/alsa-mixer.c:2815 msgid "Headset Microphone" msgstr "头挂麦克风" #: spa/plugins/alsa/acp/alsa-mixer.c:2819 msgid "Analog Output" msgstr "模拟输出" #: spa/plugins/alsa/acp/alsa-mixer.c:2821 msgid "Headphones 2" msgstr "模拟耳机 2" #: spa/plugins/alsa/acp/alsa-mixer.c:2822 msgid "Headphones Mono Output" msgstr "模拟单声道输出" #: spa/plugins/alsa/acp/alsa-mixer.c:2823 msgid "Line Out" msgstr "线缆输出" #: spa/plugins/alsa/acp/alsa-mixer.c:2824 msgid "Analog Mono Output" msgstr "模拟单声道输出" #: spa/plugins/alsa/acp/alsa-mixer.c:2825 msgid "Speakers" msgstr "扬声器" #: spa/plugins/alsa/acp/alsa-mixer.c:2826 msgid "HDMI / DisplayPort" msgstr "HDMI / DisplayPort" #: spa/plugins/alsa/acp/alsa-mixer.c:2827 msgid "Digital Output (S/PDIF)" msgstr "数字输出 (S/PDIF)" #: spa/plugins/alsa/acp/alsa-mixer.c:2828 msgid "Digital Input (S/PDIF)" msgstr "数字输入 (S/PDIF)" #: spa/plugins/alsa/acp/alsa-mixer.c:2829 msgid "Multichannel Input" msgstr "多声道输入" #: spa/plugins/alsa/acp/alsa-mixer.c:2830 msgid "Multichannel Output" msgstr "多声道输出" #: spa/plugins/alsa/acp/alsa-mixer.c:2831 msgid "Game Output" msgstr "游戏输出" #: spa/plugins/alsa/acp/alsa-mixer.c:2832 #: spa/plugins/alsa/acp/alsa-mixer.c:2833 msgid "Chat Output" msgstr "语音输出" #: spa/plugins/alsa/acp/alsa-mixer.c:2834 msgid "Chat Input" msgstr "语音输入" #: spa/plugins/alsa/acp/alsa-mixer.c:2835 msgid "Virtual Surround 7.1" msgstr "虚拟环绕 7.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4522 msgid "Analog Mono" msgstr "模拟单声道" #: spa/plugins/alsa/acp/alsa-mixer.c:4523 msgid "Analog Mono (Left)" msgstr "模拟单声道 (左声道)" #: spa/plugins/alsa/acp/alsa-mixer.c:4524 msgid "Analog Mono (Right)" msgstr "模拟单声道 (右声道)" #. Note: Not translated to "Analog Stereo Input", because the source #. * name gets "Input" appended to it automatically, so adding "Input" #. * here would lead to the source name to become "Analog Stereo Input #. * Input". The same logic applies to analog-stereo-output, #. * multichannel-input and multichannel-output. #: spa/plugins/alsa/acp/alsa-mixer.c:4525 #: spa/plugins/alsa/acp/alsa-mixer.c:4533 #: spa/plugins/alsa/acp/alsa-mixer.c:4534 msgid "Analog Stereo" msgstr "模拟立体声" #: spa/plugins/alsa/acp/alsa-mixer.c:4526 msgid "Mono" msgstr "单声道" #: spa/plugins/alsa/acp/alsa-mixer.c:4527 msgid "Stereo" msgstr "立体声" #: spa/plugins/alsa/acp/alsa-mixer.c:4535 #: spa/plugins/alsa/acp/alsa-mixer.c:4693 #: spa/plugins/bluez5/bluez5-device.c:2362 msgid "Headset" msgstr "耳机" #: spa/plugins/alsa/acp/alsa-mixer.c:4536 #: spa/plugins/alsa/acp/alsa-mixer.c:4694 msgid "Speakerphone" msgstr "扬声麦克风" #: spa/plugins/alsa/acp/alsa-mixer.c:4537 #: spa/plugins/alsa/acp/alsa-mixer.c:4538 msgid "Multichannel" msgstr "多声道" #: spa/plugins/alsa/acp/alsa-mixer.c:4539 msgid "Analog Surround 2.1" msgstr "模拟环绕 2.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4540 msgid "Analog Surround 3.0" msgstr "模拟环绕 3.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4541 msgid "Analog Surround 3.1" msgstr "模拟环绕 3.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4542 msgid "Analog Surround 4.0" msgstr "模拟环绕 4.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4543 msgid "Analog Surround 4.1" msgstr "模拟环绕 4.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4544 msgid "Analog Surround 5.0" msgstr "模拟环绕 5.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4545 msgid "Analog Surround 5.1" msgstr "模拟环绕 5.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4546 msgid "Analog Surround 6.0" msgstr "模拟环绕 6.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4547 msgid "Analog Surround 6.1" msgstr "模拟环绕 6.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4548 msgid "Analog Surround 7.0" msgstr "模拟环绕 7.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4549 msgid "Analog Surround 7.1" msgstr "模拟环绕 7.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4550 msgid "Digital Stereo (IEC958)" msgstr "数字立体声 (IEC958)" #: spa/plugins/alsa/acp/alsa-mixer.c:4551 msgid "Digital Surround 4.0 (IEC958/AC3)" msgstr "数字环绕 4.0 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4552 msgid "Digital Surround 5.1 (IEC958/AC3)" msgstr "数字环绕 5.1 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4553 msgid "Digital Surround 5.1 (IEC958/DTS)" msgstr "数字环绕 5.1 (IEC958/DTS)" #: spa/plugins/alsa/acp/alsa-mixer.c:4554 msgid "Digital Stereo (HDMI)" msgstr "数字立体声 (HDMI)" #: spa/plugins/alsa/acp/alsa-mixer.c:4555 msgid "Digital Surround 5.1 (HDMI)" msgstr "数字环绕 5.1 (HDMI)" #: spa/plugins/alsa/acp/alsa-mixer.c:4556 msgid "Chat" msgstr "语音" #: spa/plugins/alsa/acp/alsa-mixer.c:4557 msgid "Game" msgstr "游戏" #: spa/plugins/alsa/acp/alsa-mixer.c:4691 msgid "Analog Mono Duplex" msgstr "模拟单声道双工" #: spa/plugins/alsa/acp/alsa-mixer.c:4692 msgid "Analog Stereo Duplex" msgstr "模拟立体声双工" #: spa/plugins/alsa/acp/alsa-mixer.c:4695 msgid "Digital Stereo Duplex (IEC958)" msgstr "数字立体声双工 (IEC958)" #: spa/plugins/alsa/acp/alsa-mixer.c:4696 msgid "Multichannel Duplex" msgstr "多声道双工" #: spa/plugins/alsa/acp/alsa-mixer.c:4697 msgid "Stereo Duplex" msgstr "模拟立体声双工" #: spa/plugins/alsa/acp/alsa-mixer.c:4698 msgid "Mono Chat + 7.1 Surround" msgstr "单声道语音 + 7.1 环绕声" #: spa/plugins/alsa/acp/alsa-mixer.c:4799 #, c-format msgid "%s Output" msgstr "%s 输出" #: spa/plugins/alsa/acp/alsa-mixer.c:4807 #, c-format msgid "%s Input" msgstr "%s 输入" #: spa/plugins/alsa/acp/alsa-util.c:1233 spa/plugins/alsa/acp/alsa-util.c:1327 #, c-format msgid "" "snd_pcm_avail() returned a value that is exceptionally large: %lu byte (%lu " "ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_avail() returned a value that is exceptionally large: %lu bytes (%lu " "ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "snd_pcm_avail() 返回的值非常大:%lu 字节(%lu 毫秒)。\n" "这很可能是由 ALSA 驱动程序 %s 的缺陷导致的。请向 ALSA 开发者报告这个问题。" #: spa/plugins/alsa/acp/alsa-util.c:1299 #, c-format msgid "" "snd_pcm_delay() returned a value that is exceptionally large: %li byte " "(%s%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_delay() returned a value that is exceptionally large: %li bytes " "(%s%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "snd_pcm_delay() 返回的值非常大:%li 字节(%s%lu 毫秒)。\n" "这很可能是由 ALSA 驱动程序 %s 的缺陷导致的。请向 ALSA 开发者报告这个问题。" #: spa/plugins/alsa/acp/alsa-util.c:1346 #, c-format msgid "" "snd_pcm_avail_delay() returned strange values: delay %lu is less than avail " "%lu.\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr "" "snd_pcm_avail_delay() 返回的值非常很奇怪:延迟 %lu 小于可用 (avail) %lu。\n" "这很可能是由 ALSA 驱动程序 %s 的缺陷导致的。请向 ALSA 开发者报告这个问题。" #: spa/plugins/alsa/acp/alsa-util.c:1389 #, c-format msgid "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu byte " "(%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu bytes " "(%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "snd_pcm_mmap_begin() 返回的值非常大:%lu 字节(%lu ms)。\n" "这很可能是由 ALSA 驱动程序 %s 的缺陷导致的。请向 ALSA 开发者报告这个问题。" #: spa/plugins/alsa/acp/channelmap.h:460 msgid "(invalid)" msgstr "(无效)" #: spa/plugins/alsa/acp/compat.c:194 msgid "Built-in Audio" msgstr "内置音频" #: spa/plugins/alsa/acp/compat.c:199 msgid "Modem" msgstr "调制解调器" #: spa/plugins/bluez5/bluez5-device.c:1987 msgid "Audio Gateway (A2DP Source & HSP/HFP AG)" msgstr "音频网关 (A2DP 信源 或 HSP/HFP 网关)" #: spa/plugins/bluez5/bluez5-device.c:2016 msgid "Audio Streaming for Hearing Aids (ASHA Sink)" msgstr "助听器音频流 (ASHA 信宿)" #: spa/plugins/bluez5/bluez5-device.c:2059 #, c-format msgid "High Fidelity Playback (A2DP Sink, codec %s)" msgstr "高保真回放 (A2DP 信宿, 编码 %s)" #: spa/plugins/bluez5/bluez5-device.c:2062 #, c-format msgid "High Fidelity Duplex (A2DP Source/Sink, codec %s)" msgstr "高保真双工 (A2DP 信源/信宿, 编码 %s)" #: spa/plugins/bluez5/bluez5-device.c:2070 msgid "High Fidelity Playback (A2DP Sink)" msgstr "高保真回放 (A2DP 信宿)" #: spa/plugins/bluez5/bluez5-device.c:2072 msgid "High Fidelity Duplex (A2DP Source/Sink)" msgstr "高保真双工 (A2DP 信源/信宿)" #: spa/plugins/bluez5/bluez5-device.c:2146 #, c-format msgid "High Fidelity Playback (BAP Sink, codec %s)" msgstr "高保真回放 (BAP 信宿, 编码 %s)" #: spa/plugins/bluez5/bluez5-device.c:2151 #, c-format msgid "High Fidelity Input (BAP Source, codec %s)" msgstr "高保真输入 (BAP 信源, 编码 %s)" #: spa/plugins/bluez5/bluez5-device.c:2155 #, c-format msgid "High Fidelity Duplex (BAP Source/Sink, codec %s)" msgstr "高保真双工 (BAP 信源/信宿, 编码 %s)" #: spa/plugins/bluez5/bluez5-device.c:2164 msgid "High Fidelity Playback (BAP Sink)" msgstr "高保真回放 (BAP 信宿)" #: spa/plugins/bluez5/bluez5-device.c:2168 msgid "High Fidelity Input (BAP Source)" msgstr "高保真输入 (BAP 信源)" #: spa/plugins/bluez5/bluez5-device.c:2171 msgid "High Fidelity Duplex (BAP Source/Sink)" msgstr "高保真双工 (BAP 信源/信宿)" #: spa/plugins/bluez5/bluez5-device.c:2211 #, c-format msgid "Headset Head Unit (HSP/HFP, codec %s)" msgstr "头戴式耳机单元 (HSP/HFP, 编码 %s)" #: spa/plugins/bluez5/bluez5-device.c:2363 #: spa/plugins/bluez5/bluez5-device.c:2368 #: spa/plugins/bluez5/bluez5-device.c:2375 #: spa/plugins/bluez5/bluez5-device.c:2381 #: spa/plugins/bluez5/bluez5-device.c:2387 #: spa/plugins/bluez5/bluez5-device.c:2393 #: spa/plugins/bluez5/bluez5-device.c:2399 #: spa/plugins/bluez5/bluez5-device.c:2405 #: spa/plugins/bluez5/bluez5-device.c:2411 msgid "Handsfree" msgstr "免提" #: spa/plugins/bluez5/bluez5-device.c:2369 msgid "Handsfree (HFP)" msgstr "免提(HFP)" #: spa/plugins/bluez5/bluez5-device.c:2392 msgid "Portable" msgstr "便携式" #: spa/plugins/bluez5/bluez5-device.c:2398 msgid "Car" msgstr "车内" #: spa/plugins/bluez5/bluez5-device.c:2404 msgid "HiFi" msgstr "高保真" #: spa/plugins/bluez5/bluez5-device.c:2410 msgid "Phone" msgstr "电话" #: spa/plugins/bluez5/bluez5-device.c:2417 msgid "Bluetooth" msgstr "蓝牙" #: spa/plugins/bluez5/bluez5-device.c:2418 msgid "Bluetooth Handsfree" msgstr "蓝牙免提" #~ msgid "Headphone" #~ msgstr "头戴耳机" #~ msgid "Headset Head Unit (HSP/HFP)" #~ msgstr "头戴式耳机单元 (HSP/HFP)" pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/po/zh_TW.po000066400000000000000000000376521511204443500225240ustar00rootroot00000000000000# Chinese (Taiwan) translation for pipewire. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # # Cheng-Chia Tseng , 2010, 2012. # pan93412 , 2020. msgid "" msgstr "" "Project-Id-Version: PipeWire Volume Control\n" "Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/pipewire/" "issues/new\n" "POT-Creation-Date: 2021-04-18 16:54+0800\n" "PO-Revision-Date: 2020-01-11 13:49+0800\n" "Last-Translator: pan93412 \n" "Language-Team: Chinese \n" "Language: zh_TW\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Generator: Lokalize 19.12.0\n" "Plural-Forms: nplurals=1; plural=0;\n" #: src/daemon/pipewire.c:43 #, c-format msgid "" "%s [options]\n" " -h, --help Show this help\n" " --version Show version\n" " -c, --config Load config (Default %s)\n" msgstr "" #: src/daemon/pipewire.desktop.in:4 msgid "PipeWire Media System" msgstr "" #: src/daemon/pipewire.desktop.in:5 msgid "Start the PipeWire Media System" msgstr "" #: src/examples/media-session/alsa-monitor.c:526 #: spa/plugins/alsa/acp/compat.c:187 msgid "Built-in Audio" msgstr "內部音效" #: src/examples/media-session/alsa-monitor.c:530 #: spa/plugins/alsa/acp/compat.c:192 msgid "Modem" msgstr "數據機" #: src/examples/media-session/alsa-monitor.c:539 msgid "Unknown device" msgstr "" #: src/tools/pw-cat.c:991 #, c-format msgid "" "%s [options] \n" " -h, --help Show this help\n" " --version Show version\n" " -v, --verbose Enable verbose operations\n" "\n" msgstr "" #: src/tools/pw-cat.c:998 #, c-format msgid "" " -R, --remote Remote daemon name\n" " --media-type Set media type (default %s)\n" " --media-category Set media category (default %s)\n" " --media-role Set media role (default %s)\n" " --target Set node target (default %s)\n" " 0 means don't link\n" " --latency Set node latency (default %s)\n" " Xunit (unit = s, ms, us, ns)\n" " or direct samples (256)\n" " the rate is the one of the source " "file\n" " --list-targets List available targets for --target\n" "\n" msgstr "" #: src/tools/pw-cat.c:1016 #, c-format msgid "" " --rate Sample rate (req. for rec) (default " "%u)\n" " --channels Number of channels (req. for rec) " "(default %u)\n" " --channel-map Channel map\n" " one of: \"stereo\", " "\"surround-51\",... or\n" " comma separated list of channel " "names: eg. \"FL,FR\"\n" " --format Sample format %s (req. for rec) " "(default %s)\n" " --volume Stream volume 0-1.0 (default %.3f)\n" " -q --quality Resampler quality (0 - 15) (default " "%d)\n" "\n" msgstr "" #: src/tools/pw-cat.c:1033 msgid "" " -p, --playback Playback mode\n" " -r, --record Recording mode\n" " -m, --midi Midi mode\n" "\n" msgstr "" #: src/tools/pw-cli.c:2932 #, c-format msgid "" "%s [options] [command]\n" " -h, --help Show this help\n" " --version Show version\n" " -d, --daemon Start as daemon (Default false)\n" " -r, --remote Remote daemon name\n" "\n" msgstr "" #: spa/plugins/alsa/acp/acp.c:290 msgid "Pro Audio" msgstr "" #: spa/plugins/alsa/acp/acp.c:411 spa/plugins/alsa/acp/alsa-mixer.c:4704 #: spa/plugins/bluez5/bluez5-device.c:1000 msgid "Off" msgstr "關閉" #: spa/plugins/alsa/acp/channelmap.h:466 msgid "(invalid)" msgstr "(無效)" #: spa/plugins/alsa/acp/alsa-mixer.c:2709 msgid "Input" msgstr "輸入" #: spa/plugins/alsa/acp/alsa-mixer.c:2710 msgid "Docking Station Input" msgstr "Docking Station 輸入" #: spa/plugins/alsa/acp/alsa-mixer.c:2711 msgid "Docking Station Microphone" msgstr "Docking Station 麥克風" #: spa/plugins/alsa/acp/alsa-mixer.c:2712 msgid "Docking Station Line In" msgstr "Docking Station 線路輸入" #: spa/plugins/alsa/acp/alsa-mixer.c:2713 #: spa/plugins/alsa/acp/alsa-mixer.c:2804 msgid "Line In" msgstr "線路輸入" #: spa/plugins/alsa/acp/alsa-mixer.c:2714 #: spa/plugins/alsa/acp/alsa-mixer.c:2798 #: spa/plugins/bluez5/bluez5-device.c:1145 msgid "Microphone" msgstr "麥克風" #: spa/plugins/alsa/acp/alsa-mixer.c:2715 #: spa/plugins/alsa/acp/alsa-mixer.c:2799 msgid "Front Microphone" msgstr "前方麥克風" #: spa/plugins/alsa/acp/alsa-mixer.c:2716 #: spa/plugins/alsa/acp/alsa-mixer.c:2800 msgid "Rear Microphone" msgstr "後方麥克風" #: spa/plugins/alsa/acp/alsa-mixer.c:2717 msgid "External Microphone" msgstr "外接麥克風" #: spa/plugins/alsa/acp/alsa-mixer.c:2718 #: spa/plugins/alsa/acp/alsa-mixer.c:2802 msgid "Internal Microphone" msgstr "內建麥克風" #: spa/plugins/alsa/acp/alsa-mixer.c:2719 #: spa/plugins/alsa/acp/alsa-mixer.c:2805 msgid "Radio" msgstr "無線電" #: spa/plugins/alsa/acp/alsa-mixer.c:2720 #: spa/plugins/alsa/acp/alsa-mixer.c:2806 msgid "Video" msgstr "視訊" #: spa/plugins/alsa/acp/alsa-mixer.c:2721 msgid "Automatic Gain Control" msgstr "自動增益控制" #: spa/plugins/alsa/acp/alsa-mixer.c:2722 msgid "No Automatic Gain Control" msgstr "無自動增益控制" #: spa/plugins/alsa/acp/alsa-mixer.c:2723 msgid "Boost" msgstr "增強" #: spa/plugins/alsa/acp/alsa-mixer.c:2724 msgid "No Boost" msgstr "無增強" #: spa/plugins/alsa/acp/alsa-mixer.c:2725 msgid "Amplifier" msgstr "擴大器" #: spa/plugins/alsa/acp/alsa-mixer.c:2726 msgid "No Amplifier" msgstr "無擴大器" #: spa/plugins/alsa/acp/alsa-mixer.c:2727 msgid "Bass Boost" msgstr "低音增強" #: spa/plugins/alsa/acp/alsa-mixer.c:2728 msgid "No Bass Boost" msgstr "無低音增強" #: spa/plugins/alsa/acp/alsa-mixer.c:2729 #: spa/plugins/bluez5/bluez5-device.c:1150 msgid "Speaker" msgstr "喇叭" #: spa/plugins/alsa/acp/alsa-mixer.c:2730 #: spa/plugins/alsa/acp/alsa-mixer.c:2808 msgid "Headphones" msgstr "頭戴式耳機" #: spa/plugins/alsa/acp/alsa-mixer.c:2797 msgid "Analog Input" msgstr "類比輸入" #: spa/plugins/alsa/acp/alsa-mixer.c:2801 msgid "Dock Microphone" msgstr "臺座麥克風" #: spa/plugins/alsa/acp/alsa-mixer.c:2803 msgid "Headset Microphone" msgstr "耳麥麥克風" #: spa/plugins/alsa/acp/alsa-mixer.c:2807 msgid "Analog Output" msgstr "類比輸出" #: spa/plugins/alsa/acp/alsa-mixer.c:2809 #, fuzzy msgid "Headphones 2" msgstr "頭戴式耳機" #: spa/plugins/alsa/acp/alsa-mixer.c:2810 msgid "Headphones Mono Output" msgstr "頭戴式耳機單聲道輸出" #: spa/plugins/alsa/acp/alsa-mixer.c:2811 msgid "Line Out" msgstr "線路輸出" #: spa/plugins/alsa/acp/alsa-mixer.c:2812 msgid "Analog Mono Output" msgstr "類比單聲道輸出" #: spa/plugins/alsa/acp/alsa-mixer.c:2813 msgid "Speakers" msgstr "喇叭" #: spa/plugins/alsa/acp/alsa-mixer.c:2814 msgid "HDMI / DisplayPort" msgstr "HDMI / DisplayPort" #: spa/plugins/alsa/acp/alsa-mixer.c:2815 msgid "Digital Output (S/PDIF)" msgstr "數位輸出 (S/PDIF)" #: spa/plugins/alsa/acp/alsa-mixer.c:2816 msgid "Digital Input (S/PDIF)" msgstr "數位輸入 (S/PDIF)" #: spa/plugins/alsa/acp/alsa-mixer.c:2817 msgid "Multichannel Input" msgstr "多聲道輸入" #: spa/plugins/alsa/acp/alsa-mixer.c:2818 msgid "Multichannel Output" msgstr "多聲道輸出" #: spa/plugins/alsa/acp/alsa-mixer.c:2819 msgid "Game Output" msgstr "遊戲輸出" #: spa/plugins/alsa/acp/alsa-mixer.c:2820 #: spa/plugins/alsa/acp/alsa-mixer.c:2821 msgid "Chat Output" msgstr "聊天輸出" #: spa/plugins/alsa/acp/alsa-mixer.c:2822 #, fuzzy msgid "Chat Input" msgstr "聊天輸出" #: spa/plugins/alsa/acp/alsa-mixer.c:2823 #, fuzzy msgid "Virtual Surround 7.1" msgstr "虛擬環繞聲 sink" #: spa/plugins/alsa/acp/alsa-mixer.c:4527 msgid "Analog Mono" msgstr "類比單聲道" #: spa/plugins/alsa/acp/alsa-mixer.c:4528 #, fuzzy msgid "Analog Mono (Left)" msgstr "類比單聲道" #: spa/plugins/alsa/acp/alsa-mixer.c:4529 #, fuzzy msgid "Analog Mono (Right)" msgstr "類比單聲道" #. Note: Not translated to "Analog Stereo Input", because the source #. * name gets "Input" appended to it automatically, so adding "Input" #. * here would lead to the source name to become "Analog Stereo Input #. * Input". The same logic applies to analog-stereo-output, #. * multichannel-input and multichannel-output. #: spa/plugins/alsa/acp/alsa-mixer.c:4530 #: spa/plugins/alsa/acp/alsa-mixer.c:4538 #: spa/plugins/alsa/acp/alsa-mixer.c:4539 msgid "Analog Stereo" msgstr "類比立體聲" #: spa/plugins/alsa/acp/alsa-mixer.c:4531 msgid "Mono" msgstr "單聲道" #: spa/plugins/alsa/acp/alsa-mixer.c:4532 msgid "Stereo" msgstr "立體聲" #: spa/plugins/alsa/acp/alsa-mixer.c:4540 #: spa/plugins/alsa/acp/alsa-mixer.c:4698 #: spa/plugins/bluez5/bluez5-device.c:1135 msgid "Headset" msgstr "耳麥" #: spa/plugins/alsa/acp/alsa-mixer.c:4541 #: spa/plugins/alsa/acp/alsa-mixer.c:4699 #, fuzzy msgid "Speakerphone" msgstr "喇叭" #: spa/plugins/alsa/acp/alsa-mixer.c:4542 #: spa/plugins/alsa/acp/alsa-mixer.c:4543 msgid "Multichannel" msgstr "多聲道" #: spa/plugins/alsa/acp/alsa-mixer.c:4544 msgid "Analog Surround 2.1" msgstr "類比環繞聲 2.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4545 msgid "Analog Surround 3.0" msgstr "類比環繞聲 3.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4546 msgid "Analog Surround 3.1" msgstr "類比環繞聲 3.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4547 msgid "Analog Surround 4.0" msgstr "類比環繞聲 4.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4548 msgid "Analog Surround 4.1" msgstr "類比環繞聲 4.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4549 msgid "Analog Surround 5.0" msgstr "類比環繞聲 5.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4550 msgid "Analog Surround 5.1" msgstr "類比環繞聲 5.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4551 msgid "Analog Surround 6.0" msgstr "類比環繞聲 6.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4552 msgid "Analog Surround 6.1" msgstr "類比環繞聲 6.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4553 msgid "Analog Surround 7.0" msgstr "類比環繞聲 7.0" #: spa/plugins/alsa/acp/alsa-mixer.c:4554 msgid "Analog Surround 7.1" msgstr "類比環繞聲 7.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4555 msgid "Digital Stereo (IEC958)" msgstr "數位立體聲 (IEC958)" #: spa/plugins/alsa/acp/alsa-mixer.c:4556 msgid "Digital Surround 4.0 (IEC958/AC3)" msgstr "數位環繞聲 4.0 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4557 msgid "Digital Surround 5.1 (IEC958/AC3)" msgstr "數位環繞聲 5.1 (IEC958/AC3)" #: spa/plugins/alsa/acp/alsa-mixer.c:4558 msgid "Digital Surround 5.1 (IEC958/DTS)" msgstr "數位環繞聲 5.1 (IEC958/DTS)" #: spa/plugins/alsa/acp/alsa-mixer.c:4559 msgid "Digital Stereo (HDMI)" msgstr "數位立體聲 (HDMI)" #: spa/plugins/alsa/acp/alsa-mixer.c:4560 msgid "Digital Surround 5.1 (HDMI)" msgstr "數位環繞聲 5.1 (HDMI)" #: spa/plugins/alsa/acp/alsa-mixer.c:4561 msgid "Chat" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4562 msgid "Game" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4696 msgid "Analog Mono Duplex" msgstr "類比單聲道雙工" #: spa/plugins/alsa/acp/alsa-mixer.c:4697 msgid "Analog Stereo Duplex" msgstr "類比立體聲雙工" #: spa/plugins/alsa/acp/alsa-mixer.c:4700 msgid "Digital Stereo Duplex (IEC958)" msgstr "數位立體聲雙工 (IEC958)" #: spa/plugins/alsa/acp/alsa-mixer.c:4701 msgid "Multichannel Duplex" msgstr "多聲道雙工" #: spa/plugins/alsa/acp/alsa-mixer.c:4702 msgid "Stereo Duplex" msgstr "立體聲雙工" #: spa/plugins/alsa/acp/alsa-mixer.c:4703 msgid "Mono Chat + 7.1 Surround" msgstr "" #: spa/plugins/alsa/acp/alsa-mixer.c:4806 #, c-format msgid "%s Output" msgstr "%s 輸出" #: spa/plugins/alsa/acp/alsa-mixer.c:4813 #, c-format msgid "%s Input" msgstr "%s 輸入" #: spa/plugins/alsa/acp/alsa-util.c:1175 spa/plugins/alsa/acp/alsa-util.c:1269 #, c-format msgid "" "snd_pcm_avail() returned a value that is exceptionally large: %lu byte (%lu " "ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_avail() returned a value that is exceptionally large: %lu bytes (%lu " "ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "snd_pcm_avail() 傳回超出預期的大值:%lu bytes (%lu ms)。\n" "這很能是 ALSA 驅動程式「%s」的臭蟲。請回報這個問題給 ALSA 開發者。" #: spa/plugins/alsa/acp/alsa-util.c:1241 #, c-format msgid "" "snd_pcm_delay() returned a value that is exceptionally large: %li byte (%s" "%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_delay() returned a value that is exceptionally large: %li bytes (%s" "%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "snd_pcm_delay() 傳回超出預期的大值:%li bytes (%s%lu ms)。\n" "這很能是 ALSA 驅動程式「%s」的臭蟲。請回報這個問題給 ALSA 開發者。" #: spa/plugins/alsa/acp/alsa-util.c:1288 #, c-format msgid "" "snd_pcm_avail_delay() returned strange values: delay %lu is less than avail " "%lu.\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr "" "snd_pcm_avail_delay() 傳回超出預期的大值:延遲 %lu 少於可用的 %lu。\n" "這很能是 ALSA 驅動程式「%s」的臭蟲。請回報這個問題給 ALSA 開發者。" #: spa/plugins/alsa/acp/alsa-util.c:1331 #, c-format msgid "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu byte " "(%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu bytes " "(%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" "snd_pcm_mmap_begin() 傳回超出預期的大值:%lu bytes (%lu ms)。\n" "這很能是 ALSA 驅動程式「%s」的臭蟲。請回報這個問題給 ALSA 開發者。" #: spa/plugins/bluez5/bluez5-device.c:1010 msgid "Audio Gateway (A2DP Source & HSP/HFP AG)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1033 #, c-format msgid "High Fidelity Playback (A2DP Sink, codec %s)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1035 #, c-format msgid "High Fidelity Duplex (A2DP Source/Sink, codec %s)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1041 msgid "High Fidelity Playback (A2DP Sink)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1043 msgid "High Fidelity Duplex (A2DP Source/Sink)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1070 #, c-format msgid "Headset Head Unit (HSP/HFP, codec %s)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1074 msgid "Headset Head Unit (HSP/HFP)" msgstr "" #: spa/plugins/bluez5/bluez5-device.c:1140 msgid "Handsfree" msgstr "免持裝置" #: spa/plugins/bluez5/bluez5-device.c:1155 msgid "Headphone" msgstr "頭戴式耳機" #: spa/plugins/bluez5/bluez5-device.c:1160 msgid "Portable" msgstr "可攜裝置" #: spa/plugins/bluez5/bluez5-device.c:1165 msgid "Car" msgstr "汽車" #: spa/plugins/bluez5/bluez5-device.c:1170 msgid "HiFi" msgstr "HiFi" #: spa/plugins/bluez5/bluez5-device.c:1175 msgid "Phone" msgstr "手機" #: spa/plugins/bluez5/bluez5-device.c:1181 #, fuzzy msgid "Bluetooth" msgstr "藍牙輸入" pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/pw-uninstalled.sh000077500000000000000000000043141511204443500240050ustar00rootroot00000000000000#!/usr/bin/env bash set -e SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" while getopts ":b:v:" opt; do case ${opt} in b) BUILDDIR=${OPTARG} ;; v) VERSION=${OPTARG} echo "Version: ${VERSION}" ;; \?) echo "Invalid option: -${OPTARG}" exit 1 ;; :) echo "Option -${OPTARG} requires an argument" exit 1 ;; esac done if [ -z "${BUILDDIR}" ]; then BUILDDIR=${SCRIPT_DIR}/builddir echo "Using default build directory: ${BUILDDIR}" fi if [ ! -d "${BUILDDIR}" ]; then echo "Invalid build directory: ${BUILDDIR}" exit 1 fi # the config file read by the daemon export PIPEWIRE_CONFIG_DIR="${BUILDDIR}/src/daemon" # the directory with SPA plugins export SPA_PLUGIN_DIR="${BUILDDIR}/spa/plugins" export SPA_DATA_DIR="${SCRIPT_DIR}/spa/plugins" # the directory with pipewire modules export PIPEWIRE_MODULE_DIR="${BUILDDIR}/src/modules" export PATH="${BUILDDIR}/src/daemon:${BUILDDIR}/src/tools:${BUILDDIR}/src/media-session:${BUILDDIR}/src/examples:${BUILDDIR}/pipewire-v4l2/src:${PATH}" export LD_LIBRARY_PATH="${BUILDDIR}/src/pipewire/:${BUILDDIR}/pipewire-jack/src/${LD_LIBRARY_PATH+":$LD_LIBRARY_PATH"}" export GST_PLUGIN_PATH="${BUILDDIR}/src/gst/${GST_PLUGIN_PATH+":${GST_PLUGIN_PATH}"}" # the directory with card profiles and paths export ACP_PATHS_DIR="${SCRIPT_DIR}/spa/plugins/alsa/mixer/paths" export ACP_PROFILES_DIR="${SCRIPT_DIR}/spa/plugins/alsa/mixer/profile-sets" # ALSA plugin directory export ALSA_PLUGIN_DIR="${BUILDDIR}/pipewire-alsa/alsa-plugins" export PW_BUILDDIR=$BUILDDIR export PW_UNINSTALLED=1 export PKG_CONFIG_PATH="${BUILDDIR}/meson-uninstalled/:${PKG_CONFIG_PATH}" export PIPEWIRE_LOG_SYSTEMD=false if [ -d "${BUILDDIR}/subprojects/wireplumber" ]; then # FIXME: find a nice, shell-neutral way to specify a prompt "${SCRIPT_DIR}"/subprojects/wireplumber/wp-uninstalled.sh -b"${BUILDDIR}"/subprojects/wireplumber "${SHELL}" elif [ -d "${BUILDDIR}/subprojects/media-session" ]; then # FIXME: find a nice, shell-neutral way to specify a prompt "${SCRIPT_DIR}"/subprojects/media-session/media-session-uninstalled.sh -b"${BUILDDIR}"/subprojects/media-session "${SHELL}" else # FIXME: find a nice, shell-neutral way to specify a prompt ${SHELL} fi pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/000077500000000000000000000000001511204443500212615ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/examples/000077500000000000000000000000001511204443500230775ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/examples/adapter-control.c000066400000000000000000001027771511204443500263570ustar00rootroot00000000000000/* Spa */ /* SPDX-FileCopyrightText: Copyright © 2020 Collabora Ltd. */ /* SPDX-License-Identifier: MIT */ /* [title] Running audioadapter nodes. [title] [doc] Runs an output audioadapter using audiotestsrc as follower with an input audioadapter using alsa-pcm-sink as follower for easy testing. [doc] */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static SPA_LOG_IMPL(default_log); #define MIN_LATENCY 1024 #define CONTROL_BUFFER_SIZE 32768 #define DEFAULT_RAMP_SAMPLES (64*1*1024) #define DEFAULT_RAMP_STEP_SAMPLES 200 #define DEFAULT_RAMP_TIME 2000 // 2 seconds #define DEFAULT_RAMP_STEP_TIME 5000 // 5 milli seconds #define DEFAULT_DEVICE "hw:0,0" #define LINEAR "linear" #define CUBIC "cubic" #define DEFAULT_SCALE SPA_AUDIO_VOLUME_RAMP_LINEAR #define NON_NATIVE "non-native" #define NATIVE "native" #define DEFAULT_MODE NON_NATIVE struct buffer { struct spa_buffer buffer; struct spa_meta metas[1]; struct spa_meta_header header; struct spa_data datas[1]; struct spa_chunk chunks[1]; }; struct data { const char *plugin_dir; struct spa_log *log; struct spa_system *system; struct spa_loop *loop; struct spa_loop_control *control; struct spa_support support[5]; uint32_t n_support; struct spa_graph graph; struct spa_graph_state graph_state; struct spa_graph_node graph_source_node; struct spa_graph_node graph_sink_node; struct spa_graph_state graph_source_state; struct spa_graph_state graph_sink_state; struct spa_graph_port graph_source_port_0; struct spa_graph_port graph_sink_port_0; struct spa_node *source_follower_node; // audiotestsrc struct spa_node *source_node; // adapter for audiotestsrc struct spa_node *sink_follower_node; // alsa-pcm-sink struct spa_node *sink_node; // adapter for alsa-pcm-sink struct spa_io_position position; struct spa_io_buffers source_sink_io[1]; struct spa_buffer *source_buffers[1]; struct buffer source_buffer[1]; struct spa_io_buffers control_io; struct spa_buffer *control_buffers[1]; struct buffer control_buffer[1]; int buffer_count; bool start_fade_in; double volume_accum; uint32_t volume_offs; const char *alsa_device; const char *mode; enum spa_audio_volume_ramp_scale scale; uint32_t volume_ramp_samples; uint32_t volume_ramp_step_samples; uint32_t volume_ramp_time; uint32_t volume_ramp_step_time; bool running; pthread_t thread; }; static int load_handle (struct data *data, struct spa_handle **handle, const char *lib, const char *name, struct spa_dict *info) { int res; void *hnd; spa_handle_factory_enum_func_t enum_func; uint32_t i; char *path; if ((path = spa_aprintf("%s/%s", data->plugin_dir, lib)) == NULL) return -ENOMEM; hnd = dlopen(path, RTLD_NOW); free(path); if (hnd == NULL) { printf("can't load %s: %s\n", lib, dlerror()); return -ENOENT; } if ((enum_func = dlsym(hnd, SPA_HANDLE_FACTORY_ENUM_FUNC_NAME)) == NULL) { printf("can't find enum function\n"); res = -ENOENT; goto exit_cleanup; } for (i = 0;;) { const struct spa_handle_factory *factory; if ((res = enum_func(&factory, &i)) <= 0) { if (res != 0) printf("can't enumerate factories: %s\n", spa_strerror(res)); break; } if (factory->version < 1) continue; if (!spa_streq(factory->name, name)) continue; *handle = calloc(1, spa_handle_factory_get_size(factory, NULL)); if ((res = spa_handle_factory_init(factory, *handle, info, data->support, data->n_support)) < 0) { printf("can't make factory instance: %d\n", res); goto exit_cleanup; } return 0; } return -EBADF; exit_cleanup: dlclose(hnd); return res; } static int init_data(struct data *data) { int res; const char *str; struct spa_handle *handle = NULL; struct spa_dict_item items [2]; struct spa_dict info; void *iface; if ((str = getenv("SPA_PLUGIN_DIR")) == NULL) str = PLUGINDIR; data->plugin_dir = str; /* start not doing fade-in */ data->start_fade_in = true; data->volume_accum = 0.0; data->volume_offs = 0; /* init the graph */ spa_graph_init(&data->graph, &data->graph_state); /* enable the debug messages in SPA */ items [0] = SPA_DICT_ITEM_INIT(SPA_KEY_LOG_TIMESTAMP, "true"); info = SPA_DICT_INIT(items, 1); if ((res = load_handle (data, &handle, "support/libspa-support.so", SPA_NAME_SUPPORT_LOG, &info)) < 0) return res; if ((res = spa_handle_get_interface(handle, SPA_TYPE_INTERFACE_Log, &iface)) < 0) { printf("can't get System interface %d\n", res); return res; } data->log = iface; data->support[data->n_support++] = SPA_SUPPORT_INIT(SPA_TYPE_INTERFACE_Log, data->log); /* load and set support system */ if ((res = load_handle(data, &handle, "support/libspa-support.so", SPA_NAME_SUPPORT_SYSTEM, NULL)) < 0) return res; if ((res = spa_handle_get_interface(handle, SPA_TYPE_INTERFACE_System, &iface)) < 0) { printf("can't get System interface %d\n", res); return res; } data->system = iface; data->support[data->n_support++] = SPA_SUPPORT_INIT(SPA_TYPE_INTERFACE_System, data->system); data->support[data->n_support++] = SPA_SUPPORT_INIT(SPA_TYPE_INTERFACE_DataSystem, data->system); /* load and set support loop and loop control */ if ((res = load_handle(data, &handle, "support/libspa-support.so", SPA_NAME_SUPPORT_LOOP, NULL)) < 0) return res; if ((res = spa_handle_get_interface(handle, SPA_TYPE_INTERFACE_Loop, &iface)) < 0) { printf("can't get interface %d\n", res); return res; } data->loop = iface; data->support[data->n_support++] = SPA_SUPPORT_INIT(SPA_TYPE_INTERFACE_Loop, data->loop); data->support[data->n_support++] = SPA_SUPPORT_INIT(SPA_TYPE_INTERFACE_DataLoop, data->loop); if ((res = spa_handle_get_interface(handle, SPA_TYPE_INTERFACE_LoopControl, &iface)) < 0) { printf("can't get interface %d\n", res); return res; } data->control = iface; if ((str = getenv("SPA_DEBUG"))) data->log->level = atoi(str); return 0; } static int make_node(struct data *data, struct spa_node **node, const char *lib, const char *name, const struct spa_dict *props) { struct spa_handle *handle; int res = 0; void *hnd = NULL; spa_handle_factory_enum_func_t enum_func; uint32_t i; char *path; if ((path = spa_aprintf("%s/%s", data->plugin_dir, lib)) == NULL) return -ENOMEM; hnd = dlopen(path, RTLD_NOW); free(path); if (hnd == NULL) { printf("can't load %s: %s\n", lib, dlerror()); return -ENOENT; } if ((enum_func = dlsym(hnd, SPA_HANDLE_FACTORY_ENUM_FUNC_NAME)) == NULL) { printf("can't find enum function\n"); res = -ENOENT; goto exit_cleanup; } for (i = 0;;) { const struct spa_handle_factory *factory; void *iface; if ((res = enum_func(&factory, &i)) <= 0) { if (res != 0) printf("can't enumerate factories: %s\n", spa_strerror(res)); break; } if (factory->version < 1) continue; if (!spa_streq(factory->name, name)) continue; handle = calloc(1, spa_handle_factory_get_size(factory, NULL)); if ((res = spa_handle_factory_init(factory, handle, props, data->support, data->n_support)) < 0) { printf("can't make factory instance: %d\n", res); goto exit_cleanup; } if ((res = spa_handle_get_interface(handle, SPA_TYPE_INTERFACE_Node, &iface)) < 0) { printf("can't get interface %d\n", res); goto exit_cleanup; } *node = iface; return 0; } return -EBADF; exit_cleanup: dlclose(hnd); return res; } static int get_ramp_samples(struct data *data) { int samples = -1; if (data->volume_ramp_samples) samples = data->volume_ramp_samples; else if (data->volume_ramp_time) { samples = (data->volume_ramp_time * 48000) / 1000; } if (!samples) samples = -1; return samples; } static int get_ramp_step_samples(struct data *data) { int samples = -1; if (data->volume_ramp_step_samples) samples = data->volume_ramp_step_samples; else if (data->volume_ramp_step_time) { /* convert the step time which is in nano seconds to seconds */ samples = (data->volume_ramp_step_time / 1000) * (48000 / 1000); } if (!samples) samples = -1; return samples; } static double get_volume_at_scale(struct data *data) { if (data->scale == SPA_AUDIO_VOLUME_RAMP_LINEAR) return data->volume_accum; else if (data->scale == SPA_AUDIO_VOLUME_RAMP_CUBIC) return (data->volume_accum * data->volume_accum * data->volume_accum); return 0.0; } static int fade_in(struct data *data) { printf("fading in\n"); if (spa_streq (data->mode, NON_NATIVE)) { struct spa_pod_builder b; struct spa_pod_frame f[1]; void *buffer = data->control_buffer->datas[0].data; int ramp_samples = get_ramp_samples(data); int ramp_step_samples = get_ramp_step_samples(data); double step_size = ((double) ramp_step_samples / (double) ramp_samples); uint32_t buffer_size = data->control_buffer->datas[0].maxsize; data->control_buffer->datas[0].chunk[0].size = buffer_size; spa_pod_builder_init(&b, buffer, buffer_size); spa_pod_builder_push_sequence(&b, &f[0], 0); data->volume_offs = 0; do { // printf("volume level %f offset %d\n", get_volume_at_scale(data), data->volume_offs); spa_pod_builder_control(&b, data->volume_offs, SPA_CONTROL_Properties); spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_Props, 0, SPA_PROP_volume, SPA_POD_Float(get_volume_at_scale(data))); data->volume_accum += step_size; data->volume_offs += ramp_step_samples; } while (data->volume_accum < 1.0); spa_pod_builder_pop(&b, &f[0]); } else { struct spa_pod_builder b; struct spa_pod *props; int res = 0; uint8_t buffer[1024]; spa_pod_builder_init(&b, buffer, sizeof(buffer)); props = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_Props, 0, SPA_PROP_volume, SPA_POD_Float(1.0), SPA_PROP_volumeRampSamples, SPA_POD_Int(data->volume_ramp_samples), SPA_PROP_volumeRampStepSamples, SPA_POD_Int(data->volume_ramp_step_samples), SPA_PROP_volumeRampTime, SPA_POD_Int(data->volume_ramp_time), SPA_PROP_volumeRampStepTime, SPA_POD_Int(data->volume_ramp_step_time), SPA_PROP_volumeRampScale, SPA_POD_Id(data->scale)); if ((res = spa_node_set_param(data->sink_node, SPA_PARAM_Props, 0, props)) < 0) { printf("can't call volramp set params %d\n", res); return res; } } return 0; } static int fade_out(struct data *data) { printf("fading out\n"); if (spa_streq (data->mode, NON_NATIVE)) { struct spa_pod_builder b; struct spa_pod_frame f[1]; int ramp_samples = get_ramp_samples(data); int ramp_step_samples = get_ramp_step_samples(data); double step_size = ((double) ramp_step_samples / (double) ramp_samples); void *buffer = data->control_buffer->datas[0].data; uint32_t buffer_size = data->control_buffer->datas[0].maxsize; data->control_buffer->datas[0].chunk[0].size = buffer_size; spa_pod_builder_init(&b, buffer, buffer_size); spa_pod_builder_push_sequence(&b, &f[0], 0); data->volume_offs = ramp_step_samples; do { // printf("volume level %f offset %d\n", get_volume_at_scale(data), data->volume_offs); spa_pod_builder_control(&b, data->volume_offs, SPA_CONTROL_Properties); spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_Props, 0, SPA_PROP_volume, SPA_POD_Float(get_volume_at_scale(data))); data->volume_accum -= step_size; data->volume_offs += ramp_step_samples; } while (data->volume_accum > 0.0); spa_pod_builder_pop(&b, &f[0]); } else { struct spa_pod_builder b; uint8_t buffer[1024]; struct spa_pod *props; int res = 0; spa_pod_builder_init(&b, buffer, sizeof(buffer)); props = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_Props, 0, SPA_PROP_volume, SPA_POD_Float(0.0), SPA_PROP_volumeRampSamples, SPA_POD_Int(data->volume_ramp_samples), SPA_PROP_volumeRampStepSamples, SPA_POD_Int(data->volume_ramp_step_samples), SPA_PROP_volumeRampTime, SPA_POD_Int(data->volume_ramp_time), SPA_PROP_volumeRampStepTime, SPA_POD_Int(data->volume_ramp_step_time), SPA_PROP_volumeRampScale, SPA_POD_Id(data->scale)); if ((res = spa_node_set_param(data->sink_node, SPA_PARAM_Props, 0, props)) < 0) { printf("can't call volramp set params %d\n", res); return res; } } return 0; } static void do_fade(struct data *data) { if (spa_streq (data->mode, NON_NATIVE)) { switch (data->control_io.status) { case SPA_STATUS_OK: case SPA_STATUS_NEED_DATA: break; case SPA_STATUS_HAVE_DATA: case SPA_STATUS_STOPPED: default: return; } } /* fade */ if (data->start_fade_in) fade_in(data); else fade_out(data); if (spa_streq (data->mode, NON_NATIVE)) { data->control_io.status = SPA_STATUS_HAVE_DATA; data->control_io.buffer_id = 0; } /* alternate */ data->start_fade_in = !data->start_fade_in; } static int on_sink_node_ready(void *_data, int status) { struct data *data = _data; int runway = (get_ramp_samples(data) / 1024); /* only do fade in/out when buffer count is 0 */ if (data->buffer_count == 0) do_fade(data); /* update buffer count */ data->buffer_count++; if (data->buffer_count > (runway * 2)) data->buffer_count = 0; spa_graph_node_process(&data->graph_source_node); spa_graph_node_process(&data->graph_sink_node); return 0; } static const struct spa_node_callbacks sink_node_callbacks = { SPA_VERSION_NODE_CALLBACKS, .ready = on_sink_node_ready, }; static int make_nodes(struct data *data) { int res = 0; struct spa_pod *props; struct spa_pod_builder b = { 0 }; uint8_t buffer[1024]; char value[32]; struct spa_dict_item items[2]; struct spa_audio_info_raw info; struct spa_pod *param; float initial_volume = 0.0; items[0] = SPA_DICT_ITEM_INIT("clock.quantum-limit", "8192"); /* make the source node (audiotestsrc) */ if ((res = make_node(data, &data->source_follower_node, "audiotestsrc/libspa-audiotestsrc.so", "audiotestsrc", &SPA_DICT_INIT(items, 1))) < 0) { printf("can't create source follower node (audiotestsrc): %d\n", res); return res; } printf("created source follower node %p\n", data->source_follower_node); /* set the format on the source */ spa_pod_builder_init(&b, buffer, sizeof(buffer)); param = spa_format_audio_raw_build(&b, 0, &SPA_AUDIO_INFO_RAW_INIT( .format = SPA_AUDIO_FORMAT_S16, .rate = 48000, .channels = 2 )); if ((res = spa_node_port_set_param(data->source_follower_node, SPA_DIRECTION_OUTPUT, 0, SPA_PARAM_Format, 0, param)) < 0) { printf("can't set format on follower node (audiotestsrc): %d\n", res); return res; } /* make the source adapter node */ snprintf(value, sizeof(value), "pointer:%p", data->source_follower_node); items[1] = SPA_DICT_ITEM_INIT("audio.adapt.follower", value); if ((res = make_node(data, &data->source_node, "audioconvert/libspa-audioconvert.so", SPA_NAME_AUDIO_ADAPT, &SPA_DICT_INIT(items, 2))) < 0) { printf("can't create source adapter node: %d\n", res); return res; } printf("created source adapter node %p\n", data->source_node); /* setup the source node props */ spa_pod_builder_init(&b, buffer, sizeof(buffer)); props = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_Props, 0, SPA_PROP_frequency, SPA_POD_Float(600.0), SPA_PROP_volume, SPA_POD_Float(0.5), SPA_PROP_live, SPA_POD_Bool(false)); if ((res = spa_node_set_param(data->source_node, SPA_PARAM_Props, 0, props)) < 0) { printf("can't setup source follower node %d\n", res); return res; } /* setup the source node port config */ spa_zero(info); info.format = SPA_AUDIO_FORMAT_F32P; info.channels = 1; info.rate = 48000; info.position[0] = SPA_AUDIO_CHANNEL_MONO; spa_pod_builder_init(&b, buffer, sizeof(buffer)); param = spa_format_audio_raw_build(&b, SPA_PARAM_Format, &info); param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamPortConfig, SPA_PARAM_PortConfig, SPA_PARAM_PORT_CONFIG_direction, SPA_POD_Id(SPA_DIRECTION_OUTPUT), SPA_PARAM_PORT_CONFIG_mode, SPA_POD_Id(SPA_PARAM_PORT_CONFIG_MODE_dsp), SPA_PARAM_PORT_CONFIG_format, SPA_POD_Pod(param)); if ((res = spa_node_set_param(data->source_node, SPA_PARAM_PortConfig, 0, param)) < 0) { printf("can't setup source node %d\n", res); return res; } /* make the sink follower node (alsa-pcm-sink) */ if ((res = make_node(data, &data->sink_follower_node, "alsa/libspa-alsa.so", SPA_NAME_API_ALSA_PCM_SINK, &SPA_DICT_INIT(items, 1))) < 0) { printf("can't create sink follower node (alsa-pcm-sink): %d\n", res); return res; } printf("created sink follower node %p\n", data->sink_follower_node); /* make the sink adapter node */ snprintf(value, sizeof(value), "pointer:%p", data->sink_follower_node); items[1] = SPA_DICT_ITEM_INIT("audio.adapt.follower", value); if ((res = make_node(data, &data->sink_node, "audioconvert/libspa-audioconvert.so", SPA_NAME_AUDIO_ADAPT, &SPA_DICT_INIT(items, 2))) < 0) { printf("can't create sink adapter node: %d\n", res); return res; } printf("created sink adapter node %p\n", data->sink_node); /* add sink follower node callbacks */ spa_node_set_callbacks(data->sink_node, &sink_node_callbacks, data); /* setup the sink node props */ spa_pod_builder_init(&b, buffer, sizeof(buffer)); props = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_Props, 0, SPA_PROP_device, SPA_POD_String(data->alsa_device), SPA_PROP_minLatency, SPA_POD_Int(MIN_LATENCY)); if ((res = spa_node_set_param(data->sink_follower_node, SPA_PARAM_Props, 0, props)) < 0) { printf("can't setup sink follower node %d\n", res); return res; } printf("Selected (%s) alsa device\n", data->alsa_device); if (!data->start_fade_in) initial_volume = 1.0; /* setup the sink node port config */ spa_zero(info); info.format = SPA_AUDIO_FORMAT_F32P; info.channels = 1; info.rate = 48000; info.position[0] = SPA_AUDIO_CHANNEL_MONO; spa_pod_builder_init(&b, buffer, sizeof(buffer)); param = spa_format_audio_raw_build(&b, SPA_PARAM_Format, &info); if (spa_streq (data->mode, NON_NATIVE)) param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamPortConfig, SPA_PARAM_PortConfig, SPA_PARAM_PORT_CONFIG_direction, SPA_POD_Id(SPA_DIRECTION_INPUT), SPA_PARAM_PORT_CONFIG_mode, SPA_POD_Id(SPA_PARAM_PORT_CONFIG_MODE_dsp), SPA_PARAM_PORT_CONFIG_control, SPA_POD_Bool(true), SPA_PARAM_PORT_CONFIG_format, SPA_POD_Pod(param)); else param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamPortConfig, SPA_PARAM_PortConfig, SPA_PARAM_PORT_CONFIG_direction, SPA_POD_Id(SPA_DIRECTION_INPUT), SPA_PARAM_PORT_CONFIG_mode, SPA_POD_Id(SPA_PARAM_PORT_CONFIG_MODE_dsp), SPA_PARAM_PORT_CONFIG_format, SPA_POD_Pod(param)); if ((res = spa_node_set_param(data->sink_node, SPA_PARAM_PortConfig, 0, param)) < 0) { printf("can't setup sink node %d\n", res); return res; } spa_pod_builder_init(&b, buffer, sizeof(buffer)); props = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_Props, 0, SPA_PROP_volume, SPA_POD_Float(initial_volume)); if ((res = spa_node_set_param(data->sink_node, SPA_PARAM_Props, 0, props)) < 0) { printf("can't configure initial volume %d\n", res); return res; } /* set io buffers on source and sink nodes */ data->source_sink_io[0] = SPA_IO_BUFFERS_INIT; if ((res = spa_node_port_set_io(data->source_node, SPA_DIRECTION_OUTPUT, 0, SPA_IO_Buffers, &data->source_sink_io[0], sizeof(data->source_sink_io[0]))) < 0) { printf("can't set io buffers on port 0 of source node: %d\n", res); return res; } printf("set io buffers on port 0 of source node %p\n", data->source_node); if ((res = spa_node_port_set_io(data->sink_node, SPA_DIRECTION_INPUT, 0, SPA_IO_Buffers, &data->source_sink_io[0], sizeof(data->source_sink_io[0]))) < 0) { printf("can't set io buffers on port 0 of sink node: %d\n", res); return res; } printf("set io buffers on port 0 of sink node %p\n", data->sink_node); /* set io position and clock on source and sink nodes */ data->position.clock.target_rate = SPA_FRACTION(1, 48000); data->position.clock.target_duration = 1024; data->position.clock.rate = data->position.clock.target_rate; data->position.clock.duration = data->position.clock.target_duration; if ((res = spa_node_set_io(data->source_node, SPA_IO_Position, &data->position, sizeof(data->position))) < 0) { printf("can't set io position on source node: %d\n", res); return res; } if ((res = spa_node_set_io(data->sink_node, SPA_IO_Position, &data->position, sizeof(data->position))) < 0) { printf("can't set io position on sink node: %d\n", res); return res; } if ((res = spa_node_set_io(data->source_node, SPA_IO_Clock, &data->position.clock, sizeof(data->position.clock))) < 0) { printf("can't set io clock on source node: %d\n", res); return res; } if ((res = spa_node_set_io(data->sink_node, SPA_IO_Clock, &data->position.clock, sizeof(data->position.clock))) < 0) { printf("can't set io clock on sink node: %d\n", res); return res; } if (spa_streq (data->mode, NON_NATIVE)) { /* set io buffers on control port of sink node */ if ((res = spa_node_port_set_io(data->sink_node, SPA_DIRECTION_INPUT, 1, SPA_IO_Buffers, &data->control_io, sizeof(data->control_io))) < 0) { printf("can't set io buffers on control port 1 of sink node\n"); return res; } } /* add source node to the graph */ spa_graph_node_init(&data->graph_source_node, &data->graph_source_state); spa_graph_node_set_callbacks(&data->graph_source_node, &spa_graph_node_impl_default, data->source_node); spa_graph_node_add(&data->graph, &data->graph_source_node); spa_graph_port_init(&data->graph_source_port_0, SPA_DIRECTION_OUTPUT, 0, 0); spa_graph_port_add(&data->graph_source_node, &data->graph_source_port_0); /* add sink node to the graph */ spa_graph_node_init(&data->graph_sink_node, &data->graph_sink_state); spa_graph_node_set_callbacks(&data->graph_sink_node, &spa_graph_node_impl_default, data->sink_node); spa_graph_node_add(&data->graph, &data->graph_sink_node); spa_graph_port_init(&data->graph_sink_port_0, SPA_DIRECTION_INPUT, 0, 0); spa_graph_port_add(&data->graph_sink_node, &data->graph_sink_port_0); /* link source and sink nodes */ spa_graph_port_link(&data->graph_source_port_0, &data->graph_sink_port_0); return res; } static void init_buffer(struct data *data, struct spa_buffer **bufs, struct buffer *ba, int n_buffers, size_t size) { int i; for (i = 0; i < n_buffers; i++) { struct buffer *b = &ba[i]; bufs[i] = &b->buffer; b->buffer.metas = b->metas; b->buffer.n_metas = 1; b->buffer.datas = b->datas; b->buffer.n_datas = 1; b->header.flags = 0; b->header.seq = 0; b->header.pts = 0; b->header.dts_offset = 0; b->metas[0].type = SPA_META_Header; b->metas[0].data = &b->header; b->metas[0].size = sizeof(b->header); b->datas[0].type = SPA_DATA_MemPtr; b->datas[0].flags = 0; b->datas[0].fd = -1; b->datas[0].mapoffset = 0; b->datas[0].maxsize = size; b->datas[0].data = malloc(size); b->datas[0].chunk = &b->chunks[0]; b->datas[0].chunk->offset = 0; b->datas[0].chunk->size = 0; b->datas[0].chunk->stride = 0; } } static int negotiate_formats(struct data *data) { int res; struct spa_pod *filter = NULL, *param = NULL; struct spa_pod_builder b = { 0 }; uint8_t buffer[4096]; uint32_t state = 0; size_t buffer_size = 1024; /* set the sink and source formats */ spa_pod_builder_init(&b, buffer, sizeof(buffer)); param = spa_format_audio_dsp_build(&b, 0, &SPA_AUDIO_INFO_DSP_INIT( .format = SPA_AUDIO_FORMAT_F32P)); if ((res = spa_node_port_set_param(data->source_node, SPA_DIRECTION_OUTPUT, 0, SPA_PARAM_Format, 0, param)) < 0) { printf("can't set format on source node: %d\n", res); return res; } if ((res = spa_node_port_set_param(data->sink_node, SPA_DIRECTION_INPUT, 0, SPA_PARAM_Format, 0, param)) < 0) { printf("can't set format on source node: %d\n", res); return res; } if (spa_streq (data->mode, NON_NATIVE)) { spa_pod_builder_init(&b, buffer, sizeof(buffer)); param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_Format, SPA_PARAM_Format, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_application), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_control)); if ((res = spa_node_port_set_param(data->sink_node, SPA_DIRECTION_INPUT, 1, SPA_PARAM_Format, 0, param)) < 0) { printf("can't set format on control port of source node: %d\n", res); return res; } } /* get the source node buffer size */ spa_pod_builder_init(&b, buffer, sizeof(buffer)); if ((res = spa_node_port_enum_params_sync(data->source_node, SPA_DIRECTION_OUTPUT, 0, SPA_PARAM_Buffers, &state, filter, ¶m, &b)) != 1) return res ? res : -ENOTSUP; spa_pod_fixate(param); if ((res = spa_pod_parse_object(param, SPA_TYPE_OBJECT_ParamBuffers, NULL, SPA_PARAM_BUFFERS_size, SPA_POD_Int(&buffer_size))) < 0) return res; /* use buffers on the source and sink */ init_buffer(data, data->source_buffers, data->source_buffer, 1, buffer_size); if ((res = spa_node_port_use_buffers(data->source_node, SPA_DIRECTION_OUTPUT, 0, 0, data->source_buffers, 1)) < 0) return res; printf("allocated and assigned buffer (%zu) to source node %p\n", buffer_size, data->source_node); if ((res = spa_node_port_use_buffers(data->sink_node, SPA_DIRECTION_INPUT, 0, 0, data->source_buffers, 1)) < 0) return res; printf("allocated and assigned buffers to sink node %p\n", data->sink_node); if (spa_streq (data->mode, NON_NATIVE)) { /* Set the control buffers */ init_buffer(data, data->control_buffers, data->control_buffer, 1, CONTROL_BUFFER_SIZE); if ((res = spa_node_port_use_buffers(data->sink_node, SPA_DIRECTION_INPUT, 1, 0, data->control_buffers, 1)) < 0) return res; printf("allocated and assigned control buffers(%d) to sink node %p\n", CONTROL_BUFFER_SIZE, data->sink_node); } return 0; } static void *loop(void *user_data) { struct data *data = user_data; printf("enter thread\n"); spa_loop_control_enter(data->control); while (data->running) { spa_loop_control_iterate(data->control, -1); } printf("leave thread\n"); spa_loop_control_leave(data->control); return NULL; return NULL; } static void run_async_sink(struct data *data) { int res, err; struct spa_command cmd; cmd = SPA_NODE_COMMAND_INIT(SPA_NODE_COMMAND_Start); if ((res = spa_node_send_command(data->source_node, &cmd)) < 0) printf("got error %d\n", res); printf("Source node started\n"); if ((res = spa_node_send_command(data->sink_node, &cmd)) < 0) printf("got error %d\n", res); printf("sink node started\n"); spa_loop_control_leave(data->control); data->running = true; if ((err = pthread_create(&data->thread, NULL, loop, data)) != 0) { printf("can't create thread: %d %s", err, strerror(err)); data->running = false; } printf("sleeping for 1000 seconds\n"); sleep(1000); if (data->running) { data->running = false; pthread_join(data->thread, NULL); } spa_loop_control_enter(data->control); cmd = SPA_NODE_COMMAND_INIT(SPA_NODE_COMMAND_Pause); if ((res = spa_node_send_command(data->source_node, &cmd)) < 0) printf("got error %d\n", res); if ((res = spa_node_send_command(data->sink_node, &cmd)) < 0) printf("got error %d\n", res); } static const char *getscale(uint32_t scale) { const char *scale_s = NULL; if (scale == SPA_AUDIO_VOLUME_RAMP_LINEAR) scale_s = LINEAR; else if (scale == SPA_AUDIO_VOLUME_RAMP_CUBIC) scale_s = CUBIC; return scale_s; } static void show_help(struct data *data, const char *name, bool error) { fprintf(error ? stderr : stdout, "%s [options] [command]\n" " -h, --help Show this help\n" " -d, --alsa-device ALSA device(\"aplay -l\" for more info) to play the samples on(default %s)\n" " -m, --mode Volume Ramp Mode(\"NonNative\"(via Control Port) \"Native\" (via Volume Ramp Params of AudioAdapter plugin)) (default %s)\n" " -s, --ramp-samples SPA_PROP_volumeRampSamples(Samples to ramp the volume over)(default %d)\n" " -a, --ramp-step-samples SPA_PROP_volumeRampStepSamples(Step or incremental Samples to ramp the volume over)(default %d)\n" " -t, --ramp-time SPA_PROP_volumeRampTime(Time to ramp the volume over in msec)(default %d)\n" " -i, --ramp-step-time SPA_PROP_volumeRampStepTime(Step or incremental Time to ramp the volume over in nano sec)(default %d)\n" " -c, --scale SPA_PROP_volumeRampScale(the scale or graph to used to ramp the volume)(\"linear\" or \"cubic\")(default %s)\n" "examples:\n" "adapter-control\n" "-->when invoked with out any params, ramps volume with default values\n" "adapter-control --ramp-samples=70000, rest of the parameters are defaults\n" "-->ramps volume over 70000 samples(it is 1.45 seconds)\n" "adapter-control --alsa-device=hw:0,0 --ramp-samples=70000\n" "-->ramps volume on \"hw:0,0\" alsa device over 70000 samples\n" "adapter-control --alsa-device=hw:0,0 --ramp-samples=70000 --mode=native\n" "-->ramps volume on \"hw:0,0\" alsa device over 70000 samples in native mode\n" "adapter-control --alsa-device=hw:0,0 --ramp-time=1000 --mode=native\n" "-->ramps volume on \"hw:0,0\" alsa device over 1000 msec in native mode\n" "adapter-control --alsa-device=hw:0,0 --ramp-time=1000 --ramp-step-time=5000 --mode=native\n" "-->ramps volume on \"hw:0,0\" alsa device over 1000 msec in steps of 5000 nano seconds(5 msec)in native mode\n" "adapter-control --alsa-device=hw:0,0 --ramp-samples=70000 --ramp-step-samples=200 --mode=native\n" "-->ramps volume on \"hw:0,0\" alsa device over 70000 samples with a step size of 200 samples in native mode\n" "adapter-control --alsa-device=hw:1,0 --scale=linear\n" "-->ramps volume on \"hw:1,0\" in linear volume scale, one can leave choose to not use the linear scale here as it is the default\n" "adapter-control --alsa-device=hw:1,0 --ramp-samples=70000 --scale=cubic\n" "-->ramps volume on \"hw:1,0\" alsa device over 70000 samples deploying cubic volume scale\n" "adapter-control --alsa-device=hw:1,0 --ramp-samples=70000 --mode=native --scale=cubic\n" "-->ramps volume on \"hw:1,0\" alsa device over 70000 samples deploying cubic volume scale in native mode\n" "adapter-control --alsa-device=hw:1,0 --ramp-time=3000 --scale=cubic --mode=native\n" "-->ramps volume on \"hw:1,0\" alsa device over 3 seconds samples with a step size of 200 samples in native mode\n", name, DEFAULT_DEVICE, DEFAULT_MODE, DEFAULT_RAMP_SAMPLES, DEFAULT_RAMP_STEP_SAMPLES, DEFAULT_RAMP_TIME, DEFAULT_RAMP_STEP_TIME, getscale(DEFAULT_SCALE)); } int main(int argc, char *argv[]) { struct data data = { 0 }; int res = 0, c; /* default values*/ data.volume_ramp_samples = DEFAULT_RAMP_SAMPLES; data.volume_ramp_step_samples = DEFAULT_RAMP_STEP_SAMPLES; data.alsa_device = DEFAULT_DEVICE; data.mode = DEFAULT_MODE; data.scale = DEFAULT_SCALE; static const struct option long_options[] = { { "help", no_argument, NULL, 'h' }, { "alsa-device", required_argument, NULL, 'd' }, { "mode", required_argument, NULL, 'm' }, { "ramp-samples", required_argument, NULL, 's' }, { "ramp-time", required_argument, NULL, 't' }, { "ramp-step-samples", required_argument, NULL, 'a' }, { "ramp-step-time", required_argument, NULL, 'i' }, { "scale", required_argument, NULL, 'c' }, { NULL, 0, NULL, 0} }; setlocale(LC_ALL, ""); while ((c = getopt_long(argc, argv, "hd:m:s:t:i:a:c:", long_options, NULL)) != -1) { switch (c) { case 'h': show_help(&data, argv[0], false); return 0; case 'm': if (!spa_streq (optarg, NATIVE) && !spa_streq (optarg, NON_NATIVE)) printf("Invalid Mode(\"%s\"), using default(\"%s\")\n", optarg, DEFAULT_MODE); else data.mode = optarg; break; case 'c': if (!spa_streq (optarg, LINEAR) && !spa_streq (optarg, CUBIC)) printf("Invalid Scale(\"%s\"), using default(\"%s\")\n", optarg, getscale(DEFAULT_SCALE)); else if (spa_streq (optarg, LINEAR)) data.scale = SPA_AUDIO_VOLUME_RAMP_LINEAR; else if (spa_streq (optarg, CUBIC)) data.scale = SPA_AUDIO_VOLUME_RAMP_CUBIC; break; case 'd': data.alsa_device = optarg; break; case 's': data.volume_ramp_samples = atoi(optarg); break; case 't': data.volume_ramp_time = atoi(optarg); if (!data.volume_ramp_step_time) data.volume_ramp_step_time = DEFAULT_RAMP_STEP_TIME; data.volume_ramp_samples = 0; data.volume_ramp_step_samples = 0; break; case 'a': data.volume_ramp_step_samples = atoi(optarg); break; case 'i': data.volume_ramp_step_time = atoi(optarg); break; default: show_help(&data, argv[0], true); return -1; } } /* init data */ if ((res = init_data(&data)) < 0) { printf("can't init data: %d (%s)\n", res, spa_strerror(res)); return -1; } /* make the nodes (audiotestsrc and adapter with alsa-pcm-sink as follower) */ if ((res = make_nodes(&data)) < 0) { printf("can't make nodes: %d (%s)\n", res, spa_strerror(res)); return -1; } /* Negotiate format */ if ((res = negotiate_formats(&data)) < 0) { printf("can't negotiate nodes: %d (%s)\n", res, spa_strerror(res)); return -1; } printf("using %s mode\n", data.mode); if (data.volume_ramp_samples && data.volume_ramp_step_samples) printf("using %d samples with a step size of %d samples to ramp volume at %s scale\n", data.volume_ramp_samples, data.volume_ramp_step_samples, getscale(data.scale)); else if (data.volume_ramp_time && data.volume_ramp_step_time) printf("using %d msec with a step size of %d msec to ramp volume at %s scale\n", data.volume_ramp_time, (data.volume_ramp_step_time/1000), getscale(data.scale)); spa_loop_control_enter(data.control); run_async_sink(&data); spa_loop_control_leave(data.control); } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/examples/example-control.c000066400000000000000000000335561511204443500263700ustar00rootroot00000000000000/* Spa */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ /* [title] [title] */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define M_PI_M2 ( M_PI + M_PI ) static SPA_LOG_IMPL(default_log); #define spa_debug(f,...) spa_log_trace(&default_log.log, f, __VA_ARGS__) #include #include struct buffer { struct spa_buffer buffer; struct spa_meta metas[1]; struct spa_meta_header header; struct spa_data datas[1]; struct spa_chunk chunks[1]; }; struct data { const char *plugin_dir; struct spa_log *log; struct spa_system *system; struct spa_loop *loop; struct spa_loop_control *control; struct spa_support support[5]; uint32_t n_support; struct spa_graph graph; struct spa_graph_state graph_state; struct spa_graph_node source_node; struct spa_graph_state source_state; struct spa_graph_port source_out; struct spa_graph_port sink_in; struct spa_graph_node sink_node; struct spa_graph_state sink_state; struct spa_node *sink; struct spa_node *source; struct spa_io_buffers source_sink_io[1]; struct spa_buffer *source_buffers[1]; struct buffer source_buffer[1]; uint8_t ctrl[1024]; double freq_accum; double volume_accum; bool running; pthread_t thread; }; #define MIN_LATENCY 1024 #define BUFFER_SIZE 4096 static void init_buffer(struct data *data, struct spa_buffer **bufs, struct buffer *ba, int n_buffers, size_t size) { int i; for (i = 0; i < n_buffers; i++) { struct buffer *b = &ba[i]; bufs[i] = &b->buffer; b->buffer.metas = b->metas; b->buffer.n_metas = 1; b->buffer.datas = b->datas; b->buffer.n_datas = 1; b->header.flags = 0; b->header.seq = 0; b->header.pts = 0; b->header.dts_offset = 0; b->metas[0].type = SPA_META_Header; b->metas[0].data = &b->header; b->metas[0].size = sizeof(b->header); b->datas[0].type = SPA_DATA_MemPtr; b->datas[0].flags = 0; b->datas[0].fd = -1; b->datas[0].mapoffset = 0; b->datas[0].maxsize = size; b->datas[0].data = malloc(size); b->datas[0].chunk = &b->chunks[0]; b->datas[0].chunk->offset = 0; b->datas[0].chunk->size = 0; b->datas[0].chunk->stride = 0; } } static int make_node(struct data *data, struct spa_node **node, const char *lib, const char *name) { struct spa_handle *handle; int res; void *hnd; spa_handle_factory_enum_func_t enum_func; uint32_t i; char *path; if ((path = spa_aprintf("%s/%s", data->plugin_dir, lib)) == NULL) { return -ENOMEM; } if ((hnd = dlopen(path, RTLD_NOW)) == NULL) { printf("can't load %s: %s\n", lib, dlerror()); free(path); return -ENOENT; } free(path); if ((enum_func = dlsym(hnd, SPA_HANDLE_FACTORY_ENUM_FUNC_NAME)) == NULL) { printf("can't find enum function\n"); return -ENOENT; } for (i = 0;;) { const struct spa_handle_factory *factory; void *iface; if ((res = enum_func(&factory, &i)) <= 0) { if (res != 0) printf("can't enumerate factories: %s\n", spa_strerror(res)); break; } if (factory->version < 1) continue; if (!spa_streq(factory->name, name)) continue; handle = calloc(1, spa_handle_factory_get_size(factory, NULL)); if ((res = spa_handle_factory_init(factory, handle, NULL, data->support, data->n_support)) < 0) { printf("can't make factory instance: %d\n", res); return res; } if ((res = spa_handle_get_interface(handle, SPA_TYPE_INTERFACE_Node, &iface)) < 0) { printf("can't get interface %d\n", res); return res; } *node = iface; return 0; } return -EBADF; } static void update_props(struct data *data) { struct spa_pod_builder b; struct spa_pod *pod; struct spa_pod_frame f[2]; spa_pod_builder_init(&b, data->ctrl, sizeof(data->ctrl)); #if 0 spa_pod_builder_push_sequence(&b, &f[0], 0); spa_pod_builder_control(&b, 0, SPA_CONTROL_Properties); spa_pod_builder_push_object(&b, &f[1], SPA_TYPE_OBJECT_Props, 0); spa_pod_builder_prop(&b, SPA_PROP_frequency, 0); spa_pod_builder_float(&b, ((sin(data->freq_accum) + 1.0) * 200.0) + 440.0); spa_pod_builder_prop(&b, SPA_PROP_volume, 0); spa_pod_builder_float(&b, (sin(data->volume_accum) / 2.0) + 0.5); spa_pod_builder_pop(&b, &f[1]); pod = spa_pod_builder_pop(&b, &f[0]); #else spa_pod_builder_push_sequence(&b, &f[0], 0); spa_pod_builder_control(&b, 0, SPA_CONTROL_Properties); spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_Props, 0, SPA_PROP_frequency, SPA_POD_Float(((sin(data->freq_accum) + 1.0) * 200.0) + 440.0), SPA_PROP_volume, SPA_POD_Float((sin(data->volume_accum) / 2.0) + 0.5)); pod = spa_pod_builder_pop(&b, &f[0]); #endif spa_debug_pod(0, NULL, pod); data->freq_accum += M_PI_M2 / 880.0; if (data->freq_accum >= M_PI_M2) data->freq_accum -= M_PI_M2; data->volume_accum += M_PI_M2 / 2000.0; if (data->volume_accum >= M_PI_M2) data->volume_accum -= M_PI_M2; } static int on_sink_ready(void *_data, int status) { struct data *data = _data; update_props(data); spa_graph_node_process(&data->source_node); spa_graph_node_process(&data->sink_node); return 0; } static int on_sink_reuse_buffer(void *_data, uint32_t port_id, uint32_t buffer_id) { struct data *data = _data; data->source_sink_io[0].buffer_id = buffer_id; return 0; } static const struct spa_node_callbacks sink_callbacks = { SPA_VERSION_NODE_CALLBACKS, .ready = on_sink_ready, .reuse_buffer = on_sink_reuse_buffer }; static int make_nodes(struct data *data, const char *device) { int res; struct spa_pod *props; struct spa_pod_builder b = { 0 }; uint8_t buffer[512]; //uint32_t idx; if ((res = make_node(data, &data->sink, "alsa/libspa-alsa.so", SPA_NAME_API_ALSA_PCM_SINK)) < 0) { printf("can't create alsa-sink: %d\n", res); return res; } spa_node_set_callbacks(data->sink, &sink_callbacks, data); spa_pod_builder_init(&b, buffer, sizeof(buffer)); props = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_Props, 0, SPA_PROP_device, SPA_POD_String(device ? device : "hw:0"), SPA_PROP_minLatency, SPA_POD_Int(MIN_LATENCY)); spa_debug_pod(0, NULL, props); if ((res = spa_node_set_param(data->sink, SPA_PARAM_Props, 0, props)) < 0) printf("got set_props error %d\n", res); if ((res = make_node(data, &data->source, "audiotestsrc/libspa-audiotestsrc.so", "audiotestsrc")) < 0) { printf("can't create audiotestsrc: %d\n", res); return res; } spa_pod_builder_init(&b, buffer, sizeof(buffer)); props = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_Props, 0, SPA_PROP_frequency, SPA_POD_Float(600.0), SPA_PROP_volume, SPA_POD_Float(0.5), SPA_PROP_live, SPA_POD_Bool(false)); if ((res = spa_node_set_param(data->source, SPA_PARAM_Props, 0, props)) < 0) printf("got set_props error %d\n", res); if ((res = spa_node_port_set_io(data->source, SPA_DIRECTION_OUTPUT, 0, SPA_IO_Control, &data->ctrl, sizeof(data->ctrl))) < 0) { printf("can't set_io freq: %d\n", res); return res; } data->source_sink_io[0] = SPA_IO_BUFFERS_INIT; spa_node_port_set_io(data->source, SPA_DIRECTION_OUTPUT, 0, SPA_IO_Buffers, &data->source_sink_io[0], sizeof(data->source_sink_io[0])); spa_node_port_set_io(data->sink, SPA_DIRECTION_INPUT, 0, SPA_IO_Buffers, &data->source_sink_io[0], sizeof(data->source_sink_io[0])); spa_graph_node_init(&data->source_node, &data->source_state); spa_graph_node_set_callbacks(&data->source_node, &spa_graph_node_impl_default, data->source); spa_graph_node_add(&data->graph, &data->source_node); spa_graph_port_init(&data->source_out, SPA_DIRECTION_OUTPUT, 0, 0); spa_graph_port_add(&data->source_node, &data->source_out); spa_graph_node_init(&data->sink_node, &data->sink_state); spa_graph_node_set_callbacks(&data->sink_node, &spa_graph_node_impl_default, data->sink); spa_graph_node_add(&data->graph, &data->sink_node); spa_graph_port_init(&data->sink_in, SPA_DIRECTION_INPUT, 0, 0); spa_graph_port_add(&data->sink_node, &data->sink_in); spa_graph_port_link(&data->source_out, &data->sink_in); return res; } static int negotiate_formats(struct data *data) { int res; struct spa_pod *format; struct spa_pod_builder b = { 0 }; uint8_t buffer[4096]; spa_pod_builder_init(&b, buffer, sizeof(buffer)); format = spa_format_audio_raw_build(&b, 0, &SPA_AUDIO_INFO_RAW_INIT( .format = SPA_AUDIO_FORMAT_S16, .rate = 48000, .channels = 2 )); if ((res = spa_node_port_set_param(data->sink, SPA_DIRECTION_INPUT, 0, SPA_PARAM_Format, 0, format)) < 0) return res; if ((res = spa_node_port_set_param(data->source, SPA_DIRECTION_OUTPUT, 0, SPA_PARAM_Format, 0, format)) < 0) return res; init_buffer(data, data->source_buffers, data->source_buffer, 1, BUFFER_SIZE); if ((res = spa_node_port_use_buffers(data->sink, SPA_DIRECTION_INPUT, 0, 0, data->source_buffers, 1)) < 0) return res; if ((res = spa_node_port_use_buffers(data->source, SPA_DIRECTION_OUTPUT, 0, 0, data->source_buffers, 1)) < 0) return res; return 0; } static void *loop(void *user_data) { struct data *data = user_data; printf("enter thread\n"); spa_loop_control_enter(data->control); while (data->running) { spa_loop_control_iterate(data->control, -1); } printf("leave thread\n"); spa_loop_control_leave(data->control); return NULL; } static void run_async_sink(struct data *data) { int res, err; struct spa_command cmd; cmd = SPA_NODE_COMMAND_INIT(SPA_NODE_COMMAND_Start); if ((res = spa_node_send_command(data->sink, &cmd)) < 0) printf("got error %d\n", res); spa_loop_control_leave(data->control); data->running = true; if ((err = pthread_create(&data->thread, NULL, loop, data)) != 0) { printf("can't create thread: %d %s", err, strerror(err)); data->running = false; } printf("sleeping for 1000 seconds\n"); sleep(1000); if (data->running) { data->running = false; pthread_join(data->thread, NULL); } spa_loop_control_enter(data->control); cmd = SPA_NODE_COMMAND_INIT(SPA_NODE_COMMAND_Pause); if ((res = spa_node_send_command(data->sink, &cmd)) < 0) printf("got error %d\n", res); } static int load_handle(struct data *data, struct spa_handle **handle, const char *lib, const char *name) { int res; void *hnd; spa_handle_factory_enum_func_t enum_func; uint32_t i; char *path; if ((path = spa_aprintf("%s/%s", data->plugin_dir, lib)) == NULL) { return -ENOMEM; } if ((hnd = dlopen(path, RTLD_NOW)) == NULL) { printf("can't load %s: %s\n", lib, dlerror()); free(path); return -ENOENT; } free(path); if ((enum_func = dlsym(hnd, SPA_HANDLE_FACTORY_ENUM_FUNC_NAME)) == NULL) { printf("can't find enum function\n"); return -ENOENT; } for (i = 0;;) { const struct spa_handle_factory *factory; if ((res = enum_func(&factory, &i)) <= 0) { if (res != 0) printf("can't enumerate factories: %s\n", spa_strerror(res)); break; } if (factory->version < 1) continue; if (!spa_streq(factory->name, name)) continue; *handle = calloc(1, spa_handle_factory_get_size(factory, NULL)); if ((res = spa_handle_factory_init(factory, *handle, NULL, data->support, data->n_support)) < 0) { printf("can't make factory instance: %d\n", res); return res; } return 0; } return -EBADF; } static int init_data(struct data *data) { int res; const char *str; struct spa_handle *handle = NULL; void *iface; if ((str = getenv("SPA_PLUGIN_DIR")) == NULL) str = PLUGINDIR; data->plugin_dir = str; /* init the graph */ spa_graph_init(&data->graph, &data->graph_state); /* set the default log */ data->log = &default_log.log; data->support[data->n_support++] = SPA_SUPPORT_INIT(SPA_TYPE_INTERFACE_Log, data->log); /* load and set support system */ if ((res = load_handle(data, &handle, "support/libspa-support.so", SPA_NAME_SUPPORT_SYSTEM)) < 0) return res; if ((res = spa_handle_get_interface(handle, SPA_TYPE_INTERFACE_System, &iface)) < 0) { printf("can't get System interface %d\n", res); return res; } data->system = iface; data->support[data->n_support++] = SPA_SUPPORT_INIT(SPA_TYPE_INTERFACE_System, data->system); data->support[data->n_support++] = SPA_SUPPORT_INIT(SPA_TYPE_INTERFACE_DataSystem, data->system); /* load and set support loop and loop control */ if ((res = load_handle(data, &handle, "support/libspa-support.so", SPA_NAME_SUPPORT_LOOP)) < 0) return res; if ((res = spa_handle_get_interface(handle, SPA_TYPE_INTERFACE_Loop, &iface)) < 0) { printf("can't get interface %d\n", res); return res; } data->loop = iface; data->support[data->n_support++] = SPA_SUPPORT_INIT(SPA_TYPE_INTERFACE_Loop, data->loop); data->support[data->n_support++] = SPA_SUPPORT_INIT(SPA_TYPE_INTERFACE_DataLoop, data->loop); if ((res = spa_handle_get_interface(handle, SPA_TYPE_INTERFACE_LoopControl, &iface)) < 0) { printf("can't get interface %d\n", res); return res; } data->control = iface; if ((str = getenv("SPA_DEBUG"))) data->log->level = atoi(str); return 0; } int main(int argc, char *argv[]) { struct data data = { NULL }; int res; if ((res = init_data(&data)) < 0) { printf("can't init data: %d (%s)\n", res, spa_strerror(res)); return -1; } if ((res = make_nodes(&data, argc > 1 ? argv[1] : NULL)) < 0) { printf("can't make nodes: %d (%s)\n", res, spa_strerror(res)); return -1; } if ((res = negotiate_formats(&data)) < 0) { printf("can't negotiate nodes: %d (%s)\n", res, spa_strerror(res)); return -1; } spa_loop_control_enter(data.control); run_async_sink(&data); spa_loop_control_leave(data.control); return 0; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/examples/local-libcamera.c000066400000000000000000000305271511204443500262610ustar00rootroot00000000000000/* Spa */ /* SPDX-FileCopyrightText: Copyright © 2020 Collabora Ltd. */ /* @author Raghavendra Rao Sidlagatta */ /* SPDX-License-Identifier: MIT */ /* [title] Example using libspa-libcamera, with only \ref api_spa [title] */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define WIDTH 640 #define HEIGHT 480 static SPA_LOG_IMPL(default_log); #define MAX_BUFFERS 8 #define LOOP_TIMEOUT_MS 100 #define USE_BUFFER false struct buffer { struct spa_buffer buffer; struct spa_meta metas[1]; struct spa_meta_header header; struct spa_data datas[1]; struct spa_chunk chunks[1]; SDL_Texture *texture; }; struct data { const char *plugin_dir; struct spa_log *log; struct spa_system *system; struct spa_loop *loop; struct spa_loop_control *control; struct spa_support support[5]; uint32_t n_support; struct spa_node *source; struct spa_hook listener; struct spa_io_buffers source_output[1]; SDL_Renderer *renderer; SDL_Window *window; SDL_Texture *texture; bool use_buffer; bool running; pthread_t thread; struct spa_buffer *bp[MAX_BUFFERS]; struct buffer buffers[MAX_BUFFERS]; unsigned int n_buffers; }; static int load_handle(struct data *data, struct spa_handle **handle, const char *lib, const char *name, const struct spa_dict *params) { int res; void *hnd; spa_handle_factory_enum_func_t enum_func; uint32_t i; char *path = NULL; if ((path = spa_aprintf("%s/%s", data->plugin_dir, lib)) == NULL) { return -ENOMEM; } if ((hnd = dlopen(path, RTLD_NOW)) == NULL) { printf("can't load %s: %s\n", path, dlerror()); free(path); return -errno; } free(path); if ((enum_func = dlsym(hnd, SPA_HANDLE_FACTORY_ENUM_FUNC_NAME)) == NULL) { printf("can't find enum function\n"); return -errno; } for (i = 0;;) { const struct spa_handle_factory *factory; if ((res = enum_func(&factory, &i)) <= 0) { if (res != 0) printf("can't enumerate factories: %s\n", spa_strerror(res)); break; } if (!spa_streq(factory->name, name)) continue; *handle = calloc(1, spa_handle_factory_get_size(factory, params)); if ((res = spa_handle_factory_init(factory, *handle, params, data->support, data->n_support)) < 0) { printf("can't make factory instance: %d\n", res); return res; } return 0; } return -EBADF; } static int make_node(struct data *data, struct spa_node **node, const char *lib, const char *name, const struct spa_dict *params) { struct spa_handle *handle = NULL; void *iface; int res; if ((res = load_handle(data, &handle, lib, name, params)) < 0) return res; if ((res = spa_handle_get_interface(handle, SPA_TYPE_INTERFACE_Node, &iface)) < 0) { printf("can't get interface %d\n", res); return res; } *node = iface; return 0; } static int on_source_ready(void *_data, int status) { struct data *data = _data; int res; struct buffer *b; void *sdata, *ddata; int sstride, dstride; int i; uint8_t *src, *dst; struct spa_data *datas; struct spa_io_buffers *io = &data->source_output[0]; if (io->status != SPA_STATUS_HAVE_DATA || io->buffer_id >= MAX_BUFFERS) return -EINVAL; b = &data->buffers[io->buffer_id]; io->status = SPA_STATUS_NEED_DATA; datas = b->buffer.datas; if (b->texture) { SDL_Texture *texture = b->texture; SDL_UnlockTexture(texture); SDL_RenderClear(data->renderer); SDL_RenderCopy(data->renderer, texture, NULL, NULL); SDL_RenderPresent(data->renderer); if (SDL_LockTexture(texture, NULL, &sdata, &sstride) < 0) { fprintf(stderr, "Couldn't lock texture: %s\n", SDL_GetError()); return -EIO; } } else { uint8_t *map; if (SDL_LockTexture(data->texture, NULL, &ddata, &dstride) < 0) { fprintf(stderr, "Couldn't lock texture: %s\n", SDL_GetError()); return -EIO; } sdata = datas[0].data; if (datas[0].type == SPA_DATA_MemFd || datas[0].type == SPA_DATA_DmaBuf) { map = mmap(NULL, datas[0].maxsize, PROT_READ, MAP_PRIVATE, datas[0].fd, datas[0].mapoffset); if (map == MAP_FAILED) return -errno; sdata = map; } else if (datas[0].type == SPA_DATA_MemPtr) { map = NULL; sdata = datas[0].data; } else return -EIO; sstride = datas[0].chunk->stride; for (i = 0; i < HEIGHT; i++) { src = ((uint8_t *) sdata + i * sstride); dst = ((uint8_t *) ddata + i * dstride); memcpy(dst, src, SPA_MIN(sstride, dstride)); } SDL_UnlockTexture(data->texture); SDL_RenderClear(data->renderer); SDL_RenderCopy(data->renderer, data->texture, NULL, NULL); SDL_RenderPresent(data->renderer); if (map) munmap(map, datas[0].maxsize); } if ((res = spa_node_process(data->source)) < 0) printf("got process error %d\n", res); return 0; } static const struct spa_node_callbacks source_callbacks = { SPA_VERSION_NODE_CALLBACKS, .ready = on_source_ready, }; static int make_nodes(struct data *data, const char *device) { int res; struct spa_pod *props; struct spa_pod_builder b = { 0 }; uint8_t buffer[256]; uint32_t index; const struct spa_dict_item items[] = { { SPA_KEY_API_LIBCAMERA_PATH, device }, }; if ((res = make_node(data, &data->source, "libcamera/libspa-libcamera.so", SPA_NAME_API_LIBCAMERA_SOURCE, &SPA_DICT_INIT_ARRAY(items))) < 0) { printf("can't create libcamera-source: %d\n", res); return res; } spa_node_set_callbacks(data->source, &source_callbacks, data); index = 0; spa_pod_builder_init(&b, buffer, sizeof(buffer)); if ((res = spa_node_enum_params_sync(data->source, SPA_PARAM_Props, &index, NULL, &props, &b)) == 1) { spa_debug_pod(0, NULL, props); } return res; } static int setup_buffers(struct data *data) { int i; for (i = 0; i < MAX_BUFFERS; i++) { struct buffer *b = &data->buffers[i]; data->bp[i] = &b->buffer; b->texture = NULL; b->buffer.metas = b->metas; b->buffer.n_metas = 1; b->buffer.datas = b->datas; b->buffer.n_datas = 1; b->header.flags = 0; b->header.seq = 0; b->header.pts = 0; b->header.dts_offset = 0; b->metas[0].type = SPA_META_Header; b->metas[0].data = &b->header; b->metas[0].size = sizeof(b->header); b->datas[0].type = SPA_DATA_DmaBuf; b->datas[0].flags = 0; b->datas[0].fd = -1; b->datas[0].mapoffset = 0; b->datas[0].maxsize = 0; b->datas[0].data = NULL; b->datas[0].chunk = &b->chunks[0]; b->datas[0].chunk->offset = 0; b->datas[0].chunk->size = 0; b->datas[0].chunk->stride = 0; } data->n_buffers = MAX_BUFFERS; return 0; } static int sdl_alloc_buffers(struct data *data) { int i; for (i = 0; i < MAX_BUFFERS; i++) { struct buffer *b = &data->buffers[i]; SDL_Texture *texture; void *ptr; int stride; texture = SDL_CreateTexture(data->renderer, SDL_PIXELFORMAT_YUY2, SDL_TEXTUREACCESS_STREAMING, WIDTH, HEIGHT); if (!texture) { printf("can't create texture: %s\n", SDL_GetError()); return -ENOMEM; } if (SDL_LockTexture(texture, NULL, &ptr, &stride) < 0) { fprintf(stderr, "Couldn't lock texture: %s\n", SDL_GetError()); return -EIO; } b->texture = texture; b->datas[0].type = SPA_DATA_DmaBuf; b->datas[0].maxsize = stride * HEIGHT; b->datas[0].data = ptr; b->datas[0].chunk->offset = 0; b->datas[0].chunk->size = stride * HEIGHT; b->datas[0].chunk->stride = stride; } return 0; } static int negotiate_formats(struct data *data) { int res; struct spa_pod *format; uint8_t buffer[256]; struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); data->source_output[0] = SPA_IO_BUFFERS_INIT; if ((res = spa_node_port_set_io(data->source, SPA_DIRECTION_OUTPUT, 0, SPA_IO_Buffers, &data->source_output[0], sizeof(data->source_output[0]))) < 0) return res; format = spa_format_video_raw_build(&b, 0, &SPA_VIDEO_INFO_RAW_INIT( .format = SPA_VIDEO_FORMAT_YUY2, .size = SPA_RECTANGLE(WIDTH, HEIGHT), .framerate = SPA_FRACTION(25,1))); if ((res = spa_node_port_set_param(data->source, SPA_DIRECTION_OUTPUT, 0, SPA_PARAM_Format, 0, format)) < 0) return res; setup_buffers(data); if (data->use_buffer) { if ((res = sdl_alloc_buffers(data)) < 0) return res; if ((res = spa_node_port_use_buffers(data->source, SPA_DIRECTION_OUTPUT, 0, 0, data->bp, data->n_buffers)) < 0) { printf("can't allocate buffers: %s\n", spa_strerror(res)); return -1; } } else { unsigned int n_buffers; data->texture = SDL_CreateTexture(data->renderer, SDL_PIXELFORMAT_YUY2, SDL_TEXTUREACCESS_STREAMING, WIDTH, HEIGHT); if (!data->texture) { printf("can't create texture: %s\n", SDL_GetError()); return -1; } n_buffers = MAX_BUFFERS; if ((res = spa_node_port_use_buffers(data->source, SPA_DIRECTION_OUTPUT, 0, SPA_NODE_BUFFERS_FLAG_ALLOC, data->bp, n_buffers)) < 0) { printf("can't allocate buffers: %s\n", spa_strerror(res)); return -1; } data->n_buffers = n_buffers; } return 0; } static void loop(struct data *data) { int res; struct spa_command cmd; SDL_Event event; printf("starting...\n\n"); cmd = SPA_NODE_COMMAND_INIT(SPA_NODE_COMMAND_Start); if ((res = spa_node_send_command(data->source, &cmd)) < 0) printf("got error %d\n", res); data->running = true; while (data->running) { // must be called from the thread that created the renderer while (SDL_PollEvent(&event)) { switch (event.type) { case SDL_QUIT: data->running = false; break; } } // small timeout to make sure we don't starve the SDL loop spa_loop_control_iterate(data->control, LOOP_TIMEOUT_MS); } printf("pausing...\n\n"); cmd = SPA_NODE_COMMAND_INIT(SPA_NODE_COMMAND_Pause); if ((res = spa_node_send_command(data->source, &cmd)) < 0) printf("got error %d\n", res); } int main(int argc, char *argv[]) { struct data data = { 0 }; int res; const char *str; struct spa_handle *handle = NULL; void *iface; if (argc < 2) { printf("usage: %s \n", argv[0]); return EXIT_FAILURE; } if ((str = getenv("SPA_PLUGIN_DIR")) == NULL) str = PLUGINDIR; data.plugin_dir = str; if ((res = load_handle(&data, &handle, "support/libspa-support.so", SPA_NAME_SUPPORT_SYSTEM, NULL)) < 0) return res; if ((res = spa_handle_get_interface(handle, SPA_TYPE_INTERFACE_System, &iface)) < 0) { printf("can't get System interface %d\n", res); return res; } data.system = iface; data.support[data.n_support++] = SPA_SUPPORT_INIT(SPA_TYPE_INTERFACE_System, data.system); if ((res = load_handle(&data, &handle, "support/libspa-support.so", SPA_NAME_SUPPORT_LOOP, NULL)) < 0) return res; if ((res = spa_handle_get_interface(handle, SPA_TYPE_INTERFACE_Loop, &iface)) < 0) { printf("can't get interface %d\n", res); return res; } data.loop = iface; if ((res = spa_handle_get_interface(handle, SPA_TYPE_INTERFACE_LoopControl, &iface)) < 0) { printf("can't get interface %d\n", res); return res; } data.control = iface; data.use_buffer = USE_BUFFER; data.log = &default_log.log; if ((str = getenv("SPA_DEBUG"))) data.log->level = atoi(str); data.support[data.n_support++] = SPA_SUPPORT_INIT(SPA_TYPE_INTERFACE_Log, data.log); data.support[data.n_support++] = SPA_SUPPORT_INIT(SPA_TYPE_INTERFACE_Loop, data.loop); data.support[data.n_support++] = SPA_SUPPORT_INIT(SPA_TYPE_INTERFACE_DataLoop, data.loop); if (SDL_Init(SDL_INIT_VIDEO) < 0) { printf("can't initialize SDL: %s\n", SDL_GetError()); return -1; } if (SDL_CreateWindowAndRenderer (WIDTH, HEIGHT, SDL_WINDOW_RESIZABLE, &data.window, &data.renderer)) { printf("can't create window: %s\n", SDL_GetError()); return -1; } if ((res = make_nodes(&data, argv[1])) < 0) { printf("can't make nodes: %d\n", res); return -1; } if ((res = negotiate_formats(&data)) < 0) { printf("can't negotiate nodes: %d\n", res); return -1; } spa_loop_control_enter(data.control); loop(&data); spa_loop_control_leave(data.control); SDL_DestroyRenderer(data.renderer); return 0; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/examples/local-v4l2.c000066400000000000000000000301721511204443500251250ustar00rootroot00000000000000/* Spa */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ /* [title] Example using libspa-v4l2, with only \ref api_spa [title] */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static SPA_LOG_IMPL(default_log); #define MAX_BUFFERS 8 #define LOOP_TIMEOUT_MS 100 struct buffer { struct spa_buffer buffer; struct spa_meta metas[1]; struct spa_meta_header header; struct spa_data datas[1]; struct spa_chunk chunks[1]; SDL_Texture *texture; }; struct data { const char *plugin_dir; struct spa_log *log; struct spa_system *system; struct spa_loop *loop; struct spa_loop_control *control; struct spa_support support[5]; uint32_t n_support; struct spa_node *source; struct spa_hook listener; struct spa_io_buffers source_output[1]; SDL_Renderer *renderer; SDL_Window *window; SDL_Texture *texture; bool use_buffer; bool running; pthread_t thread; struct spa_buffer *bp[MAX_BUFFERS]; struct buffer buffers[MAX_BUFFERS]; unsigned int n_buffers; }; static int load_handle(struct data *data, struct spa_handle **handle, const char *lib, const char *name) { int res; void *hnd; spa_handle_factory_enum_func_t enum_func; uint32_t i; char *path = NULL; if ((path = spa_aprintf("%s/%s", data->plugin_dir, lib)) == NULL) { return -ENOMEM; } if ((hnd = dlopen(path, RTLD_NOW)) == NULL) { printf("can't load %s: %s\n", path, dlerror()); free(path); return -ENOENT; } free(path); if ((enum_func = dlsym(hnd, SPA_HANDLE_FACTORY_ENUM_FUNC_NAME)) == NULL) { printf("can't find enum function\n"); return -ENOENT; } for (i = 0;;) { const struct spa_handle_factory *factory; if ((res = enum_func(&factory, &i)) <= 0) { if (res != 0) printf("can't enumerate factories: %s\n", spa_strerror(res)); break; } if (factory->version < 1) continue; if (!spa_streq(factory->name, name)) continue; *handle = calloc(1, spa_handle_factory_get_size(factory, NULL)); if ((res = spa_handle_factory_init(factory, *handle, NULL, data->support, data->n_support)) < 0) { printf("can't make factory instance: %d\n", res); return res; } return 0; } return -EBADF; } static int make_node(struct data *data, struct spa_node **node, const char *lib, const char *name) { struct spa_handle *handle = NULL; void *iface; int res; if ((res = load_handle(data, &handle, lib, name)) < 0) return res; if ((res = spa_handle_get_interface(handle, SPA_TYPE_INTERFACE_Node, &iface)) < 0) { printf("can't get interface %d\n", res); return res; } *node = iface; return 0; } static int on_source_ready(void *_data, int status) { struct data *data = _data; int res; struct buffer *b; void *sdata, *ddata; int sstride, dstride; int i; uint8_t *src, *dst; struct spa_data *datas; struct spa_io_buffers *io = &data->source_output[0]; if (io->status != SPA_STATUS_HAVE_DATA || io->buffer_id >= MAX_BUFFERS) return -EINVAL; b = &data->buffers[io->buffer_id]; io->status = SPA_STATUS_NEED_DATA; datas = b->buffer.datas; if (b->texture) { SDL_Texture *texture = b->texture; SDL_UnlockTexture(texture); SDL_RenderClear(data->renderer); SDL_RenderCopy(data->renderer, texture, NULL, NULL); SDL_RenderPresent(data->renderer); if (SDL_LockTexture(texture, NULL, &sdata, &sstride) < 0) { fprintf(stderr, "Couldn't lock texture: %s\n", SDL_GetError()); return -EIO; } } else { uint8_t *map; if (SDL_LockTexture(data->texture, NULL, &ddata, &dstride) < 0) { fprintf(stderr, "Couldn't lock texture: %s\n", SDL_GetError()); return -EIO; } sdata = datas[0].data; if (datas[0].type == SPA_DATA_MemFd || datas[0].type == SPA_DATA_DmaBuf) { map = mmap(NULL, datas[0].maxsize, PROT_READ, MAP_PRIVATE, datas[0].fd, datas[0].mapoffset); if (map == MAP_FAILED) return -errno; sdata = map; } else if (datas[0].type == SPA_DATA_MemPtr) { map = NULL; sdata = datas[0].data; } else return -EIO; sstride = datas[0].chunk->stride; for (i = 0; i < 240; i++) { src = ((uint8_t *) sdata + i * sstride); dst = ((uint8_t *) ddata + i * dstride); memcpy(dst, src, SPA_MIN(sstride, dstride)); } SDL_UnlockTexture(data->texture); SDL_RenderClear(data->renderer); SDL_RenderCopy(data->renderer, data->texture, NULL, NULL); SDL_RenderPresent(data->renderer); if (map) munmap(map, datas[0].maxsize); } if ((res = spa_node_process(data->source)) < 0) printf("got process error %d\n", res); return 0; } static const struct spa_node_callbacks source_callbacks = { SPA_VERSION_NODE_CALLBACKS, .ready = on_source_ready, }; static int make_nodes(struct data *data, const char *device) { int res; struct spa_pod *props; struct spa_pod_builder b = { 0 }; uint8_t buffer[256]; uint32_t index; if ((res = make_node(data, &data->source, "v4l2/libspa-v4l2.so", SPA_NAME_API_V4L2_SOURCE)) < 0) { printf("can't create v4l2-source: %d\n", res); return res; } spa_node_set_callbacks(data->source, &source_callbacks, data); index = 0; spa_pod_builder_init(&b, buffer, sizeof(buffer)); if ((res = spa_node_enum_params_sync(data->source, SPA_PARAM_Props, &index, NULL, &props, &b)) == 1) { spa_debug_pod(0, NULL, props); } spa_pod_builder_init(&b, buffer, sizeof(buffer)); props = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_Props, 0, SPA_PROP_device, SPA_POD_String(device ? device : "/dev/video0")); if ((res = spa_node_set_param(data->source, SPA_PARAM_Props, 0, props)) < 0) printf("got set_props error %d\n", res); return res; } static int setup_buffers(struct data *data) { int i; for (i = 0; i < MAX_BUFFERS; i++) { struct buffer *b = &data->buffers[i]; data->bp[i] = &b->buffer; b->texture = NULL; b->buffer.metas = b->metas; b->buffer.n_metas = 1; b->buffer.datas = b->datas; b->buffer.n_datas = 1; b->header.flags = 0; b->header.seq = 0; b->header.pts = 0; b->header.dts_offset = 0; b->metas[0].type = SPA_META_Header; b->metas[0].data = &b->header; b->metas[0].size = sizeof(b->header); b->datas[0].type = 0; b->datas[0].flags = 0; b->datas[0].fd = -1; b->datas[0].mapoffset = 0; b->datas[0].maxsize = 0; b->datas[0].data = NULL; b->datas[0].chunk = &b->chunks[0]; b->datas[0].chunk->offset = 0; b->datas[0].chunk->size = 0; b->datas[0].chunk->stride = 0; } data->n_buffers = MAX_BUFFERS; return 0; } static int sdl_alloc_buffers(struct data *data) { int i; for (i = 0; i < MAX_BUFFERS; i++) { struct buffer *b = &data->buffers[i]; SDL_Texture *texture; void *ptr; int stride; texture = SDL_CreateTexture(data->renderer, SDL_PIXELFORMAT_YUY2, SDL_TEXTUREACCESS_STREAMING, 320, 240); if (!texture) { printf("can't create texture: %s\n", SDL_GetError()); return -ENOMEM; } if (SDL_LockTexture(texture, NULL, &ptr, &stride) < 0) { fprintf(stderr, "Couldn't lock texture: %s\n", SDL_GetError()); return -EIO; } b->texture = texture; b->datas[0].type = SPA_DATA_MemPtr; b->datas[0].maxsize = stride * 240; b->datas[0].data = ptr; b->datas[0].chunk->offset = 0; b->datas[0].chunk->size = stride * 240; b->datas[0].chunk->stride = stride; } return 0; } static int negotiate_formats(struct data *data) { int res; struct spa_pod *format; uint8_t buffer[256]; struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); data->source_output[0] = SPA_IO_BUFFERS_INIT; if ((res = spa_node_port_set_io(data->source, SPA_DIRECTION_OUTPUT, 0, SPA_IO_Buffers, &data->source_output[0], sizeof(data->source_output[0]))) < 0) return res; format = spa_format_video_raw_build(&b, 0, &SPA_VIDEO_INFO_RAW_INIT( .format = SPA_VIDEO_FORMAT_YUY2, .size = SPA_RECTANGLE(320, 240), .framerate = SPA_FRACTION(25,1))); if ((res = spa_node_port_set_param(data->source, SPA_DIRECTION_OUTPUT, 0, SPA_PARAM_Format, 0, format)) < 0) return res; setup_buffers(data); if (data->use_buffer) { if ((res = sdl_alloc_buffers(data)) < 0) return res; if ((res = spa_node_port_use_buffers(data->source, SPA_DIRECTION_OUTPUT, 0, 0, data->bp, data->n_buffers)) < 0) { printf("can't allocate buffers: %s\n", spa_strerror(res)); return -1; } } else { unsigned int n_buffers; data->texture = SDL_CreateTexture(data->renderer, SDL_PIXELFORMAT_YUY2, SDL_TEXTUREACCESS_STREAMING, 320, 240); if (!data->texture) { printf("can't create texture: %s\n", SDL_GetError()); return -1; } n_buffers = MAX_BUFFERS; if ((res = spa_node_port_use_buffers(data->source, SPA_DIRECTION_OUTPUT, 0, SPA_NODE_BUFFERS_FLAG_ALLOC, data->bp, n_buffers)) < 0) { printf("can't allocate buffers: %s\n", spa_strerror(res)); return -1; } data->n_buffers = n_buffers; } return 0; } static void loop(struct data *data) { int res; struct spa_command cmd; SDL_Event event; printf("starting...\n\n"); cmd = SPA_NODE_COMMAND_INIT(SPA_NODE_COMMAND_Start); if ((res = spa_node_send_command(data->source, &cmd)) < 0) printf("got error %d\n", res); data->running = true; while (data->running) { // must be called from the thread that created the renderer while (SDL_PollEvent(&event)) { switch (event.type) { case SDL_QUIT: data->running = false; break; } } // small timeout to make sure we don't starve the SDL loop spa_loop_control_iterate(data->control, LOOP_TIMEOUT_MS); } printf("pausing...\n\n"); cmd = SPA_NODE_COMMAND_INIT(SPA_NODE_COMMAND_Pause); if ((res = spa_node_send_command(data->source, &cmd)) < 0) printf("got error %d\n", res); } int main(int argc, char *argv[]) { struct data data = { 0 }; int res; const char *str; struct spa_handle *handle = NULL; void *iface; if ((str = getenv("SPA_PLUGIN_DIR")) == NULL) str = PLUGINDIR; data.plugin_dir = str; if ((res = load_handle(&data, &handle, "support/libspa-support.so", SPA_NAME_SUPPORT_SYSTEM)) < 0) return res; if ((res = spa_handle_get_interface(handle, SPA_TYPE_INTERFACE_System, &iface)) < 0) { printf("can't get System interface %d\n", res); return res; } data.system = iface; data.support[data.n_support++] = SPA_SUPPORT_INIT(SPA_TYPE_INTERFACE_System, data.system); if ((res = load_handle(&data, &handle, "support/libspa-support.so", SPA_NAME_SUPPORT_LOOP)) < 0) return res; if ((res = spa_handle_get_interface(handle, SPA_TYPE_INTERFACE_Loop, &iface)) < 0) { printf("can't get interface %d\n", res); return res; } data.loop = iface; if ((res = spa_handle_get_interface(handle, SPA_TYPE_INTERFACE_LoopControl, &iface)) < 0) { printf("can't get interface %d\n", res); return res; } data.control = iface; data.use_buffer = true; data.log = &default_log.log; if ((str = getenv("SPA_DEBUG"))) data.log->level = atoi(str); data.support[data.n_support++] = SPA_SUPPORT_INIT(SPA_TYPE_INTERFACE_Log, data.log); data.support[data.n_support++] = SPA_SUPPORT_INIT(SPA_TYPE_INTERFACE_Loop, data.loop); data.support[data.n_support++] = SPA_SUPPORT_INIT(SPA_TYPE_INTERFACE_DataLoop, data.loop); if (SDL_Init(SDL_INIT_VIDEO) < 0) { printf("can't initialize SDL: %s\n", SDL_GetError()); return -1; } if (SDL_CreateWindowAndRenderer (320, 240, SDL_WINDOW_RESIZABLE, &data.window, &data.renderer)) { printf("can't create window: %s\n", SDL_GetError()); return -1; } if ((res = make_nodes(&data, argv[1])) < 0) { printf("can't make nodes: %d\n", res); return -1; } if ((res = negotiate_formats(&data)) < 0) { printf("can't negotiate nodes: %d\n", res); return -1; } spa_loop_control_enter(data.control); loop(&data); spa_loop_control_leave(data.control); SDL_DestroyRenderer(data.renderer); return 0; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/examples/local-videotestsrc.c000066400000000000000000000317301511204443500270550ustar00rootroot00000000000000/* Spa */ /* SPDX-FileCopyrightText: Copyright © 2020 Collabora Ltd. */ /* @author Raghavendra Rao Sidlagatta */ /* SPDX-License-Identifier: MIT */ /* [title] Example using libspa-videotestsrc, with only \ref api_spa [title] */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define WIDTH 640 #define HEIGHT 480 static SPA_LOG_IMPL(default_log); #define MAX_BUFFERS 8 #define LOOP_TIMEOUT_MS 100 #define USE_BUFFER true struct buffer { struct spa_buffer buffer; struct spa_meta metas[1]; struct spa_meta_header header; struct spa_data datas[1]; struct spa_chunk chunks[1]; SDL_Texture *texture; }; struct data { const char *plugin_dir; struct spa_log *log; struct spa_system *system; struct spa_loop *loop; struct spa_loop_control *control; struct spa_loop_utils *loop_utils; struct spa_support support[6]; uint32_t n_support; struct spa_node *source; struct spa_hook listener; struct spa_io_buffers source_output[1]; SDL_Renderer *renderer; SDL_Window *window; SDL_Texture *texture; bool use_buffer; bool running; pthread_t thread; struct spa_buffer *bp[MAX_BUFFERS]; struct buffer buffers[MAX_BUFFERS]; unsigned int n_buffers; }; static int load_handle(struct data *data, struct spa_handle **handle, const char *lib, const char *name) { int res; void *hnd; spa_handle_factory_enum_func_t enum_func; uint32_t i; char *path = NULL; if ((path = spa_aprintf("%s/%s", data->plugin_dir, lib)) == NULL) { return -ENOMEM; } if ((hnd = dlopen(path, RTLD_NOW)) == NULL) { printf("can't load %s: %s\n", path, dlerror()); free(path); return -errno; } free(path); if ((enum_func = dlsym(hnd, SPA_HANDLE_FACTORY_ENUM_FUNC_NAME)) == NULL) { printf("can't find enum function\n"); return -errno; } for (i = 0;;) { const struct spa_handle_factory *factory; if ((res = enum_func(&factory, &i)) <= 0) { if (res != 0) printf("can't enumerate factories: %s\n", spa_strerror(res)); break; } if (!spa_streq(factory->name, name)) continue; *handle = calloc(1, spa_handle_factory_get_size(factory, NULL)); if ((res = spa_handle_factory_init(factory, *handle, NULL, data->support, data->n_support)) < 0) { printf("can't make factory instance: %d\n", res); return res; } return 0; } return -EBADF; } static int make_node(struct data *data, struct spa_node **node, const char *lib, const char *name) { struct spa_handle *handle = NULL; void *iface; int res; if ((res = load_handle(data, &handle, lib, name)) < 0) return res; if ((res = spa_handle_get_interface(handle, SPA_TYPE_INTERFACE_Node, &iface)) < 0) { printf("can't get interface %d\n", res); return res; } *node = iface; return 0; } static int on_source_ready(void *_data, int status) { struct data *data = _data; int res; struct buffer *b; void *sdata, *ddata; int sstride, dstride; int i; uint8_t *src, *dst; struct spa_data *datas; struct spa_io_buffers *io = &data->source_output[0]; if (io->status != SPA_STATUS_HAVE_DATA || io->buffer_id >= MAX_BUFFERS) return -EINVAL; b = &data->buffers[io->buffer_id]; io->status = SPA_STATUS_NEED_DATA; datas = b->buffer.datas; if (b->texture) { SDL_Texture *texture = b->texture; SDL_UnlockTexture(texture); if (SDL_RenderClear(data->renderer) < 0) { fprintf(stderr, "Couldn't render clear: %s\n", SDL_GetError()); return -EIO; } if (SDL_RenderCopy(data->renderer, texture, NULL, NULL) < 0) { fprintf(stderr, "Couldn't render copy: %s\n", SDL_GetError()); return -EIO; } SDL_RenderPresent(data->renderer); if (SDL_LockTexture(texture, NULL, &sdata, &sstride) < 0) { fprintf(stderr, "Couldn't lock texture: %s\n", SDL_GetError()); return -EIO; } datas[0].data = sdata; } else { uint8_t *map; if (SDL_LockTexture(data->texture, NULL, &ddata, &dstride) < 0) { fprintf(stderr, "Couldn't lock texture: %s\n", SDL_GetError()); return -EIO; } sdata = datas[0].data; if (datas[0].type == SPA_DATA_MemFd || datas[0].type == SPA_DATA_DmaBuf) { map = mmap(NULL, datas[0].maxsize, PROT_READ, MAP_PRIVATE, datas[0].fd, datas[0].mapoffset); if (map == MAP_FAILED) return -errno; sdata = map; } else if (datas[0].type == SPA_DATA_MemPtr) { map = NULL; sdata = datas[0].data; } else return -EIO; sstride = datas[0].chunk->stride; for (i = 0; i < HEIGHT; i++) { src = ((uint8_t *) sdata + i * sstride); dst = ((uint8_t *) ddata + i * dstride); memcpy(dst, src, SPA_MIN(sstride, dstride)); } SDL_UnlockTexture(data->texture); SDL_RenderClear(data->renderer); SDL_RenderCopy(data->renderer, data->texture, NULL, NULL); SDL_RenderPresent(data->renderer); if (map) munmap(map, datas[0].maxsize); } if ((res = spa_node_process(data->source)) < 0) printf("got process error %d\n", res); return 0; } static const struct spa_node_callbacks source_callbacks = { SPA_VERSION_NODE_CALLBACKS, .ready = on_source_ready, }; static int make_nodes(struct data *data, uint32_t pattern) { int res; struct spa_pod *props; struct spa_pod_builder b = { 0 }; uint8_t buffer[256]; uint32_t index; if ((res = make_node(data, &data->source, "videotestsrc/libspa-videotestsrc.so", "videotestsrc")) < 0) { printf("can't create videotestsrc: %d\n", res); return res; } spa_node_set_callbacks(data->source, &source_callbacks, data); index = 0; spa_pod_builder_init(&b, buffer, sizeof(buffer)); if ((res = spa_node_enum_params_sync(data->source, SPA_PARAM_Props, &index, NULL, &props, &b)) == 1) { spa_debug_pod(0, NULL, props); } spa_pod_builder_init(&b, buffer, sizeof(buffer)); props = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_Props, 0, SPA_PROP_patternType, SPA_POD_Int(pattern)); if ((res = spa_node_set_param(data->source, SPA_PARAM_Props, 0, props)) < 0) printf("got set_props error %d\n", res); return res; } static int setup_buffers(struct data *data) { int i; for (i = 0; i < MAX_BUFFERS; i++) { struct buffer *b = &data->buffers[i]; data->bp[i] = &b->buffer; b->texture = NULL; b->buffer.metas = b->metas; b->buffer.n_metas = 1; b->buffer.datas = b->datas; b->buffer.n_datas = 1; b->header.flags = 0; b->header.seq = 0; b->header.pts = 0; b->header.dts_offset = 0; b->metas[0].type = SPA_META_Header; b->metas[0].data = &b->header; b->metas[0].size = sizeof(b->header); b->datas[0].type = SPA_DATA_DmaBuf; b->datas[0].flags = 0; b->datas[0].fd = -1; b->datas[0].mapoffset = 0; b->datas[0].maxsize = 0; b->datas[0].data = NULL; b->datas[0].chunk = &b->chunks[0]; b->datas[0].chunk->offset = 0; b->datas[0].chunk->size = 0; b->datas[0].chunk->stride = 0; } data->n_buffers = MAX_BUFFERS; return 0; } static int sdl_alloc_buffers(struct data *data) { int i; for (i = 0; i < MAX_BUFFERS; i++) { struct buffer *b = &data->buffers[i]; SDL_Texture *texture; void *ptr; int stride; texture = SDL_CreateTexture(data->renderer, SDL_PIXELFORMAT_RGB24, SDL_TEXTUREACCESS_STREAMING, WIDTH, HEIGHT); if (!texture) { printf("can't create texture: %s\n", SDL_GetError()); return -ENOMEM; } if (SDL_LockTexture(texture, NULL, &ptr, &stride) < 0) { fprintf(stderr, "Couldn't lock texture: %s\n", SDL_GetError()); return -EIO; } b->texture = texture; b->datas[0].type = SPA_DATA_DmaBuf; b->datas[0].maxsize = stride * HEIGHT; b->datas[0].data = ptr; b->datas[0].chunk->offset = 0; b->datas[0].chunk->size = stride * HEIGHT; b->datas[0].chunk->stride = stride; } return 0; } static int negotiate_formats(struct data *data) { int res; struct spa_pod *format; uint8_t buffer[256]; struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); data->source_output[0] = SPA_IO_BUFFERS_INIT; if ((res = spa_node_port_set_io(data->source, SPA_DIRECTION_OUTPUT, 0, SPA_IO_Buffers, &data->source_output[0], sizeof(data->source_output[0]))) < 0) return res; format = spa_format_video_raw_build(&b, 0, &SPA_VIDEO_INFO_RAW_INIT( .format = SPA_VIDEO_FORMAT_RGB, .size = SPA_RECTANGLE(WIDTH, HEIGHT), .framerate = SPA_FRACTION(25,1))); if ((res = spa_node_port_set_param(data->source, SPA_DIRECTION_OUTPUT, 0, SPA_PARAM_Format, 0, format)) < 0) return res; setup_buffers(data); if (data->use_buffer) { if ((res = sdl_alloc_buffers(data)) < 0) return res; if ((res = spa_node_port_use_buffers(data->source, SPA_DIRECTION_OUTPUT, 0, 0, data->bp, data->n_buffers)) < 0) { printf("can't allocate buffers: %s\n", spa_strerror(res)); return -1; } } else { unsigned int n_buffers; data->texture = SDL_CreateTexture(data->renderer, SDL_PIXELFORMAT_RGB24, SDL_TEXTUREACCESS_STREAMING, WIDTH, HEIGHT); if (!data->texture) { printf("can't create texture: %s\n", SDL_GetError()); return -1; } n_buffers = MAX_BUFFERS; if ((res = spa_node_port_use_buffers(data->source, SPA_DIRECTION_OUTPUT, 0, SPA_NODE_BUFFERS_FLAG_ALLOC, data->bp, n_buffers)) < 0) { printf("can't allocate buffers: %s\n", spa_strerror(res)); return -1; } data->n_buffers = n_buffers; } return 0; } static void loop(struct data *data) { int res; struct spa_command cmd; SDL_Event event; printf("starting...\n\n"); cmd = SPA_NODE_COMMAND_INIT(SPA_NODE_COMMAND_Start); if ((res = spa_node_send_command(data->source, &cmd)) < 0) printf("got error %d\n", res); data->running = true; while (data->running) { // must be called from the thread that created the renderer while (SDL_PollEvent(&event)) { switch (event.type) { case SDL_QUIT: data->running = false; break; } } // small timeout to make sure we don't starve the SDL loop spa_loop_control_iterate(data->control, LOOP_TIMEOUT_MS); } printf("pausing...\n\n"); cmd = SPA_NODE_COMMAND_INIT(SPA_NODE_COMMAND_Pause); if ((res = spa_node_send_command(data->source, &cmd)) < 0) printf("got error %d\n", res); } int main(int argc, char *argv[]) { struct data data = { 0 }; int res; const char *str; struct spa_handle *handle = NULL; void *iface; uint32_t pattern = 0; if ((str = getenv("SPA_PLUGIN_DIR")) == NULL) str = PLUGINDIR; data.plugin_dir = str; if ((res = load_handle(&data, &handle, "support/libspa-support.so", SPA_NAME_SUPPORT_SYSTEM)) < 0) return res; if ((res = spa_handle_get_interface(handle, SPA_TYPE_INTERFACE_System, &iface)) < 0) { printf("can't get System interface %d\n", res); return res; } data.system = iface; data.support[data.n_support++] = SPA_SUPPORT_INIT(SPA_TYPE_INTERFACE_System, data.system); data.support[data.n_support++] = SPA_SUPPORT_INIT(SPA_TYPE_INTERFACE_DataSystem, data.system); if ((res = load_handle(&data, &handle, "support/libspa-support.so", SPA_NAME_SUPPORT_LOOP)) < 0) return res; if ((res = spa_handle_get_interface(handle, SPA_TYPE_INTERFACE_Loop, &iface)) < 0) { printf("can't get interface %d\n", res); return res; } data.loop = iface; if ((res = spa_handle_get_interface(handle, SPA_TYPE_INTERFACE_LoopControl, &iface)) < 0) { printf("can't get interface %d\n", res); return res; } data.control = iface; if ((res = spa_handle_get_interface(handle, SPA_TYPE_INTERFACE_LoopUtils, &iface)) < 0) { printf("can't get interface %d\n", res); return res; } data.loop_utils = iface; data.use_buffer = USE_BUFFER; data.log = &default_log.log; if ((str = getenv("SPA_DEBUG"))) data.log->level = atoi(str); data.support[data.n_support++] = SPA_SUPPORT_INIT(SPA_TYPE_INTERFACE_Log, data.log); data.support[data.n_support++] = SPA_SUPPORT_INIT(SPA_TYPE_INTERFACE_Loop, data.loop); data.support[data.n_support++] = SPA_SUPPORT_INIT(SPA_TYPE_INTERFACE_DataLoop, data.loop); data.support[data.n_support++] = SPA_SUPPORT_INIT(SPA_TYPE_INTERFACE_LoopUtils, data.loop_utils); if (SDL_Init(SDL_INIT_VIDEO) < 0) { printf("can't initialize SDL: %s\n", SDL_GetError()); return -1; } if (SDL_CreateWindowAndRenderer (WIDTH, HEIGHT, SDL_WINDOW_RESIZABLE, &data.window, &data.renderer)) { printf("can't create window: %s\n", SDL_GetError()); return -1; } if (argv[1] != NULL) pattern = atoi(argv[1]); if ((res = make_nodes(&data, pattern)) < 0) { printf("can't make nodes: %d\n", res); return -1; } if ((res = negotiate_formats(&data)) < 0) { printf("can't negotiate nodes: %d\n", res); return -1; } spa_loop_control_enter(data.control); loop(&data); spa_loop_control_leave(data.control); SDL_DestroyRenderer(data.renderer); return 0; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/examples/meson.build000066400000000000000000000015401511204443500252410ustar00rootroot00000000000000# Examples, in order from simple to complicated spa_examples = [ 'adapter-control', 'example-control', 'local-libcamera', 'local-v4l2', 'local-videotestsrc', ] if not get_option('examples').allowed() or not get_option('spa-plugins').allowed() subdir_done() endif spa_examples_extra_deps = { 'local-v4l2': [sdl_dep], 'local-videotestsrc': [sdl_dep], 'local-libcamera': [sdl_dep, libcamera_dep], } foreach c : spa_examples deps = spa_examples_extra_deps.get(c, []) found = true foreach dep : deps found = found and dep.found() endforeach if found executable( c, c + '.c', include_directories : [configinc], dependencies : [spa_dep, dl_lib, pthread_lib, mathlib] + deps, install : installed_tests_enabled, install_dir : installed_tests_execdir / 'examples' / 'spa' ) endif endforeach pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include-private/000077500000000000000000000000001511204443500243545ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include-private/spa-private/000077500000000000000000000000001511204443500266075ustar00rootroot00000000000000dbus-helpers.h000066400000000000000000000034411511204443500313000ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include-private/spa-private/* Spa DBus helpers */ /* SPDX-FileCopyrightText: Copyright © 2023 PipeWire authors */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_PRIVATE_DBUS_HELPERS_H #define SPA_PRIVATE_DBUS_HELPERS_H #include #include #include static inline void cancel_and_unref(DBusPendingCall **pp) { DBusPendingCall *pending_call = spa_steal_ptr(*pp); if (pending_call) { dbus_pending_call_cancel(pending_call); dbus_pending_call_unref(pending_call); } } static inline DBusMessage *steal_reply_and_unref(DBusPendingCall **pp) { DBusPendingCall *pending_call = spa_steal_ptr(*pp); DBusMessage *reply = dbus_pending_call_steal_reply(pending_call); dbus_pending_call_unref(pending_call); return reply; } SPA_DEFINE_AUTOPTR_CLEANUP(DBusMessage, DBusMessage, { spa_clear_ptr(*thing, dbus_message_unref); }) static inline bool reply_with_error(DBusConnection *conn, DBusMessage *reply_to, const char *error_name, const char *error_message) { spa_autoptr(DBusMessage) reply = dbus_message_new_error(reply_to, error_name, error_message); return reply && dbus_connection_send(conn, reply, NULL); } static inline DBusPendingCall *send_with_reply(DBusConnection *conn, DBusMessage *m, DBusPendingCallNotifyFunction callback, void *user_data) { DBusPendingCall *pending_call; if (!dbus_connection_send_with_reply(conn, m, &pending_call, DBUS_TIMEOUT_USE_DEFAULT)) return NULL; if (!pending_call) return NULL; if (!dbus_pending_call_set_notify(pending_call, callback, user_data, NULL)) { dbus_pending_call_cancel(pending_call); dbus_pending_call_unref(pending_call); return NULL; } return pending_call; } SPA_DEFINE_AUTO_CLEANUP(DBusError, DBusError, { dbus_error_free(thing); }) #endif /* SPA_PRIVATE_DBUS_HELPERS_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/000077500000000000000000000000001511204443500227045ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/meson.build000066400000000000000000000004121511204443500250430ustar00rootroot00000000000000spa_sections = [ 'buffer', 'control', 'debug', 'graph', 'interfaces', 'monitor', 'node', 'param', 'pod', 'support', 'utils', ] spa_headers = 'spa' # used by doxygen install_subdir('spa', install_dir : get_option('includedir') / spa_name, ) pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/000077500000000000000000000000001511204443500234675ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/buffer/000077500000000000000000000000001511204443500247405ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/buffer/alloc.h000066400000000000000000000256271511204443500262170ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_BUFFER_ALLOC_H #define SPA_BUFFER_ALLOC_H #include #ifdef __cplusplus extern "C" { #endif #ifndef SPA_API_BUFFER_ALLOC #ifdef SPA_API_IMPL #define SPA_API_BUFFER_ALLOC SPA_API_IMPL #else #define SPA_API_BUFFER_ALLOC static inline #endif #endif /** * \addtogroup spa_buffer * \{ */ /** information about the buffer layout */ struct spa_buffer_alloc_info { #define SPA_BUFFER_ALLOC_FLAG_INLINE_META (1<<0) /**< add metadata data in the skeleton */ #define SPA_BUFFER_ALLOC_FLAG_INLINE_CHUNK (1<<1) /**< add chunk data in the skeleton */ #define SPA_BUFFER_ALLOC_FLAG_INLINE_DATA (1<<2) /**< add buffer data to the skeleton */ #define SPA_BUFFER_ALLOC_FLAG_INLINE_ALL 0b111 #define SPA_BUFFER_ALLOC_FLAG_NO_DATA (1<<3) /**< don't set data pointers */ uint32_t flags; uint32_t max_align; /**< max of all alignments */ uint32_t n_metas; uint32_t n_datas; struct spa_meta *metas; struct spa_data *datas; uint32_t *data_aligns; size_t skel_size; /**< size of the struct spa_buffer and inlined meta/chunk/data */ size_t meta_size; /**< size of the meta if not inlined */ size_t chunk_size; /**< size of the chunk if not inlined */ size_t data_size; /**< size of the data if not inlined */ size_t mem_size; /**< size of the total memory if not inlined */ }; /** * Fill buffer allocation information * * Fill \a info with allocation information needed to allocate buffers * with the given number of metadata and data members. * * The required size of the skeleton (the struct spa_buffer) information * and the memory (for the metadata, chunk and buffer memory) will be * calculated. * * The flags member in \a info should be configured before calling this * functions. * * \param info the information to fill * \param n_metas the number of metadatas for the buffer * \param metas an array of metadata items * \param n_datas the number of datas for the buffer * \param datas an array of \a n_datas items * \param data_aligns \a n_datas alignments * \return 0 on success. * */ SPA_API_BUFFER_ALLOC int spa_buffer_alloc_fill_info(struct spa_buffer_alloc_info *info, uint32_t n_metas, struct spa_meta metas[], uint32_t n_datas, struct spa_data datas[], uint32_t data_aligns[]) { size_t size, *target; uint32_t i; info->n_metas = n_metas; info->metas = metas; info->n_datas = n_datas; info->datas = datas; info->data_aligns = data_aligns; info->max_align = 16; info->mem_size = 0; /* * The buffer skeleton is placed in memory like below and can * be accessed as a regular structure. * * +==============================+ * | struct spa_buffer | * | uint32_t n_metas | number of metas * | uint32_t n_datas | number of datas * +-| struct spa_meta *metas | pointer to array of metas * +|-| struct spa_data *datas | pointer to array of datas * || +------------------------------+ * |+>| struct spa_meta | * | | uint32_t type | metadata * | | uint32_t size | size of metadata * +|--| void *data | pointer to metadata * || | ... | more spa_meta follow * || +------------------------------+ * |+->| struct spa_data | * | | uint32_t type | memory type * | | uint32_t flags | * | | int fd | fd of shared memory block * | | uint32_t mapoffset | offset in shared memory of data * | | uint32_t maxsize | size of data block * | +-| void *data | pointer to data * |+|-| struct spa_chunk *chunk | pointer to chunk * ||| | ... | more spa_data follow * ||| +==============================+ * VVV * * metadata, chunk and memory can either be placed right * after the skeleton (inlined) or in a separate piece of memory. * * vvv * ||| +==============================+ * +-->| meta data memory | metadata memory, 8 byte aligned * || | ... | * || +------------------------------+ * +->| struct spa_chunk | memory for n_datas chunks * | | uint32_t offset | * | | uint32_t size | * | | int32_t stride | * | | int32_t flags | * | | ... chunks | * | +------------------------------+ * +>| data | memory for n_datas data, aligned * | ... blocks | according to alignments * +==============================+ */ info->skel_size = sizeof(struct spa_buffer); info->skel_size += n_metas * sizeof(struct spa_meta); info->skel_size += n_datas * sizeof(struct spa_data); for (i = 0, size = 0; i < n_metas; i++) size += SPA_ROUND_UP_N(metas[i].size, 8); info->meta_size = size; if (SPA_FLAG_IS_SET(info->flags, SPA_BUFFER_ALLOC_FLAG_INLINE_META)) target = &info->skel_size; else target = &info->mem_size; *target += info->meta_size; info->chunk_size = n_datas * sizeof(struct spa_chunk); if (SPA_FLAG_IS_SET(info->flags, SPA_BUFFER_ALLOC_FLAG_INLINE_CHUNK)) target = &info->skel_size; else target = &info->mem_size; *target += info->chunk_size; for (i = 0, size = 0; i < n_datas; i++) { int64_t align = data_aligns[i]; info->max_align = SPA_MAX(info->max_align, data_aligns[i]); size = SPA_ROUND_UP_N(size, align); size += datas[i].maxsize; } info->data_size = size; if (!SPA_FLAG_IS_SET(info->flags, SPA_BUFFER_ALLOC_FLAG_NO_DATA) && SPA_FLAG_IS_SET(info->flags, SPA_BUFFER_ALLOC_FLAG_INLINE_DATA)) target = &info->skel_size; else target = &info->mem_size; *target = SPA_ROUND_UP_N(*target, n_datas ? data_aligns[0] : 1); *target += info->data_size; *target = SPA_ROUND_UP_N(*target, info->max_align); return 0; } /** * Fill skeleton and data according to the allocation info * * Use the allocation info to create a struct \ref spa_buffer into * \a skel_mem and \a data_mem. * * Depending on the flags given when calling \ref * spa_buffer_alloc_fill_info(), the buffer meta, chunk and memory * will be referenced in either skel_mem or data_mem. * * \param info an allocation info * \param skel_mem memory to hold the struct \ref spa_buffer and the * pointers to meta, chunk and memory. * \param data_mem memory to hold the meta, chunk and memory * \return a struct \ref spa_buffer in \a skel_mem */ SPA_API_BUFFER_ALLOC struct spa_buffer * spa_buffer_alloc_layout(struct spa_buffer_alloc_info *info, void *skel_mem, void *data_mem) { struct spa_buffer *b = (struct spa_buffer*)skel_mem; size_t size; uint32_t i; void **dp, *skel, *data; struct spa_chunk *cp; b->n_metas = info->n_metas; b->metas = SPA_PTROFF(b, sizeof(struct spa_buffer), struct spa_meta); b->n_datas = info->n_datas; b->datas = SPA_PTROFF(b->metas, info->n_metas * sizeof(struct spa_meta), struct spa_data); skel = SPA_PTROFF(b->datas, info->n_datas * sizeof(struct spa_data), void); data = data_mem; if (SPA_FLAG_IS_SET(info->flags, SPA_BUFFER_ALLOC_FLAG_INLINE_META)) dp = &skel; else dp = &data; for (i = 0; i < info->n_metas; i++) { struct spa_meta *m = &b->metas[i]; *m = info->metas[i]; m->data = *dp; *dp = SPA_PTROFF(*dp, SPA_ROUND_UP_N(m->size, 8), void); } size = info->n_datas * sizeof(struct spa_chunk); if (SPA_FLAG_IS_SET(info->flags, SPA_BUFFER_ALLOC_FLAG_INLINE_CHUNK)) { cp = (struct spa_chunk*)skel; skel = SPA_PTROFF(skel, size, void); } else { cp = (struct spa_chunk*)data; data = SPA_PTROFF(data, size, void); } if (SPA_FLAG_IS_SET(info->flags, SPA_BUFFER_ALLOC_FLAG_INLINE_DATA)) dp = &skel; else dp = &data; for (i = 0; i < info->n_datas; i++) { struct spa_data *d = &b->datas[i]; *d = info->datas[i]; d->chunk = &cp[i]; if (!SPA_FLAG_IS_SET(info->flags, SPA_BUFFER_ALLOC_FLAG_NO_DATA)) { *dp = SPA_PTR_ALIGN(*dp, info->data_aligns[i], void); d->data = *dp; *dp = SPA_PTROFF(*dp, d->maxsize, void); } } return b; } /** * Layout an array of buffers * * Use the allocation info to layout the memory of an array of buffers. * * \a skel_mem should point to at least info->skel_size * \a n_buffers bytes * of memory. * \a data_mem should point to at least info->mem_size * \a n_buffers bytes * of memory. * * \param info the allocation info for one buffer * \param n_buffers the number of buffers to create * \param buffers a array with space to hold \a n_buffers pointers to buffers * \param skel_mem memory for the struct \ref spa_buffer * \param data_mem memory for the meta, chunk, memory of the buffer if not * inlined in the skeleton. * \return 0 on success. * */ SPA_API_BUFFER_ALLOC int spa_buffer_alloc_layout_array(struct spa_buffer_alloc_info *info, uint32_t n_buffers, struct spa_buffer *buffers[], void *skel_mem, void *data_mem) { uint32_t i; for (i = 0; i < n_buffers; i++) { buffers[i] = spa_buffer_alloc_layout(info, skel_mem, data_mem); skel_mem = SPA_PTROFF(skel_mem, info->skel_size, void); data_mem = SPA_PTROFF(data_mem, info->mem_size, void); } return 0; } /** * Allocate an array of buffers * * Allocate \a n_buffers with the given metadata, memory and alignment * information. * * The buffer array, structures, data and metadata will all be allocated * in one block of memory with the proper requested alignment. * * \param n_buffers the number of buffers to create * \param flags extra flags * \param n_metas number of metadatas * \param metas \a n_metas metadata specification * \param n_datas number of datas * \param datas \a n_datas memory specification * \param data_aligns \a n_datas alignment specifications * \returns an array of \a n_buffers pointers to struct \ref spa_buffer * with the given metadata, data and alignment or NULL when * allocation failed. * */ SPA_API_BUFFER_ALLOC struct spa_buffer ** spa_buffer_alloc_array(uint32_t n_buffers, uint32_t flags, uint32_t n_metas, struct spa_meta metas[], uint32_t n_datas, struct spa_data datas[], uint32_t data_aligns[]) { struct spa_buffer **buffers; struct spa_buffer_alloc_info info = { flags | SPA_BUFFER_ALLOC_FLAG_INLINE_ALL, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; void *skel; spa_buffer_alloc_fill_info(&info, n_metas, metas, n_datas, datas, data_aligns); buffers = (struct spa_buffer **)calloc(1, info.max_align + n_buffers * (sizeof(struct spa_buffer *) + info.skel_size)); if (buffers == NULL) return NULL; skel = SPA_PTROFF(buffers, sizeof(struct spa_buffer *) * n_buffers, void); skel = SPA_PTR_ALIGN(skel, info.max_align, void); spa_buffer_alloc_layout_array(&info, n_buffers, buffers, skel, NULL); return buffers; } /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_BUFFER_ALLOC_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/buffer/buffer.h000066400000000000000000000105271511204443500263670ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_BUFFER_H #define SPA_BUFFER_H #include #include #ifdef __cplusplus extern "C" { #endif #ifndef SPA_API_BUFFER #ifdef SPA_API_IMPL #define SPA_API_BUFFER SPA_API_IMPL #else #define SPA_API_BUFFER static inline #endif #endif /** \defgroup spa_buffer Buffers * * Buffers describe the data and metadata that is exchanged between * ports of a node. */ /** * \addtogroup spa_buffer * \{ */ enum spa_data_type { SPA_DATA_Invalid, SPA_DATA_MemPtr, /**< pointer to memory, the data field in * struct spa_data is set. */ SPA_DATA_MemFd, /**< memfd, mmap to get to memory. */ SPA_DATA_DmaBuf, /**< fd to dmabuf memory. This might not be readily * mappable (unless the MAPPABLE flag is set) and should * normally be handled with DMABUF apis. */ SPA_DATA_MemId, /**< memory is identified with an id. The actual memory * can be obtained in some other way and can be identified * with this id. */ SPA_DATA_SyncObj, /**< a syncobj, usually requires a spa_meta_sync_timeline metadata * with timeline points. */ _SPA_DATA_LAST, /**< not part of ABI */ }; /** Chunk of memory, can change for each buffer */ struct spa_chunk { uint32_t offset; /**< offset of valid data. Should be taken * modulo the data maxsize to get the offset * in the data memory. */ uint32_t size; /**< size of valid data. Should be clamped to * maxsize. */ int32_t stride; /**< stride of valid data */ #define SPA_CHUNK_FLAG_NONE 0 #define SPA_CHUNK_FLAG_CORRUPTED (1u<<0) /**< chunk data is corrupted in some way */ #define SPA_CHUNK_FLAG_EMPTY (1u<<1) /**< chunk data is empty with media specific * neutral data such as silence or black. This * could be used to optimize processing. */ int32_t flags; /**< chunk flags */ }; /** Data for a buffer this stays constant for a buffer */ struct spa_data { uint32_t type; /**< memory type, one of enum spa_data_type, when * allocating memory, the type contains a bitmask * of allowed types. SPA_ID_INVALID is a special * value for the allocator to indicate that the * other side did not explicitly specify any * supported data types. It should probably use * a memory type that does not require special * handling in addition to simple mmap/munmap. */ #define SPA_DATA_FLAG_NONE 0 #define SPA_DATA_FLAG_READABLE (1u<<0) /**< data is readable */ #define SPA_DATA_FLAG_WRITABLE (1u<<1) /**< data is writable */ #define SPA_DATA_FLAG_DYNAMIC (1u<<2) /**< data pointer can be changed */ #define SPA_DATA_FLAG_READWRITE (SPA_DATA_FLAG_READABLE|SPA_DATA_FLAG_WRITABLE) #define SPA_DATA_FLAG_MAPPABLE (1u<<3) /**< data is mappable with simple mmap/munmap. Some memory * types are not simply mappable (DmaBuf) unless explicitly * specified with this flag. */ uint32_t flags; /**< data flags */ int64_t fd; /**< optional fd for data */ uint32_t mapoffset; /**< offset to map fd at, this is page aligned */ uint32_t maxsize; /**< max size of data */ void *data; /**< optional data pointer */ struct spa_chunk *chunk; /**< valid chunk of memory */ }; /** A Buffer */ struct spa_buffer { uint32_t n_metas; /**< number of metadata */ uint32_t n_datas; /**< number of data members */ struct spa_meta *metas; /**< array of metadata */ struct spa_data *datas; /**< array of data members */ }; /** Find metadata in a buffer */ SPA_API_BUFFER struct spa_meta *spa_buffer_find_meta(const struct spa_buffer *b, uint32_t type) { uint32_t i; for (i = 0; i < b->n_metas; i++) if (b->metas[i].type == type) return &b->metas[i]; return NULL; } SPA_API_BUFFER void *spa_buffer_find_meta_data(const struct spa_buffer *b, uint32_t type, size_t size) { struct spa_meta *m; if ((m = spa_buffer_find_meta(b, type)) && m->size >= size) return m->data; return NULL; } SPA_API_BUFFER bool spa_buffer_has_meta_features(const struct spa_buffer *b, uint32_t type, uint32_t features) { uint32_t i; for (i = 0; i < b->n_metas; i++) { uint32_t t = b->metas[i].type; if ((t >> 16) == type && (t & features) == features) return true; } return false; } /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_BUFFER_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/buffer/meta.h000066400000000000000000000167131511204443500260470ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_META_H #define SPA_META_H #include #include #ifdef __cplusplus extern "C" { #endif #ifndef SPA_API_META #ifdef SPA_API_IMPL #define SPA_API_META SPA_API_IMPL #else #define SPA_API_META static inline #endif #endif /** * \addtogroup spa_buffer * \{ */ enum spa_meta_type { SPA_META_Invalid, SPA_META_Header, /**< struct spa_meta_header */ SPA_META_VideoCrop, /**< struct spa_meta_region with cropping data */ SPA_META_VideoDamage, /**< array of struct spa_meta_region with damage, where an invalid entry or end-of-array marks the end. */ SPA_META_Bitmap, /**< struct spa_meta_bitmap */ SPA_META_Cursor, /**< struct spa_meta_cursor */ SPA_META_Control, /**< metadata contains a spa_meta_control * associated with the data */ SPA_META_Busy, /**< don't write to buffer when count > 0 */ SPA_META_VideoTransform, /**< struct spa_meta_transform */ SPA_META_SyncTimeline, /**< struct spa_meta_sync_timeline */ _SPA_META_LAST, /**< not part of ABI/API */ SPA_META_START_custom = 0x200, SPA_META_START_features = 0x10000, /* features start, these have 0 size, the * type in the upper 16 bits and a bitmask in * the lower 16 bits with type specific features. */ }; #define SPA_META_TYPE_FEATURES(type,features) (((type)<<16)|(features)) /** * A metadata element. * * This structure is available on the buffer structure and contains * the type of the metadata and a pointer/size to the actual metadata * itself. */ struct spa_meta { uint32_t type; /**< metadata type, one of enum spa_meta_type */ uint32_t size; /**< size of metadata */ void *data; /**< pointer to metadata */ }; SPA_API_META void *spa_meta_first(const struct spa_meta *m) { return m->data; } SPA_API_META void *spa_meta_end(const struct spa_meta *m) { return SPA_PTROFF(m->data,m->size,void); } #define spa_meta_check(p,m) (SPA_PTROFF(p,sizeof(*(p)),void) <= spa_meta_end(m)) /** * Describes essential buffer header metadata such as flags and * timestamps. */ struct spa_meta_header { #define SPA_META_HEADER_FLAG_DISCONT (1 << 0) /**< data is not continuous with previous buffer */ #define SPA_META_HEADER_FLAG_CORRUPTED (1 << 1) /**< data might be corrupted */ #define SPA_META_HEADER_FLAG_MARKER (1 << 2) /**< media specific marker */ #define SPA_META_HEADER_FLAG_HEADER (1 << 3) /**< data contains a codec specific header */ #define SPA_META_HEADER_FLAG_GAP (1 << 4) /**< data contains media neutral data */ #define SPA_META_HEADER_FLAG_DELTA_UNIT (1 << 5) /**< cannot be decoded independently */ uint32_t flags; /**< flags */ uint32_t offset; /**< offset in current cycle */ int64_t pts; /**< presentation timestamp in nanoseconds */ int64_t dts_offset; /**< decoding timestamp as a difference with pts */ uint64_t seq; /**< sequence number, increments with a * media specific frequency */ }; /** metadata structure for Region or an array of these for RegionArray */ struct spa_meta_region { struct spa_region region; }; SPA_API_META bool spa_meta_region_is_valid(const struct spa_meta_region *m) { return m->region.size.width != 0 && m->region.size.height != 0; } /** iterate all the items in a metadata */ #define spa_meta_for_each(pos,meta) \ for ((pos) = (__typeof(pos))spa_meta_first(meta); \ spa_meta_check(pos, meta); \ (pos)++) /** * Bitmap information * * This metadata contains a bitmap image in the given format and size. * It is typically used for cursor images or other small images that are * better transferred inline. */ struct spa_meta_bitmap { uint32_t format; /**< bitmap video format, one of enum spa_video_format. 0 is * and invalid format and should be handled as if there is * no new bitmap information. */ struct spa_rectangle size; /**< width and height of bitmap */ int32_t stride; /**< stride of bitmap data */ uint32_t offset; /**< offset of bitmap data in this structure. An offset of * 0 means no image data (invisible), an offset >= * sizeof(struct spa_meta_bitmap) contains valid bitmap * info. */ }; SPA_API_META bool spa_meta_bitmap_is_valid(const struct spa_meta_bitmap *m) { return m->format != 0; } /** * Cursor information * * Metadata to describe the position and appearance of a pointing device. */ struct spa_meta_cursor { uint32_t id; /**< cursor id. an id of 0 is an invalid id and means that * there is no new cursor data */ uint32_t flags; /**< extra flags */ struct spa_point position; /**< position on screen */ struct spa_point hotspot; /**< offsets for hotspot in bitmap, this field has no meaning * when there is no valid bitmap (see below) */ uint32_t bitmap_offset; /**< offset of bitmap meta in this structure. When the offset * is 0, there is no new bitmap information. When the offset is * >= sizeof(struct spa_meta_cursor) there is a * struct spa_meta_bitmap at the offset. */ }; SPA_API_META bool spa_meta_cursor_is_valid(const struct spa_meta_cursor *m) { return m->id != 0; } /** a timed set of events associated with the buffer */ struct spa_meta_control { struct spa_pod_sequence sequence; }; /** a busy counter for the buffer */ struct spa_meta_busy { uint32_t flags; uint32_t count; /**< number of users busy with the buffer */ }; enum spa_meta_videotransform_value { SPA_META_TRANSFORMATION_None = 0, /**< no transform */ SPA_META_TRANSFORMATION_90, /**< 90 degree counter-clockwise */ SPA_META_TRANSFORMATION_180, /**< 180 degree counter-clockwise */ SPA_META_TRANSFORMATION_270, /**< 270 degree counter-clockwise */ SPA_META_TRANSFORMATION_Flipped, /**< 180 degree flipped around the vertical axis. Equivalent * to a reflexion through the vertical line splitting the * buffer in two equal sized parts */ SPA_META_TRANSFORMATION_Flipped90, /**< flip then rotate around 90 degree counter-clockwise */ SPA_META_TRANSFORMATION_Flipped180, /**< flip then rotate around 180 degree counter-clockwise */ SPA_META_TRANSFORMATION_Flipped270, /**< flip then rotate around 270 degree counter-clockwise */ }; /** a transformation of the buffer */ struct spa_meta_videotransform { uint32_t transform; /**< orientation transformation that was applied to the buffer, * one of enum spa_meta_videotransform_value */ }; /** * A timeline point for explicit sync * * Metadata to describe the time on the timeline when the buffer * can be acquired and when it can be reused. * * This metadata will require negotiation of 2 extra fds for the acquire * and release timelines respectively. One way to achieve this is to place * this metadata as SPA_PARAM_BUFFERS_metaType when negotiating a buffer * layout with 2 extra fds. */ #define SPA_META_FEATURE_SYNC_TIMELINE_RELEASE (1<<0) /**< metadata supports RELEASE */ struct spa_meta_sync_timeline { #define SPA_META_SYNC_TIMELINE_UNSCHEDULED_RELEASE (1<<0) /**< this flag is set by the producer and cleared * by the consumer when it promises to signal * the release point */ uint32_t flags; uint32_t padding; uint64_t acquire_point; /**< the timeline acquire point, this is when the data * can be accessed. */ uint64_t release_point; /**< the timeline release point, this timeline point should * be signaled when the data is no longer accessed. */ }; /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_META_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/buffer/type-info.h000066400000000000000000000100001511204443500270120ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_BUFFER_TYPES_H #define SPA_BUFFER_TYPES_H #include #include #include /** * \addtogroup spa_buffer * \{ */ #ifdef __cplusplus extern "C" { #endif #define SPA_TYPE_INFO_Buffer SPA_TYPE_INFO_POINTER_BASE "Buffer" #define SPA_TYPE_INFO_BUFFER_BASE SPA_TYPE_INFO_Buffer ":" /** Buffers contain data of a certain type */ #define SPA_TYPE_INFO_Data SPA_TYPE_INFO_ENUM_BASE "Data" #define SPA_TYPE_INFO_DATA_BASE SPA_TYPE_INFO_Data ":" /** base type for fd based memory */ #define SPA_TYPE_INFO_DATA_Fd SPA_TYPE_INFO_DATA_BASE "Fd" #define SPA_TYPE_INFO_DATA_FD_BASE SPA_TYPE_INFO_DATA_Fd ":" static const struct spa_type_info spa_type_data_type[] = { { SPA_DATA_Invalid, SPA_TYPE_Int, SPA_TYPE_INFO_DATA_BASE "Invalid", NULL }, { SPA_DATA_MemPtr, SPA_TYPE_Int, SPA_TYPE_INFO_DATA_BASE "MemPtr", NULL }, { SPA_DATA_MemFd, SPA_TYPE_Int, SPA_TYPE_INFO_DATA_FD_BASE "MemFd", NULL }, { SPA_DATA_DmaBuf, SPA_TYPE_Int, SPA_TYPE_INFO_DATA_FD_BASE "DmaBuf", NULL }, { SPA_DATA_MemId, SPA_TYPE_Int, SPA_TYPE_INFO_DATA_BASE "MemId", NULL }, { SPA_DATA_SyncObj, SPA_TYPE_Int, SPA_TYPE_INFO_DATA_BASE "SyncObj", NULL }, { 0, 0, NULL, NULL }, }; #define SPA_TYPE_INFO_Meta SPA_TYPE_INFO_POINTER_BASE "Meta" #define SPA_TYPE_INFO_META_BASE SPA_TYPE_INFO_Meta ":" #define SPA_TYPE_INFO_META_Array SPA_TYPE_INFO_META_BASE "Array" #define SPA_TYPE_INFO_META_ARRAY_BASE SPA_TYPE_INFO_META_Array ":" #define SPA_TYPE_INFO_META_Region SPA_TYPE_INFO_META_BASE "Region" #define SPA_TYPE_INFO_META_REGION_BASE SPA_TYPE_INFO_META_Region ":" #define SPA_TYPE_INFO_META_ARRAY_Region SPA_TYPE_INFO_META_ARRAY_BASE "Region" #define SPA_TYPE_INFO_META_ARRAY_REGION_BASE SPA_TYPE_INFO_META_ARRAY_Region ":" /* VideoTransform meta */ #define SPA_TYPE_INFO_META_Transformation SPA_TYPE_INFO_ENUM_BASE "Meta:Transformation" #define SPA_TYPE_INFO_META_TRANSFORMATION_BASE SPA_TYPE_INFO_META_Transformation ":" static const struct spa_type_info spa_type_meta_videotransform_type[] = { { SPA_META_TRANSFORMATION_None, SPA_TYPE_Int, SPA_TYPE_INFO_META_TRANSFORMATION_BASE "None", NULL }, { SPA_META_TRANSFORMATION_90, SPA_TYPE_Int, SPA_TYPE_INFO_META_TRANSFORMATION_BASE "90", NULL }, { SPA_META_TRANSFORMATION_180, SPA_TYPE_Int, SPA_TYPE_INFO_META_TRANSFORMATION_BASE "180", NULL }, { SPA_META_TRANSFORMATION_270, SPA_TYPE_Int, SPA_TYPE_INFO_META_TRANSFORMATION_BASE "270", NULL }, { SPA_META_TRANSFORMATION_Flipped, SPA_TYPE_Int, SPA_TYPE_INFO_META_TRANSFORMATION_BASE "Flipped", NULL }, { SPA_META_TRANSFORMATION_Flipped90, SPA_TYPE_Int, SPA_TYPE_INFO_META_TRANSFORMATION_BASE "Flipped90", NULL }, { SPA_META_TRANSFORMATION_Flipped180, SPA_TYPE_Int, SPA_TYPE_INFO_META_TRANSFORMATION_BASE "Flipped180", NULL }, { SPA_META_TRANSFORMATION_Flipped270, SPA_TYPE_Int, SPA_TYPE_INFO_META_TRANSFORMATION_BASE "Flipped270", NULL }, { 0, 0, NULL, NULL }, }; static const struct spa_type_info spa_type_meta_type[] = { { SPA_META_Invalid, SPA_TYPE_Pointer, SPA_TYPE_INFO_META_BASE "Invalid", NULL }, { SPA_META_Header, SPA_TYPE_Pointer, SPA_TYPE_INFO_META_BASE "Header", NULL }, { SPA_META_VideoCrop, SPA_TYPE_Pointer, SPA_TYPE_INFO_META_REGION_BASE "VideoCrop", NULL }, { SPA_META_VideoDamage, SPA_TYPE_Pointer, SPA_TYPE_INFO_META_ARRAY_REGION_BASE "VideoDamage", NULL }, { SPA_META_Bitmap, SPA_TYPE_Pointer, SPA_TYPE_INFO_META_BASE "Bitmap", NULL }, { SPA_META_Cursor, SPA_TYPE_Pointer, SPA_TYPE_INFO_META_BASE "Cursor", NULL }, { SPA_META_Control, SPA_TYPE_Pointer, SPA_TYPE_INFO_META_BASE "Control", NULL }, { SPA_META_Busy, SPA_TYPE_Pointer, SPA_TYPE_INFO_META_BASE "Busy", NULL }, { SPA_META_VideoTransform, SPA_TYPE_Pointer, SPA_TYPE_INFO_META_BASE "VideoTransform", NULL }, { SPA_META_SyncTimeline, SPA_TYPE_Pointer, SPA_TYPE_INFO_META_BASE "SyncTimeline", NULL }, { 0, 0, NULL, NULL }, }; /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_BUFFER_TYPES_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/control/000077500000000000000000000000001511204443500251475ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/control/control.h000066400000000000000000000016751511204443500270110ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_CONTROL_H #define SPA_CONTROL_H #include #include #ifdef __cplusplus extern "C" { #endif /** \defgroup spa_control Control * Control type declarations */ /** * \addtogroup spa_control * \{ */ /** Different Control types */ enum spa_control_type { SPA_CONTROL_Invalid, SPA_CONTROL_Properties, /**< SPA_TYPE_OBJECT_Props */ SPA_CONTROL_Midi, /**< spa_pod_bytes with raw midi data (deprecated, use SPA_CONTROL_UMP) */ SPA_CONTROL_OSC, /**< spa_pod_bytes with an OSC packet */ SPA_CONTROL_UMP, /**< spa_pod_bytes with raw UMP (universal MIDI packet) * data. The UMP 32 bit words are stored in native endian * format. */ _SPA_CONTROL_LAST, /**< not part of ABI */ }; /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_CONTROL_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/control/type-info.h000066400000000000000000000021211511204443500272260ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_CONTROL_TYPES_H #define SPA_CONTROL_TYPES_H #include #include #include #ifdef __cplusplus extern "C" { #endif /** * \addtogroup spa_control * \{ */ /* base for parameter object enumerations */ #define SPA_TYPE_INFO_Control SPA_TYPE_INFO_ENUM_BASE "Control" #define SPA_TYPE_INFO_CONTROL_BASE SPA_TYPE_INFO_Control ":" static const struct spa_type_info spa_type_control[] = { { SPA_CONTROL_Invalid, SPA_TYPE_Int, SPA_TYPE_INFO_CONTROL_BASE "Invalid", NULL }, { SPA_CONTROL_Properties, SPA_TYPE_Int, SPA_TYPE_INFO_CONTROL_BASE "Properties", NULL }, { SPA_CONTROL_Midi, SPA_TYPE_Int, SPA_TYPE_INFO_CONTROL_BASE "Midi", NULL }, { SPA_CONTROL_OSC, SPA_TYPE_Int, SPA_TYPE_INFO_CONTROL_BASE "OSC", NULL }, { SPA_CONTROL_UMP, SPA_TYPE_Int, SPA_TYPE_INFO_CONTROL_BASE "UMP", NULL }, { 0, 0, NULL, NULL }, }; /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_CONTROL_TYPES_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/control/ump-utils.h000066400000000000000000000132731511204443500272650ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2024 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_CONTROL_UMP_UTILS_H #define SPA_CONTROL_UMP_UTILS_H #include #include #ifdef __cplusplus extern "C" { #endif #ifndef SPA_API_CONTROL_UMP_UTILS #ifdef SPA_API_IMPL #define SPA_API_CONTROL_UMP_UTILS SPA_API_IMPL #else #define SPA_API_CONTROL_UMP_UTILS static inline #endif #endif /** * \addtogroup spa_control * \{ */ SPA_API_CONTROL_UMP_UTILS size_t spa_ump_message_size(uint8_t message_type) { static const uint32_t ump_sizes[] = { [0x0] = 1, /* Utility messages */ [0x1] = 1, /* System messages */ [0x2] = 1, /* MIDI 1.0 messages */ [0x3] = 2, /* 7bit SysEx messages */ [0x4] = 2, /* MIDI 2.0 messages */ [0x5] = 4, /* 8bit data message */ [0x6] = 1, [0x7] = 1, [0x8] = 2, [0x9] = 2, [0xa] = 2, [0xb] = 3, [0xc] = 3, [0xd] = 4, /* Flexible data messages */ [0xe] = 4, [0xf] = 4, /* Stream messages */ }; return ump_sizes[message_type & 0xf]; } SPA_API_CONTROL_UMP_UTILS int spa_ump_to_midi(const uint32_t **ump, size_t *ump_size, uint8_t *midi, size_t midi_maxsize, uint64_t *state) { int size = 0; uint32_t to_consume = 0; const uint32_t *u = *ump; if (*ump_size < 4 || (to_consume = (spa_ump_message_size(u[0]>>28) * 4)) > *ump_size) { to_consume = *ump_size; goto done; } if (midi_maxsize < 8) return -ENOSPC; switch (u[0] >> 28) { case 0x1: /* System Real Time and System Common Messages (except System Exclusive) */ midi[size++] = (u[0] >> 16) & 0xff; if (midi[0] >= 0xf1 && midi[0] <= 0xf3) { midi[size++] = (u[0] >> 8) & 0x7f; if (midi[0] == 0xf2) midi[size++] = u[0] & 0x7f; } break; case 0x2: /* MIDI 1.0 Channel Voice Messages */ midi[size++] = (u[0] >> 16); midi[size++] = (u[0] >> 8); if (midi[0] < 0xc0 || midi[0] > 0xdf) midi[size++] = (u[0]); break; case 0x3: /* Data Messages (including System Exclusive) */ { uint8_t status, i, bytes; status = (u[0] >> 20) & 0xf; bytes = SPA_CLAMP((u[0] >> 16) & 0xf, 0u, 6u); if (status == 0 || status == 1) midi[size++] = 0xf0; for (i = 0 ; i < bytes; i++) /* u[0] >> 8 | u[0] | u[1] >> 24 | u[1] >>16 ... */ midi[size++] = u[(i+2)/4] >> ((5-i)%4 * 8); if (status == 0 || status == 3) midi[size++] = 0xf7; break; } case 0x4: /* MIDI 2.0 Channel Voice Messages */ { uint8_t status = (u[0] >> 16) | 0x80; switch (status & 0xf0) { case 0xc0: /* program/bank change */ if (!(u[0] & 1)) *state = 2; if (*state == 0) { midi[size++] = (status & 0xf) | 0xb0; midi[size++] = 0; midi[size++] = (u[1] >> 8); to_consume = 0; *state = 1; } else if (*state == 1) { midi[size++] = (status & 0xf) | 0xb0; midi[size++] = 32; midi[size++] = u[1]; to_consume = 0; *state = 2; } else if (*state == 2) { midi[size++] = status; midi[size++] = (u[1] >> 24); *state = 0; } break; default: midi[size++] = status; midi[size++] = (u[0] >> 8) & 0x7f; SPA_FALLTHROUGH; case 0xd0: midi[size++] = (u[1] >> 25); break; } break; } case 0x0: /* Utility Messages */ case 0x5: /* Data Messages */ default: break; } done: (*ump_size) -= to_consume; (*ump) = SPA_PTROFF(*ump, to_consume, uint32_t); return size; } SPA_API_CONTROL_UMP_UTILS int spa_ump_from_midi(uint8_t **midi, size_t *midi_size, uint32_t *ump, size_t ump_maxsize, uint8_t group, uint64_t *state) { int size = 0; uint32_t i, prefix = group << 24, to_consume = 0, bytes; uint8_t status, *m = (*midi), end; if (*midi_size < 1) return 0; if (ump_maxsize < 16) return -ENOSPC; status = m[0]; /* SysEx */ if (*state == 0) { if (status == 0xf0) *state = 1; /* sysex start */ else if (status == 0xf7) *state = 2; /* sysex continue */ } if (*state & 3) { prefix |= 0x30000000; if (status & 0x80) { m++; to_consume++; } bytes = SPA_CLAMP(*midi_size - to_consume, 0u, 7u); if (bytes > 0) { end = m[bytes-1]; if (end & 0x80) { bytes--; /* skip terminator */ to_consume++; } else end = 0xf0; /* pretend there is a continue terminator */ bytes = SPA_CLAMP(bytes, 0u, 6u); to_consume += bytes; if (end == 0xf7) { if (*state == 2) { /* continue and done */ prefix |= 0x3 << 20; *state = 0; } } else if (*state == 1) { /* first packet but not finished */ prefix |= 0x1 << 20; *state = 2; /* sysex continue */ } else { /* continue and not finished */ prefix |= 0x2 << 20; } ump[size++] = prefix | bytes << 16; ump[size++] = 0; for (i = 0 ; i < bytes; i++) /* ump[0] |= (m[0] & 0x7f) << 8 * ump[0] |= (m[1] & 0x7f) * ump[1] |= (m[2] & 0x7f) << 24 * ... */ ump[(i+2)/4] |= (m[i] & 0x7f) << ((5-i)%4 * 8); } } else { /* regular messages */ switch (status) { case 0x80 ... 0x8f: case 0x90 ... 0x9f: case 0xa0 ... 0xaf: case 0xb0 ... 0xbf: case 0xe0 ... 0xef: to_consume = 3; prefix |= 0x20000000; break; case 0xc0 ... 0xdf: to_consume = 2; prefix |= 0x20000000; break; case 0xf2: to_consume = 3; prefix |= 0x10000000; break; case 0xf1: case 0xf3: to_consume = 2; prefix |= 0x10000000; break; case 0xf4 ... 0xff: to_consume = 1; prefix |= 0x10000000; break; default: return -EIO; } if (*midi_size < to_consume) { to_consume = *midi_size; } else { prefix |= status << 16; if (to_consume > 1) prefix |= (m[1] & 0x7f) << 8; if (to_consume > 2) prefix |= (m[2] & 0x7f); ump[size++] = prefix; } } (*midi_size) -= to_consume; (*midi) += to_consume; return size * 4; } /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_CONTROL_UMP_UTILS_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/debug/000077500000000000000000000000001511204443500245555ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/debug/buffer.h000066400000000000000000000100531511204443500261760ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_DEBUG_BUFFER_H #define SPA_DEBUG_BUFFER_H #include #include #include #include #ifdef __cplusplus extern "C" { #endif /** \defgroup spa_debug Debug * Debugging utilities */ /** * \addtogroup spa_debug * \{ */ #ifndef SPA_API_DEBUG_BUFFER #ifdef SPA_API_IMPL #define SPA_API_DEBUG_BUFFER SPA_API_IMPL #else #define SPA_API_DEBUG_BUFFER static inline #endif #endif SPA_API_DEBUG_BUFFER int spa_debugc_buffer(struct spa_debug_context *ctx, int indent, const struct spa_buffer *buffer) { uint32_t i; spa_debugc(ctx, "%*s" "struct spa_buffer %p:", indent, "", buffer); spa_debugc(ctx, "%*s" " n_metas: %u (at %p)", indent, "", buffer->n_metas, buffer->metas); for (i = 0; i < buffer->n_metas; i++) { struct spa_meta *m = &buffer->metas[i]; const char *type_name; type_name = spa_debug_type_find_name(spa_type_meta_type, m->type); spa_debugc(ctx, "%*s" " meta %d: type %d (%s), data %p, size %d:", indent, "", i, m->type, type_name, m->data, m->size); switch (m->type) { case SPA_META_Header: { struct spa_meta_header *h = (struct spa_meta_header*)m->data; spa_debugc(ctx, "%*s" " struct spa_meta_header:", indent, ""); spa_debugc(ctx, "%*s" " flags: %08x", indent, "", h->flags); spa_debugc(ctx, "%*s" " offset: %u", indent, "", h->offset); spa_debugc(ctx, "%*s" " seq: %" PRIu64, indent, "", h->seq); spa_debugc(ctx, "%*s" " pts: %" PRIi64, indent, "", h->pts); spa_debugc(ctx, "%*s" " dts_offset: %" PRIi64, indent, "", h->dts_offset); break; } case SPA_META_VideoCrop: { struct spa_meta_region *h = (struct spa_meta_region*)m->data; spa_debugc(ctx, "%*s" " struct spa_meta_region:", indent, ""); spa_debugc(ctx, "%*s" " x: %d", indent, "", h->region.position.x); spa_debugc(ctx, "%*s" " y: %d", indent, "", h->region.position.y); spa_debugc(ctx, "%*s" " width: %d", indent, "", h->region.size.width); spa_debugc(ctx, "%*s" " height: %d", indent, "", h->region.size.height); break; } case SPA_META_VideoDamage: { struct spa_meta_region *h; spa_meta_for_each(h, m) { spa_debugc(ctx, "%*s" " struct spa_meta_region:", indent, ""); spa_debugc(ctx, "%*s" " x: %d", indent, "", h->region.position.x); spa_debugc(ctx, "%*s" " y: %d", indent, "", h->region.position.y); spa_debugc(ctx, "%*s" " width: %d", indent, "", h->region.size.width); spa_debugc(ctx, "%*s" " height: %d", indent, "", h->region.size.height); } break; } case SPA_META_Bitmap: break; case SPA_META_Cursor: break; default: spa_debugc(ctx, "%*s" " Unknown:", indent, ""); spa_debugc_mem(ctx, 5, m->data, m->size); } } spa_debugc(ctx, "%*s" " n_datas: \t%u (at %p)", indent, "", buffer->n_datas, buffer->datas); for (i = 0; i < buffer->n_datas; i++) { struct spa_data *d = &buffer->datas[i]; spa_debugc(ctx, "%*s" " type: %d (%s)", indent, "", d->type, spa_debug_type_find_name(spa_type_data_type, d->type)); spa_debugc(ctx, "%*s" " flags: %d", indent, "", d->flags); spa_debugc(ctx, "%*s" " data: %p", indent, "", d->data); spa_debugc(ctx, "%*s" " fd: %" PRIi64, indent, "", d->fd); spa_debugc(ctx, "%*s" " offset: %d", indent, "", d->mapoffset); spa_debugc(ctx, "%*s" " maxsize: %u", indent, "", d->maxsize); spa_debugc(ctx, "%*s" " chunk: %p", indent, "", d->chunk); spa_debugc(ctx, "%*s" " offset: %d", indent, "", d->chunk->offset); spa_debugc(ctx, "%*s" " size: %u", indent, "", d->chunk->size); spa_debugc(ctx, "%*s" " stride: %d", indent, "", d->chunk->stride); } return 0; } SPA_API_DEBUG_BUFFER int spa_debug_buffer(int indent, const struct spa_buffer *buffer) { return spa_debugc_buffer(NULL, indent, buffer); } /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_DEBUG_BUFFER_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/debug/context.h000066400000000000000000000031671511204443500264210ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2023 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_DEBUG_CONTEXT_H #define SPA_DEBUG_CONTEXT_H #include #include #include #include #ifdef __cplusplus extern "C" { #endif /** * \addtogroup spa_debug * \{ */ #ifndef spa_debugn #define spa_debugn(_fmt,...) printf((_fmt), ## __VA_ARGS__) #endif #ifndef spa_debug #define spa_debug(_fmt,...) spa_debugn(_fmt"\n", ## __VA_ARGS__) #endif #ifndef SPA_API_DEBUG_CONTEXT #ifdef SPA_API_IMPL #define SPA_API_DEBUG_CONTEXT SPA_API_IMPL #else #define SPA_API_DEBUG_CONTEXT static inline #endif #endif struct spa_debug_context { void (*log) (struct spa_debug_context *ctx, const char *fmt, ...) SPA_PRINTF_FUNC(2, 3); }; #define spa_debugc(_c,_fmt,...) (_c)?((_c)->log((_c),_fmt, ## __VA_ARGS__)):(void)spa_debug(_fmt, ## __VA_ARGS__) SPA_API_DEBUG_CONTEXT void spa_debugc_error_location(struct spa_debug_context *c, struct spa_error_location *loc) { int i, skip = loc->col > 80 ? loc->col - 40 : 0, lc = loc->col-skip-1; char buf[80]; for (i = 0; (size_t)i < sizeof(buf)-1 && (size_t)(i + skip) < loc->len; i++) { char ch = loc->location[i + skip]; if (ch == '\n' || ch == '\0') break; buf[i] = isspace(ch) ? ' ' : ch; } buf[i] = '\0'; spa_debugc(c, "line:%6d | %s%s", loc->line, skip ? "..." : "", buf); for (i = 0; buf[i]; i++) buf[i] = i < lc ? '-' : i == lc ? '^' : ' '; spa_debugc(c, "column:%4d |-%s%s", loc->col, skip ? "---" : "", buf); } /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_DEBUG_CONTEXT_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/debug/dict.h000066400000000000000000000020261511204443500256510ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_DEBUG_DICT_H #define SPA_DEBUG_DICT_H #include #include #ifdef __cplusplus extern "C" { #endif /** * \addtogroup spa_debug * \{ */ #ifndef SPA_API_DEBUG_DICT #ifdef SPA_API_IMPL #define SPA_API_DEBUG_DICT SPA_API_IMPL #else #define SPA_API_DEBUG_DICT static inline #endif #endif SPA_API_DEBUG_DICT int spa_debugc_dict(struct spa_debug_context *ctx, int indent, const struct spa_dict *dict) { const struct spa_dict_item *item; spa_debugc(ctx, "%*sflags:%08x n_items:%d", indent, "", dict->flags, dict->n_items); spa_dict_for_each(item, dict) { spa_debugc(ctx, "%*s %s = \"%s\"", indent, "", item->key, item->value); } return 0; } SPA_API_DEBUG_DICT int spa_debug_dict(int indent, const struct spa_dict *dict) { return spa_debugc_dict(NULL, indent, dict); } /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_DEBUG_DICT_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/debug/file.h000066400000000000000000000027041511204443500256500ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2022 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_DEBUG_FILE_H #define SPA_DEBUG_FILE_H #include #include #include #include #include #include #include #include #include #include #ifdef __cplusplus extern "C" { #endif /** * \addtogroup spa_debug * \{ */ #ifndef SPA_API_DEBUG_FILE #ifdef SPA_API_IMPL #define SPA_API_DEBUG_FILE SPA_API_IMPL #else #define SPA_API_DEBUG_FILE static inline #endif #endif struct spa_debug_file_ctx { struct spa_debug_context ctx; FILE *f; }; SPA_PRINTF_FUNC(2,3) SPA_API_DEBUG_FILE void spa_debug_file_log(struct spa_debug_context *ctx, const char *fmt, ...) { struct spa_debug_file_ctx *c = SPA_CONTAINER_OF(ctx, struct spa_debug_file_ctx, ctx); va_list args; va_start(args, fmt); vfprintf(c->f, fmt, args); fputc('\n', c->f); va_end(args); } #define SPA_DEBUG_FILE_INIT(_f) \ (struct spa_debug_file_ctx){ { spa_debug_file_log }, _f, } #define spa_debug_file_error_location(f,loc,fmt,...) \ ({ \ struct spa_debug_file_ctx c = SPA_DEBUG_FILE_INIT(f); \ if (fmt) spa_debugc(&c.ctx, fmt, __VA_ARGS__); \ spa_debugc_error_location(&c.ctx, loc); \ }) /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_DEBUG_FILE_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/debug/format.h000066400000000000000000000131061511204443500262170ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_DEBUG_FORMAT_H #define SPA_DEBUG_FORMAT_H #include #include #include #include #include #include #include #ifdef __cplusplus extern "C" { #endif /** * \addtogroup spa_debug * \{ */ #ifndef SPA_API_DEBUG_FORMAT #ifdef SPA_API_IMPL #define SPA_API_DEBUG_FORMAT SPA_API_IMPL #else #define SPA_API_DEBUG_FORMAT static inline #endif #endif SPA_API_DEBUG_FORMAT int spa_debug_strbuf_format_value(struct spa_strbuf *buffer, const struct spa_type_info *info, uint32_t type, void *body, uint32_t size) { switch (type) { case SPA_TYPE_Bool: spa_strbuf_append(buffer, "%s", *(int32_t *) body ? "true" : "false"); break; case SPA_TYPE_Id: { uint32_t value = *(uint32_t *) body; const char *str = spa_debug_type_find_short_name(info, value); char tmp[64]; if (str == NULL) { snprintf(tmp, sizeof(tmp), "%" PRIu32, value); str = tmp; } spa_strbuf_append(buffer, "%s", str); break; } case SPA_TYPE_Int: spa_strbuf_append(buffer, "%d", *(int32_t *) body); break; case SPA_TYPE_Long: spa_strbuf_append(buffer, "%" PRIi64, *(int64_t *) body); break; case SPA_TYPE_Float: spa_strbuf_append(buffer, "%f", *(float *) body); break; case SPA_TYPE_Double: spa_strbuf_append(buffer, "%f", *(double *) body); break; case SPA_TYPE_String: spa_strbuf_append(buffer, "%-*s", size, (char *) body); break; case SPA_TYPE_Rectangle: { struct spa_rectangle *r = (struct spa_rectangle *)body; spa_strbuf_append(buffer, "%" PRIu32 "x%" PRIu32, r->width, r->height); break; } case SPA_TYPE_Fraction: { struct spa_fraction *f = (struct spa_fraction *)body; spa_strbuf_append(buffer, "%" PRIu32 "/%" PRIu32, f->num, f->denom); break; } case SPA_TYPE_Bitmap: spa_strbuf_append(buffer, "Bitmap"); break; case SPA_TYPE_Bytes: spa_strbuf_append(buffer, "Bytes"); break; case SPA_TYPE_Array: { void *p; struct spa_pod_array_body *b = (struct spa_pod_array_body *)body; int i = 0; info = info && info->values ? info->values : info; spa_strbuf_append(buffer, "< "); if (b->child.size >= spa_pod_type_size(b->child.type)) { SPA_POD_ARRAY_BODY_FOREACH(b, size, p) { if (i++ > 0) spa_strbuf_append(buffer, ", "); spa_debug_strbuf_format_value(buffer, info, b->child.type, p, b->child.size); } } spa_strbuf_append(buffer, " >"); break; } default: spa_strbuf_append(buffer, "INVALID type %d", type); break; } return 0; } SPA_API_DEBUG_FORMAT int spa_debug_format_value(const struct spa_type_info *info, uint32_t type, void *body, uint32_t size) { char buffer[1024]; struct spa_strbuf buf; spa_strbuf_init(&buf, buffer, sizeof(buffer)); spa_debug_strbuf_format_value(&buf, info, type, body, size); spa_debugn("%s", buffer); return 0; } SPA_API_DEBUG_FORMAT int spa_debugc_format(struct spa_debug_context *ctx, int indent, const struct spa_type_info *info, const struct spa_pod *format) { const char *media_type; const char *media_subtype; struct spa_pod_prop *prop; uint32_t mtype, mstype; if (info == NULL) info = spa_type_format; if (format == NULL || format->type != SPA_TYPE_Object) return -EINVAL; if (spa_format_parse(format, &mtype, &mstype) < 0) return -EINVAL; media_type = spa_debug_type_find_name(spa_type_media_type, mtype); media_subtype = spa_debug_type_find_name(spa_type_media_subtype, mstype); spa_debugc(ctx, "%*s %s/%s", indent, "", media_type ? spa_debug_type_short_name(media_type) : "unknown", media_subtype ? spa_debug_type_short_name(media_subtype) : "unknown"); SPA_POD_OBJECT_FOREACH((struct spa_pod_object*)format, prop) { const char *key; const struct spa_type_info *ti; uint32_t i, type, size, n_vals, choice; const struct spa_pod *val; void *vals; char buffer[1024]; struct spa_strbuf buf; if (prop->key == SPA_FORMAT_mediaType || prop->key == SPA_FORMAT_mediaSubtype) continue; val = spa_pod_get_values(&prop->value, &n_vals, &choice); type = val->type; size = val->size; if (type < SPA_TYPE_None || type >= _SPA_TYPE_LAST || n_vals < 1) continue; vals = SPA_POD_BODY(val); ti = spa_debug_type_find(info, prop->key); key = ti ? ti->name : NULL; spa_strbuf_init(&buf, buffer, sizeof(buffer)); spa_strbuf_append(&buf, "%*s %16s : (%s) ", indent, "", key ? spa_debug_type_short_name(key) : "unknown", spa_debug_type_short_name(spa_types[type].name)); if (choice == SPA_CHOICE_None) { spa_debug_strbuf_format_value(&buf, ti ? ti->values : NULL, type, vals, size); } else { const char *ssep, *esep, *sep; switch (choice) { case SPA_CHOICE_Range: case SPA_CHOICE_Step: ssep = "[ "; sep = ", "; esep = " ]"; break; default: case SPA_CHOICE_Enum: case SPA_CHOICE_Flags: ssep = "{ "; sep = ", "; esep = " }"; break; } spa_strbuf_append(&buf, "%s", ssep); for (i = 1; i < n_vals; i++) { vals = SPA_PTROFF(vals, size, void); if (i > 1) spa_strbuf_append(&buf, "%s", sep); spa_debug_strbuf_format_value(&buf, ti ? ti->values : NULL, type, vals, size); } spa_strbuf_append(&buf, "%s", esep); } spa_debugc(ctx, "%s", buffer); } return 0; } SPA_API_DEBUG_FORMAT int spa_debug_format(int indent, const struct spa_type_info *info, const struct spa_pod *format) { return spa_debugc_format(NULL, indent, info, format); } /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_DEBUG_FORMAT_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/debug/log.h000066400000000000000000000056601511204443500255160ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2022 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_DEBUG_LOG_H #define SPA_DEBUG_LOG_H #include #include #include #include #include #include #include #include #include #ifdef __cplusplus extern "C" { #endif #ifndef SPA_API_DEBUG_LOG #ifdef SPA_API_IMPL #define SPA_API_DEBUG_LOG SPA_API_IMPL #else #define SPA_API_DEBUG_LOG static inline #endif #endif /** * \addtogroup spa_debug * \{ */ struct spa_debug_log_ctx { struct spa_debug_context ctx; struct spa_log *log; enum spa_log_level level; const struct spa_log_topic *topic; const char *file; int line; const char *func; }; SPA_PRINTF_FUNC(2,3) SPA_API_DEBUG_LOG void spa_debug_log_log(struct spa_debug_context *ctx, const char *fmt, ...) { struct spa_debug_log_ctx *c = SPA_CONTAINER_OF(ctx, struct spa_debug_log_ctx, ctx); va_list args; va_start(args, fmt); spa_log_logtv(c->log, c->level, c->topic, c->file, c->line, c->func, fmt, args); va_end(args); } #define SPA_LOGF_DEBUG_INIT(_l,_lev,_t,_file,_line,_func) \ (struct spa_debug_log_ctx){ { spa_debug_log_log }, _l, _lev, _t, \ _file, _line, _func } #define SPA_LOGT_DEBUG_INIT(_l,_lev,_t) \ SPA_LOGF_DEBUG_INIT(_l,_lev,_t,__FILE__,__LINE__,__func__) #define SPA_LOG_DEBUG_INIT(l,lev) \ SPA_LOGT_DEBUG_INIT(l,lev,SPA_LOG_TOPIC_DEFAULT) #define spa_debug_log_pod(l,lev,indent,info,pod) \ ({ \ struct spa_debug_log_ctx c = SPA_LOG_DEBUG_INIT(l,lev); \ if (SPA_UNLIKELY(spa_log_level_topic_enabled(c.log, c.topic, c.level))) \ spa_debugc_pod(&c.ctx, indent, info, pod); \ }) #define spa_debug_log_format(l,lev,indent,info,format) \ ({ \ struct spa_debug_log_ctx c = SPA_LOG_DEBUG_INIT(l,lev); \ if (SPA_UNLIKELY(spa_log_level_topic_enabled(c.log, c.topic, c.level))) \ spa_debugc_format(&c.ctx, indent, info, format); \ }) #define spa_debug_log_mem(l,lev,indent,data,len) \ ({ \ struct spa_debug_log_ctx c = SPA_LOG_DEBUG_INIT(l,lev); \ if (SPA_UNLIKELY(spa_log_level_topic_enabled(c.log, c.topic, c.level))) \ spa_debugc_mem(&c.ctx, indent, data, len); \ }) #define spa_debug_log_dict(l,lev,indent,dict) \ ({ \ struct spa_debug_log_ctx c = SPA_LOG_DEBUG_INIT(l,lev); \ if (SPA_UNLIKELY(spa_log_level_topic_enabled(c.log, c.topic, c.level))) \ spa_debugc_dict(&c.ctx, indent, dict); \ }) #define spa_debug_log_error_location(l,lev,loc,fmt,...) \ ({ \ struct spa_debug_log_ctx c = SPA_LOG_DEBUG_INIT(l,lev); \ if (SPA_UNLIKELY(spa_log_level_topic_enabled(c.log, c.topic, c.level))) { \ if (fmt) spa_debugc(&c.ctx, fmt, __VA_ARGS__); \ spa_debugc_error_location(&c.ctx, loc); \ } \ }) /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_DEBUG_LOG_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/debug/mem.h000066400000000000000000000021521511204443500255040ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_DEBUG_MEM_H #define SPA_DEBUG_MEM_H #include #include #ifdef __cplusplus extern "C" { #endif /** * \addtogroup spa_debug * \{ */ #ifndef SPA_API_DEBUG_MEM #ifdef SPA_API_IMPL #define SPA_API_DEBUG_MEM SPA_API_IMPL #else #define SPA_API_DEBUG_MEM static inline #endif #endif SPA_API_DEBUG_MEM int spa_debugc_mem(struct spa_debug_context *ctx, int indent, const void *data, size_t size) { const uint8_t *t = (const uint8_t*)data; char buffer[512]; size_t i; int pos = 0; for (i = 0; i < size; i++) { if (i % 16 == 0) pos = sprintf(buffer, "%p: ", &t[i]); pos += sprintf(buffer + pos, "%02x ", t[i]); if (i % 16 == 15 || i == size - 1) { spa_debugc(ctx, "%*s" "%s", indent, "", buffer); } } return 0; } SPA_API_DEBUG_MEM int spa_debug_mem(int indent, const void *data, size_t size) { return spa_debugc_mem(NULL, indent, data, size); } /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_DEBUG_MEM_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/debug/node.h000066400000000000000000000024761511204443500256640ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_DEBUG_NODE_H #define SPA_DEBUG_NODE_H #include #include #include #ifdef __cplusplus extern "C" { #endif /** * \addtogroup spa_debug * \{ */ #ifndef SPA_API_DEBUG_NODE #ifdef SPA_API_IMPL #define SPA_API_DEBUG_NODE SPA_API_IMPL #else #define SPA_API_DEBUG_NODE static inline #endif #endif SPA_API_DEBUG_NODE int spa_debugc_port_info(struct spa_debug_context *ctx, int indent, const struct spa_port_info *info) { spa_debugc(ctx, "%*s" "struct spa_port_info %p:", indent, "", info); spa_debugc(ctx, "%*s" " flags: \t%08" PRIx64, indent, "", info->flags); spa_debugc(ctx, "%*s" " rate: \t%d/%d", indent, "", info->rate.num, info->rate.denom); spa_debugc(ctx, "%*s" " props:", indent, ""); if (info->props) spa_debugc_dict(ctx, indent + 2, info->props); else spa_debugc(ctx, "%*s" " none", indent, ""); return 0; } SPA_API_DEBUG_NODE int spa_debug_port_info(int indent, const struct spa_port_info *info) { return spa_debugc_port_info(NULL, indent, info); } /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_DEBUG_NODE_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/debug/pod.h000066400000000000000000000171141511204443500255140ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_DEBUG_POD_H #define SPA_DEBUG_POD_H #include #include #include #include #include #include #ifdef __cplusplus extern "C" { #endif /** * \addtogroup spa_debug * \{ */ #ifndef SPA_API_DEBUG_POD #ifdef SPA_API_IMPL #define SPA_API_DEBUG_POD SPA_API_IMPL #else #define SPA_API_DEBUG_POD static inline #endif #endif SPA_API_DEBUG_POD int spa_debugc_pod_value(struct spa_debug_context *ctx, int indent, const struct spa_type_info *info, uint32_t type, void *body, uint32_t size) { switch (type) { case SPA_TYPE_Bool: spa_debugc(ctx, "%*s" "Bool %s", indent, "", (*(int32_t *) body) ? "true" : "false"); break; case SPA_TYPE_Id: spa_debugc(ctx, "%*s" "Id %-8" PRIu32 " (%s)", indent, "", *(uint32_t *) body, spa_debug_type_find_name(info, *(uint32_t *) body)); break; case SPA_TYPE_Int: spa_debugc(ctx, "%*s" "Int %" PRId32, indent, "", *(int32_t *) body); break; case SPA_TYPE_Long: spa_debugc(ctx, "%*s" "Long %" PRIi64 "", indent, "", *(int64_t *) body); break; case SPA_TYPE_Float: spa_debugc(ctx, "%*s" "Float %f", indent, "", *(float *) body); break; case SPA_TYPE_Double: spa_debugc(ctx, "%*s" "Double %f", indent, "", *(double *) body); break; case SPA_TYPE_String: spa_debugc(ctx, "%*s" "String \"%s\"", indent, "", (char *) body); break; case SPA_TYPE_Fd: spa_debugc(ctx, "%*s" "Fd %d", indent, "", *(int *) body); break; case SPA_TYPE_Pointer: { struct spa_pod_pointer_body *b = (struct spa_pod_pointer_body *)body; spa_debugc(ctx, "%*s" "Pointer %s %p", indent, "", spa_debug_type_find_name(SPA_TYPE_ROOT, b->type), b->value); break; } case SPA_TYPE_Rectangle: { struct spa_rectangle *r = (struct spa_rectangle *)body; spa_debugc(ctx, "%*s" "Rectangle %" PRIu32 "x%" PRIu32 "", indent, "", r->width, r->height); break; } case SPA_TYPE_Fraction: { struct spa_fraction *f = (struct spa_fraction *)body; spa_debugc(ctx, "%*s" "Fraction %" PRIu32 "/%" PRIu32 "", indent, "", f->num, f->denom); break; } case SPA_TYPE_Bitmap: spa_debugc(ctx, "%*s" "Bitmap", indent, ""); break; case SPA_TYPE_Array: { struct spa_pod_array_body *b = (struct spa_pod_array_body *)body; void *p; const struct spa_type_info *ti = spa_debug_type_find(SPA_TYPE_ROOT, b->child.type); uint32_t min_size = spa_pod_type_size(b->child.type); spa_debugc(ctx, "%*s" "Array: child.size %" PRIu32 ", child.type %s", indent, "", b->child.size, ti ? ti->name : "unknown"); if (b->child.size < min_size) { spa_debugc(ctx, "%*s" " INVALID child.size < %" PRIu32, indent, "", min_size); } else { info = info && info->values ? info->values : info; SPA_POD_ARRAY_BODY_FOREACH(b, size, p) spa_debugc_pod_value(ctx, indent + 2, info, b->child.type, p, b->child.size); } break; } case SPA_TYPE_Choice: { struct spa_pod_choice_body *b = (struct spa_pod_choice_body *)body; void *p; const struct spa_type_info *ti = spa_debug_type_find(spa_type_choice, b->type); uint32_t min_size = spa_pod_type_size(b->child.type); spa_debugc(ctx, "%*s" "Choice: type %s, flags %08" PRIx32 " %" PRIu32 " %" PRIu32, indent, "", ti ? ti->name : "unknown", b->flags, size, b->child.size); if (b->child.size < min_size) { spa_debugc(ctx, "%*s" "INVALID child.size < %" PRIu32, indent, "", min_size); } else { SPA_POD_CHOICE_BODY_FOREACH(b, size, p) spa_debugc_pod_value(ctx, indent + 2, info, b->child.type, p, b->child.size); } break; } case SPA_TYPE_Struct: { struct spa_pod *b = (struct spa_pod *)body, *p; spa_debugc(ctx, "%*s" "Struct: size %" PRIu32, indent, "", size); SPA_POD_FOREACH(b, size, p) { uint32_t min_size = spa_pod_type_size(p->type); if (p->size < min_size) { spa_debugc(ctx, "%*s" "INVALID child.size < %" PRIu32, indent, "", min_size); } else { spa_debugc_pod_value(ctx, indent + 2, info, p->type, SPA_POD_BODY(p), p->size); } } break; } case SPA_TYPE_Object: { struct spa_pod_object_body *b = (struct spa_pod_object_body *)body; struct spa_pod_prop *p; const struct spa_type_info *ti, *ii; ti = spa_debug_type_find(info, b->type); ii = ti ? spa_debug_type_find(ti->values, 0) : NULL; ii = ii ? spa_debug_type_find(ii->values, b->id) : NULL; spa_debugc(ctx, "%*s" "Object: size %" PRIu32 ", type %s (%" PRIu32 "), id %s (%" PRIu32 ")", indent, "", size, ti ? ti->name : "unknown", b->type, ii ? ii->name : "unknown", b->id); info = ti ? ti->values : info; SPA_POD_OBJECT_BODY_FOREACH(b, size, p) { static const char custom_prefix[] = SPA_TYPE_INFO_PROPS_BASE "Custom:"; char custom_name[sizeof(custom_prefix) + 16]; const char *name = "unknown"; uint32_t min_size = spa_pod_type_size(p->value.type); ii = spa_debug_type_find(info, p->key); if (ii) { name = ii->name; } else if (p->key >= SPA_PROP_START_CUSTOM) { snprintf(custom_name, sizeof(custom_name), "%s%" PRIu32, custom_prefix, p->key - SPA_PROP_START_CUSTOM); name = custom_name; } spa_debugc(ctx, "%*s" "Prop: key %s (%" PRIu32 "), flags %08" PRIx32, indent+2, "", name, p->key, p->flags); if (p->value.size < min_size) { spa_debugc(ctx, "%*s" "INVALID value.size < %" PRIu32, indent, "", min_size); } else { spa_debugc_pod_value(ctx, indent + 4, ii ? ii->values : NULL, p->value.type, SPA_POD_CONTENTS(struct spa_pod_prop, p), p->value.size); } } break; } case SPA_TYPE_Sequence: { struct spa_pod_sequence_body *b = (struct spa_pod_sequence_body *)body; const struct spa_type_info *ti, *ii; struct spa_pod_control *c; ti = spa_debug_type_find(info, b->unit); spa_debugc(ctx, "%*s" "Sequence: size %" PRIu32 ", unit %s", indent, "", size, ti ? ti->name : "unknown"); SPA_POD_SEQUENCE_BODY_FOREACH(b, size, c) { uint32_t min_size = spa_pod_type_size(c->value.type); ii = spa_debug_type_find(spa_type_control, c->type); spa_debugc(ctx, "%*s" "Control: offset %" PRIu32 ", type %s", indent+2, "", c->offset, ii ? ii->name : "unknown"); if (c->value.size < min_size) { spa_debugc(ctx, "%*s" "INVALID value.size < %" PRIu32, indent, "", min_size); } else { spa_debugc_pod_value(ctx, indent + 4, ii ? ii->values : NULL, c->value.type, SPA_POD_CONTENTS(struct spa_pod_control, c), c->value.size); } } break; } case SPA_TYPE_Bytes: spa_debugc(ctx, "%*s" "Bytes", indent, ""); spa_debugc_mem(ctx, indent + 2, body, size); break; case SPA_TYPE_None: spa_debugc(ctx, "%*s" "None", indent, ""); spa_debugc_mem(ctx, indent + 2, body, size); break; default: spa_debugc(ctx, "%*s" "unhandled POD type %" PRIu32, indent, "", type); break; } return 0; } SPA_API_DEBUG_POD int spa_debugc_pod(struct spa_debug_context *ctx, int indent, const struct spa_type_info *info, const struct spa_pod *pod) { if (pod->size < spa_pod_type_size(pod->type)) return -EINVAL; return spa_debugc_pod_value(ctx, indent, info ? info : SPA_TYPE_ROOT, pod->type, SPA_POD_BODY(pod), pod->size); } SPA_API_DEBUG_POD int spa_debug_pod_value(int indent, const struct spa_type_info *info, uint32_t type, void *body, uint32_t size) { return spa_debugc_pod_value(NULL, indent, info, type, body, size); } SPA_API_DEBUG_POD int spa_debug_pod(int indent, const struct spa_type_info *info, const struct spa_pod *pod) { return spa_debugc_pod(NULL, indent, info, pod); } /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_DEBUG_POD_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/debug/types.h000066400000000000000000000050331511204443500260730ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_DEBUG_TYPES_H #define SPA_DEBUG_TYPES_H #include #include #ifdef __cplusplus extern "C" { #endif /** * \addtogroup spa_debug * \{ */ #ifndef SPA_API_DEBUG_TYPES #ifdef SPA_API_IMPL #define SPA_API_DEBUG_TYPES SPA_API_IMPL #else #define SPA_API_DEBUG_TYPES static inline #endif #endif SPA_API_DEBUG_TYPES const struct spa_type_info *spa_debug_type_find(const struct spa_type_info *info, uint32_t type) { const struct spa_type_info *res; if (info == NULL) info = SPA_TYPE_ROOT; while (info && info->name) { if (info->type == SPA_ID_INVALID) { if (info->values && (res = spa_debug_type_find(info->values, type))) return res; } else if (info->type == type) return info; info++; } return NULL; } SPA_API_DEBUG_TYPES const char *spa_debug_type_short_name(const char *name) { return spa_type_short_name(name); } SPA_API_DEBUG_TYPES const char *spa_debug_type_find_name(const struct spa_type_info *info, uint32_t type) { if ((info = spa_debug_type_find(info, type)) == NULL) return NULL; return info->name; } SPA_API_DEBUG_TYPES const char *spa_debug_type_find_short_name(const struct spa_type_info *info, uint32_t type) { const char *str; if ((str = spa_debug_type_find_name(info, type)) == NULL) return NULL; return spa_debug_type_short_name(str); } SPA_API_DEBUG_TYPES uint32_t spa_debug_type_find_type(const struct spa_type_info *info, const char *name) { if (info == NULL) info = SPA_TYPE_ROOT; while (info && info->name) { uint32_t res; if (strcmp(info->name, name) == 0) return info->type; if (info->values && (res = spa_debug_type_find_type(info->values, name)) != SPA_ID_INVALID) return res; info++; } return SPA_ID_INVALID; } SPA_API_DEBUG_TYPES const struct spa_type_info *spa_debug_type_find_short(const struct spa_type_info *info, const char *name) { while (info && info->name) { if (strcmp(spa_debug_type_short_name(info->name), name) == 0) return info; if (strcmp(info->name, name) == 0) return info; if (info->type != 0 && info->type == (uint32_t)atoi(name)) return info; info++; } return NULL; } SPA_API_DEBUG_TYPES uint32_t spa_debug_type_find_type_short(const struct spa_type_info *info, const char *name) { if ((info = spa_debug_type_find_short(info, name)) == NULL) return SPA_ID_INVALID; return info->type; } /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_DEBUG_NODE_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/filter-graph/000077500000000000000000000000001511204443500260535ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/filter-graph/filter-graph.h000066400000000000000000000100471511204443500306120ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2024 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_FILTER_GRAPH_H #define SPA_FILTER_GRAPH_H #include #include #include #include #ifdef __cplusplus extern "C" { #endif #ifndef SPA_API_FILTER_GRAPH #ifdef SPA_API_IMPL #define SPA_API_FILTER_GRAPH SPA_API_IMPL #else #define SPA_API_FILTER_GRAPH static inline #endif #endif /** \defgroup spa_filter_graph Filter Graph * a graph of filters */ /** * \addtogroup spa_filter_graph * \{ */ /** * A graph of filters */ #define SPA_TYPE_INTERFACE_FilterGraph SPA_TYPE_INFO_INTERFACE_BASE "FilterGraph" #define SPA_VERSION_FILTER_GRAPH 0 struct spa_filter_graph { struct spa_interface iface; }; struct spa_filter_graph_info { uint32_t n_inputs; uint32_t n_outputs; #define SPA_FILTER_GRAPH_CHANGE_MASK_FLAGS (1u<<0) #define SPA_FILTER_GRAPH_CHANGE_MASK_PROPS (1u<<1) uint64_t change_mask; uint64_t flags; struct spa_dict *props; }; struct spa_filter_graph_events { #define SPA_VERSION_FILTER_GRAPH_EVENTS 0 uint32_t version; void (*info) (void *object, const struct spa_filter_graph_info *info); void (*apply_props) (void *object, enum spa_direction direction, const struct spa_pod *props); void (*props_changed) (void *object, enum spa_direction direction); }; struct spa_filter_graph_methods { #define SPA_VERSION_FILTER_GRAPH_METHODS 0 uint32_t version; int (*add_listener) (void *object, struct spa_hook *listener, const struct spa_filter_graph_events *events, void *data); int (*enum_prop_info) (void *object, uint32_t idx, struct spa_pod_builder *b, struct spa_pod **param); int (*get_props) (void *object, struct spa_pod_builder *b, struct spa_pod **props); int (*set_props) (void *object, enum spa_direction direction, const struct spa_pod *props); int (*activate) (void *object, const struct spa_dict *props); int (*deactivate) (void *object); int (*reset) (void *object); int (*process) (void *object, const void *in[], void *out[], uint32_t n_samples); }; SPA_API_FILTER_GRAPH int spa_filter_graph_add_listener(struct spa_filter_graph *object, struct spa_hook *listener, const struct spa_filter_graph_events *events, void *data) { return spa_api_method_r(int, -ENOTSUP, spa_filter_graph, &object->iface, add_listener, 0, listener, events, data); } SPA_API_FILTER_GRAPH int spa_filter_graph_enum_prop_info(struct spa_filter_graph *object, uint32_t idx, struct spa_pod_builder *b, struct spa_pod **param) { return spa_api_method_r(int, -ENOTSUP, spa_filter_graph, &object->iface, enum_prop_info, 0, idx, b, param); } SPA_API_FILTER_GRAPH int spa_filter_graph_get_props(struct spa_filter_graph *object, struct spa_pod_builder *b, struct spa_pod **props) { return spa_api_method_r(int, -ENOTSUP, spa_filter_graph, &object->iface, get_props, 0, b, props); } SPA_API_FILTER_GRAPH int spa_filter_graph_set_props(struct spa_filter_graph *object, enum spa_direction direction, const struct spa_pod *props) { return spa_api_method_r(int, -ENOTSUP, spa_filter_graph, &object->iface, set_props, 0, direction, props); } SPA_API_FILTER_GRAPH int spa_filter_graph_activate(struct spa_filter_graph *object, const struct spa_dict *props) { return spa_api_method_r(int, -ENOTSUP, spa_filter_graph, &object->iface, activate, 0, props); } SPA_API_FILTER_GRAPH int spa_filter_graph_deactivate(struct spa_filter_graph *object) { return spa_api_method_r(int, -ENOTSUP, spa_filter_graph, &object->iface, deactivate, 0); } SPA_API_FILTER_GRAPH int spa_filter_graph_reset(struct spa_filter_graph *object) { return spa_api_method_r(int, -ENOTSUP, spa_filter_graph, &object->iface, reset, 0); } SPA_API_FILTER_GRAPH int spa_filter_graph_process(struct spa_filter_graph *object, const void *in[], void *out[], uint32_t n_samples) { return spa_api_method_r(int, -ENOTSUP, spa_filter_graph, &object->iface, process, 0, in, out, n_samples); } /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_FILTER_GRAPH_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/graph/000077500000000000000000000000001511204443500245705ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/graph/graph.h000066400000000000000000000227641511204443500260550ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_GRAPH_H #define SPA_GRAPH_H #include #include #include #include #include #include #ifdef __cplusplus extern "C" { #endif /** \defgroup spa_graph Graph * Node graph */ /** * \addtogroup spa_graph * \{ */ #ifndef SPA_API_GRAPH #ifdef SPA_API_IMPL #define SPA_API_GRAPH SPA_API_IMPL #else #define SPA_API_GRAPH static inline #endif #endif #ifndef spa_debug #define spa_debug(...) #endif struct spa_graph; struct spa_graph_node; struct spa_graph_link; struct spa_graph_port; struct spa_graph_state { int status; /**< current status */ int32_t required; /**< required number of signals */ int32_t pending; /**< number of pending signals */ }; SPA_API_GRAPH void spa_graph_state_reset(struct spa_graph_state *state) { state->pending = state->required; } struct spa_graph_link { struct spa_list link; struct spa_graph_state *state; int (*signal) (void *data); void *signal_data; }; #define spa_graph_link_signal(l) ((l)->signal((l)->signal_data)) #define spa_graph_state_dec(s) (SPA_ATOMIC_DEC(s->pending) == 0) SPA_API_GRAPH int spa_graph_link_trigger(struct spa_graph_link *link) { struct spa_graph_state *state = link->state; spa_debug("link %p: state %p: pending %d/%d", link, state, state->pending, state->required); if (spa_graph_state_dec(state)) spa_graph_link_signal(link); return state->status; } struct spa_graph { uint32_t flags; /* flags */ struct spa_graph_node *parent; /* parent node or NULL when driver */ struct spa_graph_state *state; /* state of graph */ struct spa_list nodes; /* list of nodes of this graph */ }; struct spa_graph_node_callbacks { #define SPA_VERSION_GRAPH_NODE_CALLBACKS 0 uint32_t version; int (*process) (void *data, struct spa_graph_node *node); int (*reuse_buffer) (void *data, struct spa_graph_node *node, uint32_t port_id, uint32_t buffer_id); }; struct spa_graph_node { struct spa_list link; /**< link in graph nodes list */ struct spa_graph *graph; /**< owner graph */ struct spa_list ports[2]; /**< list of input and output ports */ struct spa_list links; /**< list of links to next nodes */ uint32_t flags; /**< node flags */ struct spa_graph_state *state; /**< state of the node */ struct spa_graph_link graph_link; /**< link in graph */ struct spa_graph *subgraph; /**< subgraph or NULL */ struct spa_callbacks callbacks; struct spa_list sched_link; /**< link for scheduler */ }; #define spa_graph_node_call(n,method,version,...) \ ({ \ int __res = 0; \ spa_callbacks_call_res(&(n)->callbacks, \ struct spa_graph_node_callbacks, __res, \ method, (version), ##__VA_ARGS__); \ __res; \ }) #define spa_graph_node_process(n) spa_graph_node_call((n), process, 0, (n)) #define spa_graph_node_reuse_buffer(n,p,i) spa_graph_node_call((n), reuse_buffer, 0, (n), (p), (i)) struct spa_graph_port { struct spa_list link; /**< link in node port list */ struct spa_graph_node *node; /**< owner node */ enum spa_direction direction; /**< port direction */ uint32_t port_id; /**< port id */ uint32_t flags; /**< port flags */ struct spa_graph_port *peer; /**< peer */ }; SPA_API_GRAPH int spa_graph_node_trigger(struct spa_graph_node *node) { struct spa_graph_link *l; spa_debug("node %p trigger", node); spa_list_for_each(l, &node->links, link) spa_graph_link_trigger(l); return 0; } SPA_API_GRAPH int spa_graph_run(struct spa_graph *graph) { struct spa_graph_node *n, *t; struct spa_list pending; spa_graph_state_reset(graph->state); spa_debug("graph %p run with state %p pending %d/%d", graph, graph->state, graph->state->pending, graph->state->required); spa_list_init(&pending); spa_list_for_each(n, &graph->nodes, link) { struct spa_graph_state *s = n->state; spa_graph_state_reset(s); spa_debug("graph %p node %p: state %p pending %d/%d status %d", graph, n, s, s->pending, s->required, s->status); if (--s->pending == 0) spa_list_append(&pending, &n->sched_link); } spa_list_for_each_safe(n, t, &pending, sched_link) spa_graph_node_process(n); return 0; } SPA_API_GRAPH int spa_graph_finish(struct spa_graph *graph) { spa_debug("graph %p finish", graph); if (graph->parent) return spa_graph_node_trigger(graph->parent); return 0; } SPA_API_GRAPH int spa_graph_link_signal_node(void *data) { struct spa_graph_node *node = (struct spa_graph_node *)data; spa_debug("node %p call process", node); return spa_graph_node_process(node); } SPA_API_GRAPH int spa_graph_link_signal_graph(void *data) { struct spa_graph_node *node = (struct spa_graph_node *)data; return spa_graph_finish(node->graph); } SPA_API_GRAPH void spa_graph_init(struct spa_graph *graph, struct spa_graph_state *state) { spa_list_init(&graph->nodes); graph->flags = 0; graph->state = state; spa_debug("graph %p init state %p", graph, state); } SPA_API_GRAPH void spa_graph_link_add(struct spa_graph_node *out, struct spa_graph_state *state, struct spa_graph_link *link) { link->state = state; state->required++; spa_debug("node %p add link %p to state %p %d", out, link, state, state->required); spa_list_append(&out->links, &link->link); } SPA_API_GRAPH void spa_graph_link_remove(struct spa_graph_link *link) { link->state->required--; spa_debug("link %p state %p remove %d", link, link->state, link->state->required); spa_list_remove(&link->link); } SPA_API_GRAPH void spa_graph_node_init(struct spa_graph_node *node, struct spa_graph_state *state) { spa_list_init(&node->ports[SPA_DIRECTION_INPUT]); spa_list_init(&node->ports[SPA_DIRECTION_OUTPUT]); spa_list_init(&node->links); node->flags = 0; node->subgraph = NULL; node->state = state; node->state->required = node->state->pending = 0; node->state->status = SPA_STATUS_OK; node->graph_link.signal = spa_graph_link_signal_graph; node->graph_link.signal_data = node; spa_debug("node %p init state %p", node, state); } SPA_API_GRAPH int spa_graph_node_impl_sub_process(void *data SPA_UNUSED, struct spa_graph_node *node) { struct spa_graph *graph = node->subgraph; spa_debug("node %p: sub process %p", node, graph); return spa_graph_run(graph); } static const struct spa_graph_node_callbacks spa_graph_node_sub_impl_default = { .version = SPA_VERSION_GRAPH_NODE_CALLBACKS, .process = spa_graph_node_impl_sub_process, }; SPA_API_GRAPH void spa_graph_node_set_subgraph(struct spa_graph_node *node, struct spa_graph *subgraph) { node->subgraph = subgraph; subgraph->parent = node; spa_debug("node %p set subgraph %p", node, subgraph); } SPA_API_GRAPH void spa_graph_node_set_callbacks(struct spa_graph_node *node, const struct spa_graph_node_callbacks *callbacks, void *data) { node->callbacks = SPA_CALLBACKS_INIT(callbacks, data); } SPA_API_GRAPH void spa_graph_node_add(struct spa_graph *graph, struct spa_graph_node *node) { node->graph = graph; spa_list_append(&graph->nodes, &node->link); node->state->required++; spa_debug("node %p add to graph %p, state %p required %d", node, graph, node->state, node->state->required); spa_graph_link_add(node, graph->state, &node->graph_link); } SPA_API_GRAPH void spa_graph_node_remove(struct spa_graph_node *node) { spa_debug("node %p remove from graph %p, state %p required %d", node, node->graph, node->state, node->state->required); spa_graph_link_remove(&node->graph_link); node->state->required--; spa_list_remove(&node->link); } SPA_API_GRAPH void spa_graph_port_init(struct spa_graph_port *port, enum spa_direction direction, uint32_t port_id, uint32_t flags) { spa_debug("port %p init type %d id %d", port, direction, port_id); port->direction = direction; port->port_id = port_id; port->flags = flags; } SPA_API_GRAPH void spa_graph_port_add(struct spa_graph_node *node, struct spa_graph_port *port) { spa_debug("port %p add to node %p", port, node); port->node = node; spa_list_append(&node->ports[port->direction], &port->link); } SPA_API_GRAPH void spa_graph_port_remove(struct spa_graph_port *port) { spa_debug("port %p remove", port); spa_list_remove(&port->link); } SPA_API_GRAPH void spa_graph_port_link(struct spa_graph_port *out, struct spa_graph_port *in) { spa_debug("port %p link to %p %p %p", out, in, in->node, in->node->state); out->peer = in; in->peer = out; } SPA_API_GRAPH void spa_graph_port_unlink(struct spa_graph_port *port) { spa_debug("port %p unlink from %p", port, port->peer); if (port->peer) { port->peer->peer = NULL; port->peer = NULL; } } SPA_API_GRAPH int spa_graph_node_impl_process(void *data, struct spa_graph_node *node) { struct spa_node *n = (struct spa_node *)data; struct spa_graph_state *state = node->state; spa_debug("node %p: process state %p: %d, node %p", node, state, state->status, n); if ((state->status = spa_node_process(n)) != SPA_STATUS_OK) spa_graph_node_trigger(node); return state->status; } SPA_API_GRAPH int spa_graph_node_impl_reuse_buffer(void *data, struct spa_graph_node *node SPA_UNUSED, uint32_t port_id, uint32_t buffer_id) { struct spa_node *n = (struct spa_node *)data; return spa_node_port_reuse_buffer(n, port_id, buffer_id); } static const struct spa_graph_node_callbacks spa_graph_node_impl_default = { .version = SPA_VERSION_GRAPH_NODE_CALLBACKS, .process = spa_graph_node_impl_process, .reuse_buffer = spa_graph_node_impl_reuse_buffer, }; /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_GRAPH_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/interfaces/000077500000000000000000000000001511204443500256125ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/interfaces/audio/000077500000000000000000000000001511204443500267135ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/interfaces/audio/aec.h000066400000000000000000000107571511204443500276260ustar00rootroot00000000000000/* PipeWire */ /* SPDX-FileCopyrightText: Copyright © 2021 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #ifndef SPA_AUDIO_AEC_H #define SPA_AUDIO_AEC_H #ifdef __cplusplus extern "C" { #endif #ifndef SPA_API_AUDIO_AEC #ifdef SPA_API_IMPL #define SPA_API_AUDIO_AEC SPA_API_IMPL #else #define SPA_API_AUDIO_AEC static inline #endif #endif #define SPA_TYPE_INTERFACE_AUDIO_AEC SPA_TYPE_INFO_INTERFACE_BASE "Audio:AEC" #define SPA_VERSION_AUDIO_AEC 1 struct spa_audio_aec { struct spa_interface iface; const char *name; const struct spa_dict *info; const char *latency; }; struct spa_audio_aec_info { #define SPA_AUDIO_AEC_CHANGE_MASK_PROPS (1u<<0) uint64_t change_mask; const struct spa_dict *props; }; struct spa_audio_aec_events { #define SPA_VERSION_AUDIO_AEC_EVENTS 0 uint32_t version; /**< version of this structure */ /** Emitted when info changes */ void (*info) (void *data, const struct spa_audio_aec_info *info); }; struct spa_audio_aec_methods { #define SPA_VERSION_AUDIO_AEC_METHODS 3 uint32_t version; int (*add_listener) (void *object, struct spa_hook *listener, const struct spa_audio_aec_events *events, void *data); int (*init) (void *object, const struct spa_dict *args, const struct spa_audio_info_raw *info); int (*run) (void *object, const float *rec[], const float *play[], float *out[], uint32_t n_samples); int (*set_props) (void *object, const struct spa_dict *args); /* since 0.3.58, version 1:1 */ int (*activate) (void *object); /* since 0.3.58, version 1:1 */ int (*deactivate) (void *object); /* version 1:2 */ int (*enum_props) (void* object, int index, struct spa_pod_builder* builder); int (*get_params) (void* object, struct spa_pod_builder* builder); int (*set_params) (void *object, const struct spa_pod *args); /* version 1:3 */ int (*init2) (void *object, const struct spa_dict *args, struct spa_audio_info_raw *play_info, struct spa_audio_info_raw *rec_info, struct spa_audio_info_raw *out_info); }; SPA_API_AUDIO_AEC int spa_audio_aec_add_listener(struct spa_audio_aec *object, struct spa_hook *listener, const struct spa_audio_aec_events *events, void *data) { return spa_api_method_r(int, -ENOTSUP, spa_audio_aec, &object->iface, add_listener, 0, listener, events, data); } SPA_API_AUDIO_AEC int spa_audio_aec_init(struct spa_audio_aec *object, const struct spa_dict *args, const struct spa_audio_info_raw *info) { return spa_api_method_r(int, -ENOTSUP, spa_audio_aec, &object->iface, init, 0, args, info); } SPA_API_AUDIO_AEC int spa_audio_aec_run(struct spa_audio_aec *object, const float *rec[], const float *play[], float *out[], uint32_t n_samples) { return spa_api_method_r(int, -ENOTSUP, spa_audio_aec, &object->iface, run, 0, rec, play, out, n_samples); } SPA_API_AUDIO_AEC int spa_audio_aec_set_props(struct spa_audio_aec *object, const struct spa_dict *args) { return spa_api_method_r(int, -ENOTSUP, spa_audio_aec, &object->iface, set_props, 0, args); } SPA_API_AUDIO_AEC int spa_audio_aec_activate(struct spa_audio_aec *object) { return spa_api_method_r(int, -ENOTSUP, spa_audio_aec, &object->iface, activate, 1); } SPA_API_AUDIO_AEC int spa_audio_aec_deactivate(struct spa_audio_aec *object) { return spa_api_method_r(int, -ENOTSUP, spa_audio_aec, &object->iface, deactivate, 1); } SPA_API_AUDIO_AEC int spa_audio_aec_enum_props(struct spa_audio_aec *object, int index, struct spa_pod_builder* builder) { return spa_api_method_r(int, -ENOTSUP, spa_audio_aec, &object->iface, enum_props, 2, index, builder); } SPA_API_AUDIO_AEC int spa_audio_aec_get_params(struct spa_audio_aec *object, struct spa_pod_builder* builder) { return spa_api_method_r(int, -ENOTSUP, spa_audio_aec, &object->iface, get_params, 2, builder); } SPA_API_AUDIO_AEC int spa_audio_aec_set_params(struct spa_audio_aec *object, const struct spa_pod *args) { return spa_api_method_r(int, -ENOTSUP, spa_audio_aec, &object->iface, set_params, 2, args); } SPA_API_AUDIO_AEC int spa_audio_aec_init2(struct spa_audio_aec *object, const struct spa_dict *args, struct spa_audio_info_raw *play_info, struct spa_audio_info_raw *rec_info, struct spa_audio_info_raw *out_info) { return spa_api_method_r(int, -ENOTSUP, spa_audio_aec, &object->iface, init2, 3, args, play_info, rec_info, out_info); } #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_AUDIO_AEC_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/monitor/000077500000000000000000000000001511204443500251565ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/monitor/device.h000066400000000000000000000245531511204443500265770ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_DEVICE_H #define SPA_DEVICE_H #include #include #include #include #ifdef __cplusplus extern "C" { #endif #ifndef SPA_API_DEVICE #ifdef SPA_API_IMPL #define SPA_API_DEVICE SPA_API_IMPL #else #define SPA_API_DEVICE static inline #endif #endif /** * \defgroup spa_device Device * * The device interface can be used to monitor all kinds of devices * and create objects as a result. Objects a typically other * Devices or Nodes. * */ /** * \addtogroup spa_device * \{ */ #define SPA_TYPE_INTERFACE_Device SPA_TYPE_INFO_INTERFACE_BASE "Device" #define SPA_VERSION_DEVICE 0 struct spa_device { struct spa_interface iface; }; /** * Information about the device and parameters it supports * * This information is part of the info event on a device. */ struct spa_device_info { #define SPA_VERSION_DEVICE_INFO 0 uint32_t version; #define SPA_DEVICE_CHANGE_MASK_FLAGS (1u<<0) #define SPA_DEVICE_CHANGE_MASK_PROPS (1u<<1) #define SPA_DEVICE_CHANGE_MASK_PARAMS (1u<<2) uint64_t change_mask; uint64_t flags; const struct spa_dict *props; /**< device properties */ struct spa_param_info *params; /**< supported parameters */ uint32_t n_params; /**< number of elements in params */ }; #define SPA_DEVICE_INFO_INIT() ((struct spa_device_info){ SPA_VERSION_DEVICE_INFO, }) /** * Information about a device object * * This information is part of the object_info event on the device. */ struct spa_device_object_info { #define SPA_VERSION_DEVICE_OBJECT_INFO 0 uint32_t version; const char *type; /**< the object type managed by this device */ const char *factory_name; /**< a factory name that implements the object */ #define SPA_DEVICE_OBJECT_CHANGE_MASK_FLAGS (1u<<0) #define SPA_DEVICE_OBJECT_CHANGE_MASK_PROPS (1u<<1) uint64_t change_mask; uint64_t flags; const struct spa_dict *props; /**< extra object properties */ }; #define SPA_DEVICE_OBJECT_INFO_INIT() ((struct spa_device_object_info){ SPA_VERSION_DEVICE_OBJECT_INFO, }) /** the result of spa_device_enum_params() */ #define SPA_RESULT_TYPE_DEVICE_PARAMS 1 struct spa_result_device_params { uint32_t id; uint32_t index; uint32_t next; struct spa_pod *param; }; #define SPA_DEVICE_EVENT_INFO 0 #define SPA_DEVICE_EVENT_RESULT 1 #define SPA_DEVICE_EVENT_EVENT 2 #define SPA_DEVICE_EVENT_OBJECT_INFO 3 #define SPA_DEVICE_EVENT_NUM 4 /** * spa_device_events: * * Events are always emitted from the main thread */ struct spa_device_events { /** version of the structure */ #define SPA_VERSION_DEVICE_EVENTS 0 uint32_t version; /** notify extra information about the device */ void (*info) (void *data, const struct spa_device_info *info); /** notify a result */ void (*result) (void *data, int seq, int res, uint32_t type, const void *result); /** a device event */ void (*event) (void *data, const struct spa_event *event); /** info changed for an object managed by the device, info is NULL when * the object is removed */ void (*object_info) (void *data, uint32_t id, const struct spa_device_object_info *info); }; #define SPA_DEVICE_METHOD_ADD_LISTENER 0 #define SPA_DEVICE_METHOD_SYNC 1 #define SPA_DEVICE_METHOD_ENUM_PARAMS 2 #define SPA_DEVICE_METHOD_SET_PARAM 3 #define SPA_DEVICE_METHOD_NUM 4 /** * spa_device_methods: */ struct spa_device_methods { /* the version of the methods. This can be used to expand this * structure in the future */ #define SPA_VERSION_DEVICE_METHODS 0 uint32_t version; /** * Set events to receive asynchronous notifications from * the device. * * Setting the events will trigger the info event and an * object_info event for each managed object on the new * listener. * * \param object a \ref spa_device * \param listener a listener * \param events a struct \ref spa_device_events * \param data data passed as first argument in functions of \a events * \return 0 on success * < 0 errno on error */ int (*add_listener) (void *object, struct spa_hook *listener, const struct spa_device_events *events, void *data); /** * Perform a sync operation. * * This method will emit the result event with the given sequence * number synchronously or with the returned async return value * asynchronously. * * Because all methods are serialized in the device, this can be used * to wait for completion of all previous method calls. * * \param seq a sequence number * \return 0 on success * -EINVAL when node is NULL * an async result */ int (*sync) (void *object, int seq); /** * Enumerate the parameters of a device. * * Parameters are identified with an \a id. Some parameters can have * multiple values, see the documentation of the parameter id. * * Parameters can be filtered by passing a non-NULL \a filter. * * The result callback will be called at most \a max times with a * struct spa_result_device_params as the result. * * This function must be called from the main thread. * * \param device a \ref spa_device * \param seq a sequence number to pass to the result function * \param id the param id to enumerate * \param index the index of enumeration, pass 0 for the first item. * \param max the maximum number of items to iterate * \param filter and optional filter to use * \return 0 when there are no more parameters to enumerate * -EINVAL when invalid arguments are given * -ENOENT the parameter \a id is unknown * -ENOTSUP when there are no parameters * implemented on \a device */ int (*enum_params) (void *object, int seq, uint32_t id, uint32_t index, uint32_t max, const struct spa_pod *filter); /** * Set the configurable parameter in \a device. * * Usually, \a param will be obtained from enum_params and then * modified but it is also possible to set another spa_pod * as long as its keys and types match a supported object. * * Objects with property keys that are not known are ignored. * * This function must be called from the main thread. * * \param object \ref spa_device * \param id the parameter id to configure * \param flags additional flags * \param param the parameter to configure * * \return 0 on success * -EINVAL when invalid arguments are given * -ENOTSUP when there are no parameters implemented on \a device * -ENOENT the parameter is unknown */ int (*set_param) (void *object, uint32_t id, uint32_t flags, const struct spa_pod *param); }; SPA_API_DEVICE int spa_device_add_listener(struct spa_device *object, struct spa_hook *listener, const struct spa_device_events *events, void *data) { return spa_api_method_r(int, -ENOTSUP, spa_device, &object->iface, add_listener, 0, listener, events, data); } SPA_API_DEVICE int spa_device_sync(struct spa_device *object, int seq) { return spa_api_method_r(int, -ENOTSUP, spa_device, &object->iface, sync, 0, seq); } SPA_API_DEVICE int spa_device_enum_params(struct spa_device *object, int seq, uint32_t id, uint32_t index, uint32_t max, const struct spa_pod *filter) { return spa_api_method_r(int, -ENOTSUP, spa_device, &object->iface, enum_params, 0, seq, id, index, max, filter); } SPA_API_DEVICE int spa_device_set_param(struct spa_device *object, uint32_t id, uint32_t flags, const struct spa_pod *param) { return spa_api_method_r(int, -ENOTSUP, spa_device, &object->iface, set_param, 0, id, flags, param); } #define SPA_KEY_DEVICE_ENUM_API "device.enum.api" /**< the api used to discover this * device */ #define SPA_KEY_DEVICE_API "device.api" /**< the api used by the device * Ex. "udev", "alsa", "v4l2". */ #define SPA_KEY_DEVICE_NAME "device.name" /**< the name of the device */ #define SPA_KEY_DEVICE_ALIAS "device.alias" /**< alternative name of the device */ #define SPA_KEY_DEVICE_NICK "device.nick" /**< the device short name */ #define SPA_KEY_DEVICE_DESCRIPTION "device.description" /**< a device description */ #define SPA_KEY_DEVICE_ICON "device.icon" /**< icon for the device. A base64 blob * containing PNG image data */ #define SPA_KEY_DEVICE_ICON_NAME "device.icon-name" /**< an XDG icon name for the device. * Ex. "sound-card-speakers-usb" */ #define SPA_KEY_DEVICE_PLUGGED_USEC "device.plugged.usec" /**< when the device was plugged */ #define SPA_KEY_DEVICE_BUS_ID "device.bus-id" /**< the device bus-id */ #define SPA_KEY_DEVICE_BUS_PATH "device.bus-path" /**< bus path to the device in the OS' * format. * Ex. "pci-0000:00:14.0-usb-0:3.2:1.0" */ #define SPA_KEY_DEVICE_BUS "device.bus" /**< bus of the device if applicable. One of * "isa", "pci", "usb", "firewire", * "bluetooth" */ #define SPA_KEY_DEVICE_SUBSYSTEM "device.subsystem" /**< device subsystem */ #define SPA_KEY_DEVICE_SYSFS_PATH "device.sysfs.path" /**< device sysfs path */ #define SPA_KEY_DEVICE_VENDOR_ID "device.vendor.id" /**< vendor ID if applicable */ #define SPA_KEY_DEVICE_VENDOR_NAME "device.vendor.name" /**< vendor name if applicable */ #define SPA_KEY_DEVICE_PRODUCT_ID "device.product.id" /**< product ID if applicable */ #define SPA_KEY_DEVICE_PRODUCT_NAME "device.product.name" /**< product name if applicable */ #define SPA_KEY_DEVICE_SERIAL "device.serial" /**< Serial number if applicable */ #define SPA_KEY_DEVICE_CLASS "device.class" /**< device class */ #define SPA_KEY_DEVICE_CAPABILITIES "device.capabilities" /**< api specific device capabilities */ #define SPA_KEY_DEVICE_FORM_FACTOR "device.form-factor" /**< form factor if applicable. One of * "internal", "speaker", "handset", "tv", * "webcam", "microphone", "headset", * "headphone", "hands-free", "car", "hifi", * "computer", "portable" */ #define SPA_KEY_DEVICE_PROFILE "device.profile" /**< profile for the device */ #define SPA_KEY_DEVICE_PROFILE_SET "device.profile-set" /**< profile set for the device */ #define SPA_KEY_DEVICE_STRING "device.string" /**< device string in the underlying * layer's format. E.g. "surround51:0" */ #define SPA_KEY_DEVICE_DEVIDS "device.devids" /**< space separated list of device ids (dev_t) of the * underlying device(s) if applicable */ /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_DEVICE_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/monitor/event.h000066400000000000000000000015271511204443500264550ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2020 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_EVENT_DEVICE_H #define SPA_EVENT_DEVICE_H #include #ifdef __cplusplus extern "C" { #endif /** * \addtogroup spa_device * \{ */ /* object id of SPA_TYPE_EVENT_Device */ enum spa_device_event { SPA_DEVICE_EVENT_ObjectConfig, }; #define SPA_DEVICE_EVENT_ID(ev) SPA_EVENT_ID(ev, SPA_TYPE_EVENT_Device) #define SPA_DEVICE_EVENT_INIT(id) SPA_EVENT_INIT(SPA_TYPE_EVENT_Device, id) /* properties for SPA_TYPE_EVENT_Device */ enum spa_event_device { SPA_EVENT_DEVICE_START, SPA_EVENT_DEVICE_Object, /* an object id (Int) */ SPA_EVENT_DEVICE_Props, /* properties for an object (SPA_TYPE_OBJECT_Props) */ }; /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_EVENT_DEVICE */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/monitor/type-info.h000066400000000000000000000024421511204443500272430ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2021 Collabora Ltd. */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_DEVICE_TYPE_INFO_H #define SPA_DEVICE_TYPE_INFO_H #include #include #ifdef __cplusplus extern "C" { #endif /** * \addtogroup spa_device * \{ */ #define SPA_TYPE_INFO_DeviceEvent SPA_TYPE_INFO_EVENT_BASE "Device" #define SPA_TYPE_INFO_DEVICE_EVENT_BASE SPA_TYPE_INFO_DeviceEvent ":" #define SPA_TYPE_INFO_DeviceEventId SPA_TYPE_INFO_ENUM_BASE "DeviceEventId" #define SPA_TYPE_INFO_DEVICE_EVENT_ID_BASE SPA_TYPE_INFO_DeviceEventId ":" static const struct spa_type_info spa_type_device_event_id[] = { { SPA_DEVICE_EVENT_ObjectConfig, SPA_TYPE_EVENT_Device, SPA_TYPE_INFO_DEVICE_EVENT_ID_BASE "ObjectConfig", NULL }, { 0, 0, NULL, NULL }, }; static const struct spa_type_info spa_type_device_event[] = { { SPA_EVENT_DEVICE_START, SPA_TYPE_Id, SPA_TYPE_INFO_DEVICE_EVENT_BASE, spa_type_device_event_id }, { SPA_EVENT_DEVICE_Object, SPA_TYPE_Int, SPA_TYPE_INFO_DEVICE_EVENT_BASE "Object", NULL }, { SPA_EVENT_DEVICE_Props, SPA_TYPE_OBJECT_Props, SPA_TYPE_INFO_DEVICE_EVENT_BASE "Props", NULL }, { 0, 0, NULL, NULL }, }; /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_DEVICE_TYPE_INFO_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/monitor/utils.h000066400000000000000000000047771511204443500265060ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2019 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_DEVICE_UTILS_H #define SPA_DEVICE_UTILS_H #include #include #ifdef __cplusplus extern "C" { #endif #ifndef SPA_API_DEVICE_UTILS #ifdef SPA_API_IMPL #define SPA_API_DEVICE_UTILS SPA_API_IMPL #else #define SPA_API_DEVICE_UTILS static inline #endif #endif /** * \addtogroup spa_device * \{ */ struct spa_result_device_params_data { struct spa_pod_builder *builder; struct spa_result_device_params data; }; SPA_API_DEVICE_UTILS void spa_result_func_device_params(void *data, int seq SPA_UNUSED, int res SPA_UNUSED, uint32_t type SPA_UNUSED, const void *result) { struct spa_result_device_params_data *d = (struct spa_result_device_params_data *)data; const struct spa_result_device_params *r = (const struct spa_result_device_params *)result; uint32_t offset = d->builder->state.offset; if (spa_pod_builder_raw_padded(d->builder, r->param, SPA_POD_SIZE(r->param)) < 0) return; d->data.next = r->next; d->data.param = spa_pod_builder_deref(d->builder, offset); } SPA_API_DEVICE_UTILS int spa_device_enum_params_sync(struct spa_device *device, uint32_t id, uint32_t *index, const struct spa_pod *filter, struct spa_pod **param, struct spa_pod_builder *builder) { struct spa_result_device_params_data data = { builder, {0}}; struct spa_hook listener = {{0}, {0}, 0, 0}; static const struct spa_device_events device_events = { .version = SPA_VERSION_DEVICE_EVENTS, .info = NULL, .result = spa_result_func_device_params, }; int res; spa_device_add_listener(device, &listener, &device_events, &data); res = spa_device_enum_params(device, 0, id, *index, 1, filter); spa_hook_remove(&listener); if (data.data.param == NULL) { if (res > 0) res = 0; } else { *index = data.data.next; *param = data.data.param; res = 1; } return res; } #define spa_device_emit(hooks,method,version,...) \ spa_hook_list_call_simple(hooks, struct spa_device_events, \ method, version, ##__VA_ARGS__) #define spa_device_emit_info(hooks,i) spa_device_emit(hooks,info, 0, i) #define spa_device_emit_result(hooks,s,r,t,res) spa_device_emit(hooks,result, 0, s, r, t, res) #define spa_device_emit_event(hooks,e) spa_device_emit(hooks,event, 0, e) #define spa_device_emit_object_info(hooks,id,i) spa_device_emit(hooks,object_info, 0, id, i) /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_DEVICE_UTILS_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/node/000077500000000000000000000000001511204443500244145ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/node/command.h000066400000000000000000000033121511204443500262020ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_COMMAND_NODE_H #define SPA_COMMAND_NODE_H #include #ifdef __cplusplus extern "C" { #endif /** * \addtogroup spa_node * \{ */ /* object id of SPA_TYPE_COMMAND_Node */ enum spa_node_command { SPA_NODE_COMMAND_Suspend, /**< suspend a node, this removes all configured * formats and closes any devices */ SPA_NODE_COMMAND_Pause, /**< pause a node. this makes it stop emitting * scheduling events */ SPA_NODE_COMMAND_Start, /**< start a node, this makes it start emitting * scheduling events */ SPA_NODE_COMMAND_Enable, SPA_NODE_COMMAND_Disable, SPA_NODE_COMMAND_Flush, SPA_NODE_COMMAND_Drain, SPA_NODE_COMMAND_Marker, SPA_NODE_COMMAND_ParamBegin, /**< begin a set of parameter enumerations or * configuration that require the device to * remain opened, like query formats and then * set a format */ SPA_NODE_COMMAND_ParamEnd, /**< end a transaction */ SPA_NODE_COMMAND_RequestProcess,/**< Sent to a driver when some other node emitted * the RequestProcess event. */ SPA_NODE_COMMAND_User, /**< User defined command */ }; #define SPA_NODE_COMMAND_ID(cmd) SPA_COMMAND_ID(cmd, SPA_TYPE_COMMAND_Node) #define SPA_NODE_COMMAND_INIT(id) SPA_COMMAND_INIT(SPA_TYPE_COMMAND_Node, id) /* properties for SPA_TYPE_COMMAND_Node */ enum spa_command_node { SPA_COMMAND_NODE_START, SPA_COMMAND_NODE_START_User = 0x1000, SPA_COMMAND_NODE_extra, /** extra info (String) */ SPA_COMMAND_NODE_START_CUSTOM = 0x1000000, }; /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_COMMAND_NODE_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/node/event.h000066400000000000000000000020041511204443500257020ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_EVENT_NODE_H #define SPA_EVENT_NODE_H #include #ifdef __cplusplus extern "C" { #endif /** * \addtogroup spa_node * \{ */ /* object id of SPA_TYPE_EVENT_Node */ enum spa_node_event { SPA_NODE_EVENT_Error, SPA_NODE_EVENT_Buffering, SPA_NODE_EVENT_RequestRefresh, SPA_NODE_EVENT_RequestProcess, /*< Ask the driver to start processing * the graph */ SPA_NODE_EVENT_User, /* User defined event */ }; #define SPA_NODE_EVENT_ID(ev) SPA_EVENT_ID(ev, SPA_TYPE_EVENT_Node) #define SPA_NODE_EVENT_INIT(id) SPA_EVENT_INIT(SPA_TYPE_EVENT_Node, id) /* properties for SPA_TYPE_EVENT_Node */ enum spa_event_node { SPA_EVENT_NODE_START, SPA_EVENT_NODE_START_User = 0x1000, SPA_EVENT_NODE_extra, /** extra info (String) */ SPA_EVENT_NODE_START_CUSTOM = 0x1000000, }; /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_EVENT_NODE_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/node/io.h000066400000000000000000000401041511204443500251730ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_IO_H #define SPA_IO_H #include #include #ifdef __cplusplus extern "C" { #endif /** * \addtogroup spa_node * \{ */ /** IO areas * * IO information for a port on a node. This is allocated * by the host and configured on a node or all ports for which * IO is requested. * * The plugin will communicate with the host through the IO * areas. */ /** Different IO area types */ enum spa_io_type { SPA_IO_Invalid, SPA_IO_Buffers, /**< area to exchange buffers, struct spa_io_buffers */ SPA_IO_Range, /**< expected byte range, struct spa_io_range (currently not used in PipeWire) */ SPA_IO_Clock, /**< area to update clock information, struct spa_io_clock */ SPA_IO_Latency, /**< latency reporting, struct spa_io_latency (currently not used in * PipeWire). \see spa_param_latency */ SPA_IO_Control, /**< area for control messages, struct spa_io_sequence */ SPA_IO_Notify, /**< area for notify messages, struct spa_io_sequence */ SPA_IO_Position, /**< position information in the graph, struct spa_io_position */ SPA_IO_RateMatch, /**< rate matching between nodes, struct spa_io_rate_match */ SPA_IO_Memory, /**< memory pointer, struct spa_io_memory (currently not used in PipeWire) */ SPA_IO_AsyncBuffers, /**< async area to exchange buffers, struct spa_io_async_buffers */ }; /** * IO area to exchange buffers. * * A set of buffers should first be configured on the node/port. * Further references to those buffers will be made by using the * id of the buffer. * * If status is SPA_STATUS_OK, the host should ignore * the io area. * * If status is SPA_STATUS_NEED_DATA, the host should: * 1) recycle the buffer in buffer_id, if possible * 2) prepare a new buffer and place the id in buffer_id. * * If status is SPA_STATUS_HAVE_DATA, the host should consume * the buffer in buffer_id and set the state to * SPA_STATUS_NEED_DATA when new data is requested. * * If status is SPA_STATUS_STOPPED, some error occurred on the * port. * * If status is SPA_STATUS_DRAINED, data from the io area was * used to drain. * * Status can also be a negative errno value to indicate errors. * such as: * -EINVAL: buffer_id is invalid * -EPIPE: no more buffers available */ struct spa_io_buffers { #define SPA_STATUS_OK 0 #define SPA_STATUS_NEED_DATA (1<<0) #define SPA_STATUS_HAVE_DATA (1<<1) #define SPA_STATUS_STOPPED (1<<2) #define SPA_STATUS_DRAINED (1<<3) int32_t status; /**< the status code */ uint32_t buffer_id; /**< a buffer id */ }; #define SPA_IO_BUFFERS_INIT ((struct spa_io_buffers) { SPA_STATUS_OK, SPA_ID_INVALID, }) /** * IO area to exchange a memory region */ struct spa_io_memory { int32_t status; /**< the status code */ uint32_t size; /**< the size of \a data */ void *data; /**< a memory pointer */ }; #define SPA_IO_MEMORY_INIT ((struct spa_io_memory) { SPA_STATUS_OK, 0, NULL, }) /** A range, suitable for input ports that can suggest a range to output ports */ struct spa_io_range { uint64_t offset; /**< offset in range */ uint32_t min_size; /**< minimum size of data */ uint32_t max_size; /**< maximum size of data */ }; /** * Absolute time reporting. * * Nodes that can report clocking information will receive this io block. * The application sets the id. This is usually set as part of the * position information but can also be set separately. * * The clock counts the elapsed time according to the clock provider * since the provider was last started. * * Driver nodes are supposed to update the contents of \ref SPA_IO_Clock before * signaling the start of a graph cycle. These updated clock values become * visible to other nodes in \ref SPA_IO_Position. Non-driver nodes do * not need to update the contents of their \ref SPA_IO_Clock. Also * see \ref page_driver for further details. * * The host generally gives each node a separate \ref spa_io_clock in \ref * SPA_IO_Clock, so that updates made by the driver are not visible in the * contents of \ref SPA_IO_Clock of other nodes. Instead, \ref SPA_IO_Position * is used to look up the current graph time. * * A node is a driver when \ref spa_io_clock::id and the ID in * \ref spa_io_position.clock in \ref SPA_IO_Position are the same. * * The flags are set by the graph driver at the start of each cycle. */ struct spa_io_clock { #define SPA_IO_CLOCK_FLAG_FREEWHEEL (1u<<0) /**< Graph is freewheeling. It runs at the maximum * possible rate, only constrained by the processing * power of the machine it runs on. This can be useful * for offline processing, where processing in real * time is not desired. */ #define SPA_IO_CLOCK_FLAG_XRUN_RECOVER (1u<<1) /**< A node's process callback did not complete within * the last cycle's deadline, resulting in an xrun. * This flag is not set for the entire graph. Instead, * it is set at the start of the current cycle before * a node that experienced an xrun has its process * callback invoked. After said callback finished, the * flag is cleared again. That way, the node knows that * during the last cycle it experienced an xrun. They * can use this information for example to resynchronize * or clear custom stale states. */ #define SPA_IO_CLOCK_FLAG_LAZY (1u<<2) /**< The driver uses lazy scheduling. For details, see * \ref PW_KEY_NODE_SUPPORTS_LAZY . */ #define SPA_IO_CLOCK_FLAG_NO_RATE (1u<<3) /**< The rate of the clock is only approximately. * It is recommended to use the nsec as a clock source. * The rate_diff contains the measured inaccuracy. */ #define SPA_IO_CLOCK_FLAG_DISCONT (1u<<4) /**< The clock experienced a discontinuity in its * timestamps since the last cycle. If this is set, * nodes know that timestamps between the last and * the current cycle cannot be assumed to be * continuous. Nodes that synchronize playback against * clock timestamps should resynchronize (for example * by flushing buffers to avoid incorrect delays). * This differs from an xrun in that it is not necessariy * an error and that it is not caused by missed process * deadlines. If for example a custom network time * based driver starts to follow a different time * server, and the offset between that server and its * local clock consequently suddenly changes, then that * driver should set this flag. */ uint32_t flags; /**< Clock flags */ uint32_t id; /**< Unique clock id, set by host application */ char name[64]; /**< Clock name prefixed with API, set by node when it receives * \ref SPA_IO_Clock. The clock name is unique per clock and * can be used to check if nodes share the same clock. */ uint64_t nsec; /**< Time in nanoseconds against monotonic clock * (CLOCK_MONOTONIC). This fields reflects a real time instant * in the past, when the current cycle started. The value may * have jitter. */ struct spa_fraction rate; /**< Rate for position/duration/delay/xrun */ uint64_t position; /**< Current position, in samples @ \ref rate */ uint64_t duration; /**< Duration of current cycle, in samples @ \ref rate */ int64_t delay; /**< Delay between position and hardware, in samples @ \ref rate */ double rate_diff; /**< Rate difference between clock and monotonic time, as a ratio of * clock speeds. A value higher than 1.0 means that the driver's * internal clock is faster than the monotonic clock (by that * factor), and vice versa. */ uint64_t next_nsec; /**< Estimated next wakeup time in nanoseconds. * This time is a logical start time of the next cycle, and * is not necessarily in the future. */ struct spa_fraction target_rate; /**< Target rate of next cycle */ uint64_t target_duration; /**< Target duration of next cycle */ uint32_t target_seq; /**< Seq counter. must be equal at start and * end of read and lower bit must be 0 */ uint32_t cycle; /**< incremented each time the graph is started */ uint64_t xrun; /**< Estimated accumulated xrun duration */ }; /* the size of the video in this cycle */ struct spa_io_video_size { #define SPA_IO_VIDEO_SIZE_VALID (1<<0) uint32_t flags; /**< optional flags */ uint32_t stride; /**< video stride in bytes */ struct spa_rectangle size; /**< the video size */ struct spa_fraction framerate; /**< the minimum framerate, the cycle duration is * always smaller to ensure there is only one * video frame per cycle. */ uint32_t padding[4]; }; /** * Latency reporting * * Currently not used in PipeWire. Instead, \see spa_param_latency */ struct spa_io_latency { struct spa_fraction rate; /**< rate for min/max */ uint64_t min; /**< min latency */ uint64_t max; /**< max latency */ }; /** control stream, io area for SPA_IO_Control and SPA_IO_Notify */ struct spa_io_sequence { struct spa_pod_sequence sequence; /**< sequence of timed events */ }; /** bar and beat segment */ struct spa_io_segment_bar { #define SPA_IO_SEGMENT_BAR_FLAG_VALID (1<<0) uint32_t flags; /**< extra flags */ uint32_t offset; /**< offset in segment of this beat */ float signature_num; /**< time signature numerator */ float signature_denom; /**< time signature denominator */ double bpm; /**< beats per minute */ double beat; /**< current beat in segment */ double bar_start_tick; double ticks_per_beat; uint32_t padding[4]; }; /** video frame segment */ struct spa_io_segment_video { #define SPA_IO_SEGMENT_VIDEO_FLAG_VALID (1<<0) #define SPA_IO_SEGMENT_VIDEO_FLAG_DROP_FRAME (1<<1) #define SPA_IO_SEGMENT_VIDEO_FLAG_PULL_DOWN (1<<2) #define SPA_IO_SEGMENT_VIDEO_FLAG_INTERLACED (1<<3) uint32_t flags; /**< flags */ uint32_t offset; /**< offset in segment */ struct spa_fraction framerate; uint32_t hours; uint32_t minutes; uint32_t seconds; uint32_t frames; uint32_t field_count; /**< 0 for progressive, 1 and 2 for interlaced */ uint32_t padding[11]; }; /** * A segment converts a running time to a segment (stream) position. * * The segment position is valid when the current running time is between * start and start + duration. The position is then * calculated as: * * (running time - start) * rate + position; * * Support for looping is done by specifying the LOOPING flags with a * non-zero duration. When the running time reaches start + duration, * duration is added to start and the loop repeats. * * Care has to be taken when the running time + clock.duration extends * past the start + duration from the segment; the user should correctly * wrap around and partially repeat the loop in the current cycle. * * Extra information can be placed in the segment by setting the valid flags * and filling up the corresponding structures. */ struct spa_io_segment { uint32_t version; #define SPA_IO_SEGMENT_FLAG_LOOPING (1<<0) /**< after the duration, the segment repeats */ #define SPA_IO_SEGMENT_FLAG_NO_POSITION (1<<1) /**< position is invalid. The position can be invalid * after a seek, for example, when the exact mapping * of the extra segment info (bar, video, ...) to * position has not been determined yet */ uint32_t flags; /**< extra flags */ uint64_t start; /**< value of running time when this * info is active. Can be in the future for * pending changes. It does not have to be in * exact multiples of the clock duration. */ uint64_t duration; /**< duration when this info becomes invalid expressed * in running time. If the duration is 0, this * segment extends to the next segment. If the * segment becomes invalid and the looping flag is * set, the segment repeats. */ double rate; /**< overall rate of the segment, can be negative for * backwards time reporting. */ uint64_t position; /**< The position when the running time == start. * can be invalid when the owner of the extra segment * information has not yet made the mapping. */ struct spa_io_segment_bar bar; struct spa_io_segment_video video; }; enum spa_io_position_state { SPA_IO_POSITION_STATE_STOPPED, SPA_IO_POSITION_STATE_STARTING, SPA_IO_POSITION_STATE_RUNNING, }; /** the maximum number of segments visible in the future */ #define SPA_IO_POSITION_MAX_SEGMENTS 8 /** * The position information adds extra meaning to the raw clock times. * * It is set on all nodes in \ref SPA_IO_Position, and the contents of \ref * spa_io_position.clock contain the clock updates made by the driving node in * the graph in its \ref SPA_IO_Clock. Also, the ID in \ref spa_io_position.clock * will be the clock id of the driving node in the graph. * * The position clock indicates the logical start time of the current graph * cycle. * * The position information contains 1 or more segments that convert the * raw clock times to a stream time. They are sorted based on their * start times, and thus the order in which they will activate in * the future. This makes it possible to look ahead in the scheduled * segments and anticipate the changes in the timeline. */ struct spa_io_position { struct spa_io_clock clock; /**< clock position of driver, always valid and * read only */ struct spa_io_video_size video; /**< size of the video in the current cycle */ int64_t offset; /**< an offset to subtract from the clock position * to get a running time. This is the time that * the state has been in the RUNNING state and the * time that should be used to compare the segment * start values against. */ uint32_t state; /**< one of enum spa_io_position_state */ uint32_t n_segments; /**< number of segments */ struct spa_io_segment segments[SPA_IO_POSITION_MAX_SEGMENTS]; /**< segments */ }; /** * Rate matching. * * It is usually set on the nodes that process resampled data, by * the component (audioadapter) that handles resampling between graph * and node rates. The \a flags and \a rate fields may be modified by the node. * * The node can request a correction to the resampling rate in its process(), by setting * \ref SPA_IO_RATE_MATCH_FLAG_ACTIVE on \a flags, and setting \a rate to the desired rate * correction. Usually the rate is obtained from DLL or other adaptive mechanism that * e.g. drives the node buffer fill level toward a specific value. * * When resampling to (graph->node) direction, the number of samples produced * by the resampler varies on each cycle, as the rates are not commensurate. * * When resampling to (node->graph) direction, the number of samples consumed by the * resampler varies. Node output ports in process() should produce \a size number of * samples to match what the resampler needs to produce one graph quantum of output * samples. * * Resampling filters introduce processing delay, given by \a delay and \a delay_frac, in * samples at node rate. The delay varies on each cycle e.g. when resampling between * noncommensurate rates. * * The first sample output (graph->node) or consumed (node->graph) by the resampler is * offset by \a delay + \a delay_frac / 1e9 node samples relative to the nominal graph * cycle start position: * * \code{.unparsed} * first_resampled_sample_nsec = * first_original_sample_nsec * - (rate_match->delay * SPA_NSEC_PER_SEC + rate_match->delay_frac) / node_rate * \endcode */ struct spa_io_rate_match { uint32_t delay; /**< resampling delay, in samples at * node rate */ uint32_t size; /**< requested input size for resampler */ double rate; /**< rate for resampler (set by node) */ #define SPA_IO_RATE_MATCH_FLAG_ACTIVE (1 << 0) uint32_t flags; /**< extra flags (set by node) */ int32_t delay_frac; /**< resampling delay fractional part, * in units of nanosamples (1/10^9 sample) at node rate */ uint32_t padding[6]; }; /** async buffers */ struct spa_io_async_buffers { struct spa_io_buffers buffers[2]; /**< async buffers, writers write to current (cycle+1)&1, * readers read from (cycle)&1 */ }; /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_IO_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/node/keys.h000066400000000000000000000030151511204443500255370ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2019 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_NODE_KEYS_H #define SPA_NODE_KEYS_H #ifdef __cplusplus extern "C" { #endif /** * \addtogroup spa_node * \{ */ /** node keys */ #define SPA_KEY_NODE_NAME "node.name" /**< a node name */ #define SPA_KEY_NODE_DESCRIPTION "node.description" /**< localized human readable node one-line * description. Ex. "Foobar USB Headset" */ #define SPA_KEY_NODE_LATENCY "node.latency" /**< the requested node latency */ #define SPA_KEY_NODE_MAX_LATENCY "node.max-latency" /**< maximum supported latency */ #define SPA_KEY_NODE_DRIVER "node.driver" /**< the node can be a driver */ #define SPA_KEY_NODE_ALWAYS_PROCESS "node.always-process" /**< call the process function even if * not linked. */ #define SPA_KEY_NODE_PAUSE_ON_IDLE "node.pause-on-idle" /**< if the node should be paused * immediately when idle. */ #define SPA_KEY_NODE_MONITOR "node.monitor" /**< the node has monitor ports */ /** port keys */ #define SPA_KEY_PORT_NAME "port.name" /**< a port name */ #define SPA_KEY_PORT_ALIAS "port.alias" /**< a port alias */ #define SPA_KEY_PORT_MONITOR "port.monitor" /**< this port is a monitor port */ #define SPA_KEY_PORT_IGNORE_LATENCY "port.ignore-latency" /**< latency ignored by peers */ #define SPA_KEY_PORT_GROUP "port.group" /**< the port group this port belongs to */ /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_NODE_KEYS_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/node/node.h000066400000000000000000000632311511204443500255170ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_NODE_H #define SPA_NODE_H #include #include #include #include #include #include #include #ifdef __cplusplus extern "C" { #endif /** \defgroup spa_node Node * * A spa_node is a component that can consume and produce buffers. */ /** * \addtogroup spa_node * \{ */ #ifndef SPA_API_NODE #ifdef SPA_API_IMPL #define SPA_API_NODE SPA_API_IMPL #else #define SPA_API_NODE static inline #endif #endif #define SPA_TYPE_INTERFACE_Node SPA_TYPE_INFO_INTERFACE_BASE "Node" #define SPA_VERSION_NODE 0 struct spa_node { struct spa_interface iface; }; /** * Node information structure * * Contains the basic node information. */ struct spa_node_info { uint32_t max_input_ports; uint32_t max_output_ports; #define SPA_NODE_CHANGE_MASK_FLAGS (1u<<0) #define SPA_NODE_CHANGE_MASK_PROPS (1u<<1) #define SPA_NODE_CHANGE_MASK_PARAMS (1u<<2) uint64_t change_mask; #define SPA_NODE_FLAG_RT (1u<<0) /**< node can do real-time processing */ #define SPA_NODE_FLAG_IN_DYNAMIC_PORTS (1u<<1) /**< input ports can be added/removed */ #define SPA_NODE_FLAG_OUT_DYNAMIC_PORTS (1u<<2) /**< output ports can be added/removed */ #define SPA_NODE_FLAG_IN_PORT_CONFIG (1u<<3) /**< input ports can be reconfigured with * PortConfig parameter */ #define SPA_NODE_FLAG_OUT_PORT_CONFIG (1u<<4) /**< output ports can be reconfigured with * PortConfig parameter */ #define SPA_NODE_FLAG_NEED_CONFIGURE (1u<<5) /**< node needs configuration before it can * be started. */ #define SPA_NODE_FLAG_ASYNC (1u<<6) /**< the process function might not * immediately produce or consume data * but might offload the work to a worker * thread. */ uint64_t flags; struct spa_dict *props; /**< extra node properties */ struct spa_param_info *params; /**< parameter information */ uint32_t n_params; /**< number of items in \a params */ }; #define SPA_NODE_INFO_INIT() ((struct spa_node_info) { 0, }) /** * Port information structure * * Contains the basic port information. */ struct spa_port_info { #define SPA_PORT_CHANGE_MASK_FLAGS (1u<<0) #define SPA_PORT_CHANGE_MASK_RATE (1u<<1) #define SPA_PORT_CHANGE_MASK_PROPS (1u<<2) #define SPA_PORT_CHANGE_MASK_PARAMS (1u<<3) uint64_t change_mask; #define SPA_PORT_FLAG_REMOVABLE (1u<<0) /**< port can be removed */ #define SPA_PORT_FLAG_OPTIONAL (1u<<1) /**< processing on port is optional */ #define SPA_PORT_FLAG_CAN_ALLOC_BUFFERS (1u<<2) /**< the port can allocate buffer data */ #define SPA_PORT_FLAG_IN_PLACE (1u<<3) /**< the port can process data in-place and * will need a writable input buffer */ #define SPA_PORT_FLAG_NO_REF (1u<<4) /**< the port does not keep a ref on the buffer. * This means the node will always completely * consume the input buffer and it will be * recycled after process. */ #define SPA_PORT_FLAG_LIVE (1u<<5) /**< output buffers from this port are * timestamped against a live clock. */ #define SPA_PORT_FLAG_PHYSICAL (1u<<6) /**< connects to some device */ #define SPA_PORT_FLAG_TERMINAL (1u<<7) /**< data was not created from this port * or will not be made available on another * port */ #define SPA_PORT_FLAG_DYNAMIC_DATA (1u<<8) /**< data pointer on buffers can be changed. * Only the buffer data marked as DYNAMIC * can be changed. */ uint64_t flags; /**< port flags */ struct spa_fraction rate; /**< rate of sequence numbers on port */ const struct spa_dict *props; /**< extra port properties */ struct spa_param_info *params; /**< parameter information */ uint32_t n_params; /**< number of items in \a params */ }; #define SPA_PORT_INFO_INIT() ((struct spa_port_info) { 0, }) #define SPA_RESULT_TYPE_NODE_ERROR 1 #define SPA_RESULT_TYPE_NODE_PARAMS 2 /** an error result */ struct spa_result_node_error { const char *message; }; /** the result of enum_params or port_enum_params. */ struct spa_result_node_params { uint32_t id; /**< id of parameter */ uint32_t index; /**< index of parameter */ uint32_t next; /**< next index of iteration */ struct spa_pod *param; /**< the result param */ }; #define SPA_NODE_EVENT_INFO 0 #define SPA_NODE_EVENT_PORT_INFO 1 #define SPA_NODE_EVENT_RESULT 2 #define SPA_NODE_EVENT_EVENT 3 #define SPA_NODE_EVENT_NUM 4 /** events from the spa_node. * * All event are called from the main thread and multiple * listeners can be registered for the events with * spa_node_add_listener(). */ struct spa_node_events { #define SPA_VERSION_NODE_EVENTS 0 uint32_t version; /**< version of this structure */ /** Emitted when info changes */ void (*info) (void *data, const struct spa_node_info *info); /** Emitted when port info changes, NULL when port is removed */ void (*port_info) (void *data, enum spa_direction direction, uint32_t port, const struct spa_port_info *info); /** notify a result. * * Some methods will trigger a result event with an optional * result of the given type. Look at the documentation of the * method to know when to expect a result event. * * The result event can be called synchronously, as an event * called from inside the method itself, in which case the seq * number passed to the method will be passed unchanged. * * The result event will be called asynchronously when the * method returned an async return value. In this case, the seq * number in the result will match the async return value of * the method call. Users should match the seq number from * request to the reply. */ void (*result) (void *data, int seq, int res, uint32_t type, const void *result); /** * \param node a spa_node * \param event the event that was emitted * * This will be called when an out-of-bound event is notified * on \a node. */ void (*event) (void *data, const struct spa_event *event); }; #define SPA_NODE_CALLBACK_READY 0 #define SPA_NODE_CALLBACK_REUSE_BUFFER 1 #define SPA_NODE_CALLBACK_XRUN 2 #define SPA_NODE_CALLBACK_NUM 3 /** Node callbacks * * Callbacks are called from the real-time data thread. Only * one callback structure can be set on an spa_node. */ struct spa_node_callbacks { #define SPA_VERSION_NODE_CALLBACKS 0 uint32_t version; /** * \param node a spa_node * * The node is ready for processing. * * When this function is NULL, synchronous operation is requested * on the ports. */ int (*ready) (void *data, int state); /** * \param node a spa_node * \param port_id an input port_id * \param buffer_id the buffer id to be reused * * The node has a buffer that can be reused. * * When this function is NULL, the buffers to reuse will be set in * the io area of the input ports. */ int (*reuse_buffer) (void *data, uint32_t port_id, uint32_t buffer_id); /** * \param data user data * \param trigger the timestamp in microseconds when the xrun happened * \param delay the amount of microseconds of xrun. * \param info an object with extra info (NULL for now) * * The node has encountered an over or underrun * * The info contains an object with more information */ int (*xrun) (void *data, uint64_t trigger, uint64_t delay, struct spa_pod *info); }; /** flags that can be passed to set_param and port_set_param functions */ #define SPA_NODE_PARAM_FLAG_TEST_ONLY (1 << 0) /**< Just check if the param is accepted */ #define SPA_NODE_PARAM_FLAG_FIXATE (1 << 1) /**< Fixate the non-optional unset fields */ #define SPA_NODE_PARAM_FLAG_NEAREST (1 << 2) /**< Allow set fields to be rounded to the * nearest allowed field value. */ /** flags to pass to the use_buffers functions */ #define SPA_NODE_BUFFERS_FLAG_ALLOC (1 << 0) /**< Allocate memory for the buffers. This flag * is ignored when the port does not have the * SPA_PORT_FLAG_CAN_ALLOC_BUFFERS set. */ #define SPA_NODE_METHOD_ADD_LISTENER 0 #define SPA_NODE_METHOD_SET_CALLBACKS 1 #define SPA_NODE_METHOD_SYNC 2 #define SPA_NODE_METHOD_ENUM_PARAMS 3 #define SPA_NODE_METHOD_SET_PARAM 4 #define SPA_NODE_METHOD_SET_IO 5 #define SPA_NODE_METHOD_SEND_COMMAND 6 #define SPA_NODE_METHOD_ADD_PORT 7 #define SPA_NODE_METHOD_REMOVE_PORT 8 #define SPA_NODE_METHOD_PORT_ENUM_PARAMS 9 #define SPA_NODE_METHOD_PORT_SET_PARAM 10 #define SPA_NODE_METHOD_PORT_USE_BUFFERS 11 #define SPA_NODE_METHOD_PORT_SET_IO 12 #define SPA_NODE_METHOD_PORT_REUSE_BUFFER 13 #define SPA_NODE_METHOD_PROCESS 14 #define SPA_NODE_METHOD_NUM 15 /** * Node methods */ struct spa_node_methods { /* the version of the node methods. This can be used to expand this * structure in the future */ #define SPA_VERSION_NODE_METHODS 0 uint32_t version; /** * Adds an event listener on \a node. * * Setting the events will trigger the info event and a * port_info event for each managed port on the new * listener. * * \param node a #spa_node * \param listener a listener * \param events a struct \ref spa_node_events * \param data data passed as first argument in functions of \a events * \return 0 on success * < 0 errno on error */ int (*add_listener) (void *object, struct spa_hook *listener, const struct spa_node_events *events, void *data); /** * Set callbacks to on \a node. * if \a callbacks is NULL, the current callbacks are removed. * * This function must be called from the main thread. * * All callbacks are called from the data thread. * * \param node a spa_node * \param callbacks callbacks to set * \return 0 on success * -EINVAL when node is NULL */ int (*set_callbacks) (void *object, const struct spa_node_callbacks *callbacks, void *data); /** * Perform a sync operation. * * This method will emit the result event with the given sequence * number synchronously or with the returned async return value * asynchronously. * * Because all methods are serialized in the node, this can be used * to wait for completion of all previous method calls. * * \param seq a sequence number * \return 0 on success * -EINVAL when node is NULL * an async result */ int (*sync) (void *object, int seq); /** * Enumerate the parameters of a node. * * Parameters are identified with an \a id. Some parameters can have * multiple values, see the documentation of the parameter id. * * Parameters can be filtered by passing a non-NULL \a filter. * * The function will emit the result event up to \a max times with * the result value. The seq in the result will either be the \a seq * number when executed synchronously or the async return value of * this function when executed asynchronously. * * This function must be called from the main thread. * * \param node a \ref spa_node * \param seq a sequence number to pass to the result event when * this method is executed synchronously. * \param id the param id to enumerate * \param start the index of enumeration, pass 0 for the first item * \param max the maximum number of parameters to enumerate * \param filter and optional filter to use * * \return 0 when no more items can be iterated. * -EINVAL when invalid arguments are given * -ENOENT the parameter \a id is unknown * -ENOTSUP when there are no parameters * implemented on \a node * an async return value when the result event will be * emitted later. */ int (*enum_params) (void *object, int seq, uint32_t id, uint32_t start, uint32_t max, const struct spa_pod *filter); /** * Set the configurable parameter in \a node. * * Usually, \a param will be obtained from enum_params and then * modified but it is also possible to set another spa_pod * as long as its keys and types match a supported object. * * Objects with property keys that are not known are ignored. * * This function must be called from the main thread. * * \param node a \ref spa_node * \param id the parameter id to configure * \param flags additional flags * \param param the parameter to configure * * \return 0 on success * -EINVAL when node is NULL * -ENOTSUP when there are no parameters implemented on \a node * -ENOENT the parameter is unknown */ int (*set_param) (void *object, uint32_t id, uint32_t flags, const struct spa_pod *param); /** * Configure the given memory area with \a id on \a node. This * structure is allocated by the host and is used to exchange * data and parameters with the node. * * Setting an \a io of NULL will disable the node io. * * This function must be called from the main thread. * * \param id the id of the io area, the available ids can be * enumerated with the node parameters. * \param data a io area memory * \param size the size of \a data * \return 0 on success * -EINVAL when invalid input is given * -ENOENT when \a id is unknown * -ENOSPC when \a size is too small */ int (*set_io) (void *object, uint32_t id, void *data, size_t size); /** * Send a command to a node. * * Upon completion, a command might change the state of a node. * * This function must be called from the main thread. * * \param node a spa_node * \param command a spa_command * \return 0 on success * -EINVAL when node or command is NULL * -ENOTSUP when this node can't process commands * -EINVAL \a command is an invalid command */ int (*send_command) (void *object, const struct spa_command *command); /** * Make a new port with \a port_id. The caller should use the lowest unused * port id for the given \a direction. * * Port ids should be between 0 and max_ports as obtained from the info * event. * * This function must be called from the main thread. * * \param node a spa_node * \param direction a enum \ref spa_direction * \param port_id an unused port id * \param props extra properties * \return 0 on success * -EINVAL when node is NULL */ int (*add_port) (void *object, enum spa_direction direction, uint32_t port_id, const struct spa_dict *props); /** * Remove a port with \a port_id. * * \param node a spa_node * \param direction a enum \ref spa_direction * \param port_id a port id * \return 0 on success * -EINVAL when node is NULL or when port_id is unknown or * when the port can't be removed. */ int (*remove_port) (void *object, enum spa_direction direction, uint32_t port_id); /** * Enumerate all possible parameters of \a id on \a port_id of \a node * that are compatible with \a filter. * * When SPA_ID_INVALID is given as the port_id, the node will reply with * the params that would be returned for a new port in the given direction. * * The result parameters can be queried and modified and ultimately be used * to call port_set_param. * * The function will emit the result event up to \a max times with * the result value. The seq in the result event will either be the * \a seq number when executed synchronously or the async return * value of this function when executed asynchronously. * * This function must be called from the main thread. * * \param node a spa_node * \param seq a sequence number to pass to the result event when * this method is executed synchronously. * \param direction an spa_direction * \param port_id the port to query or SPA_ID_INVALID * \param id the parameter id to query * \param start the first index to query, 0 to get the first item * \param max the maximum number of params to query * \param filter a parameter filter or NULL for no filter * * \return 0 when no more items can be iterated. * -EINVAL when invalid parameters are given * -ENOENT when \a id is unknown * an async return value when the result event will be * emitted later. */ int (*port_enum_params) (void *object, int seq, enum spa_direction direction, uint32_t port_id, uint32_t id, uint32_t start, uint32_t max, const struct spa_pod *filter); /** * Set a parameter on \a port_id of \a node. * * When \a param is NULL, the parameter will be unset. * * This function must be called from the main thread. The node muse be paused * or the port SPA_IO_Buffers area is NULL when this function is called with * a param that changes the processing state (like a format change). * * \param node a struct \ref spa_node * \param direction a enum \ref spa_direction * \param port_id the port to configure * \param id the parameter id to set * \param flags optional flags * \param param a struct \ref spa_pod with the parameter to set * \return 0 on success * 1 on success, the value of \a param might have been * changed depending on \a flags and the final value can be found by * doing port_enum_params. * -EINVAL when node is NULL or invalid arguments are given * -ESRCH when one of the mandatory param * properties is not specified and SPA_NODE_PARAM_FLAG_FIXATE was * not set in \a flags. * -ESRCH when the type or size of a property is not correct. * -ENOENT when the param id is not found */ int (*port_set_param) (void *object, enum spa_direction direction, uint32_t port_id, uint32_t id, uint32_t flags, const struct spa_pod *param); /** * Tell the port to use the given buffers * * When \a flags contains SPA_NODE_BUFFERS_FLAG_ALLOC, the data * in the buffers should point to an array of at least 1 data entry * with the desired supported type that will be filled by this function. * * The port should also have a spa_io_buffers io area configured to exchange * the buffers with the port. * * For an input port, all the buffers will remain dequeued. * Once a buffer has been queued on a port in the spa_io_buffers, * it should not be reused until the reuse_buffer callback is notified * or when the buffer has been returned in the spa_io_buffers of * the port. * * For output ports, all buffers will be queued in the port. When process * returns SPA_STATUS_HAVE_DATA, buffers are available in one or more * of the spa_io_buffers areas. * * When a buffer can be reused, port_reuse_buffer() should be called or the * buffer_id should be placed in the spa_io_buffers area before calling * process. * * Passing NULL as \a buffers will remove the reference that the port has * on the buffers. * * When this function returns async, use the spa_node_sync operation to * wait for completion. * * This function must be called from the main thread. The node muse be paused * or the port SPA_IO_Buffers area is NULL when this function is called. * * \param object an object implementing the interface * \param direction a port direction * \param port_id a port id * \param flags extra flags * \param buffers an array of buffer pointers * \param n_buffers number of elements in \a buffers * \return 0 on success */ int (*port_use_buffers) (void *object, enum spa_direction direction, uint32_t port_id, uint32_t flags, struct spa_buffer **buffers, uint32_t n_buffers); /** * Configure the given memory area with \a id on \a port_id. This * structure is allocated by the host and is used to exchange * data and parameters with the port. * * Setting an \a io of NULL will disable the port io. * * This function must be called from the main thread. * * This function can be called when the node is running and the node * must be prepared to handle changes in io areas while running. This * is normally done by synchronizing the port io updates with the * data processing loop. * * \param direction a spa_direction * \param port_id a port id * \param id the id of the io area, the available ids can be * enumerated with the port parameters. * \param data a io area memory * \param size the size of \a data * \return 0 on success * -EINVAL when invalid input is given * -ENOENT when \a id is unknown * -ENOSPC when \a size is too small */ int (*port_set_io) (void *object, enum spa_direction direction, uint32_t port_id, uint32_t id, void *data, size_t size); /** * Tell an output port to reuse a buffer. * * This function must be called from the data thread. * * \param node a spa_node * \param port_id a port id * \param buffer_id a buffer id to reuse * \return 0 on success * -EINVAL when node is NULL */ int (*port_reuse_buffer) (void *object, uint32_t port_id, uint32_t buffer_id); /** * Process the node * * This function must be called from the data thread. * * Output io areas with SPA_STATUS_NEED_DATA will recycle the * buffers if any. * * Input areas with SPA_STATUS_HAVE_DATA are consumed if possible * and the status is set to SPA_STATUS_NEED_DATA or SPA_STATUS_OK. * * When the node has new output buffers, the SPA_STATUS_HAVE_DATA * bit will be set. * * When the node can accept new input in the next cycle, the * SPA_STATUS_NEED_DATA bit will be set. * * Note that the node might return SPA_STATUS_NEED_DATA even when * no input ports have this status. This means that the amount of * data still available on the input ports is likely not going to * be enough for the next cycle and the host might need to prefetch * data for the next cycle. */ int (*process) (void *object); }; SPA_API_NODE int spa_node_add_listener(struct spa_node *object, struct spa_hook *listener, const struct spa_node_events *events, void *data) { return spa_api_method_r(int, -ENOTSUP, spa_node, &object->iface, add_listener, 0, listener, events, data); } SPA_API_NODE int spa_node_set_callbacks(struct spa_node *object, const struct spa_node_callbacks *callbacks, void *data) { return spa_api_method_r(int, -ENOTSUP, spa_node, &object->iface, set_callbacks, 0, callbacks, data); } SPA_API_NODE int spa_node_sync(struct spa_node *object, int seq) { return spa_api_method_r(int, -ENOTSUP, spa_node, &object->iface, sync, 0, seq); } SPA_API_NODE int spa_node_enum_params(struct spa_node *object, int seq, uint32_t id, uint32_t start, uint32_t max, const struct spa_pod *filter) { return spa_api_method_r(int, -ENOTSUP, spa_node, &object->iface, enum_params, 0, seq, id, start, max, filter); } SPA_API_NODE int spa_node_set_param(struct spa_node *object, uint32_t id, uint32_t flags, const struct spa_pod *param) { return spa_api_method_r(int, -ENOTSUP, spa_node, &object->iface, set_param, 0, id, flags, param); } SPA_API_NODE int spa_node_set_io(struct spa_node *object, uint32_t id, void *data, size_t size) { return spa_api_method_r(int, -ENOTSUP, spa_node, &object->iface, set_io, 0, id, data, size); } SPA_API_NODE int spa_node_send_command(struct spa_node *object, const struct spa_command *command) { return spa_api_method_r(int, -ENOTSUP, spa_node, &object->iface, send_command, 0, command); } SPA_API_NODE int spa_node_add_port(struct spa_node *object, enum spa_direction direction, uint32_t port_id, const struct spa_dict *props) { return spa_api_method_r(int, -ENOTSUP, spa_node, &object->iface, add_port, 0, direction, port_id, props); } SPA_API_NODE int spa_node_remove_port(struct spa_node *object, enum spa_direction direction, uint32_t port_id) { return spa_api_method_r(int, -ENOTSUP, spa_node, &object->iface, remove_port, 0, direction, port_id); } SPA_API_NODE int spa_node_port_enum_params(struct spa_node *object, int seq, enum spa_direction direction, uint32_t port_id, uint32_t id, uint32_t start, uint32_t max, const struct spa_pod *filter) { return spa_api_method_r(int, -ENOTSUP, spa_node, &object->iface, port_enum_params, 0, seq, direction, port_id, id, start, max, filter); } SPA_API_NODE int spa_node_port_set_param(struct spa_node *object, enum spa_direction direction, uint32_t port_id, uint32_t id, uint32_t flags, const struct spa_pod *param) { return spa_api_method_r(int, -ENOTSUP, spa_node, &object->iface, port_set_param, 0, direction, port_id, id, flags, param); } SPA_API_NODE int spa_node_port_use_buffers(struct spa_node *object, enum spa_direction direction, uint32_t port_id, uint32_t flags, struct spa_buffer **buffers, uint32_t n_buffers) { return spa_api_method_r(int, -ENOTSUP, spa_node, &object->iface, port_use_buffers, 0, direction, port_id, flags, buffers, n_buffers); } SPA_API_NODE int spa_node_port_set_io(struct spa_node *object, enum spa_direction direction, uint32_t port_id, uint32_t id, void *data, size_t size) { return spa_api_method_r(int, -ENOTSUP, spa_node, &object->iface, port_set_io, 0, direction, port_id, id, data, size); } SPA_API_NODE int spa_node_port_reuse_buffer(struct spa_node *object, uint32_t port_id, uint32_t buffer_id) { return spa_api_method_r(int, -ENOTSUP, spa_node, &object->iface, port_reuse_buffer, 0, port_id, buffer_id); } SPA_API_NODE int spa_node_port_reuse_buffer_fast(struct spa_node *object, uint32_t port_id, uint32_t buffer_id) { return spa_api_method_fast_r(int, -ENOTSUP, spa_node, &object->iface, port_reuse_buffer, 0, port_id, buffer_id); } SPA_API_NODE int spa_node_process(struct spa_node *object) { return spa_api_method_r(int, -ENOTSUP, spa_node, &object->iface, process, 0); } SPA_API_NODE int spa_node_process_fast(struct spa_node *object) { return spa_api_method_fast_r(int, -ENOTSUP, spa_node, &object->iface, process, 0); } /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_NODE_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/node/type-info.h000066400000000000000000000102351511204443500265000ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_NODE_TYPES_H #define SPA_NODE_TYPES_H #include #include #include #include #ifdef __cplusplus extern "C" { #endif /** * \addtogroup spa_node * \{ */ #define SPA_TYPE_INFO_IO SPA_TYPE_INFO_ENUM_BASE "IO" #define SPA_TYPE_INFO_IO_BASE SPA_TYPE_INFO_IO ":" static const struct spa_type_info spa_type_io[] = { { SPA_IO_Invalid, SPA_TYPE_Int, SPA_TYPE_INFO_IO_BASE "Invalid", NULL }, { SPA_IO_Buffers, SPA_TYPE_Int, SPA_TYPE_INFO_IO_BASE "Buffers", NULL }, { SPA_IO_Range, SPA_TYPE_Int, SPA_TYPE_INFO_IO_BASE "Range", NULL }, { SPA_IO_Clock, SPA_TYPE_Int, SPA_TYPE_INFO_IO_BASE "Clock", NULL }, { SPA_IO_Latency, SPA_TYPE_Int, SPA_TYPE_INFO_IO_BASE "Latency", NULL }, { SPA_IO_Control, SPA_TYPE_Int, SPA_TYPE_INFO_IO_BASE "Control", NULL }, { SPA_IO_Notify, SPA_TYPE_Int, SPA_TYPE_INFO_IO_BASE "Notify", NULL }, { SPA_IO_Position, SPA_TYPE_Int, SPA_TYPE_INFO_IO_BASE "Position", NULL }, { SPA_IO_RateMatch, SPA_TYPE_Int, SPA_TYPE_INFO_IO_BASE "RateMatch", NULL }, { SPA_IO_Memory, SPA_TYPE_Int, SPA_TYPE_INFO_IO_BASE "Memory", NULL }, { SPA_IO_AsyncBuffers, SPA_TYPE_Int, SPA_TYPE_INFO_IO_BASE "AsyncBuffers", NULL }, { 0, 0, NULL, NULL }, }; #define SPA_TYPE_INFO_NodeEvent SPA_TYPE_INFO_EVENT_BASE "Node" #define SPA_TYPE_INFO_NODE_EVENT_BASE SPA_TYPE_INFO_NodeEvent ":" static const struct spa_type_info spa_type_node_event_id[] = { { SPA_NODE_EVENT_Error, SPA_TYPE_EVENT_Node, SPA_TYPE_INFO_NODE_EVENT_BASE "Error", NULL }, { SPA_NODE_EVENT_Buffering, SPA_TYPE_EVENT_Node, SPA_TYPE_INFO_NODE_EVENT_BASE "Buffering", NULL }, { SPA_NODE_EVENT_RequestRefresh, SPA_TYPE_EVENT_Node, SPA_TYPE_INFO_NODE_EVENT_BASE "RequestRefresh", NULL }, { SPA_NODE_EVENT_RequestProcess, SPA_TYPE_EVENT_Node, SPA_TYPE_INFO_NODE_EVENT_BASE "RequestProcess", NULL }, { SPA_NODE_EVENT_User, SPA_TYPE_EVENT_Node, SPA_TYPE_INFO_NODE_EVENT_BASE "User", NULL }, { 0, 0, NULL, NULL }, }; static const struct spa_type_info spa_type_node_event[] = { { SPA_EVENT_NODE_START, SPA_TYPE_Id, SPA_TYPE_INFO_NODE_EVENT_BASE, spa_type_node_event_id }, { SPA_EVENT_NODE_extra, SPA_TYPE_String, SPA_TYPE_INFO_NODE_EVENT_BASE "extra", NULL }, { 0, 0, NULL, NULL }, }; #define SPA_TYPE_INFO_NodeCommand SPA_TYPE_INFO_COMMAND_BASE "Node" #define SPA_TYPE_INFO_NODE_COMMAND_BASE SPA_TYPE_INFO_NodeCommand ":" static const struct spa_type_info spa_type_node_command_id[] = { { SPA_NODE_COMMAND_Suspend, SPA_TYPE_COMMAND_Node, SPA_TYPE_INFO_NODE_COMMAND_BASE "Suspend", NULL }, { SPA_NODE_COMMAND_Pause, SPA_TYPE_COMMAND_Node, SPA_TYPE_INFO_NODE_COMMAND_BASE "Pause", NULL }, { SPA_NODE_COMMAND_Start, SPA_TYPE_COMMAND_Node, SPA_TYPE_INFO_NODE_COMMAND_BASE "Start", NULL }, { SPA_NODE_COMMAND_Enable, SPA_TYPE_COMMAND_Node, SPA_TYPE_INFO_NODE_COMMAND_BASE "Enable", NULL }, { SPA_NODE_COMMAND_Disable, SPA_TYPE_COMMAND_Node, SPA_TYPE_INFO_NODE_COMMAND_BASE "Disable", NULL }, { SPA_NODE_COMMAND_Flush, SPA_TYPE_COMMAND_Node, SPA_TYPE_INFO_NODE_COMMAND_BASE "Flush", NULL }, { SPA_NODE_COMMAND_Drain, SPA_TYPE_COMMAND_Node, SPA_TYPE_INFO_NODE_COMMAND_BASE "Drain", NULL }, { SPA_NODE_COMMAND_Marker, SPA_TYPE_COMMAND_Node, SPA_TYPE_INFO_NODE_COMMAND_BASE "Marker", NULL }, { SPA_NODE_COMMAND_ParamBegin, SPA_TYPE_COMMAND_Node, SPA_TYPE_INFO_NODE_COMMAND_BASE "ParamBegin", NULL }, { SPA_NODE_COMMAND_ParamEnd, SPA_TYPE_COMMAND_Node, SPA_TYPE_INFO_NODE_COMMAND_BASE "ParamEnd", NULL }, { SPA_NODE_COMMAND_RequestProcess, SPA_TYPE_COMMAND_Node, SPA_TYPE_INFO_NODE_COMMAND_BASE "RequestProcess", NULL }, { SPA_NODE_COMMAND_User, SPA_TYPE_COMMAND_Node, SPA_TYPE_INFO_NODE_COMMAND_BASE "User", NULL }, { 0, 0, NULL, NULL }, }; static const struct spa_type_info spa_type_node_command[] = { { SPA_COMMAND_NODE_START, SPA_TYPE_Id, SPA_TYPE_INFO_NODE_COMMAND_BASE, spa_type_node_command_id }, { SPA_COMMAND_NODE_extra, SPA_TYPE_String, SPA_TYPE_INFO_NODE_COMMAND_BASE "extra", NULL }, { 0, 0, NULL, NULL }, }; /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_NODE_TYPES_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/node/utils.h000066400000000000000000000075461511204443500257410ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2019 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_NODE_UTILS_H #define SPA_NODE_UTILS_H #include #include #ifdef __cplusplus extern "C" { #endif /** * \addtogroup spa_node * \{ */ #ifndef SPA_API_NODE_UTILS #ifdef SPA_API_IMPL #define SPA_API_NODE_UTILS SPA_API_IMPL #else #define SPA_API_NODE_UTILS static inline #endif #endif struct spa_result_node_params_data { struct spa_pod_builder *builder; struct spa_result_node_params data; }; SPA_API_NODE_UTILS void spa_result_func_node_params(void *data, int seq SPA_UNUSED, int res SPA_UNUSED, uint32_t type SPA_UNUSED, const void *result) { struct spa_result_node_params_data *d = (struct spa_result_node_params_data *) data; const struct spa_result_node_params *r = (const struct spa_result_node_params *) result; uint32_t offset = d->builder->state.offset; if (spa_pod_builder_raw_padded(d->builder, r->param, SPA_POD_SIZE(r->param)) < 0) return; d->data.next = r->next; d->data.param = spa_pod_builder_deref(d->builder, offset); } SPA_API_NODE_UTILS int spa_node_enum_params_sync(struct spa_node *node, uint32_t id, uint32_t *index, const struct spa_pod *filter, struct spa_pod **param, struct spa_pod_builder *builder) { struct spa_result_node_params_data data = { builder, {0}}; struct spa_hook listener = {{0}, {0}, 0, 0}; static const struct spa_node_events node_events = { .version = SPA_VERSION_NODE_EVENTS, .info = NULL, .port_info = NULL, .result = spa_result_func_node_params, }; int res; res = spa_node_add_listener(node, &listener, &node_events, &data); if (res >= 0) { res = spa_node_enum_params(node, 0, id, *index, 1, filter); spa_hook_remove(&listener); } if (data.data.param == NULL) { if (res > 0) res = 0; } else { *index = data.data.next; *param = data.data.param; res = 1; } return res; } SPA_API_NODE_UTILS int spa_node_port_enum_params_sync(struct spa_node *node, enum spa_direction direction, uint32_t port_id, uint32_t id, uint32_t *index, const struct spa_pod *filter, struct spa_pod **param, struct spa_pod_builder *builder) { struct spa_result_node_params_data data = { builder, {0}}; struct spa_hook listener = {{0}, {0}, 0, 0}; static const struct spa_node_events node_events = { .version = SPA_VERSION_NODE_EVENTS, .info = NULL, .port_info = NULL, .result = spa_result_func_node_params, }; int res; res = spa_node_add_listener(node, &listener, &node_events, &data); if (res >= 0) { res = spa_node_port_enum_params(node, 0, direction, port_id, id, *index, 1, filter); spa_hook_remove(&listener); } if (data.data.param == NULL) { if (res > 0) res = 0; } else { *index = data.data.next; *param = data.data.param; res = 1; } return res; } #define spa_node_emit(hooks,method,version,...) \ spa_hook_list_call_simple(hooks, struct spa_node_events, \ method, version, ##__VA_ARGS__) #define spa_node_emit_info(hooks,...) spa_node_emit(hooks,info, 0, __VA_ARGS__) #define spa_node_emit_port_info(hooks,...) spa_node_emit(hooks,port_info, 0, __VA_ARGS__) #define spa_node_emit_result(hooks,...) spa_node_emit(hooks,result, 0, __VA_ARGS__) #define spa_node_emit_event(hooks,...) spa_node_emit(hooks,event, 0, __VA_ARGS__) #define spa_node_call(callbacks,method,version,...) \ ({ \ int _res; \ spa_callbacks_call_fast_res(callbacks, struct spa_node_callbacks, \ _res, method, version, ##__VA_ARGS__); \ _res; \ }) #define spa_node_call_ready(hook,...) spa_node_call(hook, ready, 0, __VA_ARGS__) #define spa_node_call_reuse_buffer(hook,...) spa_node_call(hook, reuse_buffer, 0, __VA_ARGS__) #define spa_node_call_xrun(hook,...) spa_node_call(hook, xrun, 0, __VA_ARGS__) /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_NODE_UTILS_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/param/000077500000000000000000000000001511204443500245675ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/param/audio/000077500000000000000000000000001511204443500256705ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/param/audio/aac-types.h000066400000000000000000000031141511204443500277260ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_AUDIO_AAC_TYPES_H #define SPA_AUDIO_AAC_TYPES_H #include #include #ifdef __cplusplus extern "C" { #endif /** * \addtogroup spa_param * \{ */ #define SPA_TYPE_INFO_AudioAACStreamFormat SPA_TYPE_INFO_ENUM_BASE "AudioAACStreamFormat" #define SPA_TYPE_INFO_AUDIO_AAC_STREAM_FORMAT_BASE SPA_TYPE_INFO_AudioAACStreamFormat ":" static const struct spa_type_info spa_type_audio_aac_stream_format[] = { { SPA_AUDIO_AAC_STREAM_FORMAT_UNKNOWN, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_AAC_STREAM_FORMAT_BASE "UNKNOWN", NULL }, { SPA_AUDIO_AAC_STREAM_FORMAT_RAW, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_AAC_STREAM_FORMAT_BASE "RAW", NULL }, { SPA_AUDIO_AAC_STREAM_FORMAT_MP2ADTS, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_AAC_STREAM_FORMAT_BASE "MP2ADTS", NULL }, { SPA_AUDIO_AAC_STREAM_FORMAT_MP4ADTS, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_AAC_STREAM_FORMAT_BASE "MP4ADTS", NULL }, { SPA_AUDIO_AAC_STREAM_FORMAT_MP4LOAS, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_AAC_STREAM_FORMAT_BASE "MP4LOAS", NULL }, { SPA_AUDIO_AAC_STREAM_FORMAT_MP4LATM, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_AAC_STREAM_FORMAT_BASE "MP4LATM", NULL }, { SPA_AUDIO_AAC_STREAM_FORMAT_ADIF, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_AAC_STREAM_FORMAT_BASE "ADIF", NULL }, { SPA_AUDIO_AAC_STREAM_FORMAT_MP4FF, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_AAC_STREAM_FORMAT_BASE "MP4FF", NULL }, { 0, 0, NULL, NULL }, }; /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_AUDIO_AAC_TYPES_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/param/audio/aac-utils.h000066400000000000000000000041631511204443500277270ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2023 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_AUDIO_AAC_UTILS_H #define SPA_AUDIO_AAC_UTILS_H #include #include #include #include #ifdef __cplusplus extern "C" { #endif /** * \addtogroup spa_param * \{ */ #ifndef SPA_API_AUDIO_AAC_UTILS #ifdef SPA_API_IMPL #define SPA_API_AUDIO_AAC_UTILS SPA_API_IMPL #else #define SPA_API_AUDIO_AAC_UTILS static inline #endif #endif SPA_API_AUDIO_AAC_UTILS int spa_format_audio_aac_parse(const struct spa_pod *format, struct spa_audio_info_aac *info) { int res; res = spa_pod_parse_object(format, SPA_TYPE_OBJECT_Format, NULL, SPA_FORMAT_AUDIO_rate, SPA_POD_OPT_Int(&info->rate), SPA_FORMAT_AUDIO_channels, SPA_POD_OPT_Int(&info->channels), SPA_FORMAT_AUDIO_bitrate, SPA_POD_OPT_Int(&info->bitrate), SPA_FORMAT_AUDIO_AAC_streamFormat, SPA_POD_OPT_Id(&info->stream_format)); return res; } SPA_API_AUDIO_AAC_UTILS struct spa_pod * spa_format_audio_aac_build(struct spa_pod_builder *builder, uint32_t id, const struct spa_audio_info_aac *info) { struct spa_pod_frame f; spa_pod_builder_push_object(builder, &f, SPA_TYPE_OBJECT_Format, id); spa_pod_builder_add(builder, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_audio), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_aac), SPA_FORMAT_AUDIO_format, SPA_POD_Id(SPA_AUDIO_FORMAT_ENCODED), 0); if (info->rate != 0) spa_pod_builder_add(builder, SPA_FORMAT_AUDIO_rate, SPA_POD_Int(info->rate), 0); if (info->channels != 0) spa_pod_builder_add(builder, SPA_FORMAT_AUDIO_channels, SPA_POD_Int(info->channels), 0); if (info->bitrate != 0) spa_pod_builder_add(builder, SPA_FORMAT_AUDIO_bitrate, SPA_POD_Int(info->bitrate), 0); if (info->stream_format != 0) spa_pod_builder_add(builder, SPA_FORMAT_AUDIO_AAC_streamFormat, SPA_POD_Id(info->stream_format), 0); return (struct spa_pod*)spa_pod_builder_pop(builder, &f); } /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_AUDIO_AAC_UTILS_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/param/audio/aac.h000066400000000000000000000027001511204443500265640ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2023 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_AUDIO_AAC_H #define SPA_AUDIO_AAC_H #include #ifdef __cplusplus extern "C" { #endif /** * \addtogroup spa_param * \{ */ enum spa_audio_aac_stream_format { SPA_AUDIO_AAC_STREAM_FORMAT_UNKNOWN, /** Raw AAC frames */ SPA_AUDIO_AAC_STREAM_FORMAT_RAW, /** ISO/IEC 13818-7 MPEG-2 Audio Data Transport Stream (ADTS) */ SPA_AUDIO_AAC_STREAM_FORMAT_MP2ADTS, /** ISO/IEC 14496-3 MPEG-4 Audio Data Transport Stream (ADTS) */ SPA_AUDIO_AAC_STREAM_FORMAT_MP4ADTS, /** ISO/IEC 14496-3 Low Overhead Audio Stream (LOAS) */ SPA_AUDIO_AAC_STREAM_FORMAT_MP4LOAS, /** ISO/IEC 14496-3 Low Overhead Audio Transport Multiplex (LATM) */ SPA_AUDIO_AAC_STREAM_FORMAT_MP4LATM, /** ISO/IEC 14496-3 Audio Data Interchange Format (ADIF) */ SPA_AUDIO_AAC_STREAM_FORMAT_ADIF, /** ISO/IEC 14496-12 MPEG-4 file format */ SPA_AUDIO_AAC_STREAM_FORMAT_MP4FF, SPA_AUDIO_AAC_STREAM_FORMAT_CUSTOM = 0x10000, }; struct spa_audio_info_aac { uint32_t rate; /*< sample rate */ uint32_t channels; /*< number of channels */ uint32_t bitrate; /*< stream bitrate */ enum spa_audio_aac_stream_format stream_format; /*< AAC audio stream format */ }; #define SPA_AUDIO_INFO_AAC_INIT(...) ((struct spa_audio_info_aac) { __VA_ARGS__ }) /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_AUDIO_AAC_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/param/audio/ac3-utils.h000066400000000000000000000033621511204443500276510ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2025 Carlos Rafael Giani */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_AUDIO_AC3_UTILS_H #define SPA_AUDIO_AC3_UTILS_H #include #include #include #include #ifdef __cplusplus extern "C" { #endif /** * \addtogroup spa_param * \{ */ #ifndef SPA_API_AUDIO_AC3_UTILS #ifdef SPA_API_IMPL #define SPA_API_AUDIO_AC3_UTILS SPA_API_IMPL #else #define SPA_API_AUDIO_AC3_UTILS static inline #endif #endif SPA_API_AUDIO_AC3_UTILS int spa_format_audio_ac3_parse(const struct spa_pod *format, struct spa_audio_info_ac3 *info) { int res; res = spa_pod_parse_object(format, SPA_TYPE_OBJECT_Format, NULL, SPA_FORMAT_AUDIO_rate, SPA_POD_OPT_Int(&info->rate), SPA_FORMAT_AUDIO_channels, SPA_POD_OPT_Int(&info->channels)); return res; } SPA_API_AUDIO_AC3_UTILS struct spa_pod * spa_format_audio_ac3_build(struct spa_pod_builder *builder, uint32_t id, const struct spa_audio_info_ac3 *info) { struct spa_pod_frame f; spa_pod_builder_push_object(builder, &f, SPA_TYPE_OBJECT_Format, id); spa_pod_builder_add(builder, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_audio), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_ac3), SPA_FORMAT_AUDIO_format, SPA_POD_Id(SPA_AUDIO_FORMAT_ENCODED), 0); if (info->rate != 0) spa_pod_builder_add(builder, SPA_FORMAT_AUDIO_rate, SPA_POD_Int(info->rate), 0); if (info->channels != 0) spa_pod_builder_add(builder, SPA_FORMAT_AUDIO_channels, SPA_POD_Int(info->channels), 0); return (struct spa_pod*)spa_pod_builder_pop(builder, &f); } /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_AUDIO_AC3_UTILS_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/param/audio/ac3.h000066400000000000000000000011471511204443500265120ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2025 Carlos Rafael Giani */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_AUDIO_AC3_H #define SPA_AUDIO_AC3_H #include #ifdef __cplusplus extern "C" { #endif /** * \addtogroup spa_param * \{ */ /** Dolby AC-3 audio info. */ struct spa_audio_info_ac3 { uint32_t rate; /*< sample rate */ uint32_t channels; /*< number of channels */ }; #define SPA_AUDIO_INFO_AC3_INIT(...) ((struct spa_audio_info_ac3) { __VA_ARGS__ }) /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_AUDIO_AC3_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/param/audio/alac-utils.h000066400000000000000000000033711511204443500301030ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2023 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_AUDIO_ALAC_UTILS_H #define SPA_AUDIO_ALAC_UTILS_H #include #include #include #include #ifdef __cplusplus extern "C" { #endif /** * \addtogroup spa_param * \{ */ #ifndef SPA_API_AUDIO_ALAC_UTILS #ifdef SPA_API_IMPL #define SPA_API_AUDIO_ALAC_UTILS SPA_API_IMPL #else #define SPA_API_AUDIO_ALAC_UTILS static inline #endif #endif SPA_API_AUDIO_ALAC_UTILS int spa_format_audio_alac_parse(const struct spa_pod *format, struct spa_audio_info_alac *info) { int res; res = spa_pod_parse_object(format, SPA_TYPE_OBJECT_Format, NULL, SPA_FORMAT_AUDIO_rate, SPA_POD_OPT_Int(&info->rate), SPA_FORMAT_AUDIO_channels, SPA_POD_OPT_Int(&info->channels)); return res; } SPA_API_AUDIO_ALAC_UTILS struct spa_pod * spa_format_audio_alac_build(struct spa_pod_builder *builder, uint32_t id, const struct spa_audio_info_alac *info) { struct spa_pod_frame f; spa_pod_builder_push_object(builder, &f, SPA_TYPE_OBJECT_Format, id); spa_pod_builder_add(builder, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_audio), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_alac), SPA_FORMAT_AUDIO_format, SPA_POD_Id(SPA_AUDIO_FORMAT_ENCODED), 0); if (info->rate != 0) spa_pod_builder_add(builder, SPA_FORMAT_AUDIO_rate, SPA_POD_Int(info->rate), 0); if (info->channels != 0) spa_pod_builder_add(builder, SPA_FORMAT_AUDIO_channels, SPA_POD_Int(info->channels), 0); return (struct spa_pod*)spa_pod_builder_pop(builder, &f); } /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_AUDIO_ALAC_UTILS_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/param/audio/alac.h000066400000000000000000000011071511204443500267400ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2023 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_AUDIO_ALAC_H #define SPA_AUDIO_ALAC_H #include #ifdef __cplusplus extern "C" { #endif /** * \addtogroup spa_param * \{ */ struct spa_audio_info_alac { uint32_t rate; /*< sample rate */ uint32_t channels; /*< number of channels */ }; #define SPA_AUDIO_INFO_ALAC_INIT(...) ((struct spa_audio_info_alac) { __VA_ARGS__ }) /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_AUDIO_ALAC_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/param/audio/amr-types.h000066400000000000000000000017311511204443500277640ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_AUDIO_AMR_TYPES_H #define SPA_AUDIO_AMR_TYPES_H #include #include #ifdef __cplusplus extern "C" { #endif /** * \addtogroup spa_param * \{ */ #define SPA_TYPE_INFO_AudioAMRBandMode SPA_TYPE_INFO_ENUM_BASE "AudioAMRBandMode" #define SPA_TYPE_INFO_AUDIO_AMR_BAND_MODE_BASE SPA_TYPE_INFO_AudioAMRBandMode ":" static const struct spa_type_info spa_type_audio_amr_band_mode[] = { { SPA_AUDIO_AMR_BAND_MODE_UNKNOWN, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_AMR_BAND_MODE_BASE "UNKNOWN", NULL }, { SPA_AUDIO_AMR_BAND_MODE_NB, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_AMR_BAND_MODE_BASE "NB", NULL }, { SPA_AUDIO_AMR_BAND_MODE_WB, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_AMR_BAND_MODE_BASE "WB", NULL }, { 0, 0, NULL, NULL }, }; /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_AUDIO_AMR_TYPES_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/param/audio/amr-utils.h000066400000000000000000000036541511204443500277660ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2023 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_AUDIO_AMR_UTILS_H #define SPA_AUDIO_AMR_UTILS_H #include #include #include #include #ifdef __cplusplus extern "C" { #endif /** * \addtogroup spa_param * \{ */ #ifndef SPA_API_AUDIO_AMR_UTILS #ifdef SPA_API_IMPL #define SPA_API_AUDIO_AMR_UTILS SPA_API_IMPL #else #define SPA_API_AUDIO_AMR_UTILS static inline #endif #endif SPA_API_AUDIO_AMR_UTILS int spa_format_audio_amr_parse(const struct spa_pod *format, struct spa_audio_info_amr *info) { int res; res = spa_pod_parse_object(format, SPA_TYPE_OBJECT_Format, NULL, SPA_FORMAT_AUDIO_rate, SPA_POD_OPT_Int(&info->rate), SPA_FORMAT_AUDIO_channels, SPA_POD_OPT_Int(&info->channels), SPA_FORMAT_AUDIO_AMR_bandMode, SPA_POD_OPT_Id(&info->band_mode)); return res; } SPA_API_AUDIO_AMR_UTILS struct spa_pod * spa_format_audio_amr_build(struct spa_pod_builder *builder, uint32_t id, const struct spa_audio_info_amr *info) { struct spa_pod_frame f; spa_pod_builder_push_object(builder, &f, SPA_TYPE_OBJECT_Format, id); spa_pod_builder_add(builder, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_audio), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_amr), SPA_FORMAT_AUDIO_format, SPA_POD_Id(SPA_AUDIO_FORMAT_ENCODED), 0); if (info->rate != 0) spa_pod_builder_add(builder, SPA_FORMAT_AUDIO_rate, SPA_POD_Int(info->rate), 0); if (info->channels != 0) spa_pod_builder_add(builder, SPA_FORMAT_AUDIO_channels, SPA_POD_Int(info->channels), 0); if (info->band_mode != 0) spa_pod_builder_add(builder, SPA_FORMAT_AUDIO_AMR_bandMode, SPA_POD_Id(info->band_mode), 0); return (struct spa_pod*)spa_pod_builder_pop(builder, &f); } /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_AUDIO_AMR_UTILS_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/param/audio/amr.h000066400000000000000000000013511511204443500266200ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2023 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_AUDIO_AMR_H #define SPA_AUDIO_AMR_H #include #ifdef __cplusplus extern "C" { #endif /** * \addtogroup spa_param * \{ */ enum spa_audio_amr_band_mode { SPA_AUDIO_AMR_BAND_MODE_UNKNOWN, SPA_AUDIO_AMR_BAND_MODE_NB, SPA_AUDIO_AMR_BAND_MODE_WB, }; struct spa_audio_info_amr { uint32_t rate; /*< sample rate */ uint32_t channels; /*< number of channels */ enum spa_audio_amr_band_mode band_mode; }; #define SPA_AUDIO_INFO_AMR_INIT(...) ((struct spa_audio_info_amr) { __VA_ARGS__ }) /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_AUDIO_AMR_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/param/audio/ape-utils.h000066400000000000000000000033521511204443500277470ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2023 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_AUDIO_APE_UTILS_H #define SPA_AUDIO_APE_UTILS_H #include #include #include #include #ifdef __cplusplus extern "C" { #endif /** * \addtogroup spa_param * \{ */ #ifndef SPA_API_AUDIO_APE_UTILS #ifdef SPA_API_IMPL #define SPA_API_AUDIO_APE_UTILS SPA_API_IMPL #else #define SPA_API_AUDIO_APE_UTILS static inline #endif #endif SPA_API_AUDIO_APE_UTILS int spa_format_audio_ape_parse(const struct spa_pod *format, struct spa_audio_info_ape *info) { int res; res = spa_pod_parse_object(format, SPA_TYPE_OBJECT_Format, NULL, SPA_FORMAT_AUDIO_rate, SPA_POD_OPT_Int(&info->rate), SPA_FORMAT_AUDIO_channels, SPA_POD_OPT_Int(&info->channels)); return res; } SPA_API_AUDIO_APE_UTILS struct spa_pod * spa_format_audio_ape_build(struct spa_pod_builder *builder, uint32_t id, const struct spa_audio_info_ape *info) { struct spa_pod_frame f; spa_pod_builder_push_object(builder, &f, SPA_TYPE_OBJECT_Format, id); spa_pod_builder_add(builder, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_audio), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_ape), SPA_FORMAT_AUDIO_format, SPA_POD_Id(SPA_AUDIO_FORMAT_ENCODED), 0); if (info->rate != 0) spa_pod_builder_add(builder, SPA_FORMAT_AUDIO_rate, SPA_POD_Int(info->rate), 0); if (info->channels != 0) spa_pod_builder_add(builder, SPA_FORMAT_AUDIO_channels, SPA_POD_Int(info->channels), 0); return (struct spa_pod*)spa_pod_builder_pop(builder, &f); } /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_AUDIO_APE_UTILS_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/param/audio/ape.h000066400000000000000000000011011511204443500265770ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2023 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_AUDIO_APE_H #define SPA_AUDIO_APE_H #include #ifdef __cplusplus extern "C" { #endif /** * \addtogroup spa_param * \{ */ struct spa_audio_info_ape { uint32_t rate; /*< sample rate */ uint32_t channels; /*< number of channels */ }; #define SPA_AUDIO_INFO_APE_INIT(...) ((struct spa_audio_info_ape) { __VA_ARGS__ }) /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_AUDIO_APE_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/param/audio/compressed.h000066400000000000000000000013641511204443500302110ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2021 Wim Taymans */ /* SPDX-FileCopyrightText: Copyright © 2022 Asymptotic Inc. */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_AUDIO_COMPRESSED_H #define SPA_AUDIO_COMPRESSED_H #include #include #include #include #include #include #include #include #include #include #include #include #include #include #endif /* SPA_AUDIO_COMPRESSED_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/param/audio/dsd-utils.h000066400000000000000000000054251511204443500277570ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_AUDIO_DSD_UTILS_H #define SPA_AUDIO_DSD_UTILS_H #include #include #include #include #include #ifdef __cplusplus extern "C" { #endif /** * \addtogroup spa_param * \{ */ #ifndef SPA_API_AUDIO_DSD_UTILS #ifdef SPA_API_IMPL #define SPA_API_AUDIO_DSD_UTILS SPA_API_IMPL #else #define SPA_API_AUDIO_DSD_UTILS static inline #endif #endif SPA_API_AUDIO_DSD_UTILS int spa_format_audio_dsd_parse(const struct spa_pod *format, struct spa_audio_info_dsd *info) { struct spa_pod *position = NULL; int res; uint32_t max_position = SPA_N_ELEMENTS(info->position); info->flags = 0; res = spa_pod_parse_object(format, SPA_TYPE_OBJECT_Format, NULL, SPA_FORMAT_AUDIO_bitorder, SPA_POD_OPT_Id(&info->bitorder), SPA_FORMAT_AUDIO_interleave, SPA_POD_OPT_Int(&info->interleave), SPA_FORMAT_AUDIO_rate, SPA_POD_OPT_Int(&info->rate), SPA_FORMAT_AUDIO_channels, SPA_POD_OPT_Int(&info->channels), SPA_FORMAT_AUDIO_position, SPA_POD_OPT_Pod(&position)); if (info->channels > max_position) return -ECHRNG; if (position == NULL || spa_pod_copy_array(position, SPA_TYPE_Id, info->position, max_position) != info->channels) { SPA_FLAG_SET(info->flags, SPA_AUDIO_FLAG_UNPOSITIONED); spa_memzero(info->position, max_position * sizeof(info->position[0])); } return res; } SPA_API_AUDIO_DSD_UTILS struct spa_pod * spa_format_audio_dsd_build(struct spa_pod_builder *builder, uint32_t id, const struct spa_audio_info_dsd *info) { struct spa_pod_frame f; spa_pod_builder_push_object(builder, &f, SPA_TYPE_OBJECT_Format, id); spa_pod_builder_add(builder, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_audio), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_dsd), 0); if (info->bitorder != SPA_PARAM_BITORDER_unknown) spa_pod_builder_add(builder, SPA_FORMAT_AUDIO_bitorder, SPA_POD_Id(info->bitorder), 0); if (info->interleave != 0) spa_pod_builder_add(builder, SPA_FORMAT_AUDIO_interleave, SPA_POD_Int(info->interleave), 0); if (info->rate != 0) spa_pod_builder_add(builder, SPA_FORMAT_AUDIO_rate, SPA_POD_Int(info->rate), 0); if (info->channels != 0) { spa_pod_builder_add(builder, SPA_FORMAT_AUDIO_channels, SPA_POD_Int(info->channels), 0); if (!SPA_FLAG_IS_SET(info->flags, SPA_AUDIO_FLAG_UNPOSITIONED)) { spa_pod_builder_add(builder, SPA_FORMAT_AUDIO_position, SPA_POD_Array(sizeof(uint32_t), SPA_TYPE_Id, info->channels, info->position), 0); } } return (struct spa_pod*)spa_pod_builder_pop(builder, &f); } /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_AUDIO_DSD_UTILS_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/param/audio/dsd.h000066400000000000000000000027751511204443500266260ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2021 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_AUDIO_DSD_H #define SPA_AUDIO_DSD_H #include #include #include #ifdef __cplusplus extern "C" { #endif /** * \addtogroup spa_param * \{ */ /** Extra DSD audio flags */ #define SPA_AUDIO_DSD_FLAG_NONE (0) /*< no valid flag */ /* DSD bits are transferred in a buffer grouped in bytes with the bitorder * defined by \a bitorder. * * Channels are placed in separate planes (interleave = 0) or interleaved * using the interleave value. A negative interleave value means that the * bytes need to be reversed in the group. * * Planar (interleave = 0): * plane1: l1 l2 l3 l4 l5 ... * plane2: r1 r2 r3 r4 r5 ... * * Interleaved 4: * plane1: l1 l2 l3 l4 r1 r2 r3 r4 l5 l6 l7 l8 r5 r6 r7 r8 l9 ... * * Interleaved 2: * plane1: l1 l2 r1 r2 l3 l4 r3 r4 ... */ struct spa_audio_info_dsd { enum spa_param_bitorder bitorder; /*< the order of the bits */ uint32_t flags; /*< extra flags */ int32_t interleave; /*< interleave bytes */ uint32_t rate; /*< sample rate (in bytes per second) */ uint32_t channels; /*< channels */ uint32_t position[SPA_AUDIO_MAX_CHANNELS]; /*< channel position from enum spa_audio_channel */ }; #define SPA_AUDIO_INFO_DSD_INIT(...) ((struct spa_audio_info_dsd) { __VA_ARGS__ }) /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_AUDIO_DSD_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/param/audio/dsp-utils.h000066400000000000000000000030151511204443500277640ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_AUDIO_DSP_UTILS_H #define SPA_AUDIO_DSP_UTILS_H #include #include #include #include #ifdef __cplusplus extern "C" { #endif /** * \addtogroup spa_param * \{ */ #ifndef SPA_API_AUDIO_DSP_UTILS #ifdef SPA_API_IMPL #define SPA_API_AUDIO_DSP_UTILS SPA_API_IMPL #else #define SPA_API_AUDIO_DSP_UTILS static inline #endif #endif SPA_API_AUDIO_DSP_UTILS int spa_format_audio_dsp_parse(const struct spa_pod *format, struct spa_audio_info_dsp *info) { int res; res = spa_pod_parse_object(format, SPA_TYPE_OBJECT_Format, NULL, SPA_FORMAT_AUDIO_format, SPA_POD_OPT_Id(&info->format)); return res; } SPA_API_AUDIO_DSP_UTILS struct spa_pod * spa_format_audio_dsp_build(struct spa_pod_builder *builder, uint32_t id, const struct spa_audio_info_dsp *info) { struct spa_pod_frame f; spa_pod_builder_push_object(builder, &f, SPA_TYPE_OBJECT_Format, id); spa_pod_builder_add(builder, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_audio), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_dsp), 0); if (info->format != SPA_AUDIO_FORMAT_UNKNOWN) spa_pod_builder_add(builder, SPA_FORMAT_AUDIO_format, SPA_POD_Id(info->format), 0); return (struct spa_pod*)spa_pod_builder_pop(builder, &f); } /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_AUDIO_DSP_UTILS_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/param/audio/dsp.h000066400000000000000000000011121511204443500266220ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_AUDIO_DSP_H #define SPA_AUDIO_DSP_H #include #ifdef __cplusplus extern "C" { #endif /** * \addtogroup spa_param * \{ */ struct spa_audio_info_dsp { enum spa_audio_format format; /*< format, one of the DSP formats in enum spa_audio_format */ }; #define SPA_AUDIO_INFO_DSP_INIT(...) ((struct spa_audio_info_dsp) { __VA_ARGS__ }) /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_AUDIO_DSP_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/param/audio/dts-types.h000066400000000000000000000020571511204443500300010ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2025 Carlos Rafael Giani */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_AUDIO_DTS_TYPES_H #define SPA_AUDIO_DTS_TYPES_H #include #include #ifdef __cplusplus extern "C" { #endif /** * \addtogroup spa_param * \{ */ #define SPA_TYPE_INFO_AudioDTSExtType SPA_TYPE_INFO_ENUM_BASE "AudioDTSExtType" #define SPA_TYPE_INFO_AUDIO_DTS_EXT_TYPE_BASE SPA_TYPE_INFO_AudioDTSExtType ":" static const struct spa_type_info spa_type_audio_dts_ext_type[] = { { SPA_AUDIO_DTS_EXT_UNKNOWN, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_DTS_EXT_TYPE_BASE "UNKNOWN", NULL }, { SPA_AUDIO_DTS_EXT_NONE, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_DTS_EXT_TYPE_BASE "NONE", NULL }, { SPA_AUDIO_DTS_EXT_HD_HRA, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_DTS_EXT_TYPE_BASE "HRA", NULL }, { SPA_AUDIO_DTS_EXT_HD_MA, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_DTS_EXT_TYPE_BASE "MA", NULL }, { 0, 0, NULL, NULL }, }; /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_AUDIO_DTS_TYPES_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/param/audio/dts-utils.h000066400000000000000000000036561511204443500300030ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2025 Carlos Rafael Giani */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_AUDIO_DTS_UTILS_H #define SPA_AUDIO_DTS_UTILS_H #include #include #include #include #ifdef __cplusplus extern "C" { #endif /** * \addtogroup spa_param * \{ */ #ifndef SPA_API_AUDIO_DTS_UTILS #ifdef SPA_API_IMPL #define SPA_API_AUDIO_DTS_UTILS SPA_API_IMPL #else #define SPA_API_AUDIO_DTS_UTILS static inline #endif #endif SPA_API_AUDIO_DTS_UTILS int spa_format_audio_dts_parse(const struct spa_pod *format, struct spa_audio_info_dts *info) { int res; res = spa_pod_parse_object(format, SPA_TYPE_OBJECT_Format, NULL, SPA_FORMAT_AUDIO_rate, SPA_POD_OPT_Int(&info->rate), SPA_FORMAT_AUDIO_channels, SPA_POD_OPT_Int(&info->channels), SPA_FORMAT_AUDIO_DTS_extType, SPA_POD_OPT_Id(&info->ext_type)); return res; } SPA_API_AUDIO_DTS_UTILS struct spa_pod * spa_format_audio_dts_build(struct spa_pod_builder *builder, uint32_t id, const struct spa_audio_info_dts *info) { struct spa_pod_frame f; spa_pod_builder_push_object(builder, &f, SPA_TYPE_OBJECT_Format, id); spa_pod_builder_add(builder, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_audio), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_dts), SPA_FORMAT_AUDIO_format, SPA_POD_Id(SPA_AUDIO_FORMAT_ENCODED), 0); if (info->rate != 0) spa_pod_builder_add(builder, SPA_FORMAT_AUDIO_rate, SPA_POD_Int(info->rate), 0); if (info->channels != 0) spa_pod_builder_add(builder, SPA_FORMAT_AUDIO_channels, SPA_POD_Int(info->channels), 0); if (info->ext_type != 0) spa_pod_builder_add(builder, SPA_FORMAT_AUDIO_DTS_extType, SPA_POD_Id(info->ext_type), 0); return (struct spa_pod*)spa_pod_builder_pop(builder, &f); } /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_AUDIO_DTS_UTILS_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/param/audio/dts.h000066400000000000000000000024261511204443500266370ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2025 Carlos Rafael Giani */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_AUDIO_DTS_H #define SPA_AUDIO_DTS_H #include #ifdef __cplusplus extern "C" { #endif /** * \addtogroup spa_param * \{ */ /** * Possible high-definition DTS extensions on top of core DTS. */ enum spa_audio_dts_ext_type { SPA_AUDIO_DTS_EXT_UNKNOWN, SPA_AUDIO_DTS_EXT_NONE, /**< No extension present; this is just regular DTS data */ SPA_AUDIO_DTS_EXT_HD_HRA, /**< DTS-HD High Resolution Audio (lossy HD audio extension) */ SPA_AUDIO_DTS_EXT_HD_MA, /**< DTS-HD Master Audio (lossless HD audio extension) */ }; /** * DTS Coherent Acoustics audio info. Optional extensions on top * of the DTS content can be present, resulting in what is known * as DTS-HD. \a ext_type specifies which extension is used in * combination with the core DTS content (if any). */ struct spa_audio_info_dts { uint32_t rate; /*< sample rate */ uint32_t channels; /*< number of channels */ enum spa_audio_dts_ext_type ext_type; /*< DTS-HD extension type */ }; #define SPA_AUDIO_INFO_DTS_INIT(...) ((struct spa_audio_info_dts) { __VA_ARGS__ }) /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_AUDIO_DTS_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/param/audio/eac3-utils.h000066400000000000000000000033771511204443500300240ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2025 Carlos Rafael Giani */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_AUDIO_EAC3_UTILS_H #define SPA_AUDIO_EAC3_UTILS_H #include #include #include #include #ifdef __cplusplus extern "C" { #endif /** * \addtogroup spa_param * \{ */ #ifndef SPA_API_AUDIO_EAC3_UTILS #ifdef SPA_API_IMPL #define SPA_API_AUDIO_EAC3_UTILS SPA_API_IMPL #else #define SPA_API_AUDIO_EAC3_UTILS static inline #endif #endif SPA_API_AUDIO_EAC3_UTILS int spa_format_audio_eac3_parse(const struct spa_pod *format, struct spa_audio_info_eac3 *info) { int res; res = spa_pod_parse_object(format, SPA_TYPE_OBJECT_Format, NULL, SPA_FORMAT_AUDIO_rate, SPA_POD_OPT_Int(&info->rate), SPA_FORMAT_AUDIO_channels, SPA_POD_OPT_Int(&info->channels)); return res; } SPA_API_AUDIO_EAC3_UTILS struct spa_pod * spa_format_audio_eac3_build(struct spa_pod_builder *builder, uint32_t id, const struct spa_audio_info_eac3 *info) { struct spa_pod_frame f; spa_pod_builder_push_object(builder, &f, SPA_TYPE_OBJECT_Format, id); spa_pod_builder_add(builder, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_audio), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_eac3), SPA_FORMAT_AUDIO_format, SPA_POD_Id(SPA_AUDIO_FORMAT_ENCODED), 0); if (info->rate != 0) spa_pod_builder_add(builder, SPA_FORMAT_AUDIO_rate, SPA_POD_Int(info->rate), 0); if (info->channels != 0) spa_pod_builder_add(builder, SPA_FORMAT_AUDIO_channels, SPA_POD_Int(info->channels), 0); return (struct spa_pod*)spa_pod_builder_pop(builder, &f); } /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_AUDIO_EAC3_UTILS_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/param/audio/eac3.h000066400000000000000000000011571511204443500266600ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2025 Carlos Rafael Giani */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_AUDIO_EAC3_H #define SPA_AUDIO_EAC3_H #include #ifdef __cplusplus extern "C" { #endif /** * \addtogroup spa_param * \{ */ /** Dolby E-AC-3 audio info. */ struct spa_audio_info_eac3 { uint32_t rate; /*< sample rate */ uint32_t channels; /*< number of channels */ }; #define SPA_AUDIO_INFO_EAC3_INIT(...) ((struct spa_audio_info_eac3) { __VA_ARGS__ }) /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_AUDIO_EAC3_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/param/audio/flac-utils.h000066400000000000000000000033701511204443500301070ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2023 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_AUDIO_FLAC_UTILS_H #define SPA_AUDIO_FLAC_UTILS_H #include #include #include #include #ifdef __cplusplus extern "C" { #endif /** * \addtogroup spa_param * \{ */ #ifndef SPA_API_AUDIO_FLAC_UTILS #ifdef SPA_API_IMPL #define SPA_API_AUDIO_FLAC_UTILS SPA_API_IMPL #else #define SPA_API_AUDIO_FLAC_UTILS static inline #endif #endif SPA_API_AUDIO_FLAC_UTILS int spa_format_audio_flac_parse(const struct spa_pod *format, struct spa_audio_info_flac *info) { int res; res = spa_pod_parse_object(format, SPA_TYPE_OBJECT_Format, NULL, SPA_FORMAT_AUDIO_rate, SPA_POD_OPT_Int(&info->rate), SPA_FORMAT_AUDIO_channels, SPA_POD_OPT_Int(&info->channels)); return res; } SPA_API_AUDIO_FLAC_UTILS struct spa_pod * spa_format_audio_flac_build(struct spa_pod_builder *builder, uint32_t id, const struct spa_audio_info_flac *info) { struct spa_pod_frame f; spa_pod_builder_push_object(builder, &f, SPA_TYPE_OBJECT_Format, id); spa_pod_builder_add(builder, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_audio), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_flac), SPA_FORMAT_AUDIO_format, SPA_POD_Id(SPA_AUDIO_FORMAT_ENCODED), 0); if (info->rate != 0) spa_pod_builder_add(builder, SPA_FORMAT_AUDIO_rate, SPA_POD_Int(info->rate), 0); if (info->channels != 0) spa_pod_builder_add(builder, SPA_FORMAT_AUDIO_channels, SPA_POD_Int(info->channels), 0); return (struct spa_pod*)spa_pod_builder_pop(builder, &f); } /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_AUDIO_FLAC_UTILS_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/param/audio/flac.h000066400000000000000000000011071511204443500267450ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2023 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_AUDIO_FLAC_H #define SPA_AUDIO_FLAC_H #include #ifdef __cplusplus extern "C" { #endif /** * \addtogroup spa_param * \{ */ struct spa_audio_info_flac { uint32_t rate; /*< sample rate */ uint32_t channels; /*< number of channels */ }; #define SPA_AUDIO_INFO_FLAC_INIT(...) ((struct spa_audio_info_flac) { __VA_ARGS__ }) /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_AUDIO_FLAC_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/param/audio/format-utils.h000066400000000000000000000166551511204443500305040ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_PARAM_AUDIO_FORMAT_UTILS_H #define SPA_PARAM_AUDIO_FORMAT_UTILS_H #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef __cplusplus extern "C" { #endif /** * \addtogroup spa_param * \{ */ #ifndef SPA_API_AUDIO_FORMAT_UTILS #ifdef SPA_API_IMPL #define SPA_API_AUDIO_FORMAT_UTILS SPA_API_IMPL #else #define SPA_API_AUDIO_FORMAT_UTILS static inline #endif #endif SPA_API_AUDIO_FORMAT_UTILS bool spa_format_audio_ext_valid_size(uint32_t media_subtype, size_t size) { switch (media_subtype) { case SPA_MEDIA_SUBTYPE_raw: return size >= offsetof(struct spa_audio_info, info.raw) && SPA_AUDIO_INFO_RAW_VALID_SIZE(size - offsetof(struct spa_audio_info, info.raw)); #define _SPA_FORMAT_AUDIO_EXT_VALID_SIZE_CASE(format) \ case SPA_MEDIA_SUBTYPE_ ## format: \ return size >= offsetof(struct spa_audio_info, info.format) + sizeof(struct spa_audio_info_ ## format); _SPA_FORMAT_AUDIO_EXT_VALID_SIZE_CASE(dsp) _SPA_FORMAT_AUDIO_EXT_VALID_SIZE_CASE(iec958) _SPA_FORMAT_AUDIO_EXT_VALID_SIZE_CASE(dsd) _SPA_FORMAT_AUDIO_EXT_VALID_SIZE_CASE(mp3) _SPA_FORMAT_AUDIO_EXT_VALID_SIZE_CASE(aac) _SPA_FORMAT_AUDIO_EXT_VALID_SIZE_CASE(vorbis) _SPA_FORMAT_AUDIO_EXT_VALID_SIZE_CASE(wma) _SPA_FORMAT_AUDIO_EXT_VALID_SIZE_CASE(ra) _SPA_FORMAT_AUDIO_EXT_VALID_SIZE_CASE(amr) _SPA_FORMAT_AUDIO_EXT_VALID_SIZE_CASE(alac) _SPA_FORMAT_AUDIO_EXT_VALID_SIZE_CASE(flac) _SPA_FORMAT_AUDIO_EXT_VALID_SIZE_CASE(ape) _SPA_FORMAT_AUDIO_EXT_VALID_SIZE_CASE(ac3) _SPA_FORMAT_AUDIO_EXT_VALID_SIZE_CASE(eac3) _SPA_FORMAT_AUDIO_EXT_VALID_SIZE_CASE(truehd) _SPA_FORMAT_AUDIO_EXT_VALID_SIZE_CASE(dts) _SPA_FORMAT_AUDIO_EXT_VALID_SIZE_CASE(mpegh) #undef _SPA_FORMAT_AUDIO_EXT_VALID_SIZE_CASE } return false; } SPA_API_AUDIO_FORMAT_UTILS int spa_format_audio_ext_parse(const struct spa_pod *format, struct spa_audio_info *info, size_t size) { int res; uint32_t media_type, media_subtype; if ((res = spa_format_parse(format, &media_type, &media_subtype)) < 0) return res; if (media_type != SPA_MEDIA_TYPE_audio) return -EINVAL; if (!spa_format_audio_ext_valid_size(media_subtype, size)) return -EINVAL; info->media_type = media_type; info->media_subtype = media_subtype; switch (media_subtype) { case SPA_MEDIA_SUBTYPE_raw: return spa_format_audio_raw_ext_parse(format, &info->info.raw, size - offsetof(struct spa_audio_info, info.raw)); case SPA_MEDIA_SUBTYPE_dsp: return spa_format_audio_dsp_parse(format, &info->info.dsp); case SPA_MEDIA_SUBTYPE_iec958: return spa_format_audio_iec958_parse(format, &info->info.iec958); case SPA_MEDIA_SUBTYPE_dsd: return spa_format_audio_dsd_parse(format, &info->info.dsd); case SPA_MEDIA_SUBTYPE_mp3: return spa_format_audio_mp3_parse(format, &info->info.mp3); case SPA_MEDIA_SUBTYPE_aac: return spa_format_audio_aac_parse(format, &info->info.aac); case SPA_MEDIA_SUBTYPE_vorbis: return spa_format_audio_vorbis_parse(format, &info->info.vorbis); case SPA_MEDIA_SUBTYPE_wma: return spa_format_audio_wma_parse(format, &info->info.wma); case SPA_MEDIA_SUBTYPE_ra: return spa_format_audio_ra_parse(format, &info->info.ra); case SPA_MEDIA_SUBTYPE_amr: return spa_format_audio_amr_parse(format, &info->info.amr); case SPA_MEDIA_SUBTYPE_alac: return spa_format_audio_alac_parse(format, &info->info.alac); case SPA_MEDIA_SUBTYPE_flac: return spa_format_audio_flac_parse(format, &info->info.flac); case SPA_MEDIA_SUBTYPE_ape: return spa_format_audio_ape_parse(format, &info->info.ape); case SPA_MEDIA_SUBTYPE_ac3: return spa_format_audio_ac3_parse(format, &info->info.ac3); case SPA_MEDIA_SUBTYPE_eac3: return spa_format_audio_eac3_parse(format, &info->info.eac3); case SPA_MEDIA_SUBTYPE_truehd: return spa_format_audio_truehd_parse(format, &info->info.truehd); case SPA_MEDIA_SUBTYPE_dts: return spa_format_audio_dts_parse(format, &info->info.dts); case SPA_MEDIA_SUBTYPE_mpegh: return spa_format_audio_mpegh_parse(format, &info->info.mpegh); } return -ENOTSUP; } SPA_API_AUDIO_FORMAT_UTILS int spa_format_audio_parse(const struct spa_pod *format, struct spa_audio_info *info) { return spa_format_audio_ext_parse(format, info, sizeof(*info)); } SPA_API_AUDIO_FORMAT_UTILS struct spa_pod * spa_format_audio_ext_build(struct spa_pod_builder *builder, uint32_t id, const struct spa_audio_info *info, size_t size) { if (!spa_format_audio_ext_valid_size(info->media_subtype, size)) { errno = EINVAL; return NULL; } switch (info->media_subtype) { case SPA_MEDIA_SUBTYPE_raw: return spa_format_audio_raw_ext_build(builder, id, &info->info.raw, size - offsetof(struct spa_audio_info, info.raw)); case SPA_MEDIA_SUBTYPE_dsp: return spa_format_audio_dsp_build(builder, id, &info->info.dsp); case SPA_MEDIA_SUBTYPE_iec958: return spa_format_audio_iec958_build(builder, id, &info->info.iec958); case SPA_MEDIA_SUBTYPE_dsd: return spa_format_audio_dsd_build(builder, id, &info->info.dsd); case SPA_MEDIA_SUBTYPE_mp3: return spa_format_audio_mp3_build(builder, id, &info->info.mp3); case SPA_MEDIA_SUBTYPE_aac: return spa_format_audio_aac_build(builder, id, &info->info.aac); case SPA_MEDIA_SUBTYPE_vorbis: return spa_format_audio_vorbis_build(builder, id, &info->info.vorbis); case SPA_MEDIA_SUBTYPE_wma: return spa_format_audio_wma_build(builder, id, &info->info.wma); case SPA_MEDIA_SUBTYPE_ra: return spa_format_audio_ra_build(builder, id, &info->info.ra); case SPA_MEDIA_SUBTYPE_amr: return spa_format_audio_amr_build(builder, id, &info->info.amr); case SPA_MEDIA_SUBTYPE_alac: return spa_format_audio_alac_build(builder, id, &info->info.alac); case SPA_MEDIA_SUBTYPE_flac: return spa_format_audio_flac_build(builder, id, &info->info.flac); case SPA_MEDIA_SUBTYPE_ape: return spa_format_audio_ape_build(builder, id, &info->info.ape); case SPA_MEDIA_SUBTYPE_ac3: return spa_format_audio_ac3_build(builder, id, &info->info.ac3); case SPA_MEDIA_SUBTYPE_eac3: return spa_format_audio_eac3_build(builder, id, &info->info.eac3); case SPA_MEDIA_SUBTYPE_truehd: return spa_format_audio_truehd_build(builder, id, &info->info.truehd); case SPA_MEDIA_SUBTYPE_dts: return spa_format_audio_dts_build(builder, id, &info->info.dts); case SPA_MEDIA_SUBTYPE_mpegh: return spa_format_audio_mpegh_build(builder, id, &info->info.mpegh); } errno = ENOTSUP; return NULL; } SPA_API_AUDIO_FORMAT_UTILS struct spa_pod * spa_format_audio_build(struct spa_pod_builder *builder, uint32_t id, const struct spa_audio_info *info) { return spa_format_audio_ext_build(builder, id, info, sizeof(*info)); } /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_PARAM_AUDIO_FORMAT_UTILS_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/param/audio/format.h000066400000000000000000000034631511204443500273370ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_PARAM_AUDIO_FORMAT_H #define SPA_PARAM_AUDIO_FORMAT_H #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef __cplusplus extern "C" { #endif /** * \addtogroup spa_param * \{ */ struct spa_audio_info { uint32_t media_type; uint32_t media_subtype; union { struct spa_audio_info_raw raw; struct spa_audio_info_dsp dsp; struct spa_audio_info_iec958 iec958; struct spa_audio_info_dsd dsd; struct spa_audio_info_mp3 mp3; struct spa_audio_info_aac aac; struct spa_audio_info_vorbis vorbis; struct spa_audio_info_wma wma; struct spa_audio_info_ra ra; struct spa_audio_info_amr amr; struct spa_audio_info_alac alac; struct spa_audio_info_flac flac; struct spa_audio_info_ape ape; struct spa_audio_info_ape opus; struct spa_audio_info_ac3 ac3; struct spa_audio_info_eac3 eac3; struct spa_audio_info_truehd truehd; struct spa_audio_info_dts dts; struct spa_audio_info_mpegh mpegh; } info; /* padding follows here when info has flexible size */ }; /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_PARAM_AUDIO_FORMAT_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/param/audio/iec958-types.h000066400000000000000000000041771511204443500302220ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_AUDIO_IEC958_TYPES_H #define SPA_AUDIO_IEC958_TYPES_H #include #include #ifdef __cplusplus extern "C" { #endif /** * \addtogroup spa_param * \{ */ #ifndef SPA_API_AUDIO_IEC958_TYPES #ifdef SPA_API_IMPL #define SPA_API_AUDIO_IEC958_TYPES SPA_API_IMPL #else #define SPA_API_AUDIO_IEC958_TYPES static inline #endif #endif #define SPA_TYPE_INFO_AudioIEC958Codec SPA_TYPE_INFO_ENUM_BASE "AudioIEC958Codec" #define SPA_TYPE_INFO_AUDIO_IEC958_CODEC_BASE SPA_TYPE_INFO_AudioIEC958Codec ":" static const struct spa_type_info spa_type_audio_iec958_codec[] = { { SPA_AUDIO_IEC958_CODEC_UNKNOWN, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_IEC958_CODEC_BASE "UNKNOWN", NULL }, { SPA_AUDIO_IEC958_CODEC_PCM, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_IEC958_CODEC_BASE "PCM", NULL }, { SPA_AUDIO_IEC958_CODEC_DTS, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_IEC958_CODEC_BASE "DTS", NULL }, { SPA_AUDIO_IEC958_CODEC_AC3, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_IEC958_CODEC_BASE "AC3", NULL }, { SPA_AUDIO_IEC958_CODEC_MPEG, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_IEC958_CODEC_BASE "MPEG", NULL }, { SPA_AUDIO_IEC958_CODEC_MPEG2_AAC, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_IEC958_CODEC_BASE "MPEG2-AAC", NULL }, { SPA_AUDIO_IEC958_CODEC_EAC3, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_IEC958_CODEC_BASE "EAC3", NULL }, { SPA_AUDIO_IEC958_CODEC_TRUEHD, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_IEC958_CODEC_BASE "TrueHD", NULL }, { SPA_AUDIO_IEC958_CODEC_DTSHD, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_IEC958_CODEC_BASE "DTS-HD", NULL }, { 0, 0, NULL, NULL }, }; SPA_API_AUDIO_IEC958_TYPES uint32_t spa_type_audio_iec958_codec_from_short_name(const char *name) { return spa_type_from_short_name(name, spa_type_audio_iec958_codec, SPA_AUDIO_IEC958_CODEC_UNKNOWN); } SPA_API_AUDIO_IEC958_TYPES const char * spa_type_audio_iec958_codec_to_short_name(uint32_t type) { return spa_type_to_short_name(type, spa_type_audio_iec958_codec, "UNKNOWN"); } /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_AUDIO_RAW_IEC958_TYPES_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/param/audio/iec958-utils.h000066400000000000000000000033521511204443500302100ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_AUDIO_IEC958_UTILS_H #define SPA_AUDIO_IEC958_UTILS_H #include #include #include #include #ifdef __cplusplus extern "C" { #endif /** * \addtogroup spa_param * \{ */ #ifndef SPA_API_AUDIO_IEC958_UTILS #ifdef SPA_API_IMPL #define SPA_API_AUDIO_IEC958_UTILS SPA_API_IMPL #else #define SPA_API_AUDIO_IEC958_UTILS static inline #endif #endif SPA_API_AUDIO_IEC958_UTILS int spa_format_audio_iec958_parse(const struct spa_pod *format, struct spa_audio_info_iec958 *info) { int res; res = spa_pod_parse_object(format, SPA_TYPE_OBJECT_Format, NULL, SPA_FORMAT_AUDIO_iec958Codec, SPA_POD_OPT_Id(&info->codec), SPA_FORMAT_AUDIO_rate, SPA_POD_OPT_Int(&info->rate)); return res; } SPA_API_AUDIO_IEC958_UTILS struct spa_pod * spa_format_audio_iec958_build(struct spa_pod_builder *builder, uint32_t id, const struct spa_audio_info_iec958 *info) { struct spa_pod_frame f; spa_pod_builder_push_object(builder, &f, SPA_TYPE_OBJECT_Format, id); spa_pod_builder_add(builder, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_audio), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_iec958), 0); if (info->codec != SPA_AUDIO_IEC958_CODEC_UNKNOWN) spa_pod_builder_add(builder, SPA_FORMAT_AUDIO_iec958Codec, SPA_POD_Id(info->codec), 0); if (info->rate != 0) spa_pod_builder_add(builder, SPA_FORMAT_AUDIO_rate, SPA_POD_Int(info->rate), 0); return (struct spa_pod*)spa_pod_builder_pop(builder, &f); } /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_AUDIO_IEC958_UTILS_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/param/audio/iec958.h000066400000000000000000000021141511204443500270450ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2021 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_AUDIO_IEC958_H #define SPA_AUDIO_IEC958_H #include #ifdef __cplusplus extern "C" { #endif /** * \addtogroup spa_param * \{ */ enum spa_audio_iec958_codec { SPA_AUDIO_IEC958_CODEC_UNKNOWN, SPA_AUDIO_IEC958_CODEC_PCM, SPA_AUDIO_IEC958_CODEC_DTS, SPA_AUDIO_IEC958_CODEC_AC3, SPA_AUDIO_IEC958_CODEC_MPEG, /**< MPEG-1 or MPEG-2 (Part 3, not AAC) */ SPA_AUDIO_IEC958_CODEC_MPEG2_AAC, /**< MPEG-2 AAC */ SPA_AUDIO_IEC958_CODEC_EAC3, SPA_AUDIO_IEC958_CODEC_TRUEHD, /**< Dolby TrueHD */ SPA_AUDIO_IEC958_CODEC_DTSHD, /**< DTS-HD Master Audio */ }; struct spa_audio_info_iec958 { enum spa_audio_iec958_codec codec; /*< codec, one of the values in enum spa_audio_iec958_codec */ uint32_t flags; /*< extra flags */ uint32_t rate; /*< sample rate */ }; #define SPA_AUDIO_INFO_IEC958_INIT(...) ((struct spa_audio_info_iec958) { __VA_ARGS__ }) /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_AUDIO_IEC958_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/param/audio/layout-types.h000066400000000000000000000070401511204443500305210ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2025 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_AUDIO_LAYOUT_TYPES_H #define SPA_AUDIO_LAYOUT_TYPES_H #include #include #include #ifdef __cplusplus extern "C" { #endif /** * \addtogroup spa_param * \{ */ #ifndef SPA_API_AUDIO_LAYOUT_TYPES #ifdef SPA_API_IMPL #define SPA_API_AUDIO_LAYOUT_TYPES SPA_API_IMPL #else #define SPA_API_AUDIO_LAYOUT_TYPES static inline #endif #endif static const struct spa_type_audio_layout_info { const char *name; struct spa_audio_layout_info layout; } spa_type_audio_layout_info[] = { { "Mono", { SPA_AUDIO_LAYOUT_Mono } }, { "Stereo", { SPA_AUDIO_LAYOUT_Stereo } }, { "Quad", { SPA_AUDIO_LAYOUT_Quad } }, { "Pentagonal", { SPA_AUDIO_LAYOUT_Pentagonal } }, { "Hexagonal", { SPA_AUDIO_LAYOUT_Hexagonal } }, { "Octagonal", { SPA_AUDIO_LAYOUT_Octagonal } }, { "Cube", { SPA_AUDIO_LAYOUT_Cube } }, { "MPEG-1.0", { SPA_AUDIO_LAYOUT_MPEG_1_0 } }, { "MPEG-2.0", { SPA_AUDIO_LAYOUT_MPEG_2_0 } }, { "MPEG-3.0A", { SPA_AUDIO_LAYOUT_MPEG_3_0A } }, { "MPEG-3.0B", { SPA_AUDIO_LAYOUT_MPEG_3_0B } }, { "MPEG-4.0A", { SPA_AUDIO_LAYOUT_MPEG_4_0A } }, { "MPEG-4.0B", { SPA_AUDIO_LAYOUT_MPEG_4_0B } }, { "MPEG-5.0A", { SPA_AUDIO_LAYOUT_MPEG_5_0A } }, { "MPEG-5.0B", { SPA_AUDIO_LAYOUT_MPEG_5_0B } }, { "MPEG-5.0C", { SPA_AUDIO_LAYOUT_MPEG_5_0C } }, { "MPEG-5.0D", { SPA_AUDIO_LAYOUT_MPEG_5_0D } }, { "MPEG-5.1A", { SPA_AUDIO_LAYOUT_MPEG_5_1A } }, { "MPEG-5.1B", { SPA_AUDIO_LAYOUT_MPEG_5_1B } }, { "MPEG-5.1C", { SPA_AUDIO_LAYOUT_MPEG_5_1C } }, { "MPEG-5.1D", { SPA_AUDIO_LAYOUT_MPEG_5_1D } }, { "MPEG-6.1A", { SPA_AUDIO_LAYOUT_MPEG_6_1A } }, { "MPEG-7.1A", { SPA_AUDIO_LAYOUT_MPEG_7_1A } }, { "MPEG-7.1B", { SPA_AUDIO_LAYOUT_MPEG_7_1B } }, { "MPEG-7.1C", { SPA_AUDIO_LAYOUT_MPEG_7_1C } }, { "2.1", { SPA_AUDIO_LAYOUT_2_1 } }, { "2RC", { SPA_AUDIO_LAYOUT_2RC } }, { "2FC", { SPA_AUDIO_LAYOUT_2FC } }, { "3.1", { SPA_AUDIO_LAYOUT_3_1 } }, { "4.0", { SPA_AUDIO_LAYOUT_4_0 } }, { "2.2", { SPA_AUDIO_LAYOUT_2_2 } }, { "4.1", { SPA_AUDIO_LAYOUT_4_1 } }, { "5.0", { SPA_AUDIO_LAYOUT_5_0 } }, { "5.0R", { SPA_AUDIO_LAYOUT_5_0R } }, { "5.1", { SPA_AUDIO_LAYOUT_5_1 } }, { "5.1R", { SPA_AUDIO_LAYOUT_5_1R } }, { "6.0", { SPA_AUDIO_LAYOUT_6_0 } }, { "6.0F", { SPA_AUDIO_LAYOUT_6_0F } }, { "6.1", { SPA_AUDIO_LAYOUT_6_1 } }, { "6.1F", { SPA_AUDIO_LAYOUT_6_1F } }, { "7.0", { SPA_AUDIO_LAYOUT_7_0 } }, { "7.0F", { SPA_AUDIO_LAYOUT_7_0F } }, { "7.1", { SPA_AUDIO_LAYOUT_7_1 } }, { "7.1W", { SPA_AUDIO_LAYOUT_7_1W } }, { "7.1WR", { SPA_AUDIO_LAYOUT_7_1WR } }, { NULL, }, }; SPA_API_AUDIO_LAYOUT_TYPES int spa_audio_layout_info_parse_name(struct spa_audio_layout_info *layout, size_t size, const char *name) { uint32_t max_position = SPA_AUDIO_LAYOUT_INFO_MAX_POSITION(size); if (spa_strstartswith(name, "AUX")) { uint32_t i, n_pos; if (spa_atou32(name+3, &n_pos, 10)) { if (n_pos > max_position) return -ECHRNG; for (i = 0; i < 0x1000 && i < n_pos; i++) layout->position[i] = SPA_AUDIO_CHANNEL_AUX0 + i; for (; i < n_pos; i++) layout->position[i] = SPA_AUDIO_CHANNEL_UNKNOWN; layout->n_channels = n_pos; return n_pos; } } SPA_FOR_EACH_ELEMENT_VAR(spa_type_audio_layout_info, i) { if (spa_streq(name, i->name)) { if (i->layout.n_channels > max_position) return -ECHRNG; *layout = i->layout; return i->layout.n_channels; } } return -ENOTSUP; } /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_AUDIO_LAYOUT_TYPES_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/param/audio/layout.h000066400000000000000000000210231511204443500273540ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_AUDIO_LAYOUT_H #define SPA_AUDIO_LAYOUT_H #include #include #ifdef __cplusplus extern "C" { #endif /** * \addtogroup spa_param * \{ */ struct spa_audio_layout_info { uint32_t n_channels; uint32_t position[SPA_AUDIO_MAX_CHANNELS]; /* padding may follow to allow more channels */ }; #define SPA_AUDIO_LAYOUT_INFO_MAX_POSITION(size) (((size)-offsetof(struct spa_audio_layout_info,position))/sizeof(uint32_t)) #define SPA_AUDIO_LAYOUT_Mono 1, { SPA_AUDIO_CHANNEL_MONO, } #define SPA_AUDIO_LAYOUT_Stereo 2, { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, } #define SPA_AUDIO_LAYOUT_Quad 4, { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, \ SPA_AUDIO_CHANNEL_RL, SPA_AUDIO_CHANNEL_RR, } #define SPA_AUDIO_LAYOUT_Pentagonal 5, { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, \ SPA_AUDIO_CHANNEL_RL, SPA_AUDIO_CHANNEL_RR, \ SPA_AUDIO_CHANNEL_FC, } #define SPA_AUDIO_LAYOUT_Hexagonal 6, { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, \ SPA_AUDIO_CHANNEL_RL, SPA_AUDIO_CHANNEL_RR, \ SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_RC, } #define SPA_AUDIO_LAYOUT_Octagonal 8, { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, \ SPA_AUDIO_CHANNEL_RL, SPA_AUDIO_CHANNEL_RR, \ SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_RC, \ SPA_AUDIO_CHANNEL_SL, SPA_AUDIO_CHANNEL_SR, } #define SPA_AUDIO_LAYOUT_Cube 8, { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, \ SPA_AUDIO_CHANNEL_RL, SPA_AUDIO_CHANNEL_RR, \ SPA_AUDIO_CHANNEL_TFL, SPA_AUDIO_CHANNEL_TFR, \ SPA_AUDIO_CHANNEL_TRL, SPA_AUDIO_CHANNEL_TRR, } #define SPA_AUDIO_LAYOUT_MPEG_1_0 SPA_AUDIO_LAYOUT_Mono #define SPA_AUDIO_LAYOUT_MPEG_2_0 SPA_AUDIO_LAYOUT_Stereo #define SPA_AUDIO_LAYOUT_MPEG_3_0A 3, { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, \ SPA_AUDIO_CHANNEL_FC, } #define SPA_AUDIO_LAYOUT_MPEG_3_0B 3, { SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_FL, \ SPA_AUDIO_CHANNEL_FR, } #define SPA_AUDIO_LAYOUT_MPEG_4_0A 4, { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, \ SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_RC, } #define SPA_AUDIO_LAYOUT_MPEG_4_0B 4, { SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_FL, \ SPA_AUDIO_CHANNEL_FR, SPA_AUDIO_CHANNEL_RC, } #define SPA_AUDIO_LAYOUT_MPEG_5_0A 5, { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, \ SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_SL, \ SPA_AUDIO_CHANNEL_SR, } #define SPA_AUDIO_LAYOUT_MPEG_5_0B 5, { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, \ SPA_AUDIO_CHANNEL_SL, SPA_AUDIO_CHANNEL_SR, \ SPA_AUDIO_CHANNEL_FC, } #define SPA_AUDIO_LAYOUT_MPEG_5_0C 5, { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FC, \ SPA_AUDIO_CHANNEL_FR, SPA_AUDIO_CHANNEL_SL, \ SPA_AUDIO_CHANNEL_SR, } #define SPA_AUDIO_LAYOUT_MPEG_5_0D 5, { SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_FL, \ SPA_AUDIO_CHANNEL_FR, SPA_AUDIO_CHANNEL_SL, \ SPA_AUDIO_CHANNEL_SR, } #define SPA_AUDIO_LAYOUT_MPEG_5_1A 6, { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, \ SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_LFE, \ SPA_AUDIO_CHANNEL_SL, SPA_AUDIO_CHANNEL_SR, } #define SPA_AUDIO_LAYOUT_MPEG_5_1B 6, { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, \ SPA_AUDIO_CHANNEL_SL, SPA_AUDIO_CHANNEL_SR, \ SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_LFE, } #define SPA_AUDIO_LAYOUT_MPEG_5_1C 6, { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FC, \ SPA_AUDIO_CHANNEL_FR, SPA_AUDIO_CHANNEL_SL, \ SPA_AUDIO_CHANNEL_SR, SPA_AUDIO_CHANNEL_LFE, } #define SPA_AUDIO_LAYOUT_MPEG_5_1D 6, { SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_FL, \ SPA_AUDIO_CHANNEL_FR, SPA_AUDIO_CHANNEL_SL, \ SPA_AUDIO_CHANNEL_SR, SPA_AUDIO_CHANNEL_LFE, } #define SPA_AUDIO_LAYOUT_MPEG_6_1A 7, { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, \ SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_LFE, \ SPA_AUDIO_CHANNEL_SL, SPA_AUDIO_CHANNEL_SR, \ SPA_AUDIO_CHANNEL_RC, } #define SPA_AUDIO_LAYOUT_MPEG_7_1A 8, { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, \ SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_LFE, \ SPA_AUDIO_CHANNEL_RL, SPA_AUDIO_CHANNEL_RR, \ SPA_AUDIO_CHANNEL_SL, SPA_AUDIO_CHANNEL_SR, } #define SPA_AUDIO_LAYOUT_MPEG_7_1B 8, { SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_SL, \ SPA_AUDIO_CHANNEL_SR, SPA_AUDIO_CHANNEL_FL, \ SPA_AUDIO_CHANNEL_FR, SPA_AUDIO_CHANNEL_RL, \ SPA_AUDIO_CHANNEL_RR, SPA_AUDIO_CHANNEL_LFE, } #define SPA_AUDIO_LAYOUT_MPEG_7_1C 8, { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, \ SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_LFE, \ SPA_AUDIO_CHANNEL_SL, SPA_AUDIO_CHANNEL_SR, \ SPA_AUDIO_CHANNEL_RL, SPA_AUDIO_CHANNEL_RR, } #define SPA_AUDIO_LAYOUT_2_1 3, { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, \ SPA_AUDIO_CHANNEL_LFE, } #define SPA_AUDIO_LAYOUT_2RC 3, { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, \ SPA_AUDIO_CHANNEL_RC, } #define SPA_AUDIO_LAYOUT_2FC 3, { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, \ SPA_AUDIO_CHANNEL_FC, } #define SPA_AUDIO_LAYOUT_3_1 4, { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, \ SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_LFE, } #define SPA_AUDIO_LAYOUT_4_0 4, { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, \ SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_RC, } #define SPA_AUDIO_LAYOUT_2_2 4, { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, \ SPA_AUDIO_CHANNEL_SL, SPA_AUDIO_CHANNEL_SR, } #define SPA_AUDIO_LAYOUT_4_1 5, { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, \ SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_LFE, \ SPA_AUDIO_CHANNEL_RC, } #define SPA_AUDIO_LAYOUT_5_0 5, { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, \ SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_SL, \ SPA_AUDIO_CHANNEL_SR, } #define SPA_AUDIO_LAYOUT_5_0R 5, { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, \ SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_RL, \ SPA_AUDIO_CHANNEL_RR, } #define SPA_AUDIO_LAYOUT_5_1 6, { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, \ SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_LFE, \ SPA_AUDIO_CHANNEL_SL, SPA_AUDIO_CHANNEL_SR, } #define SPA_AUDIO_LAYOUT_5_1R 6, { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, \ SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_LFE, \ SPA_AUDIO_CHANNEL_RL, SPA_AUDIO_CHANNEL_RR, } #define SPA_AUDIO_LAYOUT_6_0 6, { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, \ SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_RC, \ SPA_AUDIO_CHANNEL_SL, SPA_AUDIO_CHANNEL_SR, } #define SPA_AUDIO_LAYOUT_6_0F 6, { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, \ SPA_AUDIO_CHANNEL_FLC, SPA_AUDIO_CHANNEL_FRC, \ SPA_AUDIO_CHANNEL_SL, SPA_AUDIO_CHANNEL_SR, } #define SPA_AUDIO_LAYOUT_6_1 7, { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, \ SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_LFE, \ SPA_AUDIO_CHANNEL_RC, SPA_AUDIO_CHANNEL_SL, \ SPA_AUDIO_CHANNEL_SR, } #define SPA_AUDIO_LAYOUT_6_1F 7, { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, \ SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_LFE, \ SPA_AUDIO_CHANNEL_RL, SPA_AUDIO_CHANNEL_RR, \ SPA_AUDIO_CHANNEL_RC, } #define SPA_AUDIO_LAYOUT_7_0 7, { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, \ SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_RL, \ SPA_AUDIO_CHANNEL_RR, SPA_AUDIO_CHANNEL_SL, \ SPA_AUDIO_CHANNEL_SR, } #define SPA_AUDIO_LAYOUT_7_0F 7, { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, \ SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_FLC, \ SPA_AUDIO_CHANNEL_FRC, SPA_AUDIO_CHANNEL_SL, \ SPA_AUDIO_CHANNEL_SR, } #define SPA_AUDIO_LAYOUT_7_1 8, { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, \ SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_LFE, \ SPA_AUDIO_CHANNEL_RL, SPA_AUDIO_CHANNEL_RR, \ SPA_AUDIO_CHANNEL_SL, SPA_AUDIO_CHANNEL_SR, } #define SPA_AUDIO_LAYOUT_7_1W 8, { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, \ SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_LFE, \ SPA_AUDIO_CHANNEL_FLC, SPA_AUDIO_CHANNEL_FRC, \ SPA_AUDIO_CHANNEL_SL, SPA_AUDIO_CHANNEL_SR, } #define SPA_AUDIO_LAYOUT_7_1WR 8, { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, \ SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_LFE, \ SPA_AUDIO_CHANNEL_RL, SPA_AUDIO_CHANNEL_RR, \ SPA_AUDIO_CHANNEL_FLC, SPA_AUDIO_CHANNEL_FRC, } /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_AUDIO_LAYOUT_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/param/audio/mp3-types.h000066400000000000000000000023551511204443500277070ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_AUDIO_MP3_TYPES_H #define SPA_AUDIO_MP3_TYPES_H #include #include #ifdef __cplusplus extern "C" { #endif /** * \addtogroup spa_param * \{ */ #define SPA_TYPE_INFO_AudioMP3ChannelMode SPA_TYPE_INFO_ENUM_BASE "AudioMP3ChannelMode" #define SPA_TYPE_INFO_AUDIO_MP3_CHANNEL_MODE_BASE SPA_TYPE_INFO_AudioMP3ChannelMode ":" static const struct spa_type_info spa_type_audio_mp3_channel_mode[] = { { SPA_AUDIO_MP3_CHANNEL_MODE_UNKNOWN, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_MP3_CHANNEL_MODE_BASE "UNKNOWN", NULL }, { SPA_AUDIO_MP3_CHANNEL_MODE_MONO, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_MP3_CHANNEL_MODE_BASE "Mono", NULL }, { SPA_AUDIO_MP3_CHANNEL_MODE_STEREO, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_MP3_CHANNEL_MODE_BASE "Stereo", NULL }, { SPA_AUDIO_MP3_CHANNEL_MODE_JOINTSTEREO, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_MP3_CHANNEL_MODE_BASE "Joint-stereo", NULL }, { SPA_AUDIO_MP3_CHANNEL_MODE_DUAL, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_MP3_CHANNEL_MODE_BASE "Dual", NULL }, { 0, 0, NULL, NULL }, }; /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_AUDIO_MP3_TYPES_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/param/audio/mp3-utils.h000066400000000000000000000036741511204443500277100ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2023 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_AUDIO_MP3_UTILS_H #define SPA_AUDIO_MP3_UTILS_H #include #include #include #include #ifdef __cplusplus extern "C" { #endif /** * \addtogroup spa_param * \{ */ #ifndef SPA_API_AUDIO_MP3_UTILS #ifdef SPA_API_IMPL #define SPA_API_AUDIO_MP3_UTILS SPA_API_IMPL #else #define SPA_API_AUDIO_MP3_UTILS static inline #endif #endif SPA_API_AUDIO_MP3_UTILS int spa_format_audio_mp3_parse(const struct spa_pod *format, struct spa_audio_info_mp3 *info) { int res; res = spa_pod_parse_object(format, SPA_TYPE_OBJECT_Format, NULL, SPA_FORMAT_AUDIO_rate, SPA_POD_OPT_Int(&info->rate), SPA_FORMAT_AUDIO_channels, SPA_POD_OPT_Int(&info->channels), SPA_FORMAT_AUDIO_MP3_channelMode, SPA_POD_OPT_Id(&info->channel_mode)); return res; } SPA_API_AUDIO_MP3_UTILS struct spa_pod * spa_format_audio_mp3_build(struct spa_pod_builder *builder, uint32_t id, const struct spa_audio_info_mp3 *info) { struct spa_pod_frame f; spa_pod_builder_push_object(builder, &f, SPA_TYPE_OBJECT_Format, id); spa_pod_builder_add(builder, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_audio), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_mp3), SPA_FORMAT_AUDIO_format, SPA_POD_Id(SPA_AUDIO_FORMAT_ENCODED), 0); if (info->rate != 0) spa_pod_builder_add(builder, SPA_FORMAT_AUDIO_rate, SPA_POD_Int(info->rate), 0); if (info->channels != 0) spa_pod_builder_add(builder, SPA_FORMAT_AUDIO_channels, SPA_POD_Int(info->channels), 0); if (info->channel_mode != 0) spa_pod_builder_add(builder, SPA_FORMAT_AUDIO_MP3_channelMode, SPA_POD_Id(info->channel_mode), 0); return (struct spa_pod*)spa_pod_builder_pop(builder, &f); } /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_AUDIO_MP3_UTILS_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/param/audio/mp3.h000066400000000000000000000023431511204443500265420ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2023 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_AUDIO_MP3_H #define SPA_AUDIO_MP3_H #include #ifdef __cplusplus extern "C" { #endif /** * \addtogroup spa_param * \{ */ enum spa_audio_mp3_channel_mode { SPA_AUDIO_MP3_CHANNEL_MODE_UNKNOWN, /** Mono mode, only used if channel count is 1 */ SPA_AUDIO_MP3_CHANNEL_MODE_MONO, /** Regular stereo mode with two independent channels */ SPA_AUDIO_MP3_CHANNEL_MODE_STEREO, /** * Joint stereo mode, exploiting the similarities between channels * using techniques like mid-side coding */ SPA_AUDIO_MP3_CHANNEL_MODE_JOINTSTEREO, /** * Two mono tracks, different from stereo in that each channel * contains entirely different content (like two different mono songs) */ SPA_AUDIO_MP3_CHANNEL_MODE_DUAL, }; struct spa_audio_info_mp3 { uint32_t rate; /*< sample rate in Hz */ uint32_t channels; /*< number of channels */ enum spa_audio_mp3_channel_mode channel_mode; /*< MP3 channel mode */ }; #define SPA_AUDIO_INFO_MP3_INIT(...) ((struct spa_audio_info_mp3) { __VA_ARGS__ }) /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_AUDIO_MP3_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/param/audio/mpegh-utils.h000066400000000000000000000031241511204443500302770ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2025 Carlos Rafael Giani */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_AUDIO_MPEGH_UTILS_H #define SPA_AUDIO_MPEGH_UTILS_H #include #include #include #include #ifdef __cplusplus extern "C" { #endif /** * \addtogroup spa_param * \{ */ #ifndef SPA_API_AUDIO_MPEGH_UTILS #ifdef SPA_API_IMPL #define SPA_API_AUDIO_MPEGH_UTILS SPA_API_IMPL #else #define SPA_API_AUDIO_MPEGH_UTILS static inline #endif #endif SPA_API_AUDIO_MPEGH_UTILS int spa_format_audio_mpegh_parse(const struct spa_pod *format, struct spa_audio_info_mpegh *info) { int res; res = spa_pod_parse_object(format, SPA_TYPE_OBJECT_Format, NULL, SPA_FORMAT_AUDIO_rate, SPA_POD_OPT_Int(&info->rate)); return res; } SPA_API_AUDIO_MPEGH_UTILS struct spa_pod * spa_format_audio_mpegh_build(struct spa_pod_builder *builder, uint32_t id, const struct spa_audio_info_mpegh *info) { struct spa_pod_frame f; spa_pod_builder_push_object(builder, &f, SPA_TYPE_OBJECT_Format, id); spa_pod_builder_add(builder, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_audio), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_mpegh), SPA_FORMAT_AUDIO_format, SPA_POD_Id(SPA_AUDIO_FORMAT_ENCODED), 0); if (info->rate != 0) spa_pod_builder_add(builder, SPA_FORMAT_AUDIO_rate, SPA_POD_Int(info->rate), 0); return (struct spa_pod*)spa_pod_builder_pop(builder, &f); } /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_AUDIO_MPEGH_UTILS_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/param/audio/mpegh.h000066400000000000000000000026201511204443500271410ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2025 Carlos Rafael Giani */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_AUDIO_MPEGH_H #define SPA_AUDIO_MPEGH_H #include #ifdef __cplusplus extern "C" { #endif /** * \addtogroup spa_param * \{ */ /** * MPEG-H 3D audio info. * * MPEG-H content is assumed to be provided in the form of an MPEG-H * 3D Audio Stream (MHAS). MHAS is a lightweight bitstream format that * encapsulates MPEG-H 3D Audio frames along with associated metadata. * It serves a similar role to the Annex B byte stream format used for * H.264, providing framing and synchronization for MPEG-H frames. * * MPEG-H is documented in the ISO/IEC 23008-3 specification. * MHAS is specified in ISO/IEC 23008-3, Clause 14. * * Note that unlike other formats, this one does not specify a channel * count. This is because MPEG-H is entity-based; it contains multiple * entities of different types (channel beds, audio objects etc.) which * do not map 1:1 to channels. The channel amount is determined by * decoders instead, based on the audio scene content and the target * playback system. */ struct spa_audio_info_mpegh { uint32_t rate; /*< sample rate */ }; #define SPA_AUDIO_INFO_MPEGH_INIT(...) ((struct spa_audio_info_mpegh) { __VA_ARGS__ }) /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_AUDIO_MPEGH_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/param/audio/opus.h000066400000000000000000000011071511204443500270260ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2023 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_AUDIO_OPUS_H #define SPA_AUDIO_OPUS_H #include #ifdef __cplusplus extern "C" { #endif /** * \addtogroup spa_param * \{ */ struct spa_audio_info_opus { uint32_t rate; /*< sample rate */ uint32_t channels; /*< number of channels */ }; #define SPA_AUDIO_INFO_OPUS_INIT(...) ((struct spa_audio_info_opus) { __VA_ARGS__ }) /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_AUDIO_OPUS_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/param/audio/ra-utils.h000066400000000000000000000033341511204443500276040ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2023 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_AUDIO_RA_UTILS_H #define SPA_AUDIO_RA_UTILS_H #include #include #include #include #ifdef __cplusplus extern "C" { #endif /** * \addtogroup spa_param * \{ */ #ifndef SPA_API_AUDIO_RA_UTILS #ifdef SPA_API_IMPL #define SPA_API_AUDIO_RA_UTILS SPA_API_IMPL #else #define SPA_API_AUDIO_RA_UTILS static inline #endif #endif SPA_API_AUDIO_RA_UTILS int spa_format_audio_ra_parse(const struct spa_pod *format, struct spa_audio_info_ra *info) { int res; res = spa_pod_parse_object(format, SPA_TYPE_OBJECT_Format, NULL, SPA_FORMAT_AUDIO_rate, SPA_POD_OPT_Int(&info->rate), SPA_FORMAT_AUDIO_channels, SPA_POD_OPT_Int(&info->channels)); return res; } SPA_API_AUDIO_RA_UTILS struct spa_pod * spa_format_audio_ra_build(struct spa_pod_builder *builder, uint32_t id, const struct spa_audio_info_ra *info) { struct spa_pod_frame f; spa_pod_builder_push_object(builder, &f, SPA_TYPE_OBJECT_Format, id); spa_pod_builder_add(builder, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_audio), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_ra), SPA_FORMAT_AUDIO_format, SPA_POD_Id(SPA_AUDIO_FORMAT_ENCODED), 0); if (info->rate != 0) spa_pod_builder_add(builder, SPA_FORMAT_AUDIO_rate, SPA_POD_Int(info->rate), 0); if (info->channels != 0) spa_pod_builder_add(builder, SPA_FORMAT_AUDIO_channels, SPA_POD_Int(info->channels), 0); return (struct spa_pod*)spa_pod_builder_pop(builder, &f); } /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_AUDIO_RA_UTILS_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/param/audio/ra.h000066400000000000000000000010731511204443500264440ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2023 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_AUDIO_RA_H #define SPA_AUDIO_RA_H #include #ifdef __cplusplus extern "C" { #endif /** * \addtogroup spa_param * \{ */ struct spa_audio_info_ra { uint32_t rate; /*< sample rate */ uint32_t channels; /*< number of channels */ }; #define SPA_AUDIO_INFO_RA_INIT(...) ((struct spa_audio_info_ra) { __VA_ARGS__ }) /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_AUDIO_RA_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/param/audio/raw-json.h000066400000000000000000000116761511204443500276140ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2024 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_AUDIO_RAW_JSON_H #define SPA_AUDIO_RAW_JSON_H #include #include #include #include #include #ifdef __cplusplus extern "C" { #endif /** * \addtogroup spa_param * \{ */ #ifndef SPA_API_AUDIO_RAW_JSON #ifdef SPA_API_IMPL #define SPA_API_AUDIO_RAW_JSON SPA_API_IMPL #else #define SPA_API_AUDIO_RAW_JSON static inline #endif #endif SPA_API_AUDIO_RAW_JSON int spa_audio_parse_position_n(const char *str, size_t len, uint32_t *position, uint32_t max_position, uint32_t *n_channels) { struct spa_json iter; char v[256]; uint32_t channels = 0; if (spa_json_begin_array_relax(&iter, str, len) <= 0) return 0; while (spa_json_get_string(&iter, v, sizeof(v)) > 0) { if (channels < max_position) position[channels] = spa_type_audio_channel_from_short_name(v); channels++; } *n_channels = channels; return channels; } SPA_API_AUDIO_RAW_JSON int spa_audio_parse_position(const char *str, size_t len, uint32_t *position, uint32_t *n_channels) { return spa_audio_parse_position_n(str, len, position, SPA_AUDIO_MAX_CHANNELS, n_channels); } SPA_API_AUDIO_RAW_JSON int spa_audio_parse_layout(const char *str, uint32_t *position, uint32_t max_position, uint32_t *n_channels) { struct spa_audio_layout_info l; uint32_t i; if (spa_audio_layout_info_parse_name(&l, sizeof(l), str) <= 0) return 0; for (i = 0; i < l.n_channels && i < max_position; i++) position[i] = l.position[i]; *n_channels = l.n_channels; return l.n_channels; } SPA_API_AUDIO_RAW_JSON int spa_audio_info_raw_ext_update(struct spa_audio_info_raw *info, size_t size, const char *key, const char *val, bool force) { uint32_t v; uint32_t max_position = SPA_AUDIO_INFO_RAW_MAX_POSITION(size); if (!SPA_AUDIO_INFO_RAW_VALID_SIZE(size)) return -EINVAL; if (spa_streq(key, SPA_KEY_AUDIO_FORMAT)) { if (force || info->format == 0) info->format = (enum spa_audio_format)spa_type_audio_format_from_short_name(val); } else if (spa_streq(key, SPA_KEY_AUDIO_RATE)) { if (spa_atou32(val, &v, 0) && (force || info->rate == 0)) info->rate = v; } else if (spa_streq(key, SPA_KEY_AUDIO_CHANNELS)) { if (spa_atou32(val, &v, 0) && (force || info->channels == 0)) { if (v > max_position) return -ECHRNG; info->channels = v; } } else if (spa_streq(key, SPA_KEY_AUDIO_LAYOUT)) { if (force || info->channels == 0) { if (spa_audio_parse_layout(val, info->position, max_position, &v) > 0) { if (v > max_position) return -ECHRNG; info->channels = v; SPA_FLAG_CLEAR(info->flags, SPA_AUDIO_FLAG_UNPOSITIONED); } } } else if (spa_streq(key, SPA_KEY_AUDIO_POSITION)) { if (force || info->channels == 0) { if (spa_audio_parse_position_n(val, strlen(val), info->position, max_position, &v) > 0) { if (v > max_position) return -ECHRNG; info->channels = v; SPA_FLAG_CLEAR(info->flags, SPA_AUDIO_FLAG_UNPOSITIONED); } } } return 0; } SPA_API_AUDIO_RAW_JSON int spa_audio_info_raw_update(struct spa_audio_info_raw *info, const char *key, const char *val, bool force) { return spa_audio_info_raw_ext_update(info, sizeof(*info), key, val, force); } SPA_API_AUDIO_RAW_JSON int spa_audio_info_raw_ext_init_dict_keys_va(struct spa_audio_info_raw *info, size_t size, const struct spa_dict *defaults, const struct spa_dict *dict, va_list args) { int res; if (!SPA_AUDIO_INFO_RAW_VALID_SIZE(size)) return -EINVAL; memset(info, 0, size); SPA_FLAG_SET(info->flags, SPA_AUDIO_FLAG_UNPOSITIONED); if (dict) { const char *val, *key; while ((key = va_arg(args, const char *))) { if ((val = spa_dict_lookup(dict, key)) == NULL) continue; if ((res = spa_audio_info_raw_ext_update(info, size, key, val, true)) < 0) return res; } } if (defaults) { const struct spa_dict_item *it; spa_dict_for_each(it, defaults) if ((res = spa_audio_info_raw_ext_update(info, size, it->key, it->value, false)) < 0) return res; } return 0; } SPA_API_AUDIO_RAW_JSON int SPA_SENTINEL spa_audio_info_raw_ext_init_dict_keys(struct spa_audio_info_raw *info, size_t size, const struct spa_dict *defaults, const struct spa_dict *dict, ...) { va_list args; int res; va_start(args, dict); res = spa_audio_info_raw_ext_init_dict_keys_va(info, size, defaults, dict, args); va_end(args); return res; } SPA_API_AUDIO_RAW_JSON int SPA_SENTINEL spa_audio_info_raw_init_dict_keys(struct spa_audio_info_raw *info, const struct spa_dict *defaults, const struct spa_dict *dict, ...) { va_list args; int res; va_start(args, dict); res = spa_audio_info_raw_ext_init_dict_keys_va(info, sizeof(*info), defaults, dict, args); va_end(args); return res; } /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_AUDIO_RAW_JSON_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/param/audio/raw-types.h000066400000000000000000000520541511204443500300020ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_AUDIO_RAW_TYPES_H #define SPA_AUDIO_RAW_TYPES_H #include #include #include #ifdef __cplusplus extern "C" { #endif /** * \addtogroup spa_param * \{ */ #ifndef SPA_API_AUDIO_RAW_TYPES #ifdef SPA_API_IMPL #define SPA_API_AUDIO_RAW_TYPES SPA_API_IMPL #else #define SPA_API_AUDIO_RAW_TYPES static inline #endif #endif #define SPA_TYPE_INFO_AudioFormat SPA_TYPE_INFO_ENUM_BASE "AudioFormat" #define SPA_TYPE_INFO_AUDIO_FORMAT_BASE SPA_TYPE_INFO_AudioFormat ":" static const struct spa_type_info spa_type_audio_format[] = { { SPA_AUDIO_FORMAT_UNKNOWN, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "UNKNOWN", NULL }, { SPA_AUDIO_FORMAT_ENCODED, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "ENCODED", NULL }, { SPA_AUDIO_FORMAT_S8, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "S8", NULL }, { SPA_AUDIO_FORMAT_U8, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "U8", NULL }, { SPA_AUDIO_FORMAT_S16_LE, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "S16LE", NULL }, { SPA_AUDIO_FORMAT_S16_BE, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "S16BE", NULL }, { SPA_AUDIO_FORMAT_U16_LE, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "U16LE", NULL }, { SPA_AUDIO_FORMAT_U16_BE, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "U16BE", NULL }, { SPA_AUDIO_FORMAT_S24_32_LE, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "S24_32LE", NULL }, { SPA_AUDIO_FORMAT_S24_32_BE, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "S24_32BE", NULL }, { SPA_AUDIO_FORMAT_U24_32_LE, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "U24_32LE", NULL }, { SPA_AUDIO_FORMAT_U24_32_BE, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "U24_32BE", NULL }, { SPA_AUDIO_FORMAT_S32_LE, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "S32LE", NULL }, { SPA_AUDIO_FORMAT_S32_BE, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "S32BE", NULL }, { SPA_AUDIO_FORMAT_U32_LE, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "U32LE", NULL }, { SPA_AUDIO_FORMAT_U32_BE, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "U32BE", NULL }, { SPA_AUDIO_FORMAT_S24_LE, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "S24LE", NULL }, { SPA_AUDIO_FORMAT_S24_BE, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "S24BE", NULL }, { SPA_AUDIO_FORMAT_U24_LE, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "U24LE", NULL }, { SPA_AUDIO_FORMAT_U24_BE, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "U24BE", NULL }, { SPA_AUDIO_FORMAT_S20_LE, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "S20LE", NULL }, { SPA_AUDIO_FORMAT_S20_BE, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "S20BE", NULL }, { SPA_AUDIO_FORMAT_U20_LE, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "U20LE", NULL }, { SPA_AUDIO_FORMAT_U20_BE, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "U20BE", NULL }, { SPA_AUDIO_FORMAT_S18_LE, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "S18LE", NULL }, { SPA_AUDIO_FORMAT_S18_BE, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "S18BE", NULL }, { SPA_AUDIO_FORMAT_U18_LE, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "U18LE", NULL }, { SPA_AUDIO_FORMAT_U18_BE, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "U18BE", NULL }, { SPA_AUDIO_FORMAT_F32_LE, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "F32LE", NULL }, { SPA_AUDIO_FORMAT_F32_BE, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "F32BE", NULL }, { SPA_AUDIO_FORMAT_F64_LE, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "F64LE", NULL }, { SPA_AUDIO_FORMAT_F64_BE, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "F64BE", NULL }, { SPA_AUDIO_FORMAT_ULAW, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "ULAW", NULL }, { SPA_AUDIO_FORMAT_ALAW, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "ALAW", NULL }, { SPA_AUDIO_FORMAT_U8P, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "U8P", NULL }, { SPA_AUDIO_FORMAT_S16P, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "S16P", NULL }, { SPA_AUDIO_FORMAT_S24_32P, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "S24_32P", NULL }, { SPA_AUDIO_FORMAT_S32P, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "S32P", NULL }, { SPA_AUDIO_FORMAT_S24P, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "S24P", NULL }, { SPA_AUDIO_FORMAT_F32P, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "F32P", NULL }, { SPA_AUDIO_FORMAT_F64P, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "F64P", NULL }, { SPA_AUDIO_FORMAT_S8P, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "S8P", NULL }, #if __BYTE_ORDER == __BIG_ENDIAN { SPA_AUDIO_FORMAT_S16_OE, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "S16OE", NULL }, { SPA_AUDIO_FORMAT_S16, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "S16", NULL }, { SPA_AUDIO_FORMAT_U16_OE, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "U16OE", NULL }, { SPA_AUDIO_FORMAT_U16, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "U16", NULL }, { SPA_AUDIO_FORMAT_S24_32_OE, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "S24_32OE", NULL }, { SPA_AUDIO_FORMAT_S24_32, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "S24_32", NULL }, { SPA_AUDIO_FORMAT_U24_32_OE, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "U24_32OE", NULL }, { SPA_AUDIO_FORMAT_U24_32, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "U24_32", NULL }, { SPA_AUDIO_FORMAT_S32_OE, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "S32OE", NULL }, { SPA_AUDIO_FORMAT_S32, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "S32", NULL }, { SPA_AUDIO_FORMAT_U32_OE, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "U32OE", NULL }, { SPA_AUDIO_FORMAT_U32, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "U32", NULL }, { SPA_AUDIO_FORMAT_S24_OE, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "S24OE", NULL }, { SPA_AUDIO_FORMAT_S24, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "S24", NULL }, { SPA_AUDIO_FORMAT_U24_OE, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "U24OE", NULL }, { SPA_AUDIO_FORMAT_U24, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "U24", NULL }, { SPA_AUDIO_FORMAT_S20_OE, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "S20OE", NULL }, { SPA_AUDIO_FORMAT_S20, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "S20", NULL }, { SPA_AUDIO_FORMAT_U20_OE, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "U20OE", NULL }, { SPA_AUDIO_FORMAT_U20, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "U20", NULL }, { SPA_AUDIO_FORMAT_S18_OE, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "S18OE", NULL }, { SPA_AUDIO_FORMAT_S18, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "S18", NULL }, { SPA_AUDIO_FORMAT_U18_OE, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "U18OE", NULL }, { SPA_AUDIO_FORMAT_U18, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "U18", NULL }, { SPA_AUDIO_FORMAT_F32_OE, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "F32OE", NULL }, { SPA_AUDIO_FORMAT_F32, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "F32", NULL }, { SPA_AUDIO_FORMAT_F64_OE, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "F64OE", NULL }, { SPA_AUDIO_FORMAT_F64, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "F64", NULL }, #elif __BYTE_ORDER == __LITTLE_ENDIAN { SPA_AUDIO_FORMAT_S16, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "S16", NULL }, { SPA_AUDIO_FORMAT_S16_OE, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "S16OE", NULL }, { SPA_AUDIO_FORMAT_U16, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "U16", NULL }, { SPA_AUDIO_FORMAT_U16_OE, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "U16OE", NULL }, { SPA_AUDIO_FORMAT_S24_32, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "S24_32", NULL }, { SPA_AUDIO_FORMAT_S24_32_OE, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "S24_32OE", NULL }, { SPA_AUDIO_FORMAT_U24_32, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "U24_32", NULL }, { SPA_AUDIO_FORMAT_U24_32_OE, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "U24_32OE", NULL }, { SPA_AUDIO_FORMAT_S32, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "S32", NULL }, { SPA_AUDIO_FORMAT_S32_OE, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "S32OE", NULL }, { SPA_AUDIO_FORMAT_U32, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "U32", NULL }, { SPA_AUDIO_FORMAT_U32_OE, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "U32OE", NULL }, { SPA_AUDIO_FORMAT_S24, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "S24", NULL }, { SPA_AUDIO_FORMAT_S24_OE, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "S24OE", NULL }, { SPA_AUDIO_FORMAT_U24, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "U24", NULL }, { SPA_AUDIO_FORMAT_U24_OE, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "U24OE", NULL }, { SPA_AUDIO_FORMAT_S20, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "S20", NULL }, { SPA_AUDIO_FORMAT_S20_OE, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "S20OE", NULL }, { SPA_AUDIO_FORMAT_U20, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "U20", NULL }, { SPA_AUDIO_FORMAT_U20_OE, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "U20OE", NULL }, { SPA_AUDIO_FORMAT_S18, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "S18", NULL }, { SPA_AUDIO_FORMAT_S18_OE, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "S18OE", NULL }, { SPA_AUDIO_FORMAT_U18, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "U18", NULL }, { SPA_AUDIO_FORMAT_U18_OE, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "U18OE", NULL }, { SPA_AUDIO_FORMAT_F32, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "F32", NULL }, { SPA_AUDIO_FORMAT_F32_OE, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "F32OE", NULL }, { SPA_AUDIO_FORMAT_F64, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "F64", NULL }, { SPA_AUDIO_FORMAT_F64_OE, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FORMAT_BASE "F64OE", NULL }, #endif { 0, 0, NULL, NULL }, }; SPA_API_AUDIO_RAW_TYPES uint32_t spa_type_audio_format_from_short_name(const char *name) { return spa_type_from_short_name(name, spa_type_audio_format, SPA_AUDIO_FORMAT_UNKNOWN); } SPA_API_AUDIO_RAW_TYPES const char * spa_type_audio_format_to_short_name(uint32_t type) { return spa_type_to_short_name(type, spa_type_audio_format, "UNKNOWN"); } #define SPA_TYPE_INFO_AudioFlags SPA_TYPE_INFO_FLAGS_BASE "AudioFlags" #define SPA_TYPE_INFO_AUDIO_FLAGS_BASE SPA_TYPE_INFO_AudioFlags ":" static const struct spa_type_info spa_type_audio_flags[] = { { SPA_AUDIO_FLAG_NONE, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FLAGS_BASE "none", NULL }, { SPA_AUDIO_FLAG_UNPOSITIONED, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_FLAGS_BASE "unpositioned", NULL }, { 0, 0, NULL, NULL }, }; #define SPA_TYPE_INFO_AudioChannel SPA_TYPE_INFO_ENUM_BASE "AudioChannel" #define SPA_TYPE_INFO_AUDIO_CHANNEL_BASE SPA_TYPE_INFO_AudioChannel ":" static const struct spa_type_info spa_type_audio_channel[] = { { SPA_AUDIO_CHANNEL_UNKNOWN, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "UNK", NULL }, { SPA_AUDIO_CHANNEL_NA, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "NA", NULL }, { SPA_AUDIO_CHANNEL_MONO, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "MONO", NULL }, { SPA_AUDIO_CHANNEL_FL, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "FL", NULL }, { SPA_AUDIO_CHANNEL_FR, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "FR", NULL }, { SPA_AUDIO_CHANNEL_FC, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "FC", NULL }, { SPA_AUDIO_CHANNEL_LFE, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "LFE", NULL }, { SPA_AUDIO_CHANNEL_SL, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "SL", NULL }, { SPA_AUDIO_CHANNEL_SR, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "SR", NULL }, { SPA_AUDIO_CHANNEL_FLC, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "FLC", NULL }, { SPA_AUDIO_CHANNEL_FRC, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "FRC", NULL }, { SPA_AUDIO_CHANNEL_RC, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "RC", NULL }, { SPA_AUDIO_CHANNEL_RL, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "RL", NULL }, { SPA_AUDIO_CHANNEL_RR, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "RR", NULL }, { SPA_AUDIO_CHANNEL_TC, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "TC", NULL }, { SPA_AUDIO_CHANNEL_TFL, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "TFL", NULL }, { SPA_AUDIO_CHANNEL_TFC, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "TFC", NULL }, { SPA_AUDIO_CHANNEL_TFR, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "TFR", NULL }, { SPA_AUDIO_CHANNEL_TRL, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "TRL", NULL }, { SPA_AUDIO_CHANNEL_TRC, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "TRC", NULL }, { SPA_AUDIO_CHANNEL_TRR, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "TRR", NULL }, { SPA_AUDIO_CHANNEL_RLC, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "RLC", NULL }, { SPA_AUDIO_CHANNEL_RRC, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "RRC", NULL }, { SPA_AUDIO_CHANNEL_FLW, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "FLW", NULL }, { SPA_AUDIO_CHANNEL_FRW, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "FRW", NULL }, { SPA_AUDIO_CHANNEL_LFE2, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "LFE2", NULL }, { SPA_AUDIO_CHANNEL_FLH, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "FLH", NULL }, { SPA_AUDIO_CHANNEL_FCH, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "FCH", NULL }, { SPA_AUDIO_CHANNEL_FRH, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "FRH", NULL }, { SPA_AUDIO_CHANNEL_TFLC, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "TFLC", NULL }, { SPA_AUDIO_CHANNEL_TFRC, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "TFRC", NULL }, { SPA_AUDIO_CHANNEL_TSL, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "TSL", NULL }, { SPA_AUDIO_CHANNEL_TSR, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "TSR", NULL }, { SPA_AUDIO_CHANNEL_LLFE, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "LLFE", NULL }, { SPA_AUDIO_CHANNEL_RLFE, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "RLFE", NULL }, { SPA_AUDIO_CHANNEL_BC, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "BC", NULL }, { SPA_AUDIO_CHANNEL_BLC, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "BLC", NULL }, { SPA_AUDIO_CHANNEL_BRC, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "BRC", NULL }, { SPA_AUDIO_CHANNEL_AUX0, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "AUX0", NULL }, { SPA_AUDIO_CHANNEL_AUX1, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "AUX1", NULL }, { SPA_AUDIO_CHANNEL_AUX2, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "AUX2", NULL }, { SPA_AUDIO_CHANNEL_AUX3, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "AUX3", NULL }, { SPA_AUDIO_CHANNEL_AUX4, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "AUX4", NULL }, { SPA_AUDIO_CHANNEL_AUX5, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "AUX5", NULL }, { SPA_AUDIO_CHANNEL_AUX6, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "AUX6", NULL }, { SPA_AUDIO_CHANNEL_AUX7, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "AUX7", NULL }, { SPA_AUDIO_CHANNEL_AUX8, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "AUX8", NULL }, { SPA_AUDIO_CHANNEL_AUX9, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "AUX9", NULL }, { SPA_AUDIO_CHANNEL_AUX10, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "AUX10", NULL }, { SPA_AUDIO_CHANNEL_AUX11, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "AUX11", NULL }, { SPA_AUDIO_CHANNEL_AUX12, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "AUX12", NULL }, { SPA_AUDIO_CHANNEL_AUX13, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "AUX13", NULL }, { SPA_AUDIO_CHANNEL_AUX14, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "AUX14", NULL }, { SPA_AUDIO_CHANNEL_AUX15, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "AUX15", NULL }, { SPA_AUDIO_CHANNEL_AUX16, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "AUX16", NULL }, { SPA_AUDIO_CHANNEL_AUX17, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "AUX17", NULL }, { SPA_AUDIO_CHANNEL_AUX18, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "AUX18", NULL }, { SPA_AUDIO_CHANNEL_AUX19, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "AUX19", NULL }, { SPA_AUDIO_CHANNEL_AUX20, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "AUX20", NULL }, { SPA_AUDIO_CHANNEL_AUX21, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "AUX21", NULL }, { SPA_AUDIO_CHANNEL_AUX22, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "AUX22", NULL }, { SPA_AUDIO_CHANNEL_AUX23, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "AUX23", NULL }, { SPA_AUDIO_CHANNEL_AUX24, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "AUX24", NULL }, { SPA_AUDIO_CHANNEL_AUX25, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "AUX25", NULL }, { SPA_AUDIO_CHANNEL_AUX26, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "AUX26", NULL }, { SPA_AUDIO_CHANNEL_AUX27, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "AUX27", NULL }, { SPA_AUDIO_CHANNEL_AUX28, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "AUX28", NULL }, { SPA_AUDIO_CHANNEL_AUX29, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "AUX29", NULL }, { SPA_AUDIO_CHANNEL_AUX30, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "AUX30", NULL }, { SPA_AUDIO_CHANNEL_AUX31, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "AUX31", NULL }, { SPA_AUDIO_CHANNEL_AUX32, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "AUX32", NULL }, { SPA_AUDIO_CHANNEL_AUX33, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "AUX33", NULL }, { SPA_AUDIO_CHANNEL_AUX34, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "AUX34", NULL }, { SPA_AUDIO_CHANNEL_AUX35, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "AUX35", NULL }, { SPA_AUDIO_CHANNEL_AUX36, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "AUX36", NULL }, { SPA_AUDIO_CHANNEL_AUX37, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "AUX37", NULL }, { SPA_AUDIO_CHANNEL_AUX38, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "AUX38", NULL }, { SPA_AUDIO_CHANNEL_AUX39, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "AUX39", NULL }, { SPA_AUDIO_CHANNEL_AUX40, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "AUX40", NULL }, { SPA_AUDIO_CHANNEL_AUX41, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "AUX41", NULL }, { SPA_AUDIO_CHANNEL_AUX42, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "AUX42", NULL }, { SPA_AUDIO_CHANNEL_AUX43, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "AUX43", NULL }, { SPA_AUDIO_CHANNEL_AUX44, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "AUX44", NULL }, { SPA_AUDIO_CHANNEL_AUX45, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "AUX45", NULL }, { SPA_AUDIO_CHANNEL_AUX46, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "AUX46", NULL }, { SPA_AUDIO_CHANNEL_AUX47, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "AUX47", NULL }, { SPA_AUDIO_CHANNEL_AUX48, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "AUX48", NULL }, { SPA_AUDIO_CHANNEL_AUX49, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "AUX49", NULL }, { SPA_AUDIO_CHANNEL_AUX50, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "AUX50", NULL }, { SPA_AUDIO_CHANNEL_AUX51, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "AUX51", NULL }, { SPA_AUDIO_CHANNEL_AUX52, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "AUX52", NULL }, { SPA_AUDIO_CHANNEL_AUX53, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "AUX53", NULL }, { SPA_AUDIO_CHANNEL_AUX54, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "AUX54", NULL }, { SPA_AUDIO_CHANNEL_AUX55, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "AUX55", NULL }, { SPA_AUDIO_CHANNEL_AUX56, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "AUX56", NULL }, { SPA_AUDIO_CHANNEL_AUX57, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "AUX57", NULL }, { SPA_AUDIO_CHANNEL_AUX58, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "AUX58", NULL }, { SPA_AUDIO_CHANNEL_AUX59, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "AUX59", NULL }, { SPA_AUDIO_CHANNEL_AUX60, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "AUX60", NULL }, { SPA_AUDIO_CHANNEL_AUX61, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "AUX61", NULL }, { SPA_AUDIO_CHANNEL_AUX62, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "AUX62", NULL }, { SPA_AUDIO_CHANNEL_AUX63, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_CHANNEL_BASE "AUX63", NULL }, { 0, 0, NULL, NULL }, }; SPA_API_AUDIO_RAW_TYPES uint32_t spa_type_audio_channel_from_short_name(const char *name) { uint32_t res; if (spa_strstartswith(name, "AUX")) { if (spa_atou32(name+3, &res, 10) && res < 0x1000) res = SPA_AUDIO_CHANNEL_AUX0 + res; else res = SPA_AUDIO_CHANNEL_UNKNOWN; } else { res = spa_type_from_short_name(name, spa_type_audio_channel, SPA_AUDIO_CHANNEL_UNKNOWN); } return res; } SPA_API_AUDIO_RAW_TYPES const char * spa_type_audio_channel_to_short_name(uint32_t type) { return spa_type_to_short_name(type, spa_type_audio_channel, "UNK"); } SPA_API_AUDIO_RAW_TYPES const char * spa_type_audio_channel_make_short_name(uint32_t type, char *buf, size_t size, const char *unknown) { if (SPA_AUDIO_CHANNEL_IS_AUX(type)) { snprintf(buf, size, "AUX%u", type - SPA_AUDIO_CHANNEL_AUX0); } else { const char *str = spa_type_to_short_name(type, spa_type_audio_channel, NULL); if (str == NULL) return unknown; snprintf(buf, size, "%.7s", str); } return buf; } #define SPA_TYPE_INFO_AudioVolumeRampScale SPA_TYPE_INFO_ENUM_BASE "AudioVolumeRampScale" #define SPA_TYPE_INFO_AUDIO_VOLUME_RAMP_SCALE_BASE SPA_TYPE_INFO_AudioVolumeRampScale ":" static const struct spa_type_info spa_type_audio_volume_ramp_scale[] = { { SPA_AUDIO_VOLUME_RAMP_INVALID, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_VOLUME_RAMP_SCALE_BASE "INVALID", NULL }, { SPA_AUDIO_VOLUME_RAMP_LINEAR, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_VOLUME_RAMP_SCALE_BASE "LINEAR", NULL }, { SPA_AUDIO_VOLUME_RAMP_CUBIC, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_VOLUME_RAMP_SCALE_BASE "CUBIC", NULL }, { 0, 0, NULL, NULL }, }; /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_AUDIO_RAW_TYPES_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/param/audio/raw-utils.h000066400000000000000000000066251511204443500300010ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_AUDIO_RAW_UTILS_H #define SPA_AUDIO_RAW_UTILS_H #include #include #include #include #include #ifdef __cplusplus extern "C" { #endif /** * \addtogroup spa_param * \{ */ #ifndef SPA_API_AUDIO_RAW_UTILS #ifdef SPA_API_IMPL #define SPA_API_AUDIO_RAW_UTILS SPA_API_IMPL #else #define SPA_API_AUDIO_RAW_UTILS static inline #endif #endif SPA_API_AUDIO_RAW_UTILS int spa_format_audio_raw_ext_parse(const struct spa_pod *format, struct spa_audio_info_raw *info, size_t size) { struct spa_pod *position = NULL; int res; uint32_t max_position = SPA_AUDIO_INFO_RAW_MAX_POSITION(size); if (!SPA_AUDIO_INFO_RAW_VALID_SIZE(size)) return -EINVAL; info->flags = 0; res = spa_pod_parse_object(format, SPA_TYPE_OBJECT_Format, NULL, SPA_FORMAT_AUDIO_format, SPA_POD_OPT_Id(&info->format), SPA_FORMAT_AUDIO_rate, SPA_POD_OPT_Int(&info->rate), SPA_FORMAT_AUDIO_channels, SPA_POD_OPT_Int(&info->channels), SPA_FORMAT_AUDIO_position, SPA_POD_OPT_Pod(&position)); if (info->channels > max_position) return -ECHRNG; if (position == NULL || spa_pod_copy_array(position, SPA_TYPE_Id, info->position, max_position) != info->channels) { SPA_FLAG_SET(info->flags, SPA_AUDIO_FLAG_UNPOSITIONED); spa_memzero(info->position, max_position * sizeof(info->position[0])); } return res; } SPA_API_AUDIO_RAW_UTILS int spa_format_audio_raw_parse(const struct spa_pod *format, struct spa_audio_info_raw *info) { return spa_format_audio_raw_ext_parse(format, info, sizeof(*info)); } SPA_API_AUDIO_RAW_UTILS struct spa_pod * spa_format_audio_raw_ext_build(struct spa_pod_builder *builder, uint32_t id, const struct spa_audio_info_raw *info, size_t size) { struct spa_pod_frame f; uint32_t max_position = SPA_AUDIO_INFO_RAW_MAX_POSITION(size); if (!SPA_AUDIO_INFO_RAW_VALID_SIZE(size)) { errno = EINVAL; return NULL; } spa_pod_builder_push_object(builder, &f, SPA_TYPE_OBJECT_Format, id); spa_pod_builder_add(builder, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_audio), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), 0); if (info->format != SPA_AUDIO_FORMAT_UNKNOWN) spa_pod_builder_add(builder, SPA_FORMAT_AUDIO_format, SPA_POD_Id(info->format), 0); if (info->rate != 0) spa_pod_builder_add(builder, SPA_FORMAT_AUDIO_rate, SPA_POD_Int(info->rate), 0); if (info->channels != 0) { spa_pod_builder_add(builder, SPA_FORMAT_AUDIO_channels, SPA_POD_Int(info->channels), 0); /* we drop the positions here when we can't read all of them. This is * really a malformed spa_audio_info structure. */ if (!SPA_FLAG_IS_SET(info->flags, SPA_AUDIO_FLAG_UNPOSITIONED) && info->channels <= max_position) { spa_pod_builder_add(builder, SPA_FORMAT_AUDIO_position, SPA_POD_Array(sizeof(uint32_t), SPA_TYPE_Id, info->channels, info->position), 0); } } return (struct spa_pod*)spa_pod_builder_pop(builder, &f); } SPA_API_AUDIO_RAW_UTILS struct spa_pod * spa_format_audio_raw_build(struct spa_pod_builder *builder, uint32_t id, const struct spa_audio_info_raw *info) { return spa_format_audio_raw_ext_build(builder, id, info, sizeof(*info)); } /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_AUDIO_RAW_UTILS_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/param/audio/raw.h000066400000000000000000000264431511204443500266430ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_AUDIO_RAW_H #define SPA_AUDIO_RAW_H #include #include #ifdef __cplusplus extern "C" { #endif /** * \addtogroup spa_param * \{ */ /* This is the max number of channels, changing this will change the * size of some helper structures. This value should be at least 64 */ #ifndef SPA_AUDIO_MAX_CHANNELS #define SPA_AUDIO_MAX_CHANNELS 64u #endif enum spa_audio_format { SPA_AUDIO_FORMAT_UNKNOWN, SPA_AUDIO_FORMAT_ENCODED, /* interleaved formats */ SPA_AUDIO_FORMAT_START_Interleaved = 0x100, SPA_AUDIO_FORMAT_S8, SPA_AUDIO_FORMAT_U8, SPA_AUDIO_FORMAT_S16_LE, SPA_AUDIO_FORMAT_S16_BE, SPA_AUDIO_FORMAT_U16_LE, SPA_AUDIO_FORMAT_U16_BE, SPA_AUDIO_FORMAT_S24_32_LE, SPA_AUDIO_FORMAT_S24_32_BE, SPA_AUDIO_FORMAT_U24_32_LE, SPA_AUDIO_FORMAT_U24_32_BE, SPA_AUDIO_FORMAT_S32_LE, SPA_AUDIO_FORMAT_S32_BE, SPA_AUDIO_FORMAT_U32_LE, SPA_AUDIO_FORMAT_U32_BE, SPA_AUDIO_FORMAT_S24_LE, SPA_AUDIO_FORMAT_S24_BE, SPA_AUDIO_FORMAT_U24_LE, SPA_AUDIO_FORMAT_U24_BE, SPA_AUDIO_FORMAT_S20_LE, SPA_AUDIO_FORMAT_S20_BE, SPA_AUDIO_FORMAT_U20_LE, SPA_AUDIO_FORMAT_U20_BE, SPA_AUDIO_FORMAT_S18_LE, SPA_AUDIO_FORMAT_S18_BE, SPA_AUDIO_FORMAT_U18_LE, SPA_AUDIO_FORMAT_U18_BE, SPA_AUDIO_FORMAT_F32_LE, SPA_AUDIO_FORMAT_F32_BE, SPA_AUDIO_FORMAT_F64_LE, SPA_AUDIO_FORMAT_F64_BE, SPA_AUDIO_FORMAT_ULAW, SPA_AUDIO_FORMAT_ALAW, /* planar formats */ SPA_AUDIO_FORMAT_START_Planar = 0x200, SPA_AUDIO_FORMAT_U8P, SPA_AUDIO_FORMAT_S16P, SPA_AUDIO_FORMAT_S24_32P, SPA_AUDIO_FORMAT_S32P, SPA_AUDIO_FORMAT_S24P, SPA_AUDIO_FORMAT_F32P, SPA_AUDIO_FORMAT_F64P, SPA_AUDIO_FORMAT_S8P, /* other formats start here */ SPA_AUDIO_FORMAT_START_Other = 0x400, /* Aliases */ /* DSP formats */ SPA_AUDIO_FORMAT_DSP_S32 = SPA_AUDIO_FORMAT_S24_32P, SPA_AUDIO_FORMAT_DSP_F32 = SPA_AUDIO_FORMAT_F32P, SPA_AUDIO_FORMAT_DSP_F64 = SPA_AUDIO_FORMAT_F64P, /* native endian */ #if __BYTE_ORDER == __BIG_ENDIAN SPA_AUDIO_FORMAT_S16 = SPA_AUDIO_FORMAT_S16_BE, SPA_AUDIO_FORMAT_U16 = SPA_AUDIO_FORMAT_U16_BE, SPA_AUDIO_FORMAT_S24_32 = SPA_AUDIO_FORMAT_S24_32_BE, SPA_AUDIO_FORMAT_U24_32 = SPA_AUDIO_FORMAT_U24_32_BE, SPA_AUDIO_FORMAT_S32 = SPA_AUDIO_FORMAT_S32_BE, SPA_AUDIO_FORMAT_U32 = SPA_AUDIO_FORMAT_U32_BE, SPA_AUDIO_FORMAT_S24 = SPA_AUDIO_FORMAT_S24_BE, SPA_AUDIO_FORMAT_U24 = SPA_AUDIO_FORMAT_U24_BE, SPA_AUDIO_FORMAT_S20 = SPA_AUDIO_FORMAT_S20_BE, SPA_AUDIO_FORMAT_U20 = SPA_AUDIO_FORMAT_U20_BE, SPA_AUDIO_FORMAT_S18 = SPA_AUDIO_FORMAT_S18_BE, SPA_AUDIO_FORMAT_U18 = SPA_AUDIO_FORMAT_U18_BE, SPA_AUDIO_FORMAT_F32 = SPA_AUDIO_FORMAT_F32_BE, SPA_AUDIO_FORMAT_F64 = SPA_AUDIO_FORMAT_F64_BE, SPA_AUDIO_FORMAT_S16_OE = SPA_AUDIO_FORMAT_S16_LE, SPA_AUDIO_FORMAT_U16_OE = SPA_AUDIO_FORMAT_U16_LE, SPA_AUDIO_FORMAT_S24_32_OE = SPA_AUDIO_FORMAT_S24_32_LE, SPA_AUDIO_FORMAT_U24_32_OE = SPA_AUDIO_FORMAT_U24_32_LE, SPA_AUDIO_FORMAT_S32_OE = SPA_AUDIO_FORMAT_S32_LE, SPA_AUDIO_FORMAT_U32_OE = SPA_AUDIO_FORMAT_U32_LE, SPA_AUDIO_FORMAT_S24_OE = SPA_AUDIO_FORMAT_S24_LE, SPA_AUDIO_FORMAT_U24_OE = SPA_AUDIO_FORMAT_U24_LE, SPA_AUDIO_FORMAT_S20_OE = SPA_AUDIO_FORMAT_S20_LE, SPA_AUDIO_FORMAT_U20_OE = SPA_AUDIO_FORMAT_U20_LE, SPA_AUDIO_FORMAT_S18_OE = SPA_AUDIO_FORMAT_S18_LE, SPA_AUDIO_FORMAT_U18_OE = SPA_AUDIO_FORMAT_U18_LE, SPA_AUDIO_FORMAT_F32_OE = SPA_AUDIO_FORMAT_F32_LE, SPA_AUDIO_FORMAT_F64_OE = SPA_AUDIO_FORMAT_F64_LE, #elif __BYTE_ORDER == __LITTLE_ENDIAN SPA_AUDIO_FORMAT_S16 = SPA_AUDIO_FORMAT_S16_LE, SPA_AUDIO_FORMAT_U16 = SPA_AUDIO_FORMAT_U16_LE, SPA_AUDIO_FORMAT_S24_32 = SPA_AUDIO_FORMAT_S24_32_LE, SPA_AUDIO_FORMAT_U24_32 = SPA_AUDIO_FORMAT_U24_32_LE, SPA_AUDIO_FORMAT_S32 = SPA_AUDIO_FORMAT_S32_LE, SPA_AUDIO_FORMAT_U32 = SPA_AUDIO_FORMAT_U32_LE, SPA_AUDIO_FORMAT_S24 = SPA_AUDIO_FORMAT_S24_LE, SPA_AUDIO_FORMAT_U24 = SPA_AUDIO_FORMAT_U24_LE, SPA_AUDIO_FORMAT_S20 = SPA_AUDIO_FORMAT_S20_LE, SPA_AUDIO_FORMAT_U20 = SPA_AUDIO_FORMAT_U20_LE, SPA_AUDIO_FORMAT_S18 = SPA_AUDIO_FORMAT_S18_LE, SPA_AUDIO_FORMAT_U18 = SPA_AUDIO_FORMAT_U18_LE, SPA_AUDIO_FORMAT_F32 = SPA_AUDIO_FORMAT_F32_LE, SPA_AUDIO_FORMAT_F64 = SPA_AUDIO_FORMAT_F64_LE, SPA_AUDIO_FORMAT_S16_OE = SPA_AUDIO_FORMAT_S16_BE, SPA_AUDIO_FORMAT_U16_OE = SPA_AUDIO_FORMAT_U16_BE, SPA_AUDIO_FORMAT_S24_32_OE = SPA_AUDIO_FORMAT_S24_32_BE, SPA_AUDIO_FORMAT_U24_32_OE = SPA_AUDIO_FORMAT_U24_32_BE, SPA_AUDIO_FORMAT_S32_OE = SPA_AUDIO_FORMAT_S32_BE, SPA_AUDIO_FORMAT_U32_OE = SPA_AUDIO_FORMAT_U32_BE, SPA_AUDIO_FORMAT_S24_OE = SPA_AUDIO_FORMAT_S24_BE, SPA_AUDIO_FORMAT_U24_OE = SPA_AUDIO_FORMAT_U24_BE, SPA_AUDIO_FORMAT_S20_OE = SPA_AUDIO_FORMAT_S20_BE, SPA_AUDIO_FORMAT_U20_OE = SPA_AUDIO_FORMAT_U20_BE, SPA_AUDIO_FORMAT_S18_OE = SPA_AUDIO_FORMAT_S18_BE, SPA_AUDIO_FORMAT_U18_OE = SPA_AUDIO_FORMAT_U18_BE, SPA_AUDIO_FORMAT_F32_OE = SPA_AUDIO_FORMAT_F32_BE, SPA_AUDIO_FORMAT_F64_OE = SPA_AUDIO_FORMAT_F64_BE, #endif }; #define SPA_AUDIO_FORMAT_IS_INTERLEAVED(fmt) ((fmt) > SPA_AUDIO_FORMAT_START_Interleaved && (fmt) < SPA_AUDIO_FORMAT_START_Planar) #define SPA_AUDIO_FORMAT_IS_PLANAR(fmt) ((fmt) > SPA_AUDIO_FORMAT_START_Planar && (fmt) < SPA_AUDIO_FORMAT_START_Other) enum spa_audio_channel { SPA_AUDIO_CHANNEL_UNKNOWN, /**< unspecified */ SPA_AUDIO_CHANNEL_NA, /**< N/A, silent */ SPA_AUDIO_CHANNEL_MONO, /**< mono stream */ SPA_AUDIO_CHANNEL_FL, /**< front left */ SPA_AUDIO_CHANNEL_FR, /**< front right */ SPA_AUDIO_CHANNEL_FC, /**< front center */ SPA_AUDIO_CHANNEL_LFE, /**< LFE */ SPA_AUDIO_CHANNEL_SL, /**< side left */ SPA_AUDIO_CHANNEL_SR, /**< side right */ SPA_AUDIO_CHANNEL_FLC, /**< front left center */ SPA_AUDIO_CHANNEL_FRC, /**< front right center */ SPA_AUDIO_CHANNEL_RC, /**< rear center */ SPA_AUDIO_CHANNEL_RL, /**< rear left */ SPA_AUDIO_CHANNEL_RR, /**< rear right */ SPA_AUDIO_CHANNEL_TC, /**< top center */ SPA_AUDIO_CHANNEL_TFL, /**< top front left */ SPA_AUDIO_CHANNEL_TFC, /**< top front center */ SPA_AUDIO_CHANNEL_TFR, /**< top front right */ SPA_AUDIO_CHANNEL_TRL, /**< top rear left */ SPA_AUDIO_CHANNEL_TRC, /**< top rear center */ SPA_AUDIO_CHANNEL_TRR, /**< top rear right */ SPA_AUDIO_CHANNEL_RLC, /**< rear left center */ SPA_AUDIO_CHANNEL_RRC, /**< rear right center */ SPA_AUDIO_CHANNEL_FLW, /**< front left wide */ SPA_AUDIO_CHANNEL_FRW, /**< front right wide */ SPA_AUDIO_CHANNEL_LFE2, /**< LFE 2 */ SPA_AUDIO_CHANNEL_FLH, /**< front left high */ SPA_AUDIO_CHANNEL_FCH, /**< front center high */ SPA_AUDIO_CHANNEL_FRH, /**< front right high */ SPA_AUDIO_CHANNEL_TFLC, /**< top front left center */ SPA_AUDIO_CHANNEL_TFRC, /**< top front right center */ SPA_AUDIO_CHANNEL_TSL, /**< top side left */ SPA_AUDIO_CHANNEL_TSR, /**< top side right */ SPA_AUDIO_CHANNEL_LLFE, /**< left LFE */ SPA_AUDIO_CHANNEL_RLFE, /**< right LFE */ SPA_AUDIO_CHANNEL_BC, /**< bottom center */ SPA_AUDIO_CHANNEL_BLC, /**< bottom left center */ SPA_AUDIO_CHANNEL_BRC, /**< bottom right center */ SPA_AUDIO_CHANNEL_START_Aux = 0x1000, /**< aux channels */ SPA_AUDIO_CHANNEL_AUX0 = SPA_AUDIO_CHANNEL_START_Aux, SPA_AUDIO_CHANNEL_AUX1, SPA_AUDIO_CHANNEL_AUX2, SPA_AUDIO_CHANNEL_AUX3, SPA_AUDIO_CHANNEL_AUX4, SPA_AUDIO_CHANNEL_AUX5, SPA_AUDIO_CHANNEL_AUX6, SPA_AUDIO_CHANNEL_AUX7, SPA_AUDIO_CHANNEL_AUX8, SPA_AUDIO_CHANNEL_AUX9, SPA_AUDIO_CHANNEL_AUX10, SPA_AUDIO_CHANNEL_AUX11, SPA_AUDIO_CHANNEL_AUX12, SPA_AUDIO_CHANNEL_AUX13, SPA_AUDIO_CHANNEL_AUX14, SPA_AUDIO_CHANNEL_AUX15, SPA_AUDIO_CHANNEL_AUX16, SPA_AUDIO_CHANNEL_AUX17, SPA_AUDIO_CHANNEL_AUX18, SPA_AUDIO_CHANNEL_AUX19, SPA_AUDIO_CHANNEL_AUX20, SPA_AUDIO_CHANNEL_AUX21, SPA_AUDIO_CHANNEL_AUX22, SPA_AUDIO_CHANNEL_AUX23, SPA_AUDIO_CHANNEL_AUX24, SPA_AUDIO_CHANNEL_AUX25, SPA_AUDIO_CHANNEL_AUX26, SPA_AUDIO_CHANNEL_AUX27, SPA_AUDIO_CHANNEL_AUX28, SPA_AUDIO_CHANNEL_AUX29, SPA_AUDIO_CHANNEL_AUX30, SPA_AUDIO_CHANNEL_AUX31, SPA_AUDIO_CHANNEL_AUX32, SPA_AUDIO_CHANNEL_AUX33, SPA_AUDIO_CHANNEL_AUX34, SPA_AUDIO_CHANNEL_AUX35, SPA_AUDIO_CHANNEL_AUX36, SPA_AUDIO_CHANNEL_AUX37, SPA_AUDIO_CHANNEL_AUX38, SPA_AUDIO_CHANNEL_AUX39, SPA_AUDIO_CHANNEL_AUX40, SPA_AUDIO_CHANNEL_AUX41, SPA_AUDIO_CHANNEL_AUX42, SPA_AUDIO_CHANNEL_AUX43, SPA_AUDIO_CHANNEL_AUX44, SPA_AUDIO_CHANNEL_AUX45, SPA_AUDIO_CHANNEL_AUX46, SPA_AUDIO_CHANNEL_AUX47, SPA_AUDIO_CHANNEL_AUX48, SPA_AUDIO_CHANNEL_AUX49, SPA_AUDIO_CHANNEL_AUX50, SPA_AUDIO_CHANNEL_AUX51, SPA_AUDIO_CHANNEL_AUX52, SPA_AUDIO_CHANNEL_AUX53, SPA_AUDIO_CHANNEL_AUX54, SPA_AUDIO_CHANNEL_AUX55, SPA_AUDIO_CHANNEL_AUX56, SPA_AUDIO_CHANNEL_AUX57, SPA_AUDIO_CHANNEL_AUX58, SPA_AUDIO_CHANNEL_AUX59, SPA_AUDIO_CHANNEL_AUX60, SPA_AUDIO_CHANNEL_AUX61, SPA_AUDIO_CHANNEL_AUX62, SPA_AUDIO_CHANNEL_AUX63, SPA_AUDIO_CHANNEL_LAST_Aux = 0x1fff, /**< aux channels */ SPA_AUDIO_CHANNEL_START_Custom = 0x10000, }; #define SPA_AUDIO_CHANNEL_IS_AUX(ch) ((ch)>=SPA_AUDIO_CHANNEL_START_Aux && (ch)<=SPA_AUDIO_CHANNEL_LAST_Aux) enum spa_audio_volume_ramp_scale { SPA_AUDIO_VOLUME_RAMP_INVALID, SPA_AUDIO_VOLUME_RAMP_LINEAR, SPA_AUDIO_VOLUME_RAMP_CUBIC, }; /** Extra audio flags */ #define SPA_AUDIO_FLAG_NONE (0) /*< no valid flag */ #define SPA_AUDIO_FLAG_UNPOSITIONED (1 << 0) /*< the position array explicitly * contains unpositioned channels. */ /** Audio information description. You can assume when you receive this structure * that there is enought padding to accomodate all channel positions in case the * channel count is more than SPA_AUDIO_MAX_CHANNELS. */ struct spa_audio_info_raw { enum spa_audio_format format; /*< format, one of enum spa_audio_format */ uint32_t flags; /*< extra flags */ uint32_t rate; /*< sample rate */ uint32_t channels; /*< number of channels. This can be more than SPA_AUDIO_MAX_CHANNELS * and you may assume there is enough padding for the extra * channel positions. */ uint32_t position[SPA_AUDIO_MAX_CHANNELS]; /*< channel position from enum spa_audio_channel */ /* padding follows here when channels > SPA_AUDIO_MAX_CHANNELS */ }; #define SPA_AUDIO_INFO_RAW_INIT(...) ((struct spa_audio_info_raw) { __VA_ARGS__ }) #define SPA_AUDIO_INFO_RAW_MAX_POSITION(size) (((size)-offsetof(struct spa_audio_info_raw,position))/sizeof(uint32_t)) #define SPA_AUDIO_INFO_RAW_VALID_SIZE(size) ((size) >= offsetof(struct spa_audio_info_raw, position)) #define SPA_KEY_AUDIO_FORMAT "audio.format" /**< an audio format as string, * Ex. "S16LE" */ #define SPA_KEY_AUDIO_CHANNEL "audio.channel" /**< an audio channel as string, * Ex. "FL" */ #define SPA_KEY_AUDIO_CHANNELS "audio.channels" /**< an audio channel count as int */ #define SPA_KEY_AUDIO_RATE "audio.rate" /**< an audio sample rate as int */ #define SPA_KEY_AUDIO_LAYOUT "audio.layout" /**< channel positions as predefined layout */ #define SPA_KEY_AUDIO_POSITION "audio.position" /**< channel positions as comma separated list * of channels ex. "FL,FR" */ #define SPA_KEY_AUDIO_ALLOWED_RATES "audio.allowed-rates" /**< a list of allowed samplerates * ex. "[ 44100 48000 ]" */ /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_AUDIO_RAW_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/param/audio/truehd-utils.h000066400000000000000000000034311511204443500304730ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2025 Carlos Rafael Giani */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_AUDIO_TRUEHD_UTILS_H #define SPA_AUDIO_TRUEHD_UTILS_H #include #include #include #include #ifdef __cplusplus extern "C" { #endif /** * \addtogroup spa_param * \{ */ #ifndef SPA_API_AUDIO_TRUEHD_UTILS #ifdef SPA_API_IMPL #define SPA_API_AUDIO_TRUEHD_UTILS SPA_API_IMPL #else #define SPA_API_AUDIO_TRUEHD_UTILS static inline #endif #endif SPA_API_AUDIO_TRUEHD_UTILS int spa_format_audio_truehd_parse(const struct spa_pod *format, struct spa_audio_info_truehd *info) { int res; res = spa_pod_parse_object(format, SPA_TYPE_OBJECT_Format, NULL, SPA_FORMAT_AUDIO_rate, SPA_POD_OPT_Int(&info->rate), SPA_FORMAT_AUDIO_channels, SPA_POD_OPT_Int(&info->channels)); return res; } SPA_API_AUDIO_TRUEHD_UTILS struct spa_pod * spa_format_audio_truehd_build(struct spa_pod_builder *builder, uint32_t id, const struct spa_audio_info_truehd *info) { struct spa_pod_frame f; spa_pod_builder_push_object(builder, &f, SPA_TYPE_OBJECT_Format, id); spa_pod_builder_add(builder, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_audio), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_truehd), SPA_FORMAT_AUDIO_format, SPA_POD_Id(SPA_AUDIO_FORMAT_ENCODED), 0); if (info->rate != 0) spa_pod_builder_add(builder, SPA_FORMAT_AUDIO_rate, SPA_POD_Int(info->rate), 0); if (info->channels != 0) spa_pod_builder_add(builder, SPA_FORMAT_AUDIO_channels, SPA_POD_Int(info->channels), 0); return (struct spa_pod*)spa_pod_builder_pop(builder, &f); } /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_AUDIO_TRUEHD_UTILS_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/param/audio/truehd.h000066400000000000000000000011731511204443500273360ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2025 Carlos Rafael Giani */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_AUDIO_TRUEHD_H #define SPA_AUDIO_TRUEHD_H #include #ifdef __cplusplus extern "C" { #endif /** * \addtogroup spa_param * \{ */ /** Dolby TrueHD audio info. */ struct spa_audio_info_truehd { uint32_t rate; /*< sample rate */ uint32_t channels; /*< number of channels */ }; #define SPA_AUDIO_INFO_TRUEHD_INIT(...) ((struct spa_audio_info_truehd) { __VA_ARGS__ }) /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_AUDIO_TRUEHD_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/param/audio/type-info.h000066400000000000000000000010131511204443500277460ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_AUDIO_TYPES_H #define SPA_AUDIO_TYPES_H #include #include #include #include #include #include #include #include #endif /* SPA_AUDIO_TYPES_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/param/audio/vorbis-utils.h000066400000000000000000000034241511204443500305060ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2023 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_AUDIO_VORBIS_UTILS_H #define SPA_AUDIO_VORBIS_UTILS_H #include #include #include #include #ifdef __cplusplus extern "C" { #endif /** * \addtogroup spa_param * \{ */ #ifndef SPA_API_AUDIO_VORBIS_UTILS #ifdef SPA_API_IMPL #define SPA_API_AUDIO_VORBIS_UTILS SPA_API_IMPL #else #define SPA_API_AUDIO_VORBIS_UTILS static inline #endif #endif SPA_API_AUDIO_VORBIS_UTILS int spa_format_audio_vorbis_parse(const struct spa_pod *format, struct spa_audio_info_vorbis *info) { int res; res = spa_pod_parse_object(format, SPA_TYPE_OBJECT_Format, NULL, SPA_FORMAT_AUDIO_rate, SPA_POD_OPT_Int(&info->rate), SPA_FORMAT_AUDIO_channels, SPA_POD_OPT_Int(&info->channels)); return res; } SPA_API_AUDIO_VORBIS_UTILS struct spa_pod * spa_format_audio_vorbis_build(struct spa_pod_builder *builder, uint32_t id, const struct spa_audio_info_vorbis *info) { struct spa_pod_frame f; spa_pod_builder_push_object(builder, &f, SPA_TYPE_OBJECT_Format, id); spa_pod_builder_add(builder, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_audio), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_vorbis), SPA_FORMAT_AUDIO_format, SPA_POD_Id(SPA_AUDIO_FORMAT_ENCODED), 0); if (info->rate != 0) spa_pod_builder_add(builder, SPA_FORMAT_AUDIO_rate, SPA_POD_Int(info->rate), 0); if (info->channels != 0) spa_pod_builder_add(builder, SPA_FORMAT_AUDIO_channels, SPA_POD_Int(info->channels), 0); return (struct spa_pod*)spa_pod_builder_pop(builder, &f); } /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_AUDIO_VORBIS_UTILS_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/param/audio/vorbis.h000066400000000000000000000011231511204443500273420ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2023 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_AUDIO_VORBIS_H #define SPA_AUDIO_VORBIS_H #include #ifdef __cplusplus extern "C" { #endif /** * \addtogroup spa_param * \{ */ struct spa_audio_info_vorbis { uint32_t rate; /*< sample rate */ uint32_t channels; /*< number of channels */ }; #define SPA_AUDIO_INFO_VORBIS_INIT(...) ((struct spa_audio_info_vorbis) { __VA_ARGS__ }) /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_AUDIO_VORBIS_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/param/audio/wma-types.h000066400000000000000000000027501511204443500277730ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_AUDIO_WMA_TYPES_H #define SPA_AUDIO_WMA_TYPES_H #include #include #ifdef __cplusplus extern "C" { #endif /** * \addtogroup spa_param * \{ */ #define SPA_TYPE_INFO_AudioWMAProfile SPA_TYPE_INFO_ENUM_BASE "AudioWMAProfile" #define SPA_TYPE_INFO_AUDIO_WMA_PROFILE_BASE SPA_TYPE_INFO_AudioWMAProfile ":" static const struct spa_type_info spa_type_audio_wma_profile[] = { { SPA_AUDIO_WMA_PROFILE_UNKNOWN, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_WMA_PROFILE_BASE "UNKNOWN", NULL }, { SPA_AUDIO_WMA_PROFILE_WMA7, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_WMA_PROFILE_BASE "WMA7", NULL }, { SPA_AUDIO_WMA_PROFILE_WMA8, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_WMA_PROFILE_BASE "WMA8", NULL }, { SPA_AUDIO_WMA_PROFILE_WMA9, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_WMA_PROFILE_BASE "WMA9", NULL }, { SPA_AUDIO_WMA_PROFILE_WMA10, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_WMA_PROFILE_BASE "WMA10", NULL }, { SPA_AUDIO_WMA_PROFILE_WMA9_PRO, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_WMA_PROFILE_BASE "WMA9-Pro", NULL }, { SPA_AUDIO_WMA_PROFILE_WMA9_LOSSLESS, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_WMA_PROFILE_BASE "WMA9-Lossless", NULL }, { SPA_AUDIO_WMA_PROFILE_WMA10_LOSSLESS, SPA_TYPE_Int, SPA_TYPE_INFO_AUDIO_WMA_PROFILE_BASE "WMA10-Lossless", NULL }, { 0, 0, NULL, NULL }, }; /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_AUDIO_WMA_TYPES_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/param/audio/wma-utils.h000066400000000000000000000044341511204443500277700ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2023 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_AUDIO_WMA_UTILS_H #define SPA_AUDIO_WMA_UTILS_H #include #include #include #include #ifdef __cplusplus extern "C" { #endif /** * \addtogroup spa_param * \{ */ #ifndef SPA_API_AUDIO_WMA_UTILS #ifdef SPA_API_IMPL #define SPA_API_AUDIO_WMA_UTILS SPA_API_IMPL #else #define SPA_API_AUDIO_WMA_UTILS static inline #endif #endif SPA_API_AUDIO_WMA_UTILS int spa_format_audio_wma_parse(const struct spa_pod *format, struct spa_audio_info_wma *info) { int res; res = spa_pod_parse_object(format, SPA_TYPE_OBJECT_Format, NULL, SPA_FORMAT_AUDIO_rate, SPA_POD_OPT_Int(&info->rate), SPA_FORMAT_AUDIO_channels, SPA_POD_OPT_Int(&info->channels), SPA_FORMAT_AUDIO_bitrate, SPA_POD_OPT_Int(&info->bitrate), SPA_FORMAT_AUDIO_blockAlign, SPA_POD_OPT_Int(&info->block_align), SPA_FORMAT_AUDIO_WMA_profile, SPA_POD_OPT_Id(&info->profile)); return res; } SPA_API_AUDIO_WMA_UTILS struct spa_pod * spa_format_audio_wma_build(struct spa_pod_builder *builder, uint32_t id, const struct spa_audio_info_wma *info) { struct spa_pod_frame f; spa_pod_builder_push_object(builder, &f, SPA_TYPE_OBJECT_Format, id); spa_pod_builder_add(builder, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_audio), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_wma), SPA_FORMAT_AUDIO_format, SPA_POD_Id(SPA_AUDIO_FORMAT_ENCODED), 0); if (info->rate != 0) spa_pod_builder_add(builder, SPA_FORMAT_AUDIO_rate, SPA_POD_Int(info->rate), 0); if (info->channels != 0) spa_pod_builder_add(builder, SPA_FORMAT_AUDIO_channels, SPA_POD_Int(info->channels), 0); if (info->bitrate != 0) spa_pod_builder_add(builder, SPA_FORMAT_AUDIO_bitrate, SPA_POD_Int(info->bitrate), 0); if (info->block_align != 0) spa_pod_builder_add(builder, SPA_FORMAT_AUDIO_blockAlign, SPA_POD_Int(info->block_align), 0); if (info->profile != 0) spa_pod_builder_add(builder, SPA_FORMAT_AUDIO_WMA_profile, SPA_POD_Id(info->profile), 0); return (struct spa_pod*)spa_pod_builder_pop(builder, &f); } /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_AUDIO_WMA_UTILS_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/param/audio/wma.h000066400000000000000000000020441511204443500266250ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2023 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_AUDIO_WMA_H #define SPA_AUDIO_WMA_H #include #ifdef __cplusplus extern "C" { #endif /** * \addtogroup spa_param * \{ */ enum spa_audio_wma_profile { SPA_AUDIO_WMA_PROFILE_UNKNOWN, SPA_AUDIO_WMA_PROFILE_WMA7, SPA_AUDIO_WMA_PROFILE_WMA8, SPA_AUDIO_WMA_PROFILE_WMA9, SPA_AUDIO_WMA_PROFILE_WMA10, SPA_AUDIO_WMA_PROFILE_WMA9_PRO, SPA_AUDIO_WMA_PROFILE_WMA9_LOSSLESS, SPA_AUDIO_WMA_PROFILE_WMA10_LOSSLESS, SPA_AUDIO_WMA_PROFILE_CUSTOM = 0x10000, }; struct spa_audio_info_wma { uint32_t rate; /*< sample rate */ uint32_t channels; /*< number of channels */ uint32_t bitrate; /*< stream bitrate */ uint32_t block_align; /*< block alignment */ enum spa_audio_wma_profile profile; /*< WMA profile */ }; #define SPA_AUDIO_INFO_WMA_INIT(...) ((struct spa_audio_info_wma) { __VA_ARGS__ }) /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_AUDIO_WMA_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/param/bluetooth/000077500000000000000000000000001511204443500265745ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/param/bluetooth/audio.h000066400000000000000000000026031511204443500300470ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2020 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_BLUETOOTH_AUDIO_H #define SPA_BLUETOOTH_AUDIO_H #ifdef __cplusplus extern "C" { #endif /** * \addtogroup spa_param * \{ */ enum spa_bluetooth_audio_codec { SPA_BLUETOOTH_AUDIO_CODEC_START, /* A2DP */ SPA_BLUETOOTH_AUDIO_CODEC_SBC, SPA_BLUETOOTH_AUDIO_CODEC_SBC_XQ, SPA_BLUETOOTH_AUDIO_CODEC_MPEG, SPA_BLUETOOTH_AUDIO_CODEC_AAC, SPA_BLUETOOTH_AUDIO_CODEC_AAC_ELD, SPA_BLUETOOTH_AUDIO_CODEC_APTX, SPA_BLUETOOTH_AUDIO_CODEC_APTX_HD, SPA_BLUETOOTH_AUDIO_CODEC_LDAC, SPA_BLUETOOTH_AUDIO_CODEC_APTX_LL, SPA_BLUETOOTH_AUDIO_CODEC_APTX_LL_DUPLEX, SPA_BLUETOOTH_AUDIO_CODEC_FASTSTREAM, SPA_BLUETOOTH_AUDIO_CODEC_FASTSTREAM_DUPLEX, SPA_BLUETOOTH_AUDIO_CODEC_LC3PLUS_HR, SPA_BLUETOOTH_AUDIO_CODEC_OPUS_05, SPA_BLUETOOTH_AUDIO_CODEC_OPUS_05_51, SPA_BLUETOOTH_AUDIO_CODEC_OPUS_05_71, SPA_BLUETOOTH_AUDIO_CODEC_OPUS_05_DUPLEX, SPA_BLUETOOTH_AUDIO_CODEC_OPUS_05_PRO, SPA_BLUETOOTH_AUDIO_CODEC_OPUS_G, /* HFP */ SPA_BLUETOOTH_AUDIO_CODEC_CVSD = 0x100, SPA_BLUETOOTH_AUDIO_CODEC_MSBC, SPA_BLUETOOTH_AUDIO_CODEC_LC3_SWB, SPA_BLUETOOTH_AUDIO_CODEC_LC3_A127, /* BAP */ SPA_BLUETOOTH_AUDIO_CODEC_LC3 = 0x200, /* ASHA */ SPA_BLUETOOTH_AUDIO_CODEC_G722 = 0x300, }; /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_BLUETOOTH_AUDIO_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/param/bluetooth/type-info.h000066400000000000000000000066631511204443500306720ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_BLUETOOTH_TYPES_H #define SPA_BLUETOOTH_TYPES_H #include #ifdef __cplusplus extern "C" { #endif /** * \addtogroup spa_param * \{ */ #define SPA_TYPE_INFO_BluetoothAudioCodec SPA_TYPE_INFO_ENUM_BASE "BluetoothAudioCodec" #define SPA_TYPE_INFO_BLUETOOTH_AUDIO_CODEC_BASE SPA_TYPE_INFO_BluetoothAudioCodec ":" static const struct spa_type_info spa_type_bluetooth_audio_codec[] = { /* A2DP */ { SPA_BLUETOOTH_AUDIO_CODEC_SBC, SPA_TYPE_Int, SPA_TYPE_INFO_BLUETOOTH_AUDIO_CODEC_BASE "sbc", NULL }, { SPA_BLUETOOTH_AUDIO_CODEC_SBC_XQ, SPA_TYPE_Int, SPA_TYPE_INFO_BLUETOOTH_AUDIO_CODEC_BASE "sbc_xq", NULL }, { SPA_BLUETOOTH_AUDIO_CODEC_MPEG, SPA_TYPE_Int, SPA_TYPE_INFO_BLUETOOTH_AUDIO_CODEC_BASE "mpeg", NULL }, { SPA_BLUETOOTH_AUDIO_CODEC_AAC, SPA_TYPE_Int, SPA_TYPE_INFO_BLUETOOTH_AUDIO_CODEC_BASE "aac", NULL }, { SPA_BLUETOOTH_AUDIO_CODEC_AAC_ELD, SPA_TYPE_Int, SPA_TYPE_INFO_BLUETOOTH_AUDIO_CODEC_BASE "aac_eld", NULL }, { SPA_BLUETOOTH_AUDIO_CODEC_APTX, SPA_TYPE_Int, SPA_TYPE_INFO_BLUETOOTH_AUDIO_CODEC_BASE "aptx", NULL }, { SPA_BLUETOOTH_AUDIO_CODEC_APTX_HD, SPA_TYPE_Int, SPA_TYPE_INFO_BLUETOOTH_AUDIO_CODEC_BASE "aptx_hd", NULL }, { SPA_BLUETOOTH_AUDIO_CODEC_LDAC, SPA_TYPE_Int, SPA_TYPE_INFO_BLUETOOTH_AUDIO_CODEC_BASE "ldac", NULL }, { SPA_BLUETOOTH_AUDIO_CODEC_APTX_LL, SPA_TYPE_Int, SPA_TYPE_INFO_BLUETOOTH_AUDIO_CODEC_BASE "aptx_ll", NULL }, { SPA_BLUETOOTH_AUDIO_CODEC_APTX_LL_DUPLEX, SPA_TYPE_Int, SPA_TYPE_INFO_BLUETOOTH_AUDIO_CODEC_BASE "aptx_ll_duplex", NULL }, { SPA_BLUETOOTH_AUDIO_CODEC_FASTSTREAM, SPA_TYPE_Int, SPA_TYPE_INFO_BLUETOOTH_AUDIO_CODEC_BASE "faststream", NULL }, { SPA_BLUETOOTH_AUDIO_CODEC_FASTSTREAM_DUPLEX, SPA_TYPE_Int, SPA_TYPE_INFO_BLUETOOTH_AUDIO_CODEC_BASE "faststream_duplex", NULL }, { SPA_BLUETOOTH_AUDIO_CODEC_LC3PLUS_HR, SPA_TYPE_Int, SPA_TYPE_INFO_BLUETOOTH_AUDIO_CODEC_BASE "lc3plus_hr", NULL }, { SPA_BLUETOOTH_AUDIO_CODEC_OPUS_05, SPA_TYPE_Int, SPA_TYPE_INFO_BLUETOOTH_AUDIO_CODEC_BASE "opus_05", NULL }, { SPA_BLUETOOTH_AUDIO_CODEC_OPUS_05_51, SPA_TYPE_Int, SPA_TYPE_INFO_BLUETOOTH_AUDIO_CODEC_BASE "opus_05_51", NULL }, { SPA_BLUETOOTH_AUDIO_CODEC_OPUS_05_71, SPA_TYPE_Int, SPA_TYPE_INFO_BLUETOOTH_AUDIO_CODEC_BASE "opus_05_71", NULL }, { SPA_BLUETOOTH_AUDIO_CODEC_OPUS_05_DUPLEX, SPA_TYPE_Int, SPA_TYPE_INFO_BLUETOOTH_AUDIO_CODEC_BASE "opus_05_duplex", NULL }, { SPA_BLUETOOTH_AUDIO_CODEC_OPUS_05_PRO, SPA_TYPE_Int, SPA_TYPE_INFO_BLUETOOTH_AUDIO_CODEC_BASE "opus_05_pro", NULL }, { SPA_BLUETOOTH_AUDIO_CODEC_OPUS_G, SPA_TYPE_Int, SPA_TYPE_INFO_BLUETOOTH_AUDIO_CODEC_BASE "opus_g", NULL }, { SPA_BLUETOOTH_AUDIO_CODEC_CVSD, SPA_TYPE_Int, SPA_TYPE_INFO_BLUETOOTH_AUDIO_CODEC_BASE "cvsd", NULL }, { SPA_BLUETOOTH_AUDIO_CODEC_MSBC, SPA_TYPE_Int, SPA_TYPE_INFO_BLUETOOTH_AUDIO_CODEC_BASE "msbc", NULL }, { SPA_BLUETOOTH_AUDIO_CODEC_LC3_SWB, SPA_TYPE_Int, SPA_TYPE_INFO_BLUETOOTH_AUDIO_CODEC_BASE "lc3_swb", NULL }, { SPA_BLUETOOTH_AUDIO_CODEC_LC3_A127, SPA_TYPE_Int, SPA_TYPE_INFO_BLUETOOTH_AUDIO_CODEC_BASE "lc3_a127", NULL }, { SPA_BLUETOOTH_AUDIO_CODEC_LC3, SPA_TYPE_Int, SPA_TYPE_INFO_BLUETOOTH_AUDIO_CODEC_BASE "lc3", NULL }, { SPA_BLUETOOTH_AUDIO_CODEC_G722, SPA_TYPE_Int, SPA_TYPE_INFO_BLUETOOTH_AUDIO_CODEC_BASE "g722", NULL }, { 0, 0, NULL, NULL }, }; /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_BLUETOOTH_TYPES_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/param/buffers-types.h000066400000000000000000000052331511204443500275410ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_PARAM_BUFFERS_TYPES_H #define SPA_PARAM_BUFFERS_TYPES_H #include #include #include #ifdef __cplusplus extern "C" { #endif /** * \addtogroup spa_param * \{ */ #define SPA_TYPE_INFO_PARAM_Meta SPA_TYPE_INFO_PARAM_BASE "Meta" #define SPA_TYPE_INFO_PARAM_META_BASE SPA_TYPE_INFO_PARAM_Meta ":" static const struct spa_type_info spa_type_param_meta[] = { { SPA_PARAM_META_START, SPA_TYPE_Id, SPA_TYPE_INFO_PARAM_META_BASE, spa_type_param }, { SPA_PARAM_META_type, SPA_TYPE_Id, SPA_TYPE_INFO_PARAM_META_BASE "type", spa_type_meta_type }, { SPA_PARAM_META_size, SPA_TYPE_Int, SPA_TYPE_INFO_PARAM_META_BASE "size", NULL }, { 0, 0, NULL, NULL }, }; /** Base for parameters that describe IO areas to exchange data, * control and properties with a node. */ #define SPA_TYPE_INFO_PARAM_IO SPA_TYPE_INFO_PARAM_BASE "IO" #define SPA_TYPE_INFO_PARAM_IO_BASE SPA_TYPE_INFO_PARAM_IO ":" static const struct spa_type_info spa_type_param_io[] = { { SPA_PARAM_IO_START, SPA_TYPE_Id, SPA_TYPE_INFO_PARAM_IO_BASE, spa_type_param, }, { SPA_PARAM_IO_id, SPA_TYPE_Id, SPA_TYPE_INFO_PARAM_IO_BASE "id", spa_type_io }, { SPA_PARAM_IO_size, SPA_TYPE_Int, SPA_TYPE_INFO_PARAM_IO_BASE "size", NULL }, { 0, 0, NULL, NULL }, }; #define SPA_TYPE_INFO_PARAM_Buffers SPA_TYPE_INFO_PARAM_BASE "Buffers" #define SPA_TYPE_INFO_PARAM_BUFFERS_BASE SPA_TYPE_INFO_PARAM_Buffers ":" #define SPA_TYPE_INFO_PARAM_BlockInfo SPA_TYPE_INFO_PARAM_BUFFERS_BASE "BlockInfo" #define SPA_TYPE_INFO_PARAM_BLOCK_INFO_BASE SPA_TYPE_INFO_PARAM_BlockInfo ":" static const struct spa_type_info spa_type_param_buffers[] = { { SPA_PARAM_BUFFERS_START, SPA_TYPE_Id, SPA_TYPE_INFO_PARAM_BUFFERS_BASE, spa_type_param, }, { SPA_PARAM_BUFFERS_buffers, SPA_TYPE_Int, SPA_TYPE_INFO_PARAM_BUFFERS_BASE "buffers", NULL }, { SPA_PARAM_BUFFERS_blocks, SPA_TYPE_Int, SPA_TYPE_INFO_PARAM_BUFFERS_BASE "blocks", NULL }, { SPA_PARAM_BUFFERS_size, SPA_TYPE_Int, SPA_TYPE_INFO_PARAM_BLOCK_INFO_BASE "size", NULL }, { SPA_PARAM_BUFFERS_stride, SPA_TYPE_Int, SPA_TYPE_INFO_PARAM_BLOCK_INFO_BASE "stride", NULL }, { SPA_PARAM_BUFFERS_align, SPA_TYPE_Int, SPA_TYPE_INFO_PARAM_BLOCK_INFO_BASE "align", NULL }, { SPA_PARAM_BUFFERS_dataType, SPA_TYPE_Int, SPA_TYPE_INFO_PARAM_BLOCK_INFO_BASE "dataType", NULL }, { SPA_PARAM_BUFFERS_metaType, SPA_TYPE_Int, SPA_TYPE_INFO_PARAM_BLOCK_INFO_BASE "metaType", NULL }, { 0, 0, NULL, NULL }, }; /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_PARAM_BUFFERS_TYPES_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/param/buffers.h000066400000000000000000000030751511204443500264010ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_PARAM_BUFFERS_H #define SPA_PARAM_BUFFERS_H #include #ifdef __cplusplus extern "C" { #endif /** * \addtogroup spa_param * \{ */ /** properties for SPA_TYPE_OBJECT_ParamBuffers */ enum spa_param_buffers { SPA_PARAM_BUFFERS_START, SPA_PARAM_BUFFERS_buffers, /**< number of buffers (Int) */ SPA_PARAM_BUFFERS_blocks, /**< number of data blocks per buffer (Int) */ SPA_PARAM_BUFFERS_size, /**< size of a data block memory (Int)*/ SPA_PARAM_BUFFERS_stride, /**< stride of data block memory (Int) */ SPA_PARAM_BUFFERS_align, /**< alignment of data block memory (Int) */ SPA_PARAM_BUFFERS_dataType, /**< possible memory types (flags choice Int, mask of enum spa_data_type) */ SPA_PARAM_BUFFERS_metaType, /**< required meta data types (Int, mask of enum spa_meta_type) */ }; /** properties for SPA_TYPE_OBJECT_ParamMeta */ enum spa_param_meta { SPA_PARAM_META_START, SPA_PARAM_META_type, /**< the metadata, one of enum spa_meta_type (Id enum spa_meta_type) */ SPA_PARAM_META_size, /**< the expected maximum size the meta (Int) */ SPA_PARAM_META_features, /**< meta data features (Features Int) */ }; /** properties for SPA_TYPE_OBJECT_ParamIO */ enum spa_param_io { SPA_PARAM_IO_START, SPA_PARAM_IO_id, /**< type ID, uniquely identifies the io area (Id enum spa_io_type) */ SPA_PARAM_IO_size, /**< size of the io area (Int) */ }; /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_PARAM_BUFFERS_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/param/dict-types.h000066400000000000000000000015271511204443500270320ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_PARAM_DICT_TYPES_H #define SPA_PARAM_DICT_TYPES_H #include #include #include #ifdef __cplusplus extern "C" { #endif /** * \addtogroup spa_param * \{ */ #define SPA_TYPE_INFO_PARAM_Dict SPA_TYPE_INFO_PARAM_BASE "Dict" #define SPA_TYPE_INFO_PARAM_DICT_BASE SPA_TYPE_INFO_PARAM_Dict ":" static const struct spa_type_info spa_type_param_dict[] = { { SPA_PARAM_DICT_START, SPA_TYPE_Id, SPA_TYPE_INFO_PARAM_DICT_BASE, spa_type_param, }, { SPA_PARAM_DICT_info, SPA_TYPE_Struct, SPA_TYPE_INFO_PARAM_DICT_BASE "info", NULL, }, { 0, 0, NULL, NULL }, }; /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_PARAM_DICT_TYPES_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/param/dict-utils.h000066400000000000000000000061061511204443500270240ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2023 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_PARAM_DICT_UTILS_H #define SPA_PARAM_DICT_UTILS_H #include #include #include #include #include #include #include #ifdef __cplusplus extern "C" { #endif /** * \addtogroup spa_param * \{ */ #ifndef SPA_API_DICT_UTILS #ifdef SPA_API_IMPL #define SPA_API_DICT_UTILS SPA_API_IMPL #else #define SPA_API_DICT_UTILS static inline #endif #endif SPA_API_DICT_UTILS int spa_param_dict_compare(const struct spa_pod *a, const struct spa_pod *b) { return spa_pod_memcmp(a, b); } SPA_API_DICT_UTILS struct spa_pod * spa_param_dict_build_dict(struct spa_pod_builder *builder, uint32_t id, struct spa_dict *dict) { struct spa_pod_frame f[2]; uint32_t i, n_items; spa_pod_builder_push_object(builder, &f[0], SPA_TYPE_OBJECT_ParamDict, id); n_items = dict ? dict->n_items : 0; spa_pod_builder_prop(builder, SPA_PARAM_DICT_info, SPA_POD_PROP_FLAG_HINT_DICT); spa_pod_builder_push_struct(builder, &f[1]); spa_pod_builder_int(builder, n_items); for (i = 0; i < n_items; i++) { spa_pod_builder_string(builder, dict->items[i].key); spa_pod_builder_string(builder, dict->items[i].value); } spa_pod_builder_pop(builder, &f[1]); return (struct spa_pod*)spa_pod_builder_pop(builder, &f[0]); } SPA_API_DICT_UTILS struct spa_pod * spa_param_dict_build_info(struct spa_pod_builder *builder, uint32_t id, struct spa_param_dict_info *info) { struct spa_pod_frame f; spa_pod_builder_push_object(builder, &f, SPA_TYPE_OBJECT_ParamDict, id); spa_pod_builder_add(builder, SPA_PARAM_DICT_info, SPA_POD_PROP_FLAG_HINT_DICT); spa_pod_builder_primitive(builder, info->info); return (struct spa_pod*)spa_pod_builder_pop(builder, &f); } SPA_API_DICT_UTILS int spa_param_dict_parse(const struct spa_pod *dict, struct spa_param_dict_info *info, size_t size) { memset(info, 0, size); return spa_pod_parse_object(dict, SPA_TYPE_OBJECT_ParamDict, NULL, SPA_PARAM_DICT_info, SPA_POD_PodStruct(&info->info)); } SPA_API_DICT_UTILS int spa_param_dict_info_parse(const struct spa_param_dict_info *info, size_t size, struct spa_dict *dict, struct spa_dict_item *items) { struct spa_pod_parser prs; uint32_t n, n_items; const char *key, *value; struct spa_pod_frame f[1]; spa_pod_parser_pod(&prs, info->info); if (spa_pod_parser_push_struct(&prs, &f[0]) < 0 || spa_pod_parser_get_int(&prs, (int32_t*)&n_items) < 0) return -EINVAL; if (items == NULL) { dict->n_items = n_items; return 0; } n_items = SPA_MIN(dict->n_items, n_items); for (n = 0; n < n_items; n++) { if (spa_pod_parser_get(&prs, SPA_POD_String(&key), SPA_POD_String(&value), NULL) < 0) break; if (key == NULL || value == NULL) return -EINVAL; items[n].key = key; items[n].value = value; } dict->items = items; spa_pod_parser_pop(&prs, &f[0]); return 0; } /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_PARAM_DICT_UTILS_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/param/dict.h000066400000000000000000000013161511204443500256640ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2025 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_PARAM_DICT_H #define SPA_PARAM_DICT_H #include #ifdef __cplusplus extern "C" { #endif /** * \addtogroup spa_param * \{ */ /** properties for SPA_TYPE_OBJECT_ParamDict */ enum spa_param_dict { SPA_PARAM_DICT_START, SPA_PARAM_DICT_info, /**< Struct( * Int: n_items * (String: key * String: value)* * ) */ }; /** helper structure for managing info objects */ struct spa_param_dict_info { const struct spa_pod *info; }; /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_PARAM_DICT_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/param/format-types.h000066400000000000000000000261551511204443500274030ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_PARAM_FORMAT_TYPES_H #define SPA_PARAM_FORMAT_TYPES_H #include #include #include #include #include #ifdef __cplusplus extern "C" { #endif /** * \addtogroup spa_param * \{ */ #define SPA_TYPE_INFO_Format SPA_TYPE_INFO_PARAM_BASE "Format" #define SPA_TYPE_INFO_FORMAT_BASE SPA_TYPE_INFO_Format ":" #define SPA_TYPE_INFO_MediaType SPA_TYPE_INFO_ENUM_BASE "MediaType" #define SPA_TYPE_INFO_MEDIA_TYPE_BASE SPA_TYPE_INFO_MediaType ":" static const struct spa_type_info spa_type_media_type[] = { { SPA_MEDIA_TYPE_unknown, SPA_TYPE_Int, SPA_TYPE_INFO_MEDIA_TYPE_BASE "unknown", NULL }, { SPA_MEDIA_TYPE_audio, SPA_TYPE_Int, SPA_TYPE_INFO_MEDIA_TYPE_BASE "audio", NULL }, { SPA_MEDIA_TYPE_video, SPA_TYPE_Int, SPA_TYPE_INFO_MEDIA_TYPE_BASE "video", NULL }, { SPA_MEDIA_TYPE_image, SPA_TYPE_Int, SPA_TYPE_INFO_MEDIA_TYPE_BASE "image", NULL }, { SPA_MEDIA_TYPE_binary, SPA_TYPE_Int, SPA_TYPE_INFO_MEDIA_TYPE_BASE "binary", NULL }, { SPA_MEDIA_TYPE_stream, SPA_TYPE_Int, SPA_TYPE_INFO_MEDIA_TYPE_BASE "stream", NULL }, { SPA_MEDIA_TYPE_application, SPA_TYPE_Int, SPA_TYPE_INFO_MEDIA_TYPE_BASE "application", NULL }, { 0, 0, NULL, NULL }, }; #define SPA_TYPE_INFO_MediaSubtype SPA_TYPE_INFO_ENUM_BASE "MediaSubtype" #define SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE SPA_TYPE_INFO_MediaSubtype ":" static const struct spa_type_info spa_type_media_subtype[] = { { SPA_MEDIA_SUBTYPE_unknown, SPA_TYPE_Int, SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "unknown", NULL }, /* generic subtypes */ { SPA_MEDIA_SUBTYPE_raw, SPA_TYPE_Int, SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "raw", NULL }, { SPA_MEDIA_SUBTYPE_dsp, SPA_TYPE_Int, SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "dsp", NULL }, { SPA_MEDIA_SUBTYPE_iec958, SPA_TYPE_Int, SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "iec958", NULL }, { SPA_MEDIA_SUBTYPE_dsd, SPA_TYPE_Int, SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "dsd", NULL }, /* audio subtypes */ { SPA_MEDIA_SUBTYPE_mp3, SPA_TYPE_Int, SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "mp3", NULL }, { SPA_MEDIA_SUBTYPE_aac, SPA_TYPE_Int, SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "aac", NULL }, { SPA_MEDIA_SUBTYPE_vorbis, SPA_TYPE_Int, SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "vorbis", NULL }, { SPA_MEDIA_SUBTYPE_wma, SPA_TYPE_Int, SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "wma", NULL }, { SPA_MEDIA_SUBTYPE_ra, SPA_TYPE_Int, SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "ra", NULL }, { SPA_MEDIA_SUBTYPE_sbc, SPA_TYPE_Int, SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "sbc", NULL }, { SPA_MEDIA_SUBTYPE_adpcm, SPA_TYPE_Int, SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "adpcm", NULL }, { SPA_MEDIA_SUBTYPE_g723, SPA_TYPE_Int, SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "g723", NULL }, { SPA_MEDIA_SUBTYPE_g726, SPA_TYPE_Int, SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "g726", NULL }, { SPA_MEDIA_SUBTYPE_g729, SPA_TYPE_Int, SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "g729", NULL }, { SPA_MEDIA_SUBTYPE_amr, SPA_TYPE_Int, SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "amr", NULL }, { SPA_MEDIA_SUBTYPE_gsm, SPA_TYPE_Int, SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "gsm", NULL }, { SPA_MEDIA_SUBTYPE_alac, SPA_TYPE_Int, SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "alac", NULL }, { SPA_MEDIA_SUBTYPE_flac, SPA_TYPE_Int, SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "flac", NULL }, { SPA_MEDIA_SUBTYPE_ape, SPA_TYPE_Int, SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "ape", NULL }, { SPA_MEDIA_SUBTYPE_opus, SPA_TYPE_Int, SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "opus", NULL }, { SPA_MEDIA_SUBTYPE_ac3, SPA_TYPE_Int, SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "ac3", NULL }, { SPA_MEDIA_SUBTYPE_eac3, SPA_TYPE_Int, SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "eac3", NULL }, { SPA_MEDIA_SUBTYPE_truehd, SPA_TYPE_Int, SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "truehd", NULL }, { SPA_MEDIA_SUBTYPE_dts, SPA_TYPE_Int, SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "dts", NULL }, { SPA_MEDIA_SUBTYPE_mpegh, SPA_TYPE_Int, SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "mpegh", NULL }, /* video subtypes */ { SPA_MEDIA_SUBTYPE_h264, SPA_TYPE_Int, SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "h264", NULL }, { SPA_MEDIA_SUBTYPE_mjpg, SPA_TYPE_Int, SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "mjpg", NULL }, { SPA_MEDIA_SUBTYPE_dv, SPA_TYPE_Int, SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "dv", NULL }, { SPA_MEDIA_SUBTYPE_mpegts, SPA_TYPE_Int, SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "mpegts", NULL }, { SPA_MEDIA_SUBTYPE_h263, SPA_TYPE_Int, SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "h263", NULL }, { SPA_MEDIA_SUBTYPE_mpeg1, SPA_TYPE_Int, SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "mpeg1", NULL }, { SPA_MEDIA_SUBTYPE_mpeg2, SPA_TYPE_Int, SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "mpeg2", NULL }, { SPA_MEDIA_SUBTYPE_mpeg4, SPA_TYPE_Int, SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "mpeg4", NULL }, { SPA_MEDIA_SUBTYPE_xvid, SPA_TYPE_Int, SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "xvid", NULL }, { SPA_MEDIA_SUBTYPE_vc1, SPA_TYPE_Int, SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "vc1", NULL }, { SPA_MEDIA_SUBTYPE_vp8, SPA_TYPE_Int, SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "vp8", NULL }, { SPA_MEDIA_SUBTYPE_vp9, SPA_TYPE_Int, SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "vp9", NULL }, { SPA_MEDIA_SUBTYPE_bayer, SPA_TYPE_Int, SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "bayer", NULL }, /* image subtypes */ { SPA_MEDIA_SUBTYPE_jpeg, SPA_TYPE_Int, SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "jpeg", NULL }, /* stream subtypes */ { SPA_MEDIA_SUBTYPE_midi, SPA_TYPE_Int, SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "midi", NULL }, /* application subtypes */ { SPA_MEDIA_SUBTYPE_control, SPA_TYPE_Int, SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "control", NULL }, { 0, 0, NULL, NULL }, }; #define SPA_TYPE_INFO_FormatAudio SPA_TYPE_INFO_FORMAT_BASE "Audio" #define SPA_TYPE_INFO_FORMAT_AUDIO_BASE SPA_TYPE_INFO_FormatAudio ":" #define SPA_TYPE_INFO_FORMAT_AUDIO_AAC SPA_TYPE_INFO_FORMAT_AUDIO_BASE "AAC" #define SPA_TYPE_INFO_FORMAT_AUDIO_AAC_BASE SPA_TYPE_INFO_FORMAT_AUDIO_AAC ":" #define SPA_TYPE_INFO_FORMAT_AUDIO_WMA SPA_TYPE_INFO_FORMAT_AUDIO_BASE "WMA" #define SPA_TYPE_INFO_FORMAT_AUDIO_WMA_BASE SPA_TYPE_INFO_FORMAT_AUDIO_WMA ":" #define SPA_TYPE_INFO_FORMAT_AUDIO_AMR SPA_TYPE_INFO_FORMAT_AUDIO_BASE "AMR" #define SPA_TYPE_INFO_FORMAT_AUDIO_AMR_BASE SPA_TYPE_INFO_FORMAT_AUDIO_AMR ":" #define SPA_TYPE_INFO_FORMAT_AUDIO_MP3 SPA_TYPE_INFO_FORMAT_AUDIO_BASE "MP3" #define SPA_TYPE_INFO_FORMAT_AUDIO_MP3_BASE SPA_TYPE_INFO_FORMAT_AUDIO_MP3 ":" #define SPA_TYPE_INFO_FORMAT_AUDIO_DTS SPA_TYPE_INFO_FORMAT_AUDIO_BASE "DTS" #define SPA_TYPE_INFO_FORMAT_AUDIO_DTS_BASE SPA_TYPE_INFO_FORMAT_AUDIO_DTS ":" #define SPA_TYPE_INFO_FormatVideo SPA_TYPE_INFO_FORMAT_BASE "Video" #define SPA_TYPE_INFO_FORMAT_VIDEO_BASE SPA_TYPE_INFO_FormatVideo ":" #define SPA_TYPE_INFO_FORMAT_VIDEO_H264 SPA_TYPE_INFO_FORMAT_VIDEO_BASE "H264" #define SPA_TYPE_INFO_FORMAT_VIDEO_H264_BASE SPA_TYPE_INFO_FORMAT_VIDEO_H264 ":" #define SPA_TYPE_INFO_FormatControl SPA_TYPE_INFO_FORMAT_BASE "Control" #define SPA_TYPE_INFO_FORMAT_CONTROL_BASE SPA_TYPE_INFO_FormatControl ":" static const struct spa_type_info spa_type_format[] = { { SPA_FORMAT_START, SPA_TYPE_Id, SPA_TYPE_INFO_FORMAT_BASE, spa_type_param, }, { SPA_FORMAT_mediaType, SPA_TYPE_Id, SPA_TYPE_INFO_FORMAT_BASE "mediaType", spa_type_media_type, }, { SPA_FORMAT_mediaSubtype, SPA_TYPE_Id, SPA_TYPE_INFO_FORMAT_BASE "mediaSubtype", spa_type_media_subtype, }, { SPA_FORMAT_AUDIO_format, SPA_TYPE_Id, SPA_TYPE_INFO_FORMAT_AUDIO_BASE "format", spa_type_audio_format }, { SPA_FORMAT_AUDIO_flags, SPA_TYPE_Id, SPA_TYPE_INFO_FORMAT_AUDIO_BASE "flags", spa_type_audio_flags }, { SPA_FORMAT_AUDIO_rate, SPA_TYPE_Int, SPA_TYPE_INFO_FORMAT_AUDIO_BASE "rate", NULL }, { SPA_FORMAT_AUDIO_channels, SPA_TYPE_Int, SPA_TYPE_INFO_FORMAT_AUDIO_BASE "channels", NULL }, { SPA_FORMAT_AUDIO_position, SPA_TYPE_Array, SPA_TYPE_INFO_FORMAT_AUDIO_BASE "position", spa_type_prop_channel_map }, { SPA_FORMAT_AUDIO_iec958Codec, SPA_TYPE_Id, SPA_TYPE_INFO_FORMAT_AUDIO_BASE "iec958Codec", spa_type_audio_iec958_codec }, { SPA_FORMAT_AUDIO_bitorder, SPA_TYPE_Id, SPA_TYPE_INFO_FORMAT_AUDIO_BASE "bitorder", spa_type_param_bitorder }, { SPA_FORMAT_AUDIO_interleave, SPA_TYPE_Int, SPA_TYPE_INFO_FORMAT_AUDIO_BASE "interleave", NULL }, { SPA_FORMAT_AUDIO_bitrate, SPA_TYPE_Int, SPA_TYPE_INFO_FORMAT_AUDIO_BASE "bitrate", NULL }, { SPA_FORMAT_AUDIO_blockAlign, SPA_TYPE_Int, SPA_TYPE_INFO_FORMAT_AUDIO_BASE "blockAlign", NULL }, { SPA_FORMAT_AUDIO_AAC_streamFormat, SPA_TYPE_Id, SPA_TYPE_INFO_FORMAT_AUDIO_AAC_BASE "streamFormat", spa_type_audio_aac_stream_format }, { SPA_FORMAT_AUDIO_WMA_profile, SPA_TYPE_Id, SPA_TYPE_INFO_FORMAT_AUDIO_WMA_BASE "profile", spa_type_audio_wma_profile }, { SPA_FORMAT_AUDIO_AMR_bandMode, SPA_TYPE_Id, SPA_TYPE_INFO_FORMAT_AUDIO_AMR_BASE "bandMode", spa_type_audio_amr_band_mode }, { SPA_FORMAT_AUDIO_MP3_channelMode, SPA_TYPE_Id, SPA_TYPE_INFO_FORMAT_AUDIO_MP3_BASE "channelMode", spa_type_audio_mp3_channel_mode }, { SPA_FORMAT_AUDIO_DTS_extType, SPA_TYPE_Id, SPA_TYPE_INFO_FORMAT_AUDIO_DTS_BASE "extType", spa_type_audio_dts_ext_type }, { SPA_FORMAT_VIDEO_format, SPA_TYPE_Id, SPA_TYPE_INFO_FORMAT_VIDEO_BASE "format", spa_type_video_format, }, { SPA_FORMAT_VIDEO_modifier, SPA_TYPE_Long, SPA_TYPE_INFO_FORMAT_VIDEO_BASE "modifier", NULL }, { SPA_FORMAT_VIDEO_size, SPA_TYPE_Rectangle, SPA_TYPE_INFO_FORMAT_VIDEO_BASE "size", NULL }, { SPA_FORMAT_VIDEO_framerate, SPA_TYPE_Fraction, SPA_TYPE_INFO_FORMAT_VIDEO_BASE "framerate", NULL }, { SPA_FORMAT_VIDEO_maxFramerate, SPA_TYPE_Fraction, SPA_TYPE_INFO_FORMAT_VIDEO_BASE "maxFramerate", NULL }, { SPA_FORMAT_VIDEO_views, SPA_TYPE_Int, SPA_TYPE_INFO_FORMAT_VIDEO_BASE "views", NULL }, { SPA_FORMAT_VIDEO_interlaceMode, SPA_TYPE_Id, SPA_TYPE_INFO_FORMAT_VIDEO_BASE "interlaceMode", spa_type_video_interlace_mode, }, { SPA_FORMAT_VIDEO_pixelAspectRatio, SPA_TYPE_Fraction, SPA_TYPE_INFO_FORMAT_VIDEO_BASE "pixelAspectRatio", NULL }, { SPA_FORMAT_VIDEO_multiviewMode, SPA_TYPE_Id, SPA_TYPE_INFO_FORMAT_VIDEO_BASE "multiviewMode", NULL }, { SPA_FORMAT_VIDEO_multiviewFlags, SPA_TYPE_Id, SPA_TYPE_INFO_FORMAT_VIDEO_BASE "multiviewFlags", NULL }, { SPA_FORMAT_VIDEO_chromaSite, SPA_TYPE_Id, SPA_TYPE_INFO_FORMAT_VIDEO_BASE "chromaSite", NULL }, { SPA_FORMAT_VIDEO_colorRange, SPA_TYPE_Id, SPA_TYPE_INFO_FORMAT_VIDEO_BASE "colorRange", spa_type_video_color_range, }, { SPA_FORMAT_VIDEO_colorMatrix, SPA_TYPE_Id, SPA_TYPE_INFO_FORMAT_VIDEO_BASE "colorMatrix", spa_type_video_color_matrix, }, { SPA_FORMAT_VIDEO_transferFunction, SPA_TYPE_Id, SPA_TYPE_INFO_FORMAT_VIDEO_BASE "transferFunction", spa_type_video_transfer_function, }, { SPA_FORMAT_VIDEO_colorPrimaries, SPA_TYPE_Id, SPA_TYPE_INFO_FORMAT_VIDEO_BASE "colorPrimaries", spa_type_video_color_primaries, }, { SPA_FORMAT_VIDEO_profile, SPA_TYPE_Int, SPA_TYPE_INFO_FORMAT_VIDEO_BASE "profile", NULL }, { SPA_FORMAT_VIDEO_level, SPA_TYPE_Int, SPA_TYPE_INFO_FORMAT_VIDEO_BASE "level", NULL }, { SPA_FORMAT_VIDEO_H264_streamFormat, SPA_TYPE_Id, SPA_TYPE_INFO_FORMAT_VIDEO_H264_BASE "streamFormat", NULL }, { SPA_FORMAT_VIDEO_H264_alignment, SPA_TYPE_Id, SPA_TYPE_INFO_FORMAT_VIDEO_H264_BASE "alignment", NULL }, { SPA_FORMAT_CONTROL_types, SPA_TYPE_Id, SPA_TYPE_INFO_FORMAT_CONTROL_BASE "types", spa_type_control }, { 0, 0, NULL, NULL }, }; /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_PARAM_FORMAT_TYPES_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/param/format-utils.h000066400000000000000000000015701511204443500273710ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_PARAM_FORMAT_UTILS_H #define SPA_PARAM_FORMAT_UTILS_H #include #include #ifdef __cplusplus extern "C" { #endif /** * \addtogroup spa_param * \{ */ #ifndef SPA_API_FORMAT_UTILS #ifdef SPA_API_IMPL #define SPA_API_FORMAT_UTILS SPA_API_IMPL #else #define SPA_API_FORMAT_UTILS static inline #endif #endif SPA_API_FORMAT_UTILS int spa_format_parse(const struct spa_pod *format, uint32_t *media_type, uint32_t *media_subtype) { return spa_pod_parse_object(format, SPA_TYPE_OBJECT_Format, NULL, SPA_FORMAT_mediaType, SPA_POD_Id(media_type), SPA_FORMAT_mediaSubtype, SPA_POD_Id(media_subtype)); } /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_PARAM_FORMAT_UTILS_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/param/format.h000066400000000000000000000133001511204443500262250ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_PARAM_FORMAT_H #define SPA_PARAM_FORMAT_H #include #ifdef __cplusplus extern "C" { #endif /** * \addtogroup spa_param * \{ */ /** media type for SPA_TYPE_OBJECT_Format */ enum spa_media_type { SPA_MEDIA_TYPE_unknown, SPA_MEDIA_TYPE_audio, SPA_MEDIA_TYPE_video, SPA_MEDIA_TYPE_image, SPA_MEDIA_TYPE_binary, SPA_MEDIA_TYPE_stream, SPA_MEDIA_TYPE_application, }; /** media subtype for SPA_TYPE_OBJECT_Format */ enum spa_media_subtype { SPA_MEDIA_SUBTYPE_unknown, SPA_MEDIA_SUBTYPE_raw, SPA_MEDIA_SUBTYPE_dsp, SPA_MEDIA_SUBTYPE_iec958, /** S/PDIF */ SPA_MEDIA_SUBTYPE_dsd, SPA_MEDIA_SUBTYPE_START_Audio = 0x10000, SPA_MEDIA_SUBTYPE_mp3, SPA_MEDIA_SUBTYPE_aac, SPA_MEDIA_SUBTYPE_vorbis, SPA_MEDIA_SUBTYPE_wma, SPA_MEDIA_SUBTYPE_ra, SPA_MEDIA_SUBTYPE_sbc, SPA_MEDIA_SUBTYPE_adpcm, SPA_MEDIA_SUBTYPE_g723, SPA_MEDIA_SUBTYPE_g726, SPA_MEDIA_SUBTYPE_g729, SPA_MEDIA_SUBTYPE_amr, SPA_MEDIA_SUBTYPE_gsm, SPA_MEDIA_SUBTYPE_alac, /** since 0.3.65 */ SPA_MEDIA_SUBTYPE_flac, /** since 0.3.65 */ SPA_MEDIA_SUBTYPE_ape, /** since 0.3.65 */ SPA_MEDIA_SUBTYPE_opus, /** since 0.3.68 */ SPA_MEDIA_SUBTYPE_ac3, /** since 1.5.1 */ SPA_MEDIA_SUBTYPE_eac3, /** since 1.5.1 */ SPA_MEDIA_SUBTYPE_truehd, /** since 1.5.1 */ SPA_MEDIA_SUBTYPE_dts, /** since 1.5.1 */ SPA_MEDIA_SUBTYPE_mpegh, /** since 1.5.1 */ SPA_MEDIA_SUBTYPE_START_Video = 0x20000, SPA_MEDIA_SUBTYPE_h264, SPA_MEDIA_SUBTYPE_mjpg, SPA_MEDIA_SUBTYPE_dv, SPA_MEDIA_SUBTYPE_mpegts, SPA_MEDIA_SUBTYPE_h263, SPA_MEDIA_SUBTYPE_mpeg1, SPA_MEDIA_SUBTYPE_mpeg2, SPA_MEDIA_SUBTYPE_mpeg4, SPA_MEDIA_SUBTYPE_xvid, SPA_MEDIA_SUBTYPE_vc1, SPA_MEDIA_SUBTYPE_vp8, SPA_MEDIA_SUBTYPE_vp9, SPA_MEDIA_SUBTYPE_bayer, SPA_MEDIA_SUBTYPE_h265, SPA_MEDIA_SUBTYPE_START_Image = 0x30000, SPA_MEDIA_SUBTYPE_jpeg, SPA_MEDIA_SUBTYPE_START_Binary = 0x40000, SPA_MEDIA_SUBTYPE_START_Stream = 0x50000, SPA_MEDIA_SUBTYPE_midi, SPA_MEDIA_SUBTYPE_START_Application = 0x60000, SPA_MEDIA_SUBTYPE_control, /**< control stream, data contains * spa_pod_sequence with control info. */ }; /** properties for audio SPA_TYPE_OBJECT_Format */ enum spa_format { SPA_FORMAT_START, SPA_FORMAT_mediaType, /**< media type (Id enum spa_media_type) */ SPA_FORMAT_mediaSubtype, /**< media subtype (Id enum spa_media_subtype) */ /* Audio format keys */ SPA_FORMAT_START_Audio = 0x10000, SPA_FORMAT_AUDIO_format, /**< audio format, (Id enum spa_audio_format) */ SPA_FORMAT_AUDIO_flags, /**< optional flags (Int) */ SPA_FORMAT_AUDIO_rate, /**< sample rate (Int) */ SPA_FORMAT_AUDIO_channels, /**< number of audio channels (Int) */ SPA_FORMAT_AUDIO_position, /**< channel positions (Id enum spa_audio_position) */ SPA_FORMAT_AUDIO_iec958Codec, /**< codec used (IEC958) (Id enum spa_audio_iec958_codec) */ SPA_FORMAT_AUDIO_bitorder, /**< bit order (Id enum spa_param_bitorder) */ SPA_FORMAT_AUDIO_interleave, /**< Interleave bytes (Int) */ SPA_FORMAT_AUDIO_bitrate, /**< bit rate (Int) */ SPA_FORMAT_AUDIO_blockAlign, /**< audio data block alignment (Int) */ SPA_FORMAT_AUDIO_AAC_streamFormat, /**< AAC stream format, (Id enum spa_audio_aac_stream_format) */ SPA_FORMAT_AUDIO_WMA_profile, /**< WMA profile (Id enum spa_audio_wma_profile) */ SPA_FORMAT_AUDIO_AMR_bandMode, /**< AMR band mode (Id enum spa_audio_amr_band_mode) */ SPA_FORMAT_AUDIO_MP3_channelMode, /**< MP3 channel mode, (Id enum spa_audio_mp3_channel_mode) */ SPA_FORMAT_AUDIO_DTS_extType, /**< DTS extension type (Id enum spa_audio_dts_ext_type) */ /* Video Format keys */ SPA_FORMAT_START_Video = 0x20000, SPA_FORMAT_VIDEO_format, /**< video format (Id enum spa_video_format) */ SPA_FORMAT_VIDEO_modifier, /**< format modifier (Long) * use only with DMA-BUF and omit for other buffer types */ SPA_FORMAT_VIDEO_size, /**< size (Rectangle) */ SPA_FORMAT_VIDEO_framerate, /**< frame rate (Fraction) */ SPA_FORMAT_VIDEO_maxFramerate, /**< maximum frame rate (Fraction) */ SPA_FORMAT_VIDEO_views, /**< number of views (Int) */ SPA_FORMAT_VIDEO_interlaceMode, /**< (Id enum spa_video_interlace_mode) */ SPA_FORMAT_VIDEO_pixelAspectRatio, /**< (Rectangle) */ SPA_FORMAT_VIDEO_multiviewMode, /**< (Id enum spa_video_multiview_mode) */ SPA_FORMAT_VIDEO_multiviewFlags, /**< (Id enum spa_video_multiview_flags) */ SPA_FORMAT_VIDEO_chromaSite, /**< /Id enum spa_video_chroma_site) */ SPA_FORMAT_VIDEO_colorRange, /**< /Id enum spa_video_color_range) */ SPA_FORMAT_VIDEO_colorMatrix, /**< /Id enum spa_video_color_matrix) */ SPA_FORMAT_VIDEO_transferFunction, /**< /Id enum spa_video_transfer_function) */ SPA_FORMAT_VIDEO_colorPrimaries, /**< /Id enum spa_video_color_primaries) */ SPA_FORMAT_VIDEO_profile, /**< (Int) */ SPA_FORMAT_VIDEO_level, /**< (Int) */ SPA_FORMAT_VIDEO_H264_streamFormat, /**< (Id enum spa_h264_stream_format) */ SPA_FORMAT_VIDEO_H264_alignment, /**< (Id enum spa_h264_alignment) */ SPA_FORMAT_VIDEO_H265_streamFormat, /**< (Id enum spa_h265_stream_format) */ SPA_FORMAT_VIDEO_H265_alignment, /**< (Id enum spa_h265_alignment) */ /* Image Format keys */ SPA_FORMAT_START_Image = 0x30000, /* Binary Format keys */ SPA_FORMAT_START_Binary = 0x40000, /* Stream Format keys */ SPA_FORMAT_START_Stream = 0x50000, /* Application Format keys */ SPA_FORMAT_START_Application = 0x60000, SPA_FORMAT_CONTROL_types, /**< possible control types (flags choice Int, * mask of enum spa_control_type) */ }; #define SPA_KEY_FORMAT_DSP "format.dsp" /**< a predefined DSP format, * Ex. "32 bit float mono audio" */ /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_PARAM_FORMAT_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/param/latency-types.h000066400000000000000000000042401511204443500275410ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_PARAM_LATENCY_TYPES_H #define SPA_PARAM_LATENCY_TYPES_H #include #include #include #ifdef __cplusplus extern "C" { #endif /** * \addtogroup spa_param * \{ */ #define SPA_TYPE_INFO_PARAM_Latency SPA_TYPE_INFO_PARAM_BASE "Latency" #define SPA_TYPE_INFO_PARAM_LATENCY_BASE SPA_TYPE_INFO_PARAM_Latency ":" static const struct spa_type_info spa_type_param_latency[] = { { SPA_PARAM_LATENCY_START, SPA_TYPE_Id, SPA_TYPE_INFO_PARAM_LATENCY_BASE, spa_type_param, }, { SPA_PARAM_LATENCY_direction, SPA_TYPE_Id, SPA_TYPE_INFO_PARAM_LATENCY_BASE "direction", spa_type_direction, }, { SPA_PARAM_LATENCY_minQuantum, SPA_TYPE_Float, SPA_TYPE_INFO_PARAM_LATENCY_BASE "minQuantum", NULL, }, { SPA_PARAM_LATENCY_maxQuantum, SPA_TYPE_Float, SPA_TYPE_INFO_PARAM_LATENCY_BASE "maxQuantum", NULL, }, { SPA_PARAM_LATENCY_minRate, SPA_TYPE_Int, SPA_TYPE_INFO_PARAM_LATENCY_BASE "minRate", NULL, }, { SPA_PARAM_LATENCY_maxRate, SPA_TYPE_Int, SPA_TYPE_INFO_PARAM_LATENCY_BASE "maxRate", NULL, }, { SPA_PARAM_LATENCY_minNs, SPA_TYPE_Long, SPA_TYPE_INFO_PARAM_LATENCY_BASE "minNs", NULL, }, { SPA_PARAM_LATENCY_maxNs, SPA_TYPE_Long, SPA_TYPE_INFO_PARAM_LATENCY_BASE "maxNs", NULL, }, { 0, 0, NULL, NULL }, }; #define SPA_TYPE_INFO_PARAM_ProcessLatency SPA_TYPE_INFO_PARAM_BASE "ProcessLatency" #define SPA_TYPE_INFO_PARAM_PROCESS_LATENCY_BASE SPA_TYPE_INFO_PARAM_ProcessLatency ":" static const struct spa_type_info spa_type_param_process_latency[] = { { SPA_PARAM_PROCESS_LATENCY_START, SPA_TYPE_Id, SPA_TYPE_INFO_PARAM_LATENCY_BASE, spa_type_param, }, { SPA_PARAM_PROCESS_LATENCY_quantum, SPA_TYPE_Float, SPA_TYPE_INFO_PARAM_PROCESS_LATENCY_BASE "quantum", NULL, }, { SPA_PARAM_PROCESS_LATENCY_rate, SPA_TYPE_Int, SPA_TYPE_INFO_PARAM_PROCESS_LATENCY_BASE "rate", NULL, }, { SPA_PARAM_PROCESS_LATENCY_ns, SPA_TYPE_Long, SPA_TYPE_INFO_PARAM_PROCESS_LATENCY_BASE "ns", NULL, }, { 0, 0, NULL, NULL }, }; /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_PARAM_LATENCY_TYPES_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/param/latency-utils.h000066400000000000000000000123441511204443500275410ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2021 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_PARAM_LATENCY_UTILS_H #define SPA_PARAM_LATENCY_UTILS_H #include #include #include #include #ifdef __cplusplus extern "C" { #endif /** * \addtogroup spa_param * \{ */ #ifndef SPA_API_LATENCY_UTILS #ifdef SPA_API_IMPL #define SPA_API_LATENCY_UTILS SPA_API_IMPL #else #define SPA_API_LATENCY_UTILS static inline #endif #endif SPA_API_LATENCY_UTILS int spa_latency_info_compare(const struct spa_latency_info *a, const struct spa_latency_info *b) { if (a->min_quantum == b->min_quantum && a->max_quantum == b->max_quantum && a->min_rate == b->min_rate && a->max_rate == b->max_rate && a->min_ns == b->min_ns && a->max_ns == b->max_ns) return 0; return 1; } SPA_API_LATENCY_UTILS void spa_latency_info_combine_start(struct spa_latency_info *info, enum spa_direction direction) { *info = SPA_LATENCY_INFO_UNSET(direction); } SPA_API_LATENCY_UTILS void spa_latency_info_combine_finish(struct spa_latency_info *info) { struct spa_latency_info unset = SPA_LATENCY_INFO_UNSET(info->direction); if (info->min_quantum == unset.min_quantum) info->min_quantum = 0; if (info->max_quantum == unset.max_quantum) info->max_quantum = 0; if (info->min_rate == unset.min_rate) info->min_rate = 0; if (info->max_rate == unset.max_rate) info->max_rate = 0; if (info->min_ns == unset.min_ns) info->min_ns = 0; if (info->max_ns == unset.max_ns) info->max_ns = 0; } SPA_API_LATENCY_UTILS int spa_latency_info_combine(struct spa_latency_info *info, const struct spa_latency_info *other) { if (info->direction != other->direction) return -EINVAL; if (other->min_quantum < info->min_quantum) info->min_quantum = other->min_quantum; if (other->max_quantum > info->max_quantum) info->max_quantum = other->max_quantum; if (other->min_rate < info->min_rate) info->min_rate = other->min_rate; if (other->max_rate > info->max_rate) info->max_rate = other->max_rate; if (other->min_ns < info->min_ns) info->min_ns = other->min_ns; if (other->max_ns > info->max_ns) info->max_ns = other->max_ns; return 0; } SPA_API_LATENCY_UTILS int spa_latency_parse(const struct spa_pod *latency, struct spa_latency_info *info) { int res; spa_zero(*info); if ((res = spa_pod_parse_object(latency, SPA_TYPE_OBJECT_ParamLatency, NULL, SPA_PARAM_LATENCY_direction, SPA_POD_Id(&info->direction), SPA_PARAM_LATENCY_minQuantum, SPA_POD_OPT_Float(&info->min_quantum), SPA_PARAM_LATENCY_maxQuantum, SPA_POD_OPT_Float(&info->max_quantum), SPA_PARAM_LATENCY_minRate, SPA_POD_OPT_Int(&info->min_rate), SPA_PARAM_LATENCY_maxRate, SPA_POD_OPT_Int(&info->max_rate), SPA_PARAM_LATENCY_minNs, SPA_POD_OPT_Long(&info->min_ns), SPA_PARAM_LATENCY_maxNs, SPA_POD_OPT_Long(&info->max_ns))) < 0) return res; info->direction = (enum spa_direction)(info->direction & 1); return 0; } SPA_API_LATENCY_UTILS struct spa_pod * spa_latency_build(struct spa_pod_builder *builder, uint32_t id, const struct spa_latency_info *info) { return (struct spa_pod *)spa_pod_builder_add_object(builder, SPA_TYPE_OBJECT_ParamLatency, id, SPA_PARAM_LATENCY_direction, SPA_POD_Id(info->direction), SPA_PARAM_LATENCY_minQuantum, SPA_POD_Float(info->min_quantum), SPA_PARAM_LATENCY_maxQuantum, SPA_POD_Float(info->max_quantum), SPA_PARAM_LATENCY_minRate, SPA_POD_Int(info->min_rate), SPA_PARAM_LATENCY_maxRate, SPA_POD_Int(info->max_rate), SPA_PARAM_LATENCY_minNs, SPA_POD_Long(info->min_ns), SPA_PARAM_LATENCY_maxNs, SPA_POD_Long(info->max_ns)); } SPA_API_LATENCY_UTILS int spa_process_latency_parse(const struct spa_pod *latency, struct spa_process_latency_info *info) { int res; spa_zero(*info); if ((res = spa_pod_parse_object(latency, SPA_TYPE_OBJECT_ParamProcessLatency, NULL, SPA_PARAM_PROCESS_LATENCY_quantum, SPA_POD_OPT_Float(&info->quantum), SPA_PARAM_PROCESS_LATENCY_rate, SPA_POD_OPT_Int(&info->rate), SPA_PARAM_PROCESS_LATENCY_ns, SPA_POD_OPT_Long(&info->ns))) < 0) return res; return 0; } SPA_API_LATENCY_UTILS struct spa_pod * spa_process_latency_build(struct spa_pod_builder *builder, uint32_t id, const struct spa_process_latency_info *info) { return (struct spa_pod *)spa_pod_builder_add_object(builder, SPA_TYPE_OBJECT_ParamProcessLatency, id, SPA_PARAM_PROCESS_LATENCY_quantum, SPA_POD_Float(info->quantum), SPA_PARAM_PROCESS_LATENCY_rate, SPA_POD_Int(info->rate), SPA_PARAM_PROCESS_LATENCY_ns, SPA_POD_Long(info->ns)); } SPA_API_LATENCY_UTILS int spa_process_latency_info_add(const struct spa_process_latency_info *process, struct spa_latency_info *info) { info->min_quantum += process->quantum; info->max_quantum += process->quantum; info->min_rate += process->rate; info->max_rate += process->rate; info->min_ns += process->ns; info->max_ns += process->ns; return 0; } SPA_API_LATENCY_UTILS int spa_process_latency_info_compare(const struct spa_process_latency_info *a, const struct spa_process_latency_info *b) { if (a->quantum == b->quantum && a->rate == b->rate && a->ns == b->ns) return 0; return 1; } /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_PARAM_LATENCY_UTILS_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/param/latency.h000066400000000000000000000056651511204443500264130ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2023 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_PARAM_LATENY_H #define SPA_PARAM_LATENY_H #include #ifdef __cplusplus extern "C" { #endif /** * \addtogroup spa_param * \{ */ /** * Properties for SPA_TYPE_OBJECT_ParamLatency * * The latency indicates: * * - for playback: time delay between start of a graph cycle, and the rendering of * the first sample of that cycle in audio output. * * - for capture: time delay between start of a graph cycle, and the first sample * of that cycle having occurred in audio input. * * For physical output/input, the latency is intended to correspond to the * rendering/capture of physical audio, including hardware internal rendering delay. * * The latency values are adjusted by \ref SPA_PROP_latencyOffsetNsec or * SPA_PARAM_ProcessLatency, if present. (e.g. for ALSA this is used to adjust for * the internal hardware latency). */ enum spa_param_latency { SPA_PARAM_LATENCY_START, SPA_PARAM_LATENCY_direction, /**< direction, input/output (Id enum spa_direction) */ SPA_PARAM_LATENCY_minQuantum, /**< min latency relative to quantum (Float) */ SPA_PARAM_LATENCY_maxQuantum, /**< max latency relative to quantum (Float) */ SPA_PARAM_LATENCY_minRate, /**< min latency (Int) relative to graph rate */ SPA_PARAM_LATENCY_maxRate, /**< max latency (Int) relative to graph rate */ SPA_PARAM_LATENCY_minNs, /**< min latency (Long) in nanoseconds */ SPA_PARAM_LATENCY_maxNs, /**< max latency (Long) in nanoseconds */ }; /** helper structure for managing latency objects */ struct spa_latency_info { enum spa_direction direction; float min_quantum; float max_quantum; int32_t min_rate; int32_t max_rate; int64_t min_ns; int64_t max_ns; }; #define SPA_LATENCY_INFO(dir,...) ((struct spa_latency_info) { .direction = (dir), ## __VA_ARGS__ }) #define SPA_LATENCY_INFO_UNSET(dir) SPA_LATENCY_INFO(dir, \ .min_quantum = FLT_MAX, .max_quantum = FLT_MIN, \ .min_rate = INT32_MAX, .max_rate = INT32_MIN, \ .min_ns = INT64_MAX, .max_ns = INT64_MIN) /** * Properties for SPA_TYPE_OBJECT_ParamProcessLatency * * The processing latency indicates logical time delay between a sample in an input port, * and a corresponding sample in an output port, relative to the graph time. */ enum spa_param_process_latency { SPA_PARAM_PROCESS_LATENCY_START, SPA_PARAM_PROCESS_LATENCY_quantum, /**< latency relative to quantum (Float) */ SPA_PARAM_PROCESS_LATENCY_rate, /**< latency (Int) relative to graph rate */ SPA_PARAM_PROCESS_LATENCY_ns, /**< latency (Long) in nanoseconds */ }; /** Helper structure for managing process latency objects */ struct spa_process_latency_info { float quantum; int32_t rate; int64_t ns; }; #define SPA_PROCESS_LATENCY_INFO_INIT(...) ((struct spa_process_latency_info) { __VA_ARGS__ }) /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_PARAM_LATENY_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/param/param-types.h000066400000000000000000000112261511204443500272040ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_PARAM_TYPES_H #define SPA_PARAM_TYPES_H #include #include #include #ifdef __cplusplus extern "C" { #endif /** * \addtogroup spa_param * \{ */ /* base for parameter object enumerations */ #define SPA_TYPE_INFO_ParamId SPA_TYPE_INFO_ENUM_BASE "ParamId" #define SPA_TYPE_INFO_PARAM_ID_BASE SPA_TYPE_INFO_ParamId ":" static const struct spa_type_info spa_type_param[] = { { SPA_PARAM_Invalid, SPA_TYPE_None, SPA_TYPE_INFO_PARAM_ID_BASE "Invalid", NULL }, { SPA_PARAM_PropInfo, SPA_TYPE_OBJECT_PropInfo, SPA_TYPE_INFO_PARAM_ID_BASE "PropInfo", NULL }, { SPA_PARAM_Props, SPA_TYPE_OBJECT_Props, SPA_TYPE_INFO_PARAM_ID_BASE "Props", NULL }, { SPA_PARAM_EnumFormat, SPA_TYPE_OBJECT_Format, SPA_TYPE_INFO_PARAM_ID_BASE "EnumFormat", NULL }, { SPA_PARAM_Format, SPA_TYPE_OBJECT_Format, SPA_TYPE_INFO_PARAM_ID_BASE "Format", NULL }, { SPA_PARAM_Buffers, SPA_TYPE_OBJECT_ParamBuffers, SPA_TYPE_INFO_PARAM_ID_BASE "Buffers", NULL }, { SPA_PARAM_Meta, SPA_TYPE_OBJECT_ParamMeta, SPA_TYPE_INFO_PARAM_ID_BASE "Meta", NULL }, { SPA_PARAM_IO, SPA_TYPE_OBJECT_ParamIO, SPA_TYPE_INFO_PARAM_ID_BASE "IO", NULL }, { SPA_PARAM_EnumProfile, SPA_TYPE_OBJECT_ParamProfile, SPA_TYPE_INFO_PARAM_ID_BASE "EnumProfile", NULL }, { SPA_PARAM_Profile, SPA_TYPE_OBJECT_ParamProfile, SPA_TYPE_INFO_PARAM_ID_BASE "Profile", NULL }, { SPA_PARAM_EnumPortConfig, SPA_TYPE_OBJECT_ParamPortConfig, SPA_TYPE_INFO_PARAM_ID_BASE "EnumPortConfig", NULL }, { SPA_PARAM_PortConfig, SPA_TYPE_OBJECT_ParamPortConfig, SPA_TYPE_INFO_PARAM_ID_BASE "PortConfig", NULL }, { SPA_PARAM_EnumRoute, SPA_TYPE_OBJECT_ParamRoute, SPA_TYPE_INFO_PARAM_ID_BASE "EnumRoute", NULL }, { SPA_PARAM_Route, SPA_TYPE_OBJECT_ParamRoute, SPA_TYPE_INFO_PARAM_ID_BASE "Route", NULL }, { SPA_PARAM_Control, SPA_TYPE_Sequence, SPA_TYPE_INFO_PARAM_ID_BASE "Control", NULL }, { SPA_PARAM_Latency, SPA_TYPE_OBJECT_ParamLatency, SPA_TYPE_INFO_PARAM_ID_BASE "Latency", NULL }, { SPA_PARAM_ProcessLatency, SPA_TYPE_OBJECT_ParamProcessLatency, SPA_TYPE_INFO_PARAM_ID_BASE "ProcessLatency", NULL }, { SPA_PARAM_Tag, SPA_TYPE_OBJECT_ParamTag, SPA_TYPE_INFO_PARAM_ID_BASE "Tag", NULL }, { SPA_PARAM_PeerEnumFormat, SPA_TYPE_OBJECT_PeerParam, SPA_TYPE_INFO_PARAM_ID_BASE "PeerEnumFormat", NULL }, { SPA_PARAM_Capability, SPA_TYPE_OBJECT_ParamDict, SPA_TYPE_INFO_PARAM_ID_BASE "Capability", NULL }, { SPA_PARAM_PeerCapability, SPA_TYPE_OBJECT_PeerParam, SPA_TYPE_INFO_PARAM_ID_BASE "PeerCapability", NULL }, { 0, 0, NULL, NULL }, }; /* base for parameter objects */ #define SPA_TYPE_INFO_Param SPA_TYPE_INFO_OBJECT_BASE "Param" #define SPA_TYPE_INFO_PARAM_BASE SPA_TYPE_INFO_Param ":" #include static const struct spa_type_info spa_type_prop_float_array[] = { { SPA_PROP_START, SPA_TYPE_Float, SPA_TYPE_INFO_BASE "floatArray", NULL, }, { 0, 0, NULL, NULL }, }; static const struct spa_type_info spa_type_prop_int_array[] = { { SPA_PROP_START, SPA_TYPE_Int, SPA_TYPE_INFO_BASE "intArray", NULL, }, { 0, 0, NULL, NULL }, }; static const struct spa_type_info spa_type_prop_channel_map[] = { { SPA_PROP_START, SPA_TYPE_Id, SPA_TYPE_INFO_BASE "channelMap", spa_type_audio_channel, }, { 0, 0, NULL, NULL }, }; static const struct spa_type_info spa_type_prop_iec958_codec[] = { { SPA_PROP_START, SPA_TYPE_Id, SPA_TYPE_INFO_BASE "iec958Codec", spa_type_audio_iec958_codec, }, { 0, 0, NULL, NULL }, }; #define SPA_TYPE_INFO_ParamBitorder SPA_TYPE_INFO_ENUM_BASE "ParamBitorder" #define SPA_TYPE_INFO_PARAM_BITORDER_BASE SPA_TYPE_INFO_ParamBitorder ":" static const struct spa_type_info spa_type_param_bitorder[] = { { SPA_PARAM_BITORDER_unknown, SPA_TYPE_Int, SPA_TYPE_INFO_PARAM_BITORDER_BASE "unknown", NULL }, { SPA_PARAM_BITORDER_msb, SPA_TYPE_Int, SPA_TYPE_INFO_PARAM_BITORDER_BASE "msb", NULL }, { SPA_PARAM_BITORDER_lsb, SPA_TYPE_Int, SPA_TYPE_INFO_PARAM_BITORDER_BASE "lsb", NULL }, { 0, 0, NULL, NULL }, }; #define SPA_TYPE_INFO_ParamAvailability SPA_TYPE_INFO_ENUM_BASE "ParamAvailability" #define SPA_TYPE_INFO_PARAM_AVAILABILITY_BASE SPA_TYPE_INFO_ParamAvailability ":" static const struct spa_type_info spa_type_param_availability[] = { { SPA_PARAM_AVAILABILITY_unknown, SPA_TYPE_Int, SPA_TYPE_INFO_PARAM_AVAILABILITY_BASE "unknown", NULL }, { SPA_PARAM_AVAILABILITY_no, SPA_TYPE_Int, SPA_TYPE_INFO_PARAM_AVAILABILITY_BASE "no", NULL }, { SPA_PARAM_AVAILABILITY_yes, SPA_TYPE_Int, SPA_TYPE_INFO_PARAM_AVAILABILITY_BASE "yes", NULL }, { 0, 0, NULL, NULL }, }; /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_PARAM_TYPES_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/param/param.h000066400000000000000000000066451511204443500260530ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_PARAM_H #define SPA_PARAM_H #include #ifdef __cplusplus extern "C" { #endif /** \defgroup spa_param Parameters * Parameter value enumerations and type information */ /** * \addtogroup spa_param * \{ */ /** different parameter types that can be queried */ enum spa_param_type { SPA_PARAM_Invalid, /**< invalid */ SPA_PARAM_PropInfo, /**< property information as SPA_TYPE_OBJECT_PropInfo */ SPA_PARAM_Props, /**< properties as SPA_TYPE_OBJECT_Props */ SPA_PARAM_EnumFormat, /**< available formats as SPA_TYPE_OBJECT_Format */ SPA_PARAM_Format, /**< configured format as SPA_TYPE_OBJECT_Format */ SPA_PARAM_Buffers, /**< buffer configurations as SPA_TYPE_OBJECT_ParamBuffers*/ SPA_PARAM_Meta, /**< allowed metadata for buffers as SPA_TYPE_OBJECT_ParamMeta*/ SPA_PARAM_IO, /**< configurable IO areas as SPA_TYPE_OBJECT_ParamIO */ SPA_PARAM_EnumProfile, /**< profile enumeration as SPA_TYPE_OBJECT_ParamProfile */ SPA_PARAM_Profile, /**< profile configuration as SPA_TYPE_OBJECT_ParamProfile */ SPA_PARAM_EnumPortConfig, /**< port configuration enumeration as SPA_TYPE_OBJECT_ParamPortConfig */ SPA_PARAM_PortConfig, /**< port configuration as SPA_TYPE_OBJECT_ParamPortConfig */ SPA_PARAM_EnumRoute, /**< routing enumeration as SPA_TYPE_OBJECT_ParamRoute */ SPA_PARAM_Route, /**< routing configuration as SPA_TYPE_OBJECT_ParamRoute */ SPA_PARAM_Control, /**< Control parameter, a SPA_TYPE_Sequence */ SPA_PARAM_Latency, /**< latency reporting, a SPA_TYPE_OBJECT_ParamLatency */ SPA_PARAM_ProcessLatency, /**< processing latency, a SPA_TYPE_OBJECT_ParamProcessLatency */ SPA_PARAM_Tag, /**< tag reporting, a SPA_TYPE_OBJECT_ParamTag. Since 0.3.79 */ SPA_PARAM_PeerEnumFormat, /**< peer formats, a SPA_TYPE_OBJECT_PeerParam with * SPA_TYPE_OBJECT_Format. Since 1.5.0 */ SPA_PARAM_Capability, /**< capability info, a SPA_TYPE_OBJECT_ParamDict, Since 1.5.84 */ SPA_PARAM_PeerCapability, /**< peer capabilities, a SPA_TYPE_OBJECT_PeerParam with * SPA_TYPE_OBJECT_ParamDict, since 1.5.84 */ }; /** information about a parameter */ struct spa_param_info { uint32_t id; /**< enum spa_param_type */ #define SPA_PARAM_INFO_SERIAL (1<<0) /**< bit to signal update even when the * read/write flags don't change */ #define SPA_PARAM_INFO_READ (1<<1) #define SPA_PARAM_INFO_WRITE (1<<2) #define SPA_PARAM_INFO_READWRITE (SPA_PARAM_INFO_WRITE|SPA_PARAM_INFO_READ) uint32_t flags; uint32_t user; /**< private user field. You can use this to keep * state. */ int32_t seq; /**< private seq field. You can use this to keep * state of a pending update. */ uint32_t padding[4]; }; #define SPA_PARAM_INFO(id,flags) ((struct spa_param_info){ (id), (flags) }) enum spa_param_bitorder { SPA_PARAM_BITORDER_unknown, /**< unknown bitorder */ SPA_PARAM_BITORDER_msb, /**< most significant bit */ SPA_PARAM_BITORDER_lsb, /**< least significant bit */ }; enum spa_param_availability { SPA_PARAM_AVAILABILITY_unknown, /**< unknown availability */ SPA_PARAM_AVAILABILITY_no, /**< not available */ SPA_PARAM_AVAILABILITY_yes, /**< available */ }; #include #include #include #include /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_PARAM_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/param/peer-types.h000066400000000000000000000015141511204443500270360ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_PARAM_PEER_TYPES_H #define SPA_PARAM_PEER_TYPES_H #include #include #include #ifdef __cplusplus extern "C" { #endif /** * \addtogroup spa_param * \{ */ #define SPA_TYPE_INFO_PeerParam SPA_TYPE_INFO_OBJECT_BASE "PeerParam" #define SPA_TYPE_INFO_PEER_PARAM_BASE SPA_TYPE_INFO_PeerParam ":" static const struct spa_type_info spa_type_peer_param[] = { { SPA_PEER_PARAM_START, SPA_TYPE_Id, SPA_TYPE_INFO_PEER_PARAM_BASE, spa_type_param, }, { SPA_ID_INVALID, SPA_TYPE_Id, SPA_TYPE_INFO_PEER_PARAM_BASE, NULL, }, { 0, 0, NULL, NULL }, }; /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_PARAM_PEER_TYPES_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/param/peer-utils.h000066400000000000000000000041711511204443500270340ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2023 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_PARAM_PEER_PARAM_UTILS_H #define SPA_PARAM_PEER_PARAM_UTILS_H #include #include #include #include #include #include #include #ifdef __cplusplus extern "C" { #endif /** * \addtogroup spa_param * \{ */ #ifndef SPA_API_PEER_PARAM_UTILS #ifdef SPA_API_IMPL #define SPA_API_PEER_PARAM_UTILS SPA_API_IMPL #else #define SPA_API_PEER_PARAM_UTILS static inline #endif #endif SPA_API_PEER_PARAM_UTILS int spa_peer_param_parse(const struct spa_pod *param, struct spa_peer_param_info *info, size_t size, void **state) { int res; const struct spa_pod_object *obj = (const struct spa_pod_object*)param; const struct spa_pod_prop *first, *start, *cur; if ((res = spa_pod_parse_object(param, SPA_TYPE_OBJECT_PeerParam, NULL)) < 0) return res; first = spa_pod_prop_first(&obj->body); start = *state ? spa_pod_prop_next((struct spa_pod_prop*)*state) : first; res = 0; for (cur = start; spa_pod_prop_is_inside(&obj->body, obj->pod.size, cur); cur = spa_pod_prop_next(cur)) { info->peer_id = cur->key; info->param = &cur->value; *state = (void*)cur; return 1; } return 0; } SPA_API_PEER_PARAM_UTILS void spa_peer_param_build_start(struct spa_pod_builder *builder, struct spa_pod_frame *f, uint32_t id) { spa_pod_builder_push_object(builder, f, SPA_TYPE_OBJECT_PeerParam, id); } SPA_API_PEER_PARAM_UTILS void spa_peer_param_build_add_param(struct spa_pod_builder *builder, uint32_t peer_id, const struct spa_pod *param) { spa_pod_builder_prop(builder, peer_id, 0); if (param) spa_pod_builder_primitive(builder, param); else spa_pod_builder_none(builder); } SPA_API_PEER_PARAM_UTILS struct spa_pod * spa_peer_param_build_end(struct spa_pod_builder *builder, struct spa_pod_frame *f) { return (struct spa_pod*)spa_pod_builder_pop(builder, f); } /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_PARAM_PEER_PARAM_UTILS_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/param/peer.h000066400000000000000000000012141511204443500256710ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2025 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_PARAM_PEER_PARAM_H #define SPA_PARAM_PEER_PARAM_H #include #ifdef __cplusplus extern "C" { #endif /** * \addtogroup spa_param * \{ */ /** properties for SPA_TYPE_OBJECT_PeerParam */ enum spa_peer_param { SPA_PEER_PARAM_START, /**< id of peer as key, SPA_TYPE_Pod as value */ SPA_PEER_PARAM_END = 0xfffffffe, }; struct spa_peer_param_info { uint32_t peer_id; const struct spa_pod *param; }; /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_PARAM_PEER_PARAM_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/param/port-config-types.h000066400000000000000000000041311511204443500303300ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_PARAM_PORT_CONFIG_TYPES_H #define SPA_PARAM_PORT_CONFIG_TYPES_H #include #include #include #ifdef __cplusplus extern "C" { #endif /** * \addtogroup spa_param * \{ */ #define SPA_TYPE_INFO_ParamPortConfigMode SPA_TYPE_INFO_ENUM_BASE "ParamPortConfigMode" #define SPA_TYPE_INFO_PARAM_PORT_CONFIG_MODE_BASE SPA_TYPE_INFO_ParamPortConfigMode ":" static const struct spa_type_info spa_type_param_port_config_mode[] = { { SPA_PARAM_PORT_CONFIG_MODE_none, SPA_TYPE_Int, SPA_TYPE_INFO_PARAM_PORT_CONFIG_MODE_BASE "none", NULL }, { SPA_PARAM_PORT_CONFIG_MODE_passthrough, SPA_TYPE_Int, SPA_TYPE_INFO_PARAM_PORT_CONFIG_MODE_BASE "passthrough", NULL }, { SPA_PARAM_PORT_CONFIG_MODE_convert, SPA_TYPE_Int, SPA_TYPE_INFO_PARAM_PORT_CONFIG_MODE_BASE "convert", NULL }, { SPA_PARAM_PORT_CONFIG_MODE_dsp, SPA_TYPE_Int, SPA_TYPE_INFO_PARAM_PORT_CONFIG_MODE_BASE "dsp", NULL }, { 0, 0, NULL, NULL }, }; #define SPA_TYPE_INFO_PARAM_PortConfig SPA_TYPE_INFO_PARAM_BASE "PortConfig" #define SPA_TYPE_INFO_PARAM_PORT_CONFIG_BASE SPA_TYPE_INFO_PARAM_PortConfig ":" static const struct spa_type_info spa_type_param_port_config[] = { { SPA_PARAM_PORT_CONFIG_START, SPA_TYPE_Id, SPA_TYPE_INFO_PARAM_PORT_CONFIG_BASE, spa_type_param, }, { SPA_PARAM_PORT_CONFIG_direction, SPA_TYPE_Id, SPA_TYPE_INFO_PARAM_PORT_CONFIG_BASE "direction", spa_type_direction, }, { SPA_PARAM_PORT_CONFIG_mode, SPA_TYPE_Id, SPA_TYPE_INFO_PARAM_PORT_CONFIG_BASE "mode", spa_type_param_port_config_mode }, { SPA_PARAM_PORT_CONFIG_monitor, SPA_TYPE_Bool, SPA_TYPE_INFO_PARAM_PORT_CONFIG_BASE "monitor", NULL }, { SPA_PARAM_PORT_CONFIG_control, SPA_TYPE_Bool, SPA_TYPE_INFO_PARAM_PORT_CONFIG_BASE "control", NULL }, { SPA_PARAM_PORT_CONFIG_format, SPA_TYPE_OBJECT_Format, SPA_TYPE_INFO_PARAM_PORT_CONFIG_BASE "format", NULL }, { 0, 0, NULL, NULL }, }; /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_PARAM_PORT_CONFIG_TYPES_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/param/port-config.h000066400000000000000000000024641511204443500271750ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_PARAM_PORT_CONFIG_H #define SPA_PARAM_PORT_CONFIG_H #include #ifdef __cplusplus extern "C" { #endif /** * \addtogroup spa_param * \{ */ enum spa_param_port_config_mode { SPA_PARAM_PORT_CONFIG_MODE_none, /**< no configuration */ SPA_PARAM_PORT_CONFIG_MODE_passthrough, /**< passthrough configuration */ SPA_PARAM_PORT_CONFIG_MODE_convert, /**< convert configuration */ SPA_PARAM_PORT_CONFIG_MODE_dsp, /**< dsp configuration, depending on the external * format. For audio, ports will be configured for * the given number of channels with F32 format. */ }; /** properties for SPA_TYPE_OBJECT_ParamPortConfig */ enum spa_param_port_config { SPA_PARAM_PORT_CONFIG_START, SPA_PARAM_PORT_CONFIG_direction, /**< (Id enum spa_direction) direction */ SPA_PARAM_PORT_CONFIG_mode, /**< (Id enum spa_param_port_config_mode) mode */ SPA_PARAM_PORT_CONFIG_monitor, /**< (Bool) enable monitor output ports on input ports */ SPA_PARAM_PORT_CONFIG_control, /**< (Bool) enable control ports */ SPA_PARAM_PORT_CONFIG_format, /**< (Object) format filter */ }; /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_PARAM_PORT_CONFIG_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/param/profile-types.h000066400000000000000000000030371511204443500275450ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_PARAM_PROFILE_TYPES_H #define SPA_PARAM_PROFILE_TYPES_H #include #include #ifdef __cplusplus extern "C" { #endif /** * \addtogroup spa_param * \{ */ #define SPA_TYPE_INFO_PARAM_Profile SPA_TYPE_INFO_PARAM_BASE "Profile" #define SPA_TYPE_INFO_PARAM_PROFILE_BASE SPA_TYPE_INFO_PARAM_Profile ":" static const struct spa_type_info spa_type_param_profile[] = { { SPA_PARAM_PROFILE_START, SPA_TYPE_Id, SPA_TYPE_INFO_PARAM_PROFILE_BASE, spa_type_param, }, { SPA_PARAM_PROFILE_index, SPA_TYPE_Int, SPA_TYPE_INFO_PARAM_PROFILE_BASE "index", NULL }, { SPA_PARAM_PROFILE_name, SPA_TYPE_String, SPA_TYPE_INFO_PARAM_PROFILE_BASE "name", NULL }, { SPA_PARAM_PROFILE_description, SPA_TYPE_String, SPA_TYPE_INFO_PARAM_PROFILE_BASE "description", NULL }, { SPA_PARAM_PROFILE_priority, SPA_TYPE_Int, SPA_TYPE_INFO_PARAM_PROFILE_BASE "priority", NULL }, { SPA_PARAM_PROFILE_available, SPA_TYPE_Id, SPA_TYPE_INFO_PARAM_PROFILE_BASE "available", spa_type_param_availability, }, { SPA_PARAM_PROFILE_info, SPA_TYPE_Struct, SPA_TYPE_INFO_PARAM_PROFILE_BASE "info", NULL, }, { SPA_PARAM_PROFILE_classes, SPA_TYPE_Struct, SPA_TYPE_INFO_PARAM_PROFILE_BASE "classes", NULL, }, { SPA_PARAM_PROFILE_save, SPA_TYPE_Bool, SPA_TYPE_INFO_PARAM_PROFILE_BASE "save", NULL, }, { 0, 0, NULL, NULL }, }; /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_PARAM_PROFILE_TYPES_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/param/profile.h000066400000000000000000000026361511204443500264070ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_PARAM_PROFILE_H #define SPA_PARAM_PROFILE_H #include #ifdef __cplusplus extern "C" { #endif /** * \addtogroup spa_param * \{ */ /** properties for SPA_TYPE_OBJECT_ParamProfile */ enum spa_param_profile { SPA_PARAM_PROFILE_START, SPA_PARAM_PROFILE_index, /**< profile index (Int) */ SPA_PARAM_PROFILE_name, /**< profile name (String) */ SPA_PARAM_PROFILE_description, /**< profile description (String) */ SPA_PARAM_PROFILE_priority, /**< profile priority (Int) */ SPA_PARAM_PROFILE_available, /**< availability of the profile * (Id enum spa_param_availability) */ SPA_PARAM_PROFILE_info, /**< info (Struct( * Int : n_items, * (String : key, * String : value)*)) */ SPA_PARAM_PROFILE_classes, /**< node classes provided by this profile * (Struct( * Int : number of items following * Struct( * String : class name (eg. "Audio/Source"), * Int : number of nodes * String : property (eg. "card.profile.devices"), * Array of Int: device indexes * )*)) */ SPA_PARAM_PROFILE_save, /**< If profile should be saved (Bool) */ }; /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_PARAM_PROFILE_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/param/profiler-types.h000066400000000000000000000022771511204443500277340ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_PARAM_PROFILER_TYPES_H #define SPA_PARAM_PROFILER_TYPES_H #include #include #ifdef __cplusplus extern "C" { #endif /** * \addtogroup spa_param * \{ */ #define SPA_TYPE_INFO_Profiler SPA_TYPE_INFO_OBJECT_BASE "Profiler" #define SPA_TYPE_INFO_PROFILER_BASE SPA_TYPE_INFO_Profiler ":" static const struct spa_type_info spa_type_profiler[] = { { SPA_PROFILER_START, SPA_TYPE_Id, SPA_TYPE_INFO_PROFILER_BASE, spa_type_param, }, { SPA_PROFILER_info, SPA_TYPE_Struct, SPA_TYPE_INFO_PROFILER_BASE "info", NULL, }, { SPA_PROFILER_clock, SPA_TYPE_Struct, SPA_TYPE_INFO_PROFILER_BASE "clock", NULL, }, { SPA_PROFILER_driverBlock, SPA_TYPE_Struct, SPA_TYPE_INFO_PROFILER_BASE "driverBlock", NULL, }, { SPA_PROFILER_followerBlock, SPA_TYPE_Struct, SPA_TYPE_INFO_PROFILER_BASE "followerBlock", NULL, }, { SPA_PROFILER_followerClock, SPA_TYPE_Struct, SPA_TYPE_INFO_PROFILER_BASE "followerClock", NULL, }, { 0, 0, NULL, NULL }, }; /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_PARAM_PROFILER_TYPES_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/param/profiler.h000066400000000000000000000053601511204443500265660ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2020 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_PARAM_PROFILER_H #define SPA_PARAM_PROFILER_H #include #ifdef __cplusplus extern "C" { #endif /** * \addtogroup spa_param * \{ */ /** properties for SPA_TYPE_OBJECT_Profiler */ enum spa_profiler { SPA_PROFILER_START, SPA_PROFILER_START_Driver = 0x10000, /**< driver related profiler properties */ SPA_PROFILER_info, /**< Generic info, counter and CPU load, * (Struct( * Long : counter, * Float : cpu_load fast, * Float : cpu_load medium, * Float : cpu_load slow), * Int : xrun-count)) */ SPA_PROFILER_clock, /**< clock information * (Struct( * Int : clock flags, * Int : clock id, * String: clock name, * Long : clock nsec, * Fraction : clock rate, * Long : clock position, * Long : clock duration, * Long : clock delay, * Double : clock rate_diff, * Long : clock next_nsec, * Int : transport_state, * Int : clock cycle, * Long : xrun duration)) */ SPA_PROFILER_driverBlock, /**< generic driver info block * (Struct( * Int : driver_id, * String : name, * Long : driver prev_signal, * Long : driver signal, * Long : driver awake, * Long : driver finish, * Int : driver status, * Fraction : latency, * Int : xrun_count)) */ SPA_PROFILER_START_Follower = 0x20000, /**< follower related profiler properties */ SPA_PROFILER_followerBlock, /**< generic follower info block * (Struct( * Int : id, * String : name, * Long : prev_signal, * Long : signal, * Long : awake, * Long : finish, * Int : status, * Fraction : latency, * Int : xrun_count)) * Bool : async)) */ SPA_PROFILER_followerClock, /**< follower clock information * (Struct( * Int : clock id, * String: clock name, * Long : clock nsec, * Fraction : clock rate, * Long : clock position, * Long : clock duration, * Long : clock delay, * Double : clock rate_diff, * Long : clock next_nsec, * Long : xrun duration)) */ SPA_PROFILER_START_CUSTOM = 0x1000000, }; /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_PARAM_PROFILER_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/param/props-types.h000066400000000000000000000140311511204443500272440ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_PARAM_PROPS_TYPES_H #define SPA_PARAM_PROPS_TYPES_H #include #include #ifdef __cplusplus extern "C" { #endif /** * \addtogroup spa_param * \{ */ /** Props Param */ #define SPA_TYPE_INFO_Props SPA_TYPE_INFO_PARAM_BASE "Props" #define SPA_TYPE_INFO_PROPS_BASE SPA_TYPE_INFO_Props ":" static const struct spa_type_info spa_type_props[] = { { SPA_PROP_START, SPA_TYPE_Id, SPA_TYPE_INFO_PROPS_BASE, spa_type_param, }, { SPA_PROP_unknown, SPA_TYPE_None, SPA_TYPE_INFO_PROPS_BASE "unknown", NULL }, { SPA_PROP_device, SPA_TYPE_String, SPA_TYPE_INFO_PROPS_BASE "device", NULL }, { SPA_PROP_deviceName, SPA_TYPE_String, SPA_TYPE_INFO_PROPS_BASE "deviceName", NULL }, { SPA_PROP_deviceFd, SPA_TYPE_Fd, SPA_TYPE_INFO_PROPS_BASE "deviceFd", NULL }, { SPA_PROP_card, SPA_TYPE_String, SPA_TYPE_INFO_PROPS_BASE "card", NULL }, { SPA_PROP_cardName, SPA_TYPE_String, SPA_TYPE_INFO_PROPS_BASE "cardName", NULL }, { SPA_PROP_minLatency, SPA_TYPE_Int, SPA_TYPE_INFO_PROPS_BASE "minLatency", NULL }, { SPA_PROP_maxLatency, SPA_TYPE_Int, SPA_TYPE_INFO_PROPS_BASE "maxLatency", NULL }, { SPA_PROP_periods, SPA_TYPE_Int, SPA_TYPE_INFO_PROPS_BASE "periods", NULL }, { SPA_PROP_periodSize, SPA_TYPE_Int, SPA_TYPE_INFO_PROPS_BASE "periodSize", NULL }, { SPA_PROP_periodEvent, SPA_TYPE_Bool, SPA_TYPE_INFO_PROPS_BASE "periodEvent", NULL }, { SPA_PROP_live, SPA_TYPE_Bool, SPA_TYPE_INFO_PROPS_BASE "live", NULL }, { SPA_PROP_rate, SPA_TYPE_Double, SPA_TYPE_INFO_PROPS_BASE "rate", NULL }, { SPA_PROP_quality, SPA_TYPE_Int, SPA_TYPE_INFO_PROPS_BASE "quality", NULL }, { SPA_PROP_bluetoothAudioCodec, SPA_TYPE_Id, SPA_TYPE_INFO_PROPS_BASE "bluetoothAudioCodec", spa_type_bluetooth_audio_codec }, { SPA_PROP_bluetoothOffloadActive, SPA_TYPE_Bool, SPA_TYPE_INFO_PROPS_BASE "bluetoothOffloadActive", NULL }, { SPA_PROP_clockId, SPA_TYPE_String, SPA_TYPE_INFO_PROPS_BASE "clockId", NULL }, { SPA_PROP_clockDevice, SPA_TYPE_String, SPA_TYPE_INFO_PROPS_BASE "clockDevice", NULL }, { SPA_PROP_clockInterface, SPA_TYPE_String, SPA_TYPE_INFO_PROPS_BASE "clockInterface", NULL }, { SPA_PROP_waveType, SPA_TYPE_Int, SPA_TYPE_INFO_PROPS_BASE "waveType", NULL }, { SPA_PROP_frequency, SPA_TYPE_Int, SPA_TYPE_INFO_PROPS_BASE "frequency", NULL }, { SPA_PROP_volume, SPA_TYPE_Float, SPA_TYPE_INFO_PROPS_BASE "volume", NULL }, { SPA_PROP_mute, SPA_TYPE_Bool, SPA_TYPE_INFO_PROPS_BASE "mute", NULL }, { SPA_PROP_patternType, SPA_TYPE_Int, SPA_TYPE_INFO_PROPS_BASE "patternType", NULL }, { SPA_PROP_ditherType, SPA_TYPE_Int, SPA_TYPE_INFO_PROPS_BASE "ditherType", NULL }, { SPA_PROP_truncate, SPA_TYPE_Bool, SPA_TYPE_INFO_PROPS_BASE "truncate", NULL }, { SPA_PROP_channelVolumes, SPA_TYPE_Array, SPA_TYPE_INFO_PROPS_BASE "channelVolumes", spa_type_prop_float_array }, { SPA_PROP_volumeBase, SPA_TYPE_Float, SPA_TYPE_INFO_PROPS_BASE "volumeBase", NULL }, { SPA_PROP_volumeStep, SPA_TYPE_Float, SPA_TYPE_INFO_PROPS_BASE "volumeStep", NULL }, { SPA_PROP_channelMap, SPA_TYPE_Array, SPA_TYPE_INFO_PROPS_BASE "channelMap", spa_type_prop_channel_map }, { SPA_PROP_monitorMute, SPA_TYPE_Bool, SPA_TYPE_INFO_PROPS_BASE "monitorMute", NULL }, { SPA_PROP_monitorVolumes, SPA_TYPE_Array, SPA_TYPE_INFO_PROPS_BASE "monitorVolumes", spa_type_prop_float_array }, { SPA_PROP_latencyOffsetNsec, SPA_TYPE_Long, SPA_TYPE_INFO_PROPS_BASE "latencyOffsetNsec", NULL }, { SPA_PROP_softMute, SPA_TYPE_Bool, SPA_TYPE_INFO_PROPS_BASE "softMute", NULL }, { SPA_PROP_softVolumes, SPA_TYPE_Array, SPA_TYPE_INFO_PROPS_BASE "softVolumes", spa_type_prop_float_array }, { SPA_PROP_iec958Codecs, SPA_TYPE_Array, SPA_TYPE_INFO_PROPS_BASE "iec958Codecs", spa_type_prop_iec958_codec }, { SPA_PROP_volumeRampSamples, SPA_TYPE_Int, SPA_TYPE_INFO_PROPS_BASE "volumeRampSamples", NULL }, { SPA_PROP_volumeRampStepSamples, SPA_TYPE_Int, SPA_TYPE_INFO_PROPS_BASE "volumeRampStepSamples", NULL }, { SPA_PROP_volumeRampTime, SPA_TYPE_Int, SPA_TYPE_INFO_PROPS_BASE "volumeRampTime", NULL }, { SPA_PROP_volumeRampStepTime, SPA_TYPE_Int, SPA_TYPE_INFO_PROPS_BASE "volumeRampStepTime", NULL }, { SPA_PROP_volumeRampScale, SPA_TYPE_Id, SPA_TYPE_INFO_PROPS_BASE "volumeRampScale", spa_type_audio_volume_ramp_scale }, { SPA_PROP_brightness, SPA_TYPE_Float, SPA_TYPE_INFO_PROPS_BASE "brightness", NULL }, { SPA_PROP_contrast, SPA_TYPE_Float, SPA_TYPE_INFO_PROPS_BASE "contrast", NULL }, { SPA_PROP_saturation, SPA_TYPE_Float, SPA_TYPE_INFO_PROPS_BASE "saturation", NULL }, { SPA_PROP_hue, SPA_TYPE_Int, SPA_TYPE_INFO_PROPS_BASE "hue", NULL }, { SPA_PROP_gamma, SPA_TYPE_Int, SPA_TYPE_INFO_PROPS_BASE "gamma", NULL }, { SPA_PROP_exposure, SPA_TYPE_Int, SPA_TYPE_INFO_PROPS_BASE "exposure", NULL }, { SPA_PROP_gain, SPA_TYPE_Float, SPA_TYPE_INFO_PROPS_BASE "gain", NULL }, { SPA_PROP_sharpness, SPA_TYPE_Float, SPA_TYPE_INFO_PROPS_BASE "sharpness", NULL }, { SPA_PROP_params, SPA_TYPE_Struct, SPA_TYPE_INFO_PROPS_BASE "params", NULL }, { 0, 0, NULL, NULL }, }; /** Enum Property info */ #define SPA_TYPE_INFO_PropInfo SPA_TYPE_INFO_PARAM_BASE "PropInfo" #define SPA_TYPE_INFO_PROP_INFO_BASE SPA_TYPE_INFO_PropInfo ":" static const struct spa_type_info spa_type_prop_info[] = { { SPA_PROP_INFO_START, SPA_TYPE_Id, SPA_TYPE_INFO_PROP_INFO_BASE, spa_type_param, }, { SPA_PROP_INFO_id, SPA_TYPE_Id, SPA_TYPE_INFO_PROP_INFO_BASE "id", spa_type_props }, { SPA_PROP_INFO_name, SPA_TYPE_String, SPA_TYPE_INFO_PROP_INFO_BASE "name", NULL }, { SPA_PROP_INFO_type, SPA_TYPE_Pod, SPA_TYPE_INFO_PROP_INFO_BASE "type", NULL }, { SPA_PROP_INFO_labels, SPA_TYPE_Struct, SPA_TYPE_INFO_PROP_INFO_BASE "labels", NULL }, { SPA_PROP_INFO_container, SPA_TYPE_Id, SPA_TYPE_INFO_PROP_INFO_BASE "container", NULL }, { SPA_PROP_INFO_params, SPA_TYPE_Bool, SPA_TYPE_INFO_PROP_INFO_BASE "params", NULL }, { SPA_PROP_INFO_description, SPA_TYPE_String, SPA_TYPE_INFO_PROP_INFO_BASE "description", NULL }, { 0, 0, NULL, NULL }, }; /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_PARAM_PROPS_TYPES_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/param/props.h000066400000000000000000000077211511204443500261120ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_PARAM_PROPS_H #define SPA_PARAM_PROPS_H #include #ifdef __cplusplus extern "C" { #endif /** * \addtogroup spa_param * \{ */ /** properties of SPA_TYPE_OBJECT_PropInfo */ enum spa_prop_info { SPA_PROP_INFO_START, SPA_PROP_INFO_id, /**< associated id of the property */ SPA_PROP_INFO_name, /**< name of the property */ SPA_PROP_INFO_type, /**< type and range/enums of property */ SPA_PROP_INFO_labels, /**< labels of property if any, this is a * struct with pairs of values, the first one * is of the type of the property, the second * one is a string with a user readable label * for the value. */ SPA_PROP_INFO_container, /**< type of container if any (Id) */ SPA_PROP_INFO_params, /**< is part of params property (Bool) */ SPA_PROP_INFO_description, /**< User readable description */ }; /** predefined properties for SPA_TYPE_OBJECT_Props */ enum spa_prop { SPA_PROP_START, SPA_PROP_unknown, /**< an unknown property */ SPA_PROP_START_Device = 0x100, /**< device related properties */ SPA_PROP_device, SPA_PROP_deviceName, SPA_PROP_deviceFd, SPA_PROP_card, SPA_PROP_cardName, SPA_PROP_minLatency, SPA_PROP_maxLatency, SPA_PROP_periods, SPA_PROP_periodSize, SPA_PROP_periodEvent, SPA_PROP_live, SPA_PROP_rate, SPA_PROP_quality, SPA_PROP_bluetoothAudioCodec, SPA_PROP_bluetoothOffloadActive, SPA_PROP_clockId, SPA_PROP_clockDevice, SPA_PROP_clockInterface, SPA_PROP_START_Audio = 0x10000, /**< audio related properties */ SPA_PROP_waveType, SPA_PROP_frequency, SPA_PROP_volume, /**< a volume (Float), 0.0 silence, 1.0 no attenutation */ SPA_PROP_mute, /**< mute (Bool) */ SPA_PROP_patternType, SPA_PROP_ditherType, SPA_PROP_truncate, SPA_PROP_channelVolumes, /**< a volume array, one (linear) volume per channel * (Array of Float). 0.0 is silence, 1.0 is * without attenuation. This is the effective * volume that is applied. It can result * in a hardware volume and software volume * (see softVolumes) */ SPA_PROP_volumeBase, /**< a volume base (Float) */ SPA_PROP_volumeStep, /**< a volume step (Float) */ SPA_PROP_channelMap, /**< a channelmap array * (Array (Id enum spa_audio_channel)) */ SPA_PROP_monitorMute, /**< mute (Bool) */ SPA_PROP_monitorVolumes, /**< a volume array, one (linear) volume per * channel (Array of Float) */ SPA_PROP_latencyOffsetNsec, /**< delay adjustment */ SPA_PROP_softMute, /**< mute (Bool) applied in software */ SPA_PROP_softVolumes, /**< a volume array, one (linear) volume per channel * (Array of Float). 0.0 is silence, 1.0 is without * attenuation. This is the volume applied in * software, there might be a part applied in * hardware. */ SPA_PROP_iec958Codecs, /**< enabled IEC958 (S/PDIF) codecs, * (Array (Id enum spa_audio_iec958_codec) */ SPA_PROP_volumeRampSamples, /**< Samples to ramp the volume over */ SPA_PROP_volumeRampStepSamples, /**< Step or incremental Samples to ramp * the volume over */ SPA_PROP_volumeRampTime, /**< Time in millisec to ramp the volume over */ SPA_PROP_volumeRampStepTime, /**< Step or incremental Time in nano seconds * to ramp the */ SPA_PROP_volumeRampScale, /**< the scale or graph to used to ramp the * volume */ SPA_PROP_START_Video = 0x20000, /**< video related properties */ SPA_PROP_brightness, SPA_PROP_contrast, SPA_PROP_saturation, SPA_PROP_hue, SPA_PROP_gamma, SPA_PROP_exposure, SPA_PROP_gain, SPA_PROP_sharpness, SPA_PROP_START_Other = 0x80000, /**< other properties */ SPA_PROP_params, /**< simple control params * (Struct( * (String : key, * Pod : value)*)) */ SPA_PROP_START_CUSTOM = 0x1000000, }; /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_PARAM_PROPS_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/param/route-types.h000066400000000000000000000040361511204443500272430ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_PARAM_ROUTE_TYPES_H #define SPA_PARAM_ROUTE_TYPES_H #include #include #include #ifdef __cplusplus extern "C" { #endif /** * \addtogroup spa_param * \{ */ #define SPA_TYPE_INFO_PARAM_Route SPA_TYPE_INFO_PARAM_BASE "Route" #define SPA_TYPE_INFO_PARAM_ROUTE_BASE SPA_TYPE_INFO_PARAM_Route ":" static const struct spa_type_info spa_type_param_route[] = { { SPA_PARAM_ROUTE_START, SPA_TYPE_Id, SPA_TYPE_INFO_PARAM_ROUTE_BASE, spa_type_param, }, { SPA_PARAM_ROUTE_index, SPA_TYPE_Int, SPA_TYPE_INFO_PARAM_ROUTE_BASE "index", NULL, }, { SPA_PARAM_ROUTE_direction, SPA_TYPE_Id, SPA_TYPE_INFO_PARAM_ROUTE_BASE "direction", spa_type_direction, }, { SPA_PARAM_ROUTE_device, SPA_TYPE_Int, SPA_TYPE_INFO_PARAM_ROUTE_BASE "device", NULL, }, { SPA_PARAM_ROUTE_name, SPA_TYPE_String, SPA_TYPE_INFO_PARAM_ROUTE_BASE "name", NULL, }, { SPA_PARAM_ROUTE_description, SPA_TYPE_String, SPA_TYPE_INFO_PARAM_ROUTE_BASE "description", NULL, }, { SPA_PARAM_ROUTE_priority, SPA_TYPE_Int, SPA_TYPE_INFO_PARAM_ROUTE_BASE "priority", NULL, }, { SPA_PARAM_ROUTE_available, SPA_TYPE_Id, SPA_TYPE_INFO_PARAM_ROUTE_BASE "available", spa_type_param_availability, }, { SPA_PARAM_ROUTE_info, SPA_TYPE_Struct, SPA_TYPE_INFO_PARAM_ROUTE_BASE "info", NULL, }, { SPA_PARAM_ROUTE_profiles, SPA_TYPE_Array, SPA_TYPE_INFO_PARAM_ROUTE_BASE "profiles", spa_type_prop_int_array, }, { SPA_PARAM_ROUTE_props, SPA_TYPE_OBJECT_Props, SPA_TYPE_INFO_PARAM_ROUTE_BASE "props", NULL, }, { SPA_PARAM_ROUTE_devices, SPA_TYPE_Array, SPA_TYPE_INFO_PARAM_ROUTE_BASE "devices", spa_type_prop_int_array, }, { SPA_PARAM_ROUTE_profile, SPA_TYPE_Int, SPA_TYPE_INFO_PARAM_ROUTE_BASE "profile", NULL, }, { SPA_PARAM_ROUTE_save, SPA_TYPE_Bool, SPA_TYPE_INFO_PARAM_ROUTE_BASE "save", NULL, }, { 0, 0, NULL, NULL }, }; /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_PARAM_ROUTE_TYPES_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/param/route.h000066400000000000000000000027271511204443500261060ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_PARAM_ROUTE_H #define SPA_PARAM_ROUTE_H #include #ifdef __cplusplus extern "C" { #endif /** * \addtogroup spa_param * \{ */ /** properties for SPA_TYPE_OBJECT_ParamRoute */ enum spa_param_route { SPA_PARAM_ROUTE_START, SPA_PARAM_ROUTE_index, /**< index of the routing destination (Int) */ SPA_PARAM_ROUTE_direction, /**< direction, input/output (Id enum spa_direction) */ SPA_PARAM_ROUTE_device, /**< device id (Int) */ SPA_PARAM_ROUTE_name, /**< name of the routing destination (String) */ SPA_PARAM_ROUTE_description, /**< description of the destination (String) */ SPA_PARAM_ROUTE_priority, /**< priority of the destination (Int) */ SPA_PARAM_ROUTE_available, /**< availability of the destination * (Id enum spa_param_availability) */ SPA_PARAM_ROUTE_info, /**< info (Struct( * Int : n_items, * (String : key, * String : value)*)) */ SPA_PARAM_ROUTE_profiles, /**< associated profile indexes (Array of Int) */ SPA_PARAM_ROUTE_props, /**< properties SPA_TYPE_OBJECT_Props */ SPA_PARAM_ROUTE_devices, /**< associated device indexes (Array of Int) */ SPA_PARAM_ROUTE_profile, /**< profile id (Int) */ SPA_PARAM_ROUTE_save, /**< If route should be saved (Bool) */ }; /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_PARAM_ROUTE_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/param/tag-types.h000066400000000000000000000016651511204443500266650ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_PARAM_TAG_TYPES_H #define SPA_PARAM_TAG_TYPES_H #include #include #include #ifdef __cplusplus extern "C" { #endif /** * \addtogroup spa_param * \{ */ #define SPA_TYPE_INFO_PARAM_Tag SPA_TYPE_INFO_PARAM_BASE "Tag" #define SPA_TYPE_INFO_PARAM_TAG_BASE SPA_TYPE_INFO_PARAM_Tag ":" static const struct spa_type_info spa_type_param_tag[] = { { SPA_PARAM_TAG_START, SPA_TYPE_Id, SPA_TYPE_INFO_PARAM_TAG_BASE, spa_type_param, }, { SPA_PARAM_TAG_direction, SPA_TYPE_Id, SPA_TYPE_INFO_PARAM_TAG_BASE "direction", spa_type_direction, }, { SPA_PARAM_TAG_info, SPA_TYPE_Struct, SPA_TYPE_INFO_PARAM_TAG_BASE "info", NULL, }, { 0, 0, NULL, NULL }, }; /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_PARAM_TAG_TYPES_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/param/tag-utils.h000066400000000000000000000071441511204443500266570ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2023 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_PARAM_TAG_UTILS_H #define SPA_PARAM_TAG_UTILS_H #include #include #include #include #include #include #include #ifdef __cplusplus extern "C" { #endif /** * \addtogroup spa_param * \{ */ #ifndef SPA_API_TAG_UTILS #ifdef SPA_API_IMPL #define SPA_API_TAG_UTILS SPA_API_IMPL #else #define SPA_API_TAG_UTILS static inline #endif #endif SPA_API_TAG_UTILS int spa_tag_compare(const struct spa_pod *a, const struct spa_pod *b) { return spa_pod_memcmp(a, b); } SPA_API_TAG_UTILS int spa_tag_parse(const struct spa_pod *tag, struct spa_tag_info *info, void **state) { int res; const struct spa_pod_object *obj = (const struct spa_pod_object*)tag; const struct spa_pod_prop *first, *start, *cur; spa_zero(*info); if ((res = spa_pod_parse_object(tag, SPA_TYPE_OBJECT_ParamTag, NULL, SPA_PARAM_TAG_direction, SPA_POD_Id(&info->direction))) < 0) return res; first = spa_pod_prop_first(&obj->body); start = *state ? spa_pod_prop_next((struct spa_pod_prop*)*state) : first; res = 0; for (cur = start; spa_pod_prop_is_inside(&obj->body, obj->pod.size, cur); cur = spa_pod_prop_next(cur)) { if (cur->key == SPA_PARAM_TAG_info) { info->info = &cur->value; *state = (void*)cur; return 1; } } return 0; } SPA_API_TAG_UTILS int spa_tag_info_parse(const struct spa_tag_info *info, struct spa_dict *dict, struct spa_dict_item *items) { struct spa_pod_parser prs; uint32_t n, n_items; const char *key, *value; struct spa_pod_frame f[1]; spa_pod_parser_pod(&prs, info->info); if (spa_pod_parser_push_struct(&prs, &f[0]) < 0 || spa_pod_parser_get_int(&prs, (int32_t*)&n_items) < 0) return -EINVAL; if (items == NULL) { dict->n_items = n_items; return 0; } n_items = SPA_MIN(dict->n_items, n_items); for (n = 0; n < n_items; n++) { if (spa_pod_parser_get(&prs, SPA_POD_String(&key), SPA_POD_String(&value), NULL) < 0) break; if (key == NULL || value == NULL) return -EINVAL; items[n].key = key; items[n].value = value; } dict->items = items; spa_pod_parser_pop(&prs, &f[0]); return 0; } SPA_API_TAG_UTILS void spa_tag_build_start(struct spa_pod_builder *builder, struct spa_pod_frame *f, uint32_t id, enum spa_direction direction) { spa_pod_builder_push_object(builder, f, SPA_TYPE_OBJECT_ParamTag, id); spa_pod_builder_add(builder, SPA_PARAM_TAG_direction, SPA_POD_Id(direction), 0); } SPA_API_TAG_UTILS void spa_tag_build_add_info(struct spa_pod_builder *builder, const struct spa_pod *info) { spa_pod_builder_add(builder, SPA_PARAM_TAG_info, SPA_POD_Pod(info), 0); } SPA_API_TAG_UTILS void spa_tag_build_add_dict(struct spa_pod_builder *builder, const struct spa_dict *dict) { uint32_t i, n_items; struct spa_pod_frame f; n_items = dict ? dict->n_items : 0; spa_pod_builder_prop(builder, SPA_PARAM_TAG_info, SPA_POD_PROP_FLAG_HINT_DICT); spa_pod_builder_push_struct(builder, &f); spa_pod_builder_int(builder, n_items); for (i = 0; i < n_items; i++) { spa_pod_builder_string(builder, dict->items[i].key); spa_pod_builder_string(builder, dict->items[i].value); } spa_pod_builder_pop(builder, &f); } SPA_API_TAG_UTILS struct spa_pod * spa_tag_build_end(struct spa_pod_builder *builder, struct spa_pod_frame *f) { return (struct spa_pod*)spa_pod_builder_pop(builder, f); } /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_PARAM_TAG_UTILS_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/param/tag.h000066400000000000000000000016241511204443500255160ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2023 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_PARAM_TAG_H #define SPA_PARAM_TAG_H #include #ifdef __cplusplus extern "C" { #endif /** * \addtogroup spa_param * \{ */ /** properties for SPA_TYPE_OBJECT_ParamTag */ enum spa_param_tag { SPA_PARAM_TAG_START, SPA_PARAM_TAG_direction, /**< direction, input/output (Id enum spa_direction) */ SPA_PARAM_TAG_info, /**< Struct( * Int: n_items * (String: key * String: value)* * ) */ }; /** helper structure for managing tag objects */ struct spa_tag_info { enum spa_direction direction; const struct spa_pod *info; }; #define SPA_TAG_INFO(dir,...) ((struct spa_tag_info) { .direction = (dir), ## __VA_ARGS__ }) /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_PARAM_TAG_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/param/type-info.h000066400000000000000000000012111511204443500266450ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_PARAM_TYPE_INFO_H #define SPA_PARAM_TYPE_INFO_H #include #include #include #include #include #include #include #include #include #include #include #include #endif /* SPA_PARAM_TYPE_INFO_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/param/video/000077500000000000000000000000001511204443500256755ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/param/video/chroma.h000066400000000000000000000024431511204443500273220ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_VIDEO_CHROMA_H #define SPA_VIDEO_CHROMA_H #ifdef __cplusplus extern "C" { #endif /** * \addtogroup spa_param * \{ */ /** Various Chroma settings. */ enum spa_video_chroma_site { SPA_VIDEO_CHROMA_SITE_UNKNOWN = 0, /**< unknown cositing */ SPA_VIDEO_CHROMA_SITE_NONE = (1 << 0), /**< no cositing */ SPA_VIDEO_CHROMA_SITE_H_COSITED = (1 << 1), /**< chroma is horizontally cosited */ SPA_VIDEO_CHROMA_SITE_V_COSITED = (1 << 2), /**< chroma is vertically cosited */ SPA_VIDEO_CHROMA_SITE_ALT_LINE = (1 << 3), /**< chroma samples are sited on alternate lines */ /* some common chroma cositing */ /** chroma samples cosited with luma samples */ SPA_VIDEO_CHROMA_SITE_COSITED = (SPA_VIDEO_CHROMA_SITE_H_COSITED | SPA_VIDEO_CHROMA_SITE_V_COSITED), /** jpeg style cositing, also for mpeg1 and mjpeg */ SPA_VIDEO_CHROMA_SITE_JPEG = (SPA_VIDEO_CHROMA_SITE_NONE), /** mpeg2 style cositing */ SPA_VIDEO_CHROMA_SITE_MPEG2 = (SPA_VIDEO_CHROMA_SITE_H_COSITED), /**< DV style cositing */ SPA_VIDEO_CHROMA_SITE_DV = (SPA_VIDEO_CHROMA_SITE_COSITED | SPA_VIDEO_CHROMA_SITE_ALT_LINE), }; /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_VIDEO_CHROMA_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/param/video/color-types.h000066400000000000000000000130711511204443500303300ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2025 PipeWire authors */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_VIDEO_COLOR_TYPES_H #define SPA_VIDEO_COLOR_TYPES_H #include #include #define SPA_TYPE_INFO_VideColorRange SPA_TYPE_INFO_ENUM_BASE "VideoColorRange" #define SPA_TYPE_INFO_VIDEO_COLOR_RANGE_BASE SPA_TYPE_INFO_VideColorRange ":" static const struct spa_type_info spa_type_video_color_range[] = { { SPA_VIDEO_COLOR_RANGE_UNKNOWN, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_COLOR_RANGE_BASE "unknown", NULL }, { SPA_VIDEO_COLOR_RANGE_0_255, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_COLOR_RANGE_BASE "0-255", NULL }, { SPA_VIDEO_COLOR_RANGE_16_235, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_COLOR_RANGE_BASE "16-235", NULL }, { 0, 0, NULL, NULL }, }; #define SPA_TYPE_INFO_VideoColorMatrix SPA_TYPE_INFO_ENUM_BASE "VideoColorMatrix" #define SPA_TYPE_INFO_VIDEO_COLOR_MATRIX_BASE SPA_TYPE_INFO_VideoColorMatrix ":" static const struct spa_type_info spa_type_video_color_matrix[] = { { SPA_VIDEO_COLOR_MATRIX_UNKNOWN, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_COLOR_MATRIX_BASE "unknown", NULL }, { SPA_VIDEO_COLOR_MATRIX_RGB, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_COLOR_MATRIX_BASE "rgb", NULL }, { SPA_VIDEO_COLOR_MATRIX_FCC, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_COLOR_MATRIX_BASE "fcc", NULL }, { SPA_VIDEO_COLOR_MATRIX_BT709, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_COLOR_MATRIX_BASE "bt709", NULL }, { SPA_VIDEO_COLOR_MATRIX_BT601, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_COLOR_MATRIX_BASE "bt601", NULL }, { SPA_VIDEO_COLOR_MATRIX_SMPTE240M, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_COLOR_MATRIX_BASE "smpte240m", NULL }, { SPA_VIDEO_COLOR_MATRIX_BT2020, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_COLOR_MATRIX_BASE "bt2020", NULL }, { 0, 0, NULL, NULL }, }; #define SPA_TYPE_INFO_VideoTransferFunction SPA_TYPE_INFO_ENUM_BASE "VideoTransferFunction" #define SPA_TYPE_INFO_VIDEO_TRANSFER_FUNCTION_BASE SPA_TYPE_INFO_VideoTransferFunction ":" static const struct spa_type_info spa_type_video_transfer_function[] = { { SPA_VIDEO_TRANSFER_UNKNOWN, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_TRANSFER_FUNCTION_BASE "unknown", NULL }, { SPA_VIDEO_TRANSFER_GAMMA10, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_TRANSFER_FUNCTION_BASE "gamma10", NULL }, { SPA_VIDEO_TRANSFER_GAMMA18, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_TRANSFER_FUNCTION_BASE "gamma18", NULL }, { SPA_VIDEO_TRANSFER_GAMMA20, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_TRANSFER_FUNCTION_BASE "gamma20", NULL }, { SPA_VIDEO_TRANSFER_GAMMA22, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_TRANSFER_FUNCTION_BASE "gamma22", NULL }, { SPA_VIDEO_TRANSFER_BT709, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_TRANSFER_FUNCTION_BASE "bt709", NULL }, { SPA_VIDEO_TRANSFER_SMPTE240M, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_TRANSFER_FUNCTION_BASE "smpte240m", NULL }, { SPA_VIDEO_TRANSFER_SRGB, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_TRANSFER_FUNCTION_BASE "srgb", NULL }, { SPA_VIDEO_TRANSFER_GAMMA28, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_TRANSFER_FUNCTION_BASE "gamma28", NULL }, { SPA_VIDEO_TRANSFER_LOG100, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_TRANSFER_FUNCTION_BASE "log100", NULL }, { SPA_VIDEO_TRANSFER_LOG316, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_TRANSFER_FUNCTION_BASE "log316", NULL }, { SPA_VIDEO_TRANSFER_BT2020_12, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_TRANSFER_FUNCTION_BASE "bt2020-12", NULL }, { SPA_VIDEO_TRANSFER_ADOBERGB, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_TRANSFER_FUNCTION_BASE "adobergb", NULL }, { SPA_VIDEO_TRANSFER_BT2020_10, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_TRANSFER_FUNCTION_BASE "bt2020-10", NULL }, { SPA_VIDEO_TRANSFER_SMPTE2084, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_TRANSFER_FUNCTION_BASE "smpte2084", NULL }, { SPA_VIDEO_TRANSFER_ARIB_STD_B67, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_TRANSFER_FUNCTION_BASE "arib-std-b67", NULL }, { SPA_VIDEO_TRANSFER_BT601, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_TRANSFER_FUNCTION_BASE "bt601", NULL }, { 0, 0, NULL, NULL }, }; #define SPA_TYPE_INFO_VideoColorPrimaries SPA_TYPE_INFO_ENUM_BASE "VideoColorPrimaries" #define SPA_TYPE_INFO_VIDEO_COLOR_PRIMARIES_BASE SPA_TYPE_INFO_VideoColorPrimaries ":" static const struct spa_type_info spa_type_video_color_primaries[] = { { SPA_VIDEO_COLOR_PRIMARIES_UNKNOWN, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_COLOR_PRIMARIES_BASE "unknown", NULL }, { SPA_VIDEO_COLOR_PRIMARIES_BT709, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_COLOR_PRIMARIES_BASE "bt709", NULL }, { SPA_VIDEO_COLOR_PRIMARIES_BT470M, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_COLOR_PRIMARIES_BASE "bt470m", NULL }, { SPA_VIDEO_COLOR_PRIMARIES_BT470BG, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_COLOR_PRIMARIES_BASE "bt470bg", NULL }, { SPA_VIDEO_COLOR_PRIMARIES_SMPTE170M, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_COLOR_PRIMARIES_BASE "smpte170m", NULL }, { SPA_VIDEO_COLOR_PRIMARIES_SMPTE240M, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_COLOR_PRIMARIES_BASE "smpte240m", NULL }, { SPA_VIDEO_COLOR_PRIMARIES_FILM, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_COLOR_PRIMARIES_BASE "film", NULL }, { SPA_VIDEO_COLOR_PRIMARIES_BT2020, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_COLOR_PRIMARIES_BASE "bt2020", NULL }, { SPA_VIDEO_COLOR_PRIMARIES_ADOBERGB, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_COLOR_PRIMARIES_BASE "adobergb", NULL }, { SPA_VIDEO_COLOR_PRIMARIES_SMPTEST428, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_COLOR_PRIMARIES_BASE "smptest428", NULL }, { SPA_VIDEO_COLOR_PRIMARIES_SMPTERP431, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_COLOR_PRIMARIES_BASE "smpterp431", NULL }, { SPA_VIDEO_COLOR_PRIMARIES_SMPTEEG432, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_COLOR_PRIMARIES_BASE "smpteeg432", NULL }, { SPA_VIDEO_COLOR_PRIMARIES_EBU3213, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_COLOR_PRIMARIES_BASE "ebu3213", NULL }, { 0, 0, NULL, NULL }, }; #endif /* SPA_VIDEO_COLOR_TYPES_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/param/video/color.h000066400000000000000000000115341511204443500271700ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_VIDEO_COLOR_H #define SPA_VIDEO_COLOR_H #ifdef __cplusplus extern "C" { #endif /** * \addtogroup spa_param * \{ */ /** * Possible color range values. These constants are defined for 8 bit color * values and can be scaled for other bit depths. */ enum spa_video_color_range { SPA_VIDEO_COLOR_RANGE_UNKNOWN = 0, /**< unknown range */ SPA_VIDEO_COLOR_RANGE_0_255, /**< [0..255] for 8 bit components */ SPA_VIDEO_COLOR_RANGE_16_235 /**< [16..235] for 8 bit components. Chroma has [16..240] range. */ }; /** * The color matrix is used to convert between Y'PbPr and * non-linear RGB (R'G'B') */ enum spa_video_color_matrix { SPA_VIDEO_COLOR_MATRIX_UNKNOWN = 0, /**< unknown matrix */ SPA_VIDEO_COLOR_MATRIX_RGB, /**< identity matrix */ SPA_VIDEO_COLOR_MATRIX_FCC, /**< FCC color matrix */ SPA_VIDEO_COLOR_MATRIX_BT709, /**< ITU BT.709 color matrix */ SPA_VIDEO_COLOR_MATRIX_BT601, /**< ITU BT.601 color matrix */ SPA_VIDEO_COLOR_MATRIX_SMPTE240M, /**< SMTPE 240M color matrix */ SPA_VIDEO_COLOR_MATRIX_BT2020, /**< ITU-R BT.2020 color matrix */ }; /** * The video transfer function defines the formula for converting between * non-linear RGB (R'G'B') and linear RGB */ enum spa_video_transfer_function { SPA_VIDEO_TRANSFER_UNKNOWN = 0, /**< unknown transfer function */ SPA_VIDEO_TRANSFER_GAMMA10, /**< linear RGB, gamma 1.0 curve */ SPA_VIDEO_TRANSFER_GAMMA18, /**< Gamma 1.8 curve */ SPA_VIDEO_TRANSFER_GAMMA20, /**< Gamma 2.0 curve */ SPA_VIDEO_TRANSFER_GAMMA22, /**< Gamma 2.2 curve */ SPA_VIDEO_TRANSFER_BT709, /**< Gamma 2.2 curve with a linear segment in the lower range */ SPA_VIDEO_TRANSFER_SMPTE240M, /**< Gamma 2.2 curve with a linear segment in the lower range */ SPA_VIDEO_TRANSFER_SRGB, /**< Gamma 2.4 curve with a linear segment in the lower range */ SPA_VIDEO_TRANSFER_GAMMA28, /**< Gamma 2.8 curve */ SPA_VIDEO_TRANSFER_LOG100, /**< Logarithmic transfer characteristic 100:1 range */ SPA_VIDEO_TRANSFER_LOG316, /**< Logarithmic transfer characteristic 316.22777:1 range */ SPA_VIDEO_TRANSFER_BT2020_12, /**< Gamma 2.2 curve with a linear segment in the lower * range. Used for BT.2020 with 12 bits per * component */ SPA_VIDEO_TRANSFER_ADOBERGB, /**< Gamma 2.19921875 */ SPA_VIDEO_TRANSFER_BT2020_10, /**< Rec. ITU-R BT.2020-2 with 10 bits per component. * (functionally the same as the values * SPA_VIDEO_TRANSFER_BT709 and SPA_VIDEO_TRANSFER_BT601) */ SPA_VIDEO_TRANSFER_SMPTE2084, /**< SMPTE ST 2084 for 10, 12, 14, and 16-bit systems. * Known as perceptual quantization (PQ) */ SPA_VIDEO_TRANSFER_ARIB_STD_B67,/**< Association of Radio Industries and Businesses (ARIB) * STD-B67 and Rec. ITU-R BT.2100-1 hybrid loggamma (HLG) system */ SPA_VIDEO_TRANSFER_BT601, /**< also known as SMPTE170M / ITU-R BT1358 525 or 625 / ITU-R BT1700 NTSC * Functionally the same as the values * SPA_VIDEO_TRANSFER_BT709, and SPA_VIDEO_TRANSFER_BT2020_10 */ }; /** * The color primaries define the how to transform linear RGB values to and from * the CIE XYZ colorspace. */ enum spa_video_color_primaries { SPA_VIDEO_COLOR_PRIMARIES_UNKNOWN = 0, /**< unknown color primaries */ SPA_VIDEO_COLOR_PRIMARIES_BT709, /**< BT709 primaries */ SPA_VIDEO_COLOR_PRIMARIES_BT470M, /**< BT470M primaries */ SPA_VIDEO_COLOR_PRIMARIES_BT470BG, /**< BT470BG primaries */ SPA_VIDEO_COLOR_PRIMARIES_SMPTE170M, /**< SMPTE170M primaries */ SPA_VIDEO_COLOR_PRIMARIES_SMPTE240M, /**< SMPTE240M primaries */ SPA_VIDEO_COLOR_PRIMARIES_FILM, /**< Generic film */ SPA_VIDEO_COLOR_PRIMARIES_BT2020, /**< BT2020 primaries */ SPA_VIDEO_COLOR_PRIMARIES_ADOBERGB, /**< Adobe RGB primaries */ SPA_VIDEO_COLOR_PRIMARIES_SMPTEST428, /**< SMPTE ST 428 primaries (CIE 1931 XYZ) */ SPA_VIDEO_COLOR_PRIMARIES_SMPTERP431, /**< SMPTE RP 431 primaries (ST 431-2 (2011) / DCI P3) */ SPA_VIDEO_COLOR_PRIMARIES_SMPTEEG432, /**< SMPTE EG 432 primaries (ST 432-1 (2010) / P3 D65) */ SPA_VIDEO_COLOR_PRIMARIES_EBU3213, /**< EBU 3213 primaries (JEDEC P22 phosphors) */ }; /** * spa_video_colorimetry: * * Structure describing the color info. */ struct spa_video_colorimetry { enum spa_video_color_range range; /**< The color range. This is the valid range for the * samples. It is used to convert the samples to Y'PbPr * values. */ enum spa_video_color_matrix matrix; /**< the color matrix. Used to convert between Y'PbPr and * non-linear RGB (R'G'B') */ enum spa_video_transfer_function transfer; /**< The transfer function. Used to convert between * R'G'B' and RGB */ enum spa_video_color_primaries primaries; /**< Color primaries. Used to convert between R'G'B' * and CIE XYZ */ }; /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_VIDEO_COLOR_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/param/video/dsp-utils.h000066400000000000000000000041661511204443500300010ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_VIDEO_DSP_UTILS_H #define SPA_VIDEO_DSP_UTILS_H #include #include #include #include #ifdef __cplusplus extern "C" { #endif /** * \addtogroup spa_param * \{ */ #ifndef SPA_API_VIDEO_DSP_UTILS #ifdef SPA_API_IMPL #define SPA_API_VIDEO_DSP_UTILS SPA_API_IMPL #else #define SPA_API_VIDEO_DSP_UTILS static inline #endif #endif SPA_API_VIDEO_DSP_UTILS int spa_format_video_dsp_parse(const struct spa_pod *format, struct spa_video_info_dsp *info) { info->flags = SPA_VIDEO_FLAG_NONE; const struct spa_pod_prop *mod_prop; if ((mod_prop = spa_pod_find_prop (format, NULL, SPA_FORMAT_VIDEO_modifier)) != NULL) { info->flags |= SPA_VIDEO_FLAG_MODIFIER; if ((mod_prop->flags & SPA_POD_PROP_FLAG_DONT_FIXATE) == SPA_POD_PROP_FLAG_DONT_FIXATE) info->flags |= SPA_VIDEO_FLAG_MODIFIER_FIXATION_REQUIRED; } return spa_pod_parse_object(format, SPA_TYPE_OBJECT_Format, NULL, SPA_FORMAT_VIDEO_format, SPA_POD_OPT_Id(&info->format), SPA_FORMAT_VIDEO_modifier, SPA_POD_OPT_Long(&info->modifier)); } SPA_API_VIDEO_DSP_UTILS struct spa_pod * spa_format_video_dsp_build(struct spa_pod_builder *builder, uint32_t id, const struct spa_video_info_dsp *info) { struct spa_pod_frame f; spa_pod_builder_push_object(builder, &f, SPA_TYPE_OBJECT_Format, id); spa_pod_builder_add(builder, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_dsp), 0); if (info->format != SPA_VIDEO_FORMAT_UNKNOWN) spa_pod_builder_add(builder, SPA_FORMAT_VIDEO_format, SPA_POD_Id(info->format), 0); if (info->modifier != 0 || info->flags & SPA_VIDEO_FLAG_MODIFIER) { spa_pod_builder_prop(builder, SPA_FORMAT_VIDEO_modifier, SPA_POD_PROP_FLAG_MANDATORY); spa_pod_builder_long(builder, info->modifier); } return (struct spa_pod*)spa_pod_builder_pop(builder, &f); } /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_VIDEO_DSP_UTILS_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/param/video/dsp.h000066400000000000000000000010551511204443500266350ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_VIDEO_DSP_H #define SPA_VIDEO_DSP_H #ifdef __cplusplus extern "C" { #endif /** * \addtogroup spa_param * \{ */ #include struct spa_video_info_dsp { enum spa_video_format format; uint32_t flags; uint64_t modifier; }; #define SPA_VIDEO_INFO_DSP_INIT(...) ((struct spa_video_info_dsp) { __VA_ARGS__ }) /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_VIDEO_DSP_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/param/video/encoded.h000066400000000000000000000004271511204443500274520ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_VIDEO_ENCODED_H #define SPA_VIDEO_ENCODED_H #include #include #endif /* SPA_VIDEO_ENCODED_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/param/video/format-utils.h000066400000000000000000000041041511204443500304730ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_PARAM_VIDEO_FORMAT_UTILS_H #define SPA_PARAM_VIDEO_FORMAT_UTILS_H #include #include #include #include #include #include #include #ifdef __cplusplus extern "C" { #endif #ifndef SPA_API_VIDEO_FORMAT_UTILS #ifdef SPA_API_IMPL #define SPA_API_VIDEO_FORMAT_UTILS SPA_API_IMPL #else #define SPA_API_VIDEO_FORMAT_UTILS static inline #endif #endif SPA_API_VIDEO_FORMAT_UTILS int spa_format_video_parse(const struct spa_pod *format, struct spa_video_info *info) { int res; if ((res = spa_format_parse(format, &info->media_type, &info->media_subtype)) < 0) return res; if (info->media_type != SPA_MEDIA_TYPE_video) return -EINVAL; switch (info->media_subtype) { case SPA_MEDIA_SUBTYPE_raw: return spa_format_video_raw_parse(format, &info->info.raw); case SPA_MEDIA_SUBTYPE_dsp: return spa_format_video_dsp_parse(format, &info->info.dsp); case SPA_MEDIA_SUBTYPE_h264: return spa_format_video_h264_parse(format, &info->info.h264); case SPA_MEDIA_SUBTYPE_mjpg: return spa_format_video_mjpg_parse(format, &info->info.mjpg); } return -ENOTSUP; } SPA_API_VIDEO_FORMAT_UTILS struct spa_pod * spa_format_video_build(struct spa_pod_builder *builder, uint32_t id, const struct spa_video_info *info) { switch (info->media_subtype) { case SPA_MEDIA_SUBTYPE_raw: return spa_format_video_raw_build(builder, id, &info->info.raw); case SPA_MEDIA_SUBTYPE_dsp: return spa_format_video_dsp_build(builder, id, &info->info.dsp); case SPA_MEDIA_SUBTYPE_h264: return spa_format_video_h264_build(builder, id, &info->info.h264); case SPA_MEDIA_SUBTYPE_mjpg: return spa_format_video_mjpg_build(builder, id, &info->info.mjpg); } errno = ENOTSUP; return NULL; } #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_PARAM_VIDEO_FORMAT_UTILS_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/param/video/format.h000066400000000000000000000013311511204443500273340ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_PARAM_VIDEO_FORMAT_H #define SPA_PARAM_VIDEO_FORMAT_H #include #include #include #include #ifdef __cplusplus extern "C" { #endif /** * \addtogroup spa_param * \{ */ struct spa_video_info { uint32_t media_type; uint32_t media_subtype; union { struct spa_video_info_raw raw; struct spa_video_info_dsp dsp; struct spa_video_info_h264 h264; struct spa_video_info_mjpg mjpg; } info; }; /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_PARAM_VIDEO_FORMAT_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/param/video/h264-utils.h000066400000000000000000000045021511204443500276700ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2023 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_VIDEO_H264_UTILS_H #define SPA_VIDEO_H264_UTILS_H #include #include #include #ifdef __cplusplus extern "C" { #endif /** * \addtogroup spa_param * \{ */ #ifndef SPA_API_VIDEO_H264_UTILS #ifdef SPA_API_IMPL #define SPA_API_VIDEO_H264_UTILS SPA_API_IMPL #else #define SPA_API_VIDEO_H264_UTILS static inline #endif #endif SPA_API_VIDEO_H264_UTILS int spa_format_video_h264_parse(const struct spa_pod *format, struct spa_video_info_h264 *info) { return spa_pod_parse_object(format, SPA_TYPE_OBJECT_Format, NULL, SPA_FORMAT_VIDEO_size, SPA_POD_OPT_Rectangle(&info->size), SPA_FORMAT_VIDEO_framerate, SPA_POD_OPT_Fraction(&info->framerate), SPA_FORMAT_VIDEO_maxFramerate, SPA_POD_OPT_Fraction(&info->max_framerate), SPA_FORMAT_VIDEO_H264_streamFormat, SPA_POD_OPT_Id(&info->stream_format), SPA_FORMAT_VIDEO_H264_alignment, SPA_POD_OPT_Id(&info->alignment)); } SPA_API_VIDEO_H264_UTILS struct spa_pod * spa_format_video_h264_build(struct spa_pod_builder *builder, uint32_t id, const struct spa_video_info_h264 *info) { struct spa_pod_frame f; spa_pod_builder_push_object(builder, &f, SPA_TYPE_OBJECT_Format, id); spa_pod_builder_add(builder, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_h264), 0); if (info->size.width != 0 && info->size.height != 0) spa_pod_builder_add(builder, SPA_FORMAT_VIDEO_size, SPA_POD_Rectangle(&info->size), 0); if (info->framerate.denom != 0) spa_pod_builder_add(builder, SPA_FORMAT_VIDEO_framerate, SPA_POD_Fraction(&info->framerate), 0); if (info->max_framerate.denom != 0) spa_pod_builder_add(builder, SPA_FORMAT_VIDEO_maxFramerate, SPA_POD_Fraction(&info->max_framerate), 0); if (info->stream_format != 0) spa_pod_builder_add(builder, SPA_FORMAT_VIDEO_H264_streamFormat, SPA_POD_Id(info->stream_format), 0); if (info->alignment != 0) spa_pod_builder_add(builder, SPA_FORMAT_VIDEO_H264_alignment, SPA_POD_Id(info->alignment), 0); return (struct spa_pod*)spa_pod_builder_pop(builder, &f); } /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_VIDEO_H264_UTILS_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/param/video/h264.h000066400000000000000000000015321511204443500265320ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2023 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_VIDEO_H264_H #define SPA_VIDEO_H264_H #include #ifdef __cplusplus extern "C" { #endif /** * \addtogroup spa_param * \{ */ enum spa_h264_stream_format { SPA_H264_STREAM_FORMAT_UNKNOWN = 0, SPA_H264_STREAM_FORMAT_AVC, SPA_H264_STREAM_FORMAT_AVC3, SPA_H264_STREAM_FORMAT_BYTESTREAM }; enum spa_h264_alignment { SPA_H264_ALIGNMENT_UNKNOWN = 0, SPA_H264_ALIGNMENT_AU, SPA_H264_ALIGNMENT_NAL }; struct spa_video_info_h264 { struct spa_rectangle size; struct spa_fraction framerate; struct spa_fraction max_framerate; enum spa_h264_stream_format stream_format; enum spa_h264_alignment alignment; }; /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_VIDEO_H264_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/param/video/h265-utils.h000066400000000000000000000046001511204443500276700ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2025 Wim Taymans */ /* SPDX-FileCopyrightText: Copyright © 2025 Arun Raghavan */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_VIDEO_H265_UTILS_H #define SPA_VIDEO_H265_UTILS_H #ifdef __cplusplus extern "C" { #endif /** * \addtogroup spa_param * \{ */ #include #include #include #ifndef SPA_API_VIDEO_H265_UTILS #ifdef SPA_API_IMPL #define SPA_API_VIDEO_H265_UTILS SPA_API_IMPL #else #define SPA_API_VIDEO_H265_UTILS static inline #endif #endif SPA_API_VIDEO_H265_UTILS int spa_format_video_h265_parse(const struct spa_pod *format, struct spa_video_info_h265 *info) { return spa_pod_parse_object(format, SPA_TYPE_OBJECT_Format, NULL, SPA_FORMAT_VIDEO_size, SPA_POD_OPT_Rectangle(&info->size), SPA_FORMAT_VIDEO_framerate, SPA_POD_OPT_Fraction(&info->framerate), SPA_FORMAT_VIDEO_maxFramerate, SPA_POD_OPT_Fraction(&info->max_framerate), SPA_FORMAT_VIDEO_H265_streamFormat, SPA_POD_OPT_Id(&info->stream_format), SPA_FORMAT_VIDEO_H265_alignment, SPA_POD_OPT_Id(&info->alignment)); } SPA_API_VIDEO_H265_UTILS struct spa_pod * spa_format_video_h265_build(struct spa_pod_builder *builder, uint32_t id, const struct spa_video_info_h265 *info) { struct spa_pod_frame f; spa_pod_builder_push_object(builder, &f, SPA_TYPE_OBJECT_Format, id); spa_pod_builder_add(builder, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_h265), 0); if (info->size.width != 0 && info->size.height != 0) spa_pod_builder_add(builder, SPA_FORMAT_VIDEO_size, SPA_POD_Rectangle(&info->size), 0); if (info->framerate.denom != 0) spa_pod_builder_add(builder, SPA_FORMAT_VIDEO_framerate, SPA_POD_Fraction(&info->framerate), 0); if (info->max_framerate.denom != 0) spa_pod_builder_add(builder, SPA_FORMAT_VIDEO_maxFramerate, SPA_POD_Fraction(&info->max_framerate), 0); if (info->stream_format != 0) spa_pod_builder_add(builder, SPA_FORMAT_VIDEO_H265_streamFormat, SPA_POD_Id(info->stream_format), 0); if (info->alignment != 0) spa_pod_builder_add(builder, SPA_FORMAT_VIDEO_H265_alignment, SPA_POD_Id(info->alignment), 0); return (struct spa_pod*)spa_pod_builder_pop(builder, &f); } /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_VIDEO_H265_UTILS_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/param/video/h265.h000066400000000000000000000016311511204443500265330ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2025 Wim Taymans */ /* SPDX-FileCopyrightText: Copyright © 2025 Arun Raghavan */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_VIDEO_H265_H #define SPA_VIDEO_H265_H #ifdef __cplusplus extern "C" { #endif /** * \addtogroup spa_param * \{ */ #include enum spa_h265_stream_format { SPA_H265_STREAM_FORMAT_UNKNOWN = 0, SPA_H265_STREAM_FORMAT_HVC1, SPA_H265_STREAM_FORMAT_HEV1, SPA_H265_STREAM_FORMAT_BYTESTREAM }; enum spa_h265_alignment { SPA_H265_ALIGNMENT_UNKNOWN = 0, SPA_H265_ALIGNMENT_AU, SPA_H265_ALIGNMENT_NAL }; struct spa_video_info_h265 { struct spa_rectangle size; struct spa_fraction framerate; struct spa_fraction max_framerate; enum spa_h265_stream_format stream_format; enum spa_h265_alignment alignment; }; /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_VIDEO_H265_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/param/video/mjpg-utils.h000066400000000000000000000036431511204443500301470ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_VIDEO_MJPG_UTILS_H #define SPA_VIDEO_MJPG_UTILS_H #include #include #include #ifdef __cplusplus extern "C" { #endif /** * \addtogroup spa_param * \{ */ #ifndef SPA_API_VIDEO_MJPG_UTILS #ifdef SPA_API_IMPL #define SPA_API_VIDEO_MJPG_UTILS SPA_API_IMPL #else #define SPA_API_VIDEO_MJPG_UTILS static inline #endif #endif SPA_API_VIDEO_MJPG_UTILS int spa_format_video_mjpg_parse(const struct spa_pod *format, struct spa_video_info_mjpg *info) { return spa_pod_parse_object(format, SPA_TYPE_OBJECT_Format, NULL, SPA_FORMAT_VIDEO_size, SPA_POD_OPT_Rectangle(&info->size), SPA_FORMAT_VIDEO_framerate, SPA_POD_OPT_Fraction(&info->framerate), SPA_FORMAT_VIDEO_maxFramerate, SPA_POD_OPT_Fraction(&info->max_framerate)); } SPA_API_VIDEO_MJPG_UTILS struct spa_pod * spa_format_video_mjpg_build(struct spa_pod_builder *builder, uint32_t id, const struct spa_video_info_mjpg *info) { struct spa_pod_frame f; spa_pod_builder_push_object(builder, &f, SPA_TYPE_OBJECT_Format, id); spa_pod_builder_add(builder, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_mjpg), 0); if (info->size.width != 0 && info->size.height != 0) spa_pod_builder_add(builder, SPA_FORMAT_VIDEO_size, SPA_POD_Rectangle(&info->size), 0); if (info->framerate.denom != 0) spa_pod_builder_add(builder, SPA_FORMAT_VIDEO_framerate, SPA_POD_Fraction(&info->framerate), 0); if (info->max_framerate.denom != 0) spa_pod_builder_add(builder, SPA_FORMAT_VIDEO_maxFramerate, SPA_POD_Fraction(&info->max_framerate), 0); return (struct spa_pod*)spa_pod_builder_pop(builder, &f); } /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_VIDEO_MJPG_UTILS_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/param/video/mjpg.h000066400000000000000000000007661511204443500270140ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_VIDEO_MJPG_H #define SPA_VIDEO_MJPG_H #include #ifdef __cplusplus extern "C" { #endif /** * \addtogroup spa_param * \{ */ struct spa_video_info_mjpg { struct spa_rectangle size; struct spa_fraction framerate; struct spa_fraction max_framerate; }; /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_VIDEO_MJPG_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/param/video/multiview.h000066400000000000000000000112341511204443500300740ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_VIDEO_MULTIVIEW_H #define SPA_VIDEO_MULTIVIEW_H #ifdef __cplusplus extern "C" { #endif /** * \addtogroup spa_param * \{ */ /** * All possible stereoscopic 3D and multiview representations. * In conjunction with \ref spa_video_multiview_flags, describes how * multiview content is being transported in the stream. */ enum spa_video_multiview_mode { /** A special value indicating no multiview information. Used in spa_video_info and other * places to indicate that no specific multiview handling has been requested or provided. * This value is never carried on caps. */ SPA_VIDEO_MULTIVIEW_MODE_NONE = -1, SPA_VIDEO_MULTIVIEW_MODE_MONO = 0, /**< All frames are monoscopic */ /* Single view modes */ SPA_VIDEO_MULTIVIEW_MODE_LEFT, /**< All frames represent a left-eye view */ SPA_VIDEO_MULTIVIEW_MODE_RIGHT, /**< All frames represent a right-eye view */ /* Stereo view modes */ SPA_VIDEO_MULTIVIEW_MODE_SIDE_BY_SIDE, /**< Left and right eye views are provided * in the left and right half of the frame * respectively. */ SPA_VIDEO_MULTIVIEW_MODE_SIDE_BY_SIDE_QUINCUNX, /**< Left and right eye views are provided * in the left and right half of the * frame, but have been sampled using * quincunx method, with half-pixel offset * between the 2 views. */ SPA_VIDEO_MULTIVIEW_MODE_COLUMN_INTERLEAVED, /**< Alternating vertical columns of pixels * represent the left and right eye view * respectively. */ SPA_VIDEO_MULTIVIEW_MODE_ROW_INTERLEAVED, /**< Alternating horizontal rows of pixels * represent the left and right eye view * respectively. */ SPA_VIDEO_MULTIVIEW_MODE_TOP_BOTTOM, /**< The top half of the frame contains the * left eye, and the bottom half the right * eye. */ SPA_VIDEO_MULTIVIEW_MODE_CHECKERBOARD, /**< Pixels are arranged with alternating * pixels representing left and right eye * views in a checkerboard fashion. */ /* Padding for new frame packing modes */ SPA_VIDEO_MULTIVIEW_MODE_FRAME_BY_FRAME = 32, /**< Left and right eye views are provided * in separate frames alternately. */ /* Multiview mode(s) */ SPA_VIDEO_MULTIVIEW_MODE_MULTIVIEW_FRAME_BY_FRAME, /**< Multipleindependent views are * provided in separate frames in * sequence. This method only applies to * raw video buffers at the moment. * Specific view identification is via * metadata on raw video buffers. */ SPA_VIDEO_MULTIVIEW_MODE_SEPARATED, /**< Multiple views are provided as separate * \ref spa_data framebuffers attached * to each \ref spa_buffer, described * by the metadata */ /* future expansion for annotated modes */ }; /** * spa_video_multiview_flags are used to indicate extra properties of a * stereo/multiview stream beyond the frame layout and buffer mapping * that is conveyed in the \ref spa_video_multiview_mode. */ enum spa_video_multiview_flags { SPA_VIDEO_MULTIVIEW_FLAGS_NONE = 0, /**< No flags */ SPA_VIDEO_MULTIVIEW_FLAGS_RIGHT_VIEW_FIRST = (1 << 0), /**< For stereo streams, the normal arrangement * of left and right views is reversed */ SPA_VIDEO_MULTIVIEW_FLAGS_LEFT_FLIPPED = (1 << 1), /**< The left view is vertically mirrored */ SPA_VIDEO_MULTIVIEW_FLAGS_LEFT_FLOPPED = (1 << 2), /**< The left view is horizontally mirrored */ SPA_VIDEO_MULTIVIEW_FLAGS_RIGHT_FLIPPED = (1 << 3), /**< The right view is vertically mirrored */ SPA_VIDEO_MULTIVIEW_FLAGS_RIGHT_FLOPPED = (1 << 4), /**< The right view is horizontally mirrored */ SPA_VIDEO_MULTIVIEW_FLAGS_HALF_ASPECT = (1 << 14), /**< For frame-packed multiview * modes, indicates that the individual * views have been encoded with half the true * width or height and should be scaled back * up for display. This flag is used for * overriding input layout interpretation * by adjusting pixel-aspect-ratio. * For side-by-side, column interleaved or * checkerboard packings, the * pixel width will be doubled. * For row interleaved and * top-bottom encodings, pixel height will * be doubled */ SPA_VIDEO_MULTIVIEW_FLAGS_MIXED_MONO = (1 << 15), /**< The video stream contains both * mono and multiview portions, * signalled on each buffer by the * absence or presence of a buffer flag. */ }; /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_VIDEO_MULTIVIEW_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/param/video/raw-types.h000066400000000000000000000247211511204443500300070ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_VIDEO_RAW_TYPES_H #define SPA_VIDEO_RAW_TYPES_H #include #include #ifdef __cplusplus extern "C" { #endif /** * \addtogroup spa_param * \{ */ #ifndef SPA_API_VIDEO_RAW_TYPES #ifdef SPA_API_IMPL #define SPA_API_VIDEO_RAW_TYPES SPA_API_IMPL #else #define SPA_API_VIDEO_RAW_TYPES static inline #endif #endif #define SPA_TYPE_INFO_VideoFormat SPA_TYPE_INFO_ENUM_BASE "VideoFormat" #define SPA_TYPE_INFO_VIDEO_FORMAT_BASE SPA_TYPE_INFO_VideoFormat ":" static const struct spa_type_info spa_type_video_format[] = { { SPA_VIDEO_FORMAT_UNKNOWN, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "UNKNOWN", NULL }, { SPA_VIDEO_FORMAT_ENCODED, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "ENCODED", NULL }, { SPA_VIDEO_FORMAT_I420, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "I420", NULL }, { SPA_VIDEO_FORMAT_YV12, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "YV12", NULL }, { SPA_VIDEO_FORMAT_YUY2, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "YUY2", NULL }, { SPA_VIDEO_FORMAT_UYVY, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "UYVY", NULL }, { SPA_VIDEO_FORMAT_AYUV, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "AYUV", NULL }, { SPA_VIDEO_FORMAT_RGBx, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "RGBx", NULL }, { SPA_VIDEO_FORMAT_BGRx, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "BGRx", NULL }, { SPA_VIDEO_FORMAT_xRGB, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "xRGB", NULL }, { SPA_VIDEO_FORMAT_xBGR, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "xBGR", NULL }, { SPA_VIDEO_FORMAT_RGBA, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "RGBA", NULL }, { SPA_VIDEO_FORMAT_BGRA, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "BGRA", NULL }, { SPA_VIDEO_FORMAT_ARGB, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "ARGB", NULL }, { SPA_VIDEO_FORMAT_ABGR, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "ABGR", NULL }, { SPA_VIDEO_FORMAT_RGB, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "RGB", NULL }, { SPA_VIDEO_FORMAT_BGR, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "BGR", NULL }, { SPA_VIDEO_FORMAT_Y41B, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "Y41B", NULL }, { SPA_VIDEO_FORMAT_Y42B, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "Y42B", NULL }, { SPA_VIDEO_FORMAT_YVYU, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "YVYU", NULL }, { SPA_VIDEO_FORMAT_Y444, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "Y444", NULL }, { SPA_VIDEO_FORMAT_v210, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "v210", NULL }, { SPA_VIDEO_FORMAT_v216, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "v216", NULL }, { SPA_VIDEO_FORMAT_NV12, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "NV12", NULL }, { SPA_VIDEO_FORMAT_NV21, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "NV21", NULL }, { SPA_VIDEO_FORMAT_GRAY8, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "GRAY8", NULL }, { SPA_VIDEO_FORMAT_GRAY16_BE, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "GRAY16_BE", NULL }, { SPA_VIDEO_FORMAT_GRAY16_LE, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "GRAY16_LE", NULL }, { SPA_VIDEO_FORMAT_v308, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "v308", NULL }, { SPA_VIDEO_FORMAT_RGB16, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "RGB16", NULL }, { SPA_VIDEO_FORMAT_BGR16, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "BGR16", NULL }, { SPA_VIDEO_FORMAT_RGB15, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "RGB15", NULL }, { SPA_VIDEO_FORMAT_BGR15, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "BGR15", NULL }, { SPA_VIDEO_FORMAT_UYVP, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "UYVP", NULL }, { SPA_VIDEO_FORMAT_A420, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "A420", NULL }, { SPA_VIDEO_FORMAT_RGB8P, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "RGB8P", NULL }, { SPA_VIDEO_FORMAT_YUV9, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "YUV9", NULL }, { SPA_VIDEO_FORMAT_YVU9, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "YVU9", NULL }, { SPA_VIDEO_FORMAT_IYU1, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "IYU1", NULL }, { SPA_VIDEO_FORMAT_ARGB64, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "ARGB64", NULL }, { SPA_VIDEO_FORMAT_AYUV64, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "AYUV64", NULL }, { SPA_VIDEO_FORMAT_r210, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "r210", NULL }, { SPA_VIDEO_FORMAT_I420_10BE, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "I420_10BE", NULL }, { SPA_VIDEO_FORMAT_I420_10LE, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "I420_10LE", NULL }, { SPA_VIDEO_FORMAT_I422_10BE, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "I422_10BE", NULL }, { SPA_VIDEO_FORMAT_I422_10LE, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "I422_10LE", NULL }, { SPA_VIDEO_FORMAT_Y444_10BE, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "Y444_10BE", NULL }, { SPA_VIDEO_FORMAT_Y444_10LE, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "Y444_10LE", NULL }, { SPA_VIDEO_FORMAT_GBR, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "GBR", NULL }, { SPA_VIDEO_FORMAT_GBR_10BE, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "GBR_10BE", NULL }, { SPA_VIDEO_FORMAT_GBR_10LE, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "GBR_10LE", NULL }, { SPA_VIDEO_FORMAT_NV16, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "NV16", NULL }, { SPA_VIDEO_FORMAT_NV24, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "NV24", NULL }, { SPA_VIDEO_FORMAT_NV12_64Z32, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "NV12_64Z32", NULL }, { SPA_VIDEO_FORMAT_A420_10BE, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "A420_10BE", NULL }, { SPA_VIDEO_FORMAT_A420_10LE, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "A420_10LE", NULL }, { SPA_VIDEO_FORMAT_A422_10BE, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "A422_10BE", NULL }, { SPA_VIDEO_FORMAT_A422_10LE, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "A422_10LE", NULL }, { SPA_VIDEO_FORMAT_A444_10BE, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "A444_10BE", NULL }, { SPA_VIDEO_FORMAT_A444_10LE, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "A444_10LE", NULL }, { SPA_VIDEO_FORMAT_NV61, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "NV61", NULL }, { SPA_VIDEO_FORMAT_P010_10BE, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "P010_10BE", NULL }, { SPA_VIDEO_FORMAT_P010_10LE, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "P010_10LE", NULL }, { SPA_VIDEO_FORMAT_IYU2, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "IYU2", NULL }, { SPA_VIDEO_FORMAT_VYUY, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "VYUY", NULL }, { SPA_VIDEO_FORMAT_GBRA, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "GBRA", NULL }, { SPA_VIDEO_FORMAT_GBRA_10BE, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "GBRA_10BE", NULL }, { SPA_VIDEO_FORMAT_GBRA_10LE, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "GBRA_10LE", NULL }, { SPA_VIDEO_FORMAT_GBR_12BE, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "GBR_12BE", NULL }, { SPA_VIDEO_FORMAT_GBR_12LE, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "GBR_12LE", NULL }, { SPA_VIDEO_FORMAT_GBRA_12BE, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "GBRA_12BE", NULL }, { SPA_VIDEO_FORMAT_GBRA_12LE, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "GBRA_12LE", NULL }, { SPA_VIDEO_FORMAT_I420_12BE, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "I420_12BE", NULL }, { SPA_VIDEO_FORMAT_I420_12LE, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "I420_12LE", NULL }, { SPA_VIDEO_FORMAT_I422_12BE, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "I422_12BE", NULL }, { SPA_VIDEO_FORMAT_I422_12LE, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "I422_12LE", NULL }, { SPA_VIDEO_FORMAT_Y444_12BE, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "Y444_12BE", NULL }, { SPA_VIDEO_FORMAT_Y444_12LE, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "Y444_12LE", NULL }, { SPA_VIDEO_FORMAT_RGBA_F16, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "RGBA_F16", NULL }, { SPA_VIDEO_FORMAT_RGBA_F32, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "RGBA_F32", NULL }, { SPA_VIDEO_FORMAT_xRGB_210LE, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "xRGB_210LE", NULL }, { SPA_VIDEO_FORMAT_xBGR_210LE, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "xBGR_210LE", NULL }, { SPA_VIDEO_FORMAT_RGBx_102LE, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "RGBx_102LE", NULL }, { SPA_VIDEO_FORMAT_BGRx_102LE, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "BGRx_102LE", NULL }, { SPA_VIDEO_FORMAT_ARGB_210LE, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "ARGB_210LE", NULL }, { SPA_VIDEO_FORMAT_ABGR_210LE, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "ABGR_210LE", NULL }, { SPA_VIDEO_FORMAT_RGBA_102LE, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "RGBA_102LE", NULL }, { SPA_VIDEO_FORMAT_BGRA_102LE, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "BGRA_102LE", NULL }, { 0, 0, NULL, NULL }, }; SPA_API_VIDEO_RAW_TYPES uint32_t spa_type_video_format_from_short_name(const char *name) { return spa_type_from_short_name(name, spa_type_video_format, SPA_VIDEO_FORMAT_UNKNOWN); } SPA_API_VIDEO_RAW_TYPES const char * spa_type_video_format_to_short_name(uint32_t type) { return spa_type_to_short_name(type, spa_type_video_format, "UNKNOWN"); } #define SPA_TYPE_INFO_VideoFlags SPA_TYPE_INFO_FLAGS_BASE "VideoFlags" #define SPA_TYPE_INFO_VIDEO_FLAGS_BASE SPA_TYPE_INFO_VideoFlags ":" static const struct spa_type_info spa_type_video_flags[] = { { SPA_VIDEO_FLAG_NONE, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FLAGS_BASE "none", NULL }, { SPA_VIDEO_FLAG_VARIABLE_FPS, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FLAGS_BASE "variable-fps", NULL }, { SPA_VIDEO_FLAG_PREMULTIPLIED_ALPHA, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FLAGS_BASE "premultiplied-alpha", NULL }, { SPA_VIDEO_FLAG_MODIFIER, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FLAGS_BASE "modifier", NULL }, { 0, 0, NULL, NULL }, }; #define SPA_TYPE_INFO_VideoInterlaceMode SPA_TYPE_INFO_ENUM_BASE "VideoInterlaceMode" #define SPA_TYPE_INFO_VIDEO_INTERLACE_MODE_BASE SPA_TYPE_INFO_VideoInterlaceMode ":" static const struct spa_type_info spa_type_video_interlace_mode[] = { { SPA_VIDEO_INTERLACE_MODE_PROGRESSIVE, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_INTERLACE_MODE_BASE "progressive", NULL }, { SPA_VIDEO_INTERLACE_MODE_INTERLEAVED, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_INTERLACE_MODE_BASE "interleaved", NULL }, { SPA_VIDEO_INTERLACE_MODE_MIXED, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_INTERLACE_MODE_BASE "mixed", NULL }, { SPA_VIDEO_INTERLACE_MODE_FIELDS, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_INTERLACE_MODE_BASE "fields", NULL }, { 0, 0, NULL, NULL }, }; /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_VIDEO_RAW_TYPES_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/param/video/raw-utils.h000066400000000000000000000142231511204443500277770ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_VIDEO_RAW_UTILS_H #define SPA_VIDEO_RAW_UTILS_H #include #include #include #include #ifdef __cplusplus extern "C" { #endif /** * \addtogroup spa_param * \{ */ #ifndef SPA_API_VIDEO_RAW_UTILS #ifdef SPA_API_IMPL #define SPA_API_VIDEO_RAW_UTILS SPA_API_IMPL #else #define SPA_API_VIDEO_RAW_UTILS static inline #endif #endif SPA_API_VIDEO_RAW_UTILS int spa_format_video_raw_parse(const struct spa_pod *format, struct spa_video_info_raw *info) { info->flags = SPA_VIDEO_FLAG_NONE; const struct spa_pod_prop *mod_prop; if ((mod_prop = spa_pod_find_prop (format, NULL, SPA_FORMAT_VIDEO_modifier)) != NULL) { info->flags |= SPA_VIDEO_FLAG_MODIFIER; if ((mod_prop->flags & SPA_POD_PROP_FLAG_DONT_FIXATE) == SPA_POD_PROP_FLAG_DONT_FIXATE) info->flags |= SPA_VIDEO_FLAG_MODIFIER_FIXATION_REQUIRED; } return spa_pod_parse_object(format, SPA_TYPE_OBJECT_Format, NULL, SPA_FORMAT_VIDEO_format, SPA_POD_OPT_Id(&info->format), SPA_FORMAT_VIDEO_modifier, SPA_POD_OPT_Long(&info->modifier), SPA_FORMAT_VIDEO_size, SPA_POD_OPT_Rectangle(&info->size), SPA_FORMAT_VIDEO_framerate, SPA_POD_OPT_Fraction(&info->framerate), SPA_FORMAT_VIDEO_maxFramerate, SPA_POD_OPT_Fraction(&info->max_framerate), SPA_FORMAT_VIDEO_views, SPA_POD_OPT_Int(&info->views), SPA_FORMAT_VIDEO_interlaceMode, SPA_POD_OPT_Id(&info->interlace_mode), SPA_FORMAT_VIDEO_pixelAspectRatio, SPA_POD_OPT_Fraction(&info->pixel_aspect_ratio), SPA_FORMAT_VIDEO_multiviewMode, SPA_POD_OPT_Id(&info->multiview_mode), SPA_FORMAT_VIDEO_multiviewFlags, SPA_POD_OPT_Id(&info->multiview_flags), SPA_FORMAT_VIDEO_chromaSite, SPA_POD_OPT_Id(&info->chroma_site), SPA_FORMAT_VIDEO_colorRange, SPA_POD_OPT_Id(&info->color_range), SPA_FORMAT_VIDEO_colorMatrix, SPA_POD_OPT_Id(&info->color_matrix), SPA_FORMAT_VIDEO_transferFunction, SPA_POD_OPT_Id(&info->transfer_function), SPA_FORMAT_VIDEO_colorPrimaries, SPA_POD_OPT_Id(&info->color_primaries)); } SPA_API_VIDEO_RAW_UTILS struct spa_pod * spa_format_video_raw_build(struct spa_pod_builder *builder, uint32_t id, const struct spa_video_info_raw *info) { struct spa_pod_frame f; spa_pod_builder_push_object(builder, &f, SPA_TYPE_OBJECT_Format, id); spa_pod_builder_add(builder, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), 0); if (info->format != SPA_VIDEO_FORMAT_UNKNOWN) spa_pod_builder_add(builder, SPA_FORMAT_VIDEO_format, SPA_POD_Id(info->format), 0); if (info->size.width != 0 && info->size.height != 0) spa_pod_builder_add(builder, SPA_FORMAT_VIDEO_size, SPA_POD_Rectangle(&info->size), 0); if (info->framerate.denom != 0) spa_pod_builder_add(builder, SPA_FORMAT_VIDEO_framerate, SPA_POD_Fraction(&info->framerate), 0); if (info->modifier != 0 || info->flags & SPA_VIDEO_FLAG_MODIFIER) { spa_pod_builder_prop(builder, SPA_FORMAT_VIDEO_modifier, SPA_POD_PROP_FLAG_MANDATORY); spa_pod_builder_long(builder, info->modifier); } if (info->max_framerate.denom != 0) spa_pod_builder_add(builder, SPA_FORMAT_VIDEO_maxFramerate, SPA_POD_Fraction(&info->max_framerate), 0); if (info->views != 0) spa_pod_builder_add(builder, SPA_FORMAT_VIDEO_views, SPA_POD_Int(info->views), 0); if (info->interlace_mode != 0) spa_pod_builder_add(builder, SPA_FORMAT_VIDEO_interlaceMode, SPA_POD_Id(info->interlace_mode), 0); if (info->pixel_aspect_ratio.denom != 0) spa_pod_builder_add(builder, SPA_FORMAT_VIDEO_pixelAspectRatio, SPA_POD_Fraction(&info->pixel_aspect_ratio), 0); if (info->multiview_mode != 0) spa_pod_builder_add(builder, SPA_FORMAT_VIDEO_multiviewMode, SPA_POD_Id(info->multiview_mode), 0); if (info->multiview_flags != 0) spa_pod_builder_add(builder, SPA_FORMAT_VIDEO_multiviewFlags,SPA_POD_Id(info->multiview_flags), 0); if (info->chroma_site != 0) spa_pod_builder_add(builder, SPA_FORMAT_VIDEO_chromaSite, SPA_POD_Id(info->chroma_site), 0); if (info->color_range != 0) spa_pod_builder_add(builder, SPA_FORMAT_VIDEO_colorRange, SPA_POD_Id(info->color_range), 0); if (info->color_matrix != 0) spa_pod_builder_add(builder, SPA_FORMAT_VIDEO_colorMatrix, SPA_POD_Id(info->color_matrix), 0); if (info->transfer_function != 0) spa_pod_builder_add(builder, SPA_FORMAT_VIDEO_transferFunction,SPA_POD_Id(info->transfer_function), 0); if (info->color_primaries != 0) spa_pod_builder_add(builder, SPA_FORMAT_VIDEO_colorPrimaries,SPA_POD_Id(info->color_primaries), 0); return (struct spa_pod*)spa_pod_builder_pop(builder, &f); } static inline bool spa_format_video_is_rgb(enum spa_video_format format) { switch (format) { case SPA_VIDEO_FORMAT_RGBx: case SPA_VIDEO_FORMAT_BGRx: case SPA_VIDEO_FORMAT_xRGB: case SPA_VIDEO_FORMAT_xBGR: case SPA_VIDEO_FORMAT_RGBA: case SPA_VIDEO_FORMAT_BGRA: case SPA_VIDEO_FORMAT_ARGB: case SPA_VIDEO_FORMAT_ABGR: case SPA_VIDEO_FORMAT_RGB: case SPA_VIDEO_FORMAT_BGR: case SPA_VIDEO_FORMAT_GRAY8: case SPA_VIDEO_FORMAT_GRAY16_BE: case SPA_VIDEO_FORMAT_GRAY16_LE: case SPA_VIDEO_FORMAT_RGB16: case SPA_VIDEO_FORMAT_BGR16: case SPA_VIDEO_FORMAT_RGB15: case SPA_VIDEO_FORMAT_BGR15: case SPA_VIDEO_FORMAT_RGB8P: case SPA_VIDEO_FORMAT_ARGB64: case SPA_VIDEO_FORMAT_r210: case SPA_VIDEO_FORMAT_GBR: case SPA_VIDEO_FORMAT_GBR_10BE: case SPA_VIDEO_FORMAT_GBR_10LE: case SPA_VIDEO_FORMAT_GBRA: case SPA_VIDEO_FORMAT_GBRA_10BE: case SPA_VIDEO_FORMAT_GBRA_10LE: case SPA_VIDEO_FORMAT_GBR_12BE: case SPA_VIDEO_FORMAT_GBR_12LE: case SPA_VIDEO_FORMAT_GBRA_12BE: case SPA_VIDEO_FORMAT_GBRA_12LE: case SPA_VIDEO_FORMAT_RGBA_F16: case SPA_VIDEO_FORMAT_RGBA_F32: case SPA_VIDEO_FORMAT_xRGB_210LE: case SPA_VIDEO_FORMAT_xBGR_210LE: case SPA_VIDEO_FORMAT_RGBx_102LE: case SPA_VIDEO_FORMAT_BGRx_102LE: case SPA_VIDEO_FORMAT_ARGB_210LE: case SPA_VIDEO_FORMAT_ABGR_210LE: case SPA_VIDEO_FORMAT_RGBA_102LE: case SPA_VIDEO_FORMAT_BGRA_102LE: return true; default: return false; } } /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_VIDEO_RAW_UTILS_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/param/video/raw.h000066400000000000000000000154121511204443500266420ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_VIDEO_RAW_H #define SPA_VIDEO_RAW_H #include #include #include #include #ifdef __cplusplus extern "C" { #endif /** * \addtogroup spa_param * \{ */ #define SPA_VIDEO_MAX_PLANES 4 #define SPA_VIDEO_MAX_COMPONENTS 4 /** * Video formats * * The components are in general described in big-endian order. There are some * exceptions (e.g. RGB15 and RGB16) which use the host endianness. * * Most of the formats are identical to their GStreamer equivalent. See the * GStreamer video formats documentation for more details: * * https://gstreamer.freedesktop.org/documentation/additional/design/mediatype-video-raw.html#formats */ enum spa_video_format { SPA_VIDEO_FORMAT_UNKNOWN, SPA_VIDEO_FORMAT_ENCODED, SPA_VIDEO_FORMAT_I420, SPA_VIDEO_FORMAT_YV12, SPA_VIDEO_FORMAT_YUY2, SPA_VIDEO_FORMAT_UYVY, SPA_VIDEO_FORMAT_AYUV, SPA_VIDEO_FORMAT_RGBx, SPA_VIDEO_FORMAT_BGRx, SPA_VIDEO_FORMAT_xRGB, SPA_VIDEO_FORMAT_xBGR, SPA_VIDEO_FORMAT_RGBA, SPA_VIDEO_FORMAT_BGRA, SPA_VIDEO_FORMAT_ARGB, SPA_VIDEO_FORMAT_ABGR, SPA_VIDEO_FORMAT_RGB, SPA_VIDEO_FORMAT_BGR, SPA_VIDEO_FORMAT_Y41B, SPA_VIDEO_FORMAT_Y42B, SPA_VIDEO_FORMAT_YVYU, SPA_VIDEO_FORMAT_Y444, SPA_VIDEO_FORMAT_v210, SPA_VIDEO_FORMAT_v216, SPA_VIDEO_FORMAT_NV12, SPA_VIDEO_FORMAT_NV21, SPA_VIDEO_FORMAT_GRAY8, SPA_VIDEO_FORMAT_GRAY16_BE, SPA_VIDEO_FORMAT_GRAY16_LE, SPA_VIDEO_FORMAT_v308, SPA_VIDEO_FORMAT_RGB16, SPA_VIDEO_FORMAT_BGR16, SPA_VIDEO_FORMAT_RGB15, SPA_VIDEO_FORMAT_BGR15, SPA_VIDEO_FORMAT_UYVP, SPA_VIDEO_FORMAT_A420, SPA_VIDEO_FORMAT_RGB8P, SPA_VIDEO_FORMAT_YUV9, SPA_VIDEO_FORMAT_YVU9, SPA_VIDEO_FORMAT_IYU1, SPA_VIDEO_FORMAT_ARGB64, SPA_VIDEO_FORMAT_AYUV64, SPA_VIDEO_FORMAT_r210, SPA_VIDEO_FORMAT_I420_10BE, SPA_VIDEO_FORMAT_I420_10LE, SPA_VIDEO_FORMAT_I422_10BE, SPA_VIDEO_FORMAT_I422_10LE, SPA_VIDEO_FORMAT_Y444_10BE, SPA_VIDEO_FORMAT_Y444_10LE, SPA_VIDEO_FORMAT_GBR, SPA_VIDEO_FORMAT_GBR_10BE, SPA_VIDEO_FORMAT_GBR_10LE, SPA_VIDEO_FORMAT_NV16, SPA_VIDEO_FORMAT_NV24, SPA_VIDEO_FORMAT_NV12_64Z32, SPA_VIDEO_FORMAT_A420_10BE, SPA_VIDEO_FORMAT_A420_10LE, SPA_VIDEO_FORMAT_A422_10BE, SPA_VIDEO_FORMAT_A422_10LE, SPA_VIDEO_FORMAT_A444_10BE, SPA_VIDEO_FORMAT_A444_10LE, SPA_VIDEO_FORMAT_NV61, SPA_VIDEO_FORMAT_P010_10BE, SPA_VIDEO_FORMAT_P010_10LE, SPA_VIDEO_FORMAT_IYU2, SPA_VIDEO_FORMAT_VYUY, SPA_VIDEO_FORMAT_GBRA, SPA_VIDEO_FORMAT_GBRA_10BE, SPA_VIDEO_FORMAT_GBRA_10LE, SPA_VIDEO_FORMAT_GBR_12BE, SPA_VIDEO_FORMAT_GBR_12LE, SPA_VIDEO_FORMAT_GBRA_12BE, SPA_VIDEO_FORMAT_GBRA_12LE, SPA_VIDEO_FORMAT_I420_12BE, SPA_VIDEO_FORMAT_I420_12LE, SPA_VIDEO_FORMAT_I422_12BE, SPA_VIDEO_FORMAT_I422_12LE, SPA_VIDEO_FORMAT_Y444_12BE, SPA_VIDEO_FORMAT_Y444_12LE, SPA_VIDEO_FORMAT_RGBA_F16, SPA_VIDEO_FORMAT_RGBA_F32, SPA_VIDEO_FORMAT_xRGB_210LE, /**< 32-bit x:R:G:B 2:10:10:10 little endian */ SPA_VIDEO_FORMAT_xBGR_210LE, /**< 32-bit x:B:G:R 2:10:10:10 little endian */ SPA_VIDEO_FORMAT_RGBx_102LE, /**< 32-bit R:G:B:x 10:10:10:2 little endian */ SPA_VIDEO_FORMAT_BGRx_102LE, /**< 32-bit B:G:R:x 10:10:10:2 little endian */ SPA_VIDEO_FORMAT_ARGB_210LE, /**< 32-bit A:R:G:B 2:10:10:10 little endian */ SPA_VIDEO_FORMAT_ABGR_210LE, /**< 32-bit A:B:G:R 2:10:10:10 little endian */ SPA_VIDEO_FORMAT_RGBA_102LE, /**< 32-bit R:G:B:A 10:10:10:2 little endian */ SPA_VIDEO_FORMAT_BGRA_102LE, /**< 32-bit B:G:R:A 10:10:10:2 little endian */ /* Aliases */ SPA_VIDEO_FORMAT_DSP_F32 = SPA_VIDEO_FORMAT_RGBA_F32, }; /** * Extra video flags */ enum spa_video_flags { SPA_VIDEO_FLAG_NONE = 0, /**< no flags */ SPA_VIDEO_FLAG_VARIABLE_FPS = (1 << 0), /**< a variable fps is selected, fps_n and fps_d * denote the maximum fps of the video */ SPA_VIDEO_FLAG_PREMULTIPLIED_ALPHA = (1 << 1), /**< Each color has been scaled by the alpha value. */ SPA_VIDEO_FLAG_MODIFIER = (1 << 2), /**< use the format modifier */ SPA_VIDEO_FLAG_MODIFIER_FIXATION_REQUIRED = (1 << 3), /**< format modifier was not fixated yet */ }; /** * The possible values of the #spa_video_interlace_mode describing the interlace * mode of the stream. */ enum spa_video_interlace_mode { SPA_VIDEO_INTERLACE_MODE_PROGRESSIVE = 0, /**< all frames are progressive */ SPA_VIDEO_INTERLACE_MODE_INTERLEAVED, /**< 2 fields are interleaved in one video frame. * Extra buffer flags describe the field order. */ SPA_VIDEO_INTERLACE_MODE_MIXED, /**< frames contains both interlaced and progressive * video, the buffer flags describe the frame and * fields. */ SPA_VIDEO_INTERLACE_MODE_FIELDS, /**< 2 fields are stored in one buffer, use the * frame ID to get access to the required * field. For multiview (the 'views' * property > 1) the fields of view N can * be found at frame ID (N * 2) and (N * * 2) + 1. Each field has only half the * amount of lines as noted in the height * property. This mode requires multiple * spa_data to describe the fields. */ }; /** */ struct spa_video_info_raw { enum spa_video_format format; /**< the format */ uint32_t flags; /**< extra video flags */ uint64_t modifier; /**< format modifier * only used with DMA-BUF */ struct spa_rectangle size; /**< the frame size of the video */ struct spa_fraction framerate; /**< the framerate of the video, 0/1 means variable rate */ struct spa_fraction max_framerate; /**< the maximum framerate of the video. This is only valid when \ref framerate is 0/1 */ uint32_t views; /**< the number of views in this video */ enum spa_video_interlace_mode interlace_mode; /**< the interlace mode */ struct spa_fraction pixel_aspect_ratio; /**< the pixel aspect ratio */ enum spa_video_multiview_mode multiview_mode; /**< multiview mode */ enum spa_video_multiview_flags multiview_flags; /**< multiview flags */ enum spa_video_chroma_site chroma_site; /**< the chroma siting */ enum spa_video_color_range color_range; /**< the color range. This is the valid range for the samples. * It is used to convert the samples to Y'PbPr values. */ enum spa_video_color_matrix color_matrix; /**< the color matrix. Used to convert between Y'PbPr and * non-linear RGB (R'G'B') */ enum spa_video_transfer_function transfer_function; /**< the transfer function. used to convert between R'G'B' and RGB */ enum spa_video_color_primaries color_primaries; /**< color primaries. used to convert between R'G'B' and CIE XYZ */ }; #define SPA_VIDEO_INFO_RAW_INIT(...) ((struct spa_video_info_raw) { __VA_ARGS__ }) /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_VIDEO_RAW_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/param/video/type-info.h000066400000000000000000000004351511204443500277620ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_VIDEO_TYPES_H #define SPA_VIDEO_TYPES_H #include #include #endif /* SPA_VIDEO_TYPES_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/pod/000077500000000000000000000000001511204443500242515ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/pod/body.h000066400000000000000000000304071511204443500253630ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_POD_BODY_H #define SPA_POD_BODY_H #include #include #include #include #ifdef __cplusplus extern "C" { #endif #ifndef SPA_API_POD_BODY #ifdef SPA_API_IMPL #define SPA_API_POD_BODY SPA_API_IMPL #else #define SPA_API_POD_BODY static inline #endif #endif /** * \addtogroup spa_pod * \{ */ struct spa_pod_frame { struct spa_pod pod; struct spa_pod_frame *parent; uint32_t offset; uint32_t flags; }; SPA_API_POD_BODY uint32_t spa_pod_type_size(uint32_t type) { switch (type) { case SPA_TYPE_None: case SPA_TYPE_Bytes: case SPA_TYPE_Struct: case SPA_TYPE_Pod: return 0; case SPA_TYPE_String: return 1; case SPA_TYPE_Bool: case SPA_TYPE_Int: return sizeof(int32_t); case SPA_TYPE_Id: return sizeof(uint32_t); case SPA_TYPE_Long: return sizeof(int64_t); case SPA_TYPE_Float: return sizeof(float); case SPA_TYPE_Double: return sizeof(double); case SPA_TYPE_Rectangle: return sizeof(struct spa_rectangle); case SPA_TYPE_Fraction: return sizeof(struct spa_fraction); case SPA_TYPE_Bitmap: return sizeof(uint8_t); case SPA_TYPE_Array: return sizeof(struct spa_pod_array_body); case SPA_TYPE_Object: return sizeof(struct spa_pod_object_body); case SPA_TYPE_Sequence: return sizeof(struct spa_pod_sequence_body); case SPA_TYPE_Pointer: return sizeof(struct spa_pod_pointer_body); case SPA_TYPE_Fd: return sizeof(int64_t); case SPA_TYPE_Choice: return sizeof(struct spa_pod_choice_body); } return 0; } SPA_API_POD_BODY int spa_pod_choice_n_values(uint32_t choice_type, uint32_t *min, uint32_t *max) { switch (choice_type) { case SPA_CHOICE_Enum: *min = 2; *max = UINT32_MAX; break; case SPA_CHOICE_Range: *min = *max = 3; break; case SPA_CHOICE_Step: *min = *max = 4; break; case SPA_CHOICE_None: case SPA_CHOICE_Flags: *min = *max = 1; break; default: /* * This must always return at least 1, because callers * assume that n_vals >= spa_pod_choice_n_values() * mean that n_vals is at least 1. */ *min = 1; *max = UINT32_MAX; return 0; } return 1; } SPA_API_POD_BODY int spa_pod_body_from_data(void *data, size_t maxsize, off_t offset, size_t size, struct spa_pod *pod, const void **body) { if (offset < 0 || offset > (int64_t)UINT32_MAX) return -EINVAL; if (size < sizeof(struct spa_pod) || size > maxsize || maxsize - size < (uint32_t)offset) return -EINVAL; memcpy(pod, SPA_PTROFF(data, offset, void), sizeof(struct spa_pod)); if (!SPA_POD_IS_VALID(pod)) return -EINVAL; if (pod->size > size - sizeof(struct spa_pod)) return -EINVAL; *body = SPA_PTROFF(data, offset + sizeof(struct spa_pod), void); return 0; } SPA_API_POD_BODY int spa_pod_is_none(const struct spa_pod *pod) { return SPA_POD_CHECK_TYPE(pod, SPA_TYPE_None); } SPA_API_POD_BODY int spa_pod_is_bool(const struct spa_pod *pod) { return SPA_POD_CHECK(pod, SPA_TYPE_Bool, sizeof(int32_t)); } #define SPA_POD_BODY_LOAD_ONCE(a, b) (*(a) = SPA_LOAD_ONCE((__typeof__(a))(b))) #define SPA_POD_BODY_LOAD_FIELD_ONCE(a, b, field) ((a)->field = SPA_LOAD_ONCE(&((__typeof__(a))(b))->field)) SPA_API_POD_BODY int spa_pod_body_get_bool(const struct spa_pod *pod, const void *body, bool *value) { if (!spa_pod_is_bool(pod)) return -EINVAL; *value = !!__atomic_load_n((const int32_t *)body, __ATOMIC_RELAXED); return 0; } SPA_API_POD_BODY int spa_pod_is_id(const struct spa_pod *pod) { return SPA_POD_CHECK(pod, SPA_TYPE_Id, sizeof(uint32_t)); } SPA_API_POD_BODY int spa_pod_body_get_id(const struct spa_pod *pod, const void *body, uint32_t *value) { if (!spa_pod_is_id(pod)) return -EINVAL; SPA_POD_BODY_LOAD_ONCE(value, body); return 0; } SPA_API_POD_BODY int spa_pod_is_int(const struct spa_pod *pod) { return SPA_POD_CHECK(pod, SPA_TYPE_Int, sizeof(int32_t)); } SPA_API_POD_BODY int spa_pod_body_get_int(const struct spa_pod *pod, const void *body, int32_t *value) { if (!spa_pod_is_int(pod)) return -EINVAL; SPA_POD_BODY_LOAD_ONCE(value, body); return 0; } SPA_API_POD_BODY int spa_pod_is_long(const struct spa_pod *pod) { return SPA_POD_CHECK(pod, SPA_TYPE_Long, sizeof(int64_t)); } SPA_API_POD_BODY int spa_pod_body_get_long(const struct spa_pod *pod, const void *body, int64_t *value) { if (!spa_pod_is_long(pod)) return -EINVAL; /* TODO this is wrong per C standard, but if it breaks so does the Linux kernel. */ SPA_BARRIER; memcpy(value, body, sizeof *value); SPA_BARRIER; return 0; } SPA_API_POD_BODY int spa_pod_is_float(const struct spa_pod *pod) { return SPA_POD_CHECK(pod, SPA_TYPE_Float, sizeof(float)); } SPA_API_POD_BODY int spa_pod_body_get_float(const struct spa_pod *pod, const void *body, float *value) { if (!spa_pod_is_float(pod)) return -EINVAL; SPA_BARRIER; memcpy(value, body, sizeof *value); SPA_BARRIER; return 0; } SPA_API_POD_BODY int spa_pod_is_double(const struct spa_pod *pod) { return SPA_POD_CHECK(pod, SPA_TYPE_Double, sizeof(double)); } SPA_API_POD_BODY int spa_pod_body_get_double(const struct spa_pod *pod, const void *body, double *value) { if (!spa_pod_is_double(pod)) return -EINVAL; SPA_BARRIER; memcpy(value, body, sizeof *value); SPA_BARRIER; return 0; } SPA_API_POD_BODY int spa_pod_is_string(const struct spa_pod *pod) { return SPA_POD_CHECK(pod, SPA_TYPE_String, 1); } SPA_API_POD_BODY int spa_pod_body_get_string(const struct spa_pod *pod, const void *body, const char **value) { const char *s; if (!spa_pod_is_string(pod)) return -EINVAL; s = (const char *)body; if (((const volatile char *)s)[pod->size-1] != '\0') return -EINVAL; *value = s; return 0; } SPA_API_POD_BODY int spa_pod_body_copy_string(const struct spa_pod *pod, const void *body, char *dest, size_t maxlen) { const char *s; if (spa_pod_body_get_string(pod, body, &s) < 0 || maxlen < 1) return -EINVAL; SPA_BARRIER; strncpy(dest, s, maxlen-1); SPA_BARRIER; dest[maxlen-1]= '\0'; return 0; } SPA_API_POD_BODY int spa_pod_is_bytes(const struct spa_pod *pod) { return SPA_POD_CHECK_TYPE(pod, SPA_TYPE_Bytes); } SPA_API_POD_BODY int spa_pod_body_get_bytes(const struct spa_pod *pod, const void *body, const void **value, uint32_t *len) { if (!spa_pod_is_bytes(pod)) return -EINVAL; *value = (const void *)body; *len = pod->size; return 0; } SPA_API_POD_BODY int spa_pod_is_pointer(const struct spa_pod *pod) { return SPA_POD_CHECK(pod, SPA_TYPE_Pointer, sizeof(struct spa_pod_pointer_body)); } SPA_API_POD_BODY int spa_pod_body_get_pointer(const struct spa_pod *pod, const void *body, uint32_t *type, const void **value) { struct spa_pod_pointer_body b; if (!spa_pod_is_pointer(pod)) return -EINVAL; SPA_POD_BODY_LOAD_FIELD_ONCE(&b, body, type); SPA_POD_BODY_LOAD_FIELD_ONCE(&b, body, value); *type = b.type; *value = b.value; return 0; } SPA_API_POD_BODY int spa_pod_is_fd(const struct spa_pod *pod) { return SPA_POD_CHECK(pod, SPA_TYPE_Fd, sizeof(int64_t)); } SPA_API_POD_BODY int spa_pod_body_get_fd(const struct spa_pod *pod, const void *body, int64_t *value) { if (!spa_pod_is_fd(pod)) return -EINVAL; SPA_BARRIER; memcpy(value, body, sizeof *value); SPA_BARRIER; return 0; } SPA_API_POD_BODY int spa_pod_is_rectangle(const struct spa_pod *pod) { return SPA_POD_CHECK(pod, SPA_TYPE_Rectangle, sizeof(struct spa_rectangle)); } SPA_API_POD_BODY int spa_pod_body_get_rectangle(const struct spa_pod *pod, const void *body, struct spa_rectangle *value) { if (!spa_pod_is_rectangle(pod)) return -EINVAL; SPA_POD_BODY_LOAD_FIELD_ONCE(value, body, width); SPA_POD_BODY_LOAD_FIELD_ONCE(value, body, height); return 0; } SPA_API_POD_BODY int spa_pod_is_fraction(const struct spa_pod *pod) { return SPA_POD_CHECK(pod, SPA_TYPE_Fraction, sizeof(struct spa_fraction)); } SPA_API_POD_BODY int spa_pod_body_get_fraction(const struct spa_pod *pod, const void *body, struct spa_fraction *value) { if (!spa_pod_is_fraction(pod)) return -EINVAL; SPA_POD_BODY_LOAD_FIELD_ONCE(value, body, num); SPA_POD_BODY_LOAD_FIELD_ONCE(value, body, denom); return 0; } SPA_API_POD_BODY int spa_pod_is_bitmap(const struct spa_pod *pod) { return SPA_POD_CHECK(pod, SPA_TYPE_Bitmap, sizeof(uint8_t)); } SPA_API_POD_BODY int spa_pod_body_get_bitmap(const struct spa_pod *pod, const void *body, const uint8_t **value) { if (!spa_pod_is_bitmap(pod)) return -EINVAL; *value = (const uint8_t *)body; return 0; } SPA_API_POD_BODY int spa_pod_is_array(const struct spa_pod *pod) { return SPA_POD_CHECK(pod, SPA_TYPE_Array, sizeof(struct spa_pod_array_body)); } SPA_API_POD_BODY int spa_pod_body_get_array(const struct spa_pod *pod, const void *body, struct spa_pod_array *arr, const void **arr_body) { if (!spa_pod_is_array(pod)) return -EINVAL; arr->pod = *pod; SPA_POD_BODY_LOAD_FIELD_ONCE(&arr->body.child, body, type); SPA_POD_BODY_LOAD_FIELD_ONCE(&arr->body.child, body, size); *arr_body = SPA_PTROFF(body, sizeof(struct spa_pod_array_body), void); return 0; } SPA_API_POD_BODY const void *spa_pod_array_body_get_values(const struct spa_pod_array *arr, const void *body, uint32_t *n_values, uint32_t *val_size, uint32_t *val_type) { uint32_t child_size = arr->body.child.size; *n_values = child_size ? (arr->pod.size - sizeof(arr->body)) / child_size : 0; *val_size = child_size; *val_type = arr->body.child.type; if (*val_size < spa_pod_type_size(*val_type)) *n_values = 0; return body; } SPA_API_POD_BODY const void *spa_pod_body_get_array_values(const struct spa_pod *pod, const void *body, uint32_t *n_values, uint32_t *val_size, uint32_t *val_type) { struct spa_pod_array arr; if (spa_pod_body_get_array(pod, body, &arr, &body) < 0) return NULL; return spa_pod_array_body_get_values(&arr, body, n_values, val_size, val_type); } SPA_API_POD_BODY int spa_pod_is_choice(const struct spa_pod *pod) { return SPA_POD_CHECK(pod, SPA_TYPE_Choice, sizeof(struct spa_pod_choice_body)); } SPA_API_POD_BODY int spa_pod_body_get_choice(const struct spa_pod *pod, const void *body, struct spa_pod_choice *choice, const void **choice_body) { if (!spa_pod_is_choice(pod)) return -EINVAL; choice->pod = *pod; SPA_POD_BODY_LOAD_FIELD_ONCE(&choice->body, body, type); SPA_POD_BODY_LOAD_FIELD_ONCE(&choice->body, body, flags); SPA_POD_BODY_LOAD_FIELD_ONCE(&choice->body, body, child.size); SPA_POD_BODY_LOAD_FIELD_ONCE(&choice->body, body, child.type); *choice_body = SPA_PTROFF(body, sizeof(struct spa_pod_choice_body), void); return 0; } SPA_API_POD_BODY const void *spa_pod_choice_body_get_values(const struct spa_pod_choice *pod, const void *body, uint32_t *n_values, uint32_t *choice, uint32_t *val_size, uint32_t *val_type) { uint32_t child_size = pod->body.child.size, min, max; *val_size = child_size; *val_type = pod->body.child.type; *n_values = child_size ? (pod->pod.size - sizeof(pod->body)) / child_size : 0; *choice = pod->body.type; spa_pod_choice_n_values(*choice, &min, &max); if (*n_values < min || *val_size < spa_pod_type_size(*val_type)) *n_values = 0; else if (*n_values > max) *n_values = max; return body; } SPA_API_POD_BODY int spa_pod_is_struct(const struct spa_pod *pod) { return SPA_POD_CHECK_TYPE(pod, SPA_TYPE_Struct); } SPA_API_POD_BODY int spa_pod_is_object(const struct spa_pod *pod) { return SPA_POD_CHECK(pod, SPA_TYPE_Object, sizeof(struct spa_pod_object_body)); } SPA_API_POD_BODY int spa_pod_body_get_object(const struct spa_pod *pod, const void *body, struct spa_pod_object *object, const void **object_body) { if (!spa_pod_is_object(pod)) return -EINVAL; object->pod = *pod; SPA_POD_BODY_LOAD_FIELD_ONCE(&object->body, body, type); SPA_POD_BODY_LOAD_FIELD_ONCE(&object->body, body, id); *object_body = SPA_PTROFF(body, sizeof(struct spa_pod_object_body), void); return 0; } SPA_API_POD_BODY int spa_pod_is_sequence(const struct spa_pod *pod) { return SPA_POD_CHECK(pod, SPA_TYPE_Sequence, sizeof(struct spa_pod_sequence_body)); } SPA_API_POD_BODY int spa_pod_body_get_sequence(const struct spa_pod *pod, const void *body, struct spa_pod_sequence *seq, const void **seq_body) { if (!spa_pod_is_sequence(pod)) return -EINVAL; seq->pod = *pod; SPA_POD_BODY_LOAD_FIELD_ONCE(&seq->body, body, unit); SPA_POD_BODY_LOAD_FIELD_ONCE(&seq->body, body, pad); *seq_body = SPA_PTROFF(body, sizeof(struct spa_pod_sequence_body), void); return 0; } /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_POD_BODY_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/pod/builder.h000066400000000000000000000542471511204443500260640ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_POD_BUILDER_H #define SPA_POD_BUILDER_H #include #include #include #include #ifdef __cplusplus extern "C" { #endif /** \defgroup spa_pod POD * Binary data serialization format */ /** * \addtogroup spa_pod * \{ */ #ifndef SPA_API_POD_BUILDER #ifdef SPA_API_IMPL #define SPA_API_POD_BUILDER SPA_API_IMPL #else #define SPA_API_POD_BUILDER static inline #endif #endif struct spa_pod_builder_state { uint32_t offset; #define SPA_POD_BUILDER_FLAG_BODY (1<<0) #define SPA_POD_BUILDER_FLAG_FIRST (1<<1) uint32_t flags; struct spa_pod_frame *frame; }; struct spa_pod_builder; struct spa_pod_builder_callbacks { #define SPA_VERSION_POD_BUILDER_CALLBACKS 0 uint32_t version; int (*overflow) (void *data, uint32_t size); }; struct spa_pod_builder { void *data; uint32_t size; uint32_t _padding; struct spa_pod_builder_state state; struct spa_callbacks callbacks; }; #define SPA_POD_BUILDER_INIT(buffer,size) ((struct spa_pod_builder){ (buffer), (size), 0, {0,0,NULL},{NULL,NULL}}) SPA_API_POD_BUILDER void spa_pod_builder_get_state(struct spa_pod_builder *builder, struct spa_pod_builder_state *state) { *state = builder->state; } SPA_API_POD_BUILDER bool spa_pod_builder_corrupted(const struct spa_pod_builder *builder) { return builder->state.offset > builder->size; } SPA_API_POD_BUILDER void spa_pod_builder_set_callbacks(struct spa_pod_builder *builder, const struct spa_pod_builder_callbacks *callbacks, void *data) { builder->callbacks = SPA_CALLBACKS_INIT(callbacks, data); } SPA_API_POD_BUILDER void spa_pod_builder_reset(struct spa_pod_builder *builder, struct spa_pod_builder_state *state) { struct spa_pod_frame *f; uint32_t size = builder->state.offset - state->offset; builder->state = *state; for (f = builder->state.frame; f ; f = f->parent) f->pod.size -= SPA_MIN(size, f->pod.size); } SPA_API_POD_BUILDER void spa_pod_builder_init(struct spa_pod_builder *builder, void *data, uint32_t size) { *builder = SPA_POD_BUILDER_INIT(data, size); } SPA_API_POD_BUILDER struct spa_pod * spa_pod_builder_deref_fallback(struct spa_pod_builder *builder, uint32_t offset, struct spa_pod *fallback) { uint32_t size = builder->size; if (offset + UINT64_C(8) <= size) { struct spa_pod *pod = SPA_PTROFF(builder->data, offset, struct spa_pod); if (offset + (uint64_t)SPA_POD_SIZE(pod) <= size && SPA_POD_IS_VALID(pod)) return pod; } return fallback; } SPA_API_POD_BUILDER struct spa_pod * spa_pod_builder_deref(struct spa_pod_builder *builder, uint32_t offset) { return (struct spa_pod*)spa_pod_builder_deref_fallback(builder, offset, NULL); } SPA_API_POD_BUILDER struct spa_pod * spa_pod_builder_frame(struct spa_pod_builder *builder, struct spa_pod_frame *frame) { if (frame->offset + (uint64_t)SPA_POD_SIZE(&frame->pod) <= builder->size) return SPA_PTROFF(builder->data, frame->offset, struct spa_pod); return NULL; } SPA_API_POD_BUILDER void spa_pod_builder_push(struct spa_pod_builder *builder, struct spa_pod_frame *frame, const struct spa_pod *pod, uint32_t offset) { frame->pod = *pod; frame->offset = offset; frame->parent = builder->state.frame; frame->flags = builder->state.flags; builder->state.frame = frame; if (frame->pod.type == SPA_TYPE_Array || frame->pod.type == SPA_TYPE_Choice) builder->state.flags = SPA_POD_BUILDER_FLAG_FIRST | SPA_POD_BUILDER_FLAG_BODY; } SPA_API_POD_BUILDER int spa_pod_builder_raw(struct spa_pod_builder *builder, const void *data, uint32_t size) { int res = 0; struct spa_pod_frame *f; uint32_t offset = builder->state.offset; size_t data_offset = -1; uint64_t total_size = offset + (uint64_t) size; if (total_size > builder->size) { if (total_size > UINT32_MAX) return -ENOSPC; /* data could be inside the data we will realloc */ if (spa_ptrinside(builder->data, builder->size, data, size, NULL)) data_offset = SPA_PTRDIFF(data, builder->data); res = -ENOSPC; if (offset <= builder->size) spa_callbacks_call_res(&builder->callbacks, struct spa_pod_builder_callbacks, res, overflow, 0, total_size); } if (res == 0 && data) { if (data_offset != (size_t) -1) data = SPA_PTROFF(builder->data, data_offset, const void); memcpy(SPA_PTROFF(builder->data, offset, void), data, size); } builder->state.offset = total_size; for (f = builder->state.frame; f ; f = f->parent) f->pod.size += size; return res; } SPA_API_POD_BUILDER void spa_pod_builder_remove(struct spa_pod_builder *builder, uint32_t size) { struct spa_pod_frame *f; builder->state.offset -= SPA_MIN(size, builder->state.offset); for (f = builder->state.frame; f ; f = f->parent) f->pod.size -= SPA_MIN(size, f->pod.size); } SPA_API_POD_BUILDER int spa_pod_builder_pad(struct spa_pod_builder *builder, uint32_t size) { uint64_t zeroes = 0; if (builder->state.flags == SPA_POD_BUILDER_FLAG_BODY) return 0; size = SPA_ROUND_UP_N(size, SPA_POD_ALIGN) - size; return size ? spa_pod_builder_raw(builder, &zeroes, size) : 0; } SPA_API_POD_BUILDER int spa_pod_builder_raw_padded(struct spa_pod_builder *builder, const void *data, uint32_t size) { int r, res = spa_pod_builder_raw(builder, data, size); if ((r = spa_pod_builder_pad(builder, size)) < 0) res = r; return res; } SPA_API_POD_BUILDER void *spa_pod_builder_pop(struct spa_pod_builder *builder, struct spa_pod_frame *frame) { struct spa_pod *pod; if (SPA_FLAG_IS_SET(builder->state.flags, SPA_POD_BUILDER_FLAG_FIRST)) { const struct spa_pod p = { 0, SPA_TYPE_None }; spa_pod_builder_raw(builder, &p, sizeof(p)); } if ((pod = (struct spa_pod*)spa_pod_builder_frame(builder, frame)) != NULL) *pod = frame->pod; builder->state.frame = frame->parent; builder->state.flags = frame->flags; spa_pod_builder_pad(builder, builder->state.offset); return pod; } SPA_API_POD_BUILDER int spa_pod_builder_primitive_body(struct spa_pod_builder *builder, const struct spa_pod *p, const void *body, uint32_t body_size, const char *suffix, uint32_t suffix_size) { int res = 0, r; uint32_t size = SPA_POD_SIZE(p) - body_size - suffix_size; if (builder->state.flags != SPA_POD_BUILDER_FLAG_BODY) { SPA_FLAG_CLEAR(builder->state.flags, SPA_POD_BUILDER_FLAG_FIRST); res = spa_pod_builder_raw(builder, p, size); } if (body_size > 0 && (r = spa_pod_builder_raw(builder, body, body_size)) < 0) res = r; if (suffix_size > 0 && (r = spa_pod_builder_raw(builder, suffix, suffix_size)) < 0) res = r; if ((r = spa_pod_builder_pad(builder, builder->state.offset)) < 0) res = r; return res; } SPA_API_POD_BUILDER int spa_pod_builder_primitive(struct spa_pod_builder *builder, const struct spa_pod *p) { return spa_pod_builder_primitive_body(builder, p, SPA_POD_BODY_CONST(p), p->size, NULL, 0); } #define SPA_POD_INIT(size,type) ((struct spa_pod) { (size), (type) }) #define SPA_POD_INIT_None() SPA_POD_INIT(0, SPA_TYPE_None) SPA_API_POD_BUILDER int spa_pod_builder_none(struct spa_pod_builder *builder) { const struct spa_pod p = SPA_POD_INIT_None(); return spa_pod_builder_primitive(builder, &p); } SPA_API_POD_BUILDER int spa_pod_builder_child(struct spa_pod_builder *builder, uint32_t size, uint32_t type) { const struct spa_pod p = SPA_POD_INIT(size,type); SPA_FLAG_CLEAR(builder->state.flags, SPA_POD_BUILDER_FLAG_FIRST); return spa_pod_builder_raw(builder, &p, sizeof(p)); } #define SPA_POD_INIT_Bool(val) ((struct spa_pod_bool){ { sizeof(uint32_t), SPA_TYPE_Bool }, (val) ? 1 : 0, 0 }) SPA_API_POD_BUILDER int spa_pod_builder_bool(struct spa_pod_builder *builder, bool val) { const struct spa_pod_bool p = SPA_POD_INIT_Bool(val); return spa_pod_builder_primitive(builder, &p.pod); } #define SPA_POD_INIT_Id(val) ((struct spa_pod_id){ { sizeof(uint32_t), SPA_TYPE_Id }, (val), 0 }) SPA_API_POD_BUILDER int spa_pod_builder_id(struct spa_pod_builder *builder, uint32_t val) { const struct spa_pod_id p = SPA_POD_INIT_Id(val); return spa_pod_builder_primitive(builder, &p.pod); } #define SPA_POD_INIT_Int(val) ((struct spa_pod_int){ { sizeof(int32_t), SPA_TYPE_Int }, (val), 0 }) SPA_API_POD_BUILDER int spa_pod_builder_int(struct spa_pod_builder *builder, int32_t val) { const struct spa_pod_int p = SPA_POD_INIT_Int(val); return spa_pod_builder_primitive(builder, &p.pod); } #define SPA_POD_INIT_Long(val) ((struct spa_pod_long){ { sizeof(int64_t), SPA_TYPE_Long }, (val) }) SPA_API_POD_BUILDER int spa_pod_builder_long(struct spa_pod_builder *builder, int64_t val) { const struct spa_pod_long p = SPA_POD_INIT_Long(val); return spa_pod_builder_primitive(builder, &p.pod); } #define SPA_POD_INIT_Float(val) ((struct spa_pod_float){ { sizeof(float), SPA_TYPE_Float }, (val), 0 }) SPA_API_POD_BUILDER int spa_pod_builder_float(struct spa_pod_builder *builder, float val) { const struct spa_pod_float p = SPA_POD_INIT_Float(val); return spa_pod_builder_primitive(builder, &p.pod); } #define SPA_POD_INIT_Double(val) ((struct spa_pod_double){ { sizeof(double), SPA_TYPE_Double }, (val) }) SPA_API_POD_BUILDER int spa_pod_builder_double(struct spa_pod_builder *builder, double val) { const struct spa_pod_double p = SPA_POD_INIT_Double(val); return spa_pod_builder_primitive(builder, &p.pod); } #define SPA_POD_INIT_String(len) ((struct spa_pod_string){ { (len), SPA_TYPE_String } }) SPA_API_POD_BUILDER int spa_pod_builder_write_string(struct spa_pod_builder *builder, const char *str, uint32_t len) { int r, res; res = spa_pod_builder_raw(builder, str, len); if ((r = spa_pod_builder_raw(builder, "", 1)) < 0) res = r; if ((r = spa_pod_builder_pad(builder, builder->state.offset)) < 0) res = r; return res; } SPA_API_POD_BUILDER int spa_pod_builder_string_len(struct spa_pod_builder *builder, const char *str, uint32_t len) { const struct spa_pod_string p = SPA_POD_INIT_String(len+1); return spa_pod_builder_primitive_body(builder, &p.pod, str, len, "", 1); } SPA_API_POD_BUILDER int spa_pod_builder_string(struct spa_pod_builder *builder, const char *str) { uint32_t len = str ? strlen(str) : 0; return spa_pod_builder_string_len(builder, str ? str : "", len); } #define SPA_POD_INIT_Bytes(len) ((struct spa_pod_bytes){ { (len), SPA_TYPE_Bytes } }) SPA_API_POD_BUILDER int spa_pod_builder_bytes(struct spa_pod_builder *builder, const void *bytes, uint32_t len) { const struct spa_pod_bytes p = SPA_POD_INIT_Bytes(len); return spa_pod_builder_primitive_body(builder, &p.pod, bytes, len, NULL, 0); } SPA_API_POD_BUILDER void * spa_pod_builder_reserve_bytes(struct spa_pod_builder *builder, uint32_t len) { uint32_t offset = builder->state.offset; if (spa_pod_builder_bytes(builder, NULL, len) < 0) return NULL; return SPA_PTROFF(builder->data, offset + sizeof(struct spa_pod), void); } #define SPA_POD_INIT_Pointer(type,value) ((struct spa_pod_pointer){ { sizeof(struct spa_pod_pointer_body), SPA_TYPE_Pointer }, { (type), 0, (value) } }) SPA_API_POD_BUILDER int spa_pod_builder_pointer(struct spa_pod_builder *builder, uint32_t type, const void *val) { const struct spa_pod_pointer p = SPA_POD_INIT_Pointer(type, val); return spa_pod_builder_primitive(builder, &p.pod); } #define SPA_POD_INIT_Fd(fd) ((struct spa_pod_fd){ { sizeof(int64_t), SPA_TYPE_Fd }, (fd) }) SPA_API_POD_BUILDER int spa_pod_builder_fd(struct spa_pod_builder *builder, int64_t fd) { const struct spa_pod_fd p = SPA_POD_INIT_Fd(fd); return spa_pod_builder_primitive(builder, &p.pod); } #define SPA_POD_INIT_Rectangle(val) ((struct spa_pod_rectangle){ { sizeof(struct spa_rectangle), SPA_TYPE_Rectangle }, (val) }) SPA_API_POD_BUILDER int spa_pod_builder_rectangle(struct spa_pod_builder *builder, uint32_t width, uint32_t height) { const struct spa_pod_rectangle p = SPA_POD_INIT_Rectangle(SPA_RECTANGLE(width, height)); return spa_pod_builder_primitive(builder, &p.pod); } #define SPA_POD_INIT_Fraction(val) ((struct spa_pod_fraction){ { sizeof(struct spa_fraction), SPA_TYPE_Fraction }, (val) }) SPA_API_POD_BUILDER int spa_pod_builder_fraction(struct spa_pod_builder *builder, uint32_t num, uint32_t denom) { const struct spa_pod_fraction p = SPA_POD_INIT_Fraction(SPA_FRACTION(num, denom)); return spa_pod_builder_primitive(builder, &p.pod); } SPA_API_POD_BUILDER int spa_pod_builder_push_array(struct spa_pod_builder *builder, struct spa_pod_frame *frame) { const struct spa_pod_array p = { {sizeof(struct spa_pod_array_body) - sizeof(struct spa_pod), SPA_TYPE_Array}, {{0, 0}} }; uint32_t offset = builder->state.offset; int res = spa_pod_builder_raw(builder, &p, sizeof(p) - sizeof(struct spa_pod)); spa_pod_builder_push(builder, frame, &p.pod, offset); return res; } SPA_API_POD_BUILDER int spa_pod_builder_array(struct spa_pod_builder *builder, uint32_t child_size, uint32_t child_type, uint32_t n_elems, const void *elems) { const struct spa_pod_array p = { {(uint32_t)(sizeof(struct spa_pod_array_body) + n_elems * child_size), SPA_TYPE_Array}, {{child_size, child_type}} }; return spa_pod_builder_primitive_body(builder, &p.pod, elems, n_elems * child_size, NULL, 0); } #define SPA_POD_INIT_CHOICE_BODY(type, flags, child_size, child_type) \ ((struct spa_pod_choice_body) { (type), (flags), { (child_size), (child_type) }}) #define SPA_POD_INIT_Choice(type, ctype, child_type, n_vals, ...) \ ((struct { struct spa_pod_choice choice; ctype vals[(n_vals)];}) \ { { { (n_vals) * sizeof(ctype) + sizeof(struct spa_pod_choice_body), SPA_TYPE_Choice }, \ { (type), 0, { sizeof(ctype), (child_type) } } }, { __VA_ARGS__ } }) SPA_API_POD_BUILDER int spa_pod_builder_push_choice(struct spa_pod_builder *builder, struct spa_pod_frame *frame, uint32_t type, uint32_t flags) { const struct spa_pod_choice p = { {sizeof(struct spa_pod_choice_body) - sizeof(struct spa_pod), SPA_TYPE_Choice}, { type, flags, {0, 0}} }; uint32_t offset = builder->state.offset; int res = spa_pod_builder_raw(builder, &p, sizeof(p) - sizeof(struct spa_pod)); spa_pod_builder_push(builder, frame, &p.pod, offset); return res; } #define SPA_POD_INIT_Struct(size) ((struct spa_pod_struct){ { (size), SPA_TYPE_Struct } }) SPA_API_POD_BUILDER int spa_pod_builder_push_struct(struct spa_pod_builder *builder, struct spa_pod_frame *frame) { const struct spa_pod_struct p = SPA_POD_INIT_Struct(0); uint32_t offset = builder->state.offset; int res = spa_pod_builder_raw(builder, &p, sizeof(p)); spa_pod_builder_push(builder, frame, &p.pod, offset); return res; } #define SPA_POD_INIT_Object(size,type,id,...) ((struct spa_pod_object){ { (size), SPA_TYPE_Object }, { (type), (id) }, ##__VA_ARGS__ }) SPA_API_POD_BUILDER int spa_pod_builder_push_object(struct spa_pod_builder *builder, struct spa_pod_frame *frame, uint32_t type, uint32_t id) { const struct spa_pod_object p = SPA_POD_INIT_Object(sizeof(struct spa_pod_object_body), type, id); uint32_t offset = builder->state.offset; int res = spa_pod_builder_raw(builder, &p, sizeof(p)); spa_pod_builder_push(builder, frame, &p.pod, offset); return res; } #define SPA_POD_INIT_Prop(key,flags,size,type) \ ((struct spa_pod_prop){ (key), (flags), { (size), (type) } }) SPA_API_POD_BUILDER int spa_pod_builder_prop(struct spa_pod_builder *builder, uint32_t key, uint32_t flags) { const struct { uint32_t key; uint32_t flags; } p = { key, flags }; return spa_pod_builder_raw(builder, &p, sizeof(p)); } #define SPA_POD_INIT_Sequence(size,unit) \ ((struct spa_pod_sequence){ { (size), SPA_TYPE_Sequence}, {(unit), 0 } }) SPA_API_POD_BUILDER int spa_pod_builder_push_sequence(struct spa_pod_builder *builder, struct spa_pod_frame *frame, uint32_t unit) { const struct spa_pod_sequence p = SPA_POD_INIT_Sequence(sizeof(struct spa_pod_sequence_body), unit); uint32_t offset = builder->state.offset; int res = spa_pod_builder_raw(builder, &p, sizeof(p)); spa_pod_builder_push(builder, frame, &p.pod, offset); return res; } SPA_API_POD_BUILDER int spa_pod_builder_control(struct spa_pod_builder *builder, uint32_t offset, uint32_t type) { const struct { uint32_t offset; uint32_t type; } p = { offset, type }; return spa_pod_builder_raw(builder, &p, sizeof(p)); } SPA_API_POD_BUILDER uint32_t spa_choice_from_id_flags(char id, uint32_t *flags) { switch (id) { case 'r': return SPA_CHOICE_Range; case 's': return SPA_CHOICE_Step; case 'e': return SPA_CHOICE_Enum; case 'F': *flags |= SPA_POD_PROP_FLAG_DROP; SPA_FALLTHROUGH; case 'f': return SPA_CHOICE_Flags; case 'n': default: return SPA_CHOICE_None; } } SPA_API_POD_BUILDER uint32_t spa_choice_from_id(char id) { uint32_t flags = 0; return spa_choice_from_id_flags(id, &flags); } #define SPA_POD_BUILDER_COLLECT(builder,type,args) \ do { \ switch (type) { \ case 'b': \ spa_pod_builder_bool(builder, !!va_arg(args, int)); \ break; \ case 'I': \ spa_pod_builder_id(builder, va_arg(args, uint32_t)); \ break; \ case 'i': \ spa_pod_builder_int(builder, va_arg(args, int)); \ break; \ case 'l': \ spa_pod_builder_long(builder, va_arg(args, int64_t)); \ break; \ case 'f': \ spa_pod_builder_float(builder, (float)va_arg(args, double)); \ break; \ case 'd': \ spa_pod_builder_double(builder, va_arg(args, double)); \ break; \ case 's': \ { \ char *strval = va_arg(args, char *); \ if (strval != NULL) { \ size_t len = strlen(strval); \ spa_pod_builder_string_len(builder, strval, len); \ } \ else \ spa_pod_builder_none(builder); \ break; \ } \ case 'S': \ { \ char *strval = va_arg(args, char *); \ size_t len = va_arg(args, int); \ spa_pod_builder_string_len(builder, strval, len); \ break; \ } \ case 'y': \ { \ void *ptr = va_arg(args, void *); \ int len = va_arg(args, int); \ spa_pod_builder_bytes(builder, ptr, len); \ break; \ } \ case 'R': \ { \ struct spa_rectangle *rectval = \ va_arg(args, struct spa_rectangle *); \ spa_pod_builder_rectangle(builder, \ rectval->width, rectval->height); \ break; \ } \ case 'F': \ { \ struct spa_fraction *fracval = \ va_arg(args, struct spa_fraction *); \ spa_pod_builder_fraction(builder, fracval->num, fracval->denom);\ break; \ } \ case 'a': \ { \ int child_size = va_arg(args, int); \ int child_type = va_arg(args, int); \ int n_elems = va_arg(args, int); \ void *elems = va_arg(args, void *); \ spa_pod_builder_array(builder, child_size, \ child_type, n_elems, elems); \ break; \ } \ case 'p': \ { \ int t = va_arg(args, uint32_t); \ spa_pod_builder_pointer(builder, t, va_arg(args, void *)); \ break; \ } \ case 'h': \ spa_pod_builder_fd(builder, va_arg(args, int)); \ break; \ case 'P': \ case 'O': \ case 'T': \ case 'V': \ { \ struct spa_pod *pod = va_arg(args, struct spa_pod *); \ if (pod == NULL) \ spa_pod_builder_none(builder); \ else \ spa_pod_builder_primitive(builder, pod); \ break; \ } \ case 'Q': \ case 'N': \ case 'U': \ case 'W': \ { \ struct spa_pod *pod = va_arg(args, struct spa_pod *); \ const void *body = va_arg(args, const void *); \ spa_pod_builder_primitive_body(builder, pod, \ body, pod->size, NULL, 0); \ break; \ } \ } \ } while(false) SPA_API_POD_BUILDER int spa_pod_builder_addv(struct spa_pod_builder *builder, va_list args) { int res = 0; struct spa_pod_frame *frame = builder->state.frame; uint32_t ftype = frame ? frame->pod.type : (uint32_t)SPA_TYPE_None; do { const char *format; int n_values = 1; struct spa_pod_frame f; bool choice; uint32_t key = 0, flags = 0, offset = 0, type = 0, ctype = 0; switch (ftype) { case SPA_TYPE_Object: key = va_arg(args, uint32_t); if (key == 0) goto exit; if (key == SPA_ID_INVALID) { key = va_arg(args, uint32_t); flags = va_arg(args, uint32_t); } break; case SPA_TYPE_Sequence: offset = va_arg(args, uint32_t); type = va_arg(args, uint32_t); if (type == 0) goto exit; break; } if ((format = va_arg(args, const char *)) == NULL) break; choice = *format == '?'; if (choice) { ctype = spa_choice_from_id_flags(*++format, &flags); if (*format != '\0') format++; } switch (ftype) { case SPA_TYPE_Object: spa_pod_builder_prop(builder, key, flags); break; case SPA_TYPE_Sequence: spa_pod_builder_control(builder, offset, type); break; } if (choice) { spa_pod_builder_push_choice(builder, &f, ctype, 0); n_values = va_arg(args, int); } while (n_values-- > 0) SPA_POD_BUILDER_COLLECT(builder, *format, args); if (choice) spa_pod_builder_pop(builder, &f); } while (true); exit: return res; } SPA_API_POD_BUILDER int spa_pod_builder_add(struct spa_pod_builder *builder, ...) { int res; va_list args; va_start(args, builder); res = spa_pod_builder_addv(builder, args); va_end(args); return res; } #define spa_pod_builder_add_object(b,type,id,...) \ ({ \ struct spa_pod_builder *_b = (b); \ struct spa_pod_frame _f; \ spa_pod_builder_push_object(_b, &_f, type, id); \ spa_pod_builder_add(_b, ##__VA_ARGS__, 0); \ spa_pod_builder_pop(_b, &_f); \ }) #define spa_pod_builder_add_struct(b,...) \ ({ \ struct spa_pod_builder *_b = (b); \ struct spa_pod_frame _f; \ spa_pod_builder_push_struct(_b, &_f); \ spa_pod_builder_add(_b, ##__VA_ARGS__, NULL); \ spa_pod_builder_pop(_b, &_f); \ }) #define spa_pod_builder_add_sequence(b,unit,...) \ ({ \ struct spa_pod_builder *_b = (b); \ struct spa_pod_frame _f; \ spa_pod_builder_push_sequence(_b, &_f, unit); \ spa_pod_builder_add(_b, ##__VA_ARGS__, 0, 0); \ spa_pod_builder_pop(_b, &_f); \ }) /** Copy a pod structure */ SPA_API_POD_BUILDER struct spa_pod * spa_pod_copy(const struct spa_pod *pod) { size_t size; struct spa_pod *c; size = SPA_POD_SIZE(pod); if ((c = (struct spa_pod *) malloc(size)) == NULL) return NULL; return (struct spa_pod *) memcpy(c, pod, size); } /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_POD_BUILDER_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/pod/command.h000066400000000000000000000017141511204443500260430ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_COMMAND_H #define SPA_COMMAND_H #include #include #ifdef __cplusplus extern "C" { #endif /** * \addtogroup spa_pod * \{ */ struct spa_command_body { struct spa_pod_object_body body; }; struct spa_command { struct spa_pod pod; struct spa_command_body body; }; #define SPA_COMMAND_TYPE(cmd) ((cmd)->body.body.type) #define SPA_COMMAND_ID(cmd,type) (SPA_COMMAND_TYPE(cmd) == (type) ? \ (cmd)->body.body.id : SPA_ID_INVALID) #define SPA_COMMAND_INIT_FULL(t,size,type,id,...) ((t) \ { { (size), SPA_TYPE_Object }, \ { { (type), (id) }, ##__VA_ARGS__ } }) #define SPA_COMMAND_INIT(type,id) \ SPA_COMMAND_INIT_FULL(struct spa_command, \ sizeof(struct spa_command_body), type, id) /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_COMMAND_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/pod/compare.h000066400000000000000000000145301511204443500260530ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_POD_COMPARE_H #define SPA_POD_COMPARE_H #include #include #include #include #include #include #include #include #include #ifdef __cplusplus extern "C" { #endif #ifndef SPA_API_POD_COMPARE #ifdef SPA_API_IMPL #define SPA_API_POD_COMPARE SPA_API_IMPL #else #define SPA_API_POD_COMPARE static inline #endif #endif /** * \addtogroup spa_pod * \{ */ SPA_API_POD_COMPARE int spa_pod_compare_value(uint32_t type, const void *r1, const void *r2, uint32_t size) { switch (type) { case SPA_TYPE_None: return 0; case SPA_TYPE_Bool: return SPA_CMP(!!*(int32_t *)r1, !!*(int32_t *)r2); case SPA_TYPE_Id: return SPA_CMP(*(uint32_t *)r1, *(uint32_t *)r2); case SPA_TYPE_Int: return SPA_CMP(*(int32_t *)r1, *(int32_t *)r2); case SPA_TYPE_Long: return SPA_CMP(*(int64_t *)r1, *(int64_t *)r2); case SPA_TYPE_Float: return SPA_CMP(*(float *)r1, *(float *)r2); case SPA_TYPE_Double: return SPA_CMP(*(double *)r1, *(double *)r2); case SPA_TYPE_String: return strncmp((char *)r1, (char *)r2, size); case SPA_TYPE_Rectangle: { const struct spa_rectangle *rec1 = (struct spa_rectangle *) r1, *rec2 = (struct spa_rectangle *) r2; uint64_t a1, a2; a1 = ((uint64_t) rec1->width) * rec1->height; a2 = ((uint64_t) rec2->width) * rec2->height; if (a1 < a2) return -1; if (a1 > a2) return 1; return SPA_CMP(rec1->width, rec2->width); } case SPA_TYPE_Fraction: { const struct spa_fraction *f1 = (struct spa_fraction *) r1, *f2 = (struct spa_fraction *) r2; uint64_t n1, n2; n1 = ((uint64_t) f1->num) * f2->denom; n2 = ((uint64_t) f2->num) * f1->denom; return SPA_CMP(n1, n2); } default: return memcmp(r1, r2, size); } return 0; } SPA_API_POD_COMPARE int spa_pod_memcmp(const struct spa_pod *a, const struct spa_pod *b) { return ((a == b) || (a && b && SPA_POD_SIZE(a) == SPA_POD_SIZE(b) && memcmp(a, b, SPA_POD_SIZE(b)) == 0)) ? 0 : 1; } SPA_API_POD_COMPARE int spa_pod_compare(const struct spa_pod *pod1, const struct spa_pod *pod2) { int res = 0; uint32_t n_vals1, n_vals2; uint32_t choice1, choice2; spa_return_val_if_fail(pod1 != NULL, -EINVAL); spa_return_val_if_fail(pod2 != NULL, -EINVAL); pod1 = spa_pod_get_values(pod1, &n_vals1, &choice1); pod2 = spa_pod_get_values(pod2, &n_vals2, &choice2); if (n_vals1 != n_vals2) return -EINVAL; if (pod1->type != pod2->type) return -EINVAL; if (n_vals1 < 1) return -EINVAL; /* empty choice */ switch (pod1->type) { case SPA_TYPE_Struct: { const struct spa_pod *p1, *p2; size_t p1s, p2s; p1 = (const struct spa_pod*)SPA_POD_BODY_CONST(pod1); p1s = SPA_POD_BODY_SIZE(pod1); p2 = (const struct spa_pod*)SPA_POD_BODY_CONST(pod2); p2s = SPA_POD_BODY_SIZE(pod2); while (true) { if (!spa_pod_is_inside(pod1, p1s, p1) || !spa_pod_is_inside(pod2, p2s, p2)) return -EINVAL; if ((res = spa_pod_compare(p1, p2)) != 0) return res; p1 = (const struct spa_pod*)spa_pod_next(p1); p2 = (const struct spa_pod*)spa_pod_next(p2); } break; } case SPA_TYPE_Object: { const struct spa_pod_prop *p1, *p2; const struct spa_pod_object *o1, *o2; o1 = (const struct spa_pod_object*)pod1; o2 = (const struct spa_pod_object*)pod2; p2 = NULL; SPA_POD_OBJECT_FOREACH(o1, p1) { if ((p2 = spa_pod_object_find_prop(o2, p2, p1->key)) == NULL) return 1; if ((res = spa_pod_compare(&p1->value, &p2->value)) != 0) return res; } p1 = NULL; SPA_POD_OBJECT_FOREACH(o2, p2) { if ((p1 = spa_pod_object_find_prop(o1, p1, p2->key)) == NULL) return -1; } break; } case SPA_TYPE_Array: res = spa_pod_memcmp(pod1, pod2); break; default: if (pod1->size != pod2->size) return -EINVAL; if (pod1->size < spa_pod_type_size(pod1->type)) return -EINVAL; res = spa_pod_compare_value(pod1->type, SPA_POD_BODY(pod1), SPA_POD_BODY(pod2), pod1->size); break; } return res; } SPA_API_POD_COMPARE int spa_pod_compare_is_compatible_flags(uint32_t type, const void *r1, const void *r2, uint32_t size SPA_UNUSED) { switch (type) { case SPA_TYPE_Int: return ((*(int32_t *) r1) & (*(int32_t *) r2)) != 0; case SPA_TYPE_Long: return ((*(int64_t *) r1) & (*(int64_t *) r2)) != 0; default: return -ENOTSUP; } return 0; } SPA_API_POD_COMPARE int spa_pod_compare_is_step_of(uint32_t type, const void *r1, const void *r2, uint32_t size) { switch (type) { case SPA_TYPE_Int: if (*(int32_t *)r2 < 1) return -EINVAL; return *(int32_t *) r1 % *(int32_t *) r2 == 0; case SPA_TYPE_Long: if (*(int64_t *)r2 < 1) return -EINVAL; return *(int64_t *) r1 % *(int64_t *) r2 == 0; case SPA_TYPE_Rectangle: { const struct spa_rectangle *rec1 = (struct spa_rectangle *) r1, *rec2 = (struct spa_rectangle *) r2; if (rec2->width < 1 || rec2->height < 1) return -EINVAL; return (rec1->width % rec2->width == 0 && rec1->height % rec2->height == 0); } default: return -ENOTSUP; } return 0; } SPA_API_POD_COMPARE int spa_pod_compare_is_in_range(uint32_t type, const void *v, const void *min, const void *max, const void *step, uint32_t size SPA_UNUSED) { if (spa_pod_compare_value(type, v, min, size) < 0 || spa_pod_compare_value(type, v, max, size) > 0) return 0; if (step != NULL) return spa_pod_compare_is_step_of(type, v, step, size); return 1; } SPA_API_POD_COMPARE int spa_pod_compare_is_valid_choice(uint32_t type, uint32_t size, const void *val, const void *vals, uint32_t n_vals, uint32_t choice) { switch (choice) { case SPA_CHOICE_None: if (spa_pod_compare_value(type, val, vals, size) == 0) return 1; return 0; case SPA_CHOICE_Enum: { const void *next = vals; for (uint32_t i = 1; i < n_vals; i++) { next = SPA_PTROFF(next, size, void); if (spa_pod_compare_value(type, val, next, size) == 0) return 1; } return 0; } case SPA_CHOICE_Range: case SPA_CHOICE_Step: { void *min = SPA_PTROFF(vals,size,void); void *max = SPA_PTROFF(min,size,void); void *step = choice == SPA_CHOICE_Step ? SPA_PTROFF(max,size,void) : NULL; return spa_pod_compare_is_in_range(type, val, min, max, step, size); } case SPA_CHOICE_Flags: return 1; } return 0; } /** * \} */ #ifdef __cplusplus } #endif #endif pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/pod/dynamic.h000066400000000000000000000045571511204443500260610ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_POD_DYNAMIC_H #define SPA_POD_DYNAMIC_H #include #include #ifdef __cplusplus extern "C" { #endif #ifndef SPA_API_POD_DYNAMIC #ifdef SPA_API_IMPL #define SPA_API_POD_DYNAMIC SPA_API_IMPL #else #define SPA_API_POD_DYNAMIC static inline #endif #endif struct spa_pod_dynamic_builder { struct spa_pod_builder b; void *data; uint32_t extend; uint32_t _padding; }; static int spa_pod_dynamic_builder_overflow(void *data, uint32_t size) { struct spa_pod_dynamic_builder *d = (struct spa_pod_dynamic_builder*)data; int32_t old_size = d->b.size; int32_t new_size = SPA_ROUND_UP_N(size, d->extend); void *old_data = d->b.data, *new_data; if (old_data == d->data) d->b.data = NULL; if ((new_data = realloc(d->b.data, new_size)) == NULL) return -errno; if (old_data == d->data && new_data != old_data && old_size > 0) memcpy(new_data, old_data, old_size); d->b.data = new_data; d->b.size = new_size; return 0; } SPA_API_POD_DYNAMIC void spa_pod_dynamic_builder_init(struct spa_pod_dynamic_builder *builder, void *data, uint32_t size, uint32_t extend) { static const struct spa_pod_builder_callbacks spa_pod_dynamic_builder_callbacks = { .version = SPA_VERSION_POD_BUILDER_CALLBACKS, .overflow = spa_pod_dynamic_builder_overflow }; builder->b = SPA_POD_BUILDER_INIT(data, size); if (extend > 0) spa_pod_builder_set_callbacks(&builder->b, &spa_pod_dynamic_builder_callbacks, builder); builder->extend = extend; builder->data = data; } SPA_API_POD_DYNAMIC void spa_pod_dynamic_builder_continue(struct spa_pod_dynamic_builder *builder, struct spa_pod_builder *b) { uint32_t remain = b->state.offset >= b->size ? 0 : b->size - b->state.offset; spa_pod_dynamic_builder_init(builder, remain ? SPA_PTROFF(b->data, b->state.offset, void) : NULL, remain, b->callbacks.funcs == NULL ? 0 : 4096); } SPA_API_POD_DYNAMIC void spa_pod_dynamic_builder_clean(struct spa_pod_dynamic_builder *builder) { if (builder->data != builder->b.data) { free(builder->b.data); builder->b.data = NULL; } } SPA_DEFINE_AUTO_CLEANUP(spa_pod_dynamic_builder, struct spa_pod_dynamic_builder, { spa_pod_dynamic_builder_clean(thing); }) #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_POD_DYNAMIC_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/pod/event.h000066400000000000000000000016221511204443500255440ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_EVENT_H #define SPA_EVENT_H #include #ifdef __cplusplus extern "C" { #endif /** * \addtogroup spa_pod * \{ */ struct spa_event_body { struct spa_pod_object_body body; }; struct spa_event { struct spa_pod pod; struct spa_event_body body; }; #define SPA_EVENT_TYPE(ev) ((ev)->body.body.type) #define SPA_EVENT_ID(ev,type) (SPA_EVENT_TYPE(ev) == (type) ? \ (ev)->body.body.id : SPA_ID_INVALID) #define SPA_EVENT_INIT_FULL(t,size,type,id,...) ((t) \ { { (size), SPA_TYPE_Object }, \ { { (type), (id) }, ##__VA_ARGS__ } }) \ #define SPA_EVENT_INIT(type,id) \ SPA_EVENT_INIT_FULL(struct spa_event, \ sizeof(struct spa_event_body), type, id) /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_EVENT_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/pod/filter.h000066400000000000000000000302101511204443500257030ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_POD_FILTER_H #define SPA_POD_FILTER_H #include #include #include #include #include #include #include #include #include #include #ifdef __cplusplus extern "C" { #endif #ifndef SPA_API_POD_FILTER #ifdef SPA_API_IMPL #define SPA_API_POD_FILTER SPA_API_IMPL #else #define SPA_API_POD_FILTER static inline #endif #endif /** * \addtogroup spa_pod * \{ */ SPA_API_POD_FILTER int spa_pod_filter_flags_value(struct spa_pod_builder *b, uint32_t type, const void *r1, const void *r2, uint32_t size) { switch (type) { case SPA_TYPE_Int: { int32_t val; if (size < sizeof(int32_t)) return -EINVAL; val = (*(int32_t *) r1) & (*(int32_t *) r2); if (val == 0) return 0; spa_pod_builder_int(b, val); break; } case SPA_TYPE_Long: { int64_t val; if (size < sizeof(int64_t)) return -EINVAL; val = (*(int64_t *) r1) & (*(int64_t *) r2); if (val == 0) return 0; spa_pod_builder_long(b, val); break; } default: return -ENOTSUP; } return 1; } SPA_API_POD_FILTER int spa_pod_filter_prop(struct spa_pod_builder *b, const struct spa_pod_prop *p1, const struct spa_pod_prop *p2) { const struct spa_pod *v1, *v2; struct spa_pod_choice *nc, dummy; uint32_t j, k, nalt1, nalt2, nc_offs; void *alt1, *alt2, *a1, *a2; uint32_t type, size, p1c, p2c; struct spa_pod_frame f; int res, n_copied = 0; v1 = spa_pod_get_values(&p1->value, &nalt1, &p1c); v2 = spa_pod_get_values(&p2->value, &nalt2, &p2c); /* empty/invalid choices */ if (nalt1 < 1 || nalt2 < 1) return -EINVAL; alt1 = SPA_POD_BODY(v1); alt2 = SPA_POD_BODY(v2); type = v1->type; size = v1->size; /* incompatible property types */ if (type != v2->type || size != v2->size || p1->key != p2->key) return -EINVAL; /* start with copying the property */ spa_pod_builder_prop(b, p1->key, p1->flags & p2->flags); spa_pod_builder_push_choice(b, &f, SPA_CHOICE_None, 0); spa_zero(dummy); nc_offs = f.offset; /* start with an empty child and we will select a good default * below */ spa_pod_builder_child(b, size, type); /* we should prefer alt2 values but only if they are within the * range. Swap the order otherwise. */ if (!spa_pod_compare_is_valid_choice(type, size, alt2, alt2, nalt2, p2c)) { SPA_SWAP(alt2, alt1); SPA_SWAP(nalt2, nalt1); SPA_SWAP(p2c, p1c); } if ((p1c == SPA_CHOICE_None && p2c == SPA_CHOICE_None) || (p1c == SPA_CHOICE_None && p2c == SPA_CHOICE_Enum) || (p1c == SPA_CHOICE_Enum && p2c == SPA_CHOICE_None) || (p1c == SPA_CHOICE_Enum && p2c == SPA_CHOICE_Enum)) { /* copy all equal values. Start with alt2 so that they are prefered. */ for (j = 0, a2 = alt2; j < nalt2; j++, a2 = SPA_PTROFF(a2, size, void)) { for (k = 0, a1 = alt1; k < nalt1; k++, a1 = SPA_PTROFF(a1,size,void)) { if (spa_pod_compare_value(type, a1, a2, size) == 0) { if (n_copied++ == 0) spa_pod_builder_raw(b, a1, size); spa_pod_builder_raw(b, a1, size); } } } } else if ((p1c == SPA_CHOICE_None && p2c == SPA_CHOICE_Range) || (p1c == SPA_CHOICE_Enum && p2c == SPA_CHOICE_Range) || (p1c == SPA_CHOICE_None && p2c == SPA_CHOICE_Step) || (p1c == SPA_CHOICE_Enum && p2c == SPA_CHOICE_Step)) { void *min = SPA_PTROFF(alt2,size,void); void *max = SPA_PTROFF(min,size,void); void *step = p2c == SPA_CHOICE_Step ? SPA_PTROFF(max,size,void) : NULL; bool found_def = false; /* we should prefer the alt2 range default value but only if valid */ if (spa_pod_compare_value(type, alt2, min, size) >= 0 && spa_pod_compare_value(type, alt2, max, size) <= 0) { for (j = 0, a1 = alt1; j < nalt1; j++, a1 = SPA_PTROFF(a1,size,void)) { if (spa_pod_compare_value(type, a1, alt2, size) == 0) { /* it is in the enum, use as default then */ spa_pod_builder_raw(b, a1, size); found_def = true; break; } } } /* copy all values inside the range */ for (j = 0, a1 = alt1; j < nalt1; j++, a1 = SPA_PTROFF(a1,size,void)) { if ((res = spa_pod_compare_is_in_range(type, a1, min, max, step, size)) < 0) return res; if (res == 0) continue; if (n_copied++ == 0 && !found_def) spa_pod_builder_raw(b, a1, size); spa_pod_builder_raw(b, a1, size); } } else if ((p1c == SPA_CHOICE_Range && p2c == SPA_CHOICE_None) || (p1c == SPA_CHOICE_Range && p2c == SPA_CHOICE_Enum) || (p1c == SPA_CHOICE_Step && p2c == SPA_CHOICE_None) || (p1c == SPA_CHOICE_Step && p2c == SPA_CHOICE_Enum)) { void *min = SPA_PTROFF(alt1,size,void); void *max = SPA_PTROFF(min,size,void); void *step = p1c == SPA_CHOICE_Step ? SPA_PTROFF(max,size,void) : NULL; /* copy all values inside the range, this will automatically prefer * a valid alt2 value */ for (j = 0, a2 = alt2; j < nalt2; j++, a2 = SPA_PTROFF(a2,size,void)) { if ((res = spa_pod_compare_is_in_range(type, a2, min, max, step, size)) < 0) return res; if (res == 0) continue; if (n_copied++ == 0) spa_pod_builder_raw(b, a2, size); spa_pod_builder_raw(b, a2, size); } } else if ((p1c == SPA_CHOICE_Range && p2c == SPA_CHOICE_Range) || (p1c == SPA_CHOICE_Range && p2c == SPA_CHOICE_Step) || (p1c == SPA_CHOICE_Step && p2c == SPA_CHOICE_Range) || (p1c == SPA_CHOICE_Step && p2c == SPA_CHOICE_Step)) { void *min1 = SPA_PTROFF(alt1,size,void); void *max1 = SPA_PTROFF(min1,size,void); void *min2 = SPA_PTROFF(alt2,size,void); void *max2 = SPA_PTROFF(min2,size,void); /* max of min */ if (spa_pod_compare_value(type, min1, min2, size) < 0) min1 = min2; /* min of max */ if (spa_pod_compare_value(type, max2, max1, size) < 0) max1 = max2; /* reject impossible range */ if (spa_pod_compare_value(type, max1, min1, size) < 0) return -EINVAL; /* prefer alt2 if in new range */ a1 = alt2; if ((res = spa_pod_compare_is_in_range(type, a1, min1, max1, NULL, size)) < 0) return res; if (res == 0) { /* try alt1 otherwise */ a1 = alt1; if ((res = spa_pod_compare_is_in_range(type, a1, min1, max1, NULL, size)) < 0) return res; /* fall back to new min value then */ if (res == 0) a1 = min1; } spa_pod_builder_raw(b, a1, size); spa_pod_builder_raw(b, min1, size); spa_pod_builder_raw(b, max1, size); nc = (struct spa_pod_choice*)spa_pod_builder_deref_fallback(b, nc_offs, &dummy.pod); nc->body.type = SPA_CHOICE_Range; } else if ((p1c == SPA_CHOICE_None && p2c == SPA_CHOICE_Flags) || (p1c == SPA_CHOICE_Flags && p2c == SPA_CHOICE_None) || (p1c == SPA_CHOICE_Flags && p2c == SPA_CHOICE_Flags)) { if (spa_pod_filter_flags_value(b, type, alt1, alt2, size) != 1) return -EINVAL; nc = (struct spa_pod_choice*)spa_pod_builder_deref_fallback(b, nc_offs, &dummy.pod); nc->body.type = SPA_CHOICE_Flags; } else if (p1c == SPA_CHOICE_Range && p2c == SPA_CHOICE_Flags) return -ENOTSUP; else if (p1c == SPA_CHOICE_Enum && p2c == SPA_CHOICE_Flags) return -ENOTSUP; else if (p1c == SPA_CHOICE_Step && p2c == SPA_CHOICE_Flags) return -ENOTSUP; else if (p1c == SPA_CHOICE_Flags && p2c == SPA_CHOICE_Range) return -ENOTSUP; else if (p1c == SPA_CHOICE_Flags && p2c == SPA_CHOICE_Step) return -ENOTSUP; else if (p1c == SPA_CHOICE_Flags && p2c == SPA_CHOICE_Enum) return -ENOTSUP; nc = (struct spa_pod_choice*)spa_pod_builder_deref_fallback(b, nc_offs, &dummy.pod); if (nc->body.type == SPA_CHOICE_None) { if (n_copied == 0) { return -EINVAL; } else if (n_copied == 1) { /* we always copy the default value twice, so remove it * again when it was the only one added */ spa_pod_builder_remove(b, size); } else if (n_copied > 1) { nc->body.type = SPA_CHOICE_Enum; } } spa_pod_builder_pop(b, &f); return 0; } SPA_API_POD_FILTER int spa_pod_filter_part(struct spa_pod_builder *b, const struct spa_pod *pod, uint32_t pod_size, const struct spa_pod *filter, uint32_t filter_size) { const struct spa_pod *pp, *pf; int res = 0; pf = filter; SPA_POD_FOREACH(pod, pod_size, pp) { bool do_copy = false, do_advance = false; uint32_t filter_offset = 0; struct spa_pod_frame f; switch (pp->type) { case SPA_TYPE_Object: if (pf != NULL) { struct spa_pod_object *op = (struct spa_pod_object *) pp; struct spa_pod_object *of = (struct spa_pod_object *) pf; const struct spa_pod_prop *p1, *p2; if (pf->type != pp->type) return -EINVAL; spa_pod_builder_push_object(b, &f, op->body.type, op->body.id); p2 = NULL; SPA_POD_OBJECT_FOREACH(op, p1) { p2 = spa_pod_object_find_prop(of, p2, p1->key); if (p2 != NULL) res = spa_pod_filter_prop(b, p1, p2); else if (SPA_FLAG_IS_SET(p1->flags, SPA_POD_PROP_FLAG_MANDATORY)) res = -EINVAL; else if (!SPA_FLAG_IS_SET(p1->flags, SPA_POD_PROP_FLAG_DROP)) spa_pod_builder_raw_padded(b, p1, SPA_POD_PROP_SIZE(p1)); if (res < 0) break; } if (res >= 0) { p1 = NULL; SPA_POD_OBJECT_FOREACH(of, p2) { p1 = spa_pod_object_find_prop(op, p1, p2->key); if (p1 != NULL) continue; if (SPA_FLAG_IS_SET(p2->flags, SPA_POD_PROP_FLAG_MANDATORY)) res = -EINVAL; else if (!SPA_FLAG_IS_SET(p2->flags, SPA_POD_PROP_FLAG_DROP)) spa_pod_builder_raw_padded(b, p2, SPA_POD_PROP_SIZE(p2)); if (res < 0) break; } } spa_pod_builder_pop(b, &f); do_advance = true; } else do_copy = true; break; case SPA_TYPE_Struct: if (pf != NULL) { if (pf->type != pp->type) return -EINVAL; filter_offset = sizeof(struct spa_pod_struct); spa_pod_builder_push_struct(b, &f); res = spa_pod_filter_part(b, SPA_PTROFF(pp,filter_offset,const struct spa_pod), SPA_POD_SIZE(pp) - filter_offset, SPA_PTROFF(pf,filter_offset,const struct spa_pod), SPA_POD_SIZE(pf) - filter_offset); spa_pod_builder_pop(b, &f); do_advance = true; } else do_copy = true; break; default: if (pf != NULL) { if (spa_pod_memcmp(pp, pf) != 0) return -EINVAL; do_advance = true; } do_copy = true; break; } if (do_copy) spa_pod_builder_raw_padded(b, pp, SPA_POD_SIZE(pp)); if (do_advance) { pf = (const struct spa_pod*)spa_pod_next(pf); if (!spa_pod_is_inside(filter, filter_size, pf)) pf = NULL; } if (res < 0) break; } return res; } SPA_API_POD_FILTER int spa_pod_filter(struct spa_pod_builder *b, struct spa_pod **result, const struct spa_pod *pod, const struct spa_pod *filter) { int res; struct spa_pod_builder_state state; spa_return_val_if_fail(pod != NULL, -EINVAL); spa_return_val_if_fail(b != NULL, -EINVAL); spa_pod_builder_get_state(b, &state); if (filter == NULL) { res = spa_pod_builder_raw_padded(b, pod, SPA_POD_SIZE(pod)); } else { struct spa_pod_dynamic_builder db; spa_pod_dynamic_builder_continue(&db, b); res = spa_pod_filter_part(&db.b, pod, SPA_POD_SIZE(pod), filter, SPA_POD_SIZE(filter)); if (res >= 0) res = spa_pod_builder_raw_padded(b, db.b.data, db.b.state.offset); spa_pod_dynamic_builder_clean(&db); } if (res >= 0 && result) { *result = (struct spa_pod*)spa_pod_builder_deref(b, state.offset); if (*result == NULL) res = -ENOSPC; } return res; } SPA_API_POD_FILTER int spa_pod_filter_object_make(struct spa_pod_object *pod) { struct spa_pod_prop *res; int count = 0; SPA_POD_OBJECT_FOREACH(pod, res) { if (spa_pod_is_choice(&res->value) && !SPA_FLAG_IS_SET(res->flags, SPA_POD_PROP_FLAG_DONT_FIXATE)) { uint32_t nvals, choice; struct spa_pod *v = spa_pod_get_values(&res->value, &nvals, &choice); const void *vals = SPA_POD_BODY(v); if (nvals < 1) continue; if (spa_pod_compare_is_valid_choice(v->type, v->size, vals, vals, nvals, choice)) { ((struct spa_pod_choice*)&res->value)->body.type = SPA_CHOICE_None; count++; } } } return count; } SPA_API_POD_FILTER int spa_pod_filter_make(struct spa_pod *pod) { if (!spa_pod_is_object(pod)) return -EINVAL; return spa_pod_filter_object_make((struct spa_pod_object *)pod); } /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_POD_FILTER_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/pod/iter.h000066400000000000000000000242621511204443500253730ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_POD_ITER_H #define SPA_POD_ITER_H #include #include #include #include #ifdef __cplusplus extern "C" { #endif #ifndef SPA_API_POD_ITER #ifdef SPA_API_IMPL #define SPA_API_POD_ITER SPA_API_IMPL #else #define SPA_API_POD_ITER static inline #endif #endif /** * \addtogroup spa_pod * \{ */ SPA_API_POD_ITER bool spa_pod_is_inside(const void *pod, uint32_t size, const void *iter) { size_t remaining; return spa_ptr_type_inside(pod, size, iter, struct spa_pod, &remaining) && remaining >= SPA_POD_BODY_SIZE(iter); } SPA_API_POD_ITER void *spa_pod_next(const void *iter) { return SPA_PTROFF(iter, SPA_ROUND_UP_N(SPA_POD_SIZE(iter), SPA_POD_ALIGN), void); } SPA_API_POD_ITER struct spa_pod_prop *spa_pod_prop_first(const struct spa_pod_object_body *body) { return SPA_PTROFF(body, sizeof(struct spa_pod_object_body), struct spa_pod_prop); } SPA_API_POD_ITER bool spa_pod_prop_is_inside(const struct spa_pod_object_body *body, uint32_t size, const struct spa_pod_prop *iter) { size_t remaining; return spa_ptr_type_inside(body, size, iter, struct spa_pod_prop, &remaining) && remaining >= iter->value.size; } SPA_API_POD_ITER struct spa_pod_prop *spa_pod_prop_next(const struct spa_pod_prop *iter) { return SPA_PTROFF(iter, SPA_ROUND_UP_N(SPA_POD_PROP_SIZE(iter), SPA_POD_ALIGN), struct spa_pod_prop); } SPA_API_POD_ITER struct spa_pod_control *spa_pod_control_first(const struct spa_pod_sequence_body *body) { return SPA_PTROFF(body, sizeof(struct spa_pod_sequence_body), struct spa_pod_control); } SPA_API_POD_ITER bool spa_pod_control_is_inside(const struct spa_pod_sequence_body *body, uint32_t size, const struct spa_pod_control *iter) { size_t remaining; return spa_ptr_type_inside(body, size, iter, struct spa_pod_control, &remaining) && remaining >= iter->value.size; } SPA_API_POD_ITER struct spa_pod_control *spa_pod_control_next(const struct spa_pod_control *iter) { return SPA_PTROFF(iter, SPA_ROUND_UP_N(SPA_POD_CONTROL_SIZE(iter), SPA_POD_ALIGN), struct spa_pod_control); } #define SPA_POD_ARRAY_BODY_FOREACH(body, _size, iter) \ for ((iter) = (__typeof__(iter))SPA_PTROFF((body), sizeof(struct spa_pod_array_body), void); \ (body)->child.size > 0 && spa_ptrinside(body, _size, iter, (body)->child.size, NULL); \ (iter) = (__typeof__(iter))SPA_PTROFF((iter), (body)->child.size, void)) #define SPA_POD_ARRAY_FOREACH(obj, iter) \ SPA_POD_ARRAY_BODY_FOREACH(&(obj)->body, SPA_POD_BODY_SIZE(obj), iter) #define SPA_POD_CHOICE_BODY_FOREACH(body, _size, iter) \ for ((iter) = (__typeof__(iter))SPA_PTROFF((body), sizeof(struct spa_pod_choice_body), void); \ (body)->child.size > 0 && spa_ptrinside(body, _size, iter, (body)->child.size, NULL); \ (iter) = (__typeof__(iter))SPA_PTROFF((iter), (body)->child.size, void)) #define SPA_POD_CHOICE_FOREACH(obj, iter) \ SPA_POD_CHOICE_BODY_FOREACH(&(obj)->body, SPA_POD_BODY_SIZE(obj), iter) #define SPA_POD_FOREACH(pod, size, iter) \ for ((iter) = (pod); \ spa_pod_is_inside(pod, size, iter); \ (iter) = (__typeof__(iter))spa_pod_next(iter)) #define SPA_POD_STRUCT_FOREACH(obj, iter) \ SPA_POD_FOREACH(SPA_POD_STRUCT_BODY(obj), SPA_POD_BODY_SIZE(obj), iter) #define SPA_POD_OBJECT_BODY_FOREACH(body, size, iter) \ for ((iter) = spa_pod_prop_first(body); \ spa_pod_prop_is_inside(body, size, iter); \ (iter) = spa_pod_prop_next(iter)) #define SPA_POD_OBJECT_FOREACH(obj, iter) \ SPA_POD_OBJECT_BODY_FOREACH(&(obj)->body, SPA_POD_BODY_SIZE(obj), iter) #define SPA_POD_SEQUENCE_BODY_FOREACH(body, size, iter) \ for ((iter) = spa_pod_control_first(body); \ spa_pod_control_is_inside(body, size, iter); \ (iter) = spa_pod_control_next(iter)) #define SPA_POD_SEQUENCE_FOREACH(seq, iter) \ SPA_POD_SEQUENCE_BODY_FOREACH(&(seq)->body, SPA_POD_BODY_SIZE(seq), iter) SPA_API_POD_ITER void *spa_pod_from_data(void *data, size_t maxsize, off_t offset, size_t size) { struct spa_pod pod; const void *body; if (spa_pod_body_from_data(data, maxsize, offset, size, &pod, &body) < 0) return NULL; return SPA_PTROFF(data, offset, void); } SPA_API_POD_ITER int spa_pod_get_bool(const struct spa_pod *pod, bool *value) { return spa_pod_body_get_bool(pod, SPA_POD_BODY_CONST(pod), value); } SPA_API_POD_ITER int spa_pod_get_id(const struct spa_pod *pod, uint32_t *value) { return spa_pod_body_get_id(pod, SPA_POD_BODY_CONST(pod), value); } SPA_API_POD_ITER int spa_pod_get_int(const struct spa_pod *pod, int32_t *value) { return spa_pod_body_get_int(pod, SPA_POD_BODY_CONST(pod), value); } SPA_API_POD_ITER int spa_pod_get_long(const struct spa_pod *pod, int64_t *value) { return spa_pod_body_get_long(pod, SPA_POD_BODY_CONST(pod), value); } SPA_API_POD_ITER int spa_pod_get_float(const struct spa_pod *pod, float *value) { return spa_pod_body_get_float(pod, SPA_POD_BODY_CONST(pod), value); } SPA_API_POD_ITER int spa_pod_get_double(const struct spa_pod *pod, double *value) { return spa_pod_body_get_double(pod, SPA_POD_BODY_CONST(pod), value); } SPA_API_POD_ITER int spa_pod_get_string(const struct spa_pod *pod, const char **value) { return spa_pod_body_get_string(pod, SPA_POD_BODY_CONST(pod), value); } SPA_API_POD_ITER int spa_pod_copy_string(const struct spa_pod *pod, size_t maxlen, char *dest) { return spa_pod_body_copy_string(pod, SPA_POD_BODY_CONST(pod), dest, maxlen); } SPA_API_POD_ITER int spa_pod_get_bytes(const struct spa_pod *pod, const void **value, uint32_t *len) { return spa_pod_body_get_bytes(pod, SPA_POD_BODY_CONST(pod), value, len); } SPA_API_POD_ITER int spa_pod_get_pointer(const struct spa_pod *pod, uint32_t *type, const void **value) { return spa_pod_body_get_pointer(pod, SPA_POD_BODY_CONST(pod), type, value); } SPA_API_POD_ITER int spa_pod_get_fd(const struct spa_pod *pod, int64_t *value) { return spa_pod_body_get_fd(pod, SPA_POD_BODY_CONST(pod), value); } SPA_API_POD_ITER int spa_pod_get_rectangle(const struct spa_pod *pod, struct spa_rectangle *value) { return spa_pod_body_get_rectangle(pod, SPA_POD_BODY_CONST(pod), value); } SPA_API_POD_ITER int spa_pod_get_fraction(const struct spa_pod *pod, struct spa_fraction *value) { return spa_pod_body_get_fraction(pod, SPA_POD_BODY_CONST(pod), value); } SPA_API_POD_ITER void *spa_pod_get_array_full(const struct spa_pod *pod, uint32_t *n_values, uint32_t *val_size, uint32_t *val_type) { return (void*)spa_pod_body_get_array_values(pod, SPA_POD_BODY(pod), n_values, val_size, val_type); } SPA_API_POD_ITER void *spa_pod_get_array(const struct spa_pod *pod, uint32_t *n_values) { uint32_t size, type; return spa_pod_get_array_full(pod, n_values, &size, &type); } SPA_API_POD_ITER uint32_t spa_pod_copy_array_full(const struct spa_pod *pod, uint32_t type, uint32_t size, void *values, uint32_t max_values) { uint32_t n_values, val_size, val_type; const void *v = spa_pod_get_array_full(pod, &n_values, &val_size, &val_type); if (v == NULL || max_values == 0 || val_type != type || val_size != size) return 0; n_values = SPA_MIN(n_values, max_values); memcpy(values, v, val_size * n_values); return n_values; } #define spa_pod_copy_array(pod,type,values,max_values) \ spa_pod_copy_array_full(pod,type,sizeof(values[0]),values,max_values) SPA_API_POD_ITER struct spa_pod *spa_pod_get_values(const struct spa_pod *pod, uint32_t *n_vals, uint32_t *choice) { if (spa_pod_is_choice(pod)) { const struct spa_pod_choice *p = (const struct spa_pod_choice*)pod; uint32_t type, size; spa_pod_choice_body_get_values(p, SPA_POD_BODY_CONST(p), n_vals, choice, &size, &type); return (struct spa_pod*)&p->body.child; } else { *n_vals = pod->size < spa_pod_type_size(pod->type) ? 0 : 1; *choice = SPA_CHOICE_None; return (struct spa_pod*)pod; } } SPA_API_POD_ITER bool spa_pod_is_object_type(const struct spa_pod *pod, uint32_t type) { return (pod && spa_pod_is_object(pod) && SPA_POD_OBJECT_TYPE(pod) == type); } SPA_API_POD_ITER bool spa_pod_is_object_id(const struct spa_pod *pod, uint32_t id) { return (pod && spa_pod_is_object(pod) && SPA_POD_OBJECT_ID(pod) == id); } SPA_API_POD_ITER const struct spa_pod_prop *spa_pod_object_find_prop(const struct spa_pod_object *pod, const struct spa_pod_prop *start, uint32_t key) { const struct spa_pod_prop *first, *res; first = spa_pod_prop_first(&pod->body); start = start ? spa_pod_prop_next(start) : first; for (res = start; spa_pod_prop_is_inside(&pod->body, pod->pod.size, res); res = spa_pod_prop_next(res)) { if (res->key == key) return res; } for (res = first; res != start; res = spa_pod_prop_next(res)) { if (res->key == key) return res; } return NULL; } SPA_API_POD_ITER const struct spa_pod_prop *spa_pod_find_prop(const struct spa_pod *pod, const struct spa_pod_prop *start, uint32_t key) { if (!spa_pod_is_object(pod)) return NULL; return spa_pod_object_find_prop((const struct spa_pod_object *)pod, start, key); } SPA_API_POD_ITER int spa_pod_object_has_props(const struct spa_pod_object *pod) { struct spa_pod_prop *res; SPA_POD_OBJECT_FOREACH(pod, res) return 1; return 0; } SPA_API_POD_ITER int spa_pod_object_fixate(struct spa_pod_object *pod) { struct spa_pod_prop *res; SPA_POD_OBJECT_FOREACH(pod, res) { if (spa_pod_is_choice(&res->value) && !SPA_FLAG_IS_SET(res->flags, SPA_POD_PROP_FLAG_DONT_FIXATE)) ((struct spa_pod_choice*)&res->value)->body.type = SPA_CHOICE_None; } return 0; } SPA_API_POD_ITER int spa_pod_object_is_fixated(const struct spa_pod_object *pod) { struct spa_pod_prop *res; SPA_POD_OBJECT_FOREACH(pod, res) { if (spa_pod_is_choice(&res->value) && ((struct spa_pod_choice*)&res->value)->body.type != SPA_CHOICE_None) return 0; } return 1; } SPA_API_POD_ITER int spa_pod_fixate(struct spa_pod *pod) { if (!spa_pod_is_object(pod)) return -EINVAL; return spa_pod_object_fixate((struct spa_pod_object *)pod); } SPA_API_POD_ITER int spa_pod_is_fixated(const struct spa_pod *pod) { if (!spa_pod_is_object(pod)) return -EINVAL; return spa_pod_object_is_fixated((const struct spa_pod_object *)pod); } /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_POD_ITER_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/pod/parser.h000066400000000000000000000650671511204443500257340ustar00rootroot00000000000000/* Spa */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_POD_PARSER_H #define SPA_POD_PARSER_H #include #include #include #include #ifdef __cplusplus extern "C" { #endif #ifndef SPA_API_POD_PARSER #ifdef SPA_API_IMPL #define SPA_API_POD_PARSER SPA_API_IMPL #else #define SPA_API_POD_PARSER static inline #endif #endif /** * \addtogroup spa_pod * \{ */ struct spa_pod_parser_state { uint32_t offset; uint32_t flags; struct spa_pod_frame *frame; }; struct spa_pod_parser { const void *data; uint32_t size; uint32_t _padding; struct spa_pod_parser_state state; }; #define SPA_POD_PARSER_INIT(buffer,size) ((struct spa_pod_parser){ (buffer), (size), 0, {0,0,NULL}}) SPA_API_POD_PARSER void spa_pod_parser_init(struct spa_pod_parser *parser, const void *data, uint32_t size) { *parser = SPA_POD_PARSER_INIT(data, size); } SPA_API_POD_PARSER void spa_pod_parser_pod(struct spa_pod_parser *parser, const struct spa_pod *pod) { spa_pod_parser_init(parser, pod, SPA_POD_SIZE(pod)); } SPA_API_POD_PARSER void spa_pod_parser_init_pod_body(struct spa_pod_parser *parser, const struct spa_pod *pod, const void *body) { spa_pod_parser_init(parser, SPA_PTROFF(body, -sizeof(struct spa_pod), const struct spa_pod), pod->size + sizeof(struct spa_pod)); } SPA_API_POD_PARSER void spa_pod_parser_init_from_data(struct spa_pod_parser *parser, const void *data, uint32_t maxsize, uint32_t offset, uint32_t size) { size_t offs, sz; offs = SPA_MIN(offset, maxsize); sz = SPA_MIN(maxsize - offs, size); spa_pod_parser_init(parser, SPA_PTROFF(data, offs, void), sz); } SPA_API_POD_PARSER void spa_pod_parser_get_state(struct spa_pod_parser *parser, struct spa_pod_parser_state *state) { *state = parser->state; } SPA_API_POD_PARSER void spa_pod_parser_reset(struct spa_pod_parser *parser, struct spa_pod_parser_state *state) { parser->state = *state; } SPA_API_POD_PARSER int spa_pod_parser_read_header(struct spa_pod_parser *parser, uint32_t offset, uint32_t size, void *header, uint32_t header_size, uint32_t pod_offset, const void **body) { /* Cast to uint64_t to avoid wraparound. */ const uint64_t long_offset = (uint64_t)offset + header_size; if (long_offset <= size && (offset & 7) == 0) { struct spa_pod *pod; /* a barrier around the memcpy to make sure it is not moved around or * duplicated after the size check below. We need to work on shared * memory and so there could be updates happening while we read. */ SPA_BARRIER; memcpy(header, SPA_PTROFF(parser->data, offset, void), header_size); SPA_BARRIER; pod = SPA_PTROFF(header, pod_offset, struct spa_pod); /* Check that the size (rounded to the next multiple of 8) is in bounds. */ if (long_offset + SPA_ROUND_UP_N((uint64_t)pod->size, SPA_POD_ALIGN) <= size) { *body = SPA_PTROFF(parser->data, long_offset, void); return 0; } } return -EPIPE; } SPA_API_POD_PARSER struct spa_pod * spa_pod_parser_deref(struct spa_pod_parser *parser, uint32_t offset, uint32_t size) { struct spa_pod pod; const void *body; if (spa_pod_parser_read_header(parser, offset, size, &pod, sizeof(pod), 0, &body) < 0) return NULL; return SPA_PTROFF(body, -sizeof(pod), struct spa_pod); } SPA_API_POD_PARSER struct spa_pod *spa_pod_parser_frame(struct spa_pod_parser *parser, struct spa_pod_frame *frame) { return SPA_PTROFF(parser->data, frame->offset, struct spa_pod); } SPA_API_POD_PARSER void spa_pod_parser_push(struct spa_pod_parser *parser, struct spa_pod_frame *frame, const struct spa_pod *pod, uint32_t offset) { frame->pod = *pod; frame->offset = offset; frame->parent = parser->state.frame; frame->flags = parser->state.flags; parser->state.frame = frame; } SPA_API_POD_PARSER int spa_pod_parser_get_header(struct spa_pod_parser *parser, void *header, uint32_t header_size, uint32_t pod_offset, const void **body) { struct spa_pod_frame *f = parser->state.frame; uint32_t size = f ? f->offset + SPA_POD_SIZE(&f->pod) : parser->size; return spa_pod_parser_read_header(parser, parser->state.offset, size, header, header_size, pod_offset, body); } SPA_API_POD_PARSER int spa_pod_parser_current_body(struct spa_pod_parser *parser, struct spa_pod *pod, const void **body) { return spa_pod_parser_get_header(parser, pod, sizeof(struct spa_pod), 0, body); } SPA_API_POD_PARSER struct spa_pod *spa_pod_parser_current(struct spa_pod_parser *parser) { struct spa_pod pod; const void *body; if (spa_pod_parser_current_body(parser, &pod, &body) < 0) return NULL; return SPA_PTROFF(body, -sizeof(struct spa_pod), struct spa_pod); } SPA_API_POD_PARSER void spa_pod_parser_advance(struct spa_pod_parser *parser, const struct spa_pod *pod) { parser->state.offset += SPA_ROUND_UP_N(SPA_POD_SIZE(pod), SPA_POD_ALIGN); } SPA_API_POD_PARSER int spa_pod_parser_next_body(struct spa_pod_parser *parser, struct spa_pod *pod, const void **body) { if (spa_pod_parser_current_body(parser, pod, body) < 0) return -EINVAL; spa_pod_parser_advance(parser, pod); return 0; } SPA_API_POD_PARSER struct spa_pod *spa_pod_parser_next(struct spa_pod_parser *parser) { struct spa_pod pod; const void *body; if (spa_pod_parser_current_body(parser, &pod, &body) < 0) return NULL; spa_pod_parser_advance(parser, &pod); return SPA_PTROFF(body, -sizeof(struct spa_pod), struct spa_pod); } SPA_API_POD_PARSER void spa_pod_parser_restart(struct spa_pod_parser *parser, struct spa_pod_frame *frame) { parser->state.offset = frame->offset; } SPA_API_POD_PARSER void spa_pod_parser_unpush(struct spa_pod_parser *parser, struct spa_pod_frame *frame) { spa_pod_parser_restart(parser, frame); parser->state.frame = frame->parent; } SPA_API_POD_PARSER int spa_pod_parser_pop(struct spa_pod_parser *parser, struct spa_pod_frame *frame) { spa_pod_parser_unpush(parser, frame); spa_pod_parser_advance(parser, &frame->pod); return 0; } SPA_API_POD_PARSER int spa_pod_parser_get_bool(struct spa_pod_parser *parser, bool *value) { int res; struct spa_pod pod; const void *body; if ((res = spa_pod_parser_current_body(parser, &pod, &body)) < 0) return res; if ((res = spa_pod_body_get_bool(&pod, body, value)) >= 0) spa_pod_parser_advance(parser, &pod); return res; } SPA_API_POD_PARSER int spa_pod_parser_get_id(struct spa_pod_parser *parser, uint32_t *value) { int res; struct spa_pod pod; const void *body; if ((res = spa_pod_parser_current_body(parser, &pod, &body)) < 0) return res; if ((res = spa_pod_body_get_id(&pod, body, value)) >= 0) spa_pod_parser_advance(parser, &pod); return res; } SPA_API_POD_PARSER int spa_pod_parser_get_int(struct spa_pod_parser *parser, int32_t *value) { int res; struct spa_pod pod; const void *body; if ((res = spa_pod_parser_current_body(parser, &pod, &body)) < 0) return res; if ((res = spa_pod_body_get_int(&pod, body, value)) >= 0) spa_pod_parser_advance(parser, &pod); return res; } SPA_API_POD_PARSER int spa_pod_parser_get_long(struct spa_pod_parser *parser, int64_t *value) { int res; struct spa_pod pod; const void *body; if ((res = spa_pod_parser_current_body(parser, &pod, &body)) < 0) return res; if ((res = spa_pod_body_get_long(&pod, body, value)) >= 0) spa_pod_parser_advance(parser, &pod); return res; } SPA_API_POD_PARSER int spa_pod_parser_get_float(struct spa_pod_parser *parser, float *value) { int res; struct spa_pod pod; const void *body; if ((res = spa_pod_parser_current_body(parser, &pod, &body)) < 0) return res; if ((res = spa_pod_body_get_float(&pod, body, value)) >= 0) spa_pod_parser_advance(parser, &pod); return res; } SPA_API_POD_PARSER int spa_pod_parser_get_double(struct spa_pod_parser *parser, double *value) { int res; struct spa_pod pod; const void *body; if ((res = spa_pod_parser_current_body(parser, &pod, &body)) < 0) return res; if ((res = spa_pod_body_get_double(&pod, body, value)) >= 0) spa_pod_parser_advance(parser, &pod); return res; } SPA_API_POD_PARSER int spa_pod_parser_get_string(struct spa_pod_parser *parser, const char **value) { int res; struct spa_pod pod; const void *body; if ((res = spa_pod_parser_current_body(parser, &pod, &body)) < 0) return res; if ((res = spa_pod_body_get_string(&pod, body, value)) >= 0) spa_pod_parser_advance(parser, &pod); return res; } SPA_API_POD_PARSER int spa_pod_parser_get_bytes(struct spa_pod_parser *parser, const void **value, uint32_t *len) { int res; struct spa_pod pod; const void *body; if ((res = spa_pod_parser_current_body(parser, &pod, &body)) < 0) return res; if ((res = spa_pod_body_get_bytes(&pod, body, value, len)) >= 0) spa_pod_parser_advance(parser, &pod); return res; } SPA_API_POD_PARSER int spa_pod_parser_get_pointer(struct spa_pod_parser *parser, uint32_t *type, const void **value) { int res; struct spa_pod pod; const void *body; if ((res = spa_pod_parser_current_body(parser, &pod, &body)) < 0) return res; if ((res = spa_pod_body_get_pointer(&pod, body, type, value)) >= 0) spa_pod_parser_advance(parser, &pod); return res; } SPA_API_POD_PARSER int spa_pod_parser_get_fd(struct spa_pod_parser *parser, int64_t *value) { int res; struct spa_pod pod; const void *body; if ((res = spa_pod_parser_current_body(parser, &pod, &body)) < 0) return res; if ((res = spa_pod_body_get_fd(&pod, body, value)) >= 0) spa_pod_parser_advance(parser, &pod); return res; } SPA_API_POD_PARSER int spa_pod_parser_get_rectangle(struct spa_pod_parser *parser, struct spa_rectangle *value) { int res; struct spa_pod pod; const void *body; if ((res = spa_pod_parser_current_body(parser, &pod, &body)) < 0) return res; if ((res = spa_pod_body_get_rectangle(&pod, body, value)) >= 0) spa_pod_parser_advance(parser, &pod); return res; } SPA_API_POD_PARSER int spa_pod_parser_get_fraction(struct spa_pod_parser *parser, struct spa_fraction *value) { int res; struct spa_pod pod; const void *body; if ((res = spa_pod_parser_current_body(parser, &pod, &body)) < 0) return res; if ((res = spa_pod_body_get_fraction(&pod, body, value)) >= 0) spa_pod_parser_advance(parser, &pod); return res; } SPA_API_POD_PARSER int spa_pod_parser_get_pod_body(struct spa_pod_parser *parser, struct spa_pod *value, const void **body) { int res; if ((res = spa_pod_parser_current_body(parser, value, body)) < 0) return res; spa_pod_parser_advance(parser, value); return 0; } SPA_API_POD_PARSER int spa_pod_parser_get_pod(struct spa_pod_parser *parser, struct spa_pod **value) { int res; struct spa_pod pod; const void *body; if ((res = spa_pod_parser_get_pod_body(parser, &pod, &body)) < 0) return res; *value = SPA_PTROFF(body, -sizeof(struct spa_pod), struct spa_pod); return 0; } SPA_API_POD_PARSER int spa_pod_parser_init_struct_body(struct spa_pod_parser *parser, struct spa_pod_frame *frame, const struct spa_pod *pod, const void *body) { if (!spa_pod_is_struct(pod)) return -EINVAL; spa_pod_parser_init_pod_body(parser, pod, body); spa_pod_parser_push(parser, frame, pod, parser->state.offset); parser->state.offset += sizeof(struct spa_pod_struct); return 0; } SPA_API_POD_PARSER int spa_pod_parser_push_struct_body(struct spa_pod_parser *parser, struct spa_pod_frame *frame, struct spa_pod *str, const void **str_body) { int res; if ((res = spa_pod_parser_current_body(parser, str, str_body)) < 0) return res; if (!spa_pod_is_struct(str)) return -EINVAL; spa_pod_parser_push(parser, frame, str, parser->state.offset); parser->state.offset += sizeof(struct spa_pod_struct); return 0; } SPA_API_POD_PARSER int spa_pod_parser_push_struct(struct spa_pod_parser *parser, struct spa_pod_frame *frame) { struct spa_pod pod; const void *body; return spa_pod_parser_push_struct_body(parser, frame, &pod, &body); } SPA_API_POD_PARSER int spa_pod_parser_init_object_body(struct spa_pod_parser *parser, struct spa_pod_frame *frame, const struct spa_pod *pod, const void *body, struct spa_pod_object *object, const void **object_body) { int res; if (!spa_pod_is_object(pod)) return -EINVAL; spa_pod_parser_init_pod_body(parser, pod, body); if ((res = spa_pod_body_get_object(pod, body, object, object_body)) < 0) return res; spa_pod_parser_push(parser, frame, pod, parser->state.offset); parser->state.offset += sizeof(struct spa_pod_object); return 0; } SPA_API_POD_PARSER int spa_pod_parser_push_object_body(struct spa_pod_parser *parser, struct spa_pod_frame *frame, struct spa_pod_object *object, const void **object_body) { int res; struct spa_pod pod; const void *body; if ((res = spa_pod_parser_current_body(parser, &pod, &body)) < 0) return res; if ((res = spa_pod_body_get_object(&pod, body, object, object_body)) < 0) return res; spa_pod_parser_push(parser, frame, &pod, parser->state.offset); parser->state.offset += sizeof(struct spa_pod_object); return 0; } SPA_API_POD_PARSER int spa_pod_parser_push_object(struct spa_pod_parser *parser, struct spa_pod_frame *frame, uint32_t type, uint32_t *id) { int res; struct spa_pod_object obj; const void *obj_body; if ((res = spa_pod_parser_push_object_body(parser, frame, &obj, &obj_body)) < 0) return res; if (type != obj.body.type) { spa_pod_parser_unpush(parser, frame); return -EPROTO; } if (id != NULL) *id = obj.body.id; return 0; } SPA_API_POD_PARSER int spa_pod_parser_get_prop_body(struct spa_pod_parser *parser, struct spa_pod_prop *prop, const void **body) { int res; if ((res = spa_pod_parser_get_header(parser, prop, sizeof(struct spa_pod_prop), offsetof(struct spa_pod_prop, value), body)) >= 0) parser->state.offset += SPA_ROUND_UP_N(SPA_POD_PROP_SIZE(prop), SPA_POD_ALIGN); return res; } SPA_API_POD_PARSER int spa_pod_parser_push_sequence_body(struct spa_pod_parser *parser, struct spa_pod_frame *frame, struct spa_pod_sequence *seq, const void **seq_body) { int res; struct spa_pod pod; const void *body; if ((res = spa_pod_parser_current_body(parser, &pod, &body)) < 0) return res; if ((res = spa_pod_body_get_sequence(&pod, body, seq, seq_body)) < 0) return res; spa_pod_parser_push(parser, frame, &pod, parser->state.offset); parser->state.offset += sizeof(struct spa_pod_sequence); return 0; } SPA_API_POD_PARSER int spa_pod_parser_get_control_body(struct spa_pod_parser *parser, struct spa_pod_control *control, const void **body) { int res; if ((res = spa_pod_parser_get_header(parser, control, sizeof(struct spa_pod_control), offsetof(struct spa_pod_control, value), body)) >= 0) parser->state.offset += SPA_ROUND_UP_N(SPA_POD_CONTROL_SIZE(control), SPA_POD_ALIGN); return res; } SPA_API_POD_PARSER int spa_pod_parser_object_find_prop(struct spa_pod_parser *parser, uint32_t key, struct spa_pod_prop *prop, const void **body) { uint32_t start_offset; struct spa_pod_frame *f = parser->state.frame; if (f == NULL || f->pod.type != SPA_TYPE_Object) return -EINVAL; start_offset = f->offset; while (spa_pod_parser_get_prop_body(parser, prop, body) >= 0) { if (prop->key == key) return 0; } spa_pod_parser_restart(parser, f); parser->state.offset += sizeof(struct spa_pod_object); while (parser->state.offset != start_offset && spa_pod_parser_get_prop_body(parser, prop, body) >= 0) { if (prop->key == key) return 0; } *body = NULL; return -ENOENT; } SPA_API_POD_PARSER bool spa_pod_parser_body_can_collect(const struct spa_pod *pod, const void *body, char type) { struct spa_pod_choice choice; if (pod == NULL) return false; if (pod->type == SPA_TYPE_Choice) { if (!spa_pod_is_choice(pod)) return false; if (type == 'V' || type == 'W') return true; if (spa_pod_body_get_choice(pod, body, &choice, &body) < 0) return false; if (choice.body.type != SPA_CHOICE_None) return false; pod = &choice.body.child; } switch (type) { case 'P': case 'Q': return true; case 'b': return spa_pod_is_bool(pod); case 'I': return spa_pod_is_id(pod); case 'i': return spa_pod_is_int(pod); case 'l': return spa_pod_is_long(pod); case 'f': return spa_pod_is_float(pod); case 'd': return spa_pod_is_double(pod); case 's': return spa_pod_is_string(pod) || spa_pod_is_none(pod); case 'S': return spa_pod_is_string(pod); case 'y': return spa_pod_is_bytes(pod); case 'R': return spa_pod_is_rectangle(pod); case 'F': return spa_pod_is_fraction(pod); case 'B': return spa_pod_is_bitmap(pod); case 'a': return spa_pod_is_array(pod); case 'p': return spa_pod_is_pointer(pod); case 'h': return spa_pod_is_fd(pod); case 'T': case 'U': return spa_pod_is_struct(pod) || spa_pod_is_none(pod); case 'N': case 'O': return spa_pod_is_object(pod) || spa_pod_is_none(pod); case 'V': case 'W': default: return false; } } SPA_API_POD_PARSER bool spa_pod_parser_can_collect(const struct spa_pod *pod, char type) { return spa_pod_parser_body_can_collect(pod, SPA_POD_BODY_CONST(pod), type); } #define SPA_POD_PARSER_COLLECT_BODY(_pod,_body,_type,args) \ ({ \ int res = 0; \ struct spa_pod_choice choice; \ const struct spa_pod *_p = _pod; \ const void *_b = _body; \ if (_p->type == SPA_TYPE_Choice && _type != 'V' && _type != 'W') { \ if (spa_pod_body_get_choice(_p, _b, &choice, &_b) >= 0 && \ choice.body.type == SPA_CHOICE_None) \ _p = &choice.body.child; \ } \ switch (_type) { \ case 'b': \ { \ bool *val = va_arg(args, bool*); \ res = spa_pod_body_get_bool(_p, _b, val); \ break; \ } \ case 'I': \ { \ uint32_t *val = va_arg(args, uint32_t*); \ res = spa_pod_body_get_id(_p, _b, val); \ break; \ } \ case 'i': \ { \ int32_t *val = va_arg(args, int32_t*); \ res = spa_pod_body_get_int(_p, _b, val); \ break; \ } \ case 'l': \ { \ int64_t *val = va_arg(args, int64_t*); \ res = spa_pod_body_get_long(_p, _b, val); \ break; \ } \ case 'f': \ { \ float *val = va_arg(args, float*); \ res = spa_pod_body_get_float(_p, _b, val); \ break; \ } \ case 'd': \ { \ double *val = va_arg(args, double*); \ res = spa_pod_body_get_double(_p, _b, val); \ break; \ } \ case 's': \ { \ const char **dest = va_arg(args, const char**); \ if (_p->type == SPA_TYPE_None) \ *dest = NULL; \ else \ res = spa_pod_body_get_string(_p, _b, dest); \ break; \ } \ case 'S': \ { \ char *dest = va_arg(args, char*); \ uint32_t maxlen = va_arg(args, uint32_t); \ res = spa_pod_body_copy_string(_p, _b, dest, maxlen); \ break; \ } \ case 'y': \ { \ const void **value = va_arg(args, const void**); \ uint32_t *len = va_arg(args, uint32_t*); \ res = spa_pod_body_get_bytes(_p, _b, value, len); \ break; \ } \ case 'R': \ { \ struct spa_rectangle *val = va_arg(args, struct spa_rectangle*); \ res = spa_pod_body_get_rectangle(_p, _b, val); \ break; \ } \ case 'F': \ { \ struct spa_fraction *val = va_arg(args, struct spa_fraction*); \ res = spa_pod_body_get_fraction(_p, _b, val); \ break; \ } \ case 'B': \ { \ const uint8_t **val = va_arg(args, const uint8_t**); \ res = spa_pod_body_get_bitmap(_p, _b, val); \ break; \ } \ case 'a': \ { \ uint32_t *val_size = va_arg(args, uint32_t*); \ uint32_t *val_type = va_arg(args, uint32_t*); \ uint32_t *n_values = va_arg(args, uint32_t*); \ const void **arr_body = va_arg(args, const void**); \ *arr_body = spa_pod_body_get_array_values(_p, _b, \ n_values, val_size, val_type); \ if (*arr_body == NULL) \ res = -EINVAL; \ break; \ } \ case 'p': \ { \ uint32_t *type = va_arg(args, uint32_t*); \ const void **value = va_arg(args, const void**); \ res = spa_pod_body_get_pointer(_p, _b, type, value); \ break; \ } \ case 'h': \ { \ int64_t *val = va_arg(args, int64_t*); \ res = spa_pod_body_get_fd(_p, _b, val); \ break; \ } \ default: \ { \ bool valid = false, do_body = false; \ switch (_type) { \ case 'Q': \ do_body = true; \ SPA_FALLTHROUGH; \ case 'P': \ valid = true; \ break; \ case 'U': \ do_body = true; \ SPA_FALLTHROUGH; \ case 'T': \ valid = spa_pod_is_struct(_p) || spa_pod_is_none(_p); \ break; \ case 'N': \ do_body = true; \ SPA_FALLTHROUGH; \ case 'O': \ valid = spa_pod_is_object(_p) || spa_pod_is_none(_p); \ break; \ case 'W': \ do_body = true; \ SPA_FALLTHROUGH; \ case 'V': \ valid = spa_pod_is_choice(_p) || spa_pod_is_none(_p); \ break; \ default: \ res = -EINVAL; \ break; \ } \ if (res >= 0 && do_body) { \ struct spa_pod *p = va_arg(args, struct spa_pod*); \ const void **v = va_arg(args, const void **); \ if (valid && p && v) { \ *p = *_p; \ *v = _b; \ } \ } else if (res >= 0) { \ const struct spa_pod **d = va_arg(args, const struct spa_pod**);\ if (valid && d) \ *d = (_p->type == SPA_TYPE_None) ? \ NULL : \ SPA_PTROFF((_b), -sizeof(struct spa_pod), \ const struct spa_pod); \ } \ if (!valid) \ res = -EINVAL; \ break; \ } \ } \ res; \ }) #define SPA_POD_PARSER_COLLECT(pod,_type,args) \ SPA_POD_PARSER_COLLECT_BODY(pod, SPA_POD_BODY_CONST(pod),_type,args) #define SPA_POD_PARSER_SKIP(_type,args) \ do { \ switch (_type) { \ case 'S': \ va_arg(args, char*); \ va_arg(args, uint32_t); \ break; \ case 'a': \ va_arg(args, void*); \ va_arg(args, void*); \ SPA_FALLTHROUGH \ case 'p': \ case 'y': \ va_arg(args, void*); \ SPA_FALLTHROUGH \ case 'b': \ case 'I': \ case 'i': \ case 'l': \ case 'f': \ case 'd': \ case 's': \ case 'R': \ case 'F': \ case 'B': \ case 'h': \ case 'V': \ case 'P': \ case 'T': \ case 'O': \ case 'W': \ case 'Q': \ case 'U': \ case 'N': \ va_arg(args, void*); \ break; \ } \ } while(false) SPA_API_POD_PARSER int spa_pod_parser_getv(struct spa_pod_parser *parser, va_list args) { struct spa_pod_frame *f = parser->state.frame; int count = 0; if (f == NULL) return -EINVAL; do { bool optional; struct spa_pod pod = (struct spa_pod) { 0, SPA_TYPE_None }; const void *body = NULL; const char *format; struct spa_pod_prop prop; if (f->pod.type == SPA_TYPE_Object) { uint32_t key = va_arg(args, uint32_t), *flags = NULL; if (key == 0) break; if (key == SPA_ID_INVALID) { key = va_arg(args, uint32_t); flags = va_arg(args, uint32_t*); } if (spa_pod_parser_object_find_prop(parser, key, &prop, &body) >= 0) { pod = prop.value; if (flags) *flags = prop.flags; } } if ((format = va_arg(args, char *)) == NULL) break; if (f->pod.type == SPA_TYPE_Struct) spa_pod_parser_next_body(parser, &pod, &body); if ((optional = (*format == '?'))) format++; if (SPA_POD_PARSER_COLLECT_BODY(&pod, body, *format, args) >= 0) { count++; } else if (!optional) { if (body == NULL) return -ESRCH; else return -EPROTO; } } while (true); return count; } SPA_API_POD_PARSER int spa_pod_parser_get(struct spa_pod_parser *parser, ...) { int res; va_list args; va_start(args, parser); res = spa_pod_parser_getv(parser, args); va_end(args); return res; } #define SPA_POD_OPT_Bool(val) "?" SPA_POD_Bool(val) #define SPA_POD_OPT_Id(val) "?" SPA_POD_Id(val) #define SPA_POD_OPT_Int(val) "?" SPA_POD_Int(val) #define SPA_POD_OPT_Long(val) "?" SPA_POD_Long(val) #define SPA_POD_OPT_Float(val) "?" SPA_POD_Float(val) #define SPA_POD_OPT_Double(val) "?" SPA_POD_Double(val) #define SPA_POD_OPT_String(val) "?" SPA_POD_String(val) #define SPA_POD_OPT_Stringn(val,len) "?" SPA_POD_Stringn(val,len) #define SPA_POD_OPT_Bytes(val,len) "?" SPA_POD_Bytes(val,len) #define SPA_POD_OPT_Rectangle(val) "?" SPA_POD_Rectangle(val) #define SPA_POD_OPT_Fraction(val) "?" SPA_POD_Fraction(val) #define SPA_POD_OPT_Array(csize,ctype,n_vals,vals) "?" SPA_POD_Array(csize,ctype,n_vals,vals) #define SPA_POD_OPT_Pointer(type,val) "?" SPA_POD_Pointer(type,val) #define SPA_POD_OPT_Fd(val) "?" SPA_POD_Fd(val) #define SPA_POD_OPT_Pod(val) "?" SPA_POD_Pod(val) #define SPA_POD_OPT_PodObject(val) "?" SPA_POD_PodObject(val) #define SPA_POD_OPT_PodStruct(val) "?" SPA_POD_PodStruct(val) #define SPA_POD_OPT_PodChoice(val) "?" SPA_POD_PodChoice(val) #define SPA_POD_OPT_PodBody(val,body) "?" SPA_POD_PodBody(val,body) #define SPA_POD_OPT_PodBodyObject(val,body) "?" SPA_POD_PodBodyObject(val,body) #define SPA_POD_OPT_PodBodyStruct(val,body) "?" SPA_POD_PodBodyStruct(val,body) #define SPA_POD_OPT_PodBodyChoice(val,body) "?" SPA_POD_PodBodyChoice(val,body) #define spa_pod_parser_get_object(p,type,id,...) \ ({ \ struct spa_pod_frame _f; \ int _res; \ if ((_res = spa_pod_parser_push_object(p, &_f, type, id)) == 0) { \ _res = spa_pod_parser_get(p,##__VA_ARGS__, 0); \ spa_pod_parser_pop(p, &_f); \ } \ _res; \ }) #define spa_pod_parser_get_struct(p,...) \ ({ \ struct spa_pod_frame _f; \ int _res; \ if ((_res = spa_pod_parser_push_struct(p, &_f)) == 0) { \ _res = spa_pod_parser_get(p,##__VA_ARGS__, NULL); \ spa_pod_parser_pop(p, &_f); \ } \ _res; \ }) #define spa_pod_body_parse_object(pod,body,type,id,...) \ ({ \ struct spa_pod_parser _p; \ spa_pod_parser_init_pod_body(&_p, pod, body); \ spa_pod_parser_get_object(&_p,type,id,##__VA_ARGS__); \ }) #define spa_pod_parse_object(pod,type,id,...) \ spa_pod_body_parse_object(pod,SPA_POD_BODY_CONST(pod),type,id,##__VA_ARGS__) #define spa_pod_body_parse_struct(pod,body,...) \ ({ \ struct spa_pod_parser _p; \ spa_pod_parser_init_pod_body(&_p, pod, body); \ spa_pod_parser_get_struct(&_p,##__VA_ARGS__); \ }) #define spa_pod_parse_struct(pod,...) \ spa_pod_body_parse_struct(pod,SPA_POD_BODY_CONST(pod),##__VA_ARGS__) /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_POD_PARSER_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/pod/pod.h000066400000000000000000000156631511204443500252170ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_POD_H #define SPA_POD_H #include #include #ifdef __cplusplus extern "C" { #endif /** * \addtogroup spa_pod * \{ */ #define SPA_POD_ALIGN 8 #define SPA_POD_MAX_SIZE (1u<<20) #define SPA_POD_BODY_SIZE(pod) (((struct spa_pod*)(pod))->size) #define SPA_POD_TYPE(pod) (((struct spa_pod*)(pod))->type) #define SPA_POD_SIZE(pod) ((uint64_t)sizeof(struct spa_pod) + SPA_POD_BODY_SIZE(pod)) #define SPA_POD_CONTENTS_SIZE(type,pod) (SPA_POD_SIZE(pod)-sizeof(type)) #define SPA_POD_CONTENTS(type,pod) SPA_PTROFF((pod),sizeof(type),void) #define SPA_POD_CONTENTS_CONST(type,pod) SPA_PTROFF((pod),sizeof(type),const void) #define SPA_POD_BODY(pod) SPA_PTROFF((pod),sizeof(struct spa_pod),void) #define SPA_POD_BODY_CONST(pod) SPA_PTROFF((pod),sizeof(struct spa_pod),const void) #define SPA_POD_IS_VALID(pod) \ (SPA_POD_BODY_SIZE(pod) < SPA_POD_MAX_SIZE) #define SPA_POD_CHECK_TYPE(pod,_type) \ (SPA_POD_IS_VALID(pod) && \ (pod)->type == (_type)) #define SPA_POD_CHECK(pod,_type,_size) \ (SPA_POD_CHECK_TYPE(pod,_type) && (pod)->size >= (_size)) struct spa_pod { uint32_t size; /* size of the body */ uint32_t type; /* a basic id of enum spa_type */ }; #define SPA_POD_VALUE(type,pod) (((type*)(pod))->value) struct spa_pod_bool { struct spa_pod pod; int32_t value; int32_t _padding; }; struct spa_pod_id { struct spa_pod pod; uint32_t value; int32_t _padding; }; struct spa_pod_int { struct spa_pod pod; int32_t value; int32_t _padding; }; struct spa_pod_long { struct spa_pod pod; int64_t value; }; struct spa_pod_float { struct spa_pod pod; float value; int32_t _padding; }; struct spa_pod_double { struct spa_pod pod; double value; }; struct spa_pod_string { struct spa_pod pod; /* value here */ }; struct spa_pod_bytes { struct spa_pod pod; /* value here */ }; struct spa_pod_rectangle { struct spa_pod pod; struct spa_rectangle value; }; struct spa_pod_fraction { struct spa_pod pod; struct spa_fraction value; }; struct spa_pod_bitmap { struct spa_pod pod; /* array of uint8_t follows with the bitmap */ }; #define SPA_POD_ARRAY_CHILD(arr) (&((struct spa_pod_array*)(arr))->body.child) #define SPA_POD_ARRAY_VALUE_TYPE(arr) (SPA_POD_ARRAY_CHILD(arr)->type) #define SPA_POD_ARRAY_VALUE_SIZE(arr) (SPA_POD_ARRAY_CHILD(arr)->size) #define SPA_POD_ARRAY_N_VALUES(arr) (SPA_POD_ARRAY_VALUE_SIZE(arr) ? ((SPA_POD_BODY_SIZE(arr) - sizeof(struct spa_pod_array_body)) / SPA_POD_ARRAY_VALUE_SIZE(arr)) : 0) #define SPA_POD_ARRAY_VALUES(arr) SPA_POD_CONTENTS(struct spa_pod_array, arr) struct spa_pod_array_body { struct spa_pod child; /* array with elements of child.size follows */ }; struct spa_pod_array { struct spa_pod pod; struct spa_pod_array_body body; }; #define SPA_POD_CHOICE_CHILD(choice) (&((struct spa_pod_choice*)(choice))->body.child) #define SPA_POD_CHOICE_TYPE(choice) (((struct spa_pod_choice*)(choice))->body.type) #define SPA_POD_CHOICE_FLAGS(choice) (((struct spa_pod_choice*)(choice))->body.flags) #define SPA_POD_CHOICE_VALUE_TYPE(choice) (SPA_POD_CHOICE_CHILD(choice)->type) #define SPA_POD_CHOICE_VALUE_SIZE(choice) (SPA_POD_CHOICE_CHILD(choice)->size) #define SPA_POD_CHOICE_N_VALUES(choice) (SPA_POD_CHOICE_VALUE_SIZE(choice) ? ((SPA_POD_BODY_SIZE(choice) - sizeof(struct spa_pod_choice_body)) / SPA_POD_CHOICE_VALUE_SIZE(choice)) : 0) #define SPA_POD_CHOICE_VALUES(choice) (SPA_POD_CONTENTS(struct spa_pod_choice, choice)) enum spa_choice_type { SPA_CHOICE_None, /**< no choice, first value is current */ SPA_CHOICE_Range, /**< range: default, min, max */ SPA_CHOICE_Step, /**< range with step: default, min, max, step */ SPA_CHOICE_Enum, /**< list: default, alternative,... */ SPA_CHOICE_Flags, /**< flags: first value is flags */ }; struct spa_pod_choice_body { uint32_t type; /**< type of choice, one of enum spa_choice_type */ uint32_t flags; /**< extra flags */ struct spa_pod child; /* array with elements of child.size follows. Note that there might be more * elements than required by \a type, which should be ignored. */ }; struct spa_pod_choice { struct spa_pod pod; struct spa_pod_choice_body body; }; #define SPA_POD_STRUCT_BODY(pod) SPA_PTROFF((pod),sizeof(struct spa_pod),struct spa_pod) #define SPA_POD_STRUCT_BODY_CONST(pod) SPA_PTROFF((pod),sizeof(struct spa_pod),const struct spa_pod) struct spa_pod_struct { struct spa_pod pod; /* one or more spa_pod follow */ }; #define SPA_POD_OBJECT_TYPE(obj) (((struct spa_pod_object*)(obj))->body.type) #define SPA_POD_OBJECT_ID(obj) (((struct spa_pod_object*)(obj))->body.id) struct spa_pod_object_body { uint32_t type; /**< one of enum spa_type */ uint32_t id; /**< id of the object, depends on the object type */ /* contents follow, series of spa_pod_prop */ }; struct spa_pod_object { struct spa_pod pod; struct spa_pod_object_body body; }; struct spa_pod_pointer_body { uint32_t type; /**< pointer id, one of enum spa_type */ uint32_t _padding; const void *value; }; struct spa_pod_pointer { struct spa_pod pod; struct spa_pod_pointer_body body; }; struct spa_pod_fd { struct spa_pod pod; int64_t value; }; #define SPA_POD_PROP_SIZE(prop) (sizeof(struct spa_pod_prop) + (prop)->value.size) /* props can be inside an object */ struct spa_pod_prop { uint32_t key; /**< key of property, list of valid keys depends on the * object type */ #define SPA_POD_PROP_FLAG_READONLY (1u<<0) /**< is read-only */ #define SPA_POD_PROP_FLAG_HARDWARE (1u<<1) /**< some sort of hardware parameter */ #define SPA_POD_PROP_FLAG_HINT_DICT (1u<<2) /**< contains a dictionary struct as * (Struct( * Int : n_items, * (String : key, * String : value)*)) */ #define SPA_POD_PROP_FLAG_MANDATORY (1u<<3) /**< is mandatory, when filtering, both sides * need this property or filtering fails. */ #define SPA_POD_PROP_FLAG_DONT_FIXATE (1u<<4) /**< choices need no fixation */ #define SPA_POD_PROP_FLAG_DROP (1u<<5) /**< drop property, when filtering, both sides * need the property or it will be dropped. */ uint32_t flags; /**< flags for property */ struct spa_pod value; /* value follows */ }; #define SPA_POD_CONTROL_SIZE(ev) (sizeof(struct spa_pod_control) + (ev)->value.size) /* controls can be inside a sequence and mark timed values */ struct spa_pod_control { uint32_t offset; /**< media offset */ uint32_t type; /**< type of control, enum spa_control_type */ struct spa_pod value; /**< control value, depends on type */ /* value contents follow */ }; struct spa_pod_sequence_body { uint32_t unit; uint32_t pad; /* series of struct spa_pod_control follows */ }; /** a sequence of timed controls */ struct spa_pod_sequence { struct spa_pod pod; struct spa_pod_sequence_body body; }; /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_POD_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/pod/simplify.h000066400000000000000000000114041511204443500262560ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_POD_SIMPLIFY_H #define SPA_POD_SIMPLIFY_H #ifdef __cplusplus extern "C" { #endif #include #include #include #include #include #include #include #include #include #include #include #ifndef SPA_API_POD_SIMPLIFY #ifdef SPA_API_IMPL #define SPA_API_POD_SIMPLIFY SPA_API_IMPL #else #define SPA_API_POD_SIMPLIFY static inline #endif #endif /** * \addtogroup spa_pod * \{ */ SPA_API_POD_SIMPLIFY int spa_pod_simplify_merge(struct spa_pod_builder *b, const struct spa_pod *pod1, const struct spa_pod *pod2) { const struct spa_pod_object *o1, *o2; const struct spa_pod_prop *p1, *p2; struct spa_pod_frame f[2]; int res = 0, count = 0; if (!spa_pod_is_object(pod1) || !spa_pod_is_object(pod2)) return -ENOTSUP; o1 = (const struct spa_pod_object*) pod1; o2 = (const struct spa_pod_object*) pod2; spa_pod_builder_push_object(b, &f[0], o1->body.type, o1->body.id); p2 = NULL; SPA_POD_OBJECT_FOREACH(o1, p1) { p2 = spa_pod_object_find_prop(o2, p2, p1->key); if (p2 == NULL) goto error_enoent; if (spa_pod_compare(&p1->value, &p2->value) == 0) { spa_pod_builder_raw_padded(b, p1, SPA_POD_PROP_SIZE(p1)); } else { uint32_t i, n_vals1, n_vals2, choice1, choice2, size; const struct spa_pod *vals1, *vals2; void *alt1, *alt2, *a1, *a2; count++; if (count > 1) goto error_einval; vals1 = spa_pod_get_values(&p1->value, &n_vals1, &choice1); vals2 = spa_pod_get_values(&p2->value, &n_vals2, &choice2); if (vals1->type != vals2->type || n_vals1 < 1 || n_vals2 < 1) goto error_einval; size = vals1->size; alt1 = SPA_POD_BODY(vals1); alt2 = SPA_POD_BODY(vals2); if ((choice1 == SPA_CHOICE_None && choice2 == SPA_CHOICE_None) || (choice1 == SPA_CHOICE_None && choice2 == SPA_CHOICE_Enum) || (choice1 == SPA_CHOICE_Enum && choice2 == SPA_CHOICE_None) || (choice1 == SPA_CHOICE_Enum && choice2 == SPA_CHOICE_Enum)) { spa_pod_builder_prop(b, p1->key, p1->flags); spa_pod_builder_push_choice(b, &f[1], SPA_CHOICE_Enum, 0); spa_pod_builder_child(b, size, vals1->type); for (i = 0, a1 = alt1; i < n_vals1; i++, a1 = SPA_PTROFF(a1,size,void)) { if (i == 0 && n_vals1 == 1) spa_pod_builder_raw(b, a1, size); spa_pod_builder_raw(b, a1, size); } for (i = 0, a2 = alt2; i < n_vals2; i++, a2 = SPA_PTROFF(a2,size,void)) { spa_pod_builder_raw(b, a2, size); } spa_pod_builder_pop(b, &f[1]); } else { goto error_einval; } } } p1 = NULL; SPA_POD_OBJECT_FOREACH(o2, p2) { p1 = spa_pod_object_find_prop(o1, p1, p2->key); if (p1 == NULL) goto error_enoent; } done: spa_pod_builder_pop(b, &f[0]); return res; error_einval: res = -EINVAL; goto done; error_enoent: res = -ENOENT; goto done; } SPA_API_POD_SIMPLIFY int spa_pod_simplify_struct(struct spa_pod_builder *b, const struct spa_pod *pod, uint32_t pod_size) { struct spa_pod *p1 = NULL, *p2; struct spa_pod_frame f; struct spa_pod_builder_state state; uint32_t p1offs; spa_pod_builder_push_struct(b, &f); SPA_POD_STRUCT_FOREACH(pod, p2) { spa_pod_builder_get_state(b, &state); if (p1 == NULL || spa_pod_simplify_merge(b, p1, p2) < 0) { spa_pod_builder_reset(b, &state); spa_pod_builder_raw_padded(b, p2, SPA_POD_SIZE(p2)); p1offs = state.offset; p1 = SPA_PTROFF(b->data, p1offs, struct spa_pod); } else { void *pnew = SPA_PTROFF(b->data, state.offset, void); p1 = SPA_PTROFF(b->data, p1offs, struct spa_pod); spa_pod_builder_remove(b, SPA_POD_SIZE(p1)); memmove(p1, pnew, SPA_POD_SIZE(pnew)); } } spa_pod_builder_pop(b, &f); return 0; } SPA_API_POD_SIMPLIFY int spa_pod_simplify(struct spa_pod_builder *b, struct spa_pod **result, const struct spa_pod *pod) { int res = 0; struct spa_pod_builder_state state; spa_return_val_if_fail(pod != NULL, -EINVAL); spa_return_val_if_fail(b != NULL, -EINVAL); spa_pod_builder_get_state(b, &state); if (!spa_pod_is_struct(pod)) { res = spa_pod_builder_raw_padded(b, pod, SPA_POD_SIZE(pod)); } else { struct spa_pod_dynamic_builder db; spa_pod_dynamic_builder_continue(&db, b); res = spa_pod_simplify_struct(&db.b, pod, SPA_POD_SIZE(pod)); if (res >= 0) res = spa_pod_builder_raw_padded(b, db.b.data, db.b.state.offset); spa_pod_dynamic_builder_clean(&db); } if (res >= 0 && result) { *result = (struct spa_pod*)spa_pod_builder_deref(b, state.offset); if (*result == NULL) res = -ENOSPC; } return res; } /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_POD_SIMPLIFY_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/pod/vararg.h000066400000000000000000000100711511204443500257030ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2019 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_POD_VARARG_H #define SPA_POD_VARARG_H #include #include #ifdef __cplusplus extern "C" { #endif /** * \addtogroup spa_pod * \{ */ #define SPA_POD_Prop(key,...) \ key, ##__VA_ARGS__ #define SPA_POD_Propf(key,flags,...) \ SPA_ID_INVALID, key, flags, ##__VA_ARGS__ #define SPA_POD_Control(offset,type,...) \ offset, type, ##__VA_ARGS__ #define SPA_CHOICE_RANGE(def,min,max) 3,(def),(min),(max) #define SPA_CHOICE_STEP(def,min,max,step) 4,(def),(min),(max),(step) #define SPA_CHOICE_ENUM(n_vals,def,alt1,...) (n_vals),(def),(alt1),##__VA_ARGS__ #define SPA_CHOICE_FLAGS(flags) 1, (flags) #define SPA_CHOICE_FEATURES(features) 1, (features) #define SPA_CHOICE_BOOL(def) 3,(def),(def),!(def) #define SPA_POD_Bool(val) "b", val #define SPA_POD_CHOICE_Bool(def) "?eb", SPA_CHOICE_BOOL(def) #define SPA_POD_Id(val) "I", val #define SPA_POD_CHOICE_ENUM_Id(n_vals,...) "?eI", SPA_CHOICE_ENUM(n_vals, __VA_ARGS__) #define SPA_POD_Int(val) "i", val #define SPA_POD_CHOICE_ENUM_Int(n_vals,...) "?ei", SPA_CHOICE_ENUM(n_vals, __VA_ARGS__) #define SPA_POD_CHOICE_RANGE_Int(def,min,max) "?ri", SPA_CHOICE_RANGE(def, min, max) #define SPA_POD_CHOICE_STEP_Int(def,min,max,step) "?si", SPA_CHOICE_STEP(def, min, max, step) #define SPA_POD_CHOICE_FLAGS_Int(flags) "?fi", SPA_CHOICE_FLAGS(flags) #define SPA_POD_CHOICE_FEATURES_Int(features) "?Fi", SPA_CHOICE_FEATURES(features) #define SPA_POD_Long(val) "l", val #define SPA_POD_CHOICE_ENUM_Long(n_vals,...) "?el", SPA_CHOICE_ENUM(n_vals, __VA_ARGS__) #define SPA_POD_CHOICE_RANGE_Long(def,min,max) "?rl", SPA_CHOICE_RANGE(def, min, max) #define SPA_POD_CHOICE_STEP_Long(def,min,max,step) "?sl", SPA_CHOICE_STEP(def, min, max, step) #define SPA_POD_CHOICE_FLAGS_Long(flags) "?fl", SPA_CHOICE_FLAGS(flags) #define SPA_POD_CHOICE_FEATURES_LONG(features) "?Fl", SPA_CHOICE_FEATURES(features) #define SPA_POD_Float(val) "f", val #define SPA_POD_CHOICE_ENUM_Float(n_vals,...) "?ef", SPA_CHOICE_ENUM(n_vals, __VA_ARGS__) #define SPA_POD_CHOICE_RANGE_Float(def,min,max) "?rf", SPA_CHOICE_RANGE(def, min, max) #define SPA_POD_CHOICE_STEP_Float(def,min,max,step) "?sf", SPA_CHOICE_STEP(def, min, max, step) #define SPA_POD_Double(val) "d", val #define SPA_POD_CHOICE_ENUM_Double(n_vals,...) "?ed", SPA_CHOICE_ENUM(n_vals, __VA_ARGS__) #define SPA_POD_CHOICE_RANGE_Double(def,min,max) "?rd", SPA_CHOICE_RANGE(def, min, max) #define SPA_POD_CHOICE_STEP_Double(def,min,max,step) "?sd", SPA_CHOICE_STEP(def, min, max, step) #define SPA_POD_String(val) "s",val #define SPA_POD_Stringn(val,len) "S",val,len #define SPA_POD_Bytes(val,len) "y",val,len #define SPA_POD_Rectangle(val) "R",val #define SPA_POD_CHOICE_ENUM_Rectangle(n_vals,...) "?eR", SPA_CHOICE_ENUM(n_vals, __VA_ARGS__) #define SPA_POD_CHOICE_RANGE_Rectangle(def,min,max) "?rR", SPA_CHOICE_RANGE((def),(min),(max)) #define SPA_POD_CHOICE_STEP_Rectangle(def,min,max,step) "?sR", SPA_CHOICE_STEP((def),(min),(max),(step)) #define SPA_POD_Fraction(val) "F",val #define SPA_POD_CHOICE_ENUM_Fraction(n_vals,...) "?eF", SPA_CHOICE_ENUM(n_vals, __VA_ARGS__) #define SPA_POD_CHOICE_RANGE_Fraction(def,min,max) "?rF", SPA_CHOICE_RANGE((def),(min),(max)) #define SPA_POD_CHOICE_STEP_Fraction(def,min,max,step) "?sF", SPA_CHOICE_STEP(def, min, max, step) #define SPA_POD_Array(csize,ctype,n_vals,vals) "a", csize,ctype,n_vals,vals #define SPA_POD_Pointer(type,val) "p", type,val #define SPA_POD_Fd(val) "h", val #define SPA_POD_None() "P", NULL #define SPA_POD_Pod(val) "P", val #define SPA_POD_PodObject(val) "O", val #define SPA_POD_PodStruct(val) "T", val #define SPA_POD_PodChoice(val) "V", val #define SPA_POD_PodBody(val,body) "Q", val, body #define SPA_POD_PodBodyObject(val,body) "N", val, body #define SPA_POD_PodBodyStruct(val,body) "U", val, body #define SPA_POD_PodBodyChoice(val,body) "W", val, body /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_POD_VARARG_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/support/000077500000000000000000000000001511204443500252035ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/support/cpu.h000066400000000000000000000134051511204443500261460ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_CPU_H #define SPA_CPU_H #include #include #include #include #ifdef __cplusplus extern "C" { #endif #ifndef SPA_API_CPU #ifdef SPA_API_IMPL #define SPA_API_CPU SPA_API_IMPL #else #define SPA_API_CPU static inline #endif #endif /** \defgroup spa_cpu CPU * Querying CPU properties */ /** * \addtogroup spa_cpu * \{ */ /** * The CPU features interface */ #define SPA_TYPE_INTERFACE_CPU SPA_TYPE_INFO_INTERFACE_BASE "CPU" #define SPA_VERSION_CPU 0 struct spa_cpu { struct spa_interface iface; }; /* x86 specific */ #define SPA_CPU_FLAG_MMX (1<<0) /**< standard MMX */ #define SPA_CPU_FLAG_MMXEXT (1<<1) /**< SSE integer or AMD MMX ext */ #define SPA_CPU_FLAG_3DNOW (1<<2) /**< AMD 3DNOW */ #define SPA_CPU_FLAG_SSE (1<<3) /**< SSE */ #define SPA_CPU_FLAG_SSE2 (1<<4) /**< SSE2 */ #define SPA_CPU_FLAG_3DNOWEXT (1<<5) /**< AMD 3DNowExt */ #define SPA_CPU_FLAG_SSE3 (1<<6) /**< Prescott SSE3 */ #define SPA_CPU_FLAG_SSSE3 (1<<7) /**< Conroe SSSE3 */ #define SPA_CPU_FLAG_SSE41 (1<<8) /**< Penryn SSE4.1 */ #define SPA_CPU_FLAG_SSE42 (1<<9) /**< Nehalem SSE4.2 */ #define SPA_CPU_FLAG_AESNI (1<<10) /**< Advanced Encryption Standard */ #define SPA_CPU_FLAG_AVX (1<<11) /**< AVX */ #define SPA_CPU_FLAG_XOP (1<<12) /**< Bulldozer XOP */ #define SPA_CPU_FLAG_FMA4 (1<<13) /**< Bulldozer FMA4 */ #define SPA_CPU_FLAG_CMOV (1<<14) /**< supports cmov */ #define SPA_CPU_FLAG_AVX2 (1<<15) /**< AVX2 */ #define SPA_CPU_FLAG_FMA3 (1<<16) /**< Haswell FMA3 */ #define SPA_CPU_FLAG_BMI1 (1<<17) /**< Bit Manipulation Instruction Set 1 */ #define SPA_CPU_FLAG_BMI2 (1<<18) /**< Bit Manipulation Instruction Set 2 */ #define SPA_CPU_FLAG_AVX512 (1<<19) /**< AVX-512 */ #define SPA_CPU_FLAG_SLOW_UNALIGNED (1<<20) /**< unaligned loads/stores are slow */ /* PPC specific */ #define SPA_CPU_FLAG_ALTIVEC (1<<0) /**< standard */ #define SPA_CPU_FLAG_VSX (1<<1) /**< ISA 2.06 */ #define SPA_CPU_FLAG_POWER8 (1<<2) /**< ISA 2.07 */ /* ARM specific */ #define SPA_CPU_FLAG_ARMV5TE (1 << 0) #define SPA_CPU_FLAG_ARMV6 (1 << 1) #define SPA_CPU_FLAG_ARMV6T2 (1 << 2) #define SPA_CPU_FLAG_VFP (1 << 3) #define SPA_CPU_FLAG_VFPV3 (1 << 4) #define SPA_CPU_FLAG_NEON (1 << 5) #define SPA_CPU_FLAG_ARMV8 (1 << 6) /* RISCV specific */ #define SPA_CPU_FLAG_RISCV_V (1 << 0) #define SPA_CPU_FORCE_AUTODETECT ((uint32_t)-1) #define SPA_CPU_VM_NONE (0) #define SPA_CPU_VM_OTHER (1 << 0) #define SPA_CPU_VM_KVM (1 << 1) #define SPA_CPU_VM_QEMU (1 << 2) #define SPA_CPU_VM_BOCHS (1 << 3) #define SPA_CPU_VM_XEN (1 << 4) #define SPA_CPU_VM_UML (1 << 5) #define SPA_CPU_VM_VMWARE (1 << 6) #define SPA_CPU_VM_ORACLE (1 << 7) #define SPA_CPU_VM_MICROSOFT (1 << 8) #define SPA_CPU_VM_ZVM (1 << 9) #define SPA_CPU_VM_PARALLELS (1 << 10) #define SPA_CPU_VM_BHYVE (1 << 11) #define SPA_CPU_VM_QNX (1 << 12) #define SPA_CPU_VM_ACRN (1 << 13) #define SPA_CPU_VM_POWERVM (1 << 14) SPA_API_CPU const char *spa_cpu_vm_type_to_string(uint32_t vm_type) { switch(vm_type) { case SPA_CPU_VM_NONE: return NULL; case SPA_CPU_VM_KVM: return "kvm"; case SPA_CPU_VM_QEMU: return "qemu"; case SPA_CPU_VM_BOCHS: return "bochs"; case SPA_CPU_VM_XEN: return "xen"; case SPA_CPU_VM_UML: return "uml"; case SPA_CPU_VM_VMWARE: return "vmware"; case SPA_CPU_VM_ORACLE: return "oracle"; case SPA_CPU_VM_MICROSOFT: return "microsoft"; case SPA_CPU_VM_ZVM: return "zvm"; case SPA_CPU_VM_PARALLELS: return "parallels"; case SPA_CPU_VM_BHYVE: return "bhyve"; case SPA_CPU_VM_QNX: return "qnx"; case SPA_CPU_VM_ACRN: return "acrn"; case SPA_CPU_VM_POWERVM: return "powervm"; case SPA_CPU_VM_OTHER: return "other"; default: return "unknown"; } } /** * methods */ struct spa_cpu_methods { /** the version of the methods. This can be used to expand this structure in the future */ #define SPA_VERSION_CPU_METHODS 2 uint32_t version; /** get CPU flags */ uint32_t (*get_flags) (void *object); /** force CPU flags, use SPA_CPU_FORCE_AUTODETECT to autodetect CPU flags */ int (*force_flags) (void *object, uint32_t flags); /** get number of CPU cores */ uint32_t (*get_count) (void *object); /** get maximum required alignment of data */ uint32_t (*get_max_align) (void *object); /* check if running in a VM. Since:1 */ uint32_t (*get_vm_type) (void *object); /* denormals will be handled as zero, either with FTZ or DAZ. * Since:2 */ int (*zero_denormals) (void *object, bool enable); }; SPA_API_CPU uint32_t spa_cpu_get_flags(struct spa_cpu *c) { return spa_api_method_r(uint32_t, 0, spa_cpu, &c->iface, get_flags, 0); } SPA_API_CPU int spa_cpu_force_flags(struct spa_cpu *c, uint32_t flags) { return spa_api_method_r(int, -ENOTSUP, spa_cpu, &c->iface, force_flags, 0, flags); } SPA_API_CPU uint32_t spa_cpu_get_count(struct spa_cpu *c) { return spa_api_method_r(uint32_t, 0, spa_cpu, &c->iface, get_count, 0); } SPA_API_CPU uint32_t spa_cpu_get_max_align(struct spa_cpu *c) { return spa_api_method_r(uint32_t, 0, spa_cpu, &c->iface, get_max_align, 0); } SPA_API_CPU uint32_t spa_cpu_get_vm_type(struct spa_cpu *c) { return spa_api_method_r(uint32_t, 0, spa_cpu, &c->iface, get_vm_type, 1); } SPA_API_CPU int spa_cpu_zero_denormals(struct spa_cpu *c, bool enable) { return spa_api_method_r(int, -ENOTSUP, spa_cpu, &c->iface, zero_denormals, 2, enable); } /** keys can be given when initializing the cpu handle */ #define SPA_KEY_CPU_FORCE "cpu.force" /**< force cpu flags */ #define SPA_KEY_CPU_VM_TYPE "cpu.vm.type" /**< force a VM type */ #define SPA_KEY_CPU_ZERO_DENORMALS "cpu.zero.denormals" /**< zero denormals */ /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_CPU_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/support/dbus.h000066400000000000000000000073461511204443500263230ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_DBUS_H #define SPA_DBUS_H #include #ifdef __cplusplus extern "C" { #endif #ifndef SPA_API_DBUS #ifdef SPA_API_IMPL #define SPA_API_DBUS SPA_API_IMPL #else #define SPA_API_DBUS static inline #endif #endif /** \defgroup spa_dbus DBus * DBus communication */ /** * \addtogroup spa_dbus * \{ */ #define SPA_TYPE_INTERFACE_DBus SPA_TYPE_INFO_INTERFACE_BASE "DBus" #define SPA_VERSION_DBUS 0 struct spa_dbus { struct spa_interface iface; }; enum spa_dbus_type { SPA_DBUS_TYPE_SESSION, /**< The login session bus */ SPA_DBUS_TYPE_SYSTEM, /**< The systemwide bus */ SPA_DBUS_TYPE_STARTER /**< The bus that started us, if any */ }; #define SPA_DBUS_CONNECTION_EVENT_DESTROY 0 #define SPA_DBUS_CONNECTION_EVENT_DISCONNECTED 1 #define SPA_DBUS_CONNECTION_EVENT_NUM 2 struct spa_dbus_connection_events { #define SPA_VERSION_DBUS_CONNECTION_EVENTS 0 uint32_t version; /* a connection is destroyed */ void (*destroy) (void *data); /* a connection disconnected */ void (*disconnected) (void *data); }; struct spa_dbus_connection { #define SPA_VERSION_DBUS_CONNECTION 1 uint32_t version; /** * Get the DBusConnection from a wrapper * * Note that the returned handle is closed and unref'd by spa_dbus * immediately before emitting the asynchronous "disconnected" event. * The caller must either deal with the invalidation, or keep an extra * ref on the handle returned. * * \param conn the spa_dbus_connection wrapper * \return a pointer of type DBusConnection */ void *(*get) (struct spa_dbus_connection *conn); /** * Destroy a dbus connection wrapper * * \param conn the wrapper to destroy */ void (*destroy) (struct spa_dbus_connection *conn); /** * Add a listener for events * * Since version 1 */ void (*add_listener) (struct spa_dbus_connection *conn, struct spa_hook *listener, const struct spa_dbus_connection_events *events, void *data); }; /** \copydoc spa_dbus_connection.get * \sa spa_dbus_connection.get */ SPA_API_DBUS void *spa_dbus_connection_get(struct spa_dbus_connection *conn) { return spa_api_func_r(void *, NULL, conn, get, 0); } /** \copydoc spa_dbus_connection.destroy * \sa spa_dbus_connection.destroy */ SPA_API_DBUS void spa_dbus_connection_destroy(struct spa_dbus_connection *conn) { spa_api_func_v(conn, destroy, 0); } /** \copydoc spa_dbus_connection.add_listener * \sa spa_dbus_connection.add_listener */ SPA_API_DBUS void spa_dbus_connection_add_listener(struct spa_dbus_connection *conn, struct spa_hook *listener, const struct spa_dbus_connection_events *events, void *data) { spa_api_func_v(conn, add_listener, 1, listener, events, data); } struct spa_dbus_methods { #define SPA_VERSION_DBUS_METHODS 0 uint32_t version; /** * Get a new connection wrapper for the given bus type. * * The connection wrapper is completely configured to operate * in the main context of the handle that manages the spa_dbus * interface. * * \param dbus the dbus manager * \param type the bus type to wrap * \param error location for the DBusError * \return a new dbus connection wrapper or NULL on error */ struct spa_dbus_connection * (*get_connection) (void *object, enum spa_dbus_type type); }; /** \copydoc spa_dbus_methods.get_connection * \sa spa_dbus_methods.get_connection */ SPA_API_DBUS struct spa_dbus_connection * spa_dbus_get_connection(struct spa_dbus *dbus, enum spa_dbus_type type) { return spa_api_method_r(struct spa_dbus_connection *, NULL, spa_dbus, &dbus->iface, get_connection, 0, type); } /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_DBUS_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/support/i18n.h000066400000000000000000000036141511204443500261370ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2021 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_I18N_H #define SPA_I18N_H #include #include #ifdef __cplusplus extern "C" { #endif #ifndef SPA_API_I18N #ifdef SPA_API_IMPL #define SPA_API_I18N SPA_API_IMPL #else #define SPA_API_I18N static inline #endif #endif /** \defgroup spa_i18n I18N * Gettext interface */ /** * \addtogroup spa_i18n * \{ */ #define SPA_TYPE_INTERFACE_I18N SPA_TYPE_INFO_INTERFACE_BASE "I18N" #define SPA_VERSION_I18N 0 struct spa_i18n { struct spa_interface iface; }; struct spa_i18n_methods { #define SPA_VERSION_I18N_METHODS 0 uint32_t version; /** * Translate a message * * \param object the i18n interface * \param msgid the message * \return a translated message */ const char *(*text) (void *object, const char *msgid); /** * Translate a message for a number * * \param object the i18n interface * \param msgid the message to translate * \param msgid_plural the plural form of \a msgid * \param n a number * \return a translated message for the number \a n */ const char *(*ntext) (void *object, const char *msgid, const char *msgid_plural, unsigned long int n); }; SPA_FORMAT_ARG_FUNC(2) SPA_API_I18N const char * spa_i18n_text(struct spa_i18n *i18n, const char *msgid) { return spa_api_method_null_r(const char *, msgid, spa_i18n, i18n, &i18n->iface, text, 0, msgid); } SPA_API_I18N const char * spa_i18n_ntext(struct spa_i18n *i18n, const char *msgid, const char *msgid_plural, unsigned long int n) { return spa_api_method_null_r(const char *, n == 1 ? msgid : msgid_plural, spa_i18n, i18n, &i18n->iface, ntext, 0, msgid, msgid_plural, n); } /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_I18N_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/support/log-impl.h000066400000000000000000000056671511204443500271120ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_LOG_IMPL_H #define SPA_LOG_IMPL_H #include #include #include #ifdef __cplusplus extern "C" { #endif /** * \addtogroup spa_log * \{ */ static inline SPA_PRINTF_FUNC(7, 0) void spa_log_impl_logtv(void *object SPA_UNUSED, enum spa_log_level level, const struct spa_log_topic *topic, const char *file, int line, const char *func, const char *fmt, va_list args) { static const char * const levels[] = { "-", "E", "W", "I", "D", "T" }; const char *basename = strrchr(file, '/'); char text[512], location[1024]; char topicstr[32] = {0}; if (basename) basename += 1; /* skip '/' */ else basename = file; /* use whole string if no '/' is found */ if (topic && topic->topic) snprintf(topicstr, sizeof(topicstr), " %s ", topic->topic); vsnprintf(text, sizeof(text), fmt, args); snprintf(location, sizeof(location), "[%s]%s[%s:%i %s()] %s\n", levels[level], topicstr, basename, line, func, text); fputs(location, stderr); } static inline SPA_PRINTF_FUNC(7,8) void spa_log_impl_logt(void *object, enum spa_log_level level, const struct spa_log_topic *topic, const char *file, int line, const char *func, const char *fmt, ...) { va_list args; va_start(args, fmt); spa_log_impl_logtv(object, level, topic, file, line, func, fmt, args); va_end(args); } static inline SPA_PRINTF_FUNC(6, 0) void spa_log_impl_logv(void *object, enum spa_log_level level, const char *file, int line, const char *func, const char *fmt, va_list args) { spa_log_impl_logtv(object, level, NULL, file, line, func, fmt, args); } static inline SPA_PRINTF_FUNC(6,7) void spa_log_impl_log(void *object, enum spa_log_level level, const char *file, int line, const char *func, const char *fmt, ...) { va_list args; va_start(args, fmt); spa_log_impl_logv(object, level, file, line, func, fmt, args); va_end(args); } static inline void spa_log_impl_topic_init(void *object SPA_UNUSED, struct spa_log_topic *topic SPA_UNUSED) { /* noop */ } #define SPA_LOG_IMPL_DEFINE(name) \ struct { \ struct spa_log log; \ struct spa_log_methods methods; \ } name #define SPA_LOG_IMPL_INIT(name) \ { { { SPA_TYPE_INTERFACE_Log, SPA_VERSION_LOG, \ SPA_CALLBACKS_INIT(&(name).methods, &(name)) }, \ SPA_LOG_LEVEL_INFO, }, \ { SPA_VERSION_LOG_METHODS, \ spa_log_impl_log, \ spa_log_impl_logv, \ spa_log_impl_logt, \ spa_log_impl_logtv, \ spa_log_impl_topic_init, \ } } #define SPA_LOG_IMPL(name) \ SPA_LOG_IMPL_DEFINE(name) = SPA_LOG_IMPL_INIT(name) /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_LOG_IMPL_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/support/log.h000066400000000000000000000257001511204443500261410ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_LOG_H #define SPA_LOG_H #include #include #include #include #ifdef __cplusplus extern "C" { #endif #ifndef SPA_API_LOG #ifdef SPA_API_IMPL #define SPA_API_LOG SPA_API_IMPL #else #define SPA_API_LOG static inline #endif #endif /** \defgroup spa_log Log * Logging interface */ /** * \addtogroup spa_log * \{ */ /** The default log topic. Redefine this in your code to * allow for the spa_log_* macros to work correctly, e.g: * * \code{.c} * struct spa_log_topic *mylogger; * #undef SPA_LOG_TOPIC_DEFAULT * #define SPA_LOG_TOPIC_DEFAULT mylogger * \endcode */ #define SPA_LOG_TOPIC_DEFAULT NULL enum spa_log_level { SPA_LOG_LEVEL_NONE = 0, SPA_LOG_LEVEL_ERROR, SPA_LOG_LEVEL_WARN, SPA_LOG_LEVEL_INFO, SPA_LOG_LEVEL_DEBUG, SPA_LOG_LEVEL_TRACE, }; /** * The Log interface */ #define SPA_TYPE_INTERFACE_Log SPA_TYPE_INFO_INTERFACE_BASE "Log" struct spa_log { /** the version of this log. This can be used to expand this * structure in the future */ #define SPA_VERSION_LOG 0 struct spa_interface iface; /** * Logging level, everything above this level is not logged */ enum spa_log_level level; }; /** * \struct spa_log_topic * * Identifier for a topic. Topics are string-based filters that logically * group messages together. An implementation may decide to filter different * topics on different levels, for example the "protocol" topic may require * debug level TRACE while the "core" topic defaults to debug level INFO. * * spa_log_topics require a spa_log_methods version of 1 or higher. */ struct spa_log_topic { #define SPA_VERSION_LOG_TOPIC 0 /** the version of this topic. This can be used to expand this * structure in the future */ uint32_t version; /** The string identifier for the topic */ const char *topic; /** Logging level set for this topic */ enum spa_log_level level; /** False if this topic follows the \ref spa_log level */ bool has_custom_level; }; /** * Enumeration of log topics in a plugin * * \since 1.1.0 */ struct spa_log_topic_enum { #define SPA_VERSION_LOG_TOPIC_ENUM 0 uint32_t version; /** Array of pointers to log topics */ struct spa_log_topic * const * const topics; /** End of topics array */ struct spa_log_topic * const * const topics_end; }; struct spa_log_methods { #define SPA_VERSION_LOG_METHODS 1 uint32_t version; /** * Log a message with the given log level. * * \note If compiled with this header, this function is only called * for implementations of version 0. For versions 1 and above, see * logt() instead. * * \param log a spa_log * \param level a spa_log_level * \param file the file name * \param line the line number * \param func the function name * \param fmt printf style format * \param ... format arguments */ void (*log) (void *object, enum spa_log_level level, const char *file, int line, const char *func, const char *fmt, ...) SPA_PRINTF_FUNC(6, 7); /** * Log a message with the given log level. * * \note If compiled with this header, this function is only called * for implementations of version 0. For versions 1 and above, see * logtv() instead. * * \param log a spa_log * \param level a spa_log_level * \param file the file name * \param line the line number * \param func the function name * \param fmt printf style format * \param args format arguments */ void (*logv) (void *object, enum spa_log_level level, const char *file, int line, const char *func, const char *fmt, va_list args) SPA_PRINTF_FUNC(6, 0); /** * Log a message with the given log level for the given topic. * * \note Callers that do not use topic-based logging (version 0), the \a * topic is NULL * * \param log a spa_log * \param level a spa_log_level * \param topic the topic for this message, may be NULL * \param file the file name * \param line the line number * \param func the function name * \param fmt printf style format * \param ... format arguments * * \since 1 */ void (*logt) (void *object, enum spa_log_level level, const struct spa_log_topic *topic, const char *file, int line, const char *func, const char *fmt, ...) SPA_PRINTF_FUNC(7, 8); /** * Log a message with the given log level for the given topic. * * \note For callers that do not use topic-based logging (version 0), * the \a topic is NULL * * \param log a spa_log * \param level a spa_log_level * \param topic the topic for this message, may be NULL * \param file the file name * \param line the line number * \param func the function name * \param fmt printf style format * \param args format arguments * * \since 1 */ void (*logtv) (void *object, enum spa_log_level level, const struct spa_log_topic *topic, const char *file, int line, const char *func, const char *fmt, va_list args) SPA_PRINTF_FUNC(7, 0); /** * Initializes a \ref spa_log_topic to the correct logging level. * * \deprecated * Plugin host should obtain log topics from \ref SPA_LOG_TOPIC_ENUM_NAME * and update them itself. * * \since 1 */ void (*topic_init) (void *object, struct spa_log_topic *topic); }; #define SPA_LOG_TOPIC(v, t) \ (struct spa_log_topic){ .version = (v), .topic = (t)} SPA_API_LOG void spa_log_topic_init(struct spa_log *log, struct spa_log_topic *topic) { if (SPA_UNLIKELY(!log)) return; spa_interface_call(&log->iface, struct spa_log_methods, topic_init, 1, topic); } SPA_API_LOG bool spa_log_level_topic_enabled(const struct spa_log *log, const struct spa_log_topic *topic, enum spa_log_level level) { enum spa_log_level max_level; if (SPA_UNLIKELY(!log)) return false; if (topic && topic->has_custom_level) max_level = topic->level; else max_level = log->level; return level <= max_level; } /* Transparently calls to version 0 log if v1 is not supported */ #define spa_log_logt(l,lev,topic,...) \ ({ \ struct spa_log *_l = l; \ if (SPA_UNLIKELY(spa_log_level_topic_enabled(_l, topic, lev))) { \ struct spa_interface *_if = &_l->iface; \ if (!spa_interface_call(_if, \ struct spa_log_methods, logt, 1, \ lev, topic, \ __VA_ARGS__)) \ spa_interface_call(_if, \ struct spa_log_methods, log, 0, \ lev, __VA_ARGS__); \ } \ }) /* Transparently calls to version 0 logv if v1 is not supported */ SPA_PRINTF_FUNC(7, 0) SPA_API_LOG void spa_log_logtv(struct spa_log *l, enum spa_log_level level, const struct spa_log_topic *topic, const char *file, int line, const char *func, const char *fmt, va_list args) { if (SPA_UNLIKELY(spa_log_level_topic_enabled(l, topic, level))) { struct spa_interface *i = &l->iface; if (!spa_interface_call(i, struct spa_log_methods, logtv, 1, level, topic, file, line, func, fmt, args)) spa_interface_call(i, struct spa_log_methods, logv, 0, level, file, line, func, fmt, args); } } #define spa_logt_lev(l,lev,t,...) \ spa_log_logt(l,lev,t,__FILE__,__LINE__,__func__,__VA_ARGS__) #define spa_log_lev(l,lev,...) \ spa_logt_lev(l,lev,SPA_LOG_TOPIC_DEFAULT,__VA_ARGS__) #define spa_log_log(l,lev,...) \ spa_log_logt(l,lev,SPA_LOG_TOPIC_DEFAULT,__VA_ARGS__) #define spa_log_logv(l,lev,...) \ spa_log_logtv(l,lev,SPA_LOG_TOPIC_DEFAULT,__VA_ARGS__) #define spa_log_error(l,...) spa_log_lev(l,SPA_LOG_LEVEL_ERROR,__VA_ARGS__) #define spa_log_warn(l,...) spa_log_lev(l,SPA_LOG_LEVEL_WARN,__VA_ARGS__) #define spa_log_info(l,...) spa_log_lev(l,SPA_LOG_LEVEL_INFO,__VA_ARGS__) #define spa_log_debug(l,...) spa_log_lev(l,SPA_LOG_LEVEL_DEBUG,__VA_ARGS__) #define spa_log_trace(l,...) spa_log_lev(l,SPA_LOG_LEVEL_TRACE,__VA_ARGS__) #define spa_logt_error(l,t,...) spa_logt_lev(l,SPA_LOG_LEVEL_ERROR,t,__VA_ARGS__) #define spa_logt_warn(l,t,...) spa_logt_lev(l,SPA_LOG_LEVEL_WARN,t,__VA_ARGS__) #define spa_logt_info(l,t,...) spa_logt_lev(l,SPA_LOG_LEVEL_INFO,t,__VA_ARGS__) #define spa_logt_debug(l,t,...) spa_logt_lev(l,SPA_LOG_LEVEL_DEBUG,t,__VA_ARGS__) #define spa_logt_trace(l,t,...) spa_logt_lev(l,SPA_LOG_LEVEL_TRACE,t,__VA_ARGS__) #ifndef FASTPATH #define spa_log_trace_fp(l,...) spa_log_lev(l,SPA_LOG_LEVEL_TRACE,__VA_ARGS__) #else #define spa_log_trace_fp(l,...) #endif /** * Name of the symbol indicating a \ref spa_log_topic_enum enumerating * the static log topics in a plugin, * * \since 1.1.0 */ #define SPA_LOG_TOPIC_ENUM_NAME "spa_log_topic_enum" /** * Define the symbol for \ref SPA_LOG_TOPIC_ENUM_NAME * * \since 1.1.0 */ #define SPA_LOG_TOPIC_ENUM_DEFINE(s, e) \ SPA_EXPORT struct spa_log_topic_enum spa_log_topic_enum = (struct spa_log_topic_enum) { \ .version = SPA_VERSION_LOG_TOPIC_ENUM, \ .topics = (s), \ .topics_end = (e), \ } /** * Magically register a statically defined \ref spa_log_topic into * the log topic enumeration for a plugin. * * \since 1.1.0 */ #define SPA_LOG_TOPIC_REGISTER(v) \ __attribute__((used)) __attribute__((retain)) \ __attribute__((section("spa_log_topic"))) __attribute__((aligned(__alignof__(struct spa_log_topic *)))) \ static struct spa_log_topic * const spa_log_topic_export_##v = &v /** * Define and magically register a \ref spa_log_topic * * \since 1.1.0 */ #define SPA_LOG_TOPIC_DEFINE(var,name) \ struct spa_log_topic var = SPA_LOG_TOPIC(SPA_VERSION_LOG_TOPIC, name); \ SPA_LOG_TOPIC_REGISTER(var) /** * Define and magically register a \ref spa_log_topic with static scope * * \since 1.1.0 */ #define SPA_LOG_TOPIC_DEFINE_STATIC(var,name) \ static struct spa_log_topic var = SPA_LOG_TOPIC(SPA_VERSION_LOG_TOPIC, name); \ SPA_LOG_TOPIC_REGISTER(var) /** * Do \ref SPA_LOG_TOPIC_ENUM_DEFINE for the auto-registered * \ref spa_log_topic in the plugin. * * \since 1.1.0 */ #define SPA_LOG_TOPIC_ENUM_DEFINE_REGISTERED \ extern struct spa_log_topic * const __start_spa_log_topic[]; \ extern struct spa_log_topic * const __stop_spa_log_topic[]; \ SPA_LOG_TOPIC_ENUM_DEFINE(__start_spa_log_topic, __stop_spa_log_topic) /** \fn spa_log_error */ /** keys can be given when initializing the logger handle */ #define SPA_KEY_LOG_LEVEL "log.level" /**< the default log level */ #define SPA_KEY_LOG_COLORS "log.colors" /**< enable colors in the logger, set to "force" to enable * colors even when not logging to a terminal */ #define SPA_KEY_LOG_FILE "log.file" /**< log to the specified file instead of * stderr. */ #define SPA_KEY_LOG_TIMESTAMP "log.timestamp" /**< log timestamp type (local, realtime, monotonic, monotonic-raw). * boolean true means local. */ #define SPA_KEY_LOG_LINE "log.line" /**< log file and line numbers */ #define SPA_KEY_LOG_PATTERNS "log.patterns" /**< Spa:String:JSON array of [ {"pattern" : level}, ... ] */ /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_LOG_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/support/loop.h000066400000000000000000000451741511204443500263400ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_LOOP_H #define SPA_LOOP_H #include #include #include #include #ifdef __cplusplus extern "C" { #endif #ifndef SPA_API_LOOP #ifdef SPA_API_IMPL #define SPA_API_LOOP SPA_API_IMPL #else #define SPA_API_LOOP static inline #endif #endif /** \defgroup spa_loop Loop * Event loop interface */ /** * \addtogroup spa_loop * \{ */ #define SPA_TYPE_INTERFACE_Loop SPA_TYPE_INFO_INTERFACE_BASE "Loop" #define SPA_TYPE_INTERFACE_DataLoop SPA_TYPE_INFO_INTERFACE_BASE "DataLoop" #define SPA_VERSION_LOOP 0 struct spa_loop { struct spa_interface iface; }; #define SPA_TYPE_INTERFACE_LoopControl SPA_TYPE_INFO_INTERFACE_BASE "LoopControl" #define SPA_VERSION_LOOP_CONTROL 2 struct spa_loop_control { struct spa_interface iface; }; #define SPA_TYPE_INTERFACE_LoopUtils SPA_TYPE_INFO_INTERFACE_BASE "LoopUtils" #define SPA_VERSION_LOOP_UTILS 0 struct spa_loop_utils { struct spa_interface iface; }; struct spa_source; typedef void (*spa_source_func_t) (struct spa_source *source); struct spa_source { struct spa_loop *loop; spa_source_func_t func; void *data; int fd; uint32_t mask; uint32_t rmask; /* private data for the loop implementer */ void *priv; }; typedef int (*spa_invoke_func_t) (struct spa_loop *loop, bool async, uint32_t seq, const void *data, size_t size, void *user_data); /** * Register sources and work items to an event loop */ struct spa_loop_methods { /* the version of this structure. This can be used to expand this * structure in the future */ #define SPA_VERSION_LOOP_METHODS 0 uint32_t version; /** Add a source to the loop. Must be called from the loop's own thread. * * \param[in] object The callbacks data. * \param[in] source The source. * \return 0 on success, negative errno-style value on failure. */ int (*add_source) (void *object, struct spa_source *source); /** Update the source io mask. Must be called from the loop's own thread. * * \param[in] object The callbacks data. * \param[in] source The source. * \return 0 on success, negative errno-style value on failure. */ int (*update_source) (void *object, struct spa_source *source); /** Remove a source from the loop. Must be called from the loop's own thread. * * \param[in] object The callbacks data. * \param[in] source The source. * \return 0 on success, negative errno-style value on failure. */ int (*remove_source) (void *object, struct spa_source *source); /** Invoke a function in the context of this loop. * May be called from any thread and multiple threads at the same time. * * If called from the loop's thread, all callbacks previously queued with * invoke() will be run synchronously, which might cause unexpected * reentrancy problems. * * \param[in] object The callbacks data. * \param func The function to be invoked. * \param seq An opaque sequence number. This will be made * available to func. * \param[in] data Data that will be copied into the internal ring buffer and made * available to func. Because this data is copied, it is okay to * pass a pointer to a local variable, but do not pass a pointer to * an object that has identity. * \param size The size of data to copy. * \param block If \true, do not return until func has been called. Otherwise, * returns immediately. Passing \true can cause a deadlock when * the calling thread is holding the loop context lock. A blocking * invoke should never be done from a realtime thread. Also beware * of blocking invokes between 2 threads as you can easily end up * in a deadly embrace. * \param user_data An opaque pointer passed to func. * \return `-EPIPE` if the internal ring buffer filled up, * if block is \false, 0 if seq was SPA_ID_INVALID or * seq with the ASYNC flag set * or the return value of func otherwise. */ int (*invoke) (void *object, spa_invoke_func_t func, uint32_t seq, const void *data, size_t size, bool block, void *user_data); /** Call a function with the loop lock acquired * May be called from any thread and multiple threads at the same time. * * \param[in] object The callbacks data. * \param func The function to be called. * \param seq An opaque sequence number. This will be made * available to func. * \param[in] data Data that will be passed to func. * \param size The size of data. * \param user_data An opaque pointer passed to func. * \return the return value of func. */ int (*locked) (void *object, spa_invoke_func_t func, uint32_t seq, const void *data, size_t size, void *user_data); }; SPA_API_LOOP int spa_loop_add_source(struct spa_loop *object, struct spa_source *source) { return spa_api_method_r(int, -ENOTSUP, spa_loop, &object->iface, add_source, 0, source); } SPA_API_LOOP int spa_loop_update_source(struct spa_loop *object, struct spa_source *source) { return spa_api_method_r(int, -ENOTSUP, spa_loop, &object->iface, update_source, 0, source); } SPA_API_LOOP int spa_loop_remove_source(struct spa_loop *object, struct spa_source *source) { return spa_api_method_r(int, -ENOTSUP, spa_loop, &object->iface, remove_source, 0, source); } SPA_API_LOOP int spa_loop_invoke(struct spa_loop *object, spa_invoke_func_t func, uint32_t seq, const void *data, size_t size, bool block, void *user_data) { return spa_api_method_r(int, -ENOTSUP, spa_loop, &object->iface, invoke, 0, func, seq, data, size, block, user_data); } SPA_API_LOOP int spa_loop_locked(struct spa_loop *object, spa_invoke_func_t func, uint32_t seq, const void *data, size_t size, void *user_data) { return spa_api_method_r(int, -ENOTSUP, spa_loop, &object->iface, locked, 0, func, seq, data, size, user_data); } /** Control hooks. These hooks can't be removed from their * callbacks and must be removed from a safe place (when the loop * is not running or when it is locked). */ struct spa_loop_control_hooks { #define SPA_VERSION_LOOP_CONTROL_HOOKS 0 uint32_t version; /** Executed right before waiting for events. It is typically used to * release locks or integrate other fds into the loop. */ void (*before) (void *data); /** Executed right after waiting for events. It is typically used to * reacquire locks or integrate other fds into the loop. */ void (*after) (void *data); }; SPA_API_LOOP void spa_loop_control_hook_before(struct spa_hook_list *l) { struct spa_hook *h; spa_list_for_each_reverse(h, &l->list, link) spa_callbacks_call_fast(&h->cb, struct spa_loop_control_hooks, before, 0); } SPA_API_LOOP void spa_loop_control_hook_after(struct spa_hook_list *l) { struct spa_hook *h; spa_list_for_each(h, &l->list, link) spa_callbacks_call_fast(&h->cb, struct spa_loop_control_hooks, after, 0); } /** * Control an event loop * * The event loop control function provide API to run the event loop. * * The below (pseudo)code is a minimal example outlining the use of the loop * control: * \code{.c} * spa_loop_control_enter(loop); * while (running) { * spa_loop_control_iterate(loop, -1); * } * spa_loop_control_leave(loop); * \endcode * * It is also possible to add the loop to an existing event loop by using the * spa_loop_control_get_fd() call. This fd will become readable when activity * has been detected on the sources in the loop. spa_loop_control_iterate() with * a 0 timeout should be called to process the pending sources. * * spa_loop_control_enter() and spa_loop_control_leave() should be called once * from the thread that will run the iterate() function. */ struct spa_loop_control_methods { /* the version of this structure. This can be used to expand this * structure in the future */ #define SPA_VERSION_LOOP_CONTROL_METHODS 2 uint32_t version; /** get the loop fd * \param object the control to query * * Get the fd of this loop control. This fd will be readable when a * source in the loop has activity. The user should call iterate() * with a 0 timeout to schedule one iteration of the loop and dispatch * the sources. * \return the fd of the loop */ int (*get_fd) (void *object); /** Add a hook * \param object the control to change * \param hooks the hooks to add * * Adds hooks to the loop controlled by \a ctrl. */ void (*add_hook) (void *object, struct spa_hook *hook, const struct spa_loop_control_hooks *hooks, void *data); /** Enter a loop * \param object the control * * This function should be called before calling iterate and is * typically used to capture the thread that this loop will run in. * It should ideally be called once from the thread that will run * the loop. This function will lock the loop. */ void (*enter) (void *object); /** Leave a loop * \param object the control * * It should ideally be called once after calling iterate when the loop * will no longer be iterated from the thread that called enter(). * * This function will unlock the loop. */ void (*leave) (void *object); /** Perform one iteration of the loop. * \param ctrl the control * \param timeout an optional timeout in milliseconds. * 0 for no timeout, -1 for infinite timeout. * * This function will first unlock the loop and then block * up to \a timeout milliseconds, lock the loop again and then * dispatch the fds with activity. * * The number of dispatched fds is returned. */ int (*iterate) (void *object, int timeout); /** Check context of the loop * \param ctrl the control * * This function will check if the current thread is currently the * one that did the enter call. Since version 1:1. * * returns 1 on success, 0 or negative errno value on error. */ int (*check) (void *object); /** Lock the loop. * This will ensure the loop is not in the process of dispatching * callbacks. Since version 2:2 * * \param[in] object the control * \return 0 on success or a negative return value on error. */ int (*lock) (void *object); /** Unlock the loop. * Unlocks the loop again so that callbacks can be dispatched * again. Since version 2:2 * * \param[in] object the control * \return 0 on success or a negative return value on error. */ int (*unlock) (void *object); /** get the absolute time * Get the current time with \ref timeout that can be used in wait. * Since version 2:2 * * This function can be called from any thread. */ int (*get_time) (void *object, struct timespec *abstime, int64_t timeout); /** Wait for a signal * Wait until a thread performs signal. Since version 2:2 * * This function must be called with the loop lock. Because this is a * blocking call, it should not be performed from a realtime thread. * * \param[in] object the control * \param[in] abstime the maximum time to wait for the signal or NULL * \return 0 on success or a negative return value on error. */ int (*wait) (void *object, const struct timespec *abstime); /** Signal waiters * Wake up all threads blocked in wait. Since version 2:2 * When wait_for_accept is set, this functions blocks until all * threads performed accept. * * This function must be called with the loop lock and is safe to * call from a realtime thread source dispatch functions when * wait_for_accept is false. * * \param[in] object the control * \param[in] wait_for_accept block for accept * \return 0 on success or a negative return value on error. */ int (*signal) (void *object, bool wait_for_accept); /** Accept signalers * Resume the thread that signaled with wait_for accept. * * This function must be called with the loop lock and is safe to * call from a realtime thread source dispatch functions. * * \param[in] object the control * \return 0 on success or a negative return value on error. */ int (*accept) (void *object); }; SPA_API_LOOP int spa_loop_control_get_fd(struct spa_loop_control *object) { return spa_api_method_r(int, -ENOTSUP, spa_loop_control, &object->iface, get_fd, 0); } SPA_API_LOOP void spa_loop_control_add_hook(struct spa_loop_control *object, struct spa_hook *hook, const struct spa_loop_control_hooks *hooks, void *data) { spa_api_method_v(spa_loop_control, &object->iface, add_hook, 0, hook, hooks, data); } SPA_API_LOOP void spa_loop_control_enter(struct spa_loop_control *object) { spa_api_method_v(spa_loop_control, &object->iface, enter, 0); } SPA_API_LOOP void spa_loop_control_leave(struct spa_loop_control *object) { spa_api_method_v(spa_loop_control, &object->iface, leave, 0); } SPA_API_LOOP int spa_loop_control_iterate(struct spa_loop_control *object, int timeout) { return spa_api_method_r(int, -ENOTSUP, spa_loop_control, &object->iface, iterate, 0, timeout); } SPA_API_LOOP int spa_loop_control_iterate_fast(struct spa_loop_control *object, int timeout) { return spa_api_method_fast_r(int, -ENOTSUP, spa_loop_control, &object->iface, iterate, 0, timeout); } SPA_API_LOOP int spa_loop_control_check(struct spa_loop_control *object) { return spa_api_method_r(int, -ENOTSUP, spa_loop_control, &object->iface, check, 1); } SPA_API_LOOP int spa_loop_control_lock(struct spa_loop_control *object) { return spa_api_method_r(int, -ENOTSUP, spa_loop_control, &object->iface, lock, 2); } SPA_API_LOOP int spa_loop_control_unlock(struct spa_loop_control *object) { return spa_api_method_r(int, -ENOTSUP, spa_loop_control, &object->iface, unlock, 2); } SPA_API_LOOP int spa_loop_control_get_time(struct spa_loop_control *object, struct timespec *abstime, int64_t timeout) { return spa_api_method_r(int, -ENOTSUP, spa_loop_control, &object->iface, get_time, 2, abstime, timeout); } SPA_API_LOOP int spa_loop_control_wait(struct spa_loop_control *object, const struct timespec *abstime) { return spa_api_method_r(int, -ENOTSUP, spa_loop_control, &object->iface, wait, 2, abstime); } SPA_API_LOOP int spa_loop_control_signal(struct spa_loop_control *object, bool wait_for_accept) { return spa_api_method_r(int, -ENOTSUP, spa_loop_control, &object->iface, signal, 2, wait_for_accept); } SPA_API_LOOP int spa_loop_control_accept(struct spa_loop_control *object) { return spa_api_method_r(int, -ENOTSUP, spa_loop_control, &object->iface, accept, 2); } typedef void (*spa_source_io_func_t) (void *data, int fd, uint32_t mask); typedef void (*spa_source_idle_func_t) (void *data); typedef void (*spa_source_event_func_t) (void *data, uint64_t count); typedef void (*spa_source_timer_func_t) (void *data, uint64_t expirations); typedef void (*spa_source_signal_func_t) (void *data, int signal_number); /** * Create sources for an event loop */ struct spa_loop_utils_methods { /* the version of this structure. This can be used to expand this * structure in the future */ #define SPA_VERSION_LOOP_UTILS_METHODS 0 uint32_t version; struct spa_source *(*add_io) (void *object, int fd, uint32_t mask, bool close, spa_source_io_func_t func, void *data); int (*update_io) (void *object, struct spa_source *source, uint32_t mask); struct spa_source *(*add_idle) (void *object, bool enabled, spa_source_idle_func_t func, void *data); int (*enable_idle) (void *object, struct spa_source *source, bool enabled); struct spa_source *(*add_event) (void *object, spa_source_event_func_t func, void *data); int (*signal_event) (void *object, struct spa_source *source); struct spa_source *(*add_timer) (void *object, spa_source_timer_func_t func, void *data); int (*update_timer) (void *object, struct spa_source *source, struct timespec *value, struct timespec *interval, bool absolute); struct spa_source *(*add_signal) (void *object, int signal_number, spa_source_signal_func_t func, void *data); /** destroy a source allocated with this interface. This function * should only be called when the loop is not running or from the * context of the running loop */ void (*destroy_source) (void *object, struct spa_source *source); }; SPA_API_LOOP struct spa_source * spa_loop_utils_add_io(struct spa_loop_utils *object, int fd, uint32_t mask, bool close, spa_source_io_func_t func, void *data) { return spa_api_method_r(struct spa_source *, NULL, spa_loop_utils, &object->iface, add_io, 0, fd, mask, close, func, data); } SPA_API_LOOP int spa_loop_utils_update_io(struct spa_loop_utils *object, struct spa_source *source, uint32_t mask) { return spa_api_method_r(int, -ENOTSUP, spa_loop_utils, &object->iface, update_io, 0, source, mask); } SPA_API_LOOP struct spa_source * spa_loop_utils_add_idle(struct spa_loop_utils *object, bool enabled, spa_source_idle_func_t func, void *data) { return spa_api_method_r(struct spa_source *, NULL, spa_loop_utils, &object->iface, add_idle, 0, enabled, func, data); } SPA_API_LOOP int spa_loop_utils_enable_idle(struct spa_loop_utils *object, struct spa_source *source, bool enabled) { return spa_api_method_r(int, -ENOTSUP, spa_loop_utils, &object->iface, enable_idle, 0, source, enabled); } SPA_API_LOOP struct spa_source * spa_loop_utils_add_event(struct spa_loop_utils *object, spa_source_event_func_t func, void *data) { return spa_api_method_r(struct spa_source *, NULL, spa_loop_utils, &object->iface, add_event, 0, func, data); } SPA_API_LOOP int spa_loop_utils_signal_event(struct spa_loop_utils *object, struct spa_source *source) { return spa_api_method_r(int, -ENOTSUP, spa_loop_utils, &object->iface, signal_event, 0, source); } SPA_API_LOOP struct spa_source * spa_loop_utils_add_timer(struct spa_loop_utils *object, spa_source_timer_func_t func, void *data) { return spa_api_method_r(struct spa_source *, NULL, spa_loop_utils, &object->iface, add_timer, 0, func, data); } SPA_API_LOOP int spa_loop_utils_update_timer(struct spa_loop_utils *object, struct spa_source *source, struct timespec *value, struct timespec *interval, bool absolute) { return spa_api_method_r(int, -ENOTSUP, spa_loop_utils, &object->iface, update_timer, 0, source, value, interval, absolute); } SPA_API_LOOP struct spa_source * spa_loop_utils_add_signal(struct spa_loop_utils *object, int signal_number, spa_source_signal_func_t func, void *data) { return spa_api_method_r(struct spa_source *, NULL, spa_loop_utils, &object->iface, add_signal, 0, signal_number, func, data); } SPA_API_LOOP void spa_loop_utils_destroy_source(struct spa_loop_utils *object, struct spa_source *source) { spa_api_method_v(spa_loop_utils, &object->iface, destroy_source, 0, source); } /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_LOOP_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/support/plugin-loader.h000066400000000000000000000036471511204443500301300ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2021 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_PLUGIN_LOADER_H #define SPA_PLUGIN_LOADER_H #include #include #ifdef __cplusplus extern "C" { #endif #ifndef SPA_API_PLUGIN_LOADER #ifdef SPA_API_IMPL #define SPA_API_PLUGIN_LOADER SPA_API_IMPL #else #define SPA_API_PLUGIN_LOADER static inline #endif #endif /** \defgroup spa_plugin_loader Plugin Loader * SPA plugin loader */ /** * \addtogroup spa_plugin_loader * \{ */ #define SPA_TYPE_INTERFACE_PluginLoader SPA_TYPE_INFO_INTERFACE_BASE "PluginLoader" #define SPA_VERSION_PLUGIN_LOADER 0 struct spa_plugin_loader { struct spa_interface iface; }; struct spa_plugin_loader_methods { #define SPA_VERSION_PLUGIN_LOADER_METHODS 0 uint32_t version; /** * Load a SPA plugin. * * \param factory_name Plugin factory name * \param info Info dictionary for plugin. NULL if none. * \return plugin handle, or NULL on error */ struct spa_handle *(*load) (void *object, const char *factory_name, const struct spa_dict *info); /** * Unload a SPA plugin. * * \param handle Plugin handle. * \return 0 on success, < 0 on error */ int (*unload)(void *object, struct spa_handle *handle); }; SPA_API_PLUGIN_LOADER struct spa_handle * spa_plugin_loader_load(struct spa_plugin_loader *loader, const char *factory_name, const struct spa_dict *info) { return spa_api_method_null_r(struct spa_handle *, NULL, spa_plugin_loader, loader, &loader->iface, load, 0, factory_name, info); } SPA_API_PLUGIN_LOADER int spa_plugin_loader_unload(struct spa_plugin_loader *loader, struct spa_handle *handle) { return spa_api_method_null_r(int, -1, spa_plugin_loader, loader, &loader->iface, unload, 0, handle); } /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_PLUGIN_LOADER_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/support/plugin.h000066400000000000000000000156771511204443500266720ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_PLUGIN_H #define SPA_PLUGIN_H #include #include #include #include #ifdef __cplusplus extern "C" { #endif #ifndef SPA_API_PLUGIN #ifdef SPA_API_IMPL #define SPA_API_PLUGIN SPA_API_IMPL #else #define SPA_API_PLUGIN static inline #endif #endif /** * \defgroup spa_handle Plugin Handle * SPA plugin handle and factory interfaces */ /** * \addtogroup spa_handle * \{ */ struct spa_handle { /** Version of this struct */ #define SPA_VERSION_HANDLE 0 uint32_t version; /** * Get the interface provided by \a handle with \a type. * * \a interface is always a struct spa_interface but depending on * \a type, the struct might contain other information. * * \param handle a spa_handle * \param type the interface type * \param iface result to hold the interface. * \return 0 on success * -ENOTSUP when there are no interfaces * -EINVAL when handle or info is NULL */ int (*get_interface) (struct spa_handle *handle, const char *type, void **iface); /** * Clean up the memory of \a handle. After this, \a handle should not be used * anymore. * * \param handle a pointer to memory * \return 0 on success */ int (*clear) (struct spa_handle *handle); }; SPA_API_PLUGIN int spa_handle_get_interface(struct spa_handle *object, const char *type, void **iface) { return spa_api_func_r(int, -ENOTSUP, object, get_interface, 0, type, iface); } SPA_API_PLUGIN int spa_handle_clear(struct spa_handle *object) { return spa_api_func_r(int, -ENOTSUP, object, clear, 0); } /** * This structure lists the information about available interfaces on * handles. */ struct spa_interface_info { const char *type; /*< the type of the interface, can be * used to get the interface */ }; /** * Extra supporting infrastructure passed to the init() function of * a factory. It can be extra information or interfaces such as logging. */ struct spa_support { const char *type; /*< the type of the support item */ void *data; /*< specific data for the item */ }; /** Find a support item of the given type */ SPA_API_PLUGIN void *spa_support_find(const struct spa_support *support, uint32_t n_support, const char *type) { uint32_t i; for (i = 0; i < n_support; i++) { if (strcmp(support[i].type, type) == 0) return support[i].data; } return NULL; } #define SPA_SUPPORT_INIT(type,data) ((struct spa_support) { (type), (data) }) struct spa_handle_factory { /** The version of this structure */ #define SPA_VERSION_HANDLE_FACTORY 1 uint32_t version; /** * The name of the factory contains a logical name that describes * the function of the handle. Other plugins might contain an alternative * implementation with the same name. * * See utils/names.h for the list of standard names. * * Examples include: * * api.alsa.pcm.sink: an object to write PCM samples to an alsa PLAYBACK * device * api.v4l2.source: an object to read from a v4l2 source. */ const char *name; /** * Extra information about the handles of this factory. */ const struct spa_dict *info; /** * Get the size of handles from this factory. * * \param factory a spa_handle_factory * \param params extra parameters that determine the size of the * handle. */ size_t (*get_size) (const struct spa_handle_factory *factory, const struct spa_dict *params); /** * Initialize an instance of this factory. The caller should allocate * memory at least size bytes and pass this as \a handle. * * \a support can optionally contain extra interfaces or data items that the * plugin can use such as a logger. * * \param factory a spa_handle_factory * \param handle a pointer to memory * \param info extra handle specific information, usually obtained * from a spa_device. This can be used to configure the handle. * \param support support items * \param n_support number of elements in \a support * \return 0 on success * < 0 errno type error */ int (*init) (const struct spa_handle_factory *factory, struct spa_handle *handle, const struct spa_dict *info, const struct spa_support *support, uint32_t n_support); /** * spa_handle_factory::enum_interface_info: * \param factory: a #spa_handle_factory * \param info: result to hold spa_interface_info. * \param index: index to keep track of the enumeration, 0 for first item * * Enumerate the interface information for \a factory. * * \return 1 when an item is available * 0 when no more items are available * < 0 errno type error */ int (*enum_interface_info) (const struct spa_handle_factory *factory, const struct spa_interface_info **info, uint32_t *index); }; SPA_API_PLUGIN size_t spa_handle_factory_get_size(const struct spa_handle_factory *object, const struct spa_dict *params) { return spa_api_func_r(size_t, 0, object, get_size, 1, params); } SPA_API_PLUGIN int spa_handle_factory_init(const struct spa_handle_factory *object, struct spa_handle *handle, const struct spa_dict *info, const struct spa_support *support, uint32_t n_support) { return spa_api_func_r(int, -ENOTSUP, object, init, 1, handle, info, support, n_support); } SPA_API_PLUGIN int spa_handle_factory_enum_interface_info(const struct spa_handle_factory *object, const struct spa_interface_info **info, uint32_t *index) { return spa_api_func_r(int, -ENOTSUP, object, enum_interface_info, 1, info, index); } /** * The function signature of the entry point in a plugin. * * \param factory a location to hold the factory result * \param index index to keep track of the enumeration * \return 1 on success * 0 when there are no more factories * -EINVAL when factory is NULL */ typedef int (*spa_handle_factory_enum_func_t) (const struct spa_handle_factory **factory, uint32_t *index); #define SPA_HANDLE_FACTORY_ENUM_FUNC_NAME "spa_handle_factory_enum" /** * The entry point in a plugin. * * \param factory a location to hold the factory result * \param index index to keep track of the enumeration * \return 1 on success * 0 when no more items are available * < 0 errno type error */ int spa_handle_factory_enum(const struct spa_handle_factory **factory, uint32_t *index); #define SPA_KEY_FACTORY_NAME "factory.name" /**< the name of a factory */ #define SPA_KEY_FACTORY_AUTHOR "factory.author" /**< a comma separated list of factory authors */ #define SPA_KEY_FACTORY_DESCRIPTION "factory.description" /**< description of a factory */ #define SPA_KEY_FACTORY_USAGE "factory.usage" /**< usage of a factory */ #define SPA_KEY_LIBRARY_NAME "library.name" /**< the name of a library. This is usually * the filename of the plugin without the * path or the plugin extension. */ /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_PLUGIN_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/support/system.h000066400000000000000000000160311511204443500267010ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2019 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_SYSTEM_H #define SPA_SYSTEM_H #include #include #include #include #include #ifdef __cplusplus extern "C" { #endif struct itimerspec; #ifndef SPA_API_SYSTEM #ifdef SPA_API_IMPL #define SPA_API_SYSTEM SPA_API_IMPL #else #define SPA_API_SYSTEM static inline #endif #endif /** \defgroup spa_system System * I/O, clock, polling, timer, and signal interfaces */ /** * \addtogroup spa_system * \{ */ /** * a collection of core system functions */ #define SPA_TYPE_INTERFACE_System SPA_TYPE_INFO_INTERFACE_BASE "System" #define SPA_TYPE_INTERFACE_DataSystem SPA_TYPE_INFO_INTERFACE_BASE "DataSystem" #define SPA_VERSION_SYSTEM 0 struct spa_system { struct spa_interface iface; }; /* IO events */ #define SPA_IO_IN (1 << 0) #define SPA_IO_OUT (1 << 2) #define SPA_IO_ERR (1 << 3) #define SPA_IO_HUP (1 << 4) /* flags */ #define SPA_FD_CLOEXEC (1<<0) #define SPA_FD_NONBLOCK (1<<1) #define SPA_FD_EVENT_SEMAPHORE (1<<2) #define SPA_FD_TIMER_ABSTIME (1<<3) #define SPA_FD_TIMER_CANCEL_ON_SET (1<<4) struct spa_poll_event { uint32_t events; void *data; }; struct spa_system_methods { #define SPA_VERSION_SYSTEM_METHODS 0 uint32_t version; /* read/write/ioctl */ ssize_t (*read) (void *object, int fd, void *buf, size_t count); ssize_t (*write) (void *object, int fd, const void *buf, size_t count); int (*ioctl) (void *object, int fd, unsigned long request, ...); int (*close) (void *object, int fd); /* clock */ int (*clock_gettime) (void *object, int clockid, struct timespec *value); int (*clock_getres) (void *object, int clockid, struct timespec *res); /* poll */ int (*pollfd_create) (void *object, int flags); int (*pollfd_add) (void *object, int pfd, int fd, uint32_t events, void *data); int (*pollfd_mod) (void *object, int pfd, int fd, uint32_t events, void *data); int (*pollfd_del) (void *object, int pfd, int fd); int (*pollfd_wait) (void *object, int pfd, struct spa_poll_event *ev, int n_ev, int timeout); /* timers */ int (*timerfd_create) (void *object, int clockid, int flags); int (*timerfd_settime) (void *object, int fd, int flags, const struct itimerspec *new_value, struct itimerspec *old_value); int (*timerfd_gettime) (void *object, int fd, struct itimerspec *curr_value); int (*timerfd_read) (void *object, int fd, uint64_t *expirations); /* events */ int (*eventfd_create) (void *object, int flags); int (*eventfd_write) (void *object, int fd, uint64_t count); int (*eventfd_read) (void *object, int fd, uint64_t *count); /* signals */ int (*signalfd_create) (void *object, int signal, int flags); int (*signalfd_read) (void *object, int fd, int *signal); }; SPA_API_SYSTEM ssize_t spa_system_read(struct spa_system *object, int fd, void *buf, size_t count) { return spa_api_method_fast_r(ssize_t, -ENOTSUP, spa_system, &object->iface, read, 0, fd, buf, count); } SPA_API_SYSTEM ssize_t spa_system_write(struct spa_system *object, int fd, const void *buf, size_t count) { return spa_api_method_fast_r(ssize_t, -ENOTSUP, spa_system, &object->iface, write, 0, fd, buf, count); } #define spa_system_ioctl(object,fd,request,...) \ spa_api_method_fast_r(int, -ENOTSUP, spa_system, &object->iface, ioctl, 0, fd, request, ##__VA_ARGS__) SPA_API_SYSTEM int spa_system_close(struct spa_system *object, int fd) { return spa_api_method_fast_r(int, -ENOTSUP, spa_system, &object->iface, close, 0, fd); } SPA_API_SYSTEM int spa_system_clock_gettime(struct spa_system *object, int clockid, struct timespec *value) { return spa_api_method_fast_r(int, -ENOTSUP, spa_system, &object->iface, clock_gettime, 0, clockid, value); } SPA_API_SYSTEM int spa_system_clock_getres(struct spa_system *object, int clockid, struct timespec *res) { return spa_api_method_fast_r(int, -ENOTSUP, spa_system, &object->iface, clock_getres, 0, clockid, res); } SPA_API_SYSTEM int spa_system_pollfd_create(struct spa_system *object, int flags) { return spa_api_method_fast_r(int, -ENOTSUP, spa_system, &object->iface, pollfd_create, 0, flags); } SPA_API_SYSTEM int spa_system_pollfd_add(struct spa_system *object, int pfd, int fd, uint32_t events, void *data) { return spa_api_method_fast_r(int, -ENOTSUP, spa_system, &object->iface, pollfd_add, 0, pfd, fd, events, data); } SPA_API_SYSTEM int spa_system_pollfd_mod(struct spa_system *object, int pfd, int fd, uint32_t events, void *data) { return spa_api_method_fast_r(int, -ENOTSUP, spa_system, &object->iface, pollfd_mod, 0, pfd, fd, events, data); } SPA_API_SYSTEM int spa_system_pollfd_del(struct spa_system *object, int pfd, int fd) { return spa_api_method_fast_r(int, -ENOTSUP, spa_system, &object->iface, pollfd_del, 0, pfd, fd); } SPA_API_SYSTEM int spa_system_pollfd_wait(struct spa_system *object, int pfd, struct spa_poll_event *ev, int n_ev, int timeout) { return spa_api_method_fast_r(int, -ENOTSUP, spa_system, &object->iface, pollfd_wait, 0, pfd, ev, n_ev, timeout); } SPA_API_SYSTEM int spa_system_timerfd_create(struct spa_system *object, int clockid, int flags) { return spa_api_method_fast_r(int, -ENOTSUP, spa_system, &object->iface, timerfd_create, 0, clockid, flags); } SPA_API_SYSTEM int spa_system_timerfd_settime(struct spa_system *object, int fd, int flags, const struct itimerspec *new_value, struct itimerspec *old_value) { return spa_api_method_fast_r(int, -ENOTSUP, spa_system, &object->iface, timerfd_settime, 0, fd, flags, new_value, old_value); } SPA_API_SYSTEM int spa_system_timerfd_gettime(struct spa_system *object, int fd, struct itimerspec *curr_value) { return spa_api_method_fast_r(int, -ENOTSUP, spa_system, &object->iface, timerfd_gettime, 0, fd, curr_value); } SPA_API_SYSTEM int spa_system_timerfd_read(struct spa_system *object, int fd, uint64_t *expirations) { return spa_api_method_fast_r(int, -ENOTSUP, spa_system, &object->iface, timerfd_read, 0, fd, expirations); } SPA_API_SYSTEM int spa_system_eventfd_create(struct spa_system *object, int flags) { return spa_api_method_fast_r(int, -ENOTSUP, spa_system, &object->iface, eventfd_create, 0, flags); } SPA_API_SYSTEM int spa_system_eventfd_write(struct spa_system *object, int fd, uint64_t count) { return spa_api_method_fast_r(int, -ENOTSUP, spa_system, &object->iface, eventfd_write, 0, fd, count); } SPA_API_SYSTEM int spa_system_eventfd_read(struct spa_system *object, int fd, uint64_t *count) { return spa_api_method_fast_r(int, -ENOTSUP, spa_system, &object->iface, eventfd_read, 0, fd, count); } SPA_API_SYSTEM int spa_system_signalfd_create(struct spa_system *object, int signal, int flags) { return spa_api_method_fast_r(int, -ENOTSUP, spa_system, &object->iface, signalfd_create, 0, signal, flags); } SPA_API_SYSTEM int spa_system_signalfd_read(struct spa_system *object, int fd, int *signal) { return spa_api_method_fast_r(int, -ENOTSUP, spa_system, &object->iface, signalfd_read, 0, fd, signal); } /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_SYSTEM_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/support/thread.h000066400000000000000000000076331511204443500266340ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2021 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_THREAD_H #define SPA_THREAD_H #include #include #include #include #include #ifdef __cplusplus extern "C" { #endif #ifndef SPA_API_THREAD #ifdef SPA_API_IMPL #define SPA_API_THREAD SPA_API_IMPL #else #define SPA_API_THREAD static inline #endif #endif /** \defgroup spa_thread Thread * Threading utility interfaces */ /** * \addtogroup spa_thread * \{ */ /** a thread object. * This can be cast to a platform native thread, like pthread on posix systems */ #define SPA_TYPE_INFO_Thread SPA_TYPE_INFO_POINTER_BASE "Thread" struct spa_thread; #define SPA_TYPE_INTERFACE_ThreadUtils SPA_TYPE_INFO_INTERFACE_BASE "ThreadUtils" #define SPA_VERSION_THREAD_UTILS 0 struct spa_thread_utils { struct spa_interface iface; }; /** thread utils */ struct spa_thread_utils_methods { #define SPA_VERSION_THREAD_UTILS_METHODS 0 uint32_t version; /** create a new thread that runs \a start with \a arg */ struct spa_thread * (*create) (void *object, const struct spa_dict *props, void *(*start)(void*), void *arg); /** stop and join a thread */ int (*join)(void *object, struct spa_thread *thread, void **retval); /** get realtime priority range for threads created with \a props */ int (*get_rt_range) (void *object, const struct spa_dict *props, int *min, int *max); /** acquire realtime priority, a priority of -1 refers to the priority * configured in the realtime module */ int (*acquire_rt) (void *object, struct spa_thread *thread, int priority); /** drop realtime priority */ int (*drop_rt) (void *object, struct spa_thread *thread); }; /** \copydoc spa_thread_utils_methods.create * \sa spa_thread_utils_methods.create */ SPA_API_THREAD struct spa_thread *spa_thread_utils_create(struct spa_thread_utils *o, const struct spa_dict *props, void *(*start_routine)(void*), void *arg) { return spa_api_method_r(struct spa_thread *, NULL, spa_thread_utils, &o->iface, create, 0, props, start_routine, arg); } /** \copydoc spa_thread_utils_methods.join * \sa spa_thread_utils_methods.join */ SPA_API_THREAD int spa_thread_utils_join(struct spa_thread_utils *o, struct spa_thread *thread, void **retval) { return spa_api_method_r(int, -ENOTSUP, spa_thread_utils, &o->iface, join, 0, thread, retval); } /** \copydoc spa_thread_utils_methods.get_rt_range * \sa spa_thread_utils_methods.get_rt_range */ SPA_API_THREAD int spa_thread_utils_get_rt_range(struct spa_thread_utils *o, const struct spa_dict *props, int *min, int *max) { return spa_api_method_r(int, -ENOTSUP, spa_thread_utils, &o->iface, get_rt_range, 0, props, min, max); } /** \copydoc spa_thread_utils_methods.acquire_rt * \sa spa_thread_utils_methods.acquire_rt */ SPA_API_THREAD int spa_thread_utils_acquire_rt(struct spa_thread_utils *o, struct spa_thread *thread, int priority) { return spa_api_method_r(int, -ENOTSUP, spa_thread_utils, &o->iface, acquire_rt, 0, thread, priority); } /** \copydoc spa_thread_utils_methods.drop_rt * \sa spa_thread_utils_methods.drop_rt */ SPA_API_THREAD int spa_thread_utils_drop_rt(struct spa_thread_utils *o, struct spa_thread *thread) { return spa_api_method_r(int, -ENOTSUP, spa_thread_utils, &o->iface, drop_rt, 0, thread); } #define SPA_KEY_THREAD_NAME "thread.name" /* the thread name */ #define SPA_KEY_THREAD_STACK_SIZE "thread.stack-size" /* the stack size of the thread */ #define SPA_KEY_THREAD_AFFINITY "thread.affinity" /* array of CPUs for this thread */ #define SPA_KEY_THREAD_CREATOR "thread.creator" /* platform specific thread creator function */ #define SPA_KEY_THREAD_RESET_ON_FORK "thread.reset-on-fork" /* reset priority and policy for real-time threads on fork. Default true */ /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_THREAD_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/utils/000077500000000000000000000000001511204443500246275ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/utils/ansi.h000066400000000000000000000053111511204443500257320ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2021 Red Hat, Inc. */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_UTILS_ANSI_H #define SPA_UTILS_ANSI_H #ifdef __cplusplus extern "C" { #endif /** * \defgroup spa_ansi ANSI codes * ANSI color code macros */ /** * \addtogroup spa_ansi * \{ */ /** * Ansi escape sequences. Note that the color names are approximate only and * the actual rendering of the color depends on the terminal. */ #define SPA_ANSI_RESET "\x1B[0m" #define SPA_ANSI_BOLD "\x1B[1m" #define SPA_ANSI_ITALIC "\x1B[3m" #define SPA_ANSI_UNDERLINE "\x1B[4m" #define SPA_ANSI_BLACK "\x1B[0;30m" #define SPA_ANSI_RED "\x1B[0;31m" #define SPA_ANSI_GREEN "\x1B[0;32m" #define SPA_ANSI_YELLOW "\x1B[0;33m" #define SPA_ANSI_BLUE "\x1B[0;34m" #define SPA_ANSI_MAGENTA "\x1B[0;35m" #define SPA_ANSI_CYAN "\x1B[0;36m" #define SPA_ANSI_WHITE "\x1B[0;37m" #define SPA_ANSI_BRIGHT_BLACK "\x1B[90m" #define SPA_ANSI_BRIGHT_RED "\x1B[91m" #define SPA_ANSI_BRIGHT_GREEN "\x1B[92m" #define SPA_ANSI_BRIGHT_YELLOW "\x1B[93m" #define SPA_ANSI_BRIGHT_BLUE "\x1B[94m" #define SPA_ANSI_BRIGHT_MAGENTA "\x1B[95m" #define SPA_ANSI_BRIGHT_CYAN "\x1B[96m" #define SPA_ANSI_BRIGHT_WHITE "\x1B[97m" /* Shortcut because it's a common use-case and easier than combining both */ #define SPA_ANSI_BOLD_BLACK "\x1B[1;30m" #define SPA_ANSI_BOLD_RED "\x1B[1;31m" #define SPA_ANSI_BOLD_GREEN "\x1B[1;32m" #define SPA_ANSI_BOLD_YELLOW "\x1B[1;33m" #define SPA_ANSI_BOLD_BLUE "\x1B[1;34m" #define SPA_ANSI_BOLD_MAGENTA "\x1B[1;35m" #define SPA_ANSI_BOLD_CYAN "\x1B[1;36m" #define SPA_ANSI_BOLD_WHITE "\x1B[1;37m" #define SPA_ANSI_DARK_BLACK "\x1B[2;30m" #define SPA_ANSI_DARK_RED "\x1B[2;31m" #define SPA_ANSI_DARK_GREEN "\x1B[2;32m" #define SPA_ANSI_DARK_YELLOW "\x1B[2;33m" #define SPA_ANSI_DARK_BLUE "\x1B[2;34m" #define SPA_ANSI_DARK_MAGENTA "\x1B[2;35m" #define SPA_ANSI_DARK_CYAN "\x1B[2;36m" #define SPA_ANSI_DARK_WHITE "\x1B[2;37m" /* Background colors */ #define SPA_ANSI_BG_BLACK "\x1B[0;40m" #define SPA_ANSI_BG_RED "\x1B[0;41m" #define SPA_ANSI_BG_GREEN "\x1B[0;42m" #define SPA_ANSI_BG_YELLOW "\x1B[0;43m" #define SPA_ANSI_BG_BLUE "\x1B[0;44m" #define SPA_ANSI_BG_MAGENTA "\x1B[0;45m" #define SPA_ANSI_BG_CYAN "\x1B[0;46m" #define SPA_ANSI_BG_WHITE "\x1B[0;47m" #define SPA_ANSI_BG_BRIGHT_BLACK "\x1B[100m" #define SPA_ANSI_BG_BRIGHT_RED "\x1B[101m" #define SPA_ANSI_BG_BRIGHT_GREEN "\x1B[102m" #define SPA_ANSI_BG_BRIGHT_YELLOW "\x1B[103m" #define SPA_ANSI_BG_BRIGHT_BLUE "\x1B[104m" #define SPA_ANSI_BG_BRIGHT_MAGENTA "\x1B[105m" #define SPA_ANSI_BG_BRIGHT_CYAN "\x1B[106m" #define SPA_ANSI_BG_BRIGHT_WHITE "\x1B[107m" /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_UTILS_ANSI_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/utils/atomic.h000066400000000000000000000022621511204443500262560ustar00rootroot00000000000000/* Atomic operations */ /* SPDX-FileCopyrightText: Copyright © 2023 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_ATOMIC_H #define SPA_ATOMIC_H #ifdef __cplusplus extern "C" { #endif #define SPA_ATOMIC_CAS(v,ov,nv) \ ({ \ __typeof__(v) __ov = (ov); \ __atomic_compare_exchange_n(&(v), &__ov, (nv), \ 0, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST); \ }) #define SPA_ATOMIC_DEC(s) __atomic_sub_fetch(&(s), 1, __ATOMIC_SEQ_CST) #define SPA_ATOMIC_INC(s) __atomic_add_fetch(&(s), 1, __ATOMIC_SEQ_CST) #define SPA_ATOMIC_LOAD(s) __atomic_load_n(&(s), __ATOMIC_SEQ_CST) #define SPA_LOAD_ONCE(s) __atomic_load_n((s), __ATOMIC_RELAXED) #define SPA_STORE_ONCE(s) __atomic_store_n((s), __ATOMIC_RELAXED) #define SPA_ATOMIC_STORE(s,v) __atomic_store_n(&(s), (v), __ATOMIC_SEQ_CST) #define SPA_ATOMIC_XCHG(s,v) __atomic_exchange_n(&(s), (v), __ATOMIC_SEQ_CST) #define SPA_SEQ_WRITE(s) SPA_ATOMIC_INC(s) #define SPA_SEQ_WRITE_SUCCESS(s1,s2) ((s1) + 1 == (s2) && ((s2) & 1) == 0) #define SPA_SEQ_READ(s) SPA_ATOMIC_LOAD(s) #define SPA_SEQ_READ_SUCCESS(s1,s2) ((s1) == (s2) && ((s2) & 1) == 0) #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_ATOMIC_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/utils/cleanup.h000066400000000000000000000062071511204443500264340ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2023 PipeWire authors */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_UTILS_CLEANUP_H #define SPA_UTILS_CLEANUP_H #define spa_exchange(var, new_value) \ __extension__ ({ \ __typeof__(var) *_ptr_ = &(var); \ __typeof__(var) _old_value_ = *_ptr_; \ *_ptr_ = (new_value); \ _old_value_; \ }) /* ========================================================================== */ #if __GNUC__ >= 10 || defined(__clang__) #define spa_steal_ptr(ptr) ((__typeof__(*(ptr)) *) spa_exchange((ptr), NULL)) #else #define spa_steal_ptr(ptr) spa_exchange((ptr), NULL) #endif #define spa_clear_ptr(ptr, destructor) \ __extension__ ({ \ __typeof__(ptr) _old_value = spa_steal_ptr(ptr); \ if (_old_value) \ destructor(_old_value); \ (void) 0; \ }) /* ========================================================================== */ #include #include #define spa_steal_fd(fd) spa_exchange((fd), -1) #define spa_clear_fd(fd) \ __extension__ ({ \ int _old_value = spa_steal_fd(fd), _res = 0; \ if (_old_value >= 0) \ _res = close(_old_value); \ _res; \ }) /* ========================================================================== */ #ifdef __has_attribute #if __has_attribute(__cleanup__) #define spa_cleanup(func) __attribute__((__cleanup__(func))) #endif #endif #ifdef spa_cleanup #define SPA_DEFINE_AUTO_CLEANUP(name, type, ...) \ typedef __typeof__(type) _spa_auto_cleanup_type_ ## name; \ static inline void _spa_auto_cleanup_func_ ## name (__typeof__(type) *thing) \ { \ int _save_errno = errno; \ __VA_ARGS__ \ errno = _save_errno; \ } #define spa_auto(name) \ spa_cleanup(_spa_auto_cleanup_func_ ## name) \ _spa_auto_cleanup_type_ ## name #define SPA_DEFINE_AUTOPTR_CLEANUP(name, type, ...) \ typedef __typeof__(type) * _spa_autoptr_cleanup_type_ ## name; \ static inline void _spa_autoptr_cleanup_func_ ## name (__typeof__(type) **thing) \ { \ int _save_errno = errno; \ __VA_ARGS__ \ errno = _save_errno; \ } #define spa_autoptr(name) \ spa_cleanup(_spa_autoptr_cleanup_func_ ## name) \ _spa_autoptr_cleanup_type_ ## name /* ========================================================================== */ #include static inline void _spa_autofree_cleanup_func(void *p) { int save_errno = errno; free(*(void **) p); errno = save_errno; } #define spa_autofree spa_cleanup(_spa_autofree_cleanup_func) /* ========================================================================== */ static inline void _spa_autoclose_cleanup_func(int *fd) { int save_errno = errno; spa_clear_fd(*fd); errno = save_errno; } #define spa_autoclose spa_cleanup(_spa_autoclose_cleanup_func) /* ========================================================================== */ #include SPA_DEFINE_AUTOPTR_CLEANUP(FILE, FILE, { spa_clear_ptr(*thing, fclose); }) /* ========================================================================== */ #include SPA_DEFINE_AUTOPTR_CLEANUP(DIR, DIR, { spa_clear_ptr(*thing, closedir); }) #else #define SPA_DEFINE_AUTO_CLEANUP(name, type, ...) #define SPA_DEFINE_AUTOPTR_CLEANUP(name, type, ...) #endif #endif /* SPA_UTILS_CLEANUP_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/utils/defs.h000066400000000000000000000312501511204443500257220ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_UTILS_DEFS_H #define SPA_UTILS_DEFS_H #include #include #include #include #include #include #ifdef __cplusplus extern "C" { # if __cplusplus >= 201103L # define SPA_STATIC_ASSERT_IMPL(expr, msg, ...) static_assert(expr, msg) # define SPA_ALIGNOF alignof # endif #elif __STDC_VERSION__ >= 202311L # define SPA_STATIC_ASSERT_IMPL(expr, msg, ...) static_assert(expr, msg) # define SPA_ALIGNOF alignof #else # include # if __STDC_VERSION__ >= 201112L # define SPA_STATIC_ASSERT_IMPL(expr, msg, ...) _Static_assert(expr, msg) # define SPA_ALIGNOF _Alignof # endif #endif #ifndef SPA_STATIC_ASSERT_IMPL #define SPA_STATIC_ASSERT_IMPL(expr, ...) \ ((void)sizeof(struct { int spa_static_assertion_failed : 2 * !!(expr) - 1; })) #endif #ifndef SPA_ALIGNOF #define SPA_ALIGNOF __alignof__ #endif #define SPA_STATIC_ASSERT(expr, ...) SPA_STATIC_ASSERT_IMPL(expr, ## __VA_ARGS__, "`" #expr "` evaluated to false") #define SPA_CONCAT_NOEXPAND(a, b) a ## b #define SPA_CONCAT(a, b) SPA_CONCAT_NOEXPAND(a, b) /** * \defgroup spa_utils_defs Miscellaneous * Helper macros and functions */ /** * \addtogroup spa_utils_defs * \{ */ /** * SPA_FALLTHROUGH is an annotation to suppress compiler warnings about switch * cases that fall through without a break or return statement. SPA_FALLTHROUGH * is only needed on cases that have code: * * switch (foo) { * case 1: // These cases have no code. No fallthrough annotations are needed. * case 2: * case 3: * foo = 4; // This case has code, so a fallthrough annotation is needed: * SPA_FALLTHROUGH; * default: * return foo; * } */ #if defined(__clang__) && defined(__cplusplus) && __cplusplus >= 201103L /* clang's fallthrough annotations are only available starting in C++11. */ # define SPA_FALLTHROUGH [[clang::fallthrough]]; #elif __GNUC__ >= 7 || __clang_major__ >= 10 # define SPA_FALLTHROUGH __attribute__ ((fallthrough)); #else # define SPA_FALLTHROUGH /* FALLTHROUGH */ #endif #define SPA_FLAG_MASK(field,mask,flag) (((field) & (mask)) == (flag)) #define SPA_FLAG_IS_SET(field,flag) SPA_FLAG_MASK(field, flag, flag) #define SPA_FLAG_SET(field,flag) ((field) |= (flag)) #define SPA_FLAG_CLEAR(field, flag) \ ({ \ SPA_STATIC_ASSERT(__builtin_constant_p(flag) ? \ (__typeof__(flag))(__typeof__(field))(__typeof__(flag))(flag) == (flag) : \ sizeof(field) >= sizeof(flag), \ "truncation problem when masking " #field \ " with ~" #flag); \ ((field) &= ~(__typeof__(field))(flag)); \ }) #define SPA_FLAG_UPDATE(field,flag,val) ((val) ? SPA_FLAG_SET((field),(flag)) : SPA_FLAG_CLEAR((field),(flag))) enum spa_direction { SPA_DIRECTION_INPUT = 0, SPA_DIRECTION_OUTPUT = 1, }; #define SPA_DIRECTION_REVERSE(d) ((d) ^ 1) #define SPA_RECTANGLE(width,height) ((struct spa_rectangle){ (width), (height) }) struct spa_rectangle { uint32_t width; uint32_t height; }; #define SPA_POINT(x,y) ((struct spa_point){ (x), (y) }) struct spa_point { int32_t x; int32_t y; }; #define SPA_REGION(x,y,width,height) ((struct spa_region){ SPA_POINT(x,y), SPA_RECTANGLE(width,height) }) struct spa_region { struct spa_point position; struct spa_rectangle size; }; #define SPA_FRACTION(num,denom) ((struct spa_fraction){ (num), (denom) }) struct spa_fraction { uint32_t num; uint32_t denom; }; #define SPA_N_ELEMENTS(arr) (sizeof(arr) / sizeof((arr)[0])) /** * Array iterator macro. Usage: * ```c * struct foo array[16]; * struct foo *f; * SPA_FOR_EACH_ELEMENT(array, f) { * f->bar = baz; * } * ``` */ #define SPA_FOR_EACH_ELEMENT(arr, ptr) \ for ((ptr) = arr; (ptr) < (arr) + SPA_N_ELEMENTS(arr); (ptr)++) #define SPA_FOR_EACH_ELEMENT_VAR(arr, var) \ for (__typeof__((arr)[0])* var = arr; (var) < (arr) + SPA_N_ELEMENTS(arr); (var)++) #define SPA_ABS(a) \ ({ \ __typeof__(a) _a = (a); \ SPA_LIKELY(_a >= 0) ? _a : -_a; \ }) #define SPA_MIN(a,b) \ ({ \ __typeof__(a) _min_a = (a); \ __typeof__(b) _min_b = (b); \ SPA_LIKELY(_min_a <= _min_b) ? _min_a : _min_b; \ }) #define SPA_MAX(a,b) \ ({ \ __typeof__(a) _max_a = (a); \ __typeof__(b) _max_b = (b); \ SPA_LIKELY(_max_a >= _max_b) ? _max_a : _max_b; \ }) #define SPA_CLAMP(v,low,high) \ ({ \ __typeof__(v) _v = (v); \ __typeof__(low) _low = (low); \ __typeof__(high) _high = (high); \ SPA_MIN(SPA_MAX(_v, _low), _high); \ }) #define SPA_CLAMPF(v,low,high) \ ({ \ fminf(fmaxf(v, low), high); \ }) #define SPA_CLAMPD(v,low,high) \ ({ \ fmin(fmax(v, low), high); \ }) #define SPA_SWAP(a,b) \ ({ \ __typeof__(a) _t = (a); \ (a) = b; (b) = _t; \ }) #define SPA_TYPECHECK(type,x) \ ({ type _dummy; \ typeof(x) _dummy2; \ (void)(&_dummy == &_dummy2); \ x; \ }) /** 3-way comparison. NaN > NaN and NaN > finite numbers */ #define SPA_CMP(a, b) \ ({ \ __typeof__(a) _a = (a); \ __typeof__(b) _b = (b); \ (_a > _b) ? 1 : (_a == _b) ? 0 : (_a < _b) ? -1 \ : (_a == _a) ? -1 : (_b == _b) ? 1 \ : 1; \ }) /** * Return the address (buffer + offset) as pointer of \a type */ #define SPA_PTROFF(ptr_,offset_,type_) ((type_*)((uintptr_t)(ptr_) + (ptrdiff_t)(offset_))) #define SPA_PTROFF_ALIGN(ptr_,offset_,alignment_,type_) \ SPA_PTR_ALIGN(SPA_PTROFF(ptr_,offset_,type_),alignment_,type_) /** * Deprecated, use SPA_PTROFF and SPA_PTROFF_ALIGN instead */ #define SPA_MEMBER(b,o,t) SPA_PTROFF(b,o,t) #define SPA_MEMBER_ALIGN(b,o,a,t) SPA_PTROFF_ALIGN(b,o,a,t) #define SPA_CONTAINER_OF(p,t,m) ((t*)((uintptr_t)(p) - offsetof(t,m))) #define SPA_PTRDIFF(p1,p2) ((intptr_t)(p1) - (intptr_t)(p2)) #define SPA_PTR_TO_UINT32(p) ((uint32_t) ((uintptr_t) (p))) #define SPA_UINT32_TO_PTR(u) ((void*) ((uintptr_t) (u))) #define SPA_TIME_INVALID ((int64_t)INT64_MIN) #define SPA_IDX_INVALID ((unsigned int)-1) #define SPA_ID_INVALID ((uint32_t)0xffffffff) #define SPA_NSEC_PER_SEC (1000000000LL) #define SPA_NSEC_PER_MSEC (1000000ll) #define SPA_NSEC_PER_USEC (1000ll) #define SPA_USEC_PER_SEC (1000000ll) #define SPA_USEC_PER_MSEC (1000ll) #define SPA_MSEC_PER_SEC (1000ll) #define SPA_TIMESPEC_TO_NSEC(ts) ((ts)->tv_sec * SPA_NSEC_PER_SEC + (ts)->tv_nsec) #define SPA_TIMESPEC_TO_USEC(ts) ((ts)->tv_sec * SPA_USEC_PER_SEC + (ts)->tv_nsec / SPA_NSEC_PER_USEC) #define SPA_TIMEVAL_TO_NSEC(tv) ((tv)->tv_sec * SPA_NSEC_PER_SEC + (tv)->tv_usec * SPA_NSEC_PER_USEC) #define SPA_TIMEVAL_TO_USEC(tv) ((tv)->tv_sec * SPA_USEC_PER_SEC + (tv)->tv_usec) #ifdef __GNUC__ #define SPA_PRINTF_FUNC(fmt, arg1) __attribute__((format(printf, fmt, arg1))) #define SPA_FORMAT_ARG_FUNC(arg1) __attribute__((format_arg(arg1))) #define SPA_ALIGNED(align) __attribute__((aligned(align))) #define SPA_DEPRECATED __attribute__ ((deprecated)) #define SPA_EXPORT __attribute__((visibility("default"))) #define SPA_SENTINEL __attribute__((__sentinel__)) #define SPA_UNUSED __attribute__ ((unused)) #define SPA_NORETURN __attribute__ ((noreturn)) #define SPA_WARN_UNUSED_RESULT __attribute__ ((warn_unused_result)) #define SPA_BARRIER __asm__ __volatile__("": : :"memory") #else #define SPA_PRINTF_FUNC(fmt, arg1) #define SPA_FORMAT_ARG_FUNC(arg1) #define SPA_ALIGNED(align) #define SPA_DEPRECATED #define SPA_EXPORT #define SPA_SENTINEL #define SPA_UNUSED #define SPA_NORETURN #define SPA_WARN_UNUSED_RESULT #define SPA_BARRIER #endif #ifndef SPA_API_IMPL #define SPA_API_PROTO static inline #define SPA_API_IMPL static inline #endif #ifndef SPA_API_UTILS_DEFS #ifdef SPA_API_IMPL #define SPA_API_UTILS_DEFS SPA_API_IMPL #else #define SPA_API_UTILS_DEFS static inline #endif #endif #if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L #define SPA_RESTRICT restrict #elif defined(__GNUC__) && __GNUC__ >= 4 #define SPA_RESTRICT __restrict__ #else #define SPA_RESTRICT #endif #define SPA_ROUND_DOWN(num,value) \ ({ \ __typeof__(num) _num = (num); \ ((_num) - ((_num) % (value))); \ }) #define SPA_ROUND_UP(num,value) \ ({ \ __typeof__(value) _v = (value); \ ((((num) + (_v) - 1) / (_v)) * (_v)); \ }) #define SPA_ROUND_MASK(num,mask) ((__typeof__(num))((mask)-1)) #define SPA_ROUND_DOWN_N(num,align) ((num) & ~SPA_ROUND_MASK(num, align)) #define SPA_ROUND_UP_N(num,align) ((((num)-1) | SPA_ROUND_MASK(num, align))+1) #define SPA_SCALE32(val,num,denom) \ ({ \ uint64_t _val = (val); \ uint64_t _denom = (denom); \ (uint32_t)(((_val) * (num)) / (_denom)); \ }) #define SPA_SCALE32_UP(val,num,denom) \ ({ \ uint64_t _val = (val); \ uint64_t _denom = (denom); \ (uint32_t)(((_val) * (num) + (_denom)-1) / (_denom)); \ }) #define SPA_PTR_ALIGNMENT(p,align) ((uintptr_t)(p) & ((align)-1)) #define SPA_IS_ALIGNED(p,align) (SPA_PTR_ALIGNMENT(p,align) == 0) #define SPA_PTR_ALIGN(p,align,type) ((type*)SPA_ROUND_UP_N((intptr_t)(p), (intptr_t)(align))) #ifndef SPA_LIKELY #ifdef __GNUC__ #define SPA_LIKELY(x) (__builtin_expect(!!(x),1)) #define SPA_UNLIKELY(x) (__builtin_expect(!!(x),0)) #else #define SPA_LIKELY(x) (x) #define SPA_UNLIKELY(x) (x) #endif #endif SPA_API_UTILS_DEFS bool spa_ptrinside(const void *p1, size_t s1, const void *p2, size_t s2, size_t *remaining) { if (SPA_LIKELY((uintptr_t)p1 <= (uintptr_t)p2 && s2 <= s1 && (uintptr_t)p2 - (uintptr_t)p1 <= s1 - s2)) { if (remaining != NULL) *remaining = ((uintptr_t)p1 + s1) - ((uintptr_t)p2 + s2); return true; } else { if (remaining != NULL) *remaining = 0; return false; } } SPA_API_UTILS_DEFS bool spa_ptr_inside_and_aligned(const void *p1, size_t s1, const void *p2, size_t s2, size_t align, size_t *remaining) { if (SPA_IS_ALIGNED(p2, align)) { return spa_ptrinside(p1, s1, p2, s2, remaining); } else { if (remaining != NULL) *remaining = 0; return false; } } #define spa_ptr_type_inside(p1, s1, p2, type, remaining) \ spa_ptr_inside_and_aligned(p1, s1, p2, sizeof(type), SPA_ALIGNOF(type), remaining) #define SPA_PTR_TO_INT(p) ((int) ((intptr_t) (p))) #define SPA_INT_TO_PTR(u) ((void*) ((intptr_t) (u))) #define SPA_STRINGIFY_1(...) #__VA_ARGS__ #define SPA_STRINGIFY(...) SPA_STRINGIFY_1(__VA_ARGS__) struct spa_error_location { int line; int col; size_t len; const char *location; const char *reason; }; #define spa_return_if_fail(expr) \ do { \ if (SPA_UNLIKELY(!(expr))) { \ fprintf(stderr, "'%s' failed at %s:%u %s()\n", \ #expr , __FILE__, __LINE__, __func__); \ return; \ } \ } while(false) #define spa_return_val_if_fail(expr, val) \ do { \ if (SPA_UNLIKELY(!(expr))) { \ fprintf(stderr, "'%s' failed at %s:%u %s()\n", \ #expr , __FILE__, __LINE__, __func__); \ return (val); \ } \ } while(false) /* spa_assert_se() is an assert which guarantees side effects of x, * i.e. is never optimized away, regardless of NDEBUG or FASTPATH. */ #ifndef __COVERITY__ #define spa_assert_se(expr) \ do { \ if (SPA_UNLIKELY(!(expr))) { \ fprintf(stderr, "'%s' failed at %s:%u %s()\n", \ #expr , __FILE__, __LINE__, __func__); \ abort(); \ } \ } while (false) #else #define spa_assert_se(expr) \ do { \ int _unique_var = (expr); \ if (!_unique_var) \ abort(); \ } while (false) #endif /* Does exactly nothing */ #define spa_nop() do {} while (false) #ifdef NDEBUG #define spa_assert(expr) spa_nop() #elif defined (FASTPATH) #define spa_assert(expr) spa_assert_se(expr) #else #define spa_assert(expr) spa_assert_se(expr) #endif #ifdef NDEBUG #define spa_assert_not_reached() abort() #else #define spa_assert_not_reached() \ do { \ fprintf(stderr, "Code should not be reached at %s:%u %s()\n", \ __FILE__, __LINE__, __func__); \ abort(); \ } while (false) #endif #define spa_memzero(x,l) (memset((x), 0, (l))) #define spa_zero(x) (spa_memzero(&(x), sizeof(x))) #ifdef SPA_DEBUG_MEMCPY #define spa_memcpy(d,s,n) \ ({ \ fprintf(stderr, "%s:%u %s() memcpy(%p, %p, %zd)\n", \ __FILE__, __LINE__, __func__, (d), (s), (size_t)(n)); \ memcpy(d,s,n); \ }) #define spa_memmove(d,s,n) \ ({ \ fprintf(stderr, "%s:%u %s() memmove(%p, %p, %zd)\n", \ __FILE__, __LINE__, __func__, (d), (s), (size_t)(n)); \ memmove(d,s,n); \ }) #else #define spa_memcpy(d,s,n) memcpy(d,s,n) #define spa_memmove(d,s,n) memmove(d,s,n) #endif #define spa_aprintf(_fmt, ...) \ ({ \ char *_strp; \ if (asprintf(&(_strp), (_fmt), ## __VA_ARGS__ ) == -1) \ _strp = NULL; \ _strp; \ }) /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_UTILS_DEFS_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/utils/dict.h000066400000000000000000000052401511204443500257240ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_DICT_H #define SPA_DICT_H #include #include #ifdef __cplusplus extern "C" { #endif #ifndef SPA_API_DICT #ifdef SPA_API_IMPL #define SPA_API_DICT SPA_API_IMPL #else #define SPA_API_DICT static inline #endif #endif /** * \defgroup spa_dict Dictionary * Dictionary data structure */ /** * \addtogroup spa_dict * \{ */ struct spa_dict_item { const char *key; const char *value; }; #define SPA_DICT_ITEM(key,value) ((struct spa_dict_item) { (key), (value) }) #define SPA_DICT_ITEM_INIT(key,value) SPA_DICT_ITEM(key,value) struct spa_dict { #define SPA_DICT_FLAG_SORTED (1<<0) /**< items are sorted */ uint32_t flags; uint32_t n_items; const struct spa_dict_item *items; }; #define SPA_DICT(items,n_items) ((struct spa_dict) { 0, (n_items), (items) }) #define SPA_DICT_ARRAY(items) SPA_DICT((items),SPA_N_ELEMENTS(items)) #define SPA_DICT_ITEMS(...) SPA_DICT_ARRAY(((struct spa_dict_item[]) { __VA_ARGS__})) #define SPA_DICT_INIT(items,n_items) SPA_DICT(items,n_items) #define SPA_DICT_INIT_ARRAY(items) SPA_DICT_ARRAY(items) #define spa_dict_for_each(item, dict) \ for ((item) = (dict)->items; \ (item) < &(dict)->items[(dict)->n_items]; \ (item)++) SPA_API_DICT int spa_dict_item_compare(const void *i1, const void *i2) { const struct spa_dict_item *it1 = (const struct spa_dict_item *)i1, *it2 = (const struct spa_dict_item *)i2; return strcmp(it1->key, it2->key); } SPA_API_DICT void spa_dict_qsort(struct spa_dict *dict) { if (dict->n_items > 0) qsort((void*)dict->items, dict->n_items, sizeof(struct spa_dict_item), spa_dict_item_compare); SPA_FLAG_SET(dict->flags, SPA_DICT_FLAG_SORTED); } SPA_API_DICT const struct spa_dict_item *spa_dict_lookup_item(const struct spa_dict *dict, const char *key) { const struct spa_dict_item *item; if (SPA_FLAG_IS_SET(dict->flags, SPA_DICT_FLAG_SORTED) && dict->n_items > 0) { struct spa_dict_item k = SPA_DICT_ITEM_INIT(key, NULL); item = (const struct spa_dict_item *)bsearch(&k, (const void *) dict->items, dict->n_items, sizeof(struct spa_dict_item), spa_dict_item_compare); if (item != NULL) return item; } else { spa_dict_for_each(item, dict) { if (!strcmp(item->key, key)) return item; } } return NULL; } SPA_API_DICT const char *spa_dict_lookup(const struct spa_dict *dict, const char *key) { const struct spa_dict_item *item = spa_dict_lookup_item(dict, key); return item ? item->value : NULL; } /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_DICT_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/utils/dll.h000066400000000000000000000022301511204443500255500ustar00rootroot00000000000000/* Simple DLL */ /* SPDX-FileCopyrightText: Copyright © 2019 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_DLL_H #define SPA_DLL_H #include #include #include #ifdef __cplusplus extern "C" { #endif #ifndef SPA_API_DLL #ifdef SPA_API_IMPL #define SPA_API_DLL SPA_API_IMPL #else #define SPA_API_DLL static inline #endif #endif #define SPA_DLL_BW_MAX 0.128 #define SPA_DLL_BW_MIN 0.016 struct spa_dll { double bw; double z1, z2, z3; double w0, w1, w2; }; SPA_API_DLL void spa_dll_init(struct spa_dll *dll) { dll->bw = 0.0; dll->z1 = dll->z2 = dll->z3 = 0.0; } SPA_API_DLL void spa_dll_set_bw(struct spa_dll *dll, double bw, unsigned period, unsigned rate) { double w = 2 * M_PI * bw * period / rate; dll->w0 = 1.0 - exp (-20.0 * w); dll->w1 = w * 1.5 / period; dll->w2 = w / 1.5; dll->bw = bw; } SPA_API_DLL double spa_dll_update(struct spa_dll *dll, double err) { dll->z1 += dll->w0 * (dll->w1 * err - dll->z1); dll->z2 += dll->w0 * (dll->z1 - dll->z2); dll->z3 += dll->w2 * dll->z2; return 1.0 - (dll->z2 + dll->z3); } #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_DLL_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/utils/endian.h000066400000000000000000000011771511204443500262440ustar00rootroot00000000000000/* Spa */ /* SPDX-FileCopyrightText: Copyright © 2019 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_ENDIAN_H #define SPA_ENDIAN_H #if defined(__FreeBSD__) || defined(__MidnightBSD__) #include #define bswap_16 bswap16 #define bswap_32 bswap32 #define bswap_64 bswap64 #elif defined(_MSC_VER) && defined(_WIN32) #include #define __LITTLE_ENDIAN 1234 #define __BIG_ENDIAN 4321 #define __BYTE_ORDER __LITTLE_ENDIAN #define bswap_16 _byteswap_ushort #define bswap_32 _byteswap_ulong #define bswap_64 _byteswap_uint64 #else #include #include #endif #endif /* SPA_ENDIAN_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/utils/enum-types.h000066400000000000000000000025521511204443500271120ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_ENUM_TYPES_H #define SPA_ENUM_TYPES_H #include #include #ifdef __cplusplus extern "C" { #endif /** * \addtogroup spa_types * \{ */ #define SPA_TYPE_INFO_Direction SPA_TYPE_INFO_ENUM_BASE "Direction" #define SPA_TYPE_INFO_DIRECTION_BASE SPA_TYPE_INFO_Direction ":" static const struct spa_type_info spa_type_direction[] = { { SPA_DIRECTION_INPUT, SPA_TYPE_Int, SPA_TYPE_INFO_DIRECTION_BASE "Input", NULL }, { SPA_DIRECTION_OUTPUT, SPA_TYPE_Int, SPA_TYPE_INFO_DIRECTION_BASE "Output", NULL }, { 0, 0, NULL, NULL } }; #define SPA_TYPE_INFO_Choice SPA_TYPE_INFO_ENUM_BASE "Choice" #define SPA_TYPE_INFO_CHOICE_BASE SPA_TYPE_INFO_Choice ":" static const struct spa_type_info spa_type_choice[] = { { SPA_CHOICE_None, SPA_TYPE_Int, SPA_TYPE_INFO_CHOICE_BASE "None", NULL }, { SPA_CHOICE_Range, SPA_TYPE_Int, SPA_TYPE_INFO_CHOICE_BASE "Range", NULL }, { SPA_CHOICE_Step, SPA_TYPE_Int, SPA_TYPE_INFO_CHOICE_BASE "Step", NULL }, { SPA_CHOICE_Enum, SPA_TYPE_Int, SPA_TYPE_INFO_CHOICE_BASE "Enum", NULL }, { SPA_CHOICE_Flags, SPA_TYPE_Int, SPA_TYPE_INFO_CHOICE_BASE "Flags", NULL }, { 0, 0, NULL, NULL } }; /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_TYPE_INFO_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/utils/hook.h000066400000000000000000000407071511204443500257500ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_HOOK_H #define SPA_HOOK_H #include #include #ifdef __cplusplus extern "C" { #endif #ifndef SPA_API_HOOK #ifdef SPA_API_IMPL #define SPA_API_HOOK SPA_API_IMPL #else #define SPA_API_HOOK static inline #endif #endif /** \defgroup spa_interfaces Interfaces * * \brief Generic implementation of implementation-independent interfaces * * A SPA Interface is a generic struct that, together with a few macros, * provides a generic way of invoking methods on objects without knowing the * details of the implementation. * * The primary interaction with interfaces is through macros that expand into * the right method call. For the implementation of an interface, we need two * structs and a macro to invoke the `bar` method: * * \code{.c} * // this struct must be public and defines the interface to a * // struct foo * struct foo_methods { * uint32_t version; * void (*bar)(void *object, const char *msg); * }; * * // this struct does not need to be public * struct foo { * struct spa_interface iface; // must be first element, see foo_bar() * int some_other_field; * ... * }; * * // if struct foo is private, we need to cast to a * // generic spa_interface object * #define foo_bar(obj, ...) ({ \ * struct foo *f = obj; * spa_interface_call((struct spa_interface *)f, // pointer to spa_interface in foo * struct foo_methods, // type of callbacks * bar, // name of methods * 0, // hardcoded version to match foo_methods->version * __VA_ARGS__ // pass rest of args through * );/ * }) * \endcode * * The `struct foo_methods` and the invocation macro `foo_bar()` must be * available to the caller. The implementation of `struct foo` can be private. * * \code{.c} * void main(void) { * struct foo *myfoo = get_foo_from_somewhere(); * foo_bar(myfoo, "Invoking bar() on myfoo"); * } * \endcode * The expansion of `foo_bar()` resolves roughly into this code: * \code{.c} * void main(void) { * struct foo *myfoo = get_foo_from_somewhere(); * // foo_bar(myfoo, "Invoking bar() on myfoo"); * const struct foo_methods *methods = ((struct spa_interface*)myfoo)->cb; * if (0 >= methods->version && // version check * methods->bar) // compile error if this function does not exist, * methods->bar(myfoo, "Invoking bar() on myfoo"); * } * \endcode * * The typecast used in `foo_bar()` allows `struct foo` to be opaque to the * caller. The implementation may assign the callback methods at object * instantiation, and the caller will transparently invoke the method on the * given object. For example, the following code assigns a different `bar()` method on * Mondays - the caller does not need to know this. * \code{.c} * * static void bar_stdout(struct foo *f, const char *msg) { * printf(msg); * } * static void bar_stderr(struct foo *f, const char *msg) { * fprintf(stderr, msg); * } * * struct foo* get_foo_from_somewhere() { * struct foo *f = calloc(sizeof struct foo); * // illustrative only, use SPA_INTERFACE_INIT() * f->iface->cb = (struct foo_methods*) { .bar = bar_stdout }; * if (today_is_monday) * f->iface->cb = (struct foo_methods*) { .bar = bar_stderr }; * return f; * } * \endcode */ /** * \addtogroup spa_interfaces * \{ */ /** \struct spa_callbacks * Callbacks, contains the structure with functions and the data passed * to the functions. The structure should also contain a version field that * is checked. */ struct spa_callbacks { const void *funcs; void *data; }; /** Check if a callback \a c is of at least version \a v */ #define SPA_CALLBACK_VERSION_MIN(c,v) ((c) && ((v) == 0 || (c)->version > (v)-1)) /** Check if a callback \a c has method \a m of version \a v */ #define SPA_CALLBACK_CHECK(c,m,v) (SPA_CALLBACK_VERSION_MIN(c,v) && (c)->m) /** * Initialize the set of functions \a funcs as a \ref spa_callbacks, together * with \a _data. */ #define SPA_CALLBACKS_INIT(_funcs,_data) ((struct spa_callbacks){ (_funcs), (_data), }) /** \struct spa_interface */ struct spa_interface { const char *type; uint32_t version; struct spa_callbacks cb; }; /** * Initialize a \ref spa_interface. * * \code{.c} * const static struct foo_methods foo_funcs = { * .bar = some_bar_implementation, * }; * * struct foo *f = malloc(...); * f->iface = SPA_INTERFACE_INIT("foo type", 0, foo_funcs, NULL); * \endcode * */ #define SPA_INTERFACE_INIT(_type,_version,_funcs,_data) \ ((struct spa_interface){ (_type), (_version), SPA_CALLBACKS_INIT(_funcs,_data), }) /** * Invoke method named \a method in the \a callbacks. * The \a method_type defines the type of the method struct. * Returns true if the method could be called, false otherwise. */ #define spa_callbacks_call(callbacks,type,method,vers,...) \ ({ \ const type *_f = (const type *) (callbacks)->funcs; \ bool _res = SPA_CALLBACK_CHECK(_f,method,vers); \ if (SPA_LIKELY(_res)) \ (_f->method)((callbacks)->data, ## __VA_ARGS__); \ _res; \ }) #define spa_callbacks_call_fast(callbacks,type,method,vers,...) \ ({ \ const type *_f = (const type *) (callbacks)->funcs; \ (_f->method)((callbacks)->data, ## __VA_ARGS__); \ true; \ }) /** * True if the \a callbacks are of version \a vers, false otherwise */ #define spa_callback_version_min(callbacks,type,vers) \ ({ \ const type *_f = (const type *) (callbacks)->funcs; \ SPA_CALLBACK_VERSION_MIN(_f,vers); \ }) /** * True if the \a callbacks contains \a method of version * \a vers, false otherwise */ #define spa_callback_check(callbacks,type,method,vers) \ ({ \ const type *_f = (const type *) (callbacks)->funcs; \ SPA_CALLBACK_CHECK(_f,method,vers); \ }) /** * Invoke method named \a method in the \a callbacks. * The \a method_type defines the type of the method struct. * * The return value is stored in \a res. */ #define spa_callbacks_call_res(callbacks,type,res,method,vers,...) \ ({ \ const type *_f = (const type *) (callbacks)->funcs; \ if (SPA_LIKELY(SPA_CALLBACK_CHECK(_f,method,vers))) \ res = (_f->method)((callbacks)->data, ## __VA_ARGS__); \ res; \ }) #define spa_callbacks_call_fast_res(callbacks,type,res,method,vers,...) \ ({ \ const type *_f = (const type *) (callbacks)->funcs; \ res = (_f->method)((callbacks)->data, ## __VA_ARGS__); \ }) /** * True if the \a iface's callbacks are of version \a vers, false otherwise */ #define spa_interface_callback_version_min(iface,method_type,vers) \ spa_callback_version_min(&(iface)->cb, method_type, vers) /** * True if the \a iface's callback \a method is of version \a vers * and exists, false otherwise */ #define spa_interface_callback_check(iface,method_type,method,vers) \ spa_callback_check(&(iface)->cb, method_type, method, vers) /** * Invoke method named \a method in the callbacks on the given interface object. * The \a method_type defines the type of the method struct, not the interface * itself. */ #define spa_interface_call(iface,method_type,method,vers,...) \ spa_callbacks_call(&(iface)->cb,method_type,method,vers,##__VA_ARGS__) #define spa_interface_call_fast(iface,method_type,method,vers,...) \ spa_callbacks_call_fast(&(iface)->cb,method_type,method,vers,##__VA_ARGS__) /** * Invoke method named \a method in the callbacks on the given interface object. * The \a method_type defines the type of the method struct, not the interface * itself. * * The return value is stored in \a res. */ #define spa_interface_call_res(iface,method_type,res,method,vers,...) \ spa_callbacks_call_res(&(iface)->cb,method_type,res,method,vers,##__VA_ARGS__) #define spa_interface_call_fast_res(iface,method_type,res,method,vers,...) \ spa_callbacks_call_fast_res(&(iface)->cb,method_type,res,method,vers,##__VA_ARGS__) #define spa_api_func_v(o,method,version,...) \ ({ \ if (SPA_LIKELY(SPA_CALLBACK_CHECK(o,method,version))) \ ((o)->method)(o, ##__VA_ARGS__); \ }) #define spa_api_func_r(rtype,def,o,method,version,...) \ ({ \ rtype _res = def; \ if (SPA_LIKELY(SPA_CALLBACK_CHECK(o,method,version))) \ _res = ((o)->method)(o, ##__VA_ARGS__); \ _res; \ }) #define spa_api_func_fast(o,method,...) \ ({ \ ((o)->method)(o, ##__VA_ARGS__); \ }) #define spa_api_method_v(type,o,method,version,...) \ ({ \ struct spa_interface *_i = o; \ spa_interface_call(_i, struct type ##_methods, \ method, version, ##__VA_ARGS__); \ }) #define spa_api_method_r(rtype,def,type,o,method,version,...) \ ({ \ rtype _res = def; \ struct spa_interface *_i = o; \ spa_interface_call_res(_i, struct type ##_methods, \ _res, method, version, ##__VA_ARGS__); \ _res; \ }) #define spa_api_method_null_v(type,co,o,method,version,...) \ ({ \ struct type *_co = co; \ if (SPA_LIKELY(_co != NULL)) { \ struct spa_interface *_i = o; \ spa_interface_call(_i, struct type ##_methods, \ method, version, ##__VA_ARGS__); \ } \ }) #define spa_api_method_null_r(rtype,def,type,co,o,method,version,...) \ ({ \ rtype _res = def; \ struct type *_co = co; \ if (SPA_LIKELY(_co != NULL)) { \ struct spa_interface *_i = o; \ spa_interface_call_res(_i, struct type ##_methods, \ _res, method, version, ##__VA_ARGS__); \ } \ _res; \ }) #define spa_api_method_fast_v(type,o,method,version,...) \ ({ \ struct spa_interface *_i = o; \ spa_interface_call_fast(_i, struct type ##_methods, \ method, version, ##__VA_ARGS__); \ }) #define spa_api_method_fast_r(rtype,def,type,o,method,version,...) \ ({ \ rtype _res = def; \ struct spa_interface *_i = o; \ spa_interface_call_fast_res(_i, struct type ##_methods, \ _res, method, version, ##__VA_ARGS__); \ _res; \ }) /** * \} */ /** \defgroup spa_hooks Hooks * * A SPA Hook is a data structure to keep track of callbacks. It is similar to * the \ref spa_interfaces and typically used where an implementation allows * for multiple external callback functions. For example, an implementation may * use a hook list to implement signals with each caller using a hook to * register callbacks to be invoked on those signals. * * The below (pseudo)code is a minimal example outlining the use of hooks: * \code{.c} * // the public interface * #define VERSION_BAR_EVENTS 0 // version of the vtable * struct bar_events { * uint32_t version; // NOTE: an integral member named `version` * // must be present in the vtable * void (*boom)(void *data, const char *msg); * }; * * // private implementation * struct party { * struct spa_hook_list bar_list; * }; * * void party_add_event_listener(struct party *p, struct spa_hook *listener, * const struct bar_events *events, void *data) * { * spa_hook_list_append(&p->bar_list, listener, events, data); * } * * static void party_on(struct party *p) * { * // NOTE: this is a macro, it evaluates to an integer, * // which is the number of hooks called * spa_hook_list_call(&p->list, * struct bar_events, // vtable type * boom, // function name * 0, // hardcoded version, * // usually the version in which `boom` * // has been added to the vtable * "party on, wayne" // function argument(s) * ); * } * \endcode * * In the caller, the hooks can be used like this: * \code{.c} * static void boom_cb(void *data, const char *msg) { * // data is userdata from main() * printf("%s", msg); * } * * static const struct bar_events events = { * .version = VERSION_BAR_EVENTS, // version of the implemented interface * .boom = boom_cb, * }; * * void main(void) { * void *userdata = whatever; * struct spa_hook hook; * struct party *p = start_the_party(); * * party_add_event_listener(p, &hook, &events, userdata); * * mainloop(); * return 0; * } * * \endcode */ /** * \addtogroup spa_hooks * \{ */ /** \struct spa_hook_list * A list of hooks. This struct is primarily used by * implementation that use multiple caller-provided \ref spa_hook. */ struct spa_hook_list { struct spa_list list; }; /** \struct spa_hook * A hook, contains the structure with functions and the data passed * to the functions. * * A hook should be treated as opaque by the caller. */ struct spa_hook { struct spa_list link; struct spa_callbacks cb; /** callback and data for the hook list, private to the * hook_list implementor */ void (*removed) (struct spa_hook *hook); void *priv; }; /** Initialize a hook list to the empty list*/ SPA_API_HOOK void spa_hook_list_init(struct spa_hook_list *list) { spa_list_init(&list->list); } SPA_API_HOOK bool spa_hook_list_is_empty(struct spa_hook_list *list) { return spa_list_is_empty(&list->list); } /** Append a hook. */ SPA_API_HOOK void spa_hook_list_append(struct spa_hook_list *list, struct spa_hook *hook, const void *funcs, void *data) { spa_zero(*hook); hook->cb = SPA_CALLBACKS_INIT(funcs, data); spa_list_append(&list->list, &hook->link); } /** Prepend a hook */ SPA_API_HOOK void spa_hook_list_prepend(struct spa_hook_list *list, struct spa_hook *hook, const void *funcs, void *data) { spa_zero(*hook); hook->cb = SPA_CALLBACKS_INIT(funcs, data); spa_list_prepend(&list->list, &hook->link); } /** Remove a hook */ SPA_API_HOOK void spa_hook_remove(struct spa_hook *hook) { if (spa_list_is_initialized(&hook->link)) spa_list_remove(&hook->link); if (hook->removed) hook->removed(hook); } /** Remove all hooks from the list */ SPA_API_HOOK void spa_hook_list_clean(struct spa_hook_list *list) { struct spa_hook *h; spa_list_consume(h, &list->list, link) spa_hook_remove(h); } SPA_API_HOOK void spa_hook_list_isolate(struct spa_hook_list *list, struct spa_hook_list *save, struct spa_hook *hook, const void *funcs, void *data) { /* init save list and move hooks to it */ spa_hook_list_init(save); spa_list_insert_list(&save->list, &list->list); /* init hooks and add single hook */ spa_hook_list_init(list); spa_hook_list_append(list, hook, funcs, data); } SPA_API_HOOK void spa_hook_list_join(struct spa_hook_list *list, struct spa_hook_list *save) { spa_list_insert_list(&list->list, &save->list); } #define spa_hook_list_call_simple(l,type,method,vers,...) \ ({ \ struct spa_hook_list *_l = l; \ struct spa_hook *_h, *_t; \ spa_list_for_each_safe(_h, _t, &_l->list, link) \ spa_callbacks_call(&_h->cb,type,method,vers, ## __VA_ARGS__); \ }) /** Call all hooks in a list, starting from the given one and optionally stopping * after calling the first non-NULL function, returns the number of methods * called */ #define spa_hook_list_do_call(l,start,type,method,vers,once,...) \ ({ \ struct spa_hook_list *_list = l; \ struct spa_list *_s = start ? (struct spa_list *)start : &_list->list; \ struct spa_hook _cursor = { 0 }, *_ci; \ int _count = 0; \ spa_list_cursor_start(_cursor, _s, link); \ spa_list_for_each_cursor(_ci, _cursor, &_list->list, link) { \ if (spa_callbacks_call(&_ci->cb,type,method,vers, ## __VA_ARGS__)) { \ _count++; \ if (once) \ break; \ } \ } \ spa_list_cursor_end(_cursor, link); \ _count; \ }) /** * Call the method named \a m for each element in list \a l. * \a t specifies the type of the callback struct. */ #define spa_hook_list_call(l,t,m,v,...) spa_hook_list_do_call(l,NULL,t,m,v,false,##__VA_ARGS__) /** * Call the method named \a m for each element in list \a l, stopping after * the first invocation. * \a t specifies the type of the callback struct. */ #define spa_hook_list_call_once(l,t,m,v,...) spa_hook_list_do_call(l,NULL,t,m,v,true,##__VA_ARGS__) #define spa_hook_list_call_start(l,s,t,m,v,...) spa_hook_list_do_call(l,s,t,m,v,false,##__VA_ARGS__) #define spa_hook_list_call_once_start(l,s,t,m,v,...) spa_hook_list_do_call(l,s,t,m,v,true,##__VA_ARGS__) /** * \} */ #ifdef __cplusplus } #endif #endif /* SPA_HOOK_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/utils/json-core.h000066400000000000000000000364251511204443500267110ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2020 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_UTILS_JSON_H #define SPA_UTILS_JSON_H #include #include #include #include #include #include #include #include #ifdef __cplusplus extern "C" { #else #include #endif #ifndef SPA_API_JSON #ifdef SPA_API_IMPL #define SPA_API_JSON SPA_API_IMPL #else #define SPA_API_JSON static inline #endif #endif /** \defgroup spa_json JSON * Relaxed JSON variant parsing */ /** * \addtogroup spa_json * \{ */ /* a simple JSON compatible tokenizer */ struct spa_json { const char *cur; const char *end; struct spa_json *parent; #define SPA_JSON_ERROR_FLAG 0x100 uint32_t state; uint32_t depth; }; #define SPA_JSON_INIT(data,size) ((struct spa_json) { (data), (data)+(size), NULL, 0, 0 }) SPA_API_JSON void spa_json_init(struct spa_json * iter, const char *data, size_t size) { *iter = SPA_JSON_INIT(data, size); } #define SPA_JSON_INIT_RELAX(type,data,size) \ ((struct spa_json) { (data), (data)+(size), NULL, (uint32_t)((type) == '[' ? 0x10 : 0x0), 0 }) SPA_API_JSON void spa_json_init_relax(struct spa_json * iter, char type, const char *data, size_t size) { *iter = SPA_JSON_INIT_RELAX(type, data, size); } #define SPA_JSON_ENTER(iter) ((struct spa_json) { (iter)->cur, (iter)->end, (iter), (iter)->state & 0xff0, 0 }) SPA_API_JSON void spa_json_enter(struct spa_json * iter, struct spa_json * sub) { *sub = SPA_JSON_ENTER(iter); } #define SPA_JSON_SAVE(iter) ((struct spa_json) { (iter)->cur, (iter)->end, NULL, (iter)->state, 0 }) SPA_API_JSON void spa_json_save(struct spa_json * iter, struct spa_json * save) { *save = SPA_JSON_SAVE(iter); } #define SPA_JSON_START(iter,p) ((struct spa_json) { (p), (iter)->end, NULL, 0, 0 }) SPA_API_JSON void spa_json_start(struct spa_json * iter, struct spa_json * sub, const char *pos) { *sub = SPA_JSON_START(iter,pos); } /** Get the next token. \a value points to the token and the return value * is the length. Returns -1 on parse error, 0 on end of input. */ SPA_API_JSON int spa_json_next(struct spa_json * iter, const char **value) { int utf8_remain = 0, err = 0; enum { __NONE, __STRUCT, __BARE, __STRING, __UTF8, __ESC, __COMMENT, __ARRAY_FLAG = 0x10, /* in array context */ __PREV_ARRAY_FLAG = 0x20, /* depth=0 array context flag */ __KEY_FLAG = 0x40, /* inside object key */ __SUB_FLAG = 0x80, /* not at top-level */ __FLAGS = 0xff0, __ERROR_SYSTEM = SPA_JSON_ERROR_FLAG, __ERROR_INVALID_ARRAY_SEPARATOR, __ERROR_EXPECTED_OBJECT_KEY, __ERROR_EXPECTED_OBJECT_VALUE, __ERROR_TOO_DEEP_NESTING, __ERROR_EXPECTED_ARRAY_CLOSE, __ERROR_EXPECTED_OBJECT_CLOSE, __ERROR_MISMATCHED_BRACKET, __ERROR_ESCAPE_NOT_ALLOWED, __ERROR_CHARACTERS_NOT_ALLOWED, __ERROR_INVALID_ESCAPE, __ERROR_INVALID_STATE, __ERROR_UNFINISHED_STRING, }; uint64_t array_stack[8] = {0}; /* array context flags of depths 1...512 */ *value = iter->cur; if (iter->state & SPA_JSON_ERROR_FLAG) return -1; for (; iter->cur < iter->end; iter->cur++) { unsigned char cur = (unsigned char)*iter->cur; uint32_t flag; #define _SPA_ERROR(reason) { err = __ERROR_ ## reason; goto error; } again: flag = iter->state & __FLAGS; switch (iter->state & ~__FLAGS) { case __NONE: flag &= ~(__KEY_FLAG | __PREV_ARRAY_FLAG); iter->state = __STRUCT | flag; iter->depth = 0; goto again; case __STRUCT: switch (cur) { case '\0': case '\t': case ' ': case '\r': case '\n': case ',': continue; case ':': case '=': if (flag & __ARRAY_FLAG) _SPA_ERROR(INVALID_ARRAY_SEPARATOR); if (!(flag & __KEY_FLAG)) _SPA_ERROR(EXPECTED_OBJECT_KEY); iter->state |= __SUB_FLAG; continue; case '#': iter->state = __COMMENT | flag; continue; case '"': if (flag & __KEY_FLAG) flag |= __SUB_FLAG; if (!(flag & __ARRAY_FLAG)) SPA_FLAG_UPDATE(flag, __KEY_FLAG, !(flag & __KEY_FLAG)); *value = iter->cur; iter->state = __STRING | flag; continue; case '[': case '{': if (!(flag & __ARRAY_FLAG)) { /* At top-level we may be either in object context * or in single-item context, and then we need to * accept array/object here. */ if ((iter->state & __SUB_FLAG) && !(flag & __KEY_FLAG)) _SPA_ERROR(EXPECTED_OBJECT_KEY); SPA_FLAG_CLEAR(flag, __KEY_FLAG); } iter->state = __STRUCT | __SUB_FLAG | flag; SPA_FLAG_UPDATE(iter->state, __ARRAY_FLAG, cur == '['); /* We need to remember previous array state across calls * for depth=0, so store that in state. Others bits go to * temporary stack. */ if (iter->depth == 0) { SPA_FLAG_UPDATE(iter->state, __PREV_ARRAY_FLAG, flag & __ARRAY_FLAG); } else if (((iter->depth-1) >> 6) < SPA_N_ELEMENTS(array_stack)) { uint64_t mask = 1ULL << ((iter->depth-1) & 0x3f); SPA_FLAG_UPDATE(array_stack[(iter->depth-1) >> 6], mask, flag & __ARRAY_FLAG); } else { /* too deep */ _SPA_ERROR(TOO_DEEP_NESTING); } *value = iter->cur; if (++iter->depth > 1) continue; iter->cur++; return 1; case '}': case ']': if ((flag & __ARRAY_FLAG) && cur != ']') _SPA_ERROR(EXPECTED_ARRAY_CLOSE); if (!(flag & __ARRAY_FLAG) && cur != '}') _SPA_ERROR(EXPECTED_OBJECT_CLOSE); if (flag & __KEY_FLAG) { /* incomplete key-value pair */ _SPA_ERROR(EXPECTED_OBJECT_VALUE); } iter->state = __STRUCT | __SUB_FLAG | flag; if (iter->depth == 0) { if (iter->parent) iter->parent->cur = iter->cur; else _SPA_ERROR(MISMATCHED_BRACKET); return 0; } --iter->depth; if (iter->depth == 0) { SPA_FLAG_UPDATE(iter->state, __ARRAY_FLAG, flag & __PREV_ARRAY_FLAG); } else if (((iter->depth-1) >> 6) < SPA_N_ELEMENTS(array_stack)) { uint64_t mask = 1ULL << ((iter->depth-1) & 0x3f); SPA_FLAG_UPDATE(iter->state, __ARRAY_FLAG, SPA_FLAG_IS_SET(array_stack[(iter->depth-1) >> 6], mask)); } else { /* too deep */ _SPA_ERROR(TOO_DEEP_NESTING); } continue; case '\\': /* disallow bare escape */ _SPA_ERROR(ESCAPE_NOT_ALLOWED); default: /* allow bare ascii */ if (!(cur >= 32 && cur <= 126)) _SPA_ERROR(CHARACTERS_NOT_ALLOWED); if (flag & __KEY_FLAG) flag |= __SUB_FLAG; if (!(flag & __ARRAY_FLAG)) SPA_FLAG_UPDATE(flag, __KEY_FLAG, !(flag & __KEY_FLAG)); *value = iter->cur; iter->state = __BARE | flag; } continue; case __BARE: switch (cur) { case '\0': case '\t': case ' ': case '\r': case '\n': case '"': case '#': case ':': case ',': case '=': case ']': case '}': iter->state = __STRUCT | flag; if (iter->depth > 0) goto again; return iter->cur - *value; case '\\': /* disallow bare escape */ _SPA_ERROR(ESCAPE_NOT_ALLOWED); default: /* allow bare ascii */ if (cur >= 32 && cur <= 126) continue; } _SPA_ERROR(CHARACTERS_NOT_ALLOWED); case __STRING: switch (cur) { case '\\': iter->state = __ESC | flag; continue; case '"': iter->state = __STRUCT | flag; if (iter->depth > 0) continue; return ++iter->cur - *value; case 240 ... 247: utf8_remain++; SPA_FALLTHROUGH; case 224 ... 239: utf8_remain++; SPA_FALLTHROUGH; case 192 ... 223: utf8_remain++; iter->state = __UTF8 | flag; continue; default: if (cur >= 32 && cur <= 127) continue; } _SPA_ERROR(CHARACTERS_NOT_ALLOWED); case __UTF8: switch (cur) { case 128 ... 191: if (--utf8_remain == 0) iter->state = __STRING | flag; continue; default: break; } _SPA_ERROR(CHARACTERS_NOT_ALLOWED); case __ESC: switch (cur) { case '"': case '\\': case '/': case 'b': case 'f': case 'n': case 'r': case 't': case 'u': iter->state = __STRING | flag; continue; default: break; } _SPA_ERROR(INVALID_ESCAPE); case __COMMENT: switch (cur) { case '\n': case '\r': iter->state = __STRUCT | flag; break; default: break; } break; default: _SPA_ERROR(INVALID_STATE); } } if (iter->depth != 0 || iter->parent) _SPA_ERROR(MISMATCHED_BRACKET); switch (iter->state & ~__FLAGS) { case __STRING: case __UTF8: case __ESC: /* string/escape not closed */ _SPA_ERROR(UNFINISHED_STRING); case __COMMENT: /* trailing comment */ return 0; default: break; } if ((iter->state & __SUB_FLAG) && (iter->state & __KEY_FLAG)) { /* incomplete key-value pair */ _SPA_ERROR(EXPECTED_OBJECT_VALUE); } if ((iter->state & ~__FLAGS) != __STRUCT) { iter->state = __STRUCT | (iter->state & __FLAGS); return iter->cur - *value; } return 0; #undef _SPA_ERROR error: iter->state = err; while (iter->parent) { if (iter->parent->state & SPA_JSON_ERROR_FLAG) break; iter->parent->state = err; iter->parent->cur = iter->cur; iter = iter->parent; } return -1; } /** * Return if there was a parse error, and its possible location. * * \since 1.1.0 */ SPA_API_JSON bool spa_json_get_error(struct spa_json *iter, const char *start, struct spa_error_location *loc) { static const char *reasons[] = { "System error", "Invalid array separator", "Expected object key", "Expected object value", "Too deep nesting", "Expected array close bracket", "Expected object close brace", "Mismatched bracket", "Escape not allowed", "Character not allowed", "Invalid escape", "Invalid state", "Unfinished string", "Expected key separator", }; if (!(iter->state & SPA_JSON_ERROR_FLAG)) return false; if (loc) { int linepos = 1, colpos = 1, code; const char *p, *l; for (l = p = start; p && p != iter->cur; ++p) { if (*p == '\n') { linepos++; colpos = 1; l = p+1; } else { colpos++; } } code = SPA_CLAMP(iter->state & 0xff, 0u, SPA_N_ELEMENTS(reasons)-1); loc->line = linepos; loc->col = colpos; loc->location = l; loc->len = SPA_PTRDIFF(iter->end, loc->location) / sizeof(char); loc->reason = code == 0 ? strerror(errno) : reasons[code]; } return true; } SPA_API_JSON int spa_json_is_container(const char *val, int len) { return len > 0 && (*val == '{' || *val == '['); } /* object */ SPA_API_JSON int spa_json_is_object(const char *val, int len) { return len > 0 && *val == '{'; } /* array */ SPA_API_JSON bool spa_json_is_array(const char *val, int len) { return len > 0 && *val == '['; } /* null */ SPA_API_JSON bool spa_json_is_null(const char *val, int len) { return len == 4 && strncmp(val, "null", 4) == 0; } /* float */ SPA_API_JSON int spa_json_parse_float(const char *val, int len, float *result) { char buf[96]; char *end; int pos; if (len <= 0 || len >= (int)sizeof(buf)) return 0; for (pos = 0; pos < len; ++pos) { switch (val[pos]) { case '+': case '-': case '0' ... '9': case '.': case 'e': case 'E': break; default: return 0; } } memcpy(buf, val, len); buf[len] = '\0'; *result = spa_strtof(buf, &end); return len > 0 && end == buf + len; } SPA_API_JSON bool spa_json_is_float(const char *val, int len) { float dummy; return spa_json_parse_float(val, len, &dummy); } SPA_API_JSON char *spa_json_format_float(char *str, int size, float val) { if (SPA_UNLIKELY(!isnormal(val))) { if (isinf(val)) val = signbit(val) ? FLT_MIN : FLT_MAX; else val = 0.0f; } return spa_dtoa(str, size, val); } /* int */ SPA_API_JSON int spa_json_parse_int(const char *val, int len, int *result) { char buf[64]; char *end; if (len <= 0 || len >= (int)sizeof(buf)) return 0; memcpy(buf, val, len); buf[len] = '\0'; *result = strtol(buf, &end, 0); return len > 0 && end == buf + len; } SPA_API_JSON bool spa_json_is_int(const char *val, int len) { int dummy; return spa_json_parse_int(val, len, &dummy); } /* bool */ SPA_API_JSON bool spa_json_is_true(const char *val, int len) { return len == 4 && strncmp(val, "true", 4) == 0; } SPA_API_JSON bool spa_json_is_false(const char *val, int len) { return len == 5 && strncmp(val, "false", 5) == 0; } SPA_API_JSON bool spa_json_is_bool(const char *val, int len) { return spa_json_is_true(val, len) || spa_json_is_false(val, len); } SPA_API_JSON int spa_json_parse_bool(const char *val, int len, bool *result) { if ((*result = spa_json_is_true(val, len))) return 1; if (!(*result = !spa_json_is_false(val, len))) return 1; return -1; } /* string */ SPA_API_JSON bool spa_json_is_string(const char *val, int len) { return len > 1 && *val == '"'; } SPA_API_JSON int spa_json_parse_hex(const char *p, int num, uint32_t *res) { int i; *res = 0; for (i = 0; i < num; i++) { char v = p[i]; if (v >= '0' && v <= '9') v = v - '0'; else if (v >= 'a' && v <= 'f') v = v - 'a' + 10; else if (v >= 'A' && v <= 'F') v = v - 'A' + 10; else return -1; *res = (*res << 4) | v; } return 1; } SPA_API_JSON int spa_json_parse_stringn(const char *val, int len, char *result, int maxlen) { const char *p; if (maxlen <= len) return -ENOSPC; if (!spa_json_is_string(val, len)) { if (result != val) memmove(result, val, len); result += len; } else { for (p = val+1; p < val + len; p++) { if (*p == '\\') { p++; if (*p == 'n') *result++ = '\n'; else if (*p == 'r') *result++ = '\r'; else if (*p == 'b') *result++ = '\b'; else if (*p == 't') *result++ = '\t'; else if (*p == 'f') *result++ = '\f'; else if (*p == 'u') { uint8_t prefix[] = { 0, 0xc0, 0xe0, 0xf0 }; uint32_t idx, n, v, cp, enc[] = { 0x80, 0x800, 0x10000 }; if (val + len - p < 5 || spa_json_parse_hex(p+1, 4, &cp) < 0) { *result++ = *p; continue; } p += 4; if (cp >= 0xd800 && cp <= 0xdbff) { if (val + len - p < 7 || p[1] != '\\' || p[2] != 'u' || spa_json_parse_hex(p+3, 4, &v) < 0 || v < 0xdc00 || v > 0xdfff) continue; p += 6; cp = 0x010000 + (((cp & 0x3ff) << 10) | (v & 0x3ff)); } else if (cp >= 0xdc00 && cp <= 0xdfff) continue; for (idx = 0; idx < 3; idx++) if (cp < enc[idx]) break; for (n = idx; n > 0; n--, cp >>= 6) result[n] = (cp | 0x80) & 0xbf; *result++ = (cp | prefix[idx]) & 0xff; result += idx; } else *result++ = *p; } else if (*p == '\"') { break; } else *result++ = *p; } } *result = '\0'; return 1; } SPA_API_JSON int spa_json_parse_string(const char *val, int len, char *result) { return spa_json_parse_stringn(val, len, result, len+1); } SPA_API_JSON int spa_json_encode_string(char *str, int size, const char *val) { int len = 0; static const char hex[] = { "0123456789abcdef" }; #define __PUT(c) { if (len < size) *str++ = c; len++; } __PUT('"'); while (*val) { switch (*val) { case '\n': __PUT('\\'); __PUT('n'); break; case '\r': __PUT('\\'); __PUT('r'); break; case '\b': __PUT('\\'); __PUT('b'); break; case '\t': __PUT('\\'); __PUT('t'); break; case '\f': __PUT('\\'); __PUT('f'); break; case '\\': case '"': __PUT('\\'); __PUT(*val); break; default: if (*val > 0 && *val < 0x20) { __PUT('\\'); __PUT('u'); __PUT('0'); __PUT('0'); __PUT(hex[((*val)>>4)&0xf]); __PUT(hex[(*val)&0xf]); } else { __PUT(*val); } break; } val++; } __PUT('"'); __PUT('\0'); #undef __PUT return len-1; } /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_UTILS_JSON_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/utils/json-pod.h000066400000000000000000000105251511204443500265340ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2022 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_UTILS_JSON_POD_H #define SPA_UTILS_JSON_POD_H #include #include #include #include #include #ifdef __cplusplus extern "C" { #endif #ifndef SPA_API_JSON_POD #ifdef SPA_API_IMPL #define SPA_API_JSON_POD SPA_API_IMPL #else #define SPA_API_JSON_POD static inline #endif #endif /** \defgroup spa_json_pod JSON to POD * JSON to POD conversion */ /** * \addtogroup spa_json_pod * \{ */ SPA_API_JSON_POD int spa_json_to_pod_part(struct spa_pod_builder *b, uint32_t flags, uint32_t id, const struct spa_type_info *info, struct spa_json *iter, const char *value, int len) { const struct spa_type_info *ti; char key[256]; struct spa_pod_frame f[1]; struct spa_json it[1]; int l, res; const char *v; uint32_t type; if (spa_json_is_object(value, len) && info != NULL) { if ((ti = spa_debug_type_find(NULL, info->parent)) == NULL) return -EINVAL; spa_pod_builder_push_object(b, &f[0], info->parent, id); spa_json_enter(iter, &it[0]); while ((l = spa_json_object_next(&it[0], key, sizeof(key), &v)) > 0) { const struct spa_type_info *pi; if ((pi = spa_debug_type_find_short(ti->values, key)) != NULL) type = pi->type; else if (!spa_atou32(key, &type, 0)) continue; spa_pod_builder_prop(b, type, 0); if ((res = spa_json_to_pod_part(b, flags, id, pi, &it[0], v, l)) < 0) return res; } if (l < 0) return l; spa_pod_builder_pop(b, &f[0]); } else if (spa_json_is_array(value, len)) { if (info == NULL || info->parent == SPA_TYPE_Struct) { spa_pod_builder_push_struct(b, &f[0]); } else { spa_pod_builder_push_array(b, &f[0]); info = info->values; } spa_json_enter(iter, &it[0]); while ((l = spa_json_next(&it[0], &v)) > 0) if ((res = spa_json_to_pod_part(b, flags, id, info, &it[0], v, l)) < 0) return res; if (l < 0) return l; spa_pod_builder_pop(b, &f[0]); } else if (spa_json_is_float(value, len)) { float val = 0.0f; spa_json_parse_float(value, len, &val); switch (info ? info->parent : (uint32_t)SPA_TYPE_Struct) { case SPA_TYPE_Bool: spa_pod_builder_bool(b, val >= 0.5f); break; case SPA_TYPE_Id: spa_pod_builder_id(b, (uint32_t)val); break; case SPA_TYPE_Int: spa_pod_builder_int(b, (int32_t)val); break; case SPA_TYPE_Long: spa_pod_builder_long(b, (int64_t)val); break; case SPA_TYPE_Struct: if (spa_json_is_int(value, len)) spa_pod_builder_int(b, (int32_t)val); else spa_pod_builder_float(b, val); break; case SPA_TYPE_Float: spa_pod_builder_float(b, val); break; case SPA_TYPE_Double: spa_pod_builder_double(b, val); break; default: spa_pod_builder_none(b); break; } } else if (spa_json_is_bool(value, len)) { bool val = false; spa_json_parse_bool(value, len, &val); spa_pod_builder_bool(b, val); } else if (spa_json_is_null(value, len)) { spa_pod_builder_none(b); } else { char *val = (char*)alloca(len+1); spa_json_parse_stringn(value, len, val, len+1); switch (info ? info->parent : (uint32_t)SPA_TYPE_Struct) { case SPA_TYPE_Id: if ((ti = spa_debug_type_find_short(info->values, val)) != NULL) type = ti->type; else if (!spa_atou32(val, &type, 0)) return -EINVAL; spa_pod_builder_id(b, type); break; case SPA_TYPE_Struct: case SPA_TYPE_String: spa_pod_builder_string(b, val); break; default: spa_pod_builder_none(b); break; } } return 0; } SPA_API_JSON_POD int spa_json_to_pod_checked(struct spa_pod_builder *b, uint32_t flags, const struct spa_type_info *info, const char *value, int len, struct spa_error_location *loc) { struct spa_json iter; const char *val; int res; if (loc) spa_zero(*loc); if ((res = spa_json_begin(&iter, value, len, &val)) <= 0) goto error; res = spa_json_to_pod_part(b, flags, info->type, info, &iter, val, len); error: if (res < 0 && loc) spa_json_get_error(&iter, value, loc); return res; } SPA_API_JSON_POD int spa_json_to_pod(struct spa_pod_builder *b, uint32_t flags, const struct spa_type_info *info, const char *value, int len) { return spa_json_to_pod_checked(b, flags, info, value, len, NULL); } /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_UTILS_JSON_POD_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/utils/json.h000066400000000000000000000127711511204443500257610ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2020 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_UTILS_JSON_UTILS_H #define SPA_UTILS_JSON_UTILS_H #include #include #include #include #include #include #include #ifdef __cplusplus extern "C" { #else #include #endif #ifndef SPA_API_JSON_UTILS #ifdef SPA_API_IMPL #define SPA_API_JSON_UTILS SPA_API_IMPL #else #define SPA_API_JSON_UTILS static inline #endif #endif /** \defgroup spa_json_utils JSON Utils * Relaxed JSON variant parsing Utils */ /** * \addtogroup spa_json * \{ */ SPA_API_JSON_UTILS int spa_json_begin(struct spa_json * iter, const char *data, size_t size, const char **val) { spa_json_init(iter, data, size); return spa_json_next(iter, val); } /* float */ SPA_API_JSON_UTILS int spa_json_get_float(struct spa_json *iter, float *res) { const char *value; int len; if ((len = spa_json_next(iter, &value)) <= 0) return len; return spa_json_parse_float(value, len, res); } /* int */ SPA_API_JSON_UTILS int spa_json_get_int(struct spa_json *iter, int *res) { const char *value; int len; if ((len = spa_json_next(iter, &value)) <= 0) return len; return spa_json_parse_int(value, len, res); } /* bool */ SPA_API_JSON_UTILS int spa_json_get_bool(struct spa_json *iter, bool *res) { const char *value; int len; if ((len = spa_json_next(iter, &value)) <= 0) return len; return spa_json_parse_bool(value, len, res); } /* string */ SPA_API_JSON_UTILS int spa_json_get_string(struct spa_json *iter, char *res, int maxlen) { const char *value; int len; if ((len = spa_json_next(iter, &value)) <= 0) return len; return spa_json_parse_stringn(value, len, res, maxlen); } SPA_API_JSON_UTILS int spa_json_enter_container(struct spa_json *iter, struct spa_json *sub, char type) { const char *value; int len; if ((len = spa_json_next(iter, &value)) <= 0) return len; if (!spa_json_is_container(value, len)) return -EPROTO; if (*value != type) return -EINVAL; spa_json_enter(iter, sub); return 1; } SPA_API_JSON_UTILS int spa_json_begin_container(struct spa_json * iter, const char *data, size_t size, char type, bool relax) { int res; spa_json_init(iter, data, size); res = spa_json_enter_container(iter, iter, type); if (res == -EPROTO && relax) spa_json_init_relax(iter, type, data, size); else if (res <= 0) return res; return 1; } /** * Return length of container at current position, starting at \a value. * * \return Length of container including {} or [], or 0 on error. */ SPA_API_JSON_UTILS int spa_json_container_len(struct spa_json *iter, const char *value, int len SPA_UNUSED) { const char *val; struct spa_json sub; int res; spa_json_enter(iter, &sub); while ((res = spa_json_next(&sub, &val)) > 0); if (res < 0) return 0; return sub.cur + 1 - value; } /* object */ SPA_API_JSON_UTILS int spa_json_enter_object(struct spa_json *iter, struct spa_json *sub) { return spa_json_enter_container(iter, sub, '{'); } SPA_API_JSON_UTILS int spa_json_begin_object_relax(struct spa_json * iter, const char *data, size_t size) { return spa_json_begin_container(iter, data, size, '{', true); } SPA_API_JSON_UTILS int spa_json_begin_object(struct spa_json * iter, const char *data, size_t size) { return spa_json_begin_container(iter, data, size, '{', false); } SPA_API_JSON_UTILS int spa_json_object_next(struct spa_json *iter, char *key, int maxkeylen, const char **value) { int res1, res2; while (true) { res1 = spa_json_get_string(iter, key, maxkeylen); if (res1 <= 0 && res1 != -ENOSPC) return res1; res2 = spa_json_next(iter, value); if (res2 <= 0 || res1 != -ENOSPC) return res2; } } SPA_API_JSON_UTILS int spa_json_object_find(struct spa_json *iter, const char *key, const char **value) { struct spa_json obj = SPA_JSON_SAVE(iter); int res, len = strlen(key) + 3; char k[len]; while ((res = spa_json_object_next(&obj, k, len, value)) > 0) if (spa_streq(k, key)) return res; return -ENOENT; } SPA_API_JSON_UTILS int spa_json_str_object_find(const char *obj, size_t obj_len, const char *key, char *value, size_t maxlen) { struct spa_json iter; int l; const char *v; if (spa_json_begin_object(&iter, obj, obj_len) <= 0) return -EINVAL; if ((l = spa_json_object_find(&iter, key, &v)) <= 0) return l; return spa_json_parse_stringn(v, l, value, maxlen); } /* array */ SPA_API_JSON_UTILS int spa_json_enter_array(struct spa_json *iter, struct spa_json *sub) { return spa_json_enter_container(iter, sub, '['); } SPA_API_JSON_UTILS int spa_json_begin_array_relax(struct spa_json * iter, const char *data, size_t size) { return spa_json_begin_container(iter, data, size, '[', true); } SPA_API_JSON_UTILS int spa_json_begin_array(struct spa_json * iter, const char *data, size_t size) { return spa_json_begin_container(iter, data, size, '[', false); } #define spa_json_make_str_array_unpack(maxlen,type,conv) \ { \ struct spa_json iter; \ char v[maxlen]; \ uint32_t count = 0; \ if (spa_json_begin_array_relax(&iter, arr, arr_len) <= 0) \ return -EINVAL; \ while (spa_json_get_string(&iter, v, sizeof(v)) > 0 && count < max) \ values[count++] = conv(v); \ return count; \ } SPA_API_JSON_UTILS int spa_json_str_array_uint32(const char *arr, size_t arr_len, uint32_t *values, size_t max) { spa_json_make_str_array_unpack(32,uint32_t, atoi); } /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_UTILS_JSON_UTILS_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/utils/keys.h000066400000000000000000000170671511204443500257660ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2019 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_UTILS_KEYS_H #define SPA_UTILS_KEYS_H #ifdef __cplusplus extern "C" { #endif /** \defgroup spa_keys Key Names * Key names used by SPA plugins */ /** * \addtogroup spa_keys * \{ */ /** for objects */ #define SPA_KEY_OBJECT_PATH "object.path" /**< a unique path to * identity the object */ #define SPA_KEY_MEDIA_CLASS "media.class" /**< Media class * Ex. "Audio/Device", * "Video/Source",... */ #define SPA_KEY_MEDIA_ROLE "media.role" /**< Role: Movie, Music, Camera, * Screen, Communication, Game, * Notification, DSP, Production, * Accessibility, Test */ /** keys for udev api */ #define SPA_KEY_API_UDEV "api.udev" /**< key for the udev api */ #define SPA_KEY_API_UDEV_MATCH "api.udev.match" /**< udev subsystem match */ /** keys for alsa api */ #define SPA_KEY_API_ALSA "api.alsa" /**< key for the alsa api */ #define SPA_KEY_API_ALSA_PATH "api.alsa.path" /**< alsa device path as can be * used in snd_pcm_open() and * snd_ctl_open(). */ #define SPA_KEY_API_ALSA_CARD "api.alsa.card" /**< alsa card number */ #define SPA_KEY_API_ALSA_USE_UCM "api.alsa.use-ucm" /**< if UCM should be used */ #define SPA_KEY_API_ALSA_IGNORE_DB "api.alsa.ignore-dB" /**< if decibel info should be ignored */ #define SPA_KEY_API_ALSA_OPEN_UCM "api.alsa.open.ucm" /**< if UCM should be opened card */ #define SPA_KEY_API_ALSA_DISABLE_LONGNAME \ "api.alsa.disable-longname" /**< if card long name should not be passed to MIDI port */ #define SPA_KEY_API_ALSA_BIND_CTLS "api.alsa.bind-ctls" /**< alsa controls to bind as params */ #define SPA_KEY_API_ALSA_SPLIT_ENABLE "api.alsa.split-enable" /**< For UCM devices with split PCMs, don't split to * multiple PCMs using alsa-lib plugins, but instead * add api.alsa.split properties to emitted nodes * with PCM splitting information. */ #define SPA_KEY_API_ALSA_SPLIT_PARENT "api.alsa.split.parent" /**< PCM is UCM SplitPCM parent PCM, * to be opened with SplitPCM set. */ /** info from alsa card_info */ #define SPA_KEY_API_ALSA_CARD_ID "api.alsa.card.id" /**< id from card_info */ #define SPA_KEY_API_ALSA_CARD_COMPONENTS \ "api.alsa.card.components" /**< components from card_info */ #define SPA_KEY_API_ALSA_CARD_DRIVER "api.alsa.card.driver" /**< driver from card_info */ #define SPA_KEY_API_ALSA_CARD_NAME "api.alsa.card.name" /**< name from card_info */ #define SPA_KEY_API_ALSA_CARD_LONGNAME "api.alsa.card.longname" /**< longname from card_info */ #define SPA_KEY_API_ALSA_CARD_MIXERNAME "api.alsa.card.mixername" /**< mixername from card_info */ /** info from alsa pcm_info */ #define SPA_KEY_API_ALSA_PCM_ID "api.alsa.pcm.id" /**< id from pcm_info */ #define SPA_KEY_API_ALSA_PCM_CARD "api.alsa.pcm.card" /**< card from pcm_info */ #define SPA_KEY_API_ALSA_PCM_NAME "api.alsa.pcm.name" /**< name from pcm_info */ #define SPA_KEY_API_ALSA_PCM_SUBNAME "api.alsa.pcm.subname" /**< subdevice_name from pcm_info */ #define SPA_KEY_API_ALSA_PCM_STREAM "api.alsa.pcm.stream" /**< stream type from pcm_info */ #define SPA_KEY_API_ALSA_PCM_CLASS "api.alsa.pcm.class" /**< class from pcm_info as string */ #define SPA_KEY_API_ALSA_PCM_DEVICE "api.alsa.pcm.device" /**< device from pcm_info */ #define SPA_KEY_API_ALSA_PCM_SUBDEVICE "api.alsa.pcm.subdevice" /**< subdevice from pcm_info */ #define SPA_KEY_API_ALSA_PCM_SUBCLASS "api.alsa.pcm.subclass" /**< subclass from pcm_info as string */ #define SPA_KEY_API_ALSA_PCM_SYNC_ID "api.alsa.pcm.sync-id" /**< sync id */ #define SPA_KEY_API_ALSA_SPLIT_POSITION "api.alsa.split.position" /**< (SPA JSON list) If present, this is a * virtual device corresponding to a subset of * channels in an underlying PCM, listed in this * property. The \ref SPA_KEY_API_ALSA_PATH * contains the underlying split PCM. */ #define SPA_KEY_API_ALSA_SPLIT_HW_POSITION \ "api.alsa.split.hw-position" /**< (SPA JSON list) Channel map of the * underlying split PCM. */ /** keys for v4l2 api */ #define SPA_KEY_API_V4L2 "api.v4l2" /**< key for the v4l2 api */ #define SPA_KEY_API_V4L2_PATH "api.v4l2.path" /**< v4l2 device path as can be * used in open() */ /** keys for libcamera api */ #define SPA_KEY_API_LIBCAMERA "api.libcamera" /**< key for the libcamera api */ #define SPA_KEY_API_LIBCAMERA_PATH "api.libcamera.path" /**< libcamera device path as can be * used in open() */ #define SPA_KEY_API_LIBCAMERA_LOCATION "api.libcamera.location" /**< location of the camera: * "front", "back" or "external" */ #define SPA_KEY_API_LIBCAMERA_ROTATION "api.libcamera.rotation" /**< rotation of the camera: * "0", "90", "180" or "270" */ /** info from libcamera_capability */ #define SPA_KEY_API_LIBCAMERA_CAP_DRIVER "api.libcamera.cap.driver" /**< driver from capbility */ #define SPA_KEY_API_LIBCAMERA_CAP_CARD "api.libcamera.cap.card" /**< caps from capability */ #define SPA_KEY_API_LIBCAMERA_CAP_BUS_INFO "api.libcamera.cap.bus_info"/**< bus_info from capability */ #define SPA_KEY_API_LIBCAMERA_CAP_VERSION "api.libcamera.cap.version" /**< version from capability as %u.%u.%u */ #define SPA_KEY_API_LIBCAMERA_CAP_CAPABILITIES \ "api.libcamera.cap.capabilities" /**< capabilities from capability */ #define SPA_KEY_API_LIBCAMERA_CAP_DEVICE_CAPS \ "api.libcamera.cap.device-caps" /**< device_caps from capability */ /** info from v4l2_capability */ #define SPA_KEY_API_V4L2_CAP_DRIVER "api.v4l2.cap.driver" /**< driver from capbility */ #define SPA_KEY_API_V4L2_CAP_CARD "api.v4l2.cap.card" /**< caps from capability */ #define SPA_KEY_API_V4L2_CAP_BUS_INFO "api.v4l2.cap.bus_info" /**< bus_info from capability */ #define SPA_KEY_API_V4L2_CAP_VERSION "api.v4l2.cap.version" /**< version from capability as %u.%u.%u */ #define SPA_KEY_API_V4L2_CAP_CAPABILITIES \ "api.v4l2.cap.capabilities" /**< capabilities from capability */ #define SPA_KEY_API_V4L2_CAP_DEVICE_CAPS \ "api.v4l2.cap.device-caps" /**< device_caps from capability */ /** keys for bluez5 api */ #define SPA_KEY_API_BLUEZ5 "api.bluez5" /**< key for the bluez5 api */ #define SPA_KEY_API_BLUEZ5_PATH "api.bluez5.path" /**< a bluez5 path */ #define SPA_KEY_API_BLUEZ5_DEVICE "api.bluez5.device" /**< an internal bluez5 device */ #define SPA_KEY_API_BLUEZ5_CONNECTION "api.bluez5.connection" /**< bluez5 device connection status */ #define SPA_KEY_API_BLUEZ5_TRANSPORT "api.bluez5.transport" /**< an internal bluez5 transport */ #define SPA_KEY_API_BLUEZ5_PROFILE "api.bluez5.profile" /**< a bluetooth profile */ #define SPA_KEY_API_BLUEZ5_ADDRESS "api.bluez5.address" /**< a bluetooth address */ #define SPA_KEY_API_BLUEZ5_CODEC "api.bluez5.codec" /**< a bluetooth codec */ #define SPA_KEY_API_BLUEZ5_CLASS "api.bluez5.class" /**< a bluetooth class */ #define SPA_KEY_API_BLUEZ5_ICON "api.bluez5.icon" /**< a bluetooth icon */ #define SPA_KEY_API_BLUEZ5_ROLE "api.bluez5.role" /**< "client" or "server" */ /** keys for jack api */ #define SPA_KEY_API_JACK "api.jack" /**< key for the JACK api */ #define SPA_KEY_API_JACK_SERVER "api.jack.server" /**< a jack server name */ #define SPA_KEY_API_JACK_CLIENT "api.jack.client" /**< an internal jack client */ /** keys for glib api */ #define SPA_KEY_API_GLIB_MAINLOOP "api.glib.mainloop" /**< whether glib mainloop runs * in same thread as PW loop */ /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_UTILS_KEYS_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/utils/list.h000066400000000000000000000104631511204443500257570ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_LIST_H #define SPA_LIST_H #include #ifdef __cplusplus extern "C" { #endif #ifndef SPA_API_LIST #ifdef SPA_API_IMPL #define SPA_API_LIST SPA_API_IMPL #else #define SPA_API_LIST static inline #endif #endif /** * \defgroup spa_list List * Doubly linked list data structure */ /** * \addtogroup spa_list List * \{ */ struct spa_list { struct spa_list *next; struct spa_list *prev; }; #define SPA_LIST_INIT(list) ((struct spa_list){ (list), (list) }) SPA_API_LIST void spa_list_init(struct spa_list *list) { *list = SPA_LIST_INIT(list); } SPA_API_LIST int spa_list_is_initialized(struct spa_list *list) { return !!list->prev; } #define spa_list_is_empty(l) ((l)->next == (l)) SPA_API_LIST void spa_list_insert(struct spa_list *list, struct spa_list *elem) { elem->prev = list; elem->next = list->next; list->next = elem; elem->next->prev = elem; } SPA_API_LIST void spa_list_insert_list(struct spa_list *list, struct spa_list *other) { if (spa_list_is_empty(other)) return; other->next->prev = list; other->prev->next = list->next; list->next->prev = other->prev; list->next = other->next; } SPA_API_LIST void spa_list_remove(struct spa_list *elem) { elem->prev->next = elem->next; elem->next->prev = elem->prev; } #define spa_list_first(head, type, member) \ SPA_CONTAINER_OF((head)->next, type, member) #define spa_list_last(head, type, member) \ SPA_CONTAINER_OF((head)->prev, type, member) #define spa_list_append(list, item) \ spa_list_insert((list)->prev, item) #define spa_list_prepend(list, item) \ spa_list_insert(list, item) #define spa_list_is_end(pos, head, member) \ (&(pos)->member == (head)) #define spa_list_next(pos, member) \ SPA_CONTAINER_OF((pos)->member.next, __typeof__(*(pos)), member) #define spa_list_prev(pos, member) \ SPA_CONTAINER_OF((pos)->member.prev, __typeof__(*(pos)), member) #define spa_list_consume(pos, head, member) \ for ((pos) = spa_list_first(head, __typeof__(*(pos)), member); \ !spa_list_is_empty(head); \ (pos) = spa_list_first(head, __typeof__(*(pos)), member)) #define spa_list_for_each_next(pos, head, curr, member) \ for ((pos) = spa_list_first(curr, __typeof__(*(pos)), member); \ !spa_list_is_end(pos, head, member); \ (pos) = spa_list_next(pos, member)) #define spa_list_for_each_prev(pos, head, curr, member) \ for ((pos) = spa_list_last(curr, __typeof__(*(pos)), member); \ !spa_list_is_end(pos, head, member); \ (pos) = spa_list_prev(pos, member)) #define spa_list_for_each(pos, head, member) \ spa_list_for_each_next(pos, head, head, member) #define spa_list_for_each_reverse(pos, head, member) \ spa_list_for_each_prev(pos, head, head, member) #define spa_list_for_each_safe_next(pos, tmp, head, curr, member) \ for ((pos) = spa_list_first(curr, __typeof__(*(pos)), member); \ (tmp) = spa_list_next(pos, member), \ !spa_list_is_end(pos, head, member); \ (pos) = (tmp)) #define spa_list_for_each_safe_prev(pos, tmp, head, curr, member) \ for ((pos) = spa_list_last(curr, __typeof__(*(pos)), member); \ (tmp) = spa_list_prev(pos, member), \ !spa_list_is_end(pos, head, member); \ (pos) = (tmp)) #define spa_list_for_each_safe(pos, tmp, head, member) \ spa_list_for_each_safe_next(pos, tmp, head, head, member) #define spa_list_for_each_safe_reverse(pos, tmp, head, member) \ spa_list_for_each_safe_prev(pos, tmp, head, head, member) #define spa_list_cursor_start(cursor, head, member) \ spa_list_prepend(head, &(cursor).member) #define spa_list_for_each_cursor(pos, cursor, head, member) \ for((pos) = spa_list_first(&(cursor).member, __typeof__(*(pos)), member); \ spa_list_remove(&(pos)->member), \ spa_list_append(&(cursor).member, &(pos)->member), \ !spa_list_is_end(pos, head, member); \ (pos) = spa_list_next(&(cursor), member)) #define spa_list_cursor_end(cursor, member) \ spa_list_remove(&(cursor).member) /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_LIST_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/utils/names.h000066400000000000000000000163511511204443500261110ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2019 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_UTILS_NAMES_H #define SPA_UTILS_NAMES_H #ifdef __cplusplus extern "C" { #endif /** \defgroup spa_names Factory Names * SPA plugin factory names */ /** * \addtogroup spa_names * \{ */ /** for factory names */ #define SPA_NAME_SUPPORT_CPU "support.cpu" /**< A CPU interface */ #define SPA_NAME_SUPPORT_DBUS "support.dbus" /**< A DBUS interface */ #define SPA_NAME_SUPPORT_LOG "support.log" /**< A Log interface */ #define SPA_NAME_SUPPORT_LOOP "support.loop" /**< A Loop/LoopControl/LoopUtils * interface */ #define SPA_NAME_SUPPORT_SYSTEM "support.system" /**< A System interface */ #define SPA_NAME_SUPPORT_NODE_DRIVER "support.node.driver" /**< A dummy driver node */ /* control mixer */ #define SPA_NAME_CONTROL_MIXER "control.mixer" /**< mixes control streams */ /* audio mixer */ #define SPA_NAME_AUDIO_MIXER "audio.mixer" /**< mixes the raw audio on N input * ports together on the output * port */ #define SPA_NAME_AUDIO_MIXER_DSP "audio.mixer.dsp" /**< mixes mono audio with fixed input * and output buffer sizes. supported * formats must include f32 and * optionally f64 and s24_32 */ /** audio processing */ #define SPA_NAME_AUDIO_PROCESS_FORMAT "audio.process.format" /**< processes raw audio from one format * to another */ #define SPA_NAME_AUDIO_PROCESS_CHANNELMIX \ "audio.process.channelmix" /**< mixes raw audio channels and applies * volume change. */ #define SPA_NAME_AUDIO_PROCESS_RESAMPLE \ "audio.process.resample" /**< resamples raw audio */ #define SPA_NAME_AUDIO_PROCESS_DEINTERLEAVE \ "audio.process.deinterleave" /**< deinterleave raw audio channels */ #define SPA_NAME_AUDIO_PROCESS_INTERLEAVE \ "audio.process.interleave" /**< interleave raw audio channels */ /** audio convert combines some of the audio processing */ #define SPA_NAME_AUDIO_CONVERT "audio.convert" /**< converts raw audio from one format * to another. Must include at least * format, channelmix and resample * processing */ #define SPA_NAME_AUDIO_ADAPT "audio.adapt" /**< combination of a node and an * audio.convert. Does clock slaving */ #define SPA_NAME_AEC "audio.aec" /**< Echo canceling */ /** video processing */ #define SPA_NAME_VIDEO_PROCESS_FORMAT "video.process.format" /**< processes raw video from one format * to another */ #define SPA_NAME_VIDEO_PROCESS_SCALE "video.process.scale" /**< scales raw video */ /** video convert combines some of the video processing */ #define SPA_NAME_VIDEO_CONVERT "video.convert" /**< converts raw video from one format * to another. Must include at least * format and scaling */ #define SPA_NAME_VIDEO_CONVERT_DUMMY "video.convert.dummy" /**< a dummy converter as fallback for the * videoadapter node */ #define SPA_NAME_VIDEO_ADAPT "video.adapt" /**< combination of a node and a * video.convert. */ /** keys for alsa factory names */ #define SPA_NAME_API_ALSA_ENUM_UDEV "api.alsa.enum.udev" /**< an alsa udev Device interface */ #define SPA_NAME_API_ALSA_PCM_DEVICE "api.alsa.pcm.device" /**< an alsa Device interface */ #define SPA_NAME_API_ALSA_PCM_SOURCE "api.alsa.pcm.source" /**< an alsa Node interface for * capturing PCM */ #define SPA_NAME_API_ALSA_PCM_SINK "api.alsa.pcm.sink" /**< an alsa Node interface for * playback PCM */ #define SPA_NAME_API_ALSA_SEQ_DEVICE "api.alsa.seq.device" /**< an alsa Midi device */ #define SPA_NAME_API_ALSA_SEQ_SOURCE "api.alsa.seq.source" /**< an alsa Node interface for * capture of midi */ #define SPA_NAME_API_ALSA_SEQ_SINK "api.alsa.seq.sink" /**< an alsa Node interface for * playback of midi */ #define SPA_NAME_API_ALSA_SEQ_BRIDGE "api.alsa.seq.bridge" /**< an alsa Node interface for * bridging midi ports */ #define SPA_NAME_API_ALSA_ACP_DEVICE "api.alsa.acp.device" /**< an alsa ACP Device interface */ #define SPA_NAME_API_ALSA_COMPRESS_OFFLOAD_DEVICE "api.alsa.compress.offload.device" /**< an alsa Device interface for * compressed audio */ #define SPA_NAME_API_ALSA_COMPRESS_OFFLOAD_SINK "api.alsa.compress.offload.sink" /**< an alsa Node interface for * compressed audio */ /** keys for bluez5 factory names */ #define SPA_NAME_API_BLUEZ5_ENUM_DBUS "api.bluez5.enum.dbus" /**< a dbus Device interface */ #define SPA_NAME_API_BLUEZ5_DEVICE "api.bluez5.device" /**< a Device interface */ #define SPA_NAME_API_BLUEZ5_MEDIA_SINK "api.bluez5.media.sink" /**< a playback Node interface for A2DP/BAP profiles */ #define SPA_NAME_API_BLUEZ5_MEDIA_SOURCE "api.bluez5.media.source" /**< a capture Node interface for A2DP/BAP profiles */ #define SPA_NAME_API_BLUEZ5_A2DP_SINK "api.bluez5.a2dp.sink" /**< alias for media.sink */ #define SPA_NAME_API_BLUEZ5_A2DP_SOURCE "api.bluez5.a2dp.source" /**< alias for media.source */ #define SPA_NAME_API_BLUEZ5_SCO_SINK "api.bluez5.sco.sink" /**< a playback Node interface for HSP/HFP profiles */ #define SPA_NAME_API_BLUEZ5_SCO_SOURCE "api.bluez5.sco.source" /**< a capture Node interface for HSP/HFP profiles */ #define SPA_NAME_API_BLUEZ5_MIDI_ENUM "api.bluez5.midi.enum" /**< a dbus midi Device interface */ #define SPA_NAME_API_BLUEZ5_MIDI_NODE "api.bluez5.midi.node" /**< a midi Node interface */ /** keys for codec factory names */ #define SPA_NAME_API_CODEC_BLUEZ5_MEDIA "api.codec.bluez5.media" /**< Bluez5 Media codec plugin */ /** keys for v4l2 factory names */ #define SPA_NAME_API_V4L2_ENUM_UDEV "api.v4l2.enum.udev" /**< a v4l2 udev Device interface */ #define SPA_NAME_API_V4L2_DEVICE "api.v4l2.device" /**< a v4l2 Device interface */ #define SPA_NAME_API_V4L2_SOURCE "api.v4l2.source" /**< a v4l2 Node interface for * capturing */ /** keys for libcamera factory names */ #define SPA_NAME_API_LIBCAMERA_ENUM_CLIENT "api.libcamera.enum.client" /**< a libcamera client Device interface */ #define SPA_NAME_API_LIBCAMERA_ENUM_MANAGER "api.libcamera.enum.manager" /**< a libcamera manager Device interface */ #define SPA_NAME_API_LIBCAMERA_DEVICE "api.libcamera.device" /**< a libcamera Device interface */ #define SPA_NAME_API_LIBCAMERA_SOURCE "api.libcamera.source" /**< a libcamera Node interface for * capturing */ /** keys for jack factory names */ #define SPA_NAME_API_JACK_DEVICE "api.jack.device" /**< a jack device. This is a * client connected to a server */ #define SPA_NAME_API_JACK_SOURCE "api.jack.source" /**< a jack source */ #define SPA_NAME_API_JACK_SINK "api.jack.sink" /**< a jack sink */ /** keys for vulkan factory names */ #define SPA_NAME_API_VULKAN_COMPUTE_SOURCE \ "api.vulkan.compute.source" /**< a vulkan compute source. */ #define SPA_NAME_API_VULKAN_COMPUTE_FILTER \ "api.vulkan.compute.filter" /**< a vulkan compute filter. */ #define SPA_NAME_API_VULKAN_BLIT_FILTER \ "api.vulkan.blit.filter" /**< a vulkan blit filter. */ #define SPA_NAME_API_VULKAN_BLIT_DSP_FILTER \ "api.vulkan.blit.dsp-filter" /**< a vulkan blit dsp-filter. */ /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_UTILS_NAMES_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/utils/ratelimit.h000066400000000000000000000017261511204443500270000ustar00rootroot00000000000000/* Ratelimit */ /* SPDX-FileCopyrightText: Copyright © 2023 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_RATELIMIT_H #define SPA_RATELIMIT_H #include #include #include #ifdef __cplusplus extern "C" { #endif #ifndef SPA_API_RATELIMIT #ifdef SPA_API_IMPL #define SPA_API_RATELIMIT SPA_API_IMPL #else #define SPA_API_RATELIMIT static inline #endif #endif struct spa_ratelimit { uint64_t interval; uint64_t begin; unsigned burst; unsigned n_printed; unsigned n_suppressed; }; SPA_API_RATELIMIT int spa_ratelimit_test(struct spa_ratelimit *r, uint64_t now) { unsigned suppressed = 0; if (r->begin + r->interval < now) { suppressed = r->n_suppressed; r->begin = now; r->n_printed = 0; r->n_suppressed = 0; } else if (r->n_printed >= r->burst) { r->n_suppressed++; return -1; } r->n_printed++; return suppressed; } #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_RATELIMIT_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/utils/result.h000066400000000000000000000023021511204443500263130ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_UTILS_RESULT_H #define SPA_UTILS_RESULT_H #include #include #ifdef __cplusplus extern "C" { #endif /** * \defgroup spa_result Result handling * Asynchronous result utilities */ /** * \addtogroup spa_result * \{ */ #ifndef SPA_API_RESULT #ifdef SPA_API_IMPL #define SPA_API_RESULT SPA_API_IMPL #else #define SPA_API_RESULT static inline #endif #endif #define SPA_ASYNC_BIT (1 << 30) #define SPA_ASYNC_SEQ_MASK (SPA_ASYNC_BIT - 1) #define SPA_ASYNC_MASK (~SPA_ASYNC_SEQ_MASK) #define SPA_RESULT_IS_OK(res) ((res) >= 0) #define SPA_RESULT_IS_ERROR(res) ((res) < 0) #define SPA_RESULT_IS_ASYNC(res) (((res) & SPA_ASYNC_MASK) == SPA_ASYNC_BIT) #define SPA_RESULT_ASYNC_SEQ(res) ((res) & SPA_ASYNC_SEQ_MASK) #define SPA_RESULT_RETURN_ASYNC(seq) (SPA_ASYNC_BIT | SPA_RESULT_ASYNC_SEQ(seq)) SPA_API_RESULT const char *spa_strerror(int err) { int _err = -(err); if (SPA_RESULT_IS_ASYNC(err)) _err = EINPROGRESS; return strerror(_err); } /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_UTILS_RESULT_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/utils/ringbuffer.h000066400000000000000000000113721511204443500271350ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_RINGBUFFER_H #define SPA_RINGBUFFER_H #include #ifdef __cplusplus extern "C" { #endif /** * \defgroup spa_ringbuffer Ringbuffer * Ring buffer implementation */ /** * \addtogroup spa_ringbuffer * \{ */ struct spa_ringbuffer; #include #ifndef SPA_API_RINGBUFFER #ifdef SPA_API_IMPL #define SPA_API_RINGBUFFER SPA_API_IMPL #else #define SPA_API_RINGBUFFER static inline #endif #endif /** * A ringbuffer type. */ struct spa_ringbuffer { uint32_t readindex; /*< the current read index */ uint32_t writeindex; /*< the current write index */ }; #define SPA_RINGBUFFER_INIT() ((struct spa_ringbuffer) { 0, 0 }) /** * Initialize a spa_ringbuffer with \a size. * * \param rbuf a spa_ringbuffer */ SPA_API_RINGBUFFER void spa_ringbuffer_init(struct spa_ringbuffer *rbuf) { *rbuf = SPA_RINGBUFFER_INIT(); } /** * Sets the pointers so that the ringbuffer contains \a size bytes. * * \param rbuf a spa_ringbuffer * \param size the target size of \a rbuf */ SPA_API_RINGBUFFER void spa_ringbuffer_set_avail(struct spa_ringbuffer *rbuf, uint32_t size) { rbuf->readindex = 0; rbuf->writeindex = size; } /** * Get the read index and available bytes for reading. * * \param rbuf a spa_ringbuffer * \param index the value of readindex, should be taken modulo the size of the * ringbuffer memory to get the offset in the ringbuffer memory * \return number of available bytes to read. values < 0 mean * there was an underrun. values > rbuf->size means there * was an overrun. */ SPA_API_RINGBUFFER int32_t spa_ringbuffer_get_read_index(struct spa_ringbuffer *rbuf, uint32_t *index) { *index = __atomic_load_n(&rbuf->readindex, __ATOMIC_RELAXED); return (int32_t) (__atomic_load_n(&rbuf->writeindex, __ATOMIC_ACQUIRE) - *index); } /** * Read \a len bytes from \a rbuf starting \a offset. \a offset must be taken * modulo \a size and len should be smaller than \a size. * * \param rbuf a struct \ref spa_ringbuffer * \param buffer memory to read from * \param size the size of \a buffer * \param offset offset in \a buffer to read from * \param data destination memory * \param len number of bytes to read */ SPA_API_RINGBUFFER void spa_ringbuffer_read_data(struct spa_ringbuffer *rbuf SPA_UNUSED, const void *buffer, uint32_t size, uint32_t offset, void *data, uint32_t len) { uint32_t l0 = SPA_MIN(len, size - offset), l1 = len - l0; spa_memcpy(data, SPA_PTROFF(buffer, offset, void), l0); if (SPA_UNLIKELY(l1 > 0)) spa_memcpy(SPA_PTROFF(data, l0, void), buffer, l1); } /** * Update the read pointer to \a index. * * \param rbuf a spa_ringbuffer * \param index new index */ SPA_API_RINGBUFFER void spa_ringbuffer_read_update(struct spa_ringbuffer *rbuf, int32_t index) { __atomic_store_n(&rbuf->readindex, index, __ATOMIC_RELEASE); } /** * Get the write index and the number of bytes inside the ringbuffer. * * \param rbuf a spa_ringbuffer * \param index the value of writeindex, should be taken modulo the size of the * ringbuffer memory to get the offset in the ringbuffer memory * \return the fill level of \a rbuf. values < 0 mean * there was an underrun. values > rbuf->size means there * was an overrun. Subtract from the buffer size to get * the number of bytes available for writing. */ SPA_API_RINGBUFFER int32_t spa_ringbuffer_get_write_index(struct spa_ringbuffer *rbuf, uint32_t *index) { *index = __atomic_load_n(&rbuf->writeindex, __ATOMIC_RELAXED); return (int32_t) (*index - __atomic_load_n(&rbuf->readindex, __ATOMIC_ACQUIRE)); } /** * Write \a len bytes to \a buffer starting \a offset. \a offset must be taken * modulo \a size and len should be smaller than \a size. * * \param rbuf a spa_ringbuffer * \param buffer memory to write to * \param size the size of \a buffer * \param offset offset in \a buffer to write to * \param data source memory * \param len number of bytes to write */ SPA_API_RINGBUFFER void spa_ringbuffer_write_data(struct spa_ringbuffer *rbuf SPA_UNUSED, void *buffer, uint32_t size, uint32_t offset, const void *data, uint32_t len) { uint32_t l0 = SPA_MIN(len, size - offset), l1 = len - l0; spa_memcpy(SPA_PTROFF(buffer, offset, void), data, l0); if (SPA_UNLIKELY(l1 > 0)) spa_memcpy(buffer, SPA_PTROFF(data, l0, void), l1); } /** * Update the write pointer to \a index * * \param rbuf a spa_ringbuffer * \param index new index */ SPA_API_RINGBUFFER void spa_ringbuffer_write_update(struct spa_ringbuffer *rbuf, int32_t index) { __atomic_store_n(&rbuf->writeindex, index, __ATOMIC_RELEASE); } /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_RINGBUFFER_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/utils/string.h000066400000000000000000000203461511204443500263130ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2021 Red Hat, Inc. */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_UTILS_STRING_H #define SPA_UTILS_STRING_H #include #include #include #include #include #include #include #ifdef __cplusplus extern "C" { #endif #ifndef SPA_API_STRING #ifdef SPA_API_IMPL #define SPA_API_STRING SPA_API_IMPL #else #define SPA_API_STRING static inline #endif #endif /** * \defgroup spa_string String handling * String handling utilities */ /** * \addtogroup spa_string * \{ */ /** * \return true if the two strings are equal, false otherwise * * If both \a a and \a b are NULL, the two are considered equal. * */ SPA_API_STRING bool spa_streq(const char *s1, const char *s2) { return SPA_LIKELY(s1 && s2) ? strcmp(s1, s2) == 0 : s1 == s2; } /** * \return true if the two strings are equal, false otherwise * * If both \a a and \a b are NULL, the two are considered equal. */ SPA_API_STRING bool spa_strneq(const char *s1, const char *s2, size_t len) { return SPA_LIKELY(s1 && s2) ? strncmp(s1, s2, len) == 0 : s1 == s2; } /** * \return true if \a s starts with the \a prefix or false otherwise. * A \a s is NULL, it never starts with the given \a prefix. A \a prefix of * NULL is a bug in the caller. */ SPA_API_STRING bool spa_strstartswith(const char *s, const char *prefix) { if (SPA_UNLIKELY(s == NULL)) return false; spa_assert_se(prefix); return strncmp(s, prefix, strlen(prefix)) == 0; } /** * \return true if \a s ends with the \a suffix or false otherwise. * A \a s is NULL, it never ends with the given \a suffix. A \a suffix of * NULL is a bug in the caller. */ SPA_API_STRING bool spa_strendswith(const char *s, const char *suffix) { size_t l1, l2; if (SPA_UNLIKELY(s == NULL)) return false; spa_assert_se(suffix); l1 = strlen(s); l2 = strlen(suffix); return l1 >= l2 && spa_streq(s + l1 - l2, suffix); } /** * Convert \a str to an int32_t with the given \a base and store the * result in \a val. * * On failure, the value of \a val is unmodified. * * \return true on success, false otherwise */ SPA_API_STRING bool spa_atoi32(const char *str, int32_t *val, int base) { char *endptr; long v; if (!str || *str =='\0') return false; errno = 0; v = strtol(str, &endptr, base); if (errno != 0 || *endptr != '\0') return false; if (v != (int32_t)v) return false; *val = v; return true; } /** * Convert \a str to an uint32_t with the given \a base and store the * result in \a val. * * On failure, the value of \a val is unmodified. * * \return true on success, false otherwise */ SPA_API_STRING bool spa_atou32(const char *str, uint32_t *val, int base) { char *endptr; unsigned long long v; if (!str || *str =='\0') return false; errno = 0; v = strtoull(str, &endptr, base); if (errno != 0 || *endptr != '\0') return false; if (v != (uint32_t)v) return false; *val = v; return true; } /** * Convert \a str to an int64_t with the given \a base and store the * result in \a val. * * On failure, the value of \a val is unmodified. * * \return true on success, false otherwise */ SPA_API_STRING bool spa_atoi64(const char *str, int64_t *val, int base) { char *endptr; long long v; if (!str || *str =='\0') return false; errno = 0; v = strtoll(str, &endptr, base); if (errno != 0 || *endptr != '\0') return false; *val = v; return true; } /** * Convert \a str to an uint64_t with the given \a base and store the * result in \a val. * * On failure, the value of \a val is unmodified. * * \return true on success, false otherwise */ SPA_API_STRING bool spa_atou64(const char *str, uint64_t *val, int base) { char *endptr; unsigned long long v; if (!str || *str =='\0') return false; errno = 0; v = strtoull(str, &endptr, base); if (errno != 0 || *endptr != '\0') return false; *val = v; return true; } /** * Convert \a str to a boolean. Allowed boolean values are "true" and a * literal "1", anything else is false. * * \return true on success, false otherwise */ SPA_API_STRING bool spa_atob(const char *str) { return spa_streq(str, "true") || spa_streq(str, "1"); } /** * "Safe" version of vsnprintf. Exactly the same as vsnprintf but the * returned value is clipped to `size - 1` and a negative or zero size * will abort() the program. * * \return The number of bytes printed, capped to `size-1`, or a negative * number on error. */ SPA_PRINTF_FUNC(3, 0) SPA_API_STRING int spa_vscnprintf(char *buffer, size_t size, const char *format, va_list args) { int r; spa_assert_se((ssize_t)size > 0); r = vsnprintf(buffer, size, format, args); if (SPA_UNLIKELY(r < 0)) buffer[0] = '\0'; if (SPA_LIKELY(r < (ssize_t)size)) return r; return size - 1; } /** * "Safe" version of snprintf. Exactly the same as snprintf but the * returned value is clipped to `size - 1` and a negative or zero size * will abort() the program. * * \return The number of bytes printed, capped to `size-1`, or a negative * number on error. */ SPA_PRINTF_FUNC(3, 4) SPA_API_STRING int spa_scnprintf(char *buffer, size_t size, const char *format, ...) { int r; va_list args; va_start(args, format); r = spa_vscnprintf(buffer, size, format, args); va_end(args); return r; } /** * Convert \a str to a float in the C locale. * * If \a endptr is not NULL, a pointer to the character after the last character * used in the conversion is stored in the location referenced by endptr. * * \return the result float. */ SPA_API_STRING float spa_strtof(const char *str, char **endptr) { #ifndef __LOCALE_C_ONLY static locale_t locale = NULL; locale_t prev; #endif float v; #ifndef __LOCALE_C_ONLY if (SPA_UNLIKELY(locale == NULL)) locale = newlocale(LC_ALL_MASK, "C", NULL); prev = uselocale(locale); #endif v = strtof(str, endptr); #ifndef __LOCALE_C_ONLY uselocale(prev); #endif return v; } /** * Convert \a str to a float and store the result in \a val. * * On failure, the value of \a val is unmodified. * * \return true on success, false otherwise */ SPA_API_STRING bool spa_atof(const char *str, float *val) { char *endptr; float v; if (!str || *str =='\0') return false; errno = 0; v = spa_strtof(str, &endptr); if (errno != 0 || *endptr != '\0') return false; *val = v; return true; } /** * Convert \a str to a double in the C locale. * * If \a endptr is not NULL, a pointer to the character after the last character * used in the conversion is stored in the location referenced by endptr. * * \return the result float. */ SPA_API_STRING double spa_strtod(const char *str, char **endptr) { #ifndef __LOCALE_C_ONLY static locale_t locale = NULL; locale_t prev; #endif double v; #ifndef __LOCALE_C_ONLY if (SPA_UNLIKELY(locale == NULL)) locale = newlocale(LC_ALL_MASK, "C", NULL); prev = uselocale(locale); #endif v = strtod(str, endptr); #ifndef __LOCALE_C_ONLY uselocale(prev); #endif return v; } /** * Convert \a str to a double and store the result in \a val. * * On failure, the value of \a val is unmodified. * * \return true on success, false otherwise */ SPA_API_STRING bool spa_atod(const char *str, double *val) { char *endptr; double v; if (!str || *str =='\0') return false; errno = 0; v = spa_strtod(str, &endptr); if (errno != 0 || *endptr != '\0') return false; *val = v; return true; } SPA_API_STRING char *spa_dtoa(char *str, size_t size, double val) { int i, l; l = spa_scnprintf(str, size, "%f", val); for (i = 0; i < l; i++) if (str[i] == ',') str[i] = '.'; return str; } struct spa_strbuf { char *buffer; size_t maxsize; size_t pos; }; SPA_API_STRING void spa_strbuf_init(struct spa_strbuf *buf, char *buffer, size_t maxsize) { buf->buffer = buffer; buf->maxsize = maxsize; buf->pos = 0; if (maxsize > 0) buf->buffer[0] = '\0'; } SPA_PRINTF_FUNC(2, 3) SPA_API_STRING int spa_strbuf_append(struct spa_strbuf *buf, const char *fmt, ...) { size_t remain = buf->maxsize - buf->pos; ssize_t written; va_list args; va_start(args, fmt); written = vsnprintf(&buf->buffer[buf->pos], remain, fmt, args); va_end(args); if (written > 0) buf->pos += SPA_MIN(remain, (size_t)written); return written; } /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_UTILS_STRING_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/utils/type-info.h000066400000000000000000000110311511204443500267060ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_TYPE_INFO_H #define SPA_TYPE_INFO_H #include #include #include #include #include #include #include #ifdef __cplusplus extern "C" { #endif /** * \addtogroup spa_types * \{ */ #ifndef SPA_TYPE_ROOT #define SPA_TYPE_ROOT spa_types #endif static const struct spa_type_info spa_types[] = { /* Basic types */ { SPA_TYPE_START, SPA_TYPE_START, SPA_TYPE_INFO_BASE, NULL }, { SPA_TYPE_None, SPA_TYPE_None, SPA_TYPE_INFO_BASE "None", NULL }, { SPA_TYPE_Bool, SPA_TYPE_Bool, SPA_TYPE_INFO_BASE "Bool", NULL }, { SPA_TYPE_Id, SPA_TYPE_Int, SPA_TYPE_INFO_BASE "Id", NULL }, { SPA_TYPE_Int, SPA_TYPE_Int, SPA_TYPE_INFO_BASE "Int", NULL }, { SPA_TYPE_Long, SPA_TYPE_Long, SPA_TYPE_INFO_BASE "Long", NULL }, { SPA_TYPE_Float, SPA_TYPE_Float, SPA_TYPE_INFO_BASE "Float", NULL }, { SPA_TYPE_Double, SPA_TYPE_Double, SPA_TYPE_INFO_BASE "Double", NULL }, { SPA_TYPE_String, SPA_TYPE_String, SPA_TYPE_INFO_BASE "String", NULL }, { SPA_TYPE_Bytes, SPA_TYPE_Bytes, SPA_TYPE_INFO_BASE "Bytes", NULL }, { SPA_TYPE_Rectangle, SPA_TYPE_Rectangle, SPA_TYPE_INFO_BASE "Rectangle", NULL }, { SPA_TYPE_Fraction, SPA_TYPE_Fraction, SPA_TYPE_INFO_BASE "Fraction", NULL }, { SPA_TYPE_Bitmap, SPA_TYPE_Bitmap, SPA_TYPE_INFO_BASE "Bitmap", NULL }, { SPA_TYPE_Array, SPA_TYPE_Array, SPA_TYPE_INFO_BASE "Array", NULL }, { SPA_TYPE_Pod, SPA_TYPE_Pod, SPA_TYPE_INFO_Pod, NULL }, { SPA_TYPE_Struct, SPA_TYPE_Pod, SPA_TYPE_INFO_Struct, NULL }, { SPA_TYPE_Object, SPA_TYPE_Pod, SPA_TYPE_INFO_Object, NULL }, { SPA_TYPE_Sequence, SPA_TYPE_Pod, SPA_TYPE_INFO_POD_BASE "Sequence", NULL }, { SPA_TYPE_Pointer, SPA_TYPE_Pointer, SPA_TYPE_INFO_Pointer, NULL }, { SPA_TYPE_Fd, SPA_TYPE_Fd, SPA_TYPE_INFO_BASE "Fd", NULL }, { SPA_TYPE_Choice, SPA_TYPE_Pod, SPA_TYPE_INFO_POD_BASE "Choice", NULL }, { SPA_TYPE_POINTER_START, SPA_TYPE_Pointer, SPA_TYPE_INFO_Pointer, NULL }, { SPA_TYPE_POINTER_Buffer, SPA_TYPE_Pointer, SPA_TYPE_INFO_POINTER_BASE "Buffer", NULL }, { SPA_TYPE_POINTER_Meta, SPA_TYPE_Pointer, SPA_TYPE_INFO_POINTER_BASE "Meta", NULL }, { SPA_TYPE_POINTER_Dict, SPA_TYPE_Pointer, SPA_TYPE_INFO_POINTER_BASE "Dict", NULL }, { SPA_TYPE_EVENT_START, SPA_TYPE_Object, SPA_TYPE_INFO_Event, NULL }, { SPA_TYPE_EVENT_Device, SPA_TYPE_Object, SPA_TYPE_INFO_EVENT_BASE "Device", spa_type_device_event }, { SPA_TYPE_EVENT_Node, SPA_TYPE_Object, SPA_TYPE_INFO_EVENT_BASE "Node", spa_type_node_event }, { SPA_TYPE_COMMAND_START, SPA_TYPE_Object, SPA_TYPE_INFO_Command, NULL }, { SPA_TYPE_COMMAND_Device, SPA_TYPE_Object, SPA_TYPE_INFO_COMMAND_BASE "Device", NULL }, { SPA_TYPE_COMMAND_Node, SPA_TYPE_Object, SPA_TYPE_INFO_COMMAND_BASE "Node", spa_type_node_command }, { SPA_TYPE_OBJECT_START, SPA_TYPE_Object, SPA_TYPE_INFO_Object, NULL }, { SPA_TYPE_OBJECT_PropInfo, SPA_TYPE_Object, SPA_TYPE_INFO_PropInfo, spa_type_prop_info, }, { SPA_TYPE_OBJECT_Props, SPA_TYPE_Object, SPA_TYPE_INFO_Props, spa_type_props }, { SPA_TYPE_OBJECT_Format, SPA_TYPE_Object, SPA_TYPE_INFO_Format, spa_type_format }, { SPA_TYPE_OBJECT_ParamBuffers, SPA_TYPE_Object, SPA_TYPE_INFO_PARAM_Buffers, spa_type_param_buffers, }, { SPA_TYPE_OBJECT_ParamMeta, SPA_TYPE_Object, SPA_TYPE_INFO_PARAM_Meta, spa_type_param_meta }, { SPA_TYPE_OBJECT_ParamIO, SPA_TYPE_Object, SPA_TYPE_INFO_PARAM_IO, spa_type_param_io }, { SPA_TYPE_OBJECT_ParamProfile, SPA_TYPE_Object, SPA_TYPE_INFO_PARAM_Profile, spa_type_param_profile }, { SPA_TYPE_OBJECT_ParamPortConfig, SPA_TYPE_Object, SPA_TYPE_INFO_PARAM_PortConfig, spa_type_param_port_config }, { SPA_TYPE_OBJECT_ParamRoute, SPA_TYPE_Object, SPA_TYPE_INFO_PARAM_Route, spa_type_param_route }, { SPA_TYPE_OBJECT_Profiler, SPA_TYPE_Object, SPA_TYPE_INFO_Profiler, spa_type_profiler }, { SPA_TYPE_OBJECT_ParamLatency, SPA_TYPE_Object, SPA_TYPE_INFO_PARAM_Latency, spa_type_param_latency }, { SPA_TYPE_OBJECT_ParamProcessLatency, SPA_TYPE_Object, SPA_TYPE_INFO_PARAM_ProcessLatency, spa_type_param_process_latency }, { SPA_TYPE_OBJECT_ParamTag, SPA_TYPE_Object, SPA_TYPE_INFO_PARAM_Tag, spa_type_param_tag }, { SPA_TYPE_OBJECT_PeerParam, SPA_TYPE_Object, SPA_TYPE_INFO_PeerParam, spa_type_peer_param }, { SPA_TYPE_OBJECT_ParamDict, SPA_TYPE_Object, SPA_TYPE_INFO_PARAM_Dict, spa_type_param_dict }, { 0, 0, NULL, NULL } }; /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_TYPE_INFO_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/include/spa/utils/type.h000066400000000000000000000105431511204443500257640ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_TYPE_H #define SPA_TYPE_H #include #include #ifdef __cplusplus extern "C" { #endif #ifndef SPA_API_TYPE #ifdef SPA_API_IMPL #define SPA_API_TYPE SPA_API_IMPL #else #define SPA_API_TYPE static inline #endif #endif /** \defgroup spa_types Types * Data type information enumerations */ /** * \addtogroup spa_types * \{ */ enum { /* Basic types */ SPA_TYPE_START = 0x00000, SPA_TYPE_None, SPA_TYPE_Bool, SPA_TYPE_Id, SPA_TYPE_Int, SPA_TYPE_Long, SPA_TYPE_Float, SPA_TYPE_Double, SPA_TYPE_String, SPA_TYPE_Bytes, SPA_TYPE_Rectangle, SPA_TYPE_Fraction, SPA_TYPE_Bitmap, SPA_TYPE_Array, SPA_TYPE_Struct, SPA_TYPE_Object, SPA_TYPE_Sequence, SPA_TYPE_Pointer, SPA_TYPE_Fd, SPA_TYPE_Choice, SPA_TYPE_Pod, _SPA_TYPE_LAST, /**< not part of ABI */ /* Pointers */ SPA_TYPE_POINTER_START = 0x10000, SPA_TYPE_POINTER_Buffer, SPA_TYPE_POINTER_Meta, SPA_TYPE_POINTER_Dict, _SPA_TYPE_POINTER_LAST, /**< not part of ABI */ /* Events */ SPA_TYPE_EVENT_START = 0x20000, SPA_TYPE_EVENT_Device, SPA_TYPE_EVENT_Node, _SPA_TYPE_EVENT_LAST, /**< not part of ABI */ /* Commands */ SPA_TYPE_COMMAND_START = 0x30000, SPA_TYPE_COMMAND_Device, SPA_TYPE_COMMAND_Node, _SPA_TYPE_COMMAND_LAST, /**< not part of ABI */ /* Objects */ SPA_TYPE_OBJECT_START = 0x40000, SPA_TYPE_OBJECT_PropInfo, SPA_TYPE_OBJECT_Props, SPA_TYPE_OBJECT_Format, SPA_TYPE_OBJECT_ParamBuffers, SPA_TYPE_OBJECT_ParamMeta, SPA_TYPE_OBJECT_ParamIO, SPA_TYPE_OBJECT_ParamProfile, SPA_TYPE_OBJECT_ParamPortConfig, SPA_TYPE_OBJECT_ParamRoute, SPA_TYPE_OBJECT_Profiler, SPA_TYPE_OBJECT_ParamLatency, SPA_TYPE_OBJECT_ParamProcessLatency, SPA_TYPE_OBJECT_ParamTag, SPA_TYPE_OBJECT_PeerParam, SPA_TYPE_OBJECT_ParamDict, _SPA_TYPE_OBJECT_LAST, /**< not part of ABI */ /* vendor extensions */ SPA_TYPE_VENDOR_PipeWire = 0x02000000, SPA_TYPE_VENDOR_Other = 0x7f000000, }; #define SPA_TYPE_INFO_BASE "Spa:" #define SPA_TYPE_INFO_Flags SPA_TYPE_INFO_BASE "Flags" #define SPA_TYPE_INFO_FLAGS_BASE SPA_TYPE_INFO_Flags ":" #define SPA_TYPE_INFO_Enum SPA_TYPE_INFO_BASE "Enum" #define SPA_TYPE_INFO_ENUM_BASE SPA_TYPE_INFO_Enum ":" #define SPA_TYPE_INFO_Pod SPA_TYPE_INFO_BASE "Pod" #define SPA_TYPE_INFO_POD_BASE SPA_TYPE_INFO_Pod ":" #define SPA_TYPE_INFO_Struct SPA_TYPE_INFO_POD_BASE "Struct" #define SPA_TYPE_INFO_STRUCT_BASE SPA_TYPE_INFO_Struct ":" #define SPA_TYPE_INFO_Object SPA_TYPE_INFO_POD_BASE "Object" #define SPA_TYPE_INFO_OBJECT_BASE SPA_TYPE_INFO_Object ":" #define SPA_TYPE_INFO_Pointer SPA_TYPE_INFO_BASE "Pointer" #define SPA_TYPE_INFO_POINTER_BASE SPA_TYPE_INFO_Pointer ":" #define SPA_TYPE_INFO_Interface SPA_TYPE_INFO_POINTER_BASE "Interface" #define SPA_TYPE_INFO_INTERFACE_BASE SPA_TYPE_INFO_Interface ":" #define SPA_TYPE_INFO_Event SPA_TYPE_INFO_OBJECT_BASE "Event" #define SPA_TYPE_INFO_EVENT_BASE SPA_TYPE_INFO_Event ":" #define SPA_TYPE_INFO_Command SPA_TYPE_INFO_OBJECT_BASE "Command" #define SPA_TYPE_INFO_COMMAND_BASE SPA_TYPE_INFO_Command ":" struct spa_type_info { uint32_t type; uint32_t parent; const char *name; const struct spa_type_info *values; }; SPA_API_TYPE bool spa_type_is_a(const char *type, const char *parent) { return type != NULL && parent != NULL && strncmp(type, parent, strlen(parent)) == 0; } SPA_API_TYPE const char *spa_type_short_name(const char *name) { const char *h; if ((h = strrchr(name, ':')) != NULL) name = h + 1; return name; } SPA_API_TYPE uint32_t spa_type_from_short_name(const char *name, const struct spa_type_info *info, uint32_t unknown) { int i; for (i = 0; info[i].name; i++) { if (spa_streq(name, spa_type_short_name(info[i].name))) return info[i].type; } return unknown; } SPA_API_TYPE const char * spa_type_to_name(uint32_t type, const struct spa_type_info *info, const char *unknown) { int i; for (i = 0; info[i].name; i++) { if (info[i].type == type) return info[i].name; } return unknown; } SPA_API_TYPE const char * spa_type_to_short_name(uint32_t type, const struct spa_type_info *info, const char *unknown) { const char *n = spa_type_to_name(type, info, unknown); return n ? spa_type_short_name(n) : NULL; } /** * \} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_TYPE_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/lib/000077500000000000000000000000001511204443500220275ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/lib/lib.c000066400000000000000000000130231511204443500227400ustar00rootroot00000000000000 #undef SPA_AUDIO_MAX_CHANNELS #define SPA_API_IMPL SPA_EXPORT #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/lib/meson.build000066400000000000000000000002741511204443500241740ustar00rootroot00000000000000spa_lib = shared_library('spa', [ 'lib.c' ], include_directories : [ configinc ], dependencies : [ spa_dep, pthread_lib, mathlib ], install : true, install_dir : spa_plugindir ) pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/meson.build000066400000000000000000000166521511204443500234350ustar00rootroot00000000000000#project('spa', 'c') #cc = meson.get_compiler('c') #dl_lib = cc.find_library('dl', required : false) #pthread_lib = dependencies('threads') #mathlib = cc.find_library('m', required : false) spa_dep = declare_dependency( include_directories : [ include_directories('include'), include_directories('include-private'), ], dependencies : [atomic_dep], version : spaversion, variables : { 'plugindir' : meson.current_build_dir() / 'plugins', 'datadir' : meson.current_source_dir() / 'plugins', }, ) spa_inc_dep = declare_dependency( include_directories : [ include_directories('include'), include_directories('include-private'), ], ) meson.override_dependency('lib@0@'.format(spa_name), spa_dep) pkgconfig.generate(filebase : 'lib@0@'.format(spa_name), name : 'libspa', subdirs : spa_name, description : 'Simple Plugin API', version : spaversion, extra_cflags : ['-D_REENTRANT', '-fno-strict-aliasing', '-fno-strict-overflow'], variables : ['plugindir=${libdir}/@0@'.format(spa_name)], uninstalled_variables : ['plugindir=${prefix}/spa/plugins'], ) subdir('include') jack_dep = dependency('jack', version : '>= 1.9.10', required: get_option('jack')) summary({'JACK2': jack_dep.found()}, bool_yn: true, section: 'Backend') if get_option('spa-plugins').allowed() udevrulesdir = get_option('udevrulesdir') if udevrulesdir == '' # absolute path, otherwise meson prepends the prefix udevrulesdir = '/usr/lib/udev/rules.d' endif # plugin-specific dependencies alsa_dep = dependency('alsa', version : '>=1.2.6', required: get_option('alsa')) summary({'ALSA': alsa_dep.found()}, bool_yn: true, section: 'Backend') if alsa_dep.version().version_compare('>=1.2.11') cdata.set('HAVE_ALSA_UMP', true) endif bluez_dep = dependency('bluez', version : '>= 4.101', required: get_option('bluez5')) bluez_gio_dep = dependency('gio-2.0', required : get_option('bluez5')) bluez_gio_unix_dep = dependency('gio-unix-2.0', required : get_option('bluez5')) bluez_glib2_dep = dependency('glib-2.0', required : get_option('bluez5')) sbc_dep = dependency('sbc', required: get_option('bluez5')) summary({'SBC': sbc_dep.found()}, bool_yn: true, section: 'Bluetooth audio codecs') bluez5_deps = [ mathlib, dbus_dep, sbc_dep, bluez_dep, bluez_glib2_dep, bluez_gio_dep, bluez_gio_unix_dep ] bluez_deps_found = get_option('bluez5').allowed() foreach dep: bluez5_deps if get_option('bluez5').enabled() and not dep.found() error('bluez5 enabled, but dependency not found: ' + dep.name()) endif bluez_deps_found = bluez_deps_found and dep.found() endforeach summary({'Bluetooth audio': bluez_deps_found}, bool_yn: true, section: 'Backend') if bluez_deps_found ldac_dep = dependency('ldacBT-enc', required : get_option('bluez5-codec-ldac')) summary({'LDAC': ldac_dep.found()}, bool_yn: true, section: 'Bluetooth audio codecs') ldac_abr_dep = dependency('ldacBT-abr', required : get_option('bluez5-codec-ldac')) summary({'LDAC ABR': ldac_abr_dep.found()}, bool_yn: true, section: 'Bluetooth audio codecs') if get_option('bluez5-codec-ldac-dec').allowed() ldac_dec_dep = dependency('ldacBT-dec', required : false) if not ldac_dec_dep.found() dep = cc.find_library('ldacBT_dec', required : false) if dep.found() and cc.has_function('ldacBT_decode', dependencies : dep) ldac_dec_dep = dep endif endif if not ldac_dec_dep.found() and get_option('bluez5-codec-ldac-dec').enabled() error('LDAC decoder library not found') endif else ldac_dec_dep = dependency('', required: false) endif summary({'LDAC DEC': ldac_dec_dep.found()}, bool_yn: true, section: 'Bluetooth audio codecs') aptx_dep = dependency('libfreeaptx', required : get_option('bluez5-codec-aptx')) summary({'aptX': aptx_dep.found()}, bool_yn: true, section: 'Bluetooth audio codecs') fdk_aac_dep = dependency('fdk-aac', required : get_option('bluez5-codec-aac')) summary({'AAC': fdk_aac_dep.found()}, bool_yn: true, section: 'Bluetooth audio codecs') lc3plus_dep = dependency('lc3plus', required : false) if not lc3plus_dep.found() lc3plus_lc3plus_h_dep = cc.find_library('LC3plus', has_headers: ['lc3plus.h'], required : get_option('bluez5-codec-lc3plus')) if lc3plus_lc3plus_h_dep.found() lc3plus_dep = declare_dependency(compile_args : '-DHAVE_LC3PLUS_H', dependencies : [ lc3plus_lc3plus_h_dep ]) endif endif summary({'LC3plus': lc3plus_dep.found()}, bool_yn: true, section: 'Bluetooth audio codecs') if get_option('bluez5-codec-opus').enabled() and not opus_dep.found() error('bluez5-codec-opus enabled, but opus dependency not found') endif summary({'Opus': opus_dep.found()}, bool_yn: true, section: 'Bluetooth audio codecs') lc3_dep = dependency('lc3', required : get_option('bluez5-codec-lc3')) summary({'LC3': lc3_dep.found()}, bool_yn: true, section: 'Bluetooth audio codecs') if get_option('bluez5-backend-hsp-native').allowed() or get_option('bluez5-backend-hfp-native').allowed() mm_dep = dependency('ModemManager', version : '>= 1.10.0', required : get_option('bluez5-backend-native-mm')) summary({'ModemManager': mm_dep.found()}, bool_yn: true, section: 'Bluetooth backends') endif g722_codec_option = get_option('bluez5-codec-g722') summary({'G722': g722_codec_option.allowed()}, bool_yn: true, section: 'Bluetooth audio codecs') spandsp_dep = dependency('spandsp', required : get_option('bluez5-plc-spandsp')) cdata.set('HAVE_SPANDSP', spandsp_dep.found()) endif have_vulkan = false vulkan_dep = dependency('vulkan', version : '>= 1.2.170', required: get_option('vulkan')) if vulkan_dep.found() have_vulkan = cc.has_header('vulkan/vulkan.h', dependencies : vulkan_dep) assert((not get_option('vulkan').enabled()) or have_vulkan, 'Vulkan headers are missing') endif summary({'Vulkan': have_vulkan}, bool_yn: true, section: 'Misc dependencies') libcamera_dep = dependency('libcamera', version: '>= 0.2.0', required: get_option('libcamera')) summary({'libcamera': libcamera_dep.found()}, bool_yn: true, section: 'Backend') compress_offload_option = get_option('compress-offload') summary({'Compress-Offload': compress_offload_option.allowed()}, bool_yn: true, section: 'Backend') cdata.set('HAVE_ALSA_COMPRESS_OFFLOAD', compress_offload_option.allowed()) # common dependencies libudev_dep = dependency('libudev', required: get_option('udev')) cdata.set('HAVE_LIBUDEV', libudev_dep.found()) summary({'Udev': libudev_dep.found()}, bool_yn: true, section: 'Backend') libmysofa_dep = dependency('libmysofa', required : get_option('libmysofa')) summary({'libmysofa': libmysofa_dep.found()}, bool_yn: true, section: 'filter-graph') lilv_lib = dependency('lilv-0', required: get_option('lv2')) summary({'lilv (for lv2 plugins)': lilv_lib.found()}, bool_yn: true, section: 'filter-graph') ebur128_lib = dependency('libebur128', required: get_option('ebur128')) summary({'EBUR128': ebur128_lib.found()}, bool_yn: true, section: 'filter-graph') summary({'ffmpeg': avfilter_dep.found()}, bool_yn: true, section: 'filter-graph') onnxruntime_dep = dependency('libonnxruntime', required: get_option('onnxruntime')) summary({'onnxruntime': onnxruntime_dep.found()}, bool_yn: true, section: 'filter-graph') cdata.set('HAVE_SPA_PLUGINS', true) subdir('plugins') endif subdir('tools') subdir('tests') subdir('examples') subdir('lib') pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/000077500000000000000000000000001511204443500227425ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/aec/000077500000000000000000000000001511204443500234725ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/aec/aec-null.c000066400000000000000000000070061511204443500253410ustar00rootroot00000000000000/* PipeWire */ /* SPDX-FileCopyrightText: Copyright © 2021 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #include struct impl { struct spa_handle handle; struct spa_audio_aec aec; struct spa_log *log; struct spa_hook_list hooks_list; uint32_t channels; }; SPA_LOG_TOPIC_DEFINE_STATIC(log_topic, "spa.aec.null"); #undef SPA_LOG_TOPIC_DEFAULT #define SPA_LOG_TOPIC_DEFAULT &log_topic static int null_init(void *object, const struct spa_dict *args, const struct spa_audio_info_raw *info) { struct impl *impl = object; impl->channels = info->channels; return 0; } static int null_run(void *object, const float *rec[], const float *play[], float *out[], uint32_t n_samples) { struct impl *impl = object; uint32_t i; for (i = 0; i < impl->channels; i++) memcpy(out[i], rec[i], n_samples * sizeof(float)); return 0; } static const struct spa_audio_aec_methods impl_aec = { SPA_VERSION_AUDIO_AEC, .init = null_init, .run = null_run, }; static int impl_get_interface(struct spa_handle *handle, const char *type, void **interface) { spa_return_val_if_fail(handle != NULL, -EINVAL); spa_return_val_if_fail(interface != NULL, -EINVAL); struct impl *impl = (struct impl *) handle; if (spa_streq(type, SPA_TYPE_INTERFACE_AUDIO_AEC)) *interface = &impl->aec; else return -ENOENT; return 0; } static int impl_clear(struct spa_handle *handle) { spa_return_val_if_fail(handle != NULL, -EINVAL); return 0; } static size_t impl_get_size(const struct spa_handle_factory *factory, const struct spa_dict *params) { return sizeof(struct impl); } static int impl_init(const struct spa_handle_factory *factory, struct spa_handle *handle, const struct spa_dict *info, const struct spa_support *support, uint32_t n_support) { spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(handle != NULL, -EINVAL); handle->get_interface = impl_get_interface; handle->clear = impl_clear; struct impl *impl = (struct impl *) handle; impl->aec.iface = SPA_INTERFACE_INIT( SPA_TYPE_INTERFACE_AUDIO_AEC, SPA_VERSION_AUDIO_AEC, &impl_aec, impl); impl->aec.name = "null"; impl->aec.info = NULL; impl->aec.latency = NULL; impl->log = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log); spa_log_topic_init(impl->log, &log_topic); spa_hook_list_init(&impl->hooks_list); return 0; } static const struct spa_interface_info impl_interfaces[] = { {SPA_TYPE_INTERFACE_AUDIO_AEC,}, }; static int impl_enum_interface_info(const struct spa_handle_factory *factory, const struct spa_interface_info **info, uint32_t *index) { spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(info != NULL, -EINVAL); spa_return_val_if_fail(index != NULL, -EINVAL); switch (*index) { case 0: *info = &impl_interfaces[*index]; break; default: return 0; } (*index)++; return 1; } static const struct spa_handle_factory spa_aec_null_factory = { SPA_VERSION_HANDLE_FACTORY, SPA_NAME_AEC, NULL, impl_get_size, impl_init, impl_enum_interface_info, }; SPA_LOG_TOPIC_ENUM_DEFINE_REGISTERED; SPA_EXPORT int spa_handle_factory_enum(const struct spa_handle_factory **factory, uint32_t *index) { spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(index != NULL, -EINVAL); switch (*index) { case 0: *factory = &spa_aec_null_factory; break; default: return 0; } (*index)++; return 1; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/aec/aec-webrtc.cpp000066400000000000000000000364041511204443500262210ustar00rootroot00000000000000/* PipeWire */ /* SPDX-FileCopyrightText: Copyright © 2021 Wim Taymans */ /* SPDX-FileCopyrightText: Copyright © 2021 Arun Raghavan */ /* SPDX-License-Identifier: MIT */ #include "config.h" #include #include #include #include #include #include #include #include #ifdef HAVE_WEBRTC #include #include #include #else #include #endif struct impl_data { struct spa_handle handle; struct spa_audio_aec aec; struct spa_log *log; #if defined(HAVE_WEBRTC) || defined(HAVE_WEBRTC1) std::unique_ptr apm; #elif defined(HAVE_WEBRTC2) rtc::scoped_refptr apm; #endif spa_audio_info_raw rec_info; spa_audio_info_raw out_info; spa_audio_info_raw play_info; std::unique_ptr play_buffer, rec_buffer, out_buffer; }; SPA_LOG_TOPIC_DEFINE_STATIC(log_topic, "spa.eac.webrtc"); #undef SPA_LOG_TOPIC_DEFAULT #define SPA_LOG_TOPIC_DEFAULT &log_topic static bool webrtc_get_spa_bool(const struct spa_dict *args, const char *key, bool default_value) { if (auto str = spa_dict_lookup(args, key)) return spa_atob(str); return default_value; } #ifdef HAVE_WEBRTC /* [ f0 f1 f2 ] */ static int parse_point(struct spa_json *it, float (&f)[3]) { struct spa_json arr; int i, res; if (spa_json_enter_array(it, &arr) <= 0) return -EINVAL; for (i = 0; i < 3; i++) { if ((res = spa_json_get_float(&arr, &f[i])) <= 0) return -EINVAL; } return 0; } /* [ point1 point2 ... ] */ static int parse_mic_geometry(struct impl_data *impl, const char *mic_geometry, std::vector& geometry) { int res; size_t i; struct spa_json it[1]; if (spa_json_begin_array(&it[0], mic_geometry, strlen(mic_geometry)) <= 0) { spa_log_error(impl->log, "Error: webrtc.mic-geometry expects an array"); return -EINVAL; } for (i = 0; i < geometry.size(); i++) { float f[3]; if ((res = parse_point(&it[0], f)) < 0) { spa_log_error(impl->log, "Error: can't parse webrtc.mic-geometry points: %d", res); return res; } spa_log_info(impl->log, "mic %zd position: (%g %g %g)", i, f[0], f[1], f[2]); geometry[i].c[0] = f[0]; geometry[i].c[1] = f[1]; geometry[i].c[2] = f[2]; } return 0; } #endif static int webrtc_init2(void *object, const struct spa_dict *args, struct spa_audio_info_raw *rec_info, struct spa_audio_info_raw *out_info, struct spa_audio_info_raw *play_info) { auto impl = static_cast(object); int res; bool high_pass_filter = webrtc_get_spa_bool(args, "webrtc.high_pass_filter", true); bool noise_suppression = webrtc_get_spa_bool(args, "webrtc.noise_suppression", true); #if defined(HAVE_WEBRTC) bool extended_filter = webrtc_get_spa_bool(args, "webrtc.extended_filter", true); bool delay_agnostic = webrtc_get_spa_bool(args, "webrtc.delay_agnostic", true); // Disable experimental flags by default bool experimental_agc = webrtc_get_spa_bool(args, "webrtc.experimental_agc", false); bool experimental_ns = webrtc_get_spa_bool(args, "webrtc.experimental_ns", false); bool voice_detection = webrtc_get_spa_bool(args, "webrtc.voice_detection", true); bool beamforming = webrtc_get_spa_bool(args, "webrtc.beamforming", false); #elif defined(HAVE_WEBRTC1) bool voice_detection = webrtc_get_spa_bool(args, "webrtc.voice_detection", true); bool transient_suppression = webrtc_get_spa_bool(args, "webrtc.transient_suppression", true); bool mobile_mode = webrtc_get_spa_bool(args, "webrtc.mobile_mode", false); #elif defined(HAVE_WEBRTC2) bool mobile_mode = webrtc_get_spa_bool(args, "webrtc.mobile_mode", false); #endif // Note: AGC seems to mess up with Agnostic Delay Detection, especially with speech, // result in very poor performance, disable by default bool gain_control = webrtc_get_spa_bool(args, "webrtc.gain_control", false); #if defined(HAVE_WEBRTC) webrtc::Config config; config.Set(new webrtc::ExtendedFilter(extended_filter)); config.Set(new webrtc::DelayAgnostic(delay_agnostic)); config.Set(new webrtc::ExperimentalAgc(experimental_agc)); config.Set(new webrtc::ExperimentalNs(experimental_ns)); if (beamforming) { std::vector geometry(rec_info->channels); const char *mic_geometry, *target_direction; /* The beamformer gives a single mono channel */ out_info->channels = 1; out_info->position[0] = SPA_AUDIO_CHANNEL_MONO; if ((mic_geometry = spa_dict_lookup(args, "webrtc.mic-geometry")) == NULL) { spa_log_error(impl->log, "Error: webrtc.beamforming requires webrtc.mic-geometry"); return -EINVAL; } if ((res = parse_mic_geometry(impl, mic_geometry, geometry)) < 0) return res; if ((target_direction = spa_dict_lookup(args, "webrtc.target-direction")) != NULL) { webrtc::SphericalPointf direction(0.0f, 0.0f, 0.0f); struct spa_json it; float f[3]; spa_json_init(&it, target_direction, strlen(target_direction)); if (parse_point(&it, f) < 0) { spa_log_error(impl->log, "Error: can't parse target-direction %s", target_direction); return -EINVAL; } direction.s[0] = f[0]; direction.s[1] = f[1]; direction.s[2] = f[2]; config.Set(new webrtc::Beamforming(true, geometry, direction)); } else { config.Set(new webrtc::Beamforming(true, geometry)); } } #elif defined(HAVE_WEBRTC1) webrtc::AudioProcessing::Config config; config.echo_canceller.enabled = true; config.echo_canceller.mobile_mode = mobile_mode; config.pipeline.multi_channel_capture = rec_info->channels > 1; config.pipeline.multi_channel_render = play_info->channels > 1; // FIXME: Example code enables both gain controllers, but that seems sus config.gain_controller1.enabled = gain_control; config.gain_controller1.mode = webrtc::AudioProcessing::Config::GainController1::Mode::kAdaptiveDigital; config.gain_controller1.analog_level_minimum = 0; config.gain_controller1.analog_level_maximum = 255; config.gain_controller2.enabled = gain_control; config.high_pass_filter.enabled = high_pass_filter; config.noise_suppression.enabled = noise_suppression; config.noise_suppression.level = webrtc::AudioProcessing::Config::NoiseSuppression::kHigh; // FIXME: expose pre/postamp gain config.transient_suppression.enabled = transient_suppression; config.voice_detection.enabled = voice_detection; #elif defined(HAVE_WEBRTC2) webrtc::AudioProcessing::Config config; config.echo_canceller.enabled = true; config.echo_canceller.mobile_mode = mobile_mode; config.pipeline.multi_channel_capture = rec_info->channels > 1; config.pipeline.multi_channel_render = play_info->channels > 1; // FIXME: Example code enables both gain controllers, but that seems sus config.gain_controller1.enabled = gain_control; config.gain_controller1.mode = webrtc::AudioProcessing::Config::GainController1::Mode::kAdaptiveDigital; config.gain_controller2.enabled = gain_control; config.high_pass_filter.enabled = high_pass_filter; config.noise_suppression.enabled = noise_suppression; config.noise_suppression.level = webrtc::AudioProcessing::Config::NoiseSuppression::kHigh; // FIXME: expose pre/postamp gain #endif #if defined(HAVE_WEBRTC) || defined(HAVE_WEBRTC1) webrtc::ProcessingConfig pconfig = {{ webrtc::StreamConfig(rec_info->rate, rec_info->channels, false), /* input stream */ webrtc::StreamConfig(out_info->rate, out_info->channels, false), /* output stream */ webrtc::StreamConfig(play_info->rate, play_info->channels, false), /* reverse input stream */ webrtc::StreamConfig(play_info->rate, play_info->channels, false), /* reverse output stream */ }}; #elif defined(HAVE_WEBRTC2) webrtc::ProcessingConfig pconfig = {{ webrtc::StreamConfig(rec_info->rate, rec_info->channels), /* input stream */ webrtc::StreamConfig(out_info->rate, out_info->channels), /* output stream */ webrtc::StreamConfig(play_info->rate, play_info->channels), /* reverse input stream */ webrtc::StreamConfig(play_info->rate, play_info->channels), /* reverse output stream */ }}; #endif #if defined(HAVE_WEBRTC) auto apm = std::unique_ptr(webrtc::AudioProcessing::Create(config)); #elif defined(HAVE_WEBRTC1) auto apm = std::unique_ptr(webrtc::AudioProcessingBuilder().Create()); apm->ApplyConfig(config); #elif defined(HAVE_WEBRTC2) auto apm = webrtc::AudioProcessingBuilder().Create(); apm->ApplyConfig(config); #endif if ((res = apm->Initialize(pconfig)) != webrtc::AudioProcessing::kNoError) { spa_log_error(impl->log, "Error initialising webrtc audio processing module: %d", res); return -EINVAL; } #ifdef HAVE_WEBRTC apm->high_pass_filter()->Enable(high_pass_filter); // Always disable drift compensation since PipeWire will already do // drift compensation on all sinks and sources linked to this echo-canceler apm->echo_cancellation()->enable_drift_compensation(false); apm->echo_cancellation()->Enable(true); // TODO: wire up suppression levels to args apm->echo_cancellation()->set_suppression_level(webrtc::EchoCancellation::kHighSuppression); apm->noise_suppression()->set_level(webrtc::NoiseSuppression::kHigh); apm->noise_suppression()->Enable(noise_suppression); apm->voice_detection()->Enable(voice_detection); // TODO: wire up AGC parameters to args apm->gain_control()->set_analog_level_limits(0, 255); apm->gain_control()->set_mode(webrtc::GainControl::kAdaptiveDigital); apm->gain_control()->Enable(gain_control); #endif impl->apm = std::move(apm); impl->rec_info = *rec_info; impl->out_info = *out_info; impl->play_info = *play_info; impl->play_buffer = std::make_unique(play_info->channels); impl->rec_buffer = std::make_unique(rec_info->channels); impl->out_buffer = std::make_unique(out_info->channels); return 0; } static int webrtc_init(void *object, const struct spa_dict *args, const struct spa_audio_info_raw *info) { int res; struct spa_audio_info_raw rec_info = *info; struct spa_audio_info_raw out_info = *info; struct spa_audio_info_raw play_info = *info; res = webrtc_init2(object, args, &rec_info, &out_info, &play_info); if (rec_info.channels != out_info.channels) res = -EINVAL; return res; } static int webrtc_run(void *object, const float *rec[], const float *play[], float *out[], uint32_t n_samples) { auto impl = static_cast(object); int res; #if defined(HAVE_WEBRTC) || defined(HAVE_WEBRTC1) webrtc::StreamConfig play_config = webrtc::StreamConfig(impl->play_info.rate, impl->play_info.channels, false); webrtc::StreamConfig rec_config = webrtc::StreamConfig(impl->rec_info.rate, impl->rec_info.channels, false); webrtc::StreamConfig out_config = webrtc::StreamConfig(impl->out_info.rate, impl->out_info.channels, false); #elif defined(HAVE_WEBRTC2) webrtc::StreamConfig play_config = webrtc::StreamConfig(impl->play_info.rate, impl->play_info.channels); webrtc::StreamConfig rec_config = webrtc::StreamConfig(impl->rec_info.rate, impl->rec_info.channels); webrtc::StreamConfig out_config = webrtc::StreamConfig(impl->out_info.rate, impl->out_info.channels); #endif unsigned int num_blocks = n_samples * 1000 / impl->play_info.rate / 10; if (n_samples * 1000 / impl->play_info.rate % 10 != 0) { spa_log_error(impl->log, "Buffers must be multiples of 10ms in length (currently %u samples)", n_samples); return -EINVAL; } for (size_t i = 0; i < num_blocks; i ++) { for (size_t j = 0; j < impl->play_info.channels; j++) impl->play_buffer[j] = const_cast(play[j]) + play_config.num_frames() * i; for (size_t j = 0; j < impl->rec_info.channels; j++) impl->rec_buffer[j] = const_cast(rec[j]) + rec_config.num_frames() * i; for (size_t j = 0; j < impl->out_info.channels; j++) impl->out_buffer[j] = out[j] + out_config.num_frames() * i; if ((res = impl->apm->ProcessReverseStream(impl->play_buffer.get(), play_config, play_config, impl->play_buffer.get())) != webrtc::AudioProcessing::kNoError) { spa_log_error(impl->log, "Processing reverse stream failed: %d", res); } // Extra delay introduced by multiple frames impl->apm->set_stream_delay_ms((num_blocks - 1) * 10); if ((res = impl->apm->ProcessStream(impl->rec_buffer.get(), rec_config, out_config, impl->out_buffer.get())) != webrtc::AudioProcessing::kNoError) { spa_log_error(impl->log, "Processing stream failed: %d", res); } } return 0; } static const struct spa_audio_aec_methods impl_aec = { .version = SPA_VERSION_AUDIO_AEC_METHODS, .add_listener = NULL, .init = webrtc_init, .run = webrtc_run, .init2 = webrtc_init2, }; static int impl_get_interface(struct spa_handle *handle, const char *type, void **interface) { auto impl = reinterpret_cast(handle); spa_return_val_if_fail(handle != NULL, -EINVAL); spa_return_val_if_fail(interface != NULL, -EINVAL); if (spa_streq(type, SPA_TYPE_INTERFACE_AUDIO_AEC)) *interface = &impl->aec; else return -ENOENT; return 0; } static int impl_clear(struct spa_handle *handle) { spa_return_val_if_fail(handle != NULL, -EINVAL); auto impl = reinterpret_cast(handle); impl->~impl_data(); return 0; } static size_t impl_get_size(const struct spa_handle_factory *factory, const struct spa_dict *params) { return sizeof(struct impl_data); } static int impl_init(const struct spa_handle_factory *factory, struct spa_handle *handle, const struct spa_dict *info, const struct spa_support *support, uint32_t n_support) { spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(handle != NULL, -EINVAL); auto impl = new (handle) impl_data(); impl->handle.get_interface = impl_get_interface; impl->handle.clear = impl_clear; impl->aec.iface = SPA_INTERFACE_INIT( SPA_TYPE_INTERFACE_AUDIO_AEC, SPA_VERSION_AUDIO_AEC, &impl_aec, impl); impl->aec.name = "webrtc", impl->aec.info = NULL; impl->aec.latency = "480/48000", impl->log = static_cast(spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log)); spa_log_topic_init(impl->log, &log_topic); return 0; } static const struct spa_interface_info impl_interfaces[] = { {SPA_TYPE_INTERFACE_AUDIO_AEC,}, }; static int impl_enum_interface_info(const struct spa_handle_factory *factory, const struct spa_interface_info **info, uint32_t *index) { spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(info != NULL, -EINVAL); spa_return_val_if_fail(index != NULL, -EINVAL); switch (*index) { case 0: *info = &impl_interfaces[*index]; break; default: return 0; } (*index)++; return 1; } static const struct spa_handle_factory spa_aec_webrtc_factory = { SPA_VERSION_HANDLE_FACTORY, SPA_NAME_AEC, NULL, impl_get_size, impl_init, impl_enum_interface_info, }; extern "C" { SPA_LOG_TOPIC_ENUM_DEFINE_REGISTERED; } SPA_EXPORT int spa_handle_factory_enum(const struct spa_handle_factory **factory, uint32_t *index) { spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(index != NULL, -EINVAL); switch (*index) { case 0: *factory = &spa_aec_webrtc_factory; break; default: return 0; } (*index)++; return 1; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/aec/meson.build000066400000000000000000000006661511204443500256440ustar00rootroot00000000000000aec_null = shared_library('spa-aec-null', [ 'aec-null.c' ], include_directories : [ configinc ], dependencies : [ spa_dep ], install : true, install_dir : spa_plugindir / 'aec') if webrtc_dep.found() aec_webrtc = shared_library('spa-aec-webrtc', [ 'aec-webrtc.cpp' ], include_directories : [ configinc ], dependencies : [ spa_dep, webrtc_dep ], install : true, install_dir : spa_plugindir / 'aec') endif pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/000077500000000000000000000000001511204443500236625ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/90-pipewire-alsa.rules000066400000000000000000000327141511204443500277350ustar00rootroot00000000000000# do not edit this file, it will be overwritten on update # This file is part of PipeWire adapted from PulseAudio. # # PulseAudio is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as # published by the Free Software Foundation; either version 2.1 of the # License, or (at your option) any later version. # # PulseAudio is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with PulseAudio; if not, see . SUBSYSTEM!="sound", GOTO="pipewire_end" ACTION!="change", GOTO="pipewire_end" KERNEL!="card*", GOTO="pipewire_end" SUBSYSTEMS=="usb", GOTO="pipewire_check_usb" SUBSYSTEMS=="firewire", GOTO="pipewire_firewire_quirk" SUBSYSTEMS=="pci", GOTO="pipewire_check_pci" SUBSYSTEMS=="platform", DRIVERS=="thinkpad_acpi", ENV{ACP_IGNORE}="1" # Force enable speaker and internal mic for some laptops # This should only be necessary for kernels 3.3, 3.4 and 3.5 (as they are lacking the phantom jack kctls). # Acer AOA150 ATTRS{subsystem_vendor}=="0x1025", ATTRS{subsystem_device}=="0x015b", ENV{ACP_PROFILE_SET}="force-speaker-and-int-mic.conf" # Acer Aspire 4810TZ ATTRS{subsystem_vendor}=="0x1025", ATTRS{subsystem_device}=="0x022a", ENV{ACP_PROFILE_SET}="force-speaker-and-int-mic.conf" # Packard bell dot m/a ATTRS{subsystem_vendor}=="0x1025", ATTRS{subsystem_device}=="0x028c", ENV{ACP_PROFILE_SET}="force-speaker-and-int-mic.conf" # Acer Aspire 1810TZ ATTRS{subsystem_vendor}=="0x1025", ATTRS{subsystem_device}=="0x029b", ENV{ACP_PROFILE_SET}="force-speaker-and-int-mic.conf" # Acer AOD260 and AO532h ATTRS{subsystem_vendor}=="0x1025", ATTRS{subsystem_device}=="0x0349", ENV{ACP_PROFILE_SET}="force-speaker-and-int-mic.conf" # Dell MXC051 ATTRS{subsystem_vendor}=="0x1028", ATTRS{subsystem_device}=="0x01b5", ENV{ACP_PROFILE_SET}="force-speaker.conf" # Dell Inspiron 6400 and E1505 ATTRS{subsystem_vendor}=="0x1028", ATTRS{subsystem_device}=="0x01bd", ENV{ACP_PROFILE_SET}="force-speaker.conf" # Dell Latitude D620 ATTRS{subsystem_vendor}=="0x1028", ATTRS{subsystem_device}=="0x01c2", ENV{ACP_PROFILE_SET}="force-speaker-and-int-mic.conf" # Dell Latitude D820 ATTRS{subsystem_vendor}=="0x1028", ATTRS{subsystem_device}=="0x01cc", ENV{ACP_PROFILE_SET}="force-speaker-and-int-mic.conf" # Dell Latitude D520 ATTRS{subsystem_vendor}=="0x1028", ATTRS{subsystem_device}=="0x01d4", ENV{ACP_PROFILE_SET}="force-speaker-and-int-mic.conf" # Dell Latitude D420 ATTRS{subsystem_vendor}=="0x1028", ATTRS{subsystem_device}=="0x01d6", ENV{ACP_PROFILE_SET}="force-speaker-and-int-mic.conf" # Dell Inspiron 1525 ATTRS{subsystem_vendor}=="0x1028", ATTRS{subsystem_device}=="0x022f", ENV{ACP_PROFILE_SET}="force-speaker-and-int-mic.conf" # Dell Inspiron 1011 ATTRS{subsystem_vendor}=="0x1028", ATTRS{subsystem_device}=="0x02f4", ENV{ACP_PROFILE_SET}="force-speaker-and-int-mic.conf" # Dell XPS 14 (L401X) ATTRS{subsystem_vendor}=="0x1028", ATTRS{subsystem_device}=="0x0468", ENV{ACP_PROFILE_SET}="force-speaker-and-int-mic.conf" # Dell XPS 15 (L501X) ATTRS{subsystem_vendor}=="0x1028", ATTRS{subsystem_device}=="0x046e", ENV{ACP_PROFILE_SET}="force-speaker-and-int-mic.conf" # Dell XPS 15 (L502X) ATTRS{subsystem_vendor}=="0x1028", ATTRS{subsystem_device}=="0x050e", ENV{ACP_PROFILE_SET}="force-speaker-and-int-mic.conf" # Dell Inspiron 3420 ATTRS{subsystem_vendor}=="0x1028", ATTRS{subsystem_device}=="0x0553", ENV{ACP_PROFILE_SET}="force-speaker.conf" # Dell Inspiron 3520 ATTRS{subsystem_vendor}=="0x1028", ATTRS{subsystem_device}=="0x0555", ENV{ACP_PROFILE_SET}="force-speaker.conf" # Dell Vostro 2420 ATTRS{subsystem_vendor}=="0x1028", ATTRS{subsystem_device}=="0x0556", ENV{ACP_PROFILE_SET}="force-speaker.conf" # Dell Vostro 2520 ATTRS{subsystem_vendor}=="0x1028", ATTRS{subsystem_device}=="0x0558", ENV{ACP_PROFILE_SET}="force-speaker.conf" # Dell Inspiron One 2020 ATTRS{subsystem_vendor}=="0x1028", ATTRS{subsystem_device}=="0x0579", ENV{ACP_PROFILE_SET}="force-speaker-and-int-mic.conf" # Asus 904HA (1000H) ATTRS{subsystem_vendor}=="0x1043", ATTRS{subsystem_device}=="0x831a", ENV{ACP_PROFILE_SET}="force-speaker-and-int-mic.conf" # Asus T101MT ATTRS{subsystem_vendor}=="0x1043", ATTRS{subsystem_device}=="0x83ce", ENV{ACP_PROFILE_SET}="force-speaker-and-int-mic.conf" # Sony Vaio VGN-SR21M ATTRS{subsystem_vendor}=="0x104d", ATTRS{subsystem_device}=="0x9033", ENV{ACP_PROFILE_SET}="force-speaker-and-int-mic.conf" # Sony Vaio VPC-W115XG ATTRS{subsystem_vendor}=="0x104d", ATTRS{subsystem_device}=="0x9064", ENV{ACP_PROFILE_SET}="force-speaker-and-int-mic.conf" # Fujitsu Lifebook S7110 ATTRS{subsystem_vendor}=="0x10cf", ATTRS{subsystem_device}=="0x1397", ENV{ACP_PROFILE_SET}="force-speaker-and-int-mic.conf" # Fujitsu Lifebook A530 ATTRS{subsystem_vendor}=="0x10cf", ATTRS{subsystem_device}=="0x1531", ENV{ACP_PROFILE_SET}="force-speaker-and-int-mic.conf" # Toshiba A200 ATTRS{subsystem_vendor}=="0x1179", ATTRS{subsystem_device}=="0xff00", ENV{ACP_PROFILE_SET}="force-speaker-and-int-mic.conf" # MSI X360 ATTRS{subsystem_vendor}=="0x1462", ATTRS{subsystem_device}=="0x1053", ENV{ACP_PROFILE_SET}="force-speaker-and-int-mic.conf" # Lenovo 3000 Y410 ATTRS{subsystem_vendor}=="0x17aa", ATTRS{subsystem_device}=="0x384e", ENV{ACP_PROFILE_SET}="force-speaker.conf" GOTO="pipewire_end" LABEL="pipewire_check_usb" ATTRS{idVendor}=="17cc", ATTRS{idProduct}=="1978", ENV{ACP_PROFILE_SET}="native-instruments-audio8dj.conf" ATTRS{idVendor}=="17cc", ATTRS{idProduct}=="0839", ENV{ACP_PROFILE_SET}="native-instruments-audio4dj.conf" ATTRS{idVendor}=="17cc", ATTRS{idProduct}=="baff", ENV{ACP_PROFILE_SET}="native-instruments-traktorkontrol-s4.conf" ATTRS{idVendor}=="17cc", ATTRS{idProduct}=="4711", ENV{ACP_PROFILE_SET}="native-instruments-korecontroller.conf" # This ID 17cc:041c is verified for the older Audio 2 DJ model (pre-2014 ish). ATTRS{idVendor}=="17cc", ATTRS{idProduct}=="041c", ENV{ACP_PROFILE_SET}="native-instruments-traktor-audio2.conf" ATTRS{idVendor}=="17cc", ATTRS{idProduct}=="041d", ENV{ACP_PROFILE_SET}="native-instruments-traktor-audio2.conf" # There appear to be two IDs in use for Traktor Audio 6 (or maybe 17cc:1011 # is just incorrect - 17cc:1010 has been verified to be correct at least # for some hardware). ATTRS{idVendor}=="17cc", ATTRS{idProduct}=="1010", ENV{ACP_PROFILE_SET}="native-instruments-traktor-audio6.conf" ATTRS{idVendor}=="17cc", ATTRS{idProduct}=="1011", ENV{ACP_PROFILE_SET}="native-instruments-traktor-audio6.conf" ATTRS{idVendor}=="17cc", ATTRS{idProduct}=="1001", ENV{ACP_PROFILE_SET}="native-instruments-komplete-audio6.conf" # This entry is for the Komplete Audio 6 MK2, which has a different ID, but is functionally identical to the Komplete Audio 6. ATTRS{idVendor}=="17cc", ATTRS{idProduct}=="1870", ENV{ACP_PROFILE_SET}="native-instruments-komplete-audio6.conf" ATTRS{idVendor}=="17cc", ATTRS{idProduct}=="1021", ENV{ACP_PROFILE_SET}="native-instruments-traktor-audio10.conf" ATTRS{idVendor}=="0763", ATTRS{idProduct}=="2012", ENV{ACP_PROFILE_SET}="maudio-fasttrack-pro.conf" ATTRS{idVendor}=="045e", ATTRS{idProduct}=="02bb", ENV{ACP_PROFILE_SET}="kinect-audio.conf" ATTRS{idVendor}=="041e", ATTRS{idProduct}=="322c", ENV{ACP_PROFILE_SET}="sb-omni-surround-5.1.conf" ATTRS{idVendor}=="0bda", ATTRS{idProduct}=="4014", ENV{ACP_PROFILE_SET}="dell-dock-tb16-usb-audio.conf" ATTRS{idVendor}=="0bda", ATTRS{idProduct}=="402e", ENV{ACP_PROFILE_SET}="dell-dock-tb16-usb-audio.conf" ATTRS{idVendor}=="08bb", ATTRS{idProduct}=="2902", ENV{ACP_PROFILE_SET}="texas-instruments-pcm2902.conf" ATTRS{idVendor}=="03f0", ATTRS{idProduct}=="0269", ENV{ACP_PROFILE_SET}="hp-tbt-dock-120w-g2.conf" ATTRS{idVendor}=="03f0", ATTRS{idProduct}=="0567", ENV{ACP_PROFILE_SET}="hp-tbt-dock-audio-module.conf" ATTRS{idVendor}=="046d", ATTRS{idProduct}=="0a4c", ENV{ACP_PROFILE_SET}="logi407.conf" # ID 1038:12ad is for the 2018 refresh of the Arctis 7. # ID 1038:1294 is for Arctis Pro Wireless (which works with the Arctis 7 configuration). ATTRS{idVendor}=="1038", ATTRS{idProduct}=="1260", ENV{ACP_PROFILE_SET}="usb-gaming-headset.conf" ATTRS{idVendor}=="1038", ATTRS{idProduct}=="12ad", ENV{ACP_PROFILE_SET}="usb-gaming-headset.conf" ATTRS{idVendor}=="1038", ATTRS{idProduct}=="1294", ENV{ACP_PROFILE_SET}="usb-gaming-headset.conf" ATTRS{idVendor}=="1038", ATTRS{idProduct}=="1730", ENV{ACP_PROFILE_SET}="usb-gaming-headset.conf" # ID 1038:1282 is for SteelSeries GameDAC ATTRS{idVendor}=="1038", ATTRS{idProduct}=="1282", ENV{ACP_PROFILE_SET}="usb-gaming-headset.conf" # ID 1038:12c4 is for Arctis 9 ATTRS{idVendor}=="1038", ATTRS{idProduct}=="12c4", ENV{ACP_PROFILE_SET}="usb-gaming-headset.conf" # Lucidsound LS31 ATTRS{idVendor}=="2f12", ATTRS{idProduct}=="0109", ENV{ACP_PROFILE_SET}="usb-gaming-headset.conf" # ID 9886:002c is for the Astro A50 Gen4 ATTRS{idVendor}=="9886", ATTRS{idProduct}=="002c", ENV{ACP_PROFILE_SET}="usb-gaming-headset.conf" # ID 9886:0038 is for the Astro Mixamp Pro TR ATTRS{idVendor}=="9886", ATTRS{idProduct}=="0038", ENV{ACP_PROFILE_SET}="usb-gaming-headset.conf" # ID 9886:0045 is for the Astro A20 Gen2 ATTRS{idVendor}=="9886", ATTRS{idProduct}=="0045", ENV{ACP_PROFILE_SET}="usb-gaming-headset.conf" # ID 1532:0520 is for the Razer Kraken Tournament Edition ATTRS{idVendor}=="1532", ATTRS{idProduct}=="0520", ENV{ACP_PROFILE_SET}="usb-gaming-headset.conf" # ID 1532:0579 is Razer BlackShark v3 (usb direct to headset) ATTRS{idVendor}=="1532", ATTRS{idProduct}=="0579", ENV{ACP_PROFILE_SET}="usb-gaming-headset.conf" # ID 1532:057a is Razer BlackShark v3 (2.4GHz dongle) ATTRS{idVendor}=="1532", ATTRS{idProduct}=="057a", ENV{ACP_PROFILE_SET}="usb-gaming-headset.conf" # ID 1038:1250 is for the Arctis 5 # ID 1037:12aa is for the Arctis 5 2019 # ID 1038:1252 is for the Arctis Pro 2019 edition ATTRS{idVendor}=="1038", ATTRS{idProduct}=="1250", ENV{ACP_PROFILE_SET}="steelseries-arctis-common-usb-audio.conf" ATTRS{idVendor}=="1038", ATTRS{idProduct}=="12aa", ENV{ACP_PROFILE_SET}="steelseries-arctis-common-usb-audio.conf" ATTRS{idVendor}=="1038", ATTRS{idProduct}=="1252", ENV{ACP_PROFILE_SET}="steelseries-arctis-common-usb-audio.conf" ATTRS{idVendor}=="147a", ATTRS{idProduct}=="e055", ENV{ACP_PROFILE_SET}="cmedia-high-speed-true-hdaudio.conf" # HyperX Cloud Orbit S has three modes. Each mode has a separate product ID. # ID_SERIAL for this device is the device name + mode repeated three times. # ID_SERIAL is used for the ID_ID property, and the ID_ID property is used in # the card name in PulseAudio. The resulting card name is too long for the name # length limit, so we set a more sensible ID_ID here (the same as the default # ID_ID, but without repetition in the serial part). ATTRS{idVendor}=="0951", ATTRS{idProduct}=="16ff", ENV{ID_ID}="usb-HyperX_Cloud_Orbit_S_2Ch-$env{ID_USB_INTERFACE_NUM}" ATTRS{idVendor}=="0951", ATTRS{idProduct}=="1702", ENV{ID_ID}="usb-HyperX_Cloud_Orbit_S_Hi-Res_2Ch-$env{ID_USB_INTERFACE_NUM}" ATTRS{idVendor}=="0951", ATTRS{idProduct}=="1703", ENV{ID_ID}="usb-HyperX_Cloud_Orbit_S_3D_8Ch-$env{ID_USB_INTERFACE_NUM}" # OnePlus Type-C Bullets (ED117) ATTRS{idVendor}=="2a70", ATTRS{idProduct}=="1881", ENV{ACP_PROFILE_SET}="simple-headphones-mic.conf" # ID 1395:005e is for Sennheiser GSX 1000 # ID 1395:00a0 is for Sennheiser GSX 1000 # ID 1395:00b1 is for Sennheiser GSX 1000 v2 # ID 1395:005f is for Sennheiser GSX 1200 # ID 1395:00a1 is for Sennheiser GSX 1200 ATTRS{idVendor}=="1395", ATTRS{idProduct}=="005e", ENV{ACP_PROFILE_SET}="sennheiser-gsx.conf" ATTRS{idVendor}=="1395", ATTRS{idProduct}=="00a0", ENV{ACP_PROFILE_SET}="sennheiser-gsx.conf" ATTRS{idVendor}=="1395", ATTRS{idProduct}=="00b1", ENV{ACP_PROFILE_SET}="sennheiser-gsx.conf" ATTRS{idVendor}=="1395", ATTRS{idProduct}=="005f", ENV{ACP_PROFILE_SET}="sennheiser-gsx.conf" ATTRS{idVendor}=="1395", ATTRS{idProduct}=="00a1", ENV{ACP_PROFILE_SET}="sennheiser-gsx.conf" # Sennheiser GSA 70 wireless USB dongle for GSP 670 ATTRS{idVendor}=="1395", ATTRS{idProduct}=="0089", ENV{ACP_PROFILE_SET}="usb-gaming-headset.conf" # EPOS GSA 70 wireless USB dongle for GSP 670 (Sennheiser GSA 70 with updated firmware) ATTRS{idVendor}=="1395", ATTRS{idProduct}=="0300", ENV{ACP_PROFILE_SET}="usb-gaming-headset.conf" # Sennheiser GSP 670 USB headset ATTRS{idVendor}=="1395", ATTRS{idProduct}=="008a", ENV{ACP_PROFILE_SET}="usb-gaming-headset.conf" # Audioengine HD3 powered speakers support IEC958 but don't actually # have any digital outputs. ATTRS{idVendor}=="0a12", ATTRS{idProduct}=="4007", ENV{ACP_PROFILE_SET}="analog-only.conf" # Asus Xonar SE ATTRS{idVendor}=="0b05", ATTRS{idProduct}=="189d", ENV{ACP_PROFILE_SET}="asus-xonar-se.conf" GOTO="pipewire_end" LABEL="pipewire_check_pci" # Creative SoundBlaster Audigy-based cards # EMU10k2/CA0100/CA0102/CA10200 ATTRS{vendor}=="0x1102", ATTRS{device}=="0x0004", ENV{ACP_PROFILE_SET}="audigy.conf" # CA0108/CA10300 ATTRS{vendor}=="0x1102", ATTRS{device}=="0x0008", ENV{ACP_PROFILE_SET}="audigy.conf" GOTO="pipewire_end" LABEL="pipewire_firewire_quirk" # Focusrite Saffire Pro 10 i/o and Pro 26 i/o have a quirk to disappear from # IEEE 1394 bus when breaking connections. This brings an issue for PulseAudio # to continue the routine for ever that: # - detecting sound card # - starting PCM substream # - stopping the PCM substream # - the card disappears # - the card appears # In detail, see: https://bugzilla.kernel.org/show_bug.cgi?id=199365 ATTRS{vendor}=="0x00130e", ATTRS{model}=="0x00000[36]", ATTRS{units}=="0x00a02d:0x010001", ENV{ACP_IGNORE}="1" LABEL="pipewire_end" pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/acp-tool.c000066400000000000000000000500341511204443500255460ustar00rootroot00000000000000/* ALSA card profile test */ /* SPDX-FileCopyrightText: Copyright © 2020 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #include #include #include #include #include #include #include #include #include #define WHITESPACE "\n\r\t " struct data { int verbose; int card_index; char *properties; struct acp_card *card; bool quit; }; static void acp_debug_dict(struct acp_dict *dict, int indent) { const struct acp_dict_item *it; fprintf(stderr, "%*sproperties: (%d)\n", indent, "", dict->n_items); acp_dict_for_each(it, dict) { fprintf(stderr, "%*s%s = \"%s\"\n", indent+4, "", it->key, it->value); } } static char *split_walk(char *str, const char *delimiter, size_t *len, char **state) { char *s = *state ? *state : str; if (*s == '\0') return NULL; *len = strcspn(s, delimiter); *state = s + *len; *state += strspn(*state, delimiter); return s; } static int split_ip(char *str, const char *delimiter, int max_tokens, char *tokens[]) { char *state = NULL, *s; size_t len; int n = 0; while (true) { if ((s = split_walk(str, delimiter, &len, &state)) == NULL) break; tokens[n++] = s; if (n >= max_tokens) break; s[len] = '\0'; } return n; } static void card_props_changed(void *data) { struct data *d = data; struct acp_card *card = d->card; fprintf(stderr, "*** properties changed:\n"); acp_debug_dict(&card->props, 4); fprintf(stderr, "***\n"); } static void card_profile_changed(void *data, uint32_t old_index, uint32_t new_index) { struct data *d = data; struct acp_card *card = d->card; struct acp_card_profile *op = card->profiles[old_index]; struct acp_card_profile *np = card->profiles[new_index]; fprintf(stderr, "*** profile changed from %s to %s\n", op->name, np->name); } static void card_profile_available(void *data, uint32_t index, enum acp_available old, enum acp_available available) { struct data *d = data; struct acp_card *card = d->card; struct acp_card_profile *p = card->profiles[index]; fprintf(stderr, "*** profile %s available %s\n", p->name, acp_available_str(available)); } static void card_port_available(void *data, uint32_t index, enum acp_available old, enum acp_available available) { struct data *d = data; struct acp_card *card = d->card; struct acp_port *p = card->ports[index]; fprintf(stderr, "*** port %s available %s\n", p->name, acp_available_str(available)); } static void on_volume_changed(void *data, struct acp_device *dev) { float vol; acp_device_get_volume(dev, &vol, 1); fprintf(stderr, "*** volume %s changed to %f\n", dev->name, vol); } static void on_mute_changed(void *data, struct acp_device *dev) { bool mute; acp_device_get_mute(dev, &mute); fprintf(stderr, "*** mute %s changed to %d\n", dev->name, mute); } static const struct acp_card_events card_events = { ACP_VERSION_CARD_EVENTS, .props_changed = card_props_changed, .profile_changed = card_profile_changed, .profile_available = card_profile_available, .port_available = card_port_available, .volume_changed = on_volume_changed, .mute_changed = on_mute_changed, }; static ACP_PRINTF_FUNC(6,0) void log_func(void *data, int level, const char *file, int line, const char *func, const char *fmt, va_list arg) { static const char * const levels[] = { "E", "W", "N", "I", "D", "T" }; const char *level_str = levels[SPA_CLAMP(level, 0, (int)SPA_N_ELEMENTS(levels) - 1)]; if (file) { const char *p = strrchr(file, '/'); if (p) file = p + 1; } fprintf(stderr, "%s %16s:%-5d ", level_str, file ? file : "", line); while (level-- > 1) fprintf(stderr, " "); vfprintf(stderr, fmt, arg); fprintf(stderr, "\n"); } static void show_prompt(struct data *data) { fprintf(stderr, ">>>"); } struct command { const char *name; const char *args; const char *alias; const char *description; int (*func) (struct data *data, const struct command *cmd, int argc, char *argv[]); void *extra; }; static int cmd_help(struct data *data, const struct command *cmd, int argc, char *argv[]); static int cmd_quit(struct data *data, const struct command *cmd, int argc, char *argv[]) { data->quit = true; return 0; } static void print_profile(struct data *data, struct acp_card_profile *p, int indent, int level); static void print_device(struct data *data, struct acp_device *d, int indent, int level); static void print_port(struct data *data, struct acp_port *p, int indent, int level) { uint32_t i; fprintf(stderr, "%*s %c port %u: name:\"%s\" direction:%s prio:%d (available: %s)\n", indent, "", p->flags & ACP_PORT_ACTIVE ? '*' : ' ', p->index, p->name, acp_direction_str(p->direction), p->priority, acp_available_str(p->available)); if (level > 0) { acp_debug_dict(&p->props, indent + 8); } if (level > 1) { fprintf(stderr, "%*sprofiles: (%d)\n", indent+8, "", p->n_profiles); for (i = 0; i < p->n_profiles; i++) { struct acp_card_profile *pr = p->profiles[i]; print_profile(data, pr, indent + 8, 0); } fprintf(stderr, "%*sdevices: (%d)\n", indent+8, "", p->n_devices); for (i = 0; i < p->n_devices; i++) { struct acp_device *d = p->devices[i]; print_device(data, d, indent + 8, 0); } } } static void print_device(struct data *data, struct acp_device *d, int indent, int level) { const char **s; uint32_t i; fprintf(stderr, "%*s %c device %u: direction:%s name:\"%s\" prio:%d flags:%08x devices: ", indent, "", d->flags & ACP_DEVICE_ACTIVE ? '*' : ' ', d->index, acp_direction_str(d->direction), d->name, d->priority, d->flags); for (s = d->device_strings; *s; s++) fprintf(stderr, "\"%s\" ", *s); fprintf(stderr, "\n"); if (level > 0) { fprintf(stderr, "%*srate:%d channels:%d\n", indent+8, "", d->format.rate_mask, d->format.channels); acp_debug_dict(&d->props, indent + 8); } if (level > 1) { fprintf(stderr, "%*sports: (%d)\n", indent+8, "", d->n_ports); for (i = 0; i < d->n_ports; i++) { struct acp_port *p = d->ports[i]; print_port(data, p, indent + 8, 0); } } } static void print_profile(struct data *data, struct acp_card_profile *p, int indent, int level) { uint32_t i; fprintf(stderr, "%*s %c profile %u: name:\"%s\" prio:%d (available: %s)\n", indent, "", p->flags & ACP_PROFILE_ACTIVE ? '*' : ' ', p->index, p->name, p->priority, acp_available_str(p->available)); if (level > 0) { fprintf(stderr, "%*sdescription:\"%s\"\n", indent+8, "", p->description); } if (level > 1) { fprintf(stderr, "%*sdevices: (%d)\n", indent+8, "", p->n_devices); for (i = 0; i < p->n_devices; i++) { struct acp_device *d = p->devices[i]; print_device(data, d, indent + 8, 0); } } } static void print_card(struct data *data, struct acp_card *card, int indent, int level) { fprintf(stderr, "%*scard %d: profiles:%d devices:%d ports:%d\n", indent, "", card->index, card->n_profiles, card->n_devices, card->n_ports); if (level > 0) { acp_debug_dict(&card->props, 4); } } static int cmd_info(struct data *data, const struct command *cmd, int argc, char *argv[]) { struct acp_card *card = data->card; print_card(data, card, 0, 2); return 0; } static int cmd_card(struct data *data, const struct command *cmd, int argc, char *argv[]) { if (argc < 2) { fprintf(stderr, "arguments: missing\n"); return -EINVAL; } return 0; } static int cmd_list(struct data *data, const struct command *cmd, int argc, char *argv[]) { struct acp_card *card = data->card; uint32_t i; int level = 0; if (spa_streq(cmd->name, "list-verbose")) level = 2; print_card(data, card, 0, level); for (i = 0; i < card->n_profiles; i++) print_profile(data, card->profiles[i], 0, level); for (i = 0; i < card->n_ports; i++) print_port(data, card->ports[i], 0, level); for (i = 0; i < card->n_devices; i++) print_device(data, card->devices[i], 0, level); return 0; } static int cmd_list_profiles(struct data *data, const struct command *cmd, int argc, char *argv[]) { uint32_t i; struct acp_card *card = data->card; if (argc > 1) { i = atoi(argv[1]); if (i >= card->n_profiles) return -EINVAL; print_profile(data, card->profiles[i], 0, 2); } else { for (i = 0; i < card->n_profiles; i++) print_profile(data, card->profiles[i], 0, 0); } return 0; } static int cmd_set_profile(struct data *data, const struct command *cmd, int argc, char *argv[]) { struct acp_card *card = data->card; uint32_t index; if (argc > 1) index = atoi(argv[1]); else index = card->active_profile_index; return acp_card_set_profile(card, index, 0); } static int cmd_list_ports(struct data *data, const struct command *cmd, int argc, char *argv[]) { uint32_t i; struct acp_card *card = data->card; if (argc > 1) { i = atoi(argv[1]); if (i >= card->n_ports) return -EINVAL; print_port(data, card->ports[i], 0, 2); } else { for (i = 0; i < card->n_ports; i++) print_port(data, card->ports[i], 0, 0); } return 0; } static int cmd_set_port(struct data *data, const struct command *cmd, int argc, char *argv[]) { struct acp_card *card = data->card; uint32_t dev_id, port_id; if (argc < 3) { fprintf(stderr, "arguments: missing\n"); return -EINVAL; } dev_id = atoi(argv[1]); port_id = atoi(argv[2]); if (dev_id >= card->n_devices) return -EINVAL; return acp_device_set_port(card->devices[dev_id], port_id, 0); } static int cmd_list_devices(struct data *data, const struct command *cmd, int argc, char *argv[]) { uint32_t i; struct acp_card *card = data->card; if (argc > 1) { i = atoi(argv[1]); if (i >= card->n_devices) return -EINVAL; print_device(data, card->devices[i], 0, 2); } else { for (i = 0; i < card->n_devices; i++) print_device(data, card->devices[i], 0, 0); } return 0; } static int cmd_get_volume(struct data *data, const struct command *cmd, int argc, char *argv[]) { struct acp_card *card = data->card; uint32_t dev_id; float vol; if (argc < 2) { fprintf(stderr, "arguments: missing\n"); return -EINVAL; } dev_id = atoi(argv[1]); if (dev_id >= card->n_devices) return -EINVAL; acp_device_get_volume(card->devices[dev_id], &vol, 1); fprintf(stderr, "volume: %f\n", vol); return 0; } static int cmd_set_volume(struct data *data, const struct command *cmd, int argc, char *argv[]) { struct acp_card *card = data->card; uint32_t dev_id; float vol; if (argc < 3) { fprintf(stderr, "arguments: missing\n"); return -EINVAL; } dev_id = atoi(argv[1]); vol = (float)atof(argv[2]); if (dev_id >= card->n_devices) return -EINVAL; return acp_device_set_volume(card->devices[dev_id], &vol, 1); } static int adjust_volume(struct data *data, const struct command *cmd, int argc, char *argv[], float adjust) { struct acp_card *card = data->card; uint32_t dev_id; float vol; if (argc < 2) { fprintf(stderr, "arguments: missing\n"); return -EINVAL; } dev_id = atoi(argv[1]); if (dev_id >= card->n_devices) return -EINVAL; acp_device_get_volume(card->devices[dev_id], &vol, 1); vol += adjust; acp_device_set_volume(card->devices[dev_id], &vol, 1); fprintf(stderr, "volume: %f\n", vol); return 0; } static int cmd_inc_volume(struct data *data, const struct command *cmd, int argc, char *argv[]) { return adjust_volume(data, cmd, argc, argv, 0.2f); } static int cmd_dec_volume(struct data *data, const struct command *cmd, int argc, char *argv[]) { return adjust_volume(data, cmd, argc, argv, -0.2f); } static int cmd_get_mute(struct data *data, const struct command *cmd, int argc, char *argv[]) { struct acp_card *card = data->card; uint32_t dev_id; bool mute; if (argc < 2) { fprintf(stderr, "arguments: missing\n"); return -EINVAL; } dev_id = atoi(argv[1]); if (dev_id >= card->n_devices) return -EINVAL; acp_device_get_mute(card->devices[dev_id], &mute); fprintf(stderr, "muted: %s\n", mute ? "yes" : "no"); return 0; } static int cmd_set_mute(struct data *data, const struct command *cmd, int argc, char *argv[]) { struct acp_card *card = data->card; uint32_t dev_id; bool mute; if (argc < 3) { fprintf(stderr, "arguments: missing\n"); return -EINVAL; } dev_id = atoi(argv[1]); mute = atoi(argv[2]); if (dev_id >= card->n_devices) return -EINVAL; acp_device_set_mute(card->devices[dev_id], mute); fprintf(stderr, "muted: %s\n", mute ? "yes" : "no"); return 0; } static int cmd_toggle_mute(struct data *data, const struct command *cmd, int argc, char *argv[]) { struct acp_card *card = data->card; uint32_t dev_id; bool mute; if (argc < 2) { fprintf(stderr, "arguments: missing\n"); return -EINVAL; } dev_id = atoi(argv[1]); if (dev_id >= card->n_devices) return -EINVAL; acp_device_get_mute(card->devices[dev_id], &mute); mute = !mute; acp_device_set_mute(card->devices[dev_id], mute); fprintf(stderr, "muted: %s\n", mute ? "yes" : "no"); return 0; } static const struct command command_list[] = { { "help", "", "h", "Show available commands", cmd_help }, { "quit", "", "q", "Quit", cmd_quit }, { "card", "", "c", "Probe card", cmd_card }, { "info", "", "i", "List card info", cmd_info }, { "list", "", "l", "List all objects", cmd_list }, { "list-verbose", "", "lv", "List all data", cmd_list }, { "list-profiles", "[id]", "lpr", "List profiles", cmd_list_profiles }, { "set-profile", "", "spr", "Activate a profile", cmd_set_profile }, { "list-ports", "[id]", "lp", "List ports", cmd_list_ports }, { "set-port", "", "sp", "Activate a port", cmd_set_port }, { "list-devices", "[id]", "ld", "List available devices", cmd_list_devices }, { "get-volume", "", "gv", "Get volume from device", cmd_get_volume }, { "set-volume", " ", "v", "Set volume on device", cmd_set_volume }, { "inc-volume", "", "v+", "Increase volume on device", cmd_inc_volume }, { "dec-volume", "", "v-", "Decrease volume on device", cmd_dec_volume }, { "get-mute", "", "gm", "Get mute state from device", cmd_get_mute }, { "set-mute", " ", "sm", "Set mute on device", cmd_set_mute }, { "toggle-mute", "", "m", "Toggle mute on device", cmd_toggle_mute }, }; #define N_COMMANDS sizeof(command_list)/sizeof(command_list[0]) static const struct command *find_command(struct data *data, const char *cmd) { size_t i; for (i = 0; i < N_COMMANDS; i++) { if (spa_streq(command_list[i].name, cmd) || spa_streq(command_list[i].alias, cmd)) return &command_list[i]; } return NULL; } static int cmd_help(struct data *data, const struct command *cmd, int argc, char *argv[]) { size_t i; fprintf(stderr, "Available commands:\n"); for (i = 0; i < N_COMMANDS; i++) { fprintf(stdout, "\t%-15.15s %-10.10s\t%s (%s)\n", command_list[i].name, command_list[i].args, command_list[i].description, command_list[i].alias); } return 0; } static int run_command(struct data *data, int argc, char *argv[64]) { const struct command *command; int res; command = find_command(data, argv[0]); if (command == NULL) { fprintf(stderr, "unknown command %s\n", argv[0]); cmd_help(data, NULL, argc, argv); res = -EINVAL; } else if (command->func) { res = command->func(data, command, argc, argv); if (res < 0) { fprintf(stderr, "error: %s\n", strerror(-res)); } } else { res = -ENOTSUP; } return res; } static int handle_input(struct data *data) { char buf[4096] = { 0, }, *p, *argv[64]; ssize_t r; int res, argc; if ((r = read(STDIN_FILENO, buf, sizeof(buf)-1)) < 0) return -errno; buf[r] = 0; if (r == 0) return -EPIPE; if ((p = strchr(buf, '#'))) *p = '\0'; argc = split_ip(buf, WHITESPACE, 64, argv); if (argc < 1) return -EINVAL; res = run_command(data, argc, argv); if (!data->quit) show_prompt(data); return res; } static int do_probe(struct data *data) { uint32_t n_items = 0; struct acp_dict_item items[64]; struct acp_dict props; struct spa_json it; acp_set_log_func(log_func, data); acp_set_log_level(data->verbose); items[n_items++] = ACP_DICT_ITEM_INIT("use-ucm", "true"); items[n_items++] = ACP_DICT_ITEM_INIT("verbose", data->verbose ? "true" : "false"); if (spa_json_begin_object_relax(&it, data->properties, strlen(data->properties)) > 0) { char key[1024]; const char *value; int len; struct spa_error_location loc; while ((len = spa_json_object_next(&it, key, sizeof(key), &value)) > 0) { char *k = alloca(strlen(key) + 1); char *v = alloca(len + 1); memcpy(k, key, strlen(key) + 1); spa_json_parse_stringn(value, len, v, len + 1); items[n_items++] = ACP_DICT_ITEM_INIT(k, v); if (n_items >= SPA_N_ELEMENTS(items)) break; } if (spa_json_get_error(&it, data->properties, &loc)) { struct spa_debug_context *c = NULL; spa_debugc(c, "invalid --properties: %s", loc.reason); spa_debugc_error_location(c, &loc); return -EINVAL; } } props = ACP_DICT_INIT(items, n_items); data->card = acp_card_new(data->card_index, &props); if (data->card == NULL) return -errno; return 0; } static int do_prompt(struct data *data) { struct pollfd *pfds; int err, count; acp_card_add_listener(data->card, &card_events, data); count = acp_card_poll_descriptors_count(data->card); if (count == 0) fprintf(stderr, "card has no events\n"); count++; pfds = alloca(sizeof(struct pollfd) * count); pfds[0].fd = STDIN_FILENO; pfds[0].events = POLLIN; print_card(data, data->card, 0, 0); fprintf(stderr, "type 'help' for usage.\n"); show_prompt(data); while (!data->quit) { unsigned short revents; err = acp_card_poll_descriptors(data->card, &pfds[1], count-1); if (err < 0) return err; err = poll(pfds, (unsigned int) count, -1); if (err < 0) return -errno; if (pfds[0].revents & POLLIN) { if ((err = handle_input(data)) < 0) { if (err == -EPIPE) break; } } if (count < 2) continue; err = acp_card_poll_descriptors_revents(data->card, &pfds[1], count-1, &revents); if (err < 0) return err; if (revents) acp_card_handle_events(data->card); } return 0; } #define OPTIONS "hvc:p:" static const struct option long_options[] = { { "help", no_argument, NULL, 'h'}, { "verbose", no_argument, NULL, 'v'}, { "card", required_argument, NULL, 'c' }, { "properties", required_argument, NULL, 'p' }, { NULL, 0, NULL, 0 } }; static void show_usage(struct data *data, const char *name, bool is_error) { FILE *fp; fp = is_error ? stderr : stdout; fprintf(fp, "%s [options] [COMMAND]\n", name); fprintf(fp, " -h, --help Show this help\n" " -v --verbose Be verbose\n" " -c --card Card number\n" " -p --properties Extra properties:\n" " 'key=value ... '\n" "\n"); cmd_help(data, NULL, 0, NULL); } int main(int argc, char *argv[]) { int c, res; int longopt_index = 0, ret; struct data data = { .properties = strdup("") }; data.verbose = 1; while ((c = getopt_long(argc, argv, OPTIONS, long_options, &longopt_index)) != -1) { switch (c) { case 'h': show_usage(&data, argv[0], false); return EXIT_SUCCESS; case 'v': data.verbose++; break; case 'c': ret = atoi(optarg); if (ret < 0) { fprintf(stderr, "error: bad card %s\n", optarg); goto error_usage; } data.card_index = ret; break; case 'p': free(data.properties); data.properties = strdup(optarg); break; default: fprintf(stderr, "error: unknown option '%c'\n", c); goto error_usage; } } if ((res = do_probe(&data)) < 0) { fprintf(stderr, "failed to probe card: %s\n", strerror(-res)); return res; } if (optind < argc) run_command(&data, argc - optind, &argv[optind]); else do_prompt(&data); if (data.card) acp_card_destroy(data.card); free(data.properties); return 0; error_usage: show_usage(&data, argv[0], true); return EXIT_FAILURE; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/acp/000077500000000000000000000000001511204443500244255ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/acp/acp.c000066400000000000000000002150741511204443500253450ustar00rootroot00000000000000/* ALSA Card Profile */ /* SPDX-FileCopyrightText: Copyright © 2020 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include "acp.h" #include "alsa-mixer.h" #include "alsa-ucm.h" #include #include #include #include #include int _acp_log_level = 1; acp_log_func _acp_log_func; void *_acp_log_data; struct spa_i18n *acp_i18n; #define DEFAULT_CHANNELS 255u #define DEFAULT_RATE 48000u #define VOLUME_ACCURACY (PA_VOLUME_NORM/100) /* don't require volume adjustments to be perfectly correct. don't necessarily extend granularity in software unless the differences get greater than this level */ static const uint32_t channel_table[PA_CHANNEL_POSITION_MAX] = { [PA_CHANNEL_POSITION_MONO] = ACP_CHANNEL_MONO, [PA_CHANNEL_POSITION_FRONT_LEFT] = ACP_CHANNEL_FL, [PA_CHANNEL_POSITION_FRONT_RIGHT] = ACP_CHANNEL_FR, [PA_CHANNEL_POSITION_FRONT_CENTER] = ACP_CHANNEL_FC, [PA_CHANNEL_POSITION_REAR_CENTER] = ACP_CHANNEL_RC, [PA_CHANNEL_POSITION_REAR_LEFT] = ACP_CHANNEL_RL, [PA_CHANNEL_POSITION_REAR_RIGHT] = ACP_CHANNEL_RR, [PA_CHANNEL_POSITION_LFE] = ACP_CHANNEL_LFE, [PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER] = ACP_CHANNEL_FLC, [PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER] = ACP_CHANNEL_FRC, [PA_CHANNEL_POSITION_SIDE_LEFT] = ACP_CHANNEL_SL, [PA_CHANNEL_POSITION_SIDE_RIGHT] = ACP_CHANNEL_SR, [PA_CHANNEL_POSITION_AUX0] = ACP_CHANNEL_START_Aux + 0, [PA_CHANNEL_POSITION_AUX1] = ACP_CHANNEL_START_Aux + 1, [PA_CHANNEL_POSITION_AUX2] = ACP_CHANNEL_START_Aux + 2, [PA_CHANNEL_POSITION_AUX3] = ACP_CHANNEL_START_Aux + 3, [PA_CHANNEL_POSITION_AUX4] = ACP_CHANNEL_START_Aux + 4, [PA_CHANNEL_POSITION_AUX5] = ACP_CHANNEL_START_Aux + 5, [PA_CHANNEL_POSITION_AUX6] = ACP_CHANNEL_START_Aux + 6, [PA_CHANNEL_POSITION_AUX7] = ACP_CHANNEL_START_Aux + 7, [PA_CHANNEL_POSITION_AUX8] = ACP_CHANNEL_START_Aux + 8, [PA_CHANNEL_POSITION_AUX9] = ACP_CHANNEL_START_Aux + 9, [PA_CHANNEL_POSITION_AUX10] = ACP_CHANNEL_START_Aux + 10, [PA_CHANNEL_POSITION_AUX11] = ACP_CHANNEL_START_Aux + 11, [PA_CHANNEL_POSITION_AUX12] = ACP_CHANNEL_START_Aux + 12, [PA_CHANNEL_POSITION_AUX13] = ACP_CHANNEL_START_Aux + 13, [PA_CHANNEL_POSITION_AUX14] = ACP_CHANNEL_START_Aux + 14, [PA_CHANNEL_POSITION_AUX15] = ACP_CHANNEL_START_Aux + 15, [PA_CHANNEL_POSITION_AUX16] = ACP_CHANNEL_START_Aux + 16, [PA_CHANNEL_POSITION_AUX17] = ACP_CHANNEL_START_Aux + 17, [PA_CHANNEL_POSITION_AUX18] = ACP_CHANNEL_START_Aux + 18, [PA_CHANNEL_POSITION_AUX19] = ACP_CHANNEL_START_Aux + 19, [PA_CHANNEL_POSITION_AUX20] = ACP_CHANNEL_START_Aux + 20, [PA_CHANNEL_POSITION_AUX21] = ACP_CHANNEL_START_Aux + 21, [PA_CHANNEL_POSITION_AUX22] = ACP_CHANNEL_START_Aux + 22, [PA_CHANNEL_POSITION_AUX23] = ACP_CHANNEL_START_Aux + 23, [PA_CHANNEL_POSITION_AUX24] = ACP_CHANNEL_START_Aux + 24, [PA_CHANNEL_POSITION_AUX25] = ACP_CHANNEL_START_Aux + 25, [PA_CHANNEL_POSITION_AUX26] = ACP_CHANNEL_START_Aux + 26, [PA_CHANNEL_POSITION_AUX27] = ACP_CHANNEL_START_Aux + 27, [PA_CHANNEL_POSITION_AUX28] = ACP_CHANNEL_START_Aux + 28, [PA_CHANNEL_POSITION_AUX29] = ACP_CHANNEL_START_Aux + 29, [PA_CHANNEL_POSITION_AUX30] = ACP_CHANNEL_START_Aux + 30, [PA_CHANNEL_POSITION_AUX31] = ACP_CHANNEL_START_Aux + 31, [PA_CHANNEL_POSITION_TOP_CENTER] = ACP_CHANNEL_TC, [PA_CHANNEL_POSITION_TOP_FRONT_LEFT] = ACP_CHANNEL_TFL, [PA_CHANNEL_POSITION_TOP_FRONT_RIGHT] = ACP_CHANNEL_TFR, [PA_CHANNEL_POSITION_TOP_FRONT_CENTER] = ACP_CHANNEL_TFC, [PA_CHANNEL_POSITION_TOP_REAR_LEFT] = ACP_CHANNEL_TRL, [PA_CHANNEL_POSITION_TOP_REAR_RIGHT] = ACP_CHANNEL_TRR, [PA_CHANNEL_POSITION_TOP_REAR_CENTER] = ACP_CHANNEL_TRC, }; static const char *channel_names[] = { [ACP_CHANNEL_UNKNOWN] = "UNK", [ACP_CHANNEL_NA] = "NA", [ACP_CHANNEL_MONO] = "MONO", [ACP_CHANNEL_FL] = "FL", [ACP_CHANNEL_FR] = "FR", [ACP_CHANNEL_FC] = "FC", [ACP_CHANNEL_LFE] = "LFE", [ACP_CHANNEL_SL] = "SL", [ACP_CHANNEL_SR] = "SR", [ACP_CHANNEL_FLC] = "FLC", [ACP_CHANNEL_FRC] = "FRC", [ACP_CHANNEL_RC] = "RC", [ACP_CHANNEL_RL] = "RL", [ACP_CHANNEL_RR] = "RR", [ACP_CHANNEL_TC] = "TC", [ACP_CHANNEL_TFL] = "TFL", [ACP_CHANNEL_TFC] = "TFC", [ACP_CHANNEL_TFR] = "TFR", [ACP_CHANNEL_TRL] = "TRL", [ACP_CHANNEL_TRC] = "TRC", [ACP_CHANNEL_TRR] = "TRR", [ACP_CHANNEL_RLC] = "RLC", [ACP_CHANNEL_RRC] = "RRC", [ACP_CHANNEL_FLW] = "FLW", [ACP_CHANNEL_FRW] = "FRW", [ACP_CHANNEL_LFE2] = "LFE2", [ACP_CHANNEL_FLH] = "FLH", [ACP_CHANNEL_FCH] = "FCH", [ACP_CHANNEL_FRH] = "FRH", [ACP_CHANNEL_TFLC] = "TFLC", [ACP_CHANNEL_TFRC] = "TFRC", [ACP_CHANNEL_TSL] = "TSL", [ACP_CHANNEL_TSR] = "TSR", [ACP_CHANNEL_LLFE] = "LLFE", [ACP_CHANNEL_RLFE] = "RLFE", [ACP_CHANNEL_BC] = "BC", [ACP_CHANNEL_BLC] = "BLC", [ACP_CHANNEL_BRC] = "BRC", }; #define ACP_N_ELEMENTS(arr) (sizeof(arr) / sizeof((arr)[0])) static inline uint32_t channel_pa2acp(pa_channel_position_t channel) { if (channel < 0 || (size_t)channel >= ACP_N_ELEMENTS(channel_table)) return ACP_CHANNEL_UNKNOWN; return channel_table[channel]; } char *acp_channel_str(char *buf, size_t len, enum acp_channel ch) { if (ch >= ACP_CHANNEL_START_Aux && ch <= ACP_CHANNEL_LAST_Aux) { snprintf(buf, len, "AUX%d", ch - ACP_CHANNEL_START_Aux); } else if (ch >= ACP_CHANNEL_UNKNOWN && ch <= ACP_CHANNEL_BRC) { snprintf(buf, len, "%s", channel_names[ch]); } else { snprintf(buf, len, "UNK"); } return buf; } static enum acp_channel acp_channel_from_str(const char *buf, size_t len) { for (unsigned long i = 0; i < ACP_N_ELEMENTS(channel_names); i++) { if (strncmp(channel_names[i], buf, len) == 0) return i; } return ACP_CHANNEL_UNKNOWN; } const char *acp_available_str(enum acp_available status) { switch (status) { case ACP_AVAILABLE_UNKNOWN: return "unknown"; case ACP_AVAILABLE_NO: return "no"; case ACP_AVAILABLE_YES: return "yes"; } return "error"; } const char *acp_direction_str(enum acp_direction direction) { switch (direction) { case ACP_DIRECTION_CAPTURE: return "capture"; case ACP_DIRECTION_PLAYBACK: return "playback"; } return "error"; } static void port_free(void *data) { pa_device_port *dp = data; pa_dynarray_clear(&dp->devices); pa_dynarray_clear(&dp->prof); pa_device_port_free(dp); } static void device_free(void *data) { pa_alsa_device *dev = data; pa_dynarray_clear(&dev->port_array); pa_proplist_free(dev->proplist); pa_hashmap_free(dev->ports); free(dev->device.format.map); } static inline void channelmap_to_acp(pa_channel_map *m, uint32_t *map) { uint32_t i, j; for (i = 0; i < m->channels; i++) { map[i] = channel_pa2acp(m->map[i]); for (j = 0; j < i; j++) { if (map[i] == map[j]) map[i] += 32; } } } static void init_device(pa_card *impl, pa_alsa_device *dev, pa_alsa_direction_t direction, pa_alsa_mapping *m, uint32_t index) { char **d; dev->card = impl; dev->mapping = m; dev->device.index = index; dev->device.name = m->name; dev->device.description = m->description; dev->device.priority = m->priority; dev->device.device_strings = (const char **)m->device_strings; dev->device.format.format_mask = m->sample_spec.format; dev->device.format.rate_mask = m->sample_spec.rate; dev->device.format.channels = m->channel_map.channels; dev->device.format.map = calloc(m->channel_map.channels, sizeof(uint32_t)); channelmap_to_acp(&m->channel_map, dev->device.format.map); pa_cvolume_reset(&dev->real_volume, dev->device.format.channels); pa_cvolume_reset(&dev->soft_volume, dev->device.format.channels); dev->direction = direction; dev->proplist = pa_proplist_new(); pa_proplist_update(dev->proplist, PA_UPDATE_REPLACE, m->proplist); if (direction == PA_ALSA_DIRECTION_OUTPUT) { dev->mixer_path_set = m->output_path_set; dev->pcm_handle = m->output_pcm; dev->device.direction = ACP_DIRECTION_PLAYBACK; pa_proplist_update(dev->proplist, PA_UPDATE_REPLACE, m->output_proplist); } else { dev->mixer_path_set = m->input_path_set; dev->pcm_handle = m->input_pcm; dev->device.direction = ACP_DIRECTION_CAPTURE; pa_proplist_update(dev->proplist, PA_UPDATE_REPLACE, m->input_proplist); } if (m->split) { char pos[PA_CHANNELS_MAX*8]; struct spa_strbuf b; int i; spa_strbuf_init(&b, pos, sizeof(pos)); spa_strbuf_append(&b, "["); for (i = 0; i < m->split->channels; ++i) spa_strbuf_append(&b, "%sAUX%d", ((i == 0) ? "" : ","), m->split->idx[i]); spa_strbuf_append(&b, "]"); pa_proplist_sets(dev->proplist, "api.alsa.split.position", pos); spa_strbuf_init(&b, pos, sizeof(pos)); spa_strbuf_append(&b, "["); for (i = 0; i < m->split->hw_channels; ++i) spa_strbuf_append(&b, "%sAUX%d", ((i == 0) ? "" : ","), i); spa_strbuf_append(&b, "]"); pa_proplist_sets(dev->proplist, "api.alsa.split.hw-position", pos); } pa_proplist_sets(dev->proplist, PA_PROP_DEVICE_PROFILE_NAME, m->name); pa_proplist_sets(dev->proplist, PA_PROP_DEVICE_PROFILE_DESCRIPTION, m->description); pa_proplist_setf(dev->proplist, "card.profile.device", "%u", index); pa_proplist_as_dict(dev->proplist, &dev->device.props); dev->ports = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); if (m->ucm_context.ucm) { dev->ucm_context = &m->ucm_context; if (impl->ucm.alib_prefix != NULL) { for (d = m->device_strings; *d; d++) { if (pa_startswith(*d, impl->ucm.alib_prefix)) { size_t plen = strlen(impl->ucm.alib_prefix); size_t len = strlen(*d); memmove(*d, (*d) + plen, len - plen + 1); dev->device.flags |= ACP_DEVICE_UCM_DEVICE; } } } } for (d = m->device_strings; *d; d++) { if (pa_startswith(*d, "iec958") || pa_startswith(*d, "hdmi")) dev->device.flags |= ACP_DEVICE_IEC958; } pa_dynarray_init(&dev->port_array, NULL); } static int compare_profile(const void *a, const void *b) { const pa_hashmap_item *i1 = a; const pa_hashmap_item *i2 = b; const pa_alsa_profile *p1, *p2; if (i1->key == NULL || i2->key == NULL) return 0; p1 = i1->value; p2 = i2->value; if (p1->profile.priority == 0 || p2->profile.priority == 0) return 0; return p2->profile.priority - p1->profile.priority; } static void profile_free(void *data) { pa_alsa_profile *ap = data; pa_dynarray_clear(&ap->out.devices); if (ap->profile.flags & ACP_PROFILE_OFF) { free(ap->name); free(ap->description); free(ap); } } static const char *find_best_verb(pa_card *impl) { const char *res = NULL; unsigned prio = 0; pa_alsa_ucm_verb *verb; PA_LLIST_FOREACH(verb, impl->ucm.verbs) { if (verb->priority >= prio) res = pa_proplist_gets(verb->proplist, PA_ALSA_PROP_UCM_NAME); } return res; } static int add_pro_profile(pa_card *impl, uint32_t index) { snd_ctl_t *ctl_hndl; int err, dev, count = 0, n_capture = 0, n_playback = 0; pa_alsa_profile *ap; pa_alsa_profile_set *ps = impl->profile_set; pa_alsa_mapping *m; char *device; snd_pcm_info_t *pcminfo; pa_sample_spec ss; snd_pcm_uframes_t try_period_size, try_buffer_size; uint32_t idx; if (impl->use_ucm) { const char *verb = find_best_verb(impl); if (verb == NULL) return -ENOTSUP; if ((err = snd_use_case_set(impl->ucm.ucm_mgr, "_verb", verb)) < 0) { pa_log_error("error setting verb: %s", snd_strerror(err)); return err; } } ss.format = PA_SAMPLE_S32LE; ss.rate = impl->rate; ss.channels = impl->pro_channels; ap = pa_xnew0(pa_alsa_profile, 1); ap->profile_set = ps; ap->profile.name = ap->name = pa_xstrdup("pro-audio"); ap->profile.description = ap->description = pa_xstrdup(_("Pro Audio")); ap->profile.available = ACP_AVAILABLE_YES; ap->profile.flags = ACP_PROFILE_PRO; ap->output_mappings = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); ap->input_mappings = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); pa_hashmap_put(ps->profiles, ap->name, ap); ap->output_name = pa_xstrdup("pro-output"); ap->input_name = pa_xstrdup("pro-input"); ap->priority = 1; pa_assert_se(asprintf(&device, "hw:%d", index) >= 0); if ((err = snd_ctl_open(&ctl_hndl, device, 0)) < 0) { pa_log_error("can't open control for card %s: %s", device, snd_strerror(err)); free(device); return err; } free(device); snd_pcm_info_alloca(&pcminfo); dev = -1; while (1) { char desc[128], devstr[128]; if ((err = snd_ctl_pcm_next_device(ctl_hndl, &dev)) < 0) { pa_log_error("error iterating devices: %s", snd_strerror(err)); break; } if (dev < 0) break; snd_pcm_info_set_device(pcminfo, dev); snd_pcm_info_set_subdevice(pcminfo, 0); snprintf(devstr, sizeof(devstr), "hw:%d,%d", index, dev); if (count++ == 0) snprintf(desc, sizeof(desc), "Pro"); else snprintf(desc, sizeof(desc), "Pro %d", dev); snd_pcm_info_set_stream(pcminfo, SND_PCM_STREAM_PLAYBACK); if ((err = snd_ctl_pcm_info(ctl_hndl, pcminfo)) < 0) { if (err != -ENOENT) pa_log_error("error pcm info: %s", snd_strerror(err)); } if (err >= 0) { spa_autofree char *name = NULL; pa_assert_se(asprintf(&name, "Mapping pro-output-%d", dev) >= 0); m = pa_alsa_mapping_get(ps, name); } else { m = NULL; } if (m) { m->description = pa_xstrdup(desc); m->device_strings = pa_split_spaces_strv(devstr); try_period_size = 1024; try_buffer_size = 1024 * 64; m->sample_spec = ss; if ((m->output_pcm = pa_alsa_open_by_template(m->device_strings, devstr, NULL, &m->sample_spec, &m->channel_map, SND_PCM_STREAM_PLAYBACK, &try_period_size, &try_buffer_size, 0, NULL, NULL, NULL, NULL, false))) { pa_alsa_init_proplist_pcm(NULL, m->output_proplist, m->output_pcm); pa_proplist_setf(m->output_proplist, "clock.name", "api.alsa.%u", index); pa_proplist_setf(m->output_proplist, "device.profile.pro", "true"); pa_alsa_close(&m->output_pcm); m->supported = true; pa_channel_map_init_auto(&m->channel_map, m->sample_spec.channels, PA_CHANNEL_MAP_AUX); n_playback++; } pa_idxset_put(ap->output_mappings, m, NULL); } snd_pcm_info_set_stream(pcminfo, SND_PCM_STREAM_CAPTURE); if ((err = snd_ctl_pcm_info(ctl_hndl, pcminfo)) < 0) { if (err != -ENOENT) pa_log_error("error pcm info: %s", snd_strerror(err)); } if (err >= 0) { spa_autofree char *name = NULL; pa_assert_se(asprintf(&name, "Mapping pro-input-%d", dev) >= 0); m = pa_alsa_mapping_get(ps, name); } else { m = NULL; } if (m) { m->description = pa_xstrdup(desc); m->device_strings = pa_split_spaces_strv(devstr); try_period_size = 1024; try_buffer_size = 1024 * 64; m->sample_spec = ss; if ((m->input_pcm = pa_alsa_open_by_template(m->device_strings, devstr, NULL, &m->sample_spec, &m->channel_map, SND_PCM_STREAM_CAPTURE, &try_period_size, &try_buffer_size, 0, NULL, NULL, NULL, NULL, false))) { pa_alsa_init_proplist_pcm(NULL, m->input_proplist, m->input_pcm); pa_proplist_setf(m->input_proplist, "clock.name", "api.alsa.%u", index); pa_proplist_setf(m->input_proplist, "device.profile.pro", "true"); pa_alsa_close(&m->input_pcm); m->supported = true; pa_channel_map_init_auto(&m->channel_map, m->sample_spec.channels, PA_CHANNEL_MAP_AUX); n_capture++; } pa_idxset_put(ap->input_mappings, m, NULL); } } snd_ctl_close(ctl_hndl); /* FireWire ALSA driver latency is determined by the buffer size and not the * period. Timer-based scheduling is then not really useful on these devices as * the latency is fixed. Enable IRQ scheduling unconditionally for these devices, * so that controlling the latency works properly. */ bool is_firewire = spa_streq(pa_proplist_gets(impl->proplist, "device.bus"), "firewire"); if ((n_capture == 1 && n_playback == 1) || is_firewire) { PA_IDXSET_FOREACH(m, ap->output_mappings, idx) { pa_proplist_setf(m->output_proplist, "node.group", "pro-audio-%u", index); pa_proplist_setf(m->output_proplist, "node.link-group", "pro-audio-%u", index); pa_proplist_setf(m->output_proplist, "api.alsa.auto-link", "true"); pa_proplist_setf(m->output_proplist, "api.alsa.disable-tsched", "true"); } PA_IDXSET_FOREACH(m, ap->input_mappings, idx) { pa_proplist_setf(m->input_proplist, "node.group", "pro-audio-%u", index); pa_proplist_setf(m->input_proplist, "node.link-group", "pro-audio-%u", index); pa_proplist_setf(m->input_proplist, "api.alsa.auto-link", "true"); pa_proplist_setf(m->input_proplist, "api.alsa.disable-tsched", "true"); } } return 0; } static bool contains_string(const char *arr, const char *str) { struct spa_json it[1]; char v[256]; if (arr == NULL || str == NULL) return false; if (spa_json_begin_array_relax(&it[0], arr, strlen(arr)) <= 0) return false; while (spa_json_get_string(&it[0], v, sizeof(v)) > 0) { if (spa_streq(v, str)) return true; } return false; } static void add_profiles(pa_card *impl) { pa_alsa_profile *ap; void *state; struct acp_card_profile *cp; pa_device_port *dp; pa_alsa_device *dev; int n_profiles, n_ports, n_devices; uint32_t idx; const char *arr; bool broken_ucm = false; n_devices = 0; pa_dynarray_init(&impl->out.devices, device_free); ap = pa_xnew0(pa_alsa_profile, 1); ap->profile.name = ap->name = pa_xstrdup("off"); ap->profile.description = ap->description = pa_xstrdup(_("Off")); ap->profile.available = ACP_AVAILABLE_YES; ap->profile.flags = ACP_PROFILE_OFF; pa_hashmap_put(impl->profiles, ap->name, ap); if (!impl->disable_pro_audio) add_pro_profile(impl, impl->card.index); PA_HASHMAP_FOREACH(ap, impl->profile_set->profiles, state) { pa_alsa_mapping *m; cp = &ap->profile; cp->name = ap->name; cp->description = ap->description; cp->priority = ap->priority ? ap->priority : 1; pa_dynarray_init(&ap->out.devices, NULL); if (ap->output_mappings) { PA_IDXSET_FOREACH(m, ap->output_mappings, idx) { dev = &m->output; if (dev->mapping == NULL) { init_device(impl, dev, PA_ALSA_DIRECTION_OUTPUT, m, n_devices++); pa_dynarray_append(&impl->out.devices, dev); } if (impl->use_ucm) { if (m->ucm_context.ucm_device) { pa_alsa_ucm_add_port(NULL, &m->ucm_context, true, impl->ports, ap, NULL); pa_alsa_ucm_add_ports(&dev->ports, m->proplist, &m->ucm_context, true, impl, dev->pcm_handle, impl->profile_set->ignore_dB); } } else pa_alsa_path_set_add_ports(m->output_path_set, ap, impl->ports, dev->ports, NULL); pa_dynarray_append(&ap->out.devices, dev); if (m->split && m->split->broken) broken_ucm = true; } } if (ap->input_mappings) { PA_IDXSET_FOREACH(m, ap->input_mappings, idx) { dev = &m->input; if (dev->mapping == NULL) { init_device(impl, dev, PA_ALSA_DIRECTION_INPUT, m, n_devices++); pa_dynarray_append(&impl->out.devices, dev); } if (impl->use_ucm) { if (m->ucm_context.ucm_device) { pa_alsa_ucm_add_port(NULL, &m->ucm_context, false, impl->ports, ap, NULL); pa_alsa_ucm_add_ports(&dev->ports, m->proplist, &m->ucm_context, false, impl, dev->pcm_handle, impl->profile_set->ignore_dB); } } else pa_alsa_path_set_add_ports(m->input_path_set, ap, impl->ports, dev->ports, NULL); pa_dynarray_append(&ap->out.devices, dev); if (m->split && m->split->broken) broken_ucm = true; } } cp->n_devices = pa_dynarray_size(&ap->out.devices); cp->devices = ap->out.devices.array.data; pa_hashmap_put(impl->profiles, ap->name, cp); } /* Add a conspicuous notice if there are errors in the UCM profile */ if (broken_ucm) { const char *desc; char *new_desc = NULL; desc = pa_proplist_gets(impl->proplist, PA_PROP_DEVICE_DESCRIPTION); if (!desc) desc = ""; new_desc = spa_aprintf(_("%s [ALSA UCM error]"), desc); pa_log_notice("Errors in ALSA UCM profile for card %s", desc); if (new_desc) pa_proplist_sets(impl->proplist, PA_PROP_DEVICE_DESCRIPTION, new_desc); free(new_desc); } pa_dynarray_init(&impl->out.ports, NULL); n_ports = 0; PA_HASHMAP_FOREACH(dp, impl->ports, state) { void *state2; dp->card = impl; dp->port.index = n_ports++; dp->port.priority = dp->priority; pa_dynarray_init(&dp->prof, NULL); pa_dynarray_init(&dp->devices, NULL); n_profiles = 0; PA_HASHMAP_FOREACH(cp, dp->profiles, state2) { pa_dynarray_append(&dp->prof, cp); n_profiles++; } dp->port.n_profiles = n_profiles; dp->port.profiles = dp->prof.array.data; pa_proplist_setf(dp->proplist, "card.profile.port", "%u", dp->port.index); pa_proplist_as_dict(dp->proplist, &dp->port.props); pa_dynarray_append(&impl->out.ports, dp); } PA_DYNARRAY_FOREACH(dev, &impl->out.devices, idx) { PA_HASHMAP_FOREACH(dp, dev->ports, state) { pa_dynarray_append(&dev->port_array, dp); pa_dynarray_append(&dp->devices, dev); } dev->device.ports = dev->port_array.array.data; dev->device.n_ports = pa_dynarray_size(&dev->port_array); } arr = pa_proplist_gets(impl->proplist, "api.acp.hidden-ports"); PA_HASHMAP_FOREACH(dp, impl->ports, state) { if (contains_string(arr, dp->name)) dp->port.flags |= ACP_PORT_HIDDEN; dp->port.devices = dp->devices.array.data; dp->port.n_devices = pa_dynarray_size(&dp->devices); } pa_hashmap_sort(impl->profiles, compare_profile); n_profiles = 0; pa_dynarray_init(&impl->out.profiles, NULL); arr = pa_proplist_gets(impl->proplist, "api.acp.hidden-profiles"); PA_HASHMAP_FOREACH(cp, impl->profiles, state) { if (contains_string(arr, cp->name)) cp->flags |= ACP_PROFILE_HIDDEN; cp->index = n_profiles++; pa_dynarray_append(&impl->out.profiles, cp); } } static pa_available_t calc_port_state(pa_device_port *p, pa_card *impl) { void *state; pa_alsa_jack *jack; pa_available_t pa = PA_AVAILABLE_UNKNOWN; pa_device_port *port; PA_HASHMAP_FOREACH(jack, impl->jacks, state) { pa_available_t cpa; if (impl->use_ucm) port = pa_hashmap_get(impl->ports, jack->name); else { if (jack->path) port = jack->path->port; else continue; } if (p != port) continue; cpa = jack->plugged_in ? jack->state_plugged : jack->state_unplugged; if (cpa == PA_AVAILABLE_NO) { /* If a plugged-in jack causes the availability to go to NO, it * should override all other availability information (like a * blacklist) so set and bail */ if (jack->plugged_in) { pa = cpa; break; } /* If the current availability is unknown go the more precise no, * but otherwise don't change state */ if (pa == PA_AVAILABLE_UNKNOWN) pa = cpa; } else if (cpa == PA_AVAILABLE_YES) { /* Output is available through at least one jack, so go to that * level of availability. We still need to continue iterating through * the jacks in case a jack is plugged in that forces the state to no */ pa = cpa; } } return pa; } static void profile_set_available(pa_card *impl, uint32_t index, enum acp_available status, bool emit) { struct acp_card_profile *p = impl->card.profiles[index]; enum acp_available old = p->available; if (old != status) pa_log_info("Profile %s available %s -> %s", p->name, acp_available_str(old), acp_available_str(status)); p->available = status; if (emit && impl->events && impl->events->profile_available) impl->events->profile_available(impl->user_data, index, old, status); } struct temp_port_avail { pa_device_port *port; pa_available_t avail; }; static int report_jack_state(snd_mixer_elem_t *melem, unsigned int mask) { pa_card *impl = snd_mixer_elem_get_callback_private(melem); snd_hctl_elem_t **_elem = snd_mixer_elem_get_private(melem), *elem; snd_ctl_elem_value_t *elem_value; bool plugged_in, any_input_port_available; void *state; pa_alsa_jack *jack; struct temp_port_avail *tp, *tports; pa_alsa_profile *profile; enum acp_available active_available = ACP_AVAILABLE_UNKNOWN; size_t size; pa_assert(_elem); elem = *_elem; #if 0 /* Changing the jack state may cause a port change, and a port change will * make the sink or source change the mixer settings. If there are multiple * users having pulseaudio running, the mixer changes done by inactive * users may mess up the volume settings for the active users, because when * the inactive users change the mixer settings, those changes are picked * up by the active user's pulseaudio instance and the changes are * interpreted as if the active user changed the settings manually e.g. * with alsamixer. Even single-user systems suffer from this, because gdm * runs its own pulseaudio instance. * * We rerun this function when being unsuspended to catch up on jack state * changes */ if (u->card->suspend_cause & PA_SUSPEND_SESSION) return 0; #endif if (mask == SND_CTL_EVENT_MASK_REMOVE) return 0; snd_ctl_elem_value_alloca(&elem_value); if (snd_hctl_elem_read(elem, elem_value) < 0) { pa_log_warn("Failed to read jack detection from '%s'", pa_strnull(snd_hctl_elem_get_name(elem))); return 0; } plugged_in = !!snd_ctl_elem_value_get_boolean(elem_value, 0); pa_log_debug("Jack '%s' is now %s", pa_strnull(snd_hctl_elem_get_name(elem)), plugged_in ? "plugged in" : "unplugged"); size = sizeof(struct temp_port_avail) * (pa_hashmap_size(impl->jacks)+1); tports = tp = alloca(size); memset(tports, 0, size); PA_HASHMAP_FOREACH(jack, impl->jacks, state) if (jack->melem == melem) { pa_alsa_jack_set_plugged_in(jack, plugged_in); if (impl->use_ucm) { /* When using UCM, pa_alsa_jack_set_plugged_in() maps the jack * state to port availability. */ continue; } /* When not using UCM, we have to do the jack state -> port * availability mapping ourselves. */ pa_assert_se(tp->port = jack->path->port); tp->avail = calc_port_state(tp->port, impl); tp++; } /* Report available ports before unavailable ones: in case port 1 * becomes available when port 2 becomes unavailable, * this prevents an unnecessary switch port 1 -> port 3 -> port 2 */ for (tp = tports; tp->port; tp++) if (tp->avail != PA_AVAILABLE_NO) pa_device_port_set_available(tp->port, tp->avail); for (tp = tports; tp->port; tp++) if (tp->avail == PA_AVAILABLE_NO) pa_device_port_set_available(tp->port, tp->avail); for (tp = tports; tp->port; tp++) { pa_alsa_port_data *data; data = PA_DEVICE_PORT_DATA(tp->port); if (!data->suspend_when_unavailable) continue; #if 0 pa_sink *sink; uint32_t idx; PA_IDXSET_FOREACH(sink, u->core->sinks, idx) { if (sink->active_port == tp->port) pa_sink_suspend(sink, tp->avail == PA_AVAILABLE_NO, PA_SUSPEND_UNAVAILABLE); } #endif } /* Update profile availabilities. Ideally we would mark all profiles * unavailable that contain unavailable devices. We can't currently do that * in all cases, because if there are multiple sinks in a profile, and the * profile contains a mix of available and unavailable ports, we don't know * how the ports are distributed between the different sinks. It's possible * that some sinks contain only unavailable ports, in which case we should * mark the profile as unavailable, but it's also possible that all sinks * contain at least one available port, in which case we should mark the * profile as available. Until the data structures are improved so that we * can distinguish between these two cases, we mark the problematic cases * as available (well, "unknown" to be precise, but there's little * practical difference). * * When all output ports are unavailable, we know that all sinks are * unavailable, and therefore the profile is marked unavailable as well. * The same applies to input ports as well, of course. * * If there are no output ports at all, but the profile contains at least * one sink, then the output is considered to be available. */ if (impl->card.active_profile_index != ACP_INVALID_INDEX) active_available = impl->card.profiles[impl->card.active_profile_index]->available; /* First round - detect, if we have any input port available. If the hardware can report the state for all I/O jacks, only speakers may be plugged in. */ any_input_port_available = false; PA_HASHMAP_FOREACH(profile, impl->profiles, state) { pa_device_port *port; void *state2; if (profile->profile.flags & ACP_PROFILE_OFF) continue; PA_HASHMAP_FOREACH(port, impl->ports, state2) { if (!pa_hashmap_get(port->profiles, profile->profile.name)) continue; if (port->port.direction == ACP_DIRECTION_CAPTURE && port->port.available != ACP_AVAILABLE_NO) { any_input_port_available = true; goto input_port_found; } } } input_port_found: /* Second round */ PA_HASHMAP_FOREACH(profile, impl->profiles, state) { pa_device_port *port; void *state2; bool has_input_port = false; bool has_output_port = false; bool found_available_input_port = false; bool found_available_output_port = false; enum acp_available available = ACP_AVAILABLE_UNKNOWN; if (profile->profile.flags & ACP_PROFILE_OFF) continue; PA_HASHMAP_FOREACH(port, impl->ports, state2) { if (!pa_hashmap_get(port->profiles, profile->profile.name)) continue; if (port->port.direction == ACP_DIRECTION_CAPTURE) { has_input_port = true; if (port->port.available != ACP_AVAILABLE_NO) found_available_input_port = true; } else { has_output_port = true; if (port->port.available != ACP_AVAILABLE_NO) found_available_output_port = true; } } if ((has_input_port && !found_available_input_port) || (has_output_port && !found_available_output_port)) available = ACP_AVAILABLE_NO; if (has_input_port && !has_output_port && found_available_input_port) available = ACP_AVAILABLE_YES; if (has_output_port && (!has_input_port || !any_input_port_available) && found_available_output_port) available = ACP_AVAILABLE_YES; if (has_output_port && has_input_port && found_available_output_port && found_available_input_port) available = ACP_AVAILABLE_YES; /* We want to update the active profile's status last, so logic that * may change the active profile based on profile availability status * has an updated view of all profiles' availabilities. */ if (profile->profile.index == impl->card.active_profile_index) active_available = available; else profile_set_available(impl, profile->profile.index, available, false); } if (impl->card.active_profile_index != ACP_INVALID_INDEX) profile_set_available(impl, impl->card.active_profile_index, active_available, true); return 0; } static void init_jacks(pa_card *impl) { void *state; pa_alsa_path* path; pa_alsa_jack* jack; char buf[64]; impl->jacks = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); if (impl->use_ucm) { PA_LLIST_FOREACH(jack, impl->ucm.jacks) if (jack->has_control) pa_hashmap_put(impl->jacks, jack, jack); } else { /* See if we have any jacks */ if (impl->profile_set->output_paths) PA_HASHMAP_FOREACH(path, impl->profile_set->output_paths, state) PA_LLIST_FOREACH(jack, path->jacks) if (jack->has_control) pa_hashmap_put(impl->jacks, jack, jack); if (impl->profile_set->input_paths) PA_HASHMAP_FOREACH(path, impl->profile_set->input_paths, state) PA_LLIST_FOREACH(jack, path->jacks) if (jack->has_control) pa_hashmap_put(impl->jacks, jack, jack); } pa_log_debug("Found %d jacks.", pa_hashmap_size(impl->jacks)); if (pa_hashmap_size(impl->jacks) == 0) return; PA_HASHMAP_FOREACH(jack, impl->jacks, state) { if (!jack->mixer_device_name) { jack->mixer_handle = pa_alsa_open_mixer(impl->ucm.mixers, impl->card.index, false); if (!jack->mixer_handle) { pa_log("Failed to open mixer for card %d for jack detection", impl->card.index); continue; } } else { jack->mixer_handle = pa_alsa_open_mixer_by_name(impl->ucm.mixers, jack->mixer_device_name, false); if (!jack->mixer_handle) { pa_log("Failed to open mixer '%s' for jack detection", jack->mixer_device_name); continue; } } pa_alsa_mixer_use_for_poll(impl->ucm.mixers, jack->mixer_handle); jack->melem = pa_alsa_mixer_find_card(jack->mixer_handle, &jack->alsa_id, 0); if (!jack->melem) { pa_alsa_mixer_id_to_string(buf, sizeof(buf), &jack->alsa_id); pa_log_warn("Jack '%s' seems to have disappeared.", buf); pa_alsa_jack_set_has_control(jack, false); continue; } snd_mixer_elem_set_callback(jack->melem, report_jack_state); snd_mixer_elem_set_callback_private(jack->melem, impl); report_jack_state(jack->melem, 0); } } static pa_device_port* find_port_with_eld_device(pa_card *impl, int device) { void *state; pa_device_port *p; if (impl->use_ucm) { PA_HASHMAP_FOREACH(p, impl->ports, state) { pa_alsa_ucm_port_data *data = PA_DEVICE_PORT_DATA(p); pa_assert(data->eld_mixer_device_name); if (device == data->eld_device) return p; } } else { PA_HASHMAP_FOREACH(p, impl->ports, state) { pa_alsa_port_data *data = PA_DEVICE_PORT_DATA(p); pa_assert(data->path); if (device == data->path->eld_device) return p; } } return NULL; } static void acp_iec958_codec_mask_to_json(uint64_t codecs, char *buf, size_t maxsize) { struct spa_strbuf b; const struct spa_type_info *info; spa_strbuf_init(&b, buf, maxsize); for (info = spa_type_audio_iec958_codec; info->name; ++info) if ((codecs & (1ULL << info->type)) && info->type != SPA_AUDIO_IEC958_CODEC_UNKNOWN) spa_strbuf_append(&b, "%s\"%s\"", (b.pos ? "," : "["), spa_type_audio_iec958_codec_to_short_name(info->type)); if (b.pos) spa_strbuf_append(&b, "]"); } void acp_iec958_codecs_to_json(const uint32_t *codecs, size_t n_codecs, char *buf, size_t maxsize) { struct spa_strbuf b; spa_strbuf_init(&b, buf, maxsize); spa_strbuf_append(&b, "["); for (size_t i = 0; i < n_codecs; ++i) spa_strbuf_append(&b, "%s\"%s\"", (i ? "," : ""), spa_type_audio_iec958_codec_to_short_name(codecs[i])); spa_strbuf_append(&b, "]"); } size_t acp_iec958_codecs_from_json(const char *str, uint32_t *codecs, size_t max_codecs) { struct spa_json it; char v[256]; size_t n_codecs = 0; if (spa_json_begin_array_relax(&it, str, strlen(str)) <= 0) return 0; while (spa_json_get_string(&it, v, sizeof(v)) > 0) { uint32_t type = spa_type_audio_iec958_codec_from_short_name(v); if (type != SPA_AUDIO_IEC958_CODEC_UNKNOWN) codecs[n_codecs++] = type; if (n_codecs >= max_codecs) break; } return n_codecs; } static int hdmi_eld_changed(snd_mixer_elem_t *melem, unsigned int mask) { pa_card *impl = snd_mixer_elem_get_callback_private(melem); snd_hctl_elem_t **_elem = snd_mixer_elem_get_private(melem), *elem; int device; const char *old_monitor_name, *old_iec958_codec_list, *old_channels, *old_position; pa_device_port *p; pa_hdmi_eld eld; bool changed = false; pa_assert(_elem); elem = *_elem; device = snd_hctl_elem_get_device(elem); if (mask == SND_CTL_EVENT_MASK_REMOVE) return 0; p = find_port_with_eld_device(impl, device); if (p == NULL) { pa_log_error("Invalid device changed in ALSA: %d", device); return 0; } if (pa_alsa_get_hdmi_eld(elem, &eld) < 0) memset(&eld, 0, sizeof(eld)); // Strip trailing whitespace from monitor_name (primarily an NVidia driver bug for now) for (int i = strlen(eld.monitor_name) - 1; i >= 0; i--) { if (eld.monitor_name[i] == '\n' || eld.monitor_name[i] == '\r' || eld.monitor_name[i] == '\t' || eld.monitor_name[i] == ' ') eld.monitor_name[i] = 0; else break; } old_monitor_name = pa_proplist_gets(p->proplist, PA_PROP_DEVICE_PRODUCT_NAME); if (eld.monitor_name[0] == '\0') { changed |= old_monitor_name != NULL; pa_proplist_unset(p->proplist, PA_PROP_DEVICE_PRODUCT_NAME); } else { changed |= (old_monitor_name == NULL) || (!spa_streq(old_monitor_name, eld.monitor_name)); pa_proplist_sets(p->proplist, PA_PROP_DEVICE_PRODUCT_NAME, eld.monitor_name); } old_iec958_codec_list = pa_proplist_gets(p->proplist, ACP_KEY_IEC958_CODECS_DETECTED); if (eld.iec958_codecs == 0) { changed |= old_iec958_codec_list != NULL; pa_proplist_unset(p->proplist, ACP_KEY_IEC958_CODECS_DETECTED); } else { char codecs[512]; acp_iec958_codec_mask_to_json(eld.iec958_codecs, codecs, sizeof(codecs)); changed |= (old_iec958_codec_list == NULL) || (!spa_streq(old_iec958_codec_list, codecs)); pa_proplist_sets(p->proplist, ACP_KEY_IEC958_CODECS_DETECTED, codecs); } old_channels = pa_proplist_gets(p->proplist, ACP_KEY_AUDIO_CHANNELS_DETECTED); if (eld.lpcm_channels == 0) { changed |= old_channels != NULL; pa_proplist_unset(p->proplist, ACP_KEY_AUDIO_CHANNELS_DETECTED); } else { char channels[4]; snprintf(channels, sizeof(channels), "%u", eld.lpcm_channels); changed |= (old_channels == NULL) || (!spa_streq(old_channels, channels)); pa_proplist_sets(p->proplist, ACP_KEY_AUDIO_CHANNELS_DETECTED, channels); } old_position = pa_proplist_gets(p->proplist, ACP_KEY_AUDIO_POSITION_DETECTED); if (eld.speakers == 0) { changed |= old_position != NULL; pa_proplist_unset(p->proplist, ACP_KEY_AUDIO_POSITION_DETECTED); } else { uint32_t positions[eld.lpcm_channels]; char position[eld.lpcm_channels * 8]; struct spa_strbuf b; int i = 0; if (eld.speakers & 0x01) { positions[i++] = ACP_CHANNEL_FL; positions[i++] = ACP_CHANNEL_FR; } if (eld.speakers & 0x02) { positions[i++] = ACP_CHANNEL_LFE; } if (eld.speakers & 0x04) { positions[i++] = ACP_CHANNEL_FC; } if (eld.speakers & 0x08) { positions[i++] = ACP_CHANNEL_RL; positions[i++] = ACP_CHANNEL_RR; } /* The rest are out of order in order of what channels we would prefer to use/expose first */ if (eld.speakers & 0x40) { /* Use SL/SR instead of RLC/RRC */ positions[i++] = ACP_CHANNEL_SL; positions[i++] = ACP_CHANNEL_SR; } if (eld.speakers & 0x20) { positions[i++] = ACP_CHANNEL_RLC; positions[i++] = ACP_CHANNEL_RRC; } if (eld.speakers & 0x10) { positions[i++] = ACP_CHANNEL_RC; } while (i < eld.lpcm_channels) positions[i++] = ACP_CHANNEL_UNKNOWN; spa_strbuf_init(&b, position, sizeof(position)); spa_strbuf_append(&b, "["); for (i = 0; i < eld.lpcm_channels; i++) spa_strbuf_append(&b, "%s%s", i ? "," : "", channel_names[positions[i]]); spa_strbuf_append(&b, "]"); changed |= (old_position == NULL) || (!spa_streq(old_position, position)); pa_proplist_sets(p->proplist, ACP_KEY_AUDIO_POSITION_DETECTED, position); } pa_proplist_as_dict(p->proplist, &p->port.props); if (changed && mask != 0 && impl->events && impl->events->props_changed) impl->events->props_changed(impl->user_data); return 0; } static void init_eld_ctls(pa_card *impl) { void *state; pa_device_port *port; /* The code in this function expects ports to have a pa_alsa_port_data * struct as their data, but in UCM mode ports don't have any data. Hence, * the ELD controls can't currently be used in UCM mode. */ PA_HASHMAP_FOREACH(port, impl->ports, state) { snd_mixer_t *mixer_handle; snd_mixer_elem_t* melem; int device; if (impl->use_ucm) { pa_alsa_ucm_port_data *data = PA_DEVICE_PORT_DATA(port); device = data->eld_device; if (device < 0 || !data->eld_mixer_device_name) continue; mixer_handle = pa_alsa_open_mixer_by_name(impl->ucm.mixers, data->eld_mixer_device_name, true); } else { pa_alsa_port_data *data = PA_DEVICE_PORT_DATA(port); pa_assert(data->path); device = data->path->eld_device; if (device < 0) continue; mixer_handle = pa_alsa_open_mixer(impl->ucm.mixers, impl->card.index, true); } if (!mixer_handle) continue; melem = pa_alsa_mixer_find_pcm(mixer_handle, "ELD", device); if (melem) { pa_alsa_mixer_use_for_poll(impl->ucm.mixers, mixer_handle); snd_mixer_elem_set_callback(melem, hdmi_eld_changed); snd_mixer_elem_set_callback_private(melem, impl); hdmi_eld_changed(melem, 0); pa_log_info("ELD device found for port %s (%d).", port->port.name, device); } else pa_log_debug("No ELD device found for port %s (%d).", port->port.name, device); } } uint32_t acp_card_find_best_profile_index(struct acp_card *card, const char *name) { uint32_t i; uint32_t best, best2, off; struct acp_card_profile **profiles = card->profiles; best = best2 = ACP_INVALID_INDEX; off = 0; for (i = 0; i < card->n_profiles; i++) { struct acp_card_profile *p = profiles[i]; if (SPA_FLAG_IS_SET(p->flags, ACP_PROFILE_HIDDEN)) continue; if (name) { if (spa_streq(name, p->name)) best = i; } else if (p->flags & ACP_PROFILE_OFF) { off = i; } else if (p->available == ACP_AVAILABLE_YES) { if (best == ACP_INVALID_INDEX || p->priority > profiles[best]->priority) best = i; } else if (p->available != ACP_AVAILABLE_NO) { if (best2 == ACP_INVALID_INDEX || p->priority > profiles[best2]->priority) best2 = i; } } if (best == ACP_INVALID_INDEX) best = best2; if (best == ACP_INVALID_INDEX) best = off; return best; } static void find_mixer(pa_card *impl, pa_alsa_device *dev, const char *element, bool ignore_dB) { const char *mdev = NULL; pa_alsa_mapping *mapping = dev->mapping; if (!mapping && !element) return; if (!element && mapping && pa_alsa_path_set_is_empty(dev->mixer_path_set)) return; if (mapping) mdev = pa_proplist_gets(mapping->proplist, "alsa.mixer_device"); if (mdev) { dev->mixer_handle = pa_alsa_open_mixer_by_name(impl->ucm.mixers, mdev, true); } else { dev->mixer_handle = pa_alsa_open_mixer(impl->ucm.mixers, impl->card.index, true); } if (!dev->mixer_handle) { pa_log_info("Failed to find a working mixer device."); return; } if (element) { if (!(dev->mixer_path = pa_alsa_path_synthesize(element, dev->direction))) goto fail; if (pa_alsa_path_probe(dev->mixer_path, NULL, dev->mixer_handle, ignore_dB) < 0) goto fail; pa_log_debug("Probed mixer path %s:", dev->mixer_path->name); pa_alsa_path_dump(dev->mixer_path); } return; fail: if (dev->mixer_path) { pa_alsa_path_free(dev->mixer_path); dev->mixer_path = NULL; } dev->mixer_handle = NULL; } static int mixer_callback(snd_mixer_elem_t *elem, unsigned int mask) { pa_alsa_device *dev = snd_mixer_elem_get_callback_private(elem); if (mask == SND_CTL_EVENT_MASK_REMOVE) return 0; pa_log_info("%p mixer changed %d", dev, mask); if (mask & SND_CTL_EVENT_MASK_VALUE) { if (dev->read_volume) dev->read_volume(dev); if (dev->read_mute) dev->read_mute(dev); } return 0; } static int read_volume(pa_alsa_device *dev) { pa_card *impl = dev->card; pa_cvolume r; uint32_t i; int res; if (dev->ucm_context) { if (!dev->active_port) return 0; pa_alsa_ucm_port_data *data = PA_DEVICE_PORT_DATA(dev->active_port); if (pa_alsa_ucm_port_device_status(data) <= 0) return 0; } if (!dev->mixer_handle) return 0; if (dev->mixer_path->has_volume_mute && dev->muted) { /* Shift up by the base volume */ pa_sw_cvolume_divide_scalar(&r, &dev->hardware_volume, dev->base_volume); pa_log_debug("Reading cached volume only."); } else { if ((res = pa_alsa_path_get_volume(dev->mixer_path, dev->mixer_handle, &dev->mapping->channel_map, &r)) < 0) return res; } /* Shift down by the base volume, so that 0dB becomes maximum volume */ pa_sw_cvolume_multiply_scalar(&r, &r, dev->base_volume); if (pa_cvolume_equal(&dev->hardware_volume, &r)) return 0; dev->real_volume = dev->hardware_volume = r; pa_log_info("New hardware volume: min:%d max:%d", pa_cvolume_min(&r), pa_cvolume_max(&r)); for (i = 0; i < r.channels; i++) pa_log_debug(" %d: %d", i, r.values[i]); pa_cvolume_reset(&dev->soft_volume, r.channels); if (impl->events && impl->events->volume_changed) impl->events->volume_changed(impl->user_data, &dev->device); return 0; } static void set_volume(pa_alsa_device *dev, const pa_cvolume *v) { pa_cvolume r; bool write_to_hw; if (v != &dev->real_volume) dev->real_volume = *v; if (dev->ucm_context) { if (!dev->active_port) return; pa_alsa_ucm_port_data *data = PA_DEVICE_PORT_DATA(dev->active_port); if (pa_alsa_ucm_port_device_status(data) <= 0) return; } if (!dev->mixer_handle) return; /* Shift up by the base volume */ pa_sw_cvolume_divide_scalar(&r, &dev->real_volume, dev->base_volume); write_to_hw = !(dev->mixer_path->has_volume_mute && dev->muted); if (pa_alsa_path_set_volume(dev->mixer_path, dev->mixer_handle, &dev->mapping->channel_map, &r, false, write_to_hw) < 0) return; /* Shift down by the base volume, so that 0dB becomes maximum volume */ pa_sw_cvolume_multiply_scalar(&r, &r, dev->base_volume); dev->hardware_volume = r; if (dev->mixer_path->has_dB) { pa_cvolume new_soft_volume; bool accurate_enough; /* Match exactly what the user requested by software */ pa_sw_cvolume_divide(&new_soft_volume, &dev->real_volume, &dev->hardware_volume); /* If the adjustment to do in software is only minimal we * can skip it. That saves us CPU at the expense of a bit of * accuracy */ accurate_enough = (pa_cvolume_min(&new_soft_volume) >= (PA_VOLUME_NORM - VOLUME_ACCURACY)) && (pa_cvolume_max(&new_soft_volume) <= (PA_VOLUME_NORM + VOLUME_ACCURACY)); pa_log_debug("Requested volume: %d", pa_cvolume_max(&dev->real_volume)); pa_log_debug("Got hardware volume: %d", pa_cvolume_max(&dev->hardware_volume)); pa_log_debug("Calculated software volume: %d (accurate-enough=%s)", pa_cvolume_max(&new_soft_volume), pa_yes_no(accurate_enough)); if (accurate_enough) pa_cvolume_reset(&new_soft_volume, new_soft_volume.channels); dev->soft_volume = new_soft_volume; } else { pa_log_debug("Wrote hardware volume: %d", pa_cvolume_max(&r)); /* We can't match exactly what the user requested, hence let's * at least tell the user about it */ dev->real_volume = r; } } static int read_mute(pa_alsa_device *dev) { pa_card *impl = dev->card; bool mute; int res; if (dev->ucm_context) { if (!dev->active_port) return 0; pa_alsa_ucm_port_data *data = PA_DEVICE_PORT_DATA(dev->active_port); if (pa_alsa_ucm_port_device_status(data) <= 0) return 0; } if (!dev->mixer_handle) return 0; if (dev->mixer_path->has_volume_mute) { pa_cvolume mute_vol; pa_cvolume r; pa_cvolume_mute(&mute_vol, dev->mapping->channel_map.channels); if ((res = pa_alsa_path_get_volume(dev->mixer_path, dev->mixer_handle, &dev->mapping->channel_map, &r)) < 0) return res; mute = pa_cvolume_equal(&mute_vol, &r); } else { if ((res = pa_alsa_path_get_mute(dev->mixer_path, dev->mixer_handle, &mute)) < 0) return res; } if (mute == dev->muted) return 0; dev->muted = mute; pa_log_info("New hardware muted: %d", mute); if (impl->events && impl->events->mute_changed) impl->events->mute_changed(impl->user_data, &dev->device); return 0; } static void set_mute(pa_alsa_device *dev, bool mute) { dev->muted = mute; if (dev->ucm_context) { if (!dev->active_port) return; pa_alsa_ucm_port_data *data = PA_DEVICE_PORT_DATA(dev->active_port); if (pa_alsa_ucm_port_device_status(data) <= 0) return; } if (!dev->mixer_handle) return; if (dev->mixer_path->has_volume_mute) { pa_cvolume r; if (mute) { pa_cvolume_mute(&r, dev->mapping->channel_map.channels); pa_alsa_path_set_volume(dev->mixer_path, dev->mixer_handle, &dev->mapping->channel_map, &r, false, true); } else { /* Shift up by the base volume */ pa_sw_cvolume_divide_scalar(&r, &dev->real_volume, dev->base_volume); pa_log_debug("Restoring volume: %d", pa_cvolume_max(&dev->real_volume)); if (pa_alsa_path_set_volume(dev->mixer_path, dev->mixer_handle, &dev->mapping->channel_map, &r, false, true) < 0) pa_log_error("Unable to restore volume %d during unmute", pa_cvolume_max(&dev->real_volume)); } } else { pa_alsa_path_set_mute(dev->mixer_path, dev->mixer_handle, mute); } } static void mixer_volume_init(pa_card *impl, pa_alsa_device *dev) { pa_assert(dev); if (impl->soft_mixer || !dev->mixer_path || !dev->mixer_path->has_volume) { dev->read_volume = NULL; dev->set_volume = NULL; pa_log_info("Driver does not support hardware volume control, " "falling back to software volume control."); dev->base_volume = PA_VOLUME_NORM; dev->n_volume_steps = PA_VOLUME_NORM+1; dev->device.flags &= ~ACP_DEVICE_HW_VOLUME; } else { dev->read_volume = read_volume; dev->set_volume = set_volume; dev->device.flags |= ACP_DEVICE_HW_VOLUME; #if 0 if (u->mixer_path->has_dB && u->deferred_volume) { pa_sink_set_write_volume_callback(u->sink, sink_write_volume_cb); pa_log_info("Successfully enabled deferred volume."); } else pa_sink_set_write_volume_callback(u->sink, NULL); #endif if (dev->mixer_path->has_dB) { dev->decibel_volume = true; pa_log_info("Hardware volume ranges from %0.2f dB to %0.2f dB.", dev->mixer_path->min_dB, dev->mixer_path->max_dB); dev->base_volume = pa_sw_volume_from_dB(-dev->mixer_path->max_dB); dev->n_volume_steps = PA_VOLUME_NORM+1; /* If minimum volume is set to -99999 dB, then volume control supports * mute */ if (dev->mixer_path->min_dB == -99999.99 && !dev->mixer_path->has_mute) dev->mixer_path->has_volume_mute = true; pa_log_info("Fixing base volume to %0.2f dB", pa_sw_volume_to_dB(dev->base_volume)); } else { dev->decibel_volume = false; pa_log_info("Hardware volume ranges from %li to %li.", dev->mixer_path->min_volume, dev->mixer_path->max_volume); dev->base_volume = PA_VOLUME_NORM; dev->n_volume_steps = dev->mixer_path->max_volume - dev->mixer_path->min_volume + 1; } pa_log_info("Using hardware volume control. Hardware dB scale %s.", dev->mixer_path->has_dB ? "supported" : "not supported"); } dev->device.base_volume = (float)pa_sw_volume_to_linear(dev->base_volume); dev->device.volume_step = 1.0f / dev->n_volume_steps; if (impl->soft_mixer || !dev->mixer_path || (!dev->mixer_path->has_mute && !dev->mixer_path->has_volume_mute)) { dev->read_mute = NULL; dev->set_mute = NULL; pa_log_info("Driver does not support hardware mute control, falling back to software mute control."); dev->device.flags &= ~ACP_DEVICE_HW_MUTE; } else { dev->read_mute = read_mute; dev->set_mute = set_mute; pa_log_info("Using hardware %smute control.", dev->mixer_path->has_volume_mute ? "volume-" : ""); dev->device.flags |= ACP_DEVICE_HW_MUTE; } } static int setup_mixer(pa_card *impl, pa_alsa_device *dev, bool ignore_dB) { int res; bool need_mixer_callback = false; /* This code is before the u->mixer_handle check, because if the UCM * configuration doesn't specify volume or mute controls, u->mixer_handle * will be NULL, but the UCM device enable sequence will still need to be * executed. */ if (dev->active_port && dev->ucm_context) { if ((res = pa_alsa_ucm_set_port(dev->ucm_context, dev->active_port)) < 0) return res; } if (!dev->mixer_handle) return 0; if (dev->active_port) { if (!impl->use_ucm) { pa_alsa_port_data *data; /* We have a list of supported paths, so let's activate the * one that has been chosen as active */ data = PA_DEVICE_PORT_DATA(dev->active_port); dev->mixer_path = data->path; if (!impl->disable_mixer_path) pa_alsa_path_select(data->path, data->setting, dev->mixer_handle, dev->muted); } else { pa_alsa_ucm_port_data *data; data = PA_DEVICE_PORT_DATA(dev->active_port); /* Now activate volume controls, if any */ if (data->path) { dev->mixer_path = data->path; if (!impl->disable_mixer_path) pa_alsa_path_select(dev->mixer_path, NULL, dev->mixer_handle, dev->muted); } } } else { if (!dev->mixer_path && dev->mixer_path_set) dev->mixer_path = pa_hashmap_first(dev->mixer_path_set->paths); if (dev->mixer_path) { /* Hmm, we have only a single path, then let's activate it */ if (!impl->disable_mixer_path) pa_alsa_path_select(dev->mixer_path, dev->mixer_path->settings, dev->mixer_handle, dev->muted); } else return 0; } mixer_volume_init(impl, dev); /* Will we need to register callbacks? */ if (dev->mixer_path_set && dev->mixer_path_set->paths) { pa_alsa_path *p; void *state; PA_HASHMAP_FOREACH(p, dev->mixer_path_set->paths, state) { if (p->has_volume || p->has_mute) need_mixer_callback = true; } } else if (dev->mixer_path) need_mixer_callback = dev->mixer_path->has_volume || dev->mixer_path->has_mute; if (!impl->soft_mixer && need_mixer_callback) { pa_alsa_mixer_use_for_poll(impl->ucm.mixers, dev->mixer_handle); if (dev->mixer_path_set) pa_alsa_path_set_set_callback(dev->mixer_path_set, dev->mixer_handle, mixer_callback, dev); else pa_alsa_path_set_callback(dev->mixer_path, dev->mixer_handle, mixer_callback, dev); } return 0; } static int device_disable(pa_card *impl, pa_alsa_mapping *mapping, pa_alsa_device *dev) { dev->device.flags &= ~ACP_DEVICE_ACTIVE; if (dev->active_port) { dev->active_port->port.flags &= ~ACP_PORT_ACTIVE; dev->active_port = NULL; } return 0; } static int device_enable(pa_card *impl, pa_alsa_mapping *mapping, pa_alsa_device *dev) { const char *mod_name; uint32_t i, port_index; pa_device_port *p; void *state = NULL; int res; if (impl->use_ucm && (mod_name = pa_proplist_gets(mapping->proplist, PA_ALSA_PROP_UCM_MODIFIER))) { if (snd_use_case_set(impl->ucm.ucm_mgr, "_enamod", mod_name) < 0) pa_log("Failed to enable ucm modifier %s", mod_name); else pa_log_debug("Enabled ucm modifier %s", mod_name); } pa_log_info("Device: %s mapping '%s' (%s).", dev->device.description, mapping->description, mapping->name); dev->device.flags |= ACP_DEVICE_ACTIVE; find_mixer(impl, dev, NULL, impl->ignore_dB); /* Synchronize priority values, as it may have changed when setting the profile */ for (i = 0; i < impl->card.n_ports; i++) { p = (pa_device_port *)impl->card.ports[i]; p->port.priority = p->priority; } if (impl->auto_port) port_index = acp_device_find_best_port_index(&dev->device, NULL); else port_index = ACP_INVALID_INDEX; if (port_index == ACP_INVALID_INDEX) dev->active_port = NULL; else dev->active_port = (pa_device_port*)impl->card.ports[port_index]; if (dev->active_port) dev->active_port->port.flags |= ACP_PORT_ACTIVE; if (impl->use_eld_channels) { while ((p = pa_hashmap_iterate(dev->ports, &state, NULL))) { const char *channels = pa_proplist_gets(p->proplist, ACP_KEY_AUDIO_CHANNELS_DETECTED); const char *positions = pa_proplist_gets(p->proplist, ACP_KEY_AUDIO_POSITION_DETECTED); if (channels && positions) { const char *position, *split_state = NULL; size_t i = 0, n; dev->device.format.channels = atoi(channels); free(dev->device.format.map); dev->device.format.map = calloc(dev->device.format.channels, sizeof(uint32_t)); while ((position = pa_split_in_place(positions, ",", &n, &split_state)) != NULL && i < dev->device.format.channels) { dev->device.format.map[i++] = acp_channel_from_str(position, n); } break; } } } if ((res = setup_mixer(impl, dev, impl->ignore_dB)) < 0) return res; if (dev->read_volume) dev->read_volume(dev); else { pa_cvolume_reset(&dev->real_volume, dev->device.format.channels); pa_cvolume_reset(&dev->soft_volume, dev->device.format.channels); } if (dev->read_mute) dev->read_mute(dev); else dev->muted = false; state = NULL; while ((p = pa_hashmap_iterate(dev->ports, &state, NULL))) { const char *codecs = pa_proplist_gets(p->proplist, ACP_KEY_IEC958_CODECS_DETECTED); if (codecs) { dev->device.n_codecs = acp_iec958_codecs_from_json(codecs, dev->device.codecs, ACP_N_ELEMENTS(dev->device.codecs)); } if (codecs) break; } return 0; } int acp_card_set_profile(struct acp_card *card, uint32_t new_index, uint32_t flags) { pa_card *impl = (pa_card *)card; pa_alsa_mapping *am; uint32_t old_index = impl->card.active_profile_index; struct acp_card_profile **profiles = card->profiles; pa_alsa_profile *op, *np; uint32_t idx; int res; if (new_index >= card->n_profiles) return -EINVAL; np = (pa_alsa_profile*)profiles[new_index]; if (SPA_FLAG_IS_SET(np->profile.flags, ACP_PROFILE_HIDDEN)) return -EINVAL; op = old_index != ACP_INVALID_INDEX ? (pa_alsa_profile*)profiles[old_index] : NULL; if (op == np) return 0; pa_log_info("activate profile: %s (%d)", np->profile.name, new_index); if (op && op->output_mappings) { PA_IDXSET_FOREACH(am, op->output_mappings, idx) { if (np->output_mappings && pa_idxset_get_by_data(np->output_mappings, am, NULL)) continue; device_disable(impl, am, &am->output); } } if (op && op->input_mappings) { PA_IDXSET_FOREACH(am, op->input_mappings, idx) { if (np->input_mappings && pa_idxset_get_by_data(np->input_mappings, am, NULL)) continue; device_disable(impl, am, &am->input); } } /* if UCM is available for this card then update the verb */ if (impl->use_ucm) { if (np->profile.flags & ACP_PROFILE_OFF) { if ((res = pa_alsa_ucm_set_profile(&impl->ucm, impl, NULL, op)) < 0) return res; } else if (np->profile.flags & ACP_PROFILE_PRO) { const char *verb = find_best_verb(impl); if ((res = pa_alsa_ucm_set_profile(&impl->ucm, impl, NULL, op)) < 0) return res; if ((res = snd_use_case_set(impl->ucm.ucm_mgr, "_verb", verb)) < 0) { pa_log_error("error setting verb: %s", snd_strerror(res)); return res; } } else if ((res = pa_alsa_ucm_set_profile(&impl->ucm, impl, np, op)) < 0) { return res; } } if (np->output_mappings) { PA_IDXSET_FOREACH(am, np->output_mappings, idx) { if (impl->use_ucm) { /* Update ports priorities */ if (am->ucm_context.ucm_device) { pa_alsa_ucm_add_port(am->output.ports, &am->ucm_context, true, impl->ports, np, NULL); } } device_enable(impl, am, &am->output); } } if (np->input_mappings) { PA_IDXSET_FOREACH(am, np->input_mappings, idx) { if (impl->use_ucm) { /* Update ports priorities */ if (am->ucm_context.ucm_device) { pa_alsa_ucm_add_port(am->input.ports, &am->ucm_context, false, impl->ports, np, NULL); } } device_enable(impl, am, &am->input); } } if (op) op->profile.flags &= ~(ACP_PROFILE_ACTIVE | ACP_PROFILE_SAVE); np->profile.flags |= ACP_PROFILE_ACTIVE | flags; impl->card.active_profile_index = new_index; if (impl->events && impl->events->profile_changed) impl->events->profile_changed(impl->user_data, old_index, new_index); return 0; } static void prune_singleton_availability_groups(pa_hashmap *ports) { pa_device_port *p; pa_hashmap *group_counts; void *state, *count; const char *group; /* Collect groups and erase those that don't have more than 1 path */ group_counts = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); PA_HASHMAP_FOREACH(p, ports, state) { if (p->availability_group) { count = pa_hashmap_get(group_counts, p->availability_group); pa_hashmap_remove(group_counts, p->availability_group); pa_hashmap_put(group_counts, p->availability_group, PA_UINT_TO_PTR(PA_PTR_TO_UINT(count) + 1)); } } /* Now we have an availability_group -> count map, let's drop all groups * that have only one member */ PA_HASHMAP_FOREACH_KV(group, count, group_counts, state) { if (count == PA_UINT_TO_PTR(1)) pa_hashmap_remove(group_counts, group); } PA_HASHMAP_FOREACH(p, ports, state) { if (p->availability_group && !pa_hashmap_get(group_counts, p->availability_group)) { pa_log_debug("Pruned singleton availability group %s from port %s", p->availability_group, p->name); pa_xfree(p->availability_group); p->availability_group = NULL; } } pa_hashmap_free(group_counts); } struct acp_card *acp_card_new(uint32_t index, const struct acp_dict *props) { pa_card *impl; struct acp_card *card; const char *s, *profile_set = NULL, *profile = NULL; char device_id[16]; uint32_t profile_index; int res; impl = calloc(1, sizeof(*impl)); if (impl == NULL) return NULL; pa_alsa_refcnt_inc(); snprintf(device_id, sizeof(device_id), "%d", index); impl->proplist = pa_proplist_new_dict(props); card = &impl->card; card->index = index; card->active_profile_index = ACP_INVALID_INDEX; impl->use_ucm = true; impl->auto_profile = true; impl->auto_port = true; impl->ignore_dB = false; impl->rate = DEFAULT_RATE; impl->pro_channels = DEFAULT_CHANNELS; if (props) { if ((s = acp_dict_lookup(props, "api.alsa.use-ucm")) != NULL) impl->use_ucm = spa_atob(s); if ((s = acp_dict_lookup(props, "api.alsa.soft-mixer")) != NULL) impl->soft_mixer = spa_atob(s); if ((s = acp_dict_lookup(props, "api.alsa.disable-mixer-path")) != NULL) impl->disable_mixer_path = spa_atob(s); if ((s = acp_dict_lookup(props, "api.alsa.ignore-dB")) != NULL) impl->ignore_dB = spa_atob(s); if ((s = acp_dict_lookup(props, "device.profile-set")) != NULL) profile_set = s; if ((s = acp_dict_lookup(props, "device.profile")) != NULL) profile = s; if ((s = acp_dict_lookup(props, "api.acp.auto-profile")) != NULL) impl->auto_profile = spa_atob(s); if ((s = acp_dict_lookup(props, "api.acp.auto-port")) != NULL) impl->auto_port = spa_atob(s); if ((s = acp_dict_lookup(props, "api.acp.probe-rate")) != NULL) impl->rate = atoi(s); if ((s = acp_dict_lookup(props, "api.acp.pro-channels")) != NULL) impl->pro_channels = atoi(s); if ((s = acp_dict_lookup(props, "api.alsa.split-enable")) != NULL) impl->ucm.split_enable = spa_atob(s); if ((s = acp_dict_lookup(props, "api.acp.disable-pro-audio")) != NULL) impl->disable_pro_audio = spa_atob(s); if ((s = acp_dict_lookup(props, "api.acp.use-eld-channels")) != NULL) impl->use_eld_channels = spa_atob(s); } #if SND_LIB_VERSION < 0x10207 if (impl->ucm.split_enable) pa_log_info("alsa-lib too old for PipeWire-side UCM SplitPCM"); impl->ucm.split_enable = false; /* API addition in 1.2.7 */ #endif impl->ucm.default_sample_spec.format = PA_SAMPLE_S16NE; impl->ucm.default_sample_spec.rate = impl->rate; impl->ucm.default_sample_spec.channels = 2; pa_channel_map_init_extend(&impl->ucm.default_channel_map, impl->ucm.default_sample_spec.channels, PA_CHANNEL_MAP_ALSA); impl->ucm.default_n_fragments = 4; impl->ucm.default_fragment_size_msec = 25; impl->ucm.mixers = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, pa_xfree, (pa_free_cb_t) pa_alsa_mixer_free); impl->profiles = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL, (pa_free_cb_t) profile_free); impl->ports = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL, (pa_free_cb_t) port_free); snd_config_update_free_global(); res = impl->use_ucm ? pa_alsa_ucm_query_profiles(&impl->ucm, card->index) : -1; if (res == -PA_ALSA_ERR_UCM_LINKED) { res = -EEXIST; goto error; } if (res == 0) { pa_log_info("Found UCM profiles"); impl->profile_set = pa_alsa_ucm_add_profile_set(&impl->ucm, &impl->ucm.default_channel_map); } else { impl->use_ucm = false; impl->profile_set = pa_alsa_profile_set_new(profile_set, &impl->ucm.default_channel_map); } if (impl->profile_set == NULL) { res = -ENOTSUP; goto error; } impl->profile_set->ignore_dB = impl->ignore_dB; pa_alsa_profile_set_probe(impl->profile_set, impl->ucm.mixers, device_id, &impl->ucm.default_sample_spec, impl->ucm.default_n_fragments, impl->ucm.default_fragment_size_msec); pa_alsa_init_proplist_card(NULL, impl->proplist, impl->card.index); pa_proplist_sets(impl->proplist, PA_PROP_DEVICE_STRING, device_id); pa_alsa_init_description(impl->proplist, NULL); add_profiles(impl); prune_singleton_availability_groups(impl->ports); card->n_profiles = pa_dynarray_size(&impl->out.profiles); card->profiles = impl->out.profiles.array.data; card->n_ports = pa_dynarray_size(&impl->out.ports); card->ports = impl->out.ports.array.data; card->n_devices = pa_dynarray_size(&impl->out.devices); card->devices = impl->out.devices.array.data; pa_proplist_as_dict(impl->proplist, &card->props); init_jacks(impl); if (!impl->auto_profile && profile == NULL) profile = "off"; init_eld_ctls(impl); profile_index = acp_card_find_best_profile_index(&impl->card, profile); acp_card_set_profile(&impl->card, profile_index, 0); return &impl->card; error: pa_alsa_refcnt_dec(); free(impl); errno = -res; return NULL; } void acp_card_add_listener(struct acp_card *card, const struct acp_card_events *events, void *user_data) { pa_card *impl = (pa_card *)card; impl->events = events; impl->user_data = user_data; } void acp_card_destroy(struct acp_card *card) { pa_card *impl = (pa_card *)card; if (impl->profiles) pa_hashmap_free(impl->profiles); if (impl->ports) pa_hashmap_free(impl->ports); pa_dynarray_clear(&impl->out.devices); pa_dynarray_clear(&impl->out.profiles); pa_dynarray_clear(&impl->out.ports); if (impl->ucm.mixers) pa_hashmap_free(impl->ucm.mixers); if (impl->jacks) pa_hashmap_free(impl->jacks); if (impl->profile_set) pa_alsa_profile_set_free(impl->profile_set); pa_alsa_ucm_free(&impl->ucm); pa_proplist_free(impl->proplist); pa_alsa_refcnt_dec(); free(impl); } int acp_card_poll_descriptors_count(struct acp_card *card) { pa_card *impl = (pa_card *)card; void *state; pa_alsa_mixer *pm; int n, count = 0; PA_HASHMAP_FOREACH(pm, impl->ucm.mixers, state) { if (!pm->used_for_poll) continue; n = snd_mixer_poll_descriptors_count(pm->mixer_handle); if (n < 0) return n; count += n; } return count; } int acp_card_poll_descriptors(struct acp_card *card, struct pollfd *pfds, unsigned int space) { pa_card *impl = (pa_card *)card; void *state; pa_alsa_mixer *pm; int n, count = 0; PA_HASHMAP_FOREACH(pm, impl->ucm.mixers, state) { if (!pm->used_for_poll) continue; n = snd_mixer_poll_descriptors(pm->mixer_handle, pfds, space); if (n < 0) return n; if (space >= (unsigned int) n) { count += n; space -= n; pfds += n; } else space = 0; } return count; } int acp_card_poll_descriptors_revents(struct acp_card *card, struct pollfd *pfds, unsigned int nfds, unsigned short *revents) { unsigned int idx; unsigned short res; if (nfds == 0) return -EINVAL; res = 0; for (idx = 0; idx < nfds; idx++, pfds++) res |= pfds->revents & (POLLIN|POLLERR|POLLNVAL); *revents = res; return 0; } int acp_card_handle_events(struct acp_card *card) { pa_card *impl = (pa_card *)card; void *state; pa_alsa_mixer *pm; int n, count = 0; PA_HASHMAP_FOREACH(pm, impl->ucm.mixers, state) { if (!pm->used_for_poll) continue; n = snd_mixer_handle_events(pm->mixer_handle); if (n < 0) return n; count += n; } return count; } static void sync_mixer(pa_alsa_device *d, pa_device_port *port) { pa_alsa_setting *setting = NULL; pa_card *impl = d->card; if (!d->mixer_path) return; /* port may be NULL, because if we use a synthesized mixer path, then the * sink has no ports. */ if (port && !d->ucm_context) { pa_alsa_port_data *data; data = PA_DEVICE_PORT_DATA(port); setting = data->setting; } if (d->mixer_handle && !impl->disable_mixer_path) pa_alsa_path_select(d->mixer_path, setting, d->mixer_handle, d->muted); if (d->set_mute) d->set_mute(d, d->muted); if (d->set_volume) d->set_volume(d, &d->real_volume); } uint32_t acp_device_find_best_port_index(struct acp_device *dev, const char *name) { uint32_t i; uint32_t best, best2, best3; struct acp_port **ports = dev->ports; best = best2 = best3 = ACP_INVALID_INDEX; for (i = 0; i < dev->n_ports; i++) { struct acp_port *p = ports[i]; if (SPA_FLAG_IS_SET(p->flags, ACP_PORT_HIDDEN)) continue; if (name) { if (spa_streq(name, p->name)) best = i; } else if (p->available == ACP_AVAILABLE_YES) { if (best == ACP_INVALID_INDEX || p->priority > ports[best]->priority) best = i; } else if (p->available != ACP_AVAILABLE_NO) { if (best2 == ACP_INVALID_INDEX || p->priority > ports[best2]->priority) best2 = i; } else { if (best3 == ACP_INVALID_INDEX || p->priority > ports[best3]->priority) best3 = i; } } if (best == ACP_INVALID_INDEX) best = best2; if (best == ACP_INVALID_INDEX) best = best3; if (best == ACP_INVALID_INDEX) best = 0; if (best < dev->n_ports) return ports[best]->index; else return ACP_INVALID_INDEX; } int acp_device_set_port(struct acp_device *dev, uint32_t port_index, uint32_t flags) { pa_alsa_device *d = (pa_alsa_device*)dev; pa_card *impl = d->card; pa_device_port *p, *old = d->active_port; int res; if (port_index >= impl->card.n_ports) return -EINVAL; p = (pa_device_port*)impl->card.ports[port_index]; if (!pa_hashmap_get(d->ports, p->name)) return -EINVAL; if (SPA_FLAG_IS_SET(p->port.flags, ACP_PORT_HIDDEN)) return -EINVAL; p->port.flags = ACP_PORT_ACTIVE | flags; if (p == old) return 0; if (old) old->port.flags &= ~(ACP_PORT_ACTIVE | ACP_PORT_SAVE); d->active_port = p; if (impl->use_ucm) { pa_alsa_ucm_port_data *data; data = PA_DEVICE_PORT_DATA(p); d->mixer_path = data->path; mixer_volume_init(impl, d); res = pa_alsa_ucm_set_port(d->ucm_context, p); sync_mixer(d, p); } else { pa_alsa_port_data *data; data = PA_DEVICE_PORT_DATA(p); d->mixer_path = data->path; mixer_volume_init(impl, d); sync_mixer(d, p); res = 0; #if 0 if (data->suspend_when_unavailable && p->available == PA_AVAILABLE_NO) pa_sink_suspend(s, true, PA_SUSPEND_UNAVAILABLE); else pa_sink_suspend(s, false, PA_SUSPEND_UNAVAILABLE); #endif } if (impl->events && impl->events->port_changed) impl->events->port_changed(impl->user_data, old ? old->port.index : 0, p->port.index); return res; } int acp_device_set_volume(struct acp_device *dev, const float *volume, uint32_t n_volume) { pa_alsa_device *d = (pa_alsa_device*)dev; pa_card *impl = d->card; uint32_t i; pa_cvolume v, old_volume; if (n_volume == 0) return -EINVAL; old_volume = d->real_volume; v.channels = d->mapping->channel_map.channels; for (i = 0; i < v.channels; i++) v.values[i] = pa_sw_volume_from_linear(volume[i % n_volume]); pa_log_info("Set %s volume: min:%d max:%d", d->set_volume ? "hardware" : "software", pa_cvolume_min(&v), pa_cvolume_max(&v)); for (i = 0; i < v.channels; i++) pa_log_debug(" %d: %d", i, v.values[i]); if (d->set_volume) { d->set_volume(d, &v); } else { d->real_volume = v; d->soft_volume = v; } if (!pa_cvolume_equal(&d->real_volume, &old_volume)) if (impl->events && impl->events->volume_changed) impl->events->volume_changed(impl->user_data, dev); return 0; } static int get_volume(pa_cvolume *v, float *volume, uint32_t n_volume) { uint32_t i; if (v->channels == 0) return -EIO; for (i = 0; i < n_volume; i++) volume[i] = (float)pa_sw_volume_to_linear(v->values[i % v->channels]); return 0; } int acp_device_get_soft_volume(struct acp_device *dev, float *volume, uint32_t n_volume) { pa_alsa_device *d = (pa_alsa_device*)dev; return get_volume(&d->soft_volume, volume, n_volume); } int acp_device_get_volume(struct acp_device *dev, float *volume, uint32_t n_volume) { pa_alsa_device *d = (pa_alsa_device*)dev; return get_volume(&d->real_volume, volume, n_volume); } int acp_device_set_mute(struct acp_device *dev, bool mute) { pa_alsa_device *d = (pa_alsa_device*)dev; pa_card *impl = d->card; bool old_muted = d->muted; if (old_muted == mute) return 0; pa_log_info("Set %s mute: %d", d->set_mute ? "hardware" : "software", mute); if (d->set_mute) { d->set_mute(d, mute); } else { d->muted = mute; } if (old_muted != mute) if (impl->events && impl->events->mute_changed) impl->events->mute_changed(impl->user_data, dev); return 0; } int acp_device_get_mute(struct acp_device *dev, bool *mute) { pa_alsa_device *d = (pa_alsa_device*)dev; *mute = d->muted; return 0; } void acp_set_log_func(acp_log_func func, void *data) { _acp_log_func = func; _acp_log_data = data; } void acp_set_log_level(int level) { _acp_log_level = level; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/acp/acp.h000066400000000000000000000250421511204443500253440ustar00rootroot00000000000000/* ALSA Card Profile */ /* SPDX-FileCopyrightText: Copyright © 2020 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef ACP_H #define ACP_H #include #include #include #include #include #ifdef __cplusplus extern "C" { #else #include #endif #ifdef __GNUC__ #define ACP_PRINTF_FUNC(fmt, arg1) __attribute__((format(printf, fmt, arg1))) #else #define ACP_PRINTF_FUNC(fmt, arg1) #endif #define ACP_INVALID_INDEX ((uint32_t)-1) struct acp_dict_item { const char *key; const char *value; }; #define ACP_DICT_ITEM_INIT(key,value) ((struct acp_dict_item) { (key), (value) }) struct acp_dict { uint32_t flags; uint32_t n_items; const struct acp_dict_item *items; }; enum acp_channel { ACP_CHANNEL_UNKNOWN, /**< unspecified */ ACP_CHANNEL_NA, /**< N/A, silent */ ACP_CHANNEL_MONO, /**< mono stream */ ACP_CHANNEL_FL, /**< front left */ ACP_CHANNEL_FR, /**< front right */ ACP_CHANNEL_FC, /**< front center */ ACP_CHANNEL_LFE, /**< LFE */ ACP_CHANNEL_SL, /**< side left */ ACP_CHANNEL_SR, /**< side right */ ACP_CHANNEL_FLC, /**< front left center */ ACP_CHANNEL_FRC, /**< front right center */ ACP_CHANNEL_RC, /**< rear center */ ACP_CHANNEL_RL, /**< rear left */ ACP_CHANNEL_RR, /**< rear right */ ACP_CHANNEL_TC, /**< top center */ ACP_CHANNEL_TFL, /**< top front left */ ACP_CHANNEL_TFC, /**< top front center */ ACP_CHANNEL_TFR, /**< top front right */ ACP_CHANNEL_TRL, /**< top rear left */ ACP_CHANNEL_TRC, /**< top rear center */ ACP_CHANNEL_TRR, /**< top rear right */ ACP_CHANNEL_RLC, /**< rear left center */ ACP_CHANNEL_RRC, /**< rear right center */ ACP_CHANNEL_FLW, /**< front left wide */ ACP_CHANNEL_FRW, /**< front right wide */ ACP_CHANNEL_LFE2, /**< LFE 2 */ ACP_CHANNEL_FLH, /**< front left high */ ACP_CHANNEL_FCH, /**< front center high */ ACP_CHANNEL_FRH, /**< front right high */ ACP_CHANNEL_TFLC, /**< top front left center */ ACP_CHANNEL_TFRC, /**< top front right center */ ACP_CHANNEL_TSL, /**< top side left */ ACP_CHANNEL_TSR, /**< top side right */ ACP_CHANNEL_LLFE, /**< left LFE */ ACP_CHANNEL_RLFE, /**< right LFE */ ACP_CHANNEL_BC, /**< bottom center */ ACP_CHANNEL_BLC, /**< bottom left center */ ACP_CHANNEL_BRC, /**< bottom right center */ ACP_CHANNEL_START_Aux = 0x1000, ACP_CHANNEL_LAST_Aux = 0x1fff, ACP_CHANNEL_START_Custom = 0x10000, }; char *acp_channel_str(char *buf, size_t len, enum acp_channel ch); struct acp_format { uint32_t flags; uint32_t format_mask; uint32_t rate_mask; uint32_t channels; uint32_t *map; }; #define ACP_DICT_INIT(items,n_items) ((struct acp_dict) { 0, (n_items), (items) }) #define ACP_DICT_INIT_ARRAY(items) ((struct acp_dict) { 0, sizeof(items)/sizeof((items)[0]), (items) }) #define acp_dict_for_each(item, dict) \ for ((item) = (dict)->items; \ (item) < &(dict)->items[(dict)->n_items]; \ (item)++) static inline const char *acp_dict_lookup(const struct acp_dict *dict, const char *key) { const struct acp_dict_item *it; acp_dict_for_each(it, dict) if (strcmp(key, it->key) == 0) return it->value; return NULL; } enum acp_direction { ACP_DIRECTION_PLAYBACK = 1, ACP_DIRECTION_CAPTURE = 2 }; const char *acp_direction_str(enum acp_direction direction); enum acp_available { ACP_AVAILABLE_UNKNOWN = 0, ACP_AVAILABLE_NO = 1, ACP_AVAILABLE_YES = 2 }; const char *acp_available_str(enum acp_available status); #define ACP_KEY_PORT_TYPE "port.type" /**< a Port type, like "aux", "speaker", ... */ #define ACP_KEY_PORT_AVAILABILITY_GROUP "port.availability-group" /**< An identifier for the group of ports that share their availability status with * each other. This is meant especially for handling cases where one 3.5 mm connector * is used for headphones, headsets and microphones, and the hardware can only tell * that something was plugged in but not what exactly. In this situation the ports for * all those devices share their availability status, and ACP can't tell which * one is actually plugged in, and some application may ask the user what was plugged * in. Such applications should get a list of all card ports and compare their * `available_group` fields. Ports that have the same group are those that need * input from the user to determine which device was plugged in. The application should * then activate the user-chosen port. * * May be NULL, in which case the port is not part of any availability group (which is * the same as having a group with only one member). * * The group identifier must be treated as an opaque identifier. The string may look * like an ALSA control name, but applications must not assume any such relationship. * The group naming scheme can change without a warning. */ #define ACP_KEY_IEC958_CODECS_DETECTED "iec958.codecs.detected" /**< A list of IEC958 passthrough formats which have been auto-detected as being * supported by a given node. This only serves as a hint, as the auto-detected * values may be incorrect and/or might change, e.g. when external devices such * as receivers are powered on or off. */ #define ACP_KEY_AUDIO_CHANNELS_DETECTED "audio.channels.detected" /**< The number of channels detected detected via EDID-like data read from a device * connected via HDMI/DisplayPort. This only serves as a hint, as the auto-detected * values may be incorrect and/or might change, e.g. when external devices such * as receivers are powered on or off. */ #define ACP_KEY_AUDIO_POSITION_DETECTED "audio.position.detected" /**< The channel positions detected detected via EDID-like data read from a device * connected via HDMI/DisplayPort. This only serves as a hint, as the auto-detected * values may be incorrect and/or might change, e.g. when external devices such * as receivers are powered on or off. */ struct acp_device; struct acp_card_events { #define ACP_VERSION_CARD_EVENTS 0 uint32_t version; void (*destroy) (void *data); void (*props_changed) (void *data); void (*profile_changed) (void *data, uint32_t old_index, uint32_t new_index); void (*profile_available) (void *data, uint32_t index, enum acp_available old, enum acp_available available); void (*port_changed) (void *data, uint32_t old_index, uint32_t new_index); void (*port_available) (void *data, uint32_t index, enum acp_available old, enum acp_available available); void (*volume_changed) (void *data, struct acp_device *dev); void (*mute_changed) (void *data, struct acp_device *dev); }; struct acp_port { uint32_t index; /**< unique index for this port */ #define ACP_PORT_ACTIVE (1<<0) #define ACP_PORT_SAVE (1<<1) /* if the port needs saving */ #define ACP_PORT_HIDDEN (1<<2) uint32_t flags; /**< extra port flags */ const char *name; /**< Name of this port */ const char *description; /**< Description of this port */ uint32_t priority; /**< The higher this value is, the more useful this port is as a default. */ enum acp_direction direction; enum acp_available available; /**< A flags (see #acp_port_available), indicating availability status of this port. */ struct acp_dict props; /**< extra port properties */ uint32_t n_profiles; /**< number of elements in profiles array */ struct acp_card_profile **profiles; /**< array of profiles for this port */ uint32_t n_devices; /**< number of elements in devices array */ struct acp_device **devices; /**< array of devices */ }; struct acp_device { uint32_t index; #define ACP_DEVICE_ACTIVE (1<<0) #define ACP_DEVICE_HW_VOLUME (1<<1) #define ACP_DEVICE_HW_MUTE (1<<2) #define ACP_DEVICE_UCM_DEVICE (1<<3) #define ACP_DEVICE_IEC958 (1<<4) #define ACP_DEVICE_HIDDEN (1<<5) uint32_t flags; const char *name; const char *description; uint32_t priority; enum acp_direction direction; struct acp_dict props; const char **device_strings; struct acp_format format; float base_volume; float volume_step; uint32_t n_ports; struct acp_port **ports; int64_t latency_ns; uint32_t codecs[32]; uint32_t n_codecs; }; struct acp_card_profile { uint32_t index; #define ACP_PROFILE_ACTIVE (1<<0) #define ACP_PROFILE_OFF (1<<1) /* the Off profile */ #define ACP_PROFILE_SAVE (1<<2) /* if the profile needs saving */ #define ACP_PROFILE_PRO (1<<3) /* the Pro profile */ #define ACP_PROFILE_HIDDEN (1<<4) /* don't show the profile */ uint32_t flags; const char *name; const char *description; uint32_t priority; enum acp_available available; struct acp_dict props; uint32_t n_devices; struct acp_device **devices; }; struct acp_card { uint32_t index; uint32_t flags; struct acp_dict props; uint32_t n_profiles; uint32_t active_profile_index; struct acp_card_profile **profiles; uint32_t n_devices; struct acp_device **devices; uint32_t n_ports; struct acp_port **ports; uint32_t preferred_input_port_index; uint32_t preferred_output_port_index; }; struct acp_card *acp_card_new(uint32_t index, const struct acp_dict *props); void acp_card_add_listener(struct acp_card *card, const struct acp_card_events *events, void *user_data); void acp_card_destroy(struct acp_card *card); int acp_card_poll_descriptors_count(struct acp_card *card); int acp_card_poll_descriptors(struct acp_card *card, struct pollfd *pfds, unsigned int space); int acp_card_poll_descriptors_revents(struct acp_card *card, struct pollfd *pfds, unsigned int nfds, unsigned short *revents); int acp_card_handle_events(struct acp_card *card); uint32_t acp_card_find_best_profile_index(struct acp_card *card, const char *name); int acp_card_set_profile(struct acp_card *card, uint32_t profile_index, uint32_t flags); uint32_t acp_device_find_best_port_index(struct acp_device *dev, const char *name); int acp_device_set_port(struct acp_device *dev, uint32_t port_index, uint32_t flags); int acp_device_set_volume(struct acp_device *dev, const float *volume, uint32_t n_volume); int acp_device_get_soft_volume(struct acp_device *dev, float *volume, uint32_t n_volume); int acp_device_get_volume(struct acp_device *dev, float *volume, uint32_t n_volume); int acp_device_set_mute(struct acp_device *dev, bool mute); int acp_device_get_mute(struct acp_device *dev, bool *mute); typedef void (*acp_log_func) (void *data, int level, const char *file, int line, const char *func, const char *fmt, va_list arg) ACP_PRINTF_FUNC(6,0); void acp_set_log_func(acp_log_func, void *data); void acp_set_log_level(int level); void acp_iec958_codecs_to_json(const uint32_t *codecs, size_t n_codecs, char *buf, size_t maxsize); size_t acp_iec958_codecs_from_json(const char *str, uint32_t *codecs, size_t max_codecs); #ifdef __cplusplus } #endif #endif /* ACP_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/acp/alsa-mixer.c000066400000000000000000005332511511204443500266440ustar00rootroot00000000000000/*** This file is part of PulseAudio. Copyright 2004-2009 Lennart Poettering Copyright 2006 Pierre Ossman for Cendio AB PulseAudio is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. PulseAudio is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with PulseAudio; if not, see . ***/ #include "config.h" #include #include #include #include #include "conf-parser.h" #include "alsa-mixer.h" #include "alsa-util.h" /** * ALSA (1/100) dB volume equal or below this is considered muted. * The actual ALSA value is SND_CTL_TLV_DB_GAIN_MUTE = -9999999 */ #ifdef SND_CTL_TLV_DB_GAIN_MUTE #define ALSA_DB_MUTED (SND_CTL_TLV_DB_GAIN_MUTE) #else #define ALSA_DB_MUTED (-9999999) #endif static int setting_select(pa_alsa_setting *s, snd_mixer_t *m); struct description_map { const char *key; const char *description; }; struct description2_map { const char *key; const char *description; pa_device_port_type_t type; }; char *pa_alsa_mixer_id_to_string(char *dst, size_t dst_len, pa_alsa_mixer_id *id) { if (id->index > 0) { snprintf(dst, dst_len, "'%s',%d", id->name, id->index); } else { snprintf(dst, dst_len, "'%s'", id->name); } return dst; } static int alsa_id_decode(const char *src, char *name, int *index) { char *idx, c; int i; *index = 0; c = src[0]; /* Strip quotes in entries such as 'Speaker',1 or "Speaker",1 */ if (c == '\'' || c == '"') { strcpy(name, src + 1); for (i = 0; name[i] != '\0' && name[i] != c; i++); idx = NULL; if (name[i]) { name[i] = '\0'; idx = strchr(name + i + 1, ','); } } else { strcpy(name, src); idx = strchr(name, ','); } if (idx == NULL) return 0; *idx = '\0'; idx++; if (*idx < '0' || *idx > '9') { pa_log("Element %s: index value is invalid", src); return 1; } *index = atoi(idx); return 0; } pa_alsa_jack *pa_alsa_jack_new(pa_alsa_path *path, const char *mixer_device_name, const char *name, int index) { pa_alsa_jack *jack; pa_assert(name); jack = pa_xnew0(pa_alsa_jack, 1); jack->path = path; jack->mixer_device_name = pa_xstrdup(mixer_device_name); jack->name = pa_xstrdup(name); jack->alsa_id.name = pa_sprintf_malloc("%s Jack", name); jack->alsa_id.index = index; jack->state_unplugged = PA_AVAILABLE_NO; jack->state_plugged = PA_AVAILABLE_YES; jack->ucm_devices = pa_dynarray_new(NULL); jack->ucm_hw_mute_devices = pa_dynarray_new(NULL); return jack; } void pa_alsa_jack_free(pa_alsa_jack *jack) { pa_assert(jack); pa_dynarray_free(jack->ucm_hw_mute_devices); pa_dynarray_free(jack->ucm_devices); pa_xfree(jack->alsa_id.name); pa_xfree(jack->name); pa_xfree(jack->mixer_device_name); pa_xfree(jack); } void pa_alsa_jack_set_has_control(pa_alsa_jack *jack, bool has_control) { pa_alsa_ucm_device *device; unsigned idx; pa_assert(jack); if (has_control == jack->has_control) return; jack->has_control = has_control; PA_DYNARRAY_FOREACH(device, jack->ucm_hw_mute_devices, idx) pa_alsa_ucm_device_update_available(device); PA_DYNARRAY_FOREACH(device, jack->ucm_devices, idx) pa_alsa_ucm_device_update_available(device); } void pa_alsa_jack_set_plugged_in(pa_alsa_jack *jack, bool plugged_in) { pa_alsa_ucm_device *device; unsigned idx; pa_assert(jack); if (plugged_in == jack->plugged_in) return; jack->plugged_in = plugged_in; /* XXX: If this is a headphone jack that mutes speakers when plugged in, * and the headphones get unplugged, then the headphone device must be set * to unavailable and the speaker device must be set to unknown. So far so * good. But there's an ugly detail: we must first set the availability of * the speakers and then the headphones. We shouldn't need to care about * the order, but we have to, because module-switch-on-port-available gets * separate events for the two devices, and the intermediate state between * the two events is such that the second event doesn't trigger the desired * port switch, if the event order is "wrong". * * These are the transitions when the event order is "right": * * speakers: 1) unavailable -> 2) unknown -> 3) unknown * headphones: 1) available -> 2) available -> 3) unavailable * * In the 2 -> 3 transition, headphones become unavailable, and * module-switch-on-port-available sees that speakers can be used, so the * port gets changed as it should. * * These are the transitions when the event order is "wrong": * * speakers: 1) unavailable -> 2) unavailable -> 3) unknown * headphones: 1) available -> 2) unavailable -> 3) unavailable * * In the 1 -> 2 transition, headphones become unavailable, and there are * no available ports to use, so no port change happens. In the 2 -> 3 * transition, speaker availability becomes unknown, but that's not * a strong enough signal for module-switch-on-port-available, so it still * doesn't do the port switch. * * We should somehow merge the two events so that * module-switch-on-port-available would handle both transitions in one go. * If module-switch-on-port-available used a defer event to delay * the port availability processing, that would probably do the trick. */ PA_DYNARRAY_FOREACH(device, jack->ucm_hw_mute_devices, idx) pa_alsa_ucm_device_update_available(device); PA_DYNARRAY_FOREACH(device, jack->ucm_devices, idx) pa_alsa_ucm_device_update_available(device); } void pa_alsa_jack_add_ucm_device(pa_alsa_jack *jack, pa_alsa_ucm_device *device) { pa_alsa_ucm_device *idevice; unsigned idx, prio, iprio; pa_assert(jack); pa_assert(device); /* store the ucm device with the sequence of priority from low to high. this * could guarantee when the jack state is changed, the device with highest * priority will send to the module-switch-on-port-available last */ prio = device->playback_priority ? device->playback_priority : device->capture_priority; PA_DYNARRAY_FOREACH(idevice, jack->ucm_devices, idx) { iprio = idevice->playback_priority ? idevice->playback_priority : idevice->capture_priority; if (iprio > prio) break; } pa_dynarray_insert_by_index(jack->ucm_devices, device, idx); } void pa_alsa_jack_add_ucm_hw_mute_device(pa_alsa_jack *jack, pa_alsa_ucm_device *device) { pa_assert(jack); pa_assert(device); pa_dynarray_append(jack->ucm_hw_mute_devices, device); } static const char *lookup_description(const char *key, const struct description_map dm[], unsigned n) { unsigned i; if (!key) return NULL; for (i = 0; i < n; i++) if (pa_streq(dm[i].key, key)) return _(dm[i].description); return NULL; } static const struct description2_map *lookup_description2(const char *key, const struct description2_map dm[], unsigned n) { unsigned i; if (!key) return NULL; for (i = 0; i < n; i++) if (pa_streq(dm[i].key, key)) return &dm[i]; return NULL; } void pa_alsa_mixer_use_for_poll(pa_hashmap *mixers, snd_mixer_t *mixer_handle) { pa_alsa_mixer *pm; void *state; PA_HASHMAP_FOREACH(pm, mixers, state) { if (pm->mixer_handle == mixer_handle) { pm->used_for_probe_only = false; pm->used_for_poll = true; } } } #if 0 struct pa_alsa_fdlist { unsigned num_fds; struct pollfd *fds; /* This is a temporary buffer used to avoid lots of mallocs */ struct pollfd *work_fds; snd_mixer_t *mixer; snd_hctl_t *hctl; pa_mainloop_api *m; pa_defer_event *defer; pa_io_event **ios; bool polled; void (*cb)(void *userdata); void *userdata; }; static void io_cb(pa_mainloop_api *a, pa_io_event *e, int fd, pa_io_event_flags_t events, void *userdata) { struct pa_alsa_fdlist *fdl = userdata; int err; unsigned i; unsigned short revents; pa_assert(a); pa_assert(fdl); pa_assert(fdl->mixer || fdl->hctl); pa_assert(fdl->fds); pa_assert(fdl->work_fds); if (fdl->polled) return; fdl->polled = true; memcpy(fdl->work_fds, fdl->fds, sizeof(struct pollfd) * fdl->num_fds); for (i = 0; i < fdl->num_fds; i++) { if (e == fdl->ios[i]) { if (events & PA_IO_EVENT_INPUT) fdl->work_fds[i].revents |= POLLIN; if (events & PA_IO_EVENT_OUTPUT) fdl->work_fds[i].revents |= POLLOUT; if (events & PA_IO_EVENT_ERROR) fdl->work_fds[i].revents |= POLLERR; if (events & PA_IO_EVENT_HANGUP) fdl->work_fds[i].revents |= POLLHUP; break; } } pa_assert(i != fdl->num_fds); if (fdl->hctl) err = snd_hctl_poll_descriptors_revents(fdl->hctl, fdl->work_fds, fdl->num_fds, &revents); else err = snd_mixer_poll_descriptors_revents(fdl->mixer, fdl->work_fds, fdl->num_fds, &revents); if (err < 0) { pa_log_error("Unable to get poll revent: %s", pa_alsa_strerror(err)); return; } a->defer_enable(fdl->defer, 1); if (revents) { if (fdl->hctl) snd_hctl_handle_events(fdl->hctl); else snd_mixer_handle_events(fdl->mixer); } } static void defer_cb(pa_mainloop_api *a, pa_defer_event *e, void *userdata) { struct pa_alsa_fdlist *fdl = userdata; unsigned num_fds, i; int err, n; struct pollfd *temp; pa_assert(a); pa_assert(fdl); pa_assert(fdl->mixer || fdl->hctl); a->defer_enable(fdl->defer, 0); if (fdl->hctl) n = snd_hctl_poll_descriptors_count(fdl->hctl); else n = snd_mixer_poll_descriptors_count(fdl->mixer); if (n < 0) { pa_log("snd_mixer_poll_descriptors_count() failed: %s", pa_alsa_strerror(n)); return; } else if (n == 0) { pa_log_warn("Mixer has no poll descriptors. Please control mixer from PulseAudio only."); return; } num_fds = (unsigned) n; if (num_fds != fdl->num_fds) { if (fdl->fds) pa_xfree(fdl->fds); if (fdl->work_fds) pa_xfree(fdl->work_fds); fdl->fds = pa_xnew0(struct pollfd, num_fds); fdl->work_fds = pa_xnew(struct pollfd, num_fds); } memset(fdl->work_fds, 0, sizeof(struct pollfd) * num_fds); if (fdl->hctl) err = snd_hctl_poll_descriptors(fdl->hctl, fdl->work_fds, num_fds); else err = snd_mixer_poll_descriptors(fdl->mixer, fdl->work_fds, num_fds); if (err < 0) { pa_log_error("Unable to get poll descriptors: %s", pa_alsa_strerror(err)); return; } fdl->polled = false; if (memcmp(fdl->fds, fdl->work_fds, sizeof(struct pollfd) * num_fds) == 0) return; if (fdl->ios) { for (i = 0; i < fdl->num_fds; i++) a->io_free(fdl->ios[i]); if (num_fds != fdl->num_fds) { pa_xfree(fdl->ios); fdl->ios = NULL; } } if (!fdl->ios) fdl->ios = pa_xnew(pa_io_event*, num_fds); /* Swap pointers */ temp = fdl->work_fds; fdl->work_fds = fdl->fds; fdl->fds = temp; fdl->num_fds = num_fds; for (i = 0;i < num_fds;i++) fdl->ios[i] = a->io_new(a, fdl->fds[i].fd, ((fdl->fds[i].events & POLLIN) ? PA_IO_EVENT_INPUT : 0) | ((fdl->fds[i].events & POLLOUT) ? PA_IO_EVENT_OUTPUT : 0), io_cb, fdl); } struct pa_alsa_fdlist *pa_alsa_fdlist_new(void) { struct pa_alsa_fdlist *fdl; fdl = pa_xnew0(struct pa_alsa_fdlist, 1); return fdl; } void pa_alsa_fdlist_free(struct pa_alsa_fdlist *fdl) { pa_assert(fdl); if (fdl->defer) { pa_assert(fdl->m); fdl->m->defer_free(fdl->defer); } if (fdl->ios) { unsigned i; pa_assert(fdl->m); for (i = 0; i < fdl->num_fds; i++) fdl->m->io_free(fdl->ios[i]); pa_xfree(fdl->ios); } if (fdl->fds) pa_xfree(fdl->fds); if (fdl->work_fds) pa_xfree(fdl->work_fds); pa_xfree(fdl); } /* We can listen to either a snd_hctl_t or a snd_mixer_t, but not both */ int pa_alsa_fdlist_set_handle(struct pa_alsa_fdlist *fdl, snd_mixer_t *mixer_handle, snd_hctl_t *hctl_handle, pa_mainloop_api *m) { pa_assert(fdl); pa_assert(hctl_handle || mixer_handle); pa_assert(!(hctl_handle && mixer_handle)); pa_assert(m); pa_assert(!fdl->m); fdl->hctl = hctl_handle; fdl->mixer = mixer_handle; fdl->m = m; fdl->defer = m->defer_new(m, defer_cb, fdl); return 0; } struct pa_alsa_mixer_pdata { pa_rtpoll *rtpoll; pa_rtpoll_item *poll_item; snd_mixer_t *mixer; }; struct pa_alsa_mixer_pdata *pa_alsa_mixer_pdata_new(void) { struct pa_alsa_mixer_pdata *pd; pd = pa_xnew0(struct pa_alsa_mixer_pdata, 1); return pd; } void pa_alsa_mixer_pdata_free(struct pa_alsa_mixer_pdata *pd) { pa_assert(pd); if (pd->poll_item) { pa_rtpoll_item_free(pd->poll_item); } pa_xfree(pd); } static int rtpoll_work_cb(pa_rtpoll_item *i) { struct pa_alsa_mixer_pdata *pd; struct pollfd *p; unsigned n_fds; unsigned short revents = 0; int err, ret = 0; pd = pa_rtpoll_item_get_work_userdata(i); pa_assert_fp(pd); pa_assert_fp(i == pd->poll_item); p = pa_rtpoll_item_get_pollfd(i, &n_fds); if ((err = snd_mixer_poll_descriptors_revents(pd->mixer, p, n_fds, &revents)) < 0) { pa_log_error("Unable to get poll revent: %s", pa_alsa_strerror(err)); ret = -1; goto fail; } if (revents) { if (revents & (POLLNVAL | POLLERR)) { pa_log_debug("Device disconnected, stopping poll on mixer"); goto fail; } else if (revents & POLLERR) { /* This shouldn't happen. */ pa_log_error("Got a POLLERR (revents = %04x), stopping poll on mixer", revents); goto fail; } err = snd_mixer_handle_events(pd->mixer); if (PA_LIKELY(err >= 0)) { pa_rtpoll_item_free(i); pa_alsa_set_mixer_rtpoll(pd, pd->mixer, pd->rtpoll); } else { pa_log_error("Error handling mixer event: %s", pa_alsa_strerror(err)); ret = -1; goto fail; } } return ret; fail: pa_rtpoll_item_free(i); pd->poll_item = NULL; pd->rtpoll = NULL; pd->mixer = NULL; return ret; } int pa_alsa_set_mixer_rtpoll(struct pa_alsa_mixer_pdata *pd, snd_mixer_t *mixer, pa_rtpoll *rtp) { pa_rtpoll_item *i; struct pollfd *p; int err, n; pa_assert(pd); pa_assert(mixer); pa_assert(rtp); if ((n = snd_mixer_poll_descriptors_count(mixer)) < 0) { pa_log("snd_mixer_poll_descriptors_count() failed: %s", pa_alsa_strerror(n)); return -1; } else if (n == 0) { pa_log_warn("Mixer has no poll descriptors. Please control mixer from PulseAudio only."); return 0; } i = pa_rtpoll_item_new(rtp, PA_RTPOLL_LATE, (unsigned) n); p = pa_rtpoll_item_get_pollfd(i, NULL); memset(p, 0, sizeof(struct pollfd) * n); if ((err = snd_mixer_poll_descriptors(mixer, p, (unsigned) n)) < 0) { pa_log_error("Unable to get poll descriptors: %s", pa_alsa_strerror(err)); pa_rtpoll_item_free(i); return -1; } pd->rtpoll = rtp; pd->poll_item = i; pd->mixer = mixer; pa_rtpoll_item_set_work_callback(i, rtpoll_work_cb, pd); return 0; } #endif static const snd_mixer_selem_channel_id_t alsa_channel_ids[PA_CHANNEL_POSITION_MAX] = { [PA_CHANNEL_POSITION_MONO] = SND_MIXER_SCHN_MONO, /* The ALSA name is just an alias! */ [PA_CHANNEL_POSITION_FRONT_CENTER] = SND_MIXER_SCHN_FRONT_CENTER, [PA_CHANNEL_POSITION_FRONT_LEFT] = SND_MIXER_SCHN_FRONT_LEFT, [PA_CHANNEL_POSITION_FRONT_RIGHT] = SND_MIXER_SCHN_FRONT_RIGHT, [PA_CHANNEL_POSITION_REAR_CENTER] = SND_MIXER_SCHN_REAR_CENTER, [PA_CHANNEL_POSITION_REAR_LEFT] = SND_MIXER_SCHN_REAR_LEFT, [PA_CHANNEL_POSITION_REAR_RIGHT] = SND_MIXER_SCHN_REAR_RIGHT, [PA_CHANNEL_POSITION_LFE] = SND_MIXER_SCHN_WOOFER, [PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER] = SND_MIXER_SCHN_UNKNOWN, [PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER] = SND_MIXER_SCHN_UNKNOWN, [PA_CHANNEL_POSITION_SIDE_LEFT] = SND_MIXER_SCHN_SIDE_LEFT, [PA_CHANNEL_POSITION_SIDE_RIGHT] = SND_MIXER_SCHN_SIDE_RIGHT, [PA_CHANNEL_POSITION_AUX0] = SND_MIXER_SCHN_UNKNOWN, [PA_CHANNEL_POSITION_AUX1] = SND_MIXER_SCHN_UNKNOWN, [PA_CHANNEL_POSITION_AUX2] = SND_MIXER_SCHN_UNKNOWN, [PA_CHANNEL_POSITION_AUX3] = SND_MIXER_SCHN_UNKNOWN, [PA_CHANNEL_POSITION_AUX4] = SND_MIXER_SCHN_UNKNOWN, [PA_CHANNEL_POSITION_AUX5] = SND_MIXER_SCHN_UNKNOWN, [PA_CHANNEL_POSITION_AUX6] = SND_MIXER_SCHN_UNKNOWN, [PA_CHANNEL_POSITION_AUX7] = SND_MIXER_SCHN_UNKNOWN, [PA_CHANNEL_POSITION_AUX8] = SND_MIXER_SCHN_UNKNOWN, [PA_CHANNEL_POSITION_AUX9] = SND_MIXER_SCHN_UNKNOWN, [PA_CHANNEL_POSITION_AUX10] = SND_MIXER_SCHN_UNKNOWN, [PA_CHANNEL_POSITION_AUX11] = SND_MIXER_SCHN_UNKNOWN, [PA_CHANNEL_POSITION_AUX12] = SND_MIXER_SCHN_UNKNOWN, [PA_CHANNEL_POSITION_AUX13] = SND_MIXER_SCHN_UNKNOWN, [PA_CHANNEL_POSITION_AUX14] = SND_MIXER_SCHN_UNKNOWN, [PA_CHANNEL_POSITION_AUX15] = SND_MIXER_SCHN_UNKNOWN, [PA_CHANNEL_POSITION_AUX16] = SND_MIXER_SCHN_UNKNOWN, [PA_CHANNEL_POSITION_AUX17] = SND_MIXER_SCHN_UNKNOWN, [PA_CHANNEL_POSITION_AUX18] = SND_MIXER_SCHN_UNKNOWN, [PA_CHANNEL_POSITION_AUX19] = SND_MIXER_SCHN_UNKNOWN, [PA_CHANNEL_POSITION_AUX20] = SND_MIXER_SCHN_UNKNOWN, [PA_CHANNEL_POSITION_AUX21] = SND_MIXER_SCHN_UNKNOWN, [PA_CHANNEL_POSITION_AUX22] = SND_MIXER_SCHN_UNKNOWN, [PA_CHANNEL_POSITION_AUX23] = SND_MIXER_SCHN_UNKNOWN, [PA_CHANNEL_POSITION_AUX24] = SND_MIXER_SCHN_UNKNOWN, [PA_CHANNEL_POSITION_AUX25] = SND_MIXER_SCHN_UNKNOWN, [PA_CHANNEL_POSITION_AUX26] = SND_MIXER_SCHN_UNKNOWN, [PA_CHANNEL_POSITION_AUX27] = SND_MIXER_SCHN_UNKNOWN, [PA_CHANNEL_POSITION_AUX28] = SND_MIXER_SCHN_UNKNOWN, [PA_CHANNEL_POSITION_AUX29] = SND_MIXER_SCHN_UNKNOWN, [PA_CHANNEL_POSITION_AUX30] = SND_MIXER_SCHN_UNKNOWN, [PA_CHANNEL_POSITION_AUX31] = SND_MIXER_SCHN_UNKNOWN, [PA_CHANNEL_POSITION_TOP_CENTER] = SND_MIXER_SCHN_UNKNOWN, [PA_CHANNEL_POSITION_TOP_FRONT_CENTER] = SND_MIXER_SCHN_UNKNOWN, [PA_CHANNEL_POSITION_TOP_FRONT_LEFT] = SND_MIXER_SCHN_UNKNOWN, [PA_CHANNEL_POSITION_TOP_FRONT_RIGHT] = SND_MIXER_SCHN_UNKNOWN, [PA_CHANNEL_POSITION_TOP_REAR_CENTER] = SND_MIXER_SCHN_UNKNOWN, [PA_CHANNEL_POSITION_TOP_REAR_LEFT] = SND_MIXER_SCHN_UNKNOWN, [PA_CHANNEL_POSITION_TOP_REAR_RIGHT] = SND_MIXER_SCHN_UNKNOWN }; static snd_mixer_selem_channel_id_t alsa_channel_positions[POSITION_MASK_CHANNELS] = { SND_MIXER_SCHN_FRONT_LEFT, SND_MIXER_SCHN_FRONT_RIGHT, SND_MIXER_SCHN_REAR_LEFT, SND_MIXER_SCHN_REAR_RIGHT, SND_MIXER_SCHN_FRONT_CENTER, SND_MIXER_SCHN_WOOFER, SND_MIXER_SCHN_SIDE_LEFT, SND_MIXER_SCHN_SIDE_RIGHT, #if POSITION_MASK_CHANNELS > 8 #error "Extend alsa_channel_positions[] array (9+)" #endif }; static void setting_free(pa_alsa_setting *s) { pa_assert(s); if (s->options) pa_idxset_free(s->options, NULL); pa_xfree(s->name); pa_xfree(s->description); pa_xfree(s); } static void option_free(pa_alsa_option *o) { pa_assert(o); pa_xfree(o->alsa_name); pa_xfree(o->name); pa_xfree(o->description); pa_xfree(o); } static void decibel_fix_free(pa_alsa_decibel_fix *db_fix) { pa_assert(db_fix); pa_xfree(db_fix->name); pa_xfree(db_fix->db_values); pa_xfree(db_fix->key); pa_xfree(db_fix); } static void element_free(pa_alsa_element *e) { pa_alsa_option *o; pa_assert(e); while ((o = e->options)) { PA_LLIST_REMOVE(pa_alsa_option, e->options, o); option_free(o); } if (e->db_fix) decibel_fix_free(e->db_fix); pa_xfree(e->alsa_id.name); pa_xfree(e); } void pa_alsa_path_free(pa_alsa_path *p) { pa_alsa_jack *j; pa_alsa_element *e; pa_alsa_setting *s; pa_assert(p); while ((j = p->jacks)) { PA_LLIST_REMOVE(pa_alsa_jack, p->jacks, j); pa_alsa_jack_free(j); } while ((e = p->elements)) { PA_LLIST_REMOVE(pa_alsa_element, p->elements, e); element_free(e); } while ((s = p->settings)) { PA_LLIST_REMOVE(pa_alsa_setting, p->settings, s); setting_free(s); } pa_proplist_free(p->proplist); pa_xfree(p->availability_group); pa_xfree(p->name); pa_xfree(p->description); pa_xfree(p->description_key); pa_xfree(p); } void pa_alsa_path_set_free(pa_alsa_path_set *ps) { pa_assert(ps); if (ps->paths) pa_hashmap_free(ps->paths); pa_xfree(ps); } int pa_alsa_path_set_is_empty(pa_alsa_path_set *ps) { if (ps && !pa_hashmap_isempty(ps->paths)) return 0; return 1; } static long to_alsa_dB(pa_volume_t v) { return lround(pa_sw_volume_to_dB(v) * 100.0); } static pa_volume_t from_alsa_dB(long v) { return pa_sw_volume_from_dB((double) v / 100.0); } static long to_alsa_volume(pa_volume_t v, long min, long max) { long w; w = (long) round(((double) v * (double) (max - min)) / PA_VOLUME_NORM) + min; return PA_CLAMP_UNLIKELY(w, min, max); } static pa_volume_t from_alsa_volume(long v, long min, long max) { return (pa_volume_t) round(((double) (v - min) * PA_VOLUME_NORM) / (double) (max - min)); } #define SELEM_INIT(sid, aid) \ do { \ snd_mixer_selem_id_alloca(&(sid)); \ snd_mixer_selem_id_set_name((sid), (aid)->name); \ snd_mixer_selem_id_set_index((sid), (aid)->index); \ } while(false) static int element_get_volume(pa_alsa_element *e, snd_mixer_t *m, const pa_channel_map *cm, pa_cvolume *v) { snd_mixer_selem_id_t *sid; snd_mixer_elem_t *me; snd_mixer_selem_channel_id_t c; pa_channel_position_mask_t mask = 0; char buf[64]; unsigned k; pa_assert(m); pa_assert(e); pa_assert(cm); pa_assert(v); SELEM_INIT(sid, &e->alsa_id); if (!(me = snd_mixer_find_selem(m, sid))) { pa_alsa_mixer_id_to_string(buf, sizeof(buf), &e->alsa_id); pa_log_warn("Element %s seems to have disappeared.", buf); return -1; } pa_cvolume_mute(v, cm->channels); /* We take the highest volume of all channels that match */ for (c = 0; c <= SND_MIXER_SCHN_LAST; c++) { int r; pa_volume_t f; if (e->has_dB) { long value = 0; if (e->direction == PA_ALSA_DIRECTION_OUTPUT) { if (snd_mixer_selem_has_playback_channel(me, c)) { if (e->db_fix) { if ((r = snd_mixer_selem_get_playback_volume(me, c, &value)) >= 0) { /* If the channel volume is outside the limits set * by the dB fix, we clamp the hw volume to be * within the limits. */ if (value < e->db_fix->min_step) { value = e->db_fix->min_step; snd_mixer_selem_set_playback_volume(me, c, value); pa_alsa_mixer_id_to_string(buf, sizeof(buf), &e->alsa_id); pa_log_debug("Playback volume for element %s channel %i was below the dB fix limit. " "Volume reset to %0.2f dB.", buf, c, e->db_fix->db_values[value - e->db_fix->min_step] / 100.0); } else if (value > e->db_fix->max_step) { value = e->db_fix->max_step; snd_mixer_selem_set_playback_volume(me, c, value); pa_alsa_mixer_id_to_string(buf, sizeof(buf), &e->alsa_id); pa_log_debug("Playback volume for element %s channel %i was over the dB fix limit. " "Volume reset to %0.2f dB.", buf, c, e->db_fix->db_values[value - e->db_fix->min_step] / 100.0); } /* Volume step -> dB value conversion. */ value = e->db_fix->db_values[value - e->db_fix->min_step]; } } else r = snd_mixer_selem_get_playback_dB(me, c, &value); } else r = -1; } else { if (snd_mixer_selem_has_capture_channel(me, c)) { if (e->db_fix) { if ((r = snd_mixer_selem_get_capture_volume(me, c, &value)) >= 0) { /* If the channel volume is outside the limits set * by the dB fix, we clamp the hw volume to be * within the limits. */ if (value < e->db_fix->min_step) { value = e->db_fix->min_step; snd_mixer_selem_set_capture_volume(me, c, value); pa_alsa_mixer_id_to_string(buf, sizeof(buf), &e->alsa_id); pa_log_debug("Capture volume for element %s channel %i was below the dB fix limit. " "Volume reset to %0.2f dB.", buf, c, e->db_fix->db_values[value - e->db_fix->min_step] / 100.0); } else if (value > e->db_fix->max_step) { value = e->db_fix->max_step; snd_mixer_selem_set_capture_volume(me, c, value); pa_alsa_mixer_id_to_string(buf, sizeof(buf), &e->alsa_id); pa_log_debug("Capture volume for element %s channel %i was over the dB fix limit. " "Volume reset to %0.2f dB.", buf, c, e->db_fix->db_values[value - e->db_fix->min_step] / 100.0); } /* Volume step -> dB value conversion. */ value = e->db_fix->db_values[value - e->db_fix->min_step]; } } else r = snd_mixer_selem_get_capture_dB(me, c, &value); } else r = -1; } if (r < 0) continue; VALGRIND_MAKE_MEM_DEFINED(&value, sizeof(value)); f = from_alsa_dB(value); } else { long value = 0; if (e->direction == PA_ALSA_DIRECTION_OUTPUT) { if (snd_mixer_selem_has_playback_channel(me, c)) r = snd_mixer_selem_get_playback_volume(me, c, &value); else r = -1; } else { if (snd_mixer_selem_has_capture_channel(me, c)) r = snd_mixer_selem_get_capture_volume(me, c, &value); else r = -1; } if (r < 0) continue; f = from_alsa_volume(value, e->min_volume, e->max_volume); } for (k = 0; k < cm->channels; k++) if (e->masks[c][e->n_channels-1] & PA_CHANNEL_POSITION_MASK(cm->map[k])) if (v->values[k] < f) v->values[k] = f; mask |= e->masks[c][e->n_channels-1]; } for (k = 0; k < cm->channels; k++) if (!(mask & PA_CHANNEL_POSITION_MASK(cm->map[k]))) v->values[k] = PA_VOLUME_NORM; return 0; } int pa_alsa_path_get_volume(pa_alsa_path *p, snd_mixer_t *m, const pa_channel_map *cm, pa_cvolume *v) { pa_alsa_element *e; pa_assert(m); pa_assert(p); pa_assert(cm); pa_assert(v); if (!p->has_volume) return -1; pa_cvolume_reset(v, cm->channels); PA_LLIST_FOREACH(e, p->elements) { pa_cvolume ev; if (e->volume_use != PA_ALSA_VOLUME_MERGE) continue; pa_assert(!p->has_dB || e->has_dB); if (element_get_volume(e, m, cm, &ev) < 0) return -1; /* If we have no dB information all we can do is take the first element and leave */ if (!p->has_dB) { *v = ev; return 0; } pa_sw_cvolume_multiply(v, v, &ev); } return 0; } static int element_get_switch(pa_alsa_element *e, snd_mixer_t *m, bool *b) { snd_mixer_selem_id_t *sid; snd_mixer_elem_t *me; snd_mixer_selem_channel_id_t c; char buf[64]; pa_assert(m); pa_assert(e); pa_assert(b); SELEM_INIT(sid, &e->alsa_id); if (!(me = snd_mixer_find_selem(m, sid))) { pa_alsa_mixer_id_to_string(buf, sizeof(buf), &e->alsa_id); pa_log_warn("Element %s seems to have disappeared.", buf); return -1; } /* We return muted if at least one channel is muted */ for (c = 0; c <= SND_MIXER_SCHN_LAST; c++) { int r; int value = 0; if (e->direction == PA_ALSA_DIRECTION_OUTPUT) { if (snd_mixer_selem_has_playback_channel(me, c)) r = snd_mixer_selem_get_playback_switch(me, c, &value); else r = -1; } else { if (snd_mixer_selem_has_capture_channel(me, c)) r = snd_mixer_selem_get_capture_switch(me, c, &value); else r = -1; } if (r < 0) continue; if (!value) { *b = false; return 0; } } *b = true; return 0; } int pa_alsa_path_get_mute(pa_alsa_path *p, snd_mixer_t *m, bool *muted) { pa_alsa_element *e; pa_assert(m); pa_assert(p); pa_assert(muted); if (!p->has_mute) return -1; PA_LLIST_FOREACH(e, p->elements) { bool b; if (e->switch_use != PA_ALSA_SWITCH_MUTE) continue; if (element_get_switch(e, m, &b) < 0) return -1; if (!b) { *muted = true; return 0; } } *muted = false; return 0; } /* Finds the closest item in db_fix->db_values and returns the corresponding * step. *db_value is replaced with the value from the db_values table. * Rounding is done based on the rounding parameter: -1 means rounding down and * +1 means rounding up. */ static long decibel_fix_get_step(pa_alsa_decibel_fix *db_fix, long *db_value, int rounding) { unsigned i = 0; unsigned max_i = 0; pa_assert(db_fix); pa_assert(db_value); pa_assert(rounding != 0); max_i = db_fix->max_step - db_fix->min_step; if (rounding > 0) { for (i = 0; i < max_i; i++) { if (db_fix->db_values[i] >= *db_value) break; } } else { for (i = 0; i < max_i; i++) { if (db_fix->db_values[i + 1] > *db_value) break; } } *db_value = db_fix->db_values[i]; return i + db_fix->min_step; } /* Same as snd_mixer_selem_ask_playback/capture_dB_vol(), but if the result is muted, * round volume up instead. */ static int element_ask_unmuted_dB_vol(snd_mixer_elem_t *me, pa_alsa_direction_t d, long value_dB, int rounding, long *alsa_val) { int r = -1; long val, dB, base_val; pa_assert(me); if (d == PA_ALSA_DIRECTION_OUTPUT) { if ((r = snd_mixer_selem_ask_playback_dB_vol(me, value_dB, rounding, &val)) < 0) return r; if ((r = snd_mixer_selem_ask_playback_vol_dB(me, val, &dB)) < 0) return r; base_val = val; if (dB <= ALSA_DB_MUTED && value_dB > ALSA_DB_MUTED) if ((r = snd_mixer_selem_ask_playback_dB_vol(me, value_dB, +1, &val)) < 0) return r; *alsa_val = val; } else { if ((r = snd_mixer_selem_ask_capture_dB_vol(me, value_dB, rounding, &val)) < 0) return r; if ((r = snd_mixer_selem_ask_capture_vol_dB(me, val, &dB)) < 0) return r; base_val = val; if (dB <= ALSA_DB_MUTED && value_dB > ALSA_DB_MUTED) if ((r = snd_mixer_selem_ask_capture_dB_vol(me, value_dB, +1, &val)) < 0) return r; *alsa_val = val; } if (val != base_val) pa_log_debug("Volume rounded to mute: %ld -> %ld (dB/100) rounding:%d, correcting vol:%ld -> %ld", value_dB, dB, rounding, base_val, val); return r; } /* Alsa lib documentation says for snd_mixer_selem_set_playback_dB() direction argument, * that "-1 = accurate or first below, 0 = accurate, 1 = accurate or first above". * But even with accurate nearest dB volume step is not selected, so that is why we need * this function. Returns 0 and nearest selectable volume in *value_dB on success or * negative error code if fails. */ static int element_get_nearest_alsa_dB(snd_mixer_elem_t *me, snd_mixer_selem_channel_id_t c, pa_alsa_direction_t d, long *value_dB) { long alsa_val; long value_high; long value_low; int r = -1; pa_assert(me); pa_assert(value_dB); if (d == PA_ALSA_DIRECTION_OUTPUT) { if ((r = snd_mixer_selem_ask_playback_dB_vol(me, *value_dB, +1, &alsa_val)) >= 0) r = snd_mixer_selem_ask_playback_vol_dB(me, alsa_val, &value_high); if (r < 0) return r; if (value_high == *value_dB) return r; if ((r = snd_mixer_selem_ask_playback_dB_vol(me, *value_dB, -1, &alsa_val)) >= 0) r = snd_mixer_selem_ask_playback_vol_dB(me, alsa_val, &value_low); } else { if ((r = snd_mixer_selem_ask_capture_dB_vol(me, *value_dB, +1, &alsa_val)) >= 0) r = snd_mixer_selem_ask_capture_vol_dB(me, alsa_val, &value_high); if (r < 0) return r; if (value_high == *value_dB) return r; if ((r = snd_mixer_selem_ask_capture_dB_vol(me, *value_dB, -1, &alsa_val)) >= 0) r = snd_mixer_selem_ask_capture_vol_dB(me, alsa_val, &value_low); } if (r < 0) return r; if (labs(value_high - *value_dB) < labs(value_low - *value_dB)) *value_dB = value_high; else *value_dB = value_low; return r; } static int element_set_volume(pa_alsa_element *e, snd_mixer_t *m, const pa_channel_map *cm, pa_cvolume *v, bool deferred_volume, bool write_to_hw) { snd_mixer_selem_id_t *sid; pa_cvolume rv; snd_mixer_elem_t *me; snd_mixer_selem_channel_id_t c; pa_channel_position_mask_t mask = 0; char buf[64]; unsigned k; pa_assert(m); pa_assert(e); pa_assert(cm); pa_assert(v); pa_assert(pa_cvolume_compatible_with_channel_map(v, cm)); SELEM_INIT(sid, &e->alsa_id); if (!(me = snd_mixer_find_selem(m, sid))) { pa_alsa_mixer_id_to_string(buf, sizeof(buf), &e->alsa_id); pa_log_warn("Element %s seems to have disappeared.", buf); return -1; } pa_cvolume_mute(&rv, cm->channels); for (c = 0; c <= SND_MIXER_SCHN_LAST; c++) { int r; pa_volume_t f = PA_VOLUME_MUTED; bool found = false; for (k = 0; k < cm->channels; k++) if (e->masks[c][e->n_channels-1] & PA_CHANNEL_POSITION_MASK(cm->map[k])) { found = true; if (v->values[k] > f) f = v->values[k]; } if (!found) { /* Hmm, so this channel does not exist in the volume * struct, so let's bind it to the overall max of the * volume. */ f = pa_cvolume_max(v); } if (e->has_dB) { long value = to_alsa_dB(f); int rounding; if (e->volume_limit >= 0 && value > (e->max_dB * 100)) value = (long) (e->max_dB * 100); if (e->direction == PA_ALSA_DIRECTION_OUTPUT) { /* If we call set_playback_volume() without checking first * if the channel is available, ALSA behaves very * strangely and doesn't fail the call */ if (snd_mixer_selem_has_playback_channel(me, c)) { rounding = +1; if (e->db_fix) { if (write_to_hw) r = snd_mixer_selem_set_playback_volume(me, c, decibel_fix_get_step(e->db_fix, &value, rounding)); else { decibel_fix_get_step(e->db_fix, &value, rounding); r = 0; } } else { long alsa_val; if (write_to_hw) { if (deferred_volume) { if ((r = element_get_nearest_alsa_dB(me, c, PA_ALSA_DIRECTION_OUTPUT, &value)) >= 0) r = snd_mixer_selem_set_playback_dB(me, c, value, 0); } else { if ((r = element_ask_unmuted_dB_vol(me, e->direction, value, rounding, &alsa_val)) >= 0) if ((r = snd_mixer_selem_set_playback_volume(me, c, alsa_val)) >= 0) r = snd_mixer_selem_get_playback_dB(me, c, &value); } } else { if ((r = element_ask_unmuted_dB_vol(me, e->direction, value, rounding, &alsa_val)) >= 0) r = snd_mixer_selem_ask_playback_vol_dB(me, alsa_val, &value); } } } else r = -1; } else { if (snd_mixer_selem_has_capture_channel(me, c)) { rounding = -1; if (e->db_fix) { if (write_to_hw) r = snd_mixer_selem_set_capture_volume(me, c, decibel_fix_get_step(e->db_fix, &value, rounding)); else { decibel_fix_get_step(e->db_fix, &value, rounding); r = 0; } } else { long alsa_val; if (write_to_hw) { if (deferred_volume) { if ((r = element_get_nearest_alsa_dB(me, c, PA_ALSA_DIRECTION_INPUT, &value)) >= 0) r = snd_mixer_selem_set_capture_dB(me, c, value, 0); } else { if ((r = element_ask_unmuted_dB_vol(me, e->direction, value, rounding, &alsa_val)) >= 0) if ((r = snd_mixer_selem_set_capture_volume(me, c, alsa_val)) >= 0) r = snd_mixer_selem_ask_capture_vol_dB(me, alsa_val, &value); } } else { if ((r = element_ask_unmuted_dB_vol(me, e->direction, value, rounding, &alsa_val)) >= 0) r = snd_mixer_selem_ask_capture_vol_dB(me, alsa_val, &value); } } } else r = -1; } if (r < 0) continue; f = from_alsa_dB(value); } else { long value; value = to_alsa_volume(f, e->min_volume, e->max_volume); if (e->direction == PA_ALSA_DIRECTION_OUTPUT) { if (snd_mixer_selem_has_playback_channel(me, c)) { if ((r = snd_mixer_selem_set_playback_volume(me, c, value)) >= 0) r = snd_mixer_selem_get_playback_volume(me, c, &value); } else r = -1; } else { if (snd_mixer_selem_has_capture_channel(me, c)) { if ((r = snd_mixer_selem_set_capture_volume(me, c, value)) >= 0) r = snd_mixer_selem_get_capture_volume(me, c, &value); } else r = -1; } if (r < 0) continue; f = from_alsa_volume(value, e->min_volume, e->max_volume); } for (k = 0; k < cm->channels; k++) if (e->masks[c][e->n_channels-1] & PA_CHANNEL_POSITION_MASK(cm->map[k])) if (rv.values[k] < f) rv.values[k] = f; mask |= e->masks[c][e->n_channels-1]; } for (k = 0; k < cm->channels; k++) if (!(mask & PA_CHANNEL_POSITION_MASK(cm->map[k]))) rv.values[k] = PA_VOLUME_NORM; *v = rv; return 0; } int pa_alsa_path_set_volume(pa_alsa_path *p, snd_mixer_t *m, const pa_channel_map *cm, pa_cvolume *v, bool deferred_volume, bool write_to_hw) { pa_alsa_element *e; pa_cvolume rv; pa_assert(m); pa_assert(p); pa_assert(cm); pa_assert(v); pa_assert(pa_cvolume_compatible_with_channel_map(v, cm)); if (!p->has_volume) return -1; rv = *v; /* Remaining adjustment */ pa_cvolume_reset(v, cm->channels); /* Adjustment done */ PA_LLIST_FOREACH(e, p->elements) { pa_cvolume ev; if (e->volume_use != PA_ALSA_VOLUME_MERGE) continue; pa_assert(!p->has_dB || e->has_dB); ev = rv; if (element_set_volume(e, m, cm, &ev, deferred_volume, write_to_hw) < 0) return -1; if (!p->has_dB) { *v = ev; return 0; } pa_sw_cvolume_multiply(v, v, &ev); pa_sw_cvolume_divide(&rv, &rv, &ev); } return 0; } static int element_set_switch(pa_alsa_element *e, snd_mixer_t *m, bool b) { snd_mixer_elem_t *me; snd_mixer_selem_id_t *sid; char buf[64]; int r; pa_assert(m); pa_assert(e); SELEM_INIT(sid, &e->alsa_id); if (!(me = snd_mixer_find_selem(m, sid))) { pa_alsa_mixer_id_to_string(buf, sizeof(buf), &e->alsa_id); pa_log_warn("Element %s seems to have disappeared.", buf); return -1; } if (e->direction == PA_ALSA_DIRECTION_OUTPUT) r = snd_mixer_selem_set_playback_switch_all(me, b); else r = snd_mixer_selem_set_capture_switch_all(me, b); if (r < 0) { pa_alsa_mixer_id_to_string(buf, sizeof(buf), &e->alsa_id); pa_log_warn("Failed to set switch of %s: %s", buf, pa_alsa_strerror(errno)); } return r; } int pa_alsa_path_set_mute(pa_alsa_path *p, snd_mixer_t *m, bool muted) { pa_alsa_element *e; pa_assert(m); pa_assert(p); if (!p->has_mute) return -1; PA_LLIST_FOREACH(e, p->elements) { if (e->switch_use != PA_ALSA_SWITCH_MUTE) continue; if (element_set_switch(e, m, !muted) < 0) return -1; } return 0; } /* Depending on whether e->volume_use is _OFF, _ZERO or _CONSTANT, this * function sets all channels of the volume element to e->min_volume, 0 dB or * e->constant_volume. */ static int element_set_constant_volume(pa_alsa_element *e, snd_mixer_t *m) { snd_mixer_elem_t *me = NULL; snd_mixer_selem_id_t *sid = NULL; int r = 0; long volume = -1; bool volume_set = false; char buf[64]; pa_assert(m); pa_assert(e); SELEM_INIT(sid, &e->alsa_id); if (!(me = snd_mixer_find_selem(m, sid))) { pa_alsa_mixer_id_to_string(buf, sizeof(buf), &e->alsa_id); pa_log_warn("Element %s seems to have disappeared.", buf); return -1; } switch (e->volume_use) { case PA_ALSA_VOLUME_OFF: volume = e->min_volume; volume_set = true; break; case PA_ALSA_VOLUME_ZERO: if (e->db_fix) { long dB = 0; volume = decibel_fix_get_step(e->db_fix, &dB, (e->direction == PA_ALSA_DIRECTION_OUTPUT ? +1 : -1)); volume_set = true; } break; case PA_ALSA_VOLUME_CONSTANT: volume = e->constant_volume; volume_set = true; break; default: pa_assert_not_reached(); } if (volume_set) { if (e->direction == PA_ALSA_DIRECTION_OUTPUT) r = snd_mixer_selem_set_playback_volume_all(me, volume); else r = snd_mixer_selem_set_capture_volume_all(me, volume); } else { int rounding = (e->direction == PA_ALSA_DIRECTION_OUTPUT) ? +1 : -1; pa_assert(e->volume_use == PA_ALSA_VOLUME_ZERO); pa_assert(!e->db_fix); r = element_ask_unmuted_dB_vol(me, e->direction, 0, rounding, &volume); if (r >= 0) { if (e->direction == PA_ALSA_DIRECTION_OUTPUT) r = snd_mixer_selem_set_playback_volume_all(me, volume); else r = snd_mixer_selem_set_capture_volume_all(me, volume); } } if (r < 0) { pa_alsa_mixer_id_to_string(buf, sizeof(buf), &e->alsa_id); pa_log_warn("Failed to set volume of %s: %s", buf, pa_alsa_strerror(errno)); } return r; } int pa_alsa_path_select(pa_alsa_path *p, pa_alsa_setting *s, snd_mixer_t *m, bool device_is_muted) { pa_alsa_element *e; int r = 0; pa_assert(m); pa_assert(p); pa_log_info("Activating path %s", p->name); pa_alsa_path_dump(p); /* First turn on hw mute if available, to avoid noise * when setting the mixer controls. */ if (p->mute_during_activation) { PA_LLIST_FOREACH(e, p->elements) { if (e->switch_use == PA_ALSA_SWITCH_MUTE) /* If the muting fails here, that's not a critical problem for * selecting a path, so we ignore the return value. * element_set_switch() will print a warning anyway, so this * won't be a silent failure either. */ (void) element_set_switch(e, m, false); } } PA_LLIST_FOREACH(e, p->elements) { switch (e->switch_use) { case PA_ALSA_SWITCH_OFF: r = element_set_switch(e, m, false); break; case PA_ALSA_SWITCH_ON: r = element_set_switch(e, m, true); break; case PA_ALSA_SWITCH_MUTE: case PA_ALSA_SWITCH_IGNORE: case PA_ALSA_SWITCH_SELECT: r = 0; break; } if (r < 0) return -1; switch (e->volume_use) { case PA_ALSA_VOLUME_OFF: case PA_ALSA_VOLUME_ZERO: case PA_ALSA_VOLUME_CONSTANT: r = element_set_constant_volume(e, m); break; case PA_ALSA_VOLUME_MERGE: case PA_ALSA_VOLUME_IGNORE: r = 0; break; } if (r < 0) return -1; } if (s) setting_select(s, m); /* Finally restore hw mute to the device mute status. */ if (p->mute_during_activation) { PA_LLIST_FOREACH(e, p->elements) { if (e->switch_use == PA_ALSA_SWITCH_MUTE) { if (element_set_switch(e, m, !device_is_muted) < 0) return -1; } } } return 0; } static int check_required(pa_alsa_element *e, snd_mixer_elem_t *me) { bool has_switch; bool has_enumeration; bool has_volume; pa_assert(e); pa_assert(me); if (e->direction == PA_ALSA_DIRECTION_OUTPUT) { has_switch = snd_mixer_selem_has_playback_switch(me) || (e->direction_try_other && snd_mixer_selem_has_capture_switch(me)); } else { has_switch = snd_mixer_selem_has_capture_switch(me) || (e->direction_try_other && snd_mixer_selem_has_playback_switch(me)); } if (e->direction == PA_ALSA_DIRECTION_OUTPUT) { has_volume = snd_mixer_selem_has_playback_volume(me) || (e->direction_try_other && snd_mixer_selem_has_capture_volume(me)); } else { has_volume = snd_mixer_selem_has_capture_volume(me) || (e->direction_try_other && snd_mixer_selem_has_playback_volume(me)); } has_enumeration = snd_mixer_selem_is_enumerated(me); if ((e->required == PA_ALSA_REQUIRED_SWITCH && !has_switch) || (e->required == PA_ALSA_REQUIRED_VOLUME && !has_volume) || (e->required == PA_ALSA_REQUIRED_ENUMERATION && !has_enumeration)) return -1; if (e->required == PA_ALSA_REQUIRED_ANY && !(has_switch || has_volume || has_enumeration)) return -1; if ((e->required_absent == PA_ALSA_REQUIRED_SWITCH && has_switch) || (e->required_absent == PA_ALSA_REQUIRED_VOLUME && has_volume) || (e->required_absent == PA_ALSA_REQUIRED_ENUMERATION && has_enumeration)) return -1; if (e->required_absent == PA_ALSA_REQUIRED_ANY && (has_switch || has_volume || has_enumeration)) return -1; if (e->required_any != PA_ALSA_REQUIRED_IGNORE) { switch (e->required_any) { case PA_ALSA_REQUIRED_VOLUME: e->path->req_any_present |= (e->volume_use != PA_ALSA_VOLUME_IGNORE); break; case PA_ALSA_REQUIRED_SWITCH: e->path->req_any_present |= (e->switch_use != PA_ALSA_SWITCH_IGNORE); break; case PA_ALSA_REQUIRED_ENUMERATION: e->path->req_any_present |= (e->enumeration_use != PA_ALSA_ENUMERATION_IGNORE); break; case PA_ALSA_REQUIRED_ANY: e->path->req_any_present |= (e->volume_use != PA_ALSA_VOLUME_IGNORE) || (e->switch_use != PA_ALSA_SWITCH_IGNORE) || (e->enumeration_use != PA_ALSA_ENUMERATION_IGNORE); break; default: pa_assert_not_reached(); } } if (e->enumeration_use == PA_ALSA_ENUMERATION_SELECT) { pa_alsa_option *o; PA_LLIST_FOREACH(o, e->options) { e->path->req_any_present |= (o->required_any != PA_ALSA_REQUIRED_IGNORE) && (o->alsa_idx >= 0); if (o->required != PA_ALSA_REQUIRED_IGNORE && o->alsa_idx < 0) return -1; if (o->required_absent != PA_ALSA_REQUIRED_IGNORE && o->alsa_idx >= 0) return -1; } } return 0; } static int element_ask_vol_dB(snd_mixer_elem_t *me, pa_alsa_direction_t dir, long value, long *dBvalue) { if (dir == PA_ALSA_DIRECTION_OUTPUT) return snd_mixer_selem_ask_playback_vol_dB(me, value, dBvalue); else return snd_mixer_selem_ask_capture_vol_dB(me, value, dBvalue); } static bool element_probe_volume(pa_alsa_element *e, snd_mixer_elem_t *me) { long min_dB = 0, max_dB = 0; int r; bool is_mono; pa_channel_position_t p; char buf[64]; if (e->direction == PA_ALSA_DIRECTION_OUTPUT) { if (!snd_mixer_selem_has_playback_volume(me)) { if (e->direction_try_other && snd_mixer_selem_has_capture_volume(me)) e->direction = PA_ALSA_DIRECTION_INPUT; else return false; } } else { if (!snd_mixer_selem_has_capture_volume(me)) { if (e->direction_try_other && snd_mixer_selem_has_playback_volume(me)) e->direction = PA_ALSA_DIRECTION_OUTPUT; else return false; } } e->direction_try_other = false; if (e->direction == PA_ALSA_DIRECTION_OUTPUT) r = snd_mixer_selem_get_playback_volume_range(me, &e->min_volume, &e->max_volume); else r = snd_mixer_selem_get_capture_volume_range(me, &e->min_volume, &e->max_volume); if (r < 0) { pa_alsa_mixer_id_to_string(buf, sizeof(buf), &e->alsa_id); pa_log_warn("Failed to get volume range of %s: %s", buf, pa_alsa_strerror(r)); return false; } if (e->min_volume >= e->max_volume) { pa_alsa_mixer_id_to_string(buf, sizeof(buf), &e->alsa_id); pa_log_warn("Your kernel driver is broken for element %s: it reports a volume range from %li to %li which makes no sense.", buf, e->min_volume, e->max_volume); return false; } if (e->volume_use == PA_ALSA_VOLUME_CONSTANT && (e->min_volume > e->constant_volume || e->max_volume < e->constant_volume)) { pa_alsa_mixer_id_to_string(buf, sizeof(buf), &e->alsa_id); pa_log_warn("Constant volume %li configured for element %s, but the available range is from %li to %li.", e->constant_volume, buf, e->min_volume, e->max_volume); return false; } if (e->db_fix && ((e->min_volume > e->db_fix->min_step) || (e->max_volume < e->db_fix->max_step))) { pa_alsa_mixer_id_to_string(buf, sizeof(buf), &e->alsa_id); pa_log_warn("The step range of the decibel fix for element %s (%li-%li) doesn't fit to the " "real hardware range (%li-%li). Disabling the decibel fix.", buf, e->db_fix->min_step, e->db_fix->max_step, e->min_volume, e->max_volume); decibel_fix_free(e->db_fix); e->db_fix = NULL; } if (e->db_fix) { e->has_dB = true; e->min_volume = e->db_fix->min_step; e->max_volume = e->db_fix->max_step; min_dB = e->db_fix->db_values[0]; max_dB = e->db_fix->db_values[e->db_fix->max_step - e->db_fix->min_step]; } else if (e->direction == PA_ALSA_DIRECTION_OUTPUT) e->has_dB = snd_mixer_selem_get_playback_dB_range(me, &min_dB, &max_dB) >= 0; else e->has_dB = snd_mixer_selem_get_capture_dB_range(me, &min_dB, &max_dB) >= 0; /* Assume decibel data to be incorrect if max_dB is negative and dB range is * suspiciously small (< 10 dB). This can happen eg. if USB device is using volume * values as arbitrary scale ignoring USB standard on their meaning. */ if (e->has_dB && max_dB < 0 && SPA_ABS(max_dB - min_dB) < 10*100 && !e->db_fix) { pa_alsa_mixer_id_to_string(buf, sizeof(buf), &e->alsa_id); pa_log_warn("The decibel volume range for element %s (%0.2f dB to %0.2f dB) has negative maximum " "and suspiciously small range. " "Disabling the decibel range.", buf, min_dB/100.0, max_dB/100.0); e->has_dB = false; } /* Check that the kernel driver returns consistent limits with * both _get_*_dB_range() and _ask_*_vol_dB(). */ if (e->has_dB && !e->db_fix) { long min_dB_checked = 0; long max_dB_checked = 0; if (element_ask_vol_dB(me, e->direction, e->min_volume, &min_dB_checked) < 0) { pa_alsa_mixer_id_to_string(buf, sizeof(buf), &e->alsa_id); pa_log_warn("Failed to query the dB value for %s at volume level %li", buf, e->min_volume); return false; } if (element_ask_vol_dB(me, e->direction, e->max_volume, &max_dB_checked) < 0) { pa_alsa_mixer_id_to_string(buf, sizeof(buf), &e->alsa_id); pa_log_warn("Failed to query the dB value for %s at volume level %li", buf, e->max_volume); return false; } if (min_dB != min_dB_checked || max_dB != max_dB_checked) { pa_alsa_mixer_id_to_string(buf, sizeof(buf), &e->alsa_id); pa_log_warn("Your kernel driver is broken: the reported dB range for %s (from %0.2f dB to %0.2f dB) " "doesn't match the dB values at minimum and maximum volume levels: %0.2f dB at level %li, " "%0.2f dB at level %li.", buf, min_dB / 100.0, max_dB / 100.0, min_dB_checked / 100.0, e->min_volume, max_dB_checked / 100.0, e->max_volume); return false; } } if (e->has_dB) { e->min_dB = ((double) min_dB) / 100.0; e->max_dB = ((double) max_dB) / 100.0; if (min_dB >= max_dB) { pa_assert(!e->db_fix); pa_log_warn("Your kernel driver is broken: it reports a volume range from %0.2f dB to %0.2f dB which makes no sense.", e->min_dB, e->max_dB); e->has_dB = false; } } if (e->volume_limit >= 0) { if (e->volume_limit <= e->min_volume || e->volume_limit > e->max_volume) { pa_alsa_mixer_id_to_string(buf, sizeof(buf), &e->alsa_id); pa_log_warn("Volume limit for element %s of path %s is invalid: %li isn't within the valid range " "%li-%li. The volume limit is ignored.", buf, e->path->name, e->volume_limit, e->min_volume + 1, e->max_volume); } else { e->max_volume = e->volume_limit; if (e->has_dB) { if (e->db_fix) { e->db_fix->max_step = e->max_volume; e->max_dB = ((double) e->db_fix->db_values[e->db_fix->max_step - e->db_fix->min_step]) / 100.0; } else if (element_ask_vol_dB(me, e->direction, e->max_volume, &max_dB) < 0) { pa_alsa_mixer_id_to_string(buf, sizeof(buf), &e->alsa_id); pa_log_warn("Failed to get dB value of %s: %s", buf, pa_alsa_strerror(r)); e->has_dB = false; } else e->max_dB = ((double) max_dB) / 100.0; } } } if (e->direction == PA_ALSA_DIRECTION_OUTPUT) is_mono = snd_mixer_selem_is_playback_mono(me) > 0; else is_mono = snd_mixer_selem_is_capture_mono(me) > 0; if (is_mono) { e->n_channels = 1; if ((e->override_map & (1 << (e->n_channels-1))) && e->masks[SND_MIXER_SCHN_MONO][e->n_channels-1] == 0) { pa_log_warn("Override map for mono element %s is invalid, ignoring override map", e->path->name); e->override_map &= ~(1 << (e->n_channels-1)); } if (!(e->override_map & (1 << (e->n_channels-1)))) { for (p = PA_CHANNEL_POSITION_FRONT_LEFT; p < PA_CHANNEL_POSITION_MAX; p++) { if (alsa_channel_ids[p] == SND_MIXER_SCHN_UNKNOWN) continue; e->masks[alsa_channel_ids[p]][e->n_channels-1] = 0; } e->masks[SND_MIXER_SCHN_MONO][e->n_channels-1] = PA_CHANNEL_POSITION_MASK_ALL; } e->merged_mask = e->masks[SND_MIXER_SCHN_MONO][e->n_channels-1]; return true; } e->n_channels = 0; for (p = PA_CHANNEL_POSITION_FRONT_LEFT; p < PA_CHANNEL_POSITION_MAX; p++) { if (alsa_channel_ids[p] == SND_MIXER_SCHN_UNKNOWN) continue; if (e->direction == PA_ALSA_DIRECTION_OUTPUT) e->n_channels += snd_mixer_selem_has_playback_channel(me, alsa_channel_ids[p]) > 0; else e->n_channels += snd_mixer_selem_has_capture_channel(me, alsa_channel_ids[p]) > 0; } if (e->n_channels <= 0) { pa_alsa_mixer_id_to_string(buf, sizeof(buf), &e->alsa_id); pa_log_warn("Volume element %s with no channels?", buf); return false; } else if (e->n_channels > POSITION_MASK_CHANNELS) { /* FIXME: In some places code like this is used: * * e->masks[alsa_channel_ids[p]][e->n_channels-1] * * The definition of e->masks is * * pa_channel_position_mask_t masks[SND_MIXER_SCHN_LAST + 1][POSITION_MASK_CHANNELS]; * * Since the array size is fixed at POSITION_MASK_CHANNELS, we obviously * don't support elements with more than POSITION_MASK_CHANNELS * channels... */ pa_alsa_mixer_id_to_string(buf, sizeof(buf), &e->alsa_id); pa_log_warn("Volume element %s has %u channels. That's too much! I can't handle that!", buf, e->n_channels); return false; } retry: if (!(e->override_map & (1 << (e->n_channels-1)))) { for (p = PA_CHANNEL_POSITION_FRONT_LEFT; p < PA_CHANNEL_POSITION_MAX; p++) { bool has_channel; if (alsa_channel_ids[p] == SND_MIXER_SCHN_UNKNOWN) continue; if (e->direction == PA_ALSA_DIRECTION_OUTPUT) has_channel = snd_mixer_selem_has_playback_channel(me, alsa_channel_ids[p]) > 0; else has_channel = snd_mixer_selem_has_capture_channel(me, alsa_channel_ids[p]) > 0; e->masks[alsa_channel_ids[p]][e->n_channels-1] = has_channel ? PA_CHANNEL_POSITION_MASK(p) : 0; } } e->merged_mask = 0; for (p = PA_CHANNEL_POSITION_FRONT_LEFT; p < PA_CHANNEL_POSITION_MAX; p++) { if (alsa_channel_ids[p] == SND_MIXER_SCHN_UNKNOWN) continue; e->merged_mask |= e->masks[alsa_channel_ids[p]][e->n_channels-1]; } if (e->merged_mask == 0) { if (!(e->override_map & (1 << (e->n_channels-1)))) { pa_log_warn("Channel map for element %s is invalid", e->path->name); return false; } pa_log_warn("Override map for element %s has empty result, ignoring override map", e->path->name); e->override_map &= ~(1 << (e->n_channels-1)); goto retry; } return true; } static int element_probe(pa_alsa_element *e, snd_mixer_t *m) { snd_mixer_selem_id_t *sid; snd_mixer_elem_t *me; pa_assert(m); pa_assert(e); pa_assert(e->path); SELEM_INIT(sid, &e->alsa_id); if (!(me = snd_mixer_find_selem(m, sid))) { if (e->required != PA_ALSA_REQUIRED_IGNORE) return -1; e->switch_use = PA_ALSA_SWITCH_IGNORE; e->volume_use = PA_ALSA_VOLUME_IGNORE; e->enumeration_use = PA_ALSA_ENUMERATION_IGNORE; return 0; } if (e->switch_use != PA_ALSA_SWITCH_IGNORE) { if (e->direction == PA_ALSA_DIRECTION_OUTPUT) { if (!snd_mixer_selem_has_playback_switch(me)) { if (e->direction_try_other && snd_mixer_selem_has_capture_switch(me)) e->direction = PA_ALSA_DIRECTION_INPUT; else e->switch_use = PA_ALSA_SWITCH_IGNORE; } } else { if (!snd_mixer_selem_has_capture_switch(me)) { if (e->direction_try_other && snd_mixer_selem_has_playback_switch(me)) e->direction = PA_ALSA_DIRECTION_OUTPUT; else e->switch_use = PA_ALSA_SWITCH_IGNORE; } } if (e->switch_use != PA_ALSA_SWITCH_IGNORE) e->direction_try_other = false; } if (!element_probe_volume(e, me)) e->volume_use = PA_ALSA_VOLUME_IGNORE; if (e->switch_use == PA_ALSA_SWITCH_SELECT) { pa_alsa_option *o; PA_LLIST_FOREACH(o, e->options) o->alsa_idx = pa_streq(o->alsa_name, "on") ? 1 : 0; } else if (e->enumeration_use == PA_ALSA_ENUMERATION_SELECT) { int n; pa_alsa_option *o; if ((n = snd_mixer_selem_get_enum_items(me)) < 0) { pa_log("snd_mixer_selem_get_enum_items() failed: %s", pa_alsa_strerror(n)); return -1; } PA_LLIST_FOREACH(o, e->options) { int i; for (i = 0; i < n; i++) { char buf[128]; if (snd_mixer_selem_get_enum_item_name(me, i, sizeof(buf), buf) < 0) continue; if (!pa_streq(buf, o->alsa_name)) continue; o->alsa_idx = i; } } } if (check_required(e, me) < 0) return -1; return 0; } static int jack_probe(pa_alsa_jack *j, pa_alsa_mapping *mapping, snd_mixer_t *m) { bool has_control; pa_assert(j); pa_assert(j->path); if (j->append_pcm_to_name) { char *new_name; if (!mapping) { /* This could also be an assertion, because this should never * happen. At the time of writing, mapping can only be NULL when * module-alsa-sink/source synthesizes a path, and those * synthesized paths never have any jacks, so jack_probe() should * never be called with a NULL mapping. */ pa_log("Jack %s: append_pcm_to_name is set, but mapping is NULL. Can't use this jack.", j->name); return -1; } new_name = pa_sprintf_malloc("%s,pcm=%i Jack", j->name, mapping->hw_device_index); pa_xfree(j->alsa_id.name); j->alsa_id.name = new_name; j->append_pcm_to_name = false; } has_control = pa_alsa_mixer_find_card(m, &j->alsa_id, 0) != NULL; pa_alsa_jack_set_has_control(j, has_control); if (j->has_control) { if (j->required_absent != PA_ALSA_REQUIRED_IGNORE) return -1; if (j->required_any != PA_ALSA_REQUIRED_IGNORE) j->path->req_any_present = true; } else { if (j->required != PA_ALSA_REQUIRED_IGNORE) return -1; } return 0; } pa_alsa_element * pa_alsa_element_get(pa_alsa_path *p, const char *section, bool prefixed) { pa_alsa_element *e; char *name; int index; pa_assert(p); pa_assert(section); if (prefixed) { if (!pa_startswith(section, "Element ")) return NULL; section += 8; } /* This is not an element section, but an enum section? */ if (strchr(section, ':')) return NULL; name = alloca(strlen(section) + 1); if (alsa_id_decode(section, name, &index)) return NULL; if (p->last_element && pa_streq(p->last_element->alsa_id.name, name) && p->last_element->alsa_id.index == index) return p->last_element; PA_LLIST_FOREACH(e, p->elements) if (pa_streq(e->alsa_id.name, name) && e->alsa_id.index == index) goto finish; e = pa_xnew0(pa_alsa_element, 1); e->path = p; e->alsa_id.name = pa_xstrdup(name); e->alsa_id.index = index; e->direction = p->direction; e->volume_limit = -1; PA_LLIST_INSERT_AFTER(pa_alsa_element, p->elements, p->last_element, e); finish: p->last_element = e; return e; } static pa_alsa_jack* jack_get(pa_alsa_path *p, const char *section) { pa_alsa_jack *j; char *name; int index; if (!pa_startswith(section, "Jack ")) return NULL; section += 5; name = alloca(strlen(section) + 1); if (alsa_id_decode(section, name, &index)) return NULL; if (p->last_jack && pa_streq(p->last_jack->name, name) && p->last_jack->alsa_id.index == index) return p->last_jack; PA_LLIST_FOREACH(j, p->jacks) if (pa_streq(j->name, name) && j->alsa_id.index == index) goto finish; j = pa_alsa_jack_new(p, NULL, name, index); PA_LLIST_INSERT_AFTER(pa_alsa_jack, p->jacks, p->last_jack, j); finish: p->last_jack = j; return j; } static pa_alsa_option* option_get(pa_alsa_path *p, const char *section) { char *en, *name; const char *on; pa_alsa_option *o; pa_alsa_element *e; size_t len; int index; if (!pa_startswith(section, "Option ")) return NULL; section += 7; /* This is not an enum section, but an element section? */ if (!(on = strchr(section, ':'))) return NULL; len = on - section; en = alloca(len + 1); strncpy(en, section, len); en[len] = '\0'; name = alloca(strlen(en) + 1); if (alsa_id_decode(en, name, &index)) return NULL; on++; if (p->last_option && pa_streq(p->last_option->element->alsa_id.name, name) && p->last_option->element->alsa_id.index == index && pa_streq(p->last_option->alsa_name, on)) { return p->last_option; } pa_assert_se(e = pa_alsa_element_get(p, en, false)); PA_LLIST_FOREACH(o, e->options) if (pa_streq(o->alsa_name, on)) goto finish; o = pa_xnew0(pa_alsa_option, 1); o->element = e; o->alsa_name = pa_xstrdup(on); o->alsa_idx = -1; if (p->last_option && p->last_option->element == e) PA_LLIST_INSERT_AFTER(pa_alsa_option, e->options, p->last_option, o); else PA_LLIST_PREPEND(pa_alsa_option, e->options, o); finish: p->last_option = o; return o; } static int element_parse_switch(pa_config_parser_state *state) { pa_alsa_path *p; pa_alsa_element *e; pa_assert(state); p = state->userdata; if (!(e = pa_alsa_element_get(p, state->section, true))) { pa_log("[%s:%u] Switch makes no sense in '%s'", state->filename, state->lineno, state->section); return -1; } if (pa_streq(state->rvalue, "ignore")) e->switch_use = PA_ALSA_SWITCH_IGNORE; else if (pa_streq(state->rvalue, "mute")) e->switch_use = PA_ALSA_SWITCH_MUTE; else if (pa_streq(state->rvalue, "off")) e->switch_use = PA_ALSA_SWITCH_OFF; else if (pa_streq(state->rvalue, "on")) e->switch_use = PA_ALSA_SWITCH_ON; else if (pa_streq(state->rvalue, "select")) e->switch_use = PA_ALSA_SWITCH_SELECT; else { pa_log("[%s:%u] Switch invalid of '%s'", state->filename, state->lineno, state->section); return -1; } return 0; } static int element_parse_volume(pa_config_parser_state *state) { pa_alsa_path *p; pa_alsa_element *e; pa_assert(state); p = state->userdata; if (!(e = pa_alsa_element_get(p, state->section, true))) { pa_log("[%s:%u] Volume makes no sense in '%s'", state->filename, state->lineno, state->section); return -1; } if (pa_streq(state->rvalue, "ignore")) e->volume_use = PA_ALSA_VOLUME_IGNORE; else if (pa_streq(state->rvalue, "merge")) e->volume_use = PA_ALSA_VOLUME_MERGE; else if (pa_streq(state->rvalue, "off")) e->volume_use = PA_ALSA_VOLUME_OFF; else if (pa_streq(state->rvalue, "zero")) e->volume_use = PA_ALSA_VOLUME_ZERO; else { uint32_t constant; if (pa_atou(state->rvalue, &constant) >= 0) { e->volume_use = PA_ALSA_VOLUME_CONSTANT; e->constant_volume = constant; } else { pa_log("[%s:%u] Volume invalid of '%s'", state->filename, state->lineno, state->section); return -1; } } return 0; } static int element_parse_enumeration(pa_config_parser_state *state) { pa_alsa_path *p; pa_alsa_element *e; pa_assert(state); p = state->userdata; if (!(e = pa_alsa_element_get(p, state->section, true))) { pa_log("[%s:%u] Enumeration makes no sense in '%s'", state->filename, state->lineno, state->section); return -1; } if (pa_streq(state->rvalue, "ignore")) e->enumeration_use = PA_ALSA_ENUMERATION_IGNORE; else if (pa_streq(state->rvalue, "select")) e->enumeration_use = PA_ALSA_ENUMERATION_SELECT; else { pa_log("[%s:%u] Enumeration invalid of '%s'", state->filename, state->lineno, state->section); return -1; } return 0; } static int parse_type(pa_config_parser_state *state) { struct device_port_types { const char *name; pa_device_port_type_t type; } device_port_types[] = { { "unknown", PA_DEVICE_PORT_TYPE_UNKNOWN }, { "aux", PA_DEVICE_PORT_TYPE_AUX }, { "speaker", PA_DEVICE_PORT_TYPE_SPEAKER }, { "headphones", PA_DEVICE_PORT_TYPE_HEADPHONES }, { "line", PA_DEVICE_PORT_TYPE_LINE }, { "mic", PA_DEVICE_PORT_TYPE_MIC }, { "headset", PA_DEVICE_PORT_TYPE_HEADSET }, { "handset", PA_DEVICE_PORT_TYPE_HANDSET }, { "earpiece", PA_DEVICE_PORT_TYPE_EARPIECE }, { "spdif", PA_DEVICE_PORT_TYPE_SPDIF }, { "hdmi", PA_DEVICE_PORT_TYPE_HDMI }, { "tv", PA_DEVICE_PORT_TYPE_TV }, { "radio", PA_DEVICE_PORT_TYPE_RADIO }, { "video", PA_DEVICE_PORT_TYPE_VIDEO }, { "usb", PA_DEVICE_PORT_TYPE_USB }, { "bluetooth", PA_DEVICE_PORT_TYPE_BLUETOOTH }, { "portable", PA_DEVICE_PORT_TYPE_PORTABLE }, { "handsfree", PA_DEVICE_PORT_TYPE_HANDSFREE }, { "car", PA_DEVICE_PORT_TYPE_CAR }, { "hifi", PA_DEVICE_PORT_TYPE_HIFI }, { "phone", PA_DEVICE_PORT_TYPE_PHONE }, { "network", PA_DEVICE_PORT_TYPE_NETWORK }, { "analog", PA_DEVICE_PORT_TYPE_ANALOG }, }; pa_alsa_path *path; unsigned int idx; path = state->userdata; for (idx = 0; idx < PA_ELEMENTSOF(device_port_types); idx++) if (pa_streq(state->rvalue, device_port_types[idx].name)) { path->device_port_type = device_port_types[idx].type; return 0; } pa_log("[%s:%u] Invalid value for option 'type': %s", state->filename, state->lineno, state->rvalue); return -1; } static int parse_eld_device(pa_config_parser_state *state) { pa_alsa_path *path; uint32_t eld_device; path = state->userdata; if (pa_atou(state->rvalue, &eld_device) >= 0) { path->autodetect_eld_device = false; path->eld_device = eld_device; return 0; } if (pa_streq(state->rvalue, "auto")) { path->autodetect_eld_device = true; path->eld_device = -1; return 0; } pa_log("[%s:%u] Invalid value for option 'eld-device': %s", state->filename, state->lineno, state->rvalue); return -1; } static int option_parse_priority(pa_config_parser_state *state) { pa_alsa_path *p; pa_alsa_option *o; uint32_t prio; pa_assert(state); p = state->userdata; if (!(o = option_get(p, state->section))) { pa_log("[%s:%u] Priority makes no sense in '%s'", state->filename, state->lineno, state->section); return -1; } if (pa_atou(state->rvalue, &prio) < 0) { pa_log("[%s:%u] Priority invalid of '%s'", state->filename, state->lineno, state->section); return -1; } o->priority = prio; return 0; } static int option_parse_name(pa_config_parser_state *state) { pa_alsa_path *p; pa_alsa_option *o; pa_assert(state); p = state->userdata; if (!(o = option_get(p, state->section))) { pa_log("[%s:%u] Name makes no sense in '%s'", state->filename, state->lineno, state->section); return -1; } pa_xfree(o->name); o->name = pa_xstrdup(state->rvalue); return 0; } static int element_parse_required(pa_config_parser_state *state) { pa_alsa_path *p; pa_alsa_element *e; pa_alsa_option *o; pa_alsa_jack *j; pa_alsa_required_t req; pa_assert(state); p = state->userdata; e = pa_alsa_element_get(p, state->section, true); o = option_get(p, state->section); j = jack_get(p, state->section); if (!e && !o && !j) { pa_log("[%s:%u] Required makes no sense in '%s'", state->filename, state->lineno, state->section); return -1; } if (pa_streq(state->rvalue, "ignore")) req = PA_ALSA_REQUIRED_IGNORE; else if (pa_streq(state->rvalue, "switch") && e) req = PA_ALSA_REQUIRED_SWITCH; else if (pa_streq(state->rvalue, "volume") && e) req = PA_ALSA_REQUIRED_VOLUME; else if (pa_streq(state->rvalue, "enumeration")) req = PA_ALSA_REQUIRED_ENUMERATION; else if (pa_streq(state->rvalue, "any")) req = PA_ALSA_REQUIRED_ANY; else { pa_log("[%s:%u] Required invalid of '%s'", state->filename, state->lineno, state->section); return -1; } if (pa_streq(state->lvalue, "required-absent")) { if (e) e->required_absent = req; if (o) o->required_absent = req; if (j) j->required_absent = req; } else if (pa_streq(state->lvalue, "required-any")) { if (e) { e->required_any = req; e->path->has_req_any |= (req != PA_ALSA_REQUIRED_IGNORE); } if (o) { o->required_any = req; o->element->path->has_req_any |= (req != PA_ALSA_REQUIRED_IGNORE); } if (j) { j->required_any = req; j->path->has_req_any |= (req != PA_ALSA_REQUIRED_IGNORE); } } else { if (e) e->required = req; if (o) o->required = req; if (j) j->required = req; } return 0; } static int element_parse_direction(pa_config_parser_state *state) { pa_alsa_path *p; pa_alsa_element *e; pa_assert(state); p = state->userdata; if (!(e = pa_alsa_element_get(p, state->section, true))) { pa_log("[%s:%u] Direction makes no sense in '%s'", state->filename, state->lineno, state->section); return -1; } if (pa_streq(state->rvalue, "playback")) e->direction = PA_ALSA_DIRECTION_OUTPUT; else if (pa_streq(state->rvalue, "capture")) e->direction = PA_ALSA_DIRECTION_INPUT; else { pa_log("[%s:%u] Direction invalid of '%s'", state->filename, state->lineno, state->section); return -1; } return 0; } static int element_parse_direction_try_other(pa_config_parser_state *state) { pa_alsa_path *p; pa_alsa_element *e; int yes; pa_assert(state); p = state->userdata; if (!(e = pa_alsa_element_get(p, state->section, true))) { pa_log("[%s:%u] Direction makes no sense in '%s'", state->filename, state->lineno, state->section); return -1; } if ((yes = pa_parse_boolean(state->rvalue)) < 0) { pa_log("[%s:%u] Direction invalid of '%s'", state->filename, state->lineno, state->section); return -1; } e->direction_try_other = !!yes; return 0; } static int element_parse_volume_limit(pa_config_parser_state *state) { pa_alsa_path *p; pa_alsa_element *e; long volume_limit; pa_assert(state); p = state->userdata; if (!(e = pa_alsa_element_get(p, state->section, true))) { pa_log("[%s:%u] volume-limit makes no sense in '%s'", state->filename, state->lineno, state->section); return -1; } if (pa_atol(state->rvalue, &volume_limit) < 0 || volume_limit < 0) { pa_log("[%s:%u] Invalid value for volume-limit", state->filename, state->lineno); return -1; } e->volume_limit = volume_limit; return 0; } static unsigned int parse_channel_position(const char *m) { pa_channel_position_t p; if ((p = pa_channel_position_from_string(m)) == PA_CHANNEL_POSITION_INVALID) return SND_MIXER_SCHN_UNKNOWN; return alsa_channel_ids[p]; } static pa_channel_position_mask_t parse_mask(const char *m) { pa_channel_position_mask_t v; if (pa_streq(m, "all-left")) v = PA_CHANNEL_POSITION_MASK_LEFT; else if (pa_streq(m, "all-right")) v = PA_CHANNEL_POSITION_MASK_RIGHT; else if (pa_streq(m, "all-center")) v = PA_CHANNEL_POSITION_MASK_CENTER; else if (pa_streq(m, "all-front")) v = PA_CHANNEL_POSITION_MASK_FRONT; else if (pa_streq(m, "all-rear")) v = PA_CHANNEL_POSITION_MASK_REAR; else if (pa_streq(m, "all-side")) v = PA_CHANNEL_POSITION_MASK_SIDE_OR_TOP_CENTER; else if (pa_streq(m, "all-top")) v = PA_CHANNEL_POSITION_MASK_TOP; else if (pa_streq(m, "all-no-lfe")) v = PA_CHANNEL_POSITION_MASK_ALL ^ PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_LFE); else if (pa_streq(m, "all")) v = PA_CHANNEL_POSITION_MASK_ALL; else { pa_channel_position_t p; if ((p = pa_channel_position_from_string(m)) == PA_CHANNEL_POSITION_INVALID) return 0; v = PA_CHANNEL_POSITION_MASK(p); } return v; } static int element_parse_override_map(pa_config_parser_state *state) { pa_alsa_path *p; pa_alsa_element *e; const char *split_state = NULL; char *s; unsigned i = 0; int channel_count = 0; char *n; pa_assert(state); p = state->userdata; if (!(e = pa_alsa_element_get(p, state->section, true))) { pa_log("[%s:%u] Override map makes no sense in '%s'", state->filename, state->lineno, state->section); return -1; } s = strstr(state->lvalue, "."); if (s) { pa_atoi(s + 1, &channel_count); if (channel_count < 1 || channel_count > POSITION_MASK_CHANNELS) { pa_log("[%s:%u] Override map index '%s' invalid in '%s'", state->filename, state->lineno, state->lvalue, state->section); return 0; } } else { pa_log("[%s:%u] Invalid override map syntax '%s' in '%s'", state->filename, state->lineno, state->lvalue, state->section); return -1; } while ((n = pa_split(state->rvalue, ",", &split_state))) { pa_channel_position_mask_t m; snd_mixer_selem_channel_id_t channel_position; if (i >= (unsigned)channel_count) { pa_log("[%s:%u] Invalid override map size (>%d) in '%s'", state->filename, state->lineno, channel_count, state->section); pa_xfree(n); return -1; } channel_position = alsa_channel_positions[i]; if (!*n) m = 0; else { s = strstr(n, ":"); if (s) { *s = '\0'; s++; channel_position = parse_channel_position(n); if (channel_position == SND_MIXER_SCHN_UNKNOWN) { pa_log("[%s:%u] Override map position '%s' invalid in '%s'", state->filename, state->lineno, n, state->section); pa_xfree(n); return -1; } } if ((m = parse_mask(s ? s : n)) == 0) { pa_log("[%s:%u] Override map '%s' invalid in '%s'", state->filename, state->lineno, s ? s : n, state->section); pa_xfree(n); return -1; } } if (e->masks[channel_position][channel_count-1]) { pa_log("[%s:%u] Override map '%s' duplicate position '%s' in '%s'", state->filename, state->lineno, s ? s : n, snd_mixer_selem_channel_name(channel_position), state->section); pa_xfree(n); return -1; } e->override_map |= (1 << (channel_count - 1)); e->masks[channel_position][channel_count-1] = m; pa_xfree(n); i++; } return 0; } static int jack_parse_state(pa_config_parser_state *state) { pa_alsa_path *p; pa_alsa_jack *j; pa_available_t pa; pa_assert(state); p = state->userdata; if (!(j = jack_get(p, state->section))) { pa_log("[%s:%u] state makes no sense in '%s'", state->filename, state->lineno, state->section); return -1; } if (pa_streq(state->rvalue, "yes")) pa = PA_AVAILABLE_YES; else if (pa_streq(state->rvalue, "no")) pa = PA_AVAILABLE_NO; else if (pa_streq(state->rvalue, "unknown")) pa = PA_AVAILABLE_UNKNOWN; else { pa_log("[%s:%u] state must be 'yes', 'no' or 'unknown' in '%s'", state->filename, state->lineno, state->section); return -1; } if (pa_streq(state->lvalue, "state.unplugged")) j->state_unplugged = pa; else { j->state_plugged = pa; pa_assert(pa_streq(state->lvalue, "state.plugged")); } return 0; } static int jack_parse_append_pcm_to_name(pa_config_parser_state *state) { pa_alsa_path *path; pa_alsa_jack *jack; int b; pa_assert(state); path = state->userdata; if (!(jack = jack_get(path, state->section))) { pa_log("[%s:%u] Option 'append_pcm_to_name' not expected in section '%s'", state->filename, state->lineno, state->section); return -1; } b = pa_parse_boolean(state->rvalue); if (b < 0) { pa_log("[%s:%u] Invalid value for 'append_pcm_to_name': %s", state->filename, state->lineno, state->rvalue); return -1; } jack->append_pcm_to_name = b; return 0; } static int element_set_option(pa_alsa_element *e, snd_mixer_t *m, int alsa_idx) { snd_mixer_selem_id_t *sid; snd_mixer_elem_t *me; char buf[64]; int r; pa_assert(e); pa_assert(m); SELEM_INIT(sid, &e->alsa_id); if (!(me = snd_mixer_find_selem(m, sid))) { pa_alsa_mixer_id_to_string(buf, sizeof(buf), &e->alsa_id); pa_log_warn("Element %s seems to have disappeared.", buf); return -1; } if (e->switch_use == PA_ALSA_SWITCH_SELECT) { if (e->direction == PA_ALSA_DIRECTION_OUTPUT) r = snd_mixer_selem_set_playback_switch_all(me, alsa_idx); else r = snd_mixer_selem_set_capture_switch_all(me, alsa_idx); if (r < 0) { pa_alsa_mixer_id_to_string(buf, sizeof(buf), &e->alsa_id); pa_log_warn("Failed to set switch of %s: %s", buf, pa_alsa_strerror(errno)); } } else { pa_assert(e->enumeration_use == PA_ALSA_ENUMERATION_SELECT); if ((r = snd_mixer_selem_set_enum_item(me, 0, alsa_idx)) < 0) { pa_alsa_mixer_id_to_string(buf, sizeof(buf), &e->alsa_id); pa_log_warn("Failed to set enumeration of %s: %s", buf, pa_alsa_strerror(errno)); } } return r; } static int setting_select(pa_alsa_setting *s, snd_mixer_t *m) { pa_alsa_option *o; uint32_t idx; pa_assert(s); pa_assert(m); PA_IDXSET_FOREACH(o, s->options, idx) element_set_option(o->element, m, o->alsa_idx); return 0; } static int option_verify(pa_alsa_option *o) { static const struct description_map well_known_descriptions[] = { { "input", N_("Input") }, { "input-docking", N_("Docking Station Input") }, { "input-docking-microphone", N_("Docking Station Microphone") }, { "input-docking-linein", N_("Docking Station Line In") }, { "input-linein", N_("Line In") }, { "input-microphone", N_("Microphone") }, { "input-microphone-front", N_("Front Microphone") }, { "input-microphone-rear", N_("Rear Microphone") }, { "input-microphone-external", N_("External Microphone") }, { "input-microphone-internal", N_("Internal Microphone") }, { "input-radio", N_("Radio") }, { "input-video", N_("Video") }, { "input-agc-on", N_("Automatic Gain Control") }, { "input-agc-off", N_("No Automatic Gain Control") }, { "input-boost-on", N_("Boost") }, { "input-boost-off", N_("No Boost") }, { "output-amplifier-on", N_("Amplifier") }, { "output-amplifier-off", N_("No Amplifier") }, { "output-bass-boost-on", N_("Bass Boost") }, { "output-bass-boost-off", N_("No Bass Boost") }, { "output-speaker", N_("Speaker") }, { "output-headphones", N_("Headphones") } }; char buf[64]; pa_assert(o); if (!o->name) { pa_log("No name set for option %s", o->alsa_name); return -1; } if (o->element->enumeration_use != PA_ALSA_ENUMERATION_SELECT && o->element->switch_use != PA_ALSA_SWITCH_SELECT) { pa_alsa_mixer_id_to_string(buf, sizeof(buf), &o->element->alsa_id); pa_log("Element %s of option %s not set for select.", buf, o->name); return -1; } if (o->element->switch_use == PA_ALSA_SWITCH_SELECT && !pa_streq(o->alsa_name, "on") && !pa_streq(o->alsa_name, "off")) { pa_alsa_mixer_id_to_string(buf, sizeof(buf), &o->element->alsa_id); pa_log("Switch %s options need be named off or on ", buf); return -1; } if (!o->description) o->description = pa_xstrdup(lookup_description(o->name, well_known_descriptions, PA_ELEMENTSOF(well_known_descriptions))); if (!o->description) o->description = pa_xstrdup(o->name); return 0; } static int element_verify(pa_alsa_element *e) { pa_alsa_option *o; char buf[64]; pa_assert(e); // pa_log_debug("Element %s, path %s: r=%d, r-any=%d, r-abs=%d", e->alsa_name, e->path->name, e->required, e->required_any, e->required_absent); if ((e->required != PA_ALSA_REQUIRED_IGNORE && e->required == e->required_absent) || (e->required_any != PA_ALSA_REQUIRED_IGNORE && e->required_any == e->required_absent) || (e->required_absent == PA_ALSA_REQUIRED_ANY && e->required_any != PA_ALSA_REQUIRED_IGNORE) || (e->required_absent == PA_ALSA_REQUIRED_ANY && e->required != PA_ALSA_REQUIRED_IGNORE)) { pa_alsa_mixer_id_to_string(buf, sizeof(buf), &e->alsa_id); pa_log("Element %s cannot be required and absent at the same time.", buf); return -1; } if (e->switch_use == PA_ALSA_SWITCH_SELECT && e->enumeration_use == PA_ALSA_ENUMERATION_SELECT) { pa_alsa_mixer_id_to_string(buf, sizeof(buf), &e->alsa_id); pa_log("Element %s cannot set select for both switch and enumeration.", buf); return -1; } PA_LLIST_FOREACH(o, e->options) if (option_verify(o) < 0) return -1; return 0; } static int path_verify(pa_alsa_path *p) { static const struct description2_map well_known_descriptions[] = { { "analog-input", N_("Analog Input"), PA_DEVICE_PORT_TYPE_ANALOG }, { "analog-input-microphone", N_("Microphone"), PA_DEVICE_PORT_TYPE_MIC }, { "analog-input-microphone-front", N_("Front Microphone"), PA_DEVICE_PORT_TYPE_MIC }, { "analog-input-microphone-rear", N_("Rear Microphone"), PA_DEVICE_PORT_TYPE_MIC }, { "analog-input-microphone-dock", N_("Dock Microphone"), PA_DEVICE_PORT_TYPE_MIC }, { "analog-input-microphone-internal", N_("Internal Microphone"), PA_DEVICE_PORT_TYPE_MIC }, { "analog-input-microphone-headset", N_("Headset Microphone"), PA_DEVICE_PORT_TYPE_HEADSET }, { "analog-input-linein", N_("Line In"), PA_DEVICE_PORT_TYPE_LINE }, { "analog-input-radio", N_("Radio"), PA_DEVICE_PORT_TYPE_RADIO }, { "analog-input-video", N_("Video"), PA_DEVICE_PORT_TYPE_VIDEO }, { "analog-output", N_("Analog Output"), PA_DEVICE_PORT_TYPE_ANALOG }, { "analog-output-headphones", N_("Headphones"), PA_DEVICE_PORT_TYPE_HEADPHONES }, { "analog-output-headphones-2", N_("Headphones 2"), PA_DEVICE_PORT_TYPE_HEADPHONES }, { "analog-output-headphones-mono", N_("Headphones Mono Output"), PA_DEVICE_PORT_TYPE_HEADPHONES }, { "analog-output-lineout", N_("Line Out"), PA_DEVICE_PORT_TYPE_LINE }, { "analog-output-mono", N_("Analog Mono Output"), PA_DEVICE_PORT_TYPE_ANALOG }, { "analog-output-speaker", N_("Speakers"), PA_DEVICE_PORT_TYPE_SPEAKER }, { "hdmi-output", N_("HDMI / DisplayPort"), PA_DEVICE_PORT_TYPE_HDMI }, { "iec958-stereo-output", N_("Digital Output (S/PDIF)"), PA_DEVICE_PORT_TYPE_SPDIF }, { "iec958-stereo-input", N_("Digital Input (S/PDIF)"), PA_DEVICE_PORT_TYPE_SPDIF }, { "multichannel-input", N_("Multichannel Input"), PA_DEVICE_PORT_TYPE_LINE }, { "multichannel-output", N_("Multichannel Output"), PA_DEVICE_PORT_TYPE_LINE }, { "steelseries-arctis-output-game-common", N_("Game Output"), PA_DEVICE_PORT_TYPE_HEADSET }, { "steelseries-arctis-output-chat-common", N_("Chat Output"), PA_DEVICE_PORT_TYPE_HEADSET }, { "analog-chat-output", N_("Chat Output"), PA_DEVICE_PORT_TYPE_HEADSET }, { "analog-chat-input", N_("Chat Input"), PA_DEVICE_PORT_TYPE_HEADSET }, { "virtual-surround-7.1", N_("Virtual Surround 7.1"), PA_DEVICE_PORT_TYPE_HEADPHONES }, }; pa_alsa_element *e; const char *key = p->description_key ? p->description_key : p->name; const struct description2_map *map = lookup_description2(key, well_known_descriptions, PA_ELEMENTSOF(well_known_descriptions)); pa_assert(p); PA_LLIST_FOREACH(e, p->elements) if (element_verify(e) < 0) return -1; if (map) { if (p->device_port_type == PA_DEVICE_PORT_TYPE_UNKNOWN) p->device_port_type = map->type; if (!p->description) p->description = pa_xstrdup(_(map->description)); } if (!p->description) { if (p->description_key) pa_log_warn("Path %s: Unrecognized description key: %s", p->name, p->description_key); p->description = pa_xstrdup(p->name); } return 0; } pa_alsa_path* pa_alsa_path_new(const char *paths_dir, const char *fname, pa_alsa_direction_t direction) { pa_alsa_path *p; char *fn; int r; const char *n; bool mute_during_activation = false; pa_config_item items[] = { /* [General] */ { "priority", pa_config_parse_unsigned, NULL, "General" }, { "description-key", pa_config_parse_string, NULL, "General" }, { "description", pa_config_parse_string, NULL, "General" }, { "mute-during-activation", pa_config_parse_bool, NULL, "General" }, { "type", parse_type, NULL, "General" }, { "eld-device", parse_eld_device, NULL, "General" }, /* [Option ...] */ { "priority", option_parse_priority, NULL, NULL }, { "name", option_parse_name, NULL, NULL }, /* [Jack ...] */ { "state.plugged", jack_parse_state, NULL, NULL }, { "state.unplugged", jack_parse_state, NULL, NULL }, { "append-pcm-to-name", jack_parse_append_pcm_to_name, NULL, NULL }, /* [Element ...] */ { "switch", element_parse_switch, NULL, NULL }, { "volume", element_parse_volume, NULL, NULL }, { "enumeration", element_parse_enumeration, NULL, NULL }, { "override-map.1", element_parse_override_map, NULL, NULL }, { "override-map.2", element_parse_override_map, NULL, NULL }, { "override-map.3", element_parse_override_map, NULL, NULL }, { "override-map.4", element_parse_override_map, NULL, NULL }, { "override-map.5", element_parse_override_map, NULL, NULL }, { "override-map.6", element_parse_override_map, NULL, NULL }, { "override-map.7", element_parse_override_map, NULL, NULL }, { "override-map.8", element_parse_override_map, NULL, NULL }, #if POSITION_MASK_CHANNELS > 8 #error "Add override-map.9+ definitions" #endif /* ... later on we might add override-map.3 and so on here ... */ { "required", element_parse_required, NULL, NULL }, { "required-any", element_parse_required, NULL, NULL }, { "required-absent", element_parse_required, NULL, NULL }, { "direction", element_parse_direction, NULL, NULL }, { "direction-try-other", element_parse_direction_try_other, NULL, NULL }, { "volume-limit", element_parse_volume_limit, NULL, NULL }, { NULL, NULL, NULL, NULL } }; pa_assert(fname); p = pa_xnew0(pa_alsa_path, 1); n = pa_path_get_filename(fname); p->name = pa_xstrndup(n, strcspn(n, ".")); p->proplist = pa_proplist_new(); p->direction = direction; p->eld_device = -1; items[0].data = &p->priority; items[1].data = &p->description_key; items[2].data = &p->description; items[3].data = &mute_during_activation; fn = get_data_path(paths_dir, "paths", fname); pa_log_info("Loading path config: %s", fn); r = pa_config_parse(fn, NULL, items, p->proplist, false, p); pa_xfree(fn); if (r < 0) goto fail; p->mute_during_activation = mute_during_activation; if (path_verify(p) < 0) goto fail; if (p->description) { char *tmp = p->description; p->description = pa_xstrdup(_(tmp)); free(tmp); } return p; fail: pa_alsa_path_free(p); return NULL; } pa_alsa_path *pa_alsa_path_synthesize(const char *element, pa_alsa_direction_t direction) { pa_alsa_path *p; pa_alsa_element *e; char *name; int index; pa_assert(element); name = alloca(strlen(element) + 1); if (alsa_id_decode(element, name, &index)) return NULL; p = pa_xnew0(pa_alsa_path, 1); p->name = pa_xstrdup(element); p->direction = direction; p->proplist = pa_proplist_new(); e = pa_xnew0(pa_alsa_element, 1); e->path = p; e->alsa_id.name = pa_xstrdup(name); e->alsa_id.index = index; e->direction = direction; e->volume_limit = -1; e->switch_use = PA_ALSA_SWITCH_MUTE; e->volume_use = PA_ALSA_VOLUME_MERGE; PA_LLIST_PREPEND(pa_alsa_element, p->elements, e); p->last_element = e; return p; } static bool element_drop_unsupported(pa_alsa_element *e) { pa_alsa_option *o, *n; pa_assert(e); for (o = e->options; o; o = n) { n = o->next; if (o->alsa_idx < 0) { PA_LLIST_REMOVE(pa_alsa_option, e->options, o); option_free(o); } } return e->switch_use != PA_ALSA_SWITCH_IGNORE || e->volume_use != PA_ALSA_VOLUME_IGNORE || e->enumeration_use != PA_ALSA_ENUMERATION_IGNORE; } static void path_drop_unsupported(pa_alsa_path *p) { pa_alsa_element *e, *n; pa_assert(p); for (e = p->elements; e; e = n) { n = e->next; if (!element_drop_unsupported(e)) { PA_LLIST_REMOVE(pa_alsa_element, p->elements, e); element_free(e); } } } static void path_make_options_unique(pa_alsa_path *p) { pa_alsa_element *e; pa_alsa_option *o, *u; PA_LLIST_FOREACH(e, p->elements) { PA_LLIST_FOREACH(o, e->options) { unsigned i; char *m; for (u = o->next; u; u = u->next) if (pa_streq(u->name, o->name)) break; if (!u) continue; m = pa_xstrdup(o->name); /* OK, this name is not unique, hence let's rename */ for (i = 1, u = o; u; u = u->next) { char *nn, *nd; if (!pa_streq(u->name, m)) continue; nn = pa_sprintf_malloc("%s-%u", m, i); pa_xfree(u->name); u->name = nn; nd = pa_sprintf_malloc("%s %u", u->description, i); pa_xfree(u->description); u->description = nd; i++; } pa_xfree(m); } } } static bool element_create_settings(pa_alsa_element *e, pa_alsa_setting *template) { pa_alsa_option *o; for (; e; e = e->next) if (e->switch_use == PA_ALSA_SWITCH_SELECT || e->enumeration_use == PA_ALSA_ENUMERATION_SELECT) break; if (!e) return false; for (o = e->options; o; o = o->next) { pa_alsa_setting *s; if (template) { s = pa_xnewdup(pa_alsa_setting, template, 1); s->options = pa_idxset_copy(template->options, NULL); s->name = pa_sprintf_malloc("%s+%s", template->name, o->name); s->description = (template->description[0] && o->description[0]) ? pa_sprintf_malloc("%s / %s", template->description, o->description) : (template->description[0] ? pa_xstrdup(template->description) : pa_xstrdup(o->description)); s->priority = PA_MAX(template->priority, o->priority); } else { s = pa_xnew0(pa_alsa_setting, 1); s->options = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); s->name = pa_xstrdup(o->name); s->description = pa_xstrdup(o->description); s->priority = o->priority; } pa_idxset_put(s->options, o, NULL); if (element_create_settings(e->next, s)) /* This is not a leaf, so let's get rid of it */ setting_free(s); else { /* This is a leaf, so let's add it */ PA_LLIST_INSERT_AFTER(pa_alsa_setting, e->path->settings, e->path->last_setting, s); e->path->last_setting = s; } } return true; } static void path_create_settings(pa_alsa_path *p) { pa_assert(p); element_create_settings(p->elements, NULL); } int pa_alsa_path_probe(pa_alsa_path *p, pa_alsa_mapping *mapping, snd_mixer_t *m, bool ignore_dB) { pa_alsa_element *e; pa_alsa_jack *j; double min_dB[PA_CHANNEL_POSITION_MAX], max_dB[PA_CHANNEL_POSITION_MAX]; pa_channel_position_t t; pa_channel_position_mask_t path_volume_channels = 0; bool min_dB_set, max_dB_set; char buf[64]; pa_assert(p); pa_assert(m); if (p->probed) return p->supported ? 0 : -1; p->probed = true; pa_zero(min_dB); pa_zero(max_dB); pa_log_debug("Probing path '%s'", p->name); PA_LLIST_FOREACH(j, p->jacks) { pa_alsa_mixer_id_to_string(buf, sizeof(buf), &j->alsa_id); if (jack_probe(j, mapping, m) < 0) { p->supported = false; pa_log_debug("Probe of jack %s failed.", buf); return -1; } pa_log_debug("Probe of jack %s succeeded (%s)", buf, j->has_control ? "found!" : "not found"); } PA_LLIST_FOREACH(e, p->elements) { pa_alsa_mixer_id_to_string(buf, sizeof(buf), &e->alsa_id); if (element_probe(e, m) < 0) { p->supported = false; pa_log_debug("Probe of element %s failed.", buf); return -1; } pa_log_debug("Probe of element %s succeeded (volume=%d, switch=%d, enumeration=%d, has_dB=%d).", buf, e->volume_use, e->switch_use, e->enumeration_use, e->has_dB); if (ignore_dB) e->has_dB = false; if (e->volume_use == PA_ALSA_VOLUME_MERGE) { if (!p->has_volume) { p->min_volume = e->min_volume; p->max_volume = e->max_volume; } if (e->has_dB) { if (!p->has_volume) { for (t = 0; t < PA_CHANNEL_POSITION_MAX; t++) if (PA_CHANNEL_POSITION_MASK(t) & e->merged_mask) { min_dB[t] = e->min_dB; max_dB[t] = e->max_dB; path_volume_channels |= PA_CHANNEL_POSITION_MASK(t); } p->has_dB = true; } else { if (p->has_dB) { for (t = 0; t < PA_CHANNEL_POSITION_MAX; t++) if (PA_CHANNEL_POSITION_MASK(t) & e->merged_mask) { min_dB[t] += e->min_dB; max_dB[t] += e->max_dB; path_volume_channels |= PA_CHANNEL_POSITION_MASK(t); } } else { /* Hmm, there's another element before us * which cannot do dB volumes, so we we need * to 'neutralize' this slider */ e->volume_use = PA_ALSA_VOLUME_ZERO; pa_log_info("Zeroing volume of %s on path '%s'", buf, p->name); } } } else if (p->has_volume) { /* We can't use this volume, so let's ignore it */ e->volume_use = PA_ALSA_VOLUME_IGNORE; pa_log_info("Ignoring volume of %s on path '%s' (missing dB info)", buf, p->name); } p->has_volume = true; } if (e->switch_use == PA_ALSA_SWITCH_MUTE) p->has_mute = true; } if (p->has_req_any && !p->req_any_present) { p->supported = false; pa_log_debug("Skipping path '%s', none of required-any elements preset.", p->name); return -1; } path_drop_unsupported(p); path_make_options_unique(p); path_create_settings(p); p->supported = true; p->min_dB = INFINITY; min_dB_set = false; p->max_dB = -INFINITY; max_dB_set = false; for (t = 0; t < PA_CHANNEL_POSITION_MAX; t++) { if (path_volume_channels & PA_CHANNEL_POSITION_MASK(t)) { if (p->min_dB > min_dB[t]) { p->min_dB = min_dB[t]; min_dB_set = true; } if (p->max_dB < max_dB[t]) { p->max_dB = max_dB[t]; max_dB_set = true; } } } /* this is probably a wrong prediction, but it should be safe */ if (!min_dB_set) p->min_dB = -INFINITY; if (!max_dB_set) p->max_dB = 0; return 0; } void pa_alsa_setting_dump(pa_alsa_setting *s) { pa_assert(s); pa_log_debug("Setting %s (%s) priority=%u", s->name, pa_strnull(s->description), s->priority); } void pa_alsa_jack_dump(pa_alsa_jack *j) { pa_assert(j); pa_log_debug("Jack %s, alsa_name='%s', index='%d', detection %s", j->name, j->alsa_id.name, j->alsa_id.index, j->has_control ? "possible" : "unavailable"); } void pa_alsa_option_dump(pa_alsa_option *o) { pa_assert(o); pa_log_debug("Option %s (%s/%s) index=%i, priority=%u", o->alsa_name, pa_strnull(o->name), pa_strnull(o->description), o->alsa_idx, o->priority); } void pa_alsa_element_dump(pa_alsa_element *e) { char buf[64]; pa_alsa_option *o; pa_assert(e); pa_alsa_mixer_id_to_string(buf, sizeof(buf), &e->alsa_id); pa_log_debug("Element %s, direction=%i, switch=%i, volume=%i, volume_limit=%li, enumeration=%i, required=%i, required_any=%i, required_absent=%i, mask=0x%llx, n_channels=%u, override_map=%02x", buf, e->direction, e->switch_use, e->volume_use, e->volume_limit, e->enumeration_use, e->required, e->required_any, e->required_absent, (long long unsigned) e->merged_mask, e->n_channels, e->override_map); PA_LLIST_FOREACH(o, e->options) pa_alsa_option_dump(o); } void pa_alsa_path_dump(pa_alsa_path *p) { pa_alsa_element *e; pa_alsa_jack *j; pa_alsa_setting *s; pa_assert(p); pa_log_debug("Path %s (%s), direction=%i, priority=%u, probed=%s, supported=%s, has_mute=%s, has_volume=%s, " "has_dB=%s, min_volume=%li, max_volume=%li, min_dB=%g, max_dB=%g", p->name, pa_strnull(p->description), p->direction, p->priority, pa_yes_no(p->probed), pa_yes_no(p->supported), pa_yes_no(p->has_mute), pa_yes_no(p->has_volume), pa_yes_no(p->has_dB), p->min_volume, p->max_volume, p->min_dB, p->max_dB); PA_LLIST_FOREACH(e, p->elements) pa_alsa_element_dump(e); PA_LLIST_FOREACH(j, p->jacks) pa_alsa_jack_dump(j); PA_LLIST_FOREACH(s, p->settings) pa_alsa_setting_dump(s); } static void element_set_callback(pa_alsa_element *e, snd_mixer_t *m, snd_mixer_elem_callback_t cb, void *userdata) { snd_mixer_selem_id_t *sid; snd_mixer_elem_t *me; char buf[64]; pa_assert(e); pa_assert(m); pa_assert(cb); SELEM_INIT(sid, &e->alsa_id); if (!(me = snd_mixer_find_selem(m, sid))) { pa_alsa_mixer_id_to_string(buf, sizeof(buf), &e->alsa_id); pa_log_warn("Element %s seems to have disappeared.", buf); return; } snd_mixer_elem_set_callback(me, cb); snd_mixer_elem_set_callback_private(me, userdata); } void pa_alsa_path_set_callback(pa_alsa_path *p, snd_mixer_t *m, snd_mixer_elem_callback_t cb, void *userdata) { pa_alsa_element *e; pa_assert(p); pa_assert(m); pa_assert(cb); PA_LLIST_FOREACH(e, p->elements) element_set_callback(e, m, cb, userdata); } void pa_alsa_path_set_set_callback(pa_alsa_path_set *ps, snd_mixer_t *m, snd_mixer_elem_callback_t cb, void *userdata) { pa_alsa_path *p; void *state; pa_assert(ps); pa_assert(m); pa_assert(cb); PA_HASHMAP_FOREACH(p, ps->paths, state) pa_alsa_path_set_callback(p, m, cb, userdata); } static pa_alsa_path *profile_set_get_path(pa_alsa_profile_set *ps, const char *path_name) { pa_alsa_path *path; pa_assert(ps); pa_assert(path_name); if ((path = pa_hashmap_get(ps->output_paths, path_name))) return path; return pa_hashmap_get(ps->input_paths, path_name); } static void profile_set_add_path(pa_alsa_profile_set *ps, pa_alsa_path *path) { pa_assert(ps); pa_assert(path); switch (path->direction) { case PA_ALSA_DIRECTION_OUTPUT: pa_assert_se(pa_hashmap_put(ps->output_paths, path->name, path) >= 0); break; case PA_ALSA_DIRECTION_INPUT: pa_assert_se(pa_hashmap_put(ps->input_paths, path->name, path) >= 0); break; default: pa_assert_not_reached(); } } pa_alsa_path_set *pa_alsa_path_set_new(pa_alsa_mapping *m, pa_alsa_direction_t direction, const char *paths_dir) { pa_alsa_path_set *ps; char **pn = NULL, **en = NULL, **ie; pa_alsa_decibel_fix *db_fix; void *state, *state2; char name[64]; int index; pa_assert(m); pa_assert(m->profile_set); pa_assert(m->profile_set->decibel_fixes); pa_assert(direction == PA_ALSA_DIRECTION_OUTPUT || direction == PA_ALSA_DIRECTION_INPUT); if (m->direction != PA_ALSA_DIRECTION_ANY && m->direction != direction) return NULL; ps = pa_xnew0(pa_alsa_path_set, 1); ps->direction = direction; ps->paths = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); if (direction == PA_ALSA_DIRECTION_OUTPUT) pn = m->output_path_names; else pn = m->input_path_names; if (pn) { char **in; for (in = pn; *in; in++) { pa_alsa_path *p = NULL; bool duplicate = false; char **kn; for (kn = pn; kn < in; kn++) if (pa_streq(*kn, *in)) { duplicate = true; break; } if (duplicate) continue; p = profile_set_get_path(m->profile_set, *in); if (p && p->direction != direction) { pa_log("Configuration error: Path %s is used both as an input and as an output path.", p->name); goto fail; } if (!p) { char *fn = pa_sprintf_malloc("%s.conf", *in); p = pa_alsa_path_new(paths_dir, fn, direction); pa_xfree(fn); if (p) profile_set_add_path(m->profile_set, p); } if (p) pa_hashmap_put(ps->paths, p, p); } goto finish; } if (direction == PA_ALSA_DIRECTION_OUTPUT) en = m->output_element; else en = m->input_element; if (!en) goto fail; for (ie = en; *ie; ie++) { char **je; pa_alsa_path *p; p = pa_alsa_path_synthesize(*ie, direction); /* Mark all other passed elements for require-absent */ for (je = en; *je; je++) { pa_alsa_element *e; if (je == ie) continue; if (strlen(*je) + 1 >= sizeof(name)) { pa_log("Element identifier %s is too long!", *je); continue; } if (alsa_id_decode(*je, name, &index)) continue; e = pa_xnew0(pa_alsa_element, 1); e->path = p; e->alsa_id.name = pa_xstrdup(name); e->alsa_id.index = index; e->direction = direction; e->required_absent = PA_ALSA_REQUIRED_ANY; e->volume_limit = -1; PA_LLIST_INSERT_AFTER(pa_alsa_element, p->elements, p->last_element, e); p->last_element = e; } pa_hashmap_put(ps->paths, *ie, p); } finish: /* Assign decibel fixes to elements. */ PA_HASHMAP_FOREACH(db_fix, m->profile_set->decibel_fixes, state) { pa_alsa_path *p; PA_HASHMAP_FOREACH(p, ps->paths, state2) { pa_alsa_element *e; PA_LLIST_FOREACH(e, p->elements) { if (e->volume_use != PA_ALSA_VOLUME_IGNORE && pa_streq(db_fix->name, e->alsa_id.name) && db_fix->index == e->alsa_id.index) { /* The profile set that contains the dB fix may be freed * before the element, so we have to copy the dB fix * object. */ e->db_fix = pa_xnewdup(pa_alsa_decibel_fix, db_fix, 1); e->db_fix->profile_set = NULL; e->db_fix->key = pa_xstrdup(db_fix->key); e->db_fix->name = pa_xstrdup(db_fix->name); e->db_fix->db_values = pa_xmemdup(db_fix->db_values, (db_fix->max_step - db_fix->min_step + 1) * sizeof(long)); } } } } return ps; fail: if (ps) pa_alsa_path_set_free(ps); return NULL; } void pa_alsa_path_set_dump(pa_alsa_path_set *ps) { pa_alsa_path *p; void *state; pa_assert(ps); pa_log_debug("Path Set %p, direction=%i", (void*) ps, ps->direction); PA_HASHMAP_FOREACH(p, ps->paths, state) pa_alsa_path_dump(p); } static bool options_have_option(pa_alsa_option *options, const char *alsa_name) { pa_alsa_option *o; pa_assert(options); pa_assert(alsa_name); PA_LLIST_FOREACH(o, options) { if (pa_streq(o->alsa_name, alsa_name)) return true; } return false; } static bool enumeration_is_subset(pa_alsa_option *a_options, pa_alsa_option *b_options) { pa_alsa_option *oa, *ob; if (!a_options) return true; if (!b_options) return false; /* If there is an option A offers that B does not, then A is not a subset of B. */ PA_LLIST_FOREACH(oa, a_options) { bool found = false; PA_LLIST_FOREACH(ob, b_options) { if (pa_streq(oa->alsa_name, ob->alsa_name)) { found = true; break; } } if (!found) return false; } return true; } /** * Compares two elements to see if a is a subset of b */ static bool element_is_subset(pa_alsa_element *a, pa_alsa_element *b, snd_mixer_t *m) { char buf[64]; pa_assert(a); pa_assert(b); pa_assert(m); /* General rules: * Every state is a subset of itself (with caveats for volume_limits and options) * IGNORE is a subset of every other state */ /* Check the volume_use */ if (a->volume_use != PA_ALSA_VOLUME_IGNORE) { /* "Constant" is subset of "Constant" only when their constant values are equal */ if (a->volume_use == PA_ALSA_VOLUME_CONSTANT && b->volume_use == PA_ALSA_VOLUME_CONSTANT && a->constant_volume != b->constant_volume) return false; /* Different volume uses when b is not "Merge" means we are definitely not a subset */ if (a->volume_use != b->volume_use && b->volume_use != PA_ALSA_VOLUME_MERGE) return false; /* "Constant" is a subset of "Merge", if there is not a "volume-limit" in "Merge" below the actual constant. * "Zero" and "Off" are just special cases of "Constant" when comparing to "Merge" * "Merge" with a "volume-limit" is a subset of "Merge" without a "volume-limit" or with a higher "volume-limit" */ if (b->volume_use == PA_ALSA_VOLUME_MERGE && b->volume_limit >= 0) { long a_limit; if (a->volume_use == PA_ALSA_VOLUME_CONSTANT) a_limit = a->constant_volume; else if (a->volume_use == PA_ALSA_VOLUME_ZERO) { long dB = 0; int rounding = (a->direction == PA_ALSA_DIRECTION_OUTPUT ? +1 : -1); if (a->db_fix) { a_limit = decibel_fix_get_step(a->db_fix, &dB, rounding); } else { snd_mixer_selem_id_t *sid; snd_mixer_elem_t *me; SELEM_INIT(sid, &a->alsa_id); if (!(me = snd_mixer_find_selem(m, sid))) { pa_alsa_mixer_id_to_string(buf, sizeof(buf), &a->alsa_id); pa_log_warn("Element %s seems to have disappeared.", buf); return false; } if (element_ask_unmuted_dB_vol(me, a->direction, dB, rounding, &a_limit) < 0) return false; } } else if (a->volume_use == PA_ALSA_VOLUME_OFF) a_limit = a->min_volume; else if (a->volume_use == PA_ALSA_VOLUME_MERGE) a_limit = a->volume_limit; else pa_assert_not_reached(); if (a_limit > b->volume_limit) return false; } if (a->volume_use == PA_ALSA_VOLUME_MERGE) { int s; /* If override-maps are different, they're not subsets */ if (a->n_channels != b->n_channels) return false; for (s = 0; s <= SND_MIXER_SCHN_LAST; s++) if (a->masks[s][a->n_channels-1] != b->masks[s][b->n_channels-1]) { pa_alsa_mixer_id_to_string(buf, sizeof(buf), &a->alsa_id); pa_log_debug("Element %s is not a subset - mask a: 0x%" PRIx64 ", mask b: 0x%" PRIx64 ", at channel %d", buf, a->masks[s][a->n_channels-1], b->masks[s][b->n_channels-1], s); return false; } } } if (a->switch_use != PA_ALSA_SWITCH_IGNORE) { /* "On" is a subset of "Mute". * "Off" is a subset of "Mute". * "On" is a subset of "Select", if there is an "Option:On" in B. * "Off" is a subset of "Select", if there is an "Option:Off" in B. * "Select" is a subset of "Select", if they have the same options (is this always true?). */ if (a->switch_use != b->switch_use) { if (a->switch_use == PA_ALSA_SWITCH_SELECT || a->switch_use == PA_ALSA_SWITCH_MUTE || b->switch_use == PA_ALSA_SWITCH_OFF || b->switch_use == PA_ALSA_SWITCH_ON) return false; if (b->switch_use == PA_ALSA_SWITCH_SELECT) { if (a->switch_use == PA_ALSA_SWITCH_ON) { if (!options_have_option(b->options, "on")) return false; } else if (a->switch_use == PA_ALSA_SWITCH_OFF) { if (!options_have_option(b->options, "off")) return false; } } } else if (a->switch_use == PA_ALSA_SWITCH_SELECT) { if (!enumeration_is_subset(a->options, b->options)) return false; } } if (a->enumeration_use != PA_ALSA_ENUMERATION_IGNORE) { if (b->enumeration_use == PA_ALSA_ENUMERATION_IGNORE) return false; if (!enumeration_is_subset(a->options, b->options)) return false; } return true; } static void path_set_condense(pa_alsa_path_set *ps, snd_mixer_t *m) { pa_alsa_path *p; void *state; pa_assert(ps); pa_assert(m); /* If we only have one path, then don't bother */ if (pa_hashmap_size(ps->paths) < 2) return; PA_HASHMAP_FOREACH(p, ps->paths, state) { pa_alsa_path *p2; void *state2; PA_HASHMAP_FOREACH(p2, ps->paths, state2) { pa_alsa_element *ea, *eb; pa_alsa_jack *ja, *jb; bool is_subset = true; if (p == p2) continue; /* If a has a jack that b does not have, a is not a subset */ PA_LLIST_FOREACH(ja, p->jacks) { bool exists = false; if (!ja->has_control) continue; PA_LLIST_FOREACH(jb, p2->jacks) { if (jb->has_control && pa_streq(ja->alsa_id.name, jb->alsa_id.name) && (ja->alsa_id.index == jb->alsa_id.index) && (ja->state_plugged == jb->state_plugged) && (ja->state_unplugged == jb->state_unplugged)) { exists = true; break; } } if (!exists) { is_subset = false; break; } } /* Compare the elements of each set... */ PA_LLIST_FOREACH(ea, p->elements) { bool found_matching_element = false; if (!is_subset) break; PA_LLIST_FOREACH(eb, p2->elements) { if (pa_streq(ea->alsa_id.name, eb->alsa_id.name) && ea->alsa_id.index == eb->alsa_id.index) { found_matching_element = true; is_subset = element_is_subset(ea, eb, m); break; } } if (!found_matching_element) is_subset = false; } if (is_subset) { pa_log_debug("Removing path '%s' as it is a subset of '%s'.", p->name, p2->name); pa_hashmap_remove(ps->paths, p); break; } } } } static pa_alsa_path* path_set_find_path_by_description(pa_alsa_path_set *ps, const char* description, pa_alsa_path *ignore) { pa_alsa_path* p; void *state; PA_HASHMAP_FOREACH(p, ps->paths, state) if (p != ignore && pa_streq(p->description, description)) return p; return NULL; } static void path_set_make_path_descriptions_unique(pa_alsa_path_set *ps) { pa_alsa_path *p, *q; void *state, *state2; PA_HASHMAP_FOREACH(p, ps->paths, state) { unsigned i; char *old_description; q = path_set_find_path_by_description(ps, p->description, p); if (!q) continue; old_description = pa_xstrdup(p->description); /* OK, this description is not unique, hence let's rename */ i = 1; PA_HASHMAP_FOREACH(q, ps->paths, state2) { char *new_description; if (!pa_streq(q->description, old_description)) continue; new_description = pa_sprintf_malloc("%s %u", q->description, i); pa_xfree(q->description); q->description = new_description; i++; } pa_xfree(old_description); } } void pa_alsa_mapping_free(pa_alsa_mapping *m) { pa_assert(m); pa_xfree(m->name); pa_xfree(m->description); pa_xfree(m->description_key); pa_proplist_free(m->proplist); pa_xstrfreev(m->device_strings); pa_xstrfreev(m->input_path_names); pa_xstrfreev(m->output_path_names); pa_xstrfreev(m->input_element); pa_xstrfreev(m->output_element); if (m->input_path_set) pa_alsa_path_set_free(m->input_path_set); if (m->output_path_set) pa_alsa_path_set_free(m->output_path_set); pa_proplist_free(m->input_proplist); pa_proplist_free(m->output_proplist); pa_xfree(m->split); pa_assert(!m->input_pcm); pa_assert(!m->output_pcm); pa_alsa_ucm_mapping_context_free(&m->ucm_context); pa_xfree(m); } void pa_alsa_profile_free(pa_alsa_profile *p) { pa_assert(p); pa_xfree(p->name); pa_xfree(p->description); pa_xfree(p->description_key); pa_xfree(p->input_name); pa_xfree(p->output_name); pa_xstrfreev(p->input_mapping_names); pa_xstrfreev(p->output_mapping_names); if (p->input_mappings) pa_idxset_free(p->input_mappings, NULL); if (p->output_mappings) pa_idxset_free(p->output_mappings, NULL); pa_xfree(p); } void pa_alsa_profile_set_free(pa_alsa_profile_set *ps) { pa_assert(ps); if (ps->input_paths) pa_hashmap_free(ps->input_paths); if (ps->output_paths) pa_hashmap_free(ps->output_paths); if (ps->profiles) pa_hashmap_free(ps->profiles); if (ps->mappings) pa_hashmap_free(ps->mappings); if (ps->decibel_fixes) pa_hashmap_free(ps->decibel_fixes); pa_xfree(ps); } pa_alsa_mapping *pa_alsa_mapping_get(pa_alsa_profile_set *ps, const char *name) { pa_alsa_mapping *m; if (!pa_startswith(name, "Mapping ")) return NULL; name += 8; if ((m = pa_hashmap_get(ps->mappings, name))) return m; m = pa_xnew0(pa_alsa_mapping, 1); m->profile_set = ps; m->exact_channels = true; m->name = pa_xstrdup(name); pa_sample_spec_init(&m->sample_spec); pa_channel_map_init(&m->channel_map); m->proplist = pa_proplist_new(); m->hw_device_index = -1; m->input_proplist = pa_proplist_new(); m->output_proplist = pa_proplist_new(); pa_hashmap_put(ps->mappings, m->name, m); return m; } static pa_alsa_profile *profile_get(pa_alsa_profile_set *ps, const char *name) { pa_alsa_profile *p; if (!pa_startswith(name, "Profile ")) return NULL; name += 8; if ((p = pa_hashmap_get(ps->profiles, name))) return p; p = pa_xnew0(pa_alsa_profile, 1); p->profile_set = ps; p->name = pa_xstrdup(name); pa_hashmap_put(ps->profiles, p->name, p); return p; } static pa_alsa_decibel_fix *decibel_fix_get(pa_alsa_profile_set *ps, const char *alsa_id) { pa_alsa_decibel_fix *db_fix; char *name; int index; if (!pa_startswith(alsa_id, "DecibelFix ")) return NULL; alsa_id += 11; if ((db_fix = pa_hashmap_get(ps->decibel_fixes, alsa_id))) return db_fix; name = alloca(strlen(alsa_id) + 1); if (alsa_id_decode(alsa_id, name, &index)) return NULL; db_fix = pa_xnew0(pa_alsa_decibel_fix, 1); db_fix->profile_set = ps; db_fix->name = pa_xstrdup(name); db_fix->index = index; db_fix->key = pa_xstrdup(alsa_id); pa_hashmap_put(ps->decibel_fixes, db_fix->key, db_fix); return db_fix; } static int mapping_parse_device_strings(pa_config_parser_state *state) { pa_alsa_profile_set *ps; pa_alsa_mapping *m; pa_assert(state); ps = state->userdata; if (!(m = pa_alsa_mapping_get(ps, state->section))) { pa_log("[%s:%u] %s invalid in section %s", state->filename, state->lineno, state->lvalue, state->section); return -1; } pa_xstrfreev(m->device_strings); if (!(m->device_strings = pa_split_spaces_strv(state->rvalue))) { pa_log("[%s:%u] Device string list empty of '%s'", state->filename, state->lineno, state->section); return -1; } return 0; } static int mapping_parse_channel_map(pa_config_parser_state *state) { pa_alsa_profile_set *ps; pa_alsa_mapping *m; pa_assert(state); ps = state->userdata; if (!(m = pa_alsa_mapping_get(ps, state->section))) { pa_log("[%s:%u] %s invalid in section %s", state->filename, state->lineno, state->lvalue, state->section); return -1; } if (!(pa_channel_map_parse(&m->channel_map, state->rvalue))) { pa_log("[%s:%u] Channel map invalid of '%s'", state->filename, state->lineno, state->section); return -1; } return 0; } static int mapping_parse_paths(pa_config_parser_state *state) { pa_alsa_profile_set *ps; pa_alsa_mapping *m; pa_assert(state); ps = state->userdata; if (!(m = pa_alsa_mapping_get(ps, state->section))) { pa_log("[%s:%u] %s invalid in section %s", state->filename, state->lineno, state->lvalue, state->section); return -1; } if (pa_streq(state->lvalue, "paths-input")) { pa_xstrfreev(m->input_path_names); m->input_path_names = pa_split_spaces_strv(state->rvalue); } else { pa_xstrfreev(m->output_path_names); m->output_path_names = pa_split_spaces_strv(state->rvalue); } return 0; } static int mapping_parse_exact_channels(pa_config_parser_state *state) { pa_alsa_profile_set *ps; pa_alsa_mapping *m; int b; pa_assert(state); ps = state->userdata; if (!(m = pa_alsa_mapping_get(ps, state->section))) { pa_log("[%s:%u] %s invalid in section %s", state->filename, state->lineno, state->lvalue, state->section); return -1; } if ((b = pa_parse_boolean(state->rvalue)) < 0) { pa_log("[%s:%u] %s has invalid value '%s'", state->filename, state->lineno, state->lvalue, state->section); return -1; } m->exact_channels = b; return 0; } static int mapping_parse_element(pa_config_parser_state *state) { pa_alsa_profile_set *ps; pa_alsa_mapping *m; pa_assert(state); ps = state->userdata; if (!(m = pa_alsa_mapping_get(ps, state->section))) { pa_log("[%s:%u] %s invalid in section %s", state->filename, state->lineno, state->lvalue, state->section); return -1; } if (pa_streq(state->lvalue, "element-input")) { pa_xstrfreev(m->input_element); m->input_element = pa_split_spaces_strv(state->rvalue); } else { pa_xstrfreev(m->output_element); m->output_element = pa_split_spaces_strv(state->rvalue); } return 0; } static int mapping_parse_direction(pa_config_parser_state *state) { pa_alsa_profile_set *ps; pa_alsa_mapping *m; pa_assert(state); ps = state->userdata; if (!(m = pa_alsa_mapping_get(ps, state->section))) { pa_log("[%s:%u] Section name %s invalid.", state->filename, state->lineno, state->section); return -1; } if (pa_streq(state->rvalue, "input")) m->direction = PA_ALSA_DIRECTION_INPUT; else if (pa_streq(state->rvalue, "output")) m->direction = PA_ALSA_DIRECTION_OUTPUT; else if (pa_streq(state->rvalue, "any")) m->direction = PA_ALSA_DIRECTION_ANY; else { pa_log("[%s:%u] Direction %s invalid.", state->filename, state->lineno, state->rvalue); return -1; } return 0; } static int mapping_parse_description(pa_config_parser_state *state) { pa_alsa_profile_set *ps; pa_alsa_profile *p; pa_alsa_mapping *m; pa_assert(state); ps = state->userdata; if ((m = pa_alsa_mapping_get(ps, state->section))) { pa_xfree(m->description); m->description = pa_xstrdup(_(state->rvalue)); } else if ((p = profile_get(ps, state->section))) { pa_xfree(p->description); p->description = pa_xstrdup(_(state->rvalue)); } else { pa_log("[%s:%u] Section name %s invalid.", state->filename, state->lineno, state->section); return -1; } return 0; } static int mapping_parse_description_key(pa_config_parser_state *state) { pa_alsa_profile_set *ps; pa_alsa_profile *p; pa_alsa_mapping *m; pa_assert(state); ps = state->userdata; if ((m = pa_alsa_mapping_get(ps, state->section))) { pa_xfree(m->description_key); m->description_key = pa_xstrdup(state->rvalue); } else if ((p = profile_get(ps, state->section))) { pa_xfree(p->description_key); p->description_key = pa_xstrdup(state->rvalue); } else { pa_log("[%s:%u] Section name %s invalid.", state->filename, state->lineno, state->section); return -1; } return 0; } static int mapping_parse_priority(pa_config_parser_state *state) { pa_alsa_profile_set *ps; pa_alsa_profile *p; pa_alsa_mapping *m; uint32_t prio; pa_assert(state); ps = state->userdata; if (pa_atou(state->rvalue, &prio) < 0) { pa_log("[%s:%u] Priority invalid of '%s'", state->filename, state->lineno, state->section); return -1; } if ((m = pa_alsa_mapping_get(ps, state->section))) m->priority = prio; else if ((p = profile_get(ps, state->section))) p->priority = prio; else { pa_log("[%s:%u] Section name %s invalid.", state->filename, state->lineno, state->section); return -1; } return 0; } static int mapping_parse_fallback(pa_config_parser_state *state) { pa_alsa_profile_set *ps; pa_alsa_profile *p; pa_alsa_mapping *m; int k; pa_assert(state); ps = state->userdata; if ((k = pa_parse_boolean(state->rvalue)) < 0) { pa_log("[%s:%u] Fallback invalid of '%s'", state->filename, state->lineno, state->section); return -1; } if ((m = pa_alsa_mapping_get(ps, state->section))) m->fallback = k; else if ((p = profile_get(ps, state->section))) p->fallback_input = p->fallback_output = k; else { pa_log("[%s:%u] Section name %s invalid.", state->filename, state->lineno, state->section); return -1; } return 0; } static int mapping_parse_intended_roles(pa_config_parser_state *state) { pa_alsa_profile_set *ps; pa_alsa_mapping *m; pa_assert(state); ps = state->userdata; if (!(m = pa_alsa_mapping_get(ps, state->section))) { pa_log("[%s:%u] %s invalid in section %s", state->filename, state->lineno, state->lvalue, state->section); return -1; } pa_proplist_sets(m->proplist, PA_PROP_DEVICE_INTENDED_ROLES, state->rvalue); return 0; } static int profile_parse_mappings(pa_config_parser_state *state) { pa_alsa_profile_set *ps; pa_alsa_profile *p; pa_assert(state); ps = state->userdata; if (!(p = profile_get(ps, state->section))) { pa_log("[%s:%u] %s invalid in section %s", state->filename, state->lineno, state->lvalue, state->section); return -1; } if (pa_streq(state->lvalue, "input-mappings")) { pa_xstrfreev(p->input_mapping_names); p->input_mapping_names = pa_split_spaces_strv(state->rvalue); } else { pa_xstrfreev(p->output_mapping_names); p->output_mapping_names = pa_split_spaces_strv(state->rvalue); } return 0; } static int profile_parse_skip_probe(pa_config_parser_state *state) { pa_alsa_profile_set *ps; pa_alsa_profile *p; int b; pa_assert(state); ps = state->userdata; if (!(p = profile_get(ps, state->section))) { pa_log("[%s:%u] %s invalid in section %s", state->filename, state->lineno, state->lvalue, state->section); return -1; } if ((b = pa_parse_boolean(state->rvalue)) < 0) { pa_log("[%s:%u] Skip probe invalid of '%s'", state->filename, state->lineno, state->section); return -1; } p->supported = b; return 0; } static int decibel_fix_parse_db_values(pa_config_parser_state *state) { pa_alsa_profile_set *ps; pa_alsa_decibel_fix *db_fix; char **items; char *item; long *db_values; unsigned n = 8; /* Current size of the db_values table. */ unsigned min_step = 0; unsigned max_step = 0; unsigned i = 0; /* Index to the items table. */ unsigned prev_step = 0; double prev_db = 0; pa_assert(state); ps = state->userdata; if (!(db_fix = decibel_fix_get(ps, state->section))) { pa_log("[%s:%u] %s invalid in section %s", state->filename, state->lineno, state->lvalue, state->section); return -1; } if (!(items = pa_split_spaces_strv(state->rvalue))) { pa_log("[%s:%u] Value missing", state->filename, state->lineno); return -1; } db_values = pa_xnew(long, n); while ((item = items[i++])) { char *s = item; /* Step value string. */ char *d = item; /* dB value string. */ uint32_t step; double db; /* Move d forward until it points to a colon or to the end of the item. */ for (; *d && *d != ':'; ++d); if (d == s) { /* item started with colon. */ pa_log("[%s:%u] No step value found in %s", state->filename, state->lineno, item); goto fail; } if (!*d || !*(d + 1)) { /* No colon found, or it was the last character in item. */ pa_log("[%s:%u] No dB value found in %s", state->filename, state->lineno, item); goto fail; } /* pa_atou() needs a null-terminating string. Let's replace the colon * with a zero byte. */ *d++ = '\0'; if (pa_atou(s, &step) < 0) { pa_log("[%s:%u] Invalid step value: %s", state->filename, state->lineno, s); goto fail; } if (pa_atod(d, &db) < 0) { pa_log("[%s:%u] Invalid dB value: %s", state->filename, state->lineno, d); goto fail; } if (step <= prev_step && i != 1) { pa_log("[%s:%u] Step value %u not greater than the previous value %u", state->filename, state->lineno, step, prev_step); goto fail; } if (db < prev_db && i != 1) { pa_log("[%s:%u] Decibel value %0.2f less than the previous value %0.2f", state->filename, state->lineno, db, prev_db); goto fail; } if (i == 1) { min_step = step; db_values[0] = (long) (db * 100.0); prev_step = step; prev_db = db; } else { /* Interpolate linearly. */ double db_increment = (db - prev_db) / (step - prev_step); for (; prev_step < step; ++prev_step, prev_db += db_increment) { /* Reallocate the db_values table if it's about to overflow. */ if (prev_step + 1 - min_step == n) { n *= 2; db_values = pa_xrenew(long, db_values, n); } db_values[prev_step + 1 - min_step] = (long) ((prev_db + db_increment) * 100.0); } } max_step = step; } db_fix->min_step = min_step; db_fix->max_step = max_step; pa_xfree(db_fix->db_values); db_fix->db_values = db_values; pa_xstrfreev(items); return 0; fail: pa_xstrfreev(items); pa_xfree(db_values); return -1; } /* the logic is simple: if we see the jack in multiple paths */ /* assign all those paths to one availability_group */ static void profile_set_set_availability_groups(pa_alsa_profile_set *ps) { pa_dynarray *paths; pa_alsa_path *p; void *state; unsigned idx1; uint32_t num = 1; /* Merge ps->input_paths and ps->output_paths into one dynarray. */ paths = pa_dynarray_new(NULL); PA_HASHMAP_FOREACH(p, ps->input_paths, state) pa_dynarray_append(paths, p); PA_HASHMAP_FOREACH(p, ps->output_paths, state) pa_dynarray_append(paths, p); PA_DYNARRAY_FOREACH(p, paths, idx1) { pa_alsa_jack *j; const char *found = NULL; bool has_control = false; PA_LLIST_FOREACH(j, p->jacks) { pa_alsa_path *p2; unsigned idx2; if (!j->has_control || j->state_plugged == PA_AVAILABLE_NO) continue; has_control = true; PA_DYNARRAY_FOREACH(p2, paths, idx2) { pa_alsa_jack *j2; if (p2 == p) break; PA_LLIST_FOREACH(j2, p2->jacks) { if (!j2->has_control || j2->state_plugged == PA_AVAILABLE_NO) continue; if (pa_streq(j->alsa_id.name, j2->alsa_id.name) && j->alsa_id.index == j2->alsa_id.index) { j->state_plugged = PA_AVAILABLE_UNKNOWN; j2->state_plugged = PA_AVAILABLE_UNKNOWN; found = p2->availability_group; break; } } } if (found) break; } if (!has_control) continue; if (!found) { p->availability_group = pa_sprintf_malloc("Legacy %d", num); } else { p->availability_group = pa_xstrdup(found); } if (!found) num++; } pa_dynarray_free(paths); } static void mapping_paths_probe(pa_alsa_mapping *m, pa_alsa_profile *profile, pa_alsa_direction_t direction, pa_hashmap *used_paths, pa_hashmap *mixers) { pa_alsa_path *p; void *state; snd_pcm_t *pcm_handle; pa_alsa_path_set *ps; snd_mixer_t *mixer_handle; if (direction == PA_ALSA_DIRECTION_OUTPUT) { if (m->output_path_set) return; /* Already probed */ m->output_path_set = ps = pa_alsa_path_set_new(m, direction, NULL); /* FIXME: Handle paths_dir */ pcm_handle = m->output_pcm; } else { if (m->input_path_set) return; /* Already probed */ m->input_path_set = ps = pa_alsa_path_set_new(m, direction, NULL); /* FIXME: Handle paths_dir */ pcm_handle = m->input_pcm; } if (!ps) return; /* No paths */ pa_assert(pcm_handle); mixer_handle = pa_alsa_open_mixer_for_pcm(mixers, pcm_handle, true); if (!mixer_handle) { /* Cannot open mixer, remove all entries */ pa_hashmap_remove_all(ps->paths); return; } PA_HASHMAP_FOREACH(p, ps->paths, state) { if (p->autodetect_eld_device) p->eld_device = m->hw_device_index; if (pa_alsa_path_probe(p, m, mixer_handle, m->profile_set->ignore_dB) < 0) pa_hashmap_remove(ps->paths, p); } path_set_condense(ps, mixer_handle); path_set_make_path_descriptions_unique(ps); PA_HASHMAP_FOREACH(p, ps->paths, state) pa_hashmap_put(used_paths, p, p); pa_log_debug("Available mixer paths (after tidying):"); pa_alsa_path_set_dump(ps); } static int mapping_verify(pa_alsa_mapping *m, const pa_channel_map *bonus) { static const struct description_map well_known_descriptions[] = { { "analog-mono", N_("Analog Mono") }, { "analog-mono-left", N_("Analog Mono (Left)") }, { "analog-mono-right", N_("Analog Mono (Right)") }, { "analog-stereo", N_("Analog Stereo") }, { "mono-fallback", N_("Mono") }, { "stereo-fallback", N_("Stereo") }, /* Note: Not translated to "Analog Stereo Input", because the source * name gets "Input" appended to it automatically, so adding "Input" * here would lead to the source name to become "Analog Stereo Input * Input". The same logic applies to analog-stereo-output, * multichannel-input and multichannel-output. */ { "analog-stereo-input", N_("Analog Stereo") }, { "analog-stereo-output", N_("Analog Stereo") }, { "analog-stereo-headset", N_("Headset") }, { "analog-stereo-speakerphone", N_("Speakerphone") }, { "multichannel-input", N_("Multichannel") }, { "multichannel-output", N_("Multichannel") }, { "analog-surround-21", N_("Analog Surround 2.1") }, { "analog-surround-30", N_("Analog Surround 3.0") }, { "analog-surround-31", N_("Analog Surround 3.1") }, { "analog-surround-40", N_("Analog Surround 4.0") }, { "analog-surround-41", N_("Analog Surround 4.1") }, { "analog-surround-50", N_("Analog Surround 5.0") }, { "analog-surround-51", N_("Analog Surround 5.1") }, { "analog-surround-61", N_("Analog Surround 6.0") }, { "analog-surround-61", N_("Analog Surround 6.1") }, { "analog-surround-70", N_("Analog Surround 7.0") }, { "analog-surround-71", N_("Analog Surround 7.1") }, { "iec958-stereo", N_("Digital Stereo (IEC958)") }, { "iec958-ac3-surround-40", N_("Digital Surround 4.0 (IEC958/AC3)") }, { "iec958-ac3-surround-51", N_("Digital Surround 5.1 (IEC958/AC3)") }, { "iec958-dts-surround-51", N_("Digital Surround 5.1 (IEC958/DTS)") }, { "hdmi-stereo", N_("Digital Stereo (HDMI)") }, { "hdmi-surround-51", N_("Digital Surround 5.1 (HDMI)") }, { "gaming-headset-chat", N_("Chat") }, { "gaming-headset-game", N_("Game") }, }; const char *description_key = m->description_key ? m->description_key : m->name; pa_assert(m); if (!pa_channel_map_valid(&m->channel_map)) { pa_log("Mapping %s is missing channel map.", m->name); return -1; } if (!m->device_strings) { pa_log("Mapping %s is missing device strings.", m->name); return -1; } if ((m->input_path_names && m->input_element) || (m->output_path_names && m->output_element)) { pa_log("Mapping %s must have either mixer path or mixer element, not both.", m->name); return -1; } if (!m->description) m->description = pa_xstrdup(lookup_description(description_key, well_known_descriptions, PA_ELEMENTSOF(well_known_descriptions))); if (!m->description) m->description = pa_xstrdup(m->name); if (bonus) { if (pa_channel_map_equal(&m->channel_map, bonus)) m->priority += 50; else if (m->channel_map.channels == bonus->channels) m->priority += 30; } return 0; } void pa_alsa_mapping_dump(pa_alsa_mapping *m) { char cm[PA_CHANNEL_MAP_SNPRINT_MAX]; pa_assert(m); pa_log_debug("Mapping %s (%s), priority=%u, channel_map=%s, supported=%s, direction=%i", m->name, pa_strnull(m->description), m->priority, pa_channel_map_snprint(cm, sizeof(cm), &m->channel_map), pa_yes_no(m->supported), m->direction); } static void profile_set_add_auto_pair( pa_alsa_profile_set *ps, pa_alsa_mapping *m, /* output */ pa_alsa_mapping *n /* input */) { char *name; pa_alsa_profile *p; pa_assert(ps); pa_assert(m || n); if (m && m->direction == PA_ALSA_DIRECTION_INPUT) return; if (n && n->direction == PA_ALSA_DIRECTION_OUTPUT) return; if (m && n) name = pa_sprintf_malloc("output:%s+input:%s", m->name, n->name); else if (m) name = pa_sprintf_malloc("output:%s", m->name); else name = pa_sprintf_malloc("input:%s", n->name); if (pa_hashmap_get(ps->profiles, name)) { pa_xfree(name); return; } p = pa_xnew0(pa_alsa_profile, 1); p->profile_set = ps; p->name = name; if (m) { p->output_name = pa_xstrdup(m->name); p->output_mappings = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); pa_idxset_put(p->output_mappings, m, NULL); p->priority += m->priority * 100; p->fallback_output = m->fallback; } if (n) { p->input_name = pa_xstrdup(n->name); p->input_mappings = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); pa_idxset_put(p->input_mappings, n, NULL); p->priority += n->priority; p->fallback_input = n->fallback; } pa_hashmap_put(ps->profiles, p->name, p); } static void profile_set_add_auto(pa_alsa_profile_set *ps) { pa_alsa_mapping *m, *n; void *m_state, *n_state; pa_assert(ps); /* The order is important here: 1) try single inputs and outputs before trying their combination, because if the half-duplex test failed, we don't have to try full duplex. 2) try the output right before the input combinations with that output, because then the output_pcm is not closed between tests. */ PA_HASHMAP_FOREACH(n, ps->mappings, n_state) profile_set_add_auto_pair(ps, NULL, n); PA_HASHMAP_FOREACH(m, ps->mappings, m_state) { profile_set_add_auto_pair(ps, m, NULL); PA_HASHMAP_FOREACH(n, ps->mappings, n_state) profile_set_add_auto_pair(ps, m, n); } } static int profile_verify(pa_alsa_profile *p) { static const struct description_map well_known_descriptions[] = { { "output:analog-mono+input:analog-mono", N_("Analog Mono Duplex") }, { "output:analog-stereo+input:analog-stereo", N_("Analog Stereo Duplex") }, { "output:analog-stereo-headset+input:analog-stereo-headset", N_("Headset") }, { "output:analog-stereo-speakerphone+input:analog-stereo-speakerphone", N_("Speakerphone") }, { "output:iec958-stereo+input:iec958-stereo", N_("Digital Stereo Duplex (IEC958)") }, { "output:multichannel-output+input:multichannel-input", N_("Multichannel Duplex") }, { "output:unknown-stereo+input:unknown-stereo", N_("Stereo Duplex") }, { "output:analog-output-surround71+output:analog-output-chat+input:analog-input", N_("Mono Chat + 7.1 Surround") }, { "off", N_("Off") } }; const char *description_key = p->description_key ? p->description_key : p->name; pa_assert(p); /* Replace the output mapping names by the actual mappings */ if (p->output_mapping_names) { char **name; pa_assert(!p->output_mappings); p->output_mappings = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); for (name = p->output_mapping_names; *name; name++) { pa_alsa_mapping *m; char **in; bool duplicate = false; for (in = name + 1; *in; in++) if (pa_streq(*name, *in)) { duplicate = true; break; } if (duplicate) continue; if (!(m = pa_hashmap_get(p->profile_set->mappings, *name)) || m->direction == PA_ALSA_DIRECTION_INPUT) { pa_log("Profile '%s' refers to nonexistent mapping '%s'.", p->name, *name); return -1; } pa_idxset_put(p->output_mappings, m, NULL); if (p->supported) m->supported++; } pa_xstrfreev(p->output_mapping_names); p->output_mapping_names = NULL; } /* Replace the input mapping names by the actual mappings */ if (p->input_mapping_names) { char **name; pa_assert(!p->input_mappings); p->input_mappings = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); for (name = p->input_mapping_names; *name; name++) { pa_alsa_mapping *m; char **in; bool duplicate = false; for (in = name + 1; *in; in++) if (pa_streq(*name, *in)) { duplicate = true; break; } if (duplicate) continue; if (!(m = pa_hashmap_get(p->profile_set->mappings, *name)) || m->direction == PA_ALSA_DIRECTION_OUTPUT) { pa_log("Profile '%s' refers to nonexistent mapping '%s'.", p->name, *name); return -1; } pa_idxset_put(p->input_mappings, m, NULL); if (p->supported) m->supported++; } pa_xstrfreev(p->input_mapping_names); p->input_mapping_names = NULL; } if (!p->input_mappings && !p->output_mappings) { pa_log("Profile '%s' lacks mappings.", p->name); return -1; } if (!p->description) p->description = pa_xstrdup(lookup_description(description_key, well_known_descriptions, PA_ELEMENTSOF(well_known_descriptions))); if (!p->description) { pa_strbuf *sb; uint32_t idx; pa_alsa_mapping *m; sb = pa_strbuf_new(); if (p->output_mappings) PA_IDXSET_FOREACH(m, p->output_mappings, idx) { if (!pa_strbuf_isempty(sb)) pa_strbuf_puts(sb, " + "); pa_strbuf_printf(sb, _("%s Output"), m->description); } if (p->input_mappings) PA_IDXSET_FOREACH(m, p->input_mappings, idx) { if (!pa_strbuf_isempty(sb)) pa_strbuf_puts(sb, " + "); pa_strbuf_printf(sb, _("%s Input"), m->description); } p->description = pa_strbuf_to_string_free(sb); } return 0; } void pa_alsa_profile_dump(pa_alsa_profile *p) { uint32_t idx; pa_alsa_mapping *m; pa_assert(p); pa_log_debug("Profile %s (%s), input=%s, output=%s priority=%u, supported=%s n_input_mappings=%u, n_output_mappings=%u", p->name, pa_strnull(p->description), pa_strnull(p->input_name), pa_strnull(p->output_name), p->priority, pa_yes_no(p->supported), p->input_mappings ? pa_idxset_size(p->input_mappings) : 0, p->output_mappings ? pa_idxset_size(p->output_mappings) : 0); if (p->input_mappings) PA_IDXSET_FOREACH(m, p->input_mappings, idx) pa_log_debug("Input %s", m->name); if (p->output_mappings) PA_IDXSET_FOREACH(m, p->output_mappings, idx) pa_log_debug("Output %s", m->name); } static int decibel_fix_verify(pa_alsa_decibel_fix *db_fix) { pa_assert(db_fix); /* Check that the dB mapping has been configured. Since "db-values" is * currently the only option in the DecibelFix section, and decibel fix * objects don't get created if a DecibelFix section is empty, this is * actually a redundant check. Having this may prevent future bugs, * however. */ if (!db_fix->db_values) { pa_log("Decibel fix for element %s lacks the dB values.", db_fix->name); return -1; } return 0; } void pa_alsa_decibel_fix_dump(pa_alsa_decibel_fix *db_fix) { char *db_values = NULL; pa_assert(db_fix); if (db_fix->db_values) { pa_strbuf *buf; unsigned long i, nsteps; pa_assert(db_fix->min_step <= db_fix->max_step); nsteps = db_fix->max_step - db_fix->min_step + 1; buf = pa_strbuf_new(); for (i = 0; i < nsteps; ++i) pa_strbuf_printf(buf, "[%li]:%0.2f ", i + db_fix->min_step, db_fix->db_values[i] / 100.0); db_values = pa_strbuf_to_string_free(buf); } pa_log_debug("Decibel fix %s, min_step=%li, max_step=%li, db_values=%s", db_fix->name, db_fix->min_step, db_fix->max_step, pa_strnull(db_values)); pa_xfree(db_values); } pa_alsa_profile_set* pa_alsa_profile_set_new(const char *fname, const pa_channel_map *bonus) { pa_alsa_profile_set *ps; pa_alsa_profile *p; pa_alsa_mapping *m; pa_alsa_decibel_fix *db_fix; char *fn; int r; void *state; static pa_config_item items[] = { /* [General] */ { "auto-profiles", pa_config_parse_bool, NULL, "General" }, /* [Mapping ...] */ { "device-strings", mapping_parse_device_strings, NULL, NULL }, { "channel-map", mapping_parse_channel_map, NULL, NULL }, { "paths-input", mapping_parse_paths, NULL, NULL }, { "paths-output", mapping_parse_paths, NULL, NULL }, { "element-input", mapping_parse_element, NULL, NULL }, { "element-output", mapping_parse_element, NULL, NULL }, { "direction", mapping_parse_direction, NULL, NULL }, { "exact-channels", mapping_parse_exact_channels, NULL, NULL }, { "intended-roles", mapping_parse_intended_roles, NULL, NULL }, /* Shared by [Mapping ...] and [Profile ...] */ { "description", mapping_parse_description, NULL, NULL }, { "description-key", mapping_parse_description_key,NULL, NULL }, { "priority", mapping_parse_priority, NULL, NULL }, { "fallback", mapping_parse_fallback, NULL, NULL }, /* [Profile ...] */ { "input-mappings", profile_parse_mappings, NULL, NULL }, { "output-mappings", profile_parse_mappings, NULL, NULL }, { "skip-probe", profile_parse_skip_probe, NULL, NULL }, /* [DecibelFix ...] */ { "db-values", decibel_fix_parse_db_values, NULL, NULL }, { NULL, NULL, NULL, NULL } }; ps = pa_xnew0(pa_alsa_profile_set, 1); ps->mappings = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL, (pa_free_cb_t) pa_alsa_mapping_free); ps->profiles = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL, (pa_free_cb_t) pa_alsa_profile_free); ps->decibel_fixes = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL, (pa_free_cb_t) decibel_fix_free); ps->input_paths = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL, (pa_free_cb_t) pa_alsa_path_free); ps->output_paths = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL, (pa_free_cb_t) pa_alsa_path_free); items[0].data = &ps->auto_profiles; fn = get_data_path(NULL, "profile-sets", fname ? fname : "default.conf"); pa_log_info("Loading profile set: %s", fn); if ((r = access(fn, R_OK)) != 0) { if (fname != NULL) { pa_log_warn("profile-set '%s' can't be accessed: %m", fn); fn = get_data_path(NULL, "profile-sets", "default.conf"); r = access(fn, R_OK); } if (r != 0) { pa_log_warn("profile-set '%s' can't be accessed: %m", fn); } } r = pa_config_parse(fn, NULL, items, NULL, false, ps); pa_xfree(fn); if (r < 0) goto fail; PA_HASHMAP_FOREACH(m, ps->mappings, state) if (mapping_verify(m, bonus) < 0) goto fail; if (ps->auto_profiles) profile_set_add_auto(ps); PA_HASHMAP_FOREACH(p, ps->profiles, state) if (profile_verify(p) < 0) goto fail; PA_HASHMAP_FOREACH(db_fix, ps->decibel_fixes, state) if (decibel_fix_verify(db_fix) < 0) goto fail; return ps; fail: pa_alsa_profile_set_free(ps); return NULL; } static void profile_finalize_probing(pa_alsa_profile *to_be_finalized, pa_alsa_profile *next) { pa_alsa_mapping *m; uint32_t idx; if (!to_be_finalized) return; if (to_be_finalized->output_mappings) PA_IDXSET_FOREACH(m, to_be_finalized->output_mappings, idx) { if (!m->output_pcm) continue; if (to_be_finalized->supported) m->supported++; /* If this mapping is also in the next profile, we won't close the * pcm handle here, because it would get immediately reopened * anyway. */ if (next && next->output_mappings && pa_idxset_get_by_data(next->output_mappings, m, NULL)) continue; pa_alsa_init_proplist_pcm(NULL, m->output_proplist, m->output_pcm); pa_alsa_close(&m->output_pcm); } if (to_be_finalized->input_mappings) PA_IDXSET_FOREACH(m, to_be_finalized->input_mappings, idx) { if (!m->input_pcm) continue; if (to_be_finalized->supported) m->supported++; /* If this mapping is also in the next profile, we won't close the * pcm handle here, because it would get immediately reopened * anyway. */ if (next && next->input_mappings && pa_idxset_get_by_data(next->input_mappings, m, NULL)) continue; pa_alsa_init_proplist_pcm(NULL, m->input_proplist, m->input_pcm); pa_alsa_close(&m->input_pcm); } } static snd_pcm_t* mapping_open_pcm(pa_alsa_mapping *m, const pa_sample_spec *ss, const char *dev_id, bool exact_channels, int mode, unsigned default_n_fragments, unsigned default_fragment_size_msec) { snd_pcm_t* handle; pa_sample_spec try_ss = *ss; pa_channel_map try_map = m->channel_map; snd_pcm_uframes_t try_period_size, try_buffer_size; try_ss.channels = try_map.channels; try_period_size = pa_usec_to_bytes(default_fragment_size_msec * PA_USEC_PER_MSEC, &try_ss) / pa_frame_size(&try_ss); try_buffer_size = default_n_fragments * try_period_size; handle = pa_alsa_open_by_template( m->device_strings, dev_id, NULL, &try_ss, &try_map, mode, &try_period_size, &try_buffer_size, 0, NULL, NULL, NULL, NULL, exact_channels); if (handle && !exact_channels && m->channel_map.channels != try_map.channels) { char buf[PA_CHANNEL_MAP_SNPRINT_MAX]; pa_log_debug("Channel map for mapping '%s' permanently changed to '%s'", m->name, pa_channel_map_snprint(buf, sizeof(buf), &try_map)); m->channel_map = try_map; } return handle; } static void paths_drop_unused(pa_hashmap* h, pa_hashmap *keep) { void* state = NULL; const void* key; pa_alsa_path* p; pa_assert(h); pa_assert(keep); p = pa_hashmap_iterate(h, &state, &key); while (p) { if (pa_hashmap_get(keep, p) == NULL) pa_hashmap_remove_and_free(h, key); p = pa_hashmap_iterate(h, &state, &key); } } static int add_profiles_to_probe( pa_alsa_profile **list, pa_hashmap *profiles, bool fallback_output, bool fallback_input) { int i = 0; void *state; pa_alsa_profile *p; PA_HASHMAP_FOREACH(p, profiles, state) if (p->fallback_input == fallback_input && p->fallback_output == fallback_output) { *list = p; list++; i++; } return i; } static void mapping_query_hw_device(pa_alsa_mapping *mapping, snd_pcm_t *pcm) { int r; snd_pcm_info_t* pcm_info; snd_pcm_info_alloca(&pcm_info); r = snd_pcm_info(pcm, pcm_info); if (r < 0) { pa_log("Mapping %s: snd_pcm_info() failed %s: ", mapping->name, pa_alsa_strerror(r)); return; } /* XXX: It's not clear what snd_pcm_info_get_device() does if the device is * not backed by a hw device or if it's backed by multiple hw devices. We * only use hw_device_index for HDMI devices, however, and for those the * return value is expected to be always valid, so this shouldn't be a * significant problem. */ mapping->hw_device_index = snd_pcm_info_get_device(pcm_info); } void pa_alsa_profile_set_probe( pa_alsa_profile_set *ps, pa_hashmap *mixers, const char *dev_id, const pa_sample_spec *ss, unsigned default_n_fragments, unsigned default_fragment_size_msec) { bool found_output = false, found_input = false; pa_alsa_profile *p, *last = NULL; pa_alsa_profile **pp, **probe_order; pa_alsa_mapping *m; pa_hashmap *broken_inputs, *broken_outputs, *used_paths; pa_alsa_mapping *selected_fallback_input = NULL, *selected_fallback_output = NULL; pa_assert(ps); pa_assert(dev_id); pa_assert(ss); if (ps->probed) return; broken_inputs = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); broken_outputs = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); used_paths = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); pp = probe_order = pa_xnew0(pa_alsa_profile *, pa_hashmap_size(ps->profiles) + 1); pp += add_profiles_to_probe(pp, ps->profiles, false, false); pp += add_profiles_to_probe(pp, ps->profiles, false, true); pp += add_profiles_to_probe(pp, ps->profiles, true, false); pp += add_profiles_to_probe(pp, ps->profiles, true, true); for (pp = probe_order; *pp; pp++) { uint32_t idx; p = *pp; /* Skip if fallback and already found something, but still probe already selected fallbacks. * If UCM is used then both fallback_input and fallback_output flags are false. * If UCM is not used then there will be only a single entry in mappings. */ if (found_input && p->fallback_input) if (selected_fallback_input == NULL || pa_idxset_get_by_index(p->input_mappings, 0) != selected_fallback_input) continue; if (found_output && p->fallback_output) if (selected_fallback_output == NULL || pa_idxset_get_by_index(p->output_mappings, 0) != selected_fallback_output) continue; /* Skip if this is already marked that it is supported (i.e. from the config file) */ if (!p->supported) { profile_finalize_probing(last, p); p->supported = true; if (p->output_mappings) { PA_IDXSET_FOREACH(m, p->output_mappings, idx) { if (pa_hashmap_get(broken_outputs, m) == m) { pa_log_debug("Skipping profile %s - will not be able to open output:%s", p->name, m->name); p->supported = false; break; } } } if (p->input_mappings && p->supported) { PA_IDXSET_FOREACH(m, p->input_mappings, idx) { if (pa_hashmap_get(broken_inputs, m) == m) { pa_log_debug("Skipping profile %s - will not be able to open input:%s", p->name, m->name); p->supported = false; break; } } } if (p->supported) pa_log_debug("Looking at profile %s", p->name); /* Check if we can open all new ones */ if (p->output_mappings && p->supported) PA_IDXSET_FOREACH(m, p->output_mappings, idx) { if (m->output_pcm) continue; pa_log_debug("Checking for playback on %s (%s)", m->description, m->name); if (!(m->output_pcm = mapping_open_pcm(m, ss, dev_id, m->exact_channels, SND_PCM_STREAM_PLAYBACK, default_n_fragments, default_fragment_size_msec))) { p->supported = false; if (pa_idxset_size(p->output_mappings) == 1 && ((!p->input_mappings) || pa_idxset_size(p->input_mappings) == 0)) { pa_log_debug("Caching failure to open output:%s", m->name); pa_hashmap_put(broken_outputs, m, m); } break; } if (m->hw_device_index < 0) mapping_query_hw_device(m, m->output_pcm); } if (p->input_mappings && p->supported) PA_IDXSET_FOREACH(m, p->input_mappings, idx) { if (m->input_pcm) continue; pa_log_debug("Checking for recording on %s (%s)", m->description, m->name); if (!(m->input_pcm = mapping_open_pcm(m, ss, dev_id, m->exact_channels, SND_PCM_STREAM_CAPTURE, default_n_fragments, default_fragment_size_msec))) { p->supported = false; if (pa_idxset_size(p->input_mappings) == 1 && ((!p->output_mappings) || pa_idxset_size(p->output_mappings) == 0)) { pa_log_debug("Caching failure to open input:%s", m->name); pa_hashmap_put(broken_inputs, m, m); } break; } if (m->hw_device_index < 0) mapping_query_hw_device(m, m->input_pcm); } last = p; if (!p->supported) continue; } pa_log_debug("Profile %s supported.", p->name); if (p->output_mappings) PA_IDXSET_FOREACH(m, p->output_mappings, idx) if (m->output_pcm) { found_output = true; if (p->fallback_output && selected_fallback_output == NULL) { selected_fallback_output = m; } mapping_paths_probe(m, p, PA_ALSA_DIRECTION_OUTPUT, used_paths, mixers); } if (p->input_mappings) PA_IDXSET_FOREACH(m, p->input_mappings, idx) if (m->input_pcm) { found_input = true; if (p->fallback_input && selected_fallback_input == NULL) { selected_fallback_input = m; } mapping_paths_probe(m, p, PA_ALSA_DIRECTION_INPUT, used_paths, mixers); } } /* Clean up */ profile_finalize_probing(last, NULL); pa_alsa_profile_set_drop_unsupported(ps); paths_drop_unused(ps->input_paths, used_paths); paths_drop_unused(ps->output_paths, used_paths); pa_hashmap_free(broken_inputs); pa_hashmap_free(broken_outputs); pa_hashmap_free(used_paths); pa_xfree(probe_order); profile_set_set_availability_groups(ps); ps->probed = true; } void pa_alsa_profile_set_dump(pa_alsa_profile_set *ps) { pa_alsa_profile *p; pa_alsa_mapping *m; pa_alsa_decibel_fix *db_fix; void *state; pa_assert(ps); pa_log_debug("Profile set %p, auto_profiles=%s, probed=%s, n_mappings=%u, n_profiles=%u, n_decibel_fixes=%u", (void*) ps, pa_yes_no(ps->auto_profiles), pa_yes_no(ps->probed), pa_hashmap_size(ps->mappings), pa_hashmap_size(ps->profiles), pa_hashmap_size(ps->decibel_fixes)); PA_HASHMAP_FOREACH(m, ps->mappings, state) pa_alsa_mapping_dump(m); PA_HASHMAP_FOREACH(p, ps->profiles, state) pa_alsa_profile_dump(p); PA_HASHMAP_FOREACH(db_fix, ps->decibel_fixes, state) pa_alsa_decibel_fix_dump(db_fix); } void pa_alsa_profile_set_drop_unsupported(pa_alsa_profile_set *ps) { pa_alsa_profile *p; pa_alsa_mapping *m; void *state; PA_HASHMAP_FOREACH(p, ps->profiles, state) { if (!p->supported) pa_hashmap_remove_and_free(ps->profiles, p->name); } PA_HASHMAP_FOREACH(m, ps->mappings, state) { if (m->supported <= 0) pa_hashmap_remove_and_free(ps->mappings, m->name); } } static pa_device_port* device_port_alsa_init(pa_hashmap *ports, /* card ports */ const char* name, const char* description, pa_alsa_path *path, pa_alsa_setting *setting, pa_card_profile *cp, pa_hashmap *extra, /* sink/source ports */ pa_core *core) { pa_device_port *p; pa_assert(path); p = pa_hashmap_get(ports, name); if (!p) { pa_alsa_port_data *data; pa_device_port_new_data port_data; pa_device_port_new_data_init(&port_data); pa_device_port_new_data_set_name(&port_data, name); pa_device_port_new_data_set_description(&port_data, description); pa_device_port_new_data_set_direction(&port_data, path->direction == PA_ALSA_DIRECTION_OUTPUT ? PA_DIRECTION_OUTPUT : PA_DIRECTION_INPUT); pa_device_port_new_data_set_type(&port_data, path->device_port_type); pa_device_port_new_data_set_availability_group(&port_data, path->availability_group); p = pa_device_port_new(core, &port_data, sizeof(pa_alsa_port_data)); pa_device_port_new_data_done(&port_data); pa_assert(p); pa_hashmap_put(ports, p->name, p); pa_proplist_update(p->proplist, PA_UPDATE_REPLACE, path->proplist); data = PA_DEVICE_PORT_DATA(p); /* Ownership of the path and setting is not transferred to the port data, so we don't deal with freeing them */ data->path = path; data->setting = setting; path->port = p; } if (cp) pa_hashmap_put(p->profiles, cp->name, cp); if (extra) { pa_hashmap_put(extra, p->name, p); } return p; } void pa_alsa_path_set_add_ports( pa_alsa_path_set *ps, pa_card_profile *cp, pa_hashmap *ports, /* card ports */ pa_hashmap *extra, /* sink/source ports */ pa_core *core) { pa_alsa_path *path; void *state; pa_assert(ports); if (!ps) return; PA_HASHMAP_FOREACH(path, ps->paths, state) { if (!path->settings || !path->settings->next) { /* If there is no or just one setting we only need a * single entry */ pa_device_port *port = device_port_alsa_init(ports, path->name, path->description, path, path->settings, cp, extra, core); port->priority = path->priority * 100; } else { pa_alsa_setting *s; PA_LLIST_FOREACH(s, path->settings) { pa_device_port *port; char *n, *d; n = pa_sprintf_malloc("%s;%s", path->name, s->name); if (s->description[0]) d = pa_sprintf_malloc("%s / %s", path->description, s->description); else d = pa_xstrdup(path->description); port = device_port_alsa_init(ports, n, d, path, s, cp, extra, core); port->priority = path->priority * 100 + s->priority; pa_xfree(n); pa_xfree(d); } } } } void pa_alsa_add_ports(pa_hashmap *ports, pa_alsa_path_set *ps, pa_card *card) { pa_assert(ps); if (ps->paths && pa_hashmap_size(ps->paths) > 0) { pa_assert(card); pa_alsa_path_set_add_ports(ps, NULL, card->ports, ports, card->core); } pa_log_debug("Added %u ports", pa_hashmap_size(ports)); } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/acp/alsa-mixer.h000066400000000000000000000346301511204443500266460ustar00rootroot00000000000000#ifndef fooalsamixerhfoo #define fooalsamixerhfoo /*** This file is part of PulseAudio. Copyright 2004-2006 Lennart Poettering Copyright 2006 Pierre Ossman for Cendio AB PulseAudio is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. PulseAudio is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with PulseAudio; if not, see . ***/ #include typedef struct pa_alsa_mixer pa_alsa_mixer; typedef struct pa_alsa_setting pa_alsa_setting; typedef struct pa_alsa_mixer_id pa_alsa_mixer_id; typedef struct pa_alsa_option pa_alsa_option; typedef struct pa_alsa_element pa_alsa_element; typedef struct pa_alsa_jack pa_alsa_jack; typedef struct pa_alsa_path pa_alsa_path; typedef struct pa_alsa_path_set pa_alsa_path_set; typedef struct pa_alsa_mapping pa_alsa_mapping; typedef struct pa_alsa_profile pa_alsa_profile; typedef struct pa_alsa_decibel_fix pa_alsa_decibel_fix; typedef struct pa_alsa_profile_set pa_alsa_profile_set; typedef struct pa_alsa_port_data pa_alsa_port_data; typedef struct pa_alsa_profile pa_alsa_profile; typedef struct pa_alsa_profile pa_card_profile; typedef struct pa_alsa_device pa_alsa_device; #define POSITION_MASK_CHANNELS 8 typedef enum pa_alsa_switch_use { PA_ALSA_SWITCH_IGNORE, PA_ALSA_SWITCH_MUTE, /* make this switch follow mute status */ PA_ALSA_SWITCH_OFF, /* set this switch to 'off' unconditionally */ PA_ALSA_SWITCH_ON, /* set this switch to 'on' unconditionally */ PA_ALSA_SWITCH_SELECT /* allow the user to select switch status through a setting */ } pa_alsa_switch_use_t; typedef enum pa_alsa_volume_use { PA_ALSA_VOLUME_IGNORE, PA_ALSA_VOLUME_MERGE, /* merge this volume slider into the global volume slider */ PA_ALSA_VOLUME_OFF, /* set this volume to minimal unconditionally */ PA_ALSA_VOLUME_ZERO, /* set this volume to 0dB unconditionally */ PA_ALSA_VOLUME_CONSTANT /* set this volume to a constant value unconditionally */ } pa_alsa_volume_use_t; typedef enum pa_alsa_enumeration_use { PA_ALSA_ENUMERATION_IGNORE, PA_ALSA_ENUMERATION_SELECT } pa_alsa_enumeration_use_t; typedef enum pa_alsa_required { PA_ALSA_REQUIRED_IGNORE, PA_ALSA_REQUIRED_SWITCH, PA_ALSA_REQUIRED_VOLUME, PA_ALSA_REQUIRED_ENUMERATION, PA_ALSA_REQUIRED_ANY } pa_alsa_required_t; typedef enum pa_alsa_direction { PA_ALSA_DIRECTION_ANY, PA_ALSA_DIRECTION_OUTPUT, PA_ALSA_DIRECTION_INPUT } pa_alsa_direction_t; #include "acp.h" #include "device-port.h" #include "alsa-util.h" #include "alsa-ucm.h" #include "card.h" /* A setting combines a couple of options into a single entity that * may be selected. Only one setting can be active at the same * time. */ struct pa_alsa_setting { pa_alsa_path *path; PA_LLIST_FIELDS(pa_alsa_setting); pa_idxset *options; char *name; char *description; unsigned priority; }; /* An entry for one ALSA mixer */ struct pa_alsa_mixer { struct pa_alsa_mixer *alias; snd_mixer_t *mixer_handle; bool used_for_poll:1; bool used_for_probe_only:1; }; /* ALSA mixer element identifier */ struct pa_alsa_mixer_id { char *name; int index; }; char *pa_alsa_mixer_id_to_string(char *dst, size_t dst_len, pa_alsa_mixer_id *id); /* An option belongs to an element and refers to one enumeration item * of the element is an enumeration item, or a switch status if the * element is a switch item. */ struct pa_alsa_option { pa_alsa_element *element; PA_LLIST_FIELDS(pa_alsa_option); char *alsa_name; int alsa_idx; char *name; char *description; unsigned priority; pa_alsa_required_t required; pa_alsa_required_t required_any; pa_alsa_required_t required_absent; }; /* An element wraps one specific ALSA element. A series of elements * make up a path (see below). If the element is an enumeration or switch * element it may include a list of options. */ struct pa_alsa_element { pa_alsa_path *path; PA_LLIST_FIELDS(pa_alsa_element); struct pa_alsa_mixer_id alsa_id; pa_alsa_direction_t direction; pa_alsa_switch_use_t switch_use; pa_alsa_volume_use_t volume_use; pa_alsa_enumeration_use_t enumeration_use; pa_alsa_required_t required; pa_alsa_required_t required_any; pa_alsa_required_t required_absent; long constant_volume; unsigned int override_map; bool direction_try_other:1; bool has_dB:1; long min_volume, max_volume; long volume_limit; /* -1 for no configured limit */ double min_dB, max_dB; pa_channel_position_mask_t masks[SND_MIXER_SCHN_LAST + 1][POSITION_MASK_CHANNELS]; unsigned n_channels; pa_channel_position_mask_t merged_mask; PA_LLIST_HEAD(pa_alsa_option, options); pa_alsa_decibel_fix *db_fix; }; struct pa_alsa_jack { pa_alsa_path *path; PA_LLIST_FIELDS(pa_alsa_jack); snd_mixer_t *mixer_handle; char *mixer_device_name; struct pa_alsa_mixer_id alsa_id; char *name; /* E g "Headphone" */ bool has_control; /* is the jack itself present? */ bool plugged_in; /* is this jack currently plugged in? */ snd_mixer_elem_t *melem; /* Jack detection handle */ pa_available_t state_unplugged, state_plugged; pa_alsa_required_t required; pa_alsa_required_t required_any; pa_alsa_required_t required_absent; pa_dynarray *ucm_devices; /* pa_alsa_ucm_device */ pa_dynarray *ucm_hw_mute_devices; /* pa_alsa_ucm_device */ bool append_pcm_to_name; }; pa_alsa_jack *pa_alsa_jack_new(pa_alsa_path *path, const char *mixer_device_name, const char *name, int index); void pa_alsa_jack_free(pa_alsa_jack *jack); void pa_alsa_jack_set_has_control(pa_alsa_jack *jack, bool has_control); void pa_alsa_jack_set_plugged_in(pa_alsa_jack *jack, bool plugged_in); void pa_alsa_jack_add_ucm_device(pa_alsa_jack *jack, pa_alsa_ucm_device *device); void pa_alsa_jack_add_ucm_hw_mute_device(pa_alsa_jack *jack, pa_alsa_ucm_device *device); /* A path wraps a series of elements into a single entity which can be * used to control it as if it had a single volume slider, a single * mute switch and a single list of selectable options. */ struct pa_alsa_path { pa_alsa_direction_t direction; pa_device_port* port; char *name; char *description_key; char *description; char *availability_group; pa_device_port_type_t device_port_type; unsigned priority; bool autodetect_eld_device; pa_alsa_mixer *eld_mixer_handle; int eld_device; pa_proplist *proplist; bool probed:1; bool supported:1; bool has_mute:1; bool has_volume:1; bool has_dB:1; bool has_volume_mute:1; bool mute_during_activation:1; /* These two are used during probing only */ bool has_req_any:1; bool req_any_present:1; long min_volume, max_volume; double min_dB, max_dB; /* This is used during parsing only, as a shortcut so that we * don't have to iterate the list all the time */ pa_alsa_element *last_element; pa_alsa_option *last_option; pa_alsa_setting *last_setting; pa_alsa_jack *last_jack; PA_LLIST_HEAD(pa_alsa_element, elements); PA_LLIST_HEAD(pa_alsa_setting, settings); PA_LLIST_HEAD(pa_alsa_jack, jacks); }; /* A path set is simply a set of paths that are applicable to a * device */ struct pa_alsa_path_set { pa_hashmap *paths; pa_alsa_direction_t direction; }; void pa_alsa_setting_dump(pa_alsa_setting *s); void pa_alsa_option_dump(pa_alsa_option *o); void pa_alsa_jack_dump(pa_alsa_jack *j); void pa_alsa_element_dump(pa_alsa_element *e); pa_alsa_path *pa_alsa_path_new(const char *paths_dir, const char *fname, pa_alsa_direction_t direction); pa_alsa_path *pa_alsa_path_synthesize(const char *element, pa_alsa_direction_t direction); pa_alsa_element *pa_alsa_element_get(pa_alsa_path *p, const char *section, bool prefixed); int pa_alsa_path_probe(pa_alsa_path *p, pa_alsa_mapping *mapping, snd_mixer_t *m, bool ignore_dB); void pa_alsa_path_dump(pa_alsa_path *p); int pa_alsa_path_get_volume(pa_alsa_path *p, snd_mixer_t *m, const pa_channel_map *cm, pa_cvolume *v); int pa_alsa_path_get_mute(pa_alsa_path *path, snd_mixer_t *m, bool *muted); int pa_alsa_path_set_volume(pa_alsa_path *path, snd_mixer_t *m, const pa_channel_map *cm, pa_cvolume *v, bool deferred_volume, bool write_to_hw); int pa_alsa_path_set_mute(pa_alsa_path *path, snd_mixer_t *m, bool muted); int pa_alsa_path_select(pa_alsa_path *p, pa_alsa_setting *s, snd_mixer_t *m, bool device_is_muted); void pa_alsa_path_set_callback(pa_alsa_path *p, snd_mixer_t *m, snd_mixer_elem_callback_t cb, void *userdata); void pa_alsa_path_free(pa_alsa_path *p); pa_alsa_path_set *pa_alsa_path_set_new(pa_alsa_mapping *m, pa_alsa_direction_t direction, const char *paths_dir); void pa_alsa_path_set_dump(pa_alsa_path_set *s); void pa_alsa_path_set_set_callback(pa_alsa_path_set *ps, snd_mixer_t *m, snd_mixer_elem_callback_t cb, void *userdata); void pa_alsa_path_set_free(pa_alsa_path_set *s); int pa_alsa_path_set_is_empty(pa_alsa_path_set *s); struct pa_alsa_device { struct acp_device device; pa_card *card; pa_alsa_direction_t direction; pa_proplist *proplist; pa_alsa_mapping *mapping; pa_alsa_ucm_mapping_context *ucm_context; pa_hashmap *ports; pa_dynarray port_array; pa_device_port *active_port; snd_mixer_t *mixer_handle; pa_alsa_path_set *mixer_path_set; pa_alsa_path *mixer_path; snd_pcm_t *pcm_handle; unsigned muted:1; unsigned decibel_volume:1; pa_cvolume real_volume; pa_cvolume hardware_volume; pa_cvolume soft_volume; pa_volume_t base_volume; unsigned n_volume_steps; int (*read_volume)(pa_alsa_device *dev); int (*read_mute)(pa_alsa_device *dev); void (*set_volume)(pa_alsa_device *dev, const pa_cvolume *v); void (*set_mute)(pa_alsa_device *dev, bool m); }; struct pa_alsa_mapping { pa_alsa_profile_set *profile_set; char *name; char *description; char *description_key; unsigned priority; pa_alsa_direction_t direction; /* These are copied over to the resultant sink/source */ pa_proplist *proplist; pa_sample_spec sample_spec; pa_channel_map channel_map; pa_alsa_ucm_split *split; char **device_strings; char **input_path_names; char **output_path_names; char **input_element; /* list of fallbacks */ char **output_element; pa_alsa_path_set *input_path_set; pa_alsa_path_set *output_path_set; unsigned supported; bool exact_channels:1; bool fallback:1; /* The "y" in "hw:x,y". This is set to -1 before the device index has been * queried, or if the query failed. */ int hw_device_index; /* Temporarily used during probing */ snd_pcm_t *input_pcm; snd_pcm_t *output_pcm; pa_proplist *input_proplist; pa_proplist *output_proplist; pa_alsa_device output; pa_alsa_device input; /* ucm device context */ pa_alsa_ucm_mapping_context ucm_context; }; struct pa_alsa_profile { struct acp_card_profile profile; pa_alsa_profile_set *profile_set; char *name; char *description; char *description_key; unsigned priority; char *input_name; char *output_name; bool supported:1; bool fallback_input:1; bool fallback_output:1; char **input_mapping_names; char **output_mapping_names; pa_idxset *input_mappings; pa_idxset *output_mappings; /* ucm device context */ pa_alsa_ucm_profile_context ucm_context; struct { pa_dynarray devices; } out; }; struct pa_alsa_decibel_fix { char *key; pa_alsa_profile_set *profile_set; char *name; /* Alsa volume element name. */ int index; /* Alsa volume element index. */ long min_step; long max_step; /* An array that maps alsa volume element steps to decibels. The steps can * be used as indices to this array, after subtracting min_step from the * real value. * * The values are actually stored as integers representing millibels, * because that's the format the alsa API uses. */ long *db_values; }; struct pa_alsa_profile_set { pa_hashmap *mappings; pa_hashmap *profiles; pa_hashmap *decibel_fixes; pa_hashmap *input_paths; pa_hashmap *output_paths; bool auto_profiles; bool ignore_dB:1; bool probed:1; }; void pa_alsa_mapping_dump(pa_alsa_mapping *m); void pa_alsa_profile_dump(pa_alsa_profile *p); void pa_alsa_decibel_fix_dump(pa_alsa_decibel_fix *db_fix); pa_alsa_mapping *pa_alsa_mapping_get(pa_alsa_profile_set *ps, const char *name); void pa_alsa_mapping_free (pa_alsa_mapping *m); void pa_alsa_profile_free (pa_alsa_profile *p); pa_alsa_profile_set* pa_alsa_profile_set_new(const char *fname, const pa_channel_map *bonus); void pa_alsa_profile_set_probe(pa_alsa_profile_set *ps, pa_hashmap *mixers, const char *dev_id, const pa_sample_spec *ss, unsigned default_n_fragments, unsigned default_fragment_size_msec); void pa_alsa_profile_set_free(pa_alsa_profile_set *s); void pa_alsa_profile_set_dump(pa_alsa_profile_set *s); void pa_alsa_profile_set_drop_unsupported(pa_alsa_profile_set *s); void pa_alsa_mixer_use_for_poll(pa_hashmap *mixers, snd_mixer_t *mixer_handle); #if 0 pa_alsa_fdlist *pa_alsa_fdlist_new(void); void pa_alsa_fdlist_free(pa_alsa_fdlist *fdl); int pa_alsa_fdlist_set_handle(pa_alsa_fdlist *fdl, snd_mixer_t *mixer_handle, snd_hctl_t *hctl_handle, pa_mainloop_api* m); /* Alternative for handling alsa mixer events in io-thread. */ pa_alsa_mixer_pdata *pa_alsa_mixer_pdata_new(void); void pa_alsa_mixer_pdata_free(pa_alsa_mixer_pdata *pd); int pa_alsa_set_mixer_rtpoll(struct pa_alsa_mixer_pdata *pd, snd_mixer_t *mixer, pa_rtpoll *rtp); #endif /* Data structure for inclusion in pa_device_port for alsa * sinks/sources. This contains nothing that needs to be freed * individually */ struct pa_alsa_port_data { pa_alsa_path *path; pa_alsa_setting *setting; bool suspend_when_unavailable; }; void pa_alsa_add_ports(pa_hashmap *ports, pa_alsa_path_set *ps, pa_card *card); void pa_alsa_path_set_add_ports(pa_alsa_path_set *ps, pa_alsa_profile *cp, pa_hashmap *ports, pa_hashmap *extra, pa_core *core); #endif pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/acp/alsa-ucm.c000066400000000000000000003044541511204443500263050ustar00rootroot00000000000000/*** This file is part of PulseAudio. Copyright 2011 Wolfson Microelectronics PLC Author Margarita Olaya Copyright 2012 Feng Wei , Freescale Ltd. PulseAudio is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. PulseAudio is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with PulseAudio; if not, see . ***/ #include "config.h" #include #include #include #include #ifdef HAVE_VALGRIND_MEMCHECK_H #include #endif #include "alsa-mixer.h" #include "alsa-util.h" #include "alsa-ucm.h" #define PA_UCM_PRE_TAG_OUTPUT "[Out] " #define PA_UCM_PRE_TAG_INPUT "[In] " #define PA_UCM_PLAYBACK_PRIORITY_UNSET(device) ((device)->playback_channels && !(device)->playback_priority) #define PA_UCM_CAPTURE_PRIORITY_UNSET(device) ((device)->capture_channels && !(device)->capture_priority) #define PA_UCM_DEVICE_PRIORITY_SET(device, priority) \ do { \ if (PA_UCM_PLAYBACK_PRIORITY_UNSET(device)) (device)->playback_priority = (priority); \ if (PA_UCM_CAPTURE_PRIORITY_UNSET(device)) (device)->capture_priority = (priority); \ } while (0) #define PA_UCM_IS_MODIFIER_MAPPING(m) ((pa_proplist_gets((m)->proplist, PA_ALSA_PROP_UCM_MODIFIER)) != NULL) #ifdef HAVE_ALSA_UCM struct ucm_type { const char *prefix; pa_device_port_type_t type; }; struct ucm_items { const char *id; const char *property; }; struct ucm_info { const char *id; unsigned priority; }; static pa_alsa_jack* ucm_get_jack(pa_alsa_ucm_config *ucm, pa_alsa_ucm_device *device); static void device_set_jack(pa_alsa_ucm_device *device, pa_alsa_jack *jack); static void device_add_hw_mute_jack(pa_alsa_ucm_device *device, pa_alsa_jack *jack); static pa_alsa_ucm_device *verb_find_device(pa_alsa_ucm_verb *verb, const char *device_name); static void ucm_port_data_init(pa_alsa_ucm_port_data *port, pa_alsa_ucm_config *ucm, pa_device_port *core_port, pa_alsa_ucm_device *device); static void ucm_port_data_free(pa_device_port *port); static struct ucm_type types[] = { {"None", PA_DEVICE_PORT_TYPE_UNKNOWN}, {"Speaker", PA_DEVICE_PORT_TYPE_SPEAKER}, {"Line", PA_DEVICE_PORT_TYPE_LINE}, {"Mic", PA_DEVICE_PORT_TYPE_MIC}, {"Headphones", PA_DEVICE_PORT_TYPE_HEADPHONES}, {"Headset", PA_DEVICE_PORT_TYPE_HEADSET}, {"Handset", PA_DEVICE_PORT_TYPE_HANDSET}, {"Bluetooth", PA_DEVICE_PORT_TYPE_BLUETOOTH}, {"Earpiece", PA_DEVICE_PORT_TYPE_EARPIECE}, {"SPDIF", PA_DEVICE_PORT_TYPE_SPDIF}, {"HDMI", PA_DEVICE_PORT_TYPE_HDMI}, {NULL, 0} }; static struct ucm_items item[] = { {"PlaybackPCM", PA_ALSA_PROP_UCM_SINK}, {"CapturePCM", PA_ALSA_PROP_UCM_SOURCE}, {"PlaybackCTL", PA_ALSA_PROP_UCM_PLAYBACK_CTL_DEVICE}, {"PlaybackVolume", PA_ALSA_PROP_UCM_PLAYBACK_VOLUME}, {"PlaybackSwitch", PA_ALSA_PROP_UCM_PLAYBACK_SWITCH}, {"PlaybackMixer", PA_ALSA_PROP_UCM_PLAYBACK_MIXER_DEVICE}, {"PlaybackMixerElem", PA_ALSA_PROP_UCM_PLAYBACK_MIXER_ELEM}, {"PlaybackMasterElem", PA_ALSA_PROP_UCM_PLAYBACK_MASTER_ELEM}, {"PlaybackMasterType", PA_ALSA_PROP_UCM_PLAYBACK_MASTER_TYPE}, {"PlaybackPriority", PA_ALSA_PROP_UCM_PLAYBACK_PRIORITY}, {"PlaybackRate", PA_ALSA_PROP_UCM_PLAYBACK_RATE}, {"PlaybackChannels", PA_ALSA_PROP_UCM_PLAYBACK_CHANNELS}, {"CaptureCTL", PA_ALSA_PROP_UCM_CAPTURE_CTL_DEVICE}, {"CaptureVolume", PA_ALSA_PROP_UCM_CAPTURE_VOLUME}, {"CaptureSwitch", PA_ALSA_PROP_UCM_CAPTURE_SWITCH}, {"CaptureMixer", PA_ALSA_PROP_UCM_CAPTURE_MIXER_DEVICE}, {"CaptureMixerElem", PA_ALSA_PROP_UCM_CAPTURE_MIXER_ELEM}, {"CaptureMasterElem", PA_ALSA_PROP_UCM_CAPTURE_MASTER_ELEM}, {"CaptureMasterType", PA_ALSA_PROP_UCM_CAPTURE_MASTER_TYPE}, {"CapturePriority", PA_ALSA_PROP_UCM_CAPTURE_PRIORITY}, {"CaptureRate", PA_ALSA_PROP_UCM_CAPTURE_RATE}, {"CaptureChannels", PA_ALSA_PROP_UCM_CAPTURE_CHANNELS}, {"TQ", PA_ALSA_PROP_UCM_QOS}, {"JackCTL", PA_ALSA_PROP_UCM_JACK_DEVICE}, {"JackControl", PA_ALSA_PROP_UCM_JACK_CONTROL}, {"JackHWMute", PA_ALSA_PROP_UCM_JACK_HW_MUTE}, {NULL, NULL}, }; /* UCM verb info - this should eventually be part of policy management */ static struct ucm_info verb_info[] = { {SND_USE_CASE_VERB_INACTIVE, 0}, {SND_USE_CASE_VERB_HIFI, 8000}, {SND_USE_CASE_VERB_HIFI_LOW_POWER, 7000}, {SND_USE_CASE_VERB_VOICE, 6000}, {SND_USE_CASE_VERB_VOICE_LOW_POWER, 5000}, {SND_USE_CASE_VERB_VOICECALL, 4000}, {SND_USE_CASE_VERB_IP_VOICECALL, 4000}, {SND_USE_CASE_VERB_ANALOG_RADIO, 3000}, {SND_USE_CASE_VERB_DIGITAL_RADIO, 3000}, {NULL, 0} }; /* UCM device info - should be overwritten by ucm property */ static struct ucm_info dev_info[] = { {SND_USE_CASE_DEV_SPEAKER, 100}, {SND_USE_CASE_DEV_LINE, 100}, {SND_USE_CASE_DEV_HEADPHONES, 100}, {SND_USE_CASE_DEV_HEADSET, 300}, {SND_USE_CASE_DEV_HANDSET, 200}, {SND_USE_CASE_DEV_BLUETOOTH, 400}, {SND_USE_CASE_DEV_EARPIECE, 100}, {SND_USE_CASE_DEV_SPDIF, 100}, {SND_USE_CASE_DEV_HDMI, 100}, {SND_USE_CASE_DEV_NONE, 100}, {NULL, 0} }; static char *ucm_verb_value( snd_use_case_mgr_t *uc_mgr, const char *verb_name, const char *id) { const char *value; char *_id = pa_sprintf_malloc("=%s//%s", id, verb_name); int err = snd_use_case_get(uc_mgr, _id, &value); pa_xfree(_id); if (err < 0) return NULL; pa_log_debug("Got %s for verb %s: %s", id, verb_name, value); /* Use the cast here to allow free() call without casting for callers. * The snd_use_case_get() returns mallocated string. * See the Note: in use-case.h for snd_use_case_get(). */ return (char *)value; } static void ucm_add_devices_to_idxset( pa_idxset *idxset, pa_alsa_ucm_device *me, pa_alsa_ucm_device *devices, const char **dev_names, int n) { pa_alsa_ucm_device *d; PA_LLIST_FOREACH(d, devices) { const char *name; int i; if (d == me) continue; name = pa_proplist_gets(d->proplist, PA_ALSA_PROP_UCM_NAME); for (i = 0; i < n; i++) if (pa_streq(dev_names[i], name)) pa_idxset_put(idxset, d, NULL); } } /* Split a string into words. Like pa_split_spaces() but handle '' and "". */ static char *ucm_split_devnames(const char *c, const char **state) { const char *current = *state ? *state : c; char h; size_t l; if (!*current || *c == 0) return NULL; current += strspn(current, "\n\r \t"); h = *current; if (h == '\'' || h =='"') { c = ++current; for (l = 0; *c && *c != h; l++) c++; if (*c != h) return NULL; *state = c + 1; } else { l = strcspn(current, "\n\r \t"); *state = current+l; } return pa_xstrndup(current, l); } static void ucm_volume_free(pa_alsa_ucm_volume *vol) { pa_assert(vol); pa_xfree(vol->mixer_elem); pa_xfree(vol->master_elem); pa_xfree(vol->master_type); pa_xfree(vol); } /* Get the volume identifier */ static char *ucm_get_mixer_id( pa_alsa_ucm_device *device, const char *mprop, const char *cprop, const char *cid) { #if SND_LIB_VERSION >= 0x10201 /* alsa-lib-1.2.1+ check */ snd_ctl_elem_id_t *ctl; int err; #endif const char *value; char *value2; int index; /* mixer element as first, if it's found, return it without modifications */ value = pa_proplist_gets(device->proplist, mprop); if (value) return pa_xstrdup(value); /* fallback, get the control element identifier */ /* and try to do some heuristic to determine the mixer element name */ value = pa_proplist_gets(device->proplist, cprop); if (value == NULL) return NULL; #if SND_LIB_VERSION >= 0x10201 /* alsa-lib-1.2.1+ check */ /* The new parser may return also element index. */ snd_ctl_elem_id_alloca(&ctl); err = snd_use_case_parse_ctl_elem_id(ctl, cid, value); if (err < 0) return NULL; value = snd_ctl_elem_id_get_name(ctl); index = snd_ctl_elem_id_get_index(ctl); #else #warning "Upgrade to alsa-lib 1.2.1!" index = 0; #endif if (!(value2 = pa_str_strip_suffix(value, " Playback Volume"))) if (!(value2 = pa_str_strip_suffix(value, " Capture Volume"))) if (!(value2 = pa_str_strip_suffix(value, " Volume"))) value2 = pa_xstrdup(value); if (index > 0) { char *mix = pa_sprintf_malloc("'%s',%d", value2, index); pa_xfree(value2); return mix; } return value2; } /* Get the volume identifier */ static pa_alsa_ucm_volume *ucm_get_mixer_volume( pa_alsa_ucm_device *device, const char *mprop, const char *cprop, const char *cid, const char *masterid, const char *mastertype) { pa_alsa_ucm_volume *vol; char *mixer_elem; mixer_elem = ucm_get_mixer_id(device, mprop, cprop, cid); if (mixer_elem == NULL) return NULL; vol = pa_xnew0(pa_alsa_ucm_volume, 1); if (vol == NULL) { pa_xfree(mixer_elem); return NULL; } vol->mixer_elem = mixer_elem; vol->master_elem = pa_xstrdup(pa_proplist_gets(device->proplist, masterid)); vol->master_type = pa_xstrdup(pa_proplist_gets(device->proplist, mastertype)); return vol; } /* Get the ALSA mixer device for the UCM device */ static const char *get_mixer_device(pa_alsa_ucm_device *dev, bool is_sink) { const char *dev_name; if (is_sink) { dev_name = pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_PLAYBACK_MIXER_DEVICE); if (!dev_name) dev_name = pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_PLAYBACK_CTL_DEVICE); } else { dev_name = pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_CAPTURE_MIXER_DEVICE); if (!dev_name) dev_name = pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_CAPTURE_CTL_DEVICE); } return dev_name; } /* Get the ALSA mixer device for the UCM jack */ static const char *get_jack_mixer_device(pa_alsa_ucm_device *dev, bool is_sink) { const char *dev_name = pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_JACK_DEVICE); if (!dev_name) return get_mixer_device(dev, is_sink); return dev_name; } static PA_PRINTF_FUNC(2,3) const char *ucm_get_string(snd_use_case_mgr_t *uc_mgr, const char *fmt, ...) { char *id; const char *value; va_list args; int err; va_start(args, fmt); id = pa_vsprintf_malloc(fmt, args); va_end(args); err = snd_use_case_get(uc_mgr, id, &value); if (err >= 0) pa_log_debug("Got %s: %s", id, value); pa_xfree(id); if (err < 0) { errno = -err; return NULL; } return value; } static pa_alsa_ucm_split *ucm_get_split_channels(pa_alsa_ucm_device *device, snd_use_case_mgr_t *uc_mgr, const char *prefix) { pa_alsa_ucm_split *split; const char *value; const char *device_name; int i; uint32_t hw_channels; const char *pcm_name; const char *rule_name; if (spa_streq(prefix, "Playback")) pcm_name = pa_proplist_gets(device->proplist, PA_ALSA_PROP_UCM_SINK); else pcm_name = pa_proplist_gets(device->proplist, PA_ALSA_PROP_UCM_SOURCE); if (!pcm_name) pcm_name = ""; device_name = pa_proplist_gets(device->proplist, PA_ALSA_PROP_UCM_NAME); if (!device_name) return NULL; value = ucm_get_string(uc_mgr, "%sChannels/%s", prefix, device_name); if (pa_atou(value, &hw_channels) < 0) return NULL; split = pa_xnew0(pa_alsa_ucm_split, 1); for (i = 0; i < PA_CHANNELS_MAX; i++) { uint32_t idx; snd_pcm_chmap_t *map; value = ucm_get_string(uc_mgr, "%sChannel%d/%s", prefix, i, device_name); if (pa_atou(value, &idx) < 0) break; if (idx >= hw_channels) { pa_log_notice("Error in ALSA UCM profile for %s (%s): %sChannel%d=%d >= %sChannels=%d", pcm_name, device_name, prefix, i, idx, prefix, hw_channels); split->broken = true; } value = ucm_get_string(uc_mgr, "%sChannelPos%d/%s", prefix, i, device_name); if (!value) { rule_name = "ChannelPos"; goto fail; } map = snd_pcm_chmap_parse_string(value); if (!map) { rule_name = "ChannelPos value"; goto fail; } if (map->channels == 1) { pa_log_debug("Split %s channel %d -> device %s channel %d: %s (%d)", prefix, (int)idx, device_name, i, value, map->pos[0]); split->idx[i] = idx; split->pos[i] = map->pos[0]; free(map); } else { free(map); rule_name = "channel map parsing"; goto fail; } } if (i == 0) { pa_xfree(split); return NULL; } split->channels = i; split->hw_channels = hw_channels; return split; fail: pa_log_warn("Invalid SplitPCM ALSA UCM %s for device %s (%s)", rule_name, pcm_name, device_name); pa_xfree(split); return NULL; } /* Create a property list for this ucm device */ static int ucm_get_device_property( pa_alsa_ucm_device *device, snd_use_case_mgr_t *uc_mgr, pa_alsa_ucm_verb *verb, const char *device_name) { const char *value; const char **devices; char *id, *s; int i; int err; uint32_t ui; int n_confdev, n_suppdev; pa_alsa_ucm_volume *vol; /* determine the device type */ device->type = PA_DEVICE_PORT_TYPE_UNKNOWN; id = s = pa_xstrdup(device_name); while (s && *s && isalpha(*s)) s++; if (s) *s = '\0'; for (i = 0; types[i].prefix; i++) if (pa_streq(id, types[i].prefix)) { device->type = types[i].type; break; } pa_xfree(id); /* set properties */ for (i = 0; item[i].id; i++) { id = pa_sprintf_malloc("%s/%s", item[i].id, device_name); err = snd_use_case_get(uc_mgr, id, &value); pa_xfree(id); if (err < 0) continue; pa_log_debug("Got %s for device %s: %s", item[i].id, device_name, value); pa_proplist_sets(device->proplist, item[i].property, value); free((void*)value); } /* get direction and channels */ value = pa_proplist_gets(device->proplist, PA_ALSA_PROP_UCM_PLAYBACK_CHANNELS); if (value) { /* output */ /* get channels */ if (pa_atou(value, &ui) == 0 && pa_channels_valid(ui)) device->playback_channels = ui; else pa_log("UCM playback channels %s for device %s out of range", value, device_name); /* get pcm */ value = pa_proplist_gets(device->proplist, PA_ALSA_PROP_UCM_SINK); if (!value) /* take pcm from verb playback default */ pa_log("UCM playback device %s fetch pcm failed", device_name); } if (pa_proplist_gets(device->proplist, PA_ALSA_PROP_UCM_SINK) && device->playback_channels == 0) { pa_log_info("UCM file does not specify 'PlaybackChannels' " "for device %s, assuming stereo.", device_name); device->playback_channels = 2; } value = pa_proplist_gets(device->proplist, PA_ALSA_PROP_UCM_CAPTURE_CHANNELS); if (value) { /* input */ /* get channels */ if (pa_atou(value, &ui) == 0 && pa_channels_valid(ui)) device->capture_channels = ui; else pa_log("UCM capture channels %s for device %s out of range", value, device_name); /* get pcm */ value = pa_proplist_gets(device->proplist, PA_ALSA_PROP_UCM_SOURCE); if (!value) /* take pcm from verb capture default */ pa_log("UCM capture device %s fetch pcm failed", device_name); } if (pa_proplist_gets(device->proplist, PA_ALSA_PROP_UCM_SOURCE) && device->capture_channels == 0) { pa_log_info("UCM file does not specify 'CaptureChannels' " "for device %s, assuming stereo.", device_name); device->capture_channels = 2; } /* get rate and priority of device */ if (device->playback_channels) { /* sink device */ /* get rate */ if ((value = pa_proplist_gets(device->proplist, PA_ALSA_PROP_UCM_PLAYBACK_RATE))) { if (pa_atou(value, &ui) == 0 && pa_sample_rate_valid(ui)) { pa_log_debug("UCM playback device %s rate %d", device_name, ui); device->playback_rate = ui; } else pa_log_debug("UCM playback device %s has bad rate %s", device_name, value); } value = pa_proplist_gets(device->proplist, PA_ALSA_PROP_UCM_PLAYBACK_PRIORITY); if (value) { /* get priority from ucm config */ if (pa_atou(value, &ui) == 0) device->playback_priority = ui; else pa_log_debug("UCM playback priority %s for device %s error", value, device_name); } vol = ucm_get_mixer_volume(device, PA_ALSA_PROP_UCM_PLAYBACK_MIXER_ELEM, PA_ALSA_PROP_UCM_PLAYBACK_VOLUME, "PlaybackVolume", PA_ALSA_PROP_UCM_PLAYBACK_MASTER_ELEM, PA_ALSA_PROP_UCM_PLAYBACK_MASTER_TYPE); if (vol) pa_hashmap_put(device->playback_volumes, pa_xstrdup(pa_proplist_gets(verb->proplist, PA_ALSA_PROP_UCM_NAME)), vol); } if (device->capture_channels) { /* source device */ /* get rate */ if ((value = pa_proplist_gets(device->proplist, PA_ALSA_PROP_UCM_CAPTURE_RATE))) { if (pa_atou(value, &ui) == 0 && pa_sample_rate_valid(ui)) { pa_log_debug("UCM capture device %s rate %d", device_name, ui); device->capture_rate = ui; } else pa_log_debug("UCM capture device %s has bad rate %s", device_name, value); } value = pa_proplist_gets(device->proplist, PA_ALSA_PROP_UCM_CAPTURE_PRIORITY); if (value) { /* get priority from ucm config */ if (pa_atou(value, &ui) == 0) device->capture_priority = ui; else pa_log_debug("UCM capture priority %s for device %s error", value, device_name); } vol = ucm_get_mixer_volume(device, PA_ALSA_PROP_UCM_CAPTURE_MIXER_ELEM, PA_ALSA_PROP_UCM_CAPTURE_VOLUME, "CaptureVolume", PA_ALSA_PROP_UCM_CAPTURE_MASTER_ELEM, PA_ALSA_PROP_UCM_CAPTURE_MASTER_TYPE); if (vol) pa_hashmap_put(device->capture_volumes, pa_xstrdup(pa_proplist_gets(verb->proplist, PA_ALSA_PROP_UCM_NAME)), vol); } device->playback_split = ucm_get_split_channels(device, uc_mgr, "Playback"); device->capture_split = ucm_get_split_channels(device, uc_mgr, "Capture"); if (PA_UCM_PLAYBACK_PRIORITY_UNSET(device) || PA_UCM_CAPTURE_PRIORITY_UNSET(device)) { /* get priority from static table */ for (i = 0; dev_info[i].id; i++) { if (strcasecmp(dev_info[i].id, device_name) == 0) { PA_UCM_DEVICE_PRIORITY_SET(device, dev_info[i].priority); break; } } } if (PA_UCM_PLAYBACK_PRIORITY_UNSET(device)) { /* fall through to default priority */ device->playback_priority = 100; } if (PA_UCM_CAPTURE_PRIORITY_UNSET(device)) { /* fall through to default priority */ device->capture_priority = 100; } id = pa_sprintf_malloc("%s/%s", "_conflictingdevs", device_name); n_confdev = snd_use_case_get_list(uc_mgr, id, &devices); pa_xfree(id); device->conflicting_devices = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); if (n_confdev <= 0) pa_log_debug("No %s for device %s", "_conflictingdevs", device_name); else { ucm_add_devices_to_idxset(device->conflicting_devices, device, verb->devices, devices, n_confdev); snd_use_case_free_list(devices, n_confdev); } id = pa_sprintf_malloc("%s/%s", "_supporteddevs", device_name); n_suppdev = snd_use_case_get_list(uc_mgr, id, &devices); pa_xfree(id); device->supported_devices = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); if (n_suppdev <= 0) pa_log_debug("No %s for device %s", "_supporteddevs", device_name); else { ucm_add_devices_to_idxset(device->supported_devices, device, verb->devices, devices, n_suppdev); snd_use_case_free_list(devices, n_suppdev); } return 0; }; /* Create a property list for this ucm modifier */ static int ucm_get_modifier_property( pa_alsa_ucm_modifier *modifier, snd_use_case_mgr_t *uc_mgr, pa_alsa_ucm_verb *verb, const char *modifier_name) { const char *value; char *id; int i; const char **devices; int n_confdev, n_suppdev; for (i = 0; item[i].id; i++) { int err; id = pa_sprintf_malloc("=%s/%s", item[i].id, modifier_name); err = snd_use_case_get(uc_mgr, id, &value); pa_xfree(id); if (err < 0) continue; pa_log_debug("Got %s for modifier %s: %s", item[i].id, modifier_name, value); pa_proplist_sets(modifier->proplist, item[i].property, value); free((void*)value); } id = pa_sprintf_malloc("%s/%s", "_conflictingdevs", modifier_name); n_confdev = snd_use_case_get_list(uc_mgr, id, &devices); pa_xfree(id); modifier->conflicting_devices = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); if (n_confdev <= 0) pa_log_debug("No %s for modifier %s", "_conflictingdevs", modifier_name); else { ucm_add_devices_to_idxset(modifier->conflicting_devices, NULL, verb->devices, devices, n_confdev); snd_use_case_free_list(devices, n_confdev); } id = pa_sprintf_malloc("%s/%s", "_supporteddevs", modifier_name); n_suppdev = snd_use_case_get_list(uc_mgr, id, &devices); pa_xfree(id); modifier->supported_devices = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); if (n_suppdev <= 0) pa_log_debug("No %s for modifier %s", "_supporteddevs", modifier_name); else { ucm_add_devices_to_idxset(modifier->supported_devices, NULL, verb->devices, devices, n_suppdev); snd_use_case_free_list(devices, n_suppdev); } return 0; }; /* Create a list of devices for this verb */ static int ucm_get_devices(pa_alsa_ucm_verb *verb, snd_use_case_mgr_t *uc_mgr) { const char **dev_list; int num_dev, i; num_dev = snd_use_case_get_list(uc_mgr, "_devices", &dev_list); if (num_dev < 0) return num_dev; for (i = 0; i < num_dev; i += 2) { pa_alsa_ucm_device *d = pa_xnew0(pa_alsa_ucm_device, 1); d->proplist = pa_proplist_new(); pa_proplist_sets(d->proplist, PA_ALSA_PROP_UCM_NAME, pa_strnull(dev_list[i])); pa_proplist_sets(d->proplist, PA_ALSA_PROP_UCM_DESCRIPTION, pa_strna(dev_list[i + 1])); d->ucm_ports = pa_dynarray_new(NULL); d->hw_mute_jacks = pa_dynarray_new(NULL); d->available = PA_AVAILABLE_UNKNOWN; d->playback_volumes = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, pa_xfree, (pa_free_cb_t) ucm_volume_free); d->capture_volumes = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, pa_xfree, (pa_free_cb_t) ucm_volume_free); PA_LLIST_PREPEND(pa_alsa_ucm_device, verb->devices, d); } snd_use_case_free_list(dev_list, num_dev); return 0; }; static long ucm_device_status(pa_alsa_ucm_config *ucm, pa_alsa_ucm_device *dev) { const char *dev_name = pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_NAME); char *devstatus; long status = 0; if (!ucm->active_verb) { pa_log_error("Failed to get status for UCM device %s: no UCM verb set", dev_name); return -1; } devstatus = pa_sprintf_malloc("_devstatus/%s", dev_name); if (snd_use_case_geti(ucm->ucm_mgr, devstatus, &status) < 0) { pa_log_debug("Failed to get status for UCM device %s", dev_name); status = -1; } pa_xfree(devstatus); return status; } static int ucm_device_disable(pa_alsa_ucm_config *ucm, pa_alsa_ucm_device *dev) { const char *dev_name = pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_NAME); if (!ucm->active_verb) { pa_log_error("Failed to disable UCM device %s: no UCM verb set", dev_name); return -1; } /* If any of dev's conflicting devices is enabled, trying to disable * dev gives an error despite the fact that it's already disabled. * Check that dev is enabled to avoid this error. */ if (ucm_device_status(ucm, dev) == 0) { pa_log_debug("UCM device %s is already disabled", dev_name); return 0; } pa_log_debug("Disabling UCM device %s", dev_name); if (snd_use_case_set(ucm->ucm_mgr, "_disdev", dev_name) < 0) { pa_log("Failed to disable UCM device %s", dev_name); return -1; } return 0; } static int ucm_device_enable(pa_alsa_ucm_config *ucm, pa_alsa_ucm_device *dev) { const char *dev_name = pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_NAME); if (!ucm->active_verb) { pa_log_error("Failed to enable UCM device %s: no UCM verb set", dev_name); return -1; } /* We don't need to enable devices that are already enabled */ if (ucm_device_status(ucm, dev) > 0) { pa_log_debug("UCM device %s is already enabled", dev_name); return 0; } pa_log_debug("Enabling UCM device %s", dev_name); if (snd_use_case_set(ucm->ucm_mgr, "_enadev", dev_name) < 0) { pa_log("Failed to enable UCM device %s", dev_name); return -1; } return 0; } static int ucm_get_modifiers(pa_alsa_ucm_verb *verb, snd_use_case_mgr_t *uc_mgr) { const char **mod_list; int num_mod, i; num_mod = snd_use_case_get_list(uc_mgr, "_modifiers", &mod_list); if (num_mod < 0) return num_mod; for (i = 0; i < num_mod; i += 2) { pa_alsa_ucm_modifier *m; if (!mod_list[i]) { pa_log_warn("Got a modifier with a null name. Skipping."); continue; } m = pa_xnew0(pa_alsa_ucm_modifier, 1); m->proplist = pa_proplist_new(); pa_proplist_sets(m->proplist, PA_ALSA_PROP_UCM_NAME, mod_list[i]); pa_proplist_sets(m->proplist, PA_ALSA_PROP_UCM_DESCRIPTION, pa_strna(mod_list[i + 1])); PA_LLIST_PREPEND(pa_alsa_ucm_modifier, verb->modifiers, m); } snd_use_case_free_list(mod_list, num_mod); return 0; }; static long ucm_modifier_status(pa_alsa_ucm_config *ucm, pa_alsa_ucm_modifier *mod) { const char *mod_name = pa_proplist_gets(mod->proplist, PA_ALSA_PROP_UCM_NAME); char *modstatus; long status = 0; if (!ucm->active_verb) { pa_log_error("Failed to get status for UCM modifier %s: no UCM verb set", mod_name); return -1; } modstatus = pa_sprintf_malloc("_modstatus/%s", mod_name); if (snd_use_case_geti(ucm->ucm_mgr, modstatus, &status) < 0) { pa_log_debug("Failed to get status for UCM modifier %s", mod_name); status = -1; } pa_xfree(modstatus); return status; } static int ucm_modifier_disable(pa_alsa_ucm_config *ucm, pa_alsa_ucm_modifier *mod) { const char *mod_name = pa_proplist_gets(mod->proplist, PA_ALSA_PROP_UCM_NAME); if (!ucm->active_verb) { pa_log_error("Failed to disable UCM modifier %s: no UCM verb set", mod_name); return -1; } /* We don't need to disable modifiers that are already disabled */ if (ucm_modifier_status(ucm, mod) == 0) { pa_log_debug("UCM modifier %s is already disabled", mod_name); return 0; } pa_log_debug("Disabling UCM modifier %s", mod_name); if (snd_use_case_set(ucm->ucm_mgr, "_dismod", mod_name) < 0) { pa_log("Failed to disable UCM modifier %s", mod_name); return -1; } return 0; } static int ucm_modifier_enable(pa_alsa_ucm_config *ucm, pa_alsa_ucm_modifier *mod) { const char *mod_name = pa_proplist_gets(mod->proplist, PA_ALSA_PROP_UCM_NAME); if (!ucm->active_verb) { pa_log_error("Failed to enable UCM modifier %s: no UCM verb set", mod_name); return -1; } /* We don't need to enable modifiers that are already enabled */ if (ucm_modifier_status(ucm, mod) > 0) { pa_log_debug("UCM modifier %s is already enabled", mod_name); return 0; } pa_log_debug("Enabling UCM modifier %s", mod_name); if (snd_use_case_set(ucm->ucm_mgr, "_enamod", mod_name) < 0) { pa_log("Failed to enable UCM modifier %s", mod_name); return -1; } return 0; } static void add_role_to_device(pa_alsa_ucm_device *dev, const char *dev_name, const char *role_name, const char *role) { const char *cur = pa_proplist_gets(dev->proplist, role_name); if (!cur) pa_proplist_sets(dev->proplist, role_name, role); else if (!pa_str_in_list_spaces(cur, role)) { /* does not exist */ char *value = pa_sprintf_malloc("%s %s", cur, role); pa_proplist_sets(dev->proplist, role_name, value); pa_xfree(value); } pa_log_info("Add role %s to device %s(%s), result %s", role, dev_name, role_name, pa_proplist_gets(dev->proplist, role_name)); } static void add_media_role(pa_alsa_ucm_device *dev, const char *role_name, const char *role, bool is_sink) { const char *dev_name = pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_NAME); const char *sink = pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_SINK); const char *source = pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_SOURCE); if (is_sink && sink) add_role_to_device(dev, dev_name, role_name, role); else if (!is_sink && source) add_role_to_device(dev, dev_name, role_name, role); } static char *modifier_name_to_role(const char *mod_name, bool *is_sink) { char *sub = NULL, *tmp, *pos; *is_sink = false; if (pa_startswith(mod_name, "Play")) { *is_sink = true; sub = pa_xstrdup(mod_name + 4); } else if (pa_startswith(mod_name, "Capture")) sub = pa_xstrdup(mod_name + 7); pos = sub; while (pos && *pos == ' ') pos++; if (!pos || !*pos) { pa_xfree(sub); pa_log_warn("Can't match media roles for modifier %s", mod_name); return NULL; } tmp = pos; do { *tmp = tolower(*tmp); } while (*(++tmp)); tmp = pa_xstrdup(pos); pa_xfree(sub); return tmp; } static void ucm_set_media_roles(pa_alsa_ucm_modifier *modifier, const char *mod_name) { pa_alsa_ucm_device *dev; bool is_sink = false; char *sub = NULL; const char *role_name; uint32_t idx; sub = modifier_name_to_role(mod_name, &is_sink); if (!sub) return; modifier->action_direction = is_sink ? PA_DIRECTION_OUTPUT : PA_DIRECTION_INPUT; modifier->media_role = sub; role_name = is_sink ? PA_ALSA_PROP_UCM_PLAYBACK_ROLES : PA_ALSA_PROP_UCM_CAPTURE_ROLES; PA_IDXSET_FOREACH(dev, modifier->supported_devices, idx) { /* if modifier has no specific pcm, we add role intent to its supported devices */ if (!pa_proplist_gets(modifier->proplist, PA_ALSA_PROP_UCM_SINK) && !pa_proplist_gets(modifier->proplist, PA_ALSA_PROP_UCM_SOURCE)) add_media_role(dev, role_name, sub, is_sink); } } static void append_lost_relationship(pa_alsa_ucm_device *dev) { uint32_t idx; pa_alsa_ucm_device *d; PA_IDXSET_FOREACH(d, dev->conflicting_devices, idx) if (pa_idxset_put(d->conflicting_devices, dev, NULL) == 0) pa_log_warn("Add lost conflicting device %s to %s", pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_NAME), pa_proplist_gets(d->proplist, PA_ALSA_PROP_UCM_NAME)); PA_IDXSET_FOREACH(d, dev->supported_devices, idx) if (pa_idxset_put(d->supported_devices, dev, NULL) == 0) pa_log_warn("Add lost supported device %s to %s", pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_NAME), pa_proplist_gets(d->proplist, PA_ALSA_PROP_UCM_NAME)); } int pa_alsa_ucm_query_profiles(pa_alsa_ucm_config *ucm, int card_index) { char *card_name; const char **verb_list, *value; int num_verbs, i, err = 0; const char *split_prefix = ucm->split_enable ? "<<>>" : ""; /* support multiple card instances, address card directly by index */ card_name = pa_sprintf_malloc("%shw:%i", split_prefix, card_index); if (card_name == NULL) return -PA_ALSA_ERR_UNSPECIFIED; err = snd_use_case_mgr_open(&ucm->ucm_mgr, card_name); if (err < 0) { char *ucm_card_name; /* fallback longname: is UCM available for this card ? */ pa_xfree(card_name); err = snd_card_get_name(card_index, &ucm_card_name); if (err < 0) { pa_log("Card can't get card_name from card_index %d", card_index); err = -PA_ALSA_ERR_UNSPECIFIED; goto name_fail; } card_name = pa_sprintf_malloc("%s%s", split_prefix, ucm_card_name); free(ucm_card_name); if (card_name == NULL) { err = -PA_ALSA_ERR_UNSPECIFIED; goto name_fail; } err = snd_use_case_mgr_open(&ucm->ucm_mgr, card_name); if (err < 0) { pa_log_info("UCM not available for card %s", card_name); err = -PA_ALSA_ERR_UCM_OPEN; goto ucm_mgr_fail; } } err = snd_use_case_get(ucm->ucm_mgr, "=Linked", &value); if (err >= 0) { if (strcasecmp(value, "true") == 0 || strcasecmp(value, "1") == 0) { free((void *)value); pa_log_info("Empty (linked) UCM for card %s", card_name); err = -PA_ALSA_ERR_UCM_LINKED; goto ucm_verb_fail; } free((void *)value); } pa_log_info("UCM available for card %s", card_name); if (snd_use_case_get(ucm->ucm_mgr, "_alibpref", &value) == 0) { if (value[0]) { ucm->alib_prefix = pa_xstrdup(value); pa_log_debug("UCM _alibpref=%s", ucm->alib_prefix); } free((void *)value); } /* get a list of all UCM verbs for this card */ num_verbs = snd_use_case_verb_list(ucm->ucm_mgr, &verb_list); if (num_verbs < 0) { pa_log("UCM verb list not found for %s", card_name); err = -PA_ALSA_ERR_UNSPECIFIED; goto ucm_verb_fail; } /* get the properties of each UCM verb */ for (i = 0; i < num_verbs; i += 2) { pa_alsa_ucm_verb *verb; /* Get devices and modifiers for each verb */ err = pa_alsa_ucm_get_verb(ucm->ucm_mgr, verb_list[i], verb_list[i+1], &verb); if (err < 0) { pa_log("Failed to get the verb %s", verb_list[i]); continue; } PA_LLIST_PREPEND(pa_alsa_ucm_verb, ucm->verbs, verb); } if (!ucm->verbs) { pa_log("No UCM verb is valid for %s", card_name); err = -PA_ALSA_ERR_UCM_NO_VERB; } snd_use_case_free_list(verb_list, num_verbs); ucm_verb_fail: if (err < 0) { snd_use_case_mgr_close(ucm->ucm_mgr); ucm->ucm_mgr = NULL; } ucm_mgr_fail: pa_xfree(card_name); name_fail: return err; } static void ucm_verb_set_split_leaders(pa_alsa_ucm_verb *verb) { pa_alsa_ucm_device *d, *d2; /* Set first virtual device in each split HW PCM as the split leader */ PA_LLIST_FOREACH(d, verb->devices) { if (d->playback_split) d->playback_split->leader = true; if (d->capture_split) d->capture_split->leader = true; } PA_LLIST_FOREACH(d, verb->devices) { const char *sink = pa_proplist_gets(d->proplist, PA_ALSA_PROP_UCM_SINK); const char *source = pa_proplist_gets(d->proplist, PA_ALSA_PROP_UCM_SOURCE); if (d->playback_split) { if (!sink) d->playback_split->leader = false; if (d->playback_split->leader) { PA_LLIST_FOREACH(d2, verb->devices) { const char *sink2 = pa_proplist_gets(d2->proplist, PA_ALSA_PROP_UCM_SINK); if (d == d2 || !d2->playback_split || !sink || !sink2 || !pa_streq(sink, sink2)) continue; d2->playback_split->leader = false; } } } if (d->capture_split) { if (!source) d->capture_split->leader = false; if (d->capture_split->leader) { PA_LLIST_FOREACH(d2, verb->devices) { const char *source2 = pa_proplist_gets(d2->proplist, PA_ALSA_PROP_UCM_SOURCE); if (d == d2 || !d2->capture_split || !source || !source2 || !pa_streq(source, source2)) continue; d2->capture_split->leader = false; } } } } } int pa_alsa_ucm_get_verb(snd_use_case_mgr_t *uc_mgr, const char *verb_name, const char *verb_desc, pa_alsa_ucm_verb **p_verb) { pa_alsa_ucm_device *d; pa_alsa_ucm_modifier *mod; pa_alsa_ucm_verb *verb; char *value; unsigned ui; int err = 0; *p_verb = NULL; pa_log_info("Set UCM verb to %s", verb_name); err = snd_use_case_set(uc_mgr, "_verb", verb_name); if (err < 0) return err; verb = pa_xnew0(pa_alsa_ucm_verb, 1); verb->proplist = pa_proplist_new(); pa_proplist_sets(verb->proplist, PA_ALSA_PROP_UCM_NAME, pa_strnull(verb_name)); pa_proplist_sets(verb->proplist, PA_ALSA_PROP_UCM_DESCRIPTION, pa_strna(verb_desc)); value = ucm_verb_value(uc_mgr, verb_name, "Priority"); if (value && !pa_atou(value, &ui)) verb->priority = ui > 10000 ? 10000 : ui; free(value); err = ucm_get_devices(verb, uc_mgr); if (err < 0) pa_log("No UCM devices for verb %s", verb_name); err = ucm_get_modifiers(verb, uc_mgr); if (err < 0) pa_log("No UCM modifiers for verb %s", verb_name); PA_LLIST_FOREACH(d, verb->devices) { const char *dev_name = pa_proplist_gets(d->proplist, PA_ALSA_PROP_UCM_NAME); /* Devices properties */ ucm_get_device_property(d, uc_mgr, verb, dev_name); } ucm_verb_set_split_leaders(verb); /* make conflicting or supported device mutual */ PA_LLIST_FOREACH(d, verb->devices) append_lost_relationship(d); PA_LLIST_FOREACH(mod, verb->modifiers) { const char *mod_name = pa_proplist_gets(mod->proplist, PA_ALSA_PROP_UCM_NAME); /* Modifier properties */ ucm_get_modifier_property(mod, uc_mgr, verb, mod_name); /* Set PA_PROP_DEVICE_INTENDED_ROLES property to devices */ pa_log_debug("Set media roles for verb %s, modifier %s", verb_name, mod_name); ucm_set_media_roles(mod, mod_name); } *p_verb = verb; return 0; } static int pa_alsa_ucm_device_cmp(const void *a, const void *b) { const pa_alsa_ucm_device *d1 = *(pa_alsa_ucm_device **)a; const pa_alsa_ucm_device *d2 = *(pa_alsa_ucm_device **)b; return strcmp(pa_proplist_gets(d1->proplist, PA_ALSA_PROP_UCM_NAME), pa_proplist_gets(d2->proplist, PA_ALSA_PROP_UCM_NAME)); } static void set_eld_devices(pa_hashmap *hash) { pa_device_port *port; pa_alsa_ucm_port_data *data; pa_alsa_ucm_device *dev; void *state; PA_HASHMAP_FOREACH(port, hash, state) { data = PA_DEVICE_PORT_DATA(port); dev = data->device; data->eld_device = dev->eld_device; if (data->eld_mixer_device_name) pa_xfree(data->eld_mixer_device_name); data->eld_mixer_device_name = pa_xstrdup(dev->eld_mixer_device_name); } } static void update_mixer_paths(pa_hashmap *ports, const char *verb_name) { pa_device_port *port; pa_alsa_ucm_port_data *data; void *state; /* select volume controls on ports */ PA_HASHMAP_FOREACH(port, ports, state) { pa_log_info("Updating mixer path for %s: %s", verb_name, port->name); data = PA_DEVICE_PORT_DATA(port); data->path = pa_hashmap_get(data->paths, verb_name); } } static void probe_volumes(pa_hashmap *hash, bool is_sink, snd_pcm_t *pcm_handle, pa_hashmap *mixers, bool ignore_dB) { pa_device_port *port; pa_alsa_path *path; pa_alsa_ucm_port_data *data; pa_alsa_ucm_device *dev; snd_mixer_t *mixer_handle; const char *verb_name, *mdev; void *state, *state2; PA_HASHMAP_FOREACH(port, hash, state) { data = PA_DEVICE_PORT_DATA(port); dev = data->device; mdev = get_mixer_device(dev, is_sink); if (mdev == NULL || !(mixer_handle = pa_alsa_open_mixer_by_name(mixers, mdev, true))) { pa_log_error("Failed to find a working mixer device (%s).", mdev); goto fail; } PA_HASHMAP_FOREACH_KV(verb_name, path, data->paths, state2) { if (pa_alsa_path_probe(path, NULL, mixer_handle, ignore_dB) < 0) { pa_log_warn("Could not probe path: %s, using s/w volume", path->name); pa_hashmap_remove(data->paths, verb_name); } else if (!path->has_volume && !path->has_mute) { pa_log_warn("Path %s is not a volume or mute control", path->name); pa_hashmap_remove(data->paths, verb_name); } else pa_log_debug("Set up h/w %s using '%s' for %s:%s", path->has_volume ? "volume" : "mute", path->name, verb_name, port->name); } } return; fail: /* We could not probe the paths we created. Free them and revert to software volumes. */ PA_HASHMAP_FOREACH(port, hash, state) { data = PA_DEVICE_PORT_DATA(port); pa_hashmap_remove_all(data->paths); } } static void proplist_set_icon_name( pa_proplist *proplist, pa_device_port_type_t type, bool is_sink) { const char *icon; if (is_sink) { switch (type) { case PA_DEVICE_PORT_TYPE_HEADPHONES: icon = "audio-headphones"; break; case PA_DEVICE_PORT_TYPE_HDMI: icon = "video-display"; break; case PA_DEVICE_PORT_TYPE_SPEAKER: default: icon = "audio-speakers"; break; } } else { switch (type) { case PA_DEVICE_PORT_TYPE_HEADSET: icon = "audio-headset"; break; case PA_DEVICE_PORT_TYPE_MIC: default: icon = "audio-input-microphone"; break; } } pa_proplist_sets(proplist, "device.icon_name", icon); } static char *devset_name(pa_idxset *devices, const char *sep) { int i = 0; int num = pa_idxset_size(devices); pa_alsa_ucm_device *sorted[num], *dev; char *dev_names = NULL; char *tmp = NULL; uint32_t idx; PA_IDXSET_FOREACH(dev, devices, idx) { sorted[i] = dev; i++; } /* Sort by alphabetical order so as to have a deterministic naming scheme */ qsort(&sorted[0], num, sizeof(pa_alsa_ucm_device *), pa_alsa_ucm_device_cmp); for (i = 0; i < num; i++) { dev = sorted[i]; const char *dev_name = pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_NAME); if (!dev_names) { dev_names = pa_xstrdup(dev_name); } else { tmp = pa_sprintf_malloc("%s%s%s", dev_names, sep, dev_name); pa_xfree(dev_names); dev_names = tmp; } } return dev_names; } PA_UNUSED static char *devset_description(pa_idxset *devices, const char *sep) { int i = 0; int num = pa_idxset_size(devices); pa_alsa_ucm_device *sorted[num], *dev; char *dev_descs = NULL; char *tmp = NULL; uint32_t idx; PA_IDXSET_FOREACH(dev, devices, idx) { sorted[i] = dev; i++; } /* Sort by alphabetical order to match devset_name() */ qsort(&sorted[0], num, sizeof(pa_alsa_ucm_device *), pa_alsa_ucm_device_cmp); for (i = 0; i < num; i++) { dev = sorted[i]; const char *dev_desc = pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_DESCRIPTION); if (!dev_descs) { dev_descs = pa_xstrdup(dev_desc); } else { tmp = pa_sprintf_malloc("%s%s%s", dev_descs, sep, dev_desc); pa_xfree(dev_descs); dev_descs = tmp; } } return dev_descs; } /* If invert is true, uses the formula 1/p = 1/p1 + 1/p2 + ... 1/pn. * This way, the result will always be less than the individual components, * yet higher components will lead to higher result. */ static unsigned devset_playback_priority(pa_idxset *devices, bool invert) { pa_alsa_ucm_device *dev; uint32_t idx; double priority = 0; PA_IDXSET_FOREACH(dev, devices, idx) { if (dev->playback_priority > 0 && invert) priority += 1.0 / dev->playback_priority; else priority += dev->playback_priority; } if (priority > 0 && invert) return (unsigned)(1.0 / priority); return (unsigned) priority; } static unsigned devset_capture_priority(pa_idxset *devices, bool invert) { pa_alsa_ucm_device *dev; uint32_t idx; double priority = 0; PA_IDXSET_FOREACH(dev, devices, idx) { if (dev->capture_priority > 0 && invert) priority += 1.0 / dev->capture_priority; else priority += dev->capture_priority; } if (priority > 0 && invert) return (unsigned)(1.0 / priority); return (unsigned) priority; } static void ucm_add_port_props( pa_device_port *port, bool is_sink) { proplist_set_icon_name(port->proplist, port->type, is_sink); } void pa_alsa_ucm_add_port( pa_hashmap *hash, pa_alsa_ucm_mapping_context *context, bool is_sink, pa_hashmap *ports, pa_card_profile *cp, pa_core *core) { pa_device_port *port; unsigned priority; char *name, *desc; const char *dev_name; const char *direction; const char *verb_name; pa_alsa_ucm_device *dev; pa_alsa_ucm_port_data *data; pa_alsa_ucm_volume *vol; pa_alsa_jack *jack; pa_device_port_type_t type; void *state; dev = context->ucm_device; if (!dev) return; dev_name = pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_NAME); name = pa_sprintf_malloc("%s%s", is_sink ? PA_UCM_PRE_TAG_OUTPUT : PA_UCM_PRE_TAG_INPUT, dev_name); desc = pa_xstrdup(pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_DESCRIPTION)); priority = is_sink ? dev->playback_priority : dev->capture_priority; jack = ucm_get_jack(context->ucm, dev); type = dev->type; port = pa_hashmap_get(ports, name); if (!port) { pa_device_port_new_data port_data; pa_device_port_new_data_init(&port_data); pa_device_port_new_data_set_name(&port_data, name); pa_device_port_new_data_set_description(&port_data, desc); pa_device_port_new_data_set_type(&port_data, type); pa_device_port_new_data_set_direction(&port_data, is_sink ? PA_DIRECTION_OUTPUT : PA_DIRECTION_INPUT); if (jack) pa_device_port_new_data_set_availability_group(&port_data, jack->name); port = pa_device_port_new(core, &port_data, sizeof(pa_alsa_ucm_port_data)); pa_device_port_new_data_done(&port_data); data = PA_DEVICE_PORT_DATA(port); ucm_port_data_init(data, context->ucm, port, dev); port->impl_free = ucm_port_data_free; pa_hashmap_put(ports, port->name, port); pa_log_debug("Add port %s: %s", port->name, port->description); ucm_add_port_props(port, is_sink); } data = PA_DEVICE_PORT_DATA(port); PA_HASHMAP_FOREACH_KV(verb_name, vol, is_sink ? dev->playback_volumes : dev->capture_volumes, state) { if (pa_hashmap_get(data->paths, verb_name)) continue; pa_alsa_path *path = pa_alsa_path_synthesize(vol->mixer_elem, is_sink ? PA_ALSA_DIRECTION_OUTPUT : PA_ALSA_DIRECTION_INPUT); if (!path) pa_log_warn("Failed to set up volume control: %s", vol->mixer_elem); else { if (vol->master_elem) { pa_alsa_element *e = pa_alsa_element_get(path, vol->master_elem, false); e->switch_use = PA_ALSA_SWITCH_MUTE; e->volume_use = PA_ALSA_VOLUME_MERGE; } pa_hashmap_put(data->paths, pa_xstrdup(verb_name), path); /* Add path also to already created empty path set */ if (is_sink) pa_hashmap_put(dev->playback_mapping->output_path_set->paths, pa_xstrdup(vol->mixer_elem), path); else pa_hashmap_put(dev->capture_mapping->input_path_set->paths, pa_xstrdup(vol->mixer_elem), path); } } port->priority = priority; pa_xfree(name); pa_xfree(desc); direction = is_sink ? "output" : "input"; pa_log_debug("Port %s direction %s, priority %d", port->name, direction, priority); if (cp) { pa_log_debug("Adding profile %s to port %s.", cp->name, port->name); pa_hashmap_put(port->profiles, cp->name, cp); } if (hash) { pa_hashmap_put(hash, port->name, port); } /* ELD devices */ set_eld_devices(ports); } static bool devset_supports_device(pa_idxset *devices, pa_alsa_ucm_device *dev) { const char *sink, *sink2, *source, *source2; pa_alsa_ucm_device *d; uint32_t idx; pa_assert(devices); pa_assert(dev); /* Can add anything to empty group */ if (pa_idxset_isempty(devices)) return true; /* Device already selected */ if (pa_idxset_contains(devices, dev)) return true; /* No conflicting device must already be selected */ if (!pa_idxset_isdisjoint(devices, dev->conflicting_devices)) return false; /* No already selected device must be unsupported */ if (!pa_idxset_isempty(dev->supported_devices)) if (!pa_idxset_issubset(devices, dev->supported_devices)) return false; sink = pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_SINK); source = pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_SOURCE); PA_IDXSET_FOREACH(d, devices, idx) { /* Must not be unsupported by any selected device */ if (!pa_idxset_isempty(d->supported_devices)) if (!pa_idxset_contains(d->supported_devices, dev)) return false; /* PlaybackPCM must not be the same as any selected device, except when both split */ sink2 = pa_proplist_gets(d->proplist, PA_ALSA_PROP_UCM_SINK); if (sink && sink2 && pa_streq(sink, sink2)) { if (!(dev->playback_split && d->playback_split)) return false; } /* CapturePCM must not be the same as any selected device, except when both split */ source2 = pa_proplist_gets(d->proplist, PA_ALSA_PROP_UCM_SOURCE); if (source && source2 && pa_streq(source, source2)) { if (!(dev->capture_split && d->capture_split)) return false; } } return true; } /* Iterates nonempty subsets of UCM devices that can be simultaneously * used, including subsets of previously returned subsets. At start, * *state should be NULL. It's not safe to modify the devices argument * until iteration ends. The returned idxsets must be freed by the * caller. */ static pa_idxset *iterate_device_subsets(pa_idxset *devices, void **state) { uint32_t idx; pa_alsa_ucm_device *dev; pa_assert(devices); pa_assert(state); if (*state == NULL) { /* First iteration, start adding from first device */ *state = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); dev = pa_idxset_first(devices, &idx); } else { /* Backtrack the most recent device we added and skip it */ dev = pa_idxset_steal_last(*state, NULL); pa_idxset_get_by_data(devices, dev, &idx); if (dev) dev = pa_idxset_next(devices, &idx); } /* Try adding devices we haven't decided on yet */ for (; dev; dev = pa_idxset_next(devices, &idx)) { if (devset_supports_device(*state, dev)) pa_idxset_put(*state, dev, NULL); } if (pa_idxset_isempty(*state)) { /* No more choices to backtrack on, therefore no more subsets to * return after this. Don't return the empty set, instead clean * up and end iteration. */ pa_idxset_free(*state, NULL); *state = NULL; return NULL; } return pa_idxset_copy(*state, NULL); } /* This a wrapper around iterate_device_subsets() that only returns the * biggest possible groups and not any of their subsets. */ static pa_idxset *iterate_maximal_device_subsets(pa_idxset *devices, void **state) { uint32_t idx; pa_alsa_ucm_device *dev; pa_idxset *subset = NULL; pa_assert(devices); pa_assert(state); while (subset == NULL && (subset = iterate_device_subsets(devices, state))) { /* Skip this group if it's incomplete, by checking if we can add any * other device. If we can, this iteration is a subset of another * group that we already returned or eventually return. */ PA_IDXSET_FOREACH(dev, devices, idx) { if (subset && !pa_idxset_contains(subset, dev) && devset_supports_device(subset, dev)) { pa_idxset_free(subset, NULL); subset = NULL; } } } return subset; } static char* merge_roles(const char *cur, const char *add) { char *r, *ret; const char *state = NULL; if (add == NULL) return pa_xstrdup(cur); else if (cur == NULL) return pa_xstrdup(add); ret = pa_xstrdup(cur); while ((r = pa_split_spaces(add, &state))) { char *value; if (!pa_str_in_list_spaces(ret, r)) value = pa_sprintf_malloc("%s %s", ret, r); else { pa_xfree(r); continue; } pa_xfree(ret); ret = value; pa_xfree(r); } return ret; } void pa_alsa_ucm_add_ports( pa_hashmap **p, pa_proplist *proplist, pa_alsa_ucm_mapping_context *context, bool is_sink, pa_card *card, snd_pcm_t *pcm_handle, bool ignore_dB) { char *merged_roles; const char *role_name = is_sink ? PA_ALSA_PROP_UCM_PLAYBACK_ROLES : PA_ALSA_PROP_UCM_CAPTURE_ROLES; pa_alsa_ucm_device *dev; pa_alsa_ucm_modifier *mod; char *tmp; pa_assert(p); pa_assert(*p); /* add ports first */ pa_alsa_ucm_add_port(*p, context, is_sink, card->ports, NULL, card->core); /* now set up volume paths if any */ probe_volumes(*p, is_sink, pcm_handle, context->ucm->mixers, ignore_dB); /* probe_volumes() removes per-verb paths from ports if probing them * fails. The path for the current verb is cached in * pa_alsa_ucm_port_data.path, which is not cleared by probe_volumes() if * the path gets removed, so we have to call update_mixer_paths() here to * unset the cached path if needed. */ if (context->ucm->active_verb) { const char *verb_name; verb_name = pa_proplist_gets(context->ucm->active_verb->proplist, PA_ALSA_PROP_UCM_NAME); update_mixer_paths(*p, verb_name); } /* then set property PA_PROP_DEVICE_INTENDED_ROLES */ merged_roles = pa_xstrdup(pa_proplist_gets(proplist, PA_PROP_DEVICE_INTENDED_ROLES)); dev = context->ucm_device; if (dev) { const char *roles = pa_proplist_gets(dev->proplist, role_name); tmp = merge_roles(merged_roles, roles); pa_xfree(merged_roles); merged_roles = tmp; } mod = context->ucm_modifier; if (mod) { tmp = merge_roles(merged_roles, mod->media_role); pa_xfree(merged_roles); merged_roles = tmp; } if (merged_roles) pa_proplist_sets(proplist, PA_PROP_DEVICE_INTENDED_ROLES, merged_roles); pa_log_info("ALSA device %s roles: %s", pa_proplist_gets(proplist, PA_PROP_DEVICE_STRING), pa_strnull(merged_roles)); pa_xfree(merged_roles); } /* Change UCM verb and device to match selected card profile */ int pa_alsa_ucm_set_profile(pa_alsa_ucm_config *ucm, pa_card *card, pa_alsa_profile *new_profile, pa_alsa_profile *old_profile) { int ret = 0; const char *verb_name, *profile_name; pa_alsa_ucm_verb *verb; pa_alsa_mapping *map; uint32_t idx; if (new_profile == old_profile) return 0; if (new_profile == NULL) { verb = NULL; profile_name = SND_USE_CASE_VERB_INACTIVE; verb_name = SND_USE_CASE_VERB_INACTIVE; } else { verb = new_profile->ucm_context.verb; profile_name = new_profile->name; verb_name = pa_proplist_gets(verb->proplist, PA_ALSA_PROP_UCM_NAME); } pa_log_info("Set profile to %s", profile_name); if (ucm->active_verb != verb) { /* change verb */ pa_log_info("Set UCM verb to %s", verb_name); if ((snd_use_case_set(ucm->ucm_mgr, "_verb", verb_name)) < 0) { pa_log("Failed to set verb %s", verb_name); ret = -1; } } else if (ucm->active_verb && old_profile) { /* Disable modifiers not in new profile. Has to be done before * devices, because _dismod fails if a modifier's supported * devices are disabled. */ PA_IDXSET_FOREACH(map, old_profile->input_mappings, idx) if (new_profile && !pa_idxset_contains(new_profile->input_mappings, map)) if (map->ucm_context.ucm_modifier && ucm_modifier_disable(ucm, map->ucm_context.ucm_modifier) < 0) ret = -1; PA_IDXSET_FOREACH(map, old_profile->output_mappings, idx) if (new_profile && !pa_idxset_contains(new_profile->output_mappings, map)) if (map->ucm_context.ucm_modifier && ucm_modifier_disable(ucm, map->ucm_context.ucm_modifier) < 0) ret = -1; /* Disable devices not in new profile */ PA_IDXSET_FOREACH(map, old_profile->input_mappings, idx) if (new_profile && !pa_idxset_contains(new_profile->input_mappings, map)) if (map->ucm_context.ucm_device && ucm_device_disable(ucm, map->ucm_context.ucm_device) < 0) ret = -1; PA_IDXSET_FOREACH(map, old_profile->output_mappings, idx) if (new_profile && !pa_idxset_contains(new_profile->output_mappings, map)) if (map->ucm_context.ucm_device && ucm_device_disable(ucm, map->ucm_context.ucm_device) < 0) ret = -1; } ucm->active_verb = verb; update_mixer_paths(card->ports, verb_name); return ret; } int pa_alsa_ucm_set_port(pa_alsa_ucm_mapping_context *context, pa_device_port *port) { pa_alsa_ucm_config *ucm; pa_alsa_ucm_device *dev; pa_alsa_ucm_port_data *data; const char *dev_name, *ucm_dev_name; pa_assert(context && context->ucm); ucm = context->ucm; pa_assert(ucm->ucm_mgr); data = PA_DEVICE_PORT_DATA(port); dev = data->device; pa_assert(dev); if (context->ucm_device) { dev_name = pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_NAME); ucm_dev_name = pa_proplist_gets(context->ucm_device->proplist, PA_ALSA_PROP_UCM_NAME); if (!pa_streq(dev_name, ucm_dev_name)) { pa_log_error("Failed to set port %s with wrong UCM context: %s", dev_name, ucm_dev_name); return -1; } } return ucm_device_enable(ucm, dev); } static void ucm_add_mapping(pa_alsa_profile *p, pa_alsa_mapping *m) { pa_alsa_path_set *ps; /* create empty path set for the future path additions */ ps = pa_xnew0(pa_alsa_path_set, 1); ps->direction = m->direction; ps->paths = pa_hashmap_new_full(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func, pa_xfree, (pa_free_cb_t) pa_alsa_path_free); switch (m->direction) { case PA_ALSA_DIRECTION_ANY: pa_idxset_put(p->output_mappings, m, NULL); pa_idxset_put(p->input_mappings, m, NULL); m->output_path_set = ps; m->input_path_set = ps; break; case PA_ALSA_DIRECTION_OUTPUT: pa_idxset_put(p->output_mappings, m, NULL); m->output_path_set = ps; break; case PA_ALSA_DIRECTION_INPUT: pa_idxset_put(p->input_mappings, m, NULL); m->input_path_set = ps; break; } } static void alsa_mapping_add_ucm_device(pa_alsa_mapping *m, pa_alsa_ucm_device *device) { char *cur_desc; const char *new_desc, *mdev; bool is_sink = m->direction == PA_ALSA_DIRECTION_OUTPUT; m->ucm_context.ucm_device = device; new_desc = pa_proplist_gets(device->proplist, PA_ALSA_PROP_UCM_DESCRIPTION); cur_desc = m->description; if (cur_desc) m->description = pa_sprintf_malloc("%s + %s", cur_desc, new_desc); else m->description = pa_xstrdup(new_desc); pa_xfree(cur_desc); /* walk around null case */ m->description = m->description ? m->description : pa_xstrdup(""); /* save mapping to ucm device */ if (is_sink) device->playback_mapping = m; else device->capture_mapping = m; proplist_set_icon_name(m->proplist, device->type, is_sink); mdev = get_mixer_device(device, is_sink); if (mdev) pa_proplist_sets(m->proplist, "alsa.mixer_device", mdev); } static void alsa_mapping_add_ucm_modifier(pa_alsa_mapping *m, pa_alsa_ucm_modifier *modifier) { char *cur_desc; const char *new_desc, *mod_name, *channel_str; uint32_t channels = 0; m->ucm_context.ucm_modifier = modifier; new_desc = pa_proplist_gets(modifier->proplist, PA_ALSA_PROP_UCM_DESCRIPTION); cur_desc = m->description; if (cur_desc) m->description = pa_sprintf_malloc("%s + %s", cur_desc, new_desc); else m->description = pa_xstrdup(new_desc); pa_xfree(cur_desc); m->description = m->description ? m->description : pa_xstrdup(""); /* Modifier sinks should not be routed to by default */ m->priority = 0; mod_name = pa_proplist_gets(modifier->proplist, PA_ALSA_PROP_UCM_NAME); pa_proplist_sets(m->proplist, PA_ALSA_PROP_UCM_MODIFIER, mod_name); /* save mapping to ucm modifier */ if (m->direction == PA_ALSA_DIRECTION_OUTPUT) { modifier->playback_mapping = m; channel_str = pa_proplist_gets(modifier->proplist, PA_ALSA_PROP_UCM_PLAYBACK_CHANNELS); } else { modifier->capture_mapping = m; channel_str = pa_proplist_gets(modifier->proplist, PA_ALSA_PROP_UCM_CAPTURE_CHANNELS); } if (channel_str) { /* FIXME: channel_str is unsanitized input from the UCM configuration, * we should do proper error handling instead of asserting. * https://bugs.freedesktop.org/show_bug.cgi?id=71823 */ pa_assert_se(pa_atou(channel_str, &channels) == 0 && pa_channels_valid(channels)); pa_log_debug("Got channel count %" PRIu32 " for modifier", channels); } if (channels) pa_channel_map_init_extend(&m->channel_map, channels, PA_CHANNEL_MAP_ALSA); else pa_channel_map_init(&m->channel_map); } static pa_alsa_mapping* ucm_alsa_mapping_get(pa_alsa_ucm_config *ucm, pa_alsa_profile_set *ps, const char *verb_name, const char *ucm_name, bool is_sink) { pa_alsa_mapping *m; char *mapping_name; mapping_name = pa_sprintf_malloc("Mapping %s: %s: %s", verb_name, ucm_name, is_sink ? "sink" : "source"); m = pa_alsa_mapping_get(ps, mapping_name); if (!m) pa_log("No mapping for %s", mapping_name); pa_xfree(mapping_name); return m; } static const struct { enum snd_pcm_chmap_position pos; pa_channel_position_t channel; } chmap_info[] = { [SND_CHMAP_MONO] = { SND_CHMAP_MONO, PA_CHANNEL_POSITION_MONO }, [SND_CHMAP_FL] = { SND_CHMAP_FL, PA_CHANNEL_POSITION_FRONT_LEFT }, [SND_CHMAP_FR] = { SND_CHMAP_FR, PA_CHANNEL_POSITION_FRONT_RIGHT }, [SND_CHMAP_RL] = { SND_CHMAP_RL, PA_CHANNEL_POSITION_REAR_LEFT }, [SND_CHMAP_RR] = { SND_CHMAP_RR, PA_CHANNEL_POSITION_REAR_RIGHT }, [SND_CHMAP_FC] = { SND_CHMAP_FC, PA_CHANNEL_POSITION_FRONT_CENTER }, [SND_CHMAP_LFE] = { SND_CHMAP_LFE, PA_CHANNEL_POSITION_LFE }, [SND_CHMAP_SL] = { SND_CHMAP_SL, PA_CHANNEL_POSITION_SIDE_LEFT }, [SND_CHMAP_SR] = { SND_CHMAP_SR, PA_CHANNEL_POSITION_SIDE_RIGHT }, [SND_CHMAP_RC] = { SND_CHMAP_RC, PA_CHANNEL_POSITION_REAR_CENTER }, [SND_CHMAP_FLC] = { SND_CHMAP_FLC, PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER }, [SND_CHMAP_FRC] = { SND_CHMAP_FRC, PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER }, /* XXX: missing channel positions, mapped to aux... */ /* [SND_CHMAP_RLC] = { SND_CHMAP_RLC, PA_CHANNEL_POSITION_REAR_LEFT_OF_CENTER }, */ /* [SND_CHMAP_RRC] = { SND_CHMAP_RRC, PA_CHANNEL_POSITION_REAR_RIGHT_OF_CENTER }, */ /* [SND_CHMAP_FLW] = { SND_CHMAP_FLW, PA_CHANNEL_POSITION_FRONT_LEFT_WIDE }, */ /* [SND_CHMAP_FRW] = { SND_CHMAP_FRW, PA_CHANNEL_POSITION_FRONT_RIGHT_WIDE }, */ /* [SND_CHMAP_FLH] = { SND_CHMAP_FLH, PA_CHANNEL_POSITION_FRONT_LEFT_HIGH }, */ /* [SND_CHMAP_FCH] = { SND_CHMAP_FCH, PA_CHANNEL_POSITION_FRONT_CENTER_HIGH }, */ /* [SND_CHMAP_FRH] = { SND_CHMAP_FRH, PA_CHANNEL_POSITION_FRONT_RIGHT_HIGH }, */ [SND_CHMAP_TC] = { SND_CHMAP_TC, PA_CHANNEL_POSITION_TOP_CENTER }, [SND_CHMAP_TFL] = { SND_CHMAP_TFL, PA_CHANNEL_POSITION_TOP_FRONT_LEFT }, [SND_CHMAP_TFR] = { SND_CHMAP_TFR, PA_CHANNEL_POSITION_TOP_FRONT_RIGHT }, [SND_CHMAP_TFC] = { SND_CHMAP_TFC, PA_CHANNEL_POSITION_TOP_FRONT_CENTER }, [SND_CHMAP_TRL] = { SND_CHMAP_TRL, PA_CHANNEL_POSITION_TOP_REAR_LEFT }, [SND_CHMAP_TRR] = { SND_CHMAP_TRR, PA_CHANNEL_POSITION_TOP_REAR_RIGHT }, [SND_CHMAP_TRC] = { SND_CHMAP_TRC, PA_CHANNEL_POSITION_TOP_REAR_CENTER }, /* [SND_CHMAP_TFLC] = { SND_CHMAP_TFLC, PA_CHANNEL_POSITION_TOP_FRONT_LEFT_OF_CENTER }, */ /* [SND_CHMAP_TFRC] = { SND_CHMAP_TFRC, PA_CHANNEL_POSITION_TOP_FRONT_RIGHT_OF_CENTER }, */ /* [SND_CHMAP_TSL] = { SND_CHMAP_TSL, PA_CHANNEL_POSITION_TOP_SIDE_LEFT }, */ /* [SND_CHMAP_TSR] = { SND_CHMAP_TSR, PA_CHANNEL_POSITION_TOP_SIDE_RIGHT }, */ /* [SND_CHMAP_LLFE] = { SND_CHMAP_LLFE, PA_CHANNEL_POSITION_LEFT_LFE }, */ /* [SND_CHMAP_RLFE] = { SND_CHMAP_RLFE, PA_CHANNEL_POSITION_RIGHT_LFE }, */ /* [SND_CHMAP_BC] = { SND_CHMAP_BC, PA_CHANNEL_POSITION_BOTTOM_CENTER }, */ /* [SND_CHMAP_BLC] = { SND_CHMAP_BLC, PA_CHANNEL_POSITION_BOTTOM_LEFT_OF_CENTER }, */ /* [SND_CHMAP_BRC] = { SND_CHMAP_BRC, PA_CHANNEL_POSITION_BOTTOM_RIGHT_OF_CENTER }, */ }; static void ucm_split_to_channel_map(pa_channel_map *m, const pa_alsa_ucm_split *s) { const int n = sizeof(chmap_info) / sizeof(chmap_info[0]); int i; int aux = 0; for (i = 0; i < s->channels; ++i) { int p = s->pos[i]; if (p >= 0 && p < n && (int)chmap_info[p].pos == p) m->map[i] = chmap_info[p].channel; else m->map[i] = PA_CHANNEL_POSITION_AUX0 + aux++; if (aux >= 32) break; } m->channels = i; } static int ucm_create_mapping_direction( pa_alsa_ucm_config *ucm, pa_alsa_profile_set *ps, pa_alsa_ucm_device *device, const char *verb_name, const char *device_name, const char *device_str, bool is_sink) { pa_alsa_mapping *m; unsigned priority, rate, channels; m = ucm_alsa_mapping_get(ucm, ps, verb_name, device_name, is_sink); if (!m) return -1; pa_log_debug("UCM mapping: %s dev %s", m->name, device_name); priority = is_sink ? device->playback_priority : device->capture_priority; rate = is_sink ? device->playback_rate : device->capture_rate; channels = is_sink ? device->playback_channels : device->capture_channels; if (!m->ucm_context.ucm_device) { /* new mapping */ m->ucm_context.ucm = ucm; m->ucm_context.direction = is_sink ? PA_DIRECTION_OUTPUT : PA_DIRECTION_INPUT; m->device_strings = pa_xnew0(char*, 2); m->device_strings[0] = pa_xstrdup(device_str); m->direction = is_sink ? PA_ALSA_DIRECTION_OUTPUT : PA_ALSA_DIRECTION_INPUT; if (rate) m->sample_spec.rate = rate; pa_channel_map_init_extend(&m->channel_map, channels, PA_CHANNEL_MAP_ALSA); } /* mapping priority is the highest one of ucm devices */ if (priority > m->priority) m->priority = priority; /* mapping channels is the lowest one of ucm devices */ if (channels < m->channel_map.channels) pa_channel_map_init_extend(&m->channel_map, channels, PA_CHANNEL_MAP_ALSA); if (is_sink && device->playback_split) { m->split = pa_xmemdup(device->playback_split, sizeof(*m->split)); ucm_split_to_channel_map(&m->channel_map, m->split); } else if (!is_sink && device->capture_split) { m->split = pa_xmemdup(device->capture_split, sizeof(*m->split)); ucm_split_to_channel_map(&m->channel_map, m->split); } alsa_mapping_add_ucm_device(m, device); return 0; } static int ucm_create_mapping_for_modifier( pa_alsa_ucm_config *ucm, pa_alsa_profile_set *ps, pa_alsa_ucm_modifier *modifier, const char *verb_name, const char *mod_name, const char *device_str, bool is_sink) { pa_alsa_mapping *m; m = ucm_alsa_mapping_get(ucm, ps, verb_name, mod_name, is_sink); if (!m) return -1; pa_log_info("UCM mapping: %s modifier %s", m->name, mod_name); if (!m->ucm_context.ucm_device && !m->ucm_context.ucm_modifier) { /* new mapping */ m->ucm_context.ucm = ucm; m->ucm_context.direction = is_sink ? PA_DIRECTION_OUTPUT : PA_DIRECTION_INPUT; m->device_strings = pa_xnew0(char*, 2); m->device_strings[0] = pa_xstrdup(device_str); m->direction = is_sink ? PA_ALSA_DIRECTION_OUTPUT : PA_ALSA_DIRECTION_INPUT; /* Modifier sinks should not be routed to by default */ m->priority = 0; } alsa_mapping_add_ucm_modifier(m, modifier); return 0; } static int ucm_create_mapping( pa_alsa_ucm_config *ucm, pa_alsa_profile_set *ps, pa_alsa_ucm_device *device, const char *verb_name, const char *device_name, const char *sink, const char *source) { int ret = 0; if (!sink && !source) { pa_log("No sink and source at %s: %s", verb_name, device_name); return -1; } if (sink) ret = ucm_create_mapping_direction(ucm, ps, device, verb_name, device_name, sink, true); if (ret == 0 && source) ret = ucm_create_mapping_direction(ucm, ps, device, verb_name, device_name, source, false); return ret; } static pa_alsa_jack* ucm_get_jack(pa_alsa_ucm_config *ucm, pa_alsa_ucm_device *device) { pa_alsa_jack *j; const char *device_name; const char *jack_control; const char *mixer_device_name; char *name; pa_assert(ucm); pa_assert(device); device_name = pa_proplist_gets(device->proplist, PA_ALSA_PROP_UCM_NAME); jack_control = pa_proplist_gets(device->proplist, PA_ALSA_PROP_UCM_JACK_CONTROL); if (jack_control) { #if SND_LIB_VERSION >= 0x10201 snd_ctl_elem_id_t *ctl; int err, index; snd_ctl_elem_id_alloca(&ctl); err = snd_use_case_parse_ctl_elem_id(ctl, "JackControl", jack_control); if (err < 0) return NULL; jack_control = snd_ctl_elem_id_get_name(ctl); index = snd_ctl_elem_id_get_index(ctl); if (index > 0) { pa_log("[%s] Invalid JackControl index value: \"%s\",%d", device_name, jack_control, index); return NULL; } #else #warning "Upgrade to alsa-lib 1.2.1!" #endif if (!pa_endswith(jack_control, " Jack")) { pa_log("[%s] Invalid JackControl value: \"%s\"", device_name, jack_control); return NULL; } /* pa_alsa_jack_new() expects a jack name without " Jack" at the * end, so drop the trailing " Jack". */ name = pa_xstrndup(jack_control, strlen(jack_control) - 5); } else { /* The jack control hasn't been explicitly configured, fail. */ return NULL; } PA_LLIST_FOREACH(j, ucm->jacks) if (pa_streq(j->name, name)) goto finish; mixer_device_name = get_jack_mixer_device(device, true); if (!mixer_device_name) mixer_device_name = get_jack_mixer_device(device, false); if (!mixer_device_name) { pa_log("[%s] No mixer device name for JackControl \"%s\"", device_name, jack_control); j = NULL; goto finish; } j = pa_alsa_jack_new(NULL, mixer_device_name, name, 0); PA_LLIST_PREPEND(pa_alsa_jack, ucm->jacks, j); finish: pa_xfree(name); return j; } static int ucm_create_profile( pa_alsa_ucm_config *ucm, pa_alsa_profile_set *ps, pa_alsa_ucm_verb *verb, pa_idxset *mappings, const char *profile_name, const char *profile_desc, unsigned int profile_priority) { pa_alsa_profile *p; pa_alsa_mapping *map; uint32_t idx; pa_assert(ps); if (pa_hashmap_get(ps->profiles, profile_name)) { pa_log("Profile %s already exists", profile_name); return -1; } p = pa_xnew0(pa_alsa_profile, 1); p->profile_set = ps; p->name = pa_xstrdup(profile_name); p->description = pa_xstrdup(profile_desc); p->priority = profile_priority; p->ucm_context.verb = verb; p->output_mappings = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); p->input_mappings = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); p->supported = true; pa_hashmap_put(ps->profiles, p->name, p); PA_IDXSET_FOREACH(map, mappings, idx) ucm_add_mapping(p, map); pa_alsa_profile_dump(p); return 0; } static int ucm_create_verb_profiles( pa_alsa_ucm_config *ucm, pa_alsa_profile_set *ps, pa_alsa_ucm_verb *verb, const char *verb_name, const char *verb_desc) { pa_idxset *verb_devices, *p_devices, *p_mappings; pa_alsa_ucm_device *dev; pa_alsa_ucm_modifier *mod; int i = 0; int n_profiles = 0; const char *name, *sink, *source; char *p_name, *p_desc, *tmp; unsigned int verb_priority, p_priority; uint32_t idx; void *state = NULL; /* TODO: get profile priority from policy management */ verb_priority = verb->priority; if (verb_priority == 0) { char *verb_cmp, *c; c = verb_cmp = pa_xstrdup(verb_name); while (*c) { if (*c == '_') *c = ' '; c++; } for (i = 0; verb_info[i].id; i++) { if (strcasecmp(verb_info[i].id, verb_cmp) == 0) { verb_priority = verb_info[i].priority; break; } } pa_xfree(verb_cmp); } PA_LLIST_FOREACH(dev, verb->devices) { pa_alsa_jack *jack; const char *jack_hw_mute; name = pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_NAME); sink = pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_SINK); source = pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_SOURCE); ucm_create_mapping(ucm, ps, dev, verb_name, name, sink, source); jack = ucm_get_jack(ucm, dev); if (jack) device_set_jack(dev, jack); /* JackHWMute contains a list of device names. Each listed device must * be associated with the jack object that we just created. */ jack_hw_mute = pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_JACK_HW_MUTE); if (jack_hw_mute && !jack) { pa_log("[%s] JackHWMute set, but JackControl is missing", name); jack_hw_mute = NULL; } if (jack_hw_mute) { char *hw_mute_device_name; const char *state = NULL; while ((hw_mute_device_name = ucm_split_devnames(jack_hw_mute, &state))) { pa_alsa_ucm_verb *verb2; bool device_found = false; /* Search the referenced device from all verbs. If there are * multiple verbs that have a device with this name, we add the * hw mute association to each of those devices. */ PA_LLIST_FOREACH(verb2, ucm->verbs) { pa_alsa_ucm_device *hw_mute_device; hw_mute_device = verb_find_device(verb2, hw_mute_device_name); if (hw_mute_device) { device_found = true; device_add_hw_mute_jack(hw_mute_device, jack); } } if (!device_found) pa_log("[%s] JackHWMute references an unknown device: %s", name, hw_mute_device_name); pa_xfree(hw_mute_device_name); } } } /* Now find modifiers that have their own PlaybackPCM and create * separate sinks for them. */ PA_LLIST_FOREACH(mod, verb->modifiers) { name = pa_proplist_gets(mod->proplist, PA_ALSA_PROP_UCM_NAME); sink = pa_proplist_gets(mod->proplist, PA_ALSA_PROP_UCM_SINK); source = pa_proplist_gets(mod->proplist, PA_ALSA_PROP_UCM_SOURCE); if (sink) ucm_create_mapping_for_modifier(ucm, ps, mod, verb_name, name, sink, true); else if (source) ucm_create_mapping_for_modifier(ucm, ps, mod, verb_name, name, source, false); } verb_devices = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); PA_LLIST_FOREACH(dev, verb->devices) pa_idxset_put(verb_devices, dev, NULL); while ((p_devices = iterate_maximal_device_subsets(verb_devices, &state))) { p_mappings = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); /* Add the mappings that include our selected devices */ PA_IDXSET_FOREACH(dev, p_devices, idx) { if (dev->playback_mapping) pa_idxset_put(p_mappings, dev->playback_mapping, NULL); if (dev->capture_mapping) pa_idxset_put(p_mappings, dev->capture_mapping, NULL); } /* Add mappings only for the modifiers that can work with our * device selection */ PA_LLIST_FOREACH(mod, verb->modifiers) if (pa_idxset_isempty(mod->supported_devices) || pa_idxset_issubset(mod->supported_devices, p_devices)) if (pa_idxset_isdisjoint(mod->conflicting_devices, p_devices)) { if (mod->playback_mapping) pa_idxset_put(p_mappings, mod->playback_mapping, NULL); if (mod->capture_mapping) pa_idxset_put(p_mappings, mod->capture_mapping, NULL); } /* If we'll have multiple profiles for this verb, their names * must be unique. Use a list of chosen devices to disambiguate * them. If the profile contains all devices of a verb, we'll * generate only onle profile whose name should be the verb * name. GUIs usually show the profile description instead of * the name, add the device names to those as well. */ tmp = devset_name(p_devices, ", "); if (pa_idxset_equals(p_devices, verb_devices)) { p_name = pa_xstrdup(verb_name); p_desc = pa_xstrdup(verb_desc); } else { p_name = pa_sprintf_malloc("%s (%s)", verb_name, tmp); p_desc = pa_sprintf_malloc("%s (%s)", verb_desc, tmp); } /* Make sure profiles with higher-priority devices are * prioritized. */ p_priority = verb_priority + devset_playback_priority(p_devices, false) + devset_capture_priority(p_devices, false); if (ucm_create_profile(ucm, ps, verb, p_mappings, p_name, p_desc, p_priority) == 0) { pa_log_debug("Created profile %s for UCM verb %s", p_name, verb_name); n_profiles++; } pa_xfree(tmp); pa_xfree(p_name); pa_xfree(p_desc); pa_idxset_free(p_mappings, NULL); pa_idxset_free(p_devices, NULL); } pa_idxset_free(verb_devices, NULL); if (n_profiles == 0) { pa_log("UCM verb %s created no profiles", verb_name); return -1; } return 0; } static void mapping_init_eld(pa_alsa_mapping *m, snd_pcm_t *pcm) { pa_alsa_ucm_mapping_context *context = &m->ucm_context; pa_alsa_ucm_device *dev; char *mdev, *alib_prefix; snd_pcm_info_t *info; int pcm_card, pcm_device; snd_pcm_info_alloca(&info); if (snd_pcm_info(pcm, info) < 0) return; if ((pcm_card = snd_pcm_info_get_card(info)) < 0) return; if ((pcm_device = snd_pcm_info_get_device(info)) < 0) return; alib_prefix = context->ucm->alib_prefix; dev = context->ucm_device; mdev = pa_sprintf_malloc("%shw:%i", alib_prefix ? alib_prefix : "", pcm_card); if (mdev == NULL) return; dev->eld_mixer_device_name = mdev; dev->eld_device = pcm_device; } static snd_pcm_t* mapping_open_pcm(pa_alsa_ucm_config *ucm, pa_alsa_mapping *m, int mode, bool max_channels) { snd_pcm_t* pcm; pa_sample_spec try_ss = ucm->default_sample_spec; pa_channel_map try_map; snd_pcm_uframes_t try_period_size, try_buffer_size; bool exact_channels = m->channel_map.channels > 0; if (!m->split) { if (max_channels) { errno = EINVAL; return NULL; } if (exact_channels) { try_map = m->channel_map; try_ss.channels = try_map.channels; } else pa_channel_map_init_extend(&try_map, try_ss.channels, PA_CHANNEL_MAP_ALSA); } else { if (!m->split->leader) { errno = EINVAL; return NULL; } exact_channels = false; try_ss.channels = max_channels ? PA_CHANNELS_MAX : m->split->hw_channels; pa_channel_map_init_extend(&try_map, try_ss.channels, PA_CHANNEL_MAP_AUX); } try_period_size = pa_usec_to_bytes(ucm->default_fragment_size_msec * PA_USEC_PER_MSEC, &try_ss) / pa_frame_size(&try_ss); try_buffer_size = ucm->default_n_fragments * try_period_size; pcm = pa_alsa_open_by_device_string(m->device_strings[0], NULL, &try_ss, &try_map, mode, &try_period_size, &try_buffer_size, 0, NULL, NULL, NULL, NULL, exact_channels); if (pcm) { if (m->split) { const char *mode_name = mode == SND_PCM_STREAM_PLAYBACK ? "Playback" : "Capture"; if (try_map.channels < m->split->hw_channels) { pa_logl((max_channels ? PA_LOG_NOTICE : PA_LOG_DEBUG), "Error in ALSA UCM profile for %s (%s): %sChannels=%d > avail %d", m->device_strings[0], m->name, mode_name, m->split->hw_channels, try_map.channels); /* Retry with max channel count, in case ALSA rounded down */ if (!max_channels) { pa_alsa_close(&pcm); return mapping_open_pcm(ucm, m, mode, true); } /* Just accept whatever we got... Some of the routings won't get connected * anywhere */ m->split->hw_channels = try_map.channels; m->split->broken = true; } else if (try_map.channels > m->split->hw_channels) { pa_log_notice("Error in ALSA UCM profile for %s (%s): %sChannels=%d < avail %d", m->device_strings[0], m->name, mode_name, m->split->hw_channels, try_map.channels); m->split->hw_channels = try_map.channels; m->split->broken = true; } } else if (!exact_channels) { m->channel_map = try_map; } mapping_init_eld(m, pcm); } return pcm; } static void pa_alsa_init_split_pcm(pa_idxset *mappings, pa_alsa_mapping *leader, pa_direction_t direction) { pa_proplist *props = pa_proplist_new(); uint32_t idx; pa_alsa_mapping *m; if (direction == PA_DIRECTION_OUTPUT) pa_alsa_init_proplist_pcm(NULL, props, leader->output_pcm); else pa_alsa_init_proplist_pcm(NULL, props, leader->input_pcm); PA_IDXSET_FOREACH(m, mappings, idx) { if (!m->split) continue; if (!pa_streq(m->device_strings[0], leader->device_strings[0])) continue; if (direction == PA_DIRECTION_OUTPUT) pa_proplist_update(m->output_proplist, PA_UPDATE_REPLACE, props); else pa_proplist_update(m->input_proplist, PA_UPDATE_REPLACE, props); /* Update HW channel count to match probed one */ m->split->hw_channels = leader->split->hw_channels; } pa_proplist_free(props); } static void profile_finalize_probing(pa_alsa_profile *p) { pa_alsa_mapping *m; uint32_t idx; PA_IDXSET_FOREACH(m, p->output_mappings, idx) { if (p->supported) m->supported++; if (!m->output_pcm) continue; if (!m->split) pa_alsa_init_proplist_pcm(NULL, m->output_proplist, m->output_pcm); else pa_alsa_init_split_pcm(p->output_mappings, m, PA_DIRECTION_OUTPUT); pa_alsa_close(&m->output_pcm); } PA_IDXSET_FOREACH(m, p->input_mappings, idx) { if (p->supported) m->supported++; if (!m->input_pcm) continue; if (!m->split) pa_alsa_init_proplist_pcm(NULL, m->input_proplist, m->input_pcm); else pa_alsa_init_split_pcm(p->input_mappings, m, PA_DIRECTION_INPUT); pa_alsa_close(&m->input_pcm); } } static void ucm_mapping_jack_probe(pa_alsa_mapping *m, pa_hashmap *mixers) { snd_mixer_t *mixer_handle; pa_alsa_ucm_mapping_context *context = &m->ucm_context; pa_alsa_ucm_device *dev; bool has_control; dev = context->ucm_device; if (!dev->jack || !dev->jack->mixer_device_name) return; mixer_handle = pa_alsa_open_mixer_by_name(mixers, dev->jack->mixer_device_name, true); if (!mixer_handle) { pa_log_error("Unable to determine open mixer device '%s' for jack %s", dev->jack->mixer_device_name, dev->jack->name); return; } has_control = pa_alsa_mixer_find_card(mixer_handle, &dev->jack->alsa_id, 0) != NULL; pa_alsa_jack_set_has_control(dev->jack, has_control); pa_log_info("UCM jack %s has_control=%d", dev->jack->name, dev->jack->has_control); } static void ucm_probe_profile_set(pa_alsa_ucm_config *ucm, pa_alsa_profile_set *ps) { void *state; pa_alsa_profile *p; pa_alsa_mapping *m; const char *verb_name; uint32_t idx; PA_HASHMAP_FOREACH(p, ps->profiles, state) { pa_log_info("Probing profile %s", p->name); /* change verb */ verb_name = pa_proplist_gets(p->ucm_context.verb->proplist, PA_ALSA_PROP_UCM_NAME); pa_log_info("Set ucm verb to %s", verb_name); if ((snd_use_case_set(ucm->ucm_mgr, "_verb", verb_name)) < 0) { pa_log("Profile '%s': failed to set verb %s", p->name, verb_name); p->supported = false; continue; } PA_IDXSET_FOREACH(m, p->output_mappings, idx) { if (PA_UCM_IS_MODIFIER_MAPPING(m)) { /* Skip jack probing on modifier PCMs since we expect this to * only be controlled on the main device/verb PCM. */ continue; } if (m->split && !m->split->leader) continue; m->output_pcm = mapping_open_pcm(ucm, m, SND_PCM_STREAM_PLAYBACK, false); if (!m->output_pcm) { pa_log_info("Profile '%s' mapping '%s': output PCM open failed", p->name, m->name); p->supported = false; break; } } if (p->supported) { PA_IDXSET_FOREACH(m, p->input_mappings, idx) { if (PA_UCM_IS_MODIFIER_MAPPING(m)) { /* Skip jack probing on modifier PCMs since we expect this to * only be controlled on the main device/verb PCM. */ continue; } if (m->split && !m->split->leader) continue; m->input_pcm = mapping_open_pcm(ucm, m, SND_PCM_STREAM_CAPTURE, false); if (!m->input_pcm) { pa_log_info("Profile '%s' mapping '%s': input PCM open failed", p->name, m->name); p->supported = false; break; } } } if (!p->supported) { profile_finalize_probing(p); pa_log_info("Profile %s not supported", p->name); continue; } pa_log_debug("Profile %s supported.", p->name); PA_IDXSET_FOREACH(m, p->output_mappings, idx) if (!PA_UCM_IS_MODIFIER_MAPPING(m)) ucm_mapping_jack_probe(m, ucm->mixers); PA_IDXSET_FOREACH(m, p->input_mappings, idx) if (!PA_UCM_IS_MODIFIER_MAPPING(m)) ucm_mapping_jack_probe(m, ucm->mixers); profile_finalize_probing(p); } /* restore ucm state */ snd_use_case_set(ucm->ucm_mgr, "_verb", SND_USE_CASE_VERB_INACTIVE); pa_alsa_profile_set_drop_unsupported(ps); } pa_alsa_profile_set* pa_alsa_ucm_add_profile_set(pa_alsa_ucm_config *ucm, pa_channel_map *default_channel_map) { pa_alsa_ucm_verb *verb; pa_alsa_profile_set *ps; ps = pa_xnew0(pa_alsa_profile_set, 1); ps->mappings = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL, (pa_free_cb_t) pa_alsa_mapping_free); ps->profiles = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL, (pa_free_cb_t) pa_alsa_profile_free); ps->decibel_fixes = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); /* create profiles for each verb */ PA_LLIST_FOREACH(verb, ucm->verbs) { const char *verb_name; const char *verb_desc; verb_name = pa_proplist_gets(verb->proplist, PA_ALSA_PROP_UCM_NAME); verb_desc = pa_proplist_gets(verb->proplist, PA_ALSA_PROP_UCM_DESCRIPTION); if (verb_name == NULL) { pa_log("Verb with no name"); continue; } ucm_create_verb_profiles(ucm, ps, verb, verb_name, verb_desc); } ucm_probe_profile_set(ucm, ps); ps->probed = true; return ps; } static void free_verb(pa_alsa_ucm_verb *verb) { pa_alsa_ucm_device *di, *dn; pa_alsa_ucm_modifier *mi, *mn; PA_LLIST_FOREACH_SAFE(di, dn, verb->devices) { PA_LLIST_REMOVE(pa_alsa_ucm_device, verb->devices, di); if (di->hw_mute_jacks) pa_dynarray_free(di->hw_mute_jacks); if (di->ucm_ports) pa_dynarray_free(di->ucm_ports); if (di->playback_volumes) pa_hashmap_free(di->playback_volumes); if (di->capture_volumes) pa_hashmap_free(di->capture_volumes); pa_proplist_free(di->proplist); pa_idxset_free(di->conflicting_devices, NULL); pa_idxset_free(di->supported_devices, NULL); pa_xfree(di->eld_mixer_device_name); pa_xfree(di->playback_split); pa_xfree(di->capture_split); pa_xfree(di); } PA_LLIST_FOREACH_SAFE(mi, mn, verb->modifiers) { PA_LLIST_REMOVE(pa_alsa_ucm_modifier, verb->modifiers, mi); pa_proplist_free(mi->proplist); pa_idxset_free(mi->conflicting_devices, NULL); pa_idxset_free(mi->supported_devices, NULL); pa_xfree(mi->media_role); pa_xfree(mi); } pa_proplist_free(verb->proplist); pa_xfree(verb); } static pa_alsa_ucm_device *verb_find_device(pa_alsa_ucm_verb *verb, const char *device_name) { pa_alsa_ucm_device *device; pa_assert(verb); pa_assert(device_name); PA_LLIST_FOREACH(device, verb->devices) { const char *name; name = pa_proplist_gets(device->proplist, PA_ALSA_PROP_UCM_NAME); if (pa_streq(name, device_name)) return device; } return NULL; } void pa_alsa_ucm_free(pa_alsa_ucm_config *ucm) { pa_alsa_ucm_verb *vi, *vn; pa_alsa_jack *ji, *jn; PA_LLIST_FOREACH_SAFE(vi, vn, ucm->verbs) { PA_LLIST_REMOVE(pa_alsa_ucm_verb, ucm->verbs, vi); free_verb(vi); } PA_LLIST_FOREACH_SAFE(ji, jn, ucm->jacks) { PA_LLIST_REMOVE(pa_alsa_jack, ucm->jacks, ji); pa_alsa_jack_free(ji); } if (ucm->ucm_mgr) { snd_use_case_mgr_close(ucm->ucm_mgr); ucm->ucm_mgr = NULL; } pa_xfree(ucm->alib_prefix); ucm->alib_prefix = NULL; } void pa_alsa_ucm_mapping_context_free(pa_alsa_ucm_mapping_context *context) { pa_alsa_ucm_device *dev; pa_alsa_ucm_modifier *mod; dev = context->ucm_device; if (dev) { /* clear ucm device pointer to mapping */ if (context->direction == PA_DIRECTION_OUTPUT) dev->playback_mapping = NULL; else dev->capture_mapping = NULL; } mod = context->ucm_modifier; if (mod) { if (context->direction == PA_DIRECTION_OUTPUT) mod->playback_mapping = NULL; else mod->capture_mapping = NULL; } } /* Enable the modifier when the first stream with matched role starts */ void pa_alsa_ucm_roled_stream_begin(pa_alsa_ucm_config *ucm, const char *role, pa_direction_t dir) { pa_alsa_ucm_modifier *mod; if (!ucm->active_verb) return; PA_LLIST_FOREACH(mod, ucm->active_verb->modifiers) { if ((mod->action_direction == dir) && (pa_streq(mod->media_role, role))) { if (mod->enabled_counter == 0) { ucm_modifier_enable(ucm, mod); } mod->enabled_counter++; break; } } } /* Disable the modifier when the last stream with matched role ends */ void pa_alsa_ucm_roled_stream_end(pa_alsa_ucm_config *ucm, const char *role, pa_direction_t dir) { pa_alsa_ucm_modifier *mod; if (!ucm->active_verb) return; PA_LLIST_FOREACH(mod, ucm->active_verb->modifiers) { if ((mod->action_direction == dir) && (pa_streq(mod->media_role, role))) { mod->enabled_counter--; if (mod->enabled_counter == 0) ucm_modifier_disable(ucm, mod); break; } } } static void device_set_jack(pa_alsa_ucm_device *device, pa_alsa_jack *jack) { pa_assert(device); pa_assert(jack); device->jack = jack; pa_alsa_jack_add_ucm_device(jack, device); pa_alsa_ucm_device_update_available(device); } static void device_add_hw_mute_jack(pa_alsa_ucm_device *device, pa_alsa_jack *jack) { pa_assert(device); pa_assert(jack); pa_dynarray_append(device->hw_mute_jacks, jack); pa_alsa_jack_add_ucm_hw_mute_device(jack, device); pa_alsa_ucm_device_update_available(device); } static void device_set_available(pa_alsa_ucm_device *device, pa_available_t available) { pa_alsa_ucm_port_data *port; unsigned idx; pa_assert(device); if (available == device->available) return; device->available = available; PA_DYNARRAY_FOREACH(port, device->ucm_ports, idx) pa_device_port_set_available(port->core_port, port->device->available); } void pa_alsa_ucm_device_update_available(pa_alsa_ucm_device *device) { pa_available_t available = PA_AVAILABLE_UNKNOWN; pa_alsa_jack *jack; unsigned idx; pa_assert(device); if (device->jack && device->jack->has_control) available = device->jack->plugged_in ? PA_AVAILABLE_YES : PA_AVAILABLE_NO; PA_DYNARRAY_FOREACH(jack, device->hw_mute_jacks, idx) { if (jack->plugged_in) { available = PA_AVAILABLE_NO; break; } } device_set_available(device, available); } static void ucm_port_data_init(pa_alsa_ucm_port_data *port, pa_alsa_ucm_config *ucm, pa_device_port *core_port, pa_alsa_ucm_device *device) { pa_assert(ucm); pa_assert(core_port); pa_assert(device); port->ucm = ucm; port->core_port = core_port; port->eld_device = -1; port->device = device; pa_dynarray_append(device->ucm_ports, port); port->paths = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, pa_xfree, NULL); pa_device_port_set_available(port->core_port, port->device->available); } static void ucm_port_data_free(pa_device_port *port) { pa_alsa_ucm_port_data *ucm_port; pa_assert(port); ucm_port = PA_DEVICE_PORT_DATA(port); if (ucm_port->paths) pa_hashmap_free(ucm_port->paths); pa_xfree(ucm_port->eld_mixer_device_name); } long pa_alsa_ucm_port_device_status(pa_alsa_ucm_port_data *data) { return ucm_device_status(data->ucm, data->device); } #else /* HAVE_ALSA_UCM */ /* Dummy functions for systems without UCM support */ int pa_alsa_ucm_query_profiles(pa_alsa_ucm_config *ucm, int card_index) { pa_log_info("UCM not available."); return -1; } pa_alsa_profile_set* pa_alsa_ucm_add_profile_set(pa_alsa_ucm_config *ucm, pa_channel_map *default_channel_map) { return NULL; } int pa_alsa_ucm_set_profile(pa_alsa_ucm_config *ucm, pa_card *card, pa_alsa_profile *new_profile, pa_alsa_profile *old_profile) { return -1; } int pa_alsa_ucm_get_verb(snd_use_case_mgr_t *uc_mgr, const char *verb_name, const char *verb_desc, pa_alsa_ucm_verb **p_verb) { return -1; } void pa_alsa_ucm_add_ports( pa_hashmap **hash, pa_proplist *proplist, pa_alsa_ucm_mapping_context *context, bool is_sink, pa_card *card, snd_pcm_t *pcm_handle, bool ignore_dB) { } void pa_alsa_ucm_add_port( pa_hashmap *hash, pa_alsa_ucm_mapping_context *context, bool is_sink, pa_hashmap *ports, pa_card_profile *cp, pa_core *core) { } int pa_alsa_ucm_set_port(pa_alsa_ucm_mapping_context *context, pa_device_port *port) { return -1; } void pa_alsa_ucm_free(pa_alsa_ucm_config *ucm) { } void pa_alsa_ucm_mapping_context_free(pa_alsa_ucm_mapping_context *context) { } void pa_alsa_ucm_roled_stream_begin(pa_alsa_ucm_config *ucm, const char *role, pa_direction_t dir) { } void pa_alsa_ucm_roled_stream_end(pa_alsa_ucm_config *ucm, const char *role, pa_direction_t dir) { } long pa_alsa_ucm_port_device_status(pa_alsa_ucm_port_data *data) { return -1; } #endif pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/acp/alsa-ucm.h000066400000000000000000000251621511204443500263060ustar00rootroot00000000000000#ifndef fooalsaucmhfoo #define fooalsaucmhfoo /*** This file is part of PulseAudio. Copyright 2011 Wolfson Microelectronics PLC Author Margarita Olaya Copyright 2012 Feng Wei , Freescale Ltd. PulseAudio is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. PulseAudio is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with PulseAudio; if not, see . ***/ #ifdef HAVE_ALSA_UCM #include #else typedef void snd_use_case_mgr_t; #endif #include "compat.h" #include "alsa-mixer.h" /** For devices: List of verbs, devices or modifiers available */ #define PA_ALSA_PROP_UCM_NAME "alsa.ucm.name" /** For devices: List of supported devices per verb*/ #define PA_ALSA_PROP_UCM_DESCRIPTION "alsa.ucm.description" /** For devices: Playback device name e.g PlaybackPCM */ #define PA_ALSA_PROP_UCM_SINK "alsa.ucm.sink" /** For devices: Capture device name e.g CapturePCM*/ #define PA_ALSA_PROP_UCM_SOURCE "alsa.ucm.source" /** For devices: Playback roles */ #define PA_ALSA_PROP_UCM_PLAYBACK_ROLES "alsa.ucm.playback.roles" /** For devices: Playback control device name */ #define PA_ALSA_PROP_UCM_PLAYBACK_CTL_DEVICE "alsa.ucm.playback.ctldev" /** For devices: Playback control volume ID string. e.g PlaybackVolume */ #define PA_ALSA_PROP_UCM_PLAYBACK_VOLUME "alsa.ucm.playback.volume" /** For devices: Playback switch e.g PlaybackSwitch */ #define PA_ALSA_PROP_UCM_PLAYBACK_SWITCH "alsa.ucm.playback.switch" /** For devices: Playback mixer device name */ #define PA_ALSA_PROP_UCM_PLAYBACK_MIXER_DEVICE "alsa.ucm.playback.mixer.device" /** For devices: Playback mixer identifier */ #define PA_ALSA_PROP_UCM_PLAYBACK_MIXER_ELEM "alsa.ucm.playback.mixer.element" /** For devices: Playback mixer master identifier */ #define PA_ALSA_PROP_UCM_PLAYBACK_MASTER_ELEM "alsa.ucm.playback.master.element" /** For devices: Playback mixer master type */ #define PA_ALSA_PROP_UCM_PLAYBACK_MASTER_TYPE "alsa.ucm.playback.master.type" /** For devices: Playback mixer master identifier */ #define PA_ALSA_PROP_UCM_PLAYBACK_MASTER_ID "alsa.ucm.playback.master.id" /** For devices: Playback mixer master type */ #define PA_ALSA_PROP_UCM_PLAYBACK_MASTER_TYPE "alsa.ucm.playback.master.type" /** For devices: Playback priority */ #define PA_ALSA_PROP_UCM_PLAYBACK_PRIORITY "alsa.ucm.playback.priority" /** For devices: Playback rate */ #define PA_ALSA_PROP_UCM_PLAYBACK_RATE "alsa.ucm.playback.rate" /** For devices: Playback channels */ #define PA_ALSA_PROP_UCM_PLAYBACK_CHANNELS "alsa.ucm.playback.channels" /** For devices: Capture roles */ #define PA_ALSA_PROP_UCM_CAPTURE_ROLES "alsa.ucm.capture.roles" /** For devices: Capture control device name */ #define PA_ALSA_PROP_UCM_CAPTURE_CTL_DEVICE "alsa.ucm.capture.ctldev" /** For devices: Capture controls volume ID string. e.g CaptureVolume */ #define PA_ALSA_PROP_UCM_CAPTURE_VOLUME "alsa.ucm.capture.volume" /** For devices: Capture switch e.g CaptureSwitch */ #define PA_ALSA_PROP_UCM_CAPTURE_SWITCH "alsa.ucm.capture.switch" /** For devices: Capture mixer device name */ #define PA_ALSA_PROP_UCM_CAPTURE_MIXER_DEVICE "alsa.ucm.capture.mixer.device" /** For devices: Capture mixer identifier */ #define PA_ALSA_PROP_UCM_CAPTURE_MIXER_ELEM "alsa.ucm.capture.mixer.element" /** For devices: Capture mixer identifier */ #define PA_ALSA_PROP_UCM_CAPTURE_MASTER_ELEM "alsa.ucm.capture.master.element" /** For devices: Capture mixer identifier */ #define PA_ALSA_PROP_UCM_CAPTURE_MASTER_TYPE "alsa.ucm.capture.master.type" /** For devices: Capture mixer identifier */ #define PA_ALSA_PROP_UCM_CAPTURE_MASTER_ID "alsa.ucm.capture.master.id" /** For devices: Capture mixer identifier */ #define PA_ALSA_PROP_UCM_CAPTURE_MASTER_TYPE "alsa.ucm.capture.master.type" /** For devices: Capture priority */ #define PA_ALSA_PROP_UCM_CAPTURE_PRIORITY "alsa.ucm.capture.priority" /** For devices: Capture rate */ #define PA_ALSA_PROP_UCM_CAPTURE_RATE "alsa.ucm.capture.rate" /** For devices: Capture channels */ #define PA_ALSA_PROP_UCM_CAPTURE_CHANNELS "alsa.ucm.capture.channels" /** For devices: Quality of Service */ #define PA_ALSA_PROP_UCM_QOS "alsa.ucm.qos" /** For devices: The modifier (if any) that this device corresponds to */ #define PA_ALSA_PROP_UCM_MODIFIER "alsa.ucm.modifier" /* Corresponds to the "JackCTL" UCM value. */ #define PA_ALSA_PROP_UCM_JACK_DEVICE "alsa.ucm.jack_device" /* Corresponds to the "JackControl" UCM value. */ #define PA_ALSA_PROP_UCM_JACK_CONTROL "alsa.ucm.jack_control" /* Corresponds to the "JackHWMute" UCM value. */ #define PA_ALSA_PROP_UCM_JACK_HW_MUTE "alsa.ucm.jack_hw_mute" typedef struct pa_alsa_ucm_verb pa_alsa_ucm_verb; typedef struct pa_alsa_ucm_modifier pa_alsa_ucm_modifier; typedef struct pa_alsa_ucm_device pa_alsa_ucm_device; typedef struct pa_alsa_ucm_config pa_alsa_ucm_config; typedef struct pa_alsa_ucm_mapping_context pa_alsa_ucm_mapping_context; typedef struct pa_alsa_ucm_profile_context pa_alsa_ucm_profile_context; typedef struct pa_alsa_ucm_port_data pa_alsa_ucm_port_data; typedef struct pa_alsa_ucm_volume pa_alsa_ucm_volume; typedef struct pa_alsa_ucm_split pa_alsa_ucm_split; int pa_alsa_ucm_query_profiles(pa_alsa_ucm_config *ucm, int card_index); pa_alsa_profile_set* pa_alsa_ucm_add_profile_set(pa_alsa_ucm_config *ucm, pa_channel_map *default_channel_map); int pa_alsa_ucm_set_profile(pa_alsa_ucm_config *ucm, pa_card *card, pa_alsa_profile *new_profile, pa_alsa_profile *old_profile); int pa_alsa_ucm_get_verb(snd_use_case_mgr_t *uc_mgr, const char *verb_name, const char *verb_desc, pa_alsa_ucm_verb **p_verb); void pa_alsa_ucm_add_ports( pa_hashmap **hash, pa_proplist *proplist, pa_alsa_ucm_mapping_context *context, bool is_sink, pa_card *card, snd_pcm_t *pcm_handle, bool ignore_dB); void pa_alsa_ucm_add_port( pa_hashmap *hash, pa_alsa_ucm_mapping_context *context, bool is_sink, pa_hashmap *ports, pa_card_profile *cp, pa_core *core); int pa_alsa_ucm_set_port(pa_alsa_ucm_mapping_context *context, pa_device_port *port); void pa_alsa_ucm_free(pa_alsa_ucm_config *ucm); void pa_alsa_ucm_mapping_context_free(pa_alsa_ucm_mapping_context *context); void pa_alsa_ucm_roled_stream_begin(pa_alsa_ucm_config *ucm, const char *role, pa_direction_t dir); void pa_alsa_ucm_roled_stream_end(pa_alsa_ucm_config *ucm, const char *role, pa_direction_t dir); /* UCM - Use Case Manager is available on some audio cards */ struct pa_alsa_ucm_split { /* UCM SplitPCM channel remapping */ bool leader; int hw_channels; int channels; int idx[PA_CHANNELS_MAX]; enum snd_pcm_chmap_position pos[PA_CHANNELS_MAX]; bool broken; }; struct pa_alsa_ucm_device { PA_LLIST_FIELDS(pa_alsa_ucm_device); pa_proplist *proplist; pa_device_port_type_t type; unsigned playback_priority; unsigned capture_priority; unsigned playback_rate; unsigned capture_rate; unsigned playback_channels; unsigned capture_channels; /* These may be different per verb, so we store this as a hashmap of verb -> volume_control. We might eventually want to * make this a hashmap of verb -> per-verb-device-properties-struct. */ pa_hashmap *playback_volumes; pa_hashmap *capture_volumes; pa_alsa_mapping *playback_mapping; pa_alsa_mapping *capture_mapping; pa_idxset *conflicting_devices; pa_idxset *supported_devices; /* One device may be part of multiple ports, since each device has * a dedicated port, and in addition to that we sometimes generate ports * that represent combinations of devices. */ pa_dynarray *ucm_ports; /* struct ucm_port */ pa_alsa_jack *jack; pa_dynarray *hw_mute_jacks; /* pa_alsa_jack */ pa_available_t available; char *eld_mixer_device_name; int eld_device; pa_alsa_ucm_split *playback_split; pa_alsa_ucm_split *capture_split; }; void pa_alsa_ucm_device_update_available(pa_alsa_ucm_device *device); struct pa_alsa_ucm_modifier { PA_LLIST_FIELDS(pa_alsa_ucm_modifier); pa_proplist *proplist; pa_idxset *conflicting_devices; pa_idxset *supported_devices; pa_direction_t action_direction; char *media_role; /* Non-NULL if the modifier has its own PlaybackPCM/CapturePCM */ pa_alsa_mapping *playback_mapping; pa_alsa_mapping *capture_mapping; /* Count how many role matched streams are running */ int enabled_counter; }; struct pa_alsa_ucm_verb { PA_LLIST_FIELDS(pa_alsa_ucm_verb); pa_proplist *proplist; unsigned priority; PA_LLIST_HEAD(pa_alsa_ucm_device, devices); PA_LLIST_HEAD(pa_alsa_ucm_modifier, modifiers); }; struct pa_alsa_ucm_config { pa_sample_spec default_sample_spec; pa_channel_map default_channel_map; unsigned default_fragment_size_msec; unsigned default_n_fragments; bool split_enable; snd_use_case_mgr_t *ucm_mgr; pa_alsa_ucm_verb *active_verb; char *alib_prefix; pa_hashmap *mixers; PA_LLIST_HEAD(pa_alsa_ucm_verb, verbs); PA_LLIST_HEAD(pa_alsa_jack, jacks); }; struct pa_alsa_ucm_mapping_context { pa_alsa_ucm_config *ucm; pa_direction_t direction; pa_alsa_ucm_device *ucm_device; pa_alsa_ucm_modifier *ucm_modifier; }; struct pa_alsa_ucm_profile_context { pa_alsa_ucm_verb *verb; }; struct pa_alsa_ucm_port_data { pa_alsa_ucm_config *ucm; pa_device_port *core_port; pa_alsa_ucm_device *device; /* verb name -> pa_alsa_path for volume control */ pa_hashmap *paths; /* Current path, set when activating verb */ pa_alsa_path *path; /* ELD info */ char *eld_mixer_device_name; int eld_device; /* PCM device number */ }; long pa_alsa_ucm_port_device_status(pa_alsa_ucm_port_data *data); struct pa_alsa_ucm_volume { char *mixer_elem; /* mixer element identifier */ char *master_elem; /* master mixer element identifier */ char *master_type; }; #endif pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/acp/alsa-util.c000066400000000000000000002047651511204443500265020ustar00rootroot00000000000000/*** This file is part of PulseAudio. Copyright 2004-2009 Lennart Poettering Copyright 2006 Pierre Ossman for Cendio AB PulseAudio is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. PulseAudio is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with PulseAudio; if not, see . ***/ #include "config.h" #include #include #include "alsa-util.h" #include "alsa-mixer.h" #include #ifdef HAVE_UDEV #include #endif static int set_format(snd_pcm_t *pcm_handle, snd_pcm_hw_params_t *hwparams, pa_sample_format_t *f) { static const snd_pcm_format_t format_trans[] = { [PA_SAMPLE_U8] = SND_PCM_FORMAT_U8, [PA_SAMPLE_ALAW] = SND_PCM_FORMAT_A_LAW, [PA_SAMPLE_ULAW] = SND_PCM_FORMAT_MU_LAW, [PA_SAMPLE_S16LE] = SND_PCM_FORMAT_S16_LE, [PA_SAMPLE_S16BE] = SND_PCM_FORMAT_S16_BE, [PA_SAMPLE_FLOAT32LE] = SND_PCM_FORMAT_FLOAT_LE, [PA_SAMPLE_FLOAT32BE] = SND_PCM_FORMAT_FLOAT_BE, [PA_SAMPLE_S32LE] = SND_PCM_FORMAT_S32_LE, [PA_SAMPLE_S32BE] = SND_PCM_FORMAT_S32_BE, [PA_SAMPLE_S24LE] = SND_PCM_FORMAT_S24_3LE, [PA_SAMPLE_S24BE] = SND_PCM_FORMAT_S24_3BE, [PA_SAMPLE_S24_32LE] = SND_PCM_FORMAT_S24_LE, [PA_SAMPLE_S24_32BE] = SND_PCM_FORMAT_S24_BE, }; static const pa_sample_format_t try_order[] = { PA_SAMPLE_FLOAT32NE, PA_SAMPLE_FLOAT32RE, PA_SAMPLE_S32NE, PA_SAMPLE_S32RE, PA_SAMPLE_S24_32NE, PA_SAMPLE_S24_32RE, PA_SAMPLE_S24NE, PA_SAMPLE_S24RE, PA_SAMPLE_S16NE, PA_SAMPLE_S16RE, PA_SAMPLE_ALAW, PA_SAMPLE_ULAW, PA_SAMPLE_U8 }; unsigned i; int ret; pa_assert(pcm_handle); pa_assert(hwparams); pa_assert(f); if ((ret = snd_pcm_hw_params_set_format(pcm_handle, hwparams, format_trans[*f])) >= 0) return ret; pa_log_debug("snd_pcm_hw_params_set_format(%s) failed: %s", snd_pcm_format_description(format_trans[*f]), pa_alsa_strerror(ret)); if (*f == PA_SAMPLE_FLOAT32BE) *f = PA_SAMPLE_FLOAT32LE; else if (*f == PA_SAMPLE_FLOAT32LE) *f = PA_SAMPLE_FLOAT32BE; else if (*f == PA_SAMPLE_S24BE) *f = PA_SAMPLE_S24LE; else if (*f == PA_SAMPLE_S24LE) *f = PA_SAMPLE_S24BE; else if (*f == PA_SAMPLE_S24_32BE) *f = PA_SAMPLE_S24_32LE; else if (*f == PA_SAMPLE_S24_32LE) *f = PA_SAMPLE_S24_32BE; else if (*f == PA_SAMPLE_S16BE) *f = PA_SAMPLE_S16LE; else if (*f == PA_SAMPLE_S16LE) *f = PA_SAMPLE_S16BE; else if (*f == PA_SAMPLE_S32BE) *f = PA_SAMPLE_S32LE; else if (*f == PA_SAMPLE_S32LE) *f = PA_SAMPLE_S32BE; else goto try_auto; if ((ret = snd_pcm_hw_params_set_format(pcm_handle, hwparams, format_trans[*f])) >= 0) return ret; pa_log_debug("snd_pcm_hw_params_set_format(%s) failed: %s", snd_pcm_format_description(format_trans[*f]), pa_alsa_strerror(ret)); try_auto: for (i = 0; i < PA_ELEMENTSOF(try_order); i++) { *f = try_order[i]; if ((ret = snd_pcm_hw_params_set_format(pcm_handle, hwparams, format_trans[*f])) >= 0) return ret; pa_log_debug("snd_pcm_hw_params_set_format(%s) failed: %s", snd_pcm_format_description(format_trans[*f]), pa_alsa_strerror(ret)); } return -1; } static int set_period_size(snd_pcm_t *pcm_handle, snd_pcm_hw_params_t *hwparams, snd_pcm_uframes_t size) { snd_pcm_uframes_t s; int d, ret; pa_assert(pcm_handle); pa_assert(hwparams); s = size; d = 0; if (snd_pcm_hw_params_set_period_size_near(pcm_handle, hwparams, &s, &d) < 0) { s = size; d = -1; if (snd_pcm_hw_params_set_period_size_near(pcm_handle, hwparams, &s, &d) < 0) { s = size; d = 1; if ((ret = snd_pcm_hw_params_set_period_size_near(pcm_handle, hwparams, &s, &d)) < 0) { pa_log_info("snd_pcm_hw_params_set_period_size_near() failed: %s", pa_alsa_strerror(ret)); return ret; } } } return 0; } static int set_buffer_size(snd_pcm_t *pcm_handle, snd_pcm_hw_params_t *hwparams, snd_pcm_uframes_t size) { int ret; pa_assert(pcm_handle); pa_assert(hwparams); if ((ret = snd_pcm_hw_params_set_buffer_size_near(pcm_handle, hwparams, &size)) < 0) { pa_log_info("snd_pcm_hw_params_set_buffer_size_near() failed: %s", pa_alsa_strerror(ret)); return ret; } return 0; } static void check_access(snd_pcm_t *pcm_handle, snd_pcm_hw_params_t *hwparams, bool use_mmap) { if ((use_mmap && !snd_pcm_hw_params_test_access(pcm_handle, hwparams, SND_PCM_ACCESS_MMAP_INTERLEAVED)) || !snd_pcm_hw_params_test_access(pcm_handle, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED)) pa_log_error("Weird, PCM claims to support interleaved access, but snd_pcm_hw_params_set_access() failed."); if ((use_mmap && !snd_pcm_hw_params_test_access(pcm_handle, hwparams, SND_PCM_ACCESS_MMAP_NONINTERLEAVED)) || !snd_pcm_hw_params_test_access(pcm_handle, hwparams, SND_PCM_ACCESS_RW_NONINTERLEAVED)) pa_log_debug("PCM seems to support non-interleaved access, but PA doesn't."); else if (use_mmap && !snd_pcm_hw_params_test_access(pcm_handle, hwparams, SND_PCM_ACCESS_MMAP_COMPLEX)) { pa_log_debug("PCM seems to support mmapped complex access, but PA doesn't."); } } /* Set the hardware parameters of the given ALSA device. Returns the * selected fragment settings in *buffer_size and *period_size. Determine * whether mmap and tsched mode can be enabled. */ int pa_alsa_set_hw_params( snd_pcm_t *pcm_handle, pa_sample_spec *ss, snd_pcm_uframes_t *period_size, snd_pcm_uframes_t *buffer_size, snd_pcm_uframes_t tsched_size, bool *use_mmap, bool *use_tsched, bool require_exact_channel_number) { int ret = -1; snd_pcm_hw_params_t *hwparams, *hwparams_copy; int dir; snd_pcm_uframes_t _period_size = period_size ? *period_size : 0; snd_pcm_uframes_t _buffer_size = buffer_size ? *buffer_size : 0; bool _use_mmap = use_mmap && *use_mmap; bool _use_tsched = use_tsched && *use_tsched; pa_sample_spec _ss = *ss; pa_assert(pcm_handle); pa_assert(ss); snd_pcm_hw_params_alloca(&hwparams); snd_pcm_hw_params_alloca(&hwparams_copy); if ((ret = snd_pcm_hw_params_any(pcm_handle, hwparams)) < 0) { pa_log_debug("snd_pcm_hw_params_any() failed: %s", pa_alsa_strerror(ret)); goto finish; } if ((ret = snd_pcm_hw_params_set_rate_resample(pcm_handle, hwparams, 0)) < 0) { pa_log_debug("snd_pcm_hw_params_set_rate_resample() failed: %s", pa_alsa_strerror(ret)); goto finish; } if (_use_mmap) { if (snd_pcm_hw_params_set_access(pcm_handle, hwparams, SND_PCM_ACCESS_MMAP_INTERLEAVED) < 0) { /* mmap() didn't work, fall back to interleaved */ if ((ret = snd_pcm_hw_params_set_access(pcm_handle, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) { pa_log_debug("snd_pcm_hw_params_set_access() failed: %s", pa_alsa_strerror(ret)); check_access(pcm_handle, hwparams, true); goto finish; } _use_mmap = false; } } else if ((ret = snd_pcm_hw_params_set_access(pcm_handle, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) { pa_log_debug("snd_pcm_hw_params_set_access() failed: %s", pa_alsa_strerror(ret)); check_access(pcm_handle, hwparams, false); goto finish; } if (!_use_mmap) _use_tsched = false; if (!pa_alsa_pcm_is_hw(pcm_handle)) _use_tsched = false; /* The PCM pointer is only updated with period granularity */ if (snd_pcm_hw_params_is_batch(hwparams)) { bool is_usb = false; const char *id; snd_pcm_info_t* pcm_info; snd_pcm_info_alloca(&pcm_info); if (snd_pcm_info(pcm_handle, pcm_info) == 0 && (id = snd_pcm_info_get_id(pcm_info))) { /* This horrible hack makes sure we don't disable tsched on USB * devices, which have a low enough transfer size for timer-based * scheduling to work. This can go away when the ALSA API supports * querying the block transfer size. */ if (pa_streq(id, "USB Audio")) is_usb = true; } if (!is_usb) { pa_log_info("Disabling tsched mode since BATCH flag is set"); _use_tsched = false; } } #if (SND_LIB_VERSION >= ((1<<16)|(0<<8)|24)) /* API additions in 1.0.24 */ if (_use_tsched) { /* try to disable period wakeups if hardware can do so */ if (snd_pcm_hw_params_can_disable_period_wakeup(hwparams)) { if ((ret = snd_pcm_hw_params_set_period_wakeup(pcm_handle, hwparams, false)) < 0) /* don't bail, keep going with default mode with period wakeups */ pa_log_debug("snd_pcm_hw_params_set_period_wakeup() failed: %s", pa_alsa_strerror(ret)); else pa_log_info("Trying to disable ALSA period wakeups, using timers only"); } else pa_log_info("Cannot disable ALSA period wakeups"); } #endif if ((ret = set_format(pcm_handle, hwparams, &_ss.format)) < 0) goto finish; if ((ret = snd_pcm_hw_params_set_rate_near(pcm_handle, hwparams, &_ss.rate, NULL)) < 0) { pa_log_debug("snd_pcm_hw_params_set_rate_near() failed: %s", pa_alsa_strerror(ret)); goto finish; } /* We ignore very small sampling rate deviations */ if (_ss.rate >= ss->rate*.95 && _ss.rate <= ss->rate*1.05) _ss.rate = ss->rate; if (require_exact_channel_number) { if ((ret = snd_pcm_hw_params_set_channels(pcm_handle, hwparams, _ss.channels)) < 0) { pa_log_debug("snd_pcm_hw_params_set_channels(%u) failed: %s", _ss.channels, pa_alsa_strerror(ret)); goto finish; } } else { unsigned int c = _ss.channels; if ((ret = snd_pcm_hw_params_set_channels_near(pcm_handle, hwparams, &c)) < 0) { pa_log_debug("snd_pcm_hw_params_set_channels_near(%u) failed: %s", _ss.channels, pa_alsa_strerror(ret)); goto finish; } _ss.channels = c; } if (_use_tsched && tsched_size > 0) { _buffer_size = (snd_pcm_uframes_t) (((uint64_t) tsched_size * _ss.rate) / ss->rate); _period_size = _buffer_size; } else { _period_size = (snd_pcm_uframes_t) (((uint64_t) _period_size * _ss.rate) / ss->rate); _buffer_size = (snd_pcm_uframes_t) (((uint64_t) _buffer_size * _ss.rate) / ss->rate); } if (_buffer_size > 0 || _period_size > 0) { snd_pcm_uframes_t max_frames = 0; if ((ret = snd_pcm_hw_params_get_buffer_size_max(hwparams, &max_frames)) < 0) pa_log_warn("snd_pcm_hw_params_get_buffer_size_max() failed: %s", pa_alsa_strerror(ret)); else pa_log_debug("Maximum hw buffer size is %lu ms", (long unsigned) (max_frames * PA_MSEC_PER_SEC / _ss.rate)); /* Some ALSA drivers really don't like if we set the buffer * size first and the number of periods second (which would * make a lot more sense to me). So, try a few combinations * before we give up. */ if (_buffer_size > 0 && _period_size > 0) { snd_pcm_hw_params_copy(hwparams_copy, hwparams); /* First try: set buffer size first, followed by period size */ if (set_buffer_size(pcm_handle, hwparams_copy, _buffer_size) >= 0 && set_period_size(pcm_handle, hwparams_copy, _period_size) >= 0 && snd_pcm_hw_params(pcm_handle, hwparams_copy) >= 0) { pa_log_debug("Set buffer size first (to %lu samples), period size second (to %lu samples).", (unsigned long) _buffer_size, (unsigned long) _period_size); goto success; } snd_pcm_hw_params_copy(hwparams_copy, hwparams); /* Second try: set period size first, followed by buffer size */ if (set_period_size(pcm_handle, hwparams_copy, _period_size) >= 0 && set_buffer_size(pcm_handle, hwparams_copy, _buffer_size) >= 0 && snd_pcm_hw_params(pcm_handle, hwparams_copy) >= 0) { pa_log_debug("Set period size first (to %lu samples), buffer size second (to %lu samples).", (unsigned long) _period_size, (unsigned long) _buffer_size); goto success; } } if (_buffer_size > 0) { snd_pcm_hw_params_copy(hwparams_copy, hwparams); /* Third try: set only buffer size */ if (set_buffer_size(pcm_handle, hwparams_copy, _buffer_size) >= 0 && snd_pcm_hw_params(pcm_handle, hwparams_copy) >= 0) { pa_log_debug("Set only buffer size (to %lu samples).", (unsigned long) _buffer_size); goto success; } } if (_period_size > 0) { snd_pcm_hw_params_copy(hwparams_copy, hwparams); /* Fourth try: set only period size */ if (set_period_size(pcm_handle, hwparams_copy, _period_size) >= 0 && snd_pcm_hw_params(pcm_handle, hwparams_copy) >= 0) { pa_log_debug("Set only period size (to %lu samples).", (unsigned long) _period_size); goto success; } } } pa_log_debug("Set neither period nor buffer size."); /* Last chance, set nothing */ if ((ret = snd_pcm_hw_params(pcm_handle, hwparams)) < 0) { pa_log_info("snd_pcm_hw_params failed: %s", pa_alsa_strerror(ret)); goto finish; } success: if (ss->rate != _ss.rate) pa_log_info("Device %s doesn't support %u Hz, changed to %u Hz.", snd_pcm_name(pcm_handle), ss->rate, _ss.rate); if (ss->channels != _ss.channels) pa_log_info("Device %s doesn't support %u channels, changed to %u.", snd_pcm_name(pcm_handle), ss->channels, _ss.channels); if (ss->format != _ss.format) pa_log_info("Device %s doesn't support sample format %s, changed to %s.", snd_pcm_name(pcm_handle), pa_sample_format_to_string(ss->format), pa_sample_format_to_string(_ss.format)); if ((ret = snd_pcm_hw_params_current(pcm_handle, hwparams)) < 0) { pa_log_info("snd_pcm_hw_params_current() failed: %s", pa_alsa_strerror(ret)); goto finish; } if ((ret = snd_pcm_hw_params_get_period_size(hwparams, &_period_size, &dir)) < 0 || (ret = snd_pcm_hw_params_get_buffer_size(hwparams, &_buffer_size)) < 0) { pa_log_info("snd_pcm_hw_params_get_{period|buffer}_size() failed: %s", pa_alsa_strerror(ret)); goto finish; } #if (SND_LIB_VERSION >= ((1<<16)|(0<<8)|24)) /* API additions in 1.0.24 */ if (_use_tsched) { unsigned int no_wakeup; /* see if period wakeups were disabled */ snd_pcm_hw_params_get_period_wakeup(pcm_handle, hwparams, &no_wakeup); if (no_wakeup == 0) pa_log_info("ALSA period wakeups disabled"); else pa_log_info("ALSA period wakeups were not disabled"); } #endif ss->rate = _ss.rate; ss->channels = _ss.channels; ss->format = _ss.format; pa_assert(_period_size > 0); pa_assert(_buffer_size > 0); if (buffer_size) *buffer_size = _buffer_size; if (period_size) *period_size = _period_size; if (use_mmap) *use_mmap = _use_mmap; if (use_tsched) *use_tsched = _use_tsched; ret = 0; finish: return ret; } int pa_alsa_set_sw_params(snd_pcm_t *pcm, snd_pcm_uframes_t avail_min, bool period_event) { snd_pcm_sw_params_t *swparams; snd_pcm_uframes_t boundary; int err; pa_assert(pcm); snd_pcm_sw_params_alloca(&swparams); if ((err = snd_pcm_sw_params_current(pcm, swparams)) < 0) { pa_log_warn("Unable to determine current swparams: %s", pa_alsa_strerror(err)); return err; } if ((err = snd_pcm_sw_params_set_period_event(pcm, swparams, period_event)) < 0) { pa_log_warn("Unable to disable period event: %s", pa_alsa_strerror(err)); return err; } if ((err = snd_pcm_sw_params_set_tstamp_mode(pcm, swparams, SND_PCM_TSTAMP_ENABLE)) < 0) { pa_log_warn("Unable to enable time stamping: %s", pa_alsa_strerror(err)); return err; } if ((err = snd_pcm_sw_params_get_boundary(swparams, &boundary)) < 0) { pa_log_warn("Unable to get boundary: %s", pa_alsa_strerror(err)); return err; } if ((err = snd_pcm_sw_params_set_stop_threshold(pcm, swparams, boundary)) < 0) { pa_log_warn("Unable to set stop threshold: %s", pa_alsa_strerror(err)); return err; } if ((err = snd_pcm_sw_params_set_start_threshold(pcm, swparams, (snd_pcm_uframes_t) -1)) < 0) { pa_log_warn("Unable to set start threshold: %s", pa_alsa_strerror(err)); return err; } if ((err = snd_pcm_sw_params_set_avail_min(pcm, swparams, avail_min)) < 0) { pa_log_error("snd_pcm_sw_params_set_avail_min() failed: %s", pa_alsa_strerror(err)); return err; } if ((err = snd_pcm_sw_params(pcm, swparams)) < 0) { pa_log_warn("Unable to set sw params: %s", pa_alsa_strerror(err)); return err; } return 0; } #if 0 snd_pcm_t *pa_alsa_open_by_device_id_auto( const char *dev_id, char **dev, pa_sample_spec *ss, pa_channel_map* map, int mode, snd_pcm_uframes_t *period_size, snd_pcm_uframes_t *buffer_size, snd_pcm_uframes_t tsched_size, bool *use_mmap, bool *use_tsched, pa_sample_format_t **query_supported_formats, unsigned int **query_supported_rates, pa_alsa_profile_set *ps, pa_alsa_mapping **mapping) { char *d; snd_pcm_t *pcm_handle; void *state; pa_alsa_mapping *m; pa_assert(dev_id); pa_assert(dev); pa_assert(ss); pa_assert(map); pa_assert(ps); /* First we try to find a device string with a superset of the * requested channel map. We iterate through our device table from * top to bottom and take the first that matches. If we didn't * find a working device that way, we iterate backwards, and check * all devices that do not provide a superset of the requested * channel map.*/ PA_HASHMAP_FOREACH(m, ps->mappings, state) { if (!pa_channel_map_superset(&m->channel_map, map)) continue; pa_log_debug("Checking for superset %s (%s)", m->name, m->device_strings[0]); pcm_handle = pa_alsa_open_by_device_id_mapping( dev_id, dev, ss, map, mode, period_size, buffer_size, tsched_size, use_mmap, use_tsched, query_supported_formats, query_supported_rates, m); if (pcm_handle) { if (mapping) *mapping = m; return pcm_handle; } } PA_HASHMAP_FOREACH_BACKWARDS(m, ps->mappings, state) { if (pa_channel_map_superset(&m->channel_map, map)) continue; pa_log_debug("Checking for subset %s (%s)", m->name, m->device_strings[0]); pcm_handle = pa_alsa_open_by_device_id_mapping( dev_id, dev, ss, map, mode, period_size, buffer_size, tsched_size, use_mmap, use_tsched, query_supported_formats, query_supported_rates, m); if (pcm_handle) { if (mapping) *mapping = m; return pcm_handle; } } /* OK, we didn't find any good device, so let's try the raw hw: stuff */ d = pa_sprintf_malloc("hw:%s", dev_id); pa_log_debug("Trying %s as last resort...", d); pcm_handle = pa_alsa_open_by_device_string( d, dev, ss, map, mode, period_size, buffer_size, tsched_size, use_mmap, use_tsched, query_supported_formats, query_supported_rates, false); pa_xfree(d); if (pcm_handle && mapping) *mapping = NULL; return pcm_handle; } #endif snd_pcm_t *pa_alsa_open_by_device_id_mapping( const char *dev_id, char **dev, pa_sample_spec *ss, pa_channel_map* map, int mode, snd_pcm_uframes_t *period_size, snd_pcm_uframes_t *buffer_size, snd_pcm_uframes_t tsched_size, bool *use_mmap, bool *use_tsched, pa_sample_format_t **query_supported_formats, unsigned int **query_supported_rates, pa_alsa_mapping *m) { snd_pcm_t *pcm_handle; pa_sample_spec try_ss; pa_channel_map try_map; pa_assert(dev_id); pa_assert(dev); pa_assert(ss); pa_assert(map); pa_assert(m); try_ss.channels = m->channel_map.channels; try_ss.rate = ss->rate; try_ss.format = ss->format; try_map = m->channel_map; pcm_handle = pa_alsa_open_by_template( m->device_strings, dev_id, dev, &try_ss, &try_map, mode, period_size, buffer_size, tsched_size, use_mmap, use_tsched, query_supported_formats, query_supported_rates, pa_channel_map_valid(&m->channel_map) /* Query the channel count if we don't know what we want */); if (!pcm_handle) return NULL; *ss = try_ss; *map = try_map; pa_assert(map->channels == ss->channels); return pcm_handle; } int pa_alsa_close(snd_pcm_t **pcm) { int err; pa_assert(pcm); pa_log_info("ALSA device close %p", *pcm); if (*pcm == NULL) return 0; if ((err = snd_pcm_close(*pcm)) < 0) { pa_log_warn("ALSA close failed: %s", snd_strerror(err)); } *pcm = NULL; return err; } snd_pcm_t *pa_alsa_open_by_device_string( const char *device, char **dev, pa_sample_spec *ss, pa_channel_map* map, int mode, snd_pcm_uframes_t *period_size, snd_pcm_uframes_t *buffer_size, snd_pcm_uframes_t tsched_size, bool *use_mmap, bool *use_tsched, pa_sample_format_t **query_supported_formats, unsigned int **query_supported_rates, bool require_exact_channel_number) { int err; char *d; snd_pcm_t *pcm_handle; bool reformat = false; pa_assert(device); pa_assert(ss); pa_assert(map); d = pa_xstrdup(device); for (;;) { pa_log_debug("Trying %s %s SND_PCM_NO_AUTO_FORMAT ...", d, reformat ? "without" : "with"); if ((err = snd_pcm_open(&pcm_handle, d, mode, SND_PCM_NONBLOCK| SND_PCM_NO_AUTO_RESAMPLE| SND_PCM_NO_AUTO_CHANNELS| (reformat ? 0 : SND_PCM_NO_AUTO_FORMAT))) < 0) { pa_log_info("Error opening PCM device %s: %s", d, pa_alsa_strerror(err)); goto fail; } pa_log_info("ALSA device open '%s' %s: %p", d, mode == SND_PCM_STREAM_CAPTURE ? "capture" : "playback", pcm_handle); if (query_supported_formats) *query_supported_formats = pa_alsa_get_supported_formats(pcm_handle, ss->format); if (query_supported_rates) *query_supported_rates = pa_alsa_get_supported_rates(pcm_handle, ss->rate); if ((err = pa_alsa_set_hw_params( pcm_handle, ss, period_size, buffer_size, tsched_size, use_mmap, use_tsched, require_exact_channel_number)) < 0) { if (!reformat) { reformat = true; pa_alsa_close(&pcm_handle); continue; } /* Hmm, some hw is very exotic, so we retry with plug, if without it didn't work */ if (!pa_startswith(d, "plug:") && !pa_startswith(d, "plughw:")) { char *t; t = pa_sprintf_malloc("plug:SLAVE='%s'", d); pa_xfree(d); d = t; reformat = false; pa_alsa_close(&pcm_handle); continue; } pa_log_info("Failed to set hardware parameters on %s: %s", d, pa_alsa_strerror(err)); pa_alsa_close(&pcm_handle); goto fail; } if (ss->channels > PA_CHANNELS_MAX) { pa_log("Device %s has %u channels, but PulseAudio supports only %u channels. Unable to use the device.", d, ss->channels, PA_CHANNELS_MAX); pa_alsa_close(&pcm_handle); goto fail; } if (dev) *dev = d; else pa_xfree(d); if (ss->channels != map->channels) pa_channel_map_init_extend(map, ss->channels, PA_CHANNEL_MAP_ALSA); return pcm_handle; } fail: pa_xfree(d); return NULL; } snd_pcm_t *pa_alsa_open_by_template( char **template, const char *dev_id, char **dev, pa_sample_spec *ss, pa_channel_map* map, int mode, snd_pcm_uframes_t *period_size, snd_pcm_uframes_t *buffer_size, snd_pcm_uframes_t tsched_size, bool *use_mmap, bool *use_tsched, pa_sample_format_t **query_supported_formats, unsigned int **query_supported_rates, bool require_exact_channel_number) { snd_pcm_t *pcm_handle; char **i; for (i = template; *i; i++) { char *d; d = pa_replace(*i, "%f", dev_id); pcm_handle = pa_alsa_open_by_device_string( d, dev, ss, map, mode, period_size, buffer_size, tsched_size, use_mmap, use_tsched, query_supported_formats, query_supported_rates, require_exact_channel_number); pa_xfree(d); if (pcm_handle) return pcm_handle; } return NULL; } void pa_alsa_dump(pa_log_level_t level, snd_pcm_t *pcm) { int err; snd_output_t *out; pa_assert(pcm); pa_assert_se(snd_output_buffer_open(&out) == 0); if ((err = snd_pcm_dump(pcm, out)) < 0) pa_logl(level, "snd_pcm_dump(): %s", pa_alsa_strerror(err)); else { char *s = NULL; snd_output_buffer_string(out, &s); pa_logl(level, "snd_pcm_dump():\n%s", pa_strnull(s)); } pa_assert_se(snd_output_close(out) == 0); } #if 0 void pa_alsa_dump_status(snd_pcm_t *pcm) { int err; snd_output_t *out; snd_pcm_status_t *status; char *s = NULL; pa_assert(pcm); snd_pcm_status_alloca(&status); if ((err = snd_output_buffer_open(&out)) < 0) { pa_log_debug("snd_output_buffer_open() failed: %s", pa_cstrerror(err)); return; } if ((err = snd_pcm_status(pcm, status)) < 0) { pa_log_debug("snd_pcm_status() failed: %s", pa_cstrerror(err)); goto finish; } if ((err = snd_pcm_status_dump(status, out)) < 0) { pa_log_debug("snd_pcm_status_dump(): %s", pa_alsa_strerror(err)); goto finish; } snd_output_buffer_string(out, &s); pa_log_debug("snd_pcm_status_dump():\n%s", pa_strnull(s)); finish: snd_output_close(out); } #endif static PA_PRINTF_FUNC(5,0) void alsa_local_handler(const char *file, int line, const char *function, int err, const char *fmt, va_list arg) { pa_log_levelv_meta(PA_LOG_INFO, file, line, function, fmt, arg); } static PA_PRINTF_FUNC(5,6) void alsa_error_handler(const char *file, int line, const char *function, int err, const char *fmt,...) { va_list ap; // char *alsa_file; // alsa_file = pa_sprintf_malloc("(alsa-lib)%s", file); va_start(ap, fmt); pa_log_levelv_meta(PA_LOG_INFO, file, line, function, fmt, ap); va_end(ap); // pa_xfree(alsa_file); } static int n_error_handler_installed = 0; typedef void (*snd_lib2_error_handler_t)(const char *file, int line, const char *function, int err, const char *fmt, ...) PA_PRINTF_FUNC(5,6) /* __attribute__ ((format (printf, 5, 6))) */; typedef void (*snd_lib2_local_handler_t)(const char *file, int line, const char *function, int err, const char *fmt, va_list args) PA_PRINTF_FUNC(5,0) /* __attribute__ ((format (printf, 5, 0))) */; extern int snd_lib_error_set_handler(snd_lib2_error_handler_t handler); extern snd_local_error_handler_t snd_lib_error_set_local(snd_lib2_local_handler_t handler); void pa_alsa_refcnt_inc(void) { /* This is not really thread safe, but we do our best */ if (n_error_handler_installed++ == 0) { snd_lib_error_set_handler(alsa_error_handler); snd_lib_error_set_local(alsa_local_handler); } } void pa_alsa_refcnt_dec(void) { int r; pa_assert_se((r = n_error_handler_installed--) >= 1); if (r == 1) { snd_lib_error_set_handler(NULL); snd_lib_error_set_local(NULL); snd_config_update_free_global(); } } bool pa_alsa_init_description(pa_proplist *p, pa_card *card) { const char *d, *k; pa_assert(p); if (pa_alsa_device_init_description(p, card)) return true; if (!(d = pa_proplist_gets(p, "alsa.card_name"))) d = pa_proplist_gets(p, "alsa.name"); if (!d) return false; k = pa_proplist_gets(p, PA_PROP_DEVICE_PROFILE_DESCRIPTION); if (d && k) pa_proplist_setf(p, PA_PROP_DEVICE_DESCRIPTION, "%s %s", d, k); else if (d) pa_proplist_sets(p, PA_PROP_DEVICE_DESCRIPTION, d); return false; } void pa_alsa_init_proplist_card(pa_core *c, pa_proplist *p, int card) { char *cn, *lcn, *dn, name[64]; pa_assert(p); pa_assert(card >= 0); pa_proplist_setf(p, "alsa.card", "%i", card); if (snd_card_get_name(card, &cn) >= 0) { pa_proplist_sets(p, "alsa.card_name", pa_strip(cn)); free(cn); } if (snd_card_get_longname(card, &lcn) >= 0) { pa_proplist_sets(p, "alsa.long_card_name", pa_strip(lcn)); free(lcn); } if ((dn = pa_alsa_get_driver_name(card))) { pa_proplist_sets(p, "alsa.driver_name", dn); pa_xfree(dn); } snprintf(name, sizeof(name), "hw:%d", card); pa_alsa_init_proplist_ctl(p, name); #ifdef HAVE_UDEV pa_udev_get_info(card, p); #endif } void pa_alsa_init_proplist_pcm_info(pa_core *c, pa_proplist *p, snd_pcm_info_t *pcm_info) { static const char * const alsa_class_table[SND_PCM_CLASS_LAST+1] = { [SND_PCM_CLASS_GENERIC] = "generic", [SND_PCM_CLASS_MULTI] = "multi", [SND_PCM_CLASS_MODEM] = "modem", [SND_PCM_CLASS_DIGITIZER] = "digitizer" }; static const char * const class_table[SND_PCM_CLASS_LAST+1] = { [SND_PCM_CLASS_GENERIC] = "sound", [SND_PCM_CLASS_MULTI] = NULL, [SND_PCM_CLASS_MODEM] = "modem", [SND_PCM_CLASS_DIGITIZER] = NULL }; static const char * const alsa_subclass_table[SND_PCM_SUBCLASS_LAST+1] = { [SND_PCM_SUBCLASS_GENERIC_MIX] = "generic-mix", [SND_PCM_SUBCLASS_MULTI_MIX] = "multi-mix" }; snd_pcm_class_t class; snd_pcm_subclass_t subclass; const char *n, *id, *sdn; int card; snd_pcm_sync_id_t sync_id; pa_assert(p); pa_assert(pcm_info); if ((card = snd_pcm_info_get_card(pcm_info)) >= 0) pa_alsa_init_proplist_card(c, p, card); pa_proplist_sets(p, PA_PROP_DEVICE_API, "alsa"); if ((class = snd_pcm_info_get_class(pcm_info)) <= SND_PCM_CLASS_LAST) { if (class_table[class]) pa_proplist_sets(p, PA_PROP_DEVICE_CLASS, class_table[class]); if (alsa_class_table[class]) pa_proplist_sets(p, "alsa.class", alsa_class_table[class]); } if ((subclass = snd_pcm_info_get_subclass(pcm_info)) <= SND_PCM_SUBCLASS_LAST) if (alsa_subclass_table[subclass]) pa_proplist_sets(p, "alsa.subclass", alsa_subclass_table[subclass]); if ((n = snd_pcm_info_get_name(pcm_info))) { char *t = pa_xstrdup(n); pa_proplist_sets(p, "alsa.name", pa_strip(t)); pa_xfree(t); } if ((id = snd_pcm_info_get_id(pcm_info))) pa_proplist_sets(p, "alsa.id", id); pa_proplist_setf(p, "alsa.subdevice", "%u", snd_pcm_info_get_subdevice(pcm_info)); if ((sdn = snd_pcm_info_get_subdevice_name(pcm_info))) pa_proplist_sets(p, "alsa.subdevice_name", sdn); pa_proplist_setf(p, "alsa.device", "%u", snd_pcm_info_get_device(pcm_info)); sync_id = snd_pcm_info_get_sync(pcm_info); pa_proplist_setf(p, "alsa.sync.id", "%08x:%08x:%08x:%08x", sync_id.id32[0], sync_id.id32[1], sync_id.id32[2], sync_id.id32[3]); } void pa_alsa_init_proplist_pcm(pa_core *c, pa_proplist *p, snd_pcm_t *pcm) { snd_pcm_hw_params_t *hwparams; snd_pcm_info_t *info; int bits, err; snd_pcm_hw_params_alloca(&hwparams); snd_pcm_info_alloca(&info); if ((err = snd_pcm_hw_params_current(pcm, hwparams)) < 0) pa_log_warn("Error fetching hardware parameter info: %s", pa_alsa_strerror(err)); else { if ((bits = snd_pcm_hw_params_get_sbits(hwparams)) >= 0) pa_proplist_setf(p, "alsa.resolution_bits", "%i", bits); } if ((err = snd_pcm_info(pcm, info)) < 0) pa_log_warn("Error fetching PCM info: %s", pa_alsa_strerror(err)); else pa_alsa_init_proplist_pcm_info(c, p, info); } void pa_alsa_init_proplist_ctl(pa_proplist *p, const char *name) { int err; snd_ctl_t *ctl; snd_ctl_card_info_t *info; const char *t; pa_assert(p); snd_ctl_card_info_alloca(&info); if ((err = snd_ctl_open(&ctl, name, 0)) < 0) { pa_log_warn("Error opening low-level control device '%s': %s", name, snd_strerror(err)); return; } if ((err = snd_ctl_card_info(ctl, info)) < 0) { pa_log_warn("Control device %s card info: %s", name, snd_strerror(err)); snd_ctl_close(ctl); return; } if ((t = snd_ctl_card_info_get_mixername(info)) && *t) pa_proplist_sets(p, "alsa.mixer_name", t); if ((t = snd_ctl_card_info_get_components(info)) && *t) pa_proplist_sets(p, "alsa.components", t); if ((t = snd_ctl_card_info_get_id(info)) && *t) pa_proplist_sets(p, "alsa.id", t); snd_ctl_close(ctl); } #if 0 int pa_alsa_recover_from_poll(snd_pcm_t *pcm, int revents) { snd_pcm_state_t state; snd_pcm_hw_params_t *hwparams; int err; pa_assert(pcm); if (revents & POLLERR) pa_log_debug("Got POLLERR from ALSA"); if (revents & POLLNVAL) pa_log_warn("Got POLLNVAL from ALSA"); if (revents & POLLHUP) pa_log_warn("Got POLLHUP from ALSA"); if (revents & POLLPRI) pa_log_warn("Got POLLPRI from ALSA"); if (revents & POLLIN) pa_log_debug("Got POLLIN from ALSA"); if (revents & POLLOUT) pa_log_debug("Got POLLOUT from ALSA"); state = snd_pcm_state(pcm); pa_log_debug("PCM state is %s", snd_pcm_state_name(state)); /* Try to recover from this error */ switch (state) { case SND_PCM_STATE_DISCONNECTED: /* Do not try to recover */ pa_log_info("Device disconnected."); return -1; case SND_PCM_STATE_XRUN: if ((err = snd_pcm_recover(pcm, -EPIPE, 1)) != 0) { pa_log_warn("Could not recover from POLLERR|POLLNVAL|POLLHUP and XRUN: %s", pa_alsa_strerror(err)); return -1; } break; case SND_PCM_STATE_SUSPENDED: snd_pcm_hw_params_alloca(&hwparams); if ((err = snd_pcm_hw_params_any(pcm, hwparams)) < 0) { pa_log_debug("snd_pcm_hw_params_any() failed: %s", pa_alsa_strerror(err)); return -1; } if (snd_pcm_hw_params_can_resume(hwparams)) { /* Retry resume 3 times before giving up, then fallback to restarting the stream. */ for (int i = 0; i < 3; i++) { if ((err = snd_pcm_resume(pcm)) == 0) return 0; if (err != -EAGAIN) break; pa_msleep(25); } pa_log_warn("Could not recover alsa device from SUSPENDED state, trying to restart PCM"); } /* Fall through */ default: snd_pcm_drop(pcm); return 1; } return 0; } pa_rtpoll_item* pa_alsa_build_pollfd(snd_pcm_t *pcm, pa_rtpoll *rtpoll) { int n, err; struct pollfd *pollfd; pa_rtpoll_item *item; pa_assert(pcm); if ((n = snd_pcm_poll_descriptors_count(pcm)) < 0) { pa_log("snd_pcm_poll_descriptors_count() failed: %s", pa_alsa_strerror(n)); return NULL; } item = pa_rtpoll_item_new(rtpoll, PA_RTPOLL_NEVER, (unsigned) n); pollfd = pa_rtpoll_item_get_pollfd(item, NULL); if ((err = snd_pcm_poll_descriptors(pcm, pollfd, (unsigned) n)) < 0) { pa_log("snd_pcm_poll_descriptors() failed: %s", pa_alsa_strerror(err)); pa_rtpoll_item_free(item); return NULL; } return item; } snd_pcm_sframes_t pa_alsa_safe_avail(snd_pcm_t *pcm, size_t hwbuf_size, const pa_sample_spec *ss) { snd_pcm_sframes_t n; size_t k; pa_assert(pcm); pa_assert(hwbuf_size > 0); pa_assert(ss); /* Some ALSA driver expose weird bugs, let's inform the user about * what is going on */ n = snd_pcm_avail(pcm); if (n <= 0) return n; k = (size_t) n * pa_frame_size(ss); if (PA_UNLIKELY(k >= hwbuf_size * 5 || k >= pa_bytes_per_second(ss)*10)) { PA_ONCE_BEGIN { char *dn = pa_alsa_get_driver_name_by_pcm(pcm); pa_log_debug(ngettext("snd_pcm_avail() returned a value that is exceptionally large: %lu byte (%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue to the ALSA developers.", "snd_pcm_avail() returned a value that is exceptionally large: %lu bytes (%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue to the ALSA developers.", (unsigned long) k), (unsigned long) k, (unsigned long) (pa_bytes_to_usec(k, ss) / PA_USEC_PER_MSEC), pa_strnull(dn)); pa_xfree(dn); pa_alsa_dump(PA_LOG_DEBUG, pcm); } PA_ONCE_END; /* Mhmm, let's try not to fail completely */ n = (snd_pcm_sframes_t) (hwbuf_size / pa_frame_size(ss)); } return n; } int pa_alsa_safe_delay(snd_pcm_t *pcm, snd_pcm_status_t *status, snd_pcm_sframes_t *delay, size_t hwbuf_size, const pa_sample_spec *ss, bool capture) { ssize_t k; size_t abs_k; int err; snd_pcm_sframes_t avail = 0; #if (SND_LIB_VERSION >= ((1<<16)|(1<<8)|0)) /* API additions in 1.1.0 */ snd_pcm_audio_tstamp_config_t tstamp_config; #endif pa_assert(pcm); pa_assert(delay); pa_assert(hwbuf_size > 0); pa_assert(ss); /* Some ALSA driver expose weird bugs, let's inform the user about * what is going on. We're going to get both the avail and delay values so * that we can compare and check them for capture. * This is done with snd_pcm_status() which provides * avail, delay and timestamp values in a single kernel call to improve * timer-based scheduling */ #if (SND_LIB_VERSION >= ((1<<16)|(1<<8)|0)) /* API additions in 1.1.0 */ /* The time stamp configuration needs to be set so that the * ALSA code will use the internal delay reported by the driver. * The time stamp configuration was introduced in alsa version 1.1.0. */ tstamp_config.type_requested = 1; /* ALSA default time stamp type */ tstamp_config.report_delay = 1; snd_pcm_status_set_audio_htstamp_config(status, &tstamp_config); #endif if ((err = snd_pcm_status(pcm, status)) < 0) return err; avail = snd_pcm_status_get_avail(status); *delay = snd_pcm_status_get_delay(status); k = (ssize_t) *delay * (ssize_t) pa_frame_size(ss); abs_k = k >= 0 ? (size_t) k : (size_t) -k; if (PA_UNLIKELY(abs_k >= hwbuf_size * 5 || abs_k >= pa_bytes_per_second(ss)*10)) { PA_ONCE_BEGIN { char *dn = pa_alsa_get_driver_name_by_pcm(pcm); pa_log_debug(ngettext("snd_pcm_delay() returned a value that is exceptionally large: %li byte (%s%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue to the ALSA developers.", "snd_pcm_delay() returned a value that is exceptionally large: %li bytes (%s%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue to the ALSA developers.", (signed long) k), (signed long) k, k < 0 ? "-" : "", (unsigned long) (pa_bytes_to_usec(abs_k, ss) / PA_USEC_PER_MSEC), pa_strnull(dn)); pa_xfree(dn); pa_alsa_dump(PA_LOG_DEBUG, pcm); } PA_ONCE_END; /* Mhmm, let's try not to fail completely */ if (k < 0) *delay = -(snd_pcm_sframes_t) (hwbuf_size / pa_frame_size(ss)); else *delay = (snd_pcm_sframes_t) (hwbuf_size / pa_frame_size(ss)); } if (capture) { abs_k = (size_t) avail * pa_frame_size(ss); if (PA_UNLIKELY(abs_k >= hwbuf_size * 5 || abs_k >= pa_bytes_per_second(ss)*10)) { PA_ONCE_BEGIN { char *dn = pa_alsa_get_driver_name_by_pcm(pcm); pa_log_debug(ngettext("snd_pcm_avail() returned a value that is exceptionally large: %lu byte (%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue to the ALSA developers.", "snd_pcm_avail() returned a value that is exceptionally large: %lu bytes (%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue to the ALSA developers.", (unsigned long) k), (unsigned long) k, (unsigned long) (pa_bytes_to_usec(k, ss) / PA_USEC_PER_MSEC), pa_strnull(dn)); pa_xfree(dn); pa_alsa_dump(PA_LOG_DEBUG, pcm); } PA_ONCE_END; /* Mhmm, let's try not to fail completely */ avail = (snd_pcm_sframes_t) (hwbuf_size / pa_frame_size(ss)); } if (PA_UNLIKELY(*delay < avail)) { PA_ONCE_BEGIN { char *dn = pa_alsa_get_driver_name_by_pcm(pcm); pa_log(_("snd_pcm_avail_delay() returned strange values: delay %lu is less than avail %lu.\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue to the ALSA developers."), (unsigned long) *delay, (unsigned long) avail, pa_strnull(dn)); pa_xfree(dn); pa_alsa_dump(PA_LOG_ERROR, pcm); } PA_ONCE_END; /* try to fixup */ *delay = avail; } } return 0; } int pa_alsa_safe_mmap_begin(snd_pcm_t *pcm, const snd_pcm_channel_area_t **areas, snd_pcm_uframes_t *offset, snd_pcm_uframes_t *frames, size_t hwbuf_size, const pa_sample_spec *ss) { int r; snd_pcm_uframes_t before; size_t k; pa_assert(pcm); pa_assert(areas); pa_assert(offset); pa_assert(frames); pa_assert(hwbuf_size > 0); pa_assert(ss); before = *frames; r = snd_pcm_mmap_begin(pcm, areas, offset, frames); if (r < 0) return r; k = (size_t) *frames * pa_frame_size(ss); if (PA_UNLIKELY(*frames > before || k >= hwbuf_size * 3 || k >= pa_bytes_per_second(ss)*10)) PA_ONCE_BEGIN { char *dn = pa_alsa_get_driver_name_by_pcm(pcm); pa_log_debug(ngettext("snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu byte (%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue to the ALSA developers.", "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu bytes (%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue to the ALSA developers.", (unsigned long) k), (unsigned long) k, (unsigned long) (pa_bytes_to_usec(k, ss) / PA_USEC_PER_MSEC), pa_strnull(dn)); pa_xfree(dn); pa_alsa_dump(PA_LOG_DEBUG, pcm); } PA_ONCE_END; return r; } #endif char *pa_alsa_get_driver_name(int card) { char *t, *m, *n; pa_assert(card >= 0); t = pa_sprintf_malloc("/sys/class/sound/card%i/device/driver/module", card); m = pa_readlink(t); pa_xfree(t); if (!m) return NULL; n = pa_xstrdup(pa_path_get_filename(m)); pa_xfree(m); return n; } char *pa_alsa_get_driver_name_by_pcm(snd_pcm_t *pcm) { int card; snd_pcm_info_t* info; snd_pcm_info_alloca(&info); pa_assert(pcm); if (snd_pcm_info(pcm, info) < 0) return NULL; if ((card = snd_pcm_info_get_card(info)) < 0) return NULL; return pa_alsa_get_driver_name(card); } #if 0 char *pa_alsa_get_reserve_name(const char *device) { const char *t; int i; pa_assert(device); if ((t = strchr(device, ':'))) device = t+1; if ((i = snd_card_get_index(device)) < 0) { int32_t k; if (pa_atoi(device, &k) < 0) return NULL; i = (int) k; } return pa_sprintf_malloc("Audio%i", i); } #endif static void dump_supported_rates(unsigned int* values) { pa_strbuf *buf; char *str; int i; buf = pa_strbuf_new(); for (i = 0; values[i]; i++) { pa_strbuf_printf(buf, " %u", values[i]); } str = pa_strbuf_to_string_free(buf); pa_log_debug("Supported rates:%s", str); pa_xfree(str); } unsigned int *pa_alsa_get_supported_rates(snd_pcm_t *pcm, unsigned int fallback_rate) { static unsigned int all_rates[] = { 8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000, 64000, 88200, 96000, 128000, 176400, 192000, 352800, 384000, 705600, 768000 }; bool supported[PA_ELEMENTSOF(all_rates)] = { false, }; snd_pcm_hw_params_t *hwparams; unsigned int i, j, n, *rates = NULL; int ret; snd_pcm_hw_params_alloca(&hwparams); if ((ret = snd_pcm_hw_params_any(pcm, hwparams)) < 0) { pa_log_debug("snd_pcm_hw_params_any() failed: %s", pa_alsa_strerror(ret)); return NULL; } for (i = 0, n = 0; i < PA_ELEMENTSOF(all_rates); i++) { if (snd_pcm_hw_params_test_rate(pcm, hwparams, all_rates[i], 0) == 0) { supported[i] = true; n++; } } if (n > 0) { rates = pa_xnew(unsigned int, n + 1); for (i = 0, j = 0; i < PA_ELEMENTSOF(all_rates); i++) { if (supported[i]) rates[j++] = all_rates[i]; } rates[j] = 0; } else { rates = pa_xnew(unsigned int, 2); rates[0] = fallback_rate; if ((ret = snd_pcm_hw_params_set_rate_near(pcm, hwparams, &rates[0], NULL)) < 0) { pa_log_debug("snd_pcm_hw_params_set_rate_near() failed: %s", pa_alsa_strerror(ret)); pa_xfree(rates); return NULL; } rates[1] = 0; } dump_supported_rates(rates); return rates; } pa_sample_format_t *pa_alsa_get_supported_formats(snd_pcm_t *pcm, pa_sample_format_t fallback_format) { static const snd_pcm_format_t format_trans_to_pcm[] = { [PA_SAMPLE_U8] = SND_PCM_FORMAT_U8, [PA_SAMPLE_ALAW] = SND_PCM_FORMAT_A_LAW, [PA_SAMPLE_ULAW] = SND_PCM_FORMAT_MU_LAW, [PA_SAMPLE_S16LE] = SND_PCM_FORMAT_S16_LE, [PA_SAMPLE_S16BE] = SND_PCM_FORMAT_S16_BE, [PA_SAMPLE_FLOAT32LE] = SND_PCM_FORMAT_FLOAT_LE, [PA_SAMPLE_FLOAT32BE] = SND_PCM_FORMAT_FLOAT_BE, [PA_SAMPLE_S32LE] = SND_PCM_FORMAT_S32_LE, [PA_SAMPLE_S32BE] = SND_PCM_FORMAT_S32_BE, [PA_SAMPLE_S24LE] = SND_PCM_FORMAT_S24_3LE, [PA_SAMPLE_S24BE] = SND_PCM_FORMAT_S24_3BE, [PA_SAMPLE_S24_32LE] = SND_PCM_FORMAT_S24_LE, [PA_SAMPLE_S24_32BE] = SND_PCM_FORMAT_S24_BE, }; static const pa_sample_format_t all_formats[] = { PA_SAMPLE_U8, PA_SAMPLE_ALAW, PA_SAMPLE_ULAW, PA_SAMPLE_S16LE, PA_SAMPLE_S16BE, PA_SAMPLE_FLOAT32LE, PA_SAMPLE_FLOAT32BE, PA_SAMPLE_S32LE, PA_SAMPLE_S32BE, PA_SAMPLE_S24LE, PA_SAMPLE_S24BE, PA_SAMPLE_S24_32LE, PA_SAMPLE_S24_32BE, }; bool supported[PA_ELEMENTSOF(all_formats)] = { false, }; snd_pcm_hw_params_t *hwparams; unsigned int i, j, n; pa_sample_format_t *formats = NULL; int ret; snd_pcm_hw_params_alloca(&hwparams); if ((ret = snd_pcm_hw_params_any(pcm, hwparams)) < 0) { pa_log_debug("snd_pcm_hw_params_any() failed: %s", pa_alsa_strerror(ret)); return NULL; } for (i = 0, n = 0; i < PA_ELEMENTSOF(all_formats); i++) { if (snd_pcm_hw_params_test_format(pcm, hwparams, format_trans_to_pcm[all_formats[i]]) == 0) { supported[i] = true; n++; } } if (n > 0) { formats = pa_xnew(pa_sample_format_t, n + 1); for (i = 0, j = 0; i < PA_ELEMENTSOF(all_formats); i++) { if (supported[i]) formats[j++] = all_formats[i]; } formats[j] = PA_SAMPLE_MAX; } else { formats = pa_xnew(pa_sample_format_t, 2); formats[0] = fallback_format; if ((ret = snd_pcm_hw_params_set_format(pcm, hwparams, format_trans_to_pcm[formats[0]])) < 0) { pa_log_debug("snd_pcm_hw_params_set_format() failed: %s", pa_alsa_strerror(ret)); pa_xfree(formats); return NULL; } formats[1] = PA_SAMPLE_MAX; } return formats; } bool pa_alsa_pcm_is_hw(snd_pcm_t *pcm) { snd_pcm_info_t* info; snd_pcm_info_alloca(&info); pa_assert(pcm); if (snd_pcm_info(pcm, info) < 0) return false; return snd_pcm_info_get_card(info) >= 0; } bool pa_alsa_pcm_is_modem(snd_pcm_t *pcm) { snd_pcm_info_t* info; snd_pcm_info_alloca(&info); pa_assert(pcm); if (snd_pcm_info(pcm, info) < 0) return false; return snd_pcm_info_get_class(info) == SND_PCM_CLASS_MODEM; } const char* pa_alsa_strerror(int errnum) { return snd_strerror(errnum); } #if 0 bool pa_alsa_may_tsched(bool want) { if (!want) return false; if (!pa_rtclock_hrtimer()) { /* We cannot depend on being woken up in time when the timers are inaccurate, so let's fallback to classic IO based playback then. */ pa_log_notice("Disabling timer-based scheduling because high-resolution timers are not available from the kernel."); return false; } if (pa_running_in_vm()) { /* We cannot depend on being woken up when we ask for in a VM, * so let's fallback to classic IO based playback then. */ pa_log_notice("Disabling timer-based scheduling because running inside a VM."); return false; } return true; } #endif #define SND_MIXER_ELEM_PULSEAUDIO (SND_MIXER_ELEM_LAST + 10) static snd_mixer_elem_t *pa_alsa_mixer_find(snd_mixer_t *mixer, snd_ctl_elem_iface_t iface, const char *name, unsigned int index, unsigned int device, unsigned int subdevice) { snd_mixer_elem_t *elem; for (elem = snd_mixer_first_elem(mixer); elem; elem = snd_mixer_elem_next(elem)) { snd_hctl_elem_t **_helem, *helem; if (snd_mixer_elem_get_type(elem) != SND_MIXER_ELEM_PULSEAUDIO) continue; _helem = snd_mixer_elem_get_private(elem); helem = *_helem; if (snd_hctl_elem_get_interface(helem) != iface) continue; if (!pa_streq(snd_hctl_elem_get_name(helem), name)) continue; if (snd_hctl_elem_get_index(helem) != index) continue; if (snd_hctl_elem_get_device(helem) != device) continue; if (snd_hctl_elem_get_subdevice(helem) != subdevice) continue; return elem; } return NULL; } snd_mixer_elem_t *pa_alsa_mixer_find_card(snd_mixer_t *mixer, struct pa_alsa_mixer_id *alsa_id, unsigned int device) { return pa_alsa_mixer_find(mixer, SND_CTL_ELEM_IFACE_CARD, alsa_id->name, alsa_id->index, device, 0); } snd_mixer_elem_t *pa_alsa_mixer_find_pcm(snd_mixer_t *mixer, const char *name, unsigned int device) { return pa_alsa_mixer_find(mixer, SND_CTL_ELEM_IFACE_PCM, name, 0, device, 0); } static int mixer_class_compare(const snd_mixer_elem_t *c1, const snd_mixer_elem_t *c2) { /* Dummy compare function */ return c1 == c2 ? 0 : (c1 > c2 ? 1 : -1); } static void mixer_melem_free(snd_mixer_elem_t *elem) { snd_hctl_elem_t **_helem; _helem = snd_mixer_elem_get_private(elem); pa_xfree(_helem); } static int mixer_class_event(snd_mixer_class_t *class, unsigned int mask, snd_hctl_elem_t *helem, snd_mixer_elem_t *melem) { int err; const char *name = snd_hctl_elem_get_name(helem); snd_hctl_elem_t **_helem; // NOTE: The remove event defined as '~0U`. if (mask == SND_CTL_EVENT_MASK_REMOVE) { // NOTE: unless remove pointer to melem from link-list at private_data of helem, hits // assertion in alsa-lib since the list is not empty. _helem = snd_mixer_elem_get_private(melem); *_helem = NULL; snd_mixer_elem_detach(melem, helem); } else if (mask & SND_CTL_EVENT_MASK_ADD) { snd_ctl_elem_iface_t iface = snd_hctl_elem_get_interface(helem); if (iface == SND_CTL_ELEM_IFACE_CARD || iface == SND_CTL_ELEM_IFACE_PCM) { snd_mixer_t *mixer = snd_mixer_class_get_mixer(class); snd_ctl_elem_iface_t iface = snd_hctl_elem_get_interface(helem); const char *name = snd_hctl_elem_get_name(helem); const int index = snd_hctl_elem_get_index(helem); const int device = snd_hctl_elem_get_device(helem); const int subdevice = snd_hctl_elem_get_subdevice(helem); snd_mixer_elem_t *new_melem; bool found = true; new_melem = pa_alsa_mixer_find(mixer, iface, name, index, device, subdevice); if (!new_melem) { _helem = pa_xmalloc(sizeof(snd_hctl_elem_t *)); *_helem = helem; /* Put the hctl pointer as our private data - it will be useful for callbacks */ if ((err = snd_mixer_elem_new(&new_melem, SND_MIXER_ELEM_PULSEAUDIO, 0, _helem, mixer_melem_free)) < 0) { pa_log_warn("snd_mixer_elem_new failed: %s", pa_alsa_strerror(err)); return 0; } found = false; } else { _helem = snd_mixer_elem_get_private(new_melem); if (_helem) { char *s1, *s2; snd_ctl_elem_id_t *id1, *id2; snd_ctl_elem_id_alloca(&id1); snd_ctl_elem_id_alloca(&id2); snd_hctl_elem_get_id(helem, id1); snd_hctl_elem_get_id(*_helem, id2); s1 = snd_ctl_ascii_elem_id_get(id1); s2 = snd_ctl_ascii_elem_id_get(id2); pa_log_warn("mixer_class_event - duplicate mixer controls: %s | %s", s1, s2); free(s2); free(s1); return 0; } *_helem = helem; } if ((err = snd_mixer_elem_attach(new_melem, helem)) < 0) { pa_log_warn("snd_mixer_elem_attach failed: %s", pa_alsa_strerror(err)); snd_mixer_elem_free(melem); return 0; } if (!found) { if ((err = snd_mixer_elem_add(new_melem, class)) < 0) { pa_log_warn("snd_mixer_elem_add failed: %s", pa_alsa_strerror(err)); return 0; } } } } else if (mask & SND_CTL_EVENT_MASK_VALUE) { snd_mixer_elem_value(melem); /* Calls the element callback */ return 0; } else pa_log_info("Got an unknown mixer class event for %s: mask 0x%x", name, mask); return 0; } static int prepare_mixer(snd_mixer_t *mixer, const char *dev, snd_hctl_t *hctl) { int err; snd_mixer_class_t *class; pa_assert(mixer); pa_assert(dev); if ((err = snd_mixer_attach_hctl(mixer, hctl)) < 0) { pa_log_info("Unable to attach to mixer %s: %s", dev, pa_alsa_strerror(err)); return -1; } if (snd_mixer_class_malloc(&class)) { pa_log_info("Failed to allocate mixer class for %s", dev); return -1; } snd_mixer_class_set_event(class, mixer_class_event); snd_mixer_class_set_compare(class, mixer_class_compare); if ((err = snd_mixer_class_register(class, mixer)) < 0) { pa_log_info("Unable register mixer class for %s: %s", dev, pa_alsa_strerror(err)); snd_mixer_class_free(class); return -1; } /* From here on, the mixer class is deallocated by alsa on snd_mixer_close/free. */ if ((err = snd_mixer_selem_register(mixer, NULL, NULL)) < 0) { pa_log_warn("Unable to register mixer: %s", pa_alsa_strerror(err)); return -1; } if ((err = snd_mixer_load(mixer)) < 0) { pa_log_warn("Unable to load mixer: %s", pa_alsa_strerror(err)); return -1; } pa_log_info("Successfully attached to mixer '%s'", dev); return 0; } snd_mixer_t *pa_alsa_open_mixer(pa_hashmap *mixers, int alsa_card_index, bool probe) { char *md = pa_sprintf_malloc("hw:%i", alsa_card_index); snd_mixer_t *m = pa_alsa_open_mixer_by_name(mixers, md, probe); pa_xfree(md); return m; } pa_alsa_mixer *pa_alsa_create_mixer(pa_hashmap *mixers, const char *dev, snd_mixer_t *m, bool probe) { pa_alsa_mixer *pm; pm = pa_xnew0(pa_alsa_mixer, 1); if (pm == NULL) return NULL; pm->used_for_probe_only = probe; pm->mixer_handle = m; pa_hashmap_put(mixers, pa_xstrdup(dev), pm); return pm; } snd_mixer_t *pa_alsa_open_mixer_by_name(pa_hashmap *mixers, const char *dev, bool probe) { int err; snd_mixer_t *m; snd_hctl_t *hctl; pa_alsa_mixer *pm; pa_assert(mixers); pa_assert(dev); pm = pa_hashmap_get(mixers, dev); if (pm) { if (!probe) pm->used_for_probe_only = false; return pm->mixer_handle; } if ((err = snd_mixer_open(&m, 0)) < 0) { pa_log("Error opening mixer: %s", pa_alsa_strerror(err)); return NULL; } err = snd_hctl_open(&hctl, dev, 0); if (err < 0) { pa_log("Error opening hctl device: %s", pa_alsa_strerror(err)); goto __close; } if (prepare_mixer(m, dev, hctl) >= 0) { /* get the ALSA card number (index) and ID (alias) and create two identical mixers */ char *p, *dev2, *dev_idx, *dev_id; snd_ctl_card_info_t *info; snd_ctl_card_info_alloca(&info); err = snd_ctl_card_info(snd_hctl_ctl(hctl), info); if (err < 0) goto __std; dev2 = pa_xstrdup(dev); if (dev2 == NULL) goto __close; p = strchr(dev2, ':'); /* sanity check - only hw: devices */ if (p == NULL || (p - dev2) < 2 || !pa_strneq(p - 2, "hw:", 3)) { pa_xfree(dev2); goto __std; } *p = '\0'; dev_idx = pa_sprintf_malloc("%s:%u", dev2, snd_ctl_card_info_get_card(info)); dev_id = pa_sprintf_malloc("%s:%s", dev2, snd_ctl_card_info_get_id(info)); pa_log_debug("ALSA alias mixers: %s = %s", dev_idx, dev_id); if (dev_idx && dev_id && (strcmp(dev, dev_idx) == 0 || strcmp(dev, dev_id) == 0)) { pm = pa_alsa_create_mixer(mixers, dev_idx, m, probe); if (pm) { pa_alsa_mixer *pm2; pm2 = pa_alsa_create_mixer(mixers, dev_id, m, probe); if (pm2) { pm->alias = pm2; pm2->alias = pm; } } } pa_xfree(dev_id); pa_xfree(dev_idx); pa_xfree(dev2); __std: if (pm == NULL) pm = pa_alsa_create_mixer(mixers, dev, m, probe); if (pm) return m; } __close: snd_mixer_close(m); return NULL; } snd_mixer_t *pa_alsa_open_mixer_for_pcm(pa_hashmap *mixers, snd_pcm_t *pcm, bool probe) { snd_pcm_info_t* info; snd_pcm_info_alloca(&info); pa_assert(pcm); if (snd_pcm_info(pcm, info) >= 0) { int card_idx; if ((card_idx = snd_pcm_info_get_card(info)) >= 0) return pa_alsa_open_mixer(mixers, card_idx, probe); } return NULL; } #if 0 void pa_alsa_mixer_set_fdlist(pa_hashmap *mixers, snd_mixer_t *mixer_handle, pa_mainloop_api *ml) { pa_alsa_mixer *pm; void *state; PA_HASHMAP_FOREACH(pm, mixers, state) if (pm->mixer_handle == mixer_handle) { pm->used_for_probe_only = false; if (!pm->fdl) { pm->fdl = pa_alsa_fdlist_new(); if (pm->fdl) pa_alsa_fdlist_set_handle(pm->fdl, pm->mixer_handle, NULL, ml); } } } #endif void pa_alsa_mixer_free(pa_alsa_mixer *mixer) { if (mixer->mixer_handle && mixer->alias == NULL) snd_mixer_close(mixer->mixer_handle); if (mixer->alias) mixer->alias->alias = NULL; pa_xfree(mixer); } int pa_alsa_get_hdmi_eld(snd_hctl_elem_t *elem, pa_hdmi_eld *eld) { /* The ELD format is specific to HDA Intel sound cards and defined in the HDA specification: http://www.intel.com/content/www/us/en/standards/high-definition-audio-specification.html */ int err; snd_ctl_elem_info_t *info; snd_ctl_elem_value_t *value; uint8_t *elddata; unsigned int eldsize, mnl, sad_count; unsigned int device; pa_assert(eld != NULL); pa_assert(elem != NULL); /* Does it have any contents? */ snd_ctl_elem_info_alloca(&info); snd_ctl_elem_value_alloca(&value); if ((err = snd_hctl_elem_info(elem, info)) < 0 || (err = snd_hctl_elem_read(elem, value)) < 0) { pa_log_warn("Accessing ELD control failed with error %s", snd_strerror(err)); return -1; } device = snd_hctl_elem_get_device(elem); eldsize = snd_ctl_elem_info_get_count(info); elddata = (unsigned char *) snd_ctl_elem_value_get_bytes(value); if (elddata == NULL || eldsize == 0) { pa_log_debug("ELD info empty (for device=%d)", device); return -1; } if (eldsize < 20 || eldsize > 256) { pa_log_debug("ELD info has wrong size (for device=%d)", device); return -1; } /* Try to fetch monitor name */ mnl = elddata[4] & 0x1f; if (mnl == 0 || mnl > 16 || 20 + mnl > eldsize) { pa_log_debug("No monitor name in ELD info (for device=%d)", device); mnl = 0; } memcpy(eld->monitor_name, &elddata[20], mnl); eld->monitor_name[mnl] = '\0'; if (mnl) pa_log_debug("Monitor name in ELD info is '%s' (for device=%d)", eld->monitor_name, device); /* Fetch Short Audio Descriptors */ sad_count = (elddata[5] & 0xf0) >> 4; pa_log_debug("SAD count in ELD info is %u (for device=%d)", sad_count, device); if (20 + mnl + 3 * sad_count > eldsize) { pa_log_debug("Invalid SAD count (%u) in ELD info (for device=%d)", sad_count, device); sad_count = 0; } /* Look up speaker presence in Speaker Allocation Data Block */ eld->speakers = elddata[7] & 0x7f; eld->lpcm_channels = 0; eld->iec958_codecs = 0; for (unsigned i = 0; i < sad_count; i++) { uint8_t *sad = &elddata[20 + mnl + 3 * i]; uint8_t lpcm_channels; /* https://en.wikipedia.org/wiki/Extended_Display_Identification_Data#Audio_Data_Blocks */ switch ((sad[0] & 0x78) >> 3) { case 1: eld->iec958_codecs |= 1ULL << SPA_AUDIO_IEC958_CODEC_PCM; /* Lowest 3 bits are channel count - 1 */ lpcm_channels = (sad[0] & 0x07) + 1; if (lpcm_channels > eld->lpcm_channels) eld->lpcm_channels = lpcm_channels; break; case 2: eld->iec958_codecs |= 1ULL << SPA_AUDIO_IEC958_CODEC_AC3; break; case 3: eld->iec958_codecs |= 1ULL << SPA_AUDIO_IEC958_CODEC_MPEG; break; case 4: eld->iec958_codecs |= 1ULL << SPA_AUDIO_IEC958_CODEC_MPEG; break; case 5: eld->iec958_codecs |= 1ULL << SPA_AUDIO_IEC958_CODEC_MPEG; break; case 6: eld->iec958_codecs |= 1ULL << SPA_AUDIO_IEC958_CODEC_MPEG2_AAC; break; case 7: eld->iec958_codecs |= 1ULL << SPA_AUDIO_IEC958_CODEC_DTS; break; case 10: eld->iec958_codecs |= 1ULL << SPA_AUDIO_IEC958_CODEC_EAC3; break; case 11: eld->iec958_codecs |= 1ULL << SPA_AUDIO_IEC958_CODEC_DTSHD; break; case 12: eld->iec958_codecs |= 1ULL << SPA_AUDIO_IEC958_CODEC_TRUEHD; break; default: eld->iec958_codecs |= 1ULL << SPA_AUDIO_IEC958_CODEC_UNKNOWN; break; } } return 0; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/acp/alsa-util.h000066400000000000000000000170621511204443500264770ustar00rootroot00000000000000#ifndef fooalsautilhfoo #define fooalsautilhfoo /*** This file is part of PulseAudio. Copyright 2004-2006 Lennart Poettering Copyright 2006 Pierre Ossman for Cendio AB PulseAudio is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. PulseAudio is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with PulseAudio; if not, see . ***/ #include #include #include "compat.h" #include "alsa-mixer.h" enum { PA_ALSA_ERR_UNSPECIFIED = 1, PA_ALSA_ERR_UCM_OPEN = 1000, PA_ALSA_ERR_UCM_NO_VERB = 1001, PA_ALSA_ERR_UCM_LINKED = 1002 }; int pa_alsa_set_hw_params( snd_pcm_t *pcm_handle, pa_sample_spec *ss, /* modified at return */ snd_pcm_uframes_t *period_size, /* modified at return */ snd_pcm_uframes_t *buffer_size, /* modified at return */ snd_pcm_uframes_t tsched_size, bool *use_mmap, /* modified at return */ bool *use_tsched, /* modified at return */ bool require_exact_channel_number); int pa_alsa_set_sw_params( snd_pcm_t *pcm, snd_pcm_uframes_t avail_min, bool period_event); #if 0 /* Picks a working mapping from the profile set based on the specified ss/map */ snd_pcm_t *pa_alsa_open_by_device_id_auto( const char *dev_id, char **dev, /* modified at return */ pa_sample_spec *ss, /* modified at return */ pa_channel_map* map, /* modified at return */ int mode, snd_pcm_uframes_t *period_size, /* modified at return */ snd_pcm_uframes_t *buffer_size, /* modified at return */ snd_pcm_uframes_t tsched_size, bool *use_mmap, /* modified at return */ bool *use_tsched, /* modified at return */ pa_sample_format_t **query_supported_formats, /* modified at return */ unsigned int **query_supported_rates, /* modified at return */ pa_alsa_profile_set *ps, pa_alsa_mapping **mapping); /* modified at return */ #endif /* Uses the specified mapping */ snd_pcm_t *pa_alsa_open_by_device_id_mapping( const char *dev_id, char **dev, /* modified at return */ pa_sample_spec *ss, /* modified at return */ pa_channel_map* map, /* modified at return */ int mode, snd_pcm_uframes_t *period_size, /* modified at return */ snd_pcm_uframes_t *buffer_size, /* modified at return */ snd_pcm_uframes_t tsched_size, bool *use_mmap, /* modified at return */ bool *use_tsched, /* modified at return */ pa_sample_format_t **query_supported_formats, /* modified at return */ unsigned int **query_supported_rates, /* modified at return */ pa_alsa_mapping *mapping); /* Opens the explicit ALSA device */ snd_pcm_t *pa_alsa_open_by_device_string( const char *dir, char **dev, /* modified at return */ pa_sample_spec *ss, /* modified at return */ pa_channel_map* map, /* modified at return */ int mode, snd_pcm_uframes_t *period_size, /* modified at return */ snd_pcm_uframes_t *buffer_size, /* modified at return */ snd_pcm_uframes_t tsched_size, bool *use_mmap, /* modified at return */ bool *use_tsched, /* modified at return */ pa_sample_format_t **query_supported_formats, /* modified at return */ unsigned int **query_supported_rates, /* modified at return */ bool require_exact_channel_number); /* Opens the explicit ALSA device with a fallback list */ snd_pcm_t *pa_alsa_open_by_template( char **template, const char *dev_id, char **dev, /* modified at return */ pa_sample_spec *ss, /* modified at return */ pa_channel_map* map, /* modified at return */ int mode, snd_pcm_uframes_t *period_size, /* modified at return */ snd_pcm_uframes_t *buffer_size, /* modified at return */ snd_pcm_uframes_t tsched_size, bool *use_mmap, /* modified at return */ bool *use_tsched, /* modified at return */ pa_sample_format_t **query_supported_formats, /* modified at return */ unsigned int **query_supported_rates, /* modified at return */ bool require_exact_channel_number); #if 0 void pa_alsa_dump(pa_log_level_t level, snd_pcm_t *pcm); void pa_alsa_dump_status(snd_pcm_t *pcm); #endif int pa_alsa_close(snd_pcm_t **pcm); void pa_alsa_refcnt_inc(void); void pa_alsa_refcnt_dec(void); void pa_alsa_init_proplist_pcm_info(pa_core *c, pa_proplist *p, snd_pcm_info_t *pcm_info); void pa_alsa_init_proplist_card(pa_core *c, pa_proplist *p, int card); void pa_alsa_init_proplist_pcm(pa_core *c, pa_proplist *p, snd_pcm_t *pcm); void pa_alsa_init_proplist_ctl(pa_proplist *p, const char *name); bool pa_alsa_init_description(pa_proplist *p, pa_card *card); #if 0 int pa_alsa_recover_from_poll(snd_pcm_t *pcm, int revents); pa_rtpoll_item* pa_alsa_build_pollfd(snd_pcm_t *pcm, pa_rtpoll *rtpoll); snd_pcm_sframes_t pa_alsa_safe_avail(snd_pcm_t *pcm, size_t hwbuf_size, const pa_sample_spec *ss); int pa_alsa_safe_delay(snd_pcm_t *pcm, snd_pcm_status_t *status, snd_pcm_sframes_t *delay, size_t hwbuf_size, const pa_sample_spec *ss, bool capture); int pa_alsa_safe_mmap_begin(snd_pcm_t *pcm, const snd_pcm_channel_area_t **areas, snd_pcm_uframes_t *offset, snd_pcm_uframes_t *frames, size_t hwbuf_size, const pa_sample_spec *ss); #endif char *pa_alsa_get_driver_name(int card); char *pa_alsa_get_driver_name_by_pcm(snd_pcm_t *pcm); char *pa_alsa_get_reserve_name(const char *device); unsigned int *pa_alsa_get_supported_rates(snd_pcm_t *pcm, unsigned int fallback_rate); pa_sample_format_t *pa_alsa_get_supported_formats(snd_pcm_t *pcm, pa_sample_format_t fallback_format); bool pa_alsa_pcm_is_hw(snd_pcm_t *pcm); bool pa_alsa_pcm_is_modem(snd_pcm_t *pcm); const char* pa_alsa_strerror(int errnum); #if 0 bool pa_alsa_may_tsched(bool want); #endif snd_mixer_elem_t *pa_alsa_mixer_find_card(snd_mixer_t *mixer, struct pa_alsa_mixer_id *alsa_id, unsigned int device); snd_mixer_elem_t *pa_alsa_mixer_find_pcm(snd_mixer_t *mixer, const char *name, unsigned int device); snd_mixer_t *pa_alsa_open_mixer(pa_hashmap *mixers, int alsa_card_index, bool probe); snd_mixer_t *pa_alsa_open_mixer_by_name(pa_hashmap *mixers, const char *dev, bool probe); snd_mixer_t *pa_alsa_open_mixer_for_pcm(pa_hashmap *mixers, snd_pcm_t *pcm, bool probe); #if 0 void pa_alsa_mixer_set_fdlist(pa_hashmap *mixers, snd_mixer_t *mixer, pa_mainloop_api *ml); #endif void pa_alsa_mixer_free(pa_alsa_mixer *mixer); typedef struct pa_hdmi_eld pa_hdmi_eld; struct pa_hdmi_eld { char monitor_name[17]; uint8_t speakers; uint64_t iec958_codecs; uint8_t lpcm_channels; }; int pa_alsa_get_hdmi_eld(snd_hctl_elem_t *elem, pa_hdmi_eld *eld); #endif pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/acp/array.h000066400000000000000000000061521511204443500257200ustar00rootroot00000000000000/* ALSA Card Profile */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef PA_ARRAY_H #define PA_ARRAY_H #include #include #ifdef __cplusplus extern "C" { #endif typedef struct pa_array { void *data; /**< pointer to array data */ size_t size; /**< length of array in bytes */ size_t alloc; /**< number of allocated memory in \a data */ size_t extend; /**< number of bytes to extend with */ } pa_array; #define PW_ARRAY_INIT(extend) ((struct pa_array) { NULL, 0, 0, (extend) }) #define pa_array_get_len_s(a,s) ((a)->size / (s)) #define pa_array_get_unchecked_s(a,idx,s,t) (t*)((uint8_t*)(a)->data + (int)((idx)*(s))) #define pa_array_check_index_s(a,idx,s) ((idx) < pa_array_get_len_s(a,s)) #define pa_array_get_len(a,t) pa_array_get_len_s(a,sizeof(t)) #define pa_array_get_unchecked(a,idx,t) pa_array_get_unchecked_s(a,idx,sizeof(t),t) #define pa_array_check_index(a,idx,t) pa_array_check_index_s(a,idx,sizeof(t)) #define pa_array_first(a) ((a)->data) #define pa_array_end(a) (void*)((uint8_t*)(a)->data + (int)(a)->size) #define pa_array_check(a,p) ((void*)((uint8_t*)p + (int)sizeof(*p)) <= pa_array_end(a)) #define pa_array_for_each(pos, array) \ for (pos = (__typeof__(pos)) pa_array_first(array); \ pa_array_check(array, pos); \ (pos)++) #define pa_array_consume(pos, array) \ while (pos = (__typeof__(pos)) pa_array_first(array) && \ pa_array_check(array, pos) #define pa_array_remove(a,p) \ ({ \ (a)->size -= sizeof(*(p)); \ memmove(p, ((uint8_t*)(p) + (int)sizeof(*(p))), \ (uint8_t*)pa_array_end(a) - (uint8_t*)(p)); \ }) static inline void pa_array_init(pa_array *arr, size_t extend) { arr->data = NULL; arr->size = arr->alloc = 0; arr->extend = extend; } static inline void pa_array_clear(pa_array *arr) { free(arr->data); } static inline void pa_array_reset(pa_array *arr) { arr->size = 0; } static inline int pa_array_ensure_size(pa_array *arr, size_t size) { size_t alloc, need; alloc = arr->alloc; need = arr->size + size; if (alloc < need) { void *data; alloc = alloc > arr->extend ? alloc : arr->extend; while (alloc < need) alloc *= 2; if ((data = realloc(arr->data, alloc)) == NULL) return -errno; arr->data = data; arr->alloc = alloc; } return 0; } static inline void *pa_array_add(pa_array *arr, size_t size) { void *p; if (pa_array_ensure_size(arr, size) < 0) return NULL; p = (void*)((uint8_t*)arr->data + (int)arr->size); arr->size += size; return p; } static inline void *pa_array_add_fixed(pa_array *arr, size_t size) { void *p; if (arr->alloc < arr->size + size) { errno = ENOSPC; return NULL; } p = ((uint8_t*)arr->data + (int)arr->size); arr->size += size; return p; } #define pa_array_add_ptr(a,p) \ *((void**) pa_array_add(a, sizeof(void*))) = (p) static inline int pa_array_add_data(pa_array *arr, const void *data, size_t size) { void *d; if ((d = pa_array_add(arr, size)) == NULL) return -1; memcpy(d, data, size); return size; } #ifdef __cplusplus } /* extern "C" */ #endif #endif /* PA_ARRAY_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/acp/card.h000066400000000000000000000032071511204443500255110ustar00rootroot00000000000000/*** This file is part of PulseAudio. Copyright 2004-2006 Lennart Poettering Copyright 2006 Pierre Ossman for Cendio AB PulseAudio is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. PulseAudio is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with PulseAudio; if not, see . ***/ #ifndef PULSE_CARD_H #define PULSE_CARD_H #include "compat.h" #ifdef __cplusplus extern "C" { #else #include #endif typedef struct pa_card pa_card; struct pa_card { struct acp_card card; pa_core *core; char *name; char *driver; pa_proplist *proplist; bool use_ucm; bool soft_mixer; bool disable_mixer_path; bool auto_profile; bool auto_port; bool ignore_dB; bool disable_pro_audio; bool use_eld_channels; uint32_t rate; uint32_t pro_channels; pa_alsa_ucm_config ucm; pa_alsa_profile_set *profile_set; pa_hashmap *ports; pa_hashmap *profiles; pa_hashmap *jacks; struct { pa_dynarray ports; pa_dynarray profiles; pa_dynarray devices; } out; const struct acp_card_events *events; void *user_data; }; bool pa_alsa_device_init_description(pa_proplist *p, pa_card *card); #ifdef __cplusplus } #endif #endif /* PULSE_CARD_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/acp/channelmap.h000066400000000000000000000441041511204443500267070ustar00rootroot00000000000000/*** This file is part of PulseAudio. Copyright 2004-2006 Lennart Poettering Copyright 2006 Pierre Ossman for Cendio AB PulseAudio is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. PulseAudio is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with PulseAudio; if not, see . ***/ #ifndef PULSE_CHANNELMAP_H #define PULSE_CHANNELMAP_H #include "spa/utils/defs.h" #ifdef __cplusplus extern "C" { #endif #ifdef SPA_AUDIO_MAX_CHANNELS #define PA_CHANNELS_MAX ((int)SPA_AUDIO_MAX_CHANNELS) #else #define PA_CHANNELS_MAX 64 #endif #define PA_CHANNEL_MAP_SNPRINT_MAX (PA_CHANNELS_MAX * 32) typedef enum pa_channel_map_def { PA_CHANNEL_MAP_AIFF, PA_CHANNEL_MAP_ALSA, PA_CHANNEL_MAP_AUX, PA_CHANNEL_MAP_WAVEEX, PA_CHANNEL_MAP_OSS, PA_CHANNEL_MAP_DEF_MAX, PA_CHANNEL_MAP_DEFAULT = PA_CHANNEL_MAP_AIFF } pa_channel_map_def_t; typedef enum pa_channel_position { PA_CHANNEL_POSITION_INVALID = -1, PA_CHANNEL_POSITION_MONO = 0, PA_CHANNEL_POSITION_FRONT_LEFT, /**< Apple, Dolby call this 'Left' */ PA_CHANNEL_POSITION_FRONT_RIGHT, /**< Apple, Dolby call this 'Right' */ PA_CHANNEL_POSITION_FRONT_CENTER, /**< Apple, Dolby call this 'Center' */ /** \cond fulldocs */ PA_CHANNEL_POSITION_LEFT = PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_RIGHT = PA_CHANNEL_POSITION_FRONT_RIGHT, PA_CHANNEL_POSITION_CENTER = PA_CHANNEL_POSITION_FRONT_CENTER, /** \endcond */ PA_CHANNEL_POSITION_REAR_CENTER, /**< Microsoft calls this 'Back Center', Apple calls this 'Center Surround', Dolby calls this 'Surround Rear Center' */ PA_CHANNEL_POSITION_REAR_LEFT, /**< Microsoft calls this 'Back Left', Apple calls this 'Left Surround' (!), Dolby calls this 'Surround Rear Left' */ PA_CHANNEL_POSITION_REAR_RIGHT, /**< Microsoft calls this 'Back Right', Apple calls this 'Right Surround' (!), Dolby calls this 'Surround Rear Right' */ PA_CHANNEL_POSITION_LFE, /**< Microsoft calls this 'Low Frequency', Apple calls this 'LFEScreen' */ /** \cond fulldocs */ PA_CHANNEL_POSITION_SUBWOOFER = PA_CHANNEL_POSITION_LFE, /** \endcond */ PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER, /**< Apple, Dolby call this 'Left Center' */ PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER, /**< Apple, Dolby call this 'Right Center */ PA_CHANNEL_POSITION_SIDE_LEFT, /**< Apple calls this 'Left Surround Direct', Dolby calls this 'Surround Left' (!) */ PA_CHANNEL_POSITION_SIDE_RIGHT, /**< Apple calls this 'Right Surround Direct', Dolby calls this 'Surround Right' (!) */ PA_CHANNEL_POSITION_AUX0, PA_CHANNEL_POSITION_AUX1, PA_CHANNEL_POSITION_AUX2, PA_CHANNEL_POSITION_AUX3, PA_CHANNEL_POSITION_AUX4, PA_CHANNEL_POSITION_AUX5, PA_CHANNEL_POSITION_AUX6, PA_CHANNEL_POSITION_AUX7, PA_CHANNEL_POSITION_AUX8, PA_CHANNEL_POSITION_AUX9, PA_CHANNEL_POSITION_AUX10, PA_CHANNEL_POSITION_AUX11, PA_CHANNEL_POSITION_AUX12, PA_CHANNEL_POSITION_AUX13, PA_CHANNEL_POSITION_AUX14, PA_CHANNEL_POSITION_AUX15, PA_CHANNEL_POSITION_AUX16, PA_CHANNEL_POSITION_AUX17, PA_CHANNEL_POSITION_AUX18, PA_CHANNEL_POSITION_AUX19, PA_CHANNEL_POSITION_AUX20, PA_CHANNEL_POSITION_AUX21, PA_CHANNEL_POSITION_AUX22, PA_CHANNEL_POSITION_AUX23, PA_CHANNEL_POSITION_AUX24, PA_CHANNEL_POSITION_AUX25, PA_CHANNEL_POSITION_AUX26, PA_CHANNEL_POSITION_AUX27, PA_CHANNEL_POSITION_AUX28, PA_CHANNEL_POSITION_AUX29, PA_CHANNEL_POSITION_AUX30, PA_CHANNEL_POSITION_AUX31, PA_CHANNEL_POSITION_TOP_CENTER, /**< Apple calls this 'Top Center Surround' */ PA_CHANNEL_POSITION_TOP_FRONT_LEFT, /**< Apple calls this 'Vertical Height Left' */ PA_CHANNEL_POSITION_TOP_FRONT_RIGHT, /**< Apple calls this 'Vertical Height Right' */ PA_CHANNEL_POSITION_TOP_FRONT_CENTER, /**< Apple calls this 'Vertical Height Center' */ PA_CHANNEL_POSITION_TOP_REAR_LEFT, /**< Microsoft and Apple call this 'Top Back Left' */ PA_CHANNEL_POSITION_TOP_REAR_RIGHT, /**< Microsoft and Apple call this 'Top Back Right' */ PA_CHANNEL_POSITION_TOP_REAR_CENTER, /**< Microsoft and Apple call this 'Top Back Center' */ PA_CHANNEL_POSITION_MAX } pa_channel_position_t; typedef struct pa_channel_map { uint8_t channels; pa_channel_position_t map[PA_CHANNELS_MAX]; } pa_channel_map; static inline int pa_channels_valid(uint8_t channels) { return channels > 0 && channels <= PA_CHANNELS_MAX; } static inline int pa_channel_map_valid(const pa_channel_map *map) { unsigned c; if (!pa_channels_valid(map->channels)) return 0; for (c = 0; c < map->channels; c++) if (map->map[c] < 0 || map->map[c] >= PA_CHANNEL_POSITION_MAX) return 0; return 1; } static inline pa_channel_map* pa_channel_map_init(pa_channel_map *m) { unsigned c; m->channels = 0; for (c = 0; c < PA_CHANNELS_MAX; c++) m->map[c] = PA_CHANNEL_POSITION_INVALID; return m; } static inline pa_channel_map* pa_channel_map_init_auto(pa_channel_map *m, unsigned channels, pa_channel_map_def_t def) { unsigned i; pa_assert(m); pa_assert(pa_channels_valid(channels)); pa_assert(def < PA_CHANNEL_MAP_DEF_MAX); pa_channel_map_init(m); m->channels = (uint8_t) channels; switch (def) { case PA_CHANNEL_MAP_ALSA: switch (channels) { case 1: m->map[0] = PA_CHANNEL_POSITION_MONO; return m; case 8: m->map[6] = PA_CHANNEL_POSITION_SIDE_LEFT; m->map[7] = PA_CHANNEL_POSITION_SIDE_RIGHT; SPA_FALLTHROUGH; case 6: m->map[5] = PA_CHANNEL_POSITION_LFE; SPA_FALLTHROUGH; case 5: m->map[4] = PA_CHANNEL_POSITION_FRONT_CENTER; SPA_FALLTHROUGH; case 4: m->map[2] = PA_CHANNEL_POSITION_REAR_LEFT; m->map[3] = PA_CHANNEL_POSITION_REAR_RIGHT; SPA_FALLTHROUGH; case 2: m->map[0] = PA_CHANNEL_POSITION_FRONT_LEFT; m->map[1] = PA_CHANNEL_POSITION_FRONT_RIGHT; return m; default: return NULL; } case PA_CHANNEL_MAP_AUX: for (i = 0; i < channels; i++) m->map[i] = PA_CHANNEL_POSITION_AUX0 + (i & 31); return m; default: break; } return NULL; } static inline pa_channel_map* pa_channel_map_init_extend(pa_channel_map *m, unsigned channels, pa_channel_map_def_t def) { pa_channel_map *r; if ((r = pa_channel_map_init_auto(m, channels, def)) != NULL) return r; return pa_channel_map_init_auto(m, channels, PA_CHANNEL_MAP_AUX); } typedef uint64_t pa_channel_position_mask_t; #define PA_CHANNEL_POSITION_MASK(f) ((pa_channel_position_mask_t) (1ULL << (f))) #define PA_CHANNEL_POSITION_MASK_LEFT \ (PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_FRONT_LEFT) \ | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_REAR_LEFT) \ | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER) \ | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_SIDE_LEFT) \ | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_FRONT_LEFT) \ | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_REAR_LEFT)) \ #define PA_CHANNEL_POSITION_MASK_RIGHT \ (PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_FRONT_RIGHT) \ | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_REAR_RIGHT) \ | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER) \ | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_SIDE_RIGHT) \ | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_FRONT_RIGHT) \ | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_REAR_RIGHT)) #define PA_CHANNEL_POSITION_MASK_CENTER \ (PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_FRONT_CENTER) \ | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_REAR_CENTER) \ | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_CENTER) \ | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_FRONT_CENTER) \ | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_REAR_CENTER)) #define PA_CHANNEL_POSITION_MASK_FRONT \ (PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_FRONT_LEFT) \ | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_FRONT_RIGHT) \ | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_FRONT_CENTER) \ | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER) \ | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER) \ | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_FRONT_LEFT) \ | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_FRONT_RIGHT) \ | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_FRONT_CENTER)) #define PA_CHANNEL_POSITION_MASK_REAR \ (PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_REAR_LEFT) \ | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_REAR_RIGHT) \ | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_REAR_CENTER) \ | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_REAR_LEFT) \ | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_REAR_RIGHT) \ | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_REAR_CENTER)) #define PA_CHANNEL_POSITION_MASK_LFE \ PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_LFE) #define PA_CHANNEL_POSITION_MASK_HFE \ (PA_CHANNEL_POSITION_MASK_REAR | PA_CHANNEL_POSITION_MASK_FRONT \ | PA_CHANNEL_POSITION_MASK_LEFT | PA_CHANNEL_POSITION_MASK_RIGHT \ | PA_CHANNEL_POSITION_MASK_CENTER) #define PA_CHANNEL_POSITION_MASK_SIDE_OR_TOP_CENTER \ (PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_SIDE_LEFT) \ | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_SIDE_RIGHT) \ | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_CENTER)) #define PA_CHANNEL_POSITION_MASK_TOP \ (PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_CENTER) \ | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_FRONT_LEFT) \ | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_FRONT_RIGHT) \ | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_FRONT_CENTER) \ | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_REAR_LEFT) \ | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_REAR_RIGHT) \ | PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_TOP_REAR_CENTER)) #define PA_CHANNEL_POSITION_MASK_ALL \ ((pa_channel_position_mask_t) (PA_CHANNEL_POSITION_MASK(PA_CHANNEL_POSITION_MAX)-1)) static const char *const pa_position_table[PA_CHANNEL_POSITION_MAX] = { [PA_CHANNEL_POSITION_MONO] = "mono", [PA_CHANNEL_POSITION_FRONT_CENTER] = "front-center", [PA_CHANNEL_POSITION_FRONT_LEFT] = "front-left", [PA_CHANNEL_POSITION_FRONT_RIGHT] = "front-right", [PA_CHANNEL_POSITION_REAR_CENTER] = "rear-center", [PA_CHANNEL_POSITION_REAR_LEFT] = "rear-left", [PA_CHANNEL_POSITION_REAR_RIGHT] = "rear-right", [PA_CHANNEL_POSITION_LFE] = "lfe", [PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER] = "front-left-of-center", [PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER] = "front-right-of-center", [PA_CHANNEL_POSITION_SIDE_LEFT] = "side-left", [PA_CHANNEL_POSITION_SIDE_RIGHT] = "side-right", [PA_CHANNEL_POSITION_AUX0] = "aux0", [PA_CHANNEL_POSITION_AUX1] = "aux1", [PA_CHANNEL_POSITION_AUX2] = "aux2", [PA_CHANNEL_POSITION_AUX3] = "aux3", [PA_CHANNEL_POSITION_AUX4] = "aux4", [PA_CHANNEL_POSITION_AUX5] = "aux5", [PA_CHANNEL_POSITION_AUX6] = "aux6", [PA_CHANNEL_POSITION_AUX7] = "aux7", [PA_CHANNEL_POSITION_AUX8] = "aux8", [PA_CHANNEL_POSITION_AUX9] = "aux9", [PA_CHANNEL_POSITION_AUX10] = "aux10", [PA_CHANNEL_POSITION_AUX11] = "aux11", [PA_CHANNEL_POSITION_AUX12] = "aux12", [PA_CHANNEL_POSITION_AUX13] = "aux13", [PA_CHANNEL_POSITION_AUX14] = "aux14", [PA_CHANNEL_POSITION_AUX15] = "aux15", [PA_CHANNEL_POSITION_AUX16] = "aux16", [PA_CHANNEL_POSITION_AUX17] = "aux17", [PA_CHANNEL_POSITION_AUX18] = "aux18", [PA_CHANNEL_POSITION_AUX19] = "aux19", [PA_CHANNEL_POSITION_AUX20] = "aux20", [PA_CHANNEL_POSITION_AUX21] = "aux21", [PA_CHANNEL_POSITION_AUX22] = "aux22", [PA_CHANNEL_POSITION_AUX23] = "aux23", [PA_CHANNEL_POSITION_AUX24] = "aux24", [PA_CHANNEL_POSITION_AUX25] = "aux25", [PA_CHANNEL_POSITION_AUX26] = "aux26", [PA_CHANNEL_POSITION_AUX27] = "aux27", [PA_CHANNEL_POSITION_AUX28] = "aux28", [PA_CHANNEL_POSITION_AUX29] = "aux29", [PA_CHANNEL_POSITION_AUX30] = "aux30", [PA_CHANNEL_POSITION_AUX31] = "aux31", [PA_CHANNEL_POSITION_TOP_CENTER] = "top-center", [PA_CHANNEL_POSITION_TOP_FRONT_CENTER] = "top-front-center", [PA_CHANNEL_POSITION_TOP_FRONT_LEFT] = "top-front-left", [PA_CHANNEL_POSITION_TOP_FRONT_RIGHT] = "top-front-right", [PA_CHANNEL_POSITION_TOP_REAR_CENTER] = "top-rear-center", [PA_CHANNEL_POSITION_TOP_REAR_LEFT] = "top-rear-left", [PA_CHANNEL_POSITION_TOP_REAR_RIGHT] = "top-rear-right" }; static inline pa_channel_position_t pa_channel_position_from_string(const char *p) { pa_channel_position_t i; /* Some special aliases */ if (pa_streq(p, "left")) return PA_CHANNEL_POSITION_LEFT; else if (pa_streq(p, "right")) return PA_CHANNEL_POSITION_RIGHT; else if (pa_streq(p, "center")) return PA_CHANNEL_POSITION_CENTER; else if (pa_streq(p, "subwoofer")) return PA_CHANNEL_POSITION_SUBWOOFER; for (i = 0; i < PA_CHANNEL_POSITION_MAX; i++) if (pa_streq(p, pa_position_table[i])) return i; return PA_CHANNEL_POSITION_INVALID; } static inline pa_channel_map *pa_channel_map_parse(pa_channel_map *rmap, const char *s) { const char *state; pa_channel_map map; char *p; pa_channel_map_init(&map); if (pa_streq(s, "stereo")) { map.channels = 2; map.map[0] = PA_CHANNEL_POSITION_LEFT; map.map[1] = PA_CHANNEL_POSITION_RIGHT; goto finish; } else if (pa_streq(s, "surround-21")) { map.channels = 3; map.map[0] = PA_CHANNEL_POSITION_FRONT_LEFT; map.map[1] = PA_CHANNEL_POSITION_FRONT_RIGHT; map.map[2] = PA_CHANNEL_POSITION_LFE; goto finish; } else if (pa_streq(s, "surround-40")) { map.channels = 4; map.map[0] = PA_CHANNEL_POSITION_FRONT_LEFT; map.map[1] = PA_CHANNEL_POSITION_FRONT_RIGHT; map.map[2] = PA_CHANNEL_POSITION_REAR_LEFT; map.map[3] = PA_CHANNEL_POSITION_REAR_RIGHT; goto finish; } else if (pa_streq(s, "surround-41")) { map.channels = 5; map.map[0] = PA_CHANNEL_POSITION_FRONT_LEFT; map.map[1] = PA_CHANNEL_POSITION_FRONT_RIGHT; map.map[2] = PA_CHANNEL_POSITION_REAR_LEFT; map.map[3] = PA_CHANNEL_POSITION_REAR_RIGHT; map.map[4] = PA_CHANNEL_POSITION_LFE; goto finish; } else if (pa_streq(s, "surround-50")) { map.channels = 5; map.map[0] = PA_CHANNEL_POSITION_FRONT_LEFT; map.map[1] = PA_CHANNEL_POSITION_FRONT_RIGHT; map.map[2] = PA_CHANNEL_POSITION_REAR_LEFT; map.map[3] = PA_CHANNEL_POSITION_REAR_RIGHT; map.map[4] = PA_CHANNEL_POSITION_FRONT_CENTER; goto finish; } else if (pa_streq(s, "surround-51")) { map.channels = 6; map.map[0] = PA_CHANNEL_POSITION_FRONT_LEFT; map.map[1] = PA_CHANNEL_POSITION_FRONT_RIGHT; map.map[2] = PA_CHANNEL_POSITION_REAR_LEFT; map.map[3] = PA_CHANNEL_POSITION_REAR_RIGHT; map.map[4] = PA_CHANNEL_POSITION_FRONT_CENTER; map.map[5] = PA_CHANNEL_POSITION_LFE; goto finish; } else if (pa_streq(s, "surround-71")) { map.channels = 8; map.map[0] = PA_CHANNEL_POSITION_FRONT_LEFT; map.map[1] = PA_CHANNEL_POSITION_FRONT_RIGHT; map.map[2] = PA_CHANNEL_POSITION_REAR_LEFT; map.map[3] = PA_CHANNEL_POSITION_REAR_RIGHT; map.map[4] = PA_CHANNEL_POSITION_FRONT_CENTER; map.map[5] = PA_CHANNEL_POSITION_LFE; map.map[6] = PA_CHANNEL_POSITION_SIDE_LEFT; map.map[7] = PA_CHANNEL_POSITION_SIDE_RIGHT; goto finish; } state = NULL; map.channels = 0; while ((p = pa_split(s, ",", &state))) { pa_channel_position_t f; if (map.channels >= PA_CHANNELS_MAX) { pa_xfree(p); return NULL; } if ((f = pa_channel_position_from_string(p)) == PA_CHANNEL_POSITION_INVALID) { pa_xfree(p); return NULL; } map.map[map.channels++] = f; pa_xfree(p); } finish: if (!pa_channel_map_valid(&map)) return NULL; *rmap = map; return rmap; } static inline const char* pa_channel_position_to_string(pa_channel_position_t pos) { if (pos < 0 || pos >= PA_CHANNEL_POSITION_MAX) return NULL; return pa_position_table[pos]; } static inline int pa_channel_map_equal(const pa_channel_map *a, const pa_channel_map *b) { unsigned c; if (PA_UNLIKELY(a == b)) return 1; if (a->channels != b->channels) return 0; for (c = 0; c < a->channels; c++) if (a->map[c] != b->map[c]) return 0; return 1; } static inline char* pa_channel_map_snprint(char *s, size_t l, const pa_channel_map *map) { unsigned channel; char *e; if (!pa_channel_map_valid(map)) { pa_snprintf(s, l, "%s", _("(invalid)")); return s; } *(e = s) = 0; for (channel = 0; channel < map->channels && l > 1; channel++) { l -= pa_snprintf(e, l, "%s%s", channel == 0 ? "" : ",", pa_channel_position_to_string(map->map[channel])); e = strchr(e, 0); } return s; } #ifdef __cplusplus } #endif #endif /* PULSE_CHANNELMAP_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/acp/compat.c000066400000000000000000000201271511204443500260560ustar00rootroot00000000000000/*** This file is part of PulseAudio. Copyright 2004-2009 Lennart Poettering Copyright 2006 Pierre Ossman for Cendio AB PulseAudio is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. PulseAudio is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with PulseAudio; if not, see . ***/ #include "config.h" #include #include #include "compat.h" #include "device-port.h" #include "alsa-mixer.h" static const char *port_types[] = { [PA_DEVICE_PORT_TYPE_UNKNOWN] = "unknown", [PA_DEVICE_PORT_TYPE_AUX] = "aux", [PA_DEVICE_PORT_TYPE_SPEAKER] = "speaker", [PA_DEVICE_PORT_TYPE_HEADPHONES] = "headphones", [PA_DEVICE_PORT_TYPE_LINE] = "line", [PA_DEVICE_PORT_TYPE_MIC] = "mic", [PA_DEVICE_PORT_TYPE_HEADSET] = "headset", [PA_DEVICE_PORT_TYPE_HANDSET] = "handset", [PA_DEVICE_PORT_TYPE_EARPIECE] = "earpiece", [PA_DEVICE_PORT_TYPE_SPDIF] = "spdif", [PA_DEVICE_PORT_TYPE_HDMI] = "hdmi", [PA_DEVICE_PORT_TYPE_TV] = "tv", [PA_DEVICE_PORT_TYPE_RADIO] = "radio", [PA_DEVICE_PORT_TYPE_VIDEO] = "video", [PA_DEVICE_PORT_TYPE_USB] = "usb", [PA_DEVICE_PORT_TYPE_BLUETOOTH] = "bluetooth", [PA_DEVICE_PORT_TYPE_PORTABLE] = "portable", [PA_DEVICE_PORT_TYPE_HANDSFREE] = "handsfree", [PA_DEVICE_PORT_TYPE_CAR] = "car", [PA_DEVICE_PORT_TYPE_HIFI] = "hifi", [PA_DEVICE_PORT_TYPE_PHONE] = "phone", [PA_DEVICE_PORT_TYPE_NETWORK] = "network", [PA_DEVICE_PORT_TYPE_ANALOG] = "analog", }; static const char *str_port_type(pa_device_port_type_t type) { int idx = (type < PA_ELEMENTSOF(port_types)) ? type : 0; return port_types[idx]; } pa_device_port_new_data *pa_device_port_new_data_init(pa_device_port_new_data *data) { pa_assert(data); pa_zero(*data); data->type = PA_DEVICE_PORT_TYPE_UNKNOWN; data->available = PA_AVAILABLE_UNKNOWN; return data; } void pa_device_port_new_data_set_name(pa_device_port_new_data *data, const char *name) { pa_assert(data); pa_xfree(data->name); data->name = pa_xstrdup(name); } void pa_device_port_new_data_set_description(pa_device_port_new_data *data, const char *description) { pa_assert(data); pa_xfree(data->description); data->description = pa_xstrdup(description); } void pa_device_port_new_data_set_available(pa_device_port_new_data *data, pa_available_t available) { pa_assert(data); data->available = available; } void pa_device_port_new_data_set_availability_group(pa_device_port_new_data *data, const char *group) { pa_assert(data); pa_xfree(data->availability_group); data->availability_group = pa_xstrdup(group); } void pa_device_port_new_data_set_direction(pa_device_port_new_data *data, pa_direction_t direction) { pa_assert(data); data->direction = direction; } void pa_device_port_new_data_set_type(pa_device_port_new_data *data, pa_device_port_type_t type) { pa_assert(data); data->type = type; } void pa_device_port_new_data_done(pa_device_port_new_data *data) { pa_assert(data); pa_xfree(data->name); pa_xfree(data->description); pa_xfree(data->availability_group); } pa_device_port *pa_device_port_new(pa_core *c, pa_device_port_new_data *data, size_t extra) { pa_device_port *p; pa_assert(data); pa_assert(data->name); pa_assert(data->description); pa_assert(data->direction == PA_DIRECTION_OUTPUT || data->direction == PA_DIRECTION_INPUT); p = calloc(1, sizeof(pa_device_port) + extra); p->port.name = p->name = data->name; data->name = NULL; p->port.description = p->description = data->description; data->description = NULL; p->priority = p->port.priority = 0; p->available = data->available; p->port.available = (enum acp_available) data->available; p->availability_group = data->availability_group; data->availability_group = NULL; p->profiles = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); p->direction = data->direction; p->port.direction = data->direction == PA_DIRECTION_OUTPUT ? ACP_DIRECTION_PLAYBACK : ACP_DIRECTION_CAPTURE; p->type = data->type; p->proplist = pa_proplist_new(); pa_proplist_sets(p->proplist, ACP_KEY_PORT_TYPE, str_port_type(data->type)); if (p->availability_group) pa_proplist_sets(p->proplist, ACP_KEY_PORT_AVAILABILITY_GROUP, p->availability_group); p->user_data = (void*)((uint8_t*)p + sizeof(pa_device_port)); return p; } void pa_device_port_free(pa_device_port *port) { pa_xfree(port->name); pa_xfree(port->description); pa_xfree(port->availability_group); pa_hashmap_free(port->profiles); pa_proplist_free(port->proplist); if (port->impl_free) port->impl_free (port); free(port); } void pa_device_port_set_available(pa_device_port *p, pa_available_t status) { pa_available_t old = p->available; if (old == status) return; p->available = status; p->port.available = (enum acp_available) status; if (p->card && p->card->events && p->card->events->port_available) p->card->events->port_available(p->card->user_data, p->port.index, (enum acp_available)old, p->port.available); } bool pa_alsa_device_init_description(pa_proplist *p, pa_card *card) { const char *s, *d = NULL, *k; pa_assert(p); if (pa_proplist_contains(p, PA_PROP_DEVICE_DESCRIPTION)) return true; if (card) if ((s = pa_proplist_gets(card->proplist, PA_PROP_DEVICE_DESCRIPTION))) d = s; if (!d) if ((s = pa_proplist_gets(p, PA_PROP_DEVICE_FORM_FACTOR))) if (pa_streq(s, "internal")) d = _("Built-in Audio"); if (!d) if ((s = pa_proplist_gets(p, PA_PROP_DEVICE_CLASS))) if (pa_streq(s, "modem")) d = _("Modem"); if (!d) d = pa_proplist_gets(p, PA_PROP_DEVICE_PRODUCT_NAME); if (!d) return false; k = pa_proplist_gets(p, PA_PROP_DEVICE_PROFILE_DESCRIPTION); if (d && k) pa_proplist_setf(p, PA_PROP_DEVICE_DESCRIPTION, "%s %s", d, k); else if (d) pa_proplist_sets(p, PA_PROP_DEVICE_DESCRIPTION, d); return true; } static char *try_path(const char *fname, const char *path) { char *result = pa_maybe_prefix_path(fname, path); pa_log_trace("Check for file: %s", result); if (access(result, R_OK) == 0) return result; pa_xfree(result); return NULL; } static char *get_xdg_home(const char *key, const char *fallback) { const char *e; e = getenv(key); if (e && *e) { return strdup(e); } else { e = getenv("HOME"); if (!(e && *e)) e = getenv("USERPROFILE"); if (e && *e) return spa_aprintf("%s/%s", e, fallback); } return NULL; } char *get_data_path(const char *data_dir, const char *data_type, const char *fname) { static const char * const subpaths[] = { "alsa-card-profile/mixer", "alsa-card-profile", }; const char *e; spa_autofree char *base = NULL; char *result; if (data_dir) if ((result = try_path(fname, data_dir)) != NULL) return result; e = getenv("ACP_PATHS_DIR"); if (e && *e && spa_streq(data_type, "paths")) if ((result = try_path(fname, e)) != NULL) return result; e = getenv("ACP_PROFILES_DIR"); if (e && *e && spa_streq(data_type, "profile-sets")) if ((result = try_path(fname, e)) != NULL) return result; base = get_xdg_home("XDG_CONFIG_HOME", ".config"); if (base) { SPA_FOR_EACH_ELEMENT_VAR(subpaths, subpath) { spa_autofree char *path = spa_aprintf("%s/%s/%s", base, *subpath, data_type); if ((result = try_path(fname, path)) != NULL) return result; } } SPA_FOR_EACH_ELEMENT_VAR(subpaths, subpath) { spa_autofree char *path = spa_aprintf("/etc/%s/%s", *subpath, data_type); if ((result = try_path(fname, path)) != NULL) return result; } spa_autofree char *path = spa_aprintf("%s/%s", PA_ALSA_DATA_DIR, data_type); return pa_maybe_prefix_path(fname, path); } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/acp/compat.h000066400000000000000000000456551511204443500261000ustar00rootroot00000000000000/*** This file is part of PulseAudio. Copyright 2004-2006 Lennart Poettering Copyright 2006 Pierre Ossman for Cendio AB PulseAudio is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. PulseAudio is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with PulseAudio; if not, see . ***/ #ifndef PULSE_COMPAT_H #define PULSE_COMPAT_H #include #include #include #include #include #include #include #include #include #ifdef __cplusplus extern "C" { #else #include #endif typedef struct pa_core pa_core; typedef void *(*pa_copy_func_t)(const void *p); typedef void (*pa_free_cb_t)(void *p); #ifdef __GNUC__ #define PA_LIKELY(x) (__builtin_expect(!!(x),1)) #define PA_UNLIKELY(x) (__builtin_expect(!!(x),0)) #define PA_PRINTF_FUNC(fmt, arg1) __attribute__((format(printf, fmt, arg1))) #define PA_UNUSED __attribute__ ((unused)) #else #define PA_LIKELY(x) (x) #define PA_UNLIKELY(x) (x) #define PA_PRINTF_FUNC(fmt, arg1) #define PA_UNUSED #endif #define PA_MIN(a,b) \ ({ \ __typeof__(a) _a = (a); \ __typeof__(b) _b = (b); \ PA_LIKELY(_a < _b) ? _a : _b; \ }) #define PA_MAX(a,b) \ ({ \ __typeof__(a) _a = (a); \ __typeof__(b) _b = (b); \ PA_LIKELY(_a > _b) ? _a : _b; \ }) #define PA_CLAMP_UNLIKELY(v,low,high) \ ({ \ __typeof__(v) _v = (v); \ __typeof__(low) _low = (low); \ __typeof__(high) _high = (high); \ PA_MIN(PA_MAX(_v, _low), _high); \ }) #define PA_PTR_TO_UINT(p) ((unsigned int) ((uintptr_t) (p))) #define PA_UINT_TO_PTR(u) ((void*) ((uintptr_t) (u))) #include "array.h" #include "llist.h" #include "hashmap.h" #include "dynarray.h" #include "idxset.h" #include "proplist.h" typedef enum pa_direction { PA_DIRECTION_OUTPUT = 0x0001U, /**< Output direction */ PA_DIRECTION_INPUT = 0x0002U /**< Input direction */ } pa_direction_t; /* This enum replaces pa_port_available_t (defined in pulse/def.h) for * internal use, so make sure both enum types stay in sync. */ typedef enum pa_available { PA_AVAILABLE_UNKNOWN = 0, PA_AVAILABLE_NO = 1, PA_AVAILABLE_YES = 2, } pa_available_t; #define PA_RATE_MAX (48000U*16U) typedef enum pa_sample_format { PA_SAMPLE_U8, /**< Unsigned 8 Bit PCM */ PA_SAMPLE_ALAW, /**< 8 Bit a-Law */ PA_SAMPLE_ULAW, /**< 8 Bit mu-Law */ PA_SAMPLE_S16LE, /**< Signed 16 Bit PCM, little endian (PC) */ PA_SAMPLE_S16BE, /**< Signed 16 Bit PCM, big endian */ PA_SAMPLE_FLOAT32LE, /**< 32 Bit IEEE floating point, little endian (PC), range -1.0 to 1.0 */ PA_SAMPLE_FLOAT32BE, /**< 32 Bit IEEE floating point, big endian, range -1.0 to 1.0 */ PA_SAMPLE_S32LE, /**< Signed 32 Bit PCM, little endian (PC) */ PA_SAMPLE_S32BE, /**< Signed 32 Bit PCM, big endian */ PA_SAMPLE_S24LE, /**< Signed 24 Bit PCM packed, little endian (PC). \since 0.9.15 */ PA_SAMPLE_S24BE, /**< Signed 24 Bit PCM packed, big endian. \since 0.9.15 */ PA_SAMPLE_S24_32LE, /**< Signed 24 Bit PCM in LSB of 32 Bit words, little endian (PC). \since 0.9.15 */ PA_SAMPLE_S24_32BE, /**< Signed 24 Bit PCM in LSB of 32 Bit words, big endian. \since 0.9.15 */ PA_SAMPLE_MAX, /**< Upper limit of valid sample types */ PA_SAMPLE_INVALID = -1 /**< An invalid value */ } pa_sample_format_t; static inline int pa_sample_format_valid(unsigned format) { return format < PA_SAMPLE_MAX; } #ifdef WORDS_BIGENDIAN #define PA_SAMPLE_S16NE PA_SAMPLE_S16BE #define PA_SAMPLE_FLOAT32NE PA_SAMPLE_FLOAT32BE #define PA_SAMPLE_S32NE PA_SAMPLE_S32BE #define PA_SAMPLE_S24NE PA_SAMPLE_S24BE #define PA_SAMPLE_S24_32NE PA_SAMPLE_S24_32BE #define PA_SAMPLE_S16RE PA_SAMPLE_S16LE #define PA_SAMPLE_FLOAT32RE PA_SAMPLE_FLOAT32LE #define PA_SAMPLE_S32RE PA_SAMPLE_S32LE #define PA_SAMPLE_S24RE PA_SAMPLE_S24LE #define PA_SAMPLE_S24_32RE PA_SAMPLE_S24_32LE #else #define PA_SAMPLE_S16NE PA_SAMPLE_S16LE #define PA_SAMPLE_FLOAT32NE PA_SAMPLE_FLOAT32LE #define PA_SAMPLE_S32NE PA_SAMPLE_S32LE #define PA_SAMPLE_S24NE PA_SAMPLE_S24LE #define PA_SAMPLE_S24_32NE PA_SAMPLE_S24_32LE #define PA_SAMPLE_S16RE PA_SAMPLE_S16BE #define PA_SAMPLE_FLOAT32RE PA_SAMPLE_FLOAT32BE #define PA_SAMPLE_S32RE PA_SAMPLE_S32BE #define PA_SAMPLE_S24RE PA_SAMPLE_S24BE #define PA_SAMPLE_S24_32RE PA_SAMPLE_S24_32BE #endif static const size_t pa_sample_size_table[] = { [PA_SAMPLE_U8] = 1, [PA_SAMPLE_ULAW] = 1, [PA_SAMPLE_ALAW] = 1, [PA_SAMPLE_S16LE] = 2, [PA_SAMPLE_S16BE] = 2, [PA_SAMPLE_FLOAT32LE] = 4, [PA_SAMPLE_FLOAT32BE] = 4, [PA_SAMPLE_S32LE] = 4, [PA_SAMPLE_S32BE] = 4, [PA_SAMPLE_S24LE] = 3, [PA_SAMPLE_S24BE] = 3, [PA_SAMPLE_S24_32LE] = 4, [PA_SAMPLE_S24_32BE] = 4 }; static inline const char *pa_sample_format_to_string(pa_sample_format_t f) { static const char* const table[]= { [PA_SAMPLE_U8] = "u8", [PA_SAMPLE_ALAW] = "aLaw", [PA_SAMPLE_ULAW] = "uLaw", [PA_SAMPLE_S16LE] = "s16le", [PA_SAMPLE_S16BE] = "s16be", [PA_SAMPLE_FLOAT32LE] = "float32le", [PA_SAMPLE_FLOAT32BE] = "float32be", [PA_SAMPLE_S32LE] = "s32le", [PA_SAMPLE_S32BE] = "s32be", [PA_SAMPLE_S24LE] = "s24le", [PA_SAMPLE_S24BE] = "s24be", [PA_SAMPLE_S24_32LE] = "s24-32le", [PA_SAMPLE_S24_32BE] = "s24-32be", }; if (!pa_sample_format_valid(f)) return NULL; return table[f]; } typedef struct pa_sample_spec { pa_sample_format_t format; uint32_t rate; uint8_t channels; } pa_sample_spec; typedef uint64_t pa_usec_t; #define PA_MSEC_PER_SEC ((pa_usec_t) 1000ULL) #define PA_USEC_PER_SEC ((pa_usec_t) 1000000ULL) #define PA_USEC_PER_MSEC ((pa_usec_t) 1000ULL) static inline size_t pa_usec_to_bytes(pa_usec_t t, const pa_sample_spec *spec) { return (size_t) (((t * spec->rate) / PA_USEC_PER_SEC)) * (pa_sample_size_table[spec->format] * spec->channels); } static inline int pa_sample_rate_valid(uint32_t rate) { return rate > 0 && rate <= PA_RATE_MAX * 101 / 100; } static inline size_t pa_frame_size(const pa_sample_spec *spec) { return pa_sample_size_table[spec->format] * spec->channels; } typedef enum pa_log_level { PA_LOG_ERROR = 0, /* Error messages */ PA_LOG_WARN = 1, /* Warning messages */ PA_LOG_NOTICE = 2, /* Notice messages */ PA_LOG_INFO = 3, /* Info messages */ PA_LOG_DEBUG = 4, /* Debug messages */ PA_LOG_TRACE = 5, PA_LOG_LEVEL_MAX } pa_log_level_t; extern int _acp_log_level; extern acp_log_func _acp_log_func; extern void * _acp_log_data; #define pa_log_level_enabled(lev) (_acp_log_level >= (int)(lev)) #define pa_log_levelv_meta(lev,f,l,func,fmt,ap) \ ({ \ if (pa_log_level_enabled (lev) && _acp_log_func) \ _acp_log_func(_acp_log_data,lev,f,l,func,fmt,ap); \ }) static inline PA_PRINTF_FUNC(5, 6) void pa_log_level_meta(enum pa_log_level level, const char *file, int line, const char *func, const char *fmt, ...) { va_list args; va_start(args,fmt); pa_log_levelv_meta(level,file,line,func,fmt,args); va_end(args); } #define pa_logl(lev,fmt,...) pa_log_level_meta(lev,__FILE__, __LINE__, __func__, fmt, ##__VA_ARGS__) #define pa_log_error(fmt,...) pa_logl(PA_LOG_ERROR, fmt, ##__VA_ARGS__) #define pa_log_warn(fmt,...) pa_logl(PA_LOG_WARN, fmt, ##__VA_ARGS__) #define pa_log_notice(fmt,...) pa_logl(PA_LOG_NOTICE, fmt, ##__VA_ARGS__) #define pa_log_info(fmt,...) pa_logl(PA_LOG_INFO, fmt, ##__VA_ARGS__) #define pa_log_debug(fmt,...) pa_logl(PA_LOG_DEBUG, fmt, ##__VA_ARGS__) #define pa_log_trace(fmt,...) pa_logl(PA_LOG_TRACE, fmt, ##__VA_ARGS__) #define pa_log pa_log_error #define pa_assert_se(expr) \ do { \ if (PA_UNLIKELY(!(expr))) { \ fprintf(stderr, "'%s' failed at %s:%u %s()\n", \ #expr , __FILE__, __LINE__, __func__); \ abort(); \ } \ } while (false) #define pa_assert(expr) \ do { \ if (PA_UNLIKELY(!(expr))) { \ fprintf(stderr, "'%s' failed at %s:%u %s()\n", \ #expr , __FILE__, __LINE__, __func__); \ abort(); \ } \ } while (false) #define pa_assert_not_reached() \ do { \ fprintf(stderr, "Code should not be reached at %s:%u %s()\n", \ __FILE__, __LINE__, __func__); \ abort(); \ } while (false) #define pa_memzero(x,l) (memset((x), 0, (l))) #define pa_zero(x) (pa_memzero(&(x), sizeof(x))) #define PA_ELEMENTSOF(x) (sizeof(x)/sizeof((x)[0])) #define pa_streq(a,b) (!strcmp((a),(b))) #define pa_strneq(a,b,n) (!strncmp((a),(b),(n))) #define pa_strnull(s) ((s) ? (s) : "null") #define pa_startswith(s,pfx) (strstr(s, pfx) == s) PA_PRINTF_FUNC(3, 0) static inline size_t pa_vsnprintf(char *str, size_t size, const char *format, va_list ap) { int ret; pa_assert(str); pa_assert(size > 0); pa_assert(format); ret = vsnprintf(str, size, format, ap); str[size-1] = 0; if (ret < 0) return strlen(str); if ((size_t) ret > size-1) return size-1; return (size_t) ret; } PA_PRINTF_FUNC(3, 4) static inline size_t pa_snprintf(char *str, size_t size, const char *format, ...) { size_t ret; va_list ap; pa_assert(str); pa_assert(size > 0); pa_assert(format); va_start(ap, format); ret = pa_vsnprintf(str, size, format, ap); va_end(ap); return ret; } #define pa_xnullcheck(p) ({ void *_mem_alloc = (p); spa_assert_se(_mem_alloc); _mem_alloc; }) #define pa_xstrdup(s) ((s) != NULL ? pa_xnullcheck(strdup(s)) : NULL) #define pa_xstrndup(s,n) ((s) != NULL ? pa_xnullcheck(strndup(s,n)) : NULL) #define pa_xfree free #define pa_xmalloc(n) pa_xnullcheck(malloc(n)) #define pa_xnew0(t,n) pa_xnullcheck(calloc((n), sizeof(t))) #define pa_xnew(t,n) pa_xnew0(t,n) #define pa_xrenew(t,p,n) ((t*) pa_xnullcheck(realloc(p, (n)*sizeof(t)))) static inline void* pa_xmemdup(const void *p, size_t l) { if (!p) { return NULL; } else { void *dst = pa_xmalloc(l); memcpy(dst, p, l); return dst; } } #define pa_xnewdup(t,p,n) ((t*) pa_xmemdup((p), (n)*sizeof(t))) static inline void pa_xfreev(void**a) { int i; for (i = 0; a && a[i]; i++) free(a[i]); free(a); } static inline void pa_xstrfreev(char **a) { pa_xfreev((void**)a); } typedef struct { size_t size; char *ptr; FILE *f; } pa_strbuf; static inline pa_strbuf *pa_strbuf_new(void) { pa_strbuf *s = pa_xnew0(pa_strbuf,1); s->f = open_memstream(&s->ptr, &s->size); return s; } static PA_PRINTF_FUNC(2,3) inline size_t pa_strbuf_printf(pa_strbuf *sb, const char *format, ...) { int ret; va_list args; va_start(args, format); ret = vfprintf(sb->f, format, args); va_end(args); return ret > 0 ? ret : 0; } static inline void pa_strbuf_puts(pa_strbuf *sb, const char *t) { fputs(t, sb->f); } static inline bool pa_strbuf_isempty(pa_strbuf *sb) { fflush(sb->f); return sb->size == 0; } static inline char *pa_strbuf_to_string_free(pa_strbuf *sb) { char *ptr; fclose(sb->f); ptr = sb->ptr; free(sb); return ptr; } #define pa_cstrerror strerror #define PA_PATH_SEP "/" #define PA_PATH_SEP_CHAR '/' #define PA_WHITESPACE "\n\r \t" static PA_PRINTF_FUNC(1,2) inline char *pa_sprintf_malloc(const char *fmt, ...) { char *res; va_list args; va_start(args, fmt); if (vasprintf(&res, fmt, args) < 0) res = NULL; va_end(args); return res; } static PA_PRINTF_FUNC(1,0) inline char *pa_vsprintf_malloc(const char *fmt, va_list args) { char *res; if (vasprintf(&res, fmt, args) < 0) res = NULL; return res; } #define pa_fopen_cloexec(f,m) fopen(f,m"e") static inline char *pa_path_get_filename(const char *p) { char *fn; if (!p) return NULL; if ((fn = strrchr(p, PA_PATH_SEP_CHAR))) return fn+1; return (char*) p; } static inline bool pa_is_path_absolute(const char *fn) { return *fn == PA_PATH_SEP_CHAR; } static inline char* pa_maybe_prefix_path(const char *path, const char *prefix) { if (pa_is_path_absolute(path)) return pa_xstrdup(path); return pa_sprintf_malloc("%s" PA_PATH_SEP "%s", prefix, path); } static inline bool pa_endswith(const char *s, const char *sfx) { size_t l1, l2; l1 = strlen(s); l2 = strlen(sfx); return l1 >= l2 && pa_streq(s + l1 - l2, sfx); } static inline char *pa_replace(const char*s, const char*a, const char *b) { struct pa_array res; size_t an, bn; an = strlen(a); bn = strlen(b); pa_array_init(&res, an); for (;;) { const char *p; if (!(p = strstr(s, a))) break; pa_array_add_data(&res, s, p-s); pa_array_add_data(&res, b, bn); s = p + an; } pa_array_add_data(&res, s, strlen(s) + 1); return res.data; } static inline char *pa_split(const char *c, const char *delimiter, const char**state) { const char *current = *state ? *state : c; size_t l; if (!*current) return NULL; l = strcspn(current, delimiter); *state = current+l; if (**state) (*state)++; return pa_xstrndup(current, l); } static inline char *pa_split_spaces(const char *c, const char **state) { const char *current = *state ? *state : c; size_t l; if (!*current || *c == 0) return NULL; current += strspn(current, PA_WHITESPACE); l = strcspn(current, PA_WHITESPACE); *state = current+l; return pa_xstrndup(current, l); } static inline char **pa_split_spaces_strv(const char *s) { char **t, *e; unsigned i = 0, n = 8; const char *state = NULL; t = pa_xnew(char*, n); while ((e = pa_split_spaces(s, &state))) { t[i++] = e; if (i >= n) { n *= 2; t = pa_xrenew(char*, t, n); } } if (i <= 0) { pa_xfree(t); return NULL; } t[i] = NULL; return t; } static inline char* pa_str_strip_suffix(const char *str, const char *suffix) { size_t str_l, suf_l, prefix; char *ret; str_l = strlen(str); suf_l = strlen(suffix); if (str_l < suf_l) return NULL; prefix = str_l - suf_l; if (!pa_streq(&str[prefix], suffix)) return NULL; ret = pa_xmalloc(prefix + 1); memcpy(ret, str, prefix); ret[prefix] = '\0'; return ret; } static inline const char *pa_split_in_place(const char *c, const char *delimiter, size_t *n, const char**state) { const char *current = *state ? *state : c; size_t l; if (!*current) return NULL; l = strcspn(current, delimiter); *state = current+l; if (**state) (*state)++; *n = l; return current; } static inline const char *pa_split_spaces_in_place(const char *c, size_t *n, const char **state) { const char *current = *state ? *state : c; size_t l; if (!*current || *c == 0) return NULL; current += strspn(current, PA_WHITESPACE); l = strcspn(current, PA_WHITESPACE); *state = current+l; *n = l; return current; } static inline bool pa_str_in_list_spaces(const char *haystack, const char *needle) { const char *s; size_t n; const char *state = NULL; if (!haystack || !needle) return false; while ((s = pa_split_spaces_in_place(haystack, &n, &state))) { if (pa_strneq(needle, s, n)) return true; } return false; } static inline char *pa_strip(char *s) { char *e, *l = NULL; s += strspn(s, PA_WHITESPACE); for (e = s; *e; e++) if (!strchr(PA_WHITESPACE, *e)) l = e; if (l) *(l+1) = 0; else *s = 0; return s; } static inline int pa_atod(const char *s, double *ret_d) { if (spa_atod(s, ret_d) && !isnan(*ret_d)) return 0; errno = EINVAL; return -1; } static inline int pa_atoi(const char *s, int32_t *ret_i) { if (spa_atoi32(s, ret_i, 0)) return 0; errno = EINVAL; return -1; } static inline int pa_atou(const char *s, uint32_t *ret_u) { if (spa_atou32(s, ret_u, 0)) return 0; errno = EINVAL; return -1; } static inline int pa_atol(const char *s, long *ret_l) { int64_t res; if (spa_atoi64(s, &res, 0)) { *ret_l = res; if (*ret_l == res) return 0; } errno = EINVAL; return -1; } static inline int pa_parse_boolean(const char *v) { if (pa_streq(v, "1") || !strcasecmp(v, "y") || !strcasecmp(v, "t") || !strcasecmp(v, "yes") || !strcasecmp(v, "true") || !strcasecmp(v, "on")) return 1; else if (pa_streq(v, "0") || !strcasecmp(v, "n") || !strcasecmp(v, "f") || !strcasecmp(v, "no") || !strcasecmp(v, "false") || !strcasecmp(v, "off")) return 0; errno = EINVAL; return -1; } static inline const char *pa_yes_no(bool b) { return b ? "yes" : "no"; } static inline const char *pa_strna(const char *x) { return x ? x : "n/a"; } static inline pa_sample_spec* pa_sample_spec_init(pa_sample_spec *spec) { spec->format = PA_SAMPLE_INVALID; spec->rate = 0; spec->channels = 0; return spec; } static inline char *pa_readlink(const char *p) { #ifdef HAVE_READLINK size_t l = 100; for (;;) { char *c; ssize_t n; c = pa_xmalloc(l); if (c == NULL) return NULL; if ((n = readlink(p, c, l-1)) < 0) { pa_xfree(c); return NULL; } if ((size_t) n < l-1) { c[n] = 0; return c; } pa_xfree(c); if (l >= (size_t)(INT_MAX / 2)) return NULL; l *= 2; } #else return NULL; #endif } char *get_data_path(const char *data_dir, const char *data_type, const char *fname); #include extern struct spa_i18n *acp_i18n; #define _(String) spa_i18n_text(acp_i18n, String) #ifdef gettext_noop #define N_(String) gettext_noop(String) #else #define N_(String) (String) #endif #include "channelmap.h" #include "volume.h" #ifdef __cplusplus } #endif #endif /* PULSE_COMPAT_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/acp/conf-parser.c000066400000000000000000000241751511204443500270210ustar00rootroot00000000000000/*** This file is part of PulseAudio. Copyright 2004-2006 Lennart Poettering PulseAudio is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. PulseAudio is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with PulseAudio; if not, see . ***/ #include "config.h" #include #include #include #include #include "compat.h" #include "conf-parser.h" #define WHITESPACE " \t\n" #define COMMENTS "#;\n" /* Run the user supplied parser for an assignment */ static int normal_assignment(pa_config_parser_state *state) { const pa_config_item *item; pa_assert(state); for (item = state->item_table; item->parse; item++) { if (item->lvalue && !pa_streq(state->lvalue, item->lvalue)) continue; if (item->section && !state->section) continue; if (item->section && !pa_streq(state->section, item->section)) continue; state->data = item->data; return item->parse(state); } pa_log("[%s:%u] Unknown lvalue '%s' in section '%s'.", state->filename, state->lineno, state->lvalue, pa_strna(state->section)); return -1; } /* Parse a proplist entry. */ static int proplist_assignment(pa_config_parser_state *state) { pa_assert(state); pa_assert(state->proplist); if (pa_proplist_sets(state->proplist, state->lvalue, state->rvalue) < 0) { pa_log("[%s:%u] Failed to parse a proplist entry: %s = %s", state->filename, state->lineno, state->lvalue, state->rvalue); return -1; } return 0; } /* Parse a variable assignment line */ static int parse_line(pa_config_parser_state *state) { char *c; state->lvalue = state->buf + strspn(state->buf, WHITESPACE); if ((c = strpbrk(state->lvalue, COMMENTS))) *c = 0; if (!*state->lvalue) return 0; if (pa_startswith(state->lvalue, ".include ")) { char *path = NULL, *fn; int r; fn = pa_strip(state->lvalue + 9); if (!pa_is_path_absolute(fn)) { const char *k; if ((k = strrchr(state->filename, '/'))) { char *dir = pa_xstrndup(state->filename, k - state->filename); fn = path = pa_sprintf_malloc("%s" PA_PATH_SEP "%s", dir, fn); pa_xfree(dir); } } r = pa_config_parse(fn, NULL, state->item_table, state->proplist, false, state->userdata); pa_xfree(path); return r; } if (*state->lvalue == '[') { size_t k; k = strlen(state->lvalue); pa_assert(k > 0); if (state->lvalue[k-1] != ']') { pa_log("[%s:%u] Invalid section header.", state->filename, state->lineno); return -1; } pa_xfree(state->section); state->section = pa_xstrndup(state->lvalue + 1, k-2); if (pa_streq(state->section, "Properties")) { if (!state->proplist) { pa_log("[%s:%u] \"Properties\" section is not allowed in this file.", state->filename, state->lineno); return -1; } state->in_proplist = true; } else state->in_proplist = false; return 0; } if (!(state->rvalue = strchr(state->lvalue, '='))) { pa_log("[%s:%u] Missing '='.", state->filename, state->lineno); return -1; } *state->rvalue = 0; state->rvalue++; state->lvalue = pa_strip(state->lvalue); state->rvalue = pa_strip(state->rvalue); if (state->in_proplist) return proplist_assignment(state); else return normal_assignment(state); } #ifndef OS_IS_WIN32 static int conf_filter(const struct dirent *entry) { return pa_endswith(entry->d_name, ".conf"); } #endif /* Go through the file and parse each line */ int pa_config_parse(const char *filename, FILE *f, const pa_config_item *t, pa_proplist *proplist, bool use_dot_d, void *userdata) { int r = -1; bool do_close = !f; pa_config_parser_state state; pa_assert(filename); pa_assert(t); pa_zero(state); if (!f && !(f = pa_fopen_cloexec(filename, "r"))) { if (errno == ENOENT) { pa_log_debug("Failed to open configuration file '%s': %s", filename, pa_cstrerror(errno)); r = 0; goto finish; } pa_log_warn("Failed to open configuration file '%s': %s", filename, pa_cstrerror(errno)); goto finish; } pa_log_debug("Parsing configuration file '%s'", filename); state.filename = filename; state.item_table = t; state.userdata = userdata; if (proplist) state.proplist = pa_proplist_new(); while (!feof(f)) { if (!fgets(state.buf, sizeof(state.buf), f)) { if (feof(f)) break; pa_log_warn("Failed to read configuration file '%s': %s", filename, pa_cstrerror(errno)); goto finish; } state.lineno++; if (parse_line(&state) < 0) goto finish; } if (proplist) pa_proplist_update(proplist, PA_UPDATE_REPLACE, state.proplist); r = 0; finish: if (state.proplist) pa_proplist_free(state.proplist); pa_xfree(state.section); if (do_close && f) fclose(f); if (use_dot_d) { #ifdef OS_IS_WIN32 char *dir_name = pa_sprintf_malloc("%s.d", filename); char *pattern = pa_sprintf_malloc("%s\\*.conf", dir_name); HANDLE fh; WIN32_FIND_DATA wfd; fh = FindFirstFile(pattern, &wfd); if (fh != INVALID_HANDLE_VALUE) { do { if (!(wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) { char *filename2 = pa_sprintf_malloc("%s\\%s", dir_name, wfd.cFileName); pa_config_parse(filename2, NULL, t, proplist, false, userdata); pa_xfree(filename2); } } while (FindNextFile(fh, &wfd)); FindClose(fh); } else { DWORD err = GetLastError(); if (err == ERROR_PATH_NOT_FOUND) { pa_log_debug("Pattern %s did not match any files, ignoring.", pattern); } else { LPVOID msgbuf; DWORD fret = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, err, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)&msgbuf, 0, NULL); if (fret != 0) { pa_log_warn("FindFirstFile(%s) failed with error %ld (%s), ignoring.", pattern, err, (char*)msgbuf); LocalFree(msgbuf); } else { pa_log_warn("FindFirstFile(%s) failed with error %ld, ignoring.", pattern, err); pa_log_warn("FormatMessage failed with error %ld", GetLastError()); } } } pa_xfree(pattern); pa_xfree(dir_name); #else char *dir_name; int n; struct dirent **entries = NULL; dir_name = pa_sprintf_malloc("%s.d", filename); n = scandir(dir_name, &entries, conf_filter, alphasort); if (n >= 0) { int i; for (i = 0; i < n; i++) { char *filename2; filename2 = pa_sprintf_malloc("%s" PA_PATH_SEP "%s", dir_name, entries[i]->d_name); pa_config_parse(filename2, NULL, t, proplist, false, userdata); pa_xfree(filename2); free(entries[i]); } free(entries); } else { if (errno == ENOENT) pa_log_debug("%s does not exist, ignoring.", dir_name); else pa_log_warn("scandir(\"%s\") failed: %s", dir_name, pa_cstrerror(errno)); } pa_xfree(dir_name); #endif } return r; } int pa_config_parse_int(pa_config_parser_state *state) { int *i; int32_t k; pa_assert(state); i = state->data; if (pa_atoi(state->rvalue, &k) < 0) { pa_log("[%s:%u] Failed to parse numeric value: %s", state->filename, state->lineno, state->rvalue); return -1; } *i = (int) k; return 0; } int pa_config_parse_unsigned(pa_config_parser_state *state) { unsigned *u; uint32_t k; pa_assert(state); u = state->data; if (pa_atou(state->rvalue, &k) < 0) { pa_log("[%s:%u] Failed to parse numeric value: %s", state->filename, state->lineno, state->rvalue); return -1; } *u = (unsigned) k; return 0; } int pa_config_parse_size(pa_config_parser_state *state) { size_t *i; uint32_t k; pa_assert(state); i = state->data; if (pa_atou(state->rvalue, &k) < 0) { pa_log("[%s:%u] Failed to parse numeric value: %s", state->filename, state->lineno, state->rvalue); return -1; } *i = (size_t) k; return 0; } int pa_config_parse_bool(pa_config_parser_state *state) { int k; bool *b; pa_assert(state); b = state->data; if ((k = pa_parse_boolean(state->rvalue)) < 0) { pa_log("[%s:%u] Failed to parse boolean value: %s", state->filename, state->lineno, state->rvalue); return -1; } *b = !!k; return 0; } int pa_config_parse_not_bool(pa_config_parser_state *state) { int k; bool *b; pa_assert(state); b = state->data; if ((k = pa_parse_boolean(state->rvalue)) < 0) { pa_log("[%s:%u] Failed to parse boolean value: %s", state->filename, state->lineno, state->rvalue); return -1; } *b = !k; return 0; } int pa_config_parse_string(pa_config_parser_state *state) { char **s; pa_assert(state); s = state->data; pa_xfree(*s); *s = *state->rvalue ? pa_xstrdup(state->rvalue) : NULL; return 0; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/acp/conf-parser.h000066400000000000000000000066001511204443500270170ustar00rootroot00000000000000#ifndef fooconfparserhfoo #define fooconfparserhfoo /*** This file is part of PulseAudio. Copyright 2004-2006 Lennart Poettering PulseAudio is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. PulseAudio is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with PulseAudio; if not, see . ***/ #include #include #include "compat.h" /* An abstract parser for simple, line based, shallow configuration * files consisting of variable assignments only. */ typedef struct pa_config_parser_state pa_config_parser_state; typedef int (*pa_config_parser_cb_t)(pa_config_parser_state *state); /* Wraps info for parsing a specific configuration variable */ typedef struct pa_config_item { const char *lvalue; /* name of the variable */ pa_config_parser_cb_t parse; /* Function that is called to parse the variable's value */ void *data; /* Where to store the variable's data */ const char *section; } pa_config_item; struct pa_config_parser_state { const char *filename; unsigned lineno; char *section; char *lvalue; char *rvalue; void *data; /* The data pointer of the current pa_config_item. */ void *userdata; /* The pointer that was given to pa_config_parse(). */ /* Private data to be used only by conf-parser.c. */ const pa_config_item *item_table; char buf[4096]; pa_proplist *proplist; bool in_proplist; }; /* The configuration file parsing routine. Expects a table of * pa_config_items in *t that is terminated by an item where lvalue is * NULL. * * If use_dot_d is true, then after parsing the file named by the filename * argument, the function will parse all files ending with ".conf" in * alphabetical order from a directory whose name is filename + ".d", if such * directory exists. * * Some configuration files may contain a Properties section, which * is a bit special. Normally all accepted lvalues must be predefined * in the pa_config_item table, but in the Properties section the * pa_config_item table is ignored, and all lvalues are accepted (as * long as they are valid proplist keys). If the proplist pointer is * non-NULL, the parser will parse any section named "Properties" as * properties, and those properties will be merged into the given * proplist. If proplist is NULL, then sections named "Properties" * are not allowed at all in the configuration file. */ int pa_config_parse(const char *filename, FILE *f, const pa_config_item *t, pa_proplist *proplist, bool use_dot_d, void *userdata); /* Generic parsers for integers, size_t, booleans and strings */ int pa_config_parse_int(pa_config_parser_state *state); int pa_config_parse_unsigned(pa_config_parser_state *state); int pa_config_parse_size(pa_config_parser_state *state); int pa_config_parse_bool(pa_config_parser_state *state); int pa_config_parse_not_bool(pa_config_parser_state *state); int pa_config_parse_string(pa_config_parser_state *state); #endif pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/acp/device-port.h000066400000000000000000000073201511204443500270210ustar00rootroot00000000000000/*** This file is part of PulseAudio. Copyright 2004-2006 Lennart Poettering Copyright 2006 Pierre Ossman for Cendio AB PulseAudio is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. PulseAudio is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with PulseAudio; if not, see . ***/ #ifndef PULSE_DEVICE_PORT_H #define PULSE_DEVICE_PORT_H #include "compat.h" #ifdef __cplusplus extern "C" { #else #include #endif typedef struct pa_card pa_card; typedef struct pa_device_port pa_device_port; /** Port type. \since 14.0 */ typedef enum pa_device_port_type { PA_DEVICE_PORT_TYPE_UNKNOWN = 0, PA_DEVICE_PORT_TYPE_AUX = 1, PA_DEVICE_PORT_TYPE_SPEAKER = 2, PA_DEVICE_PORT_TYPE_HEADPHONES = 3, PA_DEVICE_PORT_TYPE_LINE = 4, PA_DEVICE_PORT_TYPE_MIC = 5, PA_DEVICE_PORT_TYPE_HEADSET = 6, PA_DEVICE_PORT_TYPE_HANDSET = 7, PA_DEVICE_PORT_TYPE_EARPIECE = 8, PA_DEVICE_PORT_TYPE_SPDIF = 9, PA_DEVICE_PORT_TYPE_HDMI = 10, PA_DEVICE_PORT_TYPE_TV = 11, PA_DEVICE_PORT_TYPE_RADIO = 12, PA_DEVICE_PORT_TYPE_VIDEO = 13, PA_DEVICE_PORT_TYPE_USB = 14, PA_DEVICE_PORT_TYPE_BLUETOOTH = 15, PA_DEVICE_PORT_TYPE_PORTABLE = 16, PA_DEVICE_PORT_TYPE_HANDSFREE = 17, PA_DEVICE_PORT_TYPE_CAR = 18, PA_DEVICE_PORT_TYPE_HIFI = 19, PA_DEVICE_PORT_TYPE_PHONE = 20, PA_DEVICE_PORT_TYPE_NETWORK = 21, PA_DEVICE_PORT_TYPE_ANALOG = 22, } pa_device_port_type_t; struct pa_device_port { struct acp_port port; pa_card *card; char *name; char *description; char *preferred_profile; pa_device_port_type_t type; unsigned priority; pa_available_t available; /* PA_AVAILABLE_UNKNOWN, PA_AVAILABLE_NO or PA_AVAILABLE_YES */ char *availability_group; /* a string identifier which determine the group of devices handling the available state simultaneously */ pa_direction_t direction; int64_t latency_offset; pa_proplist *proplist; pa_hashmap *profiles; pa_dynarray prof; pa_dynarray devices; void (*impl_free)(struct pa_device_port *port); void *user_data; }; #define PA_DEVICE_PORT_DATA(p) (p->user_data); typedef struct pa_device_port_new_data { char *name; char *description; pa_available_t available; char *availability_group; pa_direction_t direction; pa_device_port_type_t type; } pa_device_port_new_data; pa_device_port_new_data *pa_device_port_new_data_init(pa_device_port_new_data *data); void pa_device_port_new_data_set_name(pa_device_port_new_data *data, const char *name); void pa_device_port_new_data_set_description(pa_device_port_new_data *data, const char *description); void pa_device_port_new_data_set_available(pa_device_port_new_data *data, pa_available_t available); void pa_device_port_new_data_set_availability_group(pa_device_port_new_data *data, const char *group); void pa_device_port_new_data_set_direction(pa_device_port_new_data *data, pa_direction_t direction); void pa_device_port_new_data_set_type(pa_device_port_new_data *data, pa_device_port_type_t type); void pa_device_port_new_data_done(pa_device_port_new_data *data); pa_device_port *pa_device_port_new(pa_core *c, pa_device_port_new_data *data, size_t extra); void pa_device_port_free(pa_device_port *port); void pa_device_port_set_available(pa_device_port *p, pa_available_t status); #ifdef __cplusplus } #endif #endif /* PULSE_DEVICE_PORT_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/acp/dynarray.h000066400000000000000000000061041511204443500264300ustar00rootroot00000000000000/* ALSA Card Profile */ /* SPDX-FileCopyrightText: Copyright © 2020 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef PA_DYNARRAY_H #define PA_DYNARRAY_H #include "compat.h" #ifdef __cplusplus extern "C" { #endif typedef struct pa_dynarray_item { void *ptr; } pa_dynarray_item; typedef struct pa_dynarray { pa_array array; pa_free_cb_t free_cb; } pa_dynarray; static inline void pa_dynarray_init(pa_dynarray *array, pa_free_cb_t free_cb) { pa_array_init(&array->array, 16); array->free_cb = free_cb; } static inline void pa_dynarray_item_free(pa_dynarray *array, pa_dynarray_item *item) { if (array->free_cb) array->free_cb(item->ptr); } static inline void pa_dynarray_clear(pa_dynarray *array) { pa_dynarray_item *item; pa_array_for_each(item, &array->array) pa_dynarray_item_free(array, item); pa_array_clear(&array->array); } static inline pa_dynarray* pa_dynarray_new(pa_free_cb_t free_cb) { pa_dynarray *d = calloc(1, sizeof(*d)); pa_dynarray_init(d, free_cb); return d; } static inline void pa_dynarray_free(pa_dynarray *array) { pa_dynarray_clear(array); free(array); } static inline void pa_dynarray_append(pa_dynarray *array, void *p) { pa_dynarray_item *item = pa_array_add(&array->array, sizeof(*item)); item->ptr = p; } static inline pa_dynarray_item *pa_dynarray_find_item(pa_dynarray *array, void *p) { pa_dynarray_item *item; pa_array_for_each(item, &array->array) { if (item->ptr == p) return item; } return NULL; } static inline pa_dynarray_item *pa_dynarray_get_item(pa_dynarray *array, unsigned i) { if (!pa_array_check_index(&array->array, i, pa_dynarray_item)) return NULL; return pa_array_get_unchecked(&array->array, i, pa_dynarray_item); } static inline void *pa_dynarray_get(pa_dynarray *array, unsigned i) { pa_dynarray_item *item = pa_dynarray_get_item(array, i); if (item == NULL) return NULL; return item->ptr; } static inline int pa_dynarray_insert_by_index(pa_dynarray *array, void *p, unsigned i) { unsigned j, len; pa_dynarray_item *item; len = pa_array_get_len(&array->array, pa_dynarray_item); if (i > len) return -EINVAL; item = pa_array_add(&array->array, sizeof(*item)); for (j = len; j > i; j--) { item--; item[1].ptr = item[0].ptr; } item->ptr = p; return 0; } static inline int pa_dynarray_remove_by_index(pa_dynarray *array, unsigned i) { pa_dynarray_item *item = pa_dynarray_get_item(array, i); if (item == NULL) return -ENOENT; pa_dynarray_item_free(array, item); pa_array_remove(&array->array, item); return 0; } static inline int pa_dynarray_remove_by_data(pa_dynarray *array, void *p) { pa_dynarray_item *item = pa_dynarray_find_item(array, p); if (item == NULL) return -ENOENT; pa_dynarray_item_free(array, item); pa_array_remove(&array->array, item); return 0; } static inline unsigned pa_dynarray_size(pa_dynarray *array) { return pa_array_get_len(&array->array, pa_dynarray_item); } #define PA_DYNARRAY_FOREACH(elem, array, idx) \ for ((idx) = 0; ((elem) = pa_dynarray_get(array, idx)); (idx)++) #ifdef __cplusplus } /* extern "C" */ #endif #endif /* PA_DYNARRAY_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/acp/hashmap.h000066400000000000000000000112601511204443500262170ustar00rootroot00000000000000/* ALSA Card Profile */ /* SPDX-FileCopyrightText: Copyright © 2020 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef PA_HASHMAP_H #define PA_HASHMAP_H #include "array.h" #ifdef __cplusplus extern "C" { #endif typedef unsigned (*pa_hash_func_t)(const void *p); typedef int (*pa_compare_func_t)(const void *a, const void *b); typedef struct pa_hashmap_item { void *key; void *value; } pa_hashmap_item; typedef struct pa_hashmap { pa_array array; pa_hash_func_t hash_func; pa_compare_func_t compare_func; pa_free_cb_t key_free_func; pa_free_cb_t value_free_func; } pa_hashmap; static inline pa_hashmap *pa_hashmap_new(pa_hash_func_t hash_func, pa_compare_func_t compare_func) { pa_hashmap *m = calloc(1, sizeof(pa_hashmap)); pa_array_init(&m->array, 16); m->hash_func = hash_func; m->compare_func = compare_func; return m; } static inline pa_hashmap *pa_hashmap_new_full(pa_hash_func_t hash_func, pa_compare_func_t compare_func, pa_free_cb_t key_free_func, pa_free_cb_t value_free_func) { pa_hashmap *m = pa_hashmap_new(hash_func, compare_func); m->key_free_func = key_free_func; m->value_free_func = value_free_func; return m; } static inline void pa_hashmap_item_free(pa_hashmap *h, pa_hashmap_item *item) { if (h->key_free_func && item->key) h->key_free_func(item->key); if (h->value_free_func && item->value) h->value_free_func(item->value); } static inline void pa_hashmap_remove_all(pa_hashmap *h) { pa_hashmap_item *item; pa_array_for_each(item, &h->array) pa_hashmap_item_free(h, item); pa_array_reset(&h->array); } static inline void pa_hashmap_free(pa_hashmap *h) { pa_hashmap_remove_all(h); pa_array_clear(&h->array); free(h); } static inline pa_hashmap_item* pa_hashmap_find_free(pa_hashmap *h) { pa_hashmap_item *item; pa_array_for_each(item, &h->array) { if (item->key == NULL) return item; } return pa_array_add(&h->array, sizeof(*item)); } static inline pa_hashmap_item* pa_hashmap_find(const pa_hashmap *h, const void *key) { pa_hashmap_item *item = NULL; pa_array_for_each(item, &h->array) { if (item->key != NULL && h->compare_func(item->key, key) == 0) return item; } return NULL; } static inline void* pa_hashmap_get(const pa_hashmap *h, const void *key) { const pa_hashmap_item *item = pa_hashmap_find(h, key); if (item == NULL) return NULL; return item->value; } static inline int pa_hashmap_put(pa_hashmap *h, void *key, void *value) { pa_hashmap_item *item = pa_hashmap_find(h, key); if (item != NULL) return -1; item = pa_hashmap_find_free(h); item->key = key; item->value = value; return 0; } static inline void* pa_hashmap_remove(pa_hashmap *h, const void *key) { pa_hashmap_item *item = pa_hashmap_find(h, key); void *value; if (item == NULL) return NULL; value = item->value; if (h->key_free_func) h->key_free_func(item->key); item->key = NULL; item->value = NULL; return value; } static inline int pa_hashmap_remove_and_free(pa_hashmap *h, const void *key) { void *val = pa_hashmap_remove(h, key); if (val && h->value_free_func) h->value_free_func(val); return val ? 0 : -1; } static inline void *pa_hashmap_first(const pa_hashmap *h) { pa_hashmap_item *item; pa_array_for_each(item, &h->array) { if (item->key != NULL) return item->value; } return NULL; } static inline void *pa_hashmap_iterate(const pa_hashmap *h, void **state, const void **key) { pa_hashmap_item *it = *state; if (it == NULL) *state = pa_array_first(&h->array); do { it = *state; if (!pa_array_check(&h->array, it)) return NULL; *state = it + 1; } while (it->key == NULL); if (key) *key = it->key; return it->value; } static inline bool pa_hashmap_isempty(const pa_hashmap *h) { pa_hashmap_item *item; pa_array_for_each(item, &h->array) if (item->key != NULL) return false; return true; } static inline unsigned pa_hashmap_size(const pa_hashmap *h) { unsigned count = 0; pa_hashmap_item *item; pa_array_for_each(item, &h->array) if (item->key != NULL) count++; return count; } static inline void pa_hashmap_sort(pa_hashmap *h, int (*compar)(const void *, const void *)) { qsort((void*)h->array.data, pa_array_get_len(&h->array, pa_hashmap_item), sizeof(pa_hashmap_item), compar); } #define PA_HASHMAP_FOREACH(e, h, state) \ for ((state) = NULL, (e) = pa_hashmap_iterate((h), &(state), NULL); \ (e); (e) = pa_hashmap_iterate((h), &(state), NULL)) /* A macro to ease iteration through all key, value pairs */ #define PA_HASHMAP_FOREACH_KV(k, e, h, state) \ for ((state) = NULL, (e) = pa_hashmap_iterate((h), &(state), (const void **) &(k)); \ (e); (e) = pa_hashmap_iterate((h), &(state), (const void **) &(k))) #ifdef __cplusplus } /* extern "C" */ #endif #endif /* PA_HASHMAP_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/acp/idxset.h000066400000000000000000000136621511204443500261060ustar00rootroot00000000000000/* ALSA Card Profile */ /* SPDX-FileCopyrightText: Copyright © 2020 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef PA_IDXSET_H #define PA_IDXSET_H #include "array.h" #ifdef __cplusplus extern "C" { #endif #define PA_IDXSET_INVALID ((uint32_t) -1) typedef unsigned (*pa_hash_func_t)(const void *p); typedef int (*pa_compare_func_t)(const void *a, const void *b); typedef struct pa_idxset_item { void *ptr; } pa_idxset_item; typedef struct pa_idxset { pa_array array; pa_hash_func_t hash_func; pa_compare_func_t compare_func; } pa_idxset; static inline unsigned pa_idxset_trivial_hash_func(const void *p) { return PA_PTR_TO_UINT(p); } static inline int pa_idxset_trivial_compare_func(const void *a, const void *b) { return a < b ? -1 : (a > b ? 1 : 0); } static inline unsigned pa_idxset_string_hash_func(const void *p) { unsigned hash = 0; const char *c; for (c = p; *c; c++) hash = 31 * hash + (unsigned) *c; return hash; } static inline int pa_idxset_string_compare_func(const void *a, const void *b) { return strcmp(a, b); } static inline pa_idxset *pa_idxset_new(pa_hash_func_t hash_func, pa_compare_func_t compare_func) { pa_idxset *s = calloc(1, sizeof(pa_idxset)); pa_array_init(&s->array, 16); s->hash_func = hash_func; s->compare_func = compare_func; return s; } static inline void pa_idxset_free(pa_idxset *s, pa_free_cb_t free_cb) { if (free_cb) { pa_idxset_item *item; pa_array_for_each(item, &s->array) free_cb(item->ptr); } pa_array_clear(&s->array); free(s); } static inline pa_idxset_item* pa_idxset_find(const pa_idxset *s, const void *ptr) { pa_idxset_item *item; pa_array_for_each(item, &s->array) { if (item->ptr == NULL) { if (ptr == NULL) return item; else continue; } if (s->compare_func(item->ptr, ptr) == 0) return item; } return NULL; } static inline int pa_idxset_put(pa_idxset*s, void *p, uint32_t *idx) { pa_idxset_item *item = pa_idxset_find(s, p); int res = item ? -1 : 0; if (item == NULL) { item = pa_idxset_find(s, NULL); if (item == NULL) item = pa_array_add(&s->array, sizeof(*item)); item->ptr = p; } if (idx) *idx = item - (pa_idxset_item*)s->array.data; return res; } static inline pa_idxset *pa_idxset_copy(pa_idxset *s, pa_copy_func_t copy_func) { pa_idxset_item *item; pa_idxset *copy = pa_idxset_new(s->hash_func, s->compare_func); pa_array_for_each(item, &s->array) { if (item->ptr) pa_idxset_put(copy, copy_func ? copy_func(item->ptr) : item->ptr, NULL); } return copy; } static inline bool pa_idxset_isempty(const pa_idxset *s) { pa_idxset_item *item; pa_array_for_each(item, &s->array) if (item->ptr != NULL) return false; return true; } static inline unsigned pa_idxset_size(pa_idxset*s) { unsigned count = 0; pa_idxset_item *item; pa_array_for_each(item, &s->array) if (item->ptr != NULL) count++; return count; } static inline pa_idxset_item *pa_idxset_search(pa_idxset *s, uint32_t *idx) { pa_idxset_item *item; for (item = pa_array_get_unchecked(&s->array, *idx, pa_idxset_item); pa_array_check(&s->array, item); item++, (*idx)++) { if (item->ptr != NULL) return item; } *idx = PA_IDXSET_INVALID; return NULL; } static inline pa_idxset_item *pa_idxset_reverse_search(pa_idxset *s, uint32_t *idx) { pa_idxset_item *item; for (item = pa_array_get_unchecked(&s->array, *idx, pa_idxset_item); pa_array_check(&s->array, item); item--, (*idx)--) { if (item->ptr != NULL) return item; } *idx = PA_IDXSET_INVALID; return NULL; } static inline void *pa_idxset_next(pa_idxset *s, uint32_t *idx) { pa_idxset_item *item; (*idx)++;; item = pa_idxset_search(s, idx); return item ? item->ptr : NULL; } static inline void* pa_idxset_first(pa_idxset *s, uint32_t *idx) { uint32_t i = 0; pa_idxset_item *item = pa_idxset_search(s, &i); if (idx) *idx = i; return item ? item->ptr : NULL; } static inline void* pa_idxset_last(pa_idxset *s, uint32_t *idx) { uint32_t i = pa_array_get_len(&s->array, pa_idxset_item) - 1; pa_idxset_item *item = pa_idxset_reverse_search(s, &i); if (idx) *idx = i; return item ? item->ptr : NULL; } static inline void* pa_idxset_steal_last(pa_idxset *s, uint32_t *idx) { uint32_t i = pa_array_get_len(&s->array, pa_idxset_item) - 1; void *ptr = NULL; pa_idxset_item *item = pa_idxset_reverse_search(s, &i); if (idx) *idx = i; if (item) { ptr = item->ptr; item->ptr = NULL; pa_array_remove(&s->array, item); } return ptr; } static inline void* pa_idxset_get_by_data(pa_idxset*s, const void *p, uint32_t *idx) { pa_idxset_item *item = pa_idxset_find(s, p); if (item == NULL) { if (idx) *idx = PA_IDXSET_INVALID; return NULL; } if (idx) *idx = item - (pa_idxset_item*)s->array.data; return item->ptr; } static inline bool pa_idxset_contains(pa_idxset *s, const void *p) { return pa_idxset_get_by_data(s, p, NULL) == p; } static inline bool pa_idxset_isdisjoint(pa_idxset *s, pa_idxset *t) { pa_idxset_item *item; pa_array_for_each(item, &s->array) { if (item->ptr && pa_idxset_contains(t, item->ptr)) return false; } return true; } static inline bool pa_idxset_issubset(pa_idxset *s, pa_idxset *t) { pa_idxset_item *item; pa_array_for_each(item, &s->array) { if (item->ptr && !pa_idxset_contains(t, item->ptr)) return false; } return true; } static inline bool pa_idxset_issuperset(pa_idxset *s, pa_idxset *t) { return pa_idxset_issubset(t, s); } static inline bool pa_idxset_equals(pa_idxset *s, pa_idxset *t) { return pa_idxset_issubset(s, t) && pa_idxset_issuperset(s, t); } static inline void* pa_idxset_get_by_index(pa_idxset*s, uint32_t idx) { pa_idxset_item *item; if (!pa_array_check_index(&s->array, idx, pa_idxset_item)) return NULL; item = pa_array_get_unchecked(&s->array, idx, pa_idxset_item); return item->ptr; } #define PA_IDXSET_FOREACH(e, s, idx) \ for ((e) = pa_idxset_first((s), &(idx)); (e); (e) = pa_idxset_next((s), &(idx))) #ifdef __cplusplus } /* extern "C" */ #endif #endif /* PA_IDXSET_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/acp/llist.h000066400000000000000000000126411511204443500257310ustar00rootroot00000000000000#ifndef foollistfoo #define foollistfoo /*** This file is part of PulseAudio. Copyright 2004-2006 Lennart Poettering PulseAudio is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. PulseAudio is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with PulseAudio; if not, see . ***/ /* Some macros for maintaining doubly linked lists */ /* The head of the linked list. Use this in the structure that shall * contain the head of the linked list */ #define PA_LLIST_HEAD(t,name) \ t *name /* The pointers in the linked list's items. Use this in the item structure */ #define PA_LLIST_FIELDS(t) \ t *next, *prev /* Initialize the list's head */ #define PA_LLIST_HEAD_INIT(t,item) \ do { \ (item) = (t*) NULL; } \ while(0) /* Initialize a list item */ #define PA_LLIST_INIT(t,item) \ do { \ t *_item = (item); \ pa_assert(_item); \ _item->prev = _item->next = NULL; \ } while(0) /* Prepend an item to the list */ #define PA_LLIST_PREPEND(t,head,item) \ do { \ t **_head = &(head), *_item = (item); \ pa_assert(_item); \ if ((_item->next = *_head)) \ _item->next->prev = _item; \ _item->prev = NULL; \ *_head = _item; \ } while (0) /* Remove an item from the list */ #define PA_LLIST_REMOVE(t,head,item) \ do { \ t **_head = &(head), *_item = (item); \ pa_assert(_item); \ if (_item->next) \ _item->next->prev = _item->prev; \ if (_item->prev) \ _item->prev->next = _item->next; \ else { \ pa_assert(*_head == _item); \ *_head = _item->next; \ } \ _item->next = _item->prev = NULL; \ } while(0) /* Find the head of the list */ #define PA_LLIST_FIND_HEAD(t,item,head) \ do { \ t **_head = (head), *_item = (item); \ *_head = _item; \ pa_assert(_head); \ while ((*_head)->prev) \ *_head = (*_head)->prev; \ } while (0) /* Insert an item after another one (a = where, b = what) */ #define PA_LLIST_INSERT_AFTER(t,head,a,b) \ do { \ t **_head = &(head), *_a = (a), *_b = (b); \ pa_assert(_b); \ if (!_a) { \ if ((_b->next = *_head)) \ _b->next->prev = _b; \ _b->prev = NULL; \ *_head = _b; \ } else { \ if ((_b->next = _a->next)) \ _b->next->prev = _b; \ _b->prev = _a; \ _a->next = _b; \ } \ } while (0) #define PA_LLIST_FOREACH(i,head) \ for (i = (head); i; i = i->next) #define PA_LLIST_FOREACH_SAFE(i,n,head) \ for (i = (head); i && ((n = i->next), 1); i = n) #endif pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/acp/meson.build000066400000000000000000000006211511204443500265660ustar00rootroot00000000000000acp_sources = [ 'acp.c', 'compat.c', 'alsa-mixer.c', 'alsa-ucm.c', 'alsa-util.c', 'conf-parser.c', ] acp_c_args = [ '-DHAVE_ALSA_UCM', '-DHAVE_READLINK', ] acp_lib = static_library( 'acp', acp_sources, c_args : acp_c_args, include_directories : [configinc, includes_inc ], dependencies : [ spa_dep, alsa_dep, mathlib, ] ) acp_dep = declare_dependency(link_with: acp_lib) pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/acp/proplist.h000066400000000000000000000112501511204443500264510ustar00rootroot00000000000000/* ALSA Card Profile */ /* SPDX-FileCopyrightText: Copyright © 2020 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef PA_PROPLIST_H #define PA_PROPLIST_H #include #include "array.h" #include "acp.h" #ifdef __cplusplus extern "C" { #endif #define PA_PROP_DEVICE_DESCRIPTION "device.description" #define PA_PROP_DEVICE_CLASS "device.class" #define PA_PROP_DEVICE_FORM_FACTOR "device.form_factor" #define PA_PROP_DEVICE_INTENDED_ROLES "device.intended_roles" #define PA_PROP_DEVICE_PROFILE_NAME "device.profile.name" #define PA_PROP_DEVICE_STRING "device.string" #define PA_PROP_DEVICE_API "device.api" #define PA_PROP_DEVICE_PRODUCT_NAME "device.product.name" #define PA_PROP_DEVICE_PROFILE_DESCRIPTION "device.profile.description" typedef struct pa_proplist_item { char *key; char *value; } pa_proplist_item; typedef struct pa_proplist { struct pa_array array; } pa_proplist; static inline pa_proplist* pa_proplist_new(void) { pa_proplist *p = calloc(1, sizeof(*p)); pa_array_init(&p->array, 16); return p; } static inline pa_proplist_item* pa_proplist_item_find(const pa_proplist *p, const void *key) { pa_proplist_item *item; pa_array_for_each(item, &p->array) { if (strcmp(key, item->key) == 0) return item; } return NULL; } static inline void pa_proplist_item_free(pa_proplist_item* it) { free(it->key); free(it->value); } static inline void pa_proplist_clear(pa_proplist* p) { pa_proplist_item *item; pa_array_for_each(item, &p->array) pa_proplist_item_free(item); pa_array_reset(&p->array); } static inline void pa_proplist_free(pa_proplist* p) { pa_proplist_clear(p); pa_array_clear(&p->array); free(p); } static inline unsigned pa_proplist_size(const pa_proplist *p) { return pa_array_get_len(&p->array, pa_proplist_item); } static inline int pa_proplist_contains(const pa_proplist *p, const char *key) { return pa_proplist_item_find(p, key) ? 1 : 0; } static inline int pa_proplist_sets(pa_proplist *p, const char *key, const char *value) { pa_proplist_item *item = pa_proplist_item_find(p, key); if (item != NULL) pa_proplist_item_free(item); else item = pa_array_add(&p->array, sizeof(*item)); item->key = strdup(key); item->value = strdup(value); return 0; } static inline int pa_proplist_unset(pa_proplist *p, const char *key) { pa_proplist_item *item = pa_proplist_item_find(p, key); if (item == NULL) return -ENOENT; pa_proplist_item_free(item); pa_array_remove(&p->array, item); return 0; } static PA_PRINTF_FUNC(3,4) inline int pa_proplist_setf(pa_proplist *p, const char *key, const char *format, ...) { pa_proplist_item *item = pa_proplist_item_find(p, key); va_list args; int res; va_start(args, format); if (item != NULL) pa_proplist_item_free(item); else item = pa_array_add(&p->array, sizeof(*item)); item->key = strdup(key); if ((res = vasprintf(&item->value, format, args)) < 0) res = -errno; va_end(args); return res; } static inline const char *pa_proplist_gets(const pa_proplist *p, const char *key) { pa_proplist_item *item = pa_proplist_item_find(p, key); return item ? item->value : NULL; } typedef enum pa_update_mode { PA_UPDATE_SET /**< Replace the entire property list with the new one. Don't keep * any of the old data around. */, PA_UPDATE_MERGE /**< Merge new property list into the existing one, not replacing * any old entries if they share a common key with the new * property list. */, PA_UPDATE_REPLACE /**< Merge new property list into the existing one, replacing all * old entries that share a common key with the new property * list. */ } pa_update_mode_t; static inline void pa_proplist_update(pa_proplist *p, pa_update_mode_t mode, const pa_proplist *other) { pa_proplist_item *item; if (mode == PA_UPDATE_SET) pa_proplist_clear(p); pa_array_for_each(item, &other->array) { if (mode == PA_UPDATE_MERGE && pa_proplist_contains(p, item->key)) continue; pa_proplist_sets(p, item->key, item->value); } } static inline pa_proplist* pa_proplist_new_dict(const struct acp_dict *dict) { pa_proplist *p = pa_proplist_new(); if (dict) { const struct acp_dict_item *item; struct acp_dict_item *it; acp_dict_for_each(item, dict) { it = pa_array_add(&p->array, sizeof(*it)); it->key = strdup(item->key); it->value = strdup(item->value); } } return p; } static inline void pa_proplist_as_dict(const pa_proplist *p, struct acp_dict *dict) { dict->n_items = pa_proplist_size(p); dict->items = p->array.data; } #ifdef __cplusplus } /* extern "C" */ #endif #endif /* PA_PROPLIST_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/acp/volume.h000066400000000000000000000132341511204443500261100ustar00rootroot00000000000000/*** This file is part of PulseAudio. Copyright 2004-2006 Lennart Poettering Copyright 2006 Pierre Ossman for Cendio AB PulseAudio is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. PulseAudio is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with PulseAudio; if not, see . ***/ #ifndef PA_VOLUME_H #define PA_VOLUME_H #include #ifdef __cplusplus extern "C" { #endif typedef uint32_t pa_volume_t; #define PA_VOLUME_MUTED ((pa_volume_t) 0U) #define PA_VOLUME_NORM ((pa_volume_t) 0x10000U) #define PA_VOLUME_MAX ((pa_volume_t) UINT32_MAX/2) #ifdef INFINITY #define PA_DECIBEL_MININFTY ((double) -INFINITY) #else #define PA_DECIBEL_MININFTY ((double) -200.0) #endif #define PA_CLAMP_VOLUME(v) (PA_CLAMP_UNLIKELY((v), PA_VOLUME_MUTED, PA_VOLUME_MAX)) typedef struct pa_cvolume { uint32_t channels; /**< Number of channels */ pa_volume_t values[PA_CHANNELS_MAX]; /**< Per-channel volume */ } pa_cvolume; static inline double pa_volume_linear_to_dB(double v) { return 20.0 * log10(v); } static inline double pa_sw_volume_to_linear(pa_volume_t v) { double f; if (v <= PA_VOLUME_MUTED) return 0.0; if (v == PA_VOLUME_NORM) return 1.0; f = ((double) v / PA_VOLUME_NORM); return f*f*f; } static inline double pa_sw_volume_to_dB(pa_volume_t v) { if (v <= PA_VOLUME_MUTED) return PA_DECIBEL_MININFTY; return pa_volume_linear_to_dB(pa_sw_volume_to_linear(v)); } static inline double pa_volume_dB_to_linear(double v) { return pow(10.0, v / 20.0); } static inline pa_volume_t pa_sw_volume_from_linear(double v) { if (v <= 0.0) return PA_VOLUME_MUTED; return (pa_volume_t) PA_CLAMP_VOLUME((uint64_t) lround(cbrt(v) * PA_VOLUME_NORM)); } static inline pa_volume_t pa_sw_volume_from_dB(double dB) { if (dB == -INFINITY || dB <= PA_DECIBEL_MININFTY) return PA_VOLUME_MUTED; return pa_sw_volume_from_linear(pa_volume_dB_to_linear(dB)); } static inline pa_cvolume* pa_cvolume_set(pa_cvolume *a, unsigned channels, pa_volume_t v) { uint32_t i; a->channels = (uint8_t) channels; for (i = 0; i < a->channels; i++) a->values[i] = PA_CLAMP_VOLUME(v); return a; } static inline int pa_cvolume_equal(const pa_cvolume *a, const pa_cvolume *b) { uint32_t i; if (PA_UNLIKELY(a == b)) return 1; if (a->channels != b->channels) return 0; for (i = 0; i < a->channels; i++) if (a->values[i] != b->values[i]) return 0; return 1; } static inline pa_volume_t pa_sw_volume_multiply(pa_volume_t a, pa_volume_t b) { uint64_t result; result = ((uint64_t) a * (uint64_t) b + (uint64_t) PA_VOLUME_NORM / 2ULL) / (uint64_t) PA_VOLUME_NORM; if (result > (uint64_t)PA_VOLUME_MAX) pa_log_warn("pa_sw_volume_multiply: Volume exceeds maximum allowed value and will be clipped. Please check your volume settings."); return (pa_volume_t) PA_CLAMP_VOLUME(result); } static inline pa_cvolume *pa_sw_cvolume_multiply(pa_cvolume *dest, const pa_cvolume *a, const pa_cvolume *b) { unsigned i; dest->channels = PA_MIN(a->channels, b->channels); for (i = 0; i < dest->channels; i++) dest->values[i] = pa_sw_volume_multiply(a->values[i], b->values[i]); return dest; } static inline pa_cvolume *pa_sw_cvolume_multiply_scalar(pa_cvolume *dest, const pa_cvolume *a, pa_volume_t b) { unsigned i; for (i = 0; i < a->channels; i++) dest->values[i] = pa_sw_volume_multiply(a->values[i], b); dest->channels = (uint8_t) i; return dest; } static inline pa_volume_t pa_sw_volume_divide(pa_volume_t a, pa_volume_t b) { uint64_t result; if (b <= PA_VOLUME_MUTED) return 0; result = ((uint64_t) a * (uint64_t) PA_VOLUME_NORM + (uint64_t) b / 2ULL) / (uint64_t) b; if (result > (uint64_t)PA_VOLUME_MAX) pa_log_warn("pa_sw_volume_divide: Volume exceeds maximum allowed value and will be clipped. Please check your volume settings."); return (pa_volume_t) PA_CLAMP_VOLUME(result); } static inline pa_cvolume *pa_sw_cvolume_divide_scalar(pa_cvolume *dest, const pa_cvolume *a, pa_volume_t b) { unsigned i; for (i = 0; i < a->channels; i++) dest->values[i] = pa_sw_volume_divide(a->values[i], b); dest->channels = (uint8_t) i; return dest; } static inline pa_cvolume *pa_sw_cvolume_divide(pa_cvolume *dest, const pa_cvolume *a, const pa_cvolume *b) { unsigned i; dest->channels = PA_MIN(a->channels, b->channels); for (i = 0; i < dest->channels; i++) dest->values[i] = pa_sw_volume_divide(a->values[i], b->values[i]); return dest; } #define pa_cvolume_reset(a, n) pa_cvolume_set((a), (n), PA_VOLUME_NORM) #define pa_cvolume_mute(a, n) pa_cvolume_set((a), (n), PA_VOLUME_MUTED) static inline int pa_cvolume_compatible_with_channel_map(const pa_cvolume *v, const pa_channel_map *cm) { return v->channels == cm->channels; } static inline pa_volume_t pa_cvolume_max(const pa_cvolume *a) { pa_volume_t m = PA_VOLUME_MUTED; unsigned c; for (c = 0; c < a->channels; c++) if (a->values[c] > m) m = a->values[c]; return m; } static inline pa_volume_t pa_cvolume_min(const pa_cvolume *a) { pa_volume_t m = PA_VOLUME_MAX; unsigned c; for (c = 0; c < a->channels; c++) if (a->values[c] < m) m = a->values[c]; return m; } #ifdef __cplusplus } /* extern "C" */ #endif #endif /* PA_VOLUME_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/alsa-acp-device.c000066400000000000000000001040161511204443500267460ustar00rootroot00000000000000/* Spa ALSA Device */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "alsa.h" #include "acp/acp.h" extern struct spa_i18n *acp_i18n; #define MAX_POLL 16 #define MAX_CHANNELS SPA_AUDIO_MAX_CHANNELS #define DEFAULT_DEVICE "hw:0" #define DEFAULT_AUTO_PROFILE true #define DEFAULT_AUTO_PORT true struct props { char device[64]; bool auto_profile; bool auto_port; }; static void reset_props(struct props *props) { strncpy(props->device, DEFAULT_DEVICE, 64); props->auto_profile = DEFAULT_AUTO_PROFILE; props->auto_port = DEFAULT_AUTO_PORT; } struct impl { struct spa_handle handle; struct spa_device device; struct spa_log *log; struct spa_loop *loop; uint32_t info_all; struct spa_device_info info; #define IDX_EnumProfile 0 #define IDX_Profile 1 #define IDX_EnumRoute 2 #define IDX_Route 3 struct spa_param_info params[4]; struct spa_hook_list hooks; struct props props; uint32_t profile; struct acp_card *card; struct pollfd pfds[MAX_POLL]; int n_pfds; struct spa_source sources[MAX_POLL]; }; static int emit_info(struct impl *this, bool full); static void handle_acp_poll(struct spa_source *source) { struct impl *this = source->data; int i; for (i = 0; i < this->n_pfds; i++) this->pfds[i].revents = this->sources[i].rmask; acp_card_handle_events(this->card); for (i = 0; i < this->n_pfds; i++) this->sources[i].rmask = 0; emit_info(this, false); } static void remove_sources(struct impl *this) { int i; for (i = 0; i < this->n_pfds; i++) { spa_loop_remove_source(this->loop, &this->sources[i]); } this->n_pfds = 0; } static int setup_sources(struct impl *this) { int i; remove_sources(this); this->n_pfds = acp_card_poll_descriptors(this->card, this->pfds, MAX_POLL); for (i = 0; i < this->n_pfds; i++) { this->sources[i].func = handle_acp_poll; this->sources[i].data = this; this->sources[i].fd = this->pfds[i].fd; this->sources[i].mask = this->pfds[i].events; this->sources[i].rmask = 0; spa_loop_add_source(this->loop, &this->sources[i]); } return 0; } static int replace_string(const char *str, const char *val, const char *rep, char *buf, size_t size) { struct spa_strbuf s; const char *p; size_t len = strlen(val); spa_assert(len > 0); spa_strbuf_init(&s, buf, size); while (1) { p = strstr(str, val); if (!p) break; spa_strbuf_append(&s, "%.*s%s", (int)SPA_PTRDIFF(p, str), str, rep); str = p + len; } spa_strbuf_append(&s, "%s", str); return 0; } static int emit_node(struct impl *this, struct acp_device *dev) { struct spa_dict_item *items; const struct acp_dict_item *it; uint32_t n_items, i; char device_name[128], path[210], channels[16], ch[12], routes[16]; char card_index[16], card_name[64]; char positions[MAX_CHANNELS * 12]; char codecs[512]; struct spa_device_object_info info; struct acp_card *card = this->card; const char *stream, *card_id, *bus; struct spa_strbuf b; info = SPA_DEVICE_OBJECT_INFO_INIT(); info.type = SPA_TYPE_INTERFACE_Node; if (dev->direction == ACP_DIRECTION_PLAYBACK) { info.factory_name = SPA_NAME_API_ALSA_PCM_SINK; stream = "playback"; } else { info.factory_name = SPA_NAME_API_ALSA_PCM_SOURCE; stream = "capture"; } info.change_mask = SPA_DEVICE_OBJECT_CHANGE_MASK_PROPS; items = alloca((dev->props.n_items + 12) * sizeof(*items)); n_items = 0; snprintf(card_index, sizeof(card_index), "%d", card->index); card_id = acp_dict_lookup(&card->props, "alsa.id"); snprintf(card_name, sizeof(card_name), "%s", card_id ? card_id : card_index); replace_string(dev->device_strings[0], "%f", card_index, device_name, sizeof(device_name)); snprintf(path, sizeof(path), "alsa:acp:%s:%d:%s", card_name, dev->index, stream); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_OBJECT_PATH, path); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_API_ALSA_PATH, device_name); if (dev->flags & ACP_DEVICE_UCM_DEVICE) items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_API_ALSA_OPEN_UCM, "true"); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_API_ALSA_PCM_CARD, card_index); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_API_ALSA_PCM_STREAM, stream); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_PORT_GROUP, stream); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_ICON_NAME, "audio-card-analog"); bus = acp_dict_lookup(&card->props, SPA_KEY_DEVICE_BUS); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_BUS, bus); snprintf(channels, sizeof(channels), "%d", dev->format.channels); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_AUDIO_CHANNELS, channels); spa_strbuf_init(&b, positions, sizeof(positions)); spa_strbuf_append(&b, "["); for (i = 0; i < dev->format.channels; i++) { spa_strbuf_append(&b, "%s%s", i == 0 ? " " : ", ", acp_channel_str(ch, sizeof(ch), dev->format.map[i])); } spa_strbuf_append(&b, " ]"); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_AUDIO_POSITION, positions); if (dev->n_codecs > 0) { acp_iec958_codecs_to_json(dev->codecs, dev->n_codecs, codecs, sizeof(codecs)); items[n_items++] = SPA_DICT_ITEM_INIT("iec958.codecs", codecs); } snprintf(routes, sizeof(routes), "%d", dev->n_ports); items[n_items++] = SPA_DICT_ITEM_INIT("device.routes", routes); acp_dict_for_each(it, &dev->props) items[n_items++] = SPA_DICT_ITEM_INIT(it->key, it->value); info.props = &SPA_DICT_INIT(items, n_items); spa_device_emit_object_info(&this->hooks, dev->index, &info); return 0; } static int emit_info(struct impl *this, bool full) { int err = 0; struct spa_dict_item *items; uint32_t n_items; const struct acp_dict_item *it; struct acp_card *card = this->card; char path[128]; uint64_t old = full ? this->info.change_mask : 0; const char *card_id; if (full) this->info.change_mask = this->info_all; if (this->info.change_mask) { n_items = card->props.n_items + 4; items = alloca(n_items * sizeof(*items)); card_id = acp_dict_lookup(&card->props, "alsa.id"); n_items = 0; #define ADD_ITEM(key, value) items[n_items++] = SPA_DICT_ITEM_INIT(key, value) if (card_id) snprintf(path, sizeof(path), "alsa:acp:%s", card_id); else snprintf(path, sizeof(path), "alsa:acp:%d", card->index); ADD_ITEM(SPA_KEY_OBJECT_PATH, path); ADD_ITEM(SPA_KEY_DEVICE_API, "alsa:acp"); ADD_ITEM(SPA_KEY_MEDIA_CLASS, "Audio/Device"); ADD_ITEM(SPA_KEY_API_ALSA_PATH, (char *)this->props.device); acp_dict_for_each(it, &card->props) ADD_ITEM(it->key, it->value); this->info.props = &SPA_DICT_INIT(items, n_items); #undef ADD_ITEM if (this->info.change_mask & SPA_DEVICE_CHANGE_MASK_PARAMS) { SPA_FOR_EACH_ELEMENT_VAR(this->params, p) { if (p->user > 0) { p->flags ^= SPA_PARAM_INFO_SERIAL; p->user = 0; } } } spa_device_emit_info(&this->hooks, &this->info); this->info.change_mask = old; } return err; } static int impl_add_listener(void *object, struct spa_hook *listener, const struct spa_device_events *events, void *data) { struct impl *this = object; struct spa_hook_list save; struct acp_card *card; struct acp_card_profile *profile; uint32_t i; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(events != NULL, -EINVAL); card = this->card; if (card->active_profile_index < card->n_profiles) profile = card->profiles[card->active_profile_index]; else profile = NULL; spa_hook_list_isolate(&this->hooks, &save, listener, events, data); if (events->info || events->object_info) emit_info(this, true); if (profile) { for (i = 0; i < profile->n_devices; i++) emit_node(this, profile->devices[i]); } spa_hook_list_join(&this->hooks, &save); return 0; } static int impl_sync(void *object, int seq) { struct impl *this = object; spa_return_val_if_fail(this != NULL, -EINVAL); spa_device_emit_result(&this->hooks, seq, 0, 0, NULL); return 0; } static struct spa_pod *build_profile(struct spa_pod_builder *b, uint32_t id, struct acp_card_profile *pr, bool current) { struct spa_pod_frame f[2]; uint32_t i, n_classes, n_capture = 0, n_playback = 0; uint32_t *capture, *playback; capture = alloca(sizeof(uint32_t) * pr->n_devices); playback = alloca(sizeof(uint32_t) * pr->n_devices); for (i = 0; i < pr->n_devices; i++) { struct acp_device *dev = pr->devices[i]; switch (dev->direction) { case ACP_DIRECTION_PLAYBACK: playback[n_playback++] = dev->index; break; case ACP_DIRECTION_CAPTURE: capture[n_capture++] = dev->index; break; } } n_classes = n_capture > 0 ? 1 : 0; n_classes += n_playback > 0 ? 1 : 0; spa_pod_builder_push_object(b, &f[0], SPA_TYPE_OBJECT_ParamProfile, id); spa_pod_builder_add(b, SPA_PARAM_PROFILE_index, SPA_POD_Int(pr->index), SPA_PARAM_PROFILE_name, SPA_POD_String(pr->name), SPA_PARAM_PROFILE_description, SPA_POD_String(pr->description), SPA_PARAM_PROFILE_priority, SPA_POD_Int(pr->priority), SPA_PARAM_PROFILE_available, SPA_POD_Id(pr->available), 0); spa_pod_builder_prop(b, SPA_PARAM_PROFILE_classes, 0); spa_pod_builder_push_struct(b, &f[1]); spa_pod_builder_int(b, n_classes); if (n_capture > 0) { spa_pod_builder_add_struct(b, SPA_POD_String("Audio/Source"), SPA_POD_Int(n_capture), SPA_POD_String("card.profile.devices"), SPA_POD_Array(sizeof(uint32_t), SPA_TYPE_Int, n_capture, capture)); } if (n_playback > 0) { spa_pod_builder_add_struct(b, SPA_POD_String("Audio/Sink"), SPA_POD_Int(n_playback), SPA_POD_String("card.profile.devices"), SPA_POD_Array(sizeof(uint32_t), SPA_TYPE_Int, n_playback, playback)); } spa_pod_builder_pop(b, &f[1]); if (current) { spa_pod_builder_prop(b, SPA_PARAM_PROFILE_save, 0); spa_pod_builder_bool(b, SPA_FLAG_IS_SET(pr->flags, ACP_PROFILE_SAVE)); } return spa_pod_builder_pop(b, &f[0]); } static struct spa_pod *build_route(struct spa_pod_builder *b, uint32_t id, struct acp_port *p, struct acp_device *dev, uint32_t profile) { struct spa_pod_frame f[2]; const struct acp_dict_item *item; uint32_t i; enum spa_direction direction; switch (p->direction) { case ACP_DIRECTION_PLAYBACK: direction = SPA_DIRECTION_OUTPUT; break; case ACP_DIRECTION_CAPTURE: direction = SPA_DIRECTION_INPUT; break; default: errno = EINVAL; return NULL; } spa_pod_builder_push_object(b, &f[0], SPA_TYPE_OBJECT_ParamRoute, id); spa_pod_builder_add(b, SPA_PARAM_ROUTE_index, SPA_POD_Int(p->index), SPA_PARAM_ROUTE_direction, SPA_POD_Id(direction), SPA_PARAM_ROUTE_name, SPA_POD_String(p->name), SPA_PARAM_ROUTE_description, SPA_POD_String(p->description), SPA_PARAM_ROUTE_priority, SPA_POD_Int(p->priority), SPA_PARAM_ROUTE_available, SPA_POD_Id(p->available), 0); spa_pod_builder_prop(b, SPA_PARAM_ROUTE_info, SPA_POD_PROP_FLAG_HINT_DICT); spa_pod_builder_push_struct(b, &f[1]); spa_pod_builder_int(b, p->props.n_items + (dev ? 2 : 0)); acp_dict_for_each(item, &p->props) { spa_pod_builder_add(b, SPA_POD_String(item->key), SPA_POD_String(item->value), NULL); } if (dev != NULL) { const char *str; str = SPA_FLAG_IS_SET(dev->flags, ACP_DEVICE_HW_MUTE) ? "true" : "false"; spa_pod_builder_add(b, SPA_POD_String("route.hw-mute"), SPA_POD_String(str), NULL); str = SPA_FLAG_IS_SET(dev->flags, ACP_DEVICE_HW_VOLUME) ? "true" : "false"; spa_pod_builder_add(b, SPA_POD_String("route.hw-volume"), SPA_POD_String(str), NULL); } spa_pod_builder_pop(b, &f[1]); spa_pod_builder_prop(b, SPA_PARAM_ROUTE_profiles, 0); spa_pod_builder_push_array(b, &f[1]); for (i = 0; i < p->n_profiles; i++) spa_pod_builder_int(b, p->profiles[i]->index); spa_pod_builder_pop(b, &f[1]); if (dev != NULL) { uint32_t channels = dev->format.channels; float volumes[channels]; float soft_volumes[channels]; bool mute; acp_device_get_mute(dev, &mute); spa_zero(volumes); spa_zero(soft_volumes); acp_device_get_volume(dev, volumes, channels); acp_device_get_soft_volume(dev, soft_volumes, channels); spa_pod_builder_prop(b, SPA_PARAM_ROUTE_device, 0); spa_pod_builder_int(b, dev->index); spa_pod_builder_prop(b, SPA_PARAM_ROUTE_props, 0); spa_pod_builder_push_object(b, &f[1], SPA_TYPE_OBJECT_Props, id); spa_pod_builder_prop(b, SPA_PROP_mute, SPA_FLAG_IS_SET(dev->flags, ACP_DEVICE_HW_MUTE) ? SPA_POD_PROP_FLAG_HARDWARE : 0); spa_pod_builder_bool(b, mute); spa_pod_builder_prop(b, SPA_PROP_channelVolumes, SPA_FLAG_IS_SET(dev->flags, ACP_DEVICE_HW_VOLUME) ? SPA_POD_PROP_FLAG_HARDWARE : 0); spa_pod_builder_array(b, sizeof(float), SPA_TYPE_Float, channels, volumes); spa_pod_builder_prop(b, SPA_PROP_volumeBase, SPA_POD_PROP_FLAG_READONLY); spa_pod_builder_float(b, dev->base_volume); spa_pod_builder_prop(b, SPA_PROP_volumeStep, SPA_POD_PROP_FLAG_READONLY); spa_pod_builder_float(b, dev->volume_step); spa_pod_builder_prop(b, SPA_PROP_channelMap, 0); spa_pod_builder_array(b, sizeof(uint32_t), SPA_TYPE_Id, channels, dev->format.map); spa_pod_builder_prop(b, SPA_PROP_softVolumes, 0); spa_pod_builder_array(b, sizeof(float), SPA_TYPE_Float, channels, soft_volumes); spa_pod_builder_prop(b, SPA_PROP_latencyOffsetNsec, 0); spa_pod_builder_long(b, dev->latency_ns); if (SPA_FLAG_IS_SET(dev->flags, ACP_DEVICE_IEC958)) { spa_pod_builder_prop(b, SPA_PROP_iec958Codecs, 0); spa_pod_builder_array(b, sizeof(uint32_t), SPA_TYPE_Id, dev->n_codecs, dev->codecs); } spa_pod_builder_pop(b, &f[1]); } spa_pod_builder_prop(b, SPA_PARAM_ROUTE_devices, 0); spa_pod_builder_push_array(b, &f[1]); for (i = 0; i < p->n_devices; i++) spa_pod_builder_int(b, p->devices[i]->index); spa_pod_builder_pop(b, &f[1]); if (profile != SPA_ID_INVALID) { spa_pod_builder_prop(b, SPA_PARAM_ROUTE_profile, 0); spa_pod_builder_int(b, profile); spa_pod_builder_prop(b, SPA_PARAM_ROUTE_save, 0); spa_pod_builder_bool(b, SPA_FLAG_IS_SET(p->flags, ACP_PORT_SAVE)); } return spa_pod_builder_pop(b, &f[0]); } static struct acp_port *find_port_for_device(struct acp_card *card, struct acp_device *dev) { uint32_t i; for (i = 0; i < dev->n_ports; i++) { struct acp_port *p = dev->ports[i]; if (SPA_FLAG_IS_SET(p->flags, ACP_PORT_ACTIVE)) return p; } return NULL; } static int impl_enum_params(void *object, int seq, uint32_t id, uint32_t start, uint32_t num, const struct spa_pod *filter) { struct impl *this = object; struct spa_pod *param; spa_auto(spa_pod_dynamic_builder) b = { 0 }; struct spa_pod_builder_state state; uint8_t buffer[4096]; struct spa_result_device_params result; uint32_t count = 0; struct acp_card *card; struct acp_card_profile *pr; struct acp_port *p; struct acp_device *dev; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(num != 0, -EINVAL); spa_pod_dynamic_builder_init(&b, buffer, sizeof(buffer), 4096); spa_pod_builder_get_state(&b.b, &state); card = this->card; result.id = id; result.next = start; next: result.index = result.next++; spa_pod_builder_reset(&b.b, &state); switch (id) { case SPA_PARAM_EnumProfile: if (result.index >= card->n_profiles) return 0; pr = card->profiles[result.index]; if (SPA_FLAG_IS_SET(pr->flags, ACP_PROFILE_HIDDEN)) goto next; param = build_profile(&b.b, id, pr, false); break; case SPA_PARAM_Profile: if (result.index > 0 || card->active_profile_index >= card->n_profiles) return 0; pr = card->profiles[card->active_profile_index]; if (SPA_FLAG_IS_SET(pr->flags, ACP_PROFILE_HIDDEN)) goto next; param = build_profile(&b.b, id, pr, true); break; case SPA_PARAM_EnumRoute: if (result.index >= card->n_ports) return 0; p = card->ports[result.index]; if (SPA_FLAG_IS_SET(p->flags, ACP_PORT_HIDDEN)) goto next; param = build_route(&b.b, id, p, NULL, SPA_ID_INVALID); break; case SPA_PARAM_Route: while (true) { if (result.index >= card->n_devices) return 0; dev = card->devices[result.index]; if (SPA_FLAG_IS_SET(dev->flags, ACP_DEVICE_HIDDEN)) goto next; if (SPA_FLAG_IS_SET(dev->flags, ACP_DEVICE_ACTIVE) && (p = find_port_for_device(card, dev)) != NULL) break; result.index++; } result.next = result.index + 1; if (SPA_FLAG_IS_SET(p->flags, ACP_PORT_HIDDEN)) goto next; param = build_route(&b.b, id, p, dev, card->active_profile_index); if (param == NULL) return -errno; break; default: return -ENOENT; } if (spa_pod_filter(&b.b, &result.param, param, filter) < 0) goto next; spa_device_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_DEVICE_PARAMS, &result); if (++count != num) goto next; return 0; } static void on_latency_changed(void *data, struct acp_device *dev) { struct impl *this = data; struct spa_event *event; uint8_t buffer[4096]; struct spa_pod_builder b = { 0 }; struct spa_pod_frame f[1]; spa_log_info(this->log, "device %s latency changed", dev->name); this->info.change_mask |= SPA_DEVICE_CHANGE_MASK_PARAMS; this->params[IDX_Route].user++; spa_pod_builder_init(&b, buffer, sizeof(buffer)); spa_pod_builder_push_object(&b, &f[0], SPA_TYPE_EVENT_Device, SPA_DEVICE_EVENT_ObjectConfig); spa_pod_builder_prop(&b, SPA_EVENT_DEVICE_Object, 0); spa_pod_builder_int(&b, dev->index); spa_pod_builder_prop(&b, SPA_EVENT_DEVICE_Props, 0); spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_Props, SPA_EVENT_DEVICE_Props, SPA_PROP_latencyOffsetNsec, SPA_POD_Long(dev->latency_ns)); event = spa_pod_builder_pop(&b, &f[0]); spa_device_emit_event(&this->hooks, event); } static void on_codecs_changed(void *data, struct acp_device *dev) { struct impl *this = data; struct spa_event *event; uint8_t buffer[4096]; struct spa_pod_builder b = { 0 }; struct spa_pod_frame f[1]; spa_log_info(this->log, "device %s codecs changed", dev->name); this->info.change_mask |= SPA_DEVICE_CHANGE_MASK_PARAMS; this->params[IDX_Route].user++; spa_pod_builder_init(&b, buffer, sizeof(buffer)); spa_pod_builder_push_object(&b, &f[0], SPA_TYPE_EVENT_Device, SPA_DEVICE_EVENT_ObjectConfig); spa_pod_builder_prop(&b, SPA_EVENT_DEVICE_Object, 0); spa_pod_builder_int(&b, dev->index); spa_pod_builder_prop(&b, SPA_EVENT_DEVICE_Props, 0); spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_Props, SPA_EVENT_DEVICE_Props, SPA_PROP_iec958Codecs, SPA_POD_Array(sizeof(uint32_t), SPA_TYPE_Id, dev->n_codecs, dev->codecs)); event = spa_pod_builder_pop(&b, &f[0]); spa_device_emit_event(&this->hooks, event); } static int apply_device_props(struct impl *this, struct acp_device *dev, struct spa_pod *props) { float volume = 0; bool mute = 0; struct spa_pod_prop *prop; struct spa_pod_object *obj = (struct spa_pod_object *) props; int changed = 0; float volumes[MAX_CHANNELS]; uint32_t channels[MAX_CHANNELS]; uint32_t n_volumes = 0; if (!spa_pod_is_object_type(props, SPA_TYPE_OBJECT_Props)) return -EINVAL; SPA_POD_OBJECT_FOREACH(obj, prop) { switch (prop->key) { case SPA_PROP_volume: if (spa_pod_get_float(&prop->value, &volume) == 0) { acp_device_set_volume(dev, &volume, 1); changed++; } break; case SPA_PROP_mute: if (spa_pod_get_bool(&prop->value, &mute) == 0) { acp_device_set_mute(dev, mute); changed++; } break; case SPA_PROP_channelVolumes: if ((n_volumes = spa_pod_copy_array(&prop->value, SPA_TYPE_Float, volumes, SPA_N_ELEMENTS(volumes))) > 0) { changed++; } break; case SPA_PROP_channelMap: if (spa_pod_copy_array(&prop->value, SPA_TYPE_Id, channels, SPA_N_ELEMENTS(channels)) > 0) { changed++; } break; case SPA_PROP_latencyOffsetNsec: { int64_t latency_ns; if (spa_pod_get_long(&prop->value, &latency_ns) == 0) { if (dev->latency_ns != latency_ns) { dev->latency_ns = latency_ns; on_latency_changed(this, dev); changed++; } } break; } case SPA_PROP_iec958Codecs: { uint32_t codecs[32], n_codecs; n_codecs = spa_pod_copy_array(&prop->value, SPA_TYPE_Id, codecs, SPA_N_ELEMENTS(codecs)); if (n_codecs != dev->n_codecs || memcmp(dev->codecs, codecs, n_codecs * sizeof(uint32_t)) != 0) { memcpy(dev->codecs, codecs, n_codecs * sizeof(uint32_t)); dev->n_codecs = n_codecs; on_codecs_changed(this, dev); changed++; } break; } default: break; } } if (n_volumes > 0) acp_device_set_volume(dev, volumes, n_volumes); return changed; } static uint32_t find_profile_by_name(struct acp_card *card, const char *name) { uint32_t i; for (i = 0; i < card->n_profiles; i++) { if (spa_streq(card->profiles[i]->name, name)) return i; } return SPA_ID_INVALID; } static uint32_t find_route_by_name(struct acp_card *card, const char *name) { uint32_t i; for (i = 0; i < card->n_ports; i++) { if (spa_streq(card->ports[i]->name, name)) return i; } return SPA_ID_INVALID; } static bool check_active_profile_port(struct impl *this, uint32_t device, uint32_t port_index) { struct acp_port *p; uint32_t i; if (port_index >= this->card->n_ports) return false; p = this->card->ports[port_index]; /* Port must be in active profile */ for (i = 0; i < p->n_profiles; i++) if (p->profiles[i]->index == this->card->active_profile_index) break; if (i == p->n_profiles) return false; /* Port must correspond to the device */ for (i = 0; i< p->n_devices; i++) if (p->devices[i]->index == device) break; if (i == p->n_devices) return false; return true; } static int impl_set_param(void *object, uint32_t id, uint32_t flags, const struct spa_pod *param) { struct impl *this = object; int res; spa_return_val_if_fail(this != NULL, -EINVAL); switch (id) { case SPA_PARAM_Profile: { uint32_t idx = SPA_ID_INVALID; const char *name = NULL; bool save = false; if (param == NULL) { idx = acp_card_find_best_profile_index(this->card, NULL); save = true; } else if ((res = spa_pod_parse_object(param, SPA_TYPE_OBJECT_ParamProfile, NULL, SPA_PARAM_PROFILE_index, SPA_POD_OPT_Int(&idx), SPA_PARAM_PROFILE_name, SPA_POD_OPT_String(&name), SPA_PARAM_PROFILE_save, SPA_POD_OPT_Bool(&save))) < 0) { spa_log_warn(this->log, "can't parse profile"); spa_debug_log_pod(this->log, SPA_LOG_LEVEL_DEBUG, 0, NULL, param); return res; } if (idx == SPA_ID_INVALID && name == NULL) { spa_log_warn(this->log, "profile needs name or index"); return -EINVAL; } if (idx == SPA_ID_INVALID) idx = find_profile_by_name(this->card, name); if (idx == SPA_ID_INVALID) { spa_log_warn(this->log, "unknown profile %s", name); return -EINVAL; } acp_card_set_profile(this->card, idx, save ? ACP_PROFILE_SAVE : 0); emit_info(this, false); break; } case SPA_PARAM_Route: { uint32_t idx = SPA_ID_INVALID, device; const char *name = NULL; struct spa_pod *props = NULL; struct acp_device *dev; bool save = false; if (param == NULL) return -EINVAL; if ((res = spa_pod_parse_object(param, SPA_TYPE_OBJECT_ParamRoute, NULL, SPA_PARAM_ROUTE_index, SPA_POD_OPT_Int(&idx), SPA_PARAM_ROUTE_name, SPA_POD_OPT_String(&name), SPA_PARAM_ROUTE_device, SPA_POD_Int(&device), SPA_PARAM_ROUTE_props, SPA_POD_OPT_Pod(&props), SPA_PARAM_ROUTE_save, SPA_POD_OPT_Bool(&save))) < 0) { spa_log_warn(this->log, "can't parse route"); spa_debug_log_pod(this->log, SPA_LOG_LEVEL_DEBUG, 0, NULL, param); return res; } if (device >= this->card->n_devices) return -EINVAL; if (idx == SPA_ID_INVALID && name == NULL) return -EINVAL; dev = this->card->devices[device]; if (SPA_FLAG_IS_SET(dev->flags, ACP_DEVICE_HIDDEN)) return -EINVAL; if (idx == SPA_ID_INVALID) idx = find_route_by_name(this->card, name); if (idx == SPA_ID_INVALID) return -EINVAL; if (!check_active_profile_port(this, device, idx)) return -EINVAL; acp_device_set_port(dev, idx, save ? ACP_PORT_SAVE : 0); if (props) apply_device_props(this, dev, props); emit_info(this, false); break; } default: return -ENOENT; } return 0; } static const struct spa_device_methods impl_device = { SPA_VERSION_DEVICE_METHODS, .add_listener = impl_add_listener, .sync = impl_sync, .enum_params = impl_enum_params, .set_param = impl_set_param, }; static void card_props_changed(void *data) { struct impl *this = data; spa_log_info(this->log, "card properties changed"); } static bool has_device(struct acp_card_profile *pr, uint32_t index) { uint32_t i; for (i = 0; i < pr->n_devices; i++) if (pr->devices[i]->index == index) return true; return false; } static void card_profile_changed(void *data, uint32_t old_index, uint32_t new_index) { struct impl *this = data; struct acp_card *card = this->card; struct acp_card_profile *op = card->profiles[old_index]; struct acp_card_profile *np = card->profiles[new_index]; uint32_t i; spa_log_info(this->log, "card profile changed from %s to %s", op->name, np->name); for (i = 0; i < op->n_devices; i++) { uint32_t index = op->devices[i]->index; if (has_device(np, index)) continue; spa_device_emit_object_info(&this->hooks, index, NULL); } for (i = 0; i < np->n_devices; i++) { emit_node(this, np->devices[i]); } setup_sources(this); this->info.change_mask |= SPA_DEVICE_CHANGE_MASK_PARAMS; this->params[IDX_Profile].user++; this->params[IDX_Route].user++; this->params[IDX_EnumRoute].user++; } static void card_profile_available(void *data, uint32_t index, enum acp_available old, enum acp_available available) { struct impl *this = data; struct acp_card *card = this->card; struct acp_card_profile *p = card->profiles[index]; spa_log_info(this->log, "card profile %s available %s -> %s", p->name, acp_available_str(old), acp_available_str(available)); this->info.change_mask |= SPA_DEVICE_CHANGE_MASK_PARAMS; this->params[IDX_EnumProfile].user++; this->params[IDX_Profile].user++; if (this->props.auto_profile) { uint32_t best = acp_card_find_best_profile_index(card, NULL); acp_card_set_profile(card, best, 0); } } static void card_port_changed(void *data, uint32_t old_index, uint32_t new_index) { struct impl *this = data; struct acp_card *card = this->card; struct acp_port *op = card->ports[old_index]; struct acp_port *np = card->ports[new_index]; spa_log_info(this->log, "card port changed from %s to %s", op->name, np->name); this->info.change_mask |= SPA_DEVICE_CHANGE_MASK_PARAMS; this->params[IDX_Route].user++; } static void card_port_available(void *data, uint32_t index, enum acp_available old, enum acp_available available) { struct impl *this = data; struct acp_card *card = this->card; struct acp_port *p = card->ports[index]; spa_log_info(this->log, "card port %s available %s -> %s", p->name, acp_available_str(old), acp_available_str(available)); this->info.change_mask |= SPA_DEVICE_CHANGE_MASK_PARAMS; this->params[IDX_EnumRoute].user++; this->params[IDX_Route].user++; if (this->props.auto_port) { uint32_t i; for (i = 0; i < p->n_devices; i++) { struct acp_device *d = p->devices[i]; uint32_t best; if (!(d->flags & ACP_DEVICE_ACTIVE)) continue; best = acp_device_find_best_port_index(d, NULL); acp_device_set_port(d, best, 0); } } } static void on_volume_changed(void *data, struct acp_device *dev) { struct impl *this = data; struct spa_event *event; uint8_t buffer[4096]; struct spa_pod_builder b = { 0 }; struct spa_pod_frame f[1]; uint32_t n_volume = dev->format.channels; float volume[n_volume]; float soft_volume[n_volume]; spa_log_info(this->log, "device %s volume changed", dev->name); this->info.change_mask |= SPA_DEVICE_CHANGE_MASK_PARAMS; this->params[IDX_Route].user++; spa_zero(volume); spa_zero(soft_volume); acp_device_get_volume(dev, volume, n_volume); acp_device_get_soft_volume(dev, soft_volume, n_volume); spa_pod_builder_init(&b, buffer, sizeof(buffer)); spa_pod_builder_push_object(&b, &f[0], SPA_TYPE_EVENT_Device, SPA_DEVICE_EVENT_ObjectConfig); spa_pod_builder_prop(&b, SPA_EVENT_DEVICE_Object, 0); spa_pod_builder_int(&b, dev->index); spa_pod_builder_prop(&b, SPA_EVENT_DEVICE_Props, 0); spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_Props, SPA_EVENT_DEVICE_Props, SPA_PROP_channelVolumes, SPA_POD_Array(sizeof(float), SPA_TYPE_Float, n_volume, volume), SPA_PROP_channelMap, SPA_POD_Array(sizeof(uint32_t), SPA_TYPE_Id, dev->format.channels, dev->format.map), SPA_PROP_softVolumes, SPA_POD_Array(sizeof(float), SPA_TYPE_Float, n_volume, soft_volume)); event = spa_pod_builder_pop(&b, &f[0]); spa_device_emit_event(&this->hooks, event); } static void on_mute_changed(void *data, struct acp_device *dev) { struct impl *this = data; struct spa_event *event; uint8_t buffer[4096]; struct spa_pod_builder b = { 0 }; struct spa_pod_frame f[1]; bool mute; spa_log_info(this->log, "device %s mute changed", dev->name); this->info.change_mask |= SPA_DEVICE_CHANGE_MASK_PARAMS; this->params[IDX_Route].user++; acp_device_get_mute(dev, &mute); spa_pod_builder_init(&b, buffer, sizeof(buffer)); spa_pod_builder_push_object(&b, &f[0], SPA_TYPE_EVENT_Device, SPA_DEVICE_EVENT_ObjectConfig); spa_pod_builder_prop(&b, SPA_EVENT_DEVICE_Object, 0); spa_pod_builder_int(&b, dev->index); spa_pod_builder_prop(&b, SPA_EVENT_DEVICE_Props, 0); spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_Props, SPA_EVENT_DEVICE_Props, SPA_PROP_mute, SPA_POD_Bool(mute), SPA_PROP_softMute, SPA_POD_Bool(mute)); event = spa_pod_builder_pop(&b, &f[0]); spa_device_emit_event(&this->hooks, event); } static const struct acp_card_events card_events = { ACP_VERSION_CARD_EVENTS, .props_changed = card_props_changed, .profile_changed = card_profile_changed, .profile_available = card_profile_available, .port_changed = card_port_changed, .port_available = card_port_available, .volume_changed = on_volume_changed, .mute_changed = on_mute_changed, }; static int impl_get_interface(struct spa_handle *handle, const char *type, void **interface) { struct impl *this; spa_return_val_if_fail(handle != NULL, -EINVAL); spa_return_val_if_fail(interface != NULL, -EINVAL); this = (struct impl *) handle; if (spa_streq(type, SPA_TYPE_INTERFACE_Device)) *interface = &this->device; else return -ENOENT; return 0; } static SPA_PRINTF_FUNC(6,0) void impl_acp_log_func(void *data, int level, const char *file, int line, const char *func, const char *fmt, va_list arg) { struct spa_log *log = data; spa_log_logv(log, (enum spa_log_level)level, file, line, func, fmt, arg); } static int impl_clear(struct spa_handle *handle) { struct impl *this = (struct impl *) handle; remove_sources(this); if (this->card) { acp_card_destroy(this->card); this->card = NULL; } return 0; } static size_t impl_get_size(const struct spa_handle_factory *factory, const struct spa_dict *params) { return sizeof(struct impl); } static int impl_init(const struct spa_handle_factory *factory, struct spa_handle *handle, const struct spa_dict *info, const struct spa_support *support, uint32_t n_support) { struct impl *this; const char *str; struct acp_dict_item *items = NULL; const struct spa_dict_item *it; uint32_t n_items = 0; spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(handle != NULL, -EINVAL); handle->get_interface = impl_get_interface; handle->clear = impl_clear; this = (struct impl *) handle; this->log = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log); alsa_log_topic_init(this->log); this->loop = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Loop); acp_i18n = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_I18N); if (this->loop == NULL) { spa_log_error(this->log, "a Loop interface is needed"); return -EINVAL; } acp_set_log_func(impl_acp_log_func, this->log); acp_set_log_level(6); this->device.iface = SPA_INTERFACE_INIT( SPA_TYPE_INTERFACE_Device, SPA_VERSION_DEVICE, &impl_device, this); spa_hook_list_init(&this->hooks); reset_props(&this->props); if (info) { if ((str = spa_dict_lookup(info, SPA_KEY_API_ALSA_PATH)) != NULL) snprintf(this->props.device, sizeof(this->props.device), "%s", str); if ((str = spa_dict_lookup(info, "api.acp.auto-port")) != NULL) this->props.auto_port = spa_atob(str); if ((str = spa_dict_lookup(info, "api.acp.auto-profile")) != NULL) this->props.auto_profile = spa_atob(str); items = alloca((info->n_items) * sizeof(*items)); spa_dict_for_each(it, info) items[n_items++] = ACP_DICT_ITEM_INIT(it->key, it->value); } spa_log_debug(this->log, "probe card %s", this->props.device); if ((str = strchr(this->props.device, ':')) == NULL) return -EINVAL; this->card = acp_card_new(atoi(str+1), &ACP_DICT_INIT(items, n_items)); if (this->card == NULL) return -errno; setup_sources(this); acp_card_add_listener(this->card, &card_events, this); this->info = SPA_DEVICE_INFO_INIT(); this->info_all = SPA_DEVICE_CHANGE_MASK_PROPS | SPA_DEVICE_CHANGE_MASK_PARAMS; this->params[IDX_EnumProfile] = SPA_PARAM_INFO(SPA_PARAM_EnumProfile, SPA_PARAM_INFO_READ); this->params[IDX_Profile] = SPA_PARAM_INFO(SPA_PARAM_Profile, SPA_PARAM_INFO_READWRITE); this->params[IDX_EnumRoute] = SPA_PARAM_INFO(SPA_PARAM_EnumRoute, SPA_PARAM_INFO_READ); this->params[IDX_Route] = SPA_PARAM_INFO(SPA_PARAM_Route, SPA_PARAM_INFO_READWRITE); this->info.params = this->params; this->info.n_params = 4; return 0; } static const struct spa_interface_info impl_interfaces[] = { {SPA_TYPE_INTERFACE_Device,}, }; static int impl_enum_interface_info(const struct spa_handle_factory *factory, const struct spa_interface_info **info, uint32_t *index) { spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(info != NULL, -EINVAL); spa_return_val_if_fail(index != NULL, -EINVAL); if (*index >= SPA_N_ELEMENTS(impl_interfaces)) return 0; *info = &impl_interfaces[(*index)++]; return 1; } const struct spa_handle_factory spa_alsa_acp_device_factory = { SPA_VERSION_HANDLE_FACTORY, SPA_NAME_API_ALSA_ACP_DEVICE, NULL, impl_get_size, impl_init, impl_enum_interface_info, }; alsa-compress-offload-device.c000066400000000000000000000425421511204443500313740ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/* Spa ALSA Compress-Offload device */ /* SPDX-FileCopyrightText: Copyright @ 2023 Carlos Rafael Giani */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "compress-offload-api-util.h" #include "alsa.h" static const char default_device[] = "hw:0"; struct props { char device[64]; unsigned int card_nr; }; static void reset_props(struct props *props) { strncpy(props->device, default_device, 64); props->card_nr = 0; } struct impl { struct spa_handle handle; struct spa_device device; struct spa_log *log; uint32_t info_all; struct spa_device_info device_info; #define IDX_EnumProfile 0 #define IDX_Profile 1 struct spa_param_info params[2]; struct spa_hook_list hooks; struct props props; uint32_t n_nodes; uint32_t n_capture; uint32_t n_playback; uint32_t profile; }; #define ADD_DICT_ITEM(key, value) do { items[n_items++] = SPA_DICT_ITEM_INIT(key, value); } while (0) static void emit_node(struct impl *this, const char *device_node, unsigned int device_nr, enum spa_compress_offload_direction direction, snd_ctl_card_info_t *cardinfo, uint32_t id) { struct spa_dict_item items[5]; uint32_t n_items = 0; char alsa_path[128], path[180]; char node_name[200]; char node_desc[200]; struct spa_device_object_info info; const char *stream; spa_log_debug(this->log, "emitting node info for device %s (card nr %u device nr %u)", device_node, this->props.card_nr, device_nr); info = SPA_DEVICE_OBJECT_INFO_INIT(); info.type = SPA_TYPE_INTERFACE_Node; if (direction == SPA_COMPRESS_OFFLOAD_DIRECTION_PLAYBACK) { stream = "playback"; info.factory_name = SPA_NAME_API_ALSA_COMPRESS_OFFLOAD_SINK; } else { stream = "capture"; /* TODO: This is not yet implemented, because getting Compress-Offload * hardware that can capture audio is difficult to do. The only hardware * known is the Wolfson ADSP; the only driver in the kernel that exposes * Compress-Offload capture devices is the one for that hardware. */ spa_assert_not_reached(); } info.change_mask = SPA_DEVICE_OBJECT_CHANGE_MASK_PROPS; snprintf(alsa_path, sizeof(alsa_path), "%s,%u", this->props.device, device_nr); snprintf(path, sizeof(path), "alsa:compressed:%s:%u:%s", snd_ctl_card_info_get_id(cardinfo), device_nr, stream); snprintf(node_name, sizeof(node_name), "comprC%uD%u", this->props.card_nr, device_nr); snprintf(node_desc, sizeof(node_desc), "Compress-Offload sink node (ALSA card %u device %u)", this->props.card_nr, device_nr); ADD_DICT_ITEM(SPA_KEY_NODE_NAME, node_name); ADD_DICT_ITEM(SPA_KEY_NODE_DESCRIPTION, node_desc); ADD_DICT_ITEM(SPA_KEY_OBJECT_PATH, path); ADD_DICT_ITEM(SPA_KEY_API_ALSA_PATH, alsa_path); /* NOTE: Set alsa.name, since session managers look for this, or for * SPA_KEY_API_ALSA_PCM_NAME, or other items. The best fit in this * case seems to be alsa.name, since SPA_KEY_API_ALSA_PCM_NAME is * PCM specific, as the name suggests. If none of these items are * provided, session managers may not work properly. WirePlumber's * alsa.lua script looks for these for example. * And, since we have no good way of getting a name, just reuse * the alsa_path here. */ ADD_DICT_ITEM("alsa.name", alsa_path); info.props = &SPA_DICT_INIT(items, n_items); spa_log_debug(this->log, "node information:"); spa_debug_dict(2, info.props); spa_device_emit_object_info(&this->hooks, id, &info); } static int set_profile(struct impl *this, uint32_t id) { int ret = 0; uint32_t i, n_cap, n_play; char prefix[32]; int prefix_length; struct dirent *entry; DIR *snd_dir = NULL; snd_ctl_t *ctl_handle = NULL; snd_ctl_card_info_t *cardinfo; spa_log_debug(this->log, "enumerate Compress-Offload nodes for card %s; profile: %d", this->props.device, id); if ((ret = snd_ctl_open(&ctl_handle, this->props.device, 0)) < 0) { spa_log_error(this->log, "can't open control for card %s: %s", this->props.device, snd_strerror(ret)); goto finish; } this->profile = id; snd_ctl_card_info_alloca(&cardinfo); if ((ret = snd_ctl_card_info(ctl_handle, cardinfo)) < 0) { spa_log_error(this->log, "error card info: %s", snd_strerror(ret)); goto finish; } /* Clear any previous node object info. */ for (i = 0; i < this->n_nodes; i++) spa_device_emit_object_info(&this->hooks, i, NULL); this->n_nodes = this->n_capture = this->n_playback = 0; /* Profile ID 0 is the "off" profile, that is, the profile where the device * is "disabled". To implement such a disabled state, simply exit here without * adding any nodes after we removed any existing one (see above). */ if (id == 0) { spa_log_debug(this->log, "\"Off\" profile selected - exiting without " "creating any nodes after all previous ones were removed"); goto finish; } spa_scnprintf(prefix, sizeof(prefix), "comprC%uD", this->props.card_nr); prefix_length = strlen(prefix); /* There is no API to enumerate all Compress-Offload devices, so we have * to stick to walking through the /dev/snd directory entries and looking * for device nodes that match the comprCD prefix. */ snd_dir = opendir("/dev/snd"); if (snd_dir == NULL) goto errno_error; i = 0; i = n_cap = n_play = 0; while ((errno = 0, entry = readdir(snd_dir)) != NULL) { long long device_nr; enum spa_compress_offload_direction direction; if (!(entry->d_type == DT_CHR && spa_strstartswith(entry->d_name, prefix))) continue; /* Parse the device number from the device filename. We know that the filename * is always structured like this: comprCD * We consider "comprCD" to form the "prefix" here. Right after * that prefix, the device number can be parsed, so skip the prefix. */ device_nr = strtol(entry->d_name + prefix_length, NULL, 10); if ((device_nr < 0) || (device_nr > UINT_MAX)) { spa_log_warn(this->log, "device %s contains unusable device number; " "skipping", entry->d_name); continue; } if (get_compress_offload_device_direction(this->props.card_nr, device_nr, this->log, &direction) < 0) goto finish; switch (direction) { case SPA_COMPRESS_OFFLOAD_DIRECTION_PLAYBACK: n_play++; emit_node(this, entry->d_name, device_nr, direction, cardinfo, i++); break; case SPA_COMPRESS_OFFLOAD_DIRECTION_CAPTURE: /* TODO: Disabled for now. See the TODO in emit_node() for details. */ #if 0 n_cap++; emit_node(this, entry->d_name, device_nr, direction, cardinfo, i++); #endif break; } } this->n_capture = n_cap; this->n_playback = n_play; this->n_nodes = i; this->device_info.change_mask |= SPA_DEVICE_CHANGE_MASK_PARAMS; this->params[IDX_Profile].user++; finish: if (snd_dir != NULL) closedir(snd_dir); if (ctl_handle != NULL) snd_ctl_close(ctl_handle); return ret; errno_error: ret = -errno; goto finish; } static int emit_info(struct impl *this, bool full) { int err = 0; struct spa_dict_item items[20]; uint32_t n_items = 0; char path[128]; char device_name[200]; char device_desc[200]; if (full) this->device_info.change_mask = this->info_all; if (this->device_info.change_mask) { snd_ctl_card_info_t *info; snd_ctl_t *ctl_hndl; spa_log_debug(this->log, "open card %s", this->props.device); if ((err = snd_ctl_open(&ctl_hndl, this->props.device, 0)) < 0) { spa_log_error(this->log, "can't open control for card %s: %s", this->props.device, snd_strerror(err)); return err; } snd_ctl_card_info_alloca(&info); err = snd_ctl_card_info(ctl_hndl, info); spa_log_debug(this->log, "close card %s", this->props.device); snd_ctl_close(ctl_hndl); if (err < 0) { spa_log_error(this->log, "error hardware info: %s", snd_strerror(err)); return err; } snprintf(path, sizeof(path), "alsa:compressed:%s", snd_ctl_card_info_get_id(info)); snprintf(device_name, sizeof(device_name), "comprC%u", this->props.card_nr); snprintf(device_desc, sizeof(device_desc), "Compress-Offload device (ALSA card %u)", this->props.card_nr); ADD_DICT_ITEM(SPA_KEY_OBJECT_PATH, path); ADD_DICT_ITEM(SPA_KEY_DEVICE_API, "alsa:compressed"); ADD_DICT_ITEM(SPA_KEY_DEVICE_NICK, "alsa:compressed"); ADD_DICT_ITEM(SPA_KEY_DEVICE_NAME, device_name); ADD_DICT_ITEM(SPA_KEY_DEVICE_DESCRIPTION, device_desc); ADD_DICT_ITEM(SPA_KEY_MEDIA_CLASS, "Audio/Device"); ADD_DICT_ITEM(SPA_KEY_API_ALSA_PATH, (char *)this->props.device); ADD_DICT_ITEM(SPA_KEY_API_ALSA_CARD_ID, snd_ctl_card_info_get_id(info)); ADD_DICT_ITEM(SPA_KEY_API_ALSA_CARD_COMPONENTS, snd_ctl_card_info_get_components(info)); ADD_DICT_ITEM(SPA_KEY_API_ALSA_CARD_DRIVER, snd_ctl_card_info_get_driver(info)); ADD_DICT_ITEM(SPA_KEY_API_ALSA_CARD_NAME, snd_ctl_card_info_get_name(info)); ADD_DICT_ITEM(SPA_KEY_API_ALSA_CARD_LONGNAME, snd_ctl_card_info_get_longname(info)); ADD_DICT_ITEM(SPA_KEY_API_ALSA_CARD_MIXERNAME, snd_ctl_card_info_get_mixername(info)); this->device_info.props = &SPA_DICT_INIT(items, n_items); if (this->device_info.change_mask & SPA_DEVICE_CHANGE_MASK_PARAMS) { SPA_FOR_EACH_ELEMENT_VAR(this->params, p) { if (p->user > 0) { p->flags ^= SPA_PARAM_INFO_SERIAL; p->user = 0; } } } spa_device_emit_info(&this->hooks, &this->device_info); this->device_info.change_mask = 0; } return 0; } static int impl_add_listener(void *object, struct spa_hook *listener, const struct spa_device_events *events, void *data) { struct impl *this = object; struct spa_hook_list save; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(events != NULL, -EINVAL); spa_hook_list_isolate(&this->hooks, &save, listener, events, data); if (events->info || events->object_info) emit_info(this, true); spa_hook_list_join(&this->hooks, &save); return 0; } static int impl_sync(void *object, int seq) { struct impl *this = object; spa_return_val_if_fail(this != NULL, -EINVAL); spa_device_emit_result(&this->hooks, seq, 0, 0, NULL); return 0; } static struct spa_pod *build_profile(struct impl *this, struct spa_pod_builder *b, uint32_t id, uint32_t index) { struct spa_pod_frame f[2]; const char *name, *desc; switch (index) { case 0: name = "off"; desc = "Off"; break; case 1: name = "on"; desc = "On"; break; default: errno = EINVAL; return NULL; } spa_pod_builder_push_object(b, &f[0], SPA_TYPE_OBJECT_ParamProfile, id); spa_pod_builder_add(b, SPA_PARAM_PROFILE_index, SPA_POD_Int(index), SPA_PARAM_PROFILE_name, SPA_POD_String(name), SPA_PARAM_PROFILE_description, SPA_POD_String(desc), 0); if (index == 1) { spa_pod_builder_prop(b, SPA_PARAM_PROFILE_classes, 0); spa_pod_builder_push_struct(b, &f[1]); if (this->n_capture) { spa_pod_builder_add_struct(b, SPA_POD_String("Audio/Source"), SPA_POD_Int(this->n_capture)); } if (this->n_playback) { spa_pod_builder_add_struct(b, SPA_POD_String("Audio/Sink"), SPA_POD_Int(this->n_playback)); } spa_pod_builder_pop(b, &f[1]); } return spa_pod_builder_pop(b, &f[0]); } static int impl_enum_params(void *object, int seq, uint32_t id, uint32_t start, uint32_t num, const struct spa_pod *filter) { struct impl *this = object; struct spa_pod *param; struct spa_pod_builder b = { 0 }; uint8_t buffer[1024]; struct spa_result_device_params result; uint32_t count = 0; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(num != 0, -EINVAL); result.id = id; result.next = start; next: result.index = result.next++; spa_pod_builder_init(&b, buffer, sizeof(buffer)); switch (id) { case SPA_PARAM_EnumProfile: { switch (result.index) { case 0: case 1: param = build_profile(this, &b, id, result.index); break; default: return 0; } break; } case SPA_PARAM_Profile: { switch (result.index) { case 0: param = build_profile(this, &b, id, this->profile); break; default: return 0; } break; } default: return -ENOENT; } if (spa_pod_filter(&b, &result.param, param, filter) < 0) goto next; spa_device_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_DEVICE_PARAMS, &result); if (++count != num) goto next; return 0; } static uint32_t find_profile_by_name(const char *name) { if (spa_streq(name, "off")) return 0; else if (spa_streq(name, "on")) return 1; return SPA_ID_INVALID; } static int impl_set_param(void *object, uint32_t id, uint32_t flags, const struct spa_pod *param) { struct impl *this = object; int res; spa_return_val_if_fail(this != NULL, -EINVAL); switch (id) { case SPA_PARAM_Profile: { uint32_t idx = SPA_ID_INVALID; const char *name = NULL; if ((res = spa_pod_parse_object(param, SPA_TYPE_OBJECT_ParamProfile, NULL, SPA_PARAM_PROFILE_index, SPA_POD_OPT_Int(&idx), SPA_PARAM_PROFILE_name, SPA_POD_OPT_String(&name))) < 0) { spa_log_warn(this->log, "can't parse profile"); spa_debug_log_pod(this->log, SPA_LOG_LEVEL_DEBUG, 0, NULL, param); return res; } if (idx == SPA_ID_INVALID && name == NULL) return -EINVAL; if (idx == SPA_ID_INVALID) idx = find_profile_by_name(name); if (idx == SPA_ID_INVALID) return -EINVAL; set_profile(this, idx); emit_info(this, false); break; } default: return -ENOENT; } return 0; } static const struct spa_device_methods impl_device = { SPA_VERSION_DEVICE_METHODS, .add_listener = impl_add_listener, .sync = impl_sync, .enum_params = impl_enum_params, .set_param = impl_set_param, }; static int impl_get_interface(struct spa_handle *handle, const char *type, void **interface) { struct impl *this; spa_return_val_if_fail(handle != NULL, -EINVAL); spa_return_val_if_fail(interface != NULL, -EINVAL); this = (struct impl *) handle; if (spa_streq(type, SPA_TYPE_INTERFACE_Device)) *interface = &this->device; else return -ENOENT; return 0; } static int impl_clear(struct spa_handle *handle) { return 0; } static size_t impl_get_size(const struct spa_handle_factory *factory, const struct spa_dict *params) { return sizeof(struct impl); } static int impl_init(const struct spa_handle_factory *factory, struct spa_handle *handle, const struct spa_dict *info, const struct spa_support *support, uint32_t n_support) { struct impl *this; spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(handle != NULL, -EINVAL); handle->get_interface = impl_get_interface; handle->clear = impl_clear; this = (struct impl *) handle; this->log = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log); alsa_log_topic_init(this->log); this->device.iface = SPA_INTERFACE_INIT( SPA_TYPE_INTERFACE_Device, SPA_VERSION_DEVICE, &impl_device, this); spa_hook_list_init(&this->hooks); reset_props(&this->props); snd_config_update_free_global(); if (info) { uint32_t i; for (i = 0; info && i < info->n_items; i++) { const char *k = info->items[i].key; const char *s = info->items[i].value; if (spa_streq(k, SPA_KEY_API_ALSA_PATH)) { snprintf(this->props.device, 64, "%s", s); spa_log_debug(this->log, "using ALSA path \"%s\"", this->props.device); } else if (spa_streq(k, SPA_KEY_API_ALSA_CARD)) { long long card_nr = strtol(s, NULL, 10); if ((card_nr >= 0) && (card_nr <= UINT_MAX)) { this->props.card_nr = card_nr; spa_log_debug(this->log, "using ALSA card number %u", this->props.card_nr); } else spa_log_warn(this->log, "invalid ALSA card number \"%s\"; using default", s); } } } this->device_info = SPA_DEVICE_INFO_INIT(); this->info_all = SPA_DEVICE_CHANGE_MASK_PROPS | SPA_DEVICE_CHANGE_MASK_PARAMS; this->params[IDX_EnumProfile] = SPA_PARAM_INFO(SPA_PARAM_EnumProfile, SPA_PARAM_INFO_READ); this->params[IDX_Profile] = SPA_PARAM_INFO(SPA_PARAM_Profile, SPA_PARAM_INFO_READWRITE); this->device_info.params = this->params; this->device_info.n_params = SPA_N_ELEMENTS(this->params); return 0; } static const struct spa_interface_info impl_interfaces[] = { {SPA_TYPE_INTERFACE_Device,}, }; static int impl_enum_interface_info(const struct spa_handle_factory *factory, const struct spa_interface_info **info, uint32_t *index) { spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(info != NULL, -EINVAL); spa_return_val_if_fail(index != NULL, -EINVAL); if (*index >= SPA_N_ELEMENTS(impl_interfaces)) return 0; *info = &impl_interfaces[(*index)++]; return 1; } const struct spa_handle_factory spa_alsa_compress_offload_device_factory = { SPA_VERSION_HANDLE_FACTORY, SPA_NAME_API_ALSA_COMPRESS_OFFLOAD_DEVICE, NULL, impl_get_size, impl_init, impl_enum_interface_info, }; alsa-compress-offload-sink.c000066400000000000000000001660021511204443500310770ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/* Spa ALSA Compress-Offload sink */ /* SPDX-FileCopyrightText: Copyright © 2022 Wim Taymans */ /* SPDX-FileCopyrightText: Copyright © 2022 Asymptotic Inc. */ /* SPDX-FileCopyrightText: Copyright @ 2023 Carlos Rafael Giani */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "alsa.h" #include "compress-offload-api.h" /* * This creates a PipeWire sink node which uses the ALSA Compress-Offload API * for writing compressed data ike MP3, FLAC etc. to an DSP that can handle * such data directly. * * These show up under /dev/snd like "comprCxDx", as opposed to regular * ALSA PCM devices. This sink node still refers to those devices in * regular ALSA fashion as "hw:x,y" devices, where x = card number and * y = device number. For example, "hw:4,7" maps to /dev/snd/comprC4D7. * * ## Example configuration *\code{.unparsed} * context.objects = [ * { factory = adapter * args = { * factory.name = "api.alsa.compress.offload.sink" * node.name = "Compress-Offload-Sink" * node.description = "Audio sink for compressed audio" * media.class = "Audio/Sink" * api.alsa.path = "hw:0,3" * node.param.PortConfig = { * direction = Input * mode = passthrough * } * } * } *] *\endcode * * TODO: * - DLL for adjusting driver timer intervals to match the device timestamps in on_driver_timeout() * - Automatic loading using alsa-udev */ /* FLAC support has been present in kernel headers older than 5.5. * However, those older versions don't support FLAC decoding params. */ #if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 5, 0) #define COMPRESS_OFFLOAD_HAS_FLAC_DEC_PARAMS #endif /* Prior to kernel 5.7, WMA9 Pro/Lossless and WMA10 Lossless * codec profiles were missing. * As for ALAC and Monkey's Audio (APE), those are new in 5.7. */ #if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 7, 0) #define COMPRESS_OFFLOAD_SUPPORTS_WMA9_PRO #define COMPRESS_OFFLOAD_SUPPORTS_WMA9_LOSSLESS #define COMPRESS_OFFLOAD_SUPPORTS_WMA10_LOSSLESS #define COMPRESS_OFFLOAD_SUPPORTS_ALAC #define COMPRESS_OFFLOAD_SUPPORTS_APE #endif #define CHECK_PORT(this, d, p) (((d) == SPA_DIRECTION_INPUT) && ((p) == 0)) #define MAX_BUFFERS (32) #define BUFFER_FLAG_AVAILABLE_FOR_NEW_DATA (1 << 0) /* Information about a buffer that got allocated by the PW graph. */ struct buffer { uint32_t id; uint32_t flags; struct spa_buffer *buf; struct spa_list link; }; /* Node properties. These are accessible through SPA_PARAM_Props. */ struct props { /* The'"hw::" device. */ char device[128]; /* These are the card and device numbers from the * from the "hw::" device.*/ int card_nr; int device_nr; bool device_name_set; }; /* Main sink node structure. */ struct impl { /* Basic states */ struct spa_handle handle; struct spa_node node; struct spa_log *log; struct spa_loop *data_loop; struct spa_system *data_system; struct spa_hook_list hooks; struct spa_callbacks callbacks; struct props props; bool have_format; struct spa_audio_info current_audio_info; /* This is set to true when the SPA_NODE_COMMAND_Start is * received, and set back to false when SPA_NODE_COMMAND_Pause * or SPA_NODE_COMMAND_Suspend is received. */ bool started; bool freewheel; /* SPA buffer states */ struct buffer buffers[MAX_BUFFERS]; unsigned int n_buffers; struct spa_list queued_output_buffers; size_t offset_within_oldest_output_buffer; /* Driver and cycle specific states */ int driver_timerfd; struct spa_source driver_timer_source; uint64_t next_driver_time; bool following; /* Duration and rate of one graph cycle. * The duration equals the quantum size. */ uint32_t cycle_duration; int cycle_rate; /* Node specific states */ uint64_t node_info_all; struct spa_node_info node_info; #define NODE_PropInfo 0 #define NODE_Props 1 #define NODE_IO 2 #define NODE_EnumPortConfig 3 #define N_NODE_PARAMS 4 struct spa_param_info node_params[N_NODE_PARAMS]; struct spa_io_clock *node_clock_io; struct spa_io_position *node_position_io; /* Port specific states */ uint64_t port_info_all; struct spa_port_info port_info; #define PORT_EnumFormat 0 #define PORT_Format 1 #define PORT_IO 2 #define PORT_Buffers 3 #define N_PORT_PARAMS 4 struct spa_param_info port_params[N_PORT_PARAMS]; struct spa_io_buffers *port_buffers_io; /* Compress-Offload specific states */ struct compress_offload_api_context *device_context; struct snd_codec audio_codec_info; bool device_started; uint32_t min_fragment_size; uint32_t max_fragment_size; uint32_t min_num_fragments; uint32_t max_num_fragments; uint32_t configured_fragment_size; uint32_t configured_num_fragments; bool device_is_paused; }; /* Compress-Offload device and audio codec functions */ static int init_audio_codec_info(struct impl *this, struct spa_audio_info *info, uint32_t *out_rate); static int device_open(struct impl *this); static void device_close(struct impl *this); static int device_start(struct impl *this); static int device_pause(struct impl *this); static int device_resume(struct impl *this); static int device_write(struct impl *this, const void *data, uint32_t size); /* Driver timer functions */ static int set_driver_timeout(struct impl *this, uint64_t time); static int configure_driver_timer(struct impl *this); static int start_driver_timer(struct impl *this); static void stop_driver_timer(struct impl *this); static void on_driver_timeout(struct spa_source *source); static inline void check_position_and_clock_config(struct impl *this); static void reevaluate_following_state(struct impl *this); static void reevaluate_freewheel_state(struct impl *this); /* Miscellaneous functions */ static int parse_device(struct impl *this); static void reset_props(struct props *props); static void clear_buffers(struct impl *this); static inline bool is_following(struct impl *this); static int do_start(struct impl *this); static void do_stop(struct impl *this); static int write_queued_output_buffers(struct impl *this); /* Node and port functions */ static void emit_node_info(struct impl *this, bool full); static void emit_port_info(struct impl *this, bool full); static int impl_node_add_listener(void *object, struct spa_hook *listener, const struct spa_node_events *events, void *data); static int impl_node_set_callbacks(void *object, const struct spa_node_callbacks *callbacks, void *data); static int impl_node_sync(void *object, int seq); static int impl_node_enum_params(void *object, int seq, uint32_t id, uint32_t start, uint32_t num, const struct spa_pod *filter); static int impl_node_set_param(void *object, uint32_t id, uint32_t flags, const struct spa_pod *param); static int impl_node_set_io(void *object, uint32_t id, void *data, size_t size); static int impl_node_send_command(void *object, const struct spa_command *command); static int impl_node_add_port(void *object, enum spa_direction direction, uint32_t port_id, const struct spa_dict *props); static int impl_node_remove_port(void *object, enum spa_direction direction, uint32_t port_id); static int port_enum_formats(struct impl *this, int seq, uint32_t start, uint32_t num, const struct spa_pod *filter, struct spa_pod_builder *b); static int impl_port_enum_params(void *object, int seq, enum spa_direction direction, uint32_t port_id, uint32_t id, uint32_t start, uint32_t num, const struct spa_pod *filter); static int port_set_format(void *object, enum spa_direction direction, uint32_t port_id, uint32_t flags, const struct spa_pod *format); static int impl_port_set_param(void *object, enum spa_direction direction, uint32_t port_id, uint32_t id, uint32_t flags, const struct spa_pod *param); static int impl_port_use_buffers(void *object, enum spa_direction direction, uint32_t port_id, uint32_t flags, struct spa_buffer **buffers, uint32_t n_buffers); static int impl_port_set_io(void *object, enum spa_direction direction, uint32_t port_id, uint32_t id, void *data, size_t size); static int impl_port_reuse_buffer(void *object, uint32_t port_id, uint32_t buffer_id); static int impl_node_process(void *object); /* Compress-Offload device and audio codec functions */ struct known_codec_info { uint32_t codec_id; uint32_t media_subtype; const char *name; }; static struct known_codec_info known_codecs[] = { { SND_AUDIOCODEC_VORBIS, SPA_MEDIA_SUBTYPE_vorbis, "Ogg Vorbis" }, { SND_AUDIOCODEC_MP3, SPA_MEDIA_SUBTYPE_mp3, "MP3" }, { SND_AUDIOCODEC_AAC, SPA_MEDIA_SUBTYPE_aac, "AAC" }, { SND_AUDIOCODEC_FLAC, SPA_MEDIA_SUBTYPE_flac, "FLAC" }, { SND_AUDIOCODEC_WMA, SPA_MEDIA_SUBTYPE_wma, "WMA" }, #ifdef COMPRESS_OFFLOAD_SUPPORTS_ALAC { SND_AUDIOCODEC_ALAC, SPA_MEDIA_SUBTYPE_alac, "ALAC" }, #endif #ifdef COMPRESS_OFFLOAD_SUPPORTS_APE { SND_AUDIOCODEC_APE, SPA_MEDIA_SUBTYPE_ape, "Monkey's Audio (APE)" }, #endif { SND_AUDIOCODEC_REAL, SPA_MEDIA_SUBTYPE_ra, "Real Audio" }, { SND_AUDIOCODEC_AMRWB, SPA_MEDIA_SUBTYPE_amr, "AMR wideband" }, { SND_AUDIOCODEC_AMR, SPA_MEDIA_SUBTYPE_amr, "AMR" }, }; static int init_audio_codec_info(struct impl *this, struct spa_audio_info *info, uint32_t *out_rate) { struct snd_codec *codec; uint32_t channels, rate; const struct spa_type_info *media_subtype_info; media_subtype_info = spa_debug_type_find(spa_type_media_subtype, info->media_subtype); if (media_subtype_info == NULL) { spa_log_error(this->log, "%p: media subtype %d is unknown", this, info->media_subtype); return -ENOTSUP; } memset(&this->audio_codec_info, 0, sizeof(this->audio_codec_info)); codec = &this->audio_codec_info; switch (info->media_subtype) { case SPA_MEDIA_SUBTYPE_vorbis: codec->id = SND_AUDIOCODEC_VORBIS; rate = info->info.vorbis.rate; channels = info->info.vorbis.channels; spa_log_info(this->log, "%p: initialized codec info to Vorbis; rate: %" PRIu32 "; channels: %" PRIu32, this, rate, channels); break; case SPA_MEDIA_SUBTYPE_mp3: codec->id = SND_AUDIOCODEC_MP3; rate = info->info.mp3.rate; channels = info->info.mp3.channels; spa_log_info(this->log, "%p: initialized codec info to MP3; rate: %" PRIu32 "; channels: %" PRIu32, this, rate, channels); break; case SPA_MEDIA_SUBTYPE_aac: codec->id = SND_AUDIOCODEC_AAC; rate = info->info.aac.rate; channels = info->info.aac.channels; spa_log_info(this->log, "%p: initialized codec info to AAC; rate: %" PRIu32 "; channels: %" PRIu32, this, rate, channels); break; case SPA_MEDIA_SUBTYPE_flac: codec->id = SND_AUDIOCODEC_FLAC; #ifdef COMPRESS_OFFLOAD_HAS_FLAC_DEC_PARAMS /* The min/max block sizes are from the FLAC specification: * https://xiph.org/flac/format.html#blocking * * The smallest valid frame possible is 11, which * is why min_frame_size is set to this quantity. * * FFmpeg's flac.h specifies 8192 as the average frame size. * tinycompress' fcplay uses 4x that amount as the max frame * size to have enough headroom to be safe. * We do the same here. * * sample_size is set to 0. According to the FLAC spec, this * is OK to do if a STREAMINFO block was sent into the device * (see: https://xiph.org/flac/format.html#frame_header), and * we deal with full FLAC streams here, not just single frames. */ codec->options.flac_d.min_blk_size = 16; codec->options.flac_d.max_blk_size = 65535; codec->options.flac_d.min_frame_size = 11; codec->options.flac_d.max_frame_size = 8192 * 4; codec->options.flac_d.sample_size = 0; #endif rate = info->info.flac.rate; channels = info->info.flac.channels; spa_log_info(this->log, "%p: initialized codec info to FLAC; rate: %" PRIu32 "; channels: %" PRIu32, this, rate, channels); break; case SPA_MEDIA_SUBTYPE_wma: { const char *profile_name; codec->id = SND_AUDIOCODEC_WMA; /* WMA does not work with Compress-Offload * if codec profile is not set. */ switch (info->info.wma.profile) { case SPA_AUDIO_WMA_PROFILE_WMA9: codec->profile = SND_AUDIOPROFILE_WMA9; profile_name = "WMA9"; break; case SPA_AUDIO_WMA_PROFILE_WMA10: codec->profile = SND_AUDIOPROFILE_WMA10; profile_name = "WMA10"; break; #ifdef COMPRESS_OFFLOAD_SUPPORTS_WMA9_PRO case SPA_AUDIO_WMA_PROFILE_WMA9_PRO: codec->profile = SND_AUDIOPROFILE_WMA9_PRO; profile_name = "WMA9 Pro"; break; #endif #ifdef COMPRESS_OFFLOAD_SUPPORTS_WMA9_LOSSLESS case SPA_AUDIO_WMA_PROFILE_WMA9_LOSSLESS: codec->profile = SND_AUDIOPROFILE_WMA9_LOSSLESS; profile_name = "WMA9 Lossless"; break; #endif #ifdef COMPRESS_OFFLOAD_SUPPORTS_WMA10_LOSSLESS case SPA_AUDIO_WMA_PROFILE_WMA10_LOSSLESS: codec->profile = SND_AUDIOPROFILE_WMA10_LOSSLESS; profile_name = "WMA10 Lossless"; break; #endif default: spa_log_error(this->log, "%p: Invalid WMA profile", this); return -EINVAL; } codec->bit_rate = info->info.wma.bitrate; codec->align = info->info.wma.block_align; rate = info->info.wma.rate; channels = info->info.wma.channels; spa_log_info(this->log, "%p: initialized codec info to WMA; rate: %" PRIu32 "; channels: %" PRIu32 "; profile %s", this, rate, channels, profile_name); break; } #ifdef COMPRESS_OFFLOAD_SUPPORTS_ALAC case SPA_MEDIA_SUBTYPE_alac: codec->id = SND_AUDIOCODEC_ALAC; rate = info->info.alac.rate; channels = info->info.alac.channels; spa_log_info(this->log, "%p: initialized codec info to ALAC; rate: %" PRIu32 "; channels: %" PRIu32, this, rate, channels); break; #endif #ifdef COMPRESS_OFFLOAD_SUPPORTS_APE case SPA_MEDIA_SUBTYPE_ape: codec->id = SND_AUDIOCODEC_APE; rate = info->info.ape.rate; channels = info->info.ape.channels; spa_log_info(this->log, "%p: initialized codec info to APE (Monkey's Audio);" " rate: %" PRIu32 "; channels: %" PRIu32, this, rate, channels); break; #endif case SPA_MEDIA_SUBTYPE_ra: codec->id = SND_AUDIOCODEC_REAL; rate = info->info.ra.rate; channels = info->info.ra.channels; spa_log_info(this->log, "%p: initialized codec info to Real Audio; rate: %" PRIu32 "; channels: %" PRIu32, this, rate, channels); break; case SPA_MEDIA_SUBTYPE_amr: if (info->info.amr.band_mode == SPA_AUDIO_AMR_BAND_MODE_WB) codec->id = SND_AUDIOCODEC_AMRWB; else codec->id = SND_AUDIOCODEC_AMR; rate = info->info.amr.rate; channels = info->info.amr.channels; spa_log_info(this->log, "%p: initialized codec info to %s; rate: %" PRIu32 "; channels: %" PRIu32, this, (codec->id == SND_AUDIOCODEC_AMRWB) ? "AMR wideband" : "AMR", rate, channels); break; default: spa_log_error(this->log, "%p: media subtype %s is not supported", this, media_subtype_info->name); return -ENOTSUP; } codec->ch_in = channels; codec->ch_out = channels; codec->sample_rate = rate; codec->rate_control = 0; codec->level = 0; codec->ch_mode = 0; codec->format = 0; *out_rate = rate; return 0; } static int device_open(struct impl *this) { assert(this->device_context == NULL); spa_log_info(this->log, "%p: opening Compress-Offload device, card #%d device #%d", this, this->props.card_nr, this->props.device_nr); this->device_context = compress_offload_api_open(this->props.card_nr, this->props.device_nr, this->log); if (this->device_context == NULL) return -errno; return 0; } static void device_close(struct impl *this) { if (this->device_context == NULL) return; spa_log_info(this->log, "%p: closing Compress-Offload device, card #%d device #%d", this, this->props.card_nr, this->props.device_nr); if (this->device_started) compress_offload_api_stop(this->device_context); compress_offload_api_close(this->device_context); this->device_context = NULL; this->device_started = false; this->device_is_paused = false; this->have_format = false; } static int device_start(struct impl *this) { assert(this->device_context != NULL); if (compress_offload_api_start(this->device_context) < 0) return -errno; this->device_started = true; return 0; } static int device_pause(struct impl *this) { /* device_pause() can sometimes be called when the device context is already * gone. In particular, this can happen when the suspend command is received * after the pause command. */ if (this->device_context == NULL) return 0; if (this->device_is_paused) return 0; if (compress_offload_api_pause(this->device_context) < 0) return -errno; this->device_is_paused = true; return 0; } static int device_resume(struct impl *this) { assert(this->device_context != NULL); if (!this->device_is_paused) return 0; if (compress_offload_api_resume(this->device_context) < 0) return -errno; this->device_is_paused = false; return 0; } static int device_write(struct impl *this, const void *data, uint32_t size) { int res; uint32_t num_bytes_to_write; struct snd_compr_avail available_space; /* In here, try to write out as much data as possible, * in a non-blocking manner, retaining the unwritten * data for the next write call. */ if (SPA_UNLIKELY((res = compress_offload_api_get_available_space( this->device_context, &available_space)) < 0)) return res; /* We can only write data if there is at least enough space for one * fragment's worth of data, or if the data we want to write is * small (smaller than a fragment). The latter can happen when we * are writing the last few bits of the compressed audio medium. * When the former happens, we try to write as much data as we * can, limited by the amount of space available in the device. */ if ((available_space.avail < this->min_fragment_size) && (available_space.avail < size)) return 0; num_bytes_to_write = SPA_MIN(size, available_space.avail); res = compress_offload_api_write(this->device_context, data, num_bytes_to_write); if (SPA_UNLIKELY(res < 0)) { if (res == -EBADFD) spa_log_debug(this->log, "%p: device is paused", this); else spa_log_error(this->log, "%p: write error: %s", this, spa_strerror(res)); return res; } spa_log_trace_fp(this->log, "%p: wrote %d bytes; original request: %" PRIu32 "; adjusted " "for available space in device: %" PRIu32, this, res, size, num_bytes_to_write); if (SPA_UNLIKELY(((uint32_t)res) > num_bytes_to_write)) { spa_log_error(this->log, "%p: wrote more bytes than what was requested; " "requested: %" PRId32 " wrote: %d", this, num_bytes_to_write, res); return -EIO; } return res; } /* Driver timer functions */ static int set_driver_timeout(struct impl *this, uint64_t time) { struct itimerspec ts; ts.it_value.tv_sec = time / SPA_NSEC_PER_SEC; ts.it_value.tv_nsec = time % SPA_NSEC_PER_SEC; ts.it_interval.tv_sec = 0; ts.it_interval.tv_nsec = 0; spa_system_timerfd_settime(this->data_system, this->driver_timerfd, SPA_FD_TIMER_ABSTIME, &ts, NULL); return 0; } static int configure_driver_timer(struct impl *this) { struct timespec now; int res; if ((res = spa_system_clock_gettime(this->data_system, CLOCK_MONOTONIC, &now)) < 0) { spa_log_error(this->log, "%p: could not get time from monotonic sysclock: %s", this, spa_strerror(res)); return res; } this->next_driver_time = SPA_TIMESPEC_TO_NSEC(&now); if (this->following) set_driver_timeout(this, 0); else set_driver_timeout(this, this->next_driver_time); return 0; } static int start_driver_timer(struct impl *this) { int res; spa_log_debug(this->log, "%p: starting driver timer", this); this->driver_timer_source.func = on_driver_timeout; this->driver_timer_source.data = this; this->driver_timer_source.fd = this->driver_timerfd; this->driver_timer_source.mask = SPA_IO_IN; this->driver_timer_source.rmask = 0; spa_loop_add_source(this->data_loop, &this->driver_timer_source); if (SPA_UNLIKELY((res = configure_driver_timer(this)) < 0)) return res; return 0; } static int do_remove_driver_timer_source(struct spa_loop *loop, bool async, uint32_t seq, const void *data, size_t size, void *user_data) { struct impl *this = user_data; spa_loop_remove_source(this->data_loop, &this->driver_timer_source); set_driver_timeout(this, 0); return 0; } static void stop_driver_timer(struct impl *this) { spa_log_debug(this->log, "%p: stopping driver timer", this); /* Perform the actual stop within * the dataloop to avoid data races. */ spa_loop_locked(this->data_loop, do_remove_driver_timer_source, 0, NULL, 0, this); } static void on_driver_timeout(struct spa_source *source) { struct impl *this = source->data; uint64_t expire, current_time; int res; if (SPA_LIKELY(this->started)) { if (SPA_UNLIKELY((res = spa_system_timerfd_read(this->data_system, this->driver_timerfd, &expire)) < 0)) { if (res != -EAGAIN) spa_log_warn(this->log, "%p: error reading from timerfd: %s", this, spa_strerror(res)); return; } } if (SPA_LIKELY(this->node_position_io != NULL)) { this->cycle_duration = this->node_position_io->clock.target_duration; this->cycle_rate = this->node_position_io->clock.target_rate.denom; } else { /* This can happen at the very beginning if node_position_io * isn't passed to this node in time. */ this->cycle_duration = 1024; this->cycle_rate = 48000; } current_time = this->next_driver_time; this->next_driver_time += ((uint64_t)(this->cycle_duration)) * 1000000000ULL / this->cycle_rate; if (this->node_clock_io != NULL) { this->node_clock_io->nsec = current_time; this->node_clock_io->rate = this->node_clock_io->target_rate; this->node_clock_io->position += this->node_clock_io->duration; this->node_clock_io->duration = this->cycle_duration; this->node_clock_io->delay = 0; this->node_clock_io->rate_diff = 1.0; this->node_clock_io->next_nsec = this->next_driver_time; spa_log_trace_fp(this->log, "%p: clock IO updated to: nsec %" PRIu64 " pos %" PRIu64 " dur %" PRIu64 " next-nsec %" PRIu64, this, this->node_clock_io->nsec, this->node_clock_io->position, this->node_clock_io->duration, this->node_clock_io->next_nsec); } /* Adapt the graph cycle progression to the needs of the sink. * If the sink still has data to output, don't advance. */ if (spa_list_is_empty(&this->queued_output_buffers)) { struct spa_io_buffers *io = this->port_buffers_io; if (SPA_LIKELY(io != NULL)) { spa_log_trace_fp(this->log, "%p: ran out of buffers to output, " "need more; IO status: %d", this, io->status); io->status = SPA_STATUS_NEED_DATA; spa_node_call_ready(&this->callbacks, SPA_STATUS_NEED_DATA); } else { /* This should not happen. If it does, then there may be * an error in when the timer is stopped. When it happens, * do not schedule a next timeout. */ spa_log_warn(this->log, "%p: buffers IO was set to NULL before " "the driver timer was stopped", this); set_driver_timeout(this, 0); return; } } else { write_queued_output_buffers(this); } // TODO check for impossible timeouts (only relevant when taking device timestamps into account) set_driver_timeout(this, this->next_driver_time); } static inline void check_position_and_clock_config(struct impl *this) { if (SPA_LIKELY(this->node_position_io != NULL)) { this->cycle_duration = this->node_position_io->clock.duration; this->cycle_rate = this->node_position_io->clock.rate.denom; } else { /* This can happen at the very beginning if node_position_io * isn't passed to this node in time. */ this->cycle_duration = 1024; this->cycle_rate = 48000; } } static int do_reevaluate_following_state(struct spa_loop *loop, bool async, uint32_t seq, const void *data, size_t size, void *user_data) { struct impl *this = user_data; configure_driver_timer(this); return 0; } static void reevaluate_following_state(struct impl *this) { bool following; if (!this->started) return; following = is_following(this); if (following != this->following) { spa_log_debug(this->log, "%p: following state changed: %d->%d", this, this->following, following); this->following = following; spa_loop_locked(this->data_loop, do_reevaluate_following_state, 0, NULL, 0, this); } } static void reevaluate_freewheel_state(struct impl *this) { bool freewheel; if (!this->started) return; freewheel = (this->node_position_io != NULL) && SPA_FLAG_IS_SET(this->node_position_io->clock.flags, SPA_IO_CLOCK_FLAG_FREEWHEEL); if (this->freewheel != freewheel) { spa_log_debug(this->log, "%p: freewheel state changed: %d->%d", this, this->freewheel, freewheel); this->freewheel = freewheel; if (freewheel) device_pause(this); else device_resume(this); } } /* Miscellaneous functions */ static int parse_device(struct impl *this) { char *device; char *nextptr; #define NUM_DEVICE_VALUES (2) long values[NUM_DEVICE_VALUES]; int value_index; device = this->props.device; /* Valid devices always match the "hw:," pattern. */ if (strncmp(device, "hw:", 3) != 0) { spa_log_error(this->log, "%p: device \"%s\" does not begin with \"hw:\"", this, device); return -EINVAL; } nextptr = device + 3; for (value_index = 0; ; ++value_index) { const char *value_label; switch (value_index) { case 0: value_label = "card"; break; case 1: value_label = "device"; break; default: spa_assert_not_reached(); } errno = 0; values[value_index] = strtol(nextptr, &nextptr, 10); if (errno != 0) { spa_log_error(this->log, "%p: device \"%s\" has invalid %s value", this, device, value_label); return -EINVAL; } if (values[value_index] < 0) { spa_log_error(this->log, "%p: device \"%s\" has negative %s value", this, device, value_label); return -EINVAL; } if (values[value_index] > INT_MAX) { spa_log_error(this->log, "%p: device \"%s\" has %s value larger than %d", this, device, value_label, INT_MAX); return -EINVAL; } if (value_index >= (NUM_DEVICE_VALUES - 1)) break; if ((*nextptr) != ',') { spa_log_error(this->log, "%p: expected ',' separator between numbers in " "device \"%s\", got '%c'", this, device, *nextptr); return -EINVAL; } /* Skip the comma between the values. */ nextptr++; } this->props.card_nr = values[0]; this->props.device_nr = values[1]; return 0; } static void reset_props(struct props *props) { memset(props->device, 0, sizeof(props->device)); props->card_nr = 0; props->device_nr = 0; props->device_name_set = false; } static void clear_buffers(struct impl *this) { if (this->n_buffers > 0) { spa_log_debug(this->log, "%p: clearing buffers", this); spa_list_init(&this->queued_output_buffers); this->n_buffers = 0; } } static inline bool is_following(struct impl *this) { return (this->node_position_io != NULL) && (this->node_clock_io != NULL) && (this->node_position_io->clock.id != this->node_clock_io->id); } static int do_start(struct impl *this) { int res; if (this->started) return 0; this->following = is_following(this); spa_log_debug(this->log, "%p: starting output; starting as follower: %d", this, this->following); if (SPA_UNLIKELY((res = start_driver_timer(this)) < 0)) return res; this->started = true; /* Not starting the compress-offload device here right away. * That's because we first need to give it at least one * fragment's worth of data. Starting the device prior to * that results in buffer underflows inside the device. */ return 0; } static void do_stop(struct impl *this) { if (!this->started) return; spa_log_debug(this->log, "%p: stopping output", this); device_pause(this); this->started = false; stop_driver_timer(this); } static int write_queued_output_buffers(struct impl *this) { int res; uint32_t total_num_written_bytes; bool wrote_data = false; check_position_and_clock_config(this); /* In here, we write as much data as possible. The device may * initially not have sufficient space, but it is possible * that due to ongoing data consumption, it can accommodate * for more data in a next attempt, hence the "again" label. * * If during the write attempts, only a portion of a chunk * is written, we must keep track of the portion that hasn't * been consumed yet. offset_within_oldest_output_buffer * exists for this purpose. This can happen when the * device_write() call below returns 0. The loop is then * aborted, and the chunk is not fully written. * * In this sink node, each SPA buffer has exactly one chunk, * so when a chunk is fully consumed, the corresponding buffer * is removed from the queued_output_buffers list, marked as * available, and returned to the pool through * spa_node_call_reuse_buffer(). */ again: total_num_written_bytes = 0; while (!spa_list_is_empty(&this->queued_output_buffers)) { struct buffer *b; struct spa_data *d; uint32_t chunk_start_offset, chunk_size, pending_data_size; bool reuse_buffer = false; b = spa_list_first(&this->queued_output_buffers, struct buffer, link); d = b->buf->datas; assert(b->buf->n_datas >= 1); chunk_start_offset = d[0].chunk->offset + this->offset_within_oldest_output_buffer; chunk_size = d[0].chunk->size; /* An empty chunk signals that the source is skipping this cycle. This * is normal and necessary in cases when the compressed data frames are * longer than the quantum size. The source then has to keep track of * the excess lengths, and if these sum up to the length of a quantum, * it sends a buffer with an empty chunk to compensate. If this is not * done, there will eventually be an overflow, this sink will miss * cycles, and audible errors will occur. */ if (chunk_size != 0) { int num_written_bytes; pending_data_size = chunk_size - this->offset_within_oldest_output_buffer; chunk_start_offset = SPA_MIN(chunk_start_offset, d[0].maxsize); pending_data_size = SPA_MIN(pending_data_size, d[0].maxsize - chunk_start_offset); num_written_bytes = device_write(this, SPA_PTROFF(d[0].data, chunk_start_offset, void), pending_data_size); if (SPA_UNLIKELY(num_written_bytes < 0)) return num_written_bytes; if (num_written_bytes == 0) break; this->offset_within_oldest_output_buffer += num_written_bytes; total_num_written_bytes += num_written_bytes; wrote_data = wrote_data || (num_written_bytes > 0); if (this->offset_within_oldest_output_buffer >= chunk_size) { spa_log_trace_fp(this->log, "%p: buffer with ID %u was fully written; reusing this buffer", this, b->id); reuse_buffer = true; this->offset_within_oldest_output_buffer = 0; } } else { spa_log_trace_fp(this->log, "%p: buffer with ID %u has empty chunk; reusing this buffer", this, b->id); reuse_buffer = true; } if (reuse_buffer) { spa_list_remove(&b->link); SPA_FLAG_SET(b->flags, BUFFER_FLAG_AVAILABLE_FOR_NEW_DATA); this->port_buffers_io->buffer_id = b->id; spa_node_call_reuse_buffer(&this->callbacks, 0, b->id); } } if (!spa_list_is_empty(&this->queued_output_buffers) && (total_num_written_bytes > 0)) goto again; /* We start the device only after having written data to avoid * underruns due to an under-populated device ringbuffer. */ if (wrote_data && !this->device_started) { spa_log_debug(this->log, "%p: starting device", this); if ((res = device_start(this)) < 0) { spa_log_error(this->log, "%p: starting device failed: %s", this, spa_strerror(res)); return res; } this->device_started = true; } return 0; } /* Node and port functions */ static const struct spa_dict_item node_info_items[] = { { SPA_KEY_DEVICE_API, "alsa" }, { SPA_KEY_MEDIA_CLASS, "Audio/Sink" }, { SPA_KEY_NODE_DRIVER, "true" }, { SPA_KEY_NODE_PAUSE_ON_IDLE, "true" }, }; static void emit_node_info(struct impl *this, bool full) { uint64_t old = full ? this->node_info.change_mask : 0; if (full) this->node_info.change_mask = this->node_info_all; if (this->node_info.change_mask) { this->node_info.props = &SPA_DICT_INIT_ARRAY(node_info_items); spa_node_emit_info(&this->hooks, &this->node_info); this->node_info.change_mask = old; } } static void emit_port_info(struct impl *this, bool full) { uint64_t old = full ? this->port_info.change_mask : 0; if (full) this->port_info.change_mask = this->port_info_all; if (this->port_info.change_mask) { spa_node_emit_port_info(&this->hooks, SPA_DIRECTION_INPUT, 0, &this->port_info); this->port_info.change_mask = old; } } static int impl_node_add_listener(void *object, struct spa_hook *listener, const struct spa_node_events *events, void *data) { struct impl *this = object; struct spa_hook_list save; spa_return_val_if_fail(this != NULL, -EINVAL); spa_hook_list_isolate(&this->hooks, &save, listener, events, data); emit_node_info(this, true); emit_port_info(this, true); spa_hook_list_join(&this->hooks, &save); return 0; } static int impl_node_set_callbacks(void *object, const struct spa_node_callbacks *callbacks, void *data) { struct impl *this = object; spa_return_val_if_fail(this != NULL, -EINVAL); this->callbacks = SPA_CALLBACKS_INIT(callbacks, data); return 0; } static int impl_node_sync(void *object, int seq) { struct impl *this = object; spa_return_val_if_fail(this != NULL, -EINVAL); spa_node_emit_result(&this->hooks, seq, 0, 0, NULL); return 0; } static int impl_node_enum_params(void *object, int seq, uint32_t id, uint32_t start, uint32_t num, const struct spa_pod *filter) { struct impl *this = object; struct spa_pod *param; struct spa_pod_builder b = { 0 }; uint8_t buffer[4096]; struct spa_result_node_params result; uint32_t count = 0; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(num != 0, -EINVAL); result.id = id; result.next = start; next: result.index = result.next++; spa_pod_builder_init(&b, buffer, sizeof(buffer)); switch (id) { case SPA_PARAM_PropInfo: { struct props *p = &this->props; switch (result.index) { case 0: param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_PropInfo, id, SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_device), SPA_PROP_INFO_name, SPA_POD_String(SPA_KEY_API_ALSA_PATH), SPA_PROP_INFO_description, SPA_POD_String("The ALSA Compress-Offload device"), SPA_PROP_INFO_type, SPA_POD_Stringn(p->device, sizeof(p->device))); break; default: return 0; } break; } case SPA_PARAM_Props: { struct props *p = &this->props; switch (result.index) { case 0: param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_Props, id, SPA_PROP_device, SPA_POD_Stringn(p->device, sizeof(p->device)) ); break; default: return 0; } break; } case SPA_PARAM_IO: switch (result.index) { case 0: param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamIO, id, SPA_PARAM_IO_id, SPA_POD_Id(SPA_IO_Clock), SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_clock))); break; case 1: param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamIO, id, SPA_PARAM_IO_id, SPA_POD_Id(SPA_IO_Position), SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_position))); break; default: return 0; } break; case SPA_PARAM_EnumPortConfig: { switch (result.index) { case 0: /* Force ports to be configured to run in passthrough mode. * This is essential when dealing with compressed data. */ param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamPortConfig, id, SPA_PARAM_PORT_CONFIG_direction, SPA_POD_Id(SPA_DIRECTION_INPUT), SPA_PARAM_PORT_CONFIG_mode, SPA_POD_Id(SPA_PARAM_PORT_CONFIG_MODE_passthrough) ); break; default: return 0; } break; } default: return -ENOENT; } if (spa_pod_filter(&b, &result.param, param, filter) < 0) goto next; spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); if (++count != num) goto next; return 0; } static int impl_node_set_param(void *object, uint32_t id, uint32_t flags, const struct spa_pod *param) { struct impl *this = object; int res; spa_return_val_if_fail(this != NULL, -EINVAL); switch (id) { case SPA_PARAM_Props: { struct props *p = &this->props; if (param == NULL) { reset_props(p); return 0; } spa_pod_parse_object(param, SPA_TYPE_OBJECT_Props, NULL, SPA_PROP_device, SPA_POD_OPT_Stringn(p->device, sizeof(p->device)) ); spa_log_debug(this->log, "%p: setting device name to \"%s\"", this, p->device); p->device_name_set = true; if ((res = parse_device(this)) < 0) { p->device_name_set = false; return res; } emit_node_info(this, false); break; } default: res = -ENOENT; break; } return res; } static int impl_node_set_io(void *object, uint32_t id, void *data, size_t size) { struct impl *this = object; spa_return_val_if_fail(this != NULL, -EINVAL); switch (id) { case SPA_IO_Clock: spa_log_debug(this->log, "%p: got clock IO", this); this->node_clock_io = data; break; case SPA_IO_Position: spa_log_debug(this->log, "%p: got position IO", this); this->node_position_io = data; break; default: return -ENOENT; } reevaluate_following_state(this); reevaluate_freewheel_state(this); return 0; } static int impl_node_send_command(void *object, const struct spa_command *command) { struct impl *this = object; int res; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(command != NULL, -EINVAL); spa_log_debug(this->log, "%p: got new command: %s", this, spa_debug_type_find_name(spa_type_node_command_id, SPA_NODE_COMMAND_ID(command))); switch (SPA_NODE_COMMAND_ID(command)) { case SPA_NODE_COMMAND_ParamBegin: if (SPA_UNLIKELY((res = device_open(this)) < 0)) return res; break; case SPA_NODE_COMMAND_ParamEnd: device_close(this); break; case SPA_NODE_COMMAND_Start: if (!this->have_format) return -EIO; if (this->n_buffers == 0) return -EIO; if (SPA_UNLIKELY((res = do_start(this)) < 0)) return res; break; case SPA_NODE_COMMAND_Suspend: case SPA_NODE_COMMAND_Pause: do_stop(this); break; default: return -ENOTSUP; } return 0; } static int impl_node_add_port(void *object, enum spa_direction direction, uint32_t port_id, const struct spa_dict *props) { return -ENOTSUP; } static int impl_node_remove_port(void *object, enum spa_direction direction, uint32_t port_id) { return -ENOTSUP; } static int port_enum_formats(struct impl *this, int seq, uint32_t start, uint32_t num, const struct spa_pod *filter, struct spa_pod_builder *b) { bool device_started, device_opened; struct spa_result_node_params result; struct spa_pod *fmt; const struct known_codec_info *codec_info; uint32_t count = 0; int res; bool codec_supported; struct spa_audio_info info; device_opened = (this->device_context != NULL); device_started = this->device_started; spa_log_debug(this->log, "%p: about to enumerate supported codecs: " "device opened: %d have configured format: %d device started: %d", this, device_opened, this->have_format, device_started); if (!this->started && this->have_format) { spa_log_debug(this->log, "%p: closing device to reset configured format", this); device_close(this); device_opened = false; } if (!device_opened) { if ((res = device_open(this)) < 0) return res; } spa_zero(result); result.id = SPA_PARAM_EnumFormat; result.next = start; next: result.index = result.next++; if (result.index >= SPA_N_ELEMENTS(known_codecs)) goto enum_end; codec_info = &(known_codecs[result.index]); codec_supported = compress_offload_api_supports_codec(this->device_context, codec_info->codec_id); spa_log_debug(this->log, "%p: codec %s supported: %s", this, codec_info->name, codec_supported ? "yes" : "no"); if (!codec_supported) goto next; spa_zero(info); info.media_type = SPA_MEDIA_TYPE_audio; info.media_subtype = codec_info->media_subtype; if ((fmt = spa_format_audio_build(b, SPA_PARAM_EnumFormat, &info)) == NULL) { res = -errno; spa_log_error(this->log, "%p: error while building enumerated audio info: %s", this, spa_strerror(res)); return res; } if (spa_pod_filter(b, &result.param, fmt, filter) < 0) goto next; spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); if (++count != num) goto next; enum_end: res = 0; if (!device_opened) device_close(this); spa_log_debug(this->log, "%p: done enumerating supported codecs", this); return res; } static int impl_port_enum_params(void *object, int seq, enum spa_direction direction, uint32_t port_id, uint32_t id, uint32_t start, uint32_t num, const struct spa_pod *filter) { struct impl *this = object; struct spa_pod *param = NULL; struct spa_pod_builder b = { 0 }; uint8_t buffer[4096]; struct spa_result_node_params result; uint32_t count = 0; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(num != 0, -EINVAL); spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); result.id = id; result.next = start; next: result.index = result.next++; spa_pod_builder_init(&b, buffer, sizeof(buffer)); switch (id) { case SPA_PARAM_EnumFormat: return port_enum_formats(this, seq, start, num, filter, &b); case SPA_PARAM_Format: if (!this->have_format) { spa_log_debug(this->log, "%p: attempted to enumerate current " "format, but no current audio info set", this); return -EIO; } if (result.index > 0) return 0; spa_log_debug(this->log, "%p: current audio info is set; " "enumerating currently set format", this); param = spa_format_audio_build(&b, id, &this->current_audio_info); break; case SPA_PARAM_IO: switch (result.index) { case 0: param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamIO, id, SPA_PARAM_IO_id, SPA_POD_Id(SPA_IO_Buffers), SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_buffers))); break; default: return 0; } break; case SPA_PARAM_Buffers: if (!this->have_format) return -EIO; if (result.index > 0) return 0; param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamBuffers, id, SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(1, 1, MAX_BUFFERS), /* blocks is set to 1 since we don't have planar data */ SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(1), SPA_PARAM_BUFFERS_size, SPA_POD_CHOICE_RANGE_Int( this->configured_fragment_size * this->configured_num_fragments, this->configured_fragment_size * this->configured_num_fragments, this->max_fragment_size * this->max_num_fragments), /* "stride" has no meaning when dealing with compressed data */ SPA_PARAM_BUFFERS_stride, SPA_POD_Int(0)); break; default: return -ENOENT; } if (spa_pod_filter(&b, &result.param, param, filter) < 0) goto next; spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); if (++count != num) goto next; return 0; } static int port_set_format(void *object, enum spa_direction direction, uint32_t port_id, uint32_t flags, const struct spa_pod *format) { struct impl *this = object; int res; if (format == NULL) { if (!this->have_format) return 0; spa_log_debug(this->log, "%p: clearing format and closing device", this); device_close(this); clear_buffers(this); } else { struct spa_audio_info info = { 0 }; uint32_t rate; const struct snd_compr_caps *compress_offload_caps; spa_log_debug(this->log, "%p: about to set format", this); if ((res = spa_format_audio_parse(format, &info)) < 0) { spa_log_error(this->log, "%p: error while parsing audio format: %s", this, spa_strerror(res)); return res; } if (this->device_context != NULL) { spa_log_debug(this->log, "%p: need to close device to be able to reopen it with new format", this); device_close(this); } if ((res = init_audio_codec_info(this, &info, &rate)) < 0) return res; if ((res = device_open(this)) < 0) return res; if (!compress_offload_api_supports_codec(this->device_context, this->audio_codec_info.id)) { spa_log_error(this->log, "%p: codec is not supported by the device", this); device_close(this); return -ENOTSUP; } if ((res = compress_offload_api_set_params(this->device_context, &(this->audio_codec_info), 0, 0)) < 0) return res; compress_offload_caps = compress_offload_api_get_caps(this->device_context); this->min_fragment_size = compress_offload_caps->min_fragment_size; this->max_fragment_size = compress_offload_caps->max_fragment_size; this->min_num_fragments = compress_offload_caps->min_fragments; this->max_num_fragments = compress_offload_caps->max_fragments; spa_log_debug( this->log, "%p: min/max fragment size: %" PRIu32 "/%" PRIu32 " min/max num fragments: %" PRIu32 "/%" PRIu32, this, this->min_fragment_size, this->max_fragment_size, this->min_num_fragments, this->max_num_fragments ); compress_offload_api_get_fragment_config(this->device_context, &(this->configured_fragment_size), &(this->configured_num_fragments)); spa_log_debug( this->log, "%p: configured fragment size: %" PRIu32 " configured num fragments: %" PRIu32, this, this->configured_fragment_size, this->configured_num_fragments ); this->current_audio_info = info; this->have_format = true; this->port_info.rate = SPA_FRACTION(1, rate); } this->node_info.change_mask |= SPA_NODE_CHANGE_MASK_FLAGS; this->node_info.flags &= ~SPA_NODE_FLAG_NEED_CONFIGURE; emit_node_info(this, false); this->port_info.change_mask |= SPA_PORT_CHANGE_MASK_RATE; this->port_info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; if (this->have_format) { this->port_params[PORT_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_READWRITE); this->port_params[PORT_Buffers] = SPA_PARAM_INFO(SPA_PARAM_Buffers, SPA_PARAM_INFO_READ); } else { this->port_params[PORT_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); this->port_params[PORT_Buffers] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); } emit_port_info(this, false); return 0; } static int impl_port_set_param(void *object, enum spa_direction direction, uint32_t port_id, uint32_t id, uint32_t flags, const struct spa_pod *param) { struct impl *this = object; int res; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); switch (id) { case SPA_PARAM_Format: res = port_set_format(this, direction, port_id, flags, param); break; default: res = -ENOENT; break; } return res; } static int impl_port_use_buffers(void *object, enum spa_direction direction, uint32_t port_id, uint32_t flags, struct spa_buffer **buffers, uint32_t n_buffers) { struct impl *this = object; uint32_t i; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); if (this->n_buffers > 0) { spa_log_debug(this->log, "%p: %u buffers currently already in use; stopping device " "to remove them before using new ones", this, this->n_buffers); do_stop(this); clear_buffers(this); } spa_log_debug(this->log, "%p: using a pool with %d buffer(s)", this, n_buffers); if (n_buffers > 0 && !this->have_format) return -EIO; if (n_buffers > MAX_BUFFERS) return -ENOSPC; for (i = 0; i < n_buffers; i++) { struct buffer *b = &this->buffers[i]; struct spa_data *d = buffers[i]->datas; b->id = i; b->flags = BUFFER_FLAG_AVAILABLE_FOR_NEW_DATA; b->buf = buffers[i]; if (d[0].data == NULL) { spa_log_error(this->log, "%p: need mapped memory", this); return -EINVAL; } spa_log_debug(this->log, "%p: got buffer with ID %d bufptr %p data %p", this, i, b->buf, d[0].data); } this->n_buffers = n_buffers; return 0; } static int impl_port_set_io(void *object, enum spa_direction direction, uint32_t port_id, uint32_t id, void *data, size_t size) { struct impl *this = object; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); switch (id) { case SPA_IO_Buffers: spa_log_debug(this->log, "%p: got buffers IO with data %p", this, data); this->port_buffers_io = data; break; default: return -ENOENT; } return 0; } static int impl_port_reuse_buffer(void *object, uint32_t port_id, uint32_t buffer_id) { return -ENOTSUP; } static int impl_node_process(void *object) { struct impl *this = object; struct spa_io_buffers *io; struct buffer *b; int res; spa_return_val_if_fail(this != NULL, -EINVAL); io = this->port_buffers_io; spa_return_val_if_fail(io != NULL, -EIO); /* Sinks aren't supposed to actually consume anything * when the graph runs in freewheel mode. */ if (this->node_position_io && this->node_position_io->clock.flags & SPA_IO_CLOCK_FLAG_FREEWHEEL) { io->status = SPA_STATUS_NEED_DATA; return SPA_STATUS_HAVE_DATA; } /* Add the incoming data if there is some. We place the data in * a queue instead of just consuming it directly. This allows for * adjusting driver cycles to the needs of the sink - if the sink * already has data queued, it does not yet need to schedule a next * cycle. See on_driver_timeout() for details. This is only relevnt * if the sink is running as the graph's driver. */ if ((io->status == SPA_STATUS_HAVE_DATA) && (io->buffer_id < this->n_buffers)) { b = &this->buffers[io->buffer_id]; if (!SPA_FLAG_IS_SET(b->flags, BUFFER_FLAG_AVAILABLE_FOR_NEW_DATA)) { spa_log_warn(this->log, "%p: buffer %u in use", this, io->buffer_id); io->status = -EINVAL; return -EINVAL; } if (this->device_is_paused) { spa_log_debug(this->log, "%p: resuming paused device", this); if ((res = device_resume(this)) < 0) { io->status = res; return SPA_STATUS_STOPPED; } } spa_log_trace_fp(this->log, "%p: queuing buffer %u", this, io->buffer_id); spa_list_append(&this->queued_output_buffers, &b->link); SPA_FLAG_CLEAR(b->flags, BUFFER_FLAG_AVAILABLE_FOR_NEW_DATA); /* This is essential to be able to hold back this buffer * (which is because we queued it in a custom list for late * consumption). By setting buffer_id to SPA_ID_INVALID, * we essentially inform the graph that it must not attempt * to return this buffer to the buffer pool. */ io->buffer_id = SPA_ID_INVALID; if (SPA_UNLIKELY((res = write_queued_output_buffers(this)) < 0)) { io->status = res; return SPA_STATUS_STOPPED; } io->status = SPA_STATUS_OK; } return SPA_STATUS_HAVE_DATA; } /* SPA node information and init / clear procedures */ static const struct spa_node_methods impl_node = { SPA_VERSION_NODE_METHODS, .add_listener = impl_node_add_listener, .set_callbacks = impl_node_set_callbacks, .sync = impl_node_sync, .enum_params = impl_node_enum_params, .set_param = impl_node_set_param, .set_io = impl_node_set_io, .send_command = impl_node_send_command, .add_port = impl_node_add_port, .remove_port = impl_node_remove_port, .port_enum_params = impl_port_enum_params, .port_set_param = impl_port_set_param, .port_use_buffers = impl_port_use_buffers, .port_set_io = impl_port_set_io, .port_reuse_buffer = impl_port_reuse_buffer, .process = impl_node_process, }; static int impl_get_interface(struct spa_handle *handle, const char *type, void **interface) { struct impl *this; spa_return_val_if_fail(handle != NULL, -EINVAL); spa_return_val_if_fail(interface != NULL, -EINVAL); this = (struct impl *) handle; if (spa_streq(type, SPA_TYPE_INTERFACE_Node)) *interface = &this->node; else return -ENOENT; return 0; } static int impl_clear(struct spa_handle *handle) { struct impl *this; spa_return_val_if_fail(handle != NULL, -EINVAL); this = (struct impl *) handle; device_close(this); if (this->driver_timerfd > 0) { spa_system_close(this->data_system, this->driver_timerfd); this->driver_timerfd = -1; } spa_log_info(this->log, "%p: created Compress-Offload sink", this); return 0; } static size_t impl_get_size(const struct spa_handle_factory *factory, const struct spa_dict *params) { return sizeof(struct impl); } static int impl_init(const struct spa_handle_factory *factory, struct spa_handle *handle, const struct spa_dict *info, const struct spa_support *support, uint32_t n_support) { struct impl *this; uint32_t i; int res = 0; spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(handle != NULL, -EINVAL); handle->get_interface = impl_get_interface; handle->clear = impl_clear; this = (struct impl *) handle; this->log = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log); /* A logger must always exist, otherwise something is very wrong. */ assert(this->log != NULL); alsa_log_topic_init(this->log); this->data_loop = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_DataLoop); if (this->data_loop == NULL) { spa_log_error(this->log, "%p: could not find a loop", this); res = -EINVAL; goto error; } this->data_system = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_DataSystem); if (this->data_system == NULL) { spa_log_error(this->log, "%p: could not find a data system", this); res = -EINVAL; goto error; } this->node.iface = SPA_INTERFACE_INIT( SPA_TYPE_INTERFACE_Node, SPA_VERSION_NODE, &impl_node, this); spa_hook_list_init(&this->hooks); reset_props(&this->props); this->have_format = false; this->started = false; this->freewheel = false; this->n_buffers = 0; spa_list_init(&this->queued_output_buffers); this->offset_within_oldest_output_buffer = 0; res = this->driver_timerfd = spa_system_timerfd_create(this->data_system, CLOCK_MONOTONIC, SPA_FD_CLOEXEC | SPA_FD_NONBLOCK); if (SPA_UNLIKELY(res < 0)) { spa_log_error(this->log, "%p: could not create driver timerfd: %s", this, spa_strerror(res)); goto error; } this->next_driver_time = 0; this->following = false; this->node_info_all = SPA_NODE_CHANGE_MASK_FLAGS | SPA_NODE_CHANGE_MASK_PROPS | SPA_NODE_CHANGE_MASK_PARAMS; this->node_info = SPA_NODE_INFO_INIT(); this->node_info.max_input_ports = 1; this->node_info.flags = SPA_NODE_FLAG_RT | SPA_NODE_FLAG_IN_PORT_CONFIG | SPA_NODE_FLAG_NEED_CONFIGURE; this->node_params[NODE_PropInfo] = SPA_PARAM_INFO(SPA_PARAM_PropInfo, SPA_PARAM_INFO_READ); this->node_params[NODE_Props] = SPA_PARAM_INFO(SPA_PARAM_Props, SPA_PARAM_INFO_READWRITE); this->node_params[NODE_IO] = SPA_PARAM_INFO(SPA_PARAM_IO, SPA_PARAM_INFO_READ); this->node_params[NODE_EnumPortConfig] = SPA_PARAM_INFO(SPA_PARAM_EnumPortConfig, SPA_PARAM_INFO_READ); this->node_info.params = this->node_params; this->node_info.n_params = N_NODE_PARAMS; this->node_clock_io = NULL; this->node_position_io = NULL; this->port_info_all = SPA_PORT_CHANGE_MASK_FLAGS | SPA_PORT_CHANGE_MASK_PARAMS; this->port_info = SPA_PORT_INFO_INIT(); this->port_info.flags = SPA_PORT_FLAG_LIVE | SPA_PORT_FLAG_PHYSICAL | SPA_PORT_FLAG_TERMINAL; this->port_params[PORT_EnumFormat] = SPA_PARAM_INFO(SPA_PARAM_EnumFormat, SPA_PARAM_INFO_READ); this->port_params[PORT_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); this->port_params[PORT_IO] = SPA_PARAM_INFO(SPA_PARAM_IO, SPA_PARAM_INFO_READ); this->port_params[PORT_Buffers] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); this->port_info.params = this->port_params; this->port_info.n_params = N_PORT_PARAMS; this->port_buffers_io = NULL; this->device_context = NULL; this->device_started = false; memset(&this->audio_codec_info, 0, sizeof(this->audio_codec_info)); this->device_is_paused = false; spa_log_info(this->log, "%p: initialized Compress-Offload sink", this); for (i = 0; info && i < info->n_items; i++) { const char *k = info->items[i].key; const char *s = info->items[i].value; if (spa_streq(k, SPA_KEY_API_ALSA_PATH)) { snprintf(this->props.device, sizeof(this->props.device), "%s", s); if ((res = parse_device(this)) < 0) return res; } } finish: return res; error: impl_clear((struct spa_handle *)this); goto finish; } static const struct spa_interface_info impl_interfaces[] = { {SPA_TYPE_INTERFACE_Node,}, }; static int impl_enum_interface_info(const struct spa_handle_factory *factory, const struct spa_interface_info **info, uint32_t *index) { spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(info != NULL, -EINVAL); spa_return_val_if_fail(index != NULL, -EINVAL); switch (*index) { case 0: *info = &impl_interfaces[*index]; break; default: return 0; } (*index)++; return 1; } /* Factory info */ static const struct spa_dict_item info_items[] = { { SPA_KEY_FACTORY_AUTHOR, "Sanchayan Maity , Carlos Rafael Giani " }, { SPA_KEY_FACTORY_DESCRIPTION, "Play compressed audio (like MP3 or AAC) with the ALSA Compress-Offload API" }, { SPA_KEY_FACTORY_USAGE, "["SPA_KEY_API_ALSA_PATH"=]" }, }; static const struct spa_dict info = SPA_DICT_INIT_ARRAY(info_items); const struct spa_handle_factory spa_alsa_compress_offload_sink_factory = { SPA_VERSION_HANDLE_FACTORY, SPA_NAME_API_ALSA_COMPRESS_OFFLOAD_SINK, &info, impl_get_size, impl_init, impl_enum_interface_info, }; pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/alsa-pcm-device.c000066400000000000000000000366671511204443500270020ustar00rootroot00000000000000/* Spa ALSA Device */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "alsa.h" #define MAX_DEVICES 64 static const char default_device[] = "hw:0"; struct props { char device[64]; }; static void reset_props(struct props *props) { strncpy(props->device, default_device, 64); } struct impl { struct spa_handle handle; struct spa_device device; struct spa_log *log; uint32_t info_all; struct spa_device_info device_info; #define IDX_EnumProfile 0 #define IDX_Profile 1 struct spa_param_info params[2]; struct spa_hook_list hooks; struct props props; uint32_t n_nodes; uint32_t n_capture; uint32_t n_playback; uint32_t profile; }; static const char *get_stream(snd_pcm_info_t *pcminfo) { switch (snd_pcm_info_get_stream(pcminfo)) { case SND_PCM_STREAM_PLAYBACK: return "playback"; case SND_PCM_STREAM_CAPTURE: return "capture"; default: return "unknown"; } } static const char *get_class(snd_pcm_info_t *pcminfo) { switch (snd_pcm_info_get_class(pcminfo)) { case SND_PCM_CLASS_GENERIC: return "generic"; case SND_PCM_CLASS_MULTI: return "multichannel"; case SND_PCM_CLASS_MODEM: return "modem"; case SND_PCM_CLASS_DIGITIZER: return "digitizer"; default: return "unknown"; } } static const char *get_subclass(snd_pcm_info_t *pcminfo) { switch (snd_pcm_info_get_subclass(pcminfo)) { case SND_PCM_SUBCLASS_GENERIC_MIX: return "generic-mix"; case SND_PCM_SUBCLASS_MULTI_MIX: return "multichannel-mix"; default: return "unknown"; } } static int emit_node(struct impl *this, snd_ctl_card_info_t *cardinfo, snd_pcm_info_t *pcminfo, uint32_t id) { struct spa_dict_item items[12]; char device_name[128], path[180]; char sync_name[128], dev[16], subdev[16], card[16]; struct spa_device_object_info info; snd_pcm_sync_id_t sync_id; const char *stream; info = SPA_DEVICE_OBJECT_INFO_INIT(); info.type = SPA_TYPE_INTERFACE_Node; if (snd_pcm_info_get_stream(pcminfo) == SND_PCM_STREAM_PLAYBACK) { info.factory_name = SPA_NAME_API_ALSA_PCM_SINK; stream = "playback"; } else { info.factory_name = SPA_NAME_API_ALSA_PCM_SOURCE; stream = "capture"; } info.change_mask = SPA_DEVICE_OBJECT_CHANGE_MASK_PROPS; snprintf(card, sizeof(card), "%d", snd_pcm_info_get_card(pcminfo)); snprintf(dev, sizeof(dev), "%d", snd_pcm_info_get_device(pcminfo)); snprintf(subdev, sizeof(subdev), "%d", snd_pcm_info_get_subdevice(pcminfo)); snprintf(device_name, sizeof(device_name), "%s,%s", this->props.device, dev); snprintf(path, sizeof(path), "alsa:pcm:%s:%s:%s", snd_ctl_card_info_get_id(cardinfo), dev, stream); items[0] = SPA_DICT_ITEM_INIT(SPA_KEY_OBJECT_PATH, path); items[1] = SPA_DICT_ITEM_INIT(SPA_KEY_API_ALSA_PATH, device_name); items[2] = SPA_DICT_ITEM_INIT(SPA_KEY_API_ALSA_PCM_CARD, card); items[3] = SPA_DICT_ITEM_INIT(SPA_KEY_API_ALSA_PCM_DEVICE, dev); items[4] = SPA_DICT_ITEM_INIT(SPA_KEY_API_ALSA_PCM_SUBDEVICE, subdev); items[5] = SPA_DICT_ITEM_INIT(SPA_KEY_API_ALSA_PCM_STREAM, get_stream(pcminfo)); items[6] = SPA_DICT_ITEM_INIT(SPA_KEY_API_ALSA_PCM_ID, snd_pcm_info_get_id(pcminfo)); items[7] = SPA_DICT_ITEM_INIT(SPA_KEY_API_ALSA_PCM_NAME, snd_pcm_info_get_name(pcminfo)); items[8] = SPA_DICT_ITEM_INIT(SPA_KEY_API_ALSA_PCM_SUBNAME, snd_pcm_info_get_subdevice_name(pcminfo)); items[9] = SPA_DICT_ITEM_INIT(SPA_KEY_API_ALSA_PCM_CLASS, get_class(pcminfo)); items[10] = SPA_DICT_ITEM_INIT(SPA_KEY_API_ALSA_PCM_SUBCLASS, get_subclass(pcminfo)); sync_id = snd_pcm_info_get_sync(pcminfo); snprintf(sync_name, sizeof(sync_name), "%08x:%08x:%08x:%08x", sync_id.id32[0], sync_id.id32[1], sync_id.id32[2], sync_id.id32[3]); items[11] = SPA_DICT_ITEM_INIT(SPA_KEY_API_ALSA_PCM_SYNC_ID, sync_name); info.props = &SPA_DICT_INIT_ARRAY(items); spa_device_emit_object_info(&this->hooks, id, &info); return 0; } static int activate_profile(struct impl *this, snd_ctl_t *ctl_hndl, uint32_t id) { int err = 0, dev; uint32_t i, n_cap, n_play; snd_pcm_info_t *pcminfo; snd_ctl_card_info_t *cardinfo; this->profile = id; snd_ctl_card_info_alloca(&cardinfo); if ((err = snd_ctl_card_info(ctl_hndl, cardinfo)) < 0) { spa_log_error(this->log, "error card info: %s", snd_strerror(err)); return err; } for (i = 0; i < this->n_nodes; i++) spa_device_emit_object_info(&this->hooks, i, NULL); this->n_nodes = this->n_capture = this->n_playback = 0; if (id == 0) return 0; snd_pcm_info_alloca(&pcminfo); dev = -1; i = n_cap = n_play = 0; while (1) { if ((err = snd_ctl_pcm_next_device(ctl_hndl, &dev)) < 0) { spa_log_error(this->log, "error iterating devices: %s", snd_strerror(err)); break; } if (dev < 0) break; snd_pcm_info_set_device(pcminfo, dev); snd_pcm_info_set_subdevice(pcminfo, 0); snd_pcm_info_set_stream(pcminfo, SND_PCM_STREAM_PLAYBACK); if ((err = snd_ctl_pcm_info(ctl_hndl, pcminfo)) < 0) { if (err != -ENOENT) spa_log_error(this->log, "error pcm info: %s", snd_strerror(err)); } if (err >= 0) { n_play++; emit_node(this, cardinfo, pcminfo, i++); } snd_pcm_info_set_stream(pcminfo, SND_PCM_STREAM_CAPTURE); if ((err = snd_ctl_pcm_info(ctl_hndl, pcminfo)) < 0) { if (err != -ENOENT) spa_log_error(this->log, "error pcm info: %s", snd_strerror(err)); } if (err >= 0) { n_cap++; emit_node(this, cardinfo, pcminfo, i++); } } this->n_capture = n_cap; this->n_playback = n_play; this->n_nodes = i; return err; } static int set_profile(struct impl *this, uint32_t id) { snd_ctl_t *ctl_hndl; int err; spa_log_debug(this->log, "enumerate PCM nodes for card %s; profile: %d", this->props.device, id); if ((err = snd_ctl_open(&ctl_hndl, this->props.device, 0)) < 0) { spa_log_error(this->log, "can't open control for card %s: %s", this->props.device, snd_strerror(err)); return err; } err = activate_profile(this, ctl_hndl, id); spa_log_debug(this->log, "done enumerating PCM nodes for card %s", this->props.device); snd_ctl_close(ctl_hndl); this->device_info.change_mask |= SPA_DEVICE_CHANGE_MASK_PARAMS; this->params[IDX_Profile].user++; return err; } static int emit_info(struct impl *this, bool full) { int err = 0; struct spa_dict_item items[20]; uint32_t n_items = 0; char path[128]; if (full) this->device_info.change_mask = this->info_all; if (this->device_info.change_mask) { snd_ctl_card_info_t *info; snd_ctl_t *ctl_hndl; spa_log_debug(this->log, "open card %s", this->props.device); if ((err = snd_ctl_open(&ctl_hndl, this->props.device, 0)) < 0) { spa_log_error(this->log, "can't open control for card %s: %s", this->props.device, snd_strerror(err)); return err; } snd_ctl_card_info_alloca(&info); err = snd_ctl_card_info(ctl_hndl, info); spa_log_debug(this->log, "close card %s", this->props.device); snd_ctl_close(ctl_hndl); if (err < 0) { spa_log_error(this->log, "error hardware info: %s", snd_strerror(err)); return err; } #define ADD_ITEM(key, value) items[n_items++] = SPA_DICT_ITEM_INIT(key, value) snprintf(path, sizeof(path), "alsa:pcm:%s", snd_ctl_card_info_get_id(info)); ADD_ITEM(SPA_KEY_OBJECT_PATH, path); ADD_ITEM(SPA_KEY_DEVICE_API, "alsa:pcm"); ADD_ITEM(SPA_KEY_MEDIA_CLASS, "Audio/Device"); ADD_ITEM(SPA_KEY_API_ALSA_PATH, (char *)this->props.device); ADD_ITEM(SPA_KEY_API_ALSA_CARD_ID, snd_ctl_card_info_get_id(info)); ADD_ITEM(SPA_KEY_API_ALSA_CARD_COMPONENTS, snd_ctl_card_info_get_components(info)); ADD_ITEM(SPA_KEY_API_ALSA_CARD_DRIVER, snd_ctl_card_info_get_driver(info)); ADD_ITEM(SPA_KEY_API_ALSA_CARD_NAME, snd_ctl_card_info_get_name(info)); ADD_ITEM(SPA_KEY_API_ALSA_CARD_LONGNAME, snd_ctl_card_info_get_longname(info)); ADD_ITEM(SPA_KEY_API_ALSA_CARD_MIXERNAME, snd_ctl_card_info_get_mixername(info)); this->device_info.props = &SPA_DICT_INIT(items, n_items); #undef ADD_ITEM if (this->device_info.change_mask & SPA_DEVICE_CHANGE_MASK_PARAMS) { SPA_FOR_EACH_ELEMENT_VAR(this->params, p) { if (p->user > 0) { p->flags ^= SPA_PARAM_INFO_SERIAL; p->user = 0; } } } spa_device_emit_info(&this->hooks, &this->device_info); this->device_info.change_mask = 0; } return 0; } static int impl_add_listener(void *object, struct spa_hook *listener, const struct spa_device_events *events, void *data) { struct impl *this = object; struct spa_hook_list save; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(events != NULL, -EINVAL); spa_hook_list_isolate(&this->hooks, &save, listener, events, data); if (events->info || events->object_info) emit_info(this, true); spa_hook_list_join(&this->hooks, &save); return 0; } static int impl_sync(void *object, int seq) { struct impl *this = object; spa_return_val_if_fail(this != NULL, -EINVAL); spa_device_emit_result(&this->hooks, seq, 0, 0, NULL); return 0; } static struct spa_pod *build_profile(struct impl *this, struct spa_pod_builder *b, uint32_t id, uint32_t index) { struct spa_pod_frame f[2]; const char *name, *desc; switch (index) { case 0: name = "off"; desc = "Off"; break; case 1: name = "on"; desc = "On"; break; default: errno = EINVAL; return NULL; } spa_pod_builder_push_object(b, &f[0], SPA_TYPE_OBJECT_ParamProfile, id); spa_pod_builder_add(b, SPA_PARAM_PROFILE_index, SPA_POD_Int(index), SPA_PARAM_PROFILE_name, SPA_POD_String(name), SPA_PARAM_PROFILE_description, SPA_POD_String(desc), 0); if (index == 1) { spa_pod_builder_prop(b, SPA_PARAM_PROFILE_classes, 0); spa_pod_builder_push_struct(b, &f[1]); if (this->n_capture) { spa_pod_builder_add_struct(b, SPA_POD_String("Audio/Source"), SPA_POD_Int(this->n_capture)); } if (this->n_playback) { spa_pod_builder_add_struct(b, SPA_POD_String("Audio/Sink"), SPA_POD_Int(this->n_playback)); } spa_pod_builder_pop(b, &f[1]); } return spa_pod_builder_pop(b, &f[0]); } static int impl_enum_params(void *object, int seq, uint32_t id, uint32_t start, uint32_t num, const struct spa_pod *filter) { struct impl *this = object; struct spa_pod *param; struct spa_pod_builder b = { 0 }; uint8_t buffer[1024]; struct spa_result_device_params result; uint32_t count = 0; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(num != 0, -EINVAL); result.id = id; result.next = start; next: result.index = result.next++; spa_pod_builder_init(&b, buffer, sizeof(buffer)); switch (id) { case SPA_PARAM_EnumProfile: { switch (result.index) { case 0: case 1: param = build_profile(this, &b, id, result.index); break; default: return 0; } break; } case SPA_PARAM_Profile: { switch (result.index) { case 0: param = build_profile(this, &b, id, this->profile); break; default: return 0; } break; } default: return -ENOENT; } if (spa_pod_filter(&b, &result.param, param, filter) < 0) goto next; spa_device_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_DEVICE_PARAMS, &result); if (++count != num) goto next; return 0; } static uint32_t find_profile_by_name(const char *name) { if (spa_streq(name, "off")) return 0; else if (spa_streq(name, "on")) return 1; return SPA_ID_INVALID; } static int impl_set_param(void *object, uint32_t id, uint32_t flags, const struct spa_pod *param) { struct impl *this = object; int res; spa_return_val_if_fail(this != NULL, -EINVAL); switch (id) { case SPA_PARAM_Profile: { uint32_t idx = SPA_ID_INVALID; const char *name = NULL; if (param == NULL) { idx = 1; } else if ((res = spa_pod_parse_object(param, SPA_TYPE_OBJECT_ParamProfile, NULL, SPA_PARAM_PROFILE_index, SPA_POD_OPT_Int(&idx), SPA_PARAM_PROFILE_name, SPA_POD_OPT_String(&name))) < 0) { spa_log_warn(this->log, "can't parse profile"); spa_debug_log_pod(this->log, SPA_LOG_LEVEL_DEBUG, 0, NULL, param); return res; } if (idx == SPA_ID_INVALID && name == NULL) { spa_log_warn(this->log, "profile needs name or index"); return -EINVAL; } if (idx == SPA_ID_INVALID) idx = find_profile_by_name(name); if (idx == SPA_ID_INVALID) { spa_log_warn(this->log, "unknown profile %s", name); return -EINVAL; } set_profile(this, idx); emit_info(this, false); break; } default: return -ENOENT; } return 0; } static const struct spa_device_methods impl_device = { SPA_VERSION_DEVICE_METHODS, .add_listener = impl_add_listener, .sync = impl_sync, .enum_params = impl_enum_params, .set_param = impl_set_param, }; static int impl_get_interface(struct spa_handle *handle, const char *type, void **interface) { struct impl *this; spa_return_val_if_fail(handle != NULL, -EINVAL); spa_return_val_if_fail(interface != NULL, -EINVAL); this = (struct impl *) handle; if (spa_streq(type, SPA_TYPE_INTERFACE_Device)) *interface = &this->device; else return -ENOENT; return 0; } static int impl_clear(struct spa_handle *handle) { return 0; } static size_t impl_get_size(const struct spa_handle_factory *factory, const struct spa_dict *params) { return sizeof(struct impl); } static int impl_init(const struct spa_handle_factory *factory, struct spa_handle *handle, const struct spa_dict *info, const struct spa_support *support, uint32_t n_support) { struct impl *this; const char *str; spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(handle != NULL, -EINVAL); handle->get_interface = impl_get_interface; handle->clear = impl_clear; this = (struct impl *) handle; this->log = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log); alsa_log_topic_init(this->log); this->device.iface = SPA_INTERFACE_INIT( SPA_TYPE_INTERFACE_Device, SPA_VERSION_DEVICE, &impl_device, this); spa_hook_list_init(&this->hooks); reset_props(&this->props); snd_config_update_free_global(); if (info && (str = spa_dict_lookup(info, SPA_KEY_API_ALSA_PATH))) snprintf(this->props.device, 64, "%s", str); this->device_info = SPA_DEVICE_INFO_INIT(); this->info_all = SPA_DEVICE_CHANGE_MASK_PROPS | SPA_DEVICE_CHANGE_MASK_PARAMS; this->params[IDX_EnumProfile] = SPA_PARAM_INFO(SPA_PARAM_EnumProfile, SPA_PARAM_INFO_READ); this->params[IDX_Profile] = SPA_PARAM_INFO(SPA_PARAM_Profile, SPA_PARAM_INFO_READWRITE); this->device_info.params = this->params; this->device_info.n_params = SPA_N_ELEMENTS(this->params); return 0; } static const struct spa_interface_info impl_interfaces[] = { {SPA_TYPE_INTERFACE_Device,}, }; static int impl_enum_interface_info(const struct spa_handle_factory *factory, const struct spa_interface_info **info, uint32_t *index) { spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(info != NULL, -EINVAL); spa_return_val_if_fail(index != NULL, -EINVAL); if (*index >= SPA_N_ELEMENTS(impl_interfaces)) return 0; *info = &impl_interfaces[(*index)++]; return 1; } const struct spa_handle_factory spa_alsa_pcm_device_factory = { SPA_VERSION_HANDLE_FACTORY, SPA_NAME_API_ALSA_PCM_DEVICE, NULL, impl_get_size, impl_init, impl_enum_interface_info, }; pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/alsa-pcm-sink.c000066400000000000000000000641761511204443500265030ustar00rootroot00000000000000/* Spa ALSA Sink */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #include #include #include #include #include #include #include #include #include "alsa-pcm.h" #define CHECK_PORT(this,d,p) ((d) == SPA_DIRECTION_INPUT && (p) == 0) static const char default_device[] = "hw:0"; static void reset_props(struct props *props) { strncpy(props->device, default_device, 64); props->use_chmap = DEFAULT_USE_CHMAP; spa_scnprintf(props->media_class, sizeof(props->media_class), "%s", "Audio/Sink"); } static int impl_node_enum_params(void *object, int seq, uint32_t id, uint32_t start, uint32_t num, const struct spa_pod *filter) { struct state *this = object; struct spa_pod *param; spa_auto(spa_pod_dynamic_builder) b = { 0 }; struct spa_pod_builder_state state; uint8_t buffer[4096]; struct spa_result_node_params result; uint32_t count = 0; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(num != 0, -EINVAL); spa_pod_dynamic_builder_init(&b, buffer, sizeof(buffer), 4096); spa_pod_builder_get_state(&b.b, &state); result.id = id; result.next = start; next: result.index = result.next++; spa_pod_builder_reset(&b.b, &state); switch (id) { case SPA_PARAM_PropInfo: { struct props *p = &this->props; switch (result.index) { case 0: param = spa_pod_builder_add_object(&b.b, SPA_TYPE_OBJECT_PropInfo, id, SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_device), SPA_PROP_INFO_name, SPA_POD_String(SPA_KEY_API_ALSA_PATH), SPA_PROP_INFO_description, SPA_POD_String("The ALSA device"), SPA_PROP_INFO_type, SPA_POD_Stringn(p->device, sizeof(p->device))); break; case 1: param = spa_pod_builder_add_object(&b.b, SPA_TYPE_OBJECT_PropInfo, id, SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_deviceName), SPA_PROP_INFO_description, SPA_POD_String("The ALSA device name"), SPA_PROP_INFO_type, SPA_POD_Stringn(p->device_name, sizeof(p->device_name))); break; case 2: param = spa_pod_builder_add_object(&b.b, SPA_TYPE_OBJECT_PropInfo, id, SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_cardName), SPA_PROP_INFO_description, SPA_POD_String("The ALSA card name"), SPA_PROP_INFO_type, SPA_POD_Stringn(p->card_name, sizeof(p->card_name))); break; case 3: param = spa_pod_builder_add_object(&b.b, SPA_TYPE_OBJECT_PropInfo, id, SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_latencyOffsetNsec), SPA_PROP_INFO_description, SPA_POD_String("Latency offset (ns)"), SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Long(0LL, 0LL, 2 * SPA_NSEC_PER_SEC)); break; case 4: if (!this->is_iec958 && !this->is_hdmi) goto next; param = spa_pod_builder_add_object(&b.b, SPA_TYPE_OBJECT_PropInfo, id, SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_iec958Codecs), SPA_PROP_INFO_name, SPA_POD_String("iec958.codecs"), SPA_PROP_INFO_description, SPA_POD_String("Enabled IEC958 (S/PDIF) codecs"), SPA_PROP_INFO_type, SPA_POD_Id(SPA_AUDIO_IEC958_CODEC_UNKNOWN), SPA_PROP_INFO_params, SPA_POD_Bool(true), SPA_PROP_INFO_container, SPA_POD_Id(SPA_TYPE_Array)); break; default: param = spa_alsa_enum_propinfo(this, result.index - 5, &b.b); if (param == NULL) return 0; } break; } case SPA_PARAM_Props: { struct props *p = &this->props; struct spa_pod_frame f; uint32_t codecs[16], n_codecs; switch (result.index) { case 0: spa_pod_builder_push_object(&b.b, &f, SPA_TYPE_OBJECT_Props, id); spa_pod_builder_add(&b.b, SPA_PROP_device, SPA_POD_Stringn(p->device, sizeof(p->device)), SPA_PROP_deviceName, SPA_POD_Stringn(p->device_name, sizeof(p->device_name)), SPA_PROP_cardName, SPA_POD_Stringn(p->card_name, sizeof(p->card_name)), SPA_PROP_latencyOffsetNsec, SPA_POD_Long(this->process_latency.ns), 0); if (this->is_iec958 || this->is_hdmi) { n_codecs = spa_alsa_get_iec958_codecs(this, codecs, SPA_N_ELEMENTS(codecs)); spa_pod_builder_prop(&b.b, SPA_PROP_iec958Codecs, 0); spa_pod_builder_array(&b.b, sizeof(uint32_t), SPA_TYPE_Id, n_codecs, codecs); } spa_alsa_add_prop_params(this, &b.b); param = spa_pod_builder_pop(&b.b, &f); break; default: return 0; } break; } case SPA_PARAM_IO: switch (result.index) { case 0: param = spa_pod_builder_add_object(&b.b, SPA_TYPE_OBJECT_ParamIO, id, SPA_PARAM_IO_id, SPA_POD_Id(SPA_IO_Clock), SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_clock))); break; case 1: param = spa_pod_builder_add_object(&b.b, SPA_TYPE_OBJECT_ParamIO, id, SPA_PARAM_IO_id, SPA_POD_Id(SPA_IO_Position), SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_position))); break; default: return 0; } break; case SPA_PARAM_ProcessLatency: switch (result.index) { case 0: param = spa_process_latency_build(&b.b, id, &this->process_latency); break; default: return 0; } break; default: return -ENOENT; } if (spa_pod_filter(&b.b, &result.param, param, filter) < 0) goto next; spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); if (++count != num) goto next; return 0; } static int impl_node_set_io(void *object, uint32_t id, void *data, size_t size) { struct state *this = object; spa_return_val_if_fail(this != NULL, -EINVAL); switch (id) { case SPA_IO_Clock: if (size > 0 && size < sizeof(struct spa_io_clock)) return -EINVAL; this->clock = data; break; case SPA_IO_Position: if (size > 0 && size < sizeof(struct spa_io_position)) return -EINVAL; this->position = data; break; default: return -ENOENT; } spa_alsa_reassign_follower(this); return 0; } static void handle_process_latency(struct state *this, const struct spa_process_latency_info *info) { bool ns_changed = this->process_latency.ns != info->ns; if (this->process_latency.quantum == info->quantum && this->process_latency.rate == info->rate && !ns_changed) return; this->process_latency = *info; this->info.change_mask |= SPA_NODE_CHANGE_MASK_PARAMS; if (ns_changed) this->params[NODE_Props].user++; this->params[NODE_ProcessLatency].user++; this->port_info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; this->port_params[PORT_Latency].user++; } static int impl_node_set_param(void *object, uint32_t id, uint32_t flags, const struct spa_pod *param) { struct state *this = object; int res; spa_return_val_if_fail(this != NULL, -EINVAL); switch (id) { case SPA_PARAM_Props: { struct props *p = &this->props; struct spa_pod *iec958_codecs = NULL, *params = NULL; int64_t lat_ns = -1; if (param == NULL) { reset_props(p); return 0; } spa_pod_parse_object(param, SPA_TYPE_OBJECT_Props, NULL, SPA_PROP_device, SPA_POD_OPT_Stringn(p->device, sizeof(p->device)), SPA_PROP_latencyOffsetNsec, SPA_POD_OPT_Long(&lat_ns), SPA_PROP_iec958Codecs, SPA_POD_OPT_Pod(&iec958_codecs), SPA_PROP_params, SPA_POD_OPT_Pod(¶ms)); if ((this->is_iec958 || this->is_hdmi) && iec958_codecs != NULL) { uint32_t i, codecs[16], n_codecs; n_codecs = spa_pod_copy_array(iec958_codecs, SPA_TYPE_Id, codecs, SPA_N_ELEMENTS(codecs)); this->iec958_codecs = 1ULL << SPA_AUDIO_IEC958_CODEC_PCM; for (i = 0; i < n_codecs; i++) this->iec958_codecs |= 1ULL << codecs[i]; this->info.change_mask |= SPA_NODE_CHANGE_MASK_PARAMS; this->params[NODE_Props].user++; this->port_info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; this->port_params[PORT_EnumFormat].user++; } spa_alsa_parse_prop_params(this, params); if (lat_ns != -1) { struct spa_process_latency_info info; info = this->process_latency; info.ns = lat_ns; handle_process_latency(this, &info); } spa_alsa_emit_node_info(this, false); spa_alsa_emit_port_info(this, false); break; } case SPA_PARAM_ProcessLatency: { struct spa_process_latency_info info; if (param == NULL) spa_zero(info); else if ((res = spa_process_latency_parse(param, &info)) < 0) return res; handle_process_latency(this, &info); spa_alsa_emit_node_info(this, false); spa_alsa_emit_port_info(this, false); break; } default: return -ENOENT; } return 0; } static int impl_node_send_command(void *object, const struct spa_command *command) { struct state *this = object; int res; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(command != NULL, -EINVAL); switch (SPA_NODE_COMMAND_ID(command)) { case SPA_NODE_COMMAND_ParamBegin: if ((res = spa_alsa_open(this, NULL)) < 0) return res; break; case SPA_NODE_COMMAND_ParamEnd: if (this->have_format) return 0; if ((res = spa_alsa_close(this)) < 0) return res; break; case SPA_NODE_COMMAND_Start: if (!this->have_format) return -EIO; if (this->n_buffers == 0) return -EIO; this->want_started = true; if ((res = spa_alsa_start(this)) < 0) return res; break; case SPA_NODE_COMMAND_Suspend: case SPA_NODE_COMMAND_Pause: this->want_started = false; if ((res = spa_alsa_pause(this)) < 0) return res; break; default: return -ENOTSUP; } return 0; } static int impl_node_add_listener(void *object, struct spa_hook *listener, const struct spa_node_events *events, void *data) { struct state *this = object; struct spa_hook_list save; spa_return_val_if_fail(this != NULL, -EINVAL); spa_hook_list_isolate(&this->hooks, &save, listener, events, data); spa_alsa_emit_node_info(this, true); spa_alsa_emit_port_info(this, true); spa_hook_list_join(&this->hooks, &save); return 0; } static int impl_node_set_callbacks(void *object, const struct spa_node_callbacks *callbacks, void *data) { struct state *this = object; spa_return_val_if_fail(this != NULL, -EINVAL); this->callbacks = SPA_CALLBACKS_INIT(callbacks, data); return 0; } static int impl_node_sync(void *object, int seq) { struct state *this = object; spa_return_val_if_fail(this != NULL, -EINVAL); spa_node_emit_result(&this->hooks, seq, 0, 0, NULL); return 0; } static int impl_node_add_port(void *object, enum spa_direction direction, uint32_t port_id, const struct spa_dict *props) { return -ENOTSUP; } static int impl_node_remove_port(void *object, enum spa_direction direction, uint32_t port_id) { return -ENOTSUP; } static int impl_node_port_enum_params(void *object, int seq, enum spa_direction direction, uint32_t port_id, uint32_t id, uint32_t start, uint32_t num, const struct spa_pod *filter) { struct state *this = object; struct spa_pod *param; spa_auto(spa_pod_dynamic_builder) b = { 0 }; struct spa_pod_builder_state state; uint8_t buffer[1024]; struct spa_result_node_params result; uint32_t count = 0; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(num != 0, -EINVAL); spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); spa_pod_dynamic_builder_init(&b, buffer, sizeof(buffer), 4096); spa_pod_builder_get_state(&b.b, &state); result.id = id; result.next = start; next: result.index = result.next++; spa_pod_builder_reset(&b.b, &state); switch (id) { case SPA_PARAM_EnumFormat: return spa_alsa_enum_format(this, seq, start, num, filter); case SPA_PARAM_Format: if (!this->have_format) return -EIO; if (result.index > 0) return 0; switch (this->current_format.media_subtype) { case SPA_MEDIA_SUBTYPE_raw: param = spa_format_audio_raw_build(&b.b, id, &this->current_format.info.raw); break; case SPA_MEDIA_SUBTYPE_iec958: param = spa_format_audio_iec958_build(&b.b, id, &this->current_format.info.iec958); break; case SPA_MEDIA_SUBTYPE_dsd: param = spa_format_audio_dsd_build(&b.b, id, &this->current_format.info.dsd); break; default: return -EIO; } break; case SPA_PARAM_Buffers: { uint32_t min_buffers; if (!this->have_format) return -EIO; if (result.index > 0) return 0; min_buffers = (this->quantum_limit * 4 * this->frame_scale) > this->buffer_frames ? 2 : 1; param = spa_pod_builder_add_object(&b.b, SPA_TYPE_OBJECT_ParamBuffers, id, SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(min_buffers, min_buffers, MAX_BUFFERS), SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(this->blocks), SPA_PARAM_BUFFERS_size, SPA_POD_CHOICE_RANGE_Int( this->quantum_limit * this->frame_size * this->frame_scale, 16 * this->frame_size * this->frame_scale, INT32_MAX), SPA_PARAM_BUFFERS_stride, SPA_POD_Int(this->frame_size)); break; } case SPA_PARAM_Meta: switch (result.index) { case 0: param = spa_pod_builder_add_object(&b.b, SPA_TYPE_OBJECT_ParamMeta, id, SPA_PARAM_META_type, SPA_POD_Id(SPA_META_Header), SPA_PARAM_META_size, SPA_POD_Int(sizeof(struct spa_meta_header))); break; default: return 0; } break; case SPA_PARAM_IO: switch (result.index) { case 0: param = spa_pod_builder_add_object(&b.b, SPA_TYPE_OBJECT_ParamIO, id, SPA_PARAM_IO_id, SPA_POD_Id(SPA_IO_Buffers), SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_buffers))); break; case 1: param = spa_pod_builder_add_object(&b.b, SPA_TYPE_OBJECT_ParamIO, id, SPA_PARAM_IO_id, SPA_POD_Id(SPA_IO_RateMatch), SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_rate_match))); break; default: return 0; } break; case SPA_PARAM_Latency: switch (result.index) { case 0: case 1: { struct spa_latency_info latency = this->latency[result.index]; if (latency.direction == SPA_DIRECTION_INPUT) spa_process_latency_info_add(&this->process_latency, &latency); param = spa_latency_build(&b.b, id, &latency); break; } default: return 0; } break; case SPA_PARAM_Tag: switch (result.index) { case 0: case 1: if ((param = this->tag[result.index]) == NULL) goto next; break; default: return 0; } break; default: return -ENOENT; } if (spa_pod_filter(&b.b, &result.param, param, filter) < 0) goto next; spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); if (++count != num) goto next; return 0; } static int clear_buffers(struct state *this) { if (this->n_buffers > 0) { spa_list_init(&this->ready); this->n_buffers = 0; } return 0; } static int port_set_format(void *object, enum spa_direction direction, uint32_t port_id, uint32_t flags, const struct spa_pod *format) { struct state *this = object; int err = 0; if (format == NULL) { if (!this->have_format) return 0; spa_log_debug(this->log, "clear format"); spa_alsa_close(this); clear_buffers(this); } else { struct spa_audio_info info = { 0 }; if ((err = spa_format_parse(format, &info.media_type, &info.media_subtype)) < 0) return err; if (info.media_type != SPA_MEDIA_TYPE_audio) return -EINVAL; switch (info.media_subtype) { case SPA_MEDIA_SUBTYPE_raw: if (spa_format_audio_raw_parse(format, &info.info.raw) < 0) return -EINVAL; break; case SPA_MEDIA_SUBTYPE_iec958: if (spa_format_audio_iec958_parse(format, &info.info.iec958) < 0) return -EINVAL; break; case SPA_MEDIA_SUBTYPE_dsd: if (spa_format_audio_dsd_parse(format, &info.info.dsd) < 0) return -EINVAL; break; default: return -EINVAL; } if ((err = spa_alsa_set_format(this, &info, flags)) < 0) return err; this->current_format = info; } this->info.change_mask |= SPA_NODE_CHANGE_MASK_PROPS; spa_alsa_emit_node_info(this, false); this->port_info.change_mask |= SPA_PORT_CHANGE_MASK_RATE; this->port_info.rate = SPA_FRACTION(1, this->rate); this->port_info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; if (this->have_format) { this->port_params[PORT_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_READWRITE); this->port_params[PORT_Buffers] = SPA_PARAM_INFO(SPA_PARAM_Buffers, SPA_PARAM_INFO_READ); this->port_params[PORT_Latency].user++; } else { this->port_params[PORT_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); this->port_params[PORT_Buffers] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); } spa_alsa_emit_port_info(this, false); return err; } static int impl_node_port_set_param(void *object, enum spa_direction direction, uint32_t port_id, uint32_t id, uint32_t flags, const struct spa_pod *param) { struct state *this = object; int res = 0; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); switch (id) { case SPA_PARAM_Format: res = port_set_format(this, direction, port_id, flags, param); break; case SPA_PARAM_Latency: { enum spa_direction other = SPA_DIRECTION_REVERSE(direction); struct spa_latency_info info; if (param == NULL) info = SPA_LATENCY_INFO(other); else if ((res = spa_latency_parse(param, &info)) < 0) return res; if (info.direction != other) return -EINVAL; this->latency[info.direction] = info; this->port_info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; this->port_params[PORT_Latency].user++; spa_alsa_emit_port_info(this, false); break; } case SPA_PARAM_Tag: { enum spa_direction other = SPA_DIRECTION_REVERSE(direction); if (param != NULL) { struct spa_tag_info info; void *state = NULL; if (spa_tag_parse(param, &info, &state) < 0 || info.direction != other) return -EINVAL; } if (spa_tag_compare(param, this->tag[other]) != 0) { free(this->tag[other]); this->tag[other] = param ? spa_pod_copy(param) : NULL; this->port_info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; this->port_params[PORT_Tag].user++; spa_alsa_emit_port_info(this, false); } break; } default: res = -ENOENT; break; } return res; } static int impl_node_port_use_buffers(void *object, enum spa_direction direction, uint32_t port_id, uint32_t flags, struct spa_buffer **buffers, uint32_t n_buffers) { struct state *this = object; uint32_t i; int res; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); spa_log_debug(this->log, "%p: use %d buffers", this, n_buffers); if (this->n_buffers > 0) { spa_alsa_pause(this); if ((res = clear_buffers(this)) < 0) return res; } if (n_buffers > 0 && !this->have_format) return -EIO; if (n_buffers > MAX_BUFFERS) return -ENOSPC; for (i = 0; i < n_buffers; i++) { struct buffer *b = &this->buffers[i]; struct spa_data *d = buffers[i]->datas; b->buf = buffers[i]; b->id = i; b->flags = BUFFER_FLAG_OUT; b->h = spa_buffer_find_meta_data(b->buf, SPA_META_Header, sizeof(*b->h)); if (d[0].data == NULL) { spa_log_error(this->log, "%p: need mapped memory", this); return -EINVAL; } spa_log_debug(this->log, "%p: %d %p data:%p", this, i, b->buf, d[0].data); } this->n_buffers = n_buffers; return 0; } static int impl_node_port_set_io(void *object, enum spa_direction direction, uint32_t port_id, uint32_t id, void *data, size_t size) { struct state *this = object; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); spa_log_debug(this->log, "%p: io %d %p %zd", this, id, data, size); switch (id) { case SPA_IO_Buffers: this->io = data; break; case SPA_IO_RateMatch: this->rate_match = data; if (this->rate_match) spa_alsa_update_rate_match(this); break; default: return -ENOENT; } return 0; } static int impl_node_port_reuse_buffer(void *object, uint32_t port_id, uint32_t buffer_id) { return -ENOTSUP; } static int impl_node_process(void *object) { struct state *this = object; struct spa_io_buffers *io; spa_return_val_if_fail(this != NULL, -EINVAL); if ((io = this->io) == NULL) return -EIO; spa_log_trace_fp(this->log, "%p: process %d %d/%d", this, io->status, io->buffer_id, this->n_buffers); if (this->position && this->position->clock.flags & SPA_IO_CLOCK_FLAG_FREEWHEEL) { io->status = SPA_STATUS_NEED_DATA; return SPA_STATUS_HAVE_DATA; } if (io->status == SPA_STATUS_HAVE_DATA && io->buffer_id < this->n_buffers) { struct buffer *b = &this->buffers[io->buffer_id]; if (!SPA_FLAG_IS_SET(b->flags, BUFFER_FLAG_OUT)) { spa_log_warn(this->log, "%p: buffer %u in use", this, io->buffer_id); io->status = -EINVAL; return -EINVAL; } spa_log_trace_fp(this->log, "%p: queue buffer %u", this, io->buffer_id); spa_list_append(&this->ready, &b->link); SPA_FLAG_CLEAR(b->flags, BUFFER_FLAG_OUT); io->buffer_id = SPA_ID_INVALID; } if (!spa_list_is_empty(&this->ready)) { spa_alsa_write(this); io->status = SPA_STATUS_OK; } return SPA_STATUS_HAVE_DATA; } static const struct spa_node_methods impl_node = { SPA_VERSION_NODE_METHODS, .add_listener = impl_node_add_listener, .set_callbacks = impl_node_set_callbacks, .sync = impl_node_sync, .enum_params = impl_node_enum_params, .set_param = impl_node_set_param, .set_io = impl_node_set_io, .send_command = impl_node_send_command, .add_port = impl_node_add_port, .remove_port = impl_node_remove_port, .port_enum_params = impl_node_port_enum_params, .port_set_param = impl_node_port_set_param, .port_use_buffers = impl_node_port_use_buffers, .port_set_io = impl_node_port_set_io, .port_reuse_buffer = impl_node_port_reuse_buffer, .process = impl_node_process, }; static int impl_get_interface(struct spa_handle *handle, const char *type, void **interface) { struct state *this; spa_return_val_if_fail(handle != NULL, -EINVAL); spa_return_val_if_fail(interface != NULL, -EINVAL); this = (struct state *) handle; if (spa_streq(type, SPA_TYPE_INTERFACE_Node)) *interface = &this->node; else return -ENOENT; return 0; } static int impl_clear(struct spa_handle *handle) { struct state *this; spa_return_val_if_fail(handle != NULL, -EINVAL); this = (struct state *) handle; spa_alsa_close(this); spa_alsa_clear(this); return 0; } static size_t impl_get_size(const struct spa_handle_factory *factory, const struct spa_dict *params) { return sizeof(struct state); } static int impl_init(const struct spa_handle_factory *factory, struct spa_handle *handle, const struct spa_dict *info, const struct spa_support *support, uint32_t n_support) { struct state *this; spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(handle != NULL, -EINVAL); handle->get_interface = impl_get_interface; handle->clear = impl_clear; this = (struct state *) handle; this->log = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log); alsa_log_topic_init(this->log); this->data_system = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_DataSystem); this->data_loop = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_DataLoop); this->main_loop = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Loop); if (this->main_loop == NULL) { spa_log_error(this->log, "a main loop is needed"); return -EINVAL; } if (this->data_loop == NULL) { spa_log_error(this->log, "a data loop is needed"); return -EINVAL; } if (this->data_system == NULL) { spa_log_error(this->log, "a data system is needed"); return -EINVAL; } this->node.iface = SPA_INTERFACE_INIT( SPA_TYPE_INTERFACE_Node, SPA_VERSION_NODE, &impl_node, this); spa_hook_list_init(&this->hooks); this->stream = SND_PCM_STREAM_PLAYBACK; this->port_direction = SPA_DIRECTION_INPUT; this->latency[this->port_direction] = SPA_LATENCY_INFO( this->port_direction, .min_quantum = 1.0f, .max_quantum = 1.0f); this->latency[SPA_DIRECTION_OUTPUT] = SPA_LATENCY_INFO(SPA_DIRECTION_OUTPUT); this->info_all = SPA_NODE_CHANGE_MASK_FLAGS | SPA_NODE_CHANGE_MASK_PROPS | SPA_NODE_CHANGE_MASK_PARAMS; this->info = SPA_NODE_INFO_INIT(); this->info.max_input_ports = 1; this->info.flags = SPA_NODE_FLAG_RT; this->params[NODE_PropInfo] = SPA_PARAM_INFO(SPA_PARAM_PropInfo, SPA_PARAM_INFO_READ); this->params[NODE_Props] = SPA_PARAM_INFO(SPA_PARAM_Props, SPA_PARAM_INFO_READWRITE); this->params[NODE_IO] = SPA_PARAM_INFO(SPA_PARAM_IO, SPA_PARAM_INFO_READ); this->params[NODE_ProcessLatency] = SPA_PARAM_INFO(SPA_PARAM_ProcessLatency, SPA_PARAM_INFO_READWRITE); this->info.params = this->params; this->info.n_params = N_NODE_PARAMS; reset_props(&this->props); this->port_info_all = SPA_PORT_CHANGE_MASK_FLAGS | SPA_PORT_CHANGE_MASK_PARAMS; this->port_info = SPA_PORT_INFO_INIT(); this->port_info.flags = SPA_PORT_FLAG_LIVE | SPA_PORT_FLAG_PHYSICAL | SPA_PORT_FLAG_TERMINAL; this->port_params[PORT_EnumFormat] = SPA_PARAM_INFO(SPA_PARAM_EnumFormat, SPA_PARAM_INFO_READ); this->port_params[PORT_Meta] = SPA_PARAM_INFO(SPA_PARAM_Meta, SPA_PARAM_INFO_READ); this->port_params[PORT_IO] = SPA_PARAM_INFO(SPA_PARAM_IO, SPA_PARAM_INFO_READ); this->port_params[PORT_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); this->port_params[PORT_Buffers] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); this->port_params[PORT_Latency] = SPA_PARAM_INFO(SPA_PARAM_Latency, SPA_PARAM_INFO_READWRITE); this->port_params[PORT_Tag] = SPA_PARAM_INFO(SPA_PARAM_Tag, SPA_PARAM_INFO_READWRITE); this->port_info.params = this->port_params; this->port_info.n_params = N_PORT_PARAMS; spa_list_init(&this->ready); return spa_alsa_init(this, info); } static const struct spa_interface_info impl_interfaces[] = { {SPA_TYPE_INTERFACE_Node,}, }; static int impl_enum_interface_info(const struct spa_handle_factory *factory, const struct spa_interface_info **info, uint32_t *index) { spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(info != NULL, -EINVAL); spa_return_val_if_fail(index != NULL, -EINVAL); switch (*index) { case 0: *info = &impl_interfaces[*index]; break; default: return 0; } (*index)++; return 1; } static const struct spa_dict_item info_items[] = { { SPA_KEY_FACTORY_AUTHOR, "Wim Taymans " }, { SPA_KEY_FACTORY_DESCRIPTION, "Play audio with the alsa API" }, { SPA_KEY_FACTORY_USAGE, "["SPA_KEY_API_ALSA_PATH"=]" }, }; static const struct spa_dict info = SPA_DICT_INIT_ARRAY(info_items); const struct spa_handle_factory spa_alsa_sink_factory = { SPA_VERSION_HANDLE_FACTORY, SPA_NAME_API_ALSA_PCM_SINK, &info, impl_get_size, impl_init, impl_enum_interface_info, }; pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/alsa-pcm-source.c000066400000000000000000000575611511204443500270370ustar00rootroot00000000000000/* Spa ALSA Source */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #include #include #include #include #include #include #include #include "alsa.h" #include "alsa-pcm.h" #define CHECK_PORT(this,d,p) ((d) == SPA_DIRECTION_OUTPUT && (p) == 0) static const char default_device[] = "hw:0"; static void reset_props(struct props *props) { strncpy(props->device, default_device, 64); props->use_chmap = DEFAULT_USE_CHMAP; spa_scnprintf(props->media_class, sizeof(props->media_class), "%s", "Audio/Source"); } static int impl_node_enum_params(void *object, int seq, uint32_t id, uint32_t start, uint32_t num, const struct spa_pod *filter) { struct state *this = object; struct spa_pod *param; uint8_t buffer[4096]; spa_auto(spa_pod_dynamic_builder) b = { 0 }; struct spa_pod_builder_state state; struct props *p; struct spa_result_node_params result; uint32_t count = 0; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(num != 0, -EINVAL); spa_pod_dynamic_builder_init(&b, buffer, sizeof(buffer), 4096); spa_pod_builder_get_state(&b.b, &state); p = &this->props; result.id = id; result.next = start; next: result.index = result.next++; spa_pod_builder_reset(&b.b, &state); switch (id) { case SPA_PARAM_PropInfo: switch (result.index) { case 0: param = spa_pod_builder_add_object(&b.b, SPA_TYPE_OBJECT_PropInfo, id, SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_device), SPA_PROP_INFO_name, SPA_POD_String(SPA_KEY_API_ALSA_PATH), SPA_PROP_INFO_description, SPA_POD_String("The ALSA device"), SPA_PROP_INFO_type, SPA_POD_Stringn(p->device, sizeof(p->device))); break; case 1: param = spa_pod_builder_add_object(&b.b, SPA_TYPE_OBJECT_PropInfo, id, SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_deviceName), SPA_PROP_INFO_description, SPA_POD_String("The ALSA device name"), SPA_PROP_INFO_type, SPA_POD_Stringn(p->device_name, sizeof(p->device_name))); break; case 2: param = spa_pod_builder_add_object(&b.b, SPA_TYPE_OBJECT_PropInfo, id, SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_cardName), SPA_PROP_INFO_description, SPA_POD_String("The ALSA card name"), SPA_PROP_INFO_type, SPA_POD_Stringn(p->card_name, sizeof(p->card_name))); break; case 3: param = spa_pod_builder_add_object(&b.b, SPA_TYPE_OBJECT_PropInfo, id, SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_latencyOffsetNsec), SPA_PROP_INFO_description, SPA_POD_String("Latency offset (ns)"), SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Long(0LL, 0LL, 2 * SPA_NSEC_PER_SEC)); break; default: param = spa_alsa_enum_propinfo(this, result.index - 4, &b.b); if (param == NULL) return 0; } break; case SPA_PARAM_Props: { struct spa_pod_frame f; switch (result.index) { case 0: spa_pod_builder_push_object(&b.b, &f, SPA_TYPE_OBJECT_Props, id); spa_pod_builder_add(&b.b, SPA_PROP_device, SPA_POD_Stringn(p->device, sizeof(p->device)), SPA_PROP_deviceName, SPA_POD_Stringn(p->device_name, sizeof(p->device_name)), SPA_PROP_cardName, SPA_POD_Stringn(p->card_name, sizeof(p->card_name)), SPA_PROP_latencyOffsetNsec, SPA_POD_Long(this->process_latency.ns), 0); spa_alsa_add_prop_params(this, &b.b); param = spa_pod_builder_pop(&b.b, &f); break; default: return 0; } break; } case SPA_PARAM_IO: switch (result.index) { case 0: param = spa_pod_builder_add_object(&b.b, SPA_TYPE_OBJECT_ParamIO, id, SPA_PARAM_IO_id, SPA_POD_Id(SPA_IO_Clock), SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_clock))); break; case 1: param = spa_pod_builder_add_object(&b.b, SPA_TYPE_OBJECT_ParamIO, id, SPA_PARAM_IO_id, SPA_POD_Id(SPA_IO_Position), SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_position))); break; default: return 0; } break; case SPA_PARAM_ProcessLatency: switch (result.index) { case 0: param = spa_process_latency_build(&b.b, id, &this->process_latency); break; default: return 0; } break; default: return -ENOENT; } if (spa_pod_filter(&b.b, &result.param, param, filter) < 0) goto next; spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); if (++count != num) goto next; return 0; } static int impl_node_set_io(void *object, uint32_t id, void *data, size_t size) { struct state *this = object; spa_return_val_if_fail(this != NULL, -EINVAL); switch (id) { case SPA_IO_Clock: if (size > 0 && size < sizeof(struct spa_io_clock)) return -EINVAL; this->clock = data; break; case SPA_IO_Position: if (size > 0 && size < sizeof(struct spa_io_position)) return -EINVAL; this->position = data; break; default: return -ENOENT; } spa_alsa_reassign_follower(this); return 0; } static void handle_process_latency(struct state *this, const struct spa_process_latency_info *info) { bool ns_changed = this->process_latency.ns != info->ns; if (this->process_latency.quantum == info->quantum && this->process_latency.rate == info->rate && !ns_changed) return; this->process_latency = *info; this->info.change_mask |= SPA_NODE_CHANGE_MASK_PARAMS; if (ns_changed) this->params[NODE_Props].user++; this->params[NODE_ProcessLatency].user++; this->port_info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; this->port_params[PORT_Latency].user++; } static int impl_node_set_param(void *object, uint32_t id, uint32_t flags, const struct spa_pod *param) { struct state *this = object; int res; spa_return_val_if_fail(this != NULL, -EINVAL); switch (id) { case SPA_PARAM_Props: { struct props *p = &this->props; struct spa_pod *params = NULL; int64_t lat_ns = -1; if (param == NULL) { reset_props(p); return 0; } spa_pod_parse_object(param, SPA_TYPE_OBJECT_Props, NULL, SPA_PROP_device, SPA_POD_OPT_Stringn(p->device, sizeof(p->device)), SPA_PROP_latencyOffsetNsec, SPA_POD_OPT_Long(&lat_ns), SPA_PROP_params, SPA_POD_OPT_Pod(¶ms)); spa_alsa_parse_prop_params(this, params); if (lat_ns != -1) { struct spa_process_latency_info info; info = this->process_latency; info.ns = lat_ns; handle_process_latency(this, &info); } spa_alsa_emit_node_info(this, false); spa_alsa_emit_port_info(this, false); break; } case SPA_PARAM_ProcessLatency: { struct spa_process_latency_info info; if (param == NULL) spa_zero(info); else if ((res = spa_process_latency_parse(param, &info)) < 0) return res; handle_process_latency(this, &info); spa_alsa_emit_node_info(this, false); spa_alsa_emit_port_info(this, false); break; } default: return -ENOENT; } return 0; } static int impl_node_send_command(void *object, const struct spa_command *command) { struct state *this = object; int res; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(command != NULL, -EINVAL); switch (SPA_NODE_COMMAND_ID(command)) { case SPA_NODE_COMMAND_ParamBegin: if ((res = spa_alsa_open(this, NULL)) < 0) return res; break; case SPA_NODE_COMMAND_ParamEnd: if (this->have_format) return 0; if ((res = spa_alsa_close(this)) < 0) return res; break; case SPA_NODE_COMMAND_Start: if (!this->have_format) return -EIO; if (this->n_buffers == 0) return -EIO; this->want_started = true; if ((res = spa_alsa_start(this)) < 0) return res; break; case SPA_NODE_COMMAND_Pause: case SPA_NODE_COMMAND_Suspend: this->want_started = false; if ((res = spa_alsa_pause(this)) < 0) return res; break; default: return -ENOTSUP; } return 0; } static int impl_node_add_listener(void *object, struct spa_hook *listener, const struct spa_node_events *events, void *data) { struct state *this = object; struct spa_hook_list save; spa_return_val_if_fail(this != NULL, -EINVAL); spa_hook_list_isolate(&this->hooks, &save, listener, events, data); spa_alsa_emit_node_info(this, true); spa_alsa_emit_port_info(this, true); spa_hook_list_join(&this->hooks, &save); return 0; } static int impl_node_set_callbacks(void *object, const struct spa_node_callbacks *callbacks, void *data) { struct state *this = object; spa_return_val_if_fail(this != NULL, -EINVAL); this->callbacks = SPA_CALLBACKS_INIT(callbacks, data); return 0; } static int impl_node_sync(void *object, int seq) { struct state *this = object; spa_return_val_if_fail(this != NULL, -EINVAL); spa_node_emit_result(&this->hooks, seq, 0, 0, NULL); return 0; } static int impl_node_add_port(void *object, enum spa_direction direction, uint32_t port_id, const struct spa_dict *props) { return -ENOTSUP; } static int impl_node_remove_port(void *object, enum spa_direction direction, uint32_t port_id) { return -ENOTSUP; } static int impl_node_port_enum_params(void *object, int seq, enum spa_direction direction, uint32_t port_id, uint32_t id, uint32_t start, uint32_t num, const struct spa_pod *filter) { struct state *this = object; struct spa_pod *param; spa_auto(spa_pod_dynamic_builder) b = { 0 }; struct spa_pod_builder_state state; uint8_t buffer[1024]; struct spa_result_node_params result; uint32_t count = 0; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(num != 0, -EINVAL); spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); spa_pod_dynamic_builder_init(&b, buffer, sizeof(buffer), 4096); spa_pod_builder_get_state(&b.b, &state); result.id = id; result.next = start; next: result.index = result.next++; spa_pod_builder_reset(&b.b, &state); switch (id) { case SPA_PARAM_EnumFormat: return spa_alsa_enum_format(this, seq, start, num, filter); case SPA_PARAM_Format: if (!this->have_format) return -EIO; if (result.index > 0) return 0; param = spa_format_audio_raw_build(&b.b, id, &this->current_format.info.raw); break; case SPA_PARAM_Buffers: if (!this->have_format) return -EIO; if (result.index > 0) return 0; param = spa_pod_builder_add_object(&b.b, SPA_TYPE_OBJECT_ParamBuffers, id, SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(2, 2, MAX_BUFFERS), SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(this->blocks), SPA_PARAM_BUFFERS_size, SPA_POD_CHOICE_RANGE_Int( this->quantum_limit * this->frame_size, 16 * this->frame_size, INT32_MAX), SPA_PARAM_BUFFERS_stride, SPA_POD_Int(this->frame_size)); break; case SPA_PARAM_Meta: switch (result.index) { case 0: param = spa_pod_builder_add_object(&b.b, SPA_TYPE_OBJECT_ParamMeta, id, SPA_PARAM_META_type, SPA_POD_Id(SPA_META_Header), SPA_PARAM_META_size, SPA_POD_Int(sizeof(struct spa_meta_header))); break; default: return 0; } break; case SPA_PARAM_IO: switch (result.index) { case 0: param = spa_pod_builder_add_object(&b.b, SPA_TYPE_OBJECT_ParamIO, id, SPA_PARAM_IO_id, SPA_POD_Id(SPA_IO_Buffers), SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_buffers))); break; case 1: param = spa_pod_builder_add_object(&b.b, SPA_TYPE_OBJECT_ParamIO, id, SPA_PARAM_IO_id, SPA_POD_Id(SPA_IO_RateMatch), SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_rate_match))); break; default: return 0; } break; case SPA_PARAM_Latency: switch (result.index) { case 0: case 1: { struct spa_latency_info latency = this->latency[result.index]; if (latency.direction == SPA_DIRECTION_OUTPUT) spa_process_latency_info_add(&this->process_latency, &latency); param = spa_latency_build(&b.b, id, &latency); break; } default: return 0; } break; case SPA_PARAM_Tag: switch (result.index) { case 0: case 1: if ((param = this->tag[result.index]) == NULL) goto next; break; default: return 0; } break; default: return -ENOENT; } if (spa_pod_filter(&b.b, &result.param, param, filter) < 0) goto next; spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); if (++count != num) goto next; return 0; } static int clear_buffers(struct state *this) { if (this->n_buffers > 0) { spa_list_init(&this->free); spa_list_init(&this->ready); this->n_buffers = 0; } return 0; } static int port_set_format(void *object, enum spa_direction direction, uint32_t port_id, uint32_t flags, const struct spa_pod *format) { struct state *this = object; int err = 0; if (format == NULL) { if (!this->have_format) return 0; spa_log_debug(this->log, "clear format"); spa_alsa_close(this); clear_buffers(this); } else { struct spa_audio_info info = { 0 }; if ((err = spa_format_parse(format, &info.media_type, &info.media_subtype)) < 0) return err; if (info.media_type != SPA_MEDIA_TYPE_audio || info.media_subtype != SPA_MEDIA_SUBTYPE_raw) return -EINVAL; if (spa_format_audio_raw_parse(format, &info.info.raw) < 0) return -EINVAL; if ((err = spa_alsa_set_format(this, &info, flags)) < 0) return err; this->current_format = info; } this->info.change_mask |= SPA_NODE_CHANGE_MASK_PROPS; spa_alsa_emit_node_info(this, false); this->port_info.change_mask |= SPA_PORT_CHANGE_MASK_RATE; this->port_info.rate = SPA_FRACTION(1, this->rate); this->port_info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; if (this->have_format) { this->port_params[PORT_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_READWRITE); this->port_params[PORT_Buffers] = SPA_PARAM_INFO(SPA_PARAM_Buffers, SPA_PARAM_INFO_READ); this->port_params[PORT_Latency].user++; } else { this->port_params[PORT_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); this->port_params[PORT_Buffers] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); } spa_alsa_emit_port_info(this, false); return err; } static int impl_node_port_set_param(void *object, enum spa_direction direction, uint32_t port_id, uint32_t id, uint32_t flags, const struct spa_pod *param) { struct state *this = object; int res = 0; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); switch (id) { case SPA_PARAM_Format: res = port_set_format(this, direction, port_id, flags, param); break; case SPA_PARAM_Latency: { enum spa_direction other = SPA_DIRECTION_REVERSE(direction); struct spa_latency_info info; if (param == NULL) info = SPA_LATENCY_INFO(other); else if ((res = spa_latency_parse(param, &info)) < 0) return res; if (info.direction != other) return -EINVAL; this->latency[info.direction] = info; this->port_info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; this->port_params[PORT_Latency].user++; spa_alsa_emit_port_info(this, false); break; } case SPA_PARAM_Tag: { enum spa_direction other = SPA_DIRECTION_REVERSE(direction); if (param != NULL) { struct spa_tag_info info; void *state = NULL; if (spa_tag_parse(param, &info, &state) < 0 || info.direction != other) return -EINVAL; } if (spa_tag_compare(param, this->tag[other]) != 0) { free(this->tag[other]); this->tag[other] = param ? spa_pod_copy(param) : NULL; this->port_info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; this->port_params[PORT_Tag].user++; spa_alsa_emit_port_info(this, false); } break; } default: res = -ENOENT; break; } return res; } static int impl_node_port_use_buffers(void *object, enum spa_direction direction, uint32_t port_id, uint32_t flags, struct spa_buffer **buffers, uint32_t n_buffers) { struct state *this = object; int res; uint32_t i; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); spa_log_debug(this->log, "%p: use %d buffers", this, n_buffers); if (this->n_buffers > 0) { spa_alsa_pause(this); if ((res = clear_buffers(this)) < 0) return res; } if (n_buffers > 0 && !this->have_format) return -EIO; if (n_buffers > MAX_BUFFERS) return -ENOSPC; for (i = 0; i < n_buffers; i++) { struct buffer *b = &this->buffers[i]; struct spa_data *d = buffers[i]->datas; b->buf = buffers[i]; b->id = i; b->flags = 0; b->h = spa_buffer_find_meta_data(b->buf, SPA_META_Header, sizeof(*b->h)); if (d[0].data == NULL) { spa_log_error(this->log, "%p: need mapped memory", this); return -EINVAL; } spa_list_append(&this->free, &b->link); } this->n_buffers = n_buffers; return 0; } static int impl_node_port_set_io(void *object, enum spa_direction direction, uint32_t port_id, uint32_t id, void *data, size_t size) { struct state *this = object; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); spa_log_debug(this->log, "%p: io %d %p %zd", this, id, data, size); switch (id) { case SPA_IO_Buffers: this->io = data; break; case SPA_IO_RateMatch: this->rate_match = data; if (this->rate_match) spa_alsa_update_rate_match(this); break; default: return -ENOENT; } return 0; } static int impl_node_port_reuse_buffer(void *object, uint32_t port_id, uint32_t buffer_id) { struct state *this = object; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(port_id == 0, -EINVAL); if (this->n_buffers == 0) return -EIO; if (buffer_id >= this->n_buffers) return -EINVAL; spa_alsa_recycle_buffer(this, buffer_id); return 0; } static int impl_node_process(void *object) { struct state *this = object; struct spa_io_buffers *io; struct buffer *b; spa_return_val_if_fail(this != NULL, -EINVAL); if ((io = this->io) == NULL) return -EIO; spa_log_trace_fp(this->log, "%p; status %d", this, io->status); if (io->status == SPA_STATUS_HAVE_DATA) return SPA_STATUS_HAVE_DATA; if (io->buffer_id < this->n_buffers) { spa_alsa_recycle_buffer(this, io->buffer_id); io->buffer_id = SPA_ID_INVALID; } if (spa_list_is_empty(&this->ready) && this->following) { if (this->freewheel) spa_alsa_skip(this); else spa_alsa_read(this); } if (spa_list_is_empty(&this->ready) || !this->following) return SPA_STATUS_OK; b = spa_list_first(&this->ready, struct buffer, link); spa_list_remove(&b->link); SPA_FLAG_SET(b->flags, BUFFER_FLAG_OUT); spa_log_trace_fp(this->log, "%p: dequeue buffer %d", this, b->id); io->buffer_id = b->id; io->status = SPA_STATUS_HAVE_DATA; return SPA_STATUS_HAVE_DATA; } static const struct spa_node_methods impl_node = { SPA_VERSION_NODE_METHODS, .add_listener = impl_node_add_listener, .set_callbacks = impl_node_set_callbacks, .sync = impl_node_sync, .enum_params = impl_node_enum_params, .set_param = impl_node_set_param, .set_io = impl_node_set_io, .send_command = impl_node_send_command, .add_port = impl_node_add_port, .remove_port = impl_node_remove_port, .port_enum_params = impl_node_port_enum_params, .port_set_param = impl_node_port_set_param, .port_use_buffers = impl_node_port_use_buffers, .port_set_io = impl_node_port_set_io, .port_reuse_buffer = impl_node_port_reuse_buffer, .process = impl_node_process, }; static int impl_get_interface(struct spa_handle *handle, const char *type, void **interface) { struct state *this; spa_return_val_if_fail(handle != NULL, -EINVAL); spa_return_val_if_fail(interface != NULL, -EINVAL); this = (struct state *) handle; if (spa_streq(type, SPA_TYPE_INTERFACE_Node)) *interface = &this->node; else return -ENOENT; return 0; } static int impl_clear(struct spa_handle *handle) { struct state *this; spa_return_val_if_fail(handle != NULL, -EINVAL); this = (struct state *) handle; spa_alsa_close(this); spa_alsa_clear(this); return 0; } static size_t impl_get_size(const struct spa_handle_factory *factory, const struct spa_dict *params) { return sizeof(struct state); } static int impl_init(const struct spa_handle_factory *factory, struct spa_handle *handle, const struct spa_dict *info, const struct spa_support *support, uint32_t n_support) { struct state *this; spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(handle != NULL, -EINVAL); handle->get_interface = impl_get_interface; handle->clear = impl_clear; this = (struct state *) handle; this->log = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log); alsa_log_topic_init(this->log); this->data_system = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_DataSystem); this->data_loop = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_DataLoop); this->main_loop = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Loop); if (this->main_loop == NULL) { spa_log_error(this->log, "a main loop is needed"); return -EINVAL; } if (this->data_loop == NULL) { spa_log_error(this->log, "%p: a data loop is needed", this); return -EINVAL; } if (this->data_system == NULL) { spa_log_error(this->log, "%p: a data system is needed", this); return -EINVAL; } this->node.iface = SPA_INTERFACE_INIT(SPA_TYPE_INTERFACE_Node, SPA_VERSION_NODE, &impl_node, this); spa_hook_list_init(&this->hooks); this->stream = SND_PCM_STREAM_CAPTURE; this->port_direction = SPA_DIRECTION_OUTPUT; this->latency[this->port_direction] = SPA_LATENCY_INFO( this->port_direction, .min_quantum = 1.0f, .max_quantum = 1.0f); this->latency[SPA_DIRECTION_INPUT] = SPA_LATENCY_INFO(SPA_DIRECTION_INPUT); this->info_all = SPA_NODE_CHANGE_MASK_FLAGS | SPA_NODE_CHANGE_MASK_PROPS | SPA_NODE_CHANGE_MASK_PARAMS; this->info.max_output_ports = 1; this->info.flags = SPA_NODE_FLAG_RT; this->params[NODE_PropInfo] = SPA_PARAM_INFO(SPA_PARAM_PropInfo, SPA_PARAM_INFO_READ); this->params[NODE_Props] = SPA_PARAM_INFO(SPA_PARAM_Props, SPA_PARAM_INFO_READWRITE); this->params[NODE_IO] = SPA_PARAM_INFO(SPA_PARAM_IO, SPA_PARAM_INFO_READ); this->params[NODE_ProcessLatency] = SPA_PARAM_INFO(SPA_PARAM_ProcessLatency, SPA_PARAM_INFO_READWRITE); this->info.params = this->params; this->info.n_params = N_NODE_PARAMS; reset_props(&this->props); this->port_info_all = SPA_PORT_CHANGE_MASK_FLAGS | SPA_PORT_CHANGE_MASK_PARAMS; this->port_info = SPA_PORT_INFO_INIT(); this->port_info.flags = SPA_PORT_FLAG_LIVE | SPA_PORT_FLAG_PHYSICAL | SPA_PORT_FLAG_TERMINAL; this->port_params[PORT_EnumFormat] = SPA_PARAM_INFO(SPA_PARAM_EnumFormat, SPA_PARAM_INFO_READ); this->port_params[PORT_Meta] = SPA_PARAM_INFO(SPA_PARAM_Meta, SPA_PARAM_INFO_READ); this->port_params[PORT_IO] = SPA_PARAM_INFO(SPA_PARAM_IO, SPA_PARAM_INFO_READ); this->port_params[PORT_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); this->port_params[PORT_Buffers] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); this->port_params[PORT_Latency] = SPA_PARAM_INFO(SPA_PARAM_Latency, SPA_PARAM_INFO_READWRITE); this->port_params[PORT_Tag] = SPA_PARAM_INFO(SPA_PARAM_Tag, SPA_PARAM_INFO_READWRITE); this->port_info.params = this->port_params; this->port_info.n_params = N_PORT_PARAMS; spa_list_init(&this->free); spa_list_init(&this->ready); return spa_alsa_init(this, info); } static const struct spa_interface_info impl_interfaces[] = { {SPA_TYPE_INTERFACE_Node,}, }; static int impl_enum_interface_info(const struct spa_handle_factory *factory, const struct spa_interface_info **info, uint32_t *index) { spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(info != NULL, -EINVAL); spa_return_val_if_fail(index != NULL, -EINVAL); if (*index >= SPA_N_ELEMENTS(impl_interfaces)) return 0; *info = &impl_interfaces[(*index)++]; return 1; } static const struct spa_dict_item info_items[] = { { SPA_KEY_FACTORY_AUTHOR, "Wim Taymans " }, { SPA_KEY_FACTORY_DESCRIPTION, "Record audio with the alsa API" }, { SPA_KEY_FACTORY_USAGE, "["SPA_KEY_API_ALSA_PATH"=]" }, }; static const struct spa_dict info = SPA_DICT_INIT_ARRAY(info_items); const struct spa_handle_factory spa_alsa_source_factory = { SPA_VERSION_HANDLE_FACTORY, SPA_NAME_API_ALSA_PCM_SOURCE, &info, impl_get_size, impl_init, impl_enum_interface_info, }; pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/alsa-pcm.c000066400000000000000000003561771511204443500255460ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "alsa-pcm.h" static struct spa_list cards = SPA_LIST_INIT(&cards); static struct spa_list states = SPA_LIST_INIT(&states); #define SPA_ALSA_DLL_BW_MIN 0.001 static struct card *find_card(uint32_t index) { struct card *c; spa_list_for_each(c, &cards, link) { if (c->index == index) { c->ref++; return c; } } return NULL; } static struct card *ensure_card(uint32_t index, bool ucm, bool ucm_split) { struct card *c; char card_name[128]; const char *alibpref = NULL; int err; if (index == SPA_ID_INVALID) return NULL; if ((c = find_card(index)) != NULL) return c; c = calloc(1, sizeof(*c)); c->ref = 1; c->index = index; if (ucm) { const char *split_prefix = ucm_split ? "<<>>" : ""; snprintf(card_name, sizeof(card_name), "%shw:%i", split_prefix, index); err = snd_use_case_mgr_open(&c->ucm, card_name); if (err < 0) { char *name; err = snd_card_get_name(index, &name); if (err < 0) goto error; snprintf(card_name, sizeof(card_name), "%s%s", split_prefix, name); free(name); err = snd_use_case_mgr_open(&c->ucm, card_name); if (err < 0) goto error; } if ((snd_use_case_get(c->ucm, "_alibpref", &alibpref) != 0)) alibpref = NULL; c->ucm_prefix = (char*)alibpref; } spa_list_append(&cards, &c->link); return c; error: free(c); errno = -err; return NULL; } static void release_card(struct card *c) { if (!c) return; spa_assert(c->ref > 0); if (--c->ref > 0) return; spa_list_remove(&c->link); if (c->ucm) { free(c->ucm_prefix); snd_use_case_mgr_close(c->ucm); } free(c); } #define CHECK(s,msg,...) if ((err = (s)) < 0) { spa_log_error(state->log, msg ": %s", ##__VA_ARGS__, snd_strerror(err)); return err; } static int write_bind_ctl_param(struct state *state, const char *name, const char *param) { int err; unsigned int count, idx; char _name[1024]; for (unsigned int i = 0; i < state->num_bind_ctls; i++) { snd_ctl_elem_info_t *info = state->bound_ctls[i].info; bool changed = false; int type; if(!state->bound_ctls[i].value || !info) continue; snprintf(_name, sizeof(_name), "api.alsa.bind-ctl.%s", snd_ctl_elem_info_get_name(info)); if (!spa_streq(name, _name)) continue; type = snd_ctl_elem_info_get_type(info); count = snd_ctl_elem_info_get_count(info); switch (type) { case SND_CTL_ELEM_TYPE_BOOLEAN: { bool b = spa_atob(param); for (idx = 0; idx < count; idx++) snd_ctl_elem_value_set_boolean(state->bound_ctls[i].value, idx, b); changed = true; } break; case SND_CTL_ELEM_TYPE_INTEGER: { long l = (long) atoi(param); for (idx = 0; idx < count; idx++) snd_ctl_elem_value_set_integer(state->bound_ctls[i].value, idx, l); changed = true; } break; default: spa_log_warn(state->log, "%s ctl '%s' not supported", snd_ctl_elem_type_name(snd_ctl_elem_info_get_type(info)), snd_ctl_elem_info_get_name(info)); break; } if(changed) CHECK(snd_ctl_elem_write(state->ctl, state->bound_ctls[i].value), "snd_ctl_elem_write"); return 0; } return 0; } static int alsa_set_param(struct state *state, const char *k, const char *s) { int fmt_change = 0; if (spa_streq(k, SPA_KEY_AUDIO_CHANNELS)) { state->default_channels = atoi(s); if (state->default_channels > MAX_CHANNELS) { spa_log_warn(state->log, "%p: %s: %s > %d, clamping", state, k, s, MAX_CHANNELS); state->default_channels = MAX_CHANNELS; } fmt_change++; } else if (spa_streq(k, SPA_KEY_AUDIO_RATE)) { state->default_rate = atoi(s); fmt_change++; } else if (spa_streq(k, SPA_KEY_AUDIO_FORMAT)) { state->default_format = spa_type_audio_format_from_short_name(s); fmt_change++; } else if (spa_streq(k, SPA_KEY_AUDIO_POSITION)) { spa_alsa_parse_position(&state->default_pos, s, strlen(s)); fmt_change++; } else if (spa_streq(k, SPA_KEY_AUDIO_ALLOWED_RATES)) { state->n_allowed_rates = spa_alsa_parse_rates(state->allowed_rates, MAX_RATES, s, strlen(s)); fmt_change++; } else if (spa_streq(k, "iec958.codecs")) { spa_alsa_parse_iec958_codecs(&state->iec958_codecs, s, strlen(s)); fmt_change++; } else if (spa_streq(k, "api.alsa.period-size")) { state->default_period_size = atoi(s); } else if (spa_streq(k, "api.alsa.period-num")) { state->default_period_num = atoi(s); } else if (spa_streq(k, "api.alsa.headroom")) { state->default_headroom = atoi(s); } else if (spa_streq(k, "api.alsa.start-delay")) { state->default_start_delay = atoi(s); } else if (spa_streq(k, "api.alsa.disable-mmap")) { state->disable_mmap = spa_atob(s); } else if (spa_streq(k, "api.alsa.disable-batch")) { state->disable_batch = spa_atob(s); } else if (spa_streq(k, "api.alsa.disable-tsched")) { state->disable_tsched = spa_atob(s); } else if (spa_streq(k, "api.alsa.dll-bandwidth-max")) { state->dll_bw_max = SPA_CLAMPD(spa_strtod(s, NULL), SPA_ALSA_DLL_BW_MIN, SPA_DLL_BW_MAX); } else if (spa_streq(k, "api.alsa.use-chmap")) { state->props.use_chmap = spa_atob(s); } else if (spa_streq(k, "api.alsa.multi-rate")) { state->multi_rate = spa_atob(s); } else if (spa_streq(k, "api.alsa.htimestamp")) { state->htimestamp = spa_atob(s); } else if (spa_streq(k, "api.alsa.htimestamp.max-errors")) { state->htimestamp_max_errors = atoi(s); } else if (spa_streq(k, "api.alsa.auto-link")) { state->auto_link = spa_atob(s); } else if (spa_streq(k, "api.alsa.dsd-lsb")) { state->dsd_lsb = spa_atob(s); } else if (spa_streq(k, "latency.internal.rate")) { state->process_latency.rate = atoi(s); } else if (spa_streq(k, "latency.internal.ns")) { state->process_latency.ns = atoi(s); } else if (spa_streq(k, "clock.name")) { spa_scnprintf(state->clock_name, sizeof(state->clock_name), "%s", s); } else if (spa_strstartswith(k, "api.alsa.bind-ctl.")) { write_bind_ctl_param(state, k, s); fmt_change++; } else if (spa_streq(k, SPA_KEY_MEDIA_CLASS)) { spa_scnprintf(state->props.media_class, sizeof(state->props.media_class), "%s", s); } else if (spa_streq(k, "api.alsa.split.parent")) { state->is_split_parent = true; } else return 0; if (fmt_change > 0) { state->port_info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; state->port_params[PORT_EnumFormat].user++; } return 1; } static int position_to_string(struct channel_map *map, char *val, size_t len) { uint32_t i; char pos[8]; struct spa_strbuf b; spa_strbuf_init(&b, val, len); spa_strbuf_append(&b, "["); for (i = 0; i < map->n_pos; i++) { spa_strbuf_append(&b, "%s%s", i == 0 ? " " : ", ", spa_type_audio_channel_make_short_name(map->pos[i], pos, sizeof(pos), "UNK")); } if (spa_strbuf_append(&b, " ]") < 2) return -ENOSPC; return 0; } static int uint32_array_to_string(uint32_t *vals, uint32_t n_vals, char *val, size_t len) { uint32_t i; struct spa_strbuf b; spa_strbuf_init(&b, val, len); spa_strbuf_append(&b, "["); for (i = 0; i < n_vals; i++) spa_strbuf_append(&b, "%s%d", i == 0 ? " " : ", ", vals[i]); if (spa_strbuf_append(&b, " ]") < 2) return -ENOSPC; return 0; } static struct spa_pod *enum_bind_ctl_propinfo(struct state *state, uint32_t idx, struct spa_pod_builder *b) { char param_name[1024]; char param_desc[1024]; snd_ctl_elem_info_t *info = state->bound_ctls[idx].info; if (!info) { // This will end iteration early, so print a warning spa_log_warn(state->log, "Don't have prop info for bind ctl, bailing"); return NULL; } snprintf(param_name, sizeof(param_name), "api.alsa.bind-ctl.%s", snd_ctl_elem_info_get_name(info)); snprintf(param_desc, sizeof(param_desc), "Value of ALSA control '%s'", snd_ctl_elem_info_get_name(info)); // We don't have meaningful default values switch (snd_ctl_elem_info_get_type(info)) { case SND_CTL_ELEM_TYPE_BOOLEAN: return spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_PropInfo, SPA_PARAM_PropInfo, SPA_PROP_INFO_name, SPA_POD_String(param_name), SPA_PROP_INFO_description, SPA_POD_String(param_desc), SPA_PROP_INFO_type, SPA_POD_Bool(false), SPA_PROP_INFO_params, SPA_POD_Bool(true)); case SND_CTL_ELEM_TYPE_INTEGER: return spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_PropInfo, SPA_PARAM_PropInfo, SPA_PROP_INFO_name, SPA_POD_String(param_name), SPA_PROP_INFO_description, SPA_POD_String(param_desc), SPA_PROP_INFO_type, SPA_POD_Int(0), SPA_PROP_INFO_params, SPA_POD_Bool(true)); break; case SND_CTL_ELEM_TYPE_INTEGER64: return spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_PropInfo, SPA_PARAM_PropInfo, SPA_PROP_INFO_name, SPA_POD_String(param_name), SPA_PROP_INFO_description, SPA_POD_String(param_desc), SPA_PROP_INFO_type, SPA_POD_Long(0), SPA_PROP_INFO_params, SPA_POD_Bool(true)); break; case SND_CTL_ELEM_TYPE_ENUMERATED: return spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_PropInfo, SPA_PARAM_PropInfo, SPA_PROP_INFO_name, SPA_POD_String(param_name), SPA_PROP_INFO_description, SPA_POD_String(param_desc), SPA_PROP_INFO_type, SPA_POD_Int(0), SPA_PROP_INFO_params, SPA_POD_Bool(true)); break; default: // FIXME: we can probably support bytes but the length seems unknown in the API spa_log_warn(state->log, "%s ctl '%s' not supported", snd_ctl_elem_type_name(snd_ctl_elem_info_get_type(info)), snd_ctl_elem_info_get_name(info)); return NULL; } } struct spa_pod *spa_alsa_enum_propinfo(struct state *state, uint32_t idx, struct spa_pod_builder *b) { struct spa_pod *param; switch (idx) { case 0: param = spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_PropInfo, SPA_PARAM_PropInfo, SPA_PROP_INFO_name, SPA_POD_String(SPA_KEY_AUDIO_CHANNELS), SPA_PROP_INFO_description, SPA_POD_String("Audio Channels"), SPA_PROP_INFO_type, SPA_POD_Int(state->default_channels), SPA_PROP_INFO_params, SPA_POD_Bool(true)); break; case 1: param = spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_PropInfo, SPA_PARAM_PropInfo, SPA_PROP_INFO_name, SPA_POD_String(SPA_KEY_AUDIO_RATE), SPA_PROP_INFO_description, SPA_POD_String("Audio Rate"), SPA_PROP_INFO_type, SPA_POD_Int(state->default_rate), SPA_PROP_INFO_params, SPA_POD_Bool(true)); break; case 2: param = spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_PropInfo, SPA_PARAM_PropInfo, SPA_PROP_INFO_name, SPA_POD_String(SPA_KEY_AUDIO_FORMAT), SPA_PROP_INFO_description, SPA_POD_String("Audio Format"), SPA_PROP_INFO_type, SPA_POD_String( spa_debug_type_find_short_name(spa_type_audio_format, state->default_format)), SPA_PROP_INFO_params, SPA_POD_Bool(true)); break; case 3: { char buf[1024]; position_to_string(&state->default_pos, buf, sizeof(buf)); param = spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_PropInfo, SPA_PARAM_PropInfo, SPA_PROP_INFO_name, SPA_POD_String(SPA_KEY_AUDIO_POSITION), SPA_PROP_INFO_description, SPA_POD_String("Audio Position"), SPA_PROP_INFO_type, SPA_POD_String(buf), SPA_PROP_INFO_params, SPA_POD_Bool(true)); break; } case 4: { char buf[1024]; uint32_array_to_string(state->allowed_rates, state->n_allowed_rates, buf, sizeof(buf)); param = spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_PropInfo, SPA_PARAM_PropInfo, SPA_PROP_INFO_name, SPA_POD_String(SPA_KEY_AUDIO_ALLOWED_RATES), SPA_PROP_INFO_description, SPA_POD_String("Audio Allowed Rates"), SPA_PROP_INFO_type, SPA_POD_String(buf), SPA_PROP_INFO_params, SPA_POD_Bool(true)); break; } case 5: param = spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_PropInfo, SPA_PARAM_PropInfo, SPA_PROP_INFO_name, SPA_POD_String("api.alsa.period-size"), SPA_PROP_INFO_description, SPA_POD_String("Period Size"), SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Int(state->default_period_size, 0, 8192), SPA_PROP_INFO_params, SPA_POD_Bool(true)); break; case 6: param = spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_PropInfo, SPA_PARAM_PropInfo, SPA_PROP_INFO_name, SPA_POD_String("api.alsa.period-num"), SPA_PROP_INFO_description, SPA_POD_String("Number of Periods"), SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Int(state->default_period_num, 0, 1024), SPA_PROP_INFO_params, SPA_POD_Bool(true)); break; case 7: param = spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_PropInfo, SPA_PARAM_PropInfo, SPA_PROP_INFO_name, SPA_POD_String("api.alsa.headroom"), SPA_PROP_INFO_description, SPA_POD_String("Headroom"), SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Int(state->default_headroom, 0, 8192), SPA_PROP_INFO_params, SPA_POD_Bool(true)); break; case 8: param = spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_PropInfo, SPA_PARAM_PropInfo, SPA_PROP_INFO_name, SPA_POD_String("api.alsa.start-delay"), SPA_PROP_INFO_description, SPA_POD_String("Start Delay"), SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Int(state->default_start_delay, 0, 8192), SPA_PROP_INFO_params, SPA_POD_Bool(true)); break; case 9: param = spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_PropInfo, SPA_PARAM_PropInfo, SPA_PROP_INFO_name, SPA_POD_String("api.alsa.disable-mmap"), SPA_PROP_INFO_description, SPA_POD_String("Disable MMAP"), SPA_PROP_INFO_type, SPA_POD_CHOICE_Bool(state->disable_mmap), SPA_PROP_INFO_params, SPA_POD_Bool(true)); break; case 10: param = spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_PropInfo, SPA_PARAM_PropInfo, SPA_PROP_INFO_name, SPA_POD_String("api.alsa.disable-batch"), SPA_PROP_INFO_description, SPA_POD_String("Disable Batch"), SPA_PROP_INFO_type, SPA_POD_CHOICE_Bool(state->disable_batch), SPA_PROP_INFO_params, SPA_POD_Bool(true)); break; case 11: param = spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_PropInfo, SPA_PARAM_PropInfo, SPA_PROP_INFO_name, SPA_POD_String("api.alsa.disable-tsched"), SPA_PROP_INFO_description, SPA_POD_String("Disable timer based scheduling"), SPA_PROP_INFO_type, SPA_POD_CHOICE_Bool(state->disable_tsched), SPA_PROP_INFO_params, SPA_POD_Bool(true)); break; case 12: param = spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_PropInfo, SPA_PARAM_PropInfo, SPA_PROP_INFO_name, SPA_POD_String("api.alsa.use-chmap"), SPA_PROP_INFO_description, SPA_POD_String("Use the driver channelmap"), SPA_PROP_INFO_type, SPA_POD_CHOICE_Bool(state->props.use_chmap), SPA_PROP_INFO_params, SPA_POD_Bool(true)); break; case 13: param = spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_PropInfo, SPA_PARAM_PropInfo, SPA_PROP_INFO_name, SPA_POD_String("api.alsa.multi-rate"), SPA_PROP_INFO_description, SPA_POD_String("Support multiple rates"), SPA_PROP_INFO_type, SPA_POD_CHOICE_Bool(state->multi_rate), SPA_PROP_INFO_params, SPA_POD_Bool(true)); break; case 14: param = spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_PropInfo, SPA_PARAM_PropInfo, SPA_PROP_INFO_name, SPA_POD_String("api.alsa.htimestamp"), SPA_PROP_INFO_description, SPA_POD_String("Use hires timestamps"), SPA_PROP_INFO_type, SPA_POD_CHOICE_Bool(state->htimestamp), SPA_PROP_INFO_params, SPA_POD_Bool(true)); break; case 15: param = spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_PropInfo, SPA_PARAM_PropInfo, SPA_PROP_INFO_name, SPA_POD_String("latency.internal.rate"), SPA_PROP_INFO_description, SPA_POD_String("Internal latency in samples"), SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Int(state->process_latency.rate, 0, 65536), SPA_PROP_INFO_params, SPA_POD_Bool(true)); break; case 16: param = spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_PropInfo, SPA_PARAM_PropInfo, SPA_PROP_INFO_name, SPA_POD_String("latency.internal.ns"), SPA_PROP_INFO_description, SPA_POD_String("Internal latency in nanoseconds"), SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Long(state->process_latency.ns, 0LL, 2 * SPA_NSEC_PER_SEC), SPA_PROP_INFO_params, SPA_POD_Bool(true)); break; case 17: param = spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_PropInfo, SPA_PARAM_PropInfo, SPA_PROP_INFO_name, SPA_POD_String("clock.name"), SPA_PROP_INFO_description, SPA_POD_String("The name of the clock"), SPA_PROP_INFO_type, SPA_POD_String(state->clock_name), SPA_PROP_INFO_params, SPA_POD_Bool(true)); break; case 18: param = spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_PropInfo, SPA_PARAM_PropInfo, SPA_PROP_INFO_name, SPA_POD_String("api.alsa.htimestamp.max-errors"), SPA_PROP_INFO_description, SPA_POD_String("Max errors before disabling htimestamp"), SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Int(state->htimestamp_max_errors, 0, INT32_MAX), SPA_PROP_INFO_params, SPA_POD_Bool(true)); break; // While adding params here, update the math in default too default: idx -= 18; if (idx <= state->num_bind_ctls) param = enum_bind_ctl_propinfo(state, idx - 1, b); else return NULL; } return param; } static void add_bind_ctl_param(struct state *state, const snd_ctl_elem_value_t *elem, const snd_ctl_elem_info_t *info, struct spa_pod_builder *b) { struct spa_pod_frame f[1]; char param_name[1024]; unsigned int count = snd_ctl_elem_info_get_count(info); int type = snd_ctl_elem_info_get_type(info); bool is_array = count > 1 && type != SND_CTL_ELEM_TYPE_BYTES; snprintf(param_name, sizeof(param_name), "api.alsa.bind-ctl.%s", snd_ctl_elem_info_get_name(info)); spa_pod_builder_string(b, param_name); if (is_array) spa_pod_builder_push_array(b, &f[0]); switch (type) { case SND_CTL_ELEM_TYPE_BOOLEAN: for (unsigned int i = 0; i < count; i++) spa_pod_builder_bool(b, snd_ctl_elem_value_get_boolean(elem, i)); break; case SND_CTL_ELEM_TYPE_INTEGER: for (unsigned int i = 0; i < count; i++) spa_pod_builder_int(b, snd_ctl_elem_value_get_integer(elem, i)); break; case SND_CTL_ELEM_TYPE_INTEGER64: for (unsigned int i = 0; i < count; i++) spa_pod_builder_long(b, snd_ctl_elem_value_get_integer64(elem, i)); break; case SND_CTL_ELEM_TYPE_ENUMERATED: for (unsigned int i = 0; i < count; i++) spa_pod_builder_int(b, snd_ctl_elem_value_get_enumerated(elem, i)); break; case SND_CTL_ELEM_TYPE_BYTES: spa_pod_builder_bytes(b, snd_ctl_elem_value_get_bytes(elem), count); break; default: spa_log_warn(state->log, "%s ctl '%s' not supported", snd_ctl_elem_type_name(snd_ctl_elem_info_get_type(info)), snd_ctl_elem_info_get_name(info)); break; } if (is_array) spa_pod_builder_pop(b, &f[0]); } static void add_bind_ctl_params(struct state *state, struct spa_pod_builder *b) { int err; for (unsigned int i = 0; i < state->num_bind_ctls; i++) { if(!state->bound_ctls[i].value || !state->bound_ctls[i].info) continue; err = snd_ctl_elem_read(state->ctl, state->bound_ctls[i].value); if (err < 0) { spa_log_warn(state->log, "Could not read elem value for '%s': %s", state->bound_ctls[i].name, snd_strerror(err)); } add_bind_ctl_param(state, state->bound_ctls[i].value, state->bound_ctls[i].info, b); } } int spa_alsa_add_prop_params(struct state *state, struct spa_pod_builder *b) { struct spa_pod_frame f[1]; char buf[1024]; spa_pod_builder_prop(b, SPA_PROP_params, 0); spa_pod_builder_push_struct(b, &f[0]); spa_pod_builder_string(b, SPA_KEY_AUDIO_CHANNELS); spa_pod_builder_int(b, state->default_channels); spa_pod_builder_string(b, SPA_KEY_AUDIO_RATE); spa_pod_builder_int(b, state->default_rate); spa_pod_builder_string(b, SPA_KEY_AUDIO_FORMAT); spa_pod_builder_string(b, spa_debug_type_find_short_name(spa_type_audio_format, state->default_format)); position_to_string(&state->default_pos, buf, sizeof(buf)); spa_pod_builder_string(b, SPA_KEY_AUDIO_POSITION); spa_pod_builder_string(b, buf); uint32_array_to_string(state->allowed_rates, state->n_allowed_rates, buf, sizeof(buf)); spa_pod_builder_string(b, SPA_KEY_AUDIO_ALLOWED_RATES); spa_pod_builder_string(b, buf); spa_pod_builder_string(b, "api.alsa.period-size"); spa_pod_builder_int(b, state->default_period_size); spa_pod_builder_string(b, "api.alsa.period-num"); spa_pod_builder_int(b, state->default_period_num); spa_pod_builder_string(b, "api.alsa.headroom"); spa_pod_builder_int(b, state->default_headroom); spa_pod_builder_string(b, "api.alsa.start-delay"); spa_pod_builder_int(b, state->default_start_delay); spa_pod_builder_string(b, "api.alsa.disable-mmap"); spa_pod_builder_bool(b, state->disable_mmap); spa_pod_builder_string(b, "api.alsa.disable-batch"); spa_pod_builder_bool(b, state->disable_batch); spa_pod_builder_string(b, "api.alsa.disable-tsched"); spa_pod_builder_bool(b, state->disable_tsched); spa_pod_builder_string(b, "api.alsa.use-chmap"); spa_pod_builder_bool(b, state->props.use_chmap); spa_pod_builder_string(b, "api.alsa.multi-rate"); spa_pod_builder_bool(b, state->multi_rate); spa_pod_builder_string(b, "api.alsa.htimestamp"); spa_pod_builder_bool(b, state->htimestamp); spa_pod_builder_string(b, "api.alsa.htimestamp.max-errors"); spa_pod_builder_int(b, state->htimestamp_max_errors); spa_pod_builder_string(b, "latency.internal.rate"); spa_pod_builder_int(b, state->process_latency.rate); spa_pod_builder_string(b, "latency.internal.ns"); spa_pod_builder_long(b, state->process_latency.ns); spa_pod_builder_string(b, "clock.name"); spa_pod_builder_string(b, state->clock_name); add_bind_ctl_params(state, b); spa_pod_builder_pop(b, &f[0]); return 0; } int spa_alsa_parse_prop_params(struct state *state, struct spa_pod *params) { struct spa_pod_parser prs; struct spa_pod_frame f; int changed = 0; if (params == NULL) return 0; spa_pod_parser_pod(&prs, params); if (spa_pod_parser_push_struct(&prs, &f) < 0) return 0; while (true) { const char *name; struct spa_pod *pod; char value[512]; if (spa_pod_parser_get_string(&prs, &name) < 0) break; if (spa_pod_parser_get_pod(&prs, &pod) < 0) break; if (spa_pod_is_string(pod)) { spa_pod_copy_string(pod, sizeof(value), value); } else if (spa_pod_is_int(pod)) { snprintf(value, sizeof(value), "%d", SPA_POD_VALUE(struct spa_pod_int, pod)); } else if (spa_pod_is_long(pod)) { snprintf(value, sizeof(value), "%"PRIi64, SPA_POD_VALUE(struct spa_pod_long, pod)); } else if (spa_pod_is_bool(pod)) { snprintf(value, sizeof(value), "%s", SPA_POD_VALUE(struct spa_pod_bool, pod) ? "true" : "false"); } else continue; spa_log_info(state->log, "key:'%s' val:'%s'", name, value); alsa_set_param(state, name, value); changed++; } if (changed > 0) { state->info.change_mask |= SPA_NODE_CHANGE_MASK_PARAMS; state->params[NODE_Props].user++; } return changed; } static ssize_t log_write(void *cookie, const char *buf, size_t size) { struct state *state = cookie; int len; while (size > 0) { len = strcspn(buf, "\n"); if (len > 0) spa_log_debug(state->log, "%.*s", (int)len, buf); buf += len + 1; size -= len + 1; } return size; } static cookie_io_functions_t io_funcs = { .write = log_write, }; static void silence_error_handler(const char *file, int line, const char *function, int err, const char *fmt, ...) { } static void fill_device_name(struct state *state, const char *params, char device_name[], size_t len) { spa_scnprintf(device_name, len, "%s%s%s", state->card && state->card->ucm_prefix ? state->card->ucm_prefix : "", state->props.device, params ? params : ""); } static void bind_ctl_event(struct spa_source *source) { struct state *state = source->data; snd_ctl_event_t *ev; snd_ctl_elem_id_t *id, *bound_id; snd_ctl_elem_value_t *old_value; unsigned short revents; int err; // Do the same demangling of revents we do for PCM pollfds for (int i = 0; i < state->ctl_n_fds; i++) { state->ctl_pfds[i].revents = state->ctl_sources[i].rmask; state->ctl_sources[i].rmask = 0; } err = snd_ctl_poll_descriptors_revents(state->ctl, state->ctl_pfds, state->ctl_n_fds, &revents); if (SPA_UNLIKELY(err < 0)) { spa_log_warn(state->log, "Could not read ctl revents: %s", snd_strerror(err)); return; } if (!revents) { spa_log_trace(state->log, "Got a bind ctl wakeup but no actual event"); return; } snd_ctl_event_alloca(&ev); snd_ctl_elem_id_alloca(&id); snd_ctl_elem_id_alloca(&bound_id); snd_ctl_elem_value_alloca(&old_value); while ((err = snd_ctl_read(state->ctl, ev)) > 0) { bool changed = false; if (snd_ctl_event_get_type(ev) != SND_CTL_EVENT_ELEM) continue; snd_ctl_event_elem_get_id(ev, id); for (unsigned int i = 0; i < state->num_bind_ctls; i++) { int err; if(!state->bound_ctls[i].value || !state->bound_ctls[i].info) continue; // Check if we have the right element snd_ctl_elem_value_get_id(state->bound_ctls[i].value, bound_id); if (snd_ctl_elem_id_compare_set(id, bound_id) || snd_ctl_elem_id_compare_numid(id, bound_id)) { continue; } snd_ctl_elem_value_copy(old_value, state->bound_ctls[i].value); err = snd_ctl_elem_read(state->ctl, state->bound_ctls[i].value); if (err < 0) { spa_log_warn(state->log, "Could not read ctl '%s': %s", state->bound_ctls[i].name, snd_strerror(err)); continue; } if (snd_ctl_elem_value_compare(old_value, state->bound_ctls[i].value) != 0) { // We don't need to check all the ctls, if one changed, // we'll emit a notification and they'll be read when // the props are read spa_log_debug(state->log, "bound ctl '%s' has changed", state->bound_ctls[i].name); changed = true; break; } } if (changed) { state->info.change_mask |= SPA_NODE_CHANGE_MASK_PARAMS; state->params[NODE_Props].user++; spa_alsa_emit_node_info(state, false); } } if (err < 0 && err != -EAGAIN) spa_log_warn(state->log, "Could not read ctl: %s", snd_strerror(err)); } static void fetch_bind_ctls(struct state *state) { snd_ctl_elem_list_t* element_list; unsigned int elem_count = 0; int err; if (!state->num_bind_ctls) return; snd_ctl_elem_list_alloca(&element_list); /* Get number of elements */ err = snd_ctl_elem_list(state->ctl, element_list); if (SPA_UNLIKELY(err < 0)) { spa_log_warn(state->log, "Couldn't get elem list count. Error: %s", snd_strerror(err)); return; } elem_count = snd_ctl_elem_list_get_count(element_list); err = snd_ctl_elem_list_alloc_space(element_list, elem_count); if (SPA_UNLIKELY(err < 0)) { spa_log_error(state->log, "Couldn't allocate elem_list space. Error: %s", snd_strerror(err)); return; } /* Get identifiers */ err = snd_ctl_elem_list(state->ctl, element_list); if (SPA_UNLIKELY(err < 0)) { spa_log_warn(state->log, "Couldn't get elem list. Error: %s", snd_strerror(err)); goto cleanup; } for (unsigned int i = 0; i < state->num_bind_ctls; i++) { unsigned int numid = 0; for (unsigned int j = 0; j < elem_count; j++) { const char* element_name = snd_ctl_elem_list_get_name(element_list, j); if (!strcmp(element_name, state->bound_ctls[i].name)) { numid = snd_ctl_elem_list_get_numid(element_list, j); break; } } /* zero = invalid numid */ if (SPA_UNLIKELY(!numid)) { spa_log_warn(state->log, "Didn't find ctl: '%s', count: %u", state->bound_ctls[i].name, elem_count); continue; } snd_ctl_elem_info_malloc(&state->bound_ctls[i].info); snd_ctl_elem_info_set_numid(state->bound_ctls[i].info, numid); err = snd_ctl_elem_info(state->ctl, state->bound_ctls[i].info); if (SPA_UNLIKELY(err < 0)) { spa_log_warn(state->log, "Could not read elem info for '%s': %s", state->bound_ctls[i].name, snd_strerror(err)); snd_ctl_elem_info_free(state->bound_ctls[i].info); state->bound_ctls[i].info = NULL; continue; } snd_ctl_elem_value_malloc(&state->bound_ctls[i].value); snd_ctl_elem_value_set_numid(state->bound_ctls[i].value, numid); spa_log_debug(state->log, "Binding ctl for '%s'", snd_ctl_elem_info_get_name(state->bound_ctls[i].info)); } cleanup: snd_ctl_elem_list_free_space(element_list); } int open_card_ctl(struct state *state) { int err; char card_name[256]; snprintf(card_name, sizeof(card_name), "hw:%d", state->card_index); spa_log_debug(state->log, "Trying to open ctl device '%s'", card_name); err = snd_ctl_open(&state->ctl, card_name, SND_CTL_NONBLOCK); if (err < 0) { spa_log_info(state->log, "%s could not find ctl card: %s", card_name, snd_strerror(err)); return err; } return 0; } static void bind_ctls_for_params(struct state *state) { int err; if (state->num_bind_ctls == 0) return; if (!state->ctl) { err = open_card_ctl(state); if (err < 0) return; } state->ctl_n_fds = snd_ctl_poll_descriptors_count(state->ctl); if (state->ctl_n_fds > (int)SPA_N_ELEMENTS(state->ctl_sources)) { spa_log_warn(state->log, "Too many poll descriptors (%d), listening to a subset", state->ctl_n_fds); state->ctl_n_fds = SPA_N_ELEMENTS(state->ctl_sources); } if ((err = snd_ctl_poll_descriptors(state->ctl, state->ctl_pfds, state->ctl_n_fds)) < 0) { spa_log_warn(state->log, "Could not get poll descriptors: %s", snd_strerror(err)); return; } snd_ctl_subscribe_events(state->ctl, 1); for (int i = 0; i < state->ctl_n_fds; i++) { state->ctl_sources[i].func = bind_ctl_event; state->ctl_sources[i].data = state; state->ctl_sources[i].fd = state->ctl_pfds[i].fd; state->ctl_sources[i].mask = SPA_IO_IN; state->ctl_sources[i].rmask = 0; spa_loop_add_source(state->main_loop, &state->ctl_sources[i]); } fetch_bind_ctls(state); } int spa_alsa_init(struct state *state, const struct spa_dict *info) { uint32_t i; int err; const char *str; spa_list_init(&state->followers); spa_list_init(&state->rt.followers); snd_config_update_free_global(); if (info && (str = spa_dict_lookup(info, "device.profile.pro")) != NULL) state->is_pro = spa_atob(str); if (info && spa_strstartswith(spa_dict_lookup(info, SPA_KEY_API_ALSA_CARD_NAME), "sof-") && state->stream == SND_PCM_STREAM_PLAYBACK) { state->use_period_size_min_as_headroom = true; spa_log_info(state->log, "ALSA SOF driver detected: default api.alsa.use-period-size-min-as-headroom=true"); } state->multi_rate = true; state->htimestamp = false; state->htimestamp_max_errors = MAX_HTIMESTAMP_ERROR; state->card_index = SPA_ID_INVALID; state->dll_bw_max = SPA_DLL_BW_MAX; for (i = 0; info && i < info->n_items; i++) { const char *k = info->items[i].key; const char *s = info->items[i].value; if (spa_streq(k, SPA_KEY_API_ALSA_PATH)) { snprintf(state->props.device, 63, "%s", s); } else if (spa_streq(k, SPA_KEY_API_ALSA_PCM_CARD)) { state->card_index = atoi(s); } else if (spa_streq(k, SPA_KEY_API_ALSA_OPEN_UCM)) { state->open_ucm = spa_atob(s); if (state->open_ucm) state->props.use_chmap = true; } else if (spa_streq(k, "clock.quantum-limit")) { spa_atou32(s, &state->quantum_limit, 0); } else if (spa_streq(k, SPA_KEY_API_ALSA_BIND_CTLS)) { struct spa_json it[1]; char v[256]; unsigned int i = 0; /* Read a list of ALSA control names to bind as params */ if (spa_json_begin_array_relax(&it[0], s, strlen(s)) <= 0) continue; while (spa_json_get_string(&it[0], v, sizeof(v)) > 0 && i < SPA_N_ELEMENTS(state->bound_ctls)) { snprintf(state->bound_ctls[i].name, sizeof(state->bound_ctls[i].name), "%s", v); i++; } state->num_bind_ctls = i; /* We'll do the actual binding after checking the card exists */ } else if (spa_streq(k, SPA_KEY_DEVICE_BUS)) { state->is_firewire = spa_streq(s, "firewire"); } else { alsa_set_param(state, k, s); } } if (state->card_index == SPA_ID_INVALID) { /* If we don't have a card index, see if we have a *: string */ sscanf(state->props.device, "%*[^:]:%u", &state->card_index); if (state->card_index == SPA_ID_INVALID) { spa_log_info(state->log, "Could not determine card index. %s and/or clock.name " "may need to be configured manually", SPA_KEY_API_ALSA_PCM_CARD); } } if (state->clock_name[0] == '\0' && state->card_index != SPA_ID_INVALID) snprintf(state->clock_name, sizeof(state->clock_name), "api.alsa.%s-%u", state->stream == SND_PCM_STREAM_PLAYBACK ? "p" : "c", state->card_index); if (state->stream == SND_PCM_STREAM_PLAYBACK) { state->is_iec958 = spa_strstartswith(state->props.device, "iec958"); state->is_hdmi = spa_strstartswith(state->props.device, "hdmi"); state->iec958_codecs |= 1ULL << SPA_AUDIO_IEC958_CODEC_PCM; } state->card = ensure_card(state->card_index, state->open_ucm, state->is_split_parent); state->log_file = fopencookie(state, "w", io_funcs); if (state->log_file == NULL) { spa_log_error(state->log, "can't create log file"); return -errno; } CHECK(snd_output_stdio_attach(&state->output, state->log_file, 0), "attach failed"); spa_list_append(&states, &state->link); state->rate_limit.interval = 2 * SPA_NSEC_PER_SEC; state->rate_limit.burst = 1; bind_ctls_for_params(state); return 0; } int spa_alsa_clear(struct state *state) { int err; struct state *follower; spa_list_remove(&state->link); release_card(state->card); if (state->driver != NULL) { spa_list_remove(&state->driver_link); state->driver = NULL; } if (state->rt.driver != NULL) { spa_list_remove(&state->rt.driver_link); state->rt.driver = NULL; } spa_list_consume(follower, &state->followers, driver_link) { spa_list_remove(&follower->driver_link); follower->driver = NULL; } spa_list_consume(follower, &state->rt.followers, rt.driver_link) { spa_list_remove(&follower->rt.driver_link); follower->rt.driver = NULL; } state->card = NULL; state->card_index = SPA_ID_INVALID; if ((err = snd_output_close(state->output)) < 0) spa_log_warn(state->log, "output close failed: %s", snd_strerror(err)); fclose(state->log_file); free(state->tag[0]); free(state->tag[1]); if (state->ctl) { for (int i = 0; i < state->ctl_n_fds; i++) { spa_loop_remove_source(state->main_loop, &state->ctl_sources[i]); } snd_ctl_close(state->ctl); state->ctl = NULL; for (unsigned int i = 0; i < state->num_bind_ctls; i++) { if (state->bound_ctls[i].info) { snd_ctl_elem_info_free(state->bound_ctls[i].info); state->bound_ctls[i].info = NULL; } if (state->bound_ctls[i].value) { snd_ctl_elem_value_free(state->bound_ctls[i].value); state->bound_ctls[i].value = NULL; } } } return err; } static int probe_pitch_ctl(struct state *state) { snd_ctl_elem_id_t *id; /* TODO: Add configuration params for the control name and units */ const char *elem_name = state->stream == SND_PCM_STREAM_CAPTURE ? "Capture Pitch 1000000" : "Playback Pitch 1000000"; bool opened = false; int err; snd_lib_error_set_handler(silence_error_handler); if (!state->ctl) { err = open_card_ctl(state); if (err < 0) goto error; opened = true; } snd_ctl_elem_id_alloca(&id); snd_ctl_elem_id_set_name(id, elem_name); snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_PCM); snd_ctl_elem_value_malloc(&state->pitch_elem); snd_ctl_elem_value_set_id(state->pitch_elem, id); err = snd_ctl_elem_read(state->ctl, state->pitch_elem); if (err < 0) { spa_log_debug(state->log, "%s: did not find ctl: %s", elem_name, snd_strerror(err)); snd_ctl_elem_value_free(state->pitch_elem); state->pitch_elem = NULL; if (opened) { snd_ctl_close(state->ctl); state->ctl = NULL; } goto error; } snd_ctl_elem_value_set_integer(state->pitch_elem, 0, 1000000); CHECK(snd_ctl_elem_write(state->ctl, state->pitch_elem), "snd_ctl_elem_write"); state->last_rate = 1.0; spa_log_info(state->log, "found ctl %s", elem_name); err = 0; error: snd_lib_error_set_handler(NULL); return err; } static int do_link(struct state *driver, struct state *state) { int res; snd_pcm_status_t *status; snd_pcm_status_alloca(&status); snd_pcm_status(driver->hndl, status); snd_pcm_status_dump(status, state->output); snd_pcm_status(state->hndl, status); snd_pcm_status_dump(status, state->output); fflush(state->log_file); res = snd_pcm_link(driver->hndl, state->hndl); if (res >= 0 || res == -EALREADY) state->linked = true; spa_log_info(state->log, "%p: linked to driver %p: %u (%s)", state, driver, state->linked, snd_strerror(res)); return 0; } int spa_alsa_open(struct state *state, const char *params) { int err; struct props *props = &state->props; char device_name[256]; if (state->opened) return 0; fill_device_name(state, params, device_name, sizeof(device_name)); spa_scnprintf(state->name, sizeof(state->name), "%s%s", props->device, state->stream == SND_PCM_STREAM_CAPTURE ? "c" : "p"); spa_log_info(state->log, "%p: ALSA device open '%s' %s", state, device_name, state->stream == SND_PCM_STREAM_CAPTURE ? "capture" : "playback"); CHECK(snd_pcm_open(&state->hndl, device_name, state->stream, SND_PCM_NONBLOCK | SND_PCM_NO_AUTO_RESAMPLE | SND_PCM_NO_AUTO_CHANNELS | SND_PCM_NO_AUTO_FORMAT), "'%s': %s open failed", device_name, state->stream == SND_PCM_STREAM_CAPTURE ? "capture" : "playback"); if (!state->disable_tsched) { if ((err = spa_system_timerfd_create(state->data_system, CLOCK_MONOTONIC, SPA_FD_CLOEXEC | SPA_FD_NONBLOCK)) < 0) goto error_exit_close; state->timerfd = err; } else { /* ALSA pollfds may only be ready after setting swparams, so * these are initialised in spa_alsa_start() */ } state->opened = true; state->sample_count = 0; state->sample_time = 0; probe_pitch_ctl(state); return 0; error_exit_close: spa_log_info(state->log, "%p: Device '%s' closing: %s", state, state->name, spa_strerror(err)); snd_pcm_close(state->hndl); return err; } static void try_unlink(struct state *state) { struct state *follower; if (state->driver != NULL && state->linked) { snd_pcm_unlink(state->hndl); spa_log_info(state->log, "%p: unlinked from driver %p", state, state->driver); state->linked = false; } spa_list_for_each(follower, &state->followers, driver_link) { if (follower->opened && follower->linked) { snd_pcm_unlink(follower->hndl); spa_log_info(state->log, "%p: follower unlinked from driver %p", follower, state); follower->linked = false; } } } int spa_alsa_close(struct state *state) { int err = 0; if (!state->opened) return 0; try_unlink(state); spa_alsa_pause(state); spa_log_info(state->log, "%p: Device '%s' closing", state, state->name); if ((err = snd_pcm_close(state->hndl)) < 0) spa_log_warn(state->log, "%s: close failed: %s", state->name, snd_strerror(err)); if (!state->disable_tsched) spa_system_close(state->data_system, state->timerfd); else state->n_fds = 0; if (state->have_format && state->card) state->card->format_ref--; state->have_format = false; state->opened = false; state->linked = false; if (state->pitch_elem) { snd_ctl_elem_value_free(state->pitch_elem); state->pitch_elem = NULL; // Close it unless we've got some bind_ctls we're listening to if (state->ctl_n_fds == 0) { snd_ctl_close(state->ctl); state->ctl = NULL; } } return err; } struct format_info { uint32_t spa_format; uint32_t spa_pformat; snd_pcm_format_t format; }; static const struct format_info format_info[] = { { SPA_AUDIO_FORMAT_UNKNOWN, SPA_AUDIO_FORMAT_UNKNOWN, SND_PCM_FORMAT_UNKNOWN}, { SPA_AUDIO_FORMAT_F32_LE, SPA_AUDIO_FORMAT_F32P, SND_PCM_FORMAT_FLOAT_LE}, { SPA_AUDIO_FORMAT_F32_BE, SPA_AUDIO_FORMAT_F32P, SND_PCM_FORMAT_FLOAT_BE}, { SPA_AUDIO_FORMAT_S32_LE, SPA_AUDIO_FORMAT_S32P, SND_PCM_FORMAT_S32_LE}, { SPA_AUDIO_FORMAT_S32_BE, SPA_AUDIO_FORMAT_S32P, SND_PCM_FORMAT_S32_BE}, { SPA_AUDIO_FORMAT_S24_32_LE, SPA_AUDIO_FORMAT_S24_32P, SND_PCM_FORMAT_S24_LE}, { SPA_AUDIO_FORMAT_S24_32_BE, SPA_AUDIO_FORMAT_S24_32P, SND_PCM_FORMAT_S24_BE}, { SPA_AUDIO_FORMAT_S24_LE, SPA_AUDIO_FORMAT_S24P, SND_PCM_FORMAT_S24_3LE}, { SPA_AUDIO_FORMAT_S24_BE, SPA_AUDIO_FORMAT_S24P, SND_PCM_FORMAT_S24_3BE}, { SPA_AUDIO_FORMAT_S16_LE, SPA_AUDIO_FORMAT_S16P, SND_PCM_FORMAT_S16_LE}, { SPA_AUDIO_FORMAT_S16_BE, SPA_AUDIO_FORMAT_S16P, SND_PCM_FORMAT_S16_BE}, { SPA_AUDIO_FORMAT_S8, SPA_AUDIO_FORMAT_UNKNOWN, SND_PCM_FORMAT_S8}, { SPA_AUDIO_FORMAT_U8, SPA_AUDIO_FORMAT_U8P, SND_PCM_FORMAT_U8}, { SPA_AUDIO_FORMAT_U16_LE, SPA_AUDIO_FORMAT_UNKNOWN, SND_PCM_FORMAT_U16_LE}, { SPA_AUDIO_FORMAT_U16_BE, SPA_AUDIO_FORMAT_UNKNOWN, SND_PCM_FORMAT_U16_BE}, { SPA_AUDIO_FORMAT_U24_32_LE, SPA_AUDIO_FORMAT_UNKNOWN, SND_PCM_FORMAT_U24_LE}, { SPA_AUDIO_FORMAT_U24_32_BE, SPA_AUDIO_FORMAT_UNKNOWN, SND_PCM_FORMAT_U24_BE}, { SPA_AUDIO_FORMAT_U24_LE, SPA_AUDIO_FORMAT_UNKNOWN, SND_PCM_FORMAT_U24_3LE}, { SPA_AUDIO_FORMAT_U24_BE, SPA_AUDIO_FORMAT_UNKNOWN, SND_PCM_FORMAT_U24_3BE}, { SPA_AUDIO_FORMAT_U32_LE, SPA_AUDIO_FORMAT_UNKNOWN, SND_PCM_FORMAT_U32_LE}, { SPA_AUDIO_FORMAT_U32_BE, SPA_AUDIO_FORMAT_UNKNOWN, SND_PCM_FORMAT_U32_BE}, { SPA_AUDIO_FORMAT_F64_LE, SPA_AUDIO_FORMAT_F64P, SND_PCM_FORMAT_FLOAT64_LE}, { SPA_AUDIO_FORMAT_F64_BE, SPA_AUDIO_FORMAT_F64P, SND_PCM_FORMAT_FLOAT64_BE}, }; static snd_pcm_format_t spa_format_to_alsa(uint32_t format, bool *planar) { SPA_FOR_EACH_ELEMENT_VAR(format_info, i) { *planar = i->spa_pformat == format; if (i->spa_format == format || *planar) return i->format; } return SND_PCM_FORMAT_UNKNOWN; } struct chmap_info { enum snd_pcm_chmap_position pos; enum spa_audio_channel channel; }; static const struct chmap_info chmap_info[] = { [SND_CHMAP_UNKNOWN] = { SND_CHMAP_UNKNOWN, SPA_AUDIO_CHANNEL_UNKNOWN }, [SND_CHMAP_NA] = { SND_CHMAP_NA, SPA_AUDIO_CHANNEL_NA }, [SND_CHMAP_MONO] = { SND_CHMAP_MONO, SPA_AUDIO_CHANNEL_MONO }, [SND_CHMAP_FL] = { SND_CHMAP_FL, SPA_AUDIO_CHANNEL_FL }, [SND_CHMAP_FR] = { SND_CHMAP_FR, SPA_AUDIO_CHANNEL_FR }, [SND_CHMAP_RL] = { SND_CHMAP_RL, SPA_AUDIO_CHANNEL_RL }, [SND_CHMAP_RR] = { SND_CHMAP_RR, SPA_AUDIO_CHANNEL_RR }, [SND_CHMAP_FC] = { SND_CHMAP_FC, SPA_AUDIO_CHANNEL_FC }, [SND_CHMAP_LFE] = { SND_CHMAP_LFE, SPA_AUDIO_CHANNEL_LFE }, [SND_CHMAP_SL] = { SND_CHMAP_SL, SPA_AUDIO_CHANNEL_SL }, [SND_CHMAP_SR] = { SND_CHMAP_SR, SPA_AUDIO_CHANNEL_SR }, [SND_CHMAP_RC] = { SND_CHMAP_RC, SPA_AUDIO_CHANNEL_RC }, [SND_CHMAP_FLC] = { SND_CHMAP_FLC, SPA_AUDIO_CHANNEL_FLC }, [SND_CHMAP_FRC] = { SND_CHMAP_FRC, SPA_AUDIO_CHANNEL_FRC }, [SND_CHMAP_RLC] = { SND_CHMAP_RLC, SPA_AUDIO_CHANNEL_RLC }, [SND_CHMAP_RRC] = { SND_CHMAP_RRC, SPA_AUDIO_CHANNEL_RRC }, [SND_CHMAP_FLW] = { SND_CHMAP_FLW, SPA_AUDIO_CHANNEL_FLW }, [SND_CHMAP_FRW] = { SND_CHMAP_FRW, SPA_AUDIO_CHANNEL_FRW }, [SND_CHMAP_FLH] = { SND_CHMAP_FLH, SPA_AUDIO_CHANNEL_FLH }, [SND_CHMAP_FCH] = { SND_CHMAP_FCH, SPA_AUDIO_CHANNEL_FCH }, [SND_CHMAP_FRH] = { SND_CHMAP_FRH, SPA_AUDIO_CHANNEL_FRH }, [SND_CHMAP_TC] = { SND_CHMAP_TC, SPA_AUDIO_CHANNEL_TC }, [SND_CHMAP_TFL] = { SND_CHMAP_TFL, SPA_AUDIO_CHANNEL_TFL }, [SND_CHMAP_TFR] = { SND_CHMAP_TFR, SPA_AUDIO_CHANNEL_TFR }, [SND_CHMAP_TFC] = { SND_CHMAP_TFC, SPA_AUDIO_CHANNEL_TFC }, [SND_CHMAP_TRL] = { SND_CHMAP_TRL, SPA_AUDIO_CHANNEL_TRL }, [SND_CHMAP_TRR] = { SND_CHMAP_TRR, SPA_AUDIO_CHANNEL_TRR }, [SND_CHMAP_TRC] = { SND_CHMAP_TRC, SPA_AUDIO_CHANNEL_TRC }, [SND_CHMAP_TFLC] = { SND_CHMAP_TFLC, SPA_AUDIO_CHANNEL_TFLC }, [SND_CHMAP_TFRC] = { SND_CHMAP_TFRC, SPA_AUDIO_CHANNEL_TFRC }, [SND_CHMAP_TSL] = { SND_CHMAP_TSL, SPA_AUDIO_CHANNEL_TSL }, [SND_CHMAP_TSR] = { SND_CHMAP_TSR, SPA_AUDIO_CHANNEL_TSR }, [SND_CHMAP_LLFE] = { SND_CHMAP_LLFE, SPA_AUDIO_CHANNEL_LLFE }, [SND_CHMAP_RLFE] = { SND_CHMAP_RLFE, SPA_AUDIO_CHANNEL_RLFE }, [SND_CHMAP_BC] = { SND_CHMAP_BC, SPA_AUDIO_CHANNEL_BC }, [SND_CHMAP_BLC] = { SND_CHMAP_BLC, SPA_AUDIO_CHANNEL_BLC }, [SND_CHMAP_BRC] = { SND_CHMAP_BRC, SPA_AUDIO_CHANNEL_BRC }, }; #define _M(ch) (1LL << SND_CHMAP_ ##ch) struct def_mask { int channels; uint64_t mask; }; static const struct def_mask default_layouts[] = { { 0, 0 }, { 1, _M(MONO) }, { 2, _M(FL) | _M(FR) }, { 3, _M(FL) | _M(FR) | _M(LFE) }, { 4, _M(FL) | _M(FR) | _M(RL) |_M(RR) }, { 5, _M(FL) | _M(FR) | _M(RL) |_M(RR) | _M(FC) }, { 6, _M(FL) | _M(FR) | _M(RL) |_M(RR) | _M(FC) | _M(LFE) }, { 7, _M(FL) | _M(FR) | _M(RL) |_M(RR) | _M(SL) | _M(SR) | _M(FC) }, { 8, _M(FL) | _M(FR) | _M(RL) |_M(RR) | _M(SL) | _M(SR) | _M(FC) | _M(LFE) }, }; #define _C(ch) (SPA_AUDIO_CHANNEL_ ##ch) static const struct channel_map default_map[] = { { 0, { 0, } } , { 1, { _C(MONO), } }, { 2, { _C(FL), _C(FR), } }, { 3, { _C(FL), _C(FR), _C(LFE) } }, { 4, { _C(FL), _C(FR), _C(RL), _C(RR), } }, { 5, { _C(FL), _C(FR), _C(RL), _C(RR), _C(FC) } }, { 6, { _C(FL), _C(FR), _C(RL), _C(RR), _C(FC), _C(LFE), } }, { 7, { _C(FL), _C(FR), _C(RL), _C(RR), _C(FC), _C(SL), _C(SR), } }, { 8, { _C(FL), _C(FR), _C(RL), _C(RR), _C(FC), _C(LFE), _C(SL), _C(SR), } }, }; static enum spa_audio_channel chmap_position_to_channel(enum snd_pcm_chmap_position pos) { return chmap_info[pos].channel; } static void sanitize_map(snd_pcm_chmap_t* map) { uint64_t mask = 0, p, dup = 0; const struct def_mask *def; uint32_t i, j, pos; for (i = 0; i < map->channels; i++) { if (map->pos[i] > SND_CHMAP_LAST) map->pos[i] = SND_CHMAP_UNKNOWN; p = 1LL << map->pos[i]; if (mask & p) { /* duplicate channel */ for (j = 0; j <= i; j++) if (map->pos[j] == map->pos[i]) map->pos[j] = SND_CHMAP_UNKNOWN; dup |= p; p = 1LL << SND_CHMAP_UNKNOWN; } mask |= p; } if ((mask & (1LL << SND_CHMAP_UNKNOWN)) == 0) return; def = &default_layouts[map->channels]; /* remove duplicates */ mask &= ~dup; /* keep unassigned channels */ mask = def->mask & ~mask; pos = 0; for (i = 0; i < map->channels; i++) { if (map->pos[i] == SND_CHMAP_UNKNOWN) { do { mask >>= 1; pos++; } while (mask != 0 && (mask & 1) == 0); map->pos[i] = mask ? pos : 0; } } } static bool uint32_array_contains(uint32_t *vals, uint32_t n_vals, uint32_t val) { uint32_t i; for (i = 0; i < n_vals; i++) if (vals[i] == val) return true; return false; } static int add_rate(struct state *state, uint32_t scale, uint32_t interleave, bool all, uint32_t index, uint32_t *next, uint32_t min_allowed_rate, snd_pcm_hw_params_t *params, struct spa_pod_builder *b) { struct spa_pod_frame f[1]; int err, dir; unsigned int min, max; struct spa_pod_choice *choice; uint32_t rate; CHECK(snd_pcm_hw_params_get_rate_min(params, &min, &dir), "get_rate_min"); CHECK(snd_pcm_hw_params_get_rate_max(params, &max, &dir), "get_rate_max"); spa_log_debug(state->log, "min:%u max:%u min-allowed:%u scale:%u interleave:%u all:%d", min, max, min_allowed_rate, scale, interleave, all); min = SPA_MAX(min_allowed_rate * scale / interleave, min) * interleave / scale; max = max * interleave / scale; if (max < min) return 0; if (!state->multi_rate && state->card && state->card->format_ref > 0) rate = state->card->rate; else rate = state->default_rate; if (rate < min || rate > max) rate = 0; if (rate != 0 && !all) min = max = rate; if (rate == 0) rate = state->position ? state->position->clock.target_rate.denom : DEFAULT_RATE; rate = SPA_CLAMP(rate, min, max); spa_log_debug(state->log, "rate:%u multi:%d card:%u def:%d", rate, state->multi_rate, state->card ? state->card->rate : 0, state->default_rate); spa_pod_builder_prop(b, SPA_FORMAT_AUDIO_rate, 0); spa_pod_builder_push_choice(b, &f[0], SPA_CHOICE_None, 0); choice = (struct spa_pod_choice*)spa_pod_builder_frame(b, &f[0]); if (state->n_allowed_rates > 0) { uint32_t i, v, last = 0, count = 0; if (uint32_array_contains(state->allowed_rates, state->n_allowed_rates, rate)) { spa_pod_builder_int(b, rate * scale); count++; } for (i = 0; i < state->n_allowed_rates; i++) { v = SPA_CLAMP(state->allowed_rates[i], min, max); if (v != last && uint32_array_contains(state->allowed_rates, state->n_allowed_rates, v)) { spa_pod_builder_int(b, v * scale); if (count == 0) spa_pod_builder_int(b, v * scale); count++; } last = v; } if (count > 1) choice->body.type = SPA_CHOICE_Enum; } else { spa_pod_builder_int(b, rate * scale); if (min != max) { spa_pod_builder_int(b, min * scale); spa_pod_builder_int(b, max * scale); choice->body.type = SPA_CHOICE_Range; } } spa_pod_builder_pop(b, &f[0]); return 1; } static int add_channels(struct state *state, bool all, uint32_t index, uint32_t *next, snd_pcm_hw_params_t *params, struct spa_pod_builder *b) { struct spa_pod_frame f[1]; size_t i; int err; snd_pcm_t *hndl = state->hndl; snd_pcm_chmap_query_t **maps; unsigned int min, max; CHECK(snd_pcm_hw_params_get_channels_min(params, &min), "get_channels_min"); CHECK(snd_pcm_hw_params_get_channels_max(params, &max), "get_channels_max"); spa_log_debug(state->log, "channels (%d %d) default:%d all:%d", min, max, state->default_channels, all); min = SPA_MIN(min, MAX_CHANNELS); max = SPA_MIN(max, MAX_CHANNELS); if (state->default_channels != 0 && !all) { if (min > state->default_channels || max < state->default_channels) spa_log_warn(state->log, "given audio.channels %d out of range:%d-%d", state->default_channels, min, max); else min = max = state->default_channels; } spa_pod_builder_prop(b, SPA_FORMAT_AUDIO_channels, 0); if (state->props.use_chmap && (maps = snd_pcm_query_chmaps(hndl)) != NULL) { uint32_t channel; snd_pcm_chmap_t* map; skip_channels: if (maps[index] == NULL) { snd_pcm_free_chmaps(maps); return 0; } map = &maps[index]->map; spa_log_debug(state->log, "map %d channels (%d %d)", map->channels, min, max); if (map->channels < min || map->channels > max) { index = (*next)++; goto skip_channels; } spa_log_debug(state->log, "%p: using chmap", state); sanitize_map(map); spa_pod_builder_int(b, map->channels); spa_pod_builder_prop(b, SPA_FORMAT_AUDIO_position, 0); spa_pod_builder_push_array(b, &f[0]); for (i = 0; i < map->channels; i++) { spa_log_debug(state->log, "%p: position %zd %d", state, i, map->pos[i]); channel = chmap_position_to_channel(map->pos[i]); spa_pod_builder_id(b, channel); } spa_pod_builder_pop(b, &f[0]); snd_pcm_free_chmaps(maps); } else { if (index > 0) return 0; if (min != max) { spa_pod_builder_push_choice(b, &f[0], SPA_CHOICE_Range, 0); spa_pod_builder_int(b, max); spa_pod_builder_int(b, min); spa_pod_builder_int(b, max); spa_pod_builder_pop(b, &f[0]); } else { const struct channel_map *map = NULL; spa_pod_builder_int(b, min); if (state->default_pos.n_pos == min) { map = &state->default_pos; spa_log_debug(state->log, "%p: using provided default", state); } else if (min <= 8) { map = &default_map[min]; spa_log_debug(state->log, "%p: using default %d channel map", state, min); } if (map) { spa_pod_builder_prop(b, SPA_FORMAT_AUDIO_position, 0); spa_pod_builder_push_array(b, &f[0]); for (i = 0; i < map->n_pos; i++) { spa_log_debug(state->log, "%p: position %zd %d", state, i, map->pos[i]); spa_pod_builder_id(b, map->pos[i]); } spa_pod_builder_pop(b, &f[0]); } } } return 1; } static void debug_hw_params(struct state *state, const char *prefix, snd_pcm_hw_params_t *params) { if (SPA_UNLIKELY(spa_log_level_topic_enabled(state->log, SPA_LOG_TOPIC_DEFAULT, SPA_LOG_LEVEL_DEBUG))) { spa_log_debug(state->log, "%s:", prefix); snd_pcm_hw_params_dump(params, state->output); fflush(state->log_file); } } static int enum_pcm_formats(struct state *state, uint32_t index, uint32_t *next, struct spa_pod **result, struct spa_pod_builder *b) { int res, err; size_t j; snd_pcm_t *hndl; snd_pcm_hw_params_t *params; struct spa_pod_frame f[2]; snd_pcm_format_mask_t *fmask; snd_pcm_access_mask_t *amask; unsigned int rrate, rchannels; struct spa_pod_choice *choice; hndl = state->hndl; snd_pcm_hw_params_alloca(¶ms); CHECK(snd_pcm_hw_params_any(hndl, params), "Broken configuration: no configurations available"); debug_hw_params(state, __func__, params); CHECK(snd_pcm_hw_params_set_rate_resample(hndl, params, 0), "set_rate_resample"); if (state->default_channels != 0) { rchannels = state->default_channels; CHECK(snd_pcm_hw_params_set_channels_near(hndl, params, &rchannels), "set_channels"); if (state->default_channels != rchannels) { spa_log_warn(state->log, "%s: Channels doesn't match (requested %u, got %u)", state->name, state->default_channels, rchannels); } } if (state->default_rate != 0) { rrate = state->default_rate; CHECK(snd_pcm_hw_params_set_rate_near(hndl, params, &rrate, 0), "set_rate_near"); if (state->default_rate != rrate) { spa_log_warn(state->log, "%s: Rate doesn't match (requested %u, got %u)", state->name, state->default_rate, rrate); } } spa_pod_builder_push_object(b, &f[0], SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat); spa_pod_builder_add(b, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_audio), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), 0); snd_pcm_format_mask_alloca(&fmask); snd_pcm_hw_params_get_format_mask(params, fmask); snd_pcm_access_mask_alloca(&amask); snd_pcm_hw_params_get_access_mask(params, amask); spa_pod_builder_prop(b, SPA_FORMAT_AUDIO_format, 0); spa_pod_builder_push_choice(b, &f[1], SPA_CHOICE_None, 0); choice = (struct spa_pod_choice*)spa_pod_builder_frame(b, &f[1]); j = 0; SPA_FOR_EACH_ELEMENT_VAR(format_info, fi) { if (fi->format == SND_PCM_FORMAT_UNKNOWN) continue; if (snd_pcm_format_mask_test(fmask, fi->format)) { if ((snd_pcm_access_mask_test(amask, SND_PCM_ACCESS_MMAP_NONINTERLEAVED) || snd_pcm_access_mask_test(amask, SND_PCM_ACCESS_RW_NONINTERLEAVED)) && fi->spa_pformat != SPA_AUDIO_FORMAT_UNKNOWN && (state->default_format == 0 || state->default_format == fi->spa_pformat)) { if (j++ == 0) spa_pod_builder_id(b, fi->spa_pformat); spa_pod_builder_id(b, fi->spa_pformat); } if ((snd_pcm_access_mask_test(amask, SND_PCM_ACCESS_MMAP_INTERLEAVED) || snd_pcm_access_mask_test(amask, SND_PCM_ACCESS_RW_INTERLEAVED)) && (state->default_format == 0 || state->default_format == fi->spa_format)) { if (j++ == 0) spa_pod_builder_id(b, fi->spa_format); spa_pod_builder_id(b, fi->spa_format); } } } if (j > 1) choice->body.type = SPA_CHOICE_Enum; spa_pod_builder_pop(b, &f[1]); if (j == 0) { char buf[1024]; int i, r, offs; for (i = 0, offs = 0; i <= SND_PCM_FORMAT_LAST; i++) { if (snd_pcm_format_mask_test(fmask, (snd_pcm_format_t)i)) { r = snprintf(&buf[offs], sizeof(buf) - offs, "%s ", snd_pcm_format_name((snd_pcm_format_t)i)); if (r < 0 || r + offs >= (int)sizeof(buf)) return -ENOSPC; offs += r; } } spa_log_warn(state->log, "%s: no format found (def:%d) formats:%s", state->name, state->default_format, buf); for (i = 0, offs = 0; i <= SND_PCM_ACCESS_LAST; i++) { if (snd_pcm_access_mask_test(amask, (snd_pcm_access_t)i)) { r = snprintf(&buf[offs], sizeof(buf) - offs, "%s ", snd_pcm_access_name((snd_pcm_access_t)i)); if (r < 0 || r + offs >= (int)sizeof(buf)) return -ENOSPC; offs += r; } } spa_log_warn(state->log, "%s: access:%s", state->name, buf); return -ENOTSUP; } if ((res = add_rate(state, 1, 1, false, index & 0xffff, next, 0, params, b)) != 1) return res; if ((res = add_channels(state, false, index & 0xffff, next, params, b)) != 1) return res; *result = spa_pod_builder_pop(b, &f[0]); return 1; } static bool codec_supported(uint32_t codec, unsigned int chmax, unsigned int rmax) { switch (codec) { case SPA_AUDIO_IEC958_CODEC_PCM: case SPA_AUDIO_IEC958_CODEC_DTS: case SPA_AUDIO_IEC958_CODEC_AC3: case SPA_AUDIO_IEC958_CODEC_MPEG: case SPA_AUDIO_IEC958_CODEC_MPEG2_AAC: if (chmax >= 2) return true; break; case SPA_AUDIO_IEC958_CODEC_EAC3: if (rmax >= 48000 * 4 && chmax >= 2) return true; break; case SPA_AUDIO_IEC958_CODEC_TRUEHD: case SPA_AUDIO_IEC958_CODEC_DTSHD: if (chmax >= 8) return true; break; } return false; } static int enum_iec958_formats(struct state *state, uint32_t index, uint32_t *next, struct spa_pod **result, struct spa_pod_builder *b) { int res, err, dir; snd_pcm_t *hndl; snd_pcm_hw_params_t *params; struct spa_pod_frame f[2]; unsigned int rmin, rmax; unsigned int chmin, chmax; uint32_t i, c, codecs[16], n_codecs; if ((index & 0xffff) > 0) return 0; if (!(state->is_iec958 || state->is_hdmi)) return 0; if (state->iec958_codecs == 0) return 0; hndl = state->hndl; snd_pcm_hw_params_alloca(¶ms); CHECK(snd_pcm_hw_params_any(hndl, params), "Broken configuration: no configurations available"); debug_hw_params(state, __func__, params); CHECK(snd_pcm_hw_params_set_rate_resample(hndl, params, 0), "set_rate_resample"); spa_pod_builder_push_object(b, &f[0], SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat); spa_pod_builder_add(b, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_audio), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_iec958), 0); CHECK(snd_pcm_hw_params_get_channels_min(params, &chmin), "get_channels_min"); CHECK(snd_pcm_hw_params_get_channels_max(params, &chmax), "get_channels_max"); spa_log_debug(state->log, "channels (%d %d)", chmin, chmax); CHECK(snd_pcm_hw_params_get_rate_min(params, &rmin, &dir), "get_rate_min"); CHECK(snd_pcm_hw_params_get_rate_max(params, &rmax, &dir), "get_rate_max"); spa_log_debug(state->log, "rate (%d %d)", rmin, rmax); if (state->default_rate != 0) { if (rmin > state->default_rate || rmax < state->default_rate) spa_log_warn(state->log, "given audio.rate %d out of range:%d-%d", state->default_rate, rmin, rmax); else rmin = rmax = state->default_rate; } spa_pod_builder_prop(b, SPA_FORMAT_AUDIO_iec958Codec, 0); spa_pod_builder_push_choice(b, &f[1], SPA_CHOICE_Enum, 0); n_codecs = spa_alsa_get_iec958_codecs(state, codecs, SPA_N_ELEMENTS(codecs)); for (i = 0, c = 0; i < n_codecs; i++) { if (!codec_supported(codecs[i], chmax, rmax)) continue; if (c++ == 0) spa_pod_builder_id(b, codecs[i]); spa_pod_builder_id(b, codecs[i]); } spa_pod_builder_pop(b, &f[1]); if ((res = add_rate(state, 1, 1, true, index & 0xffff, next, 0, params, b)) != 1) return res; (*next)++; *result = spa_pod_builder_pop(b, &f[0]); return 1; } static int enum_dsd_formats(struct state *state, uint32_t index, uint32_t *next, struct spa_pod **result, struct spa_pod_builder *b) { int res, err; snd_pcm_t *hndl; snd_pcm_hw_params_t *params; snd_pcm_format_mask_t *fmask; struct spa_pod_frame f[2]; int32_t interleave; if ((index & 0xffff) > 0) return 0; hndl = state->hndl; snd_pcm_hw_params_alloca(¶ms); CHECK(snd_pcm_hw_params_any(hndl, params), "Broken configuration: no configurations available"); debug_hw_params(state, __func__, params); snd_pcm_format_mask_alloca(&fmask); snd_pcm_hw_params_get_format_mask(params, fmask); if (snd_pcm_format_mask_test(fmask, SND_PCM_FORMAT_DSD_U32_BE)) interleave = 4; else if (snd_pcm_format_mask_test(fmask, SND_PCM_FORMAT_DSD_U32_LE)) interleave = -4; else if (snd_pcm_format_mask_test(fmask, SND_PCM_FORMAT_DSD_U16_BE)) interleave = 2; else if (snd_pcm_format_mask_test(fmask, SND_PCM_FORMAT_DSD_U16_LE)) interleave = -2; else if (snd_pcm_format_mask_test(fmask, SND_PCM_FORMAT_DSD_U8)) interleave = 1; else return 0; CHECK(snd_pcm_hw_params_set_rate_resample(hndl, params, 0), "set_rate_resample"); spa_pod_builder_push_object(b, &f[0], SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat); spa_pod_builder_add(b, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_audio), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_dsd), 0); spa_pod_builder_prop(b, SPA_FORMAT_AUDIO_bitorder, 0); spa_pod_builder_id(b, state->dsd_lsb ? SPA_PARAM_BITORDER_lsb : SPA_PARAM_BITORDER_msb); spa_pod_builder_prop(b, SPA_FORMAT_AUDIO_interleave, 0); spa_pod_builder_int(b, interleave); /* Use a lower rate limit of 352800 (= 44100 * 64 / 8). This is because in * PipeWire, DSD rates are given in bytes, not bits, so 352800 corresponds * to the bit rate of DSD64. (The "64" in DSD64 means "64 times the rate * of 44.1 kHz".) Some hardware may report rates lower than that, for example * 176400. This would correspond to "DSD32" (which does not exist). Trying * to use such a rate with DSD hardware does not work and may cause undefined * behavior in said hardware. */ if ((res = add_rate(state, 8, SPA_ABS(interleave), true, index & 0xffff, next, 44100, params, b)) != 1) return res; if ((res = add_channels(state, true, index & 0xffff, next, params, b)) != 1) return res; *result = spa_pod_builder_pop(b, &f[0]); return 1; } /* find smaller power of 2 */ static uint32_t flp2(uint32_t x) { x = x | (x >> 1); x = x | (x >> 2); x = x | (x >> 4); x = x | (x >> 8); x = x | (x >> 16); return x - (x >> 1); } int spa_alsa_enum_format(struct state *state, int seq, uint32_t start, uint32_t num, const struct spa_pod *filter) { uint8_t buffer[4096]; struct spa_pod_builder b = { 0 }; struct spa_pod *fmt; int err, res; bool opened; struct spa_result_node_params result; uint32_t count = 0; spa_log_debug(state->log, "opened:%d format:%d started:%d", state->opened, state->have_format, state->started); opened = state->opened; if (!state->started && state->have_format) spa_alsa_close(state); if ((err = spa_alsa_open(state, NULL)) < 0) return err; result.id = SPA_PARAM_EnumFormat; result.next = start; next: result.index = result.next++; spa_pod_builder_init(&b, buffer, sizeof(buffer)); if (result.index < 0x10000) { if ((res = enum_pcm_formats(state, result.index, &result.next, &fmt, &b)) != 1) { result.next = 0x10000; goto next; } } else if (result.index < 0x20000) { if ((res = enum_iec958_formats(state, result.index, &result.next, &fmt, &b)) != 1) { result.next = 0x20000; goto next; } } else if (result.index < 0x30000) { if ((res = enum_dsd_formats(state, result.index, &result.next, &fmt, &b)) != 1) { result.next = 0x30000; goto next; } } else goto enum_end; if (spa_pod_filter(&b, &result.param, fmt, filter) < 0) goto next; spa_node_emit_result(&state->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); if (++count != num) goto next; enum_end: res = 0; if (!opened) spa_alsa_close(state); return res; } static void recalc_headroom(struct state *state) { uint32_t latency; uint32_t rate = 0; if (state->position != NULL) rate = state->position->clock.target_rate.denom; if (state->use_period_size_min_as_headroom) state->headroom = state->default_headroom ? state->default_headroom : state->period_size_min; else state->headroom = state->default_headroom; if (!state->disable_tsched || state->resample) { /* When using timers, we might miss the pointer update for batch * devices so add some extra headroom. With IRQ, we know the pointers * are updated when we wake up and we don't need the headroom. */ if (state->is_batch) state->headroom += state->period_frames; /* Add 32 extra samples of headroom to handle jitter in capture. * For IRQ, we don't need this because when we wake up, we have * exactly enough samples to read or write. */ if (state->stream == SND_PCM_STREAM_CAPTURE) state->headroom = SPA_MAX(state->headroom, 32u); } if (SPA_LIKELY(state->buffer_frames >= state->threshold)) state->headroom = SPA_MIN(state->headroom, state->buffer_frames - state->threshold); else state->headroom = 0; latency = SPA_MAX(state->min_delay, SPA_MIN(state->max_delay, state->headroom)); if (rate != 0 && state->rate != 0) latency = SPA_SCALE32_UP(latency, rate, state->rate); if (state->is_firewire) { /* XXX: For ALSA FireWire drivers, unlike for other ALSA drivers, buffer size * XXX: contributes extra latency (as of kernel 6.16). */ latency += state->buffer_frames; } state->latency[state->port_direction].min_rate = state->latency[state->port_direction].max_rate = latency; } int spa_alsa_set_format(struct state *state, struct spa_audio_info *fmt, uint32_t flags) { unsigned int rrate, rchannels, val, rscale = 1, period_scale = 1; snd_pcm_uframes_t period_size; int err, dir; snd_pcm_hw_params_t *params; snd_pcm_format_t rformat; snd_pcm_access_mask_t *amask; snd_pcm_t *hndl; unsigned int periods; bool match = true, planar = false; char spdif_params[128] = ""; uint32_t default_period; spa_log_debug(state->log, "opened:%d format:%d started:%d", state->opened, state->have_format, state->started); state->use_mmap = !state->disable_mmap; state->force_quantum = state->disable_tsched; switch (fmt->media_subtype) { case SPA_MEDIA_SUBTYPE_raw: { struct spa_audio_info_raw *f = &fmt->info.raw; rrate = f->rate; rchannels = f->channels; rformat = spa_format_to_alsa(f->format, &planar); break; } case SPA_MEDIA_SUBTYPE_iec958: { struct spa_audio_info_iec958 *f = &fmt->info.iec958; unsigned aes3; spa_log_info(state->log, "using IEC958 Codec:%s rate:%d", spa_type_audio_iec958_codec_to_short_name(f->codec), f->rate); rformat = SND_PCM_FORMAT_S16_LE; rchannels = 2; rrate = f->rate; switch (f->codec) { case SPA_AUDIO_IEC958_CODEC_PCM: case SPA_AUDIO_IEC958_CODEC_DTS: case SPA_AUDIO_IEC958_CODEC_AC3: case SPA_AUDIO_IEC958_CODEC_MPEG: case SPA_AUDIO_IEC958_CODEC_MPEG2_AAC: break; case SPA_AUDIO_IEC958_CODEC_EAC3: /* EAC3 has 3 rates, 32, 44.1 and 48KHz. We need to * open the device in 4x that rate. Some clients * already multiply (mpv,..) others don't (vlc). */ if (rrate <= 48000) rrate *= 4; break; case SPA_AUDIO_IEC958_CODEC_TRUEHD: case SPA_AUDIO_IEC958_CODEC_DTSHD: rchannels = 8; break; default: return -ENOTSUP; } switch (rrate) { case 22050: aes3 = IEC958_AES3_CON_FS_22050; break; case 24000: aes3 = IEC958_AES3_CON_FS_24000; break; case 32000: aes3 = IEC958_AES3_CON_FS_32000; break; case 44100: aes3 = IEC958_AES3_CON_FS_44100; break; case 48000: aes3 = IEC958_AES3_CON_FS_48000; break; case 88200: aes3 = IEC958_AES3_CON_FS_88200; break; case 96000: aes3 = IEC958_AES3_CON_FS_96000; break; case 176400: aes3 = IEC958_AES3_CON_FS_176400; break; case 192000: aes3 = IEC958_AES3_CON_FS_192000; break; case 768000: aes3 = IEC958_AES3_CON_FS_768000; break; default: aes3 = IEC958_AES3_CON_FS_NOTID; break; } spa_scnprintf(spdif_params, sizeof(spdif_params), ",AES0=0x%x,AES1=0x%x,AES2=0x%x,AES3=0x%x", IEC958_AES0_CON_EMPHASIS_NONE | IEC958_AES0_NONAUDIO, IEC958_AES1_CON_ORIGINAL | IEC958_AES1_CON_PCM_CODER, 0, aes3); state->force_quantum = true; break; } case SPA_MEDIA_SUBTYPE_dsd: { struct spa_audio_info_dsd *f = &fmt->info.dsd; rrate = f->rate; rchannels = f->channels; switch (f->interleave) { case 4: rformat = SND_PCM_FORMAT_DSD_U32_BE; rrate /= 4; rscale = 4; break; case -4: rformat = SND_PCM_FORMAT_DSD_U32_LE; rrate /= 4; rscale = 4; break; case 2: rformat = SND_PCM_FORMAT_DSD_U16_BE; rrate /= 2; rscale = 2; break; case -2: rformat = SND_PCM_FORMAT_DSD_U16_LE; rrate /= 2; rscale = 2; break; case 1: rformat = SND_PCM_FORMAT_DSD_U8; rscale = 1; break; default: return -ENOTSUP; } state->force_quantum = true; break; } default: return -ENOTSUP; } if (rformat == SND_PCM_FORMAT_UNKNOWN) { spa_log_warn(state->log, "%s: unknown format", state->name); return -EINVAL; } if (!state->started && state->have_format) spa_alsa_close(state); if ((err = spa_alsa_open(state, spdif_params)) < 0) return err; hndl = state->hndl; snd_pcm_hw_params_alloca(¶ms); /* choose all parameters */ CHECK(snd_pcm_hw_params_any(hndl, params), "Broken configuration for playback: no configurations available"); debug_hw_params(state, __func__, params); /* set hardware resampling, no resample */ CHECK(snd_pcm_hw_params_set_rate_resample(hndl, params, 0), "set_rate_resample"); /* set the interleaved/planar read/write format */ snd_pcm_access_mask_alloca(&amask); snd_pcm_hw_params_get_access_mask(params, amask); if (state->use_mmap) { if ((err = snd_pcm_hw_params_set_access(hndl, params, planar ? SND_PCM_ACCESS_MMAP_NONINTERLEAVED : SND_PCM_ACCESS_MMAP_INTERLEAVED)) < 0) { spa_log_debug(state->log, "%p: MMAP not possible: %s", state, snd_strerror(err)); state->use_mmap = false; } } if (!state->use_mmap) { if ((err = snd_pcm_hw_params_set_access(hndl, params, planar ? SND_PCM_ACCESS_RW_NONINTERLEAVED : SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) { spa_log_error(state->log, "%s: RW not possible: %s", state->name, snd_strerror(err)); return err; } } /* set the sample format */ spa_log_debug(state->log, "%p: Stream parameters are %iHz fmt:%s access:%s-%s channels:%i", state, rrate, snd_pcm_format_name(rformat), state->use_mmap ? "mmap" : "rw", planar ? "planar" : "interleaved", rchannels); CHECK(snd_pcm_hw_params_set_format(hndl, params, rformat), "set_format"); /* set the count of channels */ val = rchannels; CHECK(snd_pcm_hw_params_set_channels_near(hndl, params, &val), "set_channels"); if (rchannels != val) { spa_log_warn(state->log, "%s: Channels doesn't match (requested %u, got %u)", state->name, rchannels, val); if (!SPA_FLAG_IS_SET(flags, SPA_NODE_PARAM_FLAG_NEAREST)) return -EINVAL; if (fmt->media_subtype != SPA_MEDIA_SUBTYPE_raw) return -EINVAL; rchannels = val; fmt->info.raw.channels = rchannels; match = false; } if (!state->multi_rate && state->card && state->card->format_ref > 0 && state->card->rate != rrate) { spa_log_error(state->log, "%p: card already opened at rate:%i", state, state->card->rate); return -EINVAL; } /* set the stream rate */ val = rrate; CHECK(snd_pcm_hw_params_set_rate_near(hndl, params, &val, 0), "set_rate_near"); if (rrate != val) { spa_log_warn(state->log, "%s: Rate doesn't match (requested %iHz, got %iHz)", state->name, rrate, val); if (!SPA_FLAG_IS_SET(flags, SPA_NODE_PARAM_FLAG_NEAREST)) return -EINVAL; if (fmt->media_subtype != SPA_MEDIA_SUBTYPE_raw) return -EINVAL; rrate = val; fmt->info.raw.rate = rrate; match = false; } if (rchannels == 0 || rrate == 0) { spa_log_error(state->log, "%s: invalid channels:%d or rate:%d", state->name, rchannels, rrate); return -EIO; } state->format = rformat; state->channels = rchannels; state->rate = rrate; state->frame_size = snd_pcm_format_physical_width(rformat) / 8; state->frame_scale = rscale; state->planar = planar; state->blocks = 1; if (planar) state->blocks *= rchannels; else state->frame_size *= rchannels; /* make sure we update threshold in check_position_config() because they depend * on the samplerate. */ state->driver_duration = 0; state->driver_rate.denom = 0; state->have_format = true; if (state->card && state->card->format_ref++ == 0) state->card->rate = rrate; dir = 0; period_size = state->default_period_size; state->is_batch = snd_pcm_hw_params_is_batch(params) && !state->disable_batch; default_period = SPA_SCALE32_UP(DEFAULT_PERIOD, state->rate, DEFAULT_RATE); default_period = flp2(2 * default_period - 1); /* no period size specified. If we are batch or forcing our quantum, * use the graph requested quantum scaled by our rate */ if (period_size == 0 && (state->is_batch || state->force_quantum) && state->position) { period_size = SPA_SCALE32_UP(state->position->clock.target_duration, state->rate, state->position->clock.target_rate.denom); period_size = flp2(period_size); } if (period_size == 0) period_size = default_period; if (!state->disable_tsched || state->resample) { if (state->is_batch) { /* batch devices get their hw pointers updated every period. Make * the period smaller and add one period of headroom. Limit the * period size to our default so that we don't create too much * headroom. */ period_size = SPA_MIN(period_size, default_period) / 2; period_scale = 2; } else { /* disable ALSA wakeups */ if (snd_pcm_hw_params_can_disable_period_wakeup(params)) CHECK(snd_pcm_hw_params_set_period_wakeup(hndl, params, 0), "set_period_wakeup"); } } if (state->default_period_num != 0) { /* period number given use that */ periods = state->default_period_num; } else if (state->disable_tsched) { /* IRQ mode, use 3 periods. This is a bit of a workaround * for Firewire devices, which seem to only work with 3 periods. * For PipeWire it does not actually matter how many periods * are used, we will always keep 1 filled, so we can work fine * with anything from 2 periods to MAX. */ periods = 3; } else { periods = UINT_MAX; } /* Query the minimum period size for this configuration * This information is used as headroom if use_period_size_min_as_headroom is * set and default_headroom is 0 (not forced by user) */ CHECK(snd_pcm_hw_params_get_period_size_min(params, &state->period_size_min, &dir), "snd_pcm_hw_params_get_period_size_min"); if (state->default_period_size == 0) { /* Some devices (FireWire) don't produce audio if period number is too * small, so force a minimum. This will restrict possible period sizes if * the device has small buffer (like FireWire), so force it only if * period size was not set manually. */ snd_pcm_uframes_t period_size_max; unsigned int periods_min = (periods == UINT_MAX) ? 3 : periods; err = snd_pcm_hw_params_set_periods_min(hndl, params, &periods_min, &dir); if (!err) { CHECK(snd_pcm_hw_params_get_period_size_max(params, &period_size_max, &dir), "get_period_size_max"); if (period_size > period_size_max) period_size = SPA_MIN(period_size, flp2(period_size_max)); } else { spa_log_debug(state->log, "set_periods_min: %s", snd_strerror(err)); } } CHECK(snd_pcm_hw_params_set_period_size_near(hndl, params, &period_size, &dir), "set_period_size_near"); if (period_size == 0) { spa_log_error(state->log, "%s: invalid period_size 0 (driver error?)", state->name); return -EIO; } state->period_frames = period_size; state->duration = period_size * period_scale; if (periods != UINT_MAX) { CHECK(snd_pcm_hw_params_set_periods_near(hndl, params, &periods, &dir), "set_periods"); state->buffer_frames = period_size * periods; } else { CHECK(snd_pcm_hw_params_get_buffer_size_max(params, &state->buffer_frames), "get_buffer_size_max"); state->buffer_frames = SPA_MIN(state->buffer_frames, state->quantum_limit * 4 * state->frame_scale); CHECK(snd_pcm_hw_params_set_buffer_size_min(hndl, params, &state->buffer_frames), "set_buffer_size_min"); CHECK(snd_pcm_hw_params_set_buffer_size_near(hndl, params, &state->buffer_frames), "set_buffer_size_near"); periods = state->buffer_frames / period_size; } if (state->buffer_frames == 0) { spa_log_error(state->log, "%s: invalid buffer_frames 0 (driver error?)", state->name); return -EIO; } state->max_delay = state->buffer_frames / 2; if (spa_strstartswith(state->props.device, "a52") || spa_strstartswith(state->props.device, "dca") || (spa_strstartswith(state->props.device, "plug:") && strstr(state->props.device, "a52:"))) state->min_delay = SPA_MIN(2048u, state->buffer_frames); else state->min_delay = 0; state->start_delay = state->default_start_delay; recalc_headroom(state); spa_log_info(state->log, "%s: format:%s access:%s-%s rate:%d channels:%d " "buffer frames %lu, period frames %lu (min:%lu), periods %u, frame_size %zd " "headroom %u start-delay:%u batch:%u tsched:%u resample:%u", state->name, snd_pcm_format_name(state->format), state->use_mmap ? "mmap" : "rw", planar ? "planar" : "interleaved", state->rate, state->channels, state->buffer_frames, state->period_frames, state->period_size_min, periods, state->frame_size, state->headroom, state->start_delay, state->is_batch, !state->disable_tsched, state->resample); /* write the parameters to device */ CHECK(snd_pcm_hw_params(hndl, params), "set_hw_params"); return match ? 0 : 1; } int spa_alsa_update_rate_match(struct state *state) { uint64_t pitch, last_pitch; int err; if (!state->pitch_elem) return -ENOENT; /* The rate/pitch defines the rate of input to output (if there were a * resampler, it's the ratio of input samples to output samples). This * means that to adjust the playback rate, we need to apply the inverse * of the given rate. */ if (state->stream == SND_PCM_STREAM_CAPTURE) { pitch = (uint64_t)(1000000 * state->rate_match->rate); last_pitch = (uint64_t)(1000000 * state->last_rate); } else { pitch = (uint64_t)(1000000 / state->rate_match->rate); last_pitch = (uint64_t)(1000000 / state->last_rate); } /* The pitch adjustment is limited to 1 ppm according to the spec, but * let's avoid very granular changes so that we don't spam the host * (and ourselves, if bind-ctls are enabled). */ if (SPA_ABS((int)pitch - (int)last_pitch) < 10) return 0; snd_ctl_elem_value_set_integer(state->pitch_elem, 0, pitch); CHECK(snd_ctl_elem_write(state->ctl, state->pitch_elem), "snd_ctl_elem_write"); spa_log_trace_fp(state->log, "%s %u set rate to %g", state->name, state->stream, state->rate_match->rate); state->last_rate = state->rate_match->rate; return 0; } static int set_swparams(struct state *state) { snd_pcm_t *hndl = state->hndl; int err = 0; snd_pcm_sw_params_t *params; snd_pcm_sw_params_alloca(¶ms); /* get the current params */ CHECK(snd_pcm_sw_params_current(hndl, params), "sw_params_current"); CHECK(snd_pcm_sw_params_set_tstamp_mode(hndl, params, SND_PCM_TSTAMP_ENABLE), "sw_params_set_tstamp_mode"); CHECK(snd_pcm_sw_params_set_tstamp_type(hndl, params, SND_PCM_TSTAMP_TYPE_MONOTONIC), "sw_params_set_tstamp_type"); #if 0 snd_pcm_uframes_t boundary; CHECK(snd_pcm_sw_params_get_boundary(params, &boundary), "get_boundary"); CHECK(snd_pcm_sw_params_set_stop_threshold(hndl, params, boundary), "set_stop_threshold"); #endif /* start the transfer */ CHECK(snd_pcm_sw_params_set_start_threshold(hndl, params, LONG_MAX), "set_start_threshold"); if (state->disable_tsched) { snd_pcm_uframes_t avail_min = 0; if (state->stream == SND_PCM_STREAM_PLAYBACK) { /* wake up when buffer has target frames or less data (will underrun soon) */ if (state->buffer_frames >= (state->threshold + state->headroom)) avail_min = state->buffer_frames - (state->threshold + state->headroom); } else { /* wake up when there's target frames or more (enough for us to read and push a buffer) */ avail_min = SPA_MIN(state->threshold + state->headroom, state->buffer_frames); } CHECK(snd_pcm_sw_params_set_avail_min(hndl, params, avail_min), "set_avail_min"); } /* write the parameters to the playback device */ CHECK(snd_pcm_sw_params(hndl, params), "sw_params"); if (SPA_UNLIKELY(spa_log_level_topic_enabled(state->log, SPA_LOG_TOPIC_DEFAULT, SPA_LOG_LEVEL_DEBUG))) { spa_log_debug(state->log, "state after sw_params:"); snd_pcm_dump(hndl, state->output); fflush(state->log_file); } return 0; } static int set_timeout(struct state *state, uint64_t time) { struct itimerspec ts; ts.it_value.tv_sec = time / SPA_NSEC_PER_SEC; ts.it_value.tv_nsec = time % SPA_NSEC_PER_SEC; ts.it_interval.tv_sec = 0; ts.it_interval.tv_nsec = 0; spa_system_timerfd_settime(state->data_system, state->timerfd, SPA_FD_TIMER_ABSTIME, &ts, NULL); return 0; } static int spa_alsa_silence(struct state *state, snd_pcm_uframes_t silence) { snd_pcm_t *hndl = state->hndl; const snd_pcm_channel_area_t *my_areas; snd_pcm_uframes_t frames, offset; int i, res; if (state->use_mmap) { frames = state->buffer_frames; if (SPA_UNLIKELY((res = snd_pcm_mmap_begin(hndl, &my_areas, &offset, &frames)) < 0)) { spa_log_error(state->log, "%s: snd_pcm_mmap_begin error: %s", state->name, snd_strerror(res)); return res; } silence = SPA_MIN(silence, frames); spa_log_trace_fp(state->log, "%p: frames:%ld offset:%ld silence %ld", state, frames, offset, silence); snd_pcm_areas_silence(my_areas, offset, state->channels, silence, state->format); if (SPA_UNLIKELY((res = snd_pcm_mmap_commit(hndl, offset, silence)) < 0)) { spa_log_error(state->log, "%s: snd_pcm_mmap_commit error: %s", state->name, snd_strerror(res)); return res; } } else { uint8_t buffer[silence * state->frame_size]; memset(buffer, 0, silence * state->frame_size); if (state->planar) { void *bufs[state->channels]; for (i = 0; i < state->channels; i++) bufs[i] = buffer; snd_pcm_writen(hndl, bufs, silence); } else { snd_pcm_writei(hndl, buffer, silence); } } return 0; } static void reset_buffers(struct state *this) { uint32_t i; spa_list_init(&this->free); spa_list_init(&this->ready); this->ready_offset = 0; for (i = 0; i < this->n_buffers; i++) { struct buffer *b = &this->buffers[i]; if (this->stream == SND_PCM_STREAM_PLAYBACK) { SPA_FLAG_SET(b->flags, BUFFER_FLAG_OUT); spa_node_call_reuse_buffer(&this->callbacks, 0, b->id); } else { spa_list_append(&this->free, &b->link); SPA_FLAG_CLEAR(b->flags, BUFFER_FLAG_OUT); } } } static int do_prepare(struct state *state) { int err; state->last_threshold = state->threshold; spa_log_debug(state->log, "%p: start threshold:%d duration:%d rate:%d follower:%d match:%d resample:%d", state, state->threshold, state->driver_duration, state->driver_rate.denom, state->following, state->matching, state->resample); CHECK(set_swparams(state), "swparams"); if ((!state->linked) && (err = snd_pcm_prepare(state->hndl)) < 0 && err != -EBUSY) { spa_log_error(state->log, "%s: snd_pcm_prepare error: %s", state->name, snd_strerror(err)); return err; } if (state->stream == SND_PCM_STREAM_PLAYBACK) { snd_pcm_uframes_t silence = state->start_delay + state->threshold + state->headroom; if (state->disable_tsched) silence += state->threshold; spa_alsa_silence(state, silence); } reset_buffers(state); state->alsa_sync = true; state->alsa_sync_warning = false; state->alsa_started = false; spa_dll_init(&state->dll); return 0; } static inline int do_drop(struct state *state) { int res; spa_log_debug(state->log, "%p: snd_pcm_drop linked:%u", state, state->linked); if (!state->linked && (res = snd_pcm_drop(state->hndl)) < 0) { spa_log_error(state->log, "%s: snd_pcm_drop: %s", state->name, snd_strerror(res)); return res; } return 0; } static inline int do_start(struct state *state) { int res; if (SPA_UNLIKELY(!state->alsa_started)) { spa_log_debug(state->log, "%p: snd_pcm_start linked:%u", state, state->linked); if (!state->linked && (res = snd_pcm_start(state->hndl)) < 0) { spa_log_error(state->log, "%s: snd_pcm_start: %s", state->name, snd_strerror(res)); return res; } state->alsa_started = true; } return 0; } static inline int check_position_config(struct state *state, bool starting); static void update_sources(struct state *state, bool active); static int alsa_recover(struct state *state) { int res, st, retry = 0; snd_pcm_status_t *status; struct state *driver, *follower; snd_pcm_status_alloca(&status); if (SPA_UNLIKELY((res = snd_pcm_status(state->hndl, status)) < 0)) { spa_log_error(state->log, "%s: snd_pcm_status error: %s", state->name, snd_strerror(res)); goto recover; } st = snd_pcm_status_get_state(status); switch (st) { case SND_PCM_STATE_XRUN: { struct timeval now, trigger, diff; uint64_t delay, missing; snd_pcm_status_get_tstamp (status, &now); snd_pcm_status_get_trigger_tstamp (status, &trigger); timersub(&now, &trigger, &diff); delay = SPA_TIMEVAL_TO_USEC(&diff); missing = delay * state->rate / SPA_USEC_PER_SEC; missing += state->start_delay + state->threshold + state->headroom; spa_log_trace(state->log, "%p: xrun of %"PRIu64" usec %"PRIu64, state, delay, missing); if (state->clock) { state->clock->xrun += SPA_SCALE32_UP(missing, state->clock->rate.denom, state->rate); } spa_node_call_xrun(&state->callbacks, SPA_TIMEVAL_TO_USEC(&trigger), delay, NULL); break; } case SND_PCM_STATE_SUSPENDED: spa_log_info(state->log, "%s: recover from state %s", state->name, snd_pcm_state_name(st)); while (retry++ < 5 && (res = snd_pcm_resume(state->hndl)) == -EAGAIN) /* wait until suspend flag is released */ poll(NULL, 0, 1000); if (res >= 0) return res; /* try to drop and prepare below */ break; default: spa_log_error(state->log, "%s: recover from error state %s", state->name, snd_pcm_state_name(st)); break; } recover: if (state->driver && state->linked) driver = state->driver; else driver = state; do_drop(driver); spa_list_for_each(follower, &driver->rt.followers, rt.driver_link) { if (follower != driver && follower->linked) { do_drop(follower); check_position_config(follower, false); } } do_prepare(driver); spa_list_for_each(follower, &driver->rt.followers, rt.driver_link) { if (follower != driver && follower->linked) do_prepare(follower); } do_start(driver); spa_list_for_each(follower, &driver->rt.followers, rt.driver_link) { if (follower != driver && follower->linked) do_start(follower); } update_sources(state, true); return 0; } static inline snd_pcm_sframes_t alsa_avail(struct state *state) { snd_pcm_sframes_t avail; if (!state->matching && state->disable_tsched && !state->resample) avail = snd_pcm_avail_update(state->hndl); else avail = snd_pcm_avail(state->hndl); return avail; } static int get_avail(struct state *state, uint64_t current_time, snd_pcm_uframes_t *delay) { int res, suppressed; snd_pcm_sframes_t avail; if (SPA_UNLIKELY((avail = alsa_avail(state)) < 0)) { if ((res = alsa_recover(state)) < 0) return res; if ((avail = alsa_avail(state)) < 0) { if ((suppressed = spa_ratelimit_test(&state->rate_limit, current_time)) >= 0) { spa_log_warn(state->log, "%s: (%d suppressed) snd_pcm_avail after recover: %s", state->name, suppressed, snd_strerror(avail)); } avail = state->threshold * 2; } } *delay = avail; if (state->htimestamp) { snd_pcm_uframes_t havail; snd_htimestamp_t tstamp; uint64_t then; if ((res = snd_pcm_htimestamp(state->hndl, &havail, &tstamp)) < 0) { if ((suppressed = spa_ratelimit_test(&state->rate_limit, current_time)) >= 0) { spa_log_warn(state->log, "%s: (%d suppressed) snd_pcm_htimestamp error: %s", state->name, suppressed, snd_strerror(res)); } return avail; } avail = havail; *delay = havail; if ((then = SPA_TIMESPEC_TO_NSEC(&tstamp)) != 0) { int64_t diff; if (then < current_time) diff = ((int64_t)(current_time - then)) * state->rate / SPA_NSEC_PER_SEC; else diff = -((int64_t)(then - current_time)) * state->rate / SPA_NSEC_PER_SEC; spa_log_trace_fp(state->log, "%"PRIu64" %"PRIu64" %"PRIi64, current_time, then, diff); if (SPA_ABS(diff) < state->threshold * 3) { *delay += SPA_CLAMP(diff, -((int64_t)state->threshold), (int64_t)state->threshold); state->htimestamp_error = 0; } else if (state->htimestamp_max_errors) { if (++state->htimestamp_error > state->htimestamp_max_errors) { spa_log_error(state->log, "%s: wrong htimestamps from driver, disabling", state->name); state->htimestamp_error = 0; state->htimestamp = false; } else if ((suppressed = spa_ratelimit_test(&state->rate_limit, current_time)) >= 0) { spa_log_warn(state->log, "%s: (%d suppressed) impossible htimestamp diff:%"PRIi64, state->name, suppressed, diff); } } } } return avail; } static int get_status(struct state *state, uint64_t current_time, snd_pcm_uframes_t *avail, snd_pcm_uframes_t *delay, snd_pcm_uframes_t *target) { int res; snd_pcm_uframes_t a, d; if ((res = get_avail(state, current_time, &d)) < 0) return res; a = SPA_MIN(res, (int)state->buffer_frames); if (state->resample && state->rate_match) { state->delay = state->rate_match->delay; state->read_size = state->rate_match->size; } else { state->delay = 0; state->read_size = state->threshold; } if (state->stream == SND_PCM_STREAM_PLAYBACK) { *avail = state->buffer_frames - a; *delay = state->buffer_frames - SPA_MIN(d, state->buffer_frames); *target = state->threshold + state->headroom; } else { *avail = a; *delay = d; *target = SPA_MAX(state->threshold, state->read_size) + state->headroom; } *target = SPA_CLAMP(*target, state->min_delay, state->max_delay); return 0; } static int update_time(struct state *state, uint64_t current_time, snd_pcm_sframes_t delay, snd_pcm_sframes_t target, bool follower) { double err, corr, avg; int32_t diff; if (SPA_UNLIKELY(state->dll.bw == 0.0)) { spa_dll_set_bw(&state->dll, state->dll_bw_max, state->threshold, state->rate); state->next_time = current_time; state->base_time = current_time; } if (state->disable_tsched && !follower) { err = (int64_t)(current_time - state->next_time); err = err / 1e9 * state->rate; } else { if (state->stream == SND_PCM_STREAM_PLAYBACK) err = delay - target; else err = target - delay; } diff = (int32_t) (state->last_threshold - state->threshold); if (SPA_UNLIKELY(diff != 0)) { err -= diff; spa_log_trace(state->log, "%p: follower:%d quantum change %d -> %d (%d) %f", state, follower, state->last_threshold, state->threshold, diff, err); state->last_threshold = state->threshold; state->alsa_sync = true; state->alsa_sync_warning = false; } if (err > state->max_resync) { state->alsa_sync = true; if (err > state->max_error) err = state->max_error; } else if (err < -state->max_resync) { state->alsa_sync = true; if (err < -state->max_error) err = -state->max_error; } if (!follower || state->matching) { corr = spa_dll_update(&state->dll, err); avg = (state->err_avg * state->err_wdw + (err - state->err_avg)) / (state->err_wdw + 1.0); state->err_var = (state->err_var * state->err_wdw + (err - state->err_avg) * (err - avg)) / (state->err_wdw + 1.0); state->err_avg = avg; } else { corr = 1.0; } if (diff < 0) state->next_time += (uint64_t)(diff / corr * 1e9 / state->rate); if (SPA_UNLIKELY((state->next_time - state->base_time) > BW_PERIOD)) { double bw; state->base_time = state->next_time; bw = (fabs(state->err_avg) + sqrt(fabs(state->err_var)))/1000.0; spa_log_debug(state->log, "%s: follower:%d match:%d rate:%f " "bw:%f thr:%u del:%ld target:%ld err:%f max_err:%f max_resync: %f var:%f:%f:%f", state->name, follower, state->matching, corr, state->dll.bw, state->threshold, delay, target, err, state->max_error, state->max_resync, state->err_avg, state->err_var, bw); spa_dll_set_bw(&state->dll, SPA_CLAMPD(bw, SPA_ALSA_DLL_BW_MIN, state->dll_bw_max), state->threshold, state->rate); } if (state->rate_match) { if (state->stream == SND_PCM_STREAM_PLAYBACK) state->rate_match->rate = corr; else state->rate_match->rate = 1.0/corr; if (state->pitch_elem && state->matching) spa_alsa_update_rate_match(state); else SPA_FLAG_UPDATE(state->rate_match->flags, SPA_IO_RATE_MATCH_FLAG_ACTIVE, state->matching); } state->next_time += (uint64_t)(state->threshold / corr * 1e9 / state->rate); if (SPA_LIKELY(state->clock)) { state->clock->nsec = current_time; state->clock->rate = state->driver_rate; state->clock->position += state->clock->duration; state->clock->duration = state->driver_duration; state->clock->delay = delay + state->delay; state->clock->rate_diff = corr; state->clock->next_nsec = state->next_time; } spa_log_trace_fp(state->log, "%p: follower:%d %"PRIu64" %"PRIu64" %f %ld %ld %f %f %u %d", state, follower, current_time, state->next_time, corr, delay, target, err, state->threshold * corr, state->threshold, state->matching); return 0; } static bool need_resample(struct state *state) { return !state->pitch_elem && ((state->rate != 0 && state->driver_rate.denom != 0 && (uint32_t)state->rate != state->driver_rate.denom) || state->matching); } static int setup_matching(struct state *state) { state->matching = state->following; if (state->position == NULL) return -ENOTSUP; spa_log_debug(state->log, "driver clock:'%s' our clock:'%s'", state->position->clock.name, state->clock_name); if (spa_streq(state->position->clock.name, state->clock_name)) state->matching = false; state->resample = need_resample(state); check_position_config(state, false); recalc_headroom(state); spa_log_info(state->log, "driver clock:'%s'@%d our clock:'%s'@%d matching:%d resample:%d", state->position->clock.name, state->driver_rate.denom, state->clock_name, state->rate, state->matching, state->resample); return 0; } static void update_sources(struct state *state, bool active) { if (state->disable_tsched && state->rt.sources_added) { for (int i = 0; i < state->n_fds; i++) { state->source[i].mask = active ? state->pfds[i].events : 0; spa_loop_update_source(state->data_loop, &state->source[i]); } } } static inline int check_position_config(struct state *state, bool starting) { uint64_t target_duration; struct spa_fraction target_rate; struct spa_io_position *pos; bool can_force; if (SPA_UNLIKELY((pos = state->position) == NULL)) return 0; /* we can force rate/duration when we are the driver and started/starting */ can_force = (starting || state->started) && !state->following; if (state->force_quantum && can_force) { target_rate = SPA_FRACTION(1, state->rate); target_duration = state->duration; pos->clock.target_rate = target_rate; pos->clock.target_duration = target_duration; } else { target_rate = pos->clock.target_rate; target_duration = pos->clock.target_duration; } if (target_duration == 0 || target_rate.denom == 0) return -EIO; if (SPA_UNLIKELY((state->driver_duration != target_duration) || (state->driver_rate.denom != target_rate.denom))) { spa_log_info(state->log, "%p: follower:%d duration:%u->%"PRIu64" rate:%d->%d", state, state->following, state->driver_duration, target_duration, state->driver_rate.denom, target_rate.denom); state->driver_duration = target_duration; state->driver_rate = target_rate; state->threshold = SPA_SCALE32_UP(state->driver_duration, state->rate, state->driver_rate.denom); state->max_error = SPA_MAX(256.0f, (state->threshold + state->headroom) / 2.0f); state->max_resync = SPA_MIN(state->threshold + state->headroom, state->max_error); state->err_wdw = (double)state->driver_rate.denom/state->driver_duration; state->resample = need_resample(state); state->alsa_sync = true; } return 0; } static int alsa_write_sync(struct state *state, uint64_t current_time) { int res, suppressed; snd_pcm_uframes_t avail, delay, target; bool following = state->following; if (SPA_UNLIKELY((res = check_position_config(state, false)) < 0)) return res; if (SPA_UNLIKELY((res = get_status(state, current_time, &avail, &delay, &target)) < 0)) { spa_log_error(state->log, "get_status error: %s", spa_strerror(res)); state->next_time += (uint64_t)(state->threshold * 1e9 / state->rate); return res; } if (SPA_UNLIKELY(!following && state->alsa_started && delay > target + state->max_error)) { spa_log_trace(state->log, "%p: early wakeup %ld %lu %lu", state, avail, delay, target); if (delay > target * 3) delay = target * 3; state->next_time = current_time + (delay - target) * SPA_NSEC_PER_SEC / state->rate; return -EAGAIN; } if (SPA_UNLIKELY((res = update_time(state, current_time, delay, target, following)) < 0)) return res; if (following && state->alsa_started) { if (SPA_UNLIKELY(state->alsa_sync)) { enum spa_log_level lev; if (!state->linked) { if (SPA_UNLIKELY(state->alsa_sync_warning)) lev = SPA_LOG_LEVEL_WARN; else lev = SPA_LOG_LEVEL_INFO; if ((suppressed = spa_ratelimit_test(&state->rate_limit, current_time)) < 0) lev = SPA_LOG_LEVEL_DEBUG; spa_log_lev(state->log, lev, "%s: follower avail:%lu delay:%ld " "target:%ld thr:%u, resync (%d suppressed)", state->name, avail, delay, target, state->threshold, suppressed); if (delay > target) snd_pcm_rewind(state->hndl, delay - target); else if (delay < target) spa_alsa_silence(state, target - delay); avail = target; } spa_dll_init(&state->dll); state->alsa_sync = false; } else state->alsa_sync_warning = true; } return 0; } static int alsa_write_frames(struct state *state) { snd_pcm_t *hndl = state->hndl; const snd_pcm_channel_area_t *my_areas; snd_pcm_uframes_t written, frames, offset, off, to_write, total_written; snd_pcm_sframes_t commitres; int res = 0; size_t frame_size = state->frame_size; total_written = 0; again: frames = state->buffer_frames; if (state->use_mmap && frames > 0) { if (SPA_UNLIKELY((res = snd_pcm_mmap_begin(hndl, &my_areas, &offset, &frames)) < 0)) { spa_log_error(state->log, "%s: snd_pcm_mmap_begin error: %s", state->name, snd_strerror(res)); alsa_recover(state); return res; } spa_log_trace_fp(state->log, "%p: begin offset:%ld avail:%ld threshold:%d", state, offset, frames, state->threshold); off = offset; } else { off = 0; } to_write = frames; written = 0; while (!spa_list_is_empty(&state->ready) && to_write > 0) { size_t n_bytes, n_frames; struct buffer *b; struct spa_data *d; uint32_t i, offs, size, last_offset; b = spa_list_first(&state->ready, struct buffer, link); d = b->buf->datas; offs = d[0].chunk->offset + state->ready_offset; last_offset = d[0].chunk->size; size = last_offset - state->ready_offset; offs = SPA_MIN(offs, d[0].maxsize); size = SPA_MIN(d[0].maxsize - offs, size); n_frames = SPA_MIN(size / frame_size, to_write); n_bytes = n_frames * frame_size; if (SPA_LIKELY(state->use_mmap)) { for (i = 0; i < b->buf->n_datas; i++) { spa_memcpy(channel_area_addr(&my_areas[i], off), SPA_PTROFF(d[i].data, offs, void), n_bytes); } } else { void *bufs[b->buf->n_datas]; for (i = 0; i < b->buf->n_datas; i++) bufs[i] = SPA_PTROFF(d[i].data, offs, void); if (state->planar) snd_pcm_writen(hndl, bufs, n_frames); else snd_pcm_writei(hndl, bufs[0], n_frames); } state->ready_offset += n_bytes; if (state->ready_offset >= last_offset) { spa_list_remove(&b->link); SPA_FLAG_SET(b->flags, BUFFER_FLAG_OUT); state->io->buffer_id = b->id; spa_log_trace_fp(state->log, "%p: reuse buffer %u", state, b->id); spa_node_call_reuse_buffer(&state->callbacks, 0, b->id); state->ready_offset = 0; } written += n_frames; off += n_frames; to_write -= n_frames; } spa_log_trace_fp(state->log, "%p: commit offset:%ld written:%ld sample_count:%"PRIi64, state, offset, written, state->sample_count); total_written += written; if (state->use_mmap && written > 0) { if (SPA_UNLIKELY((commitres = snd_pcm_mmap_commit(hndl, offset, written)) < 0)) { if (commitres == -EPIPE || commitres == -ESTRPIPE) { spa_log_warn(state->log, "%s: snd_pcm_mmap_commit error: %s", state->name, snd_strerror(commitres)); } else { spa_log_error(state->log, "%s: snd_pcm_mmap_commit error: %s", state->name, snd_strerror(commitres)); return res; } } if (commitres > 0 && written != (snd_pcm_uframes_t) commitres) { spa_log_warn(state->log, "%s: mmap_commit wrote %ld instead of %ld", state->name, commitres, written); } } if (!spa_list_is_empty(&state->ready) && written > 0) goto again; state->sample_count += total_written; if (SPA_UNLIKELY(!state->alsa_started && (total_written > 0 || frames == 0))) do_start(state); update_sources(state, true); return 0; } int spa_alsa_write(struct state *state) { if (state->following && state->rt.driver == NULL) { uint64_t current_time = state->position->clock.nsec; alsa_write_sync(state, current_time); } return alsa_write_frames(state); } void spa_alsa_recycle_buffer(struct state *this, uint32_t buffer_id) { struct buffer *b = &this->buffers[buffer_id]; if (SPA_FLAG_IS_SET(b->flags, BUFFER_FLAG_OUT)) { spa_log_trace_fp(this->log, "%p: recycle buffer %u", this, buffer_id); spa_list_append(&this->free, &b->link); SPA_FLAG_CLEAR(b->flags, BUFFER_FLAG_OUT); } } static snd_pcm_uframes_t push_frames(struct state *state, const snd_pcm_channel_area_t *my_areas, snd_pcm_uframes_t offset, snd_pcm_uframes_t frames) { snd_pcm_uframes_t total_frames = 0; if (spa_list_is_empty(&state->free)) { spa_log_warn(state->log, "%s: no more buffers", state->name); total_frames = frames; } else { size_t n_bytes, left, frame_size = state->frame_size; struct buffer *b; struct spa_data *d; uint32_t i, avail, l0, l1; b = spa_list_first(&state->free, struct buffer, link); spa_list_remove(&b->link); if (b->h) { b->h->seq = state->sample_count; b->h->pts = state->next_time; b->h->dts_offset = 0; } d = b->buf->datas; avail = d[0].maxsize / frame_size; total_frames = SPA_MIN(avail, frames); n_bytes = total_frames * frame_size; if (my_areas) { left = state->buffer_frames - offset; l0 = SPA_MIN(n_bytes, left * frame_size); l1 = n_bytes - l0; for (i = 0; i < b->buf->n_datas; i++) { spa_memcpy(d[i].data, channel_area_addr(&my_areas[i], offset), l0); if (SPA_UNLIKELY(l1 > 0)) spa_memcpy(SPA_PTROFF(d[i].data, l0, void), channel_area_addr(&my_areas[i], 0), l1); d[i].chunk->offset = 0; d[i].chunk->size = n_bytes; d[i].chunk->stride = frame_size; } } else { void *bufs[b->buf->n_datas]; for (i = 0; i < b->buf->n_datas; i++) { bufs[i] = d[i].data; d[i].chunk->offset = 0; d[i].chunk->size = n_bytes; d[i].chunk->stride = frame_size; } if (state->planar) { snd_pcm_readn(state->hndl, bufs, total_frames); } else { snd_pcm_readi(state->hndl, bufs[0], total_frames); } } spa_log_trace_fp(state->log, "%p: wrote %ld frames into buffer %d", state, total_frames, b->id); spa_list_append(&state->ready, &b->link); } return total_frames; } static int alsa_read_sync(struct state *state, uint64_t current_time) { int res, suppressed; snd_pcm_uframes_t avail, delay, target, max_read; bool following = state->following; if (SPA_UNLIKELY(!state->alsa_started)) return 0; if (SPA_UNLIKELY((res = check_position_config(state, false)) < 0)) return res; if (SPA_UNLIKELY((res = get_status(state, current_time, &avail, &delay, &target)) < 0)) { spa_log_error(state->log, "get_status error: %s", spa_strerror(res)); state->next_time += (uint64_t)(state->threshold * 1e9 / state->rate); return res; } if (SPA_UNLIKELY(!following && avail < state->read_size)) { spa_log_trace(state->log, "%p: early wakeup %ld %ld %ld %d", state, delay, avail, target, state->read_size); state->next_time = current_time + (state->read_size - avail) * SPA_NSEC_PER_SEC / state->rate; return -EAGAIN; } if (SPA_UNLIKELY((res = update_time(state, current_time, delay, target, following)) < 0)) return res; max_read = state->buffer_frames; if (following) { if (state->alsa_sync) { if (!state->linked) { enum spa_log_level lev; if (SPA_UNLIKELY(state->alsa_sync_warning)) lev = SPA_LOG_LEVEL_WARN; else lev = SPA_LOG_LEVEL_INFO; if ((suppressed = spa_ratelimit_test(&state->rate_limit, current_time)) < 0) lev = SPA_LOG_LEVEL_DEBUG; spa_log_lev(state->log, lev, "%s: follower delay:%ld target:%ld thr:%u " "resample:%d, resync (%d suppressed)", state->name, delay, target, state->threshold, state->resample, suppressed); if (avail < target) max_read = target - avail; else if (avail > target) { snd_pcm_forward(state->hndl, avail - target); avail = target; } } state->alsa_sync = false; spa_dll_init(&state->dll); } else state->alsa_sync_warning = true; if (avail < state->read_size) max_read = 0; } state->max_read = SPA_MIN(max_read, state->read_size); return 0; } static int alsa_read_frames(struct state *state) { snd_pcm_t *hndl = state->hndl; snd_pcm_uframes_t total_read = 0, avail; const snd_pcm_channel_area_t *my_areas; snd_pcm_uframes_t read, frames, offset; snd_pcm_sframes_t commitres; int res = 0; frames = state->max_read; if (state->use_mmap) { avail = state->buffer_frames; if ((res = snd_pcm_mmap_begin(hndl, &my_areas, &offset, &avail)) < 0) { spa_log_error(state->log, "%s: snd_pcm_mmap_begin error: %s", state->name, snd_strerror(res)); alsa_recover(state); return res; } spa_log_trace_fp(state->log, "%p: begin offs:%ld frames:%ld avail:%ld thres:%d", state, offset, frames, avail, state->threshold); } else { my_areas = NULL; offset = 0; } if (frames > 0) { read = push_frames(state, my_areas, offset, frames); total_read += read; } else { spa_alsa_skip(state); total_read += state->read_size; read = 0; } if (state->use_mmap && read > 0) { spa_log_trace_fp(state->log, "%p: commit offs:%ld read:%ld count:%"PRIi64, state, offset, read, state->sample_count); if ((commitres = snd_pcm_mmap_commit(hndl, offset, read)) < 0) { enum spa_log_level lev; if (SPA_UNLIKELY(state->alsa_sync_warning)) lev = SPA_LOG_LEVEL_ERROR; else lev = SPA_LOG_LEVEL_INFO; spa_log_lev(state->log, lev, "%s: snd_pcm_mmap_commit error %lu %lu %lu: %s", state->name, frames, avail, read, snd_strerror(commitres)); if (commitres != -EPIPE && commitres != -ESTRPIPE) return res; } if (commitres > 0 && read != (snd_pcm_uframes_t) commitres) { spa_log_warn(state->log, "%s: mmap_commit read %ld instead of %ld", state->name, commitres, read); } } state->sample_count += total_read; return 0; } int spa_alsa_read(struct state *state) { if (state->following && state->rt.driver == NULL) { uint64_t current_time = state->position->clock.nsec; alsa_read_sync(state, current_time); } else if (state->resample && state->rate_match) { state->read_size = state->rate_match->size; state->max_read = SPA_MIN(state->buffer_frames, state->read_size); } return alsa_read_frames(state); } int spa_alsa_skip(struct state *state) { struct buffer *b; struct spa_data *d; uint32_t i, avail, total_frames, n_bytes, frames; if (SPA_UNLIKELY(spa_list_is_empty(&state->free))) { spa_log_warn(state->log, "%s: no more buffers", state->name); return -EPIPE; } frames = state->read_size; b = spa_list_first(&state->free, struct buffer, link); spa_list_remove(&b->link); d = b->buf->datas; avail = d[0].maxsize / state->frame_size; total_frames = SPA_MIN(avail, frames); n_bytes = total_frames * state->frame_size; for (i = 0; i < b->buf->n_datas; i++) { memset(d[i].data, 0, n_bytes); d[i].chunk->offset = 0; d[i].chunk->size = n_bytes; d[i].chunk->stride = state->frame_size; } spa_list_append(&state->ready, &b->link); return 0; } static int playback_ready(struct state *state) { struct spa_io_buffers *io = state->io; spa_log_trace_fp(state->log, "%p: %d", state, io ? io->status : 0); update_sources(state, false); if (io != NULL) io->status = SPA_STATUS_NEED_DATA; return spa_node_call_ready(&state->callbacks, SPA_STATUS_NEED_DATA); } static int capture_ready(struct state *state) { struct spa_io_buffers *io; bool have_data; have_data = !spa_list_is_empty(&state->ready); io = state->io; if (io != NULL && (io->status != SPA_STATUS_HAVE_DATA || state->rate_match != NULL)) { struct buffer *b; if (SPA_LIKELY(io->buffer_id < state->n_buffers)) spa_alsa_recycle_buffer(state, io->buffer_id); if (SPA_LIKELY(have_data)) { b = spa_list_first(&state->ready, struct buffer, link); spa_list_remove(&b->link); SPA_FLAG_SET(b->flags, BUFFER_FLAG_OUT); io->buffer_id = b->id; io->status = SPA_STATUS_HAVE_DATA; } else { io->buffer_id = SPA_ID_INVALID; } spa_log_trace_fp(state->log, "%p: output buffer:%d", state, io->buffer_id); } if (have_data) spa_node_call_ready(&state->callbacks, SPA_STATUS_HAVE_DATA); return 0; } static uint64_t get_time_ns(struct state *state) { struct timespec now; if (spa_system_clock_gettime(state->data_system, CLOCK_MONOTONIC, &now) < 0) return 0; return SPA_TIMESPEC_TO_NSEC(&now); } static inline int alsa_do_wakeup_work(struct state *state, uint64_t current_time) { struct state *follower; int res; /* first do all the sync */ if (state->stream == SND_PCM_STREAM_CAPTURE) res = alsa_read_sync(state, current_time); else res = alsa_write_sync(state, current_time); /* we can get -EAGAIN when we need to wait some more */ if (SPA_UNLIKELY(res == -EAGAIN)) return res; spa_list_for_each(follower, &state->rt.followers, rt.driver_link) { if (follower == state) continue; if (follower->stream == SND_PCM_STREAM_CAPTURE) alsa_read_sync(follower, current_time); else alsa_write_sync(follower, current_time); } /* then read this source, the sinks will be written to when the * graph completes. We can't read other follower sources yet because * the resampler first needs to run. */ if (state->stream == SND_PCM_STREAM_CAPTURE) alsa_read_frames(state); /* and then trigger the graph */ if (state->stream == SND_PCM_STREAM_PLAYBACK) playback_ready(state); else capture_ready(state); return 0; } static void alsa_irq_wakeup_event(struct spa_source *source) { struct state *state = source->data; uint64_t current_time; int res, err; unsigned short revents; snd_pcm_uframes_t havail; snd_htimestamp_t tstamp; // First, take a snapshot of the wakeup time current_time = get_time_ns(state); // If the hi-res timestamps are working, we will get a timestamp that // is earlier then current_time if ((res = snd_pcm_htimestamp(state->hndl, &havail, &tstamp)) == 0) { uint64_t htime = SPA_TIMESPEC_TO_NSEC(&tstamp); if (htime < current_time) { current_time = htime; } } for (int i = 0; i < state->n_fds; i++) { state->pfds[i].revents = state->source[i].rmask; /* Reset so that we only handle all our sources' events once */ state->source[i].rmask = 0; } /* ALSA poll fds need to be "demangled" to know whether it's a real wakeup */ if (SPA_UNLIKELY(err = snd_pcm_poll_descriptors_revents(state->hndl, state->pfds, state->n_fds, &revents))) { spa_log_error(state->log, "Could not look up revents: %s", snd_strerror(err)); return; } if (!revents) { spa_log_trace_fp(state->log, "Woken up with no work to do"); return; } if (revents & POLLERR) { spa_log_trace_fp(state->log, "poll error"); if ((res = alsa_recover(state)) < 0) return; } alsa_do_wakeup_work(state, current_time); } static void alsa_timer_wakeup_event(struct spa_source *source) { struct state *state = source->data; uint64_t expire, current_time; int res, suppressed; if (SPA_LIKELY(state->started)) { if (SPA_UNLIKELY((res = spa_system_timerfd_read(state->data_system, state->timerfd, &expire)) < 0)) { /* we can get here when the timer is changed since the last * timerfd wakeup, for example by do_reassign_follower() executed * in the same epoll wakeup cycle */ if (res != -EAGAIN) spa_log_warn(state->log, "%p: error reading timerfd: %s", state, spa_strerror(res)); return; } } current_time = state->next_time; alsa_do_wakeup_work(state, current_time); if (state->next_time > current_time + SPA_NSEC_PER_SEC || current_time > state->next_time + SPA_NSEC_PER_SEC) { if ((suppressed = spa_ratelimit_test(&state->rate_limit, current_time)) >= 0) { spa_log_error(state->log, "%s: impossible timeout %" PRIu64" %"PRIu64" %"PRIi64" %d %"PRIi64" (%d suppressed)", state->name, current_time, state->next_time, state->next_time - current_time, state->threshold, state->sample_count, suppressed); } state->next_time = (uint64_t)(current_time + state->threshold * 1e9 / state->rate); } set_timeout(state, state->next_time); } static void remove_sources(struct state *state) { int i; if (state->rt.sources_added) { for (i = 0; i < state->n_fds; i++) spa_loop_remove_source(state->data_loop, &state->source[i]); state->rt.sources_added = false; } } static void add_sources(struct state *state) { int i; if (!state->rt.sources_added) { for (i = 0; i < state->n_fds; i++) spa_loop_add_source(state->data_loop, &state->source[i]); state->rt.sources_added = true; } } static int do_state_sync(struct spa_loop *loop, bool async, uint32_t seq, const void *data, size_t size, void *user_data) { struct state *state = user_data; struct rt_state *rt = &state->rt; if (state->started) { state->next_time = get_time_ns(state); if (rt->driver != state->driver) { spa_dll_init(&state->dll); if (rt->driver != NULL) spa_list_remove(&rt->driver_link); if (state->driver != NULL) spa_list_append(&state->driver->rt.followers, &rt->driver_link); rt->driver = state->driver; spa_log_debug(state->log, "state:%p -> driver:%p", state, state->driver); if (state->linked && state->matching) try_unlink(state); } if (state->following) { remove_sources(state); set_timeout(state, 0); } else { add_sources(state); if (!state->disable_tsched) set_timeout(state, state->next_time); } } else { if (rt->driver) { spa_list_remove(&rt->driver_link); rt->driver = NULL; } if (!state->disable_tsched) set_timeout(state, 0); remove_sources(state); } return 0; } int spa_alsa_prepare(struct state *state) { struct state *follower; int err; if (!state->opened) return -EIO; spa_alsa_pause(state); if (state->prepared) return 0; if (check_position_config(state, true) < 0) { spa_log_error(state->log, "%s: invalid position config", state->name); return -EIO; } if ((err = do_prepare(state)) < 0) return err; spa_list_for_each(follower, &state->followers, driver_link) { if (follower != state && !follower->matching) { if (spa_alsa_prepare(follower) < 0) continue; if (!follower->linked && state->auto_link) do_link(state, follower); } } state->prepared = true; return 0; } int spa_alsa_start(struct state *state) { struct state *follower; int err; if (state->started) return 0; else if (!state->opened) return -EIO; spa_alsa_prepare(state); if (!state->disable_tsched) { /* Timer-based scheduling */ state->source[0].func = alsa_timer_wakeup_event; state->source[0].data = state; state->source[0].fd = state->timerfd; state->source[0].mask = SPA_IO_IN; state->source[0].rmask = 0; state->n_fds = 1; } else { /* ALSA period-based scheduling */ err = snd_pcm_poll_descriptors_count(state->hndl); if (err < 0) { spa_log_error(state->log, "Could not get poll descriptor count: %s", snd_strerror(err)); return err; } if (err > MAX_POLL) { spa_log_error(state->log, "Unsupported poll descriptor count: %d", err); return -EIO; } state->n_fds = err; if ((err = snd_pcm_poll_descriptors(state->hndl, state->pfds, state->n_fds)) < 0) { spa_log_error(state->log, "Could not get poll descriptors: %s", snd_strerror(err)); return err; } /* We only add the source to the data loop if we're driving. * This is done in add_sources() */ for (int i = 0; i < state->n_fds; i++) { state->source[i].func = alsa_irq_wakeup_event; state->source[i].data = state; state->source[i].fd = state->pfds[i].fd; state->source[i].mask = state->pfds[i].events; state->source[i].rmask = 0; } } spa_list_for_each(follower, &state->followers, driver_link) if (follower != state) spa_alsa_start(follower); /* start capture now. We should have some data when the timer or IRQ * goes off later */ if (state->stream == SND_PCM_STREAM_CAPTURE) { if ((err = do_start(state)) < 0) return err; } /* playback will start after first write. Without tsched, we start * right away so that the fds become active in poll right away. */ if (state->stream == SND_PCM_STREAM_PLAYBACK) { if (state->disable_tsched || state->start_delay > 0) if ((err = do_start(state)) < 0) return err; } state->started = true; spa_loop_locked(state->data_loop, do_state_sync, 0, NULL, 0, state); return 0; } static struct state *find_state(uint32_t id) { struct state *state; spa_list_for_each(state, &states, link) { if (state->clock != NULL && state->clock->id == id) return state; } return NULL; } int spa_alsa_reassign_follower(struct state *state) { bool following, freewheel; struct spa_io_position *pos = state->position; struct spa_io_clock *clock = state->clock; struct state *driver; if (clock != NULL) spa_scnprintf(clock->name, sizeof(clock->name), "%s", state->clock_name); following = pos && clock && pos->clock.id != clock->id; driver = pos != NULL ? find_state(pos->clock.id) : NULL; if (driver != state->driver) { spa_log_debug(state->log, "%p: reassign driver %p->%p", state, state->driver, driver); if (state->driver != NULL) spa_list_remove(&state->driver_link); if (driver != NULL) { spa_list_append(&driver->followers, &state->driver_link); } state->driver = driver; } if (following != state->following) { spa_log_debug(state->log, "%p: reassign follower %d->%d", state, state->following, following); state->following = following; } setup_matching(state); if (state->started) spa_loop_locked(state->data_loop, do_state_sync, 0, NULL, 0, state); else if (state->want_started) spa_alsa_start(state); freewheel = pos != NULL && SPA_FLAG_IS_SET(pos->clock.flags, SPA_IO_CLOCK_FLAG_FREEWHEEL); if (state->freewheel != freewheel) { spa_log_debug(state->log, "%p: freewheel %d->%d", state, state->freewheel, freewheel); state->freewheel = freewheel; if (state->started) { if (freewheel) snd_pcm_pause(state->hndl, 1); else snd_pcm_pause(state->hndl, 0); } } state->alsa_sync_warning = false; return 0; } int spa_alsa_pause(struct state *state) { struct state *follower; if (!state->started) return 0; spa_log_debug(state->log, "%p: pause", state); state->started = false; spa_loop_locked(state->data_loop, do_state_sync, 0, NULL, 0, state); spa_list_for_each(follower, &state->followers, driver_link) spa_alsa_pause(follower); do_drop(state); state->prepared = false; return 0; } void spa_alsa_emit_node_info(struct state *state, bool full) { uint64_t old = full ? state->info.change_mask : 0; if (full) state->info.change_mask = state->info_all; if (state->info.change_mask) { struct spa_dict_item items[7]; uint32_t i, n_items = 0; char latency[64] = "", period[64] = "", nperiods[64] = "", headroom[64] = ""; items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_API, "alsa"); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_MEDIA_CLASS, state->props.media_class); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_NODE_DRIVER, "true"); if (state->have_format && !state->disable_tsched) snprintf(latency, sizeof(latency), "%lu/%d", state->buffer_frames / (2 * state->frame_scale), state->rate); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_NODE_MAX_LATENCY, latency[0] ? latency : NULL); if (state->have_format) snprintf(period, sizeof(period), "%lu", state->period_frames); else if (state->default_period_size) snprintf(period, sizeof(period), "%u", state->default_period_size); items[n_items++] = SPA_DICT_ITEM_INIT("api.alsa.period-size", period[0] ? period : NULL); if (state->have_format) snprintf(nperiods, sizeof(nperiods), "%lu", state->period_frames != 0 ? state->buffer_frames / state->period_frames : 0); else if (state->default_period_num) snprintf(nperiods, sizeof(nperiods), "%u", state->default_period_num); items[n_items++] = SPA_DICT_ITEM_INIT("api.alsa.period-num", nperiods[0] ? nperiods : NULL); if (state->have_format) snprintf(headroom, sizeof(headroom), "%u", state->headroom); else if (state->default_headroom) snprintf(headroom, sizeof(headroom), "%u", state->default_headroom); items[n_items++] = SPA_DICT_ITEM_INIT("api.alsa.headroom", headroom[0] ? headroom : NULL); state->info.props = &SPA_DICT_INIT(items, n_items); if (state->info.change_mask & SPA_NODE_CHANGE_MASK_PARAMS) { for (i = 0; i < state->info.n_params; i++) { if (state->params[i].user > 0) { state->params[i].flags ^= SPA_PARAM_INFO_SERIAL; state->params[i].user = 0; } } } spa_node_emit_info(&state->hooks, &state->info); state->info.change_mask = old; } } void spa_alsa_emit_port_info(struct state *state, bool full) { uint64_t old = full ? state->port_info.change_mask : 0; if (full) state->port_info.change_mask = state->port_info_all; if (state->port_info.change_mask) { uint32_t i; static const struct spa_dict_item info_items[] = { { SPA_KEY_PORT_GROUP, "stream.0" }, }; state->port_info.props = &SPA_DICT_INIT_ARRAY(info_items); if (state->port_info.change_mask & SPA_PORT_CHANGE_MASK_PARAMS) { for (i = 0; i < state->port_info.n_params; i++) { if (state->port_params[i].user > 0) { state->port_params[i].flags ^= SPA_PARAM_INFO_SERIAL; state->port_params[i].user = 0; } } } spa_node_emit_port_info(&state->hooks, state->stream == SND_PCM_STREAM_PLAYBACK ? SPA_DIRECTION_INPUT : SPA_DIRECTION_OUTPUT, 0, &state->port_info); state->port_info.change_mask = old; } } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/alsa-pcm.h000066400000000000000000000213171511204443500255340ustar00rootroot00000000000000/* Spa ALSA Sink */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_ALSA_UTILS_H #define SPA_ALSA_UTILS_H #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "alsa.h" #ifdef __cplusplus extern "C" { #endif #define MAX_RATES 16 #define MAX_CHANNELS SPA_AUDIO_MAX_CHANNELS #define DEFAULT_PERIOD 1024u #define DEFAULT_RATE 48000u #define DEFAULT_CHANNELS 2u /* CHMAP defaults to true when using UCM */ #define DEFAULT_USE_CHMAP false #define MAX_HTIMESTAMP_ERROR 64 struct props { char device[64]; char device_name[128]; char card_name[128]; char media_class[128]; bool use_chmap; }; #define MAX_BUFFERS 32 #define MAX_POLL 16 struct buffer { uint32_t id; #define BUFFER_FLAG_OUT (1<<0) uint32_t flags; struct spa_buffer *buf; struct spa_meta_header *h; struct spa_list link; }; #define BW_MAX 0.128 #define BW_MED 0.064 #define BW_MIN 0.016 #define BW_PERIOD (3 * SPA_NSEC_PER_SEC) struct channel_map { uint32_t n_pos; uint32_t pos[MAX_CHANNELS]; }; struct card { struct spa_list link; int ref; uint32_t index; snd_use_case_mgr_t *ucm; char *ucm_prefix; int format_ref; uint32_t rate; }; struct rt_state { struct spa_list followers; struct state *driver; struct spa_list driver_link; unsigned int sources_added:1; unsigned int following:1; }; struct bound_ctl { char name[256]; snd_ctl_elem_info_t *info; snd_ctl_elem_value_t *value; }; struct state { struct spa_handle handle; struct spa_node node; struct spa_log *log; struct spa_system *data_system; struct spa_loop *data_loop; struct spa_loop *main_loop; FILE *log_file; struct spa_ratelimit rate_limit; uint32_t card_index; struct card *card; snd_pcm_stream_t stream; snd_output_t *output; char name[64]; struct spa_hook_list hooks; struct spa_callbacks callbacks; uint64_t info_all; struct spa_node_info info; #define NODE_PropInfo 0 #define NODE_Props 1 #define NODE_IO 2 #define NODE_ProcessLatency 3 #define N_NODE_PARAMS 4 struct spa_param_info params[N_NODE_PARAMS]; struct props props; unsigned int opened:1; unsigned int prepared:1; unsigned int started:1; unsigned int want_started:1; snd_pcm_t *hndl; bool have_format; struct spa_audio_info current_format; uint32_t default_period_size; uint32_t default_period_num; uint32_t default_headroom; uint32_t default_start_delay; uint32_t default_format; unsigned int default_channels; unsigned int default_rate; uint32_t allowed_rates[MAX_RATES]; uint32_t n_allowed_rates; struct channel_map default_pos; unsigned int disable_mmap:1; unsigned int disable_batch:1; unsigned int disable_tsched:1; unsigned int is_split_parent:1; unsigned int is_firewire:1; char clock_name[64]; uint32_t quantum_limit; snd_pcm_uframes_t buffer_frames; snd_pcm_uframes_t period_frames; snd_pcm_format_t format; int rate; int channels; size_t frame_size; size_t frame_scale; int blocks; uint32_t delay; uint32_t read_size; uint32_t max_read; uint32_t duration; uint64_t port_info_all; struct spa_port_info port_info; #define PORT_EnumFormat 0 #define PORT_Meta 1 #define PORT_IO 2 #define PORT_Format 3 #define PORT_Buffers 4 #define PORT_Latency 5 #define PORT_Tag 6 #define N_PORT_PARAMS 7 struct spa_param_info port_params[N_PORT_PARAMS]; enum spa_direction port_direction; struct spa_io_buffers *io; struct spa_io_clock *clock; struct spa_io_position *position; struct spa_io_rate_match *rate_match; struct buffer buffers[MAX_BUFFERS]; unsigned int n_buffers; struct spa_list free; struct spa_list ready; size_t ready_offset; /* Either a single source for tsched, or a set of pollfds from ALSA */ struct spa_source source[MAX_POLL]; int timerfd; struct pollfd pfds[MAX_POLL]; int n_fds; uint32_t threshold; uint32_t last_threshold; snd_pcm_uframes_t period_size_min; uint32_t headroom; uint32_t start_delay; uint32_t min_delay; uint32_t max_delay; uint32_t htimestamp_error; uint32_t htimestamp_max_errors; struct spa_fraction driver_rate; uint32_t driver_duration; unsigned int alsa_started:1; unsigned int alsa_sync:1; unsigned int alsa_sync_warning:1; unsigned int following:1; unsigned int matching:1; unsigned int resample:1; unsigned int use_mmap:1; unsigned int planar:1; unsigned int freewheel:1; unsigned int open_ucm:1; unsigned int is_iec958:1; unsigned int is_hdmi:1; unsigned int multi_rate:1; unsigned int htimestamp:1; unsigned int is_pro:1; unsigned int sources_added:1; unsigned int auto_link:1; unsigned int dsd_lsb:1; unsigned int linked:1; unsigned int is_batch:1; unsigned int force_quantum:1; unsigned int use_period_size_min_as_headroom:1; uint64_t iec958_codecs; int64_t sample_count; int64_t sample_time; uint64_t next_time; uint64_t base_time; uint64_t underrun; struct spa_dll dll; double dll_bw_max; double max_error; double max_resync; double err_avg, err_var, err_wdw; struct spa_latency_info latency[2]; struct spa_process_latency_info process_latency; struct spa_pod *tag[2]; /* for rate match and bind ctls */ snd_ctl_t *ctl; /* Rate match via an ALSA ctl */ snd_ctl_elem_value_t *pitch_elem; double last_rate; /* ALSA ctls exposed as params */ unsigned int num_bind_ctls; struct bound_ctl bound_ctls[16]; struct pollfd ctl_pfds[MAX_POLL]; struct spa_source ctl_sources[MAX_POLL]; int ctl_n_fds; struct spa_list link; struct spa_list followers; struct state *driver; struct spa_list driver_link; struct rt_state rt; }; struct spa_pod *spa_alsa_enum_propinfo(struct state *state, uint32_t idx, struct spa_pod_builder *b); int spa_alsa_add_prop_params(struct state *state, struct spa_pod_builder *b); int spa_alsa_parse_prop_params(struct state *state, struct spa_pod *params); int spa_alsa_enum_format(struct state *state, int seq, uint32_t start, uint32_t num, const struct spa_pod *filter); int spa_alsa_set_format(struct state *state, struct spa_audio_info *info, uint32_t flags); int spa_alsa_update_rate_match(struct state *state); int spa_alsa_init(struct state *state, const struct spa_dict *info); int spa_alsa_clear(struct state *state); int spa_alsa_open(struct state *state, const char *params); int spa_alsa_prepare(struct state *state); int spa_alsa_start(struct state *state); int spa_alsa_reassign_follower(struct state *state); int spa_alsa_pause(struct state *state); int spa_alsa_close(struct state *state); int spa_alsa_write(struct state *state); int spa_alsa_read(struct state *state); int spa_alsa_skip(struct state *state); void spa_alsa_recycle_buffer(struct state *state, uint32_t buffer_id); void spa_alsa_emit_node_info(struct state *state, bool full); void spa_alsa_emit_port_info(struct state *state, bool full); static inline void spa_alsa_parse_position(struct channel_map *map, const char *val, size_t len) { spa_audio_parse_position_n(val, len, map->pos, SPA_N_ELEMENTS(map->pos), &map->n_pos); } static inline uint32_t spa_alsa_parse_rates(uint32_t *rates, uint32_t max, const char *val, size_t len) { return spa_json_str_array_uint32(val, len, rates, max); } static inline uint32_t spa_alsa_iec958_codec_from_name(const char *name) { return spa_type_audio_iec958_codec_from_short_name(name); } static inline void spa_alsa_parse_iec958_codecs(uint64_t *codecs, const char *val, size_t len) { struct spa_json it[1]; char v[256]; if (spa_json_begin_array_relax(&it[0], val, len) <= 0) return; *codecs = 0; while (spa_json_get_string(&it[0], v, sizeof(v)) > 0) *codecs |= 1ULL << spa_alsa_iec958_codec_from_name(v); } static inline uint32_t spa_alsa_get_iec958_codecs(struct state *state, uint32_t *codecs, uint32_t max_codecs) { uint64_t mask = state->iec958_codecs; uint32_t i = 0, j = 0; if (!(state->is_iec958 || state->is_hdmi)) return 0; while (mask && i < max_codecs) { if (mask & 1) codecs[i++] = j; mask >>= 1; j++; } return i; } /* This function is also as snd_pcm_channel_area_addr() since 1.2.6 which is not yet * in ubuntu and I can't figure out how to do the ALSA version check. */ static inline void *channel_area_addr(const snd_pcm_channel_area_t *area, snd_pcm_uframes_t offset) { return (char *)area->addr + (area->first + area->step * offset) / 8; } #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_ALSA_UTILS_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/alsa-seq-bridge.c000066400000000000000000000661211511204443500267740ustar00rootroot00000000000000/* Spa ALSA Source */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "alsa-seq.h" #define DEFAULT_DEVICE "default" #define DEFAULT_CLOCK_NAME "clock.system.monotonic" #define DEFAULT_DISABLE_LONGNAME true static void reset_props(struct props *props) { strncpy(props->device, DEFAULT_DEVICE, sizeof(props->device)); strncpy(props->clock_name, DEFAULT_CLOCK_NAME, sizeof(props->clock_name)); props->disable_longname = DEFAULT_DISABLE_LONGNAME; } static int impl_node_enum_params(void *object, int seq, uint32_t id, uint32_t start, uint32_t num, const struct spa_pod *filter) { struct seq_state *this = object; struct spa_pod *param; uint8_t buffer[1024]; struct spa_pod_builder b = { 0 }; struct props *p; struct spa_result_node_params result; uint32_t count = 0; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(num != 0, -EINVAL); p = &this->props; result.id = id; result.next = start; next: result.index = result.next++; spa_pod_builder_init(&b, buffer, sizeof(buffer)); switch (id) { case SPA_PARAM_PropInfo: switch (result.index) { case 0: param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_PropInfo, id, SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_device), SPA_PROP_INFO_description, SPA_POD_String("The ALSA device"), SPA_PROP_INFO_type, SPA_POD_Stringn(p->device, sizeof(p->device))); break; default: return 0; } break; case SPA_PARAM_Props: switch (result.index) { case 0: param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_Props, id, SPA_PROP_device, SPA_POD_Stringn(p->device, sizeof(p->device))); break; default: return 0; } break; case SPA_PARAM_IO: switch (result.index) { case 0: param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamIO, id, SPA_PARAM_IO_id, SPA_POD_Id(SPA_IO_Clock), SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_clock))); break; case 1: param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamIO, id, SPA_PARAM_IO_id, SPA_POD_Id(SPA_IO_Position), SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_position))); break; default: return 0; } break; default: return -ENOENT; } if (spa_pod_filter(&b, &result.param, param, filter) < 0) goto next; spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); if (++count != num) goto next; return 0; } static int impl_node_set_io(void *object, uint32_t id, void *data, size_t size) { struct seq_state *this = object; spa_return_val_if_fail(this != NULL, -EINVAL); switch (id) { case SPA_IO_Clock: this->clock = data; if (this->clock != NULL) spa_scnprintf(this->clock->name, sizeof(this->clock->name), "%s", this->props.clock_name); break; case SPA_IO_Position: this->position = data; break; default: return -ENOENT; } spa_alsa_seq_reassign_follower(this); return 0; } static int impl_node_set_param(void *object, uint32_t id, uint32_t flags, const struct spa_pod *param) { struct seq_state *this = object; spa_return_val_if_fail(this != NULL, -EINVAL); switch (id) { case SPA_PARAM_Props: { struct props *p = &this->props; if (param == NULL) { reset_props(p); return 0; } spa_pod_parse_object(param, SPA_TYPE_OBJECT_Props, NULL, SPA_PROP_device, SPA_POD_OPT_Stringn(p->device, sizeof(p->device))); break; } default: return -ENOENT; } return 0; } static int impl_node_send_command(void *object, const struct spa_command *command) { struct seq_state *this = object; int res; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(command != NULL, -EINVAL); switch (SPA_NODE_COMMAND_ID(command)) { case SPA_NODE_COMMAND_Start: if ((res = spa_alsa_seq_start(this)) < 0) return res; break; case SPA_NODE_COMMAND_Pause: case SPA_NODE_COMMAND_Suspend: if ((res = spa_alsa_seq_pause(this)) < 0) return res; break; default: return -ENOTSUP; } return 0; } static const struct spa_dict_item node_info_items[] = { { SPA_KEY_DEVICE_API, "alsa" }, { SPA_KEY_MEDIA_CLASS, "Midi/Bridge" }, { SPA_KEY_NODE_DRIVER, "true" }, { "priority.driver", "1" }, }; static void emit_node_info(struct seq_state *this, bool full) { uint64_t old = full ? this->info.change_mask : 0; if (full) this->info.change_mask = this->info_all; if (this->info.change_mask) { this->info.props = &SPA_DICT_INIT_ARRAY(node_info_items); spa_node_emit_info(&this->hooks, &this->info); this->info.change_mask = old; } } static inline void clean_name(char *name) { char *c; for (c = name; *c; ++c) { if (!isalnum(*c) && strchr(" /_:()[]", *c) == NULL) *c = '-'; } } static void emit_port_info(struct seq_state *this, struct seq_port *port, bool full) { uint64_t old = full ? port->info.change_mask : 0; if (full) port->info.change_mask = port->info_all; if (port->info.change_mask) { struct spa_dict_item items[6]; uint32_t n_items = 0; int card_id; snd_seq_port_info_t *info; snd_seq_client_info_t *client_info; const char *client_name, *port_name, *dir, *pn; char prefix[32] = ""; char card[8]; char name[256]; char path[128]; char alias[128]; char stream[32]; snd_seq_port_info_alloca(&info); snd_seq_get_any_port_info(this->sys.hndl, port->addr.client, port->addr.port, info); snd_seq_client_info_alloca(&client_info); snd_seq_get_any_client_info(this->sys.hndl, port->addr.client, client_info); card_id = snd_seq_client_info_get_card(client_info); client_name = snd_seq_client_info_get_name(client_info); port_name = snd_seq_port_info_get_name(info); dir = port->direction == SPA_DIRECTION_OUTPUT ? "capture" : "playback"; if (!this->props.disable_longname) snprintf(prefix, sizeof(prefix), "[%d:%d] ", port->addr.client, port->addr.port); pn = port_name; if (spa_strstartswith(pn, client_name)) pn += strlen(client_name); snprintf(name, sizeof(name), "%s%s%s (%s)", prefix, client_name, pn, dir); clean_name(name); snprintf(stream, sizeof(stream), "client_%d", port->addr.client); clean_name(stream); snprintf(path, sizeof(path), "alsa:seq:%s:%s:%s_%d", this->props.device, stream, dir, port->addr.port); clean_name(path); snprintf(alias, sizeof(alias), "%s:%s", client_name, port_name); clean_name(alias); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_FORMAT_DSP, "8 bit raw midi"); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_OBJECT_PATH, path); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_PORT_NAME, name); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_PORT_ALIAS, alias); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_PORT_GROUP, stream); if (card_id != -1) { snprintf(card, sizeof(card), "%d", card_id); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_API_ALSA_CARD, card); } port->info.props = &SPA_DICT_INIT(items, n_items); spa_node_emit_port_info(&this->hooks, port->direction, port->id, &port->info); port->info.change_mask = old; } } static void emit_stream_info(struct seq_state *this, struct seq_stream *stream, bool full) { struct seq_port *port; spa_list_for_each(port, &stream->port_list, link) emit_port_info(this, port, full); } static int impl_node_add_listener(void *object, struct spa_hook *listener, const struct spa_node_events *events, void *data) { struct seq_state *this = object; struct spa_hook_list save; spa_return_val_if_fail(this != NULL, -EINVAL); spa_hook_list_isolate(&this->hooks, &save, listener, events, data); emit_node_info(this, true); emit_stream_info(this, &this->streams[SPA_DIRECTION_INPUT], true); emit_stream_info(this, &this->streams[SPA_DIRECTION_OUTPUT], true); spa_hook_list_join(&this->hooks, &save); return 0; } static int impl_node_set_callbacks(void *object, const struct spa_node_callbacks *callbacks, void *data) { struct seq_state *this = object; spa_return_val_if_fail(this != NULL, -EINVAL); this->callbacks = SPA_CALLBACKS_INIT(callbacks, data); return 0; } static int impl_node_sync(void *object, int seq) { struct seq_state *this = object; spa_return_val_if_fail(this != NULL, -EINVAL); spa_node_emit_result(&this->hooks, seq, 0, 0, NULL); return 0; } static struct seq_port *find_port(struct seq_state *state, struct seq_stream *stream, const snd_seq_addr_t *addr) { struct seq_port *port; spa_list_for_each(port, &stream->port_list, link) { if (port->addr.client == addr->client && port->addr.port == addr->port) return port; } return NULL; } static struct seq_port *alloc_port(struct seq_state *state, struct seq_stream *stream) { struct seq_port *port; uint32_t i; for (i = 0; i < MAX_PORTS; i++) { if (stream->ports[i] == NULL) break; } if (i == MAX_PORTS) return NULL; if (!spa_list_is_empty(&state->free_list)) { port = spa_list_first(&state->free_list, struct seq_port, link); spa_list_remove(&port->link); } else { port = calloc(1, sizeof(struct seq_port)); if (port == NULL) return NULL; } port->id = i; port->direction = stream->direction; stream->ports[i] = port; spa_list_append(&stream->port_list, &port->link); return port; } static void free_port(struct seq_state *state, struct seq_stream *stream, struct seq_port *port) { stream->ports[port->id] = NULL; spa_list_remove(&port->link); spa_node_emit_port_info(&state->hooks, port->direction, port->id, NULL); spa_zero(*port); spa_list_append(&state->free_list, &port->link); } static void init_port(struct seq_state *state, struct seq_port *port, const snd_seq_addr_t *addr, unsigned int type) { enum spa_direction reverse = SPA_DIRECTION_REVERSE(port->direction); port->addr = *addr; port->info_all = SPA_PORT_CHANGE_MASK_FLAGS | SPA_PORT_CHANGE_MASK_PROPS | SPA_PORT_CHANGE_MASK_PARAMS; port->info = SPA_PORT_INFO_INIT(); port->info.flags = SPA_PORT_FLAG_LIVE; if (type & (SND_SEQ_PORT_TYPE_HARDWARE|SND_SEQ_PORT_TYPE_PORT|SND_SEQ_PORT_TYPE_SPECIFIC)) port->info.flags |= SPA_PORT_FLAG_PHYSICAL | SPA_PORT_FLAG_TERMINAL; port->params[PORT_EnumFormat] = SPA_PARAM_INFO(SPA_PARAM_EnumFormat, SPA_PARAM_INFO_READ); port->params[PORT_Meta] = SPA_PARAM_INFO(SPA_PARAM_Meta, SPA_PARAM_INFO_READ); port->params[PORT_IO] = SPA_PARAM_INFO(SPA_PARAM_IO, SPA_PARAM_INFO_READ); port->params[PORT_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); port->params[PORT_Buffers] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); port->params[PORT_Latency] = SPA_PARAM_INFO(SPA_PARAM_Latency, SPA_PARAM_INFO_READWRITE); port->info.params = port->params; port->info.n_params = N_PORT_PARAMS; spa_list_init(&port->free); spa_list_init(&port->ready); port->latency[port->direction] = SPA_LATENCY_INFO( port->direction, .min_quantum = 1.0f, .max_quantum = 1.0f); port->latency[reverse] = SPA_LATENCY_INFO(reverse); spa_alsa_seq_activate_port(state, port, true); emit_port_info(state, port, true); } static void update_stream_port(struct seq_state *state, struct seq_stream *stream, const snd_seq_addr_t *addr, unsigned int caps, const snd_seq_port_info_t *info) { struct seq_port *port = find_port(state, stream, addr); if (info == NULL) { spa_log_debug(state->log, "free port %d.%d", addr->client, addr->port); if (port) free_port(state, stream, port); } else { if (port == NULL && (caps & stream->caps) == stream->caps) { spa_log_debug(state->log, "new port %d.%d", addr->client, addr->port); port = alloc_port(state, stream); if (port == NULL) return; init_port(state, port, addr, snd_seq_port_info_get_type(info)); } else if (port != NULL) { if ((caps & stream->caps) != stream->caps) { spa_log_debug(state->log, "free port %d.%d", addr->client, addr->port); free_port(state, stream, port); } else { spa_log_debug(state->log, "update port %d.%d", addr->client, addr->port); port->info.change_mask = SPA_PORT_CHANGE_MASK_PROPS; emit_port_info(state, port, false); } } } } static int on_port_info(void *data, const snd_seq_addr_t *addr, const snd_seq_port_info_t *info) { struct seq_state *state = data; if (info == NULL) { update_stream_port(state, &state->streams[SPA_DIRECTION_INPUT], addr, 0, info); update_stream_port(state, &state->streams[SPA_DIRECTION_OUTPUT], addr, 0, info); } else { unsigned int caps = snd_seq_port_info_get_capability(info); if (caps & SND_SEQ_PORT_CAP_NO_EXPORT) return 0; update_stream_port(state, &state->streams[SPA_DIRECTION_INPUT], addr, caps, info); update_stream_port(state, &state->streams[SPA_DIRECTION_OUTPUT], addr, caps, info); } return 0; } static int impl_node_add_port(void *object, enum spa_direction direction, uint32_t port_id, const struct spa_dict *props) { return -ENOTSUP; } static int impl_node_remove_port(void *object, enum spa_direction direction, uint32_t port_id) { return -ENOTSUP; } static int impl_node_port_enum_params(void *object, int seq, enum spa_direction direction, uint32_t port_id, uint32_t id, uint32_t start, uint32_t num, const struct spa_pod *filter) { struct seq_state *this = object; struct seq_port *port; struct spa_pod *param; struct spa_pod_builder b = { 0 }; uint8_t buffer[1024]; struct spa_result_node_params result; uint32_t count = 0; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(num != 0, -EINVAL); spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); port = GET_PORT(this, direction, port_id); result.id = id; result.next = start; next: result.index = result.next++; spa_pod_builder_init(&b, buffer, sizeof(buffer)); switch (id) { case SPA_PARAM_EnumFormat: if (result.index > 0) return 0; param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_application), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_control)); break; case SPA_PARAM_Format: if (!port->have_format) return -EIO; if (result.index > 0) return 0; param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_Format, SPA_PARAM_Format, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_application), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_control)); break; case SPA_PARAM_Buffers: if (!port->have_format) return -EIO; if (result.index > 0) return 0; param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamBuffers, id, SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(2, 1, MAX_BUFFERS), SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(1), SPA_PARAM_BUFFERS_size, SPA_POD_CHOICE_RANGE_Int( this->quantum_limit, this->quantum_limit, INT32_MAX), SPA_PARAM_BUFFERS_stride, SPA_POD_Int(1)); break; case SPA_PARAM_Meta: switch (result.index) { case 0: param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamMeta, id, SPA_PARAM_META_type, SPA_POD_Id(SPA_META_Header), SPA_PARAM_META_size, SPA_POD_Int(sizeof(struct spa_meta_header))); break; default: return 0; } break; case SPA_PARAM_IO: switch (result.index) { case 0: param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamIO, id, SPA_PARAM_IO_id, SPA_POD_Id(SPA_IO_Buffers), SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_buffers))); break; default: return 0; } break; case SPA_PARAM_Latency: switch (result.index) { case 0: case 1: param = spa_latency_build(&b, id, &port->latency[result.index]); break; default: return 0; } break; default: return -ENOENT; } if (spa_pod_filter(&b, &result.param, param, filter) < 0) goto next; spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); if (++count != num) goto next; return 0; } static int clear_buffers(struct seq_state *this, struct seq_port *port) { if (port->n_buffers > 0) { spa_list_init(&port->free); spa_list_init(&port->ready); port->n_buffers = 0; } return 0; } static int port_set_format(void *object, struct seq_port *port, uint32_t flags, const struct spa_pod *format) { struct seq_state *this = object; int err; if (format == NULL) { if (!port->have_format) return 0; clear_buffers(this, port); port->have_format = false; } else { struct spa_audio_info info = { 0 }; if ((err = spa_format_parse(format, &info.media_type, &info.media_subtype)) < 0) return err; if (info.media_type != SPA_MEDIA_TYPE_application || info.media_subtype != SPA_MEDIA_SUBTYPE_control) return -EINVAL; port->current_format = info; port->have_format = true; } port->info.change_mask |= SPA_PORT_CHANGE_MASK_RATE; port->info.rate = SPA_FRACTION(1, 1); port->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; if (port->have_format) { port->params[PORT_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_READWRITE); port->params[PORT_Buffers] = SPA_PARAM_INFO(SPA_PARAM_Buffers, SPA_PARAM_INFO_READ); } else { port->params[PORT_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); port->params[PORT_Buffers] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); } emit_port_info(this, port, false); return 0; } static int impl_node_port_set_param(void *object, enum spa_direction direction, uint32_t port_id, uint32_t id, uint32_t flags, const struct spa_pod *param) { struct seq_state *this = object; struct seq_port *port; int res = 0; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); port = GET_PORT(this, direction, port_id); switch (id) { case SPA_PARAM_Format: res = port_set_format(this, port, flags, param); break; case SPA_PARAM_Latency: { struct spa_latency_info info; if (param == NULL) info = SPA_LATENCY_INFO(SPA_DIRECTION_REVERSE(direction)); else if ((res = spa_latency_parse(param, &info)) < 0) return res; if (direction == info.direction) return -EINVAL; port->latency[info.direction] = info; port->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; port->params[PORT_Latency].flags ^= SPA_PARAM_INFO_SERIAL; emit_port_info(this, port, false); break; } default: res = -ENOENT; break; } return res; } static int impl_node_port_use_buffers(void *object, enum spa_direction direction, uint32_t port_id, uint32_t flags, struct spa_buffer **buffers, uint32_t n_buffers) { struct seq_state *this = object; struct seq_port *port; uint32_t i; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); port = GET_PORT(this, direction, port_id); spa_log_debug(this->log, "%p: port %d.%d buffers:%d format:%d", this, direction, port_id, n_buffers, port->have_format); clear_buffers(this, port); if (n_buffers > 0 && !port->have_format) return -EIO; if (n_buffers > MAX_BUFFERS) return -ENOSPC; for (i = 0; i < n_buffers; i++) { struct buffer *b = &port->buffers[i]; struct spa_data *d = buffers[i]->datas; b->buf = buffers[i]; b->id = i; b->flags = BUFFER_FLAG_OUT; b->h = spa_buffer_find_meta_data(b->buf, SPA_META_Header, sizeof(*b->h)); if (d[0].data == NULL) { spa_log_error(this->log, "%p: need mapped memory", this); return -EINVAL; } if (direction == SPA_DIRECTION_OUTPUT) spa_alsa_seq_recycle_buffer(this, port, i); } port->n_buffers = n_buffers; return 0; } struct io_info { struct seq_state *state; struct seq_port *port; void *data; size_t size; }; static int do_port_set_io(struct spa_loop *loop, bool async, uint32_t seq, const void *data, size_t size, void *user_data) { struct io_info *info = user_data; struct seq_port *port = info->port; struct seq_stream *stream = &info->state->streams[port->direction]; if (info->data == NULL || info->size < sizeof(struct spa_io_buffers)) { port->io = NULL; if (port->mixing) { spa_list_remove(&port->mix_link); port->mixing = false; } } else { port->io = info->data; if (!port->mixing) { spa_list_append(&stream->mix_list, &port->mix_link); port->mixing = true; } } return 0; } static int impl_node_port_set_io(void *object, enum spa_direction direction, uint32_t port_id, uint32_t id, void *data, size_t size) { struct seq_state *this = object; struct seq_port *port; struct io_info info; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); port = GET_PORT(this, direction, port_id); info.state = this; info.port = port; info.data = data; info.size = size; spa_log_debug(this->log, "%p: io %d.%d %d %p %zd", this, direction, port_id, id, data, size); switch (id) { case SPA_IO_Buffers: spa_loop_locked(this->data_loop, do_port_set_io, SPA_ID_INVALID, NULL, 0, &info); break; default: return -ENOENT; } return 0; } static int impl_node_port_reuse_buffer(void *object, uint32_t port_id, uint32_t buffer_id) { struct seq_state *this = object; struct seq_port *port; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(!CHECK_PORT(this, SPA_DIRECTION_OUTPUT, port_id), -EINVAL); port = GET_PORT(this, SPA_DIRECTION_OUTPUT, port_id); if (port->n_buffers == 0) return -EIO; if (buffer_id >= port->n_buffers) return -EINVAL; spa_alsa_seq_recycle_buffer(this, port, buffer_id); return 0; } static int impl_node_process(void *object) { struct seq_state *this = object; spa_return_val_if_fail(this != NULL, -EINVAL); return spa_alsa_seq_process(this); } static const struct spa_node_methods impl_node = { SPA_VERSION_NODE_METHODS, .add_listener = impl_node_add_listener, .set_callbacks = impl_node_set_callbacks, .sync = impl_node_sync, .enum_params = impl_node_enum_params, .set_param = impl_node_set_param, .set_io = impl_node_set_io, .send_command = impl_node_send_command, .add_port = impl_node_add_port, .remove_port = impl_node_remove_port, .port_enum_params = impl_node_port_enum_params, .port_set_param = impl_node_port_set_param, .port_use_buffers = impl_node_port_use_buffers, .port_set_io = impl_node_port_set_io, .port_reuse_buffer = impl_node_port_reuse_buffer, .process = impl_node_process, }; static int impl_get_interface(struct spa_handle *handle, const char *type, void **interface) { struct seq_state *this; spa_return_val_if_fail(handle != NULL, -EINVAL); spa_return_val_if_fail(interface != NULL, -EINVAL); this = (struct seq_state *) handle; if (spa_streq(type, SPA_TYPE_INTERFACE_Node)) *interface = &this->node; else return -ENOENT; return 0; } static int impl_clear(struct spa_handle *handle) { struct seq_state *this; spa_return_val_if_fail(handle != NULL, -EINVAL); this = (struct seq_state *) handle; spa_alsa_seq_close(this); return 0; } static size_t impl_get_size(const struct spa_handle_factory *factory, const struct spa_dict *params) { return sizeof(struct seq_state); } static int impl_init(const struct spa_handle_factory *factory, struct spa_handle *handle, const struct spa_dict *info, const struct spa_support *support, uint32_t n_support) { struct seq_state *this; uint32_t i; int res; spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(handle != NULL, -EINVAL); handle->get_interface = impl_get_interface; handle->clear = impl_clear; this = (struct seq_state *) handle; this->log = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log); alsa_log_topic_init(this->log); this->data_system = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_DataSystem); this->data_loop = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_DataLoop); this->main_loop = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Loop); if (this->data_loop == NULL) { spa_log_error(this->log, "a data loop is needed"); return -EINVAL; } if (this->data_system == NULL) { spa_log_error(this->log, "a data system is needed"); return -EINVAL; } this->node.iface = SPA_INTERFACE_INIT(SPA_TYPE_INTERFACE_Node, SPA_VERSION_NODE, &impl_node, this); spa_hook_list_init(&this->hooks); this->info_all = SPA_NODE_CHANGE_MASK_FLAGS | SPA_NODE_CHANGE_MASK_PROPS | SPA_NODE_CHANGE_MASK_PARAMS; this->info.max_input_ports = MAX_PORTS; this->info.max_output_ports = MAX_PORTS; this->info.flags = SPA_NODE_FLAG_RT; this->params[NODE_PropInfo] = SPA_PARAM_INFO(SPA_PARAM_PropInfo, SPA_PARAM_INFO_READ); this->params[NODE_Props] = SPA_PARAM_INFO(SPA_PARAM_Props, SPA_PARAM_INFO_READWRITE); this->params[NODE_IO] = SPA_PARAM_INFO(SPA_PARAM_IO, SPA_PARAM_INFO_READ); this->info.params = this->params; this->info.n_params = N_NODE_PARAMS; reset_props(&this->props); this->quantum_limit = 8192; this->min_pool_size = 500; this->max_pool_size = 2000; this->ump = true; for (i = 0; info && i < info->n_items; i++) { const char *k = info->items[i].key; const char *s = info->items[i].value; if (spa_streq(k, SPA_KEY_API_ALSA_PATH)) { spa_scnprintf(this->props.device, sizeof(this->props.device), "%s", s); } else if (spa_streq(k, "clock.name")) { spa_scnprintf(this->props.clock_name, sizeof(this->props.clock_name), "%s", s); } else if (spa_streq(k, "clock.quantum-limit")) { spa_atou32(s, &this->quantum_limit, 0); } else if (spa_streq(k, SPA_KEY_API_ALSA_DISABLE_LONGNAME)) { this->props.disable_longname = spa_atob(s); } else if (spa_streq(k, "api.alsa.seq.min-pool")) { spa_atou32(s, &this->min_pool_size, 0); } else if (spa_streq(k, "api.alsa.seq.max-pool")) { spa_atou32(s, &this->max_pool_size, 0); } else if (spa_streq(k, "api.alsa.seq.ump")) { this->ump = spa_atob(s); } } this->port_info = on_port_info; this->port_info_data = this; if ((res = spa_alsa_seq_open(this)) < 0) return res; return 0; } static const struct spa_interface_info impl_interfaces[] = { {SPA_TYPE_INTERFACE_Node,}, }; static int impl_enum_interface_info(const struct spa_handle_factory *factory, const struct spa_interface_info **info, uint32_t *index) { spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(info != NULL, -EINVAL); spa_return_val_if_fail(index != NULL, -EINVAL); if (*index >= SPA_N_ELEMENTS(impl_interfaces)) return 0; *info = &impl_interfaces[(*index)++]; return 1; } static const struct spa_dict_item info_items[] = { { SPA_KEY_FACTORY_AUTHOR, "Wim Taymans " }, { SPA_KEY_FACTORY_DESCRIPTION, "Bridge midi ports with the alsa sequencer API" }, { SPA_KEY_FACTORY_USAGE, "["SPA_KEY_API_ALSA_PATH"=] " "[ clock.name=] " "[ clock.quantum-limit=] " "["SPA_KEY_API_ALSA_DISABLE_LONGNAME"=] " "[ api.alsa.seq.min-pool=] " "[ api.alsa.seq.max-pool=]" "[ api.alsa.seq.ump = ]" }, }; static const struct spa_dict info = SPA_DICT_INIT_ARRAY(info_items); const struct spa_handle_factory spa_alsa_seq_bridge_factory = { SPA_VERSION_HANDLE_FACTORY, SPA_NAME_API_ALSA_SEQ_BRIDGE, &info, impl_get_size, impl_init, impl_enum_interface_info, }; pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/alsa-seq.c000066400000000000000000001035121511204443500255360ustar00rootroot00000000000000/* Spa ALSA Sequencer */ /* SPDX-FileCopyrightText: Copyright © 2019 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "alsa.h" #include "alsa-seq.h" #define CHECK(s,msg,...) if ((res = (s)) < 0) { spa_log_error(state->log, msg ": %s", ##__VA_ARGS__, snd_strerror(res)); return res; } static int seq_open(struct seq_state *state, struct seq_conn *conn, bool with_queue, bool probe_ump) { struct props *props = &state->props; int res; spa_log_debug(state->log, "%p: ALSA seq open '%s' duplex", state, props->device); if ((res = snd_seq_open(&conn->hndl, props->device, SND_SEQ_OPEN_DUPLEX, 0)) < 0) return res; if (!state->ump) { spa_log_info(state->log, "%p: ALSA UMP MIDI disabled", state); return 0; } #ifdef HAVE_ALSA_UMP res = snd_seq_set_client_midi_version(conn->hndl, SND_SEQ_CLIENT_UMP_MIDI_2_0); if (!res) { snd_seq_client_info_t *info = NULL; /* Double check client version */ res = snd_seq_client_info_malloc(&info); if (!res) res = snd_seq_get_client_info(conn->hndl, info); if (!res) { res = snd_seq_client_info_get_midi_version(info); if (res == SND_SEQ_CLIENT_UMP_MIDI_2_0) res = 0; else res = -EIO; } if (info) snd_seq_client_info_free(info); } #else res = -EOPNOTSUPP; #endif if (res < 0) { spa_log_lev(state->log, (probe_ump ? SPA_LOG_LEVEL_INFO : SPA_LOG_LEVEL_ERROR), "%p: ALSA failed to enable UMP MIDI: %s", state, snd_strerror(res)); if (!probe_ump) { snd_seq_close(conn->hndl); return res; /* either all are UMP or none are UMP */ } state->ump = false; } else { spa_log_debug(state->log, "%p: ALSA UMP MIDI enabled", state); state->ump = true; } return 0; } static int seq_init(struct seq_state *state, struct seq_conn *conn, bool with_queue) { struct pollfd pfd; snd_seq_port_info_t *pinfo; int res; /* client id */ if ((res = snd_seq_client_id(conn->hndl)) < 0) { spa_log_error(state->log, "failed to get client id: %d", res); goto error_exit_close; } conn->addr.client = res; /* queue */ if (with_queue) { if ((res = snd_seq_alloc_queue(conn->hndl)) < 0) { spa_log_error(state->log, "failed to create queue: %d", res); goto error_exit_close; } conn->queue_id = res; } else { conn->queue_id = -1; } if ((res = snd_seq_nonblock(conn->hndl, 1)) < 0) spa_log_warn(state->log, "can't set nonblock mode: %s", snd_strerror(res)); /* port for receiving */ snd_seq_port_info_alloca(&pinfo); snd_seq_port_info_set_name(pinfo, "input"); snd_seq_port_info_set_type(pinfo, SND_SEQ_PORT_TYPE_MIDI_GENERIC); snd_seq_port_info_set_capability(pinfo, SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_READ); /* Enable timestamping for events sent by external subscribers. */ snd_seq_port_info_set_timestamping(pinfo, 1); snd_seq_port_info_set_timestamp_real(pinfo, 1); if (with_queue) snd_seq_port_info_set_timestamp_queue(pinfo, conn->queue_id); if ((res = snd_seq_create_port(conn->hndl, pinfo)) < 0) { spa_log_error(state->log, "failed to create port: %s", snd_strerror(res)); goto error_exit_close; } conn->addr.port = snd_seq_port_info_get_port(pinfo); spa_log_debug(state->log, "queue:%d client:%d port:%d", conn->queue_id, conn->addr.client, conn->addr.port); snd_seq_poll_descriptors(conn->hndl, &pfd, 1, POLLIN); conn->source.fd = pfd.fd; conn->source.mask = SPA_IO_IN; return 0; error_exit_close: snd_seq_close(conn->hndl); return res; } static int seq_close(struct seq_state *state, struct seq_conn *conn) { int res; spa_log_debug(state->log, "%p: Device '%s' closing", state, state->props.device); if ((res = snd_seq_close(conn->hndl)) < 0) { spa_log_warn(state->log, "close failed: %s", snd_strerror(res)); } return res; } static int init_stream(struct seq_state *state, enum spa_direction direction) { struct seq_stream *stream = &state->streams[direction]; int res; stream->direction = direction; if (direction == SPA_DIRECTION_INPUT) { stream->caps = SND_SEQ_PORT_CAP_SUBS_WRITE; } else { stream->caps = SND_SEQ_PORT_CAP_SUBS_READ; } if ((res = snd_midi_event_new(MAX_EVENT_SIZE, &stream->codec)) < 0) { spa_log_error(state->log, "can make event decoder: %s", snd_strerror(res)); return res; } snd_midi_event_no_status(stream->codec, 1); spa_list_init(&stream->port_list); spa_list_init(&stream->mix_list); return 0; } static int uninit_stream(struct seq_state *state, enum spa_direction direction) { struct seq_stream *stream = &state->streams[direction]; spa_list_insert_list(&state->free_list, &stream->port_list); if (stream->codec) snd_midi_event_free(stream->codec); stream->codec = NULL; return 0; } static void init_ports(struct seq_state *state) { snd_seq_addr_t addr; snd_seq_client_info_t *client_info; snd_seq_port_info_t *port_info; snd_seq_client_info_alloca(&client_info); snd_seq_port_info_alloca(&port_info); snd_seq_client_info_set_client(client_info, -1); while (snd_seq_query_next_client(state->sys.hndl, client_info) >= 0) { addr.client = snd_seq_client_info_get_client(client_info); if (addr.client == SND_SEQ_CLIENT_SYSTEM || addr.client == state->sys.addr.client || addr.client == state->event.addr.client) continue; snd_seq_port_info_set_client(port_info, addr.client); snd_seq_port_info_set_port(port_info, -1); while (snd_seq_query_next_port(state->sys.hndl, port_info) >= 0) { addr.port = snd_seq_port_info_get_port(port_info); state->port_info(state->port_info_data, &addr, port_info); } } } static void debug_event(struct seq_state *state, const char *prefix, snd_seq_event_t *ev) { enum spa_log_level lev = SPA_LOG_LEVEL_TRACE; if (SPA_LIKELY(!spa_log_level_topic_enabled(state->log, SPA_LOG_TOPIC_DEFAULT, lev))) return; spa_log_lev(state->log, lev, "%s: event type:%d flags:0x%x", prefix, ev->type, ev->flags); switch (ev->flags & SND_SEQ_TIME_STAMP_MASK) { case SND_SEQ_TIME_STAMP_TICK: spa_log_lev(state->log, lev, "%s: time: %d ticks", prefix, ev->time.tick); break; case SND_SEQ_TIME_STAMP_REAL: spa_log_lev(state->log, lev, "%s: time = %d.%09d", prefix, (int)ev->time.time.tv_sec, (int)ev->time.time.tv_nsec); break; } spa_log_lev(state->log, lev, "%s: source:%d.%d dest:%d.%d queue:%d", prefix, ev->source.client, ev->source.port, ev->dest.client, ev->dest.port, ev->queue); } #ifdef HAVE_ALSA_UMP static void debug_ump_event(struct seq_state *state, const char *prefix, snd_seq_ump_event_t *ev) { enum spa_log_level lev = SPA_LOG_LEVEL_TRACE; if (SPA_LIKELY(!spa_log_level_topic_enabled(state->log, SPA_LOG_TOPIC_DEFAULT, lev))) return; spa_log_lev(state->log, lev, "%s: event type:%d flags:0x%x", prefix, ev->type, ev->flags); switch (ev->flags & SND_SEQ_TIME_STAMP_MASK) { case SND_SEQ_TIME_STAMP_TICK: spa_log_lev(state->log, lev, "%s: time: %d ticks", prefix, ev->time.tick); break; case SND_SEQ_TIME_STAMP_REAL: spa_log_lev(state->log, lev, "%s: time = %d.%09d", prefix, (int)ev->time.time.tv_sec, (int)ev->time.time.tv_nsec); break; } spa_log_lev(state->log, lev, "%s: source:%d.%d dest:%d.%d queue:%d %08x", prefix, ev->source.client, ev->source.port, ev->dest.client, ev->dest.port, ev->queue, ev->ump[0]); } #endif static void alsa_seq_on_sys(struct spa_source *source) { struct seq_state *state = source->data; const bool ump = state->ump; int res; while (1) { const snd_seq_addr_t *addr; snd_seq_event_type_t type; if (ump) { #ifdef HAVE_ALSA_UMP snd_seq_ump_event_t *ev; res = snd_seq_ump_event_input(state->sys.hndl, &ev); if (res <= 0) break; debug_ump_event(state, "sys", ev); addr = &ev->data.addr; type = ev->type; #else spa_assert_not_reached(); #endif } else { snd_seq_event_t *ev; res = snd_seq_event_input(state->sys.hndl, &ev); if (res <= 0) break; debug_event(state, "sys", ev); addr = &ev->data.addr; type = ev->type; } if (addr->client == state->event.addr.client) continue; switch (type) { case SND_SEQ_EVENT_CLIENT_START: case SND_SEQ_EVENT_CLIENT_CHANGE: spa_log_info(state->log, "client add/change %d", addr->client); break; case SND_SEQ_EVENT_CLIENT_EXIT: spa_log_info(state->log, "client exit %d", addr->client); break; case SND_SEQ_EVENT_PORT_START: case SND_SEQ_EVENT_PORT_CHANGE: { snd_seq_port_info_t *info; snd_seq_port_info_alloca(&info); if ((res = snd_seq_get_any_port_info(state->sys.hndl, addr->client, addr->port, info)) < 0) { spa_log_warn(state->log, "can't get port info %d.%d: %s", addr->client, addr->port, snd_strerror(res)); } else { spa_log_info(state->log, "port add/change %d:%d", addr->client, addr->port); state->port_info(state->port_info_data, addr, info); } break; } case SND_SEQ_EVENT_PORT_EXIT: spa_log_info(state->log, "port_event: del %d:%d", addr->client, addr->port); state->port_info(state->port_info_data, addr, NULL); break; default: spa_log_debug(state->log, "unhandled event %d: %d:%d", type, addr->client, addr->port); break; } } } int spa_alsa_seq_open(struct seq_state *state) { int n, i, res; snd_seq_port_subscribe_t *sub; snd_seq_addr_t addr; snd_seq_queue_timer_t *timer; snd_seq_client_pool_t *pool; struct seq_conn reserve[16]; size_t pool_size; if (state->opened) return 0; spa_list_init(&state->free_list); init_stream(state, SPA_DIRECTION_INPUT); init_stream(state, SPA_DIRECTION_OUTPUT); spa_zero(reserve); for (i = 0; i < 16; i++) { spa_log_debug(state->log, "open %d", i); if ((res = seq_open(state, &reserve[i], false, (i == 0))) < 0) break; } if (i >= 2) { state->event = reserve[--i]; state->sys = reserve[--i]; res = 0; } for (n = --i; n >= 0; n--) { spa_log_debug(state->log, "close %d", n); seq_close(state, &reserve[n]); } if (res < 0) { spa_log_error(state->log, "open failed: %s", snd_strerror(res)); return res; } if ((res = seq_init(state, &state->sys, false)) < 0) goto error_close; snd_seq_set_client_name(state->sys.hndl, "PipeWire-System"); if ((res = seq_init(state, &state->event, true)) < 0) goto error_close; snd_seq_set_client_name(state->event.hndl, "PipeWire-RT-Event"); /* connect to system announce */ snd_seq_port_subscribe_alloca(&sub); addr.client = SND_SEQ_CLIENT_SYSTEM; addr.port = SND_SEQ_PORT_SYSTEM_ANNOUNCE; snd_seq_port_subscribe_set_sender(sub, &addr); snd_seq_port_subscribe_set_dest(sub, &state->sys.addr); if ((res = snd_seq_subscribe_port(state->sys.hndl, sub)) < 0) { spa_log_warn(state->log, "failed to connect announce port: %s", snd_strerror(res)); } addr.client = SND_SEQ_CLIENT_SYSTEM; addr.port = SND_SEQ_PORT_SYSTEM_TIMER; snd_seq_port_subscribe_set_sender(sub, &addr); if ((res = snd_seq_subscribe_port(state->sys.hndl, sub)) < 0) { spa_log_warn(state->log, "failed to connect timer port: %s", snd_strerror(res)); } state->sys.source.func = alsa_seq_on_sys; state->sys.source.data = state; /* increase event queue timer resolution */ snd_seq_queue_timer_alloca(&timer); if ((res = snd_seq_get_queue_timer(state->event.hndl, state->event.queue_id, timer)) < 0) { spa_log_warn(state->log, "failed to get queue timer: %s", snd_strerror(res)); } snd_seq_queue_timer_set_resolution(timer, INT_MAX); if ((res = snd_seq_set_queue_timer(state->event.hndl, state->event.queue_id, timer)) < 0) { spa_log_warn(state->log, "failed to set queue timer: %s", snd_strerror(res)); } /* Increase client pool sizes. This determines the max sysex message that * can be received. */ snd_seq_client_pool_alloca(&pool); if ((res = snd_seq_get_client_pool(state->event.hndl, pool)) < 0) { spa_log_warn(state->log, "failed to get pool: %s", snd_strerror(res)); } else { /* make sure we at least use the default size */ pool_size = snd_seq_client_pool_get_output_pool(pool); pool_size = SPA_MAX(pool_size, snd_seq_client_pool_get_input_pool(pool)); /* The pool size is in cells, which are about 24 bytes long. Try to * make sure we can fit sysex of at least twice the quantum limit. */ pool_size = SPA_MAX(pool_size, state->quantum_limit * 2 / 24); /* The kernel ignores values larger than 2000 (by default) so clamp * this here. It's configurable in case the kernel was modified. */ pool_size = SPA_CLAMP(pool_size, state->min_pool_size, state->max_pool_size); snd_seq_client_pool_set_input_pool(pool, pool_size); snd_seq_client_pool_set_output_pool(pool, pool_size); if ((res = snd_seq_set_client_pool(state->event.hndl, pool)) < 0) { spa_log_warn(state->log, "failed to set pool: %s", snd_strerror(res)); } } init_ports(state); if ((res = spa_system_timerfd_create(state->data_system, CLOCK_MONOTONIC, SPA_FD_CLOEXEC | SPA_FD_NONBLOCK)) < 0) goto error_close; state->timerfd = res; spa_loop_add_source(state->main_loop, &state->sys.source); state->opened = true; return 0; error_close: seq_close(state, &state->event); seq_close(state, &state->sys); return res; } int spa_alsa_seq_close(struct seq_state *state) { int res = 0; struct seq_port *port; if (!state->opened) return 0; spa_loop_remove_source(state->main_loop, &state->sys.source); seq_close(state, &state->sys); seq_close(state, &state->event); uninit_stream(state, SPA_DIRECTION_INPUT); uninit_stream(state, SPA_DIRECTION_OUTPUT); spa_list_consume(port, &state->free_list, link) { spa_list_remove(&port->link); free(port); } spa_system_close(state->data_system, state->timerfd); state->opened = false; return res; } static int set_timeout(struct seq_state *state, uint64_t time) { struct itimerspec ts; ts.it_value.tv_sec = time / SPA_NSEC_PER_SEC; ts.it_value.tv_nsec = time % SPA_NSEC_PER_SEC; ts.it_interval.tv_sec = 0; ts.it_interval.tv_nsec = 0; spa_system_timerfd_settime(state->data_system, state->timerfd, SPA_FD_TIMER_ABSTIME, &ts, NULL); return 0; } static struct seq_port *find_port(struct seq_state *state, struct seq_stream *stream, const snd_seq_addr_t *addr) { struct seq_port *port; spa_list_for_each(port, &stream->mix_list, mix_link) { if (port->addr.client == addr->client && port->addr.port == addr->port) return port; } return NULL; } int spa_alsa_seq_activate_port(struct seq_state *state, struct seq_port *port, bool active) { int res; snd_seq_port_subscribe_t* sub; spa_log_debug(state->log, "activate: %d.%d: started:%d active:%d wanted:%d", port->addr.client, port->addr.port, state->started, port->active, active); if (active && !state->started) return 0; if (port->active == active) return 0; snd_seq_port_subscribe_alloca(&sub); if (port->direction == SPA_DIRECTION_OUTPUT) { snd_seq_port_subscribe_set_sender(sub, &port->addr); snd_seq_port_subscribe_set_dest(sub, &state->event.addr); } else { snd_seq_port_subscribe_set_sender(sub, &state->event.addr); snd_seq_port_subscribe_set_dest(sub, &port->addr); } if (active) { snd_seq_port_subscribe_set_time_update(sub, 1); snd_seq_port_subscribe_set_time_real(sub, 1); snd_seq_port_subscribe_set_queue(sub, state->event.queue_id); if ((res = snd_seq_subscribe_port(state->event.hndl, sub)) < 0) { spa_log_error(state->log, "can't subscribe to %d:%d - %s", port->addr.client, port->addr.port, snd_strerror(res)); active = false; } spa_log_info(state->log, "subscribe: %s port %d.%d", port->direction == SPA_DIRECTION_OUTPUT ? "output" : "input", port->addr.client, port->addr.port); } else { if ((res = snd_seq_unsubscribe_port(state->event.hndl, sub)) < 0) { spa_log_warn(state->log, "can't unsubscribe from %d:%d - %s", port->addr.client, port->addr.port, snd_strerror(res)); } spa_log_info(state->log, "unsubscribe: %s port %d.%d", port->direction == SPA_DIRECTION_OUTPUT ? "output" : "input", port->addr.client, port->addr.port); } port->active = active; return res; } static struct buffer *peek_buffer(struct seq_state *state, struct seq_port *port) { if (spa_list_is_empty(&port->free)) return NULL; return spa_list_first(&port->free, struct buffer, link); } int spa_alsa_seq_recycle_buffer(struct seq_state *state, struct seq_port *port, uint32_t buffer_id) { struct buffer *b = &port->buffers[buffer_id]; if (SPA_FLAG_IS_SET(b->flags, BUFFER_FLAG_OUT)) { spa_log_trace_fp(state->log, "%p: recycle buffer port:%p buffer-id:%u", state, port, buffer_id); spa_list_append(&port->free, &b->link); SPA_FLAG_CLEAR(b->flags, BUFFER_FLAG_OUT); } return 0; } static int prepare_buffer(struct seq_state *state, struct seq_port *port) { if (port->buffer != NULL) return 0; if ((port->buffer = peek_buffer(state, port)) == NULL) return -EPIPE; spa_pod_builder_init(&port->builder, port->buffer->buf->datas[0].data, port->buffer->buf->datas[0].maxsize); spa_pod_builder_push_sequence(&port->builder, &port->frame, 0); return 0; } static int process_recycle(struct seq_state *state) { struct seq_stream *stream = &state->streams[SPA_DIRECTION_OUTPUT]; struct seq_port *port; spa_list_for_each(port, &stream->mix_list, mix_link) { struct spa_io_buffers *io = port->io; if (io->status != SPA_STATUS_HAVE_DATA && io->buffer_id < port->n_buffers) { spa_alsa_seq_recycle_buffer(state, port, io->buffer_id); io->buffer_id = SPA_ID_INVALID; } } return 0; } #define NSEC_TO_CLOCK(r,n) (((n) * (r)->denom) / ((r)->num * SPA_NSEC_PER_SEC)) #define NSEC_FROM_CLOCK(r,n) (((n) * (r)->num * SPA_NSEC_PER_SEC) / (r)->denom) static int process_read(struct seq_state *state) { struct seq_stream *stream = &state->streams[SPA_DIRECTION_OUTPUT]; const bool ump = state->ump; uint32_t *data; uint8_t midi1_data[MAX_EVENT_SIZE]; uint32_t ump_data[MAX_EVENT_SIZE]; long size; int res = -1; struct seq_port *port; /* copy all new midi events into their port buffers */ while (1) { const snd_seq_addr_t *addr; uint64_t ev_time, diff; uint32_t offset; void *event; uint8_t *midi1_ptr; size_t midi1_size = 0; uint64_t ump_state = 0; snd_seq_event_type_t SPA_UNUSED type; if (ump) { #ifdef HAVE_ALSA_UMP snd_seq_ump_event_t *ev; res = snd_seq_ump_event_input(state->event.hndl, &ev); if (res <= 0) break; debug_ump_event(state, "read", ev); event = ev; addr = &ev->source; ev_time = SPA_TIMESPEC_TO_NSEC(&ev->time.time); type = ev->type; #else spa_assert_not_reached(); #endif } else { snd_seq_event_t *ev; res = snd_seq_event_input(state->event.hndl, &ev); if (res <= 0) break; debug_event(state, "read", ev); event = ev; addr = &ev->source; ev_time = SPA_TIMESPEC_TO_NSEC(&ev->time.time); type = ev->type; } if ((port = find_port(state, stream, addr)) == NULL) { spa_log_debug(state->log, "unknown port %d.%d", addr->client, addr->port); continue; } if (port->io == NULL || port->n_buffers == 0) continue; if ((res = prepare_buffer(state, port)) < 0) { spa_log_debug(state->log, "can't prepare buffer port:%p %d.%d: %s", port, addr->client, addr->port, spa_strerror(res)); continue; } /* queue_time is the estimated current time of the queue as calculated by * the DLL. Calculate the age of the event. */ if (state->queue_time > ev_time) diff = state->queue_time - ev_time; else diff = 0; /* convert the age to samples and convert to an offset */ offset = NSEC_TO_CLOCK(&state->rate, diff); if (state->duration > offset) offset = state->duration - 1 - offset; else offset = 0; if (ump) { #ifdef HAVE_ALSA_UMP snd_seq_ump_event_t *ev = event; data = (uint32_t*)&ev->ump[0]; size = spa_ump_message_size(snd_ump_msg_hdr_type(ev->ump[0])) * 4; #else spa_assert_not_reached(); #endif } else { snd_seq_event_t *ev = event; snd_midi_event_reset_decode(stream->codec); if ((size = snd_midi_event_decode(stream->codec, midi1_data, sizeof(midi1_data), ev)) < 0) { spa_log_warn(state->log, "decode failed: %s", snd_strerror(size)); continue; } midi1_ptr = midi1_data; midi1_size = size; } do { if (!ump) { data = ump_data; size = spa_ump_from_midi(&midi1_ptr, &midi1_size, ump_data, sizeof(ump_data), 0, &ump_state); if (size <= 0) break; } spa_log_trace_fp(state->log, "event %d time:%"PRIu64" offset:%d size:%ld port:%d.%d", type, ev_time, offset, size, addr->client, addr->port); spa_pod_builder_control(&port->builder, offset, SPA_CONTROL_UMP); spa_pod_builder_bytes(&port->builder, data, size); /* make sure we can fit at least one control event of max size otherwise * we keep the event in the queue and try to copy it in the next cycle */ if (port->builder.state.offset + sizeof(struct spa_pod_control) + MAX_EVENT_SIZE > port->buffer->buf->datas[0].maxsize) goto done; } while (!ump); } done: if (res < 0 && res != -EAGAIN) spa_log_warn(state->log, "event read failed: %s", snd_strerror(res)); /* prepare a buffer on each port, some ports might have their * buffer filled above */ res = 0; spa_list_for_each(port, &stream->mix_list, mix_link) { struct spa_io_buffers *io = port->io; if (prepare_buffer(state, port) >= 0) { spa_pod_builder_pop(&port->builder, &port->frame); port->buffer->buf->datas[0].chunk->offset = 0; port->buffer->buf->datas[0].chunk->size = port->builder.state.offset; if (port->builder.state.offset > port->buffer->buf->datas[0].maxsize) { spa_log_warn(state->log, "control overflow: %d > %d", port->builder.state.offset, port->buffer->buf->datas[0].maxsize); } /* move buffer to ready queue */ spa_list_remove(&port->buffer->link); SPA_FLAG_SET(port->buffer->flags, BUFFER_FLAG_OUT); spa_list_append(&port->ready, &port->buffer->link); port->buffer = NULL; } /* if there is already data, continue */ if (io->status == SPA_STATUS_HAVE_DATA) { res |= SPA_STATUS_HAVE_DATA; continue; } if (io->buffer_id < port->n_buffers) spa_alsa_seq_recycle_buffer(state, port, io->buffer_id); if (spa_list_is_empty(&port->ready)) { /* we have no ready buffers */ io->buffer_id = SPA_ID_INVALID; io->status = -EPIPE; } else { struct buffer *b = spa_list_first(&port->ready, struct buffer, link); spa_list_remove(&b->link); /* dequeue ready buffer */ io->buffer_id = b->id; io->status = SPA_STATUS_HAVE_DATA; res |= SPA_STATUS_HAVE_DATA; } } return res; } static int process_write(struct seq_state *state) { struct seq_stream *stream = &state->streams[SPA_DIRECTION_INPUT]; const bool ump = state->ump; int err, res = 0; struct seq_port *port; spa_list_for_each(port, &stream->mix_list, mix_link) { struct spa_io_buffers *io = port->io; struct buffer *buffer; struct spa_pod_parser parser; struct spa_pod_frame frame; struct spa_pod_sequence seq; const void *seq_body; struct spa_data *d; struct spa_pod_control c; const void *c_body; uint64_t out_time; snd_seq_real_time_t out_rt; bool first = true; if (io->status != SPA_STATUS_HAVE_DATA || io->buffer_id >= port->n_buffers) continue; buffer = &port->buffers[io->buffer_id]; d = &buffer->buf->datas[0]; io->status = SPA_STATUS_NEED_DATA; spa_node_call_reuse_buffer(&state->callbacks, port->id, io->buffer_id); res |= SPA_STATUS_NEED_DATA; spa_pod_parser_init_from_data(&parser, d->data, d->maxsize, d->chunk->offset, d->chunk->size); if (spa_pod_parser_push_sequence_body(&parser, &frame, &seq, &seq_body) < 0) { spa_log_warn(state->log, "invalid sequence in buffer max:%u offset:%u size:%u", d->maxsize, d->chunk->offset, d->chunk->size); continue; } while (spa_pod_parser_get_control_body(&parser, &c, &c_body) >= 0) { size_t body_size; uint8_t *body; if (c.type != SPA_CONTROL_UMP) continue; body = (uint8_t*)c_body; body_size = c.value.size; out_time = state->queue_time + NSEC_FROM_CLOCK(&state->rate, c.offset); out_rt.tv_nsec = out_time % SPA_NSEC_PER_SEC; out_rt.tv_sec = out_time / SPA_NSEC_PER_SEC; spa_log_trace_fp(state->log, "event time:%"PRIu64" offset:%d size:%zd port:%d.%d", out_time, c.offset, body_size, port->addr.client, port->addr.port); if (ump) { #ifdef HAVE_ALSA_UMP snd_seq_ump_event_t ev; snd_seq_ump_ev_clear(&ev); snd_seq_ev_set_ump_data(&ev, body, SPA_MIN(sizeof(ev.ump), (size_t)body_size)); snd_seq_ev_set_source(&ev, state->event.addr.port); snd_seq_ev_set_dest(&ev, port->addr.client, port->addr.port); snd_seq_ev_schedule_real(&ev, state->event.queue_id, 0, &out_rt); debug_ump_event(state, "send", &ev); if ((err = snd_seq_ump_event_output(state->event.hndl, &ev)) < 0) { spa_log_warn(state->log, "failed to output event: %s", snd_strerror(err)); } #else spa_assert_not_reached(); #endif } else { snd_seq_event_t ev; uint8_t data[MAX_EVENT_SIZE]; int size; uint64_t st = 0; while (body_size > 0) { if ((size = spa_ump_to_midi((const uint32_t **)&body, &body_size, data, sizeof(data), &st)) <= 0) break; if (first) snd_seq_ev_clear(&ev); if ((size = snd_midi_event_encode(stream->codec, data, size, &ev)) < 0) { spa_log_warn(state->log, "failed to encode event: %s", snd_strerror(size)); snd_midi_event_reset_encode(stream->codec); first = true; continue; } first = false; if (ev.type == SND_SEQ_EVENT_NONE) /* this can happen when the event is not complete yet, like * a sysex message and we need to encode some more data. */ continue; snd_seq_ev_set_source(&ev, state->event.addr.port); snd_seq_ev_set_dest(&ev, port->addr.client, port->addr.port); snd_seq_ev_schedule_real(&ev, state->event.queue_id, 0, &out_rt); debug_event(state, "send", &ev); if ((err = snd_seq_event_output(state->event.hndl, &ev)) < 0) { spa_log_warn(state->log, "failed to output event: %s", snd_strerror(err)); } first = true; } } } } snd_seq_drain_output(state->event.hndl); return res; } static void update_position(struct seq_state *state) { if (SPA_LIKELY(state->position)) { struct spa_io_clock *clock = &state->position->clock; state->rate = clock->rate; if (state->rate.num == 0 || state->rate.denom == 0) state->rate = SPA_FRACTION(1, 48000); state->duration = clock->duration; } else { state->rate = SPA_FRACTION(1, 48000); state->duration = 1024; } state->threshold = state->duration; } static int update_time(struct seq_state *state, uint64_t nsec, bool follower) { snd_seq_queue_status_t *status; const snd_seq_real_time_t* queue_time; uint64_t queue_real; double err, corr; uint64_t q1, q2; /* take queue time */ snd_seq_queue_status_alloca(&status); snd_seq_get_queue_status(state->event.hndl, state->event.queue_id, status); queue_time = snd_seq_queue_status_get_real_time(status); queue_real = SPA_TIMESPEC_TO_NSEC(queue_time); if (state->dll.bw == 0.0) { spa_dll_set_bw(&state->dll, SPA_DLL_BW_MAX, state->threshold, state->rate.denom); state->next_time = nsec; state->base_time = nsec; state->queue_next = queue_real; } /* track our estimated elapsed time against the real elapsed queue time */ q1 = NSEC_TO_CLOCK(&state->rate, state->queue_next); q2 = NSEC_TO_CLOCK(&state->rate, queue_real); err = ((int64_t)q1 - (int64_t) q2); if (fabs(err) > state->threshold) spa_dll_init(&state->dll); err = SPA_CLAMP(err, -64, 64); corr = spa_dll_update(&state->dll, err); /* this is our current estimated queue time and rate */ state->queue_time = state->queue_next; state->queue_corr = corr; /* make a new estimated queue time with the current quantum, if we are following, * use the rate correction, else we will use the rate correction only for the new * timeout. */ if (state->following) state->queue_next += (uint64_t)(state->threshold * corr * 1e9 / state->rate.denom); else state->queue_next += (uint64_t)(state->threshold * 1e9 / state->rate.denom); if ((state->next_time - state->base_time) > BW_PERIOD) { state->base_time = state->next_time; spa_log_debug(state->log, "%p: follower:%d rate:%f bw:%f err:%f (%f %f %f)", state, follower, corr, state->dll.bw, err, state->dll.z1, state->dll.z2, state->dll.z3); } state->next_time += (uint64_t)(state->threshold / corr * 1e9 / state->rate.denom); if (SPA_LIKELY(state->clock)) { state->clock->nsec = nsec; state->clock->rate = state->rate; state->clock->position += state->clock->duration; state->clock->duration = state->duration; state->clock->delay = (int64_t)(state->duration * corr); state->clock->rate_diff = corr; state->clock->next_nsec = state->next_time; } spa_log_trace_fp(state->log, "now:%"PRIu64" queue:%"PRIu64" err:%f corr:%f next:%"PRIu64" thr:%d", nsec, queue_real, err, corr, state->next_time, state->threshold); return 0; } int spa_alsa_seq_process(struct seq_state *state) { int res; update_position(state); res = process_recycle(state); if (state->following && state->position) { update_time(state, state->position->clock.nsec, true); res |= process_read(state); } res |= process_write(state); return res; } static void alsa_on_timeout_event(struct spa_source *source) { struct seq_state *state = source->data; uint64_t expire; int res; if (state->started) { if ((res = spa_system_timerfd_read(state->data_system, state->timerfd, &expire)) < 0) { if (res != -EAGAIN) spa_log_warn(state->log, "%p: error reading timerfd: %s", state, spa_strerror(res)); return; } } state->current_time = state->next_time; spa_log_trace(state->log, "timeout %"PRIu64, state->current_time); if (SPA_LIKELY(state->position)) { struct spa_io_clock *clock = &state->position->clock; state->rate = clock->target_rate; if (state->rate.num == 0 || state->rate.denom == 0) state->rate = SPA_FRACTION(1, 48000); state->duration = clock->target_duration; } else { state->rate = SPA_FRACTION(1, 48000); state->duration = 1024; } state->threshold = state->duration; update_time(state, state->current_time, false); res = process_read(state); if (res >= 0) spa_node_call_ready(&state->callbacks, res | SPA_STATUS_NEED_DATA); set_timeout(state, state->next_time); } static void reset_buffers(struct seq_state *this, struct seq_port *port) { uint32_t i; spa_list_init(&port->free); spa_list_init(&port->ready); for (i = 0; i < port->n_buffers; i++) { struct buffer *b = &port->buffers[i]; if (port->direction == SPA_DIRECTION_INPUT) { SPA_FLAG_SET(b->flags, BUFFER_FLAG_OUT); } else { spa_list_append(&port->free, &b->link); SPA_FLAG_CLEAR(b->flags, BUFFER_FLAG_OUT); } } } static void reset_stream(struct seq_state *this, struct seq_stream *stream, bool active) { struct seq_port *port; spa_list_for_each(port, &stream->port_list, link) { reset_buffers(this, port); spa_alsa_seq_activate_port(this, port, active); } } static int set_timers(struct seq_state *state) { struct timespec now; int res; if ((res = spa_system_clock_gettime(state->data_system, CLOCK_MONOTONIC, &now)) < 0) return res; state->queue_time = 0; state->queue_corr = 1.0; spa_dll_init(&state->dll); state->next_time = SPA_TIMESPEC_TO_NSEC(&now); if (state->following) { set_timeout(state, 0); } else { set_timeout(state, state->next_time); } return 0; } static inline bool is_following(struct seq_state *state) { return state->position && state->clock && state->position->clock.id != state->clock->id; } int spa_alsa_seq_start(struct seq_state *state) { int res; if (state->started) return 0; state->following = is_following(state); spa_log_debug(state->log, "alsa %p: start follower:%d", state, state->following); if ((res = snd_seq_start_queue(state->event.hndl, state->event.queue_id, NULL)) < 0) { spa_log_error(state->log, "failed to start queue: %s", snd_strerror(res)); return res; } while (snd_seq_drain_output(state->event.hndl) > 0) sleep(1); update_position(state); state->started = true; reset_stream(state, &state->streams[SPA_DIRECTION_INPUT], true); reset_stream(state, &state->streams[SPA_DIRECTION_OUTPUT], true); state->source.func = alsa_on_timeout_event; state->source.data = state; state->source.fd = state->timerfd; state->source.mask = SPA_IO_IN; state->source.rmask = 0; spa_loop_add_source(state->data_loop, &state->source); res = set_timers(state); return res; } static int do_reassign_follower(struct spa_loop *loop, bool async, uint32_t seq, const void *data, size_t size, void *user_data) { struct seq_state *state = user_data; int res; if ((res = set_timers(state)) < 0) spa_log_error(state->log, "can't set timers: %s", spa_strerror(res)); return 0; } int spa_alsa_seq_reassign_follower(struct seq_state *state) { bool following; if (!state->started) return 0; following = is_following(state); if (following != state->following) { spa_log_debug(state->log, "alsa %p: reassign follower %d->%d", state, state->following, following); state->following = following; spa_loop_locked(state->data_loop, do_reassign_follower, 0, NULL, 0, state); } return 0; } static int do_remove_source(struct spa_loop *loop, bool async, uint32_t seq, const void *data, size_t size, void *user_data) { struct seq_state *state = user_data; spa_loop_remove_source(state->data_loop, &state->source); set_timeout(state, 0); return 0; } int spa_alsa_seq_pause(struct seq_state *state) { int res; if (!state->started) return 0; spa_log_debug(state->log, "alsa %p: pause", state); spa_loop_locked(state->data_loop, do_remove_source, 0, NULL, 0, state); if ((res = snd_seq_stop_queue(state->event.hndl, state->event.queue_id, NULL)) < 0) { spa_log_warn(state->log, "failed to stop queue: %s", snd_strerror(res)); } while (snd_seq_drain_output(state->event.hndl) > 0) sleep(1); state->started = false; reset_stream(state, &state->streams[SPA_DIRECTION_INPUT], false); reset_stream(state, &state->streams[SPA_DIRECTION_OUTPUT], false); return 0; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/alsa-seq.h000066400000000000000000000104311511204443500255400ustar00rootroot00000000000000/* Spa ALSA Sequencer */ /* SPDX-FileCopyrightText: Copyright © 2019 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_ALSA_SEQ_H #define SPA_ALSA_SEQ_H #include "config.h" #include #include #include #ifdef HAVE_ALSA_UMP #include #endif #include #include #include #include #include #include #include #include #include #include #include "alsa.h" #ifdef __cplusplus extern "C" { #endif struct props { char device[64]; char clock_name[64]; bool disable_longname; }; #define MAX_EVENT_SIZE 256 #define MAX_PORTS 256 #define MAX_BUFFERS 32 struct buffer { uint32_t id; #define BUFFER_FLAG_OUT (1<<0) uint32_t flags; struct spa_buffer *buf; struct spa_meta_header *h; struct spa_list link; }; struct seq_port { struct spa_list link; uint32_t id; enum spa_direction direction; snd_seq_addr_t addr; uint64_t info_all; struct spa_port_info info; #define PORT_EnumFormat 0 #define PORT_Meta 1 #define PORT_IO 2 #define PORT_Format 3 #define PORT_Buffers 4 #define PORT_Latency 5 #define N_PORT_PARAMS 6 struct spa_param_info params[N_PORT_PARAMS]; struct spa_io_buffers *io; struct buffer buffers[MAX_BUFFERS]; unsigned int n_buffers; struct spa_list free; struct spa_list ready; struct buffer *buffer; struct spa_pod_builder builder; struct spa_pod_frame frame; struct spa_audio_info current_format; unsigned int have_format:1; unsigned int active:1; struct spa_latency_info latency[2]; unsigned int mixing:1; struct spa_list mix_link; }; struct seq_stream { enum spa_direction direction; unsigned int caps; snd_midi_event_t *codec; struct seq_port *ports[MAX_PORTS]; struct spa_list port_list; struct spa_list mix_list; }; struct seq_conn { snd_seq_t *hndl; snd_seq_addr_t addr; int queue_id; int fd; struct spa_source source; }; #define BW_PERIOD (3 * SPA_NSEC_PER_SEC) struct seq_state { struct spa_handle handle; struct spa_node node; struct spa_log *log; struct spa_system *data_system; struct spa_loop *data_loop; struct spa_loop *main_loop; struct seq_conn sys; struct seq_conn event; int (*port_info) (void *data, const snd_seq_addr_t *addr, const snd_seq_port_info_t *info); void *port_info_data; struct spa_hook_list hooks; struct spa_callbacks callbacks; uint64_t info_all; struct spa_node_info info; #define NODE_PropInfo 0 #define NODE_Props 1 #define NODE_IO 2 #define N_NODE_PARAMS 3 struct spa_param_info params[N_NODE_PARAMS]; struct props props; struct spa_io_clock *clock; struct spa_io_position *position; uint32_t quantum_limit; uint32_t min_pool_size; uint32_t max_pool_size; int rate_denom; uint32_t duration; uint32_t threshold; struct spa_fraction rate; struct spa_source source; int timerfd; uint64_t current_time; uint64_t next_time; uint64_t base_time; uint64_t queue_time; uint64_t queue_next; double queue_corr; unsigned int opened:1; unsigned int started:1; unsigned int following:1; unsigned int ump:1; struct seq_stream streams[2]; struct spa_list free_list; struct spa_dll dll; }; #define VALID_DIRECTION(this,d) ((d) == SPA_DIRECTION_INPUT || (d) == SPA_DIRECTION_OUTPUT) #define VALID_PORT(this,d,p) ((p) < MAX_PORTS && this->streams[d].ports[p] != NULL) #define CHECK_IN_PORT(this,d,p) ((d) == SPA_DIRECTION_INPUT && VALID_PORT(this,d,p)) #define CHECK_OUT_PORT(this,d,p) ((d) == SPA_DIRECTION_OUTPUT && VALID_PORT(this,d,p)) #define CHECK_PORT(this,d,p) (VALID_DIRECTION(this,d) && VALID_PORT(this,d,p)) #define GET_PORT(this,d,p) (this->streams[d].ports[p]) int spa_alsa_seq_open(struct seq_state *state); int spa_alsa_seq_close(struct seq_state *state); int spa_alsa_seq_start(struct seq_state *state); int spa_alsa_seq_pause(struct seq_state *state); int spa_alsa_seq_reassign_follower(struct seq_state *state); int spa_alsa_seq_activate_port(struct seq_state *state, struct seq_port *port, bool active); int spa_alsa_seq_recycle_buffer(struct seq_state *state, struct seq_port *port, uint32_t buffer_id); int spa_alsa_seq_process(struct seq_state *state); #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_ALSA_SEQ_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/alsa-udev.c000066400000000000000000000764721511204443500257270ustar00rootroot00000000000000/* Spa ALSA udev */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "alsa.h" #define MAX_CARDS 64 enum action { ACTION_CHANGE, ACTION_REMOVE, }; /* Used for unavailable devices in the card structure. */ #define ID_DEVICE_NOT_SUPPORTED 0 /* This represents an ALSA card. * One card can have up to 1 PCM and 1 Compress-Offload device. */ struct card { unsigned int card_nr; struct udev_device *udev_device; unsigned int unavailable:1; unsigned int accessible:1; unsigned int ignored:1; unsigned int emitted:1; /* Local SPA object IDs. (Global IDs are produced by PipeWire * out of this using its registry.) Compress-Offload or PCM * is not available, the corresponding ID is set to * ID_DEVICE_NOT_SUPPORTED (= 0). * PCM device IDs are (card nr + 1) * 2, and Compress-Offload * device IDs are (card nr + 1) * 2 + 1. Assigning IDs like this * makes it easy to deal with removed devices. (card nr + 1) * is used because 0 is a valid ALSA card number. */ uint32_t pcm_device_id; uint32_t compress_offload_device_id; }; static uint32_t calc_pcm_device_id(struct card *card) { return (card->card_nr + 1) * 2 + 0; } static uint32_t calc_compress_offload_device_id(struct card *card) { return (card->card_nr + 1) * 2 + 1; } struct impl { struct spa_handle handle; struct spa_device device; struct spa_log *log; struct spa_loop *main_loop; struct spa_system *main_system; struct spa_hook_list hooks; uint64_t info_all; struct spa_device_info info; struct udev *udev; struct udev_monitor *umonitor; struct card cards[MAX_CARDS]; unsigned int n_cards; struct spa_source source; struct spa_source notify; unsigned int use_acp:1; unsigned int expose_busy:1; int use_ucm; }; static int impl_udev_open(struct impl *this) { if (this->udev == NULL) { this->udev = udev_new(); if (this->udev == NULL) return -ENOMEM; } return 0; } static int impl_udev_close(struct impl *this) { if (this->udev != NULL) udev_unref(this->udev); this->udev = NULL; return 0; } static struct card *add_card(struct impl *this, unsigned int card_nr, struct udev_device *udev_device) { struct card *card; if (this->n_cards >= MAX_CARDS) return NULL; card = &this->cards[this->n_cards++]; spa_zero(*card); card->card_nr = card_nr; udev_device_ref(udev_device); card->udev_device = udev_device; return card; } static struct card *find_card(struct impl *this, unsigned int card_nr) { unsigned int i; for (i = 0; i < this->n_cards; i++) { if (this->cards[i].card_nr == card_nr) return &this->cards[i]; } return NULL; } static void remove_card(struct impl *this, struct card *card) { udev_device_unref(card->udev_device); *card = this->cards[--this->n_cards]; } static void clear_cards(struct impl *this) { unsigned int i; for (i = 0; i < this->n_cards; i++) udev_device_unref(this->cards[i].udev_device); this->n_cards = 0; } static unsigned int get_card_nr(struct impl *this, struct udev_device *udev_device) { const char *e, *str; if (udev_device_get_property_value(udev_device, "ACP_IGNORE")) return SPA_ID_INVALID; if ((str = udev_device_get_property_value(udev_device, "SOUND_CLASS")) && spa_streq(str, "modem")) return SPA_ID_INVALID; if (udev_device_get_property_value(udev_device, "SOUND_INITIALIZED") == NULL) return SPA_ID_INVALID; if ((str = udev_device_get_property_value(udev_device, "DEVPATH")) == NULL) return SPA_ID_INVALID; if ((e = strrchr(str, '/')) == NULL) return SPA_ID_INVALID; if (strlen(e) <= 5 || strncmp(e, "/card", 5) != 0) return SPA_ID_INVALID; return atoi(e + 5); } static int dehex(char x) { if (x >= '0' && x <= '9') return x - '0'; if (x >= 'A' && x <= 'F') return x - 'A' + 10; if (x >= 'a' && x <= 'f') return x - 'a' + 10; return -1; } static void unescape(const char *src, char *dst) { const char *s; char *d; int h1 = 0, h2 = 0; enum { TEXT, BACKSLASH, EX, FIRST } state = TEXT; for (s = src, d = dst; *s; s++) { switch (state) { case TEXT: if (*s == '\\') state = BACKSLASH; else *(d++) = *s; break; case BACKSLASH: if (*s == 'x') state = EX; else { *(d++) = '\\'; *(d++) = *s; state = TEXT; } break; case EX: h1 = dehex(*s); if (h1 < 0) { *(d++) = '\\'; *(d++) = 'x'; *(d++) = *s; state = TEXT; } else state = FIRST; break; case FIRST: h2 = dehex(*s); if (h2 < 0) { *(d++) = '\\'; *(d++) = 'x'; *(d++) = *(s-1); *(d++) = *s; } else *(d++) = (char) (h1 << 4) | h2; state = TEXT; break; } } switch (state) { case TEXT: break; case BACKSLASH: *(d++) = '\\'; break; case EX: *(d++) = '\\'; *(d++) = 'x'; break; case FIRST: *(d++) = '\\'; *(d++) = 'x'; *(d++) = *(s-1); break; } *d = 0; } static int check_device_pcm_class(const char *devname) { char path[PATH_MAX]; char buf[16]; size_t sz; /* Check device class */ spa_scnprintf(path, sizeof(path), "/sys/class/sound/%s/pcm_class", devname); spa_autoptr(FILE) f = fopen(path, "re"); if (f == NULL) return -errno; sz = fread(buf, 1, sizeof(buf) - 1, f); buf[sz] = '\0'; return spa_strstartswith(buf, "modem") ? -ENXIO : 0; } static int get_num_pcm_devices(unsigned int card_nr) { char prefix[32]; struct dirent *entry; int num_dev = 0; int res; /* Check if card has PCM devices, without opening them */ spa_scnprintf(prefix, sizeof(prefix), "pcmC%uD", card_nr); spa_autoptr(DIR) snd = opendir("/dev/snd"); if (snd == NULL) return -errno; while ((errno = 0, entry = readdir(snd)) != NULL) { if (!(entry->d_type == DT_CHR && spa_strstartswith(entry->d_name, prefix))) continue; res = check_device_pcm_class(entry->d_name); if (res != -ENXIO) { /* count device also if sysfs status file not accessible */ ++num_dev; } } return errno != 0 ? -errno : num_dev; } static int get_num_compress_offload_devices(unsigned int card_nr) { char prefix[32]; struct dirent *entry; int num_dev = 0; /* Check if card has Compress-Offload devices, without opening them */ spa_scnprintf(prefix, sizeof(prefix), "comprC%uD", card_nr); spa_autoptr(DIR) snd = opendir("/dev/snd"); if (snd == NULL) return -errno; while ((errno = 0, entry = readdir(snd)) != NULL) { if (!(entry->d_type == DT_CHR && spa_strstartswith(entry->d_name, prefix))) continue; ++num_dev; } return errno != 0 ? -errno : num_dev; } static int check_udev_environment(struct udev *udev, const char *devname) { char path[PATH_MAX]; struct udev_device *dev; int ret = 0; /* Check for ACP_IGNORE on a specific PCM device (not the whole card) */ spa_scnprintf(path, sizeof(path), "/sys/class/sound/%s", devname); dev = udev_device_new_from_syspath(udev, path); if (dev == NULL) return 0; if (udev_device_get_property_value(dev, "ACP_IGNORE")) ret = -ENXIO; udev_device_unref(dev); return ret; } static int check_pcm_device_availability(struct impl *this, struct card *card, int *num_pcm_devices) { char path[PATH_MAX]; char buf[16]; size_t sz; struct dirent *entry, *entry_pcm; int res; res = get_num_pcm_devices(card->card_nr); if (res < 0) { spa_log_error(this->log, "Error finding PCM devices for ALSA card %u: %s", card->card_nr, spa_strerror(res)); return res; } *num_pcm_devices = res; spa_log_debug(this->log, "card %u has %d PCM device(s)", card->card_nr, *num_pcm_devices); /* * Check if some pcm devices of the card are busy. Check it via /proc, as we * don't want to actually open any devices using alsa-lib (generates uncontrolled * number of inotify events), or replicate its subdevice logic. * * The /proc/asound directory might not exist if kernel is compiled with * CONFIG_SND_PROCFS=n, and the pcmXX directories may be missing if compiled * with CONFIG_SND_VERBOSE_PROCFS=n. In those cases, the busy check always succeeds. */ res = 0; if (this->expose_busy) return res; spa_scnprintf(path, sizeof(path), "/proc/asound/card%u", card->card_nr); spa_autoptr(DIR) card_dir = opendir(path); if (card_dir == NULL) goto done; while ((errno = 0, entry = readdir(card_dir)) != NULL) { if (!(entry->d_type == DT_DIR && spa_strstartswith(entry->d_name, "pcm"))) continue; spa_scnprintf(path, sizeof(path), "pcmC%uD%s", card->card_nr, entry->d_name+3); if (check_device_pcm_class(path) < 0) continue; /* Check udev environment */ if (check_udev_environment(this->udev, path) < 0) continue; /* Check busy status */ spa_scnprintf(path, sizeof(path), "/proc/asound/card%u/%s", card->card_nr, entry->d_name); spa_autoptr(DIR) pcm = opendir(path); if (pcm == NULL) goto done; while ((errno = 0, entry_pcm = readdir(pcm)) != NULL) { if (!(entry_pcm->d_type == DT_DIR && spa_strstartswith(entry_pcm->d_name, "sub"))) continue; spa_scnprintf(path, sizeof(path), "/proc/asound/card%u/%s/%s/status", card->card_nr, entry->d_name, entry_pcm->d_name); spa_autoptr(FILE) f = fopen(path, "re"); if (f == NULL) goto done; sz = fread(buf, 1, 6, f); buf[sz] = '\0'; if (!spa_strstartswith(buf, "closed")) { spa_log_debug(this->log, "card %u pcm device %s busy", card->card_nr, entry->d_name); res = -EBUSY; goto done; } spa_log_debug(this->log, "card %u pcm device %s free", card->card_nr, entry->d_name); } if (errno != 0) goto done; } if (errno != 0) goto done; done: if (errno != 0) { spa_log_info(this->log, "card %u: failed to find busy status (%s)", card->card_nr, spa_strerror(-errno)); } return res; } static int check_compress_offload_device_availability(struct impl *this, struct card *card, int *num_compress_offload_devices) { int res; res = get_num_compress_offload_devices(card->card_nr); if (res < 0) { spa_log_error(this->log, "Error finding Compress-Offload devices for ALSA card %u: %s", card->card_nr, spa_strerror(res)); return res; } *num_compress_offload_devices = res; spa_log_debug(this->log, "card %u has %d Compress-Offload device(s)", card->card_nr, *num_compress_offload_devices); return 0; } static int emit_added_object_info(struct impl *this, struct card *card) { char path[32]; int res, num_pcm_devices, num_compress_offload_devices; const char *str; struct udev_device *udev_device = card->udev_device; /* * inotify close events under /dev/snd must not be emitted, except after setting * card->emitted to true. alsalib functions can be used after that. */ snprintf(path, sizeof(path), "hw:%u", card->card_nr); if ((res = check_pcm_device_availability(this, card, &num_pcm_devices)) < 0) return res; if ((res = check_compress_offload_device_availability(this, card, &num_compress_offload_devices)) < 0) return res; if ((num_pcm_devices == 0) && (num_compress_offload_devices == 0)) { spa_log_debug(this->log, "no PCM and no Compress-Offload devices for %s", path); card->ignored = true; return -ENODEV; } card->emitted = true; if (num_pcm_devices > 0) { struct spa_device_object_info info; char *cn = NULL, *cln = NULL; struct spa_dict_item items[26]; unsigned int n_items = 0; card->pcm_device_id = calc_pcm_device_id(card); spa_log_debug(this->log, "emitting ACP/PCM device interface for card %s; " "using local alsa-udev object ID %" PRIu32, path, card->pcm_device_id); info = SPA_DEVICE_OBJECT_INFO_INIT(); info.type = SPA_TYPE_INTERFACE_Device; info.factory_name = this->use_acp ? SPA_NAME_API_ALSA_ACP_DEVICE : SPA_NAME_API_ALSA_PCM_DEVICE; info.change_mask = SPA_DEVICE_OBJECT_CHANGE_MASK_FLAGS | SPA_DEVICE_OBJECT_CHANGE_MASK_PROPS; info.flags = 0; items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_ENUM_API, "udev"); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_API, "alsa"); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_MEDIA_CLASS, "Audio/Device"); if (this->use_ucm != -1) items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_API_ALSA_USE_UCM, this->use_ucm ? "true" : "false"); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_API_ALSA_PATH, path); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_API_ALSA_CARD, path+3); if (snd_card_get_name(card->card_nr, &cn) >= 0) items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_API_ALSA_CARD_NAME, cn); if (snd_card_get_longname(card->card_nr, &cln) >= 0) items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_API_ALSA_CARD_LONGNAME, cln); if ((str = udev_device_get_property_value(udev_device, "ACP_NAME")) && *str) items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_NAME, str); if ((str = udev_device_get_property_value(udev_device, "ACP_PROFILE_SET")) && *str) items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_PROFILE_SET, str); if ((str = udev_device_get_property_value(udev_device, "SOUND_CLASS")) && *str) items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_CLASS, str); if ((str = udev_device_get_property_value(udev_device, "USEC_INITIALIZED")) && *str) items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_PLUGGED_USEC, str); str = udev_device_get_property_value(udev_device, "ID_PATH"); if (!(str && *str)) str = udev_device_get_syspath(udev_device); if (str && *str) { items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_BUS_PATH, str); } if ((str = udev_device_get_devpath(udev_device)) && *str) { items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_SYSFS_PATH, str); } if ((str = udev_device_get_property_value(udev_device, "ID_ID")) && *str) { items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_BUS_ID, str); } if ((str = udev_device_get_property_value(udev_device, "ID_BUS")) && *str) { items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_BUS, str); } if ((str = udev_device_get_property_value(udev_device, "SUBSYSTEM")) && *str) { items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_SUBSYSTEM, str); } if ((str = udev_device_get_property_value(udev_device, "ID_VENDOR_ID")) && *str) { int32_t val; if (spa_atoi32(str, &val, 16)) { char *dec = alloca(12); /* 0xffffffff is max */ snprintf(dec, 12, "0x%04x", val); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_VENDOR_ID, dec); } } str = udev_device_get_property_value(udev_device, "ID_VENDOR_FROM_DATABASE"); if (!(str && *str)) { str = udev_device_get_property_value(udev_device, "ID_VENDOR_ENC"); if (!(str && *str)) { str = udev_device_get_property_value(udev_device, "ID_VENDOR"); } else { char *t = alloca(strlen(str) + 1); unescape(str, t); str = t; } } if (str && *str) { items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_VENDOR_NAME, str); } if ((str = udev_device_get_property_value(udev_device, "ID_MODEL_ID")) && *str) { int32_t val; if (spa_atoi32(str, &val, 16)) { char *dec = alloca(12); /* 0xffffffff is max */ snprintf(dec, 12, "0x%04x", val); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_PRODUCT_ID, dec); } } str = udev_device_get_property_value(udev_device, "ID_MODEL_FROM_DATABASE"); if (!(str && *str)) { str = udev_device_get_property_value(udev_device, "ID_MODEL_ENC"); if (!(str && *str)) { str = udev_device_get_property_value(udev_device, "ID_MODEL"); } else { char *t = alloca(strlen(str) + 1); unescape(str, t); str = t; } } if (str && *str) items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_PRODUCT_NAME, str); if ((str = udev_device_get_property_value(udev_device, "ID_SERIAL")) && *str) { items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_SERIAL, str); } if ((str = udev_device_get_property_value(udev_device, "SOUND_FORM_FACTOR")) && *str) { items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_FORM_FACTOR, str); } info.props = &SPA_DICT_INIT(items, n_items); spa_log_debug(this->log, "interface information:"); spa_debug_log_dict(this->log, SPA_LOG_LEVEL_DEBUG, 2, info.props); spa_device_emit_object_info(&this->hooks, card->pcm_device_id, &info); free(cn); free(cln); } else { card->pcm_device_id = ID_DEVICE_NOT_SUPPORTED; } if (num_compress_offload_devices > 0) { struct spa_device_object_info info; struct spa_dict_item items[11]; unsigned int n_items = 0; char device_name[200]; char device_desc[200]; card->compress_offload_device_id = calc_compress_offload_device_id(card); spa_log_debug(this->log, "emitting Compress-Offload device interface for card %s; " "using local alsa-udev object ID %" PRIu32, path, card->compress_offload_device_id); info = SPA_DEVICE_OBJECT_INFO_INIT(); info.type = SPA_TYPE_INTERFACE_Device; info.factory_name = SPA_NAME_API_ALSA_COMPRESS_OFFLOAD_DEVICE; info.change_mask = SPA_DEVICE_OBJECT_CHANGE_MASK_FLAGS | SPA_DEVICE_OBJECT_CHANGE_MASK_PROPS; info.flags = 0; snprintf(device_name, sizeof(device_name), "comprC%u", card->card_nr); snprintf(device_desc, sizeof(device_desc), "Compress-Offload device (ALSA card %u)", card->card_nr); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_ENUM_API, "udev"); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_API, "alsa:compressed"); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_NAME, device_name); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_DESCRIPTION, device_desc); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_MEDIA_CLASS, "Audio/Device"); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_API_ALSA_PATH, path); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_API_ALSA_CARD, path+3); if ((str = udev_device_get_property_value(udev_device, "USEC_INITIALIZED")) && *str) items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_PLUGGED_USEC, str); str = udev_device_get_property_value(udev_device, "ID_PATH"); if (!(str && *str)) str = udev_device_get_syspath(udev_device); if (str && *str) { items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_BUS_PATH, str); } if ((str = udev_device_get_devpath(udev_device)) && *str) { items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_SYSFS_PATH, str); } if ((str = udev_device_get_property_value(udev_device, "SUBSYSTEM")) && *str) { items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_SUBSYSTEM, str); } info.props = &SPA_DICT_INIT(items, n_items); spa_log_debug(this->log, "interface information:"); spa_debug_log_dict(this->log, SPA_LOG_LEVEL_DEBUG, 2, info.props); spa_device_emit_object_info(&this->hooks, card->compress_offload_device_id, &info); } else { card->compress_offload_device_id = ID_DEVICE_NOT_SUPPORTED; } return 1; } static bool check_access(struct impl *this, struct card *card) { char path[128], pcm_prefix[32], compr_prefix[32];; spa_autoptr(DIR) snd = NULL; struct dirent *entry; bool accessible = false; snprintf(path, sizeof(path), "/dev/snd/controlC%u", card->card_nr); if (access(path, R_OK|W_OK) >= 0 && (snd = opendir("/dev/snd"))) { /* * It's possible that controlCX is accessible before pcmCX* or * the other way around. Return true only if all devices are * accessible. */ accessible = true; spa_scnprintf(pcm_prefix, sizeof(pcm_prefix), "pcmC%uD", card->card_nr); spa_scnprintf(compr_prefix, sizeof(compr_prefix), "comprC%uD", card->card_nr); while ((entry = readdir(snd)) != NULL) { if (!(entry->d_type == DT_CHR && (spa_strstartswith(entry->d_name, pcm_prefix) || spa_strstartswith(entry->d_name, compr_prefix)))) continue; snprintf(path, sizeof(path), "/dev/snd/%.32s", entry->d_name); if (access(path, R_OK|W_OK) < 0) { accessible = false; break; } } } if (accessible != card->accessible) spa_log_debug(this->log, "%s accessible:%u", path, accessible); card->accessible = accessible; return card->accessible; } static void process_card(struct impl *this, enum action action, struct card *card) { if (card->ignored) return; switch (action) { case ACTION_CHANGE: { check_access(this, card); if (card->accessible && !card->emitted) { int res = emit_added_object_info(this, card); if (res < 0) { if (card->ignored) spa_log_info(this->log, "ALSA card %u unavailable (%s): it is ignored", card->card_nr, spa_strerror(res)); else if (!card->unavailable) spa_log_info(this->log, "ALSA card %u unavailable (%s): wait for it", card->card_nr, spa_strerror(res)); else spa_log_debug(this->log, "ALSA card %u still unavailable (%s)", card->card_nr, spa_strerror(res)); card->unavailable = true; } else { if (card->unavailable) spa_log_info(this->log, "ALSA card %u now available", card->card_nr); card->unavailable = false; } } else if (!card->accessible && card->emitted) { card->emitted = false; if (card->pcm_device_id != ID_DEVICE_NOT_SUPPORTED) spa_device_emit_object_info(&this->hooks, card->pcm_device_id, NULL); if (card->compress_offload_device_id != ID_DEVICE_NOT_SUPPORTED) spa_device_emit_object_info(&this->hooks, card->compress_offload_device_id, NULL); } break; } case ACTION_REMOVE: { uint32_t pcm_device_id = card->pcm_device_id; uint32_t compress_offload_device_id = card->compress_offload_device_id; bool emitted = card->emitted; remove_card(this, card); if (emitted) { if (pcm_device_id != ID_DEVICE_NOT_SUPPORTED) spa_device_emit_object_info(&this->hooks, pcm_device_id, NULL); if (compress_offload_device_id != ID_DEVICE_NOT_SUPPORTED) spa_device_emit_object_info(&this->hooks, compress_offload_device_id, NULL); } break; } } } static void process_udev_device(struct impl *this, enum action action, struct udev_device *udev_device) { unsigned int card_nr; struct card *card; if ((card_nr = get_card_nr(this, udev_device)) == SPA_ID_INVALID) return; card = find_card(this, card_nr); if (action == ACTION_CHANGE && !card) card = add_card(this, card_nr, udev_device); if (!card) return; process_card(this, action, card); } static int stop_inotify(struct impl *this) { if (this->notify.fd == -1) return 0; spa_log_info(this->log, "stop inotify"); spa_loop_remove_source(this->main_loop, &this->notify); close(this->notify.fd); this->notify.fd = -1; return 0; } static void impl_on_notify_events(struct spa_source *source) { bool deleted = false; struct impl *this = source->data; union { struct inotify_event e; char name[NAME_MAX+1+sizeof(struct inotify_event)]; } buf; while (true) { ssize_t len; const struct inotify_event *event; void *p, *e; len = read(source->fd, &buf, sizeof(buf)); if (len <= 0) break; e = SPA_PTROFF(&buf, len, void); for (p = &buf; p < e; p = SPA_PTROFF(p, sizeof(struct inotify_event) + event->len, void)) { unsigned int card_nr; struct card *card; event = (const struct inotify_event *) p; spa_assert_se(SPA_PTRDIFF(e, p) >= (ptrdiff_t)sizeof(struct inotify_event) && SPA_PTRDIFF(e, p) - sizeof(struct inotify_event) >= event->len && "bad event from kernel"); /* card becomes accessible or not busy */ if ((event->mask & (IN_ATTRIB | IN_CLOSE_WRITE))) { if (sscanf(event->name, "controlC%u", &card_nr) != 1 && sscanf(event->name, "pcmC%uD", &card_nr) != 1) continue; if ((card = find_card(this, card_nr)) == NULL) continue; process_card(this, ACTION_CHANGE, card); } /* /dev/snd/ might have been removed */ if ((event->mask & (IN_IGNORED | IN_MOVE_SELF))) deleted = true; } } if (deleted) stop_inotify(this); } static int start_inotify(struct impl *this) { int res, notify_fd; if (this->notify.fd != -1) return 0; if ((notify_fd = inotify_init1(IN_CLOEXEC | IN_NONBLOCK)) < 0) return -errno; res = inotify_add_watch(notify_fd, "/dev/snd", IN_ATTRIB | IN_CLOSE_WRITE | IN_MOVE_SELF); if (res < 0) { res = -errno; close(notify_fd); if (res == -ENOENT) { spa_log_debug(this->log, "/dev/snd/ does not exist yet"); return 0; } spa_log_error(this->log, "inotify_add_watch() failed: %s", spa_strerror(res)); return res; } spa_log_info(this->log, "start inotify"); this->notify.func = impl_on_notify_events; this->notify.data = this; this->notify.fd = notify_fd; this->notify.mask = SPA_IO_IN | SPA_IO_ERR; spa_loop_add_source(this->main_loop, &this->notify); return 0; } static void impl_on_fd_events(struct spa_source *source) { struct impl *this = source->data; struct udev_device *udev_device; const char *action; udev_device = udev_monitor_receive_device(this->umonitor); if (udev_device == NULL) return; if ((action = udev_device_get_action(udev_device)) == NULL) action = "change"; spa_log_debug(this->log, "action %s", action); start_inotify(this); if (spa_streq(action, "add") || spa_streq(action, "change")) { process_udev_device(this, ACTION_CHANGE, udev_device); } else if (spa_streq(action, "remove")) { process_udev_device(this, ACTION_REMOVE, udev_device); } udev_device_unref(udev_device); } static int start_monitor(struct impl *this) { int res; if (this->umonitor != NULL) return 0; this->umonitor = udev_monitor_new_from_netlink(this->udev, "udev"); if (this->umonitor == NULL) return -ENOMEM; udev_monitor_filter_add_match_subsystem_devtype(this->umonitor, "sound", NULL); udev_monitor_enable_receiving(this->umonitor); this->source.func = impl_on_fd_events; this->source.data = this; this->source.fd = udev_monitor_get_fd(this->umonitor); this->source.mask = SPA_IO_IN | SPA_IO_ERR; spa_log_debug(this->log, "monitor %p", this->umonitor); spa_loop_add_source(this->main_loop, &this->source); if ((res = start_inotify(this)) < 0) return res; return 0; } static int stop_monitor(struct impl *this) { if (this->umonitor == NULL) return 0; clear_cards (this); spa_loop_remove_source(this->main_loop, &this->source); udev_monitor_unref(this->umonitor); this->umonitor = NULL; stop_inotify(this); return 0; } static int enum_cards(struct impl *this) { struct udev_enumerate *enumerate; struct udev_list_entry *udev_devices; enumerate = udev_enumerate_new(this->udev); if (enumerate == NULL) return -ENOMEM; udev_enumerate_add_match_subsystem(enumerate, "sound"); udev_enumerate_scan_devices(enumerate); for (udev_devices = udev_enumerate_get_list_entry(enumerate); udev_devices; udev_devices = udev_list_entry_get_next(udev_devices)) { struct udev_device *udev_device; udev_device = udev_device_new_from_syspath(this->udev, udev_list_entry_get_name(udev_devices)); if (udev_device == NULL) continue; process_udev_device(this, ACTION_CHANGE, udev_device); udev_device_unref(udev_device); } udev_enumerate_unref(enumerate); return 0; } static const struct spa_dict_item device_info_items[] = { { SPA_KEY_DEVICE_API, "udev" }, { SPA_KEY_DEVICE_NICK, "alsa-udev" }, { SPA_KEY_API_UDEV_MATCH, "sound" }, }; static void emit_device_info(struct impl *this, bool full) { uint64_t old = full ? this->info.change_mask : 0; if (full) this->info.change_mask = this->info_all; if (this->info.change_mask) { this->info.props = &SPA_DICT_INIT_ARRAY(device_info_items); spa_device_emit_info(&this->hooks, &this->info); this->info.change_mask = old; } } static void impl_hook_removed(struct spa_hook *hook) { struct impl *this = hook->priv; if (spa_hook_list_is_empty(&this->hooks)) { stop_monitor(this); impl_udev_close(this); } } static int impl_device_add_listener(void *object, struct spa_hook *listener, const struct spa_device_events *events, void *data) { int res; struct impl *this = object; struct spa_hook_list save; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(events != NULL, -EINVAL); if ((res = impl_udev_open(this)) < 0) return res; spa_hook_list_isolate(&this->hooks, &save, listener, events, data); emit_device_info(this, true); if ((res = start_monitor(this)) < 0) return res; if ((res = enum_cards(this)) < 0) return res; spa_hook_list_join(&this->hooks, &save); listener->removed = impl_hook_removed; listener->priv = this; return 0; } static const struct spa_device_methods impl_device = { SPA_VERSION_DEVICE_METHODS, .add_listener = impl_device_add_listener, }; static int impl_get_interface(struct spa_handle *handle, const char *type, void **interface) { struct impl *this; spa_return_val_if_fail(handle != NULL, -EINVAL); spa_return_val_if_fail(interface != NULL, -EINVAL); this = (struct impl *) handle; if (spa_streq(type, SPA_TYPE_INTERFACE_Device)) *interface = &this->device; else return -ENOENT; return 0; } static int impl_clear(struct spa_handle *handle) { struct impl *this = (struct impl *) handle; stop_monitor(this); impl_udev_close(this); return 0; } static size_t impl_get_size(const struct spa_handle_factory *factory, const struct spa_dict *params) { return sizeof(struct impl); } static int impl_init(const struct spa_handle_factory *factory, struct spa_handle *handle, const struct spa_dict *info, const struct spa_support *support, uint32_t n_support) { struct impl *this; const char *str; spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(handle != NULL, -EINVAL); handle->get_interface = impl_get_interface; handle->clear = impl_clear; this = (struct impl *) handle; this->notify.fd = -1; this->log = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log); alsa_log_topic_init(this->log); this->main_loop = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Loop); this->main_system = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_System); this->use_ucm = -1; if (this->main_loop == NULL) { spa_log_error(this->log, "a main-loop is needed"); return -EINVAL; } if (this->main_system == NULL) { spa_log_error(this->log, "a main-system is needed"); return -EINVAL; } spa_hook_list_init(&this->hooks); this->device.iface = SPA_INTERFACE_INIT( SPA_TYPE_INTERFACE_Device, SPA_VERSION_DEVICE, &impl_device, this); this->info = SPA_DEVICE_INFO_INIT(); this->info_all = SPA_DEVICE_CHANGE_MASK_FLAGS | SPA_DEVICE_CHANGE_MASK_PROPS; this->info.flags = 0; if (info) { if ((str = spa_dict_lookup(info, "alsa.use-acp")) != NULL) this->use_acp = spa_atob(str); else if ((str = spa_dict_lookup(info, "alsa.udev.expose-busy")) != NULL) this->expose_busy = spa_atob(str); else if ((str = spa_dict_lookup(info, "alsa.use-ucm")) != NULL) this->use_ucm = spa_atob(str) ? 1 : 0; } return 0; } static const struct spa_interface_info impl_interfaces[] = { {SPA_TYPE_INTERFACE_Device,}, }; static int impl_enum_interface_info(const struct spa_handle_factory *factory, const struct spa_interface_info **info, uint32_t *index) { spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(info != NULL, -EINVAL); spa_return_val_if_fail(index != NULL, -EINVAL); if (*index >= SPA_N_ELEMENTS(impl_interfaces)) return 0; *info = &impl_interfaces[(*index)++]; return 1; } const struct spa_handle_factory spa_alsa_udev_factory = { SPA_VERSION_HANDLE_FACTORY, SPA_NAME_API_ALSA_ENUM_UDEV, NULL, impl_get_size, impl_init, impl_enum_interface_info, }; pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/alsa.c000066400000000000000000000034101511204443500247440ustar00rootroot00000000000000/* Spa ALSA support */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include "config.h" #include #include #include #include "alsa.h" extern const struct spa_handle_factory spa_alsa_source_factory; extern const struct spa_handle_factory spa_alsa_sink_factory; extern const struct spa_handle_factory spa_alsa_udev_factory; extern const struct spa_handle_factory spa_alsa_pcm_device_factory; extern const struct spa_handle_factory spa_alsa_seq_bridge_factory; extern const struct spa_handle_factory spa_alsa_acp_device_factory; #ifdef HAVE_ALSA_COMPRESS_OFFLOAD extern const struct spa_handle_factory spa_alsa_compress_offload_sink_factory; extern const struct spa_handle_factory spa_alsa_compress_offload_device_factory; #endif SPA_LOG_TOPIC_DEFINE(alsa_log_topic, "spa.alsa"); SPA_LOG_TOPIC_ENUM_DEFINE_REGISTERED; SPA_EXPORT int spa_handle_factory_enum(const struct spa_handle_factory **factory, uint32_t *index) { spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(index != NULL, -EINVAL); switch (*index) { case 0: *factory = &spa_alsa_source_factory; break; case 1: *factory = &spa_alsa_sink_factory; break; case 2: #ifdef HAVE_LIBUDEV *factory = &spa_alsa_udev_factory; break; #else (*index)++; SPA_FALLTHROUGH; #endif case 3: *factory = &spa_alsa_pcm_device_factory; break; case 4: *factory = &spa_alsa_seq_bridge_factory; break; case 5: *factory = &spa_alsa_acp_device_factory; break; #ifdef HAVE_ALSA_COMPRESS_OFFLOAD case 6: *factory = &spa_alsa_compress_offload_sink_factory; break; case 7: *factory = &spa_alsa_compress_offload_device_factory; break; #endif default: return 0; } (*index)++; return 1; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/alsa.h000066400000000000000000000006711511204443500247570ustar00rootroot00000000000000/* Spa ALSA Source */ /* SPDX-FileCopyrightText: Copyright © 2021 Red Hat, Inc. */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_ALSA_H #define SPA_ALSA_H #include #undef SPA_LOG_TOPIC_DEFAULT #define SPA_LOG_TOPIC_DEFAULT &alsa_log_topic extern struct spa_log_topic alsa_log_topic; static inline void alsa_log_topic_init(struct spa_log *log) { spa_log_topic_init(log, &alsa_log_topic); } #endif /* SPA_ALSA_H */ compress-offload-api-util.c000066400000000000000000000021021511204443500307270ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa#include #include #include "compress-offload-api.h" #include "compress-offload-api-util.h" int get_compress_offload_device_direction(int card_nr, int device_nr, struct spa_log *log, enum spa_compress_offload_direction *direction) { int ret = 0; struct compress_offload_api_context *device_context; const struct snd_compr_caps *compr_caps; device_context = compress_offload_api_open(card_nr, device_nr, log); if (device_context == NULL) return -errno; compr_caps = compress_offload_api_get_caps(device_context); switch (compr_caps->direction) { case SND_COMPRESS_PLAYBACK: *direction = SPA_COMPRESS_OFFLOAD_DIRECTION_PLAYBACK; break; case SND_COMPRESS_CAPTURE: *direction = SPA_COMPRESS_OFFLOAD_DIRECTION_CAPTURE; break; default: spa_log_error(log, "card nr %d device nr %d: unknown direction %#" PRIx32, card_nr, device_nr, (uint32_t)(compr_caps->direction)); ret = -EINVAL; } compress_offload_api_close(device_context); return ret; } compress-offload-api-util.h000066400000000000000000000024411511204443500307420ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa#ifndef SPA_ALSA_COMPRESS_OFFLOAD_DEVICE_UTIL_H #define SPA_ALSA_COMPRESS_OFFLOAD_DEVICE_UTIL_H #include #if defined(__GNUC__) && __GNUC__ >= 4 #define COMPR_API_PRIVATE __attribute__((visibility("hidden"))) #elif defined(__SUNPRO_C) && (__SUNPRO_C >= 0x590) #define COMPR_API_PRIVATE __attribute__((visibility("hidden"))) #elif defined(__SUNPRO_C) && (__SUNPRO_C >= 0x550) #define COMPR_API_PRIVATE __hidden #else #define COMPR_API_PRIVATE #endif enum spa_compress_offload_direction { SPA_COMPRESS_OFFLOAD_DIRECTION_PLAYBACK, SPA_COMPRESS_OFFLOAD_DIRECTION_CAPTURE }; /* This exists for situations where both the direction of the compress-offload * device and the functions from asoundlib.h are needed. It is not possible to * include asoundlib.h and the compress-offload headers in the same C file, * since these headers contain conflicting declarations. Provide this direction * check function to keep the compress-offload headers encapsulated. */ COMPR_API_PRIVATE int get_compress_offload_device_direction(int card_nr, int device_nr, struct spa_log *log, enum spa_compress_offload_direction *direction); #endif /* SPA_ALSA_COMPRESS_OFFLOAD_DEVICE_UTIL_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/compress-offload-api.c000066400000000000000000000153041511204443500300430ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include #include "compress-offload-api.h" struct compress_offload_api_context { int fd; struct snd_compr_caps caps; struct spa_log *log; uint32_t fragment_size; uint32_t num_fragments; }; struct compress_offload_api_context* compress_offload_api_open(int card_nr, int device_nr, struct spa_log *log) { struct compress_offload_api_context *context; char fn[256]; assert(card_nr >= 0); assert(device_nr >= 0); assert(log != NULL); context = calloc(1, sizeof(struct compress_offload_api_context)); if (context == NULL) { errno = ENOMEM; return NULL; } context->log = log; snprintf(fn, sizeof(fn), "/dev/snd/comprC%uD%u", card_nr, device_nr); context->fd = open(fn, O_WRONLY); if (context->fd < 0) { spa_log_error(context->log, "could not open device \"%s\": %s (%d)", fn, strerror(errno), errno); goto error; } if (ioctl(context->fd, SNDRV_COMPRESS_GET_CAPS, &(context->caps)) != 0) { spa_log_error(context->log, "could not get device caps: %s (%d)", strerror(errno), errno); goto error; } return context; error: compress_offload_api_close(context); if (errno == 0) errno = EIO; return NULL; } void compress_offload_api_close(struct compress_offload_api_context *context) { if (context == NULL) return; if (context->fd > 0) close(context->fd); free(context); } int compress_offload_api_get_fd(struct compress_offload_api_context *context) { assert(context != NULL); return context->fd; } int compress_offload_api_set_params(struct compress_offload_api_context *context, struct snd_codec *codec, uint32_t fragment_size, uint32_t num_fragments) { struct snd_compr_params params; assert(context != NULL); assert(codec != NULL); assert( (fragment_size == 0) || ((fragment_size >= context->caps.min_fragment_size) && (fragment_size <= context->caps.max_fragment_size)) ); assert( (num_fragments == 0) || ((num_fragments >= context->caps.min_fragments) && (fragment_size <= context->caps.max_fragments)) ); context->fragment_size = (fragment_size != 0) ? fragment_size : context->caps.min_fragment_size; context->num_fragments = (num_fragments != 0) ? num_fragments : context->caps.max_fragments; memset(¶ms, 0, sizeof(params)); params.buffer.fragment_size = context->fragment_size; params.buffer.fragments = context->num_fragments; memcpy(&(params.codec), codec, sizeof(struct snd_codec)); if (ioctl(context->fd, SNDRV_COMPRESS_SET_PARAMS, ¶ms) != 0) { spa_log_error(context->log, "could not set params: %s (%d)", strerror(errno), errno); return -errno; } return 0; } void compress_offload_api_get_fragment_config(struct compress_offload_api_context *context, uint32_t *fragment_size, uint32_t *num_fragments) { assert(context != NULL); assert(fragment_size != NULL); assert(num_fragments != NULL); *fragment_size = context->fragment_size; *num_fragments = context->num_fragments; } const struct snd_compr_caps * compress_offload_api_get_caps(struct compress_offload_api_context *context) { assert(context != NULL); return &(context->caps); } int compress_offload_api_get_codec_caps(struct compress_offload_api_context *context, uint32_t codec_id, struct snd_compr_codec_caps *codec_caps) { assert(context != NULL); assert(codec_id < SND_AUDIOCODEC_MAX); assert(codec_caps != NULL); memset(codec_caps, 0, sizeof(struct snd_compr_codec_caps)); codec_caps->codec = codec_id; if (ioctl(context->fd, SNDRV_COMPRESS_GET_CODEC_CAPS, codec_caps) != 0) { spa_log_error(context->log, "could not get caps for codec with ID %#08x: %s (%d)", codec_id, strerror(errno), errno); return -errno; } return 0; } bool compress_offload_api_supports_codec(struct compress_offload_api_context *context, uint32_t codec_id) { uint32_t codec_index; assert(context != NULL); assert(codec_id < SND_AUDIOCODEC_MAX); for (codec_index = 0; codec_index < context->caps.num_codecs; ++codec_index) { if (context->caps.codecs[codec_index] == codec_id) return true; } return false; } #define RUN_SIMPLE_COMMAND(CONTEXT, CMD, CMD_NAME) \ { \ assert((CONTEXT) != NULL); \ assert((CMD_NAME) != NULL); \ \ if (ioctl((CONTEXT)->fd, (CMD)) < 0) { \ spa_log_error((CONTEXT)->log, "could not %s device: %s (%d)", (CMD_NAME), strerror(errno), errno); \ return -errno; \ } \ \ return 0; \ } int compress_offload_api_start(struct compress_offload_api_context *context) { RUN_SIMPLE_COMMAND(context, SNDRV_COMPRESS_START, "start"); } int compress_offload_api_stop(struct compress_offload_api_context *context) { RUN_SIMPLE_COMMAND(context, SNDRV_COMPRESS_STOP, "stop"); } int compress_offload_api_pause(struct compress_offload_api_context *context) { RUN_SIMPLE_COMMAND(context, SNDRV_COMPRESS_PAUSE, "pause"); } int compress_offload_api_resume(struct compress_offload_api_context *context) { RUN_SIMPLE_COMMAND(context, SNDRV_COMPRESS_RESUME, "resume"); } int compress_offload_api_drain(struct compress_offload_api_context *context) { RUN_SIMPLE_COMMAND(context, SNDRV_COMPRESS_DRAIN, "drain"); } int compress_offload_api_get_timestamp(struct compress_offload_api_context *context, struct snd_compr_tstamp *timestamp) { assert(context != NULL); assert(timestamp != NULL); if (ioctl(context->fd, SNDRV_COMPRESS_TSTAMP, timestamp) < 0) { spa_log_error(context->log, "could not get timestamp device: %s (%d)", strerror(errno), errno); return -errno; } return 0; } int compress_offload_api_get_available_space(struct compress_offload_api_context *context, struct snd_compr_avail *available_space) { assert(context != NULL); assert(available_space != NULL); if (ioctl(context->fd, SNDRV_COMPRESS_AVAIL, available_space) < 0) { spa_log_error(context->log, "could not get available space from device: %s (%d)", strerror(errno), errno); return -errno; } return 0; } int compress_offload_api_write(struct compress_offload_api_context *context, const void *data, size_t size) { int num_bytes_written; assert(context != NULL); assert(data != NULL); num_bytes_written = write(context->fd, data, size); if (num_bytes_written < 0) { switch (errno) { case EBADFD: /* EBADFD indicates that the device is paused and thus is not an error. */ break; default: spa_log_error(context->log, "could not write %zu byte(s): %s (%d)", size, strerror(errno), errno); break; } return -errno; } return num_bytes_written; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/compress-offload-api.h000066400000000000000000000071721511204443500300540ustar00rootroot00000000000000#ifndef COMPRESS_OFFLOAD_API_H #define COMPRESS_OFFLOAD_API_H #include #include #include #include #include #include "compress-offload-api-util.h" struct compress_offload_api_context; /* This is a simple encapsulation of the ALSA Compress-Offload API * and its ioctl calls. It is intentionally not using any PipeWire * or SPA headers to allow for porting it or extracting it as its * own library in the future if needed. It functions as an alternative * to tinycompress, and was written, because tinycompress lacks * critical functionality (it does not expose important device caps) * and adds little value in this particular use case. * * Encapsulating the ioctls behind this API also allows for using * different backends. This might be interesting in the future for * testing purposes; for example, an alternative backend could exist * that emulates a compress-offload device by decoding with FFmpeg. * This would be useful for debugging compressed audio related issues * in PipeWire on the PC - an important advantage, since getting to * actual compress-offload hardware can sometimes be difficult. */ COMPR_API_PRIVATE struct compress_offload_api_context* compress_offload_api_open(int card_nr, int device_nr, struct spa_log *log); COMPR_API_PRIVATE void compress_offload_api_close(struct compress_offload_api_context *context); COMPR_API_PRIVATE int compress_offload_api_get_fd(struct compress_offload_api_context *context); COMPR_API_PRIVATE int compress_offload_api_set_params(struct compress_offload_api_context *context, struct snd_codec *codec, uint32_t fragment_size, uint32_t num_fragments); COMPR_API_PRIVATE void compress_offload_api_get_fragment_config(struct compress_offload_api_context *context, uint32_t *fragment_size, uint32_t *num_fragments); COMPR_API_PRIVATE const struct snd_compr_caps * compress_offload_api_get_caps(struct compress_offload_api_context *context); COMPR_API_PRIVATE int compress_offload_api_get_codec_caps(struct compress_offload_api_context *context, uint32_t codec_id, struct snd_compr_codec_caps *codec_caps); COMPR_API_PRIVATE bool compress_offload_api_supports_codec(struct compress_offload_api_context *context, uint32_t codec_id); COMPR_API_PRIVATE int compress_offload_api_start(struct compress_offload_api_context *context); COMPR_API_PRIVATE int compress_offload_api_stop(struct compress_offload_api_context *context); COMPR_API_PRIVATE int compress_offload_api_pause(struct compress_offload_api_context *context); COMPR_API_PRIVATE int compress_offload_api_resume(struct compress_offload_api_context *context); COMPR_API_PRIVATE int compress_offload_api_drain(struct compress_offload_api_context *context); COMPR_API_PRIVATE int compress_offload_api_get_timestamp(struct compress_offload_api_context *context, struct snd_compr_tstamp *timestamp); COMPR_API_PRIVATE int compress_offload_api_get_available_space(struct compress_offload_api_context *context, struct snd_compr_avail *available_space); COMPR_API_PRIVATE int compress_offload_api_write(struct compress_offload_api_context *context, const void *data, size_t size); #endif /* COMPRESS_OFFLOAD_API_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/meson.build000066400000000000000000000030721511204443500260260ustar00rootroot00000000000000subdir('acp') subdir('mixer') spa_alsa_dependencies = [ spa_dep, alsa_dep, mathlib, epoll_shim_dep, libinotify_dep ] spa_alsa_sources = ['alsa.c', 'alsa.h', 'alsa-acp-device.c', 'alsa-pcm-device.c', 'alsa-pcm-sink.c', 'alsa-pcm-source.c', 'alsa-pcm.c', 'alsa-seq-bridge.c', 'alsa-seq.c'] if libudev_dep.found() spa_alsa_sources += [ 'alsa-udev.c' ] spa_alsa_dependencies += [ libudev_dep ] endif if compress_offload_option.allowed() spa_alsa_sources += ['alsa-compress-offload-sink.c', 'alsa-compress-offload-device.c', 'compress-offload-api-util.c', 'compress-offload-api.c'] endif spa_alsa = shared_library( 'spa-alsa', [ spa_alsa_sources ], c_args : acp_c_args, include_directories : [configinc], dependencies : spa_alsa_dependencies, link_with : [ acp_lib ], install : true, install_dir : spa_plugindir / 'alsa' ) alsa_udevrules = [ '90-pipewire-alsa.rules', ] executable('spa-acp-tool', [ 'acp-tool.c' ], c_args : acp_c_args, dependencies : [ spa_dep, alsa_dep, mathlib, acp_dep ], install : true, ) executable('test-timer', [ 'test-timer.c' ], dependencies : [ spa_dep, alsa_dep, mathlib, epoll_shim_dep ], install : false, ) executable('test-hw-params', [ 'test-hw-params.c' ], dependencies : [ spa_dep, alsa_dep, mathlib ], install : false, ) if libudev_dep.found() install_data(alsa_udevrules, install_dir : udevrulesdir, ) endif pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/mixer/000077500000000000000000000000001511204443500250065ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/mixer/meson.build000066400000000000000000000001641511204443500271510ustar00rootroot00000000000000install_subdir('paths', install_dir : alsadatadir ) install_subdir('profile-sets', install_dir : alsadatadir ) pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/mixer/paths/000077500000000000000000000000001511204443500261255ustar00rootroot00000000000000analog-input-aux.conf000066400000000000000000000026071511204443500321130ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/mixer/paths# This file is part of PulseAudio. # # PulseAudio is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as # published by the Free Software Foundation; either version 2.1 of the # License, or (at your option) any later version. # # PulseAudio is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with PulseAudio; if not, see . ; For devices where an 'Aux' element exists ; ; See analog-output.conf.common for an explanation on the directives [General] priority = 80 description-key = analog-input [Element Capture] switch = mute volume = merge override-map.1 = all override-map.2 = all-left,all-right [Element Mic] switch = off volume = off [Element Internal Mic] switch = off volume = off [Element Line] switch = off volume = off [Element Aux] required = any switch = mute volume = merge override-map.1 = all override-map.2 = all-left,all-right [Element Video] switch = off volume = off [Element Mic/Line] switch = off volume = off [Element TV Tuner] switch = off volume = off [Element FM] switch = off volume = off .include analog-input.conf.common analog-input-dock-mic.conf000066400000000000000000000042111511204443500327750ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/mixer/paths# This file is part of PulseAudio. # # PulseAudio is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as # published by the Free Software Foundation; either version 2.1 of the # License, or (at your option) any later version. # # PulseAudio is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with PulseAudio; if not, see . ; For devices where a 'Dock Mic' or 'Dock Mic Boost' element exists ; ; See analog-output.conf.common for an explanation on the directives [General] priority = 78 description-key = analog-input-microphone-dock [Jack Dock Mic] required-any = any [Jack Dock Mic Phantom] state.plugged = unknown state.unplugged = unknown required-any = any [Element Capture] switch = mute volume = merge override-map.1 = all override-map.2 = all-left,all-right [Element Dock Mic Boost] required-any = any switch = select volume = merge override-map.1 = all override-map.2 = all-left,all-right [Option Dock Mic Boost:on] name = input-boost-on [Option Dock Mic Boost:off] name = input-boost-off [Element Dock Mic] required-any = any switch = mute volume = merge override-map.1 = all override-map.2 = all-left,all-right [Element Input Source] enumeration = select [Option Input Source:Dock Mic] name = analog-input-microphone-dock required-any = any [Element Capture Source] enumeration = select [Option Capture Source:Dock Mic] name = analog-input-microphone-dock required-any = any [Element Mic] switch = off volume = off [Element Internal Mic] switch = off volume = off [Element Front Mic] switch = off volume = off [Element Rear Mic] switch = off volume = off [Element Mic Boost] switch = off volume = off [Element Internal Mic Boost] switch = off volume = off [Element Front Mic Boost] switch = off volume = off [Element Rear Mic Boost] switch = off volume = off .include analog-input-mic.conf.common analog-input-fm.conf000066400000000000000000000026141511204443500317160ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/mixer/paths# This file is part of PulseAudio. # # PulseAudio is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as # published by the Free Software Foundation; either version 2.1 of the # License, or (at your option) any later version. # # PulseAudio is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with PulseAudio; if not, see . ; For devices where an 'FM' element exists ; ; See analog-output.conf.common for an explanation on the directives [General] priority = 70 description-key = analog-input-radio [Element Capture] switch = mute volume = merge override-map.1 = all override-map.2 = all-left,all-right [Element Mic] switch = off volume = off [Element Internal Mic] switch = off volume = off [Element Line] switch = off volume = off [Element Aux] switch = off volume = off [Element Video] switch = off volume = off [Element Mic/Line] switch = off volume = off [Element TV Tuner] switch = off volume = off [Element FM] required = any switch = mute volume = merge override-map.1 = all override-map.2 = all-left,all-right .include analog-input.conf.common analog-input-front-mic.conf000066400000000000000000000042241511204443500332110ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/mixer/paths# This file is part of PulseAudio. # # PulseAudio is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as # published by the Free Software Foundation; either version 2.1 of the # License, or (at your option) any later version. # # PulseAudio is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with PulseAudio; if not, see . ; For devices where a 'Front Mic' or 'Front Mic Boost' element exists ; ; See analog-output.conf.common for an explanation on the directives [General] priority = 85 description-key = analog-input-microphone-front [Jack Front Mic] required-any = any [Jack Front Mic Phantom] required-any = any state.plugged = unknown state.unplugged = unknown [Element Capture] switch = mute volume = merge override-map.1 = all override-map.2 = all-left,all-right [Element Front Mic Boost] required-any = any switch = select volume = merge override-map.1 = all override-map.2 = all-left,all-right [Option Front Mic Boost:on] name = input-boost-on [Option Front Mic Boost:off] name = input-boost-off [Element Front Mic] required-any = any switch = mute volume = merge override-map.1 = all override-map.2 = all-left,all-right [Element Input Source] enumeration = select [Option Input Source:Front Mic] name = analog-input-microphone-front required-any = any [Element Capture Source] enumeration = select [Option Capture Source:Front Mic] name = analog-input-microphone-front required-any = any [Element Mic] switch = off volume = off [Element Internal Mic] switch = off volume = off [Element Rear Mic] switch = off volume = off [Element Dock Mic] switch = off volume = off [Element Mic Boost] switch = off volume = off [Element Dock Mic Boost] switch = off volume = off [Element Internal Mic Boost] switch = off volume = off [Element Rear Mic Boost] switch = off volume = off .include analog-input-mic.conf.common analog-input-headphone-mic.conf000066400000000000000000000043721511204443500340200ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/mixer/paths# This file is part of PulseAudio. # # PulseAudio is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as # published by the Free Software Foundation; either version 2.1 of the # License, or (at your option) any later version. # # PulseAudio is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with PulseAudio; if not, see . ; For some ASUS netbooks that have one jack that can be either a Headphone ; *or* a mic. This path will be active only when it is used as a mic. ; ; See analog-output.conf.common for an explanation on the directives [General] priority = 87 description-key = analog-input-microphone [Jack Headphone Mic] required-any = any state.plugged = unknown [Element Capture] switch = mute volume = merge override-map.1 = all override-map.2 = all-left,all-right [Element Headphone Mic Boost] required-any = any switch = select volume = merge override-map.1 = all override-map.2 = all-left,all-right [Element Headphone Mic] required-any = any switch = mute volume = merge override-map.1 = all override-map.2 = all-left,all-right [Element Input Source] enumeration = select [Option Input Source:Headphone Mic] name = analog-input-microphone required-any = any [Element Capture Source] enumeration = select [Option Capture Source:Headphone Mic] name = analog-input-microphone required-any = any ; Make sure the internal speakers are not auto-muted when you plug a mic in [Element Auto-Mute Mode] enumeration = select [Option Auto-Mute Mode:Disabled] name = analog-input-microphone [Element Front Mic] switch = off volume = off [Element Internal Mic] switch = off volume = off [Element Rear Mic] switch = off volume = off [Element Dock Mic] switch = off volume = off [Element Dock Mic Boost] switch = off volume = off [Element Internal Mic Boost] switch = off volume = off [Element Front Mic Boost] switch = off volume = off [Element Rear Mic Boost] switch = off volume = off .include analog-input-mic.conf.common analog-input-headset-mic.conf000066400000000000000000000044701511204443500335010ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/mixer/paths# This file is part of PulseAudio. # # PulseAudio is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as # published by the Free Software Foundation; either version 2.1 of the # License, or (at your option) any later version. # # PulseAudio is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with PulseAudio; if not, see . ; For devices where a 'Headset Mic' or 'Headset Mic Boost' element exists ; ; See analog-output.conf.common for an explanation on the directives [General] priority = 88 description-key = analog-input-microphone-headset [Jack Headset Mic] required-any = any [Jack Headset Mic Phantom] state.plugged = unknown state.unplugged = unknown required-any = any [Jack Headphone] state.plugged = unknown [Jack Front Headphone] state.plugged = unknown [Jack Headphone Mic] state.plugged = unknown [Element Capture] switch = mute volume = merge override-map.1 = all override-map.2 = all-left,all-right [Element Headset Mic Boost] required-any = any switch = select volume = merge override-map.1 = all override-map.2 = all-left,all-right [Element Headset Mic] required-any = any switch = mute volume = merge override-map.1 = all override-map.2 = all-left,all-right [Element Headset] required-any = any switch = mute volume = merge override-map.1 = all override-map.2 = all-left,all-right [Element Input Source] enumeration = select [Option Input Source:Headset Mic] name = Headset Microphone required-any = any [Element Capture Source] enumeration = select [Option Capture Source:Headset Mic] name = Headset Microphone required-any = any [Element Front Mic] switch = off volume = off [Element Internal Mic] switch = off volume = off [Element Rear Mic] switch = off volume = off [Element Dock Mic] switch = off volume = off [Element Dock Mic Boost] switch = off volume = off [Element Internal Mic Boost] switch = off volume = off [Element Front Mic Boost] switch = off volume = off [Element Rear Mic Boost] switch = off volume = off .include analog-input-mic.conf.common analog-input-internal-mic-always.conf000066400000000000000000000053551511204443500352010ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/mixer/paths# This file is part of PulseAudio. # # PulseAudio is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as # published by the Free Software Foundation; either version 2.1 of the # License, or (at your option) any later version. # # PulseAudio is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with PulseAudio; if not, see . ; For devices where a 'Internal Mic' or 'Internal Mic Boost' element exists ; 'Int Mic' and 'Int Mic Boost' are for compatibility with kernels < 2.6.38 ; ; See analog-output.conf.common for an explanation on the directives [General] priority = 89 description-key = analog-input-microphone-internal [Jack Mic] state.plugged = no state.unplugged = unknown [Jack Dock Mic] state.plugged = no state.unplugged = unknown [Jack Front Mic] state.plugged = no state.unplugged = unknown [Jack Rear Mic] state.plugged = no state.unplugged = unknown [Element Capture] switch = mute volume = merge override-map.1 = all override-map.2 = all-left,all-right [Element Internal Mic Boost] switch = select volume = merge override-map.1 = all override-map.2 = all-left,all-right [Option Internal Mic Boost:on] name = input-boost-on [Option Internal Mic Boost:off] name = input-boost-off [Element Int Mic Boost] switch = select volume = merge override-map.1 = all override-map.2 = all-left,all-right [Option Int Mic Boost:on] name = input-boost-on [Option Int Mic Boost:off] name = input-boost-off [Element Internal Mic] switch = mute volume = merge override-map.1 = all override-map.2 = all-left,all-right [Element Int Mic] switch = mute volume = merge override-map.1 = all override-map.2 = all-left,all-right [Element Input Source] enumeration = select [Option Input Source:Internal Mic] name = analog-input-microphone-internal [Option Input Source:Int Mic] name = analog-input-microphone-internal [Element Capture Source] enumeration = select [Option Capture Source:Internal Mic] name = analog-input-microphone-internal [Option Capture Source:Int Mic] name = analog-input-microphone-internal [Element Mic] switch = off volume = off [Element Dock Mic] switch = off volume = off [Element Front Mic] switch = off volume = off [Element Rear Mic] switch = off volume = off [Element Mic Boost] switch = off volume = off [Element Dock Mic Boost] switch = off volume = off [Element Front Mic Boost] switch = off volume = off [Element Rear Mic Boost] switch = off volume = off .include analog-input-mic.conf.common analog-input-internal-mic.conf000066400000000000000000000061231511204443500336750ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/mixer/paths# This file is part of PulseAudio. # # PulseAudio is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as # published by the Free Software Foundation; either version 2.1 of the # License, or (at your option) any later version. # # PulseAudio is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with PulseAudio; if not, see . ; For devices where a 'Internal Mic' or 'Internal Mic Boost' element exists ; 'Int Mic' and 'Int Mic Boost' are for compatibility with kernels < 2.6.38 ; ; See analog-output.conf.common for an explanation on the directives [General] priority = 89 description-key = analog-input-microphone-internal [Jack Mic] state.plugged = no state.unplugged = unknown [Jack Dock Mic] state.plugged = no state.unplugged = unknown [Jack Front Mic] state.plugged = no state.unplugged = unknown [Jack Rear Mic] state.plugged = no state.unplugged = unknown [Jack Internal Mic Phantom] state.plugged = unknown state.unplugged = unknown required-any = any [Element Capture] switch = mute volume = merge override-map.1 = all override-map.2 = all-left,all-right [Element Internal Mic Boost] required-any = any switch = select volume = merge override-map.1 = all override-map.2 = all-left,all-right [Option Internal Mic Boost:on] name = input-boost-on [Option Internal Mic Boost:off] name = input-boost-off [Element Int Mic Boost] required-any = any switch = select volume = merge override-map.1 = all override-map.2 = all-left,all-right [Option Int Mic Boost:on] name = input-boost-on [Option Int Mic Boost:off] name = input-boost-off [Element Internal Mic] required-any = any switch = mute volume = merge override-map.1 = all override-map.2 = all-left,all-right [Element Int Mic] required-any = any switch = mute volume = merge override-map.1 = all override-map.2 = all-left,all-right [Element Input Source] enumeration = select [Option Input Source:Internal Mic] name = analog-input-microphone-internal required-any = any [Option Input Source:Int Mic] name = analog-input-microphone-internal required-any = any [Element Capture Source] enumeration = select [Option Capture Source:Internal Mic] name = analog-input-microphone-internal required-any = any [Option Capture Source:Int Mic] name = analog-input-microphone-internal required-any = any [Element Mic] switch = off volume = off [Element Dock Mic] switch = off volume = off [Element Front Mic] switch = off volume = off [Element Rear Mic] switch = off volume = off [Element Headphone Mic] switch = off volume = off [Element Headphone Mic Boost] switch = off volume = off [Element Mic Boost] switch = off volume = off [Element Dock Mic Boost] switch = off volume = off [Element Front Mic Boost] switch = off volume = off [Element Rear Mic Boost] switch = off volume = off .include analog-input-mic.conf.common analog-input-linein.conf000066400000000000000000000050611511204443500325710ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/mixer/paths# This file is part of PulseAudio. # # PulseAudio is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as # published by the Free Software Foundation; either version 2.1 of the # License, or (at your option) any later version. # # PulseAudio is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with PulseAudio; if not, see . ; For devices where a 'Line' element exists ; ; See analog-output.conf.common for an explanation on the directives [General] priority = 81 [Jack Line] required-any = any [Jack Line Phantom] required-any = any state.plugged = unknown state.unplugged = unknown [Jack Line - Input] required-any = any [Element Capture] switch = mute volume = merge override-map.1 = all override-map.2 = all-left,all-right [Element Line Boost] required-any = any switch = mute volume = merge override-map.1 = all override-map.2 = all-left,all-right [Element Line] required-any = any switch = mute volume = merge override-map.1 = all override-map.2 = all-left,all-right [Element Input Source] enumeration = select [Option Input Source:Line] name = analog-input-linein required-any = any [Element Capture Source] enumeration = select [Option Capture Source:Line] name = analog-input-linein required-any = any [Element PCM Capture Source] enumeration = select [Option PCM Capture Source:Line] name = analog-input-linein required-any = any [Option PCM Capture Source:Line In] name = analog-input-linein required-any = any [Element Mic] switch = off volume = off [Element Dock Mic] switch = off volume = off [Element Internal Mic] switch = off volume = off [Element Front Mic] switch = off volume = off [Element Rear Mic] switch = off volume = off [Element Mic Boost] switch = off volume = off [Element Dock Mic Boost] switch = off volume = off [Element Internal Mic Boost] switch = off volume = off [Element Front Mic Boost] switch = off volume = off [Element Rear Mic Boost] switch = off volume = off [Element Aux] switch = off volume = off [Element Video] switch = off volume = off [Element Mic/Line] switch = off volume = off [Element TV Tuner] switch = off volume = off [Element FM] switch = off volume = off [Element Mic Jack Mode] enumeration = select [Option Mic Jack Mode:Line In] priority = 19 name = input-linein analog-input-mic-line.conf000066400000000000000000000026611511204443500330130ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/mixer/paths# This file is part of PulseAudio. # # PulseAudio is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as # published by the Free Software Foundation; either version 2.1 of the # License, or (at your option) any later version. # # PulseAudio is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with PulseAudio; if not, see . ; For devices where a 'Mic/Line' element exists ; ; See analog-output.conf.common for an explanation on the directives [General] priority = 85 description-key = analog-input [Element Capture] switch = mute volume = merge override-map.1 = all override-map.2 = all-left,all-right [Element Mic] switch = off volume = off [Element Internal Mic] switch = off volume = off [Element Line] switch = off volume = off [Element Aux] switch = off volume = off [Element Video] switch = off volume = off [Element Mic/Line] required = any switch = mute volume = merge override-map.1 = all override-map.2 = all-left,all-right [Element TV Tuner] switch = off volume = off [Element FM] switch = off volume = off .include analog-input.conf.common .include analog-input-mic.conf.common analog-input-mic.conf000066400000000000000000000053551511204443500320710ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/mixer/paths# This file is part of PulseAudio. # # PulseAudio is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as # published by the Free Software Foundation; either version 2.1 of the # License, or (at your option) any later version. # # PulseAudio is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with PulseAudio; if not, see . ; For devices where a 'Mic' or 'Mic Boost' element exists ; ; See analog-output.conf.common for an explanation on the directives [General] priority = 87 description-key = analog-input-microphone [Jack Mic] required-any = any [Jack Mic Phantom] required-any = any state.plugged = unknown state.unplugged = unknown [Jack Mic - Input] required-any = any [Element Capture] switch = mute volume = merge override-map.1 = all override-map.2 = all-left,all-right [Element Mic Boost] required-any = any switch = select volume = merge override-map.1 = all override-map.2 = all-left,all-right [Option Mic Boost:on] name = input-boost-on [Option Mic Boost:off] name = input-boost-off [Element Mic] required-any = any switch = mute volume = merge override-map.1 = all override-map.2 = all-left,all-right [Element Input Source] enumeration = select [Option Input Source:Mic] name = analog-input-microphone required-any = any [Element Capture Source] enumeration = select [Option Capture Source:Mic] name = analog-input-microphone required-any = any [Element PCM Capture Source] enumeration = select [Option PCM Capture Source:Mic] name = analog-input-microphone required-any = any [Option PCM Capture Source:Mic-In/Mic Array] name = analog-input-microphone required-any = any ;;; Some AC'97s have "Mic Select" and "Mic Boost (+20dB)" [Element Mic Select] enumeration = select [Option Mic Select:Mic1] name = input-microphone priority = 20 [Option Mic Select:Mic2] name = input-microphone priority = 19 [Element Mic Boost (+20dB)] switch = select volume = merge [Option Mic Boost (+20dB):on] name = input-boost-on [Option Mic Boost (+20dB):off] name = input-boost-off [Element Front Mic] switch = off volume = off [Element Internal Mic] switch = off volume = off [Element Rear Mic] switch = off volume = off [Element Dock Mic] switch = off volume = off [Element Dock Mic Boost] switch = off volume = off [Element Internal Mic Boost] switch = off volume = off [Element Front Mic Boost] switch = off volume = off [Element Rear Mic Boost] switch = off volume = off .include analog-input-mic.conf.common analog-input-mic.conf.common000066400000000000000000000024621511204443500333540ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/mixer/paths# This file is part of PulseAudio. # # PulseAudio is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as # published by the Free Software Foundation; either version 2.1 of the # License, or (at your option) any later version. # # PulseAudio is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with PulseAudio; if not, see . ; Common element for all microphone inputs ; ; See analog-output.conf.common for an explanation on the directives [Properties] device.icon_name = audio-input-microphone [Element Line] switch = off volume = off [Element Line Boost] switch = off volume = off [Element Aux] switch = off volume = off [Element Video] switch = off volume = off [Element Mic/Line] switch = off volume = off [Element TV Tuner] switch = off volume = off [Element FM] switch = off volume = off [Element Inverted Internal Mic] switch = off volume = off [Element Mic Jack Mode] enumeration = select [Option Mic Jack Mode:Mic In] priority = 19 name = input-microphone analog-input-rear-mic.conf000066400000000000000000000042651511204443500330170ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/mixer/paths# This file is part of PulseAudio. # # PulseAudio is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as # published by the Free Software Foundation; either version 2.1 of the # License, or (at your option) any later version. # # PulseAudio is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with PulseAudio; if not, see . ; For devices where a 'Rear Mic' or 'Rear Mic Boost' element exists ; ; See analog-output.conf.common for an explanation on the directives [General] priority = 82 description-key = analog-input-microphone-rear [Jack Rear Mic] required-any = any [Jack Rear Mic - Input] required-any = any [Jack Rear Mic Phantom] required-any = any state.plugged = unknown state.unplugged = unknown [Element Capture] switch = mute volume = merge override-map.1 = all override-map.2 = all-left,all-right [Element Rear Mic Boost] required-any = any switch = select volume = merge override-map.1 = all override-map.2 = all-left,all-right [Option Rear Mic Boost:on] name = input-boost-on [Option Rear Mic Boost:off] name = input-boost-off [Element Rear Mic] required-any = any switch = mute volume = merge override-map.1 = all override-map.2 = all-left,all-right [Element Input Source] enumeration = select [Option Input Source:Rear Mic] name = analog-input-microphone-rear required-any = any [Element Capture Source] enumeration = select [Option Capture Source:Rear Mic] name = analog-input-microphone-rear required-any = any [Element Mic] switch = off volume = off [Element Internal Mic] switch = off volume = off [Element Front Mic] switch = off volume = off [Element Dock Mic] switch = off volume = off [Element Mic Boost] switch = off volume = off [Element Dock Mic Boost] switch = off volume = off [Element Internal Mic Boost] switch = off volume = off [Element Front Mic Boost] switch = off volume = off .include analog-input-mic.conf.common analog-input-tvtuner.conf000066400000000000000000000026211511204443500330210ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/mixer/paths# This file is part of PulseAudio. # # PulseAudio is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as # published by the Free Software Foundation; either version 2.1 of the # License, or (at your option) any later version. # # PulseAudio is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with PulseAudio; if not, see . ; For devices where a 'TV Tuner' element exists ; ; See analog-output.conf.common for an explanation on the directives [General] priority = 70 description-key = analog-input-video [Element Capture] switch = mute volume = merge override-map.1 = all override-map.2 = all-left,all-right [Element Mic] switch = off volume = off [Element Internal Mic] switch = off volume = off [Element Line] switch = off volume = off [Element Aux] switch = off volume = off [Element Video] switch = off volume = off [Element Mic/Line] switch = off volume = off [Element TV Tuner] required = any switch = mute volume = merge override-map.1 = all override-map.2 = all-left,all-right [Element FM] switch = off volume = off .include analog-input.conf.common analog-input-video.conf000066400000000000000000000025511511204443500324220ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/mixer/paths# This file is part of PulseAudio. # # PulseAudio is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as # published by the Free Software Foundation; either version 2.1 of the # License, or (at your option) any later version. # # PulseAudio is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with PulseAudio; if not, see . ; For devices where a 'Video' element exists ; ; See analog-output.conf.common for an explanation on the directives [General] priority = 70 [Element Capture] switch = mute volume = merge override-map.1 = all override-map.2 = all-left,all-right [Element Mic] switch = off volume = off [Element Internal Mic] switch = off volume = off [Element Line] switch = off volume = off [Element Aux] switch = off volume = off [Element Video] required = any switch = mute volume = merge override-map.1 = all override-map.2 = all-left,all-right [Element Mic/Line] switch = off volume = off [Element TV Tuner] switch = off volume = off [Element FM] switch = off volume = off .include analog-input.conf.common analog-input.conf000066400000000000000000000040101511204443500313060ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/mixer/paths# This file is part of PulseAudio. # # PulseAudio is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as # published by the Free Software Foundation; either version 2.1 of the # License, or (at your option) any later version. # # PulseAudio is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with PulseAudio; if not, see . ; A fallback for devices that lack separate Mic/Line/Aux/Video/TV ; Tuner/FM elements ; ; See analog-output.conf.common for an explanation on the directives [General] priority = 100 [Element Capture] switch = mute volume = merge override-map.1 = all override-map.2 = all-left,all-right [Element Mic] required-absent = any [Element Mic Boost] required-absent = any [Element Dock Mic] required-absent = any [Element Dock Mic Boost] required-absent = any [Element Front Mic] required-absent = any [Element Front Mic Boost] required-absent = any [Element Int Mic] required-absent = any [Element Int Mic Boost] required-absent = any [Element Internal Mic] required-absent = any [Element Internal Mic Boost] required-absent = any [Element Rear Mic] required-absent = any [Element Rear Mic Boost] required-absent = any [Element Headset] required-absent = any [Element Headset Mic] required-absent = any [Element Headset Mic Boost] required-absent = any [Element Headphone Mic] required-absent = any [Element Headphone Mic Boost] required-absent = any [Element Line] required-absent = any [Element Line Boost] required-absent = any [Element Aux] required-absent = any [Element Video] required-absent = any [Element Mic/Line] required-absent = any [Element TV Tuner] required-absent = any [Element FM] required-absent = any .include analog-input.conf.common analog-input.conf.common000066400000000000000000000132111511204443500326000ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/mixer/paths# This file is part of PulseAudio. # # PulseAudio is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as # published by the Free Software Foundation; either version 2.1 of the # License, or (at your option) any later version. # # PulseAudio is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with PulseAudio; if not, see . ; Mixer path for PulseAudio's ALSA backend, common elements for all ; input paths. If multiple options by the same id are discovered they ; will be suffixed with a number to distinguish them, in the same ; order they appear here. ; ; Source selection should use the following names: ; ; input -- If we don't know the exact kind of input ; input-microphone ; input-microphone-internal ; input-microphone-external ; input-linein ; input-video ; input-radio ; input-docking-microphone ; input-docking-linein ; input-docking ; ; We explicitly don't want to wrap the following sources: ; ; CD ; Synth/MIDI ; Phone ; Mix ; Digital/SPDIF ; Master ; PC Speaker ; ; See analog-output.conf.common for an explanation on the directives ;;; 'Input Source Select' [Element Input Source Select] enumeration = select [Option Input Source Select:Input1] name = input priority = 10 [Option Input Source Select:Input2] name = input priority = 5 ;;; 'Input Source' [Element Input Source] enumeration = select [Option Input Source:Digital Mic] name = input-microphone priority = 20 [Option Input Source:Microphone] name = input-microphone priority = 20 [Option Input Source:Front Microphone] name = input-microphone priority = 19 [Option Input Source:Internal Mic 1] name = input-microphone priority = 19 [Option Input Source:Line-In] name = input-linein priority = 18 [Option Input Source:Line In] name = input-linein priority = 18 [Option Input Source:Docking-Station] name = input-docking priority = 17 [Option Input Source:AUX IN] name = input priority = 10 ;;; 'Capture Source' [Element Capture Source] enumeration = select [Option Capture Source:TV Tuner] name = input-video [Option Capture Source:FM] name = input-radio [Option Capture Source:Mic/Line] name = input [Option Capture Source:Line/Mic] name = input [Option Capture Source:Microphone] name = input-microphone [Option Capture Source:Int DMic] name = input-microphone-internal [Option Capture Source:iMic] name = input-microphone-internal [Option Capture Source:i-Mic] name = input-microphone-internal [Option Capture Source:Internal Microphone] name = input-microphone-internal [Option Capture Source:Front Microphone] name = input-microphone [Option Capture Source:Mic1] name = input-microphone [Option Capture Source:Mic2] name = input-microphone [Option Capture Source:D-Mic] name = input-microphone [Option Capture Source:IntMic] name = input-microphone-internal [Option Capture Source:ExtMic] name = input-microphone-external [Option Capture Source:Ext Mic] name = input-microphone-external [Option Capture Source:E-Mic] name = input-microphone-external [Option Capture Source:e-Mic] name = input-microphone-external [Option Capture Source:LineIn] name = input-linein [Option Capture Source:Analog] name = input [Option Capture Source:Line-In] name = input-linein [Option Capture Source:Line In] name = input-linein [Option Capture Source:Video] name = input-video [Option Capture Source:Aux] name = input [Option Capture Source:Aux0] name = input [Option Capture Source:Aux1] name = input [Option Capture Source:Aux2] name = input [Option Capture Source:Aux3] name = input [Option Capture Source:AUX IN] name = input [Option Capture Source:Aux In] name = input [Option Capture Source:AOUT] name = input [Option Capture Source:AUX] name = input [Option Capture Source:Cam Mic] name = input-microphone [Option Capture Source:Digital Mic] name = input-microphone [Option Capture Source:Digital Mic 1] name = input-microphone [Option Capture Source:Digital Mic 2] name = input-microphone [Option Capture Source:Analog Inputs] name = input [Option Capture Source:Unknown1] name = input [Option Capture Source:Unknown2] name = input [Option Capture Source:Docking-Station] name = input-docking ;;; 'Mic Jack Mode' [Element Mic Jack Mode] enumeration = select [Option Mic Jack Mode:Mic In] name = input-microphone [Option Mic Jack Mode:Line In] name = input-linein ;;; 'Digital Input Source' [Element Digital Input Source] enumeration = select [Option Digital Input Source:Digital Mic 1] name = input-microphone [Option Digital Input Source:Analog Inputs] name = input [Option Digital Input Source:Digital Mic 2] name = input-microphone ;;; 'Analog Source' [Element Analog Source] enumeration = select [Option Analog Source:Mic] name = input-microphone [Option Analog Source:Line in] name = input-linein [Option Analog Source:Aux] name = input ;;; 'Shared Mic/Line in' [Element Shared Mic/Line in] enumeration = select [Option Shared Mic/Line in:Mic in] name = input-microphone [Option Shared Mic/Line in:Line in] name = input-linein ;;; Various Boosts [Element Capture Boost] switch = select [Option Capture Boost:on] name = input-boost-on [Option Capture Boost:off] name = input-boost-off [Element Auto Gain Control] switch = select [Option Auto Gain Control:on] name = input-agc-on [Option Auto Gain Control:off] name = input-agc-off analog-output-chat.conf000066400000000000000000000004651511204443500324360ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/mixer/paths; Some gaming devices have a separate "chat" device, this is for voice chat ; while playing games. This device is just a fairly standard analog mono ; device, but it's nicer to make it clear that this is the "chat" device ; as is mentioned in the marketing info and manual. .include analog-output.conf.common analog-output-headphones-2.conf000066400000000000000000000047541511204443500340010ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/mixer/paths# This file is part of PulseAudio. # # PulseAudio is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as # published by the Free Software Foundation; either version 2.1 of the # License, or (at your option) any later version. # # PulseAudio is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with PulseAudio; if not, see . ; Path for the second headphone output on dual-headphone machines. ; ; See analog-output.conf.common for an explanation on the directives [General] priority = 98 [Properties] device.icon_name = audio-headphones ; HP EliteDesk 800 SFF Headphone [Jack Front Headphone,1] required-any = any ; HP EliteDesk 800 DM Headphone [Jack Front Headphone Surround] required-any = any [Element Hardware Master] switch = mute volume = merge override-map.1 = all override-map.2 = all-left,all-right [Element Master] switch = mute volume = merge override-map.1 = all override-map.2 = all-left,all-right [Element Master Mono] switch = off volume = off ; This profile path is intended to control the second headphones, not ; the first headphones. But it should not hurt if we leave the ; headphone jack enabled nonetheless. [Element Headphone] switch = mute volume = zero [Element Headphone,1] required-any = any switch = mute volume = merge override-map.1 = all override-map.2 = all-left,all-right [Element Headphone+LO] switch = mute volume = zero [Element Speaker+LO] switch = off volume = off [Element Headphone2] required-any = any switch = mute volume = merge override-map.1 = all override-map.2 = all-left,all-right [Element Speaker] switch = off volume = off [Element Desktop Speaker] switch = off volume = off ; On some machines, the Front Volume Control is shared by Headphone and Lineout, ; or Headphone and Speaker, but they have independent Volume Switch. Here only ; use switch to mute Lineout or Speaker. [Element Front] switch = off volume = zero [Element Rear] switch = off volume = off [Element Surround] switch = off volume = off [Element Side] switch = off volume = off [Element Center] switch = off volume = off [Element LFE] switch = off volume = off [Element Bass Speaker] switch = off volume = off .include analog-output.conf.common analog-output-headphones.conf000066400000000000000000000071431511204443500336350ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/mixer/paths# This file is part of PulseAudio. # # PulseAudio is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as # published by the Free Software Foundation; either version 2.1 of the # License, or (at your option) any later version. # # PulseAudio is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with PulseAudio; if not, see . ; Path for mixers that have a 'Headphone' control ; ; See analog-output.conf.common for an explanation on the directives [General] priority = 99 description-key = analog-output-headphones [Properties] device.icon_name = audio-headphones [Jack Dock Headphone] required-any = any [Jack Dock Headphone Phantom] required-any = any state.plugged = unknown state.unplugged = unknown [Jack Front Headphone] required-any = any ; HP EliteDesk 800 DM Headset [Jack Front Headphone Front] required-any = any [Jack Front Headphone Phantom] required-any = any state.plugged = unknown state.unplugged = unknown [Jack Headphone] required-any = any [Jack Headphone Phantom] required-any = any state.plugged = unknown state.unplugged = unknown # This jack can be either a headphone *or* a mic. Used on some ASUS netbooks. [Jack Headphone Mic] required-any = any [Jack Headphone - Output] required-any = any [Element Hardware Master] switch = mute volume = merge override-map.1 = all override-map.2 = all-left,all-right [Element Master] switch = mute volume = merge override-map.1 = all override-map.2 = all-left,all-right [Element Master Mono] switch = off volume = off [Element Speaker+LO] switch = off volume = off [Element Headphone+LO] required-any = any switch = mute volume = merge override-map.1 = all override-map.2 = all-left,all-right [Element Headphone] required-any = any switch = mute volume = merge override-map.1 = all override-map.2 = all-left,all-right ; This path is intended to control the first headphones, not ; the second headphones. But it should not hurt if we leave the second ; headphone jack enabled nonetheless. [Element Headphone,1] switch = mute volume = zero [Element Headset] required-any = any switch = mute volume = merge override-map.1 = all override-map.2 = all-left,all-right [Element Line HP Swap] switch = on required-any = any ; This profile path is intended to control the first headphones, not ; the second headphones. But it should not hurt if we leave the second ; headphone jack enabled nonetheless. [Element Headphone2] switch = mute volume = zero [Element Speaker] switch = off volume = off [Element Desktop Speaker] switch = off volume = off ; On some machines, the Front Volume Control is shared by Headphone and Lineout, ; or Headphone and Speaker, but they have independent Volume Switch. Here only ; use switch to mute Lineout or Speaker. [Element Front] switch = off volume = zero [Element Rear] switch = off volume = off [Element Surround] switch = off volume = off [Element Side] switch = off volume = off [Element Center] switch = off volume = off [Element LFE] switch = off volume = off [Element Bass Speaker] switch = off volume = off [Element Speaker Front] switch = off volume = off [Element Speaker Surround] switch = off volume = off [Element Speaker Side] switch = off volume = off [Element Speaker CLFE] switch = off volume = off [Element Speaker Center/LFE] switch = off volume = off .include analog-output.conf.common analog-output-lineout.conf000066400000000000000000000100331511204443500331660ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/mixer/paths# This file is part of PulseAudio. # # PulseAudio is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as # published by the Free Software Foundation; either version 2.1 of the # License, or (at your option) any later version. # # PulseAudio is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with PulseAudio; if not, see . [General] priority = 90 description-key = analog-output-lineout [Jack Line Out] required-any = any [Jack Line Out Phantom] state.plugged = unknown state.unplugged = unknown required-any = any [Jack Front Line Out] required-any = any [Jack Front Line Out Phantom] state.plugged = unknown state.unplugged = unknown required-any = any [Jack Rear Line Out] required-any = any [Jack Rear Line Out Phantom] state.plugged = unknown state.unplugged = unknown required-any = any [Jack Line Out Front] required-any = any [Jack Line Out Front Phantom] state.plugged = unknown state.unplugged = unknown required-any = any [Jack Line Out CLFE] state.plugged = unknown state.unplugged = unknown required-any = any [Jack Line Out CLFE Phantom] state.plugged = unknown state.unplugged = unknown required-any = any [Jack Line Out Surround] state.plugged = unknown state.unplugged = unknown required-any = any [Jack Line Out Surround Phantom] state.plugged = unknown state.unplugged = unknown required-any = any [Jack Line Out Side] state.plugged = unknown state.unplugged = unknown required-any = any [Jack Line Out Side Phantom] state.plugged = unknown state.unplugged = unknown required-any = any [Jack Dock Line Out] required-any = any [Jack Dock Line Out Phantom] state.plugged = unknown state.unplugged = unknown required-any = any [Element Hardware Master] switch = mute volume = merge override-map.1 = all override-map.2 = all-left,all-right [Element Master] switch = mute volume = merge override-map.1 = all override-map.2 = all-left,all-right [Element Speaker+LO] switch = mute volume = merge override-map.1 = all override-map.2 = all-left,all-right required-any = any [Element Headphone+LO] switch = mute volume = merge override-map.1 = all override-map.2 = all-left,all-right required-any = any [Element Master Mono] switch = off volume = off [Element Line HP Swap] switch = off required-any = any ; This profile path is intended to control line out, let's mute headphones ; else there will be a spike when plugging in headphones [Element Headphone] switch = off volume = off [Element Headphone,1] switch = off volume = off [Element Headphone2] switch = off volume = off [Element Speaker] switch = off volume = off [Element Desktop Speaker] switch = off volume = off [Element Front] switch = mute volume = merge override-map.1 = all-front override-map.2 = front-left,front-right [Element Rear] switch = mute volume = merge override-map.1 = all-rear override-map.2 = rear-left,rear-right [Element Surround] switch = mute volume = merge override-map.1 = all-rear override-map.2 = rear-left,rear-right [Element Side] switch = mute volume = merge override-map.1 = all-side override-map.2 = side-left,side-right [Element Center] switch = mute volume = merge override-map.1 = all-center override-map.2 = all-center,all-center [Element LFE] switch = mute volume = merge override-map.1 = lfe override-map.2 = lfe,lfe [Element CLFE] switch = mute volume = merge override-map.1 = all-center override-map.2 = all-center,lfe [Element Center/LFE] switch = mute volume = merge override-map.1 = all-center override-map.2 = all-center,lfe [Element Bass Speaker] switch = off volume = off [Element Speaker Front] switch = off volume = off [Element Speaker Surround] switch = off volume = off [Element Speaker Side] switch = off volume = off [Element Speaker CLFE] switch = off volume = off .include analog-output.conf.common analog-output-mono.conf000066400000000000000000000037741511204443500324750ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/mixer/paths# This file is part of PulseAudio. # # PulseAudio is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as # published by the Free Software Foundation; either version 2.1 of the # License, or (at your option) any later version. # # PulseAudio is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with PulseAudio; if not, see . ; Intended for usage on boards that have a separate Mono output plug. ; ; See analog-output.conf.common for an explanation on the directives [General] priority = 50 [Element Hardware Master] switch = mute volume = merge override-map.1 = all override-map.2 = all-left,all-right [Element Master] switch = off volume = off [Element Master Mono] required = any switch = mute volume = merge override-map.1 = all override-map.2 = all-left,all-right ; This profile path is intended to control the speaker, not the ; headphones. But it should not hurt if we leave the headphone jack ; enabled nonetheless. [Element Headphone] switch = mute volume = zero [Element Headphone,1] switch = mute volume = zero [Element Headphone+LO] switch = mute volume = zero [Element Headphone2] switch = mute volume = zero [Element Speaker] switch = mute volume = merge override-map.1 = all override-map.2 = all-left,all-right [Element Speaker+LO] switch = mute volume = merge override-map.1 = all override-map.2 = all-left,all-right [Element Desktop Speaker] switch = off volume = off [Element Front] switch = off volume = off [Element Rear] switch = off volume = off [Element Surround] switch = off volume = off [Element Side] switch = off volume = off [Element Center] switch = off volume = off [Element LFE] switch = off volume = off .include analog-output.conf.common analog-output-speaker-always.conf000066400000000000000000000074571511204443500344570ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/mixer/paths# This file is part of PulseAudio. # # PulseAudio is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as # published by the Free Software Foundation; either version 2.1 of the # License, or (at your option) any later version. # # PulseAudio is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with PulseAudio; if not, see . ; Path for mixers that don't have a 'Speaker' control, but where we ; force enable the speaker paths nonetheless. ; Needed for some older Dell laptops. ; See analog-output.conf.common for an explanation on the directives [General] priority = 100 description-key = analog-output-speaker [Properties] device.icon_name = audio-speakers [Jack Headphone] state.plugged = no state.unplugged = unknown [Jack Front Headphone] state.plugged = no state.unplugged = unknown [Jack Line Out] state.plugged = no state.unplugged = unknown [Jack Line Out Front] state.plugged = no state.unplugged = unknown [Jack Front Line Out] state.plugged = no state.unplugged = unknown [Jack Rear Line Out] state.plugged = no state.unplugged = unknown [Jack Dock Line Out] state.plugged = no state.unplugged = unknown [Element Hardware Master] switch = mute volume = merge override-map.1 = all override-map.2 = all-left,all-right [Element Master] switch = mute volume = merge override-map.1 = all override-map.2 = all-left,all-right [Element Master Mono] switch = off volume = off ; This profile path is intended to control the speaker, not the ; headphones. But it should not hurt if we leave the headphone jack ; enabled nonetheless. [Element Headphone] switch = mute volume = zero [Element Headphone,1] switch = mute volume = zero [Element Headphone2] switch = mute volume = zero [Element Headphone+LO] switch = off volume = off [Element Speaker+LO] switch = mute volume = merge override-map.1 = all override-map.2 = all-left,all-right [Element Speaker] switch = mute volume = merge override-map.1 = all override-map.2 = all-left,all-right [Element Desktop Speaker] switch = mute volume = merge override-map.1 = all override-map.2 = all-left,all-right [Element Front] switch = mute volume = merge override-map.1 = all-front override-map.2 = front-left,front-right [Element Front Speaker] switch = mute volume = merge override-map.1 = all-front override-map.2 = front-left,front-right [Element Rear] switch = mute volume = merge override-map.1 = all-rear override-map.2 = rear-left,rear-right [Element Surround] switch = mute volume = merge override-map.1 = all-rear override-map.2 = rear-left,rear-right [Element Surround Speaker] switch = mute volume = merge override-map.1 = all-rear override-map.2 = rear-left,rear-right [Element Side] switch = mute volume = merge override-map.1 = all-side override-map.2 = side-left,side-right [Element Center] switch = mute volume = merge override-map.1 = all-center override-map.2 = all-center,all-center [Element Center Speaker] switch = mute volume = merge override-map.1 = all-center override-map.2 = all-center,all-center [Element LFE] switch = mute volume = merge override-map.1 = lfe override-map.2 = lfe,lfe [Element LFE Speaker] switch = mute volume = merge override-map.1 = lfe override-map.2 = lfe,lfe [Element Bass Speaker] switch = mute volume = merge override-map.1 = lfe override-map.2 = lfe,lfe [Element CLFE] switch = mute volume = merge override-map.1 = all-center override-map.2 = all-center,lfe [Element Center/LFE] switch = mute volume = merge override-map.1 = all-center override-map.2 = all-center,lfe .include analog-output.conf.common analog-output-speaker.conf000066400000000000000000000115301511204443500331440ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/mixer/paths# This file is part of PulseAudio. # # PulseAudio is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as # published by the Free Software Foundation; either version 2.1 of the # License, or (at your option) any later version. # # PulseAudio is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with PulseAudio; if not, see . ; Path for mixers that have a 'Speaker' control ; ; See analog-output.conf.common for an explanation on the directives [General] priority = 100 description-key = analog-output-speaker [Properties] device.icon_name = audio-speakers [Jack Headphone] state.plugged = no state.unplugged = unknown [Jack Dock Headphone] state.plugged = no state.unplugged = unknown [Jack Front Headphone] state.plugged = no state.unplugged = unknown [Jack Line Out] state.plugged = no state.unplugged = unknown [Jack Line Out Front] state.plugged = no state.unplugged = unknown [Jack Front Line Out] state.plugged = no state.unplugged = unknown [Jack Rear Line Out] state.plugged = no state.unplugged = unknown [Jack Dock Line Out] state.plugged = no state.unplugged = unknown [Jack Speaker] required-any = any [Jack Speaker Phantom] required-any = any state.plugged = unknown state.unplugged = unknown [Jack Speaker Front Phantom] required-any = any state.plugged = unknown state.unplugged = unknown [Jack Speaker - Output] required-any = any [Element Hardware Master] switch = mute volume = merge override-map.1 = all override-map.2 = all-left,all-right [Element Master] switch = mute volume = merge override-map.1 = all override-map.2 = all-left,all-right [Element Master Mono] switch = off volume = off ; Make sure the internal speakers are not auto-muted once the system has speakers [Element Auto-Mute Mode] enumeration = select [Option Auto-Mute Mode:Disabled] name = analog-output-speaker ; This profile path is intended to control the speaker, let's mute headphones ; else there will be a spike when plugging in headphones [Element Headphone] switch = off volume = off [Element Headphone,1] switch = off volume = off [Element Headphone2] switch = off volume = off [Element Headphone+LO] switch = off volume = off [Element Speaker+LO] required-any = any switch = mute volume = merge override-map.1 = all override-map.2 = all-left,all-right [Element Speaker] required-any = any switch = mute volume = merge override-map.1 = all override-map.2 = all-left,all-right [Element Desktop Speaker] required-any = any switch = mute volume = merge override-map.1 = all override-map.2 = all-left,all-right [Element Front] switch = mute volume = merge override-map.1 = all-front override-map.2 = front-left,front-right [Element Front Speaker] switch = mute volume = merge override-map.1 = all-front override-map.2 = front-left,front-right required-any = any [Element Speaker Front] switch = mute volume = merge override-map.1 = all-front override-map.2 = front-left,front-right required-any = any [Element Rear] switch = mute volume = merge override-map.1 = all-rear override-map.2 = rear-left,rear-right [Element Surround] switch = mute volume = merge override-map.1 = all-rear override-map.2 = rear-left,rear-right [Element Surround Speaker] switch = mute volume = merge override-map.1 = all-rear override-map.2 = rear-left,rear-right required-any = any [Element Speaker Surround] switch = mute volume = merge override-map.1 = all-rear override-map.2 = rear-left,rear-right required-any = any [Element Side] switch = mute volume = merge override-map.1 = all-side override-map.2 = side-left,side-right [Element Speaker Side] switch = mute volume = merge override-map.1 = all-side override-map.2 = side-left,side-right [Element Center] switch = mute volume = merge override-map.1 = all-center override-map.2 = all-center,all-center [Element Center Speaker] switch = mute volume = merge override-map.1 = all-center override-map.2 = all-center,all-center required-any = any [Element LFE] switch = mute volume = merge override-map.1 = lfe override-map.2 = lfe,lfe [Element LFE Speaker] switch = mute volume = merge override-map.1 = lfe override-map.2 = lfe,lfe required-any = any [Element Bass Speaker] switch = mute volume = merge override-map.1 = lfe override-map.2 = lfe,lfe required-any = any [Element CLFE] switch = mute volume = merge override-map.1 = all-center override-map.2 = all-center,lfe [Element Center/LFE] switch = mute volume = merge override-map.1 = all-center override-map.2 = all-center,lfe [Element Speaker CLFE] switch = mute volume = merge override-map.1 = all-center override-map.2 = all-center,lfe .include analog-output.conf.common analog-output.conf000066400000000000000000000037701511204443500315230ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/mixer/paths# This file is part of PulseAudio. # # PulseAudio is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as # published by the Free Software Foundation; either version 2.1 of the # License, or (at your option) any later version. # # PulseAudio is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with PulseAudio; if not, see . ; Intended for the 'default' output. Note that a-o-speaker.conf has a ; higher priority than this ; ; See analog-output.conf.common for an explanation on the directives [General] priority = 99 [Element Hardware Master] switch = mute volume = merge override-map.1 = all override-map.2 = all-left,all-right [Element Master] switch = mute volume = merge override-map.1 = all override-map.2 = all-left,all-right [Element Master Mono] switch = off volume = off [Element Front] switch = mute volume = merge override-map.1 = all-front override-map.2 = front-left,front-right [Element Rear] switch = mute volume = merge override-map.1 = all-rear override-map.2 = rear-left,rear-right [Element Surround] switch = mute volume = merge override-map.1 = all-rear override-map.2 = rear-left,rear-right [Element Side] switch = mute volume = merge override-map.1 = all-side override-map.2 = side-left,side-right [Element Center] switch = mute volume = merge override-map.1 = all-center override-map.2 = all-center,all-center [Element LFE] switch = mute volume = merge override-map.1 = lfe override-map.2 = lfe,lfe [Element CLFE] switch = mute volume = merge override-map.1 = all-center override-map.2 = all-center,lfe [Element Center/LFE] switch = mute volume = merge override-map.1 = all-center override-map.2 = all-center,lfe .include analog-output.conf.common analog-output.conf.common000066400000000000000000000273221511204443500330110ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/mixer/paths# This file is part of PulseAudio. # # PulseAudio is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as # published by the Free Software Foundation; either version 2.1 of the # License, or (at your option) any later version. # # PulseAudio is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with PulseAudio; if not, see . ; Common part of all paths ; So here's generally how mixer paths are used by PA: PA goes through ; a mixer path file from top to bottom and checks if a mixer element ; described therein exists. If so it is added to the list of mixer ; elements PA will control, keeping the order it read them in. If a ; mixer element described here has set the required= or ; required-absent= directives a path might not be accepted as valid ; and is ignored in its entirety (see below). However usually if a ; element listed here is missing this one element is ignored but not ; the entire path. ; ; When a device shall be muted/unmuted *all* elements listed in a path ; file with "switch = mute" will be toggled. ; ; When a device shall change its volume, PA will got through the list ; of all elements with "volume = merge" and set the volume on the ; first element. If that element does not support dB volumes, this is ; where the story ends. If it does support dB volumes, PA divides the ; requested volume by the volume that was set on this element, and ; then go on to the next element with "volume = merge" and then set ; that there, and so on. That way the first volume element in the ; path will be the one that does the 'biggest' part of the overall ; volume adjustment, with the remaining elements usually being set to ; some value next to 0dB. This logic makes sure we get the full range ; over all volume sliders and a very high granularity of volumes ; already in hardware. ; ; All switches and enumerations set to "select" are exposed via the ; "port" functionality of sinks/sources. Basically every possible ; switch setting and every possible enumeration setting will be ; combined and made into a "port". So make sure you don't list too ; many switches/enums for exposing, because the number of ports might ; rise exponentially. ; ; Only one path can be selected at a time. All paths that are valid ; for an audio device will be exposed as "port" for the sink/source. ; [General] ; type = ... # The device type. It's highly recommended to set a type for every path. ; # See parse_type() in alsa-mixer.c for supported values. ; priority = ... # Priority for this path ; description-key = ... # The path description is looked up from a table in path_verify() in ; # src/modules/alsa/alsa-mixer.c. By default the path name (i.e. the file name ; # minus the ".conf" suffix) is used as the lookup key, but if this option is ; # set, then the given string is used as the key instead. In any case the ; # "description" option can be used to override the path description. ; description = ... # Description for this path. Overrides the normal description lookup logic, as ; # described in the "description-key" documentation above. ; mute-during-activation = yes | no # If this path supports hardware mute, should the hw mute be used while activating this ; # path? In some cases this can reduce extra noises during port switching, while in other ; # cases this can increase such noises. Default: no. ; eld-device = ... # If this is an HDMI port, set to "auto" so that PulseAudio will try to read ; # the monitor ELD information from the ALSA mixer. By default the ELD information ; # is not read, because it's only applicable with HDMI. Earlier the "auto" option ; # didn't exist, and the hw device index had to be manually configured. For ; # backwards compatibility, it's still possible to manually configure the device ; # index using this option. ; ; [Properties] # Property list for this path. The list is merged into the port property list. ; = # Each property is defined on its own line. ; ... ; ; [Option ...:...] # For each option of an enumeration or switch element ; # that shall be exposed as a sink/source port. Needs to ; # be named after the Element, followed by a colon, followed ; # by the option name, resp. on/off if the element is a switch. ; name = ... # Logical name to use in the path identifier ; priority = ... # Priority if this is made into a device port ; required = ignore | enumeration | any # In this element, this option must exist or the path will be invalid. ("any" is an alias for "enumeration".) ; required-any = ignore | enumeration | any # In this element, either this or another option must exist (or an element) ; required-absent = ignore | enumeration | any # In this element, this option must not exist or the path will be invalid ; ; [Element ...] # For each element that we shall control. The "..." here is the element name, ; # or name and index separated by a comma. ; required = ignore | switch | volume | enumeration | any # If set, require this element to be of this kind and available, ; # otherwise don't consider this path valid for the card ; required-any = ignore | switch | volume | enumeration | any # If set, at least one of the elements or jacks with required-any in this ; # path must be present, otherwise this path is invalid for the card ; required-absent = ignore | switch | volume # If set, require this element to not be of this kind and not ; # available, otherwise don't consider this path valid for the card ; ; switch = ignore | mute | off | on | select # What to do with this switch: ignore it, make it follow mute status, ; # always set it to off, always to on, or make it selectable as port. ; # If set to 'select' you need to define an Option section for on ; # and off ; volume = ignore | merge | off | zero | # What to do with this volume: ignore it, merge it into the device ; # volume slider, always set it to the lowest value possible, or always ; # set it to 0 dB (for whatever that means), or always set it to ; # (this only makes sense in path configurations where ; # the exact hardware and driver are known beforehand). ; volume-limit = # Limit the maximum volume by disabling the volume steps above . ; enumeration = ignore | select # What to do with this enumeration, ignore it or make it selectable ; # via device ports. If set to 'select' you need to define an Option section ; # for each of the items you want to expose ; direction = playback | capture # Is this relevant only for playback or capture? If not set this will implicitly be ; # set the direction of the PCM device is opened as. Generally this doesn't need to be set ; # unless you have a broken driver that has playback controls marked for capture or vice ; # versa ; direction-try-other = no | yes # If the element does not supported what is requested, try the other direction, too? ; ; override-map.1 = ... # Override the channel mask of the mixer control if the control only exposes a single channel ; override-map.2 = ... # Override the channel masks of the mixer control if the control only exposes two channels ; # Override maps should list for each element channel which high-level channels it controls via a ; # channel mask. A channel mask may either be the name of a single channel, or the words "all-left", ; # "all-right", "all-center", "all-front", "all-rear", and "all" to encode a specific subset of ; # channels in a mask ; [Jack ...] # For each jack that we will use for jack detection ; # The name 'Jack Foo' must match ALSA's 'Foo Jack' control. ; required = ignore | any # If not set to ignore, make the path invalid if this jack control is not present. ; required-absent = ignore | any # If not set to ignore, make the path invalid if this jack control is present. ; required-any = ignore | any # If not set to ignore, make the path invalid if no jack controls and no elements with ; # the required-any are present. ; state.plugged = yes | no | unknown # Normally a plugged jack would mean the port becomes available, and an unplugged means it's ; state.unplugged = yes | no | unknown # unavailable, but the port status can be overridden by specifying state.plugged and/or state.unplugged. ; append-pcm-to-name = no | yes # Add ",pcm=N" to the jack name? N is the hw PCM device index. HDMI jacks have ; # the PCM device index in their name, but different drivers use different ; # numbering schemes, so we can't hardcode the full jack name in our configuration ; # files. [Element PCM] switch = mute volume = merge override-map.1 = all override-map.2 = all-left,all-right [Element External Amplifier] switch = select [Option External Amplifier:on] name = output-amplifier-on priority = 10 [Option External Amplifier:off] name = output-amplifier-off priority = 0 [Element Bass Boost] switch = select [Option Bass Boost:on] name = output-bass-boost-on priority = 0 [Option Bass Boost:off] name = output-bass-boost-off priority = 10 [Element IEC958] switch = off [Element IEC958 Optical Raw] switch = off ;;; 'Analog Output' [Element Analog Output] enumeration = select [Option Analog Output:Speakers] name = output-speaker priority = 10 [Option Analog Output:Headphones] name = output-headphones priority = 9 [Option Analog Output:FP Headphones] name = output-headphones priority = 8 ;;; 'Output Select' [Element Output Select] enumeration = select [Option Output Select:Speakers] name = output-speaker priority = 10 [Option Output Select:Headphone] name = output-headphones priority = 9 audigy-analog-output-mirror.conf000066400000000000000000000030741511204443500343100ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/mixer/paths; Mixer path for the Sound Blaster Audigy series, which uses the EMU10K2 DSP. ; We target 'Wave' and other non-'PCM' controls as a special case for when ; the device's stereo-to-all-speakers mirroring mode is in use. (For example, ; the Analog Stereo Output profile.) ; https://docs.kernel.org/sound/cards/audigy-mixer.html ; ; See analog-output.conf.common for an explanation on the directives [General] priority = 99 description-key = analog-output [Element Master] switch = mute volume = merge override-map.1 = all override-map.2 = all-left,all-right [Element Wave] volume = merge override-map.1 = all override-map.2 = all-left,all-right # The following elements also exist in analog-output.conf. We list them here # instead of including that file, for ideal positioning of the Wave element: # Placing Wave below the Master element prevents Master from reaching its # loudest until the user raises the unified volume control to maximum. # (This should reduce the chance of a surprise speaker blow-out.) # Placing Wave above the per-channel elements yields even steps at low volume. [Element Front] volume = merge override-map.1 = all-front override-map.2 = front-left,front-right [Element Surround] volume = merge override-map.1 = all-rear override-map.2 = rear-left,rear-right [Element Side] volume = merge override-map.1 = all-side override-map.2 = side-left,side-right [Element Center] volume = merge override-map.1 = all-center override-map.2 = all-center,all-center [Element LFE] volume = merge override-map.1 = lfe override-map.2 = lfe,lfe .include analog-output.conf.common audigy-analog-output.conf000066400000000000000000000021171511204443500327750ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/mixer/paths; Mixer path for the Sound Blaster Audigy series, which uses the EMU10K2 DSP. ; We target 'PCM Front' and similarly named controls instead of 'Front' et al. ; because the latter affect volume only in the device's stereo-to-all-speakers ; mirroring mode, which is not used by most profiles. ; https://docs.kernel.org/sound/cards/audigy-mixer.html ; ; See analog-output.conf.common for an explanation on the directives [General] priority = 99 description-key = analog-output [Element Master] switch = mute volume = merge override-map.1 = all override-map.2 = all-left,all-right [Element PCM Front] volume = merge override-map.1 = all-front override-map.2 = front-left,front-right [Element PCM Surround] volume = merge override-map.1 = all-rear override-map.2 = rear-left,rear-right [Element PCM Side] volume = merge override-map.1 = all-side override-map.2 = side-left,side-right [Element PCM Center] volume = merge override-map.1 = all-center override-map.2 = all-center,all-center [Element PCM LFE] volume = merge override-map.1 = lfe override-map.2 = lfe,lfe .include analog-output.conf.common hdmi-output-0.conf000066400000000000000000000003701511204443500313310ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/mixer/paths[General] description = HDMI / DisplayPort type = hdmi priority = 59 eld-device = auto [Properties] device.icon_name = video-display [Jack HDMI/DP] append-pcm-to-name = yes required = ignore [Jack HDMI] append-pcm-to-name = no required = ignore hdmi-output-1.conf000066400000000000000000000003721511204443500313340ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/mixer/paths[General] description = HDMI / DisplayPort 2 type = hdmi priority = 58 eld-device = auto [Properties] device.icon_name = video-display [Jack HDMI/DP] append-pcm-to-name = yes required = ignore [Jack HDMI] append-pcm-to-name = no required = ignore hdmi-output-10.conf000066400000000000000000000003731511204443500314150ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/mixer/paths[General] description = HDMI / DisplayPort 11 type = hdmi priority = 49 eld-device = auto [Properties] device.icon_name = video-display [Jack HDMI/DP] append-pcm-to-name = yes required = ignore [Jack HDMI] append-pcm-to-name = no required = ignore hdmi-output-2.conf000066400000000000000000000003721511204443500313350ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/mixer/paths[General] description = HDMI / DisplayPort 3 type = hdmi priority = 57 eld-device = auto [Properties] device.icon_name = video-display [Jack HDMI/DP] append-pcm-to-name = yes required = ignore [Jack HDMI] append-pcm-to-name = no required = ignore hdmi-output-3.conf000066400000000000000000000003721511204443500313360ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/mixer/paths[General] description = HDMI / DisplayPort 4 type = hdmi priority = 56 eld-device = auto [Properties] device.icon_name = video-display [Jack HDMI/DP] append-pcm-to-name = yes required = ignore [Jack HDMI] append-pcm-to-name = no required = ignore hdmi-output-4.conf000066400000000000000000000003721511204443500313370ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/mixer/paths[General] description = HDMI / DisplayPort 5 type = hdmi priority = 55 eld-device = auto [Properties] device.icon_name = video-display [Jack HDMI/DP] append-pcm-to-name = yes required = ignore [Jack HDMI] append-pcm-to-name = no required = ignore hdmi-output-5.conf000066400000000000000000000003721511204443500313400ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/mixer/paths[General] description = HDMI / DisplayPort 6 type = hdmi priority = 54 eld-device = auto [Properties] device.icon_name = video-display [Jack HDMI/DP] append-pcm-to-name = yes required = ignore [Jack HDMI] append-pcm-to-name = no required = ignore hdmi-output-6.conf000066400000000000000000000003721511204443500313410ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/mixer/paths[General] description = HDMI / DisplayPort 7 type = hdmi priority = 53 eld-device = auto [Properties] device.icon_name = video-display [Jack HDMI/DP] append-pcm-to-name = yes required = ignore [Jack HDMI] append-pcm-to-name = no required = ignore hdmi-output-7.conf000066400000000000000000000003721511204443500313420ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/mixer/paths[General] description = HDMI / DisplayPort 8 type = hdmi priority = 52 eld-device = auto [Properties] device.icon_name = video-display [Jack HDMI/DP] append-pcm-to-name = yes required = ignore [Jack HDMI] append-pcm-to-name = no required = ignore hdmi-output-8.conf000066400000000000000000000003721511204443500313430ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/mixer/paths[General] description = HDMI / DisplayPort 9 type = hdmi priority = 51 eld-device = auto [Properties] device.icon_name = video-display [Jack HDMI/DP] append-pcm-to-name = yes required = ignore [Jack HDMI] append-pcm-to-name = no required = ignore hdmi-output-9.conf000066400000000000000000000003731511204443500313450ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/mixer/paths[General] description = HDMI / DisplayPort 10 type = hdmi priority = 50 eld-device = auto [Properties] device.icon_name = video-display [Jack HDMI/DP] append-pcm-to-name = yes required = ignore [Jack HDMI] append-pcm-to-name = no required = ignore iec958-stereo-input.conf000066400000000000000000000014251511204443500323610ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/mixer/paths# This file is part of PulseAudio. # # PulseAudio is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as # published by the Free Software Foundation; either version 2.1 of the # License, or (at your option) any later version. # # PulseAudio is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with PulseAudio; if not, see . [Element PCM Capture Source] enumeration = select [Option PCM Capture Source:IEC958 In] name = iec958-input iec958-stereo-output.conf000066400000000000000000000013101511204443500325530ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/mixer/paths# This file is part of PulseAudio. # # PulseAudio is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as # published by the Free Software Foundation; either version 2.1 of the # License, or (at your option) any later version. # # PulseAudio is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with PulseAudio; if not, see . [Element IEC958] switch = mute logi407-iec958-stereo-output.conf000066400000000000000000000004261511204443500337450ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/mixer/paths; Mixer path for Logitech 407 PC Speakers ; ; See analog-output.conf.common for an explanation on the directives [General] priority = 99 description-key = iec958-stereo-output [Element PCM] switch = mute volume = merge override-map.1 = all override-map.2 = all-left,all-right steelseries-arctis-output-chat-common.conf000066400000000000000000000020601511204443500362660ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/mixer/paths# This file is part of PulseAudio. # # PulseAudio is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as # published by the Free Software Foundation; either version 2.1 of the # License, or (at your option) any later version. # # PulseAudio is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with PulseAudio; if not, see . ; Steelseries Arctis 5 USB headset stereo chat path. The headset has two ; output devices. The first one is meant for voice audio, and the second ; one meant for everything else. The purpose of this unusual design is to ; provide separate volume controls for voice and other audio, which can be ; useful in gaming. [General] priority = 50 [Element Com Speaker] switch = mute volume = merge steelseries-arctis-output-game-common.conf000066400000000000000000000020501511204443500362570ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/mixer/paths# This file is part of PulseAudio. # # PulseAudio is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as # published by the Free Software Foundation; either version 2.1 of the # License, or (at your option) any later version. # # PulseAudio is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with PulseAudio; if not, see . ; Steelseries Arctis 5 USB headset stereo game path. The headset has two ; output devices. The first one is meant for voice audio, and the second ; one meant for everything else. The purpose of this unusual design is to ; provide separate volume controls for voice and other audio, which can be ; useful in gaming. [General] priority = 99 [Element PCM] switch = mute volume = merge usb-gaming-headset-input.conf000066400000000000000000000023711511204443500335210ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/mixer/paths# This file is part of PulseAudio. # # PulseAudio is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as # published by the Free Software Foundation; either version 2.1 of the # License, or (at your option) any later version. # # PulseAudio is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with PulseAudio; if not, see . ; USB gaming headset microphone input path. These headsets usually have two ; output devices. The first one is mono, meant for voice audio, and the second ; one is stereo, meant for everything else. The purpose of this unusual design ; is to provide separate volume controls for voice and other audio, which can ; be useful in gaming. ; ; Works with: ; Steelseries Arctis 7 ; Steelseries Arctis Pro Wireless. ; Lucidsound LS31 [General] description-key = analog-input-microphone-headset [Element Headset] volume = merge switch = mute override-map.1 = all override-map.2 = all-left,all-right usb-gaming-headset-output-mono.conf000066400000000000000000000023561511204443500346730ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/mixer/paths# This file is part of PulseAudio. # # PulseAudio is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as # published by the Free Software Foundation; either version 2.1 of the # License, or (at your option) any later version. # # PulseAudio is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with PulseAudio; if not, see . ; USB gaming headset mono output path. These headsets usually have two ; output devices. The first one is mono, meant for voice audio, and the second ; one is stereo, meant for everything else. The purpose of this unusual design ; is to provide separate volume controls for voice and other audio, which can ; be useful in gaming. ; ; Works with: ; Steelseries Arctis 7 ; Steelseries Arctis Pro Wireless. ; Lucidsound LS31 [General] description-key = analog-output-headphones-mono [Element PCM] volume = merge switch = mute override-map.1 = all override-map.2 = all-left,all-right usb-gaming-headset-output-stereo.conf000066400000000000000000000023531511204443500352210ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/mixer/paths# This file is part of PulseAudio. # # PulseAudio is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as # published by the Free Software Foundation; either version 2.1 of the # License, or (at your option) any later version. # # PulseAudio is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with PulseAudio; if not, see . ; USB gaming headset mono output path. These headsets usually have two ; output devices. The first one is mono, meant for voice audio, and the second ; one is stereo, meant for everything else. The purpose of this unusual design ; is to provide separate volume controls for voice and other audio, which can ; be useful in gaming. ; ; Works with: ; Steelseries Arctis 7 ; Steelseries Arctis Pro Wireless. ; Lucidsound LS31 [General] description-key = analog-output-headphones [Element PCM,1] volume = merge switch = mute override-map.1 = all override-map.2 = all-left,all-right virtual-surround-7.1.conf000066400000000000000000000001461511204443500325660ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/mixer/paths[Element PCM,1] switch = mute volume = merge override-map.1 = all override-map.2 = all-left,all-right pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/mixer/profile-sets/000077500000000000000000000000001511204443500274225ustar00rootroot000000000000009999-custom.conf000066400000000000000000000016511511204443500321500ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/mixer/profile-sets# This file is part of PulseAudio. # # PulseAudio is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as # published by the Free Software Foundation; either version 2.1 of the # License, or (at your option) any later version. # # PulseAudio is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with PulseAudio; if not, see . ; Put your custom profiles here. ; An example for defining multiple-sink profiles #[Profile output:analog-stereo+output:iec958-stereo+input:analog-stereo] #description = Foobar #output-mappings = analog-stereo iec958-stereo #input-mappings = analog-stereo analog-only.conf000066400000000000000000000106501511204443500324340ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/mixer/profile-sets# PulseAudio is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as # published by the Free Software Foundation; either version 2.1 of the # License, or (at your option) any later version. # # PulseAudio is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with PulseAudio; if not, see . ; Some USB DACs appear to support IEC958, but don't physically have any ; digital outputs. [General] auto-profiles = yes [Mapping analog-stereo] device-strings = front:%f channel-map = left,right paths-output = analog-output analog-output-lineout analog-output-speaker analog-output-headphones analog-output-headphones-2 paths-input = analog-input-front-mic analog-input-rear-mic analog-input-internal-mic analog-input-dock-mic analog-input analog-input-mic analog-input-linein analog-input-aux analog-input-video analog-input-tvtuner analog-input-fm analog-input-mic-line analog-input-headphone-mic analog-input-headset-mic priority = 15 # If everything else fails, try to use hw:0 as a stereo device... [Mapping stereo-fallback] device-strings = hw:%f fallback = yes channel-map = front-left,front-right paths-output = analog-output analog-output-lineout analog-output-speaker analog-output-headphones analog-output-headphones-2 paths-input = analog-input-front-mic analog-input-rear-mic analog-input-internal-mic analog-input-dock-mic analog-input analog-input-mic analog-input-linein analog-input-aux analog-input-video analog-input-tvtuner analog-input-fm analog-input-mic-line analog-input-headphone-mic analog-input-headset-mic priority = 1 # ...and if even that fails, try to use hw:0 as a mono device. [Mapping mono-fallback] device-strings = hw:%f fallback = yes channel-map = mono paths-output = analog-output analog-output-lineout analog-output-speaker analog-output-headphones analog-output-headphones-2 analog-output-mono paths-input = analog-input-front-mic analog-input-rear-mic analog-input-internal-mic analog-input-dock-mic analog-input analog-input-mic analog-input-linein analog-input-aux analog-input-video analog-input-tvtuner analog-input-fm analog-input-mic-line analog-input-headset-mic priority = 1 [Mapping analog-surround-21] device-strings = surround21:%f channel-map = front-left,front-right,lfe paths-input = analog-input analog-input-linein analog-input-mic paths-output = analog-output analog-output-lineout analog-output-speaker priority = 13 [Mapping analog-surround-40] device-strings = surround40:%f channel-map = front-left,front-right,rear-left,rear-right paths-input = analog-input analog-input-linein analog-input-mic paths-output = analog-output analog-output-lineout analog-output-speaker priority = 12 [Mapping analog-surround-41] device-strings = surround41:%f channel-map = front-left,front-right,rear-left,rear-right,lfe paths-input = analog-input analog-input-linein analog-input-mic paths-output = analog-output analog-output-lineout analog-output-speaker priority = 13 [Mapping analog-surround-50] device-strings = surround50:%f channel-map = front-left,front-right,rear-left,rear-right,front-center paths-input = analog-input analog-input-linein analog-input-mic paths-output = analog-output analog-output-lineout analog-output-speaker priority = 12 [Mapping analog-surround-51] device-strings = surround51:%f channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe paths-input = analog-input analog-input-linein analog-input-mic paths-output = analog-output analog-output-lineout analog-output-speaker priority = 13 [Mapping analog-surround-71] device-strings = surround71:%f channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe,side-left,side-right description = Analog Surround 7.1 paths-input = analog-input analog-input-linein analog-input-mic paths-output = analog-output analog-output-lineout analog-output-speaker priority = 12 [Mapping multichannel-output] device-strings = hw:%f channel-map = left,right,rear-left,rear-right exact-channels = false fallback = yes priority = 1 direction = output [Mapping multichannel-input] device-strings = hw:%f channel-map = left,right,rear-left,rear-right exact-channels = false fallback = yes priority = 1 direction = input asus-xonar-se.conf000066400000000000000000000053321511204443500327220ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/mixer/profile-sets# This file is part of PulseAudio. # # PulseAudio is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as # published by the Free Software Foundation; either version 2.1 of the # License, or (at your option) any later version. # # PulseAudio is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with PulseAudio; if not, see . ; ASUS Xonar SE card. ; This card has two devices for each rear and front panel jacks. ; ; See default.conf for an explanation on the directives used here. [General] auto-profiles = yes [Mapping analog-stereo-headset] device-strings = hw:%f,1 channel-map = left,right paths-output = analog-output analog-output-headphones paths-input = analog-input-mic analog-input-headphone-mic analog-input-headset-mic priority = 15 [Mapping analog-stereo] device-strings = hw:%f,0 channel-map = left,right paths-output = analog-output analog-output-speaker paths-input = analog-input analog-input-mic analog-input-linein priority = 14 [Mapping analog-surround-21] device-strings = surround21:%f channel-map = front-left,front-right,lfe paths-output = analog-output-speaker priority = 13 direction = output [Mapping analog-surround-40] device-strings = surround40:%f channel-map = front-left,front-right,rear-left,rear-right paths-output = analog-output-speaker priority = 12 direction = output [Mapping analog-surround-41] device-strings = surround41:%f channel-map = front-left,front-right,rear-left,rear-right,lfe paths-output = analog-output-speaker priority = 13 direction = output [Mapping analog-surround-50] device-strings = surround50:%f channel-map = front-left,front-right,rear-left,rear-right,front-center paths-output = analog-output-speaker priority = 12 direction = output [Mapping analog-surround-51] device-strings = surround51:%f channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe paths-output = analog-output-speaker priority = 13 direction = output [Mapping iec958-stereo] device-strings = iec958:%f channel-map = left,right paths-output = iec958-stereo-output priority = 5 [Mapping iec958-ac3-surround-40] device-strings = a52:%f channel-map = front-left,front-right,rear-left,rear-right paths-output = iec958-stereo-output priority = 2 direction = output [Mapping iec958-ac3-surround-51] device-strings = a52:%f channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe paths-output = iec958-stereo-output priority = 3 direction = output audigy.conf000066400000000000000000000076331511204443500315050ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/mixer/profile-sets# This file is part of PulseAudio. # # PulseAudio is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as # published by the Free Software Foundation; either version 2.1 of the # License, or (at your option) any later version. # # PulseAudio is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with PulseAudio; if not, see . ; Creative Sound Blaster Audigy product line ; ; These are copies of the mappings we find in default.conf, but with analog ; mixer paths targeting appropriate Audigy driver controls, and the small ; change of making analog-stereo and analog-mono non-fallback mappings. ; The latter is needed because these cards only support duplex profiles with ; mono inputs, and in the default configuration, with stereo being a fallback ; mapping, the mono mapping is never tried. ; ; See default.conf for an explanation on the directives used here. [General] auto-profiles = yes # Based on stereo-fallback [Mapping analog-stereo] device-strings = hw:%f channel-map = front-left,front-right paths-output = audigy-analog-output-mirror analog-output-lineout analog-output-speaker analog-output-headphones analog-output-headphones-2 paths-input = analog-input-front-mic analog-input-rear-mic analog-input-internal-mic analog-input-dock-mic analog-input analog-input-mic analog-input-linein analog-input-aux analog-input-video analog-input-tvtuner analog-input-fm analog-input-mic-line analog-input-headphone-mic analog-input-headset-mic priority = 1 # Based on mono-fallback [Mapping analog-mono] device-strings = hw:%f channel-map = mono paths-output = audigy-analog-output-mirror analog-output-lineout analog-output-speaker analog-output-headphones analog-output-headphones-2 analog-output-mono paths-input = analog-input-front-mic analog-input-rear-mic analog-input-internal-mic analog-input-dock-mic analog-input analog-input-mic analog-input-linein analog-input-aux analog-input-video analog-input-tvtuner analog-input-fm analog-input-mic-line analog-input-headset-mic priority = 1 [Mapping analog-surround-21] device-strings = surround21:%f channel-map = front-left,front-right,lfe paths-output = audigy-analog-output analog-output-lineout analog-output-speaker priority = 13 direction = output [Mapping analog-surround-40] device-strings = surround40:%f channel-map = front-left,front-right,rear-left,rear-right paths-output = audigy-analog-output analog-output-lineout analog-output-speaker priority = 12 direction = output [Mapping analog-surround-41] device-strings = surround41:%f channel-map = front-left,front-right,rear-left,rear-right,lfe paths-output = audigy-analog-output analog-output-lineout analog-output-speaker priority = 13 direction = output [Mapping analog-surround-50] device-strings = surround50:%f channel-map = front-left,front-right,rear-left,rear-right,front-center paths-output = audigy-analog-output analog-output-lineout analog-output-speaker priority = 12 direction = output [Mapping analog-surround-51] device-strings = surround51:%f channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe paths-output = audigy-analog-output analog-output-lineout analog-output-speaker priority = 13 direction = output [Mapping analog-surround-71] device-strings = surround71:%f channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe,side-left,side-right description = Analog Surround 7.1 paths-output = audigy-analog-output analog-output-lineout analog-output-speaker priority = 12 direction = output [Mapping iec958-stereo] device-strings = iec958:%f channel-map = left,right paths-input = iec958-stereo-input paths-output = iec958-stereo-output priority = 5 cmedia-high-speed-true-hdaudio.conf000066400000000000000000000054631511204443500360470ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/mixer/profile-sets# This file is part of PulseAudio. # # PulseAudio is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as # published by the Free Software Foundation; either version 2.1 of the # License, or (at your option) any later version. # # PulseAudio is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with PulseAudio; if not, see . # Config for CMEDIA USB2.0 High-Speed True HD Audio 147a:e055 # Added by Jean-Philippe Guillemin [General] auto-profiles = yes [Mapping analog-stereo] device-strings = front:%f channel-map = left,right paths-output = analog-output analog-output-lineout analog-output-speaker analog-output-headphones analog-output-headphones-2 paths-input = analog-input-front-mic analog-input-rear-mic analog-input-internal-mic analog-input-dock-mic analog-input analog-input-mic analog-input-linein analog-input-aux analog-input-video analog-input-tvtuner analog-input-fm analog-input-mic-line analog-input-headphone-mic analog-input-headset-mic priority = 10 # If everything else fails, try to use hw:0 as a stereo device. [Mapping stereo-fallback] device-strings = hw:%f fallback = yes channel-map = front-left,front-right paths-output = analog-output analog-output-lineout analog-output-speaker analog-output-headphones analog-output-headphones-2 paths-input = analog-input-front-mic analog-input-rear-mic analog-input-internal-mic analog-input-dock-mic analog-input analog-input-mic analog-input-linein analog-input-aux analog-input-video analog-input-tvtuner analog-input-fm analog-input-mic-line analog-input-headphone-mic analog-input-headset-mic priority = 1 [Mapping analog-surround-21] device-strings = surround21:%f channel-map = front-left,front-right,lfe paths-output = analog-output analog-output-lineout analog-output-speaker priority = 8 direction = output [Mapping analog-surround-51] device-strings = surround51:%f channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe paths-output = analog-output analog-output-lineout analog-output-speaker priority = 8 direction = output [Mapping analog-surround-71] device-strings = surround71:%f channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe,side-left,side-right description = Analog Surround 7.1 paths-output = analog-output analog-output-lineout analog-output-speaker priority = 7 direction = output [Mapping iec958-stereo] device-strings = hw:%f,2 hw:%f,0 channel-map = left,right paths-output = iec958-stereo-output paths-input = iec958-stereo-input priority = 5 default.conf000066400000000000000000000535421511204443500316470ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/mixer/profile-sets# This file is part of PulseAudio. # # PulseAudio is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as # published by the Free Software Foundation; either version 2.1 of the # License, or (at your option) any later version. # # PulseAudio is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with PulseAudio; if not, see . ; Default profile definitions for the ALSA backend of PulseAudio. This ; is used as fallback for all cards that have no special mapping ; assigned (and should be good enough for the vast majority of ; cards). If you want to assign a different profile set than this one ; to a device, either set the udev property ACP_PROFILE_SET for the ; card, or use the "profile_set" module argument when loading ; module-alsa-card. ; ; So what is this about? Simply, what we do here is map ALSA devices ; to how they are exposed in PA. We say which ALSA device string to ; use to open a device, which channel mapping to use then, and which ; mixer path to use. This is encoded in a 'mapping'. Multiple of these ; mappings can be bound together in a 'profile' which is then directly ; exposed in the UI as a card profile. Each mapping assigned to a ; profile will result in one sink/source to be created if the profile ; is selected for the card. ; ; Additionally, the path set configuration files can describe the ; decibel values assigned to the steps of the volume elements. This ; can be used to work around situations when the alsa driver doesn't ; provide any decibel information, or when the information is ; incorrect. ; [General] ; auto-profiles = no | yes # Instead of defining all profiles manually, autogenerate ; # them by combining every input mapping with every output mapping. ; ; [Mapping id] ; device-strings = ... # ALSA device string. %f will be replaced by the card identifier. ; channel-map = ... # Channel mapping to use for this device ; description = ... # Description for the mapping. Note that it's better to set the description ; # in the well_known_descriptions table in alsa-mixer.c than with this ; # option, because the descriptions in alsa-mixer.c are translatable. ; description-key = ... # A custom key for the well_known_descriptions table (by default the mapping ; # name is used). ; paths-input = ... # A list of mixer paths to use. Every path in this list will be probed. ; # If multiple are found to be working they will be available as device ports ; paths-output = ... ; element-input = ... # Instead of configuring a full mixer path simply configure a single ; # mixer element for volume/mute handling. The value can be an element ; # name, or name and index separated by a comma. ; element-output = ... ; priority = ... ; direction = any | input | output # Only useful for? ; ; exact-channels = yes | no # If no, and the exact number of channels is not supported, ; # allow device to be opened with another channel count ; fallback = no | yes # This mapping will only be considered if all non-fallback mappings fail ; intended-roles = ... # Set the device.intended_roles property for the sink/source. ; ; [Profile id] ; input-mappings = ... # Lists mappings for sources on this profile, those mapping must be ; # defined in this file too ; output-mappings = ... # Lists mappings for sinks on this profile, those mappings must be ; # defined in this file too ; description = ... ; priority = ... # Numeric value to deduce priority for this profile ; skip-probe = no | yes # Skip probing for availability? If this is yes then this profile ; # will be assumed as working without probing. Makes initialization ; # a bit faster but only works if the card is really known well. ; ; fallback = no | yes # This profile will only be considered if all non-fallback profiles fail ; [DecibelFix element] # Decibel fixes can be used to work around missing or incorrect dB ; # information from alsa. A decibel fix is a table that maps volume steps ; # to decibel values for one volume element. The "element" part in the ; # section title is the name of the volume element (or name and index ; # separated by a comma). ; # ; # NOTE: This feature is meant just as a help for figuring out the correct ; # decibel values. PulseAudio is not the correct place to maintain the ; # decibel mappings! ; # ; # If you need this feature, then you should make sure that when you have ; # the correct values figured out, the alsa driver developers get informed ; # too, so that they can fix the driver. ; ; db-values = ... # The option value consists of pairs of step numbers and decibel values. ; # The pairs are separated with whitespace, and steps are separated from ; # the corresponding decibel values with a colon. The values must be in an ; # increasing order. Here's an example of a valid string: ; # ; # "0:-40.50 1:-38.70 3:-33.00 11:0" ; # ; # The lowest step imposes a lower limit for hardware volume and the ; # highest step correspondingly imposes a higher limit. That means that ; # that the mixer will never be set outside those values - the rest of the ; # volume scale is done using software volume. ; # ; # As can be seen in the example, you don't need to specify a dB value for ; # each step. The dB values for skipped steps will be linearly interpolated ; # using the nearest steps that are given. [General] auto-profiles = yes [Mapping analog-stereo] device-strings = front:%f channel-map = left,right paths-output = analog-output analog-output-lineout analog-output-speaker analog-output-headphones analog-output-headphones-2 paths-input = analog-input-front-mic analog-input-rear-mic analog-input-internal-mic analog-input-dock-mic analog-input analog-input-mic analog-input-linein analog-input-aux analog-input-video analog-input-tvtuner analog-input-fm analog-input-mic-line analog-input-headphone-mic analog-input-headset-mic priority = 15 # If everything else fails, try to use hw:0 as a stereo device... [Mapping stereo-fallback] device-strings = hw:%f fallback = yes channel-map = front-left,front-right paths-output = analog-output analog-output-lineout analog-output-speaker analog-output-headphones analog-output-headphones-2 paths-input = analog-input-front-mic analog-input-rear-mic analog-input-internal-mic analog-input-dock-mic analog-input analog-input-mic analog-input-linein analog-input-aux analog-input-video analog-input-tvtuner analog-input-fm analog-input-mic-line analog-input-headphone-mic analog-input-headset-mic priority = 1 # ...and if even that fails, try to use hw:0 as a mono device. [Mapping mono-fallback] device-strings = hw:%f fallback = yes channel-map = mono paths-output = analog-output analog-output-lineout analog-output-speaker analog-output-headphones analog-output-headphones-2 analog-output-mono paths-input = analog-input-front-mic analog-input-rear-mic analog-input-internal-mic analog-input-dock-mic analog-input analog-input-mic analog-input-linein analog-input-aux analog-input-video analog-input-tvtuner analog-input-fm analog-input-mic-line analog-input-headset-mic priority = 1 [Mapping analog-surround-21] device-strings = surround21:%f channel-map = front-left,front-right,lfe paths-input = analog-input analog-input-linein analog-input-mic paths-output = analog-output analog-output-lineout analog-output-speaker priority = 13 [Mapping analog-surround-40] device-strings = surround40:%f channel-map = front-left,front-right,rear-left,rear-right paths-input = analog-input analog-input-linein analog-input-mic paths-output = analog-output analog-output-lineout analog-output-speaker priority = 12 [Mapping analog-surround-41] device-strings = surround41:%f channel-map = front-left,front-right,rear-left,rear-right,lfe paths-input = analog-input analog-input-linein analog-input-mic paths-output = analog-output analog-output-lineout analog-output-speaker priority = 13 [Mapping analog-surround-50] device-strings = surround50:%f channel-map = front-left,front-right,rear-left,rear-right,front-center paths-input = analog-input analog-input-linein analog-input-mic paths-output = analog-output analog-output-lineout analog-output-speaker priority = 12 [Mapping analog-surround-51] device-strings = surround51:%f channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe paths-input = analog-input analog-input-linein analog-input-mic paths-output = analog-output analog-output-lineout analog-output-speaker priority = 13 [Mapping analog-surround-71] device-strings = surround71:%f channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe,side-left,side-right description = Analog Surround 7.1 paths-input = analog-input analog-input-linein analog-input-mic paths-output = analog-output analog-output-lineout analog-output-speaker priority = 12 [Mapping iec958-stereo] device-strings = iec958:%f channel-map = left,right paths-input = iec958-stereo-input paths-output = iec958-stereo-output priority = 5 [Mapping iec958-ac3-surround-40] device-strings = a52:%f channel-map = front-left,front-right,rear-left,rear-right paths-output = iec958-stereo-output priority = 2 direction = output [Mapping iec958-ac3-surround-51] device-strings = a52:%f channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe paths-output = iec958-stereo-output priority = 3 direction = output [Mapping iec958-dts-surround-51] device-strings = dca:%f channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe paths-output = iec958-stereo-output priority = 3 direction = output [Mapping hdmi-stereo] description = Digital Stereo (HDMI) device-strings = hdmi:%f paths-output = hdmi-output-0 channel-map = left,right priority = 9 direction = output [Mapping hdmi-surround] description = Digital Surround 5.1 (HDMI) device-strings = hdmi:%f paths-output = hdmi-output-0 channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe priority = 8 direction = output [Mapping hdmi-surround71] description = Digital Surround 7.1 (HDMI) device-strings = hdmi:%f paths-output = hdmi-output-0 channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe,side-left,side-right priority = 8 direction = output [Mapping hdmi-dts-surround] description = Digital Surround 5.1 (HDMI/DTS) device-strings = dcahdmi:%f paths-output = hdmi-output-0 channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe priority = 6 direction = output [Mapping hdmi-stereo-extra1] description = Digital Stereo (HDMI 2) device-strings = hdmi:%f,1 paths-output = hdmi-output-1 channel-map = left,right priority = 7 direction = output [Mapping hdmi-surround-extra1] description = Digital Surround 5.1 (HDMI 2) device-strings = hdmi:%f,1 paths-output = hdmi-output-1 channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe priority = 6 direction = output [Mapping hdmi-surround71-extra1] description = Digital Surround 7.1 (HDMI 2) device-strings = hdmi:%f,1 paths-output = hdmi-output-1 channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe,side-left,side-right priority = 6 direction = output [Mapping hdmi-dts-surround-extra1] description = Digital Surround 5.1 (HDMI 2/DTS) device-strings = dcahdmi:%f,1 paths-output = hdmi-output-1 channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe priority = 6 direction = output [Mapping hdmi-stereo-extra2] description = Digital Stereo (HDMI 3) device-strings = hdmi:%f,2 paths-output = hdmi-output-2 channel-map = left,right priority = 7 direction = output [Mapping hdmi-surround-extra2] description = Digital Surround 5.1 (HDMI 3) device-strings = hdmi:%f,2 paths-output = hdmi-output-2 channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe priority = 6 direction = output [Mapping hdmi-surround71-extra2] description = Digital Surround 7.1 (HDMI 3) device-strings = hdmi:%f,2 paths-output = hdmi-output-2 channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe,side-left,side-right priority = 6 direction = output [Mapping hdmi-dts-surround-extra2] description = Digital Surround 5.1 (HDMI 3/DTS) device-strings = dcahdmi:%f,2 paths-output = hdmi-output-2 channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe priority = 6 direction = output [Mapping hdmi-stereo-extra3] description = Digital Stereo (HDMI 4) device-strings = hdmi:%f,3 paths-output = hdmi-output-3 channel-map = left,right priority = 7 direction = output [Mapping hdmi-surround-extra3] description = Digital Surround 5.1 (HDMI 4) device-strings = hdmi:%f,3 paths-output = hdmi-output-3 channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe priority = 6 direction = output [Mapping hdmi-surround71-extra3] description = Digital Surround 7.1 (HDMI 4) device-strings = hdmi:%f,3 paths-output = hdmi-output-3 channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe,side-left,side-right priority = 6 direction = output [Mapping hdmi-dts-surround-extra3] description = Digital Surround 5.1 (HDMI 4/DTS) device-strings = dcahdmi:%f,3 paths-output = hdmi-output-3 channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe priority = 6 direction = output [Mapping hdmi-stereo-extra4] description = Digital Stereo (HDMI 5) device-strings = hdmi:%f,4 paths-output = hdmi-output-4 channel-map = left,right priority = 7 direction = output [Mapping hdmi-surround-extra4] description = Digital Surround 5.1 (HDMI 5) device-strings = hdmi:%f,4 paths-output = hdmi-output-4 channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe priority = 6 direction = output [Mapping hdmi-surround71-extra4] description = Digital Surround 7.1 (HDMI 5) device-strings = hdmi:%f,4 paths-output = hdmi-output-4 channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe,side-left,side-right priority = 6 direction = output [Mapping hdmi-dts-surround-extra4] description = Digital Surround 5.1 (HDMI 5/DTS) device-strings = dcahdmi:%f,4 paths-output = hdmi-output-4 channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe priority = 6 direction = output [Mapping hdmi-stereo-extra5] description = Digital Stereo (HDMI 6) device-strings = hdmi:%f,5 paths-output = hdmi-output-5 channel-map = left,right priority = 7 direction = output [Mapping hdmi-surround-extra5] description = Digital Surround 5.1 (HDMI 6) device-strings = hdmi:%f,5 paths-output = hdmi-output-5 channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe priority = 6 direction = output [Mapping hdmi-surround71-extra5] description = Digital Surround 7.1 (HDMI 6) device-strings = hdmi:%f,5 paths-output = hdmi-output-5 channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe,side-left,side-right priority = 6 direction = output [Mapping hdmi-dts-surround-extra5] description = Digital Surround 5.1 (HDMI 6/DTS) device-strings = dcahdmi:%f,5 paths-output = hdmi-output-5 channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe priority = 6 direction = output [Mapping hdmi-stereo-extra6] description = Digital Stereo (HDMI 7) device-strings = hdmi:%f,6 paths-output = hdmi-output-6 channel-map = left,right priority = 7 direction = output [Mapping hdmi-surround-extra6] description = Digital Surround 5.1 (HDMI 7) device-strings = hdmi:%f,6 paths-output = hdmi-output-6 channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe priority = 6 direction = output [Mapping hdmi-surround71-extra6] description = Digital Surround 7.1 (HDMI 7) device-strings = hdmi:%f,6 paths-output = hdmi-output-6 channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe,side-left,side-right priority = 6 direction = output [Mapping hdmi-dts-surround-extra6] description = Digital Surround 5.1 (HDMI 7/DTS) device-strings = dcahdmi:%f,6 paths-output = hdmi-output-6 channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe priority = 6 direction = output [Mapping hdmi-stereo-extra7] description = Digital Stereo (HDMI 8) device-strings = hdmi:%f,7 paths-output = hdmi-output-7 channel-map = left,right priority = 7 direction = output [Mapping hdmi-surround-extra7] description = Digital Surround 5.1 (HDMI 8) device-strings = hdmi:%f,7 paths-output = hdmi-output-7 channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe priority = 6 direction = output [Mapping hdmi-surround71-extra7] description = Digital Surround 7.1 (HDMI 8) device-strings = hdmi:%f,7 paths-output = hdmi-output-7 channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe,side-left,side-right priority = 6 direction = output [Mapping hdmi-dts-surround-extra7] description = Digital Surround 5.1 (HDMI 8/DTS) device-strings = dcahdmi:%f,7 paths-output = hdmi-output-7 channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe priority = 6 direction = output [Mapping hdmi-stereo-extra8] description = Digital Stereo (HDMI 9) device-strings = hdmi:%f,8 paths-output = hdmi-output-8 channel-map = left,right priority = 7 direction = output [Mapping hdmi-surround-extra8] description = Digital Surround 5.1 (HDMI 9) device-strings = hdmi:%f,8 paths-output = hdmi-output-8 channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe priority = 6 direction = output [Mapping hdmi-surround71-extra8] description = Digital Surround 7.1 (HDMI 9) device-strings = hdmi:%f,8 paths-output = hdmi-output-8 channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe,side-left,side-right priority = 6 direction = output [Mapping hdmi-dts-surround-extra8] description = Digital Surround 5.1 (HDMI 9/DTS) device-strings = dcahdmi:%f,8 paths-output = hdmi-output-8 channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe priority = 6 direction = output [Mapping hdmi-stereo-extra9] description = Digital Stereo (HDMI 10) device-strings = hdmi:%f,9 paths-output = hdmi-output-9 channel-map = left,right priority = 7 direction = output [Mapping hdmi-surround-extra9] description = Digital Surround 5.1 (HDMI 10) device-strings = hdmi:%f,9 paths-output = hdmi-output-9 channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe priority = 6 direction = output [Mapping hdmi-surround71-extra9] description = Digital Surround 7.1 (HDMI 10) device-strings = hdmi:%f,9 paths-output = hdmi-output-9 channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe,side-left,side-right priority = 6 direction = output [Mapping hdmi-dts-surround-extra9] description = Digital Surround 5.1 (HDMI 10/DTS) device-strings = dcahdmi:%f,9 paths-output = hdmi-output-9 channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe priority = 6 direction = output [Mapping hdmi-stereo-extra10] description = Digital Stereo (HDMI 11) device-strings = hdmi:%f,10 paths-output = hdmi-output-10 channel-map = left,right priority = 7 direction = output [Mapping hdmi-surround-extra10] description = Digital Surround 5.1 (HDMI 11) device-strings = hdmi:%f,10 paths-output = hdmi-output-10 channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe priority = 6 direction = output [Mapping hdmi-surround71-extra10] description = Digital Surround 7.1 (HDMI 11) device-strings = hdmi:%f,10 paths-output = hdmi-output-10 channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe,side-left,side-right priority = 6 direction = output [Mapping hdmi-dts-surround-extra10] description = Digital Surround 5.1 (HDMI 11/DTS) device-strings = dcahdmi:%f,10 paths-output = hdmi-output-10 channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe priority = 6 direction = output [Mapping multichannel-output] device-strings = hw:%f channel-map = left,right,rear-left,rear-right exact-channels = false fallback = yes priority = 1 direction = output [Mapping multichannel-input] device-strings = hw:%f channel-map = left,right,rear-left,rear-right exact-channels = false fallback = yes priority = 1 direction = input .include 9999-custom.conf dell-dock-tb16-usb-audio.conf000066400000000000000000000030531511204443500345110ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/mixer/profile-sets# This file is part of PulseAudio. # # PulseAudio is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as # published by the Free Software Foundation; either version 2.1 of the # License, or (at your option) any later version. # # PulseAudio is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with PulseAudio; if not, see . ; Dell Dock TB16 USB audio ; ; This card has two stereo pairs of output, One Mono input. ; ; See default.conf for an explanation on the directives used here. [General] auto-profiles = no [Mapping analog-stereo-headphone] description = Headphone device-strings = hw:%f,0,0 channel-map = left,right direction = output [Mapping analog-stereo-speaker] description = Speaker device-strings = hw:%f,1,0 channel-map = left,right direction = output [Mapping analog-stereo-mic] description = Headset-Mic device-strings = hw:%f,0,0 channel-map = left,right direction = input [Profile output:analog-stereo-speaker] description = Speaker output-mappings = analog-stereo-speaker priority = 60 skip-probe = yes [Profile output:analog-stereo-headphone+input:analog-stereo-mic] description = Headset output-mappings = analog-stereo-headphone input-mappings = analog-stereo-mic priority = 80 skip-probe = yes force-speaker-and-int-mic.conf000066400000000000000000000125631511204443500350450ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/mixer/profile-sets# This file is part of PulseAudio. # # PulseAudio is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as # published by the Free Software Foundation; either version 2.1 of the # License, or (at your option) any later version. # # PulseAudio is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with PulseAudio; if not, see . ; This profile forces speaker and internal mic ports even if we have no way ; of identifying those. ; See default.conf for explanations. [General] auto-profiles = yes [Mapping analog-mono] device-strings = hw:%f channel-map = mono paths-output = analog-output analog-output-lineout analog-output-speaker-always analog-output-headphones analog-output-headphones-2 analog-output-mono paths-input = analog-input-front-mic analog-input-rear-mic analog-input-internal-mic-always analog-input-dock-mic analog-input analog-input-mic analog-input-linein analog-input-aux analog-input-video analog-input-tvtuner analog-input-fm analog-input-mic-line priority = 1 [Mapping analog-stereo] device-strings = front:%f hw:%f channel-map = left,right paths-output = analog-output analog-output-lineout analog-output-speaker-always analog-output-headphones analog-output-headphones-2 analog-output-mono paths-input = analog-input-front-mic analog-input-rear-mic analog-input-internal-mic-always analog-input-dock-mic analog-input analog-input-mic analog-input-linein analog-input-aux analog-input-video analog-input-tvtuner analog-input-fm analog-input-mic-line priority = 10 [Mapping analog-surround-21] device-strings = surround21:%f channel-map = front-left,front-right,lfe paths-output = analog-output analog-output-lineout analog-output-speaker-always priority = 8 direction = output [Mapping analog-surround-40] device-strings = surround40:%f channel-map = front-left,front-right,rear-left,rear-right paths-output = analog-output analog-output-lineout analog-output-speaker-always priority = 7 direction = output [Mapping analog-surround-41] device-strings = surround41:%f channel-map = front-left,front-right,rear-left,rear-right,lfe paths-output = analog-output analog-output-lineout analog-output-speaker-always priority = 8 direction = output [Mapping analog-surround-50] device-strings = surround50:%f channel-map = front-left,front-right,rear-left,rear-right,front-center paths-output = analog-output analog-output-lineout analog-output-speaker-always priority = 7 direction = output [Mapping analog-surround-51] device-strings = surround51:%f channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe paths-output = analog-output analog-output-lineout analog-output-speaker-always priority = 8 direction = output [Mapping analog-surround-71] device-strings = surround71:%f channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe,side-left,side-right description = Analog Surround 7.1 paths-output = analog-output analog-output-lineout analog-output-speaker-always priority = 7 direction = output [Mapping analog-4-channel-input] # Alsa doesn't currently provide any better device name than "hw" for 4-channel # input. If this causes trouble at some point, then we will need to get a new # device name standardized in alsa. device-strings = hw:%f channel-map = aux0,aux1,aux2,aux3 priority = 1 direction = input [Mapping iec958-stereo] device-strings = iec958:%f channel-map = left,right paths-input = iec958-stereo-input paths-output = iec958-stereo-output priority = 5 [Mapping iec958-ac3-surround-40] device-strings = a52:%f channel-map = front-left,front-right,rear-left,rear-right paths-output = iec958-stereo-output priority = 2 direction = output [Mapping iec958-ac3-surround-51] device-strings = a52:%f channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe paths-output = iec958-stereo-output priority = 3 direction = output [Mapping iec958-dts-surround-51] device-strings = dca:%f channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe paths-output = iec958-stereo-output priority = 3 direction = output [Mapping hdmi-stereo] description = Digital Stereo (HDMI) device-strings = hdmi:%f paths-output = hdmi-output-0 channel-map = left,right priority = 4 direction = output [Mapping hdmi-surround] description = Digital Surround 5.1 (HDMI) device-strings = hdmi:%f paths-output = hdmi-output-0 channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe priority = 3 direction = output [Mapping hdmi-surround71] description = Digital Surround 7.1 (HDMI) device-strings = hdmi:%f paths-output = hdmi-output-0 channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe,side-left,side-right priority = 3 direction = output [Mapping hdmi-dts-surround] description = Digital Surround 5.1 (HDMI/DTS) device-strings = dcahdmi:%f paths-output = hdmi-output-0 channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe priority = 1 direction = output ; An example for defining multiple-sink profiles #[Profile output:analog-stereo+output:iec958-stereo+input:analog-stereo] #description = Foobar #output-mappings = analog-stereo iec958-stereo #input-mappings = analog-stereo force-speaker.conf000066400000000000000000000125201511204443500327400ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/mixer/profile-sets# This file is part of PulseAudio. # # PulseAudio is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as # published by the Free Software Foundation; either version 2.1 of the # License, or (at your option) any later version. # # PulseAudio is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with PulseAudio; if not, see . ; This profile forces a speaker port even if we have no way of identifying it. ; See default.conf for explanations. [General] auto-profiles = yes [Mapping analog-mono] device-strings = hw:%f channel-map = mono paths-output = analog-output analog-output-lineout analog-output-speaker-always analog-output-headphones analog-output-headphones-2 analog-output-mono paths-input = analog-input-front-mic analog-input-rear-mic analog-input-internal-mic analog-input-dock-mic analog-input analog-input-mic analog-input-linein analog-input-aux analog-input-video analog-input-tvtuner analog-input-fm analog-input-mic-line priority = 1 [Mapping analog-stereo] device-strings = front:%f hw:%f channel-map = left,right paths-output = analog-output analog-output-lineout analog-output-speaker-always analog-output-headphones analog-output-headphones-2 analog-output-mono paths-input = analog-input-front-mic analog-input-rear-mic analog-input-internal-mic analog-input-dock-mic analog-input analog-input-mic analog-input-linein analog-input-aux analog-input-video analog-input-tvtuner analog-input-fm analog-input-mic-line priority = 10 [Mapping analog-surround-21] device-strings = surround21:%f channel-map = front-left,front-right,lfe paths-output = analog-output analog-output-lineout analog-output-speaker-always priority = 8 direction = output [Mapping analog-surround-40] device-strings = surround40:%f channel-map = front-left,front-right,rear-left,rear-right paths-output = analog-output analog-output-lineout analog-output-speaker-always priority = 7 direction = output [Mapping analog-surround-41] device-strings = surround41:%f channel-map = front-left,front-right,rear-left,rear-right,lfe paths-output = analog-output analog-output-lineout analog-output-speaker-always priority = 8 direction = output [Mapping analog-surround-50] device-strings = surround50:%f channel-map = front-left,front-right,rear-left,rear-right,front-center paths-output = analog-output analog-output-lineout analog-output-speaker-always priority = 7 direction = output [Mapping analog-surround-51] device-strings = surround51:%f channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe paths-output = analog-output analog-output-lineout analog-output-speaker-always priority = 8 direction = output [Mapping analog-surround-71] device-strings = surround71:%f channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe,side-left,side-right description = Analog Surround 7.1 paths-output = analog-output analog-output-lineout analog-output-speaker-always priority = 7 direction = output [Mapping analog-4-channel-input] # Alsa doesn't currently provide any better device name than "hw" for 4-channel # input. If this causes trouble at some point, then we will need to get a new # device name standardized in alsa. device-strings = hw:%f channel-map = aux0,aux1,aux2,aux3 priority = 1 direction = input [Mapping iec958-stereo] device-strings = iec958:%f channel-map = left,right paths-input = iec958-stereo-input paths-output = iec958-stereo-output priority = 5 [Mapping iec958-ac3-surround-40] device-strings = a52:%f channel-map = front-left,front-right,rear-left,rear-right paths-output = iec958-stereo-output priority = 2 direction = output [Mapping iec958-ac3-surround-51] device-strings = a52:%f channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe paths-output = iec958-stereo-output priority = 3 direction = output [Mapping iec958-dts-surround-51] device-strings = dca:%f channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe paths-output = iec958-stereo-output priority = 3 direction = output [Mapping hdmi-stereo] description = Digital Stereo (HDMI) device-strings = hdmi:%f paths-output = hdmi-output-0 channel-map = left,right priority = 4 direction = output [Mapping hdmi-surround] description = Digital Surround 5.1 (HDMI) device-strings = hdmi:%f paths-output = hdmi-output-0 channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe priority = 3 direction = output [Mapping hdmi-surround71] description = Digital Surround 7.1 (HDMI) device-strings = hdmi:%f paths-output = hdmi-output-0 channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe,side-left,side-right priority = 3 direction = output [Mapping hdmi-dts-surround] description = Digital Surround 5.1 (HDMI/DTS) device-strings = dcahdmi:%f paths-output = hdmi-output-0 channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe priority = 1 direction = output ; An example for defining multiple-sink profiles #[Profile output:analog-stereo+output:iec958-stereo+input:analog-stereo] #description = Foobar #output-mappings = analog-stereo iec958-stereo #input-mappings = analog-stereo hdmi-ac3.conf000066400000000000000000000074361511204443500316110ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/mixer/profile-sets# This file is part of PulseAudio. # # PulseAudio is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as # published by the Free Software Foundation; either version 2.1 of the # License, or (at your option) any later version. # # PulseAudio is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with PulseAudio; if not, see . ; Profile set with HDMI/AC3 profiles. ; ; You can use udev rules to enable these, for example: ; ; ATTRS{subsystem_vendor}=="0x1849", ATTRS{subsystem_device}=="0xaaf0", ENV{ACP_PROFILE_SET}="hdmi-ac3.conf" .include default.conf [Mapping hdmi-ac3-surround] description = Digital Surround 5.1 (HDMI/AC3) device-strings = plug:{SLAVE="a52:%f,'hw:%f,3'"} paths-output = hdmi-output-0 channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe priority = 1 direction = output [Mapping hdmi-ac3-surround-extra1] description = Digital Surround 5.1 (HDMI 2/AC3) device-strings = plug:{SLAVE="a52:%f,'hw:%f,7'"} paths-output = hdmi-output-1 channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe priority = 1 direction = output [Mapping hdmi-ac3-surround-extra2] description = Digital Surround 5.1 (HDMI 3/AC3) device-strings = plug:{SLAVE="a52:%f,'hw:%f,8'"} paths-output = hdmi-output-2 channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe priority = 1 direction = output [Mapping hdmi-ac3-surround-extra3] description = Digital Surround 5.1 (HDMI 4/AC3) device-strings = plug:{SLAVE="a52:%f,'hw:%f,9'"} paths-output = hdmi-output-3 channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe priority = 1 direction = output [Mapping hdmi-ac3-surround-extra4] description = Digital Surround 5.1 (HDMI 5/AC3) device-strings = plug:{SLAVE="a52:%f,'hw:%f,10'"} paths-output = hdmi-output-4 channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe priority = 1 direction = output [Mapping hdmi-ac3-surround-extra5] description = Digital Surround 5.1 (HDMI 6/AC3) device-strings = plug:{SLAVE="a52:%f,'hw:%f,11'"} paths-output = hdmi-output-5 channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe priority = 1 direction = output [Mapping hdmi-ac3-surround-extra6] description = Digital Surround 5.1 (HDMI 7/AC3) device-strings = plug:{SLAVE="a52:%f,'hw:%f,12'"} paths-output = hdmi-output-6 channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe priority = 1 direction = output [Mapping hdmi-ac3-surround-extra7] description = Digital Surround 5.1 (HDMI 8/AC3) device-strings = plug:{SLAVE="a52:%f,'hw:%f,13'"} paths-output = hdmi-output-7 channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe priority = 1 direction = output [Mapping hdmi-ac3-surround-extra8] description = Digital Surround 5.1 (HDMI 9/AC3) device-strings = plug:{SLAVE="a52:%f,'hw:%f,14'"} paths-output = hdmi-output-8 channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe priority = 1 direction = output [Mapping hdmi-ac3-surround-extra9] description = Digital Surround 5.1 (HDMI 10/AC3) device-strings = plug:{SLAVE="a52:%f,'hw:%f,15'"} paths-output = hdmi-output-9 channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe priority = 1 direction = output [Mapping hdmi-ac3-surround-extra10] description = Digital Surround 5.1 (HDMI 11/AC3) device-strings = plug:{SLAVE="a52:%f,'hw:%f,16'"} paths-output = hdmi-output-10 channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe priority = 1 direction = output hp-tbt-dock-120w-g2.conf000066400000000000000000000024431511204443500333260ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/mixer/profile-sets# This file is part of PulseAudio. # # PulseAudio is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as # published by the Free Software Foundation; either version 2.1 of the # License, or (at your option) any later version. # # PulseAudio is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with PulseAudio; if not, see . ; HP Thunderbolt Dock 120W G2 ; ; This dock has a 3.5mm headset connector. Both input and output are stereo. ; ; There's a separate speakerphone module called "HP Thunderbolt Dock Audio ; Module", which can be attached to this dock. The module will appear in ALSA ; as a separate USB sound card, configuration for it is in ; hp-tbt-dock-audio-module.conf. [General] auto-profiles = no [Mapping analog-stereo-headset] device-strings = hw:%f,0,0 channel-map = left,right [Profile output:analog-stereo-headset+input:analog-stereo-headset] output-mappings = analog-stereo-headset input-mappings = analog-stereo-headset skip-probe = yes hp-tbt-dock-audio-module.conf000066400000000000000000000025631511204443500347160ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/mixer/profile-sets# This file is part of PulseAudio. # # PulseAudio is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as # published by the Free Software Foundation; either version 2.1 of the # License, or (at your option) any later version. # # PulseAudio is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with PulseAudio; if not, see . ; HP Thunderbolt Dock Audio Module ; ; This device attaches to the "HP Thunderbolt Dock 120W G2" dock. The audio ; module provides a speakerphone with echo cancellation and appears in ALSA as ; a USB sound card with stereo input and output. ; ; The dock itself has a 3.5mm headset connector and appears as a separate USB ; sound card, configuration for it is in hp-tbt-dock-120w-g2.conf. [General] auto-profiles = no [Mapping analog-stereo-speakerphone] device-strings = hw:%f,0,0 channel-map = left,right intended-roles = phone [Profile output:analog-stereo-speakerphone+input:analog-stereo-speakerphone] output-mappings = analog-stereo-speakerphone input-mappings = analog-stereo-speakerphone skip-probe = yes kinect-audio.conf000066400000000000000000000024041511204443500325660ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/mixer/profile-sets# This file is part of PulseAudio. # # PulseAudio is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as # published by the Free Software Foundation; either version 2.1 of the # License, or (at your option) any later version. # # PulseAudio is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with PulseAudio; if not, see . ; Audio profile for the Microsoft Kinect Sensor device in UAC mode. ; ; Copyright (C) 2011 Antonio Ospite ; ; This device has an array of four microphones, and no playback capability. ; ; See default.conf for an explanation on the directives used here. [General] auto-profiles = no [Mapping input-4-channels] device-strings = hw:%f channel-map = front-left,front-right,rear-left,rear-right description = 4 Channels Input direction = input priority = 5 [Profile input:mic-array] description = Microphone Array input-mappings = input-4-channels priority = 2 skip-probe = yes logi407.conf000066400000000000000000000023401511204443500313760ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/mixer/profile-sets# This file is part of PulseAudio. # # PulseAudio is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as # published by the Free Software Foundation; either version 2.1 of the # License, or (at your option) any later version. # # PulseAudio is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with PulseAudio; if not, see . ; Logitech Z407 Stereo PC Speaker Set ; ; These are copies of the mappings we find in default.conf, but with ; the 'PCM' control used also in the iec958 output path ; ; See default.conf for an explanation on the directives used here. [General] auto-profiles = yes # Based on analog-stereo [Mapping analog-stereo] device-strings = front:%f channel-map = left,right paths-output = analog-output priority = 15 # Based on iec958-stereo [Mapping iec958-stereo] device-strings = iec958:%f channel-map = left,right paths-output = logi407-iec958-stereo-output priority = 5 maudio-fasttrack-pro.conf000066400000000000000000000052321511204443500342500ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/mixer/profile-sets# This file is part of PulseAudio. # # PulseAudio is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as # published by the Free Software Foundation; either version 2.1 of the # License, or (at your option) any later version. # # PulseAudio is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with PulseAudio; if not, see . ; M-Audio FastTrack Pro ; ; This card has one duplex stereo channel called A and an additional ; stereo output channel called B. ; ; We knowingly only define a subset of the theoretically possible ; mapping combinations as profiles here. ; ; See default.conf for an explanation on the directives used here. [General] auto-profiles = no [Mapping analog-stereo-a-output] description = Analog Stereo Channel A device-strings = hw:%f,0,0 channel-map = left,right direction = output ; Try both device 0 and device 1 for input, see ; http://mailman.alsa-project.org/pipermail/alsa-devel/2012-March/050701.html [Mapping analog-stereo-a-input] description = Analog Stereo Channel A device-strings = hw:%f,0,0 hw:%f,1,0 channel-map = left,right direction = input [Mapping analog-stereo-b-output] description = Analog Stereo Channel B device-strings = hw:%f,1,0 channel-map = left,right direction = output [Profile output:analog-stereo-all+input:analog-stereo-all] description = Analog Stereo Duplex Channel A, Analog Stereo output Channel B output-mappings = analog-stereo-a-output analog-stereo-b-output input-mappings = analog-stereo-a-input priority = 100 skip-probe = yes [Profile output:analog-stereo-a-output+input:analog-stereo-a-input] description = Analog Stereo Duplex Channel A output-mappings = analog-stereo-a-output input-mappings = analog-stereo-a-input priority = 40 skip-probe = yes [Profile output:analog-stereo-b+input:analog-stereo-b] description = Analog Stereo Output Channel B output-mappings = analog-stereo-b-output input-mappings = priority = 50 skip-probe = yes [Profile output:analog-stereo-a] description = Analog Stereo Output Channel A output-mappings = analog-stereo-a-output priority = 5 skip-probe = yes [Profile output:analog-stereo-b] description = Analog Stereo Output Channel B output-mappings = analog-stereo-b-output priority = 6 skip-probe = yes [Profile input:analog-stereo-a] description = Analog Stereo Input Channel A input-mappings = analog-stereo-a-input priority = 2 skip-probe = yes native-instruments-audio4dj.conf000066400000000000000000000053211511204443500355730ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/mixer/profile-sets# This file is part of PulseAudio. # # PulseAudio is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as # published by the Free Software Foundation; either version 2.1 of the # License, or (at your option) any later version. # # PulseAudio is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with PulseAudio; if not, see . ; Native Instruments Audio 4 DJ ; ; This card has two stereo pairs of input and two stereo pairs of ; output, named channels A and B. Channel B has an additional ; Headphone connector. ; ; We knowingly only define a subset of the theoretically possible ; mapping combinations as profiles here. ; ; See default.conf for an explanation on the directives used here. [General] auto-profiles = no [Mapping analog-stereo-a] description = Analog Stereo Channel A device-strings = hw:%f,0,0 channel-map = left,right [Mapping analog-stereo-b-output] description = Analog Stereo Channel B (Headphones) device-strings = hw:%f,0,1 channel-map = left,right direction = output [Mapping analog-stereo-b-input] description = Analog Stereo Channel B device-strings = hw:%f,0,1 channel-map = left,right direction = input [Profile output:analog-stereo-all+input:analog-stereo-all] description = Analog Stereo Duplex Channels A, B (Headphones) output-mappings = analog-stereo-a analog-stereo-b-output input-mappings = analog-stereo-a analog-stereo-b-input priority = 100 skip-probe = yes [Profile output:analog-stereo-a+input:analog-stereo-a] description = Analog Stereo Duplex Channel A output-mappings = analog-stereo-a input-mappings = analog-stereo-a priority = 40 skip-probe = yes [Profile output:analog-stereo-b+input:analog-stereo-b] description = Analog Stereo Duplex Channel B (Headphones) output-mappings = analog-stereo-b-output input-mappings = analog-stereo-b-input priority = 50 skip-probe = yes [Profile output:analog-stereo-a] description = Analog Stereo Output Channel A output-mappings = analog-stereo-a priority = 5 skip-probe = yes [Profile output:analog-stereo-b] description = Analog Stereo Output Channel B (Headphones) output-mappings = analog-stereo-b-output priority = 6 skip-probe = yes [Profile input:analog-stereo-a] description = Analog Stereo Input Channel A input-mappings = analog-stereo-a priority = 2 skip-probe = yes [Profile input:analog-stereo-b] description = Analog Stereo Input Channel B input-mappings = analog-stereo-b-input priority = 1 skip-probe = yes native-instruments-audio8dj.conf000066400000000000000000000116071511204443500356030ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/mixer/profile-sets# This file is part of PulseAudio. # # PulseAudio is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as # published by the Free Software Foundation; either version 2.1 of the # License, or (at your option) any later version. # # PulseAudio is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with PulseAudio; if not, see . ; Native Instruments Audio 8 DJ ; ; This card has four stereo pairs of input and four stereo pairs of ; output, named channels A to D. Channel C has an additional Mic/Line ; connector, channel D an additional Headphone connector. ; ; We knowingly only define a subset of the theoretically possible ; mapping combinations as profiles here. ; ; See default.conf for an explanation on the directives used here. [General] auto-profiles = no [Mapping analog-stereo-a] description = Analog Stereo Channel A device-strings = hw:%f,0,0 channel-map = left,right [Mapping analog-stereo-b] description = Analog Stereo Channel B device-strings = hw:%f,0,1 channel-map = left,right # Since we want to set a different description for channel C's/D's input # and output we define two separate mappings for them [Mapping analog-stereo-c-output] description = Analog Stereo Channel C device-strings = hw:%f,0,2 channel-map = left,right direction = output [Mapping analog-stereo-c-input] description = Analog Stereo Channel C (Line/Mic) device-strings = hw:%f,0,2 channel-map = left,right direction = input [Mapping analog-stereo-d-output] description = Analog Stereo Channel D (Headphones) device-strings = hw:%f,0,3 channel-map = left,right direction = output [Mapping analog-stereo-d-input] description = Analog Stereo Channel D device-strings = hw:%f,0,3 channel-map = left,right direction = input [Profile output:analog-stereo-all+input:analog-stereo-all] description = Analog Stereo Duplex Channels A, B, C (Line/Mic), D (Headphones) output-mappings = analog-stereo-a analog-stereo-b analog-stereo-c-output analog-stereo-d-output input-mappings = analog-stereo-a analog-stereo-b analog-stereo-c-input analog-stereo-d-input priority = 100 skip-probe = yes [Profile output:analog-stereo-d+input:analog-stereo-c] description = Analog Stereo Channel D (Headphones) Output, Channel C (Line/Mic) Input output-mappings = analog-stereo-d-output input-mappings = analog-stereo-c-input priority = 90 skip-probe = yes [Profile output:analog-stereo-c-d+input:analog-stereo-c-d] description = Analog Stereo Duplex Channels C (Line/Mic), D (Line/Mic) output-mappings = analog-stereo-c-output analog-stereo-d-output input-mappings = analog-stereo-c-input analog-stereo-d-input priority = 80 skip-probe = yes [Profile output:analog-stereo-a+input:analog-stereo-a] description = Analog Stereo Duplex Channel A output-mappings = analog-stereo-a input-mappings = analog-stereo-a priority = 50 skip-probe = yes [Profile output:analog-stereo-b+input:analog-stereo-b] description = Analog Stereo Duplex Channel B output-mappings = analog-stereo-b input-mappings = analog-stereo-b priority = 40 skip-probe = yes [Profile output:analog-stereo-c+input:analog-stereo-c] description = Analog Stereo Duplex Channel C (Line/Mic) output-mappings = analog-stereo-c-output input-mappings = analog-stereo-c-input priority = 60 skip-probe = yes [Profile output:analog-stereo-d+input:analog-stereo-d] description = Analog Stereo Duplex Channel D (Headphones) output-mappings = analog-stereo-d-output input-mappings = analog-stereo-d-input priority = 70 skip-probe = yes [Profile output:analog-stereo-a] description = Analog Stereo Output Channel A output-mappings = analog-stereo-a priority = 6 skip-probe = yes [Profile output:analog-stereo-b] description = Analog Stereo Output Channel B output-mappings = analog-stereo-b priority = 5 skip-probe = yes [Profile output:analog-stereo-c] description = Analog Stereo Output Channel C output-mappings = analog-stereo-c-output priority = 7 skip-probe = yes [Profile output:analog-stereo-d] description = Analog Stereo Output Channel D (Headphones) output-mappings = analog-stereo-d-output priority = 8 skip-probe = yes [Profile input:analog-stereo-a] description = Analog Stereo Input Channel A input-mappings = analog-stereo-a priority = 2 skip-probe = yes [Profile input:analog-stereo-b] description = Analog Stereo Input Channel B input-mappings = analog-stereo-b priority = 1 skip-probe = yes [Profile input:analog-stereo-c] description = Analog Stereo Input Channel C (Line/Mic) input-mappings = analog-stereo-c-input priority = 4 skip-probe = yes [Profile input:analog-stereo-d] description = Analog Stereo Input Channel D input-mappings = analog-stereo-d-input priority = 3 skip-probe = yes native-instruments-komplete-audio6.conf000066400000000000000000000064631511204443500371050ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/mixer/profile-sets# This file is part of PulseAudio. # # PulseAudio is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as # published by the Free Software Foundation; either version 2.1 of the # License, or (at your option) any later version. # # PulseAudio is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with PulseAudio; if not, see . ; Native Instruments Komplete Audio 6 ; ; This card has three stereo pairs of input and three stereo pairs of ; output. ; ; We knowingly only define a subset of the theoretically possible ; mapping combinations as profiles here. ; ; See default.conf for an explanation on the directives used here. [General] auto-profiles = no [Mapping analog-stereo-out-ab] description = Analog Stereo 1/2 device-strings = hw:%f,0,0 channel-map = left,right,aux0,aux1,aux2,aux3 direction = output [Mapping analog-stereo-out-cd] description = Analog Stereo 3/4 device-strings = hw:%f,0,0 channel-map = aux0,aux1,left,right,aux2,aux3 direction = output [Mapping stereo-out-ef] description = Stereo 5/6 (S/PDIF) device-strings = hw:%f,0,0 channel-map = aux0,aux1,aux2,aux3,left,right direction = output [Mapping analog-mono-in-a] description = Analog Mono Input 1 device-strings = hw:%f,0,0 channel-map = mono,aux0,aux1,aux2,aux3,aux4 direction = input [Mapping analog-mono-in-b] description = Analog Mono Input 2 device-strings = hw:%f,0,0 channel-map = aux0,mono,aux1,aux2,aux3,aux4 direction = input [Mapping analog-stereo-in-ab] description = Analog Stereo Input 1/2 device-strings = hw:%f,0,0 channel-map = left,right,aux0,aux1,aux2,aux3 direction = input [Mapping analog-stereo-in-cd] description = Analog Stereo Input 3/4 device-strings = hw:%f,0,0 channel-map = aux0,aux1,left,right,aux2,aux3 direction = input [Mapping stereo-in-ef] description = Stereo Input 5/6 (S/PDIF) device-strings = hw:%f,0,0 channel-map = aux0,aux1,aux2,aux3,left,right direction = input [Profile output:analog-stereo-out-ab+input:analog-stereo-in-ab] description = Analog Stereo Output 1/2, Analog Stereo Input 1/2 output-mappings = analog-stereo-out-ab input-mappings = analog-stereo-in-ab priority = 100 skip-probe = yes [Profile output:analog-stereo-out-ab+input:analog-mono-in-a] description = Analog Stereo Output 1/2, Analog Mono Input 1 output-mappings = analog-stereo-out-ab input-mappings = analog-mono-in-a priority = 95 skip-probe = yes [Profile output:analog-stereo-out-ab+input:analog-mono-in-b] description = Analog Stereo Output 1/2, Analog Mono Input 2 output-mappings = analog-stereo-out-ab input-mappings = analog-mono-in-b priority = 90 skip-probe = yes [Profile output:analog-stereo-out-cd+input:analog-stereo-in-cd] description = Analog Stereo Output 3/4, Analog Stereo Input 3/4 output-mappings = analog-stereo-out-cd input-mappings = analog-stereo-in-cd priority = 80 skip-probe = yes [Profile output:stereo-out-ef+input:stereo-in-ef] description = Stereo Output 5/6 (S/PDIF), Stereo Input 5/6 (S/PDIF) output-mappings = stereo-out-ef input-mappings = stereo-in-ef priority = 70 skip-probe = yes native-instruments-korecontroller.conf000066400000000000000000000052311511204443500371340ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/mixer/profile-sets# This file is part of PulseAudio. # # PulseAudio is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as # published by the Free Software Foundation; either version 2.1 of the # License, or (at your option) any later version. # # PulseAudio is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with PulseAudio; if not, see . ; Native Instruments Kore Controller ; ; This card has one stereo pairs of input and two stereo pairs of ; output, named "Master" and "Headphone". The master channel has ; an additional Coax S/PDIF connector which is always on. ; ; We knowingly only define a subset of the theoretically possible ; mapping combinations as profiles here. ; ; See default.conf for an explanation on the directives used here. [General] auto-profiles = no [Mapping analog-stereo-master-out] description = Analog Stereo Master Channel device-strings = hw:%f,0,0 channel-map = left,right [Mapping analog-stereo-headphone-out] description = Analog Stereo Headphone Channel device-strings = hw:%f,0,1 channel-map = left,right direction = output [Mapping analog-stereo-input] description = Analog Stereo device-strings = hw:%f,0,0 channel-map = left,right direction = input [Profile output:analog-stereo-all+input:analog-stereo-all] description = Analog Stereo Duplex Master Output, Headphones Output output-mappings = analog-stereo-master-out analog-stereo-headphone-out input-mappings = analog-stereo-input priority = 100 skip-probe = yes [Profile output:analog-stereo-master+input:analog-stereo-input] description = Analog Stereo Duplex Master Output output-mappings = analog-stereo-master-out input-mappings = analog-stereo-input priority = 40 skip-probe = yes [Profile output:analog-stereo-headphone-out+input:analog-stereo-input] description = Analog Stereo Headphones Output output-mappings = analog-stereo-headphone-out input-mappings = analog-stereo-input priority = 30 skip-probe = yes [Profile output:analog-stereo-master] description = Analog Stereo Master Output output-mappings = analog-stereo-master-out priority = 3 skip-probe = yes [Profile output:analog-stereo-headphone] description = Analog Stereo Headphones Output output-mappings = analog-stereo-headphone-out priority = 2 skip-probe = yes [Profile input:analog-stereo-input] description = Analog Stereo Input input-mappings = analog-stereo-input priority = 1 skip-probe = yes native-instruments-traktor-audio10.conf000066400000000000000000000073021511204443500370170ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/mixer/profile-sets# This file is part of PulseAudio. # # PulseAudio is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as # published by the Free Software Foundation; either version 2.1 of the # License, or (at your option) any later version. # # PulseAudio is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with PulseAudio; if not, see . ; Native Instruments Audio 10 DJ ; ; This card has five stereo pairs of input and five stereo pairs of ; output ; ; We knowingly only define a subset of the theoretically possible ; mapping combinations as profiles here. ; ; See default.conf for an explanation on the directives used here. [General] auto-profiles = no [Mapping analog-stereo-out-main] description = Analog Stereo Main device-strings = hw:%f,0,0 channel-map = left,right [Mapping analog-stereo-out-a] description = Analog Stereo Channel A device-strings = hw:%f,0,1 channel-map = left,right direction = output [Mapping analog-stereo-out-b] description = Analog Stereo Channel B device-strings = hw:%f,0,1 channel-map = left,right direction = output [Mapping analog-stereo-out-c] description = Analog Stereo Channel C device-strings = hw:%f,0,2 channel-map = left,right direction = output [Mapping analog-stereo-out-d] description = Analog Stereo Channel D device-strings = hw:%f,0,3 channel-map = left,right direction = output [Mapping analog-stereo-in-main] description = Analog Stereo Main device-strings = hw:%f,0,0 channel-map = left,right [Mapping analog-stereo-in-a] description = Analog Stereo Channel A device-strings = hw:%f,0,1 channel-map = left,right direction = input [Mapping analog-stereo-in-b] description = Analog Stereo Channel B device-strings = hw:%f,0,1 channel-map = left,right direction = input [Mapping analog-stereo-in-c] description = Analog Stereo Channel C device-strings = hw:%f,0,2 channel-map = left,right direction = input [Mapping analog-stereo-in-d] description = Analog Stereo Channel D device-strings = hw:%f,0,3 channel-map = left,right direction = input [Profile output:analog-stereo-all+input:analog-stereo-all] description = Analog Stereo Duplex Channels Main, A, B, C, D output-mappings = analog-stereo-out-main analog-stereo-out-a analog-stereo-out-b analog-stereo-out-c analog-stereo-out-d input-mappings = analog-stereo-in-main analog-stereo-in-a analog-stereo-in-b analog-stereo-in-c analog-stereo-in-d priority = 100 skip-probe = yes [Profile output:analog-stereo-main+input:analog-stereo-main] description = Analog Stereo Duplex Main output-mappings = analog-stereo-out-main input-mappings = analog-stereo-in-main priority = 50 skip-probe = yes [Profile output:analog-stereo-a+input:analog-stereo-a] description = Analog Stereo Duplex Channel A output-mappings = analog-stereo-out-a input-mappings = analog-stereo-in-a priority = 40 skip-probe = yes [Profile output:analog-stereo-b+input:analog-stereo-b] description = Analog Stereo Duplex Channel B output-mappings = analog-stereo-out-b input-mappings = analog-stereo-in-b priority = 30 skip-probe = yes [Profile output:analog-stereo-a+input:analog-stereo-c] description = Analog Stereo Duplex Channel C output-mappings = analog-stereo-out-c input-mappings = analog-stereo-in-c priority = 20 skip-probe = yes [Profile output:analog-stereo-a+input:analog-stereo-d] description = Analog Stereo Duplex Channel D output-mappings = analog-stereo-out-d input-mappings = analog-stereo-in-d priority = 10 skip-probe = yes native-instruments-traktor-audio2.conf000066400000000000000000000030571511204443500367430ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/mixer/profile-sets# This file is part of PulseAudio. # # PulseAudio is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as # published by the Free Software Foundation; either version 2.1 of the # License, or (at your option) any later version. # # PulseAudio is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with PulseAudio; if not, see . ; Native Instruments Traktor Audio 2 ; ; This card has two stereo pairs of output. ; ; See default.conf for an explanation on the directives used here. [General] auto-profiles = no [Mapping analog-stereo-a] description = Analog Stereo Channel A device-strings = hw:%f,0,0 channel-map = left,right direction = output [Mapping analog-stereo-b] description = Analog Stereo Channel B device-strings = hw:%f,0,1 channel-map = left,right direction = output [Profile output:analog-stereo-a] description = Analog Stereo Output Channel A output-mappings = analog-stereo-a priority = 60 skip-probe = yes [Profile output:analog-stereo-b] description = Analog Stereo Output Channel B output-mappings = analog-stereo-b priority = 50 skip-probe = yes [Profile analog-stereo-all] description = Analog Stereo Output Channels A & B output-mappings = analog-stereo-a analog-stereo-b priority = 100 skip-probe = yes native-instruments-traktor-audio6.conf000066400000000000000000000052651511204443500367520ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/mixer/profile-sets# This file is part of PulseAudio. # # PulseAudio is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as # published by the Free Software Foundation; either version 2.1 of the # License, or (at your option) any later version. # # PulseAudio is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with PulseAudio; if not, see . ; Native Instruments Audio 6 DJ ; ; This card has three stereo pairs of input and three stereo pairs of ; output ; ; We knowingly only define a subset of the theoretically possible ; mapping combinations as profiles here. ; ; See default.conf for an explanation on the directives used here. [General] auto-profiles = no [Mapping analog-stereo-out-main] description = Analog Stereo Main device-strings = hw:%f,0,0 channel-map = left,right [Mapping analog-stereo-out-a] description = Analog Stereo Channel A device-strings = hw:%f,0,1 channel-map = left,right direction = output [Mapping analog-stereo-out-b] description = Analog Stereo Channel B device-strings = hw:%f,0,1 channel-map = left,right direction = output [Mapping analog-stereo-in-main] description = Analog Stereo Main device-strings = hw:%f,0,0 channel-map = left,right [Mapping analog-stereo-in-a] description = Analog Stereo Channel A device-strings = hw:%f,0,1 channel-map = left,right direction = input [Mapping analog-stereo-in-b] description = Analog Stereo Channel B device-strings = hw:%f,0,1 channel-map = left,right direction = input [Profile output:analog-stereo-all+input:analog-stereo-all] description = Analog Stereo Duplex Channels A, B (Headphones) output-mappings = analog-stereo-out-main analog-stereo-out-a analog-stereo-out-b input-mappings = analog-stereo-in-main analog-stereo-in-a analog-stereo-in-b priority = 100 skip-probe = yes [Profile output:analog-stereo-main+input:analog-stereo-main] description = Analog Stereo Duplex Channel Main output-mappings = analog-stereo-out-main input-mappings = analog-stereo-in-main priority = 50 skip-probe = yes [Profile output:analog-stereo-a+input:analog-stereo-a] description = Analog Stereo Duplex Channel A output-mappings = analog-stereo-out-a input-mappings = analog-stereo-in-a priority = 40 skip-probe = yes [Profile output:analog-stereo-b+input:analog-stereo-b] description = Analog Stereo Duplex Channel B output-mappings = analog-stereo-out-b input-mappings = analog-stereo-in-b priority = 30 skip-probe = yes native-instruments-traktorkontrol-s4.conf000066400000000000000000000045631511204443500375220ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/mixer/profile-sets# This file is part of PulseAudio. # # PulseAudio is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as # published by the Free Software Foundation; either version 2.1 of the # License, or (at your option) any later version. # # PulseAudio is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with PulseAudio; if not, see . ; Native Instruments Traktor Kontrol S4 ; ; This controller has two stereo pairs of input (named "Channel C" and ; "Channel D") and two stereo pairs of output, one "Main Out" and ; "Headphone Out". ; ; See default.conf for an explanation on the directives used here. [General] auto-profiles = no [Mapping analog-stereo-output-main] description = Analog Stereo Main Out device-strings = hw:%f,0,0 channel-map = left,right [Mapping analog-stereo-output-headphone] description = Analog Stereo Headphones Out device-strings = hw:%f,0,1 channel-map = left,right direction = output [Mapping analog-stereo-c-input] description = Analog Stereo Channel C device-strings = hw:%f,0,1 channel-map = left,right direction = input [Mapping analog-stereo-d-input] description = Analog Stereo Channel D device-strings = hw:%f,0,1 channel-map = left,right direction = input [Profile output:analog-stereo-all+input:analog-stereo-all] description = Analog Stereo Duplex output-mappings = analog-stereo-output-main analog-stereo-output-headphone input-mappings = analog-stereo-c-input analog-stereo-d-input priority = 100 skip-probe = yes [Profile output:analog-stereo-main] description = Analog Stereo Main Output output-mappings = analog-stereo-output-main priority = 4 skip-probe = yes [Profile output:analog-stereo-headphone] description = Analog Stereo Output Headphones Out output-mappings = analog-stereo-output-headphone priority = 3 skip-probe = yes [Profile input:analog-stereo-c] description = Analog Stereo Input Channel C input-mappings = analog-stereo-c-input priority = 2 skip-probe = yes [Profile input:analog-stereo-d] description = Analog Stereo Input Channel D input-mappings = analog-stereo-d-input priority = 1 skip-probe = yes sb-omni-surround-5.1.conf000066400000000000000000000067201511204443500337430ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/mixer/profile-sets# This file is part of PulseAudio. # # PulseAudio is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as # published by the Free Software Foundation; either version 2.1 of the # License, or (at your option) any later version. # # PulseAudio is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with PulseAudio; if not, see . ; Creative Sound Blaster Omni Surround 5.1 ; ; This config supports Linux 4.3-rc1+. ; By default there are some non-existing (physically) inputs and outputs that ; are not present in this config. ; Also in addition to natively supported modes (such as stereo, 5.1 and stereo ; S/PDIF) following useful output modes are added: 2.1, 4.0, 4.1 and 5.0. ; ; NOTE: in 2.1 and 4.1 physical LFE output will be different than in 5.1 mode. ; ; See default.conf for an explanation on the directives used here. [General] auto-profiles = no [Mapping analog-stereo-input] device-strings = hw:%f channel-map = left,right paths-input = analog-input-mic analog-input-linein direction = input [Mapping analog-stereo-output] device-strings = front:%f channel-map = left,right paths-output = analog-output direction = output [Mapping analog-surround-21] device-strings = surround51:%f channel-map = front-left,front-right,aux1,aux2,aux3,lfe paths-output = analog-output direction = output [Mapping analog-surround-40] device-strings = surround51:%f channel-map = front-left,front-right,rear-left,rear-right paths-output = analog-output direction = output [Mapping analog-surround-41] device-strings = surround51:%f channel-map = front-left,front-right,rear-left,rear-right,aux1,lfe paths-output = analog-output direction = output [Mapping analog-surround-50] device-strings = surround51:%f channel-map = front-left,front-right,rear-left,rear-right,front-center paths-output = analog-output direction = output [Mapping analog-surround-51] device-strings = surround51:%f channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe paths-output = analog-output direction = output [Mapping iec958-stereo] device-strings = iec958:%f channel-map = left,right paths-output = iec958-stereo-output direction = output [Profile output:analog-stereo-output+input:analog-stereo-input] output-mappings = analog-stereo-output input-mappings = analog-stereo-input priority = 7 [Profile output:analog-surround-21+input:analog-stereo-input] output-mappings = analog-surround-21 input-mappings = analog-stereo-input priority = 6 [Profile output:analog-surround-40+input:analog-stereo-input] output-mappings = analog-surround-40 input-mappings = analog-stereo-input priority = 5 [Profile output:analog-surround-41+input:analog-stereo-input] output-mappings = analog-surround-41 input-mappings = analog-stereo-input priority = 4 [Profile output:analog-surround-50+input:analog-stereo-input] output-mappings = analog-surround-50 input-mappings = analog-stereo-input priority = 3 [Profile output:analog-surround-51+input:analog-stereo-input] output-mappings = analog-surround-51 input-mappings = analog-stereo-input priority = 2 [Profile output:iec958-stereo+input:analog-stereo-input] output-mappings = iec958-stereo input-mappings = analog-stereo-input priority = 1 sennheiser-gsx.conf000066400000000000000000000047751511204443500331710ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/mixer/profile-sets# This file is part of PulseAudio. # # PulseAudio is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as # published by the Free Software Foundation; either version 2.1 of the # License, or (at your option) any later version. # # PulseAudio is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with PulseAudio; if not, see . ; USB Gaming DAC. ; These devices have two output devices. The first one is mono, meant for ; voice audio, and the second one is 7.1 surround, meant for everything ; else. The 7.1 surround is mapped to headphones within the device. ; The purpose of the mono/7.1 design is to provide separate volume ; controls for voice and other audio, which can be useful in gaming. ; ; Works with: ; Sennheiser GSX 1000 ; Sennheiser GSX 1200 ; ; See default.conf for an explanation on the directives used here. [General] auto-profiles = no [Mapping analog-chat-output] description-key = gaming-headset-chat device-strings = hw:%f,0 channel-map = mono paths-output = analog-chat-output direction = output priority = 4000 intended-roles = phone [Mapping analog-output-surround71] description-key = analog-surround-71 device-strings = hw:%f,1 channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe,side-left,side-right #channel-map = front-left,front-right,front-center,lfe,rear-left,rear-right,side-left,side-right # Swap channel fix that some devices require paths-output = virtual-surround-7.1 priority = 4100 direction = output [Mapping analog-chat-input] description-key = gaming-headset-chat device-strings = hw:%f,0 channel-map = mono paths-input = analog-chat-input priority = 4100 direction = input [Profile output:analog-output-surround71+output:analog-output-chat+input:analog-input] output-mappings = analog-output-surround71 analog-chat-output input-mappings = analog-chat-input priority = 5100 skip-probe = yes [Mapping stereo-output] description = 2.0 HD device-strings = hw:%f,1 channel-map = stereo priority = 3 direction = output [Profile output:stereo-output+output:analog-chat-output+input:analog-chat-input] description = 2.0 HD output-mappings = stereo-output analog-chat-output input-mappings = analog-chat-input priority = 50 skip-probe = yes simple-headphones-mic.conf000066400000000000000000000041111511204443500343620ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/mixer/profile-sets# This file is part of PulseAudio. # # PulseAudio is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as # published by the Free Software Foundation; either version 2.1 of the # License, or (at your option) any later version. # # PulseAudio is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with PulseAudio; if not, see . ; This is a profile meant for simple (stereo + mic) headphones. ; default.conf also works but using this one will hide some profiles ; that don't make sense like IEC958 and multichannel inputs. [General] auto-profiles = yes [Mapping analog-stereo] device-strings = front:%f channel-map = left,right paths-output = analog-output analog-output-lineout analog-output-speaker analog-output-headphones analog-output-headphones-2 paths-input = analog-input-front-mic analog-input-rear-mic analog-input-internal-mic analog-input-dock-mic analog-input analog-input-mic analog-input-linein analog-input-aux analog-input-video analog-input-tvtuner analog-input-fm analog-input-mic-line analog-input-headphone-mic analog-input-headset-mic priority = 15 # If everything else fails, try to use hw:0 as a stereo device... [Mapping stereo-fallback] device-strings = hw:%f fallback = yes channel-map = front-left,front-right paths-output = analog-output analog-output-lineout analog-output-speaker analog-output-headphones analog-output-headphones-2 paths-input = analog-input-front-mic analog-input-rear-mic analog-input-internal-mic analog-input-dock-mic analog-input analog-input-mic analog-input-linein analog-input-aux analog-input-video analog-input-tvtuner analog-input-fm analog-input-mic-line analog-input-headphone-mic analog-input-headset-mic priority = 1 [Mapping analog-mono] device-strings = hw:%f,0,0 channel-map = mono direction = input steelseries-arctis-common-usb-audio.conf000066400000000000000000000011361511204443500372010ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/mixer/profile-sets[General] auto-profiles = yes [Mapping analog-chat] description-key = gaming-headset-chat device-strings = hw:%f,0,0 channel-map = left,right paths-input = analog-input-mic paths-output = steelseries-arctis-output-chat-common intended-roles = phone [Mapping analog-game] description-key = gaming-headset-game device-strings = hw:%f,1,0 channel-map = left,right paths-output = steelseries-arctis-output-game-common direction = output [Profile output:analog-chat+output:analog-game+input:analog-chat] output-mappings = analog-chat analog-game input-mappings = analog-chat priority = 5100 skip-probe = yes texas-instruments-pcm2902.conf000066400000000000000000000045731511204443500350320ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/mixer/profile-sets# This file is part of PulseAudio. # # PulseAudio is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as # published by the Free Software Foundation; either version 2.1 of the # License, or (at your option) any later version. # # PulseAudio is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with PulseAudio; if not, see . ; Texas Instruments PCM2902 ; ; This is a generic chip used in multiple products, including at least ; Behringer U-Phoria UMC22, Behringer Xenyx 302USB, Intopic Jazz-UB700 and ; some unbranded "usb mini microphone". ; ; Behringer UMC22 has stereo input (representing two physical mono inputs), ; others have mono input. ; ; Some devices have a mic input path, but at least Behringer Xenyx 302USB ; doesn't have any input mixer controls. ; ; Since the UMC22 card has only stereo input PCM device but is commonly used ; with mono mics, we define special mono mappings using "mono,aux1" and ; "aux1,mono" channel maps. If we had only had the standard stereo input ; mapping, the user would have to record stereo tracks with one channel silent, ; which would be inconvenient. ; ; This config also removes default digital input/output mappings that do ; not physically exist on cards that we've seen so far. ; ; Originally added by Nazar Mokrynskyi for Behringer ; UMC22. [General] auto-profiles = yes [Mapping analog-stereo-input] device-strings = hw:%f channel-map = left,right paths-input = analog-input-mic analog-input direction = input priority = 4 [Mapping analog-mono] device-strings = hw:%f channel-map = mono paths-input = analog-input-mic analog-input direction = input priority = 3 [Mapping analog-mono-left] device-strings = hw:%f channel-map = mono,aux1 paths-input = analog-input-mic analog-input direction = input priority = 2 [Mapping analog-mono-right] device-strings = hw:%f channel-map = aux1,mono paths-input = analog-input-mic analog-input direction = input priority = 1 [Mapping analog-stereo-output] device-strings = front:%f channel-map = left,right paths-output = analog-output direction = output usb-gaming-headset.conf000066400000000000000000000040571511204443500336640ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/mixer/profile-sets# This file is part of PulseAudio. # # PulseAudio is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as # published by the Free Software Foundation; either version 2.1 of the # License, or (at your option) any later version. # # PulseAudio is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with PulseAudio; if not, see . ; USB gaming headset. ; These headsets usually have two output devices. The first one is meant ; for voice audio, and the second one is meant for everything else. ; The purpose of this unusual design is to provide separate volume ; controls for voice and other audio, which can be useful in gaming. ; ; Works with: ; Steelseries Arctis 7 ; Steelseries Arctis Pro Wireless. ; Lucidsound LS31 ; Astro A50 ; ; See default.conf for an explanation on the directives used here. [General] auto-profiles = yes [Mapping mono-chat] description-key = gaming-headset-chat device-strings = hw:%f,0,0 channel-map = mono paths-output = usb-gaming-headset-output-mono paths-input = usb-gaming-headset-input intended-roles = phone [Mapping stereo-chat] description-key = gaming-headset-chat device-strings = hw:%f,0,0 channel-map = left,right paths-output = usb-gaming-headset-output-stereo paths-input = usb-gaming-headset-input intended-roles = phone [Mapping stereo-game] description-key = gaming-headset-game device-strings = hw:%f,1,0 channel-map = left,right paths-output = usb-gaming-headset-output-stereo direction = output [Profile output:mono-chat+output:stereo-game+input:mono-chat] output-mappings = mono-chat stereo-game input-mappings = mono-chat priority = 5100 [Profile output:stereo-game+output:stereo-chat+input:mono-chat] output-mappings = stereo-game stereo-chat input-mappings = mono-chat priority = 5100 pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/mixer/samples/000077500000000000000000000000001511204443500264525ustar00rootroot00000000000000ATI IXP--Realtek ALC655 rev 0000066400000000000000000000124631511204443500325410ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/mixer/samplesSimple mixer control 'Master',0 Capabilities: pvolume pswitch pswitch-joined Playback channels: Front Left - Front Right Limits: Playback 0 - 31 Mono: Front Left: Playback 29 [94%] [-3.00dB] [on] Front Right: Playback 29 [94%] [-3.00dB] [on] Simple mixer control 'Master Mono',0 Capabilities: pvolume pvolume-joined pswitch pswitch-joined Playback channels: Mono Limits: Playback 0 - 31 Mono: Playback 0 [0%] [-46.50dB] [off] Simple mixer control 'PCM',0 Capabilities: pvolume pswitch pswitch-joined Playback channels: Front Left - Front Right Limits: Playback 0 - 31 Mono: Front Left: Playback 23 [74%] [0.00dB] [on] Front Right: Playback 23 [74%] [0.00dB] [on] Simple mixer control 'Surround',0 Capabilities: pvolume pswitch Playback channels: Front Left - Front Right Limits: Playback 0 - 31 Mono: Front Left: Playback 0 [0%] [-46.50dB] [off] Front Right: Playback 0 [0%] [-46.50dB] [off] Simple mixer control 'Surround Jack Mode',0 Capabilities: enum Items: 'Shared' 'Independent' Item0: 'Shared' Simple mixer control 'Center',0 Capabilities: pvolume pvolume-joined pswitch pswitch-joined Playback channels: Mono Limits: Playback 0 - 31 Mono: Playback 0 [0%] [-46.50dB] [off] Simple mixer control 'LFE',0 Capabilities: pvolume pvolume-joined pswitch pswitch-joined Playback channels: Mono Limits: Playback 0 - 31 Mono: Playback 0 [0%] [-46.50dB] [off] Simple mixer control 'Line',0 Capabilities: pvolume pswitch pswitch-joined cswitch cswitch-exclusive Capture exclusive group: 0 Playback channels: Front Left - Front Right Capture channels: Front Left - Front Right Limits: Playback 0 - 31 Front Left: Playback 0 [0%] [-34.50dB] [off] Capture [off] Front Right: Playback 0 [0%] [-34.50dB] [off] Capture [off] Simple mixer control 'CD',0 Capabilities: pvolume pswitch pswitch-joined cswitch cswitch-exclusive Capture exclusive group: 0 Playback channels: Front Left - Front Right Capture channels: Front Left - Front Right Limits: Playback 0 - 31 Front Left: Playback 0 [0%] [-34.50dB] [off] Capture [off] Front Right: Playback 0 [0%] [-34.50dB] [off] Capture [off] Simple mixer control 'Mic',0 Capabilities: pvolume pvolume-joined pswitch pswitch-joined cswitch cswitch-exclusive Capture exclusive group: 0 Playback channels: Mono Capture channels: Front Left - Front Right Limits: Playback 0 - 31 Mono: Playback 0 [0%] [-34.50dB] [off] Front Left: Capture [on] Front Right: Capture [on] Simple mixer control 'Mic Boost (+20dB)',0 Capabilities: pswitch pswitch-joined Playback channels: Mono Mono: Playback [off] Simple mixer control 'Mic Select',0 Capabilities: enum Items: 'Mic1' 'Mic2' Item0: 'Mic1' Simple mixer control 'Video',0 Capabilities: cswitch cswitch-exclusive Capture exclusive group: 0 Capture channels: Front Left - Front Right Front Left: Capture [off] Front Right: Capture [off] Simple mixer control 'Phone',0 Capabilities: pvolume pvolume-joined pswitch pswitch-joined cswitch cswitch-exclusive Capture exclusive group: 0 Playback channels: Mono Capture channels: Front Left - Front Right Limits: Playback 0 - 31 Mono: Playback 31 [100%] [12.00dB] [off] Front Left: Capture [off] Front Right: Capture [off] Simple mixer control 'IEC958',0 Capabilities: pswitch pswitch-joined cswitch cswitch-joined Playback channels: Mono Capture channels: Mono Mono: Playback [off] Capture [off] Simple mixer control 'IEC958 Playback AC97-SPSA',0 Capabilities: volume volume-joined Playback channels: Mono Capture channels: Mono Limits: 0 - 3 Mono: 0 [0%] Simple mixer control 'IEC958 Playback Source',0 Capabilities: enum Items: 'PCM' 'Analog In' 'IEC958 In' Item0: 'PCM' Simple mixer control 'PC Speaker',0 Capabilities: pvolume pvolume-joined pswitch pswitch-joined Playback channels: Mono Limits: Playback 0 - 15 Mono: Playback 0 [0%] [-45.00dB] [on] Simple mixer control 'Aux',0 Capabilities: pvolume pswitch pswitch-joined cswitch cswitch-exclusive Capture exclusive group: 0 Playback channels: Front Left - Front Right Capture channels: Front Left - Front Right Limits: Playback 0 - 31 Front Left: Playback 0 [0%] [-34.50dB] [on] Capture [off] Front Right: Playback 0 [0%] [-34.50dB] [on] Capture [off] Simple mixer control 'Mono Output Select',0 Capabilities: enum Items: 'Mix' 'Mic' Item0: 'Mix' Simple mixer control 'Capture',0 Capabilities: cvolume cswitch cswitch-joined Capture channels: Front Left - Front Right Limits: Capture 0 - 15 Front Left: Capture 12 [80%] [18.00dB] [on] Front Right: Capture 12 [80%] [18.00dB] [on] Simple mixer control 'Mix',0 Capabilities: cswitch cswitch-exclusive Capture exclusive group: 0 Capture channels: Front Left - Front Right Front Left: Capture [off] Front Right: Capture [off] Simple mixer control 'Mix Mono',0 Capabilities: cswitch cswitch-exclusive Capture exclusive group: 0 Capture channels: Front Left - Front Right Front Left: Capture [off] Front Right: Capture [off] Simple mixer control 'Channel Mode',0 Capabilities: enum Items: '2ch' '4ch' '6ch' Item0: '2ch' Simple mixer control 'Duplicate Front',0 Capabilities: pswitch pswitch-joined Playback channels: Mono Mono: Playback [off] Simple mixer control 'External Amplifier',0 Capabilities: pswitch pswitch-joined Playback channels: Mono Mono: Playback [on] Brooktree Bt878--Bt87x000066400000000000000000000013771511204443500320460ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/mixer/samplesSimple mixer control 'FM',0 Capabilities: cswitch cswitch-joined cswitch-exclusive Capture exclusive group: 0 Capture channels: Mono Mono: Capture [off] Simple mixer control 'Mic/Line',0 Capabilities: cswitch cswitch-joined cswitch-exclusive Capture exclusive group: 0 Capture channels: Mono Mono: Capture [off] Simple mixer control 'Capture',0 Capabilities: cvolume cvolume-joined Capture channels: Mono Limits: Capture 0 - 15 Mono: Capture 13 [87%] Simple mixer control 'Capture Boost',0 Capabilities: pswitch pswitch-joined Playback channels: Mono Mono: Playback [on] Simple mixer control 'TV Tuner',0 Capabilities: cswitch cswitch-joined cswitch-exclusive Capture exclusive group: 0 Capture channels: Mono Mono: Capture [on] Ensoniq AudioPCI--Cirrus Logic CS4297A rev 3000066400000000000000000000116161511204443500354720ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/mixer/samplesSimple mixer control 'Master',0 Capabilities: pvolume pswitch pswitch-joined Playback channels: Front Left - Front Right Limits: Playback 0 - 63 Mono: Front Left: Playback 63 [100%] [0.00dB] [on] Front Right: Playback 63 [100%] [0.00dB] [on] Simple mixer control 'Master Mono',0 Capabilities: pvolume pvolume-joined pswitch pswitch-joined Playback channels: Mono Limits: Playback 0 - 31 Mono: Playback 0 [0%] [-46.50dB] [off] Simple mixer control 'Headphone',0 Capabilities: pvolume pswitch pswitch-joined Playback channels: Front Left - Front Right Limits: Playback 0 - 31 Mono: Front Left: Playback 0 [0%] [-46.50dB] [off] Front Right: Playback 0 [0%] [-46.50dB] [off] Simple mixer control '3D Control - Center',0 Capabilities: volume volume-joined Playback channels: Mono Capture channels: Mono Limits: 0 - 15 Mono: 0 [0%] Simple mixer control '3D Control - Depth',0 Capabilities: volume volume-joined Playback channels: Mono Capture channels: Mono Limits: 0 - 15 Mono: 0 [0%] Simple mixer control '3D Control - Switch',0 Capabilities: pswitch pswitch-joined Playback channels: Mono Mono: Playback [off] Simple mixer control 'PCM',0 Capabilities: pvolume pswitch pswitch-joined Playback channels: Front Left - Front Right Limits: Playback 0 - 31 Mono: Front Left: Playback 23 [74%] [0.00dB] [on] Front Right: Playback 23 [74%] [0.00dB] [on] Simple mixer control 'Line',0 Capabilities: pvolume pswitch pswitch-joined cswitch cswitch-exclusive Capture exclusive group: 0 Playback channels: Front Left - Front Right Capture channels: Front Left - Front Right Limits: Playback 0 - 31 Front Left: Playback 0 [0%] [-34.50dB] [off] Capture [on] Front Right: Playback 0 [0%] [-34.50dB] [off] Capture [on] Simple mixer control 'CD',0 Capabilities: pvolume pswitch pswitch-joined cswitch cswitch-exclusive Capture exclusive group: 0 Playback channels: Front Left - Front Right Capture channels: Front Left - Front Right Limits: Playback 0 - 31 Front Left: Playback 0 [0%] [-34.50dB] [off] Capture [off] Front Right: Playback 0 [0%] [-34.50dB] [off] Capture [off] Simple mixer control 'Mic',0 Capabilities: pvolume pvolume-joined pswitch pswitch-joined cswitch cswitch-exclusive Capture exclusive group: 0 Playback channels: Mono Capture channels: Front Left - Front Right Limits: Playback 0 - 31 Mono: Playback 23 [74%] [0.00dB] [on] Front Left: Capture [off] Front Right: Capture [off] Simple mixer control 'Mic Boost (+20dB)',0 Capabilities: pswitch pswitch-joined Playback channels: Mono Mono: Playback [off] Simple mixer control 'Mic Select',0 Capabilities: enum Items: 'Mic1' 'Mic2' Item0: 'Mic1' Simple mixer control 'Video',0 Capabilities: pvolume pswitch pswitch-joined cswitch cswitch-exclusive Capture exclusive group: 0 Playback channels: Front Left - Front Right Capture channels: Front Left - Front Right Limits: Playback 0 - 31 Front Left: Playback 0 [0%] [-34.50dB] [off] Capture [off] Front Right: Playback 0 [0%] [-34.50dB] [off] Capture [off] Simple mixer control 'Phone',0 Capabilities: pvolume pvolume-joined pswitch pswitch-joined cswitch cswitch-exclusive Capture exclusive group: 0 Playback channels: Mono Capture channels: Front Left - Front Right Limits: Playback 0 - 31 Mono: Playback 0 [0%] [-34.50dB] [off] Front Left: Capture [off] Front Right: Capture [off] Simple mixer control 'IEC958',0 Capabilities: pswitch pswitch-joined Playback channels: Mono Mono: Playback [off] Simple mixer control 'PC Speaker',0 Capabilities: pvolume pvolume-joined pswitch pswitch-joined Playback channels: Mono Limits: Playback 0 - 15 Mono: Playback 0 [0%] [-45.00dB] [off] Simple mixer control 'Aux',0 Capabilities: pvolume pswitch pswitch-joined cswitch cswitch-exclusive Capture exclusive group: 0 Playback channels: Front Left - Front Right Capture channels: Front Left - Front Right Limits: Playback 0 - 31 Front Left: Playback 0 [0%] [-34.50dB] [off] Capture [off] Front Right: Playback 0 [0%] [-34.50dB] [off] Capture [off] Simple mixer control 'Mono Output Select',0 Capabilities: enum Items: 'Mix' 'Mic' Item0: 'Mic' Simple mixer control 'Capture',0 Capabilities: cvolume cswitch cswitch-joined Capture channels: Front Left - Front Right Limits: Capture 0 - 15 Front Left: Capture 15 [100%] [22.50dB] [on] Front Right: Capture 15 [100%] [22.50dB] [on] Simple mixer control 'Mix',0 Capabilities: cswitch cswitch-exclusive Capture exclusive group: 0 Capture channels: Front Left - Front Right Front Left: Capture [off] Front Right: Capture [off] Simple mixer control 'Mix Mono',0 Capabilities: cswitch cswitch-exclusive Capture exclusive group: 0 Capture channels: Front Left - Front Right Front Left: Capture [off] Front Right: Capture [off] Simple mixer control 'External Amplifier',0 Capabilities: pswitch pswitch-joined Playback channels: Mono Mono: Playback [off] HDA ATI HDMI--ATI R6xx HDMI000066400000000000000000000001671511204443500320400ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/mixer/samplesSimple mixer control 'IEC958',0 Capabilities: pswitch pswitch-joined Playback channels: Mono Mono: Playback [on] HDA Intel--Analog Devices AD1981000066400000000000000000000040761511204443500333640ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/mixer/samplesSimple mixer control 'Master',0 Capabilities: pvolume pswitch Playback channels: Front Left - Front Right Limits: Playback 0 - 63 Mono: Front Left: Playback 63 [100%] [3.00dB] [on] Front Right: Playback 63 [100%] [3.00dB] [on] Simple mixer control 'PCM',0 Capabilities: pvolume pswitch Playback channels: Front Left - Front Right Limits: Playback 0 - 31 Mono: Front Left: Playback 23 [74%] [0.00dB] [on] Front Right: Playback 23 [74%] [0.00dB] [on] Simple mixer control 'CD',0 Capabilities: pvolume pswitch cswitch cswitch-joined cswitch-exclusive Capture exclusive group: 0 Playback channels: Front Left - Front Right Capture channels: Mono Limits: Playback 0 - 31 Mono: Capture [off] Front Left: Playback 0 [0%] [-34.50dB] [off] Front Right: Playback 0 [0%] [-34.50dB] [off] Simple mixer control 'Mic',0 Capabilities: pvolume pswitch cswitch cswitch-joined cswitch-exclusive Capture exclusive group: 0 Playback channels: Front Left - Front Right Capture channels: Mono Limits: Playback 0 - 31 Mono: Capture [on] Front Left: Playback 0 [0%] [-34.50dB] [off] Front Right: Playback 0 [0%] [-34.50dB] [off] Simple mixer control 'Mic Boost',0 Capabilities: volume Playback channels: Front Left - Front Right Capture channels: Front Left - Front Right Limits: 0 - 3 Front Left: 0 [0%] Front Right: 0 [0%] Simple mixer control 'IEC958',0 Capabilities: pswitch pswitch-joined Playback channels: Mono Mono: Playback [off] Simple mixer control 'IEC958 Default PCM',0 Capabilities: pswitch pswitch-joined Playback channels: Mono Mono: Playback [off] Simple mixer control 'IEC958 Playback Source',0 Capabilities: enum Items: 'PCM' 'ADC' Item0: 'PCM' Simple mixer control 'Capture',0 Capabilities: cvolume cswitch Capture channels: Front Left - Front Right Limits: Capture 0 - 15 Front Left: Capture 0 [0%] [0.00dB] [on] Front Right: Capture 0 [0%] [0.00dB] [on] Simple mixer control 'Mix',0 Capabilities: cswitch cswitch-joined cswitch-exclusive Capture exclusive group: 0 Capture channels: Mono Mono: Capture [off] HDA Intel--Realtek ALC889A000066400000000000000000000073051511204443500323470ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/mixer/samplesSimple mixer control 'Master',0 Capabilities: pvolume pvolume-joined pswitch pswitch-joined Playback channels: Mono Limits: Playback 0 - 64 Mono: Playback 64 [100%] [0.00dB] [on] Simple mixer control 'Headphone',0 Capabilities: pswitch Playback channels: Front Left - Front Right Mono: Front Left: Playback [on] Front Right: Playback [on] Simple mixer control 'PCM',0 Capabilities: pvolume Playback channels: Front Left - Front Right Limits: Playback 0 - 255 Mono: Front Left: Playback 255 [100%] [0.00dB] Front Right: Playback 255 [100%] [0.00dB] Simple mixer control 'Front',0 Capabilities: pvolume pswitch Playback channels: Front Left - Front Right Limits: Playback 0 - 64 Mono: Front Left: Playback 44 [69%] [-20.00dB] [on] Front Right: Playback 44 [69%] [-20.00dB] [on] Simple mixer control 'Front Mic',0 Capabilities: pvolume pswitch Playback channels: Front Left - Front Right Limits: Playback 0 - 31 Mono: Front Left: Playback 0 [0%] [-34.50dB] [off] Front Right: Playback 0 [0%] [-34.50dB] [off] Simple mixer control 'Front Mic Boost',0 Capabilities: volume Playback channels: Front Left - Front Right Capture channels: Front Left - Front Right Limits: 0 - 3 Front Left: 0 [0%] Front Right: 0 [0%] Simple mixer control 'Surround',0 Capabilities: pvolume pswitch Playback channels: Front Left - Front Right Limits: Playback 0 - 64 Mono: Front Left: Playback 0 [0%] [-64.00dB] [on] Front Right: Playback 0 [0%] [-64.00dB] [on] Simple mixer control 'Center',0 Capabilities: pvolume pvolume-joined pswitch pswitch-joined Playback channels: Mono Limits: Playback 0 - 64 Mono: Playback 0 [0%] [-64.00dB] [on] Simple mixer control 'LFE',0 Capabilities: pvolume pvolume-joined pswitch pswitch-joined Playback channels: Mono Limits: Playback 0 - 64 Mono: Playback 0 [0%] [-64.00dB] [on] Simple mixer control 'Side',0 Capabilities: pvolume pswitch Playback channels: Front Left - Front Right Limits: Playback 0 - 64 Mono: Front Left: Playback 0 [0%] [-64.00dB] [on] Front Right: Playback 0 [0%] [-64.00dB] [on] Simple mixer control 'Line',0 Capabilities: pvolume pswitch Playback channels: Front Left - Front Right Limits: Playback 0 - 31 Mono: Front Left: Playback 0 [0%] [-34.50dB] [off] Front Right: Playback 0 [0%] [-34.50dB] [off] Simple mixer control 'Mic',0 Capabilities: pvolume pswitch Playback channels: Front Left - Front Right Limits: Playback 0 - 31 Mono: Front Left: Playback 0 [0%] [-34.50dB] [off] Front Right: Playback 0 [0%] [-34.50dB] [off] Simple mixer control 'Mic Boost',0 Capabilities: volume Playback channels: Front Left - Front Right Capture channels: Front Left - Front Right Limits: 0 - 3 Front Left: 0 [0%] Front Right: 0 [0%] Simple mixer control 'IEC958',0 Capabilities: pswitch pswitch-joined cswitch cswitch-joined Playback channels: Mono Capture channels: Mono Mono: Playback [on] Capture [on] Simple mixer control 'IEC958 Default PCM',0 Capabilities: pswitch pswitch-joined Playback channels: Mono Mono: Playback [on] Simple mixer control 'Capture',0 Capabilities: cvolume cswitch Capture channels: Front Left - Front Right Limits: Capture 0 - 46 Front Left: Capture 23 [50%] [7.00dB] [on] Front Right: Capture 23 [50%] [7.00dB] [on] Simple mixer control 'Capture',1 Capabilities: cvolume cswitch Capture channels: Front Left - Front Right Limits: Capture 0 - 46 Front Left: Capture 0 [0%] [-16.00dB] [off] Front Right: Capture 0 [0%] [-16.00dB] [off] Simple mixer control 'Input Source',0 Capabilities: cenum Items: 'Mic' 'Front Mic' 'Line' Item0: 'Mic' Simple mixer control 'Input Source',1 Capabilities: cenum Items: 'Mic' 'Front Mic' 'Line' Item0: 'Mic' Intel 82801CA-ICH3--Analog Devices AD1881A000066400000000000000000000112071511204443500344340ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/mixer/samplesSimple mixer control 'Master',0 Capabilities: pvolume pswitch pswitch-joined Playback channels: Front Left - Front Right Limits: Playback 0 - 63 Mono: Front Left: Playback 44 [70%] [-28.50dB] [on] Front Right: Playback 60 [95%] [-4.50dB] [on] Simple mixer control 'Master Mono',0 Capabilities: pvolume pvolume-joined pswitch pswitch-joined Playback channels: Mono Limits: Playback 0 - 31 Mono: Playback 17 [55%] [-21.00dB] [on] Simple mixer control '3D Control - Center',0 Capabilities: volume volume-joined Playback channels: Mono Capture channels: Mono Limits: 0 - 15 Mono: 0 [0%] Simple mixer control '3D Control - Depth',0 Capabilities: volume volume-joined Playback channels: Mono Capture channels: Mono Limits: 0 - 15 Mono: 0 [0%] Simple mixer control '3D Control - Switch',0 Capabilities: pswitch pswitch-joined Playback channels: Mono Mono: Playback [off] Simple mixer control 'PCM',0 Capabilities: pvolume pswitch pswitch-joined Playback channels: Front Left - Front Right Limits: Playback 0 - 31 Mono: Front Left: Playback 9 [29%] [-21.00dB] [on] Front Right: Playback 9 [29%] [-21.00dB] [on] Simple mixer control 'PCM Out Path & Mute',0 Capabilities: enum Items: 'pre 3D' 'post 3D' Item0: 'pre 3D' Simple mixer control 'Line',0 Capabilities: pvolume pswitch pswitch-joined cswitch cswitch-exclusive Capture exclusive group: 0 Playback channels: Front Left - Front Right Capture channels: Front Left - Front Right Limits: Playback 0 - 31 Front Left: Playback 0 [0%] [-34.50dB] [off] Capture [off] Front Right: Playback 0 [0%] [-34.50dB] [off] Capture [off] Simple mixer control 'CD',0 Capabilities: pvolume pswitch pswitch-joined cswitch cswitch-exclusive Capture exclusive group: 0 Playback channels: Front Left - Front Right Capture channels: Front Left - Front Right Limits: Playback 0 - 31 Front Left: Playback 9 [29%] [-21.00dB] [on] Capture [off] Front Right: Playback 9 [29%] [-21.00dB] [on] Capture [off] Simple mixer control 'Mic',0 Capabilities: pvolume pvolume-joined pswitch pswitch-joined cswitch cswitch-exclusive Capture exclusive group: 0 Playback channels: Mono Capture channels: Front Left - Front Right Limits: Playback 0 - 31 Mono: Playback 0 [0%] [-34.50dB] [off] Front Left: Capture [on] Front Right: Capture [on] Simple mixer control 'Mic Boost (+20dB)',0 Capabilities: pswitch pswitch-joined Playback channels: Mono Mono: Playback [off] Simple mixer control 'Mic Select',0 Capabilities: enum Items: 'Mic1' 'Mic2' Item0: 'Mic1' Simple mixer control 'Video',0 Capabilities: pvolume pswitch pswitch-joined cswitch cswitch-exclusive Capture exclusive group: 0 Playback channels: Front Left - Front Right Capture channels: Front Left - Front Right Limits: Playback 0 - 31 Front Left: Playback 0 [0%] [-34.50dB] [off] Capture [off] Front Right: Playback 0 [0%] [-34.50dB] [off] Capture [off] Simple mixer control 'Phone',0 Capabilities: pvolume pvolume-joined pswitch pswitch-joined cswitch cswitch-exclusive Capture exclusive group: 0 Playback channels: Mono Capture channels: Front Left - Front Right Limits: Playback 0 - 31 Mono: Playback 0 [0%] [-34.50dB] [off] Front Left: Capture [off] Front Right: Capture [off] Simple mixer control 'PC Speaker',0 Capabilities: pvolume pvolume-joined pswitch pswitch-joined Playback channels: Mono Limits: Playback 0 - 15 Mono: Playback 8 [53%] [-21.00dB] [on] Simple mixer control 'Aux',0 Capabilities: pvolume pswitch pswitch-joined cswitch cswitch-exclusive Capture exclusive group: 0 Playback channels: Front Left - Front Right Capture channels: Front Left - Front Right Limits: Playback 0 - 31 Front Left: Playback 0 [0%] [-34.50dB] [off] Capture [off] Front Right: Playback 0 [0%] [-34.50dB] [off] Capture [off] Simple mixer control 'Mono Output Select',0 Capabilities: enum Items: 'Mix' 'Mic' Item0: 'Mix' Simple mixer control 'Capture',0 Capabilities: cvolume cswitch cswitch-joined Capture channels: Front Left - Front Right Limits: Capture 0 - 15 Front Left: Capture 13 [87%] [19.50dB] [on] Front Right: Capture 13 [87%] [19.50dB] [on] Simple mixer control 'Mix',0 Capabilities: cswitch cswitch-exclusive Capture exclusive group: 0 Capture channels: Front Left - Front Right Front Left: Capture [off] Front Right: Capture [off] Simple mixer control 'Mix Mono',0 Capabilities: cswitch cswitch-exclusive Capture exclusive group: 0 Capture channels: Front Left - Front Right Front Left: Capture [off] Front Right: Capture [off] Simple mixer control 'External Amplifier',0 Capabilities: pswitch pswitch-joined Playback channels: Mono Mono: Playback [on] Logitech USB Speaker--USB Mixer000066400000000000000000000014561511204443500336200ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/mixer/samplesSimple mixer control 'Bass',0 Capabilities: volume volume-joined Playback channels: Mono Capture channels: Mono Limits: 0 - 48 Mono: 22 [46%] Simple mixer control 'Bass Boost',0 Capabilities: pswitch pswitch-joined Playback channels: Mono Mono: Playback [off] Simple mixer control 'Treble',0 Capabilities: volume volume-joined Playback channels: Mono Capture channels: Mono Limits: 0 - 48 Mono: 25 [52%] Simple mixer control 'PCM',0 Capabilities: pvolume pswitch pswitch-joined Playback channels: Front Left - Front Right Limits: Playback 0 - 44 Mono: Front Left: Playback 10 [23%] [-31.00dB] [on] Front Right: Playback 10 [23%] [-31.00dB] [on] Simple mixer control 'Auto Gain Control',0 Capabilities: pswitch pswitch-joined Playback channels: Mono Mono: Playback [off] USB Audio--USB Mixer000066400000000000000000000027571511204443500315550ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/mixer/samplesSimple mixer control 'Master',0 Capabilities: pvolume pvolume-joined pswitch pswitch-joined Playback channels: Mono Limits: Playback 0 - 255 Mono: Playback 105 [41%] [-28.97dB] [on] Simple mixer control 'Line',0 Capabilities: pvolume cvolume pswitch pswitch-joined cswitch cswitch-joined Playback channels: Front Left - Front Right Capture channels: Front Left - Front Right Limits: Playback 0 - 255 Capture 0 - 128 Front Left: Playback 191 [75%] [34.38dB] [off] Capture 0 [0%] [0.18dB] [off] Front Right: Playback 191 [75%] [34.38dB] [off] Capture 0 [0%] [0.18dB] [off] Simple mixer control 'Mic',0 Capabilities: pvolume pvolume-joined cvolume cvolume-joined pswitch pswitch-joined cswitch cswitch-joined cswitch-exclusive Capture exclusive group: 0 Playback channels: Mono Capture channels: Mono Limits: Playback 0 - 255 Capture 0 - 128 Mono: Playback 191 [75%] [34.38dB] [off] Capture 0 [0%] [0.18dB] [on] Simple mixer control 'Mic Capture',0 Capabilities: pswitch pswitch-joined Playback channels: Mono Mono: Playback [off] Simple mixer control 'IEC958 In',0 Capabilities: cswitch cswitch-joined Capture channels: Mono Mono: Capture [off] Simple mixer control 'Input 1',0 Capabilities: cswitch cswitch-joined cswitch-exclusive Capture exclusive group: 0 Capture channels: Mono Mono: Capture [off] Simple mixer control 'Input 2',0 Capabilities: cswitch cswitch-joined cswitch-exclusive Capture exclusive group: 0 Capture channels: Mono Mono: Capture [off] USB Device 0x46d_0x9a4--USB Mixer000066400000000000000000000002711511204443500334530ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/mixer/samplesSimple mixer control 'Mic',0 Capabilities: cvolume cvolume-joined cswitch cswitch-joined Capture channels: Mono Limits: Capture 0 - 3072 Mono: Capture 1536 [50%] [23.00dB] [on] VIA 8237--Analog Devices AD1888000066400000000000000000000161511511204443500327420ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/mixer/samplesSimple mixer control 'Master',0 Capabilities: pvolume pswitch Playback channels: Front Left - Front Right Limits: Playback 0 - 31 Mono: Front Left: Playback 31 [100%] [0.00dB] [on] Front Right: Playback 31 [100%] [0.00dB] [on] Simple mixer control 'Master Mono',0 Capabilities: pvolume pvolume-joined pswitch pswitch-joined Playback channels: Mono Limits: Playback 0 - 31 Mono: Playback 0 [0%] [-46.50dB] [off] Simple mixer control 'Master Surround',0 Capabilities: pvolume pswitch Playback channels: Front Left - Front Right Limits: Playback 0 - 31 Mono: Front Left: Playback 0 [0%] [-46.50dB] [off] Front Right: Playback 0 [0%] [-46.50dB] [off] Simple mixer control 'Headphone Jack Sense',0 Capabilities: pswitch pswitch-joined Playback channels: Mono Mono: Playback [off] Simple mixer control 'PCM',0 Capabilities: pvolume pswitch Playback channels: Front Left - Front Right Limits: Playback 0 - 31 Mono: Front Left: Playback 23 [74%] [0.00dB] [on] Front Right: Playback 23 [74%] [0.00dB] [on] Simple mixer control 'Surround',0 Capabilities: pvolume pswitch Playback channels: Front Left - Front Right Limits: Playback 0 - 31 Mono: Front Left: Playback 0 [0%] [-46.50dB] [off] Front Right: Playback 0 [0%] [-46.50dB] [off] Simple mixer control 'Surround Jack Mode',0 Capabilities: enum Items: 'Shared' 'Independent' Item0: 'Shared' Simple mixer control 'Center',0 Capabilities: pvolume pvolume-joined pswitch pswitch-joined Playback channels: Mono Limits: Playback 0 - 31 Mono: Playback 31 [100%] [0.00dB] [off] Simple mixer control 'LFE',0 Capabilities: pvolume pvolume-joined pswitch pswitch-joined Playback channels: Mono Limits: Playback 0 - 31 Mono: Playback 0 [0%] [-46.50dB] [off] Simple mixer control 'Line',0 Capabilities: pvolume pswitch cswitch cswitch-exclusive Capture exclusive group: 0 Playback channels: Front Left - Front Right Capture channels: Front Left - Front Right Limits: Playback 0 - 31 Front Left: Playback 0 [0%] [-34.50dB] [off] Capture [off] Front Right: Playback 0 [0%] [-34.50dB] [off] Capture [off] Simple mixer control 'Line Jack Sense',0 Capabilities: pswitch pswitch-joined Playback channels: Mono Mono: Playback [off] Simple mixer control 'CD',0 Capabilities: pvolume pswitch cswitch cswitch-exclusive Capture exclusive group: 0 Playback channels: Front Left - Front Right Capture channels: Front Left - Front Right Limits: Playback 0 - 31 Front Left: Playback 0 [0%] [-34.50dB] [off] Capture [off] Front Right: Playback 0 [0%] [-34.50dB] [off] Capture [off] Simple mixer control 'Mic',0 Capabilities: pvolume pvolume-joined pswitch pswitch-joined cswitch cswitch-exclusive Capture exclusive group: 0 Playback channels: Mono Capture channels: Front Left - Front Right Limits: Playback 0 - 31 Mono: Playback 0 [0%] [-34.50dB] [off] Front Left: Capture [on] Front Right: Capture [on] Simple mixer control 'Mic Boost (+20dB)',0 Capabilities: pswitch pswitch-joined Playback channels: Mono Mono: Playback [off] Simple mixer control 'Mic Select',0 Capabilities: enum Items: 'Mic1' 'Mic2' Item0: 'Mic1' Simple mixer control 'Video',0 Capabilities: cswitch cswitch-exclusive Capture exclusive group: 0 Capture channels: Front Left - Front Right Front Left: Capture [off] Front Right: Capture [off] Simple mixer control 'Phone',0 Capabilities: pvolume pvolume-joined pswitch pswitch-joined cswitch cswitch-exclusive Capture exclusive group: 0 Playback channels: Mono Capture channels: Front Left - Front Right Limits: Playback 0 - 31 Mono: Playback 0 [0%] [-34.50dB] [off] Front Left: Capture [off] Front Right: Capture [off] Simple mixer control 'IEC958',0 Capabilities: pswitch pswitch-joined Playback channels: Mono Mono: Playback [off] Simple mixer control 'IEC958 Output',0 Capabilities: pswitch pswitch-joined Playback channels: Mono Mono: Playback [off] Simple mixer control 'IEC958 Playback AC97-SPSA',0 Capabilities: volume volume-joined Playback channels: Mono Capture channels: Mono Limits: 0 - 3 Mono: 3 [100%] Simple mixer control 'IEC958 Playback Source',0 Capabilities: enum Items: 'AC-Link' 'A/D Converter' Item0: 'AC-Link' Simple mixer control 'Aux',0 Capabilities: pvolume pswitch cswitch cswitch-exclusive Capture exclusive group: 0 Playback channels: Front Left - Front Right Capture channels: Front Left - Front Right Limits: Playback 0 - 31 Front Left: Playback 0 [0%] [-34.50dB] [off] Capture [off] Front Right: Playback 0 [0%] [-34.50dB] [off] Capture [off] Simple mixer control 'Capture',0 Capabilities: cvolume cswitch Capture channels: Front Left - Front Right Limits: Capture 0 - 15 Front Left: Capture 0 [0%] [0.00dB] [on] Front Right: Capture 0 [0%] [0.00dB] [on] Simple mixer control 'Mix',0 Capabilities: cswitch cswitch-exclusive Capture exclusive group: 0 Capture channels: Front Left - Front Right Front Left: Capture [off] Front Right: Capture [off] Simple mixer control 'Mix Mono',0 Capabilities: cswitch cswitch-exclusive Capture exclusive group: 0 Capture channels: Front Left - Front Right Front Left: Capture [off] Front Right: Capture [off] Simple mixer control 'Channel Mode',0 Capabilities: enum Items: '2ch' '4ch' '6ch' Item0: '2ch' Simple mixer control 'Downmix',0 Capabilities: enum Items: 'Off' '6 -> 4' '6 -> 2' Item0: 'Off' Simple mixer control 'Exchange Front/Surround',0 Capabilities: pswitch pswitch-joined Playback channels: Mono Mono: Playback [off] Simple mixer control 'External Amplifier',0 Capabilities: pswitch pswitch-joined Playback channels: Mono Mono: Playback [on] Simple mixer control 'High Pass Filter Enable',0 Capabilities: pswitch pswitch-joined Playback channels: Mono Mono: Playback [off] Simple mixer control 'Input Source Select',0 Capabilities: enum Items: 'Input1' 'Input2' Item0: 'Input1' Simple mixer control 'Input Source Select',1 Capabilities: enum Items: 'Input1' 'Input2' Item0: 'Input1' Simple mixer control 'Spread Front to Surround and Center/LFE',0 Capabilities: pswitch pswitch-joined Playback channels: Mono Mono: Playback [off] Simple mixer control 'VIA DXS',0 Capabilities: pvolume Playback channels: Front Left - Front Right Limits: Playback 0 - 31 Mono: Front Left: Playback 31 [100%] [-48.00dB] Front Right: Playback 31 [100%] [-48.00dB] Simple mixer control 'VIA DXS',1 Capabilities: pvolume Playback channels: Front Left - Front Right Limits: Playback 0 - 31 Mono: Front Left: Playback 31 [100%] [-48.00dB] Front Right: Playback 31 [100%] [-48.00dB] Simple mixer control 'VIA DXS',2 Capabilities: pvolume Playback channels: Front Left - Front Right Limits: Playback 0 - 31 Mono: Front Left: Playback 31 [100%] [-48.00dB] Front Right: Playback 31 [100%] [-48.00dB] Simple mixer control 'VIA DXS',3 Capabilities: pvolume Playback channels: Front Left - Front Right Limits: Playback 0 - 31 Mono: Front Left: Playback 31 [100%] [-48.00dB] Front Right: Playback 31 [100%] [-48.00dB] Simple mixer control 'V_REFOUT Enable',0 Capabilities: pswitch pswitch-joined Playback channels: Mono Mono: Playback [on] VIA 8237--C-Media Electronics CMI9761A+000066400000000000000000000130501511204443500341210ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/mixer/samplesSimple mixer control 'Master',0 Capabilities: pvolume pswitch pswitch-joined Playback channels: Front Left - Front Right Limits: Playback 0 - 31 Mono: Front Left: Playback 0 [0%] [-46.50dB] [off] Front Right: Playback 0 [0%] [-46.50dB] [off] Simple mixer control 'PCM',0 Capabilities: pvolume pswitch pswitch-joined Playback channels: Front Left - Front Right Limits: Playback 0 - 31 Mono: Front Left: Playback 31 [100%] [-48.00dB] [off] Front Right: Playback 31 [100%] [-48.00dB] [off] Simple mixer control 'Surround',0 Capabilities: pswitch Playback channels: Front Left - Front Right Mono: Front Left: Playback [off] Front Right: Playback [off] Simple mixer control 'Surround Jack Mode',0 Capabilities: enum Items: 'Shared' 'Independent' Item0: 'Shared' Simple mixer control 'Center',0 Capabilities: pvolume pvolume-joined pswitch pswitch-joined Playback channels: Mono Limits: Playback 0 - 31 Mono: Playback 31 [100%] [0.00dB] [off] Simple mixer control 'LFE',0 Capabilities: pvolume pvolume-joined pswitch pswitch-joined Playback channels: Mono Limits: Playback 0 - 31 Mono: Playback 0 [0%] [-46.50dB] [off] Simple mixer control 'Line',0 Capabilities: pvolume pswitch pswitch-joined cswitch cswitch-exclusive Capture exclusive group: 0 Playback channels: Front Left - Front Right Capture channels: Front Left - Front Right Limits: Playback 0 - 31 Front Left: Playback 0 [0%] [-34.50dB] [off] Capture [off] Front Right: Playback 0 [0%] [-34.50dB] [off] Capture [off] Simple mixer control 'CD',0 Capabilities: pvolume pswitch pswitch-joined cswitch cswitch-exclusive Capture exclusive group: 0 Playback channels: Front Left - Front Right Capture channels: Front Left - Front Right Limits: Playback 0 - 31 Front Left: Playback 0 [0%] [-34.50dB] [off] Capture [off] Front Right: Playback 0 [0%] [-34.50dB] [off] Capture [off] Simple mixer control 'Mic',0 Capabilities: pvolume pswitch pswitch-joined cswitch cswitch-exclusive Capture exclusive group: 0 Playback channels: Front Left - Front Right Capture channels: Front Left - Front Right Limits: Playback 0 - 31 Front Left: Playback 0 [0%] [-34.50dB] [off] Capture [on] Front Right: Playback 0 [0%] [-34.50dB] [off] Capture [on] Simple mixer control 'Mic Boost (+20dB)',0 Capabilities: pswitch pswitch-joined Playback channels: Mono Mono: Playback [off] Simple mixer control 'Mic Select',0 Capabilities: enum Items: 'Mic1' 'Mic2' Item0: 'Mic1' Simple mixer control 'Video',0 Capabilities: cswitch cswitch-exclusive Capture exclusive group: 0 Capture channels: Front Left - Front Right Front Left: Capture [off] Front Right: Capture [off] Simple mixer control 'Phone',0 Capabilities: cswitch cswitch-exclusive Capture exclusive group: 0 Capture channels: Front Left - Front Right Front Left: Capture [off] Front Right: Capture [off] Simple mixer control 'IEC958',0 Capabilities: pswitch pswitch-joined cswitch cswitch-joined Playback channels: Mono Capture channels: Mono Mono: Playback [off] Capture [off] Simple mixer control 'IEC958 Capture Monitor',0 Capabilities: pswitch pswitch-joined Playback channels: Mono Mono: Playback [off] Simple mixer control 'IEC958 Capture Valid',0 Capabilities: pswitch pswitch-joined Playback channels: Mono Mono: Playback [off] Simple mixer control 'IEC958 Output',0 Capabilities: pswitch pswitch-joined Playback channels: Mono Mono: Playback [off] Simple mixer control 'IEC958 Playback AC97-SPSA',0 Capabilities: volume volume-joined Playback channels: Mono Capture channels: Mono Limits: 0 - 3 Mono: 3 [100%] Simple mixer control 'IEC958 Playback Source',0 Capabilities: enum Items: 'AC-Link' 'ADC' 'SPDIF-In' Item0: 'AC-Link' Simple mixer control 'PC Speaker',0 Capabilities: pvolume pvolume-joined pswitch pswitch-joined Playback channels: Mono Limits: Playback 0 - 15 Mono: Playback 0 [0%] [-45.00dB] [off] Simple mixer control 'Aux',0 Capabilities: pvolume pswitch pswitch-joined cswitch cswitch-exclusive Capture exclusive group: 0 Playback channels: Front Left - Front Right Capture channels: Front Left - Front Right Limits: Playback 0 - 31 Front Left: Playback 0 [0%] [-34.50dB] [off] Capture [off] Front Right: Playback 0 [0%] [-34.50dB] [off] Capture [off] Simple mixer control 'Mono Output Select',0 Capabilities: enum Items: 'Mix' 'Mic' Item0: 'Mix' Simple mixer control 'Capture',0 Capabilities: cvolume cswitch cswitch-joined Capture channels: Front Left - Front Right Limits: Capture 0 - 15 Front Left: Capture 0 [0%] [0.00dB] [on] Front Right: Capture 0 [0%] [0.00dB] [on] Simple mixer control 'Mix',0 Capabilities: cswitch cswitch-exclusive Capture exclusive group: 0 Capture channels: Front Left - Front Right Front Left: Capture [off] Front Right: Capture [off] Simple mixer control 'Mix Mono',0 Capabilities: cswitch cswitch-exclusive Capture exclusive group: 0 Capture channels: Front Left - Front Right Front Left: Capture [off] Front Right: Capture [off] Simple mixer control 'Channel Mode',0 Capabilities: enum Items: '2ch' '4ch' '6ch' Item0: '2ch' Simple mixer control 'DAC Clock Source',0 Capabilities: enum Items: 'AC-Link' 'SPDIF-In' 'Both' Item0: 'AC-Link' Simple mixer control 'External Amplifier',0 Capabilities: pswitch pswitch-joined Playback channels: Mono Mono: Playback [on] Simple mixer control 'Input Source Select',0 Capabilities: enum Items: 'Input1' 'Input2' Item0: 'Input1' Simple mixer control 'Input Source Select',1 Capabilities: enum Items: 'Input1' 'Input2' Item0: 'Input1' pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/test-hw-params.c000066400000000000000000000077461511204443500267200ustar00rootroot00000000000000/* Spa */ /* SPDX-FileCopyrightText: Copyright © 2022 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #include #include #include #define DEFAULT_DEVICE "default" struct state { const char *device; snd_output_t *output; snd_pcm_t *hndl; }; #define CHECK(s,msg,...) { \ int __err; \ if ((__err = (s)) < 0) { \ fprintf(stderr, msg ": %s\n", ##__VA_ARGS__, snd_strerror(__err)); \ return __err; \ } \ } static const char *get_class(snd_pcm_class_t c) { switch (c) { case SND_PCM_CLASS_GENERIC: return "generic"; case SND_PCM_CLASS_MULTI: return "multichannel"; case SND_PCM_CLASS_MODEM: return "modem"; case SND_PCM_CLASS_DIGITIZER: return "digitizer"; default: return "unknown"; } } static const char *get_subclass(snd_pcm_subclass_t c) { switch (c) { case SND_PCM_SUBCLASS_GENERIC_MIX: return "generic-mix"; case SND_PCM_SUBCLASS_MULTI_MIX: return "multichannel-mix"; default: return "unknown"; } } static void show_help(const char *name, bool error) { fprintf(error ? stderr : stdout, "%s [options]\n" " -h, --help Show this help\n" " -D, --device device name (default '%s')\n" " -C, --capture capture mode (default playback)\n", name, DEFAULT_DEVICE); } int main(int argc, char *argv[]) { struct state state = { 0, }; snd_pcm_hw_params_t *hparams; snd_pcm_info_t *info; snd_pcm_sync_id_t sync; snd_pcm_stream_t stream = SND_PCM_STREAM_PLAYBACK; snd_pcm_chmap_query_t **maps; int c, i; static const struct option long_options[] = { { "help", no_argument, NULL, 'h' }, { "device", required_argument, NULL, 'D' }, { "capture", no_argument, NULL, 'C' }, { NULL, 0, NULL, 0} }; state.device = DEFAULT_DEVICE; while ((c = getopt_long(argc, argv, "hD:C", long_options, NULL)) != -1) { switch (c) { case 'h': show_help(argv[0], false); return 0; case 'D': state.device = optarg; break; case 'C': stream = SND_PCM_STREAM_CAPTURE; break; default: show_help(argv[0], true); return -1; } } CHECK(snd_output_stdio_attach(&state.output, stdout, 0), "attach failed"); fprintf(stdout, "opening device: '%s'\n", state.device); CHECK(snd_pcm_open(&state.hndl, state.device, stream, 0), "open %s failed", state.device); snd_pcm_info_alloca(&info); snd_pcm_info(state.hndl, info); fprintf(stdout, "info:\n"); fprintf(stdout, " device: %u\n", snd_pcm_info_get_device(info)); fprintf(stdout, " subdevice: %u\n", snd_pcm_info_get_subdevice(info)); fprintf(stdout, " stream: %s\n", snd_pcm_stream_name(snd_pcm_info_get_stream(info))); fprintf(stdout, " card: %d\n", snd_pcm_info_get_card(info)); fprintf(stdout, " id: '%s'\n", snd_pcm_info_get_id(info)); fprintf(stdout, " name: '%s'\n", snd_pcm_info_get_name(info)); fprintf(stdout, " subdevice name: '%s'\n", snd_pcm_info_get_subdevice_name(info)); fprintf(stdout, " class: %s\n", get_class(snd_pcm_info_get_class(info))); fprintf(stdout, " subclass: %s\n", get_subclass(snd_pcm_info_get_subclass(info))); fprintf(stdout, " subdevice count: %u\n", snd_pcm_info_get_subdevices_count(info)); fprintf(stdout, " subdevice avail: %u\n", snd_pcm_info_get_subdevices_avail(info)); sync = snd_pcm_info_get_sync(info); fprintf(stdout, " sync: %08x:%08x:%08x:%08x\n", sync.id32[0], sync.id32[1], sync.id32[2],sync.id32[3]); /* channel maps */ if ((maps = snd_pcm_query_chmaps(state.hndl)) != NULL) { fprintf(stdout, "channels:\n"); for (i = 0; maps[i]; i++) { snd_pcm_chmap_t* map = &maps[i]->map; char buf[2048]; snd_pcm_chmap_print(map, sizeof(buf), buf); fprintf(stdout, " %d: %s\n", map->channels, buf); } snd_pcm_free_chmaps(maps); } /* hw params */ snd_pcm_hw_params_alloca(&hparams); snd_pcm_hw_params_any(state.hndl, hparams); snd_pcm_hw_params_dump(hparams, state.output); snd_pcm_close(state.hndl); return EXIT_SUCCESS; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/alsa/test-timer.c000066400000000000000000000175311511204443500261320ustar00rootroot00000000000000/* Spa */ /* SPDX-FileCopyrightText: Copyright © 2020 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #include #include #include #include #include #define DEFAULT_DEVICE "hw:0" #define M_PI_M2f (float)(M_PI+M_PI) #define BW_PERIOD (SPA_NSEC_PER_SEC * 3) struct state { const char *device; unsigned int format; unsigned int rate; unsigned int channels; snd_pcm_uframes_t period; snd_pcm_uframes_t buffer_frames; snd_pcm_t *hndl; int timerfd; double max_error; float accumulator; uint64_t next_time; uint64_t prev_time; struct spa_dll dll; }; static int set_timeout(struct state *state, uint64_t time) { struct itimerspec ts; ts.it_value.tv_sec = time / SPA_NSEC_PER_SEC; ts.it_value.tv_nsec = time % SPA_NSEC_PER_SEC; ts.it_interval.tv_sec = 0; ts.it_interval.tv_nsec = 0; return timerfd_settime(state->timerfd, TFD_TIMER_ABSTIME, &ts, NULL); } #define CHECK(s,msg,...) { \ int __err; \ if ((__err = (s)) < 0) { \ fprintf(stderr, msg ": %s\n", ##__VA_ARGS__, snd_strerror(__err)); \ return __err; \ } \ } #define LOOP(type,areas,scale) { \ uint32_t i, j; \ type *samples, v; \ samples = (type*)((uint8_t*)areas[0].addr + (areas[0].first + offset*areas[0].step) / 8); \ for (i = 0; i < frames; i++) { \ state->accumulator += M_PI_M2f * 440.0f / state->rate; \ if (state->accumulator >= M_PI_M2f) \ state->accumulator -= M_PI_M2f; \ v = (type)(sin(state->accumulator) * scale); \ for (j = 0; j < state->channels; j++) \ *samples++ = v; \ } \ } static int write_period(struct state *state) { snd_pcm_uframes_t frames = state->period; snd_pcm_uframes_t offset; const snd_pcm_channel_area_t* areas; snd_pcm_mmap_begin(state->hndl, &areas, &offset, &frames); switch (state->format) { case SND_PCM_FORMAT_S32_LE: LOOP(int32_t, areas, 0x7fffffff); break; case SND_PCM_FORMAT_S16_LE: LOOP(int16_t, areas, 0x7fff); break; default: break; } snd_pcm_mmap_commit(state->hndl, offset, frames) ; return 0; } static int on_timer_wakeup(struct state *state) { snd_pcm_sframes_t delay; double error, corr; #if 1 snd_pcm_sframes_t avail; CHECK(snd_pcm_avail_delay(state->hndl, &avail, &delay), "delay"); #else snd_pcm_uframes_t avail; snd_htimestamp_t tstamp; uint64_t then; CHECK(snd_pcm_htimestamp(state->hndl, &avail, &tstamp), "htimestamp"); delay = state->buffer_frames - avail; then = SPA_TIMESPEC_TO_NSEC(&tstamp); if (then != 0) { if (then < state->next_time) { delay -= (state->next_time - then) * state->rate / SPA_NSEC_PER_SEC; } else { delay += (then - state->next_time) * state->rate / SPA_NSEC_PER_SEC; } } #endif /* calculate the error, we want to have exactly 1 period of * samples remaining in the device when we wakeup. */ error = (double)delay - (double)state->period; if (error > state->max_error) error = state->max_error; else if (error < -state->max_error) error = -state->max_error; /* update the dll with the error, this gives a rate correction */ corr = spa_dll_update(&state->dll, error); /* set our new adjusted timeout. alternatively, this value can * instead be used to drive a resampler if this device is * slaved. */ state->next_time += (uint64_t)(state->period / corr * 1e9 / state->rate); set_timeout(state, state->next_time); if (state->next_time - state->prev_time > BW_PERIOD) { state->prev_time = state->next_time; fprintf(stdout, "corr:%f error:%f bw:%f\n", corr, error, state->dll.bw); } /* pull in new samples write a new period */ write_period(state); return 0; } static unsigned int format_from_string(const char *str) { if (strcmp(str, "S32_LE") == 0) return SND_PCM_FORMAT_S32_LE; else if (strcmp(str, "S32_BE") == 0) return SND_PCM_FORMAT_S32_BE; else if (strcmp(str, "S24_LE") == 0) return SND_PCM_FORMAT_S24_LE; else if (strcmp(str, "S24_BE") == 0) return SND_PCM_FORMAT_S24_BE; else if (strcmp(str, "S24_3LE") == 0) return SND_PCM_FORMAT_S24_3LE; else if (strcmp(str, "S24_3_BE") == 0) return SND_PCM_FORMAT_S24_3BE; else if (strcmp(str, "S16_LE") == 0) return SND_PCM_FORMAT_S16_LE; else if (strcmp(str, "S16_BE") == 0) return SND_PCM_FORMAT_S16_BE; return 0; } static void show_help(const char *name, bool error) { fprintf(error ? stderr : stdout, "%s [options]\n" " -h, --help Show this help\n" " -D, --device device name (default %s)\n", name, DEFAULT_DEVICE); } int main(int argc, char *argv[]) { struct state state = { 0, }; snd_pcm_hw_params_t *hparams; snd_pcm_sw_params_t *sparams; struct timespec now; int c; static const struct option long_options[] = { { "help", no_argument, NULL, 'h' }, { "device", required_argument, NULL, 'D' }, { "format", required_argument, NULL, 'f' }, { "rate", required_argument, NULL, 'r' }, { "channels", required_argument, NULL, 'c' }, { NULL, 0, NULL, 0} }; state.device = DEFAULT_DEVICE; state.format = SND_PCM_FORMAT_S16_LE; state.rate = 44100; state.channels = 2; state.period = 1024; while ((c = getopt_long(argc, argv, "hD:f:r:c:", long_options, NULL)) != -1) { switch (c) { case 'h': show_help(argv[0], false); return 0; case 'D': state.device = optarg; break; case 'f': state.format = format_from_string(optarg); break; case 'r': state.rate = atoi(optarg); break; case 'c': state.channels = atoi(optarg); break; default: show_help(argv[0], true); return -1; } } CHECK(snd_pcm_open(&state.hndl, state.device, SND_PCM_STREAM_PLAYBACK, 0), "open %s failed", state.device); /* hw params */ snd_pcm_hw_params_alloca(&hparams); snd_pcm_hw_params_any(state.hndl, hparams); CHECK(snd_pcm_hw_params_set_access(state.hndl, hparams, SND_PCM_ACCESS_MMAP_INTERLEAVED), "set interleaved"); CHECK(snd_pcm_hw_params_set_format(state.hndl, hparams, state.format), "set format"); CHECK(snd_pcm_hw_params_set_channels_near(state.hndl, hparams, &state.channels), "set channels"); CHECK(snd_pcm_hw_params_set_rate_near(state.hndl, hparams, &state.rate, 0), "set rate"); CHECK(snd_pcm_hw_params(state.hndl, hparams), "hw_params"); CHECK(snd_pcm_hw_params_get_buffer_size(hparams, &state.buffer_frames), "get_buffer_size_max"); fprintf(stdout, "opened format:%s rate:%u channels:%u\n", snd_pcm_format_name(state.format), state.rate, state.channels); snd_pcm_sw_params_alloca(&sparams); #if 0 CHECK(snd_pcm_sw_params_current(state.hndl, sparams), "sw_params_current"); CHECK(snd_pcm_sw_params_set_tstamp_mode(state.hndl, sparams, SND_PCM_TSTAMP_ENABLE), "sw_params_set_tstamp_type"); CHECK(snd_pcm_sw_params_set_tstamp_type(state.hndl, sparams, SND_PCM_TSTAMP_TYPE_MONOTONIC), "sw_params_set_tstamp_type"); CHECK(snd_pcm_sw_params(state.hndl, sparams), "sw_params"); #endif spa_dll_init(&state.dll); spa_dll_set_bw(&state.dll, SPA_DLL_BW_MAX, state.period, state.rate); state.max_error = SPA_MAX(256.0, state.period / 2.0f); if ((state.timerfd = timerfd_create(CLOCK_MONOTONIC, 0)) < 0) perror("timerfd"); CHECK(snd_pcm_prepare(state.hndl), "prepare"); /* before we start, write one period */ write_period(&state); /* set our first timeout for now */ clock_gettime(CLOCK_MONOTONIC, &now); state.prev_time = state.next_time = SPA_TIMESPEC_TO_NSEC(&now); set_timeout(&state, state.next_time); /* and start playback */ CHECK(snd_pcm_start(state.hndl), "start"); /* wait for timer to expire and call the wakeup function, * this can be done in a poll loop as well */ while (true) { uint64_t expirations; CHECK(read(state.timerfd, &expirations, sizeof(expirations)), "read"); on_timer_wakeup(&state); } snd_pcm_drain(state.hndl); snd_pcm_close(state.hndl); close(state.timerfd); return EXIT_SUCCESS; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/audioconvert/000077500000000000000000000000001511204443500254445ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/audioconvert/audioadapter.c000066400000000000000000001707741511204443500302720ustar00rootroot00000000000000/* SPA */ /* SPDX-FileCopyrightText: Copyright © 2019 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #undef SPA_LOG_TOPIC_DEFAULT #define SPA_LOG_TOPIC_DEFAULT &log_topic SPA_LOG_TOPIC_DEFINE_STATIC(log_topic, "spa.audioadapter"); #define DEFAULT_ALIGN 16 #define MAX_PORTS (SPA_AUDIO_MAX_CHANNELS+1) #define MAX_RETRY 64 /** \cond */ struct impl { struct spa_handle handle; struct spa_node node; struct spa_log *log; struct spa_cpu *cpu; struct spa_plugin_loader *ploader; uint32_t max_align; enum spa_direction direction; struct spa_node *target; struct spa_node *follower; struct spa_hook follower_listener; uint64_t follower_flags; struct spa_audio_info follower_current_format; struct spa_audio_info default_format; int in_set_param; struct spa_handle *hnd_convert; bool unload_handle; struct spa_node *convert; struct spa_hook convert_listener; uint64_t convert_port_flags; char *convertname; uint32_t n_buffers; struct spa_buffer **buffers; struct spa_io_buffers io_buffers; struct spa_io_rate_match io_rate_match; struct spa_io_position *io_position; uint64_t info_all; struct spa_node_info info; #define IDX_EnumFormat 0 #define IDX_PropInfo 1 #define IDX_Props 2 #define IDX_Format 3 #define IDX_EnumPortConfig 4 #define IDX_PortConfig 5 #define IDX_Latency 6 #define IDX_ProcessLatency 7 #define IDX_Tag 8 #define N_NODE_PARAMS 9 struct spa_param_info params[N_NODE_PARAMS]; uint32_t convert_params_flags[N_NODE_PARAMS]; uint32_t follower_params_flags[N_NODE_PARAMS]; uint64_t follower_port_flags; struct spa_hook_list hooks; struct spa_callbacks callbacks; unsigned int add_listener:1; unsigned int have_rate_match:1; unsigned int have_format:1; unsigned int recheck_format:1; unsigned int started:1; unsigned int ready:1; unsigned int async:1; enum spa_param_port_config_mode mode; unsigned int follower_removing:1; unsigned int in_recalc; unsigned int warned:1; unsigned int driver:1; int in_enum_sync; }; /** \endcond */ static int node_enum_params_sync(struct impl *impl, struct spa_node *node, uint32_t id, uint32_t *index, const struct spa_pod *filter, struct spa_pod **param, struct spa_pod_builder *builder) { int res; impl->in_enum_sync++; res = spa_node_enum_params_sync(node, id, index, filter, param, builder); impl->in_enum_sync--; return res; } static int node_port_enum_params_sync(struct impl *impl, struct spa_node *node, enum spa_direction direction, uint32_t port_id, uint32_t id, uint32_t *index, const struct spa_pod *filter, struct spa_pod **param, struct spa_pod_builder *builder) { int res; impl->in_enum_sync++; res = spa_node_port_enum_params_sync(node, direction, port_id, id, index, filter, param, builder); impl->in_enum_sync--; return res; } static int follower_enum_params(struct impl *this, uint32_t id, uint32_t idx, struct spa_result_node_params *result, const struct spa_pod *filter, struct spa_pod_builder *builder) { int res; if (result->next < 0x100000) { if (this->follower != this->target && this->convert_params_flags[idx] & SPA_PARAM_INFO_READ) { if ((res = node_enum_params_sync(this, this->target, id, &result->next, filter, &result->param, builder)) == 1) return res; } result->next = 0x100000; } if (result->next < 0x200000) { if (this->follower_params_flags[idx] & SPA_PARAM_INFO_READ) { result->next &= 0xfffff; if ((res = node_enum_params_sync(this, this->follower, id, &result->next, filter, &result->param, builder)) == 1) { result->next |= 0x100000; return res; } } result->next = 0x200000; } return 0; } static int convert_enum_port_config(struct impl *this, int seq, uint32_t id, uint32_t start, uint32_t num, const struct spa_pod *filter, struct spa_pod_builder *builder) { struct spa_pod *f1, *f2 = NULL; int res; if (this->convert == NULL) return 0; f1 = spa_pod_builder_add_object(builder, SPA_TYPE_OBJECT_ParamPortConfig, id, SPA_PARAM_PORT_CONFIG_direction, SPA_POD_Id(this->direction)); if (filter) { if ((res = spa_pod_filter(builder, &f2, f1, filter)) < 0) return res; } else { f2 = f1; } return spa_node_enum_params(this->convert, seq, id, start, num, f2); } static int impl_node_enum_params(void *object, int seq, uint32_t id, uint32_t start, uint32_t num, const struct spa_pod *filter) { struct impl *this = object; uint8_t buffer[4096]; spa_auto(spa_pod_dynamic_builder) b = { 0 }; struct spa_pod_builder_state state; struct spa_result_node_params result; uint32_t count = 0; int res; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(num != 0, -EINVAL); spa_pod_dynamic_builder_init(&b, buffer, sizeof(buffer), 4096); spa_pod_builder_get_state(&b.b, &state); result.id = id; result.next = start; next: result.index = result.next; spa_log_debug(this->log, "%p: %d id:%u", this, seq, id); spa_pod_builder_reset(&b.b, &state); switch (id) { case SPA_PARAM_EnumPortConfig: case SPA_PARAM_PortConfig: if (this->mode == SPA_PARAM_PORT_CONFIG_MODE_passthrough) { switch (result.index) { case 0: result.param = spa_pod_builder_add_object(&b.b, SPA_TYPE_OBJECT_ParamPortConfig, id, SPA_PARAM_PORT_CONFIG_direction, SPA_POD_Id(this->direction), SPA_PARAM_PORT_CONFIG_mode, SPA_POD_Id( SPA_PARAM_PORT_CONFIG_MODE_passthrough)); result.next++; res = 1; break; default: return 0; } } else { return convert_enum_port_config(this, seq, id, start, num, filter, &b.b); } break; case SPA_PARAM_PropInfo: res = follower_enum_params(this, id, IDX_PropInfo, &result, filter, &b.b); break; case SPA_PARAM_Props: res = follower_enum_params(this, id, IDX_Props, &result, filter, &b.b); break; case SPA_PARAM_ProcessLatency: res = follower_enum_params(this, id, IDX_ProcessLatency, &result, filter, &b.b); break; case SPA_PARAM_EnumFormat: case SPA_PARAM_Format: case SPA_PARAM_Latency: case SPA_PARAM_Tag: res = node_port_enum_params_sync(this, this->follower, this->direction, 0, id, &result.next, filter, &result.param, &b.b); break; default: return -ENOENT; } if (res != 1) return res; spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); count++; if (count != num) goto next; return 0; } static int link_io(struct impl *this) { int res; struct spa_io_rate_match *rate_match; size_t rate_match_size; spa_log_debug(this->log, "%p: controls", this); spa_zero(this->io_rate_match); this->io_rate_match.rate = 1.0; if (this->follower == this->target || !this->have_rate_match) { rate_match = NULL; rate_match_size = 0; } else { rate_match = &this->io_rate_match; rate_match_size = sizeof(this->io_rate_match); } if ((res = spa_node_port_set_io(this->follower, this->direction, 0, SPA_IO_RateMatch, rate_match, rate_match_size)) < 0) { spa_log_debug(this->log, "%p: set RateMatch on follower disabled %d %s", this, res, spa_strerror(res)); } else if (this->follower != this->target) { if ((res = spa_node_port_set_io(this->target, SPA_DIRECTION_REVERSE(this->direction), 0, SPA_IO_RateMatch, rate_match, rate_match_size)) < 0) { spa_log_warn(this->log, "%p: set RateMatch on target failed %d %s", this, res, spa_strerror(res)); } } return 0; } static int activate_io(struct impl *this, bool active) { int res; struct spa_io_buffers *data = active ? &this->io_buffers : NULL; uint32_t size = active ? sizeof(this->io_buffers) : 0; if (this->follower == this->target) return 0; if (active) this->io_buffers = SPA_IO_BUFFERS_INIT; if ((res = spa_node_port_set_io(this->follower, this->direction, 0, SPA_IO_Buffers, data, size)) < 0) { spa_log_warn(this->log, "%p: set Buffers on follower failed %d %s", this, res, spa_strerror(res)); return res; } else if ((res = spa_node_port_set_io(this->target, SPA_DIRECTION_REVERSE(this->direction), 0, SPA_IO_Buffers, data, size)) < 0) { spa_log_warn(this->log, "%p: set Buffers on convert failed %d %s", this, res, spa_strerror(res)); return res; } return 0; } static void emit_node_info(struct impl *this, bool full) { uint32_t i; uint64_t old = full ? this->info.change_mask : 0; spa_log_debug(this->log, "%p: info full:%d change:%08"PRIx64, this, full, this->info.change_mask); if (full) this->info.change_mask = this->info_all; if (this->info.change_mask) { struct spa_dict_item *items; uint32_t n_items = 0; if (this->info.props) n_items = this->info.props->n_items; items = alloca((n_items + 2) * sizeof(struct spa_dict_item)); for (i = 0; i < n_items; i++) items[i] = this->info.props->items[i]; items[n_items++] = SPA_DICT_ITEM_INIT("adapter.auto-port-config", NULL); items[n_items++] = SPA_DICT_ITEM_INIT("audio.adapt.follower", NULL); this->info.props = &SPA_DICT_INIT(items, n_items); if (this->info.change_mask & SPA_NODE_CHANGE_MASK_PARAMS) { for (i = 0; i < this->info.n_params; i++) { if (this->params[i].user > 0) { this->params[i].flags ^= SPA_PARAM_INFO_SERIAL; this->params[i].user = 0; spa_log_debug(this->log, "param %d flags:%08x", i, this->params[i].flags); } } } spa_node_emit_info(&this->hooks, &this->info); this->info.change_mask = old; spa_zero(this->info.props); } } static int debug_params(struct impl *this, struct spa_node *node, enum spa_direction direction, uint32_t port_id, uint32_t id, struct spa_pod *filter, const char *debug, int err) { struct spa_pod_builder b = { 0 }; uint8_t buffer[4096]; uint32_t state; struct spa_pod *param; int res, count = 0; spa_log_error(this->log, "params %s: %d:%d (%s) %s", spa_debug_type_find_name(spa_type_param, id), direction, port_id, debug, err ? spa_strerror(err) : "no matching params"); if (err == -EBUSY) return 0; if (filter) { spa_log_error(this->log, "with this filter:"); spa_debug_log_pod(this->log, SPA_LOG_LEVEL_ERROR, 2, NULL, filter); } else { spa_log_error(this->log, "there was no filter"); } state = 0; while (true) { spa_pod_builder_init(&b, buffer, sizeof(buffer)); res = node_port_enum_params_sync(this, node, direction, port_id, id, &state, NULL, ¶m, &b); if (res != 1) { if (res < 0) spa_log_error(this->log, " error: %s", spa_strerror(res)); break; } spa_log_error(this->log, "unmatched %s %d:", debug, count); spa_debug_log_pod(this->log, SPA_LOG_LEVEL_ERROR, 2, NULL, param); count++; } if (count == 0) spa_log_error(this->log, "could not get any %s", debug); return 0; } static int negotiate_buffers(struct impl *this) { uint8_t buffer[4096]; struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); uint32_t state; struct spa_pod *param; int res; bool follower_alloc, conv_alloc; uint32_t i, size, buffers, blocks, align, flags, stride = 0; uint32_t *aligns, data_flags; struct spa_data *datas; uint64_t follower_flags, conv_flags; struct spa_node *alloc_node; enum spa_direction alloc_direction; uint32_t alloc_flags; spa_log_debug(this->log, "%p: n_buffers:%d", this, this->n_buffers); if (this->follower == this->target) return 0; if (this->n_buffers > 0) return 0; state = 0; param = NULL; if ((res = node_port_enum_params_sync(this, this->target, SPA_DIRECTION_REVERSE(this->direction), 0, SPA_PARAM_Buffers, &state, param, ¶m, &b)) < 0) { if (res == -ENOENT) param = NULL; else { debug_params(this, this->target, SPA_DIRECTION_REVERSE(this->direction), 0, SPA_PARAM_Buffers, param, "target buffers", res); return res; } } state = 0; if ((res = node_port_enum_params_sync(this, this->follower, this->direction, 0, SPA_PARAM_Buffers, &state, param, ¶m, &b)) != 1) { if (res == -ENOENT) res = 0; else { debug_params(this, this->follower, this->direction, 0, SPA_PARAM_Buffers, param, "follower buffers", res); return res < 0 ? res : -ENOTSUP; } } if (param == NULL) return -ENOTSUP; spa_pod_fixate(param); follower_flags = this->follower_port_flags; conv_flags = this->convert_port_flags; follower_alloc = SPA_FLAG_IS_SET(follower_flags, SPA_PORT_FLAG_CAN_ALLOC_BUFFERS); conv_alloc = SPA_FLAG_IS_SET(conv_flags, SPA_PORT_FLAG_CAN_ALLOC_BUFFERS); flags = alloc_flags = 0; if (conv_alloc || follower_alloc) { flags |= SPA_BUFFER_ALLOC_FLAG_NO_DATA; alloc_flags = SPA_NODE_BUFFERS_FLAG_ALLOC; } align = DEFAULT_ALIGN; if ((res = spa_pod_parse_object(param, SPA_TYPE_OBJECT_ParamBuffers, NULL, SPA_PARAM_BUFFERS_buffers, SPA_POD_Int(&buffers), SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(&blocks), SPA_PARAM_BUFFERS_size, SPA_POD_Int(&size), SPA_PARAM_BUFFERS_stride, SPA_POD_Int(&stride), SPA_PARAM_BUFFERS_align, SPA_POD_OPT_Int(&align))) < 0) return res; if (this->async) buffers = SPA_MAX(2u, buffers); spa_log_info(this->log, "%p: buffers:%d, blocks:%d, size:%d, stride:%d align:%d %d:%d", this, buffers, blocks, size, stride, align, follower_alloc, conv_alloc); align = SPA_MAX(align, this->max_align); datas = alloca(sizeof(struct spa_data) * blocks); memset(datas, 0, sizeof(struct spa_data) * blocks); aligns = alloca(sizeof(uint32_t) * blocks); data_flags = SPA_DATA_FLAG_READWRITE; if (SPA_FLAG_IS_SET(follower_flags, SPA_PORT_FLAG_DYNAMIC_DATA) && SPA_FLAG_IS_SET(conv_flags, SPA_PORT_FLAG_DYNAMIC_DATA)) data_flags |= SPA_DATA_FLAG_DYNAMIC; for (i = 0; i < blocks; i++) { datas[i].type = SPA_DATA_MemPtr; datas[i].flags = data_flags; datas[i].maxsize = size; aligns[i] = align; } free(this->buffers); this->buffers = spa_buffer_alloc_array(buffers, flags, 0, NULL, blocks, datas, aligns); if (this->buffers == NULL) return -errno; this->n_buffers = buffers; /* prefer to let the follower alloc */ if (follower_alloc) { alloc_node = this->follower; alloc_direction = this->direction; } else { alloc_node = this->target; alloc_direction = SPA_DIRECTION_REVERSE(this->direction); } if ((res = spa_node_port_use_buffers(alloc_node, alloc_direction, 0, alloc_flags, this->buffers, this->n_buffers)) < 0) return res; alloc_node = alloc_node == this->follower ? this->target : this->follower; alloc_direction = SPA_DIRECTION_REVERSE(alloc_direction); alloc_flags = 0; if ((res = spa_node_port_use_buffers(alloc_node, alloc_direction, 0, alloc_flags, this->buffers, this->n_buffers)) < 0) return res; activate_io(this, true); return 0; } static void clear_buffers(struct impl *this) { free(this->buffers); this->buffers = NULL; this->n_buffers = 0; } static int configure_format(struct impl *this, uint32_t flags, const struct spa_pod *format) { uint8_t buffer[4096]; int res; spa_log_debug(this->log, "%p: configure format:", this); if (format == NULL) { if (!this->have_format) return 0; activate_io(this, false); } else { spa_debug_log_format(this->log, SPA_LOG_LEVEL_DEBUG, 0, NULL, format); } if ((res = spa_node_port_set_param(this->follower, this->direction, 0, SPA_PARAM_Format, flags, format)) < 0) return res; if (res > 0) { struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); uint32_t state = 0; struct spa_pod *fmt; /* format was changed to nearest compatible format */ if ((res = node_port_enum_params_sync(this, this->follower, this->direction, 0, SPA_PARAM_Format, &state, NULL, &fmt, &b)) != 1) return -EIO; format = fmt; } if (this->target != this->follower) { if ((res = spa_node_port_set_param(this->target, SPA_DIRECTION_REVERSE(this->direction), 0, SPA_PARAM_Format, flags, format)) < 0) return res; } this->have_format = format != NULL; clear_buffers(this); if (format != NULL) res = negotiate_buffers(this); return res; } static int configure_convert(struct impl *this, uint32_t mode) { struct spa_pod_builder b = { 0 }; uint8_t buffer[1024]; struct spa_pod *param; if (this->convert == NULL) return 0; spa_pod_builder_init(&b, buffer, sizeof(buffer)); spa_log_debug(this->log, "%p: configure convert %p", this, this->target); param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamPortConfig, SPA_PARAM_PortConfig, SPA_PARAM_PORT_CONFIG_direction, SPA_POD_Id(this->direction), SPA_PARAM_PORT_CONFIG_mode, SPA_POD_Id(mode)); return spa_node_set_param(this->convert, SPA_PARAM_PortConfig, 0, param); } extern const struct spa_handle_factory spa_audioconvert_factory; static const struct spa_node_events follower_node_events; static int recalc_latency(struct impl *this, struct spa_node *src, enum spa_direction direction, uint32_t port_id, struct spa_node *dst) { struct spa_pod_builder b = { 0 }; uint8_t buffer[1024]; struct spa_pod *param; uint32_t index = 0; struct spa_latency_info latency; int res; spa_log_debug(this->log, "%p: %d:%d", this, direction, port_id); if (this->target == this->follower) return 0; while (true) { spa_pod_builder_init(&b, buffer, sizeof(buffer)); if ((res = node_port_enum_params_sync(this, src, direction, port_id, SPA_PARAM_Latency, &index, NULL, ¶m, &b)) != 1) { param = NULL; break; } if ((res = spa_latency_parse(param, &latency)) < 0) return res; if (latency.direction == direction) break; } if ((res = spa_node_port_set_param(dst, SPA_DIRECTION_REVERSE(direction), 0, SPA_PARAM_Latency, 0, param)) < 0) return res; return 0; } static int recalc_tag(struct impl *this, struct spa_node *src, enum spa_direction direction, uint32_t port_id, struct spa_node *dst) { spa_auto(spa_pod_dynamic_builder) b = { 0 }; struct spa_pod_builder_state state; uint8_t buffer[2048]; struct spa_pod *param; uint32_t index = 0; struct spa_tag_info info; int res; spa_log_debug(this->log, "%p: %d:%d", this, direction, port_id); if (this->target == this->follower) return 0; spa_pod_dynamic_builder_init(&b, buffer, sizeof(buffer), 2048); spa_pod_builder_get_state(&b.b, &state); while (true) { void *tag_state = NULL; spa_pod_builder_reset(&b.b, &state); if ((res = node_port_enum_params_sync(this, src, direction, port_id, SPA_PARAM_Tag, &index, NULL, ¶m, &b.b)) != 1) { param = NULL; break; } if ((res = spa_tag_parse(param, &info, &tag_state)) < 0) return res; if (info.direction == direction) break; } return spa_node_port_set_param(dst, SPA_DIRECTION_REVERSE(direction), 0, SPA_PARAM_Tag, 0, param); } static int reconfigure_mode(struct impl *this, enum spa_param_port_config_mode mode, enum spa_direction direction, struct spa_pod *format) { int res = 0; struct spa_hook l; bool passthrough = mode == SPA_PARAM_PORT_CONFIG_MODE_passthrough; bool old_passthrough = this->mode == SPA_PARAM_PORT_CONFIG_MODE_passthrough; spa_log_debug(this->log, "%p: passthrough mode %d", this, passthrough); if (!passthrough && this->convert == NULL) return -ENOTSUP; if (old_passthrough != passthrough) { if (passthrough) { /* remove converter split/merge ports */ configure_convert(this, SPA_PARAM_PORT_CONFIG_MODE_none); } else { /* remove follower ports */ this->follower_removing = true; spa_zero(l); spa_node_add_listener(this->follower, &l, &follower_node_events, this); spa_hook_remove(&l); this->follower_removing = false; } } /* set new target */ this->target = passthrough ? this->follower : this->convert; if ((res = configure_format(this, SPA_NODE_PARAM_FLAG_NEAREST, format)) < 0) return res; this->mode = mode; if (old_passthrough != passthrough && passthrough) { /* add follower ports */ spa_zero(l); spa_node_add_listener(this->follower, &l, &follower_node_events, this); spa_hook_remove(&l); } else { /* add converter ports */ configure_convert(this, mode); } link_io(this); this->info.change_mask |= SPA_NODE_CHANGE_MASK_FLAGS | SPA_NODE_CHANGE_MASK_PARAMS; SPA_FLAG_UPDATE(this->info.flags, SPA_NODE_FLAG_NEED_CONFIGURE, this->mode == SPA_PARAM_PORT_CONFIG_MODE_none); SPA_FLAG_UPDATE(this->info.flags, SPA_NODE_FLAG_ASYNC, this->async && this->follower == this->target); this->params[IDX_Props].user++; emit_node_info(this, false); spa_log_debug(this->log, "%p: passthrough mode %d", this, passthrough); return 0; } static int impl_node_set_param(void *object, uint32_t id, uint32_t flags, const struct spa_pod *param) { int res = 0, res2 = 0; struct impl *this = object; struct spa_audio_info info = { 0 }; spa_log_debug(this->log, "%p: set param %d", this, id); switch (id) { case SPA_PARAM_Format: if (this->started) { spa_log_error(this->log, "%p: cannot set Format param: " "node already started", this); return -EIO; } if (param == NULL) { spa_log_error(this->log, "%p: attempted to set NULL Format POD", this); return -EINVAL; } if (spa_format_audio_parse(param, &info) < 0) { spa_log_error(this->log, "%p: cannot set Format param: " "parsing the POD failed", this); spa_debug_log_pod(this->log, SPA_LOG_LEVEL_ERROR, 0, NULL, param); return -EINVAL; } if (info.media_subtype != SPA_MEDIA_SUBTYPE_raw) { const char *subtype_name = spa_type_to_short_name(info.media_subtype, spa_type_media_subtype, ""); spa_log_error(this->log, "%p: cannot set Format param: " "expected raw subtype, got subtype \"%s\"", this, subtype_name); return -EINVAL; } this->follower_current_format = info; break; case SPA_PARAM_PortConfig: { enum spa_direction dir; enum spa_param_port_config_mode mode; struct spa_pod *format = NULL; if (this->started) { spa_log_error(this->log, "%p: cannot set PortConfig param: " "node already started", this); return -EIO; } if (spa_pod_parse_object(param, SPA_TYPE_OBJECT_ParamPortConfig, NULL, SPA_PARAM_PORT_CONFIG_direction, SPA_POD_Id(&dir), SPA_PARAM_PORT_CONFIG_mode, SPA_POD_Id(&mode), SPA_PARAM_PORT_CONFIG_format, SPA_POD_OPT_Pod(&format)) < 0) { spa_log_error(this->log, "%p: cannot set PortConfig param: " "parsing the POD failed", this); spa_debug_log_pod(this->log, SPA_LOG_LEVEL_ERROR, 0, NULL, param); return -EINVAL; } if (format) { struct spa_audio_info info; spa_zero(info); if ((res = spa_format_audio_parse(format, &info)) < 0) { spa_log_error(this->log, "%p: cannot set PortConfig param: " "parsing format failed: %s", this, spa_strerror(res)); spa_debug_log_pod(this->log, SPA_LOG_LEVEL_ERROR, 0, NULL, format); return res; } if (info.media_subtype == SPA_MEDIA_SUBTYPE_raw) { info.info.raw.rate = 0; } else { const char *subtype_name = spa_type_to_short_name(info.media_subtype, spa_type_media_subtype, ""); spa_log_error(this->log, "%p: cannot set PortConfig param: " "subtype \"%s\" is not supported", this, subtype_name); return -ENOTSUP; } this->default_format = info; } switch (mode) { case SPA_PARAM_PORT_CONFIG_MODE_none: spa_log_error(this->log, "%p: cannot set PortConfig param: " "\"none\" config mode is not supported", this); return -ENOTSUP; case SPA_PARAM_PORT_CONFIG_MODE_passthrough: if ((res = reconfigure_mode(this, mode, dir, format)) < 0) return res; break; case SPA_PARAM_PORT_CONFIG_MODE_convert: case SPA_PARAM_PORT_CONFIG_MODE_dsp: if ((res = reconfigure_mode(this, mode, dir, NULL)) < 0) return res; break; default: spa_log_error(this->log, "%p: invalid config mode when setting PortConfig param", this); return -EINVAL; } if (this->target != this->follower) { if ((res = spa_node_set_param(this->target, id, flags, param)) < 0) return res; res = recalc_latency(this, this->follower, this->direction, 0, this->target); } break; } case SPA_PARAM_Props: { int in_set_param = ++this->in_set_param; res = spa_node_set_param(this->follower, id, flags, param); if (this->target != this->follower && this->in_set_param == in_set_param) res2 = spa_node_set_param(this->target, id, flags, param); if (res < 0 && res2 < 0) return res; res = 0; break; } case SPA_PARAM_ProcessLatency: res = spa_node_set_param(this->follower, id, flags, param); break; default: res = -ENOTSUP; break; } return res; } static int impl_node_set_io(void *object, uint32_t id, void *data, size_t size) { struct impl *this = object; int res = 0; spa_return_val_if_fail(this != NULL, -EINVAL); switch (id) { case SPA_IO_Position: this->io_position = data; break; default: break; } if (this->target) res = spa_node_set_io(this->target, id, data, size); if (this->target != this->follower) res = spa_node_set_io(this->follower, id, data, size); return res; } static struct spa_pod *merge_objects(struct impl *this, struct spa_pod_builder *b, uint32_t id, struct spa_pod_object *o1, struct spa_pod_object *o2) { const struct spa_pod_prop *p1, *p2; struct spa_pod_frame f; struct spa_pod_builder_state state; int res = 0; if (o2 == NULL || o1->pod.type != o2->pod.type) return (struct spa_pod*)o1; spa_pod_builder_push_object(b, &f, o1->body.type, o1->body.id); p2 = NULL; SPA_POD_OBJECT_FOREACH(o1, p1) { p2 = spa_pod_object_find_prop(o2, p2, p1->key); if (p2 != NULL) { spa_pod_builder_get_state(b, &state); res = spa_pod_filter_prop(b, p2, p1); if (res < 0) spa_pod_builder_reset(b, &state); } if (p2 == NULL || res < 0) spa_pod_builder_raw_padded(b, p1, SPA_POD_PROP_SIZE(p1)); } p1 = NULL; SPA_POD_OBJECT_FOREACH(o2, p2) { p1 = spa_pod_object_find_prop(o1, p1, p2->key); if (p1 != NULL) continue; spa_pod_builder_raw_padded(b, p2, SPA_POD_PROP_SIZE(p2)); } return spa_pod_builder_pop(b, &f); } static int negotiate_format(struct impl *this) { uint32_t fstate, tstate; struct spa_pod *format, *def; uint8_t buffer[4096]; struct spa_pod_builder b = { 0 }; int res, fres; spa_log_debug(this->log, "%p: have_format:%d recheck:%d", this, this->have_format, this->recheck_format); if (this->target == this->follower) return 0; if (this->have_format && !this->recheck_format) return 0; this->recheck_format = false; spa_pod_builder_init(&b, buffer, sizeof(buffer)); spa_node_send_command(this->follower, &SPA_NODE_COMMAND_INIT(SPA_NODE_COMMAND_ParamBegin)); /* The target has been negotiated on its other ports and so it can propose * a passthrough format or an ideal conversion. We use the suggestions of the * target to find the best follower format */ for (tstate = 0;;) { format = NULL; res = node_port_enum_params_sync(this, this->target, SPA_DIRECTION_REVERSE(this->direction), 0, SPA_PARAM_EnumFormat, &tstate, NULL, &format, &b); if (res == -ENOENT) format = NULL; else if (res <= 0) break; if (format != NULL) spa_debug_log_pod(this->log, SPA_LOG_LEVEL_DEBUG, 0, NULL, format); fstate = 0; fres = node_port_enum_params_sync(this, this->follower, this->direction, 0, SPA_PARAM_EnumFormat, &fstate, format, &format, &b); if (fres == 0 && res == 1) continue; if (format != NULL) spa_debug_log_pod(this->log, SPA_LOG_LEVEL_DEBUG, 0, NULL, format); res = fres; break; } if (format == NULL) { debug_params(this, this->follower, this->direction, 0, SPA_PARAM_EnumFormat, format, "follower format", res); debug_params(this, this->target, SPA_DIRECTION_REVERSE(this->direction), 0, SPA_PARAM_EnumFormat, format, "convert format", res); res = -ENOTSUP; goto done; } def = spa_format_audio_build(&b, SPA_PARAM_Format, &this->default_format); format = merge_objects(this, &b, SPA_PARAM_Format, (struct spa_pod_object*)format, (struct spa_pod_object*)def); if (format == NULL) return -ENOSPC; spa_pod_fixate(format); res = configure_format(this, SPA_NODE_PARAM_FLAG_NEAREST, format); done: spa_node_send_command(this->follower, &SPA_NODE_COMMAND_INIT(SPA_NODE_COMMAND_ParamEnd)); return res; } static int impl_node_send_command(void *object, const struct spa_command *command) { struct impl *this = object; int res; spa_return_val_if_fail(this != NULL, -EINVAL); spa_log_debug(this->log, "%p: command %d", this, SPA_NODE_COMMAND_ID(command)); switch (SPA_NODE_COMMAND_ID(command)) { case SPA_NODE_COMMAND_Start: spa_log_debug(this->log, "%p: starting %d", this, this->started); if ((res = negotiate_format(this)) < 0) return res; this->ready = true; this->warned = false; break; case SPA_NODE_COMMAND_Suspend: spa_log_debug(this->log, "%p: suspending", this); break; case SPA_NODE_COMMAND_Pause: spa_log_debug(this->log, "%p: pausing", this); break; case SPA_NODE_COMMAND_Flush: spa_log_debug(this->log, "%p: flushing", this); this->io_buffers.status = SPA_STATUS_OK; break; default: break; } res = spa_node_send_command(this->target, command); if (res == -ENOTSUP && this->target != this->follower) res = 0; if (res < 0) { spa_log_error(this->log, "%p: can't send command %d: %s", this, SPA_NODE_COMMAND_ID(command), spa_strerror(res)); } if (res >= 0 && this->target != this->follower) { if ((res = spa_node_send_command(this->follower, command)) < 0) { spa_log_error(this->log, "%p: can't send command %d: %s", this, SPA_NODE_COMMAND_ID(command), spa_strerror(res)); } } switch (SPA_NODE_COMMAND_ID(command)) { case SPA_NODE_COMMAND_Start: if (res < 0) { spa_log_debug(this->log, "%p: start failed", this); this->ready = false; configure_format(this, 0, NULL); } else { this->started = true; spa_log_debug(this->log, "%p: started", this); } break; case SPA_NODE_COMMAND_Suspend: configure_format(this, 0, NULL); this->started = false; this->warned = false; this->ready = false; spa_log_debug(this->log, "%p: suspended", this); break; case SPA_NODE_COMMAND_Pause: this->started = false; this->warned = false; this->ready = false; spa_log_debug(this->log, "%p: paused", this); break; case SPA_NODE_COMMAND_Flush: spa_log_debug(this->log, "%p: flushed", this); break; } return res; } static void convert_node_info(void *data, const struct spa_node_info *info) { struct impl *this = data; uint32_t i; spa_log_debug(this->log, "%p: info change:%08"PRIx64, this, info->change_mask); if (info->change_mask & SPA_NODE_CHANGE_MASK_PARAMS) { for (i = 0; i < info->n_params; i++) { uint32_t idx; switch (info->params[i].id) { case SPA_PARAM_EnumPortConfig: idx = IDX_EnumPortConfig; break; case SPA_PARAM_PortConfig: idx = IDX_PortConfig; break; case SPA_PARAM_PropInfo: idx = IDX_PropInfo; break; case SPA_PARAM_Props: idx = IDX_Props; break; default: continue; } if (!this->add_listener && this->convert_params_flags[idx] == info->params[i].flags) continue; this->info.change_mask |= SPA_NODE_CHANGE_MASK_PARAMS; this->convert_params_flags[idx] = info->params[i].flags; this->params[idx].flags = (this->params[idx].flags & SPA_PARAM_INFO_SERIAL) | (info->params[i].flags & SPA_PARAM_INFO_READWRITE); if (this->add_listener) continue; this->params[idx].user++; spa_log_debug(this->log, "param %d changed", info->params[i].id); } } emit_node_info(this, false); } static void follower_convert_port_info(void *data, enum spa_direction direction, uint32_t port_id, const struct spa_port_info *info) { struct impl *this = data; uint32_t i; int res; if (info == NULL) return; spa_log_debug(this->log, "%p: convert port info %s %p %08"PRIx64, this, this->direction == SPA_DIRECTION_INPUT ? "Input" : "Output", info, info->change_mask); this->convert_port_flags = info->flags; if (info->change_mask & SPA_PORT_CHANGE_MASK_PARAMS) { for (i = 0; i < info->n_params; i++) { uint32_t idx; switch (info->params[i].id) { case SPA_PARAM_Latency: idx = IDX_Latency; break; case SPA_PARAM_Tag: idx = IDX_Tag; break; case SPA_PARAM_EnumFormat: idx = IDX_EnumFormat; break; default: continue; } if (!this->add_listener && this->convert_params_flags[idx] == info->params[i].flags) continue; this->convert_params_flags[idx] = info->params[i].flags; if (this->add_listener) continue; if (idx == IDX_Latency) { this->in_recalc++; res = recalc_latency(this, this->target, direction, port_id, this->follower); this->in_recalc--; spa_log_debug(this->log, "latency: %d (%s)", res, spa_strerror(res)); } if (idx == IDX_Tag) { this->in_recalc++; res = recalc_tag(this, this->target, direction, port_id, this->follower); this->in_recalc--; spa_log_debug(this->log, "tag: %d (%s)", res, spa_strerror(res)); } if (idx == IDX_EnumFormat) { spa_log_info(this->log, "new EnumFormat from converter"); /* we will renegotiate when restarting */ this->recheck_format = true; } spa_log_debug(this->log, "param %d changed", info->params[i].id); } } } static void convert_port_info(void *data, enum spa_direction direction, uint32_t port_id, const struct spa_port_info *info) { struct impl *this = data; struct spa_port_info pi; if (direction != this->direction) { if (port_id == 0) { /* handle the converter output port into the follower separately */ follower_convert_port_info(this, direction, port_id, info); return; } else /* the monitor ports are exposed */ port_id--; } else if (info) { pi = *info; pi.flags |= this->follower_port_flags & (SPA_PORT_FLAG_LIVE | SPA_PORT_FLAG_PHYSICAL | SPA_PORT_FLAG_TERMINAL); info = π } spa_log_debug(this->log, "%p: port info %d:%d", this, direction, port_id); if (this->target != this->follower) spa_node_emit_port_info(&this->hooks, direction, port_id, info); } static void convert_result(void *data, int seq, int res, uint32_t type, const void *result) { struct impl *this = data; if (this->target == this->follower || this->in_enum_sync) return; spa_log_trace(this->log, "%p: result %d %d", this, seq, res); spa_node_emit_result(&this->hooks, seq, res, type, result); } static const struct spa_node_events convert_node_events = { SPA_VERSION_NODE_EVENTS, .info = convert_node_info, .port_info = convert_port_info, .result = convert_result, }; static void follower_info(void *data, const struct spa_node_info *info) { struct impl *this = data; uint32_t i; spa_log_debug(this->log, "%p: info change:%08"PRIx64" %d:%d", this, info->change_mask, info->max_input_ports, info->max_output_ports); if (this->follower_removing) return; this->async = (info->flags & SPA_NODE_FLAG_ASYNC) != 0; if (info->max_input_ports > 0) this->direction = SPA_DIRECTION_INPUT; else this->direction = SPA_DIRECTION_OUTPUT; if (this->direction == SPA_DIRECTION_INPUT) { this->info.flags |= SPA_NODE_FLAG_IN_PORT_CONFIG; this->info.max_input_ports = MAX_PORTS; } else { this->info.flags |= SPA_NODE_FLAG_OUT_PORT_CONFIG; this->info.max_output_ports = MAX_PORTS; } SPA_FLAG_UPDATE(this->info.flags, SPA_NODE_FLAG_ASYNC, this->async && this->follower == this->target); spa_log_debug(this->log, "%p: follower info %s", this, this->direction == SPA_DIRECTION_INPUT ? "Input" : "Output"); if (info->change_mask & SPA_NODE_CHANGE_MASK_PROPS) { this->info.change_mask |= SPA_NODE_CHANGE_MASK_PROPS; this->info.props = info->props; } if (info->change_mask & SPA_NODE_CHANGE_MASK_PARAMS) { for (i = 0; i < info->n_params; i++) { uint32_t idx; switch (info->params[i].id) { case SPA_PARAM_PropInfo: idx = IDX_PropInfo; break; case SPA_PARAM_Props: idx = IDX_Props; break; case SPA_PARAM_ProcessLatency: idx = IDX_ProcessLatency; break; default: continue; } if (!this->add_listener && this->follower_params_flags[idx] == info->params[i].flags) continue; this->info.change_mask |= SPA_NODE_CHANGE_MASK_PARAMS; this->follower_params_flags[idx] = info->params[i].flags; this->params[idx].flags = (this->params[idx].flags & SPA_PARAM_INFO_SERIAL) | (info->params[i].flags & SPA_PARAM_INFO_READWRITE); if (this->add_listener) continue; this->params[idx].user++; spa_log_debug(this->log, "param %d changed", info->params[i].id); } } emit_node_info(this, false); spa_zero(this->info.props); this->info.change_mask &= ~SPA_NODE_CHANGE_MASK_PROPS; } static void follower_port_info(void *data, enum spa_direction direction, uint32_t port_id, const struct spa_port_info *info) { struct impl *this = data; uint32_t i; int res; if (info == NULL) return; if (this->follower_removing) { spa_node_emit_port_info(&this->hooks, direction, port_id, NULL); return; } this->follower_port_flags = info->flags; spa_log_debug(this->log, "%p: follower port info %s %p %08"PRIx64" recalc:%u", this, this->direction == SPA_DIRECTION_INPUT ? "Input" : "Output", info, info->change_mask, this->in_recalc); if (info->change_mask & SPA_PORT_CHANGE_MASK_PARAMS) { for (i = 0; i < info->n_params; i++) { uint32_t idx; switch (info->params[i].id) { case SPA_PARAM_EnumFormat: idx = IDX_EnumFormat; break; case SPA_PARAM_Format: idx = IDX_Format; break; case SPA_PARAM_Latency: idx = IDX_Latency; break; case SPA_PARAM_Tag: idx = IDX_Tag; break; default: continue; } if (!this->add_listener && this->follower_params_flags[idx] == info->params[i].flags) continue; this->info.change_mask |= SPA_NODE_CHANGE_MASK_PARAMS; this->follower_params_flags[idx] = info->params[i].flags; this->params[idx].flags = (this->params[idx].flags & SPA_PARAM_INFO_SERIAL) | (info->params[i].flags & SPA_PARAM_INFO_READWRITE); if (this->add_listener) continue; if (idx == IDX_Latency && this->in_recalc == 0) { res = recalc_latency(this, this->follower, direction, port_id, this->target); spa_log_debug(this->log, "latency: %d (%s)", res, spa_strerror(res)); } if (idx == IDX_Tag && this->in_recalc == 0) { res = recalc_tag(this, this->follower, direction, port_id, this->target); spa_log_debug(this->log, "tag: %d (%s)", res, spa_strerror(res)); } if (idx == IDX_EnumFormat) { spa_log_debug(this->log, "new EnumFormat from follower"); /* we will renegotiate when restarting */ this->recheck_format = true; } this->params[idx].user++; spa_log_debug(this->log, "param %d changed", info->params[i].id); } } emit_node_info(this, false); if (this->target == this->follower) spa_node_emit_port_info(&this->hooks, direction, port_id, info); } static void follower_result(void *data, int seq, int res, uint32_t type, const void *result) { struct impl *this = data; if (this->target != this->follower || this->in_enum_sync) return; spa_log_trace(this->log, "%p: result %d %d", this, seq, res); spa_node_emit_result(&this->hooks, seq, res, type, result); } static void follower_event(void *data, const struct spa_event *event) { struct impl *this = data; spa_log_trace(this->log, "%p: event %d", this, SPA_EVENT_TYPE(event)); switch (SPA_NODE_EVENT_ID(event)) { case SPA_NODE_EVENT_Error: case SPA_NODE_EVENT_RequestProcess: /* Forward errors and process requests */ spa_node_emit_event(&this->hooks, event); break; default: /* Ignore other events */ break; } } static const struct spa_node_events follower_node_events = { SPA_VERSION_NODE_EVENTS, .info = follower_info, .port_info = follower_port_info, .result = follower_result, .event = follower_event, }; static void follower_probe_info(void *data, const struct spa_node_info *info) { struct impl *this = data; if (info->max_input_ports > 0) this->direction = SPA_DIRECTION_INPUT; else this->direction = SPA_DIRECTION_OUTPUT; } static const struct spa_node_events follower_probe_events = { SPA_VERSION_NODE_EVENTS, .info = follower_probe_info, }; static int follower_ready(void *data, int status) { struct impl *this = data; spa_log_trace_fp(this->log, "%p: ready %d", this, status); if (!this->ready) { spa_log_info(this->log, "%p: ready stopped node", this); return -EIO; } if (this->target != this->follower) { this->driver = true; if (this->direction == SPA_DIRECTION_OUTPUT) { int retry = MAX_RETRY; while (retry--) { status = spa_node_process_fast(this->target); if (status & SPA_STATUS_HAVE_DATA) break; if (status & SPA_STATUS_NEED_DATA) { status = spa_node_process_fast(this->follower); if (!(status & SPA_STATUS_HAVE_DATA)) break; } } } } return spa_node_call_ready(&this->callbacks, status); } static int follower_reuse_buffer(void *data, uint32_t port_id, uint32_t buffer_id) { int res; struct impl *this = data; if (this->target != this->follower) res = spa_node_port_reuse_buffer(this->target, port_id, buffer_id); else res = spa_node_call_reuse_buffer(&this->callbacks, port_id, buffer_id); return res; } static int follower_xrun(void *data, uint64_t trigger, uint64_t delay, struct spa_pod *info) { struct impl *this = data; return spa_node_call_xrun(&this->callbacks, trigger, delay, info); } static const struct spa_node_callbacks follower_node_callbacks = { SPA_VERSION_NODE_CALLBACKS, .ready = follower_ready, .reuse_buffer = follower_reuse_buffer, .xrun = follower_xrun, }; static int impl_node_add_listener(void *object, struct spa_hook *listener, const struct spa_node_events *events, void *data) { struct impl *this = object; struct spa_hook l; struct spa_hook_list save; spa_return_val_if_fail(this != NULL, -EINVAL); spa_log_trace(this->log, "%p: add listener %p", this, listener); spa_hook_list_isolate(&this->hooks, &save, listener, events, data); if (events->info || events->port_info) { this->add_listener = true; spa_zero(l); spa_node_add_listener(this->follower, &l, &follower_node_events, this); spa_hook_remove(&l); if (this->follower != this->target) { spa_zero(l); spa_node_add_listener(this->target, &l, &convert_node_events, this); spa_hook_remove(&l); } this->add_listener = false; emit_node_info(this, true); } spa_hook_list_join(&this->hooks, &save); return 0; } static int impl_node_set_callbacks(void *object, const struct spa_node_callbacks *callbacks, void *data) { struct impl *this = object; spa_return_val_if_fail(this != NULL, -EINVAL); this->callbacks = SPA_CALLBACKS_INIT(callbacks, data); return 0; } static int impl_node_sync(void *object, int seq) { struct impl *this = object; spa_return_val_if_fail(this != NULL, -EINVAL); return spa_node_sync(this->follower, seq); } static int impl_node_add_port(void *object, enum spa_direction direction, uint32_t port_id, const struct spa_dict *props) { struct impl *this = object; spa_return_val_if_fail(this != NULL, -EINVAL); if (direction != this->direction) return -EINVAL; return spa_node_add_port(this->target, direction, port_id, props); } static int impl_node_remove_port(void *object, enum spa_direction direction, uint32_t port_id) { struct impl *this = object; spa_return_val_if_fail(this != NULL, -EINVAL); if (direction != this->direction) return -EINVAL; return spa_node_remove_port(this->target, direction, port_id); } static int port_enum_formats_for_convert(struct impl *this, int seq, enum spa_direction direction, uint32_t port_id, uint32_t id, uint32_t start, uint32_t num, const struct spa_pod *filter) { uint8_t buffer[4096]; struct spa_pod_builder b = { 0 }; int res; uint32_t count = 0; struct spa_result_node_params result; result.id = id; result.next = start; next: result.index = result.next; spa_pod_builder_init(&b, buffer, sizeof(buffer)); if (result.next < 0x100000) { /* Enumerate follower formats first, until we have enough or we run out */ if ((res = node_port_enum_params_sync(this, this->follower, direction, port_id, id, &result.next, filter, &result.param, &b)) != 1) { if (res == 0 || res == -ENOENT) { result.next = 0x100000; goto next; } else { spa_log_error(this->log, "could not enum follower format: %s", spa_strerror(res)); return res; } } } else if (result.next < 0x200000) { /* Then enumerate converter formats */ result.next &= 0xfffff; if ((res = node_port_enum_params_sync(this, this->convert, direction, port_id, id, &result.next, filter, &result.param, &b)) != 1) { return res; } else { result.next |= 0x100000; } } spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); if (++count < num) goto next; return 0; } static int impl_node_port_enum_params(void *object, int seq, enum spa_direction direction, uint32_t port_id, uint32_t id, uint32_t start, uint32_t num, const struct spa_pod *filter) { struct impl *this = object; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(num != 0, -EINVAL); if (direction != this->direction) port_id++; spa_log_debug(this->log, "%p: %d %u %u %u", this, seq, id, start, num); /* We only need special handling for EnumFormat in convert mode */ if (id == SPA_PARAM_EnumFormat && this->mode == SPA_PARAM_PORT_CONFIG_MODE_convert) return port_enum_formats_for_convert(this, seq, direction, port_id, id, start, num, filter); else return spa_node_port_enum_params(this->target, seq, direction, port_id, id, start, num, filter); } static int impl_node_port_set_param(void *object, enum spa_direction direction, uint32_t port_id, uint32_t id, uint32_t flags, const struct spa_pod *param) { struct impl *this = object; spa_return_val_if_fail(this != NULL, -EINVAL); spa_log_debug(this->log, " %d %d %d %d", port_id, id, direction, this->direction); if (direction != this->direction) port_id++; return spa_node_port_set_param(this->target, direction, port_id, id, flags, param); } static int impl_node_port_set_io(void *object, enum spa_direction direction, uint32_t port_id, uint32_t id, void *data, size_t size) { struct impl *this = object; spa_return_val_if_fail(this != NULL, -EINVAL); spa_log_debug(this->log, "set io %d %d %d %d", port_id, id, direction, this->direction); if (direction != this->direction) port_id++; return spa_node_port_set_io(this->target, direction, port_id, id, data, size); } static int impl_node_port_use_buffers(void *object, enum spa_direction direction, uint32_t port_id, uint32_t flags, struct spa_buffer **buffers, uint32_t n_buffers) { struct impl *this = object; int res; spa_return_val_if_fail(this != NULL, -EINVAL); if (direction != this->direction) port_id++; spa_log_debug(this->log, "%p: %d %d:%d", this, n_buffers, direction, port_id); if ((res = spa_node_port_use_buffers(this->target, direction, port_id, flags, buffers, n_buffers)) < 0) return res; return res; } static int impl_node_port_reuse_buffer(void *object, uint32_t port_id, uint32_t buffer_id) { struct impl *this = object; spa_return_val_if_fail(this != NULL, -EINVAL); return spa_node_port_reuse_buffer(this->target, port_id, buffer_id); } static int impl_node_process(void *object) { struct impl *this = object; int status = 0, fstatus, retry = MAX_RETRY; if (!this->ready) { if (!this->warned) spa_log_warn(this->log, "%p: scheduling stopped node", this); this->warned = true; return -EIO; } spa_log_trace_fp(this->log, "%p: process convert:%p driver:%d", this, this->convert, this->driver); if (this->target == this->follower) { if (this->io_position) this->io_rate_match.size = this->io_position->clock.duration; return spa_node_process_fast(this->follower); } if (this->direction == SPA_DIRECTION_INPUT) { /* an input node (sink). * First we run the converter to process the input for the follower * then if it produced data, we run the follower. */ while (retry--) { status = spa_node_process_fast(this->target); /* schedule the follower when the converter needed * a recycled buffer */ if (status == -EPIPE || status == 0) status = SPA_STATUS_HAVE_DATA; else if (status < 0) break; if (status & (SPA_STATUS_HAVE_DATA | SPA_STATUS_DRAINED)) { /* as long as the converter produced something or * is drained, process the follower. */ fstatus = spa_node_process_fast(this->follower); if (fstatus < 0) { status = fstatus; break; } /* if the follower doesn't need more data or is * drained we can stop */ if ((fstatus & SPA_STATUS_NEED_DATA) == 0 || (fstatus & SPA_STATUS_DRAINED)) break; } /* the converter needs more data */ if ((status & SPA_STATUS_NEED_DATA)) break; } } else if (!this->driver) { bool done = false; while (retry--) { /* output node (source). First run the converter to make * sure we push out any queued data. Then when it needs * more data, schedule the follower. */ status = spa_node_process_fast(this->target); if (status == 0) status = SPA_STATUS_NEED_DATA; else if (status < 0) break; done = (status & (SPA_STATUS_HAVE_DATA | SPA_STATUS_DRAINED)); if (done) break; if (status & SPA_STATUS_NEED_DATA) { /* the converter needs more data, schedule the * follower */ fstatus = spa_node_process_fast(this->follower); if (fstatus < 0) { status = fstatus; break; } /* if the follower didn't produce more data or is * not drained we can stop now */ if ((fstatus & (SPA_STATUS_HAVE_DATA | SPA_STATUS_DRAINED)) == 0) break; } } if (!done) spa_node_call_xrun(&this->callbacks, 0, 0, NULL); } else { status = spa_node_process_fast(this->follower); } spa_log_trace_fp(this->log, "%p: process status:%d", this, status); this->driver = false; return status; } static const struct spa_node_methods impl_node = { SPA_VERSION_NODE_METHODS, .add_listener = impl_node_add_listener, .set_callbacks = impl_node_set_callbacks, .sync = impl_node_sync, .enum_params = impl_node_enum_params, .set_param = impl_node_set_param, .set_io = impl_node_set_io, .send_command = impl_node_send_command, .add_port = impl_node_add_port, .remove_port = impl_node_remove_port, .port_enum_params = impl_node_port_enum_params, .port_set_param = impl_node_port_set_param, .port_use_buffers = impl_node_port_use_buffers, .port_set_io = impl_node_port_set_io, .port_reuse_buffer = impl_node_port_reuse_buffer, .process = impl_node_process, }; static int load_converter(struct impl *this, const struct spa_dict *info, const struct spa_support *support, uint32_t n_support) { const char* factory_name = NULL; struct spa_handle *hnd_convert = NULL; void *iface_conv = NULL; bool unload_handle = false; struct spa_dict_item *items; struct spa_dict cinfo; char direction[16]; uint32_t i; items = alloca((info->n_items + 1) * sizeof(struct spa_dict_item)); cinfo = SPA_DICT(items, 0); for (i = 0; i < info->n_items; i++) items[cinfo.n_items++] = info->items[i]; snprintf(direction, sizeof(direction), "%s", SPA_DIRECTION_REVERSE(this->direction) == SPA_DIRECTION_INPUT ? "input" : "output"); items[cinfo.n_items++] = SPA_DICT_ITEM("convert.direction", direction); factory_name = spa_dict_lookup(&cinfo, "audio.adapt.converter"); if (factory_name == NULL) factory_name = SPA_NAME_AUDIO_CONVERT; if (spa_streq(factory_name, SPA_NAME_AUDIO_CONVERT)) { size_t size = spa_handle_factory_get_size(&spa_audioconvert_factory, &cinfo); hnd_convert = calloc(1, size); if (hnd_convert == NULL) return -errno; spa_handle_factory_init(&spa_audioconvert_factory, hnd_convert, &cinfo, support, n_support); } else if (this->ploader) { hnd_convert = spa_plugin_loader_load(this->ploader, factory_name, &cinfo); if (!hnd_convert) return -EINVAL; unload_handle = true; } else { return -ENOTSUP; } spa_handle_get_interface(hnd_convert, SPA_TYPE_INTERFACE_Node, &iface_conv); if (iface_conv == NULL) { if (unload_handle) spa_plugin_loader_unload(this->ploader, hnd_convert); else { spa_handle_clear(hnd_convert); free(hnd_convert); } return -EINVAL; } this->hnd_convert = hnd_convert; this->convert = iface_conv; this->unload_handle = unload_handle; this->convertname = strdup(factory_name); return 0; } static int do_auto_port_config(struct impl *this, const char *str) { uint32_t state = 0, i; uint8_t buffer[4096]; struct spa_pod_builder b; #define POSITION_PRESERVE 0 #define POSITION_AUX 1 #define POSITION_UNKNOWN 2 int l, res, position = POSITION_PRESERVE; struct spa_pod *param; bool have_format = false, monitor = false, control = false; struct spa_audio_info format = { 0, }; enum spa_param_port_config_mode mode = SPA_PARAM_PORT_CONFIG_MODE_none; struct spa_json it[1]; char key[1024], val[256]; const char *v; if (spa_json_begin_object(&it[0], str, strlen(str)) <= 0) return -EINVAL; while ((l = spa_json_object_next(&it[0], key, sizeof(key), &v)) > 0) { if (spa_json_parse_stringn(v, l, val, sizeof(val)) <= 0) continue; if (spa_streq(key, "mode")) { mode = spa_debug_type_find_type_short(spa_type_param_port_config_mode, val); if (mode == SPA_ID_INVALID) mode = SPA_PARAM_PORT_CONFIG_MODE_none; } else if (spa_streq(key, "monitor")) { monitor = spa_atob(val); } else if (spa_streq(key, "control")) { control = spa_atob(val); } else if (spa_streq(key, "position")) { if (spa_streq(val, "unknown")) position = POSITION_UNKNOWN; else if (spa_streq(val, "aux")) position = POSITION_AUX; else position = POSITION_PRESERVE; } } while (true) { struct spa_audio_info info = { 0, }; spa_pod_builder_init(&b, buffer, sizeof(buffer)); if ((res = node_port_enum_params_sync(this, this->follower, this->direction, 0, SPA_PARAM_EnumFormat, &state, NULL, ¶m, &b)) != 1) break; if ((res = spa_format_audio_parse(param, &info)) < 0) continue; spa_pod_object_fixate((struct spa_pod_object*)param); if (info.media_subtype == SPA_MEDIA_SUBTYPE_raw && format.media_subtype == SPA_MEDIA_SUBTYPE_raw && format.info.raw.channels >= info.info.raw.channels) continue; format = info; have_format = true; } if (!have_format) return -ENOENT; if (format.media_subtype == SPA_MEDIA_SUBTYPE_raw) { uint32_t n_pos = SPA_MIN(SPA_N_ELEMENTS(format.info.raw.position), format.info.raw.channels); if (position == POSITION_AUX) { for (i = 0; i < n_pos; i++) format.info.raw.position[i] = SPA_AUDIO_CHANNEL_START_Aux + i; } else if (position == POSITION_UNKNOWN) { for (i = 0; i < n_pos; i++) format.info.raw.position[i] = SPA_AUDIO_CHANNEL_UNKNOWN; } } spa_pod_builder_init(&b, buffer, sizeof(buffer)); param = spa_format_audio_build(&b, SPA_PARAM_Format, &format); param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamPortConfig, SPA_PARAM_PortConfig, SPA_PARAM_PORT_CONFIG_direction, SPA_POD_Id(this->direction), SPA_PARAM_PORT_CONFIG_mode, SPA_POD_Id(mode), SPA_PARAM_PORT_CONFIG_monitor, SPA_POD_Bool(monitor), SPA_PARAM_PORT_CONFIG_control, SPA_POD_Bool(control), SPA_PARAM_PORT_CONFIG_format, SPA_POD_Pod(param)); return impl_node_set_param(this, SPA_PARAM_PortConfig, 0, param); } static int impl_get_interface(struct spa_handle *handle, const char *type, void **interface) { struct impl *this; spa_return_val_if_fail(handle != NULL, -EINVAL); spa_return_val_if_fail(interface != NULL, -EINVAL); this = (struct impl *) handle; if (spa_streq(type, SPA_TYPE_INTERFACE_Node)) *interface = &this->node; else return -ENOENT; return 0; } static int impl_clear(struct spa_handle *handle) { struct impl *this; spa_return_val_if_fail(handle != NULL, -EINVAL); this = (struct impl *) handle; spa_hook_remove(&this->follower_listener); spa_node_set_callbacks(this->follower, NULL, NULL); if (this->hnd_convert) { if (this->unload_handle) spa_plugin_loader_unload(this->ploader, this->hnd_convert); else { spa_handle_clear(this->hnd_convert); free(this->hnd_convert); } free(this->convertname); } clear_buffers(this); return 0; } static size_t impl_get_size(const struct spa_handle_factory *factory, const struct spa_dict *params) { size_t size = sizeof(struct impl); return size; } static int impl_init(const struct spa_handle_factory *factory, struct spa_handle *handle, const struct spa_dict *info, const struct spa_support *support, uint32_t n_support) { struct impl *this; const char *str; int ret; struct spa_hook probe_listener; spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(handle != NULL, -EINVAL); handle->get_interface = impl_get_interface; handle->clear = impl_clear; this = (struct impl *) handle; this->log = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log); spa_log_topic_init(this->log, &log_topic); /* FIXME, we should check the IO params for SPA_IO_RateMatch */ this->have_rate_match = true; this->cpu = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_CPU); this->ploader = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_PluginLoader); if (info == NULL || (str = spa_dict_lookup(info, "audio.adapt.follower")) == NULL) return -EINVAL; sscanf(str, "pointer:%p", &this->follower); if (this->follower == NULL) return -EINVAL; if (this->cpu) this->max_align = spa_cpu_get_max_align(this->cpu); spa_hook_list_init(&this->hooks); this->node.iface = SPA_INTERFACE_INIT( SPA_TYPE_INTERFACE_Node, SPA_VERSION_NODE, &impl_node, this); /* just probe the ports to get the direction */ spa_zero(probe_listener); spa_node_add_listener(this->follower, &probe_listener, &follower_probe_events, this); spa_hook_remove(&probe_listener); ret = load_converter(this, info, support, n_support); spa_log_info(this->log, "%p: loaded converter %s, hnd %p, convert %p", this, this->convertname, this->hnd_convert, this->convert); if (ret < 0) return ret; if (this->convert == NULL) { this->target = this->follower; this->mode = SPA_PARAM_PORT_CONFIG_MODE_passthrough; } else { this->target = this->convert; /* the actual mode is selected below */ this->mode = SPA_PARAM_PORT_CONFIG_MODE_none; configure_convert(this, this->mode); } this->info_all = SPA_NODE_CHANGE_MASK_FLAGS | SPA_NODE_CHANGE_MASK_PROPS | SPA_NODE_CHANGE_MASK_PARAMS; this->info = SPA_NODE_INFO_INIT(); this->info.flags = SPA_NODE_FLAG_RT | SPA_NODE_FLAG_NEED_CONFIGURE; this->params[IDX_EnumFormat] = SPA_PARAM_INFO(SPA_PARAM_EnumFormat, SPA_PARAM_INFO_READ); this->params[IDX_PropInfo] = SPA_PARAM_INFO(SPA_PARAM_PropInfo, SPA_PARAM_INFO_READ); this->params[IDX_Props] = SPA_PARAM_INFO(SPA_PARAM_Props, SPA_PARAM_INFO_READWRITE); this->params[IDX_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); this->params[IDX_EnumPortConfig] = SPA_PARAM_INFO(SPA_PARAM_EnumPortConfig, SPA_PARAM_INFO_READ); this->params[IDX_PortConfig] = SPA_PARAM_INFO(SPA_PARAM_PortConfig, SPA_PARAM_INFO_READWRITE); this->params[IDX_Latency] = SPA_PARAM_INFO(SPA_PARAM_Latency, SPA_PARAM_INFO_READWRITE); this->params[IDX_ProcessLatency] = SPA_PARAM_INFO(SPA_PARAM_ProcessLatency, SPA_PARAM_INFO_READWRITE); this->params[IDX_Tag] = SPA_PARAM_INFO(SPA_PARAM_Tag, SPA_PARAM_INFO_READWRITE); this->info.params = this->params; this->info.n_params = N_NODE_PARAMS; spa_node_add_listener(this->follower, &this->follower_listener, &follower_node_events, this); spa_node_set_callbacks(this->follower, &follower_node_callbacks, this); if (this->convert) { spa_node_add_listener(this->convert, &this->convert_listener, &convert_node_events, this); if (info && (str = spa_dict_lookup(info, "adapter.auto-port-config")) != NULL) do_auto_port_config(this, str); else { reconfigure_mode(this, SPA_PARAM_PORT_CONFIG_MODE_none, this->direction, NULL); } } else { reconfigure_mode(this, SPA_PARAM_PORT_CONFIG_MODE_passthrough, this->direction, NULL); } link_io(this); return 0; } static const struct spa_interface_info impl_interfaces[] = { { SPA_TYPE_INTERFACE_Node, }, }; static int impl_enum_interface_info(const struct spa_handle_factory *factory, const struct spa_interface_info **info, uint32_t *index) { spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(info != NULL, -EINVAL); spa_return_val_if_fail(index != NULL, -EINVAL); switch (*index) { case 0: *info = &impl_interfaces[*index]; break; default: return 0; } (*index)++; return 1; } const struct spa_handle_factory spa_audioadapter_factory = { .version = SPA_VERSION_HANDLE_FACTORY, .name = SPA_NAME_AUDIO_ADAPT, .get_size = impl_get_size, .init = impl_init, .enum_interface_info = impl_enum_interface_info, }; pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/audioconvert/audioconvert.c000066400000000000000000004001061511204443500303130ustar00rootroot00000000000000/* Spa */ /* SPDX-FileCopyrightText: Copyright © 2022 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "volume-ops.h" #include "fmt-ops.h" #include "channelmix-ops.h" #include "resample.h" #include "wavfile.h" #undef SPA_LOG_TOPIC_DEFAULT #define SPA_LOG_TOPIC_DEFAULT &log_topic SPA_LOG_TOPIC_DEFINE_STATIC(log_topic, "spa.audioconvert"); #define DEFAULT_RATE 48000 #define DEFAULT_CHANNELS 2 #define MAX_CHANNELS SPA_AUDIO_MAX_CHANNELS #define MAX_ALIGN FMT_OPS_MAX_ALIGN #define MAX_BUFFERS 32 #define MAX_DATAS MAX_CHANNELS #define MAX_PORTS (MAX_CHANNELS+1) #define MAX_STAGES 64 #define MAX_GRAPH 9 /* 8 active + 1 replacement slot */ #define DEFAULT_MUTE false #define DEFAULT_VOLUME VOLUME_NORM #define DEFAULT_MIN_VOLUME 0.0 #define DEFAULT_MAX_VOLUME 10.0 struct volumes { bool mute; uint32_t n_volumes; float volumes[MAX_CHANNELS]; }; static void init_volumes(struct volumes *vol) { uint32_t i; vol->mute = DEFAULT_MUTE; vol->n_volumes = 0; for (i = 0; i < MAX_CHANNELS; i++) vol->volumes[i] = DEFAULT_VOLUME; } struct volume_ramp_params { unsigned int volume_ramp_samples; unsigned int volume_ramp_step_samples; unsigned int volume_ramp_time; unsigned int volume_ramp_step_time; enum spa_audio_volume_ramp_scale scale; float start; float end; uint32_t rate; }; struct props { float volume; float min_volume; float max_volume; float prev_volume; uint32_t n_channels; uint32_t channel_map[MAX_CHANNELS]; struct volumes channel; struct volumes soft; struct volumes monitor; unsigned int have_soft_volume:1; unsigned int mix_disabled:1; unsigned int resample_disabled:1; unsigned int resample_quality; struct resample_config resample_config; double rate; char wav_path[512]; unsigned int lock_volumes:1; unsigned int filter_graph_disabled:1; }; static void props_reset(struct props *props) { uint32_t i; props->volume = DEFAULT_VOLUME; props->min_volume = DEFAULT_MIN_VOLUME; props->max_volume = DEFAULT_MAX_VOLUME; props->n_channels = 0; for (i = 0; i < MAX_CHANNELS; i++) props->channel_map[i] = SPA_AUDIO_CHANNEL_UNKNOWN; init_volumes(&props->channel); init_volumes(&props->soft); init_volumes(&props->monitor); props->have_soft_volume = false; props->mix_disabled = false; props->resample_disabled = false; props->resample_quality = RESAMPLE_DEFAULT_QUALITY; spa_zero(props->resample_config); props->rate = 1.0; spa_zero(props->wav_path); props->lock_volumes = false; props->filter_graph_disabled = false; } struct buffer { uint32_t id; #define BUFFER_FLAG_QUEUED (1<<0) #define BUFFER_FLAG_MAPPED (1<<1) uint32_t flags; struct spa_list link; struct spa_buffer *buf; void *datas[MAX_DATAS]; }; struct port { uint32_t direction; uint32_t id; struct spa_io_buffers *io; uint64_t info_all; struct spa_port_info info; #define IDX_EnumFormat 0 #define IDX_Meta 1 #define IDX_IO 2 #define IDX_Format 3 #define IDX_Buffers 4 #define IDX_Latency 5 #define IDX_Tag 6 #define N_PORT_PARAMS 7 struct spa_param_info params[N_PORT_PARAMS]; char position[16]; struct buffer buffers[MAX_BUFFERS]; uint32_t n_buffers; struct spa_latency_info latency[2]; unsigned int have_latency:1; struct spa_audio_info format; unsigned int valid:1; unsigned int have_format:1; unsigned int is_dsp:1; unsigned int is_monitor:1; unsigned int is_control:1; uint32_t blocks; uint32_t stride; uint32_t maxsize; const struct spa_pod_sequence *ctrl; uint32_t ctrl_offset; struct spa_list queue; }; struct dir { struct port *ports[MAX_PORTS]; uint32_t n_ports; enum spa_direction direction; enum spa_param_port_config_mode mode; struct spa_audio_info format; unsigned int have_format:1; unsigned int have_profile:1; struct spa_pod *tag; uint32_t remap[MAX_PORTS]; struct convert conv; unsigned int need_remap:1; unsigned int is_passthrough:1; unsigned int control:1; }; struct stage_context { #define CTX_DATA_SRC 0 #define CTX_DATA_DST 1 #define CTX_DATA_REMAP_DST 2 #define CTX_DATA_REMAP_SRC 3 #define CTX_DATA_TMP_0 4 #define CTX_DATA_TMP_1 5 #define CTX_DATA_MAX 6 void **datas[CTX_DATA_MAX]; uint32_t in_samples; uint32_t n_samples; uint32_t n_out; uint32_t src_idx; uint32_t dst_idx; uint32_t final_idx; uint32_t tmp; #define SRC_CONVERT_BIT (1<<0) #define RESAMPLE_BIT (1<<1) #define FILTER_BIT (1<<2) #define MIX_BIT (1<<3) #define DST_CONVERT_BIT (1<<4) uint32_t bits; struct port *ctrlport; bool empty; }; struct stage { struct impl *impl; uint32_t in_idx; uint32_t out_idx; void *data; void (*run) (struct stage *stage, struct stage_context *c); }; struct filter_graph { struct impl *impl; struct spa_list link; int order; struct spa_handle *handle; struct spa_filter_graph *graph; struct spa_hook listener; uint32_t n_inputs; uint32_t inputs_position[MAX_CHANNELS]; uint32_t n_outputs; uint32_t outputs_position[MAX_CHANNELS]; uint32_t latency; bool removing; bool setup; }; struct impl { struct spa_handle handle; struct spa_node node; struct spa_log *log; struct spa_cpu *cpu; struct spa_loop *data_loop; struct spa_plugin_loader *loader; uint32_t n_graph; struct filter_graph *filter_graph[MAX_GRAPH]; struct spa_list free_graphs; struct spa_list active_graphs; struct filter_graph graphs[MAX_GRAPH]; struct spa_process_latency_info latency; int in_filter_props; int filter_props_count; struct stage stages[MAX_STAGES]; uint32_t n_stages; uint32_t cpu_flags; uint32_t max_align; uint32_t quantum_limit; enum spa_direction direction; struct spa_ratelimit rate_limit; struct props props; struct spa_io_clock *io_clock; struct spa_io_position *io_position; struct spa_io_rate_match *io_rate_match; uint64_t info_all; struct spa_node_info info; #define IDX_EnumPortConfig 0 #define IDX_PortConfig 1 #define IDX_PropInfo 2 #define IDX_Props 3 #define N_NODE_PARAMS 4 struct spa_param_info params[N_NODE_PARAMS]; struct spa_hook_list hooks; unsigned int monitor:1; unsigned int monitor_channel_volumes:1; struct dir dir[2]; struct channelmix mix; struct resample resample; struct volume volume; double rate_scale; struct spa_pod_sequence *vol_ramp_sequence; void *vol_ramp_sequence_data; uint32_t vol_ramp_offset; uint32_t in_offset; uint32_t out_offset; unsigned int started:1; unsigned int setup:1; unsigned int resample_peaks:1; unsigned int ramp_volume:1; unsigned int drained:1; unsigned int rate_adjust:1; unsigned int port_ignore_latency:1; unsigned int monitor_passthrough:1; unsigned int resample_passthrough:1; bool recalc; char group_name[128]; uint32_t maxsize; uint32_t maxports; uint32_t scratch_size; uint32_t scratch_ports; float *empty; float *scratch; float *tmp[2][MAX_PORTS]; float *tmp_datas[2][MAX_PORTS]; struct wav_file *wav_file; }; #define CHECK_PORT(this,d,p) ((p) < this->dir[d].n_ports) #define GET_PORT(this,d,p) (this->dir[d].ports[p]) #define GET_IN_PORT(this,p) GET_PORT(this,SPA_DIRECTION_INPUT,p) #define GET_OUT_PORT(this,p) GET_PORT(this,SPA_DIRECTION_OUTPUT,p) #define PORT_IS_DSP(this,d,p) (GET_PORT(this,d,p)->is_dsp) #define PORT_IS_CONTROL(this,d,p) (GET_PORT(this,d,p)->is_control) static void set_volume(struct impl *this); static void emit_node_info(struct impl *this, bool full) { uint64_t old = full ? this->info.change_mask : 0; if (full) this->info.change_mask = this->info_all; if (this->info.change_mask) { if (this->info.change_mask & SPA_NODE_CHANGE_MASK_PARAMS) { SPA_FOR_EACH_ELEMENT_VAR(this->params, p) { if (p->user > 0) { p->flags ^= SPA_PARAM_INFO_SERIAL; p->user = 0; } } } spa_node_emit_info(&this->hooks, &this->info); this->info.change_mask = old; } } static void emit_port_info(struct impl *this, struct port *port, bool full) { uint64_t old = full ? port->info.change_mask : 0; if (full) port->info.change_mask = port->info_all; if (port->info.change_mask) { struct spa_dict_item items[5]; uint32_t n_items = 0; if (PORT_IS_DSP(this, port->direction, port->id)) { items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_FORMAT_DSP, "32 bit float mono audio"); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_AUDIO_CHANNEL, port->position); if (port->is_monitor) items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_PORT_MONITOR, "true"); if (this->port_ignore_latency) items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_PORT_IGNORE_LATENCY, "true"); } else if (PORT_IS_CONTROL(this, port->direction, port->id)) { items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_PORT_NAME, "control"); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_FORMAT_DSP, "8 bit raw midi"); } if (this->group_name[0] != '\0') items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_PORT_GROUP, this->group_name); port->info.props = &SPA_DICT_INIT(items, n_items); if (port->info.change_mask & SPA_PORT_CHANGE_MASK_PARAMS) { SPA_FOR_EACH_ELEMENT_VAR(port->params, p) { if (p->user > 0) { p->flags ^= SPA_PARAM_INFO_SERIAL; p->user = 0; } } } spa_node_emit_port_info(&this->hooks, port->direction, port->id, &port->info); port->info.change_mask = old; } } static void emit_info(struct impl *this, bool full) { struct port *p; uint32_t i; emit_node_info(this, full); for (i = 0; i < this->dir[SPA_DIRECTION_INPUT].n_ports; i++) { if ((p = GET_IN_PORT(this, i)) && p->valid) emit_port_info(this, p, full); } for (i = 0; i < this->dir[SPA_DIRECTION_OUTPUT].n_ports; i++) { if ((p = GET_OUT_PORT(this, i)) && p->valid) emit_port_info(this, p, full); } } static int init_port(struct impl *this, enum spa_direction direction, uint32_t port_id, uint32_t position, bool is_dsp, bool is_monitor, bool is_control) { struct port *port = GET_PORT(this, direction, port_id); spa_assert(port_id < MAX_PORTS); if (port == NULL) { port = calloc(1, sizeof(struct port)); if (port == NULL) return -errno; this->dir[direction].ports[port_id] = port; } port->direction = direction; port->id = port_id; port->latency[SPA_DIRECTION_INPUT] = SPA_LATENCY_INFO(SPA_DIRECTION_INPUT); port->latency[SPA_DIRECTION_OUTPUT] = SPA_LATENCY_INFO(SPA_DIRECTION_OUTPUT); spa_type_audio_channel_make_short_name(position, port->position, sizeof(port->position), "UNK"); port->info = SPA_PORT_INFO_INIT(); port->info.change_mask = port->info_all = SPA_PORT_CHANGE_MASK_FLAGS | SPA_PORT_CHANGE_MASK_PROPS | SPA_PORT_CHANGE_MASK_PARAMS; port->info.flags = SPA_PORT_FLAG_NO_REF | SPA_PORT_FLAG_DYNAMIC_DATA; port->params[IDX_EnumFormat] = SPA_PARAM_INFO(SPA_PARAM_EnumFormat, SPA_PARAM_INFO_READ); port->params[IDX_Meta] = SPA_PARAM_INFO(SPA_PARAM_Meta, SPA_PARAM_INFO_READ); port->params[IDX_IO] = SPA_PARAM_INFO(SPA_PARAM_IO, SPA_PARAM_INFO_READ); port->params[IDX_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); port->params[IDX_Buffers] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); port->params[IDX_Latency] = SPA_PARAM_INFO(SPA_PARAM_Latency, SPA_PARAM_INFO_READWRITE); port->params[IDX_Tag] = SPA_PARAM_INFO(SPA_PARAM_Tag, SPA_PARAM_INFO_READWRITE); port->info.params = port->params; port->info.n_params = N_PORT_PARAMS; port->n_buffers = 0; port->have_format = false; port->is_monitor = is_monitor; port->is_dsp = is_dsp; if (port->is_dsp) { port->format.media_type = SPA_MEDIA_TYPE_audio; port->format.media_subtype = SPA_MEDIA_SUBTYPE_dsp; port->format.info.dsp.format = SPA_AUDIO_FORMAT_DSP_F32; port->blocks = 1; port->stride = 4; } port->is_control = is_control; if (port->is_control) { port->format.media_type = SPA_MEDIA_TYPE_application; port->format.media_subtype = SPA_MEDIA_SUBTYPE_control; port->blocks = 1; port->stride = 1; } port->valid = true; spa_list_init(&port->queue); spa_log_debug(this->log, "%p: add port %d:%d position:%s %d %d %d", this, direction, port_id, port->position, is_dsp, is_monitor, is_control); return 0; } static int deinit_port(struct impl *this, enum spa_direction direction, uint32_t port_id) { struct port *port = GET_PORT(this, direction, port_id); if (port == NULL || !port->valid) return -ENOENT; port->valid = false; spa_node_emit_port_info(&this->hooks, direction, port_id, NULL); return 0; } static int node_param_enum_port_config(struct impl *this, uint32_t id, uint32_t index, struct spa_pod **param, struct spa_pod_builder *b) { switch (index) { case 0 ... 1: { struct dir *dir = &this->dir[index]; *param = spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_ParamPortConfig, id, SPA_PARAM_PORT_CONFIG_direction, SPA_POD_Id(dir->direction), SPA_PARAM_PORT_CONFIG_mode, SPA_POD_CHOICE_ENUM_Id(4, SPA_PARAM_PORT_CONFIG_MODE_none, SPA_PARAM_PORT_CONFIG_MODE_none, SPA_PARAM_PORT_CONFIG_MODE_dsp, SPA_PARAM_PORT_CONFIG_MODE_convert), SPA_PARAM_PORT_CONFIG_monitor, SPA_POD_CHOICE_Bool(false), SPA_PARAM_PORT_CONFIG_control, SPA_POD_CHOICE_Bool(false)); break; } default: return 0; } return 1; } static int node_param_port_config(struct impl *this, uint32_t id, uint32_t index, struct spa_pod **param, struct spa_pod_builder *b) { switch (index) { case 0 ... 1: { struct dir *dir = &this->dir[index]; struct spa_pod_frame f[1]; spa_pod_builder_push_object(b, &f[0], SPA_TYPE_OBJECT_ParamPortConfig, id); spa_pod_builder_add(b, SPA_PARAM_PORT_CONFIG_direction, SPA_POD_Id(dir->direction), SPA_PARAM_PORT_CONFIG_mode, SPA_POD_Id(dir->mode), SPA_PARAM_PORT_CONFIG_monitor, SPA_POD_Bool(this->monitor), SPA_PARAM_PORT_CONFIG_control, SPA_POD_Bool(dir->control), 0); if (dir->have_format) { spa_pod_builder_prop(b, SPA_PARAM_PORT_CONFIG_format, 0); spa_format_audio_raw_build(b, id, &dir->format.info.raw); } *param = spa_pod_builder_pop(b, &f[0]); break; } default: return 0; } return 1; } static int node_param_prop_info(struct impl *this, uint32_t id, uint32_t index, struct spa_pod **param, struct spa_pod_builder *b) { struct props *p = &this->props; struct spa_pod_frame f[2]; switch (index) { case 0: *param = spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_PropInfo, id, SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_volume), SPA_PROP_INFO_description, SPA_POD_String("Volume"), SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Float(p->volume, DEFAULT_MIN_VOLUME, DEFAULT_MAX_VOLUME)); break; case 1: *param = spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_PropInfo, id, SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_mute), SPA_PROP_INFO_description, SPA_POD_String("Mute"), SPA_PROP_INFO_type, SPA_POD_CHOICE_Bool(p->channel.mute)); break; case 2: *param = spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_PropInfo, id, SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_channelVolumes), SPA_PROP_INFO_description, SPA_POD_String("Channel Volumes"), SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Float(p->volume, DEFAULT_MIN_VOLUME, DEFAULT_MAX_VOLUME), SPA_PROP_INFO_container, SPA_POD_Id(SPA_TYPE_Array)); break; case 3: *param = spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_PropInfo, id, SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_channelMap), SPA_PROP_INFO_description, SPA_POD_String("Channel Map"), SPA_PROP_INFO_type, SPA_POD_Id(SPA_AUDIO_CHANNEL_UNKNOWN), SPA_PROP_INFO_container, SPA_POD_Id(SPA_TYPE_Array)); break; case 4: *param = spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_PropInfo, id, SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_monitorMute), SPA_PROP_INFO_description, SPA_POD_String("Monitor Mute"), SPA_PROP_INFO_type, SPA_POD_CHOICE_Bool(p->monitor.mute)); break; case 5: *param = spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_PropInfo, id, SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_monitorVolumes), SPA_PROP_INFO_description, SPA_POD_String("Monitor Volumes"), SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Float(p->volume, DEFAULT_MIN_VOLUME, DEFAULT_MAX_VOLUME), SPA_PROP_INFO_container, SPA_POD_Id(SPA_TYPE_Array)); break; case 6: *param = spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_PropInfo, id, SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_softMute), SPA_PROP_INFO_description, SPA_POD_String("Soft Mute"), SPA_PROP_INFO_type, SPA_POD_CHOICE_Bool(p->soft.mute)); break; case 7: *param = spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_PropInfo, id, SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_softVolumes), SPA_PROP_INFO_description, SPA_POD_String("Soft Volumes"), SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Float(p->volume, DEFAULT_MIN_VOLUME, DEFAULT_MAX_VOLUME), SPA_PROP_INFO_container, SPA_POD_Id(SPA_TYPE_Array)); break; case 8: *param = spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_PropInfo, id, SPA_PROP_INFO_name, SPA_POD_String("monitor.channel-volumes"), SPA_PROP_INFO_description, SPA_POD_String("Monitor channel volume"), SPA_PROP_INFO_type, SPA_POD_CHOICE_Bool( this->monitor_channel_volumes), SPA_PROP_INFO_params, SPA_POD_Bool(true)); break; case 9: *param = spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_PropInfo, id, SPA_PROP_INFO_name, SPA_POD_String("channelmix.disable"), SPA_PROP_INFO_description, SPA_POD_String("Disable Channel mixing"), SPA_PROP_INFO_type, SPA_POD_CHOICE_Bool(p->mix_disabled), SPA_PROP_INFO_params, SPA_POD_Bool(true)); break; case 10: *param = spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_PropInfo, id, SPA_PROP_INFO_name, SPA_POD_String("channelmix.min-volume"), SPA_PROP_INFO_description, SPA_POD_String("Minimum volume level"), SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Float(p->min_volume, DEFAULT_MIN_VOLUME, DEFAULT_MAX_VOLUME), SPA_PROP_INFO_params, SPA_POD_Bool(true)); break; case 11: *param = spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_PropInfo, id, SPA_PROP_INFO_name, SPA_POD_String("channelmix.max-volume"), SPA_PROP_INFO_description, SPA_POD_String("Maximum volume level"), SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Float(p->max_volume, DEFAULT_MIN_VOLUME, DEFAULT_MAX_VOLUME), SPA_PROP_INFO_params, SPA_POD_Bool(true)); break; case 12: *param = spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_PropInfo, id, SPA_PROP_INFO_name, SPA_POD_String("channelmix.normalize"), SPA_PROP_INFO_description, SPA_POD_String("Normalize Volumes"), SPA_PROP_INFO_type, SPA_POD_CHOICE_Bool( SPA_FLAG_IS_SET(this->mix.options, CHANNELMIX_OPTION_NORMALIZE)), SPA_PROP_INFO_params, SPA_POD_Bool(true)); break; case 13: *param = spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_PropInfo, id, SPA_PROP_INFO_name, SPA_POD_String("channelmix.mix-lfe"), SPA_PROP_INFO_description, SPA_POD_String("Mix LFE into channels"), SPA_PROP_INFO_type, SPA_POD_CHOICE_Bool( SPA_FLAG_IS_SET(this->mix.options, CHANNELMIX_OPTION_MIX_LFE)), SPA_PROP_INFO_params, SPA_POD_Bool(true)); break; case 14: *param = spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_PropInfo, id, SPA_PROP_INFO_name, SPA_POD_String("channelmix.upmix"), SPA_PROP_INFO_description, SPA_POD_String("Enable upmixing"), SPA_PROP_INFO_type, SPA_POD_CHOICE_Bool( SPA_FLAG_IS_SET(this->mix.options, CHANNELMIX_OPTION_UPMIX)), SPA_PROP_INFO_params, SPA_POD_Bool(true)); break; case 15: *param = spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_PropInfo, id, SPA_PROP_INFO_name, SPA_POD_String("channelmix.lfe-cutoff"), SPA_PROP_INFO_description, SPA_POD_String("LFE cutoff frequency"), SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Float( this->mix.lfe_cutoff, 0.0, 1000.0), SPA_PROP_INFO_params, SPA_POD_Bool(true)); break; case 16: *param = spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_PropInfo, id, SPA_PROP_INFO_name, SPA_POD_String("channelmix.fc-cutoff"), SPA_PROP_INFO_description, SPA_POD_String("FC cutoff frequency (Hz)"), SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Float( this->mix.fc_cutoff, 0.0, 48000.0), SPA_PROP_INFO_params, SPA_POD_Bool(true)); break; case 17: *param = spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_PropInfo, id, SPA_PROP_INFO_name, SPA_POD_String("channelmix.rear-delay"), SPA_PROP_INFO_description, SPA_POD_String("Rear channels delay (ms)"), SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Float( this->mix.rear_delay, 0.0, 1000.0), SPA_PROP_INFO_params, SPA_POD_Bool(true)); break; case 18: *param = spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_PropInfo, id, SPA_PROP_INFO_name, SPA_POD_String("channelmix.stereo-widen"), SPA_PROP_INFO_description, SPA_POD_String("Stereo widen"), SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Float( this->mix.widen, 0.0, 1.0), SPA_PROP_INFO_params, SPA_POD_Bool(true)); break; case 19: *param = spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_PropInfo, id, SPA_PROP_INFO_name, SPA_POD_String("channelmix.hilbert-taps"), SPA_PROP_INFO_description, SPA_POD_String("Taps for phase shift of rear"), SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Int( this->mix.hilbert_taps, 0, MAX_TAPS), SPA_PROP_INFO_params, SPA_POD_Bool(true)); break; case 20: spa_pod_builder_push_object(b, &f[0], SPA_TYPE_OBJECT_PropInfo, id); spa_pod_builder_add(b, SPA_PROP_INFO_name, SPA_POD_String("channelmix.upmix-method"), SPA_PROP_INFO_description, SPA_POD_String("Upmix method to use"), SPA_PROP_INFO_type, SPA_POD_String( channelmix_upmix_info[this->mix.upmix].label), SPA_PROP_INFO_params, SPA_POD_Bool(true), 0); spa_pod_builder_prop(b, SPA_PROP_INFO_labels, 0); spa_pod_builder_push_struct(b, &f[1]); SPA_FOR_EACH_ELEMENT_VAR(channelmix_upmix_info, i) { spa_pod_builder_string(b, i->label); spa_pod_builder_string(b, i->description); } spa_pod_builder_pop(b, &f[1]); *param = spa_pod_builder_pop(b, &f[0]); break; case 21: *param = spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_PropInfo, id, SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_rate), SPA_PROP_INFO_description, SPA_POD_String("Rate scaler"), SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Double(p->rate, 0.0, 10.0)); break; case 22: *param = spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_PropInfo, id, SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_quality), SPA_PROP_INFO_name, SPA_POD_String("resample.quality"), SPA_PROP_INFO_description, SPA_POD_String("Resample Quality"), SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Int(p->resample_quality, 0, 14), SPA_PROP_INFO_params, SPA_POD_Bool(true)); break; case 23: *param = spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_PropInfo, id, SPA_PROP_INFO_name, SPA_POD_String("resample.disable"), SPA_PROP_INFO_description, SPA_POD_String("Disable Resampling"), SPA_PROP_INFO_type, SPA_POD_CHOICE_Bool(p->resample_disabled), SPA_PROP_INFO_params, SPA_POD_Bool(true)); break; case 24: *param = spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_PropInfo, id, SPA_PROP_INFO_name, SPA_POD_String("dither.noise"), SPA_PROP_INFO_description, SPA_POD_String("Add noise bits"), SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Int(this->dir[1].conv.noise_bits, 0, 16), SPA_PROP_INFO_params, SPA_POD_Bool(true)); break; case 25: spa_pod_builder_push_object(b, &f[0], SPA_TYPE_OBJECT_PropInfo, id); spa_pod_builder_add(b, SPA_PROP_INFO_name, SPA_POD_String("dither.method"), SPA_PROP_INFO_description, SPA_POD_String("The dithering method"), SPA_PROP_INFO_type, SPA_POD_String( dither_method_info[this->dir[1].conv.method].label), SPA_PROP_INFO_params, SPA_POD_Bool(true), 0); spa_pod_builder_prop(b, SPA_PROP_INFO_labels, 0); spa_pod_builder_push_struct(b, &f[1]); SPA_FOR_EACH_ELEMENT_VAR(dither_method_info, i) { spa_pod_builder_string(b, i->label); spa_pod_builder_string(b, i->description); } spa_pod_builder_pop(b, &f[1]); *param = spa_pod_builder_pop(b, &f[0]); break; case 26: *param = spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_PropInfo, id, SPA_PROP_INFO_name, SPA_POD_String("debug.wav-path"), SPA_PROP_INFO_description, SPA_POD_String("Path to WAV file"), SPA_PROP_INFO_type, SPA_POD_String(p->wav_path), SPA_PROP_INFO_params, SPA_POD_Bool(true)); break; case 27: *param = spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_PropInfo, id, SPA_PROP_INFO_name, SPA_POD_String("channelmix.lock-volumes"), SPA_PROP_INFO_description, SPA_POD_String("Disable volume updates"), SPA_PROP_INFO_type, SPA_POD_CHOICE_Bool(p->lock_volumes), SPA_PROP_INFO_params, SPA_POD_Bool(true)); break; case 28: *param = spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_PropInfo, id, SPA_PROP_INFO_name, SPA_POD_String("audioconvert.filter-graph.disable"), SPA_PROP_INFO_description, SPA_POD_String("Disable Filter graph updates"), SPA_PROP_INFO_type, SPA_POD_CHOICE_Bool(p->filter_graph_disabled), SPA_PROP_INFO_params, SPA_POD_Bool(true)); break; case 29: *param = spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_PropInfo, id, SPA_PROP_INFO_name, SPA_POD_String("audioconvert.filter-graph.N"), SPA_PROP_INFO_description, SPA_POD_String("A filter graph to load"), SPA_PROP_INFO_type, SPA_POD_String(""), SPA_PROP_INFO_params, SPA_POD_Bool(true)); break; default: if (this->filter_graph[0] && this->filter_graph[0]->graph) { return spa_filter_graph_enum_prop_info(this->filter_graph[0]->graph, index - 30, b, param); } return 0; } return 1; } static int node_param_props(struct impl *this, uint32_t id, uint32_t index, struct spa_pod **param, struct spa_pod_builder *b) { struct props *p = &this->props; struct spa_pod_frame f[2]; switch (index) { case 0: spa_pod_builder_push_object(b, &f[0], SPA_TYPE_OBJECT_Props, id); spa_pod_builder_add(b, SPA_PROP_volume, SPA_POD_Float(p->volume), SPA_PROP_mute, SPA_POD_Bool(p->channel.mute), SPA_PROP_channelVolumes, SPA_POD_Array(sizeof(float), SPA_TYPE_Float, p->channel.n_volumes, p->channel.volumes), SPA_PROP_channelMap, SPA_POD_Array(sizeof(uint32_t), SPA_TYPE_Id, p->n_channels, p->channel_map), SPA_PROP_softMute, SPA_POD_Bool(p->soft.mute), SPA_PROP_softVolumes, SPA_POD_Array(sizeof(float), SPA_TYPE_Float, p->soft.n_volumes, p->soft.volumes), SPA_PROP_monitorMute, SPA_POD_Bool(p->monitor.mute), SPA_PROP_monitorVolumes, SPA_POD_Array(sizeof(float), SPA_TYPE_Float, p->monitor.n_volumes, p->monitor.volumes), 0); spa_pod_builder_prop(b, SPA_PROP_params, 0); spa_pod_builder_push_struct(b, &f[1]); spa_pod_builder_string(b, "monitor.channel-volumes"); spa_pod_builder_bool(b, this->monitor_channel_volumes); spa_pod_builder_string(b, "channelmix.disable"); spa_pod_builder_bool(b, this->props.mix_disabled); spa_pod_builder_string(b, "channelmix.min-volume"); spa_pod_builder_float(b, this->props.min_volume); spa_pod_builder_string(b, "channelmix.max-volume"); spa_pod_builder_float(b, this->props.max_volume); spa_pod_builder_string(b, "channelmix.normalize"); spa_pod_builder_bool(b, SPA_FLAG_IS_SET(this->mix.options, CHANNELMIX_OPTION_NORMALIZE)); spa_pod_builder_string(b, "channelmix.mix-lfe"); spa_pod_builder_bool(b, SPA_FLAG_IS_SET(this->mix.options, CHANNELMIX_OPTION_MIX_LFE)); spa_pod_builder_string(b, "channelmix.upmix"); spa_pod_builder_bool(b, SPA_FLAG_IS_SET(this->mix.options, CHANNELMIX_OPTION_UPMIX)); spa_pod_builder_string(b, "channelmix.lfe-cutoff"); spa_pod_builder_float(b, this->mix.lfe_cutoff); spa_pod_builder_string(b, "channelmix.fc-cutoff"); spa_pod_builder_float(b, this->mix.fc_cutoff); spa_pod_builder_string(b, "channelmix.rear-delay"); spa_pod_builder_float(b, this->mix.rear_delay); spa_pod_builder_string(b, "channelmix.stereo-widen"); spa_pod_builder_float(b, this->mix.widen); spa_pod_builder_string(b, "channelmix.hilbert-taps"); spa_pod_builder_int(b, this->mix.hilbert_taps); spa_pod_builder_string(b, "channelmix.upmix-method"); spa_pod_builder_string(b, channelmix_upmix_info[this->mix.upmix].label); spa_pod_builder_string(b, "resample.quality"); spa_pod_builder_int(b, p->resample_quality); spa_pod_builder_string(b, "resample.disable"); spa_pod_builder_bool(b, p->resample_disabled); spa_pod_builder_string(b, "dither.noise"); spa_pod_builder_int(b, this->dir[1].conv.noise_bits); spa_pod_builder_string(b, "dither.method"); spa_pod_builder_string(b, dither_method_info[this->dir[1].conv.method].label); spa_pod_builder_string(b, "debug.wav-path"); spa_pod_builder_string(b, p->wav_path); spa_pod_builder_string(b, "channelmix.lock-volumes"); spa_pod_builder_bool(b, p->lock_volumes); spa_pod_builder_string(b, "audioconvert.filter-graph.disable"); spa_pod_builder_bool(b, p->filter_graph_disabled); spa_pod_builder_string(b, "audioconvert.filter-graph"); spa_pod_builder_string(b, ""); spa_pod_builder_pop(b, &f[1]); *param = spa_pod_builder_pop(b, &f[0]); break; default: { struct spa_filter_graph *graph; int res; if (index-1 >= this->n_graph) return 0; graph = this->filter_graph[index-1]->graph; if (graph == NULL) return 1; res = spa_filter_graph_get_props(graph, b, param); if (res == 0) { *param = NULL; return 1; } return res; } } return 1; } static int impl_node_enum_params(void *object, int seq, uint32_t id, uint32_t start, uint32_t num, const struct spa_pod *filter) { struct impl *this = object; struct spa_pod *param; struct spa_pod_builder b = { 0 }; uint8_t buffer[4096]; struct spa_result_node_params result; uint32_t count = 0; int res = 0; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(num != 0, -EINVAL); result.id = id; result.next = start; next: result.index = result.next++; spa_pod_builder_init(&b, buffer, sizeof(buffer)); param = NULL; switch (id) { case SPA_PARAM_EnumPortConfig: res = node_param_enum_port_config(this, id, result.index, ¶m, &b); break; case SPA_PARAM_PortConfig: res = node_param_port_config(this, id, result.index, ¶m, &b); break; case SPA_PARAM_PropInfo: res = node_param_prop_info(this, id, result.index, ¶m, &b); break; case SPA_PARAM_Props: res = node_param_props(this, id, result.index, ¶m, &b); break; default: return 0; } if (res <= 0) return res; if (param == NULL || spa_pod_filter(&b, &result.param, param, filter) < 0) goto next; spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); if (++count != num) goto next; return 0; } static int impl_node_set_io(void *object, uint32_t id, void *data, size_t size) { struct impl *this = object; spa_return_val_if_fail(this != NULL, -EINVAL); spa_log_debug(this->log, "%p: io %d %p/%zd", this, id, data, size); switch (id) { case SPA_IO_Clock: this->io_clock = data; break; case SPA_IO_Position: { struct port *p; uint32_t i; this->io_position = data; if (this->io_position && this->io_clock && this->io_position->clock.target_rate.denom != this->io_clock->target_rate.denom && !this->props.resample_disabled) { spa_log_debug(this->log, "driver %d changed rate:%u -> %u", this->io_position->clock.id, this->io_clock->target_rate.denom, this->io_position->clock.target_rate.denom); this->io_clock->target_rate = this->io_position->clock.target_rate; for (i = 0; i < this->dir[SPA_DIRECTION_INPUT].n_ports; i++) { if ((p = GET_IN_PORT(this, i)) && p->valid && !p->is_dsp && !p->is_control) { p->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; p->params[IDX_EnumFormat].user++; } } for (i = 0; i < this->dir[SPA_DIRECTION_OUTPUT].n_ports; i++) { if ((p = GET_OUT_PORT(this, i)) && p->valid && !p->is_dsp && !p->is_control) { p->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; p->params[IDX_EnumFormat].user++; } } } break; } default: return -ENOENT; } emit_info(this, false); return 0; } static void port_update_latency(struct port *port, const struct spa_latency_info *info, bool valid) { if (spa_latency_info_compare(info, &port->latency[info->direction]) != 0) { port->latency[info->direction] = *info; port->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; port->params[IDX_Latency].user++; } port->have_latency = valid; } static void recalc_latencies(struct impl *this, enum spa_direction direction) { struct spa_latency_info info; enum spa_direction other = SPA_DIRECTION_REVERSE(direction); struct port *port; uint32_t i; bool have_latency = false; spa_latency_info_combine_start(&info, other); for (i = 0; i < this->dir[direction].n_ports; i++) { port = GET_PORT(this, direction, i); if ((port->is_monitor) || !port->have_latency) continue; spa_log_debug(this->log, "%p: combine %d", this, i); spa_latency_info_combine(&info, &port->latency[other]); have_latency = true; } spa_latency_info_combine_finish(&info); spa_process_latency_info_add(&this->latency, &info); spa_log_debug(this->log, "%p: combined %s latency %f-%f %d-%d %"PRIu64"-%"PRIu64, this, info.direction == SPA_DIRECTION_INPUT ? "input" : "output", info.min_quantum, info.max_quantum, info.min_rate, info.max_rate, info.min_ns, info.max_ns); for (i = 0; i < this->dir[other].n_ports; i++) { port = GET_PORT(this, other, i); if (port->is_monitor) continue; port_update_latency(port, &info, have_latency); } } static void recalc_graph_latency(struct impl *impl) { struct filter_graph *g; int32_t latency = 0; spa_list_for_each(g, &impl->active_graphs, link) latency += g->latency; if (latency != impl->latency.rate) { impl->latency.rate = latency; recalc_latencies(impl, SPA_DIRECTION_INPUT); recalc_latencies(impl, SPA_DIRECTION_OUTPUT); } } static void update_graph_latency(struct filter_graph *g, uint32_t latency) { if (g->latency != latency) { g->latency = latency; recalc_graph_latency(g->impl); } } static void graph_info(void *object, const struct spa_filter_graph_info *info) { struct filter_graph *g = object; struct spa_dict *props = info->props; uint32_t i; if (g->removing) return; g->n_inputs = info->n_inputs; g->n_outputs = info->n_outputs; for (i = 0; props && i < props->n_items; i++) { const char *k = props->items[i].key; const char *s = props->items[i].value; if (spa_streq(k, "n_inputs")) spa_atou32(s, &g->n_inputs, 0); else if (spa_streq(k, "n_outputs")) spa_atou32(s, &g->n_outputs, 0); else if (spa_streq(k, "inputs.audio.position")) spa_audio_parse_position_n(s, strlen(s), g->inputs_position, SPA_N_ELEMENTS(g->inputs_position), &g->n_inputs); else if (spa_streq(k, "outputs.audio.position")) spa_audio_parse_position_n(s, strlen(s), g->outputs_position, SPA_N_ELEMENTS(g->outputs_position), &g->n_outputs); else if (spa_streq(k, "latency")) { double latency; if (spa_atod(s, &latency)) update_graph_latency(g, (uint32_t)latency); } } emit_info(g->impl, false); } static int apply_props(struct impl *impl, const struct spa_pod *props); static void graph_apply_props(void *object, enum spa_direction direction, const struct spa_pod *props) { struct filter_graph *g = object; struct impl *impl = g->impl; if (g->removing) return; apply_props(impl, props); emit_info(impl, false); } static void graph_props_changed(void *object, enum spa_direction direction) { struct filter_graph *g = object; struct impl *impl = g->impl; if (g->removing) return; impl->info.change_mask |= SPA_NODE_CHANGE_MASK_PARAMS; impl->params[IDX_Props].user++; emit_info(impl, false); } struct spa_filter_graph_events graph_events = { SPA_VERSION_FILTER_GRAPH_EVENTS, .info = graph_info, .apply_props = graph_apply_props, .props_changed = graph_props_changed, }; static int setup_filter_graph(struct impl *this, struct filter_graph *g, uint32_t channels, uint32_t *position) { int res; char rate_str[64], in_ports[64]; struct dir *dir; if (g->graph == NULL || g->setup) return 0; dir = &this->dir[SPA_DIRECTION_REVERSE(this->direction)]; snprintf(rate_str, sizeof(rate_str), "%d", dir->format.info.raw.rate); if (channels) { snprintf(in_ports, sizeof(in_ports), "%d", channels); g->n_inputs = channels; if (position) { memcpy(g->inputs_position, position, sizeof(uint32_t) * channels); memcpy(g->outputs_position, position, sizeof(uint32_t) * channels); } } spa_filter_graph_deactivate(g->graph); res = spa_filter_graph_activate(g->graph, &SPA_DICT_ITEMS( SPA_DICT_ITEM(SPA_KEY_AUDIO_RATE, rate_str), SPA_DICT_ITEM("filter-graph.n_inputs", channels ? in_ports : NULL))); g->setup = res >= 0; return res; } static int setup_channelmix(struct impl *this, uint32_t channels, uint32_t *position); static void free_tmp(struct impl *this) { uint32_t i; spa_log_debug(this->log, "free tmp %d", this->scratch_size); free(this->empty); this->empty = NULL; this->scratch_size = 0; this->scratch_ports = 0; free(this->scratch); this->scratch = NULL; for (i = 0; i < MAX_PORTS; i++) { free(this->tmp[0][i]); this->tmp[0][i] = NULL; free(this->tmp[1][i]); this->tmp[1][i] = NULL; this->tmp_datas[0][i] = NULL; this->tmp_datas[1][i] = NULL; } } static int ensure_tmp(struct impl *this) { uint32_t maxsize = this->maxsize, maxports = this->maxports; uint32_t i; float *empty, *scratch, *tmp[2]; if (maxsize > this->scratch_size) { spa_log_info(this->log, "resize tmp %d -> %d", this->scratch_size, maxsize); if ((empty = realloc(this->empty, maxsize + MAX_ALIGN)) != NULL) this->empty = empty; if ((scratch = realloc(this->scratch, maxsize + MAX_ALIGN)) != NULL) this->scratch = scratch; if (empty == NULL || scratch == NULL) { free_tmp(this); return -ENOMEM; } memset(this->empty, 0, maxsize + MAX_ALIGN); for (i = 0; i < this->scratch_ports; i++) { if ((tmp[0] = realloc(this->tmp[0][i], maxsize + MAX_ALIGN)) != NULL) this->tmp[0][i] = tmp[0]; if ((tmp[1] = realloc(this->tmp[1][i], maxsize + MAX_ALIGN)) != NULL) this->tmp[1][i] = tmp[1]; if (tmp[0] == NULL || tmp[1] == NULL) { free_tmp(this); return -ENOMEM; } this->tmp_datas[0][i] = SPA_PTR_ALIGN(this->tmp[0][i], MAX_ALIGN, void); this->tmp_datas[1][i] = SPA_PTR_ALIGN(this->tmp[1][i], MAX_ALIGN, void); } this->scratch_size = maxsize; } if (maxports > this->scratch_ports) { spa_log_info(this->log, "resize ports %d -> %d", this->scratch_ports, maxports); for (i = this->scratch_ports; i < maxports; i++) { if ((tmp[0] = malloc(maxsize + MAX_ALIGN)) != NULL) this->tmp[0][i] = tmp[0]; if ((tmp[1] = malloc(maxsize + MAX_ALIGN)) != NULL) this->tmp[1][i] = tmp[1]; if (tmp[0] == NULL || tmp[1] == NULL) { free_tmp(this); return -ENOMEM; } this->tmp_datas[0][i] = SPA_PTR_ALIGN(this->tmp[0][i], MAX_ALIGN, void); this->tmp_datas[0][i] = SPA_PTR_ALIGN(this->tmp[0][i], MAX_ALIGN, void); } this->scratch_ports = maxports; } return 0; } static int setup_filter_graphs(struct impl *impl, bool force) { int res; uint32_t channels, *position; struct dir *in, *out; struct filter_graph *g, *t; in = &impl->dir[SPA_DIRECTION_INPUT]; out = &impl->dir[SPA_DIRECTION_OUTPUT]; channels = in->format.info.raw.channels; position = in->format.info.raw.position; impl->maxports = SPA_MAX(in->format.info.raw.channels, out->format.info.raw.channels); spa_list_for_each_safe(g, t, &impl->active_graphs, link) { if (g->removing) continue; if (force) g->setup = false; if ((res = setup_filter_graph(impl, g, channels, position)) < 0) { g->removing = true; spa_log_warn(impl->log, "failed to activate graph %d: %s", g->order, spa_strerror(res)); } else { channels = g->n_outputs; position = g->outputs_position; impl->maxports = SPA_MAX(impl->maxports, channels); } } if ((res = ensure_tmp(impl)) < 0) return res; if ((res = setup_channelmix(impl, channels, position)) < 0) return res; return 0; } static int do_sync_filter_graph(struct spa_loop *loop, bool async, uint32_t seq, const void *data, size_t size, void *user_data) { struct impl *impl = user_data; struct filter_graph *g; impl->n_graph = 0; spa_list_for_each(g, &impl->active_graphs, link) if (g->setup && !g->removing) impl->filter_graph[impl->n_graph++] = g; impl->recalc = true; return 0; } static void clean_filter_handles(struct impl *impl, bool force) { struct filter_graph *g, *t; spa_list_for_each_safe(g, t, &impl->active_graphs, link) { if (!g->removing) continue; spa_list_remove(&g->link); if (g->graph) spa_hook_remove(&g->listener); if (g->handle) spa_plugin_loader_unload(impl->loader, g->handle); spa_zero(*g); spa_list_append(&impl->free_graphs, &g->link); } recalc_graph_latency(impl); } static inline void insert_graph(struct spa_list *graphs, struct filter_graph *pending) { struct filter_graph *g; spa_list_for_each(g, graphs, link) { if (g->order < pending->order) break; } spa_list_append(&g->link, &pending->link); } static int load_filter_graph(struct impl *impl, const char *graph, int order) { char qlimit[64]; int res; void *iface; struct spa_handle *new_handle = NULL; struct filter_graph *pending, *g, *t; if (impl->props.filter_graph_disabled) return -EPERM; /* find graph spot */ if (spa_list_is_empty(&impl->free_graphs)) return -ENOSPC; /* find free graph for our new filter */ pending = spa_list_first(&impl->free_graphs, struct filter_graph, link); pending->impl = impl; pending->order = order; pending->removing = false; /* move active graphs with same order to inactive list */ spa_list_for_each_safe(g, t, &impl->active_graphs, link) { if (g->order == order) { g->removing = true; spa_log_info(impl->log, "removing filter-graph order:%d", order); } } if (graph != NULL && graph[0] != '\0') { snprintf(qlimit, sizeof(qlimit), "%u", impl->quantum_limit); new_handle = spa_plugin_loader_load(impl->loader, "filter.graph", &SPA_DICT_ITEMS( SPA_DICT_ITEM(SPA_KEY_LIBRARY_NAME, "filter-graph/libspa-filter-graph"), SPA_DICT_ITEM("clock.quantum-limit", qlimit), SPA_DICT_ITEM("filter.graph", graph))); if (new_handle == NULL) goto error; res = spa_handle_get_interface(new_handle, SPA_TYPE_INTERFACE_FilterGraph, &iface); if (res < 0 || iface == NULL) goto error; /* prepare new filter and swap it */ pending->graph = iface; pending->handle = new_handle; spa_filter_graph_add_listener(pending->graph, &pending->listener, &graph_events, pending); spa_list_remove(&pending->link); insert_graph(&impl->active_graphs, pending); spa_log_info(impl->log, "loading filter-graph order:%d", order); } if (impl->setup) res = setup_filter_graphs(impl, false); spa_loop_locked(impl->data_loop, do_sync_filter_graph, 0, NULL, 0, impl); if (impl->in_filter_props == 0) clean_filter_handles(impl, false); impl->info.change_mask |= SPA_NODE_CHANGE_MASK_PARAMS; impl->params[IDX_PropInfo].user++; impl->params[IDX_Props].user++; return 0; error: if (new_handle != NULL) spa_plugin_loader_unload(impl->loader, new_handle); return -ENOTSUP; } static int audioconvert_set_param(struct impl *this, const char *k, const char *s, bool *disable_filter) { int res; if (spa_streq(k, "monitor.channel-volumes")) this->monitor_channel_volumes = spa_atob(s); else if (spa_streq(k, "channelmix.disable")) this->props.mix_disabled = spa_atob(s); else if (spa_streq(k, "channelmix.min-volume")) spa_atof(s, &this->props.min_volume); else if (spa_streq(k, "channelmix.max-volume")) spa_atof(s, &this->props.max_volume); else if (spa_streq(k, "channelmix.normalize")) SPA_FLAG_UPDATE(this->mix.options, CHANNELMIX_OPTION_NORMALIZE, spa_atob(s)); else if (spa_streq(k, "channelmix.mix-lfe")) SPA_FLAG_UPDATE(this->mix.options, CHANNELMIX_OPTION_MIX_LFE, spa_atob(s)); else if (spa_streq(k, "channelmix.upmix")) SPA_FLAG_UPDATE(this->mix.options, CHANNELMIX_OPTION_UPMIX, spa_atob(s)); else if (spa_streq(k, "channelmix.lfe-cutoff")) spa_atof(s, &this->mix.lfe_cutoff); else if (spa_streq(k, "channelmix.fc-cutoff")) spa_atof(s, &this->mix.fc_cutoff); else if (spa_streq(k, "channelmix.rear-delay")) spa_atof(s, &this->mix.rear_delay); else if (spa_streq(k, "channelmix.stereo-widen")) spa_atof(s, &this->mix.widen); else if (spa_streq(k, "channelmix.hilbert-taps")) spa_atou32(s, &this->mix.hilbert_taps, 0); else if (spa_streq(k, "channelmix.upmix-method")) this->mix.upmix = channelmix_upmix_from_label(s); else if (spa_streq(k, "resample.quality")) this->props.resample_quality = atoi(s); else if (spa_streq(k, "resample.disable")) this->props.resample_disabled = spa_atob(s); else if (spa_streq(k, "resample.window")) this->props.resample_config.window = resample_window_from_label(s); else if (spa_streq(k, "resample.cutoff")) spa_atod(s, &this->props.resample_config.cutoff); else if (spa_streq(k, "resample.n-taps")) spa_atou32(s, &this->props.resample_config.n_taps, 0); else if (spa_strstartswith(k, "resample.param.")) { uint32_t idx = resample_param_from_label(k+strlen("resample.param.")); spa_atod(s, &this->props.resample_config.params[idx]); } else if (spa_streq(k, "dither.noise")) spa_atou32(s, &this->dir[1].conv.noise_bits, 0); else if (spa_streq(k, "dither.method")) this->dir[1].conv.method = dither_method_from_label(s); else if (spa_streq(k, "debug.wav-path")) { spa_scnprintf(this->props.wav_path, sizeof(this->props.wav_path), "%s", s ? s : ""); } else if (spa_streq(k, "channelmix.lock-volumes")) this->props.lock_volumes = spa_atob(s); else if (spa_strstartswith(k, "audioconvert.filter-graph.")) { int order = atoi(k + strlen("audioconvert.filter-graph.")); if ((res = load_filter_graph(this, s, order)) < 0) { spa_log_warn(this->log, "Can't load filter-graph %d: %s", order, spa_strerror(res)); } } else if (spa_streq(k, "audioconvert.filter-graph.disable")) { if (!*disable_filter) *disable_filter = spa_atob(s); } else return 0; return 1; } static int parse_prop_params(struct impl *this, struct spa_pod *params) { struct spa_pod_parser prs; struct spa_pod_frame f; int changed = 0; bool filter_graph_disabled = this->props.filter_graph_disabled; spa_pod_parser_pod(&prs, params); if (spa_pod_parser_push_struct(&prs, &f) < 0) return 0; while (true) { const char *name; struct spa_pod *pod; char value[4096]; if (spa_pod_parser_get_string(&prs, &name) < 0) break; if (spa_pod_parser_get_pod(&prs, &pod) < 0) break; if (spa_pod_is_string(pod)) { spa_pod_copy_string(pod, sizeof(value), value); } else if (spa_pod_is_float(pod)) { spa_dtoa(value, sizeof(value), SPA_POD_VALUE(struct spa_pod_float, pod)); } else if (spa_pod_is_double(pod)) { spa_dtoa(value, sizeof(value), SPA_POD_VALUE(struct spa_pod_double, pod)); } else if (spa_pod_is_int(pod)) { snprintf(value, sizeof(value), "%d", SPA_POD_VALUE(struct spa_pod_int, pod)); } else if (spa_pod_is_long(pod)) { snprintf(value, sizeof(value), "%"PRIi64, SPA_POD_VALUE(struct spa_pod_long, pod)); } else if (spa_pod_is_bool(pod)) { snprintf(value, sizeof(value), "%s", SPA_POD_VALUE(struct spa_pod_bool, pod) ? "true" : "false"); } else if (spa_pod_is_none(pod)) { spa_zero(value); } else continue; spa_log_info(this->log, "key:'%s' val:'%s'", name, value); changed += audioconvert_set_param(this, name, value, &filter_graph_disabled); } if (changed) { this->props.filter_graph_disabled = filter_graph_disabled; if (this->setup) channelmix_init(&this->mix); } return changed; } static int get_ramp_samples(struct impl *this, struct volume_ramp_params *vrp) { int samples = -1; if (vrp->volume_ramp_samples) samples = vrp->volume_ramp_samples; else if (vrp->volume_ramp_time) { samples = (vrp->volume_ramp_time * vrp->rate) / 1000; spa_log_info(this->log, "volume ramp samples calculated from time is %d", samples); } return samples; } static int get_ramp_step_samples(struct impl *this, struct volume_ramp_params *vrp) { int samples = -1; if (vrp->volume_ramp_step_samples) samples = vrp->volume_ramp_step_samples; else if (vrp->volume_ramp_step_time) { /* convert the step time which is in nano seconds to seconds, round up */ samples = SPA_MAX(1u, vrp->volume_ramp_step_time/1000) * (vrp->rate/1000); spa_log_debug(this->log, "volume ramp step samples calculated from time is %d", samples); } return samples; } static float get_volume_at_scale(struct volume_ramp_params *vrp, float value) { if (vrp->scale == SPA_AUDIO_VOLUME_RAMP_LINEAR || vrp->scale == SPA_AUDIO_VOLUME_RAMP_INVALID) return value; else if (vrp->scale == SPA_AUDIO_VOLUME_RAMP_CUBIC) return (value * value * value); return 0.0; } static struct spa_pod *generate_ramp_seq(struct impl *this, struct volume_ramp_params *vrp, void *buffer, size_t size) { struct spa_pod_dynamic_builder b; struct spa_pod_frame f[1]; float start = vrp->start, end = vrp->end; int samples = get_ramp_samples(this, vrp); int step = get_ramp_step_samples(this, vrp); int offs = 0; if (samples < 0 || step < 0 || (samples > 0 && step == 0)) return NULL; spa_pod_dynamic_builder_init(&b, buffer, size, 4096); spa_pod_builder_push_sequence(&b.b, &f[0], 0); spa_log_info(this->log, "generating ramp sequence from %f to %f with " "step %d/%d at scale %d", start, end, step, samples, vrp->scale); while (1) { float pos = (samples == 0) ? end : SPA_CLAMP(start + (end - start) * offs / samples, SPA_MIN(start, end), SPA_MAX(start, end)); float vas = get_volume_at_scale(vrp, pos); spa_log_trace(this->log, "volume %d accum %f", offs, vas); spa_pod_builder_control(&b.b, offs, SPA_CONTROL_Properties); spa_pod_builder_add_object(&b.b, SPA_TYPE_OBJECT_Props, 0, SPA_PROP_volume, SPA_POD_Float(vas)); if (offs >= samples) break; offs = SPA_MIN(samples, offs + step); } return spa_pod_builder_pop(&b.b, &f[0]); } static void generate_volume_ramp(struct impl *this, struct volume_ramp_params *vrp, void *buffer, size_t size) { void *sequence; sequence = generate_ramp_seq(this, vrp, buffer, size); if (!sequence) spa_log_error(this->log, "unable to generate sequence"); this->vol_ramp_sequence = (struct spa_pod_sequence *) sequence; this->vol_ramp_sequence_data = (void*)sequence == buffer ? NULL : sequence; this->vol_ramp_offset = 0; this->recalc = true; } static int apply_props(struct impl *this, const struct spa_pod *param) { struct spa_pod_prop *prop; struct spa_pod_object *obj = (struct spa_pod_object *) param; struct props *p = &this->props; bool have_channel_volume = false; bool have_soft_volume = false; int changed = 0; int vol_ramp_params_changed = 0; struct volume_ramp_params vrp; uint32_t n; int32_t value; uint32_t id; spa_zero(vrp); SPA_POD_OBJECT_FOREACH(obj, prop) { switch (prop->key) { case SPA_PROP_volume: p->prev_volume = p->volume; if (!p->lock_volumes && spa_pod_get_float(&prop->value, &p->volume) == 0) { spa_log_debug(this->log, "%p new volume %f", this, p->volume); changed++; } break; case SPA_PROP_mute: if (!p->lock_volumes && spa_pod_get_bool(&prop->value, &p->channel.mute) == 0) { have_channel_volume = true; changed++; } break; case SPA_PROP_volumeRampSamples: if (this->vol_ramp_sequence) { spa_log_error(this->log, "%p volume ramp sequence is being " "applied try again", this); break; } if (spa_pod_get_int(&prop->value, &value) == 0 && value) { vrp.volume_ramp_samples = value; spa_log_info(this->log, "%p volume ramp samples %d", this, value); vol_ramp_params_changed++; } break; case SPA_PROP_volumeRampStepSamples: if (this->vol_ramp_sequence) { spa_log_error(this->log, "%p volume ramp sequence is being " "applied try again", this); break; } if (spa_pod_get_int(&prop->value, &value) == 0 && value) { vrp.volume_ramp_step_samples = value; spa_log_info(this->log, "%p volume ramp step samples is %d", this, value); } break; case SPA_PROP_volumeRampTime: if (this->vol_ramp_sequence) { spa_log_error(this->log, "%p volume ramp sequence is being " "applied try again", this); break; } if (spa_pod_get_int(&prop->value, &value) == 0 && value) { vrp.volume_ramp_time = value; spa_log_info(this->log, "%p volume ramp time %d", this, value); vol_ramp_params_changed++; } break; case SPA_PROP_volumeRampStepTime: if (this->vol_ramp_sequence) { spa_log_error(this->log, "%p volume ramp sequence is being " "applied try again", this); break; } if (spa_pod_get_int(&prop->value, &value) == 0 && value) { vrp.volume_ramp_step_time = value; spa_log_info(this->log, "%p volume ramp time %d", this, value); } break; case SPA_PROP_volumeRampScale: if (this->vol_ramp_sequence) { spa_log_error(this->log, "%p volume ramp sequence is being " "applied try again", this); break; } if (spa_pod_get_id(&prop->value, &id) == 0 && id) { vrp.scale = id; spa_log_info(this->log, "%p volume ramp scale %d", this, id); } break; case SPA_PROP_channelVolumes: if (!p->lock_volumes && (n = spa_pod_copy_array(&prop->value, SPA_TYPE_Float, p->channel.volumes, SPA_N_ELEMENTS(p->channel.volumes))) > 0) { have_channel_volume = true; p->channel.n_volumes = n; changed++; } break; case SPA_PROP_channelMap: if ((n = spa_pod_copy_array(&prop->value, SPA_TYPE_Id, p->channel_map, SPA_N_ELEMENTS(p->channel_map))) > 0) { p->n_channels = n; changed++; } break; case SPA_PROP_softMute: if (!p->lock_volumes && spa_pod_get_bool(&prop->value, &p->soft.mute) == 0) { have_soft_volume = true; changed++; } break; case SPA_PROP_softVolumes: if (!p->lock_volumes && (n = spa_pod_copy_array(&prop->value, SPA_TYPE_Float, p->soft.volumes, SPA_N_ELEMENTS(p->soft.volumes))) > 0) { have_soft_volume = true; p->soft.n_volumes = n; changed++; } break; case SPA_PROP_monitorMute: if (spa_pod_get_bool(&prop->value, &p->monitor.mute) == 0) changed++; break; case SPA_PROP_monitorVolumes: if ((n = spa_pod_copy_array(&prop->value, SPA_TYPE_Float, p->monitor.volumes, SPA_N_ELEMENTS(p->monitor.volumes))) > 0) { p->monitor.n_volumes = n; changed++; } break; case SPA_PROP_rate: if (spa_pod_get_double(&prop->value, &p->rate) == 0 && !this->rate_adjust && p->rate != 1.0) { this->rate_adjust = true; spa_log_info(this->log, "%p: activating adaptive resampler", this); } break; case SPA_PROP_params: if (this->filter_props_count == 0) changed += parse_prop_params(this, &prop->value); break; default: break; } } if (changed) { if (have_soft_volume) p->have_soft_volume = true; else if (have_channel_volume) p->have_soft_volume = false; set_volume(this); this->recalc = true; } if (!p->lock_volumes && vol_ramp_params_changed) { struct dir *dir = &this->dir[SPA_DIRECTION_REVERSE(this->direction)]; vrp.start = p->prev_volume; vrp.end = p->volume; vrp.rate = dir->format.info.raw.rate; generate_volume_ramp(this, &vrp, NULL, 0); } if (changed) { this->info.change_mask |= SPA_NODE_CHANGE_MASK_PARAMS; this->params[IDX_Props].user++; } return changed; } static int apply_midi(struct impl *this, const struct spa_pod *value) { struct props *p = &this->props; uint8_t ev[8]; int ev_size; const uint32_t *body = SPA_POD_BODY_CONST(value); size_t size = SPA_POD_BODY_SIZE(value); uint64_t state = 0; ev_size = spa_ump_to_midi(&body, &size, ev, sizeof(ev), &state); if (ev_size < 3) return -EINVAL; if ((ev[0] & 0xf0) != 0xb0 || ev[1] != 7) return 0; p->volume = ev[2] / 127.0f; set_volume(this); return 1; } static int reconfigure_mode(struct impl *this, enum spa_param_port_config_mode mode, enum spa_direction direction, bool monitor, bool control, struct spa_audio_info *info) { struct dir *dir; uint32_t i; dir = &this->dir[direction]; if (dir->have_profile && this->monitor == monitor && dir->mode == mode && dir->control == control && (info == NULL || memcmp(&dir->format, info, sizeof(*info)) == 0)) return 0; spa_log_debug(this->log, "%p: port config direction:%d monitor:%d " "control:%d mode:%d %d", this, direction, monitor, control, mode, dir->n_ports); for (i = 0; i < dir->n_ports; i++) { deinit_port(this, direction, i); if (this->monitor && direction == SPA_DIRECTION_INPUT) deinit_port(this, SPA_DIRECTION_OUTPUT, i+1); } this->monitor = monitor; this->setup = false; dir->control = control; dir->have_profile = true; dir->mode = mode; switch (mode) { case SPA_PARAM_PORT_CONFIG_MODE_dsp: { if (info) { dir->n_ports = info->info.raw.channels; dir->format = *info; dir->format.info.raw.format = SPA_AUDIO_FORMAT_DSP_F32; dir->format.info.raw.rate = 0; dir->have_format = true; } else { dir->n_ports = 0; } if (this->monitor && direction == SPA_DIRECTION_INPUT) this->dir[SPA_DIRECTION_OUTPUT].n_ports = dir->n_ports + 1; for (i = 0; i < dir->n_ports; i++) { uint32_t pos = info->info.raw.position[i]; init_port(this, direction, i, pos, true, false, false); if (this->monitor && direction == SPA_DIRECTION_INPUT) init_port(this, SPA_DIRECTION_OUTPUT, i+1, pos, true, true, false); } break; } case SPA_PARAM_PORT_CONFIG_MODE_convert: { dir->n_ports = 1; dir->have_format = false; init_port(this, direction, 0, 0, false, false, false); break; } case SPA_PARAM_PORT_CONFIG_MODE_none: dir->n_ports = 0; break; default: return -ENOTSUP; } if (direction == SPA_DIRECTION_INPUT && dir->control) { i = dir->n_ports++; init_port(this, direction, i, 0, false, false, true); } /* emit all port changes */ emit_info(this, false); this->info.change_mask |= SPA_NODE_CHANGE_MASK_FLAGS | SPA_NODE_CHANGE_MASK_PARAMS; this->info.flags &= ~SPA_NODE_FLAG_NEED_CONFIGURE; this->params[IDX_Props].user++; this->params[IDX_PortConfig].user++; return 0; } static int node_set_param_port_config(struct impl *this, uint32_t flags, const struct spa_pod *param) { struct spa_audio_info info = { 0, }, *infop = NULL; struct spa_pod *format = NULL; enum spa_direction direction; enum spa_param_port_config_mode mode; bool monitor = false, control = false; int res; if (param == NULL) return 0; if (spa_pod_parse_object(param, SPA_TYPE_OBJECT_ParamPortConfig, NULL, SPA_PARAM_PORT_CONFIG_direction, SPA_POD_Id(&direction), SPA_PARAM_PORT_CONFIG_mode, SPA_POD_Id(&mode), SPA_PARAM_PORT_CONFIG_monitor, SPA_POD_OPT_Bool(&monitor), SPA_PARAM_PORT_CONFIG_control, SPA_POD_OPT_Bool(&control), SPA_PARAM_PORT_CONFIG_format, SPA_POD_OPT_Pod(&format)) < 0) return -EINVAL; if (format) { if (!spa_pod_is_object_type(format, SPA_TYPE_OBJECT_Format)) return -EINVAL; if ((res = spa_format_parse(format, &info.media_type, &info.media_subtype)) < 0) return res; if (info.media_type != SPA_MEDIA_TYPE_audio || info.media_subtype != SPA_MEDIA_SUBTYPE_raw) return -EINVAL; if (spa_format_audio_raw_parse(format, &info.info.raw) < 0) return -EINVAL; if (info.info.raw.channels == 0 || info.info.raw.channels > MAX_CHANNELS) return -EINVAL; infop = &info; } return reconfigure_mode(this, mode, direction, monitor, control, infop); } static int node_set_param_props(struct impl *this, uint32_t flags, const struct spa_pod *param) { bool have_graph = false; struct filter_graph *g, *t; if (param == NULL) return 0; this->filter_props_count = 0; spa_list_for_each_safe(g, t, &this->active_graphs, link) { if (g->removing) continue; have_graph = true; this->in_filter_props++; spa_filter_graph_set_props(g->graph, SPA_DIRECTION_INPUT, param); this->filter_props_count++; this->in_filter_props--; } if (!have_graph) apply_props(this, param); clean_filter_handles(this, false); return 0; } static int impl_node_set_param(void *object, uint32_t id, uint32_t flags, const struct spa_pod *param) { struct impl *this = object; int res; spa_return_val_if_fail(this != NULL, -EINVAL); switch (id) { case SPA_PARAM_PortConfig: res = node_set_param_port_config(this, flags, param); break; case SPA_PARAM_Props: res = node_set_param_props(this, flags, param); break; default: return -ENOENT; } emit_info(this, false); return res; } static int int32_cmp(const void *v1, const void *v2) { int32_t a1 = *(int32_t*)v1; int32_t a2 = *(int32_t*)v2; if (a1 == 0 && a2 != 0) return 1; if (a2 == 0 && a1 != 0) return -1; return a1 - a2; } static int setup_in_convert(struct impl *this) { uint32_t i, j; struct dir *in = &this->dir[SPA_DIRECTION_INPUT]; struct spa_audio_info src_info, dst_info; int res; bool remap = false; src_info = in->format; dst_info = src_info; dst_info.info.raw.format = SPA_AUDIO_FORMAT_DSP_F32; spa_log_info(this->log, "%p: %s/%d@%d->%s/%d@%d", this, spa_debug_type_find_name(spa_type_audio_format, src_info.info.raw.format), src_info.info.raw.channels, src_info.info.raw.rate, spa_debug_type_find_name(spa_type_audio_format, dst_info.info.raw.format), dst_info.info.raw.channels, dst_info.info.raw.rate); qsort(dst_info.info.raw.position, dst_info.info.raw.channels, sizeof(uint32_t), int32_cmp); for (i = 0; i < src_info.info.raw.channels; i++) { for (j = 0; j < dst_info.info.raw.channels; j++) { uint32_t pi, pj; char b1[8], b2[8]; pi = src_info.info.raw.position[i]; pj = dst_info.info.raw.position[j]; if (pi != pj) continue; in->remap[i] = j; if (i != j) remap = true; spa_log_debug(this->log, "%p: channel %d (%d) -> %d (%s -> %s)", this, i, in->remap[i], j, spa_type_audio_channel_make_short_name(pi, b1, 8, "UNK"), spa_type_audio_channel_make_short_name(pj, b2, 8, "UNK")); dst_info.info.raw.position[j] = -1; break; } } if (in->conv.free) convert_free(&in->conv); in->conv.src_fmt = src_info.info.raw.format; in->conv.dst_fmt = dst_info.info.raw.format; in->conv.n_channels = dst_info.info.raw.channels; in->conv.cpu_flags = this->cpu_flags; in->need_remap = remap; if ((res = convert_init(&in->conv)) < 0) return res; spa_log_debug(this->log, "%p: got converter features %08x:%08x passthrough:%d remap:%d %s", this, this->cpu_flags, in->conv.cpu_flags, in->conv.is_passthrough, remap, in->conv.func_name); return 0; } static void fix_volumes(struct impl *this, struct volumes *vols, uint32_t channels) { float s; uint32_t i; spa_log_debug(this->log, "%p %d -> %d", this, vols->n_volumes, channels); if (vols->n_volumes > 0) { s = 0.0f; for (i = 0; i < vols->n_volumes; i++) s += vols->volumes[i]; s /= vols->n_volumes; } else { s = 1.0f; } vols->n_volumes = channels; for (i = 0; i < vols->n_volumes; i++) vols->volumes[i] = s; } static int remap_volumes(struct impl *this, const struct spa_audio_info *info) { struct props *p = &this->props; uint32_t i, j, target = info->info.raw.channels; for (i = 0; i < p->n_channels; i++) { for (j = i; j < target; j++) { uint32_t pj = info->info.raw.position[j]; spa_log_debug(this->log, "%d %d: %d <-> %d", i, j, p->channel_map[i], pj); if (p->channel_map[i] != pj) continue; if (i != j) { SPA_SWAP(p->channel_map[i], p->channel_map[j]); SPA_SWAP(p->channel.volumes[i], p->channel.volumes[j]); SPA_SWAP(p->soft.volumes[i], p->soft.volumes[j]); SPA_SWAP(p->monitor.volumes[i], p->monitor.volumes[j]); } break; } } p->n_channels = target; for (i = 0; i < p->n_channels; i++) p->channel_map[i] = info->info.raw.position[i]; if (target == 0) return 0; if (p->channel.n_volumes != target) fix_volumes(this, &p->channel, target); if (p->soft.n_volumes != target) fix_volumes(this, &p->soft, target); if (p->monitor.n_volumes != target) fix_volumes(this, &p->monitor, target); return 1; } static void set_volume(struct impl *this) { struct volumes *vol; uint32_t i; float volumes[MAX_CHANNELS]; struct dir *dir = &this->dir[this->direction]; spa_log_debug(this->log, "%p set volume %f have_format:%d", this, this->props.volume, dir->have_format); if (dir->have_format) remap_volumes(this, &dir->format); if (this->mix.set_volume == NULL) return; if (this->props.have_soft_volume) vol = &this->props.soft; else vol = &this->props.channel; for (i = 0; i < vol->n_volumes; i++) volumes[i] = SPA_CLAMPF(vol->volumes[dir->remap[i]], this->props.min_volume, this->props.max_volume); channelmix_set_volume(&this->mix, SPA_CLAMPF(this->props.volume, this->props.min_volume, this->props.max_volume), vol->mute, vol->n_volumes, volumes); this->info.change_mask |= SPA_NODE_CHANGE_MASK_PARAMS; this->params[IDX_Props].user++; } static char *format_position(char *str, size_t len, uint32_t channels, uint32_t *position) { uint32_t i, idx = 0; char buf[8]; for (i = 0; i < channels; i++) idx += snprintf(str + idx, len - idx, "%s%s", i == 0 ? "" : " ", spa_type_audio_channel_make_short_name(position[i], buf, sizeof(buf), "UNK")); return str; } static int setup_channelmix(struct impl *this, uint32_t channels, uint32_t *position) { struct dir *in = &this->dir[SPA_DIRECTION_INPUT]; struct dir *out = &this->dir[SPA_DIRECTION_OUTPUT]; uint32_t i, src_chan, dst_chan, p; uint64_t src_mask, dst_mask; char str[1024]; int res; src_chan = channels; dst_chan = out->format.info.raw.channels; for (i = 0, src_mask = 0; i < src_chan; i++) { p = position[i]; src_mask |= 1ULL << (p < 64 ? p : 0); } for (i = 0, dst_mask = 0; i < dst_chan; i++) { p = out->format.info.raw.position[i]; dst_mask |= 1ULL << (p < 64 ? p : 0); } spa_log_info(this->log, "in %s (%016"PRIx64")", format_position(str, sizeof(str), src_chan, position), src_mask); spa_log_info(this->log, "out %s (%016"PRIx64")", format_position(str, sizeof(str), dst_chan, out->format.info.raw.position), dst_mask); spa_log_info(this->log, "%p: %s/%d@%d->%s/%d@%d %08"PRIx64":%08"PRIx64, this, spa_debug_type_find_name(spa_type_audio_format, SPA_AUDIO_FORMAT_DSP_F32), src_chan, in->format.info.raw.rate, spa_debug_type_find_name(spa_type_audio_format, SPA_AUDIO_FORMAT_DSP_F32), dst_chan, in->format.info.raw.rate, src_mask, dst_mask); if (this->props.mix_disabled && (src_chan != dst_chan || src_mask != dst_mask)) return -EPERM; this->mix.src_chan = src_chan; this->mix.src_mask = src_mask; this->mix.dst_chan = dst_chan; this->mix.dst_mask = dst_mask; this->mix.cpu_flags = this->cpu_flags; this->mix.log = this->log; this->mix.freq = in->format.info.raw.rate; if ((res = channelmix_init(&this->mix)) < 0) return res; set_volume(this); spa_log_debug(this->log, "%p: got channelmix features %08x:%08x flags:%08x %s", this, this->cpu_flags, this->mix.cpu_flags, this->mix.flags, this->mix.func_name); return 0; } static int setup_resample(struct impl *this) { struct dir *in = &this->dir[SPA_DIRECTION_INPUT]; struct dir *out = &this->dir[SPA_DIRECTION_OUTPUT]; int res; uint32_t channels; if (this->direction == SPA_DIRECTION_INPUT) channels = in->format.info.raw.channels; else channels = out->format.info.raw.channels; spa_log_info(this->log, "%p: %s/%d@%d->%s/%d@%d", this, spa_debug_type_find_name(spa_type_audio_format, SPA_AUDIO_FORMAT_DSP_F32), channels, in->format.info.raw.rate, spa_debug_type_find_name(spa_type_audio_format, SPA_AUDIO_FORMAT_DSP_F32), channels, out->format.info.raw.rate); if (this->props.resample_disabled && !this->resample_peaks && in->format.info.raw.rate != out->format.info.raw.rate) return -EPERM; if (this->resample.free) resample_free(&this->resample); this->resample.channels = channels; this->resample.i_rate = in->format.info.raw.rate; this->resample.o_rate = out->format.info.raw.rate; this->resample.log = this->log; this->resample.quality = this->props.resample_quality; this->resample.config = this->props.resample_config; this->resample.cpu_flags = this->cpu_flags; this->rate_adjust = this->props.rate != 1.0; if (this->resample_peaks) res = resample_peaks_init(&this->resample); else res = resample_native_init(&this->resample); spa_log_debug(this->log, "%p: got resample features %08x:%08x %s", this, this->cpu_flags, this->resample.cpu_flags, this->resample.func_name); return res; } static int calc_width(struct spa_audio_info *info) { switch (info->info.raw.format) { case SPA_AUDIO_FORMAT_U8: case SPA_AUDIO_FORMAT_U8P: case SPA_AUDIO_FORMAT_S8: case SPA_AUDIO_FORMAT_S8P: case SPA_AUDIO_FORMAT_ULAW: case SPA_AUDIO_FORMAT_ALAW: return 1; case SPA_AUDIO_FORMAT_S16P: case SPA_AUDIO_FORMAT_S16: case SPA_AUDIO_FORMAT_S16_OE: return 2; case SPA_AUDIO_FORMAT_S24P: case SPA_AUDIO_FORMAT_S24: case SPA_AUDIO_FORMAT_S24_OE: return 3; case SPA_AUDIO_FORMAT_F64P: case SPA_AUDIO_FORMAT_F64: case SPA_AUDIO_FORMAT_F64_OE: return 8; default: return 4; } } static int setup_out_convert(struct impl *this) { uint32_t i, j; struct dir *out = &this->dir[SPA_DIRECTION_OUTPUT]; struct spa_audio_info src_info, dst_info; int res; bool remap = false; dst_info = out->format; src_info = dst_info; src_info.info.raw.format = SPA_AUDIO_FORMAT_DSP_F32; spa_log_info(this->log, "%p: %s/%d@%d->%s/%d@%d", this, spa_debug_type_find_name(spa_type_audio_format, src_info.info.raw.format), src_info.info.raw.channels, src_info.info.raw.rate, spa_debug_type_find_name(spa_type_audio_format, dst_info.info.raw.format), dst_info.info.raw.channels, dst_info.info.raw.rate); qsort(src_info.info.raw.position, src_info.info.raw.channels, sizeof(uint32_t), int32_cmp); for (i = 0; i < src_info.info.raw.channels; i++) { for (j = 0; j < dst_info.info.raw.channels; j++) { uint32_t pi, pj; char b1[8], b2[8]; pi = src_info.info.raw.position[i]; pj = dst_info.info.raw.position[j]; if (pi != pj) continue; out->remap[i] = j; if (i != j) remap = true; spa_log_debug(this->log, "%p: channel %d (%d) -> %d (%s -> %s)", this, i, out->remap[i], j, spa_type_audio_channel_make_short_name(pi, b1, 8, "UNK"), spa_type_audio_channel_make_short_name(pj, b2, 8, "UNK")); dst_info.info.raw.position[j] = -1; break; } } if (out->conv.free) convert_free(&out->conv); out->conv.src_fmt = src_info.info.raw.format; out->conv.dst_fmt = dst_info.info.raw.format; out->conv.rate = dst_info.info.raw.rate; out->conv.n_channels = dst_info.info.raw.channels; out->conv.cpu_flags = this->cpu_flags; out->need_remap = remap; if ((res = convert_init(&out->conv)) < 0) return res; spa_log_debug(this->log, "%p: got converter features %08x:%08x quant:%d:%d" " passthrough:%d remap:%d %s", this, this->cpu_flags, out->conv.cpu_flags, out->conv.method, out->conv.noise_bits, out->conv.is_passthrough, remap, out->conv.func_name); return 0; } static uint32_t resample_update_rate_match(struct impl *this, bool passthrough, uint32_t size, uint32_t queued) { uint32_t delay, match_size; int32_t delay_frac; if (passthrough) { delay = 0; delay_frac = 0; match_size = size; } else { /* Only apply rate_scale if we're working in DSP mode (i.e. in driver rate) */ double scale = this->dir[SPA_DIRECTION_REVERSE(this->direction)].mode == SPA_PARAM_PORT_CONFIG_MODE_dsp ? this->rate_scale : 1.0; double rate = scale / this->props.rate; double fdelay; if (this->io_rate_match && SPA_FLAG_IS_SET(this->io_rate_match->flags, SPA_IO_RATE_MATCH_FLAG_ACTIVE)) rate *= this->io_rate_match->rate; resample_update_rate(&this->resample, rate); fdelay = resample_delay(&this->resample) + resample_phase(&this->resample); if (this->direction == SPA_DIRECTION_INPUT) { match_size = resample_in_len(&this->resample, size); } else { fdelay *= rate * this->resample.o_rate / this->resample.i_rate; match_size = resample_out_len(&this->resample, size); } delay = (uint32_t)round(fdelay); delay_frac = (int32_t)((fdelay - delay) * 1e9); } match_size -= SPA_MIN(match_size, queued); spa_log_trace_fp(this->log, "%p: next match %u %u %u", this, match_size, size, queued); if (this->io_rate_match) { this->io_rate_match->delay = delay + queued; this->io_rate_match->delay_frac = delay_frac; this->io_rate_match->size = match_size; } return match_size; } static inline bool resample_is_passthrough(struct impl *this) { if (this->props.resample_disabled) return true; if (this->resample.i_rate != this->resample.o_rate) return false; if (this->rate_scale != 1.0) return false; if (this->rate_adjust) return false; if (this->io_rate_match != NULL && SPA_FLAG_IS_SET(this->io_rate_match->flags, SPA_IO_RATE_MATCH_FLAG_ACTIVE)) return false; return true; } static int setup_convert(struct impl *this) { struct dir *in, *out; uint32_t i, rate, duration; struct port *p; int res; in = &this->dir[SPA_DIRECTION_INPUT]; out = &this->dir[SPA_DIRECTION_OUTPUT]; spa_log_debug(this->log, "%p: setup:%d in_format:%d out_format:%d", this, this->setup, in->have_format, out->have_format); if (this->setup) return 0; if (!in->have_format || !out->have_format) return -EINVAL; if (this->io_position != NULL) { rate = this->io_position->clock.target_rate.denom; duration = this->io_position->clock.target_duration; } else { rate = DEFAULT_RATE; duration = this->quantum_limit; } /* in DSP mode we always convert to the DSP rate */ if (in->mode == SPA_PARAM_PORT_CONFIG_MODE_dsp) in->format.info.raw.rate = rate; if (out->mode == SPA_PARAM_PORT_CONFIG_MODE_dsp) out->format.info.raw.rate = rate; /* try to passthrough the rates */ if (in->format.info.raw.rate == 0) in->format.info.raw.rate = out->format.info.raw.rate; else if (out->format.info.raw.rate == 0) out->format.info.raw.rate = in->format.info.raw.rate; /* try to passthrough the channels */ if (in->format.info.raw.channels == 0) in->format.info.raw.channels = out->format.info.raw.channels; else if (out->format.info.raw.channels == 0) out->format.info.raw.channels = in->format.info.raw.channels; if (in->format.info.raw.rate == 0 || out->format.info.raw.rate == 0) return -EINVAL; if (in->format.info.raw.channels == 0 || out->format.info.raw.channels == 0) return -EINVAL; if ((res = setup_in_convert(this)) < 0) return res; if ((res = setup_filter_graphs(this, true)) < 0) return res; if ((res = setup_resample(this)) < 0) return res; if ((res = setup_out_convert(this)) < 0) return res; this->maxsize = this->quantum_limit * sizeof(float); for (i = 0; i < in->n_ports; i++) { p = GET_IN_PORT(this, i); this->maxsize = SPA_MAX(this->maxsize, p->maxsize); } for (i = 0; i < out->n_ports; i++) { p = GET_OUT_PORT(this, i); this->maxsize = SPA_MAX(this->maxsize, p->maxsize); } if ((res = ensure_tmp(this)) < 0) return res; resample_update_rate_match(this, resample_is_passthrough(this), duration, 0); this->setup = true; this->recalc = true; return 0; } static void reset_node(struct impl *this) { struct filter_graph *g; spa_list_for_each(g, &this->active_graphs, link) { if (g->graph) spa_filter_graph_deactivate(g->graph); g->setup = false; } if (this->resample.reset) resample_reset(&this->resample); this->in_offset = 0; this->out_offset = 0; } static int impl_node_send_command(void *object, const struct spa_command *command) { struct impl *this = object; int res; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(command != NULL, -EINVAL); switch (SPA_NODE_COMMAND_ID(command)) { case SPA_NODE_COMMAND_Start: if (this->started) return 0; if ((res = setup_convert(this)) < 0) return res; this->started = true; break; case SPA_NODE_COMMAND_Suspend: reset_node(this); this->setup = false; SPA_FALLTHROUGH; case SPA_NODE_COMMAND_Pause: this->started = false; break; case SPA_NODE_COMMAND_Flush: reset_node(this); break; default: return -ENOTSUP; } return 0; } static int impl_node_add_listener(void *object, struct spa_hook *listener, const struct spa_node_events *events, void *data) { struct impl *this = object; struct spa_hook_list save; spa_return_val_if_fail(this != NULL, -EINVAL); spa_log_trace(this->log, "%p: add listener %p", this, listener); spa_hook_list_isolate(&this->hooks, &save, listener, events, data); emit_info(this, true); spa_hook_list_join(&this->hooks, &save); return 0; } static int impl_node_set_callbacks(void *object, const struct spa_node_callbacks *callbacks, void *user_data) { return 0; } static int impl_node_add_port(void *object, enum spa_direction direction, uint32_t port_id, const struct spa_dict *props) { return -ENOTSUP; } static int impl_node_remove_port(void *object, enum spa_direction direction, uint32_t port_id) { return -ENOTSUP; } static int port_param_enum_formats(struct impl *impl, struct port *port, uint32_t id, uint32_t index, struct spa_pod **param, struct spa_pod_builder *b) { switch (index) { case 0: if (port->is_dsp) { struct spa_audio_info_dsp info; info.format = SPA_AUDIO_FORMAT_DSP_F32; *param = spa_format_audio_dsp_build(b, id, &info); } else if (port->is_control) { *param = spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_Format, id, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_application), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_control)); } else { struct spa_pod_frame f[1]; uint32_t rate = impl->io_position ? impl->io_position->clock.target_rate.denom : DEFAULT_RATE; spa_pod_builder_push_object(b, &f[0], SPA_TYPE_OBJECT_Format, id); spa_pod_builder_add(b, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_audio), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), SPA_FORMAT_AUDIO_format, SPA_POD_CHOICE_ENUM_Id(25, SPA_AUDIO_FORMAT_F32P, SPA_AUDIO_FORMAT_F32P, SPA_AUDIO_FORMAT_F32, SPA_AUDIO_FORMAT_F32_OE, SPA_AUDIO_FORMAT_F64P, SPA_AUDIO_FORMAT_F64, SPA_AUDIO_FORMAT_F64_OE, SPA_AUDIO_FORMAT_S32P, SPA_AUDIO_FORMAT_S32, SPA_AUDIO_FORMAT_S32_OE, SPA_AUDIO_FORMAT_S24_32P, SPA_AUDIO_FORMAT_S24_32, SPA_AUDIO_FORMAT_S24_32_OE, SPA_AUDIO_FORMAT_S24P, SPA_AUDIO_FORMAT_S24, SPA_AUDIO_FORMAT_S24_OE, SPA_AUDIO_FORMAT_S16P, SPA_AUDIO_FORMAT_S16, SPA_AUDIO_FORMAT_S16_OE, SPA_AUDIO_FORMAT_S8P, SPA_AUDIO_FORMAT_S8, SPA_AUDIO_FORMAT_U8P, SPA_AUDIO_FORMAT_U8, SPA_AUDIO_FORMAT_ULAW, SPA_AUDIO_FORMAT_ALAW), 0); if (!impl->props.resample_disabled) { spa_pod_builder_add(b, SPA_FORMAT_AUDIO_rate, SPA_POD_CHOICE_RANGE_Int( rate, 1, INT32_MAX), 0); } spa_pod_builder_add(b, SPA_FORMAT_AUDIO_channels, SPA_POD_CHOICE_RANGE_Int( DEFAULT_CHANNELS, 1, MAX_CHANNELS), 0); *param = spa_pod_builder_pop(b, &f[0]); } break; default: return 0; } return 1; } static int port_param_format(struct impl *impl, struct port *port, uint32_t id, uint32_t index, struct spa_pod **param, struct spa_pod_builder *b) { if (!port->have_format) return -EIO; if (index > 0) return 0; if (port->is_dsp) *param = spa_format_audio_dsp_build(b, id, &port->format.info.dsp); else if (port->is_control) *param = spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_Format, id, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_application), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_control)); else *param = spa_format_audio_raw_build(b, id, &port->format.info.raw); return 1; } static int port_param_buffers(struct impl *impl, struct port *port, uint32_t id, uint32_t index, struct spa_pod **param, struct spa_pod_builder *b) { uint32_t size; if (!port->have_format) return -EIO; if (index > 0) return 0; size = impl->quantum_limit; if (!port->is_dsp) { uint32_t irate, orate; struct dir *dir = &impl->dir[port->direction]; /* Convert ports are scaled so that they can always * provide one quantum of data. irate is the rate of the * data before it goes into the resampler. */ irate = dir->format.info.raw.rate; /* scale the size for adaptive resampling */ size += size/2; /* collect the other port rate. This is the output of the resampler * and is usually one quantum. */ dir = &impl->dir[SPA_DIRECTION_REVERSE(port->direction)]; if (dir->mode == SPA_PARAM_PORT_CONFIG_MODE_dsp) orate = impl->io_position ? impl->io_position->clock.target_rate.denom : DEFAULT_RATE; else orate = dir->format.info.raw.rate; /* scale the buffer size when we can. Only do this when we downsample because * then we need to ask more input data for one quantum. */ if (irate != 0 && orate != 0 && irate > orate) size = SPA_SCALE32_UP(size, irate, orate); } *param = spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_ParamBuffers, id, SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(1, 1, MAX_BUFFERS), SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(port->blocks), SPA_PARAM_BUFFERS_size, SPA_POD_CHOICE_RANGE_Int( size * port->stride, 16 * port->stride, INT32_MAX), SPA_PARAM_BUFFERS_stride, SPA_POD_Int(port->stride)); return 1; } static int port_param_meta(struct impl *impl, struct port *port, uint32_t id, uint32_t index, struct spa_pod **param, struct spa_pod_builder *b) { switch (index) { case 0: *param = spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_ParamMeta, id, SPA_PARAM_META_type, SPA_POD_Id(SPA_META_Header), SPA_PARAM_META_size, SPA_POD_Int(sizeof(struct spa_meta_header))); break; default: return 0; } return 1; } static int port_param_io(struct impl *impl, struct port *port, uint32_t id, uint32_t index, struct spa_pod **param, struct spa_pod_builder *b) { switch (index) { case 0: *param = spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_ParamIO, id, SPA_PARAM_IO_id, SPA_POD_Id(SPA_IO_Buffers), SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_buffers))); break; default: return 0; } return 1; } static int port_param_latency(struct impl *impl, struct port *port, uint32_t id, uint32_t index, struct spa_pod **param, struct spa_pod_builder *b) { switch (index) { case 0 ... 1: *param = spa_latency_build(b, id, &port->latency[index]); break; default: return 0; } return 1; } static int port_param_tag(struct impl *impl, struct port *port, uint32_t id, uint32_t index, struct spa_pod **param, struct spa_pod_builder *b) { switch (index) { case 0 ... 1: if (port->is_monitor) index = index ^ 1; *param = impl->dir[index].tag; break; default: return 0; } return 1; } static int impl_node_port_enum_params(void *object, int seq, enum spa_direction direction, uint32_t port_id, uint32_t id, uint32_t start, uint32_t num, const struct spa_pod *filter) { struct impl *this = object; struct port *port; struct spa_pod *param; struct spa_pod_builder b = { 0 }; uint8_t buffer[4096]; struct spa_result_node_params result; uint32_t count = 0; int res; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(num != 0, -EINVAL); spa_log_debug(this->log, "%p: enum params port %d.%d %d %u", this, direction, port_id, seq, id); spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); port = GET_PORT(this, direction, port_id); result.id = id; result.next = start; next: result.index = result.next++; spa_pod_builder_init(&b, buffer, sizeof(buffer)); param = NULL; switch (id) { case SPA_PARAM_EnumFormat: res = port_param_enum_formats(this, port, id, result.index, ¶m, &b); break; case SPA_PARAM_Format: res = port_param_format(this, port, id, result.index, ¶m, &b); break; case SPA_PARAM_Buffers: res = port_param_buffers(this, port, id, result.index, ¶m, &b); break; case SPA_PARAM_Meta: res = port_param_meta(this, port, id, result.index, ¶m, &b); break; case SPA_PARAM_IO: res = port_param_io(this, port, id, result.index, ¶m, &b); break; case SPA_PARAM_Latency: res = port_param_latency(this, port, id, result.index, ¶m, &b); break; case SPA_PARAM_Tag: res = port_param_tag(this, port, id, result.index, ¶m, &b); break; default: return -ENOENT; } if (res <= 0) return res; if (param == NULL || spa_pod_filter(&b, &result.param, param, filter) < 0) goto next; spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); if (++count != num) goto next; return 0; } static int clear_buffers(struct impl *this, struct port *port) { uint32_t i, j; spa_log_debug(this->log, "%p: clear buffers %p %d", this, port, port->n_buffers); for (i = 0; i < port->n_buffers; i++) { struct buffer *b = &port->buffers[i]; if (SPA_FLAG_IS_SET(b->flags, BUFFER_FLAG_MAPPED)) { for (j = 0; j < b->buf->n_datas; j++) { if (b->datas[j]) { spa_log_debug(this->log, "%p: unmap buffer %d data %d %p", this, i, j, b->datas[j]); munmap(b->datas[j], b->buf->datas[j].maxsize); b->datas[j] = NULL; } } SPA_FLAG_CLEAR(b->flags, BUFFER_FLAG_MAPPED); } } port->n_buffers = 0; spa_list_init(&port->queue); return 0; } static int port_set_latency(void *object, enum spa_direction direction, uint32_t port_id, uint32_t flags, const struct spa_pod *latency) { struct impl *this = object; struct port *port, *oport; enum spa_direction other = SPA_DIRECTION_REVERSE(direction); struct spa_latency_info info; bool have_latency;; spa_log_debug(this->log, "%p: set latency direction:%d id:%d %p", this, direction, port_id, latency); port = GET_PORT(this, direction, port_id); if (latency == NULL) { info = SPA_LATENCY_INFO(other); have_latency = false; } else { if (spa_latency_parse(latency, &info) < 0 || info.direction != other) return -EINVAL; have_latency = true; } port_update_latency(port, &info, have_latency); spa_log_debug(this->log, "%p: set %s latency %f-%f %d-%d %"PRIu64"-%"PRIu64, this, info.direction == SPA_DIRECTION_INPUT ? "input" : "output", info.min_quantum, info.max_quantum, info.min_rate, info.max_rate, info.min_ns, info.max_ns); if (this->monitor_passthrough) { if (port->is_monitor) oport = GET_PORT(this, other, port_id-1); else if (this->monitor && direction == SPA_DIRECTION_INPUT) oport = GET_PORT(this, other, port_id+1); else return 0; if (oport != NULL) port_update_latency(oport, &info, have_latency); } recalc_latencies(this, direction); return 0; } static int port_set_tag(void *object, enum spa_direction direction, uint32_t port_id, uint32_t flags, const struct spa_pod *tag) { struct impl *this = object; struct port *port, *oport; enum spa_direction other = SPA_DIRECTION_REVERSE(direction); uint32_t i; spa_log_debug(this->log, "%p: set tag direction:%d id:%d %p", this, direction, port_id, tag); port = GET_PORT(this, direction, port_id); if (port->is_monitor && !this->monitor_passthrough) return 0; if (tag != NULL) { struct spa_tag_info info; void *state = NULL; if (spa_tag_parse(tag, &info, &state) < 0 || info.direction != other) return -EINVAL; } if (spa_tag_compare(tag, this->dir[other].tag) != 0) { free(this->dir[other].tag); this->dir[other].tag = tag ? spa_pod_copy(tag) : NULL; for (i = 0; i < this->dir[other].n_ports; i++) { oport = GET_PORT(this, other, i); oport->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; oport->params[IDX_Tag].user++; } } port->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; port->params[IDX_Tag].user++; return 0; } static int port_set_format(void *object, enum spa_direction direction, uint32_t port_id, uint32_t flags, const struct spa_pod *format) { struct impl *this = object; struct port *port; int res; port = GET_PORT(this, direction, port_id); spa_log_debug(this->log, "%p: %d:%d set format", this, direction, port_id); if (format == NULL) { port->have_format = false; clear_buffers(this, port); } else { struct spa_audio_info info = { 0 }; if ((res = spa_format_parse(format, &info.media_type, &info.media_subtype)) < 0) { spa_log_error(this->log, "can't parse format %s", spa_strerror(res)); return res; } if (PORT_IS_DSP(this, direction, port_id)) { if (info.media_type != SPA_MEDIA_TYPE_audio || info.media_subtype != SPA_MEDIA_SUBTYPE_dsp) { spa_log_error(this->log, "unexpected types %d/%d", info.media_type, info.media_subtype); return -EINVAL; } if ((res = spa_format_audio_dsp_parse(format, &info.info.dsp)) < 0) { spa_log_error(this->log, "can't parse format %s", spa_strerror(res)); return res; } if (info.info.dsp.format != SPA_AUDIO_FORMAT_DSP_F32) { spa_log_error(this->log, "unexpected format %d<->%d", info.info.dsp.format, SPA_AUDIO_FORMAT_DSP_F32); return -EINVAL; } port->blocks = 1; port->stride = 4; } else if (PORT_IS_CONTROL(this, direction, port_id)) { if (info.media_type != SPA_MEDIA_TYPE_application || info.media_subtype != SPA_MEDIA_SUBTYPE_control) { spa_log_error(this->log, "unexpected types %d/%d", info.media_type, info.media_subtype); return -EINVAL; } port->blocks = 1; port->stride = 1; } else { if (info.media_type != SPA_MEDIA_TYPE_audio || info.media_subtype != SPA_MEDIA_SUBTYPE_raw) { spa_log_error(this->log, "unexpected types %d/%d", info.media_type, info.media_subtype); return -EINVAL; } if ((res = spa_format_audio_raw_parse(format, &info.info.raw)) < 0) { spa_log_error(this->log, "can't parse format %s", spa_strerror(res)); return res; } if (info.info.raw.format == 0 || (!this->props.resample_disabled && info.info.raw.rate == 0) || info.info.raw.channels == 0 || info.info.raw.channels > MAX_CHANNELS) { spa_log_error(this->log, "invalid format:%d rate:%d channels:%d", info.info.raw.format, info.info.raw.rate, info.info.raw.channels); return -EINVAL; } port->stride = calc_width(&info); if (SPA_AUDIO_FORMAT_IS_PLANAR(info.info.raw.format)) { port->blocks = info.info.raw.channels; } else { port->stride *= info.info.raw.channels; port->blocks = 1; } this->dir[direction].format = info; this->dir[direction].have_format = true; this->setup = false; } port->format = info; port->have_format = true; spa_log_debug(this->log, "%p: %d %d %d", this, port_id, port->stride, port->blocks); } port->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; if (port->have_format) { port->params[IDX_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_READWRITE); port->params[IDX_Buffers] = SPA_PARAM_INFO(SPA_PARAM_Buffers, SPA_PARAM_INFO_READ); } else { port->params[IDX_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); port->params[IDX_Buffers] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); } return 0; } static int impl_node_port_set_param(void *object, enum spa_direction direction, uint32_t port_id, uint32_t id, uint32_t flags, const struct spa_pod *param) { struct impl *this = object; int res = 0; spa_return_val_if_fail(this != NULL, -EINVAL); spa_log_debug(this->log, "%p: set param port %d.%d %u", this, direction, port_id, id); spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); switch (id) { case SPA_PARAM_Latency: res = port_set_latency(this, direction, port_id, flags, param); break; case SPA_PARAM_Tag: res = port_set_tag(this, direction, port_id, flags, param); break; case SPA_PARAM_Format: res = port_set_format(this, direction, port_id, flags, param); break; default: return -ENOENT; } emit_info(this, false); return res; } static inline void queue_buffer(struct impl *this, struct port *port, uint32_t id) { struct buffer *b = &port->buffers[id]; spa_log_trace_fp(this->log, "%p: queue buffer %d on port %d %d", this, id, port->id, b->flags); if (SPA_FLAG_IS_SET(b->flags, BUFFER_FLAG_QUEUED)) return; spa_list_append(&port->queue, &b->link); SPA_FLAG_SET(b->flags, BUFFER_FLAG_QUEUED); } static inline struct buffer *peek_buffer(struct impl *this, struct port *port) { struct buffer *b; if (spa_list_is_empty(&port->queue)) return NULL; b = spa_list_first(&port->queue, struct buffer, link); spa_log_trace_fp(this->log, "%p: peek buffer %d/%d on port %d %u", this, b->id, port->n_buffers, port->id, b->flags); return b; } static inline void dequeue_buffer(struct impl *this, struct port *port, struct buffer *b) { spa_log_trace_fp(this->log, "%p: dequeue buffer %d on port %d %u", this, b->id, port->id, b->flags); if (!SPA_FLAG_IS_SET(b->flags, BUFFER_FLAG_QUEUED)) return; spa_list_remove(&b->link); SPA_FLAG_CLEAR(b->flags, BUFFER_FLAG_QUEUED); } static int impl_node_port_use_buffers(void *object, enum spa_direction direction, uint32_t port_id, uint32_t flags, struct spa_buffer **buffers, uint32_t n_buffers) { struct impl *this = object; struct port *port; uint32_t i, j, maxsize; int res; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); port = GET_PORT(this, direction, port_id); spa_log_debug(this->log, "%p: use buffers %d on port %d:%d", this, n_buffers, direction, port_id); if (n_buffers > 0 && !port->have_format) { res = -EIO; goto error; } if (n_buffers > MAX_BUFFERS) { res = -ENOSPC; goto error; } clear_buffers(this, port); maxsize = this->quantum_limit * sizeof(float); for (i = 0; i < n_buffers; i++) { struct buffer *b; uint32_t n_datas = buffers[i]->n_datas; struct spa_data *d = buffers[i]->datas; if (n_datas > MAX_DATAS) { res = -ENOSPC; goto error; } b = &port->buffers[i]; b->id = i; b->flags = 0; b->buf = buffers[i]; if (n_datas != port->blocks) { spa_log_error(this->log, "%p: invalid blocks %d on buffer %d", this, n_datas, i); return -EINVAL; } for (j = 0; j < n_datas; j++) { void *data = d[j].data; if (data == NULL && SPA_FLAG_IS_SET(d[j].flags, SPA_DATA_FLAG_MAPPABLE)) { int prot = 0; if (SPA_FLAG_IS_SET(d[j].flags, SPA_DATA_FLAG_READABLE)) prot |= PROT_READ; if (SPA_FLAG_IS_SET(d[j].flags, SPA_DATA_FLAG_WRITABLE)) prot |= PROT_WRITE; data = mmap(NULL, d[j].maxsize, prot, MAP_SHARED, d[j].fd, d[j].mapoffset); if (data == MAP_FAILED) { spa_log_error(this->log, "%p: mmap failed %d on buffer %d %d %p: %m", this, j, i, d[j].type, data); res = -EINVAL; goto error; } SPA_FLAG_SET(b->flags, BUFFER_FLAG_MAPPED); spa_log_debug(this->log, "%p: mmap %d on buffer %d %d %p %p", this, j, i, d[j].type, data, b); } if (data == NULL) { spa_log_error(this->log, "%p: invalid memory %d on buffer %d %d %p", this, j, i, d[j].type, data); res = -EINVAL; goto error; } else if (!SPA_IS_ALIGNED(data, this->max_align)) { spa_log_warn(this->log, "%p: memory %d on buffer %d not aligned", this, j, i); } b->datas[j] = data; maxsize = SPA_MAX(maxsize, d[j].maxsize); } if (direction == SPA_DIRECTION_OUTPUT) queue_buffer(this, port, i); port->n_buffers++; } port->maxsize = maxsize; return 0; error: clear_buffers(this, port); return res; } struct io_data { struct port *port; void *data; size_t size; }; static int do_set_port_io(struct spa_loop *loop, bool async, uint32_t seq, const void *data, size_t size, void *user_data) { const struct io_data *d = user_data; d->port->io = d->data; return 0; } static int impl_node_port_set_io(void *object, enum spa_direction direction, uint32_t port_id, uint32_t id, void *data, size_t size) { struct impl *this = object; struct port *port; spa_return_val_if_fail(this != NULL, -EINVAL); spa_log_debug(this->log, "%p: set io %d on port %d:%d %p", this, id, direction, port_id, data); spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); port = GET_PORT(this, direction, port_id); switch (id) { case SPA_IO_Buffers: if (this->data_loop) { struct io_data d = { .port = port, .data = data, .size = size }; spa_loop_locked(this->data_loop, do_set_port_io, 0, NULL, 0, &d); } else port->io = data; break; case SPA_IO_RateMatch: this->io_rate_match = data; break; default: return -ENOENT; } return 0; } static int impl_node_port_reuse_buffer(void *object, uint32_t port_id, uint32_t buffer_id) { struct impl *this = object; struct port *port; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(CHECK_PORT(this, SPA_DIRECTION_OUTPUT, port_id), -EINVAL); port = GET_OUT_PORT(this, port_id); queue_buffer(this, port, buffer_id); return 0; } static int channelmix_process_apply_sequence(struct impl *this, const struct spa_pod_sequence *sequence, uint32_t *processed_offset, void *SPA_RESTRICT dst[], const void *SPA_RESTRICT src[], uint32_t n_samples) { struct spa_pod_control *c, *prev = NULL; uint32_t avail_samples = n_samples; uint32_t i; const float *s[MAX_PORTS], **ss = (const float**) src; float *d[MAX_PORTS], **sd = (float **) dst; const struct spa_pod_sequence_body *body = &(sequence)->body; uint32_t size = SPA_POD_BODY_SIZE(sequence); bool end = false; c = spa_pod_control_first(body); while (true) { uint32_t chunk; if (c == NULL || !spa_pod_control_is_inside(body, size, c)) { c = NULL; end = true; } if (avail_samples == 0) break; /* ignore old control offsets */ if (c != NULL) { if (c->offset <= *processed_offset) { prev = c; if (c != NULL) c = spa_pod_control_next(c); continue; } chunk = SPA_MIN(avail_samples, c->offset - *processed_offset); spa_log_trace_fp(this->log, "%p: process %d-%d %d/%d", this, *processed_offset, c->offset, chunk, avail_samples); } else { chunk = avail_samples; spa_log_trace_fp(this->log, "%p: process remain %d", this, chunk); } if (prev) { switch (prev->type) { case SPA_CONTROL_UMP: apply_midi(this, &prev->value); break; case SPA_CONTROL_Properties: apply_props(this, &prev->value); break; default: continue; } } if (ss == (const float**)src && chunk != avail_samples) { for (i = 0; i < this->mix.src_chan; i++) s[i] = ss[i]; for (i = 0; i < this->mix.dst_chan; i++) d[i] = sd[i]; ss = s; sd = d; } channelmix_process(&this->mix, (void**)sd, (const void**)ss, chunk); if (chunk != avail_samples) { for (i = 0; i < this->mix.src_chan; i++) ss[i] += chunk; for (i = 0; i < this->mix.dst_chan; i++) sd[i] += chunk; } avail_samples -= chunk; *processed_offset += chunk; } return end ? 1 : 0; } static inline uint32_t resample_get_in_size(struct impl *this, bool passthrough, uint32_t out_size) { uint32_t match_size = passthrough ? out_size : resample_in_len(&this->resample, out_size); spa_log_trace_fp(this->log, "%p: current match %u", this, match_size); return match_size; } static uint64_t get_time_ns(struct impl *impl) { struct timespec now; if (clock_gettime(CLOCK_MONOTONIC, &now) < 0) return 0; return SPA_TIMESPEC_TO_NSEC(&now); } static uint32_t get_dst_idx(struct stage_context *ctx) { uint32_t res; if (ctx->bits == 0) res = ctx->final_idx; else res = CTX_DATA_TMP_0 + ((ctx->tmp++) & 1); return res; } static void run_wav_stage(struct stage *stage, struct stage_context *c) { struct impl *impl = stage->impl; const void **src = (const void **)c->datas[stage->in_idx]; if (SPA_UNLIKELY(impl->props.wav_path[0])) { if (impl->wav_file == NULL) { struct wav_file_info info; info.info = impl->dir[impl->direction].format; impl->wav_file = wav_file_open(impl->props.wav_path, "w", &info); if (impl->wav_file == NULL) spa_log_warn(impl->log, "can't open wav path: %m"); } if (impl->wav_file) { wav_file_write(impl->wav_file, src, c->n_samples); } else { spa_zero(impl->props.wav_path); } } else if (impl->wav_file != NULL) { wav_file_close(impl->wav_file); impl->wav_file = NULL; impl->recalc = true; } } static void add_wav_stage(struct impl *impl, struct stage_context *ctx) { struct stage *s = &impl->stages[impl->n_stages]; s->impl = impl; s->in_idx = ctx->src_idx; s->out_idx = ctx->src_idx; s->data = NULL; s->run = run_wav_stage; spa_log_trace(impl->log, "%p: stage %d", impl, impl->n_stages); impl->n_stages++; } static void run_dst_remap_stage(struct stage *s, struct stage_context *c) { struct impl *impl = s->impl; struct dir *dir = &impl->dir[SPA_DIRECTION_OUTPUT]; uint32_t i; for (i = 0; i < dir->conv.n_channels; i++) { c->datas[s->out_idx][i] = c->datas[s->in_idx][dir->remap[i]]; spa_log_trace_fp(impl->log, "%p: output remap %d -> %d", impl, i, dir->remap[i]); } } static void add_dst_remap_stage(struct impl *impl, struct stage_context *ctx) { struct stage *s = &impl->stages[impl->n_stages]; s->impl = impl; s->in_idx = ctx->dst_idx; s->out_idx = CTX_DATA_REMAP_DST; s->data = NULL; s->run = run_dst_remap_stage; spa_log_trace(impl->log, "%p: stage %d", impl, impl->n_stages); impl->n_stages++; ctx->dst_idx = CTX_DATA_REMAP_DST; ctx->final_idx = CTX_DATA_REMAP_DST; } static void run_src_remap_stage(struct stage *s, struct stage_context *c) { struct impl *impl = s->impl; struct dir *dir = &impl->dir[SPA_DIRECTION_INPUT]; uint32_t i; for (i = 0; i < dir->conv.n_channels; i++) { c->datas[s->out_idx][dir->remap[i]] = c->datas[s->in_idx][i]; spa_log_trace_fp(impl->log, "%p: input remap %d -> %d", impl, dir->remap[i], i); } } static void add_src_remap_stage(struct impl *impl, struct stage_context *ctx) { struct stage *s = &impl->stages[impl->n_stages]; s->impl = impl; s->in_idx = ctx->src_idx; s->out_idx = CTX_DATA_REMAP_SRC; s->data = NULL; s->run = run_src_remap_stage; spa_log_trace(impl->log, "%p: stage %d", impl, impl->n_stages); impl->n_stages++; ctx->src_idx = CTX_DATA_REMAP_SRC; } static void run_src_convert_stage(struct stage *s, struct stage_context *c) { struct impl *impl = s->impl; struct dir *dir = &impl->dir[SPA_DIRECTION_INPUT]; void *remap_src_datas[MAX_PORTS], **dst; spa_log_trace_fp(impl->log, "%p: input convert %d", impl, c->n_samples); if (dir->need_remap) { uint32_t i; for (i = 0; i < dir->conv.n_channels; i++) { remap_src_datas[i] = c->datas[s->out_idx][dir->remap[i]]; spa_log_trace_fp(impl->log, "%p: input remap %d -> %d", impl, dir->remap[i], i); } dst = remap_src_datas; } else { dst = c->datas[s->out_idx]; } if (c->empty && dir->conv.clear) convert_clear(&dir->conv, dst, c->n_samples); else convert_process(&dir->conv, dst, (const void**)c->datas[s->in_idx], c->n_samples); } static void add_src_convert_stage(struct impl *impl, struct stage_context *ctx) { struct stage *s = &impl->stages[impl->n_stages]; SPA_FLAG_CLEAR(ctx->bits, SRC_CONVERT_BIT); s->impl = impl; s->in_idx = ctx->src_idx; s->out_idx = get_dst_idx(ctx); s->data = NULL; s->run = run_src_convert_stage; spa_log_trace(impl->log, "%p: stage %d", impl, impl->n_stages); impl->n_stages++; ctx->src_idx = s->out_idx; } static void run_resample_stage(struct stage *s, struct stage_context *c) { struct impl *impl = s->impl; uint32_t in_len = c->n_samples; uint32_t out_len = c->n_out; resample_process(&impl->resample, (const void**)c->datas[s->in_idx], &in_len, c->datas[s->out_idx], &out_len); spa_log_trace_fp(impl->log, "%p: resample %d/%d -> %d/%d", impl, c->n_samples, in_len, c->n_out, out_len); c->in_samples = in_len; c->n_samples = out_len; } static void add_resample_stage(struct impl *impl, struct stage_context *ctx) { struct stage *s = &impl->stages[impl->n_stages]; SPA_FLAG_CLEAR(ctx->bits, RESAMPLE_BIT); s->impl = impl; s->in_idx = ctx->src_idx; s->out_idx = get_dst_idx(ctx); s->data = NULL; s->run = run_resample_stage; spa_log_trace(impl->log, "%p: stage %d", impl, impl->n_stages); impl->n_stages++; ctx->src_idx = s->out_idx; } static void run_filter_stage(struct stage *s, struct stage_context *c) { struct filter_graph *fg = s->data; spa_log_trace_fp(s->impl->log, "%p: filter-graph %d", s->impl, c->n_samples); spa_filter_graph_process(fg->graph, (const void **)c->datas[s->in_idx], c->datas[s->out_idx], c->n_samples); } static void add_filter_stage(struct impl *impl, uint32_t i, struct filter_graph *fg, struct stage_context *ctx) { struct stage *s = &impl->stages[impl->n_stages]; s->impl = impl; s->in_idx = ctx->src_idx; s->out_idx = get_dst_idx(ctx); s->data = fg; s->run = run_filter_stage; spa_log_trace(impl->log, "%p: stage %d", impl, impl->n_stages); impl->n_stages++; ctx->src_idx = s->out_idx; } static void run_channelmix_stage(struct stage *s, struct stage_context *c) { struct impl *impl = s->impl; void **out_datas = c->datas[s->out_idx]; const void **in_datas = (const void**)c->datas[s->in_idx]; struct port *ctrlport = c->ctrlport; spa_log_trace_fp(impl->log, "%p: channelmix %d", impl, c->n_samples); if (ctrlport != NULL && ctrlport->ctrl != NULL) { if (channelmix_process_apply_sequence(impl, ctrlport->ctrl, &ctrlport->ctrl_offset, out_datas, in_datas, c->n_samples) == 1) { ctrlport->io->status = SPA_STATUS_OK; ctrlport->ctrl = NULL; } } else if (impl->vol_ramp_sequence) { if (channelmix_process_apply_sequence(impl, impl->vol_ramp_sequence, &impl->vol_ramp_offset, out_datas, in_datas, c->n_samples) == 1) { free(impl->vol_ramp_sequence_data); impl->vol_ramp_sequence_data = NULL; impl->vol_ramp_sequence = NULL; } } else { channelmix_process(&impl->mix, out_datas, in_datas, c->n_samples); } } static void add_channelmix_stage(struct impl *impl, struct stage_context *ctx) { struct stage *s = &impl->stages[impl->n_stages]; SPA_FLAG_CLEAR(ctx->bits, MIX_BIT); s->impl = impl; s->in_idx = ctx->src_idx; s->out_idx = get_dst_idx(ctx); s->data = NULL; s->run = run_channelmix_stage; spa_log_trace(impl->log, "%p: stage %d", impl, impl->n_stages); impl->n_stages++; ctx->src_idx = s->out_idx; } static void run_dst_convert_stage(struct stage *s, struct stage_context *c) { struct impl *impl = s->impl; struct dir *dir = &impl->dir[SPA_DIRECTION_OUTPUT]; void *remap_datas[MAX_PORTS], **src; spa_log_trace_fp(impl->log, "%p: output convert %d", impl, c->n_samples); if (dir->need_remap) { uint32_t i; for (i = 0; i < dir->conv.n_channels; i++) { remap_datas[dir->remap[i]] = c->datas[s->in_idx][i]; spa_log_trace_fp(impl->log, "%p: output remap %d -> %d", impl, i, dir->remap[i]); } src = remap_datas; } else { src = c->datas[s->in_idx]; } if (c->empty && dir->conv.clear) convert_clear(&dir->conv, c->datas[s->out_idx], c->n_samples); else convert_process(&dir->conv, c->datas[s->out_idx], (const void **)src, c->n_samples); } static void add_dst_convert_stage(struct impl *impl, struct stage_context *ctx) { struct stage *s = &impl->stages[impl->n_stages]; s->impl = impl; s->in_idx = ctx->src_idx; s->out_idx = ctx->final_idx; s->data = NULL; s->run = run_dst_convert_stage; spa_log_trace(impl->log, "%p: stage %d", impl, impl->n_stages); impl->n_stages++; ctx->src_idx = s->out_idx; } static void recalc_stages(struct impl *this, struct stage_context *ctx) { struct dir *dir; bool test, do_wav; struct port *ctrlport = ctx->ctrlport; bool in_need_remap, out_need_remap; uint32_t i; this->recalc = false; this->n_stages = 0; ctx->tmp = 0; ctx->bits = 0; ctx->src_idx = CTX_DATA_SRC; ctx->dst_idx = CTX_DATA_DST; ctx->final_idx = CTX_DATA_DST; /* set bits for things we need to do */ dir = &this->dir[SPA_DIRECTION_INPUT]; SPA_FLAG_UPDATE(ctx->bits, SRC_CONVERT_BIT, !dir->conv.is_passthrough); in_need_remap = dir->need_remap; dir = &this->dir[SPA_DIRECTION_OUTPUT]; SPA_FLAG_UPDATE(ctx->bits, DST_CONVERT_BIT, !dir->conv.is_passthrough); out_need_remap = dir->need_remap; this->resample_passthrough = resample_is_passthrough(this); SPA_FLAG_UPDATE(ctx->bits, RESAMPLE_BIT, !this->resample_passthrough); SPA_FLAG_UPDATE(ctx->bits, FILTER_BIT, this->n_graph != 0); test = SPA_FLAG_IS_SET(this->mix.flags, CHANNELMIX_FLAG_IDENTITY) && (ctrlport == NULL || ctrlport->ctrl == NULL) && (this->vol_ramp_sequence == NULL); SPA_FLAG_UPDATE(ctx->bits, MIX_BIT, !test); /* if we have nothing to do, force a conversion to the destination to make sure we * actually write something to the destination buffer */ if (ctx->bits == 0) SPA_FLAG_SET(ctx->bits, DST_CONVERT_BIT); do_wav = this->props.wav_path[0] || this->wav_file != NULL; if (!SPA_FLAG_IS_SET(ctx->bits, DST_CONVERT_BIT) && out_need_remap) add_dst_remap_stage(this, ctx); if (this->direction == SPA_DIRECTION_INPUT && do_wav) add_wav_stage(this, ctx); if (SPA_FLAG_IS_SET(ctx->bits, SRC_CONVERT_BIT)) { add_src_convert_stage(this, ctx); } else { if (in_need_remap) add_src_remap_stage(this, ctx); } if (this->direction == SPA_DIRECTION_INPUT) { if (SPA_FLAG_IS_SET(ctx->bits, RESAMPLE_BIT)) add_resample_stage(this, ctx); } if (SPA_FLAG_IS_SET(ctx->bits, FILTER_BIT)) { for (i = 0; i < this->n_graph; i++) { struct filter_graph *fg = this->filter_graph[i]; if (i + 1 == this->n_graph) SPA_FLAG_CLEAR(ctx->bits, FILTER_BIT); add_filter_stage(this, i, fg, ctx); } } if (SPA_FLAG_IS_SET(ctx->bits, MIX_BIT)) add_channelmix_stage(this, ctx); if (this->direction == SPA_DIRECTION_OUTPUT) { if (SPA_FLAG_IS_SET(ctx->bits, RESAMPLE_BIT)) add_resample_stage(this, ctx); } if (SPA_FLAG_IS_SET(ctx->bits, DST_CONVERT_BIT)) add_dst_convert_stage(this, ctx); if (this->direction == SPA_DIRECTION_OUTPUT && do_wav) add_wav_stage(this, ctx); spa_log_debug(this->log, "got %u processing stages", this->n_stages); } static int impl_node_process(void *object) { struct impl *this = object; const void *src_datas[MAX_PORTS]; void *dst_datas[MAX_PORTS], *remap_src_datas[MAX_PORTS], *remap_dst_datas[MAX_PORTS], *data; uint32_t i, j, n_src_datas = 0, n_dst_datas = 0, n_mon_datas = 0, remap; uint32_t n_samples, max_in, n_out, max_out, quant_samples; struct port *port, *ctrlport = NULL; struct buffer *buf, *out_bufs[MAX_PORTS]; struct spa_data *bd; struct dir *dir; int res = 0, suppressed; bool in_avail = false, flush_in = false, flush_out = false; bool draining = false, in_empty = this->out_offset == 0, out_empty; struct spa_io_buffers *io; const struct spa_pod_sequence *ctrl = NULL; uint64_t current_time; struct stage_context ctx; /* calculate quantum scale, this is how many samples we need to produce or * consume. Also update the rate scale, this is sent to the resampler to adjust * the rate, either when the graph clock changed or when the user adjusted the * rate. */ if (SPA_LIKELY(this->io_position)) { double r = this->rate_scale; current_time = this->io_position->clock.nsec; quant_samples = this->io_position->clock.duration; if (this->direction == SPA_DIRECTION_INPUT) { if (this->io_position->clock.rate.denom != this->resample.o_rate) r = (double) this->io_position->clock.rate.denom / this->resample.o_rate; else r = 1.0; } else { if (this->io_position->clock.rate.denom != this->resample.i_rate) r = (double) this->resample.i_rate / this->io_position->clock.rate.denom; else r = 1.0; } if (this->rate_scale != r) { spa_log_info(this->log, "scale graph:%u in:%u out:%u scale:%f->%f", this->io_position->clock.rate.denom, this->resample.i_rate, this->resample.o_rate, this->rate_scale, r); this->rate_scale = r; } } else { current_time = get_time_ns(this); quant_samples = this->quantum_limit; } dir = &this->dir[SPA_DIRECTION_INPUT]; max_in = UINT32_MAX; /* collect input port data */ for (i = 0; i < dir->n_ports; i++) { port = GET_IN_PORT(this, i); if (SPA_UNLIKELY((io = port->io) == NULL)) { spa_log_trace_fp(this->log, "%p: no io on input port %d", this, port->id); buf = NULL; } else if (SPA_UNLIKELY(io->status != SPA_STATUS_HAVE_DATA)) { if (io->status & SPA_STATUS_DRAINED) { spa_log_debug(this->log, "%p: port %d drained", this, port->id); in_avail = flush_in = draining = true; in_empty = false; } else { spa_log_trace_fp(this->log, "%p: empty input port %d %p %d %d %d", this, port->id, io, io->status, io->buffer_id, port->n_buffers); this->drained = false; } buf = NULL; } else if (SPA_UNLIKELY(io->buffer_id >= port->n_buffers)) { spa_log_trace_fp(this->log, "%p: invalid input buffer port %d %p %d %d %d", this, port->id, io, io->status, io->buffer_id, port->n_buffers); io->status = -EINVAL; buf = NULL; } else { spa_log_trace_fp(this->log, "%p: input buffer port %d io:%p status:%d id:%d n:%d", this, port->id, io, io->status, io->buffer_id, port->n_buffers); buf = &port->buffers[io->buffer_id]; } if (SPA_UNLIKELY(buf == NULL)) { for (j = 0; j < port->blocks; j++) { if (port->is_control) { spa_log_trace_fp(this->log, "%p: empty control %d", this, i * port->blocks + j); } else { remap = n_src_datas++; src_datas[remap] = SPA_PTR_ALIGN(this->empty, MAX_ALIGN, void); spa_log_trace_fp(this->log, "%p: empty input %d->%d", this, i * port->blocks + j, remap); max_in = SPA_MIN(max_in, this->scratch_size / port->stride); } } } else { in_avail = true; for (j = 0; j < port->blocks; j++) { uint32_t offs, size; bd = &buf->buf->datas[j]; data = bd->data ? bd->data : buf->datas[j]; offs = SPA_MIN(bd->chunk->offset, bd->maxsize); size = SPA_MIN(bd->maxsize - offs, bd->chunk->size); if (!SPA_FLAG_IS_SET(bd->chunk->flags, SPA_CHUNK_FLAG_EMPTY)) in_empty = false; if (SPA_UNLIKELY(port->is_control)) { spa_log_trace_fp(this->log, "%p: control %d", this, i * port->blocks + j); ctrlport = port; ctrl = spa_pod_from_data(data, bd->maxsize, bd->chunk->offset, bd->chunk->size); if (ctrl && !spa_pod_is_sequence(&ctrl->pod)) ctrl = NULL; if (ctrl != ctrlport->ctrl) { ctrlport->ctrl = ctrl; ctrlport->ctrl_offset = 0; this->recalc = true; } } else { max_in = SPA_MIN(max_in, size / port->stride); remap = n_src_datas++; offs += this->in_offset * port->stride; src_datas[remap] = SPA_PTROFF(data, offs, void); spa_log_trace_fp(this->log, "%p: input %d:%d:%d %d %d %d->%d", this, offs, size, port->stride, this->in_offset, max_in, i * port->blocks + j, remap); } } } } bool resample_passthrough = resample_is_passthrough(this); if (this->resample_passthrough != resample_passthrough) this->recalc = true; /* calculate how many samples we are going to produce. */ if (this->direction == SPA_DIRECTION_INPUT) { /* in split mode we need to output exactly the size of the * duration so we don't try to flush early */ max_out = quant_samples; if (!in_avail || this->drained) { n_out = max_out - SPA_MIN(max_out, this->out_offset); /* no input, ask for more, update rate-match first */ resample_update_rate_match(this, resample_passthrough, n_out, 0); spa_log_trace_fp(this->log, "%p: no input drained:%d", this, this->drained); res |= this->drained ? SPA_STATUS_DRAINED : SPA_STATUS_NEED_DATA; return res; } flush_out = false; } else { /* in merge mode we consume one duration of samples and * always output the resulting data */ max_out = this->quantum_limit; flush_out = true; } dir = &this->dir[SPA_DIRECTION_OUTPUT]; /* collect output ports and monitor ports data */ for (i = 0; i < dir->n_ports; i++) { port = GET_OUT_PORT(this, i); if (SPA_UNLIKELY((io = port->io) == NULL || io->status == SPA_STATUS_HAVE_DATA)) { buf = NULL; } else { if (SPA_LIKELY(io->buffer_id < port->n_buffers)) queue_buffer(this, port, io->buffer_id); buf = peek_buffer(this, port); if (buf == NULL && port->n_buffers > 0 && (suppressed = spa_ratelimit_test(&this->rate_limit, current_time)) >= 0) { spa_log_warn(this->log, "%p: (%d suppressed) out of buffers on port %d %d", this, suppressed, port->id, port->n_buffers); } } out_bufs[i] = buf; if (SPA_UNLIKELY(buf == NULL)) { for (j = 0; j < port->blocks; j++) { if (port->is_monitor) { remap = n_mon_datas++; spa_log_trace_fp(this->log, "%p: empty monitor %d", this, remap); } else if (port->is_control) { spa_log_trace_fp(this->log, "%p: empty control %d", this, j); } else { remap = n_dst_datas++; dst_datas[remap] = SPA_PTR_ALIGN(this->scratch, MAX_ALIGN, void); spa_log_trace_fp(this->log, "%p: empty output %d->%d", this, i * port->blocks + j, remap); max_out = SPA_MIN(max_out, this->scratch_size / port->stride); } } } else { for (j = 0; j < port->blocks; j++) { bd = &buf->buf->datas[j]; data = bd->data ? bd->data : buf->datas[j]; bd->chunk->offset = 0; bd->chunk->size = 0; if (port->is_monitor) { float volume; uint32_t mon_max; remap = n_mon_datas++; volume = this->props.monitor.mute ? 0.0f : this->props.monitor.volumes[remap]; if (this->monitor_channel_volumes) volume *= this->props.channel.mute ? 0.0f : this->props.channel.volumes[remap]; volume = SPA_CLAMPF(volume, this->props.min_volume, this->props.max_volume); mon_max = SPA_MIN(bd->maxsize / port->stride, max_in); volume_process(&this->volume, data, src_datas[remap], volume, mon_max); bd->chunk->size = mon_max * port->stride; bd->chunk->stride = port->stride; spa_log_trace_fp(this->log, "%p: monitor %d %d", this, remap, mon_max); dequeue_buffer(this, port, buf); io->status = SPA_STATUS_HAVE_DATA; io->buffer_id = buf->id; res |= SPA_STATUS_HAVE_DATA; } else if (SPA_UNLIKELY(port->is_control)) { spa_log_trace_fp(this->log, "%p: control %d", this, j); } else { remap = n_dst_datas++; dst_datas[remap] = SPA_PTROFF(data, this->out_offset * port->stride, void); max_out = SPA_MIN(max_out, bd->maxsize / port->stride); spa_log_trace_fp(this->log, "%p: output %d offs:%d %d->%d", this, max_out, this->out_offset, i * port->blocks + j, remap); } } } } /* calculate how many samples at most we are going to consume. If we're * draining, we consume as much as we can. Otherwise we consume what is * left. */ if (SPA_UNLIKELY(draining)) n_samples = SPA_MIN(max_in, this->quantum_limit); else { n_samples = max_in - SPA_MIN(max_in, this->in_offset); } /* we only need to output the remaining samples */ n_out = max_out - SPA_MIN(max_out, this->out_offset); /* calculate how many samples we are going to consume. */ if (this->direction == SPA_DIRECTION_INPUT) { /* figure out how much input samples we need to consume */ n_samples = SPA_MIN(n_samples, resample_get_in_size(this, resample_passthrough, n_out)); } else { /* in merge mode we consume one duration of samples */ n_samples = SPA_MIN(n_samples, quant_samples); flush_in = true; } ctx.datas[CTX_DATA_SRC] = (void **)src_datas; ctx.datas[CTX_DATA_DST] = dst_datas; ctx.datas[CTX_DATA_REMAP_DST] = remap_dst_datas; ctx.datas[CTX_DATA_REMAP_SRC] = remap_src_datas; ctx.datas[CTX_DATA_TMP_0] = (void**)this->tmp_datas[0]; ctx.datas[CTX_DATA_TMP_1] = (void**)this->tmp_datas[1]; ctx.in_samples = n_samples; ctx.n_samples = n_samples; ctx.n_out = n_out; ctx.ctrlport = ctrlport; ctx.empty = in_empty; if (SPA_UNLIKELY(this->recalc)) recalc_stages(this, &ctx); for (i = 0; i < this->n_stages; i++) { struct stage *s = &this->stages[i]; s->run(s, &ctx); } this->in_offset += ctx.in_samples; this->out_offset += ctx.n_samples; out_empty = ctx.empty; spa_log_trace_fp(this->log, "%d/%d %d/%d %d->%d", this->in_offset, max_in, this->out_offset, max_out, n_samples, n_out); dir = &this->dir[SPA_DIRECTION_INPUT]; if (SPA_LIKELY(this->in_offset >= max_in || flush_in)) { /* return input buffers */ for (i = 0; i < dir->n_ports; i++) { port = GET_IN_PORT(this, i); if (port->is_control) continue; if (SPA_UNLIKELY((io = port->io) == NULL)) continue; spa_log_trace_fp(this->log, "return: input %d %d", port->id, io->buffer_id); if (!draining) io->status = SPA_STATUS_NEED_DATA; } this->in_offset = 0; max_in = 0; res |= SPA_STATUS_NEED_DATA; } dir = &this->dir[SPA_DIRECTION_OUTPUT]; if (SPA_LIKELY(this->out_offset > 0 && (this->out_offset >= max_out || flush_out))) { /* queue output buffers */ for (i = 0; i < dir->n_ports; i++) { port = GET_OUT_PORT(this, i); if (SPA_UNLIKELY(port->is_monitor || port->is_control)) continue; if (SPA_UNLIKELY((io = port->io) == NULL)) continue; if (SPA_UNLIKELY((buf = out_bufs[i]) == NULL)) continue; dequeue_buffer(this, port, buf); for (j = 0; j < port->blocks; j++) { bd = &buf->buf->datas[j]; bd->chunk->size = this->out_offset * port->stride; bd->chunk->stride = port->stride; SPA_FLAG_UPDATE(bd->chunk->flags, SPA_CHUNK_FLAG_EMPTY, out_empty); spa_log_trace_fp(this->log, "out: offs:%d stride:%d size:%d", this->out_offset, port->stride, bd->chunk->size); } io->status = SPA_STATUS_HAVE_DATA; io->buffer_id = buf->id; } res |= SPA_STATUS_HAVE_DATA; this->drained = draining; this->out_offset = 0; } else if (n_samples == 0 && this->resample_peaks) { for (i = 0; i < dir->n_ports; i++) { port = GET_OUT_PORT(this, i); if (port->is_monitor || port->is_control) continue; if (SPA_UNLIKELY((io = port->io) == NULL)) continue; io->status = SPA_STATUS_HAVE_DATA; io->buffer_id = SPA_ID_INVALID; res |= SPA_STATUS_HAVE_DATA; spa_log_trace_fp(this->log, "%p: no output buffer", this); } } { uint32_t size, queued; if (this->direction == SPA_DIRECTION_INPUT) { size = max_out - this->out_offset; queued = max_in - this->in_offset; } else { size = quant_samples; queued = 0; } if (resample_update_rate_match(this, resample_passthrough, size, queued) > 0) res |= SPA_STATUS_NEED_DATA; } return res; } static const struct spa_node_methods impl_node = { SPA_VERSION_NODE_METHODS, .add_listener = impl_node_add_listener, .set_callbacks = impl_node_set_callbacks, .enum_params = impl_node_enum_params, .set_param = impl_node_set_param, .set_io = impl_node_set_io, .send_command = impl_node_send_command, .add_port = impl_node_add_port, .remove_port = impl_node_remove_port, .port_enum_params = impl_node_port_enum_params, .port_set_param = impl_node_port_set_param, .port_use_buffers = impl_node_port_use_buffers, .port_set_io = impl_node_port_set_io, .port_reuse_buffer = impl_node_port_reuse_buffer, .process = impl_node_process, }; static int impl_get_interface(struct spa_handle *handle, const char *type, void **interface) { struct impl *this; spa_return_val_if_fail(handle != NULL, -EINVAL); spa_return_val_if_fail(interface != NULL, -EINVAL); this = (struct impl *) handle; if (spa_streq(type, SPA_TYPE_INTERFACE_Node)) *interface = &this->node; else return -ENOENT; return 0; } static void free_dir(struct dir *dir) { uint32_t i; for (i = 0; i < MAX_PORTS; i++) free(dir->ports[i]); if (dir->conv.free) convert_free(&dir->conv); free(dir->tag); } static int impl_clear(struct spa_handle *handle) { struct impl *this; spa_return_val_if_fail(handle != NULL, -EINVAL); this = (struct impl *) handle; free_dir(&this->dir[SPA_DIRECTION_INPUT]); free_dir(&this->dir[SPA_DIRECTION_OUTPUT]); free_tmp(this); clean_filter_handles(this, true); if (this->resample.free) resample_free(&this->resample); if (this->wav_file != NULL) wav_file_close(this->wav_file); free (this->vol_ramp_sequence_data); return 0; } static size_t impl_get_size(const struct spa_handle_factory *factory, const struct spa_dict *params) { return sizeof(struct impl); } static int impl_init(const struct spa_handle_factory *factory, struct spa_handle *handle, const struct spa_dict *info, const struct spa_support *support, uint32_t n_support) { struct impl *this; uint32_t i; bool filter_graph_disabled = false; spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(handle != NULL, -EINVAL); handle->get_interface = impl_get_interface; handle->clear = impl_clear; this = (struct impl *) handle; this->data_loop = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_DataLoop); this->log = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log); spa_log_topic_init(this->log, &log_topic); this->cpu = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_CPU); if (this->cpu) { this->cpu_flags = spa_cpu_get_flags(this->cpu); this->max_align = SPA_MIN(MAX_ALIGN, spa_cpu_get_max_align(this->cpu)); } this->loader = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_PluginLoader); props_reset(&this->props); filter_graph_disabled = this->props.filter_graph_disabled; spa_list_init(&this->active_graphs); spa_list_init(&this->free_graphs); for (i = 0; i < MAX_GRAPH; i++) { struct filter_graph *g = &this->graphs[i]; g->impl = this; spa_list_append(&this->free_graphs, &g->link); } this->rate_limit.interval = 2 * SPA_NSEC_PER_SEC; this->rate_limit.burst = 1; this->mix.options = CHANNELMIX_OPTION_UPMIX | CHANNELMIX_OPTION_MIX_LFE; this->mix.upmix = CHANNELMIX_UPMIX_NONE; this->mix.log = this->log; this->mix.lfe_cutoff = 0.0f; this->mix.fc_cutoff = 0.0f; this->mix.rear_delay = 0.0f; this->mix.widen = 0.0f; for (i = 0; info && i < info->n_items; i++) { const char *k = info->items[i].key; const char *s = info->items[i].value; if (spa_streq(k, "clock.quantum-limit")) spa_atou32(s, &this->quantum_limit, 0); else if (spa_streq(k, "resample.peaks")) this->resample_peaks = spa_atob(s); else if (spa_streq(k, "resample.prefill")) SPA_FLAG_UPDATE(this->resample.options, RESAMPLE_OPTION_PREFILL, spa_atob(s)); else if (spa_streq(k, "convert.direction")) { if (spa_streq(s, "output")) this->direction = SPA_DIRECTION_OUTPUT; else this->direction = SPA_DIRECTION_INPUT; } else if (spa_streq(k, SPA_KEY_AUDIO_POSITION)) { if (s == NULL) continue; spa_audio_parse_position_n(s, strlen(s), this->props.channel_map, SPA_N_ELEMENTS(this->props.channel_map), &this->props.n_channels); } else if (spa_streq(k, SPA_KEY_AUDIO_LAYOUT)) { if (s == NULL) continue; spa_audio_parse_layout(s, this->props.channel_map, SPA_N_ELEMENTS(this->props.channel_map), &this->props.n_channels); } else if (spa_streq(k, SPA_KEY_PORT_IGNORE_LATENCY)) this->port_ignore_latency = spa_atob(s); else if (spa_streq(k, SPA_KEY_PORT_GROUP)) spa_scnprintf(this->group_name, sizeof(this->group_name), "%s", s); else if (spa_streq(k, "monitor.passthrough")) this->monitor_passthrough = spa_atob(s); } this->props.channel.n_volumes = this->props.n_channels; this->props.soft.n_volumes = this->props.n_channels; this->props.monitor.n_volumes = this->props.n_channels; this->dir[SPA_DIRECTION_INPUT].direction = SPA_DIRECTION_INPUT; this->dir[SPA_DIRECTION_OUTPUT].direction = SPA_DIRECTION_OUTPUT; this->node.iface = SPA_INTERFACE_INIT( SPA_TYPE_INTERFACE_Node, SPA_VERSION_NODE, &impl_node, this); spa_hook_list_init(&this->hooks); this->info_all = SPA_NODE_CHANGE_MASK_FLAGS | SPA_NODE_CHANGE_MASK_PARAMS; this->info = SPA_NODE_INFO_INIT(); this->info.max_input_ports = MAX_PORTS; this->info.max_output_ports = MAX_PORTS; this->info.flags = SPA_NODE_FLAG_RT | SPA_NODE_FLAG_IN_PORT_CONFIG | SPA_NODE_FLAG_OUT_PORT_CONFIG | SPA_NODE_FLAG_NEED_CONFIGURE; this->params[IDX_EnumPortConfig] = SPA_PARAM_INFO(SPA_PARAM_EnumPortConfig, SPA_PARAM_INFO_READ); this->params[IDX_PortConfig] = SPA_PARAM_INFO(SPA_PARAM_PortConfig, SPA_PARAM_INFO_READWRITE); this->params[IDX_PropInfo] = SPA_PARAM_INFO(SPA_PARAM_PropInfo, SPA_PARAM_INFO_READ); this->params[IDX_Props] = SPA_PARAM_INFO(SPA_PARAM_Props, SPA_PARAM_INFO_READWRITE); this->info.params = this->params; this->info.n_params = N_NODE_PARAMS; this->volume.cpu_flags = this->cpu_flags; volume_init(&this->volume); this->rate_scale = 1.0; reconfigure_mode(this, SPA_PARAM_PORT_CONFIG_MODE_convert, SPA_DIRECTION_INPUT, false, false, NULL); reconfigure_mode(this, SPA_PARAM_PORT_CONFIG_MODE_convert, SPA_DIRECTION_OUTPUT, false, false, NULL); filter_graph_disabled = this->props.filter_graph_disabled; for (i = 0; info && i < info->n_items; i++) { const char *k = info->items[i].key; const char *s = info->items[i].value; audioconvert_set_param(this, k, s, &filter_graph_disabled); } this->props.filter_graph_disabled = filter_graph_disabled; return 0; } static const struct spa_interface_info impl_interfaces[] = { {SPA_TYPE_INTERFACE_Node,}, }; static int impl_enum_interface_info(const struct spa_handle_factory *factory, const struct spa_interface_info **info, uint32_t *index) { spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(info != NULL, -EINVAL); spa_return_val_if_fail(index != NULL, -EINVAL); switch (*index) { case 0: *info = &impl_interfaces[*index]; break; default: return 0; } (*index)++; return 1; } const struct spa_handle_factory spa_audioconvert_factory = { SPA_VERSION_HANDLE_FACTORY, SPA_NAME_AUDIO_CONVERT, NULL, impl_get_size, impl_init, impl_enum_interface_info, }; benchmark-fmt-ops.c000066400000000000000000000234531511204443500310550ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/audioconvert/* Spa */ /* SPDX-FileCopyrightText: Copyright © 2019 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include "config.h" #include #include #include #include #include #include #include "test-helper.h" #include "fmt-ops.h" static uint32_t cpu_flags; typedef void (*convert_func_t) (struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], uint32_t n_samples); struct stats { uint32_t n_samples; uint32_t n_channels; uint64_t perf; const char *name; const char *impl; }; #define MAX_SAMPLES 4096 #define MAX_CHANNELS 11 #define MAX_COUNT 100 static uint8_t samp_in[MAX_SAMPLES * MAX_CHANNELS * 4]; static uint8_t samp_out[MAX_SAMPLES * MAX_CHANNELS * 4]; static const int sample_sizes[] = { 0, 1, 128, 513, 4096 }; static const int channel_counts[] = { 1, 2, 4, 6, 8, 11 }; #define MAX_RESULTS SPA_N_ELEMENTS(sample_sizes) * SPA_N_ELEMENTS(channel_counts) * 70 static uint32_t n_results = 0; static struct stats results[MAX_RESULTS]; static void run_test1(const char *name, const char *impl, bool in_packed, bool out_packed, convert_func_t func, int n_channels, int n_samples) { int i, j; const void *ip[n_channels]; void *op[n_channels]; struct timespec ts; uint64_t count, t1, t2; struct convert conv; conv.n_channels = n_channels; for (j = 0; j < n_channels; j++) { ip[j] = &samp_in[j * n_samples * 4]; op[j] = &samp_out[j * n_samples * 4]; } clock_gettime(CLOCK_MONOTONIC, &ts); t1 = SPA_TIMESPEC_TO_NSEC(&ts); count = 0; for (i = 0; i < MAX_COUNT; i++) { func(&conv, op, ip, n_samples); count++; } clock_gettime(CLOCK_MONOTONIC, &ts); t2 = SPA_TIMESPEC_TO_NSEC(&ts); spa_assert(n_results < MAX_RESULTS); results[n_results++] = (struct stats) { .n_samples = n_samples, .n_channels = n_channels, .perf = count * (uint64_t)SPA_NSEC_PER_SEC / (t2 - t1), .name = name, .impl = impl }; } static void run_testc(const char *name, const char *impl, bool in_packed, bool out_packed, convert_func_t func, int channel_count) { SPA_FOR_EACH_ELEMENT_VAR(sample_sizes, s) { run_test1(name, impl, in_packed, out_packed, func, channel_count, (*s + (channel_count -1)) / channel_count); } } static void run_test(const char *name, const char *impl, bool in_packed, bool out_packed, convert_func_t func) { SPA_FOR_EACH_ELEMENT_VAR(sample_sizes, s) { SPA_FOR_EACH_ELEMENT_VAR(channel_counts, c) { run_test1(name, impl, in_packed, out_packed, func, *c, (*s + (*c -1)) / *c); } } } static void test_f32_u8(void) { run_test("test_f32_u8", "c", true, true, conv_f32_to_u8_c); run_test("test_f32d_u8", "c", false, true, conv_f32d_to_u8_c); run_test("test_f32_u8d", "c", true, false, conv_f32_to_u8d_c); run_test("test_f32d_u8d", "c", false, false, conv_f32d_to_u8d_c); } static void test_u8_f32(void) { run_test("test_u8_f32", "c", true, true, conv_u8_to_f32_c); run_test("test_u8d_f32", "c", false, true, conv_u8d_to_f32_c); run_test("test_u8_f32d", "c", true, false, conv_u8_to_f32d_c); run_test("test_u8d_f32d", "c", false, false, conv_u8d_to_f32d_c); } static void test_f32_s16(void) { run_test("test_f32_s16", "c", true, true, conv_f32_to_s16_c); run_test("test_f32d_s16", "c", false, true, conv_f32d_to_s16_c); #if defined (HAVE_SSE2) if (cpu_flags & SPA_CPU_FLAG_SSE2) { run_test("test_f32d_s16", "sse2", false, true, conv_f32d_to_s16_sse2); run_testc("test_f32d_s16_2", "sse2", false, true, conv_f32d_to_s16_2_sse2, 2); } #endif #if defined (HAVE_AVX2) if (cpu_flags & SPA_CPU_FLAG_AVX2) { run_test("test_f32d_s16", "avx2", false, true, conv_f32d_to_s16_avx2); run_testc("test_f32d_s16_2", "avx2", false, true, conv_f32d_to_s16_2_avx2, 2); run_testc("test_f32d_s16_4", "avx2", false, true, conv_f32d_to_s16_4_avx2, 4); } #endif #if defined (HAVE_RVV) if (cpu_flags & SPA_CPU_FLAG_RISCV_V) { run_test("test_f32_s16", "rvv", true, true, conv_f32_to_s16_rvv); run_test("test_f32d_s16d", "rvv", false, false, conv_f32d_to_s16d_rvv); run_test("test_f32d_s16", "rvv", false, true, conv_f32d_to_s16_rvv); } #endif run_test("test_f32_s16d", "c", true, false, conv_f32_to_s16d_c); run_test("test_f32d_s16d", "c", false, false, conv_f32d_to_s16d_c); } static void test_s16_f32(void) { run_test("test_s16_f32", "c", true, true, conv_s16_to_f32_c); run_test("test_s16d_f32", "c", false, true, conv_s16d_to_f32_c); run_test("test_s16_f32d", "c", true, false, conv_s16_to_f32d_c); #if defined (HAVE_SSE2) if (cpu_flags & SPA_CPU_FLAG_SSE2) { run_test("test_s16_f32d", "sse2", true, false, conv_s16_to_f32d_sse2); run_testc("test_s16_f32d_2", "sse2", true, false, conv_s16_to_f32d_2_sse2, 2); } #endif #if defined (HAVE_AVX2) if (cpu_flags & SPA_CPU_FLAG_AVX2) { run_test("test_s16_f32d", "avx2", true, false, conv_s16_to_f32d_avx2); run_testc("test_s16_f32d_2", "avx2", true, false, conv_s16_to_f32d_2_avx2, 2); } #endif #if defined (HAVE_RVV) if (cpu_flags & SPA_CPU_FLAG_RISCV_V) { run_test("test_s16_f32d", "rvv", true, false, conv_s16_to_f32d_rvv); } #endif run_test("test_s16d_f32d", "c", false, false, conv_s16d_to_f32d_c); } static void test_f32_s32(void) { run_test("test_f32_s32", "c", true, true, conv_f32_to_s32_c); run_test("test_f32d_s32", "c", false, true, conv_f32d_to_s32_c); #if defined (HAVE_SSE2) if (cpu_flags & SPA_CPU_FLAG_SSE2) { run_test("test_f32d_s32", "sse2", false, true, conv_f32d_to_s32_sse2); } #endif #if defined (HAVE_AVX2) if (cpu_flags & SPA_CPU_FLAG_AVX2) { run_test("test_f32d_s32", "avx2", false, true, conv_f32d_to_s32_avx2); } #endif #if defined (HAVE_RVV) if (cpu_flags & SPA_CPU_FLAG_RISCV_V) { run_test("test_f32d_s32", "rvv", false, true, conv_f32d_to_s32_rvv); } #endif run_test("test_f32_s32d", "c", true, false, conv_f32_to_s32d_c); run_test("test_f32d_s32d", "c", false, false, conv_f32d_to_s32d_c); } static void test_s32_f32(void) { run_test("test_s32_f32", "c", true, true, conv_s32_to_f32_c); run_test("test_s32d_f32", "c", false, true, conv_s32d_to_f32_c); #if defined (HAVE_SSE2) if (cpu_flags & SPA_CPU_FLAG_SSE2) { run_test("test_s32_f32d", "sse2", true, false, conv_s32_to_f32d_sse2); } #endif #if defined (HAVE_AVX2) if (cpu_flags & SPA_CPU_FLAG_AVX2) { run_test("test_s32_f32d", "avx2", true, false, conv_s32_to_f32d_avx2); } #endif #if defined (HAVE_RVV) if (cpu_flags & SPA_CPU_FLAG_RISCV_V) { run_test("test_s32_f32d", "rvv", true, false, conv_s32_to_f32d_rvv); } #endif run_test("test_s32_f32d", "c", true, false, conv_s32_to_f32d_c); run_test("test_s32d_f32d", "c", false, false, conv_s32d_to_f32d_c); } static void test_f32_s24(void) { run_test("test_f32_s24", "c", true, true, conv_f32_to_s24_c); run_test("test_f32d_s24", "c", false, true, conv_f32d_to_s24_c); run_test("test_f32_s24d", "c", true, false, conv_f32_to_s24d_c); run_test("test_f32d_s24d", "c", false, false, conv_f32d_to_s24d_c); } static void test_s24_f32(void) { run_test("test_s24_f32", "c", true, true, conv_s24_to_f32_c); run_test("test_s24d_f32", "c", false, true, conv_s24d_to_f32_c); run_test("test_s24_f32d", "c", true, false, conv_s24_to_f32d_c); #if defined (HAVE_SSE2) if (cpu_flags & SPA_CPU_FLAG_SSE2) { run_test("test_s24_f32d", "sse2", true, false, conv_s24_to_f32d_sse2); } #endif #if defined (HAVE_AVX2) if (cpu_flags & SPA_CPU_FLAG_AVX2) { run_test("test_s24_f32d", "avx2", true, false, conv_s24_to_f32d_avx2); } #endif #if defined (HAVE_SSSE3) if (cpu_flags & SPA_CPU_FLAG_SSSE3) { run_test("test_s24_f32d", "ssse3", true, false, conv_s24_to_f32d_ssse3); } #endif #if defined (HAVE_SSE41) if (cpu_flags & SPA_CPU_FLAG_SSE41) { run_test("test_s24_f32d", "sse41", true, false, conv_s24_to_f32d_sse41); } #endif run_test("test_s24d_f32d", "c", false, false, conv_s24d_to_f32d_c); } static void test_f32_s24_32(void) { run_test("test_f32_s24_32", "c", true, true, conv_f32_to_s24_32_c); run_test("test_f32d_s24_32", "c", false, true, conv_f32d_to_s24_32_c); run_test("test_f32_s24_32d", "c", true, false, conv_f32_to_s24_32d_c); run_test("test_f32d_s24_32d", "c", false, false, conv_f32d_to_s24_32d_c); } static void test_s24_32_f32(void) { run_test("test_s24_32_f32", "c", true, true, conv_s24_32_to_f32_c); run_test("test_s24_32d_f32", "c", false, true, conv_s24_32d_to_f32_c); run_test("test_s24_32_f32d", "c", true, false, conv_s24_32_to_f32d_c); run_test("test_s24_32d_f32d", "c", false, false, conv_s24_32d_to_f32d_c); } static void test_interleave(void) { run_test("test_8d_to_8", "c", false, true, conv_8d_to_8_c); run_test("test_16d_to_16", "c", false, true, conv_16d_to_16_c); run_test("test_24d_to_24", "c", false, true, conv_24d_to_24_c); run_test("test_32d_to_32", "c", false, true, conv_32d_to_32_c); } static void test_deinterleave(void) { run_test("test_8_to_8d", "c", true, false, conv_8_to_8d_c); run_test("test_16_to_16d", "c", true, false, conv_16_to_16d_c); run_test("test_24_to_24d", "c", true, false, conv_24_to_24d_c); run_test("test_32_to_32d", "c", true, false, conv_32_to_32d_c); } static int compare_func(const void *_a, const void *_b) { const struct stats *a = _a, *b = _b; int diff; if ((diff = strcmp(a->name, b->name)) != 0) return diff; if ((diff = a->n_samples - b->n_samples) != 0) return diff; if ((diff = a->n_channels - b->n_channels) != 0) return diff; if ((diff = b->perf - a->perf) != 0) return diff; return 0; } int main(int argc, char *argv[]) { uint32_t i; cpu_flags = get_cpu_flags(); printf("got get CPU flags %d\n", cpu_flags); test_f32_u8(); test_u8_f32(); test_f32_s16(); test_s16_f32(); test_f32_s32(); test_s32_f32(); test_f32_s24(); test_s24_f32(); test_f32_s24_32(); test_s24_32_f32(); test_interleave(); test_deinterleave(); qsort(results, n_results, sizeof(struct stats), compare_func); for (i = 0; i < n_results; i++) { struct stats *s = &results[i]; fprintf(stderr, "%-12."PRIu64" \t%-32.32s %s \t samples %d, channels %d\n", s->perf, s->name, s->impl, s->n_samples, s->n_channels); } return 0; } benchmark-resample.c000066400000000000000000000110451511204443500312720ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/audioconvert/* Spa */ /* SPDX-FileCopyrightText: Copyright © 2019 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include "config.h" #include #include #include #include #include #include #include "test-helper.h" #include "resample.h" #define MAX_SAMPLES 4096 #define MAX_CHANNELS 11 #define MAX_COUNT 200 static uint32_t cpu_flags; struct stats { uint32_t in_rate; uint32_t out_rate; uint32_t n_samples; uint32_t n_channels; uint64_t perf; const char *name; const char *impl; }; static float samp_in[MAX_SAMPLES * MAX_CHANNELS]; static float samp_out[MAX_SAMPLES * MAX_CHANNELS]; static const int sample_sizes[] = { 0, 1, 128, 513, 4096 }; static const int in_rates[] = { 44100, 44100, 48000, 96000, 22050, 96000 }; static const int out_rates[] = { 44100, 48000, 44100, 48000, 48000, 44100 }; #define MAX_RESAMPLER 5 #define MAX_SIZES SPA_N_ELEMENTS(sample_sizes) #define MAX_RATES SPA_N_ELEMENTS(in_rates) #define MAX_RESULTS MAX_RESAMPLER * MAX_SIZES * MAX_RATES static uint32_t n_results = 0; static struct stats results[MAX_RESULTS]; static void run_test1(const char *name, const char *impl, struct resample *r, int n_samples) { uint32_t i, j; const void *ip[MAX_CHANNELS]; void *op[MAX_CHANNELS]; struct timespec ts; uint64_t count, t1, t2; uint32_t in_len, out_len; for (j = 0; j < r->channels; j++) { ip[j] = &samp_in[j * MAX_SAMPLES]; op[j] = &samp_out[j * MAX_SAMPLES]; } clock_gettime(CLOCK_MONOTONIC, &ts); t1 = SPA_TIMESPEC_TO_NSEC(&ts); count = 0; for (i = 0; i < MAX_COUNT; i++) { in_len = n_samples; out_len = MAX_SAMPLES; resample_process(r, ip, &in_len, op, &out_len); count++; } clock_gettime(CLOCK_MONOTONIC, &ts); t2 = SPA_TIMESPEC_TO_NSEC(&ts); spa_assert(n_results < MAX_RESULTS); results[n_results++] = (struct stats) { .in_rate = r->i_rate, .out_rate = r->o_rate, .n_samples = n_samples, .n_channels = r->channels, .perf = count * (uint64_t)SPA_NSEC_PER_SEC / (t2 - t1), .name = name, .impl = impl }; } static void run_test(const char *name, const char *impl, struct resample *r) { size_t i; for (i = 0; i < SPA_N_ELEMENTS(sample_sizes); i++) run_test1(name, impl, r, sample_sizes[i]); } static int compare_func(const void *_a, const void *_b) { const struct stats *a = _a, *b = _b; int diff; if ((diff = a->in_rate - b->in_rate) != 0) return diff; if ((diff = a->out_rate - b->out_rate) != 0) return diff; if ((diff = a->n_samples - b->n_samples) != 0) return diff; if ((diff = a->n_channels - b->n_channels) != 0) return diff; if ((diff = b->perf - a->perf) != 0) return diff; return 0; } int main(int argc, char *argv[]) { struct resample r; uint32_t i; cpu_flags = get_cpu_flags(); printf("got get CPU flags %d\n", cpu_flags); for (i = 0; i < SPA_N_ELEMENTS(in_rates); i++) { spa_zero(r); r.channels = 2; r.cpu_flags = 0; r.i_rate = in_rates[i]; r.o_rate = out_rates[i]; r.quality = RESAMPLE_DEFAULT_QUALITY; resample_native_init(&r); run_test("native", "c", &r); resample_free(&r); } #if defined (HAVE_SSE) if (cpu_flags & SPA_CPU_FLAG_SSE) { for (i = 0; i < SPA_N_ELEMENTS(in_rates); i++) { spa_zero(r); r.channels = 2; r.cpu_flags = SPA_CPU_FLAG_SSE; r.i_rate = in_rates[i]; r.o_rate = out_rates[i]; r.quality = RESAMPLE_DEFAULT_QUALITY; resample_native_init(&r); run_test("native", "sse", &r); resample_free(&r); } } #endif #if defined (HAVE_SSSE3) if (cpu_flags & SPA_CPU_FLAG_SSSE3) { for (i = 0; i < SPA_N_ELEMENTS(in_rates); i++) { spa_zero(r); r.channels = 2; r.cpu_flags = SPA_CPU_FLAG_SSSE3 | SPA_CPU_FLAG_SLOW_UNALIGNED; r.i_rate = in_rates[i]; r.o_rate = out_rates[i]; r.quality = RESAMPLE_DEFAULT_QUALITY; resample_native_init(&r); run_test("native", "ssse3", &r); resample_free(&r); } } #endif #if defined (HAVE_AVX) && defined(HAVE_FMA) if (SPA_FLAG_IS_SET(cpu_flags, SPA_CPU_FLAG_AVX | SPA_CPU_FLAG_FMA3)) { for (i = 0; i < SPA_N_ELEMENTS(in_rates); i++) { spa_zero(r); r.channels = 2; r.cpu_flags = SPA_CPU_FLAG_AVX | SPA_CPU_FLAG_FMA3; r.i_rate = in_rates[i]; r.o_rate = out_rates[i]; r.quality = RESAMPLE_DEFAULT_QUALITY; resample_native_init(&r); run_test("native", "avx", &r); resample_free(&r); } } #endif qsort(results, n_results, sizeof(struct stats), compare_func); for (i = 0; i < n_results; i++) { struct stats *s = &results[i]; fprintf(stderr, "%-12."PRIu64" \t%-16.16s %s \t%d->%d samples %d, channels %d\n", s->perf, s->name, s->impl, s->in_rate, s->out_rate, s->n_samples, s->n_channels); } return 0; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/audioconvert/biquad.c000066400000000000000000000227571511204443500270720ustar00rootroot00000000000000/* Copyright (c) 2013 The Chromium OS Authors. All rights reserved. * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ /* Copyright (C) 2010 Google Inc. All rights reserved. * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE.WEBKIT file. */ #include #include "biquad.h" #ifndef M_PI #define M_PI 3.14159265358979323846 #endif /* Q = 1 / sqrt(2), also resulting Q value when S = 1 */ #define BIQUAD_DEFAULT_Q 0.707106781186548 static void set_coefficient(struct biquad *bq, double b0, double b1, double b2, double a0, double a1, double a2) { double a0_inv = 1 / a0; bq->b0 = (float)(b0 * a0_inv); bq->b1 = (float)(b1 * a0_inv); bq->b2 = (float)(b2 * a0_inv); bq->a1 = (float)(a1 * a0_inv); bq->a2 = (float)(a2 * a0_inv); } static void biquad_lowpass(struct biquad *bq, double cutoff, double Q) { /* Limit cutoff to 0 to 1. */ cutoff = fmax(0.0, fmin(cutoff, 1.0)); if (cutoff == 1 || cutoff == 0) { /* When cutoff is 1, the z-transform is 1. * When cutoff is zero, nothing gets through the filter, so set * coefficients up correctly. */ set_coefficient(bq, cutoff, 0, 0, 1, 0, 0); return; } /* Set Q to a sane default value if not set */ if (Q <= 0) Q = BIQUAD_DEFAULT_Q; /* Compute biquad coefficients for lowpass filter */ /* H(s) = 1 / (s^2 + s/Q + 1) */ double w0 = M_PI * cutoff; double alpha = sin(w0) / (2 * Q); double k = cos(w0); double b0 = (1 - k) / 2; double b1 = 1 - k; double b2 = (1 - k) / 2; double a0 = 1 + alpha; double a1 = -2 * k; double a2 = 1 - alpha; set_coefficient(bq, b0, b1, b2, a0, a1, a2); } static void biquad_highpass(struct biquad *bq, double cutoff, double Q) { /* Limit cutoff to 0 to 1. */ cutoff = fmax(0.0, fmin(cutoff, 1.0)); if (cutoff == 1 || cutoff == 0) { /* When cutoff is one, the z-transform is 0. */ /* When cutoff is zero, we need to be careful because the above * gives a quadratic divided by the same quadratic, with poles * and zeros on the unit circle in the same place. When cutoff * is zero, the z-transform is 1. */ set_coefficient(bq, 1 - cutoff, 0, 0, 1, 0, 0); return; } /* Set Q to a sane default value if not set */ if (Q <= 0) Q = BIQUAD_DEFAULT_Q; /* Compute biquad coefficients for highpass filter */ /* H(s) = s^2 / (s^2 + s/Q + 1) */ double w0 = M_PI * cutoff; double alpha = sin(w0) / (2 * Q); double k = cos(w0); double b0 = (1 + k) / 2; double b1 = -(1 + k); double b2 = (1 + k) / 2; double a0 = 1 + alpha; double a1 = -2 * k; double a2 = 1 - alpha; set_coefficient(bq, b0, b1, b2, a0, a1, a2); } static void biquad_bandpass(struct biquad *bq, double frequency, double Q) { /* No negative frequencies allowed. */ frequency = fmax(0.0, frequency); /* Don't let Q go negative, which causes an unstable filter. */ Q = fmax(0.0, Q); if (frequency <= 0 || frequency >= 1) { /* When the cutoff is zero, the z-transform approaches 0, if Q * > 0. When both Q and cutoff are zero, the z-transform is * pretty much undefined. What should we do in this case? * For now, just make the filter 0. When the cutoff is 1, the * z-transform also approaches 0. */ set_coefficient(bq, 0, 0, 0, 1, 0, 0); return; } if (Q <= 0) { /* When Q = 0, the above formulas have problems. If we * look at the z-transform, we can see that the limit * as Q->0 is 1, so set the filter that way. */ set_coefficient(bq, 1, 0, 0, 1, 0, 0); return; } double w0 = M_PI * frequency; double alpha = sin(w0) / (2 * Q); double k = cos(w0); double b0 = alpha; double b1 = 0; double b2 = -alpha; double a0 = 1 + alpha; double a1 = -2 * k; double a2 = 1 - alpha; set_coefficient(bq, b0, b1, b2, a0, a1, a2); } static void biquad_lowshelf(struct biquad *bq, double frequency, double Q, double db_gain) { /* Clip frequencies to between 0 and 1, inclusive. */ frequency = fmax(0.0, fmin(frequency, 1.0)); double A = pow(10.0, db_gain / 40); if (frequency == 1) { /* The z-transform is a constant gain. */ set_coefficient(bq, A * A, 0, 0, 1, 0, 0); return; } if (frequency <= 0) { /* When frequency is 0, the z-transform is 1. */ set_coefficient(bq, 1, 0, 0, 1, 0, 0); return; } /* Set Q to an equivalent value to S = 1 if not specified */ if (Q <= 0) Q = BIQUAD_DEFAULT_Q; double w0 = M_PI * frequency; double alpha = sin(w0) / (2 * Q); double k = cos(w0); double k2 = 2 * sqrt(A) * alpha; double a_plus_one = A + 1; double a_minus_one = A - 1; double b0 = A * (a_plus_one - a_minus_one * k + k2); double b1 = 2 * A * (a_minus_one - a_plus_one * k); double b2 = A * (a_plus_one - a_minus_one * k - k2); double a0 = a_plus_one + a_minus_one * k + k2; double a1 = -2 * (a_minus_one + a_plus_one * k); double a2 = a_plus_one + a_minus_one * k - k2; set_coefficient(bq, b0, b1, b2, a0, a1, a2); } static void biquad_highshelf(struct biquad *bq, double frequency, double Q, double db_gain) { /* Clip frequencies to between 0 and 1, inclusive. */ frequency = fmax(0.0, fmin(frequency, 1.0)); double A = pow(10.0, db_gain / 40); if (frequency == 1) { /* The z-transform is 1. */ set_coefficient(bq, 1, 0, 0, 1, 0, 0); return; } if (frequency <= 0) { /* When frequency = 0, the filter is just a gain, A^2. */ set_coefficient(bq, A * A, 0, 0, 1, 0, 0); return; } /* Set Q to an equivalent value to S = 1 if not specified */ if (Q <= 0) Q = BIQUAD_DEFAULT_Q; double w0 = M_PI * frequency; double alpha = sin(w0) / (2 * Q); double k = cos(w0); double k2 = 2 * sqrt(A) * alpha; double a_plus_one = A + 1; double a_minus_one = A - 1; double b0 = A * (a_plus_one + a_minus_one * k + k2); double b1 = -2 * A * (a_minus_one + a_plus_one * k); double b2 = A * (a_plus_one + a_minus_one * k - k2); double a0 = a_plus_one - a_minus_one * k + k2; double a1 = 2 * (a_minus_one - a_plus_one * k); double a2 = a_plus_one - a_minus_one * k - k2; set_coefficient(bq, b0, b1, b2, a0, a1, a2); } static void biquad_peaking(struct biquad *bq, double frequency, double Q, double db_gain) { /* Clip frequencies to between 0 and 1, inclusive. */ frequency = fmax(0.0, fmin(frequency, 1.0)); /* Don't let Q go negative, which causes an unstable filter. */ Q = fmax(0.0, Q); double A = pow(10.0, db_gain / 40); if (frequency <= 0 || frequency >= 1) { /* When frequency is 0 or 1, the z-transform is 1. */ set_coefficient(bq, 1, 0, 0, 1, 0, 0); return; } if (Q <= 0) { /* When Q = 0, the above formulas have problems. If we * look at the z-transform, we can see that the limit * as Q->0 is A^2, so set the filter that way. */ set_coefficient(bq, A * A, 0, 0, 1, 0, 0); return; } double w0 = M_PI * frequency; double alpha = sin(w0) / (2 * Q); double k = cos(w0); double b0 = 1 + alpha * A; double b1 = -2 * k; double b2 = 1 - alpha * A; double a0 = 1 + alpha / A; double a1 = -2 * k; double a2 = 1 - alpha / A; set_coefficient(bq, b0, b1, b2, a0, a1, a2); } static void biquad_notch(struct biquad *bq, double frequency, double Q) { /* Clip frequencies to between 0 and 1, inclusive. */ frequency = fmax(0.0, fmin(frequency, 1.0)); /* Don't let Q go negative, which causes an unstable filter. */ Q = fmax(0.0, Q); if (frequency <= 0 || frequency >= 1) { /* When frequency is 0 or 1, the z-transform is 1. */ set_coefficient(bq, 1, 0, 0, 1, 0, 0); return; } if (Q <= 0) { /* When Q = 0, the above formulas have problems. If we * look at the z-transform, we can see that the limit * as Q->0 is 0, so set the filter that way. */ set_coefficient(bq, 0, 0, 0, 1, 0, 0); return; } double w0 = M_PI * frequency; double alpha = sin(w0) / (2 * Q); double k = cos(w0); double b0 = 1; double b1 = -2 * k; double b2 = 1; double a0 = 1 + alpha; double a1 = -2 * k; double a2 = 1 - alpha; set_coefficient(bq, b0, b1, b2, a0, a1, a2); } static void biquad_allpass(struct biquad *bq, double frequency, double Q) { /* Clip frequencies to between 0 and 1, inclusive. */ frequency = fmax(0.0, fmin(frequency, 1.0)); /* Don't let Q go negative, which causes an unstable filter. */ Q = fmax(0.0, Q); if (frequency <= 0 || frequency >= 1) { /* When frequency is 0 or 1, the z-transform is 1. */ set_coefficient(bq, 1, 0, 0, 1, 0, 0); return; } if (Q <= 0) { /* When Q = 0, the above formulas have problems. If we * look at the z-transform, we can see that the limit * as Q->0 is -1, so set the filter that way. */ set_coefficient(bq, -1, 0, 0, 1, 0, 0); return; } double w0 = M_PI * frequency; double alpha = sin(w0) / (2 * Q); double k = cos(w0); double b0 = 1 - alpha; double b1 = -2 * k; double b2 = 1 + alpha; double a0 = 1 + alpha; double a1 = -2 * k; double a2 = 1 - alpha; set_coefficient(bq, b0, b1, b2, a0, a1, a2); } void biquad_set(struct biquad *bq, enum biquad_type type, double freq, double Q, double gain) { /* Clear history values. */ bq->type = type; bq->x1 = 0; bq->x2 = 0; switch (type) { case BQ_LOWPASS: biquad_lowpass(bq, freq, Q); break; case BQ_HIGHPASS: biquad_highpass(bq, freq, Q); break; case BQ_BANDPASS: biquad_bandpass(bq, freq, Q); break; case BQ_LOWSHELF: biquad_lowshelf(bq, freq, Q, gain); break; case BQ_HIGHSHELF: biquad_highshelf(bq, freq, Q, gain); break; case BQ_PEAKING: biquad_peaking(bq, freq, Q, gain); break; case BQ_NOTCH: biquad_notch(bq, freq, Q); break; case BQ_ALLPASS: biquad_allpass(bq, freq, Q); break; case BQ_NONE: case BQ_RAW: /* Default is an identity filter. */ set_coefficient(bq, 1, 0, 0, 1, 0, 0); break; } } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/audioconvert/biquad.h000066400000000000000000000027421511204443500270670ustar00rootroot00000000000000/* Copyright (c) 2013 The Chromium OS Authors. All rights reserved. * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #ifndef BIQUAD_H_ #define BIQUAD_H_ #ifdef __cplusplus extern "C" { #endif /* The type of the biquad filters */ enum biquad_type { BQ_NONE, BQ_LOWPASS, BQ_HIGHPASS, BQ_BANDPASS, BQ_LOWSHELF, BQ_HIGHSHELF, BQ_PEAKING, BQ_NOTCH, BQ_ALLPASS, BQ_RAW, }; /* The biquad filter parameters. The transfer function H(z) is (b0 + b1 * z^(-1) * + b2 * z^(-2)) / (1 + a1 * z^(-1) + a2 * z^(-2)). The previous two inputs * are stored in x1 and x2, and the previous two outputs are stored in y1 and * y2. * * We use double during the coefficients calculation for better accuracy, but * float is used during the actual filtering for faster computation. */ struct biquad { enum biquad_type type; float b0, b1, b2; float a1, a2; float x1, x2; }; /* Initialize a biquad filter parameters from its type and parameters. * Args: * bq - The biquad filter we want to set. * type - The type of the biquad filter. * frequency - The value should be in the range [0, 1]. It is relative to * half of the sampling rate. * Q - Quality factor. See Web Audio API for details. * gain - The value is in dB. See Web Audio API for details. */ void biquad_set(struct biquad *bq, enum biquad_type type, double freq, double Q, double gain); #ifdef __cplusplus } /* extern "C" */ #endif #endif /* BIQUAD_H_ */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/audioconvert/channelmix-ops-c.c000066400000000000000000000403531511204443500307620ustar00rootroot00000000000000/* Spa */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include "channelmix-ops.h" static inline void clear_c(float *d, uint32_t n_samples) { memset(d, 0, n_samples * sizeof(float)); } static inline void copy_c(float *d, const float *s, uint32_t n_samples) { if (d != s) spa_memcpy(d, s, n_samples * sizeof(float)); } static inline void vol_c(float *d, const float *s, float vol, uint32_t n_samples) { uint32_t n; if (vol == 0.0f) { clear_c(d, n_samples); } else if (vol == 1.0f) { copy_c(d, s, n_samples); } else { for (n = 0; n < n_samples; n++) d[n] = s[n] * vol; } } static inline void conv_c(float *d, const float **s, float *c, uint32_t n_c, uint32_t n_samples) { uint32_t n, j; for (n = 0; n < n_samples; n++) { float sum = 0.0f; for (j = 0; j < n_c; j++) sum += s[j][n] * c[j]; d[n] = sum; } } static inline void avg_c(float *d, const float *s0, const float *s1, uint32_t n_samples) { uint32_t n; for (n = 0; n < n_samples; n++) d[n] = (s0[n] + s1[n]) * 0.5f; } static inline void sub_c(float *d, const float *s0, const float *s1, uint32_t n_samples) { uint32_t n; for (n = 0; n < n_samples; n++) d[n] = s0[n] - s1[n]; } void channelmix_copy_c(struct channelmix *mix, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], uint32_t n_samples) { uint32_t i, n_dst = mix->dst_chan; float **d = (float **)dst; const float **s = (const float **)src; for (i = 0; i < n_dst; i++) vol_c(d[i], s[i], mix->matrix[i][i], n_samples); } static void lr4_process_c(struct lr4 *lr4, float *dst, const float *src, const float vol, int samples) { float x1 = lr4->x1; float x2 = lr4->x2; float y1 = lr4->y1; float y2 = lr4->y2; float b0 = lr4->bq.b0; float b1 = lr4->bq.b1; float b2 = lr4->bq.b2; float a1 = lr4->bq.a1; float a2 = lr4->bq.a2; float x, y, z; int i; if (vol == 0.0f || !lr4->active) { vol_c(dst, src, vol, samples); return; } for (i = 0; i < samples; i++) { x = src[i]; y = b0 * x + x1; x1 = b1 * x - a1 * y + x2; x2 = b2 * x - a2 * y; z = b0 * y + y1; y1 = b1 * y - a1 * z + y2; y2 = b2 * y - a2 * z; dst[i] = z * vol; } #define F(x) (isnormal(x) ? (x) : 0.0f) lr4->x1 = F(x1); lr4->x2 = F(x2); lr4->y1 = F(y1); lr4->y2 = F(y2); #undef F } static inline void delay_convolve_run_c(float *buffer, uint32_t *pos, uint32_t n_buffer, uint32_t delay, const float *taps, uint32_t n_taps, float *dst, const float *src, const float vol, uint32_t n_samples) { uint32_t i, j; uint32_t w = *pos; uint32_t o = n_buffer - delay - n_taps-1; if (n_taps == 1) { for (i = 0; i < n_samples; i++) { buffer[w] = buffer[w + n_buffer] = src[i]; dst[i] = buffer[w + o] * vol; w = w + 1 >= n_buffer ? 0 : w + 1; } } else { for (i = 0; i < n_samples; i++) { float sum = 0.0f; buffer[w] = buffer[w + n_buffer] = src[i]; for (j = 0; j < n_taps; j++) sum += taps[j] * buffer[w+o+j]; dst[i] = sum * vol; w = w + 1 >= n_buffer ? 0 : w + 1; } } *pos = w; } #define _M(ch) (1UL << SPA_AUDIO_CHANNEL_ ## ch) void channelmix_f32_n_m_c(struct channelmix *mix, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], uint32_t n_samples) { uint32_t i, j, n_dst = mix->dst_chan, n_src = mix->src_chan; float **d = (float **) dst; const float **s = (const float **) src; if (SPA_FLAG_IS_SET(mix->flags, CHANNELMIX_FLAG_ZERO)) { for (i = 0; i < n_dst; i++) clear_c(d[i], n_samples); } else if (SPA_FLAG_IS_SET(mix->flags, CHANNELMIX_FLAG_COPY)) { uint32_t copy = SPA_MIN(n_dst, n_src); for (i = 0; i < copy; i++) copy_c(d[i], s[i], n_samples); for (; i < n_dst; i++) clear_c(d[i], n_samples); } else { for (i = 0; i < n_dst; i++) { float *di = d[i]; float mj[n_src]; const float *sj[n_src]; uint32_t n_j = 0; for (j = 0; j < n_src; j++) { if (mix->matrix[i][j] == 0.0f) continue; mj[n_j] = mix->matrix[i][j]; sj[n_j++] = s[j]; } if (n_j == 0) { clear_c(di, n_samples); } else if (n_j == 1) { lr4_process_c(&mix->lr4[i], di, sj[0], mj[0], n_samples); } else { conv_c(di, sj, mj, n_j, n_samples); lr4_process_c(&mix->lr4[i], di, di, 1.0f, n_samples); } } } } #define MASK_MONO _M(FC)|_M(MONO)|_M(UNKNOWN) #define MASK_STEREO _M(FL)|_M(FR)|_M(UNKNOWN) void channelmix_f32_1_2_c(struct channelmix *mix, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], uint32_t n_samples) { float **d = (float **)dst; const float **s = (const float **)src; const float v0 = mix->matrix[0][0]; const float v1 = mix->matrix[1][0]; vol_c(d[0], s[0], v0, n_samples); vol_c(d[1], s[0], v1, n_samples); } void channelmix_f32_2_1_c(struct channelmix *mix, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], uint32_t n_samples) { uint32_t n; float **d = (float **)dst; const float **s = (const float **)src; const float v0 = mix->matrix[0][0]; const float v1 = mix->matrix[0][1]; if (SPA_FLAG_IS_SET(mix->flags, CHANNELMIX_FLAG_ZERO)) { clear_c(d[0], n_samples); } else if (SPA_FLAG_IS_SET(mix->flags, CHANNELMIX_FLAG_EQUAL)) { for (n = 0; n < n_samples; n++) d[0][n] = (s[0][n] + s[1][n]) * v0; } else { for (n = 0; n < n_samples; n++) d[0][n] = s[0][n] * v0 + s[1][n] * v1; } } void channelmix_f32_4_1_c(struct channelmix *mix, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], uint32_t n_samples) { uint32_t n; float **d = (float **)dst; const float **s = (const float **)src; const float v0 = mix->matrix[0][0]; const float v1 = mix->matrix[0][1]; const float v2 = mix->matrix[0][2]; const float v3 = mix->matrix[0][3]; if (SPA_FLAG_IS_SET(mix->flags, CHANNELMIX_FLAG_ZERO)) { clear_c(d[0], n_samples); } else if (SPA_FLAG_IS_SET(mix->flags, CHANNELMIX_FLAG_EQUAL)) { for (n = 0; n < n_samples; n++) d[0][n] = (s[0][n] + s[1][n] + s[2][n] + s[3][n]) * v0; } else { for (n = 0; n < n_samples; n++) d[0][n] = s[0][n] * v0 + s[1][n] * v1 + s[2][n] * v2 + s[3][n] * v3; } } #define MASK_QUAD _M(FL)|_M(FR)|_M(RL)|_M(RR)|_M(UNKNOWN) void channelmix_f32_2_4_c(struct channelmix *mix, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], uint32_t n_samples) { uint32_t i, n_dst = mix->dst_chan; float **d = (float **)dst; const float **s = (const float **)src; const float v0 = mix->matrix[0][0]; const float v1 = mix->matrix[1][1]; const float v2 = mix->matrix[2][0]; const float v3 = mix->matrix[3][1]; if (SPA_FLAG_IS_SET(mix->flags, CHANNELMIX_FLAG_ZERO)) { for (i = 0; i < n_dst; i++) clear_c(d[i], n_samples); } else { vol_c(d[0], s[0], v0, n_samples); vol_c(d[1], s[1], v1, n_samples); if (mix->upmix != CHANNELMIX_UPMIX_PSD) { vol_c(d[2], s[0], v2, n_samples); vol_c(d[3], s[1], v3, n_samples); } else { sub_c(d[2], s[0], s[1], n_samples); delay_convolve_run_c(mix->buffer[1], &mix->pos[1], BUFFER_SIZE, mix->delay, mix->taps, mix->n_taps, d[3], d[2], -v3, n_samples); delay_convolve_run_c(mix->buffer[0], &mix->pos[0], BUFFER_SIZE, mix->delay, mix->taps, mix->n_taps, d[2], d[2], v2, n_samples); } } } #define MASK_3_1 _M(FL)|_M(FR)|_M(FC)|_M(LFE) void channelmix_f32_2_3p1_c(struct channelmix *mix, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], uint32_t n_samples) { uint32_t i, n, n_dst = mix->dst_chan; float **d = (float **)dst; const float **s = (const float **)src; const float v0 = mix->matrix[0][0]; const float v1 = mix->matrix[1][1]; const float v2 = (mix->matrix[2][0] + mix->matrix[2][1]) * 0.5f; const float v3 = (mix->matrix[3][0] + mix->matrix[3][1]) * 0.5f; if (SPA_FLAG_IS_SET(mix->flags, CHANNELMIX_FLAG_ZERO)) { for (i = 0; i < n_dst; i++) clear_c(d[i], n_samples); } else { if (mix->widen == 0.0f) { vol_c(d[0], s[0], v0, n_samples); vol_c(d[1], s[1], v1, n_samples); avg_c(d[2], s[0], s[1], n_samples); } else { for (n = 0; n < n_samples; n++) { float c = s[0][n] + s[1][n]; float w = c * mix->widen; d[0][n] = (s[0][n] - w) * v0; d[1][n] = (s[1][n] - w) * v1; d[2][n] = c * 0.5f; } } lr4_process_c(&mix->lr4[3], d[3], d[2], v3, n_samples); lr4_process_c(&mix->lr4[2], d[2], d[2], v2, n_samples); } } #define MASK_5_1 _M(FL)|_M(FR)|_M(FC)|_M(LFE)|_M(SL)|_M(SR)|_M(RL)|_M(RR) void channelmix_f32_2_5p1_c(struct channelmix *mix, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], uint32_t n_samples) { uint32_t i, n_dst = mix->dst_chan; float **d = (float **)dst; const float **s = (const float **)src; const float v4 = mix->matrix[4][0]; const float v5 = mix->matrix[5][1]; if (SPA_FLAG_IS_SET(mix->flags, CHANNELMIX_FLAG_ZERO)) { for (i = 0; i < n_dst; i++) clear_c(d[i], n_samples); } else { channelmix_f32_2_3p1_c(mix, dst, src, n_samples); if (mix->upmix != CHANNELMIX_UPMIX_PSD) { vol_c(d[4], s[0], v4, n_samples); vol_c(d[5], s[1], v5, n_samples); } else { sub_c(d[4], s[0], s[1], n_samples); delay_convolve_run_c(mix->buffer[1], &mix->pos[1], BUFFER_SIZE, mix->delay, mix->taps, mix->n_taps, d[5], d[4], -v5, n_samples); delay_convolve_run_c(mix->buffer[0], &mix->pos[0], BUFFER_SIZE, mix->delay, mix->taps, mix->n_taps, d[4], d[4], v4, n_samples); } } } void channelmix_f32_2_7p1_c(struct channelmix *mix, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], uint32_t n_samples) { uint32_t i, n_dst = mix->dst_chan; float **d = (float **)dst; const float **s = (const float **)src; const float v4 = mix->matrix[4][0]; const float v5 = mix->matrix[5][1]; const float v6 = mix->matrix[6][0]; const float v7 = mix->matrix[7][1]; if (SPA_FLAG_IS_SET(mix->flags, CHANNELMIX_FLAG_ZERO)) { for (i = 0; i < n_dst; i++) clear_c(d[i], n_samples); } else { channelmix_f32_2_3p1_c(mix, dst, src, n_samples); vol_c(d[4], s[0], v4, n_samples); vol_c(d[5], s[1], v5, n_samples); if (mix->upmix != CHANNELMIX_UPMIX_PSD) { vol_c(d[6], s[0], v6, n_samples); vol_c(d[7], s[1], v7, n_samples); } else { sub_c(d[6], s[0], s[1], n_samples); delay_convolve_run_c(mix->buffer[1], &mix->pos[1], BUFFER_SIZE, mix->delay, mix->taps, mix->n_taps, d[7], d[6], -v7, n_samples); delay_convolve_run_c(mix->buffer[0], &mix->pos[0], BUFFER_SIZE, mix->delay, mix->taps, mix->n_taps, d[6], d[6], v6, n_samples); } } } /* FL+FR+FC+LFE -> FL+FR */ void channelmix_f32_3p1_2_c(struct channelmix *mix, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], uint32_t n_samples) { uint32_t n; float **d = (float **) dst; const float **s = (const float **) src; const float v0 = mix->matrix[0][0]; const float v1 = mix->matrix[1][1]; const float clev = (mix->matrix[0][2] + mix->matrix[1][2]) * 0.5f; const float llev = (mix->matrix[0][3] + mix->matrix[1][3]) * 0.5f; if (SPA_FLAG_IS_SET(mix->flags, CHANNELMIX_FLAG_ZERO)) { clear_c(d[0], n_samples); clear_c(d[1], n_samples); } else { for (n = 0; n < n_samples; n++) { const float ctr = clev * s[2][n] + llev * s[3][n]; d[0][n] = s[0][n] * v0 + ctr; d[1][n] = s[1][n] * v1 + ctr; } } } /* FL+FR+FC+LFE+SL+SR -> FL+FR */ void channelmix_f32_5p1_2_c(struct channelmix *mix, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], uint32_t n_samples) { uint32_t n; float **d = (float **) dst; const float **s = (const float **) src; const float v0 = mix->matrix[0][0]; const float v1 = mix->matrix[1][1]; const float clev = (mix->matrix[0][2] + mix->matrix[1][2]) * 0.5f; const float llev = (mix->matrix[0][3] + mix->matrix[1][3]) * 0.5f; const float slev0 = mix->matrix[0][4]; const float slev1 = mix->matrix[1][5]; if (SPA_FLAG_IS_SET(mix->flags, CHANNELMIX_FLAG_ZERO)) { clear_c(d[0], n_samples); clear_c(d[1], n_samples); } else { for (n = 0; n < n_samples; n++) { const float ctr = clev * s[2][n] + llev * s[3][n]; d[0][n] = s[0][n] * v0 + ctr + (slev0 * s[4][n]); d[1][n] = s[1][n] * v1 + ctr + (slev1 * s[5][n]); } } } /* FL+FR+FC+LFE+SL+SR -> FL+FR+FC+LFE*/ void channelmix_f32_5p1_3p1_c(struct channelmix *mix, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], uint32_t n_samples) { uint32_t i, n, n_dst = mix->dst_chan; float **d = (float **) dst; const float **s = (const float **) src; const float v0 = mix->matrix[0][0]; const float v1 = mix->matrix[1][1]; const float v2 = mix->matrix[2][2]; const float v3 = mix->matrix[3][3]; const float v4 = mix->matrix[0][4]; const float v5 = mix->matrix[1][5]; if (SPA_FLAG_IS_SET(mix->flags, CHANNELMIX_FLAG_ZERO)) { for (i = 0; i < n_dst; i++) clear_c(d[i], n_samples); } else { for (n = 0; n < n_samples; n++) { d[0][n] = s[0][n] * v0 + s[4][n] * v4; d[1][n] = s[1][n] * v1 + s[5][n] * v5; } vol_c(d[2], s[2], v2, n_samples); vol_c(d[3], s[3], v3, n_samples); } } /* FL+FR+FC+LFE+SL+SR -> FL+FR+RL+RR*/ void channelmix_f32_5p1_4_c(struct channelmix *mix, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], uint32_t n_samples) { uint32_t i, n_dst = mix->dst_chan; float **d = (float **) dst; const float **s = (const float **) src; const float v4 = mix->matrix[2][4]; const float v5 = mix->matrix[3][5]; if (SPA_FLAG_IS_SET(mix->flags, CHANNELMIX_FLAG_ZERO)) { for (i = 0; i < n_dst; i++) clear_c(d[i], n_samples); } else { channelmix_f32_3p1_2_c(mix, dst, src, n_samples); vol_c(d[2], s[4], v4, n_samples); vol_c(d[3], s[5], v5, n_samples); } } #define MASK_7_1 _M(FL)|_M(FR)|_M(FC)|_M(LFE)|_M(SL)|_M(SR)|_M(RL)|_M(RR) /* FL+FR+FC+LFE+SL+SR+RL+RR -> FL+FR */ void channelmix_f32_7p1_2_c(struct channelmix *mix, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], uint32_t n_samples) { uint32_t n; float **d = (float **) dst; const float **s = (const float **) src; const float v0 = mix->matrix[0][0]; const float v1 = mix->matrix[1][1]; const float clev = (mix->matrix[0][2] + mix->matrix[1][2]) * 0.5f; const float llev = (mix->matrix[0][3] + mix->matrix[1][3]) * 0.5f; const float slev0 = mix->matrix[0][4]; const float slev1 = mix->matrix[1][5]; const float rlev0 = mix->matrix[0][6]; const float rlev1 = mix->matrix[1][7]; if (SPA_FLAG_IS_SET(mix->flags, CHANNELMIX_FLAG_ZERO)) { clear_c(d[0], n_samples); clear_c(d[1], n_samples); } else { for (n = 0; n < n_samples; n++) { const float ctr = clev * s[2][n] + llev * s[3][n]; d[0][n] = s[0][n] * v0 + ctr + s[4][n] * slev0 + s[6][n] * rlev0; d[1][n] = s[1][n] * v1 + ctr + s[5][n] * slev1 + s[7][n] * rlev1; } } } /* FL+FR+FC+LFE+SL+SR+RL+RR -> FL+FR+FC+LFE*/ void channelmix_f32_7p1_3p1_c(struct channelmix *mix, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], uint32_t n_samples) { uint32_t i, n, n_dst = mix->dst_chan; float **d = (float **) dst; const float **s = (const float **) src; const float v0 = mix->matrix[0][0]; const float v1 = mix->matrix[1][1]; const float v2 = mix->matrix[2][2]; const float v3 = mix->matrix[3][3]; const float v4 = (mix->matrix[0][4] + mix->matrix[0][6]) * 0.5f; const float v5 = (mix->matrix[1][5] + mix->matrix[1][7]) * 0.5f; if (SPA_FLAG_IS_SET(mix->flags, CHANNELMIX_FLAG_ZERO)) { for (i = 0; i < n_dst; i++) clear_c(d[i], n_samples); } else { for (n = 0; n < n_samples; n++) { d[0][n] = s[0][n] * v0 + (s[4][n] + s[6][n]) * v4; d[1][n] = s[1][n] * v1 + (s[5][n] + s[7][n]) * v5; } vol_c(d[2], s[2], v2, n_samples); vol_c(d[3], s[3], v3, n_samples); } } /* FL+FR+FC+LFE+SL+SR+RL+RR -> FL+FR+RL+RR*/ void channelmix_f32_7p1_4_c(struct channelmix *mix, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], uint32_t n_samples) { uint32_t i, n, n_dst = mix->dst_chan; float **d = (float **) dst; const float **s = (const float **) src; const float v0 = mix->matrix[0][0]; const float v1 = mix->matrix[1][1]; const float clev = (mix->matrix[0][2] + mix->matrix[1][2]) * 0.5f; const float llev = (mix->matrix[0][3] + mix->matrix[1][3]) * 0.5f; const float slev0 = mix->matrix[2][4]; const float slev1 = mix->matrix[3][5]; const float rlev0 = mix->matrix[2][6]; const float rlev1 = mix->matrix[3][7]; if (SPA_FLAG_IS_SET(mix->flags, CHANNELMIX_FLAG_ZERO)) { for (i = 0; i < n_dst; i++) clear_c(d[i], n_samples); } else { for (n = 0; n < n_samples; n++) { const float ctr = s[2][n] * clev + s[3][n] * llev; const float sl = s[4][n] * slev0; const float sr = s[5][n] * slev1; d[0][n] = s[0][n] * v0 + ctr + sl; d[1][n] = s[1][n] * v1 + ctr + sr; d[2][n] = s[6][n] * rlev0 + sl; d[3][n] = s[7][n] * rlev1 + sr; } } } channelmix-ops-sse.c000066400000000000000000000506241511204443500312550ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/audioconvert/* Spa */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include "channelmix-ops.h" #include #include #include static inline void clear_sse(float *d, uint32_t n_samples) { memset(d, 0, n_samples * sizeof(float)); } static inline void copy_sse(float *d, const float *s, uint32_t n_samples) { spa_memcpy(d, s, n_samples * sizeof(float)); } static inline void vol_sse(float *d, const float *s, float vol, uint32_t n_samples) { uint32_t n, unrolled; if (vol == 0.0f) { clear_sse(d, n_samples); } else if (vol == 1.0f) { copy_sse(d, s, n_samples); } else { __m128 t[4]; const __m128 v = _mm_set1_ps(vol); if (SPA_IS_ALIGNED(d, 16) && SPA_IS_ALIGNED(s, 16)) unrolled = n_samples & ~15; else unrolled = 0; for(n = 0; n < unrolled; n += 16) { t[0] = _mm_load_ps(&s[n]); t[1] = _mm_load_ps(&s[n+4]); t[2] = _mm_load_ps(&s[n+8]); t[3] = _mm_load_ps(&s[n+12]); _mm_store_ps(&d[n], _mm_mul_ps(t[0], v)); _mm_store_ps(&d[n+4], _mm_mul_ps(t[1], v)); _mm_store_ps(&d[n+8], _mm_mul_ps(t[2], v)); _mm_store_ps(&d[n+12], _mm_mul_ps(t[3], v)); } for(; n < n_samples; n++) _mm_store_ss(&d[n], _mm_mul_ss(_mm_load_ss(&s[n]), v)); } } static inline void conv_sse(float *d, const float **s, float *c, uint32_t n_c, uint32_t n_samples) { __m128 mi[n_c], sum[2]; uint32_t n, j, unrolled; bool aligned = true; for (j = 0; j < n_c; j++) { mi[j] = _mm_set1_ps(c[j]); aligned &= SPA_IS_ALIGNED(s[j], 16); } if (aligned && SPA_IS_ALIGNED(d, 16)) unrolled = n_samples & ~7; else unrolled = 0; for (n = 0; n < unrolled; n += 8) { sum[0] = sum[1] = _mm_setzero_ps(); for (j = 0; j < n_c; j++) { sum[0] = _mm_add_ps(sum[0], _mm_mul_ps(_mm_load_ps(&s[j][n + 0]), mi[j])); sum[1] = _mm_add_ps(sum[1], _mm_mul_ps(_mm_load_ps(&s[j][n + 4]), mi[j])); } _mm_store_ps(&d[n + 0], sum[0]); _mm_store_ps(&d[n + 4], sum[1]); } for (; n < n_samples; n++) { sum[0] = _mm_setzero_ps(); for (j = 0; j < n_c; j++) sum[0] = _mm_add_ss(sum[0], _mm_mul_ss(_mm_load_ss(&s[j][n]), mi[j])); _mm_store_ss(&d[n], sum[0]); } } static inline void avg_sse(float *d, const float *s0, const float *s1, uint32_t n_samples) { uint32_t n, unrolled; __m128 half = _mm_set1_ps(0.5f); if (SPA_IS_ALIGNED(d, 16) && SPA_IS_ALIGNED(s0, 16) && SPA_IS_ALIGNED(s1, 16)) unrolled = n_samples & ~7; else unrolled = 0; for (n = 0; n < unrolled; n += 8) { _mm_store_ps(&d[n + 0], _mm_mul_ps( _mm_add_ps( _mm_load_ps(&s0[n + 0]), _mm_load_ps(&s1[n + 0])), half)); _mm_store_ps(&d[n + 4], _mm_mul_ps( _mm_add_ps( _mm_load_ps(&s0[n + 4]), _mm_load_ps(&s1[n + 4])), half)); } for (; n < n_samples; n++) _mm_store_ss(&d[n], _mm_mul_ss( _mm_add_ss( _mm_load_ss(&s0[n]), _mm_load_ss(&s1[n])), half)); } static inline void sub_sse(float *d, const float *s0, const float *s1, uint32_t n_samples) { uint32_t n, unrolled; if (SPA_IS_ALIGNED(d, 16) && SPA_IS_ALIGNED(s0, 16) && SPA_IS_ALIGNED(s1, 16)) unrolled = n_samples & ~7; else unrolled = 0; for (n = 0; n < unrolled; n += 8) { _mm_store_ps(&d[n + 0], _mm_sub_ps(_mm_load_ps(&s0[n + 0]), _mm_load_ps(&s1[n + 0]))); _mm_store_ps(&d[n + 4], _mm_sub_ps(_mm_load_ps(&s0[n + 4]), _mm_load_ps(&s1[n + 4]))); } for (; n < n_samples; n++) _mm_store_ss(&d[n], _mm_sub_ss(_mm_load_ss(&s0[n]), _mm_load_ss(&s1[n]))); } void channelmix_copy_sse(struct channelmix *mix, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], uint32_t n_samples) { uint32_t i, n_dst = mix->dst_chan; float **d = (float **)dst; const float **s = (const float **)src; for (i = 0; i < n_dst; i++) vol_sse(d[i], s[i], mix->matrix[i][i], n_samples); } static void lr4_process_sse(struct lr4 *lr4, float *dst, const float *src, const float vol, int samples) { __m128 x, y, z; __m128 b012; __m128 a12; __m128 x12, y12, v; int i; if (vol == 0.0f || !lr4->active) { vol_sse(dst, src, vol, samples); return; } b012 = _mm_setr_ps(lr4->bq.b0, lr4->bq.b1, lr4->bq.b2, 0.0f); /* b0 b1 b2 0 */ a12 = _mm_setr_ps(0.0f, lr4->bq.a1, lr4->bq.a2, 0.0f); /* 0 a1 a2 0 */ x12 = _mm_setr_ps(lr4->x1, lr4->x2, 0.0f, 0.0f); /* x1 x2 0 0 */ y12 = _mm_setr_ps(lr4->y1, lr4->y2, 0.0f, 0.0f); /* y1 y2 0 0 */ v = _mm_setr_ps(vol, vol, 0.0f, 0.0f); for (i = 0; i < samples; i++) { x = _mm_load1_ps(&src[i]); /* x x x x */ z = _mm_mul_ps(x, b012); /* b0*x b1*x b2*x 0 */ z = _mm_add_ps(z, x12); /* b0*x+x1 b1*x+x2 b2*x 0 */ y = _mm_shuffle_ps(z, z, _MM_SHUFFLE(0,0,0,0)); /* b0*x+x1 b0*x+x1 b0*x+x1 b0*x+x1 = y*/ x = _mm_mul_ps(y, a12); /* 0 a1*y a2*y 0 */ x = _mm_sub_ps(z, x); /* y x1 x2 0 */ x12 = _mm_shuffle_ps(x, x, _MM_SHUFFLE(3,3,2,1)); /* x1 x2 0 0*/ z = _mm_mul_ps(y, b012); z = _mm_add_ps(z, y12); x = _mm_shuffle_ps(z, z, _MM_SHUFFLE(0,0,0,0)); y = _mm_mul_ps(x, a12); y = _mm_sub_ps(z, y); y12 = _mm_shuffle_ps(y, y, _MM_SHUFFLE(3,3,2,1)); x = _mm_mul_ps(x, v); _mm_store_ss(&dst[i], x); } #define F(x) (isnormal(x) ? (x) : 0.0f) lr4->x1 = F(x12[0]); lr4->x2 = F(x12[1]); lr4->y1 = F(y12[0]); lr4->y2 = F(y12[1]); #undef F } static void lr4_process_2_sse(struct lr4 *lr40, struct lr4 *lr41, float *dst0, float *dst1, const float *src0, const float *src1, const float vol0, const float vol1, uint32_t samples) { __m128 x, y, z; __m128 b0, b1, b2; __m128 a1, a2; __m128 x1, x2; __m128 y1, y2, v; uint32_t i; b0 = _mm_setr_ps(lr40->bq.b0, lr41->bq.b0, 0.0f, 0.0f); b1 = _mm_setr_ps(lr40->bq.b1, lr41->bq.b1, 0.0f, 0.0f); b2 = _mm_setr_ps(lr40->bq.b2, lr41->bq.b2, 0.0f, 0.0f); a1 = _mm_setr_ps(lr40->bq.a1, lr41->bq.a1, 0.0f, 0.0f); a2 = _mm_setr_ps(lr40->bq.a2, lr41->bq.a2, 0.0f, 0.0f); x1 = _mm_setr_ps(lr40->x1, lr41->x1, 0.0f, 0.0f); x2 = _mm_setr_ps(lr40->x2, lr41->x2, 0.0f, 0.0f); y1 = _mm_setr_ps(lr40->y1, lr41->y1, 0.0f, 0.0f); y2 = _mm_setr_ps(lr40->y2, lr41->y2, 0.0f, 0.0f); v = _mm_setr_ps(vol0, vol1, 0.0f, 0.0f); for (i = 0; i < samples; i++) { x = _mm_setr_ps(src0[i], src1[i], 0.0f, 0.0f); y = _mm_mul_ps(x, b0); /* y = x * b0 */ y = _mm_add_ps(y, x1); /* y = x * b0 + x1*/ z = _mm_mul_ps(y, a1); /* z = a1 * y */ x1 = _mm_mul_ps(x, b1); /* x1 = x * b1 */ x1 = _mm_add_ps(x1, x2); /* x1 = x * b1 + x2*/ x1 = _mm_sub_ps(x1, z); /* x1 = x * b1 + x2 - a1 * y*/ z = _mm_mul_ps(y, a2); /* z = a2 * y */ x2 = _mm_mul_ps(x, b2); /* x2 = x * b2 */ x2 = _mm_sub_ps(x2, z); /* x2 = x * b2 - a2 * y*/ x = _mm_mul_ps(y, b0); /* y = x * b0 */ x = _mm_add_ps(x, y1); /* y = x * b0 + x1*/ z = _mm_mul_ps(x, a1); /* z = a1 * y */ y1 = _mm_mul_ps(y, b1); /* x1 = x * b1 */ y1 = _mm_add_ps(y1, y2); /* x1 = x * b1 + x2*/ y1 = _mm_sub_ps(y1, z); /* x1 = x * b1 + x2 - a1 * y*/ z = _mm_mul_ps(x, a2); /* z = a2 * y */ y2 = _mm_mul_ps(y, b2); /* x2 = x * b2 */ y2 = _mm_sub_ps(y2, z); /* x2 = x * b2 - a2 * y*/ x = _mm_mul_ps(x, v); dst0[i] = x[0]; dst1[i] = x[1]; } #define F(x) (isnormal(x) ? (x) : 0.0f) lr40->x1 = F(x1[0]); lr40->x2 = F(x2[0]); lr40->y1 = F(y1[0]); lr40->y2 = F(y2[0]); lr41->x1 = F(x1[1]); lr41->x2 = F(x2[1]); lr41->y1 = F(y1[1]); lr41->y2 = F(y2[1]); #undef F } static inline void convolver_run(const float *src, float *dst, const float *taps, uint32_t n_taps, const __m128 vol) { __m128 t[1], sum[4]; uint32_t i; sum[0] = _mm_setzero_ps(); for(i = 0; i < n_taps; i+=4) { t[0] = _mm_loadu_ps(&src[i]); sum[0] = _mm_add_ps(sum[0], _mm_mul_ps(_mm_load_ps(&taps[i]), t[0])); } sum[0] = _mm_add_ps(sum[0], _mm_movehl_ps(sum[0], sum[0])); sum[0] = _mm_add_ss(sum[0], _mm_shuffle_ps(sum[0], sum[0], 0x55)); t[0] = _mm_mul_ss(sum[0], vol); _mm_store_ss(dst, t[0]); } static inline void delay_convolve_run_sse(float *buffer, uint32_t *pos, uint32_t n_buffer, uint32_t delay, const float *taps, uint32_t n_taps, float *dst, const float *src, const float vol, uint32_t n_samples) { __m128 t[1]; const __m128 v = _mm_set1_ps(vol); uint32_t i; uint32_t w = *pos; uint32_t o = n_buffer - delay - n_taps-1; uint32_t n, unrolled; if (SPA_IS_ALIGNED(src, 16) && SPA_IS_ALIGNED(dst, 16)) unrolled = n_samples & ~3; else unrolled = 0; if (n_taps == 1) { for(n = 0; n < unrolled; n += 4) { t[0] = _mm_load_ps(&src[n]); _mm_storeu_ps(&buffer[w], t[0]); _mm_storeu_ps(&buffer[w+n_buffer], t[0]); t[0] = _mm_loadu_ps(&buffer[w+o]); t[0] = _mm_mul_ps(t[0], v); _mm_store_ps(&dst[n], t[0]); w += 4; if (w >= n_buffer) { w -= n_buffer; t[0] = _mm_load_ps(&buffer[n_buffer]); _mm_store_ps(&buffer[0], t[0]); } } for(; n < n_samples; n++) { t[0] = _mm_load_ss(&src[n]); _mm_store_ss(&buffer[w], t[0]); _mm_store_ss(&buffer[w+n_buffer], t[0]); t[0] = _mm_load_ss(&buffer[w+o]); t[0] = _mm_mul_ss(t[0], v); _mm_store_ss(&dst[n], t[0]); w = w + 1 >= n_buffer ? 0 : w + 1; } } else { for(n = 0; n < unrolled; n += 4) { t[0] = _mm_load_ps(&src[n]); _mm_storeu_ps(&buffer[w], t[0]); _mm_storeu_ps(&buffer[w+n_buffer], t[0]); for(i = 0; i < 4; i++) convolver_run(&buffer[w+o+i], &dst[n+i], taps, n_taps, v); w += 4; if (w >= n_buffer) { w -= n_buffer; t[0] = _mm_load_ps(&buffer[n_buffer]); _mm_store_ps(&buffer[0], t[0]); } } for(; n < n_samples; n++) { t[0] = _mm_load_ss(&src[n]); _mm_store_ss(&buffer[w], t[0]); _mm_store_ss(&buffer[w+n_buffer], t[0]); convolver_run(&buffer[w+o], &dst[n], taps, n_taps, v); w = w + 1 >= n_buffer ? 0 : w + 1; } } *pos = w; } void channelmix_f32_n_m_sse(struct channelmix *mix, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], uint32_t n_samples) { float **d = (float **) dst; const float **s = (const float **) src; uint32_t i, j, n_dst = mix->dst_chan, n_src = mix->src_chan; for (i = 0; i < n_dst; i++) { float *di = d[i]; float mj[n_src]; const float *sj[n_src]; uint32_t n_j = 0; for (j = 0; j < n_src; j++) { if (mix->matrix[i][j] == 0.0f) continue; mj[n_j] = mix->matrix[i][j]; sj[n_j++] = s[j]; } if (n_j == 0) { clear_sse(di, n_samples); } else if (n_j == 1) { lr4_process_sse(&mix->lr4[i], di, sj[0], mj[0], n_samples); } else { conv_sse(di, sj, mj, n_j, n_samples); lr4_process_sse(&mix->lr4[i], di, di, 1.0f, n_samples); } } } void channelmix_f32_2_3p1_sse(struct channelmix *mix, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], uint32_t n_samples) { uint32_t i, n, unrolled, n_dst = mix->dst_chan; float **d = (float **)dst; const float **s = (const float **)src; const float v0 = mix->matrix[0][0]; const float v1 = mix->matrix[1][1]; const float v2 = (mix->matrix[2][0] + mix->matrix[2][1]) * 0.5f; const float v3 = (mix->matrix[3][0] + mix->matrix[3][1]) * 0.5f; if (SPA_FLAG_IS_SET(mix->flags, CHANNELMIX_FLAG_ZERO)) { for (i = 0; i < n_dst; i++) clear_sse(d[i], n_samples); } else { if (mix->widen == 0.0f) { vol_sse(d[0], s[0], v0, n_samples); vol_sse(d[1], s[1], v1, n_samples); avg_sse(d[2], s[0], s[1], n_samples); } else { const __m128 mv0 = _mm_set1_ps(mix->matrix[0][0]); const __m128 mv1 = _mm_set1_ps(mix->matrix[1][1]); const __m128 mw = _mm_set1_ps(mix->widen); const __m128 mh = _mm_set1_ps(0.5f); __m128 t0[1], t1[1], w[1], c[1]; if (SPA_IS_ALIGNED(s[0], 16) && SPA_IS_ALIGNED(s[1], 16) && SPA_IS_ALIGNED(d[0], 16) && SPA_IS_ALIGNED(d[1], 16) && SPA_IS_ALIGNED(d[2], 16)) unrolled = n_samples & ~3; else unrolled = 0; for(n = 0; n < unrolled; n += 4) { t0[0] = _mm_load_ps(&s[0][n]); t1[0] = _mm_load_ps(&s[1][n]); c[0] = _mm_add_ps(t0[0], t1[0]); w[0] = _mm_mul_ps(c[0], mw); _mm_store_ps(&d[0][n], _mm_mul_ps(_mm_sub_ps(t0[0], w[0]), mv0)); _mm_store_ps(&d[1][n], _mm_mul_ps(_mm_sub_ps(t1[0], w[0]), mv1)); _mm_store_ps(&d[2][n], _mm_mul_ps(c[0], mh)); } for (; n < n_samples; n++) { t0[0] = _mm_load_ss(&s[0][n]); t1[0] = _mm_load_ss(&s[1][n]); c[0] = _mm_add_ss(t0[0], t1[0]); w[0] = _mm_mul_ss(c[0], mw); _mm_store_ss(&d[0][n], _mm_mul_ss(_mm_sub_ss(t0[0], w[0]), mv0)); _mm_store_ss(&d[1][n], _mm_mul_ss(_mm_sub_ss(t1[0], w[0]), mv1)); _mm_store_ss(&d[2][n], _mm_mul_ss(c[0], mh)); } } lr4_process_2_sse(&mix->lr4[3], &mix->lr4[2], d[3], d[2], d[2], d[2], v3, v2, n_samples); } } void channelmix_f32_2_5p1_sse(struct channelmix *mix, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], uint32_t n_samples) { uint32_t i, n_dst = mix->dst_chan; float **d = (float **)dst; const float **s = (const float **)src; const float v4 = mix->matrix[4][0]; const float v5 = mix->matrix[5][1]; if (SPA_FLAG_IS_SET(mix->flags, CHANNELMIX_FLAG_ZERO)) { for (i = 0; i < n_dst; i++) clear_sse(d[i], n_samples); } else { channelmix_f32_2_3p1_sse(mix, dst, src, n_samples); if (mix->upmix != CHANNELMIX_UPMIX_PSD) { vol_sse(d[4], s[0], v4, n_samples); vol_sse(d[5], s[1], v5, n_samples); } else { sub_sse(d[4], s[0], s[1], n_samples); delay_convolve_run_sse(mix->buffer[1], &mix->pos[1], BUFFER_SIZE, mix->delay, mix->taps, mix->n_taps, d[5], d[4], -v5, n_samples); delay_convolve_run_sse(mix->buffer[0], &mix->pos[0], BUFFER_SIZE, mix->delay, mix->taps, mix->n_taps, d[4], d[4], v4, n_samples); } } } void channelmix_f32_2_7p1_sse(struct channelmix *mix, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], uint32_t n_samples) { uint32_t i, n_dst = mix->dst_chan; float **d = (float **)dst; const float **s = (const float **)src; const float v4 = mix->matrix[4][0]; const float v5 = mix->matrix[5][1]; const float v6 = mix->matrix[6][0]; const float v7 = mix->matrix[7][1]; if (SPA_FLAG_IS_SET(mix->flags, CHANNELMIX_FLAG_ZERO)) { for (i = 0; i < n_dst; i++) clear_sse(d[i], n_samples); } else { channelmix_f32_2_3p1_sse(mix, dst, src, n_samples); vol_sse(d[4], s[0], v4, n_samples); vol_sse(d[5], s[1], v5, n_samples); if (mix->upmix != CHANNELMIX_UPMIX_PSD) { vol_sse(d[6], s[0], v6, n_samples); vol_sse(d[7], s[1], v7, n_samples); } else { sub_sse(d[6], s[0], s[1], n_samples); delay_convolve_run_sse(mix->buffer[1], &mix->pos[1], BUFFER_SIZE, mix->delay, mix->taps, mix->n_taps, d[7], d[6], -v7, n_samples); delay_convolve_run_sse(mix->buffer[0], &mix->pos[0], BUFFER_SIZE, mix->delay, mix->taps, mix->n_taps, d[6], d[6], v6, n_samples); } } } /* FL+FR+FC+LFE -> FL+FR */ void channelmix_f32_3p1_2_sse(struct channelmix *mix, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], uint32_t n_samples) { float **d = (float **) dst; const float **s = (const float **) src; const float m0 = mix->matrix[0][0]; const float m1 = mix->matrix[1][1]; const float m2 = (mix->matrix[0][2] + mix->matrix[1][2]) * 0.5f; const float m3 = (mix->matrix[0][3] + mix->matrix[1][3]) * 0.5f; if (m0 == 0.0f && m1 == 0.0f && m2 == 0.0f && m3 == 0.0f) { clear_sse(d[0], n_samples); clear_sse(d[1], n_samples); } else { uint32_t n, unrolled; const __m128 v0 = _mm_set1_ps(m0); const __m128 v1 = _mm_set1_ps(m1); const __m128 clev = _mm_set1_ps(m2); const __m128 llev = _mm_set1_ps(m3); __m128 ctr; if (SPA_IS_ALIGNED(s[0], 16) && SPA_IS_ALIGNED(s[1], 16) && SPA_IS_ALIGNED(s[2], 16) && SPA_IS_ALIGNED(s[3], 16) && SPA_IS_ALIGNED(d[0], 16) && SPA_IS_ALIGNED(d[1], 16)) unrolled = n_samples & ~3; else unrolled = 0; for(n = 0; n < unrolled; n += 4) { ctr = _mm_add_ps( _mm_mul_ps(_mm_load_ps(&s[2][n]), clev), _mm_mul_ps(_mm_load_ps(&s[3][n]), llev)); _mm_store_ps(&d[0][n], _mm_add_ps(_mm_mul_ps(_mm_load_ps(&s[0][n]), v0), ctr)); _mm_store_ps(&d[1][n], _mm_add_ps(_mm_mul_ps(_mm_load_ps(&s[1][n]), v1), ctr)); } for(; n < n_samples; n++) { ctr = _mm_add_ss(_mm_mul_ss(_mm_load_ss(&s[2][n]), clev), _mm_mul_ss(_mm_load_ss(&s[3][n]), llev)); _mm_store_ss(&d[0][n], _mm_add_ss(_mm_mul_ss(_mm_load_ss(&s[0][n]), v0), ctr)); _mm_store_ss(&d[1][n], _mm_add_ss(_mm_mul_ss(_mm_load_ss(&s[1][n]), v1), ctr)); } } } /* FL+FR+FC+LFE+SL+SR -> FL+FR */ void channelmix_f32_5p1_2_sse(struct channelmix *mix, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], uint32_t n_samples) { uint32_t n, unrolled; float **d = (float **) dst; const float **s = (const float **) src; const float m00 = mix->matrix[0][0]; const float m11 = mix->matrix[1][1]; const __m128 clev = _mm_set1_ps((mix->matrix[0][2] + mix->matrix[1][2]) * 0.5f); const __m128 llev = _mm_set1_ps((mix->matrix[0][3] + mix->matrix[1][3]) * 0.5f); const __m128 slev0 = _mm_set1_ps(mix->matrix[0][4]); const __m128 slev1 = _mm_set1_ps(mix->matrix[1][5]); __m128 in, ctr; if (SPA_IS_ALIGNED(s[0], 16) && SPA_IS_ALIGNED(s[1], 16) && SPA_IS_ALIGNED(s[2], 16) && SPA_IS_ALIGNED(s[3], 16) && SPA_IS_ALIGNED(s[4], 16) && SPA_IS_ALIGNED(s[5], 16) && SPA_IS_ALIGNED(d[0], 16) && SPA_IS_ALIGNED(d[1], 16)) unrolled = n_samples & ~3; else unrolled = 0; if (SPA_FLAG_IS_SET(mix->flags, CHANNELMIX_FLAG_ZERO)) { clear_sse(d[0], n_samples); clear_sse(d[1], n_samples); } else { const __m128 v0 = _mm_set1_ps(m00); const __m128 v1 = _mm_set1_ps(m11); for(n = 0; n < unrolled; n += 4) { ctr = _mm_add_ps(_mm_mul_ps(_mm_load_ps(&s[2][n]), clev), _mm_mul_ps(_mm_load_ps(&s[3][n]), llev)); in = _mm_mul_ps(_mm_load_ps(&s[4][n]), slev0); in = _mm_add_ps(in, ctr); in = _mm_add_ps(in, _mm_mul_ps(_mm_load_ps(&s[0][n]), v0)); _mm_store_ps(&d[0][n], in); in = _mm_mul_ps(_mm_load_ps(&s[5][n]), slev1); in = _mm_add_ps(in, ctr); in = _mm_add_ps(in, _mm_mul_ps(_mm_load_ps(&s[1][n]), v1)); _mm_store_ps(&d[1][n], in); } for(; n < n_samples; n++) { ctr = _mm_mul_ss(_mm_load_ss(&s[2][n]), clev); ctr = _mm_add_ss(ctr, _mm_mul_ss(_mm_load_ss(&s[3][n]), llev)); in = _mm_mul_ss(_mm_load_ss(&s[4][n]), slev0); in = _mm_add_ss(in, ctr); in = _mm_add_ss(in, _mm_mul_ss(_mm_load_ss(&s[0][n]), v0)); _mm_store_ss(&d[0][n], in); in = _mm_mul_ss(_mm_load_ss(&s[5][n]), slev1); in = _mm_add_ss(in, ctr); in = _mm_add_ss(in, _mm_mul_ss(_mm_load_ss(&s[1][n]), v1)); _mm_store_ss(&d[1][n], in); } } } /* FL+FR+FC+LFE+SL+SR -> FL+FR+FC+LFE*/ void channelmix_f32_5p1_3p1_sse(struct channelmix *mix, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], uint32_t n_samples) { uint32_t i, n, unrolled, n_dst = mix->dst_chan; float **d = (float **) dst; const float **s = (const float **) src; if (SPA_IS_ALIGNED(s[0], 16) && SPA_IS_ALIGNED(s[1], 16) && SPA_IS_ALIGNED(s[2], 16) && SPA_IS_ALIGNED(s[3], 16) && SPA_IS_ALIGNED(s[4], 16) && SPA_IS_ALIGNED(s[5], 16) && SPA_IS_ALIGNED(d[0], 16) && SPA_IS_ALIGNED(d[1], 16) && SPA_IS_ALIGNED(d[2], 16) && SPA_IS_ALIGNED(d[3], 16)) unrolled = n_samples & ~3; else unrolled = 0; if (SPA_FLAG_IS_SET(mix->flags, CHANNELMIX_FLAG_ZERO)) { for (i = 0; i < n_dst; i++) clear_sse(d[i], n_samples); } else { const __m128 v0 = _mm_set1_ps(mix->matrix[0][0]); const __m128 v1 = _mm_set1_ps(mix->matrix[1][1]); const __m128 slev0 = _mm_set1_ps(mix->matrix[0][4]); const __m128 slev1 = _mm_set1_ps(mix->matrix[1][5]); for(n = 0; n < unrolled; n += 4) { _mm_store_ps(&d[0][n], _mm_add_ps( _mm_mul_ps(_mm_load_ps(&s[0][n]), v0), _mm_mul_ps(_mm_load_ps(&s[4][n]), slev0))); _mm_store_ps(&d[1][n], _mm_add_ps( _mm_mul_ps(_mm_load_ps(&s[1][n]), v1), _mm_mul_ps(_mm_load_ps(&s[5][n]), slev1))); } for(; n < n_samples; n++) { _mm_store_ss(&d[0][n], _mm_add_ss( _mm_mul_ss(_mm_load_ss(&s[0][n]), v0), _mm_mul_ss(_mm_load_ss(&s[4][n]), slev0))); _mm_store_ss(&d[1][n], _mm_add_ss( _mm_mul_ss(_mm_load_ss(&s[1][n]), v1), _mm_mul_ss(_mm_load_ss(&s[5][n]), slev1))); } vol_sse(d[2], s[2], mix->matrix[2][2], n_samples); vol_sse(d[3], s[3], mix->matrix[3][3], n_samples); } } /* FL+FR+FC+LFE+SL+SR -> FL+FR+RL+RR*/ void channelmix_f32_5p1_4_sse(struct channelmix *mix, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], uint32_t n_samples) { uint32_t i, n_dst = mix->dst_chan; float **d = (float **) dst; const float **s = (const float **) src; const float v4 = mix->matrix[2][4]; const float v5 = mix->matrix[3][5]; if (SPA_FLAG_IS_SET(mix->flags, CHANNELMIX_FLAG_ZERO)) { for (i = 0; i < n_dst; i++) clear_sse(d[i], n_samples); } else { channelmix_f32_3p1_2_sse(mix, dst, src, n_samples); vol_sse(d[2], s[4], v4, n_samples); vol_sse(d[3], s[5], v5, n_samples); } } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/audioconvert/channelmix-ops.c000066400000000000000000000602441511204443500305430ustar00rootroot00000000000000/* Spa */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #include #include #include #include #include "channelmix-ops.h" #include "hilbert.h" #define ANY ((uint32_t)-1) #define EQ ((uint32_t)-2) typedef void (*channelmix_func_t) (struct channelmix *mix, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], uint32_t n_samples); #define MAKE(sc,sm,dc,dm,func,...) \ { sc, sm, dc, dm, func, #func, __VA_ARGS__ } static const struct channelmix_info { uint32_t src_chan; uint64_t src_mask; uint32_t dst_chan; uint64_t dst_mask; channelmix_func_t process; const char *name; uint32_t cpu_flags; } channelmix_table[] = { #if defined (HAVE_SSE) MAKE(2, MASK_MONO, 2, MASK_MONO, channelmix_copy_sse, SPA_CPU_FLAG_SSE), MAKE(2, MASK_STEREO, 2, MASK_STEREO, channelmix_copy_sse, SPA_CPU_FLAG_SSE), MAKE(EQ, 0, EQ, 0, channelmix_copy_sse, SPA_CPU_FLAG_SSE), #endif MAKE(2, MASK_MONO, 2, MASK_MONO, channelmix_copy_c), MAKE(2, MASK_STEREO, 2, MASK_STEREO, channelmix_copy_c), MAKE(EQ, 0, EQ, 0, channelmix_copy_c), MAKE(1, MASK_MONO, 2, MASK_STEREO, channelmix_f32_1_2_c), MAKE(2, MASK_STEREO, 1, MASK_MONO, channelmix_f32_2_1_c), MAKE(4, MASK_QUAD, 1, MASK_MONO, channelmix_f32_4_1_c), MAKE(4, MASK_3_1, 1, MASK_MONO, channelmix_f32_4_1_c), MAKE(2, MASK_STEREO, 4, MASK_QUAD, channelmix_f32_2_4_c), #if defined (HAVE_SSE) MAKE(2, MASK_STEREO, 4, MASK_3_1, channelmix_f32_2_3p1_sse, SPA_CPU_FLAG_SSE), #endif MAKE(2, MASK_STEREO, 4, MASK_3_1, channelmix_f32_2_3p1_c), #if defined (HAVE_SSE) MAKE(2, MASK_STEREO, 6, MASK_5_1, channelmix_f32_2_5p1_sse, SPA_CPU_FLAG_SSE), #endif MAKE(2, MASK_STEREO, 6, MASK_5_1, channelmix_f32_2_5p1_c), #if defined (HAVE_SSE) MAKE(2, MASK_STEREO, 8, MASK_7_1, channelmix_f32_2_7p1_sse, SPA_CPU_FLAG_SSE), #endif MAKE(2, MASK_STEREO, 8, MASK_7_1, channelmix_f32_2_7p1_c), #if defined (HAVE_SSE) MAKE(4, MASK_3_1, 2, MASK_STEREO, channelmix_f32_3p1_2_sse, SPA_CPU_FLAG_SSE), #endif MAKE(4, MASK_3_1, 2, MASK_STEREO, channelmix_f32_3p1_2_c), #if defined (HAVE_SSE) MAKE(6, MASK_5_1, 2, MASK_STEREO, channelmix_f32_5p1_2_sse, SPA_CPU_FLAG_SSE), #endif MAKE(6, MASK_5_1, 2, MASK_STEREO, channelmix_f32_5p1_2_c), #if defined (HAVE_SSE) MAKE(6, MASK_5_1, 4, MASK_QUAD, channelmix_f32_5p1_4_sse, SPA_CPU_FLAG_SSE), #endif MAKE(6, MASK_5_1, 4, MASK_QUAD, channelmix_f32_5p1_4_c), #if defined (HAVE_SSE) MAKE(6, MASK_5_1, 4, MASK_3_1, channelmix_f32_5p1_3p1_sse, SPA_CPU_FLAG_SSE), #endif MAKE(6, MASK_5_1, 4, MASK_3_1, channelmix_f32_5p1_3p1_c), MAKE(8, MASK_7_1, 2, MASK_STEREO, channelmix_f32_7p1_2_c), MAKE(8, MASK_7_1, 4, MASK_QUAD, channelmix_f32_7p1_4_c), MAKE(8, MASK_7_1, 4, MASK_3_1, channelmix_f32_7p1_3p1_c), #if defined (HAVE_SSE) MAKE(ANY, 0, ANY, 0, channelmix_f32_n_m_sse, SPA_CPU_FLAG_SSE), #endif MAKE(ANY, 0, ANY, 0, channelmix_f32_n_m_c), }; #undef MAKE #define MATCH_CHAN(a,b) ((a) == ANY || (a) == (b)) #define MATCH_CPU_FLAGS(a,b) ((a) == 0 || ((a) & (b)) == a) #define MATCH_MASK(a,b) ((a) == 0 || ((a) & (b)) == (b)) static const struct channelmix_info *find_channelmix_info(uint32_t src_chan, uint64_t src_mask, uint32_t dst_chan, uint64_t dst_mask, uint32_t cpu_flags) { SPA_FOR_EACH_ELEMENT_VAR(channelmix_table, info) { if (!MATCH_CPU_FLAGS(info->cpu_flags, cpu_flags)) continue; if (src_chan == dst_chan && src_mask == dst_mask) return info; if (MATCH_CHAN(info->src_chan, src_chan) && MATCH_CHAN(info->dst_chan, dst_chan) && MATCH_MASK(info->src_mask, src_mask) && MATCH_MASK(info->dst_mask, dst_mask)) return info; } return NULL; } #define SQRT3_2 1.224744871f /* sqrt(3/2) */ #define SQRT1_2 0.707106781f #define SQRT2 1.414213562f #define MATRIX_NORMAL 0 #define MATRIX_DOLBY 1 #define MATRIX_DPLII 2 #define _SH 2 #define _CH(ch) ((SPA_AUDIO_CHANNEL_ ## ch)-_SH) #define _MASK(ch) (1ULL << _CH(ch)) #define FRONT (_MASK(FC)) #define STEREO (_MASK(FL)|_MASK(FR)) #define REAR (_MASK(RL)|_MASK(RR)) #define SIDE (_MASK(SL)|_MASK(SR)) #define CHANNEL_BITS (64u) static uint32_t mask_to_ch(struct channelmix *mix, uint64_t mask) { uint32_t ch = 0; while (mask > 1) { ch++; mask >>= 1; } return ch; } static void distribute_mix(struct channelmix *mix, float matrix[MAX_CHANNELS][MAX_CHANNELS], uint64_t mask) { uint32_t i, ch = mask_to_ch(mix, mask); for (i = 0; i < MAX_CHANNELS; i++) matrix[i][ch]= 1.0f; } static void average_mix(struct channelmix *mix, float matrix[MAX_CHANNELS][MAX_CHANNELS], uint64_t mask) { uint32_t i, ch = mask_to_ch(mix, mask); for (i = 0; i < MAX_CHANNELS; i++) matrix[ch][i]= 1.0f; } static void pair_mix(float matrix[MAX_CHANNELS][MAX_CHANNELS]) { uint32_t i; for (i = 0; i < MAX_CHANNELS; i++) matrix[i][i]= 1.0f; } static bool match_mix(struct channelmix *mix, float matrix[MAX_CHANNELS][MAX_CHANNELS], uint64_t src_mask, uint64_t dst_mask) { bool matched = false; uint32_t i; for (i = 0; i < CHANNEL_BITS; i++) { if ((src_mask & dst_mask & (1ULL << i))) { spa_log_info(mix->log, "matched channel %u (%f)", i, 1.0f); matrix[i][i] = 1.0f; matched = true; } } return matched; } static int make_matrix(struct channelmix *mix) { float matrix[MAX_CHANNELS][MAX_CHANNELS] = {{ 0.0f }}; uint64_t src_mask = mix->src_mask, src_paired; uint64_t dst_mask = mix->dst_mask, dst_paired; uint32_t src_chan = mix->src_chan; uint32_t dst_chan = mix->dst_chan; uint64_t unassigned, keep; uint32_t i, j, ic, jc, matrix_encoding = MATRIX_NORMAL; float clev = SQRT1_2; float slev = SQRT1_2; float llev = 0.5f; float maxsum = 0.0f; bool filter_fc = false, filter_lfe = false, matched = false, normalize; #define _MATRIX(s,d) matrix[_CH(s)][_CH(d)] normalize = SPA_FLAG_IS_SET(mix->options, CHANNELMIX_OPTION_NORMALIZE); spa_log_debug(mix->log, "src-mask:%08"PRIx64" dst-mask:%08"PRIx64 " options:%08x", src_mask, dst_mask, mix->options); /* shift so that bit 0 is MONO */ src_mask >>= _SH; dst_mask >>= _SH; if (src_chan > 1 && (src_mask & _MASK(MONO))) src_mask = 0; if (dst_chan > 1 && (dst_mask & _MASK(MONO))) dst_mask = 0; src_paired = src_mask; dst_paired = dst_mask; /* unknown channels */ if (src_mask == 0 || dst_mask == 0) { if (src_chan == 1) { /* one src channel goes everywhere */ spa_log_info(mix->log, "distribute UNK (%f) %"PRIu64, 1.0f, src_mask); distribute_mix(mix, matrix, src_mask); } else if (dst_chan == 1) { /* one dst channel get average of everything */ spa_log_info(mix->log, "average UNK (%f) %"PRIu64, 1.0f / src_chan, dst_mask); average_mix(mix, matrix, dst_mask); normalize = true; } else { /* just pair channels */ spa_log_info(mix->log, "pairing UNK channels (%f)", 1.0f); if (src_mask == 0) src_paired = dst_mask; else if (dst_mask == 0) dst_paired = src_mask; pair_mix(matrix); } goto done; } else { spa_log_debug(mix->log, "matching channels"); matched = match_mix(mix, matrix, src_mask, dst_mask); } unassigned = src_mask & ~dst_mask; keep = dst_mask & ~src_mask; if (!SPA_FLAG_IS_SET(mix->options, CHANNELMIX_OPTION_UPMIX)) { /* upmix completely disabled */ keep = 0; } else { /* some upmixing (FC and LFE) enabled. */ if (mix->upmix == CHANNELMIX_UPMIX_NONE) keep = 0; if (mix->fc_cutoff > 0.0f) keep |= FRONT; else keep &= ~FRONT; if (mix->lfe_cutoff > 0.0f) keep |= _MASK(LFE); else keep &= ~_MASK(LFE); } /* if we have no channel matched, try to upmix or keep the stereo * pair or else we might end up with silence. */ if (dst_mask & STEREO && !matched) keep |= STEREO; spa_log_info(mix->log, "unassigned downmix %08" PRIx64 " %08" PRIx64, unassigned, keep); if (unassigned & _MASK(MONO)) { if ((dst_mask & STEREO) == STEREO) { spa_log_info(mix->log, "assign MONO to STEREO (%f)", 1.0f); _MATRIX(FL,MONO) += 1.0f; _MATRIX(FR,MONO) += 1.0f; keep &= ~STEREO; } else if ((dst_mask & FRONT) == FRONT) { spa_log_info(mix->log, "assign MONO to FRONT (%f)", 1.0f); _MATRIX(FC,MONO) += 1.0f; normalize = true; } else { spa_log_warn(mix->log, "can't assign MONO"); } } if (unassigned & FRONT) { if ((dst_mask & STEREO) == STEREO){ if (src_mask & STEREO) { spa_log_info(mix->log, "assign FC to STEREO (%f)", clev); _MATRIX(FL,FC) += clev; _MATRIX(FR,FC) += clev; } else { spa_log_info(mix->log, "assign FC to STEREO (%f)", SQRT1_2); _MATRIX(FL,FC) += SQRT1_2; _MATRIX(FR,FC) += SQRT1_2; } keep &= ~STEREO; } else if (dst_mask & _MASK(MONO)){ spa_log_info(mix->log, "assign FC to MONO (%f)", 1.0f); for (i = 0; i < MAX_CHANNELS; i++) matrix[i][_CH(FC)]= 1.0f; normalize = true; } else { spa_log_warn(mix->log, "can't assign FC"); } } if (unassigned & STEREO){ if (dst_mask & FRONT) { spa_log_info(mix->log, "assign STEREO to FC (%f)", SQRT1_2); _MATRIX(FC,FL) += SQRT1_2; _MATRIX(FC,FR) += SQRT1_2; if (src_mask & FRONT) { spa_log_info(mix->log, "assign FC to FC (%f)", clev * SQRT2); _MATRIX(FC,FC) = clev * SQRT2; } keep &= ~FRONT; } else if ((dst_mask & _MASK(MONO))){ spa_log_info(mix->log, "assign STEREO to MONO (%f)", 1.0f); for (i = 0; i < MAX_CHANNELS; i++) { matrix[i][_CH(FL)]= 1.0f; matrix[i][_CH(FR)]= 1.0f; } normalize = true; } else { spa_log_warn(mix->log, "can't assign STEREO"); } } if (unassigned & _MASK(RC)) { if (dst_mask & REAR){ spa_log_info(mix->log, "assign RC to RL+RR (%f)", SQRT1_2); _MATRIX(RL,RC) += SQRT1_2; _MATRIX(RR,RC) += SQRT1_2; } else if (dst_mask & SIDE) { spa_log_info(mix->log, "assign RC to SL+SR (%f)", SQRT1_2); _MATRIX(SL,RC) += SQRT1_2; _MATRIX(SR,RC) += SQRT1_2; } else if(dst_mask & STEREO) { spa_log_info(mix->log, "assign RC to FL+FR"); if (matrix_encoding == MATRIX_DOLBY || matrix_encoding == MATRIX_DPLII) { if (unassigned & (_MASK(RL)|_MASK(RR))) { _MATRIX(FL,RC) -= slev * SQRT1_2; _MATRIX(FR,RC) += slev * SQRT1_2; } else { _MATRIX(FL,RC) -= slev; _MATRIX(FR,RC) += slev; } } else { _MATRIX(FL,RC) += slev * SQRT1_2; _MATRIX(FR,RC) += slev * SQRT1_2; } } else if (dst_mask & FRONT) { spa_log_info(mix->log, "assign RC to FC (%f)", slev * SQRT1_2); _MATRIX(FC,RC) += slev * SQRT1_2; } else if (dst_mask & _MASK(MONO)){ spa_log_info(mix->log, "assign RC to MONO (%f)", 1.0f); for (i = 0; i < MAX_CHANNELS; i++) matrix[i][_CH(RC)]= 1.0f; normalize = true; } else { spa_log_warn(mix->log, "can't assign RC"); } } if (unassigned & REAR) { if (dst_mask & _MASK(RC)) { spa_log_info(mix->log, "assign RL+RR to RC"); _MATRIX(RC,RL) += SQRT1_2; _MATRIX(RC,RR) += SQRT1_2; } else if (dst_mask & SIDE) { spa_log_info(mix->log, "assign RL+RR to SL+SR"); if (src_mask & SIDE) { _MATRIX(SL,RL) += SQRT1_2; _MATRIX(SR,RR) += SQRT1_2; } else { _MATRIX(SL,RL) += 1.0f; _MATRIX(SR,RR) += 1.0f; } keep &= ~SIDE; } else if (dst_mask & STEREO) { spa_log_info(mix->log, "assign RL+RR to FL+FR (%f)", slev); if (matrix_encoding == MATRIX_DOLBY) { _MATRIX(FL,RL) -= slev * SQRT1_2; _MATRIX(FL,RR) -= slev * SQRT1_2; _MATRIX(FR,RL) += slev * SQRT1_2; _MATRIX(FR,RR) += slev * SQRT1_2; } else if (matrix_encoding == MATRIX_DPLII) { _MATRIX(FL,RL) -= slev * SQRT3_2; _MATRIX(FL,RR) -= slev * SQRT1_2; _MATRIX(FR,RL) += slev * SQRT1_2; _MATRIX(FR,RR) += slev * SQRT3_2; } else { _MATRIX(FL,RL) += slev; _MATRIX(FR,RR) += slev; } } else if (dst_mask & FRONT) { spa_log_info(mix->log, "assign RL+RR to FC (%f)", slev * SQRT1_2); _MATRIX(FC,RL)+= slev * SQRT1_2; _MATRIX(FC,RR)+= slev * SQRT1_2; } else if (dst_mask & _MASK(MONO)){ spa_log_info(mix->log, "assign RL+RR to MONO (%f)", 1.0f); for (i = 0; i < MAX_CHANNELS; i++) { matrix[i][_CH(RL)]= 1.0f; matrix[i][_CH(RR)]= 1.0f; } normalize = true; } else { spa_log_warn(mix->log, "can't assign RL"); } } if (unassigned & SIDE) { if (dst_mask & REAR) { if (src_mask & _MASK(RL)) { spa_log_info(mix->log, "assign SL+SR to RL+RR (%f)", SQRT1_2); _MATRIX(RL,SL) += SQRT1_2; _MATRIX(RR,SR) += SQRT1_2; } else { spa_log_info(mix->log, "assign SL+SR to RL+RR (%f)", 1.0f); _MATRIX(RL,SL) += 1.0f; _MATRIX(RR,SR) += 1.0f; } keep &= ~REAR; } else if (dst_mask & _MASK(RC)) { spa_log_info(mix->log, "assign SL+SR to RC (%f)", SQRT1_2); _MATRIX(RC,SL)+= SQRT1_2; _MATRIX(RC,SR)+= SQRT1_2; } else if (dst_mask & STEREO) { if (matrix_encoding == MATRIX_DOLBY) { spa_log_info(mix->log, "assign SL+SR to FL+FR (%f)", slev * SQRT1_2); _MATRIX(FL,SL) -= slev * SQRT1_2; _MATRIX(FL,SR) -= slev * SQRT1_2; _MATRIX(FR,SL) += slev * SQRT1_2; _MATRIX(FR,SR) += slev * SQRT1_2; } else if (matrix_encoding == MATRIX_DPLII) { spa_log_info(mix->log, "assign SL+SR to FL+FR (%f / %f)", slev * SQRT3_2, slev * SQRT1_2); _MATRIX(FL,SL) -= slev * SQRT3_2; _MATRIX(FL,SR) -= slev * SQRT1_2; _MATRIX(FR,SL) += slev * SQRT1_2; _MATRIX(FR,SR) += slev * SQRT3_2; } else { spa_log_info(mix->log, "assign SL+SR to FL+FR (%f)", slev); _MATRIX(FL,SL) += slev; _MATRIX(FR,SR) += slev; } } else if (dst_mask & FRONT) { spa_log_info(mix->log, "assign SL+SR to FC (%f)", slev * SQRT1_2); _MATRIX(FC,SL) += slev * SQRT1_2; _MATRIX(FC,SR) += slev * SQRT1_2; } else if (dst_mask & _MASK(MONO)){ spa_log_info(mix->log, "assign SL+SR to MONO (%f)", 1.0f); for (i = 0; i < MAX_CHANNELS; i++) { matrix[i][_CH(SL)]= 1.0f; matrix[i][_CH(SR)]= 1.0f; } normalize = true; } else { spa_log_warn(mix->log, "can't assign SL"); } } if (unassigned & _MASK(FLC)) { if (dst_mask & STEREO) { spa_log_info(mix->log, "assign FLC+FRC to FL+FR (%f)", 1.0f); _MATRIX(FL,FLC)+= 1.0f; _MATRIX(FR,FRC)+= 1.0f; } else if(dst_mask & FRONT) { spa_log_info(mix->log, "assign FLC+FRC to FC (%f)", SQRT1_2); _MATRIX(FC,FLC)+= SQRT1_2; _MATRIX(FC,FRC)+= SQRT1_2; } else if (dst_mask & _MASK(MONO)){ spa_log_info(mix->log, "assign FLC+FRC to MONO (%f)", 1.0f); for (i = 0; i < MAX_CHANNELS; i++) { matrix[i][_CH(FLC)]= 1.0f; matrix[i][_CH(FRC)]= 1.0f; } normalize = true; } else { spa_log_warn(mix->log, "can't assign FLC"); } } if (unassigned & _MASK(LFE) && SPA_FLAG_IS_SET(mix->options, CHANNELMIX_OPTION_MIX_LFE)) { if (dst_mask & FRONT) { spa_log_info(mix->log, "assign LFE to FC (%f)", llev); _MATRIX(FC,LFE) += llev; } else if (dst_mask & STEREO) { spa_log_info(mix->log, "assign LFE to FL+FR (%f)", llev * SQRT1_2); _MATRIX(FL,LFE) += llev * SQRT1_2; _MATRIX(FR,LFE) += llev * SQRT1_2; } else if ((dst_mask & _MASK(MONO))){ spa_log_info(mix->log, "assign LFE to MONO (%f)", 1.0f); for (i = 0; i < MAX_CHANNELS; i++) matrix[i][_CH(LFE)]= 1.0f; normalize = true; } else { spa_log_warn(mix->log, "can't assign LFE"); } } unassigned = dst_mask & ~src_mask & keep; spa_log_info(mix->log, "unassigned upmix %08"PRIx64" lfe:%f", unassigned, mix->lfe_cutoff); if (unassigned & STEREO) { if ((src_mask & FRONT) == FRONT) { spa_log_info(mix->log, "produce STEREO from FC (%f)", clev); _MATRIX(FL,FC) += clev; _MATRIX(FR,FC) += clev; } else if (src_mask & _MASK(MONO)) { spa_log_info(mix->log, "produce STEREO from MONO (%f)", 1.0f); _MATRIX(FL,MONO) += 1.0f; _MATRIX(FR,MONO) += 1.0f; } else { spa_log_warn(mix->log, "can't produce STEREO"); } } if (unassigned & FRONT) { if ((src_mask & STEREO) == STEREO) { spa_log_info(mix->log, "produce FC from STEREO (%f)", clev); _MATRIX(FC,FL) += clev; _MATRIX(FC,FR) += clev; filter_fc = true; } else if (src_mask & _MASK(MONO)) { spa_log_info(mix->log, "produce FC from MONO (%f)", 1.0f); _MATRIX(FC,MONO) += 1.0f; filter_fc = true; } else { spa_log_warn(mix->log, "can't produce FC"); } } if (unassigned & _MASK(LFE)) { if ((src_mask & STEREO) == STEREO) { spa_log_info(mix->log, "produce LFE from STEREO (%f)", llev); _MATRIX(LFE,FL) += llev; _MATRIX(LFE,FR) += llev; filter_lfe = true; } else if ((src_mask & FRONT) == FRONT) { spa_log_info(mix->log, "produce LFE from FC (%f)", llev); _MATRIX(LFE,FC) += llev; filter_lfe = true; } else if (src_mask & _MASK(MONO)) { spa_log_info(mix->log, "produce LFE from MONO (%f)", 1.0f); _MATRIX(LFE,MONO) += 1.0f; filter_lfe = true; } else { spa_log_warn(mix->log, "can't produce LFE"); } } if (unassigned & SIDE) { if ((src_mask & REAR) == REAR) { spa_log_info(mix->log, "produce SIDE from REAR (%f)", 1.0f); _MATRIX(SL,RL) += 1.0f; _MATRIX(SR,RR) += 1.0f; } else if ((src_mask & STEREO) == STEREO) { spa_log_info(mix->log, "produce SIDE from STEREO (%f)", slev); _MATRIX(SL,FL) += slev; _MATRIX(SR,FR) += slev; } else if ((src_mask & FRONT) == FRONT && mix->upmix == CHANNELMIX_UPMIX_SIMPLE) { spa_log_info(mix->log, "produce SIDE from FC (%f)", clev); _MATRIX(SL,FC) += clev; _MATRIX(SR,FC) += clev; } else if (src_mask & _MASK(MONO) && mix->upmix == CHANNELMIX_UPMIX_SIMPLE) { spa_log_info(mix->log, "produce SIDE from MONO (%f)", 1.0f); _MATRIX(SL,MONO) += 1.0f; _MATRIX(SR,MONO) += 1.0f; } else { spa_log_info(mix->log, "won't produce SIDE"); } } if (unassigned & REAR) { if ((src_mask & SIDE) == SIDE) { spa_log_info(mix->log, "produce REAR from SIDE (%f)", 1.0f); _MATRIX(RL,SL) += 1.0f; _MATRIX(RR,SR) += 1.0f; } else if ((src_mask & STEREO) == STEREO) { spa_log_info(mix->log, "produce REAR from STEREO (%f)", slev); _MATRIX(RL,FL) += slev; _MATRIX(RR,FR) += slev; } else if ((src_mask & FRONT) == FRONT && mix->upmix == CHANNELMIX_UPMIX_SIMPLE) { spa_log_info(mix->log, "produce REAR from FC (%f)", clev); _MATRIX(RL,FC) += clev; _MATRIX(RR,FC) += clev; } else if (src_mask & _MASK(MONO) && mix->upmix == CHANNELMIX_UPMIX_SIMPLE) { spa_log_info(mix->log, "produce REAR from MONO (%f)", 1.0f); _MATRIX(RL,MONO) += 1.0f; _MATRIX(RR,MONO) += 1.0f; } else { spa_log_info(mix->log, "won't produce SIDE"); } } if (unassigned & _MASK(RC)) { if ((src_mask & REAR) == REAR) { spa_log_info(mix->log, "produce RC from REAR (%f)", 0.5f); _MATRIX(RC,RL) += 0.5f; _MATRIX(RC,RR) += 0.5f; } else if ((src_mask & SIDE) == SIDE) { spa_log_info(mix->log, "produce RC from SIDE (%f)", 0.5f); _MATRIX(RC,SL) += 0.5f; _MATRIX(RC,SR) += 0.5f; } else if ((src_mask & STEREO) == STEREO) { spa_log_info(mix->log, "produce RC from STEREO (%f)", 0.5f); _MATRIX(RC,FL) += 0.5f; _MATRIX(RC,FR) += 0.5f; } else if ((src_mask & FRONT) == FRONT && mix->upmix == CHANNELMIX_UPMIX_SIMPLE) { spa_log_info(mix->log, "produce RC from FC (%f)", slev); _MATRIX(RC,FC) += slev; } else if (src_mask & _MASK(MONO) && mix->upmix == CHANNELMIX_UPMIX_SIMPLE) { spa_log_info(mix->log, "produce RC from MONO (%f)", 1.0f); _MATRIX(RC,MONO) += 1.0f; } else { spa_log_info(mix->log, "won't produce RC"); } } done: if (dst_paired == 0) dst_paired = ~0LU; if (src_paired == 0) src_paired = ~0LU; for (jc = 0, ic = 0, i = 0; i < CHANNEL_BITS; i++) { float sum = 0.0f; char str1[1024], str2[1024]; struct spa_strbuf sb1, sb2; spa_strbuf_init(&sb1, str1, sizeof(str1)); spa_strbuf_init(&sb2, str2, sizeof(str2)); if ((dst_paired & (1UL << i)) == 0) continue; for (jc = 0, j = 0; j < CHANNEL_BITS; j++) { if ((src_paired & (1UL << j)) == 0) continue; if (ic >= dst_chan || jc >= src_chan) continue; if (ic == 0) spa_strbuf_append(&sb2, "%-4.4s ", src_mask == 0 ? "UNK" : spa_debug_type_find_short_name(spa_type_audio_channel, j + _SH)); mix->matrix_orig[ic][jc++] = matrix[i][j]; sum += fabsf(matrix[i][j]); if (matrix[i][j] == 0.0f) spa_strbuf_append(&sb1, " "); else spa_strbuf_append(&sb1, "%1.3f ", matrix[i][j]); } if (sb2.pos > 0) spa_log_info(mix->log, " %s", str2); if (sb1.pos > 0) { spa_log_info(mix->log, "%-4.4s %s %f", dst_mask == 0 ? "UNK" : spa_debug_type_find_short_name(spa_type_audio_channel, i + _SH), str1, sum); } maxsum = SPA_MAX(maxsum, sum); if (i == _CH(LFE) && mix->lfe_cutoff > 0.0f && filter_lfe) { spa_log_info(mix->log, "channel %d is LFE cutoff:%f", ic, mix->lfe_cutoff); lr4_set(&mix->lr4[ic], BQ_LOWPASS, mix->lfe_cutoff / mix->freq); } else if (i == _CH(FC) && mix->fc_cutoff > 0.0f && filter_fc) { spa_log_info(mix->log, "channel %d is FC cutoff:%f", ic, mix->fc_cutoff); lr4_set(&mix->lr4[ic], BQ_LOWPASS, mix->fc_cutoff / mix->freq); } else { lr4_set(&mix->lr4[ic], BQ_NONE, mix->fc_cutoff / mix->freq); } ic++; } if (normalize && maxsum > 1.0f) { spa_log_info(mix->log, "normalize %f", maxsum); for (i = 0; i < dst_chan; i++) for (j = 0; j < src_chan; j++) mix->matrix_orig[i][j] /= maxsum; } return 0; } static void impl_channelmix_set_volume(struct channelmix *mix, float volume, bool mute, uint32_t n_channel_volumes, float *channel_volumes) { float volumes[MAX_CHANNELS]; float vol = mute ? 0.0f : volume, t; uint32_t i, j; uint32_t src_chan = mix->src_chan; uint32_t dst_chan = mix->dst_chan; spa_log_debug(mix->log, "volume:%f mute:%d n_volumes:%d", volume, mute, n_channel_volumes); /** apply global volume to channels */ for (i = 0; i < n_channel_volumes; i++) { volumes[i] = channel_volumes[i] * vol; spa_log_debug(mix->log, "%d: %f * %f = %f", i, channel_volumes[i], vol, volumes[i]); } /** apply volumes per channel */ if (n_channel_volumes == src_chan) { for (i = 0; i < dst_chan; i++) { for (j = 0; j < src_chan; j++) { mix->matrix[i][j] = mix->matrix_orig[i][j] * volumes[j]; } } } else if (n_channel_volumes == dst_chan) { for (i = 0; i < dst_chan; i++) { for (j = 0; j < src_chan; j++) { mix->matrix[i][j] = mix->matrix_orig[i][j] * volumes[i]; } } } else if (n_channel_volumes == 0) { for (i = 0; i < dst_chan; i++) { for (j = 0; j < src_chan; j++) { mix->matrix[i][j] = mix->matrix_orig[i][j] * vol; } } } SPA_FLAG_SET(mix->flags, CHANNELMIX_FLAG_ZERO); SPA_FLAG_SET(mix->flags, CHANNELMIX_FLAG_EQUAL); SPA_FLAG_SET(mix->flags, CHANNELMIX_FLAG_COPY); t = 0.0; for (i = 0; i < dst_chan; i++) { for (j = 0; j < src_chan; j++) { float v = mix->matrix[i][j]; spa_log_debug(mix->log, "%d %d: %f", i, j, v); if (i == 0 && j == 0) t = v; else if (t != v) SPA_FLAG_CLEAR(mix->flags, CHANNELMIX_FLAG_EQUAL); if (v != 0.0) SPA_FLAG_CLEAR(mix->flags, CHANNELMIX_FLAG_ZERO); if ((i == j && v != 1.0f) || (i != j && v != 0.0f)) SPA_FLAG_CLEAR(mix->flags, CHANNELMIX_FLAG_COPY); } } SPA_FLAG_UPDATE(mix->flags, CHANNELMIX_FLAG_IDENTITY, dst_chan == src_chan && SPA_FLAG_IS_SET(mix->flags, CHANNELMIX_FLAG_COPY)); spa_log_debug(mix->log, "flags:%08x", mix->flags); } static void impl_channelmix_free(struct channelmix *mix) { mix->process = NULL; } int channelmix_init(struct channelmix *mix) { const struct channelmix_info *info; if (mix->src_chan > MAX_CHANNELS || mix->dst_chan > MAX_CHANNELS) return -EINVAL; info = find_channelmix_info(mix->src_chan, mix->src_mask, mix->dst_chan, mix->dst_mask, mix->cpu_flags); if (info == NULL) return -ENOTSUP; mix->free = impl_channelmix_free; mix->process = info->process; mix->set_volume = impl_channelmix_set_volume; mix->cpu_flags = info->cpu_flags; mix->delay = (uint32_t)(mix->rear_delay * mix->freq / 1000.0f); mix->func_name = info->name; spa_zero(mix->taps_mem); mix->taps = SPA_PTR_ALIGN(mix->taps_mem, CHANNELMIX_OPS_MAX_ALIGN, float); mix->buffer[0] = SPA_PTR_ALIGN(&mix->buffer_mem[0], CHANNELMIX_OPS_MAX_ALIGN, float); mix->buffer[1] = SPA_PTR_ALIGN(&mix->buffer_mem[2*BUFFER_SIZE], CHANNELMIX_OPS_MAX_ALIGN, float); if (mix->hilbert_taps > 0) { mix->n_taps = SPA_CLAMP(mix->hilbert_taps, 15u, MAX_TAPS) | 1; blackman_window(mix->taps, mix->n_taps); hilbert_generate(mix->taps, mix->n_taps); reverse_taps(mix->taps, mix->n_taps); } else { mix->n_taps = 1; mix->taps[0] = 1.0f; } if (mix->delay + mix->n_taps > BUFFER_SIZE) mix->delay = BUFFER_SIZE - mix->n_taps; spa_log_debug(mix->log, "selected %s delay:%d options:%08x", info->name, mix->delay, mix->options); return make_matrix(mix); } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/audioconvert/channelmix-ops.h000066400000000000000000000107221511204443500305440ustar00rootroot00000000000000/* Spa */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #include #include "crossover.h" #define VOLUME_MIN 0.0f #define VOLUME_NORM 1.0f #define _M(ch) (1UL << SPA_AUDIO_CHANNEL_ ## ch) #define MASK_MONO _M(FC)|_M(MONO)|_M(UNKNOWN) #define MASK_STEREO _M(FL)|_M(FR)|_M(UNKNOWN) #define MASK_QUAD _M(FL)|_M(FR)|_M(RL)|_M(RR)|_M(UNKNOWN) #define MASK_3_1 _M(FL)|_M(FR)|_M(FC)|_M(LFE) #define MASK_5_1 _M(FL)|_M(FR)|_M(FC)|_M(LFE)|_M(SL)|_M(SR)|_M(RL)|_M(RR) #define MASK_7_1 _M(FL)|_M(FR)|_M(FC)|_M(LFE)|_M(SL)|_M(SR)|_M(RL)|_M(RR) #define BUFFER_SIZE 4096 #define MAX_TAPS 255u #define MAX_CHANNELS SPA_AUDIO_MAX_CHANNELS #define CHANNELMIX_OPS_MAX_ALIGN 16 struct channelmix { uint32_t src_chan; uint32_t dst_chan; uint64_t src_mask; uint64_t dst_mask; uint32_t cpu_flags; #define CHANNELMIX_OPTION_MIX_LFE (1<<0) /**< mix LFE */ #define CHANNELMIX_OPTION_NORMALIZE (1<<1) /**< normalize volumes */ #define CHANNELMIX_OPTION_UPMIX (1<<2) /**< do simple upmixing */ uint32_t options; #define CHANNELMIX_UPMIX_NONE 0 /**< disable upmixing */ #define CHANNELMIX_UPMIX_SIMPLE 1 /**< simple upmixing */ #define CHANNELMIX_UPMIX_PSD 2 /**< Passive Surround Decoding upmixing */ uint32_t upmix; struct spa_log *log; const char *func_name; #define CHANNELMIX_FLAG_ZERO (1<<0) /**< all zero components */ #define CHANNELMIX_FLAG_IDENTITY (1<<1) /**< identity matrix */ #define CHANNELMIX_FLAG_EQUAL (1<<2) /**< all values are equal */ #define CHANNELMIX_FLAG_COPY (1<<3) /**< 1 on diagonal, can be nxm */ uint32_t flags; float matrix_orig[MAX_CHANNELS][MAX_CHANNELS]; float matrix[MAX_CHANNELS][MAX_CHANNELS]; float freq; /* sample frequency */ float lfe_cutoff; /* in Hz, 0 is disabled */ float fc_cutoff; /* in Hz, 0 is disabled */ float rear_delay; /* in ms, 0 is disabled */ float widen; /* stereo widen. 0 is disabled */ uint32_t hilbert_taps; /* to phase shift, 0 disabled */ struct lr4 lr4[MAX_CHANNELS]; float buffer_mem[2 * BUFFER_SIZE*2 + CHANNELMIX_OPS_MAX_ALIGN/4]; float *buffer[2]; uint32_t pos[2]; uint32_t delay; float taps_mem[MAX_TAPS + CHANNELMIX_OPS_MAX_ALIGN/4]; float *taps; uint32_t n_taps; void (*process) (struct channelmix *mix, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], uint32_t n_samples); void (*set_volume) (struct channelmix *mix, float volume, bool mute, uint32_t n_channel_volumes, float *channel_volumes); void (*free) (struct channelmix *mix); void *data; }; int channelmix_init(struct channelmix *mix); static const struct channelmix_upmix_info { const char *label; const char *description; uint32_t upmix; } channelmix_upmix_info[] = { [CHANNELMIX_UPMIX_NONE] = { "none", "Disabled", CHANNELMIX_UPMIX_NONE }, [CHANNELMIX_UPMIX_SIMPLE] = { "simple", "Simple upmixing", CHANNELMIX_UPMIX_SIMPLE }, [CHANNELMIX_UPMIX_PSD] = { "psd", "Passive Surround Decoding", CHANNELMIX_UPMIX_PSD } }; static inline uint32_t channelmix_upmix_from_label(const char *label) { SPA_FOR_EACH_ELEMENT_VAR(channelmix_upmix_info, i) { if (spa_streq(i->label, label)) return i->upmix; } return CHANNELMIX_UPMIX_NONE; } #define channelmix_process(mix,...) (mix)->process(mix, __VA_ARGS__) #define channelmix_set_volume(mix,...) (mix)->set_volume(mix, __VA_ARGS__) #define channelmix_free(mix) (mix)->free(mix) #define DEFINE_FUNCTION(name,arch) \ void channelmix_##name##_##arch(struct channelmix *mix, \ void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], \ uint32_t n_samples); DEFINE_FUNCTION(copy, c); DEFINE_FUNCTION(f32_n_m, c); DEFINE_FUNCTION(f32_1_2, c); DEFINE_FUNCTION(f32_2_1, c); DEFINE_FUNCTION(f32_4_1, c); DEFINE_FUNCTION(f32_2_4, c); DEFINE_FUNCTION(f32_2_3p1, c); DEFINE_FUNCTION(f32_2_5p1, c); DEFINE_FUNCTION(f32_2_7p1, c); DEFINE_FUNCTION(f32_3p1_2, c); DEFINE_FUNCTION(f32_5p1_2, c); DEFINE_FUNCTION(f32_5p1_3p1, c); DEFINE_FUNCTION(f32_5p1_4, c); DEFINE_FUNCTION(f32_7p1_2, c); DEFINE_FUNCTION(f32_7p1_3p1, c); DEFINE_FUNCTION(f32_7p1_4, c); #if defined (HAVE_SSE) DEFINE_FUNCTION(copy, sse); DEFINE_FUNCTION(f32_n_m, sse); DEFINE_FUNCTION(f32_2_3p1, sse); DEFINE_FUNCTION(f32_2_5p1, sse); DEFINE_FUNCTION(f32_2_7p1, sse); DEFINE_FUNCTION(f32_3p1_2, sse); DEFINE_FUNCTION(f32_5p1_2, sse); DEFINE_FUNCTION(f32_5p1_3p1, sse); DEFINE_FUNCTION(f32_5p1_4, sse); DEFINE_FUNCTION(f32_7p1_4, sse); #endif #undef DEFINE_FUNCTION pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/audioconvert/crossover.c000066400000000000000000000006671511204443500276460ustar00rootroot00000000000000/* Copyright (c) 2013 The Chromium OS Authors. All rights reserved. * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #include #include #include "crossover.h" void lr4_set(struct lr4 *lr4, enum biquad_type type, float freq) { biquad_set(&lr4->bq, type, freq, 0, 0); lr4->x1 = 0; lr4->x2 = 0; lr4->y1 = 0; lr4->y2 = 0; lr4->active = type != BQ_NONE; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/audioconvert/crossover.h000066400000000000000000000012441511204443500276430ustar00rootroot00000000000000/* Copyright (c) 2013 The Chromium OS Authors. All rights reserved. * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #ifndef CROSSOVER_H_ #define CROSSOVER_H_ #include #include "biquad.h" /* An LR4 filter is two biquads with the same parameters connected in series: * * x -- [BIQUAD] -- y -- [BIQUAD] -- z * * Both biquad filter has the same parameter b[012] and a[12], * The variable [xyz][12] keep the history values. */ struct lr4 { struct biquad bq; float x1, x2; float y1, y2; bool active; }; void lr4_set(struct lr4 *lr4, enum biquad_type type, float freq); #endif /* CROSSOVER_H_ */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/audioconvert/dbesi0.c000066400000000000000000000144311511204443500267610ustar00rootroot00000000000000/* Copyright(C) 1996 Takuya OOURA You may use, copy, modify this code for any purpose and without fee. Package home: http://www.kurims.kyoto-u.ac.jp/~ooura/bessel.html */ /* Bessel I_0(x) function in double precision */ #include static double dbesi0 (double x) { int k; double w, t, y; static double a[65] = { 8.5246820682016865877e-11, 2.5966600546497407288e-9, 7.9689994568640180274e-8, 1.9906710409667748239e-6, 4.0312469446528002532e-5, 6.4499871606224265421e-4, 0.0079012345761930579108, 0.071111111109207045212, 0.444444444444724909, 1.7777777777777532045, 4.0000000000000011182, 3.99999999999999998, 1.0000000000000000001, 1.1520919130377195927e-10, 2.2287613013610985225e-9, 8.1903951930694585113e-8, 1.9821560631611544984e-6, 4.0335461940910133184e-5, 6.4495330974432203401e-4, 0.0079013012611467520626, 0.071111038160875566622, 0.44444450319062699316, 1.7777777439146450067, 4.0000000132337935071, 3.9999999968569015366, 1.0000000003426703174, 1.5476870780515238488e-10, 1.2685004214732975355e-9, 9.2776861851114223267e-8, 1.9063070109379044378e-6, 4.0698004389917945832e-5, 6.4370447244298070713e-4, 0.0079044749458444976958, 0.071105052411749363882, 0.44445280640924755082, 1.7777694934432109713, 4.0000055808824003386, 3.9999977081165740932, 1.0000004333949319118, 2.0675200625006793075e-10, -6.1689554705125681442e-10, 1.2436765915401571654e-7, 1.5830429403520613423e-6, 4.2947227560776583326e-5, 6.3249861665073441312e-4, 0.0079454472840953930811, 0.070994327785661860575, 0.44467219586283000332, 1.7774588182255374745, 4.0003038986252717972, 3.9998233869142057195, 1.0000472932961288324, 2.7475684794982708655e-10, -3.8991472076521332023e-9, 1.9730170483976049388e-7, 5.9651531561967674521e-7, 5.1992971474748995357e-5, 5.7327338675433770752e-4, 0.0082293143836530412024, 0.069990934858728039037, 0.44726764292723985087, 1.7726685170014087784, 4.0062907863712704432, 3.9952750700487845355, 1.0016354346654179322 }; static double b[70] = { 6.7852367144945531383e-8, 4.6266061382821826854e-7, 6.9703135812354071774e-6, 7.6637663462953234134e-5, 7.9113515222612691636e-4, 0.0073401204731103808981, 0.060677114958668837046, 0.43994941411651569622, 2.7420017097661750609, 14.289661921740860534, 59.820609640320710779, 188.78998681199150629, 399.8731367825601118, 427.56411572180478514, 1.8042097874891098754e-7, 1.2277164312044637357e-6, 1.8484393221474274861e-5, 2.0293995900091309208e-4, 0.0020918539850246207459, 0.019375315654033949297, 0.15985869016767185908, 1.1565260527420641724, 7.1896341224206072113, 37.354773811947484532, 155.80993164266268457, 489.5211371158540918, 1030.9147225169564806, 1093.5883545113746958, 4.8017305613187493564e-7, 3.261317843912380074e-6, 4.9073137508166159639e-5, 5.3806506676487583755e-4, 0.0055387918291051866561, 0.051223717488786549025, 0.42190298621367914765, 3.0463625987357355872, 18.895299447327733204, 97.915189029455461554, 407.13940115493494659, 1274.3088990480582632, 2670.9883037012547506, 2815.7166284662544712, 1.2789926338424623394e-6, 8.6718263067604918916e-6, 1.3041508821299929489e-4, 0.001428224737372747892, 0.014684070635768789378, 0.13561403190404185755, 1.1152592585977393953, 8.0387088559465389038, 49.761318895895479206, 257.2684232313529138, 1066.8543146269566231, 3328.3874581009636362, 6948.8586598121634874, 7288.4893398212481055, 3.409350368197032893e-6, 2.3079025203103376076e-5, 3.4691373283901830239e-4, 0.003794994977222908545, 0.038974209677945602145, 0.3594948380414878371, 2.9522878893539528226, 21.246564609514287056, 131.28727387146173141, 677.38107093296675421, 2802.3724744545046518, 8718.5731420798254081, 18141.348781638832286, 18948.925349296308859 }; static double c[45] = { 2.5568678676452702768e-15, 3.0393953792305924324e-14, 6.3343751991094840009e-13, 1.5041298011833009649e-11, 4.4569436918556541414e-10, 1.746393051427167951e-8, 1.0059224011079852317e-6, 1.0729838945088577089e-4, 0.05150322693642527738, 5.2527963991711562216e-15, 7.202118481421005641e-15, 7.2561421229904797156e-13, 1.482312146673104251e-11, 4.4602670450376245434e-10, 1.7463600061788679671e-8, 1.005922609132234756e-6, 1.0729838937545111487e-4, 0.051503226936437300716, 1.3365917359358069908e-14, -1.2932643065888544835e-13, 1.7450199447905602915e-12, 1.0419051209056979788e-11, 4.58047881980598326e-10, 1.7442405450073548966e-8, 1.0059461453281292278e-6, 1.0729837434500161228e-4, 0.051503226940658446941, 5.3771611477352308649e-14, -1.1396193006413731702e-12, 1.2858641335221653409e-11, -5.9802086004570057703e-11, 7.3666894305929510222e-10, 1.6731837150730356448e-8, 1.0070831435812128922e-6, 1.0729733111203704813e-4, 0.051503227360726294675, 3.7819492084858931093e-14, -4.8600496888588034879e-13, 1.6898350504817224909e-12, 4.5884624327524255865e-11, 1.2521615963377513729e-10, 1.8959658437754727957e-8, 1.0020716710561353622e-6, 1.073037119856927559e-4, 0.05150322383300230775 }; w = fabs (x); if (w < 8.5) { t = w * w * 0.0625; k = 13 * ((int) t); y = (((((((((((a[k] * t + a[k + 1]) * t + a[k + 2]) * t + a[k + 3]) * t + a[k + 4]) * t + a[k + 5]) * t + a[k + 6]) * t + a[k + 7]) * t + a[k + 8]) * t + a[k + 9]) * t + a[k + 10]) * t + a[k + 11]) * t + a[k + 12]; } else if (w < 12.5) { k = (int) w; t = w - k; k = 14 * (k - 8); y = ((((((((((((b[k] * t + b[k + 1]) * t + b[k + 2]) * t + b[k + 3]) * t + b[k + 4]) * t + b[k + 5]) * t + b[k + 6]) * t + b[k + 7]) * t + b[k + 8]) * t + b[k + 9]) * t + b[k + 10]) * t + b[k + 11]) * t + b[k + 12]) * t + b[k + 13]; } else { t = 60 / w; k = 9 * ((int) t); y = ((((((((c[k] * t + c[k + 1]) * t + c[k + 2]) * t + c[k + 3]) * t + c[k + 4]) * t + c[k + 5]) * t + c[k + 6]) * t + c[k + 7]) * t + c[k + 8]) * sqrt (t) * exp (w); } return y; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/audioconvert/fmt-ops-avx2.c000066400000000000000000001175521511204443500300660ustar00rootroot00000000000000/* Spa */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include "fmt-ops.h" #include // GCC: workaround for missing AVX intrinsic: "_mm256_setr_m128()" // (see https://stackoverflow.com/questions/32630458/setting-m256i-to-the-value-of-two-m128i-values) #ifndef _mm256_setr_m128i # ifndef _mm256_set_m128i # define _mm256_set_m128i(v0, v1) _mm256_insertf128_si256(_mm256_castsi128_si256(v1), (v0), 1) # endif # define _mm256_setr_m128i(v0, v1) _mm256_set_m128i((v1), (v0)) #endif #define _MM_CLAMP_PS(r,min,max) \ _mm_min_ps(_mm_max_ps(r, min), max) #define _MM256_CLAMP_PS(r,min,max) \ _mm256_min_ps(_mm256_max_ps(r, min), max) #define _MM_CLAMP_SS(r,min,max) \ _mm_min_ss(_mm_max_ss(r, min), max) #define _MM256_BSWAP_EPI16(x) \ ({ \ _mm256_or_si256( \ _mm256_slli_epi16(x, 8), \ _mm256_srli_epi16(x, 8)); \ }) static void conv_s16_to_f32d_1s_avx2(void *data, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src, uint32_t n_channels, uint32_t n_samples) { const int16_t *s = src; float *d0 = dst[0]; uint32_t n, unrolled; __m256i in = _mm256_setzero_si256(); __m256 out, factor = _mm256_set1_ps(1.0f / S16_SCALE); if (SPA_LIKELY(SPA_IS_ALIGNED(d0, 32))) unrolled = n_samples & ~7; else unrolled = 0; for(n = 0; n < unrolled; n += 8) { in = _mm256_insert_epi16(in, s[0*n_channels], 1); in = _mm256_insert_epi16(in, s[1*n_channels], 3); in = _mm256_insert_epi16(in, s[2*n_channels], 5); in = _mm256_insert_epi16(in, s[3*n_channels], 7); in = _mm256_insert_epi16(in, s[4*n_channels], 9); in = _mm256_insert_epi16(in, s[5*n_channels], 11); in = _mm256_insert_epi16(in, s[6*n_channels], 13); in = _mm256_insert_epi16(in, s[7*n_channels], 15); in = _mm256_srai_epi32(in, 16); out = _mm256_cvtepi32_ps(in); out = _mm256_mul_ps(out, factor); _mm256_store_ps(&d0[n], out); s += 8*n_channels; } for(; n < n_samples; n++) { __m128 out, factor = _mm_set1_ps(1.0f / S16_SCALE); out = _mm_cvtsi32_ss(factor, s[0]); out = _mm_mul_ss(out, factor); _mm_store_ss(&d0[n], out); s += n_channels; } } void conv_s16_to_f32d_avx2(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], uint32_t n_samples) { const int16_t *s = src[0]; uint32_t i = 0, n_channels = conv->n_channels; for(; i < n_channels; i++) conv_s16_to_f32d_1s_avx2(conv, &dst[i], &s[i], n_channels, n_samples); } static void conv_s16s_to_f32d_1s_avx2(void *data, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src, uint32_t n_channels, uint32_t n_samples) { const uint16_t *s = src; float *d0 = dst[0]; uint32_t n, unrolled; __m256i in = _mm256_setzero_si256(); __m256 out, factor = _mm256_set1_ps(1.0f / S16_SCALE); if (SPA_LIKELY(SPA_IS_ALIGNED(d0, 32))) unrolled = n_samples & ~7; else unrolled = 0; for(n = 0; n < unrolled; n += 8) { in = _mm256_insert_epi16(in, s[0*n_channels], 1); in = _mm256_insert_epi16(in, s[1*n_channels], 3); in = _mm256_insert_epi16(in, s[2*n_channels], 5); in = _mm256_insert_epi16(in, s[3*n_channels], 7); in = _mm256_insert_epi16(in, s[4*n_channels], 9); in = _mm256_insert_epi16(in, s[5*n_channels], 11); in = _mm256_insert_epi16(in, s[6*n_channels], 13); in = _mm256_insert_epi16(in, s[7*n_channels], 15); in = _MM256_BSWAP_EPI16(in); in = _mm256_srai_epi32(in, 16); out = _mm256_cvtepi32_ps(in); out = _mm256_mul_ps(out, factor); _mm256_store_ps(&d0[n], out); s += 8*n_channels; } for(; n < n_samples; n++) { __m128 out, factor = _mm_set1_ps(1.0f / S16_SCALE); out = _mm_cvtsi32_ss(factor, (int16_t)bswap_16(s[0])); out = _mm_mul_ss(out, factor); _mm_store_ss(&d0[n], out); s += n_channels; } } void conv_s16s_to_f32d_avx2(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], uint32_t n_samples) { const uint16_t *s = src[0]; uint32_t i = 0, n_channels = conv->n_channels; for(; i < n_channels; i++) conv_s16s_to_f32d_1s_avx2(conv, &dst[i], &s[i], n_channels, n_samples); } void conv_s16_to_f32d_2_avx2(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], uint32_t n_samples) { const int16_t *s = src[0]; float *d0 = dst[0], *d1 = dst[1]; uint32_t n, unrolled; __m256i in[2], t[4]; __m256 out[4], factor = _mm256_set1_ps(1.0f / S16_SCALE); if (SPA_IS_ALIGNED(s, 32) && SPA_IS_ALIGNED(d0, 32) && SPA_IS_ALIGNED(d1, 32)) unrolled = n_samples & ~15; else unrolled = 0; for(n = 0; n < unrolled; n += 16) { in[0] = _mm256_load_si256((__m256i*)(s + 0)); in[1] = _mm256_load_si256((__m256i*)(s + 16)); t[0] = _mm256_slli_epi32(in[0], 16); t[0] = _mm256_srai_epi32(t[0], 16); out[0] = _mm256_cvtepi32_ps(t[0]); out[0] = _mm256_mul_ps(out[0], factor); t[1] = _mm256_srai_epi32(in[0], 16); out[1] = _mm256_cvtepi32_ps(t[1]); out[1] = _mm256_mul_ps(out[1], factor); t[2] = _mm256_slli_epi32(in[1], 16); t[2] = _mm256_srai_epi32(t[2], 16); out[2] = _mm256_cvtepi32_ps(t[2]); out[2] = _mm256_mul_ps(out[2], factor); t[3] = _mm256_srai_epi32(in[1], 16); out[3] = _mm256_cvtepi32_ps(t[3]); out[3] = _mm256_mul_ps(out[3], factor); _mm256_store_ps(&d0[n + 0], out[0]); _mm256_store_ps(&d1[n + 0], out[1]); _mm256_store_ps(&d0[n + 8], out[2]); _mm256_store_ps(&d1[n + 8], out[3]); s += 32; } for(; n < n_samples; n++) { __m128 out[4], factor = _mm_set1_ps(1.0f / S16_SCALE); out[0] = _mm_cvtsi32_ss(factor, s[0]); out[0] = _mm_mul_ss(out[0], factor); out[1] = _mm_cvtsi32_ss(factor, s[1]); out[1] = _mm_mul_ss(out[1], factor); _mm_store_ss(&d0[n], out[0]); _mm_store_ss(&d1[n], out[1]); s += 2; } } void conv_s16s_to_f32d_2_avx2(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], uint32_t n_samples) { const uint16_t *s = src[0]; float *d0 = dst[0], *d1 = dst[1]; uint32_t n, unrolled; __m256i in[2], t[4]; __m256 out[4], factor = _mm256_set1_ps(1.0f / S16_SCALE); if (SPA_IS_ALIGNED(s, 32) && SPA_IS_ALIGNED(d0, 32) && SPA_IS_ALIGNED(d1, 32)) unrolled = n_samples & ~15; else unrolled = 0; for(n = 0; n < unrolled; n += 16) { in[0] = _mm256_load_si256((__m256i*)(s + 0)); in[1] = _mm256_load_si256((__m256i*)(s + 16)); in[0] = _MM256_BSWAP_EPI16(in[0]); in[1] = _MM256_BSWAP_EPI16(in[1]); t[0] = _mm256_slli_epi32(in[0], 16); t[0] = _mm256_srai_epi32(t[0], 16); out[0] = _mm256_cvtepi32_ps(t[0]); out[0] = _mm256_mul_ps(out[0], factor); t[1] = _mm256_srai_epi32(in[0], 16); out[1] = _mm256_cvtepi32_ps(t[1]); out[1] = _mm256_mul_ps(out[1], factor); t[2] = _mm256_slli_epi32(in[1], 16); t[2] = _mm256_srai_epi32(t[2], 16); out[2] = _mm256_cvtepi32_ps(t[2]); out[2] = _mm256_mul_ps(out[2], factor); t[3] = _mm256_srai_epi32(in[1], 16); out[3] = _mm256_cvtepi32_ps(t[3]); out[3] = _mm256_mul_ps(out[3], factor); _mm256_store_ps(&d0[n + 0], out[0]); _mm256_store_ps(&d1[n + 0], out[1]); _mm256_store_ps(&d0[n + 8], out[2]); _mm256_store_ps(&d1[n + 8], out[3]); s += 32; } for(; n < n_samples; n++) { __m128 out[4], factor = _mm_set1_ps(1.0f / S16_SCALE); out[0] = _mm_cvtsi32_ss(factor, (int16_t)bswap_16(s[0])); out[0] = _mm_mul_ss(out[0], factor); out[1] = _mm_cvtsi32_ss(factor, (int16_t)bswap_16(s[1])); out[1] = _mm_mul_ss(out[1], factor); _mm_store_ss(&d0[n], out[0]); _mm_store_ss(&d1[n], out[1]); s += 2; } } static void conv_s24_to_f32d_1s_avx2(void *data, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src, uint32_t n_channels, uint32_t n_samples) { const int8_t *s = src; float *d0 = dst[0]; uint32_t n, unrolled; __m128i in; __m128 out, factor = _mm_set1_ps(1.0f / S24_SCALE); __m128i mask1 = _mm_setr_epi32(0*n_channels, 3*n_channels, 6*n_channels, 9*n_channels); if (SPA_IS_ALIGNED(d0, 16) && n_samples > 0) { unrolled = n_samples & ~3; if ((n_samples & 3) == 0) unrolled -= 4; } else unrolled = 0; for(n = 0; n < unrolled; n += 4) { in = _mm_i32gather_epi32((int*)s, mask1, 1); in = _mm_slli_epi32(in, 8); in = _mm_srai_epi32(in, 8); out = _mm_cvtepi32_ps(in); out = _mm_mul_ps(out, factor); _mm_store_ps(&d0[n], out); s += 12 * n_channels; } for(; n < n_samples; n++) { out = _mm_cvtsi32_ss(factor, s24_to_s32(*(int24_t*)s)); out = _mm_mul_ss(out, factor); _mm_store_ss(&d0[n], out); s += 3 * n_channels; } } static void conv_s24_to_f32d_2s_avx2(void *data, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src, uint32_t n_channels, uint32_t n_samples) { const int8_t *s = src; float *d0 = dst[0], *d1 = dst[1]; uint32_t n, unrolled; __m128i in[2]; __m128 out[2], factor = _mm_set1_ps(1.0f / S24_SCALE); __m128i mask1 = _mm_setr_epi32(0*n_channels, 3*n_channels, 6*n_channels, 9*n_channels); if (SPA_IS_ALIGNED(d0, 16) && SPA_IS_ALIGNED(d1, 16) && n_samples > 0) { unrolled = n_samples & ~3; if ((n_samples & 3) == 0) unrolled -= 4; } else unrolled = 0; for(n = 0; n < unrolled; n += 4) { in[0] = _mm_i32gather_epi32((int*)&s[0], mask1, 1); in[1] = _mm_i32gather_epi32((int*)&s[3], mask1, 1); in[0] = _mm_slli_epi32(in[0], 8); in[1] = _mm_slli_epi32(in[1], 8); in[0] = _mm_srai_epi32(in[0], 8); in[1] = _mm_srai_epi32(in[1], 8); out[0] = _mm_cvtepi32_ps(in[0]); out[1] = _mm_cvtepi32_ps(in[1]); out[0] = _mm_mul_ps(out[0], factor); out[1] = _mm_mul_ps(out[1], factor); _mm_store_ps(&d0[n], out[0]); _mm_store_ps(&d1[n], out[1]); s += 12 * n_channels; } for(; n < n_samples; n++) { out[0] = _mm_cvtsi32_ss(factor, s24_to_s32(*((int24_t*)s+0))); out[1] = _mm_cvtsi32_ss(factor, s24_to_s32(*((int24_t*)s+1))); out[0] = _mm_mul_ss(out[0], factor); out[1] = _mm_mul_ss(out[1], factor); _mm_store_ss(&d0[n], out[0]); _mm_store_ss(&d1[n], out[1]); s += 3 * n_channels; } } static void conv_s24_to_f32d_4s_avx2(void *data, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src, uint32_t n_channels, uint32_t n_samples) { const int8_t *s = src; float *d0 = dst[0], *d1 = dst[1], *d2 = dst[2], *d3 = dst[3]; uint32_t n, unrolled; __m128i in[4]; __m128 out[4], factor = _mm_set1_ps(1.0f / S24_SCALE); __m128i mask1 = _mm_setr_epi32(0*n_channels, 3*n_channels, 6*n_channels, 9*n_channels); if (SPA_IS_ALIGNED(d0, 16) && SPA_IS_ALIGNED(d1, 16) && SPA_IS_ALIGNED(d2, 16) && SPA_IS_ALIGNED(d3, 16) && n_samples > 0) { unrolled = n_samples & ~3; if ((n_samples & 3) == 0) unrolled -= 4; } else unrolled = 0; for(n = 0; n < unrolled; n += 4) { in[0] = _mm_i32gather_epi32((int*)&s[0], mask1, 1); in[1] = _mm_i32gather_epi32((int*)&s[3], mask1, 1); in[2] = _mm_i32gather_epi32((int*)&s[6], mask1, 1); in[3] = _mm_i32gather_epi32((int*)&s[9], mask1, 1); in[0] = _mm_slli_epi32(in[0], 8); in[1] = _mm_slli_epi32(in[1], 8); in[2] = _mm_slli_epi32(in[2], 8); in[3] = _mm_slli_epi32(in[3], 8); in[0] = _mm_srai_epi32(in[0], 8); in[1] = _mm_srai_epi32(in[1], 8); in[2] = _mm_srai_epi32(in[2], 8); in[3] = _mm_srai_epi32(in[3], 8); out[0] = _mm_cvtepi32_ps(in[0]); out[1] = _mm_cvtepi32_ps(in[1]); out[2] = _mm_cvtepi32_ps(in[2]); out[3] = _mm_cvtepi32_ps(in[3]); out[0] = _mm_mul_ps(out[0], factor); out[1] = _mm_mul_ps(out[1], factor); out[2] = _mm_mul_ps(out[2], factor); out[3] = _mm_mul_ps(out[3], factor); _mm_store_ps(&d0[n], out[0]); _mm_store_ps(&d1[n], out[1]); _mm_store_ps(&d2[n], out[2]); _mm_store_ps(&d3[n], out[3]); s += 12 * n_channels; } for(; n < n_samples; n++) { out[0] = _mm_cvtsi32_ss(factor, s24_to_s32(*((int24_t*)s+0))); out[1] = _mm_cvtsi32_ss(factor, s24_to_s32(*((int24_t*)s+1))); out[2] = _mm_cvtsi32_ss(factor, s24_to_s32(*((int24_t*)s+2))); out[3] = _mm_cvtsi32_ss(factor, s24_to_s32(*((int24_t*)s+3))); out[0] = _mm_mul_ss(out[0], factor); out[1] = _mm_mul_ss(out[1], factor); out[2] = _mm_mul_ss(out[2], factor); out[3] = _mm_mul_ss(out[3], factor); _mm_store_ss(&d0[n], out[0]); _mm_store_ss(&d1[n], out[1]); _mm_store_ss(&d2[n], out[2]); _mm_store_ss(&d3[n], out[3]); s += 3 * n_channels; } } void conv_s24_to_f32d_avx2(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], uint32_t n_samples) { const int8_t *s = src[0]; uint32_t i = 0, n_channels = conv->n_channels; for(; i + 3 < n_channels; i += 4) conv_s24_to_f32d_4s_avx2(conv, &dst[i], &s[3*i], n_channels, n_samples); for(; i + 1 < n_channels; i += 2) conv_s24_to_f32d_2s_avx2(conv, &dst[i], &s[3*i], n_channels, n_samples); for(; i < n_channels; i++) conv_s24_to_f32d_1s_avx2(conv, &dst[i], &s[3*i], n_channels, n_samples); } static void conv_s32_to_f32d_4s_avx2(void *data, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src, uint32_t n_channels, uint32_t n_samples) { const int32_t *s = src; float *d0 = dst[0], *d1 = dst[1], *d2 = dst[2], *d3 = dst[3]; uint32_t n, unrolled; __m256i in[4]; __m256 out[4], factor = _mm256_set1_ps(1.0f / S32_SCALE_I2F); __m256i mask1 = _mm256_setr_epi32(0*n_channels, 1*n_channels, 2*n_channels, 3*n_channels, 4*n_channels, 5*n_channels, 6*n_channels, 7*n_channels); if (SPA_IS_ALIGNED(d0, 32) && SPA_IS_ALIGNED(d1, 32) && SPA_IS_ALIGNED(d2, 32) && SPA_IS_ALIGNED(d3, 32)) unrolled = n_samples & ~7; else unrolled = 0; for(n = 0; n < unrolled; n += 8) { in[0] = _mm256_i32gather_epi32((int*)&s[0], mask1, 4); in[1] = _mm256_i32gather_epi32((int*)&s[1], mask1, 4); in[2] = _mm256_i32gather_epi32((int*)&s[2], mask1, 4); in[3] = _mm256_i32gather_epi32((int*)&s[3], mask1, 4); out[0] = _mm256_cvtepi32_ps(in[0]); out[1] = _mm256_cvtepi32_ps(in[1]); out[2] = _mm256_cvtepi32_ps(in[2]); out[3] = _mm256_cvtepi32_ps(in[3]); out[0] = _mm256_mul_ps(out[0], factor); out[1] = _mm256_mul_ps(out[1], factor); out[2] = _mm256_mul_ps(out[2], factor); out[3] = _mm256_mul_ps(out[3], factor); _mm256_store_ps(&d0[n], out[0]); _mm256_store_ps(&d1[n], out[1]); _mm256_store_ps(&d2[n], out[2]); _mm256_store_ps(&d3[n], out[3]); s += 8*n_channels; } for(; n < n_samples; n++) { __m128 out[4], factor = _mm_set1_ps(1.0f / S32_SCALE_I2F); out[0] = _mm_cvtsi32_ss(factor, s[0]); out[1] = _mm_cvtsi32_ss(factor, s[1]); out[2] = _mm_cvtsi32_ss(factor, s[2]); out[3] = _mm_cvtsi32_ss(factor, s[3]); out[0] = _mm_mul_ss(out[0], factor); out[1] = _mm_mul_ss(out[1], factor); out[2] = _mm_mul_ss(out[2], factor); out[3] = _mm_mul_ss(out[3], factor); _mm_store_ss(&d0[n], out[0]); _mm_store_ss(&d1[n], out[1]); _mm_store_ss(&d2[n], out[2]); _mm_store_ss(&d3[n], out[3]); s += n_channels; } } static void conv_s32_to_f32d_2s_avx2(void *data, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src, uint32_t n_channels, uint32_t n_samples) { const int32_t *s = src; float *d0 = dst[0], *d1 = dst[1]; uint32_t n, unrolled; __m256i in[4]; __m256 out[4], factor = _mm256_set1_ps(1.0f / S32_SCALE_I2F); __m256i mask1 = _mm256_setr_epi32(0*n_channels, 1*n_channels, 2*n_channels, 3*n_channels, 4*n_channels, 5*n_channels, 6*n_channels, 7*n_channels); if (SPA_IS_ALIGNED(d0, 32) && SPA_IS_ALIGNED(d1, 32)) unrolled = n_samples & ~7; else unrolled = 0; for(n = 0; n < unrolled; n += 8) { in[0] = _mm256_i32gather_epi32((int*)&s[0], mask1, 4); in[1] = _mm256_i32gather_epi32((int*)&s[1], mask1, 4); out[0] = _mm256_cvtepi32_ps(in[0]); out[1] = _mm256_cvtepi32_ps(in[1]); out[0] = _mm256_mul_ps(out[0], factor); out[1] = _mm256_mul_ps(out[1], factor); _mm256_store_ps(&d0[n], out[0]); _mm256_store_ps(&d1[n], out[1]); s += 8*n_channels; } for(; n < n_samples; n++) { __m128 out[2], factor = _mm_set1_ps(1.0f / S32_SCALE_I2F); out[0] = _mm_cvtsi32_ss(factor, s[0]); out[1] = _mm_cvtsi32_ss(factor, s[1]); out[0] = _mm_mul_ss(out[0], factor); out[1] = _mm_mul_ss(out[1], factor); _mm_store_ss(&d0[n], out[0]); _mm_store_ss(&d1[n], out[1]); s += n_channels; } } static void conv_s32_to_f32d_1s_avx2(void *data, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src, uint32_t n_channels, uint32_t n_samples) { const int32_t *s = src; float *d0 = dst[0]; uint32_t n, unrolled; __m256i in[2]; __m256 out[2], factor = _mm256_set1_ps(1.0f / S32_SCALE_I2F); __m256i mask1 = _mm256_setr_epi32(0*n_channels, 1*n_channels, 2*n_channels, 3*n_channels, 4*n_channels, 5*n_channels, 6*n_channels, 7*n_channels); if (SPA_IS_ALIGNED(d0, 32)) unrolled = n_samples & ~15; else unrolled = 0; for(n = 0; n < unrolled; n += 16) { in[0] = _mm256_i32gather_epi32(&s[0*n_channels], mask1, 4); in[1] = _mm256_i32gather_epi32(&s[8*n_channels], mask1, 4); out[0] = _mm256_cvtepi32_ps(in[0]); out[1] = _mm256_cvtepi32_ps(in[1]); out[0] = _mm256_mul_ps(out[0], factor); out[1] = _mm256_mul_ps(out[1], factor); _mm256_store_ps(&d0[n+0], out[0]); _mm256_store_ps(&d0[n+8], out[1]); s += 16*n_channels; } for(; n < n_samples; n++) { __m128 out, factor = _mm_set1_ps(1.0f / S32_SCALE_I2F); out = _mm_cvtsi32_ss(factor, s[0]); out = _mm_mul_ss(out, factor); _mm_store_ss(&d0[n], out); s += n_channels; } } void conv_s32_to_f32d_avx2(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], uint32_t n_samples) { const int32_t *s = src[0]; uint32_t i = 0, n_channels = conv->n_channels; for(; i + 3 < n_channels; i += 4) conv_s32_to_f32d_4s_avx2(conv, &dst[i], &s[i], n_channels, n_samples); for(; i + 1 < n_channels; i += 2) conv_s32_to_f32d_2s_avx2(conv, &dst[i], &s[i], n_channels, n_samples); for(; i < n_channels; i++) conv_s32_to_f32d_1s_avx2(conv, &dst[i], &s[i], n_channels, n_samples); } static void conv_f32d_to_s32_1s_avx2(void *data, void * SPA_RESTRICT dst, const void * SPA_RESTRICT src[], uint32_t n_channels, uint32_t n_samples) { const float *s0 = src[0]; int32_t *d = dst; uint32_t n, unrolled; __m128 in[1]; __m128i out[4]; __m128 scale = _mm_set1_ps(S32_SCALE_F2I); __m128 int_min = _mm_set1_ps(S32_MIN_F2I); __m128 int_max = _mm_set1_ps(S32_MAX_F2I); if (SPA_IS_ALIGNED(s0, 16)) unrolled = n_samples & ~3; else unrolled = 0; for(n = 0; n < unrolled; n += 4) { in[0] = _mm_mul_ps(_mm_load_ps(&s0[n]), scale); in[0] = _MM_CLAMP_PS(in[0], int_min, int_max); out[0] = _mm_cvtps_epi32(in[0]); out[1] = _mm_shuffle_epi32(out[0], _MM_SHUFFLE(0, 3, 2, 1)); out[2] = _mm_shuffle_epi32(out[0], _MM_SHUFFLE(1, 0, 3, 2)); out[3] = _mm_shuffle_epi32(out[0], _MM_SHUFFLE(2, 1, 0, 3)); d[0*n_channels] = _mm_cvtsi128_si32(out[0]); d[1*n_channels] = _mm_cvtsi128_si32(out[1]); d[2*n_channels] = _mm_cvtsi128_si32(out[2]); d[3*n_channels] = _mm_cvtsi128_si32(out[3]); d += 4*n_channels; } for(; n < n_samples; n++) { in[0] = _mm_load_ss(&s0[n]); in[0] = _mm_mul_ss(in[0], scale); in[0] = _MM_CLAMP_SS(in[0], int_min, int_max); *d = _mm_cvtss_si32(in[0]); d += n_channels; } } #define spa_write_unaligned(ptr, type, val) \ __extension__ ({ \ __typeof__(type) _val = (val); \ memcpy((ptr), &_val, sizeof(_val)); \ }) static void conv_f32d_to_s32_2s_avx2(void *data, void * SPA_RESTRICT dst, const void * SPA_RESTRICT src[], uint32_t n_channels, uint32_t n_samples) { const float *s0 = src[0], *s1 = src[1]; int32_t *d = dst; uint32_t n, unrolled; __m256 in[2]; __m256i out[2], t[2]; __m256 scale = _mm256_set1_ps(S32_SCALE_F2I); __m256 int_min = _mm256_set1_ps(S32_MIN_F2I); __m256 int_max = _mm256_set1_ps(S32_MAX_F2I); if (SPA_IS_ALIGNED(s0, 32) && SPA_IS_ALIGNED(s1, 32)) unrolled = n_samples & ~7; else unrolled = 0; for(n = 0; n < unrolled; n += 8) { in[0] = _mm256_mul_ps(_mm256_load_ps(&s0[n]), scale); in[1] = _mm256_mul_ps(_mm256_load_ps(&s1[n]), scale); in[0] = _MM256_CLAMP_PS(in[0], int_min, int_max); in[1] = _MM256_CLAMP_PS(in[1], int_min, int_max); out[0] = _mm256_cvtps_epi32(in[0]); /* a0 a1 a2 a3 a4 a5 a6 a7 */ out[1] = _mm256_cvtps_epi32(in[1]); /* b0 b1 b2 b3 b4 b5 b6 b7 */ t[0] = _mm256_unpacklo_epi32(out[0], out[1]); /* a0 b0 a1 b1 a4 b4 a5 b5 */ t[1] = _mm256_unpackhi_epi32(out[0], out[1]); /* a2 b2 a3 b3 a6 b6 a7 b7 */ #ifdef __x86_64__ spa_write_unaligned(d + 0*n_channels, uint64_t, _mm256_extract_epi64(t[0], 0)); spa_write_unaligned(d + 1*n_channels, uint64_t, _mm256_extract_epi64(t[0], 1)); spa_write_unaligned(d + 2*n_channels, uint64_t, _mm256_extract_epi64(t[1], 0)); spa_write_unaligned(d + 3*n_channels, uint64_t, _mm256_extract_epi64(t[1], 1)); spa_write_unaligned(d + 4*n_channels, uint64_t, _mm256_extract_epi64(t[0], 2)); spa_write_unaligned(d + 5*n_channels, uint64_t, _mm256_extract_epi64(t[0], 3)); spa_write_unaligned(d + 6*n_channels, uint64_t, _mm256_extract_epi64(t[1], 2)); spa_write_unaligned(d + 7*n_channels, uint64_t, _mm256_extract_epi64(t[1], 3)); #else _mm_storel_pi((__m64*)(d + 0*n_channels), (__m128)_mm256_extracti128_si256(t[0], 0)); _mm_storeh_pi((__m64*)(d + 1*n_channels), (__m128)_mm256_extracti128_si256(t[0], 0)); _mm_storel_pi((__m64*)(d + 2*n_channels), (__m128)_mm256_extracti128_si256(t[1], 0)); _mm_storeh_pi((__m64*)(d + 3*n_channels), (__m128)_mm256_extracti128_si256(t[1], 0)); _mm_storel_pi((__m64*)(d + 4*n_channels), (__m128)_mm256_extracti128_si256(t[0], 1)); _mm_storeh_pi((__m64*)(d + 5*n_channels), (__m128)_mm256_extracti128_si256(t[0], 1)); _mm_storel_pi((__m64*)(d + 6*n_channels), (__m128)_mm256_extracti128_si256(t[1], 1)); _mm_storeh_pi((__m64*)(d + 7*n_channels), (__m128)_mm256_extracti128_si256(t[1], 1)); #endif d += 8*n_channels; } for(; n < n_samples; n++) { __m128 in[2]; __m128i out[2]; __m128 scale = _mm_set1_ps(S32_SCALE_F2I); __m128 int_min = _mm_set1_ps(S32_MIN_F2I); __m128 int_max = _mm_set1_ps(S32_MAX_F2I); in[0] = _mm_load_ss(&s0[n]); in[1] = _mm_load_ss(&s1[n]); in[0] = _mm_unpacklo_ps(in[0], in[1]); in[0] = _mm_mul_ps(in[0], scale); in[0] = _MM_CLAMP_PS(in[0], int_min, int_max); out[0] = _mm_cvtps_epi32(in[0]); _mm_storel_epi64((__m128i*)d, out[0]); d += n_channels; } } static void conv_f32d_to_s32_4s_avx2(void *data, void * SPA_RESTRICT dst, const void * SPA_RESTRICT src[], uint32_t n_channels, uint32_t n_samples) { const float *s0 = src[0], *s1 = src[1], *s2 = src[2], *s3 = src[3]; int32_t *d = dst; uint32_t n, unrolled; __m256 in[4]; __m256i out[4], t[4]; __m256 scale = _mm256_set1_ps(S32_SCALE_F2I); __m256 int_min = _mm256_set1_ps(S32_MIN_F2I); __m256 int_max = _mm256_set1_ps(S32_MAX_F2I); if (SPA_IS_ALIGNED(s0, 32) && SPA_IS_ALIGNED(s1, 32) && SPA_IS_ALIGNED(s2, 32) && SPA_IS_ALIGNED(s3, 32)) unrolled = n_samples & ~7; else unrolled = 0; for(n = 0; n < unrolled; n += 8) { in[0] = _mm256_mul_ps(_mm256_load_ps(&s0[n]), scale); in[1] = _mm256_mul_ps(_mm256_load_ps(&s1[n]), scale); in[2] = _mm256_mul_ps(_mm256_load_ps(&s2[n]), scale); in[3] = _mm256_mul_ps(_mm256_load_ps(&s3[n]), scale); in[0] = _MM256_CLAMP_PS(in[0], int_min, int_max); in[1] = _MM256_CLAMP_PS(in[1], int_min, int_max); in[2] = _MM256_CLAMP_PS(in[2], int_min, int_max); in[3] = _MM256_CLAMP_PS(in[3], int_min, int_max); out[0] = _mm256_cvtps_epi32(in[0]); /* a0 a1 a2 a3 a4 a5 a6 a7 */ out[1] = _mm256_cvtps_epi32(in[1]); /* b0 b1 b2 b3 b4 b5 b6 b7 */ out[2] = _mm256_cvtps_epi32(in[2]); /* c0 c1 c2 c3 c4 c5 c6 c7 */ out[3] = _mm256_cvtps_epi32(in[3]); /* d0 d1 d2 d3 d4 d5 d6 d7 */ t[0] = _mm256_unpacklo_epi32(out[0], out[1]); /* a0 b0 a1 b1 a4 b4 a5 b5 */ t[1] = _mm256_unpackhi_epi32(out[0], out[1]); /* a2 b2 a3 b3 a6 b6 a7 b7 */ t[2] = _mm256_unpacklo_epi32(out[2], out[3]); /* c0 d0 c1 d1 c4 d4 c5 d5 */ t[3] = _mm256_unpackhi_epi32(out[2], out[3]); /* c2 d2 c3 d3 c6 d6 c7 d7 */ out[0] = _mm256_unpacklo_epi64(t[0], t[2]); /* a0 b0 c0 d0 a4 b4 c4 d4 */ out[1] = _mm256_unpackhi_epi64(t[0], t[2]); /* a1 b1 c1 d1 a5 b5 c5 d5 */ out[2] = _mm256_unpacklo_epi64(t[1], t[3]); /* a2 b2 c2 d2 a6 b6 c6 d6 */ out[3] = _mm256_unpackhi_epi64(t[1], t[3]); /* a3 b3 c3 d3 a7 b7 c7 d7 */ _mm_storeu_si128((__m128i*)(d + 0*n_channels), _mm256_extracti128_si256(out[0], 0)); _mm_storeu_si128((__m128i*)(d + 1*n_channels), _mm256_extracti128_si256(out[1], 0)); _mm_storeu_si128((__m128i*)(d + 2*n_channels), _mm256_extracti128_si256(out[2], 0)); _mm_storeu_si128((__m128i*)(d + 3*n_channels), _mm256_extracti128_si256(out[3], 0)); _mm_storeu_si128((__m128i*)(d + 4*n_channels), _mm256_extracti128_si256(out[0], 1)); _mm_storeu_si128((__m128i*)(d + 5*n_channels), _mm256_extracti128_si256(out[1], 1)); _mm_storeu_si128((__m128i*)(d + 6*n_channels), _mm256_extracti128_si256(out[2], 1)); _mm_storeu_si128((__m128i*)(d + 7*n_channels), _mm256_extracti128_si256(out[3], 1)); d += 8*n_channels; } for(; n < n_samples; n++) { __m128 in[4]; __m128i out[4]; __m128 scale = _mm_set1_ps(S32_SCALE_F2I); __m128 int_min = _mm_set1_ps(S32_MIN_F2I); __m128 int_max = _mm_set1_ps(S32_MAX_F2I); in[0] = _mm_load_ss(&s0[n]); in[1] = _mm_load_ss(&s1[n]); in[2] = _mm_load_ss(&s2[n]); in[3] = _mm_load_ss(&s3[n]); in[0] = _mm_unpacklo_ps(in[0], in[2]); in[1] = _mm_unpacklo_ps(in[1], in[3]); in[0] = _mm_unpacklo_ps(in[0], in[1]); in[0] = _mm_mul_ps(in[0], scale); in[0] = _MM_CLAMP_PS(in[0], int_min, int_max); out[0] = _mm_cvtps_epi32(in[0]); _mm_storeu_si128((__m128i*)d, out[0]); d += n_channels; } } void conv_f32d_to_s32_avx2(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], uint32_t n_samples) { int32_t *d = dst[0]; uint32_t i = 0, n_channels = conv->n_channels; for(; i + 3 < n_channels; i += 4) conv_f32d_to_s32_4s_avx2(conv, &d[i], &src[i], n_channels, n_samples); for(; i + 1 < n_channels; i += 2) conv_f32d_to_s32_2s_avx2(conv, &d[i], &src[i], n_channels, n_samples); for(; i < n_channels; i++) conv_f32d_to_s32_1s_avx2(conv, &d[i], &src[i], n_channels, n_samples); } static void conv_f32d_to_s16_1s_avx2(void *data, void * SPA_RESTRICT dst, const void * SPA_RESTRICT src[], uint32_t n_channels, uint32_t n_samples) { const float *s0 = src[0]; int16_t *d = dst; uint32_t n, unrolled; __m128 in[2]; __m128i out[2]; __m128 int_scale = _mm_set1_ps(S16_SCALE); __m128 int_max = _mm_set1_ps(S16_MAX); __m128 int_min = _mm_set1_ps(S16_MIN); if (SPA_IS_ALIGNED(s0, 16)) unrolled = n_samples & ~7; else unrolled = 0; for(n = 0; n < unrolled; n += 8) { in[0] = _mm_mul_ps(_mm_load_ps(&s0[n]), int_scale); in[1] = _mm_mul_ps(_mm_load_ps(&s0[n+4]), int_scale); out[0] = _mm_cvtps_epi32(in[0]); out[1] = _mm_cvtps_epi32(in[1]); out[0] = _mm_packs_epi32(out[0], out[1]); d[0*n_channels] = _mm_extract_epi16(out[0], 0); d[1*n_channels] = _mm_extract_epi16(out[0], 1); d[2*n_channels] = _mm_extract_epi16(out[0], 2); d[3*n_channels] = _mm_extract_epi16(out[0], 3); d[4*n_channels] = _mm_extract_epi16(out[0], 4); d[5*n_channels] = _mm_extract_epi16(out[0], 5); d[6*n_channels] = _mm_extract_epi16(out[0], 6); d[7*n_channels] = _mm_extract_epi16(out[0], 7); d += 8*n_channels; } for(; n < n_samples; n++) { in[0] = _mm_mul_ss(_mm_load_ss(&s0[n]), int_scale); in[0] = _MM_CLAMP_SS(in[0], int_min, int_max); *d = _mm_cvtss_si32(in[0]); d += n_channels; } } static void conv_f32d_to_s16_2s_avx2(void *data, void * SPA_RESTRICT dst, const void * SPA_RESTRICT src[], uint32_t n_channels, uint32_t n_samples) { const float *s0 = src[0], *s1 = src[1]; int16_t *d = dst; uint32_t n, unrolled; __m256 in[2]; __m256i out[4], t[2]; __m256 int_scale = _mm256_set1_ps(S16_SCALE); if (SPA_IS_ALIGNED(s0, 32) && SPA_IS_ALIGNED(s1, 32)) unrolled = n_samples & ~15; else unrolled = 0; for(n = 0; n < unrolled; n += 8) { in[0] = _mm256_mul_ps(_mm256_load_ps(&s0[n+0]), int_scale); in[1] = _mm256_mul_ps(_mm256_load_ps(&s1[n+0]), int_scale); out[0] = _mm256_cvtps_epi32(in[0]); /* a0 a1 a2 a3 a4 a5 a6 a7 */ out[1] = _mm256_cvtps_epi32(in[1]); /* b0 b1 b2 b3 b4 b5 b6 b7 */ t[0] = _mm256_unpacklo_epi32(out[0], out[1]); /* a0 b0 a1 b1 a4 b4 a5 b5 */ t[1] = _mm256_unpackhi_epi32(out[0], out[1]); /* a2 b2 a3 b3 a6 b6 a7 b7 */ out[0] = _mm256_packs_epi32(t[0], t[1]); /* a0 b0 a1 b1 a2 b2 a3 b3 a4 b4 a5 b5 a6 b6 a7 b7 */ spa_write_unaligned(d + 0*n_channels, uint32_t, _mm256_extract_epi32(out[0],0)); spa_write_unaligned(d + 1*n_channels, uint32_t, _mm256_extract_epi32(out[0],1)); spa_write_unaligned(d + 2*n_channels, uint32_t, _mm256_extract_epi32(out[0],2)); spa_write_unaligned(d + 3*n_channels, uint32_t, _mm256_extract_epi32(out[0],3)); spa_write_unaligned(d + 4*n_channels, uint32_t, _mm256_extract_epi32(out[0],4)); spa_write_unaligned(d + 5*n_channels, uint32_t, _mm256_extract_epi32(out[0],5)); spa_write_unaligned(d + 6*n_channels, uint32_t, _mm256_extract_epi32(out[0],6)); spa_write_unaligned(d + 7*n_channels, uint32_t, _mm256_extract_epi32(out[0],7)); d += 8*n_channels; } for(; n < n_samples; n++) { __m128 in[2]; __m128 int_scale = _mm_set1_ps(S16_SCALE); __m128 int_max = _mm_set1_ps(S16_MAX); __m128 int_min = _mm_set1_ps(S16_MIN); in[0] = _mm_mul_ss(_mm_load_ss(&s0[n]), int_scale); in[1] = _mm_mul_ss(_mm_load_ss(&s1[n]), int_scale); in[0] = _MM_CLAMP_SS(in[0], int_min, int_max); in[1] = _MM_CLAMP_SS(in[1], int_min, int_max); d[0] = _mm_cvtss_si32(in[0]); d[1] = _mm_cvtss_si32(in[1]); d += n_channels; } } static void conv_f32d_to_s16_4s_avx2(void *data, void * SPA_RESTRICT dst, const void * SPA_RESTRICT src[], uint32_t n_channels, uint32_t n_samples) { const float *s0 = src[0], *s1 = src[1], *s2 = src[2], *s3 = src[3]; int16_t *d = dst; uint32_t n, unrolled; __m256 in[4]; __m256i out[4], t[4]; __m256 int_scale = _mm256_set1_ps(S16_SCALE); if (SPA_IS_ALIGNED(s0, 32) && SPA_IS_ALIGNED(s1, 32) && SPA_IS_ALIGNED(s2, 32) && SPA_IS_ALIGNED(s3, 32)) unrolled = n_samples & ~7; else unrolled = 0; for(n = 0; n < unrolled; n += 8) { in[0] = _mm256_mul_ps(_mm256_load_ps(&s0[n]), int_scale); in[1] = _mm256_mul_ps(_mm256_load_ps(&s1[n]), int_scale); in[2] = _mm256_mul_ps(_mm256_load_ps(&s2[n]), int_scale); in[3] = _mm256_mul_ps(_mm256_load_ps(&s3[n]), int_scale); t[0] = _mm256_cvtps_epi32(in[0]); /* a0 a1 a2 a3 a4 a5 a6 a7 */ t[1] = _mm256_cvtps_epi32(in[1]); /* b0 b1 b2 b3 b4 b5 b6 b7 */ t[2] = _mm256_cvtps_epi32(in[2]); /* c0 c1 c2 c3 c4 c5 c6 c7 */ t[3] = _mm256_cvtps_epi32(in[3]); /* d0 d1 d2 d3 d4 d5 d6 d7 */ t[0] = _mm256_packs_epi32(t[0], t[2]); /* a0 a1 a2 a3 c0 c1 c2 c3 a4 a5 a6 a7 c4 c5 c6 c7 */ t[1] = _mm256_packs_epi32(t[1], t[3]); /* b0 b1 b2 b3 d0 d1 d2 d3 b4 b5 b6 b7 d4 d5 d6 d7 */ out[0] = _mm256_unpacklo_epi16(t[0], t[1]); /* a0 b0 a1 b1 a2 b2 a3 b3 a4 b4 a5 b5 a6 b6 a7 b7 */ out[1] = _mm256_unpackhi_epi16(t[0], t[1]); /* c0 d0 c1 d1 c2 d2 c3 d3 c4 d4 c5 d5 c6 d6 c7 d7 */ out[2] = _mm256_unpacklo_epi32(out[0], out[1]); /* a0 b0 c0 d0 a1 b1 c1 d1 a4 b4 c4 d4 a5 b5 c5 d5 */ out[3] = _mm256_unpackhi_epi32(out[0], out[1]); /* a2 b2 c2 d2 a3 b3 c3 d3 a6 b6 c6 d6 a7 b7 c7 d7 */ #ifdef __x86_64__ spa_write_unaligned(d + 0*n_channels, uint64_t, _mm256_extract_epi64(out[2], 0)); /* a0 b0 c0 d0 */ spa_write_unaligned(d + 1*n_channels, uint64_t, _mm256_extract_epi64(out[2], 1)); /* a1 b1 c1 d1 */ spa_write_unaligned(d + 2*n_channels, uint64_t, _mm256_extract_epi64(out[3], 0)); /* a2 b2 c2 d2 */ spa_write_unaligned(d + 3*n_channels, uint64_t, _mm256_extract_epi64(out[3], 1)); /* a3 b3 c3 d3 */ spa_write_unaligned(d + 4*n_channels, uint64_t, _mm256_extract_epi64(out[2], 2)); /* a4 b4 c4 d4 */ spa_write_unaligned(d + 5*n_channels, uint64_t, _mm256_extract_epi64(out[2], 3)); /* a5 b5 c5 d5 */ spa_write_unaligned(d + 6*n_channels, uint64_t, _mm256_extract_epi64(out[3], 2)); /* a6 b6 c6 d6 */ spa_write_unaligned(d + 7*n_channels, uint64_t, _mm256_extract_epi64(out[3], 3)); /* a7 b7 c7 d7 */ #else _mm_storel_pi((__m64*)(d + 0*n_channels), (__m128)_mm256_extracti128_si256(out[2], 0)); _mm_storeh_pi((__m64*)(d + 1*n_channels), (__m128)_mm256_extracti128_si256(out[2], 0)); _mm_storel_pi((__m64*)(d + 2*n_channels), (__m128)_mm256_extracti128_si256(out[3], 0)); _mm_storeh_pi((__m64*)(d + 3*n_channels), (__m128)_mm256_extracti128_si256(out[3], 0)); _mm_storel_pi((__m64*)(d + 4*n_channels), (__m128)_mm256_extracti128_si256(out[2], 1)); _mm_storeh_pi((__m64*)(d + 5*n_channels), (__m128)_mm256_extracti128_si256(out[2], 1)); _mm_storel_pi((__m64*)(d + 6*n_channels), (__m128)_mm256_extracti128_si256(out[3], 1)); _mm_storeh_pi((__m64*)(d + 7*n_channels), (__m128)_mm256_extracti128_si256(out[3], 1)); #endif d += 8*n_channels; } for(; n < n_samples; n++) { __m128 in[4]; __m128 int_scale = _mm_set1_ps(S16_SCALE); __m128 int_max = _mm_set1_ps(S16_MAX); __m128 int_min = _mm_set1_ps(S16_MIN); in[0] = _mm_mul_ss(_mm_load_ss(&s0[n]), int_scale); in[1] = _mm_mul_ss(_mm_load_ss(&s1[n]), int_scale); in[2] = _mm_mul_ss(_mm_load_ss(&s2[n]), int_scale); in[3] = _mm_mul_ss(_mm_load_ss(&s3[n]), int_scale); in[0] = _MM_CLAMP_SS(in[0], int_min, int_max); in[1] = _MM_CLAMP_SS(in[1], int_min, int_max); in[2] = _MM_CLAMP_SS(in[2], int_min, int_max); in[3] = _MM_CLAMP_SS(in[3], int_min, int_max); d[0] = _mm_cvtss_si32(in[0]); d[1] = _mm_cvtss_si32(in[1]); d[2] = _mm_cvtss_si32(in[2]); d[3] = _mm_cvtss_si32(in[3]); d += n_channels; } } void conv_f32d_to_s16_avx2(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], uint32_t n_samples) { int16_t *d = dst[0]; uint32_t i = 0, n_channels = conv->n_channels; for(; i + 3 < n_channels; i += 4) conv_f32d_to_s16_4s_avx2(conv, &d[i], &src[i], n_channels, n_samples); for(; i + 1 < n_channels; i += 2) conv_f32d_to_s16_2s_avx2(conv, &d[i], &src[i], n_channels, n_samples); for(; i < n_channels; i++) conv_f32d_to_s16_1s_avx2(conv, &d[i], &src[i], n_channels, n_samples); } void conv_f32d_to_s16_4_avx2(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], uint32_t n_samples) { const float *s0 = src[0], *s1 = src[1], *s2 = src[2], *s3 = src[3]; int16_t *d = dst[0]; uint32_t n, unrolled; __m256 in[4]; __m256i out[4], t[4]; __m256 int_scale = _mm256_set1_ps(S16_SCALE); if (SPA_IS_ALIGNED(s0, 32) && SPA_IS_ALIGNED(s1, 32) && SPA_IS_ALIGNED(s2, 32) && SPA_IS_ALIGNED(s3, 32)) unrolled = n_samples & ~7; else unrolled = 0; for(n = 0; n < unrolled; n += 8) { in[0] = _mm256_mul_ps(_mm256_load_ps(&s0[n]), int_scale); in[1] = _mm256_mul_ps(_mm256_load_ps(&s1[n]), int_scale); in[2] = _mm256_mul_ps(_mm256_load_ps(&s2[n]), int_scale); in[3] = _mm256_mul_ps(_mm256_load_ps(&s3[n]), int_scale); t[0] = _mm256_cvtps_epi32(in[0]); /* a0 a1 a2 a3 a4 a5 a6 a7 */ t[1] = _mm256_cvtps_epi32(in[1]); /* b0 b1 b2 b3 b4 b5 b6 b7 */ t[2] = _mm256_cvtps_epi32(in[2]); /* c0 c1 c2 c3 c4 c5 c6 c7 */ t[3] = _mm256_cvtps_epi32(in[3]); /* d0 d1 d2 d3 d4 d5 d6 d7 */ t[0] = _mm256_packs_epi32(t[0], t[2]); /* a0 a1 a2 a3 c0 c1 c2 c3 a4 a5 a6 a7 c4 c5 c6 c7 */ t[1] = _mm256_packs_epi32(t[1], t[3]); /* b0 b1 b2 b3 d0 d1 d2 d3 b4 b5 b6 b7 d4 d5 d6 d7 */ out[0] = _mm256_unpacklo_epi16(t[0], t[1]); /* a0 b0 a1 b1 a2 b2 a3 b3 a4 b4 a5 b5 a6 b6 a7 b7 */ out[1] = _mm256_unpackhi_epi16(t[0], t[1]); /* c0 d0 c1 d1 c2 d2 c3 d3 c4 d4 c5 d5 c6 d6 c7 d7 */ t[0] = _mm256_unpacklo_epi32(out[0], out[1]); /* a0 b0 c0 d0 a1 b1 c1 d1 a4 b4 c4 d4 a5 b5 c5 d5 */ t[2] = _mm256_unpackhi_epi32(out[0], out[1]); /* a2 b2 c2 d2 a3 b3 c3 d3 a6 b6 c6 d6 a7 b7 c7 d7 */ out[0] = _mm256_inserti128_si256(t[0], _mm256_extracti128_si256(t[2], 0), 1); out[2] = _mm256_inserti128_si256(t[2], _mm256_extracti128_si256(t[0], 1), 0); _mm256_store_si256((__m256i*)(d+0), out[0]); _mm256_store_si256((__m256i*)(d+16), out[2]); d += 32; } for(; n < n_samples; n++) { __m128 in[4]; __m128 int_scale = _mm_set1_ps(S16_SCALE); __m128 int_max = _mm_set1_ps(S16_MAX); __m128 int_min = _mm_set1_ps(S16_MIN); in[0] = _mm_mul_ss(_mm_load_ss(&s0[n]), int_scale); in[1] = _mm_mul_ss(_mm_load_ss(&s1[n]), int_scale); in[2] = _mm_mul_ss(_mm_load_ss(&s2[n]), int_scale); in[3] = _mm_mul_ss(_mm_load_ss(&s3[n]), int_scale); in[0] = _MM_CLAMP_SS(in[0], int_min, int_max); in[1] = _MM_CLAMP_SS(in[1], int_min, int_max); in[2] = _MM_CLAMP_SS(in[2], int_min, int_max); in[3] = _MM_CLAMP_SS(in[3], int_min, int_max); d[0] = _mm_cvtss_si32(in[0]); d[1] = _mm_cvtss_si32(in[1]); d[2] = _mm_cvtss_si32(in[2]); d[3] = _mm_cvtss_si32(in[3]); d += 4; } } void conv_f32d_to_s16_2_avx2(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], uint32_t n_samples) { const float *s0 = src[0], *s1 = src[1]; int16_t *d = dst[0]; uint32_t n, unrolled; __m256 in[4]; __m256i out[4], t[4]; __m256 int_scale = _mm256_set1_ps(S16_SCALE); if (SPA_IS_ALIGNED(s0, 32) && SPA_IS_ALIGNED(s1, 32)) unrolled = n_samples & ~15; else unrolled = 0; for(n = 0; n < unrolled; n += 16) { in[0] = _mm256_mul_ps(_mm256_load_ps(&s0[n+0]), int_scale); in[1] = _mm256_mul_ps(_mm256_load_ps(&s1[n+0]), int_scale); in[2] = _mm256_mul_ps(_mm256_load_ps(&s0[n+8]), int_scale); in[3] = _mm256_mul_ps(_mm256_load_ps(&s1[n+8]), int_scale); out[0] = _mm256_cvtps_epi32(in[0]); /* a0 a1 a2 a3 a4 a5 a6 a7 */ out[1] = _mm256_cvtps_epi32(in[1]); /* b0 b1 b2 b3 b4 b5 b6 b7 */ out[2] = _mm256_cvtps_epi32(in[2]); /* a0 a1 a2 a3 a4 a5 a6 a7 */ out[3] = _mm256_cvtps_epi32(in[3]); /* b0 b1 b2 b3 b4 b5 b6 b7 */ t[0] = _mm256_unpacklo_epi32(out[0], out[1]); /* a0 b0 a1 b1 a4 b4 a5 b5 */ t[1] = _mm256_unpackhi_epi32(out[0], out[1]); /* a2 b2 a3 b3 a6 b6 a7 b7 */ t[2] = _mm256_unpacklo_epi32(out[2], out[3]); /* a0 b0 a1 b1 a4 b4 a5 b5 */ t[3] = _mm256_unpackhi_epi32(out[2], out[3]); /* a2 b2 a3 b3 a6 b6 a7 b7 */ out[0] = _mm256_packs_epi32(t[0], t[1]); /* a0 b0 a1 b1 a2 b2 a3 b3 a4 b4 a5 b5 a6 b6 a7 b7 */ out[1] = _mm256_packs_epi32(t[2], t[3]); /* a0 b0 a1 b1 a2 b2 a3 b3 a4 b4 a5 b5 a6 b6 a7 b7 */ _mm256_store_si256((__m256i*)(d+0), out[0]); _mm256_store_si256((__m256i*)(d+16), out[1]); d += 32; } for(; n < n_samples; n++) { __m128 in[4]; __m128 int_scale = _mm_set1_ps(S16_SCALE); __m128 int_max = _mm_set1_ps(S16_MAX); __m128 int_min = _mm_set1_ps(S16_MIN); in[0] = _mm_mul_ss(_mm_load_ss(&s0[n]), int_scale); in[1] = _mm_mul_ss(_mm_load_ss(&s1[n]), int_scale); in[0] = _MM_CLAMP_SS(in[0], int_min, int_max); in[1] = _MM_CLAMP_SS(in[1], int_min, int_max); d[0] = _mm_cvtss_si32(in[0]); d[1] = _mm_cvtss_si32(in[1]); d += 2; } } void conv_f32d_to_s16s_2_avx2(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], uint32_t n_samples) { const float *s0 = src[0], *s1 = src[1]; uint16_t *d = dst[0]; uint32_t n, unrolled; __m256 in[4]; __m256i out[4], t[4]; __m256 int_scale = _mm256_set1_ps(S16_SCALE); if (SPA_IS_ALIGNED(s0, 32) && SPA_IS_ALIGNED(s1, 32)) unrolled = n_samples & ~15; else unrolled = 0; for(n = 0; n < unrolled; n += 16) { in[0] = _mm256_mul_ps(_mm256_load_ps(&s0[n+0]), int_scale); in[1] = _mm256_mul_ps(_mm256_load_ps(&s1[n+0]), int_scale); in[2] = _mm256_mul_ps(_mm256_load_ps(&s0[n+8]), int_scale); in[3] = _mm256_mul_ps(_mm256_load_ps(&s1[n+8]), int_scale); out[0] = _mm256_cvtps_epi32(in[0]); /* a0 a1 a2 a3 a4 a5 a6 a7 */ out[1] = _mm256_cvtps_epi32(in[1]); /* b0 b1 b2 b3 b4 b5 b6 b7 */ out[2] = _mm256_cvtps_epi32(in[2]); /* a0 a1 a2 a3 a4 a5 a6 a7 */ out[3] = _mm256_cvtps_epi32(in[3]); /* b0 b1 b2 b3 b4 b5 b6 b7 */ t[0] = _mm256_unpacklo_epi32(out[0], out[1]); /* a0 b0 a1 b1 a4 b4 a5 b5 */ t[1] = _mm256_unpackhi_epi32(out[0], out[1]); /* a2 b2 a3 b3 a6 b6 a7 b7 */ t[2] = _mm256_unpacklo_epi32(out[2], out[3]); /* a0 b0 a1 b1 a4 b4 a5 b5 */ t[3] = _mm256_unpackhi_epi32(out[2], out[3]); /* a2 b2 a3 b3 a6 b6 a7 b7 */ out[0] = _mm256_packs_epi32(t[0], t[1]); /* a0 b0 a1 b1 a2 b2 a3 b3 a4 b4 a5 b5 a6 b6 a7 b7 */ out[1] = _mm256_packs_epi32(t[2], t[3]); /* a0 b0 a1 b1 a2 b2 a3 b3 a4 b4 a5 b5 a6 b6 a7 b7 */ out[0] = _MM256_BSWAP_EPI16(out[0]); out[1] = _MM256_BSWAP_EPI16(out[1]); _mm256_store_si256((__m256i*)(d+0), out[0]); _mm256_store_si256((__m256i*)(d+16), out[1]); d += 32; } for(; n < n_samples; n++) { __m128 in[4]; __m128 int_scale = _mm_set1_ps(S16_SCALE); __m128 int_max = _mm_set1_ps(S16_MAX); __m128 int_min = _mm_set1_ps(S16_MIN); in[0] = _mm_mul_ss(_mm_load_ss(&s0[n]), int_scale); in[1] = _mm_mul_ss(_mm_load_ss(&s1[n]), int_scale); in[0] = _MM_CLAMP_SS(in[0], int_min, int_max); in[1] = _MM_CLAMP_SS(in[1], int_min, int_max); d[0] = bswap_16((uint16_t)_mm_cvtss_si32(in[0])); d[1] = bswap_16((uint16_t)_mm_cvtss_si32(in[1])); d += 2; } } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/audioconvert/fmt-ops-c.c000066400000000000000000000372151511204443500274250ustar00rootroot00000000000000/* Spa */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #include #include #include "fmt-ops.h" #include "law.h" #define MAKE_COPY(size) \ void conv_copy ##size## d_c(struct convert *conv, \ void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], \ uint32_t n_samples) \ { \ uint32_t i, n_channels = conv->n_channels; \ for (i = 0; i < n_channels; i++) \ spa_memcpy(dst[i], src[i], n_samples * (size>>3)); \ } \ void conv_copy ##size## _c(struct convert *conv, \ void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], \ uint32_t n_samples) \ { \ spa_memcpy(dst[0], src[0], n_samples * conv->n_channels * (size>>3)); \ } MAKE_COPY(8); MAKE_COPY(16); MAKE_COPY(24); MAKE_COPY(32); MAKE_COPY(64); #define MAKE_D_TO_D(sname,stype,dname,dtype,func) \ void conv_ ##sname## d_to_ ##dname## d_c(struct convert *conv, \ void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], \ uint32_t n_samples) \ { \ uint32_t i, j, n_channels = conv->n_channels; \ for (i = 0; i < n_channels; i++) { \ const stype *s = src[i]; \ dtype *d = dst[i]; \ for (j = 0; j < n_samples; j++) \ d[j] = func (s[j]); \ } \ } #define MAKE_I_TO_I(sname,stype,dname,dtype,func) \ void conv_ ##sname## _to_ ##dname## _c(struct convert *conv, \ void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], \ uint32_t n_samples) \ { \ uint32_t j; \ const stype *s = src[0]; \ dtype *d = dst[0]; \ n_samples *= conv->n_channels; \ for (j = 0; j < n_samples; j++) \ d[j] = func (s[j]); \ } #define MAKE_I_TO_D(sname,stype,dname,dtype,func) \ void conv_ ##sname## _to_ ##dname## d_c(struct convert *conv, \ void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], \ uint32_t n_samples) \ { \ const stype *s = src[0]; \ dtype **d = (dtype**)dst; \ uint32_t i, j, n_channels = conv->n_channels; \ for (j = 0; j < n_samples; j++) { \ for (i = 0; i < n_channels; i++) \ d[i][j] = func (*s++); \ } \ } #define MAKE_D_TO_I(sname,stype,dname,dtype,func) \ void conv_ ##sname## d_to_ ##dname## _c(struct convert *conv, \ void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], \ uint32_t n_samples) \ { \ const stype **s = (const stype **)src; \ dtype *d = dst[0]; \ uint32_t i, j, n_channels = conv->n_channels; \ for (j = 0; j < n_samples; j++) { \ for (i = 0; i < n_channels; i++) \ *d++ = func (s[i][j]); \ } \ } /* to f32 */ MAKE_D_TO_D(u8, uint8_t, f32, float, U8_TO_F32); MAKE_I_TO_I(u8, uint8_t, f32, float, U8_TO_F32); MAKE_I_TO_D(u8, uint8_t, f32, float, U8_TO_F32); MAKE_D_TO_I(u8, uint8_t, f32, float, U8_TO_F32); MAKE_D_TO_D(s8, int8_t, f32, float, S8_TO_F32); MAKE_I_TO_I(s8, int8_t, f32, float, S8_TO_F32); MAKE_I_TO_D(s8, int8_t, f32, float, S8_TO_F32); MAKE_D_TO_I(s8, int8_t, f32, float, S8_TO_F32); MAKE_I_TO_D(alaw, uint8_t, f32, float, alaw_to_f32); MAKE_I_TO_D(ulaw, uint8_t, f32, float, ulaw_to_f32); MAKE_I_TO_I(u16, uint16_t, f32, float, U16_TO_F32); MAKE_I_TO_D(u16, uint16_t, f32, float, U16_TO_F32); MAKE_D_TO_D(s16, int16_t, f32, float, S16_TO_F32); MAKE_I_TO_I(s16, int16_t, f32, float, S16_TO_F32); MAKE_I_TO_D(s16, int16_t, f32, float, S16_TO_F32); MAKE_D_TO_I(s16, int16_t, f32, float, S16_TO_F32); MAKE_I_TO_D(s16s, uint16_t, f32, float, S16S_TO_F32); MAKE_I_TO_I(u32, uint32_t, f32, float, U32_TO_F32); MAKE_I_TO_D(u32, uint32_t, f32, float, U32_TO_F32); MAKE_D_TO_D(s32, int32_t, f32, float, S32_TO_F32); MAKE_I_TO_I(s32, int32_t, f32, float, S32_TO_F32); MAKE_I_TO_D(s32, int32_t, f32, float, S32_TO_F32); MAKE_D_TO_I(s32, int32_t, f32, float, S32_TO_F32); MAKE_I_TO_D(s32s, uint32_t, f32, float, S32S_TO_F32); MAKE_I_TO_I(u24, uint24_t, f32, float, U24_TO_F32); MAKE_I_TO_D(u24, uint24_t, f32, float, U24_TO_F32); MAKE_D_TO_D(s24, int24_t, f32, float, S24_TO_F32); MAKE_I_TO_I(s24, int24_t, f32, float, S24_TO_F32); MAKE_I_TO_D(s24, int24_t, f32, float, S24_TO_F32); MAKE_D_TO_I(s24, int24_t, f32, float, S24_TO_F32); MAKE_I_TO_D(s24s, int24_t, f32, float, S24S_TO_F32); MAKE_I_TO_I(u24_32, uint32_t, f32, float, U24_32_TO_F32); MAKE_I_TO_D(u24_32, uint32_t, f32, float, U24_32_TO_F32); MAKE_D_TO_D(s24_32, int32_t, f32, float, S24_32_TO_F32); MAKE_I_TO_I(s24_32, int32_t, f32, float, S24_32_TO_F32); MAKE_I_TO_D(s24_32, int32_t, f32, float, S24_32_TO_F32); MAKE_D_TO_I(s24_32, int32_t, f32, float, S24_32_TO_F32); MAKE_I_TO_D(s24_32s, uint32_t, f32, float, S24_32S_TO_F32); MAKE_D_TO_D(f64, double, f32, float, (float)); MAKE_I_TO_I(f64, double, f32, float, (float)); MAKE_I_TO_D(f64, double, f32, float, (float)); MAKE_D_TO_I(f64, double, f32, float, (float)); MAKE_I_TO_D(f64s, uint64_t, f32, float, (float)F64S_TO_F64); /* from f32 */ MAKE_D_TO_D(f32, float, u8, uint8_t, F32_TO_U8); MAKE_I_TO_I(f32, float, u8, uint8_t, F32_TO_U8); MAKE_I_TO_D(f32, float, u8, uint8_t, F32_TO_U8); MAKE_D_TO_I(f32, float, u8, uint8_t, F32_TO_U8); MAKE_D_TO_D(f32, float, s8, int8_t, F32_TO_S8); MAKE_I_TO_I(f32, float, s8, int8_t, F32_TO_S8); MAKE_I_TO_D(f32, float, s8, int8_t, F32_TO_S8); MAKE_D_TO_I(f32, float, s8, int8_t, F32_TO_S8); MAKE_D_TO_I(f32, float, alaw, uint8_t, f32_to_alaw); MAKE_D_TO_I(f32, float, ulaw, uint8_t, f32_to_ulaw); MAKE_I_TO_I(f32, float, u16, uint16_t, F32_TO_U16); MAKE_D_TO_I(f32, float, u16, uint16_t, F32_TO_U16); MAKE_D_TO_D(f32, float, s16, int16_t, F32_TO_S16); MAKE_I_TO_I(f32, float, s16, int16_t, F32_TO_S16); MAKE_I_TO_D(f32, float, s16, int16_t, F32_TO_S16); MAKE_D_TO_I(f32, float, s16, int16_t, F32_TO_S16); MAKE_D_TO_I(f32, float, s16s, uint16_t, F32_TO_S16S); MAKE_I_TO_I(f32, float, u32, uint32_t, F32_TO_U32); MAKE_D_TO_I(f32, float, u32, uint32_t, F32_TO_U32); MAKE_D_TO_D(f32, float, s32, int32_t, F32_TO_S32); MAKE_I_TO_I(f32, float, s32, int32_t, F32_TO_S32); MAKE_I_TO_D(f32, float, s32, int32_t, F32_TO_S32); MAKE_D_TO_I(f32, float, s32, int32_t, F32_TO_S32); MAKE_D_TO_I(f32, float, s32s, uint32_t, F32_TO_S32S); MAKE_I_TO_I(f32, float, u24, uint24_t, F32_TO_U24); MAKE_D_TO_I(f32, float, u24, uint24_t, F32_TO_U24); MAKE_D_TO_D(f32, float, s24, int24_t, F32_TO_S24); MAKE_I_TO_I(f32, float, s24, int24_t, F32_TO_S24); MAKE_I_TO_D(f32, float, s24, int24_t, F32_TO_S24); MAKE_D_TO_I(f32, float, s24, int24_t, F32_TO_S24); MAKE_D_TO_I(f32, float, s24s, int24_t, F32_TO_S24S); MAKE_I_TO_I(f32, float, u24_32, uint32_t, F32_TO_U24_32); MAKE_D_TO_I(f32, float, u24_32, uint32_t, F32_TO_U24_32); MAKE_D_TO_D(f32, float, s24_32, int32_t, F32_TO_S24_32); MAKE_I_TO_I(f32, float, s24_32, int32_t, F32_TO_S24_32); MAKE_I_TO_D(f32, float, s24_32, int32_t, F32_TO_S24_32); MAKE_D_TO_I(f32, float, s24_32, int32_t, F32_TO_S24_32); MAKE_D_TO_I(f32, float, s24_32s, uint32_t, F32_TO_S24_32S); MAKE_D_TO_D(f32, float, f64, double, (double)); MAKE_I_TO_I(f32, float, f64, double, (double)); MAKE_I_TO_D(f32, float, f64, double, (double)); MAKE_D_TO_I(f32, float, f64, double, (double)); MAKE_D_TO_I(f32, float, f64s, uint64_t, F64_TO_F64S); static inline int32_t lcnoise(uint32_t *state) { *state = (*state * 96314165) + 907633515; return (int32_t)(*state); } void conv_noise_none_c(struct convert *conv, float *noise, uint32_t n_samples) { memset(noise, 0, n_samples * sizeof(float)); } void conv_noise_rect_c(struct convert *conv, float *noise, uint32_t n_samples) { uint32_t n; uint32_t *state = &conv->random[0]; const float scale = conv->scale; for (n = 0; n < n_samples; n++) noise[n] = lcnoise(state) * scale; } void conv_noise_tri_c(struct convert *conv, float *noise, uint32_t n_samples) { uint32_t n; const float scale = conv->scale; uint32_t *state = &conv->random[0]; for (n = 0; n < n_samples; n++) noise[n] = (lcnoise(state) - lcnoise(state)) * scale; } void conv_noise_tri_hf_c(struct convert *conv, float *noise, uint32_t n_samples) { uint32_t n; const float scale = conv->scale; uint32_t *state = &conv->random[0]; int32_t *prev = &conv->prev[0], old, new; old = *prev; for (n = 0; n < n_samples; n++) { new = lcnoise(state); noise[n] = (new - old) * scale; old = new; } *prev = old; } void conv_noise_pattern_c(struct convert *conv, float *noise, uint32_t n_samples) { uint32_t n; const float scale = conv->scale; int32_t *prev = &conv->prev[0], old; old = *prev; for (n = 0; n < n_samples; n++) noise[n] = scale * (1-((old++>>10)&1)); *prev = old; } #define MAKE_D_noise(dname,dtype,func) \ void conv_f32d_to_ ##dname## d_noise_c(struct convert *conv, \ void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], \ uint32_t n_samples) \ { \ uint32_t i, j, k, chunk, n_channels = conv->n_channels, noise_size = conv->noise_size; \ float *noise = conv->noise; \ convert_update_noise(conv, noise, SPA_MIN(n_samples, noise_size)); \ for (i = 0; i < n_channels; i++) { \ const float *s = src[i]; \ dtype *d = dst[i]; \ for (j = 0; j < n_samples;) { \ chunk = SPA_MIN(n_samples - j, noise_size); \ for (k = 0; k < chunk; k++, j++) \ d[j] = func (s[j], noise[k]); \ } \ } \ } #define MAKE_I_noise(dname,dtype,func) \ void conv_f32d_to_ ##dname## _noise_c(struct convert *conv, \ void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], \ uint32_t n_samples) \ { \ const float **s = (const float **) src; \ dtype *d = dst[0]; \ uint32_t i, j, k, chunk, n_channels = conv->n_channels, noise_size = conv->noise_size; \ float *noise = conv->noise; \ convert_update_noise(conv, noise, SPA_MIN(n_samples, noise_size)); \ for (j = 0; j < n_samples;) { \ chunk = SPA_MIN(n_samples - j, noise_size); \ for (k = 0; k < chunk; k++, j++) { \ for (i = 0; i < n_channels; i++) \ *d++ = func (s[i][j], noise[k]); \ } \ } \ } MAKE_D_noise(u8, uint8_t, F32_TO_U8_D); MAKE_I_noise(u8, uint8_t, F32_TO_U8_D); MAKE_D_noise(s8, int8_t, F32_TO_S8_D); MAKE_I_noise(s8, int8_t, F32_TO_S8_D); MAKE_D_noise(s16, int16_t, F32_TO_S16_D); MAKE_I_noise(s16, int16_t, F32_TO_S16_D); MAKE_I_noise(s16s, uint16_t, F32_TO_S16S_D); MAKE_D_noise(s32, int32_t, F32_TO_S32_D); MAKE_I_noise(s32, int32_t, F32_TO_S32_D); MAKE_I_noise(s32s, uint32_t, F32_TO_S32S_D); MAKE_D_noise(s24, int24_t, F32_TO_S24_D); MAKE_I_noise(s24, int24_t, F32_TO_S24_D); MAKE_I_noise(s24s, int24_t, F32_TO_S24_D); MAKE_D_noise(s24_32, int32_t, F32_TO_S24_32_D); MAKE_I_noise(s24_32, int32_t, F32_TO_S24_32_D); MAKE_I_noise(s24_32s, int32_t, F32_TO_S24_32S_D); #define SHAPER(type,s,scale,offs,sh,min,max,d) \ ({ \ type t; \ float v = s * scale + offs; \ for (n = 0; n < n_ns; n++) \ v += sh->e[idx + n] * ns[n]; \ t = FTOI(type, v, 1.0f, 0.0f, d, min, max); \ idx = (idx - 1) & NS_MASK; \ sh->e[idx] = sh->e[idx + NS_MAX] = v - t; \ t; \ }) #define F32_TO_U8_SH(s,sh,d) SHAPER(uint8_t, s, U8_SCALE, U8_OFFS, sh, U8_MIN, U8_MAX, d) #define F32_TO_S8_SH(s,sh,d) SHAPER(int8_t, s, S8_SCALE, 0, sh, S8_MIN, S8_MAX, d) #define F32_TO_S16_SH(s,sh,d) SHAPER(int16_t, s, S16_SCALE, 0, sh, S16_MIN, S16_MAX, d) #define F32_TO_S16S_SH(s,sh,d) bswap_16(F32_TO_S16_SH(s,sh,d)) #define MAKE_D_shaped(dname,dtype,func) \ void conv_f32d_to_ ##dname## d_shaped_c(struct convert *conv, \ void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], \ uint32_t n_samples) \ { \ uint32_t i, j, k, chunk, n_channels = conv->n_channels, noise_size = conv->noise_size; \ float *noise = conv->noise; \ const float *ns = conv->ns; \ uint32_t n, n_ns = conv->n_ns; \ convert_update_noise(conv, noise, SPA_MIN(n_samples, noise_size)); \ for (i = 0; i < n_channels; i++) { \ const float *s = src[i]; \ dtype *d = dst[i]; \ struct shaper *sh = &conv->shaper[i]; \ uint32_t idx = sh->idx; \ for (j = 0; j < n_samples;) { \ chunk = SPA_MIN(n_samples - j, noise_size); \ for (k = 0; k < chunk; k++, j++) \ d[j] = func (s[j], sh, noise[k]); \ } \ sh->idx = idx; \ } \ } #define MAKE_I_shaped(dname,dtype,func) \ void conv_f32d_to_ ##dname## _shaped_c(struct convert *conv, \ void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], \ uint32_t n_samples) \ { \ dtype *d0 = dst[0]; \ uint32_t i, j, k, chunk, n_channels = conv->n_channels, noise_size = conv->noise_size; \ float *noise = conv->noise; \ const float *ns = conv->ns; \ uint32_t n, n_ns = conv->n_ns; \ convert_update_noise(conv, noise, SPA_MIN(n_samples, noise_size)); \ for (i = 0; i < n_channels; i++) { \ const float *s = src[i]; \ dtype *d = &d0[i]; \ struct shaper *sh = &conv->shaper[i]; \ uint32_t idx = sh->idx; \ for (j = 0; j < n_samples;) { \ chunk = SPA_MIN(n_samples - j, noise_size); \ for (k = 0; k < chunk; k++, j++) \ d[j*n_channels] = func (s[j], sh, noise[k]); \ } \ sh->idx = idx; \ } \ } MAKE_D_shaped(u8, uint8_t, F32_TO_U8_SH); MAKE_I_shaped(u8, uint8_t, F32_TO_U8_SH); MAKE_D_shaped(s8, int8_t, F32_TO_S8_SH); MAKE_I_shaped(s8, int8_t, F32_TO_S8_SH); MAKE_D_shaped(s16, int16_t, F32_TO_S16_SH); MAKE_I_shaped(s16, int16_t, F32_TO_S16_SH); MAKE_I_shaped(s16s, uint16_t, F32_TO_S16S_SH); #define MAKE_DEINTERLEAVE(size1,size2, type,func) \ MAKE_I_TO_D(size1,type,size2,type,func) MAKE_DEINTERLEAVE(8, 8, uint8_t, (uint8_t)); MAKE_DEINTERLEAVE(16, 16, uint16_t, (uint16_t)); MAKE_DEINTERLEAVE(24, 24, uint24_t, (uint24_t)); MAKE_DEINTERLEAVE(32, 32, uint32_t, (uint32_t)); MAKE_DEINTERLEAVE(32s, 32, uint32_t, bswap_32); MAKE_DEINTERLEAVE(64, 64, uint64_t, (uint64_t)); #define MAKE_INTERLEAVE(size1,size2,type,func) \ MAKE_D_TO_I(size1,type,size2,type,func) MAKE_INTERLEAVE(8, 8, uint8_t, (uint8_t)); MAKE_INTERLEAVE(16, 16, uint16_t, (uint16_t)); MAKE_INTERLEAVE(24, 24, uint24_t, (uint24_t)); MAKE_INTERLEAVE(32, 32, uint32_t, (uint32_t)); MAKE_INTERLEAVE(32, 32s, uint32_t, bswap_32); MAKE_INTERLEAVE(64, 64, uint64_t, (uint64_t)); #define MAKE_CLEAR(size) \ void conv_clear_ ##size## d_c(struct convert *conv, \ void * SPA_RESTRICT dst[], uint32_t n_samples) \ { \ uint32_t i, n_channels = conv->n_channels; \ for (i = 0; i < n_channels; i++) \ memset(dst[i], 0, n_samples * (size>>3)); \ } \ void conv_clear_ ##size## _c(struct convert *conv, \ void * SPA_RESTRICT dst[], uint32_t n_samples) \ { \ memset(dst[0], 0, n_samples * conv->n_channels * (size>>3)); \ } MAKE_CLEAR(8); MAKE_CLEAR(16); MAKE_CLEAR(24); MAKE_CLEAR(32); MAKE_CLEAR(64); #define MAKE_CLEAR_VAL(size,dtype,val) \ void conv_clear_ ##size## d_c(struct convert *conv, \ void * SPA_RESTRICT dst[], uint32_t n_samples) \ { \ uint32_t i, j, n_channels = conv->n_channels; \ for (i = 0; i < n_channels; i++) { \ dtype *d = dst[i]; \ for (j = 0; j < n_samples; j++) \ d[j] = val; \ } \ } \ void conv_clear_ ##size## _c(struct convert *conv, \ void * SPA_RESTRICT dst[], uint32_t n_samples) \ { \ uint32_t j; \ dtype *d = dst[0]; \ n_samples *= conv->n_channels; \ for (j = 0; j < n_samples; j++) \ d[j] = val; \ } MAKE_CLEAR_VAL(alaw, uint8_t, 0x55); MAKE_CLEAR_VAL(ulaw, uint8_t, 0xff); MAKE_CLEAR_VAL(u8, uint8_t, 0x80); MAKE_CLEAR_VAL(u16, uint16_t, 0x8000); MAKE_CLEAR_VAL(u24, uint24_t, U32_TO_U24(0x800000)); MAKE_CLEAR_VAL(u24_32, uint32_t, 0x800000); MAKE_CLEAR_VAL(u32, uint32_t, 0x80000000); pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/audioconvert/fmt-ops-neon.c000066400000000000000000000337361511204443500301460ustar00rootroot00000000000000/* Spa */ /* SPDX-FileCopyrightText: Copyright © 2020 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #include "fmt-ops.h" void conv_s16_to_f32d_2_neon(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], uint32_t n_samples) { const int16_t *s = src[0]; float *d0 = dst[0], *d1 = dst[1]; unsigned int remainder = n_samples & 7; n_samples -= remainder; #ifdef __aarch64__ asm volatile( " cmp %[n_samples], #0\n" " beq 2f\n" "1:" " ld2 {v2.8h, v3.8h}, [%[s]], #32\n" " subs %w[n_samples], %w[n_samples], #8\n" " sxtl v0.4s, v2.4h\n" " sxtl2 v1.4s, v2.8h\n" " sxtl v2.4s, v3.4h\n" " sxtl2 v3.4s, v3.8h\n" " scvtf v0.4s, v0.4s, #15\n" " scvtf v1.4s, v1.4s, #15\n" " scvtf v2.4s, v2.4s, #15\n" " scvtf v3.4s, v3.4s, #15\n" " st1 {v0.4s, v1.4s}, [%[d0]], #32\n" " st1 {v2.4s, v3.4s}, [%[d1]], #32\n" " b.ne 1b\n" "2:" " cmp %[remainder], #0\n" " beq 4f\n" "3:" " ld2 { v0.h, v1.h }[0], [%[s]], #4\n" " subs %[remainder], %[remainder], #1\n" " sshll v2.4s, v0.4h, #0\n" " sshll v3.4s, v1.4h, #0\n" " scvtf v0.4s, v2.4s, #15\n" " scvtf v1.4s, v3.4s, #15\n" " st1 { v0.s }[0], [%[d0]], #4\n" " st1 { v1.s }[0], [%[d1]], #4\n" " bne 3b\n" "4:" : [d0] "+r" (d0), [d1] "+r" (d1), [s] "+r" (s), [n_samples] "+r" (n_samples), [remainder] "+r" (remainder) : : "v0", "v1", "v2", "v3", "memory", "cc"); #else asm volatile( " cmp %[n_samples], #0\n" " beq 2f\n" "1:" " vld2.16 {d0-d3}, [%[s]]!\n" " subs %[n_samples], #8\n" " vmovl.s16 q3, d3\n" " vmovl.s16 q2, d2\n" " vmovl.s16 q1, d1\n" " vmovl.s16 q0, d0\n" " vcvt.f32.s32 q3, q3, #15\n" " vcvt.f32.s32 q2, q2, #15\n" " vcvt.f32.s32 q1, q1, #15\n" " vcvt.f32.s32 q0, q0, #15\n" " vst1.32 {d4-d7}, [%[d1]]!\n" " vst1.32 {d0-d3}, [%[d0]]!\n" " bne 1b\n" "2:" " cmp %[remainder], #0\n" " beq 4f\n" "3:" " vld2.16 { d0[0], d1[0] }, [%[s]]!\n" " subs %[remainder], %[remainder], #1\n" " vmovl.s16 q1, d1\n" " vmovl.s16 q0, d0\n" " vcvt.f32.s32 q1, q1, #15\n" " vcvt.f32.s32 q0, q0, #15\n" " vst1.32 { d2[0] }, [%[d1]]!\n" " vst1.32 { d0[0] }, [%[d0]]!\n" " bne 3b\n" "4:" : [d0] "+r" (d0), [d1] "+r" (d1), [s] "+r" (s), [n_samples] "+r" (n_samples), [remainder] "+r" (remainder) : : "q0", "q1", "q2", "q3", "memory", "cc"); #endif } static void conv_s16_to_f32d_2s_neon(void *data, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src, uint32_t n_channels, uint32_t n_samples) { const int16_t *s = src; float *d0 = dst[0], *d1 = dst[1]; uint32_t stride = n_channels << 1; unsigned int remainder = n_samples & 3; n_samples -= remainder; #ifdef __aarch64__ asm volatile( " cmp %[n_samples], #0\n" " beq 2f\n" "1:" " ld2 { v0.h, v1.h }[0], [%[s]], %[stride]\n" " ld2 { v0.h, v1.h }[1], [%[s]], %[stride]\n" " ld2 { v0.h, v1.h }[2], [%[s]], %[stride]\n" " ld2 { v0.h, v1.h }[3], [%[s]], %[stride]\n" " subs %[n_samples], %[n_samples], #4\n" " sshll v2.4s, v0.4h, #0\n" " sshll v3.4s, v1.4h, #0\n" " scvtf v0.4s, v2.4s, #15\n" " scvtf v1.4s, v3.4s, #15\n" " st1 { v0.4s }, [%[d0]], #16\n" " st1 { v1.4s }, [%[d1]], #16\n" " bne 1b\n" "2:" " cmp %[remainder], #0\n" " beq 4f\n" "3:" " ld2 { v0.h, v1.h }[0], [%[s]], %[stride]\n" " subs %[remainder], %[remainder], #1\n" " sshll v2.4s, v0.4h, #0\n" " sshll v3.4s, v1.4h, #0\n" " scvtf v0.4s, v2.4s, #15\n" " scvtf v1.4s, v3.4s, #15\n" " st1 { v0.s }[0], [%[d0]], #4\n" " st1 { v1.s }[0], [%[d1]], #4\n" " bne 3b\n" "4:" : [d0] "+r" (d0), [d1] "+r" (d1), [s] "+r" (s), [n_samples] "+r" (n_samples), [remainder] "+r" (remainder) : [stride] "r" (stride) : "cc", "v0", "v1", "v2", "v3"); #else asm volatile( " cmp %[n_samples], #0\n" " beq 2f\n" "1:" " vld2.16 { d0[0], d1[0] }, [%[s]], %[stride]\n" " vld2.16 { d0[1], d1[1] }, [%[s]], %[stride]\n" " vld2.16 { d0[2], d1[2] }, [%[s]], %[stride]\n" " vld2.16 { d0[3], d1[3] }, [%[s]], %[stride]\n" " subs %[n_samples], %[n_samples], #4\n" " vmovl.s16 q1, d1\n" " vmovl.s16 q0, d0\n" " vcvt.f32.s32 q0, q0, #15\n" " vcvt.f32.s32 q1, q1, #15\n" " vst1.32 { q0 }, [%[d0]]!\n" " vst1.32 { q1 }, [%[d1]]!\n" " bne 1b\n" "2:" " cmp %[remainder], #0\n" " beq 4f\n" "3:" " vld2.16 { d0[0], d1[0] }, [%[s]], %[stride]\n" " subs %[remainder], %[remainder], #1\n" " vmovl.s16 q1, d1\n" " vmovl.s16 q0, d0\n" " vcvt.f32.s32 q0, q0, #15\n" " vcvt.f32.s32 q1, q1, #15\n" " vst1.32 { d0[0] }, [%[d0]]!\n" " vst1.32 { d2[0] }, [%[d1]]!\n" " bne 3b\n" "4:" : [d0] "+r" (d0), [d1] "+r" (d1), [s] "+r" (s), [n_samples] "+r" (n_samples), [remainder] "+r" (remainder) : [stride] "r" (stride) : "cc", "q0", "q1"); #endif } static void conv_s16_to_f32d_1s_neon(void *data, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src, uint32_t n_channels, uint32_t n_samples) { const int16_t *s = src; float *d = dst[0]; uint32_t stride = n_channels << 1; uint32_t remainder = n_samples & 3; n_samples -= remainder; #ifdef __aarch64__ asm volatile( " cmp %[n_samples], #0\n" " beq 2f\n" "1:" " ld1 { v0.h }[0], [%[s]], %[stride]\n" " ld1 { v0.h }[1], [%[s]], %[stride]\n" " ld1 { v0.h }[2], [%[s]], %[stride]\n" " ld1 { v0.h }[3], [%[s]], %[stride]\n" " subs %[n_samples], %[n_samples], #4\n" " sshll v1.4s, v0.4h, #0\n" " scvtf v0.4s, v1.4s, #15\n" " st1 { v0.4s }, [%[d]], #16\n" " bne 1b\n" "2:" " cmp %[remainder], #0\n" " beq 4f\n" "3:" " ld1 { v0.h }[0], [%[s]], %[stride]\n" " subs %[remainder], %[remainder], #1\n" " sshll v1.4s, v0.4h, #0\n" " scvtf v0.4s, v1.4s, #15\n" " st1 { v0.s }[0], [%[d]], #4\n" " bne 3b\n" "4:" : [d] "+r" (d), [s] "+r" (s), [n_samples] "+r" (n_samples), [remainder] "+r" (remainder) : [stride] "r" (stride) : "cc", "v0", "v1"); #else asm volatile( " cmp %[n_samples], #0\n" " beq 2f\n" "1:" " vld1.16 { d0[0] }, [%[s]], %[stride]\n" " vld1.16 { d0[1] }, [%[s]], %[stride]\n" " vld1.16 { d0[2] }, [%[s]], %[stride]\n" " vld1.16 { d0[3] }, [%[s]], %[stride]\n" " subs %[n_samples], %[n_samples], #4\n" " vmovl.s16 q0, d0\n" " vcvt.f32.s32 q0, q0, #15\n" " vst1.32 { q0 }, [%[d]]!\n" " bne 1b\n" "2:" " cmp %[remainder], #0\n" " beq 4f\n" "3:" " vld1.16 { d0[0] }, [%[s]], %[stride]\n" " subs %[remainder], %[remainder], #1\n" " vmovl.s16 q0, d0\n" " vcvt.f32.s32 q0, q0, #15\n" " vst1.32 { d0[0] }, [%[d]]!\n" " bne 3b\n" "4:" : [d] "+r" (d), [s] "+r" (s), [n_samples] "+r" (n_samples), [remainder] "+r" (remainder) : [stride] "r" (stride) : "cc", "q0"); #endif } void conv_s16_to_f32d_neon(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], uint32_t n_samples) { const int16_t *s = src[0]; uint32_t i = 0, n_channels = conv->n_channels; for(; i + 1 < n_channels; i += 2) conv_s16_to_f32d_2s_neon(conv, &dst[i], &s[i], n_channels, n_samples); for(; i < n_channels; i++) conv_s16_to_f32d_1s_neon(conv, &dst[i], &s[i], n_channels, n_samples); } static void conv_f32d_to_s16_2s_neon(void *data, void * SPA_RESTRICT dst, const void * SPA_RESTRICT src[], uint32_t n_channels, uint32_t n_samples) { const float *s0 = src[0], *s1 = src[1]; int16_t *d = dst; uint32_t stride = n_channels << 1; uint32_t remainder = n_samples & 3; n_samples -= remainder; #ifdef __aarch64__ asm volatile( " dup v2.4s, %w[scale]\n" " cmp %[n_samples], #0\n" " beq 2f\n" "1:" " ld1 { v0.4s }, [%[s0]], #16\n" " ld1 { v1.4s }, [%[s1]], #16\n" " subs %[n_samples], %[n_samples], #4\n" " sqadd v0.4s, v0.4s, v2.4s\n" " sqadd v1.4s, v1.4s, v2.4s\n" " fcvtns v0.4s, v0.4s\n" " fcvtns v1.4s, v1.4s\n" " sqxtn v0.4h, v0.4s\n" " sqxtn v1.4h, v1.4s\n" " st2 { v0.h, v1.h }[0], [%[d]], %[stride]\n" " st2 { v0.h, v1.h }[1], [%[d]], %[stride]\n" " st2 { v0.h, v1.h }[2], [%[d]], %[stride]\n" " st2 { v0.h, v1.h }[3], [%[d]], %[stride]\n" " bne 1b\n" "2:" " cmp %[remainder], #0\n" " beq 4f\n" "3:" " ld1 { v0.s }[0], [%[s0]], #4\n" " ld1 { v1.s }[0], [%[s1]], #4\n" " subs %[remainder], %[remainder], #1\n" " sqadd v0.4s, v0.4s, v2.4s\n" " sqadd v1.4s, v1.4s, v2.4s\n" " fcvtns v0.4s, v0.4s\n" " fcvtns v1.4s, v1.4s\n" " sqxtn v0.4h, v0.4s\n" " sqxtn v1.4h, v1.4s\n" " st2 { v0.h, v1.h }[0], [%[d]], %[stride]\n" " bne 3b\n" "4:" : [d] "+r" (d), [s0] "+r" (s0), [s1] "+r" (s1), [n_samples] "+r" (n_samples), [remainder] "+r" (remainder) : [stride] "r" (stride), [scale] "r" (15 << 23) : "cc", "v0", "v1"); #else float32x4_t pos = vdupq_n_f32(0.4999999f / S16_SCALE); float32x4_t neg = vdupq_n_f32(-0.4999999f / S16_SCALE); asm volatile( " veor q2, q2, q2\n" " cmp %[n_samples], #0\n" " beq 2f\n" "1:" " vld1.32 { q0 }, [%[s0]]!\n" " vld1.32 { q1 }, [%[s1]]!\n" " subs %[n_samples], %[n_samples], #4\n" " vcgt.f32 q3, q0, q2\n" " vcgt.f32 q4, q0, q2\n" " vbsl q3, %q[pos], %q[neg]\n" " vbsl q4, %q[pos], %q[neg]\n" " vadd.f32 q0, q0, q3\n" " vadd.f32 q1, q1, q4\n" " vcvt.s32.f32 q0, q0, #15\n" " vcvt.s32.f32 q1, q1, #15\n" " vqmovn.s32 d0, q0\n" " vqmovn.s32 d1, q1\n" " vst2.16 { d0[0], d1[0] }, [%[d]], %[stride]\n" " vst2.16 { d0[1], d1[1] }, [%[d]], %[stride]\n" " vst2.16 { d0[2], d1[2] }, [%[d]], %[stride]\n" " vst2.16 { d0[3], d1[3] }, [%[d]], %[stride]\n" " bne 1b\n" "2:" " cmp %[remainder], #0\n" " beq 4f\n" "3:" " vld1.32 { d0[0] }, [%[s0]]!\n" " vld1.32 { d2[0] }, [%[s1]]!\n" " subs %[remainder], %[remainder], #1\n" " vcgt.f32 q3, q0, q2\n" " vcgt.f32 q4, q0, q2\n" " vbsl q3, %q[pos], %q[neg]\n" " vbsl q4, %q[pos], %q[neg]\n" " vadd.f32 q0, q0, q3\n" " vadd.f32 q1, q1, q4\n" " vcvt.s32.f32 q0, q0, #15\n" " vcvt.s32.f32 q1, q1, #15\n" " vqmovn.s32 d0, q0\n" " vqmovn.s32 d1, q1\n" " vst2.16 { d0[0], d1[0] }, [%[d]], %[stride]\n" " bne 3b\n" "4:" : [d] "+r" (d), [s0] "+r" (s0), [s1] "+r" (s1), [n_samples] "+r" (n_samples), [remainder] "+r" (remainder) : [stride] "r" (stride), [pos]"w"(pos), [neg]"w"(neg) : "cc", "q0", "q1", "q2", "q3", "q4"); #endif } static void conv_f32d_to_s16_1s_neon(void *data, void * SPA_RESTRICT dst, const void * SPA_RESTRICT src[], uint32_t n_channels, uint32_t n_samples) { const float *s = src[0]; int16_t *d = dst; uint32_t stride = n_channels << 1; uint32_t remainder = n_samples & 3; n_samples -= remainder; #ifdef __aarch64__ asm volatile( " dup v2.4s, %w[scale]\n" " cmp %[n_samples], #0\n" " beq 2f\n" "1:" " ld1 { v0.4s }, [%[s]], #16\n" " subs %[n_samples], %[n_samples], #4\n" " sqadd v0.4s, v0.4s, v2.4s\n" " fcvtns v0.4s, v0.4s\n" " sqxtn v0.4h, v0.4s\n" " st1 { v0.h }[0], [%[d]], %[stride]\n" " st1 { v0.h }[1], [%[d]], %[stride]\n" " st1 { v0.h }[2], [%[d]], %[stride]\n" " st1 { v0.h }[3], [%[d]], %[stride]\n" " bne 1b\n" "2:" " cmp %[remainder], #0\n" " beq 4f\n" "3:" " ld1 { v0.s }[0], [%[s]], #4\n" " subs %[remainder], %[remainder], #1\n" " sqadd v0.4s, v0.4s, v2.4s\n" " fcvtns v0.4s, v0.4s\n" " sqxtn v0.4h, v0.4s\n" " st1 { v0.h }[0], [%[d]], %[stride]\n" " bne 3b\n" "4:" : [d] "+r" (d), [s] "+r" (s), [n_samples] "+r" (n_samples), [remainder] "+r" (remainder) : [stride] "r" (stride), [scale] "r" (15 << 23) : "cc", "v0"); #else float32x4_t pos = vdupq_n_f32(0.4999999f / S16_SCALE); float32x4_t neg = vdupq_n_f32(-0.4999999f / S16_SCALE); asm volatile( " veor q1, q1, q1\n" " cmp %[n_samples], #0\n" " beq 2f\n" "1:" " vld1.32 { q0 }, [%[s]]!\n" " subs %[n_samples], %[n_samples], #4\n" " vcgt.f32 q2, q0, q1\n" " vbsl q2, %q[pos], %q[neg]\n" " vadd.f32 q0, q0, q2\n" " vcvt.s32.f32 q0, q0, #15\n" " vqmovn.s32 d0, q0\n" " vst1.16 { d0[0] }, [%[d]], %[stride]\n" " vst1.16 { d0[1] }, [%[d]], %[stride]\n" " vst1.16 { d0[2] }, [%[d]], %[stride]\n" " vst1.16 { d0[3] }, [%[d]], %[stride]\n" " bne 1b\n" "2:" " cmp %[remainder], #0\n" " beq 4f\n" "3:" " vld1.32 { d0[0] }, [%[s]]!\n" " subs %[remainder], %[remainder], #1\n" " vcgt.f32 q2, q0, q1\n" " vbsl q2, %q[pos], %q[neg]\n" " vadd.f32 q0, q0, q2\n" " vcvt.s32.f32 q0, q0, #15\n" " vqmovn.s32 d0, q0\n" " vst1.16 { d0[0] }, [%[d]], %[stride]\n" " bne 3b\n" "4:" : [d] "+r" (d), [s] "+r" (s), [n_samples] "+r" (n_samples), [remainder] "+r" (remainder) : [stride] "r" (stride), [pos]"w"(pos), [neg]"w"(neg) : "cc", "q0", "q1", "q2"); #endif } void conv_f32d_to_s16_neon(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], uint32_t n_samples) { int16_t *d = dst[0]; uint32_t i = 0, n_channels = conv->n_channels; for(; i + 1 < n_channels; i += 2) conv_f32d_to_s16_2s_neon(conv, &d[i], &src[i], n_channels, n_samples); for(; i < n_channels; i++) conv_f32d_to_s16_1s_neon(conv, &d[i], &src[i], n_channels, n_samples); } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/audioconvert/fmt-ops-rvv.c000066400000000000000000000220051511204443500300070ustar00rootroot00000000000000/* Spa */ /* SPDX-FileCopyrightText: Copyright (c) 2023 Institue of Software Chinese Academy of Sciences (ISCAS). */ /* SPDX-License-Identifier: MIT */ #include "fmt-ops.h" #if HAVE_RVV void f32_to_s16(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src, uint32_t n_samples) { asm __volatile__ ( ".option arch, +v \n\t" "li t0, 1191182336 \n\t" "fmv.w.x fa5, t0 \n\t" "1: \n\t" "vsetvli t0, %[n_samples], e32, m8, ta, ma \n\t" "vle32.v v8, (%[src]) \n\t" "sub %[n_samples], %[n_samples], t0 \n\t" "vfmul.vf v8, v8, fa5 \n\t" "vsetvli zero, zero, e16, m4, ta, ma \n\t" "vfncvt.x.f.w v8, v8 \n\t" "slli t0, t0, 1 \n\t" "vse16.v v8, (%[dst]) \n\t" "add %[src], %[src], t0 \n\t" "add %[dst], %[dst], t0 \n\t" "add %[src], %[src], t0 \n\t" "bnez %[n_samples], 1b \n\t" : [n_samples] "+r" (n_samples), [src] "+r" (src), [dst] "+r" (dst) : : "cc", "memory" ); } void conv_f32_to_s16_rvv(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], uint32_t n_samples) { if (n_samples * conv->n_channels <= 4) { conv_f32_to_s16_c(conv, dst, src, n_samples); return; } f32_to_s16(conv, *dst, *src, n_samples * conv -> n_channels); } void conv_f32d_to_s16d_rvv(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], uint32_t n_samples) { if (n_samples <= 4) { conv_f32d_to_s16d_c(conv, dst, src, n_samples); return; } uint32_t i = 0, n_channels = conv->n_channels; for(i = 0; i < n_channels; i++) { f32_to_s16(conv, dst[i], src[i], n_samples); } } static void f32d_to_s16(void *data, void * SPA_RESTRICT dst, const void * SPA_RESTRICT src[], uint32_t n_channels, uint32_t n_samples) { const float *s = src[0]; uint32_t stride = n_channels << 1; asm __volatile__ ( ".option arch, +v \n\t" "li t0, 1191182336 \n\t" "fmv.w.x fa5, t0 \n\t" "1: \n\t" "vsetvli t0, %[n_samples], e32, m8, ta, ma \n\t" "vle32.v v8, (%[s]) \n\t" "sub %[n_samples], %[n_samples], t0 \n\t" "vfmul.vf v8, v8, fa5 \n\t" "vsetvli zero, zero, e16, m4, ta, ma \n\t" "vfncvt.x.f.w v8, v8 \n\t" "slli t2, t0, 2 \n\t" "mul t3, t0, %[stride] \n\t" "vsse16.v v8, (%[dst]), %[stride] \n\t" "add %[s], %[s], t2 \n\t" "add %[dst], %[dst], t3 \n\t" "bnez %[n_samples], 1b \n\t" : [n_samples] "+r" (n_samples), [s] "+r" (s), [dst] "+r" (dst) : [stride] "r" (stride) : "cc", "memory" ); } void conv_f32d_to_s16_rvv(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], uint32_t n_samples) { if (n_samples <= 4) { conv_f32d_to_s16_c(conv, dst, src, n_samples); return; } int16_t *d = dst[0]; uint32_t i = 0, n_channels = conv->n_channels; for(i = 0; i < n_channels; i++) f32d_to_s16(conv, &d[i], &src[i], n_channels, n_samples); } static void s16_to_f32d(void *data, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src, uint32_t n_channels, uint32_t n_samples) { float *d = dst[0]; uint32_t stride = n_channels << 1; asm __volatile__ ( ".option arch, +v \n\t" "li t0, 939524096 \n\t" "fmv.w.x fa5, t0 \n\t" "1: \n\t" "vsetvli t0, %[n_samples], e16, m4, ta, ma \n\t" "vlse16.v v8, (%[src]), %[stride] \n\t" "sub %[n_samples], %[n_samples], t0 \n\t" "vfwcvt.f.x.v v16, v8 \n\t" "vsetvli zero, zero, e32, m8, ta, ma \n\t" "mul t4, t0, %[stride] \n\t" "vfmul.vf v8, v16, fa5 \n\t" "slli t3, t0, 2 \n\t" "vse32.v v8, (%[d]) \n\t" "add %[src], %[src], t4 \n\t" "add %[d], %[d], t3 \n\t" "bnez %[n_samples], 1b \n\t" : [n_samples] "+r" (n_samples), [src] "+r" (src), [d] "+r" (d) : [stride] "r" (stride) : "cc", "memory" ); } void conv_s16_to_f32d_rvv(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], uint32_t n_samples) { if (n_samples <= 4) { conv_s16_to_f32d_c(conv, dst, src, n_samples); return; } const int16_t *s = src[0]; uint32_t i = 0, n_channels = conv->n_channels; for(i = 0; i < n_channels; i++) s16_to_f32d(conv, &dst[i], &s[i], n_channels, n_samples); } static void s32_to_f32d(void *data, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src, uint32_t n_channels, uint32_t n_samples) { float *d = dst[0]; uint32_t stride = n_channels << 2; asm __volatile__ ( ".option arch, +v \n\t" "li t0, 805306368 \n\t" "fmv.w.x fa5, t0 \n\t" "1: \n\t" "vsetvli t0, %[n_samples], e32, m8, ta, ma \n\t" "vlse32.v v8, (%[src]), %[stride] \n\t" "sub %[n_samples], %[n_samples], t0 \n\t" "vfcvt.f.x.v v8, v8 \n\t" "mul t4, t0, %[stride] \n\t" "vfmul.vf v8, v8, fa5 \n\t" "slli t3, t0, 2 \n\t" "vse32.v v8, (%[d]) \n\t" "add %[src], %[src], t4 \n\t" "add %[d], %[d], t3 \n\t" "bnez %[n_samples], 1b \n\t" : [n_samples] "+r" (n_samples), [src] "+r" (src), [d] "+r" (d) : [stride] "r" (stride) : "cc", "memory" ); } void conv_s32_to_f32d_rvv(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], uint32_t n_samples) { if (n_samples <= 4) { conv_s32_to_f32d_c(conv, dst, src, n_samples); return; } const int32_t *s = src[0]; uint32_t i = 0, n_channels = conv->n_channels; for(i = 0; i < n_channels; i++) s32_to_f32d(conv, &dst[i], &s[i], n_channels, n_samples); return; } static void f32d_to_s32(void *data, void * SPA_RESTRICT dst, const void * SPA_RESTRICT src[], uint32_t n_channels, uint32_t n_samples) { const float *s = src[0]; uint32_t stride = n_channels << 2; asm __volatile__ ( ".option arch, +v \n\t" "li t0, 1325400064 \n\t" "li t2, 1325400063 \n\t" "fmv.w.x fa5, t0 \n\t" "fmv.w.x fa4, t2 \n\t" "1: \n\t" "vsetvli t0, %[n_samples], e32, m8, ta, ma \n\t" "vle32.v v8, (%[s]) \n\t" "sub %[n_samples], %[n_samples], t0 \n\t" "vfmul.vf v8, v8, fa5 \n\t" "vfmin.vf v8, v8, fa4 \n\t" "vfcvt.x.f.v v8, v8 \n\t" "slli t2, t0, 2 \n\t" "mul t3, t0, %[stride] \n\t" "vsse32.v v8, (%[dst]), %[stride] \n\t" "add %[s], %[s], t2 \n\t" "add %[dst], %[dst], t3 \n\t" "bnez %[n_samples], 1b \n\t" : [n_samples] "+r" (n_samples), [s] "+r" (s), [dst] "+r" (dst) : [stride] "r" (stride) : "cc", "memory" ); } void conv_f32d_to_s32_rvv(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], uint32_t n_samples) { if (n_samples <= 4) { conv_f32d_to_s32_c(conv, dst, src, n_samples); return; } int32_t *d = dst[0]; uint32_t i = 0, n_channels = conv->n_channels; for(i = 0; i < n_channels; i++) f32d_to_s32(conv, &d[i], &src[i], n_channels, n_samples); } #endif pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/audioconvert/fmt-ops-sse2.c000066400000000000000000001356271511204443500300650ustar00rootroot00000000000000/* Spa */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include "fmt-ops.h" #include #define _MM_CLAMP_PS(r,min,max) \ _mm_min_ps(_mm_max_ps(r, min), max) #define _MM_CLAMP_SS(r,min,max) \ _mm_min_ss(_mm_max_ss(r, min), max) #define _MM_BSWAP_EPI16(x) \ ({ \ _mm_or_si128( \ _mm_slli_epi16(x, 8), \ _mm_srli_epi16(x, 8)); \ }) #define _MM_BSWAP_EPI32(x) \ ({ \ __m128i a = _MM_BSWAP_EPI16(x); \ a = _mm_shufflelo_epi16(a, _MM_SHUFFLE(2, 3, 0, 1)); \ a = _mm_shufflehi_epi16(a, _MM_SHUFFLE(2, 3, 0, 1)); \ }) static void conv_s16_to_f32d_1s_sse2(void *data, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src, uint32_t n_channels, uint32_t n_samples) { const int16_t *s = src; float *d0 = dst[0]; uint32_t n, unrolled; __m128i in = _mm_setzero_si128(); __m128 out, factor = _mm_set1_ps(1.0f / S16_SCALE); if (SPA_LIKELY(SPA_IS_ALIGNED(d0, 16))) unrolled = n_samples & ~3; else unrolled = 0; for(n = 0; n < unrolled; n += 4) { in = _mm_insert_epi16(in, s[0*n_channels], 1); in = _mm_insert_epi16(in, s[1*n_channels], 3); in = _mm_insert_epi16(in, s[2*n_channels], 5); in = _mm_insert_epi16(in, s[3*n_channels], 7); in = _mm_srai_epi32(in, 16); out = _mm_cvtepi32_ps(in); out = _mm_mul_ps(out, factor); _mm_store_ps(&d0[n], out); s += 4*n_channels; } for(; n < n_samples; n++) { out = _mm_cvtsi32_ss(factor, s[0]); out = _mm_mul_ss(out, factor); _mm_store_ss(&d0[n], out); s += n_channels; } } void conv_s16_to_f32d_sse2(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], uint32_t n_samples) { const int16_t *s = src[0]; uint32_t i = 0, n_channels = conv->n_channels; for(; i < n_channels; i++) conv_s16_to_f32d_1s_sse2(conv, &dst[i], &s[i], n_channels, n_samples); } static void conv_s16s_to_f32d_1s_sse2(void *data, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src, uint32_t n_channels, uint32_t n_samples) { const uint16_t *s = src; float *d0 = dst[0]; uint32_t n, unrolled; __m128i in = _mm_setzero_si128(); __m128 out, factor = _mm_set1_ps(1.0f / S16_SCALE); if (SPA_LIKELY(SPA_IS_ALIGNED(d0, 16))) unrolled = n_samples & ~3; else unrolled = 0; for(n = 0; n < unrolled; n += 4) { in = _mm_insert_epi16(in, s[0*n_channels], 1); in = _mm_insert_epi16(in, s[1*n_channels], 3); in = _mm_insert_epi16(in, s[2*n_channels], 5); in = _mm_insert_epi16(in, s[3*n_channels], 7); in = _MM_BSWAP_EPI16(in); in = _mm_srai_epi32(in, 16); out = _mm_cvtepi32_ps(in); out = _mm_mul_ps(out, factor); _mm_store_ps(&d0[n], out); s += 4*n_channels; } for(; n < n_samples; n++) { out = _mm_cvtsi32_ss(factor, (int16_t)bswap_16(s[0])); out = _mm_mul_ss(out, factor); _mm_store_ss(&d0[n], out); s += n_channels; } } void conv_s16s_to_f32d_sse2(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], uint32_t n_samples) { const uint16_t *s = src[0]; uint32_t i = 0, n_channels = conv->n_channels; for(; i < n_channels; i++) conv_s16s_to_f32d_1s_sse2(conv, &dst[i], &s[i], n_channels, n_samples); } void conv_s16_to_f32d_2_sse2(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], uint32_t n_samples) { const int16_t *s = src[0]; float *d0 = dst[0], *d1 = dst[1]; uint32_t n, unrolled; __m128i in[2], t[4]; __m128 out[4], factor = _mm_set1_ps(1.0f / S16_SCALE); if (SPA_IS_ALIGNED(s, 16) && SPA_IS_ALIGNED(d0, 16) && SPA_IS_ALIGNED(d1, 16)) unrolled = n_samples & ~7; else unrolled = 0; for(n = 0; n < unrolled; n += 8) { in[0] = _mm_load_si128((__m128i*)(s + 0)); in[1] = _mm_load_si128((__m128i*)(s + 8)); t[0] = _mm_slli_epi32(in[0], 16); t[0] = _mm_srai_epi32(t[0], 16); out[0] = _mm_cvtepi32_ps(t[0]); out[0] = _mm_mul_ps(out[0], factor); t[1] = _mm_srai_epi32(in[0], 16); out[1] = _mm_cvtepi32_ps(t[1]); out[1] = _mm_mul_ps(out[1], factor); t[2] = _mm_slli_epi32(in[1], 16); t[2] = _mm_srai_epi32(t[2], 16); out[2] = _mm_cvtepi32_ps(t[2]); out[2] = _mm_mul_ps(out[2], factor); t[3] = _mm_srai_epi32(in[1], 16); out[3] = _mm_cvtepi32_ps(t[3]); out[3] = _mm_mul_ps(out[3], factor); _mm_store_ps(&d0[n + 0], out[0]); _mm_store_ps(&d1[n + 0], out[1]); _mm_store_ps(&d0[n + 4], out[2]); _mm_store_ps(&d1[n + 4], out[3]); s += 16; } for(; n < n_samples; n++) { out[0] = _mm_cvtsi32_ss(factor, s[0]); out[0] = _mm_mul_ss(out[0], factor); out[1] = _mm_cvtsi32_ss(factor, s[1]); out[1] = _mm_mul_ss(out[1], factor); _mm_store_ss(&d0[n], out[0]); _mm_store_ss(&d1[n], out[1]); s += 2; } } void conv_s16s_to_f32d_2_sse2(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], uint32_t n_samples) { const uint16_t *s = src[0]; float *d0 = dst[0], *d1 = dst[1]; uint32_t n, unrolled; __m128i in[2], t[4]; __m128 out[4], factor = _mm_set1_ps(1.0f / S16_SCALE); if (SPA_IS_ALIGNED(s, 16) && SPA_IS_ALIGNED(d0, 16) && SPA_IS_ALIGNED(d1, 16)) unrolled = n_samples & ~7; else unrolled = 0; for(n = 0; n < unrolled; n += 8) { in[0] = _mm_load_si128((__m128i*)(s + 0)); in[1] = _mm_load_si128((__m128i*)(s + 8)); in[0] = _MM_BSWAP_EPI16(in[0]); in[1] = _MM_BSWAP_EPI16(in[1]); t[0] = _mm_slli_epi32(in[0], 16); t[0] = _mm_srai_epi32(t[0], 16); out[0] = _mm_cvtepi32_ps(t[0]); out[0] = _mm_mul_ps(out[0], factor); t[1] = _mm_srai_epi32(in[0], 16); out[1] = _mm_cvtepi32_ps(t[1]); out[1] = _mm_mul_ps(out[1], factor); t[2] = _mm_slli_epi32(in[1], 16); t[2] = _mm_srai_epi32(t[2], 16); out[2] = _mm_cvtepi32_ps(t[2]); out[2] = _mm_mul_ps(out[2], factor); t[3] = _mm_srai_epi32(in[1], 16); out[3] = _mm_cvtepi32_ps(t[3]); out[3] = _mm_mul_ps(out[3], factor); _mm_store_ps(&d0[n + 0], out[0]); _mm_store_ps(&d1[n + 0], out[1]); _mm_store_ps(&d0[n + 4], out[2]); _mm_store_ps(&d1[n + 4], out[3]); s += 16; } for(; n < n_samples; n++) { out[0] = _mm_cvtsi32_ss(factor, (int16_t)bswap_16(s[0])); out[0] = _mm_mul_ss(out[0], factor); out[1] = _mm_cvtsi32_ss(factor, (int16_t)bswap_16(s[1])); out[1] = _mm_mul_ss(out[1], factor); _mm_store_ss(&d0[n], out[0]); _mm_store_ss(&d1[n], out[1]); s += 2; } } #define spa_read_unaligned(ptr, type) \ __extension__ ({ \ __typeof__(type) _val; \ memcpy(&_val, (ptr), sizeof(_val)); \ _val; \ }) #define spa_write_unaligned(ptr, type, val) \ __extension__ ({ \ __typeof__(type) _val = (val); \ memcpy((ptr), &_val, sizeof(_val)); \ }) void conv_s24_to_f32d_1s_sse2(void *data, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src, uint32_t n_channels, uint32_t n_samples) { const int24_t *s = src; float *d0 = dst[0]; uint32_t n, unrolled; __m128i in; __m128 out, factor = _mm_set1_ps(1.0f / S24_SCALE); if (SPA_IS_ALIGNED(d0, 16) && n_samples > 0) { unrolled = n_samples & ~3; if ((n_samples & 3) == 0) unrolled -= 4; } else unrolled = 0; for(n = 0; n < unrolled; n += 4) { in = _mm_setr_epi32( spa_read_unaligned(&s[0 * n_channels], uint32_t), spa_read_unaligned(&s[1 * n_channels], uint32_t), spa_read_unaligned(&s[2 * n_channels], uint32_t), spa_read_unaligned(&s[3 * n_channels], uint32_t)); in = _mm_slli_epi32(in, 8); in = _mm_srai_epi32(in, 8); out = _mm_cvtepi32_ps(in); out = _mm_mul_ps(out, factor); _mm_store_ps(&d0[n], out); s += 4 * n_channels; } for(; n < n_samples; n++) { out = _mm_cvtsi32_ss(factor, s24_to_s32(*s)); out = _mm_mul_ss(out, factor); _mm_store_ss(&d0[n], out); s += n_channels; } } static void conv_s24_to_f32d_2s_sse2(void *data, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src, uint32_t n_channels, uint32_t n_samples) { const int24_t *s = src; float *d0 = dst[0], *d1 = dst[1]; uint32_t n, unrolled; __m128i in[2]; __m128 out[2], factor = _mm_set1_ps(1.0f / S24_SCALE); if (SPA_IS_ALIGNED(d0, 16) && SPA_IS_ALIGNED(d1, 16) && n_samples > 0) { unrolled = n_samples & ~3; if ((n_samples & 3) == 0) unrolled -= 4; } else unrolled = 0; for(n = 0; n < unrolled; n += 4) { in[0] = _mm_setr_epi32( spa_read_unaligned(&s[0 + 0*n_channels], uint32_t), spa_read_unaligned(&s[0 + 1*n_channels], uint32_t), spa_read_unaligned(&s[0 + 2*n_channels], uint32_t), spa_read_unaligned(&s[0 + 3*n_channels], uint32_t)); in[1] = _mm_setr_epi32( spa_read_unaligned(&s[1 + 0*n_channels], uint32_t), spa_read_unaligned(&s[1 + 1*n_channels], uint32_t), spa_read_unaligned(&s[1 + 2*n_channels], uint32_t), spa_read_unaligned(&s[1 + 3*n_channels], uint32_t)); in[0] = _mm_slli_epi32(in[0], 8); in[1] = _mm_slli_epi32(in[1], 8); in[0] = _mm_srai_epi32(in[0], 8); in[1] = _mm_srai_epi32(in[1], 8); out[0] = _mm_cvtepi32_ps(in[0]); out[1] = _mm_cvtepi32_ps(in[1]); out[0] = _mm_mul_ps(out[0], factor); out[1] = _mm_mul_ps(out[1], factor); _mm_store_ps(&d0[n], out[0]); _mm_store_ps(&d1[n], out[1]); s += 4 * n_channels; } for(; n < n_samples; n++) { out[0] = _mm_cvtsi32_ss(factor, s24_to_s32(*s)); out[1] = _mm_cvtsi32_ss(factor, s24_to_s32(*(s+1))); out[0] = _mm_mul_ss(out[0], factor); out[1] = _mm_mul_ss(out[1], factor); _mm_store_ss(&d0[n], out[0]); _mm_store_ss(&d1[n], out[1]); s += n_channels; } } static void conv_s24_to_f32d_4s_sse2(void *data, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src, uint32_t n_channels, uint32_t n_samples) { const int24_t *s = src; float *d0 = dst[0], *d1 = dst[1], *d2 = dst[2], *d3 = dst[3]; uint32_t n, unrolled; __m128i in[4]; __m128 out[4], factor = _mm_set1_ps(1.0f / S24_SCALE); if (SPA_IS_ALIGNED(d0, 16) && SPA_IS_ALIGNED(d1, 16) && SPA_IS_ALIGNED(d2, 16) && SPA_IS_ALIGNED(d3, 16) && n_samples > 0) { unrolled = n_samples & ~3; if ((n_samples & 3) == 0) unrolled -= 4; } else unrolled = 0; for(n = 0; n < unrolled; n += 4) { in[0] = _mm_setr_epi32( spa_read_unaligned(&s[0 + 0*n_channels], uint32_t), spa_read_unaligned(&s[0 + 1*n_channels], uint32_t), spa_read_unaligned(&s[0 + 2*n_channels], uint32_t), spa_read_unaligned(&s[0 + 3*n_channels], uint32_t)); in[1] = _mm_setr_epi32( spa_read_unaligned(&s[1 + 0*n_channels], uint32_t), spa_read_unaligned(&s[1 + 1*n_channels], uint32_t), spa_read_unaligned(&s[1 + 2*n_channels], uint32_t), spa_read_unaligned(&s[1 + 3*n_channels], uint32_t)); in[2] = _mm_setr_epi32( spa_read_unaligned(&s[2 + 0*n_channels], uint32_t), spa_read_unaligned(&s[2 + 1*n_channels], uint32_t), spa_read_unaligned(&s[2 + 2*n_channels], uint32_t), spa_read_unaligned(&s[2 + 3*n_channels], uint32_t)); in[3] = _mm_setr_epi32( spa_read_unaligned(&s[3 + 0*n_channels], uint32_t), spa_read_unaligned(&s[3 + 1*n_channels], uint32_t), spa_read_unaligned(&s[3 + 2*n_channels], uint32_t), spa_read_unaligned(&s[3 + 3*n_channels], uint32_t)); in[0] = _mm_slli_epi32(in[0], 8); in[1] = _mm_slli_epi32(in[1], 8); in[2] = _mm_slli_epi32(in[2], 8); in[3] = _mm_slli_epi32(in[3], 8); in[0] = _mm_srai_epi32(in[0], 8); in[1] = _mm_srai_epi32(in[1], 8); in[2] = _mm_srai_epi32(in[2], 8); in[3] = _mm_srai_epi32(in[3], 8); out[0] = _mm_cvtepi32_ps(in[0]); out[1] = _mm_cvtepi32_ps(in[1]); out[2] = _mm_cvtepi32_ps(in[2]); out[3] = _mm_cvtepi32_ps(in[3]); out[0] = _mm_mul_ps(out[0], factor); out[1] = _mm_mul_ps(out[1], factor); out[2] = _mm_mul_ps(out[2], factor); out[3] = _mm_mul_ps(out[3], factor); _mm_store_ps(&d0[n], out[0]); _mm_store_ps(&d1[n], out[1]); _mm_store_ps(&d2[n], out[2]); _mm_store_ps(&d3[n], out[3]); s += 4 * n_channels; } for(; n < n_samples; n++) { out[0] = _mm_cvtsi32_ss(factor, s24_to_s32(*s)); out[1] = _mm_cvtsi32_ss(factor, s24_to_s32(*(s+1))); out[2] = _mm_cvtsi32_ss(factor, s24_to_s32(*(s+2))); out[3] = _mm_cvtsi32_ss(factor, s24_to_s32(*(s+3))); out[0] = _mm_mul_ss(out[0], factor); out[1] = _mm_mul_ss(out[1], factor); out[2] = _mm_mul_ss(out[2], factor); out[3] = _mm_mul_ss(out[3], factor); _mm_store_ss(&d0[n], out[0]); _mm_store_ss(&d1[n], out[1]); _mm_store_ss(&d2[n], out[2]); _mm_store_ss(&d3[n], out[3]); s += n_channels; } } void conv_s24_to_f32d_sse2(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], uint32_t n_samples) { const int8_t *s = src[0]; uint32_t i = 0, n_channels = conv->n_channels; for(; i + 3 < n_channels; i += 4) conv_s24_to_f32d_4s_sse2(conv, &dst[i], &s[3*i], n_channels, n_samples); for(; i + 1 < n_channels; i += 2) conv_s24_to_f32d_2s_sse2(conv, &dst[i], &s[3*i], n_channels, n_samples); for(; i < n_channels; i++) conv_s24_to_f32d_1s_sse2(conv, &dst[i], &s[3*i], n_channels, n_samples); } static void conv_s32_to_f32d_1s_sse2(void *data, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src, uint32_t n_channels, uint32_t n_samples) { const int32_t *s = src; float *d0 = dst[0]; uint32_t n, unrolled; __m128i in; __m128 out, factor = _mm_set1_ps(1.0f / S32_SCALE_I2F); if (SPA_IS_ALIGNED(d0, 16)) unrolled = n_samples & ~3; else unrolled = 0; for(n = 0; n < unrolled; n += 4) { in = _mm_setr_epi32(s[0*n_channels], s[1*n_channels], s[2*n_channels], s[3*n_channels]); out = _mm_cvtepi32_ps(in); out = _mm_mul_ps(out, factor); _mm_store_ps(&d0[n], out); s += 4*n_channels; } for(; n < n_samples; n++) { out = _mm_cvtsi32_ss(factor, s[0]); out = _mm_mul_ss(out, factor); _mm_store_ss(&d0[n], out); s += n_channels; } } void conv_s32_to_f32d_sse2(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], uint32_t n_samples) { const int32_t *s = src[0]; uint32_t i = 0, n_channels = conv->n_channels; for(; i < n_channels; i++) conv_s32_to_f32d_1s_sse2(conv, &dst[i], &s[i], n_channels, n_samples); } static void conv_f32d_to_s32_1s_sse2(void *data, void * SPA_RESTRICT dst, const void * SPA_RESTRICT src[], uint32_t n_channels, uint32_t n_samples) { const float *s0 = src[0]; int32_t *d = dst; uint32_t n, unrolled; __m128 in[1]; __m128i out[4]; __m128 scale = _mm_set1_ps(S32_SCALE_F2I); __m128 int_min = _mm_set1_ps(S32_MIN_F2I); __m128 int_max = _mm_set1_ps(S32_MAX_F2I); if (SPA_IS_ALIGNED(s0, 16)) unrolled = n_samples & ~3; else unrolled = 0; for(n = 0; n < unrolled; n += 4) { in[0] = _mm_mul_ps(_mm_load_ps(&s0[n]), scale); in[0] = _MM_CLAMP_PS(in[0], int_min, int_max); out[0] = _mm_cvtps_epi32(in[0]); out[1] = _mm_shuffle_epi32(out[0], _MM_SHUFFLE(0, 3, 2, 1)); out[2] = _mm_shuffle_epi32(out[0], _MM_SHUFFLE(1, 0, 3, 2)); out[3] = _mm_shuffle_epi32(out[0], _MM_SHUFFLE(2, 1, 0, 3)); d[0*n_channels] = _mm_cvtsi128_si32(out[0]); d[1*n_channels] = _mm_cvtsi128_si32(out[1]); d[2*n_channels] = _mm_cvtsi128_si32(out[2]); d[3*n_channels] = _mm_cvtsi128_si32(out[3]); d += 4*n_channels; } for(; n < n_samples; n++) { in[0] = _mm_load_ss(&s0[n]); in[0] = _mm_mul_ss(in[0], scale); in[0] = _MM_CLAMP_SS(in[0], int_min, int_max); *d = _mm_cvtss_si32(in[0]); d += n_channels; } } static void conv_f32d_to_s32_2s_sse2(void *data, void * SPA_RESTRICT dst, const void * SPA_RESTRICT src[], uint32_t n_channels, uint32_t n_samples) { const float *s0 = src[0], *s1 = src[1]; int32_t *d = dst; uint32_t n, unrolled; __m128 in[2]; __m128i out[2], t[2]; __m128 scale = _mm_set1_ps(S32_SCALE_F2I); __m128 int_min = _mm_set1_ps(S32_MIN_F2I); __m128 int_max = _mm_set1_ps(S32_MAX_F2I); if (SPA_IS_ALIGNED(s0, 16) && SPA_IS_ALIGNED(s1, 16)) unrolled = n_samples & ~3; else unrolled = 0; for(n = 0; n < unrolled; n += 4) { in[0] = _mm_mul_ps(_mm_load_ps(&s0[n]), scale); in[1] = _mm_mul_ps(_mm_load_ps(&s1[n]), scale); in[0] = _MM_CLAMP_PS(in[0], int_min, int_max); in[1] = _MM_CLAMP_PS(in[1], int_min, int_max); out[0] = _mm_cvtps_epi32(in[0]); out[1] = _mm_cvtps_epi32(in[1]); t[0] = _mm_unpacklo_epi32(out[0], out[1]); t[1] = _mm_unpackhi_epi32(out[0], out[1]); _mm_storel_pi((__m64*)(d + 0*n_channels), (__m128)t[0]); _mm_storeh_pi((__m64*)(d + 1*n_channels), (__m128)t[0]); _mm_storel_pi((__m64*)(d + 2*n_channels), (__m128)t[1]); _mm_storeh_pi((__m64*)(d + 3*n_channels), (__m128)t[1]); d += 4*n_channels; } for(; n < n_samples; n++) { in[0] = _mm_load_ss(&s0[n]); in[1] = _mm_load_ss(&s1[n]); in[0] = _mm_unpacklo_ps(in[0], in[1]); in[0] = _mm_mul_ps(in[0], scale); in[0] = _MM_CLAMP_PS(in[0], int_min, int_max); out[0] = _mm_cvtps_epi32(in[0]); _mm_storel_epi64((__m128i*)d, out[0]); d += n_channels; } } static void conv_f32d_to_s32_4s_sse2(void *data, void * SPA_RESTRICT dst, const void * SPA_RESTRICT src[], uint32_t n_channels, uint32_t n_samples) { const float *s0 = src[0], *s1 = src[1], *s2 = src[2], *s3 = src[3]; int32_t *d = dst; uint32_t n, unrolled; __m128 in[4]; __m128i out[4]; __m128 scale = _mm_set1_ps(S32_SCALE_F2I); __m128 int_min = _mm_set1_ps(S32_MIN_F2I); __m128 int_max = _mm_set1_ps(S32_MAX_F2I); if (SPA_IS_ALIGNED(s0, 16) && SPA_IS_ALIGNED(s1, 16) && SPA_IS_ALIGNED(s2, 16) && SPA_IS_ALIGNED(s3, 16)) unrolled = n_samples & ~3; else unrolled = 0; for(n = 0; n < unrolled; n += 4) { in[0] = _mm_mul_ps(_mm_load_ps(&s0[n]), scale); in[1] = _mm_mul_ps(_mm_load_ps(&s1[n]), scale); in[2] = _mm_mul_ps(_mm_load_ps(&s2[n]), scale); in[3] = _mm_mul_ps(_mm_load_ps(&s3[n]), scale); in[0] = _MM_CLAMP_PS(in[0], int_min, int_max); in[1] = _MM_CLAMP_PS(in[1], int_min, int_max); in[2] = _MM_CLAMP_PS(in[2], int_min, int_max); in[3] = _MM_CLAMP_PS(in[3], int_min, int_max); _MM_TRANSPOSE4_PS(in[0], in[1], in[2], in[3]); out[0] = _mm_cvtps_epi32(in[0]); out[1] = _mm_cvtps_epi32(in[1]); out[2] = _mm_cvtps_epi32(in[2]); out[3] = _mm_cvtps_epi32(in[3]); _mm_storeu_si128((__m128i*)(d + 0*n_channels), out[0]); _mm_storeu_si128((__m128i*)(d + 1*n_channels), out[1]); _mm_storeu_si128((__m128i*)(d + 2*n_channels), out[2]); _mm_storeu_si128((__m128i*)(d + 3*n_channels), out[3]); d += 4*n_channels; } for(; n < n_samples; n++) { in[0] = _mm_load_ss(&s0[n]); in[1] = _mm_load_ss(&s1[n]); in[2] = _mm_load_ss(&s2[n]); in[3] = _mm_load_ss(&s3[n]); in[0] = _mm_unpacklo_ps(in[0], in[2]); in[1] = _mm_unpacklo_ps(in[1], in[3]); in[0] = _mm_unpacklo_ps(in[0], in[1]); in[0] = _mm_mul_ps(in[0], scale); in[0] = _MM_CLAMP_PS(in[0], int_min, int_max); out[0] = _mm_cvtps_epi32(in[0]); _mm_storeu_si128((__m128i*)d, out[0]); d += n_channels; } } void conv_f32d_to_s32_sse2(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], uint32_t n_samples) { int32_t *d = dst[0]; uint32_t i = 0, n_channels = conv->n_channels; for(; i + 3 < n_channels; i += 4) conv_f32d_to_s32_4s_sse2(conv, &d[i], &src[i], n_channels, n_samples); for(; i + 1 < n_channels; i += 2) conv_f32d_to_s32_2s_sse2(conv, &d[i], &src[i], n_channels, n_samples); for(; i < n_channels; i++) conv_f32d_to_s32_1s_sse2(conv, &d[i], &src[i], n_channels, n_samples); } /* 32 bit xorshift PRNG, see https://en.wikipedia.org/wiki/Xorshift */ #define _MM_XORSHIFT_EPI32(r) \ ({ \ __m128i i, t; \ i = _mm_load_si128((__m128i*)r); \ t = _mm_slli_epi32(i, 13); \ i = _mm_xor_si128(i, t); \ t = _mm_srli_epi32(i, 17); \ i = _mm_xor_si128(i, t); \ t = _mm_slli_epi32(i, 5); \ i = _mm_xor_si128(i, t); \ _mm_store_si128((__m128i*)r, i); \ i; \ }) void conv_noise_rect_sse2(struct convert *conv, float *noise, uint32_t n_samples) { uint32_t n; const uint32_t *r = conv->random; __m128 scale = _mm_set1_ps(conv->scale); __m128i in[1]; __m128 out[1]; for (n = 0; n < n_samples; n += 4) { in[0] = _MM_XORSHIFT_EPI32(r); out[0] = _mm_cvtepi32_ps(in[0]); out[0] = _mm_mul_ps(out[0], scale); _mm_store_ps(&noise[n], out[0]); } } void conv_noise_tri_sse2(struct convert *conv, float *noise, uint32_t n_samples) { uint32_t n; const uint32_t *r = conv->random; __m128 scale = _mm_set1_ps(conv->scale); __m128i in[1]; __m128 out[1]; for (n = 0; n < n_samples; n += 4) { in[0] = _mm_sub_epi32( _MM_XORSHIFT_EPI32(r), _MM_XORSHIFT_EPI32(r)); out[0] = _mm_cvtepi32_ps(in[0]); out[0] = _mm_mul_ps(out[0], scale); _mm_store_ps(&noise[n], out[0]); } } void conv_noise_tri_hf_sse2(struct convert *conv, float *noise, uint32_t n_samples) { uint32_t n; int32_t *p = conv->prev; const uint32_t *r = conv->random; __m128 scale = _mm_set1_ps(conv->scale); __m128i in[1], old[1], new[1]; __m128 out[1]; old[0] = _mm_load_si128((__m128i*)p); for (n = 0; n < n_samples; n += 4) { new[0] = _MM_XORSHIFT_EPI32(r); in[0] = _mm_sub_epi32(old[0], new[0]); old[0] = new[0]; out[0] = _mm_cvtepi32_ps(in[0]); out[0] = _mm_mul_ps(out[0], scale); _mm_store_ps(&noise[n], out[0]); } _mm_store_si128((__m128i*)p, old[0]); } // FIXME: this function is not covered with tests. static void conv_f32d_to_s32_1s_noise_sse2(struct convert *conv, void * SPA_RESTRICT dst, const void * SPA_RESTRICT src, float *noise, uint32_t n_channels, uint32_t n_samples) { const float *s = src; int32_t *d = dst; uint32_t n, unrolled; __m128 in[1]; __m128i out[4]; __m128 scale = _mm_set1_ps(S32_SCALE_F2I); __m128 int_min = _mm_set1_ps(S32_MIN_F2I); __m128 int_max = _mm_set1_ps(S32_MAX_F2I); if (SPA_IS_ALIGNED(s, 16)) unrolled = n_samples & ~3; else unrolled = 0; for(n = 0; n < unrolled; n += 4) { in[0] = _mm_mul_ps(_mm_load_ps(&s[n]), scale); in[0] = _mm_add_ps(in[0], _mm_load_ps(&noise[n])); in[0] = _MM_CLAMP_PS(in[0], int_min, int_max); out[0] = _mm_cvtps_epi32(in[0]); out[1] = _mm_shuffle_epi32(out[0], _MM_SHUFFLE(0, 3, 2, 1)); out[2] = _mm_shuffle_epi32(out[0], _MM_SHUFFLE(1, 0, 3, 2)); out[3] = _mm_shuffle_epi32(out[0], _MM_SHUFFLE(2, 1, 0, 3)); d[0*n_channels] = _mm_cvtsi128_si32(out[0]); d[1*n_channels] = _mm_cvtsi128_si32(out[1]); d[2*n_channels] = _mm_cvtsi128_si32(out[2]); d[3*n_channels] = _mm_cvtsi128_si32(out[3]); d += 4*n_channels; } for(; n < n_samples; n++) { in[0] = _mm_load_ss(&s[n]); in[0] = _mm_mul_ss(in[0], scale); in[0] = _mm_add_ss(in[0], _mm_load_ss(&noise[n])); in[0] = _MM_CLAMP_SS(in[0], int_min, int_max); *d = _mm_cvtss_si32(in[0]); d += n_channels; } } void conv_f32d_to_s32_noise_sse2(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], uint32_t n_samples) { int32_t *d = dst[0]; uint32_t i, k, chunk, n_channels = conv->n_channels; float *noise = conv->noise; convert_update_noise(conv, noise, SPA_MIN(n_samples, conv->noise_size)); for(i = 0; i < n_channels; i++) { const float *s = src[i]; for(k = 0; k < n_samples; k += chunk) { chunk = SPA_MIN(n_samples - k, conv->noise_size); conv_f32d_to_s32_1s_noise_sse2(conv, &d[i + k*n_channels], &s[k], noise, n_channels, chunk); } } } static void conv_interleave_32_1s_sse2(void *data, void * SPA_RESTRICT dst, const void * SPA_RESTRICT src[], uint32_t n_channels, uint32_t n_samples) { const int32_t *s0 = src[0]; int32_t *d = dst; uint32_t n, unrolled; __m128i out[4]; if (SPA_IS_ALIGNED(s0, 16)) unrolled = n_samples & ~3; else unrolled = 0; for(n = 0; n < unrolled; n += 4) { out[0] = _mm_load_si128((__m128i*)&s0[n]); out[1] = _mm_shuffle_epi32(out[0], _MM_SHUFFLE(0, 3, 2, 1)); out[2] = _mm_shuffle_epi32(out[0], _MM_SHUFFLE(1, 0, 3, 2)); out[3] = _mm_shuffle_epi32(out[0], _MM_SHUFFLE(2, 1, 0, 3)); d[0*n_channels] = _mm_cvtsi128_si32(out[0]); d[1*n_channels] = _mm_cvtsi128_si32(out[1]); d[2*n_channels] = _mm_cvtsi128_si32(out[2]); d[3*n_channels] = _mm_cvtsi128_si32(out[3]); d += 4*n_channels; } for(; n < n_samples; n++) { *d = s0[n]; d += n_channels; } } static void conv_interleave_32_4s_sse2(void *data, void * SPA_RESTRICT dst, const void * SPA_RESTRICT src[], uint32_t n_channels, uint32_t n_samples) { const float *s0 = src[0], *s1 = src[1], *s2 = src[2], *s3 = src[3]; float *d = dst; uint32_t n, unrolled; __m128 out[4]; if (SPA_IS_ALIGNED(s0, 16) && SPA_IS_ALIGNED(s1, 16) && SPA_IS_ALIGNED(s2, 16) && SPA_IS_ALIGNED(s3, 16)) unrolled = n_samples & ~3; else unrolled = 0; for(n = 0; n < unrolled; n += 4) { out[0] = _mm_load_ps(&s0[n]); out[1] = _mm_load_ps(&s1[n]); out[2] = _mm_load_ps(&s2[n]); out[3] = _mm_load_ps(&s3[n]); _MM_TRANSPOSE4_PS(out[0], out[1], out[2], out[3]); _mm_storeu_ps((d + 0*n_channels), out[0]); _mm_storeu_ps((d + 1*n_channels), out[1]); _mm_storeu_ps((d + 2*n_channels), out[2]); _mm_storeu_ps((d + 3*n_channels), out[3]); d += 4*n_channels; } for(; n < n_samples; n++) { out[0] = _mm_setr_ps(s0[n], s1[n], s2[n], s3[n]); _mm_storeu_ps(d, out[0]); d += n_channels; } } void conv_32d_to_32_sse2(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], uint32_t n_samples) { int32_t *d = dst[0]; uint32_t i = 0, n_channels = conv->n_channels; for(; i + 3 < n_channels; i += 4) conv_interleave_32_4s_sse2(conv, &d[i], &src[i], n_channels, n_samples); for(; i < n_channels; i++) conv_interleave_32_1s_sse2(conv, &d[i], &src[i], n_channels, n_samples); } static void conv_interleave_32s_1s_sse2(void *data, void * SPA_RESTRICT dst, const void * SPA_RESTRICT src[], uint32_t n_channels, uint32_t n_samples) { const int32_t *s0 = src[0]; int32_t *d = dst; uint32_t n, unrolled; __m128i out[4]; if (SPA_IS_ALIGNED(s0, 16)) unrolled = n_samples & ~3; else unrolled = 0; for(n = 0; n < unrolled; n += 4) { out[0] = _mm_load_si128((__m128i*)&s0[n]); out[0] = _MM_BSWAP_EPI32(out[0]); out[1] = _mm_shuffle_epi32(out[0], _MM_SHUFFLE(0, 3, 2, 1)); out[2] = _mm_shuffle_epi32(out[0], _MM_SHUFFLE(1, 0, 3, 2)); out[3] = _mm_shuffle_epi32(out[0], _MM_SHUFFLE(2, 1, 0, 3)); d[0*n_channels] = _mm_cvtsi128_si32(out[0]); d[1*n_channels] = _mm_cvtsi128_si32(out[1]); d[2*n_channels] = _mm_cvtsi128_si32(out[2]); d[3*n_channels] = _mm_cvtsi128_si32(out[3]); d += 4*n_channels; } for(; n < n_samples; n++) { *d = bswap_32(s0[n]); d += n_channels; } } static void conv_interleave_32s_4s_sse2(void *data, void * SPA_RESTRICT dst, const void * SPA_RESTRICT src[], uint32_t n_channels, uint32_t n_samples) { const float *s0 = src[0], *s1 = src[1], *s2 = src[2], *s3 = src[3]; float *d = dst; uint32_t n, unrolled; __m128 out[4]; if (SPA_IS_ALIGNED(s0, 16) && SPA_IS_ALIGNED(s1, 16) && SPA_IS_ALIGNED(s2, 16) && SPA_IS_ALIGNED(s3, 16)) unrolled = n_samples & ~3; else unrolled = 0; for(n = 0; n < unrolled; n += 4) { out[0] = _mm_load_ps(&s0[n]); out[1] = _mm_load_ps(&s1[n]); out[2] = _mm_load_ps(&s2[n]); out[3] = _mm_load_ps(&s3[n]); _MM_TRANSPOSE4_PS(out[0], out[1], out[2], out[3]); out[0] = (__m128) _MM_BSWAP_EPI32((__m128i)out[0]); out[1] = (__m128) _MM_BSWAP_EPI32((__m128i)out[1]); out[2] = (__m128) _MM_BSWAP_EPI32((__m128i)out[2]); out[3] = (__m128) _MM_BSWAP_EPI32((__m128i)out[3]); _mm_storeu_ps(&d[0*n_channels], out[0]); _mm_storeu_ps(&d[1*n_channels], out[1]); _mm_storeu_ps(&d[2*n_channels], out[2]); _mm_storeu_ps(&d[3*n_channels], out[3]); d += 4*n_channels; } for(; n < n_samples; n++) { out[0] = _mm_setr_ps(s0[n], s1[n], s2[n], s3[n]); out[0] = (__m128) _MM_BSWAP_EPI32((__m128i)out[0]); _mm_storeu_ps(d, out[0]); d += n_channels; } } void conv_32d_to_32s_sse2(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], uint32_t n_samples) { int32_t *d = dst[0]; uint32_t i = 0, n_channels = conv->n_channels; for(; i + 3 < n_channels; i += 4) conv_interleave_32s_4s_sse2(conv, &d[i], &src[i], n_channels, n_samples); for(; i < n_channels; i++) conv_interleave_32s_1s_sse2(conv, &d[i], &src[i], n_channels, n_samples); } static void conv_deinterleave_32_1s_sse2(void *data, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src, uint32_t n_channels, uint32_t n_samples) { const float *s = src; float *d0 = dst[0]; uint32_t n, unrolled; __m128 out; if (SPA_IS_ALIGNED(d0, 16)) unrolled = n_samples & ~3; else unrolled = 0; for(n = 0; n < unrolled; n += 4) { out = _mm_setr_ps(s[0*n_channels], s[1*n_channels], s[2*n_channels], s[3*n_channels]); _mm_store_ps(&d0[n], out); s += 4*n_channels; } for(; n < n_samples; n++) { d0[n] = *s; s += n_channels; } } static void conv_deinterleave_32_4s_sse2(void *data, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src, uint32_t n_channels, uint32_t n_samples) { const float *s = src; float *d0 = dst[0], *d1 = dst[1], *d2 = dst[2], *d3 = dst[3]; uint32_t n, unrolled; __m128 out[4]; if (SPA_IS_ALIGNED(d0, 16) && SPA_IS_ALIGNED(d1, 16) && SPA_IS_ALIGNED(d2, 16) && SPA_IS_ALIGNED(d3, 16)) unrolled = n_samples & ~3; else unrolled = 0; for(n = 0; n < unrolled; n += 4) { out[0] = _mm_loadu_ps(&s[0 * n_channels]); out[1] = _mm_loadu_ps(&s[1 * n_channels]); out[2] = _mm_loadu_ps(&s[2 * n_channels]); out[3] = _mm_loadu_ps(&s[3 * n_channels]); _MM_TRANSPOSE4_PS(out[0], out[1], out[2], out[3]); _mm_store_ps(&d0[n], out[0]); _mm_store_ps(&d1[n], out[1]); _mm_store_ps(&d2[n], out[2]); _mm_store_ps(&d3[n], out[3]); s += 4 * n_channels; } for(; n < n_samples; n++) { d0[n] = s[0]; d1[n] = s[1]; d2[n] = s[2]; d3[n] = s[3]; s += n_channels; } } void conv_32_to_32d_sse2(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], uint32_t n_samples) { const float *s = src[0]; uint32_t i = 0, n_channels = conv->n_channels; for(; i + 3 < n_channels; i += 4) conv_deinterleave_32_4s_sse2(conv, &dst[i], &s[i], n_channels, n_samples); for(; i < n_channels; i++) conv_deinterleave_32_1s_sse2(conv, &dst[i], &s[i], n_channels, n_samples); } static void conv_deinterleave_32s_1s_sse2(void *data, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src, uint32_t n_channels, uint32_t n_samples) { const float *s = src; float *d0 = dst[0]; uint32_t n, unrolled; __m128 out; if (SPA_IS_ALIGNED(d0, 16)) unrolled = n_samples & ~3; else unrolled = 0; for(n = 0; n < unrolled; n += 4) { out = _mm_setr_ps(s[0*n_channels], s[1*n_channels], s[2*n_channels], s[3*n_channels]); out = (__m128) _MM_BSWAP_EPI32((__m128i)out); _mm_store_ps(&d0[n], out); s += 4*n_channels; } for(; n < n_samples; n++) { uint32_t *di = (uint32_t*)&d0[n], *si = (uint32_t*)s; *di = bswap_32(*si); s += n_channels; } } static void conv_deinterleave_32s_4s_sse2(void *data, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src, uint32_t n_channels, uint32_t n_samples) { const float *s = src; float *d0 = dst[0], *d1 = dst[1], *d2 = dst[2], *d3 = dst[3]; uint32_t n, unrolled; __m128 out[4]; if (SPA_IS_ALIGNED(d0, 16) && SPA_IS_ALIGNED(d1, 16) && SPA_IS_ALIGNED(d2, 16) && SPA_IS_ALIGNED(d3, 16)) unrolled = n_samples & ~3; else unrolled = 0; for(n = 0; n < unrolled; n += 4) { out[0] = _mm_loadu_ps(&s[0 * n_channels]); out[1] = _mm_loadu_ps(&s[1 * n_channels]); out[2] = _mm_loadu_ps(&s[2 * n_channels]); out[3] = _mm_loadu_ps(&s[3 * n_channels]); _MM_TRANSPOSE4_PS(out[0], out[1], out[2], out[3]); out[0] = (__m128) _MM_BSWAP_EPI32((__m128i)out[0]); out[1] = (__m128) _MM_BSWAP_EPI32((__m128i)out[1]); out[2] = (__m128) _MM_BSWAP_EPI32((__m128i)out[2]); out[3] = (__m128) _MM_BSWAP_EPI32((__m128i)out[3]); _mm_store_ps(&d0[n], out[0]); _mm_store_ps(&d1[n], out[1]); _mm_store_ps(&d2[n], out[2]); _mm_store_ps(&d3[n], out[3]); s += 4 * n_channels; } for(; n < n_samples; n++) { *((uint32_t*)&d0[n]) = bswap_32(*((uint32_t*)&s[0])); *((uint32_t*)&d1[n]) = bswap_32(*((uint32_t*)&s[1])); *((uint32_t*)&d2[n]) = bswap_32(*((uint32_t*)&s[2])); *((uint32_t*)&d3[n]) = bswap_32(*((uint32_t*)&s[3])); s += n_channels; } } void conv_32s_to_32d_sse2(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], uint32_t n_samples) { const float *s = src[0]; uint32_t i = 0, n_channels = conv->n_channels; for(; i + 3 < n_channels; i += 4) conv_deinterleave_32s_4s_sse2(conv, &dst[i], &s[i], n_channels, n_samples); for(; i < n_channels; i++) conv_deinterleave_32s_1s_sse2(conv, &dst[i], &s[i], n_channels, n_samples); } static void conv_f32_to_s16_1_sse2(void *data, void * SPA_RESTRICT dst, const void * SPA_RESTRICT src, uint32_t n_samples) { const float *s = src; int16_t *d = dst; uint32_t n, unrolled; __m128 in[2]; __m128i out[2]; __m128 int_scale = _mm_set1_ps(S16_SCALE); __m128 int_max = _mm_set1_ps(S16_MAX); __m128 int_min = _mm_set1_ps(S16_MIN); if (SPA_IS_ALIGNED(s, 16)) unrolled = n_samples & ~7; else unrolled = 0; for(n = 0; n < unrolled; n += 8) { in[0] = _mm_mul_ps(_mm_load_ps(&s[n]), int_scale); in[1] = _mm_mul_ps(_mm_load_ps(&s[n+4]), int_scale); out[0] = _mm_cvtps_epi32(in[0]); out[1] = _mm_cvtps_epi32(in[1]); out[0] = _mm_packs_epi32(out[0], out[1]); _mm_storeu_si128((__m128i*)(d+0), out[0]); d += 8; } for(; n < n_samples; n++) { in[0] = _mm_mul_ss(_mm_load_ss(&s[n]), int_scale); in[0] = _MM_CLAMP_SS(in[0], int_min, int_max); *d++ = _mm_cvtss_si32(in[0]); } } void conv_f32d_to_s16d_sse2(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], uint32_t n_samples) { uint32_t i, n_channels = conv->n_channels; for(i = 0; i < n_channels; i++) conv_f32_to_s16_1_sse2(conv, dst[i], src[i], n_samples); } void conv_f32_to_s16_sse2(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], uint32_t n_samples) { conv_f32_to_s16_1_sse2(conv, dst[0], src[0], n_samples * conv->n_channels); } static void conv_f32d_to_s16_1s_sse2(void *data, void * SPA_RESTRICT dst, const void * SPA_RESTRICT src[], uint32_t n_channels, uint32_t n_samples) { const float *s0 = src[0]; int16_t *d = dst; uint32_t n, unrolled; __m128 in[2]; __m128i out[2]; __m128 int_scale = _mm_set1_ps(S16_SCALE); __m128 int_max = _mm_set1_ps(S16_MAX); __m128 int_min = _mm_set1_ps(S16_MIN); if (SPA_IS_ALIGNED(s0, 16)) unrolled = n_samples & ~7; else unrolled = 0; for(n = 0; n < unrolled; n += 8) { in[0] = _mm_mul_ps(_mm_load_ps(&s0[n]), int_scale); in[1] = _mm_mul_ps(_mm_load_ps(&s0[n+4]), int_scale); out[0] = _mm_cvtps_epi32(in[0]); out[1] = _mm_cvtps_epi32(in[1]); out[0] = _mm_packs_epi32(out[0], out[1]); d[0*n_channels] = _mm_extract_epi16(out[0], 0); d[1*n_channels] = _mm_extract_epi16(out[0], 1); d[2*n_channels] = _mm_extract_epi16(out[0], 2); d[3*n_channels] = _mm_extract_epi16(out[0], 3); d[4*n_channels] = _mm_extract_epi16(out[0], 4); d[5*n_channels] = _mm_extract_epi16(out[0], 5); d[6*n_channels] = _mm_extract_epi16(out[0], 6); d[7*n_channels] = _mm_extract_epi16(out[0], 7); d += 8*n_channels; } for(; n < n_samples; n++) { in[0] = _mm_mul_ss(_mm_load_ss(&s0[n]), int_scale); in[0] = _MM_CLAMP_SS(in[0], int_min, int_max); *d = _mm_cvtss_si32(in[0]); d += n_channels; } } static void conv_f32d_to_s16_2s_sse2(void *data, void * SPA_RESTRICT dst, const void * SPA_RESTRICT src[], uint32_t n_channels, uint32_t n_samples) { const float *s0 = src[0], *s1 = src[1]; int16_t *d = dst; uint32_t n, unrolled; __m128 in[2]; __m128i out[4], t[2]; __m128 int_scale = _mm_set1_ps(S16_SCALE); __m128 int_max = _mm_set1_ps(S16_MAX); __m128 int_min = _mm_set1_ps(S16_MIN); if (SPA_IS_ALIGNED(s0, 16) && SPA_IS_ALIGNED(s1, 16)) unrolled = n_samples & ~3; else unrolled = 0; for(n = 0; n < unrolled; n += 4) { in[0] = _mm_mul_ps(_mm_load_ps(&s0[n]), int_scale); in[1] = _mm_mul_ps(_mm_load_ps(&s1[n]), int_scale); t[0] = _mm_cvtps_epi32(in[0]); t[1] = _mm_cvtps_epi32(in[1]); t[0] = _mm_packs_epi32(t[0], t[0]); t[1] = _mm_packs_epi32(t[1], t[1]); out[0] = _mm_unpacklo_epi16(t[0], t[1]); out[1] = _mm_shuffle_epi32(out[0], _MM_SHUFFLE(0, 3, 2, 1)); out[2] = _mm_shuffle_epi32(out[0], _MM_SHUFFLE(1, 0, 3, 2)); out[3] = _mm_shuffle_epi32(out[0], _MM_SHUFFLE(2, 1, 0, 3)); spa_write_unaligned(d + 0*n_channels, uint32_t, _mm_cvtsi128_si32(out[0])); spa_write_unaligned(d + 1*n_channels, uint32_t, _mm_cvtsi128_si32(out[1])); spa_write_unaligned(d + 2*n_channels, uint32_t, _mm_cvtsi128_si32(out[2])); spa_write_unaligned(d + 3*n_channels, uint32_t, _mm_cvtsi128_si32(out[3])); d += 4*n_channels; } for(; n < n_samples; n++) { in[0] = _mm_mul_ss(_mm_load_ss(&s0[n]), int_scale); in[1] = _mm_mul_ss(_mm_load_ss(&s1[n]), int_scale); in[0] = _MM_CLAMP_SS(in[0], int_min, int_max); in[1] = _MM_CLAMP_SS(in[1], int_min, int_max); d[0] = _mm_cvtss_si32(in[0]); d[1] = _mm_cvtss_si32(in[1]); d += n_channels; } } static void conv_f32d_to_s16_4s_sse2(void *data, void * SPA_RESTRICT dst, const void * SPA_RESTRICT src[], uint32_t n_channels, uint32_t n_samples) { const float *s0 = src[0], *s1 = src[1], *s2 = src[2], *s3 = src[3]; int16_t *d = dst; uint32_t n, unrolled; __m128 in[4]; __m128i out[4], t[4]; __m128 int_scale = _mm_set1_ps(S16_SCALE); __m128 int_max = _mm_set1_ps(S16_MAX); __m128 int_min = _mm_set1_ps(S16_MIN); if (SPA_IS_ALIGNED(s0, 16) && SPA_IS_ALIGNED(s1, 16) && SPA_IS_ALIGNED(s2, 16) && SPA_IS_ALIGNED(s3, 16)) unrolled = n_samples & ~3; else unrolled = 0; for(n = 0; n < unrolled; n += 4) { in[0] = _mm_mul_ps(_mm_load_ps(&s0[n]), int_scale); in[1] = _mm_mul_ps(_mm_load_ps(&s1[n]), int_scale); in[2] = _mm_mul_ps(_mm_load_ps(&s2[n]), int_scale); in[3] = _mm_mul_ps(_mm_load_ps(&s3[n]), int_scale); t[0] = _mm_cvtps_epi32(in[0]); t[1] = _mm_cvtps_epi32(in[1]); t[2] = _mm_cvtps_epi32(in[2]); t[3] = _mm_cvtps_epi32(in[3]); t[0] = _mm_packs_epi32(t[0], t[2]); t[1] = _mm_packs_epi32(t[1], t[3]); out[0] = _mm_unpacklo_epi16(t[0], t[1]); out[1] = _mm_unpackhi_epi16(t[0], t[1]); out[2] = _mm_unpacklo_epi32(out[0], out[1]); out[3] = _mm_unpackhi_epi32(out[0], out[1]); _mm_storel_pi((__m64*)(d + 0*n_channels), (__m128)out[2]); _mm_storeh_pi((__m64*)(d + 1*n_channels), (__m128)out[2]); _mm_storel_pi((__m64*)(d + 2*n_channels), (__m128)out[3]); _mm_storeh_pi((__m64*)(d + 3*n_channels), (__m128)out[3]); d += 4*n_channels; } for(; n < n_samples; n++) { in[0] = _mm_mul_ss(_mm_load_ss(&s0[n]), int_scale); in[1] = _mm_mul_ss(_mm_load_ss(&s1[n]), int_scale); in[2] = _mm_mul_ss(_mm_load_ss(&s2[n]), int_scale); in[3] = _mm_mul_ss(_mm_load_ss(&s3[n]), int_scale); in[0] = _MM_CLAMP_SS(in[0], int_min, int_max); in[1] = _MM_CLAMP_SS(in[1], int_min, int_max); in[2] = _MM_CLAMP_SS(in[2], int_min, int_max); in[3] = _MM_CLAMP_SS(in[3], int_min, int_max); d[0] = _mm_cvtss_si32(in[0]); d[1] = _mm_cvtss_si32(in[1]); d[2] = _mm_cvtss_si32(in[2]); d[3] = _mm_cvtss_si32(in[3]); d += n_channels; } } void conv_f32d_to_s16_sse2(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], uint32_t n_samples) { int16_t *d = dst[0]; uint32_t i = 0, n_channels = conv->n_channels; for(; i + 3 < n_channels; i += 4) conv_f32d_to_s16_4s_sse2(conv, &d[i], &src[i], n_channels, n_samples); for(; i + 1 < n_channels; i += 2) conv_f32d_to_s16_2s_sse2(conv, &d[i], &src[i], n_channels, n_samples); for(; i < n_channels; i++) conv_f32d_to_s16_1s_sse2(conv, &d[i], &src[i], n_channels, n_samples); } static void conv_f32d_to_s16s_1s_sse2(void *data, void * SPA_RESTRICT dst, const void * SPA_RESTRICT src[], uint32_t n_channels, uint32_t n_samples) { const float *s0 = src[0]; uint16_t *d = dst; uint32_t n, unrolled; __m128 in[2]; __m128i out[2]; __m128 int_scale = _mm_set1_ps(S16_SCALE); __m128 int_max = _mm_set1_ps(S16_MAX); __m128 int_min = _mm_set1_ps(S16_MIN); if (SPA_IS_ALIGNED(s0, 16)) unrolled = n_samples & ~7; else unrolled = 0; for(n = 0; n < unrolled; n += 8) { in[0] = _mm_mul_ps(_mm_load_ps(&s0[n]), int_scale); in[1] = _mm_mul_ps(_mm_load_ps(&s0[n+4]), int_scale); out[0] = _mm_cvtps_epi32(in[0]); out[1] = _mm_cvtps_epi32(in[1]); out[0] = _mm_packs_epi32(out[0], out[1]); out[0] = _MM_BSWAP_EPI16(out[0]); d[0*n_channels] = _mm_extract_epi16(out[0], 0); d[1*n_channels] = _mm_extract_epi16(out[0], 1); d[2*n_channels] = _mm_extract_epi16(out[0], 2); d[3*n_channels] = _mm_extract_epi16(out[0], 3); d[4*n_channels] = _mm_extract_epi16(out[0], 4); d[5*n_channels] = _mm_extract_epi16(out[0], 5); d[6*n_channels] = _mm_extract_epi16(out[0], 6); d[7*n_channels] = _mm_extract_epi16(out[0], 7); d += 8*n_channels; } for(; n < n_samples; n++) { in[0] = _mm_mul_ss(_mm_load_ss(&s0[n]), int_scale); in[0] = _MM_CLAMP_SS(in[0], int_min, int_max); *d = bswap_16((uint16_t)_mm_cvtss_si32(in[0])); d += n_channels; } } void conv_f32d_to_s16s_sse2(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], uint32_t n_samples) { uint16_t *d = dst[0]; uint32_t i = 0, n_channels = conv->n_channels; for(; i < n_channels; i++) conv_f32d_to_s16s_1s_sse2(conv, &d[i], &src[i], n_channels, n_samples); } static void conv_f32d_to_s16_1s_noise_sse2(struct convert *conv, void * SPA_RESTRICT dst, const void * SPA_RESTRICT src, const float *noise, uint32_t n_channels, uint32_t n_samples) { const float *s0 = src; int16_t *d = dst; uint32_t n, unrolled; __m128 in[2]; __m128i out[2]; __m128 int_scale = _mm_set1_ps(S16_SCALE); __m128 int_max = _mm_set1_ps(S16_MAX); __m128 int_min = _mm_set1_ps(S16_MIN); if (SPA_IS_ALIGNED(s0, 16)) unrolled = n_samples & ~7; else unrolled = 0; for(n = 0; n < unrolled; n += 8) { in[0] = _mm_mul_ps(_mm_load_ps(&s0[n]), int_scale); in[1] = _mm_mul_ps(_mm_load_ps(&s0[n+4]), int_scale); in[0] = _mm_add_ps(in[0], _mm_load_ps(&noise[n])); in[1] = _mm_add_ps(in[1], _mm_load_ps(&noise[n+4])); out[0] = _mm_cvtps_epi32(in[0]); out[1] = _mm_cvtps_epi32(in[1]); out[0] = _mm_packs_epi32(out[0], out[1]); d[0*n_channels] = _mm_extract_epi16(out[0], 0); d[1*n_channels] = _mm_extract_epi16(out[0], 1); d[2*n_channels] = _mm_extract_epi16(out[0], 2); d[3*n_channels] = _mm_extract_epi16(out[0], 3); d[4*n_channels] = _mm_extract_epi16(out[0], 4); d[5*n_channels] = _mm_extract_epi16(out[0], 5); d[6*n_channels] = _mm_extract_epi16(out[0], 6); d[7*n_channels] = _mm_extract_epi16(out[0], 7); d += 8*n_channels; } for(; n < n_samples; n++) { in[0] = _mm_mul_ss(_mm_load_ss(&s0[n]), int_scale); in[0] = _mm_add_ss(in[0], _mm_load_ss(&noise[n])); in[0] = _MM_CLAMP_SS(in[0], int_min, int_max); *d = _mm_cvtss_si32(in[0]); d += n_channels; } } void conv_f32d_to_s16_noise_sse2(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], uint32_t n_samples) { int16_t *d = dst[0]; uint32_t i, k, chunk, n_channels = conv->n_channels; float *noise = conv->noise; convert_update_noise(conv, noise, SPA_MIN(n_samples, conv->noise_size)); for(i = 0; i < n_channels; i++) { const float *s = src[i]; for(k = 0; k < n_samples; k += chunk) { chunk = SPA_MIN(n_samples - k, conv->noise_size); conv_f32d_to_s16_1s_noise_sse2(conv, &d[i + k*n_channels], &s[k], noise, n_channels, chunk); } } } static void conv_f32_to_s16_1_noise_sse2(struct convert *conv, void * SPA_RESTRICT dst, const void * SPA_RESTRICT src, const float *noise, uint32_t n_samples) { const float *s = src; int16_t *d = dst; uint32_t n, unrolled; __m128 in[2]; __m128i out[2]; __m128 int_scale = _mm_set1_ps(S16_SCALE); __m128 int_max = _mm_set1_ps(S16_MAX); __m128 int_min = _mm_set1_ps(S16_MIN); if (SPA_IS_ALIGNED(s, 16)) unrolled = n_samples & ~7; else unrolled = 0; for(n = 0; n < unrolled; n += 8) { in[0] = _mm_mul_ps(_mm_load_ps(&s[n]), int_scale); in[1] = _mm_mul_ps(_mm_load_ps(&s[n+4]), int_scale); in[0] = _mm_add_ps(in[0], _mm_load_ps(&noise[n])); in[1] = _mm_add_ps(in[1], _mm_load_ps(&noise[n+4])); out[0] = _mm_cvtps_epi32(in[0]); out[1] = _mm_cvtps_epi32(in[1]); out[0] = _mm_packs_epi32(out[0], out[1]); _mm_storeu_si128((__m128i*)(&d[n]), out[0]); } for(; n < n_samples; n++) { in[0] = _mm_mul_ss(_mm_load_ss(&s[n]), int_scale); in[0] = _mm_add_ss(in[0], _mm_load_ss(&noise[n])); in[0] = _MM_CLAMP_SS(in[0], int_min, int_max); d[n] = _mm_cvtss_si32(in[0]); } } void conv_f32d_to_s16d_noise_sse2(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], uint32_t n_samples) { uint32_t i, k, chunk, n_channels = conv->n_channels; float *noise = conv->noise; convert_update_noise(conv, noise, SPA_MIN(n_samples, conv->noise_size)); for(i = 0; i < n_channels; i++) { const float *s = src[i]; int16_t *d = dst[i]; for(k = 0; k < n_samples; k += chunk) { chunk = SPA_MIN(n_samples - k, conv->noise_size); conv_f32_to_s16_1_noise_sse2(conv, &d[k], &s[k], noise, chunk); } } } void conv_f32d_to_s16_2_sse2(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], uint32_t n_samples) { const float *s0 = src[0], *s1 = src[1]; int16_t *d = dst[0]; uint32_t n, unrolled; __m128 in[4]; __m128i out[4]; __m128 int_scale = _mm_set1_ps(S16_SCALE); __m128 int_max = _mm_set1_ps(S16_MAX); __m128 int_min = _mm_set1_ps(S16_MIN); if (SPA_IS_ALIGNED(s0, 16) && SPA_IS_ALIGNED(s1, 16)) unrolled = n_samples & ~7; else unrolled = 0; for(n = 0; n < unrolled; n += 8) { in[0] = _mm_mul_ps(_mm_load_ps(&s0[n+0]), int_scale); in[1] = _mm_mul_ps(_mm_load_ps(&s1[n+0]), int_scale); in[2] = _mm_mul_ps(_mm_load_ps(&s0[n+4]), int_scale); in[3] = _mm_mul_ps(_mm_load_ps(&s1[n+4]), int_scale); out[0] = _mm_cvtps_epi32(in[0]); out[1] = _mm_cvtps_epi32(in[1]); out[2] = _mm_cvtps_epi32(in[2]); out[3] = _mm_cvtps_epi32(in[3]); out[0] = _mm_packs_epi32(out[0], out[2]); out[1] = _mm_packs_epi32(out[1], out[3]); out[2] = _mm_unpacklo_epi16(out[0], out[1]); out[3] = _mm_unpackhi_epi16(out[0], out[1]); _mm_storeu_si128((__m128i*)(d+0), out[2]); _mm_storeu_si128((__m128i*)(d+8), out[3]); d += 16; } for(; n < n_samples; n++) { in[0] = _mm_mul_ss(_mm_load_ss(&s0[n]), int_scale); in[1] = _mm_mul_ss(_mm_load_ss(&s1[n]), int_scale); in[0] = _MM_CLAMP_SS(in[0], int_min, int_max); in[1] = _MM_CLAMP_SS(in[1], int_min, int_max); d[0] = _mm_cvtss_si32(in[0]); d[1] = _mm_cvtss_si32(in[1]); d += 2; } } void conv_f32d_to_s16s_2_sse2(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], uint32_t n_samples) { const float *s0 = src[0], *s1 = src[1]; uint16_t *d = dst[0]; uint32_t n, unrolled; __m128 in[4]; __m128i out[4]; __m128 int_scale = _mm_set1_ps(S16_SCALE); __m128 int_max = _mm_set1_ps(S16_MAX); __m128 int_min = _mm_set1_ps(S16_MIN); if (SPA_IS_ALIGNED(s0, 16) && SPA_IS_ALIGNED(s1, 16)) unrolled = n_samples & ~7; else unrolled = 0; for(n = 0; n < unrolled; n += 8) { in[0] = _mm_mul_ps(_mm_load_ps(&s0[n+0]), int_scale); in[1] = _mm_mul_ps(_mm_load_ps(&s1[n+0]), int_scale); in[2] = _mm_mul_ps(_mm_load_ps(&s0[n+4]), int_scale); in[3] = _mm_mul_ps(_mm_load_ps(&s1[n+4]), int_scale); out[0] = _mm_cvtps_epi32(in[0]); out[1] = _mm_cvtps_epi32(in[1]); out[2] = _mm_cvtps_epi32(in[2]); out[3] = _mm_cvtps_epi32(in[3]); out[0] = _mm_packs_epi32(out[0], out[2]); out[1] = _mm_packs_epi32(out[1], out[3]); out[2] = _mm_unpacklo_epi16(out[0], out[1]); out[3] = _mm_unpackhi_epi16(out[0], out[1]); out[2] = _MM_BSWAP_EPI16(out[2]); out[3] = _MM_BSWAP_EPI16(out[3]); _mm_storeu_si128((__m128i*)(d+0), out[2]); _mm_storeu_si128((__m128i*)(d+8), out[3]); d += 16; } for(; n < n_samples; n++) { in[0] = _mm_mul_ss(_mm_load_ss(&s0[n]), int_scale); in[1] = _mm_mul_ss(_mm_load_ss(&s1[n]), int_scale); in[0] = _MM_CLAMP_SS(in[0], int_min, int_max); in[1] = _MM_CLAMP_SS(in[1], int_min, int_max); d[0] = bswap_16((uint16_t)_mm_cvtss_si32(in[0])); d[1] = bswap_16((uint16_t)_mm_cvtss_si32(in[1])); d += 2; } } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/audioconvert/fmt-ops-sse41.c000066400000000000000000000043621511204443500301370ustar00rootroot00000000000000/* Spa */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include "fmt-ops.h" #include #define spa_read_unaligned(ptr, type) \ __extension__ ({ \ __typeof__(type) _val; \ memcpy(&_val, (ptr), sizeof(_val)); \ _val; \ }) static void conv_s24_to_f32d_1s_sse41(void *data, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src, uint32_t n_channels, uint32_t n_samples) { const int24_t *s = src; float *d0 = dst[0]; uint32_t n, unrolled; __m128i in = _mm_setzero_si128(); __m128 out, factor = _mm_set1_ps(1.0f / S24_SCALE); if (SPA_IS_ALIGNED(d0, 16)) unrolled = n_samples & ~3; else unrolled = 0; for(n = 0; n < unrolled; n += 4) { in = _mm_insert_epi32(in, spa_read_unaligned(&s[0 * n_channels], uint32_t), 0); in = _mm_insert_epi32(in, spa_read_unaligned(&s[1 * n_channels], uint32_t), 1); in = _mm_insert_epi32(in, spa_read_unaligned(&s[2 * n_channels], uint32_t), 2); in = _mm_insert_epi32(in, spa_read_unaligned(&s[3 * n_channels], uint32_t), 3); in = _mm_slli_epi32(in, 8); in = _mm_srai_epi32(in, 8); out = _mm_cvtepi32_ps(in); out = _mm_mul_ps(out, factor); _mm_store_ps(&d0[n], out); s += 4 * n_channels; } for(; n < n_samples; n++) { out = _mm_cvtsi32_ss(factor, s24_to_s32(*s)); out = _mm_mul_ss(out, factor); _mm_store_ss(&d0[n], out); s += n_channels; } } extern void conv_s24_to_f32d_2s_sse2(void *data, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src, uint32_t n_channels, uint32_t n_samples); extern void conv_s24_to_f32d_4s_ssse3(void *data, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src, uint32_t n_channels, uint32_t n_samples); void conv_s24_to_f32d_sse41(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], uint32_t n_samples) { const int8_t *s = src[0]; uint32_t i = 0, n_channels = conv->n_channels; #if defined (HAVE_SSSE3) for(; i + 3 < n_channels; i += 4) conv_s24_to_f32d_4s_ssse3(conv, &dst[i], &s[3*i], n_channels, n_samples); #endif #if defined (HAVE_SSE2) for(; i + 1 < n_channels; i += 2) conv_s24_to_f32d_2s_sse2(conv, &dst[i], &s[3*i], n_channels, n_samples); #endif for(; i < n_channels; i++) conv_s24_to_f32d_1s_sse41(conv, &dst[i], &s[3*i], n_channels, n_samples); } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/audioconvert/fmt-ops-ssse3.c000066400000000000000000000060441511204443500302370ustar00rootroot00000000000000/* Spa */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include "fmt-ops.h" #include static void conv_s24_to_f32d_4s_ssse3(void *data, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src, uint32_t n_channels, uint32_t n_samples) { const int24_t *s = src; float *d0 = dst[0], *d1 = dst[1], *d2 = dst[2], *d3 = dst[3]; uint32_t n, unrolled; __m128i in[4]; __m128 out[4], factor = _mm_set1_ps(1.0f / S24_SCALE); const __m128i mask = _mm_setr_epi8(-1, 0, 1, 2, -1, 3, 4, 5, -1, 6, 7, 8, -1, 9, 10, 11); //const __m128i mask = _mm_set_epi8(15, 14, 13, -1, 12, 11, 10, -1, 9, 8, 7, -1, 6, 5, 4, -1); if (SPA_IS_ALIGNED(d0, 16) && SPA_IS_ALIGNED(d1, 16) && SPA_IS_ALIGNED(d2, 16) && SPA_IS_ALIGNED(d3, 16)) unrolled = n_samples & ~3; else unrolled = 0; for(n = 0; n < unrolled; n += 4) { in[0] = _mm_loadu_si128((__m128i*)(s + 0*n_channels)); in[1] = _mm_loadu_si128((__m128i*)(s + 1*n_channels)); in[2] = _mm_loadu_si128((__m128i*)(s + 2*n_channels)); in[3] = _mm_loadu_si128((__m128i*)(s + 3*n_channels)); in[0] = _mm_shuffle_epi8(in[0], mask); in[1] = _mm_shuffle_epi8(in[1], mask); in[2] = _mm_shuffle_epi8(in[2], mask); in[3] = _mm_shuffle_epi8(in[3], mask); in[0] = _mm_srai_epi32(in[0], 8); in[1] = _mm_srai_epi32(in[1], 8); in[2] = _mm_srai_epi32(in[2], 8); in[3] = _mm_srai_epi32(in[3], 8); out[0] = _mm_cvtepi32_ps(in[0]); out[1] = _mm_cvtepi32_ps(in[1]); out[2] = _mm_cvtepi32_ps(in[2]); out[3] = _mm_cvtepi32_ps(in[3]); out[0] = _mm_mul_ps(out[0], factor); out[1] = _mm_mul_ps(out[1], factor); out[2] = _mm_mul_ps(out[2], factor); out[3] = _mm_mul_ps(out[3], factor); _MM_TRANSPOSE4_PS(out[0], out[1], out[2], out[3]); _mm_store_ps(&d0[n], out[0]); _mm_store_ps(&d1[n], out[1]); _mm_store_ps(&d2[n], out[2]); _mm_store_ps(&d3[n], out[3]); s += 4 * n_channels; } for(; n < n_samples; n++) { out[0] = _mm_cvtsi32_ss(factor, s24_to_s32(*s)); out[1] = _mm_cvtsi32_ss(factor, s24_to_s32(*(s+1))); out[2] = _mm_cvtsi32_ss(factor, s24_to_s32(*(s+2))); out[3] = _mm_cvtsi32_ss(factor, s24_to_s32(*(s+3))); out[0] = _mm_mul_ss(out[0], factor); out[1] = _mm_mul_ss(out[1], factor); out[2] = _mm_mul_ss(out[2], factor); out[3] = _mm_mul_ss(out[3], factor); _mm_store_ss(&d0[n], out[0]); _mm_store_ss(&d1[n], out[1]); _mm_store_ss(&d2[n], out[2]); _mm_store_ss(&d3[n], out[3]); s += n_channels; } } void conv_s24_to_f32d_1s_sse2(void *data, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src, uint32_t n_channels, uint32_t n_samples); void conv_s24_to_f32d_ssse3(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], uint32_t n_samples) { const int8_t *s = src[0]; uint32_t i = 0, n_channels = conv->n_channels; for(; i + 3 < n_channels; i += 4) conv_s24_to_f32d_4s_ssse3(conv, &dst[i], &s[3*i], n_channels, n_samples); for(; i < n_channels; i++) conv_s24_to_f32d_1s_sse2(conv, &dst[i], &s[3*i], n_channels, n_samples); } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/audioconvert/fmt-ops.c000066400000000000000000000464071511204443500272100ustar00rootroot00000000000000/* Spa */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #include #include #include "fmt-ops.h" #define NOISE_SIZE (1<<10) #define RANDOM_SIZE (16) typedef void (*convert_func_t) (struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], uint32_t n_samples); struct conv_info { uint32_t src_fmt; uint32_t dst_fmt; uint32_t n_channels; convert_func_t process; const char *name; uint32_t cpu_flags; #define CONV_NOISE (1<<0) #define CONV_SHAPE (1<<1) uint32_t conv_flags; }; #define MAKE(fmt1,fmt2,chan,func,...) \ { SPA_AUDIO_FORMAT_ ##fmt1, SPA_AUDIO_FORMAT_ ##fmt2, chan, func, #func , __VA_ARGS__ } static struct conv_info conv_table[] = { /* to f32 */ MAKE(U8, F32, 0, conv_u8_to_f32_c), MAKE(U8, F32, 0, conv_u8_to_f32_c), MAKE(U8P, F32P, 0, conv_u8d_to_f32d_c), MAKE(U8, F32P, 0, conv_u8_to_f32d_c), MAKE(U8P, F32, 0, conv_u8d_to_f32_c), MAKE(S8, F32, 0, conv_s8_to_f32_c), MAKE(S8P, F32P, 0, conv_s8d_to_f32d_c), MAKE(S8, F32P, 0, conv_s8_to_f32d_c), MAKE(S8P, F32, 0, conv_s8d_to_f32_c), MAKE(ALAW, F32P, 0, conv_alaw_to_f32d_c), MAKE(ULAW, F32P, 0, conv_ulaw_to_f32d_c), MAKE(U16, F32, 0, conv_u16_to_f32_c), MAKE(U16, F32P, 0, conv_u16_to_f32d_c), MAKE(S16, F32, 0, conv_s16_to_f32_c), MAKE(S16P, F32P, 0, conv_s16d_to_f32d_c), #if defined (HAVE_NEON) MAKE(S16, F32P, 2, conv_s16_to_f32d_2_neon, SPA_CPU_FLAG_NEON), MAKE(S16, F32P, 0, conv_s16_to_f32d_neon, SPA_CPU_FLAG_NEON), #endif #if defined (HAVE_AVX2) MAKE(S16, F32P, 2, conv_s16_to_f32d_2_avx2, SPA_CPU_FLAG_AVX2), MAKE(S16, F32P, 0, conv_s16_to_f32d_avx2, SPA_CPU_FLAG_AVX2), #endif #if defined (HAVE_SSE2) MAKE(S16, F32P, 2, conv_s16_to_f32d_2_sse2, SPA_CPU_FLAG_SSE2), MAKE(S16, F32P, 0, conv_s16_to_f32d_sse2, SPA_CPU_FLAG_SSE2), #endif #if defined (HAVE_RVV) MAKE(S16, F32P, 0, conv_s16_to_f32d_rvv, SPA_CPU_FLAG_RISCV_V), #endif MAKE(S16, F32P, 0, conv_s16_to_f32d_c), MAKE(S16P, F32, 0, conv_s16d_to_f32_c), #if defined (HAVE_AVX2) MAKE(S16_OE, F32P, 2, conv_s16s_to_f32d_2_avx2, SPA_CPU_FLAG_AVX2), MAKE(S16_OE, F32P, 0, conv_s16s_to_f32d_avx2, SPA_CPU_FLAG_AVX2), #endif #if defined (HAVE_SSE2) MAKE(S16_OE, F32P, 2, conv_s16s_to_f32d_2_sse2, SPA_CPU_FLAG_SSE2), MAKE(S16_OE, F32P, 0, conv_s16s_to_f32d_sse2, SPA_CPU_FLAG_SSE2), #endif MAKE(S16_OE, F32P, 0, conv_s16s_to_f32d_c), MAKE(F32, F32, 0, conv_copy32_c), MAKE(F32P, F32P, 0, conv_copy32d_c), #if defined (HAVE_SSE2) MAKE(F32, F32P, 0, conv_32_to_32d_sse2, SPA_CPU_FLAG_SSE2), #endif MAKE(F32, F32P, 0, conv_32_to_32d_c), #if defined (HAVE_SSE2) MAKE(F32P, F32, 0, conv_32d_to_32_sse2, SPA_CPU_FLAG_SSE2), #endif MAKE(F32P, F32, 0, conv_32d_to_32_c), #if defined (HAVE_SSE2) MAKE(F32_OE, F32P, 0, conv_32s_to_32d_sse2, SPA_CPU_FLAG_SSE2), #endif MAKE(F32_OE, F32P, 0, conv_32s_to_32d_c), #if defined (HAVE_SSE2) MAKE(F32P, F32_OE, 0, conv_32d_to_32s_sse2, SPA_CPU_FLAG_SSE2), #endif MAKE(F32P, F32_OE, 0, conv_32d_to_32s_c), MAKE(U32, F32, 0, conv_u32_to_f32_c), MAKE(U32, F32P, 0, conv_u32_to_f32d_c), #if defined (HAVE_AVX2) MAKE(S32, F32P, 0, conv_s32_to_f32d_avx2, SPA_CPU_FLAG_AVX2), #endif #if defined (HAVE_SSE2) MAKE(S32, F32P, 0, conv_s32_to_f32d_sse2, SPA_CPU_FLAG_SSE2), #endif #if defined (HAVE_RVV) MAKE(S32, F32P, 0, conv_s32_to_f32d_rvv, SPA_CPU_FLAG_RISCV_V), #endif MAKE(S32, F32, 0, conv_s32_to_f32_c), MAKE(S32P, F32P, 0, conv_s32d_to_f32d_c), MAKE(S32, F32P, 0, conv_s32_to_f32d_c), MAKE(S32P, F32, 0, conv_s32d_to_f32_c), MAKE(S32_OE, F32P, 0, conv_s32s_to_f32d_c), MAKE(U24, F32, 0, conv_u24_to_f32_c), MAKE(U24, F32P, 0, conv_u24_to_f32d_c), MAKE(S24, F32, 0, conv_s24_to_f32_c), MAKE(S24P, F32P, 0, conv_s24d_to_f32d_c), #if defined (HAVE_AVX2) MAKE(S24, F32P, 0, conv_s24_to_f32d_avx2, SPA_CPU_FLAG_AVX2), #endif #if defined (HAVE_SSSE3) // MAKE(S24, F32P, 0, conv_s24_to_f32d_ssse3, SPA_CPU_FLAG_SSSE3), #endif #if defined (HAVE_SSE41) MAKE(S24, F32P, 0, conv_s24_to_f32d_sse41, SPA_CPU_FLAG_SSE41), #endif #if defined (HAVE_SSE2) MAKE(S24, F32P, 0, conv_s24_to_f32d_sse2, SPA_CPU_FLAG_SSE2), #endif MAKE(S24, F32P, 0, conv_s24_to_f32d_c), MAKE(S24P, F32, 0, conv_s24d_to_f32_c), MAKE(S24_OE, F32P, 0, conv_s24s_to_f32d_c), MAKE(U24_32, F32, 0, conv_u24_32_to_f32_c), MAKE(U24_32, F32P, 0, conv_u24_32_to_f32d_c), MAKE(S24_32, F32, 0, conv_s24_32_to_f32_c), MAKE(S24_32P, F32P, 0, conv_s24_32d_to_f32d_c), MAKE(S24_32, F32P, 0, conv_s24_32_to_f32d_c), MAKE(S24_32P, F32, 0, conv_s24_32d_to_f32_c), MAKE(S24_32_OE, F32P, 0, conv_s24_32s_to_f32d_c), MAKE(F64, F32, 0, conv_f64_to_f32_c), MAKE(F64P, F32P, 0, conv_f64d_to_f32d_c), MAKE(F64, F32P, 0, conv_f64_to_f32d_c), MAKE(F64P, F32, 0, conv_f64d_to_f32_c), MAKE(F64_OE, F32P, 0, conv_f64s_to_f32d_c), /* from f32 */ MAKE(F32, U8, 0, conv_f32_to_u8_c), MAKE(F32P, U8P, 0, conv_f32d_to_u8d_shaped_c, 0, CONV_SHAPE), MAKE(F32P, U8P, 0, conv_f32d_to_u8d_noise_c, 0, CONV_NOISE), MAKE(F32P, U8P, 0, conv_f32d_to_u8d_c), MAKE(F32, U8P, 0, conv_f32_to_u8d_c), MAKE(F32P, U8, 0, conv_f32d_to_u8_shaped_c, 0, CONV_SHAPE), MAKE(F32P, U8, 0, conv_f32d_to_u8_noise_c, 0, CONV_NOISE), MAKE(F32P, U8, 0, conv_f32d_to_u8_c), MAKE(F32, S8, 0, conv_f32_to_s8_c), MAKE(F32P, S8P, 0, conv_f32d_to_s8d_shaped_c, 0, CONV_SHAPE), MAKE(F32P, S8P, 0, conv_f32d_to_s8d_noise_c, 0, CONV_NOISE), MAKE(F32P, S8P, 0, conv_f32d_to_s8d_c), MAKE(F32, S8P, 0, conv_f32_to_s8d_c), MAKE(F32P, S8, 0, conv_f32d_to_s8_shaped_c, 0, CONV_SHAPE), MAKE(F32P, S8, 0, conv_f32d_to_s8_noise_c, 0, CONV_NOISE), MAKE(F32P, S8, 0, conv_f32d_to_s8_c), MAKE(F32P, ALAW, 0, conv_f32d_to_alaw_c), MAKE(F32P, ULAW, 0, conv_f32d_to_ulaw_c), MAKE(F32, U16, 0, conv_f32_to_u16_c), MAKE(F32P, U16, 0, conv_f32d_to_u16_c), #if defined (HAVE_SSE2) MAKE(F32, S16, 0, conv_f32_to_s16_sse2, SPA_CPU_FLAG_SSE2), #endif #if defined (HAVE_RVV) MAKE(F32, S16, 0, conv_f32_to_s16_rvv, SPA_CPU_FLAG_RISCV_V), MAKE(F32P, S16P, 0, conv_f32d_to_s16d_rvv, SPA_CPU_FLAG_RISCV_V), MAKE(F32P, S16, 0, conv_f32d_to_s16_rvv, SPA_CPU_FLAG_RISCV_V), #endif MAKE(F32, S16, 0, conv_f32_to_s16_c), MAKE(F32P, S16P, 0, conv_f32d_to_s16d_shaped_c, 0, CONV_SHAPE), #if defined (HAVE_SSE2) MAKE(F32P, S16P, 0, conv_f32d_to_s16d_noise_sse2, SPA_CPU_FLAG_SSE2, CONV_NOISE), #endif MAKE(F32P, S16P, 0, conv_f32d_to_s16d_noise_c, 0, CONV_NOISE), #if defined (HAVE_SSE2) MAKE(F32P, S16P, 0, conv_f32d_to_s16d_sse2, SPA_CPU_FLAG_SSE2), #endif MAKE(F32P, S16P, 0, conv_f32d_to_s16d_c), MAKE(F32, S16P, 0, conv_f32_to_s16d_c), MAKE(F32P, S16, 0, conv_f32d_to_s16_shaped_c, 0, CONV_SHAPE), #if defined (HAVE_SSE2) MAKE(F32P, S16, 0, conv_f32d_to_s16_noise_sse2, SPA_CPU_FLAG_SSE2, CONV_NOISE), #endif MAKE(F32P, S16, 0, conv_f32d_to_s16_noise_c, 0, CONV_NOISE), #if defined (HAVE_NEON) MAKE(F32P, S16, 0, conv_f32d_to_s16_neon, SPA_CPU_FLAG_NEON), #endif #if defined (HAVE_AVX2) MAKE(F32P, S16, 4, conv_f32d_to_s16_4_avx2, SPA_CPU_FLAG_AVX2), MAKE(F32P, S16, 2, conv_f32d_to_s16_2_avx2, SPA_CPU_FLAG_AVX2), MAKE(F32P, S16, 0, conv_f32d_to_s16_avx2, SPA_CPU_FLAG_AVX2), #endif #if defined (HAVE_SSE2) MAKE(F32P, S16, 2, conv_f32d_to_s16_2_sse2, SPA_CPU_FLAG_SSE2), MAKE(F32P, S16, 0, conv_f32d_to_s16_sse2, SPA_CPU_FLAG_SSE2), #endif MAKE(F32P, S16, 0, conv_f32d_to_s16_c), MAKE(F32P, S16_OE, 0, conv_f32d_to_s16s_shaped_c, 0, CONV_SHAPE), MAKE(F32P, S16_OE, 0, conv_f32d_to_s16s_noise_c, 0, CONV_NOISE), #if defined (HAVE_SSE2) MAKE(F32P, S16_OE, 2, conv_f32d_to_s16s_2_sse2, SPA_CPU_FLAG_SSE2), MAKE(F32P, S16_OE, 0, conv_f32d_to_s16s_sse2, SPA_CPU_FLAG_SSE2), #endif MAKE(F32P, S16_OE, 0, conv_f32d_to_s16s_c), MAKE(F32, U32, 0, conv_f32_to_u32_c), MAKE(F32P, U32, 0, conv_f32d_to_u32_c), MAKE(F32, S32, 0, conv_f32_to_s32_c), MAKE(F32P, S32P, 0, conv_f32d_to_s32d_noise_c, 0, CONV_NOISE), MAKE(F32P, S32P, 0, conv_f32d_to_s32d_c), MAKE(F32, S32P, 0, conv_f32_to_s32d_c), #if defined (HAVE_SSE2) MAKE(F32P, S32, 0, conv_f32d_to_s32_noise_sse2, SPA_CPU_FLAG_SSE2, CONV_NOISE), #endif MAKE(F32P, S32, 0, conv_f32d_to_s32_noise_c, 0, CONV_NOISE), #if defined (HAVE_AVX2) MAKE(F32P, S32, 0, conv_f32d_to_s32_avx2, SPA_CPU_FLAG_AVX2), #endif #if defined (HAVE_SSE2) MAKE(F32P, S32, 0, conv_f32d_to_s32_sse2, SPA_CPU_FLAG_SSE2), #endif #if defined (HAVE_RVV) MAKE(F32P, S32, 0, conv_f32d_to_s32_rvv, SPA_CPU_FLAG_RISCV_V), #endif MAKE(F32P, S32, 0, conv_f32d_to_s32_c), MAKE(F32P, S32_OE, 0, conv_f32d_to_s32s_noise_c, 0, CONV_NOISE), MAKE(F32P, S32_OE, 0, conv_f32d_to_s32s_c), MAKE(F32, U24, 0, conv_f32_to_u24_c), MAKE(F32P, U24, 0, conv_f32d_to_u24_c), MAKE(F32, S24, 0, conv_f32_to_s24_c), MAKE(F32P, S24P, 0, conv_f32d_to_s24d_noise_c, 0, CONV_NOISE), MAKE(F32P, S24P, 0, conv_f32d_to_s24d_c), MAKE(F32, S24P, 0, conv_f32_to_s24d_c), MAKE(F32P, S24, 0, conv_f32d_to_s24_noise_c, 0, CONV_NOISE), MAKE(F32P, S24, 0, conv_f32d_to_s24_c), MAKE(F32P, S24_OE, 0, conv_f32d_to_s24s_noise_c, 0, CONV_NOISE), MAKE(F32P, S24_OE, 0, conv_f32d_to_s24s_c), MAKE(F32, U24_32, 0, conv_f32_to_u24_32_c), MAKE(F32P, U24_32, 0, conv_f32d_to_u24_32_c), MAKE(F32, S24_32, 0, conv_f32_to_s24_32_c), MAKE(F32P, S24_32P, 0, conv_f32d_to_s24_32d_noise_c, 0, CONV_NOISE), MAKE(F32P, S24_32P, 0, conv_f32d_to_s24_32d_c), MAKE(F32, S24_32P, 0, conv_f32_to_s24_32d_c), MAKE(F32P, S24_32, 0, conv_f32d_to_s24_32_noise_c, 0, CONV_NOISE), MAKE(F32P, S24_32, 0, conv_f32d_to_s24_32_c), MAKE(F32P, S24_32_OE, 0, conv_f32d_to_s24_32s_noise_c, 0, CONV_NOISE), MAKE(F32P, S24_32_OE, 0, conv_f32d_to_s24_32s_c), MAKE(F32, F64, 0, conv_f32_to_f64_c), MAKE(F32P, F64P, 0, conv_f32d_to_f64d_c), MAKE(F32, F64P, 0, conv_f32_to_f64d_c), MAKE(F32P, F64, 0, conv_f32d_to_f64_c), MAKE(F32P, F64_OE, 0, conv_f32d_to_f64s_c), /* u8 */ MAKE(U8, U8, 0, conv_copy8_c), MAKE(U8P, U8P, 0, conv_copy8d_c), MAKE(U8, U8P, 0, conv_8_to_8d_c), MAKE(U8P, U8, 0, conv_8d_to_8_c), /* s8 */ MAKE(S8, S8, 0, conv_copy8_c), MAKE(S8P, S8P, 0, conv_copy8d_c), MAKE(S8, S8P, 0, conv_8_to_8d_c), MAKE(S8P, S8, 0, conv_8d_to_8_c), /* alaw */ MAKE(ALAW, ALAW, 0, conv_copy8_c), /* ulaw */ MAKE(ULAW, ULAW, 0, conv_copy8_c), /* s16 */ MAKE(S16, S16, 0, conv_copy16_c), MAKE(S16P, S16P, 0, conv_copy16d_c), MAKE(S16, S16P, 0, conv_16_to_16d_c), MAKE(S16P, S16, 0, conv_16d_to_16_c), /* s32 */ MAKE(S32, S32, 0, conv_copy32_c), MAKE(S32P, S32P, 0, conv_copy32d_c), #if defined (HAVE_SSE2) MAKE(S32, S32P, 0, conv_32_to_32d_sse2, SPA_CPU_FLAG_SSE2), #endif MAKE(S32, S32P, 0, conv_32_to_32d_c), #if defined (HAVE_SSE2) MAKE(S32P, S32, 0, conv_32d_to_32_sse2, SPA_CPU_FLAG_SSE2), #endif MAKE(S32P, S32, 0, conv_32d_to_32_c), /* s24 */ MAKE(S24, S24, 0, conv_copy24_c), MAKE(S24P, S24P, 0, conv_copy24d_c), MAKE(S24, S24P, 0, conv_24_to_24d_c), MAKE(S24P, S24, 0, conv_24d_to_24_c), /* s24_32 */ MAKE(S24_32, S24_32, 0, conv_copy32_c), MAKE(S24_32P, S24_32P, 0, conv_copy32d_c), #if defined (HAVE_SSE2) MAKE(S24_32, S24_32P, 0, conv_32_to_32d_sse2, SPA_CPU_FLAG_SSE2), #endif MAKE(S24_32, S24_32P, 0, conv_32_to_32d_c), #if defined (HAVE_SSE2) MAKE(S24_32P, S24_32, 0, conv_32d_to_32_sse2, SPA_CPU_FLAG_SSE2), #endif MAKE(S24_32P, S24_32, 0, conv_32d_to_32_c), /* F64 */ MAKE(F64, F64, 0, conv_copy64_c), MAKE(F64P, F64P, 0, conv_copy64d_c), MAKE(F64, F64P, 0, conv_64_to_64d_c), MAKE(F64P, F64, 0, conv_64d_to_64_c), }; #undef MAKE #define MATCH_CHAN(a,b) ((a) == 0 || (a) == (b)) #define MATCH_CPU_FLAGS(a,b) ((a) == 0 || ((a) & (b)) == a) #define MATCH_DITHER(a,b) ((a) == 0 || ((a) & (b)) == a) static const struct conv_info *find_conv_info(uint32_t src_fmt, uint32_t dst_fmt, uint32_t n_channels, uint32_t cpu_flags, uint32_t conv_flags) { SPA_FOR_EACH_ELEMENT_VAR(conv_table, c) { if (c->src_fmt == src_fmt && c->dst_fmt == dst_fmt && MATCH_CHAN(c->n_channels, n_channels) && MATCH_CPU_FLAGS(c->cpu_flags, cpu_flags) && MATCH_DITHER(c->conv_flags, conv_flags)) return c; } return NULL; } typedef void (*clear_func_t) (struct convert *conv, void * SPA_RESTRICT dst[], uint32_t n_samples); struct clear_info { uint32_t fmt; clear_func_t clear; const char *name; uint32_t cpu_flags; }; #define MAKE(fmt,func,...) \ { SPA_AUDIO_FORMAT_ ##fmt, func, #func , __VA_ARGS__ } static struct clear_info clear_table[] = { MAKE(U8, conv_clear_u8_c), MAKE(U8P, conv_clear_u8d_c), MAKE(S8, conv_clear_8_c), MAKE(S8P, conv_clear_8d_c), MAKE(U16, conv_clear_u16_c), MAKE(S16, conv_clear_16_c), MAKE(S16_OE, conv_clear_16_c), MAKE(S16P, conv_clear_16d_c), MAKE(U24, conv_clear_u24_c), MAKE(S24, conv_clear_24_c), MAKE(S24_OE, conv_clear_24_c), MAKE(S24P, conv_clear_24d_c), MAKE(U24_32, conv_clear_u24_32_c), MAKE(S24_32, conv_clear_32_c), MAKE(S24_32_OE, conv_clear_32_c), MAKE(U32, conv_clear_u32_c), MAKE(S32, conv_clear_32_c), MAKE(S32_OE, conv_clear_32_c), MAKE(S32P, conv_clear_32d_c), MAKE(F32, conv_clear_32_c), MAKE(F32_OE, conv_clear_32_c), MAKE(F32P, conv_clear_32d_c), MAKE(F64, conv_clear_64_c), MAKE(F64_OE, conv_clear_64_c), MAKE(F64P, conv_clear_64d_c), MAKE(ALAW, conv_clear_alaw_c), MAKE(ULAW, conv_clear_ulaw_c), }; #undef MAKE static const struct clear_info *find_clear_info(uint32_t fmt, uint32_t cpu_flags) { SPA_FOR_EACH_ELEMENT_VAR(clear_table, c) { if (c->fmt == fmt && MATCH_CPU_FLAGS(c->cpu_flags, cpu_flags)) return c; } return NULL; } typedef void (*noise_func_t) (struct convert *conv, float * noise, uint32_t n_samples); struct noise_info { uint32_t method; noise_func_t noise; const char *name; uint32_t cpu_flags; }; #define MAKE(method,func,...) \ { NOISE_METHOD_ ##method, func, #func , __VA_ARGS__ } static struct noise_info noise_table[] = { #if defined (HAVE_SSE2) MAKE(RECTANGULAR, conv_noise_rect_sse2, SPA_CPU_FLAG_SSE2), MAKE(TRIANGULAR, conv_noise_tri_sse2, SPA_CPU_FLAG_SSE2), MAKE(TRIANGULAR_HF, conv_noise_tri_hf_sse2, SPA_CPU_FLAG_SSE2), #endif MAKE(NONE, conv_noise_none_c), MAKE(RECTANGULAR, conv_noise_rect_c), MAKE(TRIANGULAR, conv_noise_tri_c), MAKE(TRIANGULAR_HF, conv_noise_tri_hf_c), MAKE(PATTERN, conv_noise_pattern_c), }; #undef MAKE static const struct noise_info *find_noise_info(uint32_t method, uint32_t cpu_flags) { SPA_FOR_EACH_ELEMENT_VAR(noise_table, t) { if (t->method == method && MATCH_CPU_FLAGS(t->cpu_flags, cpu_flags)) return t; } return NULL; } static void impl_convert_free(struct convert *conv) { conv->process = NULL; free(conv->data); conv->data = NULL; } static bool need_dither(uint32_t format) { switch (format) { case SPA_AUDIO_FORMAT_U8: case SPA_AUDIO_FORMAT_U8P: case SPA_AUDIO_FORMAT_S8: case SPA_AUDIO_FORMAT_S8P: case SPA_AUDIO_FORMAT_ULAW: case SPA_AUDIO_FORMAT_ALAW: case SPA_AUDIO_FORMAT_S16P: case SPA_AUDIO_FORMAT_S16: case SPA_AUDIO_FORMAT_S16_OE: return true; } return false; } /* filters based on F-weighted curves * from 'Psychoacoustically Optimal Noise Shaping' (**) * this filter is the "F-Weighted" noise filter described by Wannamaker * It is designed to produce minimum audibility: */ static const float wan3[] = { /* Table 3; 3 Coefficients */ 1.623f, -0.982f, 0.109f }; /* Noise shaping coefficients from[1], moves most power of the * error noise into inaudible frequency ranges. * * [1] * "Minimally Audible Noise Shaping", Stanley P. Lipshitz, * John Vanderkooy, and Robert A. Wannamaker, * J. Audio Eng. Soc., Vol. 39, No. 11, November 1991. */ static const float lips44[] = { /* improved E-weighted (appendix: 5) */ 2.033f, -2.165f, 1.959f, -1.590f, 0.6149f }; static const struct dither_info { uint32_t method; uint32_t noise_method; uint32_t rate; const float *ns; uint32_t n_ns; } dither_info[] = { { DITHER_METHOD_NONE, NOISE_METHOD_NONE, }, { DITHER_METHOD_RECTANGULAR, NOISE_METHOD_RECTANGULAR, }, { DITHER_METHOD_TRIANGULAR, NOISE_METHOD_TRIANGULAR, }, { DITHER_METHOD_TRIANGULAR_HF, NOISE_METHOD_TRIANGULAR_HF, }, { DITHER_METHOD_WANNAMAKER_3, NOISE_METHOD_TRIANGULAR_HF, 44100, wan3, SPA_N_ELEMENTS(wan3) }, { DITHER_METHOD_LIPSHITZ, NOISE_METHOD_TRIANGULAR, 44100, lips44, SPA_N_ELEMENTS(lips44) } }; static const struct dither_info *find_dither_info(uint32_t method, uint32_t rate) { SPA_FOR_EACH_ELEMENT_VAR(dither_info, di) { if (di->method != method) continue; /* don't use shaped for too low rates, it moves the noise to * audible ranges */ if (di->ns != NULL && rate < di->rate * 3 / 4) return find_dither_info(DITHER_METHOD_TRIANGULAR_HF, rate); return di; } return NULL; } int convert_init(struct convert *conv) { const struct conv_info *info; const struct dither_info *dinfo; const struct noise_info *ninfo; const struct clear_info *cinfo; uint32_t i, conv_flags, data_size[4]; /* we generate int32 bits of random values. With this scale * factor, we bring this in the [-1.0, 1.0] range */ conv->scale = 1.0f / (float)(INT32_MAX); /* disable dither if not needed */ if (!need_dither(conv->dst_fmt)) conv->method = DITHER_METHOD_NONE; dinfo = find_dither_info(conv->method, conv->rate); if (dinfo == NULL) return -EINVAL; conv->noise_method = dinfo->noise_method; if (conv->noise_bits > 0) { switch (conv->noise_method) { case NOISE_METHOD_NONE: conv->noise_method = NOISE_METHOD_PATTERN; /* the pattern method does not use a random number * but flips the noise between [-(1<<(N-1)), 0] every * 1024 samples. */ conv->scale = -1.0f * (1 << (conv->noise_bits-1)); break; case NOISE_METHOD_RECTANGULAR: conv->noise_method = NOISE_METHOD_TRIANGULAR; SPA_FALLTHROUGH; case NOISE_METHOD_TRIANGULAR: case NOISE_METHOD_TRIANGULAR_HF: /* Amplify the random noise, with additional * N-1 bits of noise. */ conv->scale *= (1 << (conv->noise_bits-1)); break; } } /* RECTANGULAR dither goes from [-0.5, 0.5] */ if (conv->noise_method < NOISE_METHOD_TRIANGULAR) conv->scale *= 0.5f; conv_flags = 0; if (conv->noise_method != NOISE_METHOD_NONE) conv_flags |= CONV_NOISE; if (dinfo->n_ns > 0) { conv_flags |= CONV_SHAPE; conv->n_ns = dinfo->n_ns; conv->ns = dinfo->ns; } info = find_conv_info(conv->src_fmt, conv->dst_fmt, conv->n_channels, conv->cpu_flags, conv_flags); if (info == NULL) return -ENOTSUP; ninfo = find_noise_info(conv->noise_method, conv->cpu_flags); if (ninfo == NULL) return -ENOTSUP; cinfo = find_clear_info(conv->dst_fmt, conv->cpu_flags); conv->noise_size = NOISE_SIZE; data_size[0] = SPA_ROUND_UP(conv->noise_size * sizeof(float), FMT_OPS_MAX_ALIGN); data_size[1] = SPA_ROUND_UP(RANDOM_SIZE * sizeof(uint32_t), FMT_OPS_MAX_ALIGN); data_size[2] = SPA_ROUND_UP(RANDOM_SIZE * sizeof(int32_t), FMT_OPS_MAX_ALIGN); data_size[3] = SPA_ROUND_UP(conv->n_channels * sizeof(struct shaper), FMT_OPS_MAX_ALIGN); conv->data = calloc(FMT_OPS_MAX_ALIGN + data_size[0] + data_size[1] + data_size[2] + data_size[3], 1); if (conv->data == NULL) return -errno; conv->noise = SPA_PTR_ALIGN(conv->data, FMT_OPS_MAX_ALIGN, float); conv->random = SPA_PTROFF(conv->noise, data_size[0], uint32_t); conv->prev = SPA_PTROFF(conv->random, data_size[1], int32_t); conv->shaper = SPA_PTROFF(conv->prev, data_size[2], struct shaper); for (i = 0; i < RANDOM_SIZE; i++) conv->random[i] = random(); conv->is_passthrough = conv->src_fmt == conv->dst_fmt; conv->cpu_flags = info->cpu_flags; conv->update_noise = ninfo->noise; conv->process = info->process; conv->clear = cinfo ? cinfo->clear : NULL; conv->free = impl_convert_free; conv->func_name = info->name; return 0; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/audioconvert/fmt-ops.h000066400000000000000000000404121511204443500272030ustar00rootroot00000000000000/* Spa */ /* SPDX-FileCopyrightText: Copyright © 2019 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #define f32_round(a) lrintf(a) #define ITOF(type,v,scale,offs) \ (((type)(v)) * (1.0f / (scale)) - (offs)) #define FTOI(type,v,scale,offs,noise,min,max) \ (type)f32_round(SPA_CLAMPF((v) * (scale) + (offs) + (noise), min, max)) #define FMT_OPS_MAX_ALIGN 32u #define U8_MIN 0u #define U8_MAX 255u #define U8_SCALE 128.f #define U8_OFFS 128.f #define U8_TO_F32(v) ITOF(uint8_t, v, U8_SCALE, 1.0f) #define F32_TO_U8_D(v,d) FTOI(uint8_t, v, U8_SCALE, U8_OFFS, d, U8_MIN, U8_MAX) #define F32_TO_U8(v) F32_TO_U8_D(v, 0.0f) #define S8_MIN -128 #define S8_MAX 127 #define S8_SCALE 128.0f #define S8_TO_F32(v) ITOF(int8_t, v, S8_SCALE, 0.0f) #define F32_TO_S8_D(v,d) FTOI(int8_t, v, S8_SCALE, 0.0f, d, S8_MIN, S8_MAX) #define F32_TO_S8(v) F32_TO_S8_D(v, 0.0f); #define U16_MIN 0u #define U16_MAX 65535u #define U16_SCALE 32768.f #define U16_OFFS 32768.f #define U16_TO_F32(v) ITOF(uint16_t, v, U16_SCALE, 1.0f) #define U16S_TO_F32(v) U16_TO_F32(bswap_16(v)) #define F32_TO_U16_D(v,d) FTOI(uint16_t, v, U16_SCALE, U16_OFFS, d, U16_MIN, U16_MAX) #define F32_TO_U16(v) F32_TO_U16_D(v, 0.0f); #define F32_TO_U16S_D(v,d) bswap_16(F32_TO_U16_D(v,d)) #define F32_TO_U16S(v) bswap_16(F32_TO_U16(v)) #define S16_MIN -32768 #define S16_MAX 32767 #define S16_SCALE 32768.0f #define S16_TO_F32(v) ITOF(int16_t, v, S16_SCALE, 0.0f) #define S16S_TO_F32(v) S16_TO_F32(bswap_16(v)) #define F32_TO_S16_D(v,d) FTOI(int16_t, v, S16_SCALE, 0.0f, d, S16_MIN, S16_MAX) #define F32_TO_S16(v) F32_TO_S16_D(v, 0.0f) #define F32_TO_S16S_D(v,d) bswap_16(F32_TO_S16_D(v,d)) #define F32_TO_S16S(v) bswap_16(F32_TO_S16(v)) #define U24_MIN 0u #define U24_MAX 16777215u #define U24_SCALE 8388608.f #define U24_OFFS 8388608.f #define U24_TO_F32(v) ITOF(uint32_t, u24_to_u32(v), U24_SCALE, 1.0f) #define F32_TO_U24_D(v,d) u32_to_u24(FTOI(uint32_t, v, U24_SCALE, U24_OFFS, d, U24_MIN, U24_MAX)) #define F32_TO_U24(v) F32_TO_U24_D(v, 0.0f) #define S24_MIN -8388608 #define S24_MAX 8388607 #define S24_SCALE 8388608.0f #define S24_TO_F32(v) ITOF(int32_t, s24_to_s32(v), S24_SCALE, 0.0f) #define S24S_TO_F32(v) S24_TO_F32(bswap_s24(v)) #define F32_TO_S24_D(v,d) s32_to_s24(FTOI(int32_t, v, S24_SCALE, 0.0f, d, S24_MIN, S24_MAX)) #define F32_TO_S24(v) F32_TO_S24_D(v, 0.0f) #define F32_TO_S24S(v) bswap_s24(F32_TO_S24(v)) #define U24_32_TO_U32(v) (((uint32_t)(v)) << 8) #define U24_32_TO_F32(v) U32_TO_F32(U24_32_TO_U32(v)) #define U24_32S_TO_F32(v) U24_32_TO_F32(bswap_32(v)) #define F32_TO_U24_32_D(v,d) FTOI(uint32_t, v, U24_SCALE, U24_OFFS, d, U24_MIN, U24_MAX) #define F32_TO_U24_32(v) F32_TO_U24_32_D(v, 0.0f) #define F32_TO_U24_32S(v) bswap_32(F32_TO_U24_32(v)) #define F32_TO_U24_32S_D(v,d) bswap_32(F32_TO_U24_32_D(v,d)) #define U32_TO_U24_32(v) (((uint32_t)(v)) >> 8) #define S25_MIN -16777216 #define S25_MAX 16777215 #define S25_SCALE 16777216.0f #define S25_32_TO_F32(v) ITOF(int32_t, v, S25_SCALE, 0.0f) #define S25_32S_TO_F32(v) S25_32_TO_F32(bswap_32(v)) #define F32_TO_S25_32_D(v,d) FTOI(int32_t, v, S25_SCALE, 0.0f, d, S25_MIN, S25_MAX) #define F32_TO_S25_32(v) F32_TO_S25_32_D(v, 0.0f) #define F32_TO_S25_32S(v) bswap_32(F32_TO_S25_32(v)) #define F32_TO_S25_32S_D(v,d) bswap_32(F32_TO_S25_32_D(v,d)) #define S25_32_TO_S32(v) ((int32_t)(((uint32_t)(v)) << 7)) #define S32_TO_S25_32(v) (((int32_t)(v)) >> 7) #define U32_MIN 0u #define U32_MAX 4294967295u #define U32_SCALE 2147483648.f #define U32_OFFS 2147483648.f #define U32_TO_F32(v) ITOF(uint32_t, U32_TO_U24_32(v), U24_SCALE, 1.0f) #define F32_TO_U32(v) U24_32_TO_U32(F32_TO_U24_32(v)) #define F32_TO_U32_D(v,d) U24_32_TO_U32(F32_TO_U24_32_D(v,d)) #define S24_32_TO_S32(v) ((int32_t)(((uint32_t)(v)) << 8)) #define S24_32_TO_F32(v) S32_TO_F32(S24_32_TO_S32(v)) #define S24_32S_TO_F32(v) S24_32_TO_F32(bswap_32(v)) #define F32_TO_S24_32_D(v,d) FTOI(int32_t, v, S24_SCALE, 0.0f, d, S24_MIN, S24_MAX) #define F32_TO_S24_32(v) F32_TO_S24_32_D(v, 0.0f) #define F32_TO_S24_32S(v) bswap_32(F32_TO_S24_32(v)) #define F32_TO_S24_32S_D(v,d) bswap_32(F32_TO_S24_32_D(v,d)) #define S32_TO_S24_32(v) (((int32_t)(v)) >> 8) #define S32_MIN -2147483648 #define S32_MAX 2147483647 #define S32_SCALE_I2F 2147483648.0f #define S32_TO_F32(v) ITOF(int32_t, v, S32_SCALE_I2F, 0.0f) #define S32S_TO_F32(v) S32_TO_F32(bswap_32(v)) #define S32_MIN_F2I ((int32_t)(((uint32_t)(S25_MIN)) << 7)) #define S32_MAX_F2I ((S25_MAX) << 7) #define S32_SCALE_F2I (-((float)(S32_MIN_F2I))) #define F32_TO_S32_D(v,d) FTOI(int32_t, v, S32_SCALE_F2I, 0.0f, d, S32_MIN_F2I, S32_MAX_F2I) #define F32_TO_S32(v) F32_TO_S32_D(v, 0.0f) #define F32_TO_S32S(v) bswap_32(F32_TO_S32(v)) #define F32_TO_S32S_D(v,d) bswap_32(F32_TO_S32_D(v,d)) typedef struct { #if __BYTE_ORDER == __LITTLE_ENDIAN uint8_t v3; uint8_t v2; uint8_t v1; #else uint8_t v1; uint8_t v2; uint8_t v3; #endif } __attribute__ ((packed)) uint24_t; typedef struct { #if __BYTE_ORDER == __LITTLE_ENDIAN uint8_t v3; uint8_t v2; int8_t v1; #else int8_t v1; uint8_t v2; uint8_t v3; #endif } __attribute__ ((packed)) int24_t; static inline uint32_t u24_to_u32(uint24_t src) { return ((uint32_t)src.v1 << 16) | ((uint32_t)src.v2 << 8) | (uint32_t)src.v3; } #define U32_TO_U24(s) (uint24_t) { .v1 = (uint8_t)(((uint32_t)s) >> 16), \ .v2 = (uint8_t)(((uint32_t)s) >> 8), .v3 = (uint8_t)((uint32_t)s) } static inline uint24_t u32_to_u24(uint32_t src) { return U32_TO_U24(src); } static inline int32_t s24_to_s32(int24_t src) { return ((uint32_t)((int32_t)src.v1 & 0xFFFF) << 16) | ((uint32_t)src.v2 << 8) | (uint32_t)src.v3; } #define S32_TO_S24(s) (int24_t) { .v1 = (int8_t)(((int32_t)s) >> 16), \ .v2 = (uint8_t)(((uint32_t)s) >> 8), .v3 = (uint8_t)((uint32_t)s) } static inline int24_t s32_to_s24(int32_t src) { return S32_TO_S24(src); } static inline uint24_t bswap_u24(uint24_t src) { return (uint24_t) { .v1 = src.v3, .v2 = src.v2, .v3 = src.v1 }; } static inline int24_t bswap_s24(int24_t src) { return (int24_t) { .v1 = src.v3, .v2 = src.v2, .v3 = src.v1 }; } #define F32_TO_F32S(v) \ bswap_32((union { uint32_t i; float f; }){ .f = (v) }.i) #define F32S_TO_F32(v) \ ((union { uint32_t i; float f; }){ .i = bswap_32(v) }.f) #define F64_TO_F64S(v) \ bswap_32((union { uint64_t i; double d; }){ .d = (v) }.i) #define F64S_TO_F64(v) \ ((union { uint64_t i; double d; }){ .i = bswap_32(v) }.d) #define NS_MAX 8 #define NS_MASK (NS_MAX-1) struct shaper { float e[NS_MAX * 2]; uint32_t idx; float r; }; struct convert { uint32_t noise_bits; #define DITHER_METHOD_NONE 0 #define DITHER_METHOD_RECTANGULAR 1 #define DITHER_METHOD_TRIANGULAR 2 #define DITHER_METHOD_TRIANGULAR_HF 3 #define DITHER_METHOD_WANNAMAKER_3 4 #define DITHER_METHOD_LIPSHITZ 5 uint32_t method; uint32_t src_fmt; uint32_t dst_fmt; uint32_t n_channels; uint32_t rate; uint32_t cpu_flags; const char *func_name; unsigned int is_passthrough:1; float scale; uint32_t *random; int32_t *prev; #define NOISE_METHOD_NONE 0 #define NOISE_METHOD_RECTANGULAR 1 #define NOISE_METHOD_TRIANGULAR 2 #define NOISE_METHOD_TRIANGULAR_HF 3 #define NOISE_METHOD_PATTERN 4 uint32_t noise_method; float *noise; uint32_t noise_size; const float *ns; uint32_t n_ns; struct shaper *shaper; void (*update_noise) (struct convert *conv, float *noise, uint32_t n_samples); void (*process) (struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], uint32_t n_samples); void (*clear) (struct convert *conv, void * SPA_RESTRICT dst[], uint32_t n_samples); void (*free) (struct convert *conv); void *data; }; int convert_init(struct convert *conv); static const struct dither_method_info { uint32_t method; const char *label; const char *description; } dither_method_info[] = { [DITHER_METHOD_NONE] = { DITHER_METHOD_NONE, "none", "Disabled", }, [DITHER_METHOD_RECTANGULAR] = { DITHER_METHOD_RECTANGULAR, "rectangular", "Rectangular dithering", }, [DITHER_METHOD_TRIANGULAR] = { DITHER_METHOD_TRIANGULAR, "triangular", "Triangular dithering", }, [DITHER_METHOD_TRIANGULAR_HF] = { DITHER_METHOD_TRIANGULAR_HF, "triangular-hf", "Sloped Triangular dithering", }, [DITHER_METHOD_WANNAMAKER_3] = { DITHER_METHOD_WANNAMAKER_3, "wannamaker3", "Wannamaker 3 dithering", }, [DITHER_METHOD_LIPSHITZ] = { DITHER_METHOD_LIPSHITZ, "shaped5", "Lipshitz 5 dithering", }, }; static inline uint32_t dither_method_from_label(const char *label) { SPA_FOR_EACH_ELEMENT_VAR(dither_method_info, i) { if (spa_streq(i->label, label)) return i->method; } return DITHER_METHOD_NONE; } #define convert_update_noise(conv,...) (conv)->update_noise(conv, __VA_ARGS__) #define convert_process(conv,...) (conv)->process(conv, __VA_ARGS__) #define convert_clear(conv,...) (conv)->clear(conv, __VA_ARGS__) #define convert_free(conv) (conv)->free(conv) #define DEFINE_NOISE_FUNCTION(name,arch) \ void conv_noise_##name##_##arch(struct convert *conv, float *noise, \ uint32_t n_samples) DEFINE_NOISE_FUNCTION(none, c); DEFINE_NOISE_FUNCTION(rect, c); DEFINE_NOISE_FUNCTION(tri, c); DEFINE_NOISE_FUNCTION(tri_hf, c); DEFINE_NOISE_FUNCTION(pattern, c); #if defined(HAVE_SSE2) DEFINE_NOISE_FUNCTION(rect, sse2); DEFINE_NOISE_FUNCTION(tri, sse2); DEFINE_NOISE_FUNCTION(tri_hf, sse2); #endif #undef DEFINE_NOISE_FUNCTION #define DEFINE_FUNCTION(name,arch) \ void conv_##name##_##arch(struct convert *conv, void * SPA_RESTRICT dst[], \ const void * SPA_RESTRICT src[], uint32_t n_samples) DEFINE_FUNCTION(copy8d, c); DEFINE_FUNCTION(copy8, c); DEFINE_FUNCTION(copy16d, c); DEFINE_FUNCTION(copy16, c); DEFINE_FUNCTION(copy24d, c); DEFINE_FUNCTION(copy24, c); DEFINE_FUNCTION(copy32d, c); DEFINE_FUNCTION(copy32, c); DEFINE_FUNCTION(copy64d, c); DEFINE_FUNCTION(copy64, c); DEFINE_FUNCTION(u8d_to_f32d, c); DEFINE_FUNCTION(u8_to_f32, c); DEFINE_FUNCTION(u8_to_f32d, c); DEFINE_FUNCTION(u8d_to_f32, c); DEFINE_FUNCTION(s8d_to_f32d, c); DEFINE_FUNCTION(s8_to_f32, c); DEFINE_FUNCTION(s8_to_f32d, c); DEFINE_FUNCTION(s8d_to_f32, c); DEFINE_FUNCTION(ulaw_to_f32d, c); DEFINE_FUNCTION(alaw_to_f32d, c); DEFINE_FUNCTION(u16_to_f32, c); DEFINE_FUNCTION(u16_to_f32d, c); DEFINE_FUNCTION(s16d_to_f32d, c); DEFINE_FUNCTION(s16_to_f32, c); DEFINE_FUNCTION(s16_to_f32d, c); DEFINE_FUNCTION(s16s_to_f32d, c); DEFINE_FUNCTION(s16d_to_f32, c); DEFINE_FUNCTION(u32_to_f32, c); DEFINE_FUNCTION(u32_to_f32d, c); DEFINE_FUNCTION(s32d_to_f32d, c); DEFINE_FUNCTION(s32_to_f32, c); DEFINE_FUNCTION(s32_to_f32d, c); DEFINE_FUNCTION(s32s_to_f32d, c); DEFINE_FUNCTION(s32d_to_f32, c); DEFINE_FUNCTION(u24_to_f32, c); DEFINE_FUNCTION(u24_to_f32d, c); DEFINE_FUNCTION(s24d_to_f32d, c); DEFINE_FUNCTION(s24_to_f32, c); DEFINE_FUNCTION(s24_to_f32d, c); DEFINE_FUNCTION(s24s_to_f32d, c); DEFINE_FUNCTION(s24d_to_f32, c); DEFINE_FUNCTION(u24_32_to_f32, c); DEFINE_FUNCTION(u24_32_to_f32d, c); DEFINE_FUNCTION(s24_32d_to_f32d, c); DEFINE_FUNCTION(s24_32_to_f32, c); DEFINE_FUNCTION(s24_32_to_f32d, c); DEFINE_FUNCTION(s24_32s_to_f32d, c); DEFINE_FUNCTION(s24_32d_to_f32, c); DEFINE_FUNCTION(f64d_to_f32d, c); DEFINE_FUNCTION(f64_to_f32, c); DEFINE_FUNCTION(f64_to_f32d, c); DEFINE_FUNCTION(f64s_to_f32d, c); DEFINE_FUNCTION(f64d_to_f32, c); DEFINE_FUNCTION(f32d_to_u8d, c); DEFINE_FUNCTION(f32d_to_u8d_noise, c); DEFINE_FUNCTION(f32d_to_u8d_shaped, c); DEFINE_FUNCTION(f32_to_u8, c); DEFINE_FUNCTION(f32_to_u8d, c); DEFINE_FUNCTION(f32d_to_u8, c); DEFINE_FUNCTION(f32d_to_u8_noise, c); DEFINE_FUNCTION(f32d_to_u8_shaped, c); DEFINE_FUNCTION(f32d_to_s8d, c); DEFINE_FUNCTION(f32d_to_s8d_noise, c); DEFINE_FUNCTION(f32d_to_s8d_shaped, c); DEFINE_FUNCTION(f32_to_s8, c); DEFINE_FUNCTION(f32_to_s8d, c); DEFINE_FUNCTION(f32d_to_s8, c); DEFINE_FUNCTION(f32d_to_s8_noise, c); DEFINE_FUNCTION(f32d_to_s8_shaped, c); DEFINE_FUNCTION(f32d_to_alaw, c); DEFINE_FUNCTION(f32d_to_ulaw, c); DEFINE_FUNCTION(f32_to_u16, c); DEFINE_FUNCTION(f32d_to_u16, c); DEFINE_FUNCTION(f32d_to_s16d, c); DEFINE_FUNCTION(f32d_to_s16d_noise, c); DEFINE_FUNCTION(f32d_to_s16d_shaped, c); DEFINE_FUNCTION(f32_to_s16, c); DEFINE_FUNCTION(f32_to_s16d, c); DEFINE_FUNCTION(f32d_to_s16, c); DEFINE_FUNCTION(f32d_to_s16_noise, c); DEFINE_FUNCTION(f32d_to_s16_shaped, c); DEFINE_FUNCTION(f32d_to_s16s, c); DEFINE_FUNCTION(f32d_to_s16s_noise, c); DEFINE_FUNCTION(f32d_to_s16s_shaped, c); DEFINE_FUNCTION(f32_to_u32, c); DEFINE_FUNCTION(f32d_to_u32, c); DEFINE_FUNCTION(f32d_to_s32d, c); DEFINE_FUNCTION(f32d_to_s32d_noise, c); DEFINE_FUNCTION(f32_to_s32, c); DEFINE_FUNCTION(f32_to_s32d, c); DEFINE_FUNCTION(f32d_to_s32, c); DEFINE_FUNCTION(f32d_to_s32_noise, c); DEFINE_FUNCTION(f32d_to_s32s, c); DEFINE_FUNCTION(f32d_to_s32s_noise, c); DEFINE_FUNCTION(f32_to_u24, c); DEFINE_FUNCTION(f32d_to_u24, c); DEFINE_FUNCTION(f32d_to_s24d, c); DEFINE_FUNCTION(f32d_to_s24d_noise, c); DEFINE_FUNCTION(f32_to_s24, c); DEFINE_FUNCTION(f32_to_s24d, c); DEFINE_FUNCTION(f32d_to_s24, c); DEFINE_FUNCTION(f32d_to_s24_noise, c); DEFINE_FUNCTION(f32d_to_s24s, c); DEFINE_FUNCTION(f32d_to_s24s_noise, c); DEFINE_FUNCTION(f32_to_u24_32, c); DEFINE_FUNCTION(f32d_to_u24_32, c); DEFINE_FUNCTION(f32d_to_s24_32d, c); DEFINE_FUNCTION(f32d_to_s24_32d_noise, c); DEFINE_FUNCTION(f32_to_s24_32, c); DEFINE_FUNCTION(f32_to_s24_32d, c); DEFINE_FUNCTION(f32d_to_s24_32, c); DEFINE_FUNCTION(f32d_to_s24_32_noise, c); DEFINE_FUNCTION(f32d_to_s24_32s, c); DEFINE_FUNCTION(f32d_to_s24_32s_noise, c); DEFINE_FUNCTION(f32d_to_f64d, c); DEFINE_FUNCTION(f32_to_f64, c); DEFINE_FUNCTION(f32_to_f64d, c); DEFINE_FUNCTION(f32d_to_f64, c); DEFINE_FUNCTION(f32d_to_f64s, c); DEFINE_FUNCTION(8_to_8d, c); DEFINE_FUNCTION(16_to_16d, c); DEFINE_FUNCTION(24_to_24d, c); DEFINE_FUNCTION(32_to_32d, c); DEFINE_FUNCTION(32s_to_32d, c); DEFINE_FUNCTION(64_to_64d, c); DEFINE_FUNCTION(64s_to_64sd, c); DEFINE_FUNCTION(8d_to_8, c); DEFINE_FUNCTION(16d_to_16, c); DEFINE_FUNCTION(24d_to_24, c); DEFINE_FUNCTION(32d_to_32, c); DEFINE_FUNCTION(32d_to_32s, c); DEFINE_FUNCTION(64d_to_64, c); DEFINE_FUNCTION(64sd_to_64s, c); #if defined(HAVE_NEON) DEFINE_FUNCTION(s16_to_f32d_2, neon); DEFINE_FUNCTION(s16_to_f32d, neon); DEFINE_FUNCTION(f32d_to_s16, neon); #endif #if defined(HAVE_RVV) DEFINE_FUNCTION(f32d_to_s32, rvv); DEFINE_FUNCTION(f32_to_s16, rvv); DEFINE_FUNCTION(f32d_to_s16d, rvv); DEFINE_FUNCTION(f32d_to_s16, rvv); DEFINE_FUNCTION(s16_to_f32d, rvv); DEFINE_FUNCTION(s32_to_f32d, rvv); #endif #if defined(HAVE_SSE2) DEFINE_FUNCTION(s16_to_f32d_2, sse2); DEFINE_FUNCTION(s16_to_f32d, sse2); DEFINE_FUNCTION(s16s_to_f32d, sse2); DEFINE_FUNCTION(s16s_to_f32d_2, sse2); DEFINE_FUNCTION(s24_to_f32d, sse2); DEFINE_FUNCTION(s32_to_f32d, sse2); DEFINE_FUNCTION(f32d_to_s32, sse2); DEFINE_FUNCTION(f32d_to_s32_noise, sse2); DEFINE_FUNCTION(f32_to_s16, sse2); DEFINE_FUNCTION(f32d_to_s16_2, sse2); DEFINE_FUNCTION(f32d_to_s16, sse2); DEFINE_FUNCTION(f32d_to_s16s_2, sse2); DEFINE_FUNCTION(f32d_to_s16s, sse2); DEFINE_FUNCTION(f32d_to_s16_noise, sse2); DEFINE_FUNCTION(f32d_to_s16d, sse2); DEFINE_FUNCTION(f32d_to_s16d_noise, sse2); DEFINE_FUNCTION(32_to_32d, sse2); DEFINE_FUNCTION(32s_to_32d, sse2); DEFINE_FUNCTION(32d_to_32, sse2); DEFINE_FUNCTION(32d_to_32s, sse2); #endif #if defined(HAVE_SSSE3) DEFINE_FUNCTION(s24_to_f32d, ssse3); #endif #if defined(HAVE_SSE41) DEFINE_FUNCTION(s24_to_f32d, sse41); #endif #if defined(HAVE_AVX2) DEFINE_FUNCTION(s16_to_f32d_2, avx2); DEFINE_FUNCTION(s16_to_f32d, avx2); DEFINE_FUNCTION(s16s_to_f32d, avx2); DEFINE_FUNCTION(s16s_to_f32d_2, avx2); DEFINE_FUNCTION(s24_to_f32d, avx2); DEFINE_FUNCTION(s32_to_f32d, avx2); DEFINE_FUNCTION(f32d_to_s32, avx2); DEFINE_FUNCTION(f32d_to_s16_4, avx2); DEFINE_FUNCTION(f32d_to_s16_2, avx2); DEFINE_FUNCTION(f32d_to_s16, avx2); #endif #undef DEFINE_FUNCTION #define DEFINE_CLEAR_FUNCTION(name,arch) \ void conv_clear_##name##_##arch(struct convert *conv, void * SPA_RESTRICT dst[], \ uint32_t n_samples) DEFINE_CLEAR_FUNCTION(alaw, c); DEFINE_CLEAR_FUNCTION(ulaw, c); DEFINE_CLEAR_FUNCTION(8, c); DEFINE_CLEAR_FUNCTION(8d, c); DEFINE_CLEAR_FUNCTION(16, c); DEFINE_CLEAR_FUNCTION(16d, c); DEFINE_CLEAR_FUNCTION(24, c); DEFINE_CLEAR_FUNCTION(24d, c); DEFINE_CLEAR_FUNCTION(32, c); DEFINE_CLEAR_FUNCTION(32d, c); DEFINE_CLEAR_FUNCTION(64, c); DEFINE_CLEAR_FUNCTION(64d, c); DEFINE_CLEAR_FUNCTION(u8, c); DEFINE_CLEAR_FUNCTION(u8d, c); DEFINE_CLEAR_FUNCTION(u16, c); DEFINE_CLEAR_FUNCTION(u24, c); DEFINE_CLEAR_FUNCTION(u24_32, c); DEFINE_CLEAR_FUNCTION(u32, c); #undef DEFINE_CLEAR_FUNCTION pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/audioconvert/hilbert.h000066400000000000000000000020131511204443500272420ustar00rootroot00000000000000/* Hilbert function */ /* SPDX-FileCopyrightText: Copyright © 2021 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef HILBERT_H #define HILBERT_H #include #include #include #ifdef __cplusplus extern "C" { #endif static inline void blackman_window(float *taps, int n_taps) { int n; for (n = 0; n < n_taps; n++) { float w = 2.0f * (float)M_PI * n / (n_taps-1); taps[n] = 0.3635819f - 0.4891775f * cosf(w) + 0.1365995f * cosf(2 * w) - 0.0106411f * cosf(3 * w); } } static inline int hilbert_generate(float *taps, int n_taps) { int i; if ((n_taps & 1) == 0) return -EINVAL; for (i = 0; i < n_taps; i++) { int k = -(n_taps / 2) + i; if (k & 1) { float pk = (float)M_PI * k; taps[i] *= (1.0f - cosf(pk)) / pk; } else { taps[i] = 0.0f; } } return 0; } static inline void reverse_taps(float *taps, int n_taps) { int i; for (i = 0; i < n_taps/2; i++) SPA_SWAP(taps[i], taps[n_taps-1-i]); } #ifdef __cplusplus } /* extern "C" */ #endif #endif /* HILBERT_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/audioconvert/law.h000066400000000000000000004672051511204443500264160ustar00rootroot00000000000000/* Spa */ /* SPDX-FileCopyrightText: Copyright © 2021 Wim Taymans */ /* SPDX-License-Identifier: MIT */ static inline float alaw_to_f32(uint8_t alawbyte) { static int16_t alaw_decode[256] = { -5504, -5248, -6016, -5760, -4480, -4224, -4992, -4736, -7552, -7296, -8064, -7808, -6528, -6272, -7040, -6784, -2752, -2624, -3008, -2880, -2240, -2112, -2496, -2368, -3776, -3648, -4032, -3904, -3264, -3136, -3520, -3392, -22016, -20992, -24064, -23040, -17920, -16896, -19968, -18944, -30208, -29184, -32256, -31232, -26112, -25088, -28160, -27136, -11008, -10496, -12032, -11520, -8960, -8448, -9984, -9472, -15104, -14592, -16128, -15616, -13056, -12544, -14080, -13568, -344, -328, -376, -360, -280, -264, -312, -296, -472, -456, -504, -488, -408, -392, -440, -424, -88, -72, -120, -104, -24, -8, -56, -40, -216, -200, -248, -232, -152, -136, -184, -168, -1376, -1312, -1504, -1440, -1120, -1056, -1248, -1184, -1888, -1824, -2016, -1952, -1632, -1568, -1760, -1696, -688, -656, -752, -720, -560, -528, -624, -592, -944, -912, -1008, -976, -816, -784, -880, -848, 5504, 5248, 6016, 5760, 4480, 4224, 4992, 4736, 7552, 7296, 8064, 7808, 6528, 6272, 7040, 6784, 2752, 2624, 3008, 2880, 2240, 2112, 2496, 2368, 3776, 3648, 4032, 3904, 3264, 3136, 3520, 3392, 22016, 20992, 24064, 23040, 17920, 16896, 19968, 18944, 30208, 29184, 32256, 31232, 26112, 25088, 28160, 27136, 11008, 10496, 12032, 11520, 8960, 8448, 9984, 9472, 15104, 14592, 16128, 15616, 13056, 12544, 14080, 13568, 344, 328, 376, 360, 280, 264, 312, 296, 472, 456, 504, 488, 408, 392, 440, 424, 88, 72, 120, 104, 24, 8, 56, 40, 216, 200, 248, 232, 152, 136, 184, 168, 1376, 1312, 1504, 1440, 1120, 1056, 1248, 1184, 1888, 1824, 2016, 1952, 1632, 1568, 1760, 1696, 688, 656, 752, 720, 560, 528, 624, 592, 944, 912, 1008, 976, 816, 784, 880, 848 }; return S16_TO_F32(alaw_decode[alawbyte]); } static inline float ulaw_to_f32(uint8_t ulawbyte) { static int16_t ulaw_decode[256] = { -32124, -31100, -30076, -29052, -28028, -27004, -25980, -24956, -23932, -22908, -21884, -20860, -19836, -18812, -17788, -16764, -15996, -15484, -14972, -14460, -13948, -13436, -12924, -12412, -11900, -11388, -10876, -10364, -9852, -9340, -8828, -8316, -7932, -7676, -7420, -7164, -6908, -6652, -6396, -6140, -5884, -5628, -5372, -5116, -4860, -4604, -4348, -4092, -3900, -3772, -3644, -3516, -3388, -3260, -3132, -3004, -2876, -2748, -2620, -2492, -2364, -2236, -2108, -1980, -1884, -1820, -1756, -1692, -1628, -1564, -1500, -1436, -1372, -1308, -1244, -1180, -1116, -1052, -988, -924, -876, -844, -812, -780, -748, -716, -684, -652, -620, -588, -556, -524, -492, -460, -428, -396, -372, -356, -340, -324, -308, -292, -276, -260, -244, -228, -212, -196, -180, -164, -148, -132, -120, -112, -104, -96, -88, -80, -72, -64, -56, -48, -40, -32, -24, -16, -8, 0, 32124, 31100, 30076, 29052, 28028, 27004, 25980, 24956, 23932, 22908, 21884, 20860, 19836, 18812, 17788, 16764, 15996, 15484, 14972, 14460, 13948, 13436, 12924, 12412, 11900, 11388, 10876, 10364, 9852, 9340, 8828, 8316, 7932, 7676, 7420, 7164, 6908, 6652, 6396, 6140, 5884, 5628, 5372, 5116, 4860, 4604, 4348, 4092, 3900, 3772, 3644, 3516, 3388, 3260, 3132, 3004, 2876, 2748, 2620, 2492, 2364, 2236, 2108, 1980, 1884, 1820, 1756, 1692, 1628, 1564, 1500, 1436, 1372, 1308, 1244, 1180, 1116, 1052, 988, 924, 876, 844, 812, 780, 748, 716, 684, 652, 620, 588, 556, 524, 492, 460, 428, 396, 372, 356, 340, 324, 308, 292, 276, 260, 244, 228, 212, 196, 180, 164, 148, 132, 120, 112, 104, 96, 88, 80, 72, 64, 56, 48, 40, 32, 24, 16, 8, 0 }; return S16_TO_F32(ulaw_decode[ulawbyte]); } static inline uint8_t f32_to_alaw(float val) { static uint8_t alaw_encode[0x2000] = { 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6b, 0x6b, 0x6b, 0x6b, 0x6b, 0x6b, 0x6b, 0x6b, 0x68, 0x68, 0x68, 0x68, 0x68, 0x68, 0x68, 0x68, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x6e, 0x6e, 0x6e, 0x6e, 0x6e, 0x6e, 0x6e, 0x6e, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6d, 0x6d, 0x6d, 0x6d, 0x6d, 0x6d, 0x6d, 0x6d, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x67, 0x67, 0x67, 0x67, 0x67, 0x67, 0x67, 0x67, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x7a, 0x7a, 0x7a, 0x7a, 0x7b, 0x7b, 0x7b, 0x7b, 0x78, 0x78, 0x78, 0x78, 0x79, 0x79, 0x79, 0x79, 0x7e, 0x7e, 0x7e, 0x7e, 0x7f, 0x7f, 0x7f, 0x7f, 0x7c, 0x7c, 0x7c, 0x7c, 0x7d, 0x7d, 0x7d, 0x7d, 0x72, 0x72, 0x72, 0x72, 0x73, 0x73, 0x73, 0x73, 0x70, 0x70, 0x70, 0x70, 0x71, 0x71, 0x71, 0x71, 0x76, 0x76, 0x76, 0x76, 0x77, 0x77, 0x77, 0x77, 0x74, 0x74, 0x74, 0x74, 0x75, 0x75, 0x75, 0x75, 0x4a, 0x4a, 0x4b, 0x4b, 0x48, 0x48, 0x49, 0x49, 0x4e, 0x4e, 0x4f, 0x4f, 0x4c, 0x4c, 0x4d, 0x4d, 0x42, 0x42, 0x43, 0x43, 0x40, 0x40, 0x41, 0x41, 0x46, 0x46, 0x47, 0x47, 0x44, 0x44, 0x45, 0x45, 0x5a, 0x5a, 0x5b, 0x5b, 0x58, 0x58, 0x59, 0x59, 0x5e, 0x5e, 0x5f, 0x5f, 0x5c, 0x5c, 0x5d, 0x5d, 0x52, 0x52, 0x53, 0x53, 0x50, 0x50, 0x51, 0x51, 0x56, 0x56, 0x57, 0x57, 0x54, 0x54, 0x55, 0x55, 0xd5, 0xd5, 0xd4, 0xd4, 0xd7, 0xd7, 0xd6, 0xd6, 0xd1, 0xd1, 0xd0, 0xd0, 0xd3, 0xd3, 0xd2, 0xd2, 0xdd, 0xdd, 0xdc, 0xdc, 0xdf, 0xdf, 0xde, 0xde, 0xd9, 0xd9, 0xd8, 0xd8, 0xdb, 0xdb, 0xda, 0xda, 0xc5, 0xc5, 0xc4, 0xc4, 0xc7, 0xc7, 0xc6, 0xc6, 0xc1, 0xc1, 0xc0, 0xc0, 0xc3, 0xc3, 0xc2, 0xc2, 0xcd, 0xcd, 0xcc, 0xcc, 0xcf, 0xcf, 0xce, 0xce, 0xc9, 0xc9, 0xc8, 0xc8, 0xcb, 0xcb, 0xca, 0xca, 0xf5, 0xf5, 0xf5, 0xf5, 0xf4, 0xf4, 0xf4, 0xf4, 0xf7, 0xf7, 0xf7, 0xf7, 0xf6, 0xf6, 0xf6, 0xf6, 0xf1, 0xf1, 0xf1, 0xf1, 0xf0, 0xf0, 0xf0, 0xf0, 0xf3, 0xf3, 0xf3, 0xf3, 0xf2, 0xf2, 0xf2, 0xf2, 0xfd, 0xfd, 0xfd, 0xfd, 0xfc, 0xfc, 0xfc, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xfe, 0xfe, 0xfe, 0xf9, 0xf9, 0xf9, 0xf9, 0xf8, 0xf8, 0xf8, 0xf8, 0xfb, 0xfb, 0xfb, 0xfb, 0xfa, 0xfa, 0xfa, 0xfa, 0xe5, 0xe5, 0xe5, 0xe5, 0xe5, 0xe5, 0xe5, 0xe5, 0xe4, 0xe4, 0xe4, 0xe4, 0xe4, 0xe4, 0xe4, 0xe4, 0xe7, 0xe7, 0xe7, 0xe7, 0xe7, 0xe7, 0xe7, 0xe7, 0xe6, 0xe6, 0xe6, 0xe6, 0xe6, 0xe6, 0xe6, 0xe6, 0xe1, 0xe1, 0xe1, 0xe1, 0xe1, 0xe1, 0xe1, 0xe1, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe3, 0xe3, 0xe3, 0xe3, 0xe3, 0xe3, 0xe3, 0xe3, 0xe2, 0xe2, 0xe2, 0xe2, 0xe2, 0xe2, 0xe2, 0xe2, 0xed, 0xed, 0xed, 0xed, 0xed, 0xed, 0xed, 0xed, 0xec, 0xec, 0xec, 0xec, 0xec, 0xec, 0xec, 0xec, 0xef, 0xef, 0xef, 0xef, 0xef, 0xef, 0xef, 0xef, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xe9, 0xe9, 0xe9, 0xe9, 0xe9, 0xe9, 0xe9, 0xe9, 0xe8, 0xe8, 0xe8, 0xe8, 0xe8, 0xe8, 0xe8, 0xe8, 0xeb, 0xeb, 0xeb, 0xeb, 0xeb, 0xeb, 0xeb, 0xeb, 0xea, 0xea, 0xea, 0xea, 0xea, 0xea, 0xea, 0xea, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa }; return alaw_encode[(F32_TO_S16(val)>>3) + 0x1000]; } static inline uint8_t f32_to_ulaw(float val) { static uint8_t ulaw_encode[0x4000] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x43, 0x43, 0x43, 0x43, 0x43, 0x43, 0x43, 0x43, 0x43, 0x43, 0x43, 0x43, 0x43, 0x43, 0x43, 0x43, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x45, 0x45, 0x45, 0x45, 0x45, 0x45, 0x45, 0x45, 0x45, 0x45, 0x45, 0x45, 0x45, 0x45, 0x45, 0x45, 0x46, 0x46, 0x46, 0x46, 0x46, 0x46, 0x46, 0x46, 0x46, 0x46, 0x46, 0x46, 0x46, 0x46, 0x46, 0x46, 0x47, 0x47, 0x47, 0x47, 0x47, 0x47, 0x47, 0x47, 0x47, 0x47, 0x47, 0x47, 0x47, 0x47, 0x47, 0x47, 0x48, 0x48, 0x48, 0x48, 0x48, 0x48, 0x48, 0x48, 0x48, 0x48, 0x48, 0x48, 0x48, 0x48, 0x48, 0x48, 0x49, 0x49, 0x49, 0x49, 0x49, 0x49, 0x49, 0x49, 0x49, 0x49, 0x49, 0x49, 0x49, 0x49, 0x49, 0x49, 0x4a, 0x4a, 0x4a, 0x4a, 0x4a, 0x4a, 0x4a, 0x4a, 0x4a, 0x4a, 0x4a, 0x4a, 0x4a, 0x4a, 0x4a, 0x4a, 0x4b, 0x4b, 0x4b, 0x4b, 0x4b, 0x4b, 0x4b, 0x4b, 0x4b, 0x4b, 0x4b, 0x4b, 0x4b, 0x4b, 0x4b, 0x4b, 0x4c, 0x4c, 0x4c, 0x4c, 0x4c, 0x4c, 0x4c, 0x4c, 0x4c, 0x4c, 0x4c, 0x4c, 0x4c, 0x4c, 0x4c, 0x4c, 0x4d, 0x4d, 0x4d, 0x4d, 0x4d, 0x4d, 0x4d, 0x4d, 0x4d, 0x4d, 0x4d, 0x4d, 0x4d, 0x4d, 0x4d, 0x4d, 0x4e, 0x4e, 0x4e, 0x4e, 0x4e, 0x4e, 0x4e, 0x4e, 0x4e, 0x4e, 0x4e, 0x4e, 0x4e, 0x4e, 0x4e, 0x4e, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x50, 0x50, 0x50, 0x50, 0x50, 0x50, 0x50, 0x50, 0x51, 0x51, 0x51, 0x51, 0x51, 0x51, 0x51, 0x51, 0x52, 0x52, 0x52, 0x52, 0x52, 0x52, 0x52, 0x52, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x54, 0x54, 0x54, 0x54, 0x54, 0x54, 0x54, 0x54, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x56, 0x56, 0x56, 0x56, 0x56, 0x56, 0x56, 0x56, 0x57, 0x57, 0x57, 0x57, 0x57, 0x57, 0x57, 0x57, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, 0x59, 0x59, 0x59, 0x59, 0x59, 0x59, 0x59, 0x59, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5b, 0x5b, 0x5b, 0x5b, 0x5b, 0x5b, 0x5b, 0x5b, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5d, 0x5d, 0x5d, 0x5d, 0x5d, 0x5d, 0x5d, 0x5d, 0x5e, 0x5e, 0x5e, 0x5e, 0x5e, 0x5e, 0x5e, 0x5e, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x60, 0x60, 0x60, 0x60, 0x61, 0x61, 0x61, 0x61, 0x62, 0x62, 0x62, 0x62, 0x63, 0x63, 0x63, 0x63, 0x64, 0x64, 0x64, 0x64, 0x65, 0x65, 0x65, 0x65, 0x66, 0x66, 0x66, 0x66, 0x67, 0x67, 0x67, 0x67, 0x68, 0x68, 0x68, 0x68, 0x69, 0x69, 0x69, 0x69, 0x6a, 0x6a, 0x6a, 0x6a, 0x6b, 0x6b, 0x6b, 0x6b, 0x6c, 0x6c, 0x6c, 0x6c, 0x6d, 0x6d, 0x6d, 0x6d, 0x6e, 0x6e, 0x6e, 0x6e, 0x6f, 0x6f, 0x6f, 0x6f, 0x70, 0x70, 0x71, 0x71, 0x72, 0x72, 0x73, 0x73, 0x74, 0x74, 0x75, 0x75, 0x76, 0x76, 0x77, 0x77, 0x78, 0x78, 0x79, 0x79, 0x7a, 0x7a, 0x7b, 0x7b, 0x7c, 0x7c, 0x7d, 0x7d, 0x7e, 0x7e, 0xff, 0xfe, 0xfe, 0xfd, 0xfd, 0xfc, 0xfc, 0xfb, 0xfb, 0xfa, 0xfa, 0xf9, 0xf9, 0xf8, 0xf8, 0xf7, 0xf7, 0xf6, 0xf6, 0xf5, 0xf5, 0xf4, 0xf4, 0xf3, 0xf3, 0xf2, 0xf2, 0xf1, 0xf1, 0xf0, 0xf0, 0xef, 0xef, 0xef, 0xef, 0xee, 0xee, 0xee, 0xee, 0xed, 0xed, 0xed, 0xed, 0xec, 0xec, 0xec, 0xec, 0xeb, 0xeb, 0xeb, 0xeb, 0xea, 0xea, 0xea, 0xea, 0xe9, 0xe9, 0xe9, 0xe9, 0xe8, 0xe8, 0xe8, 0xe8, 0xe7, 0xe7, 0xe7, 0xe7, 0xe6, 0xe6, 0xe6, 0xe6, 0xe5, 0xe5, 0xe5, 0xe5, 0xe4, 0xe4, 0xe4, 0xe4, 0xe3, 0xe3, 0xe3, 0xe3, 0xe2, 0xe2, 0xe2, 0xe2, 0xe1, 0xe1, 0xe1, 0xe1, 0xe0, 0xe0, 0xe0, 0xe0, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xde, 0xde, 0xde, 0xde, 0xde, 0xde, 0xde, 0xde, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdc, 0xdc, 0xdc, 0xdc, 0xdc, 0xdc, 0xdc, 0xdc, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xda, 0xda, 0xda, 0xda, 0xda, 0xda, 0xda, 0xda, 0xd9, 0xd9, 0xd9, 0xd9, 0xd9, 0xd9, 0xd9, 0xd9, 0xd8, 0xd8, 0xd8, 0xd8, 0xd8, 0xd8, 0xd8, 0xd8, 0xd7, 0xd7, 0xd7, 0xd7, 0xd7, 0xd7, 0xd7, 0xd7, 0xd6, 0xd6, 0xd6, 0xd6, 0xd6, 0xd6, 0xd6, 0xd6, 0xd5, 0xd5, 0xd5, 0xd5, 0xd5, 0xd5, 0xd5, 0xd5, 0xd4, 0xd4, 0xd4, 0xd4, 0xd4, 0xd4, 0xd4, 0xd4, 0xd3, 0xd3, 0xd3, 0xd3, 0xd3, 0xd3, 0xd3, 0xd3, 0xd2, 0xd2, 0xd2, 0xd2, 0xd2, 0xd2, 0xd2, 0xd2, 0xd1, 0xd1, 0xd1, 0xd1, 0xd1, 0xd1, 0xd1, 0xd1, 0xd0, 0xd0, 0xd0, 0xd0, 0xd0, 0xd0, 0xd0, 0xd0, 0xcf, 0xcf, 0xcf, 0xcf, 0xcf, 0xcf, 0xcf, 0xcf, 0xcf, 0xcf, 0xcf, 0xcf, 0xcf, 0xcf, 0xcf, 0xcf, 0xce, 0xce, 0xce, 0xce, 0xce, 0xce, 0xce, 0xce, 0xce, 0xce, 0xce, 0xce, 0xce, 0xce, 0xce, 0xce, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcb, 0xcb, 0xcb, 0xcb, 0xcb, 0xcb, 0xcb, 0xcb, 0xcb, 0xcb, 0xcb, 0xcb, 0xcb, 0xcb, 0xcb, 0xcb, 0xca, 0xca, 0xca, 0xca, 0xca, 0xca, 0xca, 0xca, 0xca, 0xca, 0xca, 0xca, 0xca, 0xca, 0xca, 0xca, 0xc9, 0xc9, 0xc9, 0xc9, 0xc9, 0xc9, 0xc9, 0xc9, 0xc9, 0xc9, 0xc9, 0xc9, 0xc9, 0xc9, 0xc9, 0xc9, 0xc8, 0xc8, 0xc8, 0xc8, 0xc8, 0xc8, 0xc8, 0xc8, 0xc8, 0xc8, 0xc8, 0xc8, 0xc8, 0xc8, 0xc8, 0xc8, 0xc7, 0xc7, 0xc7, 0xc7, 0xc7, 0xc7, 0xc7, 0xc7, 0xc7, 0xc7, 0xc7, 0xc7, 0xc7, 0xc7, 0xc7, 0xc7, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc5, 0xc5, 0xc5, 0xc5, 0xc5, 0xc5, 0xc5, 0xc5, 0xc5, 0xc5, 0xc5, 0xc5, 0xc5, 0xc5, 0xc5, 0xc5, 0xc4, 0xc4, 0xc4, 0xc4, 0xc4, 0xc4, 0xc4, 0xc4, 0xc4, 0xc4, 0xc4, 0xc4, 0xc4, 0xc4, 0xc4, 0xc4, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc2, 0xc2, 0xc2, 0xc2, 0xc2, 0xc2, 0xc2, 0xc2, 0xc2, 0xc2, 0xc2, 0xc2, 0xc2, 0xc2, 0xc2, 0xc2, 0xc1, 0xc1, 0xc1, 0xc1, 0xc1, 0xc1, 0xc1, 0xc1, 0xc1, 0xc1, 0xc1, 0xc1, 0xc1, 0xc1, 0xc1, 0xc1, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80 }; return ulaw_encode[(F32_TO_S16(val)>>2) + 0x2000]; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/audioconvert/meson.build000066400000000000000000000153161511204443500276140ustar00rootroot00000000000000audioconvert_sources = [ 'audioadapter.c', 'audioconvert.c', 'plugin.c' ] simd_cargs = [] simd_dependencies = [] opt_flags = [] if host_machine.cpu_family() != 'alpha' opt_flags += '-Ofast' else opt_flags += '-O3' endif audioconvert_c = static_library('audioconvert_c', [ 'channelmix-ops-c.c', 'biquad.c', 'crossover.c', 'volume-ops-c.c', 'peaks-ops-c.c', 'resample-native-c.c', 'fmt-ops-c.c' ], c_args : [ opt_flags ], dependencies : [ spa_dep ], install : false ) simd_dependencies += audioconvert_c if have_sse audioconvert_sse = static_library('audioconvert_sse', ['resample-native-sse.c', 'volume-ops-sse.c', 'peaks-ops-sse.c', 'channelmix-ops-sse.c' ], c_args : [sse_args, opt_flags, '-DHAVE_SSE'], dependencies : [ spa_dep ], install : false ) simd_cargs += ['-DHAVE_SSE'] simd_dependencies += audioconvert_sse endif if have_sse2 audioconvert_sse2 = static_library('audioconvert_sse2', ['fmt-ops-sse2.c' ], c_args : [sse2_args, '-O3', '-DHAVE_SSE2'], dependencies : [ spa_dep ], install : false ) simd_cargs += ['-DHAVE_SSE2'] simd_dependencies += audioconvert_sse2 endif if have_ssse3 audioconvert_ssse3 = static_library('audioconvert_ssse3', ['fmt-ops-ssse3.c', 'resample-native-ssse3.c' ], c_args : [ssse3_args, '-O3', '-DHAVE_SSSE3'], dependencies : [ spa_dep ], install : false ) simd_cargs += ['-DHAVE_SSSE3'] simd_dependencies += audioconvert_ssse3 endif if have_sse41 audioconvert_sse41 = static_library('audioconvert_sse41', ['fmt-ops-sse41.c'], c_args : [sse41_args, '-O3', '-DHAVE_SSE41'], dependencies : [ spa_dep ], install : false ) simd_cargs += ['-DHAVE_SSE41'] simd_dependencies += audioconvert_sse41 endif if have_avx and have_fma audioconvert_avx = static_library('audioconvert_avx', ['resample-native-avx.c'], c_args : [avx_args, fma_args, '-O3', '-DHAVE_AVX', '-DHAVE_FMA'], dependencies : [ spa_dep ], install : false ) simd_cargs += ['-DHAVE_AVX', '-DHAVE_FMA'] simd_dependencies += audioconvert_avx endif if have_avx2 audioconvert_avx2 = static_library('audioconvert_avx2', ['fmt-ops-avx2.c'], c_args : [avx2_args, '-O3', '-DHAVE_AVX2'], dependencies : [ spa_dep ], install : false ) simd_cargs += ['-DHAVE_AVX2'] simd_dependencies += audioconvert_avx2 endif if have_neon audioconvert_neon = static_library('audioconvert_neon', ['resample-native-neon.c', 'fmt-ops-neon.c' ], c_args : [neon_args, '-O3', '-DHAVE_NEON'], dependencies : [ spa_dep ], install : false ) simd_cargs += ['-DHAVE_NEON'] simd_dependencies += audioconvert_neon endif if have_rvv audioconvert_rvv = static_library('audioconvert_rvv', ['fmt-ops-rvv.c' ], c_args : ['-O3', '-DHAVE_RVV'], dependencies : [ spa_dep ], install : false ) simd_cargs += ['-DHAVE_RVV'] simd_dependencies += audioconvert_rvv endif sparesampledumpcoeffs_sources = [ 'resample-native.c', 'resample-native-c.c', 'spa-resample-dump-coeffs.c', ] sparesampledumpcoeffs = executable( 'spa-resample-dump-coeffs', sparesampledumpcoeffs_sources, c_args : [ '-DRESAMPLE_DISABLE_PRECOMP' ], dependencies : [ spa_dep, mathlib_native ], install : false, native : true, ) precomptuples = [] foreach tuple : get_option('resampler-precomp-tuples') precomptuples += '-t ' + tuple endforeach resample_native_precomp_h = custom_target( 'resample-native-precomp.h', output : 'resample-native-precomp.h', capture : true, command : [ sparesampledumpcoeffs, ] + precomptuples ) audioconvert_lib = static_library('audioconvert', ['fmt-ops.c', 'channelmix-ops.c', 'peaks-ops.c', resample_native_precomp_h, 'resample-native.c', 'resample-peaks.c', 'wavfile.c', 'volume-ops.c' ], c_args : [ simd_cargs, '-O3'], link_with : simd_dependencies, include_directories : [configinc], dependencies : [ spa_dep ], install : false ) audioconvert_dep = declare_dependency(link_with: audioconvert_lib) spa_audioconvert_lib = shared_library('spa-audioconvert', audioconvert_sources, c_args : simd_cargs, dependencies : [ spa_dep, mathlib, audioconvert_dep ], install : true, install_dir : spa_plugindir / 'audioconvert') spa_audioconvert_dep = declare_dependency(link_with: spa_audioconvert_lib) test_lib = static_library('test_lib', ['test-source.c' ], c_args : ['-O3'], dependencies : [ spa_dep ], install : false ) test_inc = include_directories('../test') test_apps = [ 'test-audioadapter', 'test-audioconvert', 'test-channelmix', 'test-fmt-ops', 'test-peaks', 'test-resample', 'test-resample-delay', ] foreach a : test_apps test(a, executable(a, a + '.c', dependencies : [ spa_dep, dl_lib, pthread_lib, mathlib, audioconvert_dep, spa_audioconvert_dep ], include_directories : [ configinc, test_inc ], link_with : [ test_lib ], install_rpath : spa_plugindir / 'audioconvert', c_args : [ simd_cargs ], install : installed_tests_enabled, install_dir : installed_tests_execdir / 'audioconvert'), env : [ 'SPA_PLUGIN_DIR=@0@'.format(spa_dep.get_variable('plugindir')), ]) if installed_tests_enabled test_conf = configuration_data() test_conf.set('exec', installed_tests_execdir / 'audioconvert' / a) configure_file( input: installed_tests_template, output: a + '.test', install_dir: installed_tests_metadir / 'audioconvert', configuration: test_conf ) endif endforeach benchmark_apps = [ 'benchmark-fmt-ops', 'benchmark-resample', ] foreach a : benchmark_apps benchmark(a, executable(a, a + '.c', dependencies : [ spa_dep, dl_lib, pthread_lib, mathlib, audioconvert_dep, spa_audioconvert_dep ], include_directories : [ configinc, test_inc ], c_args : [ simd_cargs ], install_rpath : spa_plugindir / 'audioconvert', install : installed_tests_enabled, install_dir : installed_tests_execdir / 'audioconvert'), env : [ 'SPA_PLUGIN_DIR=@0@'.format(spa_dep.get_variable('plugindir')), ]) if installed_tests_enabled test_conf = configuration_data() test_conf.set('exec', installed_tests_execdir / 'audioconvert' / a) configure_file( input: installed_tests_template, output: a + '.test', install_dir: installed_tests_metadir / 'audioconvert', configuration: test_conf ) endif endforeach if sndfile_dep.found() sparesample_sources = [ 'spa-resample.c', ] executable('spa-resample', sparesample_sources, link_with : [ test_lib ], dependencies : [ spa_dep, sndfile_dep, mathlib, audioconvert_dep ], install : true, ) endif pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/audioconvert/peaks-ops-c.c000066400000000000000000000012031511204443500277260ustar00rootroot00000000000000/* Spa */ /* SPDX-FileCopyrightText: Copyright © 2022 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include "peaks-ops.h" void peaks_min_max_c(struct peaks *peaks, const float * SPA_RESTRICT src, uint32_t n_samples, float *min, float *max) { uint32_t n; float t, mi = *min, ma = *max; for (n = 0; n < n_samples; n++) { t = src[n]; mi = fminf(mi, t); ma = fmaxf(ma, t); } *min = mi; *max = ma; } float peaks_abs_max_c(struct peaks *peaks, const float * SPA_RESTRICT src, uint32_t n_samples, float max) { uint32_t n; for (n = 0; n < n_samples; n++) max = fmaxf(fabsf(src[n]), max); return max; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/audioconvert/peaks-ops-sse.c000066400000000000000000000045771511204443500303170ustar00rootroot00000000000000/* Spa */ /* SPDX-FileCopyrightText: Copyright © 2022 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include "peaks-ops.h" static inline float hmin_ps(__m128 val) { __m128 t = _mm_movehl_ps(val, val); t = _mm_min_ps(t, val); val = _mm_shuffle_ps(t, t, 0x55); val = _mm_min_ss(t, val); return _mm_cvtss_f32(val); } static inline float hmax_ps(__m128 val) { __m128 t = _mm_movehl_ps(val, val); t = _mm_max_ps(t, val); val = _mm_shuffle_ps(t, t, 0x55); val = _mm_max_ss(t, val); return _mm_cvtss_f32(val); } void peaks_min_max_sse(struct peaks *peaks, const float * SPA_RESTRICT src, uint32_t n_samples, float *min, float *max) { uint32_t n; __m128 in; __m128 mi = _mm_set1_ps(*min); __m128 ma = _mm_set1_ps(*max); for (n = 0; n < n_samples; n++) { if (SPA_IS_ALIGNED(&src[n], 16)) break; in = _mm_set1_ps(src[n]); mi = _mm_min_ps(mi, in); ma = _mm_max_ps(ma, in); } for (; n + 15 < n_samples; n += 16) { in = _mm_load_ps(&src[n + 0]); mi = _mm_min_ps(mi, in); ma = _mm_max_ps(ma, in); in = _mm_load_ps(&src[n + 4]); mi = _mm_min_ps(mi, in); ma = _mm_max_ps(ma, in); in = _mm_load_ps(&src[n + 8]); mi = _mm_min_ps(mi, in); ma = _mm_max_ps(ma, in); in = _mm_load_ps(&src[n + 12]); mi = _mm_min_ps(mi, in); ma = _mm_max_ps(ma, in); } for (; n < n_samples; n++) { in = _mm_set1_ps(src[n]); mi = _mm_min_ps(mi, in); ma = _mm_max_ps(ma, in); } *min = hmin_ps(mi); *max = hmax_ps(ma); } float peaks_abs_max_sse(struct peaks *peaks, const float * SPA_RESTRICT src, uint32_t n_samples, float max) { uint32_t n; __m128 in; __m128 ma = _mm_set1_ps(max); const __m128 mask = _mm_set1_ps(-0.0f); for (n = 0; n < n_samples; n++) { if (SPA_IS_ALIGNED(&src[n], 16)) break; in = _mm_set1_ps(src[n]); in = _mm_andnot_ps(mask, in); ma = _mm_max_ps(ma, in); } for (; n + 15 < n_samples; n += 16) { in = _mm_load_ps(&src[n + 0]); in = _mm_andnot_ps(mask, in); ma = _mm_max_ps(ma, in); in = _mm_load_ps(&src[n + 4]); in = _mm_andnot_ps(mask, in); ma = _mm_max_ps(ma, in); in = _mm_load_ps(&src[n + 8]); in = _mm_andnot_ps(mask, in); ma = _mm_max_ps(ma, in); in = _mm_load_ps(&src[n + 12]); in = _mm_andnot_ps(mask, in); ma = _mm_max_ps(ma, in); } for (; n < n_samples; n++) { in = _mm_set1_ps(src[n]); in = _mm_andnot_ps(mask, in); ma = _mm_max_ps(ma, in); } return hmax_ps(ma); } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/audioconvert/peaks-ops.c000066400000000000000000000031471511204443500275170ustar00rootroot00000000000000/* Spa */ /* SPDX-FileCopyrightText: Copyright © 2022 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #include #include #include #include "peaks-ops.h" typedef void (*peaks_min_max_func_t) (struct peaks *peaks, const float * SPA_RESTRICT src, uint32_t n_samples, float *min, float *max); typedef float (*peaks_abs_max_func_t) (struct peaks *peaks, const float * SPA_RESTRICT src, uint32_t n_samples, float max); #define MAKE(min_max,abs_max,...) \ { min_max, abs_max, #min_max , __VA_ARGS__ } static const struct peaks_info { peaks_min_max_func_t min_max; peaks_abs_max_func_t abs_max; const char *name; uint32_t cpu_flags; } peaks_table[] = { #if defined (HAVE_SSE) MAKE(peaks_min_max_sse, peaks_abs_max_sse, SPA_CPU_FLAG_SSE), #endif MAKE(peaks_min_max_c, peaks_abs_max_c), }; #undef MAKE #define MATCH_CPU_FLAGS(a,b) ((a) == 0 || ((a) & (b)) == a) static const struct peaks_info *find_peaks_info(uint32_t cpu_flags) { SPA_FOR_EACH_ELEMENT_VAR(peaks_table, t) { if (MATCH_CPU_FLAGS(t->cpu_flags, cpu_flags)) return t; } return NULL; } static void impl_peaks_free(struct peaks *peaks) { peaks->min_max = NULL; peaks->abs_max = NULL; } int peaks_init(struct peaks *peaks) { const struct peaks_info *info; info = find_peaks_info(peaks->cpu_flags); if (info == NULL) return -ENOTSUP; peaks->cpu_flags = info->cpu_flags; peaks->func_name = info->name; peaks->free = impl_peaks_free; peaks->min_max = info->min_max; peaks->abs_max = info->abs_max; return 0; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/audioconvert/peaks-ops.h000066400000000000000000000027561511204443500275310ustar00rootroot00000000000000/* Spa */ /* SPDX-FileCopyrightText: Copyright © 2022 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #undef SPA_LOG_TOPIC_DEFAULT #define SPA_LOG_TOPIC_DEFAULT &resample_log_topic extern struct spa_log_topic resample_log_topic; struct peaks { uint32_t cpu_flags; const char *func_name; struct spa_log *log; uint32_t flags; void (*min_max) (struct peaks *peaks, const float * SPA_RESTRICT src, uint32_t n_samples, float *min, float *max); float (*abs_max) (struct peaks *peaks, const float * SPA_RESTRICT src, uint32_t n_samples, float max); void (*free) (struct peaks *peaks); }; int peaks_init(struct peaks *peaks); #define peaks_min_max(peaks,...) (peaks)->min_max(peaks, __VA_ARGS__) #define peaks_abs_max(peaks,...) (peaks)->abs_max(peaks, __VA_ARGS__) #define peaks_free(peaks) (peaks)->free(peaks) #define DEFINE_MIN_MAX_FUNCTION(arch) \ void peaks_min_max_##arch(struct peaks *peaks, \ const float * SPA_RESTRICT src, \ uint32_t n_samples, float *min, float *max); #define DEFINE_ABS_MAX_FUNCTION(arch) \ float peaks_abs_max_##arch(struct peaks *peaks, \ const float * SPA_RESTRICT src, \ uint32_t n_samples, float max); #define PEAKS_OPS_MAX_ALIGN 16 DEFINE_MIN_MAX_FUNCTION(c); DEFINE_ABS_MAX_FUNCTION(c); #if defined (HAVE_SSE) DEFINE_MIN_MAX_FUNCTION(sse); DEFINE_ABS_MAX_FUNCTION(sse); #endif #undef DEFINE_MIN_MAX_FUNCTION #undef DEFINE_ABS_MAX_FUNCTION pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/audioconvert/plugin.c000066400000000000000000000013761511204443500271150ustar00rootroot00000000000000/* Spa Audioconvert plugin */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include extern const struct spa_handle_factory spa_audioconvert_factory; extern const struct spa_handle_factory spa_audioadapter_factory; SPA_LOG_TOPIC_ENUM_DEFINE_REGISTERED; SPA_EXPORT int spa_handle_factory_enum(const struct spa_handle_factory **factory, uint32_t *index) { spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(index != NULL, -EINVAL); switch (*index) { case 0: *factory = &spa_audioconvert_factory; break; case 1: *factory = &spa_audioadapter_factory; break; default: return 0; } (*index)++; return 1; } resample-native-avx.c000066400000000000000000000050601511204443500314220ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/audioconvert/* Spa */ /* SPDX-FileCopyrightText: Copyright © 2019 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include "resample-native-impl.h" #include #include static inline void inner_product_avx(float *d, const float * SPA_RESTRICT s, const float * SPA_RESTRICT taps, uint32_t n_taps) { __m256 sy[2] = { _mm256_setzero_ps(), _mm256_setzero_ps() }, ty; __m128 sx[2], tx; uint32_t i = 0; uint32_t n_taps4 = n_taps & ~0xf; for (; i < n_taps4; i += 16) { ty = _mm256_loadu_ps(s + i + 0); sy[0] = _mm256_fmadd_ps(ty, _mm256_load_ps(taps + i + 0), sy[0]); ty = _mm256_loadu_ps(s + i + 8); sy[1] = _mm256_fmadd_ps(ty, _mm256_load_ps(taps + i + 8), sy[1]); } sy[0] = _mm256_add_ps(sy[1], sy[0]); sx[1] = _mm256_extractf128_ps(sy[0], 1); sx[0] = _mm256_extractf128_ps(sy[0], 0); for (; i < n_taps; i += 8) { tx = _mm_loadu_ps(s + i + 0); sx[0] = _mm_fmadd_ps(tx, _mm_load_ps(taps + i + 0), sx[0]); tx = _mm_loadu_ps(s + i + 4); sx[1] = _mm_fmadd_ps(tx, _mm_load_ps(taps + i + 4), sx[1]); } sx[0] = _mm_add_ps(sx[0], sx[1]); sx[0] = _mm_hadd_ps(sx[0], sx[0]); sx[0] = _mm_hadd_ps(sx[0], sx[0]); _mm_store_ss(d, sx[0]); } static inline void inner_product_ip_avx(float *d, const float * SPA_RESTRICT s, const float * SPA_RESTRICT t0, const float * SPA_RESTRICT t1, float x, uint32_t n_taps) { __m256 sy[2] = { _mm256_setzero_ps(), _mm256_setzero_ps() }, ty; __m128 sx[2], tx; uint32_t i, n_taps4 = n_taps & ~0xf; for (i = 0; i < n_taps4; i += 16) { ty = _mm256_loadu_ps(s + i + 0); sy[0] = _mm256_fmadd_ps(ty, _mm256_load_ps(t0 + i + 0), sy[0]); sy[1] = _mm256_fmadd_ps(ty, _mm256_load_ps(t1 + i + 0), sy[1]); ty = _mm256_loadu_ps(s + i + 8); sy[0] = _mm256_fmadd_ps(ty, _mm256_load_ps(t0 + i + 8), sy[0]); sy[1] = _mm256_fmadd_ps(ty, _mm256_load_ps(t1 + i + 8), sy[1]); } sx[0] = _mm_add_ps(_mm256_extractf128_ps(sy[0], 0), _mm256_extractf128_ps(sy[0], 1)); sx[1] = _mm_add_ps(_mm256_extractf128_ps(sy[1], 0), _mm256_extractf128_ps(sy[1], 1)); for (; i < n_taps; i += 8) { tx = _mm_loadu_ps(s + i + 0); sx[0] = _mm_fmadd_ps(tx, _mm_load_ps(t0 + i + 0), sx[0]); sx[1] = _mm_fmadd_ps(tx, _mm_load_ps(t1 + i + 0), sx[1]); tx = _mm_loadu_ps(s + i + 4); sx[0] = _mm_fmadd_ps(tx, _mm_load_ps(t0 + i + 4), sx[0]); sx[1] = _mm_fmadd_ps(tx, _mm_load_ps(t1 + i + 4), sx[1]); } sx[1] = _mm_mul_ps(_mm_sub_ps(sx[1], sx[0]), _mm_load1_ps(&x)); sx[0] = _mm_add_ps(sx[0], sx[1]); sx[0] = _mm_hadd_ps(sx[0], sx[0]); sx[0] = _mm_hadd_ps(sx[0], sx[0]); _mm_store_ss(d, sx[0]); } MAKE_RESAMPLER_FULL(avx); MAKE_RESAMPLER_INTER(avx); resample-native-c.c000066400000000000000000000020731511204443500310470ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/audioconvert/* Spa */ /* SPDX-FileCopyrightText: Copyright © 2019 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include "resample-native-impl.h" static inline void inner_product_c(float *d, const float * SPA_RESTRICT s, const float * SPA_RESTRICT taps, uint32_t n_taps) { float sum = 0.0f; #if 1 uint32_t i, j, nt2 = n_taps/2; for (i = 0, j = n_taps-1; i < nt2; i++, j--) sum += s[i] * taps[i] + s[j] * taps[j]; #else uint32_t i; for (i = 0; i < n_taps; i++) sum += s[i] * taps[i]; #endif *d = sum; } static inline void inner_product_ip_c(float *d, const float * SPA_RESTRICT s, const float * SPA_RESTRICT t0, const float * SPA_RESTRICT t1, float x, uint32_t n_taps) { float sum[2] = { 0.0f, 0.0f }; uint32_t i; #if 1 uint32_t j, nt2 = n_taps/2; for (i = 0, j = n_taps-1; i < nt2; i++, j--) { sum[0] += s[i] * t0[i] + s[j] * t0[j]; sum[1] += s[i] * t1[i] + s[j] * t1[j]; } #else for (i = 0; i < n_taps; i++) { sum[0] += s[i] * t0[i]; sum[1] += s[i] * t1[i]; } #endif *d = (sum[1] - sum[0]) * x + sum[0]; } MAKE_RESAMPLER_FULL(c); MAKE_RESAMPLER_INTER(c); resample-native-impl.h000066400000000000000000000123231511204443500315720ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/audioconvert/* Spa */ /* SPDX-FileCopyrightText: Copyright © 2019 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include #include "resample.h" #undef SPA_LOG_TOPIC_DEFAULT #define SPA_LOG_TOPIC_DEFAULT &resample_log_topic extern struct spa_log_topic resample_log_topic; typedef void (*resample_func_t)(struct resample *r, const void * SPA_RESTRICT src[], uint32_t ioffs, uint32_t *in_len, void * SPA_RESTRICT dst[], uint32_t ooffs, uint32_t *out_len); #define FIXP_SHIFT 32 #define FIXP_SCALE ((uint64_t)1 << FIXP_SHIFT) #define FIXP_MASK (FIXP_SCALE - 1) #define UINT32_TO_FIXP(v) ((struct fixp) { (uint64_t)((uint32_t)(v)) << FIXP_SHIFT }) #define FLOAT_TO_FIXP(d) ((struct fixp) { (uint64_t)((d) * (float)FIXP_SCALE) }) #define FIXP_TO_UINT32(f) ((f).value >> FIXP_SHIFT) #define FIXP_TO_FLOAT(f) ((f).value / (float)FIXP_SCALE) struct fixp { uint64_t value; }; struct resample_info { uint32_t format; resample_func_t process_copy; const char *copy_name; resample_func_t process_full; const char *full_name; resample_func_t process_inter; const char *inter_name; uint32_t cpu_flags; }; struct native_data { double rate; uint32_t n_taps; uint32_t n_phases; struct fixp in_rate; uint32_t out_rate; struct fixp phase; float pm; uint32_t inc; struct fixp frac; uint32_t filter_stride; uint32_t filter_stride_os; uint32_t gcd; uint32_t hist; float **history; resample_func_t func; float *filter; float *hist_mem; const struct resample_info *info; }; #define DEFINE_RESAMPLER(type,arch) \ void do_resample_##type##_##arch(struct resample *r, \ const void * SPA_RESTRICT src[], uint32_t ioffs, uint32_t *in_len, \ void * SPA_RESTRICT dst[], uint32_t ooffs, uint32_t *out_len) #define MAKE_RESAMPLER_COPY(arch) \ DEFINE_RESAMPLER(copy,arch) \ { \ struct native_data *data = r->data; \ uint32_t index, n_taps = data->n_taps, n_taps2 = n_taps/2; \ uint32_t c, olen = *out_len, ilen = *in_len, ch = r->channels; \ \ index = ioffs; \ if (ooffs < olen && index + n_taps <= ilen) { \ uint32_t to_copy = SPA_MIN(olen - ooffs, \ ilen - (index + n_taps) + 1); \ for (c = 0; c < ch; c++) { \ const float *s = src[c]; \ float *d = dst[c]; \ spa_memcpy(&d[ooffs], &s[index + n_taps2], \ to_copy * sizeof(float)); \ } \ index += to_copy; \ ooffs += to_copy; \ } \ *in_len = index; \ *out_len = ooffs; \ } #define INC(index,phase,n_phases) \ index += inc; \ phase += frac; \ if (phase >= n_phases) { \ phase -= n_phases; \ index += 1; \ } #define MAKE_RESAMPLER_FULL(arch) \ DEFINE_RESAMPLER(full,arch) \ { \ struct native_data *data = r->data; \ uint32_t n_taps = data->n_taps, stride = data->filter_stride_os; \ uint32_t index; \ uint32_t c, o, olen = *out_len, ilen = *in_len; \ uint32_t inc = data->inc, ch = r->channels; \ uint64_t frac = data->frac.value, phase = data->phase.value; \ uint64_t denom = UINT32_TO_FIXP(data->out_rate).value; \ \ index = ioffs; \ for (o = ooffs; o < olen && index + n_taps <= ilen; o++) { \ float *filter = &data->filter[(phase >> FIXP_SHIFT) * stride]; \ for (c = 0; c < ch; c++) { \ const float *s = src[c]; \ float *d = dst[c]; \ inner_product_##arch(&d[o], &s[index], \ filter, n_taps); \ } \ INC(index, phase, denom); \ } \ *in_len = index; \ *out_len = o; \ data->phase.value = phase; \ } #define MAKE_RESAMPLER_INTER(arch) \ DEFINE_RESAMPLER(inter,arch) \ { \ struct native_data *data = r->data; \ uint32_t index, stride = data->filter_stride; \ uint32_t n_taps = data->n_taps; \ uint32_t c, o, olen = *out_len, ilen = *in_len; \ uint32_t inc = data->inc, ch = r->channels; \ uint32_t ph_max = data->n_phases - 1; \ uint64_t frac = data->frac.value, phase = data->phase.value; \ uint64_t denom = UINT32_TO_FIXP(data->out_rate).value; \ float pm = data->pm; \ \ index = ioffs; \ for (o = ooffs; o < olen && index + n_taps <= ilen; o++) { \ float ph = phase * pm; \ uint32_t offset = SPA_MIN((uint32_t)floorf(ph), ph_max); \ float *filter0 = &data->filter[(offset+0) * stride]; \ float *filter1 = &data->filter[(offset+1) * stride]; \ float pho = ph - offset; \ for (c = 0; c < ch; c++) { \ const float *s = src[c]; \ float *d = dst[c]; \ inner_product_ip_##arch(&d[o], &s[index], \ filter0, filter1, pho, n_taps); \ } \ INC(index, phase, denom); \ } \ *in_len = index; \ *out_len = o; \ data->phase.value = phase; \ } DEFINE_RESAMPLER(copy,c); DEFINE_RESAMPLER(full,c); DEFINE_RESAMPLER(inter,c); #if defined (HAVE_NEON) DEFINE_RESAMPLER(full,neon); DEFINE_RESAMPLER(inter,neon); #endif #if defined (HAVE_SSE) DEFINE_RESAMPLER(full,sse); DEFINE_RESAMPLER(inter,sse); #endif #if defined (HAVE_SSSE3) DEFINE_RESAMPLER(full,ssse3); DEFINE_RESAMPLER(inter,ssse3); #endif #if defined (HAVE_AVX) && defined(HAVE_FMA) DEFINE_RESAMPLER(full,avx); DEFINE_RESAMPLER(inter,avx); #endif resample-native-neon.c000066400000000000000000000143361511204443500315710ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/audioconvert/* Spa */ /* SPDX-FileCopyrightText: Copyright © 2019 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include "resample-native-impl.h" #include static inline void inner_product_neon(float *d, const float * SPA_RESTRICT s, const float * SPA_RESTRICT taps, uint32_t n_taps) { unsigned int remainder = n_taps % 16; n_taps = n_taps - remainder; #ifdef __aarch64__ asm volatile( " cmp %[n_taps], #0\n" " bne 1f\n" " ld1 {v4.4s}, [%[taps]], #16\n" " ld1 {v8.4s}, [%[s]], #16\n" " subs %[remainder], %[remainder], #4\n" " fmul v0.4s, v4.4s, v8.4s\n" " bne 4f\n" " b 5f\n" "1:" " ld1 {v4.4s, v5.4s, v6.4s, v7.4s}, [%[taps]], #64\n" " ld1 {v8.4s, v9.4s, v10.4s, v11.4s}, [%[s]], #64\n" " subs %[n_taps], %[n_taps], #16\n" " fmul v0.4s, v4.4s, v8.4s\n" " fmul v1.4s, v5.4s, v9.4s\n" " fmul v2.4s, v6.4s, v10.4s\n" " fmul v3.4s, v7.4s, v11.4s\n" " beq 3f\n" "2:" " ld1 {v4.4s, v5.4s, v6.4s, v7.4s}, [%[taps]], #64\n" " ld1 {v8.4s, v9.4s, v10.4s, v11.4s}, [%[s]], #64\n" " subs %[n_taps], %[n_taps], #16\n" " fmla v0.4s, v4.4s, v8.4s\n" " fmla v1.4s, v5.4s, v9.4s\n" " fmla v2.4s, v6.4s, v10.4s\n" " fmla v3.4s, v7.4s, v11.4s\n" " bne 2b\n" "3:" " fadd v4.4s, v0.4s, v1.4s\n" " fadd v5.4s, v2.4s, v3.4s\n" " cmp %[remainder], #0\n" " fadd v0.4s, v4.4s, v5.4s\n" " beq 5f\n" "4:" " ld1 {v6.4s}, [%[taps]], #16\n" " ld1 {v10.4s}, [%[s]], #16\n" " subs %[remainder], %[remainder], #4\n" " fmla v0.4s, v6.4s, v10.4s\n" " bne 4b\n" "5:" " faddp v0.4s, v0.4s, v0.4s\n" " faddp v0.2s, v0.2s, v0.2s\n" " str s0, [%[d]]\n" : [d] "+r" (d), [s] "+r" (s), [taps] "+r" (taps), [n_taps] "+r" (n_taps), [remainder] "+r" (remainder) : : "cc", "v0", "v1", "v2", "v3", "v4", "v5", "v6", "v7", "v8", "v9", "v10", "v11"); #else asm volatile ( " cmp %[n_taps], #0\n" " bne 1f\n" " vld1.32 {q4}, [%[taps] :128]!\n" " vld1.32 {q8}, [%[s]]!\n" " subs %[remainder], %[remainder], #4\n" " vmul.f32 q0, q4, q8\n" " bne 4f\n" " b 5f\n" "1:" " vld1.32 {q4, q5}, [%[taps] :128]!\n" " vld1.32 {q8, q9}, [%[s]]!\n" " vld1.32 {q6, q7}, [%[taps] :128]!\n" " vld1.32 {q10, q11}, [%[s]]!\n" " subs %[n_taps], %[n_taps], #16\n" " vmul.f32 q0, q4, q8\n" " vmul.f32 q1, q5, q9\n" " vmul.f32 q2, q6, q10\n" " vmul.f32 q3, q7, q11\n" " beq 3f\n" "2:" " vld1.32 {q4, q5}, [%[taps] :128]!\n" " vld1.32 {q8, q9}, [%[s]]!\n" " vld1.32 {q6, q7}, [%[taps] :128]!\n" " vld1.32 {q10, q11}, [%[s]]!\n" " subs %[n_taps], %[n_taps], #16\n" " vmla.f32 q0, q4, q8\n" " vmla.f32 q1, q5, q9\n" " vmla.f32 q2, q6, q10\n" " vmla.f32 q3, q7, q11\n" " bne 2b\n" "3:" " vadd.f32 q4, q0, q1\n" " vadd.f32 q5, q2, q3\n" " cmp %[remainder], #0\n" " vadd.f32 q0, q4, q5\n" " beq 5f\n" "4:" " vld1.32 {q6}, [%[taps] :128]!\n" " vld1.32 {q10}, [%[s]]!\n" " subs %[remainder], %[remainder], #4\n" " vmla.f32 q0, q6, q10\n" " bne 4b\n" "5:" " vadd.f32 d0, d0, d1\n" " vpadd.f32 d0, d0, d0\n" " vstr d0, [%[d]]\n" : [d] "+r" (d), [s] "+r" (s), [taps] "+r" (taps), [n_taps] "+l" (n_taps), [remainder] "+l" (remainder) : : "cc", "q0", "q1", "q2", "q3", "q4", "q5", "q6", "q7", "q8", "q9", "q10", "q11"); #endif } static inline void inner_product_ip_neon(float *d, const float * SPA_RESTRICT s, const float * SPA_RESTRICT t0, const float * SPA_RESTRICT t1, float x, uint32_t n_taps) { #ifdef __aarch64__ asm volatile( " dup v10.4s, %w[x]\n" " ld1 {v4.4s, v5.4s}, [%[t0]], #32\n" " ld1 {v8.4s, v9.4s}, [%[s]], #32\n" " ld1 {v6.4s, v7.4s}, [%[t1]], #32\n" " subs %[n_taps], %[n_taps], #8\n" " fmul v0.4s, v4.4s, v8.4s\n" " fmul v1.4s, v5.4s, v9.4s\n" " fmul v2.4s, v6.4s, v8.4s\n" " fmul v3.4s, v7.4s, v9.4s\n" " beq 3f\n" "2:" " ld1 {v4.4s, v5.4s}, [%[t0]], #32\n" " ld1 {v8.4s, v9.4s}, [%[s]], #32\n" " ld1 {v6.4s, v7.4s}, [%[t1]], #32\n" " subs %[n_taps], %[n_taps], #8\n" " fmla v0.4s, v4.4s, v8.4s\n" " fmla v1.4s, v5.4s, v9.4s\n" " fmla v2.4s, v6.4s, v8.4s\n" " fmla v3.4s, v7.4s, v9.4s\n" " bne 2b\n" "3:" " fadd v0.4s, v0.4s, v1.4s\n" /* sum[0] */ " fadd v2.4s, v2.4s, v3.4s\n" /* sum[1] */ " fsub v2.4s, v2.4s, v0.4s\n" /* sum[1] -= sum[0] */ " fmla v0.4s, v2.4s, v10.4s\n" /* sum[0] += sum[1] * x */ " faddp v0.4s, v0.4s, v0.4s\n" " faddp v0.2s, v0.2s, v0.2s\n" " str s0, [%[d]]\n" : [d] "+r" (d), [s] "+r" (s), [t0] "+r" (t0), [t1] "+r" (t1), [n_taps] "+r" (n_taps), [x] "+r" (x) : : "cc", "v0", "v1", "v2", "v3", "v4", "v5", "v6", "v7", "v8", "v9", "v10"); #else asm volatile( " vdup.32 q10, %[x]\n" " vld1.32 {q4, q5}, [%[t0] :128]!\n" " vld1.32 {q8, q9}, [%[s]]!\n" " vld1.32 {q6, q7}, [%[t1] :128]!\n" " subs %[n_taps], %[n_taps], #8\n" " vmul.f32 q0, q4, q8\n" " vmul.f32 q1, q5, q9\n" " vmul.f32 q2, q6, q8\n" " vmul.f32 q3, q7, q9\n" " beq 3f\n" "2:" " vld1.32 {q4, q5}, [%[t0] :128]!\n" " vld1.32 {q8, q9}, [%[s]]!\n" " vld1.32 {q6, q7}, [%[t1] :128]!\n" " subs %[n_taps], %[n_taps], #8\n" " vmla.f32 q0, q4, q8\n" " vmla.f32 q1, q5, q9\n" " vmla.f32 q2, q6, q8\n" " vmla.f32 q3, q7, q9\n" " bne 2b\n" "3:" " vadd.f32 q0, q0, q1\n" /* sum[0] */ " vadd.f32 q2, q2, q3\n" /* sum[1] */ " vsub.f32 q2, q2, q0\n" /* sum[1] -= sum[0] */ " vmla.f32 q0, q2, q10\n" /* sum[0] += sum[1] * x */ " vadd.f32 d0, d0, d1\n" " vpadd.f32 d0, d0, d0\n" " vstr d0, [%[d]]\n" : [d] "+r" (d), [s] "+r" (s), [t0] "+r" (t0), [t1] "+r" (t1), [n_taps] "+l" (n_taps), [x] "+l" (x) : : "cc", "q0", "q1", "q2", "q3", "q4", "q5", "q6", "q7", "q8", "q9", "q10"); #endif } MAKE_RESAMPLER_FULL(neon); MAKE_RESAMPLER_INTER(neon); resample-native-sse.c000066400000000000000000000041541511204443500314210ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/audioconvert/* Spa */ /* SPDX-FileCopyrightText: Copyright © 2019 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include "resample-native-impl.h" #include static inline void inner_product_sse(float *d, const float * SPA_RESTRICT s, const float * SPA_RESTRICT taps, uint32_t n_taps) { __m128 sum = _mm_setzero_ps(); uint32_t i = 0; #if 0 uint32_t unrolled = n_taps & ~15; for (i = 0; i < unrolled; i += 16) { sum = _mm_add_ps(sum, _mm_mul_ps( _mm_loadu_ps(s + i + 0), _mm_load_ps(taps + i + 0))); sum = _mm_add_ps(sum, _mm_mul_ps( _mm_loadu_ps(s + i + 4), _mm_load_ps(taps + i + 4))); sum = _mm_add_ps(sum, _mm_mul_ps( _mm_loadu_ps(s + i + 8), _mm_load_ps(taps + i + 8))); sum = _mm_add_ps(sum, _mm_mul_ps( _mm_loadu_ps(s + i + 12), _mm_load_ps(taps + i + 12))); } #endif for (; i < n_taps; i += 8) { sum = _mm_add_ps(sum, _mm_mul_ps( _mm_loadu_ps(s + i + 0), _mm_load_ps(taps + i + 0))); sum = _mm_add_ps(sum, _mm_mul_ps( _mm_loadu_ps(s + i + 4), _mm_load_ps(taps + i + 4))); } sum = _mm_add_ps(sum, _mm_movehl_ps(sum, sum)); sum = _mm_add_ss(sum, _mm_shuffle_ps(sum, sum, 0x55)); _mm_store_ss(d, sum); } static inline void inner_product_ip_sse(float *d, const float * SPA_RESTRICT s, const float * SPA_RESTRICT t0, const float * SPA_RESTRICT t1, float x, uint32_t n_taps) { __m128 sum[2] = { _mm_setzero_ps (), _mm_setzero_ps () }, t; uint32_t i; for (i = 0; i < n_taps; i += 8) { t = _mm_loadu_ps(s + i + 0); sum[0] = _mm_add_ps(sum[0], _mm_mul_ps(t, _mm_load_ps(t0 + i + 0))); sum[1] = _mm_add_ps(sum[1], _mm_mul_ps(t, _mm_load_ps(t1 + i + 0))); t = _mm_loadu_ps(s + i + 4); sum[0] = _mm_add_ps(sum[0], _mm_mul_ps(t, _mm_load_ps(t0 + i + 4))); sum[1] = _mm_add_ps(sum[1], _mm_mul_ps(t, _mm_load_ps(t1 + i + 4))); } sum[1] = _mm_mul_ps(_mm_sub_ps(sum[1], sum[0]), _mm_load1_ps(&x)); sum[0] = _mm_add_ps(sum[0], sum[1]); sum[0] = _mm_add_ps(sum[0], _mm_movehl_ps(sum[0], sum[0])); sum[0] = _mm_add_ss(sum[0], _mm_shuffle_ps(sum[0], sum[0], 0x55)); _mm_store_ss(d, sum[0]); } MAKE_RESAMPLER_FULL(sse); MAKE_RESAMPLER_INTER(sse); resample-native-ssse3.c000066400000000000000000000047341511204443500316730ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/audioconvert/* Spa */ /* SPDX-FileCopyrightText: Copyright © 2019 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include "resample-native-impl.h" #include static inline void inner_product_ssse3(float *d, const float * SPA_RESTRICT s, const float * SPA_RESTRICT taps, uint32_t n_taps) { __m128 sum = _mm_setzero_ps(); __m128 t0, t1; uint32_t i; switch (SPA_PTR_ALIGNMENT(s, 16)) { case 0: for (i = 0; i < n_taps; i += 8) { sum = _mm_add_ps(sum, _mm_mul_ps( _mm_load_ps(s + i + 0), _mm_load_ps(taps + i + 0))); sum = _mm_add_ps(sum, _mm_mul_ps( _mm_load_ps(s + i + 4), _mm_load_ps(taps + i + 4))); } break; case 4: t0 = _mm_load_ps(s - 1); for (i = 0; i < n_taps; i += 8) { t1 = _mm_load_ps(s + i + 3); t0 = (__m128)_mm_alignr_epi8((__m128i)t1, (__m128i)t0, 4); sum = _mm_add_ps(sum, _mm_mul_ps(t0, _mm_load_ps(taps + i + 0))); t0 = t1; t1 = _mm_load_ps(s + i + 7); t0 = (__m128)_mm_alignr_epi8((__m128i)t1, (__m128i)t0, 4); sum = _mm_add_ps(sum, _mm_mul_ps(t0, _mm_load_ps(taps + i + 4))); t0 = t1; } break; case 8: t0 = _mm_load_ps(s - 2); for (i = 0; i < n_taps; i += 8) { t1 = _mm_load_ps(s + i + 2); t0 = (__m128)_mm_alignr_epi8((__m128i)t1, (__m128i)t0, 8); sum = _mm_add_ps(sum, _mm_mul_ps(t0, _mm_load_ps(taps + i + 0))); t0 = t1; t1 = _mm_load_ps(s + i + 6); t0 = (__m128)_mm_alignr_epi8((__m128i)t1, (__m128i)t0, 8); sum = _mm_add_ps(sum, _mm_mul_ps(t0, _mm_load_ps(taps + i + 4))); t0 = t1; } break; case 12: t0 = _mm_load_ps(s - 3); for (i = 0; i < n_taps; i += 8) { t1 = _mm_load_ps(s + i + 1); t0 = (__m128)_mm_alignr_epi8((__m128i)t1, (__m128i)t0, 12); sum = _mm_add_ps(sum, _mm_mul_ps(t0, _mm_load_ps(taps + i + 0))); t0 = t1; t1 = _mm_load_ps(s + i + 5); t0 = (__m128)_mm_alignr_epi8((__m128i)t1, (__m128i)t0, 12); sum = _mm_add_ps(sum, _mm_mul_ps(t0, _mm_load_ps(taps + i + 4))); t0 = t1; } break; } sum = _mm_add_ps(sum, _mm_movehdup_ps(sum)); sum = _mm_add_ss(sum, _mm_movehl_ps(sum, sum)); _mm_store_ss(d, sum); } static inline void inner_product_ip_ssse3(float *d, const float * SPA_RESTRICT s, const float * SPA_RESTRICT t0, const float * SPA_RESTRICT t1, float x, uint32_t n_taps) { float sum[2] = { 0.0f, 0.0f }; uint32_t i; for (i = 0; i < n_taps; i++) { sum[0] += s[i] * t0[i]; sum[1] += s[i] * t1[i]; } *d = (sum[1] - sum[0]) * x + sum[0]; } MAKE_RESAMPLER_FULL(ssse3); MAKE_RESAMPLER_INTER(ssse3); pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/audioconvert/resample-native.c000066400000000000000000000433711511204443500307140ustar00rootroot00000000000000/* Spa */ /* SPDX-FileCopyrightText: Copyright © 2019 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include "resample-native-impl.h" #ifndef RESAMPLE_DISABLE_PRECOMP #include "resample-native-precomp.h" #endif SPA_LOG_TOPIC_DEFINE(resample_log_topic, "spa.resample"); #define INHERIT_PARAM(c,q,p) if ((c)->params[p] == 0.0) (c)->params[p] = (q)->params[p]; struct quality { uint32_t n_taps; double cutoff_up; /* when upsampling */ double cutoff_down; /* for downsampling */ double params[RESAMPLE_MAX_PARAMS]; }; struct window_info { uint32_t window; void (*func) (struct resample *r, double *w, double t, uint32_t n_taps); uint32_t n_qualities; const struct quality *qualities; void (*config) (struct resample *r); }; struct window_info window_info[]; static const struct quality blackman_qualities[] = { { 8, 0.58, 0.58, { 0.16, }}, { 16, 0.70, 0.70, { 0.20, }}, { 24, 0.77, 0.77, { 0.16, }}, { 32, 0.82, 0.82, { 0.16, }}, { 48, 0.87, 0.87, { 0.16, }}, /* default */ { 64, 0.895, 0.895, { 0.16, }}, { 80, 0.910, 0.910, { 0.16, }}, { 96, 0.925, 0.925, { 0.16, }}, { 128, 0.942, 0.942, { 0.16, }}, { 144, 0.950, 0.950, { 0.16, }}, { 160, 0.958, 0.958, { 0.16, }}, { 192, 0.966, 0.966, { 0.16, }}, { 256, 0.975, 0.975, { 0.16, }}, { 896, 0.988, 0.988, { 0.16, }}, { 1024, 0.990, 0.990, { 0.16, }}, }; static inline void blackman_window(struct resample *r, double *w, double t, uint32_t n_taps) { double x, alpha = r->config.params[RESAMPLE_PARAM_BLACKMAN_ALPHA]; uint32_t i, n_taps12 = n_taps/2; for (i = 0; i < n_taps12; i++, t += 1.0) { x = 2.0 * M_PI * t / n_taps; w[i] = (1.0 - alpha) / 2.0 + (1.0 / 2.0) * cos(x) + (alpha / 2.0) * cos(2.0 * x); } } static inline void blackman_config(struct resample *r) { const struct quality *q = &window_info[r->config.window].qualities[r->quality]; INHERIT_PARAM(&r->config, q, RESAMPLE_PARAM_BLACKMAN_ALPHA); } static const struct quality exp_qualities[] = { { 8, 0.58, 0.58, { 16.97789, }}, { 16, 0.70, 0.70, { 16.97789, }}, { 24, 0.77, 0.77, { 16.97789, }}, { 32, 0.82, 0.82, { 16.97789, }}, { 48, 0.87, 0.87, { 16.97789, }}, /* default */ { 64, 0.895, 0.895, { 16.97789, }}, { 80, 0.910, 0.910, { 16.97789, }}, { 96, 0.925, 0.925, { 16.97789, }}, { 128, 0.942, 0.942, { 16.97789, }}, { 144, 0.950, 0.950, { 16.97789, }}, { 160, 0.958, 0.958, { 16.97789, }}, { 192, 0.966, 0.966, { 16.97789, }}, { 256, 0.975, 0.975, { 16.97789, }}, { 896, 0.988, 0.988, { 16.97789, }}, { 1024, 0.990, 0.990, { 16.97789, }}, }; static inline void exp_window(struct resample *r, double *w, double t, uint32_t n_taps) { double x, A = r->config.params[RESAMPLE_PARAM_EXP_A]; uint32_t i, n_taps12 = n_taps/2; for (i = 0; i < n_taps12; i++, t += 1.0) { x = 2.0 * t / n_taps; /* doi:10.1109/RME.2008.4595727 with tweak */ w[i] = (exp(A * sqrt(fmax(0.0, 1.0 - x*x))) - 1) / (exp(A) - 1); } } static inline void exp_config(struct resample *r) { const struct quality *q = &window_info[r->config.window].qualities[r->quality]; INHERIT_PARAM(&r->config, q, RESAMPLE_PARAM_EXP_A); } #include "dbesi0.c" static const struct quality kaiser_qualities[] = { { 8, 0.620000, 0.620000, { 3.553376, 110.000000, 0.888064 }}, { 16, 0.780000, 0.780000, { 3.553376, 110.000000, 0.444032 }}, { 24, 0.820000, 0.820000, { 3.904154, 120.000000, 0.325043 }}, { 32, 0.865000, 0.865000, { 4.254931, 130.000000, 0.265548 }}, { 48, 0.895000, 0.895000, { 4.254931, 130.000000, 0.177032 }}, { 64, 0.915000, 0.915000, { 4.254931, 130.000000, 0.132774 }}, { 80, 0.928000, 0.928000, { 4.254931, 130.000000, 0.106219 }}, { 96, 0.942000, 0.942000, { 4.254931, 130.000000, 0.088516 }}, { 128, 0.952000, 0.952000, { 4.254931, 130.000000, 0.066387 }}, { 160, 0.960000, 0.960000, { 4.254931, 130.000000, 0.053110 }}, { 192, 0.968000, 0.968000, { 4.254931, 130.000000, 0.044258 }}, { 256, 0.976000, 0.976000, { 4.605709, 140.000000, 0.035914 }}, { 512, 0.985000, 0.985000, { 4.781097, 145.000000, 0.018637 }}, { 768, 0.990000, 0.990000, { 4.956486, 150.000000, 0.012878 }}, { 1024, 0.993000, 0.993000, { 5.131875, 155.000000, 0.009999 }}, }; static inline void kaiser_window(struct resample *r, double *w, double t, uint32_t n_taps) { double x, beta = r->config.params[RESAMPLE_PARAM_KAISER_ALPHA] * M_PI; double den = dbesi0(beta); uint32_t i, n_taps12 = n_taps/2; for (i = 0; i < n_taps12; i++, t += 1.0) { x = 2.0 * t / n_taps; w[i] = dbesi0(beta * sqrt(fmax(0.0, 1.0 - x*x))) / den; } } static inline void kaiser_config(struct resample *r) { double A, B, dw, tr_bw, alpha; uint32_t n; const struct quality *q = &window_info[r->config.window].qualities[r->quality]; if ((A = r->config.params[RESAMPLE_PARAM_KAISER_SB_ATT]) == 0.0) A = q->params[RESAMPLE_PARAM_KAISER_SB_ATT]; if ((tr_bw = r->config.params[RESAMPLE_PARAM_KAISER_TR_BW]) == 0.0) tr_bw = q->params[RESAMPLE_PARAM_KAISER_TR_BW]; if ((alpha = r->config.params[RESAMPLE_PARAM_KAISER_ALPHA]) == 0.0) { /* calculate Beta and alpha */ if (A > 50) B = 0.1102 * (A - 8.7); else if (A >= 21) B = 0.5842 * pow (A - 21, 0.4) + 0.07886 * (A - 21); else B = 0.0; r->config.params[RESAMPLE_PARAM_KAISER_ALPHA] = B / M_PI; } if (r->config.n_taps == 0) { /* calculate transition width in radians */ dw = 2 * M_PI * (tr_bw); /* order of the filter */ n = (uint32_t)((A - 8.0) / (2.285 * dw)); r->config.n_taps = n + 1; } } struct window_info window_info[] = { [RESAMPLE_WINDOW_EXP] = { RESAMPLE_WINDOW_EXP, exp_window, SPA_N_ELEMENTS(exp_qualities), exp_qualities, exp_config }, [RESAMPLE_WINDOW_BLACKMAN] = { RESAMPLE_WINDOW_BLACKMAN, blackman_window, SPA_N_ELEMENTS(blackman_qualities), blackman_qualities, blackman_config }, [RESAMPLE_WINDOW_KAISER] = { RESAMPLE_WINDOW_KAISER, kaiser_window, SPA_N_ELEMENTS(kaiser_qualities), kaiser_qualities, kaiser_config }, }; static inline double sinc(double x, double cutoff) { if (x < 1e-6) return cutoff; x *= M_PI; return sin(x * cutoff) / x; } static int build_filter(struct resample *r, float *taps, uint32_t stride, uint32_t n_taps, uint32_t n_phases, double cutoff) { uint32_t i, j, n_taps12 = n_taps/2; double window[n_taps12+1]; for (i = 0; i <= n_phases; i++) { double t = (double) i / (double) n_phases; window_info[r->config.window].func(r, window, t, n_taps); for (j = 0; j < n_taps12; j++, t += 1.0) { /* exploit symmetry in filter taps */ taps[(n_phases - i) * stride + n_taps12 + j] = taps[i * stride + (n_taps12 - j - 1)] = (float) (sinc(t, cutoff) * window[j]); } } return 0; } MAKE_RESAMPLER_COPY(c); #define MAKE(fmt,copy,full,inter,...) \ { SPA_AUDIO_FORMAT_ ##fmt, do_resample_ ##copy, #copy, \ do_resample_ ##full, #full, do_resample_ ##inter, #inter, __VA_ARGS__ } static struct resample_info resample_table[] = { #if defined (HAVE_NEON) MAKE(F32, copy_c, full_neon, inter_neon, SPA_CPU_FLAG_NEON), #endif #if defined(HAVE_AVX) && defined(HAVE_FMA) MAKE(F32, copy_c, full_avx, inter_avx, SPA_CPU_FLAG_AVX | SPA_CPU_FLAG_FMA3), #endif #if defined (HAVE_SSSE3) MAKE(F32, copy_c, full_ssse3, inter_ssse3, SPA_CPU_FLAG_SSSE3 | SPA_CPU_FLAG_SLOW_UNALIGNED), #endif #if defined (HAVE_SSE) MAKE(F32, copy_c, full_sse, inter_sse, SPA_CPU_FLAG_SSE), #endif MAKE(F32, copy_c, full_c, inter_c), }; #undef MAKE #define MATCH_CPU_FLAGS(a,b) ((a) == 0 || ((a) & (b)) == a) static const struct resample_info *find_resample_info(uint32_t format, uint32_t cpu_flags) { SPA_FOR_EACH_ELEMENT_VAR(resample_table, t) { if (t->format == format && MATCH_CPU_FLAGS(t->cpu_flags, cpu_flags)) return t; } return NULL; } static void impl_native_free(struct resample *r) { spa_log_debug(r->log, "native %p: free", r); free(r->data); r->data = NULL; } static inline uint32_t calc_gcd(uint32_t a, uint32_t b) { while (b != 0) { uint32_t temp = a; a = b; b = temp % b; } return a; } static void impl_native_update_rate(struct resample *r, double rate) { struct native_data *data = r->data; struct fixp in_rate; uint32_t out_rate; if (SPA_LIKELY(data->rate == rate)) return; data->rate = rate; in_rate = UINT32_TO_FIXP(r->i_rate); out_rate = r->o_rate; if (rate != 1.0) { in_rate.value = (uint64_t)round(in_rate.value / rate); data->func = data->info->process_inter; } else if (in_rate.value == UINT32_TO_FIXP(out_rate).value) { data->func = data->info->process_copy; } else { in_rate.value /= data->gcd; out_rate /= data->gcd; data->func = data->info->process_full; } if (data->out_rate != out_rate) { /* Cast to double to avoid overflows */ data->phase.value = (uint64_t)(data->phase.value * (double)out_rate / data->out_rate); if (data->phase.value >= UINT32_TO_FIXP(out_rate).value) data->phase.value = UINT32_TO_FIXP(out_rate).value - 1; } data->in_rate = in_rate; data->out_rate = out_rate; data->inc = in_rate.value / UINT32_TO_FIXP(out_rate).value; data->frac.value = in_rate.value % UINT32_TO_FIXP(out_rate).value; spa_log_trace_fp(r->log, "native %p: rate:%f in:%d out:%d phase:%f inc:%d frac:%f", r, rate, r->i_rate, r->o_rate, FIXP_TO_FLOAT(data->phase), data->inc, FIXP_TO_FLOAT(data->frac)); } static uint64_t fixp_floor_a_plus_bc(struct fixp a, uint32_t b, struct fixp c) { /* (a + b*c) >> FIXP_SHIFT, with bigger overflow threshold */ uint64_t hi, lo; hi = (a.value >> FIXP_SHIFT) + b * (c.value >> FIXP_SHIFT); lo = (a.value & FIXP_MASK) + b * (c.value & FIXP_MASK); return hi + (lo >> FIXP_SHIFT); } static uint32_t impl_native_in_len(struct resample *r, uint32_t out_len) { struct native_data *data = r->data; uint32_t in_len; in_len = fixp_floor_a_plus_bc(data->phase, out_len, data->frac) / data->out_rate; in_len += out_len * data->inc + (data->n_taps - data->hist); spa_log_trace_fp(r->log, "native %p: hist:%d %d->%d", r, data->hist, out_len, in_len); return in_len; } static uint32_t impl_native_out_len(struct resample *r, uint32_t in_len) { struct native_data *data = r->data; uint32_t out_len; in_len = in_len - SPA_MIN(in_len, data->n_taps - data->hist); out_len = in_len * data->out_rate - FIXP_TO_UINT32(data->phase); out_len = (UINT32_TO_FIXP(out_len).value + data->in_rate.value - 1) / data->in_rate.value; spa_log_trace_fp(r->log, "native %p: hist:%d %d->%d", r, data->hist, in_len, out_len); return out_len; } static void impl_native_process(struct resample *r, const void * SPA_RESTRICT src[], uint32_t *in_len, void * SPA_RESTRICT dst[], uint32_t *out_len) { struct native_data *data = r->data; uint32_t n_taps = data->n_taps; float **history = data->history; const float **s = (const float **)src; uint32_t c, refill, hist, in, out, remain; hist = data->hist; refill = 0; if (SPA_LIKELY(hist)) { /* first work on the history if any. */ if (SPA_UNLIKELY(hist <= n_taps)) { /* we need at least n_taps to completely process the * history before we can work on the new input. When * we have less, refill the history. */ refill = SPA_MIN(*in_len, n_taps-1); for (c = 0; c < r->channels; c++) spa_memcpy(&history[c][hist], s[c], refill * sizeof(float)); if (SPA_UNLIKELY(hist + refill < n_taps)) { /* not enough in the history, keep the input in * the history and produce no output */ data->hist = hist + refill; *in_len = refill; *out_len = 0; return; } } /* now we have at least n_taps of data in the history * and we try to process it */ in = hist + refill; out = *out_len; data->func(r, (const void**)history, 0, &in, dst, 0, &out); spa_log_trace_fp(r->log, "native %p: in:%d/%d out %d/%d hist:%d", r, hist + refill, in, *out_len, out, hist); } else { out = in = 0; } if (SPA_LIKELY(in >= hist)) { int skip = in - hist; /* we are past the history and can now work on the new * input data */ in = *in_len; data->func(r, src, skip, &in, dst, out, out_len); spa_log_trace_fp(r->log, "native %p: in:%d/%d out %d/%d skip:%d", r, *in_len, in, *out_len, out, skip); remain = *in_len - in; if (remain > 0 && remain <= n_taps) { /* not enough input data remaining for more output, * copy to history */ for (c = 0; c < r->channels; c++) spa_memcpy(history[c], &s[c][in], remain * sizeof(float)); } else { /* we have enough input data remaining to produce * more output ask to resubmit. */ remain = 0; *in_len = in; } } else { /* we are still working on the history */ *out_len = out; remain = hist - in; if (*in_len < n_taps) { /* not enough input data, add it to the history because * resubmitting it is not going to make progress. * We copied this into the history above. */ remain += refill; } else { /* input has enough data to possibly produce more output * from the history so ask to resubmit */ *in_len = 0; } if (remain) { /* move history */ for (c = 0; c < r->channels; c++) spa_memmove(history[c], &history[c][in], remain * sizeof(float)); } spa_log_trace_fp(r->log, "native %p: in:%d remain:%d", r, in, remain); } data->hist = remain; return; } static void impl_native_reset (struct resample *r) { struct native_data *d = r->data; if (d == NULL) return; memset(d->hist_mem, 0, r->channels * sizeof(float) * d->n_taps * 2); if (r->options & RESAMPLE_OPTION_PREFILL) d->hist = d->n_taps - 1; else d->hist = d->n_taps / 2; d->phase.value = 0; } static uint32_t impl_native_delay (struct resample *r) { struct native_data *d = r->data; return d->n_taps / 2 - 1; } static float impl_native_phase (struct resample *r) { struct native_data *d = r->data; float pho = 0; if (d->func == d->info->process_full) { pho = -(float)FIXP_TO_UINT32(d->phase) / d->out_rate; /* XXX: this is how it seems to behave, but not clear why */ if (d->hist >= d->n_taps - 1) pho += 1.0f; } else if (d->func == d->info->process_inter) { pho = -FIXP_TO_FLOAT(d->phase) / d->out_rate; /* XXX: this is how it seems to behave, but not clear why */ if (d->hist >= d->n_taps - 1) pho += 1.0f; } return pho; } int resample_native_init(struct resample *r) { struct native_data *d; const struct quality *q; double scale, cutoff; uint32_t i, n_taps, n_phases, filter_size, in_rate, out_rate, gcd, filter_stride; uint32_t history_stride, history_size, oversample; struct resample_config *c = &r->config; #ifndef RESAMPLE_DISABLE_PRECOMP struct resample_config def = { 0 }; bool default_config; default_config = memcmp(c, &def, sizeof(def)) == 0; #endif c->window = SPA_CLAMP(c->window, 0u, SPA_N_ELEMENTS(window_info)-1); r->quality = SPA_CLAMP(r->quality, 0, (int)(window_info[c->window].n_qualities - 1)); r->free = impl_native_free; r->update_rate = impl_native_update_rate; r->in_len = impl_native_in_len; r->out_len = impl_native_out_len; r->process = impl_native_process; r->reset = impl_native_reset; r->delay = impl_native_delay; r->phase = impl_native_phase; window_info[c->window].config(r); q = &window_info[c->window].qualities[r->quality]; cutoff = r->o_rate < r->i_rate ? q->cutoff_down : q->cutoff_up; c->cutoff = c->cutoff <= 0.0 ? cutoff: c->cutoff; n_taps = c->n_taps == 0 ? q->n_taps : c->n_taps; gcd = calc_gcd(r->i_rate, r->o_rate); in_rate = r->i_rate / gcd; out_rate = r->o_rate / gcd; scale = SPA_MIN(c->cutoff * out_rate / in_rate, c->cutoff); /* multiple of 8 taps to ease simd optimizations */ n_taps = SPA_ROUND_UP_N((uint32_t)ceil(n_taps / scale), 8); n_taps = SPA_MIN(n_taps, 1u << 18); /* try to get at least 256 phases so that interpolation is * accurate enough when activated */ n_phases = out_rate; oversample = (255 + n_phases) / n_phases; n_phases *= oversample; filter_stride = SPA_ROUND_UP_N(n_taps * sizeof(float), 64); filter_size = filter_stride * (n_phases + 1); history_stride = SPA_ROUND_UP_N(2 * n_taps * sizeof(float), 64); history_size = r->channels * history_stride; d = calloc(1, sizeof(struct native_data) + filter_size + history_size + (r->channels * sizeof(float*)) + 64); if (d == NULL) return -errno; r->data = d; c->n_taps = d->n_taps = n_taps; d->n_phases = n_phases; d->in_rate = UINT32_TO_FIXP(in_rate); d->out_rate = out_rate; d->gcd = gcd; d->pm = (float)n_phases / r->o_rate / FIXP_SCALE; d->filter = SPA_PTROFF_ALIGN(d, sizeof(struct native_data), 64, float); d->hist_mem = SPA_PTROFF_ALIGN(d->filter, filter_size, 64, float); d->history = SPA_PTROFF(d->hist_mem, history_size, float*); d->filter_stride = filter_stride / sizeof(float); d->filter_stride_os = d->filter_stride * oversample; for (i = 0; i < r->channels; i++) d->history[i] = SPA_PTROFF(d->hist_mem, i * history_stride, float); #ifndef RESAMPLE_DISABLE_PRECOMP /* See if we have precomputed coefficients */ for (i = 0; precomp_coeffs[i].filter; i++) { if (default_config && precomp_coeffs[i].in_rate == r->i_rate && precomp_coeffs[i].out_rate == r->o_rate && precomp_coeffs[i].quality == r->quality) break; } if (precomp_coeffs[i].filter) { spa_log_info(r->log, "using precomputed filter for %u->%u(%u)", r->i_rate, r->o_rate, r->quality); spa_memcpy(d->filter, precomp_coeffs[i].filter, filter_size); } else { #endif build_filter(r, d->filter, d->filter_stride, n_taps, n_phases, scale); #ifndef RESAMPLE_DISABLE_PRECOMP } #endif d->info = find_resample_info(SPA_AUDIO_FORMAT_F32, r->cpu_flags); if (SPA_UNLIKELY(d->info == NULL)) { spa_log_error(r->log, "failed to find suitable resample format!"); return -ENOTSUP; } spa_log_info(r->log, "native %p: c:%f q:%d w:%d in:%d out:%d gcd:%d n_taps:%d n_phases:%d features:%08x:%08x", r, c->cutoff, r->quality, c->window, r->i_rate, r->o_rate, gcd, n_taps, n_phases, r->cpu_flags, d->info->cpu_flags); r->cpu_flags = d->info->cpu_flags; impl_native_reset(r); impl_native_update_rate(r, 1.0); if (d->func == d->info->process_copy) r->func_name = d->info->copy_name; else if (d->func == d->info->process_full) r->func_name = d->info->full_name; else r->func_name = d->info->inter_name; return 0; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/audioconvert/resample-peaks.c000066400000000000000000000054371511204443500305320ustar00rootroot00000000000000/* Spa */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include #include "peaks-ops.h" #include "resample.h" struct peaks_data { uint32_t o_count; uint32_t i_count; struct peaks peaks; float max_f[]; }; static void resample_peaks_process(struct resample *r, const void * SPA_RESTRICT src[], uint32_t *in_len, void * SPA_RESTRICT dst[], uint32_t *out_len) { struct peaks_data *pd = r->data; uint32_t c, i, o, end, chunk, i_count, o_count; if (SPA_UNLIKELY(r->channels == 0)) return; for (c = 0; c < r->channels; c++) { const float *s = src[c]; float *d = dst[c], m = pd->max_f[c]; o_count = pd->o_count; i_count = pd->i_count; o = i = 0; while (i < *in_len && o < *out_len) { end = ((uint64_t) (o_count + 1) * r->i_rate) / r->o_rate; end = end > i_count ? end - i_count : 0; chunk = SPA_MIN(end, *in_len - i); m = peaks_abs_max(&pd->peaks, &s[i], chunk, m); i += chunk; i_count += chunk; if (chunk == end) { d[o++] = m; m = 0.0f; o_count++; } } pd->max_f[c] = m; } *out_len = o; *in_len = i; pd->o_count = o_count; pd->i_count = i_count; while (pd->i_count >= r->i_rate && pd->o_count >= r->o_rate) { pd->i_count -= r->i_rate; pd->o_count -= r->o_rate; } } static void impl_peaks_free(struct resample *r) { struct peaks_data *d = r->data; if (d != NULL) { peaks_free(&d->peaks); free(d); } r->data = NULL; } static void impl_peaks_update_rate(struct resample *r, double rate) { } static uint32_t impl_peaks_delay (struct resample *r) { return 0; } static uint32_t impl_peaks_in_len(struct resample *r, uint32_t out_len) { return out_len; } static uint32_t impl_peaks_out_len(struct resample *r, uint32_t in_len) { return in_len; } static void impl_peaks_reset (struct resample *r) { struct peaks_data *d = r->data; d->i_count = d->o_count = 0; } static float impl_peaks_phase (struct resample *r) { return 0; } int resample_peaks_init(struct resample *r) { struct peaks_data *d; int res; r->free = impl_peaks_free; r->update_rate = impl_peaks_update_rate; d = calloc(1, sizeof(struct peaks_data) + sizeof(float) * r->channels); if (d == NULL) return -errno; d->peaks.log = r->log; d->peaks.cpu_flags = r->cpu_flags; if ((res = peaks_init(&d->peaks)) < 0) { free(d); return res; } r->data = d; r->process = resample_peaks_process; r->reset = impl_peaks_reset; r->delay = impl_peaks_delay; r->in_len = impl_peaks_in_len; r->out_len = impl_peaks_out_len; r->phase = impl_peaks_phase; spa_log_debug(r->log, "peaks %p: in:%d out:%d features:%08x:%08x", r, r->i_rate, r->o_rate, r->cpu_flags, d->peaks.cpu_flags); r->cpu_flags = d->peaks.cpu_flags; d->i_count = d->o_count = 0; return 0; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/audioconvert/resample.h000066400000000000000000000073741511204443500274400ustar00rootroot00000000000000/* Spa */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef RESAMPLE_H #define RESAMPLE_H #include #include #define RESAMPLE_DEFAULT_QUALITY 4 #define RESAMPLE_WINDOW_DEFAULT RESAMPLE_WINDOW_EXP #define RESAMPLE_MAX_PARAMS 16 struct resample_config { #define RESAMPLE_WINDOW_EXP 0 #define RESAMPLE_WINDOW_BLACKMAN 1 #define RESAMPLE_WINDOW_KAISER 2 uint32_t window; double cutoff; uint32_t n_taps; #define RESAMPLE_PARAM_EXP_A 0 #define RESAMPLE_PARAM_BLACKMAN_ALPHA 0 #define RESAMPLE_PARAM_KAISER_ALPHA 0 #define RESAMPLE_PARAM_KAISER_SB_ATT 1 /* stopband attenuation */ #define RESAMPLE_PARAM_KAISER_TR_BW 2 /* transition bandwidth */ #define RESAMPLE_PARAM_INVALID (RESAMPLE_MAX_PARAMS-1) double params[RESAMPLE_MAX_PARAMS]; }; struct resample { struct spa_log *log; #define RESAMPLE_OPTION_PREFILL (1<<0) uint32_t options; uint32_t cpu_flags; const char *func_name; uint32_t channels; uint32_t i_rate; uint32_t o_rate; double rate; int quality; struct resample_config config; /* set to all 0 for defaults */ void (*free) (struct resample *r); void (*update_rate) (struct resample *r, double rate); uint32_t (*in_len) (struct resample *r, uint32_t out_len); uint32_t (*out_len) (struct resample *r, uint32_t in_len); void (*process) (struct resample *r, const void * SPA_RESTRICT src[], uint32_t *in_len, void * SPA_RESTRICT dst[], uint32_t *out_len); void (*reset) (struct resample *r); uint32_t (*delay) (struct resample *r); /** Fractional part of delay (in input samples) */ float (*phase) (struct resample *r); void *data; }; #define resample_free(r) (r)->free(r) #define resample_update_rate(r,...) (r)->update_rate(r,__VA_ARGS__) #define resample_in_len(r,...) (r)->in_len(r,__VA_ARGS__) #define resample_out_len(r,...) (r)->out_len(r,__VA_ARGS__) #define resample_process(r,...) (r)->process(r,__VA_ARGS__) #define resample_reset(r) (r)->reset(r) #define resample_delay(r) (r)->delay(r) #define resample_phase(r) (r)->phase(r) int resample_native_init(struct resample *r); int resample_native_init_config(struct resample *r, struct resample_config *conf); int resample_peaks_init(struct resample *r); static const struct resample_window_info { uint32_t window; const char *label; const char *description; uint32_t n_params; } resample_window_info[] = { [RESAMPLE_WINDOW_EXP] = { RESAMPLE_WINDOW_EXP, "exp", "Exponential window", 1 }, [RESAMPLE_WINDOW_BLACKMAN] = { RESAMPLE_WINDOW_BLACKMAN, "blackman", "Blackman window", 1 }, [RESAMPLE_WINDOW_KAISER] = { RESAMPLE_WINDOW_KAISER, "kaiser", "Kaiser window", 3 }, }; static inline uint32_t resample_window_from_label(const char *label) { SPA_FOR_EACH_ELEMENT_VAR(resample_window_info, i) { if (spa_streq(i->label, label)) return i->window; } return RESAMPLE_WINDOW_EXP; } static inline const char *resample_window_name(uint32_t idx) { return resample_window_info[SPA_CLAMP(idx, 0u, SPA_N_ELEMENTS(resample_window_info)-1)].label; } static const struct resample_param_info { uint32_t window; uint32_t idx; const char *label; } resample_param_info[] = { { RESAMPLE_WINDOW_EXP, RESAMPLE_PARAM_EXP_A, "exp.A" }, { RESAMPLE_WINDOW_BLACKMAN, RESAMPLE_PARAM_BLACKMAN_ALPHA, "blackman.alpha" }, { RESAMPLE_WINDOW_KAISER, RESAMPLE_PARAM_KAISER_ALPHA, "kaiser.alpha" }, { RESAMPLE_WINDOW_KAISER, RESAMPLE_PARAM_KAISER_SB_ATT, "kaiser.stopband-attenuation" }, { RESAMPLE_WINDOW_KAISER, RESAMPLE_PARAM_KAISER_TR_BW, "kaiser.transition-bandwidth" }, }; static inline uint32_t resample_param_from_label(const char *label) { SPA_FOR_EACH_ELEMENT_VAR(resample_param_info, i) { if (spa_streq(i->label, label)) return i->idx; } return RESAMPLE_PARAM_INVALID; } #endif /* RESAMPLE_H */ spa-resample-dump-coeffs.c000066400000000000000000000114341511204443500323330ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/audioconvert/* Spa */ /* SPDX-FileCopyrightText: Copyright © 2024 Arun Raghavan */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #include #include #include SPA_LOG_IMPL(logger); #include "resample.h" #include "resample-native-impl.h" #define OPTIONS "ht:" static const struct option long_options[] = { { "help", no_argument, NULL, 'h'}, { "tuple", required_argument, NULL, 't' }, { NULL, 0, NULL, 0 } }; static void show_usage(const char *name, bool is_error) { FILE *fp; fp = is_error ? stderr : stdout; fprintf(fp, "%s [options]\n", name); fprintf(fp, " -h, --help Show this help\n" "\n" " -t --tuple Sample rate tuple (as \"in_rate,out_rate[,quality]\")\n" "\n"); } static void parse_tuple(const char *arg, int *in, int *out, int *quality) { char tuple[256]; char *token; strncpy(tuple, arg, sizeof(tuple) - 1); *in = 0; *out = 0; token = strtok(tuple, ","); if (!token || !spa_atoi32(token, in, 10)) return; token = strtok(NULL, ","); if (!token || !spa_atoi32(token, out, 10)) return; token = strtok(NULL, ","); if (!token) { *quality = RESAMPLE_DEFAULT_QUALITY; } else if (!spa_atoi32(token, quality, 10)) { *quality = -1; return; } /* first, second now contain zeroes on error, or the numbers on success, * third contains a quality or -1 on error, default value if unspecified */ } #define PREFIX "__precomp_coeff" static void dump_header(void) { printf("/* This is a generated file, see spa-resample-dump-coeffs.c */"); printf("\n#include \n"); printf("\n#include \n"); printf("\n"); printf("struct resample_coeffs {\n"); printf("\tuint32_t in_rate;\n"); printf("\tuint32_t out_rate;\n"); printf("\tint quality;\n"); printf("\tconst float *filter;\n"); printf("};\n"); } static void dump_footer(const uint32_t *ins, const uint32_t *outs, const int *qualities) { printf("\n"); printf("static const struct resample_coeffs precomp_coeffs[] = {\n"); while (*ins && *outs) { printf("\t{ .in_rate = %u, .out_rate = %u, .quality = %u, " ".filter = %s_%u_%u_%u },\n", *ins, *outs, *qualities, PREFIX, *ins, *outs, *qualities); ins++; outs++; qualities++; } printf("\t{ .in_rate = 0, .out_rate = 0, .quality = 0, .filter = NULL },\n"); printf("};\n"); } static void dump_coeffs(unsigned int in_rate, unsigned int out_rate, int quality) { struct resample r = { 0, }; struct native_data *d; unsigned int i, filter_size; int ret; r.log = &logger.log; r.i_rate = in_rate; r.o_rate = out_rate; r.quality = quality; r.channels = 1; /* irrelevant for generated taps */ if ((ret = resample_native_init(&r)) < 0) { fprintf(stderr, "can't init converter: %s\n", spa_strerror(ret)); return; } d = r.data; filter_size = d->filter_stride * (d->n_phases + 1); printf("\n"); printf("static const float %s_%u_%u_%u[] = {", PREFIX, in_rate, out_rate, quality); for (i = 0; i < filter_size; i++) { printf("%a", d->filter[i]); if (i != filter_size - 1) printf(","); } printf("};\n"); if (r.free) r.free(&r); } int main(int argc, char* argv[]) { unsigned int ins[256] = { 0, }, outs[256] = { 0, }; int qualities[256] = { 0, }; int in_rate = 0, out_rate = 0, quality = 0; int c, longopt_index = 0, i = 0; while ((c = getopt_long(argc, argv, OPTIONS, long_options, &longopt_index)) != -1) { switch (c) { case 'h': show_usage(argv[0], false); return EXIT_SUCCESS; case 't': parse_tuple(optarg, &in_rate, &out_rate, &quality); if (in_rate <= 0) { fprintf(stderr, "error: bad input rate %d\n", in_rate); goto error; } if (out_rate <= 0) { fprintf(stderr, "error: bad output rate %d\n", out_rate); goto error; } if (quality < 0 || quality > 14) { fprintf(stderr, "error: bad quality value %s\n", optarg); goto error; } ins[i] = in_rate; outs[i] = out_rate; qualities[i] = quality; i++; break; default: fprintf(stderr, "error: unknown option\n"); goto error_usage; } } if (optind != argc) { fprintf(stderr, "error: got %d extra argument(s))\n", optind - argc); goto error_usage; } if (in_rate == 0) { fprintf(stderr, "error: input rate must be specified\n"); goto error; } if (out_rate == 0) { fprintf(stderr, "error: input rate must be specified\n"); goto error; } dump_header(); while (i--) { dump_coeffs(ins[i], outs[i], qualities[i]); } dump_footer(ins, outs, qualities); return EXIT_SUCCESS; error_usage: show_usage(argv[0], true); error: return EXIT_FAILURE; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/audioconvert/spa-resample.c000066400000000000000000000237371511204443500302150ustar00rootroot00000000000000/* Spa */ /* SPDX-FileCopyrightText: Copyright © 2020 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #include #include #include #include #include #include #include #include SPA_LOG_IMPL(logger); #include "resample.h" #define DEFAULT_QUALITY RESAMPLE_DEFAULT_QUALITY #define MAX_SAMPLES 4096u struct data { bool verbose; int rate; int format; uint32_t window; int quality; struct resample_config config; int cpu_flags; const char *iname; SF_INFO iinfo; SNDFILE *ifile; const char *oname; SF_INFO oinfo; SNDFILE *ofile; }; #define STR_FMTS "(s8|s16|s32|f32|f64)" #define OPTIONS "hvc:r:f:w:q:u:t:p:" static const struct option long_options[] = { { "help", no_argument, NULL, 'h'}, { "verbose", no_argument, NULL, 'v'}, { "cpuflags", required_argument, NULL, 'c' }, { "rate", required_argument, NULL, 'r' }, { "format", required_argument, NULL, 'f' }, { "window", required_argument, NULL, 'w' }, { "quality", required_argument, NULL, 'q' }, { "cutoff", required_argument, NULL, 'u' }, { "taps", required_argument, NULL, 't' }, { "param", required_argument, NULL, 'p' }, { NULL, 0, NULL, 0 } }; static void show_usage(const char *name, bool is_error) { FILE *fp; uint32_t i; fp = is_error ? stderr : stdout; fprintf(fp, "%s [options] \n", name); fprintf(fp, " -h, --help Show this help\n" " -v --verbose Be verbose\n" " -c --cpuflags CPU flags (default 0)\n" "\n"); fprintf(fp, " -r --rate Output sample rate (default as input)\n" " -f --format Output sample format %s (default as input)\n\n" " -w --window Window function (default %s)\n", STR_FMTS, resample_window_name(RESAMPLE_WINDOW_DEFAULT)); for (i = 0; i < SPA_N_ELEMENTS(resample_window_info); i++) { fprintf(fp, " %s: %s\n", resample_window_info[i].label, resample_window_info[i].description); } fprintf(fp, " -q --quality Resampler quality (default %u)\n" " -u --cutoff Cutoff frequency [0.0..1.0] (default from quality)\n" " -t --taps Resampler taps (default from quality)\n" " -p --param Resampler param = (default from quality)\n", DEFAULT_QUALITY); for (i = 0; i < SPA_N_ELEMENTS(resample_param_info); i++) { fprintf(fp, " %s\n", resample_param_info[i].label); } fprintf(fp, "\n"); } static inline const char * sf_fmt_to_str(int fmt) { switch(fmt & SF_FORMAT_SUBMASK) { case SF_FORMAT_PCM_S8: return "s8"; case SF_FORMAT_PCM_16: return "s16"; case SF_FORMAT_PCM_24: return "s24"; case SF_FORMAT_PCM_32: return "s32"; case SF_FORMAT_FLOAT: return "f32"; case SF_FORMAT_DOUBLE: return "f64"; default: return "unknown"; } } static inline int sf_str_to_fmt(const char *str) { if (!str) return -1; if (spa_streq(str, "s8")) return SF_FORMAT_PCM_S8; if (spa_streq(str, "s16")) return SF_FORMAT_PCM_16; if (spa_streq(str, "s24")) return SF_FORMAT_PCM_24; if (spa_streq(str, "s32")) return SF_FORMAT_PCM_32; if (spa_streq(str, "f32")) return SF_FORMAT_FLOAT; if (spa_streq(str, "f64")) return SF_FORMAT_DOUBLE; return -1; } static int open_files(struct data *d) { int i, count = 0, format = -1; d->ifile = sf_open(d->iname, SFM_READ, &d->iinfo); if (d->ifile == NULL) { fprintf(stderr, "error: failed to open input file \"%s\": %s\n", d->iname, sf_strerror(NULL)); return -EIO; } d->oinfo.channels = d->iinfo.channels; d->oinfo.samplerate = d->rate > 0 ? d->rate : d->iinfo.samplerate; d->oinfo.format = d->format > 0 ? d->format : (d->iinfo.format & SF_FORMAT_SUBMASK); /* try to guess the format from the extension */ if (sf_command(NULL, SFC_GET_FORMAT_MAJOR_COUNT, &count, sizeof(int)) != 0) count = 0; for (i = 0; i < count; i++) { SF_FORMAT_INFO fi; spa_zero(fi); fi.format = i; if (sf_command(NULL, SFC_GET_FORMAT_MAJOR, &fi, sizeof(fi)) != 0) continue; if (spa_strendswith(d->oname, fi.extension)) { format = fi.format; break; } } if (format == -1) /* use the same format as the input file otherwise */ format = d->iinfo.format & ~SF_FORMAT_SUBMASK; if (format == SF_FORMAT_WAV && d->oinfo.channels > 2) format = SF_FORMAT_WAVEX; d->oinfo.format |= format; d->ofile = sf_open(d->oname, SFM_WRITE, &d->oinfo); if (d->ofile == NULL) { fprintf(stderr, "error: failed to open output file \"%s\": %s\n", d->oname, sf_strerror(NULL)); return -EIO; } if (d->verbose) { fprintf(stdout, "input '%s': channels:%d rate:%d format:%s\n", d->iname, d->iinfo.channels, d->iinfo.samplerate, sf_fmt_to_str(d->iinfo.format)); fprintf(stdout, "output '%s': channels:%d rate:%d format:%s\n", d->oname, d->oinfo.channels, d->oinfo.samplerate, sf_fmt_to_str(d->oinfo.format)); } return 0; } static int close_files(struct data *d) { if (d->ifile) sf_close(d->ifile); if (d->ofile) sf_close(d->ofile); return 0; } static int do_conversion(struct data *d) { struct resample r; int channels = d->iinfo.channels; float in[MAX_SAMPLES * channels]; float out[MAX_SAMPLES * channels]; float ibuf[MAX_SAMPLES * channels]; float obuf[MAX_SAMPLES * channels]; uint32_t in_len, out_len, queued; uint32_t pin_len, pout_len; size_t read, written; const void *src[channels]; void *dst[channels]; uint32_t i; int res, j, k; uint32_t flushing = UINT32_MAX; spa_zero(r); r.cpu_flags = d->cpu_flags; r.log = &logger.log; r.channels = channels; r.i_rate = d->iinfo.samplerate; r.o_rate = d->oinfo.samplerate; r.quality = d->quality < 0 ? DEFAULT_QUALITY : d->quality; r.config = d->config; if ((res = resample_native_init(&r)) < 0) { fprintf(stderr, "can't init converter: %s\n", spa_strerror(res)); return res; } if (d->verbose) { fprintf(stdout, "window:%s cutoff:%f n_taps:%u\n", resample_window_name(r.config.window), r.config.cutoff, r.config.n_taps); for (i = 0; i < SPA_N_ELEMENTS(resample_param_info); i++) { if (resample_param_info[i].window != r.config.window) continue; fprintf(stdout, " param:%s %f\n", resample_param_info[i].label, r.config.params[resample_param_info[i].idx]); } } for (j = 0; j < channels; j++) src[j] = &in[MAX_SAMPLES * j]; for (j = 0; j < channels; j++) dst[j] = &out[MAX_SAMPLES * j]; read = written = queued = 0; while (true) { pout_len = out_len = MAX_SAMPLES; in_len = SPA_MIN(MAX_SAMPLES, resample_in_len(&r, out_len)); in_len -= SPA_MIN(queued, in_len); if (in_len > 0) { pin_len = in_len = sf_readf_float(d->ifile, &ibuf[queued * channels], in_len); read += pin_len; if (pin_len == 0) { if (flushing == 0) break; if (flushing == UINT32_MAX) flushing = resample_delay(&r); pin_len = in_len = SPA_MIN(MAX_SAMPLES, flushing); flushing -= in_len; for (k = 0, i = 0; i < pin_len; i++) { for (j = 0; j < channels; j++) ibuf[k++] = 0.0; } } } in_len += queued; pin_len = in_len; for (k = 0, i = 0; i < pin_len; i++) { for (j = 0; j < channels; j++) { in[MAX_SAMPLES * j + i] = ibuf[k++]; } } resample_process(&r, src, &pin_len, dst, &pout_len); queued = in_len - pin_len; if (queued) memmove(ibuf, &ibuf[pin_len * channels], queued * channels * sizeof(float)); if (pout_len > 0) { for (k = 0, i = 0; i < pout_len; i++) { for (j = 0; j < channels; j++) { obuf[k++] = out[MAX_SAMPLES * j + i]; } } pout_len = sf_writef_float(d->ofile, obuf, pout_len); written += pout_len; } } if (d->verbose) fprintf(stdout, "read %zu samples, wrote %zu samples\n", read, written); return 0; } int main(int argc, char *argv[]) { int c; int longopt_index = 0, ret; struct data data; spa_zero(data); logger.log.level = SPA_LOG_LEVEL_DEBUG; data.quality = -1; while ((c = getopt_long(argc, argv, OPTIONS, long_options, &longopt_index)) != -1) { switch (c) { case 'h': show_usage(argv[0], false); return EXIT_SUCCESS; case 'v': data.verbose = true; break; case 'r': ret = atoi(optarg); if (ret <= 0) { fprintf(stderr, "error: bad rate %s\n", optarg); goto error_usage; } data.rate = ret; break; case 'f': ret = sf_str_to_fmt(optarg); if (ret < 0) { fprintf(stderr, "error: bad format %s\n", optarg); goto error_usage; } data.format = ret; break; case 'q': ret = atoi(optarg); if (ret < 0) { fprintf(stderr, "error: bad quality %s\n", optarg); goto error_usage; } data.quality = ret; break; case 'c': data.cpu_flags = strtol(optarg, NULL, 0); break; case 'u': data.config.cutoff = strtod(optarg, NULL); fprintf(stderr, "%f\n", data.config.cutoff); break; case 'w': data.config.window = resample_window_from_label(optarg); break; case 'p': { char *eq; if ((eq = strchr(optarg, '=')) != NULL) { uint32_t idx; *eq = 0; idx = resample_param_from_label(optarg); data.config.params[idx] = atof(eq+1); } break; } case 't': data.config.n_taps = atoi(optarg); break; default: fprintf(stderr, "error: unknown option '%c'\n", c); goto error_usage; } } if (optind + 1 >= argc) { fprintf(stderr, "error: filename arguments missing (%d %d)\n", optind, argc); goto error_usage; } data.iname = argv[optind++]; data.oname = argv[optind++]; if (open_files(&data) < 0) return EXIT_FAILURE; do_conversion(&data); close_files(&data); return 0; error_usage: show_usage(argv[0], true); return EXIT_FAILURE; } test-audioadapter.c000066400000000000000000000162211511204443500311520ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/audioconvert/* Spa */ /* SPDX-FileCopyrightText: Copyright © 2019 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include SPA_LOG_IMPL(logger); extern const struct spa_handle_factory test_source_factory; struct context { struct spa_handle *follower_handle; struct spa_node *follower_node; struct spa_handle *adapter_handle; struct spa_node *adapter_node; }; static const struct spa_handle_factory *find_factory(const char *name) { uint32_t index = 0; const struct spa_handle_factory *factory; while (spa_handle_factory_enum(&factory, &index) == 1) { if (spa_streq(factory->name, name)) return factory; } return NULL; } static int setup_context(struct context *ctx) { size_t size; int res; struct spa_support support[1]; struct spa_dict_item items[1]; const struct spa_handle_factory *factory; char value[32]; void *iface; logger.log.level = SPA_LOG_LEVEL_TRACE; support[0] = SPA_SUPPORT_INIT(SPA_TYPE_INTERFACE_Log, &logger.log); /* make follower */ factory = &test_source_factory; size = spa_handle_factory_get_size(factory, NULL); ctx->follower_handle = calloc(1, size); spa_assert_se(ctx->follower_handle != NULL); res = spa_handle_factory_init(factory, ctx->follower_handle, NULL, support, 1); spa_assert_se(res >= 0); res = spa_handle_get_interface(ctx->follower_handle, SPA_TYPE_INTERFACE_Node, &iface); spa_assert_se(res >= 0); ctx->follower_node = iface; /* make adapter */ factory = find_factory(SPA_NAME_AUDIO_ADAPT); spa_assert_se(factory != NULL); size = spa_handle_factory_get_size(factory, NULL); ctx->adapter_handle = calloc(1, size); spa_assert_se(ctx->adapter_handle != NULL); snprintf(value, sizeof(value), "pointer:%p", ctx->follower_node); items[0] = SPA_DICT_ITEM_INIT("audio.adapt.follower", value); res = spa_handle_factory_init(factory, ctx->adapter_handle, &SPA_DICT_INIT(items, 1), support, 1); spa_assert_se(res >= 0); res = spa_handle_get_interface(ctx->adapter_handle, SPA_TYPE_INTERFACE_Node, &iface); spa_assert_se(res >= 0); ctx->adapter_node = iface; return 0; } static int clean_context(struct context *ctx) { spa_handle_clear(ctx->adapter_handle); spa_handle_clear(ctx->follower_handle); free(ctx->adapter_handle); free(ctx->follower_handle); return 0; } static void node_info(void *data, const struct spa_node_info *info) { fprintf(stderr, "input %d, output %d\n", info->max_input_ports, info->max_output_ports); spa_assert_se(info->max_input_ports == 0); spa_assert_se(info->max_output_ports > 0); } static void port_info_none(void *data, enum spa_direction direction, uint32_t port, const struct spa_port_info *info) { spa_assert_not_reached(); } static int test_init_state(struct context *ctx) { struct spa_hook listener; static const struct spa_node_events init_events = { SPA_VERSION_NODE_EVENTS, .info = node_info, .port_info = port_info_none, }; spa_zero(listener); spa_node_add_listener(ctx->adapter_node, &listener, &init_events, ctx); spa_hook_remove(&listener); return 0; } static void port_info_5_1(void *data, enum spa_direction direction, uint32_t port, const struct spa_port_info *info) { spa_assert_se(direction == SPA_DIRECTION_OUTPUT); spa_assert_se(port < 6); } static void port_info_1_1(void *data, enum spa_direction direction, uint32_t port, const struct spa_port_info *info) { spa_assert_se(direction == SPA_DIRECTION_OUTPUT); spa_assert_se(port < 2); } static int test_split_setup(struct context *ctx) { struct spa_pod_builder b = { 0 }; uint8_t buffer[1024]; struct spa_pod *param; struct spa_audio_info_raw info; int res; struct spa_hook listener; static const struct spa_node_events node_events = { SPA_VERSION_NODE_EVENTS, .port_info = port_info_5_1, }; /* external format */ spa_zero(info); info.format = SPA_AUDIO_FORMAT_F32P; info.channels = 6; info.rate = 48000; info.position[0] = SPA_AUDIO_CHANNEL_FL; info.position[1] = SPA_AUDIO_CHANNEL_FR; info.position[2] = SPA_AUDIO_CHANNEL_FC; info.position[3] = SPA_AUDIO_CHANNEL_LFE; info.position[4] = SPA_AUDIO_CHANNEL_SL; info.position[5] = SPA_AUDIO_CHANNEL_SR; spa_pod_builder_init(&b, buffer, sizeof(buffer)); param = spa_format_audio_raw_build(&b, SPA_PARAM_Format, &info); spa_log_debug(&logger.log, "set profile %d@%d", info.channels, info.rate); param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamPortConfig, SPA_PARAM_PortConfig, SPA_PARAM_PORT_CONFIG_direction, SPA_POD_Id(SPA_DIRECTION_OUTPUT), SPA_PARAM_PORT_CONFIG_mode, SPA_POD_Id(SPA_PARAM_PORT_CONFIG_MODE_dsp), SPA_PARAM_PORT_CONFIG_format, SPA_POD_Pod(param)); res = spa_node_set_param(ctx->adapter_node, SPA_PARAM_PortConfig, 0, param); spa_assert_se(res == 0); spa_zero(listener); spa_node_add_listener(ctx->adapter_node, &listener, &node_events, ctx); spa_hook_remove(&listener); /* internal format */ spa_pod_builder_init(&b, buffer, sizeof(buffer)); spa_zero(info); info.format = SPA_AUDIO_FORMAT_S16; info.rate = 44100; info.channels = 2; info.position[0] = SPA_AUDIO_CHANNEL_FL; info.position[1] = SPA_AUDIO_CHANNEL_FR; param = spa_format_audio_raw_build(&b, SPA_PARAM_Format, &info); spa_log_debug(&logger.log, "set format %d@%d", info.channels, info.rate); res = spa_node_set_param(ctx->adapter_node, SPA_PARAM_Format, 0, param); spa_log_debug(&logger.log, "result %d", res); spa_assert_se(res >= 0); return 0; } static int test_passthrough_setup(struct context *ctx) { struct spa_pod_builder b = { 0 }; uint8_t buffer[1024]; struct spa_pod *param; struct spa_audio_info_raw info; int res; struct spa_hook listener; static const struct spa_node_events node_events = { SPA_VERSION_NODE_EVENTS, .port_info = port_info_1_1, }; /* internal format */ spa_zero(info); info.format = SPA_AUDIO_FORMAT_S16; info.channels = 2; info.rate = 44100; info.position[0] = SPA_AUDIO_CHANNEL_FL; info.position[1] = SPA_AUDIO_CHANNEL_FR; spa_pod_builder_init(&b, buffer, sizeof(buffer)); param = spa_format_audio_raw_build(&b, SPA_PARAM_Format, &info); spa_log_debug(&logger.log, "set profile %d@%d", info.channels, info.rate); param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamPortConfig, SPA_PARAM_PortConfig, SPA_PARAM_PORT_CONFIG_direction, SPA_POD_Id(SPA_DIRECTION_OUTPUT), SPA_PARAM_PORT_CONFIG_mode, SPA_POD_Id(SPA_PARAM_PORT_CONFIG_MODE_passthrough), SPA_PARAM_PORT_CONFIG_format, SPA_POD_Pod(param)); res = spa_node_set_param(ctx->adapter_node, SPA_PARAM_PortConfig, 0, param); spa_assert_se(res == 0); spa_zero(listener); spa_node_add_listener(ctx->adapter_node, &listener, &node_events, ctx); spa_hook_remove(&listener); return 0; } int main(int argc, char *argv[]) { struct context ctx; spa_zero(ctx); setup_context(&ctx); test_init_state(&ctx); test_split_setup(&ctx); test_passthrough_setup(&ctx); clean_context(&ctx); return 0; } test-audioconvert.c000066400000000000000000000775411511204443500312260ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/audioconvert/* Spa */ /* SPDX-FileCopyrightText: Copyright © 2019 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include SPA_LOG_IMPL(logger); extern const struct spa_handle_factory test_source_factory; #define MAX_CHANNELS SPA_AUDIO_MAX_CHANNELS #define MAX_PORTS (MAX_CHANNELS+1) struct context { struct spa_handle *convert_handle; struct spa_node *convert_node; bool got_node_info; uint32_t n_port_info[2]; bool got_port_info[2][MAX_PORTS]; }; static const struct spa_handle_factory *find_factory(const char *name) { uint32_t index = 0; const struct spa_handle_factory *factory; while (spa_handle_factory_enum(&factory, &index) == 1) { if (spa_streq(factory->name, name)) return factory; } return NULL; } static int setup_context(struct context *ctx) { size_t size; int res; struct spa_support support[1]; struct spa_dict_item items[6]; const struct spa_handle_factory *factory; void *iface; logger.log.level = SPA_LOG_LEVEL_TRACE; support[0] = SPA_SUPPORT_INIT(SPA_TYPE_INTERFACE_Log, &logger); /* make convert */ factory = find_factory(SPA_NAME_AUDIO_CONVERT); spa_assert_se(factory != NULL); size = spa_handle_factory_get_size(factory, NULL); ctx->convert_handle = calloc(1, size); spa_assert_se(ctx->convert_handle != NULL); items[0] = SPA_DICT_ITEM_INIT("clock.quantum-limit", "8192"); items[1] = SPA_DICT_ITEM_INIT("channelmix.upmix", "true"); items[2] = SPA_DICT_ITEM_INIT("channelmix.upmix-method", "psd"); items[3] = SPA_DICT_ITEM_INIT("channelmix.lfe-cutoff", "150"); items[4] = SPA_DICT_ITEM_INIT("channelmix.fc-cutoff", "12000"); items[5] = SPA_DICT_ITEM_INIT("channelmix.rear-delay", "12.0"); res = spa_handle_factory_init(factory, ctx->convert_handle, &SPA_DICT_INIT(items, 6), support, 1); spa_assert_se(res >= 0); res = spa_handle_get_interface(ctx->convert_handle, SPA_TYPE_INTERFACE_Node, &iface); spa_assert_se(res >= 0); ctx->convert_node = iface; return 0; } static int clean_context(struct context *ctx) { spa_handle_clear(ctx->convert_handle); free(ctx->convert_handle); return 0; } static void node_info_check(void *data, const struct spa_node_info *info) { struct context *ctx = data; fprintf(stderr, "input %d, output %d\n", info->max_input_ports, info->max_output_ports); spa_assert_se(info->max_input_ports == MAX_PORTS); spa_assert_se(info->max_output_ports == MAX_PORTS); ctx->got_node_info = true; } static void port_info_check(void *data, enum spa_direction direction, uint32_t port, const struct spa_port_info *info) { struct context *ctx = data; fprintf(stderr, "port %d %d %p\n", direction, port, info); ctx->got_port_info[direction][port] = true; ctx->n_port_info[direction]++; } static int test_init_state(struct context *ctx) { struct spa_hook listener; static const struct spa_node_events init_events = { SPA_VERSION_NODE_EVENTS, .info = node_info_check, .port_info = port_info_check, }; spa_zero(ctx->got_node_info); spa_zero(ctx->n_port_info); spa_zero(ctx->got_port_info); spa_zero(listener); spa_node_add_listener(ctx->convert_node, &listener, &init_events, ctx); spa_hook_remove(&listener); spa_assert_se(ctx->got_node_info); spa_assert_se(ctx->n_port_info[0] == 1); spa_assert_se(ctx->n_port_info[1] == 1); spa_assert_se(ctx->got_port_info[0][0] == true); spa_assert_se(ctx->got_port_info[1][0] == true); return 0; } static int test_set_in_format(struct context *ctx) { struct spa_pod_builder b = { 0 }; uint8_t buffer[1024]; struct spa_pod *param; struct spa_audio_info_raw info; int res; /* other format */ spa_pod_builder_init(&b, buffer, sizeof(buffer)); info = (struct spa_audio_info_raw) { .format = SPA_AUDIO_FORMAT_S16, .rate = 44100, .channels = 2, .position = { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, } }; param = spa_format_audio_raw_build(&b, SPA_PARAM_Format, &info); res = spa_node_port_set_param(ctx->convert_node, SPA_DIRECTION_INPUT, 0, SPA_PARAM_Format, 0, param); spa_assert_se(res == 0); return 0; } static int test_split_setup1(struct context *ctx) { struct spa_pod_builder b = { 0 }; uint8_t buffer[1024]; struct spa_pod *param; struct spa_audio_info_raw info; int res; struct spa_hook listener; static const struct spa_node_events node_events = { SPA_VERSION_NODE_EVENTS, .port_info = port_info_check, }; spa_zero(listener); spa_node_add_listener(ctx->convert_node, &listener, &node_events, ctx); /* port config, output as DSP */ spa_zero(info); info.format = SPA_AUDIO_FORMAT_F32P; info.rate = 48000; info.channels = 6; info.position[0] = SPA_AUDIO_CHANNEL_FL; info.position[1] = SPA_AUDIO_CHANNEL_FR; info.position[2] = SPA_AUDIO_CHANNEL_FC; info.position[3] = SPA_AUDIO_CHANNEL_LFE; info.position[4] = SPA_AUDIO_CHANNEL_SL; info.position[5] = SPA_AUDIO_CHANNEL_SR; spa_pod_builder_init(&b, buffer, sizeof(buffer)); param = spa_format_audio_raw_build(&b, SPA_PARAM_Format, &info); param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamPortConfig, SPA_PARAM_PortConfig, SPA_PARAM_PORT_CONFIG_direction, SPA_POD_Id(SPA_DIRECTION_OUTPUT), SPA_PARAM_PORT_CONFIG_mode, SPA_POD_Id(SPA_PARAM_PORT_CONFIG_MODE_dsp), SPA_PARAM_PORT_CONFIG_format, SPA_POD_Pod(param)); res = spa_node_set_param(ctx->convert_node, SPA_PARAM_PortConfig, 0, param); spa_assert_se(res == 0); spa_hook_remove(&listener); return 0; } static int test_split_setup2(struct context *ctx) { struct spa_pod_builder b = { 0 }; uint8_t buffer[1024]; struct spa_pod *param; struct spa_audio_info_raw info; int res; struct spa_hook listener; static const struct spa_node_events node_events = { SPA_VERSION_NODE_EVENTS, .port_info = port_info_check, }; spa_zero(listener); spa_node_add_listener(ctx->convert_node, &listener, &node_events, ctx); /* port config, output as DSP */ spa_zero(info); info.format = SPA_AUDIO_FORMAT_F32P; info.rate = 48000; info.channels = 4; info.position[0] = SPA_AUDIO_CHANNEL_FL; info.position[1] = SPA_AUDIO_CHANNEL_FR; info.position[2] = SPA_AUDIO_CHANNEL_RL; info.position[3] = SPA_AUDIO_CHANNEL_RR; spa_pod_builder_init(&b, buffer, sizeof(buffer)); param = spa_format_audio_raw_build(&b, SPA_PARAM_Format, &info); param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamPortConfig, SPA_PARAM_PortConfig, SPA_PARAM_PORT_CONFIG_direction, SPA_POD_Id(SPA_DIRECTION_OUTPUT), SPA_PARAM_PORT_CONFIG_mode, SPA_POD_Id(SPA_PARAM_PORT_CONFIG_MODE_dsp), SPA_PARAM_PORT_CONFIG_format, SPA_POD_Pod(param)); res = spa_node_set_param(ctx->convert_node, SPA_PARAM_PortConfig, 0, param); spa_assert_se(res == 0); spa_hook_remove(&listener); return 0; } static int test_convert_setup1(struct context *ctx) { struct spa_pod_builder b = { 0 }; uint8_t buffer[1024]; struct spa_pod *param; int res; struct spa_hook listener; static const struct spa_node_events node_events = { SPA_VERSION_NODE_EVENTS, .port_info = port_info_check, }; spa_zero(listener); spa_node_add_listener(ctx->convert_node, &listener, &node_events, ctx); /* port config, output convert */ spa_pod_builder_init(&b, buffer, sizeof(buffer)); param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamPortConfig, SPA_PARAM_PortConfig, SPA_PARAM_PORT_CONFIG_direction, SPA_POD_Id(SPA_DIRECTION_OUTPUT), SPA_PARAM_PORT_CONFIG_mode, SPA_POD_Id(SPA_PARAM_PORT_CONFIG_MODE_convert)); res = spa_node_set_param(ctx->convert_node, SPA_PARAM_PortConfig, 0, param); spa_assert_se(res == 0); spa_hook_remove(&listener); return 0; } static int test_set_out_format(struct context *ctx) { struct spa_pod_builder b = { 0 }; uint8_t buffer[1024]; struct spa_pod *param; struct spa_audio_info_raw info; int res; /* out format */ spa_pod_builder_init(&b, buffer, sizeof(buffer)); info = (struct spa_audio_info_raw) { .format = SPA_AUDIO_FORMAT_S32P, .rate = 96000, .channels = 8, .position = { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_LFE, SPA_AUDIO_CHANNEL_SL, SPA_AUDIO_CHANNEL_SR, SPA_AUDIO_CHANNEL_RL, SPA_AUDIO_CHANNEL_RR, } }; param = spa_format_audio_raw_build(&b, SPA_PARAM_Format, &info); res = spa_node_port_set_param(ctx->convert_node, SPA_DIRECTION_OUTPUT, 0, SPA_PARAM_Format, 0, param); spa_assert_se(res == 0); return 0; } static int test_merge_setup1(struct context *ctx) { struct spa_pod_builder b = { 0 }; uint8_t buffer[1024]; struct spa_pod *param; struct spa_audio_info_raw info; int res; struct spa_hook listener; static const struct spa_node_events node_events = { SPA_VERSION_NODE_EVENTS, .port_info = port_info_check, }; spa_zero(listener); spa_node_add_listener(ctx->convert_node, &listener, &node_events, ctx); /* port config, output as DSP */ spa_zero(info); info.format = SPA_AUDIO_FORMAT_F32P; info.rate = 48000; info.channels = 6; info.position[0] = SPA_AUDIO_CHANNEL_FL; info.position[1] = SPA_AUDIO_CHANNEL_FR; info.position[2] = SPA_AUDIO_CHANNEL_FC; info.position[3] = SPA_AUDIO_CHANNEL_LFE; info.position[4] = SPA_AUDIO_CHANNEL_RL; info.position[5] = SPA_AUDIO_CHANNEL_RR; spa_pod_builder_init(&b, buffer, sizeof(buffer)); param = spa_format_audio_raw_build(&b, SPA_PARAM_Format, &info); param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamPortConfig, SPA_PARAM_PortConfig, SPA_PARAM_PORT_CONFIG_direction, SPA_POD_Id(SPA_DIRECTION_INPUT), SPA_PARAM_PORT_CONFIG_mode, SPA_POD_Id(SPA_PARAM_PORT_CONFIG_MODE_dsp), SPA_PARAM_PORT_CONFIG_format, SPA_POD_Pod(param)); res = spa_node_set_param(ctx->convert_node, SPA_PARAM_PortConfig, 0, param); spa_assert_se(res == 0); spa_hook_remove(&listener); return 0; } static int test_set_out_format2(struct context *ctx) { struct spa_pod_builder b = { 0 }; uint8_t buffer[1024]; struct spa_pod *param; struct spa_audio_info_raw info; int res; /* out format */ spa_pod_builder_init(&b, buffer, sizeof(buffer)); info = (struct spa_audio_info_raw) { .format = SPA_AUDIO_FORMAT_S16, .rate = 32000, .channels = 2, .position = { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, } }; param = spa_format_audio_raw_build(&b, SPA_PARAM_Format, &info); res = spa_node_port_set_param(ctx->convert_node, SPA_DIRECTION_OUTPUT, 0, SPA_PARAM_Format, 0, param); spa_assert_se(res == 0); return 0; } static int test_merge_setup2(struct context *ctx) { struct spa_pod_builder b = { 0 }; uint8_t buffer[1024]; struct spa_pod *param; struct spa_audio_info_raw info; int res; struct spa_hook listener; static const struct spa_node_events node_events = { SPA_VERSION_NODE_EVENTS, .port_info = port_info_check, }; spa_zero(listener); spa_node_add_listener(ctx->convert_node, &listener, &node_events, ctx); /* port config, output as DSP */ spa_zero(info); info.format = SPA_AUDIO_FORMAT_F32P; info.rate = 96000; info.channels = 4; info.position[0] = SPA_AUDIO_CHANNEL_FL; info.position[1] = SPA_AUDIO_CHANNEL_FR; info.position[2] = SPA_AUDIO_CHANNEL_FC; info.position[3] = SPA_AUDIO_CHANNEL_LFE; spa_pod_builder_init(&b, buffer, sizeof(buffer)); param = spa_format_audio_raw_build(&b, SPA_PARAM_Format, &info); param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamPortConfig, SPA_PARAM_PortConfig, SPA_PARAM_PORT_CONFIG_direction, SPA_POD_Id(SPA_DIRECTION_INPUT), SPA_PARAM_PORT_CONFIG_mode, SPA_POD_Id(SPA_PARAM_PORT_CONFIG_MODE_dsp), SPA_PARAM_PORT_CONFIG_format, SPA_POD_Pod(param)); res = spa_node_set_param(ctx->convert_node, SPA_PARAM_PortConfig, 0, param); spa_assert_se(res == 0); spa_hook_remove(&listener); return 0; } static int test_convert_setup2(struct context *ctx) { struct spa_pod_builder b = { 0 }; uint8_t buffer[1024]; struct spa_pod *param; int res; struct spa_hook listener; static const struct spa_node_events node_events = { SPA_VERSION_NODE_EVENTS, .port_info = port_info_check, }; spa_zero(listener); spa_node_add_listener(ctx->convert_node, &listener, &node_events, ctx); /* port config, input convert */ spa_pod_builder_init(&b, buffer, sizeof(buffer)); param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamPortConfig, SPA_PARAM_PortConfig, SPA_PARAM_PORT_CONFIG_direction, SPA_POD_Id(SPA_DIRECTION_INPUT), SPA_PARAM_PORT_CONFIG_mode, SPA_POD_Id(SPA_PARAM_PORT_CONFIG_MODE_convert)); res = spa_node_set_param(ctx->convert_node, SPA_PARAM_PortConfig, 0, param); spa_assert_se(res == 0); spa_hook_remove(&listener); return 0; } static int test_set_in_format2(struct context *ctx) { struct spa_pod_builder b = { 0 }; uint8_t buffer[1024]; struct spa_pod *param; struct spa_audio_info_raw info; int res; /* other format */ spa_pod_builder_init(&b, buffer, sizeof(buffer)); info = (struct spa_audio_info_raw) { .format = SPA_AUDIO_FORMAT_S24, .rate = 48000, .channels = 3, .position = { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, SPA_AUDIO_CHANNEL_LFE, } }; param = spa_format_audio_raw_build(&b, SPA_PARAM_Format, &info); res = spa_node_port_set_param(ctx->convert_node, SPA_DIRECTION_INPUT, 0, SPA_PARAM_Format, 0, param); spa_assert_se(res == 0); return 0; } static int setup_direction(struct context *ctx, enum spa_direction direction, uint32_t mode, struct spa_audio_info_raw *info) { struct spa_pod_builder b = { 0 }; uint8_t buffer[1024]; struct spa_pod *param, *format; int res; uint32_t i; spa_pod_builder_init(&b, buffer, sizeof(buffer)); format = spa_format_audio_raw_build(&b, SPA_PARAM_Format, info); switch (mode) { case SPA_PARAM_PORT_CONFIG_MODE_dsp: param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamPortConfig, SPA_PARAM_PortConfig, SPA_PARAM_PORT_CONFIG_direction, SPA_POD_Id(direction), SPA_PARAM_PORT_CONFIG_mode, SPA_POD_Id(mode), SPA_PARAM_PORT_CONFIG_format, SPA_POD_Pod(format)); break; case SPA_PARAM_PORT_CONFIG_MODE_convert: param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamPortConfig, SPA_PARAM_PortConfig, SPA_PARAM_PORT_CONFIG_direction, SPA_POD_Id(direction), SPA_PARAM_PORT_CONFIG_mode, SPA_POD_Id(mode)); break; default: return -EINVAL; } res = spa_node_set_param(ctx->convert_node, SPA_PARAM_PortConfig, 0, param); spa_assert_se(res == 0); switch (mode) { case SPA_PARAM_PORT_CONFIG_MODE_convert: res = spa_node_port_set_param(ctx->convert_node, direction, 0, SPA_PARAM_Format, 0, format); spa_assert_se(res == 0); break; case SPA_PARAM_PORT_CONFIG_MODE_dsp: spa_pod_builder_init(&b, buffer, sizeof(buffer)); format = spa_format_audio_dsp_build(&b, SPA_PARAM_Format, &SPA_AUDIO_INFO_DSP_INIT( .format = SPA_AUDIO_FORMAT_F32P)); for (i = 0; i < info->channels; i++) { res = spa_node_port_set_param(ctx->convert_node, direction, i, SPA_PARAM_Format, 0, format); spa_assert_se(res == 0); } break; default: return -EINVAL; } return 0; } struct buffer { struct spa_buffer buffer; struct spa_data datas[MAX_PORTS]; struct spa_chunk chunks[MAX_PORTS]; }; struct data { uint32_t mode; struct spa_audio_info_raw info; uint32_t ports; uint32_t planes; const void *data[MAX_PORTS]; uint32_t size; }; static int run_convert(struct context *ctx, struct data *in_data, struct data *out_data) { struct spa_command cmd; int res; uint32_t i, j, k; struct buffer in_buffers[in_data->ports]; struct buffer out_buffers[out_data->ports]; struct spa_io_buffers in_io[in_data->ports]; struct spa_io_buffers out_io[out_data->ports]; setup_direction(ctx, SPA_DIRECTION_INPUT, in_data->mode, &in_data->info); setup_direction(ctx, SPA_DIRECTION_OUTPUT, out_data->mode, &out_data->info); cmd = SPA_NODE_COMMAND_INIT(SPA_NODE_COMMAND_Start); res = spa_node_send_command(ctx->convert_node, &cmd); spa_assert_se(res == 0); for (i = 0, k = 0; i < in_data->ports; i++) { struct buffer *b = &in_buffers[i]; struct spa_buffer *buffers[1]; spa_zero(*b); b->buffer.datas = b->datas; b->buffer.n_datas = in_data->planes; for (j = 0; j < in_data->planes; j++, k++) { b->datas[j].type = SPA_DATA_MemPtr; b->datas[j].flags = SPA_DATA_FLAG_READABLE; b->datas[j].fd = -1; b->datas[j].mapoffset = 0; b->datas[j].maxsize = in_data->size; b->datas[j].data = (void *)in_data->data[k]; b->datas[j].chunk = &b->chunks[j]; b->datas[j].chunk->offset = 0; b->datas[j].chunk->size = in_data->size; b->datas[j].chunk->stride = 0; } buffers[0] = &b->buffer; res = spa_node_port_use_buffers(ctx->convert_node, SPA_DIRECTION_INPUT, i, 0, buffers, 1); spa_assert_se(res == 0); in_io[i].status = SPA_STATUS_HAVE_DATA; in_io[i].buffer_id = 0; res = spa_node_port_set_io(ctx->convert_node, SPA_DIRECTION_INPUT, i, SPA_IO_Buffers, &in_io[i], sizeof(in_io[i])); spa_assert_se(res == 0); } for (i = 0; i < out_data->ports; i++) { struct buffer *b = &out_buffers[i]; struct spa_buffer *buffers[1]; spa_zero(*b); b->buffer.datas = b->datas; b->buffer.n_datas = out_data->planes; for (j = 0; j < out_data->planes; j++) { b->datas[j].type = SPA_DATA_MemPtr; b->datas[j].flags = SPA_DATA_FLAG_READWRITE; b->datas[j].fd = -1; b->datas[j].mapoffset = 0; b->datas[j].maxsize = out_data->size; b->datas[j].data = calloc(1, out_data->size); b->datas[j].chunk = &b->chunks[j]; b->datas[j].chunk->offset = 0; b->datas[j].chunk->size = 0; b->datas[j].chunk->stride = 0; } buffers[0] = &b->buffer; res = spa_node_port_use_buffers(ctx->convert_node, SPA_DIRECTION_OUTPUT, i, 0, buffers, 1); spa_assert_se(res == 0); out_io[i].status = SPA_STATUS_NEED_DATA; out_io[i].buffer_id = -1; res = spa_node_port_set_io(ctx->convert_node, SPA_DIRECTION_OUTPUT, i, SPA_IO_Buffers, &out_io[i], sizeof(out_io[i])); spa_assert_se(res == 0); } res = spa_node_process(ctx->convert_node); spa_assert_se(res == (SPA_STATUS_NEED_DATA | SPA_STATUS_HAVE_DATA)); for (i = 0, k = 0; i < out_data->ports; i++) { struct buffer *b = &out_buffers[i]; spa_assert_se(out_io[i].status == SPA_STATUS_HAVE_DATA); spa_assert_se(out_io[i].buffer_id == 0); for (j = 0; j < out_data->planes; j++, k++) { spa_assert_se(b->datas[j].chunk->offset == 0); spa_assert_se(b->datas[j].chunk->size == out_data->size); res = memcmp(b->datas[j].data, out_data->data[k], out_data->size); if (res != 0) { fprintf(stderr, "error port %d plane %d\n", i, j); spa_debug_log_mem(&logger.log, SPA_LOG_LEVEL_WARN, 0, b->datas[j].data, out_data->size); spa_debug_log_mem(&logger.log, SPA_LOG_LEVEL_WARN, 2, out_data->data[k], out_data->size); } spa_assert_se(res == 0); free(b->datas[j].data); } } cmd = SPA_NODE_COMMAND_INIT(SPA_NODE_COMMAND_Suspend); res = spa_node_send_command(ctx->convert_node, &cmd); spa_assert_se(res == 0); return 0; } static const float data_f32p_1[] = { 0.1f, 0.1f, 0.1f, 0.1f }; static const float data_f32p_2[] = { 0.2f, 0.2f, 0.2f, 0.2f }; static const float data_f32p_3[] = { 0.3f, 0.3f, 0.3f, 0.3f }; static const float data_f32p_4[] = { 0.4f, 0.4f, 0.4f, 0.4f }; static const float data_f32p_5[] = { 0.5f, 0.5f, 0.5f, 0.5f }; static const float data_f32p_5_6p1[] = { 0.953553438f, 0.953553438f, 0.953553438f, 0.953553438f }; static const float data_f32p_6[] = { 0.6f, 0.6f, 0.6f, 0.6f }; static const float data_f32p_6_6p1[] = { 1.053553343f, 1.053553343f, 1.053553343f, 1.053553343f }; static const float data_f32p_7[] = { 0.7f, 0.7f, 0.7f, 0.7f }; static const float data_f32p_8[] = { 0.8f, 0.8f, 0.8f, 0.8f }; static const float data_f32_5p1[] = { 0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f, 0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f, 0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f, 0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f }; static const float data_f32_6p1[] = { 0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f, 0.7f, 0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f, 0.7f, 0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f, 0.7f, 0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f, 0.7f }; static const float data_f32_6p1_from_5p1[] = { 0.1f, 0.2f, 0.3f, 0.4f, 0.55f, 0.5f, 0.6f, 0.1f, 0.2f, 0.3f, 0.4f, 0.55f, 0.5f, 0.6f, 0.1f, 0.2f, 0.3f, 0.4f, 0.55f, 0.5f, 0.6f, 0.1f, 0.2f, 0.3f, 0.4f, 0.55f, 0.5f, 0.6f }; static const float data_f32_7p1_remapped[] = { 0.1f, 0.2f, 0.5f, 0.6f, 0.7f, 0.8f, 0.3f, 0.4f, 0.1f, 0.2f, 0.5f, 0.6f, 0.7f, 0.8f, 0.3f, 0.4f, 0.1f, 0.2f, 0.5f, 0.6f, 0.7f, 0.8f, 0.3f, 0.4f, 0.1f, 0.2f, 0.5f, 0.6f, 0.7f, 0.8f, 0.3f, 0.4f }; static const float data_f32_5p1_remapped[] = { 0.1f, 0.2f, 0.5f, 0.6f, 0.3f, 0.4f, 0.1f, 0.2f, 0.5f, 0.6f, 0.3f, 0.4f, 0.1f, 0.2f, 0.5f, 0.6f, 0.3f, 0.4f, 0.1f, 0.2f, 0.5f, 0.6f, 0.3f, 0.4f }; struct data dsp_5p1 = { .mode = SPA_PARAM_PORT_CONFIG_MODE_dsp, .info = SPA_AUDIO_INFO_RAW_INIT( .format = SPA_AUDIO_FORMAT_F32, .rate = 48000, .channels = 6, .position = { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_LFE, SPA_AUDIO_CHANNEL_RL, SPA_AUDIO_CHANNEL_RR, }), .ports = 6, .planes = 1, .data = { data_f32p_1, data_f32p_2, data_f32p_3, data_f32p_4, data_f32p_5, data_f32p_6, }, .size = sizeof(float) * 4 }; struct data dsp_5p1_from_6p1 = { .mode = SPA_PARAM_PORT_CONFIG_MODE_dsp, .info = SPA_AUDIO_INFO_RAW_INIT( .format = SPA_AUDIO_FORMAT_F32, .rate = 48000, .channels = 6, .position = { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_LFE, SPA_AUDIO_CHANNEL_RL, SPA_AUDIO_CHANNEL_RR, }), .ports = 6, .planes = 1, .data = { data_f32p_1, data_f32p_2, data_f32p_3, data_f32p_4, data_f32p_5_6p1, data_f32p_6_6p1, }, .size = sizeof(float) * 4 }; struct data dsp_5p1_remapped = { .mode = SPA_PARAM_PORT_CONFIG_MODE_dsp, .info = SPA_AUDIO_INFO_RAW_INIT( .format = SPA_AUDIO_FORMAT_F32, .rate = 48000, .channels = 6, .position = { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, SPA_AUDIO_CHANNEL_RL, SPA_AUDIO_CHANNEL_RR, SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_LFE, }), .ports = 6, .planes = 1, .data = { data_f32p_1, data_f32p_2, data_f32p_5, data_f32p_6, data_f32p_3, data_f32p_4, }, .size = sizeof(float) * 4 }; struct data dsp_5p1_remapped_from_6p1 = { .mode = SPA_PARAM_PORT_CONFIG_MODE_dsp, .info = SPA_AUDIO_INFO_RAW_INIT( .format = SPA_AUDIO_FORMAT_F32, .rate = 48000, .channels = 6, .position = { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, SPA_AUDIO_CHANNEL_RL, SPA_AUDIO_CHANNEL_RR, SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_LFE, }), .ports = 6, .planes = 1, .data = { data_f32p_1, data_f32p_2, data_f32p_5_6p1, data_f32p_6_6p1, data_f32p_3, data_f32p_4, }, .size = sizeof(float) * 4 }; struct data dsp_6p1 = { .mode = SPA_PARAM_PORT_CONFIG_MODE_dsp, .info = SPA_AUDIO_INFO_RAW_INIT( .format = SPA_AUDIO_FORMAT_F32, .rate = 48000, .channels = 7, .position = { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_LFE, SPA_AUDIO_CHANNEL_RC, SPA_AUDIO_CHANNEL_RL, SPA_AUDIO_CHANNEL_RR, }), .ports = 7, .planes = 1, .data = { data_f32p_1, data_f32p_2, data_f32p_3, data_f32p_4, data_f32p_5, data_f32p_6, data_f32p_7 }, .size = sizeof(float) * 4 }; struct data dsp_6p1_side = { .mode = SPA_PARAM_PORT_CONFIG_MODE_dsp, .info = SPA_AUDIO_INFO_RAW_INIT( .format = SPA_AUDIO_FORMAT_F32, .rate = 48000, .channels = 7, .position = { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_LFE, SPA_AUDIO_CHANNEL_RC, SPA_AUDIO_CHANNEL_SL, SPA_AUDIO_CHANNEL_SR, }), .ports = 7, .planes = 1, .data = { data_f32p_1, data_f32p_2, data_f32p_3, data_f32p_4, data_f32p_5, data_f32p_6, data_f32p_7 }, .size = sizeof(float) * 4 }; struct data dsp_7p1_remapped = { .mode = SPA_PARAM_PORT_CONFIG_MODE_dsp, .info = SPA_AUDIO_INFO_RAW_INIT( .format = SPA_AUDIO_FORMAT_F32, .rate = 48000, .channels = 8, .position = { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_LFE, SPA_AUDIO_CHANNEL_RL, SPA_AUDIO_CHANNEL_RR, SPA_AUDIO_CHANNEL_SL, SPA_AUDIO_CHANNEL_SR, }), .ports = 8, .planes = 1, .data = { data_f32p_1, data_f32p_2, data_f32p_3, data_f32p_4, data_f32p_7, data_f32p_8, data_f32p_5, data_f32p_6 }, .size = sizeof(data_f32p_1) }; struct data dsp_5p1_remapped_2 = { .mode = SPA_PARAM_PORT_CONFIG_MODE_dsp, .info = SPA_AUDIO_INFO_RAW_INIT( .format = SPA_AUDIO_FORMAT_F32, .rate = 48000, .channels = 6, .position = { SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_LFE, SPA_AUDIO_CHANNEL_RL, SPA_AUDIO_CHANNEL_RR, SPA_AUDIO_CHANNEL_FR, SPA_AUDIO_CHANNEL_FL, }), .ports = 6, .planes = 1, .data = { data_f32p_3, data_f32p_4, data_f32p_5, data_f32p_6, data_f32p_2, data_f32p_1, }, .size = sizeof(float) * 4 }; struct data conv_f32_48000_5p1 = { .mode = SPA_PARAM_PORT_CONFIG_MODE_convert, .info = SPA_AUDIO_INFO_RAW_INIT( .format = SPA_AUDIO_FORMAT_F32, .rate = 48000, .channels = 6, .position = { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_LFE, SPA_AUDIO_CHANNEL_RL, SPA_AUDIO_CHANNEL_RR, }), .ports = 1, .planes = 1, .data = { data_f32_5p1 }, .size = sizeof(data_f32_5p1) }; struct data conv_f32_48000_5p1_remapped = { .mode = SPA_PARAM_PORT_CONFIG_MODE_convert, .info = SPA_AUDIO_INFO_RAW_INIT( .format = SPA_AUDIO_FORMAT_F32, .rate = 48000, .channels = 6, .position = { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, SPA_AUDIO_CHANNEL_RL, SPA_AUDIO_CHANNEL_RR, SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_LFE, }), .ports = 1, .planes = 1, .data = { data_f32_5p1_remapped }, .size = sizeof(data_f32_5p1_remapped) }; struct data conv_f32p_48000_5p1 = { .mode = SPA_PARAM_PORT_CONFIG_MODE_convert, .info = SPA_AUDIO_INFO_RAW_INIT( .format = SPA_AUDIO_FORMAT_F32P, .rate = 48000, .channels = 6, .position = { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_LFE, SPA_AUDIO_CHANNEL_RL, SPA_AUDIO_CHANNEL_RR, }), .ports = 1, .planes = 6, .data = { data_f32p_1, data_f32p_2, data_f32p_3, data_f32p_4, data_f32p_5, data_f32p_6, }, .size = sizeof(float) * 4 }; struct data conv_f32_48000_6p1 = { .mode = SPA_PARAM_PORT_CONFIG_MODE_convert, .info = SPA_AUDIO_INFO_RAW_INIT( .format = SPA_AUDIO_FORMAT_F32, .rate = 48000, .channels = 7, .position = { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_LFE, SPA_AUDIO_CHANNEL_RC, SPA_AUDIO_CHANNEL_RL, SPA_AUDIO_CHANNEL_RR, }), .ports = 1, .planes = 1, .data = { data_f32_6p1 }, .size = sizeof(data_f32_6p1) }; struct data conv_f32_48000_6p1_from_5p1 = { .mode = SPA_PARAM_PORT_CONFIG_MODE_convert, .info = SPA_AUDIO_INFO_RAW_INIT( .format = SPA_AUDIO_FORMAT_F32, .rate = 48000, .channels = 7, .position = { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_LFE, SPA_AUDIO_CHANNEL_RC, SPA_AUDIO_CHANNEL_RL, SPA_AUDIO_CHANNEL_RR, }), .ports = 1, .planes = 1, .data = { data_f32_6p1_from_5p1 }, .size = sizeof(data_f32_6p1_from_5p1) }; struct data conv_f32_48000_6p1_side = { .mode = SPA_PARAM_PORT_CONFIG_MODE_convert, .info = SPA_AUDIO_INFO_RAW_INIT( .format = SPA_AUDIO_FORMAT_F32, .rate = 48000, .channels = 7, .position = { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_LFE, SPA_AUDIO_CHANNEL_RC, SPA_AUDIO_CHANNEL_SL, SPA_AUDIO_CHANNEL_SR, }), .ports = 1, .planes = 1, .data = { data_f32_6p1 }, .size = sizeof(data_f32_6p1) }; struct data conv_f32p_48000_6p1 = { .mode = SPA_PARAM_PORT_CONFIG_MODE_convert, .info = SPA_AUDIO_INFO_RAW_INIT( .format = SPA_AUDIO_FORMAT_F32P, .rate = 48000, .channels = 7, .position = { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_LFE, SPA_AUDIO_CHANNEL_RC, SPA_AUDIO_CHANNEL_RL, SPA_AUDIO_CHANNEL_RR, }), .ports = 1, .planes = 7, .data = { data_f32p_1, data_f32p_2, data_f32p_3, data_f32p_4, data_f32p_5, data_f32p_6, data_f32p_7 }, .size = sizeof(float) * 4 }; struct data conv_f32p_48000_5p1_remapped = { .mode = SPA_PARAM_PORT_CONFIG_MODE_convert, .info = SPA_AUDIO_INFO_RAW_INIT( .format = SPA_AUDIO_FORMAT_F32P, .rate = 48000, .channels = 6, .position = { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, SPA_AUDIO_CHANNEL_RL, SPA_AUDIO_CHANNEL_RR, SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_LFE, }), .ports = 1, .planes = 6, .data = { data_f32p_1, data_f32p_2, data_f32p_5, data_f32p_6, data_f32p_3, data_f32p_4, }, .size = sizeof(float) * 4 }; struct data conv_f32_48000_7p1_remapped = { .mode = SPA_PARAM_PORT_CONFIG_MODE_convert, .info = SPA_AUDIO_INFO_RAW_INIT( .format = SPA_AUDIO_FORMAT_F32, .rate = 48000, .channels = 8, .position = { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, SPA_AUDIO_CHANNEL_SL, SPA_AUDIO_CHANNEL_SR, SPA_AUDIO_CHANNEL_RL, SPA_AUDIO_CHANNEL_RR, SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_LFE, }), .ports = 1, .planes = 1, .data = { data_f32_7p1_remapped, }, .size = sizeof(data_f32_7p1_remapped) }; static int test_convert_remap_dsp(struct context *ctx) { run_convert(ctx, &dsp_5p1, &conv_f32_48000_5p1); run_convert(ctx, &dsp_5p1, &conv_f32p_48000_5p1); run_convert(ctx, &dsp_5p1, &conv_f32_48000_5p1_remapped); run_convert(ctx, &dsp_5p1, &conv_f32p_48000_5p1_remapped); run_convert(ctx, &dsp_5p1_remapped, &conv_f32_48000_5p1); run_convert(ctx, &dsp_5p1_remapped, &conv_f32p_48000_5p1); run_convert(ctx, &dsp_5p1_remapped, &conv_f32_48000_5p1_remapped); run_convert(ctx, &dsp_5p1_remapped, &conv_f32p_48000_5p1_remapped); run_convert(ctx, &dsp_5p1_remapped_2, &conv_f32_48000_5p1); run_convert(ctx, &dsp_5p1_remapped_2, &conv_f32p_48000_5p1); run_convert(ctx, &dsp_5p1_remapped_2, &conv_f32_48000_5p1_remapped); run_convert(ctx, &dsp_5p1_remapped_2, &conv_f32p_48000_5p1_remapped); run_convert(ctx, &dsp_6p1, &conv_f32p_48000_6p1); run_convert(ctx, &dsp_6p1, &conv_f32_48000_6p1); run_convert(ctx, &dsp_6p1_side, &conv_f32_48000_6p1_side); run_convert(ctx, &dsp_5p1, &conv_f32_48000_6p1_from_5p1); return 0; } static int test_convert_remap_conv(struct context *ctx) { run_convert(ctx, &conv_f32_48000_5p1, &dsp_5p1); run_convert(ctx, &conv_f32_48000_5p1, &dsp_5p1_remapped); run_convert(ctx, &conv_f32_48000_5p1, &dsp_5p1_remapped_2); run_convert(ctx, &conv_f32p_48000_5p1, &dsp_5p1); run_convert(ctx, &conv_f32p_48000_5p1, &dsp_5p1_remapped); run_convert(ctx, &conv_f32p_48000_5p1, &dsp_5p1_remapped_2); run_convert(ctx, &conv_f32_48000_5p1_remapped, &dsp_5p1); run_convert(ctx, &conv_f32_48000_5p1_remapped, &dsp_5p1_remapped); run_convert(ctx, &conv_f32_48000_5p1_remapped, &dsp_5p1_remapped_2); run_convert(ctx, &conv_f32p_48000_5p1_remapped, &dsp_5p1); run_convert(ctx, &conv_f32p_48000_6p1, &dsp_6p1); run_convert(ctx, &conv_f32_48000_6p1, &dsp_6p1); run_convert(ctx, &conv_f32_48000_6p1_side, &dsp_6p1_side); run_convert(ctx, &conv_f32p_48000_5p1_remapped, &dsp_5p1_remapped); run_convert(ctx, &conv_f32_48000_7p1_remapped, &dsp_7p1_remapped); run_convert(ctx, &conv_f32p_48000_5p1_remapped, &dsp_5p1_remapped_2); run_convert(ctx, &conv_f32_48000_6p1, &dsp_5p1_from_6p1); run_convert(ctx, &conv_f32_48000_6p1_side, &dsp_5p1_from_6p1); run_convert(ctx, &conv_f32_48000_6p1, &dsp_5p1_remapped_from_6p1); return 0; } int main(int argc, char *argv[]) { struct context ctx; spa_zero(ctx); setup_context(&ctx); test_init_state(&ctx); test_set_in_format(&ctx); test_split_setup1(&ctx); test_split_setup2(&ctx); test_convert_setup1(&ctx); test_set_out_format(&ctx); test_merge_setup1(&ctx); test_set_out_format2(&ctx); test_merge_setup2(&ctx); test_convert_setup2(&ctx); test_set_in_format2(&ctx); test_set_out_format(&ctx); test_convert_remap_dsp(&ctx); test_convert_remap_conv(&ctx); clean_context(&ctx); return 0; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/audioconvert/test-channelmix.c000066400000000000000000000316611511204443500307220ustar00rootroot00000000000000/* Spa */ /* SPDX-FileCopyrightText: Copyright © 2019 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include "config.h" #include #include #include #include #include #include #include #include static uint32_t cpu_flags; SPA_LOG_IMPL(logger); #define MATRIX(...) (double[]) { __VA_ARGS__ } #include "test-helper.h" #include "channelmix-ops.c" #define CLOSE_ENOUGH(a,b) (fabs((a)-(b)) < 0.000001f) static void dump_matrix(struct channelmix *mix, double *coeff) { uint32_t i, j; for (i = 0; i < mix->dst_chan; i++) { for (j = 0; j < mix->src_chan; j++) { float v = mix->matrix[i][j]; spa_log_debug(mix->log, "%d %d: %f <-> %f", i, j, v, *coeff); spa_assert_se(CLOSE_ENOUGH(v, (float)*coeff)); coeff++; } } } static void test_mix(uint32_t src_chan, uint32_t src_mask, uint32_t dst_chan, uint32_t dst_mask, uint32_t options, double *coeff) { struct channelmix mix; spa_log_debug(&logger.log, "start %d->%d (%08x -> %08x)", src_chan, dst_chan, src_mask, dst_mask); spa_zero(mix); mix.options = options; mix.src_chan = src_chan; mix.dst_chan = dst_chan; mix.src_mask = src_mask; mix.dst_mask = dst_mask; mix.log = &logger.log; mix.fc_cutoff = 120.0f; mix.lfe_cutoff = 12000.0f; spa_assert_se(channelmix_init(&mix) == 0); channelmix_set_volume(&mix, 1.0f, false, 0, NULL); dump_matrix(&mix, coeff); } static void test_1_N_MONO(void) { test_mix(1, _M(MONO), 2, _M(FL)|_M(FR), 0, MATRIX(1.0, 1.0)); test_mix(1, _M(MONO), 3, _M(FL)|_M(FR)|_M(LFE), 0, MATRIX(1.0, 1.0, 0.0)); test_mix(1, _M(MONO), 3, _M(FL)|_M(FR)|_M(LFE), CHANNELMIX_OPTION_UPMIX, MATRIX(1.0, 1.0, 1.0)); test_mix(1, _M(MONO), 4, _M(FL)|_M(FR)|_M(LFE)|_M(FC), 0, MATRIX(1.0, 1.0, 0.0, 0.0)); test_mix(1, _M(MONO), 4, _M(FL)|_M(FR)|_M(LFE)|_M(FC), CHANNELMIX_OPTION_UPMIX, MATRIX(1.0, 1.0, 1.0, 1.0)); test_mix(1, _M(MONO), 4, _M(FL)|_M(FR)|_M(RL)|_M(RR), 0, MATRIX(1.0, 1.0, 0.0, 0.0)); test_mix(1, _M(MONO), 4, _M(FL)|_M(FR)|_M(RL)|_M(RR), CHANNELMIX_OPTION_UPMIX, MATRIX(1.0, 1.0, 0.0, 0.0)); test_mix(1, _M(MONO), 12, 0, 0, MATRIX(1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0)); } static void test_1_N_FC(void) { test_mix(1, _M(FC), 2, _M(FL)|_M(FR), 0, MATRIX(0.707107, 0.707107)); test_mix(1, _M(FC), 3, _M(FL)|_M(FR)|_M(LFE), 0, MATRIX(0.707107, 0.707107, 0.0)); test_mix(1, _M(FC), 4, _M(FL)|_M(FR)|_M(LFE)|_M(FC), 0, MATRIX(0.0, 0.0, 1.0, 0.0)); test_mix(1, _M(FC), 4, _M(FL)|_M(FR)|_M(RL)|_M(RR), 0, MATRIX(0.707107, 0.707107, 0.0, 0.0)); test_mix(1, _M(FC), 12, 0, 0, MATRIX(1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0)); } static void test_N_1(void) { test_mix(1, _M(MONO), 1, _M(MONO), 0, MATRIX(1.0)); test_mix(1, _M(MONO), 1, _M(FC), 0, MATRIX(1.0)); test_mix(1, _M(FC), 1, _M(MONO), 0, MATRIX(1.0)); test_mix(1, _M(FC), 1, _M(FC), 0, MATRIX(1.0)); test_mix(2, _M(FL)|_M(FR), 1, _M(MONO), 0, MATRIX(0.5, 0.5)); test_mix(12, 0, 1, _M(MONO), 0, MATRIX(0.083333, 0.083333, 0.083333, 0.083333, 0.083333, 0.083333, 0.083333, 0.083333, 0.083333, 0.083333, 0.083333, 0.0833333)); } static void test_2_N(void) { test_mix(2, _M(FL)|_M(FR), 1, _M(MONO), 0, MATRIX(0.5, 0.5)); test_mix(2, _M(FL)|_M(FR), 1, 0, 0, MATRIX(0.5, 0.5)); test_mix(2, _M(FL)|_M(FR), 2, 0, 0, MATRIX(1.0, 0.0, 0.0, 1.0)); test_mix(2, _M(FL)|_M(FR), 2, _M(MONO), 0, MATRIX(1.0, 0.0, 0.0, 1.0)); test_mix(2, _M(FL)|_M(FR), 2, _M(FL)|_M(FR), 0, MATRIX(1.0, 0.0, 0.0, 1.0)); test_mix(2, _M(FL)|_M(FR), 4, _M(FL)|_M(FR)|_M(LFE)|_M(FC), 0, MATRIX(1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0)); test_mix(2, _M(FL)|_M(FR), 4, _M(FL)|_M(FR)|_M(LFE)|_M(FC), CHANNELMIX_OPTION_UPMIX, MATRIX(1.0, 0.0, 0.0, 1.0, 0.707107, 0.707107, 0.5, 0.5)); test_mix(2, _M(FL)|_M(FR), 6, _M(FL)|_M(FR)|_M(LFE)|_M(FC)|_M(SL)|_M(SR), 0, MATRIX(1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0)); test_mix(2, _M(FL)|_M(FR), 6, _M(FL)|_M(FR)|_M(LFE)|_M(FC)|_M(SL)|_M(SR), CHANNELMIX_OPTION_UPMIX, MATRIX(1.0, 0.0, 0.0, 1.0, 0.707107, 0.707107, 0.5, 0.5, 0.0, 0.0, 0.0, 0.0)); } static void test_3p1_N(void) { test_mix(4, _M(FL)|_M(FR)|_M(LFE)|_M(FC), 1, _M(MONO), 0, MATRIX(0.333333, 0.333333, 0.333333, 0.0)); test_mix(4, _M(FL)|_M(FR)|_M(LFE)|_M(FC), 2, _M(FL)|_M(FR), 0, MATRIX(1.0, 0.0, 0.707107, 0.0, 0.0, 1.0, 0.707107, 0.0 )); test_mix(4, _M(FL)|_M(FR)|_M(LFE)|_M(FC), 3, _M(FL)|_M(FR)|_M(LFE), 0, MATRIX(1.0, 0.0, 0.707107, 0.0, 0.0, 1.0, 0.707107, 0.0, 0.0, 0.0, 0.0, 1.0 )); test_mix(4, _M(FL)|_M(FR)|_M(LFE)|_M(FC), 4, _M(FL)|_M(FR)|_M(LFE)|_M(FC), 0, MATRIX(1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0,)); test_mix(4, _M(FL)|_M(FR)|_M(LFE)|_M(FC), 4, _M(FL)|_M(FR)|_M(RL)|_M(RR), 0, MATRIX(1.0, 0.0, 0.707107, 0.0, 0.0, 1.0, 0.707107, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,)); } static void test_4_N(void) { test_mix(4, _M(FL)|_M(FR)|_M(RL)|_M(RR), 1, _M(MONO), 0, MATRIX(0.25, 0.25, 0.25, 0.25)); test_mix(4, _M(FL)|_M(FR)|_M(SL)|_M(SR), 1, _M(MONO), 0, MATRIX(0.25, 0.25, 0.25, 0.25)); test_mix(4, _M(FL)|_M(FR)|_M(RL)|_M(RR), 2, _M(FL)|_M(FR), 0, MATRIX(1.0, 0.0, 0.707107, 0.0, 0.0, 1.0, 0.0, 0.707107)); test_mix(4, _M(FL)|_M(FR)|_M(SL)|_M(SR), 2, _M(FL)|_M(FR), 0, MATRIX(1.0, 0.0, 0.707107, 0.0, 0.0, 1.0, 0.0, 0.707107)); test_mix(4, _M(FL)|_M(FR)|_M(RL)|_M(RR), 3, _M(FL)|_M(FR)|_M(LFE), 0, MATRIX(1.0, 0.0, 0.707107, 0.0, 0.0, 1.0, 0.0, 0.707107, 0.0, 0.0, 0.0, 0.0)); test_mix(4, _M(FL)|_M(FR)|_M(RL)|_M(RR), 4, _M(FL)|_M(FR)|_M(RL)|_M(RR), 0, MATRIX(1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0)); test_mix(4, _M(FL)|_M(FR)|_M(RL)|_M(RR), 4, _M(FL)|_M(FR)|_M(LFE)|_M(FC), 0, MATRIX(1.0, 0.0, 0.707107, 0.0, 0.0, 1.0, 0.0, 0.707107, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0)); test_mix(4, _M(FL)|_M(FR)|_M(RL)|_M(RR), 4, _M(FL)|_M(FR)|_M(LFE)|_M(FC), CHANNELMIX_OPTION_UPMIX, MATRIX(1.0, 0.0, 0.707107, 0.0, 0.0, 1.0, 0.0, 0.707107, 0.707107, 0.707107, 0.0, 0.0, 0.5, 0.5, 0.0, 0.0)); } static void test_5p1_N(void) { test_mix(6, _M(FL)|_M(FR)|_M(LFE)|_M(FC)|_M(SL)|_M(SR), 1, _M(MONO), 0, MATRIX(0.20, 0.20, 0.20, 0.0, 0.20, 0.20)); test_mix(6, _M(FL)|_M(FR)|_M(LFE)|_M(FC)|_M(SL)|_M(SR), 2, _M(FL)|_M(FR), 0, MATRIX(1.0, 0.0, 0.707107, 0.0, 0.707107, 0.0, 0.0, 1.0, 0.707107, 0.0, 0.0, 0.707107)); test_mix(6, _M(FL)|_M(FR)|_M(LFE)|_M(FC)|_M(RL)|_M(RR), 2, _M(FL)|_M(FR), 0, MATRIX(1.0, 0.0, 0.707107, 0.0, 0.707107, 0.0, 0.0, 1.0, 0.707107, 0.0, 0.0, 0.707107)); test_mix(6, _M(FL)|_M(FR)|_M(LFE)|_M(FC)|_M(SL)|_M(SR), 3, _M(FL)|_M(FR)|_M(LFE), 0, MATRIX(1.0, 0.0, 0.707107, 0.0, 0.707107, 0.0, 0.0, 1.0, 0.707107, 0.0, 0.0, 0.707107, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0)); test_mix(6, _M(FL)|_M(FR)|_M(LFE)|_M(FC)|_M(SL)|_M(SR), 4, _M(FL)|_M(FR)|_M(LFE)|_M(FC), 0, MATRIX(1.0, 0.0, 0.0, 0.0, 0.707107, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.707107, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0)); test_mix(6, _M(FL)|_M(FR)|_M(LFE)|_M(FC)|_M(SL)|_M(SR), 4, _M(FL)|_M(FR)|_M(RL)|_M(RR), 0, MATRIX(1.0, 0.0, 0.707107, 0.0, 0.0, 0.0, 0.0, 1.0, 0.707107, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0)); test_mix(6, _M(FL)|_M(FR)|_M(LFE)|_M(FC)|_M(SL)|_M(SR), 5, _M(FL)|_M(FR)|_M(FC)|_M(SL)|_M(SR), 0, MATRIX(1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0)); test_mix(6, _M(FL)|_M(FR)|_M(LFE)|_M(FC)|_M(SL)|_M(SR), 6, _M(FL)|_M(FR)|_M(LFE)|_M(FC)|_M(SL)|_M(SR), 0, MATRIX(1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0)); } static void test_6p1_N(void) { test_mix(7, _M(FL)|_M(FR)|_M(LFE)|_M(FC)|_M(RC)|_M(SL)|_M(SR), 1, _M(MONO), 0, MATRIX(0.166667, 0.166667, 0.166667, 0.0, 0.166667, 0.166667, 0.166667)); test_mix(7, _M(FL)|_M(FR)|_M(LFE)|_M(FC)|_M(SL)|_M(SR)|_M(RC), 6, _M(FL)|_M(FR)|_M(LFE)|_M(FC)|_M(SL)|_M(SR), 0, MATRIX(1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.707107, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.707107)); test_mix(7, _M(FL)|_M(FR)|_M(LFE)|_M(FC)|_M(SL)|_M(SR)|_M(RC), 6, _M(FL)|_M(FR)|_M(LFE)|_M(FC)|_M(RL)|_M(RR), 0, MATRIX(1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.707107, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.707107)); test_mix(7, _M(FL)|_M(FR)|_M(LFE)|_M(FC)|_M(RC)|_M(RL)|_M(RR), 6, _M(FL)|_M(FR)|_M(LFE)|_M(FC)|_M(RL)|_M(RR), 0, MATRIX(1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.707107, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.707107, 0.0, 1.0)); test_mix(7, _M(FL)|_M(FR)|_M(LFE)|_M(FC)|_M(SL)|_M(SR)|_M(RC), 8, _M(FL)|_M(FR)|_M(LFE)|_M(FC)|_M(SL)|_M(SR)|_M(RL)|_M(RR), 0, MATRIX(1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.707107, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.707107)); } static void test_7p1_N(void) { test_mix(8, _M(FL)|_M(FR)|_M(LFE)|_M(FC)|_M(SL)|_M(SR)|_M(RL)|_M(RR), 1, _M(MONO), 0, MATRIX(0.142857, 0.142857, 0.142857, 0.0, 0.142857, 0.142857, 0.142857, 0.142857)); test_mix(8, _M(FL)|_M(FR)|_M(LFE)|_M(FC)|_M(SL)|_M(SR)|_M(RL)|_M(RR), 2, _M(FL)|_M(FR), 0, MATRIX(1.0, 0.0, 0.707107, 0.0, 0.707107, 0.0, 0.707107, 0.0, 0.0, 1.0, 0.707107, 0.0, 0.0, 0.707107, 0.0, 0.707107)); } static void check_samples(float **s1, float **s2, uint32_t n_s, uint32_t n_samples) { uint32_t i, j; for (i = 0; i < n_s; i++) { for (j = 0; j < n_samples; j++) { spa_assert_se(CLOSE_ENOUGH(s1[i][j], s2[i][j])); } } } static void run_n_m_impl(struct channelmix *mix, const void **src, uint32_t n_samples) { uint32_t dst_chan = mix->dst_chan, i; float dst_c_data[dst_chan][n_samples]; float dst_x_data[dst_chan][n_samples]; void *dst_c[dst_chan], *dst_x[dst_chan]; for (i = 0; i < dst_chan; i++) { dst_c[i] = dst_c_data[i]; dst_x[i] = dst_x_data[i]; } channelmix_f32_n_m_c(mix, dst_c, src, n_samples); channelmix_f32_n_m_c(mix, dst_x, src, n_samples); check_samples((float**)dst_c, (float**)dst_x, dst_chan, n_samples); #if defined(HAVE_SSE) if (cpu_flags & SPA_CPU_FLAG_SSE) { channelmix_f32_n_m_sse(mix, dst_x, src, n_samples); check_samples((float**)dst_c, (float**)dst_x, dst_chan, n_samples); } #endif } static void test_n_m_impl(void) { struct channelmix mix; unsigned int i, j; #define N_SAMPLES 251 float src_data[16][N_SAMPLES], *src[16]; spa_log_debug(&logger.log, "start"); for (i = 0; i < 16; i++) { for (j = 0; j < N_SAMPLES; j++) src_data[i][j] = (float)((drand48() - 0.5f) * 2.5f); src[i] = src_data[i]; } spa_zero(mix); mix.src_chan = 16; mix.dst_chan = 12; mix.log = &logger.log; mix.cpu_flags = cpu_flags; spa_assert_se(channelmix_init(&mix) == 0); channelmix_set_volume(&mix, 1.0f, false, 0, NULL); /* identity matrix */ run_n_m_impl(&mix, (const void**)src, N_SAMPLES); /* some zero destination */ mix.matrix_orig[2][2] = 0.0f; mix.matrix_orig[7][7] = 0.0f; channelmix_set_volume(&mix, 1.0f, false, 0, NULL); run_n_m_impl(&mix, (const void**)src, N_SAMPLES); /* random matrix */ for (i = 0; i < mix.dst_chan; i++) { for (j = 0; j < mix.src_chan; j++) { mix.matrix_orig[i][j] = (float)(drand48() - 0.5f); } } channelmix_set_volume(&mix, 1.0f, false, 0, NULL); run_n_m_impl(&mix, (const void**)src, N_SAMPLES); } int main(int argc, char *argv[]) { struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); srand48(SPA_TIMESPEC_TO_NSEC(&ts)); logger.log.level = SPA_LOG_LEVEL_TRACE; cpu_flags = get_cpu_flags(); printf("got CPU flags %d\n", cpu_flags); test_1_N_MONO(); test_1_N_FC(); test_N_1(); test_2_N(); test_3p1_N(); test_4_N(); test_5p1_N(); test_6p1_N(); test_7p1_N(); test_n_m_impl(); return 0; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/audioconvert/test-fmt-ops.c000066400000000000000000000714121511204443500301570ustar00rootroot00000000000000/* Spa */ /* SPDX-FileCopyrightText: Copyright © 2019 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include "config.h" #include #include #include #include #include #include #include #include "test-helper.h" #include "fmt-ops.c" #define N_SAMPLES 253 #define N_CHANNELS 11 static uint32_t cpu_flags; static uint8_t samp_in[N_SAMPLES * 8]; static uint8_t samp_out[N_SAMPLES * 8]; static uint8_t temp_in[N_SAMPLES * N_CHANNELS * 8]; static uint8_t temp_out[N_SAMPLES * N_CHANNELS * 8]; static void compare_mem(int i, int j, const void *m1, const void *m2, size_t size) { int res = memcmp(m1, m2, size); if (res != 0) { fprintf(stderr, "%d %d %zd:\n", i, j, size); spa_debug_mem(0, m1, size); spa_debug_mem(0, m2, size); } spa_assert_se(res == 0); } static void run_test(const char *name, const void *in, size_t in_size, const void *out, size_t out_size, size_t n_samples, bool in_packed, bool out_packed, convert_func_t func) { const void *ip[N_CHANNELS]; void *tp[N_CHANNELS]; int i, j; const uint8_t *in8 = in, *out8 = out; struct convert conv; conv.n_channels = N_CHANNELS; for (j = 0; j < N_SAMPLES; j++) { memcpy(&samp_in[j * in_size], &in8[(j % n_samples) * in_size], in_size); memcpy(&samp_out[j * out_size], &out8[(j % n_samples) * out_size], out_size); } for (j = 0; j < N_CHANNELS; j++) ip[j] = samp_in; if (in_packed) { tp[0] = temp_in; switch(in_size) { case 1: conv_8d_to_8_c(&conv, tp, ip, N_SAMPLES); break; case 2: conv_16d_to_16_c(&conv, tp, ip, N_SAMPLES); break; case 3: conv_24d_to_24_c(&conv, tp, ip, N_SAMPLES); break; case 4: conv_32d_to_32_c(&conv, tp, ip, N_SAMPLES); break; case 8: conv_64d_to_64_c(&conv, tp, ip, N_SAMPLES); break; default: fprintf(stderr, "unknown size %zd\n", in_size); return; } ip[0] = temp_in; } spa_zero(temp_out); for (j = 0; j < N_CHANNELS; j++) tp[j] = &temp_out[j * N_SAMPLES * out_size]; fprintf(stderr, "test %s:\n", name); func(&conv, tp, ip, N_SAMPLES); if (out_packed) { const uint8_t *d = tp[0], *s = samp_out; for (i = 0; i < N_SAMPLES; i++) { for (j = 0; j < N_CHANNELS; j++) { compare_mem(i, j, d, s, out_size); d += out_size; } s += out_size; } } else { for (j = 0; j < N_CHANNELS; j++) { compare_mem(0, j, tp[j], samp_out, N_SAMPLES * out_size); } } } static void test_f32_s8(void) { static const float in[] = { 0.0f, 1.0f, -1.0f, 0.5f, -0.5f, 1.1f, -1.1f, 1.0f/160.f, 1.0f/256.f, -1.0f/160.f, -1.0f/256.f }; static const int8_t out[] = { 0, 127, -128, 64, 192, 127, -128, 1, 0, -1, 0 }; run_test("test_f32_s8", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), true, true, conv_f32_to_s8_c); run_test("test_f32d_s8", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), false, true, conv_f32d_to_s8_c); run_test("test_f32_s8d", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), true, false, conv_f32_to_s8d_c); run_test("test_f32d_s8d", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), false, false, conv_f32d_to_s8d_c); } static void test_s8_f32(void) { static const int8_t in[] = { 0, 127, -128, 64, 192, }; static const float out[] = { 0.0f, 0.9921875f, -1.0f, 0.5f, -0.5f, }; run_test("test_s8_f32", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), true, true, conv_s8_to_f32_c); run_test("test_s8d_f32", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), false, true, conv_s8d_to_f32_c); run_test("test_s8_f32d", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), true, false, conv_s8_to_f32d_c); run_test("test_s8d_f32d", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), false, false, conv_s8d_to_f32d_c); } static void test_f32_u8(void) { static const float in[] = { 0.0f, 1.0f, -1.0f, 0.5f, -0.5f, 1.1f, -1.1f, 1.0f/160.f, 1.0f/256.f, -1.0f/160.f, -1.0f/256.f }; static const uint8_t out[] = { 128, 255, 0, 192, 64, 255, 0, 129, 128, 127, 128 }; run_test("test_f32_u8", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), true, true, conv_f32_to_u8_c); run_test("test_f32d_u8", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), false, true, conv_f32d_to_u8_c); run_test("test_f32_u8d", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), true, false, conv_f32_to_u8d_c); run_test("test_f32d_u8d", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), false, false, conv_f32d_to_u8d_c); } static void test_u8_f32(void) { static const uint8_t in[] = { 128, 255, 0, 192, 64, }; static const float out[] = { 0.0f, 0.9921875f, -1.0f, 0.5f, -0.5f, }; run_test("test_u8_f32", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), true, true, conv_u8_to_f32_c); run_test("test_u8d_f32", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), false, true, conv_u8d_to_f32_c); run_test("test_u8_f32d", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), true, false, conv_u8_to_f32d_c); run_test("test_u8d_f32d", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), false, false, conv_u8d_to_f32d_c); } static void test_f32_u16(void) { static const float in[] = { 0.0f, 1.0f, -1.0f, 0.5f, -0.5f, 1.1f, -1.1f, 1.0f/49152.f, 1.0f/65536.f, -1.0f/49152.f, -1.0f/65536.f }; static const uint16_t out[] = { 32768, 65535, 0, 49152, 16384, 65535, 0, 32769, 32768, 32767, 32768 }; run_test("test_f32_u16", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), true, true, conv_f32_to_u16_c); run_test("test_f32d_u16", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), false, true, conv_f32d_to_u16_c); } static void test_u16_f32(void) { static const uint16_t in[] = { 32768, 65535, 0, 49152, 16384, }; static const float out[] = { 0.0f, 0.999969482422f, -1.0f, 0.5f, -0.5f }; run_test("test_u16_f32d", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), true, false, conv_u16_to_f32d_c); run_test("test_u16_f32", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), true, true, conv_u16_to_f32_c); } static void test_f32_s16(void) { static const float in[] = { 0.0f, 1.0f, -1.0f, 0.5f, -0.5f, 1.1f, -1.1f, 1.0f/49152.f, 1.0f/65536.f, -1.0f/49152.f, -1.0f/65536.f }; static const int16_t out[] = { 0, 32767, -32768, 16384, -16384, 32767, -32768, 1, 0, -1, 0 }; run_test("test_f32_s16", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), true, true, conv_f32_to_s16_c); run_test("test_f32d_s16", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), false, true, conv_f32d_to_s16_c); run_test("test_f32_s16d", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), true, false, conv_f32_to_s16d_c); run_test("test_f32d_s16d", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), false, false, conv_f32d_to_s16d_c); #if defined(HAVE_SSE2) if (cpu_flags & SPA_CPU_FLAG_SSE2) { run_test("test_f32_s16_sse2", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), true, true, conv_f32_to_s16_sse2); run_test("test_f32d_s16_sse2", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), false, true, conv_f32d_to_s16_sse2); run_test("test_f32d_s16d_sse2", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), false, false, conv_f32d_to_s16d_sse2); } #endif #if defined(HAVE_AVX2) if (cpu_flags & SPA_CPU_FLAG_AVX2) { run_test("test_f32d_s16_avx2", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), false, true, conv_f32d_to_s16_avx2); } #endif #if defined(HAVE_NEON) if (cpu_flags & SPA_CPU_FLAG_NEON) { run_test("test_f32d_s16_neon", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), false, true, conv_f32d_to_s16_neon); } #endif #if defined(HAVE_RVV) if (cpu_flags & SPA_CPU_FLAG_RISCV_V) { run_test("test_f32_s16_rvv", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), true, true, conv_f32_to_s16_rvv); run_test("test_f32d_s16d_rvv", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), false, false, conv_f32d_to_s16d_rvv); run_test("test_f32d_s16_rvv", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), false, true, conv_f32d_to_s16_rvv); } #endif } static void test_s16_f32(void) { static const int16_t in[] = { 0, 32767, -32768, 16384, -16384, }; static const float out[] = { 0.0f, 0.999969482422f, -1.0f, 0.5f, -0.5f }; run_test("test_s16_f32d", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), true, false, conv_s16_to_f32d_c); run_test("test_s16d_f32", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), false, true, conv_s16d_to_f32_c); run_test("test_s16_f32", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), true, true, conv_s16_to_f32_c); run_test("test_s16d_f32d", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), false, false, conv_s16d_to_f32d_c); #if defined(HAVE_SSE2) if (cpu_flags & SPA_CPU_FLAG_SSE2) { run_test("test_s16_f32d_sse2", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), true, false, conv_s16_to_f32d_sse2); } #endif #if defined(HAVE_AVX2) if (cpu_flags & SPA_CPU_FLAG_AVX2) { run_test("test_s16_f32d_avx2", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), true, false, conv_s16_to_f32d_avx2); } #endif #if defined(HAVE_NEON) if (cpu_flags & SPA_CPU_FLAG_NEON) { run_test("test_s16_f32d_neon", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), true, false, conv_s16_to_f32d_neon); } #endif #if defined(HAVE_RVV) if (cpu_flags & SPA_CPU_FLAG_RISCV_V) { run_test("test_s16_f32d_rvv", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), true, false, conv_s16_to_f32d_rvv); } #endif } static void test_f32_u32(void) { static const float in[] = { 0.0f, 1.0f, -1.0f, 0.5f, -0.5f, 1.1f, -1.1f, 1.0f/0xa00000, 1.0f/0x1000000, -1.0f/0xa00000, -1.0f/0x1000000 }; static const uint32_t out[] = { 0x80000000, 0xffffff00, 0x0, 0xc0000000, 0x40000000, 0xffffff00, 0x0, 0x80000100, 0x80000000, 0x7fffff00, 0x80000000 }; run_test("test_f32_u32", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), true, true, conv_f32_to_u32_c); run_test("test_f32d_u32", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), false, true, conv_f32d_to_u32_c); } static void test_u32_f32(void) { static const uint32_t in[] = { 0x80000000, 0xffffff00, 0x0, 0xc0000000, 0x40000000 }; static const float out[] = { 0.0f, 0.999999880791f, -1.0f, 0.5f, -0.5f, }; run_test("test_u32_f32d", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), true, false, conv_u32_to_f32d_c); run_test("test_u32_f32", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), true, true, conv_u32_to_f32_c); } static void test_f32_s32(void) { static const float in[] = { 0.0f, 1.0f, -1.0f, 0.5f, -0.5f, 1.1f, -1.1f, 1.0f/0xa00000, -1.0f/0xa00000, 1.0f/0x800000, -1.0f/0x800000, 1.0f/0x1000000, -1.0f/0x1000000, 1.0f/0x2000000, -1.0f/0x2000000, 1.0f/0x4000000, -1.0f/0x4000000, 1.0f/0x8000000, -1.0f/0x8000000, 1.0f/0x10000000, -1.0f/0x10000000, 1.0f/0x20000000, -1.0f/0x20000000, 1.0f/0x40000000, -1.0f/0x40000000, 1.0f/0x80000000, -1.0f/0x80000000, 1.0f/0x100000000, -1.0f/0x100000000, 1.0f/0x200000000, -1.0f/0x200000000, }; static const int32_t out[] = { 0x00000000, 0x7fffff80, 0x80000000, 0x40000000, 0xc0000000, 0x7fffff80, 0x80000000, 0x000000cd, 0xffffff33, 0x00000100, 0xffffff00, 0x00000080, 0xffffff80, 0x00000040, 0xffffffc0, 0x00000020, 0xffffffe0, 0x00000010, 0xfffffff0, 0x00000008, 0xfffffff8, 0x00000004, 0xfffffffc, 0x00000002, 0xfffffffe, 0x00000001, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, }; run_test("test_f32_s32", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), true, true, conv_f32_to_s32_c); run_test("test_f32d_s32", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), false, true, conv_f32d_to_s32_c); run_test("test_f32_s32d", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), true, false, conv_f32_to_s32d_c); run_test("test_f32d_s32d", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), false, false, conv_f32d_to_s32d_c); #if defined(HAVE_SSE2) if (cpu_flags & SPA_CPU_FLAG_SSE2) { run_test("test_f32d_s32_sse2", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), false, true, conv_f32d_to_s32_sse2); } #endif #if defined(HAVE_AVX2) if (cpu_flags & SPA_CPU_FLAG_AVX2) { run_test("test_f32d_s32_avx2", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), false, true, conv_f32d_to_s32_avx2); } #endif #if defined(HAVE_RVV) if (cpu_flags & SPA_CPU_FLAG_RISCV_V) { run_test("test_f32d_s32_rvv", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), false, true, conv_f32d_to_s32_rvv); } #endif } static void test_s32_f32(void) { static const int32_t in[] = { 0, 0x7FFFFFFF, 0x80000000, 0x7fffff00, 0x80000100, 0x40000000, 0xc0000000, 0x0080, 0xFFFFFF80, 0x0100, 0xFFFFFF00, 0x0200, 0xFFFFFE00 }; static const float out[] = { 0.e+00f, 1.e+00f, -1.e+00f, 9.9999988079071044921875e-01f, -9.9999988079071044921875e-01f, 5.e-01f, -5.e-01f, 5.9604644775390625e-08f, -5.9604644775390625e-08f, 1.1920928955078125e-07f, -1.1920928955078125e-07f, 2.384185791015625e-07f, -2.384185791015625e-07f }; run_test("test_s32_f32d", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), true, false, conv_s32_to_f32d_c); run_test("test_s32d_f32", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), false, true, conv_s32d_to_f32_c); run_test("test_s32_f32", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), true, true, conv_s32_to_f32_c); run_test("test_s32d_f32d", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), false, false, conv_s32d_to_f32d_c); #if defined(HAVE_SSE2) if (cpu_flags & SPA_CPU_FLAG_SSE2) { run_test("test_s32_f32d_sse2", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), true, false, conv_s32_to_f32d_sse2); } #endif #if defined(HAVE_AVX2) if (cpu_flags & SPA_CPU_FLAG_AVX2) { run_test("test_s32_f32d_avx2", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), true, false, conv_s32_to_f32d_avx2); } #endif #if defined(HAVE_RVV) if (cpu_flags & SPA_CPU_FLAG_RISCV_V) { run_test("test_s32_f32d_rvv", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), true, false, conv_s32_to_f32d_rvv); } #endif } static void test_f32_u24(void) { static const float in[] = { 0.0f, 1.0f, -1.0f, 0.5f, -0.5f, 1.1f, -1.1f, 1.0f/0xa00000, 1.0f/0x1000000, -1.0f/0xa00000, -1.0f/0x1000000 }; static const uint24_t out[] = { U32_TO_U24(0x00800000), U32_TO_U24(0xffffff), U32_TO_U24(0x000000), U32_TO_U24(0xc00000), U32_TO_U24(0x400000), U32_TO_U24(0xffffff), U32_TO_U24(0x000000), U32_TO_U24(0x800001), U32_TO_U24(0x800000), U32_TO_U24(0x7fffff), U32_TO_U24(0x800000) }; run_test("test_f32_u24", in, sizeof(in[0]), out, 3, SPA_N_ELEMENTS(in), true, true, conv_f32_to_u24_c); run_test("test_f32d_u24", in, sizeof(in[0]), out, 3, SPA_N_ELEMENTS(in), false, true, conv_f32d_to_u24_c); } static void test_u24_f32(void) { static const uint24_t in[] = { U32_TO_U24(0x00800000), U32_TO_U24(0xffffff), U32_TO_U24(0x000000), U32_TO_U24(0xc00000), U32_TO_U24(0x400000) }; static const float out[] = { 0.0f, 0.999999880791f, -1.0f, 0.5, -0.5, }; run_test("test_u24_f32d", in, 3, out, sizeof(out[0]), SPA_N_ELEMENTS(out), true, false, conv_u24_to_f32d_c); run_test("test_u24_f32", in, 3, out, sizeof(out[0]), SPA_N_ELEMENTS(out), true, true, conv_u24_to_f32_c); } static void test_f32_s24(void) { static const float in[] = { 0.0f, 1.0f, -1.0f, 0.5f, -0.5f, 1.1f, -1.1f, 1.0f/0xa00000, 1.0f/0x1000000, -1.0f/0xa00000, -1.0f/0x1000000 }; static const int24_t out[] = { S32_TO_S24(0), S32_TO_S24(0x7fffff), S32_TO_S24(0xff800000), S32_TO_S24(0x400000), S32_TO_S24(0xc00000), S32_TO_S24(0x7fffff), S32_TO_S24(0xff800000), S32_TO_S24(0x000001), S32_TO_S24(0x000000), S32_TO_S24(0xffffffff), S32_TO_S24(0x000000) }; run_test("test_f32_s24", in, sizeof(in[0]), out, 3, SPA_N_ELEMENTS(in), true, true, conv_f32_to_s24_c); run_test("test_f32d_s24", in, sizeof(in[0]), out, 3, SPA_N_ELEMENTS(in), false, true, conv_f32d_to_s24_c); run_test("test_f32_s24d", in, sizeof(in[0]), out, 3, SPA_N_ELEMENTS(in), true, false, conv_f32_to_s24d_c); run_test("test_f32d_s24d", in, sizeof(in[0]), out, 3, SPA_N_ELEMENTS(in), false, false, conv_f32d_to_s24d_c); } static void test_s24_f32(void) { static const int24_t in[] = { S32_TO_S24(0), S32_TO_S24(0x7fffff), S32_TO_S24(0xff800000), S32_TO_S24(0x400000), S32_TO_S24(0xc00000) }; static const float out[] = { 0.0f, 0.999999880791f, -1.0f, 0.5f, -0.5f, }; run_test("test_s24_f32d", in, 3, out, sizeof(out[0]), SPA_N_ELEMENTS(out), true, false, conv_s24_to_f32d_c); run_test("test_s24d_f32", in, 3, out, sizeof(out[0]), SPA_N_ELEMENTS(out), false, true, conv_s24d_to_f32_c); run_test("test_s24_f32", in, 3, out, sizeof(out[0]), SPA_N_ELEMENTS(out), true, true, conv_s24_to_f32_c); run_test("test_s24d_f32d", in, 3, out, sizeof(out[0]), SPA_N_ELEMENTS(out), false, false, conv_s24d_to_f32d_c); #if defined(HAVE_SSE2) if (cpu_flags & SPA_CPU_FLAG_SSE2) { run_test("test_s24_f32d_sse2", in, 3, out, sizeof(out[0]), SPA_N_ELEMENTS(out), true, false, conv_s24_to_f32d_sse2); } #endif #if defined(HAVE_SSSE3) if (cpu_flags & SPA_CPU_FLAG_SSSE3) { run_test("test_s24_f32d_ssse3", in, 3, out, sizeof(out[0]), SPA_N_ELEMENTS(out), true, false, conv_s24_to_f32d_ssse3); } #endif #if defined(HAVE_SSE41) if (cpu_flags & SPA_CPU_FLAG_SSE41) { run_test("test_s24_f32d_sse41", in, 3, out, sizeof(out[0]), SPA_N_ELEMENTS(out), true, false, conv_s24_to_f32d_sse41); } #endif #if defined(HAVE_AVX2) if (cpu_flags & SPA_CPU_FLAG_AVX2) { run_test("test_s24_f32d_avx2", in, 3, out, sizeof(out[0]), SPA_N_ELEMENTS(out), true, false, conv_s24_to_f32d_avx2); } #endif } static void test_f32_u24_32(void) { static const float in[] = { 0.0f, 1.0f, -1.0f, 0.5f, -0.5f, 1.1f, -1.1f, 1.0f/0xa00000, 1.0f/0x1000000, -1.0f/0xa00000, -1.0f/0x1000000 }; static const uint32_t out[] = { 0x800000, 0xffffff, 0x0, 0xc00000, 0x400000, 0xffffff, 0x000000, 0x800001, 0x800000, 0x7fffff, 0x800000 }; run_test("test_f32_u24_32", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), true, true, conv_f32_to_u24_32_c); run_test("test_f32d_u24_32", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), false, true, conv_f32d_to_u24_32_c); } static void test_u24_32_f32(void) { static const uint32_t in[] = { 0x800000, 0xffffff, 0x0, 0xc00000, 0x400000, 0x11000000 }; static const float out[] = { 0.0f, 0.999999880791f, -1.0f, 0.5f, -0.5f, -1.0f }; run_test("test_u24_32_f32d", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), true, false, conv_u24_32_to_f32d_c); run_test("test_u24_32_f32", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), true, true, conv_u24_32_to_f32_c); } static void test_f32_s24_32(void) { static const float in[] = { 0.0f, 1.0f, -1.0f, 0.5f, -0.5f, 1.1f, -1.1f, 1.0f/0xa00000, 1.0f/0x1000000, -1.0f/0xa00000, -1.0f/0x1000000 }; static const int32_t out[] = { 0, 0x7fffff, 0xff800000, 0x400000, 0xffc00000, 0x7fffff, 0xff800000, 0x000001, 0x000000, 0xffffffff, 0x000000 }; run_test("test_f32_s24_32", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), true, true, conv_f32_to_s24_32_c); run_test("test_f32d_s24_32", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), false, true, conv_f32d_to_s24_32_c); run_test("test_f32_s24_32d", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), true, false, conv_f32_to_s24_32d_c); run_test("test_f32d_s24_32d", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), false, false, conv_f32d_to_s24_32d_c); } static void test_s24_32_f32(void) { static const int32_t in[] = { 0, 0x7fffff, 0xff800000, 0x400000, 0xffc00000, 0x66800000 }; static const float out[] = { 0.0f, 0.999999880791f, -1.0f, 0.5f, -0.5f, -1.0f }; run_test("test_s24_32_f32d", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), true, false, conv_s24_32_to_f32d_c); run_test("test_s24_32d_f32", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), false, true, conv_s24_32d_to_f32_c); run_test("test_s24_32_f32", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), true, true, conv_s24_32_to_f32_c); run_test("test_s24_32d_f32d", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), false, false, conv_s24_32d_to_f32d_c); } static void test_f64_f32(void) { static const double in[] = { 0.0, 1.0, -1.0, 0.5, -0.5, }; static const float out[] = { 0.0, 1.0, -1.0, 0.5, -0.5, }; run_test("test_f64_f32d", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), true, false, conv_f64_to_f32d_c); run_test("test_f64d_f32", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), false, true, conv_f64d_to_f32_c); run_test("test_f64_f32", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), true, true, conv_f64_to_f32_c); run_test("test_f64d_f32d", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), false, false, conv_f64d_to_f32d_c); } static void test_f32_f64(void) { static const float in[] = { 0.0f, 1.0f, -1.0f, 0.5f, -0.5f, 1.1f, -1.1f }; static const double out[] = { 0.0f, 1.0f, -1.0f, 0.5f, -0.5f, 1.1f, -1.1f }; run_test("test_f32_f64", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), true, true, conv_f32_to_f64_c); run_test("test_f32d_f64", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), false, true, conv_f32d_to_f64_c); run_test("test_f32_f64d", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), true, false, conv_f32_to_f64d_c); run_test("test_f32d_f64d", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), false, false, conv_f32d_to_f64d_c); } static void test_lossless_s8(void) { int8_t i; fprintf(stderr, "test %s:\n", __func__); for (i = S8_MIN; i < S8_MAX; i+=1) { float v = S8_TO_F32(i); int8_t t = F32_TO_S8(v); spa_assert_se(i == t); } } static void test_lossless_u8(void) { uint8_t i; fprintf(stderr, "test %s:\n", __func__); for (i = U8_MIN; i < U8_MAX; i+=1) { float v = U8_TO_F32(i); uint8_t t = F32_TO_U8(v); spa_assert_se(i == t); } } static void test_lossless_s16(void) { int32_t i; fprintf(stderr, "test %s:\n", __func__); for (i = S16_MIN; i <= S16_MAX; i+=3) { float v = S16_TO_F32((int16_t)i); int16_t t = F32_TO_S16(v); spa_assert_se(i == t); int32_t t2 = F32_TO_S32(v); spa_assert_se((int32_t)(((uint32_t)i)<<16) == t2); spa_assert_se(i == t2>>16); } } static void test_lossless_u16(void) { uint32_t i; fprintf(stderr, "test %s:\n", __func__); for (i = U16_MIN; i <= U16_MAX; i+=3) { float v = U16_TO_F32((uint16_t)i); uint16_t t = F32_TO_U16(v); spa_assert_se(i == t); uint32_t t2 = F32_TO_U32(v); spa_assert_se(i<<16 == t2); spa_assert_se(i == t2>>16); } } static void test_lossless_s24(void) { int32_t i; fprintf(stderr, "test %s:\n", __func__); for (i = S24_MIN; i < S24_MAX; i+=13) { float v = S24_TO_F32(s32_to_s24(i)); int32_t t = s24_to_s32(F32_TO_S24(v)); spa_assert_se(i == t); } } static void test_lossless_u24(void) { uint32_t i; fprintf(stderr, "test %s:\n", __func__); for (i = U24_MIN; i < U24_MAX; i+=11) { float v = U24_TO_F32(u32_to_u24(i)); uint32_t t = u24_to_u32(F32_TO_U24(v)); spa_assert_se(i == t); } } static void test_lossless_s25_32_to_f32_to_s25_32(void) { int32_t i; fprintf(stderr, "test %s:\n", __func__); for (i = S25_MIN; i <= S25_MAX; i+=11) { float v = S25_32_TO_F32(i); int32_t t = F32_TO_S25_32(v); spa_assert_se(i == t); } } static void test_lossless_s25_32_to_s32_to_f32_to_s25_32(void) { int32_t i; fprintf(stderr, "test %s:\n", __func__); for (i = S25_MIN; i <= S25_MAX; i+=13) { float v = S32_TO_F32(S25_32_TO_S32(i)); int32_t t = F32_TO_S25_32(v); spa_assert_se(i == t); } } static void test_lossless_s25_32_to_s32_to_f32_to_s32_to_s25_32(void) { int32_t i; fprintf(stderr, "test %s:\n", __func__); for (i = S25_MIN; i <= S25_MAX; i+=11) { float v = S32_TO_F32(S25_32_TO_S32(i)); int32_t t = S32_TO_S25_32(F32_TO_S32(v)); spa_assert_se(i == t); } } static void test_lossless_s25_32_to_f32_to_s32_to_s25_32(void) { int32_t i; fprintf(stderr, "test %s:\n", __func__); for (i = S25_MIN; i <= S25_MAX; i+=11) { float v = S25_32_TO_F32(i); int32_t t = S32_TO_S25_32(F32_TO_S32(v)); spa_assert_se(i == t); } } static void test_lossless_s32(void) { int64_t i; fprintf(stderr, "test %s:\n", __func__); for (i = S32_MIN; i < S32_MAX; i += 63) { float v = S32_TO_F32(i); int32_t t = F32_TO_S32(v); spa_assert_se(SPA_ABS(i - t) <= 126); // NOTE: 126 is the maximal absolute error given step=1, // for wider steps it may (errneously) be lower, // because we may not check some integer that would bump it. } } static void test_lossless_s32_lossless_subset(void) { int32_t i, j; fprintf(stderr, "test %s:\n", __func__); for (i = S25_MIN; i <= S25_MAX; i+=11) { for(j = 0; j < 8; ++j) { int32_t s = i * (1< t ? (i - t) <= 256 : (t - i) <= 256); } } static void test_swaps(void) { { uint24_t v = U32_TO_U24(0x123456); uint24_t t = U32_TO_U24(0x563412); uint24_t s = bswap_u24(v); spa_assert_se(memcmp(&s, &t, sizeof(t)) == 0); } { int24_t v = S32_TO_S24(0xfffe1dc0); int24_t t = S32_TO_S24(0xffc01dfe); int24_t s = bswap_s24(v); spa_assert_se(memcmp(&s, &t, sizeof(t)) == 0); } { int24_t v = S32_TO_S24(0x123456); int24_t t = S32_TO_S24(0x563412); int24_t s = bswap_s24(v); spa_assert_se(memcmp(&s, &t, sizeof(t)) == 0); } } static void run_test_noise(uint32_t fmt, uint32_t noise, uint32_t flags) { struct convert conv; const void *ip[N_CHANNELS]; void *op[N_CHANNELS]; uint32_t i, range; bool all_zero; spa_zero(conv); conv.noise_bits = noise; conv.src_fmt = SPA_AUDIO_FORMAT_F32P; conv.dst_fmt = fmt; conv.n_channels = 2; conv.rate = 44100; conv.cpu_flags = flags; spa_assert_se(convert_init(&conv) == 0); fprintf(stderr, "test noise %s:\n", conv.func_name); memset(samp_in, 0, sizeof(samp_in)); for (i = 0; i < conv.n_channels; i++) { ip[i] = samp_in; op[i] = samp_out; } convert_process(&conv, op, ip, N_SAMPLES); range = 1 << conv.noise_bits; all_zero = true; for (i = 0; i < conv.n_channels * N_SAMPLES; i++) { switch (fmt) { case SPA_AUDIO_FORMAT_S8: { int8_t *d = (int8_t *)samp_out; if (d[i] != 0) all_zero = false; spa_assert_se(SPA_ABS(d[i] - 0) <= (int8_t)range); break; } case SPA_AUDIO_FORMAT_U8: { uint8_t *d = (uint8_t *)samp_out; if (d[i] != 0x80) all_zero = false; spa_assert_se((int8_t)SPA_ABS(d[i] - 0x80) <= (int8_t)(range<<1)); break; } case SPA_AUDIO_FORMAT_S16: { int16_t *d = (int16_t *)samp_out; if (d[i] != 0) all_zero = false; spa_assert_se(SPA_ABS(d[i] - 0) <= (int16_t)range); break; } case SPA_AUDIO_FORMAT_S24: { int24_t *d = (int24_t *)samp_out; int32_t t = s24_to_s32(d[i]); if (t != 0) all_zero = false; spa_assert_se(SPA_ABS(t - 0) <= (int32_t)range); break; } case SPA_AUDIO_FORMAT_S32: { int32_t *d = (int32_t *)samp_out; if (d[i] != 0) all_zero = false; spa_assert_se(SPA_ABS(d[i] - 0) <= (int32_t)(range << 8)); break; } default: spa_assert_not_reached(); break; } } spa_assert_se(all_zero == false); convert_free(&conv); } static void test_noise(void) { run_test_noise(SPA_AUDIO_FORMAT_S8, 1, 0); run_test_noise(SPA_AUDIO_FORMAT_S8, 2, 0); run_test_noise(SPA_AUDIO_FORMAT_U8, 1, 0); run_test_noise(SPA_AUDIO_FORMAT_U8, 2, 0); run_test_noise(SPA_AUDIO_FORMAT_S16, 1, 0); run_test_noise(SPA_AUDIO_FORMAT_S16, 2, 0); run_test_noise(SPA_AUDIO_FORMAT_S24, 1, 0); run_test_noise(SPA_AUDIO_FORMAT_S24, 2, 0); run_test_noise(SPA_AUDIO_FORMAT_S32, 1, 0); run_test_noise(SPA_AUDIO_FORMAT_S32, 2, 0); } int main(int argc, char *argv[]) { cpu_flags = get_cpu_flags(); printf("got CPU flags %d\n", cpu_flags); test_f32_s8(); test_s8_f32(); test_f32_u8(); test_u8_f32(); test_f32_u16(); test_u16_f32(); test_f32_s16(); test_s16_f32(); test_f32_u32(); test_u32_f32(); test_f32_s32(); test_s32_f32(); test_f32_u24(); test_u24_f32(); test_f32_s24(); test_s24_f32(); test_f32_u24_32(); test_u24_32_f32(); test_f32_s24_32(); test_s24_32_f32(); test_f32_f64(); test_f64_f32(); test_lossless_s8(); test_lossless_u8(); test_lossless_s16(); test_lossless_u16(); test_lossless_s24(); test_lossless_u24(); test_lossless_s25_32_to_f32_to_s25_32(); test_lossless_s25_32_to_s32_to_f32_to_s25_32(); test_lossless_s25_32_to_s32_to_f32_to_s32_to_s25_32(); test_lossless_s25_32_to_f32_to_s32_to_s25_32(); test_lossless_s32(); test_lossless_s32_lossless_subset(); test_lossless_u32(); test_swaps(); test_noise(); return 0; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/audioconvert/test-peaks.c000066400000000000000000000045471511204443500277020ustar00rootroot00000000000000/* Spa */ /* SPDX-FileCopyrightText: Copyright © 2022 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include "config.h" #include #include #include #include #include #include #include #include SPA_LOG_IMPL(logger); static uint32_t cpu_flags; #include "test-helper.h" #include "peaks-ops.c" static void test_impl(void) { struct peaks peaks; unsigned int i; float vals[1038]; float min[2] = { 0.0f, 0.0f }, max[2] = { 0.0f, 0.0f }, absmax[2] = { 0.0f, 0.0f }; for (i = 0; i < SPA_N_ELEMENTS(vals); i++) vals[i] = (float)((drand48() - 0.5f) * 2.5f); peaks_min_max_c(&peaks, &vals[1], SPA_N_ELEMENTS(vals) - 1, &min[0], &max[0]); printf("c peaks min:%f max:%f\n", min[0], max[0]); absmax[0] = peaks_abs_max_c(&peaks, &vals[1], SPA_N_ELEMENTS(vals) - 1, 0.0f); printf("c peaks abs-max:%f\n", absmax[0]); #if defined(HAVE_SSE) if (cpu_flags & SPA_CPU_FLAG_SSE) { peaks_min_max_sse(&peaks, &vals[1], SPA_N_ELEMENTS(vals) - 1, &min[1], &max[1]); printf("sse peaks min:%f max:%f\n", min[1], max[1]); absmax[1] = peaks_abs_max_sse(&peaks, &vals[1], SPA_N_ELEMENTS(vals) - 1, 0.0f); printf("sse peaks abs-max:%f\n", absmax[1]); spa_assert(min[0] == min[1]); spa_assert(max[0] == max[1]); spa_assert(absmax[0] == absmax[1]); } #endif } static void test_min_max(void) { struct peaks peaks; const float vals[] = { 0.0f, 0.5f, -0.5f, 0.0f, 0.6f, -0.8f, -0.5f, 0.0f }; float min = 0.0f, max = 0.0f; spa_zero(peaks); peaks.log = &logger.log; peaks.cpu_flags = cpu_flags; peaks_init(&peaks); peaks_min_max(&peaks, vals, SPA_N_ELEMENTS(vals), &min, &max); spa_assert(min == -0.8f); spa_assert(max == 0.6f); } static void test_abs_max(void) { struct peaks peaks; const float vals[] = { 0.0f, 0.5f, -0.5f, 0.0f, 0.6f, -0.8f, -0.5f, 0.0f }; float max = 0.0f; spa_zero(peaks); peaks.log = &logger.log; peaks.cpu_flags = cpu_flags; peaks_init(&peaks); max = peaks_abs_max(&peaks, vals, SPA_N_ELEMENTS(vals), max); spa_assert(max == 0.8f); } int main(int argc, char *argv[]) { struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); srand48(SPA_TIMESPEC_TO_NSEC(&ts)); logger.log.level = SPA_LOG_LEVEL_TRACE; cpu_flags = get_cpu_flags(); printf("got CPU flags %d\n", cpu_flags); test_impl(); test_min_max(); test_abs_max(); return 0; } test-resample-delay.c000066400000000000000000000257651511204443500314310ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/audioconvert/* Spa */ /* SPDX-FileCopyrightText: Copyright © 2019 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #include #include #include #include #include #include SPA_LOG_IMPL(logger); #include "resample.h" static float samp_in[65536]; static float samp_out[65536]; static bool force_print; static void assert_test(bool check) { if (!check) fprintf(stderr, "FAIL\n\n"); #if 1 spa_assert_se(check); #endif } static double difference(double delay, const float *a, size_t len_a, const float *b, size_t len_b, double b_rate) { size_t i; float c = 0, wa = 0, wb = 0; /* Difference measure: sum((a-b)^2) / sqrt(sum a^2 sum b^2); restricted to overlap */ for (i = 0; i < len_a; ++i) { double jf = (i + delay) * b_rate; int j; double x; float bv; j = (int)floor(jf); if (j < 0 || j + 1 >= (int)len_b) continue; x = jf - j; bv = (float)((1 - x) * b[j] + x * b[j + 1]); c += (a[i] - bv) * (a[i] - bv); wa += a[i]*a[i]; wb = bv*bv; } if (wa == 0 || wb == 0) return 1e30; return c / sqrt(wa * wb); } struct find_delay_data { const float *a; const float *b; size_t len_a; size_t len_b; double b_rate; }; static double find_delay_func(double x, void *user_data) { const struct find_delay_data *data = user_data; return difference((float)x, data->a, data->len_a, data->b, data->len_b, data->b_rate); } static double minimum(double x1, double x4, double (*func)(double, void *), void *user_data, double tol) { /* Find minimum with golden section search */ const double phi = (1 + sqrt(5)) / 2; double x2, x3; double f2, f3; spa_assert(x4 >= x1); x2 = x4 - (x4 - x1) / phi; x3 = x1 + (x4 - x1) / phi; f2 = func(x2, user_data); f3 = func(x3, user_data); while (x4 - x1 > tol) { if (f2 > f3) { x1 = x2; x2 = x3; x3 = x1 + (x4 - x1) / phi; f2 = f3; f3 = func(x3, user_data); } else { x4 = x3; x3 = x2; x2 = x4 - (x4 - x1) / phi; f3 = f2; f2 = func(x2, user_data); } } return (f2 < f3) ? x2 : x3; } static double find_delay(const float *a, size_t len_a, const float *b, size_t len_b, double b_rate, int maxdelay, double tol) { struct find_delay_data data = { .a = a, .len_a = len_a, .b = b, .len_b = len_b, .b_rate = b_rate }; double best_x, best_f; int i; best_x = 0; best_f = find_delay_func(best_x, &data); for (i = -maxdelay; i <= maxdelay; ++i) { double f = find_delay_func(i, &data); if (f < best_f) { best_x = i; best_f = f; } } return minimum(best_x - 2, best_x + 2, find_delay_func, &data, tol); } static void test_find_delay(void) { float v1[1024]; float v2[1024]; const double tol = 0.001; double delay, expect; int i; fprintf(stderr, "\n\n-- test_find_delay\n\n"); for (i = 0; i < 1024; ++i) { v1[i] = sinf(0.1f * i); v2[i] = sinf(0.1f * (i - 3.1234f)); } delay = find_delay(v1, SPA_N_ELEMENTS(v1), v2, SPA_N_ELEMENTS(v2), 1, 50, tol); expect = 3.1234f; fprintf(stderr, "find_delay = %g (exact %g)\n", delay, expect); assert_test(expect - 2*tol < delay && delay < expect + 2*tol); for (i = 0; i < 1024; ++i) { v1[i] = sinf(0.1f * i); v2[i] = sinf(0.1f * (i*3.0f/4 - 3.1234f)); } delay = find_delay(v1, SPA_N_ELEMENTS(v1), v2, SPA_N_ELEMENTS(v2), 4.0/3.0, 50, tol); expect = 3.1234f; fprintf(stderr, "find_delay = %g (exact %g)\n", delay, expect); assert_test(expect - 2*tol < delay && delay < expect + 2*tol); } static uint32_t feed_sine(struct resample *r, uint32_t in, uint32_t *inp, uint32_t *phase, bool print) { uint32_t i, out; const void *src[1]; void *dst[1]; for (i = 0; i < in; ++i) samp_in[i] = sinf(0.01f * (*phase + i)) * expf(-0.001f * (*phase + i)); src[0] = samp_in; dst[0] = samp_out; out = SPA_N_ELEMENTS(samp_out); *inp = in; resample_process(r, src, inp, dst, &out); spa_assert_se(*inp == in); if (print || force_print) { fprintf(stderr, "inp(%u) = ", in); for (uint32_t i = 0; i < in; ++i) fprintf(stderr, "%g, ", samp_in[i]); fprintf(stderr, "\n\n"); fprintf(stderr, "out(%u) = ", out); for (uint32_t i = 0; i < out; ++i) fprintf(stderr, "%g, ", samp_out[i]); fprintf(stderr, "\n\n"); } else { fprintf(stderr, "inp(%u) = ...\n", in); fprintf(stderr, "out(%u) = ...\n", out); } *phase += in; *inp = in; return out; } static void check_delay(double rate, uint32_t out_rate, uint32_t options) { const double tol = 0.001; struct resample r; uint32_t in_phase = 0; uint32_t in, out; double expect, got; spa_zero(r); r.log = &logger.log; r.channels = 1; r.i_rate = 48000; r.o_rate = out_rate; r.quality = RESAMPLE_DEFAULT_QUALITY; r.options = options; resample_native_init(&r); resample_update_rate(&r, rate); feed_sine(&r, 512, &in, &in_phase, false); /* Test delay */ expect = resample_delay(&r) + (double)resample_phase(&r); out = feed_sine(&r, 256, &in, &in_phase, true); got = find_delay(samp_in, in, samp_out, out, out_rate / 48000.0, 100, tol); fprintf(stderr, "delay: expect = %g, got = %g\n", expect, got); assert_test(expect - 4*tol < got && got < expect + 4*tol); resample_free(&r); } static void test_delay_copy(void) { fprintf(stderr, "\n\n-- test_delay_copy (no prefill)\n\n"); check_delay(1, 48000, 0); fprintf(stderr, "\n\n-- test_delay_copy (prefill)\n\n"); check_delay(1, 48000, RESAMPLE_OPTION_PREFILL); } static void test_delay_full(void) { const uint32_t rates[] = { 16000, 32000, 44100, 48000, 88200, 96000, 144000, 192000 }; unsigned int i; for (i = 0; i < SPA_N_ELEMENTS(rates); ++i) { fprintf(stderr, "\n\n-- test_delay_full(%u, no prefill)\n\n", rates[i]); check_delay(1, rates[i], 0); fprintf(stderr, "\n\n-- test_delay_full(%u, prefill)\n\n", rates[i]); check_delay(1, rates[i], RESAMPLE_OPTION_PREFILL); } } static void test_delay_interp(void) { fprintf(stderr, "\n\n-- test_delay_interp(no prefill)\n\n"); check_delay(1 + 1e-12, 48000, 0); fprintf(stderr, "\n\n-- test_delay_interp(prefill)\n\n"); check_delay(1 + 1e-12, 48000, RESAMPLE_OPTION_PREFILL); } static void check_delay_vary_rate(double rate, double end_rate, uint32_t out_rate, uint32_t options) { const double tol = 0.001; struct resample r; uint32_t in_phase = 0; uint32_t in, out; double expect, got; fprintf(stderr, "\n\n-- check_delay_vary_rate(%g, %.14g, %u, %s)\n\n", rate, end_rate, out_rate, (options & RESAMPLE_OPTION_PREFILL) ? "prefill" : "no prefill"); spa_zero(r); r.log = &logger.log; r.channels = 1; r.i_rate = 48000; r.o_rate = out_rate; r.quality = RESAMPLE_DEFAULT_QUALITY; r.options = options; resample_native_init(&r); /* Cause nonzero resampler phase */ resample_update_rate(&r, rate); feed_sine(&r, 128, &in, &in_phase, false); resample_update_rate(&r, 1.7); feed_sine(&r, 128, &in, &in_phase, false); resample_update_rate(&r, end_rate); feed_sine(&r, 128, &in, &in_phase, false); feed_sine(&r, 255, &in, &in_phase, false); /* Test delay */ expect = (double)resample_delay(&r) + (double)resample_phase(&r); out = feed_sine(&r, 256, &in, &in_phase, true); got = find_delay(samp_in, in, samp_out, out, out_rate/48000.0, 100, tol); fprintf(stderr, "delay: expect = %g, got = %g\n", expect, got); assert_test(expect - 4*tol < got && got < expect + 4*tol); resample_free(&r); } static void test_delay_interp_vary_rate(void) { const uint32_t rates[] = { 32000, 44100, 48000, 88200, 96000 }; const double factors[] = { 1.0123456789, 1.123456789, 1.203883, 1.23456789, 1.3456789 }; unsigned int i, j; for (i = 0; i < SPA_N_ELEMENTS(rates); ++i) { for (j = 0; j < SPA_N_ELEMENTS(factors); ++j) { /* Interp at end */ check_delay_vary_rate(factors[j], 1 + 1e-12, rates[i], 0); /* Copy/full at end */ check_delay_vary_rate(factors[j], 1, rates[i], 0); /* Interp at end */ check_delay_vary_rate(factors[j], 1 + 1e-12, rates[i], RESAMPLE_OPTION_PREFILL); /* Copy/full at end */ check_delay_vary_rate(factors[j], 1, rates[i], RESAMPLE_OPTION_PREFILL); } } } static void run(uint32_t in_rate, uint32_t out_rate, double end_rate, double mid_rate, uint32_t options) { const double tol = 0.001; struct resample r; uint32_t in_phase = 0; uint32_t in, out; double expect, got; spa_zero(r); r.log = &logger.log; r.channels = 1; r.i_rate = in_rate; r.o_rate = out_rate; r.quality = RESAMPLE_DEFAULT_QUALITY; r.options = options; resample_native_init(&r); /* Cause nonzero resampler phase */ if (mid_rate != 0.0) { resample_update_rate(&r, mid_rate); feed_sine(&r, 128, &in, &in_phase, true); resample_update_rate(&r, 1.7); feed_sine(&r, 128, &in, &in_phase, true); } resample_update_rate(&r, end_rate); feed_sine(&r, 128, &in, &in_phase, true); feed_sine(&r, 255, &in, &in_phase, true); /* Test delay */ expect = (double)resample_delay(&r) + (double)resample_phase(&r); out = feed_sine(&r, 256, &in, &in_phase, true); got = find_delay(samp_in, in, samp_out, out, ((double)out_rate)/in_rate, 100, tol); fprintf(stderr, "delay: expect = %g, got = %g\n", expect, got); if (!(expect - 4*tol < got && got < expect + 4*tol)) fprintf(stderr, "FAIL\n\n"); resample_free(&r); } int main(int argc, char *argv[]) { static const struct option long_options[] = { { "in-rate", required_argument, NULL, 'i' }, { "out-rate", required_argument, NULL, 'o' }, { "end-full", no_argument, NULL, 'f' }, { "end-interp", no_argument, NULL, 'p' }, { "mid-rate", required_argument, NULL, 'm' }, { "prefill", no_argument, NULL, 'r' }, { "print", no_argument, NULL, 'P' }, { "help", no_argument, NULL, 'h' }, { NULL, 0, NULL, 0} }; const char *help = "%s [options]\n" "\n" "Check resampler delay. If no arguments, run tests.\n" "\n" "-i | --in-rate INRATE input rate\n" "-o | --out-rate OUTRATE output rate\n" "-f | --end-full force full (or copy) resampler\n" "-p | --end-interp force interp resampler\n" "-m | --mid-rate RELRATE force rate adjustment in the middle\n" "-r | --prefill enable prefill\n" "-P | --print force printing\n" "\n"; uint32_t in_rate = 0, out_rate = 0; double end_rate = 1, mid_rate = 0; uint32_t options = 0; int c; logger.log.level = SPA_LOG_LEVEL_TRACE; while ((c = getopt_long(argc, argv, "i:o:fpm:rPh", long_options, NULL)) != -1) { switch (c) { case 'h': fprintf(stderr, help, argv[0]); return 0; case 'i': if (!spa_atou32(optarg, &in_rate, 0)) goto error_arg; break; case 'o': if (!spa_atou32(optarg, &out_rate, 0)) goto error_arg; break; case 'f': end_rate = 1; break; case 'p': end_rate = 1 + 1e-12; break; case 'm': if (!spa_atod(optarg, &mid_rate)) goto error_arg; break; case 'r': options = RESAMPLE_OPTION_PREFILL; break; case 'P': force_print = true; break; default: goto error_arg; } } if (in_rate && out_rate) { run(in_rate, out_rate, end_rate, mid_rate, options); return 0; } test_find_delay(); test_delay_copy(); test_delay_full(); test_delay_interp(); test_delay_interp_vary_rate(); return 0; error_arg: fprintf(stderr, "Invalid arguments\n"); return 1; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/audioconvert/test-resample.c000066400000000000000000000127401511204443500304010ustar00rootroot00000000000000/* Spa */ /* SPDX-FileCopyrightText: Copyright © 2019 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #include #include #include #include SPA_LOG_IMPL(logger); #include "resample.h" #include "resample-native-impl.h" #define N_SAMPLES 253 #define N_CHANNELS 11 static float samp_in[N_SAMPLES * 4]; static float samp_out[N_SAMPLES * 4]; static void feed_1(struct resample *r) { uint32_t i; const void *src[1]; void *dst[1]; spa_zero(samp_out); src[0] = samp_in; dst[0] = samp_out; for (i = 0; i < 500; i++) { uint32_t in, out; in = out = 1; samp_in[0] = i; resample_process(r, src, &in, dst, &out); fprintf(stderr, "%d %d %f %d\n", i, in, samp_out[0], out); } } static void test_native(void) { struct resample r; spa_zero(r); r.log = &logger.log; r.channels = 1; r.i_rate = 44100; r.o_rate = 44100; r.quality = RESAMPLE_DEFAULT_QUALITY; resample_native_init(&r); feed_1(&r); resample_free(&r); spa_zero(r); r.log = &logger.log; r.channels = 1; r.i_rate = 44100; r.o_rate = 48000; r.quality = RESAMPLE_DEFAULT_QUALITY; resample_native_init(&r); feed_1(&r); resample_free(&r); } static void pull_blocks(struct resample *r, uint32_t first, uint32_t size, uint32_t count) { uint32_t i; float in[SPA_MAX(size, first) * 2]; float out[SPA_MAX(size, first) * 2]; const void *src[1]; void *dst[1]; uint32_t in_len, out_len; uint32_t pin_len, pout_len; src[0] = in; dst[0] = out; for (i = 0; i < count; i++) { pout_len = out_len = i == 0 ? first : size; pin_len = in_len = resample_in_len(r, out_len); resample_process(r, src, &pin_len, dst, &pout_len); fprintf(stderr, "%d: %d %d %d %d %d\n", i, in_len, pin_len, out_len, pout_len, resample_in_len(r, size)); spa_assert_se(in_len == pin_len); spa_assert_se(out_len == pout_len); } } static void pull_blocks_out(struct resample *r, uint32_t first, uint32_t size, uint32_t count) { uint32_t i; float in[SPA_MAX(size, first) * 2]; float out[SPA_MAX(size, first) * 2]; const void *src[1]; void *dst[1]; uint32_t in_len, out_len; uint32_t pin_len, pout_len; src[0] = in; dst[0] = out; for (i = 0; i < count; i++) { pin_len = in_len = i == 0 ? first : size; pout_len = out_len = resample_out_len(r, in_len); resample_process(r, src, &pin_len, dst, &pout_len); fprintf(stderr, "%d: %d %d %d %d %d\n", i, in_len, pin_len, out_len, pout_len, resample_out_len(r, size)); spa_assert_se(in_len == pin_len); spa_assert_se(out_len == pout_len); } } static void check_inout_len(struct resample *r, uint32_t first, uint32_t size, double rate, float phase) { struct native_data *data = r->data; resample_reset(r); resample_update_rate(r, rate); if (phase != 0.0) data->phase = FLOAT_TO_FIXP(phase); pull_blocks(r, first, size, 500); resample_reset(r); resample_update_rate(r, rate); if (phase != 0.0) data->phase = FLOAT_TO_FIXP(phase); pull_blocks_out(r, first, size, 500); } static void test_inout_len(void) { struct resample r; spa_zero(r); r.log = &logger.log; r.channels = 1; r.i_rate = 32000; r.o_rate = 48000; r.quality = RESAMPLE_DEFAULT_QUALITY; resample_native_init(&r); check_inout_len(&r, 1024, 1024, 1.0, 0); resample_free(&r); spa_zero(r); r.log = &logger.log; r.channels = 1; r.i_rate = 44100; r.o_rate = 48000; r.quality = RESAMPLE_DEFAULT_QUALITY; resample_native_init(&r); check_inout_len(&r, 1024, 1024, 1.0, 0); resample_free(&r); spa_zero(r); r.log = &logger.log; r.channels = 1; r.i_rate = 48000; r.o_rate = 44100; r.quality = RESAMPLE_DEFAULT_QUALITY; resample_native_init(&r); check_inout_len(&r, 1024, 1024, 1.0, 0); resample_free(&r); spa_zero(r); r.log = &logger.log; r.channels = 1; r.i_rate = 44100; r.o_rate = 48000; r.quality = RESAMPLE_DEFAULT_QUALITY; resample_native_init(&r); check_inout_len(&r, 513, 64, 1.0, 0); resample_free(&r); spa_zero(r); r.log = &logger.log; r.channels = 1; r.i_rate = 32000; r.o_rate = 48000; r.quality = RESAMPLE_DEFAULT_QUALITY; resample_native_init(&r); check_inout_len(&r, 513, 64, 1.02, 0); resample_free(&r); spa_zero(r); r.log = &logger.log; r.channels = 1; r.i_rate = 32000; r.o_rate = 48000; r.quality = RESAMPLE_DEFAULT_QUALITY; resample_native_init(&r); check_inout_len(&r, 513, 64, 1.0002, 0); resample_free(&r); spa_zero(r); r.log = &logger.log; r.channels = 1; r.i_rate = 32000; r.o_rate = 48000; r.quality = RESAMPLE_DEFAULT_QUALITY; r.options = RESAMPLE_OPTION_PREFILL; resample_native_init(&r); check_inout_len(&r, 513, 64, 1.0002, 0); resample_free(&r); /* Test value of phase that in floating-point arithmetic produces * inconsistent in_len */ spa_zero(r); r.log = &logger.log; r.channels = 1; r.i_rate = 8000; r.o_rate = 8000; r.quality = RESAMPLE_DEFAULT_QUALITY; r.options = RESAMPLE_OPTION_PREFILL; resample_native_init(&r); check_inout_len(&r, 64, 64, 1.0 + 1e-10, 7999.99f); resample_free(&r); /* Test value of phase that overflows filter buffer due to floating point rounding * up to nearest */ spa_zero(r); r.log = &logger.log; r.channels = 1; r.i_rate = 8000; r.o_rate = 8000; r.quality = RESAMPLE_DEFAULT_QUALITY; r.options = RESAMPLE_OPTION_PREFILL; resample_native_init(&r); check_inout_len(&r, 64, 64, 1.0 + 1e-10, nextafterf(8000, 0)); resample_free(&r); } int main(int argc, char *argv[]) { logger.log.level = SPA_LOG_LEVEL_TRACE; test_native(); test_inout_len(); return 0; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/audioconvert/test-source.c000066400000000000000000000530661511204443500300770ustar00rootroot00000000000000/* Spa */ /* SPDX-FileCopyrightText: Copyright © 2019 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #undef SPA_LOG_TOPIC_DEFAULT #define SPA_LOG_TOPIC_DEFAULT &log_topic SPA_LOG_TOPIC_DEFINE_STATIC(log_topic, "spa.test-source"); #define DEFAULT_RATE 44100 #define DEFAULT_CHANNELS 2 #define MAX_BUFFERS 32 struct impl; #define DEFAULT_MUTE false #define DEFAULT_VOLUME 1.0f struct props { float volume; bool mute; }; static void props_reset(struct props *props) { props->mute = DEFAULT_MUTE; props->volume = DEFAULT_VOLUME; } struct buffer { uint32_t id; #define BUFFER_FLAG_OUT (1 << 0) uint32_t flags; struct spa_list link; struct spa_buffer *outbuf; struct spa_meta_header *h; }; struct port { uint32_t direction; uint32_t id; uint64_t info_all; struct spa_port_info info; struct spa_param_info params[8]; struct spa_io_buffers *io; struct spa_audio_info format; uint32_t stride; uint32_t blocks; uint32_t size; unsigned int have_format:1; struct buffer buffers[MAX_BUFFERS]; uint32_t n_buffers; struct spa_list queue; }; struct impl { struct spa_handle handle; struct spa_node node; struct spa_log *log; uint32_t quantum_limit; struct spa_hook_list hooks; uint64_t info_all; struct spa_node_info info; struct props props; struct spa_param_info params[8]; struct spa_io_clock *clock; struct spa_io_position *position; struct port out_port; unsigned int started:1; }; #define CHECK_PORT(this,d,id) (d == SPA_DIRECTION_OUTPUT && id == 0) #define GET_OUT_PORT(this,id) (&this->out_port) #define GET_PORT(this,d,id) (d == SPA_DIRECTION_OUTPUT ? GET_OUT_PORT(this,id) : NULL) static void emit_info(struct impl *this, bool full) { uint64_t old = full ? this->info.change_mask : 0; if (full) this->info.change_mask = this->info_all; if (this->info.change_mask) { spa_node_emit_info(&this->hooks, &this->info); this->info.change_mask = old; } } static void emit_port_info(struct impl *this, struct port *port, bool full) { uint64_t old = full ? port->info.change_mask : 0; if (full) port->info.change_mask = port->info_all; if (port->info.change_mask) { spa_node_emit_port_info(&this->hooks, port->direction, port->id, &port->info); port->info.change_mask = old; } } static int impl_node_add_listener(void *object, struct spa_hook *listener, const struct spa_node_events *events, void *data) { struct impl *this = object; struct spa_hook_list save; spa_return_val_if_fail(this != NULL, -EINVAL); spa_log_trace(this->log, "%p: add listener %p", this, listener); spa_hook_list_isolate(&this->hooks, &save, listener, events, data); emit_info(this, true); emit_port_info(this, GET_OUT_PORT(this, 0), true); spa_hook_list_join(&this->hooks, &save); return 0; } static int impl_node_set_callbacks(void *object, const struct spa_node_callbacks *callbacks, void *user_data) { return 0; } static int impl_node_sync(void *object, int seq) { struct impl *this = object; spa_return_val_if_fail(this != NULL, -EINVAL); spa_node_emit_result(&this->hooks, seq, 0, 0, NULL); return 0; } static int impl_node_enum_params(void *object, int seq, uint32_t id, uint32_t start, uint32_t num, const struct spa_pod *filter) { struct impl *this = object; struct spa_pod *param; struct spa_pod_builder b = { 0 }; uint8_t buffer[1024]; struct spa_result_node_params result; uint32_t count = 0; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(num != 0, -EINVAL); result.id = id; result.next = start; next: result.index = result.next++; spa_pod_builder_init(&b, buffer, sizeof(buffer)); switch (id) { case SPA_PARAM_PropInfo: { struct props *p = &this->props; switch (result.index) { case 0: param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_PropInfo, id, SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_volume), SPA_PROP_INFO_description, SPA_POD_String("Volume"), SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Float(p->volume, 0.0, 10.0)); break; case 1: param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_PropInfo, id, SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_mute), SPA_PROP_INFO_description, SPA_POD_String("Mute"), SPA_PROP_INFO_type, SPA_POD_CHOICE_Bool(p->mute)); break; default: return 0; } break; } case SPA_PARAM_Props: { struct props *p = &this->props; switch (result.index) { case 0: param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_Props, id, SPA_PROP_volume, SPA_POD_Float(p->volume), SPA_PROP_mute, SPA_POD_Bool(p->mute)); break; default: return 0; } break; } default: return -ENOENT; } if (spa_pod_filter(&b, &result.param, param, filter) < 0) goto next; spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); if (++count != num) goto next; return 0; } static int apply_props(struct impl *this, const struct spa_pod *param) { struct spa_pod_prop *prop; struct spa_pod_object *obj = (struct spa_pod_object *) param; struct props *p = &this->props; int changed = 0; SPA_POD_OBJECT_FOREACH(obj, prop) { switch (prop->key) { case SPA_PROP_volume: if (spa_pod_get_float(&prop->value, &p->volume) == 0) changed++; break; case SPA_PROP_mute: if (spa_pod_get_bool(&prop->value, &p->mute) == 0) changed++; break; default: break; } } return changed; } static int impl_node_set_param(void *object, uint32_t id, uint32_t flags, const struct spa_pod *param) { struct impl *this = object; spa_return_val_if_fail(this != NULL, -EINVAL); switch (id) { case SPA_PARAM_Props: if (param == NULL) { props_reset(&this->props); return 0; } if (apply_props(this, param) > 0) { this->info.change_mask = SPA_NODE_CHANGE_MASK_PARAMS; this->params[1].flags ^= SPA_PARAM_INFO_SERIAL; emit_info(this, false); } break; default: return -ENOENT; } return 0; } static int impl_node_set_io(void *object, uint32_t id, void *data, size_t size) { struct impl *this = object; spa_return_val_if_fail(this != NULL, -EINVAL); switch (id) { case SPA_IO_Clock: this->clock = data; break; case SPA_IO_Position: this->position = data; break; default: return -ENOENT; } return 0; } static int impl_node_send_command(void *object, const struct spa_command *command) { struct impl *this = object; struct port *port; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(command != NULL, -EINVAL); port = GET_OUT_PORT(this, 0); switch (SPA_NODE_COMMAND_ID(command)) { case SPA_NODE_COMMAND_Start: if (!port->have_format) return -EIO; if (port->n_buffers == 0) return -EIO; this->started = true; break; case SPA_NODE_COMMAND_Pause: this->started = false; break; default: return -ENOTSUP; } return 0; } static int impl_node_add_port(void *object, enum spa_direction direction, uint32_t port_id, const struct spa_dict *props) { return -ENOTSUP; } static int impl_node_remove_port(void *object, enum spa_direction direction, uint32_t port_id) { return -ENOTSUP; } static int port_enum_formats(void *object, enum spa_direction direction, uint32_t port_id, uint32_t index, struct spa_pod **param, struct spa_pod_builder *builder) { switch (index) { case 0: *param = spa_pod_builder_add_object(builder, SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_audio), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), SPA_FORMAT_AUDIO_format, SPA_POD_Id(SPA_AUDIO_FORMAT_S16), SPA_FORMAT_AUDIO_rate, SPA_POD_CHOICE_RANGE_Int(DEFAULT_RATE, 1, INT32_MAX), SPA_FORMAT_AUDIO_channels, SPA_POD_CHOICE_RANGE_Int(DEFAULT_CHANNELS, 1, INT32_MAX)); break; case 1: *param = spa_pod_builder_add_object(builder, SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_audio), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), SPA_FORMAT_AUDIO_format, SPA_POD_Id(SPA_AUDIO_FORMAT_S32), SPA_FORMAT_AUDIO_rate, SPA_POD_CHOICE_RANGE_Int(DEFAULT_RATE, 1, INT32_MAX), SPA_FORMAT_AUDIO_channels, SPA_POD_CHOICE_RANGE_Int(DEFAULT_CHANNELS, 1, INT32_MAX)); break; default: return 0; } return 1; } static int impl_node_port_enum_params(void *object, int seq, enum spa_direction direction, uint32_t port_id, uint32_t id, uint32_t start, uint32_t num, const struct spa_pod *filter) { struct impl *this = object; struct port *port; struct spa_pod *param; struct spa_pod_builder b = { 0 }; uint8_t buffer[1024]; struct spa_result_node_params result; uint32_t count = 0; int res; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(num != 0, -EINVAL); spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); port = GET_PORT(this, direction, port_id); result.id = id; result.next = start; next: result.index = result.next++; spa_pod_builder_init(&b, buffer, sizeof(buffer)); switch (id) { case SPA_PARAM_EnumFormat: if ((res = port_enum_formats(this, direction, port_id, result.index, ¶m, &b)) <= 0) return res; break; case SPA_PARAM_Format: if (!port->have_format) return -EIO; if (result.index > 0) return 0; param = spa_format_audio_raw_build(&b, id, &port->format.info.raw); break; case SPA_PARAM_Buffers: { if (!port->have_format) return -EIO; if (result.index > 0) return 0; param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamBuffers, id, SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(2, 1, MAX_BUFFERS), SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(port->blocks), SPA_PARAM_BUFFERS_size, SPA_POD_CHOICE_RANGE_Int( this->quantum_limit * port->stride, 16 * port->stride, INT32_MAX), SPA_PARAM_BUFFERS_stride, SPA_POD_Int(port->stride)); break; } case SPA_PARAM_Meta: switch (result.index) { case 0: param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamMeta, id, SPA_PARAM_META_type, SPA_POD_Id(SPA_META_Header), SPA_PARAM_META_size, SPA_POD_Int(sizeof(struct spa_meta_header))); break; default: return 0; } break; case SPA_PARAM_IO: switch (result.index) { case 0: param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamIO, id, SPA_PARAM_IO_id, SPA_POD_Id(SPA_IO_Buffers), SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_buffers))); break; case 1: param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamIO, id, SPA_PARAM_IO_id, SPA_POD_Id(SPA_IO_Clock), SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_clock))); break; default: return 0; } break; default: return -ENOENT; } if (spa_pod_filter(&b, &result.param, param, filter) < 0) goto next; spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); if (++count != num) goto next; return 0; } static int clear_buffers(struct impl *this, struct port *port) { if (port->n_buffers > 0) { spa_log_debug(this->log, "%p: clear buffers %p", this, port); port->n_buffers = 0; spa_list_init(&port->queue); } return 0; } static int calc_width(struct spa_audio_info *info) { switch (info->info.raw.format) { case SPA_AUDIO_FORMAT_U8P: case SPA_AUDIO_FORMAT_U8: return 1; case SPA_AUDIO_FORMAT_S16P: case SPA_AUDIO_FORMAT_S16: case SPA_AUDIO_FORMAT_S16_OE: return 2; case SPA_AUDIO_FORMAT_S24P: case SPA_AUDIO_FORMAT_S24: case SPA_AUDIO_FORMAT_S24_OE: return 3; case SPA_AUDIO_FORMAT_S32P: case SPA_AUDIO_FORMAT_S32: case SPA_AUDIO_FORMAT_S32_OE: return 4; default: return 0; } } static int port_set_format(void *object, enum spa_direction direction, uint32_t port_id, uint32_t flags, const struct spa_pod *format) { struct impl *this = object; struct port *port; int res; port = GET_PORT(this, direction, port_id); if (format == NULL) { if (port->have_format) { port->have_format = false; clear_buffers(this, port); } } else { struct spa_audio_info info = { 0 }; if ((res = spa_format_parse(format, &info.media_type, &info.media_subtype)) < 0) return res; if (info.media_type != SPA_MEDIA_TYPE_audio || info.media_subtype != SPA_MEDIA_SUBTYPE_raw) return -EINVAL; if ((res = spa_format_audio_raw_parse(format, &info.info.raw)) < 0) return res; port->stride = calc_width(&info); if (port->stride == 0) return -EINVAL; if (info.info.raw.rate == 0 || info.info.raw.channels == 0) return -EINVAL; if (SPA_AUDIO_FORMAT_IS_PLANAR(info.info.raw.format)) { port->blocks = info.info.raw.channels; } else { port->stride *= info.info.raw.channels; port->blocks = 1; } port->have_format = true; port->format = info; spa_log_debug(this->log, "%p: set format on port %d %d", this, port_id, res); } port->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; if (port->have_format) { port->params[3] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_READWRITE); port->params[4] = SPA_PARAM_INFO(SPA_PARAM_Buffers, SPA_PARAM_INFO_READ); } else { port->params[3] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); port->params[4] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); } emit_port_info(this, port, false); return 0; } static int impl_node_port_set_param(void *object, enum spa_direction direction, uint32_t port_id, uint32_t id, uint32_t flags, const struct spa_pod *param) { struct impl *this = object; int res; spa_return_val_if_fail(object != NULL, -EINVAL); spa_return_val_if_fail(CHECK_PORT(object, direction, port_id), -EINVAL); spa_log_debug(this->log, "%p: set param %d", this, id); switch (id) { case SPA_PARAM_Format: res = port_set_format(object, direction, port_id, flags, param); break; default: res = -ENOENT; } return res; } static void recycle_buffer(struct impl *this, struct port *port, struct buffer *b) { if (SPA_FLAG_IS_SET(b->flags, BUFFER_FLAG_OUT)) { spa_list_append(&port->queue, &b->link); SPA_FLAG_CLEAR(b->flags, BUFFER_FLAG_OUT); spa_log_trace_fp(this->log, "%p: recycle buffer %d", this, b->id); } } static int impl_node_port_use_buffers(void *object, enum spa_direction direction, uint32_t port_id, uint32_t flags, struct spa_buffer **buffers, uint32_t n_buffers) { struct impl *this = object; struct port *port; uint32_t i, j, size = SPA_ID_INVALID; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); port = GET_PORT(this, direction, port_id); spa_return_val_if_fail(port->have_format, -EIO); spa_log_debug(this->log, "%p: use buffers %d on port %d", this, n_buffers, port_id); clear_buffers(this, port); for (i = 0; i < n_buffers; i++) { struct buffer *b; uint32_t n_datas = buffers[i]->n_datas; struct spa_data *d = buffers[i]->datas; b = &port->buffers[i]; b->id = i; b->flags = BUFFER_FLAG_OUT; b->outbuf = buffers[i]; b->h = spa_buffer_find_meta_data(buffers[i], SPA_META_Header, sizeof(*b->h)); for (j = 0; j < n_datas; j++) { if (size == SPA_ID_INVALID) size = d[j].maxsize; else if (size != d[j].maxsize) return -EINVAL; if (d[j].data == NULL) { spa_log_error(this->log, "%p: invalid memory on buffer %p", this, buffers[i]); return -EINVAL; } if (!SPA_IS_ALIGNED(d[j].data, 16)) { spa_log_warn(this->log, "%p: memory %d on buffer %d not aligned", this, j, i); } } recycle_buffer(this, port, b); } port->n_buffers = n_buffers; port->size = size; return 0; } static int impl_node_port_set_io(void *object, enum spa_direction direction, uint32_t port_id, uint32_t id, void *data, size_t size) { struct impl *this = object; struct port *port; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); port = GET_PORT(this, direction, port_id); switch (id) { case SPA_IO_Buffers: port->io = data; break; default: return -ENOENT; } return 0; } static struct buffer *dequeue_buffer(struct impl *this, struct port *port) { struct buffer *b; if (spa_list_is_empty(&port->queue)) return NULL; b = spa_list_first(&port->queue, struct buffer, link); spa_list_remove(&b->link); SPA_FLAG_SET(b->flags, BUFFER_FLAG_OUT); return b; } static int impl_node_port_reuse_buffer(void *object, uint32_t port_id, uint32_t buffer_id) { struct impl *this = object; struct port *port; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(CHECK_PORT(this, SPA_DIRECTION_OUTPUT, port_id), -EINVAL); port = GET_PORT(this, SPA_DIRECTION_OUTPUT, port_id); if (buffer_id < port->n_buffers) recycle_buffer(this, port, &port->buffers[buffer_id]); return 0; } static int impl_node_process(void *object) { struct impl *this = object; struct port *port; struct spa_io_buffers *io; struct buffer *buf; spa_return_val_if_fail(this != NULL, -EINVAL); port = GET_OUT_PORT(this, 0); if ((io = port->io) == NULL) return -EIO; spa_log_trace_fp(this->log, "%p: status %d", this, io->status); if (io->status == SPA_STATUS_HAVE_DATA) goto done; /* recycle */ if (io->buffer_id < port->n_buffers) { recycle_buffer(this, port, &port->buffers[io->buffer_id]); io->buffer_id = SPA_ID_INVALID; } if ((buf = dequeue_buffer(this, port)) == NULL) return io->status = -EPIPE; io->status = SPA_STATUS_HAVE_DATA; io->buffer_id = buf->id; done: return SPA_STATUS_HAVE_DATA; } static const struct spa_node_methods impl_node = { SPA_VERSION_NODE_METHODS, .add_listener = impl_node_add_listener, .set_callbacks = impl_node_set_callbacks, .sync = impl_node_sync, .enum_params = impl_node_enum_params, .set_param = impl_node_set_param, .set_io = impl_node_set_io, .send_command = impl_node_send_command, .add_port = impl_node_add_port, .remove_port = impl_node_remove_port, .port_enum_params = impl_node_port_enum_params, .port_set_param = impl_node_port_set_param, .port_use_buffers = impl_node_port_use_buffers, .port_set_io = impl_node_port_set_io, .port_reuse_buffer = impl_node_port_reuse_buffer, .process = impl_node_process, }; static int impl_get_interface(struct spa_handle *handle, const char *type, void **interface) { struct impl *this; spa_return_val_if_fail(handle != NULL, -EINVAL); spa_return_val_if_fail(interface != NULL, -EINVAL); this = (struct impl *) handle; if (spa_streq(type, SPA_TYPE_INTERFACE_Node)) *interface = &this->node; else return -ENOENT; return 0; } static int impl_clear(struct spa_handle *handle) { return 0; } static size_t impl_get_size(const struct spa_handle_factory *factory, const struct spa_dict *params) { return sizeof(struct impl); } static int impl_init(const struct spa_handle_factory *factory, struct spa_handle *handle, const struct spa_dict *info, const struct spa_support *support, uint32_t n_support) { struct impl *this; struct port *port; uint32_t i; spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(handle != NULL, -EINVAL); handle->get_interface = impl_get_interface; handle->clear = impl_clear; this = (struct impl *) handle; this->log = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log); for (i = 0; info && i < info->n_items; i++) { const char *k = info->items[i].key; const char *s = info->items[i].value; if (spa_streq(k, "clock.quantum-limit")) spa_atou32(s, &this->quantum_limit, 0); } spa_log_debug(this->log, "%p: init", this); spa_hook_list_init(&this->hooks); this->node.iface = SPA_INTERFACE_INIT( SPA_TYPE_INTERFACE_Node, SPA_VERSION_NODE, &impl_node, this); this->info_all = SPA_NODE_CHANGE_MASK_FLAGS | SPA_NODE_CHANGE_MASK_PARAMS; this->info = SPA_NODE_INFO_INIT(); this->info.max_output_ports = 1; this->info.flags = SPA_NODE_FLAG_RT; this->params[0] = SPA_PARAM_INFO(SPA_PARAM_PropInfo, SPA_PARAM_INFO_READ); this->params[1] = SPA_PARAM_INFO(SPA_PARAM_Props, SPA_PARAM_INFO_READWRITE); this->info.params = this->params; this->info.n_params = 2; props_reset(&this->props); port = GET_OUT_PORT(this, 0); port->direction = SPA_DIRECTION_OUTPUT; port->id = 0; port->info_all = SPA_PORT_CHANGE_MASK_FLAGS | SPA_PORT_CHANGE_MASK_PARAMS; port->info = SPA_PORT_INFO_INIT(); port->info.flags = SPA_PORT_FLAG_DYNAMIC_DATA; port->params[0] = SPA_PARAM_INFO(SPA_PARAM_EnumFormat, SPA_PARAM_INFO_READ); port->params[1] = SPA_PARAM_INFO(SPA_PARAM_Meta, SPA_PARAM_INFO_READ); port->params[2] = SPA_PARAM_INFO(SPA_PARAM_IO, SPA_PARAM_INFO_READ); port->params[3] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); port->params[4] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); port->info.params = port->params; port->info.n_params = 5; spa_list_init(&port->queue); return 0; } static const struct spa_interface_info impl_interfaces[] = { {SPA_TYPE_INTERFACE_Node,}, }; static int impl_enum_interface_info(const struct spa_handle_factory *factory, const struct spa_interface_info **info, uint32_t *index) { spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(info != NULL, -EINVAL); spa_return_val_if_fail(index != NULL, -EINVAL); switch (*index) { case 0: *info = &impl_interfaces[*index]; break; default: return 0; } (*index)++; return 1; } const struct spa_handle_factory test_source_factory = { SPA_VERSION_HANDLE_FACTORY, SPA_NAME_AUDIO_PROCESS_CHANNELMIX, NULL, impl_get_size, impl_init, impl_enum_interface_info, }; pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/audioconvert/volume-ops-c.c000066400000000000000000000010731511204443500301370ustar00rootroot00000000000000/* Spa */ /* SPDX-FileCopyrightText: Copyright © 2021 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include "volume-ops.h" void volume_f32_c(struct volume *vol, void * SPA_RESTRICT dst, const void * SPA_RESTRICT src, float volume, uint32_t n_samples) { uint32_t n; float *d = (float*)dst; const float *s = (const float*)src; if (volume == VOLUME_MIN) { memset(d, 0, n_samples * sizeof(float)); } else if (volume == VOLUME_NORM) { spa_memcpy(d, s, n_samples * sizeof(float)); } else { for (n = 0; n < n_samples; n++) d[n] = s[n] * volume; } } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/audioconvert/volume-ops-sse.c000066400000000000000000000022271511204443500305110ustar00rootroot00000000000000/* Spa */ /* SPDX-FileCopyrightText: Copyright © 2021 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include "volume-ops.h" #include void volume_f32_sse(struct volume *vol, void * SPA_RESTRICT dst, const void * SPA_RESTRICT src, float volume, uint32_t n_samples) { uint32_t n, unrolled; float *d = (float*)dst; const float *s = (const float*)src; if (volume == VOLUME_MIN) { memset(d, 0, n_samples * sizeof(float)); } else if (volume == VOLUME_NORM) { spa_memcpy(d, s, n_samples * sizeof(float)); } else { __m128 t[4]; const __m128 vol = _mm_set1_ps(volume); if (SPA_IS_ALIGNED(d, 16) && SPA_IS_ALIGNED(s, 16)) unrolled = n_samples & ~15; else unrolled = 0; for(n = 0; n < unrolled; n += 16) { t[0] = _mm_load_ps(&s[n]); t[1] = _mm_load_ps(&s[n+4]); t[2] = _mm_load_ps(&s[n+8]); t[3] = _mm_load_ps(&s[n+12]); _mm_store_ps(&d[n], _mm_mul_ps(t[0], vol)); _mm_store_ps(&d[n+4], _mm_mul_ps(t[1], vol)); _mm_store_ps(&d[n+8], _mm_mul_ps(t[2], vol)); _mm_store_ps(&d[n+12], _mm_mul_ps(t[3], vol)); } for(; n < n_samples; n++) _mm_store_ss(&d[n], _mm_mul_ss(_mm_load_ss(&s[n]), vol)); } } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/audioconvert/volume-ops.c000066400000000000000000000025401511204443500277170ustar00rootroot00000000000000/* Spa */ /* SPDX-FileCopyrightText: Copyright © 2021 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #include #include #include #include "volume-ops.h" typedef void (*volume_func_t) (struct volume *vol, void * SPA_RESTRICT dst, const void * SPA_RESTRICT src, float volume, uint32_t n_samples); #define MAKE(func,...) \ { func, #func , __VA_ARGS__ } static const struct volume_info { volume_func_t process; const char *name; uint32_t cpu_flags; } volume_table[] = { #if defined (HAVE_SSE) MAKE(volume_f32_sse, SPA_CPU_FLAG_SSE), #endif MAKE(volume_f32_c), }; #undef MAKE #define MATCH_CPU_FLAGS(a,b) ((a) == 0 || ((a) & (b)) == a) static const struct volume_info *find_volume_info(uint32_t cpu_flags) { SPA_FOR_EACH_ELEMENT_VAR(volume_table, t) { if (MATCH_CPU_FLAGS(t->cpu_flags, cpu_flags)) return t; } return NULL; } static void impl_volume_free(struct volume *vol) { vol->process = NULL; } int volume_init(struct volume *vol) { const struct volume_info *info; info = find_volume_info(vol->cpu_flags); if (info == NULL) return -ENOTSUP; vol->cpu_flags = info->cpu_flags; vol->func_name = info->name; vol->free = impl_volume_free; vol->process = info->process; return 0; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/audioconvert/volume-ops.h000066400000000000000000000020051511204443500277200ustar00rootroot00000000000000/* Spa */ /* SPDX-FileCopyrightText: Copyright © 2021 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #define VOLUME_MIN 0.0f #define VOLUME_NORM 1.0f struct volume { uint32_t cpu_flags; const char *func_name; struct spa_log *log; uint32_t flags; void (*process) (struct volume *vol, void * SPA_RESTRICT dst, const void * SPA_RESTRICT src, float volume, uint32_t n_samples); void (*free) (struct volume *vol); void *data; }; int volume_init(struct volume *vol); #define volume_process(vol,...) (vol)->process(vol, __VA_ARGS__) #define volume_free(vol) (vol)->free(vol) #define DEFINE_FUNCTION(name,arch) \ void volume_##name##_##arch(struct volume *vol, \ void * SPA_RESTRICT dst, \ const void * SPA_RESTRICT src, \ float volume, uint32_t n_samples); #define VOLUME_OPS_MAX_ALIGN 16 DEFINE_FUNCTION(f32, c); #if defined (HAVE_SSE) DEFINE_FUNCTION(f32, sse); #endif #undef DEFINE_FUNCTION pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/audioconvert/wavfile.c000066400000000000000000000157301511204443500272530ustar00rootroot00000000000000/* PipeWire * * Copyright © 2022 Wim Taymans * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice (including the next * paragraph) shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #include #include #include #include #include "wavfile.h" #define BLOCK_SIZE 4096 struct wav_file { struct spa_audio_info info; int fd; const struct format_info *fi; uint32_t length; uint32_t stride; uint32_t blocks; }; static inline ssize_t write_data(struct wav_file *wf, const void *data, size_t size) { ssize_t len; len = write(wf->fd, data, size); if (len > 0) wf->length += len; return len; } static ssize_t writei(struct wav_file *wf, const void **data, size_t samples) { return write_data(wf, data[0], samples * wf->stride); } typedef struct { uint8_t v[3]; } __attribute__ ((packed)) uint24_t; #define MAKE_WRITEN_FUNC(name, type) \ static ssize_t name (struct wav_file *wf, const void **data, size_t samples) \ { \ uint32_t b, n, k, blocks = wf->blocks, chunk; \ uint8_t buf[BLOCK_SIZE]; \ ssize_t res = 0; \ type **d = (type**)data; \ uint32_t chunk_size = sizeof(buf) / (blocks * sizeof(type)); \ for (n = 0; n < samples; ) { \ type *p = (type*)buf; \ chunk = SPA_MIN(samples - n, chunk_size); \ for (k = 0; k < chunk; k++, n++) { \ for (b = 0; b < blocks; b++) \ *p++ = d[b][n]; \ } \ res += write_data(wf, buf, \ chunk * blocks * sizeof(type)); \ } \ return res; \ } MAKE_WRITEN_FUNC(writen_8, uint8_t); MAKE_WRITEN_FUNC(writen_16, uint16_t); MAKE_WRITEN_FUNC(writen_24, uint24_t); MAKE_WRITEN_FUNC(writen_32, uint32_t); MAKE_WRITEN_FUNC(writen_64, uint64_t); static inline int write_n(int fd, const void *buf, int count) { return write(fd, buf, count) == (ssize_t)count ? count : -errno; } static inline int write_le16(int fd, uint16_t val) { uint8_t buf[2] = { val, val >> 8 }; return write_n(fd, buf, 2); } static inline int write_le32(int fd, uint32_t val) { uint8_t buf[4] = { val, val >> 8, val >> 16, val >> 24 }; return write_n(fd, buf, 4); } #define MAKE_AUDIO_RAW(format,bits,planar,fmt,...) \ { SPA_MEDIA_TYPE_audio, SPA_MEDIA_SUBTYPE_raw, format, bits, planar, fmt, __VA_ARGS__ } static struct format_info { uint32_t media_type; uint32_t media_subtype; uint32_t format; uint32_t bits; bool planar; uint32_t fmt; ssize_t (*write) (struct wav_file *wf, const void **data, size_t samples); } format_info[] = { MAKE_AUDIO_RAW(SPA_AUDIO_FORMAT_U8P, 8, true, 1, writen_8), MAKE_AUDIO_RAW(SPA_AUDIO_FORMAT_U8, 8, false, 1, writei), MAKE_AUDIO_RAW(SPA_AUDIO_FORMAT_S16P, 16, true, 1, writen_16), MAKE_AUDIO_RAW(SPA_AUDIO_FORMAT_S16_LE, 16, false, 1, writei), MAKE_AUDIO_RAW(SPA_AUDIO_FORMAT_S24P, 24, true, 1, writen_24), MAKE_AUDIO_RAW(SPA_AUDIO_FORMAT_S24_LE, 24, false, 1, writei), MAKE_AUDIO_RAW(SPA_AUDIO_FORMAT_S24_32P, 32, true, 1, writen_32), MAKE_AUDIO_RAW(SPA_AUDIO_FORMAT_S32P, 32, true, 1, writen_32), MAKE_AUDIO_RAW(SPA_AUDIO_FORMAT_S24_32_LE, 32, false, 1, writei), MAKE_AUDIO_RAW(SPA_AUDIO_FORMAT_S32_LE, 32, false, 1, writei), MAKE_AUDIO_RAW(SPA_AUDIO_FORMAT_F32P, 32, true, 3, writen_32), MAKE_AUDIO_RAW(SPA_AUDIO_FORMAT_F32_LE, 32, false, 3, writei), MAKE_AUDIO_RAW(SPA_AUDIO_FORMAT_F64P, 64, true, 3, writen_64), MAKE_AUDIO_RAW(SPA_AUDIO_FORMAT_F64_LE, 32, false, 3, writei), }; #define CHECK_RES(expr) if ((res = (expr)) < 0) return res static int write_headers(struct wav_file *wf) { int res; uint32_t channels, rate, bps, bits; const struct format_info *fi = wf->fi; lseek(wf->fd, 0, SEEK_SET); rate = wf->info.info.raw.rate; channels = wf->info.info.raw.channels; bits = fi->bits; bps = channels * bits / 8; CHECK_RES(write_n(wf->fd, "RIFF", 4)); CHECK_RES(write_le32(wf->fd, wf->length == 0 ? (uint32_t)-1 : wf->length + 12 + 8 + 16)); CHECK_RES(write_n(wf->fd, "WAVE", 4)); CHECK_RES(write_n(wf->fd, "fmt ", 4)); CHECK_RES(write_le32(wf->fd, 16)); CHECK_RES(write_le16(wf->fd, fi->fmt)); /* format */ CHECK_RES(write_le16(wf->fd, channels)); /* channels */ CHECK_RES(write_le32(wf->fd, rate)); /* rate */ CHECK_RES(write_le32(wf->fd, bps * rate)); /* bytes per sec */ CHECK_RES(write_le16(wf->fd, bps)); /* bytes per samples */ CHECK_RES(write_le16(wf->fd, bits)); /* bits per sample */ CHECK_RES(write_n(wf->fd, "data", 4)); CHECK_RES(write_le32(wf->fd, wf->length == 0 ? (uint32_t)-1 : wf->length)); return 0; } static const struct format_info *find_info(struct wav_file_info *info) { uint32_t i; if (info->info.media_type != SPA_MEDIA_TYPE_audio || info->info.media_subtype != SPA_MEDIA_SUBTYPE_raw) return NULL; for (i = 0; i < SPA_N_ELEMENTS(format_info); i++) { if (format_info[i].format == info->info.info.raw.format) return &format_info[i]; } return NULL; } static int open_write(struct wav_file *wf, const char *filename, struct wav_file_info *info) { int res; const struct format_info *fi; fi = find_info(info); if (fi == NULL) return -ENOTSUP; if ((wf->fd = open(filename, O_WRONLY | O_CREAT | O_CLOEXEC | O_TRUNC, 0660)) < 0) { res = -errno; goto exit; } wf->info = info->info; wf->fi = fi; if (fi->planar) { wf->stride = fi->bits / 8; wf->blocks = info->info.info.raw.channels; } else { wf->stride = info->info.info.raw.channels * (fi->bits / 8); wf->blocks = 1; } res = write_headers(wf); exit: return res; } struct wav_file * wav_file_open(const char *filename, const char *mode, struct wav_file_info *info) { int res; struct wav_file *wf; wf = calloc(1, sizeof(struct wav_file)); if (wf == NULL) return NULL; if (spa_streq(mode, "w")) { if ((res = open_write(wf, filename, info)) < 0) goto exit_free; } else { res = -EINVAL; goto exit_free; } return wf; exit_free: free(wf); errno = -res; return NULL; } int wav_file_close(struct wav_file *wf) { int res; CHECK_RES(write_headers(wf)); close(wf->fd); free(wf); return 0; } ssize_t wav_file_write(struct wav_file *wf, const void **data, size_t samples) { return wf->fi->write(wf, data, samples); } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/audioconvert/wavfile.h000066400000000000000000000027631511204443500272620ustar00rootroot00000000000000/* PipeWire * * Copyright © 2022 Wim Taymans * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice (including the next * paragraph) shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #include #include struct wav_file; struct wav_file_info { struct spa_audio_info info; }; struct wav_file * wav_file_open(const char *filename, const char *mode, struct wav_file_info *info); int wav_file_close(struct wav_file *wf); ssize_t wav_file_write(struct wav_file *wf, const void **data, size_t size); pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/audiomixer/000077500000000000000000000000001511204443500251105ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/audiomixer/audiomixer.c000066400000000000000000000661131511204443500274310ustar00rootroot00000000000000/* Spa */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "mix-ops.h" #undef SPA_LOG_TOPIC_DEFAULT #define SPA_LOG_TOPIC_DEFAULT &log_topic SPA_LOG_TOPIC_DEFINE_STATIC(log_topic, "spa.audiomixer"); #define DEFAULT_RATE 48000 #define DEFAULT_CHANNELS 2 #define MAX_BUFFERS 64 #define MAX_PORTS 512 #define MAX_ALIGN MIX_OPS_MAX_ALIGN #define PORT_DEFAULT_VOLUME 1.0 #define PORT_DEFAULT_MUTE false struct port_props { double volume; int32_t mute; }; static void port_props_reset(struct port_props *props) { props->volume = PORT_DEFAULT_VOLUME; props->mute = PORT_DEFAULT_MUTE; } struct buffer { uint32_t id; #define BUFFER_FLAG_QUEUED (1 << 0) uint32_t flags; struct spa_list link; struct spa_buffer *buffer; struct spa_meta_header *h; struct spa_buffer buf; }; struct port { struct spa_list link; uint32_t direction; uint32_t id; struct port_props props; struct spa_io_buffers *io[2]; uint64_t info_all; struct spa_port_info info; struct spa_param_info params[8]; unsigned int have_format:1; struct buffer buffers[MAX_BUFFERS]; uint32_t n_buffers; struct spa_list queue; size_t queued_bytes; struct spa_list mix_link; bool active; }; struct impl { struct spa_handle handle; struct spa_node node; struct spa_log *log; struct spa_cpu *cpu; uint32_t cpu_flags; uint32_t max_align; struct spa_loop *data_loop; uint32_t quantum_limit; struct mix_ops ops; uint64_t info_all; struct spa_node_info info; struct spa_param_info params[8]; struct spa_io_position *position; struct spa_hook_list hooks; struct port *in_ports[MAX_PORTS]; struct port out_ports[1]; struct spa_list port_list; struct spa_list free_list; struct buffer *mix_buffers[MAX_PORTS]; const void *mix_datas[MAX_PORTS]; int n_formats; struct spa_audio_info format; unsigned int have_format:1; unsigned int started:1; uint32_t stride; uint32_t blocks; struct spa_list mix_list; }; #define CHECK_ANY_IN(this,d,p) ((d) == SPA_DIRECTION_INPUT && (p) == SPA_ID_INVALID) #define CHECK_FREE_IN_PORT(this,d,p) ((d) == SPA_DIRECTION_INPUT && (p) < MAX_PORTS && this->in_ports[(p)] == NULL) #define CHECK_IN_PORT(this,d,p) ((d) == SPA_DIRECTION_INPUT && (p) < MAX_PORTS && this->in_ports[(p)] != NULL) #define CHECK_OUT_PORT(this,d,p) ((d) == SPA_DIRECTION_OUTPUT && (p) == 0) #define CHECK_PORT(this,d,p) (CHECK_OUT_PORT(this,d,p) || CHECK_IN_PORT (this,d,p)) #define CHECK_PORT_ANY(this,d,p) (CHECK_ANY_IN(this,d,p) || CHECK_PORT(this,d,p)) #define GET_IN_PORT(this,p) (this->in_ports[p]) #define GET_OUT_PORT(this,p) (&this->out_ports[p]) #define GET_PORT(this,d,p) (d == SPA_DIRECTION_INPUT ? GET_IN_PORT(this,p) : GET_OUT_PORT(this,p)) #define GET_PORT_ANY(this,d,p) (CHECK_ANY_IN(this,d,p) ? NULL : GET_PORT(this,d,p)) static int impl_node_enum_params(void *object, int seq, uint32_t id, uint32_t start, uint32_t num, const struct spa_pod *filter) { return -ENOTSUP; } static int impl_node_set_param(void *object, uint32_t id, uint32_t flags, const struct spa_pod *param) { return -ENOTSUP; } static int impl_node_set_io(void *object, uint32_t id, void *data, size_t size) { struct impl *this = object; switch (id) { case SPA_IO_Position: this->position = data; break; default: return -ENOTSUP; } return 0; } static int impl_node_send_command(void *object, const struct spa_command *command) { struct impl *this = object; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(command != NULL, -EINVAL); switch (SPA_NODE_COMMAND_ID(command)) { case SPA_NODE_COMMAND_Start: this->started = true; break; case SPA_NODE_COMMAND_Pause: this->started = false; break; default: return -ENOTSUP; } return 0; } static void emit_node_info(struct impl *this, bool full) { uint64_t old = full ? this->info.change_mask : 0; if (full) this->info.change_mask = this->info_all; if (this->info.change_mask) { spa_node_emit_info(&this->hooks, &this->info); this->info.change_mask = old; } } static void emit_port_info(struct impl *this, struct port *port, bool full) { uint64_t old = full ? port->info.change_mask : 0; if (full) port->info.change_mask = port->info_all; if (port->info.change_mask) { spa_node_emit_port_info(&this->hooks, port->direction, port->id, &port->info); port->info.change_mask = old; } } static int impl_node_add_listener(void *object, struct spa_hook *listener, const struct spa_node_events *events, void *data) { struct impl *this = object; struct spa_hook_list save; struct port *port; spa_return_val_if_fail(this != NULL, -EINVAL); spa_hook_list_isolate(&this->hooks, &save, listener, events, data); emit_node_info(this, true); emit_port_info(this, GET_OUT_PORT(this, 0), true); spa_list_for_each(port, &this->port_list, link) emit_port_info(this, port, true); spa_hook_list_join(&this->hooks, &save); return 0; } static int impl_node_set_callbacks(void *object, const struct spa_node_callbacks *callbacks, void *user_data) { return 0; } static struct port *get_free_port(struct impl *this) { struct port *port; if (!spa_list_is_empty(&this->free_list)) { port = spa_list_first(&this->free_list, struct port, link); spa_list_remove(&port->link); } else { port = calloc(1, sizeof(struct port)); } return port; } static int impl_node_add_port(void *object, enum spa_direction direction, uint32_t port_id, const struct spa_dict *props) { struct impl *this = object; struct port *port; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(CHECK_FREE_IN_PORT(this, direction, port_id), -EINVAL); if ((port = get_free_port(this)) == NULL) return -errno; port->direction = SPA_DIRECTION_INPUT; port->id = port_id; port_props_reset(&port->props); spa_list_init(&port->queue); port->info_all = SPA_PORT_CHANGE_MASK_FLAGS | SPA_PORT_CHANGE_MASK_PARAMS; port->info = SPA_PORT_INFO_INIT(); port->info.flags = SPA_PORT_FLAG_NO_REF | SPA_PORT_FLAG_DYNAMIC_DATA | SPA_PORT_FLAG_REMOVABLE | SPA_PORT_FLAG_OPTIONAL; port->params[0] = SPA_PARAM_INFO(SPA_PARAM_EnumFormat, SPA_PARAM_INFO_READ); port->params[1] = SPA_PARAM_INFO(SPA_PARAM_Meta, SPA_PARAM_INFO_READ); port->params[2] = SPA_PARAM_INFO(SPA_PARAM_IO, SPA_PARAM_INFO_READ); port->params[3] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); port->params[4] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); port->info.params = port->params; port->info.n_params = 5; this->in_ports[port_id] = port; spa_list_append(&this->port_list, &port->link); spa_log_debug(this->log, "%p: add port %d:%d", this, direction, port_id); emit_port_info(this, port, true); return 0; } static int impl_node_remove_port(void *object, enum spa_direction direction, uint32_t port_id) { struct impl *this = object; struct port *port; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(CHECK_IN_PORT(this, direction, port_id), -EINVAL); port = GET_IN_PORT(this, port_id); this->in_ports[port_id] = NULL; spa_list_remove(&port->link); if (port->have_format && this->have_format) { if (--this->n_formats == 0) this->have_format = false; } spa_memzero(port, sizeof(struct port)); spa_list_append(&this->free_list, &port->link); spa_log_debug(this->log, "%p: remove port %d:%d", this, direction, port_id); spa_node_emit_port_info(&this->hooks, direction, port_id, NULL); return 0; } static int port_enum_formats(void *object, struct port *port, uint32_t index, struct spa_pod **param, struct spa_pod_builder *builder) { struct impl *this = object; switch (index) { case 0: if (this->have_format) { *param = spa_pod_builder_add_object(builder, SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_audio), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), SPA_FORMAT_AUDIO_format, SPA_POD_Id(this->format.info.raw.format), SPA_FORMAT_AUDIO_rate, SPA_POD_Int(this->format.info.raw.rate), SPA_FORMAT_AUDIO_channels, SPA_POD_Int(this->format.info.raw.channels)); } else { *param = spa_pod_builder_add_object(builder, SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_audio), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), SPA_FORMAT_AUDIO_format, SPA_POD_CHOICE_ENUM_Id(12, SPA_AUDIO_FORMAT_S8, SPA_AUDIO_FORMAT_U8, SPA_AUDIO_FORMAT_S16, SPA_AUDIO_FORMAT_U16, SPA_AUDIO_FORMAT_S24, SPA_AUDIO_FORMAT_U24, SPA_AUDIO_FORMAT_S32, SPA_AUDIO_FORMAT_U32, SPA_AUDIO_FORMAT_S24_32, SPA_AUDIO_FORMAT_U24_32, SPA_AUDIO_FORMAT_F32, SPA_AUDIO_FORMAT_F64), SPA_FORMAT_AUDIO_rate, SPA_POD_CHOICE_RANGE_Int( DEFAULT_RATE, 1, INT32_MAX), SPA_FORMAT_AUDIO_channels, SPA_POD_CHOICE_RANGE_Int( DEFAULT_CHANNELS, 1, INT32_MAX)); } break; default: return 0; } return 1; } static int impl_node_port_enum_params(void *object, int seq, enum spa_direction direction, uint32_t port_id, uint32_t id, uint32_t start, uint32_t num, const struct spa_pod *filter) { struct impl *this = object; struct port *port; struct spa_pod *param; struct spa_pod_builder b = { 0 }; uint8_t buffer[1024]; struct spa_result_node_params result; uint32_t count = 0; int res; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(num != 0, -EINVAL); spa_return_val_if_fail(CHECK_PORT_ANY(this, direction, port_id), -EINVAL); port = GET_PORT_ANY(this, direction, port_id); result.id = id; result.next = start; next: result.index = result.next++; spa_pod_builder_init(&b, buffer, sizeof(buffer)); switch (id) { case SPA_PARAM_EnumFormat: if ((res = port_enum_formats(this, port, result.index, ¶m, &b)) <= 0) return res; break; case SPA_PARAM_Format: if (port == NULL || !port->have_format) return -EIO; if (result.index > 0) return 0; param = spa_format_audio_raw_build(&b, id, &this->format.info.raw); break; case SPA_PARAM_Buffers: if (port == NULL || !port->have_format) return -EIO; if (result.index > 0) return 0; param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamBuffers, id, SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(1, 1, MAX_BUFFERS), SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(this->blocks), SPA_PARAM_BUFFERS_size, SPA_POD_CHOICE_RANGE_Int( this->quantum_limit * this->stride, 16 * this->stride, INT32_MAX), SPA_PARAM_BUFFERS_stride, SPA_POD_Int(this->stride)); break; case SPA_PARAM_Meta: switch (result.index) { case 0: param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamMeta, id, SPA_PARAM_META_type, SPA_POD_Id(SPA_META_Header), SPA_PARAM_META_size, SPA_POD_Int(sizeof(struct spa_meta_header))); break; default: return 0; } break; case SPA_PARAM_IO: switch (result.index) { case 0: param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamIO, id, SPA_PARAM_IO_id, SPA_POD_Id(SPA_IO_Buffers), SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_buffers))); break; case 1: param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamIO, id, SPA_PARAM_IO_id, SPA_POD_Id(SPA_IO_AsyncBuffers), SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_async_buffers))); break; default: return 0; } break; default: return -ENOENT; } if (spa_pod_filter(&b, &result.param, param, filter) < 0) goto next; spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); if (++count != num) goto next; return 0; } static int clear_buffers(struct impl *this, struct port *port) { if (port->n_buffers > 0) { spa_log_debug(this->log, "%p: clear buffers %p", this, port); port->n_buffers = 0; spa_list_init(&port->queue); } return 0; } static int queue_buffer(struct impl *this, struct port *port, struct buffer *b) { if (SPA_FLAG_IS_SET(b->flags, BUFFER_FLAG_QUEUED)) return -EINVAL; spa_list_append(&port->queue, &b->link); SPA_FLAG_SET(b->flags, BUFFER_FLAG_QUEUED); spa_log_trace_fp(this->log, "%p: queue buffer %d", this, b->id); return 0; } static struct buffer *dequeue_buffer(struct impl *this, struct port *port) { struct buffer *b; if (spa_list_is_empty(&port->queue)) return NULL; b = spa_list_first(&port->queue, struct buffer, link); spa_list_remove(&b->link); SPA_FLAG_CLEAR(b->flags, BUFFER_FLAG_QUEUED); spa_log_trace_fp(this->log, "%p: dequeue buffer %d", this, b->id); return b; } static int calc_width(struct spa_audio_info *info) { switch (info->info.raw.format) { case SPA_AUDIO_FORMAT_U8P: case SPA_AUDIO_FORMAT_U8: case SPA_AUDIO_FORMAT_S8P: case SPA_AUDIO_FORMAT_S8: case SPA_AUDIO_FORMAT_ALAW: case SPA_AUDIO_FORMAT_ULAW: return 1; case SPA_AUDIO_FORMAT_S16P: case SPA_AUDIO_FORMAT_S16: case SPA_AUDIO_FORMAT_S16_OE: case SPA_AUDIO_FORMAT_U16: return 2; case SPA_AUDIO_FORMAT_S24P: case SPA_AUDIO_FORMAT_S24: case SPA_AUDIO_FORMAT_S24_OE: case SPA_AUDIO_FORMAT_U24: return 3; case SPA_AUDIO_FORMAT_F64P: case SPA_AUDIO_FORMAT_F64: case SPA_AUDIO_FORMAT_F64_OE: return 8; default: return 4; } } static int port_set_format(void *object, enum spa_direction direction, uint32_t port_id, uint32_t flags, const struct spa_pod *format) { struct impl *this = object; struct port *port; int res; port = GET_PORT(this, direction, port_id); spa_return_val_if_fail(!this->started || port->io == NULL, -EIO); if (format == NULL) { if (port->have_format) { port->have_format = false; if (--this->n_formats == 0) this->have_format = false; clear_buffers(this, port); } } else { struct spa_audio_info info = { 0 }; if ((res = spa_format_parse(format, &info.media_type, &info.media_subtype)) < 0) return res; if (info.media_type != SPA_MEDIA_TYPE_audio || info.media_subtype != SPA_MEDIA_SUBTYPE_raw) return -EINVAL; if (spa_format_audio_raw_parse(format, &info.info.raw) < 0) return -EINVAL; if (this->have_format) { if (memcmp(&info, &this->format, sizeof(struct spa_audio_info))) return -EINVAL; } else { if (info.info.raw.format == 0 || info.info.raw.channels == 0) return -EINVAL; this->ops.fmt = info.info.raw.format; this->ops.n_channels = info.info.raw.channels; this->ops.cpu_flags = this->cpu_flags; if ((res = mix_ops_init(&this->ops)) < 0) return res; this->stride = calc_width(&info); if (SPA_AUDIO_FORMAT_IS_PLANAR(info.info.raw.format)) { this->blocks = info.info.raw.channels; } else { this->stride *= info.info.raw.channels; this->blocks = 1; } this->have_format = true; this->format = info; } if (!port->have_format) { this->n_formats++; port->have_format = true; spa_log_debug(this->log, "%p: set format on port %d", this, port_id); } } port->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; if (port->have_format) { port->params[3] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_READWRITE); port->params[4] = SPA_PARAM_INFO(SPA_PARAM_Buffers, SPA_PARAM_INFO_READ); } else { port->params[3] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); port->params[4] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); } emit_port_info(this, port, false); return 0; } static int impl_node_port_set_param(void *object, enum spa_direction direction, uint32_t port_id, uint32_t id, uint32_t flags, const struct spa_pod *param) { struct impl *this = object; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); if (id == SPA_PARAM_Format) { return port_set_format(this, direction, port_id, flags, param); } else return -ENOENT; } static int impl_node_port_use_buffers(void *object, enum spa_direction direction, uint32_t port_id, uint32_t flags, struct spa_buffer **buffers, uint32_t n_buffers) { struct impl *this = object; struct port *port; uint32_t i; spa_return_val_if_fail(this != NULL, -EINVAL); spa_log_debug(this->log, "%p: use %d buffers on port %d:%d", this, n_buffers, direction, port_id); spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); port = GET_PORT(this, direction, port_id); spa_return_val_if_fail(!this->started || port->io == NULL, -EIO); clear_buffers(this, port); if (n_buffers > 0 && !port->have_format) return -EIO; if (n_buffers > MAX_BUFFERS) return -ENOSPC; for (i = 0; i < n_buffers; i++) { struct buffer *b; struct spa_data *d = buffers[i]->datas; b = &port->buffers[i]; b->buffer = buffers[i]; b->flags = 0; b->id = i; b->h = spa_buffer_find_meta_data(buffers[i], SPA_META_Header, sizeof(*b->h)); b->buf = *buffers[i]; if (d[0].data == NULL) { spa_log_error(this->log, "%p: invalid memory on buffer %p", this, buffers[i]); return -EINVAL; } if (!SPA_IS_ALIGNED(d[0].data, this->max_align)) { spa_log_warn(this->log, "%p: memory on buffer %d not aligned", this, i); } if (direction == SPA_DIRECTION_OUTPUT) queue_buffer(this, port, b); spa_log_debug(this->log, "%p: port %d:%d buffer:%d n_data:%d data:%p maxsize:%d", this, direction, port_id, i, buffers[i]->n_datas, d[0].data, d[0].maxsize); } port->n_buffers = n_buffers; return 0; } struct io_info { struct impl *impl; struct port *port; void *data; size_t size; }; static int do_port_set_io(struct spa_loop *loop, bool async, uint32_t seq, const void *data, size_t size, void *user_data) { struct io_info *info = user_data; struct port *port = info->port; if (info->data == NULL || info->size < sizeof(struct spa_io_buffers)) { port->io[0] = NULL; port->io[1] = NULL; if (port->active) { spa_list_remove(&port->mix_link); port->active = false; } } else { if (info->size >= sizeof(struct spa_io_async_buffers)) { struct spa_io_async_buffers *ab = info->data; port->io[0] = &ab->buffers[port->direction]; port->io[1] = &ab->buffers[port->direction^1]; } else { port->io[0] = info->data; port->io[1] = info->data; } if (!port->active) { spa_list_append(&info->impl->mix_list, &port->mix_link); port->active = true; } } return 0; } static int impl_node_port_set_io(void *object, enum spa_direction direction, uint32_t port_id, uint32_t id, void *data, size_t size) { struct impl *this = object; struct port *port; struct io_info info; spa_return_val_if_fail(this != NULL, -EINVAL); spa_log_debug(this->log, "%p: port %d:%d io %d %p/%zd", this, direction, port_id, id, data, size); spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); port = GET_PORT(this, direction, port_id); info.impl = this; info.port = port; info.data = data; info.size = size; switch (id) { case SPA_IO_Buffers: case SPA_IO_AsyncBuffers: spa_loop_locked(this->data_loop, do_port_set_io, SPA_ID_INVALID, NULL, 0, &info); break; default: return -ENOENT; } return 0; } static int impl_node_port_reuse_buffer(void *object, uint32_t port_id, uint32_t buffer_id) { struct impl *this = object; struct port *port; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(CHECK_PORT(this, SPA_DIRECTION_OUTPUT, port_id), -EINVAL); port = GET_OUT_PORT(this, 0); if (buffer_id >= port->n_buffers) return -EINVAL; return queue_buffer(this, port, &port->buffers[buffer_id]); } static int impl_node_process(void *object) { struct impl *this = object; struct port *outport, *inport; struct spa_io_buffers *outio; uint32_t n_buffers, maxsize; struct buffer **buffers; struct buffer *outb; const void **datas; uint32_t cycle = this->position->clock.cycle & 1; spa_return_val_if_fail(this != NULL, -EINVAL); outport = GET_OUT_PORT(this, 0); if ((outio = outport->io[cycle]) == NULL) return -EIO; spa_log_trace_fp(this->log, "%p: status %p %d %d", this, outio, outio->status, outio->buffer_id); if (SPA_UNLIKELY(outio->status == SPA_STATUS_HAVE_DATA)) return outio->status; /* recycle */ if (SPA_LIKELY(outio->buffer_id < outport->n_buffers)) { queue_buffer(this, outport, &outport->buffers[outio->buffer_id]); outio->buffer_id = SPA_ID_INVALID; } buffers = this->mix_buffers; datas = this->mix_datas; n_buffers = 0; maxsize = UINT32_MAX; spa_list_for_each(inport, &this->mix_list, mix_link) { struct spa_io_buffers *inio = inport->io[cycle]; struct buffer *inb; struct spa_data *bd; uint32_t size, offs; if (inio->buffer_id >= inport->n_buffers || inio->status != SPA_STATUS_HAVE_DATA) { spa_log_trace_fp(this->log, "%p: skip input idx:%d " "io:%p status:%d buf_id:%d n_buffers:%d", this, inport->id, inio, inio->status, inio->buffer_id, inport->n_buffers); continue; } inb = &inport->buffers[inio->buffer_id]; bd = &inb->buffer->datas[0]; offs = SPA_MIN(bd->chunk->offset, bd->maxsize); size = SPA_MIN(bd->maxsize - offs, bd->chunk->size); maxsize = SPA_MIN(size, maxsize); spa_log_trace_fp(this->log, "%p: mix input %d %p->%p %d %d %d:%d/%d", this, inport->id, inio, outio, inio->status, inio->buffer_id, offs, size, this->stride); if (!SPA_FLAG_IS_SET(bd->chunk->flags, SPA_CHUNK_FLAG_EMPTY)) { datas[n_buffers] = SPA_PTROFF(bd->data, offs, void); buffers[n_buffers++] = inb; } inio->status = SPA_STATUS_NEED_DATA; } outb = dequeue_buffer(this, outport); if (SPA_UNLIKELY(outb == NULL)) { if (outport->n_buffers > 0) spa_log_warn(this->log, "%p: out of buffers (%d)", this, outport->n_buffers); return -EPIPE; } if (n_buffers == 1) { *outb->buffer = *buffers[0]->buffer; } else { struct spa_data *d = outb->buf.datas; *outb->buffer = outb->buf; maxsize = SPA_MIN(maxsize, d[0].maxsize); d[0].chunk->offset = 0; d[0].chunk->size = maxsize; d[0].chunk->stride = this->stride; SPA_FLAG_UPDATE(d[0].chunk->flags, SPA_CHUNK_FLAG_EMPTY, n_buffers == 0); mix_ops_process(&this->ops, d[0].data, datas, n_buffers, maxsize / this->stride); } outio->buffer_id = outb->id; outio->status = SPA_STATUS_HAVE_DATA; return SPA_STATUS_HAVE_DATA | SPA_STATUS_NEED_DATA; } static const struct spa_node_methods impl_node = { SPA_VERSION_NODE_METHODS, .add_listener = impl_node_add_listener, .set_callbacks = impl_node_set_callbacks, .enum_params = impl_node_enum_params, .set_param = impl_node_set_param, .set_io = impl_node_set_io, .send_command = impl_node_send_command, .add_port = impl_node_add_port, .remove_port = impl_node_remove_port, .port_enum_params = impl_node_port_enum_params, .port_set_param = impl_node_port_set_param, .port_use_buffers = impl_node_port_use_buffers, .port_set_io = impl_node_port_set_io, .port_reuse_buffer = impl_node_port_reuse_buffer, .process = impl_node_process, }; static int impl_get_interface(struct spa_handle *handle, const char *type, void **interface) { struct impl *this; spa_return_val_if_fail(handle != NULL, -EINVAL); spa_return_val_if_fail(interface != NULL, -EINVAL); this = (struct impl *) handle; if (spa_streq(type, SPA_TYPE_INTERFACE_Node)) *interface = &this->node; else return -ENOENT; return 0; } static int impl_clear(struct spa_handle *handle) { struct impl *this; struct port *port; spa_return_val_if_fail(handle != NULL, -EINVAL); this = (struct impl *) handle; spa_list_insert_list(&this->free_list, &this->port_list); spa_list_consume(port, &this->free_list, link) { spa_list_remove(&port->link); free(port); } spa_list_init(&this->mix_list); if (this->ops.free) mix_ops_free(&this->ops); return 0; } static size_t impl_get_size(const struct spa_handle_factory *factory, const struct spa_dict *params) { return sizeof(struct impl); } static int impl_init(const struct spa_handle_factory *factory, struct spa_handle *handle, const struct spa_dict *info, const struct spa_support *support, uint32_t n_support) { struct impl *this; struct port *port; uint32_t i; spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(handle != NULL, -EINVAL); handle->get_interface = impl_get_interface; handle->clear = impl_clear; this = (struct impl *) handle; this->log = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log); spa_log_topic_init(this->log, &log_topic); this->data_loop = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_DataLoop); if (this->data_loop == NULL) { spa_log_error(this->log, "a data loop is needed"); return -EINVAL; } this->cpu = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_CPU); if (this->cpu) { this->cpu_flags = spa_cpu_get_flags(this->cpu); this->max_align = SPA_MIN(MAX_ALIGN, spa_cpu_get_max_align(this->cpu)); } for (i = 0; info && i < info->n_items; i++) { const char *k = info->items[i].key; const char *s = info->items[i].value; if (spa_streq(k, "clock.quantum-limit")) spa_atou32(s, &this->quantum_limit, 0); } spa_hook_list_init(&this->hooks); spa_list_init(&this->port_list); spa_list_init(&this->free_list); spa_list_init(&this->mix_list); this->node.iface = SPA_INTERFACE_INIT( SPA_TYPE_INTERFACE_Node, SPA_VERSION_NODE, &impl_node, this); this->info = SPA_NODE_INFO_INIT(); this->info.max_input_ports = MAX_PORTS; this->info.max_output_ports = 1; this->info.change_mask |= SPA_NODE_CHANGE_MASK_FLAGS; this->info.flags = SPA_NODE_FLAG_RT | SPA_NODE_FLAG_IN_DYNAMIC_PORTS; this->info_all = this->info.change_mask; port = GET_OUT_PORT(this, 0); port->direction = SPA_DIRECTION_OUTPUT; port->id = 0; port->info = SPA_PORT_INFO_INIT(); port->info.change_mask |= SPA_PORT_CHANGE_MASK_FLAGS; port->info.flags = SPA_PORT_FLAG_DYNAMIC_DATA; port->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; port->info_all = port->info.change_mask; port->params[0] = SPA_PARAM_INFO(SPA_PARAM_EnumFormat, SPA_PARAM_INFO_READ); port->params[1] = SPA_PARAM_INFO(SPA_PARAM_Meta, SPA_PARAM_INFO_READ); port->params[2] = SPA_PARAM_INFO(SPA_PARAM_IO, SPA_PARAM_INFO_READ); port->params[3] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); port->params[4] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); port->info.params = port->params; port->info.n_params = 5; spa_list_init(&port->queue); return 0; } static const struct spa_interface_info impl_interfaces[] = { {SPA_TYPE_INTERFACE_Node,}, }; static int impl_enum_interface_info(const struct spa_handle_factory *factory, const struct spa_interface_info **info, uint32_t *index) { spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(info != NULL, -EINVAL); spa_return_val_if_fail(index != NULL, -EINVAL); switch (*index) { case 0: *info = &impl_interfaces[*index]; break; default: return 0; } (*index)++; return 1; } const struct spa_handle_factory spa_audiomixer_factory = { SPA_VERSION_HANDLE_FACTORY, SPA_NAME_AUDIO_MIXER, NULL, impl_get_size, impl_init, impl_enum_interface_info, }; pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/audiomixer/benchmark-mix-ops.c000066400000000000000000000102671511204443500306060ustar00rootroot00000000000000/* Spa */ /* SPDX-FileCopyrightText: Copyright © 2019 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include "config.h" #include #include #include #include #include #include #include "test-helper.h" #include "mix-ops.h" static uint32_t cpu_flags; typedef void (*mix_func_t) (struct mix_ops *ops, void * SPA_RESTRICT dst, const void * SPA_RESTRICT src[], uint32_t n_src, uint32_t n_samples); struct stats { uint32_t n_samples; uint32_t n_src; uint64_t perf; const char *name; const char *impl; }; #define MAX_SAMPLES 4096 #define MAX_SRC 11 #define MAX_COUNT 100 static uint8_t samp_in[MAX_SAMPLES * MAX_SRC * 8]; static uint8_t samp_out[MAX_SAMPLES * 8]; static const int sample_sizes[] = { 0, 1, 128, 513, 4096 }; static const int src_counts[] = { 1, 2, 4, 6, 8, 11 }; #define MAX_RESULTS SPA_N_ELEMENTS(sample_sizes) * SPA_N_ELEMENTS(src_counts) * 70 static uint32_t n_results = 0; static struct stats results[MAX_RESULTS]; static void run_test1(const char *name, const char *impl, mix_func_t func, int n_src, int n_samples) { int i, j; const void *ip[n_src]; void *op; struct timespec ts; uint64_t count, t1, t2; struct mix_ops mix; mix.n_channels = 1; for (j = 0; j < n_src; j++) ip[j] = SPA_PTR_ALIGN(&samp_in[j * n_samples * 4], 32, void); op = SPA_PTR_ALIGN(samp_out, 32, void); clock_gettime(CLOCK_MONOTONIC, &ts); t1 = SPA_TIMESPEC_TO_NSEC(&ts); count = 0; for (i = 0; i < MAX_COUNT; i++) { func(&mix, op, ip, n_src, n_samples); count++; } clock_gettime(CLOCK_MONOTONIC, &ts); t2 = SPA_TIMESPEC_TO_NSEC(&ts); spa_assert(n_results < MAX_RESULTS); results[n_results++] = (struct stats) { .n_samples = n_samples, .n_src = n_src, .perf = count * (uint64_t)SPA_NSEC_PER_SEC / (t2 - t1), .name = name, .impl = impl }; } static void run_test(const char *name, const char *impl, mix_func_t func) { size_t i, j; for (i = 0; i < SPA_N_ELEMENTS(sample_sizes); i++) { for (j = 0; j < SPA_N_ELEMENTS(src_counts); j++) { run_test1(name, impl, func, src_counts[j], (sample_sizes[i] + (src_counts[j] -1)) / src_counts[j]); } } } static void test_s8(void) { run_test("test_s8", "c", mix_s8_c); } static void test_u8(void) { run_test("test_u8", "c", mix_u8_c); } static void test_s16(void) { run_test("test_s16", "c", mix_s16_c); } static void test_u16(void) { run_test("test_u8", "c", mix_u16_c); } static void test_s24(void) { run_test("test_s24", "c", mix_s24_c); } static void test_u24(void) { run_test("test_u24", "c", mix_u24_c); } static void test_s24_32(void) { run_test("test_s24_32", "c", mix_s24_32_c); } static void test_u24_32(void) { run_test("test_u24_32", "c", mix_u24_32_c); } static void test_s32(void) { run_test("test_s32", "c", mix_s32_c); } static void test_u32(void) { run_test("test_u32", "c", mix_u32_c); } static void test_f32(void) { run_test("test_f32", "c", mix_f32_c); #if defined (HAVE_SSE) if (cpu_flags & SPA_CPU_FLAG_SSE) { run_test("test_f32", "sse", mix_f32_sse); } #endif #if defined (HAVE_AVX) if (cpu_flags & SPA_CPU_FLAG_AVX) { run_test("test_f32", "avx", mix_f32_avx); } #endif } static void test_f64(void) { run_test("test_f64", "c", mix_f64_c); #if defined (HAVE_SSE2) if (cpu_flags & SPA_CPU_FLAG_SSE2) { run_test("test_f64", "sse2", mix_f64_sse2); } #endif } static int compare_func(const void *_a, const void *_b) { const struct stats *a = _a, *b = _b; int diff; if ((diff = strcmp(a->name, b->name)) != 0) return diff; if ((diff = a->n_samples - b->n_samples) != 0) return diff; if ((diff = a->n_src - b->n_src) != 0) return diff; if ((diff = b->perf - a->perf) != 0) return diff; return 0; } int main(int argc, char *argv[]) { uint32_t i; cpu_flags = get_cpu_flags(); printf("got get CPU flags %d\n", cpu_flags); test_s8(); test_u8(); test_s16(); test_u16(); test_s24(); test_u24(); test_s32(); test_u32(); test_s24_32(); test_u24_32(); test_f32(); test_f64(); qsort(results, n_results, sizeof(struct stats), compare_func); for (i = 0; i < n_results; i++) { struct stats *s = &results[i]; fprintf(stderr, "%-12."PRIu64" \t%-32.32s %s \t samples %d, src %d\n", s->perf, s->name, s->impl, s->n_samples, s->n_src); } return 0; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/audiomixer/meson.build000066400000000000000000000067231511204443500272620ustar00rootroot00000000000000audiomixer_sources = [ 'audiomixer.c', 'mixer-dsp.c', 'plugin.c' ] simd_cargs = [] simd_dependencies = [] audiomixer_c = static_library('audiomixer_c', ['mix-ops-c.c' ], c_args : ['-O3'], dependencies : [ spa_dep ], install : false ) simd_dependencies += audiomixer_c if have_sse audiomixer_sse = static_library('audiomixer_sse', ['mix-ops-sse.c' ], c_args : [sse_args, '-O3', '-DHAVE_SSE'], dependencies : [ spa_dep ], install : false ) simd_cargs += ['-DHAVE_SSE'] simd_dependencies += audiomixer_sse endif if have_sse2 audiomixer_sse2 = static_library('audiomixer_sse2', ['mix-ops-sse2.c' ], c_args : [sse2_args, '-O3', '-DHAVE_SSE2'], dependencies : [ spa_dep ], install : false ) simd_cargs += ['-DHAVE_SSE2'] simd_dependencies += audiomixer_sse2 endif if have_avx and have_fma audiomixer_avx = static_library('audiomixer_avx', ['mix-ops-avx.c'], c_args : [avx_args, fma_args, '-O3', '-DHAVE_AVX', '-DHAVE_FMA'], dependencies : [ spa_dep ], install : false ) simd_cargs += ['-DHAVE_AVX', '-DHAVE_FMA'] simd_dependencies += audiomixer_avx endif audiomixer_lib = static_library('audiomixer', ['mix-ops.c' ], c_args : [ simd_cargs, '-O3'], link_with : simd_dependencies, include_directories : [configinc], dependencies : [ spa_dep ], install : false ) audiomixer_dep = declare_dependency(link_with: audiomixer_lib) spa_audiomixer_lib = shared_library('spa-audiomixer', audiomixer_sources, c_args : simd_cargs, link_with : simd_dependencies, dependencies : [ spa_dep, mathlib, audiomixer_dep ], install : true, install_dir : spa_plugindir / 'audiomixer' ) spa_audiomixer_dep = declare_dependency(link_with: spa_audiomixer_lib) test_apps = [ 'test-mix-ops', ] foreach a : test_apps test(a, executable(a, a + '.c', dependencies : [ spa_dep, dl_lib, pthread_lib, mathlib, audiomixer_dep ], include_directories : [ configinc, test_inc ], link_with : [ test_lib ], install_rpath : spa_plugindir / 'audiomixer', c_args : [ simd_cargs ], install : installed_tests_enabled, install_dir : installed_tests_execdir / 'audiomixer'), env : [ 'SPA_PLUGIN_DIR=@0@'.format(spa_dep.get_variable('plugindir')), ]) if installed_tests_enabled test_conf = configuration_data() test_conf.set('exec', installed_tests_execdir / 'audiomixer' / a) configure_file( input: installed_tests_template, output: a + '.test', install_dir: installed_tests_metadir / 'audiomixer', configuration: test_conf ) endif endforeach benchmark_apps = [ 'benchmark-mix-ops', ] foreach a : benchmark_apps benchmark(a, executable(a, a + '.c', dependencies : [ spa_dep, dl_lib, pthread_lib, mathlib, audiomixer_dep ], include_directories : [ configinc, test_inc ], c_args : [ simd_cargs ], install_rpath : spa_plugindir / 'audiomixer', install : installed_tests_enabled, install_dir : installed_tests_execdir / 'audiomixer'), env : [ 'SPA_PLUGIN_DIR=@0@'.format(spa_dep.get_variable('plugindir')), ]) if installed_tests_enabled test_conf = configuration_data() test_conf.set('exec', installed_tests_execdir / 'audiomixer' / a) configure_file( input: installed_tests_template, output: a + '.test', install_dir: installed_tests_metadir / 'audiomixer', configuration: test_conf ) endif endforeach pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/audiomixer/mix-ops-avx.c000066400000000000000000000034151511204443500274470ustar00rootroot00000000000000/* Spa */ /* SPDX-FileCopyrightText: Copyright © 2019 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #include "mix-ops.h" #include void mix_f32_avx(struct mix_ops *ops, void * SPA_RESTRICT dst, const void * SPA_RESTRICT src[], uint32_t n_src, uint32_t n_samples) { n_samples *= ops->n_channels; if (n_src == 0) memset(dst, 0, n_samples * ops->n_channels * sizeof(float)); else if (n_src == 1) { if (dst != src[0]) spa_memcpy(dst, src[0], n_samples * sizeof(float)); } else { uint32_t i, n, unrolled; const float **s = (const float **)src; float *d = dst; if (SPA_LIKELY(SPA_IS_ALIGNED(dst, 32))) { unrolled = n_samples & ~31; for (i = 0; i < n_src; i++) { if (SPA_UNLIKELY(!SPA_IS_ALIGNED(src[i], 32))) { unrolled = 0; break; } } } else unrolled = 0; for (n = 0; n < unrolled; n += 32) { __m256 in[4]; in[0] = _mm256_load_ps(&s[0][n + 0]); in[1] = _mm256_load_ps(&s[0][n + 8]); in[2] = _mm256_load_ps(&s[0][n + 16]); in[3] = _mm256_load_ps(&s[0][n + 24]); for (i = 1; i < n_src; i++) { in[0] = _mm256_add_ps(in[0], _mm256_load_ps(&s[i][n + 0])); in[1] = _mm256_add_ps(in[1], _mm256_load_ps(&s[i][n + 8])); in[2] = _mm256_add_ps(in[2], _mm256_load_ps(&s[i][n + 16])); in[3] = _mm256_add_ps(in[3], _mm256_load_ps(&s[i][n + 24])); } _mm256_store_ps(&d[n + 0], in[0]); _mm256_store_ps(&d[n + 8], in[1]); _mm256_store_ps(&d[n + 16], in[2]); _mm256_store_ps(&d[n + 24], in[3]); } for (; n < n_samples; n++) { __m128 in[1]; in[0] = _mm_load_ss(&s[0][n]); for (i = 1; i < n_src; i++) in[0] = _mm_add_ss(in[0], _mm_load_ss(&s[i][n])); _mm_store_ss(&d[n], in[0]); } } } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/audiomixer/mix-ops-c.c000066400000000000000000000033301511204443500270670ustar00rootroot00000000000000/* Spa */ /* SPDX-FileCopyrightText: Copyright © 2019 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #include "mix-ops.h" #define MAKE_FUNC(name,type,atype,accum,clamp,zero) \ void mix_ ##name## _c(struct mix_ops *ops, \ void * SPA_RESTRICT dst, const void * SPA_RESTRICT src[], \ uint32_t n_src, uint32_t n_samples) \ { \ uint32_t i, n; \ type *d = dst; \ const type **s = (const type **)src; \ n_samples *= ops->n_channels; \ if (n_src == 0 && zero) \ memset(dst, 0, n_samples * sizeof(type)); \ else if (n_src == 1) { \ if (dst != src[0]) \ spa_memcpy(dst, src[0], n_samples * sizeof(type)); \ } else { \ for (n = 0; n < n_samples; n++) { \ atype ac = 0; \ for (i = 0; i < n_src; i++) \ ac = accum (ac, s[i][n]); \ d[n] = clamp (ac); \ } \ } \ } MAKE_FUNC(s8, int8_t, int16_t, S8_ACCUM, S8_CLAMP, true); MAKE_FUNC(u8, uint8_t, int16_t, U8_ACCUM, U8_CLAMP, false); MAKE_FUNC(s16, int16_t, int32_t, S16_ACCUM, S16_CLAMP, true); MAKE_FUNC(u16, uint16_t, int16_t, U16_ACCUM, U16_CLAMP, false); MAKE_FUNC(s24, int24_t, int32_t, S24_ACCUM, S24_CLAMP, false); MAKE_FUNC(u24, uint24_t, int32_t, U24_ACCUM, U24_CLAMP, false); MAKE_FUNC(s32, int32_t, int64_t, S32_ACCUM, S32_CLAMP, true); MAKE_FUNC(u32, uint32_t, int64_t, U32_ACCUM, U32_CLAMP, false); MAKE_FUNC(s24_32, int32_t, int32_t, S24_32_ACCUM, S24_32_CLAMP, true); MAKE_FUNC(u24_32, uint32_t, int32_t, U24_32_ACCUM, U24_32_CLAMP, false); MAKE_FUNC(f32, float, float, F32_ACCUM, F32_CLAMP, true); MAKE_FUNC(f64, double, double, F64_ACCUM, F64_CLAMP, true); pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/audiomixer/mix-ops-sse.c000066400000000000000000000032451511204443500274440ustar00rootroot00000000000000/* Spa */ /* SPDX-FileCopyrightText: Copyright © 2019 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #include "mix-ops.h" #include void mix_f32_sse(struct mix_ops *ops, void * SPA_RESTRICT dst, const void * SPA_RESTRICT src[], uint32_t n_src, uint32_t n_samples) { n_samples *= ops->n_channels; if (n_src == 0) { memset(dst, 0, n_samples * sizeof(float)); } else if (n_src == 1) { if (dst != src[0]) spa_memcpy(dst, src[0], n_samples * sizeof(float)); } else { uint32_t n, i, unrolled; __m128 in[4]; const float **s = (const float **)src; float *d = dst; if (SPA_LIKELY(SPA_IS_ALIGNED(dst, 16))) { unrolled = n_samples & ~15; for (i = 0; i < n_src; i++) { if (SPA_UNLIKELY(!SPA_IS_ALIGNED(src[i], 16))) { unrolled = 0; break; } } } else unrolled = 0; for (n = 0; n < unrolled; n += 16) { in[0] = _mm_load_ps(&s[0][n+ 0]); in[1] = _mm_load_ps(&s[0][n+ 4]); in[2] = _mm_load_ps(&s[0][n+ 8]); in[3] = _mm_load_ps(&s[0][n+12]); for (i = 1; i < n_src; i++) { in[0] = _mm_add_ps(in[0], _mm_load_ps(&s[i][n+ 0])); in[1] = _mm_add_ps(in[1], _mm_load_ps(&s[i][n+ 4])); in[2] = _mm_add_ps(in[2], _mm_load_ps(&s[i][n+ 8])); in[3] = _mm_add_ps(in[3], _mm_load_ps(&s[i][n+12])); } _mm_store_ps(&d[n+ 0], in[0]); _mm_store_ps(&d[n+ 4], in[1]); _mm_store_ps(&d[n+ 8], in[2]); _mm_store_ps(&d[n+12], in[3]); } for (; n < n_samples; n++) { in[0] = _mm_load_ss(&s[0][n]); for (i = 1; i < n_src; i++) in[0] = _mm_add_ss(in[0], _mm_load_ss(&s[i][n])); _mm_store_ss(&d[n], in[0]); } } } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/audiomixer/mix-ops-sse2.c000066400000000000000000000032371511204443500275270ustar00rootroot00000000000000/* Spa */ /* SPDX-FileCopyrightText: Copyright © 2019 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #include "mix-ops.h" #include void mix_f64_sse2(struct mix_ops *ops, void * SPA_RESTRICT dst, const void * SPA_RESTRICT src[], uint32_t n_src, uint32_t n_samples) { n_samples *= ops->n_channels; if (n_src == 0) { memset(dst, 0, n_samples * sizeof(double)); } else if (n_src == 1) { if (dst != src[0]) spa_memcpy(dst, src[0], n_samples * sizeof(double)); } else { uint32_t n, i, unrolled; __m128d in[4]; const double **s = (const double **)src; double *d = dst; if (SPA_LIKELY(SPA_IS_ALIGNED(dst, 16))) { unrolled = n_samples & ~15; for (i = 0; i < n_src; i++) { if (SPA_UNLIKELY(!SPA_IS_ALIGNED(src[i], 16))) { unrolled = 0; break; } } } else unrolled = 0; for (n = 0; n < unrolled; n += 8) { in[0] = _mm_load_pd(&s[0][n+0]); in[1] = _mm_load_pd(&s[0][n+2]); in[2] = _mm_load_pd(&s[0][n+4]); in[3] = _mm_load_pd(&s[0][n+6]); for (i = 1; i < n_src; i++) { in[0] = _mm_add_pd(in[0], _mm_load_pd(&s[i][n+0])); in[1] = _mm_add_pd(in[1], _mm_load_pd(&s[i][n+2])); in[2] = _mm_add_pd(in[2], _mm_load_pd(&s[i][n+4])); in[3] = _mm_add_pd(in[3], _mm_load_pd(&s[i][n+6])); } _mm_store_pd(&d[n+0], in[0]); _mm_store_pd(&d[n+2], in[1]); _mm_store_pd(&d[n+4], in[2]); _mm_store_pd(&d[n+6], in[3]); } for (; n < n_samples; n++) { in[0] = _mm_load_sd(&s[0][n]); for (i = 1; i < n_src; i++) in[0] = _mm_add_sd(in[0], _mm_load_sd(&s[i][n])); _mm_store_sd(&d[n], in[0]); } } } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/audiomixer/mix-ops.c000066400000000000000000000060671511204443500266610ustar00rootroot00000000000000/* Spa */ /* SPDX-FileCopyrightText: Copyright © 2019 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #include #include #include "mix-ops.h" typedef void (*mix_func_t) (struct mix_ops *ops, void * SPA_RESTRICT dst, const void * SPA_RESTRICT src[], uint32_t n_src, uint32_t n_samples); struct mix_info { uint32_t fmt; uint32_t n_channels; uint32_t cpu_flags; uint32_t stride; mix_func_t process; }; static struct mix_info mix_table[] = { /* f32 */ #if defined(HAVE_AVX) { SPA_AUDIO_FORMAT_F32, 0, SPA_CPU_FLAG_AVX, 4, mix_f32_avx }, { SPA_AUDIO_FORMAT_F32P, 0, SPA_CPU_FLAG_AVX, 4, mix_f32_avx }, #endif #if defined (HAVE_SSE) { SPA_AUDIO_FORMAT_F32, 0, SPA_CPU_FLAG_SSE, 4, mix_f32_sse }, { SPA_AUDIO_FORMAT_F32P, 0, SPA_CPU_FLAG_SSE, 4, mix_f32_sse }, #endif { SPA_AUDIO_FORMAT_F32, 0, 0, 4, mix_f32_c }, { SPA_AUDIO_FORMAT_F32P, 0, 0, 4, mix_f32_c }, /* f64 */ #if defined (HAVE_SSE2) { SPA_AUDIO_FORMAT_F64, 0, SPA_CPU_FLAG_SSE2, 8, mix_f64_sse2 }, { SPA_AUDIO_FORMAT_F64P, 0, SPA_CPU_FLAG_SSE2, 8, mix_f64_sse2 }, #endif { SPA_AUDIO_FORMAT_F64, 0, 0, 8, mix_f64_c }, { SPA_AUDIO_FORMAT_F64P, 0, 0, 8, mix_f64_c }, /* s8 */ { SPA_AUDIO_FORMAT_S8, 0, 0, 1, mix_s8_c }, { SPA_AUDIO_FORMAT_S8P, 0, 0, 1, mix_s8_c }, { SPA_AUDIO_FORMAT_U8, 0, 0, 1, mix_u8_c }, { SPA_AUDIO_FORMAT_U8P, 0, 0, 1, mix_u8_c }, /* s16 */ { SPA_AUDIO_FORMAT_S16, 0, 0, 2, mix_s16_c }, { SPA_AUDIO_FORMAT_S16P, 0, 0, 2, mix_s16_c }, { SPA_AUDIO_FORMAT_U16, 0, 0, 2, mix_u16_c }, /* s24 */ { SPA_AUDIO_FORMAT_S24, 0, 0, 3, mix_s24_c }, { SPA_AUDIO_FORMAT_S24P, 0, 0, 3, mix_s24_c }, { SPA_AUDIO_FORMAT_U24, 0, 0, 3, mix_u24_c }, /* s32 */ { SPA_AUDIO_FORMAT_S32, 0, 0, 4, mix_s32_c }, { SPA_AUDIO_FORMAT_S32P, 0, 0, 4, mix_s32_c }, { SPA_AUDIO_FORMAT_U32, 0, 0, 4, mix_u32_c }, /* s24_32 */ { SPA_AUDIO_FORMAT_S24_32, 0, 0, 4, mix_s24_32_c }, { SPA_AUDIO_FORMAT_S24_32P, 0, 0, 4, mix_s24_32_c }, { SPA_AUDIO_FORMAT_U24_32, 0, 0, 4, mix_u24_32_c }, }; #define MATCH_CHAN(a,b) ((a) == 0 || (a) == (b)) #define MATCH_CPU_FLAGS(a,b) ((a) == 0 || ((a) & (b)) == a) static const struct mix_info *find_mix_info(uint32_t fmt, uint32_t n_channels, uint32_t cpu_flags) { SPA_FOR_EACH_ELEMENT_VAR(mix_table, t) { if (t->fmt == fmt && MATCH_CHAN(t->n_channels, n_channels) && MATCH_CPU_FLAGS(t->cpu_flags, cpu_flags)) return t; } return NULL; } static void impl_mix_ops_clear(struct mix_ops *ops, void * SPA_RESTRICT dst, uint32_t n_samples) { const struct mix_info *info = ops->priv; memset(dst, 0, n_samples * info->stride); } static void impl_mix_ops_free(struct mix_ops *ops) { spa_zero(*ops); } int mix_ops_init(struct mix_ops *ops) { const struct mix_info *info; info = find_mix_info(ops->fmt, ops->n_channels, ops->cpu_flags); if (info == NULL) return -ENOTSUP; ops->priv = info; ops->cpu_flags = info->cpu_flags; ops->clear = impl_mix_ops_clear; ops->process = info->process; ops->free = impl_mix_ops_free; return 0; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/audiomixer/mix-ops.h000066400000000000000000000100711511204443500266540ustar00rootroot00000000000000/* Spa */ /* SPDX-FileCopyrightText: Copyright © 2019 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include typedef struct { #if __BYTE_ORDER == __LITTLE_ENDIAN uint8_t v3; uint8_t v2; uint8_t v1; #else uint8_t v1; uint8_t v2; uint8_t v3; #endif } __attribute__ ((packed)) uint24_t; typedef struct { #if __BYTE_ORDER == __LITTLE_ENDIAN uint8_t v3; uint8_t v2; int8_t v1; #else int8_t v1; uint8_t v2; uint8_t v3; #endif } __attribute__ ((packed)) int24_t; static inline uint32_t u24_to_u32(uint24_t src) { return ((uint32_t)src.v1 << 16) | ((uint32_t)src.v2 << 8) | (uint32_t)src.v3; } #define U32_TO_U24(s) (uint24_t) { .v1 = (uint8_t)(((uint32_t)s) >> 16), \ .v2 = (uint8_t)(((uint32_t)s) >> 8), .v3 = (uint8_t)((uint32_t)s) } static inline uint24_t u32_to_u24(uint32_t src) { return U32_TO_U24(src); } static inline int32_t s24_to_s32(int24_t src) { return ((uint32_t)((int32_t)src.v1 & 0xFFFF) << 16) | ((uint32_t)src.v2 << 8) | (uint32_t)src.v3; } #define S32_TO_S24(s) (int24_t) { .v1 = (int8_t)(((int32_t)s) >> 16), \ .v2 = (uint8_t)(((uint32_t)s) >> 8), .v3 = (uint8_t)((uint32_t)s) } static inline int24_t s32_to_s24(int32_t src) { return S32_TO_S24(src); } #define S8_MIN -128 #define S8_MAX 127 #define S8_ACCUM(a,b) ((a) + (int16_t)(b)) #define S8_CLAMP(a) (int8_t)(SPA_CLAMP((a), S8_MIN, S8_MAX)) #define U8_OFFS 128 #define U8_ACCUM(a,b) ((a) + ((int16_t)(b) - U8_OFFS)) #define U8_CLAMP(a) (uint8_t)(SPA_CLAMP((a), S8_MIN, S8_MAX) + U8_OFFS) #define S16_MIN -32768 #define S16_MAX 32767 #define S16_ACCUM(a,b) ((a) + (int32_t)(b)) #define S16_CLAMP(a) (int16_t)(SPA_CLAMP((a), S16_MIN, S16_MAX)) #define U16_OFFS 32768 #define U16_ACCUM(a,b) ((a) + ((int32_t)(b) - U16_OFFS)) #define U16_CLAMP(a) (uint16_t)(SPA_CLAMP((a), S16_MIN, S16_MAX) + U16_OFFS) #define S24_32_MIN -8388608 #define S24_32_MAX 8388607 #define S24_32_ACCUM(a,b) ((a) + (int32_t)(b)) #define S24_32_CLAMP(a) (int32_t)(SPA_CLAMP((a), S24_32_MIN, S24_32_MAX)) #define U24_32_OFFS 8388608 #define U24_32_ACCUM(a,b) ((a) + ((int32_t)(b) - U24_32_OFFS)) #define U24_32_CLAMP(a) (uint32_t)(SPA_CLAMP((a), S24_32_MIN, S24_32_MAX) + U24_32_OFFS) #define S24_ACCUM(a,b) S24_32_ACCUM(a, s24_to_s32(b)) #define S24_CLAMP(a) s32_to_s24(S24_32_CLAMP(a)) #define U24_ACCUM(a,b) U24_32_ACCUM(a, u24_to_u32(b)) #define U24_CLAMP(a) u32_to_u24(U24_32_CLAMP(a)) #define S32_MIN -2147483648 #define S32_MAX 2147483647 #define S32_ACCUM(a,b) ((a) + (int64_t)(b)) #define S32_CLAMP(a) (int32_t)(SPA_CLAMP((a), S32_MIN, S32_MAX)) #define U32_OFFS 2147483648 #define U32_ACCUM(a,b) ((a) + ((int64_t)(b) - U32_OFFS)) #define U32_CLAMP(a) (uint32_t)(SPA_CLAMP((a), S32_MIN, S32_MAX) + U32_OFFS) #define F32_ACCUM(a,b) ((a) + (b)) #define F32_CLAMP(a) (a) #define F64_ACCUM(a,b) ((a) + (b)) #define F64_CLAMP(a) (a) struct mix_ops { uint32_t fmt; uint32_t n_channels; uint32_t cpu_flags; void (*clear) (struct mix_ops *ops, void * SPA_RESTRICT dst, uint32_t n_samples); void (*process) (struct mix_ops *ops, void * SPA_RESTRICT dst, const void * SPA_RESTRICT src[], uint32_t n_src, uint32_t n_samples); void (*free) (struct mix_ops *ops); const void *priv; }; int mix_ops_init(struct mix_ops *ops); #define mix_ops_clear(ops,...) (ops)->clear(ops, __VA_ARGS__) #define mix_ops_process(ops,...) (ops)->process(ops, __VA_ARGS__) #define mix_ops_free(ops) (ops)->free(ops) #define DEFINE_FUNCTION(name,arch) \ void mix_##name##_##arch(struct mix_ops *ops, void * SPA_RESTRICT dst, \ const void * SPA_RESTRICT src[], uint32_t n_src, \ uint32_t n_samples) \ #define MIX_OPS_MAX_ALIGN 32u DEFINE_FUNCTION(s8, c); DEFINE_FUNCTION(u8, c); DEFINE_FUNCTION(s16, c); DEFINE_FUNCTION(u16, c); DEFINE_FUNCTION(s24, c); DEFINE_FUNCTION(u24, c); DEFINE_FUNCTION(s32, c); DEFINE_FUNCTION(u32, c); DEFINE_FUNCTION(s24_32, c); DEFINE_FUNCTION(u24_32, c); DEFINE_FUNCTION(f32, c); DEFINE_FUNCTION(f64, c); #if defined(HAVE_SSE) DEFINE_FUNCTION(f32, sse); #endif #if defined(HAVE_SSE2) DEFINE_FUNCTION(f64, sse2); #endif #if defined(HAVE_AVX) DEFINE_FUNCTION(f32, avx); #endif pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/audiomixer/mixer-dsp.c000066400000000000000000000656111511204443500271750ustar00rootroot00000000000000/* Spa */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "mix-ops.h" #undef SPA_LOG_TOPIC_DEFAULT #define SPA_LOG_TOPIC_DEFAULT &log_topic SPA_LOG_TOPIC_DEFINE_STATIC(log_topic, "spa.mixer-dsp"); #define MAX_BUFFERS 64 #define MAX_DATAS SPA_AUDIO_MAX_CHANNELS #define MAX_PORTS 512 #define MAX_ALIGN MIX_OPS_MAX_ALIGN #define PORT_DEFAULT_VOLUME 1.0 #define PORT_DEFAULT_MUTE false struct port_props { double volume; int32_t mute; }; static void port_props_reset(struct port_props *props) { props->volume = PORT_DEFAULT_VOLUME; props->mute = PORT_DEFAULT_MUTE; } struct buffer { uint32_t id; #define BUFFER_FLAG_QUEUED (1 << 0) #define BUFFER_FLAG_MAPPED (1 << 1) uint32_t flags; struct spa_list link; struct spa_buffer *buffer; struct spa_meta_header *h; struct spa_buffer buf; void *datas[MAX_DATAS]; }; struct port { struct spa_list link; uint32_t direction; uint32_t id; struct port_props props; struct spa_io_buffers *io[2]; uint64_t info_all; struct spa_port_info info; struct spa_param_info params[8]; unsigned int have_format:1; struct buffer buffers[MAX_BUFFERS]; uint32_t n_buffers; struct spa_list queue; size_t queued_bytes; struct spa_list mix_link; bool active:1; }; struct impl { struct spa_handle handle; struct spa_node node; struct spa_log *log; struct spa_cpu *cpu; uint32_t cpu_flags; uint32_t max_align; struct spa_loop *data_loop; uint32_t quantum_limit; struct mix_ops ops; uint64_t info_all; struct spa_node_info info; struct spa_param_info params[8]; struct spa_io_position *position; struct spa_hook_list hooks; struct port *in_ports[MAX_PORTS]; struct port out_ports[1]; struct spa_list port_list; struct spa_list free_list; struct buffer *mix_buffers[MAX_PORTS]; const void *mix_datas[MAX_PORTS]; int n_formats; struct spa_audio_info format; uint32_t stride; unsigned int have_format:1; unsigned int started:1; struct spa_list mix_list; }; #define CHECK_ANY_IN(this,d,p) ((d) == SPA_DIRECTION_INPUT && (p) == SPA_ID_INVALID) #define CHECK_FREE_IN_PORT(this,d,p) ((d) == SPA_DIRECTION_INPUT && (p) < MAX_PORTS && this->in_ports[(p)] == NULL) #define CHECK_IN_PORT(this,d,p) ((d) == SPA_DIRECTION_INPUT && (p) < MAX_PORTS && this->in_ports[(p)] != NULL) #define CHECK_OUT_PORT(this,d,p) ((d) == SPA_DIRECTION_OUTPUT && (p) == 0) #define CHECK_PORT(this,d,p) (CHECK_OUT_PORT(this,d,p) || CHECK_IN_PORT (this,d,p)) #define CHECK_PORT_ANY(this,d,p) (CHECK_ANY_IN(this,d,p) || CHECK_PORT(this,d,p)) #define GET_IN_PORT(this,p) (this->in_ports[p]) #define GET_OUT_PORT(this,p) (&this->out_ports[p]) #define GET_PORT(this,d,p) (d == SPA_DIRECTION_INPUT ? GET_IN_PORT(this,p) : GET_OUT_PORT(this,p)) #define GET_PORT_ANY(this,d,p) (CHECK_ANY_IN(this,d,p) ? NULL : GET_PORT(this,d,p)) static int impl_node_enum_params(void *object, int seq, uint32_t id, uint32_t start, uint32_t num, const struct spa_pod *filter) { return -ENOTSUP; } static int impl_node_set_param(void *object, uint32_t id, uint32_t flags, const struct spa_pod *param) { return -ENOTSUP; } static int impl_node_set_io(void *object, uint32_t id, void *data, size_t size) { struct impl *this = object; switch (id) { case SPA_IO_Position: this->position = data; break; default: return -ENOTSUP; } return 0; } static int impl_node_send_command(void *object, const struct spa_command *command) { struct impl *this = object; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(command != NULL, -EINVAL); switch (SPA_NODE_COMMAND_ID(command)) { case SPA_NODE_COMMAND_Start: this->started = true; break; case SPA_NODE_COMMAND_Pause: this->started = false; break; default: return -ENOTSUP; } return 0; } static void emit_node_info(struct impl *this, bool full) { uint64_t old = full ? this->info.change_mask : 0; if (full) this->info.change_mask = this->info_all; if (this->info.change_mask) { spa_node_emit_info(&this->hooks, &this->info); this->info.change_mask = old; } } static void emit_port_info(struct impl *this, struct port *port, bool full) { uint64_t old = full ? port->info.change_mask : 0; if (full) port->info.change_mask = port->info_all; if (port->info.change_mask) { spa_node_emit_port_info(&this->hooks, port->direction, port->id, &port->info); port->info.change_mask = old; } } static int impl_node_add_listener(void *object, struct spa_hook *listener, const struct spa_node_events *events, void *data) { struct impl *this = object; struct spa_hook_list save; struct port *port; spa_return_val_if_fail(this != NULL, -EINVAL); spa_hook_list_isolate(&this->hooks, &save, listener, events, data); emit_node_info(this, true); emit_port_info(this, GET_OUT_PORT(this, 0), true); spa_list_for_each(port, &this->port_list, link) emit_port_info(this, port, true); spa_hook_list_join(&this->hooks, &save); return 0; } static int impl_node_set_callbacks(void *object, const struct spa_node_callbacks *callbacks, void *user_data) { return 0; } static struct port *get_free_port(struct impl *this) { struct port *port; if (!spa_list_is_empty(&this->free_list)) { port = spa_list_first(&this->free_list, struct port, link); spa_list_remove(&port->link); } else { port = calloc(1, sizeof(struct port)); } return port; } static int impl_node_add_port(void *object, enum spa_direction direction, uint32_t port_id, const struct spa_dict *props) { struct impl *this = object; struct port *port; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(CHECK_FREE_IN_PORT(this, direction, port_id), -EINVAL); if ((port = get_free_port(this)) == NULL) return -errno; port->direction = direction; port->id = port_id; port_props_reset(&port->props); spa_list_init(&port->queue); port->info_all = SPA_PORT_CHANGE_MASK_FLAGS | SPA_PORT_CHANGE_MASK_PARAMS; port->info = SPA_PORT_INFO_INIT(); port->info.flags = SPA_PORT_FLAG_NO_REF | SPA_PORT_FLAG_DYNAMIC_DATA | SPA_PORT_FLAG_REMOVABLE | SPA_PORT_FLAG_OPTIONAL; port->params[0] = SPA_PARAM_INFO(SPA_PARAM_EnumFormat, SPA_PARAM_INFO_READ); port->params[1] = SPA_PARAM_INFO(SPA_PARAM_Meta, SPA_PARAM_INFO_READ); port->params[2] = SPA_PARAM_INFO(SPA_PARAM_IO, SPA_PARAM_INFO_READ); port->params[3] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); port->params[4] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); port->info.params = port->params; port->info.n_params = 5; this->in_ports[port_id] = port; spa_list_append(&this->port_list, &port->link); spa_log_debug(this->log, "%p: add port %d:%d", this, direction, port_id); emit_port_info(this, port, true); return 0; } static int impl_node_remove_port(void *object, enum spa_direction direction, uint32_t port_id) { struct impl *this = object; struct port *port; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(CHECK_IN_PORT(this, direction, port_id), -EINVAL); port = GET_IN_PORT (this, port_id); this->in_ports[port_id] = NULL; spa_list_remove(&port->link); if (port->have_format && this->have_format) { if (--this->n_formats == 0) this->have_format = false; } spa_memzero(port, sizeof(struct port)); spa_list_append(&this->free_list, &port->link); spa_log_debug(this->log, "%p: remove port %d:%d", this, direction, port_id); spa_node_emit_port_info(&this->hooks, direction, port_id, NULL); return 0; } static int port_enum_formats(void *object, struct port *port, uint32_t index, struct spa_pod **param, struct spa_pod_builder *builder) { struct impl *this = object; switch (index) { case 0: if (this->have_format) { *param = spa_format_audio_dsp_build(builder, SPA_PARAM_EnumFormat, &this->format.info.dsp); } else { *param = spa_pod_builder_add_object(builder, SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_audio), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_dsp), SPA_FORMAT_AUDIO_format, SPA_POD_Id(SPA_AUDIO_FORMAT_DSP_F32)); } break; default: return 0; } return 1; } static int impl_node_port_enum_params(void *object, int seq, enum spa_direction direction, uint32_t port_id, uint32_t id, uint32_t start, uint32_t num, const struct spa_pod *filter) { struct impl *this = object; struct port *port; struct spa_pod *param; struct spa_pod_builder b = { 0 }; uint8_t buffer[1024]; struct spa_result_node_params result; uint32_t count = 0; int res; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(num != 0, -EINVAL); spa_return_val_if_fail(CHECK_PORT_ANY(this, direction, port_id), -EINVAL); port = GET_PORT_ANY(this, direction, port_id); result.id = id; result.next = start; next: result.index = result.next++; spa_pod_builder_init(&b, buffer, sizeof(buffer)); switch (id) { case SPA_PARAM_EnumFormat: if ((res = port_enum_formats(this, port, result.index, ¶m, &b)) <= 0) return res; break; case SPA_PARAM_Format: if (port == NULL || !port->have_format) return -EIO; if (result.index > 0) return 0; param = spa_format_audio_dsp_build(&b, id, &this->format.info.dsp); break; case SPA_PARAM_Buffers: if (port == NULL || !port->have_format) return -EIO; if (result.index > 0) return 0; param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamBuffers, id, SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(1, 1, MAX_BUFFERS), SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(1), SPA_PARAM_BUFFERS_size, SPA_POD_CHOICE_RANGE_Int( this->quantum_limit * this->stride, 16 * this->stride, INT32_MAX), SPA_PARAM_BUFFERS_stride, SPA_POD_Int(this->stride)); break; case SPA_PARAM_Meta: switch (result.index) { case 0: param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamMeta, id, SPA_PARAM_META_type, SPA_POD_Id(SPA_META_Header), SPA_PARAM_META_size, SPA_POD_Int(sizeof(struct spa_meta_header))); break; default: return 0; } break; case SPA_PARAM_IO: switch (result.index) { case 0: param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamIO, id, SPA_PARAM_IO_id, SPA_POD_Id(SPA_IO_Buffers), SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_buffers))); break; case 1: param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamIO, id, SPA_PARAM_IO_id, SPA_POD_Id(SPA_IO_AsyncBuffers), SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_async_buffers))); break; default: return 0; } break; default: return -ENOENT; } if (spa_pod_filter(&b, &result.param, param, filter) < 0) goto next; spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); if (++count != num) goto next; return 0; } static int clear_buffers(struct impl *this, struct port *port) { uint32_t i, j; spa_log_debug(this->log, "%p: clear buffers %p %d", this, port, port->n_buffers); for (i = 0; i < port->n_buffers; i++) { struct buffer *b = &port->buffers[i]; if (SPA_FLAG_IS_SET(b->flags, BUFFER_FLAG_MAPPED)) { for (j = 0; j < b->buffer->n_datas; j++) { if (b->datas[j]) { spa_log_debug(this->log, "%p: unmap buffer %d data %d %p", this, i, j, b->datas[j]); munmap(b->datas[j], b->buffer->datas[j].maxsize); b->datas[j] = NULL; } } SPA_FLAG_CLEAR(b->flags, BUFFER_FLAG_MAPPED); } } port->n_buffers = 0; spa_list_init(&port->queue); return 0; } static int queue_buffer(struct impl *this, struct port *port, struct buffer *b) { if (SPA_FLAG_IS_SET(b->flags, BUFFER_FLAG_QUEUED)) return -EINVAL; spa_list_append(&port->queue, &b->link); SPA_FLAG_SET(b->flags, BUFFER_FLAG_QUEUED); spa_log_trace_fp(this->log, "%p: queue buffer %d", this, b->id); return 0; } static struct buffer *dequeue_buffer(struct impl *this, struct port *port) { struct buffer *b; if (spa_list_is_empty(&port->queue)) return NULL; b = spa_list_first(&port->queue, struct buffer, link); spa_list_remove(&b->link); SPA_FLAG_CLEAR(b->flags, BUFFER_FLAG_QUEUED); spa_log_trace_fp(this->log, "%p: dequeue buffer %d", this, b->id); return b; } static int port_set_format(void *object, enum spa_direction direction, uint32_t port_id, uint32_t flags, const struct spa_pod *format) { struct impl *this = object; struct port *port; int res; port = GET_PORT(this, direction, port_id); spa_return_val_if_fail(!this->started || port->io == NULL, -EIO); if (format == NULL) { if (port->have_format) { port->have_format = false; if (--this->n_formats == 0) this->have_format = false; clear_buffers(this, port); } } else { struct spa_audio_info info = { 0 }; if ((res = spa_format_parse(format, &info.media_type, &info.media_subtype)) < 0) return res; if (info.media_type != SPA_MEDIA_TYPE_audio || info.media_subtype != SPA_MEDIA_SUBTYPE_dsp) return -EINVAL; if (spa_format_audio_dsp_parse(format, &info.info.dsp) < 0) return -EINVAL; if (info.info.dsp.format != SPA_AUDIO_FORMAT_DSP_F32) return -EINVAL; if (!this->have_format) { this->ops.fmt = info.info.dsp.format; this->ops.n_channels = 1; this->ops.cpu_flags = this->cpu_flags; if ((res = mix_ops_init(&this->ops)) < 0) return res; this->stride = sizeof(float); this->have_format = true; this->format = info; } if (!port->have_format) { this->n_formats++; port->have_format = true; spa_log_debug(this->log, "%p: set format on port %d:%d", this, direction, port_id); } } port->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; if (port->have_format) { port->params[3] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_READWRITE); port->params[4] = SPA_PARAM_INFO(SPA_PARAM_Buffers, SPA_PARAM_INFO_READ); } else { port->params[3] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); port->params[4] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); } emit_port_info(this, port, false); return 0; } static int impl_node_port_set_param(void *object, enum spa_direction direction, uint32_t port_id, uint32_t id, uint32_t flags, const struct spa_pod *param) { struct impl *this = object; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); if (id == SPA_PARAM_Format) { return port_set_format(this, direction, port_id, flags, param); } else return -ENOENT; } static int impl_node_port_use_buffers(void *object, enum spa_direction direction, uint32_t port_id, uint32_t flags, struct spa_buffer **buffers, uint32_t n_buffers) { struct impl *this = object; struct port *port; uint32_t i, j; int res; spa_return_val_if_fail(this != NULL, -EINVAL); spa_log_debug(this->log, "%p: use %d buffers on port %d:%d", this, n_buffers, direction, port_id); spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); port = GET_PORT(this, direction, port_id); spa_return_val_if_fail(!this->started || port->io == NULL, -EIO); if (n_buffers > 0 && !port->have_format) { res = -EIO; goto error; } if (n_buffers > MAX_BUFFERS) { res = -ENOSPC; goto error; } clear_buffers(this, port); for (i = 0; i < n_buffers; i++) { struct buffer *b; uint32_t n_datas = buffers[i]->n_datas; struct spa_data *d = buffers[i]->datas; if (n_datas > MAX_DATAS) { res = -ENOSPC; goto error; } b = &port->buffers[i]; b->buffer = buffers[i]; b->flags = 0; b->id = i; b->h = spa_buffer_find_meta_data(buffers[i], SPA_META_Header, sizeof(*b->h)); b->buf = *buffers[i]; for (j = 0; j < n_datas; j++) { void *data = d[j].data; if (data == NULL && SPA_FLAG_IS_SET(d[j].flags, SPA_DATA_FLAG_MAPPABLE)) { int prot = 0; if (SPA_FLAG_IS_SET(d[j].flags, SPA_DATA_FLAG_READABLE)) prot |= PROT_READ; if (SPA_FLAG_IS_SET(d[j].flags, SPA_DATA_FLAG_WRITABLE)) prot |= PROT_WRITE; data = mmap(NULL, d[j].maxsize, prot, MAP_SHARED, d[j].fd, d[j].mapoffset); if (data == MAP_FAILED) { spa_log_error(this->log, "%p: mmap failed %d on buffer %d %d %p: %m", this, j, i, d[j].type, data); res = -EINVAL; goto error; } SPA_FLAG_SET(b->flags, BUFFER_FLAG_MAPPED); spa_log_debug(this->log, "%p: mmap %d on buffer %d %d %p %p", this, j, i, d[j].type, data, b); } if (data == NULL) { spa_log_error(this->log, "%p: invalid memory %d on buffer %d %d %p", this, j, i, d[j].type, data); res = -EINVAL; goto error; } else if (!SPA_IS_ALIGNED(data, this->max_align)) { spa_log_warn(this->log, "%p: memory %d on buffer %d not aligned", this, j, i); } d[j].data = b->datas[j] = data; } if (direction == SPA_DIRECTION_OUTPUT) queue_buffer(this, port, b); port->n_buffers++; spa_log_debug(this->log, "%p: port %d:%d buffer:%d n_data:%d data:%p maxsize:%d", this, direction, port_id, i, buffers[i]->n_datas, d[0].data, d[0].maxsize); } return 0; error: clear_buffers(this, port); return res; } struct io_info { struct impl *impl; struct port *port; void *data; size_t size; }; static int do_port_set_io(struct spa_loop *loop, bool async, uint32_t seq, const void *data, size_t size, void *user_data) { struct io_info *info = user_data; struct port *port = info->port; if (info->data == NULL || info->size < sizeof(struct spa_io_buffers)) { port->io[0] = NULL; port->io[1] = NULL; if (port->active) { spa_list_remove(&port->mix_link); port->active = false; } } else { if (info->size >= sizeof(struct spa_io_async_buffers)) { struct spa_io_async_buffers *ab = info->data; port->io[0] = &ab->buffers[port->direction]; port->io[1] = &ab->buffers[port->direction^1]; } else { port->io[0] = info->data; port->io[1] = info->data; } if (!port->active) { spa_list_append(&info->impl->mix_list, &port->mix_link); port->active = true; } } return 0; } static int impl_node_port_set_io(void *object, enum spa_direction direction, uint32_t port_id, uint32_t id, void *data, size_t size) { struct impl *this = object; struct port *port; struct io_info info; spa_return_val_if_fail(this != NULL, -EINVAL); spa_log_debug(this->log, "%p: port %d:%d io %d %p/%zd", this, direction, port_id, id, data, size); spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); port = GET_PORT(this, direction, port_id); info.impl = this; info.port = port; info.data = data; info.size = size; switch (id) { case SPA_IO_Buffers: case SPA_IO_AsyncBuffers: spa_loop_locked(this->data_loop, do_port_set_io, SPA_ID_INVALID, NULL, 0, &info); break; default: return -ENOENT; } return 0; } static int impl_node_port_reuse_buffer(void *object, uint32_t port_id, uint32_t buffer_id) { struct impl *this = object; struct port *port; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(CHECK_PORT(this, SPA_DIRECTION_OUTPUT, port_id), -EINVAL); port = GET_OUT_PORT(this, 0); if (buffer_id >= port->n_buffers) return -EINVAL; return queue_buffer(this, port, &port->buffers[buffer_id]); } static int impl_node_process(void *object) { struct impl *this = object; struct port *outport, *inport; struct spa_io_buffers *outio; uint32_t n_buffers, maxsize; struct buffer **buffers; struct buffer *outb; const void **datas; uint32_t cycle = this->position->clock.cycle & 1; struct spa_data *d; spa_return_val_if_fail(this != NULL, -EINVAL); outport = GET_OUT_PORT(this, 0); if ((outio = outport->io[cycle]) == NULL) return -EIO; spa_log_trace_fp(this->log, "%p: status %p %d %d", this, outio, outio->status, outio->buffer_id); if (SPA_UNLIKELY(outio->status == SPA_STATUS_HAVE_DATA)) return outio->status; /* recycle */ if (SPA_LIKELY(outio->buffer_id < outport->n_buffers)) { queue_buffer(this, outport, &outport->buffers[outio->buffer_id]); outio->buffer_id = SPA_ID_INVALID; } buffers = this->mix_buffers; datas = this->mix_datas; n_buffers = 0; maxsize = UINT32_MAX; spa_list_for_each(inport, &this->mix_list, mix_link) { struct spa_io_buffers *inio = inport->io[cycle]; struct buffer *inb; struct spa_data *bd; uint32_t size, offs; if (inio->buffer_id >= inport->n_buffers || inio->status != SPA_STATUS_HAVE_DATA) { spa_log_trace_fp(this->log, "%p: skip input idx:%d " "io:%p status:%d buf_id:%d n_buffers:%d", this, inport->id, inio, inio->status, inio->buffer_id, inport->n_buffers); continue; } inb = &inport->buffers[inio->buffer_id]; bd = &inb->buffer->datas[0]; offs = SPA_MIN(bd->chunk->offset, bd->maxsize); size = SPA_MIN(bd->maxsize - offs, bd->chunk->size); maxsize = SPA_MIN(maxsize, size); spa_log_trace_fp(this->log, "%p: mix input %d %p->%p %d %d/%d %d:%d/%d %u", this, inport->id, inio, outio, inio->status, inio->buffer_id, inport->n_buffers, offs, size, (int)sizeof(float), bd->chunk->flags); if (!SPA_FLAG_IS_SET(bd->chunk->flags, SPA_CHUNK_FLAG_EMPTY)) { datas[n_buffers] = SPA_PTROFF(bd->data, offs, void); buffers[n_buffers++] = inb; } inio->status = SPA_STATUS_NEED_DATA; } outb = dequeue_buffer(this, outport); if (SPA_UNLIKELY(outb == NULL)) { if (outport->n_buffers > 0) spa_log_warn(this->log, "%p: out of buffers (%d)", this, outport->n_buffers); return -EPIPE; } d = outb->buf.datas; if (n_buffers == 1 && SPA_FLAG_IS_SET(d[0].flags, SPA_DATA_FLAG_DYNAMIC)) { spa_log_trace_fp(this->log, "%p: %d passthrough", this, n_buffers); *outb->buffer = *buffers[0]->buffer; } else { *outb->buffer = outb->buf; maxsize = SPA_MIN(maxsize, d[0].maxsize); d[0].chunk->offset = 0; d[0].chunk->size = maxsize; d[0].chunk->stride = sizeof(float); SPA_FLAG_UPDATE(d[0].chunk->flags, SPA_CHUNK_FLAG_EMPTY, n_buffers == 0); spa_log_trace_fp(this->log, "%p: %d mix %d", this, n_buffers, maxsize); mix_ops_process(&this->ops, d[0].data, datas, n_buffers, maxsize / sizeof(float)); } outio->buffer_id = outb->id; outio->status = SPA_STATUS_HAVE_DATA; return SPA_STATUS_HAVE_DATA | SPA_STATUS_NEED_DATA; } static const struct spa_node_methods impl_node = { SPA_VERSION_NODE_METHODS, .add_listener = impl_node_add_listener, .set_callbacks = impl_node_set_callbacks, .enum_params = impl_node_enum_params, .set_param = impl_node_set_param, .set_io = impl_node_set_io, .send_command = impl_node_send_command, .add_port = impl_node_add_port, .remove_port = impl_node_remove_port, .port_enum_params = impl_node_port_enum_params, .port_set_param = impl_node_port_set_param, .port_use_buffers = impl_node_port_use_buffers, .port_set_io = impl_node_port_set_io, .port_reuse_buffer = impl_node_port_reuse_buffer, .process = impl_node_process, }; static int impl_get_interface(struct spa_handle *handle, const char *type, void **interface) { struct impl *this; spa_return_val_if_fail(handle != NULL, -EINVAL); spa_return_val_if_fail(interface != NULL, -EINVAL); this = (struct impl *) handle; if (spa_streq(type, SPA_TYPE_INTERFACE_Node)) *interface = &this->node; else return -ENOENT; return 0; } static int impl_clear(struct spa_handle *handle) { struct impl *this; struct port *port; spa_return_val_if_fail(handle != NULL, -EINVAL); this = (struct impl *) handle; spa_list_insert_list(&this->free_list, &this->port_list); spa_list_consume(port, &this->free_list, link) { spa_list_remove(&port->link); free(port); } return 0; } static size_t impl_get_size(const struct spa_handle_factory *factory, const struct spa_dict *params) { return sizeof(struct impl); } static int impl_init(const struct spa_handle_factory *factory, struct spa_handle *handle, const struct spa_dict *info, const struct spa_support *support, uint32_t n_support) { struct impl *this; struct port *port; uint32_t i; spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(handle != NULL, -EINVAL); handle->get_interface = impl_get_interface; handle->clear = impl_clear; this = (struct impl *) handle; this->log = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log); spa_log_topic_init(this->log, &log_topic); this->data_loop = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_DataLoop); if (this->data_loop == NULL) { spa_log_error(this->log, "a data loop is needed"); return -EINVAL; } this->cpu = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_CPU); if (this->cpu) { this->cpu_flags = spa_cpu_get_flags(this->cpu); this->max_align = SPA_MIN(MAX_ALIGN, spa_cpu_get_max_align(this->cpu)); } for (i = 0; info && i < info->n_items; i++) { const char *k = info->items[i].key; const char *s = info->items[i].value; if (spa_streq(k, "clock.quantum-limit")) spa_atou32(s, &this->quantum_limit, 0); } spa_hook_list_init(&this->hooks); spa_list_init(&this->port_list); spa_list_init(&this->free_list); spa_list_init(&this->mix_list); this->node.iface = SPA_INTERFACE_INIT( SPA_TYPE_INTERFACE_Node, SPA_VERSION_NODE, &impl_node, this); this->info = SPA_NODE_INFO_INIT(); this->info.max_input_ports = MAX_PORTS; this->info.max_output_ports = 1; this->info.change_mask |= SPA_NODE_CHANGE_MASK_FLAGS; this->info.flags = SPA_NODE_FLAG_RT | SPA_NODE_FLAG_IN_DYNAMIC_PORTS; this->info_all = this->info.change_mask; port = GET_OUT_PORT(this, 0); port->direction = SPA_DIRECTION_OUTPUT; port->id = 0; port->info = SPA_PORT_INFO_INIT(); port->info.change_mask |= SPA_PORT_CHANGE_MASK_FLAGS; port->info.flags = SPA_PORT_FLAG_DYNAMIC_DATA; port->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; port->info_all = port->info.change_mask; port->params[0] = SPA_PARAM_INFO(SPA_PARAM_EnumFormat, SPA_PARAM_INFO_READ); port->params[1] = SPA_PARAM_INFO(SPA_PARAM_Meta, SPA_PARAM_INFO_READ); port->params[2] = SPA_PARAM_INFO(SPA_PARAM_IO, SPA_PARAM_INFO_READ); port->params[3] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); port->params[4] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); port->info.params = port->params; port->info.n_params = 5; spa_list_init(&port->queue); return 0; } static const struct spa_interface_info impl_interfaces[] = { {SPA_TYPE_INTERFACE_Node,}, }; static int impl_enum_interface_info(const struct spa_handle_factory *factory, const struct spa_interface_info **info, uint32_t *index) { spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(info != NULL, -EINVAL); spa_return_val_if_fail(index != NULL, -EINVAL); switch (*index) { case 0: *info = &impl_interfaces[*index]; break; default: return 0; } (*index)++; return 1; } const struct spa_handle_factory spa_mixer_dsp_factory = { SPA_VERSION_HANDLE_FACTORY, SPA_NAME_AUDIO_MIXER_DSP, NULL, impl_get_size, impl_init, impl_enum_interface_info, }; pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/audiomixer/plugin.c000066400000000000000000000013621511204443500265540ustar00rootroot00000000000000/* Spa Audiomixer plugin */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include extern const struct spa_handle_factory spa_audiomixer_factory; extern const struct spa_handle_factory spa_mixer_dsp_factory; SPA_LOG_TOPIC_ENUM_DEFINE_REGISTERED; SPA_EXPORT int spa_handle_factory_enum(const struct spa_handle_factory **factory, uint32_t *index) { spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(index != NULL, -EINVAL); switch (*index) { case 0: *factory = &spa_audiomixer_factory; break; case 1: *factory = &spa_mixer_dsp_factory; break; default: return 0; } (*index)++; return 1; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/audiomixer/test-mix-ops.c000066400000000000000000000253711511204443500276350ustar00rootroot00000000000000/* Spa */ /* SPDX-FileCopyrightText: Copyright © 2022 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include "config.h" #include #include #include #include #include #include #include #include "test-helper.h" #include "mix-ops.c" static uint32_t cpu_flags; #define N_SAMPLES 1024 static uint8_t samp_out[N_SAMPLES * 8]; static void compare_mem(int i, int j, const void *m1, const void *m2, size_t size) { int res = memcmp(m1, m2, size); if (res != 0) { fprintf(stderr, "%d %d %zd:\n", i, j, size); spa_debug_mem(0, m1, size); spa_debug_mem(0, m2, size); } spa_assert_se(res == 0); } static int run_test(const char *name, const void *src[], uint32_t n_src, const void *dst, size_t dst_size, uint32_t n_samples, mix_func_t mix) { struct mix_ops ops; ops.fmt = SPA_AUDIO_FORMAT_F32; ops.n_channels = 1; ops.cpu_flags = cpu_flags; mix_ops_init(&ops); fprintf(stderr, "%s\n", name); mix(&ops, (void *)samp_out, src, n_src, n_samples); compare_mem(0, 0, samp_out, dst, dst_size); return 0; } static void test_s8(void) { int8_t out[] = { 0x00, 0x00, 0x00, 0x00 }; int8_t in_1[] = { 0x00, 0x00, 0x00, 0x00 }; int8_t in_2[] = { 0x7f, 0x80, 0x40, 0xc0 }; int8_t in_3[] = { 0x40, 0xc0, 0xc0, 0x40 }; int8_t in_4[] = { 0xc0, 0x40, 0x40, 0xc0 }; int8_t out_4[] = { 0x7f, 0x80, 0x40, 0xc0 }; const void *src[6] = { in_1, in_2, in_3, in_4 }; run_test("test_s8_0", NULL, 0, out, sizeof(out), SPA_N_ELEMENTS(out), mix_s8_c); run_test("test_s8_1", src, 1, in_1, sizeof(in_1), SPA_N_ELEMENTS(in_1), mix_s8_c); run_test("test_s8_4", src, 4, out_4, sizeof(out_4), SPA_N_ELEMENTS(out_4), mix_s8_c); } static void test_u8(void) { uint8_t out[] = { 0x80, 0x80, 0x80, 0x80 }; uint8_t in_1[] = { 0x80, 0x80, 0x80, 0x80 }; uint8_t in_2[] = { 0xff, 0x00, 0xc0, 0x40 }; uint8_t in_3[] = { 0xc0, 0x40, 0x40, 0xc0 }; uint8_t in_4[] = { 0x40, 0xc0, 0xc0, 0x40 }; uint8_t out_4[] = { 0xff, 0x00, 0xc0, 0x40 }; const void *src[6] = { in_1, in_2, in_3, in_4 }; run_test("test_u8_0", NULL, 0, out, sizeof(out), SPA_N_ELEMENTS(out), mix_u8_c); run_test("test_u8_1", src, 1, in_1, sizeof(in_1), SPA_N_ELEMENTS(in_1), mix_u8_c); run_test("test_u8_4", src, 4, out_4, sizeof(out_4), SPA_N_ELEMENTS(out_4), mix_u8_c); } static void test_s16(void) { int16_t out[] = { 0x0000, 0x0000, 0x0000, 0x0000 }; int16_t in_1[] = { 0x0000, 0x0000, 0x0000, 0x0000 }; int16_t in_2[] = { 0x7fff, 0x8000, 0x4000, 0xc000 }; int16_t in_3[] = { 0x4000, 0xc000, 0xc000, 0x4000 }; int16_t in_4[] = { 0xc000, 0x4000, 0x4000, 0xc000 }; int16_t out_4[] = { 0x7fff, 0x8000, 0x4000, 0xc000 }; const void *src[6] = { in_1, in_2, in_3, in_4 }; run_test("test_s16_0", NULL, 0, out, sizeof(out), SPA_N_ELEMENTS(out), mix_s16_c); run_test("test_s16_1", src, 1, in_1, sizeof(in_1), SPA_N_ELEMENTS(in_1), mix_s16_c); run_test("test_s16_4", src, 4, out_4, sizeof(out_4), SPA_N_ELEMENTS(out_4), mix_s16_c); } static void test_u16(void) { uint16_t out[] = { 0x8000, 0x8000, 0x8000, 0x8000 }; uint16_t in_1[] = { 0x8000, 0x8000, 0x8000 , 0x8000}; uint16_t in_2[] = { 0xffff, 0x0000, 0xc000, 0x4000 }; uint16_t in_3[] = { 0xc000, 0x4000, 0x4000, 0xc000 }; uint16_t in_4[] = { 0x4000, 0xc000, 0xc000, 0x4000 }; uint16_t out_4[] = { 0xffff, 0x0000, 0xc000, 0x4000 }; const void *src[6] = { in_1, in_2, in_3, in_4 }; run_test("test_u16_0", NULL, 0, out, sizeof(out), SPA_N_ELEMENTS(out), mix_u16_c); run_test("test_u16_1", src, 1, in_1, sizeof(in_1), SPA_N_ELEMENTS(in_1), mix_u16_c); run_test("test_u16_4", src, 4, out_4, sizeof(out_4), SPA_N_ELEMENTS(out_4), mix_u16_c); } static void test_s24(void) { int24_t out[] = { S32_TO_S24(0x000000), S32_TO_S24(0x000000), S32_TO_S24(0x000000) }; int24_t in_1[] = { S32_TO_S24(0x000000), S32_TO_S24(0x000000), S32_TO_S24(0x000000) }; int24_t in_2[] = { S32_TO_S24(0x7fffff), S32_TO_S24(0xff800000), S32_TO_S24(0x400000) }; int24_t in_3[] = { S32_TO_S24(0x400000), S32_TO_S24(0xffc00000), S32_TO_S24(0xffc00000) }; int24_t in_4[] = { S32_TO_S24(0xffc00000), S32_TO_S24(0x400000), S32_TO_S24(0x400000) }; int24_t out_4[] = { S32_TO_S24(0x7fffff), S32_TO_S24(0xff800000), S32_TO_S24(0x400000) }; const void *src[6] = { in_1, in_2, in_3, in_4 }; run_test("test_s24_0", NULL, 0, out, sizeof(out), SPA_N_ELEMENTS(out), mix_s24_c); run_test("test_s24_1", src, 1, in_1, sizeof(in_1), SPA_N_ELEMENTS(in_1), mix_s24_c); run_test("test_s24_4", src, 4, out_4, sizeof(out_4), SPA_N_ELEMENTS(out_4), mix_s24_c); } static void test_u24(void) { uint24_t out[] = { U32_TO_U24(0x800000), U32_TO_U24(0x800000), U32_TO_U24(0x800000) }; uint24_t in_1[] = { U32_TO_U24(0x800000), U32_TO_U24(0x800000), U32_TO_U24(0x800000) }; uint24_t in_2[] = { U32_TO_U24(0xffffffff), U32_TO_U24(0x000000), U32_TO_U24(0xffc00000) }; uint24_t in_3[] = { U32_TO_U24(0xffc00000), U32_TO_U24(0x400000), U32_TO_U24(0x400000) }; uint24_t in_4[] = { U32_TO_U24(0x400000), U32_TO_U24(0xffc00000), U32_TO_U24(0xffc00000) }; uint24_t out_4[] = { U32_TO_U24(0xffffffff), U32_TO_U24(0x000000), U32_TO_U24(0xffc00000) }; const void *src[6] = { in_1, in_2, in_3, in_4 }; run_test("test_u24_0", NULL, 0, out, sizeof(out), SPA_N_ELEMENTS(out), mix_u24_c); run_test("test_u24_1", src, 1, in_1, sizeof(in_1), SPA_N_ELEMENTS(in_1), mix_u24_c); run_test("test_u24_4", src, 4, out_4, sizeof(out_4), SPA_N_ELEMENTS(out_4), mix_u24_c); } static void test_s32(void) { int32_t out[] = { 0x00000000, 0x00000000, 0x00000000, 0x00000000 }; int32_t in_1[] = { 0x00000000, 0x00000000, 0x00000000, 0x00000000 }; int32_t in_2[] = { 0x7fffffff, 0x80000000, 0x40000000, 0xc0000000 }; int32_t in_3[] = { 0x40000000, 0xc0000000, 0xc0000000, 0x40000000 }; int32_t in_4[] = { 0xc0000000, 0x40000000, 0x40000000, 0xc0000000 }; int32_t out_4[] = { 0x7fffffff, 0x80000000, 0x40000000, 0xc0000000 }; const void *src[6] = { in_1, in_2, in_3, in_4 }; run_test("test_s32_0", NULL, 0, out, sizeof(out), SPA_N_ELEMENTS(out), mix_s32_c); run_test("test_s32_1", src, 1, in_1, sizeof(in_1), SPA_N_ELEMENTS(in_1), mix_s32_c); run_test("test_s32_4", src, 4, out_4, sizeof(out_4), SPA_N_ELEMENTS(out_4), mix_s32_c); } static void test_u32(void) { uint32_t out[] = { 0x80000000, 0x80000000, 0x80000000, 0x80000000 }; uint32_t in_1[] = { 0x80000000, 0x80000000, 0x80000000, 0x80000000 }; uint32_t in_2[] = { 0xffffffff, 0x00000000, 0xc0000000, 0x40000000 }; uint32_t in_3[] = { 0xc0000000, 0x40000000, 0x40000000, 0xc0000000 }; uint32_t in_4[] = { 0x40000000, 0xc0000000, 0xc0000000, 0x40000000 }; uint32_t out_4[] = { 0xffffffff, 0x00000000, 0xc0000000, 0x40000000 }; const void *src[6] = { in_1, in_2, in_3, in_4 }; run_test("test_u32_0", NULL, 0, out, sizeof(out), SPA_N_ELEMENTS(out), mix_u32_c); run_test("test_u32_1", src, 1, in_1, sizeof(in_1), SPA_N_ELEMENTS(in_1), mix_u32_c); run_test("test_u32_4", src, 4, out_4, sizeof(out_4), SPA_N_ELEMENTS(out_4), mix_u32_c); } static void test_s24_32(void) { int32_t out[] = { 0x000000, 0x000000, 0x000000, 0x000000 }; int32_t in_1[] = { 0x000000, 0x000000, 0x000000, 0x000000 }; int32_t in_2[] = { 0x7fffff, 0xff800000, 0x400000, 0xffc00000 }; int32_t in_3[] = { 0x400000, 0xffc00000, 0xffc00000, 0x400000 }; int32_t in_4[] = { 0xffc00000, 0x400000, 0x400000, 0xffc00000 }; int32_t out_4[] = { 0x7fffff, 0xff800000, 0x400000, 0xffc00000 }; const void *src[6] = { in_1, in_2, in_3, in_4 }; run_test("test_s24_32_0", NULL, 0, out, sizeof(out), SPA_N_ELEMENTS(out), mix_s24_32_c); run_test("test_s24_32_1", src, 1, in_1, sizeof(in_1), SPA_N_ELEMENTS(in_1), mix_s24_32_c); run_test("test_s24_32_4", src, 4, out_4, sizeof(out_4), SPA_N_ELEMENTS(out_4), mix_s24_32_c); } static void test_u24_32(void) { uint32_t out[] = { 0x800000, 0x800000, 0x800000, 0x800000 }; uint32_t in_1[] = { 0x800000, 0x800000, 0x800000, 0x800000 }; uint32_t in_2[] = { 0xffffff, 0x000000, 0xc00000, 0x400000 }; uint32_t in_3[] = { 0xc00000, 0x400000, 0x400000, 0xc00000 }; uint32_t in_4[] = { 0x400000, 0xc00000, 0xc00000, 0x400000 }; uint32_t out_4[] = { 0xffffff, 0x000000, 0xc00000, 0x400000 }; const void *src[6] = { in_1, in_2, in_3, in_4 }; run_test("test_u24_32_0", NULL, 0, out, sizeof(out), SPA_N_ELEMENTS(out), mix_u24_32_c); run_test("test_u24_32_1", src, 1, in_1, sizeof(in_1), SPA_N_ELEMENTS(in_1), mix_u24_32_c); run_test("test_u24_32_4", src, 4, out_4, sizeof(out_4), SPA_N_ELEMENTS(out_4), mix_u24_32_c); } static void test_f32(void) { float out[] = { 0.0f, 0.0f, 0.0f, 0.0f }; float in_1[] = { 0.0f, 0.0f, 0.0f, 0.0f }; float in_2[] = { 1.0f, -1.0f, 0.5f, -0.5f }; float in_3[] = { 0.5f, -0.5f, -0.5f, 0.5f }; float in_4[] = { -0.5f, 1.0f, 0.5f, -0.5f }; float out_4[] = { 1.0f, -0.5f, 0.5f, -0.5f }; const void *src[6] = { in_1, in_2, in_3, in_4 }; run_test("test_f32_0", NULL, 0, out, sizeof(out), SPA_N_ELEMENTS(out), mix_f32_c); run_test("test_f32_1", src, 1, in_1, sizeof(in_1), SPA_N_ELEMENTS(in_1), mix_f32_c); run_test("test_f32_4", src, 4, out_4, sizeof(out_4), SPA_N_ELEMENTS(out_4), mix_f32_c); #if defined(HAVE_SSE) if (cpu_flags & SPA_CPU_FLAG_SSE) { run_test("test_f32_0_sse", NULL, 0, out, sizeof(out), SPA_N_ELEMENTS(out), mix_f32_sse); run_test("test_f32_1_sse", src, 1, in_1, sizeof(in_1), SPA_N_ELEMENTS(in_1), mix_f32_sse); run_test("test_f32_4_sse", src, 4, out_4, sizeof(out_4), SPA_N_ELEMENTS(out_4), mix_f32_sse); } #endif #if defined(HAVE_AVX) if (cpu_flags & SPA_CPU_FLAG_AVX) { run_test("test_f32_0_avx", NULL, 0, out, sizeof(out), SPA_N_ELEMENTS(out), mix_f32_avx); run_test("test_f32_1_avx", src, 1, in_1, sizeof(in_1), SPA_N_ELEMENTS(in_1), mix_f32_avx); run_test("test_f32_4_avx", src, 4, out_4, sizeof(out_4), SPA_N_ELEMENTS(out_4), mix_f32_avx); } #endif } static void test_f64(void) { double out[] = { 0.0, 0.0, 0.0, 0.0 }; double in_1[] = { 0.0, 0.0, 0.0, 0.0 }; double in_2[] = { 1.0, -1.0, 0.5, -0.5 }; double in_3[] = { 0.5, -0.5, -0.5, 0.5 }; double in_4[] = { -0.5, 1.0, 0.5, -0.5 }; double out_4[] = { 1.0, -0.5, 0.5, -0.5 }; const void *src[6] = { in_1, in_2, in_3, in_4 }; run_test("test_f64_0", NULL, 0, out, sizeof(out), SPA_N_ELEMENTS(out), mix_f64_c); run_test("test_f64_1", src, 1, in_1, sizeof(in_1), SPA_N_ELEMENTS(in_1), mix_f64_c); run_test("test_f64_4", src, 4, out_4, sizeof(out_4), SPA_N_ELEMENTS(out_4), mix_f64_c); #if defined(HAVE_SSE2) if (cpu_flags & SPA_CPU_FLAG_SSE2) { run_test("test_f64_0_sse2", NULL, 0, out, sizeof(out), SPA_N_ELEMENTS(out), mix_f64_sse2); run_test("test_f64_1_sse2", src, 1, in_1, sizeof(in_1), SPA_N_ELEMENTS(in_1), mix_f64_sse2); run_test("test_f64_4_sse2", src, 4, out_4, sizeof(out_4), SPA_N_ELEMENTS(out_4), mix_f64_sse2); } #endif } int main(int argc, char *argv[]) { cpu_flags = get_cpu_flags(); printf("got get CPU flags %d\n", cpu_flags); test_s8(); test_u8(); test_s16(); test_u16(); test_s24(); test_u24(); test_s32(); test_u32(); test_s24_32(); test_u24_32(); test_f32(); test_f64(); return 0; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/audiotestsrc/000077500000000000000000000000001511204443500254535ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/audiotestsrc/audiotestsrc.c000066400000000000000000000702701511204443500303360ustar00rootroot00000000000000/* Spa */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #undef SPA_LOG_TOPIC_DEFAULT #define SPA_LOG_TOPIC_DEFAULT &log_topic SPA_LOG_TOPIC_DEFINE_STATIC(log_topic, "spa.audiotestsrc"); #define SAMPLES_TO_TIME(this,s) ((s) * SPA_NSEC_PER_SEC / (port)->current_format.info.raw.rate) #define BYTES_TO_SAMPLES(this,b) ((b)/(port)->bpf) #define BYTES_TO_TIME(this,b) SAMPLES_TO_TIME(this, BYTES_TO_SAMPLES (this, b)) enum wave_type { WAVE_SINE, WAVE_SQUARE, }; #define DEFAULT_RATE 48000 #define DEFAULT_CHANNELS 2 #define DEFAULT_LIVE true #define DEFAULT_WAVE WAVE_SINE #define DEFAULT_FREQ 440.0 #define DEFAULT_VOLUME 1.0 struct props { bool live; uint32_t wave; float freq; float volume; }; static void reset_props(struct props *props) { props->live = DEFAULT_LIVE; props->wave = DEFAULT_WAVE; props->freq = DEFAULT_FREQ; props->volume = DEFAULT_VOLUME; } #define MAX_BUFFERS 16 #define MAX_PORTS 1 struct buffer { uint32_t id; struct spa_buffer *outbuf; bool outstanding; struct spa_meta_header *h; struct spa_list link; }; struct impl; typedef void (*render_func_t) (struct impl *this, void *samples, size_t n_samples); struct port { uint64_t info_all; struct spa_port_info info; struct spa_param_info params[5]; struct spa_io_buffers *io; struct spa_io_sequence *io_control; uint32_t io_control_size; bool have_format; struct spa_audio_info current_format; size_t bpf; render_func_t render_func; float accumulator; struct buffer buffers[MAX_BUFFERS]; uint32_t n_buffers; struct spa_list empty; }; struct impl { struct spa_handle handle; struct spa_node node; struct spa_log *log; struct spa_loop *data_loop; struct spa_system *data_system; uint32_t quantum_limit; uint64_t info_all; struct spa_node_info info; struct spa_param_info params[2]; struct props props; struct spa_io_clock *clock; struct spa_io_position *position; struct spa_hook_list hooks; struct spa_callbacks callbacks; bool async; struct spa_source timer_source; struct itimerspec timerspec; bool started; uint64_t start_time; uint64_t elapsed_time; uint64_t sample_count; struct port port; }; #define CHECK_PORT(this,d,p) ((d) == SPA_DIRECTION_OUTPUT && (p) < MAX_PORTS) static int impl_node_enum_params(void *object, int seq, uint32_t id, uint32_t start, uint32_t num, const struct spa_pod *filter) { struct impl *this = object; struct spa_pod *param; struct spa_pod_builder b = { 0 }; uint8_t buffer[1024]; struct spa_result_node_params result; uint32_t count = 0; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(num != 0, -EINVAL); result.id = id; result.next = start; next: result.index = result.next++; spa_pod_builder_init(&b, buffer, sizeof(buffer)); switch (id) { case SPA_PARAM_PropInfo: { struct props *p = &this->props; struct spa_pod_frame f[2]; switch (result.index) { case 0: param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_PropInfo, id, SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_live), SPA_PROP_INFO_description, SPA_POD_String("Configure live mode of the source"), SPA_PROP_INFO_type, SPA_POD_Bool(p->live)); break; case 1: spa_pod_builder_push_object(&b, &f[0], SPA_TYPE_OBJECT_PropInfo, id); spa_pod_builder_add(&b, SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_waveType), SPA_PROP_INFO_description, SPA_POD_String("Select the waveform"), SPA_PROP_INFO_type, SPA_POD_Int(p->wave), 0); spa_pod_builder_prop(&b, SPA_PROP_INFO_labels, 0); spa_pod_builder_push_struct(&b, &f[1]); spa_pod_builder_int(&b, WAVE_SINE); spa_pod_builder_string(&b, "Sine wave"); spa_pod_builder_int(&b, WAVE_SQUARE); spa_pod_builder_string(&b, "Square wave"); spa_pod_builder_pop(&b, &f[1]); param = spa_pod_builder_pop(&b, &f[0]); break; case 2: param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_PropInfo, id, SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_frequency), SPA_PROP_INFO_description, SPA_POD_String("Select the frequency"), SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Float(p->freq, 0.0, 50000000.0)); break; case 3: param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_PropInfo, id, SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_volume), SPA_PROP_INFO_description, SPA_POD_String("Select the volume"), SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Float(p->volume, 0.0, 10.0)); break; default: return 0; } break; } case SPA_PARAM_Props: { struct props *p = &this->props; switch (result.index) { case 0: param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_Props, id, SPA_PROP_live, SPA_POD_Bool(p->live), SPA_PROP_waveType, SPA_POD_Int(p->wave), SPA_PROP_frequency, SPA_POD_Float(p->freq), SPA_PROP_volume, SPA_POD_Float(p->volume)); break; default: return 0; } break; } case SPA_PARAM_IO: { switch (result.index) { case 0: param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamIO, id, SPA_PARAM_IO_id, SPA_POD_Id(SPA_IO_Clock), SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_clock))); break; case 1: param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamIO, id, SPA_PARAM_IO_id, SPA_POD_Id(SPA_IO_Position), SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_position))); break; default: return 0; } break; } default: return -ENOENT; } if (spa_pod_filter(&b, &result.param, param, filter) < 0) goto next; spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); if (++count != num) goto next; return 0; } static int impl_node_set_param(void *object, uint32_t id, uint32_t flags, const struct spa_pod *param) { struct impl *this = object; spa_return_val_if_fail(this != NULL, -EINVAL); if (id == SPA_PARAM_Props) { struct props *p = &this->props; struct port *port = &this->port; if (param == NULL) { reset_props(p); return 0; } spa_pod_parse_object(param, SPA_TYPE_OBJECT_Props, NULL, SPA_PROP_live, SPA_POD_OPT_Bool(&p->live), SPA_PROP_waveType, SPA_POD_OPT_Int(&p->wave), SPA_PROP_frequency, SPA_POD_OPT_Float(&p->freq), SPA_PROP_volume, SPA_POD_OPT_Float(&p->volume)); if (p->live) port->info.flags |= SPA_PORT_FLAG_LIVE; else port->info.flags &= ~SPA_PORT_FLAG_LIVE; } else return -ENOENT; return 0; } static int impl_node_set_io(void *object, uint32_t id, void *data, size_t size) { struct impl *this = object; spa_return_val_if_fail(this != NULL, -EINVAL); switch (id) { case SPA_IO_Clock: if (size > 0 && size < sizeof(struct spa_io_clock)) return -EINVAL; this->clock = data; break; case SPA_IO_Position: this->position = data; break; default: return -ENOENT; } return 0; } #include "render.c" static void set_timer(struct impl *this, bool enabled) { if (this->async || this->props.live) { if (enabled) { if (this->props.live) { uint64_t next_time = this->start_time + this->elapsed_time; this->timerspec.it_value.tv_sec = next_time / SPA_NSEC_PER_SEC; this->timerspec.it_value.tv_nsec = next_time % SPA_NSEC_PER_SEC; } else { this->timerspec.it_value.tv_sec = 0; this->timerspec.it_value.tv_nsec = 1; } } else { this->timerspec.it_value.tv_sec = 0; this->timerspec.it_value.tv_nsec = 0; } spa_system_timerfd_settime(this->data_system, this->timer_source.fd, SPA_FD_TIMER_ABSTIME, &this->timerspec, NULL); } } static int read_timer(struct impl *this) { uint64_t expirations; int res = 0; if (this->async || this->props.live) { if ((res = spa_system_timerfd_read(this->data_system, this->timer_source.fd, &expirations)) < 0) { if (res != -EAGAIN) spa_log_error(this->log, "%p: timerfd error: %s", this, spa_strerror(res)); } } return 0; } static void update_target(struct impl *this) { if (this->position) { this->position->clock.duration = this->position->clock.target_duration; this->position->clock.rate = this->position->clock.target_rate; } } static int make_buffer(struct impl *this) { struct buffer *b; struct port *port = &this->port; struct spa_io_buffers *io = port->io; uint32_t n_bytes, n_samples, maxsize; void *data; struct spa_data *d; uint32_t filled, avail; uint32_t index, offset, l0, l1; if (read_timer(this) < 0) return 0; if (spa_list_is_empty(&port->empty)) { set_timer(this, false); spa_log_error(this->log, "%p: out of buffers", this); return -EPIPE; } b = spa_list_first(&port->empty, struct buffer, link); spa_list_remove(&b->link); b->outstanding = true; d = b->outbuf->datas; maxsize = d[0].maxsize; data = d[0].data; n_bytes = maxsize; spa_log_trace(this->log, "%p: dequeue buffer %d %d %d", this, b->id, maxsize, n_bytes); filled = 0; index = 0; avail = maxsize - filled; offset = index % maxsize; if (this->position && this->position->clock.duration) { n_bytes = SPA_MIN(avail, n_bytes); n_samples = this->position->clock.duration; if (n_samples * port->bpf < n_bytes) n_bytes = n_samples * port->bpf; } else { n_bytes = SPA_MIN(avail, n_bytes); n_samples = n_bytes / port->bpf; } l0 = SPA_MIN(n_bytes, maxsize - offset) / port->bpf; l1 = n_samples - l0; port->render_func(this, SPA_PTROFF(data, offset, void), l0); if (l1 > 0) port->render_func(this, data, l1); d[0].chunk->offset = index; d[0].chunk->size = n_bytes; d[0].chunk->stride = port->bpf; if (b->h) { b->h->seq = this->sample_count; b->h->pts = this->start_time + this->elapsed_time; b->h->dts_offset = 0; } this->sample_count += n_samples; this->elapsed_time = SAMPLES_TO_TIME(this, this->sample_count); set_timer(this, true); io->buffer_id = b->id; io->status = SPA_STATUS_HAVE_DATA; return io->status; } static void on_output(struct spa_source *source) { struct impl *this = source->data; int res; update_target(this); res = make_buffer(this); if (res == SPA_STATUS_HAVE_DATA) spa_node_call_ready(&this->callbacks, res); } static int impl_node_send_command(void *object, const struct spa_command *command) { struct impl *this = object; struct port *port; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(command != NULL, -EINVAL); port = &this->port; switch (SPA_NODE_COMMAND_ID(command)) { case SPA_NODE_COMMAND_Start: { struct timespec now; if (!port->have_format) return -EIO; if (port->n_buffers == 0) return -EIO; if (this->started) return 0; clock_gettime(CLOCK_MONOTONIC, &now); if (this->props.live) this->start_time = SPA_TIMESPEC_TO_NSEC(&now); else this->start_time = 0; this->sample_count = 0; this->elapsed_time = 0; this->started = true; set_timer(this, true); break; } case SPA_NODE_COMMAND_Suspend: case SPA_NODE_COMMAND_Pause: if (!this->started) return 0; this->started = false; set_timer(this, false); break; default: return -ENOTSUP; } return 0; } static const struct spa_dict_item node_info_items[] = { { SPA_KEY_MEDIA_CLASS, "Audio/Source" }, { SPA_KEY_NODE_DRIVER, "true" }, }; static void emit_node_info(struct impl *this, bool full) { uint64_t old = full ? this->info.change_mask : 0; if (full) this->info.change_mask = this->info_all; if (this->info.change_mask) { this->info.props = &SPA_DICT_INIT_ARRAY(node_info_items); spa_node_emit_info(&this->hooks, &this->info); this->info.change_mask = old; } } static void emit_port_info(struct impl *this, struct port *port, bool full) { uint64_t old = full ? port->info.change_mask : 0; if (full) port->info.change_mask = port->info_all; if (port->info.change_mask) { spa_node_emit_port_info(&this->hooks, SPA_DIRECTION_OUTPUT, 0, &port->info); port->info.change_mask = old; } } static int impl_node_add_listener(void *object, struct spa_hook *listener, const struct spa_node_events *events, void *data) { struct impl *this = object; struct spa_hook_list save; spa_return_val_if_fail(this != NULL, -EINVAL); spa_hook_list_isolate(&this->hooks, &save, listener, events, data); emit_node_info(this, true); emit_port_info(this, &this->port, true); spa_hook_list_join(&this->hooks, &save); return 0; } static int impl_node_set_callbacks(void *object, const struct spa_node_callbacks *callbacks, void *data) { struct impl *this = object; spa_return_val_if_fail(this != NULL, -EINVAL); this->callbacks = SPA_CALLBACKS_INIT(callbacks, data); return 0; } static int impl_node_add_port(void *object, enum spa_direction direction, uint32_t port_id, const struct spa_dict *props) { return -ENOTSUP; } static int impl_node_remove_port(void *object, enum spa_direction direction, uint32_t port_id) { return -ENOTSUP; } static int port_enum_formats(struct impl *this, enum spa_direction direction, uint32_t port_id, uint32_t index, struct spa_pod **param, struct spa_pod_builder *builder) { switch (index) { case 0: *param = spa_pod_builder_add_object(builder, SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_audio), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), SPA_FORMAT_AUDIO_format, SPA_POD_CHOICE_ENUM_Id(5, SPA_AUDIO_FORMAT_S16, SPA_AUDIO_FORMAT_S16, SPA_AUDIO_FORMAT_S32, SPA_AUDIO_FORMAT_F32, SPA_AUDIO_FORMAT_F64), SPA_FORMAT_AUDIO_rate, SPA_POD_CHOICE_RANGE_Int( DEFAULT_RATE, 1, INT32_MAX), SPA_FORMAT_AUDIO_channels, SPA_POD_CHOICE_RANGE_Int( DEFAULT_CHANNELS, 1, INT32_MAX)); break; default: return 0; } return 1; } static int impl_node_port_enum_params(void *object, int seq, enum spa_direction direction, uint32_t port_id, uint32_t id, uint32_t start, uint32_t num, const struct spa_pod *filter) { struct impl *this = object; struct port *port; struct spa_pod_builder b = { 0 }; uint8_t buffer[1024]; struct spa_pod *param; struct spa_result_node_params result; uint32_t count = 0; int res; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(num != 0, -EINVAL); spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); port = &this->port; result.id = id; result.next = start; next: result.index = result.next++; spa_pod_builder_init(&b, buffer, sizeof(buffer)); switch (id) { case SPA_PARAM_EnumFormat: if ((res = port_enum_formats(this, direction, port_id, result.index, ¶m, &b)) <= 0) return res; break; case SPA_PARAM_Format: if (!port->have_format) return -EIO; if (result.index > 0) return 0; param = spa_format_audio_raw_build(&b, id, &port->current_format.info.raw); break; case SPA_PARAM_Buffers: if (!port->have_format) return -EIO; if (result.index > 0) return 0; param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamBuffers, id, SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(1, 1, MAX_BUFFERS), SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(1), SPA_PARAM_BUFFERS_size, SPA_POD_CHOICE_RANGE_Int( this->quantum_limit * port->bpf, 16 * port->bpf, INT32_MAX), SPA_PARAM_BUFFERS_stride, SPA_POD_Int(port->bpf)); break; case SPA_PARAM_Meta: switch (result.index) { case 0: param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamMeta, id, SPA_PARAM_META_type, SPA_POD_Id(SPA_META_Header), SPA_PARAM_META_size, SPA_POD_Int(sizeof(struct spa_meta_header))); break; default: return 0; } break; case SPA_PARAM_IO: switch (result.index) { case 0: param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamIO, id, SPA_PARAM_IO_id, SPA_POD_Id(SPA_IO_Buffers), SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_buffers))); break; case 1: param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamIO, id, SPA_PARAM_IO_id, SPA_POD_Id(SPA_IO_Control), SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_sequence))); break; default: return 0; } break; default: return -ENOENT; } if (spa_pod_filter(&b, &result.param, param, filter) < 0) goto next; spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); if (++count != num) goto next; return 0; } static int clear_buffers(struct impl *this, struct port *port) { if (port->n_buffers > 0) { spa_log_info(this->log, "%p: clear buffers", this); port->n_buffers = 0; spa_list_init(&port->empty); this->started = false; set_timer(this, false); } return 0; } static int port_set_format(struct impl *this, enum spa_direction direction, uint32_t port_id, uint32_t flags, const struct spa_pod *format) { int res; struct port *port = &this->port; if (format == NULL) { port->have_format = false; clear_buffers(this, port); } else { struct spa_audio_info info = { 0 }; int idx; const size_t sizes[4] = { 2, 4, 4, 8 }; if ((res = spa_format_parse(format, &info.media_type, &info.media_subtype)) < 0) return res; if (info.media_type != SPA_MEDIA_TYPE_audio || info.media_subtype != SPA_MEDIA_SUBTYPE_raw) return -EINVAL; if (spa_format_audio_raw_parse(format, &info.info.raw) < 0) return -EINVAL; if (info.info.raw.rate == 0 || info.info.raw.channels == 0) return -EINVAL; switch (info.info.raw.format) { case SPA_AUDIO_FORMAT_S16: idx = 0; break; case SPA_AUDIO_FORMAT_S32: idx = 1; break; case SPA_AUDIO_FORMAT_F32: idx = 2; break; case SPA_AUDIO_FORMAT_F64: idx = 3; break; default: return -EINVAL; } port->bpf = sizes[idx] * info.info.raw.channels; port->current_format = info; port->have_format = true; port->render_func = sine_funcs[idx]; } port->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; if (port->have_format) { port->info.change_mask |= SPA_PORT_CHANGE_MASK_RATE; port->info.rate = SPA_FRACTION(1, port->current_format.info.raw.rate); port->params[3] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_READWRITE); port->params[4] = SPA_PARAM_INFO(SPA_PARAM_Buffers, SPA_PARAM_INFO_READ); } else { port->params[3] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); port->params[4] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); } emit_port_info(this, port, false); return 0; } static int impl_node_port_set_param(void *object, enum spa_direction direction, uint32_t port_id, uint32_t id, uint32_t flags, const struct spa_pod *param) { struct impl *this = object; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); if (id == SPA_PARAM_Format) return port_set_format(this, direction, port_id, flags, param); return -ENOENT; } static int impl_node_port_use_buffers(void *object, enum spa_direction direction, uint32_t port_id, uint32_t flags, struct spa_buffer **buffers, uint32_t n_buffers) { struct impl *this = object; struct port *port; uint32_t i; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); port = &this->port; clear_buffers(this, port); if (n_buffers > 0 && !port->have_format) return -EIO; if (n_buffers > MAX_BUFFERS) return -ENOSPC; for (i = 0; i < n_buffers; i++) { struct buffer *b; struct spa_data *d = buffers[i]->datas; b = &port->buffers[i]; b->id = i; b->outbuf = buffers[i]; b->outstanding = false; b->h = spa_buffer_find_meta_data(buffers[i], SPA_META_Header, sizeof(*b->h)); if (d[0].data == NULL) { spa_log_error(this->log, "%p: invalid memory on buffer %p", this, buffers[i]); return -EINVAL; } spa_list_append(&port->empty, &b->link); } port->n_buffers = n_buffers; return 0; } static int impl_node_port_set_io(void *object, enum spa_direction direction, uint32_t port_id, uint32_t id, void *data, size_t size) { struct impl *this = object; struct port *port; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); port = &this->port; switch (id) { case SPA_IO_Buffers: port->io = data; break; case SPA_IO_Control: port->io_control = data; port->io_control_size = size; break; default: return -ENOENT; } return 0; } static inline void reuse_buffer(struct impl *this, struct port *port, uint32_t id) { struct buffer *b = &port->buffers[id]; spa_return_if_fail(b->outstanding); spa_log_trace(this->log, "%p: reuse buffer %d", this, id); b->outstanding = false; spa_list_append(&port->empty, &b->link); if (!this->props.live) set_timer(this, true); } static int impl_node_port_reuse_buffer(void *object, uint32_t port_id, uint32_t buffer_id) { struct impl *this = object; struct port *port; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(port_id == 0, -EINVAL); port = &this->port; spa_return_val_if_fail(buffer_id < port->n_buffers, -EINVAL); reuse_buffer(this, port, buffer_id); return 0; } static int process_control(struct impl *this, struct spa_pod_sequence *sequence, uint32_t size) { struct spa_pod_parser parser; struct spa_pod_frame frame; struct spa_pod_sequence seq; const void *seq_body, *c_body; struct spa_pod_control c; spa_pod_parser_init_from_data(&parser, sequence, size, 0, size); if (spa_pod_parser_push_sequence_body(&parser, &frame, &seq, &seq_body) < 0) return 0; while (spa_pod_parser_get_control_body(&parser, &c, &c_body) >= 0) { switch (c.type) { case SPA_CONTROL_Properties: { struct props *p = &this->props; spa_pod_body_parse_object(&c.value, c_body, SPA_TYPE_OBJECT_Props, NULL, SPA_PROP_frequency, SPA_POD_OPT_Float(&p->freq), SPA_PROP_volume, SPA_POD_OPT_Float(&p->volume)); break; } default: break; } } return 0; } static int impl_node_process(void *object) { struct impl *this = object; struct port *port; struct spa_io_buffers *io; spa_return_val_if_fail(this != NULL, -EINVAL); port = &this->port; io = port->io; if ((io = port->io) == NULL) return -EIO; if (port->io_control) process_control(this, &port->io_control->sequence, port->io_control_size); if (io->status == SPA_STATUS_HAVE_DATA) return SPA_STATUS_HAVE_DATA; if (io->buffer_id < port->n_buffers) { reuse_buffer(this, port, io->buffer_id); io->buffer_id = SPA_ID_INVALID; } if (!this->props.live) return make_buffer(this); else return SPA_STATUS_OK; } static const struct spa_node_methods impl_node = { SPA_VERSION_NODE_METHODS, .add_listener = impl_node_add_listener, .set_callbacks = impl_node_set_callbacks, .enum_params = impl_node_enum_params, .set_param = impl_node_set_param, .set_io = impl_node_set_io, .send_command = impl_node_send_command, .add_port = impl_node_add_port, .remove_port = impl_node_remove_port, .port_enum_params = impl_node_port_enum_params, .port_set_param = impl_node_port_set_param, .port_use_buffers = impl_node_port_use_buffers, .port_set_io = impl_node_port_set_io, .port_reuse_buffer = impl_node_port_reuse_buffer, .process = impl_node_process, }; static int impl_get_interface(struct spa_handle *handle, const char *type, void **interface) { struct impl *this; spa_return_val_if_fail(handle != NULL, -EINVAL); spa_return_val_if_fail(interface != NULL, -EINVAL); this = (struct impl *) handle; if (spa_streq(type, SPA_TYPE_INTERFACE_Node)) *interface = &this->node; else return -ENOENT; return 0; } static int do_remove_timer(struct spa_loop *loop, bool async, uint32_t seq, const void *data, size_t size, void *user_data) { struct impl *this = user_data; spa_loop_remove_source(this->data_loop, &this->timer_source); return 0; } static int impl_clear(struct spa_handle *handle) { struct impl *this; spa_return_val_if_fail(handle != NULL, -EINVAL); this = (struct impl *) handle; if (this->data_loop) spa_loop_locked(this->data_loop, do_remove_timer, 0, NULL, 0, this); spa_system_close(this->data_system, this->timer_source.fd); return 0; } static size_t impl_get_size(const struct spa_handle_factory *factory, const struct spa_dict *params) { return sizeof(struct impl); } static int impl_init(const struct spa_handle_factory *factory, struct spa_handle *handle, const struct spa_dict *info, const struct spa_support *support, uint32_t n_support) { struct impl *this; struct port *port; uint32_t i; spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(handle != NULL, -EINVAL); handle->get_interface = impl_get_interface; handle->clear = impl_clear; this = (struct impl *) handle; this->log = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log); this->data_loop = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_DataLoop); if (this->data_loop == NULL) { spa_log_error(this->log, "%p: could not find a data loop", this); return -EINVAL; } this->data_system = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_DataSystem); if (this->data_system == NULL) { spa_log_error(this->log, "%p: could not find a data system", this); return -EINVAL; } for (i = 0; info && i < info->n_items; i++) { const char *k = info->items[i].key; const char *s = info->items[i].value; if (spa_streq(k, "clock.quantum-limit")) spa_atou32(s, &this->quantum_limit, 0); } spa_hook_list_init(&this->hooks); this->node.iface = SPA_INTERFACE_INIT( SPA_TYPE_INTERFACE_Node, SPA_VERSION_NODE, &impl_node, this); this->info_all |= SPA_NODE_CHANGE_MASK_FLAGS | SPA_NODE_CHANGE_MASK_PROPS | SPA_NODE_CHANGE_MASK_PARAMS; this->info = SPA_NODE_INFO_INIT(); this->info.max_output_ports = 1; this->info.flags = SPA_NODE_FLAG_RT; this->params[0] = SPA_PARAM_INFO(SPA_PARAM_PropInfo, SPA_PARAM_INFO_READ); this->params[1] = SPA_PARAM_INFO(SPA_PARAM_Props, SPA_PARAM_INFO_READWRITE); this->info.params = this->params; this->info.n_params = 2; reset_props(&this->props); this->timer_source.func = on_output; this->timer_source.data = this; this->timer_source.fd = spa_system_timerfd_create(this->data_system, CLOCK_MONOTONIC, SPA_FD_CLOEXEC | SPA_FD_NONBLOCK); this->timer_source.mask = SPA_IO_IN; this->timer_source.rmask = 0; this->timerspec.it_value.tv_sec = 0; this->timerspec.it_value.tv_nsec = 0; this->timerspec.it_interval.tv_sec = 0; this->timerspec.it_interval.tv_nsec = 0; if (this->data_loop) spa_loop_add_source(this->data_loop, &this->timer_source); port = &this->port; port->info_all = SPA_PORT_CHANGE_MASK_FLAGS | SPA_PORT_CHANGE_MASK_PARAMS; port->info = SPA_PORT_INFO_INIT(); port->info.flags = SPA_PORT_FLAG_NO_REF; if (this->props.live) port->info.flags |= SPA_PORT_FLAG_LIVE; port->params[0] = SPA_PARAM_INFO(SPA_PARAM_EnumFormat, SPA_PARAM_INFO_READ); port->params[1] = SPA_PARAM_INFO(SPA_PARAM_Meta, SPA_PARAM_INFO_READ); port->params[2] = SPA_PARAM_INFO(SPA_PARAM_IO, SPA_PARAM_INFO_READ); port->params[3] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); port->params[4] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); port->info.params = port->params; port->info.n_params = 5; spa_list_init(&port->empty); spa_log_info(this->log, "%p: initialized", this); return 0; } static const struct spa_interface_info impl_interfaces[] = { {SPA_TYPE_INTERFACE_Node,}, }; static int impl_enum_interface_info(const struct spa_handle_factory *factory, const struct spa_interface_info **info, uint32_t *index) { spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(info != NULL, -EINVAL); spa_return_val_if_fail(index != NULL, -EINVAL); switch (*index) { case 0: *info = &impl_interfaces[*index]; break; default: return 0; } (*index)++; return 1; } static const struct spa_dict_item info_items[] = { { SPA_KEY_FACTORY_AUTHOR, "Wim Taymans " }, { SPA_KEY_FACTORY_DESCRIPTION, "Generate an audio test pattern" }, }; static const struct spa_dict info = SPA_DICT_INIT_ARRAY(info_items); const struct spa_handle_factory spa_audiotestsrc_factory = { SPA_VERSION_HANDLE_FACTORY, "audiotestsrc", &info, impl_get_size, impl_init, impl_enum_interface_info, }; pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/audiotestsrc/meson.build000066400000000000000000000005151511204443500276160ustar00rootroot00000000000000audiotestsrc_sources = ['audiotestsrc.c', 'plugin.c'] audiotestsrclib = shared_library('spa-audiotestsrc', audiotestsrc_sources, dependencies : [ spa_dep, mathlib ], install : true, install_dir : spa_plugindir / 'audiotestsrc') pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/audiotestsrc/plugin.c000066400000000000000000000011751511204443500271210ustar00rootroot00000000000000/* Spa Volume plugin */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include extern const struct spa_handle_factory spa_audiotestsrc_factory; SPA_LOG_TOPIC_ENUM_DEFINE_REGISTERED; SPA_EXPORT int spa_handle_factory_enum(const struct spa_handle_factory **factory, uint32_t *index) { spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(index != NULL, -EINVAL); switch (*index) { case 0: *factory = &spa_audiotestsrc_factory; break; default: return 0; } (*index)++; return 1; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/audiotestsrc/render.c000066400000000000000000000025661511204443500271070ustar00rootroot00000000000000/* Spa */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #define M_PI_M2f (float)(M_PI+M_PI) #define DEFINE_SINE(type,scale) \ static void \ audio_test_src_create_sine_##type (struct impl *this, type *samples, size_t n_samples) \ { \ size_t i; \ uint32_t c, channels; \ float step, amp; \ float freq = this->props.freq; \ float volume = this->props.volume; \ \ channels = this->port.current_format.info.raw.channels; \ step = M_PI_M2f * freq / this->port.current_format.info.raw.rate; \ amp = volume * scale; \ \ for (i = 0; i < n_samples; i++) { \ type val; \ this->port.accumulator += step; \ if (this->port.accumulator >= M_PI_M2f) \ this->port.accumulator -= M_PI_M2f; \ val = (type) (sin (this->port.accumulator) * amp); \ for (c = 0; c < channels; ++c) \ *samples++ = val; \ } \ } DEFINE_SINE(int16_t, 32767.0f); DEFINE_SINE(int32_t, 2147483647.0f); DEFINE_SINE(float, 1.0f); DEFINE_SINE(double, 1.0f); static const render_func_t sine_funcs[] = { (render_func_t) audio_test_src_create_sine_int16_t, (render_func_t) audio_test_src_create_sine_int32_t, (render_func_t) audio_test_src_create_sine_float, (render_func_t) audio_test_src_create_sine_double }; pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/avb/000077500000000000000000000000001511204443500235125ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/avb/avb-pcm-sink.c000066400000000000000000000537451511204443500261630ustar00rootroot00000000000000/* Spa AVB PCM Sink */ /* SPDX-FileCopyrightText: Copyright © 2022 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #include #include #include #include #include #include #include "avb-pcm.h" #define CHECK_PORT(this,d,p) ((d) == SPA_DIRECTION_INPUT && (p) == 0) #define GET_PORT(this,d,p) (&this->ports[p]) static void reset_props(struct props *props) { snprintf(props->ifname, sizeof(props->ifname), "%s", DEFAULT_IFNAME); parse_addr(props->addr, DEFAULT_ADDR); props->prio = DEFAULT_PRIO; parse_streamid(&props->streamid, DEFAULT_STREAMID); props->mtt = DEFAULT_MTT; props->t_uncertainty = DEFAULT_TU; props->frames_per_pdu = DEFAULT_FRAMES_PER_PDU; } static void emit_node_info(struct state *this, bool full) { uint64_t old = full ? this->info.change_mask : 0; if (full) this->info.change_mask = this->info_all; if (this->info.change_mask) { struct spa_dict_item items[4]; uint32_t i, n_items = 0; items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_API, "avb"); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_MEDIA_CLASS, "Audio/Sink"); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_NODE_DRIVER, "true"); this->info.props = &SPA_DICT_INIT(items, n_items); if (this->info.change_mask & SPA_NODE_CHANGE_MASK_PARAMS) { for (i = 0; i < this->info.n_params; i++) { if (this->params[i].user > 0) { this->params[i].flags ^= SPA_PARAM_INFO_SERIAL; this->params[i].user = 0; } } } spa_node_emit_info(&this->hooks, &this->info); this->info.change_mask = old; } } static void emit_port_info(struct state *this, struct port *port, bool full) { uint64_t old = full ? port->info.change_mask : 0; if (full) port->info.change_mask = port->info_all; if (port->info.change_mask) { uint32_t i; if (port->info.change_mask & SPA_PORT_CHANGE_MASK_PARAMS) { for (i = 0; i < port->info.n_params; i++) { if (port->params[i].user > 0) { port->params[i].flags ^= SPA_PARAM_INFO_SERIAL; port->params[i].user = 0; } } } spa_node_emit_port_info(&this->hooks, port->direction, port->id, &port->info); port->info.change_mask = old; } } static int impl_node_enum_params(void *object, int seq, uint32_t id, uint32_t start, uint32_t num, const struct spa_pod *filter) { struct state *this = object; struct spa_pod *param; struct spa_pod_builder b = { 0 }; uint8_t buffer[4096]; struct spa_result_node_params result; uint32_t count = 0; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(num != 0, -EINVAL); result.id = id; result.next = start; next: result.index = result.next++; spa_pod_builder_init(&b, buffer, sizeof(buffer)); switch (id) { case SPA_PARAM_PropInfo: { switch (result.index) { default: param = spa_avb_enum_propinfo(this, result.index, &b); if (param == NULL) return 0; } break; } case SPA_PARAM_Props: { struct spa_pod_frame f; switch (result.index) { case 0: spa_pod_builder_push_object(&b, &f, SPA_TYPE_OBJECT_Props, id); spa_pod_builder_add(&b, SPA_PROP_latencyOffsetNsec, SPA_POD_Long(this->process_latency.ns), 0); spa_avb_add_prop_params(this, &b); param = spa_pod_builder_pop(&b, &f); break; default: return 0; } break; } case SPA_PARAM_IO: switch (result.index) { case 0: param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamIO, id, SPA_PARAM_IO_id, SPA_POD_Id(SPA_IO_Clock), SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_clock))); break; case 1: param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamIO, id, SPA_PARAM_IO_id, SPA_POD_Id(SPA_IO_Position), SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_position))); break; default: return 0; } break; case SPA_PARAM_ProcessLatency: switch (result.index) { case 0: param = spa_process_latency_build(&b, id, &this->process_latency); break; default: return 0; } break; default: return -ENOENT; } if (spa_pod_filter(&b, &result.param, param, filter) < 0) goto next; spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); if (++count != num) goto next; return 0; } static int impl_node_set_io(void *object, uint32_t id, void *data, size_t size) { struct state *this = object; spa_return_val_if_fail(this != NULL, -EINVAL); switch (id) { case SPA_IO_Clock: this->clock = data; break; case SPA_IO_Position: this->position = data; break; default: return -ENOENT; } spa_avb_reassign_follower(this); return 0; } static void handle_process_latency(struct state *this, const struct spa_process_latency_info *info) { bool ns_changed = this->process_latency.ns != info->ns; struct port *port = &this->ports[0]; if (this->process_latency.quantum == info->quantum && this->process_latency.rate == info->rate && !ns_changed) return; this->process_latency = *info; this->info.change_mask |= SPA_NODE_CHANGE_MASK_PARAMS; if (ns_changed) this->params[NODE_Props].user++; this->params[NODE_ProcessLatency].user++; port->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; port->params[PORT_Latency].user++; } static int impl_node_set_param(void *object, uint32_t id, uint32_t flags, const struct spa_pod *param) { struct state *this = object; int res; spa_return_val_if_fail(this != NULL, -EINVAL); switch (id) { case SPA_PARAM_Props: { struct props *p = &this->props; struct spa_pod *params = NULL; int64_t lat_ns = -1; if (param == NULL) { reset_props(p); return 0; } spa_pod_parse_object(param, SPA_TYPE_OBJECT_Props, NULL, SPA_PROP_latencyOffsetNsec, SPA_POD_OPT_Long(&lat_ns), SPA_PROP_params, SPA_POD_OPT_Pod(¶ms)); spa_avb_parse_prop_params(this, params); if (lat_ns != -1) { struct spa_process_latency_info info; info = this->process_latency; info.ns = lat_ns; handle_process_latency(this, &info); } emit_node_info(this, false); emit_port_info(this, &this->ports[0], false); break; } case SPA_PARAM_ProcessLatency: { struct spa_process_latency_info info; if ((res = spa_process_latency_parse(param, &info)) < 0) return res; handle_process_latency(this, &info); emit_node_info(this, false); emit_port_info(this, &this->ports[0], false); break; } default: return -ENOENT; } return 0; } static int impl_node_send_command(void *object, const struct spa_command *command) { struct state *this = object; int res; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(command != NULL, -EINVAL); switch (SPA_NODE_COMMAND_ID(command)) { case SPA_NODE_COMMAND_ParamBegin: break; case SPA_NODE_COMMAND_ParamEnd: break; case SPA_NODE_COMMAND_Start: if (!this->ports[0].have_format) return -EIO; if (this->ports[0].n_buffers == 0) return -EIO; if ((res = spa_avb_start(this)) < 0) return res; break; case SPA_NODE_COMMAND_Suspend: case SPA_NODE_COMMAND_Pause: if ((res = spa_avb_pause(this)) < 0) return res; break; default: return -ENOTSUP; } return 0; } static int impl_node_add_listener(void *object, struct spa_hook *listener, const struct spa_node_events *events, void *data) { struct state *this = object; struct spa_hook_list save; spa_return_val_if_fail(this != NULL, -EINVAL); spa_hook_list_isolate(&this->hooks, &save, listener, events, data); emit_node_info(this, true); emit_port_info(this, &this->ports[0], true); spa_hook_list_join(&this->hooks, &save); return 0; } static int impl_node_set_callbacks(void *object, const struct spa_node_callbacks *callbacks, void *data) { struct state *this = object; spa_return_val_if_fail(this != NULL, -EINVAL); this->callbacks = SPA_CALLBACKS_INIT(callbacks, data); return 0; } static int impl_node_sync(void *object, int seq) { struct state *this = object; spa_return_val_if_fail(this != NULL, -EINVAL); spa_node_emit_result(&this->hooks, seq, 0, 0, NULL); return 0; } static int impl_node_add_port(void *object, enum spa_direction direction, uint32_t port_id, const struct spa_dict *props) { return -ENOTSUP; } static int impl_node_remove_port(void *object, enum spa_direction direction, uint32_t port_id) { return -ENOTSUP; } static int impl_node_port_enum_params(void *object, int seq, enum spa_direction direction, uint32_t port_id, uint32_t id, uint32_t start, uint32_t num, const struct spa_pod *filter) { struct state *this = object; struct spa_pod *param; struct spa_pod_builder b = { 0 }; uint8_t buffer[1024]; struct spa_result_node_params result; uint32_t count = 0; struct port *port; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(num != 0, -EINVAL); spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); port = GET_PORT(this, direction, port_id); result.id = id; result.next = start; next: result.index = result.next++; spa_pod_builder_init(&b, buffer, sizeof(buffer)); switch (id) { case SPA_PARAM_EnumFormat: return spa_avb_enum_format(this, seq, start, num, filter); case SPA_PARAM_Format: if (!port->have_format) return -EIO; if (result.index > 0) return 0; param = spa_format_audio_raw_build(&b, id, &port->current_format.info.raw); break; case SPA_PARAM_Buffers: if (!port->have_format) return -EIO; if (result.index > 0) return 0; param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamBuffers, id, SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(2, 1, MAX_BUFFERS), SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(this->blocks), SPA_PARAM_BUFFERS_size, SPA_POD_CHOICE_RANGE_Int( this->quantum_limit * this->stride, 16 * this->stride, INT32_MAX), SPA_PARAM_BUFFERS_stride, SPA_POD_Int(this->stride)); break; case SPA_PARAM_Meta: switch (result.index) { case 0: param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamMeta, id, SPA_PARAM_META_type, SPA_POD_Id(SPA_META_Header), SPA_PARAM_META_size, SPA_POD_Int(sizeof(struct spa_meta_header))); break; default: return 0; } break; case SPA_PARAM_IO: switch (result.index) { case 0: param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamIO, id, SPA_PARAM_IO_id, SPA_POD_Id(SPA_IO_Buffers), SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_buffers))); break; case 1: param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamIO, id, SPA_PARAM_IO_id, SPA_POD_Id(SPA_IO_RateMatch), SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_rate_match))); break; default: return 0; } break; case SPA_PARAM_Latency: switch (result.index) { case 0: case 1: { struct spa_latency_info latency = this->latency[result.index]; if (latency.direction == SPA_DIRECTION_INPUT) spa_process_latency_info_add(&this->process_latency, &latency); param = spa_latency_build(&b, id, &latency); break; } default: return 0; } break; default: return -ENOENT; } if (spa_pod_filter(&b, &result.param, param, filter) < 0) goto next; spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); if (++count != num) goto next; return 0; } static int clear_buffers(struct state *this, struct port *port) { if (port->n_buffers > 0) { spa_list_init(&port->ready); port->n_buffers = 0; } return 0; } static int port_set_format(void *object, struct port *port, uint32_t flags, const struct spa_pod *format) { struct state *this = object; int err; if (format == NULL) { if (!port->have_format) return 0; spa_log_debug(this->log, "clear format"); port->have_format = false; spa_avb_clear_format(this); clear_buffers(this, port); } else { struct spa_audio_info info = { 0 }; if ((err = spa_format_parse(format, &info.media_type, &info.media_subtype)) < 0) return err; if (info.media_type != SPA_MEDIA_TYPE_audio || info.media_subtype != SPA_MEDIA_SUBTYPE_raw) return -EINVAL; if (spa_format_audio_raw_parse(format, &info.info.raw) < 0) return -EINVAL; if ((err = spa_avb_set_format(this, &info, flags)) < 0) return err; port->current_format = info; port->have_format = true; } this->info.change_mask |= SPA_NODE_CHANGE_MASK_PROPS; emit_node_info(this, false); port->info.change_mask |= SPA_PORT_CHANGE_MASK_RATE; port->info.rate = SPA_FRACTION(1, this->rate); port->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; if (port->have_format) { port->params[PORT_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_READWRITE); port->params[PORT_Buffers] = SPA_PARAM_INFO(SPA_PARAM_Buffers, SPA_PARAM_INFO_READ); port->params[PORT_Latency].user++; } else { port->params[PORT_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); port->params[PORT_Buffers] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); } emit_port_info(this, port, false); return 0; } static int impl_node_port_set_param(void *object, enum spa_direction direction, uint32_t port_id, uint32_t id, uint32_t flags, const struct spa_pod *param) { struct state *this = object; struct port *port; int res = 0; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); port = GET_PORT(this, direction, port_id); switch (id) { case SPA_PARAM_Format: res = port_set_format(this, port, flags, param); break; case SPA_PARAM_Latency: { struct spa_latency_info info; if (param == NULL) info = SPA_LATENCY_INFO(SPA_DIRECTION_REVERSE(direction)); else if ((res = spa_latency_parse(param, &info)) < 0) return res; if (direction == info.direction) return -EINVAL; this->latency[info.direction] = info; port->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; port->params[PORT_Latency].user++; emit_port_info(this, port, false); break; } default: res = -ENOENT; break; } return res; } static int impl_node_port_use_buffers(void *object, enum spa_direction direction, uint32_t port_id, uint32_t flags, struct spa_buffer **buffers, uint32_t n_buffers) { struct state *this = object; struct port *port; uint32_t i; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); port = GET_PORT(this, direction, port_id); spa_log_debug(this->log, "%p: use %d buffers", this, n_buffers); if (port->n_buffers > 0) { spa_avb_pause(this); clear_buffers(this, port); } if (n_buffers > 0 && !port->have_format) return -EIO; if (n_buffers > MAX_BUFFERS) return -ENOSPC; for (i = 0; i < n_buffers; i++) { struct buffer *b = &port->buffers[i]; struct spa_data *d = buffers[i]->datas; b->buf = buffers[i]; b->id = i; b->flags = BUFFER_FLAG_OUT; b->h = spa_buffer_find_meta_data(b->buf, SPA_META_Header, sizeof(*b->h)); if (d[0].data == NULL) { spa_log_error(this->log, "%p: need mapped memory", this); return -EINVAL; } spa_log_debug(this->log, "%p: %d %p data:%p", this, i, b->buf, d[0].data); } port->n_buffers = n_buffers; return 0; } static int impl_node_port_set_io(void *object, enum spa_direction direction, uint32_t port_id, uint32_t id, void *data, size_t size) { struct state *this = object; struct port *port; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); port = GET_PORT(this, direction, port_id); spa_log_debug(this->log, "%p: io %d %p %zd", this, id, data, size); switch (id) { case SPA_IO_Buffers: port->io = data; break; case SPA_IO_RateMatch: port->rate_match = data; break; default: return -ENOENT; } return 0; } static int impl_node_port_reuse_buffer(void *object, uint32_t port_id, uint32_t buffer_id) { return -ENOTSUP; } static int impl_node_process(void *object) { struct state *this = object; struct port *port; struct spa_io_buffers *io; spa_return_val_if_fail(this != NULL, -EINVAL); port = GET_PORT(this, SPA_DIRECTION_INPUT, 0); if ((io = port->io) == NULL) return -EIO; spa_log_trace_fp(this->log, "%p: process %d %d/%d", this, io->status, io->buffer_id, port->n_buffers); if (this->position && this->position->clock.flags & SPA_IO_CLOCK_FLAG_FREEWHEEL) { io->status = SPA_STATUS_NEED_DATA; return SPA_STATUS_HAVE_DATA; } if (io->status == SPA_STATUS_HAVE_DATA && io->buffer_id < port->n_buffers) { struct buffer *b = &port->buffers[io->buffer_id]; if (!SPA_FLAG_IS_SET(b->flags, BUFFER_FLAG_OUT)) { spa_log_warn(this->log, "%p: buffer %u in use", this, io->buffer_id); io->status = -EINVAL; return -EINVAL; } spa_log_trace_fp(this->log, "%p: queue buffer %u", this, io->buffer_id); spa_list_append(&port->ready, &b->link); SPA_FLAG_CLEAR(b->flags, BUFFER_FLAG_OUT); io->buffer_id = SPA_ID_INVALID; spa_avb_write(this); io->status = SPA_STATUS_OK; } return SPA_STATUS_HAVE_DATA; } static const struct spa_node_methods impl_node = { SPA_VERSION_NODE_METHODS, .add_listener = impl_node_add_listener, .set_callbacks = impl_node_set_callbacks, .sync = impl_node_sync, .enum_params = impl_node_enum_params, .set_param = impl_node_set_param, .set_io = impl_node_set_io, .send_command = impl_node_send_command, .add_port = impl_node_add_port, .remove_port = impl_node_remove_port, .port_enum_params = impl_node_port_enum_params, .port_set_param = impl_node_port_set_param, .port_use_buffers = impl_node_port_use_buffers, .port_set_io = impl_node_port_set_io, .port_reuse_buffer = impl_node_port_reuse_buffer, .process = impl_node_process, }; static int impl_get_interface(struct spa_handle *handle, const char *type, void **interface) { struct state *this; spa_return_val_if_fail(handle != NULL, -EINVAL); spa_return_val_if_fail(interface != NULL, -EINVAL); this = (struct state *) handle; if (spa_streq(type, SPA_TYPE_INTERFACE_Node)) *interface = &this->node; else return -ENOENT; return 0; } static int impl_clear(struct spa_handle *handle) { struct state *this; spa_return_val_if_fail(handle != NULL, -EINVAL); this = (struct state *) handle; spa_avb_clear(this); return 0; } static size_t impl_get_size(const struct spa_handle_factory *factory, const struct spa_dict *params) { return sizeof(struct state); } static int impl_init(const struct spa_handle_factory *factory, struct spa_handle *handle, const struct spa_dict *info, const struct spa_support *support, uint32_t n_support) { struct state *this; struct port *port; spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(handle != NULL, -EINVAL); handle->get_interface = impl_get_interface; handle->clear = impl_clear; this = (struct state *) handle; this->log = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log); avb_log_topic_init(this->log); this->data_system = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_DataSystem); this->data_loop = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_DataLoop); if (this->data_loop == NULL) { spa_log_error(this->log, "a data loop is needed"); return -EINVAL; } if (this->data_system == NULL) { spa_log_error(this->log, "a data system is needed"); return -EINVAL; } this->node.iface = SPA_INTERFACE_INIT( SPA_TYPE_INTERFACE_Node, SPA_VERSION_NODE, &impl_node, this); spa_hook_list_init(&this->hooks); this->info_all = SPA_NODE_CHANGE_MASK_FLAGS | SPA_NODE_CHANGE_MASK_PROPS | SPA_NODE_CHANGE_MASK_PARAMS; this->info = SPA_NODE_INFO_INIT(); this->info.max_input_ports = 1; this->info.flags = SPA_NODE_FLAG_RT; this->params[NODE_PropInfo] = SPA_PARAM_INFO(SPA_PARAM_PropInfo, SPA_PARAM_INFO_READ); this->params[NODE_Props] = SPA_PARAM_INFO(SPA_PARAM_Props, SPA_PARAM_INFO_READWRITE); this->params[NODE_IO] = SPA_PARAM_INFO(SPA_PARAM_IO, SPA_PARAM_INFO_READ); this->params[NODE_ProcessLatency] = SPA_PARAM_INFO(SPA_PARAM_ProcessLatency, SPA_PARAM_INFO_READWRITE); this->info.params = this->params; this->info.n_params = N_NODE_PARAMS; reset_props(&this->props); port = GET_PORT(this, SPA_DIRECTION_INPUT, 0); port->direction = SPA_DIRECTION_INPUT; port->info_all = SPA_PORT_CHANGE_MASK_FLAGS | SPA_PORT_CHANGE_MASK_PARAMS; port->info = SPA_PORT_INFO_INIT(); port->info.flags = SPA_PORT_FLAG_LIVE | SPA_PORT_FLAG_PHYSICAL | SPA_PORT_FLAG_TERMINAL; port->params[PORT_EnumFormat] = SPA_PARAM_INFO(SPA_PARAM_EnumFormat, SPA_PARAM_INFO_READ); port->params[PORT_Meta] = SPA_PARAM_INFO(SPA_PARAM_Meta, SPA_PARAM_INFO_READ); port->params[PORT_IO] = SPA_PARAM_INFO(SPA_PARAM_IO, SPA_PARAM_INFO_READ); port->params[PORT_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); port->params[PORT_Buffers] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); port->params[PORT_Latency] = SPA_PARAM_INFO(SPA_PARAM_Latency, SPA_PARAM_INFO_READWRITE); port->info.params = port->params; port->info.n_params = N_PORT_PARAMS; spa_list_init(&port->ready); this->latency[port->direction] = SPA_LATENCY_INFO( port->direction, .min_quantum = 1.0f, .max_quantum = 1.0f); this->latency[SPA_DIRECTION_OUTPUT] = SPA_LATENCY_INFO(SPA_DIRECTION_OUTPUT); return spa_avb_init(this, info); } static const struct spa_interface_info impl_interfaces[] = { {SPA_TYPE_INTERFACE_Node,}, }; static int impl_enum_interface_info(const struct spa_handle_factory *factory, const struct spa_interface_info **info, uint32_t *index) { spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(info != NULL, -EINVAL); spa_return_val_if_fail(index != NULL, -EINVAL); switch (*index) { case 0: *info = &impl_interfaces[*index]; break; default: return 0; } (*index)++; return 1; } static const struct spa_dict_item info_items[] = { { SPA_KEY_FACTORY_AUTHOR, "Wim Taymans " }, { SPA_KEY_FACTORY_DESCRIPTION, "Play audio with AVB" }, { SPA_KEY_FACTORY_USAGE, "[]" }, }; static const struct spa_dict info = SPA_DICT_INIT_ARRAY(info_items); const struct spa_handle_factory spa_avb_sink_factory = { SPA_VERSION_HANDLE_FACTORY, "avb.pcm.sink", &info, impl_get_size, impl_init, impl_enum_interface_info, }; pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/avb/avb-pcm-source.c000066400000000000000000000536661511204443500265210ustar00rootroot00000000000000/* Spa AVB PCM Source */ /* SPDX-FileCopyrightText: Copyright © 2022 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #include #include #include #include #include #include #include "avb-pcm.h" #define CHECK_PORT(this,d,p) ((d) == SPA_DIRECTION_OUTPUT && (p) == 0) #define GET_PORT(this,d,p) (&this->ports[p]) static void reset_props(struct props *props) { snprintf(props->ifname, sizeof(props->ifname), "%s", DEFAULT_IFNAME); parse_addr(props->addr, DEFAULT_ADDR); props->prio = DEFAULT_PRIO; parse_streamid(&props->streamid, DEFAULT_STREAMID); props->mtt = DEFAULT_MTT; props->t_uncertainty = DEFAULT_TU; props->frames_per_pdu = DEFAULT_FRAMES_PER_PDU; } static void emit_node_info(struct state *this, bool full) { uint64_t old = full ? this->info.change_mask : 0; if (full) this->info.change_mask = this->info_all; if (this->info.change_mask) { struct spa_dict_item items[4]; uint32_t i, n_items = 0; items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_API, "avb"); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_MEDIA_CLASS, "Audio/Source"); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_NODE_DRIVER, "true"); this->info.props = &SPA_DICT_INIT(items, n_items); if (this->info.change_mask & SPA_NODE_CHANGE_MASK_PARAMS) { for (i = 0; i < this->info.n_params; i++) { if (this->params[i].user > 0) { this->params[i].flags ^= SPA_PARAM_INFO_SERIAL; this->params[i].user = 0; } } } spa_node_emit_info(&this->hooks, &this->info); this->info.change_mask = old; } } static void emit_port_info(struct state *this, struct port *port, bool full) { uint64_t old = full ? port->info.change_mask : 0; if (full) port->info.change_mask = port->info_all; if (port->info.change_mask) { uint32_t i; if (port->info.change_mask & SPA_PORT_CHANGE_MASK_PARAMS) { for (i = 0; i < port->info.n_params; i++) { if (port->params[i].user > 0) { port->params[i].flags ^= SPA_PARAM_INFO_SERIAL; port->params[i].user = 0; } } } spa_node_emit_port_info(&this->hooks, port->direction, port->id, &port->info); port->info.change_mask = old; } } static int impl_node_enum_params(void *object, int seq, uint32_t id, uint32_t start, uint32_t num, const struct spa_pod *filter) { struct state *this = object; struct spa_pod *param; struct spa_pod_builder b = { 0 }; uint8_t buffer[4096]; struct spa_result_node_params result; uint32_t count = 0; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(num != 0, -EINVAL); result.id = id; result.next = start; next: result.index = result.next++; spa_pod_builder_init(&b, buffer, sizeof(buffer)); switch (id) { case SPA_PARAM_PropInfo: { switch (result.index) { default: param = spa_avb_enum_propinfo(this, result.index, &b); if (param == NULL) return 0; } break; } case SPA_PARAM_Props: { struct spa_pod_frame f; switch (result.index) { case 0: spa_pod_builder_push_object(&b, &f, SPA_TYPE_OBJECT_Props, id); spa_pod_builder_add(&b, SPA_PROP_latencyOffsetNsec, SPA_POD_Long(this->process_latency.ns), 0); spa_avb_add_prop_params(this, &b); param = spa_pod_builder_pop(&b, &f); break; default: return 0; } break; } case SPA_PARAM_IO: switch (result.index) { case 0: param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamIO, id, SPA_PARAM_IO_id, SPA_POD_Id(SPA_IO_Clock), SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_clock))); break; case 1: param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamIO, id, SPA_PARAM_IO_id, SPA_POD_Id(SPA_IO_Position), SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_position))); break; default: return 0; } break; case SPA_PARAM_ProcessLatency: switch (result.index) { case 0: param = spa_process_latency_build(&b, id, &this->process_latency); break; default: return 0; } break; default: return -ENOENT; } if (spa_pod_filter(&b, &result.param, param, filter) < 0) goto next; spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); if (++count != num) goto next; return 0; } static int impl_node_set_io(void *object, uint32_t id, void *data, size_t size) { struct state *this = object; spa_return_val_if_fail(this != NULL, -EINVAL); switch (id) { case SPA_IO_Clock: this->clock = data; break; case SPA_IO_Position: this->position = data; break; default: return -ENOENT; } spa_avb_reassign_follower(this); return 0; } static void handle_process_latency(struct state *this, const struct spa_process_latency_info *info) { bool ns_changed = this->process_latency.ns != info->ns; struct port *port = &this->ports[0]; if (this->process_latency.quantum == info->quantum && this->process_latency.rate == info->rate && !ns_changed) return; this->process_latency = *info; this->info.change_mask |= SPA_NODE_CHANGE_MASK_PARAMS; if (ns_changed) this->params[NODE_Props].user++; this->params[NODE_ProcessLatency].user++; port->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; port->params[PORT_Latency].user++; } static int impl_node_set_param(void *object, uint32_t id, uint32_t flags, const struct spa_pod *param) { struct state *this = object; int res; spa_return_val_if_fail(this != NULL, -EINVAL); switch (id) { case SPA_PARAM_Props: { struct props *p = &this->props; struct spa_pod *params = NULL; int64_t lat_ns = -1; if (param == NULL) { reset_props(p); return 0; } spa_pod_parse_object(param, SPA_TYPE_OBJECT_Props, NULL, SPA_PROP_latencyOffsetNsec, SPA_POD_OPT_Long(&lat_ns), SPA_PROP_params, SPA_POD_OPT_Pod(¶ms)); spa_avb_parse_prop_params(this, params); if (lat_ns != -1) { struct spa_process_latency_info info; info = this->process_latency; info.ns = lat_ns; handle_process_latency(this, &info); } emit_node_info(this, false); emit_port_info(this, &this->ports[0], false); break; } case SPA_PARAM_ProcessLatency: { struct spa_process_latency_info info; if ((res = spa_process_latency_parse(param, &info)) < 0) return res; handle_process_latency(this, &info); emit_node_info(this, false); emit_port_info(this, &this->ports[0], false); break; } default: return -ENOENT; } return 0; } static int impl_node_send_command(void *object, const struct spa_command *command) { struct state *this = object; int res; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(command != NULL, -EINVAL); switch (SPA_NODE_COMMAND_ID(command)) { case SPA_NODE_COMMAND_ParamBegin: break; case SPA_NODE_COMMAND_ParamEnd: break; case SPA_NODE_COMMAND_Start: if (!this->ports[0].have_format) return -EIO; if (this->ports[0].n_buffers == 0) return -EIO; if ((res = spa_avb_start(this)) < 0) return res; break; case SPA_NODE_COMMAND_Suspend: case SPA_NODE_COMMAND_Pause: if ((res = spa_avb_pause(this)) < 0) return res; break; default: return -ENOTSUP; } return 0; } static int impl_node_add_listener(void *object, struct spa_hook *listener, const struct spa_node_events *events, void *data) { struct state *this = object; struct spa_hook_list save; spa_return_val_if_fail(this != NULL, -EINVAL); spa_hook_list_isolate(&this->hooks, &save, listener, events, data); emit_node_info(this, true); emit_port_info(this, &this->ports[0], true); spa_hook_list_join(&this->hooks, &save); return 0; } static int impl_node_set_callbacks(void *object, const struct spa_node_callbacks *callbacks, void *data) { struct state *this = object; spa_return_val_if_fail(this != NULL, -EINVAL); this->callbacks = SPA_CALLBACKS_INIT(callbacks, data); return 0; } static int impl_node_sync(void *object, int seq) { struct state *this = object; spa_return_val_if_fail(this != NULL, -EINVAL); spa_node_emit_result(&this->hooks, seq, 0, 0, NULL); return 0; } static int impl_node_add_port(void *object, enum spa_direction direction, uint32_t port_id, const struct spa_dict *props) { return -ENOTSUP; } static int impl_node_remove_port(void *object, enum spa_direction direction, uint32_t port_id) { return -ENOTSUP; } static int impl_node_port_enum_params(void *object, int seq, enum spa_direction direction, uint32_t port_id, uint32_t id, uint32_t start, uint32_t num, const struct spa_pod *filter) { struct state *this = object; struct spa_pod *param; struct spa_pod_builder b = { 0 }; uint8_t buffer[1024]; struct spa_result_node_params result; uint32_t count = 0; struct port *port; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(num != 0, -EINVAL); spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); port = GET_PORT(this, direction, port_id); result.id = id; result.next = start; next: result.index = result.next++; spa_pod_builder_init(&b, buffer, sizeof(buffer)); switch (id) { case SPA_PARAM_EnumFormat: return spa_avb_enum_format(this, seq, start, num, filter); case SPA_PARAM_Format: if (!port->have_format) return -EIO; if (result.index > 0) return 0; param = spa_format_audio_raw_build(&b, id, &port->current_format.info.raw); break; case SPA_PARAM_Buffers: if (!port->have_format) return -EIO; if (result.index > 0) return 0; param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamBuffers, id, SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(2, 1, MAX_BUFFERS), SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(this->blocks), SPA_PARAM_BUFFERS_size, SPA_POD_CHOICE_RANGE_Int( this->quantum_limit * this->stride, 16 * this->stride, INT32_MAX), SPA_PARAM_BUFFERS_stride, SPA_POD_Int(this->stride)); break; case SPA_PARAM_Meta: switch (result.index) { case 0: param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamMeta, id, SPA_PARAM_META_type, SPA_POD_Id(SPA_META_Header), SPA_PARAM_META_size, SPA_POD_Int(sizeof(struct spa_meta_header))); break; default: return 0; } break; case SPA_PARAM_IO: switch (result.index) { case 0: param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamIO, id, SPA_PARAM_IO_id, SPA_POD_Id(SPA_IO_Buffers), SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_buffers))); break; case 1: param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamIO, id, SPA_PARAM_IO_id, SPA_POD_Id(SPA_IO_RateMatch), SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_rate_match))); break; default: return 0; } break; case SPA_PARAM_Latency: switch (result.index) { case 0: case 1: { struct spa_latency_info latency = this->latency[result.index]; if (latency.direction == SPA_DIRECTION_OUTPUT) spa_process_latency_info_add(&this->process_latency, &latency); param = spa_latency_build(&b, id, &latency); break; } default: return 0; } break; default: return -ENOENT; } if (spa_pod_filter(&b, &result.param, param, filter) < 0) goto next; spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); if (++count != num) goto next; return 0; } static int clear_buffers(struct state *this, struct port *port) { if (port->n_buffers > 0) { spa_list_init(&port->ready); port->n_buffers = 0; } return 0; } static int port_set_format(void *object, struct port *port, uint32_t flags, const struct spa_pod *format) { struct state *this = object; int err; if (format == NULL) { if (!port->have_format) return 0; spa_log_debug(this->log, "clear format"); port->have_format = false; spa_avb_clear_format(this); clear_buffers(this, port); } else { struct spa_audio_info info = { 0 }; if ((err = spa_format_parse(format, &info.media_type, &info.media_subtype)) < 0) return err; if (info.media_type != SPA_MEDIA_TYPE_audio || info.media_subtype != SPA_MEDIA_SUBTYPE_raw) return -EINVAL; if (spa_format_audio_raw_parse(format, &info.info.raw) < 0) return -EINVAL; if ((err = spa_avb_set_format(this, &info, flags)) < 0) return err; port->current_format = info; port->have_format = true; } this->info.change_mask |= SPA_NODE_CHANGE_MASK_PROPS; emit_node_info(this, false); port->info.change_mask |= SPA_PORT_CHANGE_MASK_RATE; port->info.rate = SPA_FRACTION(1, this->rate); port->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; if (port->have_format) { port->params[PORT_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_READWRITE); port->params[PORT_Buffers] = SPA_PARAM_INFO(SPA_PARAM_Buffers, SPA_PARAM_INFO_READ); port->params[PORT_Latency].user++; } else { port->params[PORT_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); port->params[PORT_Buffers] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); } emit_port_info(this, port, false); return 0; } static int impl_node_port_set_param(void *object, enum spa_direction direction, uint32_t port_id, uint32_t id, uint32_t flags, const struct spa_pod *param) { struct state *this = object; struct port *port; int res = 0; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); port = GET_PORT(this, direction, port_id); switch (id) { case SPA_PARAM_Format: res = port_set_format(this, port, flags, param); break; case SPA_PARAM_Latency: { struct spa_latency_info info; if (param == NULL) info = SPA_LATENCY_INFO(SPA_DIRECTION_REVERSE(direction)); else if ((res = spa_latency_parse(param, &info)) < 0) return res; if (direction == info.direction) return -EINVAL; this->latency[info.direction] = info; port->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; port->params[PORT_Latency].user++; emit_port_info(this, port, false); break; } default: res = -ENOENT; break; } return res; } static int impl_node_port_use_buffers(void *object, enum spa_direction direction, uint32_t port_id, uint32_t flags, struct spa_buffer **buffers, uint32_t n_buffers) { struct state *this = object; struct port *port; uint32_t i; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); port = GET_PORT(this, direction, port_id); spa_log_debug(this->log, "%p: use %d buffers", this, n_buffers); if (port->n_buffers > 0) { spa_avb_pause(this); clear_buffers(this, port); } if (n_buffers > 0 && !port->have_format) return -EIO; if (n_buffers > MAX_BUFFERS) return -ENOSPC; for (i = 0; i < n_buffers; i++) { struct buffer *b = &port->buffers[i]; struct spa_data *d = buffers[i]->datas; b->buf = buffers[i]; b->id = i; b->flags = BUFFER_FLAG_OUT; b->h = spa_buffer_find_meta_data(b->buf, SPA_META_Header, sizeof(*b->h)); if (d[0].data == NULL) { spa_log_error(this->log, "%p: need mapped memory", this); return -EINVAL; } spa_log_debug(this->log, "%p: %d %p data:%p", this, i, b->buf, d[0].data); } port->n_buffers = n_buffers; return 0; } static int impl_node_port_set_io(void *object, enum spa_direction direction, uint32_t port_id, uint32_t id, void *data, size_t size) { struct state *this = object; struct port *port; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); port = GET_PORT(this, direction, port_id); spa_log_debug(this->log, "%p: io %d %p %zd", this, id, data, size); switch (id) { case SPA_IO_Buffers: port->io = data; break; case SPA_IO_RateMatch: port->rate_match = data; break; default: return -ENOENT; } return 0; } static int impl_node_port_reuse_buffer(void *object, uint32_t port_id, uint32_t buffer_id) { return -ENOTSUP; } static int impl_node_process(void *object) { struct state *this = object; struct port *port; struct spa_io_buffers *io; struct buffer *b; spa_return_val_if_fail(this != NULL, -EINVAL); port = GET_PORT(this, SPA_DIRECTION_OUTPUT, 0); if ((io = port->io) == NULL) return -EIO; spa_log_trace_fp(this->log, "%p: process %d %d/%d %d", this, io->status, io->buffer_id, port->n_buffers, this->following); if (io->status == SPA_STATUS_HAVE_DATA) return SPA_STATUS_HAVE_DATA; if (io->buffer_id < port->n_buffers) { spa_avb_recycle_buffer(this, port, io->buffer_id); io->buffer_id = SPA_ID_INVALID; } if (spa_list_is_empty(&port->ready) && this->following) { spa_avb_read(this); } if (spa_list_is_empty(&port->ready) || !this->following) return SPA_STATUS_OK; b = spa_list_first(&port->ready, struct buffer, link); spa_list_remove(&b->link); SPA_FLAG_SET(b->flags, BUFFER_FLAG_OUT); spa_log_trace_fp(this->log, "%p: dequeue buffer %d", this, b->id); io->buffer_id = b->id; io->status = SPA_STATUS_HAVE_DATA; return SPA_STATUS_HAVE_DATA; } static const struct spa_node_methods impl_node = { SPA_VERSION_NODE_METHODS, .add_listener = impl_node_add_listener, .set_callbacks = impl_node_set_callbacks, .sync = impl_node_sync, .enum_params = impl_node_enum_params, .set_param = impl_node_set_param, .set_io = impl_node_set_io, .send_command = impl_node_send_command, .add_port = impl_node_add_port, .remove_port = impl_node_remove_port, .port_enum_params = impl_node_port_enum_params, .port_set_param = impl_node_port_set_param, .port_use_buffers = impl_node_port_use_buffers, .port_set_io = impl_node_port_set_io, .port_reuse_buffer = impl_node_port_reuse_buffer, .process = impl_node_process, }; static int impl_get_interface(struct spa_handle *handle, const char *type, void **interface) { struct state *this; spa_return_val_if_fail(handle != NULL, -EINVAL); spa_return_val_if_fail(interface != NULL, -EINVAL); this = (struct state *) handle; if (spa_streq(type, SPA_TYPE_INTERFACE_Node)) *interface = &this->node; else return -ENOENT; return 0; } static int impl_clear(struct spa_handle *handle) { struct state *this; spa_return_val_if_fail(handle != NULL, -EINVAL); this = (struct state *) handle; spa_avb_clear(this); return 0; } static size_t impl_get_size(const struct spa_handle_factory *factory, const struct spa_dict *params) { return sizeof(struct state); } static int impl_init(const struct spa_handle_factory *factory, struct spa_handle *handle, const struct spa_dict *info, const struct spa_support *support, uint32_t n_support) { struct state *this; struct port *port; spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(handle != NULL, -EINVAL); handle->get_interface = impl_get_interface; handle->clear = impl_clear; this = (struct state *) handle; this->log = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log); avb_log_topic_init(this->log); this->data_system = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_DataSystem); this->data_loop = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_DataLoop); if (this->data_loop == NULL) { spa_log_error(this->log, "a data loop is needed"); return -EINVAL; } if (this->data_system == NULL) { spa_log_error(this->log, "a data system is needed"); return -EINVAL; } this->node.iface = SPA_INTERFACE_INIT( SPA_TYPE_INTERFACE_Node, SPA_VERSION_NODE, &impl_node, this); spa_hook_list_init(&this->hooks); this->info_all = SPA_NODE_CHANGE_MASK_FLAGS | SPA_NODE_CHANGE_MASK_PROPS | SPA_NODE_CHANGE_MASK_PARAMS; this->info = SPA_NODE_INFO_INIT(); this->info.max_output_ports = 1; this->info.flags = SPA_NODE_FLAG_RT; this->params[NODE_PropInfo] = SPA_PARAM_INFO(SPA_PARAM_PropInfo, SPA_PARAM_INFO_READ); this->params[NODE_Props] = SPA_PARAM_INFO(SPA_PARAM_Props, SPA_PARAM_INFO_READWRITE); this->params[NODE_IO] = SPA_PARAM_INFO(SPA_PARAM_IO, SPA_PARAM_INFO_READ); this->params[NODE_ProcessLatency] = SPA_PARAM_INFO(SPA_PARAM_ProcessLatency, SPA_PARAM_INFO_READWRITE); this->info.params = this->params; this->info.n_params = N_NODE_PARAMS; reset_props(&this->props); port = GET_PORT(this, SPA_DIRECTION_OUTPUT, 0); port->direction = SPA_DIRECTION_OUTPUT; port->info_all = SPA_PORT_CHANGE_MASK_FLAGS | SPA_PORT_CHANGE_MASK_PARAMS; port->info = SPA_PORT_INFO_INIT(); port->info.flags = SPA_PORT_FLAG_LIVE | SPA_PORT_FLAG_PHYSICAL | SPA_PORT_FLAG_TERMINAL; port->params[PORT_EnumFormat] = SPA_PARAM_INFO(SPA_PARAM_EnumFormat, SPA_PARAM_INFO_READ); port->params[PORT_Meta] = SPA_PARAM_INFO(SPA_PARAM_Meta, SPA_PARAM_INFO_READ); port->params[PORT_IO] = SPA_PARAM_INFO(SPA_PARAM_IO, SPA_PARAM_INFO_READ); port->params[PORT_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); port->params[PORT_Buffers] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); port->params[PORT_Latency] = SPA_PARAM_INFO(SPA_PARAM_Latency, SPA_PARAM_INFO_READWRITE); port->info.params = port->params; port->info.n_params = N_PORT_PARAMS; spa_list_init(&port->ready); this->latency[port->direction] = SPA_LATENCY_INFO( port->direction, .min_quantum = 1.0f, .max_quantum = 1.0f); this->latency[SPA_DIRECTION_INPUT] = SPA_LATENCY_INFO(SPA_DIRECTION_INPUT); return spa_avb_init(this, info); } static const struct spa_interface_info impl_interfaces[] = { {SPA_TYPE_INTERFACE_Node,}, }; static int impl_enum_interface_info(const struct spa_handle_factory *factory, const struct spa_interface_info **info, uint32_t *index) { spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(info != NULL, -EINVAL); spa_return_val_if_fail(index != NULL, -EINVAL); switch (*index) { case 0: *info = &impl_interfaces[*index]; break; default: return 0; } (*index)++; return 1; } static const struct spa_dict_item info_items[] = { { SPA_KEY_FACTORY_AUTHOR, "Wim Taymans " }, { SPA_KEY_FACTORY_DESCRIPTION, "Play audio with AVB" }, { SPA_KEY_FACTORY_USAGE, "[]" }, }; static const struct spa_dict info = SPA_DICT_INIT_ARRAY(info_items); const struct spa_handle_factory spa_avb_source_factory = { SPA_VERSION_HANDLE_FACTORY, "avb.pcm.source", &info, impl_get_size, impl_init, impl_enum_interface_info, }; pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/avb/avb-pcm.c000066400000000000000000001046711511204443500252140ustar00rootroot00000000000000/* Spa AVB PCM */ /* SPDX-FileCopyrightText: Copyright © 2022 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "avb-pcm.h" #define TAI_OFFSET (37ULL * SPA_NSEC_PER_SEC) #define TAI_TO_UTC(t) (t - TAI_OFFSET) static int avb_set_param(struct state *state, const char *k, const char *s) { struct props *p = &state->props; int fmt_change = 0; if (spa_streq(k, SPA_KEY_AUDIO_CHANNELS)) { state->default_channels = atoi(s); fmt_change++; } else if (spa_streq(k, SPA_KEY_AUDIO_RATE)) { state->default_rate = atoi(s); fmt_change++; } else if (spa_streq(k, SPA_KEY_AUDIO_FORMAT)) { state->default_format = spa_type_audio_format_from_short_name(s); fmt_change++; } else if (spa_streq(k, SPA_KEY_AUDIO_POSITION)) { spa_audio_parse_position_n(s, strlen(s), state->default_pos.pos, SPA_N_ELEMENTS(state->default_pos.pos), &state->default_pos.channels); fmt_change++; } else if (spa_streq(k, SPA_KEY_AUDIO_LAYOUT)) { spa_audio_parse_layout(s, state->default_pos.pos, SPA_N_ELEMENTS(state->default_pos.pos), &state->default_pos.channels); fmt_change++; } else if (spa_streq(k, SPA_KEY_AUDIO_ALLOWED_RATES)) { state->n_allowed_rates = spa_avb_parse_rates(state->allowed_rates, MAX_RATES, s, strlen(s)); fmt_change++; } else if (spa_streq(k, "avb.ifname")) { snprintf(p->ifname, sizeof(p->ifname), "%s", s); } else if (spa_streq(k, "avb.macaddr")) { parse_addr(p->addr, s); } else if (spa_streq(k, "avb.prio")) { p->prio = atoi(s); } else if (spa_streq(k, "avb.streamid")) { parse_streamid(&p->streamid, s); } else if (spa_streq(k, "avb.mtt")) { p->mtt = atoi(s); } else if (spa_streq(k, "avb.time-uncertainty")) { p->t_uncertainty = atoi(s); } else if (spa_streq(k, "avb.frames-per-pdu")) { p->frames_per_pdu = atoi(s); } else if (spa_streq(k, "avb.ptime-tolerance")) { p->ptime_tolerance = atoi(s); } else if (spa_streq(k, "latency.internal.rate")) { state->process_latency.rate = atoi(s); } else if (spa_streq(k, "latency.internal.ns")) { state->process_latency.ns = atoi(s); } else if (spa_streq(k, "clock.name")) { spa_scnprintf(state->clock_name, sizeof(state->clock_name), "%s", s); } else return 0; if (fmt_change > 0) { struct port *port = &state->ports[0]; port->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; port->params[PORT_EnumFormat].user++; } return 1; } static int position_to_string(struct channel_map *map, char *val, size_t len) { uint32_t i, o = 0; int r; char pos[8]; o += snprintf(val, len, "[ "); for (i = 0; i < map->channels; i++) { r = snprintf(val+o, len-o, "%s%s", i == 0 ? "" : ", ", spa_type_audio_channel_make_short_name(map->pos[i], pos, sizeof(pos), "UNK")); if (r < 0 || o + r >= len) return -ENOSPC; o += r; } if (len > o) o += snprintf(val+o, len-o, " ]"); return 0; } static int uint32_array_to_string(uint32_t *vals, uint32_t n_vals, char *val, size_t len) { uint32_t i, o = 0; int r; o += snprintf(val, len, "[ "); for (i = 0; i < n_vals; i++) { r = snprintf(val+o, len-o, "%s%d", i == 0 ? "" : ", ", vals[i]); if (r < 0 || o + r >= len) return -ENOSPC; o += r; } if (len > o) o += snprintf(val+o, len-o, " ]"); return 0; } struct spa_pod *spa_avb_enum_propinfo(struct state *state, uint32_t idx, struct spa_pod_builder *b) { struct spa_pod *param; struct props *p = &state->props; char tmp[128]; switch (idx) { case 0: param = spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_PropInfo, SPA_PARAM_PropInfo, SPA_PROP_INFO_name, SPA_POD_String(SPA_KEY_AUDIO_CHANNELS), SPA_PROP_INFO_description, SPA_POD_String("Audio Channels"), SPA_PROP_INFO_type, SPA_POD_Int(state->default_channels), SPA_PROP_INFO_params, SPA_POD_Bool(true)); break; case 1: param = spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_PropInfo, SPA_PARAM_PropInfo, SPA_PROP_INFO_name, SPA_POD_String(SPA_KEY_AUDIO_RATE), SPA_PROP_INFO_description, SPA_POD_String("Audio Rate"), SPA_PROP_INFO_type, SPA_POD_Int(state->default_rate), SPA_PROP_INFO_params, SPA_POD_Bool(true)); break; case 2: param = spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_PropInfo, SPA_PARAM_PropInfo, SPA_PROP_INFO_name, SPA_POD_String(SPA_KEY_AUDIO_FORMAT), SPA_PROP_INFO_description, SPA_POD_String("Audio Format"), SPA_PROP_INFO_type, SPA_POD_String( spa_debug_type_find_short_name(spa_type_audio_format, state->default_format)), SPA_PROP_INFO_params, SPA_POD_Bool(true)); break; case 3: { char buf[1024]; position_to_string(&state->default_pos, buf, sizeof(buf)); param = spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_PropInfo, SPA_PARAM_PropInfo, SPA_PROP_INFO_name, SPA_POD_String(SPA_KEY_AUDIO_POSITION), SPA_PROP_INFO_description, SPA_POD_String("Audio Position"), SPA_PROP_INFO_type, SPA_POD_String(buf), SPA_PROP_INFO_params, SPA_POD_Bool(true)); break; } case 4: { char buf[1024]; uint32_array_to_string(state->allowed_rates, state->n_allowed_rates, buf, sizeof(buf)); param = spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_PropInfo, SPA_PARAM_PropInfo, SPA_PROP_INFO_name, SPA_POD_String(SPA_KEY_AUDIO_ALLOWED_RATES), SPA_PROP_INFO_description, SPA_POD_String("Audio Allowed Rates"), SPA_PROP_INFO_type, SPA_POD_String(buf), SPA_PROP_INFO_params, SPA_POD_Bool(true)); break; } case 5: param = spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_PropInfo, SPA_PARAM_PropInfo, SPA_PROP_INFO_name, SPA_POD_String("avb.ifname"), SPA_PROP_INFO_description, SPA_POD_String("The AVB interface name"), SPA_PROP_INFO_type, SPA_POD_Stringn(p->ifname, sizeof(p->ifname)), SPA_PROP_INFO_params, SPA_POD_Bool(true)); break; case 6: format_addr(tmp, sizeof(tmp), p->addr); param = spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_PropInfo, SPA_PARAM_PropInfo, SPA_PROP_INFO_name, SPA_POD_String("avb.macaddr"), SPA_PROP_INFO_description, SPA_POD_String("The AVB MAC address"), SPA_PROP_INFO_type, SPA_POD_String(tmp), SPA_PROP_INFO_params, SPA_POD_Bool(true)); break; case 7: param = spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_PropInfo, SPA_PARAM_PropInfo, SPA_PROP_INFO_name, SPA_POD_String("avb.prio"), SPA_PROP_INFO_description, SPA_POD_String("The AVB stream priority"), SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Int(p->prio, 0, INT32_MAX), SPA_PROP_INFO_params, SPA_POD_Bool(true)); break; case 8: format_streamid(tmp, sizeof(tmp), p->streamid); param = spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_PropInfo, SPA_PARAM_PropInfo, SPA_PROP_INFO_name, SPA_POD_String("avb.streamid"), SPA_PROP_INFO_description, SPA_POD_String("The AVB stream id"), SPA_PROP_INFO_type, SPA_POD_String(tmp), SPA_PROP_INFO_params, SPA_POD_Bool(true)); break; case 9: param = spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_PropInfo, SPA_PARAM_PropInfo, SPA_PROP_INFO_name, SPA_POD_String("avb.mtt"), SPA_PROP_INFO_description, SPA_POD_String("The AVB mtt"), SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Int(p->mtt, 0, INT32_MAX), SPA_PROP_INFO_params, SPA_POD_Bool(true)); break; case 10: param = spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_PropInfo, SPA_PARAM_PropInfo, SPA_PROP_INFO_name, SPA_POD_String("avb.time-uncertainty"), SPA_PROP_INFO_description, SPA_POD_String("The AVB time uncertainty"), SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Int(p->t_uncertainty, 0, INT32_MAX), SPA_PROP_INFO_params, SPA_POD_Bool(true)); break; case 11: param = spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_PropInfo, SPA_PARAM_PropInfo, SPA_PROP_INFO_name, SPA_POD_String("avb.frames-per-pdu"), SPA_PROP_INFO_description, SPA_POD_String("The AVB frames per packet"), SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Int(p->frames_per_pdu, 0, INT32_MAX), SPA_PROP_INFO_params, SPA_POD_Bool(true)); break; case 12: param = spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_PropInfo, SPA_PARAM_PropInfo, SPA_PROP_INFO_name, SPA_POD_String("avb.ptime-tolerance"), SPA_PROP_INFO_description, SPA_POD_String("The AVB packet tolerance"), SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Int(p->ptime_tolerance, 0, INT32_MAX), SPA_PROP_INFO_params, SPA_POD_Bool(true)); break; case 13: param = spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_PropInfo, SPA_PARAM_PropInfo, SPA_PROP_INFO_name, SPA_POD_String("latency.internal.rate"), SPA_PROP_INFO_description, SPA_POD_String("Internal latency in samples"), SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Int(state->process_latency.rate, 0, 65536), SPA_PROP_INFO_params, SPA_POD_Bool(true)); break; case 14: param = spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_PropInfo, SPA_PARAM_PropInfo, SPA_PROP_INFO_name, SPA_POD_String("latency.internal.ns"), SPA_PROP_INFO_description, SPA_POD_String("Internal latency in nanoseconds"), SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Long(state->process_latency.ns, 0, 2 * SPA_NSEC_PER_SEC), SPA_PROP_INFO_params, SPA_POD_Bool(true)); break; case 15: param = spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_PropInfo, SPA_PARAM_PropInfo, SPA_PROP_INFO_name, SPA_POD_String("clock.name"), SPA_PROP_INFO_description, SPA_POD_String("The name of the clock"), SPA_PROP_INFO_type, SPA_POD_String(state->clock_name), SPA_PROP_INFO_params, SPA_POD_Bool(true)); break; default: return NULL; } return param; } int spa_avb_add_prop_params(struct state *state, struct spa_pod_builder *b) { struct props *p = &state->props; struct spa_pod_frame f[1]; char buf[1024]; spa_pod_builder_prop(b, SPA_PROP_params, 0); spa_pod_builder_push_struct(b, &f[0]); spa_pod_builder_string(b, SPA_KEY_AUDIO_CHANNELS); spa_pod_builder_int(b, state->default_channels); spa_pod_builder_string(b, SPA_KEY_AUDIO_RATE); spa_pod_builder_int(b, state->default_rate); spa_pod_builder_string(b, SPA_KEY_AUDIO_FORMAT); spa_pod_builder_string(b, spa_debug_type_find_short_name(spa_type_audio_format, state->default_format)); position_to_string(&state->default_pos, buf, sizeof(buf)); spa_pod_builder_string(b, SPA_KEY_AUDIO_POSITION); spa_pod_builder_string(b, buf); uint32_array_to_string(state->allowed_rates, state->n_allowed_rates, buf, sizeof(buf)); spa_pod_builder_string(b, SPA_KEY_AUDIO_ALLOWED_RATES); spa_pod_builder_string(b, buf); spa_pod_builder_string(b, "avb.ifname"); spa_pod_builder_string(b, p->ifname); format_addr(buf, sizeof(buf), p->addr); spa_pod_builder_string(b, "avb.macadr"); spa_pod_builder_string(b, buf); spa_pod_builder_string(b, "avb.prio"); spa_pod_builder_int(b, p->prio); format_streamid(buf, sizeof(buf), p->streamid); spa_pod_builder_string(b, "avb.streamid"); spa_pod_builder_string(b, buf); spa_pod_builder_string(b, "avb.mtt"); spa_pod_builder_int(b, p->mtt); spa_pod_builder_string(b, "avb.time-uncertainty"); spa_pod_builder_int(b, p->t_uncertainty); spa_pod_builder_string(b, "avb.frames-per-pdu"); spa_pod_builder_int(b, p->frames_per_pdu); spa_pod_builder_string(b, "avb.ptime-tolerance"); spa_pod_builder_int(b, p->ptime_tolerance); spa_pod_builder_string(b, "latency.internal.rate"); spa_pod_builder_int(b, state->process_latency.rate); spa_pod_builder_string(b, "latency.internal.ns"); spa_pod_builder_long(b, state->process_latency.ns); spa_pod_builder_string(b, "clock.name"); spa_pod_builder_string(b, state->clock_name); spa_pod_builder_pop(b, &f[0]); return 0; } int spa_avb_parse_prop_params(struct state *state, struct spa_pod *params) { struct spa_pod_parser prs; struct spa_pod_frame f; int changed = 0; if (params == NULL) return 0; spa_pod_parser_pod(&prs, params); if (spa_pod_parser_push_struct(&prs, &f) < 0) return 0; while (true) { const char *name; struct spa_pod *pod; char value[512]; if (spa_pod_parser_get_string(&prs, &name) < 0) break; if (spa_pod_parser_get_pod(&prs, &pod) < 0) break; if (spa_pod_is_string(pod)) { spa_pod_copy_string(pod, sizeof(value), value); } else if (spa_pod_is_int(pod)) { snprintf(value, sizeof(value), "%d", SPA_POD_VALUE(struct spa_pod_int, pod)); } else if (spa_pod_is_long(pod)) { snprintf(value, sizeof(value), "%"PRIi64, SPA_POD_VALUE(struct spa_pod_long, pod)); } else if (spa_pod_is_bool(pod)) { snprintf(value, sizeof(value), "%s", SPA_POD_VALUE(struct spa_pod_bool, pod) ? "true" : "false"); } else continue; spa_log_info(state->log, "key:'%s' val:'%s'", name, value); avb_set_param(state, name, value); changed++; } if (changed > 0) { state->info.change_mask |= SPA_NODE_CHANGE_MASK_PARAMS; state->params[NODE_Props].user++; } return changed; } int spa_avb_init(struct state *state, const struct spa_dict *info) { uint32_t i; state->quantum_limit = 8192; for (i = 0; info && i < info->n_items; i++) { const char *k = info->items[i].key; const char *s = info->items[i].value; if (spa_streq(k, "clock.quantum-limit")) { spa_atou32(s, &state->quantum_limit, 0); } else { avb_set_param(state, k, s); } } state->ringbuffer_size = state->quantum_limit * 64; state->ringbuffer_data = calloc(1, state->ringbuffer_size * 4); spa_ringbuffer_init(&state->ring); return 0; } int spa_avb_clear(struct state *state) { return 0; } static int spa_format_to_aaf(uint32_t format) { switch(format) { case SPA_AUDIO_FORMAT_F32_BE: return SPA_AVBTP_AAF_FORMAT_FLOAT_32BIT; case SPA_AUDIO_FORMAT_S32_BE: return SPA_AVBTP_AAF_FORMAT_INT_32BIT; case SPA_AUDIO_FORMAT_S24_BE: return SPA_AVBTP_AAF_FORMAT_INT_24BIT; case SPA_AUDIO_FORMAT_S16_BE: return SPA_AVBTP_AAF_FORMAT_INT_16BIT; default: return SPA_AVBTP_AAF_FORMAT_USER; } } static int calc_frame_size(uint32_t format) { switch(format) { case SPA_AUDIO_FORMAT_F32_BE: case SPA_AUDIO_FORMAT_S32_BE: return 4; case SPA_AUDIO_FORMAT_S24_BE: return 3; case SPA_AUDIO_FORMAT_S16_BE: return 2; default: return 0; } } static int spa_rate_to_aaf(uint32_t rate) { switch(rate) { case 8000: return SPA_AVBTP_AAF_PCM_NSR_8KHZ; case 16000: return SPA_AVBTP_AAF_PCM_NSR_16KHZ; case 24000: return SPA_AVBTP_AAF_PCM_NSR_24KHZ; case 32000: return SPA_AVBTP_AAF_PCM_NSR_32KHZ; case 44100: return SPA_AVBTP_AAF_PCM_NSR_44_1KHZ; case 48000: return SPA_AVBTP_AAF_PCM_NSR_48KHZ; case 88200: return SPA_AVBTP_AAF_PCM_NSR_88_2KHZ; case 96000: return SPA_AVBTP_AAF_PCM_NSR_96KHZ; case 176400: return SPA_AVBTP_AAF_PCM_NSR_176_4KHZ; case 192000: return SPA_AVBTP_AAF_PCM_NSR_192KHZ; default: return SPA_AVBTP_AAF_PCM_NSR_USER; } } int spa_avb_enum_format(struct state *state, int seq, uint32_t start, uint32_t num, const struct spa_pod *filter) { uint8_t buffer[4096]; struct spa_pod_builder b = { 0 }; struct spa_pod_frame f[2]; struct spa_pod *fmt; int res = 0; struct spa_result_node_params result; uint32_t count = 0; result.id = SPA_PARAM_EnumFormat; result.next = start; next: result.index = result.next++; if (result.index > 0) return 0; spa_pod_builder_init(&b, buffer, sizeof(buffer)); spa_pod_builder_push_object(&b, &f[0], SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat); spa_pod_builder_add(&b, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_audio), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), 0); spa_pod_builder_prop(&b, SPA_FORMAT_AUDIO_format, 0); if (state->default_format != 0) { spa_pod_builder_id(&b, state->default_format); } else { spa_pod_builder_push_choice(&b, &f[1], SPA_CHOICE_Enum, 0); spa_pod_builder_id(&b, SPA_AUDIO_FORMAT_F32_BE); spa_pod_builder_id(&b, SPA_AUDIO_FORMAT_F32_BE); spa_pod_builder_id(&b, SPA_AUDIO_FORMAT_S32_BE); spa_pod_builder_id(&b, SPA_AUDIO_FORMAT_S24_BE); spa_pod_builder_id(&b, SPA_AUDIO_FORMAT_S16_BE); spa_pod_builder_pop(&b, &f[1]); } spa_pod_builder_prop(&b, SPA_FORMAT_AUDIO_rate, 0); if (state->default_rate != 0) { spa_pod_builder_int(&b, state->default_rate); } else { spa_pod_builder_push_choice(&b, &f[1], SPA_CHOICE_Enum, 0); spa_pod_builder_int(&b, 48000); spa_pod_builder_int(&b, 8000); spa_pod_builder_int(&b, 16000); spa_pod_builder_int(&b, 24000); spa_pod_builder_int(&b, 32000); spa_pod_builder_int(&b, 44100); spa_pod_builder_int(&b, 48000); spa_pod_builder_int(&b, 88200); spa_pod_builder_int(&b, 96000); spa_pod_builder_int(&b, 176400); spa_pod_builder_int(&b, 192000); spa_pod_builder_pop(&b, &f[1]); } spa_pod_builder_prop(&b, SPA_FORMAT_AUDIO_channels, 0); if (state->default_channels != 0) { spa_pod_builder_int(&b, state->default_channels); } else { spa_pod_builder_push_choice(&b, &f[1], SPA_CHOICE_Range, 0); spa_pod_builder_int(&b, 8); spa_pod_builder_int(&b, 2); spa_pod_builder_int(&b, 32); spa_pod_builder_pop(&b, &f[1]); } fmt = spa_pod_builder_pop(&b, &f[0]); if (spa_pod_filter(&b, &result.param, fmt, filter) < 0) goto next; spa_node_emit_result(&state->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); if (++count != num) goto next; return res; } static int setup_socket(struct state *state) { int fd, res; struct ifreq req; struct props *p = &state->props; fd = socket(AF_PACKET, SOCK_DGRAM|SOCK_NONBLOCK, htons(ETH_P_TSN)); if (fd < 0) { spa_log_error(state->log, "socket() failed: %m"); return -errno; } snprintf(req.ifr_name, sizeof(req.ifr_name), "%s", p->ifname); res = ioctl(fd, SIOCGIFINDEX, &req); if (res < 0) { spa_log_error(state->log, "SIOCGIFINDEX %s failed: %m", p->ifname); res = -errno; goto error_close; } state->sock_addr.sll_family = AF_PACKET; state->sock_addr.sll_protocol = htons(ETH_P_TSN); state->sock_addr.sll_halen = ETH_ALEN; state->sock_addr.sll_ifindex = req.ifr_ifindex; memcpy(&state->sock_addr.sll_addr, p->addr, ETH_ALEN); if (state->ports[0].direction == SPA_DIRECTION_INPUT) { struct sock_txtime txtime_cfg; res = setsockopt(fd, SOL_SOCKET, SO_PRIORITY, &p->prio, sizeof(p->prio)); if (res < 0) { spa_log_error(state->log, "setsockopt(SO_PRIORITY %d) failed: %m", p->prio); res = -errno; goto error_close; } txtime_cfg.clockid = CLOCK_TAI; txtime_cfg.flags = 0; res = setsockopt(fd, SOL_SOCKET, SO_TXTIME, &txtime_cfg, sizeof(txtime_cfg)); if (res < 0) { spa_log_error(state->log, "setsockopt(SO_TXTIME) failed: %m"); res = -errno; goto error_close; } } else { struct packet_mreq mreq = { 0 }; res = bind(fd, (struct sockaddr *) &state->sock_addr, sizeof(state->sock_addr)); if (res < 0) { spa_log_error(state->log, "bind() failed: %m"); res = -errno; goto error_close; } mreq.mr_ifindex = req.ifr_ifindex; mreq.mr_type = PACKET_MR_MULTICAST; mreq.mr_alen = ETH_ALEN; memcpy(&mreq.mr_address, p->addr, ETH_ALEN); res = setsockopt(fd, SOL_PACKET, PACKET_ADD_MEMBERSHIP, &mreq, sizeof(struct packet_mreq)); if (res < 0) { spa_log_error(state->log, "setsockopt(ADD_MEMBERSHIP) failed: %m"); res = -errno; goto error_close; } } state->sockfd = fd; return 0; error_close: close(fd); return res; } static int setup_packet(struct state *state, struct spa_audio_info *fmt) { struct spa_avbtp_packet_aaf *pdu; struct props *p = &state->props; ssize_t payload_size, hdr_size, pdu_size; hdr_size = sizeof(*pdu); payload_size = state->stride * p->frames_per_pdu; pdu_size = hdr_size + payload_size; if ((pdu = calloc(1, pdu_size)) == NULL) return -errno; SPA_AVBTP_PACKET_AAF_SET_SUBTYPE(pdu, SPA_AVBTP_SUBTYPE_AAF); if (state->ports[0].direction == SPA_DIRECTION_INPUT) { SPA_AVBTP_PACKET_AAF_SET_SV(pdu, 1); SPA_AVBTP_PACKET_AAF_SET_STREAM_ID(pdu, p->streamid); SPA_AVBTP_PACKET_AAF_SET_TV(pdu, 1); SPA_AVBTP_PACKET_AAF_SET_FORMAT(pdu, spa_format_to_aaf(state->format)); SPA_AVBTP_PACKET_AAF_SET_NSR(pdu, spa_rate_to_aaf(state->rate)); SPA_AVBTP_PACKET_AAF_SET_CHAN_PER_FRAME(pdu, state->channels); SPA_AVBTP_PACKET_AAF_SET_BIT_DEPTH(pdu, calc_frame_size(state->format)*8); SPA_AVBTP_PACKET_AAF_SET_DATA_LEN(pdu, payload_size); SPA_AVBTP_PACKET_AAF_SET_SP(pdu, SPA_AVBTP_AAF_PCM_SP_NORMAL); } state->pdu = pdu; state->hdr_size = hdr_size; state->payload_size = payload_size; state->pdu_size = pdu_size; return 0; } static int setup_msg(struct state *state) { state->iov[0].iov_base = state->pdu; state->iov[0].iov_len = state->hdr_size; state->iov[1].iov_base = state->pdu->payload; state->iov[1].iov_len = state->payload_size; state->iov[2].iov_base = state->pdu->payload; state->iov[2].iov_len = 0; state->msg.msg_name = &state->sock_addr; state->msg.msg_namelen = sizeof(state->sock_addr); state->msg.msg_iov = state->iov; state->msg.msg_iovlen = 3; state->msg.msg_control = state->control; state->msg.msg_controllen = sizeof(state->control); state->cmsg = CMSG_FIRSTHDR(&state->msg); state->cmsg->cmsg_level = SOL_SOCKET; state->cmsg->cmsg_type = SCM_TXTIME; state->cmsg->cmsg_len = CMSG_LEN(sizeof(__u64)); return 0; } int spa_avb_clear_format(struct state *state) { close(state->sockfd); close(state->timerfd); free(state->pdu); return 0; } int spa_avb_set_format(struct state *state, struct spa_audio_info *fmt, uint32_t flags) { int res, frame_size; struct props *p = &state->props; frame_size = calc_frame_size(fmt->info.raw.format); if (frame_size == 0) return -EINVAL; if (fmt->info.raw.rate == 0 || fmt->info.raw.channels == 0) return -EINVAL; state->format = fmt->info.raw.format; state->rate = fmt->info.raw.rate; state->channels = fmt->info.raw.channels; state->blocks = 1; state->stride = state->channels * frame_size; if ((res = setup_socket(state)) < 0) return res; if ((res = spa_system_timerfd_create(state->data_system, CLOCK_REALTIME, SPA_FD_CLOEXEC | SPA_FD_NONBLOCK)) < 0) goto error_close_sockfd; state->timerfd = res; if ((res = setup_packet(state, fmt)) < 0) return res; if ((res = setup_msg(state)) < 0) return res; state->pdu_period = SPA_NSEC_PER_SEC * p->frames_per_pdu / state->rate; return 0; error_close_sockfd: close(state->sockfd); return res; } void spa_avb_recycle_buffer(struct state *this, struct port *port, uint32_t buffer_id) { struct buffer *b = &port->buffers[buffer_id]; if (SPA_FLAG_IS_SET(b->flags, BUFFER_FLAG_OUT)) { spa_log_trace_fp(this->log, "%p: recycle buffer %u", this, buffer_id); spa_list_append(&port->free, &b->link); SPA_FLAG_CLEAR(b->flags, BUFFER_FLAG_OUT); } } static void reset_buffers(struct state *this, struct port *port) { uint32_t i; spa_list_init(&port->free); spa_list_init(&port->ready); for (i = 0; i < port->n_buffers; i++) { struct buffer *b = &port->buffers[i]; if (port->direction == SPA_DIRECTION_INPUT) { SPA_FLAG_SET(b->flags, BUFFER_FLAG_OUT); spa_node_call_reuse_buffer(&this->callbacks, 0, b->id); } else { spa_list_append(&port->free, &b->link); SPA_FLAG_CLEAR(b->flags, BUFFER_FLAG_OUT); } } } static inline bool is_pdu_valid(struct state *state) { uint8_t seq_num; seq_num = SPA_AVBTP_PACKET_AAF_GET_SEQ_NUM(state->pdu); if (state->prev_seq != 0 && (uint8_t)(state->prev_seq + 1) != seq_num) { spa_log_warn(state->log, "dropped packets %d != %d", state->prev_seq + 1, seq_num); } state->prev_seq = seq_num; return true; } static inline void set_iovec(struct spa_ringbuffer *rbuf, void *buffer, uint32_t size, uint32_t offset, struct iovec *iov, uint32_t len) { iov[0].iov_len = SPA_MIN(len, size - offset); iov[0].iov_base = SPA_PTROFF(buffer, offset, void); iov[1].iov_len = len - iov[0].iov_len; iov[1].iov_base = buffer; } static void avb_on_socket_event(struct spa_source *source) { struct state *state = source->data; ssize_t n; int32_t filled; uint32_t subtype, index; struct spa_avbtp_packet_aaf *pdu = state->pdu; bool overrun = false; filled = spa_ringbuffer_get_write_index(&state->ring, &index); overrun = filled > (int32_t) state->ringbuffer_size; if (overrun) { state->iov[1].iov_base = state->pdu->payload; state->iov[1].iov_len = state->payload_size; state->iov[2].iov_len = 0; } else { set_iovec(&state->ring, state->ringbuffer_data, state->ringbuffer_size, index % state->ringbuffer_size, &state->iov[1], state->payload_size); } n = recvmsg(state->sockfd, &state->msg, 0); if (n < 0) { spa_log_error(state->log, "recv() failed: %m"); return; } if (n != (ssize_t)state->pdu_size) { spa_log_error(state->log, "AVB packet dropped: Invalid size"); return; } subtype = SPA_AVBTP_PACKET_AAF_GET_SUBTYPE(pdu); if (subtype != SPA_AVBTP_SUBTYPE_AAF) { spa_log_error(state->log, "non supported subtype %d", subtype); return; } if (!is_pdu_valid(state)) { spa_log_error(state->log, "AAF PDU invalid"); return; } if (overrun) { spa_log_warn(state->log, "overrun %d", filled); return; } index += state->payload_size; spa_ringbuffer_write_update(&state->ring, index); } static void set_timeout(struct state *state, uint64_t next_time) { struct itimerspec ts; uint64_t time_utc; spa_log_trace(state->log, "set timeout %"PRIu64, next_time); time_utc = next_time > TAI_OFFSET ? TAI_TO_UTC(next_time) : 0; ts.it_value.tv_sec = time_utc / SPA_NSEC_PER_SEC; ts.it_value.tv_nsec = time_utc % SPA_NSEC_PER_SEC; ts.it_interval.tv_sec = 0; ts.it_interval.tv_nsec = 0; spa_system_timerfd_settime(state->data_system, state->timer_source.fd, SPA_FD_TIMER_ABSTIME, &ts, NULL); } static void update_position(struct state *state) { if (state->position) { state->duration = state->position->clock.duration; state->rate_denom = state->position->clock.rate.denom; } else { state->duration = 1024; state->rate_denom = state->rate; } } static int flush_write(struct state *state, uint64_t current_time) { int32_t avail, wanted; uint32_t index; uint64_t ptime, txtime; int pdu_count; struct props *p = &state->props; struct spa_avbtp_packet_aaf *pdu = state->pdu; ssize_t n; avail = spa_ringbuffer_get_read_index(&state->ring, &index); wanted = state->duration * state->stride; if (avail < wanted) { spa_log_warn(state->log, "underrun %d < %d", avail, wanted); return -EPIPE; } pdu_count = state->duration / p->frames_per_pdu; txtime = current_time + p->t_uncertainty; ptime = txtime + p->mtt; while (pdu_count--) { *(__u64 *)CMSG_DATA(state->cmsg) = txtime; set_iovec(&state->ring, state->ringbuffer_data, state->ringbuffer_size, index % state->ringbuffer_size, &state->iov[1], state->payload_size); SPA_AVBTP_PACKET_AAF_SET_SEQ_NUM(pdu, state->pdu_seq++); SPA_AVBTP_PACKET_AAF_SET_TIMESTAMP(pdu, ptime); n = sendmsg(state->sockfd, &state->msg, MSG_NOSIGNAL); if (n < 0 || n != (ssize_t)state->pdu_size) { spa_log_error(state->log, "sendmdg() failed: %m"); } txtime += state->pdu_period; ptime += state->pdu_period; index += state->payload_size; } spa_ringbuffer_read_update(&state->ring, index); return 0; } int spa_avb_write(struct state *state) { int32_t filled; uint32_t index, to_write; struct port *port = &state->ports[0]; update_position(state); filled = spa_ringbuffer_get_write_index(&state->ring, &index); if (filled < 0) { spa_log_warn(state->log, "underrun %d", filled); } else if (filled > (int32_t)state->ringbuffer_size) { spa_log_warn(state->log, "overrun %d", filled); } to_write = state->ringbuffer_size - filled; while (!spa_list_is_empty(&port->ready) && to_write > 0) { size_t n_bytes; struct buffer *b; struct spa_data *d; uint32_t offs, avail, size; b = spa_list_first(&port->ready, struct buffer, link); d = b->buf->datas; offs = SPA_MIN(d[0].chunk->offset + port->ready_offset, d[0].maxsize); size = SPA_MIN(d[0].chunk->size, d[0].maxsize - offs); avail = size - offs; n_bytes = SPA_MIN(avail, to_write); if (n_bytes == 0) break; spa_ringbuffer_write_data(&state->ring, state->ringbuffer_data, state->ringbuffer_size, index % state->ringbuffer_size, SPA_PTROFF(d[0].data, offs, void), n_bytes); port->ready_offset += n_bytes; if (port->ready_offset >= size || avail == 0) { spa_list_remove(&b->link); SPA_FLAG_SET(b->flags, BUFFER_FLAG_OUT); port->io->buffer_id = b->id; spa_log_trace_fp(state->log, "%p: reuse buffer %u", state, b->id); spa_node_call_reuse_buffer(&state->callbacks, 0, b->id); port->ready_offset = 0; } to_write -= n_bytes; index += n_bytes; } spa_ringbuffer_write_update(&state->ring, index); if (state->following) flush_write(state, state->position->clock.nsec); return 0; } static int handle_play(struct state *state, uint64_t current_time) { update_position(state); flush_write(state, current_time); spa_node_call_ready(&state->callbacks, SPA_STATUS_NEED_DATA); return 0; } int spa_avb_read(struct state *state) { int32_t avail, wanted; uint32_t index; struct port *port = &state->ports[0]; struct buffer *b; struct spa_data *d; uint32_t n_bytes; update_position(state); avail = spa_ringbuffer_get_read_index(&state->ring, &index); wanted = state->duration * state->stride; if (spa_list_is_empty(&port->free)) { spa_log_warn(state->log, "out of buffers"); return -EPIPE; } b = spa_list_first(&port->free, struct buffer, link); d = b->buf->datas; n_bytes = SPA_MIN(d[0].maxsize, (uint32_t)wanted); if (avail < wanted) { spa_log_warn(state->log, "capture underrun %d < %d", avail, wanted); memset(d[0].data, 0, n_bytes); } else { spa_ringbuffer_read_data(&state->ring, state->ringbuffer_data, state->ringbuffer_size, index % state->ringbuffer_size, d[0].data, n_bytes); index += n_bytes; spa_ringbuffer_read_update(&state->ring, index); } d[0].chunk->offset = 0; d[0].chunk->size = n_bytes; d[0].chunk->stride = state->stride; d[0].chunk->flags = 0; spa_list_remove(&b->link); spa_list_append(&port->ready, &b->link); return 0; } static int handle_capture(struct state *state, uint64_t current_time) { struct port *port = &state->ports[0]; struct spa_io_buffers *io; struct buffer *b; spa_avb_read(state); if (spa_list_is_empty(&port->ready)) return 0; io = port->io; if (io != NULL && (io->status != SPA_STATUS_HAVE_DATA || port->rate_match != NULL)) { if (io->buffer_id < port->n_buffers) spa_avb_recycle_buffer(state, port, io->buffer_id); b = spa_list_first(&port->ready, struct buffer, link); spa_list_remove(&b->link); SPA_FLAG_SET(b->flags, BUFFER_FLAG_OUT); io->buffer_id = b->id; io->status = SPA_STATUS_HAVE_DATA; spa_log_trace_fp(state->log, "%p: output buffer:%d", state, b->id); } spa_node_call_ready(&state->callbacks, SPA_STATUS_HAVE_DATA); return 0; } static void avb_on_timeout_event(struct spa_source *source) { struct state *state = source->data; uint64_t expirations, current_time, duration; struct spa_fraction rate; int res; spa_log_trace(state->log, "timeout"); if ((res = spa_system_timerfd_read(state->data_system, state->timer_source.fd, &expirations)) < 0) { if (res != -EAGAIN) spa_log_error(state->log, "read timerfd: %s", spa_strerror(res)); return; } current_time = state->next_time; if (SPA_LIKELY(state->position)) { duration = state->position->clock.target_duration; rate = state->position->clock.target_rate; } else { duration = 1024; rate = SPA_FRACTION(1, 48000); } state->next_time = current_time + duration * SPA_NSEC_PER_SEC / rate.denom; if (state->ports[0].direction == SPA_DIRECTION_INPUT) handle_play(state, current_time); else handle_capture(state, current_time); if (SPA_LIKELY(state->clock)) { state->clock->nsec = current_time; state->clock->rate = rate; state->clock->position += state->clock->duration; state->clock->duration = duration; state->clock->delay = 0; state->clock->rate_diff = 1.0; state->clock->next_nsec = state->next_time; } set_timeout(state, state->next_time); } static int set_timers(struct state *state) { struct timespec now; int res; if ((res = spa_system_clock_gettime(state->data_system, CLOCK_TAI, &now)) < 0) return res; state->next_time = SPA_TIMESPEC_TO_NSEC(&now); if (state->following) { set_timeout(state, 0); } else { set_timeout(state, state->next_time); } return 0; } static inline bool is_following(struct state *state) { return state->position && state->clock && state->position->clock.id != state->clock->id; } static int do_reassign_follower(struct spa_loop *loop, bool async, uint32_t seq, const void *data, size_t size, void *user_data) { struct state *state = user_data; spa_dll_init(&state->dll); set_timers(state); return 0; } int spa_avb_reassign_follower(struct state *state) { bool following, freewheel; if (!state->started) return 0; following = is_following(state); if (following != state->following) { spa_log_debug(state->log, "%p: reassign follower %d->%d", state, state->following, following); state->following = following; spa_loop_locked(state->data_loop, do_reassign_follower, 0, NULL, 0, state); } freewheel = state->position && SPA_FLAG_IS_SET(state->position->clock.flags, SPA_IO_CLOCK_FLAG_FREEWHEEL); if (state->freewheel != freewheel) { spa_log_debug(state->log, "%p: freewheel %d->%d", state, state->freewheel, freewheel); state->freewheel = freewheel; } return 0; } int spa_avb_start(struct state *state) { if (state->started) return 0; update_position(state); spa_dll_init(&state->dll); state->max_error = (256.0 * state->rate) / state->rate_denom; state->following = is_following(state); state->timer_source.func = avb_on_timeout_event; state->timer_source.data = state; state->timer_source.fd = state->timerfd; state->timer_source.mask = SPA_IO_IN; state->timer_source.rmask = 0; spa_loop_add_source(state->data_loop, &state->timer_source); state->pdu_seq = 0; if (state->ports[0].direction == SPA_DIRECTION_OUTPUT) { state->sock_source.func = avb_on_socket_event; state->sock_source.data = state; state->sock_source.fd = state->sockfd; state->sock_source.mask = SPA_IO_IN; state->sock_source.rmask = 0; spa_loop_add_source(state->data_loop, &state->sock_source); } reset_buffers(state, &state->ports[0]); set_timers(state); state->started = true; return 0; } static int do_remove_source(struct spa_loop *loop, bool async, uint32_t seq, const void *data, size_t size, void *user_data) { struct state *state = user_data; spa_loop_remove_source(state->data_loop, &state->timer_source); set_timeout(state, 0); if (state->ports[0].direction == SPA_DIRECTION_OUTPUT) spa_loop_remove_source(state->data_loop, &state->sock_source); return 0; } int spa_avb_pause(struct state *state) { if (!state->started) return 0; spa_log_debug(state->log, "%p: pause", state); spa_loop_locked(state->data_loop, do_remove_source, 0, NULL, 0, state); state->started = false; return 0; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/avb/avb-pcm.h000066400000000000000000000153071511204443500252160ustar00rootroot00000000000000/* Spa AVB PCM */ /* SPDX-FileCopyrightText: Copyright © 2022 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_AVB_PCM_H #define SPA_AVB_PCM_H #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "avb.h" #ifdef __cplusplus extern "C" { #endif #define MAX_RATES 16 #define DEFAULT_IFNAME "eth0" #define DEFAULT_ADDR "01:AA:AA:AA:AA:AA" #define DEFAULT_PRIO 0 #define DEFAULT_STREAMID "AA:BB:CC:DD:EE:FF:0000" #define DEFAULT_MTT 5000000 #define DEFAULT_TU 1000000 #define DEFAULT_FRAMES_PER_PDU 8 #define DEFAULT_PERIOD 1024u #define DEFAULT_RATE 48000u #define DEFAULT_CHANNELS 8u struct props { char ifname[IFNAMSIZ]; unsigned char addr[ETH_ALEN]; int prio; uint64_t streamid; int mtt; int t_uncertainty; uint32_t frames_per_pdu; int ptime_tolerance; }; static inline int parse_addr(unsigned char addr[ETH_ALEN], const char *str) { unsigned char ad[ETH_ALEN]; if (sscanf(str, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx", &ad[0], &ad[1], &ad[2], &ad[3], &ad[4], &ad[5]) != 6) return -EINVAL; memcpy(addr, ad, sizeof(ad)); return 0; } static inline char *format_addr(char *str, size_t size, const unsigned char addr[ETH_ALEN]) { snprintf(str, size, "%02x:%02x:%02x:%02x:%02x:%02x", addr[0], addr[1], addr[2], addr[3], addr[4], addr[5]); return str; } static inline int parse_streamid(uint64_t *streamid, const char *str) { unsigned char addr[6]; unsigned short unique_id; if (sscanf(str, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx:%hx", &addr[0], &addr[1], &addr[2], &addr[3], &addr[4], &addr[5], &unique_id) != 7) return -EINVAL; *streamid = (uint64_t) addr[0] << 56 | (uint64_t) addr[1] << 48 | (uint64_t) addr[2] << 40 | (uint64_t) addr[3] << 32 | (uint64_t) addr[4] << 24 | (uint64_t) addr[5] << 16 | unique_id; return 0; } static inline char *format_streamid(char *str, size_t size, const uint64_t streamid) { snprintf(str, size, "%02x:%02x:%02x:%02x:%02x:%02x:%04x", (uint8_t)(streamid >> 56), (uint8_t)(streamid >> 48), (uint8_t)(streamid >> 40), (uint8_t)(streamid >> 32), (uint8_t)(streamid >> 24), (uint8_t)(streamid >> 16), (uint16_t)(streamid)); return str; } #define MAX_CHANNELS SPA_AUDIO_MAX_CHANNELS #define MAX_BUFFERS 32 struct buffer { uint32_t id; #define BUFFER_FLAG_OUT (1<<0) uint32_t flags; struct spa_buffer *buf; struct spa_meta_header *h; struct spa_list link; }; #define BW_MAX 0.128 #define BW_MED 0.064 #define BW_MIN 0.016 #define BW_PERIOD (3 * SPA_NSEC_PER_SEC) struct channel_map { uint32_t channels; uint32_t pos[MAX_CHANNELS]; }; struct port { enum spa_direction direction; uint32_t id; uint64_t info_all; struct spa_port_info info; #define PORT_EnumFormat 0 #define PORT_Meta 1 #define PORT_IO 2 #define PORT_Format 3 #define PORT_Buffers 4 #define PORT_Latency 5 #define N_PORT_PARAMS 6 struct spa_param_info params[N_PORT_PARAMS]; bool have_format; struct spa_audio_info current_format; struct spa_io_buffers *io; struct spa_io_rate_match *rate_match; struct buffer buffers[MAX_BUFFERS]; unsigned int n_buffers; struct spa_list free; struct spa_list ready; uint32_t ready_offset; }; struct state { struct spa_handle handle; struct spa_node node; struct spa_log *log; struct spa_system *data_system; struct spa_loop *data_loop; struct spa_hook_list hooks; struct spa_callbacks callbacks; uint64_t info_all; struct spa_node_info info; #define NODE_PropInfo 0 #define NODE_Props 1 #define NODE_IO 2 #define NODE_ProcessLatency 3 #define N_NODE_PARAMS 4 struct spa_param_info params[N_NODE_PARAMS]; struct props props; uint32_t default_period_size; uint32_t default_format; unsigned int default_channels; unsigned int default_rate; uint32_t allowed_rates[MAX_RATES]; uint32_t n_allowed_rates; struct channel_map default_pos; char clock_name[64]; uint32_t quantum_limit; uint32_t format; uint32_t rate; uint32_t channels; uint32_t stride; uint32_t blocks; uint32_t rate_denom; struct spa_io_clock *clock; struct spa_io_position *position; struct port ports[1]; uint32_t duration; unsigned int following:1; unsigned int matching:1; unsigned int resample:1; unsigned int started:1; unsigned int freewheel:1; int timerfd; struct spa_source timer_source; uint64_t next_time; int sockfd; struct spa_source sock_source; struct sockaddr_ll sock_addr; struct spa_avbtp_packet_aaf *pdu; size_t hdr_size; size_t payload_size; size_t pdu_size; int64_t pdu_period; uint8_t pdu_seq; uint8_t prev_seq; struct iovec iov[3]; struct msghdr msg; char control[CMSG_SPACE(sizeof(__u64))]; struct cmsghdr *cmsg; uint8_t *ringbuffer_data; uint32_t ringbuffer_size; struct spa_ringbuffer ring; struct spa_dll dll; double max_error; struct spa_latency_info latency[2]; struct spa_process_latency_info process_latency; }; struct spa_pod *spa_avb_enum_propinfo(struct state *state, uint32_t idx, struct spa_pod_builder *b); int spa_avb_add_prop_params(struct state *state, struct spa_pod_builder *b); int spa_avb_parse_prop_params(struct state *state, struct spa_pod *params); int spa_avb_enum_format(struct state *state, int seq, uint32_t start, uint32_t num, const struct spa_pod *filter); int spa_avb_clear_format(struct state *state); int spa_avb_set_format(struct state *state, struct spa_audio_info *info, uint32_t flags); int spa_avb_init(struct state *state, const struct spa_dict *info); int spa_avb_clear(struct state *state); int spa_avb_start(struct state *state); int spa_avb_reassign_follower(struct state *state); int spa_avb_pause(struct state *state); int spa_avb_write(struct state *state); int spa_avb_read(struct state *state); int spa_avb_skip(struct state *state); void spa_avb_recycle_buffer(struct state *state, struct port *port, uint32_t buffer_id); static inline uint32_t spa_avb_parse_rates(uint32_t *rates, uint32_t max, const char *val, size_t len) { struct spa_json it[1]; char v[256]; uint32_t count; if (spa_json_begin_array_relax(&it[0], val, len) <= 0) return 0; count = 0; while (spa_json_get_string(&it[0], v, sizeof(v)) > 0 && count < max) rates[count++] = atoi(v); return count; } #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_AVB_PCM_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/avb/avb.c000066400000000000000000000014551511204443500244330ustar00rootroot00000000000000/* Spa AVB support */ /* SPDX-FileCopyrightText: Copyright © 2022 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include #include "avb.h" extern const struct spa_handle_factory spa_avb_sink_factory; extern const struct spa_handle_factory spa_avb_source_factory; SPA_LOG_TOPIC_DEFINE(avb_log_topic, "spa.avb"); SPA_LOG_TOPIC_ENUM_DEFINE_REGISTERED; SPA_EXPORT int spa_handle_factory_enum(const struct spa_handle_factory **factory, uint32_t *index) { spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(index != NULL, -EINVAL); switch (*index) { case 0: *factory = &spa_avb_sink_factory; break; case 1: *factory = &spa_avb_source_factory; break; default: return 0; } (*index)++; return 1; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/avb/avb.h000066400000000000000000000006501511204443500244340ustar00rootroot00000000000000/* Spa AVB */ /* SPDX-FileCopyrightText: Copyright © 2022 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_AVB_H #define SPA_AVB_H #include #undef SPA_LOG_TOPIC_DEFAULT #define SPA_LOG_TOPIC_DEFAULT &avb_log_topic extern struct spa_log_topic avb_log_topic; static inline void avb_log_topic_init(struct spa_log *log) { spa_log_topic_init(log, &avb_log_topic); } #endif /* SPA_AVB_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/avb/avbtp/000077500000000000000000000000001511204443500246265ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/avb/avbtp/packets.h000066400000000000000000000154761511204443500264460ustar00rootroot00000000000000/* Spa AVB support */ /* SPDX-FileCopyrightText: Copyright © 2022 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_AVB_PACKETS_H #define SPA_AVB_PACKETS_H #define SPA_AVBTP_SUBTYPE_61883_IIDC 0x00 #define SPA_AVBTP_SUBTYPE_MMA_STREAM 0x01 #define SPA_AVBTP_SUBTYPE_AAF 0x02 #define SPA_AVBTP_SUBTYPE_CVF 0x03 #define SPA_AVBTP_SUBTYPE_CRF 0x04 #define SPA_AVBTP_SUBTYPE_TSCF 0x05 #define SPA_AVBTP_SUBTYPE_SVF 0x06 #define SPA_AVBTP_SUBTYPE_RVF 0x07 #define SPA_AVBTP_SUBTYPE_AEF_CONTINUOUS 0x6E #define SPA_AVBTP_SUBTYPE_VSF_STREAM 0x6F #define SPA_AVBTP_SUBTYPE_EF_STREAM 0x7F #define SPA_AVBTP_SUBTYPE_NTSCF 0x82 #define SPA_AVBTP_SUBTYPE_ESCF 0xEC #define SPA_AVBTP_SUBTYPE_EECF 0xED #define SPA_AVBTP_SUBTYPE_AEF_DISCRETE 0xEE #define SPA_AVBTP_SUBTYPE_ADP 0xFA #define SPA_AVBTP_SUBTYPE_AECP 0xFB #define SPA_AVBTP_SUBTYPE_ACMP 0xFC #define SPA_AVBTP_SUBTYPE_MAAP 0xFE #define SPA_AVBTP_SUBTYPE_EF_CONTROL 0xFF struct spa_avbtp_packet_common { uint8_t subtype; #if __BYTE_ORDER == __BIG_ENDIAN unsigned sv:1; /* stream_id valid */ unsigned version:3; unsigned subtype_data1:4; #elif __BYTE_ORDER == __LITTLE_ENDIAN unsigned subtype_data1:4; unsigned version:3; unsigned sv:1; #elif #error "Unknown byte order" #endif uint16_t subtype_data2; uint64_t stream_id; uint8_t payload[0]; } __attribute__ ((__packed__)); #define SPA_AVBTP_PACKET_SET_SUBTYPE(p,v) ((p)->subtype = (v)) #define SPA_AVBTP_PACKET_SET_SV(p,v) ((p)->sv = (v)) #define SPA_AVBTP_PACKET_SET_VERSION(p,v) ((p)->version = (v)) #define SPA_AVBTP_PACKET_SET_STREAM_ID(p,v) ((p)->stream_id = htobe64(v)) #define SPA_AVBTP_PACKET_GET_SUBTYPE(p) ((p)->subtype) #define SPA_AVBTP_PACKET_GET_SV(p) ((p)->sv) #define SPA_AVBTP_PACKET_GET_VERSION(p) ((p)->version) #define SPA_AVBTP_PACKET_GET_STREAM_ID(p) be64toh((p)->stream_id) struct spa_avbtp_packet_cc { uint8_t subtype; #if __BYTE_ORDER == __BIG_ENDIAN unsigned sv:1; unsigned version:3; unsigned control_data1:4; #elif __BYTE_ORDER == __LITTLE_ENDIAN unsigned control_data1:4; unsigned version:3; unsigned sv:1; #endif uint8_t status; uint16_t control_frame_length; uint64_t stream_id; uint8_t payload[0]; } __attribute__ ((__packed__)); #define SPA_AVBTP_PACKET_CC_SET_SUBTYPE(p,v) ((p)->subtype = (v)) #define SPA_AVBTP_PACKET_CC_SET_SV(p,v) ((p)->sv = (v)) #define SPA_AVBTP_PACKET_CC_SET_VERSION(p,v) ((p)->version = (v)) #define SPA_AVBTP_PACKET_CC_SET_STREAM_ID(p,v) ((p)->stream_id = htobe64(v)) #define SPA_AVBTP_PACKET_CC_SET_STATUS(p,v) ((p)->status = (v)) #define SPA_AVBTP_PACKET_CC_SET_LENGTH(p,v) ((p)->control_frame_length = htons(v)) #define SPA_AVBTP_PACKET_CC_GET_SUBTYPE(p) ((p)->subtype) #define SPA_AVBTP_PACKET_CC_GET_SV(p) ((p)->sv) #define SPA_AVBTP_PACKET_CC_GET_VERSION(p) ((p)->version) #define SPA_AVBTP_PACKET_CC_GET_STREAM_ID(p) be64toh((p)->stream_id) #define SPA_AVBTP_PACKET_CC_GET_STATUS(p) ((p)->status) #define SPA_AVBTP_PACKET_CC_GET_LENGTH(p) ntohs((p)->control_frame_length) /* AAF */ struct spa_avbtp_packet_aaf { uint8_t subtype; #if __BYTE_ORDER == __BIG_ENDIAN unsigned sv:1; unsigned version:3; unsigned mr:1; unsigned _r1:1; unsigned gv:1; unsigned tv:1; uint8_t seq_num; unsigned _r2:7; unsigned tu:1; #elif __BYTE_ORDER == __LITTLE_ENDIAN unsigned tv:1; unsigned gv:1; unsigned _r1:1; unsigned mr:1; unsigned version:3; unsigned sv:1; uint8_t seq_num; unsigned tu:1; unsigned _r2:7; #endif uint64_t stream_id; uint32_t timestamp; #define SPA_AVBTP_AAF_FORMAT_USER 0x00 #define SPA_AVBTP_AAF_FORMAT_FLOAT_32BIT 0x01 #define SPA_AVBTP_AAF_FORMAT_INT_32BIT 0x02 #define SPA_AVBTP_AAF_FORMAT_INT_24BIT 0x03 #define SPA_AVBTP_AAF_FORMAT_INT_16BIT 0x04 #define SPA_AVBTP_AAF_FORMAT_AES3_32BIT 0x05 uint8_t format; #define SPA_AVBTP_AAF_PCM_NSR_USER 0x00 #define SPA_AVBTP_AAF_PCM_NSR_8KHZ 0x01 #define SPA_AVBTP_AAF_PCM_NSR_16KHZ 0x02 #define SPA_AVBTP_AAF_PCM_NSR_32KHZ 0x03 #define SPA_AVBTP_AAF_PCM_NSR_44_1KHZ 0x04 #define SPA_AVBTP_AAF_PCM_NSR_48KHZ 0x05 #define SPA_AVBTP_AAF_PCM_NSR_88_2KHZ 0x06 #define SPA_AVBTP_AAF_PCM_NSR_96KHZ 0x07 #define SPA_AVBTP_AAF_PCM_NSR_176_4KHZ 0x08 #define SPA_AVBTP_AAF_PCM_NSR_192KHZ 0x09 #define SPA_AVBTP_AAF_PCM_NSR_24KHZ 0x0A #if __BYTE_ORDER == __BIG_ENDIAN unsigned nsr:4; unsigned _r3:4; #elif __BYTE_ORDER == __LITTLE_ENDIAN unsigned _r3:4; unsigned nsr:4; #endif uint8_t chan_per_frame; uint8_t bit_depth; uint16_t data_len; #define SPA_AVBTP_AAF_PCM_SP_NORMAL 0x00 #define SPA_AVBTP_AAF_PCM_SP_SPARSE 0x01 #if __BYTE_ORDER == __BIG_ENDIAN unsigned _r4:3; unsigned sp:1; unsigned event:4; #elif __BYTE_ORDER == __LITTLE_ENDIAN unsigned event:4; unsigned sp:1; unsigned _r4:3; #endif uint8_t _r5; uint8_t payload[0]; } __attribute__ ((__packed__)); #define SPA_AVBTP_PACKET_AAF_SET_SUBTYPE(p,v) ((p)->subtype = (v)) #define SPA_AVBTP_PACKET_AAF_SET_SV(p,v) ((p)->sv = (v)) #define SPA_AVBTP_PACKET_AAF_SET_VERSION(p,v) ((p)->version = (v)) #define SPA_AVBTP_PACKET_AAF_SET_MR(p,v) ((p)->mr = (v)) #define SPA_AVBTP_PACKET_AAF_SET_GV(p,v) ((p)->gv = (v)) #define SPA_AVBTP_PACKET_AAF_SET_TV(p,v) ((p)->tv = (v)) #define SPA_AVBTP_PACKET_AAF_SET_SEQ_NUM(p,v) ((p)->seq_num = (v)) #define SPA_AVBTP_PACKET_AAF_SET_TU(p,v) ((p)->tu = (v)) #define SPA_AVBTP_PACKET_AAF_SET_STREAM_ID(p,v) ((p)->stream_id = htobe64(v)) #define SPA_AVBTP_PACKET_AAF_SET_TIMESTAMP(p,v) ((p)->timestamp = htonl(v)) #define SPA_AVBTP_PACKET_AAF_SET_DATA_LEN(p,v) ((p)->data_len = htons(v)) #define SPA_AVBTP_PACKET_AAF_SET_FORMAT(p,v) ((p)->format = (v)) #define SPA_AVBTP_PACKET_AAF_SET_NSR(p,v) ((p)->nsr = (v)) #define SPA_AVBTP_PACKET_AAF_SET_CHAN_PER_FRAME(p,v) ((p)->chan_per_frame = (v)) #define SPA_AVBTP_PACKET_AAF_SET_BIT_DEPTH(p,v) ((p)->bit_depth = (v)) #define SPA_AVBTP_PACKET_AAF_SET_SP(p,v) ((p)->sp = (v)) #define SPA_AVBTP_PACKET_AAF_SET_EVENT(p,v) ((p)->event = (v)) #define SPA_AVBTP_PACKET_AAF_GET_SUBTYPE(p) ((p)->subtype) #define SPA_AVBTP_PACKET_AAF_GET_SV(p) ((p)->sv) #define SPA_AVBTP_PACKET_AAF_GET_VERSION(p) ((p)->version) #define SPA_AVBTP_PACKET_AAF_GET_MR(p) ((p)->mr) #define SPA_AVBTP_PACKET_AAF_GET_GV(p) ((p)->gv) #define SPA_AVBTP_PACKET_AAF_GET_TV(p) ((p)->tv) #define SPA_AVBTP_PACKET_AAF_GET_SEQ_NUM(p) ((p)->seq_num) #define SPA_AVBTP_PACKET_AAF_GET_TU(p) ((p)->tu) #define SPA_AVBTP_PACKET_AAF_GET_STREAM_ID(p) be64toh((p)->stream_id) #define SPA_AVBTP_PACKET_AAF_GET_TIMESTAMP(p) ntohl((p)->timestamp) #define SPA_AVBTP_PACKET_AAF_GET_DATA_LEN(p) ntohs((p)->data_len) #define SPA_AVBTP_PACKET_AAF_GET_FORMAT(p) ((p)->format) #define SPA_AVBTP_PACKET_AAF_GET_NSR(p) ((p)->nsr) #define SPA_AVBTP_PACKET_AAF_GET_CHAN_PER_FRAME(p) ((p)->chan_per_frame) #define SPA_AVBTP_PACKET_AAF_GET_BIT_DEPTH(p) ((p)->bit_depth) #define SPA_AVBTP_PACKET_AAF_GET_SP(p) ((p)->sp) #define SPA_AVBTP_PACKET_AAF_GET_EVENT(p) ((p)->event) #endif /* SPA_AVB_PACKETS_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/avb/meson.build000066400000000000000000000005561511204443500256620ustar00rootroot00000000000000spa_avb_sources = ['avb.c', 'avb.h', 'avb-pcm-sink.c', 'avb-pcm-source.c', 'avb-pcm.c' ] spa_avb = shared_library( 'spa-avb', [ spa_avb_sources ], include_directories : [configinc], dependencies : [ spa_dep, mathlib, epoll_shim_dep ], install : true, install_dir : spa_plugindir / 'avb' ) pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/bluez5/000077500000000000000000000000001511204443500241505ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/bluez5/README-MIDI.md000066400000000000000000000014541511204443500261530ustar00rootroot00000000000000## BLE MIDI & SELinux The SELinux configuration on Fedora 37 (as of 2022-11-10) does not permit access to the bluetoothd APIs needed for BLE MIDI. As a workaround, hopefully to be not necessary in future, you can permit such access by creating a file `blemidi.te` with contents: policy_module(blemidi, 1.0); require { type system_dbusd_t; type unconfined_t; type bluetooth_t; } allow bluetooth_t unconfined_t:unix_stream_socket { read write }; allow system_dbusd_t bluetooth_t:unix_stream_socket { read write }; Then having package `selinux-policy-devel` installed, running `make -f /usr/share/selinux/devel/Makefile blemidi.pp`, and finally to insert the rules via `sudo semodule -i blemidi.pp`. The policy change can be removed by `sudo semodule -r blemidi`. pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/bluez5/README-OPUS-A2DP.md000066400000000000000000000346071511204443500267110ustar00rootroot00000000000000--- title: OPUS-A2DP-0.5 specification author: Pauli Virtanen date: Jun 4, 2022 --- # OPUS-A2DP-0.5 specification In this file, a way to use Opus as an A2DP vendor codec is specified. We will call this "OPUS-A2DP-0.5". There is no previous public specification for using Opus as an A2DP vendor codec (to my knowledge), which is why we need this one. [[_TOC_]] # Media Codec Capabilities The Media Codec Specific Information Elements ([AVDTP v1.3], §8.21.5) capability and configuration structure is as follows: | Octet | Bits | Meaning | |-------|------|-----------------------------------------------| | 0-5 | 0-7 | Vendor ID Part | | 6-7 | 0-7 | Channel Configuration | | 8-11 | 0-7 | Audio Location Configuration | | 12-14 | 0-7 | Limits Configuration | | 15-16 | 0-7 | Return Direction Channel Configuration | | 17-20 | 0-7 | Return Direction Audio Location Configuration | | 21-23 | 0-7 | Return Direction Limits Configuration | All integer fields and multi-byte bitfields are laid out in **little endian** order. All integer fields are unsigned. Each entry may have different meaning when present as a capability. Below, we indicate this by abbreviations CAP for capability and SEL for the value selected by SRC. Bits in fields marked RFA (Reserved For Additions) shall be set to zero. > **Note** > > See `a2dp-codec-caps.h` for definition as C structs. ## Vendor ID Part The fixed value | Octet | Bits | Meaning | |-------|------|-------------------------------| | 0-3 | 0-7 | A2DP Vendor ID (0x05F1) | | 4-5 | 0-7 | A2DP Vendor Codec ID (0x1005) | > **Note** > > The Vendor ID is that of the Linux Foundation, and we are using it > here unofficially. ## Channel Configuration The channel configuration consists of the channel count, and the count of coupled streams. The latter indicates which channels are encoded as left/right pairs, as defined in Sec. 5.1.1 of Opus Ogg Encapsulation [RFC7845]. | Octet | Bits | Meaning | |-------|------|------------------------------------------------------------| | 6 | 0-7 | Channel Count. CAP: maximum number supported. SEL: actual. | | 7 | 0-7 | Coupled Stream Count. CAP: 0. SEL: actual. | The Channel Count indicates the number of logical channels encoded in the data stream. The Coupled Stream Count indicates the number of streams that encode a coupled (left & right) channel pair. The count shall satisfy `(Channel Count) >= 2*(Coupled Stream Count)`. The Stream Count is `(Channel Count) - (Coupled Stream Count)`. The logical Channels are identified by a Channel Index *j* such that `0 <= j < (Channel Count)`. The channels `0 <= j < 2*(Coupled Stream Count)` are encoded in the *k*-th stream of the payload, where `k = floor(j/2)` and `j mod 2` determines which of the two channels of the stream the logical channel is. The channels `2*(Coupled Stream Count) <= j < (Channel Count)` are encoded in the *k*-th stream of the payload, where `k = j - (Coupled Stream Count)`. > **Note** > > The prescription here is identical to [RFC7845] with channel mapping > `mapping[j] = j`. We do not want to include the mapping table in the > A2DP capabilities, so it is assumed to be trivial. ## Audio Location Configuration The semantic meaning for each channel is determined by their Audio Location bitfield. | Octet | Bits | Meaning | |-------|------|------------------------------------------------------| | 8-11 | 0-7 | Audio Location bitfield. CAP: available. SEL: actual | The values specified in CAP are informative, and SEL may contain bits that were not set in CAP. SNK shall handle unsupported audio locations. It may do this for example by ignoring unsupported channels or via suitable up/downmixing. Hence, SRC may transmit channels with audio locations that are not marked supported by SNK. The audio location bit values are: | Channel Order | Bitmask | Audio Location | |---------------|------------|-------------------------| | 0 | 0x00000001 | Front Left | | 1 | 0x00000002 | Front Right | | 2 | 0x00000400 | Side Left | | 3 | 0x00000800 | Side Right | | 4 | 0x00000010 | Back Left | | 5 | 0x00000020 | Back Right | | 6 | 0x00000040 | Front Left of Center | | 7 | 0x00000080 | Front Right of Center | | 8 | 0x00001000 | Top Front Left | | 9 | 0x00002000 | Top Front Right | | 10 | 0x00040000 | Top Side Left | | 11 | 0x00080000 | Top Side Right | | 12 | 0x00010000 | Top Back Left | | 13 | 0x00020000 | Top Back Right | | 14 | 0x00400000 | Bottom Front Left | | 15 | 0x00800000 | Bottom Front Right | | 16 | 0x01000000 | Front Left Wide | | 17 | 0x02000000 | Front Right Wide | | 18 | 0x04000000 | Left Surround | | 19 | 0x08000000 | Right Surround | | 20 | 0x00000004 | Front Center | | 21 | 0x00000100 | Back Center | | 22 | 0x00004000 | Top Front Center | | 23 | 0x00008000 | Top Center | | 24 | 0x00100000 | Top Back Center | | 25 | 0x00200000 | Bottom Front Center | | 26 | 0x00000008 | Low Frequency Effects 1 | | 27 | 0x00000200 | Low Frequency Effects 2 | | 28 | 0x10000000 | RFA | | 29 | 0x20000000 | RFA | | 30 | 0x40000000 | RFA | | 31 | 0x80000000 | RFA | Each bit value is associated with a Channel Order. The bits set in the bitfield define audio locations for the streams present in the payload. The set bit with the smallest Channel Order value defines the audio location for the Channel Index *j=0*, the bit with the next lowest Channel Order value defines the audio location for the Channel Index *j=1*, and so forth. When the Channel Count is larger than the number of bits set in the Audio Location bitfield, the audio locations of the remaining channels are unspecified. Implementations may handle them as appropriate for their use case, considering them as AUX0–AUXN, or in the case of Channel Count = 1, as the single mono audio channel. When the Channel Count is smaller than the number of bits set in the Audio Location bitfield, the audio locations for the channels are assigned as above, and remaining excess bits shall be ignored. > **Note** > > The channel audio location specification is similar to the location > bitfield of the `Audio_Channel_Allocation` LTV structure in Bluetooth > SIG [Assigned Numbers, Generic Audio] used in the LE Audio, and the > bitmasks defined above are the same. > > The channel ordering differs from LE Audio, and is defined here to be > compatible with the internal stream ordering in the reference Opus > Multistream surround encoder Mapping Family 0 and 1 output. This > allows making use of its surround masking and LFE handling > capabilities. The stream ordering of the reference Opus surround > encoder, although being unchanged since its addition in 2013, is an > internal detail of the encoder. Implementations using the surround > encoder need to check that the mapping table used by the encoder > corresponds to the above channel ordering. > > For reference, we list the Audio Location bitfield values > corresponding to the different channel counts in Opus Mapping Family 0 > and 1 surround encoder output, and the expected mapping table: > > | Mapping Family | Channel Count | Audio Location Value | Stream Ordering | Mapping Table | > |----------------|---------------|----------------------|---------------------------------|--------------------------| > | 0 | 1 | 0x00000000 | mono | {0} | > | 0 | 2 | 0x00000003 | FL, FR | {0, 1} | > | 1 | 1 | 0x00000000 | mono | {0} | > | 1 | 2 | 0x00000003 | FL, FR | {0, 1} | > | 1 | 3 | 0x00000007 | FL, FR, FC | {0, 2, 1} | > | 1 | 4 | 0x00000033 | FL, FR, BL, BR | {0, 1, 2, 3} | > | 1 | 5 | 0x00000037 | FL, FR, BL, BR, FC | {0, 4, 1, 2, 3} | > | 1 | 6 | 0x0000003f | FL, FR, BL, BR, FC, LFE | {0, 4, 1, 2, 3, 5} | > | 1 | 7 | 0x00000d0f | FL, FR, SL, SR, FC, BC, LFE | {0, 4, 1, 2, 3, 5, 6} | > | 1 | 8 | 0x00000c3f | FL, FR, SL, SR, BL, BR, FC, LFE | {0, 6, 1, 2, 3, 4, 5, 7} | > > The Mapping Table in the table indicates the mapping table selected by > `opus_multistream_surround_encoder_create` (Opus 1.3.1). If the > encoder outputs a different mapping table in a future Opus encoder > release, the channel ordering will be incorrect, and the surround > encoder can not be used. We expect that the probability of the Opus > encoder authors making such changes is negligible. ## Limits Configuration The limits for allowed frame durations and maximum bitrate can also be configured. | Octet | Bits | Meaning | |-------|------|-----------------------------------------------------| | 16 | 0 | Frame duration 2.5ms. CAP: supported, SEL: selected | | 16 | 1 | Frame duration 5ms. CAP: supported, SEL: selected | | 16 | 2 | Frame duration 10ms. CAP: supported, SEL: selected | | 16 | 3 | Frame duration 20ms. CAP: supported, SEL: selected | | 16 | 4 | Frame duration 40ms. CAP: supported, SEL: selected | | 16 | 5-7 | RFA | | Octet | Bits | Meaning | |-------|------|------------------------------------------------| | 17-18 | 0-7 | Maximum bitrate. CAP: supported, SEL: selected | The maximum bitrate is given in units of 1024 bits per second. The maximum bitrate field in CAP may contain value 0 to indicate everything is supported. ## Bidirectional Audio Configuration Bidirectional audio may be supported. Its Channel Configuration, Audio Location Configuration, and Limits Configuration have identical form to the forward direction, and represented by exactly similar structures. Namely: | Octet | Bits | Meaning | |-------|------|----------------------------------------------------| | 19-20 | 0-7 | Channel Configuration fields, for return direction | | 21-28 | 0-7 | Audio Location fields, for return direction | | 29-31 | 0-7 | Limits Configuration fields, for return direction | If no return channel is supported or selected, the number of channels is set to 0 in CAP or SEL. > **Note** > > This is a nonstandard extension to A2DP. The return direction audio > data is simply sent back via the underlying L2CAP connection, which > is bidirectional, in the same format as the forward direction audio. > This is similar to what aptX-LL and FastStream do. # Packet Structure Each packet consists of an RTP header, an RTP payload header, and a payload containing Opus Multistream data. | Octet | Bits | Meaning | |-------|------|--------------------------| | 0-11 | 0-7 | RTP header | | 12 | 0-7 | RTP payload header | | 13-N | 0-7 | Opus Multistream payload | For each Bluetooth packet, the payload shall contain exactly one Opus Multistream packet, or a fragment of one. The Opus Multistream packet may be fragmented to several consecutive Bluetooth packets. The format of the Multistream data is the same as in the audio packets of [RFC7845], or, as produced/consumed by the Opus Multistream API. > **Note** > > We DO NOT follow [RFC7587], as we want fragmentation and multichannel support. ## RTP Header See [RFC3550]. The RTP payload type is pt=96 (dynamic). ## RTP Payload Header The RTP payload header is used to indicate if and how the Opus Multistream packet is fragmented across several consecutive Bluetooth packets. | Octet | Bits | Meaning |--------|------|-------------------------------------------------------- | 0 | 0-3 | Frame Count | 4 | 4 | RFA | 4 | 5 | Is Last Fragment | 4 | 6 | Is First Fragment | 4 | 7 | Is Fragmented In each packet, Frame Count indicates how many Bluetooth packets are still to be received (including the present packet) before the Opus Multistream packet is complete. The Is Fragment flag indicates whether the present packet contains fragmented payload. The Is Last Fragment flag indicates whether the present packet is the last part of fragmented payload. The Is First Fragment flag indicates whether the present packet is the first part of fragmented payload. In non-fragmented packets, Frame Count shall be (1), and the other bits in the header zero. ## Opus Payload The Opus payload is a single Opus Multistream packet, or its fragment. In case of fragmentation, as indicated by the RTP payload header, concatenating the payloads of the fragment Bluetooth packets shall yield the total Opus Multistream packet. The SRC should choose encoder parameters such that Bluetooth bandwidth limitations are not exceeded. The SRC may include FEC data. The SNK may enable forward error correction instead of PLC. # References 1. Bluetooth [AVDTP v1.3] 2. IETF [RFC3550] 3. IETF [RFC7587] 4. IETF [RFC7845] 5. Bluetooth [Assigned Numbers, Generic Audio] [AVDTP v1.3]: https://www.bluetooth.com/specifications/specs/a-v-distribution-transport-protocol-1-3/ [RFC3550]: https://datatracker.ietf.org/doc/html/rfc3550 [RFC7587]: https://datatracker.ietf.org/doc/html/rfc7587 [RFC7845]: https://datatracker.ietf.org/doc/html/rfc7845 [Assigned Numbers, Generic Audio]: https://www.bluetooth.com/specifications/assigned-numbers/ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/bluez5/README-SBC-XQ.md000066400000000000000000000050521511204443500263640ustar00rootroot00000000000000## SBC XQ SBC XQ is standard SBC codec operating at high bitrates and thus reaching the transparent audio transport quality of AptX (HD) or other proprietary codecs. A2DP specification (A2DP SPEC) defines SBC parameters. These parameters are negotiated between the source (SRC) and the receiver (SNK) at connection time : - Audio channel mode : Joint Stereo, Stereo, Dual Channel, Mono : all modes are MANDATORY for the SNK according to A2DP specification - Number of subbands: 4 or 8 - both MANDATORY for the SNK implementation - Blocks Length: 4, 8, 12, 16 - all MANDATORY for the SNK implementation - Allocation Method: Loudness, SNR - both MANDATORY for the SNK implementation - Maximum and minimum bit pool : between 2 to 250, expressed in 8 bit uint (Unsigned integer, Most significant bit first) : - A2DP spec v1.2 states that requires all SNK implementation shall handle bitrates of up to 512 kbps (which correspond to bitpool = 76). - A2DP spec v1.3 doesn't specify any bitrate limit, and some high-end SNK devices announce bitpool between 62 and 94 (bitpool 94 = 551kbps bitrate). Bluetooth standard radio capabilities are as follow : | Bluetooth speed EDR | EDR 2Mbps | | EDR 3Mbps | |-------------------------|-----------|-------|-----------| | Speed (b/s) | 2097152 | | 3145728 | | Radio slot length (s) | 0.000625 | | 0.000625 | | Radio slots / s | 1600 | | 1600 | | Slot size (B) | 163.84 | | 245.76 | | Max payload/5 slots (B) | 676.2 | | 1085.8 | | max bitrate (Kb/s) | 1408.75 | | 2262.08 | The A2DP specification V1.3 provides RECOMMENDATIONS for bitpool implementation for the encoder of the SRC : it is required to support AT LEAST the following settings : - STEREO MODE : 53 - MONO MODE : 31 - DUAL CHANNEL : unspecified, so let's assume that the MONO value can be used : 3 According to http://soundexpert.org/articles/-/blogs/audio-quality-of-sbc-xq-bluetooth-audio-codec , AptX quality can be reached either : - in STEREO MODE, with bitpool ~ 76 - in DUAL CHANNEL MODE, with bitpool ~ 38 per channel | sampling Freq (Hz) | 44100 | 48000 | |-------------------------|-----------|-------| | bitpool / channel | 38 | 35 | | Frame length DUAL (B) | 164 | 152 | | Frame length JST (B) | 165 | 153 | | Frame length ST (B) | 164 | 152 | | bitrate DUAL CH (kb/s) | 452 | 456 | | bitrate JOINT ST (kb/s) | 454 | 459 | | bitrate STEREO (kb/s) | 452 | 456 | pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/bluez5/README-Telephony.md000066400000000000000000000310261511204443500273760ustar00rootroot00000000000000# PipeWire Bluetooth Telephony service The Telephony service is a D-Bus service that allows applications to communicate with the HFP native backend in order to control phone calls. Phone call features are a core part of the HFP specification and are available when a mobile phone is paired (therefore, PipeWire acts as the Hands-Free and the phone is the Audio Gateway). The service is exposed on the user session bus by default, but there is an option to make it available on the system bus instead. The service implements its own interfaces alongside the standard DBus Introspectable, ObjectManager and Properties interfaces, where needed. These interfaces are mostly compatible with the ofono Manager, VoiceCallManager and VoiceCall interfaces. For compatibility, the `org.ofono.Manager`, `org.ofono.VoiceCallManager` and `org.ofono.VoiceCall` are also implemented with any additional compatibility methods & signals are necessary to allow ofono-based applications to be able to work just by modifying the service name, the manager object path and the operating bus (session vs system). In addition, to the compatibility interfaces, there is a runtime option to also register the service as `org.ofono` on the system bus, making it a drop-in replacement for ofono. Note, however, that this service is not a full replacement, but only for the Bluetooth-based voice calls. ## Manager Object ``` Service org.pipewire.Telephony or org.ofono Object path /org/pipewire/Telephony or / Implements org.ofono.Manager org.freedesktop.DBus.Introspectable org.freedesktop.DBus.ObjectManager ``` The manager object is always available and allows applications to get access to the connected audio gateways. The object path is set to `/` when ofono service compatibility is enabled, in which case the service name `org.ofono` is also registered instead of `org.pipewire.Telephony`. The methods and signals below are made available on the `org.ofono.Manager` interface, for compatibility. AudioGateway objects are normally announced via the standard DBus ObjectManager interface. ### Methods `array{object,dict} GetModems()` Get an array of AudioGateway objects and properties that represents the currently connected audio gateways. ### Signals `ModemAdded(object path, dict properties)` Signal that is sent when a new audio gateway is added. It contains the object path of the new audio gateway and also its properties. `ModemRemoved(object path)` Signal that is sent when an audio gateway has been removed. The object path is no longer accessible after this signal and only emitted for reference. ## AudioGateway Object ``` Service org.pipewire.Telephony or org.ofono Object path /org/pipewire/Telephony/{ag0,ag1,...} Implements org.pipewire.Telephony.AudioGateway1 org.ofono.VoiceCallManager org.freedesktop.DBus.Introspectable org.freedesktop.DBus.ObjectManager ``` Audio gateway objects represent the currently connected AG devices (typically mobile phones). The methods, signals and properties listed below are made available on both `org.pipewire.Telephony.AudioGateway1` and `org.ofono.VoiceCallManager` interfaces, unless explicitly documented otherwise. Call objects are announced via both the standard DBus ObjectManager interface and via the `org.ofono.VoiceCallManager` interface, for compatibility. ### Methods `array{object,dict} GetCalls()` Get an array of call object paths and properties that represents the currently present calls. This method call should only be used once when an application starts up. Further call additions and removal shall be monitored via CallAdded and CallRemoved signals. NOTE: This method is implemented only on the `org.ofono.VoiceCallManager` interface, for compatibility. Call announcements are normally made available via the standard `org.freedesktop.DBus.ObjectManager` interface. `void Dial(string number)` Initiates a new outgoing call. If this succeeds, the new call is announced via the signals. The number must be a string containing the following characters: `[0-9+*#,ABCD]{1,80}` In other words, it must be a non-empty string consisting of 1 to 80 characters. The character set can contain numbers, `+`, `*`, `#`, `,` and the letters `A` to `D`. Besides this sanity checking no further number validation is performed. It is assumed that the gateway and/or the network will perform further validation. NOTE: If an active call (single or multiparty) exists, then it is automatically put on hold if the dial procedure is successful. Possible Errors: * org.pipewire.Telephony.Error.InvalidState * org.freedesktop.DBus.Error.InvalidArgs * org.freedesktop.DBus.Error.Failed `void SwapCalls()` Swaps Active and Held calls. The effect of this is that all calls (0 or more including calls in a multi-party conversation) that were Active are now Held, and all calls (0 or more) that were Held are now Active. GSM specification does not allow calls to be swapped in the case where Held, Active and Waiting calls exist. Some modems implement this anyway, thus it is manufacturer specific whether this method will succeed in the case of Held, Active and Waiting calls. Possible Errors: * org.pipewire.Telephony.Error.InvalidState * org.freedesktop.DBus.Error.Failed `void ReleaseAndAnswer()` Releases currently active call (0 or more) and answers the currently waiting call. Please note that if the current call is a multiparty call, then all parties in the multi-party call will be released. Possible Errors: * org.pipewire.Telephony.Error.InvalidState * org.freedesktop.DBus.Error.Failed `void ReleaseAndSwap()` Releases currently active call (0 or more) and activates any currently held calls. Please note that if the current call is a multiparty call, then all parties in the multi-party call will be released. Possible Errors: * org.pipewire.Telephony.Error.InvalidState * org.freedesktop.DBus.Error.Failed `void HoldAndAnswer()` Puts the current call (including multi-party calls) on hold and answers the currently waiting call. Calling this function when a user already has a both Active and Held calls is invalid, since in GSM a user can have only a single Held call at a time. Possible Errors: * org.pipewire.Telephony.Error.InvalidState * org.freedesktop.DBus.Error.Failed `void HangupAll()` Releases all calls except waiting calls. This includes multiparty calls. Possible Errors: * org.pipewire.Telephony.Error.InvalidState * org.freedesktop.DBus.Error.Failed `void CreateMultiparty()` Joins active and held calls together into a multi-party call. If one of the calls is already a multi-party call, then the other call is added to the multiparty conversation. Changes to the call objects are announced via the signals. There can only be one subscriber controlled multi-party call according to the GSM specification. Possible Errors: * org.pipewire.Telephony.Error.InvalidState * org.freedesktop.DBus.Error.Failed `void SendTones(string tones)` Sends the DTMF tones to the network. The tones have a fixed duration. Tones can be one of: '0' - '9', '*', '#', 'A', 'B', 'C', 'D'. The last four are typically not used in normal circumstances. Possible Errors: * org.pipewire.Telephony.Error.InvalidState * org.freedesktop.DBus.Error.InvalidArgs * org.freedesktop.DBus.Error.Failed ### Signals `CallAdded(object path, dict properties)` Signal that is sent when a new call is added. It contains the object path of the new voice call and also its properties. NOTE: This method is implemented only on the `org.ofono.VoiceCallManager` interface, for compatibility. Call announcements are normally made available via the standard `org.freedesktop.DBus.ObjectManager` interface. `CallRemoved(object path)` Signal that is sent when a voice call has been released. The object path is no longer accessible after this signal and only emitted for reference. NOTE: This method is implemented only on the `org.ofono.VoiceCallManager` interface, for compatibility. Call announcements are normally made available via the standard `org.freedesktop.DBus.ObjectManager` interface. ## Call Object ``` Service org.pipewire.Telephony or org.ofono Object path /org/pipewire/Telephony/{ag0,ag1,...}/{call0,call1,...} Implements org.pipewire.Telephony.Call1 org.ofono.VoiceCall org.freedesktop.DBus.Introspectable org.freedesktop.DBus.Properties ``` Call objects represent active calls and allow managing them. The methods, signals and properties listed below are made available on both `org.pipewire.Telephony.Call1` and `org.ofono.VoiceCall` interfaces, unless explicitly documented otherwise. ### Methods `dict GetProperties()` Returns all properties for this object. See the properties section for available properties. NOTE: This method is implemented only on the `org.ofono.VoiceCall` interface, for compatibility. Properties are normally made available via the standard `org.freedesktop.DBus.Properties` interface. `void Answer()` Answers an incoming call. Only valid if the state of the call is "incoming". Possible Errors: * org.pipewire.Telephony.Error.InvalidState * org.freedesktop.DBus.Error.Failed `void Hangup()` Hangs up the call. For an incoming call, the call is hung up using ATH or equivalent. For a waiting call, the remote party is notified by using the User Determined User Busy (UDUB) condition. This is generally implemented using CHLD=0. Please note that the GSM specification does not allow the release of a held call when a waiting call exists. This is because 27.007 allows CHLD=1X to operate only on active calls. Hence a held call cannot be hung up without affecting the state of the incoming call (e.g. using other CHLD alternatives). Most manufacturers provide vendor extensions that do allow the state of the held call to be modified using CHLD=1X or equivalent. It should be noted that Bluetooth HFP specifies the classic 27.007 behavior and does not allow CHLD=1X to modify the state of held calls. Based on the discussion above, it should also be noted that releasing a particular party of a held multiparty call might not be possible on some implementations. It is recommended for the applications to structure their UI accordingly. NOTE: Releasing active calls does not produce side-effects. That is the state of held or waiting calls is not affected. As an exception, in the case where a single active call and a waiting call are present, releasing the active call will result in the waiting call transitioning to the 'incoming' state. Possible Errors: * org.pipewire.Telephony.Error.InvalidState * org.freedesktop.DBus.Error.Failed ### Signals `PropertyChanged(string property, variant value)` Signal is emitted whenever a property has changed. The new value is passed as the signal argument. NOTE: This method is implemented only on the `org.ofono.VoiceCall` interface, for compatibility. Properties are normally made available via the standard `org.freedesktop.DBus.Properties` interface. ### Properties `string LineIdentification [readonly]` Contains the Line Identification information returned by the network, if present. For incoming calls this is effectively the CLIP. For outgoing calls this attribute will hold the dialed number, or the COLP if received by the audio gateway. Please note that COLP may be different from the dialed number. A special "withheld" value means the remote party refused to provide caller ID and the "override category" option was not provisioned for the current subscriber. `string IncomingLine [readonly, optional]` Contains the Called Line Identification information returned by the network. This is only available for incoming calls and indicates the local subscriber number which was dialed by the remote party. This is useful for subscribers which have a multiple line service with their network provider and would like to know what line the call is coming in on. `string Name [readonly]` Contains the Name Identification information returned by the network, if present. `boolean Multiparty [readonly]` Contains the indication if the call is part of a multiparty call or not. Notifications if a call becomes part or leaves a multiparty call are sent. `string State [readonly]` Contains the state of the current call. The state can be one of: - "active" - The call is active - "held" - The call is on hold - "dialing" - The call is being dialed - "alerting" - The remote party is being alerted - "incoming" - Incoming call in progress - "waiting" - Call is waiting - "disconnected" - No further use of this object is allowed, it will be destroyed shortly pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/bluez5/a2dp-codec-aac.c000066400000000000000000000460021511204443500267410ustar00rootroot00000000000000/* Spa A2DP AAC codec */ /* SPDX-FileCopyrightText: Copyright © 2020 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #include #include #include #include #include "rtp.h" #include "media-codecs.h" static struct spa_log *log; #define DEFAULT_AAC_BITRATE 320000 #define MIN_AAC_BITRATE 64000 struct props { int bitratemode; }; struct impl { HANDLE_AACENCODER aacenc; HANDLE_AACDECODER aacdec; struct rtp_header *header; size_t mtu; int codesize; int max_bitrate; int cur_bitrate; uint32_t rate; uint32_t channels; int samplesize; uint32_t enc_delay; uint32_t dec_delay; }; static bool eld_supported(void) { static bool supported = false, checked = false; HANDLE_AACENCODER aacenc = NULL; if (checked) return supported; if (aacEncOpen(&aacenc, 0, 2) != AACENC_OK) goto done; if (aacEncoder_SetParam(aacenc, AACENC_AOT, AOT_ER_AAC_ELD) != AACENC_OK) goto done; if (aacEncoder_SetParam(aacenc, AACENC_SBR_MODE, 1) != AACENC_OK) goto done; supported = true; done: if (aacenc) aacEncClose(&aacenc); checked = true; spa_log_debug(log, "FDK-AAC AAC-ELD support:%d", (int)supported); return supported; } static int codec_fill_caps(const struct media_codec *codec, uint32_t flags, const struct spa_dict *settings, uint8_t caps[A2DP_MAX_CAPS_SIZE]) { const a2dp_aac_t a2dp_aac = { .object_type = /* NOTE: AAC Long Term Prediction and AAC Scalable are * not supported by the FDK-AAC library. */ AAC_OBJECT_TYPE_MPEG2_AAC_LC | AAC_OBJECT_TYPE_MPEG4_AAC_LC | (eld_supported() ? AAC_OBJECT_TYPE_MPEG4_AAC_ELD : 0), AAC_INIT_FREQUENCY( AAC_SAMPLING_FREQ_8000 | AAC_SAMPLING_FREQ_11025 | AAC_SAMPLING_FREQ_12000 | AAC_SAMPLING_FREQ_16000 | AAC_SAMPLING_FREQ_22050 | AAC_SAMPLING_FREQ_24000 | AAC_SAMPLING_FREQ_32000 | AAC_SAMPLING_FREQ_44100 | AAC_SAMPLING_FREQ_48000 | AAC_SAMPLING_FREQ_64000 | AAC_SAMPLING_FREQ_88200 | AAC_SAMPLING_FREQ_96000) .channels = AAC_CHANNELS_1 | AAC_CHANNELS_2, .vbr = 1, AAC_INIT_BITRATE(DEFAULT_AAC_BITRATE) }; memcpy(caps, &a2dp_aac, sizeof(a2dp_aac)); return sizeof(a2dp_aac); } static const struct media_codec_config aac_frequencies[] = { { AAC_SAMPLING_FREQ_48000, 48000, 11 }, { AAC_SAMPLING_FREQ_44100, 44100, 10 }, { AAC_SAMPLING_FREQ_96000, 96000, 9 }, { AAC_SAMPLING_FREQ_88200, 88200, 8 }, { AAC_SAMPLING_FREQ_64000, 64000, 7 }, { AAC_SAMPLING_FREQ_32000, 32000, 6 }, { AAC_SAMPLING_FREQ_24000, 24000, 5 }, { AAC_SAMPLING_FREQ_22050, 22050, 4 }, { AAC_SAMPLING_FREQ_16000, 16000, 3 }, { AAC_SAMPLING_FREQ_12000, 12000, 2 }, { AAC_SAMPLING_FREQ_11025, 11025, 1 }, { AAC_SAMPLING_FREQ_8000, 8000, 0 }, }; static const struct media_codec_config aac_channel_modes[] = { { AAC_CHANNELS_2, 2, 1 }, { AAC_CHANNELS_1, 1, 0 }, }; static int get_valid_aac_bitrate(a2dp_aac_t *conf) { if (AAC_GET_BITRATE(*conf) < MIN_AAC_BITRATE) { /* Unknown (0) or bogus bitrate */ return DEFAULT_AAC_BITRATE; } else { return SPA_MIN(AAC_GET_BITRATE(*conf), DEFAULT_AAC_BITRATE); } } static int codec_select_config(const struct media_codec *codec, uint32_t flags, const void *caps, size_t caps_size, const struct media_codec_audio_info *info, const struct spa_dict *settings, uint8_t config[A2DP_MAX_CAPS_SIZE], void **config_data) { a2dp_aac_t conf; int i; if (caps_size < sizeof(conf)) return -EINVAL; conf = *(a2dp_aac_t*)caps; if (codec->id == SPA_BLUETOOTH_AUDIO_CODEC_AAC_ELD) { if (!eld_supported()) return -ENOTSUP; if (conf.object_type & AAC_OBJECT_TYPE_MPEG4_AAC_ELD) conf.object_type = AAC_OBJECT_TYPE_MPEG4_AAC_ELD; else return -ENOTSUP; } else { if (conf.object_type & AAC_OBJECT_TYPE_MPEG2_AAC_LC) conf.object_type = AAC_OBJECT_TYPE_MPEG2_AAC_LC; else if (conf.object_type & AAC_OBJECT_TYPE_MPEG4_AAC_LC) conf.object_type = AAC_OBJECT_TYPE_MPEG4_AAC_LC; else if (conf.object_type & AAC_OBJECT_TYPE_MPEG4_AAC_LTP) return -ENOTSUP; /* Not supported by FDK-AAC */ else if (conf.object_type & AAC_OBJECT_TYPE_MPEG4_AAC_SCA) return -ENOTSUP; /* Not supported by FDK-AAC */ else return -ENOTSUP; } if ((i = media_codec_select_config(aac_frequencies, SPA_N_ELEMENTS(aac_frequencies), AAC_GET_FREQUENCY(conf), info ? info->rate : A2DP_CODEC_DEFAULT_RATE )) < 0) return -ENOTSUP; AAC_SET_FREQUENCY(conf, aac_frequencies[i].config); if ((i = media_codec_select_config(aac_channel_modes, SPA_N_ELEMENTS(aac_channel_modes), conf.channels, info ? info->channels : A2DP_CODEC_DEFAULT_CHANNELS )) < 0) return -ENOTSUP; conf.channels = aac_channel_modes[i].config; AAC_SET_BITRATE(conf, get_valid_aac_bitrate(&conf)); memcpy(config, &conf, sizeof(conf)); return sizeof(conf); } static int codec_enum_config(const struct media_codec *codec, uint32_t flags, const void *caps, size_t caps_size, uint32_t id, uint32_t idx, struct spa_pod_builder *b, struct spa_pod **param) { a2dp_aac_t conf; struct spa_pod_frame f[2]; struct spa_pod_choice *choice; uint32_t position[2]; uint32_t i = 0; if (caps_size < sizeof(conf)) return -EINVAL; memcpy(&conf, caps, sizeof(conf)); if (idx > 0) return 0; spa_pod_builder_push_object(b, &f[0], SPA_TYPE_OBJECT_Format, id); spa_pod_builder_add(b, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_audio), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), SPA_FORMAT_AUDIO_format, SPA_POD_Id(SPA_AUDIO_FORMAT_S16), 0); spa_pod_builder_prop(b, SPA_FORMAT_AUDIO_rate, 0); spa_pod_builder_push_choice(b, &f[1], SPA_CHOICE_None, 0); choice = (struct spa_pod_choice*)spa_pod_builder_frame(b, &f[1]); i = 0; SPA_FOR_EACH_ELEMENT_VAR(aac_frequencies, f) { if (AAC_GET_FREQUENCY(conf) & f->config) { if (i++ == 0) spa_pod_builder_int(b, f->value); spa_pod_builder_int(b, f->value); } } if (i > 1) choice->body.type = SPA_CHOICE_Enum; spa_pod_builder_pop(b, &f[1]); if (i == 0) return -EINVAL; if (SPA_FLAG_IS_SET(conf.channels, AAC_CHANNELS_1 | AAC_CHANNELS_2)) { spa_pod_builder_add(b, SPA_FORMAT_AUDIO_channels, SPA_POD_CHOICE_RANGE_Int(2, 1, 2), 0); } else if (conf.channels & AAC_CHANNELS_1) { position[0] = SPA_AUDIO_CHANNEL_MONO; spa_pod_builder_add(b, SPA_FORMAT_AUDIO_channels, SPA_POD_Int(1), SPA_FORMAT_AUDIO_position, SPA_POD_Array(sizeof(uint32_t), SPA_TYPE_Id, 1, position), 0); } else if (conf.channels & AAC_CHANNELS_2) { position[0] = SPA_AUDIO_CHANNEL_FL; position[1] = SPA_AUDIO_CHANNEL_FR; spa_pod_builder_add(b, SPA_FORMAT_AUDIO_channels, SPA_POD_Int(2), SPA_FORMAT_AUDIO_position, SPA_POD_Array(sizeof(uint32_t), SPA_TYPE_Id, 2, position), 0); } else return -EINVAL; *param = spa_pod_builder_pop(b, &f[0]); return *param == NULL ? -EIO : 1; } static int codec_validate_config(const struct media_codec *codec, uint32_t flags, const void *caps, size_t caps_size, struct spa_audio_info *info) { a2dp_aac_t conf; size_t j; if (caps == NULL || caps_size < sizeof(conf)) return -EINVAL; memcpy(&conf, caps, sizeof(conf)); spa_zero(*info); info->media_type = SPA_MEDIA_TYPE_audio; info->media_subtype = SPA_MEDIA_SUBTYPE_raw; info->info.raw.format = SPA_AUDIO_FORMAT_S16; /* * A2DP v1.3.2, 4.5.2: only one bit shall be set in bitfields. * However, there is a report (#1342) of device setting multiple * bits for AAC object type. It's not clear if this was due to * a BlueZ bug, but we can be lax here and below in codec_init. */ if (!(conf.object_type & (AAC_OBJECT_TYPE_MPEG2_AAC_LC | AAC_OBJECT_TYPE_MPEG4_AAC_LC | AAC_OBJECT_TYPE_MPEG4_AAC_ELD))) return -EINVAL; j = 0; SPA_FOR_EACH_ELEMENT_VAR(aac_frequencies, f) { if (AAC_GET_FREQUENCY(conf) & f->config) { info->info.raw.rate = f->value; j++; break; } } if (j == 0) return -EINVAL; if (conf.channels & AAC_CHANNELS_2) { info->info.raw.channels = 2; info->info.raw.position[0] = SPA_AUDIO_CHANNEL_FL; info->info.raw.position[1] = SPA_AUDIO_CHANNEL_FR; } else if (conf.channels & AAC_CHANNELS_1) { info->info.raw.channels = 1; info->info.raw.position[0] = SPA_AUDIO_CHANNEL_MONO; } else { return -EINVAL; } return 0; } static void *codec_init_props(const struct media_codec *codec, uint32_t flags, const struct spa_dict *settings) { struct props *p = calloc(1, sizeof(struct props)); const char *str; if (p == NULL) return NULL; if (settings == NULL || (str = spa_dict_lookup(settings, "bluez5.a2dp.aac.bitratemode")) == NULL) str = "0"; p->bitratemode = SPA_CLAMP(atoi(str), 0, 5); return p; } static void codec_clear_props(void *props) { free(props); } static void *codec_init(const struct media_codec *codec, uint32_t flags, void *config, size_t config_len, const struct spa_audio_info *info, void *props, size_t mtu) { struct impl *this; a2dp_aac_t *conf = config; struct props *p = props; UINT bitratemode; int res; this = calloc(1, sizeof(struct impl)); if (this == NULL) { res = -errno; goto error; } this->mtu = mtu; this->rate = info->info.raw.rate; this->channels = info->info.raw.channels; if (info->media_type != SPA_MEDIA_TYPE_audio || info->media_subtype != SPA_MEDIA_SUBTYPE_raw || info->info.raw.format != SPA_AUDIO_FORMAT_S16) { res = -EINVAL; goto error; } this->samplesize = 2; bitratemode = p ? p->bitratemode : 0; res = aacEncOpen(&this->aacenc, 0, this->channels); if (res != AACENC_OK) goto error; /* If object type has multiple bits set (invalid per spec, see above), * assume the device usually means AAC-LC. */ if (conf->object_type & AAC_OBJECT_TYPE_MPEG4_AAC_LC) { res = aacEncoder_SetParam(this->aacenc, AACENC_AOT, AOT_AAC_LC); if (res != AACENC_OK) goto error; } else if (conf->object_type & AAC_OBJECT_TYPE_MPEG2_AAC_LC) { res = aacEncoder_SetParam(this->aacenc, AACENC_AOT, AOT_MP2_AAC_LC); if (res != AACENC_OK) goto error; } else if (conf->object_type & AAC_OBJECT_TYPE_MPEG4_AAC_ELD) { res = aacEncoder_SetParam(this->aacenc, AACENC_AOT, AOT_ER_AAC_ELD); if (res != AACENC_OK) goto error; res = aacEncoder_SetParam(this->aacenc, AACENC_SBR_MODE, 1); if (res != AACENC_OK) goto error; } else { res = -EINVAL; goto error; } res = aacEncoder_SetParam(this->aacenc, AACENC_SAMPLERATE, this->rate); if (res != AACENC_OK) goto error; res = aacEncoder_SetParam(this->aacenc, AACENC_CHANNELMODE, this->channels); if (res != AACENC_OK) goto error; if (conf->vbr) { res = aacEncoder_SetParam(this->aacenc, AACENC_BITRATEMODE, bitratemode); if (res != AACENC_OK) goto error; } res = aacEncoder_SetParam(this->aacenc, AACENC_AUDIOMUXVER, 2); if (res != AACENC_OK) goto error; res = aacEncoder_SetParam(this->aacenc, AACENC_SIGNALING_MODE, 1); if (res != AACENC_OK) goto error; // Fragmentation is not implemented yet, // so make sure every encoded AAC frame fits in (mtu - header) this->max_bitrate = ((this->mtu - sizeof(struct rtp_header)) * 8 * this->rate) / 1024; this->max_bitrate = SPA_MIN(this->max_bitrate, get_valid_aac_bitrate(conf)); this->cur_bitrate = this->max_bitrate; res = aacEncoder_SetParam(this->aacenc, AACENC_BITRATE, this->cur_bitrate); if (res != AACENC_OK) goto error; res = aacEncoder_SetParam(this->aacenc, AACENC_PEAK_BITRATE, this->max_bitrate); if (res != AACENC_OK) goto error; res = aacEncoder_SetParam(this->aacenc, AACENC_TRANSMUX, TT_MP4_LATM_MCP1); if (res != AACENC_OK) goto error; res = aacEncoder_SetParam(this->aacenc, AACENC_HEADER_PERIOD, 1); if (res != AACENC_OK) goto error; res = aacEncoder_SetParam(this->aacenc, AACENC_AFTERBURNER, 1); if (res != AACENC_OK) goto error; res = aacEncEncode(this->aacenc, NULL, NULL, NULL, NULL); if (res != AACENC_OK) goto error; AACENC_InfoStruct enc_info = {}; res = aacEncInfo(this->aacenc, &enc_info); if (res != AACENC_OK) goto error; this->enc_delay = enc_info.nDelay; this->codesize = enc_info.frameLength * this->channels * this->samplesize; this->aacdec = aacDecoder_Open(TT_MP4_LATM_MCP1, 1); if (!this->aacdec) { res = -EINVAL; goto error; } #ifdef AACDECODER_LIB_VL0 res = aacDecoder_SetParam(this->aacdec, AAC_PCM_MIN_OUTPUT_CHANNELS, this->channels); if (res != AAC_DEC_OK) { spa_log_debug(log, "Couldn't set min output channels: 0x%04X", res); goto error; } res = aacDecoder_SetParam(this->aacdec, AAC_PCM_MAX_OUTPUT_CHANNELS, this->channels); if (res != AAC_DEC_OK) { spa_log_debug(log, "Couldn't set max output channels: 0x%04X", res); goto error; } #else res = aacDecoder_SetParam(this->aacdec, AAC_PCM_OUTPUT_CHANNELS, this->channels); if (res != AAC_DEC_OK) { spa_log_debug(log, "Couldn't set output channels: 0x%04X", res); goto error; } #endif this->dec_delay = 0; return this; error: if (this && this->aacenc) aacEncClose(&this->aacenc); if (this && this->aacdec) aacDecoder_Close(this->aacdec); free(this); errno = -res; return NULL; } static void codec_deinit(void *data) { struct impl *this = data; if (this->aacenc) aacEncClose(&this->aacenc); if (this->aacdec) aacDecoder_Close(this->aacdec); free(this); } static int codec_get_block_size(void *data) { struct impl *this = data; return this->codesize; } static int codec_start_encode (void *data, void *dst, size_t dst_size, uint16_t seqnum, uint32_t timestamp) { struct impl *this = data; this->header = (struct rtp_header *)dst; memset(this->header, 0, sizeof(struct rtp_header)); this->header->v = 2; this->header->pt = 96; this->header->sequence_number = htons(seqnum); this->header->timestamp = htonl(timestamp); this->header->ssrc = htonl(1); return sizeof(struct rtp_header); } static int codec_encode(void *data, const void *src, size_t src_size, void *dst, size_t dst_size, size_t *dst_out, int *need_flush) { struct impl *this = data; int res; void *in_bufs[] = {(void *) src}; int in_buf_ids[] = {IN_AUDIO_DATA}; int in_buf_sizes[] = {src_size}; int in_buf_el_sizes[] = {this->samplesize}; AACENC_BufDesc in_buf_desc = { .numBufs = 1, .bufs = in_bufs, .bufferIdentifiers = in_buf_ids, .bufSizes = in_buf_sizes, .bufElSizes = in_buf_el_sizes, }; AACENC_InArgs in_args = { .numInSamples = src_size / this->samplesize, }; void *out_bufs[] = {dst}; int out_buf_ids[] = {OUT_BITSTREAM_DATA}; int out_buf_sizes[] = {dst_size}; int out_buf_el_sizes[] = {this->samplesize}; AACENC_BufDesc out_buf_desc = { .numBufs = 1, .bufs = out_bufs, .bufferIdentifiers = out_buf_ids, .bufSizes = out_buf_sizes, .bufElSizes = out_buf_el_sizes, }; AACENC_OutArgs out_args = {}; res = aacEncEncode(this->aacenc, &in_buf_desc, &out_buf_desc, &in_args, &out_args); if (res != AACENC_OK) return -EINVAL; *dst_out = out_args.numOutBytes; *need_flush = NEED_FLUSH_ALL; /* RFC6416: It is set to 1 to indicate that the RTP packet contains a complete * audioMuxElement or the last fragment of an audioMuxElement */ this->header->m = 1; return out_args.numInSamples * this->samplesize; } static int codec_start_decode (void *data, const void *src, size_t src_size, uint16_t *seqnum, uint32_t *timestamp) { const struct rtp_header *header = src; size_t header_size = sizeof(struct rtp_header); if (src_size <= header_size) return -EINVAL; if (seqnum) *seqnum = ntohs(header->sequence_number); if (timestamp) *timestamp = ntohl(header->timestamp); return header_size; } static int codec_decode(void *data, const void *src, size_t src_size, void *dst, size_t dst_size, size_t *dst_out) { struct impl *this = data; uint data_size = (uint)src_size; uint bytes_valid = data_size; CStreamInfo *aacinf; int res; res = aacDecoder_Fill(this->aacdec, (UCHAR **)&src, &data_size, &bytes_valid); if (res != AAC_DEC_OK) { spa_log_debug(log, "AAC buffer fill error: 0x%04X", res); return -EINVAL; } res = aacDecoder_DecodeFrame(this->aacdec, dst, dst_size, 0); if (res != AAC_DEC_OK) { spa_log_debug(log, "AAC decode frame error: 0x%04X", res); return -EINVAL; } aacinf = aacDecoder_GetStreamInfo(this->aacdec); if (!aacinf) { spa_log_debug(log, "AAC get stream info failed"); return -EINVAL; } *dst_out = aacinf->frameSize * aacinf->numChannels * this->samplesize; return src_size - bytes_valid; } static int codec_abr_process (void *data, size_t unsent) { return -ENOTSUP; } static int codec_change_bitrate(struct impl *this, int new_bitrate) { int res; new_bitrate = SPA_MIN(new_bitrate, this->max_bitrate); new_bitrate = SPA_MAX(new_bitrate, 64000); if (new_bitrate == this->cur_bitrate) return 0; this->cur_bitrate = new_bitrate; res = aacEncoder_SetParam(this->aacenc, AACENC_BITRATE, this->cur_bitrate); if (res != AACENC_OK) return -EINVAL; return this->cur_bitrate; } static int codec_reduce_bitpool(void *data) { struct impl *this = data; return codec_change_bitrate(this, (this->cur_bitrate * 2) / 3); } static int codec_increase_bitpool(void *data) { struct impl *this = data; return codec_change_bitrate(this, (this->cur_bitrate * 4) / 3); } static void codec_get_delay(void *data, uint32_t *encoder, uint32_t *decoder) { struct impl *this = data; if (encoder) *encoder = this->enc_delay; if (decoder) { CStreamInfo *info = aacDecoder_GetStreamInfo(this->aacdec); if (info) this->dec_delay = info->outputDelay; *decoder = this->dec_delay; } } static void codec_set_log(struct spa_log *global_log) { log = global_log; spa_log_topic_init(log, &codec_plugin_log_topic); } const struct media_codec a2dp_codec_aac = { .id = SPA_BLUETOOTH_AUDIO_CODEC_AAC, .kind = MEDIA_CODEC_A2DP, .codec_id = A2DP_CODEC_MPEG24, .name = "aac", .description = "AAC", .fill_caps = codec_fill_caps, .select_config = codec_select_config, .enum_config = codec_enum_config, .validate_config = codec_validate_config, .init_props = codec_init_props, .clear_props = codec_clear_props, .init = codec_init, .deinit = codec_deinit, .get_block_size = codec_get_block_size, .start_encode = codec_start_encode, .encode = codec_encode, .start_decode = codec_start_decode, .decode = codec_decode, .abr_process = codec_abr_process, .reduce_bitpool = codec_reduce_bitpool, .increase_bitpool = codec_increase_bitpool, .set_log = codec_set_log, .get_delay = codec_get_delay, }; const struct media_codec a2dp_codec_aac_eld = { .id = SPA_BLUETOOTH_AUDIO_CODEC_AAC_ELD, .kind = MEDIA_CODEC_A2DP, .codec_id = A2DP_CODEC_MPEG24, .name = "aac_eld", .description = "AAC-ELD", .endpoint_name = "aac", .fill_caps = NULL, .select_config = codec_select_config, .enum_config = codec_enum_config, .validate_config = codec_validate_config, .init_props = codec_init_props, .clear_props = codec_clear_props, .init = codec_init, .deinit = codec_deinit, .get_block_size = codec_get_block_size, .start_encode = codec_start_encode, .encode = codec_encode, .start_decode = codec_start_decode, .decode = codec_decode, .abr_process = codec_abr_process, .reduce_bitpool = codec_reduce_bitpool, .increase_bitpool = codec_increase_bitpool, .set_log = codec_set_log, .get_delay = codec_get_delay, }; MEDIA_CODEC_EXPORT_DEF( "aac", &a2dp_codec_aac, &a2dp_codec_aac_eld ); pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/bluez5/a2dp-codec-aptx.c000066400000000000000000000466641511204443500272070ustar00rootroot00000000000000/* Spa A2DP aptX codec */ /* SPDX-FileCopyrightText: Copyright © 2020 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #include #include #include #include #include "rtp.h" #include "media-codecs.h" #define APTX_LL_LEVEL1(level) (((level) >> 8) & 0xFF) #define APTX_LL_LEVEL2(level) (((level) >> 0) & 0xFF) #define APTX_LL_LEVEL(level1, level2) ((((level1) & 0xFF) << 8) | (((level2) & 0xFF) << 0)) #define MSBC_DECODED_SIZE 240 #define MSBC_ENCODED_SIZE 60 #define MSBC_PAYLOAD_SIZE 57 /* * XXX: Bump requested device buffer levels up by 50% from defaults, * XXX: increasing latency similarly. This seems to be necessary for * XXX: stable output when moving headphones. It might be possible to * XXX: reduce this by changing the scheduling of the socket writes. */ #define LL_LEVEL_ADJUSTMENT 3/2 struct impl { struct aptx_context *aptx; struct rtp_header *header; size_t mtu; int codesize; int frame_length; int frame_count; int max_frames; bool hd; }; struct msbc_impl { sbc_t msbc; }; static inline bool codec_is_hd(const struct media_codec *codec) { return codec->vendor.codec_id == APTX_HD_CODEC_ID && codec->vendor.vendor_id == APTX_HD_VENDOR_ID; } static inline bool codec_is_ll(const struct media_codec *codec) { return (codec->id == SPA_BLUETOOTH_AUDIO_CODEC_APTX_LL) || (codec->id == SPA_BLUETOOTH_AUDIO_CODEC_APTX_LL_DUPLEX); } static inline size_t codec_get_caps_size(const struct media_codec *codec) { if (codec_is_hd(codec)) return sizeof(a2dp_aptx_hd_t); else if (codec_is_ll(codec)) return sizeof(a2dp_aptx_ll_t); else return sizeof(a2dp_aptx_t); } static int codec_fill_caps(const struct media_codec *codec, uint32_t flags, const struct spa_dict *settings, uint8_t caps[A2DP_MAX_CAPS_SIZE]) { size_t actual_conf_size = codec_get_caps_size(codec); const a2dp_aptx_t a2dp_aptx = { .info = codec->vendor, .frequency = APTX_SAMPLING_FREQ_16000 | APTX_SAMPLING_FREQ_32000 | APTX_SAMPLING_FREQ_44100 | APTX_SAMPLING_FREQ_48000, .channel_mode = APTX_CHANNEL_MODE_STEREO, }; const a2dp_aptx_ll_t a2dp_aptx_ll = { .aptx = a2dp_aptx, .bidirect_link = codec->duplex_codec ? true : false, .has_new_caps = false, }; if (codec_is_ll(codec)) memcpy(caps, &a2dp_aptx_ll, sizeof(a2dp_aptx_ll)); else memcpy(caps, &a2dp_aptx, sizeof(a2dp_aptx)); return actual_conf_size; } static const struct media_codec_config aptx_frequencies[] = { { APTX_SAMPLING_FREQ_48000, 48000, 3 }, { APTX_SAMPLING_FREQ_44100, 44100, 2 }, { APTX_SAMPLING_FREQ_32000, 32000, 1 }, { APTX_SAMPLING_FREQ_16000, 16000, 0 }, }; static int codec_select_config(const struct media_codec *codec, uint32_t flags, const void *caps, size_t caps_size, const struct media_codec_audio_info *info, const struct spa_dict *settings, uint8_t config[A2DP_MAX_CAPS_SIZE], void **config_data) { a2dp_aptx_t conf; int i; size_t actual_conf_size = codec_get_caps_size(codec); if (caps_size < sizeof(conf) || actual_conf_size < sizeof(conf)) return -EINVAL; memcpy(&conf, caps, sizeof(conf)); if (codec->vendor.vendor_id != conf.info.vendor_id || codec->vendor.codec_id != conf.info.codec_id) return -ENOTSUP; if ((i = media_codec_select_config(aptx_frequencies, SPA_N_ELEMENTS(aptx_frequencies), conf.frequency, info ? info->rate : A2DP_CODEC_DEFAULT_RATE )) < 0) return -ENOTSUP; conf.frequency = aptx_frequencies[i].config; if (conf.channel_mode & APTX_CHANNEL_MODE_STEREO) conf.channel_mode = APTX_CHANNEL_MODE_STEREO; else return -ENOTSUP; memcpy(config, &conf, sizeof(conf)); return actual_conf_size; } static int codec_select_config_ll(const struct media_codec *codec, uint32_t flags, const void *caps, size_t caps_size, const struct media_codec_audio_info *info, const struct spa_dict *settings, uint8_t config[A2DP_MAX_CAPS_SIZE], void **config_data) { a2dp_aptx_ll_ext_t conf = { 0 }; size_t actual_conf_size; int res; /* caps may contain only conf.base, or also the extended attributes */ if (caps_size < sizeof(conf.base)) return -EINVAL; memcpy(&conf, caps, SPA_MIN(caps_size, sizeof(conf))); actual_conf_size = conf.base.has_new_caps ? sizeof(conf) : sizeof(conf.base); if (caps_size < actual_conf_size) return -EINVAL; if (codec->duplex_codec && !conf.base.bidirect_link) return -ENOTSUP; if ((res = codec_select_config(codec, flags, caps, caps_size, info, settings, config, NULL)) < 0) return res; memcpy(&conf.base.aptx, config, sizeof(conf.base.aptx)); if (conf.base.has_new_caps) { int target_level = APTX_LL_LEVEL(conf.target_level1, conf.target_level2); int initial_level = APTX_LL_LEVEL(conf.initial_level1, conf.initial_level2); int good_working_level = APTX_LL_LEVEL(conf.good_working_level1, conf.good_working_level2); target_level = SPA_MAX(target_level, APTX_LL_TARGET_CODEC_LEVEL * LL_LEVEL_ADJUSTMENT); initial_level = SPA_MAX(initial_level, APTX_LL_INITIAL_CODEC_LEVEL * LL_LEVEL_ADJUSTMENT); good_working_level = SPA_MAX(good_working_level, APTX_LL_GOOD_WORKING_LEVEL * LL_LEVEL_ADJUSTMENT); conf.target_level1 = APTX_LL_LEVEL1(target_level); conf.target_level2 = APTX_LL_LEVEL2(target_level); conf.initial_level1 = APTX_LL_LEVEL1(initial_level); conf.initial_level2 = APTX_LL_LEVEL2(initial_level); conf.good_working_level1 = APTX_LL_LEVEL1(good_working_level); conf.good_working_level2 = APTX_LL_LEVEL2(good_working_level); if (conf.sra_max_rate == 0) conf.sra_max_rate = APTX_LL_SRA_MAX_RATE; if (conf.sra_avg_time == 0) conf.sra_avg_time = APTX_LL_SRA_AVG_TIME; } memcpy(config, &conf, actual_conf_size); return actual_conf_size; } static int codec_enum_config(const struct media_codec *codec, uint32_t flags, const void *caps, size_t caps_size, uint32_t id, uint32_t idx, struct spa_pod_builder *b, struct spa_pod **param) { a2dp_aptx_t conf; struct spa_pod_frame f[2]; struct spa_pod_choice *choice; uint32_t position[2]; uint32_t i = 0; if (caps_size < sizeof(conf)) return -EINVAL; memcpy(&conf, caps, sizeof(conf)); if (idx > 0) return 0; spa_pod_builder_push_object(b, &f[0], SPA_TYPE_OBJECT_Format, id); spa_pod_builder_add(b, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_audio), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), SPA_FORMAT_AUDIO_format, SPA_POD_Id(SPA_AUDIO_FORMAT_S24), 0); spa_pod_builder_prop(b, SPA_FORMAT_AUDIO_rate, 0); spa_pod_builder_push_choice(b, &f[1], SPA_CHOICE_None, 0); choice = (struct spa_pod_choice*)spa_pod_builder_frame(b, &f[1]); i = 0; if (conf.frequency & APTX_SAMPLING_FREQ_48000) { if (i++ == 0) spa_pod_builder_int(b, 48000); spa_pod_builder_int(b, 48000); } if (conf.frequency & APTX_SAMPLING_FREQ_44100) { if (i++ == 0) spa_pod_builder_int(b, 44100); spa_pod_builder_int(b, 44100); } if (conf.frequency & APTX_SAMPLING_FREQ_32000) { if (i++ == 0) spa_pod_builder_int(b, 32000); spa_pod_builder_int(b, 32000); } if (conf.frequency & APTX_SAMPLING_FREQ_16000) { if (i++ == 0) spa_pod_builder_int(b, 16000); spa_pod_builder_int(b, 16000); } if (i > 1) choice->body.type = SPA_CHOICE_Enum; spa_pod_builder_pop(b, &f[1]); if (i == 0) return -EINVAL; if (SPA_FLAG_IS_SET(conf.channel_mode, APTX_CHANNEL_MODE_MONO | APTX_CHANNEL_MODE_STEREO)) { spa_pod_builder_add(b, SPA_FORMAT_AUDIO_channels, SPA_POD_CHOICE_RANGE_Int(2, 1, 2), 0); } else if (conf.channel_mode & APTX_CHANNEL_MODE_MONO) { position[0] = SPA_AUDIO_CHANNEL_MONO; spa_pod_builder_add(b, SPA_FORMAT_AUDIO_channels, SPA_POD_Int(1), SPA_FORMAT_AUDIO_position, SPA_POD_Array(sizeof(uint32_t), SPA_TYPE_Id, 1, position), 0); } else if (conf.channel_mode & APTX_CHANNEL_MODE_STEREO) { position[0] = SPA_AUDIO_CHANNEL_FL; position[1] = SPA_AUDIO_CHANNEL_FR; spa_pod_builder_add(b, SPA_FORMAT_AUDIO_channels, SPA_POD_Int(2), SPA_FORMAT_AUDIO_position, SPA_POD_Array(sizeof(uint32_t), SPA_TYPE_Id, 2, position), 0); } else return -EINVAL; *param = spa_pod_builder_pop(b, &f[0]); return *param == NULL ? -EIO : 1; } static int codec_reduce_bitpool(void *data) { return -ENOTSUP; } static int codec_increase_bitpool(void *data) { return -ENOTSUP; } static int codec_get_block_size(void *data) { struct impl *this = data; return this->codesize; } static void *codec_init(const struct media_codec *codec, uint32_t flags, void *config, size_t config_len, const struct spa_audio_info *info, void *props, size_t mtu) { struct impl *this = NULL; a2dp_aptx_t conf; int res; int frequency; if (config_len < sizeof(conf)) { res = -EINVAL; goto error; } memcpy(&conf, config, sizeof(conf)); if ((this = calloc(1, sizeof(struct impl))) == NULL) goto error_errno; this->hd = codec_is_hd(codec); if ((this->aptx = aptx_init(this->hd)) == NULL) goto error_errno; this->mtu = mtu; if (info->media_type != SPA_MEDIA_TYPE_audio || info->media_subtype != SPA_MEDIA_SUBTYPE_raw || info->info.raw.format != SPA_AUDIO_FORMAT_S24) { res = -EINVAL; goto error; } this->frame_length = this->hd ? 6 : 4; this->codesize = 4 * 3 * 2; frequency = media_codec_get_config(aptx_frequencies, SPA_N_ELEMENTS(aptx_frequencies), conf.frequency); if (frequency < 0) { res = -EINVAL; goto error; } if (this->hd) this->max_frames = (this->mtu - sizeof(struct rtp_header)) / this->frame_length; else if (codec_is_ll(codec)) /* try to make 7.5ms packets */ this->max_frames = SPA_MIN((unsigned)frequency * 75u/10000u / 4u, this->mtu / this->frame_length); else this->max_frames = this->mtu / this->frame_length; return this; error_errno: res = -errno; goto error; error: if (this->aptx) aptx_finish(this->aptx); free(this); errno = -res; return NULL; } static void codec_deinit(void *data) { struct impl *this = data; aptx_finish(this->aptx); free(this); } static int codec_abr_process (void *data, size_t unsent) { return -ENOTSUP; } static int codec_start_encode (void *data, void *dst, size_t dst_size, uint16_t seqnum, uint32_t timestamp) { struct impl *this = data; this->frame_count = 0; if (!this->hd) return 0; this->header = (struct rtp_header *)dst; memset(this->header, 0, sizeof(struct rtp_header)); this->header->v = 2; this->header->pt = 96; this->header->sequence_number = htons(seqnum); this->header->timestamp = htonl(timestamp); return sizeof(struct rtp_header); } static int codec_encode(void *data, const void *src, size_t src_size, void *dst, size_t dst_size, size_t *dst_out, int *need_flush) { struct impl *this = data; size_t avail_dst_size; int res; avail_dst_size = (this->max_frames - this->frame_count) * this->frame_length; if (SPA_UNLIKELY(dst_size < avail_dst_size)) { *need_flush = NEED_FLUSH_ALL; return 0; } res = aptx_encode(this->aptx, src, src_size, dst, avail_dst_size, dst_out); if(SPA_UNLIKELY(res < 0)) return -EINVAL; this->frame_count += *dst_out / this->frame_length; *need_flush = (this->frame_count >= this->max_frames) ? NEED_FLUSH_ALL : NEED_FLUSH_NO; return res; } static int codec_start_decode (void *data, const void *src, size_t src_size, uint16_t *seqnum, uint32_t *timestamp) { struct impl *this = data; if (!this->hd) return 0; const struct rtp_header *header = src; size_t header_size = sizeof(struct rtp_header); if (src_size <= header_size) return -EINVAL; if (seqnum) *seqnum = ntohs(header->sequence_number); if (timestamp) *timestamp = ntohl(header->timestamp); return header_size; } static int codec_decode(void *data, const void *src, size_t src_size, void *dst, size_t dst_size, size_t *dst_out) { struct impl *this = data; int res; res = aptx_decode(this->aptx, src, src_size, dst, dst_size, dst_out); return res; } static void codec_get_delay(void *data, uint32_t *encoder, uint32_t *decoder) { if (encoder) *encoder = 90; if (decoder) *decoder = 0; } /* * mSBC duplex codec * * When connected as SRC to SNK, aptX-LL sink may send back mSBC data. */ static int msbc_enum_config(const struct media_codec *codec, uint32_t flags, const void *caps, size_t caps_size, uint32_t id, uint32_t idx, struct spa_pod_builder *b, struct spa_pod **param) { struct spa_audio_info_raw info = { 0, }; if (caps_size < sizeof(a2dp_aptx_ll_t)) return -EINVAL; if (idx > 0) return 0; info.format = SPA_AUDIO_FORMAT_S16_LE; info.channels = 1; info.position[0] = SPA_AUDIO_CHANNEL_MONO; info.rate = 16000; *param = spa_format_audio_raw_build(b, id, &info); return *param == NULL ? -EIO : 1; } static int msbc_validate_config(const struct media_codec *codec, uint32_t flags, const void *caps, size_t caps_size, struct spa_audio_info *info) { spa_zero(*info); info->media_type = SPA_MEDIA_TYPE_audio; info->media_subtype = SPA_MEDIA_SUBTYPE_raw; info->info.raw.format = SPA_AUDIO_FORMAT_S16_LE; info->info.raw.channels = 1; info->info.raw.position[0] = SPA_AUDIO_CHANNEL_MONO; info->info.raw.rate = 16000; return 0; } static int msbc_reduce_bitpool(void *data) { return -ENOTSUP; } static int msbc_increase_bitpool(void *data) { return -ENOTSUP; } static int msbc_get_block_size(void *data) { return MSBC_DECODED_SIZE; } static void *msbc_init(const struct media_codec *codec, uint32_t flags, void *config, size_t config_len, const struct spa_audio_info *info, void *props, size_t mtu) { struct msbc_impl *this = NULL; int res; if (info->media_type != SPA_MEDIA_TYPE_audio || info->media_subtype != SPA_MEDIA_SUBTYPE_raw || info->info.raw.format != SPA_AUDIO_FORMAT_S16_LE) { res = -EINVAL; goto error; } if ((this = calloc(1, sizeof(struct msbc_impl))) == NULL) goto error_errno; if ((res = sbc_init_msbc(&this->msbc, 0)) < 0) goto error; this->msbc.endian = SBC_LE; return this; error_errno: res = -errno; goto error; error: free(this); errno = -res; return NULL; } static void msbc_deinit(void *data) { struct msbc_impl *this = data; sbc_finish(&this->msbc); free(this); } static int msbc_abr_process (void *data, size_t unsent) { return -ENOTSUP; } static int msbc_start_encode (void *data, void *dst, size_t dst_size, uint16_t seqnum, uint32_t timestamp) { return -ENOTSUP; } static int msbc_encode(void *data, const void *src, size_t src_size, void *dst, size_t dst_size, size_t *dst_out, int *need_flush) { return -ENOTSUP; } static int msbc_start_decode (void *data, const void *src, size_t src_size, uint16_t *seqnum, uint32_t *timestamp) { return 0; } static int msbc_decode(void *data, const void *src, size_t src_size, void *dst, size_t dst_size, size_t *dst_out) { struct msbc_impl *this = data; const uint8_t sync[3] = { 0xAD, 0x00, 0x00 }; size_t processed = 0; int res; spa_assert(sizeof(sync) <= MSBC_PAYLOAD_SIZE); *dst_out = 0; /* Scan for msbc sync sequence. * We could probably assume fixed (<57-byte payload><1-byte pad>)+ format * which devices seem to be sending. Don't know if there are variations, * so we make weaker assumption here. */ while (src_size >= MSBC_PAYLOAD_SIZE) { if (memcmp(src, sync, sizeof(sync)) == 0) break; src = (uint8_t*)src + 1; --src_size; ++processed; } res = sbc_decode(&this->msbc, src, src_size, dst, dst_size, dst_out); if (res <= 0) res = SPA_MIN((size_t)MSBC_PAYLOAD_SIZE, src_size); /* skip bad payload */ processed += res; return processed; } const struct media_codec a2dp_codec_aptx = { .id = SPA_BLUETOOTH_AUDIO_CODEC_APTX, .kind = MEDIA_CODEC_A2DP, .codec_id = A2DP_CODEC_VENDOR, .vendor = { .vendor_id = APTX_VENDOR_ID, .codec_id = APTX_CODEC_ID }, .name = "aptx", .description = "aptX", .fill_caps = codec_fill_caps, .select_config = codec_select_config, .enum_config = codec_enum_config, .init = codec_init, .deinit = codec_deinit, .get_block_size = codec_get_block_size, .abr_process = codec_abr_process, .start_encode = codec_start_encode, .encode = codec_encode, .start_decode = codec_start_decode, .decode = codec_decode, .reduce_bitpool = codec_reduce_bitpool, .increase_bitpool = codec_increase_bitpool, .get_delay = codec_get_delay, }; const struct media_codec a2dp_codec_aptx_hd = { .id = SPA_BLUETOOTH_AUDIO_CODEC_APTX_HD, .kind = MEDIA_CODEC_A2DP, .codec_id = A2DP_CODEC_VENDOR, .vendor = { .vendor_id = APTX_HD_VENDOR_ID, .codec_id = APTX_HD_CODEC_ID }, .name = "aptx_hd", .description = "aptX HD", .fill_caps = codec_fill_caps, .select_config = codec_select_config, .enum_config = codec_enum_config, .init = codec_init, .deinit = codec_deinit, .get_block_size = codec_get_block_size, .abr_process = codec_abr_process, .start_encode = codec_start_encode, .encode = codec_encode, .start_decode = codec_start_decode, .decode = codec_decode, .reduce_bitpool = codec_reduce_bitpool, .increase_bitpool = codec_increase_bitpool, .get_delay = codec_get_delay, }; #define APTX_LL_COMMON_DEFS \ .kind = MEDIA_CODEC_A2DP, \ .codec_id = A2DP_CODEC_VENDOR, \ .description = "aptX-LL", \ .fill_caps = codec_fill_caps, \ .select_config = codec_select_config_ll, \ .enum_config = codec_enum_config, \ .init = codec_init, \ .deinit = codec_deinit, \ .get_block_size = codec_get_block_size, \ .abr_process = codec_abr_process, \ .start_encode = codec_start_encode, \ .encode = codec_encode, \ .reduce_bitpool = codec_reduce_bitpool, \ .increase_bitpool = codec_increase_bitpool, \ .get_delay = codec_get_delay const struct media_codec a2dp_codec_aptx_ll_0 = { APTX_LL_COMMON_DEFS, .id = SPA_BLUETOOTH_AUDIO_CODEC_APTX_LL, .vendor = { .vendor_id = APTX_LL_VENDOR_ID, .codec_id = APTX_LL_CODEC_ID }, .name = "aptx_ll", .endpoint_name = "aptx_ll_0", }; const struct media_codec a2dp_codec_aptx_ll_1 = { APTX_LL_COMMON_DEFS, .id = SPA_BLUETOOTH_AUDIO_CODEC_APTX_LL, .vendor = { .vendor_id = APTX_LL_VENDOR_ID2, .codec_id = APTX_LL_CODEC_ID }, .name = "aptx_ll", .endpoint_name = "aptx_ll_1", }; /* Voice channel mSBC, not a real A2DP codec */ static const struct media_codec aptx_ll_msbc = { .codec_id = A2DP_CODEC_VENDOR, .name = "aptx_ll_msbc", .description = "aptX-LL mSBC", .fill_caps = codec_fill_caps, .select_config = codec_select_config_ll, .enum_config = msbc_enum_config, .validate_config = msbc_validate_config, .init = msbc_init, .deinit = msbc_deinit, .get_block_size = msbc_get_block_size, .abr_process = msbc_abr_process, .start_encode = msbc_start_encode, .encode = msbc_encode, .start_decode = msbc_start_decode, .decode = msbc_decode, .reduce_bitpool = msbc_reduce_bitpool, .increase_bitpool = msbc_increase_bitpool, }; static const struct spa_dict_item duplex_info_items[] = { { "duplex.boost", "true" }, }; static const struct spa_dict duplex_info = SPA_DICT_INIT_ARRAY(duplex_info_items); const struct media_codec a2dp_codec_aptx_ll_duplex_0 = { APTX_LL_COMMON_DEFS, .id = SPA_BLUETOOTH_AUDIO_CODEC_APTX_LL_DUPLEX, .vendor = { .vendor_id = APTX_LL_VENDOR_ID, .codec_id = APTX_LL_CODEC_ID }, .name = "aptx_ll_duplex", .endpoint_name = "aptx_ll_duplex_0", .duplex_codec = &aptx_ll_msbc, .info = &duplex_info, }; const struct media_codec a2dp_codec_aptx_ll_duplex_1 = { APTX_LL_COMMON_DEFS, .id = SPA_BLUETOOTH_AUDIO_CODEC_APTX_LL_DUPLEX, .vendor = { .vendor_id = APTX_LL_VENDOR_ID2, .codec_id = APTX_LL_CODEC_ID }, .name = "aptx_ll_duplex", .endpoint_name = "aptx_ll_duplex_1", .duplex_codec = &aptx_ll_msbc, .info = &duplex_info, }; MEDIA_CODEC_EXPORT_DEF( "aptx", &a2dp_codec_aptx_hd, &a2dp_codec_aptx, &a2dp_codec_aptx_ll_0, &a2dp_codec_aptx_ll_1, &a2dp_codec_aptx_ll_duplex_0, &a2dp_codec_aptx_ll_duplex_1 ); pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/bluez5/a2dp-codec-caps.h000066400000000000000000000335711511204443500271570ustar00rootroot00000000000000/* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2006-2010 Nokia Corporation * Copyright (C) 2004-2010 Marcel Holtmann * Copyright (C) 2018 Pali Rohár * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * */ #ifndef SPA_BLUEZ5_A2DP_CODEC_CAPS_H_ #define SPA_BLUEZ5_A2DP_CODEC_CAPS_H_ #include #include #define A2DP_CODEC_SBC 0x00 #define A2DP_CODEC_MPEG12 0x01 #define A2DP_CODEC_MPEG24 0x02 #define A2DP_CODEC_ATRAC 0x03 #define A2DP_CODEC_VENDOR 0xFF #define A2DP_MAX_CAPS_SIZE 254 /* customized 16-bit vendor extension */ #define A2DP_CODEC_VENDOR_APTX 0x4FFF #define A2DP_CODEC_VENDOR_LDAC 0x2DFF #define SBC_SAMPLING_FREQ_48000 (1 << 0) #define SBC_SAMPLING_FREQ_44100 (1 << 1) #define SBC_SAMPLING_FREQ_32000 (1 << 2) #define SBC_SAMPLING_FREQ_16000 (1 << 3) #define SBC_CHANNEL_MODE_JOINT_STEREO (1 << 0) #define SBC_CHANNEL_MODE_STEREO (1 << 1) #define SBC_CHANNEL_MODE_DUAL_CHANNEL (1 << 2) #define SBC_CHANNEL_MODE_MONO (1 << 3) #define SBC_BLOCK_LENGTH_16 (1 << 0) #define SBC_BLOCK_LENGTH_12 (1 << 1) #define SBC_BLOCK_LENGTH_8 (1 << 2) #define SBC_BLOCK_LENGTH_4 (1 << 3) #define SBC_SUBBANDS_8 (1 << 0) #define SBC_SUBBANDS_4 (1 << 1) #define SBC_ALLOCATION_LOUDNESS (1 << 0) #define SBC_ALLOCATION_SNR (1 << 1) #define SBC_MIN_BITPOOL 2 #define SBC_MAX_BITPOOL 64 #define MPEG_CHANNEL_MODE_JOINT_STEREO (1 << 0) #define MPEG_CHANNEL_MODE_STEREO (1 << 1) #define MPEG_CHANNEL_MODE_DUAL_CHANNEL (1 << 2) #define MPEG_CHANNEL_MODE_MONO (1 << 3) #define MPEG_LAYER_MP3 (1 << 0) #define MPEG_LAYER_MP2 (1 << 1) #define MPEG_LAYER_MP1 (1 << 2) #define MPEG_SAMPLING_FREQ_48000 (1 << 0) #define MPEG_SAMPLING_FREQ_44100 (1 << 1) #define MPEG_SAMPLING_FREQ_32000 (1 << 2) #define MPEG_SAMPLING_FREQ_24000 (1 << 3) #define MPEG_SAMPLING_FREQ_22050 (1 << 4) #define MPEG_SAMPLING_FREQ_16000 (1 << 5) #define MPEG_BIT_RATE_VBR 0x8000 #define MPEG_BIT_RATE_320000 0x4000 #define MPEG_BIT_RATE_256000 0x2000 #define MPEG_BIT_RATE_224000 0x1000 #define MPEG_BIT_RATE_192000 0x0800 #define MPEG_BIT_RATE_160000 0x0400 #define MPEG_BIT_RATE_128000 0x0200 #define MPEG_BIT_RATE_112000 0x0100 #define MPEG_BIT_RATE_96000 0x0080 #define MPEG_BIT_RATE_80000 0x0040 #define MPEG_BIT_RATE_64000 0x0020 #define MPEG_BIT_RATE_56000 0x0010 #define MPEG_BIT_RATE_48000 0x0008 #define MPEG_BIT_RATE_40000 0x0004 #define MPEG_BIT_RATE_32000 0x0002 #define MPEG_BIT_RATE_FREE 0x0001 #define AAC_OBJECT_TYPE_MPEG2_AAC_LC 0x80 #define AAC_OBJECT_TYPE_MPEG4_AAC_LC 0x40 #define AAC_OBJECT_TYPE_MPEG4_AAC_LTP 0x20 #define AAC_OBJECT_TYPE_MPEG4_AAC_SCA 0x10 #define AAC_OBJECT_TYPE_MPEG4_AAC_ELD 0x02 #define AAC_SAMPLING_FREQ_8000 0x0800 #define AAC_SAMPLING_FREQ_11025 0x0400 #define AAC_SAMPLING_FREQ_12000 0x0200 #define AAC_SAMPLING_FREQ_16000 0x0100 #define AAC_SAMPLING_FREQ_22050 0x0080 #define AAC_SAMPLING_FREQ_24000 0x0040 #define AAC_SAMPLING_FREQ_32000 0x0020 #define AAC_SAMPLING_FREQ_44100 0x0010 #define AAC_SAMPLING_FREQ_48000 0x0008 #define AAC_SAMPLING_FREQ_64000 0x0004 #define AAC_SAMPLING_FREQ_88200 0x0002 #define AAC_SAMPLING_FREQ_96000 0x0001 #define AAC_CHANNELS_1 0x08 #define AAC_CHANNELS_2 0x04 #define AAC_CHANNELS_5_1 0x02 #define AAC_CHANNELS_7_1 0x01 #define AAC_GET_BITRATE(a) ((a).bitrate1 << 16 | \ (a).bitrate2 << 8 | (a).bitrate3) #define AAC_GET_FREQUENCY(a) ((a).frequency1 << 4 | (a).frequency2) #define AAC_SET_BITRATE(a, b) \ do { \ (a).bitrate1 = ((b) >> 16) & 0x7f; \ (a).bitrate2 = ((b) >> 8) & 0xff; \ (a).bitrate3 = (b) & 0xff; \ } while (0) #define AAC_SET_FREQUENCY(a, f) \ do { \ (a).frequency1 = ((f) >> 4) & 0xff; \ (a).frequency2 = (f) & 0x0f; \ } while (0) #define AAC_INIT_BITRATE(b) \ .bitrate1 = ((b) >> 16) & 0x7f, \ .bitrate2 = ((b) >> 8) & 0xff, \ .bitrate3 = (b) & 0xff, #define AAC_INIT_FREQUENCY(f) \ .frequency1 = ((f) >> 4) & 0xff, \ .frequency2 = (f) & 0x0f, #define APTX_VENDOR_ID 0x0000004f #define APTX_CODEC_ID 0x0001 #define APTX_CHANNEL_MODE_MONO 0x01 #define APTX_CHANNEL_MODE_STEREO 0x02 #define APTX_SAMPLING_FREQ_16000 0x08 #define APTX_SAMPLING_FREQ_32000 0x04 #define APTX_SAMPLING_FREQ_44100 0x02 #define APTX_SAMPLING_FREQ_48000 0x01 #define APTX_HD_VENDOR_ID 0x000000D7 #define APTX_HD_CODEC_ID 0x0024 #define APTX_HD_CHANNEL_MODE_MONO 0x1 #define APTX_HD_CHANNEL_MODE_STEREO 0x2 #define APTX_HD_SAMPLING_FREQ_16000 0x8 #define APTX_HD_SAMPLING_FREQ_32000 0x4 #define APTX_HD_SAMPLING_FREQ_44100 0x2 #define APTX_HD_SAMPLING_FREQ_48000 0x1 #define APTX_LL_VENDOR_ID 0x0000000a #define APTX_LL_VENDOR_ID2 0x000000d7 #define APTX_LL_CODEC_ID 0x0002 /** * Default parameters for aptX LL (Sprint) encoder */ #define APTX_LL_TARGET_CODEC_LEVEL 180 /* target codec buffer level */ #define APTX_LL_INITIAL_CODEC_LEVEL 360 /* initial codec buffer level */ #define APTX_LL_SRA_MAX_RATE 50 /* x/10000 = 0.005 SRA rate */ #define APTX_LL_SRA_AVG_TIME 1 /* SRA averaging time = 1s */ #define APTX_LL_GOOD_WORKING_LEVEL 180 /* good working buffer level */ #define LDAC_VENDOR_ID 0x0000012d #define LDAC_CODEC_ID 0x00aa #define LDAC_CHANNEL_MODE_MONO 0x04 #define LDAC_CHANNEL_MODE_DUAL_CHANNEL 0x02 #define LDAC_CHANNEL_MODE_STEREO 0x01 #define LDAC_SAMPLING_FREQ_44100 0x20 #define LDAC_SAMPLING_FREQ_48000 0x10 #define LDAC_SAMPLING_FREQ_88200 0x08 #define LDAC_SAMPLING_FREQ_96000 0x04 #define LDAC_SAMPLING_FREQ_176400 0x02 #define LDAC_SAMPLING_FREQ_192000 0x01 #define FASTSTREAM_VENDOR_ID 0x0000000a #define FASTSTREAM_CODEC_ID 0x0001 #define FASTSTREAM_DIRECTION_SINK 0x1 #define FASTSTREAM_DIRECTION_SOURCE 0x2 #define FASTSTREAM_SINK_SAMPLING_FREQ_44100 0x2 #define FASTSTREAM_SINK_SAMPLING_FREQ_48000 0x1 #define FASTSTREAM_SOURCE_SAMPLING_FREQ_16000 0x2 #define LC3PLUS_HR_GET_FRAME_DURATION(a) ((a).frame_duration & 0xf0) #define LC3PLUS_HR_INIT_FRAME_DURATION(v) \ .frame_duration = ((v) & 0xf0), #define LC3PLUS_HR_SET_FRAME_DURATION(a, v) \ do { \ (a).frame_duration = ((v) & 0xf0); \ } while (0) #define LC3PLUS_HR_GET_FREQUENCY(a) (((a).frequency1 << 8) | (a).frequency2) #define LC3PLUS_HR_INIT_FREQUENCY(v) \ .frequency1 = (((v) >> 8) & 0xff), \ .frequency2 = ((v) & 0xff), #define LC3PLUS_HR_SET_FREQUENCY(a, v) \ do { \ (a).frequency1 = ((v) >> 8) & 0xff; \ (a).frequency2 = (v) & 0xff; \ } while (0) #define LC3PLUS_HR_VENDOR_ID 0x000008a9 #define LC3PLUS_HR_CODEC_ID 0x0001 #define LC3PLUS_HR_FRAME_DURATION_10MS (1 << 6) #define LC3PLUS_HR_FRAME_DURATION_5MS (1 << 5) #define LC3PLUS_HR_FRAME_DURATION_2_5MS (1 << 4) #define LC3PLUS_HR_CHANNELS_1 (1 << 7) #define LC3PLUS_HR_CHANNELS_2 (1 << 6) #define LC3PLUS_HR_SAMPLING_FREQ_48000 (1 << 8) #define LC3PLUS_HR_SAMPLING_FREQ_96000 (1 << 7) #define OPUS_05_VENDOR_ID 0x000005f1 #define OPUS_05_CODEC_ID 0x1005 #define OPUS_05_MAPPING_FAMILY_0 (1 << 0) #define OPUS_05_MAPPING_FAMILY_1 (1 << 1) #define OPUS_05_MAPPING_FAMILY_255 (1 << 2) #define OPUS_05_FRAME_DURATION_25 (1 << 0) #define OPUS_05_FRAME_DURATION_50 (1 << 1) #define OPUS_05_FRAME_DURATION_100 (1 << 2) #define OPUS_05_FRAME_DURATION_200 (1 << 3) #define OPUS_05_FRAME_DURATION_400 (1 << 4) #define OPUS_05_GET_UINT16(a, field) \ (((a).field ## 2 << 8) | (a).field ## 1) #define OPUS_05_INIT_UINT16(field, v) \ .field ## 1 = ((v) & 0xff), \ .field ## 2 = (((v) >> 8) & 0xff), #define OPUS_05_SET_UINT16(a, field, v) \ do { \ (a).field ## 1 = ((v) & 0xff); \ (a).field ## 2 = (((v) >> 8) & 0xff); \ } while (0) #define OPUS_05_GET_UINT32(a, field) \ (((a).field ## 4 << 24) | ((a).field ## 3 << 16) | \ ((a).field ## 2 << 8) | (a).field ## 1) #define OPUS_05_INIT_UINT32(field, v) \ .field ## 1 = ((v) & 0xff), \ .field ## 2 = (((v) >> 8) & 0xff), \ .field ## 3 = (((v) >> 16) & 0xff), \ .field ## 4 = (((v) >> 24) & 0xff), #define OPUS_05_SET_UINT32(a, field, v) \ do { \ (a).field ## 1 = ((v) & 0xff); \ (a).field ## 2 = (((v) >> 8) & 0xff); \ (a).field ## 3 = (((v) >> 16) & 0xff); \ (a).field ## 4 = (((v) >> 24) & 0xff); \ } while (0) #define OPUS_05_GET_LOCATION(a) OPUS_05_GET_UINT32(a, location) #define OPUS_05_INIT_LOCATION(v) OPUS_05_INIT_UINT32(location, v) #define OPUS_05_SET_LOCATION(a, v) OPUS_05_SET_UINT32(a, location, v) #define OPUS_05_GET_BITRATE(a) OPUS_05_GET_UINT16(a, bitrate) #define OPUS_05_INIT_BITRATE(v) OPUS_05_INIT_UINT16(bitrate, v) #define OPUS_05_SET_BITRATE(a, v) OPUS_05_SET_UINT16(a, bitrate, v) #define OPUS_G_VENDOR_ID 0x000000e0 #define OPUS_G_CODEC_ID 0x0001 #define OPUS_G_FREQUENCY_MASK 0x80 #define OPUS_G_FREQUENCY_48000 0x80 #define OPUS_G_DURATION_MASK 0x18 #define OPUS_G_DURATION_100 0x08 #define OPUS_G_DURATION_200 0x10 #define OPUS_G_CHANNELS_MASK 0x07 #define OPUS_G_CHANNELS_MONO 0x01 #define OPUS_G_CHANNELS_STEREO 0x02 #define OPUS_G_CHANNELS_MONO_2 0x04 #define OPUS_G_GET_FREQUENCY(a) ((a).data & OPUS_G_FREQUENCY_MASK) #define OPUS_G_GET_DURATION(a) ((a).data & OPUS_G_DURATION_MASK) #define OPUS_G_GET_CHANNELS(a) ((a).data & OPUS_G_CHANNELS_MASK) #define OPUS_G_SET(a, freq, dur, ch) \ (a).data = ((freq) & OPUS_G_FREQUENCY_MASK) | ((dur) & OPUS_G_DURATION_MASK) | ((ch) & OPUS_G_CHANNELS_MASK) typedef struct { uint32_t vendor_id; uint16_t codec_id; } __attribute__ ((packed)) a2dp_vendor_codec_t; typedef struct { a2dp_vendor_codec_t info; uint8_t frequency; uint8_t channel_mode; } __attribute__ ((packed)) a2dp_ldac_t; #if __BYTE_ORDER == __LITTLE_ENDIAN typedef struct { uint8_t channel_mode:4; uint8_t frequency:4; uint8_t allocation_method:2; uint8_t subbands:2; uint8_t block_length:4; uint8_t min_bitpool; uint8_t max_bitpool; } __attribute__ ((packed)) a2dp_sbc_t; typedef struct { uint8_t channel_mode:4; uint8_t crc:1; uint8_t layer:3; uint8_t frequency:6; uint8_t mpf:1; uint8_t rfa:1; uint16_t bitrate; } __attribute__ ((packed)) a2dp_mpeg_t; typedef struct { uint8_t object_type; uint8_t frequency1; uint8_t channels:4; uint8_t frequency2:4; uint8_t bitrate1:7; uint8_t vbr:1; uint8_t bitrate2; uint8_t bitrate3; } __attribute__ ((packed)) a2dp_aac_t; typedef struct { a2dp_vendor_codec_t info; uint8_t channel_mode:4; uint8_t frequency:4; } __attribute__ ((packed)) a2dp_aptx_t; typedef struct { a2dp_vendor_codec_t info; uint8_t channel_mode:4; uint8_t frequency:4; uint32_t rfa; } __attribute__ ((packed)) a2dp_aptx_hd_t; typedef struct { a2dp_aptx_t aptx; uint8_t bidirect_link:1; uint8_t has_new_caps:1; uint8_t reserved:6; } __attribute__ ((packed)) a2dp_aptx_ll_t; typedef struct { a2dp_vendor_codec_t info; uint8_t direction; uint8_t sink_frequency:4; uint8_t source_frequency:4; } __attribute__ ((packed)) a2dp_faststream_t; #elif __BYTE_ORDER == __BIG_ENDIAN typedef struct { uint8_t frequency:4; uint8_t channel_mode:4; uint8_t block_length:4; uint8_t subbands:2; uint8_t allocation_method:2; uint8_t min_bitpool; uint8_t max_bitpool; } __attribute__ ((packed)) a2dp_sbc_t; typedef struct { uint8_t layer:3; uint8_t crc:1; uint8_t channel_mode:4; uint8_t rfa:1; uint8_t mpf:1; uint8_t frequency:6; uint16_t bitrate; } __attribute__ ((packed)) a2dp_mpeg_t; typedef struct { uint8_t object_type; uint8_t frequency1; uint8_t frequency2:4; uint8_t channels:4; uint8_t vbr:1; uint8_t bitrate1:7; uint8_t bitrate2; uint8_t bitrate3; } __attribute__ ((packed)) a2dp_aac_t; typedef struct { a2dp_vendor_codec_t info; uint8_t frequency:4; uint8_t channel_mode:4; } __attribute__ ((packed)) a2dp_aptx_t; typedef struct { a2dp_vendor_codec_t info; uint8_t frequency:4; uint8_t channel_mode:4; uint32_t rfa; } __attribute__ ((packed)) a2dp_aptx_hd_t; typedef struct { a2dp_aptx_t aptx; uint8_t reserved:6; uint8_t has_new_caps:1; uint8_t bidirect_link:1; } __attribute__ ((packed)) a2dp_aptx_ll_t; typedef struct { a2dp_vendor_codec_t info; uint8_t direction; uint8_t source_frequency:4; uint8_t sink_frequency:4; } __attribute__ ((packed)) a2dp_faststream_t; #else #error "Unknown byte order" #endif typedef struct { a2dp_aptx_ll_t base; uint8_t reserved; uint8_t target_level2; uint8_t target_level1; uint8_t initial_level2; uint8_t initial_level1; uint8_t sra_max_rate; uint8_t sra_avg_time; uint8_t good_working_level2; uint8_t good_working_level1; } __attribute__ ((packed)) a2dp_aptx_ll_ext_t; typedef struct { a2dp_vendor_codec_t info; uint8_t frame_duration; uint8_t channels; uint8_t frequency1; uint8_t frequency2; } __attribute__ ((packed)) a2dp_lc3plus_hr_t; typedef struct { uint8_t channels; uint8_t coupled_streams; uint8_t location1; uint8_t location2; uint8_t location3; uint8_t location4; uint8_t frame_duration; uint8_t bitrate1; uint8_t bitrate2; } __attribute__ ((packed)) a2dp_opus_05_direction_t; typedef struct { a2dp_vendor_codec_t info; a2dp_opus_05_direction_t main; a2dp_opus_05_direction_t bidi; } __attribute__ ((packed)) a2dp_opus_05_t; typedef struct { a2dp_vendor_codec_t info; uint8_t data; } __attribute__ ((packed)) a2dp_opus_g_t; #define ASHA_CODEC_G722 0x63 #endif pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/bluez5/a2dp-codec-faststream.c000066400000000000000000000357511511204443500303770ustar00rootroot00000000000000/* Spa A2DP FastStream codec */ /* SPDX-FileCopyrightText: Copyright © 2020 Wim Taymans */ /* SPDX-FileCopyrightText: Copyright © 2021 Pauli Virtanen */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #include #include #include #include #include "media-codecs.h" struct impl { sbc_t sbc; size_t mtu; int codesize; int frame_count; int max_frames; }; struct duplex_impl { sbc_t sbc; }; static int codec_fill_caps(const struct media_codec *codec, uint32_t flags, const struct spa_dict *settings, uint8_t caps[A2DP_MAX_CAPS_SIZE]) { const a2dp_faststream_t a2dp_faststream = { .info = codec->vendor, .direction = FASTSTREAM_DIRECTION_SINK | (codec->duplex_codec ? FASTSTREAM_DIRECTION_SOURCE : 0), .sink_frequency = FASTSTREAM_SINK_SAMPLING_FREQ_44100 | FASTSTREAM_SINK_SAMPLING_FREQ_48000, .source_frequency = FASTSTREAM_SOURCE_SAMPLING_FREQ_16000, }; memcpy(caps, &a2dp_faststream, sizeof(a2dp_faststream)); return sizeof(a2dp_faststream); } static const struct media_codec_config frequencies[] = { { FASTSTREAM_SINK_SAMPLING_FREQ_48000, 48000, 1 }, { FASTSTREAM_SINK_SAMPLING_FREQ_44100, 44100, 0 }, }; static const struct media_codec_config duplex_frequencies[] = { { FASTSTREAM_SOURCE_SAMPLING_FREQ_16000, 16000, 0 }, }; static int codec_select_config(const struct media_codec *codec, uint32_t flags, const void *caps, size_t caps_size, const struct media_codec_audio_info *info, const struct spa_dict *settings, uint8_t config[A2DP_MAX_CAPS_SIZE], void **config_data) { a2dp_faststream_t conf; int i; if (caps_size < sizeof(conf)) return -EINVAL; memcpy(&conf, caps, sizeof(conf)); if (codec->vendor.vendor_id != conf.info.vendor_id || codec->vendor.codec_id != conf.info.codec_id) return -ENOTSUP; if (codec->duplex_codec && !(conf.direction & FASTSTREAM_DIRECTION_SOURCE)) return -ENOTSUP; if (!(conf.direction & FASTSTREAM_DIRECTION_SINK)) return -ENOTSUP; conf.direction = FASTSTREAM_DIRECTION_SINK; if (codec->duplex_codec) conf.direction |= FASTSTREAM_DIRECTION_SOURCE; if ((i = media_codec_select_config(frequencies, SPA_N_ELEMENTS(frequencies), conf.sink_frequency, info ? info->rate : A2DP_CODEC_DEFAULT_RATE )) < 0) return -ENOTSUP; conf.sink_frequency = frequencies[i].config; if ((i = media_codec_select_config(duplex_frequencies, SPA_N_ELEMENTS(duplex_frequencies), conf.source_frequency, 16000 )) < 0) return -ENOTSUP; conf.source_frequency = duplex_frequencies[i].config; memcpy(config, &conf, sizeof(conf)); return sizeof(conf); } static int codec_enum_config(const struct media_codec *codec, uint32_t flags, const void *caps, size_t caps_size, uint32_t id, uint32_t idx, struct spa_pod_builder *b, struct spa_pod **param) { a2dp_faststream_t conf; struct spa_pod_frame f[2]; struct spa_pod_choice *choice; uint32_t position[2]; uint32_t i = 0; if (caps_size < sizeof(conf)) return -EINVAL; memcpy(&conf, caps, sizeof(conf)); if (idx > 0) return 0; spa_pod_builder_push_object(b, &f[0], SPA_TYPE_OBJECT_Format, id); spa_pod_builder_add(b, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_audio), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), SPA_FORMAT_AUDIO_format, SPA_POD_Id(SPA_AUDIO_FORMAT_S16), 0); spa_pod_builder_prop(b, SPA_FORMAT_AUDIO_rate, 0); spa_pod_builder_push_choice(b, &f[1], SPA_CHOICE_None, 0); choice = (struct spa_pod_choice*)spa_pod_builder_frame(b, &f[1]); i = 0; if (conf.sink_frequency & FASTSTREAM_SINK_SAMPLING_FREQ_48000) { if (i++ == 0) spa_pod_builder_int(b, 48000); spa_pod_builder_int(b, 48000); } if (conf.sink_frequency & FASTSTREAM_SINK_SAMPLING_FREQ_44100) { if (i++ == 0) spa_pod_builder_int(b, 44100); spa_pod_builder_int(b, 44100); } if (i > 1) choice->body.type = SPA_CHOICE_Enum; spa_pod_builder_pop(b, &f[1]); if (i == 0) return -EINVAL; position[0] = SPA_AUDIO_CHANNEL_FL; position[1] = SPA_AUDIO_CHANNEL_FR; spa_pod_builder_add(b, SPA_FORMAT_AUDIO_channels, SPA_POD_Int(2), SPA_FORMAT_AUDIO_position, SPA_POD_Array(sizeof(uint32_t), SPA_TYPE_Id, 2, position), 0); *param = spa_pod_builder_pop(b, &f[0]); return *param == NULL ? -EIO : 1; } static int codec_reduce_bitpool(void *data) { return -ENOTSUP; } static int codec_increase_bitpool(void *data) { return -ENOTSUP; } static int codec_get_block_size(void *data) { struct impl *this = data; return this->codesize; } static size_t ceil2(size_t v) { if (v % 2 != 0 && v < SIZE_MAX) v += 1; return v; } static void *codec_init(const struct media_codec *codec, uint32_t flags, void *config, size_t config_len, const struct spa_audio_info *info, void *props, size_t mtu) { a2dp_faststream_t *conf = config; struct impl *this; bool sbc_initialized = false; int res; if ((this = calloc(1, sizeof(struct impl))) == NULL) goto error_errno; if ((res = sbc_init(&this->sbc, 0)) < 0) goto error; sbc_initialized = true; this->sbc.endian = SBC_LE; this->mtu = mtu; if (info->media_type != SPA_MEDIA_TYPE_audio || info->media_subtype != SPA_MEDIA_SUBTYPE_raw || info->info.raw.format != SPA_AUDIO_FORMAT_S16) { res = -EINVAL; goto error; } switch (conf->sink_frequency) { case FASTSTREAM_SINK_SAMPLING_FREQ_44100: this->sbc.frequency = SBC_FREQ_44100; break; case FASTSTREAM_SINK_SAMPLING_FREQ_48000: this->sbc.frequency = SBC_FREQ_48000; break; default: res = -EINVAL; goto error; } this->sbc.mode = SBC_MODE_JOINT_STEREO; this->sbc.subbands = SBC_SB_8; this->sbc.allocation = SBC_AM_LOUDNESS; this->sbc.blocks = SBC_BLK_16; this->sbc.bitpool = 29; this->codesize = sbc_get_codesize(&this->sbc); this->max_frames = 3; if (this->mtu < this->max_frames * ceil2(sbc_get_frame_length(&this->sbc))) { res = -EINVAL; goto error; } return this; error_errno: res = -errno; goto error; error: if (sbc_initialized) sbc_finish(&this->sbc); free(this); errno = -res; return NULL; } static void codec_deinit(void *data) { struct impl *this = data; sbc_finish(&this->sbc); free(this); } static int codec_abr_process (void *data, size_t unsent) { return -ENOTSUP; } static int codec_start_encode (void *data, void *dst, size_t dst_size, uint16_t seqnum, uint32_t timestamp) { struct impl *this = data; this->frame_count = 0; return 0; } static int codec_encode(void *data, const void *src, size_t src_size, void *dst, size_t dst_size, size_t *dst_out, int *need_flush) { struct impl *this = data; int res; res = sbc_encode(&this->sbc, src, src_size, dst, dst_size, (ssize_t*)dst_out); if (SPA_UNLIKELY(res < 0)) return -EINVAL; spa_assert(res == this->codesize); if (*dst_out % 2 != 0 && *dst_out < dst_size) { /* Pad similarly as in input stream */ *((uint8_t *)dst + *dst_out) = 0; ++*dst_out; } this->frame_count += res / this->codesize; *need_flush = (this->frame_count >= this->max_frames) ? NEED_FLUSH_ALL : NEED_FLUSH_NO; return res; } static SPA_UNUSED int codec_start_decode (void *data, const void *src, size_t src_size, uint16_t *seqnum, uint32_t *timestamp) { if (!src_size) return -EINVAL; return 0; } static int do_decode(sbc_t *sbc, const void *src, size_t src_size, void *dst, size_t dst_size, size_t *dst_out) { size_t processed = 0; int res; *dst_out = 0; /* Scan for SBC syncword. * We could probably assume 1-byte paddings instead, * which devices seem to be sending. */ while (src_size >= 1) { if (*(uint8_t*)src == 0x9C) break; src = (uint8_t*)src + 1; --src_size; ++processed; } res = sbc_decode(sbc, src, src_size, dst, dst_size, dst_out); if (res <= 0) res = SPA_MIN((size_t)1, src_size); /* skip bad payload */ processed += res; return processed; } static SPA_UNUSED int codec_decode(void *data, const void *src, size_t src_size, void *dst, size_t dst_size, size_t *dst_out) { struct impl *this = data; return do_decode(&this->sbc, src, src_size, dst, dst_size, dst_out); } /* * Duplex codec * * When connected as SRC to SNK, FastStream sink may send back SBC data. */ static int duplex_enum_config(const struct media_codec *codec, uint32_t flags, const void *caps, size_t caps_size, uint32_t id, uint32_t idx, struct spa_pod_builder *b, struct spa_pod **param) { a2dp_faststream_t conf; struct spa_audio_info_raw info = { 0, }; if (caps_size < sizeof(conf)) return -EINVAL; memcpy(&conf, caps, sizeof(conf)); if (idx > 0) return 0; switch (conf.source_frequency) { case FASTSTREAM_SOURCE_SAMPLING_FREQ_16000: info.rate = 16000; break; default: return -EINVAL; } /* * Some headsets send mono stream, others stereo. This information * is contained in the SBC headers, and becomes known only when * stream arrives. To be able to work in both cases, we will * produce 2-channel output, and will double the channels * in the decoding step if mono stream was received. */ info.format = SPA_AUDIO_FORMAT_S16_LE; info.channels = 2; info.position[0] = SPA_AUDIO_CHANNEL_FL; info.position[1] = SPA_AUDIO_CHANNEL_FR; *param = spa_format_audio_raw_build(b, id, &info); return *param == NULL ? -EIO : 1; } static int duplex_validate_config(const struct media_codec *codec, uint32_t flags, const void *caps, size_t caps_size, struct spa_audio_info *info) { spa_zero(*info); info->media_type = SPA_MEDIA_TYPE_audio; info->media_subtype = SPA_MEDIA_SUBTYPE_raw; info->info.raw.format = SPA_AUDIO_FORMAT_S16_LE; info->info.raw.channels = 2; info->info.raw.position[0] = SPA_AUDIO_CHANNEL_FL; info->info.raw.position[1] = SPA_AUDIO_CHANNEL_FR; info->info.raw.rate = 16000; return 0; } static int duplex_reduce_bitpool(void *data) { return -ENOTSUP; } static int duplex_increase_bitpool(void *data) { return -ENOTSUP; } static int duplex_get_block_size(void *data) { return 0; } static void *duplex_init(const struct media_codec *codec, uint32_t flags, void *config, size_t config_len, const struct spa_audio_info *info, void *props, size_t mtu) { a2dp_faststream_t *conf = config; struct duplex_impl *this = NULL; int res; if (info->media_type != SPA_MEDIA_TYPE_audio || info->media_subtype != SPA_MEDIA_SUBTYPE_raw || info->info.raw.format != SPA_AUDIO_FORMAT_S16_LE) { res = -EINVAL; goto error; } if ((this = calloc(1, sizeof(struct duplex_impl))) == NULL) goto error_errno; if ((res = sbc_init(&this->sbc, 0)) < 0) goto error; switch (conf->source_frequency) { case FASTSTREAM_SOURCE_SAMPLING_FREQ_16000: this->sbc.frequency = SBC_FREQ_16000; break; default: res = -EINVAL; goto error; } this->sbc.endian = SBC_LE; this->sbc.mode = SBC_MODE_MONO; this->sbc.subbands = SBC_SB_8; this->sbc.allocation = SBC_AM_LOUDNESS; this->sbc.blocks = SBC_BLK_16; this->sbc.bitpool = 32; return this; error_errno: res = -errno; goto error; error: free(this); errno = -res; return NULL; } static void duplex_deinit(void *data) { struct duplex_impl *this = data; sbc_finish(&this->sbc); free(this); } static int duplex_abr_process (void *data, size_t unsent) { return -ENOTSUP; } static int duplex_start_encode (void *data, void *dst, size_t dst_size, uint16_t seqnum, uint32_t timestamp) { return -ENOTSUP; } static int duplex_encode(void *data, const void *src, size_t src_size, void *dst, size_t dst_size, size_t *dst_out, int *need_flush) { return -ENOTSUP; } static int duplex_start_decode (void *data, const void *src, size_t src_size, uint16_t *seqnum, uint32_t *timestamp) { return 0; } /** Convert S16LE stereo -> S16LE mono, in-place (only for testing purposes) */ static SPA_UNUSED size_t convert_s16le_c2_to_c1(int16_t *data, size_t size, size_t max_size) { size_t i; for (i = 0; i < size / 2; ++i) #if __BYTE_ORDER == __LITTLE_ENDIAN data[i] = data[2*i]/2 + data[2*i+1]/2; #else data[i] = bswap_16(bswap_16(data[2*i])/2 + bswap_16(data[2*i+1])/2); #endif return size / 2; } /** Convert S16LE mono -> S16LE stereo, in-place */ static size_t convert_s16le_c1_to_c2(uint8_t *data, size_t size, size_t max_size) { size_t pos; pos = 2 * SPA_MIN(size / 2, max_size / 4); size = 2 * pos; /* We'll trust the compiler to optimize this */ while (pos >= 2) { pos -= 2; data[2*pos+3] = data[pos+1]; data[2*pos+2] = data[pos]; data[2*pos+1] = data[pos+1]; data[2*pos] = data[pos]; } return size; } static int duplex_decode(void *data, const void *src, size_t src_size, void *dst, size_t dst_size, size_t *dst_out) { struct duplex_impl *this = data; int res; *dst_out = 0; res = do_decode(&this->sbc, src, src_size, dst, dst_size, dst_out); /* * Depending on headers of first frame, libsbc may output either * 1 or 2 channels. This function should always produce 2 channels, * so we'll just double the channels here. */ if (this->sbc.mode == SBC_MODE_MONO) *dst_out = convert_s16le_c1_to_c2(dst, *dst_out, dst_size); return res; } static void codec_get_delay(void *data, uint32_t *encoder, uint32_t *decoder) { if (encoder) *encoder = 73; if (decoder) *decoder = 0; } /* Voice channel SBC, not a real A2DP codec */ static const struct media_codec duplex_codec = { .codec_id = A2DP_CODEC_VENDOR, .name = "faststream_sbc", .description = "FastStream duplex SBC", .fill_caps = codec_fill_caps, .select_config = codec_select_config, .enum_config = duplex_enum_config, .validate_config = duplex_validate_config, .init = duplex_init, .deinit = duplex_deinit, .get_block_size = duplex_get_block_size, .abr_process = duplex_abr_process, .start_encode = duplex_start_encode, .encode = duplex_encode, .start_decode = duplex_start_decode, .decode = duplex_decode, .reduce_bitpool = duplex_reduce_bitpool, .increase_bitpool = duplex_increase_bitpool, }; #define FASTSTREAM_COMMON_DEFS \ .kind = MEDIA_CODEC_A2DP, \ .codec_id = A2DP_CODEC_VENDOR, \ .vendor = { .vendor_id = FASTSTREAM_VENDOR_ID, \ .codec_id = FASTSTREAM_CODEC_ID }, \ .description = "FastStream", \ .fill_caps = codec_fill_caps, \ .select_config = codec_select_config, \ .enum_config = codec_enum_config, \ .init = codec_init, \ .deinit = codec_deinit, \ .get_block_size = codec_get_block_size, \ .abr_process = codec_abr_process, \ .start_encode = codec_start_encode, \ .encode = codec_encode, \ .reduce_bitpool = codec_reduce_bitpool, \ .increase_bitpool = codec_increase_bitpool, \ .get_delay = codec_get_delay const struct media_codec a2dp_codec_faststream = { FASTSTREAM_COMMON_DEFS, .id = SPA_BLUETOOTH_AUDIO_CODEC_FASTSTREAM, .name = "faststream", }; static const struct spa_dict_item duplex_info_items[] = { { "duplex.boost", "true" }, }; static const struct spa_dict duplex_info = SPA_DICT_INIT_ARRAY(duplex_info_items); const struct media_codec a2dp_codec_faststream_duplex = { FASTSTREAM_COMMON_DEFS, .id = SPA_BLUETOOTH_AUDIO_CODEC_FASTSTREAM_DUPLEX, .name = "faststream_duplex", .duplex_codec = &duplex_codec, .info = &duplex_info, }; MEDIA_CODEC_EXPORT_DEF( "faststream", &a2dp_codec_faststream, &a2dp_codec_faststream_duplex ); pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/bluez5/a2dp-codec-lc3plus.c000066400000000000000000000520041511204443500276010ustar00rootroot00000000000000/* Spa A2DP LC3plus HR codec */ /* SPDX-FileCopyrightText: Copyright © 2020 Wim Taymans */ /* SPDX-FileCopyrightText: Copyright © 2022 Pauli Virtanen */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #include #include #include #ifdef HAVE_LC3PLUS_H #include #else #include #endif #include "rtp.h" #include "media-codecs.h" #define BITRATE_MIN 96000 #define BITRATE_MAX 512000 #define BITRATE_DEFAULT 160000 struct dec_data { int frame_size; int fragment_size; int fragment_count; uint8_t fragment[LC3PLUS_MAX_BYTES]; }; struct enc_data { struct rtp_header *header; struct rtp_payload *payload; int samples; int codesize; int packet_size; int fragment_size; int fragment_count; void *fragment; int bitrate; int next_bitrate; }; struct impl { LC3PLUS_Enc *enc; LC3PLUS_Dec *dec; int mtu; int samplerate; int channels; int frame_dms; int bitrate; struct dec_data d; struct enc_data e; int32_t buf[2][LC3PLUS_MAX_SAMPLES]; }; static int codec_fill_caps(const struct media_codec *codec, uint32_t flags, const struct spa_dict *settings, uint8_t caps[A2DP_MAX_CAPS_SIZE]) { const a2dp_lc3plus_hr_t a2dp_lc3plus_hr = { .info = codec->vendor, LC3PLUS_HR_INIT_FRAME_DURATION(LC3PLUS_HR_FRAME_DURATION_10MS | LC3PLUS_HR_FRAME_DURATION_5MS | LC3PLUS_HR_FRAME_DURATION_2_5MS) .channels = LC3PLUS_HR_CHANNELS_1 | LC3PLUS_HR_CHANNELS_2, LC3PLUS_HR_INIT_FREQUENCY(LC3PLUS_HR_SAMPLING_FREQ_48000 | (lc3plus_samplerate_supported(96000) ? LC3PLUS_HR_SAMPLING_FREQ_96000 : 0)) }; memcpy(caps, &a2dp_lc3plus_hr, sizeof(a2dp_lc3plus_hr)); return sizeof(a2dp_lc3plus_hr); } static int codec_select_config(const struct media_codec *codec, uint32_t flags, const void *caps, size_t caps_size, const struct media_codec_audio_info *info, const struct spa_dict *settings, uint8_t config[A2DP_MAX_CAPS_SIZE], void **config_data) { a2dp_lc3plus_hr_t conf; if (caps_size < sizeof(conf)) return -EINVAL; memcpy(&conf, caps, sizeof(conf)); if (codec->vendor.vendor_id != conf.info.vendor_id || codec->vendor.codec_id != conf.info.codec_id) return -ENOTSUP; if ((LC3PLUS_HR_GET_FREQUENCY(conf) & LC3PLUS_HR_SAMPLING_FREQ_48000) && lc3plus_samplerate_supported(48000)) LC3PLUS_HR_SET_FREQUENCY(conf, LC3PLUS_HR_SAMPLING_FREQ_48000); else if ((LC3PLUS_HR_GET_FREQUENCY(conf) & LC3PLUS_HR_SAMPLING_FREQ_96000) && lc3plus_samplerate_supported(96000)) LC3PLUS_HR_SET_FREQUENCY(conf, LC3PLUS_HR_SAMPLING_FREQ_96000); else return -ENOTSUP; if ((conf.channels & LC3PLUS_HR_CHANNELS_2) && lc3plus_channels_supported(2)) conf.channels = LC3PLUS_HR_CHANNELS_2; else if ((conf.channels & LC3PLUS_HR_CHANNELS_1) && lc3plus_channels_supported(1)) conf.channels = LC3PLUS_HR_CHANNELS_1; else return -ENOTSUP; if (LC3PLUS_HR_GET_FRAME_DURATION(conf) & LC3PLUS_HR_FRAME_DURATION_10MS) LC3PLUS_HR_SET_FRAME_DURATION(conf, LC3PLUS_HR_FRAME_DURATION_10MS); else if (LC3PLUS_HR_GET_FRAME_DURATION(conf) & LC3PLUS_HR_FRAME_DURATION_5MS) LC3PLUS_HR_SET_FRAME_DURATION(conf, LC3PLUS_HR_FRAME_DURATION_5MS); else if (LC3PLUS_HR_GET_FRAME_DURATION(conf) & LC3PLUS_HR_FRAME_DURATION_2_5MS) LC3PLUS_HR_SET_FRAME_DURATION(conf, LC3PLUS_HR_FRAME_DURATION_2_5MS); else return -ENOTSUP; memcpy(config, &conf, sizeof(conf)); return sizeof(conf); } static int codec_caps_preference_cmp(const struct media_codec *codec, uint32_t flags, const void *caps1, size_t caps1_size, const void *caps2, size_t caps2_size, const struct media_codec_audio_info *info, const struct spa_dict *global_settings) { a2dp_lc3plus_hr_t conf1, conf2; a2dp_lc3plus_hr_t *conf; int res1, res2; int a, b; /* Order selected configurations by preference */ res1 = codec->select_config(codec, 0, caps1, caps1_size, info, NULL, (uint8_t *)&conf1, NULL); res2 = codec->select_config(codec, 0, caps2, caps2_size, info , NULL, (uint8_t *)&conf2, NULL); #define PREFER_EXPR(expr) \ do { \ conf = &conf1; \ a = (expr); \ conf = &conf2; \ b = (expr); \ if (a != b) \ return b - a; \ } while (0) #define PREFER_BOOL(expr) PREFER_EXPR((expr) ? 1 : 0) /* Prefer valid */ a = (res1 > 0 && (size_t)res1 == sizeof(a2dp_lc3plus_hr_t)) ? 1 : 0; b = (res2 > 0 && (size_t)res2 == sizeof(a2dp_lc3plus_hr_t)) ? 1 : 0; if (!a || !b) return b - a; PREFER_BOOL(conf->channels & LC3PLUS_HR_CHANNELS_2); PREFER_BOOL(LC3PLUS_HR_GET_FREQUENCY(*conf) & (LC3PLUS_HR_SAMPLING_FREQ_48000 | LC3PLUS_HR_SAMPLING_FREQ_96000)); PREFER_BOOL(LC3PLUS_HR_GET_FREQUENCY(*conf) & LC3PLUS_HR_SAMPLING_FREQ_48000); return 0; #undef PREFER_EXPR #undef PREFER_BOOL } static int codec_enum_config(const struct media_codec *codec, uint32_t flags, const void *caps, size_t caps_size, uint32_t id, uint32_t idx, struct spa_pod_builder *b, struct spa_pod **param) { a2dp_lc3plus_hr_t conf; struct spa_pod_frame f[2]; struct spa_pod_choice *choice; uint32_t position[2]; uint32_t i = 0; if (caps_size < sizeof(conf)) return -EINVAL; memcpy(&conf, caps, sizeof(conf)); if (idx > 0) return 0; spa_pod_builder_push_object(b, &f[0], SPA_TYPE_OBJECT_Format, id); spa_pod_builder_add(b, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_audio), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), SPA_FORMAT_AUDIO_format, SPA_POD_Id(SPA_AUDIO_FORMAT_S24_32), 0); spa_pod_builder_prop(b, SPA_FORMAT_AUDIO_rate, 0); spa_pod_builder_push_choice(b, &f[1], SPA_CHOICE_None, 0); choice = (struct spa_pod_choice*)spa_pod_builder_frame(b, &f[1]); i = 0; if ((LC3PLUS_HR_GET_FREQUENCY(conf) & LC3PLUS_HR_SAMPLING_FREQ_96000) && lc3plus_samplerate_supported(96000)) { if (i++ == 0) spa_pod_builder_int(b, 96000); spa_pod_builder_int(b, 96000); } if ((LC3PLUS_HR_GET_FREQUENCY(conf) & LC3PLUS_HR_SAMPLING_FREQ_48000) && lc3plus_samplerate_supported(48000)) { if (i++ == 0) spa_pod_builder_int(b, 48000); spa_pod_builder_int(b, 48000); } if (i == 0) return -EINVAL; if (i > 1) choice->body.type = SPA_CHOICE_Enum; spa_pod_builder_pop(b, &f[1]); if ((conf.channels & (LC3PLUS_HR_CHANNELS_2 | LC3PLUS_HR_CHANNELS_1)) && lc3plus_channels_supported(2) && lc3plus_channels_supported(1)) { spa_pod_builder_add(b, SPA_FORMAT_AUDIO_channels, SPA_POD_CHOICE_RANGE_Int(2, 1, 2), 0); } else if ((conf.channels & LC3PLUS_HR_CHANNELS_2) && lc3plus_channels_supported(2)) { position[0] = SPA_AUDIO_CHANNEL_FL; position[1] = SPA_AUDIO_CHANNEL_FR; spa_pod_builder_add(b, SPA_FORMAT_AUDIO_channels, SPA_POD_Int(2), SPA_FORMAT_AUDIO_position, SPA_POD_Array(sizeof(uint32_t), SPA_TYPE_Id, 2, position), 0); } else if ((conf.channels & LC3PLUS_HR_CHANNELS_1) && lc3plus_channels_supported(1)) { position[0] = SPA_AUDIO_CHANNEL_MONO; spa_pod_builder_add(b, SPA_FORMAT_AUDIO_channels, SPA_POD_Int(1), SPA_FORMAT_AUDIO_position, SPA_POD_Array(sizeof(uint32_t), SPA_TYPE_Id, 1, position), 0); } *param = spa_pod_builder_pop(b, &f[0]); return *param == NULL ? -EIO : 1; } static int codec_validate_config(const struct media_codec *codec, uint32_t flags, const void *caps, size_t caps_size, struct spa_audio_info *info) { const a2dp_lc3plus_hr_t *conf; if (caps == NULL || caps_size < sizeof(*conf)) return -EINVAL; conf = caps; spa_zero(*info); info->media_type = SPA_MEDIA_TYPE_audio; info->media_subtype = SPA_MEDIA_SUBTYPE_raw; info->info.raw.format = SPA_AUDIO_FORMAT_S24_32; switch (LC3PLUS_HR_GET_FREQUENCY(*conf)) { case LC3PLUS_HR_SAMPLING_FREQ_96000: if (!lc3plus_samplerate_supported(96000)) return -EINVAL; info->info.raw.rate = 96000; break; case LC3PLUS_HR_SAMPLING_FREQ_48000: if (!lc3plus_samplerate_supported(48000)) return -EINVAL; info->info.raw.rate = 48000; break; default: return -EINVAL; } switch (conf->channels) { case LC3PLUS_HR_CHANNELS_2: if (!lc3plus_channels_supported(2)) return -EINVAL; info->info.raw.channels = 2; info->info.raw.position[0] = SPA_AUDIO_CHANNEL_FL; info->info.raw.position[1] = SPA_AUDIO_CHANNEL_FR; break; case LC3PLUS_HR_CHANNELS_1: if (!lc3plus_channels_supported(1)) return -EINVAL; info->info.raw.channels = 1; info->info.raw.position[0] = SPA_AUDIO_CHANNEL_MONO; break; default: return -EINVAL; } switch (LC3PLUS_HR_GET_FRAME_DURATION(*conf)) { case LC3PLUS_HR_FRAME_DURATION_10MS: case LC3PLUS_HR_FRAME_DURATION_5MS: case LC3PLUS_HR_FRAME_DURATION_2_5MS: break; default: return -EINVAL; } return 0; } static size_t ceildiv(size_t v, size_t divisor) { if (v % divisor == 0) return v / divisor; else return v / divisor + 1; } static bool check_mtu_vs_frame_dms(struct impl *this) { /* Only 10ms frames can be fragmented (max 0xf fragments); * others must fit in single MTU */ size_t header_size = sizeof(struct rtp_header) + sizeof(struct rtp_payload); size_t max_fragments = (this->frame_dms == 100) ? 0xf : 1; size_t payload_size = lc3plus_enc_get_num_bytes(this->enc); return (size_t)this->mtu >= header_size + ceildiv(payload_size, max_fragments); } static void *codec_init(const struct media_codec *codec, uint32_t flags, void *config, size_t config_len, const struct spa_audio_info *info, void *props, size_t mtu) { a2dp_lc3plus_hr_t *conf = config; struct impl *this = NULL; struct spa_audio_info config_info; int size; int res; if (info->media_type != SPA_MEDIA_TYPE_audio || info->media_subtype != SPA_MEDIA_SUBTYPE_raw || info->info.raw.format != SPA_AUDIO_FORMAT_S24_32) { res = -EINVAL; goto error; } if ((this = calloc(1, sizeof(struct impl))) == NULL) goto error_errno; if ((res = codec_validate_config(codec, flags, config, config_len, &config_info)) < 0) goto error; this->mtu = mtu; this->samplerate = config_info.info.raw.rate; this->channels = config_info.info.raw.channels; this->bitrate = BITRATE_DEFAULT * this->channels; switch (LC3PLUS_HR_GET_FRAME_DURATION(*conf)) { case LC3PLUS_HR_FRAME_DURATION_10MS: this->frame_dms = 100; break; case LC3PLUS_HR_FRAME_DURATION_5MS: this->frame_dms = 50; break; case LC3PLUS_HR_FRAME_DURATION_2_5MS: this->frame_dms = 25; break; default: res = -EINVAL; goto error; } if ((size = lc3plus_enc_get_size(this->samplerate, this->channels)) == 0) { res = -EIO; goto error; } if ((this->enc = calloc(1, size)) == NULL) goto error_errno; if (lc3plus_enc_init(this->enc, this->samplerate, this->channels) != LC3PLUS_OK) { res = -EINVAL; goto error; } if (lc3plus_enc_set_frame_ms(this->enc, this->frame_dms/10.0f) != LC3PLUS_OK) { res = -EINVAL; goto error; } if (lc3plus_enc_set_hrmode(this->enc, 1) != LC3PLUS_OK) { res = -EINVAL; goto error; } while (1) { /* Find a valid bitrate */ if (lc3plus_enc_set_bitrate(this->enc, this->bitrate) != LC3PLUS_OK) { res = -EINVAL; goto error; } if (check_mtu_vs_frame_dms(this)) break; this->bitrate = this->bitrate * 3/4; } if ((size = lc3plus_dec_get_size(this->samplerate, this->channels)) == 0) { res = -EINVAL; goto error; } if ((this->dec = calloc(1, size)) == NULL) goto error_errno; if (lc3plus_dec_init(this->dec, this->samplerate, this->channels, LC3PLUS_PLC_ADVANCED) != LC3PLUS_OK) { res = -EINVAL; goto error; } if (lc3plus_dec_set_frame_ms(this->dec, this->frame_dms/10.0f) != LC3PLUS_OK) { res = -EINVAL; goto error; } if (lc3plus_dec_set_hrmode(this->dec, 1) != LC3PLUS_OK) { res = -EINVAL; goto error; } this->e.samples = lc3plus_enc_get_input_samples(this->enc); this->e.codesize = this->e.samples * this->channels * sizeof(int32_t); spa_assert(this->e.samples <= LC3PLUS_MAX_SAMPLES); this->e.bitrate = this->bitrate; this->e.next_bitrate = this->bitrate; return this; error_errno: res = -errno; goto error; error: if (this && this->enc) lc3plus_enc_free_memory(this->enc); if (this && this->dec) lc3plus_dec_free_memory(this->dec); free(this); errno = -res; return NULL; } static void codec_deinit(void *data) { struct impl *this = data; lc3plus_enc_free_memory(this->enc); lc3plus_dec_free_memory(this->dec); free(this); } static int codec_get_block_size(void *data) { struct impl *this = data; return this->e.codesize; } static int codec_abr_process (void *data, size_t unsent) { return -ENOTSUP; } static int codec_update_bitrate(struct impl *this) { this->e.next_bitrate = SPA_CLAMP(this->e.next_bitrate, BITRATE_MIN * this->channels, BITRATE_MAX * this->channels); if (this->e.next_bitrate == this->e.bitrate) return 0; this->e.bitrate = this->e.next_bitrate; if (lc3plus_enc_set_bitrate(this->enc, this->e.bitrate) != LC3PLUS_OK || !check_mtu_vs_frame_dms(this)) { lc3plus_enc_set_bitrate(this->enc, this->bitrate); return -EINVAL; } this->bitrate = this->e.bitrate; return 0; } static int codec_start_encode (void *data, void *dst, size_t dst_size, uint16_t seqnum, uint32_t timestamp) { struct impl *this = data; size_t header_size = sizeof(struct rtp_header) + sizeof(struct rtp_payload); if (dst_size <= header_size) return -EINVAL; codec_update_bitrate(this); this->e.header = (struct rtp_header *)dst; this->e.payload = SPA_PTROFF(dst, sizeof(struct rtp_header), struct rtp_payload); memset(dst, 0, header_size); this->e.payload->frame_count = 0; this->e.header->v = 2; this->e.header->pt = 96; this->e.header->sequence_number = htons(seqnum); this->e.header->timestamp = htonl(timestamp); this->e.header->ssrc = htonl(1); this->e.packet_size = header_size; return this->e.packet_size; } static void deinterleave_32_c2(int32_t * SPA_RESTRICT * SPA_RESTRICT dst, const int32_t * SPA_RESTRICT src, size_t n_samples) { /* We'll trust the compiler to optimize this */ const size_t n_channels = 2; size_t i, j; for (j = 0; j < n_samples; ++j) for (i = 0; i < n_channels; ++i) dst[i][j] = *src++; } static void interleave_32_c2(int32_t * SPA_RESTRICT dst, const int32_t * SPA_RESTRICT * SPA_RESTRICT src, size_t n_samples) { const size_t n_channels = 2; size_t i, j; for (j = 0; j < n_samples; ++j) for (i = 0; i < n_channels; ++i) *dst++ = src[i][j]; } static int codec_encode(void *data, const void *src, size_t src_size, void *dst, size_t dst_size, size_t *dst_out, int *need_flush) { struct impl *this = data; int frame_bytes; LC3PLUS_Error res; int size, processed; int header_size = sizeof(struct rtp_header) + sizeof(struct rtp_payload); int32_t *inputs[2]; if (src == NULL) { /* Produce fragment packets. * * We assume the caller gives the same buffer here as in the previous * calls to encode(), without changes in the buffer content. */ if (this->e.fragment == NULL || this->e.fragment_count <= 1 || this->e.fragment < dst || SPA_PTROFF(this->e.fragment, this->e.fragment_size, void) > SPA_PTROFF(dst, dst_size, void)) { this->e.fragment = NULL; return -EINVAL; } size = SPA_MIN(this->mtu - header_size, this->e.fragment_size); memmove(dst, this->e.fragment, size); *dst_out = size; this->e.payload->is_fragmented = 1; this->e.payload->frame_count = --this->e.fragment_count; this->e.payload->is_last_fragment = (this->e.fragment_count == 1); if (this->e.fragment_size > size && this->e.fragment_count > 1) { this->e.fragment = SPA_PTROFF(this->e.fragment, size, void); this->e.fragment_size -= size; *need_flush = NEED_FLUSH_FRAGMENT; } else { this->e.fragment = NULL; *need_flush = NEED_FLUSH_ALL; } return 0; } frame_bytes = lc3plus_enc_get_num_bytes(this->enc); processed = 0; if (src_size < (size_t)this->e.codesize) goto done; if (dst_size < (size_t)frame_bytes) goto done; if (this->e.payload->frame_count > 0 && this->e.packet_size + frame_bytes > this->mtu) goto done; if (this->channels == 1) { inputs[0] = (int32_t *)src; res = lc3plus_enc24(this->enc, inputs, dst, &size); } else { inputs[0] = this->buf[0]; inputs[1] = this->buf[1]; deinterleave_32_c2(inputs, src, this->e.samples); res = lc3plus_enc24(this->enc, inputs, dst, &size); } if (SPA_UNLIKELY(res != LC3PLUS_OK)) return -EINVAL; *dst_out = size; processed += this->e.codesize; this->e.packet_size += size; this->e.payload->frame_count++; done: if (this->e.payload->frame_count == 0) return processed; if (this->e.payload->frame_count < 0xf && this->frame_dms * (this->e.payload->frame_count + 1) < 200 && this->e.packet_size + frame_bytes <= this->mtu) return processed; /* add another frame */ if (this->e.packet_size > this->mtu) { /* Fragment packet */ spa_assert(this->e.payload->frame_count == 1); spa_assert(this->frame_dms == 100); this->e.fragment_count = ceildiv(this->e.packet_size - header_size, this->mtu - header_size); this->e.payload->is_fragmented = 1; this->e.payload->is_first_fragment = 1; this->e.payload->frame_count = this->e.fragment_count; this->e.fragment_size = this->e.packet_size - this->mtu; this->e.fragment = SPA_PTROFF(dst, *dst_out - this->e.fragment_size, void); *need_flush = NEED_FLUSH_FRAGMENT; /* * We keep the rest of the encoded frame in the same buffer, and rely * that the caller won't overwrite it before the next call to encode() */ *dst_out = SPA_PTRDIFF(this->e.fragment, dst); } else { *need_flush = NEED_FLUSH_ALL; } return processed; } static SPA_UNUSED int codec_start_decode (void *data, const void *src, size_t src_size, uint16_t *seqnum, uint32_t *timestamp) { struct impl *this = data; const struct rtp_header *header = src; const struct rtp_payload *payload = SPA_PTROFF(src, sizeof(struct rtp_header), void); size_t header_size = sizeof(struct rtp_header) + sizeof(struct rtp_payload); if (src_size <= header_size) return -EINVAL; if (seqnum) *seqnum = ntohs(header->sequence_number); if (timestamp) *timestamp = ntohl(header->timestamp); if (payload->is_fragmented) { if (payload->is_first_fragment) { this->d.fragment_size = 0; } else if (payload->frame_count + 1 != this->d.fragment_count || (payload->frame_count == 1 && !payload->is_last_fragment)){ /* Fragments not in right order: drop packet */ return -EINVAL; } this->d.fragment_count = payload->frame_count; this->d.frame_size = src_size - header_size; } else { if (payload->frame_count <= 0) return -EINVAL; this->d.fragment_count = 0; this->d.frame_size = (src_size - header_size) / payload->frame_count; if (this->d.frame_size <= 0) return -EINVAL; } return header_size; } static SPA_UNUSED int codec_decode(void *data, const void *src, size_t src_size, void *dst, size_t dst_size, size_t *dst_out) { struct impl *this = data; LC3PLUS_Error res; int32_t *outputs[2]; int consumed; int samples; if (this->d.fragment_count > 0) { /* Fragmented frame */ size_t avail; avail = SPA_MIN(sizeof(this->d.fragment) - this->d.fragment_size, src_size); memcpy(SPA_PTROFF(this->d.fragment, this->d.fragment_size, void), src, avail); this->d.fragment_size += avail; consumed = src_size; if (this->d.fragment_count > 1) { /* More fragments to come */ *dst_out = 0; return consumed; } src = this->d.fragment; src_size = this->d.fragment_size; this->d.fragment_count = 0; this->d.fragment_size = 0; } else { src_size = SPA_MIN((size_t)this->d.frame_size, src_size); consumed = src_size; } samples = lc3plus_dec_get_output_samples(this->dec); *dst_out = samples * this->channels * sizeof(int32_t); if (dst_size < *dst_out) return -EINVAL; if (this->channels == 1) { outputs[0] = (int32_t *)dst; res = lc3plus_dec24(this->dec, (void *)src, src_size, outputs, 0); } else { outputs[0] = this->buf[0]; outputs[1] = this->buf[1]; res = lc3plus_dec24(this->dec, (void *)src, src_size, outputs, 0); interleave_32_c2(dst, (const int32_t**)outputs, samples); } if (SPA_UNLIKELY(res != LC3PLUS_OK && res != LC3PLUS_DECODE_ERROR)) return -EINVAL; return consumed; } static int codec_reduce_bitpool(void *data) { struct impl *this = data; this->e.next_bitrate = SPA_CLAMP(this->bitrate * 3 / 4, BITRATE_MIN * this->channels, BITRATE_MAX * this->channels); return this->e.next_bitrate; } static int codec_increase_bitpool(void *data) { struct impl *this = data; this->e.next_bitrate = SPA_CLAMP(this->bitrate * 5 / 4, BITRATE_MIN * this->channels, BITRATE_MAX * this->channels); return this->e.next_bitrate; } const struct media_codec a2dp_codec_lc3plus_hr = { .id = SPA_BLUETOOTH_AUDIO_CODEC_LC3PLUS_HR, .kind = MEDIA_CODEC_A2DP, .name = "lc3plus_hr", .codec_id = A2DP_CODEC_VENDOR, .vendor = { .vendor_id = LC3PLUS_HR_VENDOR_ID, .codec_id = LC3PLUS_HR_CODEC_ID }, .description = "LC3plus HR", .fill_caps = codec_fill_caps, .select_config = codec_select_config, .enum_config = codec_enum_config, .validate_config = codec_validate_config, .caps_preference_cmp = codec_caps_preference_cmp, .init = codec_init, .deinit = codec_deinit, .get_block_size = codec_get_block_size, .abr_process = codec_abr_process, .start_encode = codec_start_encode, .encode = codec_encode, .start_decode = codec_start_decode, .decode = codec_decode, .reduce_bitpool = codec_reduce_bitpool, .increase_bitpool = codec_increase_bitpool }; MEDIA_CODEC_EXPORT_DEF( "lc3plus", &a2dp_codec_lc3plus_hr ); pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/bluez5/a2dp-codec-ldac.c000066400000000000000000000502211511204443500271160ustar00rootroot00000000000000/* Spa A2DP LDAC codec */ /* SPDX-FileCopyrightText: Copyright © 2020 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #include #include #include #include #include #include #ifdef ENABLE_LDAC_ABR #include #endif #if defined(ENABLE_LDAC_DEC) int ldacBT_decode(HANDLE_LDAC_BT handle, unsigned char *src, unsigned char *dst, LDACBT_SMPL_FMT_T fmt, int src_size, int *consumed, int *dst_out); int ldacBT_init_handle_decode(HANDLE_LDAC_BT handle, int channel_mode, int frequency, int dummy1, int dummy2, int dummy3); #endif #include "rtp.h" #include "media-codecs.h" #define LDACBT_EQMID_AUTO -1 #define LDAC_ABR_MAX_PACKET_NBYTES 1280 #define LDAC_ABR_INTERVAL_MS 5 /* 2 frames * 128 lsu / 48000 */ /* decrease ABR thresholds to increase stability */ #define LDAC_ABR_THRESHOLD_CRITICAL 6 #define LDAC_ABR_THRESHOLD_DANGEROUSTREND 4 #define LDAC_ABR_THRESHOLD_SAFETY_FOR_HQSQ 3 #define LDAC_ABR_SOCK_BUFFER_SIZE (LDAC_ABR_THRESHOLD_CRITICAL * LDAC_ABR_MAX_PACKET_NBYTES) static struct spa_log *log_; struct props { int eqmid; }; struct dec_data { int frames; size_t max_frame_bytes; }; struct impl { HANDLE_LDAC_BT ldac; #ifdef ENABLE_LDAC_ABR HANDLE_LDAC_ABR ldac_abr; #endif #ifdef ENABLE_LDAC_DEC HANDLE_LDAC_BT ldac_dec; #endif bool enable_abr; struct rtp_header *header; struct rtp_payload *payload; int mtu; int eqmid; int frequency; int fmt; int codesize; int frame_length; int frame_count; struct dec_data d; }; static int codec_fill_caps(const struct media_codec *codec, uint32_t flags, const struct spa_dict *settings, uint8_t caps[A2DP_MAX_CAPS_SIZE]) { static const a2dp_ldac_t a2dp_ldac = { .info.vendor_id = LDAC_VENDOR_ID, .info.codec_id = LDAC_CODEC_ID, .frequency = LDACBT_SAMPLING_FREQ_044100 | LDACBT_SAMPLING_FREQ_048000 | LDACBT_SAMPLING_FREQ_088200 | LDACBT_SAMPLING_FREQ_096000, .channel_mode = LDACBT_CHANNEL_MODE_MONO | LDACBT_CHANNEL_MODE_DUAL_CHANNEL | LDACBT_CHANNEL_MODE_STEREO, }; memcpy(caps, &a2dp_ldac, sizeof(a2dp_ldac)); return sizeof(a2dp_ldac); } static const struct media_codec_config ldac_frequencies[] = { { LDACBT_SAMPLING_FREQ_044100, 44100, 3 }, { LDACBT_SAMPLING_FREQ_048000, 48000, 2 }, { LDACBT_SAMPLING_FREQ_088200, 88200, 1 }, { LDACBT_SAMPLING_FREQ_096000, 96000, 0 }, }; static const struct media_codec_config ldac_channel_modes[] = { { LDACBT_CHANNEL_MODE_STEREO, 2, 2 }, { LDACBT_CHANNEL_MODE_DUAL_CHANNEL, 2, 1 }, { LDACBT_CHANNEL_MODE_MONO, 1, 0 }, }; static int codec_select_config(const struct media_codec *codec, uint32_t flags, const void *caps, size_t caps_size, const struct media_codec_audio_info *info, const struct spa_dict *settings, uint8_t config[A2DP_MAX_CAPS_SIZE], void **config_data) { a2dp_ldac_t conf; int i; if (caps_size < sizeof(conf)) return -EINVAL; memcpy(&conf, caps, sizeof(conf)); if (codec->vendor.vendor_id != conf.info.vendor_id || codec->vendor.codec_id != conf.info.codec_id) return -ENOTSUP; if ((i = media_codec_select_config(ldac_frequencies, SPA_N_ELEMENTS(ldac_frequencies), conf.frequency, info ? info->rate : A2DP_CODEC_DEFAULT_RATE )) < 0) return -ENOTSUP; conf.frequency = ldac_frequencies[i].config; if ((i = media_codec_select_config(ldac_channel_modes, SPA_N_ELEMENTS(ldac_channel_modes), conf.channel_mode, info ? info->channels : A2DP_CODEC_DEFAULT_CHANNELS )) < 0) return -ENOTSUP; conf.channel_mode = ldac_channel_modes[i].config; memcpy(config, &conf, sizeof(conf)); return sizeof(conf); } static int codec_enum_config(const struct media_codec *codec, uint32_t flags, const void *caps, size_t caps_size, uint32_t id, uint32_t idx, struct spa_pod_builder *b, struct spa_pod **param) { a2dp_ldac_t conf; struct spa_pod_frame f[2]; struct spa_pod_choice *choice; uint32_t i = 0; uint32_t position[2]; if (caps_size < sizeof(conf)) return -EINVAL; memcpy(&conf, caps, sizeof(conf)); if (idx > 0) return 0; spa_pod_builder_push_object(b, &f[0], SPA_TYPE_OBJECT_Format, id); spa_pod_builder_add(b, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_audio), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), SPA_FORMAT_AUDIO_format, SPA_POD_CHOICE_ENUM_Id(5, SPA_AUDIO_FORMAT_F32, SPA_AUDIO_FORMAT_F32, SPA_AUDIO_FORMAT_S32, SPA_AUDIO_FORMAT_S24, SPA_AUDIO_FORMAT_S16), 0); spa_pod_builder_prop(b, SPA_FORMAT_AUDIO_rate, 0); spa_pod_builder_push_choice(b, &f[1], SPA_CHOICE_None, 0); choice = (struct spa_pod_choice*)spa_pod_builder_frame(b, &f[1]); i = 0; if (conf.frequency & LDACBT_SAMPLING_FREQ_048000) { if (i++ == 0) spa_pod_builder_int(b, 48000); spa_pod_builder_int(b, 48000); } if (conf.frequency & LDACBT_SAMPLING_FREQ_044100) { if (i++ == 0) spa_pod_builder_int(b, 44100); spa_pod_builder_int(b, 44100); } if (conf.frequency & LDACBT_SAMPLING_FREQ_088200) { if (i++ == 0) spa_pod_builder_int(b, 88200); spa_pod_builder_int(b, 88200); } if (conf.frequency & LDACBT_SAMPLING_FREQ_096000) { if (i++ == 0) spa_pod_builder_int(b, 96000); spa_pod_builder_int(b, 96000); } if (i > 1) choice->body.type = SPA_CHOICE_Enum; spa_pod_builder_pop(b, &f[1]); if (i == 0) return -EINVAL; if (conf.channel_mode & LDACBT_CHANNEL_MODE_MONO && conf.channel_mode & (LDACBT_CHANNEL_MODE_STEREO | LDACBT_CHANNEL_MODE_DUAL_CHANNEL)) { spa_pod_builder_add(b, SPA_FORMAT_AUDIO_channels, SPA_POD_CHOICE_RANGE_Int(2, 1, 2), 0); } else if (conf.channel_mode & LDACBT_CHANNEL_MODE_MONO) { position[0] = SPA_AUDIO_CHANNEL_MONO; spa_pod_builder_add(b, SPA_FORMAT_AUDIO_channels, SPA_POD_Int(1), SPA_FORMAT_AUDIO_position, SPA_POD_Array(sizeof(uint32_t), SPA_TYPE_Id, 1, position), 0); } else { position[0] = SPA_AUDIO_CHANNEL_FL; position[1] = SPA_AUDIO_CHANNEL_FR; spa_pod_builder_add(b, SPA_FORMAT_AUDIO_channels, SPA_POD_Int(2), SPA_FORMAT_AUDIO_position, SPA_POD_Array(sizeof(uint32_t), SPA_TYPE_Id, 2, position), 0); } *param = spa_pod_builder_pop(b, &f[0]); return *param == NULL ? -EIO : 1; } static int codec_reduce_bitpool(void *data) { #ifdef ENABLE_LDAC_ABR return -ENOTSUP; #else struct impl *this = data; int res; if (this->eqmid == LDACBT_EQMID_MQ || !this->enable_abr) return this->eqmid; res = ldacBT_alter_eqmid_priority(this->ldac, LDACBT_EQMID_INC_CONNECTION); return res; #endif } static int codec_increase_bitpool(void *data) { #ifdef ENABLE_LDAC_ABR return -ENOTSUP; #else struct impl *this = data; int res; if (!this->enable_abr) return this->eqmid; res = ldacBT_alter_eqmid_priority(this->ldac, LDACBT_EQMID_INC_QUALITY); return res; #endif } static int codec_get_block_size(void *data) { struct impl *this = data; return this->codesize; } static int string_to_eqmid(const char * eqmid) { if (spa_streq("auto", eqmid)) return LDACBT_EQMID_AUTO; else if (spa_streq("hq", eqmid)) return LDACBT_EQMID_HQ; else if (spa_streq("sq", eqmid)) return LDACBT_EQMID_SQ; else if (spa_streq("mq", eqmid)) return LDACBT_EQMID_MQ; else return LDACBT_EQMID_AUTO; } static void *codec_init_props(const struct media_codec *codec, uint32_t flags, const struct spa_dict *settings) { struct props *p = calloc(1, sizeof(struct props)); const char *str; if (p == NULL) return NULL; if (settings == NULL || (str = spa_dict_lookup(settings, "bluez5.a2dp.ldac.quality")) == NULL) str = "auto"; p->eqmid = string_to_eqmid(str); return p; } static void codec_clear_props(void *props) { free(props); } static int codec_enum_props(void *props, const struct spa_dict *settings, uint32_t id, uint32_t idx, struct spa_pod_builder *b, struct spa_pod **param) { struct props *p = props; struct spa_pod_frame f[2]; switch (id) { case SPA_PARAM_PropInfo: { switch (idx) { case 0: spa_pod_builder_push_object(b, &f[0], SPA_TYPE_OBJECT_PropInfo, id); spa_pod_builder_prop(b, SPA_PROP_INFO_id, 0); spa_pod_builder_id(b, SPA_PROP_quality); spa_pod_builder_prop(b, SPA_PROP_INFO_description, 0); spa_pod_builder_string(b, "LDAC quality"); spa_pod_builder_prop(b, SPA_PROP_INFO_type, 0); spa_pod_builder_push_choice(b, &f[1], SPA_CHOICE_Enum, 0); spa_pod_builder_int(b, p->eqmid); spa_pod_builder_int(b, LDACBT_EQMID_AUTO); spa_pod_builder_int(b, LDACBT_EQMID_HQ); spa_pod_builder_int(b, LDACBT_EQMID_SQ); spa_pod_builder_int(b, LDACBT_EQMID_MQ); spa_pod_builder_pop(b, &f[1]); spa_pod_builder_prop(b, SPA_PROP_INFO_labels, 0); spa_pod_builder_push_struct(b, &f[1]); spa_pod_builder_int(b, LDACBT_EQMID_AUTO); spa_pod_builder_string(b, "auto"); spa_pod_builder_int(b, LDACBT_EQMID_HQ); spa_pod_builder_string(b, "hq"); spa_pod_builder_int(b, LDACBT_EQMID_SQ); spa_pod_builder_string(b, "sq"); spa_pod_builder_int(b, LDACBT_EQMID_MQ); spa_pod_builder_string(b, "mq"); spa_pod_builder_pop(b, &f[1]); *param = spa_pod_builder_pop(b, &f[0]); break; default: return 0; } break; } case SPA_PARAM_Props: { switch (idx) { case 0: *param = spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_Props, id, SPA_PROP_quality, SPA_POD_Int(p->eqmid)); break; default: return 0; } break; } default: return -ENOENT; } return 1; } static int codec_set_props(void *props, const struct spa_pod *param) { struct props *p = props; const int prev_eqmid = p->eqmid; if (param == NULL) { p->eqmid = LDACBT_EQMID_AUTO; } else { spa_pod_parse_object(param, SPA_TYPE_OBJECT_Props, NULL, SPA_PROP_quality, SPA_POD_OPT_Int(&p->eqmid)); if (p->eqmid != LDACBT_EQMID_AUTO && (p->eqmid < LDACBT_EQMID_HQ || p->eqmid > LDACBT_EQMID_MQ)) p->eqmid = prev_eqmid; } return prev_eqmid != p->eqmid; } static const char *ldac_strerror(int ldac_error) { #define LDAC_BT_ERROR_CASE(name) case name: return #name switch (ldac_error) { LDAC_BT_ERROR_CASE(LDACBT_ERR_NONE); LDAC_BT_ERROR_CASE(LDACBT_ERR_NON_FATAL); LDAC_BT_ERROR_CASE(LDACBT_ERR_BIT_ALLOCATION); LDAC_BT_ERROR_CASE(LDACBT_ERR_NOT_IMPLEMENTED); LDAC_BT_ERROR_CASE(LDACBT_ERR_NON_FATAL_ENCODE); LDAC_BT_ERROR_CASE(LDACBT_ERR_FATAL); LDAC_BT_ERROR_CASE(LDACBT_ERR_SYNTAX_BAND); LDAC_BT_ERROR_CASE(LDACBT_ERR_SYNTAX_GRAD_A); LDAC_BT_ERROR_CASE(LDACBT_ERR_SYNTAX_GRAD_B); LDAC_BT_ERROR_CASE(LDACBT_ERR_SYNTAX_GRAD_C); LDAC_BT_ERROR_CASE(LDACBT_ERR_SYNTAX_GRAD_D); LDAC_BT_ERROR_CASE(LDACBT_ERR_SYNTAX_GRAD_E); LDAC_BT_ERROR_CASE(LDACBT_ERR_SYNTAX_IDSF); LDAC_BT_ERROR_CASE(LDACBT_ERR_SYNTAX_SPEC); LDAC_BT_ERROR_CASE(LDACBT_ERR_BIT_PACKING); LDAC_BT_ERROR_CASE(LDACBT_ERR_ALLOC_MEMORY); LDAC_BT_ERROR_CASE(LDACBT_ERR_FATAL_HANDLE); LDAC_BT_ERROR_CASE(LDACBT_ERR_ILL_SYNCWORD); LDAC_BT_ERROR_CASE(LDACBT_ERR_ILL_SMPL_FORMAT); LDAC_BT_ERROR_CASE(LDACBT_ERR_ILL_PARAM); LDAC_BT_ERROR_CASE(LDACBT_ERR_ASSERT_SAMPLING_FREQ); LDAC_BT_ERROR_CASE(LDACBT_ERR_ASSERT_SUP_SAMPLING_FREQ); LDAC_BT_ERROR_CASE(LDACBT_ERR_CHECK_SAMPLING_FREQ); LDAC_BT_ERROR_CASE(LDACBT_ERR_ASSERT_CHANNEL_CONFIG); LDAC_BT_ERROR_CASE(LDACBT_ERR_CHECK_CHANNEL_CONFIG); LDAC_BT_ERROR_CASE(LDACBT_ERR_ASSERT_FRAME_LENGTH); LDAC_BT_ERROR_CASE(LDACBT_ERR_ASSERT_SUP_FRAME_LENGTH); LDAC_BT_ERROR_CASE(LDACBT_ERR_ASSERT_FRAME_STATUS); LDAC_BT_ERROR_CASE(LDACBT_ERR_ASSERT_NSHIFT); LDAC_BT_ERROR_CASE(LDACBT_ERR_ASSERT_CHANNEL_MODE); LDAC_BT_ERROR_CASE(LDACBT_ERR_ENC_INIT_ALLOC); LDAC_BT_ERROR_CASE(LDACBT_ERR_ENC_ILL_GRADMODE); LDAC_BT_ERROR_CASE(LDACBT_ERR_ENC_ILL_GRADPAR_A); LDAC_BT_ERROR_CASE(LDACBT_ERR_ENC_ILL_GRADPAR_B); LDAC_BT_ERROR_CASE(LDACBT_ERR_ENC_ILL_GRADPAR_C); LDAC_BT_ERROR_CASE(LDACBT_ERR_ENC_ILL_GRADPAR_D); LDAC_BT_ERROR_CASE(LDACBT_ERR_ENC_ILL_NBANDS); LDAC_BT_ERROR_CASE(LDACBT_ERR_PACK_BLOCK_FAILED); LDAC_BT_ERROR_CASE(LDACBT_ERR_DEC_INIT_ALLOC); LDAC_BT_ERROR_CASE(LDACBT_ERR_INPUT_BUFFER_SIZE); LDAC_BT_ERROR_CASE(LDACBT_ERR_UNPACK_BLOCK_FAILED); LDAC_BT_ERROR_CASE(LDACBT_ERR_UNPACK_BLOCK_ALIGN); LDAC_BT_ERROR_CASE(LDACBT_ERR_UNPACK_FRAME_ALIGN); LDAC_BT_ERROR_CASE(LDACBT_ERR_FRAME_LENGTH_OVER); LDAC_BT_ERROR_CASE(LDACBT_ERR_FRAME_ALIGN_OVER); LDAC_BT_ERROR_CASE(LDACBT_ERR_ALTER_EQMID_LIMITED); LDAC_BT_ERROR_CASE(LDACBT_ERR_HANDLE_NOT_INIT); LDAC_BT_ERROR_CASE(LDACBT_ERR_ILL_EQMID); LDAC_BT_ERROR_CASE(LDACBT_ERR_ILL_SAMPLING_FREQ); LDAC_BT_ERROR_CASE(LDACBT_ERR_ILL_NUM_CHANNEL); LDAC_BT_ERROR_CASE(LDACBT_ERR_ILL_MTU_SIZE); LDAC_BT_ERROR_CASE(LDACBT_ERR_DEC_CONFIG_UPDATED); default: return "other error"; } } static void *codec_init(const struct media_codec *codec, uint32_t flags, void *config, size_t config_len, const struct spa_audio_info *info, void *props, size_t mtu) { struct impl *this; a2dp_ldac_t *conf = config; int res; struct props *p = props; this = calloc(1, sizeof(struct impl)); if (this == NULL) goto error_errno; this->ldac = ldacBT_get_handle(); if (this->ldac == NULL) goto error_errno; #ifdef ENABLE_LDAC_DEC this->ldac_dec = ldacBT_get_handle(); if (this->ldac_dec == NULL) goto error_errno; #endif #ifdef ENABLE_LDAC_ABR this->ldac_abr = ldac_ABR_get_handle(); if (this->ldac_abr == NULL) goto error_errno; #endif if (p == NULL || p->eqmid == LDACBT_EQMID_AUTO) { this->eqmid = LDACBT_EQMID_SQ; this->enable_abr = true; } else { this->eqmid = p->eqmid; this->enable_abr = false; } this->mtu = mtu; this->frequency = info->info.raw.rate; this->codesize = info->info.raw.channels * LDACBT_ENC_LSU; switch (info->info.raw.format) { case SPA_AUDIO_FORMAT_F32: this->fmt = LDACBT_SMPL_FMT_F32; this->codesize *= 4; break; case SPA_AUDIO_FORMAT_S32: this->fmt = LDACBT_SMPL_FMT_S32; this->codesize *= 4; break; case SPA_AUDIO_FORMAT_S24: this->fmt = LDACBT_SMPL_FMT_S24; this->codesize *= 3; break; case SPA_AUDIO_FORMAT_S16: this->fmt = LDACBT_SMPL_FMT_S16; this->codesize *= 2; break; default: res = -EINVAL; goto error; } this->d.max_frame_bytes = (size_t)this->codesize * LDACBT_MAX_LSU / LDACBT_ENC_LSU; res = ldacBT_init_handle_encode(this->ldac, this->mtu, this->eqmid, conf->channel_mode, this->fmt, this->frequency); if (res < 0) { res = ldacBT_get_error_code(this->ldac); spa_log_error(log_, "LDAC encoder initialization failed: %s (%d)", ldac_strerror(LDACBT_API_ERR(res)), res); res = -EIO; goto error; } #ifdef ENABLE_LDAC_DEC res = ldacBT_init_handle_decode(this->ldac_dec, conf->channel_mode, this->frequency, 0, 0, 0); if (res < 0) { res = ldacBT_get_error_code(this->ldac_dec); spa_log_error(log_, "LDAC decoder initialization failed: %s (%d)", ldac_strerror(LDACBT_API_ERR(res)), res); res = -EIO; goto error; } #endif #ifdef ENABLE_LDAC_ABR res = ldac_ABR_Init(this->ldac_abr, LDAC_ABR_INTERVAL_MS); if (res < 0) goto error; res = ldac_ABR_set_thresholds(this->ldac_abr, LDAC_ABR_THRESHOLD_CRITICAL, LDAC_ABR_THRESHOLD_DANGEROUSTREND, LDAC_ABR_THRESHOLD_SAFETY_FOR_HQSQ); if (res < 0) goto error; #endif return this; error_errno: res = -errno; error: if (this && this->ldac) ldacBT_free_handle(this->ldac); #ifdef ENABLE_LDAC_ABR if (this && this->ldac_abr) ldac_ABR_free_handle(this->ldac_abr); #endif free(this); errno = -res; return NULL; } static void codec_deinit(void *data) { struct impl *this = data; if (this->ldac) ldacBT_free_handle(this->ldac); #ifdef ENABLE_LDAC_ABR if (this->ldac_abr) ldac_ABR_free_handle(this->ldac_abr); #endif free(this); } static int codec_update_props(void *data, void *props) { struct impl *this = data; struct props *p = props; int res; if (p == NULL) return 0; if (p->eqmid == LDACBT_EQMID_AUTO) { this->eqmid = LDACBT_EQMID_SQ; this->enable_abr = true; } else { this->eqmid = p->eqmid; this->enable_abr = false; } if ((res = ldacBT_set_eqmid(this->ldac, this->eqmid)) < 0) goto error; return 0; error: return res; } static int codec_abr_process(void *data, size_t unsent) { #ifdef ENABLE_LDAC_ABR struct impl *this = data; int res; res = ldac_ABR_Proc(this->ldac, this->ldac_abr, unsent / LDAC_ABR_MAX_PACKET_NBYTES, this->enable_abr); return res; #else return -ENOTSUP; #endif } static int codec_start_encode (void *data, void *dst, size_t dst_size, uint16_t seqnum, uint32_t timestamp) { struct impl *this = data; this->header = (struct rtp_header *)dst; this->payload = SPA_PTROFF(dst, sizeof(struct rtp_header), struct rtp_payload); memset(this->header, 0, sizeof(struct rtp_header)+sizeof(struct rtp_payload)); this->payload->frame_count = 0; this->header->v = 2; this->header->pt = 96; this->header->sequence_number = htons(seqnum); this->header->timestamp = htonl(timestamp); this->header->ssrc = htonl(1); return sizeof(struct rtp_header) + sizeof(struct rtp_payload); } static int codec_encode(void *data, const void *src, size_t src_size, void *dst, size_t dst_size, size_t *dst_out, int *need_flush) { struct impl *this = data; int res, src_used, dst_used, frame_num = 0; src_used = src_size; dst_used = dst_size; res = ldacBT_encode(this->ldac, (void*)src, &src_used, dst, &dst_used, &frame_num); if (SPA_UNLIKELY(res < 0)) return -EINVAL; *dst_out = dst_used; this->payload->frame_count += frame_num; *need_flush = (this->payload->frame_count > 0) ? NEED_FLUSH_ALL : NEED_FLUSH_NO; return src_used; } #ifdef ENABLE_LDAC_DEC static int codec_start_decode (void *data, const void *src, size_t src_size, uint16_t *seqnum, uint32_t *timestamp) { struct impl *this = data; const struct rtp_header *header = src; const struct rtp_payload *payload = SPA_PTROFF(src, sizeof(struct rtp_header), struct rtp_payload); size_t header_size = sizeof(struct rtp_header) + sizeof(struct rtp_payload); if (src_size <= header_size) return -EINVAL; if (seqnum) *seqnum = ntohs(header->sequence_number); if (timestamp) *timestamp = ntohl(header->timestamp); this->d.frames = payload->frame_count; return header_size; } static int codec_decode(void *data, const void *src, size_t src_size, void *dst, size_t dst_size, size_t *dst_out) { struct impl *this = data; void *to = dst; size_t avail = dst_size; size_t processed = 0; *dst_out = 0; for (; this->d.frames > 0; --this->d.frames) { int consumed; int written; if (avail < this->d.max_frame_bytes) return -EINVAL; if (ldacBT_decode(this->ldac_dec, (void *)src, to, this->fmt, src_size, &consumed, &written) != 0) { return -EINVAL; } if (consumed < 0 || (size_t)consumed > src_size) return -EINVAL; if (written < 0 || (size_t)written > avail) return -EINVAL; src = SPA_PTROFF(src, consumed, const void); src_size -= consumed; processed += consumed; to = SPA_PTROFF(to, written, void); avail -= written; *dst_out += written; } return processed; } #endif static void codec_get_delay(void *data, uint32_t *encoder, uint32_t *decoder) { struct impl *this = data; if (encoder) { switch (this->frequency) { case 96000: case 88200: *encoder = 256; break; default: *encoder = 128; break; } } if (decoder) *decoder = 0; } static void codec_set_log(struct spa_log *global_log) { log_ = global_log; spa_log_topic_init(log_, &codec_plugin_log_topic); } const struct media_codec a2dp_codec_ldac = { .id = SPA_BLUETOOTH_AUDIO_CODEC_LDAC, .kind = MEDIA_CODEC_A2DP, .codec_id = A2DP_CODEC_VENDOR, .vendor = { .vendor_id = LDAC_VENDOR_ID, .codec_id = LDAC_CODEC_ID }, .name = "ldac", .description = "LDAC", #ifdef ENABLE_LDAC_ABR .send_buf_size = LDAC_ABR_SOCK_BUFFER_SIZE, #endif .fill_caps = codec_fill_caps, .select_config = codec_select_config, .enum_config = codec_enum_config, .init_props = codec_init_props, .enum_props = codec_enum_props, .set_props = codec_set_props, .clear_props = codec_clear_props, .init = codec_init, .deinit = codec_deinit, .update_props = codec_update_props, .get_block_size = codec_get_block_size, .abr_process = codec_abr_process, .start_encode = codec_start_encode, .encode = codec_encode, #ifdef ENABLE_LDAC_DEC .start_decode = codec_start_decode, .decode = codec_decode, #endif .reduce_bitpool = codec_reduce_bitpool, .increase_bitpool = codec_increase_bitpool, .get_delay = codec_get_delay, .set_log = codec_set_log, }; MEDIA_CODEC_EXPORT_DEF( "ldac", &a2dp_codec_ldac ); pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/bluez5/a2dp-codec-opus-g.c000066400000000000000000000320421511204443500274260ustar00rootroot00000000000000/* Spa A2DP Opus Codec */ /* SPDX-FileCopyrightText: Copyright © 2020 Wim Taymans */ /* SPDX-FileCopyrightText: Copyright © 2022 Pauli Virtanen */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "rtp.h" #include "media-codecs.h" static struct spa_log *log; struct dec_data { int32_t delay; }; struct enc_data { struct rtp_header *header; struct rtp_payload *payload; int samples; int codesize; int frame_dms; int bitrate; int packet_size; int32_t delay; }; struct impl { OpusEncoder *enc; OpusDecoder *dec; int mtu; int samplerate; int channels; int application; struct dec_data d; struct enc_data e; }; static int codec_fill_caps(const struct media_codec *codec, uint32_t flags, const struct spa_dict *settings, uint8_t caps[A2DP_MAX_CAPS_SIZE]) { a2dp_opus_g_t conf = { .info = codec->vendor, }; OPUS_G_SET(conf, OPUS_G_FREQUENCY_48000, OPUS_G_DURATION_100 | OPUS_G_DURATION_200, OPUS_G_CHANNELS_MONO | OPUS_G_CHANNELS_STEREO | OPUS_G_CHANNELS_MONO_2); memcpy(caps, &conf, sizeof(conf)); return sizeof(conf); } static int codec_select_config(const struct media_codec *codec, uint32_t flags, const void *caps, size_t caps_size, const struct media_codec_audio_info *info, const struct spa_dict *global_settings, uint8_t config[A2DP_MAX_CAPS_SIZE], void **config_data) { a2dp_opus_g_t conf; int frequency, duration, channels; if (caps_size < sizeof(conf)) return -EINVAL; memcpy(&conf, caps, sizeof(conf)); if (codec->vendor.vendor_id != conf.info.vendor_id || codec->vendor.codec_id != conf.info.codec_id) return -ENOTSUP; if (OPUS_G_GET_FREQUENCY(conf) & OPUS_G_FREQUENCY_48000) frequency = OPUS_G_FREQUENCY_48000; else return -EINVAL; if (OPUS_G_GET_DURATION(conf) & OPUS_G_DURATION_200) duration = OPUS_G_DURATION_200; else if (OPUS_G_GET_DURATION(conf) & OPUS_G_DURATION_100) duration = OPUS_G_DURATION_100; else return -EINVAL; if (OPUS_G_GET_CHANNELS(conf) & OPUS_G_CHANNELS_STEREO) channels = OPUS_G_CHANNELS_STEREO; else if (OPUS_G_GET_CHANNELS(conf) & OPUS_G_CHANNELS_MONO) channels = OPUS_G_CHANNELS_MONO; else if (OPUS_G_GET_CHANNELS(conf) & OPUS_G_CHANNELS_MONO_2) channels = OPUS_G_CHANNELS_MONO_2; else return -EINVAL; OPUS_G_SET(conf, frequency, duration, channels); memcpy(config, &conf, sizeof(conf)); return sizeof(conf); } static int codec_caps_preference_cmp(const struct media_codec *codec, uint32_t flags, const void *caps1, size_t caps1_size, const void *caps2, size_t caps2_size, const struct media_codec_audio_info *info, const struct spa_dict *global_settings) { a2dp_opus_g_t conf1, conf2, cap1, cap2; a2dp_opus_g_t *conf; int res1, res2; int a, b; /* Order selected configurations by preference */ res1 = codec->select_config(codec, flags, caps1, caps1_size, info, global_settings, (uint8_t *)&conf1, NULL); res2 = codec->select_config(codec, flags, caps2, caps2_size, info, global_settings, (uint8_t *)&conf2, NULL); #define PREFER_EXPR(expr) \ do { \ conf = &conf1; \ a = (expr); \ conf = &conf2; \ b = (expr); \ if (a != b) \ return b - a; \ } while (0) #define PREFER_BOOL(expr) PREFER_EXPR((expr) ? 1 : 0) /* Prefer valid */ a = (res1 > 0 && (size_t)res1 == sizeof(a2dp_opus_g_t)) ? 1 : 0; b = (res2 > 0 && (size_t)res2 == sizeof(a2dp_opus_g_t)) ? 1 : 0; if (!a || !b) return b - a; memcpy(&cap1, caps1, sizeof(cap1)); memcpy(&cap2, caps2, sizeof(cap2)); PREFER_EXPR(OPUS_G_GET_CHANNELS(*conf) & OPUS_G_CHANNELS_STEREO); return 0; #undef PREFER_EXPR #undef PREFER_BOOL } static int codec_enum_config(const struct media_codec *codec, uint32_t flags, const void *caps, size_t caps_size, uint32_t id, uint32_t idx, struct spa_pod_builder *b, struct spa_pod **param) { a2dp_opus_g_t conf; struct spa_pod_frame f[1]; uint32_t position[2]; int channels; if (caps_size < sizeof(conf)) return -EINVAL; memcpy(&conf, caps, sizeof(conf)); if (idx > 0) return 0; switch (OPUS_G_GET_CHANNELS(conf)) { case OPUS_G_CHANNELS_STEREO: channels = 2; position[0] = SPA_AUDIO_CHANNEL_FL; position[1] = SPA_AUDIO_CHANNEL_FR; break; case OPUS_G_CHANNELS_MONO: channels = 1; position[0] = SPA_AUDIO_CHANNEL_MONO; break; case OPUS_G_CHANNELS_MONO_2: channels = 2; position[0] = SPA_AUDIO_CHANNEL_AUX0; position[1] = SPA_AUDIO_CHANNEL_AUX1; break; default: return -EINVAL; } spa_pod_builder_push_object(b, &f[0], SPA_TYPE_OBJECT_Format, id); spa_pod_builder_add(b, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_audio), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), SPA_FORMAT_AUDIO_format, SPA_POD_Id(SPA_AUDIO_FORMAT_F32), SPA_FORMAT_AUDIO_rate, SPA_POD_CHOICE_ENUM_Int(6, 48000, 48000, 24000, 16000, 12000, 8000), SPA_FORMAT_AUDIO_channels, SPA_POD_Int(channels), SPA_FORMAT_AUDIO_position, SPA_POD_Array(sizeof(uint32_t), SPA_TYPE_Id, channels, position), 0); *param = spa_pod_builder_pop(b, &f[0]); return *param == NULL ? -EIO : 1; } static int codec_validate_config(const struct media_codec *codec, uint32_t flags, const void *caps, size_t caps_size, struct spa_audio_info *info) { a2dp_opus_g_t conf; if (caps == NULL || caps_size < sizeof(conf)) return -EINVAL; memcpy(&conf, caps, sizeof(conf)); spa_zero(*info); info->media_type = SPA_MEDIA_TYPE_audio; info->media_subtype = SPA_MEDIA_SUBTYPE_raw; info->info.raw.format = SPA_AUDIO_FORMAT_F32; info->info.raw.rate = 0; /* not specified by config */ switch (OPUS_G_GET_FREQUENCY(conf)) { case OPUS_G_FREQUENCY_48000: break; default: return -EINVAL; } switch (OPUS_G_GET_DURATION(conf)) { case OPUS_G_DURATION_100: case OPUS_G_DURATION_200: break; default: return -EINVAL; } switch (OPUS_G_GET_CHANNELS(conf)) { case OPUS_G_CHANNELS_STEREO: info->info.raw.channels = 2; info->info.raw.position[0] = SPA_AUDIO_CHANNEL_FL; info->info.raw.position[1] = SPA_AUDIO_CHANNEL_FR; break; case OPUS_G_CHANNELS_MONO: info->info.raw.channels = 1; info->info.raw.position[0] = SPA_AUDIO_CHANNEL_MONO; break; case OPUS_G_CHANNELS_MONO_2: info->info.raw.channels = 2; info->info.raw.position[0] = SPA_AUDIO_CHANNEL_AUX0; info->info.raw.position[1] = SPA_AUDIO_CHANNEL_AUX1; break; default: return -EINVAL; } return 0; } static int parse_frame_dms(int value) { switch (value) { case OPUS_G_DURATION_100: return 100; case OPUS_G_DURATION_200: return 200; default: return -EINVAL; } } static void *codec_init(const struct media_codec *codec, uint32_t flags, void *config, size_t config_len, const struct spa_audio_info *info, void *props, size_t mtu) { a2dp_opus_g_t conf; struct impl *this = NULL; struct spa_audio_info config_info; int res; if (config_len < sizeof(conf)) { res = -EINVAL; goto error; } memcpy(&conf, config, sizeof(conf)); if (info->media_type != SPA_MEDIA_TYPE_audio || info->media_subtype != SPA_MEDIA_SUBTYPE_raw || info->info.raw.format != SPA_AUDIO_FORMAT_F32) { res = -EINVAL; goto error; } if ((this = calloc(1, sizeof(struct impl))) == NULL) goto error_errno; if ((res = codec_validate_config(codec, flags, config, config_len, &config_info)) < 0) goto error; if (config_info.info.raw.channels != info->info.raw.channels) { res = -EINVAL; goto error; } this->mtu = mtu; this->samplerate = info->info.raw.rate; this->channels = config_info.info.raw.channels; this->application = OPUS_APPLICATION_AUDIO; /* * Setup encoder */ this->enc = opus_encoder_create(this->samplerate, this->channels, this->application, &res); if (this->enc == NULL) { res = -EINVAL; goto error; } if ((this->e.frame_dms = parse_frame_dms(OPUS_G_GET_DURATION(conf))) < 0) { res = -EINVAL; goto error; } this->e.samples = this->e.frame_dms * this->samplerate / 10000; this->e.codesize = this->e.samples * (int)this->channels * sizeof(float); int header_size = sizeof(struct rtp_header) + sizeof(struct rtp_payload); this->e.bitrate = SPA_MIN(128000 * this->channels, (int64_t)8 * (this->mtu - header_size) * 10000 / this->e.frame_dms); opus_encoder_ctl(this->enc, OPUS_SET_BITRATE(this->e.bitrate)); opus_encoder_ctl(this->enc, OPUS_GET_LOOKAHEAD(&this->e.delay)); /* * Setup decoder */ this->dec = opus_decoder_create(this->samplerate, this->channels, &res); if (this->dec == NULL) { res = -EINVAL; goto error; } opus_decoder_ctl(this->dec, OPUS_GET_LOOKAHEAD(&this->d.delay)); return this; error_errno: res = -errno; goto error; error: if (this && this->enc) opus_encoder_destroy(this->enc); if (this && this->dec) opus_decoder_destroy(this->dec); free(this); errno = -res; return NULL; } static void codec_deinit(void *data) { struct impl *this = data; opus_encoder_destroy(this->enc); opus_decoder_destroy(this->dec); free(this); } static int codec_get_block_size(void *data) { struct impl *this = data; return this->e.codesize; } static int codec_start_encode (void *data, void *dst, size_t dst_size, uint16_t seqnum, uint32_t timestamp) { struct impl *this = data; size_t header_size = sizeof(struct rtp_header) + sizeof(struct rtp_payload); if (dst_size <= header_size) return -EINVAL; this->e.header = (struct rtp_header *)dst; this->e.payload = SPA_PTROFF(dst, sizeof(struct rtp_header), struct rtp_payload); memset(dst, 0, header_size); this->e.payload->frame_count = 0; this->e.header->v = 2; this->e.header->pt = 96; this->e.header->sequence_number = htons(seqnum); this->e.header->timestamp = htonl(timestamp); this->e.header->ssrc = htonl(1); this->e.packet_size = header_size; return this->e.packet_size; } static int codec_encode(void *data, const void *src, size_t src_size, void *dst, size_t dst_size, size_t *dst_out, int *need_flush) { struct impl *this = data; int res; if (src_size < (size_t)this->e.codesize) { *dst_out = 0; return 0; } if (this->e.packet_size >= this->mtu) return -EINVAL; dst_size = SPA_MIN(dst_size, (size_t)(this->mtu - this->e.packet_size)); res = opus_encode_float(this->enc, src, this->e.samples, dst, dst_size); if (res < 0) return -EINVAL; *dst_out = res; this->e.packet_size += res; this->e.payload->frame_count++; *need_flush = NEED_FLUSH_ALL; return this->e.codesize; } static int codec_start_decode (void *data, const void *src, size_t src_size, uint16_t *seqnum, uint32_t *timestamp) { struct impl SPA_UNUSED *this = data; const struct rtp_header *header = src; const struct rtp_payload *payload = SPA_PTROFF(src, sizeof(struct rtp_header), void); size_t header_size = sizeof(struct rtp_header) + sizeof(struct rtp_payload); if (src_size <= header_size) return -EINVAL; if (seqnum) *seqnum = ntohs(header->sequence_number); if (timestamp) *timestamp = ntohl(header->timestamp); if (payload->is_fragmented) return -EINVAL; /* fragmentation not supported */ if (payload->frame_count != 1) return -EINVAL; /* wrong number of frames in packet */ return header_size; } static int codec_decode(void *data, const void *src, size_t src_size, void *dst, size_t dst_size, size_t *dst_out) { struct impl SPA_UNUSED *this = data; int consumed = src_size; int res; int dst_samples; dst_samples = dst_size / (sizeof(float) * this->channels); res = opus_decode_float(this->dec, src, src_size, dst, dst_samples, 0); if (res < 0) return -EINVAL; *dst_out = (size_t)res * this->channels * sizeof(float); return consumed; } static int codec_abr_process (void *data, size_t unsent) { return -ENOTSUP; } static int codec_reduce_bitpool(void *data) { return 0; } static int codec_increase_bitpool(void *data) { return 0; } static void codec_get_delay(void *data, uint32_t *encoder, uint32_t *decoder) { struct impl *this = data; if (encoder) *encoder = this->e.delay; if (decoder) *decoder = this->d.delay; } static void codec_set_log(struct spa_log *global_log) { log = global_log; spa_log_topic_init(log, &codec_plugin_log_topic); } const struct media_codec a2dp_codec_opus_g = { .id = SPA_BLUETOOTH_AUDIO_CODEC_OPUS_G, .kind = MEDIA_CODEC_A2DP, .codec_id = A2DP_CODEC_VENDOR, .vendor = { .vendor_id = OPUS_G_VENDOR_ID, .codec_id = OPUS_G_CODEC_ID }, .select_config = codec_select_config, .enum_config = codec_enum_config, .validate_config = codec_validate_config, .caps_preference_cmp = codec_caps_preference_cmp, .init = codec_init, .deinit = codec_deinit, .get_block_size = codec_get_block_size, .start_encode = codec_start_encode, .encode = codec_encode, .abr_process = codec_abr_process, .reduce_bitpool = codec_reduce_bitpool, .increase_bitpool = codec_increase_bitpool, .set_log = codec_set_log, .start_decode = codec_start_decode, .decode = codec_decode, .name = "opus_g", .description = "Opus", .fill_caps = codec_fill_caps, .get_delay = codec_get_delay, }; MEDIA_CODEC_EXPORT_DEF( "opus-g", &a2dp_codec_opus_g ); pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/bluez5/a2dp-codec-opus.c000066400000000000000000001240541511204443500272070ustar00rootroot00000000000000/* Spa A2DP Opus Codec */ /* SPDX-FileCopyrightText: Copyright © 2020 Wim Taymans */ /* SPDX-FileCopyrightText: Copyright © 2022 Pauli Virtanen */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "rtp.h" #include "media-codecs.h" static struct spa_log *log; #define BUFSIZE_FROM_BITRATE(frame_dms,bitrate) ((bitrate)/8 * (frame_dms) / 10000 * 5/4) /* estimate */ /* * Opus CVBR target bitrate. When connecting, it is set to the INITIAL * value, and after that adjusted according to link quality between the MIN and * MAX values. The bitrate adjusts up to either MAX or the value at * which the socket buffer starts filling up, whichever is lower. * * With perfect connection quality, the target bitrate converges to the MAX * value. Under realistic conditions, the upper limit may often be as low as * 300-500kbit/s, so the INITIAL values are not higher than this. * * The MAX is here set to 2-2.5x and INITIAL to 1.5x the upper Opus recommended * values [1], to be safer quality-wise for CVBR, and MIN to the lower * recommended value. * * [1] https://wiki.xiph.org/Opus_Recommended_Settings */ #define BITRATE_INITIAL 192000 #define BITRATE_MAX 320000 #define BITRATE_MIN 96000 #define BITRATE_INITIAL_51 384000 #define BITRATE_MAX_51 600000 #define BITRATE_MIN_51 128000 #define BITRATE_INITIAL_71 450000 #define BITRATE_MAX_71 900000 #define BITRATE_MIN_71 256000 #define BITRATE_DUPLEX_BIDI 160000 #define MAX_CHANNELS SPA_AUDIO_MAX_CHANNELS #define OPUS_05_MAX_BYTES (15 * 1024) struct props { uint32_t channels; uint32_t coupled_streams; uint32_t location; uint32_t max_bitrate; uint8_t frame_duration; int application; uint32_t bidi_channels; uint32_t bidi_coupled_streams; uint32_t bidi_location; uint32_t bidi_max_bitrate; uint32_t bidi_frame_duration; int bidi_application; }; struct dec_data { int fragment_size; int fragment_count; uint8_t fragment[OPUS_05_MAX_BYTES]; int32_t delay; }; struct abr { uint64_t now; uint64_t last_update; uint32_t buffer_level; uint32_t packet_size; uint32_t total_size; bool bad; uint64_t last_change; uint64_t retry_interval; bool prev_bad; }; struct enc_data { struct rtp_header *header; struct rtp_payload *payload; struct abr abr; int samples; int codesize; int packet_size; int fragment_size; int fragment_count; void *fragment; int bitrate_min; int bitrate_max; int bitrate; int next_bitrate; int frame_dms; int application; int32_t delay; }; struct impl { OpusMSEncoder *enc; OpusMSDecoder *dec; int mtu; int samplerate; int application; uint8_t channels; uint8_t streams; uint8_t coupled_streams; bool is_bidi; struct dec_data d; struct enc_data e; }; struct audio_location { uint32_t mask; enum spa_audio_channel position; }; struct surround_encoder_mapping { uint8_t channels; uint8_t coupled_streams; uint32_t location; uint8_t mapping[8]; /**< permutation streams -> vorbis order */ uint8_t inv_mapping[8]; /**< permutation vorbis order -> streams */ }; /* Bluetooth SIG, Assigned Numbers, Generic Audio, Audio Location Definitions */ #define BT_AUDIO_LOCATION_FL 0x00000001 /* Front Left */ #define BT_AUDIO_LOCATION_FR 0x00000002 /* Front Right */ #define BT_AUDIO_LOCATION_FC 0x00000004 /* Front Center */ #define BT_AUDIO_LOCATION_LFE 0x00000008 /* Low Frequency Effects 1 */ #define BT_AUDIO_LOCATION_RL 0x00000010 /* Back Left */ #define BT_AUDIO_LOCATION_RR 0x00000020 /* Back Right */ #define BT_AUDIO_LOCATION_FLC 0x00000040 /* Front Left of Center */ #define BT_AUDIO_LOCATION_FRC 0x00000080 /* Front Right of Center */ #define BT_AUDIO_LOCATION_RC 0x00000100 /* Back Center */ #define BT_AUDIO_LOCATION_LFE2 0x00000200 /* Low Frequency Effects 2 */ #define BT_AUDIO_LOCATION_SL 0x00000400 /* Side Left */ #define BT_AUDIO_LOCATION_SR 0x00000800 /* Side Right */ #define BT_AUDIO_LOCATION_TFL 0x00001000 /* Top Front Left */ #define BT_AUDIO_LOCATION_TFR 0x00002000 /* Top Front Right */ #define BT_AUDIO_LOCATION_TFC 0x00004000 /* Top Front Center */ #define BT_AUDIO_LOCATION_TC 0x00008000 /* Top Center */ #define BT_AUDIO_LOCATION_TRL 0x00010000 /* Top Back Left */ #define BT_AUDIO_LOCATION_TRR 0x00020000 /* Top Back Right */ #define BT_AUDIO_LOCATION_TSL 0x00040000 /* Top Side Left */ #define BT_AUDIO_LOCATION_TSR 0x00080000 /* Top Side Right */ #define BT_AUDIO_LOCATION_TRC 0x00100000 /* Top Back Center */ #define BT_AUDIO_LOCATION_BC 0x00200000 /* Bottom Front Center */ #define BT_AUDIO_LOCATION_BLC 0x00400000 /* Bottom Front Left */ #define BT_AUDIO_LOCATION_BRC 0x00800000 /* Bottom Front Right */ #define BT_AUDIO_LOCATION_FLW 0x01000000 /* Front Left Wide */ #define BT_AUDIO_LOCATION_FRW 0x02000000 /* Front Right Wide */ #define BT_AUDIO_LOCATION_SSL 0x04000000 /* Left Surround */ #define BT_AUDIO_LOCATION_SSR 0x08000000 /* Right Surround */ #define BT_AUDIO_LOCATION_ANY 0x0fffffff static const struct audio_location audio_locations[] = { { BT_AUDIO_LOCATION_FL, SPA_AUDIO_CHANNEL_FL }, { BT_AUDIO_LOCATION_FR, SPA_AUDIO_CHANNEL_FR }, { BT_AUDIO_LOCATION_SL, SPA_AUDIO_CHANNEL_SL }, { BT_AUDIO_LOCATION_SR, SPA_AUDIO_CHANNEL_SR }, { BT_AUDIO_LOCATION_RL, SPA_AUDIO_CHANNEL_RL }, { BT_AUDIO_LOCATION_RR, SPA_AUDIO_CHANNEL_RR }, { BT_AUDIO_LOCATION_FLC, SPA_AUDIO_CHANNEL_FLC }, { BT_AUDIO_LOCATION_FRC, SPA_AUDIO_CHANNEL_FRC }, { BT_AUDIO_LOCATION_TFL, SPA_AUDIO_CHANNEL_TFL }, { BT_AUDIO_LOCATION_TFR, SPA_AUDIO_CHANNEL_TFR }, { BT_AUDIO_LOCATION_TSL, SPA_AUDIO_CHANNEL_TSL }, { BT_AUDIO_LOCATION_TSR, SPA_AUDIO_CHANNEL_TSR }, { BT_AUDIO_LOCATION_TRL, SPA_AUDIO_CHANNEL_TRL }, { BT_AUDIO_LOCATION_TRR, SPA_AUDIO_CHANNEL_TRR }, { BT_AUDIO_LOCATION_BLC, SPA_AUDIO_CHANNEL_BLC }, { BT_AUDIO_LOCATION_BRC, SPA_AUDIO_CHANNEL_BRC }, { BT_AUDIO_LOCATION_FLW, SPA_AUDIO_CHANNEL_FLW }, { BT_AUDIO_LOCATION_FRW, SPA_AUDIO_CHANNEL_FRW }, { BT_AUDIO_LOCATION_SSL, SPA_AUDIO_CHANNEL_SL }, /* ~ Side Left */ { BT_AUDIO_LOCATION_SSR, SPA_AUDIO_CHANNEL_SR }, /* ~ Side Right */ { BT_AUDIO_LOCATION_FC, SPA_AUDIO_CHANNEL_FC }, { BT_AUDIO_LOCATION_RC, SPA_AUDIO_CHANNEL_RC }, { BT_AUDIO_LOCATION_TFC, SPA_AUDIO_CHANNEL_TFC }, { BT_AUDIO_LOCATION_TC, SPA_AUDIO_CHANNEL_TC }, { BT_AUDIO_LOCATION_TRC, SPA_AUDIO_CHANNEL_TRC }, { BT_AUDIO_LOCATION_BC, SPA_AUDIO_CHANNEL_BC }, { BT_AUDIO_LOCATION_LFE, SPA_AUDIO_CHANNEL_LFE }, { BT_AUDIO_LOCATION_LFE2, SPA_AUDIO_CHANNEL_LFE2 }, }; /* Opus surround encoder mapping tables for the supported channel configurations */ static const struct surround_encoder_mapping surround_encoders[] = { { 1, 0, (0x0), { 0 }, { 0 } }, { 2, 1, (BT_AUDIO_LOCATION_FL | BT_AUDIO_LOCATION_FR), { 0, 1 }, { 0, 1 } }, { 3, 1, (BT_AUDIO_LOCATION_FL | BT_AUDIO_LOCATION_FR | BT_AUDIO_LOCATION_FC), { 0, 2, 1 }, { 0, 2, 1 } }, { 4, 2, (BT_AUDIO_LOCATION_FL | BT_AUDIO_LOCATION_FR | BT_AUDIO_LOCATION_RL | BT_AUDIO_LOCATION_RR), { 0, 1, 2, 3 }, { 0, 1, 2, 3 } }, { 5, 2, (BT_AUDIO_LOCATION_FL | BT_AUDIO_LOCATION_FR | BT_AUDIO_LOCATION_RL | BT_AUDIO_LOCATION_RR | BT_AUDIO_LOCATION_FC), { 0, 4, 1, 2, 3 }, { 0, 2, 3, 4, 1 } }, { 6, 2, (BT_AUDIO_LOCATION_FL | BT_AUDIO_LOCATION_FR | BT_AUDIO_LOCATION_RL | BT_AUDIO_LOCATION_RR | BT_AUDIO_LOCATION_FC | BT_AUDIO_LOCATION_LFE), { 0, 4, 1, 2, 3, 5 }, { 0, 2, 3, 4, 1, 5 } }, { 7, 3, (BT_AUDIO_LOCATION_FL | BT_AUDIO_LOCATION_FR | BT_AUDIO_LOCATION_SL | BT_AUDIO_LOCATION_SR | BT_AUDIO_LOCATION_FC | BT_AUDIO_LOCATION_RC | BT_AUDIO_LOCATION_LFE), { 0, 4, 1, 2, 3, 5, 6 }, { 0, 2, 3, 4, 1, 5, 6 } }, { 8, 3, (BT_AUDIO_LOCATION_FL | BT_AUDIO_LOCATION_FR | BT_AUDIO_LOCATION_SL | BT_AUDIO_LOCATION_SR | BT_AUDIO_LOCATION_RL | BT_AUDIO_LOCATION_RR | BT_AUDIO_LOCATION_FC | BT_AUDIO_LOCATION_LFE), { 0, 6, 1, 2, 3, 4, 5, 7 }, { 0, 2, 3, 4, 5, 6, 1, 7 } }, }; static uint32_t bt_channel_from_name(const char *name) { size_t i; enum spa_audio_channel position = spa_type_audio_channel_from_short_name(name); for (i = 0; i < SPA_N_ELEMENTS(audio_locations); i++) { if (position == audio_locations[i].position) return audio_locations[i].mask; } return 0; } static uint32_t parse_locations(const char *str) { char *s, *p, *save = NULL; uint32_t location = 0; if (!str) return 0; s = strdup(str); if (s == NULL) return 0; for (p = s; (p = strtok_r(p, ", ", &save)) != NULL; p = NULL) { if (*p == '\0') continue; location |= bt_channel_from_name(p); } free(s); return location; } static void parse_settings(struct props *props, const struct spa_dict *settings) { const char *str; uint32_t v; /* Pro Audio settings */ spa_zero(*props); props->channels = 8; props->coupled_streams = 0; props->location = 0; props->max_bitrate = BITRATE_MAX; props->frame_duration = OPUS_05_FRAME_DURATION_100; props->application = OPUS_APPLICATION_AUDIO; props->bidi_channels = 1; props->bidi_coupled_streams = 0; props->bidi_location = 0; props->bidi_max_bitrate = BITRATE_DUPLEX_BIDI; props->bidi_frame_duration = OPUS_05_FRAME_DURATION_400; props->bidi_application = OPUS_APPLICATION_AUDIO; if (settings == NULL) return; if (spa_atou32(spa_dict_lookup(settings, "bluez5.a2dp.opus.pro.channels"), &v, 0)) props->channels = SPA_CLAMP(v, 1u, MAX_CHANNELS); if (spa_atou32(spa_dict_lookup(settings, "bluez5.a2dp.opus.pro.max-bitrate"), &v, 0)) props->max_bitrate = SPA_MAX(v, (uint32_t)BITRATE_MIN); if (spa_atou32(spa_dict_lookup(settings, "bluez5.a2dp.opus.pro.coupled-streams"), &v, 0)) props->coupled_streams = SPA_CLAMP(v, 0u, props->channels / 2); if (spa_atou32(spa_dict_lookup(settings, "bluez5.a2dp.opus.pro.bidi.channels"), &v, 0)) props->bidi_channels = SPA_CLAMP(v, 0u, MAX_CHANNELS); if (spa_atou32(spa_dict_lookup(settings, "bluez5.a2dp.opus.pro.bidi.max-bitrate"), &v, 0)) props->bidi_max_bitrate = SPA_MAX(v, (uint32_t)BITRATE_MIN); if (spa_atou32(spa_dict_lookup(settings, "bluez5.a2dp.opus.pro.bidi.coupled-streams"), &v, 0)) props->bidi_coupled_streams = SPA_CLAMP(v, 0u, props->bidi_channels / 2); str = spa_dict_lookup(settings, "bluez5.a2dp.opus.pro.locations"); props->location = parse_locations(str); str = spa_dict_lookup(settings, "bluez5.a2dp.opus.pro.bidi.locations"); props->bidi_location = parse_locations(str); str = spa_dict_lookup(settings, "bluez5.a2dp.opus.pro.frame-dms"); if (spa_streq(str, "25")) props->frame_duration = OPUS_05_FRAME_DURATION_25; else if (spa_streq(str, "50")) props->frame_duration = OPUS_05_FRAME_DURATION_50; else if (spa_streq(str, "100")) props->frame_duration = OPUS_05_FRAME_DURATION_100; else if (spa_streq(str, "200")) props->frame_duration = OPUS_05_FRAME_DURATION_200; else if (spa_streq(str, "400")) props->frame_duration = OPUS_05_FRAME_DURATION_400; str = spa_dict_lookup(settings, "bluez5.a2dp.opus.pro.bidi.frame-dms"); if (spa_streq(str, "25")) props->bidi_frame_duration = OPUS_05_FRAME_DURATION_25; else if (spa_streq(str, "50")) props->bidi_frame_duration = OPUS_05_FRAME_DURATION_50; else if (spa_streq(str, "100")) props->bidi_frame_duration = OPUS_05_FRAME_DURATION_100; else if (spa_streq(str, "200")) props->bidi_frame_duration = OPUS_05_FRAME_DURATION_200; else if (spa_streq(str, "400")) props->bidi_frame_duration = OPUS_05_FRAME_DURATION_400; str = spa_dict_lookup(settings, "bluez5.a2dp.opus.pro.application"); if (spa_streq(str, "audio")) props->application = OPUS_APPLICATION_AUDIO; else if (spa_streq(str, "voip")) props->application = OPUS_APPLICATION_VOIP; else if (spa_streq(str, "lowdelay")) props->application = OPUS_APPLICATION_RESTRICTED_LOWDELAY; str = spa_dict_lookup(settings, "bluez5.a2dp.opus.pro.bidi.application"); if (spa_streq(str, "audio")) props->bidi_application = OPUS_APPLICATION_AUDIO; else if (spa_streq(str, "voip")) props->bidi_application = OPUS_APPLICATION_VOIP; else if (spa_streq(str, "lowdelay")) props->bidi_application = OPUS_APPLICATION_RESTRICTED_LOWDELAY; } static int set_channel_conf(const struct media_codec *codec, a2dp_opus_05_t *caps, const struct props *props) { /* * Predefined codec profiles */ if (caps->main.channels < 1) return -EINVAL; caps->main.coupled_streams = 0; OPUS_05_SET_LOCATION(caps->main, 0); caps->bidi.coupled_streams = 0; OPUS_05_SET_LOCATION(caps->bidi, 0); switch (codec->id) { case SPA_BLUETOOTH_AUDIO_CODEC_OPUS_05: caps->main.channels = SPA_MIN(2, caps->main.channels); if (caps->main.channels == 2) { caps->main.coupled_streams = surround_encoders[1].coupled_streams; OPUS_05_SET_LOCATION(caps->main, surround_encoders[1].location); } caps->bidi.channels = 0; break; case SPA_BLUETOOTH_AUDIO_CODEC_OPUS_05_51: if (caps->main.channels < 6) return -EINVAL; caps->main.channels = surround_encoders[5].channels; caps->main.coupled_streams = surround_encoders[5].coupled_streams; OPUS_05_SET_LOCATION(caps->main, surround_encoders[5].location); caps->bidi.channels = 0; break; case SPA_BLUETOOTH_AUDIO_CODEC_OPUS_05_71: if (caps->main.channels < 8) return -EINVAL; caps->main.channels = surround_encoders[7].channels; caps->main.coupled_streams = surround_encoders[7].coupled_streams; OPUS_05_SET_LOCATION(caps->main, surround_encoders[7].location); caps->bidi.channels = 0; break; case SPA_BLUETOOTH_AUDIO_CODEC_OPUS_05_DUPLEX: if (caps->bidi.channels < 1) return -EINVAL; caps->main.channels = SPA_MIN(2, caps->main.channels); if (caps->main.channels == 2) { caps->main.coupled_streams = surround_encoders[1].coupled_streams; OPUS_05_SET_LOCATION(caps->main, surround_encoders[1].location); } caps->bidi.channels = SPA_MIN(2, caps->bidi.channels); if (caps->bidi.channels == 2) { caps->bidi.coupled_streams = surround_encoders[1].coupled_streams; OPUS_05_SET_LOCATION(caps->bidi, surround_encoders[1].location); } break; case SPA_BLUETOOTH_AUDIO_CODEC_OPUS_05_PRO: if (caps->main.channels < props->channels) return -EINVAL; if (props->bidi_channels == 0 && caps->bidi.channels != 0) return -EINVAL; if (caps->bidi.channels < props->bidi_channels) return -EINVAL; caps->main.channels = props->channels; caps->main.coupled_streams = props->coupled_streams; OPUS_05_SET_LOCATION(caps->main, props->location); caps->bidi.channels = props->bidi_channels; caps->bidi.coupled_streams = props->bidi_coupled_streams; OPUS_05_SET_LOCATION(caps->bidi, props->bidi_location); break; default: spa_assert_not_reached(); }; return 0; } static void get_default_bitrates(const struct media_codec *codec, bool bidi, int *min, int *max, int *init) { int tmp; if (min == NULL) min = &tmp; if (max == NULL) max = &tmp; if (init == NULL) init = &tmp; if (bidi) { *min = SPA_MIN(BITRATE_MIN, BITRATE_DUPLEX_BIDI); *max = BITRATE_DUPLEX_BIDI; *init = BITRATE_DUPLEX_BIDI; return; } switch (codec->id) { case SPA_BLUETOOTH_AUDIO_CODEC_OPUS_05: *min = BITRATE_MIN; *max = BITRATE_MAX; *init = BITRATE_INITIAL; break; case SPA_BLUETOOTH_AUDIO_CODEC_OPUS_05_51: *min = BITRATE_MIN_51; *max = BITRATE_MAX_51; *init = BITRATE_INITIAL_51; break; case SPA_BLUETOOTH_AUDIO_CODEC_OPUS_05_71: *min = BITRATE_MIN_71; *max = BITRATE_MAX_71; *init = BITRATE_INITIAL_71; break; case SPA_BLUETOOTH_AUDIO_CODEC_OPUS_05_DUPLEX: *min = BITRATE_MIN; *max = BITRATE_MAX; *init = BITRATE_INITIAL; break; case SPA_BLUETOOTH_AUDIO_CODEC_OPUS_05_PRO: default: spa_assert_not_reached(); }; } static int get_mapping(const struct media_codec *codec, const a2dp_opus_05_direction_t *conf, bool use_surround_encoder, uint8_t *streams_ret, uint8_t *coupled_streams_ret, const uint8_t **surround_mapping, uint32_t *positions) { const uint32_t channels = conf->channels; const uint32_t location = OPUS_05_GET_LOCATION(*conf); const uint8_t coupled_streams = conf->coupled_streams; const uint8_t *permutation = NULL; size_t i, j; if (channels > MAX_CHANNELS) return -EINVAL; if (2 * coupled_streams > channels) return -EINVAL; if (streams_ret) *streams_ret = channels - coupled_streams; if (coupled_streams_ret) *coupled_streams_ret = coupled_streams; if (channels == 0) return 0; if (use_surround_encoder) { /* Opus surround encoder supports only some channel configurations, and * needs a specific input channel ordering */ for (i = 0; i < SPA_N_ELEMENTS(surround_encoders); ++i) { const struct surround_encoder_mapping *m = &surround_encoders[i]; if (m->channels == channels && m->coupled_streams == coupled_streams && m->location == location) { spa_assert(channels <= SPA_N_ELEMENTS(m->inv_mapping)); permutation = m->inv_mapping; if (surround_mapping) *surround_mapping = m->mapping; break; } } if (permutation == NULL && surround_mapping) *surround_mapping = NULL; } if (positions) { for (i = 0, j = 0; i < SPA_N_ELEMENTS(audio_locations) && j < channels; ++i) { const struct audio_location loc = audio_locations[i]; if (location & loc.mask) { uint32_t idx = permutation ? permutation[j] : j; positions[idx] = loc.position; j++; } } for (i = SPA_AUDIO_CHANNEL_START_Aux; j < channels; ++i, ++j) positions[j] = i; } return 0; } static int codec_fill_caps(const struct media_codec *codec, uint32_t flags, const struct spa_dict *settings, uint8_t caps[A2DP_MAX_CAPS_SIZE]) { a2dp_opus_05_t a2dp_opus_05 = { .info = codec->vendor, .main = { .channels = SPA_MIN(255u, MAX_CHANNELS), .frame_duration = (OPUS_05_FRAME_DURATION_25 | OPUS_05_FRAME_DURATION_50 | OPUS_05_FRAME_DURATION_100 | OPUS_05_FRAME_DURATION_200 | OPUS_05_FRAME_DURATION_400), OPUS_05_INIT_LOCATION(BT_AUDIO_LOCATION_ANY) OPUS_05_INIT_BITRATE(0) }, .bidi = { .channels = SPA_MIN(255u, MAX_CHANNELS), .frame_duration = (OPUS_05_FRAME_DURATION_25 | OPUS_05_FRAME_DURATION_50 | OPUS_05_FRAME_DURATION_100 | OPUS_05_FRAME_DURATION_200 | OPUS_05_FRAME_DURATION_400), OPUS_05_INIT_LOCATION(BT_AUDIO_LOCATION_ANY) OPUS_05_INIT_BITRATE(0) } }; /* Only duplex/pro codec has bidi, since bluez5-device has to know early * whether to show nodes or not. */ if (codec->id != SPA_BLUETOOTH_AUDIO_CODEC_OPUS_05_DUPLEX && codec->id != SPA_BLUETOOTH_AUDIO_CODEC_OPUS_05_PRO) spa_zero(a2dp_opus_05.bidi); memcpy(caps, &a2dp_opus_05, sizeof(a2dp_opus_05)); return sizeof(a2dp_opus_05); } static int codec_select_config(const struct media_codec *codec, uint32_t flags, const void *caps, size_t caps_size, const struct media_codec_audio_info *info, const struct spa_dict *global_settings, uint8_t config[A2DP_MAX_CAPS_SIZE], void **config_data) { struct props props; a2dp_opus_05_t conf; int res; int max; if (caps_size < sizeof(conf)) return -EINVAL; memcpy(&conf, caps, sizeof(conf)); if (codec->vendor.vendor_id != conf.info.vendor_id || codec->vendor.codec_id != conf.info.codec_id) return -ENOTSUP; parse_settings(&props, global_settings); /* Channel Configuration & Audio Location */ if ((res = set_channel_conf(codec, &conf, &props)) < 0) return res; /* Limits */ if (codec->id == SPA_BLUETOOTH_AUDIO_CODEC_OPUS_05_PRO) { max = props.max_bitrate; if (OPUS_05_GET_BITRATE(conf.main) != 0) OPUS_05_SET_BITRATE(conf.main, SPA_MIN(OPUS_05_GET_BITRATE(conf.main), max / 1024)); else OPUS_05_SET_BITRATE(conf.main, max / 1024); max = props.bidi_max_bitrate; if (OPUS_05_GET_BITRATE(conf.bidi) != 0) OPUS_05_SET_BITRATE(conf.bidi, SPA_MIN(OPUS_05_GET_BITRATE(conf.bidi), max / 1024)); else OPUS_05_SET_BITRATE(conf.bidi, max / 1024); if (conf.main.frame_duration & props.frame_duration) conf.main.frame_duration = props.frame_duration; else return -EINVAL; if (conf.bidi.channels == 0) true; else if (conf.bidi.frame_duration & props.bidi_frame_duration) conf.bidi.frame_duration = props.bidi_frame_duration; else return -EINVAL; } else { if (conf.main.frame_duration & OPUS_05_FRAME_DURATION_100) conf.main.frame_duration = OPUS_05_FRAME_DURATION_100; else if (conf.main.frame_duration & OPUS_05_FRAME_DURATION_200) conf.main.frame_duration = OPUS_05_FRAME_DURATION_200; else if (conf.main.frame_duration & OPUS_05_FRAME_DURATION_400) conf.main.frame_duration = OPUS_05_FRAME_DURATION_400; else if (conf.main.frame_duration & OPUS_05_FRAME_DURATION_50) conf.main.frame_duration = OPUS_05_FRAME_DURATION_50; else if (conf.main.frame_duration & OPUS_05_FRAME_DURATION_25) conf.main.frame_duration = OPUS_05_FRAME_DURATION_25; else return -EINVAL; get_default_bitrates(codec, false, NULL, &max, NULL); if (OPUS_05_GET_BITRATE(conf.main) != 0) OPUS_05_SET_BITRATE(conf.main, SPA_MIN(OPUS_05_GET_BITRATE(conf.main), max / 1024)); else OPUS_05_SET_BITRATE(conf.main, max / 1024); /* longer bidi frames appear to work better */ if (conf.bidi.channels == 0) true; else if (conf.bidi.frame_duration & OPUS_05_FRAME_DURATION_200) conf.bidi.frame_duration = OPUS_05_FRAME_DURATION_200; else if (conf.bidi.frame_duration & OPUS_05_FRAME_DURATION_100) conf.bidi.frame_duration = OPUS_05_FRAME_DURATION_100; else if (conf.bidi.frame_duration & OPUS_05_FRAME_DURATION_400) conf.bidi.frame_duration = OPUS_05_FRAME_DURATION_400; else if (conf.bidi.frame_duration & OPUS_05_FRAME_DURATION_50) conf.bidi.frame_duration = OPUS_05_FRAME_DURATION_50; else if (conf.bidi.frame_duration & OPUS_05_FRAME_DURATION_25) conf.bidi.frame_duration = OPUS_05_FRAME_DURATION_25; else return -EINVAL; get_default_bitrates(codec, true, NULL, &max, NULL); if (conf.bidi.channels == 0) true; else if (OPUS_05_GET_BITRATE(conf.bidi) != 0) OPUS_05_SET_BITRATE(conf.bidi, SPA_MIN(OPUS_05_GET_BITRATE(conf.bidi), max / 1024)); else OPUS_05_SET_BITRATE(conf.bidi, max / 1024); } memcpy(config, &conf, sizeof(conf)); return sizeof(conf); } static int codec_caps_preference_cmp(const struct media_codec *codec, uint32_t flags, const void *caps1, size_t caps1_size, const void *caps2, size_t caps2_size, const struct media_codec_audio_info *info, const struct spa_dict *global_settings) { a2dp_opus_05_t conf1, conf2, cap1, cap2; a2dp_opus_05_t *conf; int res1, res2; int a, b; /* Order selected configurations by preference */ res1 = codec->select_config(codec, flags, caps1, caps1_size, info, global_settings, (uint8_t *)&conf1, NULL); res2 = codec->select_config(codec, flags, caps2, caps2_size, info, global_settings, (uint8_t *)&conf2, NULL); #define PREFER_EXPR(expr) \ do { \ conf = &conf1; \ a = (expr); \ conf = &conf2; \ b = (expr); \ if (a != b) \ return b - a; \ } while (0) #define PREFER_BOOL(expr) PREFER_EXPR((expr) ? 1 : 0) /* Prefer valid */ a = (res1 > 0 && (size_t)res1 == sizeof(a2dp_opus_05_t)) ? 1 : 0; b = (res2 > 0 && (size_t)res2 == sizeof(a2dp_opus_05_t)) ? 1 : 0; if (!a || !b) return b - a; memcpy(&cap1, caps1, sizeof(cap1)); memcpy(&cap2, caps2, sizeof(cap2)); if (conf1.bidi.channels == 0 && conf2.bidi.channels == 0) { /* If no bidi, prefer the SEP that has none */ a = (cap1.bidi.channels == 0); b = (cap2.bidi.channels == 0); if (a != b) return b - a; } PREFER_EXPR(conf->main.channels); PREFER_EXPR(conf->bidi.channels); PREFER_EXPR(OPUS_05_GET_BITRATE(conf->main)); PREFER_EXPR(OPUS_05_GET_BITRATE(conf->bidi)); return 0; #undef PREFER_EXPR #undef PREFER_BOOL } static bool is_duplex_codec(const struct media_codec *codec) { return codec->id == 0; } static bool use_surround_encoder(const struct media_codec *codec, bool is_sink) { if (codec->id == SPA_BLUETOOTH_AUDIO_CODEC_OPUS_05_PRO) return false; if (is_duplex_codec(codec)) return is_sink; else return !is_sink; } static int codec_enum_config(const struct media_codec *codec, uint32_t flags, const void *caps, size_t caps_size, uint32_t id, uint32_t idx, struct spa_pod_builder *b, struct spa_pod **param) { const bool surround_encoder = use_surround_encoder(codec, flags & MEDIA_CODEC_FLAG_SINK); a2dp_opus_05_t conf; a2dp_opus_05_direction_t *dir; struct spa_pod_frame f[1]; uint32_t position[MAX_CHANNELS]; if (caps_size < sizeof(conf)) return -EINVAL; memcpy(&conf, caps, sizeof(conf)); if (idx > 0) return 0; dir = !is_duplex_codec(codec) ? &conf.main : &conf.bidi; if (get_mapping(codec, dir, surround_encoder, NULL, NULL, NULL, position) < 0) return -EINVAL; spa_pod_builder_push_object(b, &f[0], SPA_TYPE_OBJECT_Format, id); spa_pod_builder_add(b, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_audio), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), SPA_FORMAT_AUDIO_format, SPA_POD_Id(SPA_AUDIO_FORMAT_F32), SPA_FORMAT_AUDIO_rate, SPA_POD_CHOICE_ENUM_Int(6, 48000, 48000, 24000, 16000, 12000, 8000), SPA_FORMAT_AUDIO_channels, SPA_POD_Int(dir->channels), SPA_FORMAT_AUDIO_position, SPA_POD_Array(sizeof(uint32_t), SPA_TYPE_Id, dir->channels, position), 0); *param = spa_pod_builder_pop(b, &f[0]); return *param == NULL ? -EIO : 1; } static int codec_validate_config(const struct media_codec *codec, uint32_t flags, const void *caps, size_t caps_size, struct spa_audio_info *info) { const bool surround_encoder = use_surround_encoder(codec, flags & MEDIA_CODEC_FLAG_SINK); const a2dp_opus_05_direction_t *dir1, *dir2; const a2dp_opus_05_t *conf; if (caps == NULL || caps_size < sizeof(*conf)) return -EINVAL; conf = caps; spa_zero(*info); info->media_type = SPA_MEDIA_TYPE_audio; info->media_subtype = SPA_MEDIA_SUBTYPE_raw; info->info.raw.format = SPA_AUDIO_FORMAT_F32; info->info.raw.rate = 0; /* not specified by config */ if (2 * conf->main.coupled_streams > conf->main.channels) return -EINVAL; if (2 * conf->bidi.coupled_streams > conf->bidi.channels) return -EINVAL; if (!is_duplex_codec(codec)) { dir1 = &conf->main; dir2 = &conf->bidi; } else { dir1 = &conf->bidi; dir2 = &conf->main; } info->info.raw.channels = dir1->channels; if (get_mapping(codec, dir1, surround_encoder, NULL, NULL, NULL, info->info.raw.position) < 0) return -EINVAL; if (get_mapping(codec, dir2, surround_encoder, NULL, NULL, NULL, NULL) < 0) return -EINVAL; return 0; } static size_t ceildiv(size_t v, size_t divisor) { if (v % divisor == 0) return v / divisor; else return v / divisor + 1; } static bool check_bitrate_vs_frame_dms(struct impl *this, size_t bitrate) { size_t header_size = sizeof(struct rtp_header) + sizeof(struct rtp_payload); size_t max_fragments = 0xf; size_t payload_size = BUFSIZE_FROM_BITRATE(bitrate, this->e.frame_dms); return (size_t)this->mtu >= header_size + ceildiv(payload_size, max_fragments); } static int parse_frame_dms(int bitfield) { switch (bitfield) { case OPUS_05_FRAME_DURATION_25: return 25; case OPUS_05_FRAME_DURATION_50: return 50; case OPUS_05_FRAME_DURATION_100: return 100; case OPUS_05_FRAME_DURATION_200: return 200; case OPUS_05_FRAME_DURATION_400: return 400; default: return -EINVAL; } } static void *codec_init_props(const struct media_codec *codec, uint32_t flags, const struct spa_dict *settings) { struct props *p; if (codec->id != SPA_BLUETOOTH_AUDIO_CODEC_OPUS_05_PRO) return NULL; p = calloc(1, sizeof(struct props)); if (p == NULL) return NULL; parse_settings(p, settings); return p; } static void codec_clear_props(void *props) { free(props); } static void *codec_init(const struct media_codec *codec, uint32_t flags, void *config, size_t config_len, const struct spa_audio_info *info, void *props, size_t mtu) { const bool surround_encoder = use_surround_encoder(codec, flags & MEDIA_CODEC_FLAG_SINK); a2dp_opus_05_t *conf = config; a2dp_opus_05_direction_t *dir; struct impl *this = NULL; struct spa_audio_info config_info; const uint8_t *enc_mapping = NULL; unsigned char mapping[256]; size_t i; int res; if (info->media_type != SPA_MEDIA_TYPE_audio || info->media_subtype != SPA_MEDIA_SUBTYPE_raw || info->info.raw.format != SPA_AUDIO_FORMAT_F32) { res = -EINVAL; goto error; } if ((this = calloc(1, sizeof(struct impl))) == NULL) goto error_errno; this->is_bidi = is_duplex_codec(codec); dir = !this->is_bidi ? &conf->main : &conf->bidi; if ((res = codec_validate_config(codec, flags, config, config_len, &config_info)) < 0) goto error; if ((res = get_mapping(codec, dir, surround_encoder, &this->streams, &this->coupled_streams, &enc_mapping, NULL)) < 0) goto error; if (config_info.info.raw.channels != info->info.raw.channels) { res = -EINVAL; goto error; } this->mtu = mtu; this->samplerate = info->info.raw.rate; this->channels = config_info.info.raw.channels; this->application = OPUS_APPLICATION_AUDIO; if (codec->id == SPA_BLUETOOTH_AUDIO_CODEC_OPUS_05_PRO && props) { struct props *p = props; this->application = !this->is_bidi ? p->application : p->bidi_application; } /* * Setup encoder */ if (enc_mapping) { int streams, coupled_streams; bool incompatible_opus_surround_encoder = false; this->enc = opus_multistream_surround_encoder_create( this->samplerate, this->channels, 1, &streams, &coupled_streams, mapping, this->application, &res); if (this->enc) { /* Check surround encoder channel mapping is what we want */ if (streams != this->streams || coupled_streams != this->coupled_streams) incompatible_opus_surround_encoder = true; for (i = 0; i < this->channels; ++i) if (enc_mapping[i] != mapping[i]) incompatible_opus_surround_encoder = true; } /* Assert: this should never happen */ spa_assert(!incompatible_opus_surround_encoder); if (incompatible_opus_surround_encoder) { res = -EINVAL; goto error; } } else { for (i = 0; i < this->channels; ++i) mapping[i] = i; this->enc = opus_multistream_encoder_create( this->samplerate, this->channels, this->streams, this->coupled_streams, mapping, this->application, &res); } if (this->enc == NULL) { res = -EINVAL; goto error; } if ((this->e.frame_dms = parse_frame_dms(dir->frame_duration)) < 0) { res = -EINVAL; goto error; } if (codec->id != SPA_BLUETOOTH_AUDIO_CODEC_OPUS_05_PRO) { get_default_bitrates(codec, this->is_bidi, &this->e.bitrate_min, &this->e.bitrate_max, &this->e.bitrate); this->e.bitrate_max = SPA_MIN(this->e.bitrate_max, OPUS_05_GET_BITRATE(*dir) * 1024); } else { this->e.bitrate_max = OPUS_05_GET_BITRATE(*dir) * 1024; this->e.bitrate_min = BITRATE_MIN; this->e.bitrate = BITRATE_INITIAL; } this->e.bitrate_min = SPA_MIN(this->e.bitrate_min, this->e.bitrate_max); this->e.bitrate = SPA_CLAMP(this->e.bitrate, this->e.bitrate_min, this->e.bitrate_max); this->e.next_bitrate = this->e.bitrate; opus_multistream_encoder_ctl(this->enc, OPUS_SET_BITRATE(this->e.bitrate)); this->e.samples = this->e.frame_dms * this->samplerate / 10000; this->e.codesize = this->e.samples * (int)this->channels * sizeof(float); opus_multistream_encoder_ctl(this->enc, OPUS_GET_LOOKAHEAD(&this->e.delay)); /* * Setup decoder */ for (i = 0; i < this->channels; ++i) mapping[i] = i; this->dec = opus_multistream_decoder_create( this->samplerate, this->channels, this->streams, this->coupled_streams, mapping, &res); if (this->dec == NULL) { res = -EINVAL; goto error; } opus_multistream_decoder_ctl(this->dec, OPUS_GET_LOOKAHEAD(&this->d.delay)); return this; error_errno: res = -errno; goto error; error: if (this && this->enc) opus_multistream_encoder_destroy(this->enc); if (this && this->dec) opus_multistream_decoder_destroy(this->dec); free(this); errno = -res; return NULL; } static void codec_deinit(void *data) { struct impl *this = data; opus_multistream_encoder_destroy(this->enc); opus_multistream_decoder_destroy(this->dec); free(this); } static int codec_get_block_size(void *data) { struct impl *this = data; return this->e.codesize; } static int codec_update_bitrate(struct impl *this) { this->e.next_bitrate = SPA_CLAMP(this->e.next_bitrate, this->e.bitrate_min, this->e.bitrate_max); if (!check_bitrate_vs_frame_dms(this, this->e.next_bitrate)) { this->e.next_bitrate = this->e.bitrate; return 0; } this->e.bitrate = this->e.next_bitrate; opus_multistream_encoder_ctl(this->enc, OPUS_SET_BITRATE(this->e.bitrate)); return 0; } static int codec_start_encode (void *data, void *dst, size_t dst_size, uint16_t seqnum, uint32_t timestamp) { struct impl *this = data; size_t header_size = sizeof(struct rtp_header) + sizeof(struct rtp_payload); if (dst_size <= header_size) return -EINVAL; codec_update_bitrate(this); this->e.header = (struct rtp_header *)dst; this->e.payload = SPA_PTROFF(dst, sizeof(struct rtp_header), struct rtp_payload); memset(dst, 0, header_size); this->e.payload->frame_count = 0; this->e.header->v = 2; this->e.header->pt = 96; this->e.header->sequence_number = htons(seqnum); this->e.header->timestamp = htonl(timestamp); this->e.header->ssrc = htonl(1); this->e.packet_size = header_size; return this->e.packet_size; } static int codec_encode(void *data, const void *src, size_t src_size, void *dst, size_t dst_size, size_t *dst_out, int *need_flush) { struct impl *this = data; const int header_size = sizeof(struct rtp_header) + sizeof(struct rtp_payload); int size; int res; if (src == NULL) { /* Produce fragment packets. * * We assume the caller gives the same buffer here as in the previous * calls to encode(), without changes in the buffer content. */ if (this->e.fragment == NULL || this->e.fragment_count <= 1 || this->e.fragment < dst || SPA_PTROFF(this->e.fragment, this->e.fragment_size, void) > SPA_PTROFF(dst, dst_size, void)) { this->e.fragment = NULL; return -EINVAL; } size = SPA_MIN(this->mtu - header_size, this->e.fragment_size); memmove(dst, this->e.fragment, size); *dst_out = size; this->e.payload->is_fragmented = 1; this->e.payload->frame_count = --this->e.fragment_count; this->e.payload->is_last_fragment = (this->e.fragment_count == 1); if (this->e.fragment_size > size && this->e.fragment_count > 1) { this->e.fragment = SPA_PTROFF(this->e.fragment, size, void); this->e.fragment_size -= size; *need_flush = NEED_FLUSH_FRAGMENT; } else { this->e.fragment = NULL; *need_flush = NEED_FLUSH_ALL; } return 0; } if (src_size < (size_t)this->e.codesize) { *dst_out = 0; return 0; } res = opus_multistream_encode_float( this->enc, src, this->e.samples, dst, dst_size); if (res < 0) return -EINVAL; *dst_out = res; this->e.packet_size += res; this->e.payload->frame_count++; if (this->e.packet_size > this->mtu) { /* Fragment packet */ this->e.fragment_count = ceildiv(this->e.packet_size - header_size, this->mtu - header_size); this->e.payload->is_fragmented = 1; this->e.payload->is_first_fragment = 1; this->e.payload->frame_count = this->e.fragment_count; this->e.fragment_size = this->e.packet_size - this->mtu; this->e.fragment = SPA_PTROFF(dst, *dst_out - this->e.fragment_size, void); *need_flush = NEED_FLUSH_FRAGMENT; /* * We keep the rest of the encoded frame in the same buffer, and rely * that the caller won't overwrite it before the next call to encode() */ *dst_out = SPA_PTRDIFF(this->e.fragment, dst); } else { *need_flush = NEED_FLUSH_ALL; } return this->e.codesize; } static SPA_UNUSED int codec_start_decode (void *data, const void *src, size_t src_size, uint16_t *seqnum, uint32_t *timestamp) { struct impl *this = data; const struct rtp_header *header = src; const struct rtp_payload *payload = SPA_PTROFF(src, sizeof(struct rtp_header), void); size_t header_size = sizeof(struct rtp_header) + sizeof(struct rtp_payload); if (src_size <= header_size) return -EINVAL; if (seqnum) *seqnum = ntohs(header->sequence_number); if (timestamp) *timestamp = ntohl(header->timestamp); if (payload->is_fragmented) { if (payload->is_first_fragment) { this->d.fragment_size = 0; } else if (payload->frame_count + 1 != this->d.fragment_count || (payload->frame_count == 1 && !payload->is_last_fragment)){ /* Fragments not in right order: drop packet */ return -EINVAL; } this->d.fragment_count = payload->frame_count; } else { if (payload->frame_count != 1) return -EINVAL; this->d.fragment_count = 0; } return header_size; } static SPA_UNUSED int codec_decode(void *data, const void *src, size_t src_size, void *dst, size_t dst_size, size_t *dst_out) { struct impl *this = data; int consumed = src_size; int res; int dst_samples; if (this->d.fragment_count > 0) { /* Fragmented frame */ size_t avail; avail = SPA_MIN(sizeof(this->d.fragment) - this->d.fragment_size, src_size); memcpy(SPA_PTROFF(this->d.fragment, this->d.fragment_size, void), src, avail); this->d.fragment_size += avail; if (this->d.fragment_count > 1) { /* More fragments to come */ *dst_out = 0; return consumed; } src = this->d.fragment; src_size = this->d.fragment_size; this->d.fragment_count = 0; this->d.fragment_size = 0; } dst_samples = dst_size / (sizeof(float) * this->channels); res = opus_multistream_decode_float(this->dec, src, src_size, dst, dst_samples, 0); if (res < 0) return -EINVAL; *dst_out = (size_t)res * this->channels * sizeof(float); return consumed; } static int codec_abr_process(void *data, size_t unsent) { const uint64_t interval = SPA_NSEC_PER_SEC; struct impl *this = data; struct abr *abr = &this->e.abr; bool level_bad, level_good; uint32_t actual_bitrate; abr->total_size += this->e.packet_size; if (this->e.payload->is_fragmented && !this->e.payload->is_first_fragment) return 0; abr->now += this->e.frame_dms * SPA_NSEC_PER_MSEC / 10; abr->buffer_level = SPA_MAX(abr->buffer_level, unsent); abr->packet_size = SPA_MAX(abr->packet_size, (uint32_t)this->e.packet_size); abr->packet_size = SPA_MAX(abr->packet_size, 128u); level_bad = abr->buffer_level > 2 * (uint32_t)this->mtu || abr->bad; level_good = abr->buffer_level == 0; if (!(abr->last_update + interval <= abr->now || (level_bad && abr->last_change + interval <= abr->now))) return 0; actual_bitrate = (uint64_t)abr->total_size*8*SPA_NSEC_PER_SEC / SPA_MAX(1u, abr->now - abr->last_update); spa_log_debug(log, "opus ABR bitrate:%d actual:%d level:%d (%s) bad:%d retry:%ds size:%d", (int)this->e.bitrate, (int)actual_bitrate, (int)abr->buffer_level, level_bad ? "bad" : (level_good ? "good" : "-"), (int)abr->bad, (int)(abr->retry_interval / SPA_NSEC_PER_SEC), (int)abr->packet_size); if (level_bad) { this->e.next_bitrate = this->e.bitrate * 11 / 12; abr->last_change = abr->now; abr->retry_interval = SPA_MIN(abr->retry_interval + 10*interval, 30 * interval); } else if (!level_good) { abr->last_change = abr->now; } else if (abr->now < abr->last_change + abr->retry_interval) { /* noop */ } else if (actual_bitrate*3/2 < (uint32_t)this->e.bitrate) { /* actual bitrate is small compared to target; probably silence */ } else { this->e.next_bitrate = this->e.bitrate + SPA_MAX(1, this->e.bitrate_max / 40); abr->last_change = abr->now; abr->retry_interval = SPA_MAX(abr->retry_interval, (5+4)*interval) - 4*interval; } abr->last_update = abr->now; abr->buffer_level = 0; abr->bad = false; abr->packet_size = 0; abr->total_size = 0; return 0; } static int codec_reduce_bitpool(void *data) { struct impl *this = data; struct abr *abr = &this->e.abr; abr->bad = true; return 0; } static int codec_increase_bitpool(void *data) { return 0; } static void codec_get_delay(void *data, uint32_t *encoder, uint32_t *decoder) { struct impl *this = data; if (encoder) *encoder = this->e.delay; if (decoder) *decoder = this->d.delay; } static void codec_set_log(struct spa_log *global_log) { log = global_log; spa_log_topic_init(log, &codec_plugin_log_topic); } #define OPUS_05_COMMON_DEFS \ .kind = MEDIA_CODEC_A2DP, \ .codec_id = A2DP_CODEC_VENDOR, \ .vendor = { .vendor_id = OPUS_05_VENDOR_ID, \ .codec_id = OPUS_05_CODEC_ID }, \ .select_config = codec_select_config, \ .enum_config = codec_enum_config, \ .validate_config = codec_validate_config, \ .caps_preference_cmp = codec_caps_preference_cmp, \ .init = codec_init, \ .deinit = codec_deinit, \ .get_block_size = codec_get_block_size, \ .abr_process = codec_abr_process, \ .start_encode = codec_start_encode, \ .encode = codec_encode, \ .reduce_bitpool = codec_reduce_bitpool, \ .increase_bitpool = codec_increase_bitpool, \ .set_log = codec_set_log, \ .get_delay = codec_get_delay #define OPUS_05_COMMON_FULL_DEFS \ OPUS_05_COMMON_DEFS, \ .start_decode = codec_start_decode, \ .decode = codec_decode const struct media_codec a2dp_codec_opus_05 = { OPUS_05_COMMON_FULL_DEFS, .id = SPA_BLUETOOTH_AUDIO_CODEC_OPUS_05, .name = "opus_05", .description = "Opus 05", .fill_caps = codec_fill_caps, }; const struct media_codec a2dp_codec_opus_05_51 = { OPUS_05_COMMON_DEFS, .id = SPA_BLUETOOTH_AUDIO_CODEC_OPUS_05_51, .name = "opus_05_51", .description = "Opus 05 5.1 Surround", .endpoint_name = "opus_05", .fill_caps = NULL, }; const struct media_codec a2dp_codec_opus_05_71 = { OPUS_05_COMMON_DEFS, .id = SPA_BLUETOOTH_AUDIO_CODEC_OPUS_05_71, .name = "opus_05_71", .description = "Opus 05 7.1 Surround", .endpoint_name = "opus_05", .fill_caps = NULL, }; /* Bidi return channel codec: doesn't have endpoints */ const struct media_codec a2dp_codec_opus_05_return = { OPUS_05_COMMON_FULL_DEFS, .id = 0, .name = "opus_05_duplex_bidi", .description = "Opus 05 Duplex Bidi channel", }; const struct media_codec a2dp_codec_opus_05_duplex = { OPUS_05_COMMON_FULL_DEFS, .id = SPA_BLUETOOTH_AUDIO_CODEC_OPUS_05_DUPLEX, .name = "opus_05_duplex", .description = "Opus 05 Duplex", .duplex_codec = &a2dp_codec_opus_05_return, .fill_caps = codec_fill_caps, }; const struct media_codec a2dp_codec_opus_05_pro = { OPUS_05_COMMON_DEFS, .id = SPA_BLUETOOTH_AUDIO_CODEC_OPUS_05_PRO, .name = "opus_05_pro", .description = "Opus 05 Pro Audio", .init_props = codec_init_props, .clear_props = codec_clear_props, .duplex_codec = &a2dp_codec_opus_05_return, .endpoint_name = "opus_05_duplex", .fill_caps = NULL, }; MEDIA_CODEC_EXPORT_DEF( "opus", &a2dp_codec_opus_05, &a2dp_codec_opus_05_51, &a2dp_codec_opus_05_71, &a2dp_codec_opus_05_duplex, &a2dp_codec_opus_05_pro ); pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/bluez5/a2dp-codec-sbc.c000066400000000000000000000424311511204443500267660ustar00rootroot00000000000000/* Spa A2DP SBC codec */ /* SPDX-FileCopyrightText: Copyright © 2020 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #include #include #include #include "rtp.h" #include "media-codecs.h" #define MAX_FRAME_COUNT 16 struct impl { sbc_t sbc; struct rtp_header *header; struct rtp_payload *payload; size_t mtu; int codesize; int max_frames; int min_bitpool; int max_bitpool; uint32_t enc_delay; }; static int codec_fill_caps(const struct media_codec *codec, uint32_t flags, const struct spa_dict *settings, uint8_t caps[A2DP_MAX_CAPS_SIZE]) { static const a2dp_sbc_t a2dp_sbc = { .frequency = SBC_SAMPLING_FREQ_16000 | SBC_SAMPLING_FREQ_32000 | SBC_SAMPLING_FREQ_44100 | SBC_SAMPLING_FREQ_48000, .channel_mode = SBC_CHANNEL_MODE_MONO | SBC_CHANNEL_MODE_DUAL_CHANNEL | SBC_CHANNEL_MODE_STEREO | SBC_CHANNEL_MODE_JOINT_STEREO, .block_length = SBC_BLOCK_LENGTH_4 | SBC_BLOCK_LENGTH_8 | SBC_BLOCK_LENGTH_12 | SBC_BLOCK_LENGTH_16, .subbands = SBC_SUBBANDS_4 | SBC_SUBBANDS_8, .allocation_method = SBC_ALLOCATION_SNR | SBC_ALLOCATION_LOUDNESS, .min_bitpool = SBC_MIN_BITPOOL, .max_bitpool = SBC_MAX_BITPOOL, }; memcpy(caps, &a2dp_sbc, sizeof(a2dp_sbc)); return sizeof(a2dp_sbc); } static uint8_t default_bitpool(uint8_t freq, uint8_t mode, bool xq) { /* A2DP spec v1.2 states that all SNK implementation shall handle bitrates * of up to 512 kbps (~ bitpool = 86 stereo, or 2x43 dual channel at 44.1KHz * or ~ bitpool = 78 stereo, or 2x39 dual channel at 48KHz). */ switch (freq) { case SBC_SAMPLING_FREQ_16000: case SBC_SAMPLING_FREQ_32000: return 64; case SBC_SAMPLING_FREQ_44100: switch (mode) { case SBC_CHANNEL_MODE_MONO: case SBC_CHANNEL_MODE_DUAL_CHANNEL: return xq ? 43 : 32; case SBC_CHANNEL_MODE_STEREO: case SBC_CHANNEL_MODE_JOINT_STEREO: return xq ? 86 : 64; } return xq ? 86 : 64; case SBC_SAMPLING_FREQ_48000: switch (mode) { case SBC_CHANNEL_MODE_MONO: case SBC_CHANNEL_MODE_DUAL_CHANNEL: return xq ? 39 : 29; case SBC_CHANNEL_MODE_STEREO: case SBC_CHANNEL_MODE_JOINT_STEREO: return xq ? 78 : 58; } return xq ? 78 : 58; } return xq ? 86 : 64; } static const struct media_codec_config sbc_frequencies[] = { { SBC_SAMPLING_FREQ_48000, 48000, 3 }, { SBC_SAMPLING_FREQ_44100, 44100, 2 }, { SBC_SAMPLING_FREQ_32000, 32000, 1 }, { SBC_SAMPLING_FREQ_16000, 16000, 0 }, }; static const struct media_codec_config sbc_xq_frequencies[] = { { SBC_SAMPLING_FREQ_44100, 44100, 1 }, { SBC_SAMPLING_FREQ_48000, 48000, 0 }, }; static const struct media_codec_config sbc_channel_modes[] = { { SBC_CHANNEL_MODE_JOINT_STEREO, 2, 3 }, { SBC_CHANNEL_MODE_STEREO, 2, 2 }, { SBC_CHANNEL_MODE_DUAL_CHANNEL, 2, 1 }, { SBC_CHANNEL_MODE_MONO, 1, 0 }, }; static const struct media_codec_config sbc_xq_channel_modes[] = { { SBC_CHANNEL_MODE_DUAL_CHANNEL, 2, 2 }, { SBC_CHANNEL_MODE_JOINT_STEREO, 2, 1 }, { SBC_CHANNEL_MODE_STEREO, 2, 0 }, }; static int codec_select_config(const struct media_codec *codec, uint32_t flags, const void *caps, size_t caps_size, const struct media_codec_audio_info *info, const struct spa_dict *settings, uint8_t config[A2DP_MAX_CAPS_SIZE], void **config_data) { a2dp_sbc_t conf; int bitpool, i; size_t n; const struct media_codec_config *configs; bool xq = false; if (caps_size < sizeof(conf)) return -EINVAL; xq = (spa_streq(codec->name, "sbc_xq")); memcpy(&conf, caps, sizeof(conf)); if (xq) { configs = sbc_xq_frequencies; n = SPA_N_ELEMENTS(sbc_xq_frequencies); } else { configs = sbc_frequencies; n = SPA_N_ELEMENTS(sbc_frequencies); } if ((i = media_codec_select_config(configs, n, conf.frequency, info ? info->rate : A2DP_CODEC_DEFAULT_RATE )) < 0) return -ENOTSUP; conf.frequency = configs[i].config; if (xq) { configs = sbc_xq_channel_modes; n = SPA_N_ELEMENTS(sbc_xq_channel_modes); } else { configs = sbc_channel_modes; n = SPA_N_ELEMENTS(sbc_channel_modes); } if ((i = media_codec_select_config(configs, n, conf.channel_mode, info ? info->channels : A2DP_CODEC_DEFAULT_CHANNELS )) < 0) return -ENOTSUP; conf.channel_mode = configs[i].config; if (conf.block_length & SBC_BLOCK_LENGTH_16) conf.block_length = SBC_BLOCK_LENGTH_16; else if (conf.block_length & SBC_BLOCK_LENGTH_12) conf.block_length = SBC_BLOCK_LENGTH_12; else if (conf.block_length & SBC_BLOCK_LENGTH_8) conf.block_length = SBC_BLOCK_LENGTH_8; else if (conf.block_length & SBC_BLOCK_LENGTH_4) conf.block_length = SBC_BLOCK_LENGTH_4; else return -ENOTSUP; if (conf.subbands & SBC_SUBBANDS_8) conf.subbands = SBC_SUBBANDS_8; else if (conf.subbands & SBC_SUBBANDS_4) conf.subbands = SBC_SUBBANDS_4; else return -ENOTSUP; if (conf.allocation_method & SBC_ALLOCATION_LOUDNESS) conf.allocation_method = SBC_ALLOCATION_LOUDNESS; else if (conf.allocation_method & SBC_ALLOCATION_SNR) conf.allocation_method = SBC_ALLOCATION_SNR; else return -ENOTSUP; bitpool = default_bitpool(conf.frequency, conf.channel_mode, xq); conf.min_bitpool = SPA_MAX(SBC_MIN_BITPOOL, conf.min_bitpool); conf.max_bitpool = SPA_MIN(bitpool, conf.max_bitpool); memcpy(config, &conf, sizeof(conf)); return sizeof(conf); } static int codec_caps_preference_cmp(const struct media_codec *codec, uint32_t flags, const void *caps1, size_t caps1_size, const void *caps2, size_t caps2_size, const struct media_codec_audio_info *info, const struct spa_dict *global_settings) { a2dp_sbc_t conf1, conf2; a2dp_sbc_t *conf; int res1, res2; int a, b; bool xq = (spa_streq(codec->name, "sbc_xq")); /* Order selected configurations by preference */ res1 = codec->select_config(codec, 0, caps1, caps1_size, info, NULL, (uint8_t *)&conf1, NULL); res2 = codec->select_config(codec, 0, caps2, caps2_size, info , NULL, (uint8_t *)&conf2, NULL); #define PREFER_EXPR(expr) \ do { \ conf = &conf1; \ a = (expr); \ conf = &conf2; \ b = (expr); \ if (a != b) \ return b - a; \ } while (0) #define PREFER_BOOL(expr) PREFER_EXPR((expr) ? 1 : 0) /* Prefer valid */ a = (res1 > 0 && (size_t)res1 == sizeof(a2dp_sbc_t)) ? 1 : 0; b = (res2 > 0 && (size_t)res2 == sizeof(a2dp_sbc_t)) ? 1 : 0; if (!a || !b) return b - a; PREFER_BOOL(conf->frequency & (SBC_SAMPLING_FREQ_48000 | SBC_SAMPLING_FREQ_44100)); if (xq) PREFER_BOOL(conf->channel_mode & SBC_CHANNEL_MODE_DUAL_CHANNEL); else PREFER_BOOL(conf->channel_mode & SBC_CHANNEL_MODE_JOINT_STEREO); PREFER_EXPR(conf->max_bitpool); return 0; #undef PREFER_EXPR #undef PREFER_BOOL } static int codec_validate_config(const struct media_codec *codec, uint32_t flags, const void *caps, size_t caps_size, struct spa_audio_info *info) { const a2dp_sbc_t *conf; if (caps == NULL || caps_size < sizeof(*conf)) return -EINVAL; conf = caps; spa_zero(*info); info->media_type = SPA_MEDIA_TYPE_audio; info->media_subtype = SPA_MEDIA_SUBTYPE_raw; info->info.raw.format = SPA_AUDIO_FORMAT_S16; switch (conf->frequency) { case SBC_SAMPLING_FREQ_16000: info->info.raw.rate = 16000; break; case SBC_SAMPLING_FREQ_32000: info->info.raw.rate = 32000; break; case SBC_SAMPLING_FREQ_44100: info->info.raw.rate = 44100; break; case SBC_SAMPLING_FREQ_48000: info->info.raw.rate = 48000; break; default: return -EINVAL; } switch (conf->channel_mode) { case SBC_CHANNEL_MODE_MONO: info->info.raw.channels = 1; info->info.raw.position[0] = SPA_AUDIO_CHANNEL_MONO; break; case SBC_CHANNEL_MODE_DUAL_CHANNEL: case SBC_CHANNEL_MODE_STEREO: case SBC_CHANNEL_MODE_JOINT_STEREO: info->info.raw.channels = 2; info->info.raw.position[0] = SPA_AUDIO_CHANNEL_FL; info->info.raw.position[1] = SPA_AUDIO_CHANNEL_FR; break; default: return -EINVAL; } switch (conf->subbands) { case SBC_SUBBANDS_4: case SBC_SUBBANDS_8: break; default: return -EINVAL; } switch (conf->block_length) { case SBC_BLOCK_LENGTH_4: case SBC_BLOCK_LENGTH_8: case SBC_BLOCK_LENGTH_12: case SBC_BLOCK_LENGTH_16: break; default: return -EINVAL; } return 0; } static int codec_set_bitpool(struct impl *this, int bitpool) { size_t rtp_size = sizeof(struct rtp_header) + sizeof(struct rtp_payload); this->sbc.bitpool = SPA_CLAMP(bitpool, this->min_bitpool, this->max_bitpool); this->codesize = sbc_get_codesize(&this->sbc); this->max_frames = (this->mtu - rtp_size) / sbc_get_frame_length(&this->sbc); if (this->max_frames > 15) this->max_frames = 15; return this->sbc.bitpool; } static int codec_enum_config(const struct media_codec *codec, uint32_t flags, const void *caps, size_t caps_size, uint32_t id, uint32_t idx, struct spa_pod_builder *b, struct spa_pod **param) { a2dp_sbc_t conf; struct spa_pod_frame f[2]; struct spa_pod_choice *choice; uint32_t i = 0; uint32_t position[2]; if (caps_size < sizeof(conf)) return -EINVAL; memcpy(&conf, caps, sizeof(conf)); if (idx > 0) return 0; spa_pod_builder_push_object(b, &f[0], SPA_TYPE_OBJECT_Format, id); spa_pod_builder_add(b, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_audio), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), SPA_FORMAT_AUDIO_format, SPA_POD_Id(SPA_AUDIO_FORMAT_S16), 0); spa_pod_builder_prop(b, SPA_FORMAT_AUDIO_rate, 0); spa_pod_builder_push_choice(b, &f[1], SPA_CHOICE_None, 0); choice = (struct spa_pod_choice*)spa_pod_builder_frame(b, &f[1]); i = 0; if (conf.frequency & SBC_SAMPLING_FREQ_48000) { if (i++ == 0) spa_pod_builder_int(b, 48000); spa_pod_builder_int(b, 48000); } if (conf.frequency & SBC_SAMPLING_FREQ_44100) { if (i++ == 0) spa_pod_builder_int(b, 44100); spa_pod_builder_int(b, 44100); } if (conf.frequency & SBC_SAMPLING_FREQ_32000) { if (i++ == 0) spa_pod_builder_int(b, 32000); spa_pod_builder_int(b, 32000); } if (conf.frequency & SBC_SAMPLING_FREQ_16000) { if (i++ == 0) spa_pod_builder_int(b, 16000); spa_pod_builder_int(b, 16000); } if (i > 1) choice->body.type = SPA_CHOICE_Enum; spa_pod_builder_pop(b, &f[1]); if (conf.channel_mode & SBC_CHANNEL_MODE_MONO && conf.channel_mode & (SBC_CHANNEL_MODE_JOINT_STEREO | SBC_CHANNEL_MODE_STEREO | SBC_CHANNEL_MODE_DUAL_CHANNEL)) { spa_pod_builder_add(b, SPA_FORMAT_AUDIO_channels, SPA_POD_CHOICE_RANGE_Int(2, 1, 2), 0); } else if (conf.channel_mode & SBC_CHANNEL_MODE_MONO) { position[0] = SPA_AUDIO_CHANNEL_MONO; spa_pod_builder_add(b, SPA_FORMAT_AUDIO_channels, SPA_POD_Int(1), SPA_FORMAT_AUDIO_position, SPA_POD_Array(sizeof(uint32_t), SPA_TYPE_Id, 1, position), 0); } else { position[0] = SPA_AUDIO_CHANNEL_FL; position[1] = SPA_AUDIO_CHANNEL_FR; spa_pod_builder_add(b, SPA_FORMAT_AUDIO_channels, SPA_POD_Int(2), SPA_FORMAT_AUDIO_position, SPA_POD_Array(sizeof(uint32_t), SPA_TYPE_Id, 2, position), 0); } *param = spa_pod_builder_pop(b, &f[0]); return *param == NULL ? -EIO : 1; } static int codec_reduce_bitpool(void *data) { struct impl *this = data; return codec_set_bitpool(this, this->sbc.bitpool - 2); } static int codec_increase_bitpool(void *data) { struct impl *this = data; return codec_set_bitpool(this, this->sbc.bitpool + 1); } static int codec_get_block_size(void *data) { struct impl *this = data; return this->codesize; } static void *codec_init(const struct media_codec *codec, uint32_t flags, void *config, size_t config_len, const struct spa_audio_info *info, void *props, size_t mtu) { struct impl *this; a2dp_sbc_t *conf = config; int res; this = calloc(1, sizeof(struct impl)); if (this == NULL) { res = -errno; goto error; } sbc_init(&this->sbc, 0); this->sbc.endian = SBC_LE; this->mtu = mtu; if (info->media_type != SPA_MEDIA_TYPE_audio || info->media_subtype != SPA_MEDIA_SUBTYPE_raw || info->info.raw.format != SPA_AUDIO_FORMAT_S16) { res = -EINVAL; goto error; } switch (conf->frequency) { case SBC_SAMPLING_FREQ_16000: this->sbc.frequency = SBC_FREQ_16000; break; case SBC_SAMPLING_FREQ_32000: this->sbc.frequency = SBC_FREQ_32000; break; case SBC_SAMPLING_FREQ_44100: this->sbc.frequency = SBC_FREQ_44100; break; case SBC_SAMPLING_FREQ_48000: this->sbc.frequency = SBC_FREQ_48000; break; default: res = -EINVAL; goto error; } switch (conf->channel_mode) { case SBC_CHANNEL_MODE_MONO: this->sbc.mode = SBC_MODE_MONO; break; case SBC_CHANNEL_MODE_DUAL_CHANNEL: this->sbc.mode = SBC_MODE_DUAL_CHANNEL; break; case SBC_CHANNEL_MODE_STEREO: this->sbc.mode = SBC_MODE_STEREO; break; case SBC_CHANNEL_MODE_JOINT_STEREO: this->sbc.mode = SBC_MODE_JOINT_STEREO; break; default: res = -EINVAL; goto error; } switch (conf->subbands) { case SBC_SUBBANDS_4: this->sbc.subbands = SBC_SB_4; this->enc_delay = 37; break; case SBC_SUBBANDS_8: this->sbc.subbands = SBC_SB_8; this->enc_delay = 73; break; default: res = -EINVAL; goto error; } if (conf->allocation_method & SBC_ALLOCATION_LOUDNESS) this->sbc.allocation = SBC_AM_LOUDNESS; else this->sbc.allocation = SBC_AM_SNR; switch (conf->block_length) { case SBC_BLOCK_LENGTH_4: this->sbc.blocks = SBC_BLK_4; break; case SBC_BLOCK_LENGTH_8: this->sbc.blocks = SBC_BLK_8; break; case SBC_BLOCK_LENGTH_12: this->sbc.blocks = SBC_BLK_12; break; case SBC_BLOCK_LENGTH_16: this->sbc.blocks = SBC_BLK_16; break; default: res = -EINVAL; goto error; } this->min_bitpool = SPA_MAX(conf->min_bitpool, 12); this->max_bitpool = conf->max_bitpool; codec_set_bitpool(this, conf->max_bitpool); return this; error: errno = -res; return NULL; } static void codec_deinit(void *data) { struct impl *this = data; sbc_finish(&this->sbc); free(this); } static int codec_abr_process (void *data, size_t unsent) { return -ENOTSUP; } static int codec_start_encode (void *data, void *dst, size_t dst_size, uint16_t seqnum, uint32_t timestamp) { struct impl *this = data; this->header = (struct rtp_header *)dst; this->payload = SPA_PTROFF(dst, sizeof(struct rtp_header), struct rtp_payload); memset(this->header, 0, sizeof(struct rtp_header)+sizeof(struct rtp_payload)); this->payload->frame_count = 0; this->header->v = 2; this->header->pt = 96; this->header->sequence_number = htons(seqnum); this->header->timestamp = htonl(timestamp); this->header->ssrc = htonl(1); return sizeof(struct rtp_header) + sizeof(struct rtp_payload); } static int codec_encode(void *data, const void *src, size_t src_size, void *dst, size_t dst_size, size_t *dst_out, int *need_flush) { struct impl *this = data; int res; res = sbc_encode(&this->sbc, src, src_size, dst, dst_size, (ssize_t*)dst_out); if (SPA_UNLIKELY(res < 0)) return -EINVAL; spa_assert(res == this->codesize); this->payload->frame_count += res / this->codesize; *need_flush = (this->payload->frame_count >= this->max_frames) ? NEED_FLUSH_ALL : NEED_FLUSH_NO; return res; } static int codec_start_decode (void *data, const void *src, size_t src_size, uint16_t *seqnum, uint32_t *timestamp) { const struct rtp_header *header = src; size_t header_size = sizeof(struct rtp_header) + sizeof(struct rtp_payload); if (src_size <= header_size) return -EINVAL; if (seqnum) *seqnum = ntohs(header->sequence_number); if (timestamp) *timestamp = ntohl(header->timestamp); return header_size; } static int codec_decode(void *data, const void *src, size_t src_size, void *dst, size_t dst_size, size_t *dst_out) { struct impl *this = data; int res; res = sbc_decode(&this->sbc, src, src_size, dst, dst_size, dst_out); return res; } static void codec_get_delay(void *data, uint32_t *encoder, uint32_t *decoder) { struct impl *this = data; if (encoder) *encoder = this->enc_delay; if (decoder) *decoder = 0; } const struct media_codec a2dp_codec_sbc = { .id = SPA_BLUETOOTH_AUDIO_CODEC_SBC, .kind = MEDIA_CODEC_A2DP, .codec_id = A2DP_CODEC_SBC, .name = "sbc", .description = "SBC", .fill_caps = codec_fill_caps, .select_config = codec_select_config, .enum_config = codec_enum_config, .validate_config = codec_validate_config, .caps_preference_cmp = codec_caps_preference_cmp, .init = codec_init, .deinit = codec_deinit, .get_block_size = codec_get_block_size, .abr_process = codec_abr_process, .start_encode = codec_start_encode, .encode = codec_encode, .start_decode = codec_start_decode, .decode = codec_decode, .reduce_bitpool = codec_reduce_bitpool, .increase_bitpool = codec_increase_bitpool, .get_delay = codec_get_delay, }; const struct media_codec a2dp_codec_sbc_xq = { .id = SPA_BLUETOOTH_AUDIO_CODEC_SBC_XQ, .kind = MEDIA_CODEC_A2DP, .codec_id = A2DP_CODEC_SBC, .name = "sbc_xq", .description = "SBC-XQ", .endpoint_name = "sbc", .fill_caps = NULL, .select_config = codec_select_config, .enum_config = codec_enum_config, .validate_config = codec_validate_config, .caps_preference_cmp = codec_caps_preference_cmp, .init = codec_init, .deinit = codec_deinit, .get_block_size = codec_get_block_size, .abr_process = codec_abr_process, .start_encode = codec_start_encode, .encode = codec_encode, .start_decode = codec_start_decode, .decode = codec_decode, .reduce_bitpool = codec_reduce_bitpool, .increase_bitpool = codec_increase_bitpool, .get_delay = codec_get_delay, }; MEDIA_CODEC_EXPORT_DEF( "sbc", &a2dp_codec_sbc, &a2dp_codec_sbc_xq ); pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/bluez5/asha-codec-g722.c000066400000000000000000000101241511204443500267600ustar00rootroot00000000000000/* Spa ASHA G722 codec */ /* SPDX-FileCopyrightText: Copyright © 2024 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include #include "rtp.h" #include "media-codecs.h" #include "g722/g722_enc_dec.h" #define ASHA_HEADER_SZ 1 /* 1 byte sequence number */ #define ASHA_ENCODED_PKT_SZ 160 static struct spa_log *spalog; struct impl { g722_encode_state_t encode; unsigned int codesize; }; static int codec_reduce_bitpool(void *data) { return -ENOTSUP; } static int codec_increase_bitpool(void *data) { return -ENOTSUP; } static int codec_abr_process (void *data, size_t unsent) { return -ENOTSUP; } static int codec_get_block_size(void *data) { struct impl *this = data; return this->codesize; } static int codec_start_encode (void *data, void *dst, size_t dst_size, uint16_t seqnum, uint32_t timestamp) { return 0; } static int codec_enum_config(const struct media_codec *codec, uint32_t flags, const void *caps, size_t caps_size, uint32_t id, uint32_t idx, struct spa_pod_builder *b, struct spa_pod **param) { struct spa_pod_frame f[1]; uint32_t position[1]; if (idx > 0) return 0; spa_pod_builder_push_object(b, &f[0], SPA_TYPE_OBJECT_Format, id); spa_pod_builder_add(b, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_audio), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), SPA_FORMAT_AUDIO_format, SPA_POD_Id(SPA_AUDIO_FORMAT_S16), 0); spa_pod_builder_add(b, SPA_FORMAT_AUDIO_rate, SPA_POD_Int(16000), 0); spa_pod_builder_add(b, SPA_FORMAT_AUDIO_channels, SPA_POD_Int(1), 0); position[0] = SPA_AUDIO_CHANNEL_MONO; spa_pod_builder_add(b, SPA_FORMAT_AUDIO_channels, SPA_POD_Int(1), SPA_FORMAT_AUDIO_position, SPA_POD_Array(sizeof(uint32_t), SPA_TYPE_Id, 1, position), 0); *param = spa_pod_builder_pop(b, &f[0]); return *param == NULL ? -EIO : 1; } static void codec_deinit(void *data) { return; } static void *codec_init(const struct media_codec *codec, uint32_t flags, void *config, size_t config_len, const struct spa_audio_info *info, void *props, size_t mtu) { struct impl *this; if ((this = calloc(1, sizeof(struct impl))) == NULL) return NULL; g722_encode_init(&this->encode, 64000, G722_PACKED); /* * G722 has a compression ratio of 4. Considering 160 bytes of encoded * payload, we need 640 bytes for generating an encoded frame. */ this->codesize = ASHA_ENCODED_PKT_SZ * 4; spa_log_debug(spalog, "Codec initialized"); return this; } static int codec_encode(void *data, const void *src, size_t src_size, void *dst, size_t dst_size, size_t *dst_out, int *need_flush) { struct impl *this = data; uint8_t *dest = (uint8_t *)dst; size_t src_sz; int ret; if (src_size < this->codesize) { spa_log_trace(spalog, "Insufficient bytes for encoding, %zd", src_size); return 0; } if (dst_size < (ASHA_HEADER_SZ + ASHA_ENCODED_PKT_SZ)) { spa_log_trace(spalog, "No space for encoded output, %zd", dst_size); return 0; } src_sz = (src_size > this->codesize) ? this->codesize : src_size; /* Sequence number will be set in media-sink before flushing */ *dest = 0; dest++; ret = g722_encode(&this->encode, dest, src, src_sz / 2 /* S16LE */); if (ret < 0) { spa_log_error(spalog, "encode error: %d", ret); return -EIO; } *dst_out = ret + ASHA_HEADER_SZ; *need_flush = NEED_FLUSH_ALL; return src_sz; } static void codec_set_log(struct spa_log *global_log) { spalog = global_log; spa_log_topic_init(spalog, &codec_plugin_log_topic); } const struct media_codec asha_codec_g722 = { .id = SPA_BLUETOOTH_AUDIO_CODEC_G722, .kind = MEDIA_CODEC_ASHA, .codec_id = ASHA_CODEC_G722, .name = "g722", .description = "G722", .fill_caps = NULL, .enum_config = codec_enum_config, .init = codec_init, .deinit = codec_deinit, .get_block_size = codec_get_block_size, .start_encode = codec_start_encode, .encode = codec_encode, .abr_process = codec_abr_process, .reduce_bitpool = codec_reduce_bitpool, .increase_bitpool = codec_increase_bitpool, .set_log = codec_set_log, }; MEDIA_CODEC_EXPORT_DEF( "g722", &asha_codec_g722 ); pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/bluez5/backend-hsphfpd.c000066400000000000000000001545061511204443500273500ustar00rootroot00000000000000/* Spa hsphfpd backend */ /* SPDX-FileCopyrightText: Copyright © 2020 Collabora Ltd. */ /* SPDX-License-Identifier: MIT */ /* Based on previous work for pulseaudio by: Pali Rohár */ #include #include #include #include #include #include #include #include #include #include #include #include #include "defs.h" #include "media-codecs.h" #include "hfp-codec-caps.h" SPA_LOG_TOPIC_DEFINE_STATIC(log_topic, "spa.bluez5.hsphfpd"); #undef SPA_LOG_TOPIC_DEFAULT #define SPA_LOG_TOPIC_DEFAULT &log_topic struct impl { struct spa_bt_backend this; struct spa_bt_monitor *monitor; struct spa_log *log; struct spa_loop *main_loop; struct spa_dbus *dbus; DBusConnection *conn; const struct spa_bt_quirks *quirks; struct spa_list endpoint_list; bool endpoints_listed; char *hsphfpd_service_id; bool acquire_in_progress; unsigned int filters_added:1; unsigned int msbc_supported:1; }; enum hsphfpd_volume_control { HSPHFPD_VOLUME_CONTROL_NONE = 1, HSPHFPD_VOLUME_CONTROL_LOCAL, HSPHFPD_VOLUME_CONTROL_REMOTE, }; enum hsphfpd_profile { HSPHFPD_PROFILE_HEADSET = 1, HSPHFPD_PROFILE_HANDSFREE, }; enum hsphfpd_role { HSPHFPD_ROLE_CLIENT = 1, HSPHFPD_ROLE_GATEWAY, }; struct hsphfpd_transport_data { char *transport_path; bool rx_soft_volume; bool tx_soft_volume; int rx_volume_gain; int tx_volume_gain; int max_rx_volume_gain; int max_tx_volume_gain; enum hsphfpd_volume_control rx_volume_control; enum hsphfpd_volume_control tx_volume_control; }; struct hsphfpd_endpoint { struct spa_list link; char *path; bool valid; bool connected; char *remote_address; char *local_address; enum hsphfpd_profile profile; enum hsphfpd_role role; int air_codecs; }; #define DBUS_INTERFACE_OBJECTMANAGER "org.freedesktop.DBus.ObjectManager" #define HSPHFPD_APPLICATION_MANAGER_INTERFACE HSPHFPD_SERVICE ".ApplicationManager" #define HSPHFPD_ENDPOINT_INTERFACE HSPHFPD_SERVICE ".Endpoint" #define HSPHFPD_AUDIO_AGENT_INTERFACE HSPHFPD_SERVICE ".AudioAgent" #define HSPHFPD_AUDIO_TRANSPORT_INTERFACE HSPHFPD_SERVICE ".AudioTransport" #define APPLICATION_OBJECT_MANAGER_PATH "/Profile/hsphfpd/manager" #define HSPHFP_AUDIO_CLIENT_PCM_S16LE_8KHZ "/Profile/hsphfpd/pcm_s16le_8khz_agent" #define HSPHFP_AUDIO_CLIENT_MSBC "/Profile/hsphfpd/msbc_agent" #define HSPHFP_AIR_CODEC_CVSD "CVSD" #define HSPHFP_AIR_CODEC_MSBC "mSBC" #define HSPHFP_AGENT_CODEC_PCM "PCM_s16le_8kHz" #define HSPHFP_AGENT_CODEC_MSBC "mSBC" #define APPLICATION_OBJECT_MANAGER_INTROSPECT_XML \ DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \ "\n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ "\n" #define AUDIO_AGENT_ENDPOINT_INTROSPECT_XML \ DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \ "\n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ "\n" #define HSPHFPD_ERROR_INVALID_ARGUMENTS HSPHFPD_SERVICE ".Error.InvalidArguments" #define HSPHFPD_ERROR_ALREADY_EXISTS HSPHFPD_SERVICE ".Error.AlreadyExists" #define HSPHFPD_ERROR_DOES_NOT_EXISTS HSPHFPD_SERVICE ".Error.DoesNotExist" #define HSPHFPD_ERROR_NOT_CONNECTED HSPHFPD_SERVICE ".Error.NotConnected" #define HSPHFPD_ERROR_ALREADY_CONNECTED HSPHFPD_SERVICE ".Error.AlreadyConnected" #define HSPHFPD_ERROR_IN_PROGRESS HSPHFPD_SERVICE ".Error.InProgress" #define HSPHFPD_ERROR_IN_USE HSPHFPD_SERVICE ".Error.InUse" #define HSPHFPD_ERROR_NOT_SUPPORTED HSPHFPD_SERVICE ".Error.NotSupported" #define HSPHFPD_ERROR_NOT_AVAILABLE HSPHFPD_SERVICE ".Error.NotAvailable" #define HSPHFPD_ERROR_FAILED HSPHFPD_SERVICE ".Error.Failed" #define HSPHFPD_ERROR_REJECTED HSPHFPD_SERVICE ".Error.Rejected" #define HSPHFPD_ERROR_CANCELED HSPHFPD_SERVICE ".Error.Canceled" static struct hsphfpd_endpoint *endpoint_find(struct impl *backend, const char *path) { struct hsphfpd_endpoint *d; spa_list_for_each(d, &backend->endpoint_list, link) if (spa_streq(d->path, path)) return d; return NULL; } static void endpoint_free(struct hsphfpd_endpoint *endpoint) { spa_list_remove(&endpoint->link); free(endpoint->path); free(endpoint->local_address); free(endpoint->remote_address); free(endpoint); } static bool hsphfpd_cmp_transport_path(struct spa_bt_transport *t, const void *data) { struct hsphfpd_transport_data *td = t->user_data; if (spa_streq(td->transport_path, data)) return true; return false; } static inline bool check_signature(DBusMessage *m, const char sig[]) { return spa_streq(dbus_message_get_signature(m), sig); } static int set_dbus_property(struct impl *backend, const char *service, const char *path, const char *interface, const char *property, int type, void *value) { spa_autoptr(DBusMessage) m = NULL, r = NULL; DBusMessageIter iter; spa_auto(DBusError) err = DBUS_ERROR_INIT; m = dbus_message_new_method_call(HSPHFPD_SERVICE, path, DBUS_INTERFACE_PROPERTIES, "Set"); if (m == NULL) return -ENOMEM; dbus_message_append_args(m, DBUS_TYPE_STRING, &interface, DBUS_TYPE_STRING, &property, DBUS_TYPE_INVALID); dbus_message_iter_init_append(m, &iter); dbus_message_iter_append_basic(&iter, type, value); r = dbus_connection_send_with_reply_and_block(backend->conn, m, -1, &err); if (r == NULL) { spa_log_error(backend->log, "Transport Set() failed for transport %s (%s)", path, err.message); return -EIO; } if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) { spa_log_error(backend->log, "Set() returned error: %s", dbus_message_get_error_name(r)); return -EIO; } return 0; } static inline void set_rx_volume_gain_property(const struct spa_bt_transport *transport, uint16_t gain) { struct impl *backend = SPA_CONTAINER_OF(transport->backend, struct impl, this); struct hsphfpd_transport_data *transport_data = transport->user_data; if (transport->fd < 0 || transport_data->rx_volume_control <= HSPHFPD_VOLUME_CONTROL_NONE) return; if (set_dbus_property(backend, HSPHFPD_SERVICE, transport_data->transport_path, HSPHFPD_AUDIO_TRANSPORT_INTERFACE, "RxVolumeGain", DBUS_TYPE_UINT16, &gain)) spa_log_error(backend->log, "Changing rx volume gain to %u for transport %s failed", (unsigned)gain, transport_data->transport_path); } static inline void set_tx_volume_gain_property(const struct spa_bt_transport *transport, uint16_t gain) { struct impl *backend = SPA_CONTAINER_OF(transport->backend, struct impl, this); struct hsphfpd_transport_data *transport_data = transport->user_data; if (transport->fd < 0 || transport_data->tx_volume_control <= HSPHFPD_VOLUME_CONTROL_NONE) return; if (set_dbus_property(backend, HSPHFPD_SERVICE, transport_data->transport_path, HSPHFPD_AUDIO_TRANSPORT_INTERFACE, "TxVolumeGain", DBUS_TYPE_UINT16, &gain)) spa_log_error(backend->log, "Changing tx volume gain to %u for transport %s failed", (unsigned)gain, transport_data->transport_path); } static void parse_transport_properties_values(struct impl *backend, const char *transport_path, DBusMessageIter *i, const char **endpoint_path, const char **air_codec, enum hsphfpd_volume_control *rx_volume_control, enum hsphfpd_volume_control *tx_volume_control, uint16_t *rx_volume_gain, uint16_t *tx_volume_gain, uint16_t *mtu) { DBusMessageIter element_i; spa_assert(i); dbus_message_iter_recurse(i, &element_i); while (dbus_message_iter_get_arg_type(&element_i) == DBUS_TYPE_DICT_ENTRY) { DBusMessageIter dict_i, variant_i; const char *key; dbus_message_iter_recurse(&element_i, &dict_i); if (dbus_message_iter_get_arg_type(&dict_i) != DBUS_TYPE_STRING) { spa_log_error(backend->log, "Received invalid property for transport %s", transport_path); return; } dbus_message_iter_get_basic(&dict_i, &key); if (!dbus_message_iter_next(&dict_i)) { spa_log_error(backend->log, "Received invalid property for transport %s", transport_path); return; } if (dbus_message_iter_get_arg_type(&dict_i) != DBUS_TYPE_VARIANT) { spa_log_error(backend->log, "Received invalid property for transport %s", transport_path); return; } dbus_message_iter_recurse(&dict_i, &variant_i); switch (dbus_message_iter_get_arg_type(&variant_i)) { case DBUS_TYPE_STRING: if (spa_streq(key, "RxVolumeControl") || spa_streq(key, "TxVolumeControl")) { const char *value; enum hsphfpd_volume_control volume_control; dbus_message_iter_get_basic(&variant_i, &value); if (spa_streq(value, "none")) volume_control = HSPHFPD_VOLUME_CONTROL_NONE; else if (spa_streq(value, "local")) volume_control = HSPHFPD_VOLUME_CONTROL_LOCAL; else if (spa_streq(value, "remote")) volume_control = HSPHFPD_VOLUME_CONTROL_REMOTE; else volume_control = 0; if (!volume_control) spa_log_warn(backend->log, "Transport %s received invalid '%s' property value '%s', ignoring", transport_path, key, value); else if (spa_streq(key, "RxVolumeControl")) *rx_volume_control = volume_control; else if (spa_streq(key, "TxVolumeControl")) *tx_volume_control = volume_control; } else if (spa_streq(key, "AirCodec")) dbus_message_iter_get_basic(&variant_i, air_codec); break; case DBUS_TYPE_UINT16: if (spa_streq(key, "MTU")) dbus_message_iter_get_basic(&variant_i, mtu); else if (spa_streq(key, "RxVolumeGain")) dbus_message_iter_get_basic(&variant_i, rx_volume_gain); else if (spa_streq(key, "TxVolumeGain")) dbus_message_iter_get_basic(&variant_i, tx_volume_gain); break; case DBUS_TYPE_OBJECT_PATH: if (spa_streq(key, "Endpoint")) dbus_message_iter_get_basic(&variant_i, endpoint_path); break; } dbus_message_iter_next(&element_i); } } static void hsphfpd_parse_transport_properties(struct impl *backend, struct spa_bt_transport *transport, DBusMessageIter *i) { struct hsphfpd_transport_data *transport_data = transport->user_data; const char *endpoint_path = NULL; const char *air_codec = NULL; enum hsphfpd_volume_control rx_volume_control = 0; enum hsphfpd_volume_control tx_volume_control = 0; uint16_t rx_volume_gain = -1; uint16_t tx_volume_gain = -1; uint16_t mtu = 0; bool rx_volume_gain_changed = false; bool tx_volume_gain_changed = false; bool rx_volume_control_changed = false; bool tx_volume_control_changed = false; bool rx_soft_volume_changed = false; bool tx_soft_volume_changed = false; parse_transport_properties_values(backend, transport_data->transport_path, i, &endpoint_path, &air_codec, &rx_volume_control, &tx_volume_control, &rx_volume_gain, &tx_volume_gain, &mtu); if (endpoint_path) spa_log_warn(backend->log, "Transport %s received a duplicate '%s' property, ignoring", transport_data->transport_path, "Endpoint"); if (air_codec) spa_log_warn(backend->log, "Transport %s received a duplicate '%s' property, ignoring", transport_data->transport_path, "AirCodec"); if (mtu) spa_log_warn(backend->log, "Transport %s received a duplicate '%s' property, ignoring", transport_data->transport_path, "MTU"); if (rx_volume_control) { if (!!transport_data->rx_soft_volume != !!(rx_volume_control != HSPHFPD_VOLUME_CONTROL_REMOTE)) { spa_log_info(backend->log, "Transport %s changed rx soft volume from %d to %d", transport_data->transport_path, transport_data->rx_soft_volume, (rx_volume_control != HSPHFPD_VOLUME_CONTROL_REMOTE)); transport_data->rx_soft_volume = (rx_volume_control != HSPHFPD_VOLUME_CONTROL_REMOTE); rx_soft_volume_changed = true; } if (transport_data->rx_volume_control != rx_volume_control) { transport_data->rx_volume_control = rx_volume_control; rx_volume_control_changed = true; } } if (tx_volume_control) { if (!!transport_data->tx_soft_volume != !!(tx_volume_control != HSPHFPD_VOLUME_CONTROL_REMOTE)) { spa_log_info(backend->log, "Transport %s changed tx soft volume from %d to %d", transport_data->transport_path, transport_data->rx_soft_volume, (tx_volume_control != HSPHFPD_VOLUME_CONTROL_REMOTE)); transport_data->tx_soft_volume = (tx_volume_control != HSPHFPD_VOLUME_CONTROL_REMOTE); tx_soft_volume_changed = true; } if (transport_data->tx_volume_control != tx_volume_control) { transport_data->tx_volume_control = tx_volume_control; tx_volume_control_changed = true; } } if (rx_volume_gain != (uint16_t)-1) { if (transport_data->rx_volume_gain != rx_volume_gain) { spa_log_info(backend->log, "Transport %s changed rx volume gain from %u to %u", transport_data->transport_path, (unsigned)transport_data->rx_volume_gain, (unsigned)rx_volume_gain); transport_data->rx_volume_gain = rx_volume_gain; rx_volume_gain_changed = true; } } if (tx_volume_gain != (uint16_t)-1) { if (transport_data->tx_volume_gain != tx_volume_gain) { spa_log_info(backend->log, "Transport %s changed tx volume gain from %u to %u", transport_data->transport_path, (unsigned)transport_data->tx_volume_gain, (unsigned)tx_volume_gain); transport_data->tx_volume_gain = tx_volume_gain; tx_volume_gain_changed = true; } } #if 0 if (rx_volume_gain_changed || rx_soft_volume_changed) pa_hook_fire(pa_bluetooth_discovery_hook(transport_data->hsphfpd->discovery, PA_BLUETOOTH_HOOK_TRANSPORT_RX_VOLUME_GAIN_CHANGED), transport); if (tx_volume_gain_changed || tx_soft_volume_changed) pa_hook_fire(pa_bluetooth_discovery_hook(transport_data->hsphfpd->discovery, PA_BLUETOOTH_HOOK_TRANSPORT_TX_VOLUME_GAIN_CHANGED), transport); #else spa_log_debug(backend->log, "RX volume gain changed: %d, soft volume changed: %d", rx_volume_gain_changed, rx_soft_volume_changed); spa_log_debug(backend->log, "TX volume gain changed: %d, soft volume changed: %d", tx_volume_gain_changed, tx_soft_volume_changed); #endif if (rx_volume_control_changed) set_rx_volume_gain_property(transport, transport_data->rx_volume_gain); if (tx_volume_control_changed) set_tx_volume_gain_property(transport, transport_data->tx_volume_gain); } static DBusHandlerResult audio_agent_get_property(DBusConnection *conn, DBusMessage *m, const char *path, void *userdata) { const char *interface; const char *property; const char *agent_codec; spa_autoptr(DBusMessage) r = NULL; if (!check_signature(m, "ss")) { r = dbus_message_new_error(m, DBUS_ERROR_INVALID_ARGS, "Invalid signature in method call"); goto fail; } if (dbus_message_get_args(m, NULL, DBUS_TYPE_STRING, &interface, DBUS_TYPE_STRING, &property, DBUS_TYPE_INVALID) == FALSE) { r = dbus_message_new_error(m, DBUS_ERROR_INVALID_ARGS, "Invalid arguments in method call"); goto fail; } if (!spa_streq(interface, HSPHFPD_AUDIO_AGENT_INTERFACE)) return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; if (!spa_streq(property, "AgentCodec")) { r = dbus_message_new_error(m, DBUS_ERROR_INVALID_ARGS, "Invalid property in method call"); goto fail; } if (spa_streq(path, HSPHFP_AUDIO_CLIENT_PCM_S16LE_8KHZ)) agent_codec = HSPHFP_AGENT_CODEC_PCM; else if (spa_streq(path, HSPHFP_AUDIO_CLIENT_MSBC)) agent_codec = HSPHFP_AGENT_CODEC_MSBC; else { r = dbus_message_new_error(m, DBUS_ERROR_INVALID_ARGS, "Invalid path in method call"); goto fail; } if ((r = dbus_message_new_method_return(m)) == NULL) return DBUS_HANDLER_RESULT_NEED_MEMORY; if (!dbus_message_append_args(r, DBUS_TYPE_STRING, &agent_codec, DBUS_TYPE_INVALID)) return DBUS_HANDLER_RESULT_NEED_MEMORY; fail: if (!dbus_connection_send(conn, r, NULL)) return DBUS_HANDLER_RESULT_NEED_MEMORY; return DBUS_HANDLER_RESULT_HANDLED; } static DBusHandlerResult audio_agent_getall_properties(DBusConnection *conn, DBusMessage *m, const char *path, void *userdata) { const char *interface; DBusMessageIter iter, array, dict, data; const char *agent_codec; spa_autoptr(DBusMessage) r = NULL; if (!check_signature(m, "s")) { r = dbus_message_new_error(m, DBUS_ERROR_INVALID_ARGS, "Invalid signature in method call"); goto fail; } if (dbus_message_get_args(m, NULL, DBUS_TYPE_STRING, &interface, DBUS_TYPE_INVALID) == FALSE) { r = dbus_message_new_error(m, DBUS_ERROR_INVALID_ARGS, "Invalid arguments in method call"); goto fail; } if (spa_streq(path, HSPHFP_AUDIO_CLIENT_PCM_S16LE_8KHZ)) agent_codec = HSPHFP_AGENT_CODEC_PCM; else if (spa_streq(path, HSPHFP_AUDIO_CLIENT_MSBC)) agent_codec = HSPHFP_AGENT_CODEC_MSBC; else { r = dbus_message_new_error(m, DBUS_ERROR_INVALID_ARGS, "Invalid path in method call"); goto fail; } if (!spa_streq(interface, HSPHFPD_AUDIO_AGENT_INTERFACE)) return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; if ((r = dbus_message_new_method_return(m)) == NULL) return DBUS_HANDLER_RESULT_NEED_MEMORY; dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &array); dbus_message_iter_open_container(&array, DBUS_TYPE_DICT_ENTRY, NULL, &dict); dbus_message_iter_append_basic(&dict, DBUS_TYPE_STRING, &(const char *){ "AgentCodec" }); dbus_message_iter_open_container(&dict, DBUS_TYPE_VARIANT, "s", &data); dbus_message_iter_append_basic(&data, DBUS_TYPE_STRING, &agent_codec); dbus_message_iter_close_container(&dict, &data); dbus_message_iter_close_container(&array, &dict); dbus_message_iter_close_container(&iter, &array); fail: if (!dbus_connection_send(conn, r, NULL)) return DBUS_HANDLER_RESULT_NEED_MEMORY; return DBUS_HANDLER_RESULT_HANDLED; } static DBusHandlerResult hsphfpd_new_audio_connection(DBusConnection *conn, DBusMessage *m, const char *path, void *userdata) { struct impl *backend = userdata; DBusMessageIter arg_i; const char *transport_path; const char *sender; const char *endpoint_path = NULL; const char *air_codec = NULL; enum hsphfpd_volume_control rx_volume_control = 0; enum hsphfpd_volume_control tx_volume_control = 0; uint16_t rx_volume_gain = -1; uint16_t tx_volume_gain = -1; uint16_t mtu = 0; unsigned int codec; struct hsphfpd_endpoint *endpoint; struct spa_bt_transport *transport; struct hsphfpd_transport_data *transport_data; spa_autoptr(DBusMessage) r = NULL; spa_autoclose int fd = -1; if (!check_signature(m, "oha{sv}")) { r = dbus_message_new_error(m, DBUS_ERROR_INVALID_ARGS, "Invalid signature in method call"); goto fail; } if (!dbus_message_iter_init(m, &arg_i)) return DBUS_HANDLER_RESULT_NEED_MEMORY; dbus_message_iter_get_basic(&arg_i, &transport_path); dbus_message_iter_next(&arg_i); dbus_message_iter_get_basic(&arg_i, &fd); spa_log_debug(backend->log, "NewConnection %s, fd %d", transport_path, fd); sender = dbus_message_get_sender(m); if (!spa_streq(sender, backend->hsphfpd_service_id)) { spa_log_error(backend->log, "Sender '%s' is not authorized", sender); r = dbus_message_new_error_printf(m, HSPHFPD_ERROR_REJECTED, "Sender '%s' is not authorized", sender); goto fail; } if (spa_streq(path, HSPHFP_AUDIO_CLIENT_PCM_S16LE_8KHZ)) codec = HFP_AUDIO_CODEC_CVSD; else if (spa_streq(path, HSPHFP_AUDIO_CLIENT_MSBC)) codec = HFP_AUDIO_CODEC_MSBC; else { r = dbus_message_new_error(m, HSPHFPD_ERROR_REJECTED, "Invalid path"); goto fail; } dbus_message_iter_next(&arg_i); parse_transport_properties_values(backend, transport_path, &arg_i, &endpoint_path, &air_codec, &rx_volume_control, &tx_volume_control, &rx_volume_gain, &tx_volume_gain, &mtu); if (!endpoint_path) { spa_log_error(backend->log, "Endpoint property was not specified"); r = dbus_message_new_error(m, HSPHFPD_ERROR_REJECTED, "Endpoint property was not specified"); goto fail; } if (!air_codec) { spa_log_error(backend->log, "AirCodec property was not specified"); r = dbus_message_new_error(m, HSPHFPD_ERROR_REJECTED, "AirCodec property was not specified"); goto fail; } if (!rx_volume_control) { spa_log_error(backend->log, "RxVolumeControl property was not specified"); r = dbus_message_new_error(m, HSPHFPD_ERROR_REJECTED, "RxVolumeControl property was not specified"); goto fail; } if (!tx_volume_control) { spa_log_error(backend->log, "TxVolumeControl property was not specified"); r = dbus_message_new_error(m, HSPHFPD_ERROR_REJECTED, "TxVolumeControl property was not specified"); goto fail; } if (rx_volume_control != HSPHFPD_VOLUME_CONTROL_NONE) { if (rx_volume_gain == (uint16_t)-1) { spa_log_error(backend->log, "RxVolumeGain property was not specified, but VolumeControl is not none"); r = dbus_message_new_error(m, HSPHFPD_ERROR_REJECTED, "RxVolumeGain property was not specified, but VolumeControl is not none"); goto fail; } } else { rx_volume_gain = 15; /* No volume control, so set maximal value */ } if (tx_volume_control != HSPHFPD_VOLUME_CONTROL_NONE) { if (tx_volume_gain == (uint16_t)-1) { spa_log_error(backend->log, "TxVolumeGain property was not specified, but VolumeControl is not none"); r = dbus_message_new_error(m, HSPHFPD_ERROR_REJECTED, "TxVolumeGain property was not specified, but VolumeControl is not none"); goto fail; } } else { tx_volume_gain = 15; /* No volume control, so set maximal value */ } if (!mtu) { spa_log_error(backend->log, "MTU property was not specified"); r = dbus_message_new_error(m, HSPHFPD_ERROR_REJECTED, "MTU property was not specified"); goto fail; } endpoint = endpoint_find(backend, endpoint_path); if (!endpoint) { spa_log_error(backend->log, "Endpoint %s does not exist", endpoint_path); r = dbus_message_new_error_printf(m, HSPHFPD_ERROR_REJECTED, "Endpoint %s does not exist", endpoint_path); goto fail; } if (!endpoint->valid) { spa_log_error(backend->log, "Endpoint %s is not valid", endpoint_path); r = dbus_message_new_error_printf(m, HSPHFPD_ERROR_REJECTED, "Endpoint %s is not valid", endpoint_path); goto fail; } transport = spa_bt_transport_find(backend->monitor, endpoint_path); if (!transport) { spa_log_error(backend->log, "Endpoint %s is not connected", endpoint_path); r = dbus_message_new_error_printf(m, HSPHFPD_ERROR_REJECTED, "Endpoint %s is not connected", endpoint_path); goto fail; } if (transport->media_codec->codec_id != codec) { spa_log_warn(backend->log, "Expecting codec to be %d, got %d", transport->media_codec->codec_id, codec); r = dbus_message_new_error_printf(m, HSPHFPD_ERROR_REJECTED, "Endpoint %s has wrong codec", endpoint_path); goto fail; } if (transport->fd >= 0) { spa_log_error(backend->log, "Endpoint %s has already active transport", endpoint_path); r = dbus_message_new_error_printf(m, HSPHFPD_ERROR_REJECTED, "Endpoint %s has already active transport", endpoint_path); goto fail; } transport_data = transport->user_data; transport_data->transport_path = strdup(transport_path); transport_data->rx_soft_volume = (rx_volume_control != HSPHFPD_VOLUME_CONTROL_REMOTE); transport_data->tx_soft_volume = (tx_volume_control != HSPHFPD_VOLUME_CONTROL_REMOTE); transport_data->rx_volume_gain = rx_volume_gain; transport_data->tx_volume_gain = tx_volume_gain; transport_data->rx_volume_control = rx_volume_control; transport_data->tx_volume_control = tx_volume_control; #if 0 pa_hook_fire(pa_bluetooth_discovery_hook(hsphfpd->discovery, PA_BLUETOOTH_HOOK_TRANSPORT_RX_VOLUME_GAIN_CHANGED), transport); pa_hook_fire(pa_bluetooth_discovery_hook(hsphfpd->discovery, PA_BLUETOOTH_HOOK_TRANSPORT_TX_VOLUME_GAIN_CHANGED), transport); #endif transport->read_mtu = mtu; transport->write_mtu = mtu; transport->fd = spa_steal_fd(fd); if ((r = dbus_message_new_method_return(m)) == NULL) return DBUS_HANDLER_RESULT_NEED_MEMORY; fail: if (r) { if (!dbus_connection_send(backend->conn, r, NULL)) return DBUS_HANDLER_RESULT_NEED_MEMORY; } return DBUS_HANDLER_RESULT_HANDLED; } static DBusHandlerResult audio_agent_endpoint_handler(DBusConnection *c, DBusMessage *m, void *userdata) { struct impl *backend = userdata; const char *path, *interface, *member; DBusHandlerResult res; path = dbus_message_get_path(m); interface = dbus_message_get_interface(m); member = dbus_message_get_member(m); spa_log_debug(backend->log, "dbus: path=%s, interface=%s, member=%s", path, interface, member); if (dbus_message_is_method_call(m, "org.freedesktop.DBus.Introspectable", "Introspect")) { const char *xml = AUDIO_AGENT_ENDPOINT_INTROSPECT_XML; spa_autoptr(DBusMessage) r = NULL; if ((r = dbus_message_new_method_return(m)) == NULL) return DBUS_HANDLER_RESULT_NEED_MEMORY; if (!dbus_message_append_args(r, DBUS_TYPE_STRING, &xml, DBUS_TYPE_INVALID)) return DBUS_HANDLER_RESULT_NEED_MEMORY; if (!dbus_connection_send(backend->conn, r, NULL)) return DBUS_HANDLER_RESULT_NEED_MEMORY; res = DBUS_HANDLER_RESULT_HANDLED; } else if (dbus_message_is_method_call(m, DBUS_INTERFACE_PROPERTIES, "Get")) res = audio_agent_get_property(c, m, path, userdata); else if (dbus_message_is_method_call(m, DBUS_INTERFACE_PROPERTIES, "GetAll")) res = audio_agent_getall_properties(c, m, path, userdata); else if (dbus_message_is_method_call(m, HSPHFPD_AUDIO_AGENT_INTERFACE, "NewConnection")) res = hsphfpd_new_audio_connection(c, m, path, userdata); else res = DBUS_HANDLER_RESULT_NOT_YET_HANDLED; return res; } static void append_audio_agent_object(DBusMessageIter *iter, const char *endpoint, const char *agent_codec) { const char *interface_name = HSPHFPD_AUDIO_AGENT_INTERFACE; DBusMessageIter object, array, entry, dict, codec, data; dbus_message_iter_open_container(iter, DBUS_TYPE_DICT_ENTRY, NULL, &object); dbus_message_iter_append_basic(&object, DBUS_TYPE_OBJECT_PATH, &endpoint); dbus_message_iter_open_container(&object, DBUS_TYPE_ARRAY, "{sa{sv}}", &array); dbus_message_iter_open_container(&array, DBUS_TYPE_DICT_ENTRY, NULL, &entry); dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &interface_name); dbus_message_iter_open_container(&entry, DBUS_TYPE_ARRAY, "{sv}", &dict); dbus_message_iter_open_container(&dict, DBUS_TYPE_DICT_ENTRY, NULL, &codec); dbus_message_iter_append_basic(&codec, DBUS_TYPE_STRING, &(const char *) { "AgentCodec" }); dbus_message_iter_open_container(&codec, DBUS_TYPE_VARIANT, "s", &data); dbus_message_iter_append_basic(&data, DBUS_TYPE_STRING, &agent_codec); dbus_message_iter_close_container(&codec, &data); dbus_message_iter_close_container(&dict, &codec); dbus_message_iter_close_container(&entry, &dict); dbus_message_iter_close_container(&array, &entry); dbus_message_iter_close_container(&object, &array); dbus_message_iter_close_container(iter, &object); } static DBusHandlerResult application_object_manager_handler(DBusConnection *c, DBusMessage *m, void *userdata) { struct impl *backend = userdata; const char *path, *interface, *member; spa_autoptr(DBusMessage) r = NULL; path = dbus_message_get_path(m); interface = dbus_message_get_interface(m); member = dbus_message_get_member(m); spa_log_debug(backend->log, "dbus: path=%s, interface=%s, member=%s", path, interface, member); if (dbus_message_is_method_call(m, "org.freedesktop.DBus.Introspectable", "Introspect")) { const char *xml = APPLICATION_OBJECT_MANAGER_INTROSPECT_XML; if ((r = dbus_message_new_method_return(m)) == NULL) return DBUS_HANDLER_RESULT_NEED_MEMORY; if (!dbus_message_append_args(r, DBUS_TYPE_STRING, &xml, DBUS_TYPE_INVALID)) return DBUS_HANDLER_RESULT_NEED_MEMORY; } else if (dbus_message_is_method_call(m, DBUS_INTERFACE_OBJECTMANAGER, "GetManagedObjects")) { DBusMessageIter iter, array; if ((r = dbus_message_new_method_return(m)) == NULL) return DBUS_HANDLER_RESULT_NEED_MEMORY; dbus_message_iter_init_append(r, &iter); dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{oa{sa{sv}}}", &array); append_audio_agent_object(&array, HSPHFP_AUDIO_CLIENT_PCM_S16LE_8KHZ, HSPHFP_AGENT_CODEC_PCM); if (backend->msbc_supported) append_audio_agent_object(&array, HSPHFP_AUDIO_CLIENT_MSBC, HSPHFP_AGENT_CODEC_MSBC); dbus_message_iter_close_container(&iter, &array); } else return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; if (!dbus_connection_send(backend->conn, r, NULL)) return DBUS_HANDLER_RESULT_NEED_MEMORY; return DBUS_HANDLER_RESULT_HANDLED; } static void hsphfpd_audio_acquire_reply(DBusPendingCall *pending, void *user_data) { struct spa_bt_transport *transport = user_data; struct impl *backend = SPA_CONTAINER_OF(transport->backend, struct impl, this); const char *transport_path; const char *service_id; const char *agent_path; spa_auto(DBusError) error = DBUS_ERROR_INIT; int ret = 0; backend->acquire_in_progress = false; spa_autoptr(DBusMessage) r = steal_reply_and_unref(&pending); if (r == NULL) return; if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) { spa_log_error(backend->log, "RegisterApplication() failed: %s", dbus_message_get_error_name(r)); ret = -EIO; goto finish; } if (!spa_streq(dbus_message_get_sender(r), backend->hsphfpd_service_id)) { spa_log_error(backend->log, "Reply for " HSPHFPD_ENDPOINT_INTERFACE ".ConnectAudio() from invalid sender"); ret = -EIO; goto finish; } if (!check_signature(r, "oso")) { spa_log_error(backend->log, "Invalid reply signature for " HSPHFPD_ENDPOINT_INTERFACE ".ConnectAudio()"); ret = -EIO; goto finish; } if (dbus_message_get_args(r, &error, DBUS_TYPE_OBJECT_PATH, &transport_path, DBUS_TYPE_STRING, &service_id, DBUS_TYPE_OBJECT_PATH, &agent_path, DBUS_TYPE_INVALID) == FALSE) { spa_log_error(backend->log, "Failed to parse " HSPHFPD_ENDPOINT_INTERFACE ".ConnectAudio() reply: %s", error.message); ret = -EIO; goto finish; } if (!spa_streq(service_id, dbus_bus_get_unique_name(backend->conn))) { spa_log_warn(backend->log, HSPHFPD_ENDPOINT_INTERFACE ".ConnectAudio() failed: Other audio application took audio socket"); ret = -EIO; goto finish; } spa_log_debug(backend->log, "hsphfpd audio acquired"); finish: if (ret < 0) spa_bt_transport_set_state(transport, SPA_BT_TRANSPORT_STATE_ERROR); else spa_bt_transport_set_state(transport, SPA_BT_TRANSPORT_STATE_ACTIVE); } static int hsphfpd_audio_acquire(void *data, bool optional) { struct spa_bt_transport *transport = data; struct impl *backend = SPA_CONTAINER_OF(transport->backend, struct impl, this); spa_autoptr(DBusMessage) m = NULL; const char *air_codec = HSPHFP_AIR_CODEC_CVSD; const char *agent_codec = HSPHFP_AGENT_CODEC_PCM; spa_log_debug(backend->log, "transport %p: Acquire %s", transport, transport->path); if (backend->acquire_in_progress) return -EINPROGRESS; if (transport->media_codec->codec_id == HFP_AUDIO_CODEC_MSBC) { air_codec = HSPHFP_AIR_CODEC_MSBC; agent_codec = HSPHFP_AGENT_CODEC_MSBC; } m = dbus_message_new_method_call(HSPHFPD_SERVICE, transport->path, HSPHFPD_ENDPOINT_INTERFACE, "ConnectAudio"); if (m == NULL) return -ENOMEM; dbus_message_append_args(m, DBUS_TYPE_STRING, &air_codec, DBUS_TYPE_STRING, &agent_codec, DBUS_TYPE_INVALID); if (!send_with_reply(backend->conn, m, hsphfpd_audio_acquire_reply, transport)) return -EIO; backend->acquire_in_progress = true; return 0; } static int hsphfpd_audio_release(void *data) { struct spa_bt_transport *transport = data; struct impl *backend = SPA_CONTAINER_OF(transport->backend, struct impl, this); struct hsphfpd_transport_data *transport_data = transport->user_data; spa_log_debug(backend->log, "transport %p: Release %s", transport, transport->path); spa_bt_transport_set_state(transport, SPA_BT_TRANSPORT_STATE_IDLE); if (transport->sco_io) { spa_bt_sco_io_destroy(transport->sco_io); transport->sco_io = NULL; } /* shutdown to make sure connection is dropped immediately */ shutdown(transport->fd, SHUT_RDWR); close(transport->fd); if (transport_data->transport_path) { free(transport_data->transport_path); transport_data->transport_path = NULL; } transport->fd = -1; return 0; } static int hsphfpd_audio_destroy(void *data) { struct spa_bt_transport *transport = data; struct hsphfpd_transport_data *transport_data = transport->user_data; if (transport_data->transport_path) { free(transport_data->transport_path); transport_data->transport_path = NULL; } return 0; } static const struct spa_bt_transport_implementation hsphfpd_transport_impl = { SPA_VERSION_BT_TRANSPORT_IMPLEMENTATION, .acquire = hsphfpd_audio_acquire, .release = hsphfpd_audio_release, .destroy = hsphfpd_audio_destroy, }; static DBusHandlerResult hsphfpd_parse_endpoint_properties(struct impl *backend, struct hsphfpd_endpoint *endpoint, DBusMessageIter *i) { DBusMessageIter element_i; struct spa_bt_device *d; struct spa_bt_transport *t; const struct media_codec *codec; dbus_message_iter_recurse(i, &element_i); while (dbus_message_iter_get_arg_type(&element_i) == DBUS_TYPE_DICT_ENTRY) { DBusMessageIter dict_i, value_i; const char *key; dbus_message_iter_recurse(&element_i, &dict_i); dbus_message_iter_get_basic(&dict_i, &key); dbus_message_iter_next(&dict_i); dbus_message_iter_recurse(&dict_i, &value_i); switch (dbus_message_iter_get_arg_type(&value_i)) { case DBUS_TYPE_STRING: { const char *value; dbus_message_iter_get_basic(&value_i, &value); if (spa_streq(key, "RemoteAddress")) endpoint->remote_address = strdup(value); else if (spa_streq(key, "LocalAddress")) endpoint->local_address = strdup(value); else if (spa_streq(key, "Profile")) { if (endpoint->profile) spa_log_warn(backend->log, "Endpoint %s received a duplicate '%s' property, ignoring", endpoint->path, key); else if (spa_streq(value, "headset")) endpoint->profile = HSPHFPD_PROFILE_HEADSET; else if (spa_streq(value, "handsfree")) endpoint->profile = HSPHFPD_PROFILE_HANDSFREE; else spa_log_warn(backend->log, "Endpoint %s received invalid '%s' property value '%s', ignoring", endpoint->path, key, value); } else if (spa_streq(key, "Role")) { if (endpoint->role) spa_log_warn(backend->log, "Endpoint %s received a duplicate '%s' property, ignoring", endpoint->path, key); else if (spa_streq(value, "client")) endpoint->role = HSPHFPD_ROLE_CLIENT; else if (spa_streq(value, "gateway")) endpoint->role = HSPHFPD_ROLE_GATEWAY; else spa_log_warn(backend->log, "Endpoint %s received invalid '%s' property value '%s', ignoring", endpoint->path, key, value); } spa_log_trace(backend->log, " %s: %s (%p)", key, value, endpoint); } break; case DBUS_TYPE_BOOLEAN: { dbus_bool_t value; dbus_message_iter_get_basic(&value_i, &value); if (spa_streq(key, "Connected")) endpoint->connected = value; spa_log_trace(backend->log, " %s: %d", key, value); } break; case DBUS_TYPE_ARRAY: { if (spa_streq(key, "AudioCodecs")) { DBusMessageIter array_i; const char *value; endpoint->air_codecs = 0; dbus_message_iter_recurse(&value_i, &array_i); while (dbus_message_iter_get_arg_type(&array_i) != DBUS_TYPE_INVALID) { dbus_message_iter_get_basic(&array_i, &value); if (spa_streq(value, HSPHFP_AIR_CODEC_CVSD)) endpoint->air_codecs |= HFP_AUDIO_CODEC_CVSD; if (spa_streq(value, HSPHFP_AIR_CODEC_MSBC)) endpoint->air_codecs |= HFP_AUDIO_CODEC_MSBC; dbus_message_iter_next(&array_i); } } } break; } dbus_message_iter_next(&element_i); } if (!endpoint->valid && endpoint->local_address && endpoint->remote_address && endpoint->profile && endpoint->role) endpoint->valid = true; if (!endpoint->remote_address || !endpoint->local_address) { spa_log_debug(backend->log, "Missing addresses for %s", endpoint->path); return DBUS_HANDLER_RESULT_HANDLED; } d = spa_bt_device_find_by_address(backend->monitor, endpoint->remote_address, endpoint->local_address); if (!d || !d->adapter) { spa_log_debug(backend->log, "No device for %s", endpoint->path); return DBUS_HANDLER_RESULT_HANDLED; } if ((t = spa_bt_transport_find(backend->monitor, endpoint->path)) != NULL) { /* Release transport on disconnection, or when mSBC is supported if there is an update of the remote codecs */ if (!endpoint->connected || (backend->msbc_supported && (endpoint->air_codecs & HFP_AUDIO_CODEC_MSBC) && t->media_codec->codec_id == HFP_AUDIO_CODEC_CVSD)) { spa_bt_transport_free(t); spa_bt_device_check_profiles(d, false); spa_log_debug(backend->log, "Transport released for %s", endpoint->path); } else { spa_log_debug(backend->log, "Transport already configured for %s", endpoint->path); return DBUS_HANDLER_RESULT_HANDLED; } } if (!endpoint->valid || !endpoint->connected) return DBUS_HANDLER_RESULT_HANDLED; if (backend->msbc_supported && (endpoint->air_codecs & HFP_AUDIO_CODEC_MSBC)) codec = spa_bt_get_hfp_codec(backend->monitor, HFP_AUDIO_CODEC_MSBC); else codec = spa_bt_get_hfp_codec(backend->monitor, HFP_AUDIO_CODEC_CVSD); if (!codec) { spa_log_error(backend->log, "cannot get codec for %s", endpoint->path); return DBUS_HANDLER_RESULT_HANDLED; } char *t_path = strdup(endpoint->path); t = spa_bt_transport_create(backend->monitor, t_path, sizeof(struct hsphfpd_transport_data)); if (t == NULL) { spa_log_warn(backend->log, "can't create transport: %m"); free(t_path); return DBUS_HANDLER_RESULT_NEED_MEMORY; } spa_bt_transport_set_implementation(t, &hsphfpd_transport_impl, t); t->device = d; spa_list_append(&t->device->transport_list, &t->device_link); t->backend = &backend->this; t->profile = SPA_BT_PROFILE_NULL; if (endpoint->profile == HSPHFPD_PROFILE_HEADSET) { if (endpoint->role == HSPHFPD_ROLE_CLIENT) t->profile = SPA_BT_PROFILE_HSP_HS; else if (endpoint->role == HSPHFPD_ROLE_GATEWAY) t->profile = SPA_BT_PROFILE_HSP_AG; } else if (endpoint->profile == HSPHFPD_PROFILE_HANDSFREE) { if (endpoint->role == HSPHFPD_ROLE_CLIENT) t->profile = SPA_BT_PROFILE_HFP_HF; else if (endpoint->role == HSPHFPD_ROLE_GATEWAY) t->profile = SPA_BT_PROFILE_HFP_AG; } t->media_codec = codec; t->n_channels = 1; t->channels[0] = SPA_AUDIO_CHANNEL_MONO; spa_bt_device_add_profile(d, t->profile); spa_bt_device_connect_profile(t->device, t->profile); spa_log_debug(backend->log, "Transport %s available for hsphfpd", endpoint->path); return DBUS_HANDLER_RESULT_HANDLED; } static DBusHandlerResult hsphfpd_parse_interfaces(struct impl *backend, DBusMessageIter *dict_i) { DBusMessageIter element_i; const char *path; spa_assert(backend); spa_assert(dict_i); dbus_message_iter_get_basic(dict_i, &path); dbus_message_iter_next(dict_i); dbus_message_iter_recurse(dict_i, &element_i); while (dbus_message_iter_get_arg_type(&element_i) == DBUS_TYPE_DICT_ENTRY) { DBusMessageIter iface_i; const char *interface; dbus_message_iter_recurse(&element_i, &iface_i); dbus_message_iter_get_basic(&iface_i, &interface); dbus_message_iter_next(&iface_i); if (spa_streq(interface, HSPHFPD_ENDPOINT_INTERFACE)) { struct hsphfpd_endpoint *endpoint; endpoint = endpoint_find(backend, path); if (!endpoint) { endpoint = calloc(1, sizeof(struct hsphfpd_endpoint)); endpoint->path = strdup(path); spa_list_append(&backend->endpoint_list, &endpoint->link); spa_log_debug(backend->log, "Found endpoint %s", path); } hsphfpd_parse_endpoint_properties(backend, endpoint, &iface_i); } else spa_log_debug(backend->log, "Unknown interface %s found, skipping", interface); dbus_message_iter_next(&element_i); } return DBUS_HANDLER_RESULT_HANDLED; } static void hsphfpd_get_endpoints_reply(DBusPendingCall *pending, void *user_data) { struct impl *backend = user_data; DBusMessageIter i, array_i; spa_autoptr(DBusMessage) r = steal_reply_and_unref(&pending); if (r == NULL) return; if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) { spa_log_error(backend->log, "Failed to get a list of endpoints from hsphfpd: %s", dbus_message_get_error_name(r)); return; } if (!spa_streq(dbus_message_get_sender(r), backend->hsphfpd_service_id)) { spa_log_error(backend->log, "Reply for GetManagedObjects() from invalid sender"); return; } if (!dbus_message_iter_init(r, &i) || !check_signature(r, "a{oa{sa{sv}}}")) { spa_log_error(backend->log, "Invalid arguments in GetManagedObjects() reply"); return; } dbus_message_iter_recurse(&i, &array_i); while (dbus_message_iter_get_arg_type(&array_i) != DBUS_TYPE_INVALID) { DBusMessageIter dict_i; dbus_message_iter_recurse(&array_i, &dict_i); hsphfpd_parse_interfaces(backend, &dict_i); dbus_message_iter_next(&array_i); } backend->endpoints_listed = true; } static int hsphfpd_register(struct impl *backend) { spa_autoptr(DBusMessage) m = NULL, r = NULL; const char *path = APPLICATION_OBJECT_MANAGER_PATH; spa_auto(DBusError) err = DBUS_ERROR_INIT; spa_log_debug(backend->log, "Registering to hsphfpd"); m = dbus_message_new_method_call(HSPHFPD_SERVICE, "/", HSPHFPD_APPLICATION_MANAGER_INTERFACE, "RegisterApplication"); if (m == NULL) return -ENOMEM; dbus_message_append_args(m, DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID); r = dbus_connection_send_with_reply_and_block(backend->conn, m, -1, &err); if (r == NULL) { if (dbus_error_has_name(&err, "org.freedesktop.DBus.Error.ServiceUnknown")) { spa_log_info(backend->log, "hsphfpd not available: %s", err.message); return -ENOTSUP; } else { spa_log_warn(backend->log, "Registering application %s failed: %s (%s)", path, err.message, err.name); return -EIO; } } if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) { spa_log_error(backend->log, "RegisterApplication() failed: %s", dbus_message_get_error_name(r)); return -EIO; } backend->hsphfpd_service_id = strdup(dbus_message_get_sender(r)); spa_log_debug(backend->log, "Registered to hsphfpd"); return 0; } static int hsphfpd_get_endpoints(struct impl *backend) { spa_autoptr(DBusMessage) m = NULL; m = dbus_message_new_method_call(HSPHFPD_SERVICE, "/", DBUS_INTERFACE_OBJECTMANAGER, "GetManagedObjects"); if (m == NULL) return -ENOMEM; if (!send_with_reply(backend->conn, m, hsphfpd_get_endpoints_reply, backend)) return -EIO; return 0; } static int backend_hsphfpd_register(void *data) { int ret = hsphfpd_register(data); if (ret < 0) return ret; ret = hsphfpd_get_endpoints(data); if (ret < 0) return ret; return 0; } static int backend_hsphfpd_unregistered(void *data) { struct impl *backend = data; struct hsphfpd_endpoint *endpoint; if (backend->hsphfpd_service_id) { free(backend->hsphfpd_service_id); backend->hsphfpd_service_id = NULL; } backend->endpoints_listed = false; spa_list_consume(endpoint, &backend->endpoint_list, link) endpoint_free(endpoint); return 0; } static DBusHandlerResult hsphfpd_filter_cb(DBusConnection *bus, DBusMessage *m, void *user_data) { const char *sender; struct impl *backend = user_data; sender = dbus_message_get_sender(m); if (backend->hsphfpd_service_id && spa_streq(sender, backend->hsphfpd_service_id)) { if (dbus_message_is_signal(m, DBUS_INTERFACE_OBJECTMANAGER, "InterfacesAdded")) { DBusMessageIter arg_i; spa_log_warn(backend->log, "sender: %s", dbus_message_get_sender(m)); if (!backend->endpoints_listed) goto finish; if (!dbus_message_iter_init(m, &arg_i) || !check_signature(m, "oa{sa{sv}}")) { spa_log_error(backend->log, "Invalid signature found in InterfacesAdded"); goto finish; } hsphfpd_parse_interfaces(backend, &arg_i); } else if (dbus_message_is_signal(m, DBUS_INTERFACE_OBJECTMANAGER, "InterfacesRemoved")) { const char *path; DBusMessageIter arg_i, element_i; if (!backend->endpoints_listed) goto finish; if (!dbus_message_iter_init(m, &arg_i) || !check_signature(m, "oas")) { spa_log_error(backend->log, "Invalid signature found in InterfacesRemoved"); goto finish; } dbus_message_iter_get_basic(&arg_i, &path); dbus_message_iter_next(&arg_i); dbus_message_iter_recurse(&arg_i, &element_i); while (dbus_message_iter_get_arg_type(&element_i) == DBUS_TYPE_STRING) { const char *iface; dbus_message_iter_get_basic(&element_i, &iface); if (spa_streq(iface, HSPHFPD_ENDPOINT_INTERFACE)) { struct hsphfpd_endpoint *endpoint; struct spa_bt_transport *transport = spa_bt_transport_find(backend->monitor, path); if (transport) spa_bt_transport_free(transport); spa_log_debug(backend->log, "Remove endpoint %s", path); endpoint = endpoint_find(backend, path); if (endpoint) endpoint_free(endpoint); } dbus_message_iter_next(&element_i); } } else if (dbus_message_is_signal(m, DBUS_INTERFACE_PROPERTIES, "PropertiesChanged")) { DBusMessageIter arg_i; const char *iface; const char *path; if (!backend->endpoints_listed) goto finish; if (!dbus_message_iter_init(m, &arg_i) || !check_signature(m, "sa{sv}as")) { spa_log_error(backend->log, "Invalid signature found in PropertiesChanged"); goto finish; } dbus_message_iter_get_basic(&arg_i, &iface); dbus_message_iter_next(&arg_i); path = dbus_message_get_path(m); if (spa_streq(iface, HSPHFPD_ENDPOINT_INTERFACE)) { struct hsphfpd_endpoint *endpoint = endpoint_find(backend, path); if (!endpoint) { spa_log_warn(backend->log, "Properties changed on unknown endpoint %s", path); goto finish; } spa_log_debug(backend->log, "Properties changed on endpoint %s", path); hsphfpd_parse_endpoint_properties(backend, endpoint, &arg_i); } else if (spa_streq(iface, HSPHFPD_AUDIO_TRANSPORT_INTERFACE)) { struct spa_bt_transport *transport = spa_bt_transport_find_full(backend->monitor, hsphfpd_cmp_transport_path, (const void *)path); if (!transport) { spa_log_warn(backend->log, "Properties changed on unknown transport %s", path); goto finish; } spa_log_debug(backend->log, "Properties changed on transport %s", path); hsphfpd_parse_transport_properties(backend, transport, &arg_i); } } } finish: return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } static int add_filters(void *data) { struct impl *backend = data; if (backend->filters_added) return 0; if (!dbus_connection_add_filter(backend->conn, hsphfpd_filter_cb, backend, NULL)) { spa_log_error(backend->log, "failed to add filter function"); return -EIO; } spa_auto(DBusError) err = DBUS_ERROR_INIT; dbus_bus_add_match(backend->conn, "type='signal',sender='" HSPHFPD_SERVICE "'," "interface='" DBUS_INTERFACE_OBJECTMANAGER "',member='InterfacesAdded'", &err); dbus_bus_add_match(backend->conn, "type='signal',sender='" HSPHFPD_SERVICE "'," "interface='" DBUS_INTERFACE_OBJECTMANAGER "',member='InterfacesRemoved'", &err); dbus_bus_add_match(backend->conn, "type='signal',sender='" HSPHFPD_SERVICE "'," "interface='" DBUS_INTERFACE_PROPERTIES "',member='PropertiesChanged'," "arg0='" HSPHFPD_ENDPOINT_INTERFACE "'", &err); dbus_bus_add_match(backend->conn, "type='signal',sender='" HSPHFPD_SERVICE "'," "interface='" DBUS_INTERFACE_PROPERTIES "',member='PropertiesChanged'," "arg0='" HSPHFPD_AUDIO_TRANSPORT_INTERFACE "'", &err); backend->filters_added = true; return 0; } static int backend_hsphfpd_free(void *data) { struct impl *backend = data; struct hsphfpd_endpoint *endpoint; if (backend->filters_added) { dbus_connection_remove_filter(backend->conn, hsphfpd_filter_cb, backend); backend->filters_added = false; } if (backend->msbc_supported) dbus_connection_unregister_object_path(backend->conn, HSPHFP_AUDIO_CLIENT_MSBC); dbus_connection_unregister_object_path(backend->conn, HSPHFP_AUDIO_CLIENT_PCM_S16LE_8KHZ); dbus_connection_unregister_object_path(backend->conn, APPLICATION_OBJECT_MANAGER_PATH); spa_list_consume(endpoint, &backend->endpoint_list, link) endpoint_free(endpoint); free(backend); return 0; } static int backend_hsphfpd_supports_codec(void *data, struct spa_bt_device *device, unsigned int codec) { struct impl *backend = data; switch (codec) { case HFP_AUDIO_CODEC_CVSD: return 1; case HFP_AUDIO_CODEC_MSBC: return backend->msbc_supported; } return 0; } static const struct spa_bt_backend_implementation backend_impl = { SPA_VERSION_BT_BACKEND_IMPLEMENTATION, .free = backend_hsphfpd_free, .register_profiles = backend_hsphfpd_register, .unregister_profiles = backend_hsphfpd_unregistered, .supports_codec = backend_hsphfpd_supports_codec, }; static bool is_available(struct impl *backend) { spa_autoptr(DBusMessage) m = NULL, r = NULL; spa_auto(DBusError) err = DBUS_ERROR_INIT; m = dbus_message_new_method_call(HSPHFPD_SERVICE, "/", DBUS_INTERFACE_INTROSPECTABLE, "Introspect"); if (m == NULL) return false; r = dbus_connection_send_with_reply_and_block(backend->conn, m, -1, &err); if (r && dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_METHOD_RETURN) return true; return false; } struct spa_bt_backend *backend_hsphfpd_new(struct spa_bt_monitor *monitor, void *dbus_connection, const struct spa_dict *info, const struct spa_bt_quirks *quirks, const struct spa_support *support, uint32_t n_support) { struct impl *backend; const char *str; static const DBusObjectPathVTable vtable_application_object_manager = { .message_function = application_object_manager_handler, }; static const DBusObjectPathVTable vtable_audio_agent_endpoint = { .message_function = audio_agent_endpoint_handler, }; backend = calloc(1, sizeof(struct impl)); if (backend == NULL) return NULL; spa_bt_backend_set_implementation(&backend->this, &backend_impl, backend); backend->this.name = "hsphfpd"; backend->this.exclusive = true; backend->monitor = monitor; backend->quirks = quirks; backend->log = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log); backend->dbus = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_DBus); backend->main_loop = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Loop); backend->conn = dbus_connection; if (info && (str = spa_dict_lookup(info, "bluez5.enable-msbc"))) backend->msbc_supported = spa_atob(str); else backend->msbc_supported = false; if (!spa_bt_get_hfp_codec(monitor, HFP_AUDIO_CODEC_MSBC)) backend->msbc_supported = false; spa_log_topic_init(backend->log, &log_topic); spa_list_init(&backend->endpoint_list); if (!dbus_connection_register_object_path(backend->conn, APPLICATION_OBJECT_MANAGER_PATH, &vtable_application_object_manager, backend)) { free(backend); return NULL; } if (!dbus_connection_register_object_path(backend->conn, HSPHFP_AUDIO_CLIENT_PCM_S16LE_8KHZ, &vtable_audio_agent_endpoint, backend)) { dbus_connection_unregister_object_path(backend->conn, APPLICATION_OBJECT_MANAGER_PATH); free(backend); return NULL; } if (backend->msbc_supported && !dbus_connection_register_object_path(backend->conn, HSPHFP_AUDIO_CLIENT_MSBC, &vtable_audio_agent_endpoint, backend)) { dbus_connection_unregister_object_path(backend->conn, HSPHFP_AUDIO_CLIENT_PCM_S16LE_8KHZ); dbus_connection_unregister_object_path(backend->conn, APPLICATION_OBJECT_MANAGER_PATH); free(backend); return NULL; } if (add_filters(backend) < 0) { dbus_connection_unregister_object_path(backend->conn, HSPHFP_AUDIO_CLIENT_MSBC); dbus_connection_unregister_object_path(backend->conn, HSPHFP_AUDIO_CLIENT_PCM_S16LE_8KHZ); dbus_connection_unregister_object_path(backend->conn, APPLICATION_OBJECT_MANAGER_PATH); free(backend); return NULL; } backend->this.available = is_available(backend); return &backend->this; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/bluez5/backend-native.c000066400000000000000000003651311511204443500272000ustar00rootroot00000000000000/* Spa HSP/HFP native backend */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-FileCopyrightText: Copyright © 2021 Collabora */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "defs.h" #include "media-codecs.h" #include "hfp-codec-caps.h" #ifdef HAVE_LIBUSB #include #endif #include "modemmanager.h" #include "upower.h" #include "telephony.h" SPA_LOG_TOPIC_DEFINE_STATIC(log_topic, "spa.bluez5.native"); #undef SPA_LOG_TOPIC_DEFAULT #define SPA_LOG_TOPIC_DEFAULT &log_topic #define PROP_KEY_ROLES "bluez5.roles" #define PROP_KEY_HEADSET_ROLES "bluez5.headset-roles" #define PROP_KEY_HFP_DISABLE_NREC "bluez5.hfp-hf.disable-nrec" #define PROP_KEY_HFP_DEFAULT_MIC_VOL "bluez5.hfp-hf.default-mic-volume" #define PROP_KEY_HFP_DEFAULT_SPEAKER_VOL "bluez5.hfp-hf.default-speaker-volume" #define HFP_CODEC_SWITCH_INITIAL_TIMEOUT_MSEC 5000 #define HFP_CODEC_SWITCH_TIMEOUT_MSEC 20000 #define INTERNATIONAL_NUMBER 145 #define NATIONAL_NUMBER 129 #define MAX_HF_INDICATORS 16 #define RFCOMM_MESSAGE_MAX_LENGTH 256 enum { HFP_AG_INITIAL_CODEC_SETUP_NONE = 0, HFP_AG_INITIAL_CODEC_SETUP_SEND, HFP_AG_INITIAL_CODEC_SETUP_WAIT }; #define CIND_INDICATORS "(\"service\",(0-1)),(\"call\",(0-1)),(\"callsetup\",(0-3)),(\"callheld\",(0-2)),(\"signal\",(0-5)),(\"roam\",(0-1)),(\"battchg\",(0-5))" enum { CIND_SERVICE = 1, CIND_CALL, CIND_CALLSETUP, CIND_CALLHELD, CIND_SIGNAL, CIND_ROAM, CIND_BATTERY_LEVEL, CIND_MAX }; struct modem { bool network_has_service; unsigned int signal_strength; bool network_is_roaming; char *operator_name; char *own_number; bool active_call; unsigned int call_setup; }; struct impl { struct spa_bt_backend this; struct spa_bt_monitor *monitor; struct spa_log *log; struct spa_loop *main_loop; struct spa_system *main_system; struct spa_loop_utils *loop_utils; struct spa_dbus *dbus; DBusConnection *conn; const struct media_codec * const * codecs; #define DEFAULT_ENABLED_PROFILES (SPA_BT_PROFILE_HFP_HF | SPA_BT_PROFILE_HFP_AG) enum spa_bt_profile enabled_profiles; bool hfp_disable_nrec; int hfp_default_mic_volume; int hfp_default_speaker_volume; struct spa_source sco; const struct spa_bt_quirks *quirks; struct spa_list rfcomm_list; unsigned int defer_setup_enabled:1; struct modem modem; unsigned int battery_level; void *modemmanager; struct spa_source *ring_timer; void *upower; struct spa_bt_telephony *telephony; }; struct transport_data { struct rfcomm *rfcomm; struct spa_source sco; int err; bool requesting; }; enum hfp_hf_state { hfp_hf_idle, hfp_hf_brsf, hfp_hf_bac, hfp_hf_cind1, hfp_hf_cind2, hfp_hf_cmer, hfp_hf_chld, hfp_hf_clip, hfp_hf_ccwa, hfp_hf_cmee, hfp_hf_nrec, hfp_hf_clcc, hfp_hf_vgs, hfp_hf_clcc_update, hfp_hf_chld1_hangup }; enum hsp_hs_state { hsp_hs_init1, hsp_hs_init2, hsp_hs_vgs, hsp_hs_vgm, }; struct rfcomm_volume { bool active; int hw_volume; }; struct rfcomm_call_data { struct rfcomm *rfcomm; struct spa_bt_telephony_call *call; }; struct rfcomm_cmd { struct spa_list link; int next_state; DBusMessage *msg; char cmd[RFCOMM_MESSAGE_MAX_LENGTH + 1]; }; struct codec_item { struct spa_list link; const struct media_codec *codec; }; struct updated_call { struct spa_list link; int id; }; struct rfcomm { struct spa_list link; struct spa_source source; struct impl *backend; struct spa_bt_device *device; struct spa_hook device_listener; struct spa_bt_transport *transport; struct spa_hook transport_listener; enum spa_bt_profile profile; struct spa_source timer; struct spa_source *volume_sync_timer; char* path; bool has_volume; struct rfcomm_volume volumes[SPA_BT_VOLUME_ID_TERM]; unsigned int broken_mic_hw_volume:1; unsigned int hfp_cmd_in_progress:1; union { enum hfp_hf_state hf_state; enum hsp_hs_state hs_state; int hf_or_hs_state; }; struct spa_list cmd_send_queue; // elements: struct rfcomm_cmd #ifdef HAVE_BLUEZ_5_BACKEND_HFP_NATIVE struct spa_list available_codec_list; struct spa_list supported_codec_list; unsigned int slc_configured:1; unsigned int codec_negotiation_supported:1; unsigned int hfp_ag_switching_codec:1; unsigned int hfp_ag_initial_codec_setup:2; unsigned int cind_call_active:1; unsigned int cind_call_notify:1; unsigned int extended_error_reporting:1; unsigned int clip_notify:1; unsigned int hfp_hf_3way:1; unsigned int hfp_hf_nrec:1; unsigned int hfp_hf_clcc:1; unsigned int hfp_hf_cme:1; unsigned int hfp_hf_in_progress:1; unsigned int chld_supported:1; unsigned int codec; uint32_t cind_enabled_indicators; char *hf_indicators[MAX_HF_INDICATORS]; struct spa_bt_telephony_ag *telephony_ag; struct spa_list updated_call_list; char *dialing_number; #endif }; static const struct media_codec *codec_list_get(struct impl *backend, struct spa_list *list, unsigned int codec_id) { struct codec_item *item; /* CVSD is always supported: not included in the list */ if (codec_id == HFP_AUDIO_CODEC_CVSD) return spa_bt_get_hfp_codec(backend->monitor, codec_id); spa_list_for_each(item, list, link) if (item->codec->codec_id == codec_id) return item->codec; return NULL; } static bool codec_list_add(struct spa_list *list, const struct media_codec *codec) { struct codec_item *item; if (codec->codec_id == HFP_AUDIO_CODEC_CVSD) return true; spa_list_for_each(item, list, link) if (item->codec == codec) return true; item = calloc(1, sizeof(*item)); if (!item) return false; item->codec = codec; spa_list_append(list, &item->link); return true; } static void codec_list_clear(struct spa_list *list) { struct codec_item *item; spa_list_consume(item, list, link) { spa_list_remove(&item->link); free(item); } } static const struct media_codec *codec_list_best(struct impl *backend, struct spa_list *list) { size_t i; /* Codec list is in 'best' order */ for (i = 0; backend->codecs[i]; ++i) { const struct media_codec *c = backend->codecs[i]; if (c->kind == MEDIA_CODEC_HFP && codec_list_get(backend, list, c->codec_id)) return c; } spa_assert_not_reached(); return NULL; } static DBusHandlerResult profile_release(DBusConnection *conn, DBusMessage *m, void *userdata) { if (!reply_with_error(conn, m, BLUEZ_PROFILE_INTERFACE ".Error.NotImplemented", "Method not implemented")) return DBUS_HANDLER_RESULT_NEED_MEMORY; return DBUS_HANDLER_RESULT_HANDLED; } static void transport_destroy(void *data) { struct rfcomm *rfcomm = data; struct impl *backend = rfcomm->backend; spa_log_debug(backend->log, "transport %p destroy", rfcomm->transport); rfcomm->transport = NULL; } static void transport_state_changed (void *data, enum spa_bt_transport_state old, enum spa_bt_transport_state state) { struct rfcomm *rfcomm = data; if (rfcomm->telephony_ag) { rfcomm->telephony_ag->transport.state = state; telephony_ag_transport_notify_updated_props(rfcomm->telephony_ag); } } static const struct spa_bt_transport_events transport_events = { SPA_VERSION_BT_TRANSPORT_EVENTS, .destroy = transport_destroy, .state_changed = transport_state_changed, }; static const struct spa_bt_transport_implementation sco_transport_impl; static int rfcomm_new_transport(struct rfcomm *rfcomm, int codec_id) { struct impl *backend = rfcomm->backend; const struct media_codec *codec; struct spa_bt_transport *t = NULL; struct transport_data *td; char* pathfd; if (rfcomm->transport) { spa_hook_remove(&rfcomm->transport_listener); spa_bt_transport_free(rfcomm->transport); rfcomm->transport = NULL; } codec = spa_bt_get_hfp_codec(backend->monitor, codec_id); if (!codec) { spa_log_warn(backend->log, "failed to get HFP codec %d", codec_id); goto fail; } if ((pathfd = spa_aprintf("%s/fd%d", rfcomm->path, rfcomm->source.fd)) == NULL) goto fail; t = spa_bt_transport_create(backend->monitor, pathfd, sizeof(struct transport_data)); if (t == NULL) { free(pathfd); goto fail; } spa_bt_transport_set_implementation(t, &sco_transport_impl, t); t->device = rfcomm->device; spa_list_append(&t->device->transport_list, &t->device_link); t->profile = rfcomm->profile; t->backend = &backend->this; t->n_channels = 1; t->channels[0] = SPA_AUDIO_CHANNEL_MONO; t->media_codec = codec; td = t->user_data; td->rfcomm = rfcomm; if (t->profile & SPA_BT_PROFILE_HEADSET_AUDIO_GATEWAY) { t->volumes[SPA_BT_VOLUME_ID_RX].volume = DEFAULT_AG_VOLUME; t->volumes[SPA_BT_VOLUME_ID_TX].volume = DEFAULT_AG_VOLUME; } else { t->volumes[SPA_BT_VOLUME_ID_RX].volume = DEFAULT_RX_VOLUME; t->volumes[SPA_BT_VOLUME_ID_TX].volume = DEFAULT_TX_VOLUME; } for (int i = 0; i < SPA_BT_VOLUME_ID_TERM ; ++i) { t->volumes[i].active = rfcomm->volumes[i].active; t->volumes[i].hw_volume_max = SPA_BT_VOLUME_HS_MAX; if (rfcomm->volumes[i].active && rfcomm->volumes[i].hw_volume != SPA_BT_VOLUME_INVALID) t->volumes[i].volume = (float) spa_bt_volume_hw_to_linear(rfcomm->volumes[i].hw_volume, t->volumes[i].hw_volume_max); } spa_bt_transport_add_listener(t, &rfcomm->transport_listener, &transport_events, rfcomm); if (rfcomm->telephony_ag) { rfcomm->telephony_ag->transport.codec = codec_id; rfcomm->telephony_ag->transport.state = SPA_BT_TRANSPORT_STATE_IDLE; telephony_ag_transport_notify_updated_props(rfcomm->telephony_ag); } rfcomm->transport = t; return 0; fail: spa_log_warn(backend->log, "failed to create transport"); return -ENOMEM; } static int codec_switch_stop_timer(struct rfcomm *rfcomm); static void volume_sync_stop_timer(struct rfcomm *rfcomm); static void rfcomm_free(struct rfcomm *rfcomm) { struct updated_call *updated_call; struct rfcomm_cmd *cmd; spa_list_consume(updated_call, &rfcomm->updated_call_list, link) { spa_list_remove(&updated_call->link); free(updated_call); } spa_list_consume(cmd, &rfcomm->cmd_send_queue, link) { if (cmd->msg) { telephony_send_dbus_method_reply(rfcomm->backend->telephony, cmd->msg, BT_TELEPHONY_ERROR_FAILED, 0); spa_clear_ptr(cmd->msg, dbus_message_unref); } spa_list_remove(&cmd->link); free(cmd); } codec_switch_stop_timer(rfcomm); if (rfcomm->telephony_ag) { telephony_ag_destroy(rfcomm->telephony_ag); rfcomm->telephony_ag = NULL; } for (int i = 0; i < MAX_HF_INDICATORS; i++) { if (rfcomm->hf_indicators[i]) { free(rfcomm->hf_indicators[i]); } } spa_list_remove(&rfcomm->link); if (rfcomm->path) free(rfcomm->path); if (rfcomm->transport) { spa_hook_remove(&rfcomm->transport_listener); spa_bt_transport_free(rfcomm->transport); } if (rfcomm->device) { spa_bt_device_report_battery_level(rfcomm->device, SPA_BT_NO_BATTERY); spa_hook_remove(&rfcomm->device_listener); rfcomm->device = NULL; } if (rfcomm->source.fd >= 0) { if (rfcomm->source.loop) spa_loop_remove_source(rfcomm->source.loop, &rfcomm->source); shutdown(rfcomm->source.fd, SHUT_RDWR); close (rfcomm->source.fd); rfcomm->source.fd = -1; } if (rfcomm->volume_sync_timer) spa_loop_utils_destroy_source(rfcomm->backend->loop_utils, rfcomm->volume_sync_timer); codec_list_clear(&rfcomm->available_codec_list); codec_list_clear(&rfcomm->supported_codec_list); free(rfcomm); } static void rfcomm_cmd_done(struct rfcomm *rfcomm, char *reply) { struct impl *backend = rfcomm->backend; if (SPA_LIKELY (!spa_list_is_empty(&rfcomm->cmd_send_queue))) { struct rfcomm_cmd *cmd = NULL; cmd = spa_list_first(&rfcomm->cmd_send_queue, struct rfcomm_cmd, link); spa_log_debug(backend->log, "%s -> %s", cmd->cmd, reply); if (cmd->msg) { enum spa_bt_telephony_error err = BT_TELEPHONY_ERROR_NONE; uint8_t cme_error = 0; if (spa_strstartswith(reply, "+CME ERROR:")) { cme_error = atoi(reply + strlen("+CME ERROR:")); err = BT_TELEPHONY_ERROR_CME; } else if (!spa_strstartswith(reply, "OK")) { err = BT_TELEPHONY_ERROR_FAILED; } telephony_send_dbus_method_reply(backend->telephony, cmd->msg, err, cme_error); spa_clear_ptr(cmd->msg, dbus_message_unref); } spa_list_remove(&cmd->link); free(cmd); } else { spa_log_warn(backend->log, "received response but no command was sent"); } rfcomm->hfp_cmd_in_progress = false; } static ssize_t rfcomm_send_next_cmd(struct rfcomm *rfcomm) { struct impl *backend = rfcomm->backend; struct rfcomm_cmd *cmd = NULL; char message[RFCOMM_MESSAGE_MAX_LENGTH + 1]; ssize_t len = 0; if (rfcomm->hfp_cmd_in_progress) return -EINPROGRESS; if (spa_list_is_empty(&rfcomm->cmd_send_queue)) return -ENODATA; cmd = spa_list_first(&rfcomm->cmd_send_queue, struct rfcomm_cmd, link); strncpy(message, cmd->cmd, RFCOMM_MESSAGE_MAX_LENGTH + 1); len = strlen(message); spa_log_debug(backend->log, "RFCOMM >> %s", message); /* * The format of an AT command from the HF to the AG shall be: * - HFP 1.8, 4.34.1 * * The format for a command from the HS to the AG is thus: AT= * - HSP 1.2, 4.8.1 */ message[len] = '\r'; /* `message` is no longer null-terminated */ len = write(rfcomm->source.fd, message, len + 1); /* we ignore any errors, it's not critical and real errors should * be caught with the HANGUP and ERROR events handled above */ if (len < 0) { len = -errno; spa_log_error(backend->log, "RFCOMM write error: %s", strerror(errno)); } rfcomm->hfp_cmd_in_progress = true; rfcomm->hf_or_hs_state = cmd->next_state; return len; } /* from HF/HS to AG */ SPA_PRINTF_FUNC(4, 5) static ssize_t rfcomm_send_cmd(struct rfcomm *rfcomm, int next_state, DBusMessage *m, const char *format, ...) { struct impl *backend = rfcomm->backend; spa_autofree struct rfcomm_cmd *cmd = NULL; ssize_t len; va_list args; cmd = calloc(1, sizeof(struct rfcomm_cmd)); va_start(args, format); len = vsnprintf(cmd->cmd, RFCOMM_MESSAGE_MAX_LENGTH + 1, format, args); va_end(args); if (len < 0) return -EINVAL; if (len > RFCOMM_MESSAGE_MAX_LENGTH) return -E2BIG; spa_log_debug(backend->log, "Queueing command: %s", cmd->cmd); cmd->next_state = next_state; cmd->msg = m ? dbus_message_ref(m) : NULL; spa_list_append(&rfcomm->cmd_send_queue, &cmd->link); cmd = NULL; return rfcomm_send_next_cmd(rfcomm); } /* from AG to HF/HS */ SPA_PRINTF_FUNC(2, 3) static ssize_t rfcomm_send_reply(const struct rfcomm *rfcomm, const char *format, ...) { struct impl *backend = rfcomm->backend; char message[RFCOMM_MESSAGE_MAX_LENGTH + 4]; ssize_t len; va_list args; va_start(args, format); len = vsnprintf(&message[2], RFCOMM_MESSAGE_MAX_LENGTH + 1, format, args); va_end(args); if (len < 0) return -EINVAL; if (len > RFCOMM_MESSAGE_MAX_LENGTH) return -E2BIG; spa_log_debug(backend->log, "RFCOMM >> %s", &message[2]); /* * The format of the OK code from the AG to the HF shall be: OK * The format of the generic ERROR code from the AG to the HF shall be: ERROR * The format of an unsolicited result code from the AG to the HF shall be: * - HFP 1.8, 4.34.1 * * If the command is processed successfully, the resulting response from the AG to the HS is: OK * If the command is not processed successfully, or is not recognized, * the resulting response from the AG to the HS is: ERROR * The format for an unsolicited result code (such as RING) from the AG to the HS is: * - HSP 1.2, 4.8.1 */ message[0] = '\r'; message[1] = '\n'; message[len + 2] = '\r'; message[len + 3] = '\n'; /* `message` is no longer null-terminated */ len = write(rfcomm->source.fd, message, len + 4); /* we ignore any errors, it's not critical and real errors should * be caught with the HANGUP and ERROR events handled above */ if (len < 0) { len = -errno; spa_log_error(backend->log, "RFCOMM write error: %s", strerror(errno)); } return len; } static void rfcomm_send_error(const struct rfcomm *rfcomm, enum cmee_error error) { if (rfcomm->extended_error_reporting) rfcomm_send_reply(rfcomm, "+CME ERROR: %d", error); else rfcomm_send_reply(rfcomm, "ERROR"); } static bool rfcomm_hw_volume_enabled(struct rfcomm *rfcomm) { return rfcomm->device != NULL && (rfcomm->device->hw_volume_profiles & rfcomm->profile); } static void rfcomm_emit_volume_changed(struct rfcomm *rfcomm, int id, int hw_volume) { struct spa_bt_transport_volume *t_volume; bool valid_volume = (id == SPA_BT_VOLUME_ID_RX || id == SPA_BT_VOLUME_ID_TX); if (valid_volume && hw_volume >= 0) { rfcomm->volumes[id].active = true; rfcomm->volumes[id].hw_volume = hw_volume; } spa_log_debug(rfcomm->backend->log, "volume changed %d", hw_volume); if (rfcomm_hw_volume_enabled(rfcomm)) { if (rfcomm->transport == NULL || !rfcomm->has_volume) return; for (int i = 0; i < SPA_BT_VOLUME_ID_TERM ; ++i) { t_volume = &rfcomm->transport->volumes[i]; t_volume->active = rfcomm->volumes[i].active; t_volume->volume = (float) spa_bt_volume_hw_to_linear(rfcomm->volumes[i].hw_volume, t_volume->hw_volume_max); } spa_bt_transport_emit_volume_changed(rfcomm->transport); } if (rfcomm->telephony_ag && valid_volume) { rfcomm->telephony_ag->volume[id] = hw_volume; telephony_ag_notify_updated_props(rfcomm->telephony_ag); } } #ifdef HAVE_BLUEZ_5_BACKEND_HSP_NATIVE static bool rfcomm_hsp_ag(struct rfcomm *rfcomm, char* buf) { struct impl *backend = rfcomm->backend; unsigned int gain; /* There are only three HSP AT commands: * AT+VGS=value: value between 0 and 15, sent by the HS to AG to set the speaker gain. * AT+VGM=value: value between 0 and 15, sent by the HS to AG to set the microphone gain. * AT+CKPD=200: Sent by HS when headset button is pressed. */ if (sscanf(buf, "AT+VGS=%d", &gain) == 1) { if (gain <= SPA_BT_VOLUME_HS_MAX) { rfcomm_emit_volume_changed(rfcomm, SPA_BT_VOLUME_ID_TX, gain); rfcomm_send_reply(rfcomm, "OK"); } else { spa_log_debug(backend->log, "RFCOMM receive unsupported VGS gain: %s", buf); rfcomm_send_reply(rfcomm, "ERROR"); } } else if (sscanf(buf, "AT+VGM=%d", &gain) == 1) { if (gain <= SPA_BT_VOLUME_HS_MAX) { if (!rfcomm->broken_mic_hw_volume) rfcomm_emit_volume_changed(rfcomm, SPA_BT_VOLUME_ID_RX, gain); rfcomm_send_reply(rfcomm, "OK"); } else { rfcomm_send_reply(rfcomm, "ERROR"); spa_log_debug(backend->log, "RFCOMM receive unsupported VGM gain: %s", buf); } } else if (spa_strstartswith(buf, "AT+CKPD=200") == 1) { rfcomm_send_reply(rfcomm, "OK"); spa_bt_device_emit_switch_profile(rfcomm->device); } else { return false; } return true; } static void rfcomm_send_volume_cmd(struct rfcomm *rfcomm, int next_state, DBusMessage *m, int id) { struct spa_bt_transport_volume *t_volume; const char *format; int hw_volume = rfcomm->volumes[id].hw_volume; if (rfcomm_hw_volume_enabled(rfcomm)) { t_volume = rfcomm->transport ? &rfcomm->transport->volumes[id] : NULL; if (t_volume && t_volume->active) { hw_volume = spa_bt_volume_linear_to_hw(t_volume->volume, t_volume->hw_volume_max); rfcomm->volumes[id].hw_volume = hw_volume; } } if (rfcomm->telephony_ag) { rfcomm->telephony_ag->volume[id] = hw_volume; telephony_ag_notify_updated_props(rfcomm->telephony_ag); } if (id == SPA_BT_VOLUME_ID_TX) format = "AT+VGM"; else if (id == SPA_BT_VOLUME_ID_RX) format = "AT+VGS"; else spa_assert_not_reached(); rfcomm_send_cmd(rfcomm, next_state, m, "%s=%d", format, hw_volume); } static bool rfcomm_hsp_hs(struct rfcomm *rfcomm, char* buf) { struct impl *backend = rfcomm->backend; unsigned int gain; /* There are only three HSP AT result codes: * +VGS=value: value between 0 and 15, sent by AG to HS as a response to an AT+VGS command * or when the gain is changed on the AG side. * +VGM=value: value between 0 and 15, sent by AG to HS as a response to an AT+VGM command * or when the gain is changed on the AG side. * RING: Sent by AG to HS to notify of an incoming call. It can safely be ignored because * it does not expect a reply. */ if (sscanf(buf, "+VGS=%d", &gain) == 1) { if (gain <= SPA_BT_VOLUME_HS_MAX) { rfcomm_emit_volume_changed(rfcomm, SPA_BT_VOLUME_ID_RX, gain); } else { spa_log_debug(backend->log, "RFCOMM receive unsupported VGS gain: %s", buf); } } else if (sscanf(buf, "+VGM=%d", &gain) == 1) { if (gain <= SPA_BT_VOLUME_HS_MAX) { rfcomm_emit_volume_changed(rfcomm, SPA_BT_VOLUME_ID_TX, gain); } else { spa_log_debug(backend->log, "RFCOMM receive unsupported VGM gain: %s", buf); } } else if (spa_streq(buf, "OK") || spa_streq(buf, "ERROR")) { rfcomm_cmd_done(rfcomm, buf); if (spa_streq(buf, "OK")) { if (rfcomm->hs_state == hsp_hs_init2) { rfcomm_send_volume_cmd(rfcomm, hsp_hs_vgs, NULL, SPA_BT_VOLUME_ID_RX); } else if (rfcomm->hs_state == hsp_hs_vgs) { rfcomm_send_volume_cmd(rfcomm, hsp_hs_vgm, NULL, SPA_BT_VOLUME_ID_TX); } } rfcomm_send_next_cmd(rfcomm); } return true; } #endif #ifdef HAVE_LIBUSB static bool check_usb_altsetting_6(struct impl *backend, uint16_t vendor_id, uint16_t product_id) { libusb_context *ctx = NULL; struct libusb_config_descriptor *cfg = NULL; libusb_device **devices = NULL; ssize_t ndev, idev; int res; bool ok = false; if ((res = libusb_init(&ctx)) < 0) { ctx = NULL; goto fail; } if ((ndev = libusb_get_device_list(ctx, &devices)) < 0) { res = ndev; devices = NULL; goto fail; } for (idev = 0; idev < ndev; ++idev) { libusb_device *dev = devices[idev]; struct libusb_device_descriptor desc; int icfg; libusb_get_device_descriptor(dev, &desc); if (vendor_id != desc.idVendor || product_id != desc.idProduct) continue; /* Check the device has Bluetooth isoch. altsetting 6 interface */ for (icfg = 0; icfg < desc.bNumConfigurations; ++icfg) { int iiface; if ((res = libusb_get_config_descriptor(dev, icfg, &cfg)) != 0) { cfg = NULL; goto fail; } for (iiface = 0; iiface < cfg->bNumInterfaces; ++iiface) { const struct libusb_interface *iface = &cfg->interface[iiface]; int ialt; for (ialt = 0; ialt < iface->num_altsetting; ++ialt) { const struct libusb_interface_descriptor *idesc = &iface->altsetting[ialt]; int iep; if (idesc->bInterfaceClass != LIBUSB_CLASS_WIRELESS || idesc->bInterfaceSubClass != 1 /* RF */ || idesc->bInterfaceProtocol != 1 /* Bluetooth */ || idesc->bAlternateSetting != 6) continue; for (iep = 0; iep < idesc->bNumEndpoints; ++iep) { const struct libusb_endpoint_descriptor *ep = &idesc->endpoint[iep]; if ((ep->bmAttributes & 0x3) == 0x1 /* isochronous */) { ok = true; goto done; } } } } libusb_free_config_descriptor(cfg); cfg = NULL; } } done: if (cfg) libusb_free_config_descriptor(cfg); if (devices) libusb_free_device_list(devices, true); if (ctx) libusb_exit(ctx); return ok; fail: spa_log_info(backend->log, "failed to acquire USB device info: %d (%s)", res, libusb_strerror(res)); ok = false; goto done; } #endif #ifdef HAVE_BLUEZ_5_BACKEND_HFP_NATIVE static bool device_supports_codec(struct impl *backend, struct spa_bt_device *device, enum spa_bluetooth_audio_codec codec) { int res; bool alt6_ok = true, alt1_ok = true; bool msbc_alt6_ok = true, msbc_alt1_ok = true; uint32_t bt_features; if (device->adapter == NULL) return false; if (backend->quirks && spa_bt_quirks_get_features(backend->quirks, device->adapter, device, &bt_features) == 0) { msbc_alt1_ok = (bt_features & (SPA_BT_FEATURE_MSBC_ALT1 | SPA_BT_FEATURE_MSBC_ALT1_RTL)); msbc_alt6_ok = (bt_features & SPA_BT_FEATURE_MSBC); } switch (codec) { case SPA_BLUETOOTH_AUDIO_CODEC_CVSD: return true; case SPA_BLUETOOTH_AUDIO_CODEC_MSBC: alt1_ok = msbc_alt1_ok; alt6_ok = msbc_alt6_ok; break; case SPA_BLUETOOTH_AUDIO_CODEC_LC3_SWB: default: /* LC3-SWB has same transport requirements as msbc. * However, ALT1/ALT5 modes don't appear to work, seem * to lose frame sync so output is garbled. */ alt1_ok = false; alt6_ok = msbc_alt6_ok; break; } spa_log_info(backend->log, "bluez-monitor/hardware.conf: alt6:%d alt1/5:%d", (int)alt6_ok, (int)alt1_ok); if (!alt6_ok && !alt1_ok) return false; res = spa_bt_adapter_has_msbc(device->adapter); if (res < 0) { spa_log_warn(backend->log, "adapter %s: failed to determine msbc/esco capability (%d)", device->adapter->path, res); } else if (res == 0) { spa_log_info(backend->log, "adapter %s: no msbc/esco transport", device->adapter->path); return false; } else { spa_log_debug(backend->log, "adapter %s: has msbc/esco transport", device->adapter->path); } /* Check if USB ALT6 is really available on the device */ if (device->adapter->bus_type == BUS_TYPE_USB && !alt1_ok && alt6_ok) { #ifdef HAVE_LIBUSB if (device->adapter->source_id == SOURCE_ID_USB) { alt6_ok = check_usb_altsetting_6(backend, device->adapter->vendor_id, device->adapter->product_id); } else { alt6_ok = false; } if (!alt6_ok) spa_log_info(backend->log, "bluetooth host adapter does not support USB ALT6"); #else spa_log_info(backend->log, "compiled without libusb; can't check if bluetooth adapter has USB ALT6, assuming no"); alt6_ok = false; #endif } if (device->adapter->bus_type != BUS_TYPE_USB) alt1_ok = false; return alt6_ok || alt1_ok; } static void make_available_codec_list(struct impl *backend, struct spa_bt_device *device, struct spa_list *codec_list) { size_t i; codec_list_clear(codec_list); for (i = 0; backend->codecs[i]; ++i) { const struct media_codec *codec = backend->codecs[i]; if (codec->kind != MEDIA_CODEC_HFP) continue; if (device_supports_codec(backend, device, codec->id)) codec_list_add(codec_list, codec); } } static int codec_switch_start_timer(struct rfcomm *rfcomm, int timeout_msec); static void process_xevent_indicator(struct rfcomm *rfcomm, unsigned int level, unsigned int nlevels) { struct impl *backend = rfcomm->backend; uint8_t perc; spa_log_debug(backend->log, "AT+XEVENT level:%u nlevels:%u", level, nlevels); if (nlevels <= 1) return; /* 0 <= level < nlevels */ perc = SPA_MIN(level, nlevels - 1) * 100 / (nlevels - 1); spa_bt_device_report_battery_level(rfcomm->device, perc); } static void process_iphoneaccev_indicator(struct rfcomm *rfcomm, unsigned int key, unsigned int value) { struct impl *backend = rfcomm->backend; spa_log_debug(backend->log, "key:%u value:%u", key, value); switch (key) { case SPA_BT_HFP_HF_IPHONEACCEV_KEY_BATTERY_LEVEL: { // Battery level is reported in range of 0-9, convert to 10-100% uint8_t level = (SPA_CLAMP(value, 0u, 9u) + 1) * 10; spa_log_debug(backend->log, "battery level: %d%%", (int) level); // TODO: report without Battery Provider (using props) spa_bt_device_report_battery_level(rfcomm->device, level); break; } case SPA_BT_HFP_HF_IPHONEACCEV_KEY_DOCK_STATE: break; default: spa_log_warn(backend->log, "unknown AT+IPHONEACCEV key:%u value:%u", key, value); break; } } static void process_hfp_hf_indicator(struct rfcomm *rfcomm, unsigned int indicator, unsigned int value) { struct impl *backend = rfcomm->backend; spa_log_debug(backend->log, "indicator:%u value:%u", indicator, value); switch (indicator) { case SPA_BT_HFP_HF_INDICATOR_ENHANCED_SAFETY: rfcomm_send_error(rfcomm, CMEE_AG_FAILURE); break; case SPA_BT_HFP_HF_INDICATOR_BATTERY_LEVEL: // Battery level is reported in range 0-100 spa_log_debug(backend->log, "battery level: %u%%", value); if (value <= 100) { // TODO: report without Battery Provider (using props) spa_bt_device_report_battery_level(rfcomm->device, value); rfcomm_send_reply(rfcomm, "OK"); } else { spa_log_warn(backend->log, "battery HF indicator %u outside of range [0, 100]: %u", indicator, value); rfcomm_send_error(rfcomm, CMEE_AG_FAILURE); } break; default: spa_log_warn(backend->log, "unknown HF indicator:%u value:%u", indicator, value); rfcomm_send_error(rfcomm, CMEE_AG_FAILURE); break; } } static void rfcomm_hfp_ag_set_cind(struct rfcomm *rfcomm, bool call_active) { if (rfcomm->profile != SPA_BT_PROFILE_HFP_HF) return; if (call_active == rfcomm->cind_call_active) return; rfcomm->cind_call_active = call_active; if (!rfcomm->cind_call_notify) return; rfcomm_send_reply(rfcomm, "+CIEV: 2,%d", rfcomm->cind_call_active); } static bool rfcomm_hfp_ag(struct rfcomm *rfcomm, char* buf) { struct impl *backend = rfcomm->backend; unsigned int features; unsigned int gain; unsigned int count, r; unsigned int selected_codec; unsigned int indicator; unsigned int indicator_value; unsigned int value; unsigned int xevent_level; unsigned int xevent_nlevels; int xapl_vendor; int xapl_product; int xapl_features; if (sscanf(buf, "AT+BRSF=%u", &features) == 1) { unsigned int ag_features = SPA_BT_HFP_AG_FEATURE_NONE; /* * Determine device volume control. Some headsets only support control of * TX volume, but not RX, even if they have a microphone. Determine this * separately based on whether we also get AT+VGS/AT+VGM, and quirks. */ rfcomm->has_volume = (features & SPA_BT_HFP_HF_FEATURE_REMOTE_VOLUME_CONTROL); /* Decide if we want to signal that the computer supports codec negotiation This should be done when the computers bluetooth adapter supports the necessary transport mode */ if (!spa_list_is_empty(&rfcomm->available_codec_list)) { /* set the feature bit that indicates AG (=computer) supports codec negotiation */ ag_features |= SPA_BT_HFP_AG_FEATURE_CODEC_NEGOTIATION; /* let's see if the headset supports codec negotiation */ if ((features & (SPA_BT_HFP_HF_FEATURE_CODEC_NEGOTIATION)) != 0) { spa_log_debug(backend->log, "RFCOMM features = %i, codec negotiation supported by headset", features); /* Prepare reply: Audio Gateway (=computer) supports codec negotiation */ rfcomm->codec_negotiation_supported = true; } else { /* Codec negotiation not supported */ spa_log_debug(backend->log, "RFCOMM features = %i, codec negotiation NOT supported by headset", features); rfcomm->codec_negotiation_supported = false; } } /* send reply to HF with the features supported by Audio Gateway (=computer) */ ag_features |= mm_supported_features(); ag_features |= SPA_BT_HFP_AG_FEATURE_HF_INDICATORS | SPA_BT_HFP_AG_FEATURE_ESCO_S4; rfcomm_send_reply(rfcomm, "+BRSF: %u", ag_features); rfcomm_send_reply(rfcomm, "OK"); } else if (spa_strstartswith(buf, "AT+BAC=")) { /* retrieve supported codecs */ /* response has the form AT+BAC=,, strategy: split the string into tokens */ char* token; int cntr = 0; codec_list_clear(&rfcomm->supported_codec_list); while ((token = strsep(&buf, "=,"))) { unsigned int codec_id; /* skip token 0 i.e. the "AT+BAC=" part */ if (cntr > 0 && sscanf(token, "%u", &codec_id) == 1) { const struct media_codec *codec; spa_log_debug(backend->log, "RFCOMM AT+BAC found codec %u", codec_id); codec = codec_list_get(backend, &rfcomm->available_codec_list, codec_id); if (codec) { spa_log_debug(backend->log, "RFCOMM AT+BAC codec %s supported", codec->description); codec_list_add(&rfcomm->supported_codec_list, codec); } else { spa_log_debug(backend->log, "RFCOMM AT+BAC codec %u not supported", codec_id); } } cntr++; } rfcomm_send_reply(rfcomm, "OK"); } else if (spa_strstartswith(buf, "AT+CIND=?")) { rfcomm_send_reply(rfcomm, "+CIND:%s", CIND_INDICATORS); rfcomm_send_reply(rfcomm, "OK"); } else if (spa_strstartswith(buf, "AT+CIND?")) { rfcomm_send_reply(rfcomm, "+CIND: %d,%d,%d,0,%d,%d,%d", backend->modem.network_has_service, backend->modem.active_call, backend->modem.call_setup, backend->modem.signal_strength, backend->modem.network_is_roaming, backend->battery_level); rfcomm_send_reply(rfcomm, "OK"); } else if (spa_strstartswith(buf, "AT+CMER")) { const struct media_codec *best_codec = codec_list_best(backend, &rfcomm->supported_codec_list); int mode, keyp, disp, ind; rfcomm->slc_configured = true; rfcomm_send_reply(rfcomm, "OK"); rfcomm->cind_call_active = false; if (sscanf(buf, "AT+CMER= %d , %d , %d , %d", &mode, &keyp, &disp, &ind) == 4) rfcomm->cind_call_notify = ind ? true : false; else rfcomm->cind_call_notify = false; /* switch to better codec by sending unsolicited +BCS message */ if (rfcomm->codec_negotiation_supported && best_codec && best_codec->id != SPA_BLUETOOTH_AUDIO_CODEC_CVSD) { spa_log_debug(backend->log, "RFCOMM initial codec setup"); rfcomm->hfp_ag_initial_codec_setup = HFP_AG_INITIAL_CODEC_SETUP_SEND; rfcomm_send_reply(rfcomm, "+BCS: %u", best_codec->codec_id); codec_switch_start_timer(rfcomm, HFP_CODEC_SWITCH_INITIAL_TIMEOUT_MSEC); } else { if (rfcomm_new_transport(rfcomm, HFP_AUDIO_CODEC_CVSD) < 0) { // TODO: We should manage the missing transport } else { spa_bt_device_connect_profile(rfcomm->device, rfcomm->profile); rfcomm_emit_volume_changed(rfcomm, -1, SPA_BT_VOLUME_INVALID); } } } else if (spa_streq(buf, "")) { /* No commands, reply OK (ITU-T Rec. V.250 Sec. 5.2.1 & 5.6) */ rfcomm_send_reply(rfcomm, "OK"); } else if (!rfcomm->slc_configured) { spa_log_warn(backend->log, "RFCOMM receive command before SLC completed: %s", buf); rfcomm_send_error(rfcomm, CMEE_AG_FAILURE); return true; /* ***** * Following commands requires a Service Level Connection * ***** */ } else if (sscanf(buf, "AT+BCS=%u", &selected_codec) == 1) { /* parse BCS(=Bluetooth Codec Selection) reply */ bool was_switching_codec = rfcomm->hfp_ag_switching_codec && (rfcomm->device != NULL); const struct media_codec *codec = codec_list_get(backend, &rfcomm->supported_codec_list, selected_codec); rfcomm->hfp_ag_switching_codec = false; rfcomm->hfp_ag_initial_codec_setup = HFP_AG_INITIAL_CODEC_SETUP_NONE; codec_switch_stop_timer(rfcomm); volume_sync_stop_timer(rfcomm); if (!codec) { spa_log_warn(backend->log, "unsupported codec negotiation: %d", selected_codec); rfcomm_send_error(rfcomm, CMEE_AG_FAILURE); if (was_switching_codec) spa_bt_device_emit_codec_switched(rfcomm->device, -EIO); return true; } rfcomm->codec = selected_codec; spa_log_debug(backend->log, "RFCOMM selected_codec = %i", selected_codec); /* Recreate transport, since previous connection may now be invalid */ if (rfcomm_new_transport(rfcomm, selected_codec) < 0) { // TODO: We should manage the missing transport rfcomm_send_error(rfcomm, CMEE_AG_FAILURE); if (was_switching_codec) spa_bt_device_emit_codec_switched(rfcomm->device, -ENOMEM); return true; } spa_bt_device_connect_profile(rfcomm->device, rfcomm->profile); rfcomm_emit_volume_changed(rfcomm, -1, SPA_BT_VOLUME_INVALID); rfcomm_send_reply(rfcomm, "OK"); if (was_switching_codec) spa_bt_device_emit_codec_switched(rfcomm->device, 0); } else if (spa_strstartswith(buf, "AT+BCC")) { if (!rfcomm->codec_negotiation_supported) return false; rfcomm_send_reply(rfcomm, "OK"); rfcomm_send_reply(rfcomm, "+BCS: %u", rfcomm->codec); rfcomm->hfp_ag_switching_codec = true; rfcomm->hfp_ag_initial_codec_setup = HFP_AG_INITIAL_CODEC_SETUP_NONE; codec_switch_start_timer(rfcomm, HFP_CODEC_SWITCH_TIMEOUT_MSEC); } else if (spa_strstartswith(buf, "AT+BIA=")) { /* retrieve indicators activation * form: AT+BIA=[indrep1],[indrep2],[indrepx] */ char *str = buf + 7; unsigned int ind = 1; while (*str && ind < CIND_MAX && *str != '\r' && *str != '\n') { if (*str == ',') { ind++; goto next_indicator; } /* Ignore updates to mandantory indicators which are always ON */ if (ind == CIND_CALL || ind == CIND_CALLSETUP || ind == CIND_CALLHELD) goto next_indicator; switch (*str) { case '0': rfcomm->cind_enabled_indicators &= ~(1 << ind); break; case '1': rfcomm->cind_enabled_indicators |= (1 << ind); break; default: spa_log_warn(backend->log, "Unsupported entry in %s: %c", buf, *str); } next_indicator: str++; } rfcomm_send_reply(rfcomm, "OK"); } else if (spa_strstartswith(buf, "AT+CCWA=")) { /* * Claim that call waiting notifications are supported. * Required for some devices (e.g. Soundcore Motion 300), * as they stop sending commands if the reply to CCWA is not OK. */ rfcomm_send_reply(rfcomm, "OK"); } else if (spa_strstartswith(buf, "AT+CLCC")) { struct spa_list *calls; struct call *call; unsigned int type; if (backend->modemmanager) { calls = mm_get_calls(backend->modemmanager); spa_list_for_each(call, calls, link) { if (!call->number) { rfcomm_send_reply(rfcomm, "+CLCC: %u,%u,%u,0,%u", call->index, call->direction, call->state, call->multiparty); } else { if (spa_strstartswith(call->number, "+")) type = INTERNATIONAL_NUMBER; else type = NATIONAL_NUMBER; rfcomm_send_reply(rfcomm, "+CLCC: %u,%u,%u,0,%u,\"%s\",%d", call->index, call->direction, call->state, call->multiparty, call->number, type); } } } rfcomm_send_reply(rfcomm, "OK"); } else if (sscanf(buf, "AT+CLIP=%u", &value) == 1) { if (value > 1) { spa_log_debug(backend->log, "Unsupported AT+CLIP value: %u", value); rfcomm_send_error(rfcomm, CMEE_AG_FAILURE); return true; } rfcomm->clip_notify = value; rfcomm_send_reply(rfcomm, "OK"); } else if (sscanf(buf, "AT+CMEE=%u", &value) == 1) { if (value > 1) { spa_log_debug(backend->log, "Unsupported AT+CMEE value: %u", value); rfcomm_send_error(rfcomm, CMEE_AG_FAILURE); return true; } rfcomm->extended_error_reporting = value; rfcomm_send_reply(rfcomm, "OK"); } else if (spa_strstartswith(buf, "AT+CNUM")) { if (backend->modem.own_number) { unsigned int type; if (spa_strstartswith(backend->modem.own_number, "+")) type = INTERNATIONAL_NUMBER; else type = NATIONAL_NUMBER; rfcomm_send_reply(rfcomm, "+CNUM: ,\"%s\",%u,,4", backend->modem.own_number, type); } rfcomm_send_reply(rfcomm, "OK"); } else if (spa_strstartswith(buf, "AT+COPS=")) { unsigned int mode, val; if (sscanf(buf, "AT+COPS=%u,%u", &mode, &val) != 2 || mode != 3 || val != 0) { rfcomm_send_error(rfcomm, CMEE_AG_FAILURE); } else { rfcomm_send_reply(rfcomm, "OK"); } } else if (spa_strstartswith(buf, "AT+COPS?")) { if (!backend->modem.network_has_service) { rfcomm_send_error(rfcomm, CMEE_NO_NETWORK_SERVICE); } else { if (backend->modem.operator_name) rfcomm_send_reply(rfcomm, "+COPS: 0,0,\"%s\"", backend->modem.operator_name); else rfcomm_send_reply(rfcomm, "+COPS: 0,,"); rfcomm_send_reply(rfcomm, "OK"); } } else if (sscanf(buf, "AT+VGM=%u", &gain) == 1) { if (gain <= SPA_BT_VOLUME_HS_MAX) { if (!rfcomm->broken_mic_hw_volume) rfcomm_emit_volume_changed(rfcomm, SPA_BT_VOLUME_ID_RX, gain); rfcomm_send_reply(rfcomm, "OK"); } else { spa_log_debug(backend->log, "RFCOMM receive unsupported VGM gain: %s", buf); rfcomm_send_error(rfcomm, CMEE_OPERATION_NOT_ALLOWED); } } else if (sscanf(buf, "AT+VGS=%u", &gain) == 1) { if (gain <= SPA_BT_VOLUME_HS_MAX) { rfcomm_emit_volume_changed(rfcomm, SPA_BT_VOLUME_ID_TX, gain); rfcomm_send_reply(rfcomm, "OK"); } else { spa_log_debug(backend->log, "RFCOMM receive unsupported VGS gain: %s", buf); rfcomm_send_error(rfcomm, CMEE_OPERATION_NOT_ALLOWED); } } else if (spa_strstartswith(buf, "AT+BIND=?")) { rfcomm_send_reply(rfcomm, "+BIND: (2)"); rfcomm_send_reply(rfcomm, "OK"); } else if (spa_strstartswith(buf, "AT+BIND?")) { rfcomm_send_reply(rfcomm, "+BIND: 1,0"); rfcomm_send_reply(rfcomm, "+BIND: 2,1"); rfcomm_send_reply(rfcomm, "OK"); } else if (spa_strstartswith(buf, "AT+BIND=")) { // BIND=... should return a comma separated list of indicators and // 2 should be among the other numbers telling that battery charge // is supported rfcomm_send_reply(rfcomm, "OK"); } else if (sscanf(buf, "AT+BIEV=%u,%u", &indicator, &indicator_value) == 2) { process_hfp_hf_indicator(rfcomm, indicator, indicator_value); } else if (sscanf(buf, "AT+XAPL=%04x-%04x-%*[^,],%u", &xapl_vendor, &xapl_product, &xapl_features) == 3) { if (xapl_features & SPA_BT_HFP_HF_XAPL_FEATURE_BATTERY_REPORTING) { /* claim, that we support battery status reports */ rfcomm_send_reply(rfcomm, "+XAPL=iPhone,%u", SPA_BT_HFP_HF_XAPL_FEATURE_BATTERY_REPORTING); } rfcomm_send_reply(rfcomm, "OK"); } else if (spa_strstartswith(buf, "AT+XEVENT=USER-AGENT")) { rfcomm_send_reply(rfcomm, "OK"); } else if (sscanf(buf, "AT+XEVENT=BATTERY,%u,%u,%*u,%*u", &xevent_level, &xevent_nlevels) == 2) { process_xevent_indicator(rfcomm, xevent_level, xevent_nlevels); rfcomm_send_reply(rfcomm, "OK"); } else if (sscanf(buf, "AT+XEVENT=BATTERY,%u", &xevent_level) == 1) { process_xevent_indicator(rfcomm, xevent_level + 1, 11); rfcomm_send_reply(rfcomm, "OK"); } else if (sscanf(buf, "AT+IPHONEACCEV=%u%n", &count, &r) == 1) { if (count < 1 || count > 100) return false; buf += r; for (unsigned int i = 0; i < count; i++) { unsigned int key, value; if (sscanf(buf, " , %u , %u%n", &key, &value, &r) != 2) return false; process_iphoneaccev_indicator(rfcomm, key, value); buf += r; } rfcomm_send_reply(rfcomm, "OK"); } else if (spa_strstartswith(buf, "AT+APLSIRI?")) { // This command is sent when we activate Apple extensions rfcomm_send_reply(rfcomm, "OK"); } else if (!mm_is_available(backend->modemmanager)) { spa_log_info(backend->log, "RFCOMM receive command but modem not available: %s", buf); rfcomm_send_error(rfcomm, CMEE_NO_CONNECTION_TO_PHONE); return true; /* ***** * Following commands requires a Service Level Connection * and access to a modem * ***** */ } else if (!backend->modem.network_has_service) { spa_log_warn(backend->log, "RFCOMM receive command but network not available: %s", buf); rfcomm_send_error(rfcomm, CMEE_NO_NETWORK_SERVICE); return true; /* ***** * Following commands requires a Service Level Connection, * access to a modem and to the network * ***** */ } else if (spa_strstartswith(buf, "ATA")) { enum cmee_error error; if (!mm_answer_call(backend->modemmanager, rfcomm, &error)) { rfcomm_send_error(rfcomm, error); return true; } } else if (spa_strstartswith(buf, "ATD")) { char number[31], sep; enum cmee_error error; if (sscanf(buf, "ATD%30[^;]%c", number, &sep) != 2 || sep != ';') { spa_log_debug(backend->log, "Failed to parse ATD: \"%s\"", buf); rfcomm_send_error(rfcomm, CMEE_AG_FAILURE); return true; } if (!mm_do_call(backend->modemmanager, number, rfcomm, &error)) { rfcomm_send_error(rfcomm, error); return true; } } else if (spa_strstartswith(buf, "AT+CHUP")) { enum cmee_error error; if (!mm_hangup_call(backend->modemmanager, rfcomm, &error)) { rfcomm_send_error(rfcomm, error); return true; } } else if (spa_strstartswith(buf, "AT+VTS=")) { char dtmf[2]; enum cmee_error error; if (sscanf(buf, "AT+VTS=%1s", dtmf) != 1) { spa_log_debug(backend->log, "Failed to parse AT+VTS: \"%s\"", buf); rfcomm_send_error(rfcomm, CMEE_AG_FAILURE); return true; } if (!mm_send_dtmf(backend->modemmanager, dtmf, rfcomm, &error)) { rfcomm_send_error(rfcomm, error); return true; } } else { return false; } return true; } static void hfp_hf_answer(void *data, DBusMessage *m) { struct rfcomm_call_data *call_data = data; struct rfcomm *rfcomm = call_data->rfcomm; struct impl *backend = rfcomm->backend; if (call_data->call->state != CALL_STATE_INCOMING) { telephony_send_dbus_method_reply(backend->telephony, m, BT_TELEPHONY_ERROR_INVALID_STATE, 0); return; } rfcomm_send_cmd(rfcomm, hfp_hf_idle, m, "ATA"); } static void hfp_hf_hangup(void *data, DBusMessage *m) { struct rfcomm_call_data *call_data = data; struct rfcomm *rfcomm = call_data->rfcomm; struct impl *backend = rfcomm->backend; enum spa_bt_telephony_error err = BT_TELEPHONY_ERROR_NONE; struct spa_bt_telephony_call *call; bool found_held = false; spa_list_for_each(call, &rfcomm->telephony_ag->call_list, link) { if (call->state == CALL_STATE_HELD) found_held = true; } switch (call_data->call->state) { case CALL_STATE_ACTIVE: case CALL_STATE_DIALING: case CALL_STATE_ALERTING: case CALL_STATE_INCOMING: if (found_held) { if (!rfcomm->chld_supported) { err = BT_TELEPHONY_ERROR_NOT_SUPPORTED; goto error; } else if (rfcomm->hfp_hf_in_progress) { err = BT_TELEPHONY_ERROR_IN_PROGRESS; goto error; } rfcomm_send_cmd(rfcomm, hfp_hf_chld1_hangup, m, "AT+CHLD=1"); rfcomm->hfp_hf_in_progress = true; } else { rfcomm_send_cmd(rfcomm, hfp_hf_idle, m, "AT+CHUP"); } break; case CALL_STATE_HELD: case CALL_STATE_WAITING: if (rfcomm->hfp_hf_in_progress) { err = BT_TELEPHONY_ERROR_IN_PROGRESS; goto error; } rfcomm_send_cmd(rfcomm, hfp_hf_idle, m, "AT+CHLD=0"); rfcomm->hfp_hf_in_progress = true; break; default: spa_log_warn(backend->log, "Call in invalid state: skip hangup"); err = BT_TELEPHONY_ERROR_INVALID_STATE; goto error; } return; error: telephony_send_dbus_method_reply(backend->telephony, m, err, 0); } static const struct spa_bt_telephony_call_callbacks telephony_call_callbacks = { SPA_VERSION_BT_TELEPHONY_AG_CALLBACKS, .answer = hfp_hf_answer, .hangup = hfp_hf_hangup, }; static struct spa_bt_telephony_call *hfp_hf_add_call(struct rfcomm *rfcomm, struct spa_bt_telephony_ag *ag, enum spa_bt_telephony_call_state state, const char *number) { struct spa_bt_telephony_call *call; struct rfcomm_call_data *data; call = telephony_call_new(ag, sizeof(*data)); if (!call) return NULL; call->state = state; if (number) call->line_identification = strdup(number); data = telephony_call_get_user_data(call); data->rfcomm = rfcomm; data->call = call; telephony_call_set_callbacks(call, &telephony_call_callbacks, data); telephony_call_register(call); return call; } static void hfp_hf_dial(void *data, const char *number, DBusMessage *m) { struct rfcomm *rfcomm = data; struct impl *backend = rfcomm->backend; /* store the number in case we need to create the Call object via CIND notifications (if CLCC is not supported) */ free(spa_exchange(rfcomm->dialing_number, strdup(number))); spa_log_info(backend->log, "Dialing: \"%s\"", number); rfcomm_send_cmd(rfcomm, hfp_hf_idle, m, "ATD%s;", number); } static void hfp_hf_swap_calls(void *data, DBusMessage *m) { struct rfcomm *rfcomm = data; struct impl *backend = rfcomm->backend; enum spa_bt_telephony_error err = BT_TELEPHONY_ERROR_NONE; struct spa_bt_telephony_call *call; bool found_held = false; if (!rfcomm->chld_supported) { err = BT_TELEPHONY_ERROR_NOT_SUPPORTED; goto error; } else if (rfcomm->hfp_hf_in_progress) { err = BT_TELEPHONY_ERROR_IN_PROGRESS; goto error; } spa_list_for_each(call, &rfcomm->telephony_ag->call_list, link) { if (call->state == CALL_STATE_HELD) { found_held = true; break; } } if (!found_held) { spa_log_debug(backend->log, "no held calls"); err = BT_TELEPHONY_ERROR_INVALID_STATE; goto error; } rfcomm_send_cmd(rfcomm, hfp_hf_idle, m, "AT+CHLD=2"); rfcomm->hfp_hf_in_progress = true; return; error: telephony_send_dbus_method_reply(backend->telephony, m, err, 0); } static void hfp_hf_release_and_answer(void *data, DBusMessage *m) { struct rfcomm *rfcomm = data; struct impl *backend = rfcomm->backend; enum spa_bt_telephony_error err = BT_TELEPHONY_ERROR_NONE; struct spa_bt_telephony_call *call; bool found_active = false; bool found_waiting = false; if (!rfcomm->chld_supported) { err = BT_TELEPHONY_ERROR_NOT_SUPPORTED; goto error; } else if (rfcomm->hfp_hf_in_progress) { err = BT_TELEPHONY_ERROR_IN_PROGRESS; goto error; } spa_list_for_each(call, &rfcomm->telephony_ag->call_list, link) { if (call->state == CALL_STATE_ACTIVE) found_active = true; else if (call->state == CALL_STATE_WAITING) found_waiting = true; } if (!found_active || !found_waiting) { spa_log_debug(backend->log, "no active and waiting calls"); err = BT_TELEPHONY_ERROR_INVALID_STATE; goto error; } rfcomm_send_cmd(rfcomm, hfp_hf_idle, m, "AT+CHLD=1"); rfcomm->hfp_hf_in_progress = true; return; error: telephony_send_dbus_method_reply(backend->telephony, m, err, 0); } static void hfp_hf_release_and_swap(void *data, DBusMessage *m) { struct rfcomm *rfcomm = data; struct impl *backend = rfcomm->backend; enum spa_bt_telephony_error err = BT_TELEPHONY_ERROR_NONE; struct spa_bt_telephony_call *call; bool found_active = false; bool found_held = false; if (!rfcomm->chld_supported) { err = BT_TELEPHONY_ERROR_NOT_SUPPORTED; goto error; } else if (rfcomm->hfp_hf_in_progress) { err = BT_TELEPHONY_ERROR_IN_PROGRESS; goto error; } spa_list_for_each(call, &rfcomm->telephony_ag->call_list, link) { if (call->state == CALL_STATE_WAITING) { spa_log_debug(backend->log, "call waiting before release and swap"); err = BT_TELEPHONY_ERROR_INVALID_STATE; goto error; } else if (call->state == CALL_STATE_ACTIVE) found_active = true; else if (call->state == CALL_STATE_HELD) found_held = true; } if (!found_active || !found_held) { spa_log_debug(backend->log, "no active and held calls"); err = BT_TELEPHONY_ERROR_INVALID_STATE; goto error; } rfcomm_send_cmd(rfcomm, hfp_hf_idle, m, "AT+CHLD=1"); rfcomm->hfp_hf_in_progress = true; return; error: telephony_send_dbus_method_reply(backend->telephony, m, err, 0); } static void hfp_hf_hold_and_answer(void *data, DBusMessage *m) { struct rfcomm *rfcomm = data; struct impl *backend = rfcomm->backend; enum spa_bt_telephony_error err = BT_TELEPHONY_ERROR_NONE; struct spa_bt_telephony_call *call; bool found_active = false; bool found_waiting = false; if (!rfcomm->chld_supported) { err = BT_TELEPHONY_ERROR_NOT_SUPPORTED; goto error; } else if (rfcomm->hfp_hf_in_progress) { err = BT_TELEPHONY_ERROR_IN_PROGRESS; goto error; } spa_list_for_each(call, &rfcomm->telephony_ag->call_list, link) { if (call->state == CALL_STATE_ACTIVE) found_active = true; else if (call->state == CALL_STATE_WAITING) found_waiting = true; } if (!found_active || !found_waiting) { spa_log_debug(backend->log, "no active and waiting calls"); err = BT_TELEPHONY_ERROR_INVALID_STATE; goto error; } rfcomm_send_cmd(rfcomm, hfp_hf_idle, m, "AT+CHLD=2"); rfcomm->hfp_hf_in_progress = true; return; error: telephony_send_dbus_method_reply(backend->telephony, m, err, 0); } static void hfp_hf_hangup_all(void *data, DBusMessage *m) { struct rfcomm *rfcomm = data; struct spa_bt_telephony_call *call; bool found_active = false; bool found_held = false; spa_list_for_each(call, &rfcomm->telephony_ag->call_list, link) { switch (call->state) { case CALL_STATE_ACTIVE: case CALL_STATE_DIALING: case CALL_STATE_ALERTING: case CALL_STATE_INCOMING: found_active = true; break; case CALL_STATE_HELD: case CALL_STATE_WAITING: found_held = true; break; default: break; } } /* Hangup held calls */ if (found_held) { rfcomm_send_cmd(rfcomm, hfp_hf_idle, found_active ? NULL : m, "AT+CHLD=0"); } /* Hangup active calls */ if (found_active) { rfcomm_send_cmd(rfcomm, hfp_hf_idle, m, "AT+CHUP"); } } static void hfp_hf_create_multiparty(void *data, DBusMessage *m) { struct rfcomm *rfcomm = data; struct impl *backend = rfcomm->backend; enum spa_bt_telephony_error err = BT_TELEPHONY_ERROR_NONE; struct spa_bt_telephony_call *call; bool found_active = false; bool found_held = false; if (!rfcomm->chld_supported) { err = BT_TELEPHONY_ERROR_NOT_SUPPORTED; goto error; } else if (rfcomm->hfp_hf_in_progress) { err = BT_TELEPHONY_ERROR_IN_PROGRESS; goto error; } spa_list_for_each(call, &rfcomm->telephony_ag->call_list, link) { if (call->state == CALL_STATE_WAITING) { spa_log_debug(backend->log, "call waiting before creating multiparty"); err = BT_TELEPHONY_ERROR_INVALID_STATE; goto error; } else if (call->state == CALL_STATE_ACTIVE) found_active = true; else if (call->state == CALL_STATE_HELD) found_held = true; } if (!found_active || !found_held) { spa_log_debug(backend->log, "no active and held calls"); err = BT_TELEPHONY_ERROR_INVALID_STATE; goto error; } rfcomm_send_cmd(rfcomm, hfp_hf_idle, m, "AT+CHLD=3"); rfcomm->hfp_hf_in_progress = true; return; error: telephony_send_dbus_method_reply(backend->telephony, m, err, 0); } static void hfp_hf_send_tones(void *data, const char *tones, DBusMessage *m) { struct rfcomm *rfcomm = data; struct impl *backend = rfcomm->backend; enum spa_bt_telephony_error err = BT_TELEPHONY_ERROR_NONE; struct spa_bt_telephony_call *call; bool found = false; spa_list_for_each(call, &rfcomm->telephony_ag->call_list, link) { if (call->state == CALL_STATE_ACTIVE) { found = true; break; } } if (!found) { spa_log_debug(backend->log, "no active call"); err = BT_TELEPHONY_ERROR_INVALID_STATE; goto error; } rfcomm_send_cmd(rfcomm, hfp_hf_idle, m, "AT+VTS=%s", tones); return; error: telephony_send_dbus_method_reply(backend->telephony, m, err, 0); } static int sco_do_connect(struct spa_bt_transport *t); static void hfp_hf_transport_activate(void *data, DBusMessage *m) { struct rfcomm *rfcomm = data; struct impl *backend = rfcomm->backend; enum spa_bt_telephony_error err = BT_TELEPHONY_ERROR_NONE; if (rfcomm->transport && rfcomm->transport->fd > 0) { spa_log_debug(backend->log, "transport is already active; SCO socket exists"); err = BT_TELEPHONY_ERROR_INVALID_STATE; goto out; } if (rfcomm->codec_negotiation_supported) { rfcomm_send_cmd(rfcomm, hfp_hf_idle, m, "AT+BCC"); return; } else { if (!rfcomm->transport || rfcomm->transport->media_codec->id != SPA_BLUETOOTH_AUDIO_CODEC_CVSD) { err = BT_TELEPHONY_ERROR_NOT_SUPPORTED; goto out; } sco_do_connect(rfcomm->transport); } out: telephony_send_dbus_method_reply(backend->telephony, m, err, 0); } static void hfp_hf_set_speaker_volume(void *data, uint8_t volume, DBusMessage *m) { struct rfcomm *rfcomm = data; struct spa_bt_transport_volume *t_volume; rfcomm->volumes[SPA_BT_VOLUME_ID_RX].hw_volume = volume; if (rfcomm_hw_volume_enabled(rfcomm)) { t_volume = rfcomm->transport ? &rfcomm->transport->volumes[SPA_BT_VOLUME_ID_RX] : NULL; if (t_volume && t_volume->active) { t_volume->volume = (float) spa_bt_volume_hw_to_linear(volume, t_volume->hw_volume_max); spa_bt_transport_emit_volume_changed(rfcomm->transport); } } rfcomm_send_volume_cmd(rfcomm, hfp_hf_idle, m, SPA_BT_VOLUME_ID_RX); } static void hfp_hf_set_microphone_volume(void *data, uint8_t volume, DBusMessage *m) { struct rfcomm *rfcomm = data; struct spa_bt_transport_volume *t_volume; rfcomm->volumes[SPA_BT_VOLUME_ID_TX].hw_volume = volume; if (rfcomm_hw_volume_enabled(rfcomm)) { t_volume = rfcomm->transport ? &rfcomm->transport->volumes[SPA_BT_VOLUME_ID_TX] : NULL; if (t_volume && t_volume->active) { t_volume->volume = (float) spa_bt_volume_hw_to_linear(volume, t_volume->hw_volume_max); spa_bt_transport_emit_volume_changed(rfcomm->transport); } } rfcomm_send_volume_cmd(rfcomm, hfp_hf_idle, m, SPA_BT_VOLUME_ID_TX); } static const struct spa_bt_telephony_ag_callbacks telephony_ag_callbacks = { SPA_VERSION_BT_TELEPHONY_AG_CALLBACKS, .dial = hfp_hf_dial, .swap_calls = hfp_hf_swap_calls, .release_and_answer = hfp_hf_release_and_answer, .release_and_swap = hfp_hf_release_and_swap, .hold_and_answer = hfp_hf_hold_and_answer, .hangup_all = hfp_hf_hangup_all, .create_multiparty = hfp_hf_create_multiparty, .send_tones = hfp_hf_send_tones, .transport_activate = hfp_hf_transport_activate, .set_speaker_volume = hfp_hf_set_speaker_volume, .set_microphone_volume = hfp_hf_set_microphone_volume, }; #define hfp_hf_set_call_state(log, obj, new_state) \ ({ \ spa_log_debug(log, "call id: %u, %u -> %u", obj->id, obj->state, new_state); \ obj->state = new_state; \ }) static void hfp_hf_remove_disconnected_calls(struct rfcomm *rfcomm) { struct impl *backend = rfcomm->backend; struct spa_bt_telephony_call *call, *call_tmp; struct updated_call *updated_call; bool found; spa_list_for_each_safe(call, call_tmp, &rfcomm->telephony_ag->call_list, link) { found = false; spa_list_for_each(updated_call, &rfcomm->updated_call_list, link) { if (call->id == updated_call->id) { found = true; break; } } spa_log_debug(backend->log, "call %d -> %s", call->id, found ? "updated" : "disconnected"); if (!found) { hfp_hf_set_call_state(backend->log, call, CALL_STATE_DISCONNECTED); telephony_call_notify_updated_props(call); telephony_call_destroy(call); } } spa_list_consume(updated_call, &rfcomm->updated_call_list, link) { spa_list_remove(&updated_call->link); free(updated_call); } } static bool rfcomm_hfp_hf(struct rfcomm *rfcomm, char* token) { struct impl *backend = rfcomm->backend; unsigned int features, gain, selected_codec, indicator, value, type; char number[17]; if (sscanf(token, "+BRSF:%u", &features) == 1) { if (((features & (SPA_BT_HFP_AG_FEATURE_CODEC_NEGOTIATION)) != 0) && !spa_list_is_empty(&rfcomm->available_codec_list)) rfcomm->codec_negotiation_supported = true; rfcomm->hfp_hf_3way = (features & SPA_BT_HFP_AG_FEATURE_3WAY) != 0; rfcomm->hfp_hf_nrec = (features & SPA_BT_HFP_AG_FEATURE_ECNR) != 0; rfcomm->hfp_hf_clcc = (features & SPA_BT_HFP_AG_FEATURE_ENHANCED_CALL_STATUS) != 0; rfcomm->hfp_hf_cme = (features & SPA_BT_HFP_AG_FEATURE_EXTENDED_RES_CODE) != 0; } else if (sscanf(token, "+BCS:%u", &selected_codec) == 1 && rfcomm->codec_negotiation_supported) { const struct media_codec *codec = codec_list_get(backend, &rfcomm->available_codec_list, selected_codec); if (!codec) { spa_log_warn(backend->log, "unsupported codec negotiation: %d", selected_codec); } else { spa_log_debug(backend->log, "RFCOMM selected_codec = %i", selected_codec); /* send codec selection to AG */ rfcomm_send_cmd(rfcomm, hfp_hf_idle, NULL, "AT+BCS=%u", selected_codec); if (!rfcomm->transport || (rfcomm->codec != selected_codec) ) { if (rfcomm_new_transport(rfcomm, selected_codec) < 0) { // TODO: We should manage the missing transport } else { spa_bt_device_connect_profile(rfcomm->device, rfcomm->profile); } } } } else if (sscanf(token, "+VGM%*1[:=]%u", &gain) == 1) { if (gain <= SPA_BT_VOLUME_HS_MAX) { rfcomm_emit_volume_changed(rfcomm, SPA_BT_VOLUME_ID_TX, gain); } else { spa_log_debug(backend->log, "RFCOMM receive unsupported VGM gain: %s", token); } } else if (sscanf(token, "+VGS%*1[:=]%u", &gain) == 1) { if (gain <= SPA_BT_VOLUME_HS_MAX) { rfcomm_emit_volume_changed(rfcomm, SPA_BT_VOLUME_ID_RX, gain); } else { spa_log_debug(backend->log, "RFCOMM receive unsupported VGS gain: %s", token); } } else if (spa_strstartswith(token, "+CIND: (")) { uint8_t i = 1; while (strstr(token, "\"")) { token += strcspn(token, "\"") + 1; token[strcspn(token, "\"")] = 0; rfcomm->hf_indicators[i] = strdup(token); token += strcspn(token, "\"") + 1; i++; if (i == MAX_HF_INDICATORS) { break; } } } else if (spa_strstartswith(token, "+CIND: ")) { token[strcspn(token, "\r")] = 0; token[strcspn(token, "\n")] = 0; token += strlen("+CIND: "); uint8_t i = 1; while (strlen(token)) { if (i >= MAX_HF_INDICATORS || !rfcomm->hf_indicators[i]) { break; } token[strcspn(token, ",")] = 0; spa_log_info(backend->log, "AG indicator state: %s = %i", rfcomm->hf_indicators[i], atoi(token)); if (spa_streq(rfcomm->hf_indicators[i], "battchg")) { spa_bt_device_report_battery_level(rfcomm->device, atoi(token) * 100 / 5); } token += strcspn(token, "\0") + 1; i++; } } else if (spa_strstartswith(token, "+CHLD: (")) { int chlds = 0; token[strcspn(token, "\r")] = 0; token[strcspn(token, "\n")] = 0; token[strcspn(token, ")")] = 0; token += strlen("+CHLD: ("); while (strlen(token)) { token[strcspn(token, ",")] = 0; if (spa_streq(token, "0")) chlds |= 1 << 0; else if (spa_streq(token, "1")) chlds |= 1 << 1; else if (spa_streq(token, "2")) chlds |= 1 << 2; else if (spa_streq(token, "3")) chlds |= 1 << 3; token += strcspn(token, "\0") + 1; } rfcomm->chld_supported = (chlds == 0x0F); spa_log_debug(backend->log, "AT+CHLD supported: %d (0x%X)", rfcomm->chld_supported, chlds); } else if (sscanf(token, "+CIEV: %u,%u", &indicator, &value) == 2) { if (indicator >= MAX_HF_INDICATORS || !rfcomm->hf_indicators[indicator]) { spa_log_warn(backend->log, "indicator %u has not been registered, ignoring", indicator); } else { spa_log_info(backend->log, "AG indicator update: %s = %u", rfcomm->hf_indicators[indicator], value); if (spa_streq(rfcomm->hf_indicators[indicator], "battchg")) { spa_bt_device_report_battery_level(rfcomm->device, value * 100 / 5); } else if (spa_streq(rfcomm->hf_indicators[indicator], "callsetup")) { if (rfcomm->hfp_hf_clcc) { rfcomm_send_cmd(rfcomm, hfp_hf_clcc_update, NULL, "AT+CLCC"); return true; } if (value == CIND_CALLSETUP_NONE) { struct spa_bt_telephony_call *call, *tcall; spa_list_for_each_safe(call, tcall, &rfcomm->telephony_ag->call_list, link) { if (call->state == CALL_STATE_DIALING || call->state == CALL_STATE_ALERTING || call->state == CALL_STATE_INCOMING || call->state == CALL_STATE_WAITING) { hfp_hf_set_call_state(backend->log, call, CALL_STATE_DISCONNECTED); telephony_call_notify_updated_props(call); telephony_call_destroy(call); } } } else if (value == CIND_CALLSETUP_INCOMING) { struct spa_bt_telephony_call *call; bool found = false; spa_list_for_each(call, &rfcomm->telephony_ag->call_list, link) { if (call->state == CALL_STATE_INCOMING || call->state == CALL_STATE_WAITING) { spa_log_info(backend->log, "incoming call already in progress (%d)", call->state); found = true; break; } } if (!found && !rfcomm->hfp_hf_clcc) { spa_log_info(backend->log, "Incoming call"); if (hfp_hf_add_call(rfcomm, rfcomm->telephony_ag, CALL_STATE_INCOMING, NULL) == NULL) spa_log_warn(backend->log, "failed to create incoming call"); } } else if (value == CIND_CALLSETUP_DIALING) { struct spa_bt_telephony_call *call; bool found = false; spa_list_for_each(call, &rfcomm->telephony_ag->call_list, link) { if (call->state == CALL_STATE_DIALING || call->state == CALL_STATE_ALERTING) { spa_log_info(backend->log, "dialing call already in progress (%d)", call->state); found = true; break; } } if (!found && !rfcomm->hfp_hf_clcc) { spa_log_info(backend->log, "Dialing call"); if (hfp_hf_add_call(rfcomm, rfcomm->telephony_ag, CALL_STATE_DIALING, rfcomm->dialing_number) == NULL) spa_log_warn(backend->log, "failed to create dialing call"); spa_clear_ptr(rfcomm->dialing_number, free); } } else if (value == CIND_CALLSETUP_ALERTING) { struct spa_bt_telephony_call *call; spa_list_for_each(call, &rfcomm->telephony_ag->call_list, link) { if (call->state == CALL_STATE_DIALING) { hfp_hf_set_call_state(backend->log, call, CALL_STATE_ALERTING); telephony_call_notify_updated_props(call); } } } rfcomm->hfp_hf_in_progress = false; } else if (spa_streq(rfcomm->hf_indicators[indicator], "call")) { if (rfcomm->hfp_hf_clcc) { rfcomm_send_cmd(rfcomm, hfp_hf_clcc_update, NULL, "AT+CLCC"); return true; } if (value == 0) { struct spa_bt_telephony_call *call, *tcall; spa_list_for_each_safe(call, tcall, &rfcomm->telephony_ag->call_list, link) { if (call->state == CALL_STATE_ACTIVE) { hfp_hf_set_call_state(backend->log, call, CALL_STATE_DISCONNECTED); telephony_call_notify_updated_props(call); telephony_call_destroy(call); } } } else if (value == 1) { struct spa_bt_telephony_call *call; spa_list_for_each(call, &rfcomm->telephony_ag->call_list, link) { if (call->state == CALL_STATE_DIALING || call->state == CALL_STATE_ALERTING || call->state == CALL_STATE_INCOMING) { hfp_hf_set_call_state(backend->log, call, CALL_STATE_ACTIVE); telephony_call_notify_updated_props(call); } } } rfcomm->hfp_hf_in_progress = false; } else if (spa_streq(rfcomm->hf_indicators[indicator], "callheld")) { if (rfcomm->hfp_hf_clcc) { rfcomm_send_cmd(rfcomm, hfp_hf_clcc_update, NULL, "AT+CLCC"); return true; } if (value == 0) { /* Reject waiting call or no held calls */ struct spa_bt_telephony_call *call, *tcall; bool found_waiting = false; spa_list_for_each_safe(call, tcall, &rfcomm->telephony_ag->call_list, link) { if (call->state == CALL_STATE_WAITING) { hfp_hf_set_call_state(backend->log, call, CALL_STATE_DISCONNECTED); telephony_call_notify_updated_props(call); telephony_call_destroy(call); found_waiting = true; break; } } if (!found_waiting) { spa_list_for_each_safe(call, tcall, &rfcomm->telephony_ag->call_list, link) { if (call->state == CALL_STATE_HELD) { hfp_hf_set_call_state(backend->log, call, CALL_STATE_DISCONNECTED); telephony_call_notify_updated_props(call); telephony_call_destroy(call); } } } } else if (value == 1) { /* Swap calls */ struct spa_bt_telephony_call *call; spa_list_for_each(call, &rfcomm->telephony_ag->call_list, link) { bool changed = false; if (call->state == CALL_STATE_ACTIVE) { hfp_hf_set_call_state(backend->log, call, CALL_STATE_HELD); changed = true; } else if (call->state == CALL_STATE_HELD) { hfp_hf_set_call_state(backend->log, call, CALL_STATE_ACTIVE); changed = true; } if (changed) telephony_call_notify_updated_props(call); } } else if (value == 2) { /* No active calls, place waiting on hold */ struct spa_bt_telephony_call *call; spa_list_for_each(call, &rfcomm->telephony_ag->call_list, link) { bool changed = false; if (call->state == CALL_STATE_ACTIVE || call->state == CALL_STATE_WAITING) { hfp_hf_set_call_state(backend->log, call, CALL_STATE_HELD); changed = true; } if (changed) telephony_call_notify_updated_props(call); } } rfcomm->hfp_hf_in_progress = false; } } } else if (sscanf(token, "+CLIP: \"%16[^\"]\",%u", number, &type) == 2) { struct spa_bt_telephony_call *call; spa_list_for_each(call, &rfcomm->telephony_ag->call_list, link) { if (call->state == CALL_STATE_INCOMING && !spa_streq(number, call->line_identification)) { if (call->line_identification) free(call->line_identification); call->line_identification = strdup(number); telephony_call_notify_updated_props(call); break; } } } else if (sscanf(token, "+CCWA: \"%16[^\"]\",%u", number, &type) == 2) { struct spa_bt_telephony_call *call; bool found = false; spa_log_info(backend->log, "Waiting call"); spa_list_for_each(call, &rfcomm->telephony_ag->call_list, link) { if (call->state == CALL_STATE_WAITING) { spa_log_info(backend->log, "waiting call already in progress (id: %d)", call->id); found = true; break; } } if (!found) { call = hfp_hf_add_call(rfcomm, rfcomm->telephony_ag, CALL_STATE_WAITING, number); if (call == NULL) spa_log_warn(backend->log, "failed to create waiting call"); } } else if (spa_strstartswith(token, "+CLCC:")) { struct spa_bt_telephony_call *call; size_t pos; char *token_end; int idx; unsigned int status, mpty; bool parsed = false, found = false; number[0] = '\0'; token[strcspn(token, "\r")] = 0; token[strcspn(token, "\n")] = 0; token_end = token + strlen(token); token += strlen("+CLCC:"); if (token < token_end) { pos = strcspn(token, ","); token[pos] = '\0'; idx = atoi(token); token += pos + 1; } if (token < token_end) { // Skip direction pos = strcspn(token, ","); token += pos + 1; } if (token < token_end) { pos = strcspn(token, ","); token[pos] = '\0'; status = atoi(token); token += pos + 1; } if (token < token_end) { // Skip mode pos = strcspn(token, ","); token += pos + 1; } if (token < token_end) { pos = strcspn(token, ","); token[pos] = '\0'; mpty = atoi(token); token += pos + 1; parsed = true; } if (token < token_end) { if (sscanf(token, "\"%16[^\"]\",%u", number, &type) != 2) { spa_log_warn(backend->log, "Failed to parse number: %s", token); number[0] = '\0'; } } if (SPA_LIKELY (parsed)) { struct updated_call *updated_call; updated_call = calloc(1, sizeof(struct updated_call)); updated_call->id = idx; spa_list_append(&rfcomm->updated_call_list, &updated_call->link); spa_list_for_each(call, &rfcomm->telephony_ag->call_list, link) { if (call->id == idx) { bool changed = false; found = true; if (call->state != status) { call->state =status; changed = true; } if (call->multiparty != mpty) { call->multiparty = mpty; changed = true; } if (strlen(number) && !spa_streq(number, call->line_identification)) { if (call->line_identification) free(call->line_identification); call->line_identification = strdup(number); changed = true; } if (changed) telephony_call_notify_updated_props(call); } } if (!found) { spa_log_info(backend->log, "New call, initial state: %u", status); call = hfp_hf_add_call(rfcomm, rfcomm->telephony_ag, status, strlen(number) ? number : NULL); if (call == NULL) spa_log_warn(backend->log, "failed to create call"); else if (call->id != idx) spa_log_warn(backend->log, "wrong call index: %d, expected: %d", call->id, idx); if (spa_streq(number, rfcomm->dialing_number)) { free(rfcomm->dialing_number); rfcomm->dialing_number = NULL; } } } else { spa_log_warn(backend->log, "malformed +CLCC command received from AG"); } } else if (spa_strstartswith(token, "OK") || spa_strstartswith(token, "ERROR") || spa_strstartswith(token, "+CME ERROR:")) { rfcomm_cmd_done(rfcomm, token); if (spa_strstartswith(token, "OK")) { switch(rfcomm->hf_state) { case hfp_hf_brsf: if (rfcomm->codec_negotiation_supported) { char buf[64]; struct spa_strbuf str; struct codec_item *item; spa_strbuf_init(&str, buf, sizeof(buf)); spa_strbuf_append(&str, "1"); spa_list_for_each(item, &rfcomm->available_codec_list, link) spa_strbuf_append(&str, ",%u", item->codec->codec_id); rfcomm_send_cmd(rfcomm, hfp_hf_bac, NULL, "AT+BAC=%s", buf); } else { rfcomm_send_cmd(rfcomm, hfp_hf_cind1, NULL, "AT+CIND=?"); } break; case hfp_hf_bac: rfcomm_send_cmd(rfcomm, hfp_hf_cind1, NULL, "AT+CIND=?"); break; case hfp_hf_cind1: rfcomm_send_cmd(rfcomm, hfp_hf_cind2, NULL, "AT+CIND?"); break; case hfp_hf_cind2: rfcomm_send_cmd(rfcomm, hfp_hf_cmer, NULL, "AT+CMER=3,0,0,1"); break; case hfp_hf_cmer: if (rfcomm->hfp_hf_3way) { rfcomm_send_cmd(rfcomm, hfp_hf_chld, NULL, "AT+CHLD=?"); break; } SPA_FALLTHROUGH; case hfp_hf_chld: rfcomm->slc_configured = true; if (!rfcomm->codec_negotiation_supported) { if (rfcomm_new_transport(rfcomm, HFP_AUDIO_CODEC_CVSD) < 0) { // TODO: We should manage the missing transport } else { spa_bt_device_connect_profile(rfcomm->device, rfcomm->profile); } } rfcomm->telephony_ag = telephony_ag_new(backend->telephony, 0); rfcomm->telephony_ag->address = strdup(rfcomm->device->address); rfcomm->telephony_ag->volume[SPA_BT_VOLUME_ID_RX] = rfcomm->volumes[SPA_BT_VOLUME_ID_RX].hw_volume = backend->hfp_default_speaker_volume; rfcomm->telephony_ag->volume[SPA_BT_VOLUME_ID_TX] = rfcomm->volumes[SPA_BT_VOLUME_ID_TX].hw_volume = backend->hfp_default_mic_volume; telephony_ag_set_callbacks(rfcomm->telephony_ag, &telephony_ag_callbacks, rfcomm); if (rfcomm->transport) { rfcomm->telephony_ag->transport.codec = rfcomm->transport->media_codec->codec_id; rfcomm->telephony_ag->transport.state = rfcomm->transport->state; } telephony_ag_register(rfcomm->telephony_ag); rfcomm_send_cmd(rfcomm, hfp_hf_clip, NULL, "AT+CLIP=1"); break; case hfp_hf_clip: if (rfcomm->chld_supported) { rfcomm_send_cmd(rfcomm, hfp_hf_ccwa, NULL, "AT+CCWA=1"); break; } SPA_FALLTHROUGH; case hfp_hf_ccwa: if (rfcomm->hfp_hf_cme) { rfcomm_send_cmd(rfcomm, hfp_hf_cmee, NULL, "AT+CMEE=1"); break; } SPA_FALLTHROUGH; case hfp_hf_cmee: if (backend->hfp_disable_nrec && rfcomm->hfp_hf_nrec) { rfcomm_send_cmd(rfcomm, hfp_hf_nrec, NULL, "AT+NREC=0"); break; } SPA_FALLTHROUGH; case hfp_hf_nrec: if (rfcomm->hfp_hf_clcc) { rfcomm_send_cmd(rfcomm, hfp_hf_clcc, NULL, "AT+CLCC"); break; } else { // TODO: Create calls if CIND reports one during SLC setup } /* Report volume on SLC establishment */ SPA_FALLTHROUGH; case hfp_hf_clcc: if (rfcomm->hf_state == hfp_hf_clcc) { hfp_hf_remove_disconnected_calls(rfcomm); } rfcomm_send_volume_cmd(rfcomm, hfp_hf_vgs, NULL, SPA_BT_VOLUME_ID_RX); break; case hfp_hf_vgs: rfcomm_send_volume_cmd(rfcomm, hfp_hf_idle, NULL, SPA_BT_VOLUME_ID_TX); break; case hfp_hf_clcc_update: hfp_hf_remove_disconnected_calls(rfcomm); rfcomm->hfp_hf_in_progress = false; break; case hfp_hf_chld1_hangup: /* For HFP/HF/TWC/BV-03-C - see 0e92ab9307e05758b3f70b4c0648e29c1d1e50be */ if (!rfcomm->hfp_hf_clcc) { struct spa_bt_telephony_call *call, *tcall; spa_list_for_each_safe(call, tcall, &rfcomm->telephony_ag->call_list, link) { if (call->state == CALL_STATE_ACTIVE) { hfp_hf_set_call_state(backend->log, call, CALL_STATE_DISCONNECTED); telephony_call_notify_updated_props(call); telephony_call_destroy(call); } } spa_list_for_each(call, &rfcomm->telephony_ag->call_list, link) { if (call->state == CALL_STATE_HELD) { hfp_hf_set_call_state(backend->log, call, CALL_STATE_ACTIVE); telephony_call_notify_updated_props(call); } } } break; case hfp_hf_idle: default: break; } } else { /* reset state in case of an error reply */ rfcomm->hfp_hf_in_progress = false; } rfcomm_send_next_cmd(rfcomm); } return true; } #endif static void rfcomm_process_events(struct rfcomm *rfcomm, char *buf, bool ag, bool (*handler)(struct rfcomm *, char *)) { struct impl *backend = rfcomm->backend; char *token; /* Relaxed parsing of both \r (AG) and \r\n\r\n (HF) */ while ((token = strsep(&buf, "\r"))) { size_t len; /* Skip leading and trailing \n */ while (*token == '\n') ++token; for (len = strlen(token); len > 0 && token[len - 1] == '\n'; --len) token[len - 1] = '\0'; /* Skip empty (only last one if AG) */ if (*token == '\0' && (buf == NULL || !ag)) continue; spa_log_debug(backend->log, "RFCOMM event: %s", token); if (!handler(rfcomm, token)) { spa_log_debug(backend->log, "RFCOMM received unsupported event: %s", token); if (ag) rfcomm_send_error(rfcomm, CMEE_OPERATION_NOT_SUPPORTED); } } } static void rfcomm_event(struct spa_source *source) { struct rfcomm *rfcomm = source->data; struct impl *backend = rfcomm->backend; if (source->rmask & (SPA_IO_HUP | SPA_IO_ERR)) { spa_log_info(backend->log, "lost RFCOMM connection."); rfcomm_free(rfcomm); return; } if (source->rmask & SPA_IO_IN) { char buf[512]; ssize_t len; len = read(source->fd, buf, sizeof(buf) - 1); if (len < 0) { spa_log_error(backend->log, "RFCOMM read error: %s", strerror(errno)); return; } buf[len] = 0; spa_log_debug(backend->log, "RFCOMM << %s", buf); spa_debug_log_mem(backend->log, SPA_LOG_LEVEL_DEBUG, 2, buf, strlen(buf)); switch (rfcomm->profile) { #ifdef HAVE_BLUEZ_5_BACKEND_HSP_NATIVE case SPA_BT_PROFILE_HSP_HS: rfcomm_process_events(rfcomm, buf, true, rfcomm_hsp_ag); break; case SPA_BT_PROFILE_HSP_AG: rfcomm_process_events(rfcomm, buf, false, rfcomm_hsp_hs); break; #endif #ifdef HAVE_BLUEZ_5_BACKEND_HFP_NATIVE case SPA_BT_PROFILE_HFP_HF: rfcomm_process_events(rfcomm, buf, true, rfcomm_hfp_ag); break; case SPA_BT_PROFILE_HFP_AG: rfcomm_process_events(rfcomm, buf, false, rfcomm_hfp_hf); break; #endif default: break; } } } static int sco_create_socket(struct impl *backend, struct spa_bt_adapter *adapter, bool transparent) { struct sockaddr_sco addr; socklen_t len; bdaddr_t src; spa_autoclose int sock = socket(PF_BLUETOOTH, SOCK_SEQPACKET | SOCK_NONBLOCK, BTPROTO_SCO); if (sock < 0) { spa_log_error(backend->log, "socket(SEQPACKET, SCO) %s", strerror(errno)); return -1; } str2ba(adapter->address, &src); len = sizeof(addr); memset(&addr, 0, len); addr.sco_family = AF_BLUETOOTH; bacpy(&addr.sco_bdaddr, &src); if (bind(sock, (struct sockaddr *) &addr, len) < 0) { spa_log_error(backend->log, "bind(): %s", strerror(errno)); return -1; } spa_log_debug(backend->log, "transparent=%d", (int)transparent); if (transparent) { /* set correct socket options for mSBC/LC3 */ struct bt_voice voice_config; memset(&voice_config, 0, sizeof(voice_config)); voice_config.setting = BT_VOICE_TRANSPARENT; if (setsockopt(sock, SOL_BLUETOOTH, BT_VOICE, &voice_config, sizeof(voice_config)) < 0) { spa_log_error(backend->log, "setsockopt(): %s", strerror(errno)); return -1; } } return spa_steal_fd(sock); } static int sco_do_connect(struct spa_bt_transport *t) { struct impl *backend = SPA_CONTAINER_OF(t->backend, struct impl, this); struct spa_bt_device *d = t->device; struct transport_data *td = t->user_data; struct sockaddr_sco addr; int err; spa_log_debug(backend->log, "transport %p: enter sco_do_connect, codec=%s", t, t->media_codec->description); td->err = -EIO; if (d->adapter == NULL) return -EIO; spa_zero(addr); addr.sco_family = AF_BLUETOOTH; str2ba(d->address, &addr.sco_bdaddr); for (int retry = 2;;) { bool encoded = t->media_codec->id != SPA_BLUETOOTH_AUDIO_CODEC_CVSD; spa_autoclose int sock = sco_create_socket(backend, d->adapter, encoded); if (sock < 0) return -1; spa_log_debug(backend->log, "transport %p: doing connect", t); err = connect(sock, (struct sockaddr *) &addr, sizeof(addr)); if (err < 0 && errno == ECONNABORTED && retry-- > 0) { spa_log_warn(backend->log, "connect(): %s. Remaining retry:%d", strerror(errno), retry); continue; } else if (err < 0 && !(errno == EAGAIN || errno == EINPROGRESS)) { spa_log_error(backend->log, "connect(): %s", strerror(errno)); #ifdef HAVE_BLUEZ_5_BACKEND_HFP_NATIVE if (errno == EOPNOTSUPP && encoded && td->rfcomm->codec_negotiation_supported) { /* Adapter doesn't support msbc/lc3/etc. Renegotiate. */ d->adapter->msbc_probed = true; d->adapter->has_msbc = false; codec_list_clear(&td->rfcomm->available_codec_list); codec_list_clear(&td->rfcomm->supported_codec_list); if (t->profile == SPA_BT_PROFILE_HFP_HF) { td->rfcomm->hfp_ag_switching_codec = true; rfcomm_send_reply(td->rfcomm, "+BCS: 1"); } else if (t->profile == SPA_BT_PROFILE_HFP_AG) { rfcomm_send_cmd(td->rfcomm, hfp_hf_idle, NULL, "AT+BAC=1"); } } #endif return -1; } td->err = -EINPROGRESS; return spa_steal_fd(sock); } } static int rfcomm_ag_sync_volume(struct rfcomm *rfcomm, bool later); static void sco_ready(struct spa_bt_transport *t) { struct impl *backend = SPA_CONTAINER_OF(t->backend, struct impl, this); struct transport_data *td = t->user_data; struct sco_options sco_opt; socklen_t len; int err; spa_log_debug(backend->log, "transport %p: ready", t); /* Read socket error status */ if (t->fd >= 0) { if (td->err == -EINPROGRESS) { len = sizeof(err); memset(&err, 0, len); if (getsockopt(t->fd, SOL_SOCKET, SO_ERROR, &err, &len) < 0) td->err = -errno; else td->err = -err; } } else { td->err = -EIO; } if (!td->requesting) return; td->requesting = false; if (td->err) goto done; /* XXX: The MTU as currently reported by kernel (6.2) here is not a valid packet size, * XXX: for USB adapters, see sco-io. */ len = sizeof(sco_opt); memset(&sco_opt, 0, len); if (getsockopt(t->fd, SOL_SCO, SCO_OPTIONS, &sco_opt, &len) < 0) { spa_log_warn(backend->log, "getsockopt(SCO_OPTIONS) failed: %d (%m)", errno); t->read_mtu = 144; t->write_mtu = 144; } else { spa_log_debug(backend->log, "autodetected mtu = %u", sco_opt.mtu); t->read_mtu = sco_opt.mtu; t->write_mtu = sco_opt.mtu; } /* Clear nonblocking flag we set for connect() */ err = fcntl(t->fd, F_GETFL, O_NONBLOCK); if (err < 0) { td->err = -errno; goto done; } err &= ~O_NONBLOCK; err = fcntl(t->fd, F_SETFL, O_NONBLOCK, err); if (err < 0) { td->err = -errno; goto done; } done: if (td->err) { spa_log_debug(backend->log, "transport %p: acquire failed: %s (%d)", t, strerror(-td->err), td->err); spa_bt_transport_set_state(t, SPA_BT_TRANSPORT_STATE_ERROR); return; } spa_log_debug(backend->log, "transport %p: acquire complete, read_mtu=%u, write_mtu=%u", t, t->read_mtu, t->write_mtu); /* * Send RFCOMM volume after connection is ready, and also after * a timeout. * * Some headsets adjust their HFP volume when in A2DP mode * without reporting via RFCOMM to us, so the volume level can * be out of sync, and we can't know what it is. Moreover, they may * take the first +VGS command after connection only partially * into account, and need a long enough timeout. * * E.g. with Sennheiser HD-250BT, the first +VGS changes the * actual volume, but does not update the level in the hardware * volume buttons, which is updated by an +VGS event only after * sufficient time is elapsed from the connection. */ rfcomm_ag_sync_volume(td->rfcomm, false); rfcomm_ag_sync_volume(td->rfcomm, true); spa_bt_transport_set_state(t, SPA_BT_TRANSPORT_STATE_ACTIVE); } static void sco_start_source(struct spa_bt_transport *t); static int sco_acquire_cb(void *data, bool optional) { struct spa_bt_transport *t = data; struct transport_data *td = t->user_data; struct impl *backend = SPA_CONTAINER_OF(t->backend, struct impl, this); int sock; spa_log_debug(backend->log, "transport %p: enter sco_acquire_cb", t); if (optional || t->fd > 0) sock = t->fd; else sock = sco_do_connect(t); if (sock < 0) goto fail; #ifdef HAVE_BLUEZ_5_BACKEND_HFP_NATIVE if (!mm_is_available(backend->modemmanager)) rfcomm_hfp_ag_set_cind(td->rfcomm, true); #endif t->fd = sock; td->requesting = true; sco_start_source(t); if (td->err != -EINPROGRESS) sco_ready(t); return 0; fail: spa_bt_transport_set_state(t, SPA_BT_TRANSPORT_STATE_ERROR); return -1; } static int sco_destroy_cb(void *data) { struct spa_bt_transport *t = data; struct transport_data *td = t->user_data; struct impl *backend = SPA_CONTAINER_OF(t->backend, struct impl, this); if (t->sco_io) { spa_bt_sco_io_destroy(t->sco_io); t->sco_io = NULL; } if (td->sco.loop) spa_loop_remove_source(backend->main_loop, &td->sco); if (t->fd > 0) { /* Shutdown and close the socket */ shutdown(t->fd, SHUT_RDWR); close(t->fd); t->fd = -1; } return 0; } static int sco_release_cb(void *data) { struct spa_bt_transport *t = data; struct transport_data *td = t->user_data; struct impl *backend = SPA_CONTAINER_OF(t->backend, struct impl, this); spa_log_info(backend->log, "Transport %s released", t->path); spa_bt_transport_set_state(t, SPA_BT_TRANSPORT_STATE_IDLE); #ifdef HAVE_BLUEZ_5_BACKEND_HFP_NATIVE if (!mm_is_available(backend->modemmanager)) rfcomm_hfp_ag_set_cind(td->rfcomm, false); #endif sco_destroy_cb(t); return 0; } static void sco_event(struct spa_source *source) { struct spa_bt_transport *t = source->data; struct transport_data *td = t->user_data; struct impl *backend = SPA_CONTAINER_OF(t->backend, struct impl, this); if (source->rmask & (SPA_IO_HUP | SPA_IO_ERR)) { /* sco_ready() reads the socket error status in td->err */ sco_ready(t); if (td->err < 0) { spa_log_info(backend->log, "transport %p: SCO socket error: %s (%d)", t, strerror(-td->err), td->err); } else { spa_log_debug(backend->log, "transport %p: SCO socket hangup", t); } if (source->loop) spa_loop_remove_source(source->loop, source); if (t->fd >= 0) { spa_bt_transport_set_state(t, SPA_BT_TRANSPORT_STATE_IDLE); shutdown(t->fd, SHUT_RDWR); close(t->fd); t->fd = -1; } } if (source->rmask & (SPA_IO_OUT | SPA_IO_IN)) { SPA_FLAG_CLEAR(source->mask, SPA_IO_OUT | SPA_IO_IN); spa_loop_update_source(backend->main_loop, source); sco_ready(t); } } static void sco_start_source(struct spa_bt_transport *t) { struct impl *backend = SPA_CONTAINER_OF(t->backend, struct impl, this); struct transport_data *td = t->user_data; if (td->sco.loop) return; td->err = -EINPROGRESS; td->sco.func = sco_event; td->sco.data = t; td->sco.fd = t->fd; td->sco.mask = SPA_IO_HUP | SPA_IO_ERR; td->sco.rmask = 0; switch (t->device->adapter->bus_type) { case BUS_TYPE_USB: /* With USB controllers, we have to determine packet size from incoming * packets before we can send. Wait for POLLIN when connecting (not * POLLOUT as usual). */ td->sco.mask |= SPA_IO_IN; break; default: td->sco.mask |= SPA_IO_OUT; break; } spa_loop_add_source(backend->main_loop, &td->sco); } static void sco_listen_event(struct spa_source *source) { struct impl *backend = source->data; struct sockaddr_sco addr; socklen_t addrlen; char local_address[18], remote_address[18]; struct rfcomm *rfcomm; struct spa_bt_transport *t = NULL; if (source->rmask & (SPA_IO_HUP | SPA_IO_ERR)) { spa_log_error(backend->log, "error listening SCO connection: %s", strerror(errno)); return; } memset(&addr, 0, sizeof(addr)); addrlen = sizeof(addr); spa_log_debug(backend->log, "doing accept"); spa_autoclose int sock = accept(source->fd, (struct sockaddr *) &addr, &addrlen); if (sock < 0) { if (errno != EAGAIN) spa_log_error(backend->log, "SCO accept(): %s", strerror(errno)); return; } ba2str(&addr.sco_bdaddr, remote_address); memset(&addr, 0, sizeof(addr)); addrlen = sizeof(addr); if (getsockname(sock, (struct sockaddr *) &addr, &addrlen) < 0) { spa_log_error(backend->log, "SCO getsockname(): %s", strerror(errno)); return; } ba2str(&addr.sco_bdaddr, local_address); /* Find transport for local and remote address */ spa_list_for_each(rfcomm, &backend->rfcomm_list, link) { if ((rfcomm->profile & SPA_BT_PROFILE_HEADSET_AUDIO_GATEWAY) && rfcomm->transport && spa_streq(rfcomm->device->address, remote_address) && spa_streq(rfcomm->device->adapter->address, local_address)) { t = rfcomm->transport; break; } } if (!t) { spa_log_debug(backend->log, "No transport for adapter %s and remote %s", local_address, remote_address); return; } spa_assert(t->profile & SPA_BT_PROFILE_HEADSET_AUDIO_GATEWAY); if (rfcomm->telephony_ag && rfcomm->telephony_ag->transport.rejectSCO) { spa_log_info(backend->log, "rejecting SCO, AudioGatewayTransport1.RejectSCO=true"); return; } if (t->fd >= 0) { spa_log_debug(backend->log, "transport %p: Rejecting, audio already connected", t); return; } spa_log_debug(backend->log, "transport %p: codec=%s", t, t->media_codec->description); if (backend->defer_setup_enabled) { /* In BT_DEFER_SETUP mode, when a connection is accepted, the listening socket is unblocked but * the effective connection setup happens only on first receive, allowing to configure the * accepted socket. */ char buff; if (t->media_codec->id != SPA_BLUETOOTH_AUDIO_CODEC_CVSD) { /* set correct socket options for mSBC/LC3 */ struct bt_voice voice_config; memset(&voice_config, 0, sizeof(voice_config)); voice_config.setting = BT_VOICE_TRANSPARENT; if (setsockopt(sock, SOL_BLUETOOTH, BT_VOICE, &voice_config, sizeof(voice_config)) < 0) { spa_log_error(backend->log, "transport %p: setsockopt(): %s", t, strerror(errno)); return; } } /* First read from the accepted socket is non-blocking and returns a zero length buffer. */ if (read(sock, &buff, 1) == -1) { spa_log_error(backend->log, "transport %p: Couldn't authorize SCO connection: %s", t, strerror(errno)); return; } } t->fd = spa_steal_fd(sock); sco_start_source(t); spa_log_debug(backend->log, "transport %p: audio connected", t); /* Report initial volume to remote */ if (t->profile == SPA_BT_PROFILE_HSP_AG) { rfcomm_send_volume_cmd(rfcomm, hsp_hs_vgs, NULL, SPA_BT_VOLUME_ID_RX); } else if (t->profile == SPA_BT_PROFILE_HFP_AG && rfcomm->hf_state > hfp_hf_vgs) { /* Report volume only if SLC and setup sequence has been completed * else this could break the sequence. * The volumes will be reported at the end of the setup sequence. */ rfcomm_send_volume_cmd(rfcomm, hfp_hf_vgs, NULL, SPA_BT_VOLUME_ID_RX); } spa_bt_transport_set_state(t, SPA_BT_TRANSPORT_STATE_PENDING); } static void sco_listen(struct impl *backend) { struct sockaddr_sco addr; uint32_t defer = 1; spa_autoclose int sock = socket(PF_BLUETOOTH, SOCK_SEQPACKET | SOCK_NONBLOCK | SOCK_CLOEXEC, BTPROTO_SCO); if (sock < 0) { spa_log_error(backend->log, "socket(SEQPACKET, SCO) %m"); return; } /* Bind to local address */ memset(&addr, 0, sizeof(addr)); addr.sco_family = AF_BLUETOOTH; bacpy(&addr.sco_bdaddr, BDADDR_ANY); if (bind(sock, (struct sockaddr *) &addr, sizeof(addr)) < 0) { spa_log_error(backend->log, "bind(): %m"); return; } if (setsockopt(sock, SOL_BLUETOOTH, BT_DEFER_SETUP, &defer, sizeof(defer)) < 0) { spa_log_warn(backend->log, "Can't enable deferred setup: %s", strerror(errno)); backend->defer_setup_enabled = 0; } else { backend->defer_setup_enabled = 1; } spa_log_debug(backend->log, "doing listen"); if (listen(sock, 1) < 0) { spa_log_error(backend->log, "listen(): %m"); return; } backend->sco.func = sco_listen_event; backend->sco.data = backend; backend->sco.fd = spa_steal_fd(sock); backend->sco.mask = SPA_IO_IN; backend->sco.rmask = 0; spa_loop_add_source(backend->main_loop, &backend->sco); return; } static int rfcomm_ag_set_volume(struct spa_bt_transport *t, int id) { struct transport_data *td = t->user_data; struct rfcomm *rfcomm = td->rfcomm; const char *format; int value; if (!rfcomm_hw_volume_enabled(rfcomm) || !(rfcomm->profile & SPA_BT_PROFILE_HEADSET_HEAD_UNIT) || !(rfcomm->has_volume && rfcomm->volumes[id].active)) return -ENOTSUP; value = rfcomm->volumes[id].hw_volume; if (id == SPA_BT_VOLUME_ID_RX) if (rfcomm->profile & SPA_BT_PROFILE_HFP_HF) format = "+VGM: %d"; else format = "+VGM=%d"; else if (id == SPA_BT_VOLUME_ID_TX) if (rfcomm->profile & SPA_BT_PROFILE_HFP_HF) format = "+VGS: %d"; else format = "+VGS=%d"; else spa_assert_not_reached(); if (rfcomm->transport) rfcomm_send_reply(rfcomm, format, value); return 0; } static int sco_set_volume_cb(void *data, int id, float volume) { struct spa_bt_transport *t = data; struct spa_bt_transport_volume *t_volume = &t->volumes[id]; struct transport_data *td = t->user_data; struct rfcomm *rfcomm = td->rfcomm; int value; if (!rfcomm_hw_volume_enabled(rfcomm) || !(rfcomm->profile & SPA_BT_PROFILE_HEADSET_HEAD_UNIT) || !(rfcomm->has_volume && rfcomm->volumes[id].active)) return -ENOTSUP; value = spa_bt_volume_linear_to_hw(volume, t_volume->hw_volume_max); t_volume->volume = volume; if (rfcomm->volumes[id].hw_volume == value) return 0; rfcomm->volumes[id].hw_volume = value; return rfcomm_ag_set_volume(t, id); } static const struct spa_bt_transport_implementation sco_transport_impl = { SPA_VERSION_BT_TRANSPORT_IMPLEMENTATION, .acquire = sco_acquire_cb, .release = sco_release_cb, .set_volume = sco_set_volume_cb, .destroy = sco_destroy_cb, }; static struct rfcomm *device_find_rfcomm(struct impl *backend, struct spa_bt_device *device, enum spa_bt_profile profile) { struct rfcomm *rfcomm; spa_list_for_each(rfcomm, &backend->rfcomm_list, link) { if (rfcomm->device == device && (rfcomm->profile & profile)) return rfcomm; } return NULL; } static int backend_native_supports_codec(void *data, struct spa_bt_device *device, unsigned int codec) { #ifdef HAVE_BLUEZ_5_BACKEND_HFP_NATIVE struct impl *backend = data; struct rfcomm *rfcomm; rfcomm = device_find_rfcomm(backend, device, SPA_BT_PROFILE_HFP_HF); if (rfcomm == NULL) return -ENOTSUP; if (codec == HFP_AUDIO_CODEC_CVSD) return 1; if (!rfcomm->codec_negotiation_supported) return 0; return codec_list_get(backend, &rfcomm->supported_codec_list, codec) ? 1 : 0; #else return -ENOTSUP; #endif } static int codec_switch_stop_timer(struct rfcomm *rfcomm) { struct impl *backend = rfcomm->backend; struct itimerspec ts; if (rfcomm->timer.data == NULL) return 0; spa_loop_remove_source(backend->main_loop, &rfcomm->timer); ts.it_value.tv_sec = 0; ts.it_value.tv_nsec = 0; ts.it_interval.tv_sec = 0; ts.it_interval.tv_nsec = 0; spa_system_timerfd_settime(backend->main_system, rfcomm->timer.fd, 0, &ts, NULL); spa_system_close(backend->main_system, rfcomm->timer.fd); rfcomm->timer.data = NULL; return 0; } static void volume_sync_stop_timer(struct rfcomm *rfcomm) { if (rfcomm->volume_sync_timer) spa_loop_utils_update_timer(rfcomm->backend->loop_utils, rfcomm->volume_sync_timer, NULL, NULL, false); } static void volume_sync_timer_event(void *data, uint64_t expirations) { struct rfcomm *rfcomm = data; volume_sync_stop_timer(rfcomm); if (rfcomm->transport) { rfcomm_ag_set_volume(rfcomm->transport, SPA_BT_VOLUME_ID_TX); rfcomm_ag_set_volume(rfcomm->transport, SPA_BT_VOLUME_ID_RX); } } static int volume_sync_start_timer(struct rfcomm *rfcomm) { struct timespec ts; const uint64_t timeout = 1500 * SPA_NSEC_PER_MSEC; if (rfcomm->volume_sync_timer == NULL) rfcomm->volume_sync_timer = spa_loop_utils_add_timer(rfcomm->backend->loop_utils, volume_sync_timer_event, rfcomm); if (rfcomm->volume_sync_timer == NULL) return -EIO; ts.tv_sec = timeout / SPA_NSEC_PER_SEC; ts.tv_nsec = timeout % SPA_NSEC_PER_SEC; spa_loop_utils_update_timer(rfcomm->backend->loop_utils, rfcomm->volume_sync_timer, &ts, NULL, false); return 0; } static int rfcomm_ag_sync_volume(struct rfcomm *rfcomm, bool later) { if (rfcomm->transport == NULL) return -ENOENT; if (!later) { rfcomm_ag_set_volume(rfcomm->transport, SPA_BT_VOLUME_ID_TX); rfcomm_ag_set_volume(rfcomm->transport, SPA_BT_VOLUME_ID_RX); } else { volume_sync_start_timer(rfcomm); } return 0; } static void codec_switch_timer_event(struct spa_source *source) { struct rfcomm *rfcomm = source->data; struct impl *backend = rfcomm->backend; const struct media_codec *best_codec; uint64_t exp; if (spa_system_timerfd_read(backend->main_system, source->fd, &exp) < 0) spa_log_warn(backend->log, "error reading timerfd: %s", strerror(errno)); codec_switch_stop_timer(rfcomm); spa_log_debug(backend->log, "rfcomm %p: codec switch timeout", rfcomm); switch (rfcomm->hfp_ag_initial_codec_setup) { case HFP_AG_INITIAL_CODEC_SETUP_SEND: /* Retry codec selection */ best_codec = codec_list_best(backend, &rfcomm->supported_codec_list); if (best_codec && best_codec->id != SPA_BLUETOOTH_AUDIO_CODEC_CVSD) { rfcomm->hfp_ag_initial_codec_setup = HFP_AG_INITIAL_CODEC_SETUP_WAIT; rfcomm_send_reply(rfcomm, "+BCS: %u", best_codec->codec_id); codec_switch_start_timer(rfcomm, HFP_CODEC_SWITCH_TIMEOUT_MSEC); return; } SPA_FALLTHROUGH; case HFP_AG_INITIAL_CODEC_SETUP_WAIT: /* Failure, try falling back to CVSD. */ rfcomm->hfp_ag_initial_codec_setup = HFP_AG_INITIAL_CODEC_SETUP_NONE; if (rfcomm->transport == NULL) { if (rfcomm_new_transport(rfcomm, HFP_AUDIO_CODEC_CVSD) == 0) { spa_bt_device_connect_profile(rfcomm->device, rfcomm->profile); } } rfcomm_send_reply(rfcomm, "+BCS: 1"); return; default: break; } if (rfcomm->hfp_ag_switching_codec) { rfcomm->hfp_ag_switching_codec = false; if (rfcomm->device) spa_bt_device_emit_codec_switched(rfcomm->device, -EIO); } } static int codec_switch_start_timer(struct rfcomm *rfcomm, int timeout_msec) { struct impl *backend = rfcomm->backend; struct itimerspec ts; spa_log_debug(backend->log, "rfcomm %p: start timer", rfcomm); if (rfcomm->timer.data == NULL) { rfcomm->timer.data = rfcomm; rfcomm->timer.func = codec_switch_timer_event; rfcomm->timer.fd = spa_system_timerfd_create(backend->main_system, CLOCK_MONOTONIC, SPA_FD_CLOEXEC | SPA_FD_NONBLOCK); rfcomm->timer.mask = SPA_IO_IN; rfcomm->timer.rmask = 0; spa_loop_add_source(backend->main_loop, &rfcomm->timer); } ts.it_value.tv_sec = timeout_msec / SPA_MSEC_PER_SEC; ts.it_value.tv_nsec = (timeout_msec % SPA_MSEC_PER_SEC) * SPA_NSEC_PER_MSEC; ts.it_interval.tv_sec = 0; ts.it_interval.tv_nsec = 0; spa_system_timerfd_settime(backend->main_system, rfcomm->timer.fd, 0, &ts, NULL); return 0; } static int backend_native_ensure_codec(void *data, struct spa_bt_device *device, unsigned int codec) { #ifdef HAVE_BLUEZ_5_BACKEND_HFP_NATIVE struct impl *backend = data; struct rfcomm *rfcomm; int res; res = backend_native_supports_codec(data, device, codec); if (res < 0) return res; else if (!res) return -EINVAL; rfcomm = device_find_rfcomm(backend, device, SPA_BT_PROFILE_HFP_HF); if (rfcomm == NULL) return -ENOTSUP; if (!rfcomm->codec_negotiation_supported) return -ENOTSUP; if (rfcomm->codec == codec) { spa_bt_device_emit_codec_switched(device, 0); return 0; } if ((res = rfcomm_send_reply(rfcomm, "+BCS: %u", codec)) < 0) return res; rfcomm->hfp_ag_switching_codec = true; codec_switch_start_timer(rfcomm, HFP_CODEC_SWITCH_TIMEOUT_MSEC); return 0; #else return -ENOTSUP; #endif } static void device_destroy(void *data) { struct rfcomm *rfcomm = data; rfcomm_free(rfcomm); } static const struct spa_bt_device_events device_events = { SPA_VERSION_BT_DEVICE_EVENTS, .destroy = device_destroy, }; static enum spa_bt_profile path_to_profile(const char *path) { #ifdef HAVE_BLUEZ_5_BACKEND_HSP_NATIVE if (spa_streq(path, PROFILE_HSP_AG)) return SPA_BT_PROFILE_HSP_HS; if (spa_streq(path, PROFILE_HSP_HS)) return SPA_BT_PROFILE_HSP_AG; #endif #ifdef HAVE_BLUEZ_5_BACKEND_HFP_NATIVE if (spa_streq(path, PROFILE_HFP_AG)) return SPA_BT_PROFILE_HFP_HF; if (spa_streq(path, PROFILE_HFP_HF)) return SPA_BT_PROFILE_HFP_AG; #endif return SPA_BT_PROFILE_NULL; } static DBusHandlerResult profile_new_connection(DBusConnection *conn, DBusMessage *m, void *userdata) { struct impl *backend = userdata; spa_autoptr(DBusMessage) r = NULL; DBusMessageIter it; const char *handler, *path; enum spa_bt_profile profile; struct rfcomm *rfcomm; struct spa_bt_device *d; spa_autoclose int fd = -1; if (!dbus_message_has_signature(m, "oha{sv}")) { spa_log_warn(backend->log, "invalid NewConnection() signature"); return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } handler = dbus_message_get_path(m); profile = path_to_profile(handler); if (profile == SPA_BT_PROFILE_NULL) { spa_log_warn(backend->log, "invalid handler %s", handler); return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } dbus_message_iter_init(m, &it); dbus_message_iter_get_basic(&it, &path); d = spa_bt_device_find(backend->monitor, path); if (d == NULL || d->adapter == NULL) { spa_log_warn(backend->log, "unknown device for path %s", path); return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } spa_bt_device_add_profile(d, profile); dbus_message_iter_next(&it); dbus_message_iter_get_basic(&it, &fd); spa_log_debug(backend->log, "NewConnection path=%s, fd=%d, profile %s", path, fd, handler); rfcomm = calloc(1, sizeof(struct rfcomm)); if (rfcomm == NULL) return DBUS_HANDLER_RESULT_NEED_MEMORY; rfcomm->backend = backend; rfcomm->profile = profile; rfcomm->device = d; rfcomm->path = strdup(path); rfcomm->source.func = rfcomm_event; rfcomm->source.data = rfcomm; rfcomm->source.fd = spa_steal_fd(fd); rfcomm->source.mask = SPA_IO_IN; rfcomm->source.rmask = 0; spa_list_init(&rfcomm->cmd_send_queue); spa_list_init(&rfcomm->updated_call_list); /* By default all indicators are enabled */ rfcomm->cind_enabled_indicators = 0xFFFFFFFF; memset(rfcomm->hf_indicators, 0, sizeof rfcomm->hf_indicators); spa_list_init(&rfcomm->available_codec_list); spa_list_init(&rfcomm->supported_codec_list); for (int i = 0; i < SPA_BT_VOLUME_ID_TERM; ++i) { if (rfcomm->profile & SPA_BT_PROFILE_HEADSET_AUDIO_GATEWAY) rfcomm->volumes[i].active = true; rfcomm->volumes[i].hw_volume = SPA_BT_VOLUME_INVALID; } spa_bt_device_add_listener(d, &rfcomm->device_listener, &device_events, rfcomm); spa_loop_add_source(backend->main_loop, &rfcomm->source); spa_list_append(&backend->rfcomm_list, &rfcomm->link); if (profile == SPA_BT_PROFILE_HSP_HS || profile == SPA_BT_PROFILE_HSP_AG) { if (rfcomm_new_transport(rfcomm, HFP_AUDIO_CODEC_CVSD) < 0) goto fail_need_memory; rfcomm->has_volume = rfcomm_hw_volume_enabled(rfcomm); if (profile == SPA_BT_PROFILE_HSP_AG) { rfcomm->hs_state = hsp_hs_init1; } spa_bt_device_connect_profile(rfcomm->device, profile); spa_log_debug(backend->log, "Transport %s available for profile %s", rfcomm->transport->path, handler); } else if (profile == SPA_BT_PROFILE_HFP_AG) { /* Start SLC connection */ unsigned int hf_features = SPA_BT_HFP_HF_FEATURE_CLIP | SPA_BT_HFP_HF_FEATURE_3WAY | SPA_BT_HFP_HF_FEATURE_ECNR | SPA_BT_HFP_HF_FEATURE_ENHANCED_CALL_STATUS | SPA_BT_HFP_HF_FEATURE_ESCO_S4; make_available_codec_list(backend, rfcomm->device, &rfcomm->available_codec_list); rfcomm->codec_negotiation_supported = false; /* Decide if we want to signal that the HF supports mSBC/LC3 negotiation This should be done when the bluetooth adapter supports the necessary transport mode */ if (!spa_list_is_empty(&rfcomm->available_codec_list)) hf_features |= SPA_BT_HFP_HF_FEATURE_CODEC_NEGOTIATION; rfcomm->has_volume = true; hf_features |= SPA_BT_HFP_HF_FEATURE_REMOTE_VOLUME_CONTROL; /* send command to AG with the features supported by Hands-Free */ rfcomm_send_cmd(rfcomm, hfp_hf_brsf, NULL, "AT+BRSF=%u", hf_features); } else if (profile == SPA_BT_PROFILE_HFP_HF) { make_available_codec_list(backend, rfcomm->device, &rfcomm->available_codec_list); } if (rfcomm_hw_volume_enabled(rfcomm) && (profile == SPA_BT_PROFILE_HFP_HF || profile == SPA_BT_PROFILE_HSP_HS)) { uint32_t device_features; if (spa_bt_quirks_get_features(backend->quirks, d->adapter, d, &device_features) == 0) { rfcomm->broken_mic_hw_volume = !(device_features & SPA_BT_FEATURE_HW_VOLUME_MIC); if (rfcomm->broken_mic_hw_volume) spa_log_debug(backend->log, "microphone HW volume disabled by quirk"); } } if ((r = dbus_message_new_method_return(m)) == NULL) goto fail_need_memory; if (!dbus_connection_send(conn, r, NULL)) goto fail_need_memory; return DBUS_HANDLER_RESULT_HANDLED; fail_need_memory: if (rfcomm) rfcomm_free(rfcomm); return DBUS_HANDLER_RESULT_NEED_MEMORY; } static DBusHandlerResult profile_request_disconnection(DBusConnection *conn, DBusMessage *m, void *userdata) { struct impl *backend = userdata; spa_autoptr(DBusMessage) r = NULL; const char *handler, *path; struct spa_bt_device *d; enum spa_bt_profile profile = SPA_BT_PROFILE_NULL; DBusMessageIter it[5]; struct rfcomm *rfcomm, *rfcomm_tmp; if (!dbus_message_has_signature(m, "o")) { spa_log_warn(backend->log, "invalid RequestDisconnection() signature"); return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } handler = dbus_message_get_path(m); profile = path_to_profile(handler); if (profile == SPA_BT_PROFILE_NULL) { spa_log_warn(backend->log, "invalid handler %s", handler); return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } dbus_message_iter_init(m, &it[0]); dbus_message_iter_get_basic(&it[0], &path); d = spa_bt_device_find(backend->monitor, path); if (d == NULL || d->adapter == NULL) { spa_log_warn(backend->log, "unknown device for path %s", path); return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } spa_list_for_each_safe(rfcomm, rfcomm_tmp, &backend->rfcomm_list, link) { if (rfcomm->device == d && rfcomm->profile == profile) { rfcomm_free(rfcomm); } } spa_bt_device_check_profiles(d, false); if ((r = dbus_message_new_method_return(m)) == NULL) return DBUS_HANDLER_RESULT_NEED_MEMORY; if (!dbus_connection_send(conn, r, NULL)) return DBUS_HANDLER_RESULT_NEED_MEMORY; return DBUS_HANDLER_RESULT_HANDLED; } static DBusHandlerResult profile_handler(DBusConnection *c, DBusMessage *m, void *userdata) { struct impl *backend = userdata; const char *path, *interface, *member; DBusHandlerResult res; path = dbus_message_get_path(m); interface = dbus_message_get_interface(m); member = dbus_message_get_member(m); spa_log_debug(backend->log, "dbus: path=%s, interface=%s, member=%s", path, interface, member); if (dbus_message_is_method_call(m, "org.freedesktop.DBus.Introspectable", "Introspect")) { const char *xml = PROFILE_INTROSPECT_XML; spa_autoptr(DBusMessage) r = NULL; if ((r = dbus_message_new_method_return(m)) == NULL) return DBUS_HANDLER_RESULT_NEED_MEMORY; if (!dbus_message_append_args(r, DBUS_TYPE_STRING, &xml, DBUS_TYPE_INVALID)) return DBUS_HANDLER_RESULT_NEED_MEMORY; if (!dbus_connection_send(backend->conn, r, NULL)) return DBUS_HANDLER_RESULT_NEED_MEMORY; res = DBUS_HANDLER_RESULT_HANDLED; } else if (dbus_message_is_method_call(m, BLUEZ_PROFILE_INTERFACE, "Release")) res = profile_release(c, m, userdata); else if (dbus_message_is_method_call(m, BLUEZ_PROFILE_INTERFACE, "RequestDisconnection")) res = profile_request_disconnection(c, m, userdata); else if (dbus_message_is_method_call(m, BLUEZ_PROFILE_INTERFACE, "NewConnection")) res = profile_new_connection(c, m, userdata); else res = DBUS_HANDLER_RESULT_NOT_YET_HANDLED; return res; } static void register_profile_reply(DBusPendingCall *pending, void *user_data) { struct impl *backend = user_data; spa_autoptr(DBusMessage) r = steal_reply_and_unref(&pending); if (r == NULL) return; if (dbus_message_is_error(r, BLUEZ_ERROR_NOT_SUPPORTED)) { spa_log_warn(backend->log, "Register profile not supported"); return; } if (dbus_message_is_error(r, DBUS_ERROR_UNKNOWN_METHOD)) { spa_log_warn(backend->log, "Error registering profile"); return; } if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) { spa_log_error(backend->log, "RegisterProfile() failed: %s", dbus_message_get_error_name(r)); return; } } static int register_profile(struct impl *backend, const char *profile, const char *uuid) { spa_autoptr(DBusMessage) m = NULL; DBusMessageIter it[4]; dbus_bool_t autoconnect; dbus_uint16_t version, chan, features; const char *str; if (!(backend->enabled_profiles & spa_bt_profile_from_uuid(uuid))) return -ECANCELED; spa_log_debug(backend->log, "Registering Profile %s %s", profile, uuid); m = dbus_message_new_method_call(BLUEZ_SERVICE, "/org/bluez", BLUEZ_PROFILE_MANAGER_INTERFACE, "RegisterProfile"); if (m == NULL) return -ENOMEM; dbus_message_iter_init_append(m, &it[0]); dbus_message_iter_append_basic(&it[0], DBUS_TYPE_OBJECT_PATH, &profile); dbus_message_iter_append_basic(&it[0], DBUS_TYPE_STRING, &uuid); dbus_message_iter_open_container(&it[0], DBUS_TYPE_ARRAY, "{sv}", &it[1]); if (spa_streq(uuid, SPA_BT_UUID_HSP_HS) || spa_streq(uuid, SPA_BT_UUID_HSP_HS_ALT)) { /* In the headset role, the connection will only be initiated from the remote side */ str = "AutoConnect"; autoconnect = 0; dbus_message_iter_open_container(&it[1], DBUS_TYPE_DICT_ENTRY, NULL, &it[2]); dbus_message_iter_append_basic(&it[2], DBUS_TYPE_STRING, &str); dbus_message_iter_open_container(&it[2], DBUS_TYPE_VARIANT, "b", &it[3]); dbus_message_iter_append_basic(&it[3], DBUS_TYPE_BOOLEAN, &autoconnect); dbus_message_iter_close_container(&it[2], &it[3]); dbus_message_iter_close_container(&it[1], &it[2]); str = "Channel"; chan = HSP_HS_DEFAULT_CHANNEL; dbus_message_iter_open_container(&it[1], DBUS_TYPE_DICT_ENTRY, NULL, &it[2]); dbus_message_iter_append_basic(&it[2], DBUS_TYPE_STRING, &str); dbus_message_iter_open_container(&it[2], DBUS_TYPE_VARIANT, "q", &it[3]); dbus_message_iter_append_basic(&it[3], DBUS_TYPE_UINT16, &chan); dbus_message_iter_close_container(&it[2], &it[3]); dbus_message_iter_close_container(&it[1], &it[2]); /* HSP version 1.2 */ str = "Version"; version = 0x0102; dbus_message_iter_open_container(&it[1], DBUS_TYPE_DICT_ENTRY, NULL, &it[2]); dbus_message_iter_append_basic(&it[2], DBUS_TYPE_STRING, &str); dbus_message_iter_open_container(&it[2], DBUS_TYPE_VARIANT, "q", &it[3]); dbus_message_iter_append_basic(&it[3], DBUS_TYPE_UINT16, &version); dbus_message_iter_close_container(&it[2], &it[3]); dbus_message_iter_close_container(&it[1], &it[2]); } else if (spa_streq(uuid, SPA_BT_UUID_HFP_AG)) { str = "Features"; features = 0; if (spa_bt_get_hfp_codec(backend->monitor, HFP_AUDIO_CODEC_MSBC)) features |= SPA_BT_HFP_SDP_AG_FEATURE_WIDEBAND_SPEECH; if (spa_bt_get_hfp_codec(backend->monitor, HFP_AUDIO_CODEC_LC3_SWB)) features |= SPA_BT_HFP_SDP_AG_FEATURE_SUPER_WIDEBAND_SPEECH; dbus_message_iter_open_container(&it[1], DBUS_TYPE_DICT_ENTRY, NULL, &it[2]); dbus_message_iter_append_basic(&it[2], DBUS_TYPE_STRING, &str); dbus_message_iter_open_container(&it[2], DBUS_TYPE_VARIANT, "q", &it[3]); dbus_message_iter_append_basic(&it[3], DBUS_TYPE_UINT16, &features); dbus_message_iter_close_container(&it[2], &it[3]); dbus_message_iter_close_container(&it[1], &it[2]); /* HFP version 1.9 */ str = "Version"; version = 0x0109; dbus_message_iter_open_container(&it[1], DBUS_TYPE_DICT_ENTRY, NULL, &it[2]); dbus_message_iter_append_basic(&it[2], DBUS_TYPE_STRING, &str); dbus_message_iter_open_container(&it[2], DBUS_TYPE_VARIANT, "q", &it[3]); dbus_message_iter_append_basic(&it[3], DBUS_TYPE_UINT16, &version); dbus_message_iter_close_container(&it[2], &it[3]); dbus_message_iter_close_container(&it[1], &it[2]); } else if (spa_streq(uuid, SPA_BT_UUID_HFP_HF)) { str = "Features"; features = 0; if (spa_bt_get_hfp_codec(backend->monitor, HFP_AUDIO_CODEC_MSBC)) features |= SPA_BT_HFP_SDP_HF_FEATURE_WIDEBAND_SPEECH; if (spa_bt_get_hfp_codec(backend->monitor, HFP_AUDIO_CODEC_LC3_SWB)) features |= SPA_BT_HFP_SDP_HF_FEATURE_SUPER_WIDEBAND_SPEECH; dbus_message_iter_open_container(&it[1], DBUS_TYPE_DICT_ENTRY, NULL, &it[2]); dbus_message_iter_append_basic(&it[2], DBUS_TYPE_STRING, &str); dbus_message_iter_open_container(&it[2], DBUS_TYPE_VARIANT, "q", &it[3]); dbus_message_iter_append_basic(&it[3], DBUS_TYPE_UINT16, &features); dbus_message_iter_close_container(&it[2], &it[3]); dbus_message_iter_close_container(&it[1], &it[2]); /* HFP version 1.9 */ str = "Version"; version = 0x0109; dbus_message_iter_open_container(&it[1], DBUS_TYPE_DICT_ENTRY, NULL, &it[2]); dbus_message_iter_append_basic(&it[2], DBUS_TYPE_STRING, &str); dbus_message_iter_open_container(&it[2], DBUS_TYPE_VARIANT, "q", &it[3]); dbus_message_iter_append_basic(&it[3], DBUS_TYPE_UINT16, &version); dbus_message_iter_close_container(&it[2], &it[3]); dbus_message_iter_close_container(&it[1], &it[2]); } dbus_message_iter_close_container(&it[0], &it[1]); if (!send_with_reply(backend->conn, m, register_profile_reply, backend)) return -EIO; return 0; } static void unregister_profile(struct impl *backend, const char *profile) { spa_autoptr(DBusMessage) m = NULL, r = NULL; spa_auto(DBusError) err = DBUS_ERROR_INIT; spa_log_debug(backend->log, "Unregistering Profile %s", profile); m = dbus_message_new_method_call(BLUEZ_SERVICE, "/org/bluez", BLUEZ_PROFILE_MANAGER_INTERFACE, "UnregisterProfile"); if (m == NULL) return; dbus_message_append_args(m, DBUS_TYPE_OBJECT_PATH, &profile, DBUS_TYPE_INVALID); r = dbus_connection_send_with_reply_and_block(backend->conn, m, -1, &err); if (r == NULL) { spa_log_info(backend->log, "Unregistering Profile %s failed", profile); return; } if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) { spa_log_error(backend->log, "UnregisterProfile() returned error: %s", dbus_message_get_error_name(r)); return; } } static int backend_native_register_profiles(void *data) { struct impl *backend = data; #ifdef HAVE_BLUEZ_5_BACKEND_HSP_NATIVE register_profile(backend, PROFILE_HSP_AG, SPA_BT_UUID_HSP_AG); register_profile(backend, PROFILE_HSP_HS, SPA_BT_UUID_HSP_HS); #endif #ifdef HAVE_BLUEZ_5_BACKEND_HFP_NATIVE register_profile(backend, PROFILE_HFP_AG, SPA_BT_UUID_HFP_AG); register_profile(backend, PROFILE_HFP_HF, SPA_BT_UUID_HFP_HF); #endif if (backend->enabled_profiles & SPA_BT_PROFILE_HEADSET_HEAD_UNIT) sco_listen(backend); return 0; } static void sco_close(struct impl *backend) { if (backend->sco.fd >= 0) { if (backend->sco.loop) spa_loop_remove_source(backend->sco.loop, &backend->sco); shutdown(backend->sco.fd, SHUT_RDWR); close (backend->sco.fd); backend->sco.fd = -1; } } static int backend_native_unregister_profiles(void *data) { struct impl *backend = data; sco_close(backend); #ifdef HAVE_BLUEZ_5_BACKEND_HSP_NATIVE if (backend->enabled_profiles & SPA_BT_PROFILE_HSP_AG) unregister_profile(backend, PROFILE_HSP_AG); if (backend->enabled_profiles & SPA_BT_PROFILE_HSP_HS) unregister_profile(backend, PROFILE_HSP_HS); #endif #ifdef HAVE_BLUEZ_5_BACKEND_HFP_NATIVE if (backend->enabled_profiles & SPA_BT_PROFILE_HFP_AG) unregister_profile(backend, PROFILE_HFP_AG); if (backend->enabled_profiles & SPA_BT_PROFILE_HFP_HF) unregister_profile(backend, PROFILE_HFP_HF); #endif return 0; } static void send_ciev_for_each_rfcomm(struct impl *backend, int indicator, int value) { struct rfcomm *rfcomm; spa_list_for_each(rfcomm, &backend->rfcomm_list, link) { if (rfcomm->profile == SPA_BT_PROFILE_HFP_HF && rfcomm->slc_configured && ((indicator == CIND_CALL || indicator == CIND_CALLSETUP || indicator == CIND_CALLHELD) || (rfcomm->cind_call_notify && (rfcomm->cind_enabled_indicators & (1 << indicator))))) rfcomm_send_reply(rfcomm, "+CIEV: %d,%d", indicator, value); } } static void ring_timer_event(void *data, uint64_t expirations) { struct impl *backend = data; const char *number; unsigned int type; struct timespec ts; const uint64_t timeout = 1 * SPA_NSEC_PER_SEC; struct rfcomm *rfcomm; number = mm_get_incoming_call_number(backend->modemmanager); if (number) { if (spa_strstartswith(number, "+")) type = INTERNATIONAL_NUMBER; else type = NATIONAL_NUMBER; } ts.tv_sec = timeout / SPA_NSEC_PER_SEC; ts.tv_nsec = timeout % SPA_NSEC_PER_SEC; spa_loop_utils_update_timer(backend->loop_utils, backend->ring_timer, &ts, NULL, false); spa_list_for_each(rfcomm, &backend->rfcomm_list, link) { if (rfcomm->profile == SPA_BT_PROFILE_HFP_HF && rfcomm->slc_configured) { rfcomm_send_reply(rfcomm, "RING"); if (rfcomm->clip_notify && number) rfcomm_send_reply(rfcomm, "+CLIP: \"%s\",%u", number, type); } } } static void set_call_active(bool active, void *user_data) { struct impl *backend = user_data; if (backend->modem.active_call != active) { backend->modem.active_call = active; send_ciev_for_each_rfcomm(backend, CIND_CALL, active); } } static void set_call_setup(enum call_setup value, void *user_data) { struct impl *backend = user_data; enum call_setup old = backend->modem.call_setup; if (backend->modem.call_setup != value) { backend->modem.call_setup = value; send_ciev_for_each_rfcomm(backend, CIND_CALLSETUP, value); } if (value == CIND_CALLSETUP_INCOMING) { if (backend->ring_timer == NULL) backend->ring_timer = spa_loop_utils_add_timer(backend->loop_utils, ring_timer_event, backend); if (backend->ring_timer == NULL) { spa_log_warn(backend->log, "Failed to create ring timer"); return; } ring_timer_event(backend, 0); } else if (old == CIND_CALLSETUP_INCOMING) { spa_loop_utils_update_timer(backend->loop_utils, backend->ring_timer, NULL, NULL, false); } } static void set_battery_level(unsigned int level, void *user_data) { struct impl *backend = user_data; if (backend->battery_level != level) { backend->battery_level = level; send_ciev_for_each_rfcomm(backend, CIND_BATTERY_LEVEL, level); } } static void set_modem_operator_name(const char *name, void *user_data) { struct impl *backend = user_data; if (backend->modem.operator_name) { free(backend->modem.operator_name); backend->modem.operator_name = NULL; } if (name) backend->modem.operator_name = strdup(name); } static void set_modem_roaming(bool is_roaming, void *user_data) { struct impl *backend = user_data; if (backend->modem.network_is_roaming != is_roaming) { backend->modem.network_is_roaming = is_roaming; send_ciev_for_each_rfcomm(backend, CIND_ROAM, is_roaming); } } static void set_modem_own_number(const char *number, void *user_data) { struct impl *backend = user_data; if (backend->modem.own_number) { free(backend->modem.own_number); backend->modem.own_number = NULL; } if (number) backend->modem.own_number = strdup(number); } static void set_modem_service(bool available, void *user_data) { struct impl *backend = user_data; if (backend->modem.network_has_service != available) { backend->modem.network_has_service = available; send_ciev_for_each_rfcomm(backend, CIND_SERVICE, available); } } static void set_modem_signal_strength(unsigned int strength, void *user_data) { struct impl *backend = user_data; if (backend->modem.signal_strength != strength) { backend->modem.signal_strength = strength; send_ciev_for_each_rfcomm(backend, CIND_SIGNAL, strength); } } static void send_cmd_result(bool success, enum cmee_error error, void *user_data) { struct rfcomm *rfcomm = user_data; if (success) { rfcomm_send_reply(rfcomm, "OK"); return; } rfcomm_send_error(rfcomm, error); } static int backend_native_free(void *data) { struct impl *backend = data; struct rfcomm *rfcomm; sco_close(backend); if (backend->modemmanager) { mm_unregister(backend->modemmanager); backend->modemmanager = NULL; } if (backend->upower) { upower_unregister(backend->upower); backend->upower = NULL; } spa_clear_ptr(backend->telephony, telephony_free); if (backend->ring_timer) spa_loop_utils_destroy_source(backend->loop_utils, backend->ring_timer); #ifdef HAVE_BLUEZ_5_BACKEND_HSP_NATIVE dbus_connection_unregister_object_path(backend->conn, PROFILE_HSP_AG); dbus_connection_unregister_object_path(backend->conn, PROFILE_HSP_HS); #endif #ifdef HAVE_BLUEZ_5_BACKEND_HFP_NATIVE dbus_connection_unregister_object_path(backend->conn, PROFILE_HFP_AG); dbus_connection_unregister_object_path(backend->conn, PROFILE_HFP_HF); #endif spa_list_consume(rfcomm, &backend->rfcomm_list, link) rfcomm_free(rfcomm); if (backend->modem.operator_name) free(backend->modem.operator_name); free(backend); return 0; } static int parse_headset_roles(struct impl *backend, const struct spa_dict *info) { const char *str; int profiles = SPA_BT_PROFILE_NULL; if (!info) goto fallback; if ((str = spa_dict_lookup(info, PROP_KEY_ROLES)) == NULL && (str = spa_dict_lookup(info, PROP_KEY_HEADSET_ROLES)) == NULL) goto fallback; profiles = spa_bt_profiles_from_json_array(str); if (profiles < 0) goto fallback; backend->enabled_profiles = profiles & SPA_BT_PROFILE_HEADSET_AUDIO; return 0; fallback: backend->enabled_profiles = DEFAULT_ENABLED_PROFILES; return 0; } static void parse_hfp_disable_nrec(struct impl *backend, const struct spa_dict *info) { const char *str; if ((str = spa_dict_lookup(info, PROP_KEY_HFP_DISABLE_NREC)) != NULL) backend->hfp_disable_nrec = spa_atob(str); else backend->hfp_disable_nrec = false; } static void parse_hfp_default_volumes(struct impl *backend, const struct spa_dict *info) { const char *str; int vol = -1; if ((str = spa_dict_lookup(info, PROP_KEY_HFP_DEFAULT_MIC_VOL)) != NULL) spa_atoi32(str, &vol, 10); if (vol >= 0 && vol <= 15) backend->hfp_default_mic_volume = vol; else backend->hfp_default_mic_volume = SPA_BT_VOLUME_HS_MAX; vol = -1; if ((str = spa_dict_lookup(info, PROP_KEY_HFP_DEFAULT_SPEAKER_VOL)) != NULL) spa_atoi32(str, &vol, 10); if (vol >= 0 && vol <= 15) backend->hfp_default_speaker_volume = vol; else backend->hfp_default_speaker_volume = SPA_BT_VOLUME_HS_MAX; } static const struct spa_bt_backend_implementation backend_impl = { SPA_VERSION_BT_BACKEND_IMPLEMENTATION, .free = backend_native_free, .register_profiles = backend_native_register_profiles, .unregister_profiles = backend_native_unregister_profiles, .ensure_codec = backend_native_ensure_codec, .supports_codec = backend_native_supports_codec, }; static const struct mm_ops mm_ops = { .send_cmd_result = send_cmd_result, .set_modem_service = set_modem_service, .set_modem_signal_strength = set_modem_signal_strength, .set_modem_operator_name = set_modem_operator_name, .set_modem_own_number = set_modem_own_number, .set_modem_roaming = set_modem_roaming, .set_call_active = set_call_active, .set_call_setup = set_call_setup, }; struct spa_bt_backend *backend_native_new(struct spa_bt_monitor *monitor, void *dbus_connection, const struct spa_dict *info, const struct spa_bt_quirks *quirks, const struct spa_support *support, uint32_t n_support) { struct impl *backend; static const DBusObjectPathVTable vtable_profile = { .message_function = profile_handler, }; backend = calloc(1, sizeof(struct impl)); if (backend == NULL) return NULL; spa_bt_backend_set_implementation(&backend->this, &backend_impl, backend); backend->this.name = "native"; backend->monitor = monitor; backend->quirks = quirks; backend->log = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log); backend->dbus = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_DBus); backend->main_loop = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Loop); backend->main_system = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_System); backend->loop_utils = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_LoopUtils); backend->conn = dbus_connection; backend->sco.fd = -1; backend->codecs = spa_bt_get_media_codecs(monitor); spa_log_topic_init(backend->log, &log_topic); spa_list_init(&backend->rfcomm_list); if (parse_headset_roles(backend, info) < 0) goto fail; parse_hfp_disable_nrec(backend, info); parse_hfp_default_volumes(backend, info); #ifdef HAVE_BLUEZ_5_BACKEND_HSP_NATIVE if (!dbus_connection_register_object_path(backend->conn, PROFILE_HSP_AG, &vtable_profile, backend)) { goto fail; } if (!dbus_connection_register_object_path(backend->conn, PROFILE_HSP_HS, &vtable_profile, backend)) { goto fail1; } #endif #ifdef HAVE_BLUEZ_5_BACKEND_HFP_NATIVE if (!dbus_connection_register_object_path(backend->conn, PROFILE_HFP_AG, &vtable_profile, backend)) { goto fail2; } if (!dbus_connection_register_object_path(backend->conn, PROFILE_HFP_HF, &vtable_profile, backend)) { goto fail3; } #endif backend->modemmanager = mm_register(backend->log, backend->conn, info, &mm_ops, backend); backend->upower = upower_register(backend->log, backend->conn, set_battery_level, backend); backend->telephony = telephony_new(backend->log, backend->dbus, info); return &backend->this; #ifdef HAVE_BLUEZ_5_BACKEND_HFP_NATIVE fail3: dbus_connection_unregister_object_path(backend->conn, PROFILE_HFP_AG); fail2: #endif #ifdef HAVE_BLUEZ_5_BACKEND_HSP_NATIVE dbus_connection_unregister_object_path(backend->conn, PROFILE_HSP_HS); fail1: dbus_connection_unregister_object_path(backend->conn, PROFILE_HSP_AG); #endif fail: free(backend); return NULL; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/bluez5/backend-ofono.c000066400000000000000000000660511511204443500270310ustar00rootroot00000000000000/* Spa oFono backend */ /* SPDX-FileCopyrightText: Copyright © 2020 Collabora Ltd. */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "defs.h" #include "media-codecs.h" #include "hfp-codec-caps.h" #define INITIAL_INTERVAL_NSEC (500 * SPA_NSEC_PER_MSEC) #define ACTION_INTERVAL_NSEC (3000 * SPA_NSEC_PER_MSEC) SPA_LOG_TOPIC_DEFINE_STATIC(log_topic, "spa.bluez5.ofono"); #undef SPA_LOG_TOPIC_DEFAULT #define SPA_LOG_TOPIC_DEFAULT &log_topic struct impl { struct spa_bt_backend this; struct spa_bt_monitor *monitor; struct spa_log *log; struct spa_loop *main_loop; struct spa_system *main_system; struct spa_dbus *dbus; struct spa_loop_utils *loop_utils; DBusConnection *conn; const struct spa_bt_quirks *quirks; struct spa_source *timer; unsigned int filters_added:1; unsigned int msbc_supported:1; }; struct transport_data { struct spa_source sco; unsigned int codec_id; unsigned int broken:1; unsigned int activated:1; }; #define OFONO_HF_AUDIO_MANAGER_INTERFACE OFONO_SERVICE ".HandsfreeAudioManager" #define OFONO_HF_AUDIO_CARD_INTERFACE OFONO_SERVICE ".HandsfreeAudioCard" #define OFONO_HF_AUDIO_AGENT_INTERFACE OFONO_SERVICE ".HandsfreeAudioAgent" #define OFONO_AUDIO_CLIENT "/Profile/ofono" #define OFONO_INTROSPECT_XML \ DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \ "" \ " " \ " " \ " " \ " " \ " " \ " " \ " " \ " " \ " " \ " " \ " " \ " " \ " " \ " " \ "" #define OFONO_ERROR_INVALID_ARGUMENTS "org.ofono.Error.InvalidArguments" #define OFONO_ERROR_NOT_IMPLEMENTED "org.ofono.Error.NotImplemented" #define OFONO_ERROR_IN_USE "org.ofono.Error.InUse" #define OFONO_ERROR_FAILED "org.ofono.Error.Failed" static void ofono_transport_get_mtu(struct impl *backend, struct spa_bt_transport *t) { struct sco_options sco_opt; socklen_t len; /* Fallback values */ t->read_mtu = 144; t->write_mtu = 144; len = sizeof(sco_opt); memset(&sco_opt, 0, len); if (getsockopt(t->fd, SOL_SCO, SCO_OPTIONS, &sco_opt, &len) < 0) spa_log_warn(backend->log, "getsockopt(SCO_OPTIONS) failed: %d (%m)", errno); else { spa_log_debug(backend->log, "autodetected mtu = %u", sco_opt.mtu); t->read_mtu = sco_opt.mtu; t->write_mtu = sco_opt.mtu; } } static struct spa_bt_transport *_transport_create(struct impl *backend, const char *path, struct spa_bt_device *device, enum spa_bt_profile profile, int codec_id, struct spa_callbacks *impl) { struct spa_bt_transport *t = NULL; const struct media_codec *codec; struct transport_data *td; char *t_path; codec = spa_bt_get_hfp_codec(backend->monitor, codec_id); if (!codec) { spa_log_warn(backend->log, "can't create transport: no HFP codec %d", codec_id); return NULL; } t_path = strdup(path); t = spa_bt_transport_create(backend->monitor, t_path, sizeof(struct transport_data)); if (t == NULL) { spa_log_warn(backend->log, "can't create transport: %m"); free(t_path); return NULL; } spa_bt_transport_set_implementation(t, impl, t); t->device = device; spa_list_append(&t->device->transport_list, &t->device_link); t->backend = &backend->this; t->profile = profile; t->media_codec = codec; t->n_channels = 1; t->channels[0] = SPA_AUDIO_CHANNEL_MONO; td = t->user_data; td->codec_id = codec_id; return t; } static int _audio_acquire(struct impl *backend, const char *path, uint8_t *codec) { spa_autoptr(DBusMessage) m = NULL, r = NULL; spa_auto(DBusError) err = DBUS_ERROR_INIT; int ret = 0; m = dbus_message_new_method_call(OFONO_SERVICE, path, OFONO_HF_AUDIO_CARD_INTERFACE, "Acquire"); if (m == NULL) return -ENOMEM; /* * XXX: We assume here oFono replies. It however can happen that the headset does * XXX: not properly respond to the codec negotiation RFCOMM commands. * XXX: oFono (1.34) fails to handle this condition, and will not send DBus reply * XXX: in this case. The transport acquire API is synchronous, so we can't * XXX: do better here right now. */ r = dbus_connection_send_with_reply_and_block(backend->conn, m, -1, &err); if (r == NULL) { spa_log_error(backend->log, "Transport Acquire() failed for transport %s (%s)", path, err.message); return -EIO; } if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) { spa_log_error(backend->log, "Acquire returned error: %s", dbus_message_get_error_name(r)); return -EIO; } if (!dbus_message_get_args(r, &err, DBUS_TYPE_UNIX_FD, &ret, DBUS_TYPE_BYTE, codec, DBUS_TYPE_INVALID)) { spa_log_error(backend->log, "Failed to parse Acquire() reply: %s", err.message); return -EIO; } return ret; } static int ofono_audio_acquire(void *data, bool optional) { struct spa_bt_transport *transport = data; struct transport_data *td = transport->user_data; struct impl *backend = SPA_CONTAINER_OF(transport->backend, struct impl, this); uint8_t codec_id; int ret = 0; if (transport->fd >= 0) goto finish; if (td->broken) { ret = -EIO; goto finish; } spa_bt_device_update_last_bluez_action_time(transport->device); ret = _audio_acquire(backend, transport->path, &codec_id); if (ret < 0) goto finish; transport->fd = ret; if (transport->media_codec->codec_id != codec_id) { struct timespec ts; spa_log_info(backend->log, "transport %p: acquired codec (%d) differs from transport one (%d)", transport, codec_id, transport->media_codec->codec_id); /* shutdown to make sure connection is dropped immediately */ shutdown(transport->fd, SHUT_RDWR); close(transport->fd); transport->fd = -1; /* schedule immediate profile update, from main loop */ td->codec_id = codec_id; td->broken = true; ts.tv_sec = 0; ts.tv_nsec = 1; spa_loop_utils_update_timer(backend->loop_utils, backend->timer, &ts, NULL, false); ret = -EIO; goto finish; } td->broken = false; spa_log_debug(backend->log, "transport %p: Acquire %s, fd %d codec %s", transport, transport->path, transport->fd, transport->media_codec->description); ofono_transport_get_mtu(backend, transport); ret = 0; finish: if (ret < 0) spa_bt_transport_set_state(transport, SPA_BT_TRANSPORT_STATE_ERROR); else spa_bt_transport_set_state(transport, SPA_BT_TRANSPORT_STATE_ACTIVE); return ret; } static int ofono_audio_release(void *data) { struct spa_bt_transport *transport = data; struct impl *backend = SPA_CONTAINER_OF(transport->backend, struct impl, this); spa_log_debug(backend->log, "transport %p: Release %s", transport, transport->path); spa_bt_transport_set_state(transport, SPA_BT_TRANSPORT_STATE_IDLE); if (transport->sco_io) { spa_bt_sco_io_destroy(transport->sco_io); transport->sco_io = NULL; } /* shutdown to make sure connection is dropped immediately */ shutdown(transport->fd, SHUT_RDWR); close(transport->fd); transport->fd = -1; return 0; } static DBusHandlerResult ofono_audio_card_removed(struct impl *backend, const char *path) { struct spa_bt_transport *transport; spa_assert(backend); spa_assert(path); spa_log_debug(backend->log, "card removed: %s", path); transport = spa_bt_transport_find(backend->monitor, path); if (transport != NULL) { struct spa_bt_device *device = transport->device; spa_log_debug(backend->log, "transport %p: free %s", transport, transport->path); spa_bt_transport_free(transport); if (device != NULL) spa_bt_device_check_profiles(device, false); } return DBUS_HANDLER_RESULT_HANDLED; } static const struct spa_bt_transport_implementation ofono_transport_impl = { SPA_VERSION_BT_TRANSPORT_IMPLEMENTATION, .acquire = ofono_audio_acquire, .release = ofono_audio_release, }; static bool activate_transport(struct spa_bt_transport *t, const void *data) { struct impl *backend = (void *)data; struct transport_data *td = t->user_data; struct timespec ts; uint64_t now, threshold; if (t->backend != &backend->this) return false; /* Check device-specific rate limit */ spa_system_clock_gettime(backend->main_system, CLOCK_MONOTONIC, &ts); now = SPA_TIMESPEC_TO_NSEC(&ts); threshold = t->device->last_bluez_action_time + ACTION_INTERVAL_NSEC; if (now < threshold) { ts.tv_sec = (threshold - now) / SPA_NSEC_PER_SEC; ts.tv_nsec = (threshold - now) % SPA_NSEC_PER_SEC; spa_loop_utils_update_timer(backend->loop_utils, backend->timer, &ts, NULL, false); return false; } if (!td->activated) { /* Connect profile */ spa_log_debug(backend->log, "Transport %s activated", t->path); td->activated = true; spa_bt_device_connect_profile(t->device, t->profile); } if (td->broken) { /* Recreate the transport */ struct spa_bt_transport *t_copy; t_copy = _transport_create(backend, t->path, t->device, t->profile, td->codec_id, (struct spa_callbacks *)&ofono_transport_impl); spa_bt_transport_free(t); if (t_copy) spa_bt_device_connect_profile(t_copy->device, t_copy->profile); return true; } return false; } static void activate_transports(struct impl *backend) { while (spa_bt_transport_find_full(backend->monitor, activate_transport, backend)); } static void activate_timer_event(void *userdata, uint64_t expirations) { struct impl *backend = userdata; spa_loop_utils_update_timer(backend->loop_utils, backend->timer, NULL, NULL, false); activate_transports(backend); } static DBusHandlerResult ofono_audio_card_found(struct impl *backend, char *path, DBusMessageIter *props_i) { const char *remote_address = NULL; const char *local_address = NULL; struct spa_bt_device *d; struct spa_bt_transport *t; struct transport_data *td; enum spa_bt_profile profile = SPA_BT_PROFILE_HFP_AG; uint8_t codec_id = backend->msbc_supported ? HFP_AUDIO_CODEC_MSBC : HFP_AUDIO_CODEC_CVSD; spa_assert(backend); spa_assert(path); spa_assert(props_i); spa_log_debug(backend->log, "new card: %s", path); while (dbus_message_iter_get_arg_type(props_i) != DBUS_TYPE_INVALID) { DBusMessageIter i, value_i; const char *key, *value; char c; dbus_message_iter_recurse(props_i, &i); dbus_message_iter_get_basic(&i, &key); dbus_message_iter_next(&i); dbus_message_iter_recurse(&i, &value_i); if ((c = dbus_message_iter_get_arg_type(&value_i)) != DBUS_TYPE_STRING) { spa_log_error(backend->log, "Invalid properties for %s: expected 's', received '%c'", path, c); return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } dbus_message_iter_get_basic(&value_i, &value); if (spa_streq(key, "RemoteAddress")) { remote_address = value; } else if (spa_streq(key, "LocalAddress")) { local_address = value; } else if (spa_streq(key, "Type")) { if (spa_streq(value, "gateway")) profile = SPA_BT_PROFILE_HFP_HF; } spa_log_debug(backend->log, "%s: %s", key, value); dbus_message_iter_next(props_i); } if (!remote_address || !local_address) { spa_log_error(backend->log, "Missing addresses for %s", path); return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } d = spa_bt_device_find_by_address(backend->monitor, remote_address, local_address); if (!d || !d->adapter) { spa_log_error(backend->log, "Device doesn’t exist for %s", path); return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } spa_bt_device_add_profile(d, profile); t = _transport_create(backend, path, d, profile, codec_id, (struct spa_callbacks *)&ofono_transport_impl); if (t == NULL) { spa_log_error(backend->log, "failed to create transport: %s", spa_strerror(-errno)); return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } td = t->user_data; /* * For HF profile, delay profile connect, so that we likely don't do it at the * same time as the device is busy with A2DP connect. This avoids some oFono * misbehavior (see comment in _audio_acquire above). * * For AG mode, we delay the emission of the nodes, so it is not necessary * to know the codec in advance. */ if (profile == SPA_BT_PROFILE_HFP_HF) { struct timespec ts; ts.tv_sec = INITIAL_INTERVAL_NSEC / SPA_NSEC_PER_SEC; ts.tv_nsec = INITIAL_INTERVAL_NSEC % SPA_NSEC_PER_SEC; spa_loop_utils_update_timer(backend->loop_utils, backend->timer, &ts, NULL, false); } else { td->activated = true; spa_bt_device_connect_profile(t->device, t->profile); } spa_log_debug(backend->log, "Transport %s available, codec %s", t->path, t->media_codec->description); return DBUS_HANDLER_RESULT_HANDLED; } static DBusHandlerResult ofono_release(DBusConnection *conn, DBusMessage *m, void *userdata) { struct impl *backend = userdata; spa_log_warn(backend->log, "release"); if (!reply_with_error(conn, m, OFONO_HF_AUDIO_AGENT_INTERFACE ".Error.NotImplemented", "Method not implemented")) return DBUS_HANDLER_RESULT_NEED_MEMORY; return DBUS_HANDLER_RESULT_HANDLED; } static void sco_event(struct spa_source *source) { struct spa_bt_transport *t = source->data; struct impl *backend = SPA_CONTAINER_OF(t->backend, struct impl, this); if (source->rmask & (SPA_IO_HUP | SPA_IO_ERR)) { spa_log_debug(backend->log, "transport %p: error on SCO socket: %s", t, strerror(errno)); if (t->fd >= 0) { if (source->loop) spa_loop_remove_source(source->loop, source); shutdown(t->fd, SHUT_RDWR); close (t->fd); t->fd = -1; spa_bt_transport_set_state(t, SPA_BT_TRANSPORT_STATE_IDLE); } } } static int enable_sco_socket(int sock) { char c; struct pollfd pfd; if (sock < 0) return ENOTCONN; memset(&pfd, 0, sizeof(pfd)); pfd.fd = sock; pfd.events = POLLOUT; if (poll(&pfd, 1, 0) < 0) return errno; /* * If socket already writable then it is not in defer setup state, * otherwise it needs to be read to authorize the connection. */ if ((pfd.revents & POLLOUT)) return 0; /* Enable socket by reading 1 byte */ if (read(sock, &c, 1) < 0) return errno; return 0; } static DBusHandlerResult ofono_new_audio_connection(DBusConnection *conn, DBusMessage *m, void *userdata) { struct impl *backend = userdata; const char *path; int fd; uint8_t codec_id; struct spa_bt_transport *t; struct transport_data *td; spa_autoptr(DBusMessage) r = NULL; if (dbus_message_get_args(m, NULL, DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_UNIX_FD, &fd, DBUS_TYPE_BYTE, &codec_id, DBUS_TYPE_INVALID) == FALSE) { r = dbus_message_new_error(m, OFONO_ERROR_INVALID_ARGUMENTS, "Invalid arguments in method call"); goto fail; } t = spa_bt_transport_find(backend->monitor, path); if (t && (t->profile & SPA_BT_PROFILE_HEADSET_AUDIO_GATEWAY)) { int err; const struct media_codec *codec; codec = spa_bt_get_hfp_codec(backend->monitor, codec_id); if (!codec) { spa_log_error(backend->log, "transport %p: Couldn't find HFP codec %d", t, codec_id); shutdown(fd, SHUT_RDWR); close(fd); goto fail; } err = enable_sco_socket(fd); if (err) { spa_log_error(backend->log, "transport %p: Couldn't authorize SCO connection: %s", t, strerror(err)); r = dbus_message_new_error(m, OFONO_ERROR_FAILED, "SCO authorization failed"); shutdown(fd, SHUT_RDWR); close(fd); goto fail; } t->fd = fd; t->media_codec = codec; spa_log_debug(backend->log, "transport %p: NewConnection %s, fd %d codec %s", t, t->path, t->fd, t->media_codec->description); td = t->user_data; td->sco.func = sco_event; td->sco.data = t; td->sco.fd = fd; td->sco.mask = SPA_IO_HUP | SPA_IO_ERR; td->sco.rmask = 0; spa_loop_add_source(backend->main_loop, &td->sco); ofono_transport_get_mtu(backend, t); spa_bt_transport_set_state (t, SPA_BT_TRANSPORT_STATE_PENDING); } else if (fd) { spa_log_debug(backend->log, "ignoring NewConnection"); r = dbus_message_new_error(m, OFONO_ERROR_NOT_IMPLEMENTED, "Method not implemented"); shutdown(fd, SHUT_RDWR); close(fd); } fail: if (r) { if (!dbus_connection_send(backend->conn, r, NULL)) return DBUS_HANDLER_RESULT_NEED_MEMORY; return DBUS_HANDLER_RESULT_HANDLED; } return DBUS_HANDLER_RESULT_HANDLED; } static DBusHandlerResult ofono_handler(DBusConnection *c, DBusMessage *m, void *userdata) { struct impl *backend = userdata; const char *path, *interface, *member; DBusHandlerResult res; path = dbus_message_get_path(m); interface = dbus_message_get_interface(m); member = dbus_message_get_member(m); spa_log_debug(backend->log, "path=%s, interface=%s, member=%s", path, interface, member); if (dbus_message_is_method_call(m, "org.freedesktop.DBus.Introspectable", "Introspect")) { const char *xml = OFONO_INTROSPECT_XML; spa_autoptr(DBusMessage) r = NULL; if ((r = dbus_message_new_method_return(m)) == NULL) return DBUS_HANDLER_RESULT_NEED_MEMORY; if (!dbus_message_append_args(r, DBUS_TYPE_STRING, &xml, DBUS_TYPE_INVALID)) return DBUS_HANDLER_RESULT_NEED_MEMORY; if (!dbus_connection_send(backend->conn, r, NULL)) return DBUS_HANDLER_RESULT_NEED_MEMORY; res = DBUS_HANDLER_RESULT_HANDLED; } else if (dbus_message_is_method_call(m, OFONO_HF_AUDIO_AGENT_INTERFACE, "Release")) res = ofono_release(c, m, userdata); else if (dbus_message_is_method_call(m, OFONO_HF_AUDIO_AGENT_INTERFACE, "NewConnection")) res = ofono_new_audio_connection(c, m, userdata); else res = DBUS_HANDLER_RESULT_NOT_YET_HANDLED; return res; } static void ofono_getcards_reply(DBusPendingCall *pending, void *user_data) { struct impl *backend = user_data; DBusMessageIter i, array_i, struct_i, props_i; spa_autoptr(DBusMessage) r = steal_reply_and_unref(&pending); if (r == NULL) return; if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) { spa_log_error(backend->log, "Failed to get a list of handsfree audio cards: %s", dbus_message_get_error_name(r)); return; } if (!dbus_message_iter_init(r, &i) || !spa_streq(dbus_message_get_signature(r), "a(oa{sv})")) { spa_log_error(backend->log, "Invalid arguments in GetCards() reply"); return; } dbus_message_iter_recurse(&i, &array_i); while (dbus_message_iter_get_arg_type(&array_i) != DBUS_TYPE_INVALID) { char *path; dbus_message_iter_recurse(&array_i, &struct_i); dbus_message_iter_get_basic(&struct_i, &path); dbus_message_iter_next(&struct_i); dbus_message_iter_recurse(&struct_i, &props_i); ofono_audio_card_found(backend, path, &props_i); dbus_message_iter_next(&array_i); } } static int ofono_register(struct impl *backend) { spa_autoptr(DBusMessage) m = NULL, r = NULL; const char *path = OFONO_AUDIO_CLIENT; uint8_t codecs[2]; const uint8_t *pcodecs = codecs; int ncodecs = 0; spa_auto(DBusError) err = DBUS_ERROR_INIT; spa_log_debug(backend->log, "Registering"); m = dbus_message_new_method_call(OFONO_SERVICE, "/", OFONO_HF_AUDIO_MANAGER_INTERFACE, "Register"); if (m == NULL) return -ENOMEM; codecs[ncodecs++] = HFP_AUDIO_CODEC_CVSD; if (backend->msbc_supported) codecs[ncodecs++] = HFP_AUDIO_CODEC_MSBC; dbus_message_append_args(m, DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE, &pcodecs, ncodecs, DBUS_TYPE_INVALID); r = dbus_connection_send_with_reply_and_block(backend->conn, m, -1, &err); if (r == NULL) { if (dbus_error_has_name(&err, "org.freedesktop.DBus.Error.ServiceUnknown")) { spa_log_info(backend->log, "oFono not available: %s", err.message); return -ENOTSUP; } else { spa_log_warn(backend->log, "Registering Profile %s failed: %s (%s)", path, err.message, err.name); return -EIO; } } if (dbus_message_is_error(r, OFONO_ERROR_INVALID_ARGUMENTS)) { spa_log_warn(backend->log, "invalid arguments"); return -EIO; } if (dbus_message_is_error(r, OFONO_ERROR_IN_USE)) { spa_log_warn(backend->log, "already in use"); return -EIO; } if (dbus_message_is_error(r, DBUS_ERROR_UNKNOWN_METHOD)) { spa_log_warn(backend->log, "Error registering profile"); return -EIO; } if (dbus_message_is_error(r, DBUS_ERROR_SERVICE_UNKNOWN)) { spa_log_info(backend->log, "oFono not available, disabling"); return -EIO; } if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) { spa_log_error(backend->log, "Register() failed: %s", dbus_message_get_error_name(r)); return -EIO; } spa_log_debug(backend->log, "registered"); return 0; } static int ofono_getcards(struct impl *backend) { spa_autoptr(DBusMessage) m = NULL; m = dbus_message_new_method_call(OFONO_SERVICE, "/", OFONO_HF_AUDIO_MANAGER_INTERFACE, "GetCards"); if (m == NULL) return -ENOMEM; if (!send_with_reply(backend->conn, m, ofono_getcards_reply, backend)) return -EIO; return 0; } static int backend_ofono_register(void *data) { int ret = ofono_register(data); if (ret < 0) return ret; ret = ofono_getcards(data); if (ret < 0) return ret; return 0; } static DBusHandlerResult ofono_filter_cb(DBusConnection *bus, DBusMessage *m, void *user_data) { struct impl *backend = user_data; if (dbus_message_is_signal(m, OFONO_HF_AUDIO_MANAGER_INTERFACE, "CardAdded")) { char *p; DBusMessageIter arg_i, props_i; if (!dbus_message_iter_init(m, &arg_i) || !spa_streq(dbus_message_get_signature(m), "oa{sv}")) { spa_log_error(backend->log, "Failed to parse org.ofono.HandsfreeAudioManager.CardAdded"); goto fail; } dbus_message_iter_get_basic(&arg_i, &p); dbus_message_iter_next(&arg_i); spa_assert(dbus_message_iter_get_arg_type(&arg_i) == DBUS_TYPE_ARRAY); dbus_message_iter_recurse(&arg_i, &props_i); return ofono_audio_card_found(backend, p, &props_i); } else if (dbus_message_is_signal(m, OFONO_HF_AUDIO_MANAGER_INTERFACE, "CardRemoved")) { const char *p; spa_auto(DBusError) err = DBUS_ERROR_INIT; if (!dbus_message_get_args(m, &err, DBUS_TYPE_OBJECT_PATH, &p, DBUS_TYPE_INVALID)) { spa_log_error(backend->log, "Failed to parse org.ofono.HandsfreeAudioManager.CardRemoved: %s", err.message); goto fail; } return ofono_audio_card_removed(backend, p); } fail: return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } static int add_filters(struct impl *backend) { if (backend->filters_added) return 0; if (!dbus_connection_add_filter(backend->conn, ofono_filter_cb, backend, NULL)) { spa_log_error(backend->log, "failed to add filter function"); return -EIO; } spa_auto(DBusError) err = DBUS_ERROR_INIT; dbus_bus_add_match(backend->conn, "type='signal',sender='" OFONO_SERVICE "'," "interface='" OFONO_HF_AUDIO_MANAGER_INTERFACE "',member='CardAdded'", &err); dbus_bus_add_match(backend->conn, "type='signal',sender='" OFONO_SERVICE "'," "interface='" OFONO_HF_AUDIO_MANAGER_INTERFACE "',member='CardRemoved'", &err); backend->filters_added = true; return 0; } static int backend_ofono_free(void *data) { struct impl *backend = data; if (backend->filters_added) { dbus_connection_remove_filter(backend->conn, ofono_filter_cb, backend); backend->filters_added = false; } if (backend->timer) spa_loop_utils_destroy_source(backend->loop_utils, backend->timer); dbus_connection_unregister_object_path(backend->conn, OFONO_AUDIO_CLIENT); free(backend); return 0; } static int backend_ofono_supports_codec(void *data, struct spa_bt_device *device, unsigned int codec) { struct impl *backend = data; switch (codec) { case HFP_AUDIO_CODEC_CVSD: return 1; case HFP_AUDIO_CODEC_MSBC: return backend->msbc_supported; } return 0; } static const struct spa_bt_backend_implementation backend_impl = { SPA_VERSION_BT_BACKEND_IMPLEMENTATION, .free = backend_ofono_free, .register_profiles = backend_ofono_register, .supports_codec = backend_ofono_supports_codec, }; static bool is_available(struct impl *backend) { spa_autoptr(DBusMessage) m = NULL, r = NULL; spa_auto(DBusError) err = DBUS_ERROR_INIT; m = dbus_message_new_method_call(OFONO_SERVICE, "/", DBUS_INTERFACE_INTROSPECTABLE, "Introspect"); if (m == NULL) return false; r = dbus_connection_send_with_reply_and_block(backend->conn, m, -1, &err); if (r && dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_METHOD_RETURN) return true; return false; } struct spa_bt_backend *backend_ofono_new(struct spa_bt_monitor *monitor, void *dbus_connection, const struct spa_dict *info, const struct spa_bt_quirks *quirks, const struct spa_support *support, uint32_t n_support) { struct impl *backend; const char *str; static const DBusObjectPathVTable vtable_profile = { .message_function = ofono_handler, }; backend = calloc(1, sizeof(struct impl)); if (backend == NULL) return NULL; spa_bt_backend_set_implementation(&backend->this, &backend_impl, backend); backend->this.name = "ofono"; backend->this.exclusive = true; backend->monitor = monitor; backend->quirks = quirks; backend->log = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log); backend->dbus = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_DBus); backend->main_loop = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Loop); backend->main_system = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_System); backend->loop_utils = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_LoopUtils); backend->conn = dbus_connection; if (info && (str = spa_dict_lookup(info, "bluez5.enable-msbc"))) backend->msbc_supported = spa_atob(str); else backend->msbc_supported = false; if (!spa_bt_get_hfp_codec(monitor, HFP_AUDIO_CODEC_MSBC)) backend->msbc_supported = false; spa_log_topic_init(backend->log, &log_topic); backend->timer = spa_loop_utils_add_timer(backend->loop_utils, activate_timer_event, backend); if (backend->timer == NULL) { free(backend); return NULL; } if (!dbus_connection_register_object_path(backend->conn, OFONO_AUDIO_CLIENT, &vtable_profile, backend)) { free(backend); return NULL; } if (add_filters(backend) < 0) { dbus_connection_unregister_object_path(backend->conn, OFONO_AUDIO_CLIENT); free(backend); return NULL; } backend->this.available = is_available(backend); return &backend->this; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/bluez5/bap-codec-caps.h000066400000000000000000000204541511204443500270670ustar00rootroot00000000000000/* Spa BAP codec API */ /* SPDX-FileCopyrightText: Copyright © 2022 Collabora */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_BLUEZ5_BAP_CODEC_CAPS_H_ #define SPA_BLUEZ5_BAP_CODEC_CAPS_H_ #include #define BAP_CODEC_LC3 0x06 #define LC3_TYPE_FREQ 0x01 #define LC3_FREQ_8KHZ (1 << 0) #define LC3_FREQ_11KHZ (1 << 1) #define LC3_FREQ_16KHZ (1 << 2) #define LC3_FREQ_22KHZ (1 << 3) #define LC3_FREQ_24KHZ (1 << 4) #define LC3_FREQ_32KHZ (1 << 5) #define LC3_FREQ_44KHZ (1 << 6) #define LC3_FREQ_48KHZ (1 << 7) #define LC3_FREQ_ANY (LC3_FREQ_8KHZ | \ LC3_FREQ_11KHZ | \ LC3_FREQ_16KHZ | \ LC3_FREQ_22KHZ | \ LC3_FREQ_24KHZ | \ LC3_FREQ_32KHZ | \ LC3_FREQ_44KHZ | \ LC3_FREQ_48KHZ) #define LC3_VAL_FREQ_8KHZ 8000 #define LC3_VAL_FREQ_11KHZ 11025 #define LC3_VAL_FREQ_16KHZ 16000 #define LC3_VAL_FREQ_22KHZ 22050 #define LC3_VAL_FREQ_24KHZ 24000 #define LC3_VAL_FREQ_32KHZ 32000 #define LC3_VAL_FREQ_44KHZ 44100 #define LC3_VAL_FREQ_48KHZ 48000 #define LC3_TYPE_DUR 0x02 #define LC3_DUR_7_5 (1 << 0) #define LC3_DUR_10 (1 << 1) #define LC3_DUR_ANY (LC3_DUR_7_5 | \ LC3_DUR_10) #define LC3_VAL_DUR_7_5 7.5 #define LC3_VAL_DUR_10 10 #define LC3_TYPE_CHAN 0x03 #define LC3_CHAN_1 (1 << 0) #define LC3_CHAN_2 (1 << 1) #define LC3_CHAN_3 (1 << 2) #define LC3_CHAN_4 (1 << 3) #define LC3_CHAN_5 (1 << 4) #define LC3_CHAN_6 (1 << 5) #define LC3_CHAN_7 (1 << 6) #define LC3_CHAN_8 (1 << 7) #define LC3_VAL_CHAN_1 1 #define LC3_VAL_CHAN_2 2 #define LC3_VAL_CHAN_3 3 #define LC3_VAL_CHAN_4 4 #define LC3_VAL_CHAN_5 5 #define LC3_VAL_CHAN_6 6 #define LC3_VAL_CHAN_7 7 #define LC3_VAL_CHAN_8 8 #define LC3_TYPE_FRAMELEN 0x04 #define LC3_TYPE_BLKS 0x05 /* LC3 config parameters */ #define LC3_CONFIG_FREQ_8KHZ 0x01 #define LC3_CONFIG_FREQ_11KHZ 0x02 #define LC3_CONFIG_FREQ_16KHZ 0x03 #define LC3_CONFIG_FREQ_22KHZ 0x04 #define LC3_CONFIG_FREQ_24KHZ 0x05 #define LC3_CONFIG_FREQ_32KHZ 0x06 #define LC3_CONFIG_FREQ_44KHZ 0x07 #define LC3_CONFIG_FREQ_48KHZ 0x08 #define LC3_CONFIG_DURATION_7_5 0x00 #define LC3_CONFIG_DURATION_10 0x01 #define LC3_MAX_CHANNELS 28 /* Metadata types */ #define BAP_META_TYPE_PREFERRED_CONTEXT 0x01 #define BAP_META_TYPE_STREAMING_CONTEXT 0x02 #define BAP_META_TYPE_PROGRAM_INFO 0x03 #define BAP_META_TYPE_LANGUAGE 0x04 #define BAP_META_TYPE_CCID_LIST 0x05 #define BAP_META_TYPE_PARENTAL_RATING 0x06 #define BAP_META_TYPE_PROGRAM_INFO_URI 0x07 #define BAP_META_TYPE_AUDIO_ACTIVE_STATE 0x08 #define BAP_META_TYPE_BCAST_IMMEDIATE 0x09 #define BAP_META_TYPE_ASSISTED_LISTENING 0x0a #define BAP_META_TYPE_BCAST_NAME 0x0b #define BAP_META_TYPE_EXTENDED 0xfe #define BAP_META_TYPE_VENDOR 0xff #define BAP_CHANNEL_MONO 0x00000000 /* mono */ #define BAP_CHANNEL_FL 0x00000001 /* front left */ #define BAP_CHANNEL_FR 0x00000002 /* front right */ #define BAP_CHANNEL_FC 0x00000004 /* front center */ #define BAP_CHANNEL_LFE 0x00000008 /* LFE */ #define BAP_CHANNEL_BL 0x00000010 /* back left */ #define BAP_CHANNEL_BR 0x00000020 /* back right */ #define BAP_CHANNEL_FLC 0x00000040 /* front left center */ #define BAP_CHANNEL_FRC 0x00000080 /* front right center */ #define BAP_CHANNEL_BC 0x00000100 /* back center */ #define BAP_CHANNEL_LFE2 0x00000200 /* LFE 2 */ #define BAP_CHANNEL_SL 0x00000400 /* side left */ #define BAP_CHANNEL_SR 0x00000800 /* side right */ #define BAP_CHANNEL_TFL 0x00001000 /* top front left */ #define BAP_CHANNEL_TFR 0x00002000 /* top front right */ #define BAP_CHANNEL_TFC 0x00004000 /* top front center */ #define BAP_CHANNEL_TC 0x00008000 /* top center */ #define BAP_CHANNEL_TBL 0x00010000 /* top back left */ #define BAP_CHANNEL_TBR 0x00020000 /* top back right */ #define BAP_CHANNEL_TSL 0x00040000 /* top side left */ #define BAP_CHANNEL_TSR 0x00080000 /* top side right */ #define BAP_CHANNEL_TBC 0x00100000 /* top back center */ #define BAP_CHANNEL_BFC 0x00200000 /* bottom front center */ #define BAP_CHANNEL_BFL 0x00400000 /* bottom front left */ #define BAP_CHANNEL_BFR 0x00800000 /* bottom front right */ #define BAP_CHANNEL_FLW 0x01000000 /* front left wide */ #define BAP_CHANNEL_FRW 0x02000000 /* front right wide */ #define BAP_CHANNEL_LS 0x04000000 /* left surround */ #define BAP_CHANNEL_RS 0x08000000 /* right surround */ #define BAP_CHANNEL_ALL 0x0fffffff /* mask of all */ #define BAP_CONTEXT_PROHIBITED 0x0000 /* Prohibited */ #define BAP_CONTEXT_UNSPECIFIED 0x0001 /* Unspecified */ #define BAP_CONTEXT_CONVERSATIONAL 0x0002 /* Telephony, video calls, ... */ #define BAP_CONTEXT_MEDIA 0x0004 /* Music, radio, podcast, movie soundtrack, TV */ #define BAP_CONTEXT_GAME 0x0008 /* Gaming media, game effects, music, in-game voice chat */ #define BAP_CONTEXT_INSTRUCTIONAL 0x0010 /* Instructional audio, navigation, announcements, user guidance */ #define BAP_CONTEXT_VOICE 0x0020 /* Man-machine communication, voice recognition, virtual assistants */ #define BAP_CONTEXT_LIVE 0x0040 /* Live audio, perceived both via direct acoustic path and via BAP */ #define BAP_CONTEXT_SOUND_EFFECTS 0x0080 /* Keyboard and touch feedback, menu, UI, other system sounds */ #define BAP_CONTEXT_NOTIFICATIONS 0x0100 /* Attention-seeking, message arrival, reminders */ #define BAP_CONTEXT_RINGTONE 0x0200 /* Incoming call alert audio */ #define BAP_CONTEXT_ALERTS 0x0400 /* Alarms and timers, critical battery, alarm clock, toaster */ #define BAP_CONTEXT_EMERGENCY 0x0800 /* Fire alarm, other urgent alerts */ #define BAP_CONTEXT_ALL 0x0fff #define BT_ISO_QOS_CIG_UNSET 0xff #define BT_ISO_QOS_CIS_UNSET 0xff #define BT_ISO_QOS_TARGET_LATENCY_LOW 0x01 #define BT_ISO_QOS_TARGET_LATENCY_BALANCED 0x02 #define BT_ISO_QOS_TARGET_LATENCY_RELIABILITY 0x03 struct bap_endpoint_qos { uint8_t framing; uint8_t phy; uint8_t retransmission; uint16_t latency; uint32_t delay_min; uint32_t delay_max; uint32_t preferred_delay_min; uint32_t preferred_delay_max; uint32_t locations; uint16_t supported_context; uint16_t context; uint32_t channel_allocation; }; struct bap_codec_qos { uint32_t interval; uint8_t framing; uint8_t phy; uint16_t sdu; uint8_t retransmission; uint16_t latency; uint32_t delay; uint8_t target_latency; }; struct bap_codec_qos_full { uint8_t cig; uint8_t cis; uint8_t big; uint8_t bis; struct bap_codec_qos qos; }; static const struct { uint32_t bit; enum spa_audio_channel channel; } bap_channel_bits[] = { { BAP_CHANNEL_MONO, SPA_AUDIO_CHANNEL_MONO }, { BAP_CHANNEL_FL, SPA_AUDIO_CHANNEL_FL }, { BAP_CHANNEL_FR, SPA_AUDIO_CHANNEL_FR }, { BAP_CHANNEL_FC, SPA_AUDIO_CHANNEL_FC }, { BAP_CHANNEL_LFE, SPA_AUDIO_CHANNEL_LFE }, { BAP_CHANNEL_BL, SPA_AUDIO_CHANNEL_RL }, { BAP_CHANNEL_BR, SPA_AUDIO_CHANNEL_RR }, { BAP_CHANNEL_FLC, SPA_AUDIO_CHANNEL_FLC }, { BAP_CHANNEL_FRC, SPA_AUDIO_CHANNEL_FRC }, { BAP_CHANNEL_BC, SPA_AUDIO_CHANNEL_BC }, { BAP_CHANNEL_LFE2, SPA_AUDIO_CHANNEL_LFE2 }, { BAP_CHANNEL_SL, SPA_AUDIO_CHANNEL_SL }, { BAP_CHANNEL_SR, SPA_AUDIO_CHANNEL_SR }, { BAP_CHANNEL_TFL, SPA_AUDIO_CHANNEL_TFL }, { BAP_CHANNEL_TFR, SPA_AUDIO_CHANNEL_TFR }, { BAP_CHANNEL_TFC, SPA_AUDIO_CHANNEL_TFC }, { BAP_CHANNEL_TC, SPA_AUDIO_CHANNEL_TC }, { BAP_CHANNEL_TBL, SPA_AUDIO_CHANNEL_TRL }, { BAP_CHANNEL_TBR, SPA_AUDIO_CHANNEL_TRR }, { BAP_CHANNEL_TSL, SPA_AUDIO_CHANNEL_TSL }, { BAP_CHANNEL_TSR, SPA_AUDIO_CHANNEL_TSR }, { BAP_CHANNEL_TBC, SPA_AUDIO_CHANNEL_TRC }, { BAP_CHANNEL_BFC, SPA_AUDIO_CHANNEL_BC }, { BAP_CHANNEL_BFL, SPA_AUDIO_CHANNEL_BLC }, { BAP_CHANNEL_BFR, SPA_AUDIO_CHANNEL_BRC }, { BAP_CHANNEL_FLW, SPA_AUDIO_CHANNEL_FLW }, { BAP_CHANNEL_FRW, SPA_AUDIO_CHANNEL_FRW }, { BAP_CHANNEL_LS, SPA_AUDIO_CHANNEL_SL }, /* is it the right mapping? */ { BAP_CHANNEL_RS, SPA_AUDIO_CHANNEL_SR }, /* is it the right mapping? */ }; #endif pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/bluez5/bap-codec-lc3.c000066400000000000000000001331271511204443500266170ustar00rootroot00000000000000/* Spa BAP LC3 codec */ /* SPDX-FileCopyrightText: Copyright © 2020 Wim Taymans */ /* SPDX-FileCopyrightText: Copyright © 2022 Pauli Virtanen */ /* SPDX-FileCopyrightText: Copyright © 2022 Collabora */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "media-codecs.h" #include "bap-codec-caps.h" #define MAX_PACS 64 static struct spa_log *log_; struct impl { lc3_encoder_t enc[LC3_MAX_CHANNELS]; lc3_decoder_t dec[LC3_MAX_CHANNELS]; int samplerate; int codec_samplerate; int channels; int frame_dus; int framelen; int samples; unsigned int codesize; uint16_t seqnum; }; struct settings { uint32_t locations; uint32_t channel_allocation; uint16_t supported_context; uint16_t available_context; bool sink; bool duplex; char *qos_name; int retransmission; int latency; int64_t delay; int framing; }; struct pac_data { const void *data; size_t size; const void *metadata; size_t metadata_size; int index; const struct settings *settings; }; struct bap_qos { char *name; uint8_t rate; uint8_t frame_duration; bool framing; uint16_t framelen; uint8_t retransmission; uint16_t latency; uint32_t delay; unsigned int priority; }; typedef struct { uint8_t rate; uint8_t frame_duration; uint32_t channels; uint16_t framelen; uint8_t n_blks; bool sink; bool duplex; uint16_t preferred_context; unsigned int priority; } bap_lc3_t; struct config_data { bap_lc3_t conf; int pac_index; struct settings settings; }; #define BAP_QOS(name_, rate_, duration_, framing_, framelen_, rtn_, latency_, delay_, priority_) \ ((struct bap_qos){ .name = (name_), .rate = (rate_), .frame_duration = (duration_), .framing = (framing_), \ .framelen = (framelen_), .retransmission = (rtn_), .latency = (latency_), \ .delay = (delay_), .priority = (priority_) }) static const struct bap_qos bap_qos_configs[] = { /* Priority: low-latency > high-reliability, 7.5ms > 10ms, * bigger frequency and sdu better */ /* BAP v1.0.1 Table 5.2; low-latency */ BAP_QOS("8_1_1", LC3_CONFIG_FREQ_8KHZ, LC3_CONFIG_DURATION_7_5, false, 26, 2, 8, 40000, 30), /* 8_1_1 */ BAP_QOS("8_2_1", LC3_CONFIG_FREQ_8KHZ, LC3_CONFIG_DURATION_10, false, 30, 2, 10, 40000, 20), /* 8_2_1 */ BAP_QOS("16_1_1", LC3_CONFIG_FREQ_16KHZ, LC3_CONFIG_DURATION_7_5, false, 30, 2, 8, 40000, 31), /* 16_1_1 */ BAP_QOS("16_2_1", LC3_CONFIG_FREQ_16KHZ, LC3_CONFIG_DURATION_10, false, 40, 2, 10, 40000, 21), /* 16_2_1 (mandatory) */ BAP_QOS("24_1_1", LC3_CONFIG_FREQ_24KHZ, LC3_CONFIG_DURATION_7_5, false, 45, 2, 8, 40000, 32), /* 24_1_1 */ BAP_QOS("24_2_1", LC3_CONFIG_FREQ_24KHZ, LC3_CONFIG_DURATION_10, false, 60, 2, 10, 40000, 22), /* 24_2_1 */ BAP_QOS("32_1_1", LC3_CONFIG_FREQ_32KHZ, LC3_CONFIG_DURATION_7_5, false, 60, 2, 8, 40000, 33), /* 32_1_1 */ BAP_QOS("32_2_1", LC3_CONFIG_FREQ_32KHZ, LC3_CONFIG_DURATION_10, false, 80, 2, 10, 40000, 23), /* 32_2_1 */ BAP_QOS("441_1_1", LC3_CONFIG_FREQ_44KHZ, LC3_CONFIG_DURATION_7_5, true, 97, 5, 24, 40000, 34), /* 441_1_1 */ BAP_QOS("441_2_1", LC3_CONFIG_FREQ_44KHZ, LC3_CONFIG_DURATION_10, true, 130, 5, 31, 40000, 24), /* 441_2_1 */ BAP_QOS("48_1_1", LC3_CONFIG_FREQ_48KHZ, LC3_CONFIG_DURATION_7_5, false, 75, 5, 15, 40000, 35), /* 48_1_1 */ BAP_QOS("48_2_1", LC3_CONFIG_FREQ_48KHZ, LC3_CONFIG_DURATION_10, false, 100, 5, 20, 40000, 25), /* 48_2_1 */ BAP_QOS("48_3_1", LC3_CONFIG_FREQ_48KHZ, LC3_CONFIG_DURATION_7_5, false, 90, 5, 15, 40000, 36), /* 48_3_1 */ BAP_QOS("48_4_1", LC3_CONFIG_FREQ_48KHZ, LC3_CONFIG_DURATION_10, false, 120, 5, 20, 40000, 26), /* 48_4_1 */ BAP_QOS("48_5_1", LC3_CONFIG_FREQ_48KHZ, LC3_CONFIG_DURATION_7_5, false, 117, 5, 15, 40000, 37), /* 48_5_1 */ BAP_QOS("48_6_1", LC3_CONFIG_FREQ_48KHZ, LC3_CONFIG_DURATION_10, false, 155, 5, 20, 40000, 27), /* 48_6_1 */ /* BAP v1.0.1 Table 5.2; high-reliability */ BAP_QOS("8_1_2", LC3_CONFIG_FREQ_8KHZ, LC3_CONFIG_DURATION_7_5, false, 26, 13, 75, 40000, 10), /* 8_1_2 */ BAP_QOS("8_2_2", LC3_CONFIG_FREQ_8KHZ, LC3_CONFIG_DURATION_10, false, 30, 13, 95, 40000, 0), /* 8_2_2 */ BAP_QOS("16_1_2", LC3_CONFIG_FREQ_16KHZ, LC3_CONFIG_DURATION_7_5, false, 30, 13, 75, 40000, 11), /* 16_1_2 */ BAP_QOS("16_2_2", LC3_CONFIG_FREQ_16KHZ, LC3_CONFIG_DURATION_10, false, 40, 13, 95, 40000, 1), /* 16_2_2 */ BAP_QOS("24_1_2", LC3_CONFIG_FREQ_24KHZ, LC3_CONFIG_DURATION_7_5, false, 45, 13, 75, 40000, 12), /* 24_1_2 */ BAP_QOS("24_2_2", LC3_CONFIG_FREQ_24KHZ, LC3_CONFIG_DURATION_10, false, 60, 13, 95, 40000, 2), /* 24_2_2 */ BAP_QOS("32_1_2", LC3_CONFIG_FREQ_32KHZ, LC3_CONFIG_DURATION_7_5, false, 60, 13, 75, 40000, 13), /* 32_1_2 */ BAP_QOS("32_2_2", LC3_CONFIG_FREQ_32KHZ, LC3_CONFIG_DURATION_10, false, 80, 13, 95, 40000, 3), /* 32_2_2 */ BAP_QOS("441_1_2", LC3_CONFIG_FREQ_44KHZ, LC3_CONFIG_DURATION_7_5, true, 97, 13, 80, 40000, 14), /* 441_1_2 */ BAP_QOS("441_2_2", LC3_CONFIG_FREQ_44KHZ, LC3_CONFIG_DURATION_10, true, 130, 13, 85, 40000, 4), /* 441_2_2 */ BAP_QOS("48_1_2", LC3_CONFIG_FREQ_48KHZ, LC3_CONFIG_DURATION_7_5, false, 75, 13, 75, 40000, 15), /* 48_1_2 */ BAP_QOS("48_2_2", LC3_CONFIG_FREQ_48KHZ, LC3_CONFIG_DURATION_10, false, 100, 13, 95, 40000, 5), /* 48_2_2 */ BAP_QOS("48_3_2", LC3_CONFIG_FREQ_48KHZ, LC3_CONFIG_DURATION_7_5, false, 90, 13, 75, 40000, 16), /* 48_3_2 */ BAP_QOS("48_4_2", LC3_CONFIG_FREQ_48KHZ, LC3_CONFIG_DURATION_10, false, 120, 13, 100, 40000, 6), /* 48_4_2 */ BAP_QOS("48_5_2", LC3_CONFIG_FREQ_48KHZ, LC3_CONFIG_DURATION_7_5, false, 117, 13, 75, 40000, 17), /* 48_5_2 */ BAP_QOS("48_6_2", LC3_CONFIG_FREQ_48KHZ, LC3_CONFIG_DURATION_10, false, 155, 13, 100, 40000, 7), /* 48_6_2 */ }; static const struct bap_qos bap_bcast_qos_configs[] = { /* Priority: low-latency > high-reliability, 7.5ms > 10ms, * bigger frequency and sdu better */ /* BAP v1.0.1 Table 6.4; low-latency */ BAP_QOS("8_1_1", LC3_CONFIG_FREQ_8KHZ, LC3_CONFIG_DURATION_7_5, false, 26, 2, 8, 40000, 30), /* 8_1_1 */ BAP_QOS("8_2_1", LC3_CONFIG_FREQ_8KHZ, LC3_CONFIG_DURATION_10, false, 30, 2, 10, 40000, 20), /* 8_2_1 */ BAP_QOS("16_1_1", LC3_CONFIG_FREQ_16KHZ, LC3_CONFIG_DURATION_7_5, false, 30, 2, 8, 40000, 31), /* 16_1_1 */ BAP_QOS("16_2_1", LC3_CONFIG_FREQ_16KHZ, LC3_CONFIG_DURATION_10, false, 40, 2, 10, 40000, 21), /* 16_2_1 (mandatory) */ BAP_QOS("24_1_1", LC3_CONFIG_FREQ_24KHZ, LC3_CONFIG_DURATION_7_5, false, 45, 2, 8, 40000, 32), /* 24_1_1 */ BAP_QOS("24_2_1", LC3_CONFIG_FREQ_24KHZ, LC3_CONFIG_DURATION_10, false, 60, 2, 10, 40000, 22), /* 24_2_1 */ BAP_QOS("32_1_1", LC3_CONFIG_FREQ_32KHZ, LC3_CONFIG_DURATION_7_5, false, 60, 2, 8, 40000, 33), /* 32_1_1 */ BAP_QOS("32_2_1", LC3_CONFIG_FREQ_32KHZ, LC3_CONFIG_DURATION_10, false, 80, 2, 10, 40000, 23), /* 32_2_1 */ BAP_QOS("441_1_1", LC3_CONFIG_FREQ_44KHZ, LC3_CONFIG_DURATION_7_5, true, 97, 4, 24, 40000, 34), /* 441_1_1 */ BAP_QOS("441_2_1", LC3_CONFIG_FREQ_44KHZ, LC3_CONFIG_DURATION_10, true, 130, 4, 31, 40000, 24), /* 441_2_1 */ BAP_QOS("48_1_1", LC3_CONFIG_FREQ_48KHZ, LC3_CONFIG_DURATION_7_5, false, 75, 4, 15, 40000, 35), /* 48_1_1 */ BAP_QOS("48_2_1", LC3_CONFIG_FREQ_48KHZ, LC3_CONFIG_DURATION_10, false, 100, 4, 20, 40000, 25), /* 48_2_1 */ BAP_QOS("48_3_1", LC3_CONFIG_FREQ_48KHZ, LC3_CONFIG_DURATION_7_5, false, 90, 4, 15, 40000, 36), /* 48_3_1 */ BAP_QOS("48_4_1", LC3_CONFIG_FREQ_48KHZ, LC3_CONFIG_DURATION_10, false, 120, 4, 20, 40000, 26), /* 48_4_1 */ BAP_QOS("48_5_1", LC3_CONFIG_FREQ_48KHZ, LC3_CONFIG_DURATION_7_5, false, 117, 4, 15, 40000, 37), /* 48_5_1 */ BAP_QOS("48_6_1", LC3_CONFIG_FREQ_48KHZ, LC3_CONFIG_DURATION_10, false, 155, 4, 20, 40000, 27), /* 48_6_1 */ /* BAP v1.0.1 Table 6.4; high-reliability */ BAP_QOS("8_1_2", LC3_CONFIG_FREQ_8KHZ, LC3_CONFIG_DURATION_7_5, false, 26, 4, 45, 40000, 10), /* 8_1_2 */ BAP_QOS("8_2_2", LC3_CONFIG_FREQ_8KHZ, LC3_CONFIG_DURATION_10, false, 30, 4, 60, 40000, 0), /* 8_2_2 */ BAP_QOS("16_1_2", LC3_CONFIG_FREQ_16KHZ, LC3_CONFIG_DURATION_7_5, false, 30, 4, 45, 40000, 11), /* 16_1_2 */ BAP_QOS("16_2_2", LC3_CONFIG_FREQ_16KHZ, LC3_CONFIG_DURATION_10, false, 40, 4, 60, 40000, 1), /* 16_2_2 */ BAP_QOS("24_1_2", LC3_CONFIG_FREQ_24KHZ, LC3_CONFIG_DURATION_7_5, false, 45, 4, 45, 40000, 12), /* 24_1_2 */ BAP_QOS("24_2_2", LC3_CONFIG_FREQ_24KHZ, LC3_CONFIG_DURATION_10, false, 60, 4, 60, 40000, 2), /* 24_2_2 */ BAP_QOS("32_1_2", LC3_CONFIG_FREQ_32KHZ, LC3_CONFIG_DURATION_7_5, false, 60, 4, 45, 40000, 13), /* 32_1_2 */ BAP_QOS("32_2_2", LC3_CONFIG_FREQ_32KHZ, LC3_CONFIG_DURATION_10, false, 80, 4, 60, 40000, 3), /* 32_2_2 */ BAP_QOS("441_1_2", LC3_CONFIG_FREQ_44KHZ, LC3_CONFIG_DURATION_7_5, true, 97, 4, 54, 40000, 14), /* 441_1_2 */ BAP_QOS("441_2_2", LC3_CONFIG_FREQ_44KHZ, LC3_CONFIG_DURATION_10, true, 130, 4, 60, 40000, 4), /* 441_2_2 */ BAP_QOS("48_1_2", LC3_CONFIG_FREQ_48KHZ, LC3_CONFIG_DURATION_7_5, false, 75, 4, 50, 40000, 15), /* 48_1_2 */ BAP_QOS("48_2_2", LC3_CONFIG_FREQ_48KHZ, LC3_CONFIG_DURATION_10, false, 100, 4, 65, 40000, 5), /* 48_2_2 */ BAP_QOS("48_3_2", LC3_CONFIG_FREQ_48KHZ, LC3_CONFIG_DURATION_7_5, false, 90, 4, 50, 40000, 16), /* 48_3_2 */ BAP_QOS("48_4_2", LC3_CONFIG_FREQ_48KHZ, LC3_CONFIG_DURATION_10, false, 120, 4, 65, 40000, 6), /* 48_4_2 */ BAP_QOS("48_5_2", LC3_CONFIG_FREQ_48KHZ, LC3_CONFIG_DURATION_7_5, false, 117, 4, 50, 40000, 17), /* 48_5_2 */ BAP_QOS("48_6_2", LC3_CONFIG_FREQ_48KHZ, LC3_CONFIG_DURATION_10, false, 155, 4, 65, 40000, 7), /* 48_6_2 */ }; static unsigned int get_rate_mask(uint8_t rate) { switch (rate) { case LC3_CONFIG_FREQ_8KHZ: return LC3_FREQ_8KHZ; case LC3_CONFIG_FREQ_16KHZ: return LC3_FREQ_16KHZ; case LC3_CONFIG_FREQ_24KHZ: return LC3_FREQ_24KHZ; case LC3_CONFIG_FREQ_32KHZ: return LC3_FREQ_32KHZ; case LC3_CONFIG_FREQ_44KHZ: return LC3_FREQ_44KHZ; case LC3_CONFIG_FREQ_48KHZ: return LC3_FREQ_48KHZ; } return 0; } static unsigned int get_duration_mask(uint8_t rate) { switch (rate) { case LC3_CONFIG_DURATION_7_5: return LC3_DUR_7_5; case LC3_CONFIG_DURATION_10: return LC3_DUR_10; } return 0; } static uint16_t parse_rates(const char *str) { struct spa_json it; uint16_t rate_mask = 0; int value; if (spa_json_begin_array_relax(&it, str, strlen(str)) <= 0) return rate_mask; while (spa_json_get_int(&it, &value) > 0) { switch (value) { case LC3_VAL_FREQ_8KHZ: rate_mask |= LC3_FREQ_8KHZ; break; case LC3_VAL_FREQ_16KHZ: rate_mask |= LC3_FREQ_16KHZ; break; case LC3_VAL_FREQ_24KHZ: rate_mask |= LC3_FREQ_24KHZ; break; case LC3_VAL_FREQ_32KHZ: rate_mask |= LC3_FREQ_32KHZ; break; case LC3_VAL_FREQ_44KHZ: rate_mask |= LC3_FREQ_44KHZ; break; case LC3_VAL_FREQ_48KHZ: rate_mask |= LC3_FREQ_48KHZ; break; default: break; } } return rate_mask; } static uint8_t parse_durations(const char *str) { struct spa_json it; uint8_t duration_mask = 0; float value; if (spa_json_begin_array_relax(&it, str, strlen(str)) <= 0) return duration_mask; while (spa_json_get_float(&it, &value) > 0) { if (value == (float)LC3_VAL_DUR_7_5) duration_mask |= LC3_DUR_7_5; else if (value == (float)LC3_VAL_DUR_10) duration_mask |= LC3_DUR_10; } return duration_mask; } static uint8_t parse_channel_counts(const char *str) { struct spa_json it; uint8_t channel_counts = 0; int value; if (spa_json_begin_array_relax(&it, str, strlen(str)) <= 0) return channel_counts; while (spa_json_get_int(&it, &value) > 0) { switch (value) { case LC3_VAL_CHAN_1: channel_counts |= LC3_CHAN_1; break; case LC3_VAL_CHAN_2: channel_counts |= LC3_CHAN_2; break; case LC3_VAL_CHAN_3: channel_counts |= LC3_CHAN_3; break; case LC3_VAL_CHAN_4: channel_counts |= LC3_CHAN_4; break; case LC3_VAL_CHAN_5: channel_counts |= LC3_CHAN_5; break; case LC3_VAL_CHAN_6: channel_counts |= LC3_CHAN_6; break; case LC3_VAL_CHAN_7: channel_counts |= LC3_CHAN_7; break; case LC3_VAL_CHAN_8: channel_counts |= LC3_CHAN_8; break; default: break; } } return channel_counts; } static int codec_fill_caps(const struct media_codec *codec, uint32_t flags, const struct spa_dict *settings, uint8_t caps[A2DP_MAX_CAPS_SIZE]) { const char *str; uint16_t framelen[2]; uint16_t rate_mask = LC3_FREQ_48KHZ | LC3_FREQ_44KHZ | LC3_FREQ_32KHZ | \ LC3_FREQ_24KHZ | LC3_FREQ_16KHZ | LC3_FREQ_8KHZ; uint8_t duration_mask = LC3_DUR_ANY; uint8_t channel_counts = LC3_CHAN_1 | LC3_CHAN_2; uint16_t framelen_min = LC3_MIN_FRAME_BYTES; uint16_t framelen_max = LC3_MAX_FRAME_BYTES; uint8_t max_frames = 2; uint32_t value; struct ltv_writer writer = LTV_WRITER(caps, A2DP_MAX_CAPS_SIZE); if (settings && (str = spa_dict_lookup(settings, "bluez5.bap-server-capabilities.rates"))) rate_mask = parse_rates(str); if (settings && (str = spa_dict_lookup(settings, "bluez5.bap-server-capabilities.durations"))) duration_mask = parse_durations(str); if (settings && (str = spa_dict_lookup(settings, "bluez5.bap-server-capabilities.channels"))) channel_counts = parse_channel_counts(str); if (settings && (str = spa_dict_lookup(settings, "bluez5.bap-server-capabilities.framelen_min"))) if (spa_atou32(str, &value, 0)) framelen_min = value; if (settings && (str = spa_dict_lookup(settings, "bluez5.bap-server-capabilities.framelen_max"))) if (spa_atou32(str, &value, 0)) framelen_max = value; if (settings && (str = spa_dict_lookup(settings, "bluez5.bap-server-capabilities.max_frames"))) if (spa_atou32(str, &value, 0)) max_frames = value; framelen[0] = htobs(framelen_min); framelen[1] = htobs(framelen_max); ltv_writer_uint16(&writer, LC3_TYPE_FREQ, rate_mask); ltv_writer_uint8(&writer, LC3_TYPE_DUR, duration_mask); ltv_writer_uint8(&writer, LC3_TYPE_CHAN, channel_counts); ltv_writer_data(&writer, LC3_TYPE_FRAMELEN, framelen, sizeof(framelen)); /* XXX: we support only one frame block -> max 2 frames per SDU */ if (max_frames > 2) max_frames = 2; ltv_writer_uint8(&writer, LC3_TYPE_BLKS, max_frames); return ltv_writer_end(&writer); } static void debugc_ltv(struct spa_debug_context *debug_ctx, int pac, struct ltv *ltv) { switch (ltv->len) { case 0: spa_debugc(debug_ctx, "PAC %d: --", pac); break; case 2: spa_debugc(debug_ctx, "PAC %d: 0x%02x %x", pac, ltv->type, ltv->value[0]); break; case 3: spa_debugc(debug_ctx, "PAC %d: 0x%02x %x %x", pac, ltv->type, ltv->value[0], ltv->value[1]); break; case 5: spa_debugc(debug_ctx, "PAC %d: 0x%02x %x %x %x %x", pac, ltv->type, ltv->value[0], ltv->value[1], ltv->value[2], ltv->value[3]); break; default: spa_debugc(debug_ctx, "PAC %d: 0x%02x", pac, ltv->type); spa_debugc_mem(debug_ctx, 7, ltv->value, ltv->len - 1); break; } } static int parse_bluez_pacs_data(const uint8_t *data, size_t data_size, struct pac_data pacs[MAX_PACS], struct spa_debug_context *debug_ctx) { /* * BlueZ capabilities for the same codec may contain multiple * PACs separated by zero-length LTV (see BlueZ b907befc2d80) */ int pac = 0; pacs[pac] = (struct pac_data){ .data = data }; while (data_size > 0) { struct ltv *ltv = (struct ltv *)data; if (ltv->len == 0) { /* delimiter */ if (pac + 1 >= MAX_PACS) break; ++pac; pacs[pac] = (struct pac_data){ .data = data + 1, .index = pac }; } else if (ltv->len >= data_size) { return -EINVAL; } else { debugc_ltv(debug_ctx, pac, ltv); pacs[pac].size += ltv->len + 1; } data_size -= ltv->len + 1; data += ltv->len + 1; } return pac + 1; } static int parse_bluez_pacs(const uint8_t *data, size_t data_size, const uint8_t *metadata, size_t metadata_size, struct pac_data pacs[MAX_PACS], struct spa_debug_context *debug_ctx) { struct pac_data meta[MAX_PACS]; int pac_count, meta_count; pac_count = parse_bluez_pacs_data(data, data_size, pacs, debug_ctx); if (pac_count < 0) return pac_count; meta_count = parse_bluez_pacs_data(metadata, metadata_size, meta, debug_ctx); if (meta_count == pac_count) { for (int i = 0; i < pac_count; ++i) { pacs[i].metadata = meta[i].data; pacs[i].metadata_size = meta[i].size; } } return pac_count; } static uint8_t get_channel_count(uint32_t channels) { uint8_t num; channels &= BAP_CHANNEL_ALL; if (channels == 0) return 1; /* MONO */ for (num = 0; channels; channels >>= 1) if (channels & 0x1) ++num; return num; } static bool supports_channel_count(uint8_t mask, uint8_t count) { if (count == 0 || count > 8) return false; return mask & (1u << (count - 1)); } static bool select_bap_qos(struct bap_qos *conf, const struct settings *s, unsigned int rate_mask, unsigned int duration_mask, uint16_t framelen_min, uint16_t framelen_max) { bool found = false; conf->name = NULL; conf->priority = 0; SPA_FOR_EACH_ELEMENT_VAR(bap_qos_configs, cur_conf) { struct bap_qos c = *cur_conf; /* Check if custom QoS settings are configured. If so, we check if * the configured settings are compatible with unicast server */ if (spa_streq(c.name, s->qos_name)) c.priority = UINT_MAX; else if (c.priority < conf->priority) continue; if (s->retransmission >= 0) c.retransmission = s->retransmission; if (s->latency >= 0) c.latency = s->latency; if (s->delay >= 0) c.delay = s->delay; if (s->framing >= 0) c.framing = s->framing; if (!(get_rate_mask(c.rate) & rate_mask)) continue; if (!(get_duration_mask(c.frame_duration) & duration_mask)) continue; if (c.framelen < framelen_min || c.framelen > framelen_max) continue; *conf = c; found = true; } return found; } static int select_channels(uint8_t channel_counts, uint32_t locations, uint32_t channel_allocation, uint32_t *allocation) { unsigned int i, num; locations &= BAP_CHANNEL_ALL; if (!channel_counts) return -1; if (!locations) { *allocation = 0; /* mono (omit Audio_Channel_Allocation) */ return 0; } if (channel_allocation) { channel_allocation &= locations; /* sanity check channel allocation */ while (!supports_channel_count(channel_counts, get_channel_count(channel_allocation))) { for (i = 32; i > 0; --i) { uint32_t mask = (1u << (i-1)); if (channel_allocation & mask) { channel_allocation &= ~mask; break; } } if (i == 0) break; } *allocation = channel_allocation; return 0; } /* XXX: select some channels, but upper level should tell us what */ if ((channel_counts & LC3_CHAN_2) && get_channel_count(locations) >= 2) num = 2; else if ((channel_counts & LC3_CHAN_1) && get_channel_count(locations) >= 1) num = 1; else return -1; *allocation = 0; for (i = 0; i < SPA_N_ELEMENTS(bap_channel_bits); ++i) { if (locations & bap_channel_bits[i].bit) { *allocation |= bap_channel_bits[i].bit; --num; if (num == 0) break; } } return 0; } static bool select_config(bap_lc3_t *conf, const struct pac_data *pac, struct spa_debug_context *debug_ctx) { const void *data = pac->data; size_t data_size = pac->size; uint16_t framelen_min = 0, framelen_max = 0; int max_frames = -1; uint8_t channel_counts = LC3_CHAN_1; /* Default: 1 channel (BAP v1.0.1 Sec 4.3.1) */ uint8_t max_channels = 0; uint8_t duration_mask = 0; uint16_t rate_mask = 0; uint16_t preferred_context = 0; struct bap_qos bap_qos; unsigned int i; bool found = false; const struct ltv *ltv; if (!data_size) return false; memset(conf, 0, sizeof(*conf)); conf->sink = pac->settings->sink; conf->duplex = pac->settings->duplex; /* XXX: we always use one frame block */ conf->n_blks = 1; while ((ltv = ltv_next(&data, &data_size))) { switch (ltv->type) { case LC3_TYPE_FREQ: spa_return_val_if_fail(ltv->len == 3, false); rate_mask = ltv->value[0] + (ltv->value[1] << 8); break; case LC3_TYPE_DUR: spa_return_val_if_fail(ltv->len == 2, false); duration_mask = ltv->value[0]; break; case LC3_TYPE_CHAN: spa_return_val_if_fail(ltv->len == 2, false); channel_counts = ltv->value[0]; break; case LC3_TYPE_FRAMELEN: spa_return_val_if_fail(ltv->len == 5, false); framelen_min = ltv->value[0] + (ltv->value[1] << 8); framelen_max = ltv->value[2] + (ltv->value[3] << 8); break; case LC3_TYPE_BLKS: spa_return_val_if_fail(ltv->len == 2, false); max_frames = ltv->value[0]; break; default: spa_debugc(debug_ctx, "unknown LTV type: 0x%02x", ltv->type); break; } } if (data) { spa_debugc(debug_ctx, "invalid LTV data"); return false; } data = pac->metadata; data_size = pac->metadata_size; while ((ltv = ltv_next(&data, &data_size))) { switch (ltv->type) { case BAP_META_TYPE_PREFERRED_CONTEXT: if (ltv->len != 3) break; preferred_context = ltv->value[0] + (ltv->value[1] << 8); break; } } if (data) spa_debugc(debug_ctx, "malformed metadata"); for (i = 0; i < 8; ++i) if (channel_counts & (1u << i)) max_channels = i + 1; /* Default: 1 frame per channel (BAP v1.0.1 Sec 4.3.1) */ if (max_frames < 0) max_frames = max_channels; /* * Workaround: * Creative Zen Hybrid Pro sets Supported_Max_Codec_Frames_Per_SDU == 1 * but channels == 0x3, and the 2-channel audio stream works. */ if (max_frames < max_channels) { spa_debugc(debug_ctx, "workaround: fixing bad Supported_Max_Codec_Frames_Per_SDU: %u->%u", max_frames, max_channels); max_frames = max_channels; } if (select_channels(channel_counts, pac->settings->locations, pac->settings->channel_allocation, &conf->channels) < 0) { spa_debugc(debug_ctx, "invalid channel configuration: 0x%02x %u", channel_counts, max_frames); return false; } if (max_frames < get_channel_count(conf->channels)) { spa_debugc(debug_ctx, "invalid max frames per SDU: %u", max_frames); return false; } /* * Select supported rate + frame length combination. * * Frame length is not limited by ISO MTU, as kernel will fragment * and reassemble SDUs as needed. */ if (pac->settings->duplex) { /* 16KHz input is mandatory in BAP v1.0.1 Table 3.5, and 32KHz in TMAP, * so prefer those for now for input rate in duplex configuration. * * It appears few devices support 48kHz out + input, so in duplex mode * try 32 kHz or 16 kHz also for output direction. * * Devices may list other values but not certain they will work properly. */ found = select_bap_qos(&bap_qos, pac->settings, rate_mask & (LC3_FREQ_16KHZ | LC3_FREQ_32KHZ), duration_mask, framelen_min, framelen_max); } if (!found) found = select_bap_qos(&bap_qos, pac->settings, rate_mask, duration_mask, framelen_min, framelen_max); if (!found) { spa_debugc(debug_ctx, "no compatible configuration found, rate:0x%08x, duration:0x%08x frame:%u-%u", rate_mask, duration_mask, framelen_min, framelen_max); return false; } conf->rate = bap_qos.rate; conf->frame_duration = bap_qos.frame_duration; conf->framelen = bap_qos.framelen; conf->priority = bap_qos.priority; conf->preferred_context = preferred_context; return true; } static bool parse_conf(bap_lc3_t *conf, const void *data, size_t data_size) { const struct ltv *ltv; if (!data_size) return false; memset(conf, 0, sizeof(*conf)); conf->frame_duration = 0xFF; /* Absent Codec_Frame_Blocks_Per_SDU means 0x1 (BAP v1.0.1 Sec 4.3.2) */ conf->n_blks = 1; while ((ltv = ltv_next(&data, &data_size))) { switch (ltv->type) { case LC3_TYPE_FREQ: spa_return_val_if_fail(ltv->len == 2, false); conf->rate = ltv->value[0]; break; case LC3_TYPE_DUR: spa_return_val_if_fail(ltv->len == 2, false); conf->frame_duration = ltv->value[0]; break; case LC3_TYPE_CHAN: spa_return_val_if_fail(ltv->len == 5, false); conf->channels = ltv->value[0] + (ltv->value[1] << 8) + (ltv->value[2] << 16) + (ltv->value[3] << 24); break; case LC3_TYPE_FRAMELEN: spa_return_val_if_fail(ltv->len == 3, false); conf->framelen = ltv->value[0] + (ltv->value[1] << 8); break; case LC3_TYPE_BLKS: spa_return_val_if_fail(ltv->len == 2, false); conf->n_blks = ltv->value[0]; /* XXX: we only support 1 frame block for now */ if (conf->n_blks != 1) return false; break; default: return false; } } if (data) return false; if (conf->frame_duration == 0xFF || !conf->rate) return false; return true; } static uint16_t get_wanted_context(const struct settings *settings) { uint16_t context; /* Stick with contexts specified in TMAP. Anything else is probably crapshoot due * to interesting device firmware behavior. * * Eg. some Earfun firmwares fail if we set MEDIA | CONVERSATIONAL, other versions * disconnect if LIVE is set, Samsung Galaxy Buds fail on microphone Enable if * CONVERSATIONAL is not set. */ if (settings->sink || settings->duplex) context = BAP_CONTEXT_CONVERSATIONAL; else context = BAP_CONTEXT_MEDIA; /* CAP v1.0.1 Sec 7.3.1.2.1: drop contexts if not available, otherwise unspecified */ context &= settings->available_context; if (!context) context = BAP_CONTEXT_UNSPECIFIED & settings->available_context; return context; } static int conf_cmp(const bap_lc3_t *conf1, int res1, const bap_lc3_t *conf2, int res2, const struct settings *settings) { const bap_lc3_t *conf; int a, b; #define PREFER_EXPR(expr) \ do { \ conf = conf1; \ a = (expr); \ conf = conf2; \ b = (expr); \ if (a != b) \ return b - a; \ } while (0) #define PREFER_BOOL(expr) PREFER_EXPR((expr) ? 1 : 0) /* Prefer valid */ a = (res1 > 0 && (size_t)res1 == sizeof(bap_lc3_t)) ? 1 : 0; b = (res2 > 0 && (size_t)res2 == sizeof(bap_lc3_t)) ? 1 : 0; if (!a || !b) return b - a; PREFER_EXPR(conf->priority == UINT_MAX); /* CAP v1.0.1 Sec 7.3.1.2.4: should use PAC preferred context if possible */ PREFER_BOOL(conf->preferred_context & get_wanted_context(settings)); PREFER_BOOL(conf->channels & LC3_CHAN_2); PREFER_BOOL(conf->channels & LC3_CHAN_1); if (conf->duplex) PREFER_BOOL(conf->rate & (LC3_CONFIG_FREQ_16KHZ | LC3_CONFIG_FREQ_32KHZ)); PREFER_EXPR(conf->priority); return 0; #undef PREFER_EXPR #undef PREFER_BOOL } static int pac_cmp(const void *p1, const void *p2) { const struct pac_data *pac1 = p1; const struct pac_data *pac2 = p2; struct spa_debug_log_ctx debug_ctx = SPA_LOG_DEBUG_INIT(log_, SPA_LOG_LEVEL_TRACE); bap_lc3_t conf1, conf2; int res1, res2; res1 = select_config(&conf1, pac1, &debug_ctx.ctx) ? (int)sizeof(bap_lc3_t) : -EINVAL; res2 = select_config(&conf2, pac2, &debug_ctx.ctx) ? (int)sizeof(bap_lc3_t) : -EINVAL; return conf_cmp(&conf1, res1, &conf2, res2, pac1->settings); } static void parse_settings(struct settings *s, const struct spa_dict *settings, struct spa_debug_log_ctx *debug_ctx) { const char *str; uint32_t value; spa_zero(*s); s->retransmission = -1; s->latency = -1; s->delay = -1; s->framing = -1; if (!settings) return; if ((str = spa_dict_lookup(settings, "bluez5.bap.preset"))) s->qos_name = strdup(str); if (spa_atou32(spa_dict_lookup(settings, "bluez5.bap.rtn"), &value, 0)) s->retransmission = value; if (spa_atou32(spa_dict_lookup(settings, "bluez5.bap.latency"), &value, 0)) s->latency = value; if (spa_atou32(spa_dict_lookup(settings, "bluez5.bap.delay"), &value, 0)) s->delay = value; if ((str = spa_dict_lookup(settings, "bluez5.bap.framing"))) s->framing = spa_atob(str); if (spa_atou32(spa_dict_lookup(settings, "bluez5.bap.locations"), &value, 0)) s->locations = value; if (spa_atou32(spa_dict_lookup(settings, "bluez5.bap.channel-allocation"), &value, 0)) s->channel_allocation = value; if (spa_atou32(spa_dict_lookup(settings, "bluez5.bap.supported-context"), &value, 0)) s->supported_context = value; if (spa_atou32(spa_dict_lookup(settings, "bluez5.bap.available-context"), &value, 0)) s->available_context = value; if (spa_atob(spa_dict_lookup(settings, "bluez5.bap.debug"))) *debug_ctx = SPA_LOG_DEBUG_INIT(log_, SPA_LOG_LEVEL_DEBUG); else *debug_ctx = SPA_LOG_DEBUG_INIT(NULL, SPA_LOG_LEVEL_TRACE); /* Is local endpoint sink or source */ s->sink = spa_atob(spa_dict_lookup(settings, "bluez5.bap.sink")); /* Is remote endpoint duplex */ s->duplex = spa_atob(spa_dict_lookup(settings, "bluez5.bap.duplex")); spa_debugc(&debug_ctx->ctx, "BAP LC3 settings: preset:%s rtn:%d latency:%d delay:%d framing:%d " "locations:%x chnalloc:%x sink:%d duplex:%d", s->qos_name ? s->qos_name : "auto", s->retransmission, s->latency, (int)s->delay, s->framing, (unsigned int)s->locations, (unsigned int)s->channel_allocation, (int)s->sink, (int)s->duplex); } static void free_config_data(struct config_data *d) { if (!d) return; free(d->settings.qos_name); free(d); } SPA_DEFINE_AUTOPTR_CLEANUP(config_data, struct config_data, { spa_clear_ptr(*thing, free_config_data); }); static int codec_select_config(const struct media_codec *codec, uint32_t flags, const void *caps, size_t caps_size, const struct media_codec_audio_info *info, const struct spa_dict *settings, uint8_t config[A2DP_MAX_CAPS_SIZE], void **config_data) { struct pac_data pacs[MAX_PACS]; int npacs; bap_lc3_t conf; struct spa_debug_log_ctx debug_ctx; spa_autoptr(config_data) d = NULL; int i, ret; struct ltv_writer writer = LTV_WRITER(config, A2DP_MAX_CAPS_SIZE); const void *metadata = NULL; uint32_t metadata_len = 0; if (caps == NULL) return -EINVAL; d = calloc(1, sizeof(*d)); if (!d) return -ENOMEM; parse_settings(&d->settings, settings, &debug_ctx); if (spa_atou32(spa_dict_lookup(settings, "bluez5.bap.metadata"), &metadata_len, 0)) metadata = spa_dict_lookup(settings, "bluez5.bap.metadata"); if (!metadata) metadata_len = 0; /* Select best conf from those possible */ npacs = parse_bluez_pacs(caps, caps_size, metadata, metadata_len, pacs, &debug_ctx.ctx); if (npacs < 0) { spa_debugc(&debug_ctx.ctx, "malformed PACS"); return npacs; } else if (npacs == 0) { spa_debugc(&debug_ctx.ctx, "no PACS"); return -EINVAL; } for (i = 0; i < npacs; ++i) pacs[i].settings = &d->settings; qsort(pacs, npacs, sizeof(struct pac_data), pac_cmp); spa_debugc(&debug_ctx.ctx, "selected PAC %d", pacs[0].index); if (!select_config(&conf, &pacs[0], &debug_ctx.ctx)) return -ENOTSUP; d->conf = conf; d->pac_index = pacs[0].index; ltv_writer_uint8(&writer, LC3_TYPE_FREQ, conf.rate); ltv_writer_uint8(&writer, LC3_TYPE_DUR, conf.frame_duration); /* Indicate MONO with absent Audio_Channel_Allocation (BAP v1.0.1 Sec. 4.3.2) */ if (conf.channels != 0) ltv_writer_uint32(&writer, LC3_TYPE_CHAN, conf.channels); ltv_writer_uint16(&writer, LC3_TYPE_FRAMELEN, conf.framelen); ltv_writer_uint8(&writer, LC3_TYPE_BLKS, conf.n_blks); ret = ltv_writer_end(&writer); if (ret >= 0 && config_data) *config_data = spa_steal_ptr(d); return ret; } static int codec_caps_preference_cmp(const struct media_codec *codec, uint32_t flags, const void *caps1, size_t caps1_size, const void *caps2, size_t caps2_size, const struct media_codec_audio_info *info, const struct spa_dict *global_settings) { bap_lc3_t conf1, conf2; int res1, res2, res; void *data1 = NULL; void *data2 = NULL; const struct config_data *d; /* Order selected configurations by preference */ res1 = codec->select_config(codec, 0, caps1, caps1_size, info, global_settings, (uint8_t *)&conf1, &data1); res2 = codec->select_config(codec, 0, caps2, caps2_size, info, global_settings, (uint8_t *)&conf2, &data2); d = data1 ? data1 : data2; res = conf_cmp(&conf1, res1, &conf2, res2, d ? &d->settings : NULL); codec->free_config_data(codec, data1); codec->free_config_data(codec, data2); return res; } static uint8_t channels_to_positions(uint32_t channels, uint32_t *position, uint32_t max_position) { uint32_t n_channels = get_channel_count(channels); uint8_t n_positions = 0; spa_assert(n_channels <= max_position); if (channels == 0) { position[0] = SPA_AUDIO_CHANNEL_MONO; n_positions = 1; } else { unsigned int i; for (i = 0; i < SPA_N_ELEMENTS(bap_channel_bits); ++i) if (channels & bap_channel_bits[i].bit) position[n_positions++] = bap_channel_bits[i].channel; } if (n_positions != n_channels) return 0; /* error */ return n_positions; } static int codec_enum_config(const struct media_codec *codec, uint32_t flags, const void *caps, size_t caps_size, uint32_t id, uint32_t idx, struct spa_pod_builder *b, struct spa_pod **param) { bap_lc3_t conf; struct spa_pod_frame f[2]; struct spa_pod_choice *choice; uint32_t position[LC3_MAX_CHANNELS]; uint32_t i = 0; uint8_t res; if (!parse_conf(&conf, caps, caps_size)) return -EINVAL; if (idx > 0) return 0; spa_pod_builder_push_object(b, &f[0], SPA_TYPE_OBJECT_Format, id); spa_pod_builder_add(b, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_audio), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), SPA_FORMAT_AUDIO_format, SPA_POD_Id(SPA_AUDIO_FORMAT_S24_32), 0); spa_pod_builder_prop(b, SPA_FORMAT_AUDIO_rate, 0); spa_pod_builder_push_choice(b, &f[1], SPA_CHOICE_None, 0); choice = (struct spa_pod_choice*)spa_pod_builder_frame(b, &f[1]); i = 0; if (conf.rate == LC3_CONFIG_FREQ_48KHZ) { if (i++ == 0) spa_pod_builder_int(b, 48000); spa_pod_builder_int(b, 48000); } if (conf.rate == LC3_CONFIG_FREQ_44KHZ) { if (i++ == 0) spa_pod_builder_int(b, 44100); spa_pod_builder_int(b, 44100); } if (conf.rate == LC3_CONFIG_FREQ_32KHZ) { if (i++ == 0) spa_pod_builder_int(b, 32000); spa_pod_builder_int(b, 32000); } if (conf.rate == LC3_CONFIG_FREQ_24KHZ) { if (i++ == 0) spa_pod_builder_int(b, 24000); spa_pod_builder_int(b, 24000); } if (conf.rate == LC3_CONFIG_FREQ_16KHZ) { if (i++ == 0) spa_pod_builder_int(b, 16000); spa_pod_builder_int(b, 16000); } if (conf.rate == LC3_CONFIG_FREQ_8KHZ) { if (i++ == 0) spa_pod_builder_int(b, 8000); spa_pod_builder_int(b, 8000); } if (i > 1) choice->body.type = SPA_CHOICE_Enum; spa_pod_builder_pop(b, &f[1]); if (i == 0) return -EINVAL; res = channels_to_positions(conf.channels, position, SPA_N_ELEMENTS(position)); if (res == 0) return -EINVAL; spa_pod_builder_add(b, SPA_FORMAT_AUDIO_channels, SPA_POD_Int(res), SPA_FORMAT_AUDIO_position, SPA_POD_Array(sizeof(uint32_t), SPA_TYPE_Id, res, position), 0); *param = spa_pod_builder_pop(b, &f[0]); return *param == NULL ? -EIO : 1; } static int codec_validate_config(const struct media_codec *codec, uint32_t flags, const void *caps, size_t caps_size, struct spa_audio_info *info) { bap_lc3_t conf; uint8_t res; if (caps == NULL) return -EINVAL; if (!parse_conf(&conf, caps, caps_size)) return -ENOTSUP; spa_zero(*info); info->media_type = SPA_MEDIA_TYPE_audio; info->media_subtype = SPA_MEDIA_SUBTYPE_raw; info->info.raw.format = SPA_AUDIO_FORMAT_S24_32; switch (conf.rate) { case LC3_CONFIG_FREQ_48KHZ: info->info.raw.rate = 48000U; break; case LC3_CONFIG_FREQ_44KHZ: info->info.raw.rate = 44100U; break; case LC3_CONFIG_FREQ_32KHZ: info->info.raw.rate = 32000U; break; case LC3_CONFIG_FREQ_24KHZ: info->info.raw.rate = 24000U; break; case LC3_CONFIG_FREQ_16KHZ: info->info.raw.rate = 16000U; break; case LC3_CONFIG_FREQ_8KHZ: info->info.raw.rate = 8000U; break; default: return -EINVAL; } res = channels_to_positions(conf.channels, info->info.raw.position, SPA_N_ELEMENTS(info->info.raw.position)); if (res == 0) return -EINVAL; info->info.raw.channels = res; switch (conf.frame_duration) { case LC3_CONFIG_DURATION_10: case LC3_CONFIG_DURATION_7_5: break; default: return -EINVAL; } return 0; } static int codec_get_qos(const struct media_codec *codec, const struct bap_endpoint_qos *endpoint_qos, const void *config_data, struct bap_codec_qos *qos) { struct bap_qos bap_qos; bap_lc3_t conf; bool found = false; const struct config_data *d = config_data; spa_zero(*qos); if (!d) return -EINVAL; conf = d->conf; found = select_bap_qos(&bap_qos, &d->settings, get_rate_mask(conf.rate), get_duration_mask(conf.frame_duration), conf.framelen, conf.framelen); if (!found) { /* shouldn't happen: select_config should pick existing one */ spa_log_error(log_, "no QoS settings found"); return -EINVAL; } if (endpoint_qos->framing == 0x01) qos->framing = true; else qos->framing = bap_qos.framing; if (endpoint_qos->phy & 0x2) qos->phy = 0x2; else if (endpoint_qos->phy & 0x1) qos->phy = 0x1; else qos->phy = 0x2; qos->sdu = conf.framelen * conf.n_blks * get_channel_count(conf.channels); qos->interval = (conf.frame_duration == LC3_CONFIG_DURATION_7_5 ? 7500 : 10000); qos->target_latency = BT_ISO_QOS_TARGET_LATENCY_BALANCED; qos->delay = bap_qos.delay; qos->latency = bap_qos.latency; qos->retransmission = bap_qos.retransmission; /* Clamp to ASE values (if known) */ if (endpoint_qos->delay_min) qos->delay = SPA_MAX(qos->delay, endpoint_qos->delay_min); if (endpoint_qos->delay_max) qos->delay = SPA_MIN(qos->delay, endpoint_qos->delay_max); /* * We ignore endpoint suggested latency and RTN. On current devices * these do not appear to be very useful numbers, so it's better * to just pick one from the table in the spec. */ return 0; } static int codec_get_metadata(const struct media_codec *codec, const void *config_data, uint8_t *meta, size_t meta_max_size) { const struct config_data *d = config_data; struct ltv_writer writer = LTV_WRITER(meta, meta_max_size); uint16_t ctx; ctx = get_wanted_context(&d->settings); if (!ctx) ctx = BAP_CONTEXT_UNSPECIFIED; ltv_writer_uint16(&writer, BAP_META_TYPE_STREAMING_CONTEXT, ctx); return ltv_writer_end(&writer); } static void codec_free_config_data(const struct media_codec *codec, void *config_data) { free_config_data(config_data); } static void *codec_init(const struct media_codec *codec, uint32_t flags, void *config, size_t config_len, const struct spa_audio_info *info, void *props, size_t mtu) { bap_lc3_t conf; struct impl *this = NULL; struct spa_audio_info config_info; int res, ich; if (info->media_type != SPA_MEDIA_TYPE_audio || info->media_subtype != SPA_MEDIA_SUBTYPE_raw || info->info.raw.format != SPA_AUDIO_FORMAT_S24_32) { res = -EINVAL; goto error; } if ((this = calloc(1, sizeof(struct impl))) == NULL) goto error_errno; if ((res = codec_validate_config(codec, flags, config, config_len, &config_info)) < 0) goto error; if (!parse_conf(&conf, config, config_len)) { spa_log_error(log_, "invalid LC3 config"); res = -ENOTSUP; goto error; } this->samplerate = config_info.info.raw.rate; this->channels = config_info.info.raw.channels; this->framelen = conf.framelen; /* Google liblc3 doesn't have direct support for encoding to 44.1kHz; instead * lc3.h suggests using a nearby samplerate, so we do just that */ this->codec_samplerate = (this->samplerate == 44100) ? 48000 : this->samplerate; switch (conf.frame_duration) { case LC3_CONFIG_DURATION_10: this->frame_dus = 10000; break; case LC3_CONFIG_DURATION_7_5: this->frame_dus = 7500; break; default: res = -EINVAL; goto error; } spa_log_info(log_, "LC3 rate:%d frame_duration:%d channels:%d framelen:%d nblks:%d", this->samplerate, this->frame_dus, this->channels, this->framelen, conf.n_blks); res = lc3_frame_samples(this->frame_dus, this->codec_samplerate); if (res < 0) { spa_log_error(log_, "invalid LC3 frame samples"); res = -EINVAL; goto error; } this->samples = res; this->codesize = (size_t)this->samples * this->channels * conf.n_blks * sizeof(int32_t); if (!(flags & MEDIA_CODEC_FLAG_SINK)) { for (ich = 0; ich < this->channels; ich++) { this->enc[ich] = lc3_setup_encoder(this->frame_dus, this->codec_samplerate, 0, calloc(1, lc3_encoder_size(this->frame_dus, this->codec_samplerate))); if (this->enc[ich] == NULL) { res = -EINVAL; goto error; } } } else { for (ich = 0; ich < this->channels; ich++) { this->dec[ich] = lc3_setup_decoder(this->frame_dus, this->codec_samplerate, 0, calloc(1, lc3_decoder_size(this->frame_dus, this->codec_samplerate))); if (this->dec[ich] == NULL) { res = -EINVAL; goto error; } } } return this; error_errno: res = -errno; goto error; error: if (this) { for (ich = 0; ich < this->channels; ich++) { if (this->enc[ich]) free(this->enc[ich]); if (this->dec[ich]) free(this->dec[ich]); } } free(this); errno = -res; return NULL; } static void codec_deinit(void *data) { struct impl *this = data; int ich; for (ich = 0; ich < this->channels; ich++) { if (this->enc[ich]) free(this->enc[ich]); if (this->dec[ich]) free(this->dec[ich]); } free(this); } static int codec_get_block_size(void *data) { struct impl *this = data; return this->codesize; } static uint64_t codec_get_interval(void *data) { struct impl *this = data; return (uint64_t)this->samples * SPA_NSEC_PER_SEC / this->samplerate; } static int codec_abr_process (void *data, size_t unsent) { return -ENOTSUP; } static int codec_start_encode (void *data, void *dst, size_t dst_size, uint16_t seqnum, uint32_t timestamp) { return 0; } static int codec_encode(void *data, const void *src, size_t src_size, void *dst, size_t dst_size, size_t *dst_out, int *need_flush) { struct impl *this = data; int ich, res; int size, processed; processed = 0; size = 0; if (src_size < (size_t)this->codesize) return -EINVAL; if (dst_size < (size_t)this->framelen * this->channels) return -EINVAL; for (ich = 0; ich < this->channels; ich++) { uint8_t *in = (uint8_t *)src + (ich * 4); uint8_t *out = (uint8_t *)dst + ich * this->framelen; res = lc3_encode(this->enc[ich], LC3_PCM_FORMAT_S24, in, this->channels, this->framelen, out); size += this->framelen; if (SPA_UNLIKELY(res != 0)) return -EINVAL; } *dst_out = size; processed += this->codesize; *need_flush = NEED_FLUSH_ALL; return processed; } static int codec_start_decode (void *data, const void *src, size_t src_size, uint16_t *seqnum, uint32_t *timestamp) { struct impl *this = data; /* packets come from controller, so also invalid ones bump seqnum */ this->seqnum++; if (!src_size) return -EINVAL; if (*seqnum) *seqnum = this->seqnum; return 0; } static int codec_decode(void *data, const void *src, size_t src_size, void *dst, size_t dst_size, size_t *dst_out) { struct impl *this = data; int ich, res; int consumed; consumed = 0; if (src_size < (size_t)this->framelen * this->channels) return -EINVAL; if (dst_size < this->codesize) return -EINVAL; for (ich = 0; ich < this->channels; ich++) { uint8_t *in = (uint8_t *)src + ich * this->framelen; uint8_t *out = (uint8_t *)dst + (ich * 4); res = lc3_decode(this->dec[ich], in, this->framelen, LC3_PCM_FORMAT_S24, out, this->channels); if (SPA_UNLIKELY(res < 0)) return -EINVAL; consumed += this->framelen; } *dst_out = this->codesize; return consumed; } static int codec_produce_plc(void *data, void *dst, size_t dst_size) { struct impl *this = data; int ich, res; if (dst_size < this->codesize) return -EINVAL; for (ich = 0; ich < this->channels; ich++) { uint8_t *out = (uint8_t *)dst + (ich * 4); res = lc3_decode(this->dec[ich], NULL, 0, LC3_PCM_FORMAT_S24, out, this->channels); if (SPA_UNLIKELY(res < 0)) return -EINVAL; } return this->codesize; } static int codec_reduce_bitpool(void *data) { return -ENOTSUP; } static int codec_increase_bitpool(void *data) { return -ENOTSUP; } static void codec_set_log(struct spa_log *global_log) { log_ = global_log; spa_log_topic_init(log_, &codec_plugin_log_topic); } static int codec_get_bis_config(const struct media_codec *codec, uint8_t *caps, uint8_t *caps_size, struct spa_dict *settings, struct bap_codec_qos *qos) { const char *preset_name = NULL; int channel_allocation = 0; int i, ret; struct ltv_writer writer = LTV_WRITER(caps, *caps_size); const struct bap_qos *preset = NULL; *caps_size = 0; if (settings) { for (i = 0; i < (int)settings->n_items; ++i) { if (spa_streq(settings->items[i].key, "channel_allocation")) sscanf(settings->items[i].value, "%"PRIu32, &channel_allocation); if (spa_streq(settings->items[i].key, "preset")) preset_name = settings->items[i].value; } } if (preset_name == NULL) return -EINVAL; SPA_FOR_EACH_ELEMENT_VAR(bap_bcast_qos_configs, c) { if (spa_streq(c->name, preset_name)) { preset = c; break; } } if (!preset) return -EINVAL; ltv_writer_uint8(&writer, LC3_TYPE_FREQ, preset->rate); ltv_writer_uint16(&writer, LC3_TYPE_FRAMELEN, preset->framelen); ltv_writer_uint8(&writer, LC3_TYPE_DUR, preset->frame_duration); ltv_writer_uint32(&writer, LC3_TYPE_CHAN, channel_allocation); if (preset->framing) qos->framing = 1; else qos->framing = 0; qos->sdu = preset->framelen * get_channel_count(channel_allocation); qos->retransmission = preset->retransmission; qos->latency = preset->latency; qos->delay = preset->delay; qos->phy = 2; qos->interval = (preset->frame_duration == LC3_CONFIG_DURATION_7_5 ? 7500 : 10000); ret = ltv_writer_end(&writer); if (ret < 0) return ret; if (ret > UINT8_MAX) return -ENOSPC; *caps_size = ret; return 0; } const struct media_codec bap_codec_lc3 = { .id = SPA_BLUETOOTH_AUDIO_CODEC_LC3, .kind = MEDIA_CODEC_BAP, .name = "lc3", .codec_id = BAP_CODEC_LC3, .description = "LC3", .fill_caps = codec_fill_caps, .select_config = codec_select_config, .enum_config = codec_enum_config, .validate_config = codec_validate_config, .get_qos = codec_get_qos, .get_metadata = codec_get_metadata, .free_config_data = codec_free_config_data, .caps_preference_cmp = codec_caps_preference_cmp, .init = codec_init, .deinit = codec_deinit, .get_block_size = codec_get_block_size, .get_interval = codec_get_interval, .abr_process = codec_abr_process, .start_encode = codec_start_encode, .encode = codec_encode, .start_decode = codec_start_decode, .decode = codec_decode, .produce_plc = codec_produce_plc, .reduce_bitpool = codec_reduce_bitpool, .increase_bitpool = codec_increase_bitpool, .set_log = codec_set_log, .get_bis_config = codec_get_bis_config }; MEDIA_CODEC_EXPORT_DEF( "lc3", &bap_codec_lc3 ); pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/bluez5/bluez-hardware.conf000066400000000000000000000134731511204443500277430ustar00rootroot00000000000000# List of hardware/kernel features, which cannot be detected generically. # # The `feature` is enabled only if all three of adapter, device, and # kernel have it. # # For each of the adapter/device/kernel, the match rules are processed # one at a time, and the first one that matches is used. # # Features and tags: # msbc "standard" mSBC (60 byte tx packet) # msbc-alt1 USB adapters with mSBC in ALT1 setting (24 byte tx packet) # msbc-alt1-rtl Realtek USB adapters with mSBC in ALT1 setting (24 byte tx packet) # hw-volume AVRCP and HSP/HFP hardware volume support # hw-volume-mic Functional HSP/HFP microphone volume support # sbc-xq "nonstandard" SBC codec setting with better sound quality # faststream FastStream codec support # a2dp-duplex A2DP duplex codec support # # Features are disabled with the key "no-features" whose value is an # array of strings in the match rule. bluez5.features.device = [ # properties: # - name # - address ("ff:ff:ff:ff:ff:ff") # - vendor-id ("bluetooth:ffff", "usb:ffff") # - product-id # - version-id { name = "Air 1 Plus", no-features = [ hw-volume-mic ] }, { name = "AirPods", no-features = [ msbc-alt1, msbc-alt1-rtl ] }, { name = "AirPods Pro", no-features = [ msbc-alt1, msbc-alt1-rtl ] }, { name = "Audio Pro_A26", address = "~^7c:96:d2:", no-features = [ hw-volume ]}, # doesn't remember volume, #pipewire-3225 { name = "AXLOIE Goin", no-features = [ msbc-alt1, msbc-alt1-rtl ] }, { name = "BAA 100", no-features = [ hw-volume ] }, # Buxton BAA 100, doesn't remember volume, #pipewire-1449 { name = "D50s", address = "~^00:13:ef:", no-features = [ hw-volume ] }, # volume has no effect, #pipewire-1562 { name = "FiiO BTR3", address = "~^40:ed:98:", no-features = [ faststream ] }, # #pipewire-1658 { name = "JBL Endurance RUN BT", no-features = [ msbc-alt1, msbc-alt1-rtl ] }, { name = "JBL LIVE650BTNC" }, { name = "Motorola DC800", no-features = [ sbc-xq ] }, # #pipewire-1590 { name = "Motorola S305", no-features = [ sbc-xq ] }, # #pipewire-1590 { name = "PMK True Wireless Earbuds" no-features = [ sbc-xq ] }, # Primark earbud { name = "Rockbox Brick", no-features = [ hw-volume ] }, # #pipewire-3786 { name = "Soundcore Life P2-L", no-features = [ msbc-alt1, msbc-alt1-rtl ] }, { name = "Soundcore Motion B", no-features = [ hw-volume ] }, { name = "SoundCore mini", no-features = [ hw-volume ] }, # #pipewire-1686 { name = "SoundCore mini2", no-features = [ hw-volume ] }, # #pipewire-2927 { name = "SoundCore 2", no-features = [ sbc-xq ] }, # #pipewire-2291 { name = "Tribit MAXSound Plus", no-features = [ hw-volume ] }, # #pipewire-1592 { name = "Urbanista Stockholm Plus", no-features = [ msbc-alt1, msbc-alt1-rtl ] }, { name = "WorkTunes Connect", no-features = [ hw-volume ] }, # 3M WorkTunes Connect { address = "~^44:5e:cd:", no-features = [ faststream, a2dp-duplex ]}, # #pipewire-1756 { address = "~^2c:53:d7:", no-features = [ sbc-xq ] }, # Phonak hearing aids #pipewire-3939 { address = "~^94:16:25:", no-features = [ hw-volume ]}, # AirPods 2 { address = "~^9c:64:8b:", no-features = [ hw-volume ]}, # AirPods 2 { address = "~^a0:e9:db:", no-features = [ hw-volume ]}, # Ausdom M05 { address = "~^0c:a6:94:", no-features = [ hw-volume ]}, # deepblue2 { address = "~^00:14:02:", no-features = [ hw-volume ]}, # iKross IKBT83B HS { address = "~^44:5e:f3:", no-features = [ hw-volume ]}, # JayBird BlueBuds X { address = "~^d4:9c:28:", no-features = [ hw-volume ]}, # JayBird BlueBuds X { address = "~^00:18:6b:", no-features = [ hw-volume ]}, # LG Tone HBS-730 { address = "~^b8:ad:3e:", no-features = [ hw-volume ]}, # LG Tone HBS-730 { address = "~^a0:e9:db:", no-features = [ hw-volume ]}, # LG Tone HV-800 { address = "~^00:24:1c:", no-features = [ hw-volume ]}, # Motorola Roadster { address = "~^00:11:b1:", no-features = [ hw-volume ]}, # Mpow Cheetah { address = "~^a4:15:66:", no-features = [ hw-volume ]}, # SOL REPUBLIC Tracks Air { address = "~^00:14:f1:", no-features = [ hw-volume ]}, # Swage Rokitboost HS { address = "~^00:26:7e:", no-features = [ hw-volume ]}, # VW Car Kit { address = "~^90:03:b7:", no-features = [ hw-volume ]}, # VW Car Kit # All features are enabled by default; it's simpler to block non-working devices one by one. ] bluez5.features.adapter = [ # properties: # - address ("ff:ff:ff:ff:ff:ff") # - bus-type ("usb", "other") # - vendor-id ("usb:ffff") # - product-id ("ffff") # Realtek Semiconductor Corp. { bus-type = "usb", vendor-id = "usb:0bda" }, # Generic USB adapters { bus-type = "usb", no-features = [ msbc-alt1-rtl ] }, # Other adapters { no-features = [ msbc-alt1-rtl ] }, ] bluez5.features.kernel = [ # properties (as in uname): # - sysname # - release # - version # See https://lore.kernel.org/linux-bluetooth/20201210012003.133000-1-tpiepho@gmail.com/ # https://lore.kernel.org/linux-bluetooth/b86543908684cc6cd9afaf4de10fac7af1a49665.camel@iki.fi/ { sysname = "Linux", release = "~^[0-4]\\.", no-features = [ msbc-alt1, msbc-alt1-rtl ] }, { sysname = "Linux", release = "~^5\\.[1-7]\\.", no-features = [ msbc-alt1, msbc-alt1-rtl ] }, { sysname = "Linux", release = "~^5\\.(8|9)\\.", no-features = [ msbc-alt1 ] }, { sysname = "Linux", release = "~^5\\.10\\.(1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|16|17|18|51|52|53|54|55|56|57|58|59|60|61)($|[^0-9])", no-features = [ msbc-alt1 ] }, { sysname = "Linux", release = "~^5\\.12\\.(18|19)($|[^0-9])", no-features = [ msbc-alt1 ] }, { sysname = "Linux", release = "~^5\\.13\\.(3|4|5|6|7|8|9|10|11|12|13)($|[^0-9])", no-features = [ msbc-alt1 ] }, { no-features = [] }, ] pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/bluez5/bluez5-dbus.c000066400000000000000000006770741511204443500265020ustar00rootroot00000000000000/* Spa V4l2 dbus */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "codec-loader.h" #include "player.h" #include "iso-io.h" #include "bap-codec-caps.h" #include "defs.h" SPA_LOG_TOPIC_DEFINE_STATIC(log_topic, "spa.bluez5"); #undef SPA_LOG_TOPIC_DEFAULT #define SPA_LOG_TOPIC_DEFAULT &log_topic enum backend_selection { BACKEND_NONE = -2, BACKEND_ANY = -1, BACKEND_HSPHFPD = 0, BACKEND_OFONO = 1, BACKEND_NATIVE = 2, BACKEND_NUM, }; /* * Rate limit for BlueZ SetConfiguration calls. * * Too rapid calls to BlueZ API may cause A2DP profile to disappear, as the * internal BlueZ/connection state gets confused. Use some reasonable minimum * interval. * * AVDTP v1.3 Sec. 6.13 mentions 3 seconds as a reasonable timeout in one case * (ACP connection reset timeout, if no INT response). The case here is * different, but we assume a similar value is fine here. */ #define BLUEZ_ACTION_RATE_MSEC 3000 /* How many times to retry acquire on errors, and how long delay to require before we can * try again. */ #define TRANSPORT_ERROR_MAX_RETRY 3 #define TRANSPORT_ERROR_TIMEOUT (2*BLUEZ_ACTION_RATE_MSEC*SPA_NSEC_PER_MSEC) struct spa_bt_monitor { struct spa_handle handle; struct spa_device device; struct spa_log *log; struct spa_loop *main_loop; struct spa_loop *data_loop; struct spa_loop_utils *loop_utils; struct spa_system *main_system; struct spa_system *data_system; struct spa_plugin_loader *plugin_loader; struct spa_dbus *dbus; struct spa_dbus_connection *dbus_connection; DBusConnection *conn; struct spa_hook_list hooks; uint32_t id; const struct media_codec * const * media_codecs; /* * Lists of BlueZ objects, kept up-to-date by following DBus events * initiated by BlueZ. Object lifetime is also determined by that. */ struct spa_list adapter_list; struct spa_list device_list; struct spa_list remote_endpoint_list; struct spa_list transport_list; unsigned int filters_added:1; unsigned int objects_listed:1; DBusPendingCall *get_managed_objects_call; struct spa_bt_backend *backend; struct spa_bt_backend *backends[BACKEND_NUM]; enum backend_selection backend_selection; struct spa_dict enabled_codecs; enum spa_bt_profile enabled_profiles; unsigned int connection_info_supported:1; unsigned int dummy_avrcp_player:1; struct spa_list bcast_source_config_list; uint32_t bap_sink_locations; uint32_t bap_sink_contexts; uint32_t bap_sink_supported_contexts; uint32_t bap_source_locations; uint32_t bap_source_contexts; uint32_t bap_source_supported_contexts; struct spa_bt_quirks *quirks; #define MAX_SETTINGS 128 struct spa_dict_item global_setting_items[MAX_SETTINGS]; struct spa_dict global_settings; /* A reference audio info for A2DP codec configuration. */ struct media_codec_audio_info default_audio_info; }; /* Stream endpoints owned by BlueZ for each device */ struct spa_bt_remote_endpoint { struct spa_list link; struct spa_list device_link; struct spa_bt_monitor *monitor; char *path; char *transport_path; char *uuid; unsigned int codec; struct spa_bt_device *device; uint8_t *capabilities; size_t capabilities_len; uint8_t *metadata; size_t metadata_len; bool delay_reporting; bool acceptor; struct bap_endpoint_qos qos; bool asha_right_side; uint64_t hisyncid; }; #define METADATA_MAX_LEN 255 #define CC_MAX_LEN 255 /* * This structure stores metadata as defined * in Assigned Numbers chapter 6.12.6 Metadata * LTV structures. Length contains the size of * type and value. */ struct spa_bt_metadata { struct spa_list link; int length; int type; uint8_t value[METADATA_MAX_LEN - 1]; }; struct spa_bt_bis { struct spa_list link; char qos_preset[255]; int channel_allocation; struct spa_list metadata_list; }; #define BROADCAST_CODE_LEN 16 struct spa_bt_big { struct spa_list link; char broadcast_code[BROADCAST_CODE_LEN]; bool encryption; struct spa_list bis_list; int big_id; int sync_factor; }; /* * Codec switching tries various codec/remote endpoint combinations * in order, until an acceptable one is found. This triggers BlueZ * to initiate DBus calls that result to the creation of a transport * with the desired capabilities. * The codec switch struct tracks candidates still to be tried. */ #define SPA_TYPE_BT_WORK_CODEC_SWITCH SPA_TYPE_INFO_BT_WORK_BASE "CodecSwitch" #define SPA_TYPE_BT_WORK_RATE_LIMIT SPA_TYPE_INFO_BT_WORK_BASE "RateLimit" struct spa_bt_codec_switch_path { char *path; bool clear; }; struct spa_bt_codec_switch { struct spa_list link; bool canceled; bool failed; bool waiting; uint32_t profiles; struct spa_bt_device *device; struct spa_source *timer; DBusPendingCall *pending; /* * Called asynchronously, so endpoint paths instead of pointers (which may be * invalidated in the meantime). */ const struct media_codec *codec; struct spa_bt_codec_switch_path *paths; unsigned int path_idx; }; static struct spa_bt_codec_switch *codec_switch_cmp_sw; /* global for qsort */ static void codec_switch_list_process(struct spa_list *codec_switch_list); static void codec_switch_destroy(struct spa_bt_codec_switch *sw); #define DEFAULT_RECONNECT_PROFILES SPA_BT_PROFILE_NULL #define DEFAULT_HW_VOLUME_PROFILES (SPA_BT_PROFILE_HEADSET_AUDIO_GATEWAY | SPA_BT_PROFILE_HEADSET_HEAD_UNIT | \ SPA_BT_PROFILE_A2DP_SOURCE | SPA_BT_PROFILE_A2DP_SINK | \ SPA_BT_PROFILE_BAP_AUDIO) #define BT_DEVICE_DISCONNECTED 0 #define BT_DEVICE_CONNECTED 1 #define BT_DEVICE_INIT -1 /* * SCO socket connect may fail with ECONNABORTED if it is done too soon after * previous close. To avoid this in cases where nodes are toggled between * stopped/started rapidly, postpone release until the transport has remained * unused for a time. * * Avoiding unnecessary release+reacquire also makes sense for ISO. */ #define TRANSPORT_RELEASE_TIMEOUT_MSEC 1000 #define TRANSPORT_VOLUME_TIMEOUT_MSEC 200 #define SPA_BT_TRANSPORT_IS_A2DP(transport) ((transport)->profile & (SPA_BT_PROFILE_A2DP_SOURCE | SPA_BT_PROFILE_A2DP_SINK)) static int spa_bt_transport_stop_volume_timer(struct spa_bt_transport *transport); static int spa_bt_transport_start_volume_timer(struct spa_bt_transport *transport); static int spa_bt_transport_stop_release_timer(struct spa_bt_transport *transport); static int spa_bt_transport_start_release_timer(struct spa_bt_transport *transport); static void spa_bt_transport_commit_release_timer(struct spa_bt_transport *transport); static int device_start_timer(struct spa_bt_device *device); static int device_stop_timer(struct spa_bt_device *device); // Working with BlueZ Battery Provider. // Developed using https://github.com/dgreid/adhd/commit/655b58f as an example of DBus calls. // Name of battery, formatted as /org/freedesktop/pipewire/battery/org/bluez/hciX/dev_XX_XX_XX_XX_XX_XX static char *battery_get_name(const char *device_path) { return spa_aprintf(PIPEWIRE_BATTERY_PROVIDER "%s", device_path); } // Unregister virtual battery of device static void battery_remove(struct spa_bt_device *device) { DBusMessageIter i, entry; spa_autoptr(DBusMessage) m = NULL; const char *interface; cancel_and_unref(&device->battery_pending_call); if (!device->adapter || !device->adapter->has_battery_provider || !device->has_battery) return; spa_log_debug(device->monitor->log, "Removing virtual battery: %s", device->battery_path); m = dbus_message_new_signal(PIPEWIRE_BATTERY_PROVIDER, DBUS_INTERFACE_OBJECT_MANAGER, DBUS_SIGNAL_INTERFACES_REMOVED); dbus_message_iter_init_append(m, &i); dbus_message_iter_append_basic(&i, DBUS_TYPE_OBJECT_PATH, &device->battery_path); dbus_message_iter_open_container(&i, DBUS_TYPE_ARRAY, DBUS_TYPE_STRING_AS_STRING, &entry); interface = BLUEZ_INTERFACE_BATTERY_PROVIDER; dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &interface); dbus_message_iter_close_container(&i, &entry); if (!dbus_connection_send(device->monitor->conn, m, NULL)) { spa_log_error(device->monitor->log, "sending " DBUS_SIGNAL_INTERFACES_REMOVED " failed"); } device->has_battery = false; } // Create properties for Battery Provider request static void battery_write_properties(DBusMessageIter *iter, struct spa_bt_device *device) { DBusMessageIter dict, entry, variant; dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, "{sv}", &dict); dbus_message_iter_open_container(&dict, DBUS_TYPE_DICT_ENTRY, NULL, &entry); const char *prop_percentage = "Percentage"; dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &prop_percentage); dbus_message_iter_open_container(&entry, DBUS_TYPE_VARIANT, DBUS_TYPE_BYTE_AS_STRING, &variant); dbus_message_iter_append_basic(&variant, DBUS_TYPE_BYTE, &device->battery); dbus_message_iter_close_container(&entry, &variant); dbus_message_iter_close_container(&dict, &entry); dbus_message_iter_open_container(&dict, DBUS_TYPE_DICT_ENTRY, NULL, &entry); const char *prop_device = "Device"; dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &prop_device); dbus_message_iter_open_container(&entry, DBUS_TYPE_VARIANT, DBUS_TYPE_OBJECT_PATH_AS_STRING, &variant); dbus_message_iter_append_basic(&variant, DBUS_TYPE_OBJECT_PATH, &device->path); dbus_message_iter_close_container(&entry, &variant); dbus_message_iter_close_container(&dict, &entry); dbus_message_iter_close_container(iter, &dict); } // Send current percentage to BlueZ static void battery_update(struct spa_bt_device *device) { spa_log_debug(device->monitor->log, "updating battery: %s", device->battery_path); spa_autoptr(DBusMessage) msg = NULL; DBusMessageIter iter; msg = dbus_message_new_signal(device->battery_path, DBUS_INTERFACE_PROPERTIES, DBUS_SIGNAL_PROPERTIES_CHANGED); dbus_message_iter_init_append(msg, &iter); const char *interface = BLUEZ_INTERFACE_BATTERY_PROVIDER; dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &interface); battery_write_properties(&iter, device); if (!dbus_connection_send(device->monitor->conn, msg, NULL)) spa_log_error(device->monitor->log, "Error updating battery"); } // Create new virtual battery with value stored in current device object static void battery_create(struct spa_bt_device *device) { spa_autoptr(DBusMessage) msg = NULL; DBusMessageIter iter, entry, dict; msg = dbus_message_new_signal(PIPEWIRE_BATTERY_PROVIDER, DBUS_INTERFACE_OBJECT_MANAGER, DBUS_SIGNAL_INTERFACES_ADDED); dbus_message_iter_init_append(msg, &iter); dbus_message_iter_append_basic(&iter, DBUS_TYPE_OBJECT_PATH, &device->battery_path); dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sa{sv}}", &dict); dbus_message_iter_open_container(&dict, DBUS_TYPE_DICT_ENTRY, NULL, &entry); const char *interface = BLUEZ_INTERFACE_BATTERY_PROVIDER; dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &interface); battery_write_properties(&entry, device); dbus_message_iter_close_container(&dict, &entry); dbus_message_iter_close_container(&iter, &dict); if (!dbus_connection_send(device->monitor->conn, msg, NULL)) { spa_log_error(device->monitor->log, "Failed to create virtual battery for %s", device->address); return; } spa_log_debug(device->monitor->log, "Created virtual battery for %s", device->address); device->has_battery = true; } static void on_battery_provider_registered(DBusPendingCall *pending_call, void *data) { struct spa_bt_device *device = data; spa_assert(device->battery_pending_call == pending_call); spa_autoptr(DBusMessage) reply = steal_reply_and_unref(&device->battery_pending_call); if (dbus_message_get_type(reply) == DBUS_MESSAGE_TYPE_ERROR) { spa_log_error(device->monitor->log, "Failed to register battery provider. Error: %s", dbus_message_get_error_name(reply)); spa_log_error(device->monitor->log, "BlueZ Battery Provider is not available, won't retry to register it. Make sure you are running BlueZ 5.56+ with experimental features to use Battery Provider."); device->adapter->battery_provider_unavailable = true; return; } spa_log_debug(device->monitor->log, "Registered Battery Provider"); device->adapter->has_battery_provider = true; if (!device->has_battery) battery_create(device); } // Register Battery Provider for adapter and then create virtual battery for device static void register_battery_provider(struct spa_bt_device *device) { spa_autoptr(DBusMessage) method_call = NULL; DBusMessageIter message_iter; if (device->battery_pending_call) { spa_log_debug(device->monitor->log, "Already registering battery provider"); return; } method_call = dbus_message_new_method_call( BLUEZ_SERVICE, device->adapter_path, BLUEZ_INTERFACE_BATTERY_PROVIDER_MANAGER, "RegisterBatteryProvider"); if (!method_call) { spa_log_error(device->monitor->log, "Failed to register battery provider"); return; } dbus_message_iter_init_append(method_call, &message_iter); const char *object_path = PIPEWIRE_BATTERY_PROVIDER; dbus_message_iter_append_basic(&message_iter, DBUS_TYPE_OBJECT_PATH, &object_path); device->battery_pending_call = send_with_reply(device->monitor->conn, method_call, on_battery_provider_registered, device); if (!device->battery_pending_call) { spa_log_error(device->monitor->log, "Failed to register battery provider"); return; } } const struct media_codec * const * spa_bt_get_media_codecs(struct spa_bt_monitor *monitor) { return monitor->media_codecs; } static int media_codec_to_endpoint(const struct media_codec *codec, enum spa_bt_media_direction direction, char** object_path) { const char * endpoint; if (direction == SPA_BT_MEDIA_SOURCE) endpoint = codec->kind == MEDIA_CODEC_BAP ? BAP_SOURCE_ENDPOINT : A2DP_SOURCE_ENDPOINT; else if (direction == SPA_BT_MEDIA_SINK) endpoint = codec->kind == MEDIA_CODEC_BAP ? BAP_SINK_ENDPOINT : A2DP_SINK_ENDPOINT; else if (direction == SPA_BT_MEDIA_SOURCE_BROADCAST) endpoint = BAP_BROADCAST_SOURCE_ENDPOINT; else if (direction == SPA_BT_MEDIA_SINK_BROADCAST) endpoint = BAP_BROADCAST_SINK_ENDPOINT; *object_path = spa_aprintf("%s/%s", endpoint, codec->endpoint_name ? codec->endpoint_name : codec->name); if (*object_path == NULL) return -errno; return 0; } static const struct media_codec *media_endpoint_to_codec(struct spa_bt_monitor *monitor, const char *endpoint, bool *sink, const struct media_codec *preferred) { const char *ep_name; const struct media_codec * const * const media_codecs = monitor->media_codecs; const struct media_codec *found = NULL; int i; if (spa_strstartswith(endpoint, A2DP_SINK_ENDPOINT "/")) { ep_name = endpoint + strlen(A2DP_SINK_ENDPOINT "/"); *sink = true; } else if (spa_strstartswith(endpoint, A2DP_SOURCE_ENDPOINT "/")) { ep_name = endpoint + strlen(A2DP_SOURCE_ENDPOINT "/"); *sink = false; } else if (spa_strstartswith(endpoint, BAP_SOURCE_ENDPOINT "/")) { ep_name = endpoint + strlen(BAP_SOURCE_ENDPOINT "/"); *sink = false; } else if (spa_strstartswith(endpoint, BAP_SINK_ENDPOINT "/")) { ep_name = endpoint + strlen(BAP_SINK_ENDPOINT "/"); *sink = true; } else if (spa_strstartswith(endpoint, BAP_BROADCAST_SOURCE_ENDPOINT "/")) { ep_name = endpoint + strlen(BAP_BROADCAST_SOURCE_ENDPOINT "/"); *sink = false; } else if (spa_strstartswith(endpoint, BAP_BROADCAST_SINK_ENDPOINT "/")) { ep_name = endpoint + strlen(BAP_BROADCAST_SINK_ENDPOINT "/"); *sink = true; } else { *sink = true; return NULL; } for (i = 0; media_codecs[i]; i++) { const struct media_codec *codec = media_codecs[i]; const char *codec_ep_name = codec->endpoint_name ? codec->endpoint_name : codec->name; if (!preferred && !codec->fill_caps) continue; if (!spa_streq(ep_name, codec_ep_name)) continue; if ((*sink && !codec->decode) || (!*sink && !codec->encode)) continue; /* Same endpoint may be shared with multiple codec objects, * which may e.g. correspond to different encoder settings. * Look up which one we selected. */ if ((preferred && codec == preferred) || found == NULL) found = codec; } return found; } static int media_endpoint_to_profile(const char *endpoint) { if (spa_strstartswith(endpoint, A2DP_SINK_ENDPOINT "/")) return SPA_BT_PROFILE_A2DP_SOURCE; else if (spa_strstartswith(endpoint, A2DP_SOURCE_ENDPOINT "/")) return SPA_BT_PROFILE_A2DP_SINK; else if (spa_strstartswith(endpoint, BAP_SINK_ENDPOINT "/")) return SPA_BT_PROFILE_BAP_SOURCE; else if (spa_strstartswith(endpoint, BAP_SOURCE_ENDPOINT "/")) return SPA_BT_PROFILE_BAP_SINK; else if (spa_strstartswith(endpoint, BAP_BROADCAST_SINK_ENDPOINT "/")) return SPA_BT_PROFILE_BAP_BROADCAST_SOURCE; else if (spa_strstartswith(endpoint, BAP_BROADCAST_SOURCE_ENDPOINT "/")) return SPA_BT_PROFILE_BAP_BROADCAST_SINK; else return SPA_BT_PROFILE_NULL; } static bool is_media_codec_enabled(struct spa_bt_monitor *monitor, const struct media_codec *codec) { /* Mandatory codecs are always enabled */ switch (codec->id) { case SPA_BLUETOOTH_AUDIO_CODEC_SBC: case SPA_BLUETOOTH_AUDIO_CODEC_CVSD: case SPA_BLUETOOTH_AUDIO_CODEC_LC3: return true; default: return spa_dict_lookup(&monitor->enabled_codecs, codec->name) != NULL; } } static enum spa_bt_profile get_codec_profile(const struct media_codec *codec, enum spa_bt_media_direction direction) { switch (direction) { case SPA_BT_MEDIA_SOURCE: return codec->kind == MEDIA_CODEC_BAP ? SPA_BT_PROFILE_BAP_SOURCE : SPA_BT_PROFILE_A2DP_SOURCE; case SPA_BT_MEDIA_SINK: if (codec->kind == MEDIA_CODEC_ASHA) return SPA_BT_PROFILE_ASHA_SINK; else if (codec->kind == MEDIA_CODEC_BAP) return SPA_BT_PROFILE_BAP_SINK; else return SPA_BT_PROFILE_A2DP_SINK; case SPA_BT_MEDIA_SOURCE_BROADCAST: return SPA_BT_PROFILE_BAP_BROADCAST_SOURCE; case SPA_BT_MEDIA_SINK_BROADCAST: return SPA_BT_PROFILE_BAP_BROADCAST_SINK; default: spa_assert_not_reached(); } } static bool codec_has_direction(struct spa_bt_monitor *monitor, const struct media_codec *codec, enum spa_bt_media_direction direction) { if (!is_media_codec_enabled(monitor, codec)) return false; if (!(get_codec_profile(codec, direction) & monitor->enabled_profiles)) return false; switch (direction) { case SPA_BT_MEDIA_SOURCE: case SPA_BT_MEDIA_SOURCE_BROADCAST: return codec->encode; case SPA_BT_MEDIA_SINK: case SPA_BT_MEDIA_SINK_BROADCAST: return codec->decode; default: spa_assert_not_reached(); } } static enum spa_bt_profile swap_profile(enum spa_bt_profile profile) { switch (profile) { case SPA_BT_PROFILE_A2DP_SOURCE: return SPA_BT_PROFILE_A2DP_SINK; case SPA_BT_PROFILE_A2DP_SINK: return SPA_BT_PROFILE_A2DP_SOURCE; case SPA_BT_PROFILE_BAP_SOURCE: return SPA_BT_PROFILE_BAP_SINK; case SPA_BT_PROFILE_BAP_SINK: return SPA_BT_PROFILE_BAP_SOURCE; case SPA_BT_PROFILE_BAP_BROADCAST_SOURCE: return SPA_BT_PROFILE_BAP_BROADCAST_SINK; case SPA_BT_PROFILE_BAP_BROADCAST_SINK: return SPA_BT_PROFILE_BAP_BROADCAST_SOURCE; default: return SPA_BT_PROFILE_NULL; } } static uint32_t get_codec_target_profile(struct spa_bt_monitor *monitor, const struct media_codec *codec) { enum spa_bt_profile profile = 0; int i; for (i = 0; i < SPA_BT_MEDIA_DIRECTION_LAST; ++i) if (codec_has_direction(monitor, codec, i)) profile |= swap_profile(get_codec_profile(codec, i)); return profile; } static bool endpoint_should_be_registered(struct spa_bt_monitor *monitor, const struct media_codec *codec, enum spa_bt_media_direction direction) { /* Codecs with fill_caps == NULL share endpoint with another codec, * and don't have their own endpoint */ return codec_has_direction(monitor, codec, direction) && codec->fill_caps; } static DBusHandlerResult endpoint_select_configuration(DBusConnection *conn, DBusMessage *m, void *userdata) { struct spa_bt_monitor *monitor = userdata; const char *path; uint8_t *cap, config[A2DP_MAX_CAPS_SIZE]; uint8_t *pconf = (uint8_t *) config; spa_autoptr(DBusMessage) r = NULL; spa_auto(DBusError) err = DBUS_ERROR_INIT; int size, res; const struct media_codec *codec; bool sink; path = dbus_message_get_path(m); if (!dbus_message_get_args(m, &err, DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE, &cap, &size, DBUS_TYPE_INVALID)) { spa_log_error(monitor->log, "Endpoint SelectConfiguration(): %s", err.message); return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } spa_log_info(monitor->log, "%p: %s select conf %d", monitor, path, size); spa_debug_log_mem(monitor->log, SPA_LOG_LEVEL_DEBUG, 2, cap, (size_t)size); /* For codecs sharing the same endpoint, BlueZ-initiated connections * always pick the default one. The session manager will * switch the codec to a saved value after connection, so this generally * does not matter. */ codec = media_endpoint_to_codec(monitor, path, &sink, NULL); spa_log_debug(monitor->log, "%p: %s codec:%s", monitor, path, codec ? codec->name : ""); if (codec != NULL) /* FIXME: We can't determine which device the SelectConfiguration() * call is associated with, therefore device settings are not passed. * This causes inconsistency with SelectConfiguration() triggered * by codec switching. */ res = codec->select_config(codec, sink ? MEDIA_CODEC_FLAG_SINK : 0, cap, size, &monitor->default_audio_info, &monitor->global_settings, config, NULL); else res = -ENOTSUP; if (res < 0 || res != size) { spa_log_error(monitor->log, "can't select config: %d (%s)", res, spa_strerror(res)); if ((r = dbus_message_new_error(m, "org.bluez.Error.InvalidArguments", "Unable to select configuration")) == NULL) return DBUS_HANDLER_RESULT_NEED_MEMORY; goto exit_send; } spa_debug_log_mem(monitor->log, SPA_LOG_LEVEL_DEBUG, 2, pconf, (size_t)size); if ((r = dbus_message_new_method_return(m)) == NULL) return DBUS_HANDLER_RESULT_NEED_MEMORY; if (!dbus_message_append_args(r, DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE, &pconf, size, DBUS_TYPE_INVALID)) return DBUS_HANDLER_RESULT_NEED_MEMORY; exit_send: if (!dbus_connection_send(conn, r, NULL)) return DBUS_HANDLER_RESULT_NEED_MEMORY; return DBUS_HANDLER_RESULT_HANDLED; } static void append_basic_variant_dict_entry(DBusMessageIter *dict, const char* key, int variant_type_int, const char* variant_type_str, void* variant); static void append_basic_array_variant_dict_entry(DBusMessageIter *dict, const char* key, const char* variant_type_str, const char* array_type_str, int array_type_int, void* data, int data_size); static struct spa_bt_remote_endpoint *remote_endpoint_find(struct spa_bt_monitor *monitor, const char *path); static bool check_iter_signature(DBusMessageIter *it, const char *sig) { char *v; bool res; v = dbus_message_iter_get_signature(it); res = spa_streq(v, sig); dbus_free(v); return res; } static void parse_codec_qos(struct spa_bt_monitor *monitor, DBusMessageIter *iter, struct bap_codec_qos_full *qos) { DBusMessageIter dict_iter = *iter; memset(qos, 0, sizeof(*qos)); qos->cig = 0xff; qos->cis = 0xff; qos->big = 0xff; qos->bis = 0xff; if (!check_iter_signature(&dict_iter, "{sv}")) { spa_log_warn(monitor->log, "Invalid BAP QoS in DBus"); return; } while (dbus_message_iter_get_arg_type(&dict_iter) != DBUS_TYPE_INVALID) { DBusMessageIter it[2]; const char *key; int type; dbus_message_iter_recurse(&dict_iter, &it[0]); dbus_message_iter_get_basic(&it[0], &key); dbus_message_iter_next(&it[0]); dbus_message_iter_recurse(&it[0], &it[1]); type = dbus_message_iter_get_arg_type(&it[1]); if (type == DBUS_TYPE_BYTE) { uint8_t value; dbus_message_iter_get_basic(&it[1], &value); spa_log_debug(monitor->log, "qos: %s=%d", key, (int)value); if (spa_streq(key, "PHY")) qos->qos.phy = value; else if (spa_streq(key, "Retransmissions")) qos->qos.retransmission = value; else if (spa_streq(key, "CIG")) qos->cig = value; else if (spa_streq(key, "CIS")) qos->cis = value; else if (spa_streq(key, "BIG")) qos->big = value; else if (spa_streq(key, "BIS")) qos->bis = value; else if (spa_streq(key, "TargetLatency")) qos->qos.target_latency = value; else if (spa_streq(key, "Framing")) qos->qos.framing = value; } else if (type == DBUS_TYPE_UINT16) { dbus_uint16_t value; dbus_message_iter_get_basic(&it[1], &value); spa_log_debug(monitor->log, "qos: %s=%d", key, (int)value); if (spa_streq(key, "SDU")) qos->qos.sdu = value; else if (spa_streq(key, "Latency") || spa_streq(key, "MaximumLatency")) qos->qos.latency = value; } else if (type == DBUS_TYPE_UINT32) { dbus_uint32_t value; dbus_message_iter_get_basic(&it[1], &value); spa_log_debug(monitor->log, "qos: %s=%d", key, (int)value); if (spa_streq(key, "Interval")) qos->qos.interval = value; else if (spa_streq(key, "PresentationDelay")) qos->qos.delay = value; } dbus_message_iter_next(&dict_iter); } } static void parse_endpoint_qos(struct spa_bt_monitor *monitor, DBusMessageIter *iter, struct bap_endpoint_qos *qos) { DBusMessageIter dict_iter = *iter; if (!check_iter_signature(&dict_iter, "{sv}")) { spa_log_warn(monitor->log, "Invalid BAP Endpoint QoS in DBus"); return; } while (dbus_message_iter_get_arg_type(&dict_iter) != DBUS_TYPE_INVALID) { DBusMessageIter it[2]; const char *key; int type; dbus_message_iter_recurse(&dict_iter, &it[0]); dbus_message_iter_get_basic(&it[0], &key); dbus_message_iter_next(&it[0]); dbus_message_iter_recurse(&it[0], &it[1]); type = dbus_message_iter_get_arg_type(&it[1]); if (type == DBUS_TYPE_BYTE) { uint8_t value; dbus_message_iter_get_basic(&it[1], &value); spa_log_debug(monitor->log, "ep qos: %s=%d", key, (int)value); if (spa_streq(key, "Framing")) qos->framing = value; else if (spa_streq(key, "PHY")) qos->phy = value; else if (spa_streq(key, "Retransmissions")) qos->retransmission = value; } else if (type == DBUS_TYPE_UINT16) { dbus_uint16_t value; dbus_message_iter_get_basic(&it[1], &value); spa_log_debug(monitor->log, "ep qos: %s=%d", key, (int)value); if (spa_streq(key, "Latency") || spa_streq(key, "MaximumLatency")) qos->latency = value; else if (spa_streq(key, "Context")) qos->context = value; else if (spa_streq(key, "SupportedContext")) qos->supported_context = value; } else if (type == DBUS_TYPE_UINT32) { dbus_uint32_t value; dbus_message_iter_get_basic(&it[1], &value); spa_log_debug(monitor->log, "ep qos: %s=%d", key, (int)value); if (spa_streq(key, "MinimumDelay")) qos->delay_min = value; else if (spa_streq(key, "MaximumDelay")) qos->delay_max = value; else if (spa_streq(key, "PreferredMinimumDelay")) qos->preferred_delay_min = value; else if (spa_streq(key, "PreferredMaximumDelay")) qos->preferred_delay_max = value; } dbus_message_iter_next(&dict_iter); } } static int parse_endpoint_props(struct spa_bt_monitor *monitor, DBusMessageIter *iter, uint8_t **caps, size_t *caps_size, uint8_t **meta, size_t *meta_size, const char **endpoint_path, struct bap_endpoint_qos *qos) { DBusMessageIter dict_iter = *iter; const char *key = NULL; int type = 0; if (!check_iter_signature(&dict_iter, "{sv}")) { spa_log_warn(monitor->log, "Invalid BAP Endpoint QoS in DBus"); return -EINVAL; } while (dbus_message_iter_get_arg_type(&dict_iter) != DBUS_TYPE_INVALID) { DBusMessageIter it[3]; dbus_message_iter_recurse(&dict_iter, &it[0]); dbus_message_iter_get_basic(&it[0], &key); dbus_message_iter_next(&it[0]); dbus_message_iter_recurse(&it[0], &it[1]); type = dbus_message_iter_get_arg_type(&it[1]); if (spa_streq(key, "Capabilities") || spa_streq(key, "Metadata")) { uint8_t **dest; size_t *size; uint8_t *data, *buf; int n; if (spa_streq(key, "Capabilities")) { dest = caps; size = caps_size; } else { dest = meta; size = meta_size; } if (!dest) goto next; spa_assert(dest && size); if (type != DBUS_TYPE_ARRAY) goto bad_property; dbus_message_iter_recurse(&it[1], &it[2]); type = dbus_message_iter_get_arg_type(&it[2]); if (type != DBUS_TYPE_BYTE) goto bad_property; dbus_message_iter_get_fixed_array(&it[2], &data, &n); buf = malloc(n); if (!buf) return -ENOMEM; free(*dest); *dest = buf; *size = n; memcpy(buf, data, n); spa_log_info(monitor->log, "%p: %s size:%zu", monitor, key, *size); spa_debug_log_mem(monitor->log, SPA_LOG_LEVEL_DEBUG, ' ', *dest, *size); } else if (spa_streq(key, "Endpoint")) { if (!endpoint_path) goto next; if (type != DBUS_TYPE_OBJECT_PATH) goto bad_property; dbus_message_iter_get_basic(&it[1], endpoint_path); spa_log_info(monitor->log, "%p: %s %s", monitor, key, *endpoint_path); } else if (spa_streq(key, "QoS")) { if (!qos) goto next; if (!check_iter_signature(&it[1], "a{sv}")) goto bad_property; dbus_message_iter_recurse(&it[1], &it[2]); parse_endpoint_qos(monitor, &it[2], qos); } else if (spa_streq(key, "Locations") || spa_streq(key, "Location")) { dbus_uint32_t value; if (!qos) goto next; if (type != DBUS_TYPE_UINT32) goto bad_property; dbus_message_iter_get_basic(&it[1], &value); spa_log_debug(monitor->log, "ep qos: %s=%d", key, (int)value); qos->locations = value; } else if (spa_streq(key, "ChannelAllocation")) { dbus_uint32_t value; if (!qos) goto next; if (type != DBUS_TYPE_UINT32) goto bad_property; dbus_message_iter_get_basic(&it[1], &value); spa_log_debug(monitor->log, "ep qos: %s=%d", key, (int)value); qos->channel_allocation = value; } else if (spa_streq(key, "Context") || spa_streq(key, "SupportedContext")) { dbus_uint16_t value; if (!qos) goto next; if (type != DBUS_TYPE_UINT16) goto bad_property; dbus_message_iter_get_basic(&it[1], &value); spa_log_debug(monitor->log, "ep qos: %s=%d", key, (int)value); if (spa_streq(key, "Context")) qos->context = value; else if (spa_streq(key, "SupportedContext")) qos->supported_context = value; } next: dbus_message_iter_next(&dict_iter); } return 0; bad_property: spa_log_error(monitor->log, "Property %s of wrong type %c", key, (char)type); return -EINVAL; } static DBusHandlerResult endpoint_select_properties(DBusConnection *conn, DBusMessage *m, void *userdata) { struct spa_bt_monitor *monitor = userdata; const char *path; DBusMessageIter args, props, iter; spa_autoptr(DBusMessage) r = NULL; int res; const struct media_codec *codec; struct spa_bt_remote_endpoint *ep; bool sink, duplex; const char *err_msg = "Unknown error"; struct spa_dict settings; struct spa_dict_item setting_items[128]; unsigned int i, j; const char *endpoint_path = NULL; uint8_t config[A2DP_MAX_CAPS_SIZE]; void *config_data = NULL; char locations[64] = {0}; char channel_allocation[64] = {0}; char supported_context[64] = {0}; char available_context[64] = {0}; char metadata_len[64] = {0}; int conf_size; DBusMessageIter dict; if (!dbus_message_iter_init(m, &args) || !spa_streq(dbus_message_get_signature(m), "a{sv}")) { spa_log_error(monitor->log, "Invalid signature for method SelectProperties()"); return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } dbus_message_iter_recurse(&args, &props); if (dbus_message_iter_get_arg_type(&props) != DBUS_TYPE_DICT_ENTRY) return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; path = dbus_message_get_path(m); if ((r = dbus_message_new_method_return(m)) == NULL) return DBUS_HANDLER_RESULT_NEED_MEMORY; /* TODO: for codecs with shared endpoint, this currently always picks the default * one. However, currently we don't have BAP codecs with shared endpoint, so * this does not matter, but in case they are needed later we should pick the * right one here. */ codec = media_endpoint_to_codec(monitor, path, &sink, NULL); spa_log_debug(monitor->log, "%p: %s codec:%s", monitor, path, codec ? codec->name : ""); if (!codec || codec->kind != MEDIA_CODEC_BAP || !codec->get_qos) { spa_log_error(monitor->log, "Unsupported codec"); err_msg = "Unsupported codec"; goto error; } /* Find endpoint */ iter = props; if (parse_endpoint_props(monitor, &iter, NULL, NULL, NULL, NULL, &endpoint_path, NULL) < 0) goto error_invalid; ep = remote_endpoint_find(monitor, endpoint_path); if (!ep || !ep->device || !ep->uuid) { spa_log_warn(monitor->log, "Unable to find remote endpoint for %s", endpoint_path); goto error_invalid; } /* Call of SelectProperties means that local device is BAP Client * and therefore remote endpoint is BAP Server / acceptor */ ep->acceptor = true; /* Parse endpoint properties */ iter = props; if (parse_endpoint_props(monitor, &iter, &ep->capabilities, &ep->capabilities_len, &ep->metadata, &ep->metadata_len, NULL, &ep->qos) < 0) goto error_invalid; if (ep->qos.locations) spa_scnprintf(locations, sizeof(locations), "%"PRIu32, ep->qos.locations); if (ep->qos.channel_allocation) spa_scnprintf(channel_allocation, sizeof(channel_allocation), "%"PRIu32, ep->qos.channel_allocation); spa_scnprintf(supported_context, sizeof(supported_context), "%"PRIu16, ep->qos.supported_context); spa_scnprintf(available_context, sizeof(available_context), "%"PRIu16, ep->qos.context); spa_scnprintf(metadata_len, sizeof(metadata_len), "%zu", ep->metadata_len); if (!ep->device->preferred_profiles) ep->device->preferred_profiles = ep->device->profiles; duplex = SPA_FLAG_IS_SET(ep->device->preferred_profiles, SPA_BT_PROFILE_BAP_DUPLEX); i = 0; setting_items[i++] = SPA_DICT_ITEM_INIT("bluez5.bap.locations", locations); setting_items[i++] = SPA_DICT_ITEM_INIT("bluez5.bap.channel-allocation", channel_allocation); setting_items[i++] = SPA_DICT_ITEM_INIT("bluez5.bap.supported-context", supported_context); setting_items[i++] = SPA_DICT_ITEM_INIT("bluez5.bap.available-context", available_context); setting_items[i++] = SPA_DICT_ITEM_INIT("bluez5.bap.sink", sink ? "true" : "false"); setting_items[i++] = SPA_DICT_ITEM_INIT("bluez5.bap.duplex", duplex ? "true" : "false"); setting_items[i++] = SPA_DICT_ITEM_INIT("bluez5.bap.debug", "true"); setting_items[i++] = SPA_DICT_ITEM_INIT("bluez5.bap.metadata", (void *)ep->metadata); setting_items[i++] = SPA_DICT_ITEM_INIT("bluez5.bap.metadata-len", metadata_len); if (ep->device->settings) for (j = 0; j < ep->device->settings->n_items && i < SPA_N_ELEMENTS(setting_items); ++i, ++j) setting_items[i] = ep->device->settings->items[j]; settings = SPA_DICT_INIT(setting_items, i); conf_size = codec->select_config(codec, 0, ep->capabilities, ep->capabilities_len, &monitor->default_audio_info, &settings, config, &config_data); if (conf_size < 0) { spa_log_error(monitor->log, "can't select config: %d (%s)", conf_size, spa_strerror(conf_size)); goto error_invalid; } spa_log_info(monitor->log, "%p: selected conf %d", monitor, conf_size); spa_debug_log_mem(monitor->log, SPA_LOG_LEVEL_DEBUG, ' ', (uint8_t *)config, (size_t)conf_size); dbus_message_iter_init_append(r, &iter); dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict); append_basic_array_variant_dict_entry(&dict, "Capabilities", "ay", "y", DBUS_TYPE_BYTE, &config, conf_size); { struct bap_codec_qos qos; DBusMessageIter entry, variant, qos_dict; const char *entry_key = "QoS"; uint8_t cig = 0xff; spa_zero(qos); res = codec->get_qos(codec, &ep->qos, config_data, &qos); if (res < 0) { spa_log_error(monitor->log, "can't select QOS config: %d (%s)", res, spa_strerror(res)); goto error_invalid; } if (ep->device->settings) { const char *str = spa_dict_lookup(ep->device->settings, "bluez5.bap.cig"); uint32_t value; if (spa_atou32(str, &value, 0)) cig = value; } spa_log_debug(monitor->log, "select qos: interval:%d framing:%d phy:%d sdu:%d " "rtn:%d latency:%d delay:%d target_latency:%d cig:%u", qos.interval, qos.framing, qos.phy, qos.sdu, qos.retransmission, qos.latency, (int)qos.delay, qos.target_latency, cig); dbus_message_iter_open_container(&dict, DBUS_TYPE_DICT_ENTRY, NULL, &entry); dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &entry_key); dbus_message_iter_open_container(&entry, DBUS_TYPE_VARIANT, "a{sv}", &variant); dbus_message_iter_open_container(&variant, DBUS_TYPE_ARRAY, DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &qos_dict); append_basic_variant_dict_entry(&qos_dict, "Interval", DBUS_TYPE_UINT32, "u", &qos.interval); append_basic_variant_dict_entry(&qos_dict, "Framing", DBUS_TYPE_BYTE, "y", &qos.framing); append_basic_variant_dict_entry(&qos_dict, "PHY", DBUS_TYPE_BYTE, "y", &qos.phy); append_basic_variant_dict_entry(&qos_dict, "SDU", DBUS_TYPE_UINT16, "q", &qos.sdu); append_basic_variant_dict_entry(&qos_dict, "Retransmissions", DBUS_TYPE_BYTE, "y", &qos.retransmission); append_basic_variant_dict_entry(&qos_dict, "Latency", DBUS_TYPE_UINT16, "q", &qos.latency); append_basic_variant_dict_entry(&qos_dict, "PresentationDelay", DBUS_TYPE_UINT32, "u", &qos.delay); append_basic_variant_dict_entry(&qos_dict, "TargetLatency", DBUS_TYPE_BYTE, "y", &qos.target_latency); if (cig < 0xf0) append_basic_variant_dict_entry(&qos_dict, "CIG", DBUS_TYPE_BYTE, "y", &cig); dbus_message_iter_close_container(&variant, &qos_dict); dbus_message_iter_close_container(&entry, &variant); dbus_message_iter_close_container(&dict, &entry); } if (codec->get_metadata) { uint8_t meta[4096] = {}; size_t meta_size; meta_size = res = codec->get_metadata(codec, config_data, meta, sizeof(meta)); if (res < 0) { spa_log_error(monitor->log, "can't select metadata config: %d (%s)", res, spa_strerror(res)); goto error_invalid; } if (meta_size) { spa_log_info(monitor->log, "%p: selected metadata %d", monitor, (int)meta_size); spa_debug_log_mem(monitor->log, SPA_LOG_LEVEL_DEBUG, ' ', meta, meta_size); append_basic_array_variant_dict_entry(&dict, "Metadata", "ay", "y", DBUS_TYPE_BYTE, &meta, meta_size); } } dbus_message_iter_close_container(&iter, &dict); if (config_data && codec->free_config_data) codec->free_config_data(codec, config_data); if (!dbus_connection_send(conn, r, NULL)) return DBUS_HANDLER_RESULT_NEED_MEMORY; return DBUS_HANDLER_RESULT_HANDLED; error_invalid: err_msg = "Invalid property"; goto error; error: if (config_data && codec->free_config_data) codec->free_config_data(codec, config_data); if (!reply_with_error(conn, m, "org.bluez.Error.InvalidArguments", err_msg)) return DBUS_HANDLER_RESULT_NEED_MEMORY; return DBUS_HANDLER_RESULT_HANDLED; } static struct spa_bt_adapter *adapter_find(struct spa_bt_monitor *monitor, const char *path) { struct spa_bt_adapter *d; spa_list_for_each(d, &monitor->adapter_list, link) if (spa_streq(d->path, path)) return d; return NULL; } static int parse_modalias(const char *modalias, uint16_t *source, uint16_t *vendor, uint16_t *product, uint16_t *version) { char *pos; unsigned int src, i, j, k; if (spa_strstartswith(modalias, "bluetooth:")) src = SOURCE_ID_BLUETOOTH; else if (spa_strstartswith(modalias, "usb:")) src = SOURCE_ID_USB; else return -EINVAL; pos = strchr(modalias, ':'); if (pos == NULL) return -EINVAL; if (sscanf(pos + 1, "v%04Xp%04Xd%04X", &i, &j, &k) != 3) return -EINVAL; /* Ignore BlueZ placeholder value */ if (src == SOURCE_ID_USB && i == 0x1d6b && j == 0x0246) return -ENXIO; *source = src; *vendor = i; *product = j; *version = k; return 0; } static int adapter_update_props(struct spa_bt_adapter *adapter, DBusMessageIter *props_iter, DBusMessageIter *invalidated_iter) { struct spa_bt_monitor *monitor = adapter->monitor; while (dbus_message_iter_get_arg_type(props_iter) != DBUS_TYPE_INVALID) { DBusMessageIter it[2]; const char *key; int type; dbus_message_iter_recurse(props_iter, &it[0]); dbus_message_iter_get_basic(&it[0], &key); dbus_message_iter_next(&it[0]); dbus_message_iter_recurse(&it[0], &it[1]); type = dbus_message_iter_get_arg_type(&it[1]); if (type == DBUS_TYPE_STRING || type == DBUS_TYPE_OBJECT_PATH) { const char *value; dbus_message_iter_get_basic(&it[1], &value); spa_log_debug(monitor->log, "adapter %p: %s=%s", adapter, key, value); if (spa_streq(key, "Alias")) { free(adapter->alias); adapter->alias = strdup(value); } else if (spa_streq(key, "Name")) { free(adapter->name); adapter->name = strdup(value); } else if (spa_streq(key, "Address")) { free(adapter->address); adapter->address = strdup(value); } else if (spa_streq(key, "Modalias")) { int ret; ret = parse_modalias(value, &adapter->source_id, &adapter->vendor_id, &adapter->product_id, &adapter->version_id); if (ret < 0) spa_log_debug(monitor->log, "adapter %p: %s=%s ignored: %s", adapter, key, value, spa_strerror(ret)); } } else if (type == DBUS_TYPE_UINT32) { uint32_t value; dbus_message_iter_get_basic(&it[1], &value); spa_log_debug(monitor->log, "adapter %p: %s=%d", adapter, key, value); if (spa_streq(key, "Class")) adapter->bluetooth_class = value; } else if (type == DBUS_TYPE_BOOLEAN) { int value; dbus_message_iter_get_basic(&it[1], &value); spa_log_debug(monitor->log, "adapter %p: %s=%d", adapter, key, value); if (spa_streq(key, "Powered")) { adapter->powered = value; } } else if (spa_streq(key, "UUIDs")) { DBusMessageIter iter; if (!check_iter_signature(&it[1], "as")) goto next; dbus_message_iter_recurse(&it[1], &iter); while (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_INVALID) { const char *uuid; enum spa_bt_profile profile; dbus_message_iter_get_basic(&iter, &uuid); profile = spa_bt_profile_from_uuid(uuid); if (profile && (adapter->profiles & profile) == 0) { spa_log_debug(monitor->log, "adapter %p: add UUID=%s", adapter, uuid); adapter->profiles |= profile; } else if (strcasecmp(uuid, SPA_BT_UUID_PACS) == 0 && (adapter->profiles & SPA_BT_PROFILE_BAP_SINK) == 0) { spa_log_debug(monitor->log, "adapter %p: add UUID=%s", adapter, SPA_BT_UUID_BAP_SINK); adapter->profiles |= SPA_BT_PROFILE_BAP_SINK; spa_log_debug(monitor->log, "adapter %p: add UUID=%s", adapter, SPA_BT_UUID_BAP_SOURCE); adapter->profiles |= SPA_BT_PROFILE_BAP_SOURCE; spa_log_debug(monitor->log, "adapter %p: add UUID=%s", adapter, SPA_BT_UUID_BAP_BROADCAST_SOURCE); adapter->profiles |= SPA_BT_PROFILE_BAP_BROADCAST_SOURCE; spa_log_debug(monitor->log, "adapter %p: add UUID=%s", adapter, SPA_BT_UUID_BAP_BROADCAST_SINK); adapter->profiles |= SPA_BT_PROFILE_BAP_BROADCAST_SINK; } dbus_message_iter_next(&iter); } } else spa_log_debug(monitor->log, "adapter %p: unhandled key %s", adapter, key); next: dbus_message_iter_next(props_iter); } return 0; } static int adapter_media_update_props(struct spa_bt_adapter *adapter, DBusMessageIter *props_iter, DBusMessageIter *invalidated_iter) { /* Handle org.bluez.Media1 interface properties of .Adapter1 objects */ struct spa_bt_monitor *monitor = adapter->monitor; while (dbus_message_iter_get_arg_type(props_iter) != DBUS_TYPE_INVALID) { DBusMessageIter it[2]; const char *key; dbus_message_iter_recurse(props_iter, &it[0]); dbus_message_iter_get_basic(&it[0], &key); dbus_message_iter_next(&it[0]); dbus_message_iter_recurse(&it[0], &it[1]); if (spa_streq(key, "SupportedUUIDs")) { DBusMessageIter iter; if (!check_iter_signature(&it[1], "as")) goto next; dbus_message_iter_recurse(&it[1], &iter); while (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_INVALID) { const char *uuid; dbus_message_iter_get_basic(&iter, &uuid); if (spa_streq(uuid, SPA_BT_UUID_BAP_SINK)) { adapter->le_audio_supported = true; spa_log_info(monitor->log, "Adapter %s: LE Audio supported", adapter->path); } if (spa_streq(uuid, SPA_BT_UUID_BAP_BROADCAST_SOURCE) || spa_streq(uuid, SPA_BT_UUID_BAP_BROADCAST_SINK)) { adapter->le_audio_bcast_supported = true; spa_log_info(monitor->log, "Adapter %s: LE Broadcast Audio supported", adapter->path); } dbus_message_iter_next(&iter); } } else if (spa_streq(key, "SupportedFeatures")) { DBusMessageIter iter; if (!check_iter_signature(&it[1], "as")) goto next; dbus_message_iter_recurse(&it[1], &iter); while (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_INVALID) { const char *feature; dbus_message_iter_get_basic(&iter, &feature); if (spa_streq(feature, "tx-timestamping")) { adapter->tx_timestamping_supported = true; spa_log_info(monitor->log, "Adapter %s: TX timestamping supported", adapter->path); } dbus_message_iter_next(&iter); } } else spa_log_debug(monitor->log, "media: unhandled key %s", key); next: dbus_message_iter_next(props_iter); } return 0; } static void adapter_update_devices(struct spa_bt_adapter *adapter) { struct spa_bt_monitor *monitor = adapter->monitor; struct spa_bt_device *device; /* * Update devices when new adapter appears. * Devices may appear on DBus before or after the adapter does. */ spa_list_for_each(device, &monitor->device_list, link) { if (device->adapter == NULL && spa_streq(device->adapter_path, adapter->path)) device->adapter = adapter; } } static void adapter_register_player(struct spa_bt_adapter *adapter) { if (adapter->player_registered || !adapter->monitor->dummy_avrcp_player) return; if (spa_bt_player_register(adapter->dummy_player, adapter->path) == 0) adapter->player_registered = true; } static int adapter_init_bus_type(struct spa_bt_monitor *monitor, struct spa_bt_adapter *d) { char path[1024], buf[1024]; const char *str; ssize_t res = -EINVAL; d->bus_type = BUS_TYPE_OTHER; str = strrchr(d->path, '/'); /* hciXX */ if (str == NULL) return -ENOENT; snprintf(path, sizeof(path), "/sys/class/bluetooth/%s/device/subsystem", str); if ((res = readlink(path, buf, sizeof(buf)-1)) < 0) return -errno; buf[res] = '\0'; str = strrchr(buf, '/'); if (str && spa_streq(str, "/usb")) d->bus_type = BUS_TYPE_USB; return 0; } static int adapter_init_modalias(struct spa_bt_monitor *monitor, struct spa_bt_adapter *d) { char path[1024]; int vendor_id, product_id; const char *str; /* Lookup vendor/product id for the device, if present */ str = strrchr(d->path, '/'); /* hciXX */ if (str == NULL) return -EINVAL; snprintf(path, sizeof(path), "/sys/class/bluetooth/%s/device/modalias", str); spa_autoptr(FILE) f = fopen(path, "rbe"); if (f == NULL) return -errno; if (fscanf(f, "usb:v%04Xp%04X", &vendor_id, &product_id) != 2) return -EINVAL; d->source_id = SOURCE_ID_USB; d->vendor_id = vendor_id; d->product_id = product_id; spa_log_debug(monitor->log, "adapter %p: usb vendor:%04x product:%04x", d, vendor_id, product_id); return 0; } static struct spa_bt_adapter *adapter_create(struct spa_bt_monitor *monitor, const char *path) { struct spa_bt_adapter *d; d = calloc(1, sizeof(struct spa_bt_adapter)); if (d == NULL) return NULL; d->dummy_player = spa_bt_player_new(monitor->conn, monitor->log); if (d->dummy_player == NULL) { free(d); return NULL; } d->monitor = monitor; d->path = strdup(path); spa_list_prepend(&monitor->adapter_list, &d->link); adapter_init_bus_type(monitor, d); adapter_init_modalias(monitor, d); return d; } static void device_free(struct spa_bt_device *device); static void adapter_free(struct spa_bt_adapter *adapter) { struct spa_bt_monitor *monitor = adapter->monitor; struct spa_bt_device *d, *td; spa_log_debug(monitor->log, "%p", adapter); /* Devices should be destroyed before their assigned adapter */ spa_list_for_each_safe(d, td, &monitor->device_list, link) if (d->adapter == adapter) device_free(d); spa_bt_player_destroy(adapter->dummy_player); spa_list_remove(&adapter->link); free(adapter->alias); free(adapter->name); free(adapter->address); free(adapter->path); free(adapter); } static void metadata_entry_free(struct spa_bt_metadata *metadata_entry) { spa_list_remove(&metadata_entry->link); free(metadata_entry); } static void bis_entry_free(struct spa_bt_bis *bis_entry) { struct spa_bt_metadata *m; spa_list_consume(m, &bis_entry->metadata_list, link) metadata_entry_free(m); spa_list_remove(&bis_entry->link); free(bis_entry); } static void big_entry_free(struct spa_bt_big *big_entry) { struct spa_bt_bis *b; spa_list_consume(b, &big_entry->bis_list, link) bis_entry_free(b); spa_list_remove(&big_entry->link); free(big_entry); } static uint32_t adapter_connectable_profiles(struct spa_bt_adapter *adapter) { struct spa_bt_monitor *monitor = adapter->monitor; const uint32_t profiles = adapter->profiles; uint32_t mask = 0; if (profiles & SPA_BT_PROFILE_A2DP_SINK) mask |= SPA_BT_PROFILE_A2DP_SOURCE; if (profiles & SPA_BT_PROFILE_A2DP_SOURCE) mask |= SPA_BT_PROFILE_A2DP_SINK; if (profiles & SPA_BT_PROFILE_BAP_SINK) mask |= SPA_BT_PROFILE_BAP_SOURCE; if (profiles & SPA_BT_PROFILE_BAP_SOURCE) mask |= SPA_BT_PROFILE_BAP_SINK; if (profiles & SPA_BT_PROFILE_BAP_BROADCAST_SINK) mask |= SPA_BT_PROFILE_BAP_BROADCAST_SOURCE; if (profiles & SPA_BT_PROFILE_BAP_BROADCAST_SOURCE) mask |= SPA_BT_PROFILE_BAP_BROADCAST_SINK; if (profiles & SPA_BT_PROFILE_HSP_AG) mask |= SPA_BT_PROFILE_HSP_HS; if (profiles & SPA_BT_PROFILE_HSP_HS) mask |= SPA_BT_PROFILE_HSP_AG; if (profiles & SPA_BT_PROFILE_HFP_AG) mask |= SPA_BT_PROFILE_HFP_HF; if (profiles & SPA_BT_PROFILE_HFP_HF) mask |= SPA_BT_PROFILE_HFP_AG; if (monitor->backend_selection == BACKEND_NONE) mask &= ~SPA_BT_PROFILE_HEADSET_AUDIO; return mask; } struct spa_bt_device *spa_bt_device_find(struct spa_bt_monitor *monitor, const char *path) { struct spa_bt_device *d; spa_list_for_each(d, &monitor->device_list, link) if (spa_streq(d->path, path)) return d; return NULL; } struct spa_bt_device *spa_bt_device_find_by_address(struct spa_bt_monitor *monitor, const char *remote_address, const char *local_address) { struct spa_bt_device *d; spa_list_for_each(d, &monitor->device_list, link) if (spa_streq(d->address, remote_address) && spa_streq(d->adapter->address, local_address)) return d; return NULL; } static uint64_t get_time_now(struct spa_bt_monitor *monitor) { struct timespec ts; spa_system_clock_gettime(monitor->main_system, CLOCK_MONOTONIC, &ts); return SPA_TIMESPEC_TO_NSEC(&ts); } void spa_bt_device_update_last_bluez_action_time(struct spa_bt_device *device) { device->last_bluez_action_time = get_time_now(device->monitor); } static struct spa_bt_device *device_create(struct spa_bt_monitor *monitor, const char *path) { struct spa_bt_device *d; d = calloc(1, sizeof(struct spa_bt_device)); if (d == NULL) return NULL; d->id = monitor->id++; d->monitor = monitor; d->path = strdup(path); d->battery_path = battery_get_name(d->path); d->reconnect_profiles = DEFAULT_RECONNECT_PROFILES; d->hw_volume_profiles = DEFAULT_HW_VOLUME_PROFILES; spa_list_init(&d->remote_endpoint_list); spa_list_init(&d->transport_list); spa_list_init(&d->codec_switch_list); spa_list_init(&d->set_membership_list); spa_hook_list_init(&d->listener_list); spa_list_prepend(&monitor->device_list, &d->link); spa_bt_device_update_last_bluez_action_time(d); return d; } static void device_clear_sub(struct spa_bt_device *device) { battery_remove(device); spa_bt_device_release_transports(device); device->preferred_codec = NULL; device->preferred_profiles = 0; } static void device_free(struct spa_bt_device *device) { struct spa_bt_remote_endpoint *ep, *tep; struct spa_bt_codec_switch *sw; struct spa_bt_transport *t, *tt; struct spa_bt_monitor *monitor = device->monitor; struct spa_bt_set_membership *s; spa_log_debug(monitor->log, "%p", device); spa_bt_device_emit_destroy(device); device_clear_sub(device); device_stop_timer(device); if (device->added) { spa_device_emit_object_info(&monitor->hooks, device->id, NULL); } spa_list_for_each_safe(ep, tep, &device->remote_endpoint_list, device_link) { if (ep->device == device) { spa_list_remove(&ep->device_link); ep->device = NULL; } } spa_list_for_each_safe(t, tt, &device->transport_list, device_link) { if (t->device == device) { spa_list_remove(&t->device_link); t->device = NULL; } } spa_list_consume(sw, &device->codec_switch_list, link) codec_switch_destroy(sw); spa_list_consume(s, &device->set_membership_list, link) { spa_list_remove(&s->link); spa_list_remove(&s->others); free(s->path); free(s); } spa_list_remove(&device->link); free(device->path); free(device->alias); free(device->address); free(device->adapter_path); free(device->battery_path); free(device->name); free(device->icon); free(device); } static struct spa_bt_set_membership *device_set_find(struct spa_bt_monitor *monitor, const char *path) { struct spa_bt_device *d; spa_list_for_each(d, &monitor->device_list, link) { struct spa_bt_set_membership *s; spa_list_for_each(s, &d->set_membership_list, link) { if (spa_streq(s->path, path)) return s; } } return NULL; } static int device_add_device_set(struct spa_bt_device *device, const char *path, uint8_t rank) { struct spa_bt_monitor *monitor = device->monitor; struct spa_bt_set_membership *s, *set; spa_list_for_each(s, &device->set_membership_list, link) { if (spa_streq(s->path, path)) { if (rank) s->rank = rank; return 0; } } s = calloc(1, sizeof(struct spa_bt_set_membership)); if (s == NULL) return -ENOMEM; s->path = strdup(path); if (!s->path) { free(s); return -ENOMEM; } s->device = device; s->rank = rank; spa_list_init(&s->others); /* Join with other set members, if any */ set = device_set_find(monitor, path); if (set) spa_list_append(&set->others, &s->others); spa_list_append(&device->set_membership_list, &s->link); spa_log_debug(monitor->log, "device %p: add %s to device set %s", device, device->path, path); return 1; } static bool device_remove_device_set(struct spa_bt_device *device, const char *path) { struct spa_bt_monitor *monitor = device->monitor; struct spa_bt_set_membership *s; spa_list_for_each(s, &device->set_membership_list, link) { if (spa_streq(s->path, path)) { spa_log_debug(monitor->log, "device %p: remove %s from device set %s", device, device->path, path); spa_list_remove(&s->link); spa_list_remove(&s->others); free(s->path); free(s); return true; } } return false; } int spa_bt_format_vendor_product_id(uint16_t source_id, uint16_t vendor_id, uint16_t product_id, char *vendor_str, int vendor_str_size, char *product_str, int product_str_size) { const char *source_str; switch (source_id) { case SOURCE_ID_USB: source_str = "usb"; break; case SOURCE_ID_BLUETOOTH: source_str = "bluetooth"; break; default: return -EINVAL; } spa_scnprintf(vendor_str, vendor_str_size, "%s:%04x", source_str, (unsigned int)vendor_id); spa_scnprintf(product_str, product_str_size, "%04x", (unsigned int)product_id); return 0; } static void emit_device_info(struct spa_bt_monitor *monitor, struct spa_bt_device *device, bool with_connection) { struct spa_device_object_info info; char dev[32], name[128], class[16], vendor_id[64], product_id[64], product_id_tot[67]; struct spa_dict_item items[24]; uint32_t n_items = 0; enum spa_bt_form_factor ff; info = SPA_DEVICE_OBJECT_INFO_INIT(); info.type = SPA_TYPE_INTERFACE_Device; info.factory_name = SPA_NAME_API_BLUEZ5_DEVICE; info.change_mask = SPA_DEVICE_OBJECT_CHANGE_MASK_FLAGS | SPA_DEVICE_OBJECT_CHANGE_MASK_PROPS; info.flags = 0; ff = spa_bt_form_factor_from_class(device->bluetooth_class); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_API, "bluez5"); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_BUS, "bluetooth"); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_MEDIA_CLASS, "Audio/Device"); snprintf(name, sizeof(name), "bluez_card.%s", device->address); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_NAME, name); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_DESCRIPTION, device->alias); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_ALIAS, device->name); if (spa_bt_format_vendor_product_id( device->source_id, device->vendor_id, device->product_id, vendor_id, sizeof(vendor_id), product_id, sizeof(product_id)) == 0) { snprintf(product_id_tot, sizeof(product_id_tot), "0x%s", product_id); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_VENDOR_ID, vendor_id); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_PRODUCT_ID, product_id_tot); } items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_FORM_FACTOR, spa_bt_form_factor_name(ff)); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_ICON_NAME, spa_bt_form_factor_icon_name(ff)); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_STRING, device->address); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_API_BLUEZ5_ICON, device->icon); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_API_BLUEZ5_PATH, device->path); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_API_BLUEZ5_ADDRESS, device->address); snprintf(dev, sizeof(dev), "pointer:%p", device); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_API_BLUEZ5_DEVICE, dev); snprintf(class, sizeof(class), "0x%06x", device->bluetooth_class); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_API_BLUEZ5_CLASS, class); if (with_connection) { items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_API_BLUEZ5_CONNECTION, device->connected ? "connected": "disconnected"); } info.props = &SPA_DICT_INIT(items, n_items); spa_device_emit_object_info(&monitor->hooks, device->id, &info); } static int device_connected_old(struct spa_bt_monitor *monitor, struct spa_bt_device *device, int connected) { if (connected == BT_DEVICE_INIT) return 0; device->connected = connected; if (device->connected) { emit_device_info(monitor, device, false); device->added = true; } else { if (!device->added) return 0; device_clear_sub(device); spa_device_emit_object_info(&monitor->hooks, device->id, NULL); device->added = false; } return 0; } enum { BT_DEVICE_RECONNECT_INIT = 0, BT_DEVICE_RECONNECT_PROFILE, BT_DEVICE_RECONNECT_STOP }; static int device_connected(struct spa_bt_monitor *monitor, struct spa_bt_device *device, int status) { bool connected, init = (status == BT_DEVICE_INIT); connected = init ? 0 : status; if (!init) { device->reconnect_state = connected ? BT_DEVICE_RECONNECT_STOP : BT_DEVICE_RECONNECT_PROFILE; } if ((device->connected_profiles != 0) ^ connected) { spa_log_error(monitor->log, "device %p: unexpected call, connected_profiles:%08x connected:%d", device, device->connected_profiles, device->connected); return -EINVAL; } if (!monitor->connection_info_supported) return device_connected_old(monitor, device, status); if (init) { device->connected = connected; } else { if (!device->added || !(connected ^ device->connected)) return 0; device->connected = connected; spa_bt_device_emit_connected(device, device->connected); if (!device->connected) device_clear_sub(device); } emit_device_info(monitor, device, true); device->added = true; return 0; } /* * Add profile to device based on bluez actions * (update property UUIDs, trigger profile handlers), * in case UUIDs is empty on signal InterfaceAdded for * org.bluez.Device1. And emit device info if there is * at least 1 profile on device. This should be called * before any device setting accessing. */ int spa_bt_device_add_profile(struct spa_bt_device *device, enum spa_bt_profile profile) { struct spa_bt_monitor *monitor = device->monitor; if (profile && (device->profiles & profile) == 0) { spa_log_info(monitor->log, "device %p: add new profile %08x", device, profile); device->profiles |= profile; } if (!device->added && device->profiles) { device_connected(monitor, device, BT_DEVICE_INIT); if (device->reconnect_state == BT_DEVICE_RECONNECT_INIT) device_start_timer(device); } return 0; } static int device_try_connect_profile(struct spa_bt_device *device, const char *profile_uuid) { struct spa_bt_monitor *monitor = device->monitor; spa_autoptr(DBusMessage) m = NULL; spa_log_info(monitor->log, "device %p %s: profile %s not connected; try ConnectProfile()", device, device->path, profile_uuid); /* Call org.bluez.Device1.ConnectProfile() on device, ignoring result */ m = dbus_message_new_method_call(BLUEZ_SERVICE, device->path, BLUEZ_DEVICE_INTERFACE, "ConnectProfile"); if (m == NULL) return -ENOMEM; dbus_message_append_args(m, DBUS_TYPE_STRING, &profile_uuid, DBUS_TYPE_INVALID); if (!dbus_connection_send(monitor->conn, m, NULL)) return -EIO; return 0; } static int reconnect_device_profiles(struct spa_bt_device *device) { struct spa_bt_monitor *monitor = device->monitor; struct spa_bt_device *d; uint32_t reconnect = device->profiles & device->reconnect_profiles & (device->connected_profiles ^ device->profiles); /* Don't try to connect to same device via multiple adapters */ spa_list_for_each(d, &monitor->device_list, link) { if (d != device && spa_streq(d->address, device->address)) { if (d->paired && d->trusted && !d->blocked && d->reconnect_state == BT_DEVICE_RECONNECT_STOP) reconnect &= ~d->reconnect_profiles; if (d->connected_profiles) reconnect = 0; } } /* Connect only profiles the adapter has a counterpart for */ if (device->adapter) reconnect &= adapter_connectable_profiles(device->adapter); if (!(device->connected_profiles & SPA_BT_PROFILE_HEADSET_HEAD_UNIT)) { if (reconnect & SPA_BT_PROFILE_HFP_HF) { SPA_FLAG_CLEAR(reconnect, SPA_BT_PROFILE_HSP_HS); } else if (reconnect & SPA_BT_PROFILE_HSP_HS) { SPA_FLAG_CLEAR(reconnect, SPA_BT_PROFILE_HFP_HF); } } else SPA_FLAG_CLEAR(reconnect, SPA_BT_PROFILE_HEADSET_HEAD_UNIT); if (!(device->connected_profiles & SPA_BT_PROFILE_HEADSET_AUDIO_GATEWAY)) { if (reconnect & SPA_BT_PROFILE_HFP_AG) SPA_FLAG_CLEAR(reconnect, SPA_BT_PROFILE_HSP_AG); else if (reconnect & SPA_BT_PROFILE_HSP_AG) SPA_FLAG_CLEAR(reconnect, SPA_BT_PROFILE_HFP_AG); } else SPA_FLAG_CLEAR(reconnect, SPA_BT_PROFILE_HEADSET_AUDIO_GATEWAY); if (reconnect & SPA_BT_PROFILE_HFP_HF) device_try_connect_profile(device, SPA_BT_UUID_HFP_HF); if (reconnect & SPA_BT_PROFILE_HSP_HS) device_try_connect_profile(device, SPA_BT_UUID_HSP_HS); if (reconnect & SPA_BT_PROFILE_HFP_AG) device_try_connect_profile(device, SPA_BT_UUID_HFP_AG); if (reconnect & SPA_BT_PROFILE_HSP_AG) device_try_connect_profile(device, SPA_BT_UUID_HSP_AG); if (reconnect & SPA_BT_PROFILE_A2DP_SINK) device_try_connect_profile(device, SPA_BT_UUID_A2DP_SINK); if (reconnect & SPA_BT_PROFILE_A2DP_SOURCE) device_try_connect_profile(device, SPA_BT_UUID_A2DP_SOURCE); if (reconnect & SPA_BT_PROFILE_BAP_SINK) device_try_connect_profile(device, SPA_BT_UUID_BAP_SINK); if (reconnect & SPA_BT_PROFILE_BAP_SOURCE) device_try_connect_profile(device, SPA_BT_UUID_BAP_SOURCE); if (reconnect & SPA_BT_PROFILE_BAP_BROADCAST_SINK) device_try_connect_profile(device, SPA_BT_UUID_BAP_BROADCAST_SINK); if (reconnect & SPA_BT_PROFILE_BAP_BROADCAST_SOURCE) device_try_connect_profile(device, SPA_BT_UUID_BAP_BROADCAST_SOURCE); return reconnect; } #define DEVICE_RECONNECT_TIMEOUT_SEC 2 #define DEVICE_PROFILE_TIMEOUT_SEC 6 static void device_timer_event(struct spa_source *source) { struct spa_bt_device *device = source->data; struct spa_bt_monitor *monitor = device->monitor; uint64_t exp; if (spa_system_timerfd_read(monitor->main_system, source->fd, &exp) < 0) spa_log_warn(monitor->log, "error reading timerfd: %s", strerror(errno)); spa_log_debug(monitor->log, "device %p: timeout %08x %08x", device, device->profiles, device->connected_profiles); device_stop_timer(device); if (BT_DEVICE_RECONNECT_STOP != device->reconnect_state) { device->reconnect_state = BT_DEVICE_RECONNECT_STOP; if (device->paired && device->trusted && !device->blocked && device->reconnect_profiles != 0 && reconnect_device_profiles(device)) { device_start_timer(device); return; } } if (device->connected_profiles) device_connected(device->monitor, device, BT_DEVICE_CONNECTED); } static int device_start_timer(struct spa_bt_device *device) { struct spa_bt_monitor *monitor = device->monitor; struct itimerspec ts; spa_log_debug(monitor->log, "device %p: start timer", device); if (device->timer.data == NULL) { device->timer.data = device; device->timer.func = device_timer_event; device->timer.fd = spa_system_timerfd_create(monitor->main_system, CLOCK_MONOTONIC, SPA_FD_CLOEXEC | SPA_FD_NONBLOCK); device->timer.mask = SPA_IO_IN; device->timer.rmask = 0; spa_loop_add_source(monitor->main_loop, &device->timer); } ts.it_value.tv_sec = device->reconnect_state == BT_DEVICE_RECONNECT_STOP ? DEVICE_PROFILE_TIMEOUT_SEC : DEVICE_RECONNECT_TIMEOUT_SEC; ts.it_value.tv_nsec = 0; ts.it_interval.tv_sec = 0; ts.it_interval.tv_nsec = 0; spa_system_timerfd_settime(monitor->main_system, device->timer.fd, 0, &ts, NULL); return 0; } static int device_stop_timer(struct spa_bt_device *device) { struct spa_bt_monitor *monitor = device->monitor; struct itimerspec ts; if (device->timer.data == NULL) return 0; spa_log_debug(monitor->log, "device %p: stop timer", device); spa_loop_remove_source(monitor->main_loop, &device->timer); ts.it_value.tv_sec = 0; ts.it_value.tv_nsec = 0; ts.it_interval.tv_sec = 0; ts.it_interval.tv_nsec = 0; spa_system_timerfd_settime(monitor->main_system, device->timer.fd, 0, &ts, NULL); spa_system_close(monitor->main_system, device->timer.fd); device->timer.data = NULL; return 0; } static bool has_codec_switch(struct spa_bt_device *device) { return !spa_list_is_empty(&device->codec_switch_list); } int spa_bt_device_check_profiles(struct spa_bt_device *device, bool force) { struct spa_bt_monitor *monitor = device->monitor; struct spa_bt_set_membership *s, *set; uint32_t connected_profiles = device->connected_profiles; uint32_t connectable_profiles = device->adapter ? adapter_connectable_profiles(device->adapter) : 0; uint32_t direction_masks[4] = { SPA_BT_PROFILE_MEDIA_SINK | SPA_BT_PROFILE_HEADSET_HEAD_UNIT, SPA_BT_PROFILE_MEDIA_SOURCE, SPA_BT_PROFILE_HEADSET_AUDIO_GATEWAY, SPA_BT_PROFILE_ASHA_SINK, }; bool direction_connected = false; bool set_connected = true; bool all_connected; size_t i; if (connected_profiles & SPA_BT_PROFILE_HEADSET_HEAD_UNIT) connected_profiles |= SPA_BT_PROFILE_HEADSET_HEAD_UNIT; if (connected_profiles & SPA_BT_PROFILE_HEADSET_AUDIO_GATEWAY) connected_profiles |= SPA_BT_PROFILE_HEADSET_AUDIO_GATEWAY; for (i = 0; i < SPA_N_ELEMENTS(direction_masks); ++i) { uint32_t mask = direction_masks[i] & device->profiles & connectable_profiles; if (mask && (connected_profiles & mask) == mask) direction_connected = true; } all_connected = ((device->profiles & connected_profiles & connectable_profiles) == (device->profiles & connectable_profiles)); spa_list_for_each(set, &device->set_membership_list, link) spa_bt_for_each_set_member(s, set) if ((s->device->connected_profiles & s->device->profiles) != s->device->profiles) set_connected = false; spa_log_debug(monitor->log, "device %p: profiles %08x %08x connectable:%08x added:%d all:%d dir:%d set:%d", device, device->profiles, connected_profiles, connectable_profiles, device->added, all_connected, direction_connected, set_connected); if (has_codec_switch(device)) { /* noop */ } else if (connected_profiles == 0) { device_stop_timer(device); device_connected(monitor, device, BT_DEVICE_DISCONNECTED); } else if (force || ((direction_connected || all_connected) && set_connected && connected_profiles)) { device_stop_timer(device); device_connected(monitor, device, BT_DEVICE_CONNECTED); } else { /* The initial reconnect event has not been triggered, * the connecting is triggered by bluez. */ if (device->reconnect_state == BT_DEVICE_RECONNECT_INIT) device->reconnect_state = BT_DEVICE_RECONNECT_PROFILE; device_start_timer(device); } return 0; } static void device_set_connected(struct spa_bt_device *device, int connected) { struct spa_bt_monitor *monitor = device->monitor; if (device->connected && !connected) device->connected_profiles = 0; if (connected) { spa_bt_quirks_log_features(monitor->quirks, device->adapter, device); spa_bt_device_check_profiles(device, false); } else { /* Stop works on disconnect */ struct spa_bt_codec_switch *sw; spa_list_consume(sw, &device->codec_switch_list, link) codec_switch_destroy(sw); if (device->reconnect_state != BT_DEVICE_RECONNECT_INIT) device_stop_timer(device); device_connected(monitor, device, BT_DEVICE_DISCONNECTED); } } static void device_update_set_status(struct spa_bt_device *device, bool force, const char *path); int spa_bt_device_connect_profile(struct spa_bt_device *device, enum spa_bt_profile profile) { device->connected_profiles |= profile; if (profile & SPA_BT_PROFILE_BAP_DUPLEX || profile & SPA_BT_PROFILE_ASHA_SINK) device_update_set_status(device, true, NULL); spa_bt_device_check_profiles(device, false); spa_bt_device_emit_profiles_changed(device, profile); return 0; } static void device_update_hw_volume_profiles(struct spa_bt_device *device) { struct spa_bt_monitor *monitor = device->monitor; uint32_t bt_features = 0; if (!monitor->quirks) return; if (spa_bt_quirks_get_features(monitor->quirks, device->adapter, device, &bt_features) != 0) return; if (!(bt_features & SPA_BT_FEATURE_HW_VOLUME)) device->hw_volume_profiles = 0; spa_log_debug(monitor->log, "hw-volume-profiles:%08x", (int)device->hw_volume_profiles); } static bool device_set_update_leader(struct spa_bt_set_membership *set) { struct spa_bt_set_membership *s, *leader = NULL; /* Make minimum rank device the leader, so that device set nodes always * appear under a specific device. */ spa_bt_for_each_set_member(s, set) { bool bap_duplex = s->device->connected_profiles & SPA_BT_PROFILE_BAP_DUPLEX; bool is_asha = s->device->connected_profiles & SPA_BT_PROFILE_ASHA_SINK; if (!bap_duplex && !is_asha) continue; if (leader == NULL || s->rank < leader->rank || (s->rank == leader->rank && s->leader)) leader = s; } if (leader == NULL || (leader && leader->leader)) return false; spa_bt_for_each_set_member(s, set) s->leader = false; leader->leader = true; spa_log_debug(leader->device->monitor->log, "device set %p %s: leader is %s", set, leader->path, leader->device->path); return true; } static void device_update_set_status(struct spa_bt_device *device, bool force, const char *path) { struct spa_bt_set_membership *s, *set; spa_list_for_each(set, &device->set_membership_list, link) { if (path && !spa_streq(set->path, path)) continue; if (device_set_update_leader(set) || force) { spa_bt_for_each_set_member(s, set) if (!s->leader) spa_bt_device_emit_device_set_changed(s->device); spa_bt_for_each_set_member(s, set) if (s->leader) spa_bt_device_emit_device_set_changed(s->device); } } } static int device_set_update_props(struct spa_bt_monitor *monitor, const char *path, DBusMessageIter *props_iter, DBusMessageIter *invalidated_iter) { struct spa_bt_device *old[256]; struct spa_bt_device *new[256]; struct spa_bt_set_membership *set; size_t num_old = 0, num_new = 0; size_t i; if (!props_iter) goto done; /* Find current devices */ while (dbus_message_iter_get_arg_type(props_iter) != DBUS_TYPE_INVALID) { DBusMessageIter it[2]; const char *key; dbus_message_iter_recurse(props_iter, &it[0]); dbus_message_iter_get_basic(&it[0], &key); dbus_message_iter_next(&it[0]); dbus_message_iter_recurse(&it[0], &it[1]); if (spa_streq(key, "Devices")) { DBusMessageIter iter; int i = 0; if (!check_iter_signature(&it[1], "ao")) goto next; dbus_message_iter_recurse(&it[1], &iter); while (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_INVALID) { struct spa_bt_device *d; const char *dev_path; dbus_message_iter_get_basic(&iter, &dev_path); spa_log_debug(monitor->log, "device set %s: Devices[%d]=%s", path, i++, dev_path); if (num_new >= SPA_N_ELEMENTS(new)) break; d = spa_bt_device_find(monitor, dev_path); if (d) new[num_new++] = d; dbus_message_iter_next(&iter); } } else spa_log_debug(monitor->log, "device set %s: unhandled key %s", path, key); next: dbus_message_iter_next(props_iter); } done: /* Find devices to remove */ set = device_set_find(monitor, path); if (set) { struct spa_bt_set_membership *s; spa_bt_for_each_set_member(s, set) { for (i = 0; i < num_new; ++i) if (s->device == new[i]) break; if (i == num_new) { if (num_old >= SPA_N_ELEMENTS(old)) break; old[num_old++] = s->device; } } } /* Remove old devices */ for (i = 0; i < num_old; ++i) device_remove_device_set(old[i], path); /* Add new devices */ for (i = 0; i < num_new; ++i) device_add_device_set(new[i], path, 0); /* Emit signals & update set leader */ for (i = 0; i < num_old; ++i) spa_bt_device_emit_device_set_changed(old[i]); if (num_new > 0) device_update_set_status(new[0], true, path); return 0; } static int device_update_device_sets_prop(struct spa_bt_device *device, DBusMessageIter *iter) { struct spa_bt_monitor *monitor = device->monitor; DBusMessageIter it[5]; bool changed = false; if (!check_iter_signature(iter, "a{oa{sv}}")) return -EINVAL; dbus_message_iter_recurse(iter, &it[0]); while (dbus_message_iter_get_arg_type(&it[0]) != DBUS_TYPE_INVALID) { uint8_t rank = 0; const char *set_path; dbus_message_iter_recurse(&it[0], &it[1]); dbus_message_iter_get_basic(&it[1], &set_path); dbus_message_iter_next(&it[1]); dbus_message_iter_recurse(&it[1], &it[2]); while (dbus_message_iter_get_arg_type(&it[2]) != DBUS_TYPE_INVALID) { const char *key; int type; dbus_message_iter_recurse(&it[2], &it[3]); dbus_message_iter_get_basic(&it[3], &key); dbus_message_iter_next(&it[3]); dbus_message_iter_recurse(&it[3], &it[4]); type = dbus_message_iter_get_arg_type(&it[4]); if (spa_streq(key, "Rank") && type == DBUS_TYPE_BYTE) dbus_message_iter_get_basic(&it[4], &rank); dbus_message_iter_next(&it[2]); } spa_log_debug(monitor->log, "device %p: path %s device set %s rank %d", device, device->path, set_path, (int)rank); /* Only add. Removals are handled in device set updates. */ if (device_add_device_set(device, set_path, rank) == 1) changed = true; dbus_message_iter_next(&it[0]); } /* Emit change signals */ device_update_set_status(device, changed, NULL); return 0; } static int device_update_props(struct spa_bt_device *device, DBusMessageIter *props_iter, DBusMessageIter *invalidated_iter) { struct spa_bt_monitor *monitor = device->monitor; while (dbus_message_iter_get_arg_type(props_iter) != DBUS_TYPE_INVALID) { DBusMessageIter it[2]; const char *key; int type; dbus_message_iter_recurse(props_iter, &it[0]); dbus_message_iter_get_basic(&it[0], &key); dbus_message_iter_next(&it[0]); dbus_message_iter_recurse(&it[0], &it[1]); type = dbus_message_iter_get_arg_type(&it[1]); if (type == DBUS_TYPE_STRING || type == DBUS_TYPE_OBJECT_PATH) { const char *value; dbus_message_iter_get_basic(&it[1], &value); spa_log_debug(monitor->log, "device %p: %s=%s", device, key, value); if (spa_streq(key, "Alias")) { free(device->alias); device->alias = strdup(value); } else if (spa_streq(key, "Name")) { free(device->name); device->name = strdup(value); } else if (spa_streq(key, "Address")) { free(device->address); device->address = strdup(value); } else if (spa_streq(key, "Adapter")) { free(device->adapter_path); device->adapter_path = strdup(value); device->adapter = adapter_find(monitor, value); if (device->adapter == NULL) { spa_log_info(monitor->log, "unknown adapter %s", value); } } else if (spa_streq(key, "Icon")) { free(device->icon); device->icon = strdup(value); } else if (spa_streq(key, "Modalias")) { int ret; ret = parse_modalias(value, &device->source_id, &device->vendor_id, &device->product_id, &device->version_id); if (ret < 0) spa_log_debug(monitor->log, "device %p: %s=%s ignored: %s", device, key, value, spa_strerror(ret)); } } else if (type == DBUS_TYPE_UINT32) { uint32_t value; dbus_message_iter_get_basic(&it[1], &value); spa_log_debug(monitor->log, "device %p: %s=%08x", device, key, value); if (spa_streq(key, "Class")) device->bluetooth_class = value; } else if (type == DBUS_TYPE_UINT16) { uint16_t value; dbus_message_iter_get_basic(&it[1], &value); spa_log_debug(monitor->log, "device %p: %s=%d", device, key, value); if (spa_streq(key, "Appearance")) device->appearance = value; } else if (type == DBUS_TYPE_INT16) { int16_t value; dbus_message_iter_get_basic(&it[1], &value); spa_log_debug(monitor->log, "device %p: %s=%d", device, key, value); if (spa_streq(key, "RSSI")) device->RSSI = value; } else if (type == DBUS_TYPE_BOOLEAN) { int value; dbus_message_iter_get_basic(&it[1], &value); spa_log_debug(monitor->log, "device %p: %s=%d", device, key, value); if (spa_streq(key, "Paired")) { device->paired = value; } else if (spa_streq(key, "Trusted")) { device->trusted = value; } else if (spa_streq(key, "Connected")) { device_set_connected(device, value); } else if (spa_streq(key, "Blocked")) { device->blocked = value; } else if (spa_streq(key, "ServicesResolved")) { if (value) spa_bt_device_check_profiles(device, false); } } else if (spa_streq(key, "UUIDs")) { DBusMessageIter iter; uint32_t prev_profiles = device->profiles; if (!check_iter_signature(&it[1], "as")) goto next; dbus_message_iter_recurse(&it[1], &iter); while (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_INVALID) { const char *uuid; enum spa_bt_profile profile; dbus_message_iter_get_basic(&iter, &uuid); profile = spa_bt_profile_from_uuid(uuid); if (profile && (device->profiles & profile) == 0) { spa_log_debug(monitor->log, "device %p: add UUID=%s", device, uuid); device->profiles |= profile; } dbus_message_iter_next(&iter); } if (device->profiles != prev_profiles) spa_bt_device_emit_profiles_changed(device, 0); } else if (spa_streq(key, "Sets")) { device_update_device_sets_prop(device, &it[1]); } else spa_log_debug(monitor->log, "device %p: unhandled key %s type %d", device, key, type); next: dbus_message_iter_next(props_iter); } return 0; } static bool device_props_ready(struct spa_bt_device *device) { /* * In some cases, BlueZ device props may be missing part of * the information required when the interface first appears. */ return device->adapter && device->address; } bool spa_bt_device_supports_media_codec(struct spa_bt_device *device, const struct media_codec *codec, enum spa_bt_profile profile) { struct spa_bt_monitor *monitor = device->monitor; struct spa_bt_remote_endpoint *ep; enum spa_bt_profile codec_target_profile; struct spa_bt_transport *t; const struct { enum spa_bluetooth_audio_codec codec; uint32_t mask; } quirks[] = { { SPA_BLUETOOTH_AUDIO_CODEC_SBC_XQ, SPA_BT_FEATURE_SBC_XQ }, { SPA_BLUETOOTH_AUDIO_CODEC_FASTSTREAM, SPA_BT_FEATURE_FASTSTREAM }, { SPA_BLUETOOTH_AUDIO_CODEC_FASTSTREAM_DUPLEX, SPA_BT_FEATURE_FASTSTREAM }, { SPA_BLUETOOTH_AUDIO_CODEC_APTX_LL_DUPLEX, SPA_BT_FEATURE_A2DP_DUPLEX }, { SPA_BLUETOOTH_AUDIO_CODEC_FASTSTREAM_DUPLEX, SPA_BT_FEATURE_A2DP_DUPLEX }, }; bool is_a2dp = codec->kind == MEDIA_CODEC_A2DP; size_t i; codec_target_profile = get_codec_target_profile(monitor, codec); if (!codec_target_profile) return false; if (codec->kind == MEDIA_CODEC_HFP) { if (!(profile & SPA_BT_PROFILE_HEADSET_AUDIO)) return false; return spa_bt_backend_supports_codec(monitor->backend, device, codec->codec_id) == 1; } if (!device->adapter->a2dp_application_registered && is_a2dp) { /* Codec switching not supported: only plain SBC allowed */ return (codec->codec_id == A2DP_CODEC_SBC && spa_streq(codec->name, "sbc") && device->adapter->legacy_endpoints_registered); } if (!device->adapter->bap_application_registered && codec->kind == MEDIA_CODEC_BAP) return false; /* Check codec quirks */ for (i = 0; i < SPA_N_ELEMENTS(quirks); ++i) { uint32_t bt_features; if (codec->id != quirks[i].codec) continue; if (monitor->quirks == NULL) break; if (spa_bt_quirks_get_features(monitor->quirks, device->adapter, device, &bt_features) < 0) break; if (!(bt_features & quirks[i].mask)) return false; } spa_list_for_each(ep, &device->remote_endpoint_list, device_link) { enum spa_bt_profile ep_profile = spa_bt_profile_from_uuid(ep->uuid); if (!(ep_profile & codec_target_profile & profile)) continue; if (media_codec_check_caps(codec, ep->codec, ep->capabilities, ep->capabilities_len, &ep->monitor->default_audio_info, &monitor->global_settings)) return true; } /* Codecs on configured transports are always supported. * * Remote BAP endpoints correspond to capabilities of the remote * BAP Server, not to remote BAP Client, and need not be the same. * BAP Clients may not have any remote endpoints. In this case we * can only know that the currently configured codec is supported. */ spa_list_for_each(t, &device->transport_list, device_link) { if (!(t->profile & codec_target_profile & profile)) continue; if (codec == t->media_codec) return true; } return false; } const struct media_codec **spa_bt_device_get_supported_media_codecs(struct spa_bt_device *device, size_t *count) { struct spa_bt_monitor *monitor = device->monitor; const struct media_codec * const * const media_codecs = monitor->media_codecs; spa_autofree const struct media_codec **supported_codecs = NULL; size_t i, j, size; *count = 0; size = 8; supported_codecs = malloc(size * sizeof(const struct media_codec *)); if (supported_codecs == NULL) return NULL; j = 0; for (i = 0; media_codecs[i] != NULL; ++i) { if (spa_bt_device_supports_media_codec(device, media_codecs[i], device->connected_profiles)) { supported_codecs[j] = media_codecs[i]; ++j; } if (j >= size) { const struct media_codec **p; size = size * 2; #ifdef HAVE_REALLOCARRAY p = reallocarray(supported_codecs, size, sizeof(const struct media_codec *)); #else p = realloc(supported_codecs, size * sizeof(const struct media_codec *)); #endif if (p == NULL) return NULL; supported_codecs = p; } } supported_codecs[j] = NULL; *count = j; return spa_steal_ptr(supported_codecs); } const struct media_codec *spa_bt_get_hfp_codec(struct spa_bt_monitor *monitor, unsigned int hfp_codec_id) { const struct media_codec * const * const media_codecs = monitor->media_codecs; size_t i; for (i = 0; media_codecs[i] != NULL; ++i) { const struct media_codec *codec = media_codecs[i]; if (codec->kind != MEDIA_CODEC_HFP) continue; if (!is_media_codec_enabled(monitor, codec)) continue; if (codec->codec_id == hfp_codec_id) return codec; } return NULL; } static struct spa_bt_remote_endpoint *device_remote_endpoint_find(struct spa_bt_device *device, const char *path) { struct spa_bt_remote_endpoint *ep; spa_list_for_each(ep, &device->remote_endpoint_list, device_link) if (spa_streq(ep->path, path)) return ep; return NULL; } static struct spa_bt_remote_endpoint *remote_endpoint_find(struct spa_bt_monitor *monitor, const char *path) { struct spa_bt_remote_endpoint *ep; spa_list_for_each(ep, &monitor->remote_endpoint_list, link) if (spa_streq(ep->path, path)) return ep; return NULL; } static struct spa_bt_device *create_bcast_device(struct spa_bt_monitor *monitor, const char *adapter_path, const char *transport_path, const char *address) { struct spa_bt_device *d; struct spa_bt_adapter *adapter; adapter = adapter_find(monitor, adapter_path); if (adapter == NULL) { spa_log_warn(monitor->log, "unknown adapter %s", adapter_path); return NULL; } d = device_create(monitor, transport_path); if (d == NULL) { spa_log_warn(monitor->log, "can't create Bluetooth device %s: %m", transport_path); return NULL; } d->adapter = adapter; d->adapter_path = strdup(adapter->path); d->address = spa_aprintf("%s.%d", address, d->id); d->alias = strdup(d->address); d->name = strdup(d->address); d->reconnect_state = BT_DEVICE_RECONNECT_STOP; device_update_hw_volume_profiles(d); spa_bt_device_add_profile(d, SPA_BT_PROFILE_NULL); return d; } static int setup_asha_transport(struct spa_bt_remote_endpoint *remote_endpoint, struct spa_bt_monitor *monitor); static int remote_endpoint_update_props(struct spa_bt_remote_endpoint *remote_endpoint, DBusMessageIter *props_iter, DBusMessageIter *invalidated_iter) { struct spa_bt_monitor *monitor = remote_endpoint->monitor; DBusMessageIter copy_iter = *props_iter; parse_endpoint_props(monitor, ©_iter, &remote_endpoint->capabilities, &remote_endpoint->capabilities_len, &remote_endpoint->metadata, &remote_endpoint->metadata_len, NULL, &remote_endpoint->qos); while (dbus_message_iter_get_arg_type(props_iter) != DBUS_TYPE_INVALID) { DBusMessageIter it[2]; const char *key; int type; dbus_message_iter_recurse(props_iter, &it[0]); dbus_message_iter_get_basic(&it[0], &key); dbus_message_iter_next(&it[0]); dbus_message_iter_recurse(&it[0], &it[1]); type = dbus_message_iter_get_arg_type(&it[1]); if (spa_streq(key, "Capabilities") || spa_streq(key, "Metadata") || spa_streq(key, "Locations") || spa_streq(key, "QoS") || spa_streq(key, "Context") || spa_streq(key, "SupportedContext")) { /* parsed by parse_endpoint_props */ } else if (type == DBUS_TYPE_STRING || type == DBUS_TYPE_OBJECT_PATH) { const char *value; dbus_message_iter_get_basic(&it[1], &value); spa_log_debug(monitor->log, "remote_endpoint %p: %s=%s", remote_endpoint, key, value); if (spa_streq(key, "UUID")) { free(remote_endpoint->uuid); remote_endpoint->uuid = strdup(value); if(spa_streq(remote_endpoint->uuid, SPA_BT_UUID_BAP_BROADCAST_SINK)) /* Set remote endpoint as an acceptor for a broadcast sink. * So the transport is an initiator. */ remote_endpoint->acceptor = true; } else if (spa_streq(key, "Device")) { struct spa_bt_device *device; device = spa_bt_device_find(monitor, value); if (device == NULL) goto next; spa_log_debug(monitor->log, "remote_endpoint %p: device -> %p", remote_endpoint, device); if (remote_endpoint->device != device) { if (remote_endpoint->device != NULL) spa_list_remove(&remote_endpoint->device_link); remote_endpoint->device = device; if (device != NULL) spa_list_append(&device->remote_endpoint_list, &remote_endpoint->device_link); } } else if (spa_streq(key, "Transport")) { /* For ASHA */ free(remote_endpoint->transport_path); remote_endpoint->transport_path = strdup(value); } else if (spa_streq(key, "Side")) { if (spa_streq(value, "right")) remote_endpoint->asha_right_side = true; else remote_endpoint->asha_right_side = false; } else { goto unhandled; } } else if (type == DBUS_TYPE_BOOLEAN) { int value; dbus_message_iter_get_basic(&it[1], &value); spa_log_debug(monitor->log, "remote_endpoint %p: %s=%d", remote_endpoint, key, value); if (spa_streq(key, "DelayReporting")) { remote_endpoint->delay_reporting = value; } else { goto unhandled; } } else if (type == DBUS_TYPE_BYTE) { uint8_t value; dbus_message_iter_get_basic(&it[1], &value); spa_log_debug(monitor->log, "remote_endpoint %p: %s=%02x", remote_endpoint, key, value); if (spa_streq(key, "Codec")) { remote_endpoint->codec = value; } else { goto unhandled; } } else if (type == DBUS_TYPE_UINT16) { /* Codecs property is present for ASHA */ uint16_t value; dbus_message_iter_get_basic(&it[1], &value); if (spa_streq(key, "Codecs")) { spa_log_debug(monitor->log, "remote_endpoint %p: %s=%02x", remote_endpoint, key, value); } else { goto unhandled; } } /* * HiSyncId property is present for ASHA. An ASHA "left" and * "right" device pair will always have the same "HiSyncId". */ else if (spa_streq(key, "HiSyncId")) { DBusMessageIter iter; uint8_t *value; int len; if (!check_iter_signature(&it[1], "ay")) goto next; dbus_message_iter_recurse(&it[1], &iter); dbus_message_iter_get_fixed_array(&iter, &value, &len); if (len != 8 /* HiSyncId will always be 8 bytes */) goto next; remote_endpoint->hisyncid = *(uint64_t *)value; spa_log_debug(monitor->log, "remote_endpoint %p: %s=%"PRIu64, remote_endpoint, key, remote_endpoint->hisyncid); } else { unhandled: spa_log_debug(monitor->log, "remote_endpoint %p: unhandled key %s", remote_endpoint, key); } next: dbus_message_iter_next(props_iter); } /* BAP profile UUIDs do not appear in device UUID list. * Instead, we detect these capabilities based on available * endpoints (i.e. PACs). */ if (remote_endpoint->uuid && remote_endpoint->device) { enum spa_bt_profile profile; profile = spa_bt_profile_from_uuid(remote_endpoint->uuid); if (profile & SPA_BT_PROFILE_BAP_AUDIO) spa_bt_device_add_profile(remote_endpoint->device, profile); if (spa_streq(remote_endpoint->uuid, SPA_BT_UUID_ASHA_SINK)) { if (profile & SPA_BT_PROFILE_ASHA_SINK) setup_asha_transport(remote_endpoint, monitor); } } return 0; } static struct spa_bt_remote_endpoint *remote_endpoint_create(struct spa_bt_monitor *monitor, const char *path) { struct spa_bt_remote_endpoint *ep; ep = calloc(1, sizeof(struct spa_bt_remote_endpoint)); if (ep == NULL) return NULL; ep->monitor = monitor; ep->path = strdup(path); spa_list_prepend(&monitor->remote_endpoint_list, &ep->link); return ep; } static void remote_endpoint_free(struct spa_bt_remote_endpoint *remote_endpoint) { struct spa_bt_monitor *monitor = remote_endpoint->monitor; spa_log_debug(monitor->log, "remote endpoint %p: free %s", remote_endpoint, remote_endpoint->path); if (remote_endpoint->device) spa_list_remove(&remote_endpoint->device_link); spa_list_remove(&remote_endpoint->link); free(remote_endpoint->path); free(remote_endpoint->transport_path); free(remote_endpoint->uuid); free(remote_endpoint->capabilities); free(remote_endpoint->metadata); free(remote_endpoint); } struct spa_bt_transport *spa_bt_transport_find(struct spa_bt_monitor *monitor, const char *path) { struct spa_bt_transport *t; spa_list_for_each(t, &monitor->transport_list, link) if (spa_streq(t->path, path)) return t; return NULL; } struct spa_bt_transport *spa_bt_transport_find_full(struct spa_bt_monitor *monitor, bool (*callback) (struct spa_bt_transport *t, const void *data), const void *data) { struct spa_bt_transport *t; spa_list_for_each(t, &monitor->transport_list, link) if (callback(t, data) == true) return t; return NULL; } struct spa_bt_transport *spa_bt_transport_create(struct spa_bt_monitor *monitor, char *path, size_t extra) { struct spa_bt_transport *t; t = calloc(1, sizeof(struct spa_bt_transport) + extra); if (t == NULL) return NULL; t->acquire_refcount = 0; t->monitor = monitor; t->path = path; t->fd = -1; t->sco_io = NULL; t->delay_us = SPA_BT_UNKNOWN_DELAY; t->latency_us = SPA_BT_UNKNOWN_DELAY; t->bap_cig = 0xff; t->bap_cis = 0xff; t->bap_big = 0xff; t->bap_bis = 0xff; t->user_data = SPA_PTROFF(t, sizeof(struct spa_bt_transport), void); spa_hook_list_init(&t->listener_list); spa_list_init(&t->bap_transport_linked); spa_list_append(&monitor->transport_list, &t->link); return t; } bool spa_bt_transport_volume_enabled(struct spa_bt_transport *transport) { return transport->device != NULL && (transport->device->hw_volume_profiles & transport->profile); } static void transport_sync_volume(struct spa_bt_transport *transport) { if (!spa_bt_transport_volume_enabled(transport)) return; for (int i = 0; i < SPA_BT_VOLUME_ID_TERM; ++i) spa_bt_transport_set_volume(transport, i, transport->volumes[i].volume); spa_bt_transport_emit_volume_changed(transport); } void spa_bt_transport_set_state(struct spa_bt_transport *transport, enum spa_bt_transport_state state) { struct spa_bt_monitor *monitor = transport->monitor; enum spa_bt_transport_state old = transport->state; if (old != state) { transport->state = state; spa_log_debug(monitor->log, "transport %p: %s state changed %d -> %d", transport, transport->path, old, state); spa_bt_transport_emit_state_changed(transport, old, state); if (state >= SPA_BT_TRANSPORT_STATE_PENDING && old < SPA_BT_TRANSPORT_STATE_PENDING) transport_sync_volume(transport); if (state < SPA_BT_TRANSPORT_STATE_ACTIVE) { /* If transport becomes inactive, do any pending releases * immediately, since the fd is not usable any more. */ spa_bt_transport_commit_release_timer(transport); } if (state == SPA_BT_TRANSPORT_STATE_ERROR) { uint64_t now = get_time_now(monitor); if (now > transport->last_error_time + TRANSPORT_ERROR_TIMEOUT) { spa_log_error(monitor->log, "Failure in Bluetooth audio transport %s", transport->path); } transport->last_error_time = now; ++transport->error_count; } } } void spa_bt_transport_free(struct spa_bt_transport *transport) { struct spa_bt_monitor *monitor = transport->monitor; struct spa_bt_device *device = transport->device; char hisyncid[32] = { 0 }; spa_log_debug(monitor->log, "transport %p: free %s", transport, transport->path); spa_bt_transport_set_state(transport, SPA_BT_TRANSPORT_STATE_IDLE); spa_bt_transport_keepalive(transport, false); spa_bt_transport_emit_destroy(transport); spa_bt_transport_stop_volume_timer(transport); spa_bt_transport_stop_release_timer(transport); if (transport->sco_io) { spa_bt_sco_io_destroy(transport->sco_io); transport->sco_io = NULL; } if (transport->iso_io) spa_bt_iso_io_destroy(transport->iso_io); spa_bt_transport_destroy(transport); cancel_and_unref(&transport->acquire_call); cancel_and_unref(&transport->volume_call); if (transport->fd >= 0) { if (device) spa_bt_player_set_state(device->adapter->dummy_player, SPA_BT_PLAYER_STOPPED); shutdown(transport->fd, SHUT_RDWR); close(transport->fd); transport->fd = -1; } spa_list_remove(&transport->link); if (device) { struct spa_bt_transport *t; uint32_t disconnected = transport->profile; spa_list_remove(&transport->device_link); spa_list_for_each(t, &device->transport_list, device_link) disconnected &= ~t->profile; device->connected_profiles &= ~disconnected; if (transport->profile & SPA_BT_PROFILE_BAP_DUPLEX) device_update_set_status(device, true, NULL); if (transport->profile & SPA_BT_PROFILE_ASHA_SINK) { spa_scnprintf(hisyncid, sizeof(hisyncid), "/asha/%" PRIu64, transport->hisyncid); device_update_set_status(device, true, hisyncid); device_remove_device_set(device, hisyncid); } spa_bt_device_emit_profiles_changed(device, transport->profile); } spa_list_remove(&transport->bap_transport_linked); free(transport->configuration); free(transport->endpoint_path); free(transport->remote_endpoint_path); free(transport->path); free(transport); } int spa_bt_transport_keepalive(struct spa_bt_transport *t, bool keepalive) { if (keepalive) { t->keepalive = true; return 0; } t->keepalive = false; if (t->acquire_refcount == 0 && t->acquired) { t->acquire_refcount = 1; return spa_bt_transport_release(t); } return 0; } int spa_bt_transport_acquire(struct spa_bt_transport *transport, bool optional) { struct spa_bt_monitor *monitor = transport->monitor; int res; if (transport->acquire_refcount > 0) { spa_log_debug(monitor->log, "transport %p: incref %s", transport, transport->path); transport->acquire_refcount += 1; spa_bt_transport_emit_state_changed(transport, transport->state, transport->state); return 0; } spa_assert(transport->acquire_refcount == 0); /* If we are getting into error state too often, stop trying */ if (get_time_now(monitor) > transport->last_error_time + TRANSPORT_ERROR_TIMEOUT) transport->error_count = 0; if (transport->error_count >= TRANSPORT_ERROR_MAX_RETRY) return -EIO; if (!transport->acquired) res = spa_bt_transport_impl(transport, acquire, 0, optional); else res = 0; if (res >= 0) { transport->acquire_refcount = 1; transport->acquired = true; } return res; } static void spa_bt_transport_do_release(struct spa_bt_transport *transport) { struct spa_bt_monitor *monitor = transport->monitor; spa_assert(transport->acquire_refcount >= 1); spa_assert(transport->acquired); if (transport->acquire_refcount == 1) { if (!transport->keepalive) { spa_bt_transport_impl(transport, release, 0); transport->acquired = false; } else { spa_log_debug(monitor->log, "transport %p: keepalive %s on release", transport, transport->path); } } else { spa_log_debug(monitor->log, "transport %p: delayed decref %s", transport, transport->path); } transport->acquire_refcount -= 1; } int spa_bt_transport_release(struct spa_bt_transport *transport) { struct spa_bt_monitor *monitor = transport->monitor; if (transport->acquire_refcount > 1) { spa_log_debug(monitor->log, "transport %p: decref %s", transport, transport->path); transport->acquire_refcount -= 1; spa_bt_transport_emit_state_changed(transport, transport->state, transport->state); return 0; } else if (transport->acquire_refcount == 0) { spa_log_info(monitor->log, "transport %s already released", transport->path); return 0; } spa_assert(transport->acquire_refcount == 1); spa_assert(transport->acquired); /* Postpone active transport releases, since we might need it again soon. * If not active, release now since it has to be reacquired before using again. */ if (transport->state == SPA_BT_TRANSPORT_STATE_ACTIVE && !SPA_BT_TRANSPORT_IS_A2DP(transport)) { return spa_bt_transport_start_release_timer(transport); } else { spa_bt_transport_do_release(transport); return 0; } } static int spa_bt_transport_release_now(struct spa_bt_transport *transport) { int res; if (!transport->acquired) return 0; spa_bt_transport_stop_release_timer(transport); res = spa_bt_transport_impl(transport, release, 0); if (res >= 0) { transport->acquire_refcount = 0; transport->acquired = false; } return res; } int spa_bt_device_release_transports(struct spa_bt_device *device) { struct spa_bt_transport *t; spa_list_for_each(t, &device->transport_list, device_link) spa_bt_transport_release_now(t); return 0; } static int start_timeout_timer(struct spa_bt_monitor *monitor, struct spa_source *timer, spa_source_func_t timer_event, time_t timeout_msec, void *data) { struct itimerspec ts; if (timer->data == NULL) { timer->data = data; timer->func = timer_event; timer->fd = spa_system_timerfd_create( monitor->main_system, CLOCK_MONOTONIC, SPA_FD_CLOEXEC | SPA_FD_NONBLOCK); timer->mask = SPA_IO_IN; timer->rmask = 0; spa_loop_add_source(monitor->main_loop, timer); } ts.it_value.tv_sec = timeout_msec / SPA_MSEC_PER_SEC; ts.it_value.tv_nsec = (timeout_msec % SPA_MSEC_PER_SEC) * SPA_NSEC_PER_MSEC; ts.it_interval.tv_sec = 0; ts.it_interval.tv_nsec = 0; spa_system_timerfd_settime(monitor->main_system, timer->fd, 0, &ts, NULL); return 0; } static int stop_timeout_timer(struct spa_bt_monitor *monitor, struct spa_source *timer) { struct itimerspec ts; if (timer->data == NULL) return 0; spa_loop_remove_source(monitor->main_loop, timer); ts.it_value.tv_sec = 0; ts.it_value.tv_nsec = 0; ts.it_interval.tv_sec = 0; ts.it_interval.tv_nsec = 0; spa_system_timerfd_settime(monitor->main_system, timer->fd, 0, &ts, NULL); spa_system_close(monitor->main_system, timer->fd); timer->data = NULL; return 0; } static void spa_bt_transport_release_timer_event(struct spa_source *source) { struct spa_bt_transport *transport = source->data; spa_bt_transport_stop_release_timer(transport); spa_bt_transport_do_release(transport); } static int spa_bt_transport_start_release_timer(struct spa_bt_transport *transport) { return start_timeout_timer(transport->monitor, &transport->release_timer, spa_bt_transport_release_timer_event, TRANSPORT_RELEASE_TIMEOUT_MSEC, transport); } static int spa_bt_transport_stop_release_timer(struct spa_bt_transport *transport) { return stop_timeout_timer(transport->monitor, &transport->release_timer); } static void spa_bt_transport_commit_release_timer(struct spa_bt_transport *transport) { struct spa_bt_monitor *monitor = transport->monitor; /* Do release now if it is pending */ if (transport->release_timer.data) { spa_log_debug(monitor->log, "transport %p: commit pending release", transport); spa_bt_transport_release_timer_event(&transport->release_timer); } } static void spa_bt_transport_volume_changed(struct spa_bt_transport *transport) { struct spa_bt_monitor *monitor = transport->monitor; struct spa_bt_transport_volume * t_volume; int volume_id; if (transport->profile & SPA_BT_PROFILE_A2DP_SINK) volume_id = SPA_BT_VOLUME_ID_TX; else if (transport->profile & SPA_BT_PROFILE_A2DP_SOURCE) volume_id = SPA_BT_VOLUME_ID_RX; else if (transport->profile & SPA_BT_PROFILE_ASHA_SINK) volume_id = SPA_BT_VOLUME_ID_TX; else return; t_volume = &transport->volumes[volume_id]; if (t_volume->hw_volume != t_volume->new_hw_volume) { t_volume->hw_volume = t_volume->new_hw_volume; t_volume->volume = (float)spa_bt_volume_hw_to_linear(t_volume->hw_volume, t_volume->hw_volume_max); spa_log_debug(monitor->log, "transport %p: volume changed %d(%f) ", transport, t_volume->new_hw_volume, t_volume->volume); if (spa_bt_transport_volume_enabled(transport)) { transport->device->a2dp_volume_active[volume_id] = true; spa_bt_transport_emit_volume_changed(transport); } } } static void spa_bt_transport_volume_timer_event(struct spa_source *source) { struct spa_bt_transport *transport = source->data; struct spa_bt_monitor *monitor = transport->monitor; uint64_t exp; if (spa_system_timerfd_read(monitor->main_system, source->fd, &exp) < 0) spa_log_warn(monitor->log, "error reading timerfd: %s", strerror(errno)); spa_bt_transport_volume_changed(transport); } static int spa_bt_transport_start_volume_timer(struct spa_bt_transport *transport) { return start_timeout_timer(transport->monitor, &transport->volume_timer, spa_bt_transport_volume_timer_event, TRANSPORT_VOLUME_TIMEOUT_MSEC, transport); } static int spa_bt_transport_stop_volume_timer(struct spa_bt_transport *transport) { return stop_timeout_timer(transport->monitor, &transport->volume_timer); } int spa_bt_transport_ensure_sco_io(struct spa_bt_transport *t, struct spa_loop *data_loop, struct spa_system *data_system) { if (t->sco_io == NULL) { t->sco_io = spa_bt_sco_io_create(t, data_loop, data_system, t->monitor->log); if (t->sco_io == NULL) return -ENOMEM; } return 0; } int64_t spa_bt_transport_get_delay_nsec(struct spa_bt_transport *t) { if (t->delay_us != SPA_BT_UNKNOWN_DELAY) { /* end-to-end delay = (presentation) delay + transport latency * * For BAP, see Core v5.3 Vol 6/G Sec 3.2.2 Fig. 3.2 & * BAP v1.0 Sec 7.1.1. */ int64_t delay = t->delay_us; if (t->latency_us != SPA_BT_UNKNOWN_DELAY) delay += t->latency_us; return delay * SPA_NSEC_PER_USEC; } /* Fallback values when device does not provide information */ switch (t->media_codec->id) { case SPA_BLUETOOTH_AUDIO_CODEC_SBC: case SPA_BLUETOOTH_AUDIO_CODEC_SBC_XQ: case SPA_BLUETOOTH_AUDIO_CODEC_MPEG: case SPA_BLUETOOTH_AUDIO_CODEC_AAC: case SPA_BLUETOOTH_AUDIO_CODEC_APTX: case SPA_BLUETOOTH_AUDIO_CODEC_APTX_HD: case SPA_BLUETOOTH_AUDIO_CODEC_LDAC: return 125 * SPA_NSEC_PER_MSEC; case SPA_BLUETOOTH_AUDIO_CODEC_AAC_ELD: case SPA_BLUETOOTH_AUDIO_CODEC_APTX_LL: case SPA_BLUETOOTH_AUDIO_CODEC_APTX_LL_DUPLEX: case SPA_BLUETOOTH_AUDIO_CODEC_FASTSTREAM: case SPA_BLUETOOTH_AUDIO_CODEC_FASTSTREAM_DUPLEX: case SPA_BLUETOOTH_AUDIO_CODEC_LC3: return 40 * SPA_NSEC_PER_MSEC; case SPA_BLUETOOTH_AUDIO_CODEC_CVSD: case SPA_BLUETOOTH_AUDIO_CODEC_MSBC: case SPA_BLUETOOTH_AUDIO_CODEC_LC3_SWB: return 20 * SPA_NSEC_PER_MSEC; default: break; }; return 125 * SPA_NSEC_PER_MSEC; } static int transport_update_props(struct spa_bt_transport *transport, DBusMessageIter *props_iter, DBusMessageIter *invalidated_iter) { struct spa_bt_monitor *monitor = transport->monitor; while (dbus_message_iter_get_arg_type(props_iter) != DBUS_TYPE_INVALID) { DBusMessageIter it[2]; const char *key; int type; dbus_message_iter_recurse(props_iter, &it[0]); dbus_message_iter_get_basic(&it[0], &key); dbus_message_iter_next(&it[0]); dbus_message_iter_recurse(&it[0], &it[1]); type = dbus_message_iter_get_arg_type(&it[1]); if (type == DBUS_TYPE_STRING || type == DBUS_TYPE_OBJECT_PATH) { const char *value; dbus_message_iter_get_basic(&it[1], &value); spa_log_debug(monitor->log, "transport %p: %s=%s", transport, key, value); if (spa_streq(key, "UUID")) { transport->profile = swap_profile(spa_bt_profile_from_uuid(value)); if (transport->profile == SPA_BT_PROFILE_NULL) spa_log_warn(monitor->log, "unknown profile %s", value); } else if (spa_streq(key, "State")) { enum spa_bt_transport_state state = spa_bt_transport_state_from_string(value); /* Transition to active emitted only from acquire callback. */ if (state != SPA_BT_TRANSPORT_STATE_ACTIVE) spa_bt_transport_set_state(transport, state); } else if (spa_streq(key, "Device")) { char *pos; struct spa_bt_device *device = spa_bt_device_find(monitor, value); if ((device == NULL) && (transport->profile == SPA_BT_PROFILE_BAP_BROADCAST_SINK)) { /* * If a transport with profile broadcast source is detected (over DBus) * and no device is found for it, a new device will be created. * This device will be our simulated remote device. * This is done because BlueZ sets the adapter as the device * that is connected to a broadcast sink endpoint/transport. */ device = spa_bt_device_find(monitor, transport->path); if (device == NULL) { device = create_bcast_device(monitor, value, transport->path, "00:00:00:00:00:00"); if (device == NULL) { spa_log_warn(monitor->log, "could not find device %s", value); } else device_set_connected(device, 1); } } if ((device != NULL) && (transport->profile == SPA_BT_PROFILE_BAP_BROADCAST_SOURCE)) { /* * For each transport that has a broadcast source profile, * we need to create a new node for each BIS. * example of transport path = /org/bluez/hci0/dev_2D_9D_93_F9_D7_5E/bis1/fd0 * Create new devices only for a case of a big with multiple BISes, * for this case will have the scanned device to the transport * "/fd0" and create new devices for the other transports from this device * that appear only in case of multiple BISes per BIG. */ pos = strstr(transport->path, "/fd0"); if (pos == NULL) { device = create_bcast_device(monitor, device->adapter_path, transport->path, device->address); if (device == NULL) { spa_log_warn(monitor->log, "could not find device created"); } else device_set_connected(device, 1); } } if (transport->device != device) { if (transport->device != NULL) spa_list_remove(&transport->device_link); transport->device = device; if (device != NULL) spa_list_append(&device->transport_list, &transport->device_link); else spa_log_warn(monitor->log, "could not find device %s", value); } } else if (spa_streq(key, "Endpoint")) { struct spa_bt_remote_endpoint *ep = remote_endpoint_find(monitor, value); free(transport->remote_endpoint_path); transport->remote_endpoint_path = strdup(value); if (!ep) { spa_log_warn(monitor->log, "Unable to find remote endpoint for %s", value); goto next; } // If the remote endpoint is an acceptor this transport is an initiator transport->bap_initiator = ep->acceptor; } } else if (spa_streq(key, "Configuration")) { DBusMessageIter iter; uint8_t *value; int len; if (!check_iter_signature(&it[1], "ay")) goto next; dbus_message_iter_recurse(&it[1], &iter); dbus_message_iter_get_fixed_array(&iter, &value, &len); spa_log_debug(monitor->log, "transport %p: %s=%d", transport, key, len); spa_debug_log_mem(monitor->log, SPA_LOG_LEVEL_DEBUG, 2, value, (size_t)len); free(transport->configuration); transport->configuration_len = 0; transport->configuration = malloc(len); if (transport->configuration) { memcpy(transport->configuration, value, len); transport->configuration_len = len; } } else if (spa_streq(key, "Volume")) { uint16_t value; struct spa_bt_transport_volume * t_volume; if (type != DBUS_TYPE_UINT16) goto next; dbus_message_iter_get_basic(&it[1], &value); spa_log_debug(monitor->log, "transport %p: %s=%d", transport, key, value); if (transport->profile & SPA_BT_PROFILE_A2DP_SINK) t_volume = &transport->volumes[SPA_BT_VOLUME_ID_TX]; else if (transport->profile & SPA_BT_PROFILE_A2DP_SOURCE) t_volume = &transport->volumes[SPA_BT_VOLUME_ID_RX]; else if (transport->profile & SPA_BT_PROFILE_ASHA_SINK) t_volume = &transport->volumes[SPA_BT_VOLUME_ID_TX]; else if (transport->profile & SPA_BT_PROFILE_BAP_SINK) t_volume = &transport->volumes[SPA_BT_VOLUME_ID_TX]; else if (transport->profile & SPA_BT_PROFILE_BAP_SOURCE) t_volume = &transport->volumes[SPA_BT_VOLUME_ID_RX]; else goto next; t_volume->active = true; t_volume->new_hw_volume = value; if (transport->profile & SPA_BT_PROFILE_A2DP_SINK) spa_bt_transport_start_volume_timer(transport); else spa_bt_transport_volume_changed(transport); } else if (spa_streq(key, "Delay")) { uint16_t value; if (type != DBUS_TYPE_UINT16) goto next; dbus_message_iter_get_basic(&it[1], &value); spa_log_debug(monitor->log, "transport %p: %s=%d", transport, key, (int)value); transport->delay_us = value * 100; spa_bt_transport_emit_delay_changed(transport); } else if (spa_streq(key, "QoS")) { struct bap_codec_qos_full qos; DBusMessageIter value; if (!check_iter_signature(&it[1], "a{sv}")) goto next; dbus_message_iter_recurse(&it[1], &value); parse_codec_qos(monitor, &value, &qos); transport->bap_cig = qos.cig; transport->bap_cis = qos.cis; transport->bap_big = qos.big; transport->bap_bis = qos.bis; transport->delay_us = qos.qos.delay; transport->latency_us = (unsigned int)qos.qos.latency * 1000; spa_bt_transport_emit_delay_changed(transport); } else if (spa_streq(key, "Links")) { DBusMessageIter iter; if (!check_iter_signature(&it[1], "ao")) goto next; spa_list_remove(&transport->bap_transport_linked); spa_list_init(&transport->bap_transport_linked); dbus_message_iter_recurse(&it[1], &iter); while (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_INVALID) { const char *transport_path; struct spa_bt_transport *t; dbus_message_iter_get_basic(&iter, &transport_path); spa_log_debug(monitor->log, "transport %p: Linked with=%s", transport, transport_path); t = spa_bt_transport_find(monitor, transport_path); if (!t) { spa_log_warn(monitor->log, "Unable to find linked transport"); dbus_message_iter_next(&iter); continue; } if (spa_list_is_empty(&t->bap_transport_linked)) spa_list_append(&transport->bap_transport_linked, &t->bap_transport_linked); else if (spa_list_is_empty(&transport->bap_transport_linked)) spa_list_append(&t->bap_transport_linked, &transport->bap_transport_linked); dbus_message_iter_next(&iter); } } next: dbus_message_iter_next(props_iter); } return 0; } static void transport_set_property_volume_reply(DBusPendingCall *pending, void *user_data) { struct spa_bt_transport *transport = user_data; struct spa_bt_monitor *monitor = transport->monitor; spa_auto(DBusError) err = DBUS_ERROR_INIT; spa_assert(transport->volume_call == pending); spa_autoptr(DBusMessage) r = steal_reply_and_unref(&transport->volume_call); if (dbus_set_error_from_message(&err, r)) { spa_log_info(monitor->log, "transport %p: set volume failed for transport %s: %s", transport, transport->path, err.message); } else { spa_log_debug(monitor->log, "transport %p: set volume complete", transport); } } static void transport_set_property_volume(struct spa_bt_transport *transport, uint16_t value) { struct spa_bt_monitor *monitor = transport->monitor; spa_autoptr(DBusMessage) m = NULL; DBusMessageIter it[2]; const char *interface = BLUEZ_MEDIA_TRANSPORT_INTERFACE; const char *name = "Volume"; int res = 0; cancel_and_unref(&transport->volume_call); m = dbus_message_new_method_call(BLUEZ_SERVICE, transport->path, DBUS_INTERFACE_PROPERTIES, "Set"); if (m == NULL) { res = -ENOMEM; goto fail; } dbus_message_iter_init_append(m, &it[0]); dbus_message_iter_append_basic(&it[0], DBUS_TYPE_STRING, &interface); dbus_message_iter_append_basic(&it[0], DBUS_TYPE_STRING, &name); dbus_message_iter_open_container(&it[0], DBUS_TYPE_VARIANT, DBUS_TYPE_UINT16_AS_STRING, &it[1]); dbus_message_iter_append_basic(&it[1], DBUS_TYPE_UINT16, &value); dbus_message_iter_close_container(&it[0], &it[1]); transport->volume_call = send_with_reply(monitor->conn, m, transport_set_property_volume_reply, transport); if (!transport->volume_call) { res = -EIO; goto fail; } spa_log_debug(monitor->log, "transport %p: setting volume to %d", transport, value); return; fail: spa_log_debug(monitor->log, "transport %p: failed to set volume %d: %s", transport, value, spa_strerror(res)); } static int transport_set_volume(void *data, int id, float volume) { struct spa_bt_transport *transport = data; struct spa_bt_transport_volume *t_volume; uint16_t value; spa_assert(id >= 0 && id < (int)SPA_N_ELEMENTS(transport->volumes)); t_volume = &transport->volumes[id]; if (!t_volume->active || !spa_bt_transport_volume_enabled(transport)) return -ENOTSUP; value = spa_bt_volume_linear_to_hw(volume, t_volume->hw_volume_max); t_volume->volume = volume; /* AVRCP volume would not applied on remote sink device * if transport is not acquired (idle). */ if (transport->fd < 0 && (transport->profile & SPA_BT_PROFILE_A2DP_SINK)) { t_volume->hw_volume = SPA_BT_VOLUME_INVALID; return 0; } else if (t_volume->hw_volume != value) { t_volume->hw_volume = value; spa_bt_transport_stop_volume_timer(transport); transport_set_property_volume(transport, value); } return 0; } static int transport_create_iso_io(struct spa_bt_transport *transport) { struct spa_bt_monitor *monitor = transport->monitor; struct spa_bt_transport *t; if (!(transport->profile & (SPA_BT_PROFILE_BAP_SINK | SPA_BT_PROFILE_BAP_SOURCE | SPA_BT_PROFILE_BAP_BROADCAST_SINK | SPA_BT_PROFILE_BAP_BROADCAST_SOURCE))) return 0; if ((transport->profile == SPA_BT_PROFILE_BAP_BROADCAST_SINK) || (transport->profile == SPA_BT_PROFILE_BAP_BROADCAST_SOURCE)) { if (transport->bap_big == 0xff || transport->bap_bis == 0xff) return -EINVAL; } else { if (transport->bap_cig == 0xff || transport->bap_cis == 0xff) return -EINVAL; } if (transport->iso_io) { spa_log_debug(monitor->log, "transport %p: remove ISO IO", transport); spa_bt_iso_io_destroy(transport->iso_io); transport->iso_io = NULL; } /* Transports in same connected iso group share the same i/o */ spa_list_for_each(t, &monitor->transport_list, link) { if (!(t->profile & (SPA_BT_PROFILE_BAP_SINK | SPA_BT_PROFILE_BAP_SOURCE | SPA_BT_PROFILE_BAP_BROADCAST_SINK | SPA_BT_PROFILE_BAP_BROADCAST_SOURCE))) continue; if (t->device->adapter != transport->device->adapter) continue; if ((transport->profile == SPA_BT_PROFILE_BAP_BROADCAST_SINK) || (transport->profile == SPA_BT_PROFILE_BAP_BROADCAST_SOURCE)) { if (t->bap_big != transport->bap_big) continue; } else { if (t->bap_cig != transport->bap_cig) continue; } if (t->iso_io) { spa_log_debug(monitor->log, "transport %p: attach ISO IO to %p", transport, t); transport->iso_io = spa_bt_iso_io_attach(t->iso_io, transport); if (transport->iso_io == NULL) return -errno; return 0; } } spa_log_debug(monitor->log, "transport %p: new ISO IO", transport); transport->iso_io = spa_bt_iso_io_create(transport, monitor->log, monitor->data_loop, monitor->data_system); if (transport->iso_io == NULL) return -errno; return 0; } static bool transport_in_same_cig(struct spa_bt_transport *transport, struct spa_bt_transport *other) { return (other->profile & (SPA_BT_PROFILE_BAP_SINK | SPA_BT_PROFILE_BAP_SOURCE)) && other->bap_cig == transport->bap_cig && other->bap_initiator; } static void transport_acquire_reply(DBusPendingCall *pending, void *user_data) { struct spa_bt_transport *transport = user_data; struct spa_bt_monitor *monitor = transport->monitor; struct spa_bt_device *device = transport->device; int ret = 0; spa_auto(DBusError) err = DBUS_ERROR_INIT; struct spa_bt_transport *t, *t_linked; spa_assert(transport->acquire_call == pending); spa_autoptr(DBusMessage) r = steal_reply_and_unref(&transport->acquire_call); spa_bt_device_update_last_bluez_action_time(device); if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) { spa_log_error(monitor->log, "Acquire %s returned error: %s", transport->path, dbus_message_get_error_name(r)); /* If no reply, BlueZ may consider operation still active, so release to * try to get to a known state. */ if (spa_streq(dbus_message_get_error_name(r), DBUS_ERROR_NO_REPLY)) { spa_autoptr(DBusMessage) m = NULL; spa_log_info(monitor->log, "Releasing transport %s (clean up NoReply)", transport->path); m = dbus_message_new_method_call(BLUEZ_SERVICE, transport->path, BLUEZ_MEDIA_TRANSPORT_INTERFACE, "Release"); if (m) dbus_connection_send(monitor->conn, m, NULL); } ret = -EIO; goto finish; } if (transport->fd >= 0) { spa_log_error(monitor->log, "transport %p: invalid duplicate acquire", transport); ret = -EINVAL; goto finish; } if (!dbus_message_get_args(r, &err, DBUS_TYPE_UNIX_FD, &transport->fd, DBUS_TYPE_UINT16, &transport->read_mtu, DBUS_TYPE_UINT16, &transport->write_mtu, DBUS_TYPE_INVALID)) { spa_log_error(monitor->log, "Failed to parse Acquire %s reply: %s", transport->path, err.message); ret = -EIO; goto finish; } spa_log_debug(monitor->log, "transport %p: Acquired %s, fd %d MTU %d:%d", transport, transport->path, transport->fd, transport->read_mtu, transport->write_mtu); spa_bt_player_set_state(transport->device->adapter->dummy_player, SPA_BT_PLAYER_PLAYING); transport_sync_volume(transport); finish: if (ret < 0) { spa_bt_transport_set_state(transport, SPA_BT_TRANSPORT_STATE_ERROR); /* For broadcast, skip handling links. Each link acquire * is handled separately. */ if ((transport->profile == SPA_BT_PROFILE_BAP_BROADCAST_SINK) || (transport->profile == SPA_BT_PROFILE_BAP_BROADCAST_SOURCE)) return; } else { if (transport_create_iso_io(transport) < 0) spa_log_error(monitor->log, "transport %p: transport_create_iso_io failed", transport); /* For broadcast, each transport has a different fd, so it needs to be * acquired independently from others. Each transport moves to * SPA_BT_TRANSPORT_STATE_ACTIVE after acquire is completed. */ /* TODO: handling multiple BIGs support */ if ((transport->profile == SPA_BT_PROFILE_BAP_BROADCAST_SINK) || (transport->profile == SPA_BT_PROFILE_BAP_BROADCAST_SOURCE)) { spa_bt_transport_set_state(transport, SPA_BT_TRANSPORT_STATE_ACTIVE); return; } if (!transport->bap_initiator) spa_bt_transport_set_state(transport, SPA_BT_TRANSPORT_STATE_ACTIVE); } /* For LE Audio, multiple transport from the same device may share the same * stream (CIS) and group (CIG) but for different direction, e.g. a speaker and * a microphone. In this case they are linked, and we need to set the values * for all of them here. */ spa_list_for_each(t_linked, &transport->bap_transport_linked, bap_transport_linked) { if (ret < 0) { spa_bt_transport_set_state(t_linked, SPA_BT_TRANSPORT_STATE_ERROR); continue; } t_linked->fd = transport->fd; t_linked->read_mtu = transport->read_mtu; t_linked->write_mtu = transport->write_mtu; spa_log_debug(monitor->log, "transport %p: linked Acquired %s, fd %d MTU %d:%d", t_linked, t_linked->path, t_linked->fd, t_linked->read_mtu, t_linked->write_mtu); if (transport_create_iso_io(t_linked) < 0) spa_log_error(monitor->log, "transport %p: transport_create_iso_io failed", t_linked); /* For broadcast there initiator moves the transport state to SPA_BT_TRANSPORT_STATE_ACTIVE */ if ((transport->profile == SPA_BT_PROFILE_BAP_BROADCAST_SINK) || (transport->profile == SPA_BT_PROFILE_BAP_BROADCAST_SOURCE)) { spa_bt_transport_set_state(t_linked, SPA_BT_TRANSPORT_STATE_ACTIVE); } else { if (!transport->bap_initiator) spa_bt_transport_set_state(t_linked, SPA_BT_TRANSPORT_STATE_ACTIVE); } } /* * Transports in same CIG emit state change events at the same time, * after all pending acquires complete. */ if (transport->bap_initiator) { spa_list_for_each(t, &monitor->transport_list, link) { if (!transport_in_same_cig(transport, t)) continue; if (t->acquire_call) return; } spa_list_for_each(t, &monitor->transport_list, link) { if (!transport_in_same_cig(transport, t)) continue; if (t->fd >= 0) spa_bt_transport_set_state(t, SPA_BT_TRANSPORT_STATE_ACTIVE); } } } static int do_transport_acquire(struct spa_bt_transport *transport) { struct spa_bt_monitor *monitor = transport->monitor; spa_autoptr(DBusMessage) m = NULL; struct spa_bt_transport *t_linked; if ((transport->profile == SPA_BT_PROFILE_BAP_BROADCAST_SINK) || (transport->profile == SPA_BT_PROFILE_BAP_BROADCAST_SOURCE)) /* For Broadcast, all linked transports need to be * acquired independently, since they have different fds. */ goto acquire; spa_list_for_each(t_linked, &transport->bap_transport_linked, bap_transport_linked) { /* If a linked transport has been acquired, it will do all the work */ if (t_linked->acquire_call || t_linked->acquired) { spa_log_debug(monitor->log, "Acquiring %s: use linked transport %s", transport->path, t_linked->path); spa_bt_transport_emit_state_changed(transport, transport->state, transport->state); return 0; } } acquire: if (transport->acquire_call) return -EBUSY; spa_log_info(monitor->log, "Acquiring transport %s", transport->path); m = dbus_message_new_method_call(BLUEZ_SERVICE, transport->path, BLUEZ_MEDIA_TRANSPORT_INTERFACE, "Acquire"); if (m == NULL) return -ENOMEM; transport->acquire_call = send_with_reply(monitor->conn, m, transport_acquire_reply, transport); if (!transport->acquire_call) return -EIO; return 0; } static bool another_cig_transport_active(struct spa_bt_transport *transport) { struct spa_bt_monitor *monitor = transport->monitor; struct spa_bt_transport *t; spa_list_for_each(t, &monitor->transport_list, link) { if (!transport_in_same_cig(transport, t) || t == transport) continue; if (t->acquired) return true; } return false; } static int transport_acquire(void *data, bool optional) { struct spa_bt_transport *transport = data; struct spa_bt_monitor *monitor = transport->monitor; /* * XXX: When as BAP Central, all CIS in a CIG must be acquired at the same time. * XXX: This is because of kernel ISO socket limitations, which does not handle * XXX: currently starting streams in the group one by one. */ if (transport->bap_initiator && !another_cig_transport_active(transport)) { struct spa_bt_transport *t; spa_list_for_each(t, &monitor->transport_list, link) { if (!transport_in_same_cig(transport, t) || t == transport) continue; spa_log_debug(monitor->log, "Acquire CIG %d: transport %s", transport->bap_cig, t->path); do_transport_acquire(t); } spa_log_debug(monitor->log, "Acquire CIG %d: transport %s", transport->bap_cig, transport->path); } if (transport->bap_initiator && (transport->fd >= 0 || transport->acquire_call)) { /* Already acquired/acquiring */ spa_log_debug(monitor->log, "Acquiring %s: was in acquired CIG", transport->path); spa_bt_transport_emit_state_changed(transport, transport->state, transport->state); return 0; } return do_transport_acquire(data); } struct pending_release { struct spa_list link; DBusPendingCall *pending; struct spa_bt_transport *transport; bool is_idle; }; static struct pending_release *do_transport_release(struct spa_bt_transport *transport) { struct spa_bt_monitor *monitor = transport->monitor; spa_autoptr(DBusMessage) m = NULL; struct spa_bt_transport *t_linked; bool is_idle = (transport->state == SPA_BT_TRANSPORT_STATE_IDLE); bool linked = false; struct pending_release *pending; DBusPendingCall *p; spa_log_debug(monitor->log, "transport %p: Release %s", transport, transport->path); spa_bt_player_set_state(transport->device->adapter->dummy_player, SPA_BT_PLAYER_STOPPED); spa_bt_transport_set_state(transport, SPA_BT_TRANSPORT_STATE_IDLE); cancel_and_unref(&transport->acquire_call); if (transport->iso_io) { spa_log_debug(monitor->log, "transport %p: remove ISO IO", transport); spa_bt_iso_io_destroy(transport->iso_io); transport->iso_io = NULL; } /* For Unicast LE Audio, multiple transport stream (CIS) can be linked together (CIG). * If they are part of the same device they reuse the same fd, and call to * release should be done for the last one only. * * For Broadcast LE Audio, since linked transports have different fds, they * should be released independently. */ if ((transport->profile == SPA_BT_PROFILE_BAP_BROADCAST_SINK) || (transport->profile == SPA_BT_PROFILE_BAP_BROADCAST_SOURCE)) goto release; spa_list_for_each(t_linked, &transport->bap_transport_linked, bap_transport_linked) { if (t_linked->acquire_call || t_linked->acquired) { linked = true; break; } } if (linked) { spa_log_info(monitor->log, "Linked transport %s released", transport->path); transport->fd = -1; return NULL; } release: if (transport->fd >= 0) { close(transport->fd); transport->fd = -1; } spa_log_info(monitor->log, "Releasing transport %s", transport->path); m = dbus_message_new_method_call(BLUEZ_SERVICE, transport->path, BLUEZ_MEDIA_TRANSPORT_INTERFACE, "Release"); if (m == NULL) return NULL; p = send_with_reply(monitor->conn, m, NULL, NULL); if (!p) return NULL; pending = calloc(1, sizeof(*pending)); if (!pending) { dbus_pending_call_block(p); dbus_pending_call_unref(p); return NULL; } pending->pending = p; pending->transport = transport; pending->is_idle = is_idle; return pending; } static int transport_release(void *data) { struct spa_bt_transport *transport = data; struct spa_bt_monitor *monitor = transport->monitor; struct spa_list pending = SPA_LIST_INIT(&pending); struct pending_release *item; /* * XXX: When as BAP Central, release CIS in a CIG when the last transport * XXX: goes away. */ if (transport->bap_initiator) { struct spa_bt_transport *t; /* Check if another transport is alive */ if (another_cig_transport_active(transport)) { spa_log_debug(monitor->log, "Releasing %s: wait for CIG %d", transport->path, transport->bap_cig); return 0; } /* Release remaining transports in CIG */ spa_list_for_each(t, &monitor->transport_list, link) { if (!transport_in_same_cig(transport, t) || t == transport) continue; spa_log_debug(monitor->log, "Release CIG %d: transport %s", transport->bap_cig, t->path); if (t->fd >= 0) { item = do_transport_release(t); if (item) spa_list_append(&pending, &item->link); } } spa_log_debug(monitor->log, "Release CIG %d: transport %s", transport->bap_cig, transport->path); } item = do_transport_release(transport); if (item) spa_list_append(&pending, &item->link); spa_list_consume(item, &pending, link) { struct spa_bt_transport *t = item->transport; bool is_idle = item->is_idle; DBusPendingCall *p = item->pending; spa_autoptr(DBusMessage) r = NULL; spa_auto(DBusError) err = DBUS_ERROR_INIT; spa_list_remove(&item->link); free(item); if (!p) continue; dbus_pending_call_block(p); r = steal_reply_and_unref(&p); if (r == NULL) { if (is_idle) { /* XXX: The fd always needs to be closed. However, Release() * XXX: apparently doesn't need to be called on idle transports * XXX: and fails. We call it just to be sure (e.g. in case * XXX: there's a race with updating the property), but tone down the error. */ spa_log_debug(monitor->log, "Failed to release idle transport %s: %s", t->path, err.message); } else if (spa_streq(err.name, DBUS_ERROR_UNKNOWN_METHOD) || spa_streq(err.name, DBUS_ERROR_UNKNOWN_OBJECT)) { /* Transport disappeared */ spa_log_debug(monitor->log, "Failed to release (gone) transport %s: %s", t->path, err.message); } else { spa_log_error(monitor->log, "Failed to release transport %s: %s", t->path, err.message); } } else { spa_log_info(monitor->log, "Transport %s released", t->path); } } return 0; } static int transport_set_delay(void *data, int64_t delay_nsec) { struct spa_bt_transport *transport = data; struct spa_bt_monitor *monitor = transport->monitor; DBusMessageIter it[2]; spa_autoptr(DBusMessage) m = NULL; uint16_t value; const char *property = "Delay", *interface = BLUEZ_MEDIA_TRANSPORT_INTERFACE; if (!(transport->profile & SPA_BT_PROFILE_A2DP_DUPLEX)) return -ENOTSUP; value = SPA_CLAMP(delay_nsec / (100 * SPA_NSEC_PER_USEC), 0, UINT16_MAX); if (transport->delay_us == 100 * value) return 0; transport->delay_us = 100 * value; m = dbus_message_new_method_call(BLUEZ_SERVICE, transport->path, DBUS_INTERFACE_PROPERTIES, "Set"); if (m == NULL) return -ENOMEM; dbus_message_iter_init_append(m, &it[0]); dbus_message_iter_append_basic(&it[0], DBUS_TYPE_STRING, &interface); dbus_message_iter_append_basic(&it[0], DBUS_TYPE_STRING, &property); dbus_message_iter_open_container(&it[0], DBUS_TYPE_VARIANT, DBUS_TYPE_UINT16_AS_STRING, &it[1]); dbus_message_iter_append_basic(&it[1], DBUS_TYPE_UINT16, &value); dbus_message_iter_close_container(&it[0], &it[1]); if (!dbus_connection_send(monitor->conn, m, NULL)) return -EIO; spa_log_debug(monitor->log, "transport %p: set delay %d us", transport, 100 * value); return 0; } static const struct spa_bt_transport_implementation transport_impl = { SPA_VERSION_BT_TRANSPORT_IMPLEMENTATION, .acquire = transport_acquire, .release = transport_release, .set_volume = transport_set_volume, .set_delay = transport_set_delay, }; static int setup_asha_transport(struct spa_bt_remote_endpoint *remote_endpoint, struct spa_bt_monitor *monitor) { const struct media_codec * const * const media_codecs = monitor->media_codecs; const struct media_codec *codec = NULL; struct spa_bt_transport *transport; char hisyncid[32] = { 0 }; char *tpath; if (!remote_endpoint->transport_path) { spa_log_error(monitor->log, "Missing ASHA transport path"); return -EINVAL; } transport = spa_bt_transport_find(monitor, remote_endpoint->transport_path); if (transport != NULL) { spa_log_debug(monitor->log, "transport %p: free %s", transport, transport->path); spa_bt_transport_free(transport); } tpath = strdup(remote_endpoint->transport_path); transport = spa_bt_transport_create(monitor, tpath, 0); if (transport == NULL) { spa_log_error(monitor->log, "Failed to create transport for %s", remote_endpoint->transport_path); free(tpath); return -EINVAL; } spa_bt_transport_set_implementation(transport, &transport_impl, transport); spa_log_debug(monitor->log, "Created ASHA transport for %s", remote_endpoint->transport_path); for (int i = 0; media_codecs[i]; i++) { const struct media_codec *mcodec = media_codecs[i]; if (mcodec->kind != MEDIA_CODEC_ASHA) continue; if (!spa_streq(mcodec->name, "g722")) continue; codec = mcodec; spa_log_debug(monitor->log, "Setting ASHA codec: %s", mcodec->name); } free(transport->remote_endpoint_path); free(transport->endpoint_path); transport->remote_endpoint_path = strdup(remote_endpoint->path); transport->endpoint_path = strdup(remote_endpoint->path); transport->profile = SPA_BT_PROFILE_ASHA_SINK; transport->media_codec = codec; transport->device = remote_endpoint->device; transport->hisyncid = remote_endpoint->hisyncid; transport->asha_right_side = remote_endpoint->asha_right_side; spa_list_append(&remote_endpoint->device->transport_list, &transport->device_link); spa_bt_device_update_last_bluez_action_time(transport->device); transport->volumes[SPA_BT_VOLUME_ID_TX].active = true; transport->volumes[SPA_BT_VOLUME_ID_TX].volume = DEFAULT_TX_VOLUME; transport->n_channels = 1; transport->channels[0] = transport->asha_right_side ? SPA_AUDIO_CHANNEL_FR : SPA_AUDIO_CHANNEL_FL; spa_bt_device_add_profile(transport->device, transport->profile); spa_bt_device_connect_profile(transport->device, transport->profile); transport_sync_volume(transport); spa_scnprintf(hisyncid, sizeof(hisyncid), "/asha/%" PRIu64, transport->hisyncid); device_add_device_set(transport->device, hisyncid, transport->asha_right_side ? 1 : 0); device_update_set_status(transport->device, true, hisyncid); const char *side = transport->asha_right_side ? "right" : "left"; spa_log_debug(monitor->log, "ASHA transport setup complete for %s side", side); return 0; } static void codec_switch_resume(struct spa_bt_codec_switch *sw) { spa_assert(sw->waiting); sw->waiting = false; codec_switch_list_process(&sw->device->codec_switch_list); } static void codec_switch_rate_limit_event(void *data, uint64_t exp) { codec_switch_resume(data); } static bool codec_switch_rate_limit(struct spa_bt_codec_switch *sw) { struct spa_bt_device *device = sw->device; struct spa_bt_monitor *monitor = device->monitor; uint64_t now, wakeup; struct timespec ts; now = get_time_now(monitor); wakeup = device->last_bluez_action_time + BLUEZ_ACTION_RATE_MSEC * SPA_NSEC_PER_MSEC; if (now >= wakeup) return false; if (!sw->timer) sw->timer = spa_loop_utils_add_timer(monitor->loop_utils, codec_switch_rate_limit_event, sw); if (!sw->timer) return false; ts.tv_sec = wakeup / SPA_NSEC_PER_SEC; ts.tv_nsec = wakeup % SPA_NSEC_PER_SEC; if (spa_loop_utils_update_timer(monitor->loop_utils, sw->timer, &ts, NULL, true) < 0) { spa_loop_utils_destroy_source(monitor->loop_utils, sw->timer); sw->timer = NULL; return false; } return true; } static bool codec_switch_check_endpoint(struct spa_bt_remote_endpoint *ep, const struct media_codec *codec, bool *sink, char **local_endpoint) { enum spa_bt_media_direction direction; spa_autofree char *path = NULL; uint32_t ep_profile; if (!ep || !ep->uuid || !ep->device) return false; ep_profile = spa_bt_profile_from_uuid(ep->uuid); if (!(ep_profile & get_codec_target_profile(ep->device->monitor, codec))) return false; if (!media_codec_check_caps(codec, ep->codec, ep->capabilities, ep->capabilities_len, &ep->device->monitor->default_audio_info, &ep->device->monitor->global_settings)) return false; if (ep_profile & (SPA_BT_PROFILE_A2DP_SINK | SPA_BT_PROFILE_BAP_SINK)) { direction = SPA_BT_MEDIA_SOURCE; if (sink) *sink = false; } else if (ep_profile & (SPA_BT_PROFILE_A2DP_SOURCE | SPA_BT_PROFILE_BAP_SOURCE)) { direction = SPA_BT_MEDIA_SINK; if (sink) *sink = true; } else { return false; } if (!(get_codec_profile(codec, direction) & ep->device->monitor->enabled_profiles)) return false; if (media_codec_to_endpoint(codec, direction, &path) < 0) return false; if (local_endpoint) *local_endpoint = spa_steal_ptr(path); return true; } static void codec_switch_reply(DBusPendingCall *pending, void *user_data) { struct spa_bt_codec_switch *sw = user_data; struct spa_bt_device *device = sw->device; struct spa_bt_monitor *monitor = device->monitor; spa_assert(sw->pending == pending); spa_autoptr(DBusMessage) r = steal_reply_and_unref(&sw->pending); spa_bt_device_update_last_bluez_action_time(device); if (r == NULL) { spa_log_error(monitor->log, "media codec switch %p: empty reply from dbus", sw); sw->failed = true; } else if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) { spa_log_error(monitor->log, "media codec switch %p: failed (%s)", sw, dbus_message_get_error_name(r)); sw->failed = true; } codec_switch_resume(sw); } static bool codec_switch_configure_a2dp(struct spa_bt_codec_switch *sw, const char *path) { struct spa_bt_device *device = sw->device; struct spa_bt_monitor *monitor = device->monitor; struct spa_bt_remote_endpoint *ep; struct spa_bt_transport *t; const struct media_codec *codec; uint8_t config[A2DP_MAX_CAPS_SIZE]; int res, config_size; spa_autofree char *local_endpoint = NULL; spa_autoptr(DBusMessage) m = NULL; DBusMessageIter iter, d; bool sink; codec = sw->codec; ep = device_remote_endpoint_find(device, path); if (!codec_switch_check_endpoint(ep, codec, &sink, &local_endpoint)) { spa_log_error(monitor->log, "media codec switch %p: endpoint %s not valid", sw, path); return false; } /* Each A2DP endpoint can be used by only one device at a time (on each adapter) */ spa_list_for_each(t, &monitor->transport_list, link) { if (t->device == device) continue; if (t->device->adapter != device->adapter) continue; if (spa_streq(t->endpoint_path, local_endpoint)) { spa_log_error(monitor->log, "media codec switch %p: endpoint %s in use", sw, local_endpoint); return false; } } res = codec->select_config(codec, sink ? MEDIA_CODEC_FLAG_SINK : 0, ep->capabilities, ep->capabilities_len, &monitor->default_audio_info, &monitor->global_settings, config, NULL); if (res < 0) { spa_log_error(monitor->log, "media codec switch %p: incompatible capabilities (%d)", sw, res); return false; } config_size = res; spa_log_debug(monitor->log, "media codec switch %p: configuration %d", sw, config_size); spa_debug_log_mem(monitor->log, SPA_LOG_LEVEL_DEBUG, 4, config, config_size); /* Codecs may share the same endpoint, so indicate which one we are using */ device->preferred_codec = codec; /* org.bluez.MediaEndpoint1.SetConfiguration on remote endpoint */ m = dbus_message_new_method_call(BLUEZ_SERVICE, ep->path, BLUEZ_MEDIA_ENDPOINT_INTERFACE, "SetConfiguration"); if (m == NULL) { spa_log_error(monitor->log, "media codec switch %p: dbus allocation failure", sw); return false; } spa_bt_device_update_last_bluez_action_time(device); spa_log_info(monitor->log, "media codec switch %p: set codec %s for endpoint %s, local endpoint %s", sw, codec->name, ep->path, local_endpoint); dbus_message_iter_init_append(m, &iter); dbus_message_iter_append_basic(&iter, DBUS_TYPE_OBJECT_PATH, &local_endpoint); dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &d); append_basic_array_variant_dict_entry(&d, "Capabilities", "ay", "y", DBUS_TYPE_BYTE, config, config_size); dbus_message_iter_close_container(&iter, &d); spa_assert(sw->pending == NULL); sw->pending = send_with_reply(monitor->conn, m, codec_switch_reply, sw); if (!sw->pending) { spa_log_error(monitor->log, "media codec switch %p: dbus call failure", sw); return false; } return true; } static bool codec_switch_configure_bap(struct spa_bt_codec_switch *sw, const char *path, bool last) { struct spa_bt_device *device = sw->device; struct spa_bt_monitor *monitor = device->monitor; struct spa_bt_remote_endpoint *ep; spa_autoptr(DBusMessage) m = NULL; DBusMessageIter iter, d; dbus_bool_t defer = !last; ep = device_remote_endpoint_find(device, path); if (!ep) { spa_log_error(monitor->log, "media codec switch %p: no endpoint %s", sw, path); return false; } device->preferred_codec = sw->codec; device->preferred_profiles = sw->profiles; m = dbus_message_new_method_call(BLUEZ_SERVICE, ep->path, BLUEZ_MEDIA_ENDPOINT_INTERFACE, "Reconfigure"); if (m == NULL) { spa_log_error(monitor->log, "media codec switch %p: dbus allocation failure", sw); return false; } spa_bt_device_update_last_bluez_action_time(device); spa_log_info(monitor->log, "media codec switch %p: reconfigure endpoint %s, defer:%d", sw, ep->path, (int)defer); dbus_message_iter_init_append(m, &iter); dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &d); append_basic_variant_dict_entry(&d, "Defer", DBUS_TYPE_BOOLEAN, "b", &defer); dbus_message_iter_close_container(&iter, &d); spa_assert(sw->pending == NULL); sw->pending = send_with_reply(monitor->conn, m, codec_switch_reply, sw); if (!sw->pending) { spa_log_error(monitor->log, "media codec switch %p: dbus call failure", sw); return false; } return true; } static bool codec_switch_clear_bap(struct spa_bt_codec_switch *sw, const char *path) { struct spa_bt_device *device = sw->device; struct spa_bt_monitor *monitor = device->monitor; struct spa_bt_remote_endpoint *ep; spa_autoptr(DBusMessage) m = NULL; DBusMessageIter iter; ep = device_remote_endpoint_find(device, path); if (!ep) return true; m = dbus_message_new_method_call(BLUEZ_SERVICE, ep->path, BLUEZ_MEDIA_ENDPOINT_INTERFACE, "ClearConfiguration"); if (m == NULL) { spa_log_error(monitor->log, "media codec switch %p: dbus allocation failure", sw); return false; } spa_bt_device_update_last_bluez_action_time(device); spa_log_info(monitor->log, "media codec switch %p: clear endpoint %s", sw, ep->path); dbus_message_iter_init_append(m, &iter); dbus_message_iter_append_basic(&iter, DBUS_TYPE_OBJECT_PATH, &path); spa_assert(sw->pending == NULL); sw->pending = send_with_reply(monitor->conn, m, codec_switch_reply, sw); if (!sw->pending) { spa_log_error(monitor->log, "media codec switch %p: dbus call failure", sw); return false; } return true; } static void codec_switch_emit_switching(struct spa_bt_monitor *monitor) { struct spa_bt_device *d; struct spa_bt_codec_switch *sw; bool found = false; spa_list_for_each(d, &monitor->device_list, link) { spa_list_for_each(sw, &d->codec_switch_list, link) { if (sw->profiles & SPA_BT_PROFILE_BAP_AUDIO) { found = true; goto done; } } } done: spa_list_for_each(d, &monitor->device_list, link) spa_bt_device_emit_codec_switch_other(d, found); } static bool codec_switch_process(struct spa_bt_codec_switch *sw) { if (sw->waiting) return false; if (sw->canceled) return true; if (sw->failed) goto fail; if (sw->paths[sw->path_idx].path == NULL) { /* Success */ spa_log_info(sw->device->monitor->log, "media codec switch %p: success", sw); spa_bt_device_emit_codec_switched(sw->device, 0); spa_bt_device_check_profiles(sw->device, false); sw->profiles = 0; codec_switch_emit_switching(sw->device->monitor); return true; } if (sw->profiles & SPA_BT_PROFILE_A2DP_DUPLEX) { /* Rate limit BlueZ calls */ if (codec_switch_rate_limit(sw)) return false; if (!codec_switch_configure_a2dp(sw, sw->paths[sw->path_idx].path)) goto fail; } else { if (sw->path_idx == 0 && codec_switch_rate_limit(sw)) return false; if (sw->path_idx == 0) codec_switch_emit_switching(sw->device->monitor); if (sw->paths[sw->path_idx].clear) { if (!codec_switch_clear_bap(sw, sw->paths[sw->path_idx].path)) goto fail; } else { bool last = (sw->paths[sw->path_idx + 1].path == NULL); if (!codec_switch_configure_bap(sw, sw->paths[sw->path_idx].path, last)) goto fail; } } /* Configure another endpoint next */ sw->path_idx++; /* Wait for dbus reply */ return false; fail: /* Report failure. */ spa_log_info(sw->device->monitor->log, "media codec switch %p: failed", sw); spa_bt_device_emit_codec_switched(sw->device, -ENODEV); spa_bt_device_check_profiles(sw->device, false); sw->profiles = 0; codec_switch_emit_switching(sw->device->monitor); return true; } static void codec_switch_cancel(struct spa_bt_codec_switch *sw) { /* BlueZ does not appear to allow calling dbus_pending_call_cancel on an * active request, so we have to wait for the reply to arrive. */ sw->canceled = true; } static void codec_switch_destroy(struct spa_bt_codec_switch *sw) { unsigned int i; spa_list_remove(&sw->link); cancel_and_unref(&sw->pending); if (sw->paths != NULL) for (i = 0; sw->paths[i].path; ++i) free(sw->paths[i].path); if (sw->timer) spa_loop_utils_destroy_source(sw->device->monitor->loop_utils, sw->timer); free(sw->paths); free(sw); } static void codec_switch_list_process(struct spa_list *list) { struct spa_bt_codec_switch *sw; spa_list_consume(sw, list, link) { if (codec_switch_process(sw)) { codec_switch_destroy(sw); } else { sw->waiting = true; break; } } } static int codec_switch_cmp(const void *a, const void *b) { struct spa_bt_codec_switch *sw = codec_switch_cmp_sw; const struct media_codec *codec = sw->codec; struct spa_bt_monitor *monitor = sw->device->monitor; const struct spa_bt_codec_switch_path *path1 = a; const struct spa_bt_codec_switch_path *path2 = b; struct spa_bt_remote_endpoint *ep1, *ep2; uint32_t flags; ep1 = device_remote_endpoint_find(sw->device, path1->path); ep2 = device_remote_endpoint_find(sw->device, path2->path); if (ep1 != NULL && (ep1->uuid == NULL || ep1->codec != codec->codec_id)) ep1 = NULL; if (ep2 != NULL && (ep2->uuid == NULL || ep2->codec != codec->codec_id)) ep2 = NULL; if (ep1 && ep2 && !spa_streq(ep1->uuid, ep2->uuid)) { ep1 = NULL; ep2 = NULL; } if (ep1 == NULL && ep2 == NULL) return 0; else if (ep1 == NULL) return 1; else if (ep2 == NULL) return -1; if (codec->kind == MEDIA_CODEC_BAP) flags = spa_streq(ep1->uuid, SPA_BT_UUID_BAP_SOURCE) ? MEDIA_CODEC_FLAG_SINK : 0; else flags = spa_streq(ep1->uuid, SPA_BT_UUID_A2DP_SOURCE) ? MEDIA_CODEC_FLAG_SINK : 0; return codec->caps_preference_cmp(codec, flags, ep1->capabilities, ep1->capabilities_len, ep2->capabilities, ep2->capabilities_len, &monitor->default_audio_info, &monitor->global_settings); } /* Ensure there's a transport for at least one of the listed codecs */ int spa_bt_device_ensure_media_codec(struct spa_bt_device *device, const struct media_codec * const *codecs, uint32_t profiles) { struct spa_bt_monitor *monitor = device->monitor; struct spa_bt_codec_switch *sw, *sw2; struct spa_bt_remote_endpoint *ep; struct spa_bt_transport *t; const struct media_codec *codec = NULL; size_t i, j, num_eps, res; uint32_t remaining = 0; if (!device->adapter->a2dp_application_registered && !device->adapter->bap_application_registered) { /* Codec switching not supported */ return -ENOTSUP; } for (i = 0; codecs[i] != NULL; ++i) { if (codecs[i]->kind != MEDIA_CODEC_BAP && codecs[i]->kind != MEDIA_CODEC_A2DP) continue; if (spa_bt_device_supports_media_codec(device, codecs[i], device->connected_profiles)) { codec = codecs[i]; break; } } if (!profiles) profiles = device->connected_profiles & (SPA_BT_PROFILE_MEDIA_SOURCE | SPA_BT_PROFILE_MEDIA_SINK); if (!codec) return -EINVAL; /* Check if we already have an enabled transports for the profiles. * However, if there already was a codec switch running, these transports may * disappear soon. In that case, we have to do the full thing. */ if (!has_codec_switch(device)) { uint32_t found_profiles = 0; spa_list_for_each(t, &device->transport_list, device_link) { if (t->media_codec != codec) continue; found_profiles |= t->profile; } if (found_profiles == profiles) { spa_bt_device_emit_codec_switched(device, 0); return 0; } } /* Setup */ sw = calloc(1, sizeof(struct spa_bt_codec_switch)); if (sw == NULL) goto error_errno; sw->codec = codec; sw->device = device; sw->profiles = profiles; spa_list_append(&device->codec_switch_list, &sw->link); /* Find endpoints */ num_eps = 0; spa_list_for_each(ep, &device->remote_endpoint_list, device_link) ++num_eps; sw->paths = calloc(num_eps + 1, sizeof(*sw->paths)); if (!sw->paths) goto error_errno; sw->path_idx = 0; i = 0; spa_list_for_each(ep, &device->remote_endpoint_list, device_link) { sw->paths[i].path = strdup(ep->path); if (sw->paths[i].path == NULL) goto error_errno; ++i; } /* Sort in codec preference order */ if (codec->caps_preference_cmp) { codec_switch_cmp_sw = sw; qsort(sw->paths, num_eps, sizeof(*sw->paths), codec_switch_cmp); } /* Pick at most one source and one sink endpoint, if corresponding profiles are * set */ remaining = profiles; for (i = 0, j = 0; i < num_eps; ++i) { struct spa_bt_remote_endpoint *ep; bool sink; uint32_t mask; ep = remote_endpoint_find(monitor, sw->paths[i].path); if (!codec_switch_check_endpoint(ep, codec, &sink, NULL)) continue; mask = sink ? SPA_BT_PROFILE_MEDIA_SOURCE : SPA_BT_PROFILE_MEDIA_SINK; if (!(remaining & mask)) continue; SPA_FLAG_CLEAR(remaining, mask); spa_log_debug(monitor->log, "media codec switch %p: select endpoint %s for codec %s", sw, sw->paths[i].path, codec->name); SPA_SWAP(sw->paths[j], sw->paths[i]); ++j; } if (profiles & SPA_BT_PROFILE_BAP_AUDIO) { /* Active unselected endpoints must be cleared */ for (i = j; i < num_eps; ++i) { bool active_ep = false; spa_list_for_each(t, &device->transport_list, device_link) { if (spa_streq(t->remote_endpoint_path, sw->paths[i].path)) { active_ep = true; break; } } if (!active_ep) continue; spa_log_debug(monitor->log, "media codec switch %p: select endpoint %s to be cleared", sw, sw->paths[i].path); SPA_SWAP(sw->paths[j], sw->paths[i]); sw->paths[j].clear = true; ++j; } /* Reverse order so that clears come first */ for (i = 0; i < j/2; ++i) SPA_SWAP(sw->paths[i], sw->paths[j - 1 - i]); } for (; j < num_eps; ++j) { free(sw->paths[j].path); spa_zero(sw->paths[j]); } if (!sw->paths[0].path || remaining) { spa_log_error(monitor->log, "media codec switch %p: no valid profile 0x%x endpoints for codec %s", sw, profiles, codec->name); errno = EINVAL; goto error_errno; } /* Cancel other codec switches */ spa_list_for_each(sw2, &device->codec_switch_list, link) if (sw2 != sw) codec_switch_cancel(sw2); codec_switch_list_process(&device->codec_switch_list); return 0; error_errno: res = -errno; if (sw) codec_switch_destroy(sw); return res; } int spa_bt_device_ensure_hfp_codec(struct spa_bt_device *device, const struct media_codec *codec) { struct spa_bt_monitor *monitor = device->monitor; if (!codec || codec->kind != MEDIA_CODEC_HFP) return -EINVAL; return spa_bt_backend_ensure_codec(monitor->backend, device, codec->codec_id); } static DBusHandlerResult endpoint_set_configuration(DBusConnection *conn, const char *path, DBusMessage *m, void *userdata) { struct spa_bt_monitor *monitor = userdata; const char *transport_path, *endpoint; DBusMessageIter it[2]; spa_autoptr(DBusMessage) r = NULL; struct spa_bt_transport *transport; const struct media_codec *codec; int profile; bool sink; if (!dbus_message_has_signature(m, "oa{sv}")) { spa_log_warn(monitor->log, "invalid SetConfiguration() signature"); return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } endpoint = dbus_message_get_path(m); profile = media_endpoint_to_profile(endpoint); codec = media_endpoint_to_codec(monitor, endpoint, &sink, NULL); if (codec == NULL) { spa_log_warn(monitor->log, "unknown SetConfiguration() codec"); return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } dbus_message_iter_init(m, &it[0]); dbus_message_iter_get_basic(&it[0], &transport_path); dbus_message_iter_next(&it[0]); dbus_message_iter_recurse(&it[0], &it[1]); transport = spa_bt_transport_find(monitor, transport_path); if (transport == NULL) { char *tpath = strdup(transport_path); transport = spa_bt_transport_create(monitor, tpath, 0); if (transport == NULL) { free(tpath); return DBUS_HANDLER_RESULT_NEED_MEMORY; } spa_bt_transport_set_implementation(transport, &transport_impl, transport); if (profile & SPA_BT_PROFILE_A2DP_SOURCE) { transport->volumes[SPA_BT_VOLUME_ID_RX].volume = DEFAULT_AG_VOLUME; transport->volumes[SPA_BT_VOLUME_ID_TX].volume = DEFAULT_AG_VOLUME; } else { transport->volumes[SPA_BT_VOLUME_ID_RX].volume = DEFAULT_RX_VOLUME; transport->volumes[SPA_BT_VOLUME_ID_TX].volume = DEFAULT_TX_VOLUME; } } for (int i = 0; i < SPA_BT_VOLUME_ID_TERM; ++i) { transport->volumes[i].hw_volume = SPA_BT_VOLUME_INVALID; if (profile & SPA_BT_PROFILE_BAP_AUDIO) transport->volumes[i].hw_volume_max = SPA_BT_VOLUME_BAP_MAX; else transport->volumes[i].hw_volume_max = SPA_BT_VOLUME_A2DP_MAX; } free(transport->endpoint_path); transport->endpoint_path = strdup(endpoint); transport->profile = profile; transport->media_codec = codec; transport_update_props(transport, &it[1], NULL); if (transport->device == NULL || transport->device->adapter == NULL) { spa_log_warn(monitor->log, "no device found for transport"); return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } /* If multiple codecs share the endpoint, pick the one we wanted */ transport->media_codec = codec = media_endpoint_to_codec(monitor, endpoint, &sink, transport->device->preferred_codec); spa_assert(codec != NULL); spa_log_debug(monitor->log, "%p: %s codec:%s", monitor, path, codec ? codec->name : ""); spa_bt_device_update_last_bluez_action_time(transport->device); if (profile & SPA_BT_PROFILE_A2DP_SOURCE) { /* PW is the rendering device so it's responsible for reporting hardware volume. */ transport->volumes[SPA_BT_VOLUME_ID_RX].active = true; } else if (profile & SPA_BT_PROFILE_A2DP_SINK) { transport->volumes[SPA_BT_VOLUME_ID_TX].active |= transport->device->a2dp_volume_active[SPA_BT_VOLUME_ID_TX]; } if (codec->validate_config) { struct spa_audio_info info; if (codec->validate_config(codec, sink ? MEDIA_CODEC_FLAG_SINK : 0, transport->configuration, transport->configuration_len, &info) < 0) { spa_log_error(monitor->log, "invalid transport configuration"); return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } if (info.info.raw.channels > MAX_CHANNELS) { spa_log_error(monitor->log, "too many channels in transport"); return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } transport->n_channels = info.info.raw.channels; memcpy(transport->channels, info.info.raw.position, transport->n_channels * sizeof(uint32_t)); } else { transport->n_channels = 2; transport->channels[0] = SPA_AUDIO_CHANNEL_FL; transport->channels[1] = SPA_AUDIO_CHANNEL_FR; } spa_log_info(monitor->log, "%p: %s validate conf channels:%d", monitor, path, transport->n_channels); spa_bt_device_add_profile(transport->device, transport->profile); spa_bt_device_connect_profile(transport->device, transport->profile); /* Sync initial volumes */ transport_sync_volume(transport); if ((r = dbus_message_new_method_return(m)) == NULL) return DBUS_HANDLER_RESULT_NEED_MEMORY; if (!dbus_connection_send(conn, r, NULL)) return DBUS_HANDLER_RESULT_NEED_MEMORY; return DBUS_HANDLER_RESULT_HANDLED; } static DBusHandlerResult endpoint_clear_configuration(DBusConnection *conn, DBusMessage *m, void *userdata) { struct spa_bt_monitor *monitor = userdata; spa_auto(DBusError) err = DBUS_ERROR_INIT; spa_autoptr(DBusMessage) r = NULL; const char *transport_path; struct spa_bt_transport *transport; if (!dbus_message_get_args(m, &err, DBUS_TYPE_OBJECT_PATH, &transport_path, DBUS_TYPE_INVALID)) { spa_log_warn(monitor->log, "Bad ClearConfiguration method call: %s", err.message); return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } transport = spa_bt_transport_find(monitor, transport_path); if (transport != NULL) { struct spa_bt_device *device = transport->device; spa_log_debug(monitor->log, "transport %p: free %s", transport, transport->path); spa_bt_transport_free(transport); if (device != NULL) spa_bt_device_check_profiles(device, false); } if ((r = dbus_message_new_method_return(m)) == NULL) return DBUS_HANDLER_RESULT_NEED_MEMORY; if (!dbus_connection_send(conn, r, NULL)) return DBUS_HANDLER_RESULT_NEED_MEMORY; return DBUS_HANDLER_RESULT_HANDLED; } static DBusHandlerResult endpoint_release(DBusConnection *conn, DBusMessage *m, void *userdata) { if (!reply_with_error(conn, m, BLUEZ_MEDIA_ENDPOINT_INTERFACE ".Error.NotImplemented", "Method not implemented")) return DBUS_HANDLER_RESULT_NEED_MEMORY; return DBUS_HANDLER_RESULT_HANDLED; } static DBusHandlerResult endpoint_handler(DBusConnection *c, DBusMessage *m, void *userdata) { struct spa_bt_monitor *monitor = userdata; const char *path, *interface, *member; DBusHandlerResult res; path = dbus_message_get_path(m); interface = dbus_message_get_interface(m); member = dbus_message_get_member(m); spa_log_debug(monitor->log, "dbus: path=%s, interface=%s, member=%s", path, interface, member); if (dbus_message_is_method_call(m, "org.freedesktop.DBus.Introspectable", "Introspect")) { const char *xml = ENDPOINT_INTROSPECT_XML; spa_autoptr(DBusMessage) r = NULL; if ((r = dbus_message_new_method_return(m)) == NULL) return DBUS_HANDLER_RESULT_NEED_MEMORY; if (!dbus_message_append_args(r, DBUS_TYPE_STRING, &xml, DBUS_TYPE_INVALID)) return DBUS_HANDLER_RESULT_NEED_MEMORY; if (!dbus_connection_send(monitor->conn, r, NULL)) return DBUS_HANDLER_RESULT_NEED_MEMORY; res = DBUS_HANDLER_RESULT_HANDLED; } else if (dbus_message_is_method_call(m, BLUEZ_MEDIA_ENDPOINT_INTERFACE, "SetConfiguration")) res = endpoint_set_configuration(c, path, m, userdata); else if (dbus_message_is_method_call(m, BLUEZ_MEDIA_ENDPOINT_INTERFACE, "SelectConfiguration")) res = endpoint_select_configuration(c, m, userdata); else if (dbus_message_is_method_call(m, BLUEZ_MEDIA_ENDPOINT_INTERFACE, "SelectProperties")) res = endpoint_select_properties(c, m, userdata); else if (dbus_message_is_method_call(m, BLUEZ_MEDIA_ENDPOINT_INTERFACE, "ClearConfiguration")) res = endpoint_clear_configuration(c, m, userdata); else if (dbus_message_is_method_call(m, BLUEZ_MEDIA_ENDPOINT_INTERFACE, "Release")) res = endpoint_release(c, m, userdata); else res = DBUS_HANDLER_RESULT_NOT_YET_HANDLED; return res; } static void bluez_register_endpoint_legacy_reply(DBusPendingCall *pending, void *user_data) { struct spa_bt_adapter *adapter = user_data; struct spa_bt_monitor *monitor = adapter->monitor; spa_autoptr(DBusMessage) r = steal_reply_and_unref(&pending); if (r == NULL) return; if (dbus_message_is_error(r, DBUS_ERROR_UNKNOWN_METHOD)) { spa_log_warn(monitor->log, "BlueZ D-Bus ObjectManager not available"); return; } if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) { spa_log_error(monitor->log, "RegisterEndpoint() failed: %s", dbus_message_get_error_name(r)); return; } adapter->legacy_endpoints_registered = true; } static void append_basic_variant_dict_entry(DBusMessageIter *dict, const char* key, int variant_type_int, const char* variant_type_str, void* variant) { DBusMessageIter dict_entry_it, variant_it; dbus_message_iter_open_container(dict, DBUS_TYPE_DICT_ENTRY, NULL, &dict_entry_it); dbus_message_iter_append_basic(&dict_entry_it, DBUS_TYPE_STRING, &key); dbus_message_iter_open_container(&dict_entry_it, DBUS_TYPE_VARIANT, variant_type_str, &variant_it); dbus_message_iter_append_basic(&variant_it, variant_type_int, variant); dbus_message_iter_close_container(&dict_entry_it, &variant_it); dbus_message_iter_close_container(dict, &dict_entry_it); } static void append_basic_array_variant_dict_entry(DBusMessageIter *dict, const char* key, const char* variant_type_str, const char* array_type_str, int array_type_int, void* data, int data_size) { DBusMessageIter dict_entry_it, variant_it, array_it; dbus_message_iter_open_container(dict, DBUS_TYPE_DICT_ENTRY, NULL, &dict_entry_it); dbus_message_iter_append_basic(&dict_entry_it, DBUS_TYPE_STRING, &key); dbus_message_iter_open_container(&dict_entry_it, DBUS_TYPE_VARIANT, variant_type_str, &variant_it); dbus_message_iter_open_container(&variant_it, DBUS_TYPE_ARRAY, array_type_str, &array_it); dbus_message_iter_append_fixed_array (&array_it, array_type_int, &data, data_size); dbus_message_iter_close_container(&variant_it, &array_it); dbus_message_iter_close_container(&dict_entry_it, &variant_it); dbus_message_iter_close_container(dict, &dict_entry_it); } static int bluez_register_endpoint_legacy(struct spa_bt_adapter *adapter, enum spa_bt_media_direction direction, const char *uuid, const struct media_codec *codec) { struct spa_bt_monitor *monitor = adapter->monitor; const char *path = adapter->path; spa_autofree char *object_path = NULL; spa_autoptr(DBusMessage) m = NULL; DBusMessageIter object_it, dict_it; uint8_t caps[A2DP_MAX_CAPS_SIZE]; int ret, caps_size; uint16_t codec_id = codec->codec_id; bool sink = (direction == SPA_BT_MEDIA_SINK); spa_assert(codec->fill_caps); ret = media_codec_to_endpoint(codec, direction, &object_path); if (ret < 0) return ret; ret = caps_size = codec->fill_caps(codec, sink ? MEDIA_CODEC_FLAG_SINK : 0, &monitor->global_settings, caps); if (ret < 0) return ret; m = dbus_message_new_method_call(BLUEZ_SERVICE, path, BLUEZ_MEDIA_INTERFACE, "RegisterEndpoint"); if (m == NULL) return -EIO; dbus_message_iter_init_append(m, &object_it); dbus_message_iter_append_basic(&object_it, DBUS_TYPE_OBJECT_PATH, &object_path); dbus_message_iter_open_container(&object_it, DBUS_TYPE_ARRAY, "{sv}", &dict_it); append_basic_variant_dict_entry(&dict_it,"UUID", DBUS_TYPE_STRING, "s", &uuid); append_basic_variant_dict_entry(&dict_it, "Codec", DBUS_TYPE_BYTE, "y", &codec_id); append_basic_array_variant_dict_entry(&dict_it, "Capabilities", "ay", "y", DBUS_TYPE_BYTE, caps, caps_size); dbus_message_iter_close_container(&object_it, &dict_it); if (!send_with_reply(monitor->conn, m, bluez_register_endpoint_legacy_reply, adapter)) return -EIO; return 0; } static int adapter_register_endpoints_legacy(struct spa_bt_adapter *a) { struct spa_bt_monitor *monitor = a->monitor; const struct media_codec * const * const media_codecs = monitor->media_codecs; int i; int err = 0; bool registered = false; if (a->legacy_endpoints_registered) return err; /* The legacy bluez5 api doesn't support codec switching * It doesn't make sense to register codecs other than SBC * as bluez5 will probably use SBC anyway and we have no control over it * let's incentivize users to upgrade their bluez5 daemon * if they want proper media codec support * */ spa_log_warn(monitor->log, "Using legacy bluez5 API for A2DP - only SBC will be supported. " "Please upgrade bluez5."); for (i = 0; media_codecs[i]; i++) { const struct media_codec *codec = media_codecs[i]; if (codec->id != SPA_BLUETOOTH_AUDIO_CODEC_SBC) continue; if (endpoint_should_be_registered(monitor, codec, SPA_BT_MEDIA_SOURCE)) { if ((err = bluez_register_endpoint_legacy(a, SPA_BT_MEDIA_SOURCE, SPA_BT_UUID_A2DP_SOURCE, codec))) goto out; } if (endpoint_should_be_registered(monitor, codec, SPA_BT_MEDIA_SINK)) { if ((err = bluez_register_endpoint_legacy(a, SPA_BT_MEDIA_SINK, SPA_BT_UUID_A2DP_SINK, codec))) goto out; } registered = true; break; } if (!registered) { /* Should never happen as SBC support is always enabled */ spa_log_error(monitor->log, "Broken PipeWire build - unable to locate SBC codec"); err = -ENOSYS; } out: if (err) { spa_log_error(monitor->log, "Failed to register bluez5 endpoints"); } return err; } static void append_media_object(struct spa_bt_monitor *monitor, DBusMessageIter *iter, const char *endpoint, const char *uuid, uint8_t codec_id, uint8_t *caps, size_t caps_size) { const char *interface_name = BLUEZ_MEDIA_ENDPOINT_INTERFACE; DBusMessageIter object, array, entry, dict; dbus_message_iter_open_container(iter, DBUS_TYPE_DICT_ENTRY, NULL, &object); dbus_message_iter_append_basic(&object, DBUS_TYPE_OBJECT_PATH, &endpoint); dbus_message_iter_open_container(&object, DBUS_TYPE_ARRAY, "{sa{sv}}", &array); dbus_message_iter_open_container(&array, DBUS_TYPE_DICT_ENTRY, NULL, &entry); dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &interface_name); dbus_message_iter_open_container(&entry, DBUS_TYPE_ARRAY, "{sv}", &dict); append_basic_variant_dict_entry(&dict, "UUID", DBUS_TYPE_STRING, "s", &uuid); append_basic_variant_dict_entry(&dict, "Codec", DBUS_TYPE_BYTE, "y", &codec_id); append_basic_array_variant_dict_entry(&dict, "Capabilities", "ay", "y", DBUS_TYPE_BYTE, caps, caps_size); if (spa_bt_profile_from_uuid(uuid) & (SPA_BT_PROFILE_A2DP_SINK | SPA_BT_PROFILE_A2DP_SOURCE)) { dbus_bool_t delay_reporting = TRUE; append_basic_variant_dict_entry(&dict, "DelayReporting", DBUS_TYPE_BOOLEAN, "b", &delay_reporting); } if (spa_bt_profile_from_uuid(uuid) & (SPA_BT_PROFILE_BAP_SINK | SPA_BT_PROFILE_BAP_SOURCE)) { dbus_uint32_t locations; dbus_uint16_t supported_contexts, contexts; if (spa_bt_profile_from_uuid(uuid) & SPA_BT_PROFILE_BAP_SINK) { locations = monitor->bap_sink_locations; contexts = monitor->bap_sink_contexts; supported_contexts = monitor->bap_sink_supported_contexts; } else { locations = monitor->bap_source_locations; contexts = monitor->bap_source_contexts; supported_contexts = monitor->bap_source_supported_contexts; } spa_log_debug(monitor->log, "BAP endpoint %s locations:0x%x contexts:0x%x supported-contexs:0x%x", endpoint, locations, contexts, supported_contexts); append_basic_variant_dict_entry(&dict, "Locations", DBUS_TYPE_UINT32, "u", &locations); append_basic_variant_dict_entry(&dict, "Context", DBUS_TYPE_UINT16, "q", &contexts); append_basic_variant_dict_entry(&dict, "SupportedContext", DBUS_TYPE_UINT16, "q", &supported_contexts); } dbus_message_iter_close_container(&entry, &dict); dbus_message_iter_close_container(&array, &entry); dbus_message_iter_close_container(&object, &array); dbus_message_iter_close_container(iter, &object); } static DBusHandlerResult object_manager_handler(DBusConnection *c, DBusMessage *m, void *user_data, bool is_bap) { struct spa_bt_monitor *monitor = user_data; const struct media_codec * const * const media_codecs = monitor->media_codecs; const char *path, *interface, *member; DBusMessageIter iter, array; DBusHandlerResult res; int i; path = dbus_message_get_path(m); interface = dbus_message_get_interface(m); member = dbus_message_get_member(m); spa_log_debug(monitor->log, "dbus: path=%s, interface=%s, member=%s", path, interface, member); if (dbus_message_is_method_call(m, "org.freedesktop.DBus.Introspectable", "Introspect")) { const char *xml = OBJECT_MANAGER_INTROSPECT_XML; spa_autoptr(DBusMessage) r = NULL; if ((r = dbus_message_new_method_return(m)) == NULL) return DBUS_HANDLER_RESULT_NEED_MEMORY; if (!dbus_message_append_args(r, DBUS_TYPE_STRING, &xml, DBUS_TYPE_INVALID)) return DBUS_HANDLER_RESULT_NEED_MEMORY; if (!dbus_connection_send(monitor->conn, r, NULL)) return DBUS_HANDLER_RESULT_NEED_MEMORY; res = DBUS_HANDLER_RESULT_HANDLED; } else if (dbus_message_is_method_call(m, "org.freedesktop.DBus.ObjectManager", "GetManagedObjects")) { spa_autoptr(DBusMessage) r = NULL; struct spa_bt_adapter *a; bool register_bcast = false; if ((r = dbus_message_new_method_return(m)) == NULL) return DBUS_HANDLER_RESULT_NEED_MEMORY; dbus_message_iter_init_append(r, &iter); dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{oa{sa{sv}}}", &array); /* * Verify if an adapter exists that supports bap broadcast. * If this adapter exists will register the broadcast endpoint. */ spa_list_for_each(a, &monitor->adapter_list, link) { if (a->le_audio_bcast_supported) { register_bcast = true; break; } } for (i = 0; media_codecs[i]; i++) { const struct media_codec *codec = media_codecs[i]; uint8_t caps[A2DP_MAX_CAPS_SIZE]; int caps_size, ret; uint16_t codec_id = codec->codec_id; enum media_codec_kind kind = is_bap ? MEDIA_CODEC_BAP : MEDIA_CODEC_A2DP; if (codec->kind != kind) continue; if (!is_media_codec_enabled(monitor, codec)) continue; if (endpoint_should_be_registered(monitor, codec, SPA_BT_MEDIA_SINK)) { caps_size = codec->fill_caps(codec, MEDIA_CODEC_FLAG_SINK, &monitor->global_settings, caps); if (caps_size < 0) continue; spa_autofree char *endpoint = NULL; ret = media_codec_to_endpoint(codec, SPA_BT_MEDIA_SINK, &endpoint); if (ret == 0) { spa_log_info(monitor->log, "register media sink codec %s: %s", media_codecs[i]->name, endpoint); append_media_object(monitor, &array, endpoint, is_bap ? SPA_BT_UUID_BAP_SINK : SPA_BT_UUID_A2DP_SINK, codec_id, caps, caps_size); } } if (endpoint_should_be_registered(monitor, codec, SPA_BT_MEDIA_SOURCE)) { caps_size = codec->fill_caps(codec, 0, &monitor->global_settings, caps); if (caps_size < 0) continue; spa_autofree char *endpoint = NULL; ret = media_codec_to_endpoint(codec, SPA_BT_MEDIA_SOURCE, &endpoint); if (ret == 0) { spa_log_info(monitor->log, "register media source codec %s: %s", media_codecs[i]->name, endpoint); append_media_object(monitor, &array, endpoint, is_bap ? SPA_BT_UUID_BAP_SOURCE : SPA_BT_UUID_A2DP_SOURCE, codec_id, caps, caps_size); } } if (is_bap && register_bcast) { if (endpoint_should_be_registered(monitor, codec, SPA_BT_MEDIA_SOURCE_BROADCAST)) { caps_size = codec->fill_caps(codec, 0, &monitor->global_settings, caps); if (caps_size < 0) continue; spa_autofree char *endpoint = NULL; ret = media_codec_to_endpoint(codec, SPA_BT_MEDIA_SOURCE_BROADCAST, &endpoint); if (ret == 0) { spa_log_info(monitor->log, "register media source codec %s: %s", media_codecs[i]->name, endpoint); append_media_object(monitor, &array, endpoint, SPA_BT_UUID_BAP_BROADCAST_SOURCE, codec_id, caps, caps_size); } } if (endpoint_should_be_registered(monitor, codec, SPA_BT_MEDIA_SINK_BROADCAST)) { caps_size = codec->fill_caps(codec, MEDIA_CODEC_FLAG_SINK, &monitor->global_settings, caps); if (caps_size < 0) continue; spa_autofree char *endpoint = NULL; ret = media_codec_to_endpoint(codec, SPA_BT_MEDIA_SINK_BROADCAST, &endpoint); if (ret == 0) { spa_log_info(monitor->log, "register broadcast media sink codec %s: %s", media_codecs[i]->name, endpoint); append_media_object(monitor, &array, endpoint, SPA_BT_UUID_BAP_BROADCAST_SINK, codec_id, caps, caps_size); } } } } dbus_message_iter_close_container(&iter, &array); if (!dbus_connection_send(monitor->conn, r, NULL)) return DBUS_HANDLER_RESULT_NEED_MEMORY; res = DBUS_HANDLER_RESULT_HANDLED; } else res = DBUS_HANDLER_RESULT_NOT_YET_HANDLED; return res; } static DBusHandlerResult object_manager_handler_a2dp(DBusConnection *c, DBusMessage *m, void *user_data) { return object_manager_handler(c, m, user_data, false); } static DBusHandlerResult object_manager_handler_bap(DBusConnection *c, DBusMessage *m, void *user_data) { return object_manager_handler(c, m, user_data, true); } static void bluez_register_application_a2dp_reply(DBusPendingCall *pending, void *user_data) { struct spa_bt_adapter *adapter = user_data; struct spa_bt_monitor *monitor = adapter->monitor; bool fallback = true; spa_autoptr(DBusMessage) r = steal_reply_and_unref(&pending); if (r == NULL) return; if (dbus_message_is_error(r, BLUEZ_ERROR_NOT_SUPPORTED)) { spa_log_warn(monitor->log, "Registering media applications for adapter %s is disabled in bluez5", adapter->path); goto finish; } if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) { spa_log_error(monitor->log, "RegisterApplication() failed: %s", dbus_message_get_error_name(r)); goto finish; } fallback = false; adapter->a2dp_application_registered = true; finish: if (fallback) adapter_register_endpoints_legacy(adapter); } static void bluez_register_application_bap_reply(DBusPendingCall *pending, void *user_data) { struct spa_bt_adapter *adapter = user_data; struct spa_bt_monitor *monitor = adapter->monitor; spa_autoptr(DBusMessage) r = steal_reply_and_unref(&pending); if (r == NULL) return; if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) { spa_log_error(monitor->log, "RegisterApplication() failed: %s", dbus_message_get_error_name(r)); return; } adapter->bap_application_registered = true; } static int register_media_endpoint(struct spa_bt_monitor *monitor, const struct media_codec *codec, enum spa_bt_media_direction direction) { static const DBusObjectPathVTable vtable_endpoint = { .message_function = endpoint_handler, }; if (!endpoint_should_be_registered(monitor, codec, direction)) return 0; spa_autofree char *object_path = NULL; int ret = media_codec_to_endpoint(codec, direction, &object_path); if (ret < 0) return ret; spa_log_info(monitor->log, "Registering DBus media endpoint: %s", object_path); if (!dbus_connection_register_object_path(monitor->conn, object_path, &vtable_endpoint, monitor)) return -EIO; return 0; } static int register_media_application(struct spa_bt_monitor * monitor) { const struct media_codec * const * const media_codecs = monitor->media_codecs; static const DBusObjectPathVTable vtable_object_manager_a2dp = { .message_function = object_manager_handler_a2dp, }; static const DBusObjectPathVTable vtable_object_manager_bap = { .message_function = object_manager_handler_bap, }; spa_log_info(monitor->log, "Registering DBus media object manager: %s", A2DP_OBJECT_MANAGER_PATH); if (!dbus_connection_register_object_path(monitor->conn, A2DP_OBJECT_MANAGER_PATH, &vtable_object_manager_a2dp, monitor)) return -EIO; spa_log_info(monitor->log, "Registering DBus media object manager: %s", BAP_OBJECT_MANAGER_PATH); if (!dbus_connection_register_object_path(monitor->conn, BAP_OBJECT_MANAGER_PATH, &vtable_object_manager_bap, monitor)) return -EIO; for (int i = 0; media_codecs[i]; i++) { const struct media_codec *codec = media_codecs[i]; register_media_endpoint(monitor, codec, SPA_BT_MEDIA_SOURCE); register_media_endpoint(monitor, codec, SPA_BT_MEDIA_SINK); if (codec->kind == MEDIA_CODEC_BAP) { register_media_endpoint(monitor, codec, SPA_BT_MEDIA_SOURCE_BROADCAST); register_media_endpoint(monitor, codec, SPA_BT_MEDIA_SINK_BROADCAST); } } return 0; } static void unregister_media_endpoint(struct spa_bt_monitor *monitor, const struct media_codec *codec, enum spa_bt_media_direction direction) { if (!endpoint_should_be_registered(monitor, codec, direction)) return; spa_autofree char *object_path = NULL; int ret = media_codec_to_endpoint(codec, direction, &object_path); if (ret < 0) return; spa_log_info(monitor->log, "unregistering endpoint: %s", object_path); if (!dbus_connection_unregister_object_path(monitor->conn, object_path)) spa_log_warn(monitor->log, "failed to unregister %s\n", object_path); } static void unregister_media_application(struct spa_bt_monitor * monitor) { const struct media_codec * const * const media_codecs = monitor->media_codecs; for (int i = 0; media_codecs[i]; i++) { const struct media_codec *codec = media_codecs[i]; unregister_media_endpoint(monitor, codec, SPA_BT_MEDIA_SOURCE); unregister_media_endpoint(monitor, codec, SPA_BT_MEDIA_SINK); if (codec->kind == MEDIA_CODEC_BAP) { unregister_media_endpoint(monitor, codec, SPA_BT_MEDIA_SOURCE_BROADCAST); unregister_media_endpoint(monitor, codec, SPA_BT_MEDIA_SINK_BROADCAST); } } dbus_connection_unregister_object_path(monitor->conn, BAP_OBJECT_MANAGER_PATH); dbus_connection_unregister_object_path(monitor->conn, A2DP_OBJECT_MANAGER_PATH); } static bool have_codec_endpoints(struct spa_bt_monitor *monitor, bool bap) { const struct media_codec * const * const media_codecs = monitor->media_codecs; int i; for (i = 0; media_codecs[i]; i++) { const struct media_codec *codec = media_codecs[i]; enum media_codec_kind kind = bap ? MEDIA_CODEC_BAP : MEDIA_CODEC_A2DP; if (codec->kind != kind) continue; if (endpoint_should_be_registered(monitor, codec, SPA_BT_MEDIA_SINK) || endpoint_should_be_registered(monitor, codec, SPA_BT_MEDIA_SOURCE) || endpoint_should_be_registered(monitor, codec, SPA_BT_MEDIA_SOURCE_BROADCAST) || endpoint_should_be_registered(monitor, codec, SPA_BT_MEDIA_SINK_BROADCAST)) return true; } return false; } static int adapter_register_application(struct spa_bt_adapter *a, bool bap) { const char *object_manager_path = bap ? BAP_OBJECT_MANAGER_PATH : A2DP_OBJECT_MANAGER_PATH; struct spa_bt_monitor *monitor = a->monitor; const char *ep_type_name = (bap ? "LE Audio" : "A2DP"); spa_autoptr(DBusMessage) m = NULL; DBusMessageIter i, d; if (bap && a->bap_application_registered) return 0; if (!bap && a->a2dp_application_registered) return 0; if ((bap && !a->le_audio_supported) && (bap && !a->le_audio_bcast_supported)) { spa_log_info(monitor->log, "Adapter %s indicates LE Audio unsupported: not registering application", a->path); return -ENOTSUP; } if (!have_codec_endpoints(monitor, bap)) { spa_log_warn(monitor->log, "No available %s codecs to register on adapter %s", ep_type_name, a->path); return -ENOENT; } spa_log_debug(monitor->log, "Registering bluez5 %s media application on adapter %s", ep_type_name, a->path); m = dbus_message_new_method_call(BLUEZ_SERVICE, a->path, BLUEZ_MEDIA_INTERFACE, "RegisterApplication"); if (m == NULL) return -EIO; dbus_message_iter_init_append(m, &i); dbus_message_iter_append_basic(&i, DBUS_TYPE_OBJECT_PATH, &object_manager_path); dbus_message_iter_open_container(&i, DBUS_TYPE_ARRAY, "{sv}", &d); dbus_message_iter_close_container(&i, &d); if (!send_with_reply(monitor->conn, m, bap ? bluez_register_application_bap_reply : bluez_register_application_a2dp_reply, a)) return -EIO; return 0; } static int switch_backend(struct spa_bt_monitor *monitor, struct spa_bt_backend *backend) { int res; size_t i; spa_return_val_if_fail(backend != NULL, -EINVAL); if (!backend->available) return -ENODEV; for (i = 0; i < SPA_N_ELEMENTS(monitor->backends); ++i) { struct spa_bt_backend *b = monitor->backends[i]; if (backend != b && b && b->available && b->exclusive) spa_log_warn(monitor->log, "%s running, but not configured as HFP/HSP backend: " "it may interfere with HFP/HSP functionality.", b->name); } if (monitor->backend == backend) return 0; spa_log_info(monitor->log, "Switching to HFP/HSP backend %s", backend->name); spa_bt_backend_unregister_profiles(monitor->backend); if ((res = spa_bt_backend_register_profiles(backend)) < 0) { monitor->backend = NULL; return res; } monitor->backend = backend; return 0; } static void reselect_backend(struct spa_bt_monitor *monitor, bool silent) { struct spa_bt_backend *backend; size_t i; spa_log_debug(monitor->log, "re-selecting HFP/HSP backend"); if (monitor->backend_selection == BACKEND_NONE) { spa_bt_backend_unregister_profiles(monitor->backend); monitor->backend = NULL; return; } else if (monitor->backend_selection == BACKEND_ANY) { for (i = 0; i < SPA_N_ELEMENTS(monitor->backends); ++i) { backend = monitor->backends[i]; if (backend && switch_backend(monitor, backend) == 0) return; } } else { backend = monitor->backends[monitor->backend_selection]; if (backend && switch_backend(monitor, backend) == 0) return; } spa_bt_backend_unregister_profiles(monitor->backend); monitor->backend = NULL; if (!silent) spa_log_error(monitor->log, "Failed to start HFP/HSP backend %s", backend ? backend->name : "none"); } static void configure_bis(struct spa_bt_monitor *monitor, const struct media_codec *codec, DBusConnection *conn, const char *object_path, const char *interface_name, struct spa_bt_big *big, struct spa_bt_bis *bis, const char *local_endpoint) { DBusMessageIter iter, entry, variant, qos_dict; spa_autoptr(DBusMessage) msg = NULL; DBusMessageIter dict; int bis_id = 0xFF; uint8_t caps [CC_MAX_LEN]; uint8_t metadata [METADATA_MAX_LEN]; uint8_t caps_size, metadata_size = 0; struct bap_codec_qos qos; struct spa_bt_metadata *metadata_entry; struct spa_dict settings; struct spa_dict_item setting_items[2]; char channel_allocation[64] = {0}; int mse = 0; int options = 0; int skip = 0; int sync_cte_type = 0; int sync_timeout = 2000; int timeout = 2000; int ret; /* Configure each BIS from a BIG */ spa_list_for_each(metadata_entry, &bis->metadata_list, link) { if ((metadata_size + metadata_entry->length + 1) > METADATA_MAX_LEN) { spa_log_warn(monitor->log, "Metadata configured for the BIS exceeds the maximum metadata size"); return; } metadata[metadata_size] = (uint8_t)metadata_entry->length; metadata_size++; metadata[metadata_size] = (uint8_t)metadata_entry->type; metadata_size++; memcpy(&metadata[metadata_size], metadata_entry->value, metadata_entry->length - 1); metadata_size += metadata_entry->length - 1; } spa_log_debug(monitor->log, "bis->channel_allocation %d", bis->channel_allocation); if (bis->channel_allocation) spa_scnprintf(channel_allocation, sizeof(channel_allocation), "%"PRIu32, bis->channel_allocation); setting_items[0] = SPA_DICT_ITEM_INIT("channel_allocation", channel_allocation); setting_items[1] = SPA_DICT_ITEM_INIT("preset", bis->qos_preset); settings = SPA_DICT_INIT(setting_items, 2); caps_size = sizeof(caps); ret = codec->get_bis_config(codec, caps, &caps_size, &settings, &qos); if (ret < 0) { spa_log_warn(monitor->log, "Getting BIS config failed"); return; } msg = dbus_message_new_method_call(BLUEZ_SERVICE, object_path, interface_name, "SetConfiguration"); dbus_message_iter_init_append(msg, &iter); dbus_message_iter_append_basic(&iter, DBUS_TYPE_OBJECT_PATH, &local_endpoint); dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &dict); append_basic_array_variant_dict_entry(&dict, "Capabilities", "ay", "y", DBUS_TYPE_BYTE, caps, caps_size); append_basic_array_variant_dict_entry(&dict, "Metadata", "ay", "y", DBUS_TYPE_BYTE, metadata, metadata_size); dbus_message_iter_open_container(&dict, DBUS_TYPE_DICT_ENTRY, NULL, &entry); dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &(const char *) { "QoS" }); dbus_message_iter_open_container(&entry, DBUS_TYPE_VARIANT, "a{sv}", &variant); dbus_message_iter_open_container(&variant, DBUS_TYPE_ARRAY, DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &qos_dict); append_basic_variant_dict_entry(&qos_dict, "BIG", DBUS_TYPE_BYTE, "y", &big->big_id); append_basic_variant_dict_entry(&qos_dict, "BIS", DBUS_TYPE_BYTE, "y", &bis_id); /* sync_factor should be >=2 to avoid invalid extended advertising interval value */ if (big->sync_factor < 2) big->sync_factor = 2; append_basic_variant_dict_entry(&qos_dict, "SyncFactor", DBUS_TYPE_BYTE, "y", &big->sync_factor); append_basic_variant_dict_entry(&qos_dict, "Options", DBUS_TYPE_BYTE, "y", &options); append_basic_variant_dict_entry(&qos_dict, "Skip", DBUS_TYPE_UINT16, "q", &skip); append_basic_variant_dict_entry(&qos_dict, "SyncTimeout", DBUS_TYPE_UINT16, "q", &sync_timeout); append_basic_variant_dict_entry(&qos_dict, "SyncCteType", DBUS_TYPE_BYTE, "y", &sync_cte_type); append_basic_variant_dict_entry(&qos_dict, "MSE", DBUS_TYPE_BYTE, "y", &mse); append_basic_variant_dict_entry(&qos_dict, "Timeout", DBUS_TYPE_UINT16, "q", &timeout); append_basic_array_variant_dict_entry(&qos_dict, "BCode", "ay", "y", DBUS_TYPE_BYTE, big->broadcast_code, BROADCAST_CODE_LEN); append_basic_variant_dict_entry(&qos_dict, "Encryption", DBUS_TYPE_BYTE, "y", &big->encryption); append_basic_variant_dict_entry(&qos_dict, "Interval", DBUS_TYPE_UINT32, "u", &qos.interval); append_basic_variant_dict_entry(&qos_dict, "Framing", DBUS_TYPE_BYTE, "y", &qos.framing); append_basic_variant_dict_entry(&qos_dict, "PHY", DBUS_TYPE_BYTE, "y", &qos.phy); append_basic_variant_dict_entry(&qos_dict, "SDU", DBUS_TYPE_UINT16, "q", &qos.sdu); append_basic_variant_dict_entry(&qos_dict, "Retransmissions", DBUS_TYPE_BYTE, "y", &qos.retransmission); append_basic_variant_dict_entry(&qos_dict, "Latency", DBUS_TYPE_UINT16, "q", &qos.latency); append_basic_variant_dict_entry(&qos_dict, "PresentationDelay", DBUS_TYPE_UINT32, "u", &qos.delay); dbus_message_iter_close_container(&variant, &qos_dict); dbus_message_iter_close_container(&entry, &variant); dbus_message_iter_close_container(&dict, &entry); dbus_message_iter_close_container(&iter, &dict); dbus_message_set_no_reply(msg, TRUE); if (!dbus_connection_send(conn, msg, NULL)) { spa_log_error(monitor->log, "sending SetConfiguration failed"); } } static void configure_bcast_source(struct spa_bt_monitor *monitor, const struct media_codec *codec, DBusConnection *conn, const char *object_path, const char *interface_name, const char *local_endpoint) { struct spa_bt_big *big; struct spa_bt_bis *bis; /* Configure each BIS from a BIG */ spa_list_for_each(big, &monitor->bcast_source_config_list, link) { spa_list_for_each(bis, &big->bis_list, link) { configure_bis(monitor, codec, conn, object_path, interface_name, big, bis, local_endpoint); } } } static void interface_added(struct spa_bt_monitor *monitor, DBusConnection *conn, const char *object_path, const char *interface_name, DBusMessageIter *props_iter) { spa_log_debug(monitor->log, "Found object %s, interface %s", object_path, interface_name); if (spa_streq(interface_name, BLUEZ_ADAPTER_INTERFACE) || spa_streq(interface_name, BLUEZ_MEDIA_INTERFACE)) { struct spa_bt_adapter *a; a = adapter_find(monitor, object_path); if (a == NULL) { a = adapter_create(monitor, object_path); if (a == NULL) { spa_log_warn(monitor->log, "can't create adapter: %m"); return; } } if (spa_streq(interface_name, BLUEZ_ADAPTER_INTERFACE)) { adapter_update_props(a, props_iter, NULL); a->has_adapter1_interface = true; } else { adapter_media_update_props(a, props_iter, NULL); a->has_media1_interface = true; } if (a->has_adapter1_interface && a->has_media1_interface) { adapter_register_application(a, false); adapter_register_application(a, true); adapter_register_player(a); adapter_update_devices(a); } } else if (spa_streq(interface_name, BLUEZ_PROFILE_MANAGER_INTERFACE)) { if (monitor->backends[BACKEND_NATIVE]) monitor->backends[BACKEND_NATIVE]->available = true; reselect_backend(monitor, false); } else if (spa_streq(interface_name, BLUEZ_DEVICE_INTERFACE)) { struct spa_bt_device *d; d = spa_bt_device_find(monitor, object_path); if (d == NULL) { d = device_create(monitor, object_path); if (d == NULL) { spa_log_warn(monitor->log, "can't create Bluetooth device %s: %m", object_path); return; } spa_log_info(monitor->log, "Created Bluetooth device %s", object_path); } device_update_props(d, props_iter, NULL); d->reconnect_state = BT_DEVICE_RECONNECT_INIT; if (!device_props_ready(d)) return; device_update_hw_volume_profiles(d); /* Trigger bluez device creation before bluez profile negotiation started so that * profile connection handlers can receive per-device settings during profile negotiation. */ spa_bt_device_add_profile(d, SPA_BT_PROFILE_NULL); } else if (spa_streq(interface_name, BLUEZ_DEVICE_SET_INTERFACE)) { device_set_update_props(monitor, object_path, props_iter, NULL); } else if (spa_streq(interface_name, BLUEZ_MEDIA_ENDPOINT_INTERFACE)) { struct spa_bt_remote_endpoint *ep; struct spa_bt_device *d; ep = remote_endpoint_find(monitor, object_path); if (ep == NULL) { ep = remote_endpoint_create(monitor, object_path); if (ep == NULL) { spa_log_warn(monitor->log, "can't create Bluetooth remote endpoint %s: %m", object_path); return; } } remote_endpoint_update_props(ep, props_iter, NULL); d = ep->device; if (d) spa_bt_device_emit_profiles_changed(d, 0); if (spa_streq(ep->uuid, SPA_BT_UUID_BAP_BROADCAST_SINK)) { int ret, i; bool codec_found = false; spa_autofree char *local_endpoint = NULL; /* get local endpoint */ for (i = 0; monitor->media_codecs[i]; i++) { if (monitor->media_codecs[i]->kind != MEDIA_CODEC_BAP) continue; if (!is_media_codec_enabled(monitor, monitor->media_codecs[i])) continue; if (monitor->media_codecs[i]->codec_id == ep->codec){ ret = media_codec_to_endpoint(monitor->media_codecs[i], SPA_BT_MEDIA_SOURCE_BROADCAST, &local_endpoint); if (ret == 0) { codec_found = true; break; } } } if (!codec_found) { spa_log_warn(monitor->log, "endpoint codec not found"); return; } if (local_endpoint != NULL) configure_bcast_source(monitor, monitor->media_codecs[i], conn, object_path, interface_name, local_endpoint); } } } static void interfaces_added(struct spa_bt_monitor *monitor, DBusMessageIter *arg_iter) { DBusMessageIter it[3]; const char *object_path; dbus_message_iter_get_basic(arg_iter, &object_path); dbus_message_iter_next(arg_iter); dbus_message_iter_recurse(arg_iter, &it[0]); while (dbus_message_iter_get_arg_type(&it[0]) != DBUS_TYPE_INVALID) { const char *interface_name; dbus_message_iter_recurse(&it[0], &it[1]); dbus_message_iter_get_basic(&it[1], &interface_name); dbus_message_iter_next(&it[1]); dbus_message_iter_recurse(&it[1], &it[2]); interface_added(monitor, monitor->conn, object_path, interface_name, &it[2]); dbus_message_iter_next(&it[0]); } } static void interfaces_removed(struct spa_bt_monitor *monitor, DBusMessageIter *arg_iter) { const char *object_path; DBusMessageIter it; dbus_message_iter_get_basic(arg_iter, &object_path); dbus_message_iter_next(arg_iter); dbus_message_iter_recurse(arg_iter, &it); while (dbus_message_iter_get_arg_type(&it) != DBUS_TYPE_INVALID) { const char *interface_name; dbus_message_iter_get_basic(&it, &interface_name); spa_log_debug(monitor->log, "Found object %s, interface %s", object_path, interface_name); if (spa_streq(interface_name, BLUEZ_DEVICE_INTERFACE)) { struct spa_bt_device *d; d = spa_bt_device_find(monitor, object_path); if (d != NULL) device_free(d); } else if (spa_streq(interface_name, BLUEZ_DEVICE_SET_INTERFACE)) { device_set_update_props(monitor, object_path, NULL, NULL); } else if (spa_streq(interface_name, BLUEZ_ADAPTER_INTERFACE) || spa_streq(interface_name, BLUEZ_MEDIA_INTERFACE)) { struct spa_bt_adapter *a; a = adapter_find(monitor, object_path); if (a != NULL) adapter_free(a); } else if (spa_streq(interface_name, BLUEZ_MEDIA_ENDPOINT_INTERFACE)) { struct spa_bt_remote_endpoint *ep; ep = remote_endpoint_find(monitor, object_path); if (ep != NULL) { struct spa_bt_device *d = ep->device; remote_endpoint_free(ep); if (d) spa_bt_device_emit_profiles_changed(d, 0); } } else if (spa_streq(interface_name, BLUEZ_MEDIA_TRANSPORT_INTERFACE)) { struct spa_bt_transport *transport; transport = spa_bt_transport_find(monitor, object_path); if (transport != NULL) { if (transport->profile == SPA_BT_PROFILE_BAP_BROADCAST_SINK) { struct spa_bt_device *d = transport->device; if (d != NULL){ device_free(d); } } else if (transport->profile == SPA_BT_PROFILE_BAP_BROADCAST_SOURCE) { /* * For each transport that has a broadcast source profile, * we need to create a new node for each BIS. * example of transport path = /org/bluez/hci0/dev_2D_9D_93_F9_D7_5E/bis1/fd0 * Create new devices only for a case of a big with multiple BISes, * for this case will have the scanned device to the transport * "/fd0" and create new devices for the other transports from this device * that appear only in case of multiple BISes per BIG. * * Here we delete the created devices. */ char *pos = strstr(transport->path, "/fd0"); if (pos == NULL) { struct spa_bt_device *d = transport->device; if (d != NULL){ device_free(d); } } } spa_bt_transport_free(transport); } } dbus_message_iter_next(&it); } } static void get_managed_objects_reply(DBusPendingCall *pending, void *user_data) { struct spa_bt_monitor *monitor = user_data; DBusMessageIter it[6]; spa_assert(monitor->get_managed_objects_call == pending); spa_autoptr(DBusMessage) r = steal_reply_and_unref(&monitor->get_managed_objects_call); if (r == NULL) return; if (dbus_message_is_error(r, DBUS_ERROR_UNKNOWN_METHOD)) { spa_log_warn(monitor->log, "BlueZ D-Bus ObjectManager not available"); return; } if (dbus_message_is_error(r, DBUS_ERROR_NAME_HAS_NO_OWNER)) { spa_log_warn(monitor->log, "BlueZ system service is not available"); return; } if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) { spa_log_error(monitor->log, "GetManagedObjects() failed: %s", dbus_message_get_error_name(r)); return; } if (!dbus_message_iter_init(r, &it[0]) || !spa_streq(dbus_message_get_signature(r), "a{oa{sa{sv}}}")) { spa_log_error(monitor->log, "Invalid reply signature for GetManagedObjects()"); return; } dbus_message_iter_recurse(&it[0], &it[1]); while (dbus_message_iter_get_arg_type(&it[1]) != DBUS_TYPE_INVALID) { dbus_message_iter_recurse(&it[1], &it[2]); interfaces_added(monitor, &it[2]); dbus_message_iter_next(&it[1]); } reselect_backend(monitor, false); monitor->objects_listed = true; } static void get_managed_objects(struct spa_bt_monitor *monitor) { if (monitor->objects_listed || monitor->get_managed_objects_call) return; spa_autoptr(DBusMessage) m = NULL; m = dbus_message_new_method_call(BLUEZ_SERVICE, "/", "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); dbus_message_set_auto_start(m, false); monitor->get_managed_objects_call = send_with_reply(monitor->conn, m, get_managed_objects_reply, monitor); } static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *m, void *user_data) { struct spa_bt_monitor *monitor = user_data; if (dbus_message_is_signal(m, "org.freedesktop.DBus", "NameOwnerChanged")) { const char *name, *old_owner, *new_owner; spa_auto(DBusError) err = DBUS_ERROR_INIT; spa_log_debug(monitor->log, "Name owner changed %s", dbus_message_get_path(m)); if (!dbus_message_get_args(m, &err, DBUS_TYPE_STRING, &name, DBUS_TYPE_STRING, &old_owner, DBUS_TYPE_STRING, &new_owner, DBUS_TYPE_INVALID)) { spa_log_error(monitor->log, "Failed to parse org.freedesktop.DBus.NameOwnerChanged: %s", err.message); goto finish; } if (spa_streq(name, BLUEZ_SERVICE)) { bool has_old_owner = old_owner && *old_owner; bool has_new_owner = new_owner && *new_owner; if (has_old_owner) { spa_log_debug(monitor->log, "Bluetooth daemon disappeared"); if (monitor->backends[BACKEND_NATIVE]) monitor->backends[BACKEND_NATIVE]->available = false; reselect_backend(monitor, true); } if (has_old_owner || has_new_owner) { struct spa_bt_adapter *a; struct spa_bt_device *d; struct spa_bt_remote_endpoint *ep; struct spa_bt_transport *t; monitor->objects_listed = false; spa_list_consume(t, &monitor->transport_list, link) spa_bt_transport_free(t); spa_list_consume(ep, &monitor->remote_endpoint_list, link) remote_endpoint_free(ep); spa_list_consume(d, &monitor->device_list, link) device_free(d); spa_list_consume(a, &monitor->adapter_list, link) adapter_free(a); } if (has_new_owner) { spa_log_debug(monitor->log, "Bluetooth daemon appeared"); get_managed_objects(monitor); } } else if (spa_streq(name, OFONO_SERVICE)) { if (monitor->backends[BACKEND_OFONO]) monitor->backends[BACKEND_OFONO]->available = (new_owner && *new_owner); reselect_backend(monitor, false); } else if (spa_streq(name, HSPHFPD_SERVICE)) { if (monitor->backends[BACKEND_HSPHFPD]) monitor->backends[BACKEND_HSPHFPD]->available = (new_owner && *new_owner); reselect_backend(monitor, false); } } else if (dbus_message_is_signal(m, "org.freedesktop.DBus.ObjectManager", "InterfacesAdded")) { DBusMessageIter it; spa_log_debug(monitor->log, "interfaces added %s", dbus_message_get_path(m)); if (!monitor->objects_listed) goto finish; if (!dbus_message_iter_init(m, &it) || !spa_streq(dbus_message_get_signature(m), "oa{sa{sv}}")) { spa_log_error(monitor->log, "Invalid signature found in InterfacesAdded"); goto finish; } interfaces_added(monitor, &it); } else if (dbus_message_is_signal(m, "org.freedesktop.DBus.ObjectManager", "InterfacesRemoved")) { DBusMessageIter it; spa_log_debug(monitor->log, "interfaces removed %s", dbus_message_get_path(m)); if (!monitor->objects_listed) goto finish; if (!dbus_message_iter_init(m, &it) || !spa_streq(dbus_message_get_signature(m), "oas")) { spa_log_error(monitor->log, "Invalid signature found in InterfacesRemoved"); goto finish; } interfaces_removed(monitor, &it); } else if (dbus_message_is_signal(m, "org.freedesktop.DBus.Properties", "PropertiesChanged")) { DBusMessageIter it[2]; const char *iface, *path; if (!monitor->objects_listed) goto finish; if (!dbus_message_iter_init(m, &it[0]) || !spa_streq(dbus_message_get_signature(m), "sa{sv}as")) { spa_log_error(monitor->log, "Invalid signature found in PropertiesChanged"); goto finish; } path = dbus_message_get_path(m); dbus_message_iter_get_basic(&it[0], &iface); dbus_message_iter_next(&it[0]); dbus_message_iter_recurse(&it[0], &it[1]); if (spa_streq(iface, BLUEZ_ADAPTER_INTERFACE) || spa_streq(iface, BLUEZ_MEDIA_INTERFACE)) { struct spa_bt_adapter *a; a = adapter_find(monitor, path); if (a == NULL) { spa_log_warn(monitor->log, "Properties changed in unknown adapter %s", path); goto finish; } spa_log_debug(monitor->log, "Properties changed in adapter %s", path); if (spa_streq(iface, BLUEZ_ADAPTER_INTERFACE)) adapter_update_props(a, &it[1], NULL); else adapter_media_update_props(a, &it[1], NULL); } else if (spa_streq(iface, BLUEZ_DEVICE_INTERFACE)) { struct spa_bt_device *d; d = spa_bt_device_find(monitor, path); if (d == NULL) { spa_log_debug(monitor->log, "Properties changed in unknown device %s", path); goto finish; } spa_log_debug(monitor->log, "Properties changed in device %s", path); device_update_props(d, &it[1], NULL); if (!device_props_ready(d)) goto finish; device_update_hw_volume_profiles(d); spa_bt_device_add_profile(d, SPA_BT_PROFILE_NULL); } else if (spa_streq(iface, BLUEZ_DEVICE_SET_INTERFACE)) { device_set_update_props(monitor, path, &it[1], NULL); } else if (spa_streq(iface, BLUEZ_MEDIA_ENDPOINT_INTERFACE)) { struct spa_bt_remote_endpoint *ep; struct spa_bt_device *d; ep = remote_endpoint_find(monitor, path); if (ep == NULL) { spa_log_debug(monitor->log, "Properties changed in unknown remote endpoint %s", path); goto finish; } spa_log_debug(monitor->log, "Properties changed in remote endpoint %s", path); remote_endpoint_update_props(ep, &it[1], NULL); d = ep->device; if (d) spa_bt_device_emit_profiles_changed(d, 0); } else if (spa_streq(iface, BLUEZ_MEDIA_TRANSPORT_INTERFACE)) { struct spa_bt_transport *transport; transport = spa_bt_transport_find(monitor, path); if (transport == NULL) { spa_log_warn(monitor->log, "Properties changed in unknown transport '%s'. " "Multiple sound server instances (PipeWire/Pulseaudio/bluez-alsa) are " "probably trying to use Bluetooth audio at the same time, which can " "cause problems. The system configuration likely should be fixed " "to have only one sound server that manages Bluetooth audio.", path); goto finish; } spa_log_debug(monitor->log, "Properties changed in transport %s", path); transport_update_props(transport, &it[1], NULL); } } finish: return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } static void add_filters(struct spa_bt_monitor *this) { if (this->filters_added) return; if (!dbus_connection_add_filter(this->conn, filter_cb, this, NULL)) { spa_log_error(this->log, "failed to add filter function"); return; } spa_auto(DBusError) err = DBUS_ERROR_INIT; dbus_bus_add_match(this->conn, "type='signal',sender='org.freedesktop.DBus'," "interface='org.freedesktop.DBus',member='NameOwnerChanged'," "arg0='" BLUEZ_SERVICE "'", &err); #ifdef HAVE_BLUEZ_5_BACKEND_OFONO dbus_bus_add_match(this->conn, "type='signal',sender='org.freedesktop.DBus'," "interface='org.freedesktop.DBus',member='NameOwnerChanged'," "arg0='" OFONO_SERVICE "'", &err); #endif #ifdef HAVE_BLUEZ_5_BACKEND_HSPHFPD dbus_bus_add_match(this->conn, "type='signal',sender='org.freedesktop.DBus'," "interface='org.freedesktop.DBus',member='NameOwnerChanged'," "arg0='" HSPHFPD_SERVICE "'", &err); #endif dbus_bus_add_match(this->conn, "type='signal',sender='" BLUEZ_SERVICE "'," "interface='org.freedesktop.DBus.ObjectManager',member='InterfacesAdded'", &err); dbus_bus_add_match(this->conn, "type='signal',sender='" BLUEZ_SERVICE "'," "interface='org.freedesktop.DBus.ObjectManager',member='InterfacesRemoved'", &err); dbus_bus_add_match(this->conn, "type='signal',sender='" BLUEZ_SERVICE "'," "interface='org.freedesktop.DBus.Properties',member='PropertiesChanged'," "arg0='" BLUEZ_ADAPTER_INTERFACE "'", &err); dbus_bus_add_match(this->conn, "type='signal',sender='" BLUEZ_SERVICE "'," "interface='org.freedesktop.DBus.Properties',member='PropertiesChanged'," "arg0='" BLUEZ_MEDIA_INTERFACE "'", &err); dbus_bus_add_match(this->conn, "type='signal',sender='" BLUEZ_SERVICE "'," "interface='org.freedesktop.DBus.Properties',member='PropertiesChanged'," "arg0='" BLUEZ_DEVICE_INTERFACE "'", &err); dbus_bus_add_match(this->conn, "type='signal',sender='" BLUEZ_SERVICE "'," "interface='org.freedesktop.DBus.Properties',member='PropertiesChanged'," "arg0='" BLUEZ_DEVICE_SET_INTERFACE "'", &err); dbus_bus_add_match(this->conn, "type='signal',sender='" BLUEZ_SERVICE "'," "interface='org.freedesktop.DBus.Properties',member='PropertiesChanged'," "arg0='" BLUEZ_MEDIA_ENDPOINT_INTERFACE "'", &err); dbus_bus_add_match(this->conn, "type='signal',sender='" BLUEZ_SERVICE "'," "interface='org.freedesktop.DBus.Properties',member='PropertiesChanged'," "arg0='" BLUEZ_MEDIA_TRANSPORT_INTERFACE "'", &err); this->filters_added = true; } static int impl_device_add_listener(void *object, struct spa_hook *listener, const struct spa_device_events *events, void *data) { struct spa_bt_monitor *this = object; struct spa_hook_list save; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(events != NULL, -EINVAL); spa_hook_list_isolate(&this->hooks, &save, listener, events, data); add_filters(this); get_managed_objects(this); struct spa_bt_device *device; spa_list_for_each(device, &this->device_list, link) { if (device->added) emit_device_info(this, device, this->connection_info_supported); } spa_hook_list_join(&this->hooks, &save); return 0; } static const struct spa_device_methods impl_device = { SPA_VERSION_DEVICE_METHODS, .add_listener = impl_device_add_listener, }; static int impl_get_interface(struct spa_handle *handle, const char *type, void **interface) { struct spa_bt_monitor *this; spa_return_val_if_fail(handle != NULL, -EINVAL); spa_return_val_if_fail(interface != NULL, -EINVAL); this = (struct spa_bt_monitor *) handle; if (spa_streq(type, SPA_TYPE_INTERFACE_Device)) *interface = &this->device; else return -ENOENT; return 0; } static int impl_clear(struct spa_handle *handle) { struct spa_bt_monitor *monitor; struct spa_bt_adapter *a; struct spa_bt_device *d; struct spa_bt_remote_endpoint *ep; struct spa_bt_transport *t; struct spa_bt_big *b; const struct spa_dict_item *it; size_t i; monitor = (struct spa_bt_monitor *) handle; /* * We don't call BlueZ API unregister methods here, since BlueZ generally does the * unregistration when the DBus connection is closed below. We'll unregister DBus * object managers and filter callbacks though. */ unregister_media_application(monitor); if (monitor->filters_added) { dbus_connection_remove_filter(monitor->conn, filter_cb, monitor); monitor->filters_added = false; } cancel_and_unref(&monitor->get_managed_objects_call); spa_list_consume(t, &monitor->transport_list, link) spa_bt_transport_free(t); spa_list_consume(ep, &monitor->remote_endpoint_list, link) remote_endpoint_free(ep); spa_list_consume(d, &monitor->device_list, link) device_free(d); spa_list_consume(a, &monitor->adapter_list, link) adapter_free(a); spa_list_consume(b, &monitor->bcast_source_config_list, link) big_entry_free(b); for (i = 0; i < SPA_N_ELEMENTS(monitor->backends); ++i) { spa_bt_backend_free(monitor->backends[i]); monitor->backends[i] = NULL; } spa_dict_for_each(it, &monitor->global_settings) { free((void *)it->key); free((void *)it->value); } free((void*)monitor->enabled_codecs.items); spa_zero(monitor->enabled_codecs); dbus_connection_unref(monitor->conn); spa_dbus_connection_destroy(monitor->dbus_connection); monitor->dbus_connection = NULL; monitor->conn = NULL; monitor->objects_listed = false; monitor->connection_info_supported = false; monitor->backend = NULL; monitor->backend_selection = BACKEND_NATIVE; spa_bt_quirks_destroy(monitor->quirks); free_media_codecs(monitor->media_codecs); return 0; } static size_t impl_get_size(const struct spa_handle_factory *factory, const struct spa_dict *params) { return sizeof(struct spa_bt_monitor); } int spa_bt_profiles_from_json_array(const char *str) { struct spa_json it_array; char role_name[256]; enum spa_bt_profile profiles = SPA_BT_PROFILE_NULL; if (spa_json_begin_array(&it_array, str, strlen(str)) <= 0) return -EINVAL; while (spa_json_get_string(&it_array, role_name, sizeof(role_name)) > 0) { if (spa_streq(role_name, "hsp_hs")) { profiles |= SPA_BT_PROFILE_HSP_HS; } else if (spa_streq(role_name, "hsp_ag")) { profiles |= SPA_BT_PROFILE_HSP_AG; } else if (spa_streq(role_name, "hfp_hf")) { profiles |= SPA_BT_PROFILE_HFP_HF; } else if (spa_streq(role_name, "hfp_ag")) { profiles |= SPA_BT_PROFILE_HFP_AG; } else if (spa_streq(role_name, "a2dp_sink")) { profiles |= SPA_BT_PROFILE_A2DP_SINK; } else if (spa_streq(role_name, "a2dp_source")) { profiles |= SPA_BT_PROFILE_A2DP_SOURCE; } else if (spa_streq(role_name, "bap_sink")) { profiles |= SPA_BT_PROFILE_BAP_SINK; } else if (spa_streq(role_name, "bap_source")) { profiles |= SPA_BT_PROFILE_BAP_SOURCE; } else if (spa_streq(role_name, "bap_bcast_source")) { profiles |= SPA_BT_PROFILE_BAP_BROADCAST_SOURCE; } else if (spa_streq(role_name, "bap_bcast_sink")) { profiles |= SPA_BT_PROFILE_BAP_BROADCAST_SINK; } else if (spa_streq(role_name, "asha_sink")) { profiles |= SPA_BT_PROFILE_ASHA_SINK; } } return profiles; } static int parse_roles(struct spa_bt_monitor *monitor, const struct spa_dict *info) { const char *str; int res = 0; int profiles = SPA_BT_PROFILE_MEDIA_SINK | SPA_BT_PROFILE_MEDIA_SOURCE | SPA_BT_PROFILE_ASHA_SINK; /* HSP/HFP backends parse this property separately */ if (info && (str = spa_dict_lookup(info, "bluez5.roles"))) { res = spa_bt_profiles_from_json_array(str); if (res < 0) { spa_log_warn(monitor->log, "malformed bluez5.roles setting ignored"); goto done; } profiles &= res; } res = 0; done: monitor->enabled_profiles = profiles; return res; } static void parse_broadcast_source_config(struct spa_bt_monitor *monitor, const struct spa_dict *info) { const char *str; char key[256]; char bis_key[256]; char qos_key[256]; char bcode[BROADCAST_CODE_LEN + 3]; int cursor; int big_id = 0; struct spa_json it[3], it_array[4]; struct spa_list big_list = SPA_LIST_INIT(&big_list); struct spa_error_location loc; struct spa_bt_big *big; /* Search for bluez5.bcast_source.config */ if (!(info && (str = spa_dict_lookup(info, "bluez5.bcast_source.config")))) return; /* Verify is an array of BIGS */ if (spa_json_begin_array(&it_array[0], str, strlen(str)) <= 0) goto parse_failed; /* Iterate on all BIG objects */ while (spa_json_enter_object(&it_array[0], &it[0]) > 0) { struct spa_bt_big *big_entry = calloc(1, sizeof(struct spa_bt_big)); if (!big_entry) goto errno_failed; big_entry->big_id = big_id++; spa_list_init(&big_entry->bis_list); spa_list_append(&big_list, &big_entry->link); /* Iterate on all BIG values */ while (spa_json_get_string(&it[0], key, sizeof(key)) > 0) { if (spa_streq(key, "broadcast_code")) { if (spa_json_get_string(&it[0], bcode, sizeof(bcode)) <= 0) goto parse_failed; if (strlen(bcode) > BROADCAST_CODE_LEN) goto parse_failed; memcpy(big_entry->broadcast_code, bcode, strlen(bcode)); spa_log_debug(monitor->log, "big_entry->broadcast_code %s", big_entry->broadcast_code); } else if (spa_streq(key, "encryption")) { if (spa_json_get_bool(&it[0], &big_entry->encryption) <= 0) goto parse_failed; spa_log_debug(monitor->log, "big_entry->encryption %d", big_entry->encryption); } else if (spa_streq(key, "sync_factor")) { if (spa_json_get_int(&it[0], &big_entry->sync_factor) <= 0) goto parse_failed; spa_log_debug(monitor->log, "big_entry->sync_factor %d", big_entry->sync_factor); } else if (spa_streq(key, "bis")) { if (spa_json_enter_array(&it[0], &it_array[1]) <= 0) goto parse_failed; while (spa_json_enter_object(&it_array[1], &it[1]) > 0) { /* Iterate on all BIS values */ struct spa_bt_bis *bis_entry = calloc(1, sizeof(struct spa_bt_bis)); if (!bis_entry) goto errno_failed; spa_list_init(&bis_entry->metadata_list); spa_list_append(&big_entry->bis_list, &bis_entry->link); while (spa_json_get_string(&it[1], bis_key, sizeof(bis_key)) > 0) { if (spa_streq(bis_key, "qos_preset")) { if (spa_json_get_string(&it[1], bis_entry->qos_preset, sizeof(bis_entry->qos_preset)) <= 0) goto parse_failed; spa_log_debug(monitor->log, "bis_entry->qos_preset %s", bis_entry->qos_preset); } else if (spa_streq(bis_key, "audio_channel_allocation")) { if (spa_json_get_int(&it[1], &bis_entry->channel_allocation) <= 0) goto parse_failed; spa_log_debug(monitor->log, "bis_entry->channel_allocation %d", bis_entry->channel_allocation); } else if (spa_streq(bis_key, "metadata")) { if (spa_json_enter_array(&it[1], &it_array[2]) <= 0) goto parse_failed; while (spa_json_enter_object(&it_array[2], &it[2]) > 0) { struct spa_bt_metadata *metadata_entry = calloc(1, sizeof(struct spa_bt_metadata)); if (!metadata_entry) goto errno_failed; spa_list_append(&bis_entry->metadata_list, &metadata_entry->link); while (spa_json_get_string(&it[2], qos_key, sizeof(qos_key)) > 0) { if (spa_streq(qos_key, "type")) { if (spa_json_get_int(&it[2], &metadata_entry->type) <= 0) goto parse_failed; spa_log_debug(monitor->log, "metadata_entry->type %d", metadata_entry->type); } else if (spa_streq(qos_key, "value")) { if (spa_json_enter_array(&it[2], &it_array[3]) <= 0) goto parse_failed; for (cursor = 0; cursor < METADATA_MAX_LEN - 1; cursor++) { int temp_val = 0; if (spa_json_get_int(&it_array[3], &temp_val) <= 0) break; metadata_entry->value[cursor] = (uint8_t)temp_val; spa_log_debug(monitor->log, "metadata_entry->value[%d] %d", cursor, metadata_entry->value[cursor]); } /* length is size of value plus 1 octet for type */ metadata_entry->length = cursor + 1; spa_log_debug(monitor->log, "metadata_entry->length %d", metadata_entry->length); spa_log_debug(monitor->log, "metadata_entry->value_size %d", cursor); } } } } } } } } } spa_list_insert_list(&monitor->bcast_source_config_list, &big_list); return; errno_failed: spa_log_warn(monitor->log, "failed in bluez5.bcast_source.config: %m"); goto cleanup; parse_failed: str = spa_dict_lookup(info, "bluez5.bcast_source.config"); if (spa_json_get_error(&it_array[0], str, &loc)) { spa_debug_log_error_location(monitor->log, SPA_LOG_LEVEL_WARN, &loc, "malformed bluez5.bcast_source.config: %s", loc.reason); } else { spa_log_warn(monitor->log, "malformed bluez5.bcast_source.config"); } goto cleanup; cleanup: spa_list_consume(big, &big_list, link) big_entry_free(big); } static int parse_codec_array(struct spa_bt_monitor *this, const struct spa_dict *info) { const struct media_codec * const * const media_codecs = this->media_codecs; const char *str; struct spa_dict_item *codecs; struct spa_json it_array; char codec_name[256]; size_t num_codecs; int i; /* Parse bluez5.codecs property to a dict of enabled codecs */ num_codecs = 0; while (media_codecs[num_codecs]) ++num_codecs; codecs = calloc(num_codecs, sizeof(struct spa_dict_item)); if (codecs == NULL) return -ENOMEM; if (info == NULL || (str = spa_dict_lookup(info, "bluez5.codecs")) == NULL) goto fallback; if (spa_json_begin_array(&it_array, str, strlen(str)) <= 0) { spa_log_error(this->log, "property bluez5.codecs '%s' is not an array", str); goto fallback; } this->enabled_codecs = SPA_DICT_INIT(codecs, 0); while (spa_json_get_string(&it_array, codec_name, sizeof(codec_name)) > 0) { int i; for (i = 0; media_codecs[i]; ++i) { const struct media_codec *codec = media_codecs[i]; if (!spa_streq(codec->name, codec_name)) continue; if (spa_dict_lookup_item(&this->enabled_codecs, codec->name) != NULL) continue; spa_assert(this->enabled_codecs.n_items < num_codecs); codecs[this->enabled_codecs.n_items].key = codec->name; codecs[this->enabled_codecs.n_items].value = "true"; ++this->enabled_codecs.n_items; break; } } spa_dict_qsort(&this->enabled_codecs); for (i = 0; media_codecs[i]; ++i) { const struct media_codec *codec = media_codecs[i]; spa_log_debug(this->log, "codec %s: %s", codec->name, is_media_codec_enabled(this, codec) ? "enabled" : "disabled"); } return 0; fallback: for (i = 0; media_codecs[i]; ++i) { const struct media_codec *codec = media_codecs[i]; spa_log_debug(this->log, "enabling codec %s", codec->name); codecs[i].key = codec->name; codecs[i].value = "true"; } this->enabled_codecs = SPA_DICT_INIT(codecs, i); spa_dict_qsort(&this->enabled_codecs); return 0; } static void parse_bap_locations(struct spa_bt_monitor *this, const struct spa_dict *info, const char *key, uint32_t *value) { const char *str; uint32_t position[MAX_CHANNELS]; uint32_t n_channels; uint32_t locations; unsigned int i, j; if (!info || !(str = spa_dict_lookup(info, key))) return; if (spa_atou32(str, value, 0)) return; if (!spa_audio_parse_position_n(str, strlen(str), position, SPA_N_ELEMENTS(position), &n_channels)) { spa_log_error(this->log, "property %s '%s' is not valid position array", key, str); return; } locations = 0; for (i = 0; i < n_channels; ++i) for (j = 0; j < SPA_N_ELEMENTS(bap_channel_bits); ++j) if (bap_channel_bits[j].channel == position[i]) locations |= bap_channel_bits[j].bit; *value = locations; } static void parse_bap_server(struct spa_bt_monitor *this, const struct spa_dict *info) { this->bap_sink_locations = BAP_CHANNEL_FL | BAP_CHANNEL_FR; this->bap_source_locations = BAP_CHANNEL_FL | BAP_CHANNEL_FR; this->bap_sink_contexts = this->bap_sink_supported_contexts = BAP_CONTEXT_ALL; this->bap_source_contexts = this->bap_source_supported_contexts = (BAP_CONTEXT_UNSPECIFIED | BAP_CONTEXT_CONVERSATIONAL | BAP_CONTEXT_MEDIA | BAP_CONTEXT_GAME); if (!info) return; parse_bap_locations(this, info, "bluez5.bap-server-capabilities.sink.locations", &this->bap_sink_locations); spa_atou32(spa_dict_lookup(info, "bluez5.bap-server-capabilities.sink.contexts"), &this->bap_sink_contexts, 0); spa_atou32(spa_dict_lookup(info, "bluez5.bap-server-capabilities.sink.supported-contexts"), &this->bap_sink_supported_contexts, 0); parse_bap_locations(this, info, "bluez5.bap-server-capabilities.source.locations", &this->bap_source_locations); spa_atou32(spa_dict_lookup(info, "bluez5.bap-server-capabilities.source.contexts"), &this->bap_source_contexts, 0); spa_atou32(spa_dict_lookup(info, "bluez5.bap-server-capabilities.source.supported-contexts"), &this->bap_source_supported_contexts, 0); } static void get_global_settings(struct spa_bt_monitor *this, const struct spa_dict *dict) { uint32_t n_items = 0; uint32_t i; if (dict == NULL) { this->global_settings = SPA_DICT_INIT(this->global_setting_items, 0); return; } for (i = 0; i < dict->n_items && n_items < SPA_N_ELEMENTS(this->global_setting_items); i++) { const struct spa_dict_item *it = &dict->items[i]; if (spa_strstartswith(it->key, "bluez5.") && it->value != NULL) this->global_setting_items[n_items++] = SPA_DICT_ITEM_INIT(strdup(it->key), strdup(it->value)); } this->global_settings = SPA_DICT_INIT(this->global_setting_items, n_items); } static int impl_init(const struct spa_handle_factory *factory, struct spa_handle *handle, const struct spa_dict *info, const struct spa_support *support, uint32_t n_support) { struct spa_bt_monitor *this; int res; spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(handle != NULL, -EINVAL); handle->get_interface = impl_get_interface; handle->clear = impl_clear; this = (struct spa_bt_monitor *) handle; this->log = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log); this->dbus = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_DBus); this->main_loop = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Loop); this->data_loop = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_DataLoop); this->loop_utils = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_LoopUtils); this->main_system = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_System); this->data_system = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_DataSystem); this->plugin_loader = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_PluginLoader); spa_log_topic_init(this->log, &log_topic); if (this->dbus == NULL) { spa_log_error(this->log, "a dbus is needed"); return -EINVAL; } if (this->plugin_loader == NULL) { spa_log_error(this->log, "a plugin loader is needed"); return -EINVAL; } if (this->loop_utils == NULL) { spa_log_error(this->log, "loop utils is needed"); return -EINVAL; } this->media_codecs = NULL; this->quirks = NULL; this->conn = NULL; this->dbus_connection = NULL; this->media_codecs = load_media_codecs(this->plugin_loader, this->log); if (this->media_codecs == NULL) { spa_log_error(this->log, "failed to load required media codec plugins"); res = -EIO; goto fail; } this->quirks = spa_bt_quirks_create(info, this->log); if (this->quirks == NULL) { spa_log_error(this->log, "failed to parse quirk table"); res = -EINVAL; goto fail; } this->dbus_connection = spa_dbus_get_connection(this->dbus, SPA_DBUS_TYPE_SYSTEM); if (this->dbus_connection == NULL) { spa_log_error(this->log, "no dbus connection"); res = -EIO; goto fail; } this->conn = spa_dbus_connection_get(this->dbus_connection); if (this->conn == NULL) { spa_log_error(this->log, "failed to get dbus connection"); res = -EIO; goto fail; } /* XXX: We should handle spa_dbus reconnecting, but we don't, so ref * XXX: the handle so that we can keep it if spa_dbus unrefs it. */ dbus_connection_ref(this->conn); spa_hook_list_init(&this->hooks); this->device.iface = SPA_INTERFACE_INIT( SPA_TYPE_INTERFACE_Device, SPA_VERSION_DEVICE, &impl_device, this); spa_list_init(&this->adapter_list); spa_list_init(&this->device_list); spa_list_init(&this->remote_endpoint_list); spa_list_init(&this->transport_list); spa_list_init(&this->bcast_source_config_list); if ((res = parse_codec_array(this, info)) < 0) goto fail; parse_roles(this, info); parse_broadcast_source_config(this, info); parse_bap_server(this, info); this->default_audio_info.rate = A2DP_CODEC_DEFAULT_RATE; this->default_audio_info.channels = A2DP_CODEC_DEFAULT_CHANNELS; this->backend_selection = BACKEND_NATIVE; get_global_settings(this, info); if (info) { const char *str; uint32_t tmp; if ((str = spa_dict_lookup(info, "api.bluez5.connection-info")) != NULL && spa_atob(str)) this->connection_info_supported = true; if ((str = spa_dict_lookup(info, "bluez5.default.rate")) != NULL && (tmp = atoi(str)) > 0) this->default_audio_info.rate = tmp; if ((str = spa_dict_lookup(info, "bluez5.default.channels")) != NULL && ((tmp = atoi(str)) > 0)) this->default_audio_info.channels = tmp; if ((str = spa_dict_lookup(info, "bluez5.hfphsp-backend")) != NULL) { if (spa_streq(str, "none")) this->backend_selection = BACKEND_NONE; else if (spa_streq(str, "any")) this->backend_selection = BACKEND_ANY; else if (spa_streq(str, "ofono")) this->backend_selection = BACKEND_OFONO; else if (spa_streq(str, "hsphfpd")) this->backend_selection = BACKEND_HSPHFPD; else if (spa_streq(str, "native")) this->backend_selection = BACKEND_NATIVE; } if ((str = spa_dict_lookup(info, "bluez5.dummy-avrcp-player")) != NULL) this->dummy_avrcp_player = spa_atob(str); else this->dummy_avrcp_player = false; } register_media_application(this); /* Create backends. They're started after we get a reply from Bluez. */ this->backends[BACKEND_NATIVE] = backend_native_new(this, this->conn, info, this->quirks, support, n_support); this->backends[BACKEND_OFONO] = backend_ofono_new(this, this->conn, info, this->quirks, support, n_support); this->backends[BACKEND_HSPHFPD] = backend_hsphfpd_new(this, this->conn, info, this->quirks, support, n_support); return 0; fail: if (this->media_codecs) free_media_codecs(this->media_codecs); if (this->quirks) spa_bt_quirks_destroy(this->quirks); if (this->conn) dbus_connection_unref(this->conn); if (this->dbus_connection) spa_dbus_connection_destroy(this->dbus_connection); this->media_codecs = NULL; this->quirks = NULL; this->conn = NULL; this->dbus_connection = NULL; return res; } static const struct spa_interface_info impl_interfaces[] = { {SPA_TYPE_INTERFACE_Device,}, }; static int impl_enum_interface_info(const struct spa_handle_factory *factory, const struct spa_interface_info **info, uint32_t *index) { spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(info != NULL, -EINVAL); spa_return_val_if_fail(index != NULL, -EINVAL); if (*index >= SPA_N_ELEMENTS(impl_interfaces)) return 0; *info = &impl_interfaces[(*index)++]; return 1; } const struct spa_handle_factory spa_bluez5_dbus_factory = { SPA_VERSION_HANDLE_FACTORY, SPA_NAME_API_BLUEZ5_ENUM_DBUS, NULL, impl_get_size, impl_init, impl_enum_interface_info, }; // Report battery percentage to BlueZ using experimental (BlueZ 5.56) Battery Provider API. No-op if no changes occurred. int spa_bt_device_report_battery_level(struct spa_bt_device *device, uint8_t percentage) { if (percentage == SPA_BT_NO_BATTERY) { battery_remove(device); return 0; } // BlueZ likely is running without battery provider support, don't try to report battery if (device->adapter->battery_provider_unavailable) return 0; // If everything is initialized and battery level has not changed we don't need to send anything to BlueZ if (device->adapter->has_battery_provider && device->has_battery && device->battery == percentage) return 1; device->battery = percentage; if (!device->adapter->has_battery_provider) { // No provider: register it, create battery when registered register_battery_provider(device); } else if (!device->has_battery) { // Have provider but no battery: create battery with correct percentage battery_create(device); } else { // Just update existing battery percentage battery_update(device); } return 1; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/bluez5/bluez5-device.c000066400000000000000000002700561511204443500267710ustar00rootroot00000000000000/* Spa Bluez5 Device */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "defs.h" #include "media-codecs.h" SPA_LOG_TOPIC_DEFINE_STATIC(log_topic, "spa.bluez5.device"); #undef SPA_LOG_TOPIC_DEFAULT #define SPA_LOG_TOPIC_DEFAULT &log_topic #define MAX_NODES (2*MAX_CHANNELS) #define DEVICE_ID_SOURCE 0 #define DEVICE_ID_SINK 1 #define DEVICE_ID_SOURCE_SET (MAX_NODES + 0) #define DEVICE_ID_SINK_SET (MAX_NODES + 1) #define SINK_ID_FLAG 0x1 #define DYNAMIC_NODE_ID_FLAG 0x1000 static struct spa_i18n *_i18n; #define _(_str) spa_i18n_text(_i18n,(_str)) #define N_(_str) (_str) enum device_profile { DEVICE_PROFILE_OFF = 0, DEVICE_PROFILE_AG, DEVICE_PROFILE_A2DP, DEVICE_PROFILE_HSP_HFP, DEVICE_PROFILE_BAP, DEVICE_PROFILE_BAP_SINK, DEVICE_PROFILE_BAP_SOURCE, DEVICE_PROFILE_ASHA, DEVICE_PROFILE_LAST, }; enum { ROUTE_INPUT = 0, ROUTE_OUTPUT, ROUTE_HF_OUTPUT, ROUTE_SET_INPUT, ROUTE_SET_OUTPUT, ROUTE_LAST, }; struct props { enum spa_bluetooth_audio_codec codec; bool offload_active; }; static void reset_props(struct props *props) { props->codec = 0; props->offload_active = false; } struct impl; struct node { struct impl *impl; struct spa_bt_transport *transport; struct spa_hook transport_listener; uint32_t id; unsigned int active:1; unsigned int mute:1; unsigned int save:1; unsigned int a2dp_duplex:1; unsigned int offload_acquired:1; uint32_t n_channels; int64_t latency_offset; uint32_t channels[MAX_CHANNELS]; float volumes[MAX_CHANNELS]; float soft_volumes[MAX_CHANNELS]; }; struct dynamic_node { struct impl *impl; struct spa_bt_transport *transport; struct spa_hook transport_listener; uint32_t id; const char *factory_name; bool a2dp_duplex; }; struct device_set_member { struct impl *impl; struct spa_bt_transport *transport; struct spa_hook listener; uint32_t id; }; struct device_set { struct impl *impl; char *path; bool sink_enabled; bool source_enabled; bool leader; uint32_t sinks; uint32_t sources; struct device_set_member sink[MAX_CHANNELS]; struct device_set_member source[MAX_CHANNELS]; }; struct impl { struct spa_handle handle; struct spa_device device; struct spa_log *log; uint32_t info_all; struct spa_device_info info; #define IDX_EnumProfile 0 #define IDX_Profile 1 #define IDX_EnumRoute 2 #define IDX_Route 3 #define IDX_PropInfo 4 #define IDX_Props 5 struct spa_param_info params[6]; struct spa_hook_list hooks; struct props props; struct spa_bt_device *bt_dev; struct spa_hook bt_dev_listener; uint32_t profile; unsigned int switching_codec:1; unsigned int switching_codec_other:1; unsigned int save_profile:1; uint32_t prev_bt_connected_profiles; struct device_set device_set; const struct media_codec **supported_codecs; size_t supported_codec_count; struct dynamic_node dyn_nodes[MAX_NODES + 2]; #define MAX_SETTINGS 32 struct spa_dict_item setting_items[MAX_SETTINGS]; struct spa_dict setting_dict; struct node nodes[MAX_NODES + 2]; }; static void init_node(struct impl *this, struct node *node, uint32_t id) { uint32_t i; spa_zero(*node); node->id = id; for (i = 0; i < MAX_CHANNELS; i++) { node->volumes[i] = 1.0f; node->soft_volumes[i] = 1.0f; } } static bool profile_is_bap(enum device_profile profile) { switch (profile) { case DEVICE_PROFILE_BAP: case DEVICE_PROFILE_BAP_SINK: case DEVICE_PROFILE_BAP_SOURCE: return true; default: break; } return false; } static void get_media_codecs(struct impl *this, enum spa_bluetooth_audio_codec id, const struct media_codec **codecs, size_t size) { const struct media_codec * const *c; spa_assert(size > 0); spa_assert(this->supported_codecs); for (c = this->supported_codecs; *c && size > 1; ++c) { if ((*c)->kind == MEDIA_CODEC_HFP) continue; if ((*c)->id == id || id == 0) { *codecs++ = *c; --size; } } *codecs = NULL; } static const struct media_codec *get_supported_media_codec(struct impl *this, enum spa_bluetooth_audio_codec id, int *priority, enum spa_bt_profile profile) { const struct media_codec *media_codec = NULL; size_t i; for (i = 0; i < this->supported_codec_count; ++i) { if (this->supported_codecs[i]->id == id) { media_codec = this->supported_codecs[i]; break; } } if (!media_codec) return NULL; if (!spa_bt_device_supports_media_codec(this->bt_dev, media_codec, profile)) return NULL; if (priority) { *priority = 0; for (i = 0; i < this->supported_codec_count; ++i) { if (this->supported_codecs[i] == media_codec) break; if (this->supported_codecs[i]->kind == media_codec->kind) ++(*priority); } } return media_codec; } static bool is_bap_client(struct impl *this) { struct spa_bt_device *device = this->bt_dev; struct spa_bt_transport *t; spa_list_for_each(t, &device->transport_list, device_link) { if (t->bap_initiator) return true; } return false; } static const char *get_codec_name(struct spa_bt_transport *t, bool a2dp_duplex) { if (a2dp_duplex && t->media_codec->duplex_codec) return t->media_codec->duplex_codec->name; return t->media_codec->name; } static void transport_destroy(void *userdata) { struct node *node = userdata; node->transport = NULL; } static void emit_node_props(struct impl *this, struct node *node, bool full) { struct spa_event *event; uint8_t buffer[4096]; struct spa_pod_builder b = { 0 }; struct spa_pod_frame f[1]; spa_pod_builder_init(&b, buffer, sizeof(buffer)); spa_pod_builder_push_object(&b, &f[0], SPA_TYPE_EVENT_Device, SPA_DEVICE_EVENT_ObjectConfig); spa_pod_builder_prop(&b, SPA_EVENT_DEVICE_Object, 0); spa_pod_builder_int(&b, node->id); spa_pod_builder_prop(&b, SPA_EVENT_DEVICE_Props, 0); spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_Props, SPA_EVENT_DEVICE_Props, SPA_PROP_channelVolumes, SPA_POD_Array(sizeof(float), SPA_TYPE_Float, node->n_channels, node->volumes), SPA_PROP_softVolumes, SPA_POD_Array(sizeof(float), SPA_TYPE_Float, node->n_channels, node->soft_volumes), SPA_PROP_channelMap, SPA_POD_Array(sizeof(uint32_t), SPA_TYPE_Id, node->n_channels, node->channels)); if (full) { spa_pod_builder_add(&b, SPA_PROP_mute, SPA_POD_Bool(node->mute), SPA_PROP_softMute, SPA_POD_Bool(node->mute), SPA_PROP_latencyOffsetNsec, SPA_POD_Long(node->latency_offset), 0); } event = spa_pod_builder_pop(&b, &f[0]); spa_device_emit_event(&this->hooks, event); } static void emit_volume(struct impl *this, struct node *node) { emit_node_props(this, node, false); } static void emit_info(struct impl *this, bool full); static float get_soft_volume_boost(struct node *node) { const struct media_codec *codec = node->transport ? node->transport->media_codec : NULL; /* * For A2DP duplex, the duplex microphone channel sometimes does not appear * to have hardware gain, and input volume is very low. * * Work around this by boosting the software volume level, i.e. adjust * the scale on the user-visible volume control to something more sensible. * If this causes clipping, the user can just reduce the mic volume to * bring SW gain below 1. */ if (node->a2dp_duplex && node->transport && codec && codec->info && spa_atob(spa_dict_lookup(codec->info, "duplex.boost")) && !(node->id & SINK_ID_FLAG) && !node->transport->volumes[SPA_BT_VOLUME_ID_RX].active) return 10.0f; /* 20 dB boost */ /* In all other cases, no boost */ return 1.0f; } static float node_get_hw_volume(struct node *node) { uint32_t i; float hw_volume = 0.0f; for (i = 0; i < node->n_channels; i++) hw_volume = SPA_MAX(node->volumes[i], hw_volume); return SPA_MIN(hw_volume, 1.0f); } static void node_update_soft_volumes(struct node *node, float hw_volume) { for (uint32_t i = 0; i < node->n_channels; ++i) { node->soft_volumes[i] = hw_volume > 0.0f ? node->volumes[i] / hw_volume : 0.0f; } } static int get_volume_id(int node_id) { return (node_id & SINK_ID_FLAG) ? SPA_BT_VOLUME_ID_TX : SPA_BT_VOLUME_ID_RX; } static bool node_update_volume_from_transport(struct node *node, bool reset) { struct impl *impl = node->impl; int volume_id = get_volume_id(node->id); struct spa_bt_transport_volume *t_volume; float prev_hw_volume; if (!node->active || !node->transport || !spa_bt_transport_volume_enabled(node->transport)) return false; /* PW is the controller for remote device. */ if (impl->profile != DEVICE_PROFILE_A2DP && impl->profile != DEVICE_PROFILE_BAP && impl->profile != DEVICE_PROFILE_BAP_SINK && impl->profile != DEVICE_PROFILE_BAP_SOURCE && impl->profile != DEVICE_PROFILE_HSP_HFP) return false; t_volume = &node->transport->volumes[volume_id]; if (!t_volume->active) return false; prev_hw_volume = node_get_hw_volume(node); if (!reset) { for (uint32_t i = 0; i < node->n_channels; ++i) { node->volumes[i] = prev_hw_volume > 0.0f ? node->volumes[i] * t_volume->volume / prev_hw_volume : t_volume->volume; } } else { for (uint32_t i = 0; i < node->n_channels; ++i) node->volumes[i] = t_volume->volume; } node_update_soft_volumes(node, t_volume->volume); /* * Consider volume changes from the headset as requested * by the user, and to be saved by the SM. */ node->save = true; return true; } static void volume_changed(void *userdata) { struct node *node = userdata; struct impl *impl = node->impl; if (!node_update_volume_from_transport(node, false)) return; emit_volume(impl, node); impl->info.change_mask |= SPA_DEVICE_CHANGE_MASK_PARAMS; impl->params[IDX_Route].flags ^= SPA_PARAM_INFO_SERIAL; emit_info(impl, false); } static const struct spa_bt_transport_events transport_events = { SPA_VERSION_BT_DEVICE_EVENTS, .destroy = transport_destroy, .volume_changed = volume_changed, }; static int node_offload_set_active(struct node *node, bool active) { int res = 0; if (node->transport == NULL || !node->active) return -ENOTSUP; if (active && !node->offload_acquired) res = spa_bt_transport_acquire(node->transport, false); else if (!active && node->offload_acquired) res = spa_bt_transport_release(node->transport); if (res >= 0) node->offload_acquired = active; return res; } static void get_channels(struct spa_bt_transport *t, bool a2dp_duplex, uint32_t *n_channels, uint32_t *channels) { const struct media_codec *codec; struct spa_audio_info info = { 0 }; if (!a2dp_duplex || !t->media_codec || !t->media_codec->duplex_codec) { *n_channels = t->n_channels; memcpy(channels, t->channels, t->n_channels * sizeof(uint32_t)); return; } codec = t->media_codec->duplex_codec; if (!codec->validate_config || codec->validate_config(codec, 0, t->configuration, t->configuration_len, &info) < 0) { *n_channels = 1; channels[0] = SPA_AUDIO_CHANNEL_MONO; return; } *n_channels = info.info.raw.channels; memcpy(channels, info.info.raw.position, info.info.raw.channels * sizeof(uint32_t)); } static const char *get_channel_name(uint32_t channel) { return spa_type_to_short_name(channel, spa_type_audio_channel, NULL); } static int channel_position_cmp(const void *pa, const void *pb) { uint32_t a = *(uint32_t *)pa, b = *(uint32_t *)pb; return (int)a - (int)b; } static void emit_device_set_node(struct impl *this, uint32_t id) { struct spa_bt_device *device = this->bt_dev; struct node *node = &this->nodes[id]; struct spa_device_object_info info; struct spa_dict_item items[9]; char str_id[32], members_json[8192], channels_json[512]; struct device_set_member *members; uint32_t n_members; uint32_t n_items = 0; struct spa_strbuf json; unsigned int i; items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_API_BLUEZ5_ADDRESS, device->address); items[n_items++] = SPA_DICT_ITEM_INIT("api.bluez5.set", this->device_set.path); items[n_items++] = SPA_DICT_ITEM_INIT("api.bluez5.set.leader", "true"); snprintf(str_id, sizeof(str_id), "%d", id); items[n_items++] = SPA_DICT_ITEM_INIT("card.profile.device", str_id); items[n_items++] = SPA_DICT_ITEM_INIT("device.routes", "1"); if (id == DEVICE_ID_SOURCE_SET) { items[n_items++] = SPA_DICT_ITEM_INIT("media.class", "Audio/Source"); members = this->device_set.source; n_members = this->device_set.sources; } else if (id == DEVICE_ID_SINK_SET) { items[n_items++] = SPA_DICT_ITEM_INIT("media.class", "Audio/Sink"); members = this->device_set.sink; n_members = this->device_set.sinks; } else { spa_assert_not_reached(); } node->impl = this; node->active = true; node->transport = NULL; node->a2dp_duplex = false; node->offload_acquired = false; node->mute = false; node->save = false; node->latency_offset = 0; /* Form channel map from members */ node->n_channels = 0; for (i = 0; i < n_members; ++i) { struct spa_bt_transport *t = members[i].transport; unsigned int j; for (j = 0; j < t->n_channels; ++j) { unsigned int k; if (!get_channel_name(t->channels[j])) continue; for (k = 0; k < node->n_channels; ++k) { if (node->channels[k] == t->channels[j]) break; } if (k == node->n_channels && node->n_channels < MAX_CHANNELS) node->channels[node->n_channels++] = t->channels[j]; } } qsort(node->channels, node->n_channels, sizeof(uint32_t), channel_position_cmp); for (i = 0; i < node->n_channels; ++i) { /* Session manager will override this, so put in some safe number */ node->volumes[i] = node->soft_volumes[i] = 0.064f; } /* Produce member info json */ spa_strbuf_init(&json, members_json, sizeof(members_json)); spa_strbuf_append(&json, "["); for (i = 0; i < n_members; ++i) { struct spa_bt_transport *t = members[i].transport; uint32_t member_id = members[i].id; char object_path[512]; unsigned int j; if (i > 0) spa_strbuf_append(&json, ","); spa_scnprintf(object_path, sizeof(object_path), "%s/%s-%"PRIu32, this->device_set.path, t->device->address, member_id); spa_strbuf_append(&json, "{\"object.path\":\"%s\",\"channels\":[", object_path); for (j = 0; j < t->n_channels; ++j) { if (j > 0) spa_strbuf_append(&json, ","); spa_strbuf_append(&json, "\"%s\"", get_channel_name(t->channels[j])); } spa_strbuf_append(&json, "]}"); } spa_strbuf_append(&json, "]"); json.buffer[SPA_MIN(json.pos, json.maxsize-1)] = 0; items[n_items++] = SPA_DICT_ITEM_INIT("api.bluez5.set.members", members_json); spa_strbuf_init(&json, channels_json, sizeof(channels_json)); spa_strbuf_append(&json, "["); for (i = 0; i < node->n_channels; ++i) { if (i > 0) spa_strbuf_append(&json, ","); spa_strbuf_append(&json, "\"%s\"", get_channel_name(node->channels[i])); } spa_strbuf_append(&json, "]"); json.buffer[SPA_MIN(json.pos, json.maxsize-1)] = 0; items[n_items++] = SPA_DICT_ITEM_INIT("api.bluez5.set.channels", channels_json); /* Emit */ info = SPA_DEVICE_OBJECT_INFO_INIT(); info.type = SPA_TYPE_INTERFACE_Node; info.factory_name = (id == DEVICE_ID_SOURCE_SET) ? "source" : "sink"; info.change_mask = SPA_DEVICE_OBJECT_CHANGE_MASK_PROPS; info.props = &SPA_DICT_INIT(items, n_items); spa_device_emit_object_info(&this->hooks, id, &info); emit_node_props(this, &this->nodes[id], true); } static void emit_node(struct impl *this, struct spa_bt_transport *t, uint32_t id, const char *factory_name, bool a2dp_duplex) { struct spa_bt_device *device = this->bt_dev; struct spa_device_object_info info; struct spa_dict_item items[13]; uint32_t n_items = 0; char transport[32], str_id[32], object_path[512]; bool is_dyn_node = SPA_FLAG_IS_SET(id, DYNAMIC_NODE_ID_FLAG); bool in_device_set = false; spa_log_debug(this->log, "%p: node, transport:%p id:%08x factory:%s", this, t, id, factory_name); if (id & SINK_ID_FLAG) in_device_set = this->device_set.sink_enabled; else in_device_set = this->device_set.source_enabled; snprintf(transport, sizeof(transport), "pointer:%p", t); items[0] = SPA_DICT_ITEM_INIT(SPA_KEY_API_BLUEZ5_TRANSPORT, transport); items[1] = SPA_DICT_ITEM_INIT(SPA_KEY_API_BLUEZ5_PROFILE, spa_bt_profile_name(t->profile)); items[2] = SPA_DICT_ITEM_INIT(SPA_KEY_API_BLUEZ5_CODEC, get_codec_name(t, a2dp_duplex)); items[3] = SPA_DICT_ITEM_INIT(SPA_KEY_API_BLUEZ5_ADDRESS, device->address); items[4] = SPA_DICT_ITEM_INIT("device.routes", "1"); n_items = 5; if (!is_dyn_node && !in_device_set) { snprintf(str_id, sizeof(str_id), "%d", id); items[n_items] = SPA_DICT_ITEM_INIT("card.profile.device", str_id); n_items++; } if (spa_streq(spa_bt_profile_name(t->profile), "headset-head-unit")) { items[n_items] = SPA_DICT_ITEM_INIT("device.intended-roles", "Communication"); n_items++; } if (a2dp_duplex) { items[n_items] = SPA_DICT_ITEM_INIT("api.bluez5.a2dp-duplex", "true"); n_items++; } if (in_device_set) { items[n_items] = SPA_DICT_ITEM_INIT("api.bluez5.set", this->device_set.path); n_items++; items[n_items] = SPA_DICT_ITEM_INIT("api.bluez5.internal", "true"); n_items++; /* object.path can be used in match rules with only basic node props */ spa_scnprintf(object_path, sizeof(object_path), "%s/%s-%d", this->device_set.path, device->address, id); items[n_items] = SPA_DICT_ITEM_INIT("object.path", object_path); n_items++; } info = SPA_DEVICE_OBJECT_INFO_INIT(); info.type = SPA_TYPE_INTERFACE_Node; info.factory_name = factory_name; info.change_mask = SPA_DEVICE_OBJECT_CHANGE_MASK_PROPS; info.props = &SPA_DICT_INIT(items, n_items); SPA_FLAG_CLEAR(id, DYNAMIC_NODE_ID_FLAG); spa_assert(id < SPA_N_ELEMENTS(this->nodes)); spa_device_emit_object_info(&this->hooks, id, &info); if (in_device_set) { /* Device set member nodes don't have their own routes */ this->nodes[id].impl = this; this->nodes[id].active = false; if (this->nodes[id].transport) spa_hook_remove(&this->nodes[id].transport_listener); this->nodes[id].transport = NULL; return; } if (!is_dyn_node) { uint32_t prev_channels = this->nodes[id].n_channels; float boost; this->nodes[id].impl = this; this->nodes[id].active = true; this->nodes[id].offload_acquired = false; this->nodes[id].a2dp_duplex = a2dp_duplex; get_channels(t, a2dp_duplex, &this->nodes[id].n_channels, this->nodes[id].channels); if (this->nodes[id].transport) spa_hook_remove(&this->nodes[id].transport_listener); this->nodes[id].transport = t; spa_bt_transport_add_listener(t, &this->nodes[id].transport_listener, &transport_events, &this->nodes[id]); if (prev_channels > 0) { size_t i; /* Spread mono volume to all channels, if we had switched HFP -> A2DP. */ for (i = prev_channels; i < this->nodes[id].n_channels; ++i) this->nodes[id].volumes[i] = this->nodes[id].volumes[i % prev_channels]; } node_update_volume_from_transport(&this->nodes[id], true); boost = get_soft_volume_boost(&this->nodes[id]); if (boost != 1.0f) { size_t i; for (i = 0; i < this->nodes[id].n_channels; ++i) this->nodes[id].soft_volumes[i] = this->nodes[id].volumes[i] * boost; } emit_node_props(this, &this->nodes[id], true); } } static void init_dummy_input_node(struct impl *this, uint32_t id) { uint32_t prev_channels = this->nodes[id].n_channels; /* Don't emit a device node, only initialize volume etc. for the route */ spa_log_debug(this->log, "%p: node, id:%08x", this, id); this->nodes[id].impl = this; this->nodes[id].active = true; this->nodes[id].offload_acquired = false; this->nodes[id].a2dp_duplex = false; this->nodes[id].n_channels = 1; this->nodes[id].channels[0] = SPA_AUDIO_CHANNEL_MONO; if (prev_channels > 0) { size_t i; /* Spread mono volume to all channels */ for (i = prev_channels; i < this->nodes[id].n_channels; ++i) this->nodes[id].volumes[i] = this->nodes[id].volumes[i % prev_channels]; } } static bool transport_enabled(struct spa_bt_transport *t, int profile) { return (t->profile & t->device->connected_profiles) && (t->profile & profile) == t->profile; } static struct spa_bt_transport *find_device_transport(struct spa_bt_device *device, int profile) { struct spa_bt_transport *t; spa_list_for_each(t, &device->transport_list, device_link) { if (transport_enabled(t, profile)) return t; } return NULL; } static struct spa_bt_transport *find_transport(struct impl *this, int profile) { return find_device_transport(this->bt_dev, profile); } static void dynamic_node_transport_destroy(void *data) { struct dynamic_node *this = data; spa_log_debug(this->impl->log, "transport %p destroy", this->transport); this->transport = NULL; } static void dynamic_node_transport_state_changed(void *data, enum spa_bt_transport_state old, enum spa_bt_transport_state state) { struct dynamic_node *this = data; struct impl *impl = this->impl; struct spa_bt_transport *t = this->transport; spa_log_debug(impl->log, "transport %p state %d->%d", t, old, state); if (state >= SPA_BT_TRANSPORT_STATE_PENDING && old < SPA_BT_TRANSPORT_STATE_PENDING) { if (!SPA_FLAG_IS_SET(this->id, DYNAMIC_NODE_ID_FLAG)) { SPA_FLAG_SET(this->id, DYNAMIC_NODE_ID_FLAG); spa_bt_transport_keepalive(t, true); emit_node(impl, t, this->id, this->factory_name, this->a2dp_duplex); } } else if (state < SPA_BT_TRANSPORT_STATE_PENDING && old >= SPA_BT_TRANSPORT_STATE_PENDING) { if (SPA_FLAG_IS_SET(this->id, DYNAMIC_NODE_ID_FLAG)) { SPA_FLAG_CLEAR(this->id, DYNAMIC_NODE_ID_FLAG); spa_bt_transport_keepalive(t, false); spa_device_emit_object_info(&impl->hooks, this->id, NULL); } } } static void dynamic_node_volume_changed(void *data) { struct dynamic_node *node = data; struct impl *impl = node->impl; struct spa_event *event; uint8_t buffer[4096]; struct spa_pod_builder b = { 0 }; struct spa_pod_frame f[1]; struct spa_bt_transport_volume *t_volume; int id = node->id, volume_id; SPA_FLAG_CLEAR(id, DYNAMIC_NODE_ID_FLAG); /* Remote device is the controller */ if (!node->transport || impl->profile != DEVICE_PROFILE_AG || !spa_bt_transport_volume_enabled(node->transport)) return; if (id == 0 || id == 2) volume_id = SPA_BT_VOLUME_ID_RX; else if (id == 1) volume_id = SPA_BT_VOLUME_ID_TX; else return; t_volume = &node->transport->volumes[volume_id]; if (!t_volume->active) return; spa_pod_builder_init(&b, buffer, sizeof(buffer)); spa_pod_builder_push_object(&b, &f[0], SPA_TYPE_EVENT_Device, SPA_DEVICE_EVENT_ObjectConfig); spa_pod_builder_prop(&b, SPA_EVENT_DEVICE_Object, 0); spa_pod_builder_int(&b, id); spa_pod_builder_prop(&b, SPA_EVENT_DEVICE_Props, 0); spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_Props, SPA_EVENT_DEVICE_Props, SPA_PROP_volume, SPA_POD_Float(t_volume->volume)); event = spa_pod_builder_pop(&b, &f[0]); spa_log_debug(impl->log, "dynamic node %d: volume %d changed %f, profile %d", node->id, volume_id, t_volume->volume, node->transport->profile); /* Dynamic node doesn't has route, we can only set volume on adaptar node. */ spa_device_emit_event(&impl->hooks, event); } static const struct spa_bt_transport_events dynamic_node_transport_events = { SPA_VERSION_BT_TRANSPORT_EVENTS, .destroy = dynamic_node_transport_destroy, .state_changed = dynamic_node_transport_state_changed, .volume_changed = dynamic_node_volume_changed, }; static void emit_dynamic_node(struct impl *impl, struct spa_bt_transport *t, uint32_t id, const char *factory_name, bool a2dp_duplex) { struct dynamic_node *this = &impl->dyn_nodes[id]; spa_assert(id < SPA_N_ELEMENTS(impl->dyn_nodes)); spa_log_debug(impl->log, "%p: dynamic node, transport: %p->%p id: %08x->%08x", this, this->transport, t, this->id, id); if (this->transport) { /* Session manager don't really handles transport ptr changing. */ spa_assert(this->transport == t); spa_hook_remove(&this->transport_listener); } this->impl = impl; this->transport = t; this->id = id; this->factory_name = factory_name; this->a2dp_duplex = a2dp_duplex; spa_bt_transport_add_listener(this->transport, &this->transport_listener, &dynamic_node_transport_events, this); /* emits the node if the state is already pending */ dynamic_node_transport_state_changed (this, SPA_BT_TRANSPORT_STATE_IDLE, t->state); } static void remove_dynamic_node(struct dynamic_node *this) { if (this->transport == NULL) return; /* destroy the node, if it exists */ dynamic_node_transport_state_changed (this, this->transport->state, SPA_BT_TRANSPORT_STATE_IDLE); spa_hook_remove(&this->transport_listener); this->impl = NULL; this->transport = NULL; this->id = 0; this->factory_name = NULL; } static void device_set_clear(struct impl *impl, struct device_set *set) { unsigned int i; for (i = 0; i < SPA_N_ELEMENTS(set->sink); ++i) if (set->sink[i].transport) spa_hook_remove(&set->sink[i].listener); for (i = 0; i < SPA_N_ELEMENTS(set->source); ++i) if (set->source[i].transport) spa_hook_remove(&set->source[i].listener); free(set->path); spa_zero(*set); set->impl = impl; for (i = 0; i < SPA_N_ELEMENTS(set->sink); ++i) set->sink[i].impl = impl; for (i = 0; i < SPA_N_ELEMENTS(set->source); ++i) set->source[i].impl = impl; } static void device_set_transport_destroy(void *data) { struct device_set_member *member = data; member->transport = NULL; spa_hook_remove(&member->listener); } static const struct spa_bt_transport_events device_set_transport_events = { SPA_VERSION_BT_DEVICE_EVENTS, .destroy = device_set_transport_destroy, }; static void device_set_update_asha(struct impl *this, struct device_set *dset) { struct spa_bt_device *device = this->bt_dev; struct spa_bt_set_membership *set; struct spa_bt_set_membership tmp_set = { .device = device, .rank = 0, .leader = true, .path = device->path, .others = SPA_LIST_INIT(&tmp_set.others), }; struct spa_list tmp_set_list = SPA_LIST_INIT(&tmp_set_list); struct spa_list *membership_list = &device->set_membership_list; /* * If no device set, use a dummy one, so that we can handle also those devices * here (they may have multiple transports regardless). */ if (spa_list_is_empty(membership_list)) { spa_list_append(&tmp_set_list, &tmp_set.link); membership_list = &tmp_set_list; } spa_list_for_each(set, membership_list, link) { struct spa_bt_set_membership *s; int num_devices = 0; device_set_clear(this, dset); spa_bt_for_each_set_member(s, set) { struct spa_bt_transport *t; bool active = false; uint32_t sink_id = DEVICE_ID_SINK; if (!(s->device->connected_profiles & SPA_BT_PROFILE_ASHA_SINK)) continue; spa_list_for_each(t, &s->device->transport_list, device_link) { if (!transport_enabled(t, SPA_BT_PROFILE_ASHA_SINK)) continue; if (dset->sinks >= SPA_N_ELEMENTS(dset->sink)) break; active = true; dset->leader = set->leader = t->asha_right_side; dset->path = strdup(set->path); dset->sink[dset->sinks].impl = this; dset->sink[dset->sinks].transport = t; dset->sink[dset->sinks].id = sink_id; sink_id += 2; spa_bt_transport_add_listener(t, &dset->sink[dset->sinks].listener, &device_set_transport_events, &dset->sink[dset->sinks]); ++dset->sinks; } if (active) ++num_devices; } if (dset == &this->device_set) spa_log_debug(this->log, "%p: %s belongs to ASHA set %s leader:%d", this, device->path, set->path, set->leader); if (num_devices > 1) break; } dset->sink_enabled = dset->path && (dset->sinks > 1); } static void device_set_update_bap(struct impl *this, struct device_set *dset) { struct spa_bt_device *device = this->bt_dev; struct spa_bt_set_membership *set; struct spa_bt_set_membership tmp_set = { .device = device, .rank = 0, .leader = true, .path = device->path, .others = SPA_LIST_INIT(&tmp_set.others), }; struct spa_list tmp_set_list = SPA_LIST_INIT(&tmp_set_list); struct spa_list *membership_list = &device->set_membership_list; /* * If no device set, use a dummy one, so that we can handle also those devices * here (they may have multiple transports regardless). */ if (spa_list_is_empty(membership_list)) { spa_list_append(&tmp_set_list, &tmp_set.link); membership_list = &tmp_set_list; } spa_list_for_each(set, membership_list, link) { struct spa_bt_set_membership *s; int num_devices = 0; device_set_clear(this, dset); spa_bt_for_each_set_member(s, set) { struct spa_bt_transport *t; bool active = false; uint32_t source_id = DEVICE_ID_SOURCE; uint32_t sink_id = DEVICE_ID_SINK; if (!(s->device->connected_profiles & SPA_BT_PROFILE_BAP_DUPLEX)) continue; spa_list_for_each(t, &s->device->transport_list, device_link) { if (!(s->device->connected_profiles & SPA_BT_PROFILE_BAP_SOURCE)) continue; if (!transport_enabled(t, SPA_BT_PROFILE_BAP_SOURCE)) continue; if (dset->sources >= SPA_N_ELEMENTS(dset->source)) break; active = true; dset->source[dset->sources].impl = this; dset->source[dset->sources].transport = t; dset->source[dset->sources].id = source_id; source_id += 2; spa_bt_transport_add_listener(t, &dset->source[dset->sources].listener, &device_set_transport_events, &dset->source[dset->sources]); ++dset->sources; } spa_list_for_each(t, &s->device->transport_list, device_link) { if (!(s->device->connected_profiles & SPA_BT_PROFILE_BAP_SINK)) continue; if (!transport_enabled(t, SPA_BT_PROFILE_BAP_SINK)) continue; if (dset->sinks >= SPA_N_ELEMENTS(dset->sink)) break; active = true; dset->sink[dset->sinks].impl = this; dset->sink[dset->sinks].transport = t; dset->sink[dset->sinks].id = sink_id; sink_id += 2; spa_bt_transport_add_listener(t, &dset->sink[dset->sinks].listener, &device_set_transport_events, &dset->sink[dset->sinks]); ++dset->sinks; } if (active) ++num_devices; } if (dset == &this->device_set) spa_log_debug(this->log, "%p: %s belongs to set %s leader:%d", this, device->path, set->path, set->leader); if (is_bap_client(this)) { dset->path = strdup(set->path); dset->leader = set->leader; } else { /* XXX: device set nodes for BAP server not supported, * XXX: it'll appear as multiple streams */ dset->path = NULL; dset->leader = false; } if (num_devices > 1) break; } dset->sink_enabled = dset->path && (dset->sinks > 1); dset->source_enabled = dset->path && (dset->sources > 1); } static void device_set_update(struct impl *this, struct device_set *dset, int profile) { if (profile_is_bap(this->profile)) device_set_update_bap(this, dset); else if (profile == DEVICE_PROFILE_ASHA) device_set_update_asha(this, dset); else device_set_clear(this, dset); } static void device_set_get_dset_info(const struct device_set *dset, int *n_set_sink, int *n_set_source) { if (dset->sink_enabled) *n_set_sink = dset->leader ? 1 : 0; if (dset->source_enabled) *n_set_source = dset->leader ? 1 : 0; } static void device_set_get_info(struct impl *this, uint32_t profile, int *n_set_sink, int *n_set_source) { struct device_set dset = { .impl = this }; *n_set_sink = -1; *n_set_source = -1; if (profile == this->profile) { device_set_get_dset_info(&this->device_set, n_set_sink, n_set_source); } else if (profile != SPA_ID_INVALID) { device_set_update(this, &dset, profile); device_set_get_dset_info(&dset, n_set_sink, n_set_source); device_set_clear(this, &dset); } else { device_set_update(this, &dset, DEVICE_PROFILE_BAP); device_set_get_dset_info(&dset, n_set_sink, n_set_source); device_set_clear(this, &dset); device_set_update(this, &dset, DEVICE_PROFILE_ASHA); device_set_get_dset_info(&dset, n_set_sink, n_set_source); device_set_clear(this, &dset); } } static bool device_set_equal(struct device_set *a, struct device_set *b) { unsigned int i; if (!spa_streq(a->path, b->path) || a->sink_enabled != b->sink_enabled || a->source_enabled != b->source_enabled || a->leader != b->leader || a->sinks != b->sinks || a->sources != b->sources) return false; for (i = 0; i < a->sinks; ++i) if (a->sink[i].transport != b->sink[i].transport) return false; for (i = 0; i < a->sources; ++i) if (a->source[i].transport != b->source[i].transport) return false; return true; } static int emit_nodes(struct impl *this) { struct spa_bt_transport *t; switch (this->profile) { case DEVICE_PROFILE_BAP: case DEVICE_PROFILE_BAP_SINK: case DEVICE_PROFILE_BAP_SOURCE: if (this->switching_codec_other) return -EBUSY; break; } this->props.codec = 0; device_set_update(this, &this->device_set, this->profile); switch (this->profile) { case DEVICE_PROFILE_OFF: break; case DEVICE_PROFILE_AG: if (this->bt_dev->connected_profiles & SPA_BT_PROFILE_HEADSET_AUDIO_GATEWAY) { t = find_transport(this, SPA_BT_PROFILE_HFP_AG); if (!t) t = find_transport(this, SPA_BT_PROFILE_HSP_AG); if (t) { this->props.codec = t->media_codec->id; emit_dynamic_node(this, t, 0, SPA_NAME_API_BLUEZ5_SCO_SOURCE, false); emit_dynamic_node(this, t, 1, SPA_NAME_API_BLUEZ5_SCO_SINK, false); } } if (this->bt_dev->connected_profiles & (SPA_BT_PROFILE_A2DP_SOURCE)) { t = find_transport(this, SPA_BT_PROFILE_A2DP_SOURCE); if (t) { this->props.codec = t->media_codec->id; emit_dynamic_node(this, t, 2, SPA_NAME_API_BLUEZ5_A2DP_SOURCE, false); if (t->media_codec->duplex_codec) emit_dynamic_node(this, t, 3, SPA_NAME_API_BLUEZ5_A2DP_SINK, true); } } break; case DEVICE_PROFILE_ASHA: if (this->bt_dev->connected_profiles & SPA_BT_PROFILE_ASHA_SINK) { struct device_set *set = &this->device_set; t = find_transport(this, SPA_BT_PROFILE_ASHA_SINK); if (t) { this->props.codec = t->media_codec->id; emit_node(this, t, DEVICE_ID_SINK, SPA_NAME_API_BLUEZ5_MEDIA_SINK, false); if (set->sink_enabled && set->leader) emit_device_set_node(this, DEVICE_ID_SINK_SET); } else { spa_log_warn(this->log, "Unable to find transport for ASHA"); } } break; case DEVICE_PROFILE_A2DP: if (this->bt_dev->connected_profiles & SPA_BT_PROFILE_A2DP_SOURCE) { t = find_transport(this, SPA_BT_PROFILE_A2DP_SOURCE); if (t) { this->props.codec = t->media_codec->id; emit_dynamic_node(this, t, DEVICE_ID_SOURCE, SPA_NAME_API_BLUEZ5_A2DP_SOURCE, false); if (t->media_codec->duplex_codec) emit_node(this, t, DEVICE_ID_SINK, SPA_NAME_API_BLUEZ5_A2DP_SINK, true); } } if (this->bt_dev->connected_profiles & SPA_BT_PROFILE_A2DP_SINK) { t = find_transport(this, SPA_BT_PROFILE_A2DP_SINK); if (t) { this->props.codec = t->media_codec->id; emit_node(this, t, DEVICE_ID_SINK, SPA_NAME_API_BLUEZ5_A2DP_SINK, false); if (t->media_codec->duplex_codec) { emit_node(this, t, DEVICE_ID_SOURCE, SPA_NAME_API_BLUEZ5_A2DP_SOURCE, true); } } } /* Setup route for HFP input, for tracking its volume even though there is * no node emitted yet. */ if ((this->bt_dev->connected_profiles & SPA_BT_PROFILE_HEADSET_HEAD_UNIT) && !(this->bt_dev->connected_profiles & SPA_BT_PROFILE_A2DP_SOURCE) && !this->nodes[DEVICE_ID_SOURCE].active) init_dummy_input_node(this, DEVICE_ID_SOURCE); if (!this->props.codec) this->props.codec = SPA_BLUETOOTH_AUDIO_CODEC_SBC; break; case DEVICE_PROFILE_BAP: case DEVICE_PROFILE_BAP_SINK: case DEVICE_PROFILE_BAP_SOURCE: { struct device_set *set = &this->device_set; unsigned int i; for (i = 0; i < set->sources; ++i) { struct spa_bt_transport *t = set->source[i].transport; uint32_t id = set->source[i].id; if (id >= MAX_NODES) continue; if (t->device != this->bt_dev) continue; this->props.codec = t->media_codec->id; if (t->bap_initiator) emit_node(this, t, id, SPA_NAME_API_BLUEZ5_MEDIA_SOURCE, false); else emit_dynamic_node(this, t, id, SPA_NAME_API_BLUEZ5_MEDIA_SOURCE, false); } if (set->source_enabled && set->leader) emit_device_set_node(this, DEVICE_ID_SOURCE_SET); for (i = 0; i < set->sinks; ++i) { struct spa_bt_transport *t = set->sink[i].transport; uint32_t id = set->sink[i].id; if (id >= MAX_NODES) continue; if (t->device != this->bt_dev) continue; this->props.codec = t->media_codec->id; if (t->bap_initiator) emit_node(this, t, id, SPA_NAME_API_BLUEZ5_MEDIA_SINK, false); else emit_dynamic_node(this, t, id, SPA_NAME_API_BLUEZ5_MEDIA_SINK, false); } if (set->sink_enabled && set->leader) emit_device_set_node(this, DEVICE_ID_SINK_SET); if (this->bt_dev->connected_profiles & (SPA_BT_PROFILE_BAP_BROADCAST_SINK)) { t = find_transport(this, SPA_BT_PROFILE_BAP_BROADCAST_SINK); if (t) { this->props.codec = t->media_codec->id; emit_node(this, t, DEVICE_ID_SINK, SPA_NAME_API_BLUEZ5_MEDIA_SINK, false); } } if (this->bt_dev->connected_profiles & (SPA_BT_PROFILE_BAP_BROADCAST_SOURCE)) { t = find_transport(this, SPA_BT_PROFILE_BAP_BROADCAST_SOURCE); if (t) { this->props.codec = t->media_codec->id; emit_dynamic_node(this, t, DEVICE_ID_SOURCE, SPA_NAME_API_BLUEZ5_MEDIA_SOURCE, false); } } if (!this->props.codec) this->props.codec = SPA_BLUETOOTH_AUDIO_CODEC_LC3; break; }; case DEVICE_PROFILE_HSP_HFP: if (this->bt_dev->connected_profiles & SPA_BT_PROFILE_HEADSET_HEAD_UNIT) { t = find_transport(this, SPA_BT_PROFILE_HFP_HF); if (!t) t = find_transport(this, SPA_BT_PROFILE_HSP_HS); if (t) { this->props.codec = t->media_codec->id; emit_node(this, t, DEVICE_ID_SOURCE, SPA_NAME_API_BLUEZ5_SCO_SOURCE, false); emit_node(this, t, DEVICE_ID_SINK, SPA_NAME_API_BLUEZ5_SCO_SINK, false); } } if (!this->props.codec) this->props.codec = SPA_BLUETOOTH_AUDIO_CODEC_CVSD; break; default: return -EINVAL; } return 0; } static const struct spa_dict_item info_items[] = { { SPA_KEY_DEVICE_API, "bluez5" }, { SPA_KEY_DEVICE_BUS, "bluetooth" }, { SPA_KEY_MEDIA_CLASS, "Audio/Device" }, }; static void emit_info(struct impl *this, bool full) { uint64_t old = full ? this->info.change_mask : 0; if (full) this->info.change_mask = this->info_all; if (this->info.change_mask) { this->info.props = &SPA_DICT_INIT_ARRAY(info_items); spa_device_emit_info(&this->hooks, &this->info); this->info.change_mask = old; } } static void emit_remove_nodes(struct impl *this) { spa_log_debug(this->log, "%p: remove nodes", this); for (uint32_t i = 0; i < SPA_N_ELEMENTS(this->dyn_nodes); i++) remove_dynamic_node (&this->dyn_nodes[i]); for (uint32_t i = 0; i < SPA_N_ELEMENTS(this->nodes); i++) { struct node * node = &this->nodes[i]; node_offload_set_active(node, false); if (node->transport) { spa_hook_remove(&node->transport_listener); node->transport = NULL; } if (node->active) { spa_device_emit_object_info(&this->hooks, i, NULL); node->active = false; } } this->props.offload_active = false; } static bool validate_profile(struct impl *this, uint32_t profile, enum spa_bluetooth_audio_codec codec); static int set_profile(struct impl *this, uint32_t profile, enum spa_bluetooth_audio_codec codec, bool save) { if (!validate_profile(this, profile, codec)) { spa_log_warn(this->log, "trying to set invalid profile %d, codec %d, %08x %08x", profile, codec, this->bt_dev->profiles, this->bt_dev->connected_profiles); return -EINVAL; } this->save_profile = save; if (this->profile == profile && (this->profile != DEVICE_PROFILE_ASHA || codec == this->props.codec) && (this->profile != DEVICE_PROFILE_A2DP || codec == this->props.codec) && (!profile_is_bap(this->profile) || codec == this->props.codec) && (this->profile != DEVICE_PROFILE_HSP_HFP || codec == this->props.codec)) return 0; emit_remove_nodes(this); spa_bt_device_release_transports(this->bt_dev); this->profile = profile; this->prev_bt_connected_profiles = this->bt_dev->connected_profiles; /* * A2DP/BAP: ensure there's a transport with the selected codec (0 means any). * Don't try to switch codecs when the device is in the A2DP source role, since * devices do not appear to like that. * * For BAP, only BAP client can configure the codec. * * XXX: codec switching also currently does not work in the duplex or * XXX: source-only case, as it will only switch the sink, and we only * XXX: list the sink codecs here. TODO: fix this */ if ((profile == DEVICE_PROFILE_A2DP || (profile_is_bap(profile) && is_bap_client(this))) && !(this->bt_dev->connected_profiles & SPA_BT_PROFILE_A2DP_SOURCE)) { int ret; const struct media_codec *codecs[64]; uint32_t profiles; get_media_codecs(this, codec, codecs, SPA_N_ELEMENTS(codecs)); this->switching_codec = true; switch (profile) { case DEVICE_PROFILE_BAP_SINK: profiles = SPA_BT_PROFILE_BAP_SINK; break; case DEVICE_PROFILE_BAP_SOURCE: profiles = SPA_BT_PROFILE_BAP_SOURCE; break; case DEVICE_PROFILE_BAP: profiles = this->bt_dev->profiles & SPA_BT_PROFILE_BAP_DUPLEX; break; case DEVICE_PROFILE_A2DP: profiles = this->bt_dev->profiles & SPA_BT_PROFILE_A2DP_DUPLEX; break; default: profiles = 0; break; } ret = spa_bt_device_ensure_media_codec(this->bt_dev, codecs, profiles); if (ret < 0) { if (ret != -ENOTSUP) spa_log_error(this->log, "failed to switch codec (%d), setting basic profile", ret); } else { return 0; } } else if (profile == DEVICE_PROFILE_HSP_HFP) { int ret; const struct media_codec *media_codec = get_supported_media_codec(this, codec, NULL, SPA_BT_PROFILE_HEADSET_AUDIO); this->switching_codec = true; ret = spa_bt_device_ensure_hfp_codec(this->bt_dev, media_codec); if (ret < 0) { if (ret != -ENOTSUP) spa_log_error(this->log, "failed to switch codec (%d), setting basic profile", ret); } else { return 0; } } this->switching_codec = false; emit_nodes(this); this->info.change_mask |= SPA_DEVICE_CHANGE_MASK_PARAMS; this->params[IDX_Profile].flags ^= SPA_PARAM_INFO_SERIAL; this->params[IDX_Route].flags ^= SPA_PARAM_INFO_SERIAL; this->params[IDX_EnumRoute].flags ^= SPA_PARAM_INFO_SERIAL; this->params[IDX_Props].flags ^= SPA_PARAM_INFO_SERIAL; this->params[IDX_PropInfo].flags ^= SPA_PARAM_INFO_SERIAL; emit_info(this, false); return 0; } static void codec_switched(void *userdata, int status) { struct impl *this = userdata; spa_log_debug(this->log, "codec switched (status %d)", status); this->switching_codec = false; if (status < 0) spa_log_error(this->log, "failed to switch codec (%d)", status); emit_remove_nodes(this); emit_nodes(this); this->info.change_mask |= SPA_DEVICE_CHANGE_MASK_PARAMS; if ((this->prev_bt_connected_profiles ^ this->bt_dev->connected_profiles) & ~SPA_BT_PROFILE_BAP_DUPLEX) { spa_log_debug(this->log, "profiles changed %x -> %x", this->prev_bt_connected_profiles, this->bt_dev->connected_profiles); this->params[IDX_EnumProfile].flags ^= SPA_PARAM_INFO_SERIAL; } this->params[IDX_Profile].flags ^= SPA_PARAM_INFO_SERIAL; this->params[IDX_Route].flags ^= SPA_PARAM_INFO_SERIAL; this->params[IDX_EnumRoute].flags ^= SPA_PARAM_INFO_SERIAL; this->params[IDX_Props].flags ^= SPA_PARAM_INFO_SERIAL; this->params[IDX_PropInfo].flags ^= SPA_PARAM_INFO_SERIAL; emit_info(this, false); } static void codec_switch_other(void *userdata, bool switching) { struct impl *this = userdata; this->switching_codec_other = switching; switch (this->profile) { case DEVICE_PROFILE_BAP: case DEVICE_PROFILE_BAP_SINK: case DEVICE_PROFILE_BAP_SOURCE: break; default: return; } spa_log_debug(this->log, "%p: BAP codec switching by another device, switching:%d", this, (int)switching); /* * In unicast BAP, output/input must be halted when another device is * switching codec, because CIG must be torn down before it can be * reconfigured. Easiest way to do this and to suspend output/input is to * remove the nodes. */ if (!find_device_transport(this->bt_dev, SPA_BT_PROFILE_BAP_SINK) && !find_device_transport(this->bt_dev, SPA_BT_PROFILE_BAP_SOURCE)) return; if (switching) { emit_remove_nodes(this); spa_bt_device_release_transports(this->bt_dev); } else { emit_remove_nodes(this); emit_nodes(this); } } static bool device_set_needs_update(struct impl *this) { struct device_set dset = { .impl = this }; bool changed; if (!profile_is_bap(this->profile) && this->profile != DEVICE_PROFILE_ASHA) return false; device_set_update(this, &dset, this->profile); changed = !device_set_equal(&dset, &this->device_set); device_set_clear(this, &dset); return changed; } static void profiles_changed(void *userdata, uint32_t connected_change) { struct impl *this = userdata; bool nodes_changed = false; /* Profiles changed. We have to re-emit device information. */ spa_log_info(this->log, "profiles changed to %08x %08x (change %08x) switching_codec:%d", this->bt_dev->profiles, this->bt_dev->connected_profiles, connected_change, this->switching_codec); if (this->switching_codec) return; free(this->supported_codecs); this->supported_codecs = spa_bt_device_get_supported_media_codecs( this->bt_dev, &this->supported_codec_count); switch (this->profile) { case DEVICE_PROFILE_OFF: /* Noop */ nodes_changed = false; break; case DEVICE_PROFILE_AG: nodes_changed = (connected_change & (SPA_BT_PROFILE_HEADSET_AUDIO_GATEWAY | SPA_BT_PROFILE_A2DP_SOURCE)); spa_log_debug(this->log, "profiles changed: AG nodes changed: %d", nodes_changed); break; case DEVICE_PROFILE_ASHA: nodes_changed = (connected_change & SPA_BT_PROFILE_ASHA_SINK); spa_log_debug(this->log, "profiles changed: ASHA nodes changed: %d", nodes_changed); break; case DEVICE_PROFILE_A2DP: nodes_changed = (connected_change & SPA_BT_PROFILE_A2DP_DUPLEX); spa_log_debug(this->log, "profiles changed: A2DP nodes changed: %d", nodes_changed); break; case DEVICE_PROFILE_BAP: case DEVICE_PROFILE_BAP_SINK: case DEVICE_PROFILE_BAP_SOURCE: nodes_changed = ((connected_change & SPA_BT_PROFILE_BAP_DUPLEX) && device_set_needs_update(this)) || (connected_change & (SPA_BT_PROFILE_BAP_BROADCAST_SINK | SPA_BT_PROFILE_BAP_BROADCAST_SOURCE)); spa_log_debug(this->log, "profiles changed: BAP nodes changed: %d", nodes_changed); break; case DEVICE_PROFILE_HSP_HFP: nodes_changed = (connected_change & SPA_BT_PROFILE_HEADSET_HEAD_UNIT); spa_log_debug(this->log, "profiles changed: HSP/HFP nodes changed: %d", nodes_changed); break; } if (nodes_changed) { emit_remove_nodes(this); emit_nodes(this); } this->info.change_mask |= SPA_DEVICE_CHANGE_MASK_PARAMS; this->params[IDX_Profile].flags ^= SPA_PARAM_INFO_SERIAL; this->params[IDX_EnumProfile].flags ^= SPA_PARAM_INFO_SERIAL; this->params[IDX_Route].flags ^= SPA_PARAM_INFO_SERIAL; /* Profile changes may affect routes */ this->params[IDX_EnumRoute].flags ^= SPA_PARAM_INFO_SERIAL; this->params[IDX_Props].flags ^= SPA_PARAM_INFO_SERIAL; this->params[IDX_PropInfo].flags ^= SPA_PARAM_INFO_SERIAL; emit_info(this, false); } static void device_set_changed(void *userdata) { struct impl *this = userdata; if (!profile_is_bap(this->profile) && this->profile != DEVICE_PROFILE_ASHA) return; if (this->switching_codec) return; if (!device_set_needs_update(this)) { spa_log_debug(this->log, "%p: device set not changed", this); return; } spa_log_debug(this->log, "%p: device set changed", this); emit_remove_nodes(this); emit_nodes(this); this->info.change_mask |= SPA_DEVICE_CHANGE_MASK_PARAMS; this->params[IDX_Profile].flags ^= SPA_PARAM_INFO_SERIAL; this->params[IDX_EnumProfile].flags ^= SPA_PARAM_INFO_SERIAL; this->params[IDX_Route].flags ^= SPA_PARAM_INFO_SERIAL; this->params[IDX_EnumRoute].flags ^= SPA_PARAM_INFO_SERIAL; emit_info(this, false); } static void set_initial_profile(struct impl *this); static void device_connected(void *userdata, bool connected) { struct impl *this = userdata; spa_log_debug(this->log, "%p: connected: %d", this, connected); if (connected ^ (this->profile != DEVICE_PROFILE_OFF)) { emit_remove_nodes(this); set_initial_profile(this); } } static void device_switch_profile(void *userdata) { struct impl *this = userdata; uint32_t profile; switch(this->profile) { case DEVICE_PROFILE_OFF: profile = DEVICE_PROFILE_HSP_HFP; break; case DEVICE_PROFILE_HSP_HFP: profile = DEVICE_PROFILE_OFF; break; default: return; } spa_log_debug(this->log, "%p: device switch profile %d -> %d", this, this->profile, profile); set_profile(this, profile, 0, false); } static const struct spa_bt_device_events bt_dev_events = { SPA_VERSION_BT_DEVICE_EVENTS, .connected = device_connected, .codec_switched = codec_switched, .codec_switch_other = codec_switch_other, .profiles_changed = profiles_changed, .device_set_changed = device_set_changed, .switch_profile = device_switch_profile, }; static int impl_add_listener(void *object, struct spa_hook *listener, const struct spa_device_events *events, void *data) { struct impl *this = object; struct spa_hook_list save; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(events != NULL, -EINVAL); spa_hook_list_isolate(&this->hooks, &save, listener, events, data); if (events->info) emit_info(this, true); if (events->object_info) emit_nodes(this); spa_hook_list_join(&this->hooks, &save); return 0; } static int impl_sync(void *object, int seq) { struct impl *this = object; spa_return_val_if_fail(this != NULL, -EINVAL); spa_device_emit_result(&this->hooks, seq, 0, 0, NULL); return 0; } static uint32_t profile_direction_mask(struct impl *this, uint32_t index, enum spa_bluetooth_audio_codec codec, bool hfp_input_for_a2dp) { struct spa_bt_device *device = this->bt_dev; uint32_t mask; bool have_output = false, have_input = false; const struct media_codec *media_codec; switch (index) { case DEVICE_PROFILE_A2DP: if (device->connected_profiles & SPA_BT_PROFILE_A2DP_SINK) have_output = true; media_codec = get_supported_media_codec(this, codec, NULL, device->connected_profiles); if (media_codec && media_codec->duplex_codec) have_input = true; if (hfp_input_for_a2dp && this->nodes[DEVICE_ID_SOURCE].active) have_input = true; break; case DEVICE_PROFILE_BAP: if (device->profiles & SPA_BT_PROFILE_BAP_SINK) have_output = true; if (device->profiles & SPA_BT_PROFILE_BAP_SOURCE) have_input = true; break; case DEVICE_PROFILE_BAP_SINK: have_output = true; break; case DEVICE_PROFILE_BAP_SOURCE: have_input = true; break; case DEVICE_PROFILE_HSP_HFP: if (device->connected_profiles & SPA_BT_PROFILE_HEADSET_HEAD_UNIT) have_output = have_input = true; break; case DEVICE_PROFILE_ASHA: if (device->connected_profiles & SPA_BT_PROFILE_ASHA_SINK) have_input = true; break; default: break; } mask = 0; if (have_output) mask |= 1 << SPA_DIRECTION_OUTPUT; if (have_input) mask |= 1 << SPA_DIRECTION_INPUT; return mask; } static uint32_t get_profile_from_index(struct impl *this, uint32_t index, uint32_t *next, enum spa_bluetooth_audio_codec *codec) { uint32_t profile = (index >> 16); const struct spa_type_info *info; switch (profile) { case DEVICE_PROFILE_OFF: case DEVICE_PROFILE_AG: *codec = 0; *next = (profile + 1) << 16; return profile; case DEVICE_PROFILE_ASHA: *codec = SPA_BLUETOOTH_AUDIO_CODEC_G722; *next = (profile + 1) << 16; return profile; case DEVICE_PROFILE_A2DP: case DEVICE_PROFILE_HSP_HFP: case DEVICE_PROFILE_BAP: case DEVICE_PROFILE_BAP_SINK: case DEVICE_PROFILE_BAP_SOURCE: *codec = (index & 0xffff); *next = (profile + 1) << 16; for (info = spa_type_bluetooth_audio_codec; info->type; ++info) if (info->type > *codec) *next = SPA_MIN(*next, (profile << 16) | (info->type & 0xffff)); return profile; default: *codec = 0; *next = SPA_ID_INVALID; profile = SPA_ID_INVALID; break; } return profile; } static uint32_t get_index_from_profile(struct impl *this, uint32_t profile, enum spa_bluetooth_audio_codec codec) { switch (profile) { case DEVICE_PROFILE_OFF: case DEVICE_PROFILE_AG: return (profile << 16); case DEVICE_PROFILE_ASHA: return (profile << 16) | (SPA_BLUETOOTH_AUDIO_CODEC_G722 & 0xffff); case DEVICE_PROFILE_A2DP: case DEVICE_PROFILE_BAP: case DEVICE_PROFILE_BAP_SINK: case DEVICE_PROFILE_BAP_SOURCE: case DEVICE_PROFILE_HSP_HFP: if (!codec) return SPA_ID_INVALID; return (profile << 16) | (codec & 0xffff); } return SPA_ID_INVALID; } static bool set_initial_asha_profile(struct impl *this) { struct spa_bt_transport *t; if (!(this->bt_dev->connected_profiles & SPA_BT_PROFILE_ASHA_SINK)) return false; t = find_transport(this, SPA_BT_PROFILE_ASHA_SINK); if (t) { this->profile = DEVICE_PROFILE_ASHA; this->props.codec = SPA_BLUETOOTH_AUDIO_CODEC_G722; spa_log_debug(this->log, "initial ASHA profile:%d codec:%d", this->profile, this->props.codec); return true; } return false; } static bool set_initial_hsp_hfp_profile(struct impl *this) { struct spa_bt_transport *t; int i; for (i = SPA_BT_PROFILE_HSP_HS; i <= SPA_BT_PROFILE_HFP_AG; i <<= 1) { if (!(this->bt_dev->connected_profiles & i)) continue; t = find_transport(this, i); if (t) { this->profile = (i & SPA_BT_PROFILE_HEADSET_AUDIO_GATEWAY) ? DEVICE_PROFILE_AG : DEVICE_PROFILE_HSP_HFP; this->props.codec = t->media_codec->id; spa_log_debug(this->log, "initial profile HSP/HFP profile:%d codec:%d", this->profile, this->props.codec); return true; } } return false; } static void set_initial_profile(struct impl *this) { struct spa_bt_transport *t; int i; this->switching_codec = false; if (this->supported_codecs) free(this->supported_codecs); this->supported_codecs = spa_bt_device_get_supported_media_codecs( this->bt_dev, &this->supported_codec_count); /* Prefer BAP, then A2DP, then HFP, then null, but select AG if the device appears not to have BAP_SINK, A2DP_SINK or any HEAD_UNIT profile */ /* If default profile is set to HSP/HFP, first try those and exit if found. */ if (this->bt_dev->settings != NULL) { const char *str = spa_dict_lookup(this->bt_dev->settings, "bluez5.profile"); if (spa_streq(str, "asha-sink") && set_initial_asha_profile(this)) return; if (spa_streq(str, "off")) goto off; if (spa_streq(str, "headset-head-unit") && set_initial_hsp_hfp_profile(this)) return; } for (i = SPA_BT_PROFILE_BAP_SINK; i <= SPA_BT_PROFILE_ASHA_SINK; i <<= 1) { if (!(this->bt_dev->connected_profiles & i)) continue; t = find_transport(this, i); if (t) { if (i == SPA_BT_PROFILE_A2DP_SOURCE || i == SPA_BT_PROFILE_BAP_SOURCE) this->profile = DEVICE_PROFILE_AG; else if (i == SPA_BT_PROFILE_BAP_SINK) this->profile = DEVICE_PROFILE_BAP; else if (i == SPA_BT_PROFILE_ASHA_SINK) this->profile = DEVICE_PROFILE_ASHA; else this->profile = DEVICE_PROFILE_A2DP; this->props.codec = t->media_codec->id; spa_log_debug(this->log, "initial profile media profile:%d codec:%d", this->profile, this->props.codec); return; } } if (set_initial_hsp_hfp_profile(this)) return; off: spa_log_debug(this->log, "initial profile off"); this->profile = DEVICE_PROFILE_OFF; this->props.codec = 0; } static struct spa_pod *build_profile(struct impl *this, struct spa_pod_builder *b, uint32_t id, uint32_t index, uint32_t profile_index, enum spa_bluetooth_audio_codec codec, bool current) { struct spa_bt_device *device = this->bt_dev; struct spa_pod_frame f[2]; const char *name, *desc; char *name_and_codec = NULL; char *desc_and_codec = NULL; uint32_t n_source = 0, n_sink = 0; uint32_t capture[1] = { DEVICE_ID_SOURCE }, playback[1] = { DEVICE_ID_SINK }; int priority; switch (profile_index) { case DEVICE_PROFILE_OFF: name = "off"; desc = _("Off"); priority = 0; break; case DEVICE_PROFILE_AG: { uint32_t profile = device->connected_profiles & (SPA_BT_PROFILE_A2DP_SOURCE | SPA_BT_PROFILE_HEADSET_AUDIO_GATEWAY); if (profile == 0) { return NULL; } else { name = "audio-gateway"; desc = _("Audio Gateway (A2DP Source & HSP/HFP AG)"); } /* * If the remote is A2DP sink and HF, we likely should prioritize being * A2DP sender, not gateway. This can occur in PW<->PW if RFCOMM gets * connected both as AG and HF. */ if ((device->connected_profiles & SPA_BT_PROFILE_A2DP_SINK) && (device->connected_profiles & SPA_BT_PROFILE_HEADSET_HEAD_UNIT)) priority = 127; else priority = 256; break; } case DEVICE_PROFILE_ASHA: { uint32_t profile = device->connected_profiles & SPA_BT_PROFILE_ASHA_SINK; int n_set_sink, n_set_source; if (codec == 0) return NULL; if (profile == 0) return NULL; if (!(profile & SPA_BT_PROFILE_ASHA_SINK)) { return NULL; } name = spa_bt_profile_name(profile); desc = _("Audio Streaming for Hearing Aids (ASHA Sink)"); n_sink++; priority = 1; device_set_get_info(this, DEVICE_PROFILE_ASHA, &n_set_sink, &n_set_source); if (n_set_sink >= 0) n_sink = n_set_sink; break; } case DEVICE_PROFILE_A2DP: { /* make this device profile visible only if there is an A2DP sink */ uint32_t profile = device->connected_profiles & (SPA_BT_PROFILE_A2DP_SINK | SPA_BT_PROFILE_A2DP_SOURCE); if (!(profile & SPA_BT_PROFILE_A2DP_SINK)) { return NULL; } /* A2DP will only enlist codec profiles */ if (!codec) return NULL; name = spa_bt_profile_name(profile); n_sink++; if (codec) { int prio; const struct media_codec *media_codec = get_supported_media_codec(this, codec, &prio, profile); if (media_codec == NULL) { errno = EINVAL; return NULL; } name_and_codec = spa_aprintf("%s-%s", name, media_codec->name); /* * Give base name to highest priority profile, so that best codec can be * selected at command line with out knowing which codecs are actually * supported */ if (prio != 0) name = name_and_codec; if (profile == SPA_BT_PROFILE_A2DP_SINK && !media_codec->duplex_codec) { desc_and_codec = spa_aprintf(_("High Fidelity Playback (A2DP Sink, codec %s)"), media_codec->description); } else { desc_and_codec = spa_aprintf(_("High Fidelity Duplex (A2DP Source/Sink, codec %s)"), media_codec->description); } desc = desc_and_codec; priority = 128 + this->supported_codec_count - prio; /* order as in codec list */ } else { if (profile == SPA_BT_PROFILE_A2DP_SINK) { desc = _("High Fidelity Playback (A2DP Sink)"); } else { desc = _("High Fidelity Duplex (A2DP Source/Sink)"); } priority = 128; } break; } case DEVICE_PROFILE_BAP_SINK: case DEVICE_PROFILE_BAP_SOURCE: /* These are client-only */ if (!is_bap_client(this)) return NULL; SPA_FALLTHROUGH; case DEVICE_PROFILE_BAP: { uint32_t profile; const struct media_codec *media_codec; int n_set_sink, n_set_source; /* BAP will only enlist codec profiles */ if (codec == 0) return NULL; switch (profile_index) { case DEVICE_PROFILE_BAP: profile = device->profiles & (SPA_BT_PROFILE_BAP_SINK | SPA_BT_PROFILE_BAP_SOURCE | SPA_BT_PROFILE_BAP_BROADCAST_SOURCE | SPA_BT_PROFILE_BAP_BROADCAST_SINK); break; case DEVICE_PROFILE_BAP_SINK: if (!(device->profiles & SPA_BT_PROFILE_BAP_SOURCE)) return NULL; profile = device->profiles & SPA_BT_PROFILE_BAP_SINK; break; case DEVICE_PROFILE_BAP_SOURCE: if (!(device->profiles & SPA_BT_PROFILE_BAP_SINK)) return NULL; profile = device->profiles & SPA_BT_PROFILE_BAP_SOURCE; break; } if (profile == 0) return NULL; if ((profile & (SPA_BT_PROFILE_BAP_SINK)) || (profile & (SPA_BT_PROFILE_BAP_BROADCAST_SINK))) n_sink++; if ((profile & (SPA_BT_PROFILE_BAP_SOURCE)) || (profile & (SPA_BT_PROFILE_BAP_BROADCAST_SOURCE))) n_source++; name = spa_bt_profile_name(profile); if (codec) { int idx; media_codec = get_supported_media_codec(this, codec, &idx, profile); if (media_codec == NULL) { errno = EINVAL; return NULL; } name_and_codec = spa_aprintf("%s-%s", name, media_codec->name); /* * Give base name to highest priority profile, so that best codec can be * selected at command line with out knowing which codecs are actually * supported */ if (idx != 0) name = name_and_codec; switch (profile) { case SPA_BT_PROFILE_BAP_SINK: case SPA_BT_PROFILE_BAP_BROADCAST_SINK: desc_and_codec = spa_aprintf(_("High Fidelity Playback (BAP Sink, codec %s)"), media_codec->description); break; case SPA_BT_PROFILE_BAP_SOURCE: case SPA_BT_PROFILE_BAP_BROADCAST_SOURCE: desc_and_codec = spa_aprintf(_("High Fidelity Input (BAP Source, codec %s)"), media_codec->description); break; default: desc_and_codec = spa_aprintf(_("High Fidelity Duplex (BAP Source/Sink, codec %s)"), media_codec->description); } desc = desc_and_codec; priority = 512 + this->supported_codec_count - idx; /* order as in codec list */ } else { switch (profile) { case SPA_BT_PROFILE_BAP_SINK: case SPA_BT_PROFILE_BAP_BROADCAST_SINK: desc = _("High Fidelity Playback (BAP Sink)"); break; case SPA_BT_PROFILE_BAP_SOURCE: case SPA_BT_PROFILE_BAP_BROADCAST_SOURCE: desc = _("High Fidelity Input (BAP Source)"); break; default: desc = _("High Fidelity Duplex (BAP Source/Sink)"); } priority = 512; } device_set_get_info(this, DEVICE_PROFILE_BAP, &n_set_sink, &n_set_source); if (n_set_sink >= 0) n_sink = n_set_sink; if (n_set_source >= 0) n_source = n_set_source; break; } case DEVICE_PROFILE_HSP_HFP: { uint32_t profile = device->connected_profiles & SPA_BT_PROFILE_HEADSET_HEAD_UNIT; int prio; const struct media_codec *media_codec = get_supported_media_codec(this, codec, &prio, profile); if (!profile) return NULL; /* Only list codec profiles */ if (!codec || !media_codec) return NULL; name = spa_bt_profile_name(profile); n_source++; n_sink++; name_and_codec = spa_aprintf("%s-%s", name, media_codec->name); /* * Give base name to highest priority profile, so that best codec can be * selected at command line with out knowing which codecs are actually * supported */ if (prio != 0) name = name_and_codec; desc_and_codec = spa_aprintf(_("Headset Head Unit (HSP/HFP, codec %s)"), media_codec->description); desc = desc_and_codec; priority = 1 + this->supported_codec_count - prio; break; } default: errno = EINVAL; return NULL; } spa_pod_builder_push_object(b, &f[0], SPA_TYPE_OBJECT_ParamProfile, id); spa_pod_builder_add(b, SPA_PARAM_PROFILE_index, SPA_POD_Int(index), SPA_PARAM_PROFILE_name, SPA_POD_String(name), SPA_PARAM_PROFILE_description, SPA_POD_String(desc), SPA_PARAM_PROFILE_available, SPA_POD_Id(SPA_PARAM_AVAILABILITY_yes), SPA_PARAM_PROFILE_priority, SPA_POD_Int(priority), 0); if (n_source > 0 || n_sink > 0) { spa_pod_builder_prop(b, SPA_PARAM_PROFILE_classes, 0); spa_pod_builder_push_struct(b, &f[1]); if (n_source > 0) { spa_pod_builder_add_struct(b, SPA_POD_String("Audio/Source"), SPA_POD_Int(n_source), SPA_POD_String("card.profile.devices"), SPA_POD_Array(sizeof(uint32_t), SPA_TYPE_Int, 1, capture)); } if (n_sink > 0) { spa_pod_builder_add_struct(b, SPA_POD_String("Audio/Sink"), SPA_POD_Int(n_sink), SPA_POD_String("card.profile.devices"), SPA_POD_Array(sizeof(uint32_t), SPA_TYPE_Int, 1, playback)); } spa_pod_builder_pop(b, &f[1]); } if (current) { spa_pod_builder_prop(b, SPA_PARAM_PROFILE_save, 0); spa_pod_builder_bool(b, this->save_profile); } if (name_and_codec) free(name_and_codec); if (desc_and_codec) free(desc_and_codec); return spa_pod_builder_pop(b, &f[0]); } static bool validate_profile(struct impl *this, uint32_t profile, enum spa_bluetooth_audio_codec codec) { struct spa_pod_builder b = { 0 }; uint8_t buffer[1024]; spa_pod_builder_init(&b, buffer, sizeof(buffer)); return (build_profile(this, &b, 0, 0, profile, codec, false) != NULL); } static bool profile_has_route(uint32_t profile, uint32_t route) { switch (profile) { case DEVICE_PROFILE_OFF: case DEVICE_PROFILE_AG: break; case DEVICE_PROFILE_A2DP: switch (route) { case ROUTE_INPUT: case ROUTE_OUTPUT: return true; } break; case DEVICE_PROFILE_HSP_HFP: switch (route) { case ROUTE_INPUT: case ROUTE_HF_OUTPUT: return true; } break; case DEVICE_PROFILE_BAP: switch (route) { case ROUTE_INPUT: case ROUTE_OUTPUT: case ROUTE_SET_INPUT: case ROUTE_SET_OUTPUT: return true; } break; case DEVICE_PROFILE_BAP_SINK: switch (route) { case ROUTE_OUTPUT: case ROUTE_SET_OUTPUT: return true; } break; case DEVICE_PROFILE_BAP_SOURCE: switch (route) { case ROUTE_INPUT: case ROUTE_SET_INPUT: return true; } break; case DEVICE_PROFILE_ASHA: switch (route) { case ROUTE_OUTPUT: return true; } break; } return false; } static bool device_has_route(struct impl *this, uint32_t route) { bool found = false; if (this->bt_dev->connected_profiles & SPA_BT_PROFILE_A2DP_DUPLEX) found = found || profile_has_route(DEVICE_PROFILE_A2DP, route); if (this->bt_dev->connected_profiles & SPA_BT_PROFILE_BAP_AUDIO) found = found || profile_has_route(DEVICE_PROFILE_BAP, route); if (this->bt_dev->connected_profiles & SPA_BT_PROFILE_HEADSET_HEAD_UNIT) found = found || profile_has_route(DEVICE_PROFILE_HSP_HFP, route); if (this->bt_dev->connected_profiles & SPA_BT_PROFILE_ASHA_SINK) found = found || profile_has_route(DEVICE_PROFILE_ASHA, route); return found; } static struct spa_pod *build_route(struct impl *this, struct spa_pod_builder *b, uint32_t id, uint32_t route, uint32_t profile) { struct spa_bt_device *device = this->bt_dev; struct spa_pod_frame f[2]; enum spa_direction direction; const char *name_prefix, *description, *hfp_description, *port_type; const char *port_icon_name = NULL; enum spa_bt_form_factor ff; enum spa_bluetooth_audio_codec codec; enum spa_param_availability available; char name[128]; uint32_t i, j, mask, next; uint32_t dev; int n_set_sink, n_set_source; ff = spa_bt_form_factor_from_class(device->bluetooth_class); switch (ff) { case SPA_BT_FORM_FACTOR_HEADSET: name_prefix = "headset"; description = _("Headset"); hfp_description = _("Handsfree"); port_type = "headset"; break; case SPA_BT_FORM_FACTOR_HANDSFREE: name_prefix = "handsfree"; description = _("Handsfree"); hfp_description = _("Handsfree (HFP)"); port_type = "handsfree"; break; case SPA_BT_FORM_FACTOR_MICROPHONE: name_prefix = "microphone"; description = _("Microphone"); hfp_description = _("Handsfree"); port_type = "mic"; break; case SPA_BT_FORM_FACTOR_SPEAKER: name_prefix = "speaker"; description = _("Speaker"); hfp_description = _("Handsfree"); port_type = "speaker"; break; case SPA_BT_FORM_FACTOR_HEADPHONE: name_prefix = "headphone"; description = _("Headphones"); hfp_description = _("Handsfree"); port_type = "headphones"; break; case SPA_BT_FORM_FACTOR_PORTABLE: name_prefix = "portable"; description = _("Portable"); hfp_description = _("Handsfree"); port_type = "portable"; break; case SPA_BT_FORM_FACTOR_CAR: name_prefix = "car"; description = _("Car"); hfp_description = _("Handsfree"); port_type = "car"; break; case SPA_BT_FORM_FACTOR_HIFI: name_prefix = "hifi"; description = _("HiFi"); hfp_description = _("Handsfree"); port_type = "hifi"; break; case SPA_BT_FORM_FACTOR_PHONE: name_prefix = "phone"; description = _("Phone"); hfp_description = _("Handsfree"); port_type = "phone"; break; case SPA_BT_FORM_FACTOR_UNKNOWN: default: name_prefix = "bluetooth"; description = _("Bluetooth"); hfp_description = _("Bluetooth Handsfree"); port_type = "bluetooth"; break; } device_set_get_info(this, profile, &n_set_sink, &n_set_source); switch (route) { case ROUTE_INPUT: direction = SPA_DIRECTION_INPUT; snprintf(name, sizeof(name), "%s-input", name_prefix); dev = DEVICE_ID_SOURCE; available = (n_set_source >= 0) ? SPA_PARAM_AVAILABILITY_no : SPA_PARAM_AVAILABILITY_yes; if ((this->bt_dev->connected_profiles & SPA_BT_PROFILE_A2DP_SINK) && !(this->bt_dev->connected_profiles & SPA_BT_PROFILE_A2DP_SOURCE) && !(this->bt_dev->connected_profiles & SPA_BT_PROFILE_BAP_AUDIO) && (this->bt_dev->connected_profiles & SPA_BT_PROFILE_HEADSET_HEAD_UNIT)) description = hfp_description; break; case ROUTE_OUTPUT: direction = SPA_DIRECTION_OUTPUT; snprintf(name, sizeof(name), "%s-output", name_prefix); dev = DEVICE_ID_SINK; available = (n_set_sink >= 0) ? SPA_PARAM_AVAILABILITY_no : SPA_PARAM_AVAILABILITY_yes; if (device_has_route(this, ROUTE_HF_OUTPUT)) { /* Distinguish A2DP vs. HFP output routes */ switch (ff) { case SPA_BT_FORM_FACTOR_HEADSET: case SPA_BT_FORM_FACTOR_HANDSFREE: port_icon_name = spa_bt_form_factor_icon_name(SPA_BT_FORM_FACTOR_HEADPHONE); /* Don't call it "headset", the HF one has the mic */ description = _("Headphones"); break; default: break; } } break; case ROUTE_HF_OUTPUT: direction = SPA_DIRECTION_OUTPUT; snprintf(name, sizeof(name), "%s-hf-output", name_prefix); description = hfp_description; dev = DEVICE_ID_SINK; available = SPA_PARAM_AVAILABILITY_yes; if (device_has_route(this, ROUTE_OUTPUT)) port_icon_name = spa_bt_form_factor_icon_name(SPA_BT_FORM_FACTOR_HEADSET); break; case ROUTE_SET_INPUT: if (n_set_source < 1) return NULL; direction = SPA_DIRECTION_INPUT; snprintf(name, sizeof(name), "%s-set-input", name_prefix); dev = DEVICE_ID_SOURCE_SET; available = SPA_PARAM_AVAILABILITY_yes; break; case ROUTE_SET_OUTPUT: if (n_set_sink < 1) return NULL; direction = SPA_DIRECTION_OUTPUT; snprintf(name, sizeof(name), "%s-set-output", name_prefix); dev = DEVICE_ID_SINK_SET; available = SPA_PARAM_AVAILABILITY_yes; break; default: return NULL; } if (profile != SPA_ID_INVALID && !profile_has_route(profile, route)) return NULL; spa_pod_builder_push_object(b, &f[0], SPA_TYPE_OBJECT_ParamRoute, id); spa_pod_builder_add(b, SPA_PARAM_ROUTE_index, SPA_POD_Int(route), SPA_PARAM_ROUTE_direction, SPA_POD_Id(direction), SPA_PARAM_ROUTE_name, SPA_POD_String(name), SPA_PARAM_ROUTE_description, SPA_POD_String(description), SPA_PARAM_ROUTE_priority, SPA_POD_Int(0), SPA_PARAM_ROUTE_available, SPA_POD_Id(available), 0); spa_pod_builder_prop(b, SPA_PARAM_ROUTE_info, 0); spa_pod_builder_push_struct(b, &f[1]); spa_pod_builder_int(b, port_icon_name ? 2 : 1); spa_pod_builder_add(b, SPA_POD_String("port.type"), SPA_POD_String(port_type), NULL); if (port_icon_name) spa_pod_builder_add(b, SPA_POD_String("device.icon-name"), SPA_POD_String(port_icon_name), NULL); spa_pod_builder_pop(b, &f[1]); spa_pod_builder_prop(b, SPA_PARAM_ROUTE_profiles, 0); spa_pod_builder_push_array(b, &f[1]); mask = 0; for (i = 0; (j = get_profile_from_index(this, i, &next, &codec)) != SPA_ID_INVALID; i = next) { uint32_t profile_mask; if (!profile_has_route(j, route)) continue; profile_mask = profile_direction_mask(this, j, codec, false); if (!(profile_mask & (1 << direction))) continue; /* Check the profile actually exists */ if (!validate_profile(this, j, codec)) continue; mask |= profile_mask; spa_pod_builder_int(b, i); } spa_pod_builder_pop(b, &f[1]); if (!(mask & (1 << direction))) { /* No profile has route direction */ return NULL; } if (profile != SPA_ID_INVALID) { struct node *node = &this->nodes[dev]; struct spa_bt_transport_volume *t_volume; mask = profile_direction_mask(this, this->profile, this->props.codec, true); if (!(mask & (1 << direction))) return NULL; t_volume = node->transport ? &node->transport->volumes[node->id] : NULL; spa_pod_builder_prop(b, SPA_PARAM_ROUTE_device, 0); spa_pod_builder_int(b, dev); spa_pod_builder_prop(b, SPA_PARAM_ROUTE_props, 0); spa_pod_builder_push_object(b, &f[1], SPA_TYPE_OBJECT_Props, id); spa_pod_builder_prop(b, SPA_PROP_mute, 0); spa_pod_builder_bool(b, node->mute); spa_pod_builder_prop(b, SPA_PROP_channelVolumes, (t_volume && t_volume->active) ? SPA_POD_PROP_FLAG_HARDWARE : 0); spa_pod_builder_array(b, sizeof(float), SPA_TYPE_Float, node->n_channels, node->volumes); if (t_volume && t_volume->active) { spa_pod_builder_prop(b, SPA_PROP_volumeStep, SPA_POD_PROP_FLAG_READONLY); spa_pod_builder_float(b, 1.0f / (t_volume->hw_volume_max + 1)); } spa_pod_builder_prop(b, SPA_PROP_channelMap, 0); spa_pod_builder_array(b, sizeof(uint32_t), SPA_TYPE_Id, node->n_channels, node->channels); if ((this->profile == DEVICE_PROFILE_A2DP || profile_is_bap(this->profile)) && (dev & SINK_ID_FLAG)) { spa_pod_builder_prop(b, SPA_PROP_latencyOffsetNsec, 0); spa_pod_builder_long(b, node->latency_offset); } spa_pod_builder_pop(b, &f[1]); spa_pod_builder_prop(b, SPA_PARAM_ROUTE_save, 0); spa_pod_builder_bool(b, node->save); spa_pod_builder_prop(b, SPA_PARAM_ROUTE_profile, 0); spa_pod_builder_int(b, profile); } spa_pod_builder_prop(b, SPA_PARAM_ROUTE_devices, 0); spa_pod_builder_push_array(b, &f[1]); spa_pod_builder_int(b, dev); spa_pod_builder_pop(b, &f[1]); return spa_pod_builder_pop(b, &f[0]); } static bool iterate_supported_media_codecs(struct impl *this, int *j, const struct media_codec **codec) { int i; const struct media_codec *c; next: *j = *j + 1; spa_assert(*j >= 0); if ((size_t)*j >= this->supported_codec_count) return false; c = this->supported_codecs[*j]; if (!(this->profile == DEVICE_PROFILE_A2DP && c->kind == MEDIA_CODEC_A2DP) && !(profile_is_bap(this->profile) && c->kind == MEDIA_CODEC_BAP) && !(this->profile == DEVICE_PROFILE_HSP_HFP && c->kind == MEDIA_CODEC_HFP) && !(this->profile == DEVICE_PROFILE_ASHA && c->kind == MEDIA_CODEC_ASHA)) goto next; /* skip endpoint aliases */ for (i = 0; i < *j; ++i) if (this->supported_codecs[i]->id == c->id) goto next; *codec = c; return true; } static struct spa_pod *build_prop_info_codec(struct impl *this, struct spa_pod_builder *b, uint32_t id) { struct spa_pod_frame f[2]; struct spa_pod_choice *choice; const struct media_codec *codec; size_t n; int j; #define FOR_EACH_MEDIA_CODEC(j, codec) \ for (j = -1; iterate_supported_media_codecs(this, &j, &codec);) spa_pod_builder_push_object(b, &f[0], SPA_TYPE_OBJECT_PropInfo, id); /* * XXX: the ids in principle should use builder_id, not builder_int, * XXX: but the type info for _type and _labels doesn't work quite right now. */ /* Transport codec */ spa_pod_builder_prop(b, SPA_PROP_INFO_id, 0); spa_pod_builder_id(b, SPA_PROP_bluetoothAudioCodec); spa_pod_builder_prop(b, SPA_PROP_INFO_description, 0); spa_pod_builder_string(b, "Air codec"); spa_pod_builder_prop(b, SPA_PROP_INFO_type, 0); spa_pod_builder_push_choice(b, &f[1], SPA_CHOICE_Enum, 0); choice = (struct spa_pod_choice *)spa_pod_builder_frame(b, &f[1]); n = 0; FOR_EACH_MEDIA_CODEC(j, codec) { if (n == 0) spa_pod_builder_int(b, codec->id); spa_pod_builder_int(b, codec->id); ++n; } if (n == 0) choice->body.type = SPA_CHOICE_None; spa_pod_builder_pop(b, &f[1]); spa_pod_builder_prop(b, SPA_PROP_INFO_labels, 0); spa_pod_builder_push_struct(b, &f[1]); FOR_EACH_MEDIA_CODEC(j, codec) { spa_pod_builder_int(b, codec->id); spa_pod_builder_string(b, codec->description); } spa_pod_builder_pop(b, &f[1]); return spa_pod_builder_pop(b, &f[0]); #undef FOR_EACH_MEDIA_CODEC } static struct spa_pod *build_props(struct impl *this, struct spa_pod_builder *b, uint32_t id) { struct props *p = &this->props; return spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_Props, id, SPA_PROP_bluetoothAudioCodec, SPA_POD_Id(p->codec), SPA_PROP_bluetoothOffloadActive, SPA_POD_Bool(p->offload_active)); } static int impl_enum_params(void *object, int seq, uint32_t id, uint32_t start, uint32_t num, const struct spa_pod *filter) { struct impl *this = object; struct spa_pod *param; struct spa_pod_builder b = { 0 }; uint8_t buffer[2048]; struct spa_result_device_params result; uint32_t count = 0; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(num != 0, -EINVAL); result.id = id; result.next = start; next: result.index = result.next++; spa_pod_builder_init(&b, buffer, sizeof(buffer)); switch (id) { case SPA_PARAM_EnumProfile: { uint32_t profile; enum spa_bluetooth_audio_codec codec; profile = get_profile_from_index(this, result.index, &result.next, &codec); if (profile == SPA_ID_INVALID) return 0; param = build_profile(this, &b, id, result.index, profile, codec, false); if (param == NULL) goto next; break; } case SPA_PARAM_Profile: { uint32_t index; switch (result.index) { case 0: index = get_index_from_profile(this, this->profile, this->props.codec); param = build_profile(this, &b, id, index, this->profile, this->props.codec, true); if (param == NULL) return 0; break; default: return 0; } break; } case SPA_PARAM_EnumRoute: { if (result.index < ROUTE_LAST) { param = build_route(this, &b, id, result.index, SPA_ID_INVALID); if (param == NULL) goto next; } else { return 0; } break; } case SPA_PARAM_Route: { if (result.index < ROUTE_LAST) { param = build_route(this, &b, id, result.index, this->profile); if (param == NULL) goto next; break; } else { return 0; } break; } case SPA_PARAM_PropInfo: { switch (result.index) { case 0: param = build_prop_info_codec(this, &b, id); break; case 1: param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_PropInfo, id, SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_bluetoothOffloadActive), SPA_PROP_INFO_description, SPA_POD_String("Bluetooth audio offload active"), SPA_PROP_INFO_type, SPA_POD_CHOICE_Bool(false)); break; default: return 0; } break; } case SPA_PARAM_Props: { switch (result.index) { case 0: param = build_props(this, &b, id); break; default: return 0; } break; } default: return -ENOENT; } if (spa_pod_filter(&b, &result.param, param, filter) < 0) goto next; spa_device_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_DEVICE_PARAMS, &result); if (++count != num) goto next; return 0; } static void device_set_update_volumes(struct node *node) { struct impl *impl = node->impl; struct device_set *dset = &impl->device_set; float hw_volume = node_get_hw_volume(node); bool sink = (node->id == DEVICE_ID_SINK_SET); int volume_id = get_volume_id(node->id); struct device_set_member *members = sink ? dset->sink : dset->source; uint32_t n_members = sink ? dset->sinks : dset->sources; uint32_t i; /* Check if all sub-devices have HW volume */ if ((sink && !dset->sink_enabled) || (!sink && !dset->source_enabled)) goto soft_volume; for (i = 0; i < n_members; ++i) { struct spa_bt_transport *t = members[i].transport; struct spa_bt_transport_volume *t_volume = t ? &t->volumes[volume_id] : NULL; if (!t_volume || !t_volume->active) goto soft_volume; } node_update_soft_volumes(node, hw_volume); for (i = 0; i < n_members; ++i) spa_bt_transport_set_volume(members[i].transport, volume_id, hw_volume); return; soft_volume: /* Soft volume fallback */ for (i = 0; i < n_members; ++i) spa_bt_transport_set_volume(members[i].transport, volume_id, 1.0f); node_update_soft_volumes(node, 1.0f); return; } static int node_set_volume(struct impl *this, struct node *node, float volumes[], uint32_t n_volumes) { uint32_t i; int changed = 0; struct spa_bt_transport_volume *t_volume; int volume_id = get_volume_id(node->id); if (n_volumes == 0) return -EINVAL; spa_log_info(this->log, "node %d volume %f", node->id, volumes[0]); for (i = 0; i < node->n_channels; i++) { if (node->volumes[i] == volumes[i % n_volumes]) continue; ++changed; node->volumes[i] = volumes[i % n_volumes]; } t_volume = node->transport ? &node->transport->volumes[volume_id]: NULL; if (t_volume && t_volume->active && spa_bt_transport_volume_enabled(node->transport)) { float hw_volume = node_get_hw_volume(node); spa_log_debug(this->log, "node %d hardware volume %f", node->id, hw_volume); node_update_soft_volumes(node, hw_volume); spa_bt_transport_set_volume(node->transport, volume_id, hw_volume); } else if (node->id == DEVICE_ID_SOURCE_SET || node->id == DEVICE_ID_SINK_SET) { device_set_update_volumes(node); } else { float boost = get_soft_volume_boost(node); for (uint32_t i = 0; i < node->n_channels; ++i) node->soft_volumes[i] = node->volumes[i] * boost; } emit_volume(this, node); return changed; } static int node_set_mute(struct impl *this, struct node *node, bool mute) { struct spa_event *event; uint8_t buffer[4096]; struct spa_pod_builder b = { 0 }; struct spa_pod_frame f[1]; int changed = 0; spa_log_info(this->log, "node %d mute %d", node->id, mute); changed = (node->mute != mute); node->mute = mute; spa_pod_builder_init(&b, buffer, sizeof(buffer)); spa_pod_builder_push_object(&b, &f[0], SPA_TYPE_EVENT_Device, SPA_DEVICE_EVENT_ObjectConfig); spa_pod_builder_prop(&b, SPA_EVENT_DEVICE_Object, 0); spa_pod_builder_int(&b, node->id); spa_pod_builder_prop(&b, SPA_EVENT_DEVICE_Props, 0); spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_Props, SPA_EVENT_DEVICE_Props, SPA_PROP_mute, SPA_POD_Bool(mute), SPA_PROP_softMute, SPA_POD_Bool(mute)); event = spa_pod_builder_pop(&b, &f[0]); spa_device_emit_event(&this->hooks, event); return changed; } static int node_set_latency_offset(struct impl *this, struct node *node, int64_t latency_offset) { struct spa_event *event; uint8_t buffer[4096]; struct spa_pod_builder b = { 0 }; struct spa_pod_frame f[1]; int changed = 0; spa_log_info(this->log, "node %d latency offset %"PRIi64" nsec", node->id, latency_offset); changed = (node->latency_offset != latency_offset); node->latency_offset = latency_offset; spa_pod_builder_init(&b, buffer, sizeof(buffer)); spa_pod_builder_push_object(&b, &f[0], SPA_TYPE_EVENT_Device, SPA_DEVICE_EVENT_ObjectConfig); spa_pod_builder_prop(&b, SPA_EVENT_DEVICE_Object, 0); spa_pod_builder_int(&b, node->id); spa_pod_builder_prop(&b, SPA_EVENT_DEVICE_Props, 0); spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_Props, SPA_EVENT_DEVICE_Props, SPA_PROP_latencyOffsetNsec, SPA_POD_Long(latency_offset)); event = spa_pod_builder_pop(&b, &f[0]); spa_device_emit_event(&this->hooks, event); return changed; } static int apply_device_props(struct impl *this, struct node *node, struct spa_pod *props) { float volume = 0; bool mute = 0; struct spa_pod_prop *prop; struct spa_pod_object *obj = (struct spa_pod_object *) props; int changed = 0; float volumes[MAX_CHANNELS]; uint32_t channels[MAX_CHANNELS]; uint32_t n_volumes = 0, SPA_UNUSED n_channels = 0; int64_t latency_offset = 0; if (!spa_pod_is_object_type(props, SPA_TYPE_OBJECT_Props)) return -EINVAL; SPA_POD_OBJECT_FOREACH(obj, prop) { switch (prop->key) { case SPA_PROP_volume: if (spa_pod_get_float(&prop->value, &volume) == 0) { int res = node_set_volume(this, node, &volume, 1); if (res > 0) ++changed; } break; case SPA_PROP_mute: if (spa_pod_get_bool(&prop->value, &mute) == 0) { int res = node_set_mute(this, node, mute); if (res > 0) ++changed; } break; case SPA_PROP_channelVolumes: n_volumes = spa_pod_copy_array(&prop->value, SPA_TYPE_Float, volumes, SPA_N_ELEMENTS(volumes)); break; case SPA_PROP_channelMap: n_channels = spa_pod_copy_array(&prop->value, SPA_TYPE_Id, channels, SPA_N_ELEMENTS(channels)); break; case SPA_PROP_latencyOffsetNsec: if (spa_pod_get_long(&prop->value, &latency_offset) == 0) { int res = node_set_latency_offset(this, node, latency_offset); if (res > 0) ++changed; } } } if (n_volumes > 0) { int res = node_set_volume(this, node, volumes, n_volumes); if (res > 0) ++changed; } return changed; } static void apply_prop_offload_active(struct impl *this, bool active) { bool old_value = this->props.offload_active; unsigned int i; this->props.offload_active = active; for (i = 0; i < SPA_N_ELEMENTS(this->nodes); i++) { node_offload_set_active(&this->nodes[i], active); if (!this->nodes[i].offload_acquired) this->props.offload_active = false; } if (this->props.offload_active != old_value) { this->info.change_mask |= SPA_DEVICE_CHANGE_MASK_PARAMS; this->params[IDX_Props].flags ^= SPA_PARAM_INFO_SERIAL; emit_info(this, false); } } static int impl_set_param(void *object, uint32_t id, uint32_t flags, const struct spa_pod *param) { struct impl *this = object; int res; spa_return_val_if_fail(this != NULL, -EINVAL); switch (id) { case SPA_PARAM_Profile: { uint32_t idx, next; uint32_t profile; enum spa_bluetooth_audio_codec codec; bool save = false; if (param == NULL) return -EINVAL; if ((res = spa_pod_parse_object(param, SPA_TYPE_OBJECT_ParamProfile, NULL, SPA_PARAM_PROFILE_index, SPA_POD_Int(&idx), SPA_PARAM_PROFILE_save, SPA_POD_OPT_Bool(&save))) < 0) { spa_log_warn(this->log, "can't parse profile"); spa_debug_log_pod(this->log, SPA_LOG_LEVEL_DEBUG, 0, NULL, param); return res; } profile = get_profile_from_index(this, idx, &next, &codec); if (profile == SPA_ID_INVALID) return -EINVAL; spa_log_debug(this->log, "%p: setting profile %d codec:%d save:%d", this, profile, codec, (int)save); return set_profile(this, profile, codec, save); } case SPA_PARAM_Route: { uint32_t idx, device; struct spa_pod *props = NULL; struct node *node; bool save = false; if (param == NULL) return -EINVAL; if ((res = spa_pod_parse_object(param, SPA_TYPE_OBJECT_ParamRoute, NULL, SPA_PARAM_ROUTE_index, SPA_POD_Int(&idx), SPA_PARAM_ROUTE_device, SPA_POD_Int(&device), SPA_PARAM_ROUTE_props, SPA_POD_OPT_Pod(&props), SPA_PARAM_ROUTE_save, SPA_POD_OPT_Bool(&save))) < 0) { spa_log_warn(this->log, "can't parse route"); spa_debug_log_pod(this->log, SPA_LOG_LEVEL_DEBUG, 0, NULL, param); return res; } if (device >= SPA_N_ELEMENTS(this->nodes) || !this->nodes[device].active) return -EINVAL; node = &this->nodes[device]; node->save = save; if (props) { int changed = apply_device_props(this, node, props); if (changed > 0) { this->info.change_mask |= SPA_DEVICE_CHANGE_MASK_PARAMS; this->params[IDX_Route].flags ^= SPA_PARAM_INFO_SERIAL; } emit_info(this, false); } break; } case SPA_PARAM_Props: { uint32_t codec_id = SPA_ID_INVALID; bool offload_active = this->props.offload_active; if (param == NULL) return 0; if ((res = spa_pod_parse_object(param, SPA_TYPE_OBJECT_Props, NULL, SPA_PROP_bluetoothAudioCodec, SPA_POD_OPT_Id(&codec_id), SPA_PROP_bluetoothOffloadActive, SPA_POD_OPT_Bool(&offload_active))) < 0) { spa_log_warn(this->log, "can't parse props"); spa_debug_log_pod(this->log, SPA_LOG_LEVEL_DEBUG, 0, NULL, param); return res; } spa_log_debug(this->log, "setting props codec:%d offload:%d", (int)codec_id, (int)offload_active); apply_prop_offload_active(this, offload_active); if (codec_id == SPA_ID_INVALID) return 0; if (this->profile == DEVICE_PROFILE_A2DP || profile_is_bap(this->profile) || this->profile == DEVICE_PROFILE_ASHA || this->profile == DEVICE_PROFILE_HSP_HFP) { size_t j; for (j = 0; j < this->supported_codec_count; ++j) { if (this->supported_codecs[j]->id == codec_id) { return set_profile(this, this->profile, codec_id, true); } } } return -EINVAL; } default: return -ENOENT; } return 0; } static const struct spa_device_methods impl_device = { SPA_VERSION_DEVICE_METHODS, .add_listener = impl_add_listener, .sync = impl_sync, .enum_params = impl_enum_params, .set_param = impl_set_param, }; static int impl_get_interface(struct spa_handle *handle, const char *type, void **interface) { struct impl *this; spa_return_val_if_fail(handle != NULL, -EINVAL); spa_return_val_if_fail(interface != NULL, -EINVAL); this = (struct impl *) handle; if (spa_streq(type, SPA_TYPE_INTERFACE_Device)) *interface = &this->device; else return -ENOENT; return 0; } static int impl_clear(struct spa_handle *handle) { struct impl *this = (struct impl *) handle; const struct spa_dict_item *it; emit_remove_nodes(this); free(this->supported_codecs); if (this->bt_dev) { this->bt_dev->settings = NULL; spa_hook_remove(&this->bt_dev_listener); } spa_dict_for_each(it, &this->setting_dict) { if(it->key) free((void *)it->key); if(it->value) free((void *)it->value); } device_set_clear(this, &this->device_set); return 0; } static size_t impl_get_size(const struct spa_handle_factory *factory, const struct spa_dict *params) { return sizeof(struct impl); } static const struct spa_dict* filter_bluez_device_setting(struct impl *this, const struct spa_dict *dict) { uint32_t n_items = 0; for (uint32_t i = 0 ; i < dict->n_items && n_items < SPA_N_ELEMENTS(this->setting_items) ; i++) { const struct spa_dict_item *it = &dict->items[i]; if (it->key != NULL && strncmp(it->key, "bluez", 5) == 0 && it->value != NULL) { this->setting_items[n_items++] = SPA_DICT_ITEM_INIT(strdup(it->key), strdup(it->value)); } } this->setting_dict = SPA_DICT_INIT(this->setting_items, n_items); return &this->setting_dict; } static int impl_init(const struct spa_handle_factory *factory, struct spa_handle *handle, const struct spa_dict *info, const struct spa_support *support, uint32_t n_support) { struct impl *this; const char *str; unsigned int i; spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(handle != NULL, -EINVAL); handle->get_interface = impl_get_interface; handle->clear = impl_clear; this = (struct impl *) handle; this->log = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log); _i18n = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_I18N); spa_log_topic_init(this->log, &log_topic); if (info && (str = spa_dict_lookup(info, SPA_KEY_API_BLUEZ5_DEVICE))) sscanf(str, "pointer:%p", &this->bt_dev); if (this->bt_dev == NULL) { spa_log_error(this->log, "a device is needed"); return -EINVAL; } if (info) { int profiles; this->bt_dev->settings = filter_bluez_device_setting(this, info); if ((str = spa_dict_lookup(info, "bluez5.auto-connect")) != NULL) { if ((profiles = spa_bt_profiles_from_json_array(str)) >= 0) this->bt_dev->reconnect_profiles = profiles; } if ((str = spa_dict_lookup(info, "bluez5.hw-volume")) != NULL) { if ((profiles = spa_bt_profiles_from_json_array(str)) >= 0) this->bt_dev->hw_volume_profiles = profiles; } } this->device.iface = SPA_INTERFACE_INIT( SPA_TYPE_INTERFACE_Device, SPA_VERSION_DEVICE, &impl_device, this); spa_hook_list_init(&this->hooks); reset_props(&this->props); for (i = 0; i < SPA_N_ELEMENTS(this->nodes); ++i) init_node(this, &this->nodes[i], i); this->info = SPA_DEVICE_INFO_INIT(); this->info_all = SPA_DEVICE_CHANGE_MASK_PROPS | SPA_DEVICE_CHANGE_MASK_PARAMS; this->params[IDX_EnumProfile] = SPA_PARAM_INFO(SPA_PARAM_EnumProfile, SPA_PARAM_INFO_READ); this->params[IDX_Profile] = SPA_PARAM_INFO(SPA_PARAM_Profile, SPA_PARAM_INFO_READWRITE); this->params[IDX_EnumRoute] = SPA_PARAM_INFO(SPA_PARAM_EnumRoute, SPA_PARAM_INFO_READ); this->params[IDX_Route] = SPA_PARAM_INFO(SPA_PARAM_Route, SPA_PARAM_INFO_READWRITE); this->params[IDX_PropInfo] = SPA_PARAM_INFO(SPA_PARAM_PropInfo, SPA_PARAM_INFO_READ); this->params[IDX_Props] = SPA_PARAM_INFO(SPA_PARAM_Props, SPA_PARAM_INFO_READWRITE); this->info.params = this->params; this->info.n_params = 6; spa_bt_device_add_listener(this->bt_dev, &this->bt_dev_listener, &bt_dev_events, this); this->device_set.impl = this; set_initial_profile(this); return 0; } static const struct spa_interface_info impl_interfaces[] = { {SPA_TYPE_INTERFACE_Device,}, }; static int impl_enum_interface_info(const struct spa_handle_factory *factory, const struct spa_interface_info **info, uint32_t *index) { spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(info != NULL, -EINVAL); spa_return_val_if_fail(index != NULL, -EINVAL); if (*index >= SPA_N_ELEMENTS(impl_interfaces)) return 0; *info = &impl_interfaces[(*index)++]; return 1; } static const struct spa_dict_item handle_info_items[] = { { SPA_KEY_FACTORY_AUTHOR, "Wim Taymans " }, { SPA_KEY_FACTORY_DESCRIPTION, "A bluetooth device" }, { SPA_KEY_FACTORY_USAGE, SPA_KEY_API_BLUEZ5_DEVICE"=" }, }; static const struct spa_dict handle_info = SPA_DICT_INIT_ARRAY(handle_info_items); const struct spa_handle_factory spa_bluez5_device_factory = { SPA_VERSION_HANDLE_FACTORY, SPA_NAME_API_BLUEZ5_DEVICE, &handle_info, impl_get_size, impl_init, impl_enum_interface_info, }; pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/bluez5/bt-latency.h000066400000000000000000000117531511204443500263720ustar00rootroot00000000000000/* Spa Bluez5 ISO I/O */ /* SPDX-FileCopyrightText: Copyright © 2024 Pauli Virtanen */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_BLUEZ5_BT_LATENCY_H #define SPA_BLUEZ5_BT_LATENCY_H #include #include #include #include #include #include #include #include "defs.h" #include "rate-control.h" /* New kernel API */ #ifndef BT_SCM_ERROR #define BT_SCM_ERROR 0x04 #endif #define NEW_SOF_TIMESTAMPING_TX_COMPLETION (1 << 18) #define NEW_SCM_TSTAMP_COMPLETION (SCM_TSTAMP_ACK + 1) /** * Bluetooth latency tracking. */ struct spa_bt_latency { uint64_t value; struct spa_bt_ptp ptp; bool valid; bool enabled; uint32_t queue; uint32_t kernel_queue; size_t unsent; struct { int64_t send[64]; size_t size[64]; uint32_t pos; int64_t prev_tx; } impl; }; static inline void spa_bt_latency_init(struct spa_bt_latency *lat, struct spa_bt_transport *transport, uint32_t period, struct spa_log *log) { int so_timestamping = (NEW_SOF_TIMESTAMPING_TX_COMPLETION | SOF_TIMESTAMPING_TX_SOFTWARE | SOF_TIMESTAMPING_SOFTWARE | SOF_TIMESTAMPING_OPT_ID | SOF_TIMESTAMPING_OPT_TSONLY); int res; spa_zero(*lat); if (!transport->device->adapter->tx_timestamping_supported) return; res = setsockopt(transport->fd, SOL_SOCKET, SO_TIMESTAMPING, &so_timestamping, sizeof(so_timestamping)); if (res < 0) { spa_log_info(log, "setsockopt(SO_TIMESTAMPING) failed (kernel feature not enabled?): %d (%m)", errno); return; } /* Flush errqueue on start */ do { res = recv(transport->fd, NULL, 0, MSG_ERRQUEUE | MSG_DONTWAIT | MSG_TRUNC); } while (res == 0); spa_bt_ptp_init(&lat->ptp, period, period / 2); lat->enabled = true; } static inline void spa_bt_latency_reset(struct spa_bt_latency *lat) { lat->value = 0; lat->valid = false; spa_bt_ptp_init(&lat->ptp, lat->ptp.period, lat->ptp.period / 2); } static inline ssize_t spa_bt_send(int fd, const void *buf, size_t size, struct spa_bt_latency *lat, uint64_t now) { ssize_t res = send(fd, buf, size, MSG_DONTWAIT | MSG_NOSIGNAL); if (!lat || !lat->enabled) return res; if (res >= 0) { lat->impl.send[lat->impl.pos] = now; lat->impl.size[lat->impl.pos] = size; lat->impl.pos++; if (lat->impl.pos >= SPA_N_ELEMENTS(lat->impl.send)) lat->impl.pos = 0; lat->queue++; lat->kernel_queue++; lat->unsent += size; } return res; } static inline int spa_bt_latency_recv_errqueue(struct spa_bt_latency *lat, int fd, struct spa_log *log) { union { char buf[CMSG_SPACE(32 * sizeof(struct scm_timestamping))]; struct cmsghdr align; } control; if (!lat->enabled) return -EOPNOTSUPP; do { struct iovec data = { .iov_base = NULL, .iov_len = 0 }; struct msghdr msg = { .msg_iov = &data, .msg_iovlen = 1, .msg_control = control.buf, .msg_controllen = sizeof(control.buf), }; struct cmsghdr *cmsg; struct scm_timestamping *tss = NULL; struct sock_extended_err *serr = NULL; int res; res = recvmsg(fd, &msg, MSG_ERRQUEUE | MSG_DONTWAIT); if (res < 0) { if (errno == EAGAIN || errno == EWOULDBLOCK) break; return -errno; } for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) { if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_TIMESTAMPING) tss = (void *)CMSG_DATA(cmsg); else if (cmsg->cmsg_level == SOL_BLUETOOTH && cmsg->cmsg_type == BT_SCM_ERROR) serr = (void *)CMSG_DATA(cmsg); else continue; } if (!tss || !serr || serr->ee_errno != ENOMSG || serr->ee_origin != SO_EE_ORIGIN_TIMESTAMPING) return -EINVAL; switch (serr->ee_info) { case SCM_TSTAMP_SND: if (lat->kernel_queue) lat->kernel_queue--; continue; case NEW_SCM_TSTAMP_COMPLETION: break; default: continue; } struct timespec *ts = &tss->ts[0]; int64_t tx_time = SPA_TIMESPEC_TO_NSEC(ts); uint32_t tx_pos = serr->ee_data % SPA_N_ELEMENTS(lat->impl.send); lat->value = tx_time - lat->impl.send[tx_pos]; if (lat->unsent > lat->impl.size[tx_pos]) lat->unsent -= lat->impl.size[tx_pos]; else lat->unsent = 0; if (lat->impl.prev_tx && tx_time > lat->impl.prev_tx) spa_bt_ptp_update(&lat->ptp, lat->value, tx_time - lat->impl.prev_tx); lat->impl.prev_tx = tx_time; if (lat->queue > 0) lat->queue--; if (!lat->queue) lat->unsent = 0; spa_log_trace(log, "fd:%d latency[%d] nsec:%"PRIu64" range:%d..%d ms", fd, tx_pos, lat->value, (int)(spa_bt_ptp_valid(&lat->ptp) ? lat->ptp.min / SPA_NSEC_PER_MSEC : -1), (int)(spa_bt_ptp_valid(&lat->ptp) ? lat->ptp.max / SPA_NSEC_PER_MSEC : -1)); } while (true); lat->valid = spa_bt_ptp_valid(&lat->ptp); return 0; } static inline void spa_bt_latency_flush(struct spa_bt_latency *lat, int fd, struct spa_log *log) { int so_timestamping = 0; if (!lat->enabled) return; /* Disable timestamping and flush errqueue */ setsockopt(fd, SOL_SOCKET, SO_TIMESTAMPING, &so_timestamping, sizeof(so_timestamping)); spa_bt_latency_recv_errqueue(lat, fd, log); lat->enabled = false; } #endif pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/bluez5/codec-loader.c000066400000000000000000000153461511204443500266460ustar00rootroot00000000000000/* Spa A2DP codec API */ /* SPDX-FileCopyrightText: Copyright © 2020 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include "config.h" #include #include "defs.h" #include "codec-loader.h" #define MEDIA_CODEC_LIB_BASE "bluez5/libspa-codec-bluez5-" /* AVDTP allows 0x3E endpoints, can't have more codecs than that */ #define MAX_CODECS 0x3E #define MAX_HANDLES MAX_CODECS SPA_LOG_TOPIC_DEFINE_STATIC(log_topic, "spa.bluez5.codecs"); #undef SPA_LOG_TOPIC_DEFAULT #define SPA_LOG_TOPIC_DEFAULT &log_topic struct impl { const struct media_codec *codecs[MAX_CODECS + 1]; struct spa_handle *handles[MAX_HANDLES]; size_t n_codecs; size_t n_handles; struct spa_plugin_loader *loader; struct spa_log *log; }; static int codec_order(const struct media_codec *c) { static const enum spa_bluetooth_audio_codec order[] = { SPA_BLUETOOTH_AUDIO_CODEC_LC3, SPA_BLUETOOTH_AUDIO_CODEC_LDAC, SPA_BLUETOOTH_AUDIO_CODEC_APTX_HD, SPA_BLUETOOTH_AUDIO_CODEC_APTX, SPA_BLUETOOTH_AUDIO_CODEC_AAC, SPA_BLUETOOTH_AUDIO_CODEC_OPUS_G, SPA_BLUETOOTH_AUDIO_CODEC_LC3PLUS_HR, SPA_BLUETOOTH_AUDIO_CODEC_MPEG, SPA_BLUETOOTH_AUDIO_CODEC_SBC, SPA_BLUETOOTH_AUDIO_CODEC_SBC_XQ, SPA_BLUETOOTH_AUDIO_CODEC_APTX_LL, SPA_BLUETOOTH_AUDIO_CODEC_APTX_LL_DUPLEX, SPA_BLUETOOTH_AUDIO_CODEC_FASTSTREAM, SPA_BLUETOOTH_AUDIO_CODEC_FASTSTREAM_DUPLEX, SPA_BLUETOOTH_AUDIO_CODEC_OPUS_05, SPA_BLUETOOTH_AUDIO_CODEC_OPUS_05_51, SPA_BLUETOOTH_AUDIO_CODEC_OPUS_05_71, SPA_BLUETOOTH_AUDIO_CODEC_OPUS_05_DUPLEX, SPA_BLUETOOTH_AUDIO_CODEC_OPUS_05_PRO, SPA_BLUETOOTH_AUDIO_CODEC_AAC_ELD, SPA_BLUETOOTH_AUDIO_CODEC_G722, SPA_BLUETOOTH_AUDIO_CODEC_LC3_SWB, SPA_BLUETOOTH_AUDIO_CODEC_LC3_A127, SPA_BLUETOOTH_AUDIO_CODEC_MSBC, SPA_BLUETOOTH_AUDIO_CODEC_CVSD, }; size_t i; for (i = 0; i < SPA_N_ELEMENTS(order); ++i) if (c->id == order[i]) return i; return SPA_N_ELEMENTS(order); } static int codec_order_cmp(const void *a, const void *b) { const struct media_codec * const *ca = a; const struct media_codec * const *cb = b; int ia = codec_order(*ca); int ib = codec_order(*cb); if (*ca == *cb) return 0; return (ia == ib) ? (*ca < *cb ? -1 : 1) : ia - ib; } static int load_media_codecs_from(struct impl *impl, const char *factory_name, const char *libname) { struct spa_handle *handle = NULL; void *iface; const struct spa_bluez5_codec_a2dp *bluez5_codec_a2dp; int n_codecs = 0; int res; size_t i; struct spa_dict_item info_items[] = { { SPA_KEY_LIBRARY_NAME, libname }, }; struct spa_dict info = SPA_DICT_INIT_ARRAY(info_items); handle = spa_plugin_loader_load(impl->loader, factory_name, &info); if (handle == NULL) { spa_log_info(impl->log, "Bluetooth codec plugin %s not available", factory_name); return -ENOENT; } spa_log_debug(impl->log, "loading codecs from %s", factory_name); if ((res = spa_handle_get_interface(handle, SPA_TYPE_INTERFACE_Bluez5CodecMedia, &iface)) < 0) { spa_log_warn(impl->log, "Bluetooth codec plugin %s has no codec interface", factory_name); goto fail; } bluez5_codec_a2dp = iface; if (bluez5_codec_a2dp->iface.version != SPA_VERSION_BLUEZ5_CODEC_MEDIA) { spa_log_warn(impl->log, "codec plugin %s has incompatible ABI version (%d != %d)", factory_name, bluez5_codec_a2dp->iface.version, SPA_VERSION_BLUEZ5_CODEC_MEDIA); res = -ENOENT; goto fail; } for (i = 0; bluez5_codec_a2dp->codecs[i]; ++i) { const struct media_codec *c = bluez5_codec_a2dp->codecs[i]; const char *ep = c->endpoint_name ? c->endpoint_name : c->name; size_t j; if (!ep) goto next_codec; if (impl->n_codecs >= MAX_CODECS) { spa_log_error(impl->log, "too many A2DP codecs"); break; } switch (c->kind) { case MEDIA_CODEC_A2DP: case MEDIA_CODEC_BAP: case MEDIA_CODEC_ASHA: case MEDIA_CODEC_HFP: break; default: spa_log_warn(impl->log, "codec plugin %s: unknown codec %s kind %d", factory_name, c->name, c->kind); continue; } /* Don't load duplicate endpoints */ for (j = 0; j < impl->n_codecs; ++j) { const struct media_codec *c2 = impl->codecs[j]; const char *ep2 = c2->endpoint_name ? c2->endpoint_name : c2->name; if (spa_streq(ep, ep2) && c->fill_caps && c2->fill_caps) { spa_log_debug(impl->log, "media codec %s from %s duplicate endpoint %s", c->name, factory_name, ep); goto next_codec; } } spa_log_debug(impl->log, "loaded media codec %s from %s, endpoint:%s", c->name, factory_name, ep); if (c->set_log) c->set_log(impl->log); impl->codecs[impl->n_codecs++] = c; ++n_codecs; next_codec: continue; } if (n_codecs > 0) impl->handles[impl->n_handles++] = handle; else spa_plugin_loader_unload(impl->loader, handle); return 0; fail: if (handle) spa_plugin_loader_unload(impl->loader, handle); return res; } const struct media_codec * const *load_media_codecs(struct spa_plugin_loader *loader, struct spa_log *log) { struct impl *impl; size_t i; const struct { const char *factory; const char *lib; } plugins[] = { #define MEDIA_CODEC_FACTORY_LIB(basename) \ { MEDIA_CODEC_FACTORY_NAME(basename), MEDIA_CODEC_LIB_BASE basename } MEDIA_CODEC_FACTORY_LIB("aac"), MEDIA_CODEC_FACTORY_LIB("aptx"), MEDIA_CODEC_FACTORY_LIB("faststream"), MEDIA_CODEC_FACTORY_LIB("ldac"), MEDIA_CODEC_FACTORY_LIB("sbc"), MEDIA_CODEC_FACTORY_LIB("lc3plus"), MEDIA_CODEC_FACTORY_LIB("opus"), MEDIA_CODEC_FACTORY_LIB("opus-g"), MEDIA_CODEC_FACTORY_LIB("lc3"), MEDIA_CODEC_FACTORY_LIB("g722"), MEDIA_CODEC_FACTORY_LIB("hfp-cvsd"), MEDIA_CODEC_FACTORY_LIB("hfp-msbc"), MEDIA_CODEC_FACTORY_LIB("hfp-lc3-swb"), MEDIA_CODEC_FACTORY_LIB("hfp-lc3-a127"), #undef MEDIA_CODEC_FACTORY_LIB }; impl = calloc(1, sizeof(struct impl)); if (impl == NULL) return NULL; impl->loader = loader; impl->log = log; spa_log_topic_init(impl->log, &log_topic); for (i = 0; i < SPA_N_ELEMENTS(plugins); ++i) load_media_codecs_from(impl, plugins[i].factory, plugins[i].lib); bool has_sbc = false, has_cvsd = false; for (i = 0; i < impl->n_codecs; ++i) { has_sbc |= impl->codecs[i]->id == SPA_BLUETOOTH_AUDIO_CODEC_SBC; has_cvsd |= impl->codecs[i]->id == SPA_BLUETOOTH_AUDIO_CODEC_CVSD; } if (!has_sbc || !has_cvsd) { if (!has_sbc) spa_log_error(impl->log, "failed to load A2DP SBC codec from plugins"); if (!has_cvsd) spa_log_error(impl->log, "failed to load HFP CVSD codec from plugins"); free_media_codecs(impl->codecs); errno = ENOENT; return NULL; } qsort(impl->codecs, impl->n_codecs, sizeof(const struct media_codec *), codec_order_cmp); return impl->codecs; } void free_media_codecs(const struct media_codec * const *media_codecs) { struct impl *impl = SPA_CONTAINER_OF(media_codecs, struct impl, codecs); size_t i; for (i = 0; i < impl->n_handles; ++i) spa_plugin_loader_unload(impl->loader, impl->handles[i]); free(impl); } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/bluez5/codec-loader.h000066400000000000000000000010051511204443500266360ustar00rootroot00000000000000/* Spa A2DP codec API */ /* SPDX-FileCopyrightText: Copyright © 2020 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_BLUEZ5_CODEC_LOADER_H_ #define SPA_BLUEZ5_CODEC_LOADER_H_ #include #include #include #include "a2dp-codec-caps.h" #include "media-codecs.h" const struct media_codec * const *load_media_codecs(struct spa_plugin_loader *loader, struct spa_log *log); void free_media_codecs(const struct media_codec * const *media_codecs); #endif pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/bluez5/dbus-monitor.c000066400000000000000000000176171511204443500267520ustar00rootroot00000000000000/* Spa midi dbus */ /* SPDX-FileCopyrightText: Copyright © 2022 Pauli Virtanen */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #include "dbus-monitor.h" static void on_g_properties_changed(GDBusProxy *proxy, GVariant *changed_properties, char **invalidated_properties, gpointer user_data) { struct dbus_monitor *monitor = user_data; GDBusInterfaceInfo *info = g_dbus_interface_get_info(G_DBUS_INTERFACE(proxy)); const char *name = info ? info->name : NULL; const struct dbus_monitor_proxy_type *p; spa_log_trace(monitor->log, "%p: dbus object updated path=%s, name=%s", monitor, g_dbus_proxy_get_object_path(proxy), name ? name : ""); for (p = monitor->proxy_types; p && p->proxy_type != G_TYPE_INVALID ; ++p) { if (G_TYPE_CHECK_INSTANCE_TYPE(proxy, p->proxy_type)) { if (p->on_update) p->on_update(monitor, G_DBUS_INTERFACE(proxy)); } } } static void on_remove(struct dbus_monitor *monitor, GDBusProxy *proxy) { const struct dbus_monitor_proxy_type *p; for (p = monitor->proxy_types; p && p->proxy_type != G_TYPE_INVALID ; ++p) { if (G_TYPE_CHECK_INSTANCE_TYPE(proxy, p->proxy_type)) { if (p->on_remove) p->on_remove(monitor, G_DBUS_INTERFACE(proxy)); } } } static void on_interface_added(GDBusObjectManager *self, GDBusObject *object, GDBusInterface *iface, gpointer user_data) { struct dbus_monitor *monitor = user_data; GDBusInterfaceInfo *info = g_dbus_interface_get_info(iface); const char *name = info ? info->name : NULL; spa_log_trace(monitor->log, "%p: dbus interface added path=%s, name=%s", monitor, g_dbus_object_get_object_path(object), name ? name : ""); if (!g_object_get_data(G_OBJECT(iface), "dbus-monitor-signals-connected")) { g_object_set_data(G_OBJECT(iface), "dbus-monitor-signals-connected", GUINT_TO_POINTER(1)); g_signal_connect(iface, "g-properties-changed", G_CALLBACK(on_g_properties_changed), monitor); } on_g_properties_changed(G_DBUS_PROXY(iface), NULL, NULL, monitor); } static void on_interface_removed(GDBusObjectManager *manager, GDBusObject *object, GDBusInterface *iface, gpointer user_data) { struct dbus_monitor *monitor = user_data; GDBusInterfaceInfo *info = g_dbus_interface_get_info(iface); const char *name = info ? info->name : NULL; spa_log_trace(monitor->log, "%p: dbus interface removed path=%s, name=%s", monitor, g_dbus_object_get_object_path(object), name ? name : ""); if (g_object_get_data(G_OBJECT(iface), "dbus-monitor-signals-connected")) { g_object_disconnect(G_OBJECT(iface), "any_signal", G_CALLBACK(on_g_properties_changed), monitor, NULL); g_object_set_data(G_OBJECT(iface), "dbus-monitor-signals-connected", NULL); } on_remove(monitor, G_DBUS_PROXY(iface)); } static void on_object_added(GDBusObjectManager *self, GDBusObject *object, gpointer user_data) { struct dbus_monitor *monitor = user_data; GList *interfaces = g_dbus_object_get_interfaces(object); /* * on_interface_added won't necessarily be called on objects on * name owner changes, so we have to call it here for all interfaces. */ for (GList *lli = g_list_first(interfaces); lli; lli = lli->next) { on_interface_added(dbus_monitor_manager(monitor), object, G_DBUS_INTERFACE(lli->data), monitor); } g_list_free_full(interfaces, g_object_unref); } static void on_object_removed(GDBusObjectManager *manager, GDBusObject *object, gpointer user_data) { struct dbus_monitor *monitor = user_data; GList *interfaces = g_dbus_object_get_interfaces(object); for (GList *lli = g_list_first(interfaces); lli; lli = lli->next) { on_interface_removed(dbus_monitor_manager(monitor), object, G_DBUS_INTERFACE(lli->data), monitor); } g_list_free_full(interfaces, g_object_unref); } static void on_notify(GObject *gobject, GParamSpec *pspec, gpointer user_data) { struct dbus_monitor *monitor = user_data; if (spa_streq(pspec->name, "name-owner") && monitor->on_name_owner_change) monitor->on_name_owner_change(monitor); } static GType get_proxy_type(GDBusObjectManagerClient *manager, const gchar *object_path, const gchar *interface_name, gpointer user_data) { struct dbus_monitor *monitor = user_data; const struct dbus_monitor_proxy_type *p; for (p = monitor->proxy_types; p && p->proxy_type != G_TYPE_INVALID; ++p) { if (spa_streq(p->interface_name, interface_name)) return p->proxy_type; } return G_TYPE_DBUS_PROXY; } static void init_done(GObject *source_object, GAsyncResult *res, gpointer user_data) { struct dbus_monitor *monitor = user_data; GError *error = NULL; GList *objects; GObject *ret; g_clear_object(&monitor->call); ret = g_async_initable_new_finish(G_ASYNC_INITABLE(source_object), res, &error); if (!ret) { spa_log_error(monitor->log, "%p: creating DBus object monitor failed: %s", monitor, error->message); g_error_free(error); return; } monitor->manager = G_DBUS_OBJECT_MANAGER_CLIENT(ret); spa_log_debug(monitor->log, "%p: DBus monitor started", monitor); g_signal_connect(monitor->manager, "interface-added", G_CALLBACK(on_interface_added), monitor); g_signal_connect(monitor->manager, "interface-removed", G_CALLBACK(on_interface_removed), monitor); g_signal_connect(monitor->manager, "object-added", G_CALLBACK(on_object_added), monitor); g_signal_connect(monitor->manager, "object-removed", G_CALLBACK(on_object_removed), monitor); g_signal_connect(monitor->manager, "notify", G_CALLBACK(on_notify), monitor); /* List all objects now */ objects = g_dbus_object_manager_get_objects(dbus_monitor_manager(monitor)); for (GList *llo = g_list_first(objects); llo; llo = llo->next) { GList *interfaces = g_dbus_object_get_interfaces(G_DBUS_OBJECT(llo->data)); for (GList *lli = g_list_first(interfaces); lli; lli = lli->next) { on_interface_added(dbus_monitor_manager(monitor), G_DBUS_OBJECT(llo->data), G_DBUS_INTERFACE(lli->data), monitor); } g_list_free_full(interfaces, g_object_unref); } g_list_free_full(objects, g_object_unref); } void dbus_monitor_init(struct dbus_monitor *monitor, GType client_type, struct spa_log *log, GDBusConnection *conn, const char *name, const char *object_path, const struct dbus_monitor_proxy_type *proxy_types, void (*on_name_owner_change)(struct dbus_monitor *monitor)) { GDBusObjectManagerClientFlags flags = G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_DO_NOT_AUTO_START; size_t i; spa_zero(*monitor); monitor->log = log; monitor->call = g_cancellable_new(); monitor->on_name_owner_change = on_name_owner_change; spa_zero(monitor->proxy_types); for (i = 0; proxy_types && proxy_types[i].proxy_type != G_TYPE_INVALID; ++i) { spa_assert(i < DBUS_MONITOR_MAX_TYPES); monitor->proxy_types[i] = proxy_types[i]; } g_async_initable_new_async(client_type, G_PRIORITY_DEFAULT, monitor->call, init_done, monitor, "flags", flags, "name", name, "connection", conn, "object-path", object_path, "get-proxy-type-func", get_proxy_type, "get-proxy-type-user-data", monitor, NULL); } void dbus_monitor_clear(struct dbus_monitor *monitor) { g_cancellable_cancel(monitor->call); g_clear_object(&monitor->call); if (monitor->manager) { /* * Indicate all objects should stop now. * * This has to be a separate hook, because the proxy finalizers * may be called later asynchronously via e.g. DBus callbacks. */ GList *objects = g_dbus_object_manager_get_objects(dbus_monitor_manager(monitor)); for (GList *llo = g_list_first(objects); llo; llo = llo->next) { GList *interfaces = g_dbus_object_get_interfaces(G_DBUS_OBJECT(llo->data)); for (GList *lli = g_list_first(interfaces); lli; lli = lli->next) { on_interface_removed(dbus_monitor_manager(monitor), G_DBUS_OBJECT(llo->data), G_DBUS_INTERFACE(lli->data), monitor); } g_list_free_full(interfaces, g_object_unref); } g_list_free_full(objects, g_object_unref); } g_clear_object(&monitor->manager); spa_zero(*monitor); } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/bluez5/dbus-monitor.h000066400000000000000000000035131511204443500267450ustar00rootroot00000000000000/* Spa midi dbus */ /* SPDX-FileCopyrightText: Copyright © 2022 Pauli Virtanen */ /* SPDX-License-Identifier: MIT */ #ifndef DBUS_MONITOR_H_ #define DBUS_MONITOR_H_ #include #include #include #include #define DBUS_MONITOR_MAX_TYPES 16 struct dbus_monitor; struct dbus_monitor_proxy_type { /** Interface name to monitor, or NULL for object type */ const char *interface_name; /** GObject type for the proxy */ GType proxy_type; /** Hook called when object added or properties changed */ void (*on_update)(struct dbus_monitor *monitor, GDBusInterface *iface); /** Hook called when object is removed (or on monitor shutdown) */ void (*on_remove)(struct dbus_monitor *monitor, GDBusInterface *iface); }; struct dbus_monitor { GDBusObjectManagerClient *manager; struct spa_log *log; GCancellable *call; struct dbus_monitor_proxy_type proxy_types[DBUS_MONITOR_MAX_TYPES+1]; void (*on_name_owner_change)(struct dbus_monitor *monitor); void *user_data; }; static inline GDBusObjectManager *dbus_monitor_manager(struct dbus_monitor *monitor) { return G_DBUS_OBJECT_MANAGER(monitor->manager); } /** * Create a DBus object monitor, with a given interface to proxy type map. * * \param proxy_types Mapping between interface names and watched proxy * types, terminated by G_TYPE_INVALID. * \param on_object_update Called for all objects and interfaces on * startup, and when object properties are modified. */ void dbus_monitor_init(struct dbus_monitor *monitor, GType client_type, struct spa_log *log, GDBusConnection *conn, const char *name, const char *object_path, const struct dbus_monitor_proxy_type *proxy_types, void (*on_name_owner_change)(struct dbus_monitor *monitor)); void dbus_monitor_clear(struct dbus_monitor *monitor); #endif // DBUS_MONITOR_H_ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/bluez5/decode-buffer.h000066400000000000000000000336121511204443500270200ustar00rootroot00000000000000/* Spa Bluez5 decode buffer */ /* SPDX-FileCopyrightText: Copyright © 2022 Pauli Virtanen */ /* SPDX-License-Identifier: MIT */ /** * \file decode-buffer.h Buffering for Bluetooth sources * * A linear buffer, which is compacted when it gets half full. * * Also contains buffering logic, which calculates a rate correction * factor to maintain the buffer level at the target value. * * Consider typical packet intervals with nominal frame duration * of 10ms: * * ... 5ms | 5ms | 20ms | 5ms | 5ms | 20ms ... * * ... 3ms | 3ms | 4ms | 30ms | 3ms | 3ms | 4ms | 30ms ... * * plus random jitter; 10ms nominal may occasionally have 20+ms interval. * The regular timer cycle cannot be aligned with this, so process() * may occur at any time. * * The instantaneous buffer level is the time position (in samples) of the last * received sample, relative to the nominal time position of the last sample of the last * received packet. If it is always larger than duration, there is no underrun. * * The rate correction aims to maintain the average level at a safety margin. */ #ifndef SPA_BLUEZ5_DECODE_BUFFER_H #define SPA_BLUEZ5_DECODE_BUFFER_H #include #include #include #include #include #include #include #include #include "rate-control.h" #define BUFFERING_LONG_MSEC (2*60000) #define BUFFERING_SHORT_MSEC 1000 #define BUFFERING_RATE_DIFF_MAX 0.005 #ifndef BT_PKT_SEQNUM #define BT_PKT_SEQNUM 22 #endif #ifndef BT_SCM_PKT_SEQNUM #define BT_SCM_PKT_SEQNUM 0x05 #endif struct spa_bt_decode_buffer { struct spa_log *log; uint32_t frame_size; uint32_t rate; int64_t avg_period; double rate_diff_max; int32_t target; /**< target buffer (0: automatic) */ int32_t max_extra; bool no_overrun_drop; uint8_t *buffer_decoded; uint32_t buffer_size; uint32_t buffer_reserve; uint32_t write_index; uint32_t read_index; struct spa_bt_ptp spike; /**< spikes (long window) */ struct spa_bt_ptp packet_size; /**< packet size (short window) */ struct spa_bt_rate_control ctl; double corr; uint32_t pos; int64_t duration_ns; int64_t next_nsec; double rate_diff; int32_t delay; int32_t delay_frac; double level; struct { int64_t nsec; int64_t position; } rx; uint8_t buffering:1; }; static inline int spa_bt_decode_buffer_init(struct spa_bt_decode_buffer *this, struct spa_log *log, uint32_t frame_size, uint32_t rate, uint32_t quantum_limit, uint32_t reserve) { spa_zero(*this); this->frame_size = frame_size; this->rate = rate; this->log = log; this->buffer_reserve = this->frame_size * reserve; this->buffer_size = this->frame_size * quantum_limit * 2; this->buffer_size += this->buffer_reserve; this->corr = 1.0; this->target = 0; this->buffering = true; this->max_extra = INT32_MAX; this->avg_period = BUFFERING_SHORT_MSEC * SPA_NSEC_PER_MSEC; this->rate_diff_max = BUFFERING_RATE_DIFF_MAX; spa_bt_rate_control_init(&this->ctl, 0); spa_bt_ptp_init(&this->spike, (uint64_t)this->rate * BUFFERING_LONG_MSEC / 1000, 0); spa_bt_ptp_init(&this->packet_size, (uint64_t)this->rate * BUFFERING_SHORT_MSEC / 1000, 0); if ((this->buffer_decoded = malloc(this->buffer_size)) == NULL) { this->buffer_size = 0; return -ENOMEM; } return 0; } static inline void spa_bt_decode_buffer_clear(struct spa_bt_decode_buffer *this) { free(this->buffer_decoded); spa_zero(*this); } static inline void spa_bt_decode_buffer_compact(struct spa_bt_decode_buffer *this) { uint32_t avail; spa_assert(this->read_index <= this->write_index); if (this->read_index == this->write_index) { this->read_index = 0; this->write_index = 0; goto done; } if (this->write_index > this->read_index + this->buffer_size - this->buffer_reserve) { /* Drop data to keep buffer reserve free */ spa_log_info(this->log, "%p buffer overrun: dropping data", this); this->read_index = this->write_index + this->buffer_reserve - this->buffer_size; } if (this->write_index < (this->buffer_size - this->buffer_reserve) / 2 || this->read_index == 0) goto done; avail = this->write_index - this->read_index; spa_memmove(this->buffer_decoded, SPA_PTROFF(this->buffer_decoded, this->read_index, void), avail); this->read_index = 0; this->write_index = avail; done: spa_assert(this->buffer_size - this->write_index >= this->buffer_reserve); } static inline void *spa_bt_decode_buffer_get_read(struct spa_bt_decode_buffer *this, uint32_t *avail) { spa_assert(this->write_index >= this->read_index); if (!this->buffering) *avail = this->write_index - this->read_index; else *avail = 0; return SPA_PTROFF(this->buffer_decoded, this->read_index, void); } static inline void spa_bt_decode_buffer_read(struct spa_bt_decode_buffer *this, uint32_t size) { spa_assert(size % this->frame_size == 0); this->read_index += size; } static inline void *spa_bt_decode_buffer_get_write(struct spa_bt_decode_buffer *this, uint32_t *avail) { spa_bt_decode_buffer_compact(this); spa_assert(this->buffer_size >= this->write_index); *avail = this->buffer_size - this->write_index; return SPA_PTROFF(this->buffer_decoded, this->write_index, void); } static inline size_t spa_bt_decode_buffer_get_size(struct spa_bt_decode_buffer *this) { return this->write_index - this->read_index; } static inline void spa_bt_decode_buffer_write_packet(struct spa_bt_decode_buffer *this, uint32_t size, uint64_t nsec) { const int32_t duration = this->duration_ns * this->rate / SPA_NSEC_PER_SEC; if (nsec) { this->rx.nsec = nsec; this->rx.position = size / this->frame_size; } else { this->rx.position += size / this->frame_size; } spa_assert(size % this->frame_size == 0); this->write_index += size; spa_bt_ptp_update(&this->packet_size, size / this->frame_size, size / this->frame_size); if (this->rx.nsec && this->next_nsec) { uint32_t avail = spa_bt_decode_buffer_get_size(this) / this->frame_size; int64_t dt = this->next_nsec - this->rx.nsec; this->level = dt * this->rate_diff * this->rate / SPA_NSEC_PER_SEC + avail + this->delay + this->delay_frac/1e9 - this->rx.position; } else { this->level = spa_bt_decode_buffer_get_size(this) / this->frame_size - duration; } } static inline void spa_bt_decode_buffer_set_target_latency(struct spa_bt_decode_buffer *this, int32_t samples) { this->target = samples; } static inline void spa_bt_decode_buffer_set_max_extra_latency(struct spa_bt_decode_buffer *this, int32_t samples) { this->max_extra = samples; } static inline int32_t spa_bt_decode_buffer_get_auto_latency(struct spa_bt_decode_buffer *this) { const int32_t duration = this->duration_ns * this->rate / SPA_NSEC_PER_SEC; const int32_t packet_size = SPA_CLAMP(this->packet_size.max, 0, INT32_MAX/8); const int32_t max_buf = (this->buffer_size - this->buffer_reserve) / this->frame_size; const int32_t spike = SPA_CLAMP(this->spike.max, 0, max_buf); int32_t target; target = SPA_CLAMP(SPA_ROUND_UP(SPA_MAX(spike * 3/2, duration), SPA_CLAMP((int)this->rate / 50, 1, INT32_MAX)), duration, max_buf - 2*packet_size); return SPA_MIN(target, duration + SPA_CLAMP(this->max_extra, 0, INT32_MAX - duration)); } static inline int32_t spa_bt_decode_buffer_get_target_latency(struct spa_bt_decode_buffer *this) { if (this->target) return this->target; return spa_bt_decode_buffer_get_auto_latency(this); } static inline void spa_bt_decode_buffer_recover(struct spa_bt_decode_buffer *this) { int32_t target = spa_bt_decode_buffer_get_target_latency(this); this->rx.nsec = 0; this->corr = 1.0; spa_bt_rate_control_init(&this->ctl, target * SPA_NSEC_PER_SEC / this->rate); spa_bt_decode_buffer_write_packet(this, 0, 0); } static inline void spa_bt_decode_buffer_process(struct spa_bt_decode_buffer *this, uint32_t samples, int64_t duration_ns, double rate_diff, int64_t next_nsec, int32_t delay, int32_t delay_frac) { const uint32_t data_size = samples * this->frame_size; const int32_t packet_size = SPA_CLAMP(this->packet_size.max, 0, INT32_MAX/8); int32_t target; uint32_t avail; double level; this->rate_diff = rate_diff; this->next_nsec = next_nsec; this->delay = delay; this->delay_frac = delay_frac; /* The fractional delay is given at the start of current cycle. Make it relative * to next_nsec used for the level calculations. */ this->delay_frac += (int32_t)(1e9 * samples - duration_ns * this->rate * this->rate_diff); if (SPA_UNLIKELY(duration_ns != this->duration_ns)) { this->duration_ns = duration_ns; spa_bt_decode_buffer_recover(this); } target = spa_bt_decode_buffer_get_target_latency(this); if (SPA_UNLIKELY(this->buffering)) { int32_t size = (this->write_index - this->read_index) / this->frame_size; this->corr = 1.0; spa_log_trace(this->log, "%p buffering size:%d", this, (int)size); if (size >= SPA_MAX((int)samples, target)) this->buffering = false; else return; spa_bt_ptp_update(&this->spike, packet_size, samples); spa_bt_decode_buffer_recover(this); } spa_bt_decode_buffer_get_read(this, &avail); /* Track buffer level */ level = SPA_MAX(this->level, 0); spa_bt_ptp_update(&this->spike, (int32_t)(this->ctl.avg * this->rate / SPA_NSEC_PER_SEC - level), samples); if (!this->no_overrun_drop && level > SPA_MAX(4 * target, 3*(int32_t)samples) && avail > data_size) { /* Lagging too much: drop data */ uint32_t size = SPA_MIN(avail - data_size, ((int32_t)ceil(level) - target) * this->frame_size); spa_bt_decode_buffer_read(this, size); spa_log_trace(this->log, "%p overrun samples:%d level:%.2f target:%d", this, (int)size/this->frame_size, level, (int)target); spa_bt_decode_buffer_recover(this); } this->pos += samples; enum spa_log_level log_level = (this->pos > this->rate) ? SPA_LOG_LEVEL_DEBUG : SPA_LOG_LEVEL_TRACE; if (SPA_UNLIKELY(spa_log_level_topic_enabled(this->log, SPA_LOG_TOPIC_DEFAULT, log_level))) { spa_log_lev(this->log, log_level, "%p avg:%.2f target:%d level:%.2f buffer:%d spike:%d corr:%g", this, this->ctl.avg * this->rate / SPA_NSEC_PER_SEC, (int)target, level, (int)(avail / this->frame_size), (int)this->spike.max, (double)this->corr - 1); this->pos = 0; } this->corr = spa_bt_rate_control_update(&this->ctl, level * SPA_NSEC_PER_SEC / this->rate, ((double)target + 0.5/this->rate) * SPA_NSEC_PER_SEC / this->rate, duration_ns, this->avg_period, this->rate_diff_max); spa_bt_decode_buffer_get_read(this, &avail); if (avail < data_size) { spa_log_debug(this->log, "%p underrun samples:%d", this, (data_size - avail) / this->frame_size); this->buffering = true; spa_bt_ptp_update(&this->spike, samples, 0); } } struct spa_bt_recvmsg_data { struct spa_log *log; struct spa_system *data_system; int fd; int64_t offset; int64_t err; }; static inline void spa_bt_recvmsg_update_clock(struct spa_bt_recvmsg_data *data, uint64_t *now) { const int64_t max_resync = (50 * SPA_NSEC_PER_USEC); const int64_t n_avg = 10; struct timespec ts1, ts2, ts3; int64_t t1, t2, t3, offset, err; spa_system_clock_gettime(data->data_system, CLOCK_MONOTONIC, &ts1); spa_system_clock_gettime(data->data_system, CLOCK_REALTIME, &ts2); spa_system_clock_gettime(data->data_system, CLOCK_MONOTONIC, &ts3); t1 = SPA_TIMESPEC_TO_NSEC(&ts1); t2 = SPA_TIMESPEC_TO_NSEC(&ts2); t3 = SPA_TIMESPEC_TO_NSEC(&ts3); if (now) *now = t1; offset = t1 + (t3 - t1) / 2 - t2; /* Moving average smoothing, discarding outliers */ err = offset - data->offset; if (SPA_ABS(err) > max_resync) { /* Clock jump */ spa_log_debug(data->log, "%p: nsec err %"PRIi64" > max_resync %"PRIi64", resetting", data, err, max_resync); data->offset = offset; data->err = 0; err = 0; } else if (SPA_ABS(err) / 2 <= data->err) { data->offset += err / n_avg; } data->err += (SPA_ABS(err) - data->err) / n_avg; } static inline ssize_t spa_bt_recvmsg(struct spa_bt_recvmsg_data *r, void *buf, size_t max_size, uint64_t *rx_time, int *seqnum) { union { char buf[CMSG_SPACE(sizeof(struct scm_timestamping)) + CMSG_SPACE(sizeof(uint16_t))]; struct cmsghdr align; } control; struct iovec data = { .iov_base = buf, .iov_len = max_size }; struct msghdr msg = { .msg_iov = &data, .msg_iovlen = 1, .msg_control = control.buf, .msg_controllen = sizeof(control.buf), }; struct cmsghdr *cmsg; uint64_t t = 0, now; ssize_t res; *seqnum = -1; res = recvmsg(r->fd, &msg, MSG_DONTWAIT); if (res < 0 || !rx_time) return res; spa_bt_recvmsg_update_clock(r, &now); for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) { struct scm_timestamping *tss; if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_TIMESTAMPING) { tss = (struct scm_timestamping *)CMSG_DATA(cmsg); t = SPA_TIMESPEC_TO_NSEC(&tss->ts[0]); } else if (cmsg->cmsg_level == SOL_BLUETOOTH && cmsg->cmsg_type == BT_SCM_PKT_SEQNUM) { *seqnum = *((uint16_t *)CMSG_DATA(cmsg)); } } if (!t) { *rx_time = now; return res; } *rx_time = t + r->offset; /* CLOCK_REALTIME may jump, so sanity check */ if (*rx_time > now || *rx_time + 20 * SPA_NSEC_PER_MSEC < now) *rx_time = now; spa_log_trace(r->log, "%p: rx:%" PRIu64 " now:%" PRIu64 " d:%"PRIu64" off:%"PRIi64" sn:%d", r, *rx_time, now, now - *rx_time, r->offset, *seqnum); return res; } static inline void spa_bt_recvmsg_init(struct spa_bt_recvmsg_data *data, int fd, struct spa_system *data_system, struct spa_log *log) { int flags = 0; socklen_t len = sizeof(flags); uint32_t opt; data->log = log; data->data_system = data_system; data->fd = fd; data->offset = 0; data->err = 0; if (getsockopt(fd, SOL_SOCKET, SO_TIMESTAMPING, &flags, &len) < 0) spa_log_info(log, "failed to get SO_TIMESTAMPING"); flags |= SOF_TIMESTAMPING_SOFTWARE | SOF_TIMESTAMPING_RX_SOFTWARE; if (setsockopt(fd, SOL_SOCKET, SO_TIMESTAMPING, &flags, sizeof(flags)) < 0) spa_log_info(log, "failed to set SO_TIMESTAMPING"); opt = 1; if (setsockopt(fd, SOL_BLUETOOTH, BT_PKT_SEQNUM, &opt, sizeof(opt)) < 0) spa_log_info(log, "failed to set BT_PKT_SEQNUM"); } #endif pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/bluez5/defs.h000066400000000000000000001025011511204443500252410ustar00rootroot00000000000000/* Spa Bluez5 Monitor */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_BLUEZ5_DEFS_H #define SPA_BLUEZ5_DEFS_H #include "config.h" #include #include #include #include #include #include #include #include #include #ifdef __cplusplus extern "C" { #endif #define BLUEZ_SERVICE "org.bluez" #define BLUEZ_PROFILE_MANAGER_INTERFACE BLUEZ_SERVICE ".ProfileManager1" #define BLUEZ_PROFILE_INTERFACE BLUEZ_SERVICE ".Profile1" #define BLUEZ_ADAPTER_INTERFACE BLUEZ_SERVICE ".Adapter1" #define BLUEZ_DEVICE_INTERFACE BLUEZ_SERVICE ".Device1" #define BLUEZ_DEVICE_SET_INTERFACE BLUEZ_SERVICE ".DeviceSet1" #define BLUEZ_MEDIA_INTERFACE BLUEZ_SERVICE ".Media1" #define BLUEZ_MEDIA_ENDPOINT_INTERFACE BLUEZ_SERVICE ".MediaEndpoint1" #define BLUEZ_MEDIA_TRANSPORT_INTERFACE BLUEZ_SERVICE ".MediaTransport1" #define BLUEZ_INTERFACE_BATTERY_PROVIDER BLUEZ_SERVICE ".BatteryProvider1" #define BLUEZ_INTERFACE_BATTERY_PROVIDER_MANAGER BLUEZ_SERVICE ".BatteryProviderManager1" #define DBUS_INTERFACE_OBJECT_MANAGER "org.freedesktop.DBus.ObjectManager" #define DBUS_SIGNAL_INTERFACES_ADDED "InterfacesAdded" #define DBUS_SIGNAL_INTERFACES_REMOVED "InterfacesRemoved" #define DBUS_SIGNAL_PROPERTIES_CHANGED "PropertiesChanged" #define PIPEWIRE_BATTERY_PROVIDER "/org/freedesktop/pipewire/battery" #define OBJECT_MANAGER_INTROSPECT_XML \ DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \ "\n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ " \n" \ "\n" #define ENDPOINT_INTROSPECT_XML \ DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \ "" \ " " \ " " \ " " \ " " \ " " \ " " \ " " \ " " \ " " \ " " \ " " \ " " \ " " \ " " \ " " \ " " \ " " \ " " \ " " \ " " \ "" #define PROFILE_INTROSPECT_XML \ DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \ "" \ " " \ " " \ " " \ " " \ " " \ " " \ " " \ " " \ " " \ " " \ " " \ " " \ " " \ " " \ " " \ " " \ " " \ "" #define BLUEZ_ERROR_NOT_SUPPORTED "org.bluez.Error.NotSupported" #define SPA_BT_UUID_A2DP_SOURCE "0000110a-0000-1000-8000-00805f9b34fb" #define SPA_BT_UUID_A2DP_SINK "0000110b-0000-1000-8000-00805f9b34fb" #define SPA_BT_UUID_HSP_HS "00001108-0000-1000-8000-00805f9b34fb" #define SPA_BT_UUID_HSP_HS_ALT "00001131-0000-1000-8000-00805f9b34fb" #define SPA_BT_UUID_HSP_AG "00001112-0000-1000-8000-00805f9b34fb" #define SPA_BT_UUID_HFP_HF "0000111e-0000-1000-8000-00805f9b34fb" #define SPA_BT_UUID_HFP_AG "0000111f-0000-1000-8000-00805f9b34fb" #define SPA_BT_UUID_PACS "00001850-0000-1000-8000-00805f9b34fb" #define SPA_BT_UUID_BAP_SINK "00002bc9-0000-1000-8000-00805f9b34fb" #define SPA_BT_UUID_BAP_SOURCE "00002bcb-0000-1000-8000-00805f9b34fb" #define SPA_BT_UUID_BAP_BROADCAST_SOURCE "00001852-0000-1000-8000-00805f9b34fb" #define SPA_BT_UUID_BAP_BROADCAST_SINK "00001851-0000-1000-8000-00805f9b34fb" #define SPA_BT_UUID_ASHA_SINK "0000FDF0-0000-1000-8000-00805f9b34fb" #define PROFILE_HSP_AG "/Profile/HSPAG" #define PROFILE_HSP_HS "/Profile/HSPHS" #define PROFILE_HFP_AG "/Profile/HFPAG" #define PROFILE_HFP_HF "/Profile/HFPHF" #define HSP_HS_DEFAULT_CHANNEL 3 #define SOURCE_ID_BLUETOOTH 0x1 /* Bluetooth SIG */ #define SOURCE_ID_USB 0x2 /* USB Implementer's Forum */ #define BUS_TYPE_USB 1 #define BUS_TYPE_OTHER 255 #define A2DP_OBJECT_MANAGER_PATH "/MediaEndpoint" #define A2DP_SINK_ENDPOINT A2DP_OBJECT_MANAGER_PATH "/A2DPSink" #define A2DP_SOURCE_ENDPOINT A2DP_OBJECT_MANAGER_PATH "/A2DPSource" #define BAP_OBJECT_MANAGER_PATH "/MediaEndpointLE" #define BAP_SINK_ENDPOINT BAP_OBJECT_MANAGER_PATH "/BAPSink" #define BAP_SOURCE_ENDPOINT BAP_OBJECT_MANAGER_PATH "/BAPSource" #define BAP_BROADCAST_SOURCE_ENDPOINT BAP_OBJECT_MANAGER_PATH "/BAPBroadcastSource" #define BAP_BROADCAST_SINK_ENDPOINT BAP_OBJECT_MANAGER_PATH "/BAPBroadcastSink" #define SPA_BT_UNKNOWN_DELAY 0 #define SPA_BT_NO_BATTERY ((uint8_t)255) #define MAX_CHANNELS (SPA_AUDIO_MAX_CHANNELS) enum spa_bt_media_direction { SPA_BT_MEDIA_SOURCE, SPA_BT_MEDIA_SINK, SPA_BT_MEDIA_SOURCE_BROADCAST, SPA_BT_MEDIA_SINK_BROADCAST, SPA_BT_MEDIA_DIRECTION_LAST, }; enum spa_bt_profile { SPA_BT_PROFILE_NULL = 0, SPA_BT_PROFILE_BAP_SINK = (1 << 0), SPA_BT_PROFILE_BAP_SOURCE = (1 << 1), SPA_BT_PROFILE_A2DP_SINK = (1 << 2), SPA_BT_PROFILE_A2DP_SOURCE = (1 << 3), SPA_BT_PROFILE_ASHA_SINK = (1 << 4), SPA_BT_PROFILE_HSP_HS = (1 << 5), SPA_BT_PROFILE_HSP_AG = (1 << 6), SPA_BT_PROFILE_HFP_HF = (1 << 7), SPA_BT_PROFILE_HFP_AG = (1 << 8), SPA_BT_PROFILE_BAP_BROADCAST_SOURCE = (1 << 9), SPA_BT_PROFILE_BAP_BROADCAST_SINK = (1 << 10), SPA_BT_PROFILE_A2DP_DUPLEX = (SPA_BT_PROFILE_A2DP_SINK | SPA_BT_PROFILE_A2DP_SOURCE), SPA_BT_PROFILE_BAP_DUPLEX = (SPA_BT_PROFILE_BAP_SINK | SPA_BT_PROFILE_BAP_SOURCE), SPA_BT_PROFILE_HEADSET_HEAD_UNIT = (SPA_BT_PROFILE_HSP_HS | SPA_BT_PROFILE_HFP_HF), SPA_BT_PROFILE_HEADSET_AUDIO_GATEWAY = (SPA_BT_PROFILE_HSP_AG | SPA_BT_PROFILE_HFP_AG), SPA_BT_PROFILE_HEADSET_AUDIO = (SPA_BT_PROFILE_HEADSET_HEAD_UNIT | SPA_BT_PROFILE_HEADSET_AUDIO_GATEWAY), SPA_BT_PROFILE_BAP_AUDIO = (SPA_BT_PROFILE_BAP_SINK | SPA_BT_PROFILE_BAP_BROADCAST_SINK | SPA_BT_PROFILE_BAP_SOURCE | SPA_BT_PROFILE_BAP_BROADCAST_SOURCE), SPA_BT_PROFILE_MEDIA_SINK = (SPA_BT_PROFILE_A2DP_SINK | SPA_BT_PROFILE_BAP_SINK | SPA_BT_PROFILE_BAP_BROADCAST_SINK), SPA_BT_PROFILE_MEDIA_SOURCE = (SPA_BT_PROFILE_A2DP_SOURCE | SPA_BT_PROFILE_BAP_SOURCE | SPA_BT_PROFILE_BAP_BROADCAST_SOURCE), }; static inline enum spa_bt_profile spa_bt_profile_from_uuid(const char *uuid) { if (!uuid) return 0; else if (strcasecmp(uuid, SPA_BT_UUID_A2DP_SOURCE) == 0) return SPA_BT_PROFILE_A2DP_SOURCE; else if (strcasecmp(uuid, SPA_BT_UUID_A2DP_SINK) == 0) return SPA_BT_PROFILE_A2DP_SINK; else if (strcasecmp(uuid, SPA_BT_UUID_HSP_HS) == 0) return SPA_BT_PROFILE_HSP_HS; else if (strcasecmp(uuid, SPA_BT_UUID_HSP_HS_ALT) == 0) return SPA_BT_PROFILE_HSP_HS; else if (strcasecmp(uuid, SPA_BT_UUID_HSP_AG) == 0) return SPA_BT_PROFILE_HSP_AG; else if (strcasecmp(uuid, SPA_BT_UUID_HFP_HF) == 0) return SPA_BT_PROFILE_HFP_HF; else if (strcasecmp(uuid, SPA_BT_UUID_HFP_AG) == 0) return SPA_BT_PROFILE_HFP_AG; else if (strcasecmp(uuid, SPA_BT_UUID_BAP_SINK) == 0) return SPA_BT_PROFILE_BAP_SINK; else if (strcasecmp(uuid, SPA_BT_UUID_BAP_SOURCE) == 0) return SPA_BT_PROFILE_BAP_SOURCE; else if (strcasecmp(uuid, SPA_BT_UUID_BAP_BROADCAST_SOURCE) == 0) return SPA_BT_PROFILE_BAP_BROADCAST_SOURCE; else if (strcasecmp(uuid, SPA_BT_UUID_BAP_BROADCAST_SINK) == 0) return SPA_BT_PROFILE_BAP_BROADCAST_SINK; else if (strcasecmp(uuid, SPA_BT_UUID_ASHA_SINK) == 0) return SPA_BT_PROFILE_ASHA_SINK; else return 0; } int spa_bt_profiles_from_json_array(const char *str); int spa_bt_format_vendor_product_id(uint16_t source_id, uint16_t vendor_id, uint16_t product_id, char *vendor_str, int vendor_str_size, char *product_str, int product_str_size); enum spa_bt_hfp_ag_feature { SPA_BT_HFP_AG_FEATURE_NONE = (0), SPA_BT_HFP_AG_FEATURE_3WAY = (1 << 0), SPA_BT_HFP_AG_FEATURE_ECNR = (1 << 1), SPA_BT_HFP_AG_FEATURE_VOICE_RECOG = (1 << 2), SPA_BT_HFP_AG_FEATURE_IN_BAND_RING_TONE = (1 << 3), SPA_BT_HFP_AG_FEATURE_ATTACH_VOICE_TAG = (1 << 4), SPA_BT_HFP_AG_FEATURE_REJECT_CALL = (1 << 5), SPA_BT_HFP_AG_FEATURE_ENHANCED_CALL_STATUS = (1 << 6), SPA_BT_HFP_AG_FEATURE_ENHANCED_CALL_CONTROL = (1 << 7), SPA_BT_HFP_AG_FEATURE_EXTENDED_RES_CODE = (1 << 8), SPA_BT_HFP_AG_FEATURE_CODEC_NEGOTIATION = (1 << 9), SPA_BT_HFP_AG_FEATURE_HF_INDICATORS = (1 << 10), SPA_BT_HFP_AG_FEATURE_ESCO_S4 = (1 << 11), }; enum spa_bt_hfp_sdp_ag_features { SPA_BT_HFP_SDP_AG_FEATURE_NONE = (0), SPA_BT_HFP_SDP_AG_FEATURE_3WAY = (1 << 0), SPA_BT_HFP_SDP_AG_FEATURE_ECNR = (1 << 1), SPA_BT_HFP_SDP_AG_FEATURE_VOICE_RECOG = (1 << 2), SPA_BT_HFP_SDP_AG_FEATURE_IN_BAND_RING_TONE = (1 << 3), SPA_BT_HFP_SDP_AG_FEATURE_ATTACH_VOICE_TAG = (1 << 4), SPA_BT_HFP_SDP_AG_FEATURE_WIDEBAND_SPEECH = (1 << 5), SPA_BT_HFP_SDP_AG_FEATURE_ENH_VOICE_RECOG_STATUS = (1 << 6), SPA_BT_HFP_SDP_AG_FEATURE_VOICE_RECOG_TEXT = (1 << 7), SPA_BT_HFP_SDP_AG_FEATURE_SUPER_WIDEBAND_SPEECH = (1 << 8), }; enum spa_bt_hfp_hf_feature { SPA_BT_HFP_HF_FEATURE_NONE = (0), SPA_BT_HFP_HF_FEATURE_ECNR = (1 << 0), SPA_BT_HFP_HF_FEATURE_3WAY = (1 << 1), SPA_BT_HFP_HF_FEATURE_CLIP = (1 << 2), SPA_BT_HFP_HF_FEATURE_VOICE_RECOGNITION = (1 << 3), SPA_BT_HFP_HF_FEATURE_REMOTE_VOLUME_CONTROL = (1 << 4), SPA_BT_HFP_HF_FEATURE_ENHANCED_CALL_STATUS = (1 << 5), SPA_BT_HFP_HF_FEATURE_ENHANCED_CALL_CONTROL = (1 << 6), SPA_BT_HFP_HF_FEATURE_CODEC_NEGOTIATION = (1 << 7), SPA_BT_HFP_HF_FEATURE_HF_INDICATORS = (1 << 8), SPA_BT_HFP_HF_FEATURE_ESCO_S4 = (1 << 9), }; /* https://btprodspecificationrefs.blob.core.windows.net/assigned-numbers/Assigned%20Number%20Types/Hands-Free%20Profile.pdf */ enum spa_bt_hfp_hf_indicator { SPA_BT_HFP_HF_INDICATOR_ENHANCED_SAFETY = 1, SPA_BT_HFP_HF_INDICATOR_BATTERY_LEVEL = 2, }; /* HFP Command AT+IPHONEACCEV * https://developer.apple.com/accessories/Accessory-Design-Guidelines.pdf */ enum spa_bt_hfp_hf_iphoneaccev_keys { SPA_BT_HFP_HF_IPHONEACCEV_KEY_BATTERY_LEVEL = 1, SPA_BT_HFP_HF_IPHONEACCEV_KEY_DOCK_STATE = 2, }; /* HFP Command AT+XAPL * https://developer.apple.com/accessories/Accessory-Design-Guidelines.pdf * Bits 0, 5 and above are reserved. */ enum spa_bt_hfp_hf_xapl_features { SPA_BT_HFP_HF_XAPL_FEATURE_BATTERY_REPORTING = (1 << 1), SPA_BT_HFP_HF_XAPL_FEATURE_DOCKED_OR_POWERED = (1 << 2), SPA_BT_HFP_HF_XAPL_FEATURE_SIRI_STATUS_REPORTING = (1 << 3), SPA_BT_HFP_HF_XAPL_FEATURE_NOISE_REDUCTION_REPORTING = (1 << 4), }; enum spa_bt_hfp_sdp_hf_features { SPA_BT_HFP_SDP_HF_FEATURE_NONE = (0), SPA_BT_HFP_SDP_HF_FEATURE_ECNR = (1 << 0), SPA_BT_HFP_SDP_HF_FEATURE_3WAY = (1 << 1), SPA_BT_HFP_SDP_HF_FEATURE_CLIP = (1 << 2), SPA_BT_HFP_SDP_HF_FEATURE_VOICE_RECOGNITION = (1 << 3), SPA_BT_HFP_SDP_HF_FEATURE_REMOTE_VOLUME_CONTROL = (1 << 4), SPA_BT_HFP_SDP_HF_FEATURE_WIDEBAND_SPEECH = (1 << 5), SPA_BT_HFP_SDP_HF_FEATURE_ENH_VOICE_RECOG_STATUS = (1 << 6), SPA_BT_HFP_SDP_HF_FEATURE_VOICE_RECOG_TEXT = (1 << 7), SPA_BT_HFP_SDP_HF_FEATURE_SUPER_WIDEBAND_SPEECH = (1 << 8), }; static inline const char *spa_bt_profile_name (enum spa_bt_profile profile) { switch (profile) { case SPA_BT_PROFILE_ASHA_SINK: return "asha-sink"; case SPA_BT_PROFILE_A2DP_SOURCE: return "a2dp-source"; case SPA_BT_PROFILE_A2DP_SINK: return "a2dp-sink"; case SPA_BT_PROFILE_A2DP_DUPLEX: return "a2dp-duplex"; case SPA_BT_PROFILE_HSP_HS: case SPA_BT_PROFILE_HFP_HF: case SPA_BT_PROFILE_HEADSET_HEAD_UNIT: return "headset-head-unit"; case SPA_BT_PROFILE_HSP_AG: case SPA_BT_PROFILE_HFP_AG: case SPA_BT_PROFILE_HEADSET_AUDIO_GATEWAY: return "headset-audio-gateway"; case SPA_BT_PROFILE_HEADSET_AUDIO: return "headset-audio"; case SPA_BT_PROFILE_BAP_SOURCE: case SPA_BT_PROFILE_BAP_BROADCAST_SOURCE: return "bap-source"; case SPA_BT_PROFILE_BAP_SINK: case SPA_BT_PROFILE_BAP_BROADCAST_SINK: return "bap-sink"; case SPA_BT_PROFILE_BAP_DUPLEX: return "bap-duplex"; default: break; } return "unknown"; } struct spa_bt_monitor; struct spa_bt_backend; struct spa_bt_player; struct spa_bt_adapter { struct spa_list link; struct spa_bt_monitor *monitor; struct spa_bt_player *dummy_player; char *path; char *alias; char *address; char *name; int bus_type; uint16_t source_id; uint16_t vendor_id; uint16_t product_id; uint16_t version_id; uint32_t bluetooth_class; uint32_t profiles; int powered; unsigned int has_msbc:1; unsigned int msbc_probed:1; unsigned int legacy_endpoints_registered:1; unsigned int a2dp_application_registered:1; unsigned int bap_application_registered:1; unsigned int player_registered:1; unsigned int has_battery_provider:1; unsigned int battery_provider_unavailable:1; unsigned int le_audio_supported:1; unsigned int has_adapter1_interface:1; unsigned int has_media1_interface:1; unsigned int le_audio_bcast_supported:1; unsigned int tx_timestamping_supported:1; }; enum spa_bt_form_factor { SPA_BT_FORM_FACTOR_UNKNOWN, SPA_BT_FORM_FACTOR_HEADSET, SPA_BT_FORM_FACTOR_HANDSFREE, SPA_BT_FORM_FACTOR_MICROPHONE, SPA_BT_FORM_FACTOR_SPEAKER, SPA_BT_FORM_FACTOR_HEADPHONE, SPA_BT_FORM_FACTOR_PORTABLE, SPA_BT_FORM_FACTOR_CAR, SPA_BT_FORM_FACTOR_HIFI, SPA_BT_FORM_FACTOR_PHONE, }; static inline const char *spa_bt_form_factor_name(enum spa_bt_form_factor ff) { switch (ff) { case SPA_BT_FORM_FACTOR_HEADSET: return "headset"; case SPA_BT_FORM_FACTOR_HANDSFREE: return "hands-free"; case SPA_BT_FORM_FACTOR_MICROPHONE: return "microphone"; case SPA_BT_FORM_FACTOR_SPEAKER: return "speaker"; case SPA_BT_FORM_FACTOR_HEADPHONE: return "headphone"; case SPA_BT_FORM_FACTOR_PORTABLE: return "portable"; case SPA_BT_FORM_FACTOR_CAR: return "car"; case SPA_BT_FORM_FACTOR_HIFI: return "hifi"; case SPA_BT_FORM_FACTOR_PHONE: return "phone"; case SPA_BT_FORM_FACTOR_UNKNOWN: default: return "unknown"; } } static inline const char *spa_bt_form_factor_icon_name(enum spa_bt_form_factor ff) { switch (ff) { case SPA_BT_FORM_FACTOR_HEADSET: return "audio-headset-bluetooth"; case SPA_BT_FORM_FACTOR_HANDSFREE: return "audio-handsfree-bluetooth"; case SPA_BT_FORM_FACTOR_MICROPHONE: return "audio-input-microphone-bluetooth"; case SPA_BT_FORM_FACTOR_SPEAKER: return "audio-speakers-bluetooth"; case SPA_BT_FORM_FACTOR_HEADPHONE: return "audio-headphones-bluetooth"; case SPA_BT_FORM_FACTOR_PORTABLE: return "multimedia-player-bluetooth"; case SPA_BT_FORM_FACTOR_PHONE: return "phone-bluetooth"; case SPA_BT_FORM_FACTOR_CAR: case SPA_BT_FORM_FACTOR_HIFI: case SPA_BT_FORM_FACTOR_UNKNOWN: default: return "audio-card-bluetooth"; } } static inline enum spa_bt_form_factor spa_bt_form_factor_from_class(uint32_t bluetooth_class) { uint32_t major, minor; /* See Bluetooth Assigned Numbers: * https://www.bluetooth.org/Technical/AssignedNumbers/baseband.htm */ major = (bluetooth_class >> 8) & 0x1F; minor = (bluetooth_class >> 2) & 0x3F; switch (major) { case 2: return SPA_BT_FORM_FACTOR_PHONE; case 4: switch (minor) { case 1: return SPA_BT_FORM_FACTOR_HEADSET; case 2: return SPA_BT_FORM_FACTOR_HANDSFREE; case 4: return SPA_BT_FORM_FACTOR_MICROPHONE; case 5: return SPA_BT_FORM_FACTOR_SPEAKER; case 6: return SPA_BT_FORM_FACTOR_HEADPHONE; case 7: return SPA_BT_FORM_FACTOR_PORTABLE; case 8: return SPA_BT_FORM_FACTOR_CAR; case 10: return SPA_BT_FORM_FACTOR_HIFI; } } return SPA_BT_FORM_FACTOR_UNKNOWN; } struct spa_bt_transport; struct spa_bt_device_events { #define SPA_VERSION_BT_DEVICE_EVENTS 0 uint32_t version; /** Device connection status */ void (*connected) (void *data, bool connected); /** Codec switching completed */ void (*codec_switched) (void *data, int status); /** Codec switching initiated or completed by another device */ void (*codec_switch_other) (void *data, bool switching); /** Profile configuration changed */ void (*profiles_changed) (void *data, uint32_t connected_change); /** Device set configuration changed */ void (*device_set_changed) (void *data); /** Switch profile between OFF and HSP_HFP */ void (*switch_profile) (void *data); /** Device freed */ void (*destroy) (void *data); }; struct media_codec; struct spa_bt_set_membership { struct spa_list link; struct spa_list others; struct spa_bt_device *device; char *path; uint8_t rank; bool leader; }; #define spa_bt_for_each_set_member(s, set) \ for ((s) = (set); (s); (s) = spa_list_next((s), others), (s) = (s) != (set) ? (s) : NULL) struct spa_bt_device { struct spa_list link; struct spa_bt_monitor *monitor; struct spa_bt_adapter *adapter; uint32_t id; char *path; char *alias; char *address; char *adapter_path; char *battery_path; char *name; char *icon; uint16_t source_id; uint16_t vendor_id; uint16_t product_id; uint16_t version_id; uint32_t bluetooth_class; uint16_t appearance; uint16_t RSSI; int paired; int trusted; int connected; int blocked; uint32_t profiles; uint32_t connected_profiles; uint32_t reconnect_profiles; int reconnect_state; struct spa_source timer; struct spa_list remote_endpoint_list; struct spa_list transport_list; struct spa_list codec_switch_list; struct spa_list set_membership_list; uint8_t battery; int has_battery; uint32_t hw_volume_profiles; /* Even though A2DP volume is exposed on transport interface, the * volume activation info would not be variate between transports * under same device. So it's safe to cache activation info here. */ bool a2dp_volume_active[2]; uint64_t last_bluez_action_time; struct spa_hook_list listener_list; bool added; const struct spa_dict *settings; DBusPendingCall *battery_pending_call; const struct media_codec *preferred_codec; uint32_t preferred_profiles; }; struct spa_bt_device *spa_bt_device_find(struct spa_bt_monitor *monitor, const char *path); struct spa_bt_device *spa_bt_device_find_by_address(struct spa_bt_monitor *monitor, const char *remote_address, const char *local_address); int spa_bt_device_add_profile(struct spa_bt_device *device, enum spa_bt_profile profile); int spa_bt_device_connect_profile(struct spa_bt_device *device, enum spa_bt_profile profile); int spa_bt_device_check_profiles(struct spa_bt_device *device, bool force); int spa_bt_device_ensure_media_codec(struct spa_bt_device *device, const struct media_codec * const *codecs, uint32_t profiles); int spa_bt_device_ensure_hfp_codec(struct spa_bt_device *device, const struct media_codec *codec); bool spa_bt_device_supports_media_codec(struct spa_bt_device *device, const struct media_codec *codec, enum spa_bt_profile profile); const struct media_codec **spa_bt_device_get_supported_media_codecs(struct spa_bt_device *device, size_t *count); int spa_bt_device_release_transports(struct spa_bt_device *device); int spa_bt_device_report_battery_level(struct spa_bt_device *device, uint8_t percentage); void spa_bt_device_update_last_bluez_action_time(struct spa_bt_device *device); const struct media_codec * const * spa_bt_get_media_codecs(struct spa_bt_monitor *monitor); const struct media_codec *spa_bt_get_hfp_codec(struct spa_bt_monitor *monitor, unsigned int hfp_codec_id); #define spa_bt_device_emit(d,m,v,...) spa_hook_list_call(&(d)->listener_list, \ struct spa_bt_device_events, \ m, v, ##__VA_ARGS__) #define spa_bt_device_emit_connected(d,...) spa_bt_device_emit(d, connected, 0, __VA_ARGS__) #define spa_bt_device_emit_codec_switched(d,...) spa_bt_device_emit(d, codec_switched, 0, __VA_ARGS__) #define spa_bt_device_emit_codec_switch_other(d,...) spa_bt_device_emit(d, codec_switch_other, 0, __VA_ARGS__) #define spa_bt_device_emit_profiles_changed(d,...) spa_bt_device_emit(d, profiles_changed, 0, __VA_ARGS__) #define spa_bt_device_emit_device_set_changed(d) spa_bt_device_emit(d, device_set_changed, 0) #define spa_bt_device_emit_switch_profile(d) spa_bt_device_emit(d, switch_profile, 0) #define spa_bt_device_emit_destroy(d) spa_bt_device_emit(d, destroy, 0) #define spa_bt_device_add_listener(d,listener,events,data) \ spa_hook_list_append(&(d)->listener_list, listener, events, data) struct spa_bt_iso_io; struct spa_bt_sco_io; struct spa_bt_sco_io *spa_bt_sco_io_create(struct spa_bt_transport *transport, struct spa_loop *data_loop, struct spa_system *data_system, struct spa_log *log); void spa_bt_sco_io_destroy(struct spa_bt_sco_io *io); void spa_bt_sco_io_set_source_cb(struct spa_bt_sco_io *io, int (*source_cb)(void *userdata, uint8_t *data, int size, uint64_t rx_time), void *userdata); int spa_bt_sco_io_write(struct spa_bt_sco_io *io, const uint8_t *buf, size_t size); void spa_bt_sco_io_write_start(struct spa_bt_sco_io *io); #define SPA_BT_VOLUME_ID_RX 0 #define SPA_BT_VOLUME_ID_TX 1 #define SPA_BT_VOLUME_ID_TERM 2 #define SPA_BT_VOLUME_INVALID -1 #define SPA_BT_VOLUME_HS_MAX 15 #define SPA_BT_VOLUME_A2DP_MAX 127 #define SPA_BT_VOLUME_BAP_MAX 255 enum spa_bt_transport_state { SPA_BT_TRANSPORT_STATE_ERROR = -1, SPA_BT_TRANSPORT_STATE_IDLE = 0, SPA_BT_TRANSPORT_STATE_PENDING = 1, SPA_BT_TRANSPORT_STATE_ACTIVE = 2, }; struct spa_bt_transport_events { #define SPA_VERSION_BT_TRANSPORT_EVENTS 0 uint32_t version; void (*destroy) (void *data); void (*delay_changed) (void *data); void (*state_changed) (void *data, enum spa_bt_transport_state old, enum spa_bt_transport_state state); void (*volume_changed) (void *data); }; struct spa_bt_transport_implementation { #define SPA_VERSION_BT_TRANSPORT_IMPLEMENTATION 0 uint32_t version; int (*acquire) (void *data, bool optional); int (*release) (void *data); int (*set_volume) (void *data, int id, float volume); int (*set_delay) (void *data, int64_t delay_nsec); int (*destroy) (void *data); }; struct spa_bt_transport_volume { bool active; float volume; int hw_volume_max; /* XXX: items below should be put to user_data */ int hw_volume; int new_hw_volume; }; struct spa_bt_transport { struct spa_list link; struct spa_bt_monitor *monitor; struct spa_bt_backend *backend; char *path; struct spa_bt_device *device; struct spa_list device_link; enum spa_bt_profile profile; enum spa_bt_transport_state state; const struct media_codec *media_codec; void *configuration; int configuration_len; char *endpoint_path; char *remote_endpoint_path; bool bap_initiator; struct spa_list bap_transport_linked; uint32_t n_channels; uint32_t channels[MAX_CHANNELS]; struct spa_bt_transport_volume volumes[SPA_BT_VOLUME_ID_TERM]; int acquire_refcount; bool acquired; bool keepalive; int error_count; uint64_t last_error_time; int fd; uint16_t read_mtu; uint16_t write_mtu; unsigned int delay_us; unsigned int latency_us; uint8_t bap_cig; uint8_t bap_cis; uint8_t bap_big; uint8_t bap_bis; struct spa_bt_iso_io *iso_io; struct spa_bt_sco_io *sco_io; struct spa_source volume_timer; struct spa_source release_timer; DBusPendingCall *acquire_call; DBusPendingCall *volume_call; struct spa_hook_list listener_list; struct spa_callbacks impl; /* For ASHA */ bool asha_right_side; uint64_t hisyncid; /* user_data must be the last item in the struct */ void *user_data; }; struct spa_bt_transport *spa_bt_transport_create(struct spa_bt_monitor *monitor, char *path, size_t extra); void spa_bt_transport_free(struct spa_bt_transport *transport); void spa_bt_transport_set_state(struct spa_bt_transport *transport, enum spa_bt_transport_state state); struct spa_bt_transport *spa_bt_transport_find(struct spa_bt_monitor *monitor, const char *path); struct spa_bt_transport *spa_bt_transport_find_full(struct spa_bt_monitor *monitor, bool (*callback) (struct spa_bt_transport *t, const void *data), const void *data); int64_t spa_bt_transport_get_delay_nsec(struct spa_bt_transport *transport); bool spa_bt_transport_volume_enabled(struct spa_bt_transport *transport); int spa_bt_transport_acquire(struct spa_bt_transport *t, bool optional); int spa_bt_transport_release(struct spa_bt_transport *t); int spa_bt_transport_keepalive(struct spa_bt_transport *t, bool keepalive); int spa_bt_transport_ensure_sco_io(struct spa_bt_transport *t, struct spa_loop *data_loop, struct spa_system *data_system); #define spa_bt_transport_emit(t,m,v,...) spa_hook_list_call(&(t)->listener_list, \ struct spa_bt_transport_events, \ m, v, ##__VA_ARGS__) #define spa_bt_transport_emit_destroy(t) spa_bt_transport_emit(t, destroy, 0) #define spa_bt_transport_emit_delay_changed(t) spa_bt_transport_emit(t, delay_changed, 0) #define spa_bt_transport_emit_state_changed(t,...) spa_bt_transport_emit(t, state_changed, 0, __VA_ARGS__) #define spa_bt_transport_emit_volume_changed(t) spa_bt_transport_emit(t, volume_changed, 0) #define spa_bt_transport_add_listener(t,listener,events,data) \ spa_hook_list_append(&(t)->listener_list, listener, events, data) #define spa_bt_transport_set_implementation(t,_impl,_data) \ (t)->impl = SPA_CALLBACKS_INIT(_impl, _data) #define spa_bt_transport_impl(t,m,v,...) \ ({ \ int res = 0; \ spa_callbacks_call_res(&(t)->impl, \ struct spa_bt_transport_implementation, \ res, m, v, ##__VA_ARGS__); \ res; \ }) #define spa_bt_transport_destroy(t) spa_bt_transport_impl(t, destroy, 0) #define spa_bt_transport_set_volume(t,...) spa_bt_transport_impl(t, set_volume, 0, __VA_ARGS__) #define spa_bt_transport_set_delay(t,...) spa_bt_transport_impl(t, set_delay, 0, __VA_ARGS__) static inline enum spa_bt_transport_state spa_bt_transport_state_from_string(const char *value) { if (strcasecmp("idle", value) == 0) return SPA_BT_TRANSPORT_STATE_IDLE; else if ((strcasecmp("pending", value) == 0) || (strcasecmp("broadcasting", value) == 0)) return SPA_BT_TRANSPORT_STATE_PENDING; else if (strcasecmp("active", value) == 0) return SPA_BT_TRANSPORT_STATE_ACTIVE; else return SPA_BT_TRANSPORT_STATE_IDLE; } #define DEFAULT_AG_VOLUME 1.0f #define DEFAULT_RX_VOLUME 1.0f #define DEFAULT_TX_VOLUME 0.064f /* spa_bt_volume_hw_to_linear(40, 100) */ /* AVRCP/HSP volume is considered as percentage, so map it to pulseaudio (cubic) volume. */ static inline uint32_t spa_bt_volume_linear_to_hw(double v, uint32_t hw_volume_max) { if (v <= 0.0) return 0; if (v >= 1.0) return hw_volume_max; return SPA_CLAMP((uint64_t) lround(cbrt(v) * hw_volume_max), 0u, hw_volume_max); } static inline double spa_bt_volume_hw_to_linear(uint32_t v, uint32_t hw_volume_max) { double f; if (v <= 0) return 0.0; if (v >= hw_volume_max) return 1.0; f = ((double) v / hw_volume_max); return f * f * f; } enum spa_bt_feature { SPA_BT_FEATURE_MSBC = (1 << 0), SPA_BT_FEATURE_MSBC_ALT1 = (1 << 1), SPA_BT_FEATURE_MSBC_ALT1_RTL = (1 << 2), SPA_BT_FEATURE_HW_VOLUME = (1 << 3), SPA_BT_FEATURE_HW_VOLUME_MIC = (1 << 4), SPA_BT_FEATURE_SBC_XQ = (1 << 5), SPA_BT_FEATURE_FASTSTREAM = (1 << 6), SPA_BT_FEATURE_A2DP_DUPLEX = (1 << 7), }; struct spa_bt_quirks; struct spa_bt_quirks *spa_bt_quirks_create(const struct spa_dict *info, struct spa_log *log); int spa_bt_quirks_get_features(const struct spa_bt_quirks *quirks, const struct spa_bt_adapter *adapter, const struct spa_bt_device *device, uint32_t *features); void spa_bt_quirks_log_features(const struct spa_bt_quirks *this, const struct spa_bt_adapter *adapter, const struct spa_bt_device *device); void spa_bt_quirks_destroy(struct spa_bt_quirks *quirks); int spa_bt_adapter_has_msbc(struct spa_bt_adapter *adapter); struct spa_bt_backend_implementation { #define SPA_VERSION_BT_BACKEND_IMPLEMENTATION 0 uint32_t version; int (*free) (void *data); int (*register_profiles) (void *data); int (*unregister_profiles) (void *data); int (*ensure_codec) (void *data, struct spa_bt_device *device, unsigned int codec); int (*supports_codec) (void *data, struct spa_bt_device *device, unsigned int codec); }; struct spa_bt_backend { struct spa_callbacks impl; const char *name; bool available; bool exclusive; }; #define spa_bt_backend_set_implementation(b,_impl,_data) \ (b)->impl = SPA_CALLBACKS_INIT(_impl, _data) #define spa_bt_backend_impl(b,m,v,...) \ ({ \ int res = -ENOTSUP; \ if (b) \ spa_callbacks_call_res(&(b)->impl, \ struct spa_bt_backend_implementation, \ res, m, v, ##__VA_ARGS__); \ res; \ }) #define spa_bt_backend_free(b) spa_bt_backend_impl(b, free, 0) #define spa_bt_backend_register_profiles(b) spa_bt_backend_impl(b, register_profiles, 0) #define spa_bt_backend_unregister_profiles(b) spa_bt_backend_impl(b, unregister_profiles, 0) #define spa_bt_backend_ensure_codec(b,...) spa_bt_backend_impl(b, ensure_codec, 0, __VA_ARGS__) #define spa_bt_backend_supports_codec(b,...) spa_bt_backend_impl(b, supports_codec, 0, __VA_ARGS__) static inline struct spa_bt_backend *dummy_backend_new(struct spa_bt_monitor *monitor, void *dbus_connection, const struct spa_dict *info, const struct spa_bt_quirks *quirks, const struct spa_support *support, uint32_t n_support) { return NULL; } #ifdef HAVE_BLUEZ_5_BACKEND_NATIVE struct spa_bt_backend *backend_native_new(struct spa_bt_monitor *monitor, void *dbus_connection, const struct spa_dict *info, const struct spa_bt_quirks *quirks, const struct spa_support *support, uint32_t n_support); #else #define backend_native_new dummy_backend_new #endif #define OFONO_SERVICE "org.ofono" #ifdef HAVE_BLUEZ_5_BACKEND_OFONO struct spa_bt_backend *backend_ofono_new(struct spa_bt_monitor *monitor, void *dbus_connection, const struct spa_dict *info, const struct spa_bt_quirks *quirks, const struct spa_support *support, uint32_t n_support); #else #define backend_ofono_new dummy_backend_new #endif #define HSPHFPD_SERVICE "org.hsphfpd" #ifdef HAVE_BLUEZ_5_BACKEND_HSPHFPD struct spa_bt_backend *backend_hsphfpd_new(struct spa_bt_monitor *monitor, void *dbus_connection, const struct spa_dict *info, const struct spa_bt_quirks *quirks, const struct spa_support *support, uint32_t n_support); #else #define backend_hsphfpd_new dummy_backend_new #endif #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_BLUEZ5_DEFS_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/bluez5/g722/000077500000000000000000000000001511204443500246315ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/bluez5/g722/g722_enc_dec.h000066400000000000000000000101701511204443500271220ustar00rootroot00000000000000/* * SpanDSP - a series of DSP components for telephony * * g722.h - The ITU G.722 codec. * * Written by Steve Underwood * * Copyright (C) 2005 Steve Underwood * * Despite my general liking of the GPL, I place my own contributions * to this code in the public domain for the benefit of all mankind - * even the slimy ones who might try to proprietize my work and use it * to my detriment. * * Based on a single channel G.722 codec which is: * ***** Copyright (c) CMU 1993 ***** * Computer Science, Speech Group * Chengxiang Lu and Alex Hauptmann * * $Id: g722.h 48959 2006-12-25 06:42:15Z rizzo $ */ /*! \file */ #if !defined(_G722_H_) #define _G722_H_ #include /*! \page g722_page G.722 encoding and decoding \section g722_page_sec_1 What does it do? The G.722 module is a bit exact implementation of the ITU G.722 specification for all three specified bit rates - 64000bps, 56000bps and 48000bps. It passes the ITU tests. To allow fast and flexible interworking with narrow band telephony, the encoder and decoder support an option for the linear audio to be an 8k samples/second stream. In this mode the codec is considerably faster, and still fully compatible with wideband terminals using G.722. \section g722_page_sec_2 How does it work? ???. */ /* Format DAC12 is added to decode directly into samples suitable for a 12-bit DAC using offset binary representation. */ enum { G722_SAMPLE_RATE_8000 = 0x0001, G722_PACKED = 0x0002, G722_FORMAT_DAC12 = 0x0004, }; #ifdef BUILD_FEATURE_DAC #define NLDECOMPRESS_APPLY_GAIN(s, g) (((s) * (int32_t)(g)) >> 16) // Equivalent to shift 16, add 0x8000, shift 4 #define NLDECOMPRESS_APPLY_GAIN_CONVERTED_DAC(s, g) \ (uint16_t)((uint16_t)(((s) * (int32_t)(g)) >> 20) + 0x800) #else #define NLDECOMPRESS_APPLY_GAIN(s, g) (((int32_t)(s) * (int32_t)(g)) >> 16) #endif #ifdef BUILD_FEATURE_DAC #define NLDECOMPRESS_PREPROCESS_PCM_SAMPLE_WITH_GAIN(s, g) \ NLDECOMPRESS_APPLY_GAIN_CONVERTED_DAC((s), (g)) #define NLDECOMPRESS_PREPROCESS_SAMPLE_WITH_GAIN(s, g) ((int16_t)NLDECOMPRESS_APPLY_GAIN((s), (g))) #else #define NLDECOMPRESS_PREPROCESS_PCM_SAMPLE_WITH_GAIN NLDECOMPRESS_PREPROCESS_SAMPLE_WITH_GAIN #define NLDECOMPRESS_PREPROCESS_SAMPLE_WITH_GAIN(s, g) \ ((int16_t)(NLDECOMPRESS_APPLY_GAIN((s), (g)))) #endif typedef struct { int s; int sp; int sz; int r[3]; int a[3]; int ap[3]; int p[3]; int d[7]; int b[7]; int bp[7]; int nb; int det; } g722_band_t; typedef struct { /*! TRUE if the operating in the special ITU test mode, with the band split filters disabled. */ int itu_test_mode; /*! TRUE if the G.722 data is packed */ int packed; /*! TRUE if encode from 8k samples/second */ int eight_k; /*! 6 for 48000kbps, 7 for 56000kbps, or 8 for 64000kbps. */ int bits_per_sample; /*! Signal history for the QMF */ int x[24]; g722_band_t band[2]; unsigned int in_buffer; int in_bits; unsigned int out_buffer; int out_bits; } g722_encode_state_t; typedef struct { /*! TRUE if the operating in the special ITU test mode, with the band split filters disabled. */ int itu_test_mode; /*! TRUE if the G.722 data is packed */ int packed; /*! TRUE if decode to 8k samples/second */ int eight_k; /*! 6 for 48000kbps, 7 for 56000kbps, or 8 for 64000kbps. */ int bits_per_sample; /*! TRUE if offset binary for a 12-bit DAC */ int dac_pcm; /*! Signal history for the QMF */ int x[24]; g722_band_t band[2]; unsigned int in_buffer; int in_bits; unsigned int out_buffer; int out_bits; } g722_decode_state_t; #ifdef __cplusplus extern "C" { #endif g722_encode_state_t *g722_encode_init(g722_encode_state_t *s, unsigned int rate, int options); int g722_encode_release(g722_encode_state_t *s); int g722_encode(g722_encode_state_t *s, uint8_t g722_data[], const int16_t amp[], int len); g722_decode_state_t *g722_decode_init(g722_decode_state_t *s, unsigned int rate, int options); int g722_decode_release(g722_decode_state_t *s); uint32_t g722_decode(g722_decode_state_t *s, int16_t amp[], const uint8_t g722_data[], int len, uint16_t aGain); #ifdef __cplusplus } #endif #endif pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/bluez5/g722/g722_encode.c000066400000000000000000000242551511204443500270030ustar00rootroot00000000000000/* * SpanDSP - a series of DSP components for telephony * * g722_encode.c - The ITU G.722 codec, encode part. * * Written by Steve Underwood * * Copyright (C) 2005 Steve Underwood * * All rights reserved. * * Despite my general liking of the GPL, I place my own contributions * to this code in the public domain for the benefit of all mankind - * even the slimy ones who might try to proprietize my work and use it * to my detriment. * * Based on a single channel 64kbps only G.722 codec which is: * ***** Copyright (c) CMU 1993 ***** * Computer Science, Speech Group * Chengxiang Lu and Alex Hauptmann * * $Id: g722_encode.c,v 1.14 2006/07/07 16:37:49 steveu Exp $ */ /*! \file */ #include #include #include "g722_enc_dec.h" #if !defined(FALSE) #define FALSE 0 #endif #if !defined(TRUE) #define TRUE (!FALSE) #endif #define PACKED_OUTPUT (0) #define BITS_PER_SAMPLE (8) #ifndef BUILD_FEATURE_G722_USE_INTRINSIC_SAT static __inline int16_t saturate(int32_t amp) { int16_t amp16; /* Hopefully this is optimised for the common case - not clipping */ amp16 = (int16_t)amp; if (amp == amp16) { return amp16; } if (amp > 0x7FFF) { return 0x7FFF; } return 0x8000; } #else static __inline int16_t saturate(int32_t val) { register int32_t res; __asm volatile("SSAT %0, #16, %1\n\t" : "=r"(res) : "r"(val) :); return (int16_t)res; } #endif /*- End of function --------------------------------------------------------*/ static void block4(g722_band_t *band, int d) { int wd1; int wd2; int wd3; int i; int sg[7]; int ap1, ap2; int sg0, sgi; int sz; /* Block 4, RECONS */ band->d[0] = d; band->r[0] = saturate(band->s + d); /* Block 4, PARREC */ band->p[0] = saturate(band->sz + d); /* Block 4, UPPOL2 */ for (i = 0; i < 3; i++) { sg[i] = band->p[i] >> 15; } wd1 = saturate(band->a[1] << 2); wd2 = (sg[0] == sg[1]) ? -wd1 : wd1; if (wd2 > 32767) { wd2 = 32767; } ap2 = (wd2 >> 7) + ((sg[0] == sg[2]) ? 128 : -128); ap2 += (band->a[2] * 32512) >> 15; if (ap2 > 12288) { ap2 = 12288; } else if (ap2 < -12288) { ap2 = -12288; } band->ap[2] = ap2; /* Block 4, UPPOL1 */ sg[0] = band->p[0] >> 15; sg[1] = band->p[1] >> 15; wd1 = (sg[0] == sg[1]) ? 192 : -192; wd2 = (band->a[1] * 32640) >> 15; ap1 = saturate(wd1 + wd2); wd3 = saturate(15360 - band->ap[2]); if (ap1 > wd3) { ap1 = wd3; } else if (ap1 < -wd3) { ap1 = -wd3; } band->ap[1] = ap1; /* Block 4, UPZERO */ /* Block 4, FILTEZ */ wd1 = (d == 0) ? 0 : 128; sg0 = sg[0] = d >> 15; for (i = 1; i < 7; i++) { sgi = band->d[i] >> 15; wd2 = (sgi == sg0) ? wd1 : -wd1; wd3 = (band->b[i] * 32640) >> 15; band->bp[i] = saturate(wd2 + wd3); } /* Block 4, DELAYA */ sz = 0; for (i = 6; i > 0; i--) { int bi; band->d[i] = band->d[i - 1]; bi = band->b[i] = band->bp[i]; wd1 = saturate(band->d[i] + band->d[i]); sz += (bi * wd1) >> 15; } band->sz = sz; for (i = 2; i > 0; i--) { band->r[i] = band->r[i - 1]; band->p[i] = band->p[i - 1]; band->a[i] = band->ap[i]; } /* Block 4, FILTEP */ wd1 = saturate(band->r[1] + band->r[1]); wd1 = (band->a[1] * wd1) >> 15; wd2 = saturate(band->r[2] + band->r[2]); wd2 = (band->a[2] * wd2) >> 15; band->sp = saturate(wd1 + wd2); /* Block 4, PREDIC */ band->s = saturate(band->sp + band->sz); } /*- End of function --------------------------------------------------------*/ g722_encode_state_t *g722_encode_init(g722_encode_state_t *s, unsigned int rate, int options) { if (s == NULL) { #ifdef G722_SUPPORT_MALLOC if ((s = (g722_encode_state_t *)malloc(sizeof(*s))) == NULL) #endif return NULL; } memset(s, 0, sizeof(*s)); if (rate == 48000) { s->bits_per_sample = 6; } else if (rate == 56000) { s->bits_per_sample = 7; } else { s->bits_per_sample = 8; } s->band[0].det = 32; s->band[1].det = 8; return s; } /*- End of function --------------------------------------------------------*/ int g722_encode_release(g722_encode_state_t *s) { free(s); return 0; } /*- End of function --------------------------------------------------------*/ /* WebRtc, tlegrand: * Only define the following if bit-exactness with reference implementation * is needed. Will only have any effect if input signal is saturated. */ // #define RUN_LIKE_REFERENCE_G722 #ifdef RUN_LIKE_REFERENCE_G722 int16_t limitValues(int16_t rl) { int16_t yl; yl = (rl > 16383) ? 16383 : ((rl < -16384) ? -16384 : rl); return yl; } /*- End of function --------------------------------------------------------*/ #endif static int16_t q6[32] = {0, 35, 72, 110, 150, 190, 233, 276, 323, 370, 422, 473, 530, 587, 650, 714, 786, 858, 940, 1023, 1121, 1219, 1339, 1458, 1612, 1765, 1980, 2195, 2557, 2919, 0, 0}; static int16_t iln[32] = {0, 63, 62, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 0}; static int16_t ilp[32] = {0, 61, 60, 59, 58, 57, 56, 55, 54, 53, 52, 51, 50, 49, 48, 47, 46, 45, 44, 43, 42, 41, 40, 39, 38, 37, 36, 35, 34, 33, 32, 0}; static int16_t wl[8] = {-60, -30, 58, 172, 334, 538, 1198, 3042}; static int16_t rl42[16] = {0, 7, 6, 5, 4, 3, 2, 1, 7, 6, 5, 4, 3, 2, 1, 0}; static int16_t ilb[32] = {2048, 2093, 2139, 2186, 2233, 2282, 2332, 2383, 2435, 2489, 2543, 2599, 2656, 2714, 2774, 2834, 2896, 2960, 3025, 3091, 3158, 3228, 3298, 3371, 3444, 3520, 3597, 3676, 3756, 3838, 3922, 4008}; static int16_t qm4[16] = {0, -20456, -12896, -8968, -6288, -4240, -2584, -1200, 20456, 12896, 8968, 6288, 4240, 2584, 1200, 0}; static int16_t qm2[4] = {-7408, -1616, 7408, 1616}; static int16_t qmf_coeffs[12] = { 3, -11, 12, 32, -210, 951, 3876, -805, 362, -156, 53, -11, }; static int16_t ihn[3] = {0, 1, 0}; static int16_t ihp[3] = {0, 3, 2}; static int16_t wh[3] = {0, -214, 798}; static int16_t rh2[4] = {2, 1, 2, 1}; int g722_encode(g722_encode_state_t *s, uint8_t g722_data[], const int16_t amp[], int len) { int dlow; int dhigh; int el; int wd; int wd1; int ril; int wd2; int il4; int ih2; int wd3; int eh; int mih; int i; int j; /* Low and high band PCM from the QMF */ int xlow; int xhigh; int g722_bytes; /* Even and odd tap accumulators */ int sumeven; int sumodd; int ihigh; int ilow; int code; g722_bytes = 0; xhigh = 0; for (j = 0; j < len;) { if (s->itu_test_mode) { xlow = xhigh = amp[j++] >> 1; } else { { /* Apply the transmit QMF */ /* Shuffle the buffer down */ for (i = 0; i < 22; i++) { s->x[i] = s->x[i + 2]; } // TODO: if len is odd, then this can be a buffer overrun s->x[22] = amp[j++]; s->x[23] = amp[j++]; /* Discard every other QMF output */ sumeven = 0; sumodd = 0; for (i = 0; i < 12; i++) { sumodd += s->x[2 * i] * qmf_coeffs[i]; sumeven += s->x[2 * i + 1] * qmf_coeffs[11 - i]; } /* We shift by 12 to allow for the QMF filters (DC gain = 4096), plus 1 to allow for us summing two filters, plus 1 to allow for the 15 bit input to the G.722 algorithm. */ xlow = (sumeven + sumodd) >> 14; xhigh = (sumeven - sumodd) >> 14; #ifdef RUN_LIKE_REFERENCE_G722 /* The following lines are only used to verify bit-exactness * with reference implementation of G.722. Higher precision * is achieved without limiting the values. */ xlow = limitValues(xlow); xhigh = limitValues(xhigh); #endif } } /* Block 1L, SUBTRA */ el = saturate(xlow - s->band[0].s); /* Block 1L, QUANTL */ wd = (el >= 0) ? el : -(el + 1); for (i = 1; i < 30; i++) { wd1 = (q6[i] * s->band[0].det) >> 12; if (wd < wd1) { break; } } ilow = (el < 0) ? iln[i] : ilp[i]; /* Block 2L, INVQAL */ ril = ilow >> 2; wd2 = qm4[ril]; dlow = (s->band[0].det * wd2) >> 15; /* Block 3L, LOGSCL */ il4 = rl42[ril]; wd = (s->band[0].nb * 127) >> 7; s->band[0].nb = wd + wl[il4]; if (s->band[0].nb < 0) { s->band[0].nb = 0; } else if (s->band[0].nb > 18432) { s->band[0].nb = 18432; } /* Block 3L, SCALEL */ wd1 = (s->band[0].nb >> 6) & 31; wd2 = 8 - (s->band[0].nb >> 11); wd3 = (wd2 < 0) ? (ilb[wd1] << -wd2) : (ilb[wd1] >> wd2); s->band[0].det = wd3 << 2; block4(&s->band[0], dlow); { int nb; /* Block 1H, SUBTRA */ eh = saturate(xhigh - s->band[1].s); /* Block 1H, QUANTH */ wd = (eh >= 0) ? eh : -(eh + 1); wd1 = (564 * s->band[1].det) >> 12; mih = (wd >= wd1) ? 2 : 1; ihigh = (eh < 0) ? ihn[mih] : ihp[mih]; /* Block 2H, INVQAH */ wd2 = qm2[ihigh]; dhigh = (s->band[1].det * wd2) >> 15; /* Block 3H, LOGSCH */ ih2 = rh2[ihigh]; wd = (s->band[1].nb * 127) >> 7; nb = wd + wh[ih2]; if (nb < 0) { nb = 0; } else if (nb > 22528) { nb = 22528; } s->band[1].nb = nb; /* Block 3H, SCALEH */ wd1 = (s->band[1].nb >> 6) & 31; wd2 = 10 - (s->band[1].nb >> 11); wd3 = (wd2 < 0) ? (ilb[wd1] << -wd2) : (ilb[wd1] >> wd2); s->band[1].det = wd3 << 2; block4(&s->band[1], dhigh); #if BITS_PER_SAMPLE == 8 code = ((ihigh << 6) | ilow); #elif BITS_PER_SAMPLE == 7 code = ((ihigh << 6) | ilow) >> 1; #elif BITS_PER_SAMPLE == 6 code = ((ihigh << 6) | ilow) >> 2; #endif } #if PACKED_OUTPUT == 1 /* Pack the code bits */ s->out_buffer |= (code << s->out_bits); s->out_bits += s->bits_per_sample; if (s->out_bits >= 8) { g722_data[g722_bytes++] = (uint8_t)(s->out_buffer & 0xFF); s->out_bits -= 8; s->out_buffer >>= 8; } #else g722_data[g722_bytes++] = (uint8_t)code; #endif } return g722_bytes; } /*- End of function --------------------------------------------------------*/ /*- End of file ------------------------------------------------------------*/ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/bluez5/hci.c000066400000000000000000000027701511204443500250650ustar00rootroot00000000000000/* Spa HSP/HFP native backend HCI support */ /* SPDX-FileCopyrightText: Copyright © 2022 Pauli Virtanen */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #include #include #include #include #include #include "defs.h" #ifndef HAVE_BLUEZ_5_HCI int spa_bt_adapter_has_msbc(struct spa_bt_adapter *adapter) { if (adapter->msbc_probed) return adapter->has_msbc; return -EOPNOTSUPP; } #else #include #include #include #include int spa_bt_adapter_has_msbc(struct spa_bt_adapter *adapter) { int hci_id; spa_autoclose int sock = -1; uint8_t features[8], max_page = 0; struct sockaddr_hci a; const char *str; if (adapter->msbc_probed) return adapter->has_msbc; str = strrchr(adapter->path, '/'); /* hciXX */ if (str == NULL || sscanf(str, "/hci%d", &hci_id) != 1 || hci_id < 0) return -ENOENT; sock = socket(AF_BLUETOOTH, SOCK_RAW | SOCK_CLOEXEC, BTPROTO_HCI); if (sock < 0) return -errno; memset(&a, 0, sizeof(a)); a.hci_family = AF_BLUETOOTH; a.hci_dev = hci_id; if (bind(sock, (struct sockaddr *) &a, sizeof(a)) < 0) return -errno; if (hci_read_local_ext_features(sock, 0, &max_page, features, 1000) < 0) return -errno; adapter->msbc_probed = true; adapter->has_msbc = ((features[2] & LMP_TRSP_SCO) && (features[3] & LMP_ESCO)) ? 1 : 0; return adapter->has_msbc; } #endif pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/bluez5/hfp-codec-caps.h000066400000000000000000000005761511204443500271050ustar00rootroot00000000000000/* Spa HFP codec API */ /* SPDX-FileCopyrightText: Copyright © 2025 Pauli Virtanen */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_BLUEZ5_HFP_CODEC_CAPS_H_ #define SPA_BLUEZ5_HFP_CODEC_CAPS_H_ #include #define HFP_AUDIO_CODEC_CVSD 0x01 #define HFP_AUDIO_CODEC_MSBC 0x02 #define HFP_AUDIO_CODEC_LC3_SWB 0x03 #define HFP_H2_PACKET_SIZE 60 #endif pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/bluez5/hfp-codec-cvsd.c000066400000000000000000000114371511204443500271070ustar00rootroot00000000000000/* Spa HFP CVSD Codec */ /* SPDX-FileCopyrightText: Copyright © 2019 Collabora Ltd. */ /* SPDX-FileCopyrightText: Copyright © 2025 Pauli Virtanen */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "media-codecs.h" #include "hfp-h2.h" static struct spa_log *log; struct impl { size_t block_size; uint16_t seq; }; static int codec_enum_config(const struct media_codec *codec, uint32_t flags, const void *caps, size_t caps_size, uint32_t id, uint32_t idx, struct spa_pod_builder *b, struct spa_pod **param) { struct spa_pod_frame f[1]; const uint32_t position[1] = { SPA_AUDIO_CHANNEL_MONO }; const int channels = 1; spa_assert(caps == NULL && caps_size == 0); if (idx > 0) return 0; spa_pod_builder_push_object(b, &f[0], SPA_TYPE_OBJECT_Format, id); spa_pod_builder_add(b, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_audio), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), SPA_FORMAT_AUDIO_format, SPA_POD_Id(SPA_AUDIO_FORMAT_S16_LE), SPA_FORMAT_AUDIO_rate, SPA_POD_Int(8000), SPA_FORMAT_AUDIO_channels, SPA_POD_Int(channels), SPA_FORMAT_AUDIO_position, SPA_POD_Array(sizeof(uint32_t), SPA_TYPE_Id, channels, position), 0); *param = spa_pod_builder_pop(b, &f[0]); return *param == NULL ? -EIO : 1; } static int codec_validate_config(const struct media_codec *codec, uint32_t flags, const void *caps, size_t caps_size, struct spa_audio_info *info) { spa_assert(caps == NULL && caps_size == 0); spa_zero(*info); info->media_type = SPA_MEDIA_TYPE_audio; info->media_subtype = SPA_MEDIA_SUBTYPE_raw; info->info.raw.format = SPA_AUDIO_FORMAT_S16_LE; info->info.raw.rate = 8000; info->info.raw.channels = 1; info->info.raw.position[0] = SPA_AUDIO_CHANNEL_MONO; return 0; } static void *codec_init(const struct media_codec *codec, uint32_t flags, void *config, size_t config_len, const struct spa_audio_info *info, void *props, size_t mtu) { struct impl *this = NULL; spa_assert(config == NULL && config_len == 0 && props == NULL); if (mtu < 2) return NULL; this = calloc(1, sizeof(struct impl)); if (!this) return NULL; this->block_size = SPA_MIN(2 * (mtu/2), 144u); /* cap to 9 ms */ return this; } static void codec_deinit(void *data) { free(data); } static int codec_get_block_size(void *data) { struct impl *this = data; return this->block_size; } static int codec_start_encode (void *data, void *dst, size_t dst_size, uint16_t seqnum, uint32_t timestamp) { return 0; } static int codec_encode(void *data, const void *src, size_t src_size, void *dst, size_t dst_size, size_t *dst_out, int *need_flush) { struct impl *this = data; if (src_size < this->block_size) return -EINVAL; if (dst_size < this->block_size) return -EINVAL; spa_memmove(dst, src, this->block_size); *dst_out = this->block_size; *need_flush = NEED_FLUSH_ALL; return this->block_size; } static int codec_start_decode (void *data, const void *src, size_t src_size, uint16_t *seqnum, uint32_t *timestamp) { struct impl *this = data; if (src_size != 48 && is_zero_packet(src, src_size)) { /* Adapter is returning non-standard CVSD stream. For example * Intel 8087:0029 at Firmware revision 0.0 build 191 week 21 2021 * on kernel 5.13.19 produces such data. */ return -EINVAL; } if (src_size % 2 != 0) { /* Unaligned data: reception or adapter problem. * Consider the whole packet lost and report. */ return -EINVAL; } if (seqnum) *seqnum = this->seq; if (timestamp) *timestamp = 0; this->seq++; return 0; } static int codec_decode(void *data, const void *src, size_t src_size, void *dst, size_t dst_size, size_t *dst_out) { uint32_t avail; avail = SPA_MIN(src_size, dst_size); if (avail) spa_memcpy(dst, src, avail); *dst_out = avail; return avail; } static void codec_set_log(struct spa_log *global_log) { log = global_log; spa_log_topic_init(log, &codec_plugin_log_topic); } const struct media_codec hfp_codec_cvsd = { .id = SPA_BLUETOOTH_AUDIO_CODEC_CVSD, .kind = MEDIA_CODEC_HFP, .codec_id = 0x01, .enum_config = codec_enum_config, .validate_config = codec_validate_config, .init = codec_init, .deinit = codec_deinit, .get_block_size = codec_get_block_size, .start_encode = codec_start_encode, .encode = codec_encode, .set_log = codec_set_log, .start_decode = codec_start_decode, .decode = codec_decode, .name = "cvsd", .description = "CVSD", }; MEDIA_CODEC_EXPORT_DEF( "hfp-cvsd", &hfp_codec_cvsd ); pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/bluez5/hfp-codec-lc3-a127.c000066400000000000000000000135031511204443500272750ustar00rootroot00000000000000/* Spa HFP LC3-24kHz (Apple) Codec */ /* SPDX-FileCopyrightText: Copyright © 2025 Pauli Virtanen */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "media-codecs.h" #include "hfp-h2.h" #include #define LC3_A127_BLOCK_SIZE 720 static struct spa_log *log; struct impl { lc3_encoder_t enc; lc3_decoder_t dec; int prev_hwseq; uint16_t seq; }; static int codec_enum_config(const struct media_codec *codec, uint32_t flags, const void *caps, size_t caps_size, uint32_t id, uint32_t idx, struct spa_pod_builder *b, struct spa_pod **param) { struct spa_pod_frame f[1]; const uint32_t position[1] = { SPA_AUDIO_CHANNEL_MONO }; const int channels = 1; spa_assert(caps == NULL && caps_size == 0); if (idx > 0) return 0; spa_pod_builder_push_object(b, &f[0], SPA_TYPE_OBJECT_Format, id); spa_pod_builder_add(b, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_audio), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), SPA_FORMAT_AUDIO_format, SPA_POD_Id(SPA_AUDIO_FORMAT_F32), SPA_FORMAT_AUDIO_rate, SPA_POD_Int(24000), SPA_FORMAT_AUDIO_channels, SPA_POD_Int(channels), SPA_FORMAT_AUDIO_position, SPA_POD_Array(sizeof(uint32_t), SPA_TYPE_Id, channels, position), 0); *param = spa_pod_builder_pop(b, &f[0]); return *param == NULL ? -EIO : 1; } static int codec_validate_config(const struct media_codec *codec, uint32_t flags, const void *caps, size_t caps_size, struct spa_audio_info *info) { spa_assert(caps == NULL && caps_size == 0); spa_zero(*info); info->media_type = SPA_MEDIA_TYPE_audio; info->media_subtype = SPA_MEDIA_SUBTYPE_raw; info->info.raw.format = SPA_AUDIO_FORMAT_F32; info->info.raw.rate = 24000; info->info.raw.channels = 1; info->info.raw.position[0] = SPA_AUDIO_CHANNEL_MONO; return 0; } static void *codec_init(const struct media_codec *codec, uint32_t flags, void *config, size_t config_len, const struct spa_audio_info *info, void *props, size_t mtu) { struct impl *this = NULL; spa_assert(config == NULL && config_len == 0 && props == NULL); this = calloc(1, sizeof(*this)); if (!this) goto fail; this->enc = lc3_setup_encoder(7500, 24000, 0, calloc(1, lc3_encoder_size(7500, 24000))); if (!this->enc) goto fail; this->dec = lc3_setup_decoder(7500, 24000, 0, calloc(1, lc3_decoder_size(7500, 24000))); if (!this->dec) goto fail; spa_assert(lc3_frame_samples(7500, 24000) * sizeof(float) == LC3_A127_BLOCK_SIZE); this->prev_hwseq = -1; return this; fail: if (this) { free(this->enc); free(this->dec); free(this); } return NULL; } static void codec_deinit(void *data) { struct impl *this = data; free(this->enc); free(this->dec); free(this); } static int codec_get_block_size(void *data) { return LC3_A127_BLOCK_SIZE; } static int codec_start_encode (void *data, void *dst, size_t dst_size, uint16_t seqnum, uint32_t timestamp) { struct impl *this = data; this->seq = seqnum; return 0; } static int codec_encode(void *data, const void *src, size_t src_size, void *dst, size_t dst_size, size_t *dst_out, int *need_flush) { struct impl *this = data; uint8_t *buf = dst; int res; if (src_size < LC3_A127_BLOCK_SIZE) return -EINVAL; if (dst_size < H2_PACKET_SIZE) return -EINVAL; buf[0] = this->seq; buf[1] = H2_PACKET_SIZE - 2; this->seq++; res = lc3_encode(this->enc, LC3_PCM_FORMAT_FLOAT, src, 1, H2_PACKET_SIZE - 2, SPA_PTROFF(buf, 2, void)); if (res != 0) return -EINVAL; *dst_out = H2_PACKET_SIZE; *need_flush = NEED_FLUSH_ALL; return LC3_A127_BLOCK_SIZE; } static int codec_start_decode (void *data, const void *src, size_t src_size, uint16_t *seqnum, uint32_t *timestamp) { struct impl *this = data; const uint8_t *buf = src; if (is_zero_packet(src, src_size)) return -EINVAL; if (src_size < 2) return -EINVAL; if (buf[1] != H2_PACKET_SIZE - 2) return -EINVAL; if (this->prev_hwseq >= 0) this->seq += (uint8_t)(buf[0] - this->prev_hwseq); this->prev_hwseq = buf[0]; if (seqnum) *seqnum = this->seq; if (timestamp) *timestamp = 0; return 2; } static int codec_decode(void *data, const void *src, size_t src_size, void *dst, size_t dst_size, size_t *dst_out) { struct impl *this = data; int res; *dst_out = 0; if (src_size < H2_PACKET_SIZE - 2) return -EINVAL; if (dst_size < LC3_A127_BLOCK_SIZE) return -EINVAL; res = lc3_decode(this->dec, src, H2_PACKET_SIZE - 2, LC3_PCM_FORMAT_FLOAT, dst, 1); if (res) return -EINVAL; *dst_out = LC3_A127_BLOCK_SIZE; return H2_PACKET_SIZE - 2; } static int codec_produce_plc(void *data, void *dst, size_t dst_size) { struct impl *this = data; int res; if (dst_size < LC3_A127_BLOCK_SIZE) return -EINVAL; res = lc3_decode(this->dec, NULL, 0, LC3_PCM_FORMAT_FLOAT, dst, 1); if (res != 1) return -EINVAL; return LC3_A127_BLOCK_SIZE; } static void codec_set_log(struct spa_log *global_log) { log = global_log; spa_log_topic_init(log, &codec_plugin_log_topic); } static const struct media_codec hfp_codec_a127 = { .id = SPA_BLUETOOTH_AUDIO_CODEC_LC3_A127, .kind = MEDIA_CODEC_HFP, .codec_id = 127, .enum_config = codec_enum_config, .validate_config = codec_validate_config, .init = codec_init, .deinit = codec_deinit, .get_block_size = codec_get_block_size, .start_encode = codec_start_encode, .encode = codec_encode, .set_log = codec_set_log, .start_decode = codec_start_decode, .decode = codec_decode, .produce_plc = codec_produce_plc, .name = "lc3_a127", .description = "LC3-24kHz", }; MEDIA_CODEC_EXPORT_DEF( "hfp-lc3-a127", &hfp_codec_a127 ); pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/bluez5/hfp-codec-lc3-swb.c000066400000000000000000000137431511204443500274240ustar00rootroot00000000000000/* Spa HFP LC3-SWB Codec */ /* SPDX-FileCopyrightText: Copyright © 2025 Pauli Virtanen */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "media-codecs.h" #include "hfp-h2.h" #include #define LC3_SWB_BLOCK_SIZE 960 static struct spa_log *log; struct impl { lc3_encoder_t enc; lc3_decoder_t dec; struct h2_reader h2; uint16_t seq; void *data; size_t avail; }; static int codec_enum_config(const struct media_codec *codec, uint32_t flags, const void *caps, size_t caps_size, uint32_t id, uint32_t idx, struct spa_pod_builder *b, struct spa_pod **param) { struct spa_pod_frame f[1]; const uint32_t position[1] = { SPA_AUDIO_CHANNEL_MONO }; const int channels = 1; spa_assert(caps == NULL && caps_size == 0); if (idx > 0) return 0; spa_pod_builder_push_object(b, &f[0], SPA_TYPE_OBJECT_Format, id); spa_pod_builder_add(b, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_audio), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), SPA_FORMAT_AUDIO_format, SPA_POD_Id(SPA_AUDIO_FORMAT_F32), SPA_FORMAT_AUDIO_rate, SPA_POD_Int(32000), SPA_FORMAT_AUDIO_channels, SPA_POD_Int(channels), SPA_FORMAT_AUDIO_position, SPA_POD_Array(sizeof(uint32_t), SPA_TYPE_Id, channels, position), 0); *param = spa_pod_builder_pop(b, &f[0]); return *param == NULL ? -EIO : 1; } static int codec_validate_config(const struct media_codec *codec, uint32_t flags, const void *caps, size_t caps_size, struct spa_audio_info *info) { spa_assert(caps == NULL && caps_size == 0); spa_zero(*info); info->media_type = SPA_MEDIA_TYPE_audio; info->media_subtype = SPA_MEDIA_SUBTYPE_raw; info->info.raw.format = SPA_AUDIO_FORMAT_F32; info->info.raw.rate = 32000; info->info.raw.channels = 1; info->info.raw.position[0] = SPA_AUDIO_CHANNEL_MONO; return 0; } static void *codec_init(const struct media_codec *codec, uint32_t flags, void *config, size_t config_len, const struct spa_audio_info *info, void *props, size_t mtu) { struct impl *this = NULL; spa_assert(config == NULL && config_len == 0 && props == NULL); this = calloc(1, sizeof(*this)); if (!this) goto fail; this->enc = lc3_setup_encoder(7500, 32000, 0, calloc(1, lc3_encoder_size(7500, 32000))); if (!this->enc) goto fail; this->dec = lc3_setup_decoder(7500, 32000, 0, calloc(1, lc3_decoder_size(7500, 32000))); if (!this->dec) goto fail; spa_assert(lc3_frame_samples(7500, 32000) * sizeof(float) == LC3_SWB_BLOCK_SIZE); h2_reader_init(&this->h2, false); return spa_steal_ptr(this); fail: if (this) { free(this->enc); free(this->dec); free(this); } return NULL; } static void codec_deinit(void *data) { struct impl *this = data; free(this->enc); free(this->dec); free(this); } static int codec_get_block_size(void *data) { return LC3_SWB_BLOCK_SIZE; } static int codec_start_encode (void *data, void *dst, size_t dst_size, uint16_t seqnum, uint32_t timestamp) { struct impl *this = data; this->seq = seqnum; return 0; } static int codec_encode(void *data, const void *src, size_t src_size, void *dst, size_t dst_size, size_t *dst_out, int *need_flush) { struct impl *this = data; int res; if (src_size < LC3_SWB_BLOCK_SIZE) return -EINVAL; if (dst_size < H2_PACKET_SIZE) return -EINVAL; h2_write(dst, this->seq); res = lc3_encode(this->enc, LC3_PCM_FORMAT_FLOAT, src, 1, H2_PACKET_SIZE - 2, SPA_PTROFF(dst, 2, void)); if (res != 0) return -EINVAL; *dst_out = H2_PACKET_SIZE; *need_flush = NEED_FLUSH_ALL; return LC3_SWB_BLOCK_SIZE; } static int codec_start_decode (void *data, const void *src, size_t src_size, uint16_t *seqnum, uint32_t *timestamp) { struct impl *this = data; size_t consumed = 0; if (is_zero_packet(src, src_size)) return -EINVAL; if (!this->data) this->data = h2_reader_read(&this->h2, src, src_size, &consumed, &this->avail); if (seqnum) *seqnum = this->h2.seq; if (timestamp) *timestamp = 0; return consumed; } static int codec_decode(void *data, const void *src, size_t src_size, void *dst, size_t dst_size, size_t *dst_out) { struct impl *this = data; size_t consumed = 0; int res; *dst_out = 0; if (dst_size < LC3_SWB_BLOCK_SIZE) return -EINVAL; if (!this->data) this->data = h2_reader_read(&this->h2, src, src_size, &consumed, &this->avail); if (!this->data) return consumed; res = lc3_decode(this->dec, this->data, this->avail, LC3_PCM_FORMAT_FLOAT, dst, 1); this->data = NULL; if (res) { /* fail decoding silently, so remainder of packet is processed */ spa_log_debug(log, "decoding failed: %d", res); return consumed; } *dst_out = LC3_SWB_BLOCK_SIZE; return consumed; } static int codec_produce_plc(void *data, void *dst, size_t dst_size) { struct impl *this = data; int res; if (dst_size < LC3_SWB_BLOCK_SIZE) return -EINVAL; res = lc3_decode(this->dec, NULL, 0, LC3_PCM_FORMAT_FLOAT, dst, 1); if (res != 1) return -EINVAL; return LC3_SWB_BLOCK_SIZE; } static void codec_set_log(struct spa_log *global_log) { log = global_log; spa_log_topic_init(log, &codec_plugin_log_topic); } const struct media_codec hfp_codec_msbc = { .id = SPA_BLUETOOTH_AUDIO_CODEC_LC3_SWB, .kind = MEDIA_CODEC_HFP, .codec_id = 0x03, .enum_config = codec_enum_config, .validate_config = codec_validate_config, .init = codec_init, .deinit = codec_deinit, .get_block_size = codec_get_block_size, .start_encode = codec_start_encode, .encode = codec_encode, .set_log = codec_set_log, .start_decode = codec_start_decode, .decode = codec_decode, .produce_plc = codec_produce_plc, .name = "lc3_swb", .description = "LC3-SWB", .stream_pkt = true, }; MEDIA_CODEC_EXPORT_DEF( "hfp-lc3-swb", &hfp_codec_msbc ); pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/bluez5/hfp-codec-msbc.c000066400000000000000000000136501511204443500270730ustar00rootroot00000000000000/* Spa HFP MSBC Codec */ /* SPDX-FileCopyrightText: Copyright © 2019 Collabora Ltd. */ /* SPDX-FileCopyrightText: Copyright © 2025 Pauli Virtanen */ /* SPDX-License-Identifier: MIT */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "media-codecs.h" #include "hfp-h2.h" #include "plc.h" #define MSBC_BLOCK_SIZE 240 static struct spa_log *log_; struct impl { sbc_t msbc; struct h2_reader h2; uint16_t seq; void *data; size_t avail; plc_state_t *plc; }; static int codec_enum_config(const struct media_codec *codec, uint32_t flags, const void *caps, size_t caps_size, uint32_t id, uint32_t idx, struct spa_pod_builder *b, struct spa_pod **param) { struct spa_pod_frame f[1]; const uint32_t position[1] = { SPA_AUDIO_CHANNEL_MONO }; const int channels = 1; spa_assert(caps == NULL && caps_size == 0); if (idx > 0) return 0; spa_pod_builder_push_object(b, &f[0], SPA_TYPE_OBJECT_Format, id); spa_pod_builder_add(b, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_audio), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), SPA_FORMAT_AUDIO_format, SPA_POD_Id(SPA_AUDIO_FORMAT_S16), SPA_FORMAT_AUDIO_rate, SPA_POD_Int(16000), SPA_FORMAT_AUDIO_channels, SPA_POD_Int(channels), SPA_FORMAT_AUDIO_position, SPA_POD_Array(sizeof(uint32_t), SPA_TYPE_Id, channels, position), 0); *param = spa_pod_builder_pop(b, &f[0]); return *param == NULL ? -EIO : 1; } static int codec_validate_config(const struct media_codec *codec, uint32_t flags, const void *caps, size_t caps_size, struct spa_audio_info *info) { spa_assert(caps == NULL && caps_size == 0); spa_zero(*info); info->media_type = SPA_MEDIA_TYPE_audio; info->media_subtype = SPA_MEDIA_SUBTYPE_raw; info->info.raw.format = SPA_AUDIO_FORMAT_S16_LE; info->info.raw.rate = 16000; info->info.raw.channels = 1; info->info.raw.position[0] = SPA_AUDIO_CHANNEL_MONO; return 0; } static void *codec_init(const struct media_codec *codec, uint32_t flags, void *config, size_t config_len, const struct spa_audio_info *info, void *props, size_t mtu) { spa_autofree struct impl *this = NULL; int res; spa_assert(config == NULL && config_len == 0 && props == NULL); this = calloc(1, sizeof(*this)); if (!this) return NULL; res = sbc_init_msbc(&this->msbc, 0); if (res < 0) return NULL; /* Libsbc expects audio samples by default in host endianness, mSBC requires little endian */ this->msbc.endian = SBC_LE; h2_reader_init(&this->h2, true); this->plc = plc_init(NULL); if (!this->plc) return NULL; return spa_steal_ptr(this); } static void codec_deinit(void *data) { struct impl *this = data; sbc_finish(&this->msbc); plc_free(this->plc); free(this); } static int codec_get_block_size(void *data) { return MSBC_BLOCK_SIZE; } static int codec_start_encode (void *data, void *dst, size_t dst_size, uint16_t seqnum, uint32_t timestamp) { struct impl *this = data; this->seq = seqnum; return 0; } static int codec_encode(void *data, const void *src, size_t src_size, void *dst, size_t dst_size, size_t *dst_out, int *need_flush) { struct impl *this = data; ssize_t written = 0; int res; if (src_size < MSBC_BLOCK_SIZE) return -EINVAL; if (dst_size < H2_PACKET_SIZE) return -EINVAL; h2_write(dst, this->seq); res = sbc_encode(&this->msbc, src, src_size, SPA_PTROFF(dst, 2, void), H2_PACKET_SIZE - 3, &written); if (res < 0) return -EINVAL; *dst_out = H2_PACKET_SIZE; *need_flush = NEED_FLUSH_ALL; return res; } static int codec_start_decode (void *data, const void *src, size_t src_size, uint16_t *seqnum, uint32_t *timestamp) { struct impl *this = data; size_t consumed = 0; if (is_zero_packet(src, src_size)) return -EINVAL; if (!this->data) this->data = h2_reader_read(&this->h2, src, src_size, &consumed, &this->avail); if (seqnum) *seqnum = this->h2.seq; if (timestamp) *timestamp = 0; return consumed; } static int codec_decode(void *data, const void *src, size_t src_size, void *dst, size_t dst_size, size_t *dst_out) { struct impl *this = data; size_t consumed = 0; int res; *dst_out = 0; if (!this->data) this->data = h2_reader_read(&this->h2, src, src_size, &consumed, &this->avail); if (!this->data) return consumed; res = sbc_decode(&this->msbc, this->data, this->avail, dst, dst_size, dst_out); this->data = NULL; if (res < 0) { /* fail decoding silently, so remainder of packet is processed */ spa_log_debug(log_, "decoding failed: %d", res); return consumed; } plc_rx(this->plc, dst, *dst_out / sizeof(int16_t)); return consumed; } static int codec_produce_plc(void *data, void *dst, size_t dst_size) { struct impl *this = data; int res; if (dst_size < MSBC_BLOCK_SIZE) return -EINVAL; res = plc_fillin(this->plc, dst, MSBC_BLOCK_SIZE / sizeof(int16_t)); if (res < 0) return res; return MSBC_BLOCK_SIZE; } static void codec_set_log(struct spa_log *global_log) { log_ = global_log; spa_log_topic_init(log_, &codec_plugin_log_topic); } const struct media_codec hfp_codec_msbc = { .id = SPA_BLUETOOTH_AUDIO_CODEC_MSBC, .kind = MEDIA_CODEC_HFP, .codec_id = 0x02, .enum_config = codec_enum_config, .validate_config = codec_validate_config, .init = codec_init, .deinit = codec_deinit, .get_block_size = codec_get_block_size, .start_encode = codec_start_encode, .encode = codec_encode, .set_log = codec_set_log, .start_decode = codec_start_decode, .decode = codec_decode, .produce_plc = codec_produce_plc, .name = "msbc", .description = "MSBC", .stream_pkt = true, }; MEDIA_CODEC_EXPORT_DEF( "hfp-msbc", &hfp_codec_msbc ); pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/bluez5/hfp-h2.h000066400000000000000000000050301511204443500254030ustar00rootroot00000000000000/* Spa HFP Codecs */ /* SPDX-FileCopyrightText: Copyright © 2025 Pauli Virtanen */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_BLUEZ5_HFP_H2_H #define SPA_BLUEZ5_HFP_H2_H #define H2_PACKET_SIZE 60 struct h2_reader { uint8_t buf[H2_PACKET_SIZE]; uint8_t pos; bool msbc; uint16_t seq; bool started; }; struct h2_writer { uint8_t seq; }; static inline void h2_reader_init(struct h2_reader *this, bool msbc) { this->pos = 0; this->msbc = msbc; this->seq = 0; this->started = false; } static inline void h2_reader_append_byte(struct h2_reader *this, uint8_t byte) { /* Parse H2 sync header */ if (this->pos == 0) { if (byte != 0x01) { this->pos = 0; return; } } else if (this->pos == 1) { if (!((byte & 0x0F) == 0x08 && ((byte >> 4) & 1) == ((byte >> 5) & 1) && ((byte >> 6) & 1) == ((byte >> 7) & 1))) { this->pos = 0; return; } } else if (this->msbc) { /* Beginning of MSBC frame: SYNCWORD + 2 nul bytes */ if (this->pos == 2) { if (byte != 0xAD) { this->pos = 0; return; } } else if (this->pos == 3) { if (byte != 0x00) { this->pos = 0; return; } } else if (this->pos == 4) { if (byte != 0x00) { this->pos = 0; return; } } } if (this->pos >= H2_PACKET_SIZE) { /* Packet completed. Reset. */ this->pos = 0; h2_reader_append_byte(this, byte); return; } this->buf[this->pos] = byte; ++this->pos; } static inline void *h2_reader_read(struct h2_reader *this, const uint8_t *src, size_t src_size, size_t *consumed, size_t *avail) { int seq; size_t i; for (i = 0; i < src_size && this->pos < H2_PACKET_SIZE; ++i) h2_reader_append_byte(this, src[i]); *consumed = i; *avail = 0; if (this->pos < H2_PACKET_SIZE) return NULL; this->pos = 0; seq = ((this->buf[1] >> 4) & 1) | ((this->buf[1] >> 6) & 2); if (!this->started) { this->seq = seq; this->started = true; } this->seq++; while (seq != this->seq % 4) this->seq++; *avail = H2_PACKET_SIZE - 2; return &this->buf[2]; } static inline void h2_write(uint8_t *buf, uint8_t seq) { static const uint8_t sntable[4] = { 0x08, 0x38, 0xc8, 0xf8 }; buf[0] = 0x01; buf[1] = sntable[seq % 4]; buf[59] = 0; } static inline bool is_zero_packet(const uint8_t *data, size_t size) { size_t i; for (i = 0; i < size; ++i) if (data[i]) return false; return true; } #endif pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/bluez5/iso-io.c000066400000000000000000000537511511204443500255260ustar00rootroot00000000000000/* Spa ISO I/O */ /* SPDX-FileCopyrightText: Copyright © 2023 Pauli Virtanen. */ /* SPDX-License-Identifier: MIT */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include "iso-io.h" #include "media-codecs.h" #include "defs.h" #include "decode-buffer.h" SPA_LOG_TOPIC_DEFINE_STATIC(log_topic, "spa.bluez5.iso"); #undef SPA_LOG_TOPIC_DEFAULT #define SPA_LOG_TOPIC_DEFAULT &log_topic #include "bt-latency.h" #define IDLE_TIME (500 * SPA_NSEC_PER_MSEC) #define EMPTY_BUF_SIZE 65536 #define LATENCY_PERIOD (1000 * SPA_NSEC_PER_MSEC) #define MAX_LATENCY (50 * SPA_NSEC_PER_MSEC) #define CLOCK_SYNC_AVG_PERIOD (500 * SPA_NSEC_PER_MSEC) #define CLOCK_SYNC_RATE_DIFF_MAX 0.005 #define ISO_BUFFERING_AVG_PERIOD (50 * SPA_NSEC_PER_MSEC) #define ISO_BUFFERING_RATE_DIFF_MAX 0.05 struct clock_sync { /** Reference monotonic time for streams in the group */ int64_t base_time; /** Average error for current cycle */ int64_t avg_err; unsigned int avg_num; /** Log rate limiting */ uint64_t log_pos; /** Rate matching ISO clock to monotonic clock */ struct spa_bt_rate_control dll; }; struct group { struct spa_log *log; struct spa_loop *data_loop; struct spa_system *data_system; struct spa_source source; struct spa_list streams; int timerfd; uint8_t id; int64_t next; int64_t duration_tx; int64_t duration_rx; bool flush; bool started; struct clock_sync rx_sync; }; struct stream { struct spa_bt_iso_io this; struct spa_list link; struct group *group; int fd; bool sink; bool idle; spa_bt_iso_io_pull_t pull; const struct media_codec *codec; uint32_t block_size; struct spa_bt_latency tx_latency; struct spa_bt_decode_buffer *source_buf; /** Stream packet sequence number, relative to group::rx_sync */ int64_t rx_pos; /** Current graph clock position */ uint64_t position; }; struct modify_info { struct stream *stream; struct spa_list *streams; }; static int do_modify(struct spa_loop *loop, bool async, uint32_t seq, const void *data, size_t size, void *user_data) { struct modify_info *info = user_data; if (info->streams) spa_list_append(info->streams, &info->stream->link); else spa_list_remove(&info->stream->link); return 0; } static void stream_link(struct group *group, struct stream *stream) { struct modify_info info = { .stream = stream, .streams = &group->streams }; int res; res = spa_loop_locked(group->data_loop, do_modify, 0, NULL, 0, &info); spa_assert_se(res == 0); } static void stream_unlink(struct stream *stream) { struct modify_info info = { .stream = stream, .streams = NULL }; int res; res = spa_loop_locked(stream->group->data_loop, do_modify, 0, NULL, 0, &info); spa_assert_se(res == 0); } static int stream_silence(struct stream *stream) { static uint8_t empty[EMPTY_BUF_SIZE] = {0}; const size_t max_size = sizeof(stream->this.buf); int res, used, need_flush; size_t encoded; stream->idle = true; res = used = stream->codec->start_encode(stream->this.codec_data, stream->this.buf, max_size, 0, 0); if (res < 0) return res; res = stream->codec->encode(stream->this.codec_data, empty, stream->block_size, SPA_PTROFF(stream->this.buf, used, void), max_size - used, &encoded, &need_flush); if (res < 0) return res; used += encoded; if (!need_flush) return -EINVAL; stream->this.size = used; return 0; } static int set_timeout(struct group *group, uint64_t time) { struct itimerspec ts; ts.it_value.tv_sec = time / SPA_NSEC_PER_SEC; ts.it_value.tv_nsec = time % SPA_NSEC_PER_SEC; ts.it_interval.tv_sec = 0; ts.it_interval.tv_nsec = 0; return spa_system_timerfd_settime(group->data_system, group->timerfd, SPA_FD_TIMER_ABSTIME, &ts, NULL); } static uint64_t get_time_ns(struct spa_system *system, clockid_t clockid) { struct timespec now; spa_system_clock_gettime(system, clockid, &now); return SPA_TIMESPEC_TO_NSEC(&now); } static int set_timers(struct group *group) { if (group->duration_tx == 0) return -EINVAL; group->next = SPA_ROUND_UP(get_time_ns(group->data_system, CLOCK_MONOTONIC) + group->duration_tx, group->duration_tx); return set_timeout(group, group->next); } static void drop_rx(int fd) { ssize_t res; do { res = recv(fd, NULL, 0, MSG_TRUNC | MSG_DONTWAIT); } while (res >= 0); } static bool group_latency_check(struct group *group) { struct stream *stream; int32_t min_latency = INT32_MAX, max_latency = INT32_MIN; unsigned int kernel_queue = UINT_MAX; spa_list_for_each(stream, &group->streams, link) { if (!stream->sink) continue; if (!stream->tx_latency.enabled) return false; if (kernel_queue == UINT_MAX) kernel_queue = stream->tx_latency.kernel_queue; if (group->flush && stream->tx_latency.queue) { spa_log_debug(group->log, "%p: ISO group:%d latency skip: flushing", group, group->id); return true; } if (stream->tx_latency.kernel_queue != kernel_queue) { /* Streams out of sync, try to correct if it persists */ spa_log_debug(group->log, "%p: ISO group:%d latency skip: imbalance", group, group->id); group->flush = true; return true; } } group->flush = false; spa_list_for_each(stream, &group->streams, link) { if (!stream->sink) continue; if (!stream->tx_latency.valid) return false; min_latency = SPA_MIN(min_latency, stream->tx_latency.ptp.min); max_latency = SPA_MAX(max_latency, stream->tx_latency.ptp.max); } if (max_latency > MAX_LATENCY) { spa_log_debug(group->log, "%p: ISO group:%d latency skip: latency %d ms", group, group->id, (int)(max_latency / SPA_NSEC_PER_MSEC)); group->flush = true; return true; } return false; } static void group_on_timeout(struct spa_source *source) { struct group *group = source->data; struct stream *stream; bool resync = false; bool fail = false; uint64_t exp; int res; if ((res = spa_system_timerfd_read(group->data_system, group->timerfd, &exp)) < 0) { if (res != -EAGAIN) spa_log_warn(group->log, "%p: ISO group:%u error reading timerfd: %s", group, group->id, spa_strerror(res)); return; } if (!exp) return; spa_list_for_each(stream, &group->streams, link) { if (!stream->sink) { if (!stream->pull) { /* Source not running: drop any incoming data */ drop_rx(stream->fd); } continue; } spa_bt_latency_recv_errqueue(&stream->tx_latency, stream->fd, group->log); if (stream->this.need_resync) { resync = true; stream->this.need_resync = false; } if (!group->started && !stream->idle && stream->this.size > 0) group->started = true; } if (group_latency_check(group)) { spa_list_for_each(stream, &group->streams, link) spa_bt_latency_reset(&stream->tx_latency); goto done; } /* Produce output */ spa_list_for_each(stream, &group->streams, link) { int res = 0; uint64_t now; if (!stream->sink) continue; if (!group->started) { stream->this.resync = true; stream->this.size = 0; continue; } if (stream->this.size == 0) { spa_log_debug(group->log, "%p: ISO group:%u miss fd:%d", group, group->id, stream->fd); stream->this.resync = true; if (stream_silence(stream) < 0) { fail = true; continue; } } now = get_time_ns(group->data_system, CLOCK_REALTIME); res = spa_bt_send(stream->fd, stream->this.buf, stream->this.size, &stream->tx_latency, now); if (res < 0) { res = -errno; fail = true; group->flush = true; } spa_log_trace(group->log, "%p: ISO group:%u sent fd:%d size:%u ts:%u idle:%d res:%d latency:%d..%d%sus queue:%u", group, group->id, stream->fd, (unsigned)stream->this.size, (unsigned)stream->this.timestamp, stream->idle, res, stream->tx_latency.ptp.min/1000, stream->tx_latency.ptp.max/1000, stream->tx_latency.valid ? " " : "* ", stream->tx_latency.queue); stream->this.size = 0; } if (fail) spa_log_debug(group->log, "%p: ISO group:%d send failure", group, group->id); done: /* Pull data for the next interval */ group->next += exp * group->duration_tx; spa_list_for_each(stream, &group->streams, link) { if (!stream->sink) continue; if (resync) stream->this.resync = true; if (stream->pull) { stream->idle = false; stream->this.now = group->next; stream->pull(&stream->this); } else { stream_silence(stream); } } set_timeout(group, group->next); } static struct group *group_create(struct spa_bt_transport *t, struct spa_log *log, struct spa_loop *data_loop, struct spa_system *data_system) { struct group *group; uint8_t id; if (t->profile & (SPA_BT_PROFILE_BAP_SINK | SPA_BT_PROFILE_BAP_SOURCE)) { id = t->bap_cig; } else if (t->profile & (SPA_BT_PROFILE_BAP_BROADCAST_SINK | SPA_BT_PROFILE_BAP_BROADCAST_SOURCE)) { id = t->bap_big; } else { errno = EINVAL; return NULL; } group = calloc(1, sizeof(struct group)); if (group == NULL) return NULL; spa_log_topic_init(log, &log_topic); group->id = id; group->log = log; group->data_loop = data_loop; group->data_system = data_system; spa_list_init(&group->streams); group->timerfd = spa_system_timerfd_create(group->data_system, CLOCK_MONOTONIC, SPA_FD_CLOEXEC | SPA_FD_NONBLOCK); if (group->timerfd < 0) { int err = errno; free(group); errno = err; return NULL; } group->source.data = group; group->source.fd = group->timerfd; group->source.func = group_on_timeout; group->source.mask = SPA_IO_IN; group->source.rmask = 0; spa_loop_add_source(group->data_loop, &group->source); return group; } static int do_remove_source(struct spa_loop *loop, bool async, uint32_t seq, const void *data, size_t size, void *user_data) { struct group *group = user_data; if (group->source.loop) spa_loop_remove_source(group->data_loop, &group->source); set_timeout(group, 0); return 0; } static void group_destroy(struct group *group) { int res; spa_assert(spa_list_is_empty(&group->streams)); res = spa_loop_locked(group->data_loop, do_remove_source, 0, NULL, 0, group); spa_assert_se(res == 0); close(group->timerfd); free(group); } static struct stream *stream_create(struct spa_bt_transport *t, struct group *group) { struct stream *stream; void *codec_data = NULL; int block_size = 0; struct spa_audio_info format = { 0 }; int res; bool sink; int64_t interval, *duration; if (t->profile == SPA_BT_PROFILE_BAP_SINK || t->profile == SPA_BT_PROFILE_BAP_BROADCAST_SINK) { sink = true; duration = &group->duration_tx; } else { sink = false; duration = &group->duration_rx; } if (t->media_codec->kind != MEDIA_CODEC_BAP || !t->media_codec->get_interval) { res = -EINVAL; goto fail; } res = t->media_codec->validate_config(t->media_codec, 0, t->configuration, t->configuration_len, &format); if (res < 0) goto fail; codec_data = t->media_codec->init(t->media_codec, 0, t->configuration, t->configuration_len, &format, NULL, t->write_mtu); if (!codec_data) { res = -EINVAL; goto fail; } block_size = t->media_codec->get_block_size(codec_data); if (block_size < 0 || block_size > EMPTY_BUF_SIZE) { res = -EINVAL; goto fail; } interval = t->media_codec->get_interval(codec_data); if (interval <= 5000) { res = -EINVAL; goto fail; } if (*duration == 0) { *duration = interval; } else if (interval != *duration) { /* SDU_Interval in ISO group must be same for each direction */ res = -EINVAL; goto fail; } if (!sink) { t->media_codec->deinit(codec_data); codec_data = NULL; } stream = calloc(1, sizeof(struct stream)); if (stream == NULL) goto fail_errno; stream->fd = t->fd; stream->sink = sink; stream->group = group; stream->this.duration = *duration; stream->codec = t->media_codec; stream->this.codec_data = codec_data; stream->this.format = format; stream->block_size = block_size; spa_bt_latency_init(&stream->tx_latency, t, LATENCY_PERIOD, group->log); if (sink) stream_silence(stream); stream_link(group, stream); return stream; fail_errno: res = -errno; fail: if (codec_data) t->media_codec->deinit(codec_data); errno = -res; return NULL; } struct spa_bt_iso_io *spa_bt_iso_io_create(struct spa_bt_transport *t, struct spa_log *log, struct spa_loop *data_loop, struct spa_system *data_system) { struct stream *stream; struct group *group; group = group_create(t, log, data_loop, data_system); if (group == NULL) return NULL; stream = stream_create(t, group); if (stream == NULL) { int err = errno; group_destroy(group); errno = err; return NULL; } return &stream->this; } struct spa_bt_iso_io *spa_bt_iso_io_attach(struct spa_bt_iso_io *this, struct spa_bt_transport *t) { struct stream *stream = SPA_CONTAINER_OF(this, struct stream, this); stream = stream_create(t, stream->group); if (stream == NULL) return NULL; return &stream->this; } void spa_bt_iso_io_destroy(struct spa_bt_iso_io *this) { struct stream *stream = SPA_CONTAINER_OF(this, struct stream, this); stream_unlink(stream); spa_bt_latency_flush(&stream->tx_latency, stream->fd, stream->group->log); if (spa_list_is_empty(&stream->group->streams)) group_destroy(stream->group); if (stream->this.codec_data) stream->codec->deinit(stream->this.codec_data); stream->this.codec_data = NULL; free(stream); } static bool group_is_enabled(struct group *group) { struct stream *stream; spa_list_for_each(stream, &group->streams, link) { if (!stream->sink) continue; if (stream->pull) return true; } return false; } /** Must be called from data thread */ void spa_bt_iso_io_set_cb(struct spa_bt_iso_io *this, spa_bt_iso_io_pull_t pull, void *user_data) { struct stream *stream = SPA_CONTAINER_OF(this, struct stream, this); bool was_enabled, enabled; if (!stream->sink) return; was_enabled = group_is_enabled(stream->group); stream->pull = pull; stream->this.user_data = user_data; enabled = group_is_enabled(stream->group); if (!enabled && was_enabled) { stream->group->started = false; set_timeout(stream->group, 0); } else if (enabled && !was_enabled) { set_timers(stream->group); } stream->idle = true; stream->this.resync = true; stream->this.size = 0; stream->this.now = stream->group->next; } /** Must be called from data thread */ int spa_bt_iso_io_recv_errqueue(struct spa_bt_iso_io *this) { struct stream *stream = SPA_CONTAINER_OF(this, struct stream, this); struct group *group = stream->group; if (!stream->sink) { struct stream *s; spa_list_for_each(s, &group->streams, link) { if (s->sink && s->fd == stream->fd) { stream = s; break; } } } return spa_bt_latency_recv_errqueue(&stream->tx_latency, stream->fd, group->log); } /** * Set decode buffer used by a stream when it has packet RX. Set to NULL when stream is * inactive. * * Must be called from data thread. */ void spa_bt_iso_io_set_source_buffer(struct spa_bt_iso_io *this, struct spa_bt_decode_buffer *buffer) { struct stream *stream = SPA_CONTAINER_OF(this, struct stream, this); struct group *group = stream->group; struct clock_sync *sync = &group->rx_sync; spa_zero(sync->dll); stream->source_buf = buffer; if (buffer) { /* Take over buffer overrun handling */ buffer->no_overrun_drop = true; buffer->buffering = false; buffer->avg_period = ISO_BUFFERING_AVG_PERIOD; buffer->rate_diff_max = ISO_BUFFERING_RATE_DIFF_MAX; } } /** * Get automatic group-wide stream RX target latency. This is useful only for BAP Client. * BAP Server target latency is determined by the presentation delay. * * Must be called from data thread. */ int32_t spa_bt_iso_io_get_source_target_latency(struct spa_bt_iso_io *this) { struct stream *stream = SPA_CONTAINER_OF(this, struct stream, this); struct group *group = stream->group; struct stream *s; int32_t latency = 0; if (!stream->source_buf) return 0; spa_list_for_each(s, &group->streams, link) if (s->source_buf) latency = SPA_MAX(latency, spa_bt_decode_buffer_get_auto_latency(s->source_buf)); return latency; } /** * Called on stream packet RX with packet monotonic timestamp. * * Returns the logical SDU reference time, with respect to which decode-buffer should * target its fill level. This is needed so that all streams converge to same latency * (with sub-sample accuracy needed for eg. stereo stream alignment). * * Determines the ISO group clock rate matching from individual stream packet RX times. * Packet arrival time is decomposed to * * now = group::rx_sync::base_time + stream::rx_pos * group::duration_rx + err * * Clock rate matching is done by drifting base_time by the rate difference, so that `err` * is zero on average across different streams. If stream's rx_pos appears to be out of * sync, it is resynchronized to a new position. * * The logical SDU timestamps for different streams are aligned and occur at equal * intervals, but the RX timestamp `now` we actually get here is a software timestamp * indicating when packet was received by kernel. In practice, they are not equally spaced * but are approximately aligned between different streams. * * The Core v6.1 specification does **not** provide any way to synchronize Controller and * Host clocks, so we can attempt to sync to ISO clock only based on the RX timestamps. * * Because the actual packet RX times are not equally spaced, it's ambiguous what the * logical SDU reference time is. It's then impossible to achieve clock synchronization with * better accuracy than this jitter (on Intel AX210 it's several ms jitter in a regular * pattern, plus some random noise). * * Aligned playback for different devices cannot be implemented with the tools provided in * the specification. Some implementation-defined clock synchronization mechanism is * needed, but kernel (6.17) doesn't have anything and it's not clear such vendor-defined * mechanisms exist over USB. * * The HW timestamps on packets do not help with this, as they are in controller's clock * domain. They are only useful for aligning packets from different streams. They are also * optional in the specification and controllers don't necessarily implement them. They * are not used here. * * Must be called from data thread. */ int64_t spa_bt_iso_io_recv(struct spa_bt_iso_io *this, int64_t now) { struct stream *stream = SPA_CONTAINER_OF(this, struct stream, this); struct group *group = stream->group; struct clock_sync *sync = &group->rx_sync; struct stream *s; bool resync = false; int64_t err, t; spa_assert(stream->source_buf); if (sync->dll.corr == 0) { sync->base_time = now; spa_bt_rate_control_init(&sync->dll, 0); } stream->rx_pos++; t = sync->base_time + group->duration_rx * stream->rx_pos; err = now - t; if (SPA_ABS(err) > group->duration_rx) { resync = true; spa_log_debug(group->log, "%p: ISO rx-resync large group:%u fd:%d", group, group->id, stream->fd); } spa_list_for_each(s, &group->streams, link) { if (s == stream || !s->source_buf) continue; if (SPA_ABS(now - s->source_buf->rx.nsec) < group->duration_rx / 2 && stream->rx_pos != s->rx_pos) { spa_log_debug(group->log, "%p: ISO rx-resync balance group:%u fd:%d fd:%d", group, group->id, stream->fd, s->fd); resync = true; break; } } if (resync) { stream->rx_pos = (now - sync->base_time + group->duration_rx/2) / group->duration_rx; t = sync->base_time + group->duration_rx * stream->rx_pos; err = now - t; spa_log_debug(group->log, "%p: ISO rx-resync group:%u fd:%d err:%"PRIi64, group, group->id, stream->fd, err); } sync->avg_err = (sync->avg_err * sync->avg_num + err) / (sync->avg_num + 1); sync->avg_num++; return t; } /** * Call at end of stream process(), after consuming data. * * Apply ISO clock rate matching. * * Realign stream RX to target latency, if it is too far off, so that rate matching * converges faster to alignment. * * Must be called from data thread */ void spa_bt_iso_io_check_rx_sync(struct spa_bt_iso_io *this, uint64_t position) { struct stream *stream = SPA_CONTAINER_OF(this, struct stream, this); struct group *group = stream->group; struct stream *s; const int64_t max_err = group->duration_rx; struct clock_sync *sync = &group->rx_sync; int32_t target; bool overrun = false; double corr; if (!stream->source_buf) return; /* Check sync after all input streams have completed process() on same cycle */ stream->position = position; spa_list_for_each(s, &group->streams, link) { if (!s->source_buf) continue; if (s->position != stream->position) return; } target = stream->source_buf->target; /* Rate match ISO clock */ corr = spa_bt_rate_control_update(&sync->dll, sync->avg_err, 0, group->duration_rx, CLOCK_SYNC_AVG_PERIOD, CLOCK_SYNC_RATE_DIFF_MAX); sync->base_time += (int64_t)(group->duration_rx * (corr - 1)); enum spa_log_level log_level = (sync->log_pos > SPA_NSEC_PER_SEC) ? SPA_LOG_LEVEL_DEBUG : SPA_LOG_LEVEL_TRACE; if (SPA_UNLIKELY(spa_log_level_topic_enabled(group->log, SPA_LOG_TOPIC_DEFAULT, log_level))) { spa_log_lev(group->log, log_level, "%p: ISO rx-sync group:%u base:%"PRIi64" avg:%g err:%"PRIi64" corr:%g", group, group->id, sync->base_time, sync->dll.avg, sync->avg_err, corr-1); sync->log_pos = 0; } sync->log_pos += stream->source_buf->duration_ns; sync->avg_err = 0; sync->avg_num = 0; /* Handle overrun (e.g. resyncs streams after initial buffering) */ spa_list_for_each(s, &group->streams, link) { if (s->source_buf) { double level = s->source_buf->level; int max_level = target + max_err * s->source_buf->rate / SPA_NSEC_PER_SEC; if (level > max_level) overrun = true; } } if (!overrun) return; spa_list_for_each(s, &group->streams, link) { if (!s->source_buf) continue; int32_t level = (int32_t)s->source_buf->level; if (level > target) { uint32_t drop = (level - target) * s->source_buf->frame_size; uint32_t avail = spa_bt_decode_buffer_get_size(s->source_buf); drop = SPA_MIN(drop, avail); spa_log_debug(group->log, "%p: ISO overrun group:%u fd:%d level:%f target:%d drop:%u", group, group->id, s->fd, s->source_buf->level, target, drop/s->source_buf->frame_size); spa_bt_decode_buffer_read(s->source_buf, drop); } spa_bt_decode_buffer_recover(s->source_buf); } } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/bluez5/iso-io.h000066400000000000000000000037641511204443500255320ustar00rootroot00000000000000/* Spa Bluez5 ISO I/O */ /* SPDX-FileCopyrightText: Copyright © 2023 Pauli Virtanen */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_BLUEZ5_ISO_IO_H #define SPA_BLUEZ5_ISO_IO_H #include #include #include #include #include struct spa_bt_decode_buffer; struct spa_bt_transport; /** * ISO I/O. * * Synchronizes related writes from different streams in the same group * to occur at same real time instant (or not at all). */ struct spa_bt_iso_io { uint64_t now; /**< Reference time position of next packet (read-only) */ uint64_t duration; /**< ISO interval duration in ns (read-only) */ bool resync; /**< Resync position for next packet; (pull callback sets to * false when done) */ uint32_t timestamp; /**< Packet timestamp (set by pull callback) */ uint8_t buf[4096]; /**< Packet data (set by pull callback) */ size_t size; /**< Packet size (set by pull callback) */ bool need_resync; /**< Resync requested (set by pull callback) */ struct spa_audio_info format; /**< Audio format */ void *codec_data; /**< Codec data */ void *user_data; }; typedef void (*spa_bt_iso_io_pull_t)(struct spa_bt_iso_io *io); struct spa_bt_iso_io *spa_bt_iso_io_create(struct spa_bt_transport *t, struct spa_log *log, struct spa_loop *data_loop, struct spa_system *data_system); struct spa_bt_iso_io *spa_bt_iso_io_attach(struct spa_bt_iso_io *io, struct spa_bt_transport *t); void spa_bt_iso_io_destroy(struct spa_bt_iso_io *io); void spa_bt_iso_io_set_cb(struct spa_bt_iso_io *io, spa_bt_iso_io_pull_t pull, void *user_data); int spa_bt_iso_io_recv_errqueue(struct spa_bt_iso_io *io); void spa_bt_iso_io_set_source_buffer(struct spa_bt_iso_io *io, struct spa_bt_decode_buffer *buffer); int32_t spa_bt_iso_io_get_source_target_latency(struct spa_bt_iso_io *io); void spa_bt_iso_io_check_rx_sync(struct spa_bt_iso_io *io, uint64_t position); int64_t spa_bt_iso_io_recv(struct spa_bt_iso_io *io, int64_t now); #endif pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/bluez5/media-codecs.c000066400000000000000000000132261511204443500266350ustar00rootroot00000000000000/* * BlueALSA - bluez-a2dp.c * Copyright (c) 2016-2017 Arkadiusz Bokowy * * This file is a part of bluez-alsa. * * This project is licensed under the terms of the MIT license. * */ #include #include #include #include "media-codecs.h" int media_codec_select_config(const struct media_codec_config configs[], size_t n, uint32_t cap, int preferred_value) { size_t i; spa_autofree int *scores = NULL; int res; unsigned int max_priority; if (n == 0) return -EINVAL; scores = calloc(n, sizeof(int)); if (scores == NULL) return -errno; max_priority = configs[0].priority; for (i = 1; i < n; ++i) { if (configs[i].priority > max_priority) max_priority = configs[i].priority; } for (i = 0; i < n; ++i) { if (!(configs[i].config & cap)) { scores[i] = -1; continue; } if (configs[i].value == preferred_value) scores[i] = 100 * (max_priority + 1); else if (configs[i].value > preferred_value) scores[i] = 10 * (max_priority + 1); else scores[i] = 1; scores[i] *= configs[i].priority + 1; } res = 0; for (i = 1; i < n; ++i) { if (scores[i] > scores[res]) res = i; } if (scores[res] < 0) return -EINVAL; return res; } int media_codec_get_config(const struct media_codec_config configs[], size_t n, uint32_t conf) { size_t i; for (i = 0; i < n; ++i) if (configs[i].config == conf) return configs[i].value; return -EINVAL; } bool media_codec_check_caps(const struct media_codec *codec, unsigned int codec_id, const void *caps, size_t caps_size, const struct media_codec_audio_info *info, const struct spa_dict *global_settings) { uint8_t config[A2DP_MAX_CAPS_SIZE]; int res; if (codec->kind == MEDIA_CODEC_HFP) return true; if (codec_id != codec->codec_id) return false; if (caps == NULL) return false; res = codec->select_config(codec, 0, caps, caps_size, info, global_settings, config, NULL); if (res < 0) return false; if (codec->kind == MEDIA_CODEC_BAP) return true; else return ((size_t)res == caps_size); } void ltv_writer_data(struct ltv_writer *w, uint8_t type, void* value, size_t len) { struct ltv *ltv; size_t sz = (size_t)w->size + sizeof(struct ltv) + len; if (!w->buf || sz > w->max_size || (uint16_t)sz != sz) { w->buf = NULL; return; } ltv = SPA_PTROFF(w->buf, w->size, struct ltv); ltv->len = len + 1; ltv->type = type; memcpy(ltv->value, value, len); w->size = sz; } void ltv_writer_uint8(struct ltv_writer *w, uint8_t type, uint8_t v) { ltv_writer_data(w, type, &v, sizeof(v)); } void ltv_writer_uint16(struct ltv_writer *w, uint8_t type, uint16_t value) { uint16_t v = htobs(value); ltv_writer_data(w, type, &v, sizeof(v)); } void ltv_writer_uint32(struct ltv_writer *w, uint8_t type, uint32_t value) { uint32_t v = htobl(value); ltv_writer_data(w, type, &v, sizeof(v)); } int ltv_writer_end(struct ltv_writer *w) { if (!w->buf) return -ENOSPC; w->buf = NULL; return w->size; } #ifdef CODEC_PLUGIN struct impl { struct spa_handle handle; struct spa_bluez5_codec_a2dp bluez5_codec_a2dp; }; static int impl_get_interface(struct spa_handle *handle, const char *type, void **interface) { struct impl *this; spa_return_val_if_fail(handle != NULL, -EINVAL); spa_return_val_if_fail(interface != NULL, -EINVAL); this = (struct impl *) handle; if (spa_streq(type, SPA_TYPE_INTERFACE_Bluez5CodecMedia)) *interface = &this->bluez5_codec_a2dp; else return -ENOENT; return 0; } static int impl_clear(struct spa_handle *handle) { spa_return_val_if_fail(handle != NULL, -EINVAL); return 0; } static size_t impl_get_size(const struct spa_handle_factory *factory, const struct spa_dict *params) { return sizeof(struct impl); } static int impl_init(const struct spa_handle_factory *factory, struct spa_handle *handle, const struct spa_dict *info, const struct spa_support *support, uint32_t n_support) { struct impl *this; spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(handle != NULL, -EINVAL); handle->get_interface = impl_get_interface; handle->clear = impl_clear; this = (struct impl *) handle; this->bluez5_codec_a2dp.codecs = codec_plugin_media_codecs; this->bluez5_codec_a2dp.iface = SPA_INTERFACE_INIT( SPA_TYPE_INTERFACE_Bluez5CodecMedia, SPA_VERSION_BLUEZ5_CODEC_MEDIA, NULL, this); return 0; } static const struct spa_interface_info impl_interfaces[] = { {SPA_TYPE_INTERFACE_Bluez5CodecMedia,}, }; static int impl_enum_interface_info(const struct spa_handle_factory *factory, const struct spa_interface_info **info, uint32_t *index) { spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(info != NULL, -EINVAL); spa_return_val_if_fail(index != NULL, -EINVAL); switch (*index) { case 0: *info = &impl_interfaces[*index]; break; default: return 0; } (*index)++; return 1; } SPA_LOG_TOPIC_ENUM_DEFINE_REGISTERED; static const struct spa_dict_item handle_info_items[] = { { SPA_KEY_FACTORY_DESCRIPTION, "Bluetooth codec plugin" }, }; static const struct spa_dict handle_info = SPA_DICT_INIT_ARRAY(handle_info_items); static struct spa_handle_factory handle_factory = { SPA_VERSION_HANDLE_FACTORY, NULL, &handle_info, impl_get_size, impl_init, impl_enum_interface_info, }; SPA_EXPORT int spa_handle_factory_enum(const struct spa_handle_factory **factory, uint32_t *index) { spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(index != NULL, -EINVAL); if (handle_factory.name == NULL) handle_factory.name = codec_plugin_factory_name; switch (*index) { case 0: *factory = &handle_factory; break; default: return 0; } (*index)++; return 1; } #endif pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/bluez5/media-codecs.h000066400000000000000000000236531511204443500266470ustar00rootroot00000000000000/* Spa A2DP codec API */ /* SPDX-FileCopyrightText: Copyright © 2020 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_BLUEZ5_A2DP_CODECS_H_ #define SPA_BLUEZ5_A2DP_CODECS_H_ #include #include #include #include #include #include #include #include #include #include #include "a2dp-codec-caps.h" #include "bap-codec-caps.h" /* * The codec plugin SPA interface is private. The version should be incremented * when any of the structs or semantics change. */ #define SPA_TYPE_INTERFACE_Bluez5CodecMedia SPA_TYPE_INFO_INTERFACE_BASE "Bluez5:Codec:Media:Private" #define SPA_VERSION_BLUEZ5_CODEC_MEDIA 16 struct spa_bluez5_codec_a2dp { struct spa_interface iface; const struct media_codec * const *codecs; /**< NULL terminated array */ }; #define MEDIA_CODEC_FACTORY_NAME(basename) (SPA_NAME_API_CODEC_BLUEZ5_MEDIA "." basename) #ifdef CODEC_PLUGIN #define MEDIA_CODEC_EXPORT_DEF(basename,...) \ const char *codec_plugin_factory_name = MEDIA_CODEC_FACTORY_NAME(basename); \ static const struct media_codec * const codec_plugin_media_codec_list[] = { __VA_ARGS__, NULL }; \ const struct media_codec * const * const codec_plugin_media_codecs = codec_plugin_media_codec_list; \ SPA_LOG_TOPIC_DEFINE(codec_plugin_log_topic, "spa.bluez5.codecs." basename); extern const struct media_codec * const * const codec_plugin_media_codecs; extern const char *codec_plugin_factory_name; extern struct spa_log_topic codec_plugin_log_topic; #undef SPA_LOG_TOPIC_DEFAULT #define SPA_LOG_TOPIC_DEFAULT &codec_plugin_log_topic #endif #define MEDIA_CODEC_FLAG_SINK (1 << 0) #define A2DP_CODEC_DEFAULT_RATE 48000 #define A2DP_CODEC_DEFAULT_CHANNELS 2 enum { NEED_FLUSH_NO = 0, NEED_FLUSH_ALL = 1, NEED_FLUSH_FRAGMENT = 2, }; enum media_codec_kind { MEDIA_CODEC_A2DP, MEDIA_CODEC_BAP, MEDIA_CODEC_ASHA, MEDIA_CODEC_HFP, }; struct media_codec_audio_info { uint32_t rate; uint32_t channels; }; struct media_codec { enum spa_bluetooth_audio_codec id; enum media_codec_kind kind; uint8_t codec_id; a2dp_vendor_codec_t vendor; const char *name; const char *description; const char *endpoint_name; /**< Endpoint name. If NULL, same as name */ const struct spa_dict *info; const size_t send_buf_size; const struct media_codec *duplex_codec; /**< Codec for non-standard A2DP duplex channel */ const bool stream_pkt; /**< If true, socket data may contain multiple packets. * After successful decode, start_decode() should be * called again to parse the remaining data. */ int (*get_bis_config)(const struct media_codec *codec, uint8_t *caps, uint8_t *caps_size, struct spa_dict *settings, struct bap_codec_qos *qos); /** If fill_caps is NULL, no endpoint is registered (for sharing with another codec). */ int (*fill_caps) (const struct media_codec *codec, uint32_t flags, const struct spa_dict *settings, uint8_t caps[A2DP_MAX_CAPS_SIZE]); int (*select_config) (const struct media_codec *codec, uint32_t flags, const void *caps, size_t caps_size, const struct media_codec_audio_info *info, const struct spa_dict *global_settings, uint8_t config[A2DP_MAX_CAPS_SIZE], void **config_data); int (*enum_config) (const struct media_codec *codec, uint32_t flags, const void *caps, size_t caps_size, uint32_t id, uint32_t idx, struct spa_pod_builder *builder, struct spa_pod **param); int (*validate_config) (const struct media_codec *codec, uint32_t flags, const void *caps, size_t caps_size, struct spa_audio_info *info); int (*get_qos)(const struct media_codec *codec, const struct bap_endpoint_qos *endpoint_qos, const void *config_data, struct bap_codec_qos *qos); int (*get_metadata)(const struct media_codec *codec, const void *config_data, uint8_t *meta, size_t meta_max_size); void (*free_config_data)(const struct media_codec *codec, void *config_data); /** qsort comparison sorting caps in order of preference for the codec. * Used in codec switching to select best remote endpoints. * The caps handed in correspond to this codec_id, but are * otherwise not checked beforehand. */ int (*caps_preference_cmp) (const struct media_codec *codec, uint32_t flags, const void *caps1, size_t caps1_size, const void *caps2, size_t caps2_size, const struct media_codec_audio_info *info, const struct spa_dict *global_settings); void *(*init_props) (const struct media_codec *codec, uint32_t flags, const struct spa_dict *settings); void (*clear_props) (void *); int (*enum_props) (void *props, const struct spa_dict *settings, uint32_t id, uint32_t idx, struct spa_pod_builder *builder, struct spa_pod **param); int (*set_props) (void *props, const struct spa_pod *param); void *(*init) (const struct media_codec *codec, uint32_t flags, void *config, size_t config_size, const struct spa_audio_info *info, void *props, size_t mtu); void (*deinit) (void *data); int (*update_props) (void *data, void *props); /** Number of bytes needed for encoding */ int (*get_block_size) (void *data); /** * Duration of the next packet in nanoseconds. * * For BAP this shall be constant and equal to the SDU interval. * * \param data Codec data from init() * \return Duration in nanoseconds. */ uint64_t (*get_interval) (void *data); int (*abr_process) (void *data, size_t unsent); /** * Start encoding new packet. * * \param data Codec data from init() * \param timestamp Packet time stamp (in samples played) * \return Size of packet header written to dst in bytes, or < 0 for error */ int (*start_encode) (void *data, void *dst, size_t dst_size, uint16_t seqnum, uint32_t timestamp); /** * Consume data from input buffer, encode to output buffer. * * \param data Codec data from init() * \param src Source data. NULL if encoding packet fragment. * \param dst Output buffer position. The memory region passed to the * previous start_encode() is still valid, and this position is inside * that region. The caller does not modify the contents of the buffer. * \param dst_size Remaining buffer space after dst * \param dst_out Bytes written to dst * \param need_flush * - NEED_FLUSH_NO: don't flush this packet, * - NEED_FLUSH_ALL: flush this packet, * - NEED_FLUSH_FRAGMENT: flush packet fragment. The next start_encode() * and encode() are expected to produce more fragments or the final * fragment with NEED_FLUSH_ALL, without consuming source data. * The fragment start_encode() is called with the same output buffer * as previous. The fragment encode() will be called with NULL src. * No new source data will be fed in before NEED_FLUSH_ALL. * \return Number of bytes consumed from src, or < 0 for error */ int (*encode) (void *data, const void *src, size_t src_size, void *dst, size_t dst_size, size_t *dst_out, int *need_flush); /** * Start decoding received packet. * * \return Number of bytes consumed from source data, or < 0 for error */ int (*start_decode) (void *data, const void *src, size_t src_size, uint16_t *seqnum, uint32_t *timestamp); /** * Decode received packet data. * * \param dst_out Number of bytes output to dst * \return Number of bytes consumed from src, or < 0 for error */ int (*decode) (void *data, const void *src, size_t src_size, void *dst, size_t dst_size, size_t *dst_out); /** * Generate audio data corresponding to one lost packet, using codec internal * packet loss concealment. * * NULL if not available. * * \return number of bytes produced, or < 0 for error */ int (*produce_plc) (void *data, void *dst, size_t dst_size); int (*reduce_bitpool) (void *data); int (*increase_bitpool) (void *data); void (*set_log) (struct spa_log *global_log); /** * Get codec internal delays, in samples at input/output rates. * * The delay does not include the duration of the PCM input/output * audio data, but is that internal to the codec. * * \param[out] encoder Encoder delay in samples, or NULL * \param[out] decoder Decoder delay in samples, or NULL */ void (*get_delay) (void *data, uint32_t *encoder, uint32_t *decoder); }; struct media_codec_config { uint32_t config; int value; unsigned int priority; }; static inline const char *media_codec_kind_str(const struct media_codec *codec) { switch (codec->kind) { case MEDIA_CODEC_A2DP: return "A2DP"; case MEDIA_CODEC_BAP: return "BAP"; case MEDIA_CODEC_ASHA: return "ASHA"; case MEDIA_CODEC_HFP: return "HFP"; } return "unknown"; } int media_codec_select_config(const struct media_codec_config configs[], size_t n, uint32_t cap, int preferred_value); int media_codec_get_config(const struct media_codec_config configs[], size_t n, uint32_t conf); bool media_codec_check_caps(const struct media_codec *codec, unsigned int codec_id, const void *caps, size_t caps_size, const struct media_codec_audio_info *info, const struct spa_dict *global_settings); struct __attribute__((packed)) ltv { uint8_t len; uint8_t type; uint8_t value[]; }; struct ltv_writer { void *buf; uint16_t size; size_t max_size; }; #define LTV_WRITER(ptr, max) ((struct ltv_writer) { .buf = (ptr), .max_size = (max) }) void ltv_writer_data(struct ltv_writer *w, uint8_t type, void* value, size_t len); void ltv_writer_uint8(struct ltv_writer *w, uint8_t type, uint8_t v); void ltv_writer_uint16(struct ltv_writer *w, uint8_t type, uint16_t value); void ltv_writer_uint32(struct ltv_writer *w, uint8_t type, uint32_t value); int ltv_writer_end(struct ltv_writer *w); static inline const struct ltv *ltv_next(const void **data, size_t *size) { const struct ltv *ltv; if (*size == 0) { *data = NULL; return NULL; } if (*size < sizeof(struct ltv)) return NULL; ltv = *data; if (ltv->len >= *size) return NULL; *data = SPA_PTROFF(*data, ltv->len + 1, void); *size -= ltv->len + 1; return ltv; } #endif pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/bluez5/media-sink.c000066400000000000000000002174241511204443500263470ustar00rootroot00000000000000/* Spa Media Sink */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "defs.h" #include "rtp.h" #include "media-codecs.h" #include "rate-control.h" #include "iso-io.h" SPA_LOG_TOPIC_DEFINE_STATIC(log_topic, "spa.bluez5.sink.media"); #undef SPA_LOG_TOPIC_DEFAULT #define SPA_LOG_TOPIC_DEFAULT &log_topic #include "bt-latency.h" #define DEFAULT_CLOCK_NAME "clock.system.monotonic" struct props { int64_t latency_offset; char clock_name[64]; }; #define FILL_FRAMES 4 #define MIN_BUFFERS 3 #define MAX_BUFFERS 32 #define BUFFER_SIZE (8192*8) #define RATE_CTL_DIFF_MAX 0.01 #define LATENCY_PERIOD (200 * SPA_NSEC_PER_MSEC) /* Wait for two cycles before trying to sync ISO. On start/driver reassign, * first cycle may have strange number of samples. */ #define RESYNC_CYCLES 2 struct buffer { uint32_t id; #define BUFFER_FLAG_OUT (1<<0) uint32_t flags; struct spa_buffer *buf; struct spa_meta_header *h; struct spa_list link; }; struct port { struct spa_audio_info current_format; uint32_t frame_size; unsigned int have_format:1; uint64_t info_all; struct spa_port_info info; struct spa_io_buffers *io; struct spa_io_rate_match *rate_match; struct spa_latency_info latency; #define IDX_EnumFormat 0 #define IDX_Meta 1 #define IDX_IO 2 #define IDX_Format 3 #define IDX_Buffers 4 #define IDX_Latency 5 #define N_PORT_PARAMS 6 struct spa_param_info params[N_PORT_PARAMS]; struct buffer buffers[MAX_BUFFERS]; uint32_t n_buffers; struct spa_list free; struct spa_list ready; size_t ready_offset; struct spa_bt_rate_control ratectl; }; #define ASHA_ENCODED_PKT_SZ 161 /* 160 bytes encoded + 1 byte sequence number */ #define ASHA_CONN_INTERVAL (20 * SPA_NSEC_PER_MSEC) struct spa_bt_asha { struct spa_source flush_source; struct spa_source timer_source; int timerfd; uint8_t buf[512]; uint64_t ref_t0; uint64_t next_time; unsigned int flush_pending:1; unsigned int set_timer:1; }; struct impl { struct spa_handle handle; struct spa_node node; struct spa_log *log; struct spa_loop *data_loop; struct spa_system *data_system; struct spa_loop_utils *loop_utils; struct spa_hook_list hooks; struct spa_callbacks callbacks; uint32_t quantum_limit; uint64_t info_all; struct spa_node_info info; #define IDX_PropInfo 0 #define IDX_Props 1 #define N_NODE_PARAMS 2 struct spa_param_info params[N_NODE_PARAMS]; struct props props; struct spa_bt_transport *transport; struct spa_hook transport_listener; struct port port; unsigned int started:1; unsigned int start_ready:1; unsigned int transport_started:1; unsigned int following:1; unsigned int is_output:1; unsigned int flush_pending:1; unsigned int iso_pending:1; unsigned int own_codec_data:1; unsigned int is_duplex:1; unsigned int is_internal:1; struct spa_source source; int timerfd; struct spa_source flush_source; struct spa_source flush_timer_source; int flush_timerfd; struct spa_io_clock *clock; struct spa_io_position *position; uint64_t current_time; uint64_t next_time; uint64_t last_error; uint64_t process_time; uint64_t process_duration; uint64_t process_rate; double process_rate_diff; uint64_t prev_flush_time; uint64_t next_flush_time; uint64_t packet_delay_ns; struct spa_source *update_delay_event; uint32_t encoder_delay; const struct media_codec *codec; bool codec_props_changed; void *codec_props; void *codec_data; struct spa_audio_info codec_format; int need_flush; bool fragment; uint32_t resync; uint32_t block_size; uint8_t buffer[BUFFER_SIZE]; uint32_t buffer_used; uint32_t header_size; uint32_t block_count; uint16_t seqnum; uint64_t last_seqnum; uint32_t timestamp; uint64_t sample_count; uint8_t tmp_buffer[BUFFER_SIZE]; uint32_t tmp_buffer_used; uint32_t fd_buffer_size; uint32_t silence_frames; struct spa_bt_asha *asha; struct spa_list asha_link; struct spa_bt_latency tx_latency; }; #define CHECK_PORT(this,d,p) ((d) == SPA_DIRECTION_INPUT && (p) == 0) static struct spa_list asha_sinks = SPA_LIST_INIT(&asha_sinks); static void drop_frames(struct impl *this, uint32_t req); static uint64_t get_reference_time(struct impl *this, uint64_t *duration_ns_ret); static struct impl *find_other_asha(struct impl *this) { struct impl *other; spa_list_for_each(other, &asha_sinks, asha_link) { if (this == other) continue; if (this->transport->hisyncid == other->transport->hisyncid) { return other; } } return NULL; } static void reset_props(struct impl *this, struct props *props) { props->latency_offset = 0; strncpy(props->clock_name, DEFAULT_CLOCK_NAME, sizeof(props->clock_name)); } static int impl_node_enum_params(void *object, int seq, uint32_t id, uint32_t start, uint32_t num, const struct spa_pod *filter) { struct impl *this = object; struct spa_pod *param; struct spa_pod_builder b = { 0 }; uint8_t buffer[1024]; struct spa_result_node_params result; uint32_t count = 0, index_offset = 0; bool enum_codec = false; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(num != 0, -EINVAL); result.id = id; result.next = start; next: result.index = result.next++; spa_pod_builder_init(&b, buffer, sizeof(buffer)); switch (id) { case SPA_PARAM_PropInfo: { switch (result.index) { case 0: param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_PropInfo, id, SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_latencyOffsetNsec), SPA_PROP_INFO_description, SPA_POD_String("Latency offset (ns)"), SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Long(0LL, INT64_MIN, INT64_MAX)); break; default: enum_codec = true; index_offset = 1; } break; } case SPA_PARAM_Props: { struct props *p = &this->props; switch (result.index) { case 0: param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_Props, id, SPA_PROP_latencyOffsetNsec, SPA_POD_Long(p->latency_offset)); break; default: enum_codec = true; index_offset = 1; } break; } default: return -ENOENT; } if (enum_codec) { int res; if (this->codec->enum_props == NULL || this->codec_props == NULL || this->transport == NULL) return 0; else if ((res = this->codec->enum_props(this->codec_props, this->transport->device->settings, id, result.index - index_offset, &b, ¶m)) != 1) return res; } if (spa_pod_filter(&b, &result.param, param, filter) < 0) goto next; spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); if (++count != num) goto next; return 0; } static int set_timeout(struct impl *this, uint64_t time) { struct itimerspec ts; ts.it_value.tv_sec = time / SPA_NSEC_PER_SEC; ts.it_value.tv_nsec = time % SPA_NSEC_PER_SEC; ts.it_interval.tv_sec = 0; ts.it_interval.tv_nsec = 0; return spa_system_timerfd_settime(this->data_system, this->timerfd, SPA_FD_TIMER_ABSTIME, &ts, NULL); } static int set_timers(struct impl *this) { struct timespec now; spa_system_clock_gettime(this->data_system, CLOCK_MONOTONIC, &now); this->next_time = SPA_TIMESPEC_TO_NSEC(&now); return set_timeout(this, this->following ? 0 : this->next_time); } static int set_asha_timeout(struct impl *this, uint64_t time) { struct itimerspec ts; ts.it_value.tv_sec = time / SPA_NSEC_PER_SEC; ts.it_value.tv_nsec = time % SPA_NSEC_PER_SEC; ts.it_interval.tv_sec = 0; ts.it_interval.tv_nsec = 0; return spa_system_timerfd_settime(this->data_system, this->asha->timerfd, SPA_FD_TIMER_ABSTIME, &ts, NULL); } static int set_asha_timer(struct impl *this, struct impl *other) { uint64_t time; if (other) { /* Try to line up our timer with the other side, and drop samples so we're sending * the same sample position on both sides */ uint64_t other_samples = (get_reference_time(other, NULL) - other->asha->ref_t0) * this->port.current_format.info.raw.rate / SPA_NSEC_PER_SEC; if (other->asha->next_time < this->process_time) { /* Other side has not yet been scheduled in this graph cycle, we expect * there might be one packet left from the previous cycle at most */ time = other->asha->next_time + ASHA_CONN_INTERVAL; other_samples += ASHA_CONN_INTERVAL * this->port.current_format.info.raw.rate / SPA_NSEC_PER_SEC; } else { /* Other side has set up its next cycle, catch up */ time = other->asha->next_time; } /* Since the quantum and packet size aren't correlated, drop any samples from this * cycle that might have been used to send a packet starting in the previous cycle */ drop_frames(this, other_samples % this->process_duration); } else { time = this->process_time; } this->asha->next_time = time; return set_asha_timeout(this, this->asha->next_time); } static inline bool is_following(struct impl *this) { return this->position && this->clock && this->position->clock.id != this->clock->id; } struct reassign_io_info { struct impl *this; struct spa_io_position *position; struct spa_io_clock *clock; }; static int do_reassign_io(struct spa_loop *loop, bool async, uint32_t seq, const void *data, size_t size, void *user_data) { struct reassign_io_info *info = user_data; struct impl *this = info->this; bool following; if (this->position != info->position || this->clock != info->clock) this->resync = RESYNC_CYCLES; this->position = info->position; this->clock = info->clock; following = is_following(this); if (following != this->following) { spa_log_debug(this->log, "%p: reassign follower %d->%d", this, this->following, following); this->following = following; set_timers(this); } return 0; } static int impl_node_set_io(void *object, uint32_t id, void *data, size_t size) { struct impl *this = object; struct reassign_io_info info = { .this = this, .position = this->position, .clock = this->clock }; spa_return_val_if_fail(this != NULL, -EINVAL); switch (id) { case SPA_IO_Clock: info.clock = data; if (info.clock != NULL) { spa_scnprintf(info.clock->name, sizeof(info.clock->name), "%s", this->props.clock_name); } break; case SPA_IO_Position: info.position = data; break; default: return -ENOENT; } if (this->started) { spa_loop_locked(this->data_loop, do_reassign_io, 0, NULL, 0, &info); } else { this->clock = info.clock; this->position = info.position; } return 0; } static void emit_node_info(struct impl *this, bool full); static void emit_port_info(struct impl *this, struct port *port, bool full); static void set_latency(struct impl *this, bool emit_latency) { struct port *port = &this->port; int64_t delay; /* in main loop */ if (this->transport == NULL || !port->have_format) return; /* * We start flushing data immediately, so the delay is: * * (packet delay) + (codec internal delay) + (transport delay) + (latency offset) * * and doesn't depend on the quantum. Kernel knows the latency due to * socket/controller queue, but doesn't tell us, so not included but * hopefully in < 10 ms range. */ delay = __atomic_load_n(&this->packet_delay_ns, __ATOMIC_RELAXED); delay += (int64_t)this->encoder_delay * SPA_NSEC_PER_SEC / port->current_format.info.raw.rate; delay += spa_bt_transport_get_delay_nsec(this->transport); delay += SPA_CLAMP(this->props.latency_offset, -delay, INT64_MAX / 2); delay = SPA_MAX(delay, 0); port->latency.min_ns = port->latency.max_ns = delay; port->latency.min_rate = port->latency.max_rate = 0; if (this->codec->kind == MEDIA_CODEC_BAP) { /* ISO has different delay */ port->latency.min_quantum = port->latency.max_quantum = 1.0f; } else { port->latency.min_quantum = port->latency.max_quantum = 0.0f; } spa_log_info(this->log, "%p: total latency:%d ms", this, (int)(delay / SPA_NSEC_PER_MSEC)); if (emit_latency) { port->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; port->params[IDX_Latency].flags ^= SPA_PARAM_INFO_SERIAL; emit_port_info(this, port, false); } } static void update_delay_event(void *data, uint64_t count) { struct impl *this = data; /* in main loop */ set_latency(this, true); } static void update_packet_delay(struct impl *this, uint64_t delay) { uint64_t old_delay = this->packet_delay_ns; /* in data thread */ delay = SPA_MAX(delay, old_delay); if (delay == old_delay) return; __atomic_store_n(&this->packet_delay_ns, delay, __ATOMIC_RELAXED); if (this->update_delay_event) spa_loop_utils_signal_event(this->loop_utils, this->update_delay_event); } static int apply_props(struct impl *this, const struct spa_pod *param) { struct props new_props = this->props; int changed = 0; if (param == NULL) { reset_props(this, &new_props); } else { spa_pod_parse_object(param, SPA_TYPE_OBJECT_Props, NULL, SPA_PROP_latencyOffsetNsec, SPA_POD_OPT_Long(&new_props.latency_offset)); } changed = (memcmp(&new_props, &this->props, sizeof(struct props)) != 0); this->props = new_props; if (changed) set_latency(this, true); return changed; } static int impl_node_set_param(void *object, uint32_t id, uint32_t flags, const struct spa_pod *param) { struct impl *this = object; spa_return_val_if_fail(this != NULL, -EINVAL); switch (id) { case SPA_PARAM_Props: { int res, codec_res = 0; res = apply_props(this, param); if (this->codec_props && this->codec->set_props) { codec_res = this->codec->set_props(this->codec_props, param); if (codec_res > 0) this->codec_props_changed = true; } if (res > 0 || codec_res > 0) { this->info.change_mask |= SPA_NODE_CHANGE_MASK_PARAMS; this->params[IDX_Props].flags ^= SPA_PARAM_INFO_SERIAL; emit_node_info(this, false); } break; } default: return -ENOENT; } return 0; } static uint32_t get_queued_frames(struct impl *this) { struct port *port = &this->port; uint32_t bytes = 0; struct buffer *b; spa_list_for_each(b, &port->ready, link) { struct spa_data *d = b->buf->datas; bytes += d[0].chunk->size; } if (bytes > port->ready_offset) bytes -= port->ready_offset; else bytes = 0; bytes += this->silence_frames * this->block_size; /* Count (partially) encoded packet */ bytes += this->tmp_buffer_used; bytes += this->block_count * this->block_size; return bytes / port->frame_size; } static uint64_t get_reference_time(struct impl *this, uint64_t *duration_ns_ret) { struct port *port = &this->port; uint64_t duration_ns; int64_t t; bool resampling; if (!this->process_rate || !this->process_duration) { if (this->position) { this->process_duration = this->position->clock.duration; this->process_rate = this->position->clock.rate.denom; this->process_rate_diff = this->position->clock.rate_diff; } else { this->process_duration = 1024; this->process_rate = 48000; this->process_rate_diff = 1.0; } } duration_ns = ((uint64_t)this->process_duration * SPA_NSEC_PER_SEC / this->process_rate); if (duration_ns_ret) *duration_ns_ret = duration_ns; /* Time at the first sample in the current packet. */ t = duration_ns; t -= ((uint64_t)get_queued_frames(this) * SPA_NSEC_PER_SEC / port->current_format.info.raw.rate); /* Account for resampling delay */ resampling = (port->current_format.info.raw.rate != this->process_rate) || this->following; if (port->rate_match && this->position && resampling) { t -= (port->rate_match->delay * SPA_NSEC_PER_SEC + port->rate_match->delay_frac) / port->current_format.info.raw.rate; } if (this->process_rate_diff > 0) t = (int64_t)(t / this->process_rate_diff); if (this->transport && this->transport->iso_io && this->transport->iso_io->size) t -= this->transport->iso_io->duration; return this->process_time + t; } static int reset_buffer(struct impl *this) { if (this->codec_props_changed && this->codec_props && this->codec->update_props) { this->codec->update_props(this->codec_data, this->codec_props); this->codec_props_changed = false; } this->need_flush = 0; this->block_count = 0; this->fragment = false; if (this->codec->kind == MEDIA_CODEC_BAP || this->codec->kind == MEDIA_CODEC_ASHA) this->timestamp = get_reference_time(this, NULL) / SPA_NSEC_PER_USEC; else this->timestamp = this->sample_count; this->buffer_used = this->codec->start_encode(this->codec_data, this->buffer, sizeof(this->buffer), ++this->seqnum, this->timestamp); this->header_size = this->buffer_used; return 0; } static int setup_matching(struct impl *this) { struct port *port = &this->port; if (!this->transport_started) port->ratectl.corr = 1.0; if (port->rate_match) { port->rate_match->rate = 1 / port->ratectl.corr; /* We rate match in the system clock domain. If driver ticks at a * different rate, we as follower must compensate. */ if (this->following && SPA_LIKELY(this->position && this->position->clock.rate_diff > 0)) port->rate_match->rate /= this->position->clock.rate_diff; SPA_FLAG_UPDATE(port->rate_match->flags, SPA_IO_RATE_MATCH_FLAG_ACTIVE, this->following); } return 0; } static int get_transport_unsent_size(struct impl *this) { int res, value; if (this->tx_latency.enabled) { res = 0; value = this->tx_latency.unsent; } else if (this->codec->kind == MEDIA_CODEC_HFP) { value = 0; } else { res = ioctl(this->flush_source.fd, TIOCOUTQ, &value); if (res < 0) { spa_log_error(this->log, "%p: ioctl fail: %m", this); return -errno; } if ((unsigned int)value > this->fd_buffer_size) return -EIO; value = this->fd_buffer_size - value; } spa_log_trace(this->log, "%p: fd unsent size:%d/%d", this, value, this->fd_buffer_size); return value; } static int send_buffer(struct impl *this) { int written, unsent; struct timespec ts_pre; if (this->codec->abr_process) { unsent = get_transport_unsent_size(this); if (unsent >= 0) this->codec->abr_process(this->codec_data, unsent); } spa_system_clock_gettime(this->data_system, CLOCK_REALTIME, &ts_pre); if (this->codec->kind == MEDIA_CODEC_HFP) { written = spa_bt_sco_io_write(this->transport->sco_io, this->buffer, this->buffer_used); } else { written = spa_bt_send(this->flush_source.fd, this->buffer, this->buffer_used, &this->tx_latency, SPA_TIMESPEC_TO_NSEC(&ts_pre)); } if (SPA_UNLIKELY(spa_log_level_topic_enabled(this->log, SPA_LOG_TOPIC_DEFAULT, SPA_LOG_LEVEL_TRACE))) { struct timespec ts; uint64_t now; uint64_t dt; spa_system_clock_gettime(this->data_system, CLOCK_MONOTONIC, &ts); now = SPA_TIMESPEC_TO_NSEC(&ts); dt = now - this->prev_flush_time; this->prev_flush_time = now; spa_log_trace(this->log, "%p: send blocks:%d block:%u seq:%u ts:%u size:%u " "wrote:%d dt:%"PRIu64, this, this->block_count, this->block_size, this->seqnum, this->timestamp, this->buffer_used, written, dt); } if (written < 0) { spa_log_debug(this->log, "%p: %m", this); return -errno; } return written; } static int encode_buffer(struct impl *this, const void *data, uint32_t size) { int processed; size_t out_encoded; struct port *port = &this->port; const void *from_data = data; int from_size = size; spa_log_trace(this->log, "%p: encode %d used %d, %d %d %d", this, size, this->buffer_used, port->frame_size, this->block_size, this->block_count); if (this->need_flush) return 0; if (this->buffer_used >= sizeof(this->buffer)) return -ENOSPC; if (size < this->block_size - this->tmp_buffer_used) { memcpy(this->tmp_buffer + this->tmp_buffer_used, data, size); this->tmp_buffer_used += size; return size; } else if (this->tmp_buffer_used > 0) { memcpy(this->tmp_buffer + this->tmp_buffer_used, data, this->block_size - this->tmp_buffer_used); from_data = this->tmp_buffer; from_size = this->block_size; this->tmp_buffer_used = this->block_size - this->tmp_buffer_used; } processed = this->codec->encode(this->codec_data, from_data, from_size, this->buffer + this->buffer_used, sizeof(this->buffer) - this->buffer_used, &out_encoded, &this->need_flush); if (processed < 0) return processed; this->sample_count += processed / port->frame_size; this->block_count += processed / this->block_size; this->buffer_used += out_encoded; spa_log_trace(this->log, "%p: processed %d %zd used %d", this, processed, out_encoded, this->buffer_used); if (this->tmp_buffer_used) { processed = this->tmp_buffer_used; this->tmp_buffer_used = 0; } return processed; } static int encode_fragment(struct impl *this) { int res; size_t out_encoded; struct port *port = &this->port; spa_log_trace(this->log, "%p: encode fragment used %d, %d %d %d", this, this->buffer_used, port->frame_size, this->block_size, this->block_count); if (this->need_flush) return 0; res = this->codec->encode(this->codec_data, NULL, 0, this->buffer + this->buffer_used, sizeof(this->buffer) - this->buffer_used, &out_encoded, &this->need_flush); if (res < 0) return res; if (res != 0) return -EINVAL; this->buffer_used += out_encoded; spa_log_trace(this->log, "%p: processed fragment %zd used %d", this, out_encoded, this->buffer_used); return 0; } static int flush_buffer(struct impl *this) { spa_log_trace(this->log, "%p: used:%d block_size:%d need_flush:%d", this, this->buffer_used, this->block_size, this->need_flush); if (this->need_flush) return send_buffer(this); return 0; } static int add_data(struct impl *this, const void *data, uint32_t size) { int processed, total = 0; while (size > 0) { processed = encode_buffer(this, data, size); if (processed <= 0) return total > 0 ? total : processed; data = SPA_PTROFF(data, processed, void); size -= processed; total += processed; } return total; } static void enable_flush_timer(struct impl *this, bool enabled) { struct itimerspec ts; if (!enabled) this->next_flush_time = 0; ts.it_value.tv_sec = this->next_flush_time / SPA_NSEC_PER_SEC; ts.it_value.tv_nsec = this->next_flush_time % SPA_NSEC_PER_SEC; ts.it_interval.tv_sec = 0; ts.it_interval.tv_nsec = 0; spa_system_timerfd_settime(this->data_system, this->flush_timerfd, SPA_FD_TIMER_ABSTIME, &ts, NULL); this->flush_pending = enabled; } static int flush_data(struct impl *this, uint64_t now_time) { struct port *port = &this->port; bool is_asha = this->codec->kind == MEDIA_CODEC_ASHA; bool is_sco = this->codec->kind == MEDIA_CODEC_HFP; uint32_t total_frames; int written; int unsent_buffer; spa_assert(this->transport_started); /* I/O in error state? */ if (this->transport == NULL || (!this->flush_source.loop && !is_asha && !is_sco)) return -EIO; if (!this->flush_timer_source.loop && !this->transport->iso_io && !is_asha) return -EIO; if (!this->transport->sco_io && is_sco) return -EIO; if (this->transport->iso_io && !this->iso_pending) return 0; total_frames = 0; again: written = 0; if (this->fragment && !this->need_flush) { int res; this->fragment = false; if ((res = encode_fragment(this)) < 0) { /* Error */ reset_buffer(this); return res; } } while (this->silence_frames && !this->need_flush) { static const uint8_t empty[1024] = {}; uint32_t avail = SPA_MIN(this->silence_frames, sizeof(empty) / port->frame_size) * port->frame_size; written = add_data(this, empty, avail); if (written <= 0) break; this->silence_frames -= written / port->frame_size; spa_log_trace(this->log, "%p: written %d silence frames", this, written / port->frame_size); } while (!spa_list_is_empty(&port->ready) && !this->need_flush) { uint8_t *src; uint32_t n_bytes, n_frames; struct buffer *b; struct spa_data *d; uint32_t index, offs, avail, l0, l1; b = spa_list_first(&port->ready, struct buffer, link); d = b->buf->datas; src = d[0].data; index = d[0].chunk->offset + port->ready_offset; avail = d[0].chunk->size - port->ready_offset; avail /= port->frame_size; offs = index % d[0].maxsize; n_frames = avail; n_bytes = n_frames * port->frame_size; l0 = SPA_MIN(n_bytes, d[0].maxsize - offs); l1 = n_bytes - l0; written = add_data(this, src + offs, l0); if (written > 0 && l1 > 0) written += add_data(this, src, l1); if (written <= 0) { if (written < 0 && written != -ENOSPC) { spa_list_remove(&b->link); SPA_FLAG_SET(b->flags, BUFFER_FLAG_OUT); this->port.io->buffer_id = b->id; spa_log_warn(this->log, "%p: error %s, reuse buffer %u", this, spa_strerror(written), b->id); spa_node_call_reuse_buffer(&this->callbacks, 0, b->id); port->ready_offset = 0; } break; } n_frames = written / port->frame_size; port->ready_offset += written; if (port->ready_offset >= d[0].chunk->size) { spa_list_remove(&b->link); SPA_FLAG_SET(b->flags, BUFFER_FLAG_OUT); spa_log_trace(this->log, "%p: reuse buffer %u", this, b->id); this->port.io->buffer_id = b->id; spa_node_call_reuse_buffer(&this->callbacks, 0, b->id); port->ready_offset = 0; } total_frames += n_frames; spa_log_trace(this->log, "%p: written %u frames", this, total_frames); } if (this->transport->iso_io) { struct spa_bt_iso_io *iso_io = this->transport->iso_io; if (this->need_flush) { size_t avail = SPA_MIN(this->buffer_used, sizeof(iso_io->buf)); uint64_t delay = 0; spa_log_trace(this->log, "%p: ISO put fd:%d size:%u sn:%u ts:%u now:%"PRIu64, this, this->transport->fd, (unsigned)avail, (unsigned)this->seqnum, (unsigned)this->timestamp, iso_io->now); memcpy(iso_io->buf, this->buffer, avail); iso_io->size = avail; iso_io->timestamp = this->timestamp; this->iso_pending = false; reset_buffer(this); if (this->process_rate) { /* Match target delay in media_iso_pull() */ delay = this->process_duration * SPA_NSEC_PER_SEC / this->process_rate; if (delay < iso_io->duration*3/2) delay = iso_io->duration*3/2 - delay; else delay = 0; } update_packet_delay(this, delay); } return 0; } if (is_asha) { struct spa_bt_asha *asha = this->asha; if (this->need_flush && !asha->flush_pending) { /* * For ASHA, we cannot send more than one encoded * packet at a time and can only send them spaced * 20 ms apart which is the ASHA connection interval. * All encoded packets will be 160 bytes + 1 byte * sequence number. * * Unlike the A2DP flow below, we cannot delay the * output by 1 packet. While that might work for the * mono case, for stereo that make the two sides be * out of sync with each other and if the two sides * differ by more than 3 credits, we would have to * drop packets or the devices themselves might drop * the connection. */ memcpy(asha->buf, this->buffer, this->buffer_used); asha->flush_pending = true; reset_buffer(this); } return 0; } if (this->flush_pending) { spa_log_trace(this->log, "%p: wait for flush timer", this); return 0; } /* * Get packet queue size before writing to it. This should be zero to increase * bitpool. Bitpool shouldn't be increased when there is unsent data. */ unsent_buffer = get_transport_unsent_size(this); written = flush_buffer(this); if (written == -EAGAIN) { spa_log_trace(this->log, "%p: fail flush", this); if (now_time - this->last_error > SPA_NSEC_PER_SEC / 2) { int res = 0; if (this->codec->reduce_bitpool) res = this->codec->reduce_bitpool(this->codec_data); spa_log_debug(this->log, "%p: reduce bitpool: %i", this, res); this->last_error = now_time; } /* * The socket buffer is full, and the device is not processing data * fast enough, so should just skip this packet. There will be a sound * glitch in any case. */ written = this->buffer_used; } if (written < 0) { spa_log_trace(this->log, "%p: error flushing %s", this, spa_strerror(written)); reset_buffer(this); enable_flush_timer(this, false); return written; } else if (written > 0) { /* * We cannot write all data we have at once, since this can exceed device * buffers (esp. for the A2DP low-latency codecs) and socket buffers, so * flush needs to be delayed. */ uint32_t packet_samples = this->block_count * this->block_size / port->frame_size; uint64_t packet_time = (uint64_t)packet_samples * SPA_NSEC_PER_SEC / port->current_format.info.raw.rate; if (SPA_LIKELY(this->position)) { uint64_t duration_ns; /* * Flush at the time position of the next buffered sample. */ this->next_flush_time = get_reference_time(this, &duration_ns) + packet_time; /* * We can delay the output by one packet to avoid waiting * for the next buffer and so make send intervals exactly regular. * However, this is not needed for A2DP or BAP. The controller * will do the scheduling for us, and there's also the socket buffer * in between. * * Although in principle this should not be needed, we * do it regardless in case it helps. */ #if 1 this->next_flush_time += SPA_MIN(packet_time, duration_ns * (SPA_MAX(port->n_buffers, 2u) - 2)); #endif } else { if (this->next_flush_time == 0) this->next_flush_time = this->process_time; this->next_flush_time += packet_time; } update_packet_delay(this, packet_time); if (this->need_flush == NEED_FLUSH_FRAGMENT) { reset_buffer(this); this->fragment = true; goto again; } if (now_time - this->last_error > SPA_NSEC_PER_SEC) { if (unsent_buffer == 0) { int res = 0; if (this->codec->increase_bitpool) res = this->codec->increase_bitpool(this->codec_data); spa_log_debug(this->log, "%p: increase bitpool: %i", this, res); } this->last_error = now_time; } spa_log_trace(this->log, "%p: flush at:%"PRIu64" process:%"PRIu64, this, this->next_flush_time, this->process_time); reset_buffer(this); enable_flush_timer(this, true); /* Encode next packet already now; it will be flushed later on timer */ goto again; } else { /* Don't want to flush yet, or failed to write anything */ spa_log_trace(this->log, "%p: skip flush", this); enable_flush_timer(this, false); } return 0; } static void drop_frames(struct impl *this, uint32_t req) { struct port *port = &this->port; if (this->silence_frames > req) { this->silence_frames -= req; req = 0; } else { req -= this->silence_frames; this->silence_frames = 0; } while (req > 0 && !spa_list_is_empty(&port->ready)) { struct buffer *b; struct spa_data *d; uint32_t avail; b = spa_list_first(&port->ready, struct buffer, link); d = b->buf->datas; avail = d[0].chunk->size - port->ready_offset; avail /= port->frame_size; avail = SPA_MIN(avail, req); port->ready_offset += avail * port->frame_size; req -= avail; if (port->ready_offset >= d[0].chunk->size) { spa_list_remove(&b->link); SPA_FLAG_SET(b->flags, BUFFER_FLAG_OUT); spa_log_trace(this->log, "%p: reuse buffer %u", this, b->id); this->port.io->buffer_id = b->id; spa_node_call_reuse_buffer(&this->callbacks, 0, b->id); port->ready_offset = 0; } spa_log_trace(this->log, "%p: skipped %u frames", this, avail); } } static void media_iso_rate_match(struct impl *this) { struct spa_bt_iso_io *iso_io = this->transport ? this->transport->iso_io : NULL; struct port *port = &this->port; const double period = 0.05 * SPA_NSEC_PER_SEC; uint64_t ref_time; uint64_t duration_ns; double value, target, err, max_err; if (!iso_io || !this->transport_started) return; if (this->resync || !this->position) { spa_bt_rate_control_init(&port->ratectl, 0); setup_matching(this); return; } /* * Rate match sample position so that the graph is max(ISO interval*3/2, quantum) * ahead of the time instant we have to send data. * * Being 1 ISO interval ahead is unavoidable otherwise we underrun, and the * rest is safety margin for the graph to deliver data in time. * * This is then the part of the TX latency on PipeWire side. There is * another part of TX latency on kernel/controller side before the * controller starts processing the packet. */ ref_time = get_reference_time(this, &duration_ns); value = (int64_t)iso_io->now - (int64_t)ref_time; if (this->process_rate) target = this->process_duration * SPA_NSEC_PER_SEC / this->process_rate; else target = 0; target = SPA_MAX(target, iso_io->duration*3/2); err = value - target; max_err = SPA_MAX(40 * SPA_NSEC_PER_MSEC, target); if (iso_io->resync && err >= 0) { unsigned int req = (unsigned int)(err * port->current_format.info.raw.rate / SPA_NSEC_PER_SEC); if (req > 0) { spa_bt_rate_control_init(&port->ratectl, 0); drop_frames(this, req); } spa_log_debug(this->log, "%p: ISO sync skip frames:%u", this, req); } else if (iso_io->resync && -err >= 0) { unsigned int req = (unsigned int)(-err * port->current_format.info.raw.rate / SPA_NSEC_PER_SEC); if (req > 0) { spa_bt_rate_control_init(&port->ratectl, 0); this->silence_frames += req; } spa_log_debug(this->log, "%p: ISO sync pad frames:%u", this, req); } else if (err > max_err || -err > max_err) { iso_io->need_resync = true; spa_log_debug(this->log, "%p: ISO sync need resync err:%+.3f", this, err / SPA_NSEC_PER_MSEC); } else { spa_bt_rate_control_update(&port->ratectl, err, 0, duration_ns, period, RATE_CTL_DIFF_MAX); spa_log_trace(this->log, "%p: ISO sync err:%+.3g value:%.6f target:%.6f (ms) corr:%g", this, port->ratectl.avg / SPA_NSEC_PER_MSEC, value / SPA_NSEC_PER_MSEC, target / SPA_NSEC_PER_MSEC, port->ratectl.corr); } iso_io->resync = false; } static void media_iso_pull(struct spa_bt_iso_io *iso_io) { struct impl *this = iso_io->user_data; this->iso_pending = true; flush_data(this, this->current_time); } static void media_on_flush_error(struct spa_source *source) { struct impl *this = source->data; if (source->rmask & SPA_IO_ERR) { /* TX timestamp info? */ if (this->transport && this->transport->iso_io) { if (spa_bt_iso_io_recv_errqueue(this->transport->iso_io) == 0) return; } else { if (spa_bt_latency_recv_errqueue(&this->tx_latency, this->flush_source.fd, this->log) == 0) return; } /* Otherwise: actual error */ } spa_log_trace(this->log, "%p: flush event", this); if (source->rmask & (SPA_IO_HUP | SPA_IO_ERR)) { spa_log_warn(this->log, "%p: connection (%s) terminated unexpectedly", this, this->transport ? this->transport->path : ""); if (this->flush_source.loop) { spa_bt_latency_flush(&this->tx_latency, this->flush_source.fd, this->log); spa_loop_remove_source(this->data_loop, &this->flush_source); } enable_flush_timer(this, false); if (this->flush_timer_source.loop) spa_loop_remove_source(this->data_loop, &this->flush_timer_source); if (this->transport && this->transport->iso_io) spa_bt_iso_io_set_cb(this->transport->iso_io, NULL, NULL); return; } } static void media_on_flush_timeout(struct spa_source *source) { struct impl *this = source->data; uint64_t exp; int res; spa_log_trace(this->log, "%p: flush on timeout", this); if ((res = spa_system_timerfd_read(this->data_system, this->flush_timerfd, &exp)) < 0) { if (res != -EAGAIN) spa_log_warn(this->log, "error reading timerfd: %s", spa_strerror(res)); return; } if (this->transport == NULL) { enable_flush_timer(this, false); return; } while (exp-- > 0) { this->flush_pending = false; flush_data(this, this->current_time); } } static void media_on_timeout(struct spa_source *source) { struct impl *this = source->data; struct port *port = &this->port; uint64_t exp, duration; uint32_t rate; struct spa_io_buffers *io = port->io; uint64_t prev_time, now_time; int status, res; if (this->started) { if ((res = spa_system_timerfd_read(this->data_system, this->timerfd, &exp)) < 0) { if (res != -EAGAIN) spa_log_warn(this->log, "error reading timerfd: %s", spa_strerror(res)); return; } } prev_time = this->current_time; now_time = this->current_time = this->next_time; spa_log_debug(this->log, "%p: timer %"PRIu64" %"PRIu64"", this, now_time, now_time - prev_time); if (SPA_LIKELY(this->position)) { duration = this->position->clock.target_duration; rate = this->position->clock.target_rate.denom; } else { duration = 1024; rate = 48000; } setup_matching(this); this->next_time = (uint64_t)(now_time + duration * SPA_NSEC_PER_SEC / rate * port->ratectl.corr); if (SPA_LIKELY(this->clock)) { this->clock->nsec = now_time; this->clock->rate = this->clock->target_rate; this->clock->position += this->clock->duration; this->clock->duration = duration; this->clock->rate_diff = 1 / port->ratectl.corr; this->clock->next_nsec = this->next_time; this->clock->delay = 0; } status = this->transport_started ? SPA_STATUS_NEED_DATA : SPA_STATUS_HAVE_DATA; spa_log_trace(this->log, "%p: %d -> %d", this, io->status, status); io->status = status; io->buffer_id = SPA_ID_INVALID; spa_node_call_ready(&this->callbacks, status); set_timeout(this, this->next_time); } static uint64_t asha_seqnum(struct impl *this) { uint64_t tn = get_reference_time(this, NULL); uint64_t dt = tn - this->asha->ref_t0; uint64_t num_packets = (dt + ASHA_CONN_INTERVAL / 2) / ASHA_CONN_INTERVAL; spa_log_trace(this->log, "%" PRIu64 " - %" PRIu64 " / 20ms = %"PRIu64, tn, this->asha->ref_t0, num_packets); if (this->asha->ref_t0 > tn) return 0; return num_packets % 256; } static void media_asha_flush_timeout(struct spa_source *source) { struct impl *this = source->data; struct port *port = &this->port; struct spa_bt_asha *asha = this->asha; const char *address = this->transport->device->address; struct timespec ts; int res, written; uint64_t exp, now; if (this->started) { if ((res = spa_system_timerfd_read(this->data_system, asha->timerfd, &exp)) < 0) { if (res != -EAGAIN) spa_log_warn(this->log, "error reading ASHA timerfd: %s", spa_strerror(res)); return; } } spa_system_clock_gettime(this->data_system, CLOCK_MONOTONIC, &ts); now = SPA_TIMESPEC_TO_NSEC(&ts); asha->next_time += (uint64_t)(ASHA_CONN_INTERVAL * port->ratectl.corr); if (asha->flush_pending) { asha->buf[0] = this->seqnum; written = send(asha->flush_source.fd, asha->buf, ASHA_ENCODED_PKT_SZ, MSG_DONTWAIT | MSG_NOSIGNAL); /* * For ASHA, when we are out of LE credits and cannot write to * the socket, return value of `send` will be -EAGAIN. */ if (written < 0) { asha->flush_pending = false; spa_log_warn(this->log, "%p: ASHA failed to flush %d seqnum on timer for %s, written:%d", this, this->seqnum, address, -errno); goto skip_flush; } if (written > 0) { asha->flush_pending = false; spa_log_trace(this->log, "%p: ASHA flush %d seqnum for %s, ts:%u", this, this->seqnum, address, this->timestamp); } } this->seqnum = asha_seqnum(this); flush_data(this, now); skip_flush: set_asha_timeout(this, asha->next_time); } static void media_asha_cb(struct spa_source *source) { struct impl *this = source->data; struct spa_bt_asha *asha = this->asha; const char *address = this->transport->device->address; if (source->rmask & (SPA_IO_HUP | SPA_IO_ERR)) { spa_log_error(this->log, "%p: ASHA source error %d on %s", this, source->rmask, address); if (asha->flush_source.loop) spa_loop_remove_source(this->data_loop, &asha->flush_source); return; } } static int do_start_transport(struct spa_loop *loop, bool async, uint32_t seq, const void *data, size_t size, void *user_data) { struct impl *this = user_data; this->transport_started = true; if (this->transport->iso_io) spa_bt_iso_io_set_cb(this->transport->iso_io, media_iso_pull, this); return 0; } static int transport_start(struct impl *this) { int val, size; struct port *port; socklen_t len; uint8_t *conf; uint32_t flags; bool is_asha; bool is_sco; if (this->transport_started) return 0; if (!this->start_ready) return -EIO; spa_return_val_if_fail(this->transport, -EIO); spa_log_debug(this->log, "%p: start transport", this); port = &this->port; conf = this->transport->configuration; size = this->transport->configuration_len; is_asha = this->codec->kind == MEDIA_CODEC_ASHA; is_sco = this->codec->kind == MEDIA_CODEC_HFP; spa_log_debug(this->log, "Transport configuration:"); spa_debug_log_mem(this->log, SPA_LOG_LEVEL_DEBUG, 2, conf, (size_t)size); flags = this->is_duplex ? MEDIA_CODEC_FLAG_SINK : 0; if (!this->transport->iso_io) { this->own_codec_data = true; this->codec_data = this->codec->init(this->codec, flags, this->transport->configuration, this->transport->configuration_len, &port->current_format, this->codec_props, this->transport->write_mtu); if (this->codec_data == NULL) { spa_log_error(this->log, "%p: codec %s initialization failed", this, this->codec->description); return -EIO; } } else { this->own_codec_data = false; this->codec_data = this->transport->iso_io->codec_data; this->codec_props_changed = true; } this->encoder_delay = 0; if (this->codec->get_delay) this->codec->get_delay(this->codec_data, &this->encoder_delay, NULL); const char *codec_profile = media_codec_kind_str(this->codec); spa_log_info(this->log, "%p: using %s codec %s, delay:%.2f ms, codec-delay:%.2f ms", this, codec_profile, this->codec->description, (double)spa_bt_transport_get_delay_nsec(this->transport) / SPA_NSEC_PER_MSEC, (double)this->encoder_delay * SPA_MSEC_PER_SEC / port->current_format.info.raw.rate); this->seqnum = UINT16_MAX; this->block_size = this->codec->get_block_size(this->codec_data); if (this->block_size > sizeof(this->tmp_buffer)) { spa_log_error(this->log, "block-size %d > %zu", this->block_size, sizeof(this->tmp_buffer)); goto fail; } spa_log_debug(this->log, "%p: block_size %d", this, this->block_size); val = this->codec->send_buf_size > 0 /* The kernel doubles the SO_SNDBUF option value set by setsockopt(). */ ? this->codec->send_buf_size / 2 + this->codec->send_buf_size % 2 : FILL_FRAMES * this->transport->write_mtu; if (setsockopt(this->transport->fd, SOL_SOCKET, SO_SNDBUF, &val, sizeof(val)) < 0) spa_log_warn(this->log, "%p: SO_SNDBUF %m", this); len = sizeof(val); if (getsockopt(this->transport->fd, SOL_SOCKET, SO_SNDBUF, &val, &len) < 0) { spa_log_warn(this->log, "%p: SO_SNDBUF %m", this); } else { spa_log_debug(this->log, "%p: SO_SNDBUF: %d", this, val); } this->fd_buffer_size = val; val = 6; if (setsockopt(this->transport->fd, SOL_SOCKET, SO_PRIORITY, &val, sizeof(val)) < 0) spa_log_warn(this->log, "SO_PRIORITY failed: %m"); reset_buffer(this); spa_bt_rate_control_init(&port->ratectl, 0); this->update_delay_event = spa_loop_utils_add_event(this->loop_utils, update_delay_event, this); spa_zero(this->tx_latency); if (is_sco) { int res; if ((res = spa_bt_transport_ensure_sco_io(this->transport, this->data_loop, this->data_system)) < 0) goto fail; spa_bt_sco_io_write_start(this->transport->sco_io); } if (!this->transport->iso_io && !is_asha) { this->flush_timer_source.data = this; this->flush_timer_source.fd = this->flush_timerfd; this->flush_timer_source.func = media_on_flush_timeout; this->flush_timer_source.mask = SPA_IO_IN; this->flush_timer_source.rmask = 0; spa_loop_add_source(this->data_loop, &this->flush_timer_source); if (!is_sco) spa_bt_latency_init(&this->tx_latency, this->transport, LATENCY_PERIOD, this->log); } if (!is_asha && !is_sco) { this->flush_source.data = this; this->flush_source.fd = this->transport->fd; this->flush_source.func = media_on_flush_error; this->flush_source.mask = SPA_IO_ERR | SPA_IO_HUP; this->flush_source.rmask = 0; spa_loop_add_source(this->data_loop, &this->flush_source); } this->resync = 0; this->flush_pending = false; this->iso_pending = false; spa_loop_locked(this->data_loop, do_start_transport, 0, NULL, 0, this); if (is_asha) { struct spa_bt_asha *asha = this->asha; asha->flush_pending = false; asha->set_timer = false; asha->timer_source.data = this; asha->timer_source.fd = this->asha->timerfd; asha->timer_source.func = media_asha_flush_timeout; asha->timer_source.mask = SPA_IO_IN; asha->timer_source.rmask = 0; spa_loop_add_source(this->data_loop, &asha->timer_source); asha->flush_source.data = this; asha->flush_source.fd = this->transport->fd; asha->flush_source.func = media_asha_cb; asha->flush_source.mask = SPA_IO_ERR | SPA_IO_HUP; asha->flush_source.rmask = 0; spa_loop_add_source(this->data_loop, &asha->flush_source); spa_list_append(&asha_sinks, &this->asha_link); } set_latency(this, true); return 0; fail: if (this->codec_data) { if (this->own_codec_data) this->codec->deinit(this->codec_data); this->own_codec_data = false; this->codec_data = NULL; } return -EIO; } static int do_start(struct impl *this) { struct port *port = &this->port; int res; if (this->started) return 0; spa_return_val_if_fail(this->transport, -EIO); this->following = is_following(this); spa_log_debug(this->log, "%p: start following:%d", this, this->following); this->start_ready = true; bool do_accept = this->transport->profile & SPA_BT_PROFILE_HEADSET_AUDIO_GATEWAY; if ((res = spa_bt_transport_acquire(this->transport, do_accept)) < 0) { this->start_ready = false; return res; } this->packet_delay_ns = 0; this->source.data = this; this->source.fd = this->timerfd; this->source.func = media_on_timeout; this->source.mask = SPA_IO_IN; this->source.rmask = 0; spa_loop_add_source(this->data_loop, &this->source); spa_bt_rate_control_init(&port->ratectl, 0); setup_matching(this); set_timers(this); this->started = true; return 0; } static int do_remove_source(struct spa_loop *loop, bool async, uint32_t seq, const void *data, size_t size, void *user_data) { struct impl *this = user_data; if (this->source.loop) spa_loop_remove_source(this->data_loop, &this->source); set_timeout(this, 0); if (this->update_delay_event) { spa_loop_utils_destroy_source(this->loop_utils, this->update_delay_event); this->update_delay_event = NULL; } return 0; } static int do_remove_transport_source(struct spa_loop *loop, bool async, uint32_t seq, const void *data, size_t size, void *user_data) { struct impl *this = user_data; this->transport_started = false; if (this->flush_source.loop) { spa_bt_latency_flush(&this->tx_latency, this->flush_source.fd, this->log); spa_loop_remove_source(this->data_loop, &this->flush_source); } if (this->flush_timer_source.loop) spa_loop_remove_source(this->data_loop, &this->flush_timer_source); if (this->codec->kind == MEDIA_CODEC_ASHA) { if (this->asha->timer_source.loop) spa_loop_remove_source(this->data_loop, &this->asha->timer_source); if (this->asha->flush_source.loop) spa_loop_remove_source(this->data_loop, &this->asha->flush_source); spa_list_remove(&this->asha_link); } enable_flush_timer(this, false); if (this->transport->iso_io) spa_bt_iso_io_set_cb(this->transport->iso_io, NULL, NULL); /* Drop queued data */ drop_frames(this, UINT32_MAX); return 0; } static void transport_stop(struct impl *this) { if (!this->transport_started) return; spa_log_trace(this->log, "%p: stop transport", this); spa_loop_locked(this->data_loop, do_remove_transport_source, 0, NULL, 0, this); if (this->codec_data && this->own_codec_data) this->codec->deinit(this->codec_data); this->codec_data = NULL; } static int do_stop(struct impl *this) { int res = 0; if (!this->started) return 0; spa_log_debug(this->log, "%p: stop", this); this->start_ready = false; spa_loop_locked(this->data_loop, do_remove_source, 0, NULL, 0, this); transport_stop(this); if (this->transport) res = spa_bt_transport_release(this->transport); this->started = false; return res; } static int impl_node_send_command(void *object, const struct spa_command *command) { struct impl *this = object; struct port *port; int res; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(command != NULL, -EINVAL); port = &this->port; switch (SPA_NODE_COMMAND_ID(command)) { case SPA_NODE_COMMAND_Start: if (!port->have_format) return -EIO; if (port->n_buffers == 0) return -EIO; if ((res = do_start(this)) < 0) return res; break; case SPA_NODE_COMMAND_Suspend: case SPA_NODE_COMMAND_Pause: if ((res = do_stop(this)) < 0) return res; break; default: return -ENOTSUP; } return 0; } static void emit_node_info(struct impl *this, bool full) { char node_group_buf[256]; char *node_group = NULL; const char *media_role = NULL; const char *codec_profile = media_codec_kind_str(this->codec); if (this->transport && (this->transport->profile & SPA_BT_PROFILE_BAP_SINK)) { spa_scnprintf(node_group_buf, sizeof(node_group_buf), "[\"bluez-iso-%s-cig-%d\"]", this->transport->device->adapter->address, this->transport->bap_cig); node_group = node_group_buf; } else if (this->transport && (this->transport->profile & SPA_BT_PROFILE_BAP_BROADCAST_SINK)) { spa_scnprintf(node_group_buf, sizeof(node_group_buf), "[\"bluez-iso-%s-big-%d\"]", this->transport->device->adapter->address, this->transport->bap_big); node_group = node_group_buf; } else if (this->transport && (this->transport->profile & SPA_BT_PROFILE_ASHA_SINK)) { spa_scnprintf(node_group_buf, sizeof(node_group_buf), "[\"bluez-asha-%" PRIu64 "d\"]", this->transport->hisyncid); node_group = node_group_buf; } if (!this->is_output && this->transport && (this->transport->profile & SPA_BT_PROFILE_HEADSET_AUDIO_GATEWAY)) media_role = "Communication"; struct spa_dict_item node_info_items[] = { { SPA_KEY_DEVICE_API, "bluez5" }, { SPA_KEY_MEDIA_CLASS, this->is_internal ? "Audio/Sink/Internal" : this->is_output ? "Audio/Sink" : "Stream/Input/Audio" }, { "media.name", ((this->transport && this->transport->device->name) ? this->transport->device->name : codec_profile ) }, { SPA_KEY_NODE_DRIVER, this->is_output ? "true" : "false" }, { "node.group", node_group }, { SPA_KEY_MEDIA_ROLE, media_role }, }; uint64_t old = full ? this->info.change_mask : 0; if (full) this->info.change_mask = this->info_all; if (this->info.change_mask) { this->info.props = &SPA_DICT_INIT_ARRAY(node_info_items); spa_node_emit_info(&this->hooks, &this->info); this->info.change_mask = old; } } static void emit_port_info(struct impl *this, struct port *port, bool full) { uint64_t old = full ? port->info.change_mask : 0; if (full) port->info.change_mask = port->info_all; if (port->info.change_mask) { spa_node_emit_port_info(&this->hooks, SPA_DIRECTION_INPUT, 0, &port->info); port->info.change_mask = old; } } static int impl_node_add_listener(void *object, struct spa_hook *listener, const struct spa_node_events *events, void *data) { struct impl *this = object; struct spa_hook_list save; spa_return_val_if_fail(this != NULL, -EINVAL); spa_hook_list_isolate(&this->hooks, &save, listener, events, data); emit_node_info(this, true); emit_port_info(this, &this->port, true); spa_hook_list_join(&this->hooks, &save); return 0; } static int impl_node_set_callbacks(void *object, const struct spa_node_callbacks *callbacks, void *data) { struct impl *this = object; spa_return_val_if_fail(this != NULL, -EINVAL); this->callbacks = SPA_CALLBACKS_INIT(callbacks, data); return 0; } static int impl_node_sync(void *object, int seq) { struct impl *this = object; spa_return_val_if_fail(this != NULL, -EINVAL); spa_node_emit_result(&this->hooks, seq, 0, 0, NULL); return 0; } static int impl_node_add_port(void *object, enum spa_direction direction, uint32_t port_id, const struct spa_dict *props) { return -ENOTSUP; } static int impl_node_remove_port(void *object, enum spa_direction direction, uint32_t port_id) { return -ENOTSUP; } static int impl_node_port_enum_params(void *object, int seq, enum spa_direction direction, uint32_t port_id, uint32_t id, uint32_t start, uint32_t num, const struct spa_pod *filter) { struct impl *this = object; struct port *port; struct spa_pod *param; struct spa_pod_builder b = { 0 }; uint8_t buffer[1024]; struct spa_result_node_params result; uint32_t count = 0; int res; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(num != 0, -EINVAL); spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); port = &this->port; result.id = id; result.next = start; next: result.index = result.next++; spa_pod_builder_init(&b, buffer, sizeof(buffer)); switch (id) { case SPA_PARAM_EnumFormat: if (this->codec == NULL) return -EIO; if (this->transport == NULL) return -EIO; if ((res = this->codec->enum_config(this->codec, this->is_duplex ? MEDIA_CODEC_FLAG_SINK : 0, this->transport->configuration, this->transport->configuration_len, id, result.index, &b, ¶m)) != 1) return res; break; case SPA_PARAM_Format: if (!port->have_format) return -EIO; if (result.index > 0) return 0; param = spa_format_audio_raw_build(&b, id, &port->current_format.info.raw); break; case SPA_PARAM_Buffers: if (!port->have_format) return -EIO; if (result.index > 0) return 0; param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamBuffers, id, SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int( MIN_BUFFERS, MIN_BUFFERS, MAX_BUFFERS), SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(1), SPA_PARAM_BUFFERS_size, SPA_POD_CHOICE_RANGE_Int( this->quantum_limit * port->frame_size, 16 * port->frame_size, INT32_MAX), SPA_PARAM_BUFFERS_stride, SPA_POD_Int(port->frame_size)); break; case SPA_PARAM_Meta: switch (result.index) { case 0: param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamMeta, id, SPA_PARAM_META_type, SPA_POD_Id(SPA_META_Header), SPA_PARAM_META_size, SPA_POD_Int(sizeof(struct spa_meta_header))); break; default: return 0; } break; case SPA_PARAM_IO: switch (result.index) { case 0: param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamIO, id, SPA_PARAM_IO_id, SPA_POD_Id(SPA_IO_Buffers), SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_buffers))); break; case 1: if (this->codec->kind != MEDIA_CODEC_BAP) return 0; param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamIO, id, SPA_PARAM_IO_id, SPA_POD_Id(SPA_IO_RateMatch), SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_rate_match))); break; default: return 0; } break; case SPA_PARAM_Latency: switch (result.index) { case 0: param = spa_latency_build(&b, id, &port->latency); break; default: return 0; } break; default: return -ENOENT; } if (spa_pod_filter(&b, &result.param, param, filter) < 0) goto next; spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); if (++count != num) goto next; return 0; } static int clear_buffers(struct impl *this, struct port *port) { do_stop(this); if (port->n_buffers > 0) { spa_list_init(&port->ready); port->n_buffers = 0; } return 0; } static int port_set_format(struct impl *this, struct port *port, uint32_t flags, const struct spa_pod *format) { int err; if (format == NULL) { spa_log_debug(this->log, "clear format"); clear_buffers(this, port); port->have_format = false; } else { struct spa_audio_info info = { 0 }; if ((err = spa_format_parse(format, &info.media_type, &info.media_subtype)) < 0) return err; if (info.media_type != SPA_MEDIA_TYPE_audio || info.media_subtype != SPA_MEDIA_SUBTYPE_raw) return -EINVAL; if (spa_format_audio_raw_parse(format, &info.info.raw) < 0) return -EINVAL; if (info.info.raw.rate == 0 || info.info.raw.channels == 0 || info.info.raw.channels > MAX_CHANNELS) return -EINVAL; if (this->transport && this->transport->iso_io) { if (memcmp(&info.info.raw, &this->transport->iso_io->format.info.raw, sizeof(info.info.raw))) { spa_log_error(this->log, "unexpected incompatible " "BAP audio format"); return -EINVAL; } } port->frame_size = info.info.raw.channels; switch (info.info.raw.format) { case SPA_AUDIO_FORMAT_S16_LE: case SPA_AUDIO_FORMAT_S16_BE: port->frame_size *= 2; break; case SPA_AUDIO_FORMAT_S24: port->frame_size *= 3; break; case SPA_AUDIO_FORMAT_S24_32: case SPA_AUDIO_FORMAT_S32: case SPA_AUDIO_FORMAT_F32: port->frame_size *= 4; break; default: return -EINVAL; } port->current_format = info; port->have_format = true; } set_latency(this, false); port->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; if (port->have_format) { port->info.change_mask |= SPA_PORT_CHANGE_MASK_RATE; port->info.rate = SPA_FRACTION(1, port->current_format.info.raw.rate); port->params[IDX_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_READWRITE); port->params[IDX_Buffers] = SPA_PARAM_INFO(SPA_PARAM_Buffers, SPA_PARAM_INFO_READ); port->params[IDX_Latency].flags ^= SPA_PARAM_INFO_SERIAL; } else { port->params[IDX_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); port->params[IDX_Buffers] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); } emit_port_info(this, port, false); return 0; } static int impl_node_port_set_param(void *object, enum spa_direction direction, uint32_t port_id, uint32_t id, uint32_t flags, const struct spa_pod *param) { struct impl *this = object; struct port *port; int res; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(CHECK_PORT(node, direction, port_id), -EINVAL); port = &this->port; switch (id) { case SPA_PARAM_Format: res = port_set_format(this, port, flags, param); break; case SPA_PARAM_Latency: res = 0; break; default: res = -ENOENT; break; } return res; } static int impl_node_port_use_buffers(void *object, enum spa_direction direction, uint32_t port_id, uint32_t flags, struct spa_buffer **buffers, uint32_t n_buffers) { struct impl *this = object; struct port *port; uint32_t i; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); port = &this->port; spa_log_debug(this->log, "%p: use buffers %d", this, n_buffers); clear_buffers(this, port); if (n_buffers > 0 && !port->have_format) return -EIO; if (n_buffers > MAX_BUFFERS) return -ENOSPC; for (i = 0; i < n_buffers; i++) { struct buffer *b = &port->buffers[i]; b->buf = buffers[i]; b->id = i; SPA_FLAG_SET(b->flags, BUFFER_FLAG_OUT); b->h = spa_buffer_find_meta_data(buffers[i], SPA_META_Header, sizeof(*b->h)); if (buffers[i]->datas[0].data == NULL) { spa_log_error(this->log, "%p: need mapped memory", this); return -EINVAL; } } port->n_buffers = n_buffers; return 0; } static int impl_node_port_set_io(void *object, enum spa_direction direction, uint32_t port_id, uint32_t id, void *data, size_t size) { struct impl *this = object; struct port *port; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); port = &this->port; switch (id) { case SPA_IO_Buffers: port->io = data; break; case SPA_IO_RateMatch: if (this->codec->kind != MEDIA_CODEC_BAP) return -ENOENT; port->rate_match = data; break; default: return -ENOENT; } return 0; } static int impl_node_port_reuse_buffer(void *object, uint32_t port_id, uint32_t buffer_id) { return -ENOTSUP; } static int impl_node_process(void *object) { struct impl *this = object; struct port *port; struct spa_io_buffers *io; int res; spa_return_val_if_fail(this != NULL, -EINVAL); port = &this->port; if ((io = port->io) == NULL) return -EIO; if (this->position && this->position->clock.flags & SPA_IO_CLOCK_FLAG_FREEWHEEL) { io->status = SPA_STATUS_NEED_DATA; return SPA_STATUS_HAVE_DATA; } if (!this->started || !this->transport_started) { if (io->status != SPA_STATUS_HAVE_DATA) { io->status = SPA_STATUS_HAVE_DATA; io->buffer_id = SPA_ID_INVALID; } return SPA_STATUS_HAVE_DATA; } if (io->status == SPA_STATUS_HAVE_DATA && io->buffer_id < port->n_buffers) { struct buffer *b = &port->buffers[io->buffer_id]; struct spa_data *d = b->buf->datas; unsigned int frames; if (!SPA_FLAG_IS_SET(b->flags, BUFFER_FLAG_OUT)) { spa_log_warn(this->log, "%p: buffer %u in use", this, io->buffer_id); io->status = -EINVAL; return -EINVAL; } frames = d ? d[0].chunk->size / port->frame_size : 0; spa_log_trace(this->log, "%p: queue buffer %u frames:%u", this, io->buffer_id, frames); spa_list_append(&port->ready, &b->link); SPA_FLAG_CLEAR(b->flags, BUFFER_FLAG_OUT); io->buffer_id = SPA_ID_INVALID; io->status = SPA_STATUS_OK; } if (this->following) { if (this->position) { this->current_time = this->position->clock.nsec; } else { struct timespec now; spa_system_clock_gettime(this->data_system, CLOCK_MONOTONIC, &now); this->current_time = SPA_TIMESPEC_TO_NSEC(&now); } } /* Make copies of current position values, so that they can be used later at any * time without shared memory races */ if (this->position) { this->process_duration = this->position->clock.duration; this->process_rate = this->position->clock.rate.denom; this->process_rate_diff = this->position->clock.rate_diff; } else { this->process_duration = 1024; this->process_rate = 48000; this->process_rate_diff = 1.0; } this->process_time = this->current_time; if (this->resync) --this->resync; setup_matching(this); media_iso_rate_match(this); if (this->codec->kind == MEDIA_CODEC_ASHA && !this->asha->set_timer) { struct impl *other = find_other_asha(this); if (other && other->asha->ref_t0 != 0) { this->asha->ref_t0 = other->asha->ref_t0; this->seqnum = asha_seqnum(this); set_asha_timer(this, other); } else { this->asha->ref_t0 = get_reference_time(this, NULL); this->seqnum = 0; set_asha_timer(this, NULL); } this->asha->set_timer = true; } spa_log_trace(this->log, "%p: on process time:%"PRIu64, this, this->process_time); if ((res = flush_data(this, this->current_time)) < 0) { io->status = res; return SPA_STATUS_STOPPED; } return SPA_STATUS_HAVE_DATA; } static const struct spa_node_methods impl_node = { SPA_VERSION_NODE_METHODS, .add_listener = impl_node_add_listener, .set_callbacks = impl_node_set_callbacks, .sync = impl_node_sync, .enum_params = impl_node_enum_params, .set_param = impl_node_set_param, .set_io = impl_node_set_io, .send_command = impl_node_send_command, .add_port = impl_node_add_port, .remove_port = impl_node_remove_port, .port_enum_params = impl_node_port_enum_params, .port_set_param = impl_node_port_set_param, .port_use_buffers = impl_node_port_use_buffers, .port_set_io = impl_node_port_set_io, .port_reuse_buffer = impl_node_port_reuse_buffer, .process = impl_node_process, }; static void transport_delay_changed(void *data) { struct impl *this = data; spa_log_debug(this->log, "transport %p delay changed", this->transport); set_latency(this, true); } static int do_transport_destroy(struct spa_loop *loop, bool async, uint32_t seq, const void *data, size_t size, void *user_data) { struct impl *this = user_data; this->transport = NULL; return 0; } static void transport_destroy(void *data) { struct impl *this = data; spa_log_debug(this->log, "transport %p destroy", this->transport); spa_loop_locked(this->data_loop, do_transport_destroy, 0, NULL, 0, this); } static void transport_state_changed(void *data, enum spa_bt_transport_state old, enum spa_bt_transport_state state) { struct impl *this = data; bool was_started = this->transport_started; spa_log_debug(this->log, "%p: transport %p state %d->%d", this, this->transport, old, state); if (state == SPA_BT_TRANSPORT_STATE_ACTIVE) transport_start(this); else transport_stop(this); if (state < SPA_BT_TRANSPORT_STATE_ACTIVE && was_started && !this->is_duplex && this->is_output) { /* * If establishing connection fails due to remote end not activating * the transport, we won't get a write error, but instead see a transport * state change. * * Treat this as a transport error, so that upper levels don't try to * retry too often. */ spa_log_debug(this->log, "%p: transport %p becomes inactive: stop and indicate error", this, this->transport); spa_bt_transport_set_state(this->transport, SPA_BT_TRANSPORT_STATE_ERROR); return; } if (state == SPA_BT_TRANSPORT_STATE_ERROR) { uint8_t buffer[1024]; struct spa_pod_builder b = { 0 }; spa_pod_builder_init(&b, buffer, sizeof(buffer)); spa_node_emit_event(&this->hooks, spa_pod_builder_add_object(&b, SPA_TYPE_EVENT_Node, SPA_NODE_EVENT_Error)); } } static const struct spa_bt_transport_events transport_events = { SPA_VERSION_BT_TRANSPORT_EVENTS, .delay_changed = transport_delay_changed, .state_changed = transport_state_changed, .destroy = transport_destroy, }; static int impl_get_interface(struct spa_handle *handle, const char *type, void **interface) { struct impl *this; spa_return_val_if_fail(handle != NULL, -EINVAL); spa_return_val_if_fail(interface != NULL, -EINVAL); this = (struct impl *) handle; if (spa_streq(type, SPA_TYPE_INTERFACE_Node)) *interface = &this->node; else return -ENOENT; return 0; } static int impl_clear(struct spa_handle *handle) { struct impl *this = (struct impl *) handle; do_stop(this); if (this->codec_props && this->codec->clear_props) this->codec->clear_props(this->codec_props); if (this->transport) spa_hook_remove(&this->transport_listener); spa_system_close(this->data_system, this->timerfd); spa_system_close(this->data_system, this->flush_timerfd); if (this->codec->kind == MEDIA_CODEC_ASHA) { spa_system_close(this->data_system, this->asha->timerfd); free(this->asha); } return 0; } static size_t impl_get_size(const struct spa_handle_factory *factory, const struct spa_dict *params) { return sizeof(struct impl); } static int impl_init(const struct spa_handle_factory *factory, struct spa_handle *handle, const struct spa_dict *info, const struct spa_support *support, uint32_t n_support) { struct impl *this; struct port *port; const char *str; spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(handle != NULL, -EINVAL); handle->get_interface = impl_get_interface; handle->clear = impl_clear; this = (struct impl *) handle; this->log = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log); this->data_loop = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_DataLoop); this->data_system = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_DataSystem); this->loop_utils = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_LoopUtils); spa_log_topic_init(this->log, &log_topic); if (this->data_loop == NULL) { spa_log_error(this->log, "a data loop is needed"); return -EINVAL; } if (this->data_system == NULL) { spa_log_error(this->log, "a data system is needed"); return -EINVAL; } if (this->loop_utils == NULL) { spa_log_error(this->log, "loop utils are needed"); return -EINVAL; } this->node.iface = SPA_INTERFACE_INIT( SPA_TYPE_INTERFACE_Node, SPA_VERSION_NODE, &impl_node, this); spa_hook_list_init(&this->hooks); this->info_all = SPA_NODE_CHANGE_MASK_FLAGS | SPA_NODE_CHANGE_MASK_PARAMS | SPA_NODE_CHANGE_MASK_PROPS; this->info = SPA_NODE_INFO_INIT(); this->info.max_input_ports = 1; this->info.max_output_ports = 0; this->info.flags = SPA_NODE_FLAG_RT; this->params[IDX_PropInfo] = SPA_PARAM_INFO(SPA_PARAM_PropInfo, SPA_PARAM_INFO_READ); this->params[IDX_Props] = SPA_PARAM_INFO(SPA_PARAM_Props, SPA_PARAM_INFO_READWRITE); this->info.params = this->params; this->info.n_params = N_NODE_PARAMS; port = &this->port; port->info_all = SPA_PORT_CHANGE_MASK_FLAGS | SPA_PORT_CHANGE_MASK_PARAMS; port->info = SPA_PORT_INFO_INIT(); port->info.flags = SPA_PORT_FLAG_LIVE | SPA_PORT_FLAG_PHYSICAL | SPA_PORT_FLAG_TERMINAL; port->params[IDX_EnumFormat] = SPA_PARAM_INFO(SPA_PARAM_EnumFormat, SPA_PARAM_INFO_READ); port->params[IDX_Meta] = SPA_PARAM_INFO(SPA_PARAM_Meta, SPA_PARAM_INFO_READ); port->params[IDX_IO] = SPA_PARAM_INFO(SPA_PARAM_IO, SPA_PARAM_INFO_READ); port->params[IDX_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); port->params[IDX_Buffers] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); port->params[IDX_Latency] = SPA_PARAM_INFO(SPA_PARAM_Latency, SPA_PARAM_INFO_READWRITE); port->info.params = port->params; port->info.n_params = N_PORT_PARAMS; port->latency = SPA_LATENCY_INFO(SPA_DIRECTION_INPUT); spa_list_init(&port->ready); this->quantum_limit = 8192; if (info && (str = spa_dict_lookup(info, "clock.quantum-limit"))) spa_atou32(str, &this->quantum_limit, 0); if (info && (str = spa_dict_lookup(info, "api.bluez5.a2dp-duplex")) != NULL) this->is_duplex = spa_atob(str); if (info && (str = spa_dict_lookup(info, "api.bluez5.internal")) != NULL) this->is_internal = spa_atob(str); if (info && (str = spa_dict_lookup(info, SPA_KEY_API_BLUEZ5_TRANSPORT))) sscanf(str, "pointer:%p", &this->transport); if (this->transport == NULL) { spa_log_error(this->log, "a transport is needed"); return -EINVAL; } if (this->transport->media_codec == NULL) { spa_log_error(this->log, "a transport codec is needed"); return -EINVAL; } this->codec = this->transport->media_codec; if (this->is_duplex) { if (!this->codec->duplex_codec) { spa_log_error(this->log, "transport codec doesn't support duplex"); return -EINVAL; } this->codec = this->codec->duplex_codec; } if (this->codec->init_props != NULL) this->codec_props = this->codec->init_props(this->codec, this->is_duplex ? MEDIA_CODEC_FLAG_SINK : 0, this->transport->device->settings); if (this->codec->kind == MEDIA_CODEC_BAP) this->is_output = this->transport->bap_initiator; else if (this->transport->profile & SPA_BT_PROFILE_HEADSET_AUDIO_GATEWAY) this->is_output = false; else this->is_output = true; reset_props(this, &this->props); set_latency(this, false); spa_bt_transport_add_listener(this->transport, &this->transport_listener, &transport_events, this); this->timerfd = spa_system_timerfd_create(this->data_system, CLOCK_MONOTONIC, SPA_FD_CLOEXEC | SPA_FD_NONBLOCK); this->flush_timerfd = spa_system_timerfd_create(this->data_system, CLOCK_MONOTONIC, SPA_FD_CLOEXEC | SPA_FD_NONBLOCK); if (this->codec->kind == MEDIA_CODEC_ASHA) { this->asha = calloc(1, sizeof(struct spa_bt_asha)); if (this->asha == NULL) return -ENOMEM; this->asha->timerfd = spa_system_timerfd_create(this->data_system, CLOCK_MONOTONIC, SPA_FD_CLOEXEC | SPA_FD_NONBLOCK); } return 0; } static const struct spa_interface_info impl_interfaces[] = { {SPA_TYPE_INTERFACE_Node,}, }; static int impl_enum_interface_info(const struct spa_handle_factory *factory, const struct spa_interface_info **info, uint32_t *index) { spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(info != NULL, -EINVAL); spa_return_val_if_fail(index != NULL, -EINVAL); switch (*index) { case 0: *info = &impl_interfaces[*index]; break; default: return 0; } (*index)++; return 1; } static const struct spa_dict_item info_items[] = { { SPA_KEY_FACTORY_AUTHOR, "Wim Taymans " }, { SPA_KEY_FACTORY_DESCRIPTION, "Play audio with the media" }, { SPA_KEY_FACTORY_USAGE, SPA_KEY_API_BLUEZ5_TRANSPORT"=" }, }; static const struct spa_dict info = SPA_DICT_INIT_ARRAY(info_items); const struct spa_handle_factory spa_media_sink_factory = { SPA_VERSION_HANDLE_FACTORY, SPA_NAME_API_BLUEZ5_MEDIA_SINK, &info, impl_get_size, impl_init, impl_enum_interface_info, }; /* Retained for backward compatibility: */ const struct spa_handle_factory spa_a2dp_sink_factory = { SPA_VERSION_HANDLE_FACTORY, SPA_NAME_API_BLUEZ5_A2DP_SINK, &info, impl_get_size, impl_init, impl_enum_interface_info, }; /* Retained for backward compatibility: */ const struct spa_handle_factory spa_sco_sink_factory = { SPA_VERSION_HANDLE_FACTORY, SPA_NAME_API_BLUEZ5_SCO_SINK, &info, impl_get_size, impl_init, impl_enum_interface_info, }; pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/bluez5/media-source.c000066400000000000000000001605471511204443500267060ustar00rootroot00000000000000/* Spa Media Source */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-FileCopyrightText: Copyright © 2019 Collabora Ltd. */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "defs.h" #include "rtp.h" #include "media-codecs.h" #include "iso-io.h" SPA_LOG_TOPIC_DEFINE_STATIC(log_topic, "spa.bluez5.source.media"); #undef SPA_LOG_TOPIC_DEFAULT #define SPA_LOG_TOPIC_DEFAULT &log_topic #include "decode-buffer.h" #define DEFAULT_CLOCK_NAME "clock.system.monotonic" struct props { char clock_name[64]; char latency[64]; bool has_latency; char rate[64]; bool has_rate; }; #define MAX_BUFFERS 32 #define MAX_PLC_PACKETS 16 struct buffer { uint32_t id; unsigned int outstanding:1; struct spa_buffer *buf; struct spa_meta_header *h; struct spa_list link; }; struct port { struct spa_audio_info current_format; uint32_t frame_size; unsigned int have_format:1; uint64_t info_all; struct spa_port_info info; struct spa_io_buffers *io; struct spa_io_rate_match *rate_match; struct spa_latency_info latency[2]; #define IDX_EnumFormat 0 #define IDX_Meta 1 #define IDX_IO 2 #define IDX_Format 3 #define IDX_Buffers 4 #define IDX_Latency 5 #define N_PORT_PARAMS 6 struct spa_param_info params[N_PORT_PARAMS]; struct buffer buffers[MAX_BUFFERS]; uint32_t n_buffers; struct spa_list free; struct spa_list ready; struct spa_bt_decode_buffer buffer; }; struct delay_info { union { struct { int32_t buffer; uint32_t duration; }; uint64_t v; }; }; SPA_STATIC_ASSERT(sizeof(struct delay_info) == sizeof(uint64_t)); struct impl { struct spa_handle handle; struct spa_node node; struct spa_log *log; struct spa_loop *data_loop; struct spa_system *data_system; struct spa_loop_utils *loop_utils; struct spa_hook_list hooks; struct spa_callbacks callbacks; uint32_t quantum_limit; uint64_t info_all; struct spa_node_info info; #define IDX_PropInfo 0 #define IDX_Props 1 #define IDX_NODE_IO 2 #define N_NODE_PARAMS 3 struct spa_param_info params[N_NODE_PARAMS]; struct props props; struct spa_bt_transport *transport; struct spa_hook transport_listener; struct port port; unsigned int started:1; unsigned int start_ready:1; unsigned int transport_started:1; unsigned int following:1; unsigned int matching:1; unsigned int resampling:1; unsigned int io_error:1; unsigned int is_input:1; unsigned int is_duplex:1; unsigned int is_internal:1; unsigned int decode_buffer_target; unsigned int node_latency; int fd; struct spa_source source; struct spa_source timer_source; int timerfd; struct spa_io_clock *clock; struct spa_io_position *position; uint64_t current_time; uint64_t next_time; const struct media_codec *codec; bool codec_props_changed; void *codec_props; void *codec_data; struct spa_audio_info codec_format; uint8_t buffer_read[4096]; uint64_t now; uint64_t sample_count; int seqnum; uint32_t plc_packets; uint32_t errqueue_count; struct delay_info delay; int64_t delay_sink; struct spa_source *update_delay_event; struct spa_bt_recvmsg_data recv; }; #define CHECK_PORT(this,d,p) ((d) == SPA_DIRECTION_OUTPUT && (p) == 0) static void reset_props(struct props *props) { strncpy(props->clock_name, DEFAULT_CLOCK_NAME, sizeof(props->clock_name)); spa_zero(props->latency); props->has_latency = false; spa_zero(props->rate); props->has_rate = false; } static int impl_node_enum_params(void *object, int seq, uint32_t id, uint32_t start, uint32_t num, const struct spa_pod *filter) { struct impl *this = object; struct spa_pod *param; struct spa_pod_builder b = { 0 }; uint8_t buffer[1024]; struct spa_result_node_params result; uint32_t count = 0, index_offset = 0; bool enum_codec = false; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(num != 0, -EINVAL); result.id = id; result.next = start; next: result.index = result.next++; spa_pod_builder_init(&b, buffer, sizeof(buffer)); switch (id) { case SPA_PARAM_PropInfo: { switch (result.index) { default: enum_codec = true; index_offset = 0; } break; } case SPA_PARAM_Props: { switch (result.index) { default: enum_codec = true; index_offset = 0; } break; } default: return -ENOENT; } if (enum_codec) { int res; if (this->codec->enum_props == NULL || this->codec_props == NULL || this->transport == NULL) return 0; else if ((res = this->codec->enum_props(this->codec_props, this->transport->device->settings, id, result.index - index_offset, &b, ¶m)) != 1) return res; } if (spa_pod_filter(&b, &result.param, param, filter) < 0) goto next; spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); if (++count != num) goto next; return 0; } static int set_timeout(struct impl *this, uint64_t time) { struct itimerspec ts; ts.it_value.tv_sec = time / SPA_NSEC_PER_SEC; ts.it_value.tv_nsec = time % SPA_NSEC_PER_SEC; ts.it_interval.tv_sec = 0; ts.it_interval.tv_nsec = 0; return spa_system_timerfd_settime(this->data_system, this->timerfd, SPA_FD_TIMER_ABSTIME, &ts, NULL); } static int set_timers(struct impl *this) { struct timespec now; spa_system_clock_gettime(this->data_system, CLOCK_MONOTONIC, &now); this->next_time = SPA_TIMESPEC_TO_NSEC(&now); return set_timeout(this, this->following ? 0 : this->next_time); } static int do_reassign_follower(struct spa_loop *loop, bool async, uint32_t seq, const void *data, size_t size, void *user_data) { struct impl *this = user_data; struct port *port = &this->port; set_timers(this); if (this->transport_started) spa_bt_decode_buffer_recover(&port->buffer); return 0; } static inline bool is_following(struct impl *this) { return this->position && this->clock && this->position->clock.id != this->clock->id; } static int impl_node_set_io(void *object, uint32_t id, void *data, size_t size) { struct impl *this = object; bool following; spa_return_val_if_fail(this != NULL, -EINVAL); switch (id) { case SPA_IO_Clock: this->clock = data; if (this->clock != NULL) { spa_scnprintf(this->clock->name, sizeof(this->clock->name), "%s", this->props.clock_name); } break; case SPA_IO_Position: this->position = data; break; default: return -ENOENT; } following = is_following(this); if (this->started && following != this->following) { spa_log_debug(this->log, "%p: reassign follower %d->%d", this, this->following, following); this->following = following; spa_loop_locked(this->data_loop, do_reassign_follower, 0, NULL, 0, this); } return 0; } static void emit_node_info(struct impl *this, bool full); static void set_latency(struct impl *this, bool emit_latency) { if (this->codec->kind == MEDIA_CODEC_BAP && !this->is_input && this->transport && this->transport->delay_us != SPA_BT_UNKNOWN_DELAY) { struct port *port = &this->port; unsigned int node_latency = 2048; uint64_t rate = port->current_format.info.raw.rate; unsigned int target = this->transport->delay_us*rate/SPA_USEC_PER_SEC * 1/2; /* Adjust requested node latency to be somewhat (~1/2) smaller * than presentation delay. The difference functions as room * for buffering rate control. */ while (node_latency > 64 && node_latency > target) node_latency /= 2; if (this->node_latency != node_latency) { this->node_latency = node_latency; if (emit_latency) emit_node_info(this, false); } spa_log_info(this->log, "BAP presentation delay %d us, node latency %u/%u", (int)this->transport->delay_us, node_latency, (unsigned int)rate); } } static int apply_props(struct impl *this, const struct spa_pod *param) { struct props new_props = this->props; int changed = 0; if (param == NULL) { reset_props(&new_props); } else { /* noop */ } changed = (memcmp(&new_props, &this->props, sizeof(struct props)) != 0); this->props = new_props; return changed; } static int impl_node_set_param(void *object, uint32_t id, uint32_t flags, const struct spa_pod *param) { struct impl *this = object; spa_return_val_if_fail(this != NULL, -EINVAL); switch (id) { case SPA_PARAM_Props: { int res, codec_res = 0; res = apply_props(this, param); if (this->codec_props && this->codec->set_props) { codec_res = this->codec->set_props(this->codec_props, param); if (codec_res > 0) this->codec_props_changed = true; } if (res > 0 || codec_res > 0) { this->info.change_mask |= SPA_NODE_CHANGE_MASK_PARAMS; this->params[IDX_Props].flags ^= SPA_PARAM_INFO_SERIAL; emit_node_info(this, false); } break; } default: return -ENOENT; } return 0; } static void reset_buffers(struct port *port) { uint32_t i; spa_list_init(&port->free); spa_list_init(&port->ready); for (i = 0; i < port->n_buffers; i++) { struct buffer *b = &port->buffers[i]; spa_list_append(&port->free, &b->link); b->outstanding = false; } } static void recycle_buffer(struct impl *this, struct port *port, uint32_t buffer_id) { struct buffer *b = &port->buffers[buffer_id]; if (b->outstanding) { spa_log_trace(this->log, "%p: recycle buffer %u", this, buffer_id); spa_list_append(&port->free, &b->link); b->outstanding = false; } } static int32_t read_data(struct impl *this, uint64_t *rx_time, int *seqnum) { const ssize_t b_size = sizeof(this->buffer_read); int32_t size_read = 0; again: /* read data from socket */ size_read = spa_bt_recvmsg(&this->recv, this->buffer_read, b_size, rx_time, seqnum); if (size_read == 0) return 0; else if (size_read < 0) { /* retry if interrupted */ if (errno == EINTR) goto again; /* return socket has no data */ if (errno == EAGAIN || errno == EWOULDBLOCK) return 0; /* go to 'stop' if socket has an error */ spa_log_error(this->log, "read error: %s", strerror(errno)); return -errno; } return size_read; } static int produce_plc_data(struct impl *this) { struct port *port = &this->port; uint32_t avail; int res; void *buf; if (!this->codec->produce_plc) return -ENOTSUP; buf = spa_bt_decode_buffer_get_write(&port->buffer, &avail); res = this->codec->produce_plc(this->codec_data, buf, avail); if (res <= 0) return res; spa_bt_decode_buffer_write_packet(&port->buffer, res, 0); spa_log_debug(this->log, "%p: produced PLC audio, frames:%u", this, (unsigned int)(res / port->frame_size)); this->plc_packets++; return res; } static int32_t decode_data(struct impl *this, uint8_t *src, uint32_t src_size, uint8_t *dst, uint32_t dst_size, uint32_t *dst_out, int pkt_seqnum) { ssize_t processed; size_t written, avail; size_t src_avail = src_size; uint16_t seqnum = this->seqnum + 1; *dst_out = 0; if ((processed = this->codec->start_decode(this->codec_data, src, src_avail, &seqnum, NULL)) < 0) return processed; if (pkt_seqnum >= 0) seqnum = pkt_seqnum; src += processed; src_avail -= processed; if (this->seqnum < 0) { /* first packet */ } else if (this->codec->stream_pkt && this->seqnum == seqnum) { /* previous packet continues */ } else { uint16_t lost = seqnum - (uint16_t)(this->seqnum + 1); if (lost) spa_log_debug(this->log, "%p: lost packets:%u (%u -> %u)", this, (unsigned int)lost, this->seqnum + 1, seqnum); if (this->plc_packets > MAX_PLC_PACKETS || lost > MAX_PLC_PACKETS) { /* Don't try to compensate for too big skips */ this->plc_packets = 0; lost = 0; } if (lost >= this->plc_packets) { lost -= this->plc_packets; } else { /* We already produced PLC audio for this packet. However, this * only occurs if we are underflowing, so we should retain this * packet regardless and let rate matching take care of it. */ lost = 0; } /* Pad with PLC audio for any missing packets */ while (lost > 0 && produce_plc_data(this) > 0) --lost; this->plc_packets = 0; } /* decode */ avail = dst_size; do { written = 0; if ((processed = this->codec->decode(this->codec_data, src, src_avail, dst, avail, &written)) < 0) return processed; /* update source and dest pointers */ spa_return_val_if_fail (avail > written, -ENOSPC); src_avail -= processed; src += processed; avail -= written; dst += written; } while (src_avail && (processed || written) && !this->codec->stream_pkt); this->seqnum = seqnum; *dst_out = dst_size - avail; return src_size - src_avail; } static void add_data(struct impl *this, uint8_t *src, uint32_t src_size, uint64_t now, int pkt_seqnum) { struct port *port = &this->port; uint32_t decoded; spa_log_trace(this->log, "%p: read socket data size:%d", this, src_size); if (this->transport->iso_io) now = spa_bt_iso_io_recv(this->transport->iso_io, now); do { int32_t consumed; uint32_t avail; void *buf; uint64_t dt; buf = spa_bt_decode_buffer_get_write(&port->buffer, &avail); consumed = decode_data(this, src, src_size, buf, avail, &decoded, pkt_seqnum); if (consumed < 0) { spa_log_debug(this->log, "%p: failed to decode data: %d", this, consumed); return; } src = SPA_PTROFF(src, consumed, void); src_size -= consumed; /* discard when not started */ if (this->started) spa_bt_decode_buffer_write_packet(&port->buffer, decoded, now); if (decoded) { dt = now - this->now; this->now = now; spa_log_trace(this->log, "%p: decoded socket data seq:%u size:%d frames:%d dt:%d dms", this, (unsigned int)this->seqnum, (int)decoded, (int)decoded/port->frame_size, (int)(dt / 100000)); } else { spa_log_trace(this->log, "no decoded socket data"); } } while (this->codec->stream_pkt && src_size && decoded); } static void handle_errqueue(struct impl *this) { int res; if (this->transport && this->transport->iso_io) if (spa_bt_iso_io_recv_errqueue(this->transport->iso_io) == 0) return; /* iso-io/media-sink use these for TX latency. * Someone else should be reading them, so drop * only after yielding. */ if (this->errqueue_count < 4) { this->errqueue_count++; return; } this->errqueue_count = 0; do { char buf[512]; res = recv(this->fd, buf, sizeof(buf), MSG_ERRQUEUE | MSG_TRUNC | MSG_DONTWAIT); spa_log_trace(this->log, "%p: ignoring errqueue data (%d)", this, res); } while (res > 0); } static void media_on_ready_read(struct spa_source *source) { struct impl *this = source->data; int32_t size_read; uint64_t now = 0; int pkt_seqnum = -1; /* make sure the source is an input */ if ((source->rmask & SPA_IO_IN) == 0) { if (source->rmask & SPA_IO_ERR) { handle_errqueue(this); return; } spa_log_error(this->log, "source is not an input, rmask=%d", source->rmask); goto stop; } if (this->transport == NULL) { spa_log_debug(this->log, "no transport, stop reading"); goto stop; } this->errqueue_count = 0; spa_log_trace(this->log, "socket poll"); /* read */ size_read = read_data (this, &now, &pkt_seqnum); if (size_read < 0) { spa_log_error(this->log, "failed to read data: %s", spa_strerror(size_read)); goto stop; } if (this->codec_props_changed && this->codec_props && this->codec->update_props) { this->codec->update_props(this->codec_data, this->codec_props); this->codec_props_changed = false; } add_data(this, this->buffer_read, size_read, now, pkt_seqnum); return; stop: this->io_error = true; if (this->source.loop) spa_loop_remove_source(this->data_loop, &this->source); if (this->transport && this->transport->iso_io) { spa_bt_iso_io_set_cb(this->transport->iso_io, NULL, NULL); spa_bt_iso_io_set_source_buffer(this->transport->iso_io, NULL); } } static int media_sco_pull(void *userdata, uint8_t *buffer_read, int size_read, uint64_t now) { struct impl *this = userdata; if (this->transport == NULL) { spa_log_debug(this->log, "no transport, stop reading"); goto stop; } if (size_read == 0) return 0; add_data(this, buffer_read, size_read, now, -1); return 0; stop: this->io_error = true; if (this->transport && this->transport->sco_io) spa_bt_sco_io_set_source_cb(this->transport->sco_io, NULL, NULL); return 1; } static int setup_matching(struct impl *this) { struct port *port = &this->port; if (!this->transport_started) port->buffer.corr = 1.0; if (this->position && port->rate_match) { port->rate_match->rate = 1 / port->buffer.corr; this->matching = this->following; this->resampling = this->matching || (port->current_format.info.raw.rate != this->position->clock.target_rate.denom); /* Rate match in system clock domain also when follower */ if (this->matching && this->position->clock.rate_diff > 0) port->rate_match->rate *= this->position->clock.rate_diff; } else { this->matching = false; this->resampling = false; } if (port->rate_match) SPA_FLAG_UPDATE(port->rate_match->flags, SPA_IO_RATE_MATCH_FLAG_ACTIVE, this->matching); return 0; } static int produce_buffer(struct impl *this); static void media_on_timeout(struct spa_source *source) { struct impl *this = source->data; struct port *port = &this->port; uint64_t exp, duration; uint32_t rate; uint64_t prev_time, now_time; int res; if (this->transport == NULL) return; if (this->started) { if ((res = spa_system_timerfd_read(this->data_system, this->timerfd, &exp)) < 0) { if (res != -EAGAIN) spa_log_warn(this->log, "error reading timerfd: %s", spa_strerror(res)); return; } } prev_time = this->current_time; now_time = this->current_time = this->next_time; spa_log_trace(this->log, "%p: timer %"PRIu64" %"PRIu64"", this, now_time, now_time - prev_time); if (SPA_LIKELY(this->position)) { duration = this->position->clock.target_duration; rate = this->position->clock.target_rate.denom; } else { duration = 1024; rate = 48000; } setup_matching(this); this->next_time = (uint64_t)(now_time + duration * SPA_NSEC_PER_SEC / port->buffer.corr / rate); if (SPA_LIKELY(this->clock)) { this->clock->nsec = now_time; this->clock->rate = this->clock->target_rate; this->clock->position += this->clock->duration; this->clock->duration = duration; this->clock->rate_diff = port->buffer.corr; this->clock->next_nsec = this->next_time; } if (port->io) { int io_status = port->io->status; int status = produce_buffer(this); spa_log_trace(this->log, "%p: io:%d->%d status:%d", this, io_status, port->io->status, status); } spa_node_call_ready(&this->callbacks, SPA_STATUS_HAVE_DATA); set_timeout(this, this->next_time); } static void media_iso_pull(struct spa_bt_iso_io *iso_io) { /* TODO: eventually use iso-io here, currently this is used just to indicate to * iso-io whether this source is running or not. */ } static void emit_port_info(struct impl *this, struct port *port, bool full); static void update_transport_delay(struct impl *this) { struct port *port = &this->port; struct delay_info info; float latency; int64_t latency_nsec; int64_t delay_sink; if (!this->transport || !port->have_format) return; info.v = __atomic_load_n(&this->delay.v, __ATOMIC_RELAXED); /* Latency to sink */ latency = info.buffer + port->latency[SPA_DIRECTION_INPUT].min_rate + port->latency[SPA_DIRECTION_INPUT].min_quantum * info.duration; latency_nsec = port->latency[SPA_DIRECTION_INPUT].min_ns + (int64_t)(latency * SPA_NSEC_PER_SEC / port->current_format.info.raw.rate); spa_bt_transport_set_delay(this->transport, latency_nsec); delay_sink = port->latency[SPA_DIRECTION_INPUT].min_ns + (int64_t)((port->latency[SPA_DIRECTION_INPUT].min_rate + port->latency[SPA_DIRECTION_INPUT].min_quantum * info.duration) * SPA_NSEC_PER_SEC / port->current_format.info.raw.rate); __atomic_store_n(&this->delay_sink, delay_sink, __ATOMIC_RELAXED); /* Latency from source */ port->latency[SPA_DIRECTION_OUTPUT] = SPA_LATENCY_INFO(SPA_DIRECTION_OUTPUT, .min_rate = info.buffer, .max_rate = info.buffer); port->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; port->params[IDX_Latency].user++; emit_port_info(this, port, false); } static void update_delay_event(void *data, uint64_t count) { /* in main loop */ update_transport_delay(data); } static int do_start_sco_iso_io(struct spa_loop *loop, bool async, uint32_t seq, const void *data, size_t size, void *user_data) { struct impl *this = user_data; if (this->transport->sco_io) spa_bt_sco_io_set_source_cb(this->transport->sco_io, media_sco_pull, this); if (this->transport->iso_io) { spa_bt_iso_io_set_cb(this->transport->iso_io, media_iso_pull, this); spa_bt_iso_io_set_source_buffer(this->transport->iso_io, &this->port.buffer); } return 0; } static int transport_start(struct impl *this) { int res, val; struct port *port = &this->port; uint32_t flags; if (this->transport_started) return 0; if (!this->start_ready) return -EIO; spa_return_val_if_fail(this->transport != NULL, -EIO); spa_log_debug(this->log, "%p: start transport state:%d", this, this->transport->state); flags = this->is_duplex ? 0 : MEDIA_CODEC_FLAG_SINK; this->codec_data = this->codec->init(this->codec, flags, this->transport->configuration, this->transport->configuration_len, &port->current_format, this->codec_props, this->transport->read_mtu); if (this->codec_data == NULL) return -EIO; spa_log_info(this->log, "%p: using %s codec %s", this, media_codec_kind_str(this->codec), this->codec->description); /* * If the link is bidirectional, media-sink may also be polling the same FD, * and this won't work properly with epoll. Always dup to avoid problems. */ this->fd = dup(this->transport->fd); if (this->fd < 0) return -errno; val = 6; if (setsockopt(this->fd, SOL_SOCKET, SO_PRIORITY, &val, sizeof(val)) < 0) spa_log_warn(this->log, "SO_PRIORITY failed: %m"); reset_buffers(port); spa_bt_decode_buffer_clear(&port->buffer); if ((res = spa_bt_decode_buffer_init(&port->buffer, this->log, port->frame_size, port->current_format.info.raw.rate, this->quantum_limit, this->quantum_limit)) < 0) return res; spa_bt_decode_buffer_set_target_latency(&port->buffer, (int32_t) this->decode_buffer_target); if (this->codec->kind == MEDIA_CODEC_HFP || this->codec->kind == MEDIA_CODEC_BAP) { /* 40 ms max buffer (on top of duration) */ spa_bt_decode_buffer_set_max_extra_latency(&port->buffer, port->current_format.info.raw.rate * 40 / 1000); } else if (this->is_duplex) { /* 80 ms max extra buffer */ spa_bt_decode_buffer_set_max_extra_latency(&port->buffer, port->current_format.info.raw.rate * 80 / 1000); } this->delay.buffer = -1; this->delay.duration = 0; this->update_delay_event = spa_loop_utils_add_event(this->loop_utils, update_delay_event, this); this->sample_count = 0; this->errqueue_count = 0; this->seqnum = -1; this->io_error = false; if (this->codec->kind != MEDIA_CODEC_HFP) { spa_bt_recvmsg_init(&this->recv, this->fd, this->data_system, this->log); spa_loop_locked(this->data_loop, do_start_sco_iso_io, 0, NULL, 0, this); this->source.data = this; this->source.fd = this->fd; this->source.func = media_on_ready_read; this->source.mask = SPA_IO_IN; this->source.rmask = 0; if ((res = spa_loop_add_source(this->data_loop, &this->source)) < 0) spa_log_error(this->log, "%p: failed to add poll source: %s", this, spa_strerror(res)); } else { spa_zero(this->source); if (spa_bt_transport_ensure_sco_io(this->transport, this->data_loop, this->data_system) < 0) goto fail; spa_loop_locked(this->data_loop, do_start_sco_iso_io, 0, NULL, 0, this); } this->transport_started = true; return 0; fail: if (this->codec_data) { this->codec->deinit(this->codec_data); this->codec_data = NULL; } return -EIO; } static int do_start(struct impl *this) { int res; if (this->started) return 0; spa_return_val_if_fail(this->transport != NULL, -EIO); this->following = is_following(this); this->start_ready = true; spa_log_debug(this->log, "%p: start following:%d", this, this->following); spa_log_debug(this->log, "%p: transport %p acquire", this, this->transport); bool do_accept = (this->transport->profile & SPA_BT_PROFILE_HEADSET_AUDIO_GATEWAY); if ((res = spa_bt_transport_acquire(this->transport, do_accept)) < 0) { this->start_ready = false; return res; } this->timer_source.data = this; this->timer_source.fd = this->timerfd; this->timer_source.func = media_on_timeout; this->timer_source.mask = SPA_IO_IN; this->timer_source.rmask = 0; spa_loop_add_source(this->data_loop, &this->timer_source); setup_matching(this); set_timers(this); this->started = true; return 0; } static int do_remove_source(struct spa_loop *loop, bool async, uint32_t seq, const void *data, size_t size, void *user_data) { struct impl *this = user_data; spa_log_debug(this->log, "%p: remove source", this); if (this->timer_source.loop) spa_loop_remove_source(this->data_loop, &this->timer_source); if (this->transport && this->transport->iso_io) { spa_bt_iso_io_set_cb(this->transport->iso_io, NULL, NULL); spa_bt_iso_io_set_source_buffer(this->transport->iso_io, NULL); } if (this->transport && this->transport->sco_io) spa_bt_sco_io_set_source_cb(this->transport->sco_io, NULL, NULL); set_timeout(this, 0); if (this->update_delay_event) { spa_loop_utils_destroy_source(this->loop_utils, this->update_delay_event); this->update_delay_event = NULL; } return 0; } static int do_remove_transport_source(struct spa_loop *loop, bool async, uint32_t seq, const void *data, size_t size, void *user_data) { struct impl *this = user_data; spa_log_debug(this->log, "%p: remove transport source", this); this->transport_started = false; if (this->source.loop) spa_loop_remove_source(this->data_loop, &this->source); if (this->transport->iso_io) { spa_bt_iso_io_set_cb(this->transport->iso_io, NULL, NULL); spa_bt_iso_io_set_source_buffer(this->transport->iso_io, NULL); } if (this->transport->sco_io) spa_bt_sco_io_set_source_cb(this->transport->sco_io, NULL, NULL); return 0; } static void transport_stop(struct impl *this) { struct port *port = &this->port; if (!this->transport_started) return; spa_log_debug(this->log, "%p: transport stop", this); spa_loop_locked(this->data_loop, do_remove_transport_source, 0, NULL, 0, this); if (this->fd >= 0) { close(this->fd); this->fd = -1; } if (this->codec_data) this->codec->deinit(this->codec_data); this->codec_data = NULL; spa_bt_decode_buffer_clear(&port->buffer); } static int do_stop(struct impl *this) { int res; if (!this->started) return 0; spa_log_debug(this->log, "%p: stop", this); this->start_ready = false; spa_loop_locked(this->data_loop, do_remove_source, 0, NULL, 0, this); transport_stop(this); if (this->transport) res = spa_bt_transport_release(this->transport); else res = 0; this->started = false; return res; } static int impl_node_send_command(void *object, const struct spa_command *command) { struct impl *this = object; struct port *port; int res; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(command != NULL, -EINVAL); port = &this->port; switch (SPA_NODE_COMMAND_ID(command)) { case SPA_NODE_COMMAND_Start: if (!port->have_format) return -EIO; if (port->n_buffers == 0) return -EIO; if ((res = do_start(this)) < 0) return res; break; case SPA_NODE_COMMAND_Suspend: case SPA_NODE_COMMAND_Pause: if ((res = do_stop(this)) < 0) return res; break; default: return -ENOTSUP; } return 0; } static void emit_node_info(struct impl *this, bool full) { uint64_t old = full ? this->info.change_mask : 0; char latency[64]; char rate[64]; char media_name[256]; const char *media_role = NULL; struct port *port = &this->port; spa_scnprintf( media_name, sizeof(media_name), "%s (codec %s)", ((this->transport && this->transport->device->name) ? this->transport->device->name : media_codec_kind_str(this->codec)), this->codec->description ); if (!this->is_input && this->transport && (this->transport->profile & SPA_BT_PROFILE_HEADSET_AUDIO_GATEWAY)) media_role = "Communication"; struct spa_dict_item node_info_items[7] = { { SPA_KEY_DEVICE_API, "bluez5" }, { SPA_KEY_MEDIA_CLASS, this->is_internal ? "Audio/Source/Internal" : this->is_input ? "Audio/Source" : "Stream/Output/Audio" }, { "media.name", media_name }, { SPA_KEY_NODE_DRIVER, this->is_input ? "true" : "false" }, { SPA_KEY_MEDIA_ROLE, media_role }, }; size_t n_items = 5; spa_assert(n_items + 2 <= SPA_N_ELEMENTS(node_info_items)); if (this->props.has_latency) { node_info_items[n_items].key = SPA_KEY_NODE_LATENCY; node_info_items[n_items].value = this->props.latency; n_items++; } else if (!this->is_input && this->node_latency != 0) { spa_scnprintf(latency, sizeof(latency), "%u/%u", this->node_latency, port->current_format.info.raw.rate); node_info_items[n_items].key = SPA_KEY_NODE_LATENCY; node_info_items[n_items].value = latency; n_items++; } if (this->props.has_rate) { node_info_items[n_items].key = "node.rate"; node_info_items[n_items].value = this->props.rate; n_items++; } else if (!this->is_input && this->node_latency != 0) { spa_scnprintf(rate, sizeof(rate), "1/%u", port->current_format.info.raw.rate); node_info_items[n_items].key = "node.rate"; node_info_items[n_items].value = rate; n_items++; } if (full) this->info.change_mask = this->info_all; if (this->info.change_mask) { this->info.props = &SPA_DICT_INIT(node_info_items, n_items); spa_node_emit_info(&this->hooks, &this->info); this->info.change_mask = old; } } static void emit_port_info(struct impl *this, struct port *port, bool full) { uint64_t old = full ? port->info.change_mask : 0; if (full) port->info.change_mask = port->info_all; if (port->info.change_mask) { spa_node_emit_port_info(&this->hooks, SPA_DIRECTION_OUTPUT, 0, &port->info); port->info.change_mask = old; } } static int impl_node_add_listener(void *object, struct spa_hook *listener, const struct spa_node_events *events, void *data) { struct impl *this = object; struct spa_hook_list save; spa_return_val_if_fail(this != NULL, -EINVAL); spa_hook_list_isolate(&this->hooks, &save, listener, events, data); emit_node_info(this, true); emit_port_info(this, &this->port, true); spa_hook_list_join(&this->hooks, &save); return 0; } static int impl_node_set_callbacks(void *object, const struct spa_node_callbacks *callbacks, void *data) { struct impl *this = object; spa_return_val_if_fail(this != NULL, -EINVAL); this->callbacks = SPA_CALLBACKS_INIT(callbacks, data); return 0; } static int impl_node_sync(void *object, int seq) { struct impl *this = object; spa_return_val_if_fail(this != NULL, -EINVAL); spa_node_emit_result(&this->hooks, seq, 0, 0, NULL); return 0; } static int impl_node_add_port(void *object, enum spa_direction direction, uint32_t port_id, const struct spa_dict *props) { return -ENOTSUP; } static int impl_node_remove_port(void *object, enum spa_direction direction, uint32_t port_id) { return -ENOTSUP; } static int impl_node_port_enum_params(void *object, int seq, enum spa_direction direction, uint32_t port_id, uint32_t id, uint32_t start, uint32_t num, const struct spa_pod *filter) { struct impl *this = object; struct port *port; struct spa_pod *param; struct spa_pod_builder b = { 0 }; uint8_t buffer[1024]; struct spa_result_node_params result; uint32_t count = 0; int res; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(num != 0, -EINVAL); spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); port = &this->port; result.id = id; result.next = start; next: result.index = result.next++; spa_pod_builder_init(&b, buffer, sizeof(buffer)); switch (id) { case SPA_PARAM_EnumFormat: if (result.index > 0) return 0; if (this->codec == NULL) return -EIO; if (this->transport == NULL) return -EIO; if ((res = this->codec->enum_config(this->codec, this->is_duplex ? 0 : MEDIA_CODEC_FLAG_SINK, this->transport->configuration, this->transport->configuration_len, id, result.index, &b, ¶m)) != 1) return res; break; case SPA_PARAM_Format: if (!port->have_format) return -EIO; if (result.index > 0) return 0; param = spa_format_audio_raw_build(&b, id, &port->current_format.info.raw); break; case SPA_PARAM_Buffers: if (!port->have_format) return -EIO; if (result.index > 0) return 0; param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamBuffers, id, SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(2, 1, MAX_BUFFERS), SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(1), SPA_PARAM_BUFFERS_size, SPA_POD_CHOICE_RANGE_Int( this->quantum_limit * port->frame_size, 16 * port->frame_size, INT32_MAX), SPA_PARAM_BUFFERS_stride, SPA_POD_Int(port->frame_size)); break; case SPA_PARAM_Meta: switch (result.index) { case 0: param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamMeta, id, SPA_PARAM_META_type, SPA_POD_Id(SPA_META_Header), SPA_PARAM_META_size, SPA_POD_Int(sizeof(struct spa_meta_header))); break; default: return 0; } break; case SPA_PARAM_IO: switch (result.index) { case 0: param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamIO, id, SPA_PARAM_IO_id, SPA_POD_Id(SPA_IO_Buffers), SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_buffers))); break; case 1: param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamIO, id, SPA_PARAM_IO_id, SPA_POD_Id(SPA_IO_RateMatch), SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_rate_match))); break; default: return 0; } break; case SPA_PARAM_Latency: switch (result.index) { case 0: case 1: param = spa_latency_build(&b, id, &port->latency[result.index]); break; default: return 0; } break; default: return -ENOENT; } if (spa_pod_filter(&b, &result.param, param, filter) < 0) goto next; spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); if (++count != num) goto next; return 0; } static int clear_buffers(struct impl *this, struct port *port) { do_stop(this); if (port->n_buffers > 0) { spa_list_init(&port->free); spa_list_init(&port->ready); port->n_buffers = 0; } return 0; } static int port_set_format(struct impl *this, struct port *port, uint32_t flags, const struct spa_pod *format) { int err; if (format == NULL) { spa_log_debug(this->log, "clear format"); clear_buffers(this, port); port->have_format = false; } else { struct spa_audio_info info = { 0 }; if ((err = spa_format_parse(format, &info.media_type, &info.media_subtype)) < 0) return err; if (info.media_type != SPA_MEDIA_TYPE_audio || info.media_subtype != SPA_MEDIA_SUBTYPE_raw) return -EINVAL; if (spa_format_audio_raw_parse(format, &info.info.raw) < 0) return -EINVAL; if (info.info.raw.rate == 0 || info.info.raw.channels == 0 || info.info.raw.channels > MAX_CHANNELS) return -EINVAL; port->frame_size = info.info.raw.channels; switch (info.info.raw.format) { case SPA_AUDIO_FORMAT_S16_LE: case SPA_AUDIO_FORMAT_S16_BE: port->frame_size *= 2; break; case SPA_AUDIO_FORMAT_S24: port->frame_size *= 3; break; case SPA_AUDIO_FORMAT_S24_32: case SPA_AUDIO_FORMAT_S32: case SPA_AUDIO_FORMAT_F32: port->frame_size *= 4; break; default: return -EINVAL; } port->current_format = info; port->have_format = true; set_latency(this, false); } port->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; if (port->have_format) { port->info.change_mask |= SPA_PORT_CHANGE_MASK_RATE; port->info.rate = SPA_FRACTION(1, port->current_format.info.raw.rate); port->params[IDX_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_READWRITE); port->params[IDX_Buffers] = SPA_PARAM_INFO(SPA_PARAM_Buffers, SPA_PARAM_INFO_READ); port->params[IDX_Latency].flags ^= SPA_PARAM_INFO_SERIAL; } else { port->params[IDX_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); port->params[IDX_Buffers] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); } emit_port_info(this, port, false); this->info.change_mask |= SPA_NODE_CHANGE_MASK_PROPS; emit_node_info(this, false); return 0; } static int impl_node_port_set_param(void *object, enum spa_direction direction, uint32_t port_id, uint32_t id, uint32_t flags, const struct spa_pod *param) { struct impl *this = object; struct port *port; int res; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(CHECK_PORT(node, direction, port_id), -EINVAL); port = &this->port; switch (id) { case SPA_PARAM_Format: res = port_set_format(this, port, flags, param); break; case SPA_PARAM_Latency: { enum spa_direction other = SPA_DIRECTION_REVERSE(direction); struct spa_latency_info info; if (param == NULL) info = SPA_LATENCY_INFO(other); else if ((res = spa_latency_parse(param, &info)) < 0) return res; if (info.direction != other) return -EINVAL; if (memcmp(&port->latency[info.direction], &info, sizeof(info)) == 0) return 0; port->latency[info.direction] = info; this->port.info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; this->port.params[IDX_Latency].user++; update_transport_delay(this); emit_port_info(this, port, false); res = 0; break; } default: res = -ENOENT; break; } return res; } static int impl_node_port_use_buffers(void *object, enum spa_direction direction, uint32_t port_id, uint32_t flags, struct spa_buffer **buffers, uint32_t n_buffers) { struct impl *this = object; struct port *port; uint32_t i; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); port = &this->port; spa_log_debug(this->log, "use buffers %d", n_buffers); clear_buffers(this, port); if (n_buffers > 0 && !port->have_format) return -EIO; if (n_buffers > MAX_BUFFERS) return -ENOSPC; for (i = 0; i < n_buffers; i++) { struct buffer *b = &port->buffers[i]; struct spa_data *d = buffers[i]->datas; b->buf = buffers[i]; b->id = i; b->h = spa_buffer_find_meta_data(buffers[i], SPA_META_Header, sizeof(*b->h)); if (d[0].data == NULL) { spa_log_error(this->log, "%p: need mapped memory", this); return -EINVAL; } spa_list_append(&port->free, &b->link); b->outstanding = false; } port->n_buffers = n_buffers; return 0; } static int impl_node_port_set_io(void *object, enum spa_direction direction, uint32_t port_id, uint32_t id, void *data, size_t size) { struct impl *this = object; struct port *port; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); port = &this->port; switch (id) { case SPA_IO_Buffers: port->io = data; break; case SPA_IO_RateMatch: port->rate_match = data; break; default: return -ENOENT; } return 0; } static int impl_node_port_reuse_buffer(void *object, uint32_t port_id, uint32_t buffer_id) { struct impl *this = object; struct port *port; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(port_id == 0, -EINVAL); port = &this->port; if (port->n_buffers == 0) return -EIO; if (buffer_id >= port->n_buffers) return -EINVAL; recycle_buffer(this, port, buffer_id); return 0; } static uint32_t get_samples(struct impl *this, int64_t *duration_ns) { struct port *port = &this->port; uint32_t samples, rate_denom; uint64_t duration; if (SPA_LIKELY(this->position)) { duration = this->position->clock.duration; rate_denom = this->position->clock.rate.denom; } else { duration = 1024; rate_denom = port->current_format.info.raw.rate; } *duration_ns = duration * SPA_NSEC_PER_SEC / rate_denom; if (SPA_LIKELY(port->rate_match) && this->resampling) { samples = port->rate_match->size; } else { samples = duration; } return samples; } static void update_target_latency(struct impl *this) { struct port *port = &this->port; uint32_t samples, latency; int64_t delay_sink; if (this->transport == NULL || !port->have_format) return; if (this->codec->kind != MEDIA_CODEC_BAP) return; if (this->is_input) { /* BAP Client. Should use same buffer size for all streams in the same * group, so that capture is in sync. */ if (this->transport->iso_io) { int32_t target = spa_bt_iso_io_get_source_target_latency(this->transport->iso_io); spa_bt_decode_buffer_set_target_latency(&port->buffer, target); } return; } if (this->transport->delay_us == SPA_BT_UNKNOWN_DELAY) return; /* Presentation delay for BAP server * * This assumes the time when we receive the packet is (on average) * the SDU synchronization reference (see Core v5.3 Vol 6/G Sec 3.2.2 Fig. 3.2, * BAP v1.0 Sec 7.1.1). * * XXX: This is not exactly true, there might be some latency in between, * XXX: but currently kernel does not provide us any better information. * XXX: Some controllers (e.g. Intel AX210) also do not seem to set timestamps * XXX: to the HCI ISO data packets, so it's not clear what we can do here * XXX: better. */ samples = (uint64_t)this->transport->delay_us * port->current_format.info.raw.rate / SPA_USEC_PER_SEC; delay_sink = __atomic_load_n(&this->delay_sink, __ATOMIC_RELAXED); latency = delay_sink * port->current_format.info.raw.rate / SPA_NSEC_PER_SEC; if (samples > latency) samples -= latency; else samples = 1; /* Too small target latency might not produce working audio. * The minimum (Presentation_Delay_Min) is configured in endpoint * DBus properties, with some default value on BlueZ side if unspecified. */ spa_bt_decode_buffer_set_target_latency(&port->buffer, samples); } #define WARN_ONCE(cond, ...) \ if (SPA_UNLIKELY(cond)) { static bool __once; if (!__once) { __once = true; spa_log_warn(__VA_ARGS__); } } static void process_buffering(struct impl *this) { struct port *port = &this->port; int64_t duration_ns; const uint32_t samples = get_samples(this, &duration_ns); uint32_t data_size = samples * port->frame_size; uint32_t avail; update_target_latency(this); if (samples > this->quantum_limit) return; /* Produce PLC data if possible to avoid underrun */ while (spa_bt_decode_buffer_get_size(&port->buffer) < data_size) { if (produce_plc_data(this) <= 0) break; } setup_matching(this); spa_bt_decode_buffer_process(&port->buffer, samples, duration_ns, this->position ? this->position->clock.rate_diff : 1.0, this->position ? this->position->clock.next_nsec : 0, this->resampling ? this->port.rate_match->delay : 0, this->resampling ? this->port.rate_match->delay_frac : 0); /* copy data to buffers */ if (!spa_list_is_empty(&port->free)) { struct buffer *buffer; struct spa_data *datas; void *buf; buffer = spa_list_first(&port->free, struct buffer, link); datas = buffer->buf->datas; WARN_ONCE(datas[0].maxsize < data_size && !this->following, this->log, "source buffer too small (%u < %u)", datas[0].maxsize, data_size); data_size = SPA_MIN(data_size, SPA_ROUND_DOWN(datas[0].maxsize, port->frame_size)); buf = spa_bt_decode_buffer_get_read(&port->buffer, &avail); avail = SPA_MIN(avail, data_size); spa_list_remove(&buffer->link); spa_log_trace(this->log, "dequeue %d", buffer->id); if (buffer->h) { buffer->h->seq = this->sample_count; buffer->h->pts = this->now; buffer->h->dts_offset = 0; } datas[0].chunk->offset = 0; datas[0].chunk->size = data_size; datas[0].chunk->stride = port->frame_size; memcpy(datas[0].data, buf, avail); spa_bt_decode_buffer_read(&port->buffer, avail); /* Pad with silence, if PLC failed to produce enough */ if (avail < data_size) memset(SPA_PTROFF(datas[0].data, avail, void), 0, data_size - avail); this->sample_count += samples; /* ready buffer if full */ spa_log_trace(this->log, "queue %d frames:%d", buffer->id, (int)samples); spa_list_append(&port->ready, &buffer->link); } if (this->transport->iso_io && this->position) spa_bt_iso_io_check_rx_sync(this->transport->iso_io, this->position->clock.position); if (this->update_delay_event) { int32_t target = spa_bt_decode_buffer_get_target_latency(&port->buffer); uint32_t decoder_delay = 0; uint32_t duration = this->position ? this->position->clock.duration : 1024; if (this->codec->get_delay) this->codec->get_delay(this->codec_data, NULL, &decoder_delay); target += decoder_delay; if (target != this->delay.buffer || duration != this->delay.duration) { struct delay_info info = { .buffer = target, .duration = duration }; __atomic_store_n(&this->delay.v, info.v, __ATOMIC_RELAXED); spa_loop_utils_signal_event(this->loop_utils, this->update_delay_event); } } } static int produce_buffer(struct impl *this) { struct buffer *buffer; struct port *port = &this->port; struct spa_io_buffers *io = port->io; if (io == NULL) return -EIO; /* Return if we already have a buffer */ if (io->status == SPA_STATUS_HAVE_DATA && (this->following || port->rate_match == NULL)) return SPA_STATUS_HAVE_DATA; /* Recycle */ if (io->buffer_id < port->n_buffers) { recycle_buffer(this, port, io->buffer_id); io->buffer_id = SPA_ID_INVALID; } if (this->io_error) { io->status = -EIO; return SPA_STATUS_STOPPED; } /* Handle buffering */ if (this->transport_started) process_buffering(this); /* Return if there are no buffers ready to be processed */ if (spa_list_is_empty(&port->ready)) return SPA_STATUS_OK; /* Get the new buffer from the ready list */ buffer = spa_list_first(&port->ready, struct buffer, link); spa_list_remove(&buffer->link); buffer->outstanding = true; /* Set the new buffer in IO */ io->buffer_id = buffer->id; io->status = SPA_STATUS_HAVE_DATA; /* Notify we have a buffer ready to be processed */ return SPA_STATUS_HAVE_DATA; } static int impl_node_process(void *object) { struct impl *this = object; struct port *port; struct spa_io_buffers *io; spa_return_val_if_fail(this != NULL, -EINVAL); port = &this->port; if ((io = port->io) == NULL) return -EIO; if (!this->started || !this->transport_started) return SPA_STATUS_OK; spa_log_trace(this->log, "%p status:%d", this, io->status); /* Return if we already have a buffer */ if (io->status == SPA_STATUS_HAVE_DATA) return SPA_STATUS_HAVE_DATA; /* Recycle */ if (io->buffer_id < port->n_buffers) { recycle_buffer(this, port, io->buffer_id); io->buffer_id = SPA_ID_INVALID; } /* Follower produces buffers here, driver in timeout */ if (this->following) return produce_buffer(this); else return SPA_STATUS_OK; } static const struct spa_node_methods impl_node = { SPA_VERSION_NODE_METHODS, .add_listener = impl_node_add_listener, .set_callbacks = impl_node_set_callbacks, .sync = impl_node_sync, .enum_params = impl_node_enum_params, .set_param = impl_node_set_param, .set_io = impl_node_set_io, .send_command = impl_node_send_command, .add_port = impl_node_add_port, .remove_port = impl_node_remove_port, .port_enum_params = impl_node_port_enum_params, .port_set_param = impl_node_port_set_param, .port_use_buffers = impl_node_port_use_buffers, .port_set_io = impl_node_port_set_io, .port_reuse_buffer = impl_node_port_reuse_buffer, .process = impl_node_process, }; static void transport_state_changed(void *data, enum spa_bt_transport_state old, enum spa_bt_transport_state state) { struct impl *this = data; spa_log_debug(this->log, "%p: transport %p state %d->%d", this, this->transport, old, state); if (state == SPA_BT_TRANSPORT_STATE_ACTIVE) transport_start(this); else transport_stop(this); if (state == SPA_BT_TRANSPORT_STATE_ERROR) { uint8_t buffer[1024]; struct spa_pod_builder b = { 0 }; spa_pod_builder_init(&b, buffer, sizeof(buffer)); spa_node_emit_event(&this->hooks, spa_pod_builder_add_object(&b, SPA_TYPE_EVENT_Node, SPA_NODE_EVENT_Error)); } } static void transport_delay_changed(void *data) { struct impl *this = data; spa_log_debug(this->log, "transport %p delay changed", this->transport); set_latency(this, true); } static int do_transport_destroy(struct spa_loop *loop, bool async, uint32_t seq, const void *data, size_t size, void *user_data) { struct impl *this = user_data; this->transport = NULL; return 0; } static void transport_destroy(void *data) { struct impl *this = data; spa_log_debug(this->log, "transport %p destroy", this->transport); spa_loop_locked(this->data_loop, do_transport_destroy, 0, NULL, 0, this); } static const struct spa_bt_transport_events transport_events = { SPA_VERSION_BT_TRANSPORT_EVENTS, .delay_changed = transport_delay_changed, .state_changed = transport_state_changed, .destroy = transport_destroy, }; static int impl_get_interface(struct spa_handle *handle, const char *type, void **interface) { struct impl *this; spa_return_val_if_fail(handle != NULL, -EINVAL); spa_return_val_if_fail(interface != NULL, -EINVAL); this = (struct impl *) handle; if (spa_streq(type, SPA_TYPE_INTERFACE_Node)) *interface = &this->node; else return -ENOENT; return 0; } static int impl_clear(struct spa_handle *handle) { struct impl *this = (struct impl *) handle; struct port *port = &this->port; do_stop(this); if (this->codec_props && this->codec->clear_props) this->codec->clear_props(this->codec_props); if (this->transport) spa_hook_remove(&this->transport_listener); spa_system_close(this->data_system, this->timerfd); spa_bt_decode_buffer_clear(&port->buffer); return 0; } static size_t impl_get_size(const struct spa_handle_factory *factory, const struct spa_dict *params) { return sizeof(struct impl); } static int impl_init(const struct spa_handle_factory *factory, struct spa_handle *handle, const struct spa_dict *info, const struct spa_support *support, uint32_t n_support) { struct impl *this; struct port *port; const char *str; spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(handle != NULL, -EINVAL); handle->get_interface = impl_get_interface; handle->clear = impl_clear; this = (struct impl *) handle; this->log = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log); this->data_loop = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_DataLoop); this->data_system = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_DataSystem); this->loop_utils = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_LoopUtils); spa_log_topic_init(this->log, &log_topic); if (this->data_loop == NULL) { spa_log_error(this->log, "a data loop is needed"); return -EINVAL; } if (this->data_system == NULL) { spa_log_error(this->log, "a data system is needed"); return -EINVAL; } if (this->loop_utils == NULL) { spa_log_error(this->log, "loop utils are needed"); return -EINVAL; } this->node.iface = SPA_INTERFACE_INIT( SPA_TYPE_INTERFACE_Node, SPA_VERSION_NODE, &impl_node, this); spa_hook_list_init(&this->hooks); reset_props(&this->props); /* set the node info */ this->info_all = SPA_NODE_CHANGE_MASK_FLAGS | SPA_NODE_CHANGE_MASK_PROPS | SPA_NODE_CHANGE_MASK_PARAMS; this->info = SPA_NODE_INFO_INIT(); this->info.max_input_ports = 0; this->info.max_output_ports = 1; this->info.flags = SPA_NODE_FLAG_RT; this->params[IDX_PropInfo] = SPA_PARAM_INFO(SPA_PARAM_PropInfo, SPA_PARAM_INFO_READ); this->params[IDX_Props] = SPA_PARAM_INFO(SPA_PARAM_Props, SPA_PARAM_INFO_READWRITE); this->params[IDX_NODE_IO] = SPA_PARAM_INFO(SPA_PARAM_IO, SPA_PARAM_INFO_READ); this->info.params = this->params; this->info.n_params = N_NODE_PARAMS; /* set the port info */ port = &this->port; port->info_all = SPA_PORT_CHANGE_MASK_FLAGS | SPA_PORT_CHANGE_MASK_PARAMS; port->info = SPA_PORT_INFO_INIT(); port->info.change_mask = SPA_PORT_CHANGE_MASK_FLAGS; port->info.flags = SPA_PORT_FLAG_LIVE | SPA_PORT_FLAG_PHYSICAL | SPA_PORT_FLAG_TERMINAL; port->params[IDX_EnumFormat] = SPA_PARAM_INFO(SPA_PARAM_EnumFormat, SPA_PARAM_INFO_READ); port->params[IDX_Meta] = SPA_PARAM_INFO(SPA_PARAM_Meta, SPA_PARAM_INFO_READ); port->params[IDX_IO] = SPA_PARAM_INFO(SPA_PARAM_IO, SPA_PARAM_INFO_READ); port->params[IDX_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); port->params[IDX_Buffers] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); port->params[IDX_Latency] = SPA_PARAM_INFO(SPA_PARAM_Latency, SPA_PARAM_INFO_READWRITE); port->info.params = port->params; port->info.n_params = N_PORT_PARAMS; port->latency[SPA_DIRECTION_INPUT] = SPA_LATENCY_INFO(SPA_DIRECTION_INPUT); port->latency[SPA_DIRECTION_OUTPUT] = SPA_LATENCY_INFO(SPA_DIRECTION_OUTPUT); /* Init the buffer lists */ spa_list_init(&port->ready); spa_list_init(&port->free); this->quantum_limit = 8192; if (info && (str = spa_dict_lookup(info, SPA_KEY_API_BLUEZ5_TRANSPORT)) != NULL) sscanf(str, "pointer:%p", &this->transport); if (this->transport == NULL) { spa_log_error(this->log, "a transport is needed"); return -EINVAL; } if (this->transport->media_codec == NULL) { spa_log_error(this->log, "a transport codec is needed"); return -EINVAL; } this->codec = this->transport->media_codec; if (this->transport->profile & SPA_BT_PROFILE_HEADSET_HEAD_UNIT) this->is_input = true; if (info) { if ((str = spa_dict_lookup(info, "clock.quantum-limit"))) spa_atou32(str, &this->quantum_limit, 0); if ((str = spa_dict_lookup(info, "bluez5.media-source-role")) != NULL) this->is_input = spa_streq(str, "input"); if ((str = spa_dict_lookup(info, "api.bluez5.a2dp-duplex")) != NULL) this->is_duplex = spa_atob(str); if ((str = spa_dict_lookup(info, "api.bluez5.internal")) != NULL) this->is_internal = spa_atob(str); if ((str = spa_dict_lookup(info, "bluez5.decode-buffer.latency")) != NULL) spa_atou32(str, &this->decode_buffer_target, 0); if ((str = spa_dict_lookup(info, SPA_KEY_NODE_LATENCY)) != NULL) { spa_scnprintf(this->props.latency, sizeof(this->props.latency), "%s", str); this->props.has_latency = true; } if ((str = spa_dict_lookup(info, "node.rate")) != NULL) { spa_scnprintf(this->props.rate, sizeof(this->props.rate), "%s", str); this->props.has_rate = true; } } if (this->is_duplex) { if (!this->codec->duplex_codec) { spa_log_error(this->log, "transport codec doesn't support duplex"); return -EINVAL; } this->codec = this->codec->duplex_codec; this->is_input = true; } if (this->codec->kind == MEDIA_CODEC_BAP) this->is_input = this->transport->bap_initiator; if (this->codec->init_props != NULL) this->codec_props = this->codec->init_props(this->codec, this->is_duplex ? 0 : MEDIA_CODEC_FLAG_SINK, this->transport->device->settings); spa_bt_transport_add_listener(this->transport, &this->transport_listener, &transport_events, this); this->timerfd = spa_system_timerfd_create(this->data_system, CLOCK_MONOTONIC, SPA_FD_CLOEXEC | SPA_FD_NONBLOCK); this->node_latency = 512; set_latency(this, false); this->fd = -1; return 0; } static const struct spa_interface_info impl_interfaces[] = { {SPA_TYPE_INTERFACE_Node,}, }; static int impl_enum_interface_info(const struct spa_handle_factory *factory, const struct spa_interface_info **info, uint32_t *index) { spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(info != NULL, -EINVAL); spa_return_val_if_fail(index != NULL, -EINVAL); switch (*index) { case 0: *info = &impl_interfaces[*index]; break; default: return 0; } (*index)++; return 1; } static const struct spa_dict_item info_items[] = { { SPA_KEY_FACTORY_AUTHOR, "Collabora Ltd. " }, { SPA_KEY_FACTORY_DESCRIPTION, "Capture bluetooth audio with media" }, { SPA_KEY_FACTORY_USAGE, SPA_KEY_API_BLUEZ5_TRANSPORT"=" }, }; static const struct spa_dict info = SPA_DICT_INIT_ARRAY(info_items); const struct spa_handle_factory spa_media_source_factory = { SPA_VERSION_HANDLE_FACTORY, SPA_NAME_API_BLUEZ5_MEDIA_SOURCE, &info, impl_get_size, impl_init, impl_enum_interface_info, }; /* Retained for backward compatibility */ const struct spa_handle_factory spa_a2dp_source_factory = { SPA_VERSION_HANDLE_FACTORY, SPA_NAME_API_BLUEZ5_A2DP_SOURCE, &info, impl_get_size, impl_init, impl_enum_interface_info, }; /* Retained for backward compatibility: */ const struct spa_handle_factory spa_sco_source_factory = { SPA_VERSION_HANDLE_FACTORY, SPA_NAME_API_BLUEZ5_SCO_SOURCE, &info, impl_get_size, impl_init, impl_enum_interface_info, }; pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/bluez5/meson.build000066400000000000000000000201341511204443500263120ustar00rootroot00000000000000gnome = import('gnome') cdata.set('HAVE_BLUEZ_5_BACKEND_NATIVE', get_option('bluez5-backend-hsp-native').allowed() or get_option('bluez5-backend-hfp-native').allowed()) cdata.set('HAVE_BLUEZ_5_BACKEND_HSP_NATIVE', get_option('bluez5-backend-hsp-native').allowed()) cdata.set('HAVE_BLUEZ_5_BACKEND_HFP_NATIVE', get_option('bluez5-backend-hfp-native').allowed()) cdata.set('HAVE_BLUEZ_5_BACKEND_NATIVE_MM', get_option('bluez5-backend-native-mm').allowed()) cdata.set('HAVE_BLUEZ_5_BACKEND_OFONO', get_option('bluez5-backend-ofono').allowed()) cdata.set('HAVE_BLUEZ_5_BACKEND_HSPHFPD', get_option('bluez5-backend-hsphfpd').allowed()) cdata.set('HAVE_BLUEZ_5_HCI', dependency('bluez', version: '< 6', required: false).found()) bluez5_sources = [ 'plugin.c', 'codec-loader.c', 'media-codecs.c', 'media-sink.c', 'media-source.c', 'sco-io.c', 'iso-io.c', 'quirks.c', 'player.c', 'bluez5-device.c', 'bluez5-dbus.c', 'hci.c', 'dbus-monitor.c', 'midi-enum.c', 'midi-parser.c', 'midi-node.c', 'midi-server.c', ] bluez5_interface_src = gnome.gdbus_codegen('bluez5-interface-gen', sources: 'org.bluez.xml', interface_prefix : 'org.bluez.', object_manager: true, namespace : 'Bluez5', annotations : [ ['org.bluez.GattCharacteristic1.AcquireNotify()', 'org.gtk.GDBus.C.UnixFD', 'true'], ['org.bluez.GattCharacteristic1.AcquireWrite()', 'org.gtk.GDBus.C.UnixFD', 'true'], ] ) bluez5_sources += [ bluez5_interface_src ] bluez5_data = ['bluez-hardware.conf'] install_data(bluez5_data, install_dir : spa_datadir / 'bluez5') if get_option('bluez5-backend-hsp-native').allowed() or get_option('bluez5-backend-hfp-native').allowed() if libusb_dep.found() bluez5_deps += libusb_dep endif if mm_dep.found() bluez5_deps += mm_dep bluez5_sources += ['modemmanager.c'] endif bluez5_sources += ['backend-native.c', 'upower.c', 'telephony.c'] endif if get_option('bluez5-backend-ofono').allowed() bluez5_sources += ['backend-ofono.c'] endif if get_option('bluez5-backend-hsphfpd').allowed() bluez5_sources += ['backend-hsphfpd.c'] endif if get_option('bluez5-codec-lc3').allowed() and lc3_dep.found() bluez5_deps += lc3_dep endif # The library uses GObject, and cannot be unloaded bluez5_link_args = [ '-Wl,-z', '-Wl,nodelete' ] bluez5lib = shared_library('spa-bluez5', bluez5_sources, include_directories : [ configinc ], dependencies : [ spa_dep, bluez5_deps ], link_args : bluez5_link_args, install : true, install_dir : spa_plugindir / 'bluez5') codec_args = [ '-DCODEC_PLUGIN' ] bluez_codec_sbc = shared_library('spa-codec-bluez5-sbc', [ 'a2dp-codec-sbc.c', 'media-codecs.c' ], include_directories : [ configinc ], c_args : codec_args, dependencies : [ spa_dep, sbc_dep ], install : true, install_dir : spa_plugindir / 'bluez5') bluez_codec_faststream = shared_library('spa-codec-bluez5-faststream', [ 'a2dp-codec-faststream.c', 'media-codecs.c' ], include_directories : [ configinc ], c_args : codec_args, dependencies : [ spa_dep, sbc_dep ], install : true, install_dir : spa_plugindir / 'bluez5') bluez_codec_hfp_cvsd = shared_library('spa-codec-bluez5-hfp-cvsd', [ 'hfp-codec-cvsd.c', 'media-codecs.c' ], include_directories : [ configinc ], c_args : codec_args, dependencies : [ spa_dep ], install : true, install_dir : spa_plugindir / 'bluez5') bluez_codec_hfp_msbc = shared_library('spa-codec-bluez5-hfp-msbc', [ 'hfp-codec-msbc.c', 'media-codecs.c' ], include_directories : [ configinc ], c_args : codec_args, dependencies : [ spa_dep, sbc_dep, spandsp_dep ], install : true, install_dir : spa_plugindir / 'bluez5') if fdk_aac_dep.found() bluez_codec_aac = shared_library('spa-codec-bluez5-aac', [ 'a2dp-codec-aac.c', 'media-codecs.c' ], include_directories : [ configinc ], c_args : codec_args, dependencies : [ spa_dep, fdk_aac_dep ], install : true, install_dir : spa_plugindir / 'bluez5') endif if aptx_dep.found() bluez_codec_aptx = shared_library('spa-codec-bluez5-aptx', [ 'a2dp-codec-aptx.c', 'media-codecs.c' ], include_directories : [ configinc ], c_args : codec_args, dependencies : [ spa_dep, aptx_dep, sbc_dep ], install : true, install_dir : spa_plugindir / 'bluez5') endif if ldac_dep.found() ldac_args = codec_args if ldac_abr_dep.found() ldac_args += [ '-DENABLE_LDAC_ABR' ] endif if get_option('bluez5-codec-ldac-dec').allowed() and ldac_dec_dep.found() ldac_args += [ '-DENABLE_LDAC_DEC' ] ldac_dep = [ldac_dep, ldac_dec_dep] endif bluez_codec_ldac = shared_library('spa-codec-bluez5-ldac', [ 'a2dp-codec-ldac.c', 'media-codecs.c' ], include_directories : [ configinc ], c_args : ldac_args, dependencies : [ spa_dep, ldac_dep, ldac_abr_dep ], install : true, install_dir : spa_plugindir / 'bluez5') endif if get_option('bluez5-codec-lc3plus').allowed() and lc3plus_dep.found() bluez_codec_lc3plus = shared_library('spa-codec-bluez5-lc3plus', [ 'a2dp-codec-lc3plus.c', 'media-codecs.c' ], include_directories : [ configinc ], c_args : codec_args, dependencies : [ spa_dep, lc3plus_dep, mathlib ], install : true, install_dir : spa_plugindir / 'bluez5') endif if get_option('bluez5-codec-opus').allowed() and opus_dep.found() opus_args = codec_args bluez_codec_opus = shared_library('spa-codec-bluez5-opus', [ 'a2dp-codec-opus.c', 'media-codecs.c' ], include_directories : [ configinc ], c_args : opus_args, dependencies : [ spa_dep, opus_dep, mathlib ], install : true, install_dir : spa_plugindir / 'bluez5') bluez_codec_opus_g = shared_library('spa-codec-bluez5-opus-g', [ 'a2dp-codec-opus-g.c', 'media-codecs.c' ], include_directories : [ configinc ], c_args : opus_args, dependencies : [ spa_dep, opus_dep, mathlib ], install : true, install_dir : spa_plugindir / 'bluez5') endif if get_option('bluez5-codec-lc3').allowed() and lc3_dep.found() bluez_codec_lc3 = shared_library('spa-codec-bluez5-lc3', [ 'bap-codec-lc3.c', 'media-codecs.c' ], include_directories : [ configinc ], c_args : codec_args, dependencies : [ spa_dep, lc3_dep, mathlib ], install : true, install_dir : spa_plugindir / 'bluez5') bluez_codec_hfp_lc3_swb = shared_library('spa-codec-bluez5-hfp-lc3-swb', [ 'hfp-codec-lc3-swb.c', 'media-codecs.c' ], include_directories : [ configinc ], c_args : codec_args, dependencies : [ spa_dep, lc3_dep, mathlib ], install : true, install_dir : spa_plugindir / 'bluez5') bluez_codec_hfp_lc3_a127 = shared_library('spa-codec-bluez5-hfp-lc3-a127', [ 'hfp-codec-lc3-a127.c', 'media-codecs.c' ], include_directories : [ configinc ], c_args : codec_args, dependencies : [ spa_dep, lc3_dep, mathlib ], install : true, install_dir : spa_plugindir / 'bluez5') endif if get_option('bluez5-codec-g722').allowed() bluez_codec_g722 = shared_library('spa-codec-bluez5-g722', [ 'g722/g722_encode.c', 'asha-codec-g722.c', 'media-codecs.c' ], include_directories : [ configinc ], c_args : codec_args, dependencies : [ spa_dep ], install : true, install_dir : spa_plugindir / 'bluez5') endif test_apps = [ 'test-midi', ] bluez5_test_lib = static_library('bluez5_test_lib', [ 'midi-parser.c' ], include_directories : [ configinc ], dependencies : [ spa_dep, bluez5_deps ], install : false ) foreach a : test_apps test(a, executable(a, a + '.c', dependencies : [ spa_dep, dl_lib, pthread_lib, mathlib, bluez5_deps ], include_directories : [ configinc ], link_with : [ bluez5_test_lib ], install_rpath : spa_plugindir / 'bluez5', install : installed_tests_enabled, install_dir : installed_tests_execdir / 'bluez5'), env : [ 'SPA_PLUGIN_DIR=@0@'.format(spa_dep.get_variable('plugindir')), ]) if installed_tests_enabled test_conf = configuration_data() test_conf.set('exec', installed_tests_execdir / 'bluez5' / a) configure_file( input: installed_tests_template, output: a + '.test', install_dir: installed_tests_metadir / 'bluez5', configuration: test_conf ) endif endforeach pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/bluez5/midi-enum.c000066400000000000000000000554361511204443500262150ustar00rootroot00000000000000/* Spa midi dbus */ /* SPDX-FileCopyrightText: Copyright © 2022 Pauli Virtanen */ /* SPDX-License-Identifier: MIT */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "midi.h" #include "bluez5-interface-gen.h" #include "dbus-monitor.h" #define MIDI_OBJECT_PATH "/midi" #define MIDI_PROFILE_PATH MIDI_OBJECT_PATH "/profile" SPA_LOG_TOPIC_DEFINE_STATIC(log_topic, "spa.bluez5.midi"); #undef SPA_LOG_TOPIC_DEFAULT #define SPA_LOG_TOPIC_DEFAULT &log_topic struct impl { struct spa_handle handle; struct spa_device device; struct spa_log *log; GDBusConnection *conn; struct dbus_monitor monitor; GDBusObjectManagerServer *manager; struct spa_hook_list hooks; uint32_t id; }; struct _MidiEnumCharacteristicProxy { Bluez5GattCharacteristic1Proxy parent_instance; struct impl *impl; gchar *description; uint32_t id; GCancellable *read_call; GCancellable *dsc_call; unsigned int node_emitted:1; unsigned int read_probed:1; unsigned int read_done:1; unsigned int dsc_probed:1; unsigned int dsc_done:1; }; G_DECLARE_FINAL_TYPE(MidiEnumCharacteristicProxy, midi_enum_characteristic_proxy, MIDI_ENUM, CHARACTERISTIC_PROXY, Bluez5GattCharacteristic1Proxy) G_DEFINE_TYPE(MidiEnumCharacteristicProxy, midi_enum_characteristic_proxy, BLUEZ5_TYPE_GATT_CHARACTERISTIC1_PROXY) #define MIDI_ENUM_TYPE_CHARACTERISTIC_PROXY (midi_enum_characteristic_proxy_get_type()) struct _MidiEnumManagerProxy { Bluez5GattManager1Proxy parent_instance; GCancellable *register_call; unsigned int registered:1; }; G_DECLARE_FINAL_TYPE(MidiEnumManagerProxy, midi_enum_manager_proxy, MIDI_ENUM, MANAGER_PROXY, Bluez5GattManager1Proxy) G_DEFINE_TYPE(MidiEnumManagerProxy, midi_enum_manager_proxy, BLUEZ5_TYPE_GATT_MANAGER1_PROXY) #define MIDI_ENUM_TYPE_MANAGER_PROXY (midi_enum_manager_proxy_get_type()) static void emit_chr_node(struct impl *impl, MidiEnumCharacteristicProxy *chr, Bluez5Device1 *device) { struct spa_device_object_info info; char nick[512], class[16]; struct spa_dict_item items[23]; uint32_t n_items = 0; const char *path = g_dbus_proxy_get_object_path(G_DBUS_PROXY(chr)); const char *alias = bluez5_device1_get_alias(device); spa_log_debug(impl->log, "emit node for path=%s", path); info = SPA_DEVICE_OBJECT_INFO_INIT(); info.type = SPA_TYPE_INTERFACE_Node; info.factory_name = SPA_NAME_API_BLUEZ5_MIDI_NODE; info.change_mask = SPA_DEVICE_OBJECT_CHANGE_MASK_FLAGS | SPA_DEVICE_OBJECT_CHANGE_MASK_PROPS; info.flags = 0; items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_API, "bluez5"); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_BUS, "bluetooth"); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_MEDIA_CLASS, "Midi/Bridge"); items[n_items++] = SPA_DICT_ITEM_INIT("node.description", alias ? alias : bluez5_device1_get_name(device)); if (chr->description && chr->description[0] != '\0') { spa_scnprintf(nick, sizeof(nick), "%s (%s)", alias, chr->description); items[n_items++] = SPA_DICT_ITEM_INIT("node.nick", nick); } items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_API_BLUEZ5_ICON, bluez5_device1_get_icon(device)); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_API_BLUEZ5_PATH, path); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_API_BLUEZ5_ADDRESS, bluez5_device1_get_address(device)); snprintf(class, sizeof(class), "0x%06x", bluez5_device1_get_class(device)); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_API_BLUEZ5_CLASS, class); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_API_BLUEZ5_ROLE, "client"); info.props = &SPA_DICT_INIT(items, n_items); spa_device_emit_object_info(&impl->hooks, chr->id, &info); } static void remove_chr_node(struct impl *impl, MidiEnumCharacteristicProxy *chr) { spa_log_debug(impl->log, "remove node for path=%s", g_dbus_proxy_get_object_path(G_DBUS_PROXY(chr))); spa_device_emit_object_info(&impl->hooks, chr->id, NULL); } static void check_chr_node(struct impl *impl, MidiEnumCharacteristicProxy *chr); static void read_probe_reply(GObject *source_object, GAsyncResult *res, gpointer user_data) { MidiEnumCharacteristicProxy *chr = MIDI_ENUM_CHARACTERISTIC_PROXY(source_object); struct impl *impl = user_data; gchar *value = NULL; GError *err = NULL; bluez5_gatt_characteristic1_call_read_value_finish( BLUEZ5_GATT_CHARACTERISTIC1(source_object), &value, res, &err); if (g_error_matches(err, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { /* Operation canceled: user_data may be invalid by now */ g_error_free(err); goto done; } if (err) { spa_log_error(impl->log, "%s.ReadValue() failed: %s", BLUEZ_GATT_CHR_INTERFACE, err->message); g_error_free(err); goto done; } g_free(value); spa_log_debug(impl->log, "MIDI GATT read probe done for path=%s", g_dbus_proxy_get_object_path(G_DBUS_PROXY(chr))); chr->read_done = true; check_chr_node(impl, chr); done: g_clear_object(&chr->read_call); } static int read_probe(struct impl *impl, MidiEnumCharacteristicProxy *chr) { GVariantBuilder builder; GVariant *options; /* * BLE MIDI-1.0 §5: The Central shall read the MIDI I/O characteristic * of the Peripheral after establishing a connection with the accessory. */ if (chr->read_probed) return 0; if (chr->read_call) return -EBUSY; chr->read_probed = true; spa_log_debug(impl->log, "MIDI GATT read probe for path=%s", g_dbus_proxy_get_object_path(G_DBUS_PROXY(chr))); chr->read_call = g_cancellable_new(); g_variant_builder_init(&builder, G_VARIANT_TYPE("a{sv}")); options = g_variant_builder_end(&builder); bluez5_gatt_characteristic1_call_read_value(BLUEZ5_GATT_CHARACTERISTIC1(chr), options, chr->read_call, read_probe_reply, impl); return 0; } static Bluez5GattDescriptor1 *find_dsc(struct impl *impl, MidiEnumCharacteristicProxy *chr) { const char *path = g_dbus_proxy_get_object_path(G_DBUS_PROXY(chr)); Bluez5GattDescriptor1 *found = NULL;; GList *objects; objects = g_dbus_object_manager_get_objects(dbus_monitor_manager(&impl->monitor)); for (GList *llo = g_list_first(objects); llo; llo = llo->next) { GList *interfaces = g_dbus_object_get_interfaces(G_DBUS_OBJECT(llo->data)); for (GList *lli = g_list_first(interfaces); lli; lli = lli->next) { Bluez5GattDescriptor1 *dsc; if (!BLUEZ5_IS_GATT_DESCRIPTOR1(lli->data)) continue; dsc = BLUEZ5_GATT_DESCRIPTOR1(lli->data); if (!spa_streq(bluez5_gatt_descriptor1_get_uuid(dsc), BT_GATT_CHARACTERISTIC_USER_DESCRIPTION_UUID)) continue; if (spa_streq(bluez5_gatt_descriptor1_get_characteristic(dsc), path)) { found = dsc; break; } } g_list_free_full(interfaces, g_object_unref); if (found) break; } g_list_free_full(objects, g_object_unref); return found; } static void read_dsc_reply(GObject *source_object, GAsyncResult *res, gpointer user_data) { MidiEnumCharacteristicProxy *chr = MIDI_ENUM_CHARACTERISTIC_PROXY(user_data); struct impl *impl = chr->impl; gchar *value = NULL; GError *err = NULL; chr->dsc_done = true; bluez5_gatt_descriptor1_call_read_value_finish( BLUEZ5_GATT_DESCRIPTOR1(source_object), &value, res, &err); if (g_error_matches(err, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { /* Operation canceled: user_data may be invalid by now */ g_error_free(err); goto done; } if (err) { spa_log_error(impl->log, "%s.ReadValue() failed: %s", BLUEZ_GATT_DSC_INTERFACE, err->message); g_error_free(err); goto done; } spa_log_debug(impl->log, "MIDI GATT read probe done for path=%s", g_dbus_proxy_get_object_path(G_DBUS_PROXY(chr))); g_free(chr->description); chr->description = value; spa_log_debug(impl->log, "MIDI GATT user descriptor value: '%s'", chr->description); check_chr_node(impl, chr); done: g_clear_object(&chr->dsc_call); } static int read_dsc(struct impl *impl, MidiEnumCharacteristicProxy *chr) { Bluez5GattDescriptor1 *dsc; GVariant *options; GVariantBuilder builder; if (chr->dsc_probed) return 0; if (chr->dsc_call) return -EBUSY; chr->dsc_probed = true; dsc = find_dsc(impl, chr); if (dsc == NULL) { chr->dsc_done = true; return -ENOENT; } spa_log_debug(impl->log, "MIDI GATT user descriptor read, path=%s", g_dbus_proxy_get_object_path(G_DBUS_PROXY(dsc))); chr->dsc_call = g_cancellable_new(); g_variant_builder_init(&builder, G_VARIANT_TYPE("a{sv}")); options = g_variant_builder_end(&builder); bluez5_gatt_descriptor1_call_read_value(BLUEZ5_GATT_DESCRIPTOR1(dsc), options, chr->dsc_call, read_dsc_reply, chr); return 0; } static int read_probe_reset(struct impl *impl, MidiEnumCharacteristicProxy *chr) { g_cancellable_cancel(chr->read_call); g_clear_object(&chr->read_call); g_cancellable_cancel(chr->dsc_call); g_clear_object(&chr->dsc_call); chr->read_probed = false; chr->read_done = false; chr->dsc_probed = false; chr->dsc_done = false; return 0; } static void lookup_chr_node(struct impl *impl, MidiEnumCharacteristicProxy *chr, Bluez5GattService1 **service, Bluez5Device1 **device) { GDBusObject *object; const char *service_path; const char *device_path; *service = NULL; *device = NULL; service_path = bluez5_gatt_characteristic1_get_service(BLUEZ5_GATT_CHARACTERISTIC1(chr)); if (!service_path) return; object = g_dbus_object_manager_get_object(dbus_monitor_manager(&impl->monitor), service_path); if (object) { GDBusInterface *iface = g_dbus_object_get_interface(object, BLUEZ_GATT_SERVICE_INTERFACE); *service = BLUEZ5_GATT_SERVICE1(iface); } if (!*service) return; device_path = bluez5_gatt_service1_get_device(*service); if (!device_path) return; object = g_dbus_object_manager_get_object(dbus_monitor_manager(&impl->monitor), device_path); if (object) { GDBusInterface *iface = g_dbus_object_get_interface(object, BLUEZ_DEVICE_INTERFACE); *device = BLUEZ5_DEVICE1(iface); } } static void check_chr_node(struct impl *impl, MidiEnumCharacteristicProxy *chr) { Bluez5GattService1 *service; Bluez5Device1 *device; bool available; lookup_chr_node(impl, chr, &service, &device); if (!device || !bluez5_device1_get_connected(device)) { /* Retry read probe on each connection */ read_probe_reset(impl, chr); } spa_log_debug(impl->log, "At %s, connected:%d resolved:%d", g_dbus_proxy_get_object_path(G_DBUS_PROXY(chr)), bluez5_device1_get_connected(device), bluez5_device1_get_services_resolved(device)); available = service && device && bluez5_device1_get_connected(device) && bluez5_device1_get_services_resolved(device) && spa_streq(bluez5_gatt_service1_get_uuid(service), BT_MIDI_SERVICE_UUID) && spa_streq(bluez5_gatt_characteristic1_get_uuid(BLUEZ5_GATT_CHARACTERISTIC1(chr)), BT_MIDI_CHR_UUID); if (available && !chr->read_done) { read_probe(impl, chr); available = false; } if (available && !chr->dsc_done) { read_dsc(impl, chr); available = chr->dsc_done; } if (chr->node_emitted && !available) { remove_chr_node(impl, chr); chr->node_emitted = false; } else if (!chr->node_emitted && available) { emit_chr_node(impl, chr, device); chr->node_emitted = true; } } static GList *get_all_valid_chr(struct impl *impl) { GList *lst = NULL; GList *objects; if (!dbus_monitor_manager(&impl->monitor)) { /* Still initializing (or it failed) */ return NULL; } objects = g_dbus_object_manager_get_objects(dbus_monitor_manager(&impl->monitor)); for (GList *p = g_list_first(objects); p; p = p->next) { GList *interfaces = g_dbus_object_get_interfaces(G_DBUS_OBJECT(p->data)); for (GList *p2 = g_list_first(interfaces); p2; p2 = p2->next) { MidiEnumCharacteristicProxy *chr; if (!MIDI_ENUM_IS_CHARACTERISTIC_PROXY(p2->data)) continue; chr = MIDI_ENUM_CHARACTERISTIC_PROXY(p2->data); if (chr->impl == NULL) continue; lst = g_list_append(lst, g_object_ref(chr)); } g_list_free_full(interfaces, g_object_unref); } g_list_free_full(objects, g_object_unref); return lst; } static void check_all_nodes(struct impl *impl) { /* * Check if the nodes we have emitted are in sync with connected devices. */ GList *chrs = get_all_valid_chr(impl); for (GList *p = chrs; p; p = p->next) check_chr_node(impl, MIDI_ENUM_CHARACTERISTIC_PROXY(p->data)); g_list_free_full(chrs, g_object_unref); } static void manager_register_application_reply(GObject *source_object, GAsyncResult *res, gpointer user_data) { MidiEnumManagerProxy *manager = MIDI_ENUM_MANAGER_PROXY(source_object); struct impl *impl = user_data; GError *err = NULL; bluez5_gatt_manager1_call_register_application_finish( BLUEZ5_GATT_MANAGER1(source_object), res, &err); if (g_error_matches(err, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { /* Operation canceled: user_data may be invalid by now */ g_error_free(err); goto done; } if (err) { spa_log_error(impl->log, "%s.RegisterApplication() failed: %s", BLUEZ_GATT_MANAGER_INTERFACE, err->message); g_error_free(err); goto done; } manager->registered = true; done: g_clear_object(&manager->register_call); } static int manager_register_application(struct impl *impl, MidiEnumManagerProxy *manager) { GVariantBuilder builder; GVariant *options; if (manager->registered) return 0; if (manager->register_call) return -EBUSY; spa_log_debug(impl->log, "%s.RegisterApplication(%s) on %s", BLUEZ_GATT_MANAGER_INTERFACE, g_dbus_object_manager_get_object_path(G_DBUS_OBJECT_MANAGER(impl->manager)), g_dbus_proxy_get_object_path(G_DBUS_PROXY(manager))); manager->register_call = g_cancellable_new(); g_variant_builder_init(&builder, G_VARIANT_TYPE("a{sv}")); options = g_variant_builder_end(&builder); bluez5_gatt_manager1_call_register_application(BLUEZ5_GATT_MANAGER1(manager), g_dbus_object_manager_get_object_path(G_DBUS_OBJECT_MANAGER(impl->manager)), options, manager->register_call, manager_register_application_reply, impl); return 0; } /* * DBus monitoring (Glib) */ static void midi_enum_characteristic_proxy_init(MidiEnumCharacteristicProxy *chr) { } static void midi_enum_characteristic_proxy_finalize(GObject *object) { MidiEnumCharacteristicProxy *chr = MIDI_ENUM_CHARACTERISTIC_PROXY(object); g_cancellable_cancel(chr->read_call); g_clear_object(&chr->read_call); g_cancellable_cancel(chr->dsc_call); g_clear_object(&chr->dsc_call); if (chr->impl && chr->node_emitted) remove_chr_node(chr->impl, chr); chr->impl = NULL; g_free(chr->description); chr->description = NULL; } static void midi_enum_characteristic_proxy_class_init(MidiEnumCharacteristicProxyClass *klass) { GObjectClass *object_class = (GObjectClass *) klass; object_class->finalize = midi_enum_characteristic_proxy_finalize; } static void midi_enum_manager_proxy_init(MidiEnumManagerProxy *manager) { } static void midi_enum_manager_proxy_finalize(GObject *object) { MidiEnumManagerProxy *manager = MIDI_ENUM_MANAGER_PROXY(object); g_cancellable_cancel(manager->register_call); g_clear_object(&manager->register_call); } static void midi_enum_manager_proxy_class_init(MidiEnumManagerProxyClass *klass) { GObjectClass *object_class = (GObjectClass *) klass; object_class->finalize = midi_enum_manager_proxy_finalize; } static void manager_update(struct dbus_monitor *monitor, GDBusInterface *iface) { struct impl *impl = SPA_CONTAINER_OF(monitor, struct impl, monitor); manager_register_application(impl, MIDI_ENUM_MANAGER_PROXY(iface)); } static void manager_clear(struct dbus_monitor *monitor, GDBusInterface *iface) { midi_enum_manager_proxy_finalize(G_OBJECT(iface)); } static void device_update(struct dbus_monitor *monitor, GDBusInterface *iface) { struct impl *impl = SPA_CONTAINER_OF(monitor, struct impl, monitor); check_all_nodes(impl); } static void service_update(struct dbus_monitor *monitor, GDBusInterface *iface) { struct impl *impl = SPA_CONTAINER_OF(monitor, struct impl, monitor); Bluez5GattService1 *service = BLUEZ5_GATT_SERVICE1(iface); if (!spa_streq(bluez5_gatt_service1_get_uuid(service), BT_MIDI_SERVICE_UUID)) return; check_all_nodes(impl); } static void chr_update(struct dbus_monitor *monitor, GDBusInterface *iface) { struct impl *impl = SPA_CONTAINER_OF(monitor, struct impl, monitor); MidiEnumCharacteristicProxy *chr = MIDI_ENUM_CHARACTERISTIC_PROXY(iface); if (!spa_streq(bluez5_gatt_characteristic1_get_uuid(BLUEZ5_GATT_CHARACTERISTIC1(chr)), BT_MIDI_CHR_UUID)) return; if (chr->impl == NULL) { chr->impl = impl; chr->id = ++impl->id; } check_chr_node(impl, chr); } static void chr_clear(struct dbus_monitor *monitor, GDBusInterface *iface) { midi_enum_characteristic_proxy_finalize(G_OBJECT(iface)); } static void monitor_start(struct impl *impl) { struct dbus_monitor_proxy_type proxy_types[] = { { BLUEZ_DEVICE_INTERFACE, BLUEZ5_TYPE_DEVICE1_PROXY, device_update, NULL }, { BLUEZ_GATT_MANAGER_INTERFACE, MIDI_ENUM_TYPE_MANAGER_PROXY, manager_update, manager_clear }, { BLUEZ_GATT_SERVICE_INTERFACE, BLUEZ5_TYPE_GATT_SERVICE1_PROXY, service_update, NULL }, { BLUEZ_GATT_CHR_INTERFACE, MIDI_ENUM_TYPE_CHARACTERISTIC_PROXY, chr_update, chr_clear }, { BLUEZ_GATT_DSC_INTERFACE, BLUEZ5_TYPE_GATT_DESCRIPTOR1_PROXY, NULL, NULL }, { NULL, BLUEZ5_TYPE_OBJECT_PROXY, NULL, NULL }, { NULL, G_TYPE_INVALID, NULL, NULL } }; SPA_STATIC_ASSERT(SPA_N_ELEMENTS(proxy_types) <= DBUS_MONITOR_MAX_TYPES); dbus_monitor_init(&impl->monitor, BLUEZ5_TYPE_OBJECT_MANAGER_CLIENT, impl->log, impl->conn, BLUEZ_SERVICE, "/", proxy_types, NULL); } /* * DBus GATT profile, to enable BlueZ autoconnect */ static gboolean profile_handle_release(Bluez5GattProfile1 *iface, GDBusMethodInvocation *invocation) { bluez5_gatt_profile1_complete_release(iface, invocation); return TRUE; } static int export_profile(struct impl *impl) { static const char *uuids[] = { BT_MIDI_SERVICE_UUID, NULL }; GDBusObjectSkeleton *skeleton = NULL; Bluez5GattProfile1 *iface = NULL; int res = -ENOMEM; iface = bluez5_gatt_profile1_skeleton_new(); if (!iface) goto done; skeleton = g_dbus_object_skeleton_new(MIDI_PROFILE_PATH); if (!skeleton) goto done; g_dbus_object_skeleton_add_interface(skeleton, G_DBUS_INTERFACE_SKELETON(iface)); bluez5_gatt_profile1_set_uuids(iface, uuids); g_signal_connect(iface, "handle-release", G_CALLBACK(profile_handle_release), NULL); g_dbus_object_manager_server_export(impl->manager, skeleton); spa_log_debug(impl->log, "MIDI GATT Profile exported, path=%s", g_dbus_object_get_object_path(G_DBUS_OBJECT(skeleton))); res = 0; done: g_clear_object(&iface); g_clear_object(&skeleton); return res; } /* * Monitor impl */ static int impl_device_add_listener(void *object, struct spa_hook *listener, const struct spa_device_events *events, void *data) { struct impl *this = object; struct spa_hook_list save; GList *chrs; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(events != NULL, -EINVAL); chrs = get_all_valid_chr(this); spa_hook_list_isolate(&this->hooks, &save, listener, events, data); for (GList *p = g_list_first(chrs); p; p = p->next) { MidiEnumCharacteristicProxy *chr = MIDI_ENUM_CHARACTERISTIC_PROXY(p->data); Bluez5Device1 *device; Bluez5GattService1 *service; if (!chr->node_emitted) continue; lookup_chr_node(this, chr, &service, &device); if (device) emit_chr_node(this, chr, device); } g_list_free_full(chrs, g_object_unref); spa_hook_list_join(&this->hooks, &save); return 0; } static const struct spa_device_methods impl_device = { SPA_VERSION_DEVICE_METHODS, .add_listener = impl_device_add_listener, }; static int impl_get_interface(struct spa_handle *handle, const char *type, void **interface) { struct impl *this; spa_return_val_if_fail(handle != NULL, -EINVAL); spa_return_val_if_fail(interface != NULL, -EINVAL); this = (struct impl *) handle; if (spa_streq(type, SPA_TYPE_INTERFACE_Device)) *interface = &this->device; else return -ENOENT; return 0; } static int impl_clear(struct spa_handle *handle) { struct impl *this; this = (struct impl *) handle; dbus_monitor_clear(&this->monitor); g_clear_object(&this->manager); g_clear_object(&this->conn); spa_zero(*this); return 0; } static size_t impl_get_size(const struct spa_handle_factory *factory, const struct spa_dict *params) { return sizeof(struct impl); } static int impl_init(const struct spa_handle_factory *factory, struct spa_handle *handle, const struct spa_dict *info, const struct spa_support *support, uint32_t n_support) { struct impl *this; GError *error = NULL; int res = 0; spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(handle != NULL, -EINVAL); handle->get_interface = impl_get_interface; handle->clear = impl_clear; this = (struct impl *) handle; this->log = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log); if (this->log == NULL) return -EINVAL; spa_log_topic_init(this->log, &log_topic); if (!(info && spa_atob(spa_dict_lookup(info, SPA_KEY_API_GLIB_MAINLOOP)))) { spa_log_error(this->log, "Glib mainloop is not usable: %s not set", SPA_KEY_API_GLIB_MAINLOOP); return -EINVAL; } spa_hook_list_init(&this->hooks); this->device.iface = SPA_INTERFACE_INIT( SPA_TYPE_INTERFACE_Device, SPA_VERSION_DEVICE, &impl_device, this); this->conn = g_bus_get_sync(G_BUS_TYPE_SYSTEM, NULL, &error); if (!this->conn) { spa_log_error(this->log, "Creating GDBus connection failed: %s", error->message); g_error_free(error); goto fail; } g_dbus_connection_set_exit_on_close(this->conn, FALSE); this->manager = g_dbus_object_manager_server_new(MIDI_OBJECT_PATH); if (!this->manager){ spa_log_error(this->log, "Creating GDBus object manager failed"); goto fail; } if ((res = export_profile(this)) < 0) goto fail; g_dbus_object_manager_server_set_connection(this->manager, this->conn); monitor_start(this); return 0; fail: res = (res < 0) ? res : ((errno > 0) ? -errno : -EIO); impl_clear(handle); return res; } static const struct spa_interface_info impl_interfaces[] = { {SPA_TYPE_INTERFACE_Device,}, }; static int impl_enum_interface_info(const struct spa_handle_factory *factory, const struct spa_interface_info **info, uint32_t *index) { spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(info != NULL, -EINVAL); spa_return_val_if_fail(index != NULL, -EINVAL); if (*index >= SPA_N_ELEMENTS(impl_interfaces)) return 0; *info = &impl_interfaces[(*index)++]; return 1; } static const struct spa_dict_item info_items[] = { { SPA_KEY_FACTORY_AUTHOR, "Pauli Virtanen " }, { SPA_KEY_FACTORY_DESCRIPTION, "Bluez5 MIDI connection" }, }; static const struct spa_dict info = SPA_DICT_INIT_ARRAY(info_items); const struct spa_handle_factory spa_bluez5_midi_enum_factory = { SPA_VERSION_HANDLE_FACTORY, SPA_NAME_API_BLUEZ5_MIDI_ENUM, &info, impl_get_size, impl_init, impl_enum_interface_info, }; pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/bluez5/midi-node.c000066400000000000000000001522321511204443500261660ustar00rootroot00000000000000/* Spa MIDI node */ /* SPDX-FileCopyrightText: Copyright © 2022 Pauli Virtanen */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "midi.h" #include "bluez5-interface-gen.h" SPA_LOG_TOPIC_DEFINE_STATIC(log_topic, "spa.bluez5.midi.node"); #undef SPA_LOG_TOPIC_DEFAULT #define SPA_LOG_TOPIC_DEFAULT &log_topic #define DEFAULT_CLOCK_NAME "clock.system.monotonic" #define DLL_BW 0.05 #define DEFAULT_LATENCY_OFFSET (0 * SPA_NSEC_PER_MSEC) #define MAX_BUFFERS 32 #define MIDI_RINGBUF_SIZE (8192*4) enum node_role { NODE_SERVER, NODE_CLIENT, }; struct props { char clock_name[64]; char device_name[512]; int64_t latency_offset; }; struct midi_event_ringbuffer_entry { uint64_t time; unsigned int size; }; struct midi_event_ringbuffer { struct spa_ringbuffer rbuf; uint8_t buf[MIDI_RINGBUF_SIZE]; }; struct buffer { uint32_t id; unsigned int outgoing:1; struct spa_buffer *buf; struct spa_meta_header *h; struct spa_list link; }; struct time_sync { uint64_t prev_recv_time; uint64_t recv_time; uint16_t prev_device_timestamp; uint16_t device_timestamp; uint64_t device_time; struct spa_dll dll; }; struct port { uint32_t id; enum spa_direction direction; struct spa_audio_info current_format; unsigned int have_format:1; uint64_t info_all; struct spa_port_info info; struct spa_io_buffers *io; struct spa_latency_info latency; #define IDX_EnumFormat 0 #define IDX_Meta 1 #define IDX_IO 2 #define IDX_Format 3 #define IDX_Buffers 4 #define IDX_Latency 5 #define N_PORT_PARAMS 6 struct spa_param_info params[N_PORT_PARAMS]; struct buffer buffers[MAX_BUFFERS]; uint32_t n_buffers; struct spa_list free; struct spa_list ready; int fd; uint16_t mtu; struct buffer *buffer; struct spa_pod_builder builder; struct spa_pod_frame frame; struct time_sync sync; unsigned int acquired:1; GCancellable *acquire_call; struct spa_source source; struct impl *impl; }; struct impl { struct spa_handle handle; struct spa_node node; struct spa_log *log; struct spa_loop *main_loop; struct spa_loop *data_loop; struct spa_system *data_system; GDBusConnection *conn; Bluez5GattCharacteristic1 *proxy; struct spa_hook_list hooks; struct spa_callbacks callbacks; uint64_t info_all; struct spa_node_info info; #define IDX_PropInfo 0 #define IDX_Props 1 #define IDX_NODE_IO 2 #define N_NODE_PARAMS 3 struct spa_param_info params[N_NODE_PARAMS]; struct props props; #define PORT_IN 0 #define PORT_OUT 1 #define N_PORTS 2 struct port ports[N_PORTS]; char *chr_path; unsigned int started:1; unsigned int following:1; struct spa_source timer_source; int timerfd; struct spa_io_clock *clock; struct spa_io_position *position; uint32_t duration; uint32_t rate; uint64_t current_time; uint64_t next_time; struct midi_event_ringbuffer event_rbuf; struct spa_bt_midi_parser parser; struct spa_bt_midi_parser tmp_parser; uint8_t read_buffer[MIDI_MAX_MTU]; struct spa_bt_midi_writer writer; enum node_role role; struct spa_bt_midi_server *server; }; #define CHECK_PORT(this,d,p) ((p) == 0 && ((d) == SPA_DIRECTION_INPUT || (d) == SPA_DIRECTION_OUTPUT)) #define GET_PORT(this,d,p) (&(this)->ports[(d) == SPA_DIRECTION_OUTPUT ? PORT_OUT : PORT_IN]) static void midi_event_ringbuffer_init(struct midi_event_ringbuffer *mbuf) { spa_ringbuffer_init(&mbuf->rbuf); } static int midi_event_ringbuffer_push(struct midi_event_ringbuffer *mbuf, uint64_t time, uint8_t *event, unsigned int size) { const unsigned int bufsize = sizeof(mbuf->buf); int32_t avail; uint32_t index; struct midi_event_ringbuffer_entry evt = { .time = time, .size = size }; avail = spa_ringbuffer_get_write_index(&mbuf->rbuf, &index); if (avail < 0 || avail + sizeof(evt) + size > bufsize) return -ENOSPC; spa_ringbuffer_write_data(&mbuf->rbuf, mbuf->buf, bufsize, index % bufsize, &evt, sizeof(evt)); index += sizeof(evt); spa_ringbuffer_write_update(&mbuf->rbuf, index); spa_ringbuffer_write_data(&mbuf->rbuf, mbuf->buf, bufsize, index % bufsize, event, size); index += size; spa_ringbuffer_write_update(&mbuf->rbuf, index); return 0; } static int midi_event_ringbuffer_peek(struct midi_event_ringbuffer *mbuf, uint64_t *time, unsigned int *size) { const unsigned bufsize = sizeof(mbuf->buf); int32_t avail; uint32_t index; struct midi_event_ringbuffer_entry evt; avail = spa_ringbuffer_get_read_index(&mbuf->rbuf, &index); if (avail < (int)sizeof(evt)) return -ENOENT; spa_ringbuffer_read_data(&mbuf->rbuf, mbuf->buf, bufsize, index % bufsize, &evt, sizeof(evt)); *time = evt.time; *size = evt.size; return 0; } static int midi_event_ringbuffer_pop(struct midi_event_ringbuffer *mbuf, uint8_t *data, size_t max_size) { const unsigned bufsize = sizeof(mbuf->buf); int32_t avail; uint32_t index; struct midi_event_ringbuffer_entry evt; avail = spa_ringbuffer_get_read_index(&mbuf->rbuf, &index); if (avail < (int)sizeof(evt)) return -ENOENT; spa_ringbuffer_read_data(&mbuf->rbuf, mbuf->buf, bufsize, index % bufsize, &evt, sizeof(evt)); index += sizeof(evt); avail -= sizeof(evt); spa_ringbuffer_read_update(&mbuf->rbuf, index); if ((uint32_t)avail < evt.size) { /* corrupted ringbuffer: should never happen */ spa_assert_not_reached(); return -EINVAL; } if (evt.size <= max_size) spa_ringbuffer_read_data(&mbuf->rbuf, mbuf->buf, bufsize, index % bufsize, data, SPA_MIN(max_size, evt.size)); index += evt.size; spa_ringbuffer_read_update(&mbuf->rbuf, index); if (evt.size > max_size) return -ENOSPC; return 0; } static void reset_props(struct props *props) { props->latency_offset = DEFAULT_LATENCY_OFFSET; strncpy(props->clock_name, DEFAULT_CLOCK_NAME, sizeof(props->clock_name)); props->device_name[0] = '\0'; } static bool is_following(struct impl *this) { return this->position && this->clock && this->position->clock.id != this->clock->id; } static int set_timeout(struct impl *this, uint64_t time) { struct itimerspec ts; ts.it_value.tv_sec = time / SPA_NSEC_PER_SEC; ts.it_value.tv_nsec = time % SPA_NSEC_PER_SEC; ts.it_interval.tv_sec = 0; ts.it_interval.tv_nsec = 0; return spa_system_timerfd_settime(this->data_system, this->timerfd, SPA_FD_TIMER_ABSTIME, &ts, NULL); } static int set_timers(struct impl *this) { struct timespec now; spa_system_clock_gettime(this->data_system, CLOCK_MONOTONIC, &now); this->next_time = SPA_TIMESPEC_TO_NSEC(&now); return set_timeout(this, this->following ? 0 : this->next_time); } static void recycle_buffer(struct impl *this, struct port *port, uint32_t buffer_id) { struct buffer *b = &port->buffers[buffer_id]; if (b->outgoing) { spa_log_trace(this->log, "%p: recycle buffer %u", this, buffer_id); spa_list_append(&port->free, &b->link); b->outgoing = false; } } static int clear_buffers(struct impl *this, struct port *port) { if (port->n_buffers > 0) { spa_list_init(&port->free); spa_list_init(&port->ready); port->n_buffers = 0; } return 0; } static void reset_buffers(struct port *port) { uint32_t i; spa_list_init(&port->free); spa_list_init(&port->ready); for (i = 0; i < port->n_buffers; i++) { struct buffer *b = &port->buffers[i]; if (port->direction == SPA_DIRECTION_OUTPUT) { spa_list_append(&port->free, &b->link); b->outgoing = false; } else { b->outgoing = true; } } } static struct buffer *peek_buffer(struct impl *this, struct port *port) { if (spa_list_is_empty(&port->free)) return NULL; return spa_list_first(&port->free, struct buffer, link); } static int prepare_buffer(struct impl *this, struct port *port) { if (port->buffer != NULL) return 0; if ((port->buffer = peek_buffer(this, port)) == NULL) return -EPIPE; spa_pod_builder_init(&port->builder, port->buffer->buf->datas[0].data, port->buffer->buf->datas[0].maxsize); spa_pod_builder_push_sequence(&port->builder, &port->frame, 0); return 0; } static int finish_buffer(struct impl *this, struct port *port) { if (port->buffer == NULL) return 0; spa_pod_builder_pop(&port->builder, &port->frame); port->buffer->buf->datas[0].chunk->offset = 0; port->buffer->buf->datas[0].chunk->size = port->builder.state.offset; /* move buffer to ready queue */ spa_list_remove(&port->buffer->link); spa_list_append(&port->ready, &port->buffer->link); port->buffer = NULL; return 0; } /* Replace value -> value + n*period, to minimize |value - target| */ static int64_t unwrap_to_closest(int64_t value, int64_t target, int64_t period) { if (value > target) value -= SPA_ROUND_DOWN(value - target + period/2, period); if (value < target) value += SPA_ROUND_DOWN(target - value + period/2, period); return value; } static int64_t time_diff(uint64_t a, uint64_t b) { if (a >= b) return a - b; else return -(int64_t)(b - a); } static void midi_event_get_last_timestamp(void *user_data, uint16_t timestamp, uint8_t *data, size_t size) { int *last_timestamp = user_data; *last_timestamp = timestamp; } static uint64_t midi_convert_time(struct time_sync *sync, uint16_t timestamp) { int offset; /* * sync->device_timestamp is a device timestamp that corresponds to system * clock time sync->device_time. * * It is the timestamp of the last MIDI event in the current packet, so we can * assume here no event here has timestamp after it. */ if (timestamp > sync->device_timestamp) offset = sync->device_timestamp + MIDI_CLOCK_PERIOD_MSEC - timestamp; else offset = sync->device_timestamp - timestamp; return sync->device_time - offset * SPA_NSEC_PER_MSEC; } static void midi_event_recv(void *user_data, uint16_t timestamp, uint8_t *data, size_t size) { struct impl *this = user_data; struct port *port = &this->ports[PORT_OUT]; struct time_sync *sync = &port->sync; uint64_t time, state = 0; int res; spa_assert(size > 0); time = midi_convert_time(sync, timestamp); spa_log_trace(this->log, "%p: event:0x%x size:%d timestamp:%d time:%"PRIu64"", this, (int)data[0], (int)size, (int)timestamp, (uint64_t)time); while (size > 0) { uint32_t ump[4]; int ump_size = spa_ump_from_midi(&data, &size, ump, sizeof(ump), 0, &state); if (ump_size <= 0) break; res = midi_event_ringbuffer_push(&this->event_rbuf, time, (uint8_t*)ump, ump_size); if (res < 0) { midi_event_ringbuffer_init(&this->event_rbuf); spa_log_warn(this->log, "%p: MIDI receive buffer overflow: %s", this, spa_strerror(res)); } } } static int unacquire_port(struct port *port) { struct impl *this = port->impl; if (!port->acquired) return 0; spa_log_debug(this->log, "%p: unacquire port:%d", this, port->direction); shutdown(port->fd, SHUT_RDWR); close(port->fd); port->fd = -1; port->acquired = false; if (this->server) spa_bt_midi_server_released(this->server, (port->direction == SPA_DIRECTION_OUTPUT)); return 0; } static int do_unacquire_port(struct spa_loop *loop, bool async, uint32_t seq, const void *data, size_t size, void *user_data) { struct port *port = user_data; /* in main thread */ unacquire_port(port); return 0; } static void on_ready_read(struct spa_source *source) { struct port *port = source->data; struct impl *this = port->impl; struct timespec now; int res, size, last_timestamp; if (SPA_FLAG_IS_SET(source->rmask, SPA_IO_ERR) || SPA_FLAG_IS_SET(source->rmask, SPA_IO_HUP)) { spa_log_debug(this->log, "%p: port:%d ERR/HUP", this, port->direction); goto stop; } spa_system_clock_gettime(this->data_system, CLOCK_MONOTONIC, &now); /* read data from socket */ again: size = recv(port->fd, this->read_buffer, sizeof(this->read_buffer), MSG_DONTWAIT | MSG_NOSIGNAL); if (size == 0) { return; } else if (size < 0) { if (errno == EINTR) goto again; if (errno == EAGAIN || errno == EWOULDBLOCK) return; goto stop; } spa_log_trace(this->log, "%p: port:%d recv data size:%d", this, port->direction, size); spa_debug_log_mem(this->log, SPA_LOG_LEVEL_TRACE, 4, this->read_buffer, size); if (port->direction != SPA_DIRECTION_OUTPUT) { /* Just monitor errors for the input port */ spa_log_debug(this->log, "%p: port:%d is not RX port; ignoring data", this, port->direction); return; } /* prepare for producing events */ if (port->io == NULL || port->n_buffers == 0 || !this->started) return; /* * Remote clock synchronization: * * Assume: Last timestamp in packet on average corresponds to packet send time. * There is some unknown latency in between, but on average it is constant. * * The `device_time` computed below is the estimated wall-clock time * corresponding to the timestamp `device_timestamp` of the last event * in the packet. This timestamp is late by the average transmission latency, * which is unknown. * * Packet reception jitter and any clock drift is smoothed over with DLL. * The estimated timestamps are stable and preserve event intervals. * * To allow latency_offset to work better, we don't write the events * to the output buffer here, but instead put them to a ringbuffer. * This is because if the offset shifts events to later buffers, * this is simpler to handle with the rbuf. */ last_timestamp = -1; spa_bt_midi_parser_dup(&this->parser, &this->tmp_parser, true); res = spa_bt_midi_parser_parse(&this->tmp_parser, this->read_buffer, size, true, midi_event_get_last_timestamp, &last_timestamp); if (res >= 0 && last_timestamp >= 0) { struct time_sync *sync = &port->sync; int64_t clock_elapsed; int64_t device_elapsed; int64_t err_nsec; double corr, tcorr; sync->prev_recv_time = sync->recv_time; sync->recv_time = SPA_TIMESPEC_TO_NSEC(&now); sync->prev_device_timestamp = sync->device_timestamp; sync->device_timestamp = last_timestamp; if (port->sync.prev_recv_time == 0) { sync->prev_recv_time = sync->recv_time; sync->prev_device_timestamp = sync->device_timestamp; spa_dll_init(&sync->dll); } if (SPA_UNLIKELY(sync->dll.bw == 0)) spa_dll_set_bw(&sync->dll, DLL_BW, 1024, 48000); /* move device clock forward */ clock_elapsed = sync->recv_time - sync->prev_recv_time; device_elapsed = (int)sync->device_timestamp - (int)sync->prev_device_timestamp; device_elapsed *= SPA_NSEC_PER_MSEC; device_elapsed = unwrap_to_closest(device_elapsed, clock_elapsed, MIDI_CLOCK_PERIOD_NSEC); sync->device_time += device_elapsed; /* smooth clock sync */ err_nsec = time_diff(sync->recv_time, sync->device_time); corr = spa_dll_update(&sync->dll, -SPA_CLAMP(err_nsec, -20*SPA_NSEC_PER_MSEC, 20*SPA_NSEC_PER_MSEC) * this->rate / SPA_NSEC_PER_SEC); tcorr = SPA_MIN(device_elapsed, SPA_NSEC_PER_SEC) * (corr - 1); sync->device_time += (uint64_t)tcorr; /* reset if too much off */ if (err_nsec < -50 * SPA_NSEC_PER_MSEC || err_nsec > 200 * SPA_NSEC_PER_MSEC || SPA_ABS(tcorr) > 20*SPA_NSEC_PER_MSEC || device_elapsed < 0) { spa_log_debug(this->log, "%p: device clock sync off too much: resync", this); spa_dll_init(&sync->dll); sync->device_time = sync->recv_time; } spa_log_debug(this->log, "timestamp:%d dt:%d dt2:%d err:%.1f tcorr:%.2f (ms) corr:%f", (int)sync->device_timestamp, (int)(clock_elapsed/SPA_NSEC_PER_MSEC), (int)(device_elapsed/SPA_NSEC_PER_MSEC), (double)err_nsec / SPA_NSEC_PER_MSEC, tcorr/SPA_NSEC_PER_MSEC, corr); } /* put midi event data to the buffer */ res = spa_bt_midi_parser_parse(&this->parser, this->read_buffer, size, false, midi_event_recv, this); if (res < 0) { /* bad data */ spa_bt_midi_parser_init(&this->parser); spa_log_info(this->log, "BLE MIDI data packet parsing failed: %d", res); spa_debug_log_mem(this->log, SPA_LOG_LEVEL_DEBUG, 4, this->read_buffer, size); } return; stop: spa_log_debug(this->log, "%p: port:%d stopping port", this, port->direction); if (port->source.loop) spa_loop_remove_source(this->data_loop, &port->source); /* port->acquired is updated only from the main thread */ spa_loop_invoke(this->main_loop, do_unacquire_port, 0, NULL, 0, false, port); } static int process_output(struct impl *this) { struct port *port = &this->ports[PORT_OUT]; struct buffer *buffer; struct spa_io_buffers *io = port->io; /* Check if we are able to process */ if (io == NULL || !port->acquired) return SPA_STATUS_OK; /* Return if we already have a buffer */ if (io->status == SPA_STATUS_HAVE_DATA) return SPA_STATUS_HAVE_DATA; /* Recycle */ if (io->buffer_id < port->n_buffers) { recycle_buffer(this, port, io->buffer_id); io->buffer_id = SPA_ID_INVALID; } /* Produce buffer */ if (prepare_buffer(this, port) >= 0) { /* * this->current_time is at the end time of the buffer, and offsets * are recorded vs. the start of the buffer. */ const uint64_t start_time = this->current_time - this->duration * SPA_NSEC_PER_SEC / this->rate; const uint64_t end_time = this->current_time; uint64_t time; uint32_t offset; void *buf; unsigned int size; int res; while (true) { res = midi_event_ringbuffer_peek(&this->event_rbuf, &time, &size); if (res < 0) break; time -= this->props.latency_offset; if (time > end_time) { break; } else if (time + SPA_NSEC_PER_MSEC < start_time) { /* Log events in the past by more than 1 ms, but don't * do anything about them. The user can change the latency * offset to choose whether to tradeoff latency for more * accurate timestamps. * * TODO: maybe this information should be available in * a more visible place, some latency property? */ spa_log_debug(this->log, "%p: event in the past by %d ms", this, (int)((start_time - time) / SPA_NSEC_PER_MSEC)); } time = SPA_MAX(time, start_time) - start_time; offset = time * this->rate / SPA_NSEC_PER_SEC; offset = SPA_CLAMP(offset, 0u, this->duration - 1); spa_pod_builder_control(&port->builder, offset, SPA_CONTROL_UMP); buf = spa_pod_builder_reserve_bytes(&port->builder, size); if (buf) { midi_event_ringbuffer_pop(&this->event_rbuf, buf, size); spa_log_trace(this->log, "%p: produce event:0x%x offset:%d time:%"PRIu64"", this, (int)*(uint8_t*)buf, (int)offset, (uint64_t)(start_time + offset * SPA_NSEC_PER_SEC / this->rate)); } } finish_buffer(this, port); } /* Return if there are no buffers ready to be processed */ if (spa_list_is_empty(&port->ready)) return SPA_STATUS_OK; /* Get the new buffer from the ready list */ buffer = spa_list_first(&port->ready, struct buffer, link); spa_list_remove(&buffer->link); buffer->outgoing = true; /* Set the new buffer in IO */ io->buffer_id = buffer->id; io->status = SPA_STATUS_HAVE_DATA; /* Notify we have a buffer ready to be processed */ return SPA_STATUS_HAVE_DATA; } static int flush_packet(struct impl *this) { struct port *port = &this->ports[PORT_IN]; int res; if (this->writer.size == 0) return 0; res = send(port->fd, this->writer.buf, this->writer.size, MSG_DONTWAIT | MSG_NOSIGNAL); if (res < 0) return -errno; spa_log_trace(this->log, "%p: send packet size:%d", this, this->writer.size); spa_debug_log_mem(this->log, SPA_LOG_LEVEL_TRACE, 4, this->writer.buf, this->writer.size); return 0; } static int write_data(struct impl *this, struct spa_data *d) { struct port *port = &this->ports[PORT_IN]; struct spa_pod_parser parser; struct spa_pod_frame frame; struct spa_pod_sequence seq; const void *seq_body, *c_body; struct spa_pod_control c; uint64_t time; int res; spa_pod_parser_init_from_data(&parser, d->data, d->maxsize, d->chunk->offset, d->chunk->size); if (spa_pod_parser_push_sequence_body(&parser, &frame, &seq, &seq_body) < 0) { spa_log_warn(this->log, "%p: invalid sequence in buffer max:%u offset:%u size:%u", this, d->maxsize, d->chunk->offset, d->chunk->size); return -EINVAL; } spa_bt_midi_writer_init(&this->writer, port->mtu); time = 0; while (spa_pod_parser_get_control_body(&parser, &c, &c_body) >= 0) { int size; uint8_t event[32]; const uint32_t *ump = c_body; size_t ump_size = c.value.size; uint64_t state = 0; if (c.type != SPA_CONTROL_UMP) continue; time = SPA_MAX(time, this->current_time + c.offset * SPA_NSEC_PER_SEC / this->rate); while (ump_size > 0) { size = spa_ump_to_midi(&ump, &ump_size, event, sizeof(event), &state); if (size <= 0) break; spa_log_trace(this->log, "%p: output event:0x%x time:%"PRIu64, this, (size > 0) ? event[0] : 0, time); do { res = spa_bt_midi_writer_write(&this->writer, time, event, size); if (res < 0) { return res; } else if (res) { int res2; if ((res2 = flush_packet(this)) < 0) return res2; } } while (res); } } if ((res = flush_packet(this)) < 0) return res; return 0; } static int process_input(struct impl *this) { struct port *port = &this->ports[PORT_IN]; struct buffer *b; struct spa_io_buffers *io = port->io; int res; /* Check if we are able to process */ if (io == NULL || !port->acquired) return SPA_STATUS_OK; if (io->status != SPA_STATUS_HAVE_DATA || io->buffer_id >= port->n_buffers) return SPA_STATUS_OK; b = &port->buffers[io->buffer_id]; if (!b->outgoing) { spa_log_warn(this->log, "%p: buffer %u not outgoing", this, io->buffer_id); io->status = -EINVAL; return -EINVAL; } if ((res = write_data(this, &b->buf->datas[0])) < 0) { spa_log_info(this->log, "%p: writing data failed: %s", this, spa_strerror(res)); } port->io->buffer_id = b->id; io->status = SPA_STATUS_NEED_DATA; spa_node_call_reuse_buffer(&this->callbacks, 0, io->buffer_id); return SPA_STATUS_HAVE_DATA; } static void update_position(struct impl *this) { if (SPA_LIKELY(this->position)) { this->duration = this->position->clock.duration; this->rate = this->position->clock.rate.denom; } else { this->duration = 1024; this->rate = 48000; } } static void on_timeout(struct spa_source *source) { struct impl *this = source->data; uint64_t exp; uint64_t prev_time, now_time; int status; if (!this->started) return; if (spa_system_timerfd_read(this->data_system, this->timerfd, &exp) < 0) spa_log_warn(this->log, "%p: error reading timerfd: %s", this, strerror(errno)); prev_time = this->current_time; now_time = this->current_time = this->next_time; spa_log_trace(this->log, "%p: timer %"PRIu64" %"PRIu64"", this, now_time, now_time - prev_time); if (SPA_LIKELY(this->position)) { this->duration = this->position->clock.target_duration; this->rate = this->position->clock.target_rate.denom; } else { this->duration = 1024; this->rate = 48000; } this->next_time = now_time + this->duration * SPA_NSEC_PER_SEC / this->rate; if (SPA_LIKELY(this->clock)) { this->clock->nsec = now_time; this->clock->rate = this->clock->target_rate; this->clock->position += this->clock->duration; this->clock->duration = this->duration; this->clock->rate_diff = 1.0f; this->clock->next_nsec = this->next_time; } status = process_output(this); spa_log_trace(this->log, "%p: status:%d", this, status); spa_node_call_ready(&this->callbacks, status | SPA_STATUS_NEED_DATA); set_timeout(this, this->next_time); } static int do_start(struct impl *this); static int do_release(struct impl *this); static int do_stop(struct impl *this); static void acquire_reply(GObject *source_object, GAsyncResult *res, gpointer user_data, bool notify) { struct port *port; struct impl *this; const char *method; GError *err = NULL; GUnixFDList *fd_list = NULL; GVariant *fd_handle = NULL; int fd; guint16 mtu; if (notify) { bluez5_gatt_characteristic1_call_acquire_notify_finish( BLUEZ5_GATT_CHARACTERISTIC1(source_object), &fd_handle, &mtu, &fd_list, res, &err); } else { bluez5_gatt_characteristic1_call_acquire_write_finish( BLUEZ5_GATT_CHARACTERISTIC1(source_object), &fd_handle, &mtu, &fd_list, res, &err); } if (g_error_matches(err, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { /* Operation canceled: user_data may be invalid by now. */ g_error_free(err); return; } port = user_data; this = port->impl; method = notify ? "AcquireNotify" : "AcquireWrite"; if (err) { spa_log_error(this->log, "%s.%s() for %s failed: %s", BLUEZ_GATT_CHR_INTERFACE, method, this->chr_path, err->message); goto fail; } fd = g_unix_fd_list_get(fd_list, g_variant_get_handle(fd_handle), &err); if (fd < 0) { spa_log_error(this->log, "%s.%s() for %s failed to get fd: %s", BLUEZ_GATT_CHR_INTERFACE, method, this->chr_path, err->message); goto fail; } spa_log_info(this->log, "%p: BLE MIDI %s %s success mtu:%d", this, this->chr_path, method, mtu); port->fd = fd; port->mtu = mtu; port->acquired = true; if (port->direction == SPA_DIRECTION_OUTPUT) { spa_bt_midi_parser_init(&this->parser); /* Start source */ port->source.data = port; port->source.fd = port->fd; port->source.func = on_ready_read; port->source.mask = SPA_IO_IN | SPA_IO_HUP | SPA_IO_ERR; port->source.rmask = 0; spa_loop_add_source(this->data_loop, &port->source); } return; fail: g_error_free(err); g_clear_object(&fd_list); g_clear_object(&fd_handle); do_stop(this); do_release(this); } static void acquire_notify_reply(GObject *source_object, GAsyncResult *res, gpointer user_data) { acquire_reply(source_object, res, user_data, true); } static void acquire_write_reply(GObject *source_object, GAsyncResult *res, gpointer user_data) { acquire_reply(source_object, res, user_data, false); } static int do_acquire(struct port *port) { struct impl *this = port->impl; const char *method = (port->direction == SPA_DIRECTION_OUTPUT) ? "AcquireNotify" : "AcquireWrite"; GVariant *options; GVariantBuilder builder; if (port->acquired) return 0; if (port->acquire_call) return 0; spa_log_info(this->log, "%p: port %d: client %s for BLE MIDI device characteristic %s", this, port->direction, method, this->chr_path); port->acquire_call = g_cancellable_new(); g_variant_builder_init(&builder, G_VARIANT_TYPE("a{sv}")); options = g_variant_builder_end(&builder); if (port->direction == SPA_DIRECTION_OUTPUT) { bluez5_gatt_characteristic1_call_acquire_notify( BLUEZ5_GATT_CHARACTERISTIC1(this->proxy), options, NULL, port->acquire_call, acquire_notify_reply, port); } else { bluez5_gatt_characteristic1_call_acquire_write( BLUEZ5_GATT_CHARACTERISTIC1(this->proxy), options, NULL, port->acquire_call, acquire_write_reply, port); } return 0; } static int server_do_acquire(struct port *port, int fd, uint16_t mtu) { struct impl *this = port->impl; const char *method = (port->direction == SPA_DIRECTION_OUTPUT) ? "AcquireWrite" : "AcquireNotify"; spa_log_info(this->log, "%p: port %d: server %s for BLE MIDI device characteristic %s", this, port->direction, method, this->server->chr_path); if (port->acquired) { spa_log_info(this->log, "%p: port %d: %s failed: already acquired", this, port->direction, method); return -EBUSY; } port->fd = fd; port->mtu = mtu; if (port->direction == SPA_DIRECTION_OUTPUT) spa_bt_midi_parser_init(&this->parser); /* Start source */ port->source.data = port; port->source.fd = port->fd; port->source.func = on_ready_read; port->source.mask = SPA_IO_HUP | SPA_IO_ERR; if (port->direction == SPA_DIRECTION_OUTPUT) port->source.mask |= SPA_IO_IN; port->source.rmask = 0; spa_loop_add_source(this->data_loop, &port->source); port->acquired = true; return 0; } static int server_acquire_write(void *user_data, int fd, uint16_t mtu) { struct impl *this = user_data; return server_do_acquire(&this->ports[PORT_OUT], fd, mtu); } static int server_acquire_notify(void *user_data, int fd, uint16_t mtu) { struct impl *this = user_data; return server_do_acquire(&this->ports[PORT_IN], fd, mtu); } static int server_release(void *user_data) { struct impl *this = user_data; do_release(this); return 0; } static const char *server_description(void *user_data) { struct impl *this = user_data; return this->props.device_name; } static int do_remove_port_source(struct spa_loop *loop, bool async, uint32_t seq, const void *data, size_t size, void *user_data) { struct impl *this = user_data; int i; for (i = 0; i < N_PORTS; ++i) { struct port *port = &this->ports[i]; if (port->source.loop) spa_loop_remove_source(this->data_loop, &port->source); } return 0; } static int do_remove_source(struct spa_loop *loop, bool async, uint32_t seq, const void *data, size_t size, void *user_data) { struct impl *this = user_data; if (this->timer_source.loop) spa_loop_remove_source(this->data_loop, &this->timer_source); set_timeout(this, 0); return 0; } static int do_stop(struct impl *this) { int res = 0; spa_log_debug(this->log, "%p: stop", this); spa_loop_locked(this->data_loop, do_remove_source, 0, NULL, 0, this); this->started = false; return res; } static int do_release(struct impl *this) { int res = 0; size_t i; spa_log_debug(this->log, "%p: release", this); spa_loop_locked(this->data_loop, do_remove_port_source, 0, NULL, 0, this); for (i = 0; i < N_PORTS; ++i) { struct port *port = &this->ports[i]; g_cancellable_cancel(port->acquire_call); g_clear_object(&port->acquire_call); unacquire_port(port); } return res; } static int do_start(struct impl *this) { int res; size_t i; if (this->started) return 0; this->following = is_following(this); update_position(this); spa_log_debug(this->log, "%p: start following:%d", this, this->following); for (i = 0; i < N_PORTS; ++i) { struct port *port = &this->ports[i]; switch (this->role) { case NODE_CLIENT: /* Acquire Bluetooth I/O */ if ((res = do_acquire(port)) < 0) { do_stop(this); do_release(this); return res; } break; case NODE_SERVER: /* * In MIDI server role, the device/BlueZ invokes * the acquire asynchronously as available/needed. */ break; default: spa_assert_not_reached(); } reset_buffers(port); } midi_event_ringbuffer_init(&this->event_rbuf); this->started = true; /* Start timer */ this->timer_source.data = this; this->timer_source.fd = this->timerfd; this->timer_source.func = on_timeout; this->timer_source.mask = SPA_IO_IN; this->timer_source.rmask = 0; spa_loop_add_source(this->data_loop, &this->timer_source); set_timers(this); return 0; } static int do_reassign_follower(struct spa_loop *loop, bool async, uint32_t seq, const void *data, size_t size, void *user_data) { struct impl *this = user_data; set_timers(this); return 0; } static int impl_node_set_io(void *object, uint32_t id, void *data, size_t size) { struct impl *this = object; bool following; spa_return_val_if_fail(this != NULL, -EINVAL); switch (id) { case SPA_IO_Clock: this->clock = data; if (this->clock != NULL) { spa_scnprintf(this->clock->name, sizeof(this->clock->name), "%s", this->props.clock_name); } break; case SPA_IO_Position: this->position = data; break; default: return -ENOENT; } following = is_following(this); if (this->started && following != this->following) { spa_log_debug(this->log, "%p: reassign follower %d->%d", this, this->following, following); this->following = following; spa_loop_locked(this->data_loop, do_reassign_follower, 0, NULL, 0, this); } return 0; } static void emit_node_info(struct impl *this, bool full); static int impl_node_enum_params(void *object, int seq, uint32_t id, uint32_t start, uint32_t num, const struct spa_pod *filter) { struct impl *this = object; struct spa_pod *param; struct spa_pod_builder b = { 0 }; uint8_t buffer[1024]; struct spa_result_node_params result; uint32_t count = 0; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(num != 0, -EINVAL); result.id = id; result.next = start; next: result.index = result.next++; spa_pod_builder_init(&b, buffer, sizeof(buffer)); switch (id) { case SPA_PARAM_PropInfo: { struct props *p = &this->props; switch (result.index) { case 0: param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_PropInfo, id, SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_latencyOffsetNsec), SPA_PROP_INFO_description, SPA_POD_String("Latency offset (ns)"), SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Long(0LL, INT64_MIN, INT64_MAX)); break; case 1: param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_PropInfo, id, SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_deviceName), SPA_PROP_INFO_description, SPA_POD_String("Device name"), SPA_PROP_INFO_type, SPA_POD_String(p->device_name)); break; default: return 0; } break; } case SPA_PARAM_Props: { struct props *p = &this->props; switch (result.index) { case 0: param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_Props, id, SPA_PROP_latencyOffsetNsec, SPA_POD_Long(p->latency_offset), SPA_PROP_deviceName, SPA_POD_String(p->device_name)); break; default: return 0; } break; } default: return -ENOENT; } if (spa_pod_filter(&b, &result.param, param, filter) < 0) goto next; spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); if (++count != num) goto next; return 0; } static void emit_port_info(struct impl *this, struct port *port, bool full); static void set_latency(struct impl *this, bool emit_latency) { struct port *port = &this->ports[PORT_OUT]; port->latency.min_ns = port->latency.max_ns = this->props.latency_offset; if (emit_latency) { port->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; port->params[IDX_Latency].flags ^= SPA_PARAM_INFO_SERIAL; emit_port_info(this, port, false); } } static int apply_props(struct impl *this, const struct spa_pod *param) { struct props new_props = this->props; int changed = 0; if (param == NULL) { reset_props(&new_props); } else { spa_pod_parse_object(param, SPA_TYPE_OBJECT_Props, NULL, SPA_PROP_latencyOffsetNsec, SPA_POD_OPT_Long(&new_props.latency_offset), SPA_PROP_deviceName, SPA_POD_OPT_Stringn(new_props.device_name, sizeof(new_props.device_name))); } changed = (memcmp(&new_props, &this->props, sizeof(struct props)) != 0); this->props = new_props; if (changed) set_latency(this, true); return changed; } static int impl_node_set_param(void *object, uint32_t id, uint32_t flags, const struct spa_pod *param) { struct impl *this = object; spa_return_val_if_fail(this != NULL, -EINVAL); switch (id) { case SPA_PARAM_Props: { if (apply_props(this, param) > 0) { this->info.change_mask |= SPA_NODE_CHANGE_MASK_PARAMS; this->params[IDX_Props].flags ^= SPA_PARAM_INFO_SERIAL; emit_node_info(this, false); } break; } default: return -ENOENT; } return 0; } static int impl_node_send_command(void *object, const struct spa_command *command) { struct impl *this = object; int res, res2; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(command != NULL, -EINVAL); switch (SPA_NODE_COMMAND_ID(command)) { case SPA_NODE_COMMAND_Start: if ((res = do_start(this)) < 0) return res; break; case SPA_NODE_COMMAND_Pause: if ((res = do_stop(this)) < 0) return res; break; case SPA_NODE_COMMAND_Suspend: res = do_stop(this); if (this->role == NODE_CLIENT) res2 = do_release(this); else res2 = 0; if (res < 0) return res; if (res2 < 0) return res2; break; default: return -ENOTSUP; } return 0; } static void emit_node_info(struct impl *this, bool full) { const struct spa_dict_item node_info_items[] = { { SPA_KEY_DEVICE_API, "bluez5" }, { SPA_KEY_MEDIA_CLASS, "Midi/Bridge" }, }; uint64_t old = full ? this->info.change_mask : 0; if (full) this->info.change_mask = this->info_all; if (this->info.change_mask) { this->info.props = &SPA_DICT_INIT_ARRAY(node_info_items); spa_node_emit_info(&this->hooks, &this->info); this->info.change_mask = old; } } static void emit_port_info(struct impl *this, struct port *port, bool full) { uint64_t old = full ? port->info.change_mask : 0; if (full) port->info.change_mask = port->info_all; if (port->info.change_mask) { spa_node_emit_port_info(&this->hooks, port->direction, port->id, &port->info); port->info.change_mask = old; } } static int impl_node_add_listener(void *object, struct spa_hook *listener, const struct spa_node_events *events, void *data) { struct impl *this = object; struct spa_hook_list save; size_t i; spa_return_val_if_fail(this != NULL, -EINVAL); spa_hook_list_isolate(&this->hooks, &save, listener, events, data); emit_node_info(this, true); for (i = 0; i < N_PORTS; ++i) emit_port_info(this, &this->ports[i], true); spa_hook_list_join(&this->hooks, &save); return 0; } static int impl_node_set_callbacks(void *object, const struct spa_node_callbacks *callbacks, void *data) { struct impl *this = object; spa_return_val_if_fail(this != NULL, -EINVAL); this->callbacks = SPA_CALLBACKS_INIT(callbacks, data); return 0; } static int impl_node_sync(void *object, int seq) { struct impl *this = object; spa_return_val_if_fail(this != NULL, -EINVAL); spa_node_emit_result(&this->hooks, seq, 0, 0, NULL); return 0; } static int impl_node_add_port(void *object, enum spa_direction direction, uint32_t port_id, const struct spa_dict *props) { return -ENOTSUP; } static int impl_node_remove_port(void *object, enum spa_direction direction, uint32_t port_id) { return -ENOTSUP; } static int impl_node_port_enum_params(void *object, int seq, enum spa_direction direction, uint32_t port_id, uint32_t id, uint32_t start, uint32_t num, const struct spa_pod *filter) { struct impl *this = object; struct port *port; struct spa_pod *param; struct spa_pod_builder b = { 0 }; uint8_t buffer[1024]; struct spa_result_node_params result; uint32_t count = 0; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(num != 0, -EINVAL); spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); port = GET_PORT(this, direction, port_id); result.id = id; result.next = start; next: result.index = result.next++; spa_pod_builder_init(&b, buffer, sizeof(buffer)); switch (id) { case SPA_PARAM_EnumFormat: if (result.index > 0) return 0; param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_application), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_control)); break; case SPA_PARAM_Format: if (!port->have_format) return -EIO; if (result.index > 0) return 0; param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_Format, SPA_PARAM_Format, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_application), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_control)); break; case SPA_PARAM_Buffers: if (!port->have_format) return -EIO; if (result.index > 0) return 0; param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamBuffers, id, SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(2, 1, MAX_BUFFERS), SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(1), SPA_PARAM_BUFFERS_size, SPA_POD_CHOICE_RANGE_Int( 4096, 4096, INT32_MAX), SPA_PARAM_BUFFERS_stride, SPA_POD_Int(1)); break; case SPA_PARAM_Meta: switch (result.index) { case 0: param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamMeta, id, SPA_PARAM_META_type, SPA_POD_Id(SPA_META_Header), SPA_PARAM_META_size, SPA_POD_Int(sizeof(struct spa_meta_header))); break; default: return 0; } break; case SPA_PARAM_IO: switch (result.index) { case 0: param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamIO, id, SPA_PARAM_IO_id, SPA_POD_Id(SPA_IO_Buffers), SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_buffers))); break; default: return 0; } break; case SPA_PARAM_Latency: switch (result.index) { case 0: param = spa_latency_build(&b, id, &port->latency); break; default: return 0; } break; default: return -ENOENT; } if (spa_pod_filter(&b, &result.param, param, filter) < 0) goto next; spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); if (++count != num) goto next; return 0; } static int port_set_format(struct impl *this, struct port *port, uint32_t flags, const struct spa_pod *format) { int err; if (format == NULL) { if (!port->have_format) return 0; clear_buffers(this, port); port->have_format = false; } else { struct spa_audio_info info = { 0 }; if ((err = spa_format_parse(format, &info.media_type, &info.media_subtype)) < 0) return err; if (info.media_type != SPA_MEDIA_TYPE_application || info.media_subtype != SPA_MEDIA_SUBTYPE_control) return -EINVAL; port->current_format = info; port->have_format = true; } port->info.change_mask |= SPA_PORT_CHANGE_MASK_RATE; port->info.rate = SPA_FRACTION(1, 1); port->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; if (port->have_format) { port->params[IDX_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_READWRITE); port->params[IDX_Buffers] = SPA_PARAM_INFO(SPA_PARAM_Buffers, SPA_PARAM_INFO_READ); } else { port->params[IDX_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); port->params[IDX_Buffers] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); } emit_port_info(this, port, false); return 0; } static int impl_node_port_set_param(void *object, enum spa_direction direction, uint32_t port_id, uint32_t id, uint32_t flags, const struct spa_pod *param) { struct impl *this = object; struct port *port; int res; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); port = GET_PORT(this, direction, port_id); switch (id) { case SPA_PARAM_Format: res = port_set_format(this, port, flags, param); break; case SPA_PARAM_Latency: res = 0; break; default: res = -ENOENT; break; } return res; } static int impl_node_port_use_buffers(void *object, enum spa_direction direction, uint32_t port_id, uint32_t flags, struct spa_buffer **buffers, uint32_t n_buffers) { struct impl *this = object; struct port *port; uint32_t i; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); port = GET_PORT(this, direction, port_id); spa_log_debug(this->log, "%p: use buffers %d", this, n_buffers); if (!port->have_format) return -EIO; clear_buffers(this, port); for (i = 0; i < n_buffers; i++) { struct buffer *b = &port->buffers[i]; struct spa_data *d = buffers[i]->datas; b->buf = buffers[i]; b->id = i; b->h = spa_buffer_find_meta_data(buffers[i], SPA_META_Header, sizeof(*b->h)); if (d[0].data == NULL) { spa_log_error(this->log, "%p: need mapped memory", this); return -EINVAL; } } port->n_buffers = n_buffers; reset_buffers(port); return 0; } static int impl_node_port_set_io(void *object, enum spa_direction direction, uint32_t port_id, uint32_t id, void *data, size_t size) { struct impl *this = object; struct port *port; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); port = GET_PORT(this, direction, port_id); switch (id) { case SPA_IO_Buffers: port->io = data; break; default: return -ENOENT; } return 0; } static int impl_node_port_reuse_buffer(void *object, uint32_t port_id, uint32_t buffer_id) { struct impl *this = object; struct port *port; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(CHECK_PORT(this, SPA_DIRECTION_OUTPUT, port_id), -EINVAL); port = GET_PORT(this, SPA_DIRECTION_OUTPUT, port_id); if (port->n_buffers == 0) return -EIO; if (buffer_id >= port->n_buffers) return -EINVAL; recycle_buffer(this, port, buffer_id); return 0; } static int impl_node_process(void *object) { struct impl *this = object; int status = SPA_STATUS_OK; spa_return_val_if_fail(this != NULL, -EINVAL); if (!this->started) return SPA_STATUS_OK; if (this->following) { if (this->position) { this->current_time = this->position->clock.nsec; } else { struct timespec now = { 0 }; spa_system_clock_gettime(this->data_system, CLOCK_MONOTONIC, &now); this->current_time = SPA_TIMESPEC_TO_NSEC(&now); } } update_position(this); if (this->following) status |= process_output(this); status |= process_input(this); return status; } static const struct spa_node_methods impl_node = { SPA_VERSION_NODE_METHODS, .add_listener = impl_node_add_listener, .set_callbacks = impl_node_set_callbacks, .sync = impl_node_sync, .enum_params = impl_node_enum_params, .set_param = impl_node_set_param, .set_io = impl_node_set_io, .send_command = impl_node_send_command, .add_port = impl_node_add_port, .remove_port = impl_node_remove_port, .port_enum_params = impl_node_port_enum_params, .port_set_param = impl_node_port_set_param, .port_use_buffers = impl_node_port_use_buffers, .port_set_io = impl_node_port_set_io, .port_reuse_buffer = impl_node_port_reuse_buffer, .process = impl_node_process, }; static const struct spa_bt_midi_server_cb impl_server = { .acquire_write = server_acquire_write, .acquire_notify = server_acquire_notify, .release = server_release, .get_description = server_description, }; static int impl_get_interface(struct spa_handle *handle, const char *type, void **interface) { struct impl *this; spa_return_val_if_fail(handle != NULL, -EINVAL); spa_return_val_if_fail(interface != NULL, -EINVAL); this = (struct impl *) handle; if (spa_streq(type, SPA_TYPE_INTERFACE_Node)) *interface = &this->node; else return -ENOENT; return 0; } static int impl_clear(struct spa_handle *handle) { struct impl *this = (struct impl *) handle; do_stop(this); do_release(this); free(this->chr_path); if (this->timerfd > 0) spa_system_close(this->data_system, this->timerfd); if (this->server) spa_bt_midi_server_destroy(this->server); g_clear_object(&this->proxy); g_clear_object(&this->conn); spa_zero(*this); return 0; } static size_t impl_get_size(const struct spa_handle_factory *factory, const struct spa_dict *params) { return sizeof(struct impl); } static int impl_init(const struct spa_handle_factory *factory, struct spa_handle *handle, const struct spa_dict *info, const struct spa_support *support, uint32_t n_support) { struct impl *this; const char *device_name = ""; int res = 0; GError *err = NULL; size_t i; spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(handle != NULL, -EINVAL); handle->get_interface = impl_get_interface; handle->clear = impl_clear; this = (struct impl *) handle; this->log = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log); this->main_loop = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Loop); this->data_loop = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_DataLoop); this->data_system = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_DataSystem); if (this->log == NULL) return -EINVAL; spa_log_topic_init(this->log, &log_topic); if (!(info && spa_atob(spa_dict_lookup(info, SPA_KEY_API_GLIB_MAINLOOP)))) { spa_log_error(this->log, "Glib mainloop is not usable: %s not set", SPA_KEY_API_GLIB_MAINLOOP); return -EINVAL; } if (this->data_loop == NULL) { spa_log_error(this->log, "a data loop is needed"); return -EINVAL; } if (this->data_system == NULL) { spa_log_error(this->log, "a data system is needed"); return -EINVAL; } this->role = NODE_CLIENT; if (info) { const char *str; if ((str = spa_dict_lookup(info, SPA_KEY_API_BLUEZ5_PATH)) != NULL) this->chr_path = strdup(str); if ((str = spa_dict_lookup(info, SPA_KEY_API_BLUEZ5_ROLE)) != NULL) { if (spa_streq(str, "server")) this->role = NODE_SERVER; } if ((str = spa_dict_lookup(info, "node.nick")) != NULL) device_name = str; else if ((str = spa_dict_lookup(info, "node.description")) != NULL) device_name = str; } if (this->role == NODE_CLIENT && this->chr_path == NULL) { spa_log_error(this->log, "missing MIDI service characteristic path"); res = -EINVAL; goto fail; } this->conn = g_bus_get_sync(G_BUS_TYPE_SYSTEM, NULL, &err); if (this->conn == NULL) { spa_log_error(this->log, "failed to get dbus connection: %s", err->message); g_error_free(err); res = -EIO; goto fail; } g_dbus_connection_set_exit_on_close(this->conn, FALSE); this->node.iface = SPA_INTERFACE_INIT( SPA_TYPE_INTERFACE_Node, SPA_VERSION_NODE, &impl_node, this); spa_hook_list_init(&this->hooks); reset_props(&this->props); spa_scnprintf(this->props.device_name, sizeof(this->props.device_name), "%s", device_name); /* set the node info */ this->info_all = SPA_NODE_CHANGE_MASK_FLAGS | SPA_NODE_CHANGE_MASK_PROPS | SPA_NODE_CHANGE_MASK_PARAMS; this->info = SPA_NODE_INFO_INIT(); this->info.max_input_ports = 1; this->info.max_output_ports = 1; this->info.flags = SPA_NODE_FLAG_RT; this->params[IDX_PropInfo] = SPA_PARAM_INFO(SPA_PARAM_PropInfo, SPA_PARAM_INFO_READ); this->params[IDX_Props] = SPA_PARAM_INFO(SPA_PARAM_Props, SPA_PARAM_INFO_READWRITE); this->params[IDX_NODE_IO] = SPA_PARAM_INFO(SPA_PARAM_IO, SPA_PARAM_INFO_READ); this->info.params = this->params; this->info.n_params = N_NODE_PARAMS; /* set the port info */ for (i = 0; i < N_PORTS; ++i) { struct port *port = &this->ports[i]; static const struct spa_dict_item in_port_items[] = { SPA_DICT_ITEM_INIT(SPA_KEY_FORMAT_DSP, "8 bit raw midi"), SPA_DICT_ITEM_INIT(SPA_KEY_PORT_NAME, "in"), SPA_DICT_ITEM_INIT(SPA_KEY_PORT_ALIAS, "in"), SPA_DICT_ITEM_INIT(SPA_KEY_PORT_GROUP, "group.0"), }; static const struct spa_dict_item out_port_items[] = { SPA_DICT_ITEM_INIT(SPA_KEY_FORMAT_DSP, "8 bit raw midi"), SPA_DICT_ITEM_INIT(SPA_KEY_PORT_NAME, "out"), SPA_DICT_ITEM_INIT(SPA_KEY_PORT_ALIAS, "out"), SPA_DICT_ITEM_INIT(SPA_KEY_PORT_GROUP, "group.0"), }; static const struct spa_dict in_port_props = SPA_DICT_INIT_ARRAY(in_port_items); static const struct spa_dict out_port_props = SPA_DICT_INIT_ARRAY(out_port_items); spa_zero(*port); port->impl = this; port->id = 0; port->direction = (i == PORT_OUT) ? SPA_DIRECTION_OUTPUT : SPA_DIRECTION_INPUT; port->info_all = SPA_PORT_CHANGE_MASK_FLAGS | SPA_PORT_CHANGE_MASK_PROPS | SPA_PORT_CHANGE_MASK_PARAMS; port->info = SPA_PORT_INFO_INIT(); port->info.change_mask = SPA_PORT_CHANGE_MASK_FLAGS; port->info.flags = SPA_PORT_FLAG_LIVE | SPA_PORT_FLAG_PHYSICAL | SPA_PORT_FLAG_TERMINAL; port->params[IDX_EnumFormat] = SPA_PARAM_INFO(SPA_PARAM_EnumFormat, SPA_PARAM_INFO_READ); port->params[IDX_Meta] = SPA_PARAM_INFO(SPA_PARAM_Meta, SPA_PARAM_INFO_READ); port->params[IDX_IO] = SPA_PARAM_INFO(SPA_PARAM_IO, SPA_PARAM_INFO_READ); port->params[IDX_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); port->params[IDX_Buffers] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); port->params[IDX_Latency] = SPA_PARAM_INFO(SPA_PARAM_Latency, SPA_PARAM_INFO_READWRITE); port->info.params = port->params; port->info.n_params = N_PORT_PARAMS; port->info.props = (i == PORT_OUT) ? &out_port_props : &in_port_props; port->latency = SPA_LATENCY_INFO(port->direction); port->latency.min_quantum = 1.0f; port->latency.max_quantum = 1.0f; /* Init the buffer lists */ spa_list_init(&port->ready); spa_list_init(&port->free); } this->duration = 1024; this->rate = 48000; set_latency(this, false); if (this->role == NODE_SERVER) { this->server = spa_bt_midi_server_new(&impl_server, this->conn, this->log, this); if (this->server == NULL) goto fail; } else { this->proxy = bluez5_gatt_characteristic1_proxy_new_sync(this->conn, G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START, BLUEZ_SERVICE, this->chr_path, NULL, &err); if (this->proxy == NULL) { spa_log_error(this->log, "Failed to create BLE MIDI GATT proxy %s: %s", this->chr_path, err->message); g_error_free(err); res = -EIO; goto fail; } } this->timerfd = spa_system_timerfd_create(this->data_system, CLOCK_MONOTONIC, SPA_FD_CLOEXEC | SPA_FD_NONBLOCK); return 0; fail: res = (res < 0) ? res : ((errno > 0) ? -errno : -EIO); impl_clear(handle); return res; } static const struct spa_interface_info impl_interfaces[] = { {SPA_TYPE_INTERFACE_Node,}, }; static int impl_enum_interface_info(const struct spa_handle_factory *factory, const struct spa_interface_info **info, uint32_t *index) { spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(info != NULL, -EINVAL); spa_return_val_if_fail(index != NULL, -EINVAL); switch (*index) { case 0: *info = &impl_interfaces[*index]; break; default: return 0; } (*index)++; return 1; } static const struct spa_dict_item info_items[] = { { SPA_KEY_FACTORY_AUTHOR, "Pauli Virtanen " }, { SPA_KEY_FACTORY_DESCRIPTION, "Bluez5 MIDI connection" }, }; static const struct spa_dict info = SPA_DICT_INIT_ARRAY(info_items); const struct spa_handle_factory spa_bluez5_midi_node_factory = { SPA_VERSION_HANDLE_FACTORY, SPA_NAME_API_BLUEZ5_MIDI_NODE, &info, impl_get_size, impl_init, impl_enum_interface_info, }; pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/bluez5/midi-parser.c000066400000000000000000000132531511204443500265340ustar00rootroot00000000000000/* BLE MIDI parser */ /* SPDX-FileCopyrightText: Copyright © 2022 Pauli Virtanen */ /* SPDX-License-Identifier: MIT */ #include #include #include "midi.h" enum midi_event_class { MIDI_BASIC, MIDI_SYSEX, MIDI_SYSCOMMON, MIDI_REALTIME, MIDI_ERROR }; static enum midi_event_class midi_event_info(uint8_t status, unsigned int *size) { switch (status) { case 0x80 ... 0x8f: case 0x90 ... 0x9f: case 0xa0 ... 0xaf: case 0xb0 ... 0xbf: case 0xe0 ... 0xef: *size = 3; return MIDI_BASIC; case 0xc0 ... 0xcf: case 0xd0 ... 0xdf: *size = 2; return MIDI_BASIC; case 0xf0: /* variable; count only status byte here */ *size = 1; return MIDI_SYSEX; case 0xf1: case 0xf3: *size = 2; return MIDI_SYSCOMMON; case 0xf2: *size = 3; return MIDI_SYSCOMMON; case 0xf6: case 0xf7: *size = 1; return MIDI_SYSCOMMON; case 0xf8 ... 0xff: *size = 1; return MIDI_REALTIME; case 0xf4: case 0xf5: default: /* undefined MIDI status */ *size = 0; return MIDI_ERROR; } } static void timestamp_set_high(uint16_t *time, uint8_t byte) { *time = (byte & 0x3f) << 7; } static void timestamp_set_low(uint16_t *time, uint8_t byte) { if ((*time & 0x7f) > (byte & 0x7f)) *time += 0x80; *time &= ~0x7f; *time |= byte & 0x7f; } int spa_bt_midi_parser_parse(struct spa_bt_midi_parser *parser, const uint8_t *src, size_t src_size, bool only_time, void (*event)(void *user_data, uint16_t time, uint8_t *event, size_t event_size), void *user_data) { const uint8_t *src_end = src + src_size; uint8_t running_status = 0; uint16_t time; uint8_t byte; #define NEXT() do { if (src == src_end) return -EINVAL; byte = *src++; } while (0) #define PUT(byte) do { if (only_time) { parser->size++; break; } \ if (parser->size == sizeof(parser->buf)) return -ENOSPC; \ parser->buf[parser->size++] = (byte); } while (0) /* Header */ NEXT(); if (!(byte & 0x80)) return -EINVAL; timestamp_set_high(&time, byte); while (src < src_end) { NEXT(); if (!parser->sysex) { uint8_t status = 0; unsigned int event_size; if (byte & 0x80) { /* Timestamp */ timestamp_set_low(&time, byte); NEXT(); /* Status? */ if (byte & 0x80) { parser->size = 0; PUT(byte); status = byte; } } if (status == 0) { /* Running status */ parser->size = 0; PUT(running_status); PUT(byte); status = running_status; } switch (midi_event_info(status, &event_size)) { case MIDI_BASIC: running_status = (event_size > 1) ? status : 0; break; case MIDI_REALTIME: case MIDI_SYSCOMMON: /* keep previous running status */ break; case MIDI_SYSEX: parser->sysex = true; /* XXX: not fully clear if SYSEX can be running status, assume no */ running_status = 0; continue; default: goto malformed; } /* Event data */ while (parser->size < event_size) { NEXT(); if (byte & 0x80) { /* BLE MIDI allows no interleaved events */ goto malformed; } PUT(byte); } event(user_data, time, parser->buf, parser->size); } else { if (byte & 0x80) { /* Timestamp */ timestamp_set_low(&time, byte); NEXT(); if (byte == 0xf7) { /* Sysex end */ PUT(byte); event(user_data, time, parser->buf, parser->size); parser->sysex = false; } else { /* Interleaved realtime event */ unsigned int event_size; if (midi_event_info(byte, &event_size) != MIDI_REALTIME) goto malformed; spa_assert(event_size == 1); event(user_data, time, &byte, 1); } } else { PUT(byte); } } } #undef NEXT #undef PUT return 0; malformed: /* Error (potentially recoverable) */ return -EINVAL; } int spa_bt_midi_writer_write(struct spa_bt_midi_writer *writer, uint64_t time, const uint8_t *event, size_t event_size) { /* BLE MIDI-1.0: maximum payload size is MTU - 3 */ const unsigned int max_size = writer->mtu - 3; const uint64_t time_msec = (time / SPA_NSEC_PER_MSEC); const uint16_t timestamp = time_msec & 0x1fff; #define PUT(byte) do { if (writer->size >= max_size) return -ENOSPC; \ writer->buf[writer->size++] = (byte); } while (0) if (writer->mtu < 5+3) return -ENOSPC; /* all events must fit */ spa_assert(max_size <= sizeof(writer->buf)); spa_assert(writer->size <= max_size); if (event_size == 0) return 0; if (writer->flush) { writer->flush = false; writer->size = 0; } if (writer->size == max_size) goto flush; /* Packet header */ if (writer->size == 0) { PUT(0x80 | (timestamp >> 7)); writer->running_status = 0; writer->running_time_msec = time_msec; } /* Timestamp low bits can wrap around, but not multiple times */ if (time_msec > writer->running_time_msec + 0x7f) goto flush; spa_assert(writer->pos < event_size); for (; writer->pos < event_size; ++writer->pos) { const unsigned int unused = max_size - writer->size; const uint8_t byte = event[writer->pos]; if (byte & 0x80) { enum midi_event_class class; unsigned int expected_size; class = midi_event_info(event[0], &expected_size); if (class == MIDI_BASIC && expected_size > 1 && writer->running_status == byte && writer->running_time_msec == time_msec) { /* Running status: continue with data */ continue; } if (unused < expected_size + 1) goto flush; /* Timestamp before status */ PUT(0x80 | (timestamp & 0x7f)); writer->running_time_msec = time_msec; if (class == MIDI_BASIC && expected_size > 1) writer->running_status = byte; else writer->running_status = 0; } else if (unused == 0) { break; } PUT(byte); } if (writer->pos < event_size) goto flush; writer->pos = 0; return 0; flush: writer->flush = true; return 1; #undef PUT } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/bluez5/midi-server.c000066400000000000000000000345501511204443500265510ustar00rootroot00000000000000/* Spa Bluez5 midi */ /* SPDX-FileCopyrightText: Copyright © 2022 Pauli Virtanen */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #include #include #include "midi.h" #include "bluez5-interface-gen.h" #include "dbus-monitor.h" SPA_LOG_TOPIC_DEFINE_STATIC(log_topic, "spa.bluez5.midi.server"); #undef SPA_LOG_TOPIC_DEFAULT #define SPA_LOG_TOPIC_DEFAULT &log_topic #define MIDI_SERVER_PATH "/midiserver%u" #define MIDI_SERVICE_PATH "/midiserver%u/service" #define MIDI_CHR_PATH "/midiserver%u/service/chr" #define MIDI_DSC_PATH "/midiserver%u/service/chr/dsc" #define BLE_DEFAULT_MTU 23 struct impl { struct spa_bt_midi_server this; struct spa_log *log; const struct spa_bt_midi_server_cb *cb; GDBusConnection *conn; struct dbus_monitor monitor; GDBusObjectManagerServer *manager; Bluez5GattCharacteristic1 *chr; void *user_data; uint32_t server_id; unsigned int write_acquired:1; unsigned int notify_acquired:1; }; struct _MidiServerManagerProxy { Bluez5GattManager1Proxy parent_instance; GCancellable *register_call; unsigned int registered:1; }; G_DECLARE_FINAL_TYPE(MidiServerManagerProxy, midi_server_manager_proxy, MIDI_SERVER, MANAGER_PROXY, Bluez5GattManager1Proxy) G_DEFINE_TYPE(MidiServerManagerProxy, midi_server_manager_proxy, BLUEZ5_TYPE_GATT_MANAGER1_PROXY) #define MIDI_SERVER_TYPE_MANAGER_PROXY (midi_server_manager_proxy_get_type()) /* * Characteristic user descriptor: not in BLE MIDI standard, but we * put a device name here in case we have multiple MIDI endpoints. */ static gboolean dsc_handle_read_value(Bluez5GattDescriptor1 *iface, GDBusMethodInvocation *invocation, GVariant *arg_options, gpointer user_data) { struct impl *impl = user_data; const char *description = NULL; uint16_t offset = 0; int len; g_variant_lookup(arg_options, "offset", "q", &offset); if (impl->cb->get_description) description = impl->cb->get_description(impl->user_data); if (!description) description = ""; len = strlen(description); if (offset > len) { g_dbus_method_invocation_return_dbus_error(invocation, "org.freedesktop.DBus.Error.InvalidArgs", "Invalid arguments"); return TRUE; } bluez5_gatt_descriptor1_complete_read_value(iface, invocation, description + offset); return TRUE; } static int export_dsc(struct impl *impl) { static const char * const flags[] = { "encrypt-read", NULL }; GDBusObjectSkeleton *skeleton = NULL; Bluez5GattDescriptor1 *iface = NULL; int res = -ENOMEM; char path[128]; iface = bluez5_gatt_descriptor1_skeleton_new(); if (!iface) goto done; spa_scnprintf(path, sizeof(path), MIDI_DSC_PATH, impl->server_id); skeleton = g_dbus_object_skeleton_new(path); if (!skeleton) goto done; g_dbus_object_skeleton_add_interface(skeleton, G_DBUS_INTERFACE_SKELETON(iface)); bluez5_gatt_descriptor1_set_uuid(iface, BT_GATT_CHARACTERISTIC_USER_DESCRIPTION_UUID); spa_scnprintf(path, sizeof(path), MIDI_CHR_PATH, impl->server_id); bluez5_gatt_descriptor1_set_characteristic(iface, path); bluez5_gatt_descriptor1_set_flags(iface, flags); g_signal_connect(iface, "handle-read-value", G_CALLBACK(dsc_handle_read_value), impl); g_dbus_object_manager_server_export(impl->manager, skeleton); spa_log_debug(impl->log, "MIDI GATT Descriptor exported, path=%s", g_dbus_object_get_object_path(G_DBUS_OBJECT(skeleton))); res = 0; done: g_clear_object(&iface); g_clear_object(&skeleton); return res; } /* * MIDI characteristic */ static gboolean chr_handle_read_value(Bluez5GattCharacteristic1 *iface, GDBusMethodInvocation *invocation, GVariant *arg_options, gpointer user_data) { /* BLE MIDI-1.0: returns empty value */ bluez5_gatt_characteristic1_complete_read_value(iface, invocation, ""); return TRUE; } static void chr_change_acquired(struct impl *impl, bool write, bool enabled) { if (write) { impl->write_acquired = enabled; bluez5_gatt_characteristic1_set_write_acquired(impl->chr, enabled); } else { impl->notify_acquired = enabled; bluez5_gatt_characteristic1_set_notify_acquired(impl->chr, enabled); } } static int create_socketpair(int fds[2]) { if (socketpair(AF_LOCAL, SOCK_SEQPACKET | SOCK_NONBLOCK | SOCK_CLOEXEC, 0, fds) < 0) return -errno; return 0; } static gboolean chr_handle_acquire(Bluez5GattCharacteristic1 *object, GDBusMethodInvocation *invocation, GUnixFDList *dummy, GVariant *arg_options, bool write, gpointer user_data) { struct impl *impl = user_data; const char *err_msg = "Failed"; uint16_t mtu = BLE_DEFAULT_MTU; gint fds[2] = {-1, -1}; int res; GUnixFDList *fd_list = NULL; GVariant *fd_handle = NULL; if ((write && (impl->cb->acquire_write == NULL)) || (!write && (impl->cb->acquire_notify == NULL))) { err_msg = "Not supported"; goto fail; } if ((write && impl->write_acquired) || (!write && impl->notify_acquired)) { err_msg = "Already acquired"; goto fail; } g_variant_lookup(arg_options, "mtu", "q", &mtu); if (create_socketpair(fds) < 0) { err_msg = "Socketpair creation failed"; goto fail; } if (write) res = impl->cb->acquire_write(impl->user_data, fds[0], mtu); else res = impl->cb->acquire_notify(impl->user_data, fds[0], mtu); if (res < 0) { err_msg = "Acquiring failed"; goto fail; } fds[0] = -1; fd_handle = g_variant_new_handle(0); fd_list = g_unix_fd_list_new_from_array(&fds[1], 1); fds[1] = -1; chr_change_acquired(impl, write, true); if (write) { bluez5_gatt_characteristic1_complete_acquire_write( object, invocation, fd_list, fd_handle, mtu); } else { bluez5_gatt_characteristic1_complete_acquire_notify( object, invocation, fd_list, fd_handle, mtu); } g_clear_object(&fd_list); return TRUE; fail: if (fds[0] >= 0) close(fds[0]); if (fds[1] >= 0) close(fds[1]); g_clear_pointer(&fd_handle, g_variant_unref); g_clear_object(&fd_list); g_dbus_method_invocation_return_dbus_error(invocation, "org.freedesktop.DBus.Error.Failed", err_msg); return TRUE; } static gboolean chr_handle_acquire_write(Bluez5GattCharacteristic1 *object, GDBusMethodInvocation *invocation, GUnixFDList *fd_list, GVariant *arg_options, gpointer user_data) { return chr_handle_acquire(object, invocation, fd_list, arg_options, true, user_data); } static gboolean chr_handle_acquire_notify(Bluez5GattCharacteristic1 *object, GDBusMethodInvocation *invocation, GUnixFDList *fd_list, GVariant *arg_options, gpointer user_data) { return chr_handle_acquire(object, invocation, fd_list, arg_options, false, user_data); } static int export_chr(struct impl *impl) { static const char * const flags[] = { "encrypt-read", "write-without-response", "encrypt-write", "encrypt-notify", NULL }; GDBusObjectSkeleton *skeleton = NULL; Bluez5GattCharacteristic1 *iface = NULL; int res = -ENOMEM; char path[128]; iface = bluez5_gatt_characteristic1_skeleton_new(); if (!iface) goto done; spa_scnprintf(path, sizeof(path), MIDI_CHR_PATH, impl->server_id); skeleton = g_dbus_object_skeleton_new(path); if (!skeleton) goto done; g_dbus_object_skeleton_add_interface(skeleton, G_DBUS_INTERFACE_SKELETON(iface)); bluez5_gatt_characteristic1_set_uuid(iface, BT_MIDI_CHR_UUID); spa_scnprintf(path, sizeof(path), MIDI_SERVICE_PATH, impl->server_id); bluez5_gatt_characteristic1_set_service(iface, path); bluez5_gatt_characteristic1_set_write_acquired(iface, FALSE); bluez5_gatt_characteristic1_set_notify_acquired(iface, FALSE); bluez5_gatt_characteristic1_set_flags(iface, flags); g_signal_connect(iface, "handle-read-value", G_CALLBACK(chr_handle_read_value), impl); g_signal_connect(iface, "handle-acquire-write", G_CALLBACK(chr_handle_acquire_write), impl); g_signal_connect(iface, "handle-acquire-notify", G_CALLBACK(chr_handle_acquire_notify), impl); g_dbus_object_manager_server_export(impl->manager, skeleton); impl->chr = g_object_ref(iface); spa_log_debug(impl->log, "MIDI GATT Characteristic exported, path=%s", g_dbus_object_get_object_path(G_DBUS_OBJECT(skeleton))); res = 0; done: g_clear_object(&iface); g_clear_object(&skeleton); return res; } /* * MIDI service */ static int export_service(struct impl *impl) { GDBusObjectSkeleton *skeleton = NULL; Bluez5GattService1 *iface = NULL; int res = -ENOMEM; char path[128]; iface = bluez5_gatt_service1_skeleton_new(); if (!iface) goto done; spa_scnprintf(path, sizeof(path), MIDI_SERVICE_PATH, impl->server_id); skeleton = g_dbus_object_skeleton_new(path); if (!skeleton) goto done; g_dbus_object_skeleton_add_interface(skeleton, G_DBUS_INTERFACE_SKELETON(iface)); bluez5_gatt_service1_set_uuid(iface, BT_MIDI_SERVICE_UUID); bluez5_gatt_service1_set_primary(iface, TRUE); g_dbus_object_manager_server_export(impl->manager, skeleton); spa_log_debug(impl->log, "MIDI GATT Service exported, path=%s", g_dbus_object_get_object_path(G_DBUS_OBJECT(skeleton))); res = 0; done: g_clear_object(&iface); g_clear_object(&skeleton); return res; } /* * Registration on all GattManagers */ static void manager_register_application_reply(GObject *source_object, GAsyncResult *res, gpointer user_data) { MidiServerManagerProxy *manager = MIDI_SERVER_MANAGER_PROXY(source_object); struct impl *impl = user_data; GError *err = NULL; bluez5_gatt_manager1_call_register_application_finish( BLUEZ5_GATT_MANAGER1(source_object), res, &err); if (g_error_matches(err, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { /* Operation canceled: user_data may be invalid by now */ g_error_free(err); goto done; } if (err) { spa_log_error(impl->log, "%s.RegisterApplication() failed: %s", BLUEZ_GATT_MANAGER_INTERFACE, err->message); g_error_free(err); goto done; } manager->registered = true; done: g_clear_object(&manager->register_call); } static int manager_register_application(struct impl *impl, MidiServerManagerProxy *manager) { GVariantBuilder builder; GVariant *options; if (manager->registered) return 0; if (manager->register_call) return -EBUSY; spa_log_debug(impl->log, "%s.RegisterApplication(%s) on %s", BLUEZ_GATT_MANAGER_INTERFACE, g_dbus_object_manager_get_object_path(G_DBUS_OBJECT_MANAGER(impl->manager)), g_dbus_proxy_get_object_path(G_DBUS_PROXY(manager))); manager->register_call = g_cancellable_new(); g_variant_builder_init(&builder, G_VARIANT_TYPE("a{sv}")); options = g_variant_builder_end(&builder); bluez5_gatt_manager1_call_register_application(BLUEZ5_GATT_MANAGER1(manager), g_dbus_object_manager_get_object_path(G_DBUS_OBJECT_MANAGER(impl->manager)), options, manager->register_call, manager_register_application_reply, impl); return 0; } static void midi_server_manager_proxy_init(MidiServerManagerProxy *manager) { } static void midi_server_manager_proxy_finalize(GObject *object) { MidiServerManagerProxy *manager = MIDI_SERVER_MANAGER_PROXY(object); g_cancellable_cancel(manager->register_call); g_clear_object(&manager->register_call); } static void midi_server_manager_proxy_class_init(MidiServerManagerProxyClass *klass) { GObjectClass *object_class = (GObjectClass *) klass; object_class->finalize = midi_server_manager_proxy_finalize; } static void manager_update(struct dbus_monitor *monitor, GDBusInterface *iface) { struct impl *impl = SPA_CONTAINER_OF(monitor, struct impl, monitor); manager_register_application(impl, MIDI_SERVER_MANAGER_PROXY(iface)); } static void manager_clear(struct dbus_monitor *monitor, GDBusInterface *iface) { midi_server_manager_proxy_finalize(G_OBJECT(iface)); } static void on_name_owner_change(struct dbus_monitor *monitor) { struct impl *impl = SPA_CONTAINER_OF(monitor, struct impl, monitor); /* * BlueZ disappeared/appeared. It does not appear to close the sockets * it quits, so we should force the chr release now. */ if (impl->cb->release) impl->cb->release(impl->user_data); chr_change_acquired(impl, true, false); chr_change_acquired(impl, false, false); } static void monitor_start(struct impl *impl) { struct dbus_monitor_proxy_type proxy_types[] = { { BLUEZ_GATT_MANAGER_INTERFACE, MIDI_SERVER_TYPE_MANAGER_PROXY, manager_update, manager_clear }, { NULL, BLUEZ5_TYPE_OBJECT_PROXY, NULL, NULL }, { NULL, G_TYPE_INVALID, NULL, NULL }, }; SPA_STATIC_ASSERT(SPA_N_ELEMENTS(proxy_types) <= DBUS_MONITOR_MAX_TYPES); dbus_monitor_init(&impl->monitor, BLUEZ5_TYPE_OBJECT_MANAGER_CLIENT, impl->log, impl->conn, BLUEZ_SERVICE, "/", proxy_types, on_name_owner_change); } /* * Object registration */ static int export_objects(struct impl *impl) { int res = 0; char path[128]; spa_scnprintf(path, sizeof(path), MIDI_SERVER_PATH, impl->server_id); impl->manager = g_dbus_object_manager_server_new(path); if (!impl->manager){ spa_log_error(impl->log, "Creating GDBus object manager failed"); goto fail; } if ((res = export_service(impl)) < 0) goto fail; if ((res = export_chr(impl)) < 0) goto fail; if ((res = export_dsc(impl)) < 0) goto fail; g_dbus_object_manager_server_set_connection(impl->manager, impl->conn); return 0; fail: res = (res < 0) ? res : ((errno > 0) ? -errno : -EIO); spa_log_error(impl->log, "Failed to register BLE MIDI services in DBus: %s", spa_strerror(res)); g_clear_object(&impl->manager); return res; } struct spa_bt_midi_server *spa_bt_midi_server_new(const struct spa_bt_midi_server_cb *cb, GDBusConnection *conn, struct spa_log *log, void *user_data) { static unsigned int server_id = 0; struct impl *impl; char path[128]; int res = 0; impl = calloc(1, sizeof(struct impl)); if (impl == NULL) goto fail; impl->server_id = server_id++; impl->user_data = user_data; impl->cb = cb; impl->log = log; impl->conn = conn; spa_log_topic_init(impl->log, &log_topic); if ((res = export_objects(impl)) < 0) goto fail; monitor_start(impl); g_object_ref(impl->conn); spa_scnprintf(path, sizeof(path), MIDI_CHR_PATH, impl->server_id); impl->this.chr_path = strdup(path); return &impl->this; fail: res = (res < 0) ? res : ((errno > 0) ? -errno : -EIO); free(impl); errno = res; return NULL; } void spa_bt_midi_server_destroy(struct spa_bt_midi_server *server) { struct impl *impl = SPA_CONTAINER_OF(server, struct impl, this); free((void *)impl->this.chr_path); g_clear_object(&impl->chr); dbus_monitor_clear(&impl->monitor); g_clear_object(&impl->manager); g_clear_object(&impl->conn); free(impl); } void spa_bt_midi_server_released(struct spa_bt_midi_server *server, bool write) { struct impl *impl = SPA_CONTAINER_OF(server, struct impl, this); chr_change_acquired(impl, write, false); } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/bluez5/midi.h000066400000000000000000000071211511204443500252440ustar00rootroot00000000000000/* Spa V4l2 dbus */ /* SPDX-FileCopyrightText: Copyright © 2022 Pauli Virtanen */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_BT_MIDI_H_ #define SPA_BT_MIDI_H_ #include #include #include #include #include #include #include #define BLUEZ_SERVICE "org.bluez" #define BLUEZ_ADAPTER_INTERFACE BLUEZ_SERVICE ".Adapter1" #define BLUEZ_DEVICE_INTERFACE BLUEZ_SERVICE ".Device1" #define BLUEZ_GATT_MANAGER_INTERFACE BLUEZ_SERVICE ".GattManager1" #define BLUEZ_GATT_PROFILE_INTERFACE BLUEZ_SERVICE ".GattProfile1" #define BLUEZ_GATT_SERVICE_INTERFACE BLUEZ_SERVICE ".GattService1" #define BLUEZ_GATT_CHR_INTERFACE BLUEZ_SERVICE ".GattCharacteristic1" #define BLUEZ_GATT_DSC_INTERFACE BLUEZ_SERVICE ".GattDescriptor1" #define BT_MIDI_SERVICE_UUID "03b80e5a-ede8-4b33-a751-6ce34ec4c700" #define BT_MIDI_CHR_UUID "7772e5db-3868-4112-a1a9-f2669d106bf3" #define BT_GATT_CHARACTERISTIC_USER_DESCRIPTION_UUID "00002901-0000-1000-8000-00805f9b34fb" #define MIDI_BUF_SIZE 8192 #define MIDI_MAX_MTU 8192 #define MIDI_CLOCK_PERIOD_MSEC 0x2000 #define MIDI_CLOCK_PERIOD_NSEC (MIDI_CLOCK_PERIOD_MSEC * SPA_NSEC_PER_MSEC) struct spa_bt_midi_server { const char *chr_path; }; struct spa_bt_midi_parser { unsigned int size; unsigned int sysex:1; uint8_t buf[MIDI_BUF_SIZE]; }; struct spa_bt_midi_writer { unsigned int size; unsigned int mtu; unsigned int pos; uint8_t running_status; uint64_t running_time_msec; unsigned int flush:1; uint8_t buf[MIDI_MAX_MTU]; }; struct spa_bt_midi_server_cb { int (*acquire_notify)(void *user_data, int fd, uint16_t mtu); int (*acquire_write)(void *user_data, int fd, uint16_t mtu); int (*release)(void *user_data); const char *(*get_description)(void *user_data); }; static inline void spa_bt_midi_parser_init(struct spa_bt_midi_parser *parser) { parser->size = 0; parser->sysex = 0; } static inline void spa_bt_midi_parser_dup(struct spa_bt_midi_parser *src, struct spa_bt_midi_parser *dst, bool only_time) { dst->size = src->size; dst->sysex = src->sysex; if (!only_time) memcpy(dst->buf, src->buf, src->size); } /** * Parse a single BLE MIDI data packet to normalized MIDI events. */ int spa_bt_midi_parser_parse(struct spa_bt_midi_parser *parser, const uint8_t *src, size_t src_size, bool only_time, void (*event)(void *user_data, uint16_t time, uint8_t *event, size_t event_size), void *user_data); static inline void spa_bt_midi_writer_init(struct spa_bt_midi_writer *writer, unsigned int mtu) { writer->size = 0; writer->mtu = SPA_MIN(mtu, (unsigned int)MIDI_MAX_MTU); writer->pos = 0; writer->running_status = 0; writer->running_time_msec = 0; writer->flush = 0; } /** * Add a new event to midi writer buffer. * * spa_bt_midi_writer_init(&writer, mtu); * for (time, event, size) in midi events { * do { * res = spa_bt_midi_writer_write(&writer, time, event, size); * if (res < 0) { * fail with error * } else if (res) { * send_packet(writer->buf, writer->size); * } * } while (res); * } * if (writer.size > 0) * send_packet(writer->buf, writer->size); */ int spa_bt_midi_writer_write(struct spa_bt_midi_writer *writer, uint64_t time, const uint8_t *event, size_t event_size); struct spa_bt_midi_server *spa_bt_midi_server_new(const struct spa_bt_midi_server_cb *cb, GDBusConnection *conn, struct spa_log *log, void *user_data); void spa_bt_midi_server_released(struct spa_bt_midi_server *server, bool write); void spa_bt_midi_server_destroy(struct spa_bt_midi_server *server); #endif pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/bluez5/modemmanager.c000066400000000000000000001026361511204443500267600ustar00rootroot00000000000000/* Spa Bluez5 ModemManager proxy */ /* SPDX-FileCopyrightText: Copyright © 2022 Collabora */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #include "modemmanager.h" SPA_LOG_TOPIC_DEFINE_STATIC(log_topic, "spa.bluez5.modemmanager"); #undef SPA_LOG_TOPIC_DEFAULT #define SPA_LOG_TOPIC_DEFAULT &log_topic #define DBUS_INTERFACE_OBJECTMANAGER "org.freedesktop.DBus.ObjectManager" struct modem { char *path; bool network_has_service; unsigned int signal_strength; }; struct impl { struct spa_log *log; DBusConnection *conn; char *allowed_modem_device; bool filters_added; DBusPendingCall *pending; DBusPendingCall *voice_pending; const struct mm_ops *ops; void *user_data; struct modem modem; struct spa_list call_list; }; struct dbus_cmd_data { struct impl *this; struct call *call; void *user_data; }; static int mm_state_to_clcc(struct impl *this, MMCallState state) { switch (state) { case MM_CALL_STATE_DIALING: return CLCC_DIALING; case MM_CALL_STATE_RINGING_OUT: return CLCC_ALERTING; case MM_CALL_STATE_RINGING_IN: return CLCC_INCOMING; case MM_CALL_STATE_ACTIVE: return CLCC_ACTIVE; case MM_CALL_STATE_HELD: return CLCC_HELD; case MM_CALL_STATE_WAITING: return CLCC_WAITING; case MM_CALL_STATE_TERMINATED: case MM_CALL_STATE_UNKNOWN: default: return -1; } } static void mm_call_state_changed(struct impl *this) { struct call *call; bool call_indicator = false; enum call_setup call_setup_indicator = CIND_CALLSETUP_NONE; spa_list_for_each(call, &this->call_list, link) { call_indicator |= (call->state == CLCC_ACTIVE); if (call->state == CLCC_INCOMING && call_setup_indicator < CIND_CALLSETUP_INCOMING) call_setup_indicator = CIND_CALLSETUP_INCOMING; else if (call->state == CLCC_DIALING && call_setup_indicator < CIND_CALLSETUP_DIALING) call_setup_indicator = CIND_CALLSETUP_DIALING; else if (call->state == CLCC_ALERTING && call_setup_indicator < CIND_CALLSETUP_ALERTING) call_setup_indicator = CIND_CALLSETUP_ALERTING; } if (this->ops->set_call_active) this->ops->set_call_active(call_indicator, this->user_data); if (this->ops->set_call_setup) this->ops->set_call_setup(call_setup_indicator, this->user_data); } static void mm_get_call_properties_reply(DBusPendingCall *pending, void *user_data) { struct call *call = user_data; struct impl *this = call->this; DBusMessageIter arg_i, element_i; MMCallDirection direction; MMCallState state; spa_assert(call->pending == pending); spa_autoptr(DBusMessage) r = steal_reply_and_unref(&call->pending); if (r == NULL) return; if (dbus_message_is_error(r, DBUS_ERROR_UNKNOWN_METHOD)) { spa_log_warn(this->log, "ModemManager D-Bus Call not available"); return; } if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) { spa_log_error(this->log, "GetAll() failed: %s", dbus_message_get_error_name(r)); return; } if (!dbus_message_iter_init(r, &arg_i) || !spa_streq(dbus_message_get_signature(r), "a{sv}")) { spa_log_error(this->log, "Invalid arguments in GetAll() reply"); return; } spa_log_debug(this->log, "Call path: %s", call->path); dbus_message_iter_recurse(&arg_i, &element_i); while (dbus_message_iter_get_arg_type(&element_i) != DBUS_TYPE_INVALID) { DBusMessageIter i, value_i; const char *key; dbus_message_iter_recurse(&element_i, &i); dbus_message_iter_get_basic(&i, &key); dbus_message_iter_next(&i); dbus_message_iter_recurse(&i, &value_i); if (spa_streq(key, MM_CALL_PROPERTY_DIRECTION)) { dbus_message_iter_get_basic(&value_i, &direction); spa_log_debug(this->log, "Call direction: %u", direction); call->direction = (direction == MM_CALL_DIRECTION_INCOMING) ? CALL_INCOMING : CALL_OUTGOING; } else if (spa_streq(key, MM_CALL_PROPERTY_NUMBER)) { char *number; dbus_message_iter_get_basic(&value_i, &number); spa_log_debug(this->log, "Call number: %s", number); if (call->number) free(call->number); call->number = strdup(number); } else if (spa_streq(key, MM_CALL_PROPERTY_STATE)) { int clcc_state; dbus_message_iter_get_basic(&value_i, &state); spa_log_debug(this->log, "Call state: %u", state); clcc_state = mm_state_to_clcc(this, state); if (clcc_state < 0) { spa_log_debug(this->log, "Unsupported modem state: %s, state=%d", call->path, call->state); } else { call->state = clcc_state; mm_call_state_changed(this); } } dbus_message_iter_next(&element_i); } } static DBusHandlerResult mm_parse_voice_properties(struct impl *this, DBusMessageIter *props_i) { while (dbus_message_iter_get_arg_type(props_i) != DBUS_TYPE_INVALID) { DBusMessageIter i, value_i, element_i; const char *key; dbus_message_iter_recurse(props_i, &i); dbus_message_iter_get_basic(&i, &key); dbus_message_iter_next(&i); dbus_message_iter_recurse(&i, &value_i); if (spa_streq(key, MM_MODEM_VOICE_PROPERTY_CALLS)) { spa_log_debug(this->log, "Voice properties"); dbus_message_iter_recurse(&value_i, &element_i); while (dbus_message_iter_get_arg_type(&element_i) == DBUS_TYPE_OBJECT_PATH) { const char *call_object; dbus_message_iter_get_basic(&element_i, &call_object); spa_log_debug(this->log, " Call: %s", call_object); dbus_message_iter_next(&element_i); } } dbus_message_iter_next(props_i); } return DBUS_HANDLER_RESULT_HANDLED; } static DBusHandlerResult mm_parse_modem3gpp_properties(struct impl *this, DBusMessageIter *props_i) { while (dbus_message_iter_get_arg_type(props_i) != DBUS_TYPE_INVALID) { DBusMessageIter i, value_i; const char *key; dbus_message_iter_recurse(props_i, &i); dbus_message_iter_get_basic(&i, &key); dbus_message_iter_next(&i); dbus_message_iter_recurse(&i, &value_i); if (spa_streq(key, MM_MODEM_MODEM3GPP_PROPERTY_OPERATORNAME)) { char *operator_name; dbus_message_iter_get_basic(&value_i, &operator_name); spa_log_debug(this->log, "Network operator code: %s", operator_name); if (this->ops->set_modem_operator_name) this->ops->set_modem_operator_name(operator_name, this->user_data); } else if (spa_streq(key, MM_MODEM_MODEM3GPP_PROPERTY_REGISTRATIONSTATE)) { MMModem3gppRegistrationState state; bool is_roaming; dbus_message_iter_get_basic(&value_i, &state); spa_log_debug(this->log, "Registration state: %d", state); if (state == MM_MODEM_3GPP_REGISTRATION_STATE_ROAMING || state == MM_MODEM_3GPP_REGISTRATION_STATE_ROAMING_CSFB_NOT_PREFERRED || state == MM_MODEM_3GPP_REGISTRATION_STATE_ROAMING_SMS_ONLY) is_roaming = true; else is_roaming = false; if (this->ops->set_modem_roaming) this->ops->set_modem_roaming(is_roaming, this->user_data); } dbus_message_iter_next(props_i); } return DBUS_HANDLER_RESULT_HANDLED; } static DBusHandlerResult mm_parse_modem_properties(struct impl *this, DBusMessageIter *props_i) { while (dbus_message_iter_get_arg_type(props_i) != DBUS_TYPE_INVALID) { DBusMessageIter i, value_i; const char *key; dbus_message_iter_recurse(props_i, &i); dbus_message_iter_get_basic(&i, &key); dbus_message_iter_next(&i); dbus_message_iter_recurse(&i, &value_i); if(spa_streq(key, MM_MODEM_PROPERTY_EQUIPMENTIDENTIFIER)) { char *imei; dbus_message_iter_get_basic(&value_i, &imei); spa_log_debug(this->log, "Modem IMEI: %s", imei); } else if(spa_streq(key, MM_MODEM_PROPERTY_MANUFACTURER)) { char *manufacturer; dbus_message_iter_get_basic(&value_i, &manufacturer); spa_log_debug(this->log, "Modem manufacturer: %s", manufacturer); } else if(spa_streq(key, MM_MODEM_PROPERTY_MODEL)) { char *model; dbus_message_iter_get_basic(&value_i, &model); spa_log_debug(this->log, "Modem model: %s", model); } else if (spa_streq(key, MM_MODEM_PROPERTY_OWNNUMBERS)) { char *number; DBusMessageIter array_i; dbus_message_iter_recurse(&value_i, &array_i); if (dbus_message_iter_get_arg_type(&array_i) == DBUS_TYPE_STRING) { dbus_message_iter_get_basic(&array_i, &number); spa_log_debug(this->log, "Modem own number: %s", number); if (this->ops->set_modem_own_number) this->ops->set_modem_own_number(number, this->user_data); } } else if(spa_streq(key, MM_MODEM_PROPERTY_REVISION)) { char *revision; dbus_message_iter_get_basic(&value_i, &revision); spa_log_debug(this->log, "Modem revision: %s", revision); } else if(spa_streq(key, MM_MODEM_PROPERTY_SIGNALQUALITY)) { unsigned int percentage, signal_strength; DBusMessageIter struct_i; dbus_message_iter_recurse(&value_i, &struct_i); if (dbus_message_iter_get_arg_type(&struct_i) == DBUS_TYPE_UINT32) { dbus_message_iter_get_basic(&struct_i, &percentage); signal_strength = (unsigned int) round(percentage / 20.0); spa_log_debug(this->log, "Network signal strength: %d (%d)", percentage, signal_strength); if(this->ops->set_modem_signal_strength) this->ops->set_modem_signal_strength(signal_strength, this->user_data); } } else if(spa_streq(key, MM_MODEM_PROPERTY_STATE)) { MMModemState state; bool has_service; dbus_message_iter_get_basic(&value_i, &state); spa_log_debug(this->log, "Network state: %d", state); has_service = (state >= MM_MODEM_STATE_REGISTERED); if (this->ops->set_modem_service) this->ops->set_modem_service(has_service, this->user_data); } dbus_message_iter_next(props_i); } return DBUS_HANDLER_RESULT_HANDLED; } static DBusHandlerResult mm_parse_interfaces(struct impl *this, DBusMessageIter *dict_i) { DBusMessageIter element_i, props_i; const char *path; spa_assert(this); spa_assert(dict_i); dbus_message_iter_get_basic(dict_i, &path); dbus_message_iter_next(dict_i); dbus_message_iter_recurse(dict_i, &element_i); while (dbus_message_iter_get_arg_type(&element_i) == DBUS_TYPE_DICT_ENTRY) { DBusMessageIter iface_i; const char *interface; dbus_message_iter_recurse(&element_i, &iface_i); dbus_message_iter_get_basic(&iface_i, &interface); dbus_message_iter_next(&iface_i); spa_assert(dbus_message_iter_get_arg_type(&iface_i) == DBUS_TYPE_ARRAY); dbus_message_iter_recurse(&iface_i, &props_i); if (spa_streq(interface, MM_DBUS_INTERFACE_MODEM)) { spa_log_debug(this->log, "Found Modem interface %s, path %s", interface, path); if (this->modem.path == NULL) { if (this->allowed_modem_device) { DBusMessageIter i; dbus_message_iter_recurse(&iface_i, &i); while (dbus_message_iter_get_arg_type(&i) != DBUS_TYPE_INVALID) { DBusMessageIter key_i, value_i; const char *key; dbus_message_iter_recurse(&i, &key_i); dbus_message_iter_get_basic(&key_i, &key); dbus_message_iter_next(&key_i); dbus_message_iter_recurse(&key_i, &value_i); if (spa_streq(key, MM_MODEM_PROPERTY_DEVICE)) { char *device; dbus_message_iter_get_basic(&value_i, &device); if (!spa_streq(this->allowed_modem_device, device)) { spa_log_debug(this->log, "Modem not allowed: %s", device); goto next; } } dbus_message_iter_next(&i); } } this->modem.path = strdup(path); } else if (!spa_streq(this->modem.path, path)) { spa_log_debug(this->log, "A modem is already registered"); goto next; } mm_parse_modem_properties(this, &props_i); } else if (spa_streq(interface, MM_DBUS_INTERFACE_MODEM_MODEM3GPP)) { if (spa_streq(this->modem.path, path)) { spa_log_debug(this->log, "Found Modem3GPP interface %s, path %s", interface, path); mm_parse_modem3gpp_properties(this, &props_i); } } else if (spa_streq(interface, MM_DBUS_INTERFACE_MODEM_VOICE)) { if (spa_streq(this->modem.path, path)) { spa_log_debug(this->log, "Found Voice interface %s, path %s", interface, path); mm_parse_voice_properties(this, &props_i); } } next: dbus_message_iter_next(&element_i); } return DBUS_HANDLER_RESULT_HANDLED; } static void mm_get_managed_objects_reply(DBusPendingCall *pending, void *user_data) { struct impl *this = user_data; DBusMessageIter i, array_i; spa_assert(this->pending == pending); spa_autoptr(DBusMessage) r = steal_reply_and_unref(&this->pending); if (r == NULL) return; if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) { spa_log_error(this->log, "Failed to get a list of endpoints from ModemManager: %s", dbus_message_get_error_name(r)); return; } if (!dbus_message_iter_init(r, &i) || !spa_streq(dbus_message_get_signature(r), "a{oa{sa{sv}}}")) { spa_log_error(this->log, "Invalid arguments in GetManagedObjects() reply"); return; } dbus_message_iter_recurse(&i, &array_i); while (dbus_message_iter_get_arg_type(&array_i) != DBUS_TYPE_INVALID) { DBusMessageIter dict_i; dbus_message_iter_recurse(&array_i, &dict_i); mm_parse_interfaces(this, &dict_i); dbus_message_iter_next(&array_i); } } static bool mm_get_managed_objects(struct impl *this) { spa_autoptr(DBusMessage) m = dbus_message_new_method_call(MM_DBUS_SERVICE, "/org/freedesktop/ModemManager1", DBUS_INTERFACE_OBJECTMANAGER, "GetManagedObjects"); if (m == NULL) return false; dbus_message_set_auto_start(m, false); this->pending = send_with_reply(this->conn, m, mm_get_managed_objects_reply, this); if (!this->pending) { spa_log_error(this->log, "dbus call failure"); return false; } return true; } static void call_free(struct call *call) { spa_list_remove(&call->link); cancel_and_unref(&call->pending); if (call->number) free(call->number); if (call->path) free(call->path); free(call); } static void mm_clean_voice(struct impl *this) { struct call *call; spa_list_consume(call, &this->call_list, link) call_free(call); cancel_and_unref(&this->voice_pending); if (this->ops->set_call_setup) this->ops->set_call_setup(CIND_CALLSETUP_NONE, this->user_data); if (this->ops->set_call_active) this->ops->set_call_active(false, this->user_data); } static void mm_clean_modem3gpp(struct impl *this) { if (this->ops->set_modem_operator_name) this->ops->set_modem_operator_name(NULL, this->user_data); if (this->ops->set_modem_roaming) this->ops->set_modem_roaming(false, this->user_data); } static void mm_clean_modem(struct impl *this) { if (this->modem.path) { free(this->modem.path); this->modem.path = NULL; } if(this->ops->set_modem_signal_strength) this->ops->set_modem_signal_strength(0, this->user_data); if (this->ops->set_modem_service) this->ops->set_modem_service(false, this->user_data); this->modem.network_has_service = false; } static DBusHandlerResult mm_filter_cb(DBusConnection *bus, DBusMessage *m, void *user_data) { struct impl *this = user_data; if (dbus_message_is_signal(m, "org.freedesktop.DBus", "NameOwnerChanged")) { const char *name, *old_owner, *new_owner; spa_auto(DBusError) err = DBUS_ERROR_INIT; spa_log_debug(this->log, "Name owner changed %s", dbus_message_get_path(m)); if (!dbus_message_get_args(m, &err, DBUS_TYPE_STRING, &name, DBUS_TYPE_STRING, &old_owner, DBUS_TYPE_STRING, &new_owner, DBUS_TYPE_INVALID)) { spa_log_error(this->log, "Failed to parse org.freedesktop.DBus.NameOwnerChanged: %s", err.message); goto finish; } if (spa_streq(name, MM_DBUS_SERVICE)) { if (old_owner && *old_owner) { spa_log_debug(this->log, "ModemManager daemon disappeared (%s)", old_owner); mm_clean_voice(this); mm_clean_modem3gpp(this); mm_clean_modem(this); } if (new_owner && *new_owner) { spa_log_debug(this->log, "ModemManager daemon appeared (%s)", new_owner); if (!mm_get_managed_objects(this)) goto finish; } } } else if (dbus_message_is_signal(m, DBUS_INTERFACE_OBJECTMANAGER, DBUS_SIGNAL_INTERFACES_ADDED)) { DBusMessageIter arg_i; if (!dbus_message_iter_init(m, &arg_i) || !spa_streq(dbus_message_get_signature(m), "oa{sa{sv}}")) { spa_log_error(this->log, "Invalid signature found in InterfacesAdded"); goto finish; } mm_parse_interfaces(this, &arg_i); } else if (dbus_message_is_signal(m, DBUS_INTERFACE_OBJECTMANAGER, DBUS_SIGNAL_INTERFACES_REMOVED)) { const char *path; DBusMessageIter arg_i, element_i; if (!dbus_message_iter_init(m, &arg_i) || !spa_streq(dbus_message_get_signature(m), "oas")) { spa_log_error(this->log, "Invalid signature found in InterfacesRemoved"); goto finish; } dbus_message_iter_get_basic(&arg_i, &path); if (!spa_streq(this->modem.path, path)) goto finish; dbus_message_iter_next(&arg_i); dbus_message_iter_recurse(&arg_i, &element_i); while (dbus_message_iter_get_arg_type(&element_i) == DBUS_TYPE_STRING) { const char *iface; dbus_message_iter_get_basic(&element_i, &iface); spa_log_debug(this->log, "Interface removed %s", path); if (spa_streq(iface, MM_DBUS_INTERFACE_MODEM)) { spa_log_debug(this->log, "Modem interface %s removed, path %s", iface, path); mm_clean_modem(this); } else if (spa_streq(iface, MM_DBUS_INTERFACE_MODEM_MODEM3GPP)) { spa_log_debug(this->log, "Modem3GPP interface %s removed, path %s", iface, path); mm_clean_modem3gpp(this); } else if (spa_streq(iface, MM_DBUS_INTERFACE_MODEM_VOICE)) { spa_log_debug(this->log, "Voice interface %s removed, path %s", iface, path); mm_clean_voice(this); } dbus_message_iter_next(&element_i); } } else if (dbus_message_is_signal(m, DBUS_INTERFACE_PROPERTIES, DBUS_SIGNAL_PROPERTIES_CHANGED)) { const char *path; DBusMessageIter iface_i, props_i; const char *interface; path = dbus_message_get_path(m); if (!spa_streq(this->modem.path, path)) goto finish; if (!dbus_message_iter_init(m, &iface_i) || !spa_streq(dbus_message_get_signature(m), "sa{sv}as")) { spa_log_error(this->log, "Invalid signature found in PropertiesChanged"); goto finish; } dbus_message_iter_get_basic(&iface_i, &interface); dbus_message_iter_next(&iface_i); spa_assert(dbus_message_iter_get_arg_type(&iface_i) == DBUS_TYPE_ARRAY); dbus_message_iter_recurse(&iface_i, &props_i); if (spa_streq(interface, MM_DBUS_INTERFACE_MODEM)) { spa_log_debug(this->log, "Properties changed on %s", path); mm_parse_modem_properties(this, &props_i); } else if (spa_streq(interface, MM_DBUS_INTERFACE_MODEM_MODEM3GPP)) { spa_log_debug(this->log, "Properties changed on %s", path); mm_parse_modem3gpp_properties(this, &props_i); } else if (spa_streq(interface, MM_DBUS_INTERFACE_MODEM_VOICE)) { spa_log_debug(this->log, "Properties changed on %s", path); mm_parse_voice_properties(this, &props_i); } } else if (dbus_message_is_signal(m, MM_DBUS_INTERFACE_MODEM_VOICE, MM_MODEM_VOICE_SIGNAL_CALLADDED)) { DBusMessageIter iface_i; const char *path; struct call *call_object; const char *mm_call_interface = MM_DBUS_INTERFACE_CALL; spa_autoptr(DBusMessage) m2 = NULL; if (!spa_streq(this->modem.path, dbus_message_get_path(m))) goto finish; if (!dbus_message_iter_init(m, &iface_i) || !spa_streq(dbus_message_get_signature(m), "o")) { spa_log_error(this->log, "Invalid signature found in %s", MM_MODEM_VOICE_SIGNAL_CALLADDED); goto finish; } dbus_message_iter_get_basic(&iface_i, &path); spa_log_debug(this->log, "New call: %s", path); call_object = calloc(1, sizeof(struct call)); if (call_object == NULL) return DBUS_HANDLER_RESULT_NEED_MEMORY; call_object->this = this; call_object->path = strdup(path); spa_list_append(&this->call_list, &call_object->link); m2 = dbus_message_new_method_call(MM_DBUS_SERVICE, path, DBUS_INTERFACE_PROPERTIES, "GetAll"); if (m2 == NULL) goto finish; dbus_message_append_args(m2, DBUS_TYPE_STRING, &mm_call_interface, DBUS_TYPE_INVALID); call_object->pending = send_with_reply(this->conn, m2, mm_get_call_properties_reply, call_object); if (!call_object->pending) { spa_log_error(this->log, "dbus call failure"); goto finish; } } else if (dbus_message_is_signal(m, MM_DBUS_INTERFACE_MODEM_VOICE, MM_MODEM_VOICE_SIGNAL_CALLDELETED)) { const char *path; DBusMessageIter iface_i; struct call *call, *call_tmp; if (!spa_streq(this->modem.path, dbus_message_get_path(m))) goto finish; if (!dbus_message_iter_init(m, &iface_i) || !spa_streq(dbus_message_get_signature(m), "o")) { spa_log_error(this->log, "Invalid signature found in %s", MM_MODEM_VOICE_SIGNAL_CALLDELETED); goto finish; } dbus_message_iter_get_basic(&iface_i, &path); spa_log_debug(this->log, "Call ended: %s", path); spa_list_for_each_safe(call, call_tmp, &this->call_list, link) { if (spa_streq(call->path, path)) call_free(call); } mm_call_state_changed(this); } else if (dbus_message_is_signal(m, MM_DBUS_INTERFACE_CALL, MM_CALL_SIGNAL_STATECHANGED)) { const char *path; DBusMessageIter iface_i; MMCallState old, new; MMCallStateReason reason; struct call *call = NULL, *call_tmp; int clcc_state; if (!dbus_message_iter_init(m, &iface_i) || !spa_streq(dbus_message_get_signature(m), "iiu")) { spa_log_error(this->log, "Invalid signature found in %s", MM_CALL_SIGNAL_STATECHANGED); goto finish; } path = dbus_message_get_path(m); dbus_message_iter_get_basic(&iface_i, &old); dbus_message_iter_next(&iface_i); dbus_message_iter_get_basic(&iface_i, &new); dbus_message_iter_next(&iface_i); dbus_message_iter_get_basic(&iface_i, &reason); spa_log_debug(this->log, "Call state %s changed to %d (old = %d, reason = %u)", path, new, old, reason); spa_list_for_each(call_tmp, &this->call_list, link) { if (spa_streq(call_tmp->path, path)) { call = call_tmp; break; } } if (call == NULL) { spa_log_warn(this->log, "No call reference for %s", path); goto finish; } clcc_state = mm_state_to_clcc(this, new); if (clcc_state < 0) { spa_log_debug(this->log, "Unsupported modem state: %s, state=%d", call->path, call->state); } else { call->state = clcc_state; mm_call_state_changed(this); } } finish: return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } static int add_filters(struct impl *this) { if (this->filters_added) return 0; if (!dbus_connection_add_filter(this->conn, mm_filter_cb, this, NULL)) { spa_log_error(this->log, "failed to add filter function"); return -EIO; } spa_auto(DBusError) err = DBUS_ERROR_INIT; dbus_bus_add_match(this->conn, "type='signal',sender='org.freedesktop.DBus'," "interface='org.freedesktop.DBus',member='NameOwnerChanged'," "arg0='" MM_DBUS_SERVICE "'", &err); dbus_bus_add_match(this->conn, "type='signal',sender='" MM_DBUS_SERVICE "'," "interface='" DBUS_INTERFACE_OBJECTMANAGER "',member='" DBUS_SIGNAL_INTERFACES_ADDED "'", &err); dbus_bus_add_match(this->conn, "type='signal',sender='" MM_DBUS_SERVICE "'," "interface='" DBUS_INTERFACE_OBJECTMANAGER "',member='" DBUS_SIGNAL_INTERFACES_REMOVED "'", &err); dbus_bus_add_match(this->conn, "type='signal',sender='" MM_DBUS_SERVICE "'," "interface='" DBUS_INTERFACE_PROPERTIES "',member='" DBUS_SIGNAL_PROPERTIES_CHANGED "'", &err); dbus_bus_add_match(this->conn, "type='signal',sender='" MM_DBUS_SERVICE "'," "interface='" MM_DBUS_INTERFACE_MODEM_VOICE "',member='" MM_MODEM_VOICE_SIGNAL_CALLADDED "'", &err); dbus_bus_add_match(this->conn, "type='signal',sender='" MM_DBUS_SERVICE "'," "interface='" MM_DBUS_INTERFACE_MODEM_VOICE "',member='" MM_MODEM_VOICE_SIGNAL_CALLDELETED "'", &err); dbus_bus_add_match(this->conn, "type='signal',sender='" MM_DBUS_SERVICE "'," "interface='" MM_DBUS_INTERFACE_CALL "',member='" MM_CALL_SIGNAL_STATECHANGED "'", &err); this->filters_added = true; return 0; } bool mm_is_available(void *modemmanager) { struct impl *this = modemmanager; if (this == NULL) return false; return this->modem.path != NULL; } unsigned int mm_supported_features(void) { return SPA_BT_HFP_AG_FEATURE_REJECT_CALL | SPA_BT_HFP_AG_FEATURE_ENHANCED_CALL_STATUS; } static void mm_get_call_simple_reply(DBusPendingCall *pending, void *data) { struct dbus_cmd_data *dbus_cmd_data = data; struct impl *this = dbus_cmd_data->this; struct call *call = dbus_cmd_data->call; void *user_data = dbus_cmd_data->user_data; free(data); spa_assert(call->pending == pending); spa_autoptr(DBusMessage) r = steal_reply_and_unref(&call->pending); if (r == NULL) return; if (dbus_message_is_error(r, DBUS_ERROR_UNKNOWN_METHOD)) { spa_log_warn(this->log, "ModemManager D-Bus method not available"); goto finish; } if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) { spa_log_error(this->log, "ModemManager method failed: %s", dbus_message_get_error_name(r)); goto finish; } this->ops->send_cmd_result(true, 0, user_data); return; finish: this->ops->send_cmd_result(false, CMEE_AG_FAILURE, user_data); } static void mm_get_call_create_reply(DBusPendingCall *pending, void *data) { struct dbus_cmd_data *dbus_cmd_data = data; struct impl *this = dbus_cmd_data->this; void *user_data = dbus_cmd_data->user_data; free(data); spa_assert(this->voice_pending == pending); spa_autoptr(DBusMessage) r = steal_reply_and_unref(&this->voice_pending); if (r == NULL) return; if (dbus_message_is_error(r, DBUS_ERROR_UNKNOWN_METHOD)) { spa_log_warn(this->log, "ModemManager D-Bus method not available"); goto finish; } if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) { spa_log_error(this->log, "ModemManager method failed: %s", dbus_message_get_error_name(r)); goto finish; } this->ops->send_cmd_result(true, 0, user_data); return; finish: this->ops->send_cmd_result(false, CMEE_AG_FAILURE, user_data); } bool mm_answer_call(void *modemmanager, void *user_data, enum cmee_error *error) { struct impl *this = modemmanager; struct call *call_object, *call_tmp; spa_autofree struct dbus_cmd_data *data = NULL; spa_autoptr(DBusMessage) m = NULL; call_object = NULL; spa_list_for_each(call_tmp, &this->call_list, link) { if (call_tmp->state == CLCC_INCOMING) { call_object = call_tmp; break; } } if (!call_object) { spa_log_debug(this->log, "No ringing in call"); if (error) *error = CMEE_OPERATION_NOT_ALLOWED; return false; } data = malloc(sizeof(struct dbus_cmd_data)); if (!data) { if (error) *error = CMEE_AG_FAILURE; return false; } data->this = this; data->call = call_object; data->user_data = user_data; m = dbus_message_new_method_call(MM_DBUS_SERVICE, call_object->path, MM_DBUS_INTERFACE_CALL, MM_CALL_METHOD_ACCEPT); if (m == NULL) { if (error) *error = CMEE_AG_FAILURE; return false; } call_object->pending = send_with_reply(this->conn, m, mm_get_call_simple_reply, data); if (!call_object->pending) { spa_log_error(this->log, "dbus call failure"); if (error) *error = CMEE_AG_FAILURE; return false; } return spa_steal_ptr(data), true; } bool mm_hangup_call(void *modemmanager, void *user_data, enum cmee_error *error) { struct impl *this = modemmanager; struct call *call_object, *call_tmp; spa_autofree struct dbus_cmd_data *data= NULL; spa_autoptr(DBusMessage) m = NULL; call_object = NULL; spa_list_for_each(call_tmp, &this->call_list, link) { if (call_tmp->state == CLCC_ACTIVE) { call_object = call_tmp; break; } } if (!call_object) { spa_list_for_each(call_tmp, &this->call_list, link) { if (call_tmp->state == CLCC_DIALING || call_tmp->state == CLCC_ALERTING || call_tmp->state == CLCC_INCOMING) { call_object = call_tmp; break; } } } if (!call_object) { spa_log_debug(this->log, "No call to reject or hang up"); if (error) *error = CMEE_OPERATION_NOT_ALLOWED; return false; } data = malloc(sizeof(struct dbus_cmd_data)); if (!data) { if (error) *error = CMEE_AG_FAILURE; return false; } data->this = this; data->call = call_object; data->user_data = user_data; m = dbus_message_new_method_call(MM_DBUS_SERVICE, call_object->path, MM_DBUS_INTERFACE_CALL, MM_CALL_METHOD_HANGUP); if (m == NULL) { if (error) *error = CMEE_AG_FAILURE; return false; } call_object->pending = send_with_reply(this->conn, m, mm_get_call_simple_reply, data); if (!call_object->pending) { spa_log_error(this->log, "dbus call failure"); if (error) *error = CMEE_AG_FAILURE; return false; } return spa_steal_ptr(data), true; } static void append_basic_variant_dict_entry(DBusMessageIter *dict, const char* key, int variant_type_int, const char* variant_type_str, void* variant) { DBusMessageIter dict_entry_it, variant_it; dbus_message_iter_open_container(dict, DBUS_TYPE_DICT_ENTRY, NULL, &dict_entry_it); dbus_message_iter_append_basic(&dict_entry_it, DBUS_TYPE_STRING, &key); dbus_message_iter_open_container(&dict_entry_it, DBUS_TYPE_VARIANT, variant_type_str, &variant_it); dbus_message_iter_append_basic(&variant_it, variant_type_int, variant); dbus_message_iter_close_container(&dict_entry_it, &variant_it); dbus_message_iter_close_container(dict, &dict_entry_it); } static inline bool is_valid_dial_string_char(char c) { return ('0' <= c && c <= '9') || ('A' <= c && c <= 'C') || c == '*' || c == '#' || c == '+'; } bool mm_do_call(void *modemmanager, const char* number, void *user_data, enum cmee_error *error) { struct impl *this = modemmanager; spa_autofree struct dbus_cmd_data *data = NULL; spa_autoptr(DBusMessage) m = NULL; DBusMessageIter iter, dict; for (size_t i = 0; number[i]; i++) { if (!is_valid_dial_string_char(number[i])) { spa_log_warn(this->log, "Call creation canceled, invalid character found in dial string: %c", number[i]); if (error) *error = CMEE_INVALID_CHARACTERS_DIAL_STRING; return false; } } data = malloc(sizeof(struct dbus_cmd_data)); if (!data) { if (error) *error = CMEE_AG_FAILURE; return false; } data->this = this; data->user_data = user_data; m = dbus_message_new_method_call(MM_DBUS_SERVICE, this->modem.path, MM_DBUS_INTERFACE_MODEM_VOICE, MM_MODEM_VOICE_METHOD_CREATECALL); if (m == NULL) { if (error) *error = CMEE_AG_FAILURE; return false; } dbus_message_iter_init_append(m, &iter); dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &dict); append_basic_variant_dict_entry(&dict, "number", DBUS_TYPE_STRING, "s", &number); dbus_message_iter_close_container(&iter, &dict); this->voice_pending = send_with_reply(this->conn, m, mm_get_call_create_reply, data); if (!this->voice_pending) { spa_log_error(this->log, "dbus call failure"); if (error) *error = CMEE_AG_FAILURE; return false; } return spa_steal_ptr(data), true; } bool mm_send_dtmf(void *modemmanager, const char *dtmf, void *user_data, enum cmee_error *error) { struct impl *this = modemmanager; struct call *call_object, *call_tmp; spa_autofree struct dbus_cmd_data *data = NULL; spa_autoptr(DBusMessage) m = NULL; call_object = NULL; spa_list_for_each(call_tmp, &this->call_list, link) { if (call_tmp->state == CLCC_ACTIVE) { call_object = call_tmp; break; } } if (!call_object) { spa_log_debug(this->log, "No active call"); if (error) *error = CMEE_OPERATION_NOT_ALLOWED; return false; } /* Allowed dtmf characters: 0-9, *, #, A-D */ if (!((dtmf[0] >= '0' && dtmf[0] <= '9') || (dtmf[0] == '*') || (dtmf[0] == '#') || (dtmf[0] >= 'A' && dtmf[0] <= 'D'))) { spa_log_debug(this->log, "Invalid DTMF character: %s", dtmf); if (error) *error = CMEE_INVALID_CHARACTERS_TEXT_STRING; return false; } data = malloc(sizeof(struct dbus_cmd_data)); if (!data) { if (error) *error = CMEE_AG_FAILURE; return false; } data->this = this; data->call = call_object; data->user_data = user_data; m = dbus_message_new_method_call(MM_DBUS_SERVICE, call_object->path, MM_DBUS_INTERFACE_CALL, MM_CALL_METHOD_SENDDTMF); if (m == NULL) { if (error) *error = CMEE_AG_FAILURE; return false; } dbus_message_append_args(m, DBUS_TYPE_STRING, &dtmf, DBUS_TYPE_INVALID); call_object->pending = send_with_reply(this->conn, m, mm_get_call_simple_reply, data); if (!call_object->pending) { spa_log_error(this->log, "dbus call failure"); if (error) *error = CMEE_AG_FAILURE; return false; } return spa_steal_ptr(data), true; } const char *mm_get_incoming_call_number(void *modemmanager) { struct impl *this = modemmanager; struct call *call_object, *call_tmp; call_object = NULL; spa_list_for_each(call_tmp, &this->call_list, link) { if (call_tmp->state == CLCC_INCOMING) { call_object = call_tmp; break; } } if (!call_object) { spa_log_debug(this->log, "No ringing in call"); return NULL; } return call_object->number; } struct spa_list *mm_get_calls(void *modemmanager) { struct impl *this = modemmanager; return &this->call_list; } void *mm_register(struct spa_log *log, void *dbus_connection, const struct spa_dict *info, const struct mm_ops *ops, void *user_data) { const char *modem_device_str = NULL; bool modem_device_found = false; spa_assert(log); spa_assert(dbus_connection); if (info) { if ((modem_device_str = spa_dict_lookup(info, "bluez5.hfphsp-backend-native-modem")) != NULL) { if (!spa_streq(modem_device_str, "none")) modem_device_found = true; } } if (!modem_device_found) { spa_log_info(log, "No modem allowed, doesn't link to ModemManager"); return NULL; } spa_autofree struct impl *this = calloc(1, sizeof(*this)); if (this == NULL) return NULL; this->log = log; this->conn = dbus_connection; this->ops = ops; this->user_data = user_data; if (modem_device_str && !spa_streq(modem_device_str, "any")) this->allowed_modem_device = strdup(modem_device_str); spa_list_init(&this->call_list); if (add_filters(this) < 0) return NULL; if (!mm_get_managed_objects(this)) return NULL; return spa_steal_ptr(this); } void mm_unregister(void *data) { struct impl *this = data; cancel_and_unref(&this->pending); mm_clean_voice(this); mm_clean_modem3gpp(this); mm_clean_modem(this); if (this->filters_added) { dbus_connection_remove_filter(this->conn, mm_filter_cb, this); this->filters_added = false; } if (this->allowed_modem_device) free(this->allowed_modem_device); free(this); } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/bluez5/modemmanager.h000066400000000000000000000071351511204443500267630ustar00rootroot00000000000000/* Spa Bluez5 ModemManager proxy */ /* SPDX-FileCopyrightText: Copyright © 2022 Collabora */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_BLUEZ5_MODEMMANAGER_H_ #define SPA_BLUEZ5_MODEMMANAGER_H_ #include #include "defs.h" enum cmee_error { CMEE_AG_FAILURE = 0, CMEE_NO_CONNECTION_TO_PHONE = 1, CMEE_OPERATION_NOT_ALLOWED = 3, CMEE_OPERATION_NOT_SUPPORTED = 4, CMEE_INVALID_CHARACTERS_TEXT_STRING = 25, CMEE_INVALID_CHARACTERS_DIAL_STRING = 27, CMEE_NO_NETWORK_SERVICE = 30 }; enum call_setup { CIND_CALLSETUP_NONE = 0, CIND_CALLSETUP_INCOMING, CIND_CALLSETUP_DIALING, CIND_CALLSETUP_ALERTING }; enum call_direction { CALL_OUTGOING, CALL_INCOMING }; enum call_state { CLCC_ACTIVE, CLCC_HELD, CLCC_DIALING, CLCC_ALERTING, CLCC_INCOMING, CLCC_WAITING, CLCC_RESPONSE_AND_HOLD }; struct call { struct spa_list link; unsigned int index; struct impl *this; DBusPendingCall *pending; char *path; char *number; bool call_indicator; enum call_direction direction; enum call_state state; bool multiparty; }; struct mm_ops { void (*send_cmd_result)(bool success, enum cmee_error error, void *user_data); void (*set_modem_service)(bool available, void *user_data); void (*set_modem_signal_strength)(unsigned int strength, void *user_data); void (*set_modem_operator_name)(const char *name, void *user_data); void (*set_modem_own_number)(const char *number, void *user_data); void (*set_modem_roaming)(bool is_roaming, void *user_data); void (*set_call_active)(bool active, void *user_data); void (*set_call_setup)(enum call_setup value, void *user_data); }; #ifdef HAVE_BLUEZ_5_BACKEND_NATIVE_MM void *mm_register(struct spa_log *log, void *dbus_connection, const struct spa_dict *info, const struct mm_ops *ops, void *user_data); void mm_unregister(void *data); bool mm_is_available(void *modemmanager); unsigned int mm_supported_features(void); bool mm_answer_call(void *modemmanager, void *user_data, enum cmee_error *error); bool mm_hangup_call(void *modemmanager, void *user_data, enum cmee_error *error); bool mm_do_call(void *modemmanager, const char* number, void *user_data, enum cmee_error *error); bool mm_send_dtmf(void *modemmanager, const char *dtmf, void *user_data, enum cmee_error *error); const char *mm_get_incoming_call_number(void *modemmanager); struct spa_list *mm_get_calls(void *modemmanager); #else static inline void *mm_register(struct spa_log *log, void *dbus_connection, const struct spa_dict *info, const struct mm_ops *ops, void *user_data) { return NULL; } static inline void mm_unregister(void *data) { } static inline bool mm_is_available(void *modemmanager) { return false; } static inline unsigned int mm_supported_features(void) { return 0; } static inline bool mm_answer_call(void *modemmanager, void *user_data, enum cmee_error *error) { if (error) *error = CMEE_OPERATION_NOT_SUPPORTED; return false; } static inline bool mm_hangup_call(void *modemmanager, void *user_data, enum cmee_error *error) { if (error) *error = CMEE_OPERATION_NOT_SUPPORTED; return false; } static inline bool mm_do_call(void *modemmanager, const char* number, void *user_data, enum cmee_error *error) { if (error) *error = CMEE_OPERATION_NOT_SUPPORTED; return false; } static inline bool mm_send_dtmf(void *modemmanager, const char *dtmf, void *user_data, enum cmee_error *error) { if (error) *error = CMEE_OPERATION_NOT_SUPPORTED; return false; } static inline const char *mm_get_incoming_call_number(void *modemmanager) { return NULL; } static inline struct spa_list *mm_get_calls(void *modemmanager) { return NULL; } #endif #endif pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/bluez5/org.bluez.xml000066400000000000000000000051251511204443500266040ustar00rootroot00000000000000 pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/bluez5/player.c000066400000000000000000000257631511204443500256250ustar00rootroot00000000000000/* Spa Bluez5 AVRCP Player */ /* SPDX-FileCopyrightText: Copyright © 2021 Pauli Virtanen */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #include #include "defs.h" #include "player.h" #define PLAYER_OBJECT_PATH_BASE "/media_player" #define PLAYER_INTERFACE "org.mpris.MediaPlayer2.Player" #define PLAYER_INTROSPECT_XML \ DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \ "" \ " " \ " " \ " " \ " " \ " " \ " " \ " " \ " " \ " " \ " " \ " " \ " " \ " " \ " " \ " " \ " " \ " " \ " " \ " " \ " " \ " " \ " " \ " " \ " " \ " " \ " " \ " " \ " " \ " " \ "" SPA_LOG_TOPIC_DEFINE_STATIC(log_topic, "spa.bluez5.player"); #undef SPA_LOG_TOPIC_DEFAULT #define SPA_LOG_TOPIC_DEFAULT &log_topic #define MAX_PROPERTIES 1 struct impl { struct spa_bt_player this; DBusConnection *conn; char *path; struct spa_log *log; struct spa_dict_item properties_items[MAX_PROPERTIES]; struct spa_dict properties; unsigned int playing_count; }; static size_t instance_counter = 0; static DBusMessage *properties_get(struct impl *impl, DBusMessage *m) { const char *iface, *name; size_t j; if (!dbus_message_get_args(m, NULL, DBUS_TYPE_STRING, &iface, DBUS_TYPE_STRING, &name, DBUS_TYPE_INVALID)) return NULL; if (!spa_streq(iface, PLAYER_INTERFACE)) return dbus_message_new_error(m, DBUS_ERROR_INVALID_ARGS, "No such interface"); for (j = 0; j < impl->properties.n_items; ++j) { const struct spa_dict_item *item = &impl->properties.items[j]; if (spa_streq(item->key, name)) { DBusMessage *r; DBusMessageIter i, v; r = dbus_message_new_method_return(m); if (r == NULL) return NULL; dbus_message_iter_init_append(r, &i); dbus_message_iter_open_container(&i, DBUS_TYPE_VARIANT, "s", &v); dbus_message_iter_append_basic(&v, DBUS_TYPE_STRING, &item->value); dbus_message_iter_close_container(&i, &v); return r; } } return dbus_message_new_error(m, DBUS_ERROR_INVALID_ARGS, "No such property"); } static void append_properties(struct impl *impl, DBusMessageIter *i) { DBusMessageIter d, e, v; size_t j; dbus_message_iter_open_container(i, DBUS_TYPE_ARRAY, DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &d); for (j = 0; j < impl->properties.n_items; ++j) { const struct spa_dict_item *item = &impl->properties.items[j]; spa_log_debug(impl->log, "player %s: %s=%s", impl->path, item->key, item->value); dbus_message_iter_open_container(&d, DBUS_TYPE_DICT_ENTRY, NULL, &e); dbus_message_iter_append_basic(&e, DBUS_TYPE_STRING, &item->key); dbus_message_iter_open_container(&e, DBUS_TYPE_VARIANT, "s", &v); dbus_message_iter_append_basic(&v, DBUS_TYPE_STRING, &item->value); dbus_message_iter_close_container(&e, &v); dbus_message_iter_close_container(&d, &e); } dbus_message_iter_close_container(i, &d); } static DBusMessage *properties_get_all(struct impl *impl, DBusMessage *m) { const char *iface, *name; DBusMessage *r; DBusMessageIter i; if (!dbus_message_get_args(m, NULL, DBUS_TYPE_STRING, &iface, DBUS_TYPE_STRING, &name, DBUS_TYPE_INVALID)) return NULL; if (!spa_streq(iface, PLAYER_INTERFACE)) return dbus_message_new_error(m, DBUS_ERROR_INVALID_ARGS, "No such interface"); r = dbus_message_new_method_return(m); if (r == NULL) return NULL; dbus_message_iter_init_append(r, &i); append_properties(impl, &i); return r; } static DBusMessage *properties_set(struct impl *impl, DBusMessage *m) { return dbus_message_new_error(m, DBUS_ERROR_PROPERTY_READ_ONLY, "Property not writable"); } static DBusMessage *introspect(struct impl *impl, DBusMessage *m) { const char *xml = PLAYER_INTROSPECT_XML; spa_autoptr(DBusMessage) r = NULL; if ((r = dbus_message_new_method_return(m)) == NULL) return NULL; if (!dbus_message_append_args(r, DBUS_TYPE_STRING, &xml, DBUS_TYPE_INVALID)) return NULL; return spa_steal_ptr(r); } static DBusHandlerResult player_handler(DBusConnection *c, DBusMessage *m, void *userdata) { struct impl *impl = userdata; spa_autoptr(DBusMessage) r = NULL; if (dbus_message_is_method_call(m, DBUS_INTERFACE_INTROSPECTABLE, "Introspect")) { r = introspect(impl, m); } else if (dbus_message_is_method_call(m, DBUS_INTERFACE_PROPERTIES, "Get")) { r = properties_get(impl, m); } else if (dbus_message_is_method_call(m, DBUS_INTERFACE_PROPERTIES, "GetAll")) { r = properties_get_all(impl, m); } else if (dbus_message_is_method_call(m, DBUS_INTERFACE_PROPERTIES, "Set")) { r = properties_set(impl, m); } else { return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } if (r == NULL) return DBUS_HANDLER_RESULT_NEED_MEMORY; if (!dbus_connection_send(impl->conn, r, NULL)) return DBUS_HANDLER_RESULT_NEED_MEMORY; return DBUS_HANDLER_RESULT_HANDLED; } static int send_update_signal(struct impl *impl) { spa_autoptr(DBusMessage) m = NULL; const char *iface = PLAYER_INTERFACE; DBusMessageIter i, a; m = dbus_message_new_signal(impl->path, DBUS_INTERFACE_PROPERTIES, "PropertiesChanged"); if (m == NULL) return -ENOMEM; dbus_message_iter_init_append(m, &i); dbus_message_iter_append_basic(&i, DBUS_TYPE_STRING, &iface); append_properties(impl, &i); dbus_message_iter_open_container(&i, DBUS_TYPE_ARRAY, DBUS_TYPE_STRING_AS_STRING, &a); dbus_message_iter_close_container(&i, &a); if (!dbus_connection_send(impl->conn, m, NULL)) return -EIO; return 0; } static void update_properties(struct impl *impl, bool send_signal) { int nitems = 0; switch (impl->this.state) { case SPA_BT_PLAYER_PLAYING: impl->properties_items[nitems++] = SPA_DICT_ITEM_INIT("PlaybackStatus", "Playing"); break; case SPA_BT_PLAYER_STOPPED: impl->properties_items[nitems++] = SPA_DICT_ITEM_INIT("PlaybackStatus", "Stopped"); break; } impl->properties = SPA_DICT_INIT(impl->properties_items, nitems); if (!send_signal) return; send_update_signal(impl); } struct spa_bt_player *spa_bt_player_new(void *dbus_connection, struct spa_log *log) { static const DBusObjectPathVTable vtable = { .message_function = player_handler, }; struct impl *impl; spa_log_topic_init(log, &log_topic); impl = calloc(1, sizeof(struct impl)); if (impl == NULL) return NULL; impl->this.state = SPA_BT_PLAYER_STOPPED; impl->conn = dbus_connection; impl->log = log; impl->path = spa_aprintf("%s%zu", PLAYER_OBJECT_PATH_BASE, instance_counter++); if (impl->path == NULL) { free(impl); return NULL; } dbus_connection_ref(impl->conn); update_properties(impl, false); if (!dbus_connection_register_object_path(impl->conn, impl->path, &vtable, impl)) { spa_bt_player_destroy(&impl->this); errno = EIO; return NULL; } return &impl->this; } void spa_bt_player_destroy(struct spa_bt_player *player) { struct impl *impl = SPA_CONTAINER_OF(player, struct impl, this); /* * We unregister only the object path, but don't unregister it from * BlueZ, to avoid hanging on BlueZ DBus activation. The assumption is * that the DBus connection is terminated immediately after. */ dbus_connection_unregister_object_path(impl->conn, impl->path); dbus_connection_unref(impl->conn); free(impl->path); free(impl); } int spa_bt_player_set_state(struct spa_bt_player *player, enum spa_bt_player_state state) { struct impl *impl = SPA_CONTAINER_OF(player, struct impl, this); switch (state) { case SPA_BT_PLAYER_PLAYING: if (impl->playing_count++ > 0) return 0; break; case SPA_BT_PLAYER_STOPPED: if (impl->playing_count == 0) return -EINVAL; if (--impl->playing_count > 0) return 0; break; default: return -EINVAL; } impl->this.state = state; update_properties(impl, true); return 0; } int spa_bt_player_register(struct spa_bt_player *player, const char *adapter_path) { struct impl *impl = SPA_CONTAINER_OF(player, struct impl, this); spa_auto(DBusError) err = DBUS_ERROR_INIT; DBusMessageIter i; spa_autoptr(DBusMessage) m = NULL, r = NULL; spa_log_debug(impl->log, "RegisterPlayer() for dummy AVRCP player %s for %s", impl->path, adapter_path); m = dbus_message_new_method_call(BLUEZ_SERVICE, adapter_path, BLUEZ_MEDIA_INTERFACE, "RegisterPlayer"); if (m == NULL) return -EIO; dbus_message_iter_init_append(m, &i); dbus_message_iter_append_basic(&i, DBUS_TYPE_OBJECT_PATH, &impl->path); append_properties(impl, &i); r = dbus_connection_send_with_reply_and_block(impl->conn, m, -1, &err); if (r == NULL) { spa_log_error(impl->log, "RegisterPlayer() failed (%s)", err.message); return -EIO; } if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) { spa_log_error(impl->log, "RegisterPlayer() failed"); return -EIO; } return 0; } int spa_bt_player_unregister(struct spa_bt_player *player, const char *adapter_path) { struct impl *impl = SPA_CONTAINER_OF(player, struct impl, this); spa_auto(DBusError) err = DBUS_ERROR_INIT; DBusMessageIter i; spa_autoptr(DBusMessage) m = NULL, r = NULL; spa_log_debug(impl->log, "UnregisterPlayer() for dummy AVRCP player %s for %s", impl->path, adapter_path); m = dbus_message_new_method_call(BLUEZ_SERVICE, adapter_path, BLUEZ_MEDIA_INTERFACE, "UnregisterPlayer"); if (m == NULL) return -EIO; dbus_message_iter_init_append(m, &i); dbus_message_iter_append_basic(&i, DBUS_TYPE_OBJECT_PATH, &impl->path); r = dbus_connection_send_with_reply_and_block(impl->conn, m, -1, &err); if (r == NULL) { spa_log_error(impl->log, "UnregisterPlayer() failed (%s)", err.message); return -EIO; } if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) { spa_log_error(impl->log, "UnregisterPlayer() failed"); return -EIO; } return 0; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/bluez5/player.h000066400000000000000000000016711511204443500256220ustar00rootroot00000000000000/* Spa Bluez5 AVRCP Player */ /* SPDX-FileCopyrightText: Copyright © 2021 Pauli Virtanen */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_BLUEZ5_PLAYER_H_ #define SPA_BLUEZ5_PLAYER_H_ enum spa_bt_player_state { SPA_BT_PLAYER_STOPPED, SPA_BT_PLAYER_PLAYING, }; /** * Dummy AVRCP player. * * Some headsets require an AVRCP player to be present, before their * AVRCP volume synchronization works. To work around this, we * register a dummy player that does nothing. */ struct spa_bt_player { enum spa_bt_player_state state; }; struct spa_bt_player *spa_bt_player_new(void *dbus_connection, struct spa_log *log); void spa_bt_player_destroy(struct spa_bt_player *player); int spa_bt_player_set_state(struct spa_bt_player *player, enum spa_bt_player_state state); int spa_bt_player_register(struct spa_bt_player *player, const char *adapter_path); int spa_bt_player_unregister(struct spa_bt_player *player, const char *adapter_path); #endif pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/bluez5/plc.h000066400000000000000000000012261511204443500251000ustar00rootroot00000000000000/* Spa PLC */ /* SPDX-FileCopyrightText: Copyright © 2025 Pauli Virtanen */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_BLUEZ5_PLC_H #define SPA_BLUEZ5_PLC_H #include #include #include #ifdef HAVE_SPANDSP #include #else typedef struct { char dummy; } plc_state_t; static inline int plc_rx(plc_state_t *s, int16_t *data, int len) { return -ENOTSUP; } static inline int plc_fillin(plc_state_t *s, int16_t *data, int len) { return -ENOTSUP; } static inline plc_state_t *plc_init(plc_state_t *s) { static plc_state_t state; return &state; } static inline int plc_free(plc_state_t *s) { return 0; } #endif #endif pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/bluez5/plugin.c000066400000000000000000000033331511204443500256140ustar00rootroot00000000000000/* Spa Volume plugin */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include #include extern const struct spa_handle_factory spa_bluez5_dbus_factory; extern const struct spa_handle_factory spa_bluez5_device_factory; extern const struct spa_handle_factory spa_media_sink_factory; extern const struct spa_handle_factory spa_media_source_factory; extern const struct spa_handle_factory spa_sco_sink_factory; extern const struct spa_handle_factory spa_sco_source_factory; extern const struct spa_handle_factory spa_a2dp_sink_factory; extern const struct spa_handle_factory spa_a2dp_source_factory; extern const struct spa_handle_factory spa_bluez5_midi_enum_factory; extern const struct spa_handle_factory spa_bluez5_midi_node_factory; SPA_LOG_TOPIC_ENUM_DEFINE_REGISTERED; SPA_EXPORT int spa_handle_factory_enum(const struct spa_handle_factory **factory, uint32_t *index) { spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(index != NULL, -EINVAL); switch (*index) { case 0: *factory = &spa_bluez5_dbus_factory; break; case 1: *factory = &spa_bluez5_device_factory; break; case 2: *factory = &spa_media_sink_factory; break; case 3: *factory = &spa_media_source_factory; break; case 4: *factory = &spa_sco_sink_factory; break; case 5: *factory = &spa_sco_source_factory; break; case 6: *factory = &spa_a2dp_sink_factory; break; case 7: *factory = &spa_a2dp_source_factory; break; case 8: *factory = &spa_bluez5_midi_enum_factory; break; case 9: *factory = &spa_bluez5_midi_node_factory; break; default: return 0; } (*index)++; return 1; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/bluez5/quirks.c000066400000000000000000000262661511204443500256460ustar00rootroot00000000000000/* Device/adapter/kernel quirk table */ /* SPDX-FileCopyrightText: Copyright © 2021 Pauli Virtanen */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "defs.h" SPA_LOG_TOPIC_DEFINE_STATIC(log_topic, "spa.bluez5.quirks"); #undef SPA_LOG_TOPIC_DEFAULT #define SPA_LOG_TOPIC_DEFAULT &log_topic struct spa_bt_quirks { struct spa_log *log; int force_msbc; int force_hw_volume; int force_sbc_xq; int force_faststream; int force_a2dp_duplex; char *device_rules; char *adapter_rules; char *kernel_rules; }; static enum spa_bt_feature parse_feature(const char *str) { static const struct { const char *key; enum spa_bt_feature value; } feature_keys[] = { { "msbc", SPA_BT_FEATURE_MSBC }, { "msbc-alt1", SPA_BT_FEATURE_MSBC_ALT1 }, { "msbc-alt1-rtl", SPA_BT_FEATURE_MSBC_ALT1_RTL }, { "hw-volume", SPA_BT_FEATURE_HW_VOLUME }, { "hw-volume-mic", SPA_BT_FEATURE_HW_VOLUME_MIC }, { "sbc-xq", SPA_BT_FEATURE_SBC_XQ }, { "faststream", SPA_BT_FEATURE_FASTSTREAM }, { "a2dp-duplex", SPA_BT_FEATURE_A2DP_DUPLEX }, }; SPA_FOR_EACH_ELEMENT_VAR(feature_keys, f) { if (spa_streq(str, f->key)) return f->value; } return 0; } static int do_match(const char *rules, struct spa_dict *dict, uint32_t *no_features) { struct spa_json rules_json = SPA_JSON_INIT(rules, strlen(rules)); struct spa_json rules_arr, it[2]; if (spa_json_enter_array(&rules_json, &rules_arr) <= 0) return 1; while (spa_json_enter_object(&rules_arr, &it[0]) > 0) { char key[256]; int match = true, len; uint32_t no_features_cur = 0; const char *value; while ((len = spa_json_object_next(&it[0], key, sizeof(key), &value)) > 0) { char val[4096]; const char *str; bool success = false; if (spa_streq(key, "no-features")) { if (spa_json_is_array(value, len) > 0) { spa_json_enter(&it[0], &it[1]); while (spa_json_get_string(&it[1], val, sizeof(val)) > 0) no_features_cur |= parse_feature(val); } continue; } if (spa_json_is_null(value, len)) { value = NULL; } else { if (spa_json_parse_stringn(value, len, val, sizeof(val)) < 0) continue; value = val; } str = spa_dict_lookup(dict, key); if (value == NULL) { success = str == NULL; } else if (str != NULL) { if (value[0] == '~') { regex_t r; if (regcomp(&r, value+1, REG_EXTENDED | REG_NOSUB) == 0) { if (regexec(&r, str, 0, NULL, 0) == 0) success = true; regfree(&r); } } else if (spa_streq(str, value)) { success = true; } } if (!success) { match = false; break; } } if (match) { *no_features = no_features_cur; return 0; } } return 0; } static int parse_force_flag(const struct spa_dict *info, const char *key) { const char *str; str = spa_dict_lookup(info, key); if (str == NULL) return -1; else return (strcmp(str, "true") == 0 || atoi(str)) ? 1 : 0; } static void load_quirks(struct spa_bt_quirks *this, const char *str, size_t len) { struct spa_json data = SPA_JSON_INIT(str, len); struct spa_json rules; char key[1024]; struct spa_error_location loc; int sz; const char *value; if (spa_json_enter_object(&data, &rules) <= 0) spa_json_init(&rules, str, len); while ((sz = spa_json_object_next(&rules, key, sizeof(key), &value)) > 0) { if (!spa_json_is_container(value, sz)) continue; sz = spa_json_container_len(&rules, value, sz); if (spa_streq(key, "bluez5.features.kernel") && !this->kernel_rules) this->kernel_rules = strndup(value, sz); else if (spa_streq(key, "bluez5.features.adapter") && !this->adapter_rules) this->adapter_rules = strndup(value, sz); else if (spa_streq(key, "bluez5.features.device") && !this->device_rules) this->device_rules = strndup(value, sz); } if (spa_json_get_error(&rules, str, &loc)) spa_debug_log_error_location(this->log, SPA_LOG_LEVEL_ERROR, &loc, "spa.bluez5 quirks syntax error: %s", loc.reason); } static int load_conf(struct spa_bt_quirks *this, const char *path) { char *data; struct stat sbuf; spa_autoclose int fd = -1; spa_log_debug(this->log, "loading %s", path); if ((fd = open(path, O_CLOEXEC | O_RDONLY)) < 0) return -errno; if (fstat(fd, &sbuf) < 0) return -errno; if ((data = mmap(NULL, sbuf.st_size, PROT_READ, MAP_PRIVATE, fd, 0)) == MAP_FAILED) return -errno; load_quirks(this, data, sbuf.st_size); munmap(data, sbuf.st_size); return 0; } struct spa_bt_quirks *spa_bt_quirks_create(const struct spa_dict *info, struct spa_log *log) { struct spa_bt_quirks *this; const char *str; if (!info) { errno = EINVAL; return NULL; } this = calloc(1, sizeof(struct spa_bt_quirks)); if (this == NULL) return NULL; this->log = log; spa_log_topic_init(this->log, &log_topic); this->force_sbc_xq = parse_force_flag(info, "bluez5.enable-sbc-xq"); this->force_msbc = parse_force_flag(info, "bluez5.enable-msbc"); this->force_hw_volume = parse_force_flag(info, "bluez5.enable-hw-volume"); this->force_faststream = parse_force_flag(info, "bluez5.enable-faststream"); this->force_a2dp_duplex = parse_force_flag(info, "bluez5.enable-a2dp-duplex"); if ((str = spa_dict_lookup(info, "bluez5.hardware-database")) != NULL) { spa_log_debug(this->log, "loading session manager provided data"); load_quirks(this, str, strlen(str)); } else { char path[PATH_MAX]; const char *dir = getenv("SPA_DATA_DIR"); int res; if (dir == NULL) dir = SPADATADIR; if (spa_scnprintf(path, sizeof(path), "%s/bluez5/bluez-hardware.conf", dir) >= 0) if ((res = load_conf(this, path)) < 0) spa_log_warn(this->log, "failed to load '%s': %s", path, spa_strerror(res)); } if (!(this->kernel_rules && this->adapter_rules && this->device_rules)) spa_log_warn(this->log, "failed to load bluez-hardware.conf"); return this; } void spa_bt_quirks_destroy(struct spa_bt_quirks *this) { free(this->kernel_rules); free(this->adapter_rules); free(this->device_rules); free(this); } static void log_props(struct spa_log *log, const struct spa_dict *dict) { const struct spa_dict_item *item; spa_dict_for_each(item, dict) spa_log_debug(log, "quirk property %s=%s", item->key, item->value); } static void strtolower(char *src, char *dst, int maxsize) { while (maxsize > 1 && *src != '\0') { *dst = (*src >= 'A' && *src <= 'Z') ? ('a' + (*src - 'A')) : *src; ++src; ++dst; --maxsize; } if (maxsize > 0) *dst = '\0'; } static int get_features(const struct spa_bt_quirks *this, const struct spa_bt_adapter *adapter, const struct spa_bt_device *device, uint32_t *features, bool debug) { struct spa_dict props; struct spa_dict_item items[5]; int res; *features = ~(uint32_t)0; /* Kernel */ if (this->kernel_rules) { uint32_t no_features = 0; int nitems = 0; struct utsname name; if ((res = uname(&name)) < 0) return res; items[nitems++] = SPA_DICT_ITEM_INIT("sysname", name.sysname); items[nitems++] = SPA_DICT_ITEM_INIT("release", name.release); items[nitems++] = SPA_DICT_ITEM_INIT("version", name.version); props = SPA_DICT_INIT(items, nitems); if (debug) log_props(this->log, &props); do_match(this->kernel_rules, &props, &no_features); if (debug) spa_log_debug(this->log, "kernel quirks:%08x", no_features); *features &= ~no_features; } /* Adapter */ if (this->adapter_rules && adapter) { uint32_t no_features = 0; int nitems = 0; char vendor_id[64], product_id[64], address[64]; if (spa_bt_format_vendor_product_id( adapter->source_id, adapter->vendor_id, adapter->product_id, vendor_id, sizeof(vendor_id), product_id, sizeof(product_id)) == 0) { items[nitems++] = SPA_DICT_ITEM_INIT("vendor-id", vendor_id); items[nitems++] = SPA_DICT_ITEM_INIT("product-id", product_id); } items[nitems++] = SPA_DICT_ITEM_INIT("bus-type", (adapter->bus_type == BUS_TYPE_USB) ? "usb" : "other"); if (adapter->address) { strtolower(adapter->address, address, sizeof(address)); items[nitems++] = SPA_DICT_ITEM_INIT("address", address); } props = SPA_DICT_INIT(items, nitems); if (debug) log_props(this->log, &props); do_match(this->adapter_rules, &props, &no_features); if (debug) spa_log_debug(this->log, "adapter quirks:%08x", no_features); *features &= ~no_features; } /* Device */ if (this->device_rules && device) { uint32_t no_features = 0; int nitems = 0; char vendor_id[64], product_id[64], version_id[64], address[64]; if (spa_bt_format_vendor_product_id( device->source_id, device->vendor_id, device->product_id, vendor_id, sizeof(vendor_id), product_id, sizeof(product_id)) == 0) { snprintf(version_id, sizeof(version_id), "%04x", (unsigned int)device->version_id); items[nitems++] = SPA_DICT_ITEM_INIT("vendor-id", vendor_id); items[nitems++] = SPA_DICT_ITEM_INIT("product-id", product_id); items[nitems++] = SPA_DICT_ITEM_INIT("version-id", version_id); } if (device->name) items[nitems++] = SPA_DICT_ITEM_INIT("name", device->name); if (device->address) { strtolower(device->address, address, sizeof(address)); items[nitems++] = SPA_DICT_ITEM_INIT("address", address); } props = SPA_DICT_INIT(items, nitems); if (debug) log_props(this->log, &props); do_match(this->device_rules, &props, &no_features); if (debug) spa_log_debug(this->log, "device quirks:%08x", no_features); *features &= ~no_features; } /* Force flags */ if (this->force_msbc != -1) { SPA_FLAG_UPDATE(*features, SPA_BT_FEATURE_MSBC, this->force_msbc); SPA_FLAG_UPDATE(*features, SPA_BT_FEATURE_MSBC_ALT1, this->force_msbc); SPA_FLAG_UPDATE(*features, SPA_BT_FEATURE_MSBC_ALT1_RTL, this->force_msbc); } if (this->force_hw_volume != -1) SPA_FLAG_UPDATE(*features, SPA_BT_FEATURE_HW_VOLUME, this->force_hw_volume); if (this->force_sbc_xq != -1) SPA_FLAG_UPDATE(*features, SPA_BT_FEATURE_SBC_XQ, this->force_sbc_xq); if (this->force_faststream != -1) SPA_FLAG_UPDATE(*features, SPA_BT_FEATURE_FASTSTREAM, this->force_faststream); if (this->force_a2dp_duplex != -1) SPA_FLAG_UPDATE(*features, SPA_BT_FEATURE_A2DP_DUPLEX, this->force_a2dp_duplex); return 0; } int spa_bt_quirks_get_features(const struct spa_bt_quirks *this, const struct spa_bt_adapter *adapter, const struct spa_bt_device *device, uint32_t *features) { return get_features(this, adapter, device, features, false); } void spa_bt_quirks_log_features(const struct spa_bt_quirks *this, const struct spa_bt_adapter *adapter, const struct spa_bt_device *device) { uint32_t features = 0; get_features(this, adapter, device, &features, true); spa_log_debug(this->log, "features:%08x", features); } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/bluez5/rate-control.h000066400000000000000000000123671511204443500267430ustar00rootroot00000000000000/* Spa Bluez5 rate control */ /* SPDX-FileCopyrightText: Copyright © 2022 Pauli Virtanen */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_BLUEZ5_RATE_CONTROL_H #define SPA_BLUEZ5_RATE_CONTROL_H #include /** Windowed min/max */ struct spa_bt_ptp { union { int32_t min; int32_t mins[4]; }; union { int32_t max; int32_t maxs[4]; }; uint32_t pos; uint32_t left; uint32_t period; }; static inline void spa_bt_ptp_init(struct spa_bt_ptp *p, int32_t period, uint32_t min_duration) { size_t i; spa_zero(*p); for (i = 0; i < SPA_N_ELEMENTS(p->mins); ++i) { p->mins[i] = INT32_MAX; p->maxs[i] = INT32_MIN; } p->left = min_duration; p->period = period; } static inline void spa_bt_ptp_update(struct spa_bt_ptp *p, int32_t value, uint32_t duration) { const size_t n = SPA_N_ELEMENTS(p->mins); size_t i; for (i = 0; i < n; ++i) { p->mins[i] = SPA_MIN(p->mins[i], value); p->maxs[i] = SPA_MAX(p->maxs[i], value); } p->pos += duration; if (p->pos >= p->period / (n - 1)) { p->pos = 0; for (i = 1; i < SPA_N_ELEMENTS(p->mins); ++i) { p->mins[i-1] = p->mins[i]; p->maxs[i-1] = p->maxs[i]; } p->mins[n-1] = INT32_MAX; p->maxs[n-1] = INT32_MIN; } if (p->left < duration) p->left = 0; else p->left -= duration; } static inline bool spa_bt_ptp_valid(struct spa_bt_ptp *p) { return p->left == 0; } /** * Rate controller. * * It's here in a form, where it operates on the running average * so it's compatible with the level spike determination, and * clamping the rate to a range is easy. The impulse response * is similar to spa_dll, and step response does not have sign changes. * * The controller iterates as * * avg(j+1) = (1 - beta) avg(j) + beta level(j) * corr(j+1) = corr(j) + a [avg(j+1) - avg(j)] / duration * + b [avg(j) - target] / duration * * with beta = duration/avg_period < 0.5 is the moving average parameter, * and a = beta/3 + ..., b = beta^2/27 + .... * * This choice results to c(j) being low-pass filtered, and buffer level(j) * converging towards target with stable damped evolution with eigenvalues * real and close to each other around (1 - beta)^(1/3). * * Derivation: * * The deviation from the buffer level target evolves as * * delta(j) = level(j) - target * delta(j+1) = delta(j) + r(j) - c(j+1) * * where r is samples received in one duration, and c corrected rate * (samples per duration). * * The rate correction is in general determined by linear filter f * * c(j+1) = c(j) + \sum_{k=0}^\infty delta(j - k) f(k) * * If \sum_k f(k) is not zero, the only fixed point is c=r, delta=0, * so this structure (if the filter is stable) rate matches and * drives buffer level to target. * * The z-transform then is * * delta(z) = G(z) r(z) * c(z) = F(z) delta(z) * G(z) = (z - 1) / [(z - 1)^2 + z f(z)] * F(z) = f(z) / (z - 1) * * We now want: poles of G(z) must be in |z|<1 for stability, F(z) * should damp high frequencies, and f(z) is causal. * * To satisfy the conditions, take * * (z - 1)^2 + z f(z) = p(z) / q(z) * * where p(z) is polynomial with leading term z^n with wanted root * structure, and q(z) is any polynomial with leading term z^{n-2}. * This guarantees f(z) is causal, and G(z) = (z-1) q(z) / p(z). * We can choose p(z) and q(z) to improve low-pass properties of F(z). * * Simplest choice is p(z)=(z-x)^2 and q(z)=1, but that gives flat * high frequency response in F(z). Better choice is p(z) = (z-u)*(z-v)*(z-w) * and q(z) = z - r. To make F(z) better lowpass, one can cancel * a resulting 1/z pole in F(z) by setting r=u*v*w. Then, * * G(z) = (z - u*v*w)*(z - 1) / [(z - u)*(z - v)*(z - w)] * F(z) = (a z + b - a) / (z - 1) * H(z) * H(z) = beta / (z - 1 + beta) * beta = 1 - u*v*w * a = [(1-u) + (1-v) + (1-w) - beta] / beta * b = (1-u)*(1-v)*(1-w) / beta * * which corresponds to iteration for c(j): * * avg(j+1) = (1 - beta) avg(j) + beta delta(j) * c(j+1) = c(j) + a [avg(j+1) - avg(j)] + b avg(j) * * So the controller operates on the running average, * which gives the low-pass property for c(j). * * The simplest filter is obtained by putting the poles at * u=v=w=(1-beta)**(1/3). Since beta << 1, computing the root * can be avoided by expanding in series. * * Overshoot in impulse response could be reduced by moving one of the * poles closer to z=1, but this increases the step response time. */ struct spa_bt_rate_control { double avg; double corr; }; static inline void spa_bt_rate_control_init(struct spa_bt_rate_control *this, double level) { this->avg = level; this->corr = 1.0; } static inline double spa_bt_rate_control_update(struct spa_bt_rate_control *this, double level, double target, double duration, double period, double rate_diff_max) { /* * u = (1 - beta)^(1/3) * x = a / beta * y = b / beta * a = (2 + u) * (1 - u)^2 / beta * b = (1 - u)^3 / beta * beta -> 0 */ const double beta = SPA_CLAMP(duration / period, 0, 0.5); const double x = 1.0/3; const double y = beta/27; double avg; avg = beta * level + (1 - beta) * this->avg; this->corr += x * (avg - this->avg) / period + y * (this->avg - target) / period; this->avg = avg; this->corr = SPA_CLAMP(this->corr, 1 - rate_diff_max, 1 + rate_diff_max); return this->corr; } #endif pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/bluez5/rtp.h000066400000000000000000000033141511204443500251270ustar00rootroot00000000000000/* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2004-2010 Marcel Holtmann * * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, see . */ #if __BYTE_ORDER == __LITTLE_ENDIAN struct rtp_header { unsigned cc:4; unsigned x:1; unsigned p:1; unsigned v:2; unsigned pt:7; unsigned m:1; uint16_t sequence_number; uint32_t timestamp; uint32_t ssrc; uint32_t csrc[0]; } __attribute__ ((packed)); struct rtp_payload { unsigned frame_count:4; unsigned rfa0:1; unsigned is_last_fragment:1; unsigned is_first_fragment:1; unsigned is_fragmented:1; } __attribute__ ((packed)); #elif __BYTE_ORDER == __BIG_ENDIAN struct rtp_header { unsigned v:2; unsigned p:1; unsigned x:1; unsigned cc:4; unsigned m:1; unsigned pt:7; uint16_t sequence_number; uint32_t timestamp; uint32_t ssrc; uint32_t csrc[0]; } __attribute__ ((packed)); struct rtp_payload { unsigned is_fragmented:1; unsigned is_first_fragment:1; unsigned is_last_fragment:1; unsigned rfa0:1; unsigned frame_count:4; } __attribute__ ((packed)); #else #error "Unknown byte order" #endif pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/bluez5/sco-io.c000066400000000000000000000220561511204443500255120ustar00rootroot00000000000000/* Spa SCO I/O */ /* SPDX-FileCopyrightText: Copyright © 2019 Collabora Ltd. */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "defs.h" #include "media-codecs.h" #include "hfp-codec-caps.h" SPA_LOG_TOPIC_DEFINE_STATIC(log_topic, "spa.bluez5.sco-io"); #undef SPA_LOG_TOPIC_DEFAULT #define SPA_LOG_TOPIC_DEFAULT &log_topic #include "decode-buffer.h" /* We'll use the read rx data size to find the correct packet size for writing, * since kernel might not report it as the socket MTU, see * https://lore.kernel.org/linux-bluetooth/20201210003528.3pmaxvubiwegxmhl@pali/T/ * * We continue reading also when there's no source connected, to keep socket * flushed. * * XXX: when the kernel/backends start giving the right values, the heuristic * XXX: can be removed */ #define MAX_MTU 1024 #define KEEPALIVE_NSEC (500 * SPA_NSEC_PER_MSEC) struct spa_bt_sco_io { uint8_t read_buffer[MAX_MTU]; size_t read_size; uint8_t write_buffer[MAX_MTU]; size_t write_size; int fd; uint16_t read_mtu; uint16_t write_mtu; struct spa_log *log; struct spa_loop *data_loop; struct spa_system *data_system; struct spa_source source; struct spa_bt_recvmsg_data recv; int (*source_cb)(void *userdata, uint8_t *data, int size, uint64_t rx_time); void *source_userdata; const struct media_codec *codec; void *codec_data; uint64_t last_tx_time; uint64_t last_rx_time; uint16_t keepalive_seqnum; bool keepalive; }; static void keepalive_send(struct spa_bt_sco_io *io) { static const uint8_t zeros[2048]; uint8_t buf[MAX_MTU]; int res, need_flush = 0; size_t size; if (io->codec->id == SPA_BLUETOOTH_AUDIO_CODEC_CVSD) { /* Doesn't have fixed block size; TX same amount as RX instead */ size = SPA_MIN(sizeof(buf), io->read_size); memset(buf, 0, size); goto send; } size = res = io->codec->start_encode(io->codec_data, buf, sizeof(buf), ++io->keepalive_seqnum, io->last_rx_time / SPA_NSEC_PER_USEC); if (res < 0) return; do { size_t encoded; res = io->codec->encode(io->codec_data, zeros, sizeof(zeros), SPA_PTROFF(buf, size, void), sizeof(buf) - size, &encoded, &need_flush); if (res < 0) return; size += encoded; if (size >= sizeof(buf)) return; } while (!need_flush); send: if (!io->keepalive) spa_bt_sco_io_write_start(io); io->keepalive = false; spa_bt_sco_io_write(io, buf, size); io->keepalive = true; } static void sco_io_on_ready(struct spa_source *source) { struct spa_bt_sco_io *io = source->data; if (SPA_FLAG_IS_SET(source->rmask, SPA_IO_IN)) { int res; int dummy; uint64_t rx_time = 0; read_again: res = spa_bt_recvmsg(&io->recv, io->read_buffer, SPA_MIN(io->read_mtu, MAX_MTU), &rx_time, &dummy); if (res <= 0) { if (errno == EINTR) { /* retry if interrupted */ goto read_again; } else if (errno == EAGAIN || errno == EWOULDBLOCK) { /* no data: try it next time */ goto read_done; } /* error */ goto stop; } if (res != (int)io->read_size) { spa_log_trace(io->log, "%p: packet size:%d", io, res); /* drop buffer when packet size changes */ io->write_size = 0; } io->read_size = res; io->last_rx_time = rx_time; if (!io->last_tx_time) io->last_tx_time = rx_time; if (io->source_cb) { int res; res = io->source_cb(io->source_userdata, io->read_buffer, io->read_size, rx_time); if (res) { io->source_cb = NULL; } } /* If sink has not supplied packets for some time, for each RX packet send * same amount of silence to keep the connection alive. Some devices (with * LC3-24kHZ) require this and it doesn't hurt for others. */ if (io->last_tx_time + KEEPALIVE_NSEC < io->last_rx_time || io->keepalive) keepalive_send(io); } read_done: if (SPA_FLAG_IS_SET(source->rmask, SPA_IO_ERR) || SPA_FLAG_IS_SET(source->rmask, SPA_IO_HUP)) { goto stop; } return; stop: if (io->source.loop) spa_loop_remove_source(io->data_loop, &io->source); } static int write_packets(struct spa_bt_sco_io *io, const uint8_t **buf, size_t *size, size_t packet_size) { while (*size >= packet_size) { ssize_t written; written = send(io->fd, *buf, packet_size, MSG_DONTWAIT | MSG_NOSIGNAL); if (written < 0) { if (errno == EINTR) continue; return -errno; } *buf += written; *size -= written; } return 0; } /* * Write data to socket in correctly sized blocks. * Returns the number of bytes written or buffered, and <0 on write error. */ int spa_bt_sco_io_write(struct spa_bt_sco_io *io, const uint8_t *buf, size_t size) { const size_t orig_size = size; const uint8_t *pos; size_t packet_size; int res; io->last_tx_time = io->last_rx_time; if (io->read_size == 0) { /* The proper write packet size is not known yet */ return 0; } if (io->keepalive) { /* Transition from keepalive to sink-fed data */ io->write_size = 0; io->keepalive = false; } packet_size = SPA_MIN(SPA_MIN(io->write_mtu, io->read_size), sizeof(io->write_buffer)); if (io->write_size >= packet_size) { /* packet size changed, drop data */ io->write_size = 0; } else if (io->write_size) { /* write fragment */ size_t need = SPA_MIN(packet_size - io->write_size, size); memcpy(io->write_buffer + io->write_size, buf, need); buf += need; size -= need; io->write_size += need; if (io->write_size < packet_size) return orig_size; pos = io->write_buffer; if ((res = write_packets(io, &pos, &io->write_size, packet_size)) < 0) goto fail; if (io->write_size) goto fail; } /* write */ if ((res = write_packets(io, &buf, &size, packet_size)) < 0) goto fail; spa_assert(size < packet_size); /* store fragment */ io->write_size = size; if (size) memcpy(io->write_buffer, buf, size); return orig_size; fail: io->write_size = 0; return res; } void spa_bt_sco_io_write_start(struct spa_bt_sco_io *io) { /* drop fragment */ io->write_size = 0; } struct spa_bt_sco_io *spa_bt_sco_io_create(struct spa_bt_transport *transport, struct spa_loop *data_loop, struct spa_system *data_system, struct spa_log *log) { spa_autofree struct spa_bt_sco_io *io = NULL; struct spa_audio_info format = { 0 }; spa_log_topic_init(log, &log_topic); io = calloc(1, sizeof(struct spa_bt_sco_io)); if (io == NULL) return io; io->fd = transport->fd; io->read_mtu = transport->read_mtu; io->write_mtu = transport->write_mtu; io->data_loop = data_loop; io->data_system = data_system; io->log = log; if (transport->device->adapter->bus_type == BUS_TYPE_USB) { /* For USB we need to wait for RX to know it. Using wrong size doesn't * work anyway, and may result to errors printed to dmesg if too big. */ io->read_size = 0; } else { /* Set some sensible initial packet size */ switch (transport->media_codec->id) { case SPA_BLUETOOTH_AUDIO_CODEC_CVSD: io->read_size = 48; /* 3ms S16_LE 8000 Hz */ break; default: io->read_size = HFP_H2_PACKET_SIZE; break; } } io->codec = transport->media_codec; if (io->codec->validate_config(io->codec, 0, NULL, 0, &format) < 0) return NULL; io->codec_data = io->codec->init(io->codec, 0, NULL, 0, &format, NULL, transport->write_mtu); if (!io->codec_data) return NULL; spa_log_debug(io->log, "%p: initial packet size:%d", io, (int)io->read_size); spa_bt_recvmsg_init(&io->recv, io->fd, io->data_system, io->log); /* Add the ready callback */ io->source.data = io; io->source.fd = io->fd; io->source.func = sco_io_on_ready; io->source.mask = SPA_IO_IN | SPA_IO_ERR | SPA_IO_HUP; io->source.rmask = 0; spa_loop_add_source(io->data_loop, &io->source); return spa_steal_ptr(io); } static int do_remove_source(struct spa_loop *loop, bool async, uint32_t seq, const void *data, size_t size, void *user_data) { struct spa_bt_sco_io *io = user_data; if (io->source.loop) spa_loop_remove_source(io->data_loop, &io->source); return 0; } void spa_bt_sco_io_destroy(struct spa_bt_sco_io *io) { spa_log_debug(io->log, "%p: destroy", io); spa_loop_locked(io->data_loop, do_remove_source, 0, NULL, 0, io); io->codec->deinit(io->codec_data); free(io); } /* Set source callback. * This function should only be called from the data thread. * Callback is called (in data loop) with data just read from the socket. */ void spa_bt_sco_io_set_source_cb(struct spa_bt_sco_io *io, int (*source_cb)(void *, uint8_t *, int, uint64_t), void *userdata) { io->source_cb = source_cb; io->source_userdata = userdata; io->last_rx_time = 0; io->last_tx_time = 0; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/bluez5/telephony.c000066400000000000000000002016011511204443500263230ustar00rootroot00000000000000/* Spa Bluez5 Telephony D-Bus service */ /* SPDX-FileCopyrightText: Copyright © 2024 Collabora Ltd. */ /* SPDX-License-Identifier: MIT */ #include "telephony.h" #include #include #include #include #include #include #include #define PW_TELEPHONY_SERVICE "org.pipewire.Telephony" #define PW_TELEPHONY_OBJECT_PATH "/org/pipewire/Telephony" #define PW_TELEPHONY_AG_IFACE "org.pipewire.Telephony.AudioGateway1" #define PW_TELEPHONY_AG_TRANSPORT_IFACE "org.pipewire.Telephony.AudioGatewayTransport1" #define PW_TELEPHONY_CALL_IFACE "org.pipewire.Telephony.Call1" #define OFONO_MANAGER_IFACE "org.ofono.Manager" #define OFONO_VOICE_CALL_MANAGER_IFACE "org.ofono.VoiceCallManager" #define OFONO_VOICE_CALL_IFACE "org.ofono.VoiceCall" #define DBUS_OBJECT_MANAGER_IFACE_INTROSPECT_XML \ " " \ " " \ " " \ " " \ " " \ " " \ " " \ " " \ " " \ " " \ " " \ " " \ " " #define DBUS_PROPERTIES_IFACE_INTROSPECT_XML \ " " \ " " \ " " \ " " \ " " \ " " \ " " \ " " \ " " \ " " \ " " \ " " \ " " \ " " \ " " \ " " \ " " \ " " \ " " \ " " \ " " #define DBUS_INTROSPECTABLE_IFACE_INTROSPECT_XML \ " " \ " " \ " " \ " " \ " " #define PW_TELEPHONY_MANAGER_INTROSPECT_XML \ DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \ "" \ " " \ " " \ " " \ " " \ " " \ " " \ " " \ " " \ " " \ " " \ " " \ " " \ DBUS_OBJECT_MANAGER_IFACE_INTROSPECT_XML \ DBUS_INTROSPECTABLE_IFACE_INTROSPECT_XML \ "" #define PW_TELEPHONY_AG_COMMON_INTROSPECT_XML \ " " \ " " \ " " \ " " \ " " \ " " \ " " \ " " \ " " \ " " \ " " \ " " \ " " \ " " \ " " \ " " \ " " \ " " #define PW_TELEPHONY_AG_INTROSPECT_XML \ DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \ "" \ " " \ PW_TELEPHONY_AG_COMMON_INTROSPECT_XML \ " " \ " " \ " " \ " " \ " " \ " " \ " " \ " " \ " " \ " " \ " " \ " " \ " " \ PW_TELEPHONY_AG_COMMON_INTROSPECT_XML \ " " \ " " \ " " \ " " \ " " \ " " \ " " \ " " \ " " \ " " \ " " \ DBUS_OBJECT_MANAGER_IFACE_INTROSPECT_XML \ DBUS_PROPERTIES_IFACE_INTROSPECT_XML \ DBUS_INTROSPECTABLE_IFACE_INTROSPECT_XML \ "" #define PW_TELEPHONY_CALL_COMMON_INTROSPECT_XML \ " " \ " " \ " " \ " " #define PW_TELEPHONY_CALL_INTROSPECT_XML \ DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \ "" \ " " \ PW_TELEPHONY_CALL_COMMON_INTROSPECT_XML \ " " \ " " \ " " \ " " \ " " \ " " \ " " \ PW_TELEPHONY_CALL_COMMON_INTROSPECT_XML \ " " \ " " \ " " \ " " \ " " \ " " \ " " \ " " \ DBUS_PROPERTIES_IFACE_INTROSPECT_XML \ DBUS_INTROSPECTABLE_IFACE_INTROSPECT_XML \ "" SPA_LOG_TOPIC_DEFINE_STATIC(log_topic, "spa.bluez5.telephony"); #undef SPA_LOG_TOPIC_DEFAULT #define SPA_LOG_TOPIC_DEFAULT &log_topic struct callimpl; struct impl { struct spa_bt_telephony this; struct spa_log *log; struct spa_dbus *dbus; struct spa_dbus_connection *dbus_connection; DBusConnection *conn; const char *path; struct spa_list ag_list; bool default_reject_sco; }; struct agimpl { struct spa_bt_telephony_ag this; struct spa_list link; char *path; struct spa_callbacks callbacks; void *user_data; struct { int volume[SPA_BT_VOLUME_ID_TERM]; struct spa_bt_telephony_ag_transport transport; } prev; }; struct callimpl { struct spa_bt_telephony_call this; char *path; struct spa_callbacks callbacks; void *user_data; /* previous values of properties */ struct { char *line_identification; char *incoming_line; char *name; bool multiparty; enum spa_bt_telephony_call_state state; } prev; }; #define ag_emit(ag,m,v,...) spa_callbacks_call(&ag->callbacks, struct spa_bt_telephony_ag_callbacks, m, v, ##__VA_ARGS__) #define ag_emit_dial(s,n,m) ag_emit(s,dial,0,n,m) #define ag_emit_swap_calls(s,m) ag_emit(s,swap_calls,0,m) #define ag_emit_release_and_answer(s,m) ag_emit(s,release_and_answer,0,m) #define ag_emit_release_and_swap(s,m) ag_emit(s,release_and_swap,0,m) #define ag_emit_hold_and_answer(s,m) ag_emit(s,hold_and_answer,0,m) #define ag_emit_hangup_all(s,m) ag_emit(s,hangup_all,0,m) #define ag_emit_create_multiparty(s,m) ag_emit(s,create_multiparty,0,m) #define ag_emit_send_tones(s,t,m) ag_emit(s,send_tones,0,t,m) #define ag_emit_transport_activate(s,m) ag_emit(s,transport_activate,0,m) #define ag_emit_set_speaker_volume(s,v,m) ag_emit(s,set_speaker_volume,0,v,m) #define ag_emit_set_microphone_volume(s,v,m) ag_emit(s,set_microphone_volume,0,v,m) #define call_emit(c,m,v,...) spa_callbacks_call(&c->callbacks, struct spa_bt_telephony_call_callbacks, m, v, ##__VA_ARGS__) #define call_emit_answer(s,m) call_emit(s,answer,0,m) #define call_emit_hangup(s,m) call_emit(s,hangup,0,m) static void dbus_iter_append_ag_interfaces(DBusMessageIter *i, struct spa_bt_telephony_ag *ag); static void dbus_iter_append_call_properties(DBusMessageIter *i, struct spa_bt_telephony_call *call, bool all); #define PW_TELEPHONY_ERROR_FAILED "org.pipewire.Telephony.Error.Failed" #define PW_TELEPHONY_ERROR_NOT_SUPPORTED "org.pipewire.Telephony.Error.NotSupported" #define PW_TELEPHONY_ERROR_INVALID_FORMAT "org.pipewire.Telephony.Error.InvalidFormat" #define PW_TELEPHONY_ERROR_INVALID_STATE "org.pipewire.Telephony.Error.InvalidState" #define PW_TELEPHONY_ERROR_IN_PROGRESS "org.pipewire.Telephony.Error.InProgress" #define PW_TELEPHONY_ERROR_CME "org.pipewire.Telephony.Error.CME" static const char *telephony_error_to_dbus (enum spa_bt_telephony_error err) { switch (err) { case BT_TELEPHONY_ERROR_FAILED: return PW_TELEPHONY_ERROR_FAILED; case BT_TELEPHONY_ERROR_NOT_SUPPORTED: return PW_TELEPHONY_ERROR_NOT_SUPPORTED; case BT_TELEPHONY_ERROR_INVALID_FORMAT: return PW_TELEPHONY_ERROR_INVALID_FORMAT; case BT_TELEPHONY_ERROR_INVALID_STATE: return PW_TELEPHONY_ERROR_INVALID_STATE; case BT_TELEPHONY_ERROR_IN_PROGRESS: return PW_TELEPHONY_ERROR_IN_PROGRESS; case BT_TELEPHONY_ERROR_CME: return PW_TELEPHONY_ERROR_CME; default: return ""; } } static const char *telephony_error_to_description (enum spa_bt_telephony_error err, uint8_t cme_error) { switch (err) { case BT_TELEPHONY_ERROR_FAILED: return "Method call failed"; case BT_TELEPHONY_ERROR_NOT_SUPPORTED: return "Method is not supported on this Audio Gateway"; case BT_TELEPHONY_ERROR_INVALID_FORMAT: return "Invalid phone number or tones"; case BT_TELEPHONY_ERROR_INVALID_STATE: return "The current state does not allow this method call"; case BT_TELEPHONY_ERROR_IN_PROGRESS: return "Command already in progress"; case BT_TELEPHONY_ERROR_CME: switch (cme_error) { case 0: return "AG failure"; case 1: return "no connection to phone"; case 3: return "operation not allowed"; case 4: return "operation not supported"; case 5: return "PH-SIM PIN required"; case 10: return "SIM not inserted"; case 11: return "SIM PIN required"; case 12: return "SIM PUK required"; case 13: return "SIM failure"; case 14: return "SIM busy"; case 16: return "incorrect password"; case 17: return "SIM PIN2 required"; case 18: return "SIM PUK2 required"; case 20: return "memory full"; case 21: return "invalid index"; case 23: return "memory failure"; case 24: return "text string too long"; case 25: return "invalid characters in text string"; case 26: return "dial string too long"; case 27: return "invalid characters in dial string"; case 30: return "no network service"; case 31: return "network Timeout"; case 32: return "network not allowed - Emergency calls only"; default: return "Unknown CME error"; } default: return ""; } } #define find_free_object_id(list, obj_type, link) \ ({ \ int id = 1; \ obj_type *object; \ spa_list_for_each(object, list, link) { \ if (object->this.id <= id) \ id = object->this.id + 1; \ } \ id; \ }) static DBusMessage *manager_introspect(struct impl *impl, DBusMessage *m) { const char *xml = PW_TELEPHONY_MANAGER_INTROSPECT_XML; spa_autoptr(DBusMessage) r = NULL; if ((r = dbus_message_new_method_return(m)) == NULL) return NULL; if (!dbus_message_append_args(r, DBUS_TYPE_STRING, &xml, DBUS_TYPE_INVALID)) return NULL; return spa_steal_ptr(r); } static DBusMessage *manager_get_managed_objects(struct impl *impl, DBusMessage *m, bool ofono_compat) { struct agimpl *agimpl; spa_autoptr(DBusMessage) r = NULL; DBusMessageIter iter, array1, entry1, props_dict; if ((r = dbus_message_new_method_return(m)) == NULL) return NULL; dbus_message_iter_init_append(r, &iter); dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, ofono_compat ? "{oa{sv}}" : "{oa{sa{sv}}}", &array1); spa_list_for_each (agimpl, &impl->ag_list, link) { if (agimpl->path) { dbus_message_iter_open_container(&array1, DBUS_TYPE_DICT_ENTRY, NULL, &entry1); if (ofono_compat) { dbus_message_iter_append_basic(&entry1, DBUS_TYPE_OBJECT_PATH, &agimpl->path); dbus_message_iter_open_container(&entry1, DBUS_TYPE_ARRAY, "{sv}", &props_dict); dbus_message_iter_close_container(&entry1, &props_dict); } else { dbus_iter_append_ag_interfaces(&entry1, &agimpl->this); } dbus_message_iter_close_container(&array1, &entry1); } } dbus_message_iter_close_container(&iter, &array1); return spa_steal_ptr(r); } static DBusHandlerResult manager_handler(DBusConnection *c, DBusMessage *m, void *userdata) { struct impl *impl = userdata; spa_autoptr(DBusMessage) r = NULL; const char *path, *interface, *member; path = dbus_message_get_path(m); interface = dbus_message_get_interface(m); member = dbus_message_get_member(m); spa_log_debug(impl->log, "dbus: path=%s, interface=%s, member=%s", path, interface, member); if (dbus_message_is_method_call(m, DBUS_INTERFACE_INTROSPECTABLE, "Introspect")) { r = manager_introspect(impl, m); } else if (dbus_message_is_method_call(m, DBUS_INTERFACE_OBJECT_MANAGER, "GetManagedObjects")) { r = manager_get_managed_objects(impl, m, false); } else if (dbus_message_is_method_call(m, OFONO_MANAGER_IFACE, "GetModems")) { r = manager_get_managed_objects(impl, m, true); } else { return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } if (r == NULL) return DBUS_HANDLER_RESULT_NEED_MEMORY; if (!dbus_connection_send(impl->conn, r, NULL)) return DBUS_HANDLER_RESULT_NEED_MEMORY; return DBUS_HANDLER_RESULT_HANDLED; } struct spa_bt_telephony * telephony_new(struct spa_log *log, struct spa_dbus *dbus, const struct spa_dict *info) { struct impl *impl = NULL; spa_auto(DBusError) err = DBUS_ERROR_INIT; bool service_enabled = true; bool ofono_service_compat = false; enum spa_dbus_type bus_type = SPA_DBUS_TYPE_SESSION; int res; static const DBusObjectPathVTable vtable_manager = { .message_function = manager_handler, }; spa_assert(log); spa_assert(dbus); spa_log_topic_init(log, &log_topic); if (info) { const char *str; if ((str = spa_dict_lookup(info, "bluez5.telephony-dbus-service")) != NULL) { service_enabled = spa_atob(str); } if ((str = spa_dict_lookup(info, "bluez5.telephony.use-system-bus")) != NULL) { bus_type = spa_atob(str) ? SPA_DBUS_TYPE_SYSTEM : SPA_DBUS_TYPE_SESSION; } if ((str = spa_dict_lookup(info, "bluez5.telephony.provide-ofono")) != NULL) { ofono_service_compat = spa_atob(str); bus_type = SPA_DBUS_TYPE_SYSTEM; } } if (!service_enabled) { spa_log_info(log, "Bluetooth Telephony service disabled by configuration"); return NULL; } impl = calloc(1, sizeof(*impl)); if (impl == NULL) return NULL; impl->log = log; impl->dbus = dbus; impl->ag_list = SPA_LIST_INIT(&impl->ag_list); impl->dbus_connection = spa_dbus_get_connection(impl->dbus, bus_type); if (impl->dbus_connection == NULL) { spa_log_warn(impl->log, "no session dbus connection"); goto fail; } impl->conn = spa_dbus_connection_get(impl->dbus_connection); if (impl->conn == NULL) { spa_log_warn(impl->log, "failed to get session dbus connection"); goto fail; } impl->default_reject_sco = false; if (info) { const char *str; if ((str = spa_dict_lookup(info, "bluez5.telephony.default-reject-sco")) != NULL) { impl->default_reject_sco = spa_atob(str); } } /* XXX: We should handle spa_dbus reconnecting, but we don't, so ref * XXX: the handle so that we can keep it if spa_dbus unrefs it. */ dbus_connection_ref(impl->conn); res = dbus_bus_request_name(impl->conn, ofono_service_compat ? OFONO_SERVICE : PW_TELEPHONY_SERVICE, DBUS_NAME_FLAG_DO_NOT_QUEUE, &err); if (res < 0) { spa_log_warn(impl->log, "D-Bus RequestName() error: %s", err.message); goto fail; } if (res == DBUS_REQUEST_NAME_REPLY_EXISTS) { spa_log_warn(impl->log, "Bluetooth Telephony service is already registered by another connection"); goto fail; } impl->path = ofono_service_compat ? "/" : PW_TELEPHONY_OBJECT_PATH; if (!dbus_connection_register_object_path(impl->conn, impl->path, &vtable_manager, impl)) { goto fail; } return &impl->this; fail: spa_log_info(impl->log, "Bluetooth Telephony service disabled due to failure"); if (impl->conn) dbus_connection_unref(impl->conn); if (impl->dbus_connection) spa_dbus_connection_destroy(impl->dbus_connection); free(impl); return NULL; } void telephony_free(struct spa_bt_telephony *telephony) { struct impl *impl = SPA_CONTAINER_OF(telephony, struct impl, this); struct agimpl *agimpl; spa_list_consume (agimpl, &impl->ag_list, link) { telephony_ag_destroy(&agimpl->this); } dbus_connection_unref(impl->conn); spa_dbus_connection_destroy(impl->dbus_connection); impl->dbus_connection = NULL; impl->conn = NULL; free(impl); } void telephony_send_dbus_method_reply(struct spa_bt_telephony *telephony, DBusMessage *m, enum spa_bt_telephony_error err, uint8_t cme_error) { struct impl *impl = SPA_CONTAINER_OF(telephony, struct impl, this); spa_autoptr(DBusMessage) reply = NULL; if (err == BT_TELEPHONY_ERROR_NONE) reply = dbus_message_new_method_return(m); else reply = dbus_message_new_error(m, telephony_error_to_dbus (err), telephony_error_to_description (err, cme_error)); dbus_connection_send(impl->conn, reply, NULL); } static void telephony_ag_commit_properties(struct spa_bt_telephony_ag *ag) { struct agimpl *agimpl = SPA_CONTAINER_OF(ag, struct agimpl, this); for (int i = 0; i < SPA_BT_VOLUME_ID_TERM; ++i) { agimpl->prev.volume[i] = ag->volume[i]; } } static void telephony_ag_transport_commit_properties(struct spa_bt_telephony_ag *ag) { struct agimpl *agimpl = SPA_CONTAINER_OF(ag, struct agimpl, this); agimpl->prev.transport = ag->transport; } static const char * const * transport_state_to_string(int state) { static const char * const state_str[] = { "error", "idle", "pending", "active", }; if (state < -1 || state > 2) state = -1; return &state_str[state + 1]; } static bool dbus_iter_append_ag_properties(DBusMessageIter *i, struct spa_bt_telephony_ag *ag, bool all) { struct agimpl *agimpl = SPA_CONTAINER_OF(ag, struct agimpl, this); DBusMessageIter dict, entry, variant; bool changed = false; dbus_message_iter_open_container(i, DBUS_TYPE_ARRAY, "{sv}", &dict); /* Address must be set before registering and never changes, so there is no need to check for changes here */ if (all) { dbus_message_iter_open_container(&dict, DBUS_TYPE_DICT_ENTRY, NULL, &entry); const char *name = "Address"; dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &name); dbus_message_iter_open_container(&entry, DBUS_TYPE_VARIANT, DBUS_TYPE_STRING_AS_STRING, &variant); dbus_message_iter_append_basic(&variant, DBUS_TYPE_STRING, &ag->address); dbus_message_iter_close_container(&entry, &variant); dbus_message_iter_close_container(&dict, &entry); changed = true; } if (all || ag->volume[SPA_BT_VOLUME_ID_RX] != agimpl->prev.volume[SPA_BT_VOLUME_ID_RX]) { dbus_message_iter_open_container(&dict, DBUS_TYPE_DICT_ENTRY, NULL, &entry); const char *name = "SpeakerVolume"; dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &name); dbus_message_iter_open_container(&entry, DBUS_TYPE_VARIANT, DBUS_TYPE_BYTE_AS_STRING, &variant); dbus_message_iter_append_basic(&variant, DBUS_TYPE_BYTE, &ag->volume[SPA_BT_VOLUME_ID_RX]); dbus_message_iter_close_container(&entry, &variant); dbus_message_iter_close_container(&dict, &entry); changed = true; } if (all || ag->volume[SPA_BT_VOLUME_ID_TX] != agimpl->prev.volume[SPA_BT_VOLUME_ID_TX]) { dbus_message_iter_open_container(&dict, DBUS_TYPE_DICT_ENTRY, NULL, &entry); const char *name = "MicrophoneVolume"; dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &name); dbus_message_iter_open_container(&entry, DBUS_TYPE_VARIANT, DBUS_TYPE_BYTE_AS_STRING, &variant); dbus_message_iter_append_basic(&variant, DBUS_TYPE_BYTE, &ag->volume[SPA_BT_VOLUME_ID_TX]); dbus_message_iter_close_container(&entry, &variant); dbus_message_iter_close_container(&dict, &entry); changed = true; } dbus_message_iter_close_container(i, &dict); return changed; } static bool dbus_iter_append_ag_transport_properties(DBusMessageIter *i, struct spa_bt_telephony_ag *ag, bool all) { struct agimpl *agimpl = SPA_CONTAINER_OF(ag, struct agimpl, this); DBusMessageIter dict, entry, variant; bool changed = false; dbus_message_iter_open_container(i, DBUS_TYPE_ARRAY, "{sv}", &dict); if (all || ag->transport.codec != agimpl->prev.transport.codec) { dbus_message_iter_open_container(&dict, DBUS_TYPE_DICT_ENTRY, NULL, &entry); const char *name = "Codec"; dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &name); dbus_message_iter_open_container(&entry, DBUS_TYPE_VARIANT, DBUS_TYPE_BYTE_AS_STRING, &variant); dbus_message_iter_append_basic(&variant, DBUS_TYPE_BYTE, &ag->transport.codec); dbus_message_iter_close_container(&entry, &variant); dbus_message_iter_close_container(&dict, &entry); changed = true; } if (all || ag->transport.state != agimpl->prev.transport.state) { dbus_message_iter_open_container(&dict, DBUS_TYPE_DICT_ENTRY, NULL, &entry); const char *name = "State"; dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &name); dbus_message_iter_open_container(&entry, DBUS_TYPE_VARIANT, DBUS_TYPE_STRING_AS_STRING, &variant); dbus_message_iter_append_basic(&variant, DBUS_TYPE_STRING, transport_state_to_string(ag->transport.state)); dbus_message_iter_close_container(&entry, &variant); dbus_message_iter_close_container(&dict, &entry); changed = true; } if (all || ag->transport.rejectSCO != agimpl->prev.transport.rejectSCO) { dbus_message_iter_open_container(&dict, DBUS_TYPE_DICT_ENTRY, NULL, &entry); const char *name = "RejectSCO"; dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &name); dbus_message_iter_open_container(&entry, DBUS_TYPE_VARIANT, DBUS_TYPE_BOOLEAN_AS_STRING, &variant); dbus_message_iter_append_basic(&variant, DBUS_TYPE_BOOLEAN, &ag->transport.rejectSCO); dbus_message_iter_close_container(&entry, &variant); dbus_message_iter_close_container(&dict, &entry); changed = true; } dbus_message_iter_close_container(i, &dict); return changed; } static void dbus_iter_append_ag_interfaces(DBusMessageIter *i, struct spa_bt_telephony_ag *ag) { struct agimpl *agimpl = SPA_CONTAINER_OF(ag, struct agimpl, this); DBusMessageIter entry, dict; dbus_message_iter_append_basic(i, DBUS_TYPE_OBJECT_PATH, &agimpl->path); dbus_message_iter_open_container(i, DBUS_TYPE_ARRAY, "{sa{sv}}", &dict); const char *interface = PW_TELEPHONY_AG_IFACE; dbus_message_iter_open_container(&dict, DBUS_TYPE_DICT_ENTRY, NULL, &entry); dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &interface); dbus_iter_append_ag_properties(&entry, ag, true); dbus_message_iter_close_container(&dict, &entry); const char *interface2 = PW_TELEPHONY_AG_TRANSPORT_IFACE; dbus_message_iter_open_container(&dict, DBUS_TYPE_DICT_ENTRY, NULL, &entry); dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &interface2); dbus_iter_append_ag_transport_properties(&entry, ag, true); dbus_message_iter_close_container(&dict, &entry); dbus_message_iter_close_container(i, &dict); } static DBusMessage *ag_introspect(struct agimpl *agimpl, DBusMessage *m) { const char *xml = PW_TELEPHONY_AG_INTROSPECT_XML; spa_autoptr(DBusMessage) r = NULL; if ((r = dbus_message_new_method_return(m)) == NULL) return NULL; if (!dbus_message_append_args(r, DBUS_TYPE_STRING, &xml, DBUS_TYPE_INVALID)) return NULL; return spa_steal_ptr(r); } static DBusMessage *ag_get_managed_objects(struct agimpl *agimpl, DBusMessage *m, bool ofono_compat) { struct callimpl *callimpl; spa_autoptr(DBusMessage) r = NULL; DBusMessageIter iter, array1, entry1, array2, entry2; const char *interface = PW_TELEPHONY_CALL_IFACE; if ((r = dbus_message_new_method_return(m)) == NULL) return NULL; dbus_message_iter_init_append(r, &iter); dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, ofono_compat ? "{oa{sv}}" : "{oa{sa{sv}}}", &array1); spa_list_for_each (callimpl, &agimpl->this.call_list, this.link) { dbus_message_iter_open_container(&array1, DBUS_TYPE_DICT_ENTRY, NULL, &entry1); dbus_message_iter_append_basic(&entry1, DBUS_TYPE_OBJECT_PATH, &callimpl->path); if (ofono_compat) { dbus_iter_append_call_properties(&entry1, &callimpl->this, true); } else { dbus_message_iter_open_container(&entry1, DBUS_TYPE_ARRAY, "{sa{sv}}", &array2); dbus_message_iter_open_container(&array2, DBUS_TYPE_DICT_ENTRY, NULL, &entry2); dbus_message_iter_append_basic(&entry2, DBUS_TYPE_STRING, &interface); dbus_iter_append_call_properties(&entry2, &callimpl->this, true); dbus_message_iter_close_container(&array2, &entry2); dbus_message_iter_close_container(&entry1, &array2); } dbus_message_iter_close_container(&array1, &entry1); } dbus_message_iter_close_container(&iter, &array1); return spa_steal_ptr(r); } static DBusMessage *ag_properties_get(struct agimpl *agimpl, DBusMessage *m) { const char *iface, *name; DBusMessage *r; DBusMessageIter i, v; if (!dbus_message_get_args(m, NULL, DBUS_TYPE_STRING, &iface, DBUS_TYPE_STRING, &name, DBUS_TYPE_INVALID)) return NULL; if (spa_streq(iface, PW_TELEPHONY_AG_IFACE)) { if (spa_streq(name, "Address")) { r = dbus_message_new_method_return(m); if (r == NULL) return NULL; dbus_message_iter_init_append(r, &i); dbus_message_iter_open_container(&i, DBUS_TYPE_VARIANT, DBUS_TYPE_STRING_AS_STRING, &v); dbus_message_iter_append_basic(&v, DBUS_TYPE_STRING, &agimpl->this.address); dbus_message_iter_close_container(&i, &v); return r; } else if (spa_streq(name, "SpeakerVolume")) { r = dbus_message_new_method_return(m); if (r == NULL) return NULL; dbus_message_iter_init_append(r, &i); dbus_message_iter_open_container(&i, DBUS_TYPE_VARIANT, DBUS_TYPE_BYTE_AS_STRING, &v); dbus_message_iter_append_basic(&v, DBUS_TYPE_BYTE, &agimpl->this.volume[SPA_BT_VOLUME_ID_RX]); dbus_message_iter_close_container(&i, &v); return r; } else if (spa_streq(name, "MicrophoneVolume")) { r = dbus_message_new_method_return(m); if (r == NULL) return NULL; dbus_message_iter_init_append(r, &i); dbus_message_iter_open_container(&i, DBUS_TYPE_VARIANT, DBUS_TYPE_BYTE_AS_STRING, &v); dbus_message_iter_append_basic(&v, DBUS_TYPE_BYTE, &agimpl->this.volume[SPA_BT_VOLUME_ID_TX]); dbus_message_iter_close_container(&i, &v); return r; } } else if (spa_streq(iface, PW_TELEPHONY_AG_TRANSPORT_IFACE)) { if (spa_streq(name, "Codec")) { r = dbus_message_new_method_return(m); if (r == NULL) return NULL; dbus_message_iter_init_append(r, &i); dbus_message_iter_open_container(&i, DBUS_TYPE_VARIANT, DBUS_TYPE_BYTE_AS_STRING, &v); dbus_message_iter_append_basic(&v, DBUS_TYPE_BYTE, &agimpl->this.transport.codec); dbus_message_iter_close_container(&i, &v); return r; } else if (spa_streq(name, "State")) { r = dbus_message_new_method_return(m); if (r == NULL) return NULL; dbus_message_iter_init_append(r, &i); dbus_message_iter_open_container(&i, DBUS_TYPE_VARIANT, DBUS_TYPE_STRING_AS_STRING, &v); dbus_message_iter_append_basic(&v, DBUS_TYPE_STRING, transport_state_to_string(agimpl->this.transport.state)); dbus_message_iter_close_container(&i, &v); return r; } else if (spa_streq(name, "RejectSCO")) { r = dbus_message_new_method_return(m); if (r == NULL) return NULL; dbus_message_iter_init_append(r, &i); dbus_message_iter_open_container(&i, DBUS_TYPE_VARIANT, DBUS_TYPE_BOOLEAN_AS_STRING, &v); dbus_message_iter_append_basic(&v, DBUS_TYPE_BOOLEAN, &agimpl->this.transport.rejectSCO); dbus_message_iter_close_container(&i, &v); return r; } } else { return dbus_message_new_error(m, DBUS_ERROR_UNKNOWN_INTERFACE, "No such interface"); } return dbus_message_new_error(m, DBUS_ERROR_UNKNOWN_PROPERTY, "No such property"); } static DBusMessage *ag_properties_get_all(struct agimpl *agimpl, DBusMessage *m) { DBusMessage *r; DBusMessageIter i; const char *iface; if (!dbus_message_get_args(m, NULL, DBUS_TYPE_STRING, &iface, DBUS_TYPE_INVALID)) return NULL; if (spa_streq(iface, PW_TELEPHONY_AG_IFACE)) { r = dbus_message_new_method_return(m); if (r == NULL) return NULL; dbus_message_iter_init_append(r, &i); dbus_iter_append_ag_properties(&i, &agimpl->this, true); return r; } else if (spa_streq(iface, PW_TELEPHONY_AG_TRANSPORT_IFACE)) { r = dbus_message_new_method_return(m); if (r == NULL) return NULL; dbus_message_iter_init_append(r, &i); dbus_iter_append_ag_transport_properties(&i, &agimpl->this, true); return r; } else { return dbus_message_new_error(m, DBUS_ERROR_UNKNOWN_INTERFACE, "No such interface"); } } static DBusMessage *ag_properties_set(struct agimpl *agimpl, DBusMessage *m) { const char *iface, *name; DBusMessageIter i, variant; if (!dbus_message_get_args(m, NULL, DBUS_TYPE_STRING, &iface, DBUS_TYPE_STRING, &name, DBUS_TYPE_INVALID)) return NULL; if (spa_streq(iface, PW_TELEPHONY_AG_IFACE)) { if (spa_streq(name, "SpeakerVolume")) { dbus_message_iter_init(m, &i); dbus_message_iter_next(&i); /* skip iface */ dbus_message_iter_next(&i); /* skip name */ dbus_message_iter_recurse(&i, &variant); /* value */ dbus_message_iter_get_basic(&variant, &agimpl->this.volume[SPA_BT_VOLUME_ID_RX]); return ag_emit_set_speaker_volume(agimpl, agimpl->this.volume[SPA_BT_VOLUME_ID_RX], m) ? NULL : dbus_message_new_error(m, telephony_error_to_dbus (BT_TELEPHONY_ERROR_FAILED), telephony_error_to_description (BT_TELEPHONY_ERROR_FAILED, 0)); } else if (spa_streq(name, "MicrophoneVolume")) { dbus_message_iter_init(m, &i); dbus_message_iter_next(&i); /* skip iface */ dbus_message_iter_next(&i); /* skip name */ dbus_message_iter_recurse(&i, &variant); /* value */ dbus_message_iter_get_basic(&variant, &agimpl->this.volume[SPA_BT_VOLUME_ID_TX]); return ag_emit_set_microphone_volume(agimpl, agimpl->this.volume[SPA_BT_VOLUME_ID_TX], m) ? NULL : dbus_message_new_error(m, telephony_error_to_dbus (BT_TELEPHONY_ERROR_FAILED), telephony_error_to_description (BT_TELEPHONY_ERROR_FAILED, 0)); } } else if (spa_streq(iface, PW_TELEPHONY_AG_TRANSPORT_IFACE)) { if (spa_streq(name, "RejectSCO")) { dbus_message_iter_init(m, &i); dbus_message_iter_next(&i); /* skip iface */ dbus_message_iter_next(&i); /* skip name */ dbus_message_iter_recurse(&i, &variant); /* value */ dbus_message_iter_get_basic(&variant, &agimpl->this.transport.rejectSCO); return dbus_message_new_method_return(m); } } return dbus_message_new_error(m, DBUS_ERROR_PROPERTY_READ_ONLY, "Property not writable"); } static bool validate_phone_number(const char *number) { const char *c; int count = 0; if (!number) return false; for (c = number; *c != '\0'; c++) { if (!(*c >= '0' && *c <= '9') && !(*c >= 'A' && *c <= 'D') && *c != '#' && *c != '*' && *c != '+' && *c != ',' ) return false; count++; } if (count < 1 || count > 80) return false; return true; } static bool validate_tones(const char *tones) { const char *c; if (!tones) return false; for (c = tones; *c != '\0'; c++) { if (!(*c >= '0' && *c <= '9') && !(*c >= 'A' && *c <= 'D') && *c != '#' && *c != '*') return false; } return true; } static DBusMessage *ag_dial(struct agimpl *agimpl, DBusMessage *m) { const char *number = NULL; enum spa_bt_telephony_error err = BT_TELEPHONY_ERROR_FAILED; if (!dbus_message_get_args(m, NULL, DBUS_TYPE_STRING, &number, DBUS_TYPE_INVALID)) return NULL; if (!validate_phone_number(number)) { err = BT_TELEPHONY_ERROR_INVALID_FORMAT; goto failed; } if (ag_emit_dial(agimpl, number, m)) return NULL; failed: return dbus_message_new_error(m, telephony_error_to_dbus (err), telephony_error_to_description (err, 0)); } static DBusMessage *ag_swap_calls(struct agimpl *agimpl, DBusMessage *m) { return ag_emit_swap_calls(agimpl, m) ? NULL : dbus_message_new_error(m, telephony_error_to_dbus (BT_TELEPHONY_ERROR_FAILED), telephony_error_to_description (BT_TELEPHONY_ERROR_FAILED, 0)); } static DBusMessage *ag_release_and_answer(struct agimpl *agimpl, DBusMessage *m) { return ag_emit_release_and_answer(agimpl, m) ? NULL : dbus_message_new_error(m, telephony_error_to_dbus (BT_TELEPHONY_ERROR_FAILED), telephony_error_to_description (BT_TELEPHONY_ERROR_FAILED, 0)); } static DBusMessage *ag_release_and_swap(struct agimpl *agimpl, DBusMessage *m) { return ag_emit_release_and_swap(agimpl, m) ? NULL : dbus_message_new_error(m, telephony_error_to_dbus (BT_TELEPHONY_ERROR_FAILED), telephony_error_to_description (BT_TELEPHONY_ERROR_FAILED, 0)); } static DBusMessage *ag_hold_and_answer(struct agimpl *agimpl, DBusMessage *m) { return ag_emit_hold_and_answer(agimpl, m) ? NULL : dbus_message_new_error(m, telephony_error_to_dbus (BT_TELEPHONY_ERROR_FAILED), telephony_error_to_description (BT_TELEPHONY_ERROR_FAILED, 0)); } static DBusMessage *ag_hangup_all(struct agimpl *agimpl, DBusMessage *m) { return ag_emit_hangup_all(agimpl, m) ? NULL : dbus_message_new_error(m, telephony_error_to_dbus (BT_TELEPHONY_ERROR_FAILED), telephony_error_to_description (BT_TELEPHONY_ERROR_FAILED, 0)); } static DBusMessage *ag_create_multiparty(struct agimpl *agimpl, DBusMessage *m) { return ag_emit_create_multiparty(agimpl, m) ? NULL : dbus_message_new_error(m, telephony_error_to_dbus (BT_TELEPHONY_ERROR_FAILED), telephony_error_to_description (BT_TELEPHONY_ERROR_FAILED, 0)); } static DBusMessage *ag_send_tones(struct agimpl *agimpl, DBusMessage *m) { const char *tones = NULL; enum spa_bt_telephony_error err = BT_TELEPHONY_ERROR_FAILED; if (!dbus_message_get_args(m, NULL, DBUS_TYPE_STRING, &tones, DBUS_TYPE_INVALID)) return NULL; if (!validate_tones(tones)) { err = BT_TELEPHONY_ERROR_INVALID_FORMAT; goto failed; } if (ag_emit_send_tones(agimpl, tones, m)) return NULL; failed: return dbus_message_new_error(m, telephony_error_to_dbus (err), telephony_error_to_description (err, 0)); } static DBusMessage *ag_transport_activate(struct agimpl *agimpl, DBusMessage *m) { return ag_emit_transport_activate(agimpl, m) ? NULL : dbus_message_new_error(m, telephony_error_to_dbus (BT_TELEPHONY_ERROR_FAILED), telephony_error_to_description (BT_TELEPHONY_ERROR_FAILED, 0)); } static DBusHandlerResult ag_handler(DBusConnection *c, DBusMessage *m, void *userdata) { struct agimpl *agimpl = userdata; struct impl *impl = SPA_CONTAINER_OF(agimpl->this.telephony, struct impl, this); spa_autoptr(DBusMessage) r = NULL; const char *path, *interface, *member; path = dbus_message_get_path(m); interface = dbus_message_get_interface(m); member = dbus_message_get_member(m); spa_log_debug(impl->log, "dbus: path=%s, interface=%s, member=%s", path, interface, member); if (dbus_message_is_method_call(m, DBUS_INTERFACE_INTROSPECTABLE, "Introspect")) { r = ag_introspect(agimpl, m); } else if (dbus_message_is_method_call(m, DBUS_INTERFACE_OBJECT_MANAGER, "GetManagedObjects")) { r = ag_get_managed_objects(agimpl, m, false); } else if (dbus_message_is_method_call(m, DBUS_INTERFACE_PROPERTIES, "Get")) { r = ag_properties_get(agimpl, m); } else if (dbus_message_is_method_call(m, DBUS_INTERFACE_PROPERTIES, "GetAll")) { r = ag_properties_get_all(agimpl, m); } else if (dbus_message_is_method_call(m, DBUS_INTERFACE_PROPERTIES, "Set")) { r = ag_properties_set(agimpl, m); } else if (dbus_message_is_method_call(m, PW_TELEPHONY_AG_IFACE, "Dial") || dbus_message_is_method_call(m, OFONO_VOICE_CALL_MANAGER_IFACE, "Dial")) { r = ag_dial(agimpl, m); } else if (dbus_message_is_method_call(m, PW_TELEPHONY_AG_IFACE, "SwapCalls") || dbus_message_is_method_call(m, OFONO_VOICE_CALL_MANAGER_IFACE, "SwapCalls")) { r = ag_swap_calls(agimpl, m); } else if (dbus_message_is_method_call(m, PW_TELEPHONY_AG_IFACE, "ReleaseAndAnswer") || dbus_message_is_method_call(m, OFONO_VOICE_CALL_MANAGER_IFACE, "ReleaseAndAnswer")) { r = ag_release_and_answer(agimpl, m); } else if (dbus_message_is_method_call(m, PW_TELEPHONY_AG_IFACE, "ReleaseAndSwap") || dbus_message_is_method_call(m, OFONO_VOICE_CALL_MANAGER_IFACE, "ReleaseAndSwap")) { r = ag_release_and_swap(agimpl, m); } else if (dbus_message_is_method_call(m, PW_TELEPHONY_AG_IFACE, "HoldAndAnswer") || dbus_message_is_method_call(m, OFONO_VOICE_CALL_MANAGER_IFACE, "HoldAndAnswer")) { r = ag_hold_and_answer(agimpl, m); } else if (dbus_message_is_method_call(m, PW_TELEPHONY_AG_IFACE, "HangupAll") || dbus_message_is_method_call(m, OFONO_VOICE_CALL_MANAGER_IFACE, "HangupAll")) { r = ag_hangup_all(agimpl, m); } else if (dbus_message_is_method_call(m, PW_TELEPHONY_AG_IFACE, "CreateMultiparty") || dbus_message_is_method_call(m, OFONO_VOICE_CALL_MANAGER_IFACE, "CreateMultiparty")) { r = ag_create_multiparty(agimpl, m); } else if (dbus_message_is_method_call(m, PW_TELEPHONY_AG_IFACE, "SendTones") || dbus_message_is_method_call(m, OFONO_VOICE_CALL_MANAGER_IFACE, "SendTones")) { r = ag_send_tones(agimpl, m); } else if (dbus_message_is_method_call(m, OFONO_VOICE_CALL_MANAGER_IFACE, "GetCalls")) { r = ag_get_managed_objects(agimpl, m, true); } else if (dbus_message_is_method_call(m, PW_TELEPHONY_AG_TRANSPORT_IFACE, "Activate")) { r = ag_transport_activate(agimpl, m); } else { return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } if (r && !dbus_connection_send(impl->conn, r, NULL)) return DBUS_HANDLER_RESULT_NEED_MEMORY; return DBUS_HANDLER_RESULT_HANDLED; } struct spa_bt_telephony_ag * telephony_ag_new(struct spa_bt_telephony *telephony, size_t user_data_size) { struct impl *impl = SPA_CONTAINER_OF(telephony, struct impl, this); struct agimpl *agimpl; spa_assert(user_data_size < SIZE_MAX - sizeof(*agimpl)); agimpl = calloc(1, sizeof(*agimpl) + user_data_size); if (agimpl == NULL) return NULL; agimpl->this.telephony = telephony; agimpl->this.id = find_free_object_id(&impl->ag_list, struct agimpl, link); spa_list_init(&agimpl->this.call_list); spa_list_append(&impl->ag_list, &agimpl->link); if (user_data_size > 0) agimpl->user_data = SPA_PTROFF(agimpl, sizeof(struct agimpl), void); agimpl->this.transport.rejectSCO = impl->default_reject_sco; return &agimpl->this; } void telephony_ag_destroy(struct spa_bt_telephony_ag *ag) { struct agimpl *agimpl = SPA_CONTAINER_OF(ag, struct agimpl, this); struct callimpl *callimpl; spa_list_consume (callimpl, &agimpl->this.call_list, this.link) { telephony_call_destroy(&callimpl->this); } telephony_ag_unregister(ag); spa_list_remove(&agimpl->link); free(ag->address); free(agimpl); } void *telephony_ag_get_user_data(struct spa_bt_telephony_ag *ag) { struct agimpl *agimpl = SPA_CONTAINER_OF(ag, struct agimpl, this); return agimpl->user_data; } int telephony_ag_register(struct spa_bt_telephony_ag *ag) { struct agimpl *agimpl = SPA_CONTAINER_OF(ag, struct agimpl, this); struct impl *impl = SPA_CONTAINER_OF(agimpl->this.telephony, struct impl, this); static const DBusObjectPathVTable vtable = { .message_function = ag_handler, }; if (agimpl->path) return -EBUSY; spa_autofree char *path = spa_aprintf(PW_TELEPHONY_OBJECT_PATH "/ag%d", agimpl->this.id); /* register object */ if (!dbus_connection_register_object_path(impl->conn, path, &vtable, agimpl)) { spa_log_error(impl->log, "failed to register %s", path); return -EIO; } agimpl->path = spa_steal_ptr(path); /* notify on ObjectManager of the Manager object */ { spa_autoptr(DBusMessage) msg = NULL; DBusMessageIter iter; msg = dbus_message_new_signal(impl->path, DBUS_INTERFACE_OBJECT_MANAGER, "InterfacesAdded"); dbus_message_iter_init_append(msg, &iter); dbus_iter_append_ag_interfaces(&iter, ag); if (!dbus_connection_send(impl->conn, msg, NULL)) { spa_log_error(impl->log, "failed to send InterfacesAdded for %s", agimpl->path); telephony_ag_unregister(ag); return -EIO; } } /* emit ModemAdded on the Manager object */ { spa_autoptr(DBusMessage) msg = NULL; DBusMessageIter iter, props_dict; msg = dbus_message_new_signal(impl->path, OFONO_MANAGER_IFACE, "ModemAdded"); dbus_message_iter_init_append(msg, &iter); dbus_message_iter_append_basic(&iter, DBUS_TYPE_OBJECT_PATH, &agimpl->path); dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &props_dict); dbus_message_iter_close_container(&iter, &props_dict); if (!dbus_connection_send(impl->conn, msg, NULL)) { spa_log_error(impl->log, "failed to send ModemAdded for %s", agimpl->path); telephony_ag_unregister(ag); return -EIO; } } spa_log_debug(impl->log, "registered AudioGateway: %s", agimpl->path); return 0; } void telephony_ag_unregister(struct spa_bt_telephony_ag *ag) { struct agimpl *agimpl = SPA_CONTAINER_OF(ag, struct agimpl, this); struct impl *impl = SPA_CONTAINER_OF(agimpl->this.telephony, struct impl, this); if (!agimpl->path) return; spa_log_debug(impl->log, "removing AudioGateway: %s", agimpl->path); { spa_autoptr(DBusMessage) msg = NULL; DBusMessageIter iter, entry; const char *interface = PW_TELEPHONY_AG_IFACE; const char *interface2 = PW_TELEPHONY_AG_TRANSPORT_IFACE; msg = dbus_message_new_signal(impl->path, DBUS_INTERFACE_OBJECT_MANAGER, "InterfacesRemoved"); dbus_message_iter_init_append(msg, &iter); dbus_message_iter_append_basic(&iter, DBUS_TYPE_OBJECT_PATH, &agimpl->path); dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, DBUS_TYPE_STRING_AS_STRING, &entry); dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &interface); dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &interface2); dbus_message_iter_close_container(&iter, &entry); if (!dbus_connection_send(impl->conn, msg, NULL)) { spa_log_warn(impl->log, "sending InterfacesRemoved failed"); } } { spa_autoptr(DBusMessage) msg = NULL; DBusMessageIter iter; msg = dbus_message_new_signal(impl->path, OFONO_MANAGER_IFACE, "ModemRemoved"); dbus_message_iter_init_append(msg, &iter); dbus_message_iter_append_basic(&iter, DBUS_TYPE_OBJECT_PATH, &agimpl->path); if (!dbus_connection_send(impl->conn, msg, NULL)) { spa_log_warn(impl->log, "sending ModemRemoved failed"); } } if (!dbus_connection_unregister_object_path(impl->conn, agimpl->path)) { spa_log_warn(impl->log, "failed to unregister %s", agimpl->path); } free(agimpl->path); agimpl->path = NULL; } /* send message to notify about volume property changes */ void telephony_ag_notify_updated_props(struct spa_bt_telephony_ag *ag) { struct agimpl *agimpl = SPA_CONTAINER_OF(ag, struct agimpl, this); struct impl *impl = SPA_CONTAINER_OF(agimpl->this.telephony, struct impl, this); spa_autoptr(DBusMessage) msg = NULL; const char *interface = PW_TELEPHONY_AG_IFACE; DBusMessageIter i, a; msg = dbus_message_new_signal(agimpl->path, DBUS_INTERFACE_PROPERTIES, "PropertiesChanged"); dbus_message_iter_init_append(msg, &i); dbus_message_iter_append_basic(&i, DBUS_TYPE_STRING, &interface); if (!dbus_iter_append_ag_properties(&i, ag, false)) return; dbus_message_iter_open_container(&i, DBUS_TYPE_ARRAY, DBUS_TYPE_STRING_AS_STRING, &a); dbus_message_iter_close_container(&i, &a); if (!dbus_connection_send(impl->conn, msg, NULL)){ spa_log_warn(impl->log, "sending PropertiesChanged failed"); } telephony_ag_commit_properties(ag); } /* send message to notify about transport property changes */ void telephony_ag_transport_notify_updated_props(struct spa_bt_telephony_ag *ag) { struct agimpl *agimpl = SPA_CONTAINER_OF(ag, struct agimpl, this); struct impl *impl = SPA_CONTAINER_OF(agimpl->this.telephony, struct impl, this); spa_autoptr(DBusMessage) msg = NULL; const char *interface = PW_TELEPHONY_AG_TRANSPORT_IFACE; DBusMessageIter i, a; msg = dbus_message_new_signal(agimpl->path, DBUS_INTERFACE_PROPERTIES, "PropertiesChanged"); dbus_message_iter_init_append(msg, &i); dbus_message_iter_append_basic(&i, DBUS_TYPE_STRING, &interface); if (!dbus_iter_append_ag_transport_properties(&i, ag, false)) return; dbus_message_iter_open_container(&i, DBUS_TYPE_ARRAY, DBUS_TYPE_STRING_AS_STRING, &a); dbus_message_iter_close_container(&i, &a); if (!dbus_connection_send(impl->conn, msg, NULL)){ spa_log_warn(impl->log, "sending PropertiesChanged failed"); } telephony_ag_transport_commit_properties(ag); } void telephony_ag_set_callbacks(struct spa_bt_telephony_ag *ag, const struct spa_bt_telephony_ag_callbacks *cbs, void *data) { struct agimpl *agimpl = SPA_CONTAINER_OF(ag, struct agimpl, this); agimpl->callbacks.funcs = cbs; agimpl->callbacks.data = data; } struct spa_bt_telephony_call * telephony_call_new(struct spa_bt_telephony_ag *ag, size_t user_data_size) { struct callimpl *callimpl; spa_assert(user_data_size < SIZE_MAX - sizeof(*callimpl)); callimpl = calloc(1, sizeof(*callimpl) + user_data_size); if (callimpl == NULL) return NULL; callimpl->this.ag = ag; callimpl->this.id = find_free_object_id(&ag->call_list, struct callimpl, this.link); spa_list_append(&ag->call_list, &callimpl->this.link); if (user_data_size > 0) callimpl->user_data = SPA_PTROFF(callimpl, sizeof(struct callimpl), void); return &callimpl->this; } void telephony_call_destroy(struct spa_bt_telephony_call *call) { struct callimpl *callimpl = SPA_CONTAINER_OF(call, struct callimpl, this); telephony_call_unregister(call); spa_list_remove(&call->link); free(callimpl->prev.line_identification); free(callimpl->prev.incoming_line); free(callimpl->prev.name); free(call->line_identification); free(call->incoming_line); free(call->name); free(callimpl); } void *telephony_call_get_user_data(struct spa_bt_telephony_call *call) { struct callimpl *callimpl = SPA_CONTAINER_OF(call, struct callimpl, this); return callimpl->user_data; } static void telephony_call_commit_properties(struct spa_bt_telephony_call *call) { struct callimpl *callimpl = SPA_CONTAINER_OF(call, struct callimpl, this); if (!spa_streq (call->line_identification, callimpl->prev.line_identification)) { free(callimpl->prev.line_identification); callimpl->prev.line_identification = call->line_identification ? strdup (call->line_identification) : NULL; } if (!spa_streq (call->incoming_line, callimpl->prev.incoming_line)) { free(callimpl->prev.incoming_line); callimpl->prev.incoming_line = call->incoming_line ? strdup (call->incoming_line) : NULL; } if (!spa_streq (call->name, callimpl->prev.name)) { free(callimpl->prev.name); callimpl->prev.name = call->name ? strdup (call->name) : NULL; } callimpl->prev.multiparty = call->multiparty; callimpl->prev.state = call->state; } static const char * const call_state_to_string[] = { "active", "held", "dialing", "alerting", "incoming", "waiting", "disconnected", }; static inline const void *safe_string(char **str) { static const char *empty_string = ""; return *str ? (const char **) str : &empty_string; } static void dbus_iter_append_call_properties(DBusMessageIter *i, struct spa_bt_telephony_call *call, bool all) { struct callimpl *callimpl = SPA_CONTAINER_OF(call, struct callimpl, this); DBusMessageIter dict, entry, variant; dbus_message_iter_open_container(i, DBUS_TYPE_ARRAY, "{sv}", &dict); if (all || !spa_streq (call->line_identification, callimpl->prev.line_identification)) { dbus_message_iter_open_container(&dict, DBUS_TYPE_DICT_ENTRY, NULL, &entry); const char *line_identification = "LineIdentification"; dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &line_identification); dbus_message_iter_open_container(&entry, DBUS_TYPE_VARIANT, DBUS_TYPE_STRING_AS_STRING, &variant); dbus_message_iter_append_basic(&variant, DBUS_TYPE_STRING, safe_string (&call->line_identification)); dbus_message_iter_close_container(&entry, &variant); dbus_message_iter_close_container(&dict, &entry); } if (all || !spa_streq (call->incoming_line, callimpl->prev.incoming_line)) { dbus_message_iter_open_container(&dict, DBUS_TYPE_DICT_ENTRY, NULL, &entry); const char *incoming_line = "IncomingLine"; dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &incoming_line); dbus_message_iter_open_container(&entry, DBUS_TYPE_VARIANT, DBUS_TYPE_STRING_AS_STRING, &variant); dbus_message_iter_append_basic(&variant, DBUS_TYPE_STRING, safe_string (&call->incoming_line)); dbus_message_iter_close_container(&entry, &variant); dbus_message_iter_close_container(&dict, &entry); } if (all || !spa_streq (call->name, callimpl->prev.name)) { dbus_message_iter_open_container(&dict, DBUS_TYPE_DICT_ENTRY, NULL, &entry); const char *name = "Name"; dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &name); dbus_message_iter_open_container(&entry, DBUS_TYPE_VARIANT, DBUS_TYPE_STRING_AS_STRING, &variant); dbus_message_iter_append_basic(&variant, DBUS_TYPE_STRING, safe_string (&call->name)); dbus_message_iter_close_container(&entry, &variant); dbus_message_iter_close_container(&dict, &entry); } if (all || call->multiparty != callimpl->prev.multiparty) { dbus_message_iter_open_container(&dict, DBUS_TYPE_DICT_ENTRY, NULL, &entry); const char *multiparty = "Multiparty"; dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &multiparty); dbus_message_iter_open_container(&entry, DBUS_TYPE_VARIANT, DBUS_TYPE_BOOLEAN_AS_STRING, &variant); dbus_message_iter_append_basic(&variant, DBUS_TYPE_BOOLEAN, &call->multiparty); dbus_message_iter_close_container(&entry, &variant); dbus_message_iter_close_container(&dict, &entry); } if (all || call->state != callimpl->prev.state) { dbus_message_iter_open_container(&dict, DBUS_TYPE_DICT_ENTRY, NULL, &entry); const char *state = "State"; dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &state); dbus_message_iter_open_container(&entry, DBUS_TYPE_VARIANT, DBUS_TYPE_STRING_AS_STRING, &variant); dbus_message_iter_append_basic(&variant, DBUS_TYPE_STRING, &call_state_to_string[call->state]); dbus_message_iter_close_container(&entry, &variant); dbus_message_iter_close_container(&dict, &entry); } dbus_message_iter_close_container(i, &dict); } static DBusMessage *call_introspect(struct callimpl *callimpl, DBusMessage *m) { const char *xml = PW_TELEPHONY_CALL_INTROSPECT_XML; spa_autoptr(DBusMessage) r = NULL; if ((r = dbus_message_new_method_return(m)) == NULL) return NULL; if (!dbus_message_append_args(r, DBUS_TYPE_STRING, &xml, DBUS_TYPE_INVALID)) return NULL; return spa_steal_ptr(r); } static DBusMessage *call_properties_get(struct callimpl *callimpl, DBusMessage *m) { const char *iface, *name; DBusMessage *r; DBusMessageIter i, v; if (!dbus_message_get_args(m, NULL, DBUS_TYPE_STRING, &iface, DBUS_TYPE_STRING, &name, DBUS_TYPE_INVALID)) return NULL; if (spa_streq(iface, PW_TELEPHONY_CALL_IFACE)) return dbus_message_new_error(m, DBUS_ERROR_INVALID_ARGS, "No such interface"); if (spa_streq(name, "Multiparty")) { r = dbus_message_new_method_return(m); if (r == NULL) return NULL; dbus_message_iter_init_append(r, &i); dbus_message_iter_open_container(&i, DBUS_TYPE_VARIANT, DBUS_TYPE_BOOLEAN_AS_STRING, &v); dbus_message_iter_append_basic(&v, DBUS_TYPE_BOOLEAN, &callimpl->this.multiparty); dbus_message_iter_close_container(&i, &v); return r; } else { const char * const *property = NULL; if (spa_streq(name, "LineIdentification")) { property = (const char * const *) &callimpl->this.line_identification; } else if (spa_streq(name, "IncomingLine")) { property = (const char * const *) &callimpl->this.incoming_line; } else if (spa_streq(name, "Name")) { property = (const char * const *) &callimpl->this.name; } else if (spa_streq(name, "State")) { property = &call_state_to_string[callimpl->this.state]; } if (property) { r = dbus_message_new_method_return(m); if (r == NULL) return NULL; dbus_message_iter_init_append(r, &i); dbus_message_iter_open_container(&i, DBUS_TYPE_VARIANT, DBUS_TYPE_STRING_AS_STRING, &v); dbus_message_iter_append_basic(&v, DBUS_TYPE_STRING, property); dbus_message_iter_close_container(&i, &v); return r; } } return dbus_message_new_error(m, DBUS_ERROR_INVALID_ARGS, "No such property"); } static DBusMessage *call_properties_get_all(struct callimpl *callimpl, DBusMessage *m, bool ofono_compat) { DBusMessage *r; DBusMessageIter i; if (!ofono_compat) { const char *iface; if (!dbus_message_get_args(m, NULL, DBUS_TYPE_STRING, &iface, DBUS_TYPE_INVALID)) return NULL; if (!spa_streq(iface, PW_TELEPHONY_CALL_IFACE)) return dbus_message_new_error(m, DBUS_ERROR_UNKNOWN_INTERFACE, "No such interface"); } r = dbus_message_new_method_return(m); if (r == NULL) return NULL; dbus_message_iter_init_append(r, &i); dbus_iter_append_call_properties(&i, &callimpl->this, true); return r; } static DBusMessage *call_properties_set(struct callimpl *callimpl, DBusMessage *m) { return dbus_message_new_error(m, DBUS_ERROR_PROPERTY_READ_ONLY, "Property not writable"); } static DBusMessage *call_answer(struct callimpl *callimpl, DBusMessage *m) { return call_emit_answer(callimpl, m) ? NULL : dbus_message_new_error(m, telephony_error_to_dbus (BT_TELEPHONY_ERROR_FAILED), telephony_error_to_description (BT_TELEPHONY_ERROR_FAILED, 0)); } static DBusMessage *call_hangup(struct callimpl *callimpl, DBusMessage *m) { return call_emit_hangup(callimpl, m) ? NULL : dbus_message_new_error(m, telephony_error_to_dbus (BT_TELEPHONY_ERROR_FAILED), telephony_error_to_description (BT_TELEPHONY_ERROR_FAILED, 0)); } static DBusHandlerResult call_handler(DBusConnection *c, DBusMessage *m, void *userdata) { struct callimpl *callimpl = userdata; struct agimpl *agimpl = SPA_CONTAINER_OF(callimpl->this.ag, struct agimpl, this); struct impl *impl = SPA_CONTAINER_OF(agimpl->this.telephony, struct impl, this); spa_autoptr(DBusMessage) r = NULL; const char *path, *interface, *member; path = dbus_message_get_path(m); interface = dbus_message_get_interface(m); member = dbus_message_get_member(m); spa_log_debug(impl->log, "dbus: path=%s, interface=%s, member=%s", path, interface, member); if (dbus_message_is_method_call(m, DBUS_INTERFACE_INTROSPECTABLE, "Introspect")) { r = call_introspect(callimpl, m); } else if (dbus_message_is_method_call(m, DBUS_INTERFACE_PROPERTIES, "Get")) { r = call_properties_get(callimpl, m); } else if (dbus_message_is_method_call(m, DBUS_INTERFACE_PROPERTIES, "GetAll")) { r = call_properties_get_all(callimpl, m, false); } else if (dbus_message_is_method_call(m, DBUS_INTERFACE_PROPERTIES, "Set")) { r = call_properties_set(callimpl, m); } else if (dbus_message_is_method_call(m, PW_TELEPHONY_CALL_IFACE, "Answer") || dbus_message_is_method_call(m, OFONO_VOICE_CALL_IFACE, "Answer")) { r = call_answer(callimpl, m); } else if (dbus_message_is_method_call(m, PW_TELEPHONY_CALL_IFACE, "Hangup") || dbus_message_is_method_call(m, OFONO_VOICE_CALL_IFACE, "Hangup")) { r = call_hangup(callimpl, m); } else if (dbus_message_is_method_call(m, OFONO_VOICE_CALL_IFACE, "GetProperties")) { r = call_properties_get_all(callimpl, m, true); } else { return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } if (r && !dbus_connection_send(impl->conn, r, NULL)) return DBUS_HANDLER_RESULT_NEED_MEMORY; return DBUS_HANDLER_RESULT_HANDLED; } int telephony_call_register(struct spa_bt_telephony_call *call) { struct callimpl *callimpl = SPA_CONTAINER_OF(call, struct callimpl, this); struct agimpl *agimpl = SPA_CONTAINER_OF(callimpl->this.ag, struct agimpl, this); struct impl *impl = SPA_CONTAINER_OF(agimpl->this.telephony, struct impl, this); static const DBusObjectPathVTable vtable = { .message_function = call_handler, }; if (callimpl->path) return -EBUSY; spa_autofree char *path = spa_aprintf("%s/call%d", agimpl->path, callimpl->this.id); /* register object */ if (!dbus_connection_register_object_path(impl->conn, path, &vtable, callimpl)) { spa_log_error(impl->log, "failed to register %s", path); return -EIO; } callimpl->path = spa_steal_ptr(path); /* notify on ObjectManager of the AudioGateway object */ { spa_autoptr(DBusMessage) msg = NULL; DBusMessageIter iter, entry, dict; const char *interface = PW_TELEPHONY_CALL_IFACE; msg = dbus_message_new_signal(agimpl->path, DBUS_INTERFACE_OBJECT_MANAGER, "InterfacesAdded"); dbus_message_iter_init_append(msg, &iter); dbus_message_iter_append_basic(&iter, DBUS_TYPE_OBJECT_PATH, &callimpl->path); dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sa{sv}}", &dict); dbus_message_iter_open_container(&dict, DBUS_TYPE_DICT_ENTRY, NULL, &entry); dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &interface); dbus_iter_append_call_properties(&entry, call, true); dbus_message_iter_close_container(&dict, &entry); dbus_message_iter_close_container(&iter, &dict); if (!dbus_connection_send(impl->conn, msg, NULL)) { spa_log_error(impl->log, "failed to send InterfacesAdded for %s", callimpl->path); telephony_call_unregister(call); return -EIO; } } /* emit CallAdded on the AudioGateway object */ { spa_autoptr(DBusMessage) msg = NULL; DBusMessageIter iter; msg = dbus_message_new_signal(agimpl->path, OFONO_VOICE_CALL_MANAGER_IFACE, "CallAdded"); dbus_message_iter_init_append(msg, &iter); dbus_message_iter_append_basic(&iter, DBUS_TYPE_OBJECT_PATH, &callimpl->path); dbus_iter_append_call_properties(&iter, call, true); if (!dbus_connection_send(impl->conn, msg, NULL)) { spa_log_error(impl->log, "failed to send CallAdded for %s", callimpl->path); telephony_call_unregister(call); return -EIO; } } telephony_call_commit_properties(call); spa_log_debug(impl->log, "registered Call: %s", callimpl->path); return 0; } void telephony_call_unregister(struct spa_bt_telephony_call *call) { struct callimpl *callimpl = SPA_CONTAINER_OF(call, struct callimpl, this); struct agimpl *agimpl = SPA_CONTAINER_OF(callimpl->this.ag, struct agimpl, this); struct impl *impl = SPA_CONTAINER_OF(agimpl->this.telephony, struct impl, this); if (!callimpl->path) return; spa_log_debug(impl->log, "removing Call: %s", callimpl->path); { spa_autoptr(DBusMessage) msg = NULL; DBusMessageIter iter, entry; const char *interface = PW_TELEPHONY_CALL_IFACE; msg = dbus_message_new_signal(agimpl->path, DBUS_INTERFACE_OBJECT_MANAGER, "InterfacesRemoved"); dbus_message_iter_init_append(msg, &iter); dbus_message_iter_append_basic(&iter, DBUS_TYPE_OBJECT_PATH, &callimpl->path); dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, DBUS_TYPE_STRING_AS_STRING, &entry); dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &interface); dbus_message_iter_close_container(&iter, &entry); if (!dbus_connection_send(impl->conn, msg, NULL)) { spa_log_warn(impl->log, "sending InterfacesRemoved failed"); } } { spa_autoptr(DBusMessage) msg = NULL; DBusMessageIter iter; msg = dbus_message_new_signal(agimpl->path, OFONO_VOICE_CALL_MANAGER_IFACE, "CallRemoved"); dbus_message_iter_init_append(msg, &iter); dbus_message_iter_append_basic(&iter, DBUS_TYPE_OBJECT_PATH, &callimpl->path); if (!dbus_connection_send(impl->conn, msg, NULL)) { spa_log_warn(impl->log, "sending CallRemoved failed"); } } if (!dbus_connection_unregister_object_path(impl->conn, callimpl->path)) { spa_log_warn(impl->log, "failed to unregister %s", callimpl->path); } free(callimpl->path); callimpl->path = NULL; } /* send message to notify about property changes */ void telephony_call_notify_updated_props(struct spa_bt_telephony_call *call) { struct callimpl *callimpl = SPA_CONTAINER_OF(call, struct callimpl, this); struct agimpl *agimpl = SPA_CONTAINER_OF(callimpl->this.ag, struct agimpl, this); struct impl *impl = SPA_CONTAINER_OF(agimpl->this.telephony, struct impl, this); { spa_autoptr(DBusMessage) msg = NULL; const char *interface = PW_TELEPHONY_CALL_IFACE; DBusMessageIter i, a; msg = dbus_message_new_signal(callimpl->path, DBUS_INTERFACE_PROPERTIES, "PropertiesChanged"); dbus_message_iter_init_append(msg, &i); dbus_message_iter_append_basic(&i, DBUS_TYPE_STRING, &interface); dbus_iter_append_call_properties(&i, call, false); dbus_message_iter_open_container(&i, DBUS_TYPE_ARRAY, DBUS_TYPE_STRING_AS_STRING, &a); dbus_message_iter_close_container(&i, &a); if (!dbus_connection_send(impl->conn, msg, NULL)){ spa_log_warn(impl->log, "sending PropertiesChanged failed"); } } if (!spa_streq (call->line_identification, callimpl->prev.line_identification)) { spa_autoptr(DBusMessage) msg = NULL; DBusMessageIter entry, variant; msg = dbus_message_new_signal(callimpl->path, OFONO_VOICE_CALL_IFACE, "PropertyChanged"); const char *line_identification = "LineIdentification"; dbus_message_iter_init_append(msg, &entry); dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &line_identification); dbus_message_iter_open_container(&entry, DBUS_TYPE_VARIANT, DBUS_TYPE_STRING_AS_STRING, &variant); dbus_message_iter_append_basic(&variant, DBUS_TYPE_STRING, safe_string (&call->line_identification)); dbus_message_iter_close_container(&entry, &variant); if (!dbus_connection_send(impl->conn, msg, NULL)){ spa_log_warn(impl->log, "sending PropertyChanged failed"); } } if (!spa_streq (call->incoming_line, callimpl->prev.incoming_line)) { spa_autoptr(DBusMessage) msg = NULL; DBusMessageIter entry, variant; msg = dbus_message_new_signal(callimpl->path, OFONO_VOICE_CALL_IFACE, "PropertyChanged"); const char *incoming_line = "IncomingLine"; dbus_message_iter_init_append(msg, &entry); dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &incoming_line); dbus_message_iter_open_container(&entry, DBUS_TYPE_VARIANT, DBUS_TYPE_STRING_AS_STRING, &variant); dbus_message_iter_append_basic(&variant, DBUS_TYPE_STRING, safe_string (&call->incoming_line)); dbus_message_iter_close_container(&entry, &variant); if (!dbus_connection_send(impl->conn, msg, NULL)){ spa_log_warn(impl->log, "sending PropertyChanged failed"); } } if (!spa_streq (call->name, callimpl->prev.name)) { spa_autoptr(DBusMessage) msg = NULL; DBusMessageIter entry, variant; msg = dbus_message_new_signal(callimpl->path, OFONO_VOICE_CALL_IFACE, "PropertyChanged"); const char *name = "Name"; dbus_message_iter_init_append(msg, &entry); dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &name); dbus_message_iter_open_container(&entry, DBUS_TYPE_VARIANT, DBUS_TYPE_STRING_AS_STRING, &variant); dbus_message_iter_append_basic(&variant, DBUS_TYPE_STRING, safe_string (&call->name)); dbus_message_iter_close_container(&entry, &variant); if (!dbus_connection_send(impl->conn, msg, NULL)){ spa_log_warn(impl->log, "sending PropertyChanged failed"); } } if (call->multiparty != callimpl->prev.multiparty) { spa_autoptr(DBusMessage) msg = NULL; DBusMessageIter entry, variant; msg = dbus_message_new_signal(callimpl->path, OFONO_VOICE_CALL_IFACE, "PropertyChanged"); const char *multiparty = "Multiparty"; dbus_message_iter_init_append(msg, &entry); dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &multiparty); dbus_message_iter_open_container(&entry, DBUS_TYPE_VARIANT, DBUS_TYPE_BOOLEAN_AS_STRING, &variant); dbus_message_iter_append_basic(&variant, DBUS_TYPE_BOOLEAN, &call->multiparty); dbus_message_iter_close_container(&entry, &variant); if (!dbus_connection_send(impl->conn, msg, NULL)){ spa_log_warn(impl->log, "sending PropertyChanged failed"); } } if (call->state != callimpl->prev.state) { spa_autoptr(DBusMessage) msg = NULL; DBusMessageIter entry, variant; msg = dbus_message_new_signal(callimpl->path, OFONO_VOICE_CALL_IFACE, "PropertyChanged"); const char *state = "State"; dbus_message_iter_init_append(msg, &entry); dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &state); dbus_message_iter_open_container(&entry, DBUS_TYPE_VARIANT, DBUS_TYPE_STRING_AS_STRING, &variant); dbus_message_iter_append_basic(&variant, DBUS_TYPE_STRING, &call_state_to_string[call->state]); dbus_message_iter_close_container(&entry, &variant); if (!dbus_connection_send(impl->conn, msg, NULL)){ spa_log_warn(impl->log, "sending PropertyChanged failed"); } } telephony_call_commit_properties(call); } void telephony_call_set_callbacks(struct spa_bt_telephony_call *call, const struct spa_bt_telephony_call_callbacks *cbs, void *data) { struct callimpl *callimpl = SPA_CONTAINER_OF(call, struct callimpl, this); callimpl->callbacks.funcs = cbs; callimpl->callbacks.data = data; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/bluez5/telephony.h000066400000000000000000000105131511204443500263300ustar00rootroot00000000000000/* Spa Bluez5 Telephony D-Bus service */ /* SPDX-FileCopyrightText: Copyright © 2024 Collabora Ltd. */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_BLUEZ5_TELEPHONY_H #define SPA_BLUEZ5_TELEPHONY_H #include "defs.h" enum spa_bt_telephony_error { BT_TELEPHONY_ERROR_NONE = 0, BT_TELEPHONY_ERROR_FAILED, BT_TELEPHONY_ERROR_NOT_SUPPORTED, BT_TELEPHONY_ERROR_INVALID_FORMAT, BT_TELEPHONY_ERROR_INVALID_STATE, BT_TELEPHONY_ERROR_IN_PROGRESS, BT_TELEPHONY_ERROR_CME, }; enum spa_bt_telephony_call_state { CALL_STATE_ACTIVE, CALL_STATE_HELD, CALL_STATE_DIALING, CALL_STATE_ALERTING, CALL_STATE_INCOMING, CALL_STATE_WAITING, CALL_STATE_DISCONNECTED, }; struct spa_bt_telephony { }; struct spa_bt_telephony_ag_transport { int8_t codec; enum spa_bt_transport_state state; dbus_bool_t rejectSCO; }; struct spa_bt_telephony_ag { struct spa_bt_telephony *telephony; struct spa_list call_list; int id; /* D-Bus properties */ char *address; int volume[SPA_BT_VOLUME_ID_TERM]; struct spa_bt_telephony_ag_transport transport; }; struct spa_bt_telephony_call { struct spa_bt_telephony_ag *ag; struct spa_list link; /* link in ag->call_list */ int id; /* D-Bus properties */ char *line_identification; char *incoming_line; char *name; bool multiparty; enum spa_bt_telephony_call_state state; }; struct spa_bt_telephony_ag_callbacks { #define SPA_VERSION_BT_TELEPHONY_AG_CALLBACKS 0 uint32_t version; void (*dial)(void *data, const char *number, DBusMessage *m); void (*swap_calls)(void *data, DBusMessage *m); void (*release_and_answer)(void *data, DBusMessage *m); void (*release_and_swap)(void *data, DBusMessage *m); void (*hold_and_answer)(void *data, DBusMessage *m); void (*hangup_all)(void *data, DBusMessage *m); void (*create_multiparty)(void *data, DBusMessage *m); void (*send_tones)(void *data, const char *tones, DBusMessage *m); void (*transport_activate)(void *data, DBusMessage *m); void (*set_speaker_volume)(void *data, uint8_t volume, DBusMessage *m); void (*set_microphone_volume)(void *data, uint8_t volume, DBusMessage *m); }; struct spa_bt_telephony_call_callbacks { #define SPA_VERSION_BT_TELEPHONY_CALL_CALLBACKS 0 uint32_t version; void (*answer)(void *data, DBusMessage *m); void (*hangup)(void *data, DBusMessage *m); }; struct spa_bt_telephony *telephony_new(struct spa_log *log, struct spa_dbus *dbus, const struct spa_dict *info); void telephony_free(struct spa_bt_telephony *telephony); /* send a reply to any of the methods (they all return void); this is must be called from the callbacks either in sync or async */ void telephony_send_dbus_method_reply(struct spa_bt_telephony *telephony, DBusMessage *m, enum spa_bt_telephony_error err, uint8_t cme_error); /* create/destroy the ag object */ struct spa_bt_telephony_ag * telephony_ag_new(struct spa_bt_telephony *telephony, size_t user_data_size); void telephony_ag_destroy(struct spa_bt_telephony_ag *ag); /* get the user data structure; struct size is set when creating the AG */ void *telephony_ag_get_user_data(struct spa_bt_telephony_ag *ag); void telephony_ag_set_callbacks(struct spa_bt_telephony_ag *ag, const struct spa_bt_telephony_ag_callbacks *cbs, void *data); void telephony_ag_notify_updated_props(struct spa_bt_telephony_ag *ag); void telephony_ag_transport_notify_updated_props(struct spa_bt_telephony_ag *ag); /* register/unregister AudioGateway object on the bus */ int telephony_ag_register(struct spa_bt_telephony_ag *ag); void telephony_ag_unregister(struct spa_bt_telephony_ag *ag); /* create/destroy the call object */ struct spa_bt_telephony_call * telephony_call_new(struct spa_bt_telephony_ag *ag, size_t user_data_size); void telephony_call_destroy(struct spa_bt_telephony_call *call); /* get the user data structure; struct size is set when creating the Call */ void *telephony_call_get_user_data(struct spa_bt_telephony_call *call); /* register/unregister Call object on the bus */ int telephony_call_register(struct spa_bt_telephony_call *call); void telephony_call_unregister(struct spa_bt_telephony_call *call); /* send message to notify about property changes */ void telephony_call_notify_updated_props(struct spa_bt_telephony_call *call); void telephony_call_set_callbacks(struct spa_bt_telephony_call *call, const struct spa_bt_telephony_call_callbacks *cbs, void *data); #endif pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/bluez5/test-midi.c000066400000000000000000000144471511204443500262250ustar00rootroot00000000000000#include #include "midi.h" #define TIME_HI(v) (0x80 | ((v >> 7) & 0x3f)) #define TIME_LO(v) (0x80 | (v & 0x7f)) struct event { uint16_t time_msec; size_t size; const uint8_t *data; }; struct packet { size_t size; const uint8_t *data; }; struct test_info { const struct packet *packets; const struct event *events; unsigned int i; }; static const struct packet midi_1_packets[] = { { .size = 27, .data = (uint8_t[]) { TIME_HI(0x1234), /* event 1 */ TIME_LO(0x1234), 0xa0, 0x01, 0x02, /* event 2: running status */ 0x03, 0x04, /* event 3: running status with timestamp */ TIME_LO(0x1235), 0x05, 0x06, /* event 4 */ TIME_LO(0x1236), 0xf8, /* event 5: sysex */ TIME_LO(0x1237), 0xf0, 0x0a, 0x0b, 0x0c, /* event 6: realtime event inside sysex */ TIME_LO(0x1238), 0xff, /* event 5 continues */ 0x0d, 0x0e, TIME_LO(0x1239), 0xf7, /* event 6: sysex */ TIME_LO(0x1240), 0xf0, 0x10, 0x11, /* packet end in middle of sysex */ }, }, { .size = 7, .data = (uint8_t[]) { TIME_HI(0x1241), /* event 6: continued from previous packet */ 0x12, TIME_LO(0x1241), 0xf7, /* event 7 */ TIME_LO(0x1242), 0xf1, 0x13, } }, {0} }; static const struct event midi_1_events[] = { { 0x1234, 3, (uint8_t[]) { 0xa0, 0x01, 0x02 } }, { 0x1234, 3, (uint8_t[]) { 0xa0, 0x03, 0x04 } }, { 0x1235, 3, (uint8_t[]) { 0xa0, 0x05, 0x06 } }, { 0x1236, 1, (uint8_t[]) { 0xf8 } }, /* realtime event inside sysex come before it */ { 0x1238, 1, (uint8_t[]) { 0xff } }, /* sysex timestamp indicates the end time; sysex contains the end marker */ { 0x1239, 7, (uint8_t[]) { 0xf0, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0xf7 } }, { 0x1241, 5, (uint8_t[]) { 0xf0, 0x10, 0x11, 0x12, 0xf7 } }, { 0x1242, 2, (uint8_t[]) { 0xf1, 0x13 } }, {0} }; static const struct packet midi_1_packets_mtu14[] = { { .size = 11, .data = (uint8_t[]) { TIME_HI(0x1234), TIME_LO(0x1234), 0xa0, 0x01, 0x02, 0x03, 0x04, /* output Apple-style BLE; running status only for coincident time */ TIME_LO(0x1235), 0xa0, 0x05, 0x06, }, }, { .size = 11, .data = (uint8_t[]) { TIME_HI(0x1236), TIME_LO(0x1236), 0xf8, TIME_LO(0x1238), 0xff, TIME_LO(0x1239), 0xf0, 0x0a, 0x0b, 0x0c, 0x0d, }, }, { .size = 11, .data = (uint8_t[]) { TIME_HI(0x1239), 0x0e, TIME_LO(0x1239), 0xf7, TIME_LO(0x1241), 0xf0, 0x10, 0x11, 0x12, TIME_LO(0x1241), 0xf7 }, }, { .size = 4, .data = (uint8_t[]) { TIME_HI(0x1242), TIME_LO(0x1242), 0xf1, 0x13 }, }, {0} }; static const struct packet midi_2_packets[] = { { .size = 9, .data = (uint8_t[]) { TIME_HI(0x1234), /* event 1 */ TIME_LO(0x1234), 0xa0, 0x01, 0x02, /* event 2: timestamp low bits rollover */ TIME_LO(0x12b3), 0xa0, 0x03, 0x04, }, }, { .size = 5, .data = (uint8_t[]) { TIME_HI(0x18b3), /* event 3: timestamp high bits jump */ TIME_LO(0x18b3), 0xa0, 0x05, 0x06, }, }, {0} }; static const struct event midi_2_events[] = { { 0x1234, 3, (uint8_t[]) { 0xa0, 0x01, 0x02 } }, { 0x12b3, 3, (uint8_t[]) { 0xa0, 0x03, 0x04 } }, { 0x18b3, 3, (uint8_t[]) { 0xa0, 0x05, 0x06 } }, {0} }; static const struct packet midi_2_packets_mtu11[] = { /* Small MTU: only room for one event per packet */ { .size = 5, .data = (uint8_t[]) { TIME_HI(0x1234), TIME_LO(0x1234), 0xa0, 0x01, 0x02, }, }, { .size = 5, .data = (uint8_t[]) { TIME_HI(0x12b3), TIME_LO(0x12b3), 0xa0, 0x03, 0x04, }, }, { .size = 5, .data = (uint8_t[]) { TIME_HI(0x18b3), TIME_LO(0x18b3), 0xa0, 0x05, 0x06, }, }, {0} }; static void check_event(void *user_data, uint16_t time, uint8_t *event, size_t event_size) { struct test_info *info = user_data; const struct event *ev = &info->events[info->i]; spa_assert_se(ev->size > 0); spa_assert_se(ev->time_msec == time); spa_assert_se(ev->size == event_size); spa_assert_se(memcmp(event, ev->data, ev->size) == 0); ++info->i; } static void check_parser(struct test_info *info) { struct spa_bt_midi_parser parser; int res; int i; info->i = 0; spa_bt_midi_parser_init(&parser); for (i = 0; info->packets[i].size > 0; ++i) { res = spa_bt_midi_parser_parse(&parser, info->packets[i].data, info->packets[i].size, false, check_event, info); spa_assert_se(res == 0); } spa_assert_se(info->events[info->i].size == 0); } static void check_writer(struct test_info *info, unsigned int mtu) { struct spa_bt_midi_writer writer; struct spa_bt_midi_parser parser; unsigned int i, packet; void SPA_UNUSED *buf = writer.buf; spa_bt_midi_parser_init(&parser); spa_bt_midi_writer_init(&writer, mtu); packet = 0; info->i = 0; for (i = 0; info->events[i].size > 0; ++i) { const struct event *ev = &info->events[i]; bool last = (info->events[i+1].size == 0); int res; do { res = spa_bt_midi_writer_write(&writer, ev->time_msec * SPA_NSEC_PER_MSEC, ev->data, ev->size); spa_assert_se(res >= 0); if (res || last) { int r; spa_assert_se(info->packets[packet].size > 0); spa_assert_se(writer.size == info->packets[packet].size); spa_assert_se(memcmp(writer.buf, info->packets[packet].data, writer.size) == 0); ++packet; /* Test roundtrip */ r = spa_bt_midi_parser_parse(&parser, writer.buf, writer.size, false, check_event, info); spa_assert_se(r == 0); } } while (res); } spa_assert_se(info->packets[packet].size == 0); spa_assert_se(info->events[info->i].size == 0); } static void test_midi_parser_1(void) { struct test_info info = { .packets = midi_1_packets, .events = midi_1_events, }; check_parser(&info); } static void test_midi_parser_2(void) { struct test_info info = { .packets = midi_2_packets, .events = midi_2_events, }; check_parser(&info); } static void test_midi_writer_1(void) { struct test_info info = { .packets = midi_1_packets_mtu14, .events = midi_1_events, }; check_writer(&info, 14); } static void test_midi_writer_2(void) { struct test_info info = { .packets = midi_2_packets, .events = midi_2_events, }; check_writer(&info, 23); check_writer(&info, 12); } static void test_midi_writer_3(void) { struct test_info info = { .packets = midi_2_packets_mtu11, .events = midi_2_events, }; check_writer(&info, 11); } int main(void) { test_midi_parser_1(); test_midi_parser_2(); test_midi_writer_1(); test_midi_writer_2(); test_midi_writer_3(); return 0; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/bluez5/upower.c000066400000000000000000000147261511204443500256470ustar00rootroot00000000000000/* Spa Bluez5 UPower proxy */ /* SPDX-FileCopyrightText: Copyright © 2022 Collabora */ /* SPDX-License-Identifier: MIT */ #include #include #include #include "upower.h" #define UPOWER_SERVICE "org.freedesktop.UPower" #define UPOWER_DEVICE_INTERFACE UPOWER_SERVICE ".Device" #define UPOWER_DISPLAY_DEVICE_OBJECT "/org/freedesktop/UPower/devices/DisplayDevice" struct impl { struct spa_bt_monitor *monitor; struct spa_log *log; DBusConnection *conn; DBusPendingCall *pending_get_call; bool filters_added; void *user_data; void (*set_battery_level)(unsigned int level, void *user_data); }; static DBusHandlerResult upower_parse_percentage(struct impl *this, DBusMessageIter *variant_i) { double percentage; unsigned int battery_level; dbus_message_iter_get_basic(variant_i, &percentage); spa_log_debug(this->log, "Battery level: %f %%", percentage); battery_level = (unsigned int) round(percentage / 20.0); this->set_battery_level(battery_level, this->user_data); return DBUS_HANDLER_RESULT_HANDLED; } static void upower_get_percentage_properties_reply(DBusPendingCall *pending, void *user_data) { struct impl *backend = user_data; DBusMessageIter i, variant_i; spa_assert(backend->pending_get_call == pending); spa_autoptr(DBusMessage) r = steal_reply_and_unref(&backend->pending_get_call); if (r == NULL) return; if (dbus_message_get_type(r) == DBUS_MESSAGE_TYPE_ERROR) { spa_log_error(backend->log, "Failed to get percentage from UPower: %s", dbus_message_get_error_name(r)); return; } if (!dbus_message_iter_init(r, &i) || !spa_streq(dbus_message_get_signature(r), "v")) { spa_log_error(backend->log, "Invalid arguments in Get() reply"); return; } dbus_message_iter_recurse(&i, &variant_i); upower_parse_percentage(backend, &variant_i); } static int update_battery_percentage(struct impl *this) { cancel_and_unref(&this->pending_get_call); spa_autoptr(DBusMessage) m = dbus_message_new_method_call(UPOWER_SERVICE, UPOWER_DISPLAY_DEVICE_OBJECT, DBUS_INTERFACE_PROPERTIES, "Get"); if (!m) return -ENOMEM; dbus_message_append_args(m, DBUS_TYPE_STRING, &(const char *){ UPOWER_DEVICE_INTERFACE }, DBUS_TYPE_STRING, &(const char *){ "Percentage" }, DBUS_TYPE_INVALID); dbus_message_set_auto_start(m, false); this->pending_get_call = send_with_reply(this->conn, m, upower_get_percentage_properties_reply, this); if (!this->pending_get_call) return -EIO; return 0; } static void upower_clean(struct impl *this) { this->set_battery_level(0, this->user_data); } static DBusHandlerResult upower_filter_cb(DBusConnection *bus, DBusMessage *m, void *user_data) { struct impl *this = user_data; if (dbus_message_is_signal(m, "org.freedesktop.DBus", "NameOwnerChanged")) { const char *name, *old_owner, *new_owner; spa_auto(DBusError) err = DBUS_ERROR_INIT; spa_log_debug(this->log, "Name owner changed %s", dbus_message_get_path(m)); if (!dbus_message_get_args(m, &err, DBUS_TYPE_STRING, &name, DBUS_TYPE_STRING, &old_owner, DBUS_TYPE_STRING, &new_owner, DBUS_TYPE_INVALID)) { spa_log_error(this->log, "Failed to parse org.freedesktop.DBus.NameOwnerChanged: %s", err.message); goto finish; } if (spa_streq(name, UPOWER_SERVICE)) { if (old_owner && *old_owner) { spa_log_debug(this->log, "UPower daemon disappeared (%s)", old_owner); upower_clean(this); } if (new_owner && *new_owner) { spa_log_debug(this->log, "UPower daemon appeared (%s)", new_owner); update_battery_percentage(this); } } } else if (dbus_message_is_signal(m, DBUS_INTERFACE_PROPERTIES, DBUS_SIGNAL_PROPERTIES_CHANGED)) { const char *path; DBusMessageIter iface_i, props_i; const char *interface; if (!dbus_message_iter_init(m, &iface_i) || !spa_streq(dbus_message_get_signature(m), "sa{sv}as")) { spa_log_error(this->log, "Invalid signature found in PropertiesChanged"); goto finish; } dbus_message_iter_get_basic(&iface_i, &interface); dbus_message_iter_next(&iface_i); spa_assert(dbus_message_iter_get_arg_type(&iface_i) == DBUS_TYPE_ARRAY); dbus_message_iter_recurse(&iface_i, &props_i); path = dbus_message_get_path(m); if (spa_streq(interface, UPOWER_DEVICE_INTERFACE)) { spa_log_debug(this->log, "Properties changed on %s", path); while (dbus_message_iter_get_arg_type(&props_i) != DBUS_TYPE_INVALID) { DBusMessageIter i, value_i; const char *key; dbus_message_iter_recurse(&props_i, &i); dbus_message_iter_get_basic(&i, &key); dbus_message_iter_next(&i); dbus_message_iter_recurse(&i, &value_i); if(spa_streq(key, "Percentage")) upower_parse_percentage(this, &value_i); dbus_message_iter_next(&props_i); } } } finish: return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } static int add_filters(struct impl *this) { if (this->filters_added) return 0; if (!dbus_connection_add_filter(this->conn, upower_filter_cb, this, NULL)) { spa_log_error(this->log, "failed to add filter function"); return -EIO; } spa_auto(DBusError) err = DBUS_ERROR_INIT; dbus_bus_add_match(this->conn, "type='signal',sender='org.freedesktop.DBus'," "interface='org.freedesktop.DBus',member='NameOwnerChanged'," "arg0='" UPOWER_SERVICE "'", &err); dbus_bus_add_match(this->conn, "type='signal',sender='" UPOWER_SERVICE "'," "interface='" DBUS_INTERFACE_PROPERTIES "',member='" DBUS_SIGNAL_PROPERTIES_CHANGED "'," "path='" UPOWER_DISPLAY_DEVICE_OBJECT "',arg0='" UPOWER_DEVICE_INTERFACE "'", &err); this->filters_added = true; return 0; } void *upower_register(struct spa_log *log, void *dbus_connection, void (*set_battery_level)(unsigned int level, void *user_data), void *user_data) { struct impl *this; spa_assert(log); spa_assert(dbus_connection); spa_assert(set_battery_level); spa_assert(user_data); this = calloc(1, sizeof(struct impl)); if (this == NULL) return NULL; this->log = log; this->conn = dbus_connection; this->set_battery_level = set_battery_level; this->user_data = user_data; if (add_filters(this) < 0) goto fail4; if (update_battery_percentage(this) < 0) goto fail4; return this; fail4: free(this); return NULL; } void upower_unregister(void *data) { struct impl *this = data; cancel_and_unref(&this->pending_get_call); if (this->filters_added) { dbus_connection_remove_filter(this->conn, upower_filter_cb, this); this->filters_added = false; } free(this); } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/bluez5/upower.h000066400000000000000000000007141511204443500256440ustar00rootroot00000000000000/* Spa Bluez5 UPower proxy */ /* SPDX-FileCopyrightText: Copyright © 2022 Collabora */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_BLUEZ5_UPOWER_H_ #define SPA_BLUEZ5_UPOWER_H_ #include "defs.h" void *upower_register(struct spa_log *log, void *dbus_connection, void (*set_battery_level)(unsigned int level, void *user_data), void *user_data); void upower_unregister(void *data); #endif pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/control/000077500000000000000000000000001511204443500244225ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/control/meson.build000066400000000000000000000020211511204443500265570ustar00rootroot00000000000000control_sources = [ 'mixer.c', 'plugin.c' ] controllib = shared_library('spa-control', control_sources, dependencies : [ spa_dep, mathlib ], install : true, install_dir : spa_plugindir / 'control') test_inc = include_directories('../test') test_apps = [ 'test-mixer-ump-sort', ] foreach a : test_apps test(a, executable(a, a + '.c', dependencies : [ spa_dep ], include_directories : [ configinc, test_inc ], install_rpath : spa_plugindir / 'control', install : installed_tests_enabled, install_dir : installed_tests_execdir / 'control'), env : [ 'SPA_PLUGIN_DIR=@0@'.format(spa_dep.get_variable('plugindir')), ]) if installed_tests_enabled test_conf = configuration_data() test_conf.set('exec', installed_tests_execdir / 'control' / a) configure_file( input: installed_tests_template, output: a + '.test', install_dir: installed_tests_metadir / 'control', configuration: test_conf ) endif endforeach pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/control/mixer.c000066400000000000000000000653651511204443500257310ustar00rootroot00000000000000/* Spa */ /* SPDX-FileCopyrightText: Copyright © 2019 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #undef SPA_LOG_TOPIC_DEFAULT #define SPA_LOG_TOPIC_DEFAULT &log_topic SPA_LOG_TOPIC_DEFINE_STATIC(log_topic, "spa.control-mixer"); #define MAX_BUFFERS 64 #define MAX_PORTS 512 struct buffer { uint32_t id; #define BUFFER_FLAG_QUEUED (1 << 0) uint32_t flags; struct spa_list link; struct spa_buffer *buffer; }; struct port { struct spa_list link; uint32_t direction; uint32_t id; struct spa_io_buffers *io[2]; uint64_t info_all; struct spa_port_info info; struct spa_param_info params[8]; unsigned int have_format:1; uint32_t types; struct buffer buffers[MAX_BUFFERS]; uint32_t n_buffers; struct spa_list queue; struct spa_list mix_link; bool active; struct spa_pod_parser parser; struct spa_pod_frame frame; struct spa_pod_control control; const void *control_body; }; struct impl { struct spa_handle handle; struct spa_node node; uint32_t quantum_limit; struct spa_log *log; struct spa_loop *data_loop; uint64_t info_all; struct spa_node_info info; struct spa_param_info params[8]; struct spa_io_position *position; struct spa_hook_list hooks; struct port *in_ports[MAX_PORTS]; struct port out_ports[1]; struct spa_list port_list; struct spa_list free_list; int n_formats; unsigned int have_format:1; unsigned int started:1; struct spa_list mix_list; struct port *mix_ports[MAX_PORTS]; }; #define CHECK_ANY_IN(this,d,p) ((d) == SPA_DIRECTION_INPUT && (p) == SPA_ID_INVALID) #define CHECK_FREE_IN_PORT(this,d,p) ((d) == SPA_DIRECTION_INPUT && (p) < MAX_PORTS && this->in_ports[(p)] == NULL) #define CHECK_IN_PORT(this,d,p) ((d) == SPA_DIRECTION_INPUT && (p) < MAX_PORTS && this->in_ports[(p)] != NULL) #define CHECK_OUT_PORT(this,d,p) ((d) == SPA_DIRECTION_OUTPUT && (p) == 0) #define CHECK_PORT(this,d,p) (CHECK_OUT_PORT(this,d,p) || CHECK_IN_PORT (this,d,p)) #define CHECK_PORT_ANY(this,d,p) (CHECK_ANY_IN(this,d,p) || CHECK_PORT(this,d,p)) #define GET_IN_PORT(this,p) (this->in_ports[p]) #define GET_OUT_PORT(this,p) (&this->out_ports[p]) #define GET_PORT(this,d,p) (d == SPA_DIRECTION_INPUT ? GET_IN_PORT(this,p) : GET_OUT_PORT(this,p)) #define GET_PORT_ANY(this,d,p) (CHECK_ANY_IN(this,d,p) ? NULL : GET_PORT(this,d,p)) static int impl_node_enum_params(void *object, int seq, uint32_t id, uint32_t start, uint32_t num, const struct spa_pod *filter) { return -ENOTSUP; } static int impl_node_set_param(void *object, uint32_t id, uint32_t flags, const struct spa_pod *param) { return -ENOTSUP; } static int impl_node_set_io(void *object, uint32_t id, void *data, size_t size) { struct impl *this = object; switch (id) { case SPA_IO_Position: this->position = data; break; default: return -ENOTSUP; } return 0; } static int impl_node_send_command(void *object, const struct spa_command *command) { struct impl *this = object; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(command != NULL, -EINVAL); switch (SPA_NODE_COMMAND_ID(command)) { case SPA_NODE_COMMAND_Start: this->started = true; break; case SPA_NODE_COMMAND_Pause: this->started = false; break; default: return -ENOTSUP; } return 0; } static void emit_node_info(struct impl *this, bool full) { uint64_t old = full ? this->info.change_mask : 0; if (full) this->info.change_mask = this->info_all; if (this->info.change_mask) { spa_node_emit_info(&this->hooks, &this->info); this->info.change_mask = old; } } static void emit_port_info(struct impl *this, struct port *port, bool full) { uint64_t old = full ? port->info.change_mask : 0; if (full) port->info.change_mask = port->info_all; if (port->info.change_mask) { spa_node_emit_port_info(&this->hooks, port->direction, port->id, &port->info); port->info.change_mask = old; } } static int impl_node_add_listener(void *object, struct spa_hook *listener, const struct spa_node_events *events, void *data) { struct impl *this = object; struct spa_hook_list save; struct port *port; spa_return_val_if_fail(this != NULL, -EINVAL); spa_hook_list_isolate(&this->hooks, &save, listener, events, data); emit_node_info(this, true); emit_port_info(this, GET_OUT_PORT(this, 0), true); spa_list_for_each(port, &this->port_list, link) emit_port_info(this, port, true); spa_hook_list_join(&this->hooks, &save); return 0; } static int impl_node_set_callbacks(void *object, const struct spa_node_callbacks *callbacks, void *user_data) { return 0; } static struct port *get_free_port(struct impl *this) { struct port *port; if (!spa_list_is_empty(&this->free_list)) { port = spa_list_first(&this->free_list, struct port, link); spa_list_remove(&port->link); } else { port = calloc(1, sizeof(struct port)); } return port; } static int impl_node_add_port(void *object, enum spa_direction direction, uint32_t port_id, const struct spa_dict *props) { struct impl *this = object; struct port *port; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(CHECK_FREE_IN_PORT(this, direction, port_id), -EINVAL); port = GET_IN_PORT (this, port_id); if ((port = get_free_port(this)) == NULL) return -errno; port->direction = direction; port->id = port_id; spa_list_init(&port->queue); port->info_all = SPA_PORT_CHANGE_MASK_FLAGS | SPA_PORT_CHANGE_MASK_PARAMS; port->info = SPA_PORT_INFO_INIT(); port->info.flags = SPA_PORT_FLAG_NO_REF | SPA_PORT_FLAG_DYNAMIC_DATA | SPA_PORT_FLAG_REMOVABLE | SPA_PORT_FLAG_OPTIONAL; port->params[0] = SPA_PARAM_INFO(SPA_PARAM_EnumFormat, SPA_PARAM_INFO_READ); port->params[1] = SPA_PARAM_INFO(SPA_PARAM_Meta, SPA_PARAM_INFO_READ); port->params[2] = SPA_PARAM_INFO(SPA_PARAM_IO, SPA_PARAM_INFO_READ); port->params[3] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); port->params[4] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); port->info.params = port->params; port->info.n_params = 5; this->in_ports[port_id] = port; spa_list_append(&this->port_list, &port->link); spa_log_debug(this->log, "%p: add port %d:%d", this, direction, port_id); emit_port_info(this, port, true); return 0; } static int impl_node_remove_port(void *object, enum spa_direction direction, uint32_t port_id) { struct impl *this = object; struct port *port; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(CHECK_IN_PORT(this, direction, port_id), -EINVAL); port = GET_IN_PORT (this, port_id); this->in_ports[port_id] = NULL; spa_list_remove(&port->link); if (port->have_format && this->have_format) { if (--this->n_formats == 0) this->have_format = false; } spa_memzero(port, sizeof(struct port)); spa_list_append(&this->free_list, &port->link); spa_log_debug(this->log, "%p: remove port %d:%d", this, direction, port_id); spa_node_emit_port_info(&this->hooks, direction, port_id, NULL); return 0; } static int port_enum_formats(void *object, struct port *port, uint32_t index, struct spa_pod **param, struct spa_pod_builder *builder) { switch (index) { case 0: *param = spa_pod_builder_add_object(builder, SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_application), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_control)); break; default: return 0; } return 1; } static int impl_node_port_enum_params(void *object, int seq, enum spa_direction direction, uint32_t port_id, uint32_t id, uint32_t start, uint32_t num, const struct spa_pod *filter) { struct impl *this = object; struct port *port; struct spa_pod *param; struct spa_pod_builder b = { 0 }; uint8_t buffer[1024]; struct spa_result_node_params result; uint32_t count = 0; int res; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(num != 0, -EINVAL); spa_return_val_if_fail(CHECK_PORT_ANY(this, direction, port_id), -EINVAL); port = GET_PORT_ANY(this, direction, port_id); result.id = id; result.next = start; next: result.index = result.next++; spa_pod_builder_init(&b, buffer, sizeof(buffer)); switch (id) { case SPA_PARAM_EnumFormat: if ((res = port_enum_formats(this, port, result.index, ¶m, &b)) <= 0) return res; break; case SPA_PARAM_Format: if (port == NULL || !port->have_format) return -EIO; if ((res = port_enum_formats(this, port, result.index, ¶m, &b)) <= 0) return res; break; case SPA_PARAM_Buffers: if (port == NULL || !port->have_format) return -EIO; if (result.index > 0) return 0; param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamBuffers, id, SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(1, 1, MAX_BUFFERS), SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(1), SPA_PARAM_BUFFERS_size, SPA_POD_CHOICE_RANGE_Int(this->quantum_limit, this->quantum_limit, INT32_MAX), SPA_PARAM_BUFFERS_stride, SPA_POD_Int(1)); break; case SPA_PARAM_IO: switch (result.index) { case 0: param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamIO, id, SPA_PARAM_IO_id, SPA_POD_Id(SPA_IO_Buffers), SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_buffers))); break; case 1: param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamIO, id, SPA_PARAM_IO_id, SPA_POD_Id(SPA_IO_AsyncBuffers), SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_async_buffers))); break; default: return 0; } break; default: return -ENOENT; } if (spa_pod_filter(&b, &result.param, param, filter) < 0) goto next; spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); if (++count != num) goto next; return 0; } static int clear_buffers(struct impl *this, struct port *port) { if (port->n_buffers > 0) { spa_log_debug(this->log, "%p: clear buffers %p", this, port); port->n_buffers = 0; spa_list_init(&port->queue); } return 0; } static int queue_buffer(struct impl *this, struct port *port, struct buffer *b) { if (SPA_FLAG_IS_SET(b->flags, BUFFER_FLAG_QUEUED)) return -EINVAL; spa_list_append(&port->queue, &b->link); SPA_FLAG_SET(b->flags, BUFFER_FLAG_QUEUED); spa_log_trace_fp(this->log, "%p: queue buffer %d", this, b->id); return 0; } static struct buffer *dequeue_buffer(struct impl *this, struct port *port) { struct buffer *b; if (spa_list_is_empty(&port->queue)) return NULL; b = spa_list_first(&port->queue, struct buffer, link); spa_list_remove(&b->link); SPA_FLAG_CLEAR(b->flags, BUFFER_FLAG_QUEUED); spa_log_trace_fp(this->log, "%p: dequeue buffer %d", this, b->id); return b; } static int port_set_format(void *object, enum spa_direction direction, uint32_t port_id, uint32_t flags, const struct spa_pod *format) { struct impl *this = object; struct port *port; int res; port = GET_PORT(this, direction, port_id); spa_return_val_if_fail(!this->started || port->io == NULL, -EIO); if (format == NULL) { if (port->have_format) { port->have_format = false; if (--this->n_formats == 0) this->have_format = false; clear_buffers(this, port); } } else { uint32_t media_type, media_subtype, types = 0; if ((res = spa_format_parse(format, &media_type, &media_subtype)) < 0) return res; if (media_type != SPA_MEDIA_TYPE_application || media_subtype != SPA_MEDIA_SUBTYPE_control) return -EINVAL; if ((res = spa_pod_parse_object(format, SPA_TYPE_OBJECT_Format, NULL, SPA_FORMAT_CONTROL_types, SPA_POD_OPT_Int(&types))) < 0) return res; this->have_format = true; if (!port->have_format) { this->n_formats++; port->have_format = true; port->types = types; spa_log_debug(this->log, "%p: set format on port %d:%d", this, direction, port_id); } } port->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; if (port->have_format) { port->params[3] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_READWRITE); port->params[4] = SPA_PARAM_INFO(SPA_PARAM_Buffers, SPA_PARAM_INFO_READ); } else { port->params[3] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); port->params[4] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); } emit_port_info(this, port, false); return 0; } static int impl_node_port_set_param(void *object, enum spa_direction direction, uint32_t port_id, uint32_t id, uint32_t flags, const struct spa_pod *param) { struct impl *this = object; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); if (id == SPA_PARAM_Format) { return port_set_format(this, direction, port_id, flags, param); } else return -ENOENT; } static int impl_node_port_use_buffers(void *object, enum spa_direction direction, uint32_t port_id, uint32_t flags, struct spa_buffer **buffers, uint32_t n_buffers) { struct impl *this = object; struct port *port; uint32_t i; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); port = GET_PORT(this, direction, port_id); spa_log_debug(this->log, "%p: use buffers %d on port %d:%d", this, n_buffers, direction, port_id); spa_return_val_if_fail(!this->started || port->io == NULL, -EIO); clear_buffers(this, port); if (n_buffers > 0 && !port->have_format) return -EIO; if (n_buffers > MAX_BUFFERS) return -ENOSPC; for (i = 0; i < n_buffers; i++) { struct buffer *b; struct spa_data *d = buffers[i]->datas; b = &port->buffers[i]; b->buffer = buffers[i]; b->flags = 0; b->id = i; if (d[0].data == NULL) { spa_log_error(this->log, "%p: invalid memory on buffer %d", this, i); return -EINVAL; } if (direction == SPA_DIRECTION_OUTPUT) queue_buffer(this, port, b); } port->n_buffers = n_buffers; return 0; } struct io_info { struct impl *impl; struct port *port; void *data; size_t size; }; static int do_port_set_io(struct spa_loop *loop, bool async, uint32_t seq, const void *data, size_t size, void *user_data) { struct io_info *info = user_data; struct port *port = info->port; if (info->data == NULL || info->size < sizeof(struct spa_io_buffers)) { port->io[0] = NULL; port->io[1] = NULL; if (port->active) { spa_list_remove(&port->mix_link); port->active = false; } } else { if (info->size >= sizeof(struct spa_io_async_buffers)) { struct spa_io_async_buffers *ab = info->data; port->io[0] = &ab->buffers[port->direction]; port->io[1] = &ab->buffers[port->direction^1]; } else { port->io[0] = info->data; port->io[1] = info->data; } if (!port->active) { spa_list_append(&info->impl->mix_list, &port->mix_link); port->active = true; } } return 0; } static int impl_node_port_set_io(void *object, enum spa_direction direction, uint32_t port_id, uint32_t id, void *data, size_t size) { struct impl *this = object; struct port *port; struct io_info info; spa_return_val_if_fail(this != NULL, -EINVAL); spa_log_debug(this->log, "%p: port %d:%d io %d %p/%zd", this, direction, port_id, id, data, size); spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); port = GET_PORT(this, direction, port_id); info.impl = this; info.port = port; info.data = data; info.size = size; switch (id) { case SPA_IO_Buffers: case SPA_IO_AsyncBuffers: spa_loop_locked(this->data_loop, do_port_set_io, SPA_ID_INVALID, NULL, 0, &info); break; default: return -ENOENT; } return 0; } static int impl_node_port_reuse_buffer(void *object, uint32_t port_id, uint32_t buffer_id) { struct impl *this = object; struct port *port; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(CHECK_PORT(this, SPA_DIRECTION_OUTPUT, port_id), -EINVAL); port = GET_OUT_PORT(this, 0); if (buffer_id >= port->n_buffers) return -EINVAL; return queue_buffer(this, port, &port->buffers[buffer_id]); } static inline int event_compare(uint8_t s1, uint8_t s2) { /* 11 (controller) > 12 (program change) > * 8 (note off) > 9 (note on) > 10 (aftertouch) > * 13 (channel pressure) > 14 (pitch bend) */ static int priotab[] = { 5,4,3,7,6,2,1,0 }; if ((s1 & 0xf) != (s2 & 0xf)) return 0; return priotab[(s2>>4) & 7] - priotab[(s1>>4) & 7]; } static inline int event_sort(struct spa_pod_control *a, const void *abody, struct spa_pod_control *b, const void *bbody) { if (a->offset < b->offset) return -1; if (a->offset > b->offset) return 1; if (a->type != b->type) return 0; switch(a->type) { case SPA_CONTROL_Midi: { const uint8_t *da = abody, *db = bbody; if (SPA_POD_BODY_SIZE(&a->value) < 1 || SPA_POD_BODY_SIZE(&b->value) < 1) return 0; return event_compare(da[0], db[0]); } case SPA_CONTROL_UMP: { const uint32_t *da = abody, *db = bbody; if (SPA_POD_BODY_SIZE(&a->value) < 4 || SPA_POD_BODY_SIZE(&b->value) < 4) return 0; if (((da[0] >> 28) != 2 && (da[0] >> 28) != 4) || ((db[0] >> 28) != 2 && (db[0] >> 28) != 4)) return 0; return event_compare(da[0] >> 16, db[0] >> 16); } default: return 0; } } static inline bool control_needs_conversion(struct port *port, uint32_t type) { /* we only converter between midi and UMP and only when the port * does not support the current type */ return (type == SPA_CONTROL_Midi || type == SPA_CONTROL_UMP) && port->types && (port->types & (1u << type)) == 0; } static int impl_node_process(void *object) { struct impl *this = object; struct port *outport, *inport; struct spa_io_buffers *outio; struct spa_pod_builder builder; uint32_t n_mix_ports; struct port **mix_ports; struct spa_pod_frame f; struct buffer *outb; struct spa_data *d; uint32_t cycle = this->position->clock.cycle & 1; spa_return_val_if_fail(this != NULL, -EINVAL); outport = GET_OUT_PORT(this, 0); if ((outio = outport->io[cycle]) == NULL) return -EIO; spa_log_trace_fp(this->log, "%p: status %p %d %d", this, outio, outio->status, outio->buffer_id); if (outio->status == SPA_STATUS_HAVE_DATA) return outio->status; /* recycle */ if (outio->buffer_id < outport->n_buffers) { queue_buffer(this, outport, &outport->buffers[outio->buffer_id]); outio->buffer_id = SPA_ID_INVALID; } /* get output buffer */ if ((outb = dequeue_buffer(this, outport)) == NULL) { if (outport->n_buffers > 0) spa_log_warn(this->log, "%p: out of buffers (%d)", this, outport->n_buffers); return -EPIPE; } mix_ports = this->mix_ports; n_mix_ports = 0; /* collect all sequence pod on input ports */ spa_list_for_each(inport, &this->mix_list, mix_link) { struct spa_io_buffers *inio = inport->io[cycle]; struct spa_pod_sequence seq; const void *seq_body; if (inio->buffer_id >= inport->n_buffers || inio->status != SPA_STATUS_HAVE_DATA) { spa_log_trace_fp(this->log, "%p: skip input idx:%d " "io:%p status:%d buf_id:%d n_buffers:%d", this, inport->id, inio, inio->status, inio->buffer_id, inport->n_buffers); continue; } spa_log_trace_fp(this->log, "%p: mix input %d %p->%p %d %d", this, inport->id, inio, outio, inio->status, inio->buffer_id); d = inport->buffers[inio->buffer_id].buffer->datas; spa_pod_parser_init_from_data(&inport->parser, d->data, d->maxsize, d->chunk->offset, d->chunk->size); if (spa_pod_parser_push_sequence_body(&inport->parser, &inport->frame, &seq, &seq_body) < 0) { spa_log_trace_fp(this->log, "%p: skip input idx:%d max:%u " "offset:%u size:%u", this, inport->id, d->maxsize, d->chunk->offset, d->chunk->size); continue; } if (spa_pod_parser_get_control_body(&inport->parser, &inport->control, &inport->control_body) < 0) { spa_log_trace_fp(this->log, "%p: skip input idx:%d %u", this, inport->id, inport->parser.state.offset); continue; } mix_ports[n_mix_ports++] = inport; inio->status = SPA_STATUS_NEED_DATA; } d = outb->buffer->datas; /* prepare to write into output */ spa_pod_builder_init(&builder, d->data, d->maxsize); spa_pod_builder_push_sequence(&builder, &f, 0); /* merge sort all sequences into output buffer */ while (true) { uint32_t i, next_index = 0; size_t size; uint8_t *body; struct port *next = NULL; struct spa_pod_control *control; for (i = 0; i < n_mix_ports; i++) { struct port *p = mix_ports[i]; if (next == NULL || event_sort(&p->control, p->control_body, &next->control, next->control_body) <= 0) { next = p; next_index = i; } } if (next == NULL) break; control = &next->control; body = (uint8_t*)next->control_body; size = SPA_POD_BODY_SIZE(&control->value); if (control_needs_conversion(outport, control->type)) { switch (control->type) { case SPA_CONTROL_Midi: { uint32_t ump[4]; uint64_t state = 0; while (size > 0) { int ump_size = spa_ump_from_midi(&body, &size, ump, sizeof(ump), 0, &state); if (ump_size <= 0) break; spa_pod_builder_control(&builder, control->offset, SPA_CONTROL_UMP); spa_pod_builder_bytes(&builder, ump, ump_size); } break; } case SPA_CONTROL_UMP: { uint8_t ev[8]; const uint32_t *ump = (const uint32_t*)body; uint64_t state = 0; while (size > 0) { int ev_size = spa_ump_to_midi(&ump, &size, ev, sizeof(ev), &state); if (ev_size <= 0) break; spa_pod_builder_control(&builder, control->offset, SPA_CONTROL_Midi); spa_pod_builder_bytes(&builder, ev, ev_size); } break; } } } else { spa_pod_builder_control(&builder, control->offset, control->type); spa_pod_builder_primitive_body(&builder, &control->value, body, size, NULL, 0); } if (spa_pod_parser_get_control_body(&next->parser, &next->control, &next->control_body) < 0) { spa_pod_parser_pop(&next->parser, &next->frame); mix_ports[next_index] = mix_ports[--n_mix_ports]; } } spa_pod_builder_pop(&builder, &f); if (builder.state.offset > d->maxsize) { spa_log_warn(this->log, "%p: control overflow %d > %d", this, builder.state.offset, d->maxsize); builder.state.offset = 0; } d->chunk->offset = 0; d->chunk->size = builder.state.offset; d->chunk->stride = 1; d->chunk->flags = 0; outio->buffer_id = outb->id; outio->status = SPA_STATUS_HAVE_DATA; return SPA_STATUS_HAVE_DATA | SPA_STATUS_NEED_DATA; } static const struct spa_node_methods impl_node = { SPA_VERSION_NODE_METHODS, .add_listener = impl_node_add_listener, .set_callbacks = impl_node_set_callbacks, .enum_params = impl_node_enum_params, .set_param = impl_node_set_param, .set_io = impl_node_set_io, .send_command = impl_node_send_command, .add_port = impl_node_add_port, .remove_port = impl_node_remove_port, .port_enum_params = impl_node_port_enum_params, .port_set_param = impl_node_port_set_param, .port_use_buffers = impl_node_port_use_buffers, .port_set_io = impl_node_port_set_io, .port_reuse_buffer = impl_node_port_reuse_buffer, .process = impl_node_process, }; static int impl_get_interface(struct spa_handle *handle, const char *type, void **interface) { struct impl *this; spa_return_val_if_fail(handle != NULL, -EINVAL); spa_return_val_if_fail(interface != NULL, -EINVAL); this = (struct impl *) handle; if (spa_streq(type, SPA_TYPE_INTERFACE_Node)) *interface = &this->node; else return -ENOENT; return 0; } static int impl_clear(struct spa_handle *handle) { struct impl *this; struct port *port; spa_return_val_if_fail(handle != NULL, -EINVAL); this = (struct impl *) handle; spa_list_insert_list(&this->free_list, &this->port_list); spa_list_consume(port, &this->free_list, link) { spa_list_remove(&port->link); free(port); } return 0; } static size_t impl_get_size(const struct spa_handle_factory *factory, const struct spa_dict *params) { return sizeof(struct impl); } static int impl_init(const struct spa_handle_factory *factory, struct spa_handle *handle, const struct spa_dict *info, const struct spa_support *support, uint32_t n_support) { struct impl *this; struct port *port; uint32_t i; spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(handle != NULL, -EINVAL); handle->get_interface = impl_get_interface; handle->clear = impl_clear; this = (struct impl *) handle; this->log = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log); this->data_loop = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_DataLoop); if (this->data_loop == NULL) { spa_log_error(this->log, "a data loop is needed"); return -EINVAL; } this->quantum_limit = 8192; for (i = 0; info && i < info->n_items; i++) { const char *k = info->items[i].key; const char *s = info->items[i].value; if (spa_streq(k, "clock.quantum-limit")) { spa_atou32(s, &this->quantum_limit, 0); } } spa_hook_list_init(&this->hooks); spa_list_init(&this->port_list); spa_list_init(&this->free_list); spa_list_init(&this->mix_list); this->node.iface = SPA_INTERFACE_INIT( SPA_TYPE_INTERFACE_Node, SPA_VERSION_NODE, &impl_node, this); this->info = SPA_NODE_INFO_INIT(); this->info.max_input_ports = MAX_PORTS; this->info.max_output_ports = 1; this->info.change_mask |= SPA_NODE_CHANGE_MASK_FLAGS; this->info.flags = SPA_NODE_FLAG_RT | SPA_NODE_FLAG_IN_DYNAMIC_PORTS; port = GET_OUT_PORT(this, 0); port->direction = SPA_DIRECTION_OUTPUT; port->id = 0; port->info = SPA_PORT_INFO_INIT(); port->info.change_mask |= SPA_PORT_CHANGE_MASK_FLAGS; port->info.flags = SPA_PORT_FLAG_DYNAMIC_DATA; port->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; port->params[0] = SPA_PARAM_INFO(SPA_PARAM_EnumFormat, SPA_PARAM_INFO_READ); port->params[1] = SPA_PARAM_INFO(SPA_PARAM_Meta, SPA_PARAM_INFO_READ); port->params[2] = SPA_PARAM_INFO(SPA_PARAM_IO, SPA_PARAM_INFO_READ); port->params[3] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); port->params[4] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); port->info.params = port->params; port->info.n_params = 5; spa_list_init(&port->queue); return 0; } static const struct spa_interface_info impl_interfaces[] = { {SPA_TYPE_INTERFACE_Node,}, }; static int impl_enum_interface_info(const struct spa_handle_factory *factory, const struct spa_interface_info **info, uint32_t *index) { spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(info != NULL, -EINVAL); spa_return_val_if_fail(index != NULL, -EINVAL); switch (*index) { case 0: *info = &impl_interfaces[*index]; break; default: return 0; } (*index)++; return 1; } const struct spa_handle_factory spa_control_mixer_factory = { SPA_VERSION_HANDLE_FACTORY, SPA_NAME_CONTROL_MIXER, NULL, impl_get_size, impl_init, impl_enum_interface_info, }; pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/control/plugin.c000066400000000000000000000012001511204443500260550ustar00rootroot00000000000000/* Spa Control plugin */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include extern const struct spa_handle_factory spa_control_mixer_factory; SPA_LOG_TOPIC_ENUM_DEFINE_REGISTERED; SPA_EXPORT int spa_handle_factory_enum(const struct spa_handle_factory **factory, uint32_t *index) { spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(index != NULL, -EINVAL); switch (*index) { case 0: *factory = &spa_control_mixer_factory; break; default: return 0; } (*index)++; return 1; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/control/test-mixer-ump-sort.c000066400000000000000000000123201511204443500304510ustar00rootroot00000000000000/* PipeWire */ /* SPDX-FileCopyrightText: Copyright © 2025 Claude Code */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #include #include #include /* Include the mixer source to access static inline functions */ #include "mixer.c" /* Simple test framework macros */ #define TEST_ASSERT(cond, msg) \ do { \ if (!(cond)) { \ fprintf(stderr, "FAIL: %s\n", msg); \ return 1; \ } \ } while (0) #define TEST_PASS() \ do { \ printf("PASS\n"); \ return 0; \ } while (0) /* Helper to create mock control structures for testing */ static struct spa_pod_control *create_mock_control(uint8_t *buffer, size_t *offset, uint64_t timestamp, uint32_t type, const void *data, size_t data_size) { struct spa_pod_control *control = (struct spa_pod_control *)(buffer + *offset); control->offset = timestamp; control->type = type; control->value.size = data_size; control->value.type = SPA_TYPE_Bytes; /* Copy data after the control structure */ memcpy(buffer + *offset + sizeof(struct spa_pod_control), data, data_size); *offset += sizeof(struct spa_pod_control) + SPA_ROUND_UP_N(data_size, 8); return control; } static int test_ump_event_sort_offset_priority(void) { uint8_t buffer[1024]; size_t offset = 0; uint32_t ump_early = 0x20904060; /* Note On Ch 0 */ uint32_t ump_late = 0x20904060; /* Note On Ch 0 */ struct spa_pod_control *a = create_mock_control(buffer, &offset, 100, SPA_CONTROL_UMP, &ump_early, 4); const void *abody = (uint8_t*)a + sizeof(struct spa_pod_control); struct spa_pod_control *b = create_mock_control(buffer, &offset, 200, SPA_CONTROL_UMP, &ump_late, 4); const void *bbody = (uint8_t*)b + sizeof(struct spa_pod_control); /* Earlier offset should sort before later offset */ TEST_ASSERT(event_sort(a, abody, b, bbody) < 0, "Earlier offset should sort before later offset"); /* Later offset should sort after earlier offset */ TEST_ASSERT(event_sort(b, bbody, a, abody) > 0, "Later offset should sort after earlier offset"); TEST_PASS(); } static int test_ump_event_sort_same_offset_different_channels(void) { uint8_t buffer[1024]; size_t offset = 0; uint32_t ump_ch0 = 0x20904060; /* Note On Ch 0 */ uint32_t ump_ch1 = 0x20914060; /* Note On Ch 1 */ struct spa_pod_control *a = create_mock_control(buffer, &offset, 100, SPA_CONTROL_UMP, &ump_ch0, 4); const void *abody = (uint8_t*)a + sizeof(struct spa_pod_control); struct spa_pod_control *b = create_mock_control(buffer, &offset, 100, SPA_CONTROL_UMP, &ump_ch1, 4); const void *bbody = (uint8_t*)b + sizeof(struct spa_pod_control); /* Different channels at same offset should return 0 (no preference) */ TEST_ASSERT(event_sort(a, abody, b, bbody) == 0, "Different channels at same offset should return 0"); TEST_ASSERT(event_sort(b, bbody, a, abody) == 0, "Different channels at same offset should return 0"); TEST_PASS(); } static int test_ump_event_sort_priority_controller_vs_note(void) { uint8_t buffer[1024]; size_t offset = 0; uint32_t ump_note_on = 0x20904060; /* Note On Ch 0 (priority 4) */ uint32_t ump_controller = 0x20B04060; /* Controller Ch 0 (priority 2) */ struct spa_pod_control *note_on = create_mock_control(buffer, &offset, 100, SPA_CONTROL_UMP, &ump_note_on, 4); const void *note_body = (uint8_t*)note_on + sizeof(struct spa_pod_control); struct spa_pod_control *controller = create_mock_control(buffer, &offset, 100, SPA_CONTROL_UMP, &ump_controller, 4); const void *ctrl_body = (uint8_t*)controller + sizeof(struct spa_pod_control); /* Controller (higher priority) should sort before Note On (lower priority) */ TEST_ASSERT(event_sort(note_on, note_body, controller, ctrl_body) > 0, "Controller should sort before Note On"); TEST_ASSERT(event_sort(controller, ctrl_body, note_on, note_body) <= 0, "Controller should sort before Note On"); TEST_PASS(); } static int test_event_compare_priority_table(void) { /* Test controller (0xB0) vs note on (0x90) on same channel */ TEST_ASSERT(event_compare(0x90, 0xB0) > 0, "Controller has higher priority than Note On"); TEST_ASSERT(event_compare(0xB0, 0x90) < 0, "Controller has higher priority than Note On"); /* Test program change (0xC0) vs note off (0x80) on same channel */ TEST_ASSERT(event_compare(0x80, 0xC0) > 0, "Program change has higher priority than Note Off"); TEST_ASSERT(event_compare(0xC0, 0x80) < 0, "Program change has higher priority than Note Off"); /* Test different channels should return 0 */ TEST_ASSERT(event_compare(0x90, 0x91) == 0, "Different channels should return 0"); TEST_PASS(); } int main(void) { int result = 0; printf("Running mixer UMP sort tests...\n"); printf("test_ump_event_sort_offset_priority: "); result |= test_ump_event_sort_offset_priority(); printf("test_ump_event_sort_same_offset_different_channels: "); result |= test_ump_event_sort_same_offset_different_channels(); printf("test_ump_event_sort_priority_controller_vs_note: "); result |= test_ump_event_sort_priority_controller_vs_note(); printf("test_event_compare_priority_table: "); result |= test_event_compare_priority_table(); if (result == 0) { printf("All tests passed!\n"); } else { printf("Some tests failed!\n"); } return result; }pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/ffmpeg/000077500000000000000000000000001511204443500242065ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/ffmpeg/ffmpeg-dec.c000066400000000000000000000260311511204443500263510ustar00rootroot00000000000000/* Spa FFmpeg decoder */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ffmpeg.h" #undef SPA_LOG_TOPIC_DEFAULT #define SPA_LOG_TOPIC_DEFAULT &log_topic SPA_LOG_TOPIC_DEFINE_STATIC(log_topic, "spa.ffmpeg.dec"); #define IS_VALID_PORT(this,d,id) ((id) == 0) #define GET_IN_PORT(this,p) (&this->in_ports[p]) #define GET_OUT_PORT(this,p) (&this->out_ports[p]) #define GET_PORT(this,d,p) (d == SPA_DIRECTION_INPUT ? GET_IN_PORT(this,p) : GET_OUT_PORT(this,p)) #define MAX_BUFFERS 32 struct buffer { uint32_t id; uint32_t flags; struct spa_buffer *outbuf; struct spa_list link; }; struct port { enum spa_direction direction; uint32_t id; uint64_t info_all; struct spa_port_info info; struct spa_param_info params[8]; struct spa_video_info current_format; unsigned int have_format:1; struct buffer buffers[MAX_BUFFERS]; uint32_t n_buffers; struct spa_io_buffers *io; struct spa_list free; struct spa_list ready; }; struct impl { struct spa_handle handle; struct spa_node node; struct spa_log *log; uint64_t info_all; struct spa_node_info info; struct spa_param_info params[2]; struct spa_hook_list hooks; struct port in_ports[1]; struct port out_ports[1]; bool started; }; static int impl_node_enum_params(void *object, int seq, uint32_t id, uint32_t start, uint32_t num, const struct spa_pod *filter) { return -ENOTSUP; } static int impl_node_set_param(void *object, uint32_t id, uint32_t flags, const struct spa_pod *param) { return -ENOTSUP; } static int impl_node_set_io(void *object, uint32_t id, void *data, size_t size) { return -ENOTSUP; } static int impl_node_send_command(void *object, const struct spa_command *command) { struct impl *this = object; if (this == NULL || command == NULL) return -EINVAL; switch (SPA_NODE_COMMAND_ID(command)) { case SPA_NODE_COMMAND_Start: this->started = true; break; case SPA_NODE_COMMAND_Pause: this->started = false; break; default: return -ENOTSUP; } return 0; } static void emit_node_info(struct impl *this, bool full) { uint64_t old = full ? this->info.change_mask : 0; if (full) this->info.change_mask = this->info_all; if (this->info.change_mask) { spa_node_emit_info(&this->hooks, &this->info); this->info.change_mask = old; } } static void emit_port_info(struct impl *this, struct port *port, bool full) { uint64_t old = full ? port->info.change_mask : 0; if (full) port->info.change_mask = port->info_all; if (port->info.change_mask) { spa_node_emit_port_info(&this->hooks, port->direction, port->id, &port->info); port->info.change_mask = old; } } static int impl_node_add_listener(void *object, struct spa_hook *listener, const struct spa_node_events *events, void *data) { struct impl *this = object; struct spa_hook_list save; spa_return_val_if_fail(this != NULL, -EINVAL); spa_hook_list_isolate(&this->hooks, &save, listener, events, data); emit_node_info(this, true); emit_port_info(this, GET_IN_PORT(this, 0), true); emit_port_info(this, GET_OUT_PORT(this, 0), true); spa_hook_list_join(&this->hooks, &save); return 0; } static int impl_node_set_callbacks(void *object, const struct spa_node_callbacks *callbacks, void *user_data) { return 0; } static int impl_node_add_port(void *object, enum spa_direction direction, uint32_t port_id, const struct spa_dict *props) { return -ENOTSUP; } static int impl_node_remove_port(void *object, enum spa_direction direction, uint32_t port_id) { return -ENOTSUP; } static int port_enum_formats(void *object, enum spa_direction direction, uint32_t port_id, uint32_t index, const struct spa_pod *filter, struct spa_pod **param, struct spa_pod_builder *builder) { if (!IS_VALID_PORT(object, direction, port_id)) return -EINVAL; switch (index) { case 0: *param = NULL; break; default: return 0; } return 1; } static int port_get_format(void *object, enum spa_direction direction, uint32_t port_id, uint32_t index, const struct spa_pod *filter, struct spa_pod **param, struct spa_pod_builder *builder) { struct impl *this = object; struct port *port; port = GET_PORT(this, direction, port_id); if (!port->have_format) return -EIO; if (index > 0) return 0; *param = NULL; return 1; } static int impl_node_port_enum_params(void *object, int seq, enum spa_direction direction, uint32_t port_id, uint32_t id, uint32_t start, uint32_t num, const struct spa_pod *filter) { struct impl *this = object; struct spa_pod_builder b = { 0 }; uint8_t buffer[1024]; struct spa_pod *param; struct spa_result_node_params result; uint32_t count = 0; int res; result.id = id; result.next = start; next: result.index = result.next++; spa_pod_builder_init(&b, buffer, sizeof(buffer)); switch (id) { case SPA_PARAM_EnumFormat: if ((res = port_enum_formats(this, direction, port_id, result.index, filter, ¶m, &b)) <= 0) return res; break; case SPA_PARAM_Format: if ((res = port_get_format(this, direction, port_id, result.index, filter, ¶m, &b)) <= 0) return res; break; default: return -ENOENT; } if (spa_pod_filter(&b, &result.param, param, filter) < 0) goto next; spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); if (++count != num) goto next; return 0; } static int port_set_format(void *object, enum spa_direction direction, uint32_t port_id, uint32_t flags, const struct spa_pod *format) { struct impl *this = object; struct port *port; int res; if (this == NULL || format == NULL) return -EINVAL; if (!IS_VALID_PORT(this, direction, port_id)) return -EINVAL; port = GET_PORT(this, direction, port_id); if (format == NULL) { port->have_format = false; } else { struct spa_video_info info = { 0 }; if ((res = spa_format_parse(format, &info.media_type, &info.media_subtype)) < 0) return res; if (info.media_type != SPA_MEDIA_TYPE_video && info.media_subtype != SPA_MEDIA_SUBTYPE_raw) return -EINVAL; if (spa_format_video_raw_parse(format, &info.info.raw) < 0) return -EINVAL; if (!(flags & SPA_NODE_PARAM_FLAG_TEST_ONLY)) { port->current_format = info; port->have_format = true; } } return 0; } static int impl_node_port_set_param(void *object, enum spa_direction direction, uint32_t port_id, uint32_t id, uint32_t flags, const struct spa_pod *param) { if (id == SPA_PARAM_Format) { return port_set_format(object, direction, port_id, flags, param); } else return -ENOENT; } static int impl_node_port_use_buffers(void *object, enum spa_direction direction, uint32_t port_id, uint32_t flags, struct spa_buffer **buffers, uint32_t n_buffers) { if (object == NULL) return -EINVAL; if (!IS_VALID_PORT(object, direction, port_id)) return -EINVAL; return -ENOTSUP; } static int impl_node_port_set_io(void *object, enum spa_direction direction, uint32_t port_id, uint32_t id, void *data, size_t size) { struct impl *this = object; struct port *port; if (this == NULL) return -EINVAL; if (!IS_VALID_PORT(this, direction, port_id)) return -EINVAL; port = GET_PORT(this, direction, port_id); if (id == SPA_IO_Buffers) port->io = data; else return -ENOENT; return 0; } static int impl_node_process(void *object) { struct impl *this = object; struct port *port; struct spa_io_buffers *output; if (this == NULL) return -EINVAL; port = &this->out_ports[0]; if ((output = port->io) == NULL) return -EIO; if (!port->have_format) { output->status = -EIO; return -EIO; } output->status = SPA_STATUS_OK; return SPA_STATUS_OK; } static int impl_node_port_reuse_buffer(void *object, uint32_t port_id, uint32_t buffer_id) { if (object == NULL) return -EINVAL; if (port_id != 0) return -EINVAL; return -ENOTSUP; } static const struct spa_node_methods impl_node = { SPA_VERSION_NODE_METHODS, .add_listener = impl_node_add_listener, .set_callbacks = impl_node_set_callbacks, .enum_params = impl_node_enum_params, .set_param = impl_node_set_param, .set_io = impl_node_set_io, .send_command = impl_node_send_command, .add_port = impl_node_add_port, .remove_port = impl_node_remove_port, .port_enum_params = impl_node_port_enum_params, .port_set_param = impl_node_port_set_param, .port_use_buffers = impl_node_port_use_buffers, .port_set_io = impl_node_port_set_io, .port_reuse_buffer = impl_node_port_reuse_buffer, .process = impl_node_process, }; static int impl_get_interface(struct spa_handle *handle, const char *type, void **interface) { struct impl *this; if (handle == NULL || interface == NULL) return -EINVAL; this = (struct impl *) handle; if (spa_streq(type, SPA_TYPE_INTERFACE_Node)) *interface = &this->node; else return -ENOENT; return 0; } static int impl_clear(struct spa_handle *handle) { spa_return_val_if_fail(handle != NULL, -EINVAL); return 0; } size_t spa_ffmpeg_dec_get_size(const struct spa_handle_factory *factory, const struct spa_dict *params) { return sizeof(struct impl); } int spa_ffmpeg_dec_init(struct spa_handle *handle, const struct spa_dict *info, const struct spa_support *support, uint32_t n_support) { struct impl *this; struct port *port; handle->get_interface = impl_get_interface; handle->clear = impl_clear; this = (struct impl *) handle; this->log = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log); spa_hook_list_init(&this->hooks); this->node.iface = SPA_INTERFACE_INIT( SPA_TYPE_INTERFACE_Node, SPA_VERSION_NODE, &impl_node, this); this->info_all = SPA_NODE_CHANGE_MASK_FLAGS; this->info = SPA_NODE_INFO_INIT(); this->info.max_input_ports = 1; this->info.max_output_ports = 1; this->info.flags = SPA_NODE_FLAG_RT; this->info.params = this->params; port = GET_IN_PORT(this, 0); port->direction = SPA_DIRECTION_INPUT; port->id = 0; port->info_all = SPA_PORT_CHANGE_MASK_FLAGS | SPA_PORT_CHANGE_MASK_PARAMS; port->info = SPA_PORT_INFO_INIT(); port->info.flags = 0; port->params[0] = SPA_PARAM_INFO(SPA_PARAM_EnumFormat, SPA_PARAM_INFO_READ); port->params[1] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); port->info.params = port->params; port->info.n_params = 2; port = GET_OUT_PORT(this, 0); port->direction = SPA_DIRECTION_OUTPUT; port->id = 0; port->info_all = SPA_PORT_CHANGE_MASK_FLAGS | SPA_PORT_CHANGE_MASK_PARAMS; port->info = SPA_PORT_INFO_INIT(); port->info.flags = 0; port->params[0] = SPA_PARAM_INFO(SPA_PARAM_EnumFormat, SPA_PARAM_INFO_READ); port->params[1] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); port->info.params = port->params; port->info.n_params = 2; return 0; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/ffmpeg/ffmpeg-enc.c000066400000000000000000000253051511204443500263660ustar00rootroot00000000000000/* Spa FFmpeg encoder */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ffmpeg.h" #undef SPA_LOG_TOPIC_DEFAULT #define SPA_LOG_TOPIC_DEFAULT &log_topic SPA_LOG_TOPIC_DEFINE_STATIC(log_topic, "spa.ffmpeg.enc"); #define IS_VALID_PORT(this,d,id) ((id) == 0) #define GET_IN_PORT(this,p) (&this->in_ports[p]) #define GET_OUT_PORT(this,p) (&this->out_ports[p]) #define GET_PORT(this,d,p) (d == SPA_DIRECTION_INPUT ? GET_IN_PORT(this,p) : GET_OUT_PORT(this,p)) #define MAX_BUFFERS 32 struct buffer { uint32_t id; uint32_t flags; struct spa_buffer *outbuf; struct spa_list link; }; struct port { enum spa_direction direction; uint32_t id; uint64_t info_all; struct spa_port_info info; struct spa_param_info params[8]; struct spa_video_info current_format; unsigned int have_format:1; struct buffer buffers[MAX_BUFFERS]; uint32_t n_buffers; struct spa_io_buffers *io; struct spa_list free; struct spa_list ready; }; struct impl { struct spa_handle handle; struct spa_node node; struct spa_log *log; uint64_t info_all; struct spa_node_info info; struct spa_param_info params[2]; struct spa_hook_list hooks; struct port in_ports[1]; struct port out_ports[1]; bool started; }; static int impl_node_enum_params(void *object, int seq, uint32_t id, uint32_t start, uint32_t num, const struct spa_pod *filter) { return -ENOTSUP; } static int impl_node_set_param(void *object, uint32_t id, uint32_t flags, const struct spa_pod *param) { return -ENOTSUP; } static int impl_node_set_io(void *object, uint32_t id, void *data, size_t size) { return -ENOTSUP; } static int impl_node_send_command(void *object, const struct spa_command *command) { struct impl *this = object; if (this == NULL || command == NULL) return -EINVAL; switch (SPA_NODE_COMMAND_ID(command)) { case SPA_NODE_COMMAND_Start: this->started = true; break; case SPA_NODE_COMMAND_Pause: this->started = false; break; default: return -ENOTSUP; } return 0; } static void emit_node_info(struct impl *this, bool full) { uint64_t old = full ? this->info.change_mask : 0; if (full) this->info.change_mask = this->info_all; if (this->info.change_mask) { spa_node_emit_info(&this->hooks, &this->info); this->info.change_mask = old; } } static void emit_port_info(struct impl *this, struct port *port, bool full) { uint64_t old = full ? port->info.change_mask : 0; if (full) port->info.change_mask = port->info_all; if (port->info.change_mask) { spa_node_emit_port_info(&this->hooks, port->direction, port->id, &port->info); port->info.change_mask = old; } } static int impl_node_add_listener(void *object, struct spa_hook *listener, const struct spa_node_events *events, void *data) { struct impl *this = object; struct spa_hook_list save; spa_return_val_if_fail(this != NULL, -EINVAL); spa_hook_list_isolate(&this->hooks, &save, listener, events, data); emit_node_info(this, true); emit_port_info(this, GET_IN_PORT(this, 0), true); emit_port_info(this, GET_OUT_PORT(this, 0), true); spa_hook_list_join(&this->hooks, &save); return 0; } static int impl_node_set_callbacks(void *object, const struct spa_node_callbacks *callbacks, void *user_data) { return 0; } static int impl_node_add_port(void *object, enum spa_direction direction, uint32_t port_id, const struct spa_dict *props) { return -ENOTSUP; } static int impl_node_remove_port(void *object, enum spa_direction direction, uint32_t port_id) { return -ENOTSUP; } static int port_enum_formats(void *object, enum spa_direction direction, uint32_t port_id, uint32_t index, const struct spa_pod *filter, struct spa_pod **param, struct spa_pod_builder *builder) { return -ENOTSUP; } static int port_get_format(void *object, enum spa_direction direction, uint32_t port_id, uint32_t index, const struct spa_pod *filter, struct spa_pod **param, struct spa_pod_builder *builder) { struct impl *this = object; struct port *port; port = GET_PORT(this, direction, port_id); if (!port->have_format) return -EIO; if (index > 0) return 0; *param = NULL; return 1; } static int impl_node_port_enum_params(void *object, int seq, enum spa_direction direction, uint32_t port_id, uint32_t id, uint32_t start, uint32_t num, const struct spa_pod *filter) { struct impl *this = object; struct spa_pod_builder b = { 0 }; uint8_t buffer[1024]; struct spa_pod *param; struct spa_result_node_params result; uint32_t count = 0; int res; result.id = id; result.next = start; next: result.index = result.next++; spa_pod_builder_init(&b, buffer, sizeof(buffer)); switch (id) { case SPA_PARAM_EnumFormat: if ((res = port_enum_formats(this, direction, port_id, result.index, filter, ¶m, &b)) <= 0) return res; break; case SPA_PARAM_Format: if ((res = port_get_format(this, direction, port_id, result.index, filter, ¶m, &b)) <= 0) return res; break; default: return -ENOENT; } if (spa_pod_filter(&b, &result.param, param, filter) < 0) goto next; spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); if (++count != num) goto next; return 0; } static int port_set_format(void *object, enum spa_direction direction, uint32_t port_id, uint32_t flags, const struct spa_pod *format) { struct impl *this = object; struct port *port; int res; port = GET_PORT(this, direction, port_id); if (format == NULL) { port->have_format = false; } else { struct spa_video_info info = { 0 }; if ((res = spa_format_parse(format, &info.media_type, &info.media_subtype)) < 0) return res; if (info.media_type != SPA_MEDIA_TYPE_video && info.media_subtype != SPA_MEDIA_SUBTYPE_raw) return -EINVAL; if (spa_format_video_raw_parse(format, &info.info.raw) < 0) return -EINVAL; if (!(flags & SPA_NODE_PARAM_FLAG_TEST_ONLY)) { port->current_format = info; port->have_format = true; } } return 0; } static int impl_node_port_set_param(void *object, enum spa_direction direction, uint32_t port_id, uint32_t id, uint32_t flags, const struct spa_pod *param) { if (id == SPA_PARAM_Format) { return port_set_format(object, direction, port_id, flags, param); } else return -ENOENT; } static int impl_node_port_use_buffers(void *object, enum spa_direction direction, uint32_t port_id, uint32_t flags, struct spa_buffer **buffers, uint32_t n_buffers) { if (object == NULL) return -EINVAL; if (!IS_VALID_PORT(object, direction, port_id)) return -EINVAL; return -ENOTSUP; } static int impl_node_port_set_io(void *object, enum spa_direction direction, uint32_t port_id, uint32_t id, void *data, size_t size) { struct impl *this = object; struct port *port; if (this == NULL) return -EINVAL; if (!IS_VALID_PORT(this, direction, port_id)) return -EINVAL; port = GET_PORT(this, direction, port_id); if (id == SPA_IO_Buffers) port->io = data; else return -ENOENT; return 0; } static int impl_node_port_reuse_buffer(void *object, uint32_t port_id, uint32_t buffer_id) { if (object == NULL) return -EINVAL; if (port_id != 0) return -EINVAL; return -ENOTSUP; } static int impl_node_process(void *object) { struct impl *this = object; struct port *port; struct spa_io_buffers *output; if (this == NULL) return -EINVAL; if ((output = this->out_ports[0].io) == NULL) return -EIO; port = &this->out_ports[0]; if (!port->have_format) { output->status = -EIO; return -EIO; } output->status = SPA_STATUS_OK; return SPA_STATUS_OK; } static const struct spa_node_methods impl_node = { SPA_VERSION_NODE_METHODS, .add_listener = impl_node_add_listener, .set_callbacks = impl_node_set_callbacks, .enum_params = impl_node_enum_params, .set_param = impl_node_set_param, .set_io = impl_node_set_io, .send_command = impl_node_send_command, .add_port = impl_node_add_port, .remove_port = impl_node_remove_port, .port_enum_params = impl_node_port_enum_params, .port_set_param = impl_node_port_set_param, .port_use_buffers = impl_node_port_use_buffers, .port_set_io = impl_node_port_set_io, .port_reuse_buffer = impl_node_port_reuse_buffer, .process = impl_node_process, }; static int impl_get_interface(struct spa_handle *handle, const char *type, void **interface) { struct impl *this; if (handle == NULL || interface == NULL) return -EINVAL; this = (struct impl *) handle; if (spa_streq(type, SPA_TYPE_INTERFACE_Node)) *interface = &this->node; else return -ENOENT; return 0; } static int impl_clear(struct spa_handle *handle) { spa_return_val_if_fail(handle != NULL, -EINVAL); return 0; } size_t spa_ffmpeg_enc_get_size(const struct spa_handle_factory *factory, const struct spa_dict *params) { return sizeof(struct impl); } int spa_ffmpeg_enc_init(struct spa_handle *handle, const struct spa_dict *info, const struct spa_support *support, uint32_t n_support) { struct impl *this; struct port *port; handle->get_interface = impl_get_interface; handle->clear = impl_clear; this = (struct impl *) handle; this->log = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log); spa_hook_list_init(&this->hooks); this->node.iface = SPA_INTERFACE_INIT( SPA_TYPE_INTERFACE_Node, SPA_VERSION_NODE, &impl_node, this); this->info_all = SPA_NODE_CHANGE_MASK_FLAGS; this->info = SPA_NODE_INFO_INIT(); this->info.max_input_ports = 1; this->info.max_output_ports = 1; this->info.flags = SPA_NODE_FLAG_RT; this->info.params = this->params; port = GET_IN_PORT(this, 0); port->direction = SPA_DIRECTION_INPUT; port->id = 0; port->info_all = SPA_PORT_CHANGE_MASK_FLAGS | SPA_PORT_CHANGE_MASK_PARAMS; port->info = SPA_PORT_INFO_INIT(); port->info.flags = 0; port->params[0] = SPA_PARAM_INFO(SPA_PARAM_EnumFormat, SPA_PARAM_INFO_READ); port->params[1] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); port->info.params = port->params; port->info.n_params = 2; port = GET_OUT_PORT(this, 0); port->direction = SPA_DIRECTION_OUTPUT; port->id = 0; port->info_all = SPA_PORT_CHANGE_MASK_FLAGS | SPA_PORT_CHANGE_MASK_PARAMS; port->info = SPA_PORT_INFO_INIT(); port->info.flags = 0; port->params[0] = SPA_PARAM_INFO(SPA_PARAM_EnumFormat, SPA_PARAM_INFO_READ); port->params[1] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); port->info.params = port->params; port->info.n_params = 2; return 0; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/ffmpeg/ffmpeg.c000066400000000000000000000055651511204443500256310ustar00rootroot00000000000000/* Spa FFmpeg support */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #include #include #include "ffmpeg.h" static int ffmpeg_dec_init(const struct spa_handle_factory *factory, struct spa_handle *handle, const struct spa_dict *info, const struct spa_support *support, uint32_t n_support) { if (factory == NULL || handle == NULL) return -EINVAL; return spa_ffmpeg_dec_init(handle, info, support, n_support); } static int ffmpeg_enc_init(const struct spa_handle_factory *factory, struct spa_handle *handle, const struct spa_dict *info, const struct spa_support *support, uint32_t n_support) { if (factory == NULL || handle == NULL) return -EINVAL; return spa_ffmpeg_enc_init(handle, info, support, n_support); } static const struct spa_interface_info ffmpeg_interfaces[] = { {SPA_TYPE_INTERFACE_Node, }, }; static int ffmpeg_enum_interface_info(const struct spa_handle_factory *factory, const struct spa_interface_info **info, uint32_t *index) { if (factory == NULL || info == NULL || index == NULL) return -EINVAL; if (*index < SPA_N_ELEMENTS(ffmpeg_interfaces)) *info = &ffmpeg_interfaces[(*index)++]; else return 0; return 1; } #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(58, 10, 100) static const AVCodec *find_codec_by_index(uint32_t index) { static void *av_iter_data; static uint32_t next_index; const AVCodec *c = NULL; if (index == 0) { av_iter_data = NULL; next_index = 0; } while (next_index <= index) { c = av_codec_iterate(&av_iter_data); next_index += 1; if (!c) break; } return c; } #else static const AVCodec *find_codec_by_index(uint32_t index) { static const AVCodec *last_codec; static uint32_t next_index; if (index == 0) { last_codec = NULL; next_index = 0; } while (next_index <= index) { last_codec = av_codec_next(last_codec); next_index += 1; if (!last_codec) break; } return last_codec; } #endif SPA_LOG_TOPIC_ENUM_DEFINE_REGISTERED; SPA_EXPORT int spa_handle_factory_enum(const struct spa_handle_factory **factory, uint32_t *index) { static char name[128]; static struct spa_handle_factory f = { SPA_VERSION_HANDLE_FACTORY, .name = name, .enum_interface_info = ffmpeg_enum_interface_info, }; #if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(58, 10, 100) avcodec_register_all(); #endif const AVCodec *c = find_codec_by_index(*index); if (c == NULL) return 0; if (av_codec_is_encoder(c)) { snprintf(name, sizeof(name), "encoder.%s", c->name); f.get_size = spa_ffmpeg_enc_get_size; f.init = ffmpeg_enc_init; } else { snprintf(name, sizeof(name), "decoder.%s", c->name); f.get_size = spa_ffmpeg_dec_get_size; f.init = ffmpeg_dec_init; } *factory = &f; (*index)++; return 1; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/ffmpeg/ffmpeg.h000066400000000000000000000012311511204443500256200ustar00rootroot00000000000000#ifndef SPA_FFMPEG_H #define SPA_FFMPEG_H #include #include struct spa_dict; struct spa_handle; struct spa_support; struct spa_handle_factory; int spa_ffmpeg_dec_init(struct spa_handle *handle, const struct spa_dict *info, const struct spa_support *support, uint32_t n_support); int spa_ffmpeg_enc_init(struct spa_handle *handle, const struct spa_dict *info, const struct spa_support *support, uint32_t n_support); size_t spa_ffmpeg_dec_get_size(const struct spa_handle_factory *factory, const struct spa_dict *params); size_t spa_ffmpeg_enc_get_size(const struct spa_handle_factory *factory, const struct spa_dict *params); #endif pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/ffmpeg/meson.build000066400000000000000000000005451511204443500263540ustar00rootroot00000000000000ffmpeg_sources = ['ffmpeg.c', 'ffmpeg-dec.c', 'ffmpeg-enc.c'] ffmpeglib = shared_library('spa-ffmpeg', ffmpeg_sources, dependencies : [ spa_dep, avcodec_dep ], install : true, install_dir : spa_plugindir / 'ffmpeg') pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/filter-graph/000077500000000000000000000000001511204443500253265ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/filter-graph/audio-dsp-avx.c000066400000000000000000000226641511204443500301650ustar00rootroot00000000000000/* Spa */ /* SPDX-FileCopyrightText: Copyright © 2022 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include "config.h" #include #include #include #include #ifndef HAVE_FFTW #include "pffft.h" #endif #include "audio-dsp-impl.h" #include static void dsp_add_avx(void *obj, float *dst, const float * SPA_RESTRICT src[], uint32_t n_src, uint32_t n_samples) { uint32_t n, i, unrolled; __m256 in[4]; const float **s = (const float **)src; float *d = dst; if (SPA_LIKELY(SPA_IS_ALIGNED(dst, 32))) { unrolled = n_samples & ~31; for (i = 0; i < n_src; i++) { if (SPA_UNLIKELY(!SPA_IS_ALIGNED(src[i], 32))) { unrolled = 0; break; } } } else unrolled = 0; for (n = 0; n < unrolled; n += 32) { in[0] = _mm256_load_ps(&s[0][n+ 0]); in[1] = _mm256_load_ps(&s[0][n+ 8]); in[2] = _mm256_load_ps(&s[0][n+16]); in[3] = _mm256_load_ps(&s[0][n+24]); for (i = 1; i < n_src; i++) { in[0] = _mm256_add_ps(in[0], _mm256_load_ps(&s[i][n+ 0])); in[1] = _mm256_add_ps(in[1], _mm256_load_ps(&s[i][n+ 8])); in[2] = _mm256_add_ps(in[2], _mm256_load_ps(&s[i][n+16])); in[3] = _mm256_add_ps(in[3], _mm256_load_ps(&s[i][n+24])); } _mm256_store_ps(&d[n+ 0], in[0]); _mm256_store_ps(&d[n+ 8], in[1]); _mm256_store_ps(&d[n+16], in[2]); _mm256_store_ps(&d[n+24], in[3]); } for (; n < n_samples; n++) { __m128 in[1]; in[0] = _mm_load_ss(&s[0][n]); for (i = 1; i < n_src; i++) in[0] = _mm_add_ss(in[0], _mm_load_ss(&s[i][n])); _mm_store_ss(&d[n], in[0]); } } static void dsp_add_1_gain_avx(void *obj, float *dst, const float * SPA_RESTRICT src[], uint32_t n_src, float gain, uint32_t n_samples) { uint32_t n, i, unrolled; __m256 in[4], g; const float **s = (const float **)src; float *d = dst; __m128 g1; if (SPA_LIKELY(SPA_IS_ALIGNED(dst, 32))) { unrolled = n_samples & ~31; for (i = 0; i < n_src; i++) { if (SPA_UNLIKELY(!SPA_IS_ALIGNED(src[i], 32))) { unrolled = 0; break; } } } else unrolled = 0; g = _mm256_set1_ps(gain); g1 = _mm_set_ss(gain); for (n = 0; n < unrolled; n += 32) { in[0] = _mm256_load_ps(&s[0][n+ 0]); in[1] = _mm256_load_ps(&s[0][n+ 8]); in[2] = _mm256_load_ps(&s[0][n+16]); in[3] = _mm256_load_ps(&s[0][n+24]); for (i = 1; i < n_src; i++) { in[0] = _mm256_add_ps(in[0], _mm256_load_ps(&s[i][n+ 0])); in[1] = _mm256_add_ps(in[1], _mm256_load_ps(&s[i][n+ 8])); in[2] = _mm256_add_ps(in[2], _mm256_load_ps(&s[i][n+16])); in[3] = _mm256_add_ps(in[3], _mm256_load_ps(&s[i][n+24])); } _mm256_store_ps(&d[n+ 0], _mm256_mul_ps(g, in[0])); _mm256_store_ps(&d[n+ 8], _mm256_mul_ps(g, in[1])); _mm256_store_ps(&d[n+16], _mm256_mul_ps(g, in[2])); _mm256_store_ps(&d[n+24], _mm256_mul_ps(g, in[3])); } for (; n < n_samples; n++) { __m128 in[1]; in[0] = _mm_load_ss(&s[0][n]); for (i = 1; i < n_src; i++) in[0] = _mm_add_ss(in[0], _mm_load_ss(&s[i][n])); _mm_store_ss(&d[n], _mm_mul_ss(g1, in[0])); } } static void dsp_add_n_gain_avx(void *obj, float *dst, const float * SPA_RESTRICT src[], uint32_t n_src, float gain[], uint32_t n_gain, uint32_t n_samples) { uint32_t n, i, unrolled; __m256 in[4], g; const float **s = (const float **)src; float *d = dst; if (SPA_LIKELY(SPA_IS_ALIGNED(dst, 32))) { unrolled = n_samples & ~31; for (i = 0; i < n_src; i++) { if (SPA_UNLIKELY(!SPA_IS_ALIGNED(src[i], 32))) { unrolled = 0; break; } } } else unrolled = 0; for (n = 0; n < unrolled; n += 32) { g = _mm256_set1_ps(gain[0]); in[0] = _mm256_mul_ps(g, _mm256_load_ps(&s[0][n+ 0])); in[1] = _mm256_mul_ps(g, _mm256_load_ps(&s[0][n+ 8])); in[2] = _mm256_mul_ps(g, _mm256_load_ps(&s[0][n+16])); in[3] = _mm256_mul_ps(g, _mm256_load_ps(&s[0][n+24])); for (i = 1; i < n_src; i++) { g = _mm256_set1_ps(gain[i]); in[0] = _mm256_add_ps(in[0], _mm256_mul_ps(g, _mm256_load_ps(&s[i][n+ 0]))); in[1] = _mm256_add_ps(in[1], _mm256_mul_ps(g, _mm256_load_ps(&s[i][n+ 8]))); in[2] = _mm256_add_ps(in[2], _mm256_mul_ps(g, _mm256_load_ps(&s[i][n+16]))); in[3] = _mm256_add_ps(in[3], _mm256_mul_ps(g, _mm256_load_ps(&s[i][n+24]))); } _mm256_store_ps(&d[n+ 0], in[0]); _mm256_store_ps(&d[n+ 8], in[1]); _mm256_store_ps(&d[n+16], in[2]); _mm256_store_ps(&d[n+24], in[3]); } for (; n < n_samples; n++) { __m128 in[1], g; g = _mm_set_ss(gain[0]); in[0] = _mm_mul_ss(g, _mm_load_ss(&s[0][n])); for (i = 1; i < n_src; i++) { g = _mm_set_ss(gain[i]); in[0] = _mm_add_ss(in[0], _mm_mul_ss(g, _mm_load_ss(&s[i][n]))); } _mm_store_ss(&d[n], in[0]); } } void dsp_mix_gain_avx(void *obj, float * SPA_RESTRICT dst, const float * SPA_RESTRICT src[], uint32_t n_src, float gain[], uint32_t n_gain, uint32_t n_samples) { if (n_src == 0) { memset(dst, 0, n_samples * sizeof(float)); } else if (n_src == 1 && gain[0] == 1.0f) { if (dst != src[0]) spa_memcpy(dst, src[0], n_samples * sizeof(float)); } else { if (n_gain == 0) dsp_add_avx(obj, dst, src, n_src, n_samples); else if (n_gain < n_src) dsp_add_1_gain_avx(obj, dst, src, n_src, gain[0], n_samples); else dsp_add_n_gain_avx(obj, dst, src, n_src, gain, n_gain, n_samples); } } void dsp_sum_avx(void *obj, float *r, const float *a, const float *b, uint32_t n_samples) { uint32_t n, unrolled; __m256 in[4]; unrolled = n_samples & ~31; if (SPA_LIKELY(SPA_IS_ALIGNED(r, 32)) && SPA_LIKELY(SPA_IS_ALIGNED(a, 32)) && SPA_LIKELY(SPA_IS_ALIGNED(b, 32))) { for (n = 0; n < unrolled; n += 32) { in[0] = _mm256_load_ps(&a[n+ 0]); in[1] = _mm256_load_ps(&a[n+ 8]); in[2] = _mm256_load_ps(&a[n+16]); in[3] = _mm256_load_ps(&a[n+24]); in[0] = _mm256_add_ps(in[0], _mm256_load_ps(&b[n+ 0])); in[1] = _mm256_add_ps(in[1], _mm256_load_ps(&b[n+ 8])); in[2] = _mm256_add_ps(in[2], _mm256_load_ps(&b[n+16])); in[3] = _mm256_add_ps(in[3], _mm256_load_ps(&b[n+24])); _mm256_store_ps(&r[n+ 0], in[0]); _mm256_store_ps(&r[n+ 8], in[1]); _mm256_store_ps(&r[n+16], in[2]); _mm256_store_ps(&r[n+24], in[3]); } } else { for (n = 0; n < unrolled; n += 32) { in[0] = _mm256_loadu_ps(&a[n+ 0]); in[1] = _mm256_loadu_ps(&a[n+ 8]); in[2] = _mm256_loadu_ps(&a[n+16]); in[3] = _mm256_loadu_ps(&a[n+24]); in[0] = _mm256_add_ps(in[0], _mm256_loadu_ps(&b[n+ 0])); in[1] = _mm256_add_ps(in[1], _mm256_loadu_ps(&b[n+ 8])); in[2] = _mm256_add_ps(in[2], _mm256_loadu_ps(&b[n+16])); in[3] = _mm256_add_ps(in[3], _mm256_loadu_ps(&b[n+24])); _mm256_storeu_ps(&r[n+ 0], in[0]); _mm256_storeu_ps(&r[n+ 8], in[1]); _mm256_storeu_ps(&r[n+16], in[2]); _mm256_storeu_ps(&r[n+24], in[3]); } } for (; n < n_samples; n++) { __m128 in[1]; in[0] = _mm_load_ss(&a[n]); in[0] = _mm_add_ss(in[0], _mm_load_ss(&b[n])); _mm_store_ss(&r[n], in[0]); } } inline static __m256 _mm256_mul_pz(__m256 ab, __m256 cd) { __m256 aa, bb, dc, x0, x1; aa = _mm256_moveldup_ps(ab); bb = _mm256_movehdup_ps(ab); x0 = _mm256_mul_ps(aa, cd); dc = _mm256_shuffle_ps(cd, cd, _MM_SHUFFLE(2,3,0,1)); x1 = _mm256_mul_ps(bb, dc); return _mm256_addsub_ps(x0, x1); } void dsp_fft_cmul_avx(void *obj, void *fft, float * SPA_RESTRICT dst, const float * SPA_RESTRICT a, const float * SPA_RESTRICT b, uint32_t len, const float scale) { #ifdef HAVE_FFTW __m256 s = _mm256_set1_ps(scale); __m256 aa[2], bb[2], dd[2]; uint32_t i, unrolled; if (SPA_IS_ALIGNED(a, 32) && SPA_IS_ALIGNED(b, 32) && SPA_IS_ALIGNED(dst, 32)) unrolled = len & ~7; else unrolled = 0; for (i = 0; i < unrolled; i+=8) { aa[0] = _mm256_load_ps(&a[2*i]); /* ar0 ai0 ar1 ai1 */ aa[1] = _mm256_load_ps(&a[2*i+8]); /* ar1 ai1 ar2 ai2 */ bb[0] = _mm256_load_ps(&b[2*i]); /* br0 bi0 br1 bi1 */ bb[1] = _mm256_load_ps(&b[2*i+8]); /* br2 bi2 br3 bi3 */ dd[0] = _mm256_mul_pz(aa[0], bb[0]); dd[1] = _mm256_mul_pz(aa[1], bb[1]); dd[0] = _mm256_mul_ps(dd[0], s); dd[1] = _mm256_mul_ps(dd[1], s); _mm256_store_ps(&dst[2*i], dd[0]); _mm256_store_ps(&dst[2*i+8], dd[1]); } for (; i < len; i++) { dst[2*i ] = (a[2*i] * b[2*i ] - a[2*i+1] * b[2*i+1]) * scale; dst[2*i+1] = (a[2*i] * b[2*i+1] + a[2*i+1] * b[2*i ]) * scale; } #else pffft_zconvolve(fft, a, b, dst, scale); #endif } void dsp_fft_cmuladd_avx(void *obj, void *fft, float * SPA_RESTRICT dst, const float * SPA_RESTRICT src, const float * SPA_RESTRICT a, const float * SPA_RESTRICT b, uint32_t len, const float scale) { #ifdef HAVE_FFTW __m256 s = _mm256_set1_ps(scale); __m256 aa[2], bb[2], dd[2], t[2]; uint32_t i, unrolled; if (SPA_IS_ALIGNED(a, 32) && SPA_IS_ALIGNED(b, 32) && SPA_IS_ALIGNED(src, 32) && SPA_IS_ALIGNED(dst, 32)) unrolled = len & ~7; else unrolled = 0; for (i = 0; i < unrolled; i+=8) { aa[0] = _mm256_load_ps(&a[2*i]); /* ar0 ai0 ar1 ai1 */ aa[1] = _mm256_load_ps(&a[2*i+8]); /* ar1 ai1 ar2 ai2 */ bb[0] = _mm256_load_ps(&b[2*i]); /* br0 bi0 br1 bi1 */ bb[1] = _mm256_load_ps(&b[2*i+8]); /* br2 bi2 br3 bi3 */ dd[0] = _mm256_mul_pz(aa[0], bb[0]); dd[1] = _mm256_mul_pz(aa[1], bb[1]); dd[0] = _mm256_mul_ps(dd[0], s); dd[1] = _mm256_mul_ps(dd[1], s); t[0] = _mm256_load_ps(&src[2*i]); t[1] = _mm256_load_ps(&src[2*i+8]); t[0] = _mm256_add_ps(t[0], dd[0]); t[1] = _mm256_add_ps(t[1], dd[1]); _mm256_store_ps(&dst[2*i], t[0]); _mm256_store_ps(&dst[2*i+8], t[1]); } for (; i < len; i++) { dst[2*i ] = src[2*i ] + (a[2*i] * b[2*i ] - a[2*i+1] * b[2*i+1]) * scale; dst[2*i+1] = src[2*i+1] + (a[2*i] * b[2*i+1] + a[2*i+1] * b[2*i ]) * scale; } #else pffft_zconvolve_accumulate(fft, a, b, src, dst, scale); #endif } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/filter-graph/audio-dsp-c.c000066400000000000000000000172631511204443500276100ustar00rootroot00000000000000/* Spa */ /* SPDX-FileCopyrightText: Copyright © 2022 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include "config.h" #include #include #include #include #include #include #ifdef HAVE_FFTW #include #else #include "pffft.h" #endif #include "audio-dsp-impl.h" void dsp_clear_c(void *obj, float * SPA_RESTRICT dst, uint32_t n_samples) { memset(dst, 0, sizeof(float) * n_samples); } void dsp_copy_c(void *obj, float * SPA_RESTRICT dst, const float * SPA_RESTRICT src, uint32_t n_samples) { if (dst != src) spa_memcpy(dst, src, sizeof(float) * n_samples); } static inline void dsp_add_c(void *obj, float * SPA_RESTRICT dst, const float * SPA_RESTRICT src, uint32_t n_samples) { uint32_t i; const float *s = src; float *d = dst; for (i = 0; i < n_samples; i++) d[i] += s[i]; } static inline void dsp_gain_c(void *obj, float * dst, const float * src, float gain, uint32_t n_samples) { uint32_t i; const float *s = src; float *d = dst; if (gain == 0.0f) dsp_clear_c(obj, dst, n_samples); else if (gain == 1.0f) dsp_copy_c(obj, dst, src, n_samples); else { for (i = 0; i < n_samples; i++) d[i] = s[i] * gain; } } static inline void dsp_gain_add_c(void *obj, float * SPA_RESTRICT dst, const float * SPA_RESTRICT src, float gain, uint32_t n_samples) { uint32_t i; const float *s = src; float *d = dst; if (gain == 0.0f) return; else if (gain == 1.0f) dsp_add_c(obj, dst, src, n_samples); else { for (i = 0; i < n_samples; i++) d[i] += s[i] * gain; } } void dsp_mix_gain_c(void *obj, float * SPA_RESTRICT dst, const float * SPA_RESTRICT src[], uint32_t n_src, float gain[], uint32_t n_gain, uint32_t n_samples) { uint32_t i; if (n_src == 0) { dsp_clear_c(obj, dst, n_samples); } else { if (n_gain < n_src) { dsp_copy_c(obj, dst, src[0], n_samples); for (i = 1; i < n_src; i++) dsp_add_c(obj, dst, src[i], n_samples); if (n_gain > 0) dsp_gain_c(obj, dst, dst, gain[0], n_samples); } else { dsp_gain_c(obj, dst, src[0], gain[0], n_samples); for (i = 1; i < n_src; i++) dsp_gain_add_c(obj, dst, src[i], gain[i], n_samples); } } } static inline void dsp_mult1_c(void *obj, float * SPA_RESTRICT dst, const float * SPA_RESTRICT src, uint32_t n_samples) { uint32_t i; const float *s = src; float *d = dst; for (i = 0; i < n_samples; i++) d[i] *= s[i]; } void dsp_mult_c(void *obj, float * SPA_RESTRICT dst, const float * SPA_RESTRICT src[], uint32_t n_src, uint32_t n_samples) { uint32_t i; if (n_src == 0) { dsp_clear_c(obj, dst, n_samples); } else { dsp_copy_c(obj, dst, src[0], n_samples); for (i = 1; i < n_src; i++) dsp_mult1_c(obj, dst, src[i], n_samples); } } static void biquad_run_c(void *obj, struct biquad *bq, float *out, const float *in, uint32_t n_samples) { float x, y, x1, x2; float b0, b1, b2, a1, a2; uint32_t i; if (bq->type == BQ_NONE) { dsp_copy_c(obj, out, in, n_samples); return; } x1 = bq->x1; x2 = bq->x2; b0 = bq->b0; b1 = bq->b1; b2 = bq->b2; a1 = bq->a1; a2 = bq->a2; for (i = 0; i < n_samples; i++) { x = in[i]; y = b0 * x + x1; x1 = b1 * x - a1 * y + x2; x2 = b2 * x - a2 * y; out[i] = y; } #define F(x) (isnormal(x) ? (x) : 0.0f) bq->x1 = F(x1); bq->x2 = F(x2); #undef F } void dsp_biquad_run_c(void *obj, struct biquad *bq, uint32_t n_bq, uint32_t bq_stride, float * SPA_RESTRICT out[], const float * SPA_RESTRICT in[], uint32_t n_src, uint32_t n_samples) { uint32_t i, j; const float *s; float *d; for (i = 0; i < n_src; i++, bq+=bq_stride) { s = in[i]; d = out[i]; if (s == NULL || d == NULL) continue; if (n_bq > 0) biquad_run_c(obj, &bq[0], d, s, n_samples); for (j = 1; j < n_bq; j++) biquad_run_c(obj, &bq[j], d, d, n_samples); } } void dsp_sum_c(void *obj, float * dst, const float * SPA_RESTRICT a, const float * SPA_RESTRICT b, uint32_t n_samples) { uint32_t i; for (i = 0; i < n_samples; i++) dst[i] = a[i] + b[i]; } void dsp_linear_c(void *obj, float * dst, const float * SPA_RESTRICT src, const float mult, const float add, uint32_t n_samples) { uint32_t i; if (add == 0.0f) { dsp_gain_c(obj, dst, src, mult, n_samples); } else { if (mult == 0.0f) { for (i = 0; i < n_samples; i++) dst[i] = add; } else if (mult == 1.0f) { for (i = 0; i < n_samples; i++) dst[i] = src[i] + add; } else { for (i = 0; i < n_samples; i++) dst[i] = mult * src[i] + add; } } } void dsp_delay_c(void *obj, float *buffer, uint32_t *pos, uint32_t n_buffer, uint32_t delay, float *dst, const float *src, uint32_t n_samples) { if (delay == 0) { dsp_copy_c(obj, dst, src, n_samples); } else { uint32_t w, o, i; w = *pos; o = n_buffer - delay; for (i = 0; i < n_samples; i++) { buffer[w] = buffer[w + n_buffer] = src[i]; dst[i] = buffer[w + o]; w = w + 1 >= n_buffer ? 0 : w + 1; } *pos = w; } } #ifdef HAVE_FFTW struct fft_info { fftwf_plan plan_r2c; fftwf_plan plan_c2r; }; #endif void *dsp_fft_new_c(void *obj, uint32_t size, bool real) { #ifdef HAVE_FFTW struct fft_info *info = calloc(1, sizeof(struct fft_info)); float *rdata; fftwf_complex *cdata; if (info == NULL) return NULL; rdata = fftwf_alloc_real(size * 2); cdata = fftwf_alloc_complex(size + 1); info->plan_r2c = fftwf_plan_dft_r2c_1d(size, rdata, cdata, FFTW_ESTIMATE); info->plan_c2r = fftwf_plan_dft_c2r_1d(size, cdata, rdata, FFTW_ESTIMATE); fftwf_free(rdata); fftwf_free(cdata); return info; #else return pffft_new_setup(size, real ? PFFFT_REAL : PFFFT_COMPLEX); #endif } void dsp_fft_free_c(void *obj, void *fft) { #ifdef HAVE_FFTW struct fft_info *info = fft; fftwf_destroy_plan(info->plan_r2c); fftwf_destroy_plan(info->plan_c2r); free(info); #else pffft_destroy_setup(fft); #endif } void *dsp_fft_memalloc_c(void *obj, uint32_t size, bool real) { #ifdef HAVE_FFTW if (real) return fftwf_alloc_real(size); else return fftwf_alloc_complex(size); #else if (real) return pffft_aligned_malloc(size * sizeof(float)); else return pffft_aligned_malloc(size * 2 * sizeof(float)); #endif } void dsp_fft_memfree_c(void *obj, void *data) { #ifdef HAVE_FFTW fftwf_free(data); #else pffft_aligned_free(data); #endif } void dsp_fft_memclear_c(void *obj, void *data, uint32_t size, bool real) { #ifdef HAVE_FFTW spa_fga_dsp_clear(obj, data, real ? size : size * 2); #else spa_fga_dsp_clear(obj, data, real ? size : size * 2); #endif } void dsp_fft_run_c(void *obj, void *fft, int direction, const float * SPA_RESTRICT src, float * SPA_RESTRICT dst) { #ifdef HAVE_FFTW struct fft_info *info = fft; if (direction > 0) fftwf_execute_dft_r2c (info->plan_r2c, (float*)src, (fftwf_complex*)dst); else fftwf_execute_dft_c2r (info->plan_c2r, (fftwf_complex*)src, dst); #else pffft_transform(fft, src, dst, NULL, direction < 0 ? PFFFT_BACKWARD : PFFFT_FORWARD); #endif } void dsp_fft_cmul_c(void *obj, void *fft, float * SPA_RESTRICT dst, const float * SPA_RESTRICT a, const float * SPA_RESTRICT b, uint32_t len, const float scale) { #ifdef HAVE_FFTW for (uint32_t i = 0; i < len; i++) { dst[2*i ] = (a[2*i] * b[2*i ] - a[2*i+1] * b[2*i+1]) * scale; dst[2*i+1] = (a[2*i] * b[2*i+1] + a[2*i+1] * b[2*i ]) * scale; } #else pffft_zconvolve(fft, a, b, dst, scale); #endif } void dsp_fft_cmuladd_c(void *obj, void *fft, float * SPA_RESTRICT dst, const float * SPA_RESTRICT src, const float * SPA_RESTRICT a, const float * SPA_RESTRICT b, uint32_t len, const float scale) { #ifdef HAVE_FFTW for (uint32_t i = 0; i < len; i++) { dst[2*i ] = src[2*i ] + (a[2*i] * b[2*i ] - a[2*i+1] * b[2*i+1]) * scale; dst[2*i+1] = src[2*i+1] + (a[2*i] * b[2*i+1] + a[2*i+1] * b[2*i ]) * scale; } #else pffft_zconvolve_accumulate(fft, a, b, src, dst, scale); #endif } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/filter-graph/audio-dsp-impl.h000066400000000000000000000065621511204443500303340ustar00rootroot00000000000000/* Spa */ /* SPDX-FileCopyrightText: Copyright © 2022 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef DSP_IMPL_H #define DSP_IMPL_H #include "audio-dsp.h" struct spa_fga_dsp * spa_fga_dsp_new(uint32_t cpu_flags); void spa_fga_dsp_free(struct spa_fga_dsp *dsp); #define MAKE_CLEAR_FUNC(arch) \ void dsp_clear_##arch(void *obj, float * SPA_RESTRICT dst, uint32_t n_samples) #define MAKE_COPY_FUNC(arch) \ void dsp_copy_##arch(void *obj, float * SPA_RESTRICT dst, \ const float * SPA_RESTRICT src, uint32_t n_samples) #define MAKE_MIX_GAIN_FUNC(arch) \ void dsp_mix_gain_##arch(void *obj, float * SPA_RESTRICT dst, \ const float * SPA_RESTRICT src[], uint32_t n_src, float gain[], uint32_t n_gain, uint32_t n_samples) #define MAKE_SUM_FUNC(arch) \ void dsp_sum_##arch (void *obj, float * SPA_RESTRICT dst, \ const float * SPA_RESTRICT a, const float * SPA_RESTRICT b, uint32_t n_samples) #define MAKE_LINEAR_FUNC(arch) \ void dsp_linear_##arch (void *obj, float * SPA_RESTRICT dst, \ const float * SPA_RESTRICT src, const float mult, const float add, uint32_t n_samples) #define MAKE_MULT_FUNC(arch) \ void dsp_mult_##arch(void *obj, float * SPA_RESTRICT dst, \ const float * SPA_RESTRICT src[], uint32_t n_src, uint32_t n_samples) #define MAKE_BIQUAD_RUN_FUNC(arch) \ void dsp_biquad_run_##arch (void *obj, struct biquad *bq, uint32_t n_bq, uint32_t bq_stride, \ float * SPA_RESTRICT out[], const float * SPA_RESTRICT in[], uint32_t n_src, uint32_t n_samples) #define MAKE_DELAY_FUNC(arch) \ void dsp_delay_##arch (void *obj, float *buffer, uint32_t *pos, uint32_t n_buffer, \ uint32_t delay, float *dst, const float *src, uint32_t n_samples) #define MAKE_FFT_NEW_FUNC(arch) \ void *dsp_fft_new_##arch(void *obj, uint32_t size, bool real) #define MAKE_FFT_FREE_FUNC(arch) \ void dsp_fft_free_##arch(void *obj, void *fft) #define MAKE_FFT_MEMALLOC_FUNC(arch) \ void *dsp_fft_memalloc_##arch(void *obj, uint32_t size, bool real) #define MAKE_FFT_MEMFREE_FUNC(arch) \ void dsp_fft_memfree_##arch(void *obj, void *mem) #define MAKE_FFT_MEMCLEAR_FUNC(arch) \ void dsp_fft_memclear_##arch(void *obj, void *mem, uint32_t size, bool real) #define MAKE_FFT_RUN_FUNC(arch) \ void dsp_fft_run_##arch(void *obj, void *fft, int direction, \ const float * SPA_RESTRICT src, float * SPA_RESTRICT dst) #define MAKE_FFT_CMUL_FUNC(arch) \ void dsp_fft_cmul_##arch(void *obj, void *fft, \ float * SPA_RESTRICT dst, const float * SPA_RESTRICT a, \ const float * SPA_RESTRICT b, uint32_t len, const float scale) #define MAKE_FFT_CMULADD_FUNC(arch) \ void dsp_fft_cmuladd_##arch(void *obj, void *fft, \ float * dst, const float * src, \ const float * SPA_RESTRICT a, const float * SPA_RESTRICT b, \ uint32_t len, const float scale) MAKE_CLEAR_FUNC(c); MAKE_COPY_FUNC(c); MAKE_MIX_GAIN_FUNC(c); MAKE_SUM_FUNC(c); MAKE_LINEAR_FUNC(c); MAKE_MULT_FUNC(c); MAKE_BIQUAD_RUN_FUNC(c); MAKE_DELAY_FUNC(c); MAKE_FFT_NEW_FUNC(c); MAKE_FFT_FREE_FUNC(c); MAKE_FFT_MEMALLOC_FUNC(c); MAKE_FFT_MEMFREE_FUNC(c); MAKE_FFT_MEMCLEAR_FUNC(c); MAKE_FFT_RUN_FUNC(c); MAKE_FFT_CMUL_FUNC(c); MAKE_FFT_CMULADD_FUNC(c); #if defined (HAVE_SSE) MAKE_MIX_GAIN_FUNC(sse); MAKE_SUM_FUNC(sse); MAKE_BIQUAD_RUN_FUNC(sse); MAKE_DELAY_FUNC(sse); MAKE_FFT_CMUL_FUNC(sse); MAKE_FFT_CMULADD_FUNC(sse); #endif #if defined (HAVE_AVX) MAKE_MIX_GAIN_FUNC(avx); MAKE_SUM_FUNC(avx); MAKE_FFT_CMUL_FUNC(avx); MAKE_FFT_CMULADD_FUNC(avx); #endif #endif /* DSP_OPS_IMPL_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/filter-graph/audio-dsp-sse.c000066400000000000000000000601671511204443500301610ustar00rootroot00000000000000/* Spa */ /* SPDX-FileCopyrightText: Copyright © 2022 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include "config.h" #include #include #include #include #include #include #ifndef HAVE_FFTW #include "pffft.h" #endif #include "audio-dsp-impl.h" #include static void dsp_add_sse(void *obj, float *dst, const float * SPA_RESTRICT src[], uint32_t n_src, uint32_t n_samples) { uint32_t n, i, unrolled; __m128 in[4]; const float **s = (const float **)src; float *d = dst; if (SPA_LIKELY(SPA_IS_ALIGNED(dst, 16))) { unrolled = n_samples & ~15; for (i = 0; i < n_src; i++) { if (SPA_UNLIKELY(!SPA_IS_ALIGNED(src[i], 16))) { unrolled = 0; break; } } } else unrolled = 0; for (n = 0; n < unrolled; n += 16) { in[0] = _mm_load_ps(&s[0][n+ 0]); in[1] = _mm_load_ps(&s[0][n+ 4]); in[2] = _mm_load_ps(&s[0][n+ 8]); in[3] = _mm_load_ps(&s[0][n+12]); for (i = 1; i < n_src; i++) { in[0] = _mm_add_ps(in[0], _mm_load_ps(&s[i][n+ 0])); in[1] = _mm_add_ps(in[1], _mm_load_ps(&s[i][n+ 4])); in[2] = _mm_add_ps(in[2], _mm_load_ps(&s[i][n+ 8])); in[3] = _mm_add_ps(in[3], _mm_load_ps(&s[i][n+12])); } _mm_store_ps(&d[n+ 0], in[0]); _mm_store_ps(&d[n+ 4], in[1]); _mm_store_ps(&d[n+ 8], in[2]); _mm_store_ps(&d[n+12], in[3]); } for (; n < n_samples; n++) { in[0] = _mm_load_ss(&s[0][n]); for (i = 1; i < n_src; i++) in[0] = _mm_add_ss(in[0], _mm_load_ss(&s[i][n])); _mm_store_ss(&d[n], in[0]); } } static void dsp_add_1_gain_sse(void *obj, float * SPA_RESTRICT dst, const float * SPA_RESTRICT src[], uint32_t n_src, float gain, uint32_t n_samples) { uint32_t n, i, unrolled; __m128 in[4], g; const float **s = (const float **)src; float *d = dst; if (SPA_LIKELY(SPA_IS_ALIGNED(dst, 16))) { unrolled = n_samples & ~15; for (i = 0; i < n_src; i++) { if (SPA_UNLIKELY(!SPA_IS_ALIGNED(src[i], 16))) { unrolled = 0; break; } } } else unrolled = 0; g = _mm_set1_ps(gain); for (n = 0; n < unrolled; n += 16) { in[0] = _mm_load_ps(&s[0][n+ 0]); in[1] = _mm_load_ps(&s[0][n+ 4]); in[2] = _mm_load_ps(&s[0][n+ 8]); in[3] = _mm_load_ps(&s[0][n+12]); for (i = 1; i < n_src; i++) { in[0] = _mm_add_ps(in[0], _mm_load_ps(&s[i][n+ 0])); in[1] = _mm_add_ps(in[1], _mm_load_ps(&s[i][n+ 4])); in[2] = _mm_add_ps(in[2], _mm_load_ps(&s[i][n+ 8])); in[3] = _mm_add_ps(in[3], _mm_load_ps(&s[i][n+12])); } _mm_store_ps(&d[n+ 0], _mm_mul_ps(in[0], g)); _mm_store_ps(&d[n+ 4], _mm_mul_ps(in[1], g)); _mm_store_ps(&d[n+ 8], _mm_mul_ps(in[2], g)); _mm_store_ps(&d[n+12], _mm_mul_ps(in[3], g)); } for (; n < n_samples; n++) { in[0] = _mm_load_ss(&s[0][n]); for (i = 1; i < n_src; i++) in[0] = _mm_add_ss(in[0], _mm_load_ss(&s[i][n])); _mm_store_ss(&d[n], _mm_mul_ss(in[0], g)); } } static void dsp_add_n_gain_sse(void *obj, float * SPA_RESTRICT dst, const float * SPA_RESTRICT src[], uint32_t n_src, float gain[], uint32_t n_gain, uint32_t n_samples) { uint32_t n, i, unrolled; __m128 in[4], g; const float **s = (const float **)src; float *d = dst; if (SPA_LIKELY(SPA_IS_ALIGNED(dst, 16))) { unrolled = n_samples & ~15; for (i = 0; i < n_src; i++) { if (SPA_UNLIKELY(!SPA_IS_ALIGNED(src[i], 16))) { unrolled = 0; break; } } } else unrolled = 0; for (n = 0; n < unrolled; n += 16) { g = _mm_set1_ps(gain[0]); in[0] = _mm_mul_ps(g, _mm_load_ps(&s[0][n+ 0])); in[1] = _mm_mul_ps(g, _mm_load_ps(&s[0][n+ 4])); in[2] = _mm_mul_ps(g, _mm_load_ps(&s[0][n+ 8])); in[3] = _mm_mul_ps(g, _mm_load_ps(&s[0][n+12])); for (i = 1; i < n_src; i++) { g = _mm_set1_ps(gain[i]); in[0] = _mm_add_ps(in[0], _mm_mul_ps(g, _mm_load_ps(&s[i][n+ 0]))); in[1] = _mm_add_ps(in[1], _mm_mul_ps(g, _mm_load_ps(&s[i][n+ 4]))); in[2] = _mm_add_ps(in[2], _mm_mul_ps(g, _mm_load_ps(&s[i][n+ 8]))); in[3] = _mm_add_ps(in[3], _mm_mul_ps(g, _mm_load_ps(&s[i][n+12]))); } _mm_store_ps(&d[n+ 0], in[0]); _mm_store_ps(&d[n+ 4], in[1]); _mm_store_ps(&d[n+ 8], in[2]); _mm_store_ps(&d[n+12], in[3]); } for (; n < n_samples; n++) { g = _mm_set_ss(gain[0]); in[0] = _mm_mul_ss(g, _mm_load_ss(&s[0][n])); for (i = 1; i < n_src; i++) { g = _mm_set_ss(gain[i]); in[0] = _mm_add_ss(in[0], _mm_mul_ss(g, _mm_load_ss(&s[i][n]))); } _mm_store_ss(&d[n], in[0]); } } void dsp_mix_gain_sse(void *obj, float * SPA_RESTRICT dst, const float * SPA_RESTRICT src[], uint32_t n_src, float gain[], uint32_t n_gain, uint32_t n_samples) { if (n_src == 0) { memset(dst, 0, n_samples * sizeof(float)); } else if (n_src == 1 && gain[0] == 1.0f) { if (dst != src[0]) spa_memcpy(dst, src[0], n_samples * sizeof(float)); } else { if (n_gain == 0) dsp_add_sse(obj, dst, src, n_src, n_samples); else if (n_gain < n_src) dsp_add_1_gain_sse(obj, dst, src, n_src, gain[0], n_samples); else dsp_add_n_gain_sse(obj, dst, src, n_src, gain, n_gain, n_samples); } } void dsp_sum_sse(void *obj, float *r, const float *a, const float *b, uint32_t n_samples) { uint32_t n, unrolled; __m128 in[4]; unrolled = n_samples & ~15; if (SPA_LIKELY(SPA_IS_ALIGNED(r, 16)) && SPA_LIKELY(SPA_IS_ALIGNED(a, 16)) && SPA_LIKELY(SPA_IS_ALIGNED(b, 16))) { for (n = 0; n < unrolled; n += 16) { in[0] = _mm_load_ps(&a[n+ 0]); in[1] = _mm_load_ps(&a[n+ 4]); in[2] = _mm_load_ps(&a[n+ 8]); in[3] = _mm_load_ps(&a[n+12]); in[0] = _mm_add_ps(in[0], _mm_load_ps(&b[n+ 0])); in[1] = _mm_add_ps(in[1], _mm_load_ps(&b[n+ 4])); in[2] = _mm_add_ps(in[2], _mm_load_ps(&b[n+ 8])); in[3] = _mm_add_ps(in[3], _mm_load_ps(&b[n+12])); _mm_store_ps(&r[n+ 0], in[0]); _mm_store_ps(&r[n+ 4], in[1]); _mm_store_ps(&r[n+ 8], in[2]); _mm_store_ps(&r[n+12], in[3]); } } else { for (n = 0; n < unrolled; n += 16) { in[0] = _mm_loadu_ps(&a[n+ 0]); in[1] = _mm_loadu_ps(&a[n+ 4]); in[2] = _mm_loadu_ps(&a[n+ 8]); in[3] = _mm_loadu_ps(&a[n+12]); in[0] = _mm_add_ps(in[0], _mm_loadu_ps(&b[n+ 0])); in[1] = _mm_add_ps(in[1], _mm_loadu_ps(&b[n+ 4])); in[2] = _mm_add_ps(in[2], _mm_loadu_ps(&b[n+ 8])); in[3] = _mm_add_ps(in[3], _mm_loadu_ps(&b[n+12])); _mm_storeu_ps(&r[n+ 0], in[0]); _mm_storeu_ps(&r[n+ 4], in[1]); _mm_storeu_ps(&r[n+ 8], in[2]); _mm_storeu_ps(&r[n+12], in[3]); } } for (; n < n_samples; n++) { in[0] = _mm_load_ss(&a[n]); in[0] = _mm_add_ss(in[0], _mm_load_ss(&b[n])); _mm_store_ss(&r[n], in[0]); } } static void dsp_biquad_run1_sse(void *obj, struct biquad *bq, float *out, const float *in, uint32_t n_samples) { __m128 x, y, z; __m128 b012; __m128 a12; __m128 x12; uint32_t i; b012 = _mm_setr_ps(bq->b0, bq->b1, bq->b2, 0.0f); /* b0 b1 b2 0 */ a12 = _mm_setr_ps(0.0f, bq->a1, bq->a2, 0.0f); /* 0 a1 a2 0 */ x12 = _mm_setr_ps(bq->x1, bq->x2, 0.0f, 0.0f); /* x1 x2 0 0 */ for (i = 0; i < n_samples; i++) { x = _mm_load1_ps(&in[i]); /* x x x x */ z = _mm_mul_ps(x, b012); /* b0*x b1*x b2*x 0 */ z = _mm_add_ps(z, x12); /* b0*x+x1 b1*x+x2 b2*x 0 */ _mm_store_ss(&out[i], z); /* out[i] = b0*x+x1 */ y = _mm_shuffle_ps(z, z, _MM_SHUFFLE(0,0,0,0)); /* b0*x+x1 b0*x+x1 b0*x+x1 b0*x+x1 = y*/ y = _mm_mul_ps(y, a12); /* 0 a1*y a2*y 0 */ y = _mm_sub_ps(z, y); /* y x1 x2 0 */ x12 = _mm_shuffle_ps(y, y, _MM_SHUFFLE(3,3,2,1)); /* x1 x2 0 0*/ } #define F(x) (isnormal(x) ? (x) : 0.0f) bq->x1 = F(x12[0]); bq->x2 = F(x12[1]); #undef F } static void dsp_biquad2_run_sse(void *obj, struct biquad *bq, float *out, const float *in, uint32_t n_samples) { __m128 x, y, z; __m128 b0, b1; __m128 a0, a1; __m128 x0, x1; uint32_t i; b0 = _mm_setr_ps(bq[0].b0, bq[0].b1, bq[0].b2, 0.0f); /* b0 b1 b2 0 */ a0 = _mm_setr_ps(0.0f, bq[0].a1, bq[0].a2, 0.0f); /* 0 a1 a2 0 */ x0 = _mm_setr_ps(bq[0].x1, bq[0].x2, 0.0f, 0.0f); /* x1 x2 0 0 */ b1 = _mm_setr_ps(bq[1].b0, bq[1].b1, bq[1].b2, 0.0f); /* b0 b1 b2 0 */ a1 = _mm_setr_ps(0.0f, bq[1].a1, bq[1].a2, 0.0f); /* 0 a1 a2 0 */ x1 = _mm_setr_ps(bq[1].x1, bq[1].x2, 0.0f, 0.0f); /* x1 x2 0 0 */ for (i = 0; i < n_samples; i++) { x = _mm_load1_ps(&in[i]); /* x x x x */ z = _mm_mul_ps(x, b0); /* b0*x b1*x b2*x 0 */ z = _mm_add_ps(z, x0); /* b0*x+x1 b1*x+x2 b2*x 0 */ y = _mm_shuffle_ps(z, z, _MM_SHUFFLE(0,0,0,0)); /* b0*x+x1 b0*x+x1 b0*x+x1 b0*x+x1 = y*/ x = _mm_mul_ps(y, a0); /* 0 a1*y a2*y 0 */ x = _mm_sub_ps(z, x); /* y x1 x2 0 */ x0 = _mm_shuffle_ps(x, x, _MM_SHUFFLE(3,3,2,1)); /* x1 x2 0 0*/ z = _mm_mul_ps(y, b1); /* b0*x b1*x b2*x 0 */ z = _mm_add_ps(z, x1); /* b0*x+x1 b1*x+x2 b2*x 0 */ x = _mm_shuffle_ps(z, z, _MM_SHUFFLE(0,0,0,0)); /* b0*x+x1 b0*x+x1 b0*x+x1 b0*x+x1 = y*/ y = _mm_mul_ps(x, a1); /* 0 a1*y a2*y 0 */ y = _mm_sub_ps(z, y); /* y x1 x2 0 */ x1 = _mm_shuffle_ps(y, y, _MM_SHUFFLE(3,3,2,1)); /* x1 x2 0 0*/ _mm_store_ss(&out[i], x); /* out[i] = b0*x+x1 */ } #define F(x) (isnormal(x) ? (x) : 0.0f) bq[0].x1 = F(x0[0]); bq[0].x2 = F(x0[1]); bq[1].x1 = F(x1[0]); bq[1].x2 = F(x1[1]); #undef F } static void dsp_biquad_run2_sse(void *obj, struct biquad *bq, uint32_t bq_stride, float **out, const float **in, uint32_t n_samples) { __m128 x, y, z; __m128 b0, b1, b2; __m128 a1, a2; __m128 x1, x2; uint32_t i; b0 = _mm_setr_ps(bq[0].b0, bq[bq_stride].b0, 0.0f, 0.0f); /* b00 b10 0 0 */ b1 = _mm_setr_ps(bq[0].b1, bq[bq_stride].b1, 0.0f, 0.0f); /* b01 b11 0 0 */ b2 = _mm_setr_ps(bq[0].b2, bq[bq_stride].b2, 0.0f, 0.0f); /* b02 b12 0 0 */ a1 = _mm_setr_ps(bq[0].a1, bq[bq_stride].a1, 0.0f, 0.0f); /* b00 b10 0 0 */ a2 = _mm_setr_ps(bq[0].a2, bq[bq_stride].a2, 0.0f, 0.0f); /* b01 b11 0 0 */ x1 = _mm_setr_ps(bq[0].x1, bq[bq_stride].x1, 0.0f, 0.0f); /* b00 b10 0 0 */ x2 = _mm_setr_ps(bq[0].x2, bq[bq_stride].x2, 0.0f, 0.0f); /* b01 b11 0 0 */ for (i = 0; i < n_samples; i++) { x = _mm_setr_ps(in[0][i], in[1][i], 0.0f, 0.0f); y = _mm_mul_ps(x, b0); /* y = x * b0 */ y = _mm_add_ps(y, x1); /* y = x * b0 + x1*/ z = _mm_mul_ps(y, a1); /* z = a1 * y */ x1 = _mm_mul_ps(x, b1); /* x1 = x * b1 */ x1 = _mm_add_ps(x1, x2); /* x1 = x * b1 + x2*/ x1 = _mm_sub_ps(x1, z); /* x1 = x * b1 + x2 - a1 * y*/ z = _mm_mul_ps(y, a2); /* z = a2 * y */ x2 = _mm_mul_ps(x, b2); /* x2 = x * b2 */ x2 = _mm_sub_ps(x2, z); /* x2 = x * b2 - a2 * y*/ out[0][i] = y[0]; out[1][i] = y[1]; } #define F(x) (isnormal(x) ? (x) : 0.0f) bq[0*bq_stride].x1 = F(x1[0]); bq[0*bq_stride].x2 = F(x2[0]); bq[1*bq_stride].x1 = F(x1[1]); bq[1*bq_stride].x2 = F(x2[1]); #undef F } static void dsp_biquad2_run2_sse(void *obj, struct biquad *bq, uint32_t bq_stride, float **out, const float **in, uint32_t n_samples) { __m128 x, y, z; __m128 b00, b01, b02, b10, b11, b12; __m128 a01, a02, a11, a12; __m128 x01, x02, x11, x12; uint32_t i; b00 = _mm_setr_ps(bq[0].b0, bq[bq_stride].b0, 0.0f, 0.0f); /* b00 b10 0 0 */ b01 = _mm_setr_ps(bq[0].b1, bq[bq_stride].b1, 0.0f, 0.0f); /* b01 b11 0 0 */ b02 = _mm_setr_ps(bq[0].b2, bq[bq_stride].b2, 0.0f, 0.0f); /* b02 b12 0 0 */ a01 = _mm_setr_ps(bq[0].a1, bq[bq_stride].a1, 0.0f, 0.0f); /* b00 b10 0 0 */ a02 = _mm_setr_ps(bq[0].a2, bq[bq_stride].a2, 0.0f, 0.0f); /* b01 b11 0 0 */ x01 = _mm_setr_ps(bq[0].x1, bq[bq_stride].x1, 0.0f, 0.0f); /* b00 b10 0 0 */ x02 = _mm_setr_ps(bq[0].x2, bq[bq_stride].x2, 0.0f, 0.0f); /* b01 b11 0 0 */ b10 = _mm_setr_ps(bq[1].b0, bq[bq_stride+1].b0, 0.0f, 0.0f); /* b00 b10 0 0 */ b11 = _mm_setr_ps(bq[1].b1, bq[bq_stride+1].b1, 0.0f, 0.0f); /* b01 b11 0 0 */ b12 = _mm_setr_ps(bq[1].b2, bq[bq_stride+1].b2, 0.0f, 0.0f); /* b02 b12 0 0 */ a11 = _mm_setr_ps(bq[1].a1, bq[bq_stride+1].a1, 0.0f, 0.0f); /* b00 b10 0 0 */ a12 = _mm_setr_ps(bq[1].a2, bq[bq_stride+1].a2, 0.0f, 0.0f); /* b01 b11 0 0 */ x11 = _mm_setr_ps(bq[1].x1, bq[bq_stride+1].x1, 0.0f, 0.0f); /* b00 b10 0 0 */ x12 = _mm_setr_ps(bq[1].x2, bq[bq_stride+1].x2, 0.0f, 0.0f); /* b01 b11 0 0 */ for (i = 0; i < n_samples; i++) { x = _mm_setr_ps(in[0][i], in[1][i], 0.0f, 0.0f); y = _mm_mul_ps(x, b00); /* y = x * b0 */ y = _mm_add_ps(y, x01); /* y = x * b0 + x1*/ z = _mm_mul_ps(y, a01); /* z = a1 * y */ x01 = _mm_mul_ps(x, b01); /* x1 = x * b1 */ x01 = _mm_add_ps(x01, x02); /* x1 = x * b1 + x2*/ x01 = _mm_sub_ps(x01, z); /* x1 = x * b1 + x2 - a1 * y*/ z = _mm_mul_ps(y, a02); /* z = a2 * y */ x02 = _mm_mul_ps(x, b02); /* x2 = x * b2 */ x02 = _mm_sub_ps(x02, z); /* x2 = x * b2 - a2 * y*/ x = y; y = _mm_mul_ps(x, b10); /* y = x * b0 */ y = _mm_add_ps(y, x11); /* y = x * b0 + x1*/ z = _mm_mul_ps(y, a11); /* z = a1 * y */ x11 = _mm_mul_ps(x, b11); /* x1 = x * b1 */ x11 = _mm_add_ps(x11, x12); /* x1 = x * b1 + x2*/ x11 = _mm_sub_ps(x11, z); /* x1 = x * b1 + x2 - a1 * y*/ z = _mm_mul_ps(y, a12); /* z = a2 * y*/ x12 = _mm_mul_ps(x, b12); /* x2 = x * b2 */ x12 = _mm_sub_ps(x12, z); /* x2 = x * b2 - a2 * y*/ out[0][i] = y[0]; out[1][i] = y[1]; } #define F(x) (isnormal(x) ? (x) : 0.0f) bq[0*bq_stride+0].x1 = F(x01[0]); bq[0*bq_stride+0].x2 = F(x02[0]); bq[1*bq_stride+0].x1 = F(x01[1]); bq[1*bq_stride+0].x2 = F(x02[1]); bq[0*bq_stride+1].x1 = F(x11[0]); bq[0*bq_stride+1].x2 = F(x12[0]); bq[1*bq_stride+1].x1 = F(x11[1]); bq[1*bq_stride+1].x2 = F(x12[1]); #undef F } static void dsp_biquad_run4_sse(void *obj, struct biquad *bq, uint32_t bq_stride, float **out, const float **in, uint32_t n_samples) { __m128 x, y, z; __m128 b0, b1, b2; __m128 a1, a2; __m128 x1, x2; uint32_t i; b0 = _mm_setr_ps(bq[0].b0, bq[bq_stride].b0, bq[2*bq_stride].b0, bq[3*bq_stride].b0); b1 = _mm_setr_ps(bq[0].b1, bq[bq_stride].b1, bq[2*bq_stride].b1, bq[3*bq_stride].b1); b2 = _mm_setr_ps(bq[0].b2, bq[bq_stride].b2, bq[2*bq_stride].b2, bq[3*bq_stride].b2); a1 = _mm_setr_ps(bq[0].a1, bq[bq_stride].a1, bq[2*bq_stride].a1, bq[3*bq_stride].a1); a2 = _mm_setr_ps(bq[0].a2, bq[bq_stride].a2, bq[2*bq_stride].a2, bq[3*bq_stride].a2); x1 = _mm_setr_ps(bq[0].x1, bq[bq_stride].x1, bq[2*bq_stride].x1, bq[3*bq_stride].x1); x2 = _mm_setr_ps(bq[0].x2, bq[bq_stride].x2, bq[2*bq_stride].x2, bq[3*bq_stride].x2); for (i = 0; i < n_samples; i++) { x = _mm_setr_ps(in[0][i], in[1][i], in[2][i], in[3][i]); y = _mm_mul_ps(x, b0); /* y = x * b0 */ y = _mm_add_ps(y, x1); /* y = x * b0 + x1*/ z = _mm_mul_ps(y, a1); /* z = a1 * y */ x1 = _mm_mul_ps(x, b1); /* x1 = x * b1 */ x1 = _mm_add_ps(x1, x2); /* x1 = x * b1 + x2*/ x1 = _mm_sub_ps(x1, z); /* x1 = x * b1 + x2 - a1 * y*/ z = _mm_mul_ps(y, a2); /* z = a2 * y */ x2 = _mm_mul_ps(x, b2); /* x2 = x * b2 */ x2 = _mm_sub_ps(x2, z); /* x2 = x * b2 - a2 * y*/ out[0][i] = y[0]; out[1][i] = y[1]; out[2][i] = y[2]; out[3][i] = y[3]; } #define F(x) (isnormal(x) ? (x) : 0.0f) bq[0*bq_stride].x1 = F(x1[0]); bq[0*bq_stride].x2 = F(x2[0]); bq[1*bq_stride].x1 = F(x1[1]); bq[1*bq_stride].x2 = F(x2[1]); bq[2*bq_stride].x1 = F(x1[2]); bq[2*bq_stride].x2 = F(x2[2]); bq[3*bq_stride].x1 = F(x1[3]); bq[3*bq_stride].x2 = F(x2[3]); #undef F } static void dsp_biquad2_run4_sse(void *obj, struct biquad *bq, uint32_t bq_stride, float **out, const float **in, uint32_t n_samples) { __m128 x, y, z; __m128 b00, b01, b02, b10, b11, b12; __m128 a01, a02, a11, a12; __m128 x01, x02, x11, x12; uint32_t i; b00 = _mm_setr_ps(bq[0].b0, bq[bq_stride].b0, bq[2*bq_stride].b0, bq[3*bq_stride].b0); b01 = _mm_setr_ps(bq[0].b1, bq[bq_stride].b1, bq[2*bq_stride].b1, bq[3*bq_stride].b1); b02 = _mm_setr_ps(bq[0].b2, bq[bq_stride].b2, bq[2*bq_stride].b2, bq[3*bq_stride].b2); a01 = _mm_setr_ps(bq[0].a1, bq[bq_stride].a1, bq[2*bq_stride].a1, bq[3*bq_stride].a1); a02 = _mm_setr_ps(bq[0].a2, bq[bq_stride].a2, bq[2*bq_stride].a2, bq[3*bq_stride].a2); x01 = _mm_setr_ps(bq[0].x1, bq[bq_stride].x1, bq[2*bq_stride].x1, bq[3*bq_stride].x1); x02 = _mm_setr_ps(bq[0].x2, bq[bq_stride].x2, bq[2*bq_stride].x2, bq[3*bq_stride].x2); b10 = _mm_setr_ps(bq[1].b0, bq[bq_stride+1].b0, bq[2*bq_stride+1].b0, bq[3*bq_stride+1].b0); b11 = _mm_setr_ps(bq[1].b1, bq[bq_stride+1].b1, bq[2*bq_stride+1].b1, bq[3*bq_stride+1].b1); b12 = _mm_setr_ps(bq[1].b2, bq[bq_stride+1].b2, bq[2*bq_stride+1].b2, bq[3*bq_stride+1].b2); a11 = _mm_setr_ps(bq[1].a1, bq[bq_stride+1].a1, bq[2*bq_stride+1].a1, bq[3*bq_stride+1].a1); a12 = _mm_setr_ps(bq[1].a2, bq[bq_stride+1].a2, bq[2*bq_stride+1].a2, bq[3*bq_stride+1].a2); x11 = _mm_setr_ps(bq[1].x1, bq[bq_stride+1].x1, bq[2*bq_stride+1].x1, bq[3*bq_stride+1].x1); x12 = _mm_setr_ps(bq[1].x2, bq[bq_stride+1].x2, bq[2*bq_stride+1].x2, bq[3*bq_stride+1].x2); for (i = 0; i < n_samples; i++) { x = _mm_setr_ps(in[0][i], in[1][i], in[2][i], in[3][i]); y = _mm_mul_ps(x, b00); /* y = x * b0 */ y = _mm_add_ps(y, x01); /* y = x * b0 + x1*/ z = _mm_mul_ps(y, a01); /* z = a1 * y */ x01 = _mm_mul_ps(x, b01); /* x1 = x * b1 */ x01 = _mm_add_ps(x01, x02); /* x1 = x * b1 + x2*/ x01 = _mm_sub_ps(x01, z); /* x1 = x * b1 + x2 - a1 * y*/ z = _mm_mul_ps(y, a02); /* z = a2 * y */ x02 = _mm_mul_ps(x, b02); /* x2 = x * b2 */ x02 = _mm_sub_ps(x02, z); /* x2 = x * b2 - a2 * y*/ x = y; y = _mm_mul_ps(x, b10); /* y = x * b0 */ y = _mm_add_ps(y, x11); /* y = x * b0 + x1*/ z = _mm_mul_ps(y, a11); /* z = a1 * y */ x11 = _mm_mul_ps(x, b11); /* x1 = x * b1 */ x11 = _mm_add_ps(x11, x12); /* x1 = x * b1 + x2*/ x11 = _mm_sub_ps(x11, z); /* x1 = x * b1 + x2 - a1 * y*/ z = _mm_mul_ps(y, a12); /* z = a2 * y*/ x12 = _mm_mul_ps(x, b12); /* x2 = x * b2 */ x12 = _mm_sub_ps(x12, z); /* x2 = x * b2 - a2 * y*/ out[0][i] = y[0]; out[1][i] = y[1]; out[2][i] = y[2]; out[3][i] = y[3]; } #define F(x) (isnormal(x) ? (x) : 0.0f) bq[0*bq_stride+0].x1 = F(x01[0]); bq[0*bq_stride+0].x2 = F(x02[0]); bq[1*bq_stride+0].x1 = F(x01[1]); bq[1*bq_stride+0].x2 = F(x02[1]); bq[2*bq_stride+0].x1 = F(x01[2]); bq[2*bq_stride+0].x2 = F(x02[2]); bq[3*bq_stride+0].x1 = F(x01[3]); bq[3*bq_stride+0].x2 = F(x02[3]); bq[0*bq_stride+1].x1 = F(x11[0]); bq[0*bq_stride+1].x2 = F(x12[0]); bq[1*bq_stride+1].x1 = F(x11[1]); bq[1*bq_stride+1].x2 = F(x12[1]); bq[2*bq_stride+1].x1 = F(x11[2]); bq[2*bq_stride+1].x2 = F(x12[2]); bq[3*bq_stride+1].x1 = F(x11[3]); bq[3*bq_stride+1].x2 = F(x12[3]); #undef F } void dsp_biquad_run_sse(void *obj, struct biquad *bq, uint32_t n_bq, uint32_t bq_stride, float * SPA_RESTRICT out[], const float * SPA_RESTRICT in[], uint32_t n_src, uint32_t n_samples) { uint32_t i, j, bqs2 = bq_stride*2, bqs4 = bqs2*2; uint32_t iunrolled4 = n_src & ~3; uint32_t iunrolled2 = n_src & ~1; uint32_t junrolled2 = n_bq & ~1; for (i = 0; i < iunrolled4; i+=4, bq+=bqs4) { const float *s[4] = { in[i], in[i+1], in[i+2], in[i+3] }; float *d[4] = { out[i], out[i+1], out[i+2], out[i+3] }; if (s[0] == NULL || s[1] == NULL || s[2] == NULL || s[3] == NULL || d[0] == NULL || d[1] == NULL || d[2] == NULL || d[3] == NULL) break; j = 0; if (j < junrolled2) { dsp_biquad2_run4_sse(obj, &bq[j], bq_stride, d, s, n_samples); s[0] = d[0]; s[1] = d[1]; s[2] = d[2]; s[3] = d[3]; j+=2; } for (; j < junrolled2; j+=2) { dsp_biquad2_run4_sse(obj, &bq[j], bq_stride, d, s, n_samples); } if (j < n_bq) { dsp_biquad_run4_sse(obj, &bq[j], bq_stride, d, s, n_samples); } } for (; i < iunrolled2; i+=2, bq+=bqs2) { const float *s[2] = { in[i], in[i+1] }; float *d[2] = { out[i], out[i+1] }; if (s[0] == NULL || s[1] == NULL || d[0] == NULL || d[1] == NULL) break; j = 0; if (j < junrolled2) { dsp_biquad2_run2_sse(obj, &bq[j], bq_stride, d, s, n_samples); s[0] = d[0]; s[1] = d[1]; j+=2; } for (; j < junrolled2; j+=2) { dsp_biquad2_run2_sse(obj, &bq[j], bq_stride, d, s, n_samples); } if (j < n_bq) { dsp_biquad_run2_sse(obj, &bq[j], bq_stride, d, s, n_samples); } } for (; i < n_src; i++, bq+=bq_stride) { const float *s = in[i]; float *d = out[i]; if (s == NULL || d == NULL) continue; j = 0; if (j < junrolled2) { dsp_biquad2_run_sse(obj, &bq[j], d, s, n_samples); s = d; j+=2; } for (; j < junrolled2; j+=2) { dsp_biquad2_run_sse(obj, &bq[j], d, s, n_samples); } if (j < n_bq) { dsp_biquad_run1_sse(obj, &bq[j], d, s, n_samples); } } } void dsp_delay_sse(void *obj, float *buffer, uint32_t *pos, uint32_t n_buffer, uint32_t delay, float *dst, const float *src, uint32_t n_samples) { __m128 t[1]; uint32_t w = *pos; uint32_t o = n_buffer - delay; uint32_t n, unrolled; if (SPA_IS_ALIGNED(src, 16) && SPA_IS_ALIGNED(dst, 16)) unrolled = n_samples & ~3; else unrolled = 0; for(n = 0; n < unrolled; n += 4) { t[0] = _mm_load_ps(&src[n]); _mm_storeu_ps(&buffer[w], t[0]); _mm_storeu_ps(&buffer[w+n_buffer], t[0]); t[0] = _mm_loadu_ps(&buffer[w+o]); _mm_store_ps(&dst[n], t[0]); w = w + 4 >= n_buffer ? 0 : w + 4; } for(; n < n_samples; n++) { t[0] = _mm_load_ss(&src[n]); _mm_store_ss(&buffer[w], t[0]); _mm_store_ss(&buffer[w+n_buffer], t[0]); t[0] = _mm_load_ss(&buffer[w+o]); _mm_store_ss(&dst[n], t[0]); w = w + 1 >= n_buffer ? 0 : w + 1; } *pos = w; } inline static void _mm_mul_pz(__m128 *a, __m128 *b, __m128 *d) { __m128 ar, ai, br, bi, arbr, arbi, aibi, aibr, dr, di; ar = _mm_shuffle_ps(a[0], a[1], _MM_SHUFFLE(2,0,2,0)); /* ar0 ar1 ar2 ar3 */ ai = _mm_shuffle_ps(a[0], a[1], _MM_SHUFFLE(3,1,3,1)); /* ai0 ai1 ai2 ai3 */ br = _mm_shuffle_ps(b[0], b[1], _MM_SHUFFLE(2,0,2,0)); /* br0 br1 br2 br3 */ bi = _mm_shuffle_ps(b[0], b[1], _MM_SHUFFLE(3,1,3,1)) /* bi0 bi1 bi2 bi3 */; arbr = _mm_mul_ps(ar, br); /* ar * br */ arbi = _mm_mul_ps(ar, bi); /* ar * bi */ aibi = _mm_mul_ps(ai, bi); /* ai * bi */ aibr = _mm_mul_ps(ai, br); /* ai * br */ dr = _mm_sub_ps(arbr, aibi); /* ar * br - ai * bi */ di = _mm_add_ps(arbi, aibr); /* ar * bi + ai * br */ d[0] = _mm_unpacklo_ps(dr, di); d[1] = _mm_unpackhi_ps(dr, di); } void dsp_fft_cmul_sse(void *obj, void *fft, float * SPA_RESTRICT dst, const float * SPA_RESTRICT a, const float * SPA_RESTRICT b, uint32_t len, const float scale) { #ifdef HAVE_FFTW __m128 s = _mm_set1_ps(scale); __m128 aa[2], bb[2], dd[2]; uint32_t i, unrolled; if (SPA_IS_ALIGNED(a, 16) && SPA_IS_ALIGNED(b, 16) && SPA_IS_ALIGNED(dst, 16)) unrolled = len & ~3; else unrolled = 0; for (i = 0; i < unrolled; i+=4) { aa[0] = _mm_load_ps(&a[2*i]); /* ar0 ai0 ar1 ai1 */ aa[1] = _mm_load_ps(&a[2*i+4]); /* ar1 ai1 ar2 ai2 */ bb[0] = _mm_load_ps(&b[2*i]); /* br0 bi0 br1 bi1 */ bb[1] = _mm_load_ps(&b[2*i+4]); /* br2 bi2 br3 bi3 */ _mm_mul_pz(aa, bb, dd); dd[0] = _mm_mul_ps(dd[0], s); dd[1] = _mm_mul_ps(dd[1], s); _mm_store_ps(&dst[2*i], dd[0]); _mm_store_ps(&dst[2*i+4], dd[1]); } for (; i < len; i++) { dst[2*i ] = (a[2*i] * b[2*i ] - a[2*i+1] * b[2*i+1]) * scale; dst[2*i+1] = (a[2*i] * b[2*i+1] + a[2*i+1] * b[2*i ]) * scale; } #else pffft_zconvolve(fft, a, b, dst, scale); #endif } void dsp_fft_cmuladd_sse(void *obj, void *fft, float * SPA_RESTRICT dst, const float * SPA_RESTRICT src, const float * SPA_RESTRICT a, const float * SPA_RESTRICT b, uint32_t len, const float scale) { #ifdef HAVE_FFTW __m128 s = _mm_set1_ps(scale); __m128 aa[2], bb[2], dd[2], t[2]; uint32_t i, unrolled; if (SPA_IS_ALIGNED(a, 16) && SPA_IS_ALIGNED(b, 16) && SPA_IS_ALIGNED(src, 16) && SPA_IS_ALIGNED(dst, 16)) unrolled = len & ~3; else unrolled = 0; for (i = 0; i < unrolled; i+=4) { aa[0] = _mm_load_ps(&a[2*i]); /* ar0 ai0 ar1 ai1 */ aa[1] = _mm_load_ps(&a[2*i+4]); /* ar1 ai1 ar2 ai2 */ bb[0] = _mm_load_ps(&b[2*i]); /* br0 bi0 br1 bi1 */ bb[1] = _mm_load_ps(&b[2*i+4]); /* br2 bi2 br3 bi3 */ _mm_mul_pz(aa, bb, dd); dd[0] = _mm_mul_ps(dd[0], s); dd[1] = _mm_mul_ps(dd[1], s); t[0] = _mm_load_ps(&src[2*i]); t[1] = _mm_load_ps(&src[2*i+4]); t[0] = _mm_add_ps(t[0], dd[0]); t[1] = _mm_add_ps(t[1], dd[1]); _mm_store_ps(&dst[2*i], t[0]); _mm_store_ps(&dst[2*i+4], t[1]); } for (; i < len; i++) { dst[2*i ] = src[2*i ] + (a[2*i] * b[2*i ] - a[2*i+1] * b[2*i+1]) * scale; dst[2*i+1] = src[2*i+1] + (a[2*i] * b[2*i+1] + a[2*i+1] * b[2*i ]) * scale; } #else pffft_zconvolve_accumulate(fft, a, b, src, dst, scale); #endif } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/filter-graph/audio-dsp.c000066400000000000000000000060061511204443500273610ustar00rootroot00000000000000/* Spa */ /* SPDX-FileCopyrightText: Copyright © 2022 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #include #include #include #include "pffft.h" #include "audio-dsp-impl.h" struct dsp_info { uint32_t cpu_flags; struct spa_fga_dsp_methods funcs; }; static const struct dsp_info dsp_table[] = { #if defined (HAVE_AVX) { SPA_CPU_FLAG_AVX, .funcs.clear = dsp_clear_c, .funcs.copy = dsp_copy_c, .funcs.mix_gain = dsp_mix_gain_avx, .funcs.biquad_run = dsp_biquad_run_sse, .funcs.sum = dsp_sum_avx, .funcs.linear = dsp_linear_c, .funcs.mult = dsp_mult_c, .funcs.fft_new = dsp_fft_new_c, .funcs.fft_free = dsp_fft_free_c, .funcs.fft_memalloc = dsp_fft_memalloc_c, .funcs.fft_memfree = dsp_fft_memfree_c, .funcs.fft_memclear = dsp_fft_memclear_c, .funcs.fft_run = dsp_fft_run_c, .funcs.fft_cmul = dsp_fft_cmul_avx, .funcs.fft_cmuladd = dsp_fft_cmuladd_avx, .funcs.delay = dsp_delay_sse, }, #endif #if defined (HAVE_SSE) { SPA_CPU_FLAG_SSE, .funcs.clear = dsp_clear_c, .funcs.copy = dsp_copy_c, .funcs.mix_gain = dsp_mix_gain_sse, .funcs.biquad_run = dsp_biquad_run_sse, .funcs.sum = dsp_sum_sse, .funcs.linear = dsp_linear_c, .funcs.mult = dsp_mult_c, .funcs.fft_new = dsp_fft_new_c, .funcs.fft_free = dsp_fft_free_c, .funcs.fft_memalloc = dsp_fft_memalloc_c, .funcs.fft_memfree = dsp_fft_memfree_c, .funcs.fft_memclear = dsp_fft_memclear_c, .funcs.fft_run = dsp_fft_run_c, .funcs.fft_cmul = dsp_fft_cmul_sse, .funcs.fft_cmuladd = dsp_fft_cmuladd_sse, .funcs.delay = dsp_delay_sse, }, #endif { 0, .funcs.clear = dsp_clear_c, .funcs.copy = dsp_copy_c, .funcs.mix_gain = dsp_mix_gain_c, .funcs.biquad_run = dsp_biquad_run_c, .funcs.sum = dsp_sum_c, .funcs.linear = dsp_linear_c, .funcs.mult = dsp_mult_c, .funcs.fft_new = dsp_fft_new_c, .funcs.fft_free = dsp_fft_free_c, .funcs.fft_memalloc = dsp_fft_memalloc_c, .funcs.fft_memfree = dsp_fft_memfree_c, .funcs.fft_memclear = dsp_fft_memclear_c, .funcs.fft_run = dsp_fft_run_c, .funcs.fft_cmul = dsp_fft_cmul_c, .funcs.fft_cmuladd = dsp_fft_cmuladd_c, .funcs.delay = dsp_delay_c, }, }; #define MATCH_CPU_FLAGS(a,b) ((a) == 0 || ((a) & (b)) == a) static const struct dsp_info *find_dsp_info(uint32_t cpu_flags) { SPA_FOR_EACH_ELEMENT_VAR(dsp_table, t) { if (MATCH_CPU_FLAGS(t->cpu_flags, cpu_flags)) return t; } return NULL; } void spa_fga_dsp_free(struct spa_fga_dsp *dsp) { free(dsp); } struct spa_fga_dsp * spa_fga_dsp_new(uint32_t cpu_flags) { const struct dsp_info *info; struct spa_fga_dsp *dsp; info = find_dsp_info(cpu_flags); if (info == NULL) { errno = ENOTSUP; return NULL; } dsp = calloc(1, sizeof(*dsp)); if (dsp == NULL) return NULL; pffft_select_cpu(cpu_flags); dsp->cpu_flags = cpu_flags; dsp->iface = SPA_INTERFACE_INIT( SPA_TYPE_INTERFACE_FILTER_GRAPH_AudioDSP, SPA_VERSION_FGA_DSP, &info->funcs, dsp); return dsp; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/filter-graph/audio-dsp.h000066400000000000000000000140631511204443500273700ustar00rootroot00000000000000/* Spa */ /* SPDX-FileCopyrightText: Copyright © 2022 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_FGA_DSP_H #define SPA_FGA_DSP_H #include #include #include "biquad.h" #define SPA_TYPE_INTERFACE_FILTER_GRAPH_AudioDSP SPA_TYPE_INFO_INTERFACE_BASE "FilterGraph:AudioDSP" #define SPA_VERSION_FGA_DSP 0 struct spa_fga_dsp { struct spa_interface iface; uint32_t cpu_flags; }; struct spa_fga_dsp_methods { #define SPA_VERSION_FGA_DSP_METHODS 0 uint32_t version; void (*clear) (void *obj, float * SPA_RESTRICT dst, uint32_t n_samples); void (*copy) (void *obj, float * SPA_RESTRICT dst, const float * SPA_RESTRICT src, uint32_t n_samples); void (*mix_gain) (void *obj, float * SPA_RESTRICT dst, const float * SPA_RESTRICT src[], uint32_t n_src, float gain[], uint32_t n_gain, uint32_t n_samples); void (*sum) (void *obj, float * dst, const float * SPA_RESTRICT a, const float * SPA_RESTRICT b, uint32_t n_samples); void *(*fft_new) (void *obj, uint32_t size, bool real); void (*fft_free) (void *obj, void *fft); void *(*fft_memalloc) (void *obj, uint32_t size, bool real); void (*fft_memfree) (void *obj, void *mem); void (*fft_memclear) (void *obj, void *mem, uint32_t size, bool real); void (*fft_run) (void *obj, void *fft, int direction, const float * SPA_RESTRICT src, float * SPA_RESTRICT dst); void (*fft_cmul) (void *obj, void *fft, float * SPA_RESTRICT dst, const float * SPA_RESTRICT a, const float * SPA_RESTRICT b, uint32_t len, const float scale); void (*fft_cmuladd) (void *obj, void *fft, float * dst, const float * src, const float * SPA_RESTRICT a, const float * SPA_RESTRICT b, uint32_t len, const float scale); void (*linear) (void *obj, float * dst, const float * SPA_RESTRICT src, const float mult, const float add, uint32_t n_samples); void (*mult) (void *obj, float * SPA_RESTRICT dst, const float * SPA_RESTRICT src[], uint32_t n_src, uint32_t n_samples); void (*biquad_run) (void *obj, struct biquad *bq, uint32_t n_bq, uint32_t bq_stride, float * SPA_RESTRICT out[], const float * SPA_RESTRICT in[], uint32_t n_src, uint32_t n_samples); void (*delay) (void *obj, float *buffer, uint32_t *pos, uint32_t n_buffer, uint32_t delay, float *dst, const float *src, uint32_t n_samples); }; static inline void spa_fga_dsp_clear(struct spa_fga_dsp *obj, float * SPA_RESTRICT dst, uint32_t n_samples) { spa_api_method_v(spa_fga_dsp, &obj->iface, clear, 0, dst, n_samples); } static inline void spa_fga_dsp_copy(struct spa_fga_dsp *obj, float * SPA_RESTRICT dst, const float * SPA_RESTRICT src, uint32_t n_samples) { spa_api_method_v(spa_fga_dsp, &obj->iface, copy, 0, dst, src, n_samples); } static inline void spa_fga_dsp_mix_gain(struct spa_fga_dsp *obj, float * SPA_RESTRICT dst, const float * SPA_RESTRICT src[], uint32_t n_src, float gain[], uint32_t n_gain, uint32_t n_samples) { spa_api_method_v(spa_fga_dsp, &obj->iface, mix_gain, 0, dst, src, n_src, gain, n_gain, n_samples); } static inline void spa_fga_dsp_sum(struct spa_fga_dsp *obj, float * dst, const float * SPA_RESTRICT a, const float * SPA_RESTRICT b, uint32_t n_samples) { spa_api_method_v(spa_fga_dsp, &obj->iface, sum, 0, dst, a, b, n_samples); } static inline void *spa_fga_dsp_fft_new(struct spa_fga_dsp *obj, uint32_t size, bool real) { return spa_api_method_r(void *, NULL, spa_fga_dsp, &obj->iface, fft_new, 0, size, real); } static inline void spa_fga_dsp_fft_free(struct spa_fga_dsp *obj, void *fft) { spa_api_method_v(spa_fga_dsp, &obj->iface, fft_free, 0, fft); } static inline void *spa_fga_dsp_fft_memalloc(struct spa_fga_dsp *obj, uint32_t size, bool real) { return spa_api_method_r(void *, NULL, spa_fga_dsp, &obj->iface, fft_memalloc, 0, size, real); } static inline void spa_fga_dsp_fft_memfree(struct spa_fga_dsp *obj, void *mem) { spa_api_method_v(spa_fga_dsp, &obj->iface, fft_memfree, 0, mem); } static inline void spa_fga_dsp_fft_memclear(struct spa_fga_dsp *obj, void *mem, uint32_t size, bool real) { spa_api_method_v(spa_fga_dsp, &obj->iface, fft_memclear, 0, mem, size, real); } static inline void spa_fga_dsp_fft_run(struct spa_fga_dsp *obj, void *fft, int direction, const float * SPA_RESTRICT src, float * SPA_RESTRICT dst) { spa_api_method_v(spa_fga_dsp, &obj->iface, fft_run, 0, fft, direction, src, dst); } static inline void spa_fga_dsp_fft_cmul(struct spa_fga_dsp *obj, void *fft, float * SPA_RESTRICT dst, const float * SPA_RESTRICT a, const float * SPA_RESTRICT b, uint32_t len, const float scale) { spa_api_method_v(spa_fga_dsp, &obj->iface, fft_cmul, 0, fft, dst, a, b, len, scale); } static inline void spa_fga_dsp_fft_cmuladd(struct spa_fga_dsp *obj, void *fft, float * dst, const float * src, const float * SPA_RESTRICT a, const float * SPA_RESTRICT b, uint32_t len, const float scale) { spa_api_method_v(spa_fga_dsp, &obj->iface, fft_cmuladd, 0, fft, dst, src, a, b, len, scale); } static inline void spa_fga_dsp_linear(struct spa_fga_dsp *obj, float * dst, const float * SPA_RESTRICT src, const float mult, const float add, uint32_t n_samples) { spa_api_method_v(spa_fga_dsp, &obj->iface, linear, 0, dst, src, mult, add, n_samples); } static inline void spa_fga_dsp_mult(struct spa_fga_dsp *obj, float * SPA_RESTRICT dst, const float * SPA_RESTRICT src[], uint32_t n_src, uint32_t n_samples) { spa_api_method_v(spa_fga_dsp, &obj->iface, mult, 0, dst, src, n_src, n_samples); } static inline void spa_fga_dsp_biquad_run(struct spa_fga_dsp *obj, struct biquad *bq, uint32_t n_bq, uint32_t bq_stride, float * SPA_RESTRICT out[], const float * SPA_RESTRICT in[], uint32_t n_src, uint32_t n_samples) { spa_api_method_v(spa_fga_dsp, &obj->iface, biquad_run, 0, bq, n_bq, bq_stride, out, in, n_src, n_samples); } static inline void spa_fga_dsp_delay(struct spa_fga_dsp *obj, float *buffer, uint32_t *pos, uint32_t n_buffer, uint32_t delay, float *dst, const float *src, uint32_t n_samples) { spa_api_method_v(spa_fga_dsp, &obj->iface, delay, 0, buffer, pos, n_buffer, delay, dst, src, n_samples); } #endif /* SPA_FGA_DSP_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/filter-graph/audio-plugin.h000066400000000000000000000056511511204443500301030ustar00rootroot00000000000000/* PipeWire */ /* SPDX-FileCopyrightText: Copyright © 2021 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_FGA_PLUGIN_H #define SPA_FGA_PLUGIN_H #include #include #include #include #define SPA_TYPE_INTERFACE_FILTER_GRAPH_AudioPlugin SPA_TYPE_INFO_INTERFACE_BASE "FilterGraph:AudioPlugin" #define SPA_VERSION_FGA_PLUGIN 0 struct spa_fga_plugin { struct spa_interface iface; }; struct spa_fga_plugin_methods { #define SPA_VERSION_FGA_PLUGIN_METHODS 0 uint32_t version; const struct spa_fga_descriptor *(*make_desc) (void *plugin, const char *name); }; struct spa_fga_port { uint32_t index; const char *name; #define SPA_FGA_PORT_INPUT (1ULL << 0) #define SPA_FGA_PORT_OUTPUT (1ULL << 1) #define SPA_FGA_PORT_CONTROL (1ULL << 2) #define SPA_FGA_PORT_AUDIO (1ULL << 3) #define SPA_FGA_PORT_SUPPORTS_NULL_DATA (1ULL << 4) #define SPA_FGA_PORT_SEQUENCE (1ULL << 5) uint64_t flags; #define SPA_FGA_HINT_BOOLEAN (1ULL << 0) #define SPA_FGA_HINT_SAMPLE_RATE (1ULL << 1) #define SPA_FGA_HINT_INTEGER (1ULL << 2) #define SPA_FGA_HINT_LATENCY (1ULL << 3) uint64_t hint; float def; float min; float max; }; #define SPA_FGA_IS_PORT_INPUT(x) ((x) & SPA_FGA_PORT_INPUT) #define SPA_FGA_IS_PORT_OUTPUT(x) ((x) & SPA_FGA_PORT_OUTPUT) #define SPA_FGA_IS_PORT_CONTROL(x) ((x) & SPA_FGA_PORT_CONTROL) #define SPA_FGA_IS_PORT_AUDIO(x) ((x) & SPA_FGA_PORT_AUDIO) #define SPA_FGA_SUPPORTS_NULL_DATA(x) ((x) & SPA_FGA_PORT_SUPPORTS_NULL_DATA) #define SPA_FGA_IS_PORT_SEQUENCE(x) ((x) & SPA_FGA_PORT_SEQUENCE) struct spa_fga_descriptor { const char *name; #define SPA_FGA_DESCRIPTOR_SUPPORTS_NULL_DATA (1ULL << 0) #define SPA_FGA_DESCRIPTOR_COPY (1ULL << 1) uint64_t flags; void (*free) (const struct spa_fga_descriptor *desc); uint32_t n_ports; struct spa_fga_port *ports; void *(*instantiate) (const struct spa_fga_plugin *plugin, const struct spa_fga_descriptor *desc, unsigned long SampleRate, int index, const char *config); void (*cleanup) (void *instance); void (*connect_port) (void *instance, unsigned long port, void *data); void (*control_changed) (void *instance); void (*activate) (void *instance); void (*deactivate) (void *instance); void (*run) (void *instance, unsigned long SampleCount); }; static inline void spa_fga_descriptor_free(const struct spa_fga_descriptor *desc) { if (desc->free) desc->free(desc); } static inline const struct spa_fga_descriptor * spa_fga_plugin_make_desc(struct spa_fga_plugin *plugin, const char *name) { return spa_api_method_r(const struct spa_fga_descriptor *, NULL, spa_fga_plugin, &plugin->iface, make_desc, 0, name); } typedef struct spa_fga_plugin *(spa_filter_graph_audio_plugin_load_func_t)(const struct spa_support *support, uint32_t n_support, const char *path, const struct spa_dict *info); #define SPA_FILTER_GRAPH_AUDIO_PLUGIN_LOAD_FUNC_NAME "spa_filter_graph_audio_plugin_load" #endif /* PLUGIN_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/filter-graph/biquad.h000066400000000000000000000027421511204443500267510ustar00rootroot00000000000000/* Copyright (c) 2013 The Chromium OS Authors. All rights reserved. * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #ifndef BIQUAD_H_ #define BIQUAD_H_ #ifdef __cplusplus extern "C" { #endif /* The type of the biquad filters */ enum biquad_type { BQ_NONE, BQ_LOWPASS, BQ_HIGHPASS, BQ_BANDPASS, BQ_LOWSHELF, BQ_HIGHSHELF, BQ_PEAKING, BQ_NOTCH, BQ_ALLPASS, BQ_RAW, }; /* The biquad filter parameters. The transfer function H(z) is (b0 + b1 * z^(-1) * + b2 * z^(-2)) / (1 + a1 * z^(-1) + a2 * z^(-2)). The previous two inputs * are stored in x1 and x2, and the previous two outputs are stored in y1 and * y2. * * We use double during the coefficients calculation for better accuracy, but * float is used during the actual filtering for faster computation. */ struct biquad { enum biquad_type type; float b0, b1, b2; float a1, a2; float x1, x2; }; /* Initialize a biquad filter parameters from its type and parameters. * Args: * bq - The biquad filter we want to set. * type - The type of the biquad filter. * frequency - The value should be in the range [0, 1]. It is relative to * half of the sampling rate. * Q - Quality factor. See Web Audio API for details. * gain - The value is in dB. See Web Audio API for details. */ void biquad_set(struct biquad *bq, enum biquad_type type, double freq, double Q, double gain); #ifdef __cplusplus } /* extern "C" */ #endif #endif /* BIQUAD_H_ */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/filter-graph/convolver.c000066400000000000000000000273251511204443500275200ustar00rootroot00000000000000/* PipeWire */ /* SPDX-FileCopyrightText: Copyright © 2017 HiFi-LoFi */ /* SPDX-FileCopyrightText: Copyright © 2021 Wim Taymans */ /* SPDX-License-Identifier: MIT */ /* Adapted from https://github.com/HiFi-LoFi/FFTConvolver */ #include "convolver.h" #include #include struct convolver1 { int blockSize; int segSize; int segCount; int fftComplexSize; float **segments; float **segmentsIr; float *fft_buffer[2]; void *fft; void *ifft; float *pre_mult; float *conv; float *inputBuffer; int inputBufferFill; int current; float scale; }; static int next_power_of_two(int val) { int r = 1; while (r < val) r *= 2; return r; } static void convolver1_reset(struct spa_fga_dsp *dsp, struct convolver1 *conv) { int i; for (i = 0; i < conv->segCount; i++) spa_fga_dsp_fft_memclear(dsp, conv->segments[i], conv->fftComplexSize, false); spa_fga_dsp_fft_memclear(dsp, conv->fft_buffer[0], conv->segSize, true); spa_fga_dsp_fft_memclear(dsp, conv->fft_buffer[1], conv->segSize, true); spa_fga_dsp_fft_memclear(dsp, conv->inputBuffer, conv->segSize, true); spa_fga_dsp_fft_memclear(dsp, conv->pre_mult, conv->fftComplexSize, false); spa_fga_dsp_fft_memclear(dsp, conv->conv, conv->fftComplexSize, false); conv->inputBufferFill = 0; conv->current = 0; } static void convolver1_free(struct spa_fga_dsp *dsp, struct convolver1 *conv) { int i; for (i = 0; i < conv->segCount; i++) { if (conv->segments) spa_fga_dsp_fft_memfree(dsp, conv->segments[i]); if (conv->segmentsIr) spa_fga_dsp_fft_memfree(dsp, conv->segmentsIr[i]); } if (conv->fft) spa_fga_dsp_fft_free(dsp, conv->fft); if (conv->ifft) spa_fga_dsp_fft_free(dsp, conv->ifft); if (conv->fft_buffer[0]) spa_fga_dsp_fft_memfree(dsp, conv->fft_buffer[0]); if (conv->fft_buffer[1]) spa_fga_dsp_fft_memfree(dsp, conv->fft_buffer[1]); free(conv->segments); free(conv->segmentsIr); spa_fga_dsp_fft_memfree(dsp, conv->pre_mult); spa_fga_dsp_fft_memfree(dsp, conv->conv); spa_fga_dsp_fft_memfree(dsp, conv->inputBuffer); free(conv); } static struct convolver1 *convolver1_new(struct spa_fga_dsp *dsp, int block, const float *ir, int irlen) { struct convolver1 *conv; int i; if (block == 0) return NULL; while (irlen > 0 && fabs(ir[irlen-1]) < 0.000001f) irlen--; conv = calloc(1, sizeof(*conv)); if (conv == NULL) return NULL; if (irlen == 0) return conv; conv->blockSize = next_power_of_two(block); conv->segSize = 2 * conv->blockSize; conv->segCount = (irlen + conv->blockSize-1) / conv->blockSize; conv->fftComplexSize = (conv->segSize / 2) + 1; conv->fft = spa_fga_dsp_fft_new(dsp, conv->segSize, true); if (conv->fft == NULL) goto error; conv->ifft = spa_fga_dsp_fft_new(dsp, conv->segSize, true); if (conv->ifft == NULL) goto error; conv->fft_buffer[0] = spa_fga_dsp_fft_memalloc(dsp, conv->segSize, true); conv->fft_buffer[1] = spa_fga_dsp_fft_memalloc(dsp, conv->segSize, true); if (conv->fft_buffer[0] == NULL || conv->fft_buffer[1] == NULL) goto error; conv->segments = calloc(conv->segCount, sizeof(float*)); conv->segmentsIr = calloc(conv->segCount, sizeof(float*)); if (conv->segments == NULL || conv->segmentsIr == NULL) goto error; for (i = 0; i < conv->segCount; i++) { int left = irlen - (i * conv->blockSize); int copy = SPA_MIN(conv->blockSize, left); conv->segments[i] = spa_fga_dsp_fft_memalloc(dsp, conv->fftComplexSize, false); conv->segmentsIr[i] = spa_fga_dsp_fft_memalloc(dsp, conv->fftComplexSize, false); if (conv->segments[i] == NULL || conv->segmentsIr[i] == NULL) goto error; spa_fga_dsp_copy(dsp, conv->fft_buffer[0], &ir[i * conv->blockSize], copy); if (copy < conv->segSize) spa_fga_dsp_fft_memclear(dsp, conv->fft_buffer[0] + copy, conv->segSize - copy, true); spa_fga_dsp_fft_run(dsp, conv->fft, 1, conv->fft_buffer[0], conv->segmentsIr[i]); } conv->pre_mult = spa_fga_dsp_fft_memalloc(dsp, conv->fftComplexSize, false); conv->conv = spa_fga_dsp_fft_memalloc(dsp, conv->fftComplexSize, false); conv->inputBuffer = spa_fga_dsp_fft_memalloc(dsp, conv->segSize, true); if (conv->pre_mult == NULL || conv->conv == NULL || conv->inputBuffer == NULL) goto error; conv->scale = 1.0f / conv->segSize; convolver1_reset(dsp, conv); return conv; error: convolver1_free(dsp, conv); return NULL; } static int convolver1_run(struct spa_fga_dsp *dsp, struct convolver1 *conv, const float *input, float *output, int len) { int i, processed = 0; if (conv == NULL || conv->segCount == 0) { spa_fga_dsp_fft_memclear(dsp, output, len, true); return len; } int inputBufferFill = conv->inputBufferFill; while (processed < len) { const int processing = SPA_MIN(len - processed, conv->blockSize - inputBufferFill); spa_fga_dsp_copy(dsp, conv->inputBuffer + inputBufferFill, input + processed, processing); if (inputBufferFill == 0 && processing < conv->blockSize) spa_fga_dsp_fft_memclear(dsp, conv->inputBuffer + processing, conv->blockSize - processing, true); spa_fga_dsp_fft_run(dsp, conv->fft, 1, conv->inputBuffer, conv->segments[conv->current]); if (conv->segCount > 1) { if (inputBufferFill == 0) { int indexAudio = (conv->current + 1) % conv->segCount; spa_fga_dsp_fft_cmul(dsp, conv->fft, conv->pre_mult, conv->segmentsIr[1], conv->segments[indexAudio], conv->fftComplexSize, conv->scale); for (i = 2; i < conv->segCount; i++) { indexAudio = (conv->current + i) % conv->segCount; spa_fga_dsp_fft_cmuladd(dsp, conv->fft, conv->pre_mult, conv->pre_mult, conv->segmentsIr[i], conv->segments[indexAudio], conv->fftComplexSize, conv->scale); } } spa_fga_dsp_fft_cmuladd(dsp, conv->fft, conv->conv, conv->pre_mult, conv->segments[conv->current], conv->segmentsIr[0], conv->fftComplexSize, conv->scale); } else { spa_fga_dsp_fft_cmul(dsp, conv->fft, conv->conv, conv->segments[conv->current], conv->segmentsIr[0], conv->fftComplexSize, conv->scale); } spa_fga_dsp_fft_run(dsp, conv->ifft, -1, conv->conv, conv->fft_buffer[0]); spa_fga_dsp_sum(dsp, output + processed, conv->fft_buffer[0] + inputBufferFill, conv->fft_buffer[1] + conv->blockSize + inputBufferFill, processing); inputBufferFill += processing; if (inputBufferFill == conv->blockSize) { inputBufferFill = 0; SPA_SWAP(conv->fft_buffer[0], conv->fft_buffer[1]); conv->current = (conv->current > 0) ? (conv->current - 1) : (conv->segCount - 1); } processed += processing; } conv->inputBufferFill = inputBufferFill; return len; } struct convolver { struct spa_fga_dsp *dsp; int headBlockSize; int tailBlockSize; struct convolver1 *headConvolver; struct convolver1 *tailConvolver0; float *tailOutput0; float *tailPrecalculated0; struct convolver1 *tailConvolver; float *tailOutput; float *tailPrecalculated; float *tailInput; int tailInputFill; }; void convolver_reset(struct convolver *conv) { struct spa_fga_dsp *dsp = conv->dsp; if (conv->headConvolver) convolver1_reset(dsp, conv->headConvolver); if (conv->tailConvolver0) { convolver1_reset(dsp, conv->tailConvolver0); spa_fga_dsp_fft_memclear(dsp, conv->tailOutput0, conv->tailBlockSize, true); spa_fga_dsp_fft_memclear(dsp, conv->tailPrecalculated0, conv->tailBlockSize, true); } if (conv->tailConvolver) { convolver1_reset(dsp, conv->tailConvolver); spa_fga_dsp_fft_memclear(dsp, conv->tailOutput, conv->tailBlockSize, true); spa_fga_dsp_fft_memclear(dsp, conv->tailPrecalculated, conv->tailBlockSize, true); } conv->tailInputFill = 0; } struct convolver *convolver_new(struct spa_fga_dsp *dsp, int head_block, int tail_block, const float *ir, int irlen) { struct convolver *conv; int head_ir_len; if (head_block == 0 || tail_block == 0) return NULL; head_block = SPA_MAX(1, head_block); if (head_block > tail_block) SPA_SWAP(head_block, tail_block); while (irlen > 0 && fabs(ir[irlen-1]) < 0.000001f) irlen--; conv = calloc(1, sizeof(*conv)); if (conv == NULL) return NULL; conv->dsp = dsp; if (irlen == 0) return conv; conv->headBlockSize = next_power_of_two(head_block); conv->tailBlockSize = next_power_of_two(tail_block); head_ir_len = SPA_MIN(irlen, conv->tailBlockSize); conv->headConvolver = convolver1_new(dsp, conv->headBlockSize, ir, head_ir_len); if (conv->headConvolver == NULL) goto error; if (irlen > conv->tailBlockSize) { int conv1IrLen = SPA_MIN(irlen - conv->tailBlockSize, conv->tailBlockSize); conv->tailConvolver0 = convolver1_new(dsp, conv->headBlockSize, ir + conv->tailBlockSize, conv1IrLen); conv->tailOutput0 = spa_fga_dsp_fft_memalloc(dsp, conv->tailBlockSize, true); conv->tailPrecalculated0 = spa_fga_dsp_fft_memalloc(dsp, conv->tailBlockSize, true); if (conv->tailConvolver0 == NULL || conv->tailOutput0 == NULL || conv->tailPrecalculated0 == NULL) goto error; } if (irlen > 2 * conv->tailBlockSize) { int tailIrLen = irlen - (2 * conv->tailBlockSize); conv->tailConvolver = convolver1_new(dsp, conv->tailBlockSize, ir + (2 * conv->tailBlockSize), tailIrLen); conv->tailOutput = spa_fga_dsp_fft_memalloc(dsp, conv->tailBlockSize, true); conv->tailPrecalculated = spa_fga_dsp_fft_memalloc(dsp, conv->tailBlockSize, true); if (conv->tailConvolver == NULL || conv->tailOutput == NULL || conv->tailPrecalculated == NULL) goto error; } if (conv->tailConvolver0 || conv->tailConvolver) { conv->tailInput = spa_fga_dsp_fft_memalloc(dsp, conv->tailBlockSize, true); if (conv->tailInput == NULL) goto error; } convolver_reset(conv); return conv; error: convolver_free(conv); return NULL; } void convolver_free(struct convolver *conv) { struct spa_fga_dsp *dsp = conv->dsp; if (conv->headConvolver) convolver1_free(dsp, conv->headConvolver); if (conv->tailConvolver0) convolver1_free(dsp, conv->tailConvolver0); if (conv->tailConvolver) convolver1_free(dsp, conv->tailConvolver); spa_fga_dsp_fft_memfree(dsp, conv->tailOutput0); spa_fga_dsp_fft_memfree(dsp, conv->tailPrecalculated0); spa_fga_dsp_fft_memfree(dsp, conv->tailOutput); spa_fga_dsp_fft_memfree(dsp, conv->tailPrecalculated); spa_fga_dsp_fft_memfree(dsp, conv->tailInput); free(conv); } int convolver_run(struct convolver *conv, const float *input, float *output, int length) { struct spa_fga_dsp *dsp = conv->dsp; convolver1_run(dsp, conv->headConvolver, input, output, length); if (conv->tailInput) { int processed = 0; while (processed < length) { int remaining = length - processed; int processing = SPA_MIN(remaining, conv->headBlockSize - (conv->tailInputFill % conv->headBlockSize)); if (conv->tailPrecalculated0) spa_fga_dsp_sum(dsp, &output[processed], &output[processed], &conv->tailPrecalculated0[conv->tailInputFill], processing); if (conv->tailPrecalculated) spa_fga_dsp_sum(dsp, &output[processed], &output[processed], &conv->tailPrecalculated[conv->tailInputFill], processing); spa_fga_dsp_copy(dsp, conv->tailInput + conv->tailInputFill, input + processed, processing); conv->tailInputFill += processing; if (conv->tailPrecalculated0 && (conv->tailInputFill % conv->headBlockSize == 0)) { int blockOffset = conv->tailInputFill - conv->headBlockSize; convolver1_run(dsp, conv->tailConvolver0, conv->tailInput + blockOffset, conv->tailOutput0 + blockOffset, conv->headBlockSize); if (conv->tailInputFill == conv->tailBlockSize) SPA_SWAP(conv->tailPrecalculated0, conv->tailOutput0); } if (conv->tailPrecalculated && conv->tailInputFill == conv->tailBlockSize) { SPA_SWAP(conv->tailPrecalculated, conv->tailOutput); convolver1_run(dsp, conv->tailConvolver, conv->tailInput, conv->tailOutput, conv->tailBlockSize); } if (conv->tailInputFill == conv->tailBlockSize) conv->tailInputFill = 0; processed += processing; } } return 0; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/filter-graph/convolver.h000066400000000000000000000007211511204443500275140ustar00rootroot00000000000000/* PipeWire */ /* SPDX-FileCopyrightText: Copyright © 2021 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include "audio-dsp.h" struct convolver *convolver_new(struct spa_fga_dsp *dsp, int block, int tail, const float *ir, int irlen); void convolver_free(struct convolver *conv); void convolver_reset(struct convolver *conv); int convolver_run(struct convolver *conv, const float *input, float *output, int length); pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/filter-graph/filter-graph.c000066400000000000000000002014761511204443500300700ustar00rootroot00000000000000/* PipeWire */ /* SPDX-FileCopyrightText: Copyright © 2021 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "audio-plugin.h" #include "audio-dsp-impl.h" #undef SPA_LOG_TOPIC_DEFAULT #define SPA_LOG_TOPIC_DEFAULT &log_topic SPA_LOG_TOPIC_DEFINE_STATIC(log_topic, "spa.filter-graph"); #define MAX_HNDL 64 #define MAX_CHANNELS SPA_AUDIO_MAX_CHANNELS #define DEFAULT_RATE 48000 #define spa_filter_graph_emit(hooks,method,version,...) \ spa_hook_list_call_simple(hooks, struct spa_filter_graph_events, \ method, version, ##__VA_ARGS__) #define spa_filter_graph_emit_info(hooks,...) spa_filter_graph_emit(hooks,info, 0, __VA_ARGS__) #define spa_filter_graph_emit_apply_props(hooks,...) spa_filter_graph_emit(hooks,apply_props, 0, __VA_ARGS__) #define spa_filter_graph_emit_props_changed(hooks,...) spa_filter_graph_emit(hooks,props_changed, 0, __VA_ARGS__) struct plugin { struct spa_list link; struct impl *impl; int ref; char type[256]; char path[PATH_MAX]; struct spa_handle *hndl; struct spa_fga_plugin *plugin; struct spa_list descriptor_list; }; struct descriptor { struct spa_list link; int ref; struct plugin *plugin; char *label; const struct spa_fga_descriptor *desc; uint32_t n_input; uint32_t n_output; uint32_t n_control; uint32_t n_notify; unsigned long *input; unsigned long *output; unsigned long *control; unsigned long *notify; float *default_control; }; struct port { struct spa_list link; struct node *node; uint32_t idx; unsigned long p; struct spa_list link_list; uint32_t n_links; uint32_t external; float control_data[MAX_HNDL]; float *audio_data[MAX_HNDL]; void *audio_mem[MAX_HNDL]; }; struct node { struct spa_list link; struct graph *graph; struct descriptor *desc; char name[256]; char *config; struct port *input_port; struct port *output_port; struct port *control_port; struct port *notify_port; uint32_t n_hndl; void *hndl[MAX_HNDL]; unsigned int n_deps; uint32_t latency_index; float min_latency; float max_latency; unsigned int disabled:1; unsigned int control_changed:1; unsigned int n_sort_deps; unsigned int sorted:1; }; struct link { struct spa_list link; struct spa_list input_link; struct spa_list output_link; struct port *output; struct port *input; }; struct graph_port { const struct spa_fga_descriptor *desc; void **hndl; uint32_t port; struct node *node; unsigned next:1; }; struct graph_hndl { const struct spa_fga_descriptor *desc; void **hndl; }; struct volume { bool mute; uint32_t n_volumes; float volumes[MAX_CHANNELS]; uint32_t n_ports; struct port *ports[MAX_CHANNELS]; float min[MAX_CHANNELS]; float max[MAX_CHANNELS]; #define SCALE_LINEAR 0 #define SCALE_CUBIC 1 int scale[MAX_CHANNELS]; }; struct graph { struct impl *impl; uint32_t n_nodes; struct spa_list node_list; struct spa_list link_list; uint32_t n_input; struct graph_port *input; uint32_t n_output; struct graph_port *output; uint32_t n_hndl; struct graph_hndl *hndl; uint32_t n_control; struct port **control_port; uint32_t n_input_names; char **input_names; uint32_t n_output_names; char **output_names; struct volume volume[2]; uint32_t n_inputs; uint32_t n_outputs; uint32_t inputs_position[MAX_CHANNELS]; uint32_t n_inputs_position; uint32_t outputs_position[MAX_CHANNELS]; uint32_t n_outputs_position; float min_latency; float max_latency; unsigned activated:1; unsigned setup:1; }; struct impl { struct spa_handle handle; struct spa_filter_graph filter_graph; struct spa_hook_list hooks; struct spa_log *log; struct spa_cpu *cpu; struct spa_fga_dsp *dsp; struct spa_plugin_loader *loader; uint64_t info_all; struct spa_filter_graph_info info; struct graph graph; uint32_t quantum_limit; uint32_t max_align; long unsigned rate; struct spa_list plugin_list; float *silence_data; float *discard_data; }; static inline void print_channels(char *buffer, size_t max_size, uint32_t n_positions, uint32_t *positions) { uint32_t i; struct spa_strbuf buf; char pos[8]; spa_strbuf_init(&buf, buffer, max_size); spa_strbuf_append(&buf, "["); for (i = 0; i < n_positions; i++) { spa_strbuf_append(&buf, "%s%s", i ? "," : "", spa_type_audio_channel_make_short_name(positions[i], pos, sizeof(pos), "UNK")); } spa_strbuf_append(&buf, "]"); } static void emit_filter_graph_info(struct impl *impl, bool full) { uint64_t old = full ? impl->info.change_mask : 0; struct graph *graph = &impl->graph; if (full) impl->info.change_mask = impl->info_all; if (impl->info.change_mask || full) { char n_inputs[64], n_outputs[64], latency[64]; struct spa_dict_item items[6]; struct spa_dict dict = SPA_DICT(items, 0); char in_pos[MAX_CHANNELS * 8]; char out_pos[MAX_CHANNELS * 8]; snprintf(n_inputs, sizeof(n_inputs), "%d", impl->graph.n_inputs); snprintf(n_outputs, sizeof(n_outputs), "%d", impl->graph.n_outputs); items[dict.n_items++] = SPA_DICT_ITEM("n_inputs", n_inputs); items[dict.n_items++] = SPA_DICT_ITEM("n_outputs", n_outputs); if (graph->n_inputs_position) { print_channels(in_pos, sizeof(in_pos), graph->n_inputs_position, graph->inputs_position); items[dict.n_items++] = SPA_DICT_ITEM("inputs.audio.position", in_pos); } if (graph->n_outputs_position) { print_channels(out_pos, sizeof(out_pos), graph->n_outputs_position, graph->outputs_position); items[dict.n_items++] = SPA_DICT_ITEM("outputs.audio.position", out_pos); } items[dict.n_items++] = SPA_DICT_ITEM("latency", spa_dtoa(latency, sizeof(latency), (graph->min_latency + graph->max_latency) / 2.0f)); impl->info.props = &dict; spa_filter_graph_emit_info(&impl->hooks, &impl->info); impl->info.props = NULL; impl->info.change_mask = old; } } static int impl_add_listener(void *object, struct spa_hook *listener, const struct spa_filter_graph_events *events, void *data) { struct impl *impl = object; struct spa_hook_list save; spa_log_trace(impl->log, "%p: add listener %p", impl, listener); spa_hook_list_isolate(&impl->hooks, &save, listener, events, data); emit_filter_graph_info(impl, true); spa_hook_list_join(&impl->hooks, &save); return 0; } static int impl_process(void *object, const void *in[], void *out[], uint32_t n_samples) { struct impl *impl = object; struct graph *graph = &impl->graph; uint32_t i, j, n_hndl = graph->n_hndl; struct graph_port *port; for (i = 0, j = 0; i < graph->n_inputs; i++) { while (j < graph->n_input) { port = &graph->input[j++]; if (port->desc && in[i]) port->desc->connect_port(*port->hndl, port->port, (float*)in[i]); if (!port->next) break; } } for (i = 0; i < graph->n_outputs; i++) { if (out[i] == NULL) continue; port = &graph->output[i]; if (port->desc) port->desc->connect_port(*port->hndl, port->port, out[i]); else memset(out[i], 0, n_samples * sizeof(float)); } for (i = 0; i < n_hndl; i++) { struct graph_hndl *hndl = &graph->hndl[i]; hndl->desc->run(*hndl->hndl, n_samples); } return 0; } static float get_default(struct impl *impl, struct descriptor *desc, uint32_t p) { struct spa_fga_port *port = &desc->desc->ports[p]; return port->def; } static struct node *find_node(struct graph *graph, const char *name) { struct node *node; spa_list_for_each(node, &graph->node_list, link) { if (spa_streq(node->name, name)) return node; } return NULL; } #if !defined(strdupa) # define strdupa(s) \ ({ \ const char *__old = (s); \ size_t __len = strlen(__old) + 1; \ char *__new = (char *) alloca(__len); \ (char *) memcpy(__new, __old, __len); \ }) #endif /* find a port by name. Valid syntax is: * ":" * ":" * "" * "" * When no node_name is given, the port is assumed in the current node. */ static struct port *find_port(struct node *node, const char *name, int descriptor) { char *col, *node_name, *port_name, *str; struct port *ports; const struct spa_fga_descriptor *d; uint32_t i, n_ports, port_id = SPA_ID_INVALID; str = strdupa(name); col = strchr(str, ':'); if (col != NULL) { struct node *find; node_name = str; port_name = col + 1; *col = '\0'; find = find_node(node->graph, node_name); if (find == NULL) { /* it's possible that the : is part of the port name, * try again without splitting things up. */ *col = ':'; col = NULL; } else { node = find; } } if (col == NULL) { node_name = node->name; port_name = str; } if (node == NULL) return NULL; if (!spa_atou32(port_name, &port_id, 0)) port_id = SPA_ID_INVALID; if (SPA_FGA_IS_PORT_INPUT(descriptor)) { if (SPA_FGA_IS_PORT_CONTROL(descriptor)) { ports = node->control_port; n_ports = node->desc->n_control; } else { ports = node->input_port; n_ports = node->desc->n_input; } } else if (SPA_FGA_IS_PORT_OUTPUT(descriptor)) { if (SPA_FGA_IS_PORT_CONTROL(descriptor)) { ports = node->notify_port; n_ports = node->desc->n_notify; } else { ports = node->output_port; n_ports = node->desc->n_output; } } else return NULL; d = node->desc->desc; for (i = 0; i < n_ports; i++) { struct port *port = &ports[i]; if (i == port_id || spa_streq(d->ports[port->p].name, port_name)) return port; } return NULL; } static int impl_enum_prop_info(void *object, uint32_t idx, struct spa_pod_builder *b, struct spa_pod **param) { struct impl *impl = object; struct graph *graph = &impl->graph; struct spa_pod *pod; struct spa_pod_frame f[2]; struct port *port; struct node *node; struct descriptor *desc; const struct spa_fga_descriptor *d; struct spa_fga_port *p; float def, min, max; char name[512]; uint32_t rate = impl->rate ? impl->rate : DEFAULT_RATE; if (idx >= graph->n_control) return 0; port = graph->control_port[idx]; node = port->node; desc = node->desc; d = desc->desc; p = &d->ports[port->p]; if (p->hint & SPA_FGA_HINT_SAMPLE_RATE) { def = p->def * rate; min = p->min * rate; max = p->max * rate; } else { def = p->def; min = p->min; max = p->max; } if (node->name[0] != '\0') snprintf(name, sizeof(name), "%s:%s", node->name, p->name); else snprintf(name, sizeof(name), "%s", p->name); spa_pod_builder_push_object(b, &f[0], SPA_TYPE_OBJECT_PropInfo, SPA_PARAM_PropInfo); spa_pod_builder_add (b, SPA_PROP_INFO_name, SPA_POD_String(name), 0); spa_pod_builder_prop(b, SPA_PROP_INFO_type, 0); if (p->hint & SPA_FGA_HINT_BOOLEAN) { if (min == max) { spa_pod_builder_bool(b, def <= 0.0f ? false : true); } else { spa_pod_builder_push_choice(b, &f[1], SPA_CHOICE_Enum, 0); spa_pod_builder_bool(b, def <= 0.0f ? false : true); spa_pod_builder_bool(b, false); spa_pod_builder_bool(b, true); spa_pod_builder_pop(b, &f[1]); } } else if (p->hint & SPA_FGA_HINT_INTEGER) { if (min == max) { spa_pod_builder_int(b, (int32_t)def); } else { spa_pod_builder_push_choice(b, &f[1], SPA_CHOICE_Range, 0); spa_pod_builder_int(b, (int32_t)def); spa_pod_builder_int(b, (int32_t)min); spa_pod_builder_int(b, (int32_t)max); spa_pod_builder_pop(b, &f[1]); } } else { if (min == max) { spa_pod_builder_float(b, def); } else { spa_pod_builder_push_choice(b, &f[1], SPA_CHOICE_Range, 0); spa_pod_builder_float(b, def); spa_pod_builder_float(b, min); spa_pod_builder_float(b, max); spa_pod_builder_pop(b, &f[1]); } } spa_pod_builder_prop(b, SPA_PROP_INFO_params, 0); spa_pod_builder_bool(b, true); pod = spa_pod_builder_pop(b, &f[0]); if (pod == NULL) return -ENOSPC; if (param) *param = pod; return 1; } static int impl_get_props(void *object, struct spa_pod_builder *b, struct spa_pod **props) { struct impl *impl = object; struct graph *graph = &impl->graph; struct spa_pod_frame f[2]; uint32_t i; char name[512]; struct spa_pod *res; spa_pod_builder_push_object(b, &f[0], SPA_TYPE_OBJECT_Props, SPA_PARAM_Props); spa_pod_builder_prop(b, SPA_PROP_params, 0); spa_pod_builder_push_struct(b, &f[1]); for (i = 0; i < graph->n_control; i++) { struct port *port = graph->control_port[i]; struct node *node = port->node; struct descriptor *desc = node->desc; const struct spa_fga_descriptor *d = desc->desc; struct spa_fga_port *p = &d->ports[port->p]; if (node->name[0] != '\0') snprintf(name, sizeof(name), "%s:%s", node->name, p->name); else snprintf(name, sizeof(name), "%s", p->name); spa_pod_builder_string(b, name); if (p->hint & SPA_FGA_HINT_BOOLEAN) { spa_pod_builder_bool(b, port->control_data[0] <= 0.0f ? false : true); } else if (p->hint & SPA_FGA_HINT_INTEGER) { spa_pod_builder_int(b, (int32_t)port->control_data[0]); } else { spa_pod_builder_float(b, port->control_data[0]); } } spa_pod_builder_pop(b, &f[1]); res = spa_pod_builder_pop(b, &f[0]); if (res == NULL) return -ENOSPC; if (props) *props = res; return 1; } static int port_set_control_value(struct port *port, float *value, uint32_t id) { struct node *node = port->node; struct impl *impl = node->graph->impl; struct descriptor *desc = node->desc; float old; bool changed; old = port->control_data[id]; port->control_data[id] = value ? *value : desc->default_control[port->idx]; spa_log_info(impl->log, "control %d %d ('%s') from %f to %f", port->idx, id, desc->desc->ports[port->p].name, old, port->control_data[id]); changed = old != port->control_data[id]; node->control_changed |= changed; return changed ? 1 : 0; } static int set_control_value(struct node *node, const char *name, float *value) { struct port *port; int count = 0; uint32_t i, n_hndl; port = find_port(node, name, SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL); if (port == NULL) return -ENOENT; /* if we don't have any instances yet, set the first control value, we will * copy to other instances later */ n_hndl = SPA_MAX(1u, port->node->n_hndl); for (i = 0; i < n_hndl; i++) count += port_set_control_value(port, value, i); return count; } static int parse_params(struct graph *graph, const struct spa_pod *pod) { struct spa_pod_parser prs; struct spa_pod_frame f; int res, changed = 0; struct node *def_node; def_node = spa_list_first(&graph->node_list, struct node, link); spa_pod_parser_pod(&prs, pod); if (spa_pod_parser_push_struct(&prs, &f) < 0) return 0; while (true) { const char *name, *str_val; float value, *val = NULL; double dbl_val; bool bool_val; int32_t int_val; int64_t long_val; if (spa_pod_parser_get_string(&prs, &name) < 0) break; if (spa_pod_parser_get_float(&prs, &value) >= 0) { val = &value; } else if (spa_pod_parser_get_double(&prs, &dbl_val) >= 0) { value = (float)dbl_val; val = &value; } else if (spa_pod_parser_get_int(&prs, &int_val) >= 0) { value = int_val; val = &value; } else if (spa_pod_parser_get_long(&prs, &long_val) >= 0) { value = long_val; val = &value; } else if (spa_pod_parser_get_bool(&prs, &bool_val) >= 0) { value = bool_val ? 1.0f : 0.0f; val = &value; } else if (spa_pod_parser_get_string(&prs, &str_val) >= 0 && spa_json_parse_float(str_val, strlen(str_val), &value) >= 0) { val = &value; } else { struct spa_pod *pod; spa_pod_parser_get_pod(&prs, &pod); } if ((res = set_control_value(def_node, name, val)) > 0) changed += res; } return changed; } static int impl_reset(void *object) { struct impl *impl = object; struct graph *graph = &impl->graph; uint32_t i; for (i = 0; i < graph->n_hndl; i++) { struct graph_hndl *hndl = &graph->hndl[i]; const struct spa_fga_descriptor *d = hndl->desc; if (hndl->hndl == NULL || *hndl->hndl == NULL) continue; if (d->deactivate) d->deactivate(*hndl->hndl); if (d->activate) d->activate(*hndl->hndl); } return 0; } static void node_control_changed(struct node *node) { const struct spa_fga_descriptor *d = node->desc->desc; uint32_t i; if (!node->control_changed) return; for (i = 0; i < node->n_hndl; i++) { if (node->hndl[i] == NULL) continue; if (d->control_changed) d->control_changed(node->hndl[i]); } node->control_changed = false; } static int sync_volume(struct graph *graph, struct volume *vol) { uint32_t i; int res = 0; if (vol->n_ports == 0) return 0; for (i = 0; i < vol->n_volumes; i++) { uint32_t n_port = i % vol->n_ports, n_hndl; struct port *p = vol->ports[n_port]; float v = vol->mute ? 0.0f : vol->volumes[i]; switch (vol->scale[n_port]) { case SCALE_CUBIC: v = cbrtf(v); break; } v = v * (vol->max[n_port] - vol->min[n_port]) + vol->min[n_port]; n_hndl = SPA_MAX(1u, p->node->n_hndl); res += port_set_control_value(p, &v, i % n_hndl); } return res; } static int impl_set_props(void *object, enum spa_direction direction, const struct spa_pod *props) { struct impl *impl = object; struct spa_pod_object *obj = (struct spa_pod_object *) props; struct spa_pod_frame f[1]; const struct spa_pod_prop *prop; struct graph *graph = &impl->graph; int changed = 0; char buf[1024]; struct spa_pod_dynamic_builder b; struct volume *vol = &graph->volume[direction]; bool do_volume = false; spa_pod_dynamic_builder_init(&b, buf, sizeof(buf), 1024); spa_pod_builder_push_object(&b.b, &f[0], SPA_TYPE_OBJECT_Props, SPA_PARAM_Props); SPA_POD_OBJECT_FOREACH(obj, prop) { switch (prop->key) { case SPA_PROP_params: changed += parse_params(graph, &prop->value); spa_pod_builder_raw_padded(&b.b, prop, SPA_POD_PROP_SIZE(prop)); break; case SPA_PROP_mute: { bool mute; if (spa_pod_get_bool(&prop->value, &mute) == 0) { if (vol->mute != mute) { vol->mute = mute; do_volume = true; } } spa_pod_builder_raw_padded(&b.b, prop, SPA_POD_PROP_SIZE(prop)); break; } case SPA_PROP_channelVolumes: { uint32_t i, n_vols; float vols[MAX_CHANNELS]; if ((n_vols = spa_pod_copy_array(&prop->value, SPA_TYPE_Float, vols, SPA_N_ELEMENTS(vols))) > 0) { if (vol->n_volumes != n_vols) do_volume = true; vol->n_volumes = n_vols; for (i = 0; i < n_vols; i++) { float v = vols[i]; if (v != vol->volumes[i]) { vol->volumes[i] = v; do_volume = true; } } } spa_pod_builder_raw_padded(&b.b, prop, SPA_POD_PROP_SIZE(prop)); break; } case SPA_PROP_softVolumes: case SPA_PROP_softMute: break; default: spa_pod_builder_raw_padded(&b.b, prop, SPA_POD_PROP_SIZE(prop)); break; } } if (do_volume && vol->n_ports != 0) { float soft_vols[MAX_CHANNELS]; uint32_t i; for (i = 0; i < vol->n_volumes; i++) soft_vols[i] = (vol->mute || vol->volumes[i] == 0.0f) ? 0.0f : 1.0f; spa_pod_builder_prop(&b.b, SPA_PROP_softMute, 0); spa_pod_builder_bool(&b.b, vol->mute); spa_pod_builder_prop(&b.b, SPA_PROP_softVolumes, 0); spa_pod_builder_array(&b.b, sizeof(float), SPA_TYPE_Float, vol->n_volumes, soft_vols); props = spa_pod_builder_pop(&b.b, &f[0]); sync_volume(graph, vol); } else { props = spa_pod_builder_pop(&b.b, &f[0]); } spa_filter_graph_emit_apply_props(&impl->hooks, direction, props); spa_pod_dynamic_builder_clean(&b); if (changed > 0) { struct node *node; spa_list_for_each(node, &graph->node_list, link) node_control_changed(node); spa_filter_graph_emit_props_changed(&impl->hooks, SPA_DIRECTION_INPUT); } return 0; } static uint32_t count_array(struct spa_json *json) { struct spa_json it = *json; char v[256]; uint32_t count = 0; while (spa_json_get_string(&it, v, sizeof(v)) > 0) count++; return count; } static void plugin_unref(struct plugin *hndl) { struct impl *impl = hndl->impl; if (--hndl->ref > 0) return; spa_list_remove(&hndl->link); if (hndl->hndl) spa_plugin_loader_unload(impl->loader, hndl->hndl); free(hndl); } static inline const char *split_walk(const char *str, const char *delimiter, size_t * len, const char **state) { const char *s = *state ? *state : str; s += strspn(s, delimiter); if (*s == '\0') return NULL; *len = strcspn(s, delimiter); *state = s + *len; return s; } static struct plugin *plugin_load(struct impl *impl, const char *type, const char *path) { struct spa_handle *hndl = NULL; struct plugin *plugin; char module[PATH_MAX]; char factory_name[256], dsp_ptr[256]; void *iface; int res; spa_list_for_each(plugin, &impl->plugin_list, link) { if (spa_streq(plugin->type, type) && spa_streq(plugin->path, path)) { plugin->ref++; return plugin; } } spa_scnprintf(module, sizeof(module), "filter-graph/libspa-filter-graph-plugin-%s", type); spa_scnprintf(factory_name, sizeof(factory_name), "filter.graph.plugin.%s", type); spa_scnprintf(dsp_ptr, sizeof(dsp_ptr), "pointer:%p", impl->dsp); hndl = spa_plugin_loader_load(impl->loader, factory_name, &SPA_DICT_ITEMS( SPA_DICT_ITEM(SPA_KEY_LIBRARY_NAME, module), SPA_DICT_ITEM("filter.graph.path", path), SPA_DICT_ITEM("filter.graph.audio.dsp", dsp_ptr))); if (hndl == NULL) { res = -errno; spa_log_error(impl->log, "can't load plugin type '%s': %m", type); goto exit; } if ((res = spa_handle_get_interface(hndl, SPA_TYPE_INTERFACE_FILTER_GRAPH_AudioPlugin, &iface)) < 0) { spa_log_error(impl->log, "can't find iface '%s': %s", SPA_TYPE_INTERFACE_FILTER_GRAPH_AudioPlugin, spa_strerror(res)); goto exit; } plugin = calloc(1, sizeof(*plugin)); if (!plugin) { res = -errno; goto exit; } plugin->ref = 1; snprintf(plugin->type, sizeof(plugin->type), "%s", type); snprintf(plugin->path, sizeof(plugin->path), "%s", path); spa_log_info(impl->log, "successfully opened '%s':'%s'", type, path); plugin->impl = impl; plugin->hndl = hndl; plugin->plugin = iface; spa_list_init(&plugin->descriptor_list); spa_list_append(&impl->plugin_list, &plugin->link); return plugin; exit: if (hndl) spa_plugin_loader_unload(impl->loader, hndl); errno = -res; return NULL; } static void descriptor_unref(struct descriptor *desc) { if (--desc->ref > 0) return; spa_list_remove(&desc->link); if (desc->desc) spa_fga_descriptor_free(desc->desc); plugin_unref(desc->plugin); free(desc->label); free(desc->input); free(desc->output); free(desc->control); free(desc->default_control); free(desc->notify); free(desc); } static struct descriptor *descriptor_load(struct impl *impl, const char *type, const char *plugin, const char *label) { struct plugin *pl; struct descriptor *desc; const struct spa_fga_descriptor *d; uint32_t i, n_input, n_output, n_control, n_notify; unsigned long p; int res; if ((pl = plugin_load(impl, type, plugin)) == NULL) return NULL; spa_list_for_each(desc, &pl->descriptor_list, link) { if (spa_streq(desc->label, label)) { desc->ref++; /* * since ladspa_handle_load() increments the reference count of the handle, * if the descriptor is found, then the handle's reference count * has already been incremented to account for the descriptor, * so we need to unref handle here since we're merely reusing * thedescriptor, not creating a new one */ plugin_unref(pl); return desc; } } desc = calloc(1, sizeof(*desc)); desc->ref = 1; desc->plugin = pl; spa_list_init(&desc->link); if ((d = spa_fga_plugin_make_desc(pl->plugin, label)) == NULL) { spa_log_error(impl->log, "cannot create label %s", label); res = -ENOENT; goto exit; } desc->desc = d; desc->label = strdup(label); n_input = n_output = n_control = n_notify = 0; for (p = 0; p < d->n_ports; p++) { struct spa_fga_port *fp = &d->ports[p]; if (SPA_FGA_IS_PORT_AUDIO(fp->flags)) { if (SPA_FGA_IS_PORT_INPUT(fp->flags)) n_input++; else if (SPA_FGA_IS_PORT_OUTPUT(fp->flags)) n_output++; } else if (SPA_FGA_IS_PORT_CONTROL(fp->flags)) { if (SPA_FGA_IS_PORT_INPUT(fp->flags)) n_control++; else if (SPA_FGA_IS_PORT_OUTPUT(fp->flags)) n_notify++; } } desc->input = calloc(n_input, sizeof(unsigned long)); desc->output = calloc(n_output, sizeof(unsigned long)); desc->control = calloc(n_control, sizeof(unsigned long)); desc->default_control = calloc(n_control, sizeof(float)); desc->notify = calloc(n_notify, sizeof(unsigned long)); for (p = 0; p < d->n_ports; p++) { struct spa_fga_port *fp = &d->ports[p]; if (SPA_FGA_IS_PORT_AUDIO(fp->flags)) { if (SPA_FGA_IS_PORT_INPUT(fp->flags)) { spa_log_info(impl->log, "using port %lu ('%s') as input %d", p, fp->name, desc->n_input); desc->input[desc->n_input++] = p; } else if (SPA_FGA_IS_PORT_OUTPUT(fp->flags)) { spa_log_info(impl->log, "using port %lu ('%s') as output %d", p, fp->name, desc->n_output); desc->output[desc->n_output++] = p; } } else if (SPA_FGA_IS_PORT_CONTROL(fp->flags)) { if (SPA_FGA_IS_PORT_INPUT(fp->flags)) { spa_log_info(impl->log, "using port %lu ('%s') as control %d", p, fp->name, desc->n_control); desc->control[desc->n_control++] = p; } else if (SPA_FGA_IS_PORT_OUTPUT(fp->flags)) { spa_log_info(impl->log, "using port %lu ('%s') as notify %d", p, fp->name, desc->n_notify); desc->notify[desc->n_notify++] = p; } } } if (desc->n_input == 0 && desc->n_output == 0 && desc->n_control == 0 && desc->n_notify == 0) { spa_log_error(impl->log, "plugin has no input and no output ports"); res = -ENOTSUP; goto exit; } for (i = 0; i < desc->n_control; i++) { p = desc->control[i]; desc->default_control[i] = get_default(impl, desc, p); spa_log_info(impl->log, "control %d ('%s') default to %f", i, d->ports[p].name, desc->default_control[i]); } spa_list_append(&pl->descriptor_list, &desc->link); return desc; exit: descriptor_unref(desc); errno = -res; return NULL; } /** * { * ... * } */ static char *copy_value(struct impl *impl, struct spa_json *value) { const char *val, *s = value->cur; int len; struct spa_error_location loc; char *result = NULL; if ((len = spa_json_next(value, &val)) <= 0) { errno = EINVAL; goto done; } if (spa_json_is_null(val, len)) goto done; if (spa_json_is_container(val, len)) { len = spa_json_container_len(value, val, len); if (len == 0) { errno = EINVAL; goto done; } } if ((result = malloc(len+1)) == NULL) goto done; spa_json_parse_stringn(val, len, result, len+1); done: if (spa_json_get_error(value, s, &loc)) spa_debug_log_error_location(impl->log, SPA_LOG_LEVEL_WARN, &loc, "error: %s", loc.reason); return result; } /** * { * "Reverb tail" = 2.0 * ... * } */ static int parse_control(struct node *node, struct spa_json *control) { struct impl *impl = node->graph->impl; char key[256]; const char *val; int len; while ((len = spa_json_object_next(control, key, sizeof(key), &val)) > 0) { float fl; int res; if (spa_json_parse_float(val, len, &fl) <= 0) { spa_log_warn(impl->log, "control '%s' expects a number, ignoring", key); } else if ((res = set_control_value(node, key, &fl)) < 0) { spa_log_warn(impl->log, "control '%s' can not be set: %s", key, spa_strerror(res)); } } return 0; } /** * output = [name:][portname] * input = [name:][portname] * ... */ static int parse_link(struct graph *graph, struct spa_json *json) { struct impl *impl = graph->impl; char key[256]; char output[256] = ""; char input[256] = ""; const char *val; struct node *def_in_node, *def_out_node; struct port *in_port, *out_port; struct link *link; int len; if (spa_list_is_empty(&graph->node_list)) { spa_log_error(impl->log, "can't make links in graph without nodes"); return -EINVAL; } while ((len = spa_json_object_next(json, key, sizeof(key), &val)) > 0) { if (spa_streq(key, "output")) { if (spa_json_parse_stringn(val, len, output, sizeof(output)) <= 0) { spa_log_error(impl->log, "output expects a string"); return -EINVAL; } } else if (spa_streq(key, "input")) { if (spa_json_parse_stringn(val, len, input, sizeof(input)) <= 0) { spa_log_error(impl->log, "input expects a string"); return -EINVAL; } } else { spa_log_error(impl->log, "unexpected link key '%s'", key); } } def_out_node = spa_list_first(&graph->node_list, struct node, link); def_in_node = spa_list_last(&graph->node_list, struct node, link); out_port = find_port(def_out_node, output, SPA_FGA_PORT_OUTPUT); in_port = find_port(def_in_node, input, SPA_FGA_PORT_INPUT); if (out_port == NULL && in_port == NULL) { /* try control ports */ out_port = find_port(def_out_node, output, SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_CONTROL); in_port = find_port(def_in_node, input, SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL); } if (in_port == NULL || out_port == NULL) { if (out_port == NULL) spa_log_error(impl->log, "unknown output port %s", output); if (in_port == NULL) spa_log_error(impl->log, "unknown input port %s", input); return -ENOENT; } if (in_port->n_links > 0) { spa_log_info(impl->log, "Can't have more than 1 link to %s, use a mixer", input); return -ENOTSUP; } if ((link = calloc(1, sizeof(*link))) == NULL) return -errno; link->output = out_port; link->input = in_port; spa_log_info(impl->log, "linking %s:%s -> %s:%s", out_port->node->name, out_port->node->desc->desc->ports[out_port->p].name, in_port->node->name, in_port->node->desc->desc->ports[in_port->p].name); spa_list_append(&out_port->link_list, &link->output_link); out_port->n_links++; spa_list_append(&in_port->link_list, &link->input_link); in_port->n_links++; in_port->node->n_deps++; spa_list_append(&graph->link_list, &link->link); return 0; } static void link_free(struct link *link) { spa_list_remove(&link->input_link); link->input->n_links--; link->input->node->n_deps--; spa_list_remove(&link->output_link); link->output->n_links--; spa_list_remove(&link->link); free(link); } /** * { * control = [name:][portname] * min = * max = * scale = * } */ static int parse_volume(struct graph *graph, struct spa_json *json, enum spa_direction direction) { struct impl *impl = graph->impl; char key[256]; char control[256] = ""; char scale[64] = "linear"; float min = 0.0f, max = 1.0f; const char *val; struct node *def_control; struct port *port; struct volume *vol = &graph->volume[direction]; int len; if (spa_list_is_empty(&graph->node_list)) { spa_log_error(impl->log, "can't set volume in graph without nodes"); return -EINVAL; } while ((len = spa_json_object_next(json, key, sizeof(key), &val)) > 0) { if (spa_streq(key, "control")) { if (spa_json_parse_stringn(val, len, control, sizeof(control)) <= 0) { spa_log_error(impl->log, "control expects a string"); return -EINVAL; } } else if (spa_streq(key, "min")) { if (spa_json_parse_float(val, len, &min) <= 0) { spa_log_error(impl->log, "min expects a float"); return -EINVAL; } } else if (spa_streq(key, "max")) { if (spa_json_parse_float(val, len, &max) <= 0) { spa_log_error(impl->log, "max expects a float"); return -EINVAL; } } else if (spa_streq(key, "scale")) { if (spa_json_parse_stringn(val, len, scale, sizeof(scale)) <= 0) { spa_log_error(impl->log, "scale expects a string"); return -EINVAL; } } else { spa_log_error(impl->log, "unexpected volume key '%s'", key); } } if (direction == SPA_DIRECTION_INPUT) def_control = spa_list_first(&graph->node_list, struct node, link); else def_control = spa_list_last(&graph->node_list, struct node, link); port = find_port(def_control, control, SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL); if (port == NULL) { spa_log_error(impl->log, "unknown control port %s", control); return -ENOENT; } if (vol->n_ports >= MAX_CHANNELS) { spa_log_error(impl->log, "too many volume controls"); return -ENOSPC; } if (spa_streq(scale, "linear")) { vol->scale[vol->n_ports] = SCALE_LINEAR; } else if (spa_streq(scale, "cubic")) { vol->scale[vol->n_ports] = SCALE_CUBIC; } else { spa_log_error(impl->log, "Invalid scale value '%s', use one of linear or cubic", scale); return -EINVAL; } spa_log_info(impl->log, "volume %d: \"%s:%s\" min:%f max:%f scale:%s", vol->n_ports, port->node->name, port->node->desc->desc->ports[port->p].name, min, max, scale); vol->ports[vol->n_ports] = port; vol->min[vol->n_ports] = min; vol->max[vol->n_ports] = max; vol->n_ports++; return 0; } /** * type = ladspa * name = rev * plugin = g2reverb * label = G2reverb * config = { * ... * } * control = { * ... * } */ static int load_node(struct graph *graph, struct spa_json *json) { struct impl *impl = graph->impl; struct spa_json control, it; struct descriptor *desc; struct node *node; const char *val; char key[256]; char type[256] = ""; char name[256] = ""; char plugin[256] = ""; spa_autofree char *label = NULL; spa_autofree char *config = NULL; bool have_control = false; uint32_t i; int len; while ((len = spa_json_object_next(json, key, sizeof(key), &val)) > 0) { if (spa_streq("type", key)) { if (spa_json_parse_stringn(val, len, type, sizeof(type)) <= 0) { spa_log_error(impl->log, "type expects a string"); return -EINVAL; } } else if (spa_streq("name", key)) { if (spa_json_parse_stringn(val, len, name, sizeof(name)) <= 0) { spa_log_error(impl->log, "name expects a string"); return -EINVAL; } } else if (spa_streq("plugin", key)) { if (spa_json_parse_stringn(val, len, plugin, sizeof(plugin)) <= 0) { spa_log_error(impl->log, "plugin expects a string"); return -EINVAL; } } else if (spa_streq("label", key)) { it = SPA_JSON_START(json, val); if ((label = copy_value(impl, &it)) == NULL) { spa_log_warn(impl->log, "error parsing label: %s", spa_strerror(-errno)); return -EINVAL; } } else if (spa_streq("control", key)) { if (!spa_json_is_object(val, len)) { spa_log_error(impl->log, "control expects an object"); return -EINVAL; } spa_json_enter(json, &control); have_control = true; } else if (spa_streq("config", key)) { it = SPA_JSON_START(json, val); if ((config = copy_value(impl, &it)) == NULL) spa_log_warn(impl->log, "error parsing config: %s", spa_strerror(-errno)); } else { spa_log_warn(impl->log, "unexpected node key '%s'", key); } } if (spa_streq(type, "builtin")) snprintf(plugin, sizeof(plugin), "%s", "builtin"); else if (spa_streq(type, "")) { spa_log_error(impl->log, "missing plugin type"); return -EINVAL; } spa_log_info(impl->log, "loading type:%s plugin:%s label:%s", type, plugin, label); if ((desc = descriptor_load(graph->impl, type, plugin, label ? label : "")) == NULL) return -errno; node = calloc(1, sizeof(*node)); if (node == NULL) return -errno; node->graph = graph; node->desc = desc; snprintf(node->name, sizeof(node->name), "%s", name); node->latency_index = SPA_IDX_INVALID; node->config = spa_steal_ptr(config); node->input_port = calloc(desc->n_input, sizeof(struct port)); node->output_port = calloc(desc->n_output, sizeof(struct port)); node->control_port = calloc(desc->n_control, sizeof(struct port)); node->notify_port = calloc(desc->n_notify, sizeof(struct port)); spa_log_info(impl->log, "loaded n_input:%d n_output:%d n_control:%d n_notify:%d", desc->n_input, desc->n_output, desc->n_control, desc->n_notify); for (i = 0; i < desc->n_input; i++) { struct port *port = &node->input_port[i]; port->node = node; port->idx = i; port->external = SPA_ID_INVALID; port->p = desc->input[i]; spa_list_init(&port->link_list); } for (i = 0; i < desc->n_output; i++) { struct port *port = &node->output_port[i]; port->node = node; port->idx = i; port->external = SPA_ID_INVALID; port->p = desc->output[i]; spa_list_init(&port->link_list); } for (i = 0; i < desc->n_control; i++) { struct port *port = &node->control_port[i]; port->node = node; port->idx = i; port->external = SPA_ID_INVALID; port->p = desc->control[i]; spa_list_init(&port->link_list); port->control_data[0] = desc->default_control[i]; } for (i = 0; i < desc->n_notify; i++) { struct port *port = &node->notify_port[i]; port->node = node; port->idx = i; port->external = SPA_ID_INVALID; port->p = desc->notify[i]; if (desc->desc->ports[port->p].hint & SPA_FGA_HINT_LATENCY) node->latency_index = i; spa_list_init(&port->link_list); } if (have_control) parse_control(node, &control); spa_list_append(&graph->node_list, &node->link); graph->n_nodes++; graph->n_control += desc->n_control; return 0; } static void node_cleanup(struct node *node) { const struct spa_fga_descriptor *d = node->desc->desc; struct impl *impl = node->graph->impl; uint32_t i; for (i = 0; i < node->n_hndl; i++) { if (node->hndl[i] == NULL) continue; spa_log_info(impl->log, "cleanup %s %s[%d]", d->name, node->name, i); if (d->deactivate) d->deactivate(node->hndl[i]); d->cleanup(node->hndl[i]); node->hndl[i] = NULL; } } static int port_ensure_data(struct port *port, uint32_t i, uint32_t max_samples) { float *data; struct node *node = port->node; const struct spa_fga_descriptor *d = node->desc->desc; struct impl *impl = node->graph->impl; if ((data = port->audio_mem[i]) == NULL) { data = calloc(max_samples, sizeof(float) + impl->max_align); if (data == NULL) { spa_log_error(impl->log, "cannot create port data: %m"); return -errno; } port->audio_mem[i] = data; port->audio_data[i] = SPA_PTR_ALIGN(data, impl->max_align, void); } spa_log_info(impl->log, "connect output port %s[%d]:%s %p", node->name, i, d->ports[port->p].name, port->audio_data[i]); d->connect_port(port->node->hndl[i], port->p, port->audio_data[i]); return 0; } static void port_free_data(struct port *port, uint32_t i) { free(port->audio_mem[i]); port->audio_mem[i] = NULL; port->audio_data[i] = NULL; } static void node_free(struct node *node) { uint32_t i, j; spa_list_remove(&node->link); for (i = 0; i < node->n_hndl; i++) { for (j = 0; j < node->desc->n_output; j++) port_free_data(&node->output_port[j], i); } node_cleanup(node); descriptor_unref(node->desc); free(node->input_port); free(node->output_port); free(node->control_port); free(node->notify_port); free(node->config); free(node); } static int impl_deactivate(void *object) { struct impl *impl = object; struct graph *graph = &impl->graph; struct node *node; if (!graph->activated) return 0; graph->activated = false; spa_list_for_each(node, &graph->node_list, link) node_cleanup(node); return 0; } static void sort_reset(struct graph *graph) { struct node *node; spa_list_for_each(node, &graph->node_list, link) { node->sorted = false; node->n_sort_deps = node->n_deps; } } static struct node *sort_next_node(struct graph *graph) { struct node *node; spa_list_for_each(node, &graph->node_list, link) { if (node->n_sort_deps == 0 && !node->sorted) { uint32_t i; struct link *link; node->sorted = true; for (i = 0; i < node->desc->n_output; i++) { spa_list_for_each(link, &node->output_port[i].link_list, output_link) link->input->node->n_sort_deps--; } for (i = 0; i < node->desc->n_notify; i++) { spa_list_for_each(link, &node->notify_port[i].link_list, output_link) link->input->node->n_sort_deps--; } return node; } } return NULL; } static int setup_graph(struct graph *graph); static int impl_activate(void *object, const struct spa_dict *props) { struct impl *impl = object; struct graph *graph = &impl->graph; struct node *node; struct port *port; struct link *link; struct descriptor *desc; const struct spa_fga_descriptor *d; const struct spa_fga_plugin *p; uint32_t i, j, max_samples = impl->quantum_limit, n_ports; int res; float *sd, *dd, *data, min_latency, max_latency; const char *rate, *str; if (graph->activated) return 0; graph->activated = true; rate = spa_dict_lookup(props, SPA_KEY_AUDIO_RATE); impl->rate = rate ? atoi(rate) : DEFAULT_RATE; if ((str = spa_dict_lookup(props, "filter-graph.n_inputs")) != NULL) { if (spa_atou32(str, &n_ports, 0) && n_ports != graph->n_inputs) { graph->n_inputs = n_ports; graph->n_outputs = 0; impl->info.change_mask |= SPA_FILTER_GRAPH_CHANGE_MASK_PROPS; graph->setup = false; } } if ((str = spa_dict_lookup(props, "filter-graph.n_outputs")) != NULL) { if (spa_atou32(str, &n_ports, 0) && n_ports != graph->n_outputs) { graph->n_outputs = n_ports; graph->n_inputs = 0; impl->info.change_mask |= SPA_FILTER_GRAPH_CHANGE_MASK_PROPS; graph->setup = false; } } if (!graph->setup) { if ((res = setup_graph(graph)) < 0) return res; graph->setup = true; } /* first make instances */ spa_list_for_each(node, &graph->node_list, link) { node_cleanup(node); desc = node->desc; d = desc->desc; p = desc->plugin->plugin; for (i = 0; i < node->n_hndl; i++) { spa_log_info(impl->log, "instantiate %s %s[%d] rate:%lu", d->name, node->name, i, impl->rate); errno = EINVAL; if ((node->hndl[i] = d->instantiate(p, d, impl->rate, i, node->config)) == NULL) { spa_log_error(impl->log, "cannot create plugin instance %d rate:%lu: %m", i, impl->rate); res = -errno; goto error; } } } /* then link ports */ spa_list_for_each(node, &graph->node_list, link) { desc = node->desc; d = desc->desc; if (d->flags & SPA_FGA_DESCRIPTOR_SUPPORTS_NULL_DATA) { sd = dd = NULL; } else { sd = impl->silence_data; dd = impl->discard_data; } for (i = 0; i < node->n_hndl; i++) { for (j = 0; j < desc->n_input; j++) { port = &node->input_port[j]; if (!spa_list_is_empty(&port->link_list)) { link = spa_list_first(&port->link_list, struct link, input_link); if ((res = port_ensure_data(link->output, i, max_samples)) < 0) goto error; data = link->output->audio_data[i]; } else if (SPA_FGA_SUPPORTS_NULL_DATA(d->ports[port->p].flags)) { data = NULL; } else { data = sd; } spa_log_info(impl->log, "connect input port %s[%d]:%s %p", node->name, i, d->ports[port->p].name, data); d->connect_port(node->hndl[i], port->p, data); } for (j = 0; j < desc->n_output; j++) { port = &node->output_port[j]; if (port->audio_data[i] == NULL) { if (SPA_FGA_SUPPORTS_NULL_DATA(d->ports[port->p].flags)) data = NULL; else data = dd; spa_log_info(impl->log, "connect output port %s[%d]:%s %p", node->name, i, d->ports[port->p].name, data); d->connect_port(node->hndl[i], port->p, data); } } for (j = 0; j < desc->n_control; j++) { port = &node->control_port[j]; if (!spa_list_is_empty(&port->link_list)) { link = spa_list_first(&port->link_list, struct link, input_link); data = &link->output->control_data[i]; } else { data = &port->control_data[i]; } spa_log_info(impl->log, "connect control port %s[%d]:%s %p", node->name, i, d->ports[port->p].name, data); d->connect_port(node->hndl[i], port->p, data); } for (j = 0; j < desc->n_notify; j++) { port = &node->notify_port[j]; spa_log_info(impl->log, "connect notify port %s[%d]:%s %p", node->name, i, d->ports[port->p].name, &port->control_data[i]); d->connect_port(node->hndl[i], port->p, &port->control_data[i]); } } } /* now activate */ spa_list_for_each(node, &graph->node_list, link) { desc = node->desc; d = desc->desc; for (i = 0; i < node->n_hndl; i++) { if (d->activate) d->activate(node->hndl[i]); if (node->control_changed && d->control_changed) d->control_changed(node->hndl[i]); } } /* calculate latency */ sort_reset(graph); while ((node = sort_next_node(graph)) != NULL) { min_latency = FLT_MAX; max_latency = 0.0f; for (i = 0; i < node->desc->n_input; i++) { spa_list_for_each(link, &node->input_port[i].link_list, input_link) { min_latency = fminf(min_latency, link->output->node->min_latency); max_latency = fmaxf(max_latency, link->output->node->max_latency); } } min_latency = min_latency == FLT_MAX ? 0.0f : min_latency; if (node->latency_index != SPA_IDX_INVALID) { port = &node->notify_port[node->latency_index]; min_latency += port->control_data[0]; max_latency += port->control_data[0]; } node->min_latency = min_latency; node->max_latency = max_latency; spa_log_info(impl->log, "%s latency:%f-%f", node->name, min_latency, max_latency); } min_latency = FLT_MAX; max_latency = 0.0f; for (i = 0; i < graph->n_outputs; i++) { struct graph_port *port = &graph->output[i]; /* ports with no descriptor are ignored */ if (port->desc == NULL) continue; max_latency = fmaxf(max_latency, port->node->max_latency); min_latency = fminf(min_latency, port->node->min_latency); } min_latency = min_latency == FLT_MAX ? 0.0f : min_latency; spa_log_info(impl->log, "graph latency min:%f max:%f", min_latency, max_latency); if (min_latency != max_latency) { spa_log_warn(impl->log, "graph has unaligned latency min:%f max:%f, " "consider adding delays or tweak node latency to " "align the signals", min_latency, max_latency); for (i = 0; i < graph->n_outputs; i++) { struct graph_port *port = &graph->output[i]; /* port with no descriptor are ignored */ if (port->desc == NULL) continue; if (min_latency != port->node->min_latency || max_latency != port->node->max_latency) spa_log_warn(impl->log, "output port %d from %s min:%f max:%f", i, port->node->name, port->node->min_latency, port->node->max_latency); } } if (graph->min_latency != min_latency || graph->max_latency != max_latency) { graph->min_latency = min_latency; graph->max_latency = max_latency; impl->info.change_mask |= SPA_FILTER_GRAPH_CHANGE_MASK_PROPS; } emit_filter_graph_info(impl, false); spa_filter_graph_emit_props_changed(&impl->hooks, SPA_DIRECTION_INPUT); return 0; error: impl_deactivate(impl); return res; } static void unsetup_graph(struct graph *graph) { struct node *node; uint32_t i; free(graph->input); graph->input = NULL; free(graph->output); graph->output = NULL; free(graph->hndl); graph->hndl = NULL; spa_list_for_each(node, &graph->node_list, link) { struct descriptor *desc = node->desc; for (i = 0; i < desc->n_input; i++) { struct port *port = &node->input_port[i]; port->external = SPA_ID_INVALID; } for (i = 0; i < desc->n_output; i++) { struct port *port = &node->output_port[i]; port->external = SPA_ID_INVALID; } } } static int setup_graph(struct graph *graph) { struct impl *impl = graph->impl; struct node *node, *first, *last; struct port *port; struct graph_port *gp; struct graph_hndl *gh; uint32_t i, j, n, n_input, n_output, n_hndl = 0; int res; struct descriptor *desc; const struct spa_fga_descriptor *d; char *pname; bool allow_unused; unsetup_graph(graph); first = spa_list_first(&graph->node_list, struct node, link); last = spa_list_last(&graph->node_list, struct node, link); /* calculate the number of inputs and outputs into the graph. * If we have a list of inputs/outputs, just use them. Otherwise * we count all input ports of the first node and all output * ports of the last node */ if (graph->n_input_names != 0) n_input = graph->n_input_names; else n_input = first->desc->n_input; if (graph->n_output_names != 0) n_output = graph->n_output_names; else n_output = last->desc->n_output; /* we allow unconnected ports when not explicitly given and the nodes support * NULL data */ allow_unused = graph->n_input_names == 0 && graph->n_output_names == 0 && SPA_FLAG_IS_SET(first->desc->desc->flags, SPA_FGA_DESCRIPTOR_SUPPORTS_NULL_DATA) && SPA_FLAG_IS_SET(last->desc->desc->flags, SPA_FGA_DESCRIPTOR_SUPPORTS_NULL_DATA); if (n_input == 0) { spa_log_error(impl->log, "no inputs"); res = -EINVAL; goto error; } if (n_output == 0) { spa_log_error(impl->log, "no outputs"); res = -EINVAL; goto error; } if (graph->n_inputs == 0) graph->n_inputs = impl->info.n_inputs; if (graph->n_inputs == 0) graph->n_inputs = n_input; if (graph->n_outputs == 0) graph->n_outputs = impl->info.n_outputs; /* compare to the requested number of inputs and duplicate the * graph n_hndl times when needed. */ n_hndl = graph->n_inputs / n_input; if (graph->n_outputs == 0) graph->n_outputs = n_output * n_hndl; if (n_hndl != graph->n_outputs / n_output) { spa_log_error(impl->log, "invalid ports. The input stream has %1$d ports and " "the filter has %2$d inputs. The output stream has %3$d ports " "and the filter has %4$d outputs. input:%1$d / input:%2$d != " "output:%3$d / output:%4$d. Check inputs and outputs objects.", graph->n_inputs, n_input, graph->n_outputs, n_output); res = -EINVAL; goto error; } if (n_hndl > MAX_HNDL) { spa_log_error(impl->log, "too many ports. %d > %d", n_hndl, MAX_HNDL); res = -EINVAL; goto error; } if (n_hndl == 0) { n_hndl = 1; if (!allow_unused) spa_log_warn(impl->log, "The input stream has %1$d ports and " "the filter has %2$d inputs. The output stream has %3$d ports " "and the filter has %4$d outputs. Some filter ports will be " "unconnected..", graph->n_inputs, n_input, graph->n_outputs, n_output); if (graph->n_outputs == 0) graph->n_outputs = n_output * n_hndl; } spa_log_info(impl->log, "using %d instances %d %d", n_hndl, n_input, n_output); graph->n_input = 0; graph->input = calloc(n_input * 16 * n_hndl, sizeof(struct graph_port)); graph->n_output = 0; graph->output = calloc(n_output * n_hndl, sizeof(struct graph_port)); /* now collect all input and output ports for all the handles. */ for (i = 0; i < n_hndl; i++) { if (graph->n_input_names == 0) { desc = first->desc; d = desc->desc; for (j = 0; j < desc->n_input; j++) { gp = &graph->input[graph->n_input++]; spa_log_info(impl->log, "input port %s[%d]:%s", first->name, i, d->ports[desc->input[j]].name); gp->desc = d; gp->node = first; gp->hndl = &first->hndl[i]; gp->port = desc->input[j]; } } else { for (n = 0; n < graph->n_input_names; n++) { pname = graph->input_names[n]; if (spa_streq(pname, "null")) { gp = &graph->input[graph->n_input++]; gp->desc = NULL; spa_log_info(impl->log, "ignore input port %d", graph->n_input); } else if ((port = find_port(first, pname, SPA_FGA_PORT_INPUT)) == NULL) { res = -ENOENT; spa_log_error(impl->log, "input port %s not found", pname); goto error; } else { bool disabled = false; desc = port->node->desc; d = desc->desc; if (i == 0 && port->external != SPA_ID_INVALID) { spa_log_error(impl->log, "input port %s[%d]:%s already used as input %d, use mixer", port->node->name, i, d->ports[port->p].name, port->external); res = -EBUSY; goto error; } if (port->n_links > 0) { spa_log_error(impl->log, "input port %s[%d]:%s already used by link, use mixer", port->node->name, i, d->ports[port->p].name); res = -EBUSY; goto error; } if (d->flags & SPA_FGA_DESCRIPTOR_COPY) { for (j = 0; j < desc->n_output; j++) { struct port *p = &port->node->output_port[j]; struct link *link; gp = NULL; spa_list_for_each(link, &p->link_list, output_link) { struct port *peer = link->input; spa_log_info(impl->log, "copy input port %s[%d]:%s", port->node->name, i, d->ports[port->p].name); peer->external = graph->n_input; gp = &graph->input[graph->n_input++]; gp->desc = peer->node->desc->desc; gp->node = peer->node; gp->hndl = &peer->node->hndl[i]; gp->port = peer->p; gp->next = true; disabled = true; } if (gp != NULL) gp->next = false; } port->node->disabled = disabled; } if (!disabled) { spa_log_info(impl->log, "input port %s[%d]:%s", port->node->name, i, d->ports[port->p].name); port->external = graph->n_input; gp = &graph->input[graph->n_input++]; gp->desc = d; gp->node = port->node; gp->hndl = &port->node->hndl[i]; gp->port = port->p; gp->next = false; } } } } if (graph->n_output_names == 0) { desc = last->desc; d = desc->desc; for (j = 0; j < desc->n_output; j++) { gp = &graph->output[graph->n_output++]; spa_log_info(impl->log, "output port %s[%d]:%s", last->name, i, d->ports[desc->output[j]].name); gp->desc = d; gp->node = last; gp->hndl = &last->hndl[i]; gp->port = desc->output[j]; } } else { for (n = 0; n < graph->n_output_names; n++) { pname = graph->output_names[n]; gp = &graph->output[graph->n_output]; if (spa_streq(pname, "null")) { gp->desc = NULL; spa_log_info(impl->log, "silence output port %d", graph->n_output); } else if ((port = find_port(last, pname, SPA_FGA_PORT_OUTPUT)) == NULL) { res = -ENOENT; spa_log_error(impl->log, "output port %s not found", pname); goto error; } else { desc = port->node->desc; d = desc->desc; if (i == 0 && port->external != SPA_ID_INVALID) { spa_log_error(impl->log, "output port %s[%d]:%s already used as output %d, use copy", port->node->name, i, d->ports[port->p].name, port->external); res = -EBUSY; goto error; } if (port->n_links > 0) { spa_log_error(impl->log, "output port %s[%d]:%s already used by link, use copy", port->node->name, i, d->ports[port->p].name); res = -EBUSY; goto error; } spa_log_info(impl->log, "output port %s[%d]:%s", port->node->name, i, d->ports[port->p].name); port->external = graph->n_output; gp->desc = d; gp->node = port->node; gp->hndl = &port->node->hndl[i]; gp->port = port->p; } graph->n_output++; } } } graph->n_hndl = 0; graph->hndl = calloc(graph->n_nodes * n_hndl, sizeof(struct graph_hndl)); /* order all nodes based on dependencies, first reset fields */ sort_reset(graph); while ((node = sort_next_node(graph)) != NULL) { node->n_hndl = n_hndl; desc = node->desc; d = desc->desc; if (!node->disabled) { for (i = 0; i < n_hndl; i++) { gh = &graph->hndl[graph->n_hndl++]; gh->hndl = &node->hndl[i]; gh->desc = d; } } for (i = 0; i < desc->n_control; i++) { /* any default values for the controls are set in the first instance * of the control data. Duplicate this to the other instances now. */ struct port *port = &node->control_port[i]; for (j = 1; j < n_hndl; j++) port->control_data[j] = port->control_data[0]; } } res = 0; error: return res; } static int setup_graph_controls(struct graph *graph) { struct node *node; uint32_t i, n_control = 0; graph->control_port = calloc(graph->n_control, sizeof(struct port *)); if (graph->control_port == NULL) return -errno; spa_list_for_each(node, &graph->node_list, link) { /* collect all control ports on the graph */ for (i = 0; i < node->desc->n_control; i++) graph->control_port[n_control++] = &node->control_port[i]; } return 0; } /** * filter.graph = { * nodes = [ * { ... } ... * ] * links = [ * { ... } ... * ] * inputs = [ ] * outputs = [ ] * input.volumes = [ * ... * ] * output.volumes = [ * ... * ] * } */ static int load_graph(struct graph *graph, const struct spa_dict *props) { struct impl *impl = graph->impl; struct spa_json it[2]; struct spa_json inputs, outputs, *pinputs = NULL, *poutputs = NULL; struct spa_json ivolumes, ovolumes, *pivolumes = NULL, *povolumes = NULL; struct spa_json nodes, *pnodes = NULL, links, *plinks = NULL; const char *json, *val; char key[256]; int res, len; spa_list_init(&graph->node_list); spa_list_init(&graph->link_list); if ((json = spa_dict_lookup(props, "filter.graph")) == NULL) { spa_log_error(impl->log, "missing filter.graph property"); return -EINVAL; } if (spa_json_begin_object(&it[0], json, strlen(json)) <= 0) { spa_log_error(impl->log, "filter.graph must be an object"); return -EINVAL; } while ((len = spa_json_object_next(&it[0], key, sizeof(key), &val)) > 0) { if (spa_streq("n_inputs", key)) { if (spa_json_parse_int(val, len, &res) <= 0) { spa_log_error(impl->log, "%s expects an integer", key); return -EINVAL; } impl->info.n_inputs = res; } else if (spa_streq("n_outputs", key)) { if (spa_json_parse_int(val, len, &res) <= 0) { spa_log_error(impl->log, "%s expects an integer", key); return -EINVAL; } impl->info.n_outputs = res; } else if (spa_streq("inputs.audio.position", key)) { if (!spa_json_is_array(val, len) || (len = spa_json_container_len(&it[0], val, len)) < 0) { spa_log_error(impl->log, "%s expects an array", key); return -EINVAL; } spa_audio_parse_position_n(val, len, graph->inputs_position, SPA_N_ELEMENTS(graph->inputs_position), &graph->n_inputs_position); impl->info.n_inputs = graph->n_inputs_position; } else if (spa_streq("outputs.audio.position", key)) { if (!spa_json_is_array(val, len) || (len = spa_json_container_len(&it[0], val, len)) < 0) { spa_log_error(impl->log, "%s expects an array", key); return -EINVAL; } spa_audio_parse_position_n(val, len, graph->outputs_position, SPA_N_ELEMENTS(graph->outputs_position), &graph->n_outputs_position); impl->info.n_outputs = graph->n_outputs_position; } else if (spa_streq("nodes", key)) { if (!spa_json_is_array(val, len)) { spa_log_error(impl->log, "%s expects an array", key); return -EINVAL; } spa_json_enter(&it[0], &nodes); pnodes = &nodes; } else if (spa_streq("links", key)) { if (!spa_json_is_array(val, len)) { spa_log_error(impl->log, "%s expects an array", key); return -EINVAL; } spa_json_enter(&it[0], &links); plinks = &links; } else if (spa_streq("inputs", key)) { if (!spa_json_is_array(val, len)) { spa_log_error(impl->log, "%s expects an array", key); return -EINVAL; } spa_json_enter(&it[0], &inputs); pinputs = &inputs; } else if (spa_streq("outputs", key)) { if (!spa_json_is_array(val, len)) { spa_log_error(impl->log, "%s expects an array", key); return -EINVAL; } spa_json_enter(&it[0], &outputs); poutputs = &outputs; } else if (spa_streq("capture.volumes", key) || spa_streq("input.volumes", key)) { if (!spa_json_is_array(val, len)) { spa_log_error(impl->log, "%s expects an array", key); return -EINVAL; } spa_json_enter(&it[0], &ivolumes); pivolumes = &ivolumes; } else if (spa_streq("playback.volumes", key) || spa_streq("output.volumes", key)) { if (!spa_json_is_array(val, len)) { spa_log_error(impl->log, "%s expects an array", key); return -EINVAL; } spa_json_enter(&it[0], &ovolumes); povolumes = &ovolumes; } else { spa_log_warn(impl->log, "unexpected graph key '%s'", key); } } if (pnodes == NULL) { spa_log_error(impl->log, "filter.graph is missing a nodes array"); return -EINVAL; } while (spa_json_enter_object(pnodes, &it[1]) > 0) { if ((res = load_node(graph, &it[1])) < 0) return res; } if (plinks != NULL) { while (spa_json_enter_object(plinks, &it[1]) > 0) { if ((res = parse_link(graph, &it[1])) < 0) return res; } } if (pivolumes != NULL) { while (spa_json_enter_object(pivolumes, &it[1]) > 0) { if ((res = parse_volume(graph, &it[1], SPA_DIRECTION_INPUT)) < 0) return res; } } if (povolumes != NULL) { while (spa_json_enter_object(povolumes, &it[1]) > 0) { if ((res = parse_volume(graph, &it[1], SPA_DIRECTION_OUTPUT)) < 0) return res; } } if (pinputs != NULL) { graph->n_input_names = count_array(pinputs); graph->input_names = calloc(graph->n_input_names, sizeof(char *)); graph->n_input_names = 0; while (spa_json_get_string(pinputs, key, sizeof(key)) > 0) graph->input_names[graph->n_input_names++] = strdup(key); } if (poutputs != NULL) { graph->n_output_names = count_array(poutputs); graph->output_names = calloc(graph->n_output_names, sizeof(char *)); graph->n_output_names = 0; while (spa_json_get_string(poutputs, key, sizeof(key)) > 0) graph->output_names[graph->n_output_names++] = strdup(key); } if ((res = setup_graph_controls(graph)) < 0) return res; return 0; } static void graph_free(struct graph *graph) { struct link *link; struct node *node; uint32_t i; unsetup_graph(graph); spa_list_consume(link, &graph->link_list, link) link_free(link); spa_list_consume(node, &graph->node_list, link) node_free(node); for (i = 0; i < graph->n_input_names; i++) free(graph->input_names[i]); free(graph->input_names); for (i = 0; i < graph->n_output_names; i++) free(graph->output_names[i]); free(graph->output_names); free(graph->control_port); graph->control_port = NULL; } static const struct spa_filter_graph_methods impl_filter_graph = { SPA_VERSION_FILTER_GRAPH_METHODS, .add_listener = impl_add_listener, .enum_prop_info = impl_enum_prop_info, .get_props = impl_get_props, .set_props = impl_set_props, .activate = impl_activate, .deactivate = impl_deactivate, .reset = impl_reset, .process = impl_process, }; static int impl_get_interface(struct spa_handle *handle, const char *type, void **interface) { struct impl *this; spa_return_val_if_fail(handle != NULL, -EINVAL); spa_return_val_if_fail(interface != NULL, -EINVAL); this = (struct impl *) handle; if (spa_streq(type, SPA_TYPE_INTERFACE_FilterGraph)) *interface = &this->filter_graph; else return -ENOENT; return 0; } static int impl_clear(struct spa_handle *handle) { struct impl *impl = (struct impl *) handle; graph_free(&impl->graph); if (impl->dsp) spa_fga_dsp_free(impl->dsp); free(impl->silence_data); free(impl->discard_data); return 0; } static size_t impl_get_size(const struct spa_handle_factory *factory, const struct spa_dict *params) { return sizeof(struct impl); } static int impl_init(const struct spa_handle_factory *factory, struct spa_handle *handle, const struct spa_dict *info, const struct spa_support *support, uint32_t n_support) { struct impl *impl; uint32_t i; int res; handle->get_interface = impl_get_interface; handle->clear = impl_clear; impl = (struct impl *) handle; impl->graph.impl = impl; impl->log = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log); spa_log_topic_init(impl->log, &log_topic); impl->cpu = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_CPU); impl->max_align = spa_cpu_get_max_align(impl->cpu); impl->dsp = spa_fga_dsp_new(impl->cpu ? spa_cpu_get_flags(impl->cpu) : 0); impl->loader = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_PluginLoader); spa_list_init(&impl->plugin_list); for (i = 0; info && i < info->n_items; i++) { const char *k = info->items[i].key; const char *s = info->items[i].value; if (spa_streq(k, "clock.quantum-limit")) spa_atou32(s, &impl->quantum_limit, 0); if (spa_streq(k, "filter-graph.n_inputs")) spa_atou32(s, &impl->info.n_inputs, 0); if (spa_streq(k, "filter-graph.n_outputs")) spa_atou32(s, &impl->info.n_outputs, 0); } if (impl->quantum_limit == 0) return -EINVAL; impl->silence_data = calloc(impl->quantum_limit, sizeof(float)); if (impl->silence_data == NULL) { res = -errno; goto error; } impl->discard_data = calloc(impl->quantum_limit, sizeof(float)); if (impl->discard_data == NULL) { res = -errno; goto error; } if ((res = load_graph(&impl->graph, info)) < 0) { spa_log_error(impl->log, "can't load graph: %s", spa_strerror(res)); goto error; } impl->filter_graph.iface = SPA_INTERFACE_INIT( SPA_TYPE_INTERFACE_FilterGraph, SPA_VERSION_FILTER_GRAPH, &impl_filter_graph, impl); spa_hook_list_init(&impl->hooks); return 0; error: free(impl->silence_data); free(impl->discard_data); return res; } static const struct spa_interface_info impl_interfaces[] = { {SPA_TYPE_INTERFACE_FilterGraph,}, }; static int impl_enum_interface_info(const struct spa_handle_factory *factory, const struct spa_interface_info **info, uint32_t *index) { spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(info != NULL, -EINVAL); spa_return_val_if_fail(index != NULL, -EINVAL); switch (*index) { case 0: *info = &impl_interfaces[*index]; break; default: return 0; } (*index)++; return 1; } static struct spa_handle_factory spa_filter_graph_factory = { SPA_VERSION_HANDLE_FACTORY, "filter.graph", NULL, impl_get_size, impl_init, impl_enum_interface_info, }; SPA_EXPORT int spa_handle_factory_enum(const struct spa_handle_factory **factory, uint32_t *index) { spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(index != NULL, -EINVAL); switch (*index) { case 0: *factory = &spa_filter_graph_factory; break; default: return 0; } (*index)++; return 1; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/filter-graph/ladspa.h000066400000000000000000000656431511204443500267610ustar00rootroot00000000000000/* ladspa.h Linux Audio Developer's Simple Plugin API Version 1.1[LGPL]. Copyright (C) 2000-2002 Richard W.E. Furse, Paul Barton-Davis, Stefan Westerfeld. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. */ #ifndef LADSPA_INCLUDED #define LADSPA_INCLUDED #define LADSPA_VERSION "1.1" #define LADSPA_VERSION_MAJOR 1 #define LADSPA_VERSION_MINOR 1 #ifdef __cplusplus extern "C" { #endif /*****************************************************************************/ /* Overview: There is a large number of synthesis packages in use or development on the Linux platform at this time. This API (`The Linux Audio Developer's Simple Plugin API') attempts to give programmers the ability to write simple `plugin' audio processors in C/C++ and link them dynamically (`plug') into a range of these packages (`hosts'). It should be possible for any host and any plugin to communicate completely through this interface. This API is deliberately short and simple. To achieve compatibility with a range of promising Linux sound synthesis packages it attempts to find the `greatest common divisor' in their logical behaviour. Having said this, certain limiting decisions are implicit, notably the use of a fixed type (LADSPA_Data) for all data transfer and absence of a parameterised `initialisation' phase. See below for the LADSPA_Data typedef. Plugins are expected to distinguish between control and audio data. Plugins have `ports' that are inputs or outputs for audio or control data and each plugin is `run' for a `block' corresponding to a short time interval measured in samples. Audio data is communicated using arrays of LADSPA_Data, allowing a block of audio to be processed by the plugin in a single pass. Control data is communicated using single LADSPA_Data values. Control data has a single value at the start of a call to the `run()' or `run_adding()' function, and may be considered to remain this value for its duration. The plugin may assume that all its input and output ports have been connected to the relevant data location (see the `connect_port()' function below) before it is asked to run. Plugins will reside in shared object files suitable for dynamic linking by dlopen() and family. The file will provide a number of `plugin types' that can be used to instantiate actual plugins (sometimes known as `plugin instances') that can be connected together to perform tasks. This API contains very limited error-handling. */ /*****************************************************************************/ /* Fundamental data type passed in and out of plugin. This data type is used to communicate audio samples and control values. It is assumed that the plugin will work sensibly given any numeric input value although it may have a preferred range (see hints below). For audio it is generally assumed that 1.0f is the `0dB' reference amplitude and is a `normal' signal level. */ typedef float LADSPA_Data; /*****************************************************************************/ /* Special Plugin Properties: Optional features of the plugin type are encapsulated in the LADSPA_Properties type. This is assembled by ORing individual properties together. */ typedef int LADSPA_Properties; /* Property LADSPA_PROPERTY_REALTIME indicates that the plugin has a real-time dependency (e.g. listens to a MIDI device) and so its output must not be cached or subject to significant latency. */ #define LADSPA_PROPERTY_REALTIME 0x1 /* Property LADSPA_PROPERTY_INPLACE_BROKEN indicates that the plugin may cease to work correctly if the host elects to use the same data location for both input and output (see connect_port()). This should be avoided as enabling this flag makes it impossible for hosts to use the plugin to process audio `in-place.' */ #define LADSPA_PROPERTY_INPLACE_BROKEN 0x2 /* Property LADSPA_PROPERTY_HARD_RT_CAPABLE indicates that the plugin is capable of running not only in a conventional host but also in a `hard real-time' environment. To qualify for this the plugin must satisfy all of the following: (1) The plugin must not use malloc(), free() or other heap memory management within its run() or run_adding() functions. All new memory used in run() must be managed via the stack. These restrictions only apply to the run() function. (2) The plugin will not attempt to make use of any library functions with the exceptions of functions in the ANSI standard C and C maths libraries, which the host is expected to provide. (3) The plugin will not access files, devices, pipes, sockets, IPC or any other mechanism that might result in process or thread blocking. (4) The plugin will take an amount of time to execute a run() or run_adding() call approximately of form (A+B*SampleCount) where A and B depend on the machine and host in use. This amount of time may not depend on input signals or plugin state. The host is left the responsibility to perform timings to estimate upper bounds for A and B. */ #define LADSPA_PROPERTY_HARD_RT_CAPABLE 0x4 #define LADSPA_IS_REALTIME(x) ((x) & LADSPA_PROPERTY_REALTIME) #define LADSPA_IS_INPLACE_BROKEN(x) ((x) & LADSPA_PROPERTY_INPLACE_BROKEN) #define LADSPA_IS_HARD_RT_CAPABLE(x) ((x) & LADSPA_PROPERTY_HARD_RT_CAPABLE) /*****************************************************************************/ /* Plugin Ports: Plugins have `ports' that are inputs or outputs for audio or data. Ports can communicate arrays of LADSPA_Data (for audio inputs/outputs) or single LADSPA_Data values (for control input/outputs). This information is encapsulated in the LADSPA_PortDescriptor type which is assembled by ORing individual properties together. Note that a port must be an input or an output port but not both and that a port must be a control or audio port but not both. */ typedef int LADSPA_PortDescriptor; /* Property LADSPA_PORT_INPUT indicates that the port is an input. */ #define LADSPA_PORT_INPUT 0x1 /* Property LADSPA_PORT_OUTPUT indicates that the port is an output. */ #define LADSPA_PORT_OUTPUT 0x2 /* Property LADSPA_PORT_CONTROL indicates that the port is a control port. */ #define LADSPA_PORT_CONTROL 0x4 /* Property LADSPA_PORT_AUDIO indicates that the port is a audio port. */ #define LADSPA_PORT_AUDIO 0x8 #define LADSPA_IS_PORT_INPUT(x) ((x) & LADSPA_PORT_INPUT) #define LADSPA_IS_PORT_OUTPUT(x) ((x) & LADSPA_PORT_OUTPUT) #define LADSPA_IS_PORT_CONTROL(x) ((x) & LADSPA_PORT_CONTROL) #define LADSPA_IS_PORT_AUDIO(x) ((x) & LADSPA_PORT_AUDIO) /*****************************************************************************/ /* Plugin Port Range Hints: The host may wish to provide a representation of data entering or leaving a plugin (e.g. to generate a GUI automatically). To make this more meaningful, the plugin should provide `hints' to the host describing the usual values taken by the data. Note that these are only hints. The host may ignore them and the plugin must not assume that data supplied to it is meaningful. If the plugin receives invalid input data it is expected to continue to run without failure and, where possible, produce a sensible output (e.g. a high-pass filter given a negative cutoff frequency might switch to an all-pass mode). Hints are meaningful for all input and output ports but hints for input control ports are expected to be particularly useful. More hint information is encapsulated in the LADSPA_PortRangeHintDescriptor type which is assembled by ORing individual hint types together. Hints may require further LowerBound and UpperBound information. All the hint information for a particular port is aggregated in the LADSPA_PortRangeHint structure. */ typedef int LADSPA_PortRangeHintDescriptor; /* Hint LADSPA_HINT_BOUNDED_BELOW indicates that the LowerBound field of the LADSPA_PortRangeHint should be considered meaningful. The value in this field should be considered the (inclusive) lower bound of the valid range. If LADSPA_HINT_SAMPLE_RATE is also specified then the value of LowerBound should be multiplied by the sample rate. */ #define LADSPA_HINT_BOUNDED_BELOW 0x1 /* Hint LADSPA_HINT_BOUNDED_ABOVE indicates that the UpperBound field of the LADSPA_PortRangeHint should be considered meaningful. The value in this field should be considered the (inclusive) upper bound of the valid range. If LADSPA_HINT_SAMPLE_RATE is also specified then the value of UpperBound should be multiplied by the sample rate. */ #define LADSPA_HINT_BOUNDED_ABOVE 0x2 /* Hint LADSPA_HINT_TOGGLED indicates that the data item should be considered a Boolean toggle. Data less than or equal to zero should be considered `off' or `false,' and data above zero should be considered `on' or `true.' LADSPA_HINT_TOGGLED may not be used in conjunction with any other hint except LADSPA_HINT_DEFAULT_0 or LADSPA_HINT_DEFAULT_1. */ #define LADSPA_HINT_TOGGLED 0x4 /* Hint LADSPA_HINT_SAMPLE_RATE indicates that any bounds specified should be interpreted as multiples of the sample rate. For instance, a frequency range from 0Hz to the Nyquist frequency (half the sample rate) could be requested by this hint in conjunction with LowerBound = 0 and UpperBound = 0.5. Hosts that support bounds at all must support this hint to retain meaning. */ #define LADSPA_HINT_SAMPLE_RATE 0x8 /* Hint LADSPA_HINT_LOGARITHMIC indicates that it is likely that the user will find it more intuitive to view values using a logarithmic scale. This is particularly useful for frequencies and gains. */ #define LADSPA_HINT_LOGARITHMIC 0x10 /* Hint LADSPA_HINT_INTEGER indicates that a user interface would probably wish to provide a stepped control taking only integer values. Any bounds set should be slightly wider than the actual integer range required to avoid floating point rounding errors. For instance, the integer set {0,1,2,3} might be described as [-0.1, 3.1]. */ #define LADSPA_HINT_INTEGER 0x20 /* The various LADSPA_HINT_HAS_DEFAULT_* hints indicate a `normal' value for the port that is sensible as a default. For instance, this value is suitable for use as an initial value in a user interface or as a value the host might assign to a control port when the user has not provided one. Defaults are encoded using a mask so only one default may be specified for a port. Some of the hints make use of lower and upper bounds, in which case the relevant bound or bounds must be available and LADSPA_HINT_SAMPLE_RATE must be applied as usual. The resulting default must be rounded if LADSPA_HINT_INTEGER is present. Default values were introduced in LADSPA v1.1. */ #define LADSPA_HINT_DEFAULT_MASK 0x3C0 /* This default values indicates that no default is provided. */ #define LADSPA_HINT_DEFAULT_NONE 0x0 /* This default hint indicates that the suggested lower bound for the port should be used. */ #define LADSPA_HINT_DEFAULT_MINIMUM 0x40 /* This default hint indicates that a low value between the suggested lower and upper bounds should be chosen. For ports with LADSPA_HINT_LOGARITHMIC, this should be exp(log(lower) * 0.75 + log(upper) * 0.25). Otherwise, this should be (lower * 0.75 + upper * 0.25). */ #define LADSPA_HINT_DEFAULT_LOW 0x80 /* This default hint indicates that a middle value between the suggested lower and upper bounds should be chosen. For ports with LADSPA_HINT_LOGARITHMIC, this should be exp(log(lower) * 0.5 + log(upper) * 0.5). Otherwise, this should be (lower * 0.5 + upper * 0.5). */ #define LADSPA_HINT_DEFAULT_MIDDLE 0xC0 /* This default hint indicates that a high value between the suggested lower and upper bounds should be chosen. For ports with LADSPA_HINT_LOGARITHMIC, this should be exp(log(lower) * 0.25 + log(upper) * 0.75). Otherwise, this should be (lower * 0.25 + upper * 0.75). */ #define LADSPA_HINT_DEFAULT_HIGH 0x100 /* This default hint indicates that the suggested upper bound for the port should be used. */ #define LADSPA_HINT_DEFAULT_MAXIMUM 0x140 /* This default hint indicates that the number 0 should be used. Note that this default may be used in conjunction with LADSPA_HINT_TOGGLED. */ #define LADSPA_HINT_DEFAULT_0 0x200 /* This default hint indicates that the number 1 should be used. Note that this default may be used in conjunction with LADSPA_HINT_TOGGLED. */ #define LADSPA_HINT_DEFAULT_1 0x240 /* This default hint indicates that the number 100 should be used. */ #define LADSPA_HINT_DEFAULT_100 0x280 /* This default hint indicates that the Hz frequency of `concert A' should be used. This will be 440 unless the host uses an unusual tuning convention, in which case it may be within a few Hz. */ #define LADSPA_HINT_DEFAULT_440 0x2C0 #define LADSPA_IS_HINT_BOUNDED_BELOW(x) ((x) & LADSPA_HINT_BOUNDED_BELOW) #define LADSPA_IS_HINT_BOUNDED_ABOVE(x) ((x) & LADSPA_HINT_BOUNDED_ABOVE) #define LADSPA_IS_HINT_TOGGLED(x) ((x) & LADSPA_HINT_TOGGLED) #define LADSPA_IS_HINT_SAMPLE_RATE(x) ((x) & LADSPA_HINT_SAMPLE_RATE) #define LADSPA_IS_HINT_LOGARITHMIC(x) ((x) & LADSPA_HINT_LOGARITHMIC) #define LADSPA_IS_HINT_INTEGER(x) ((x) & LADSPA_HINT_INTEGER) #define LADSPA_IS_HINT_HAS_DEFAULT(x) ((x) & LADSPA_HINT_DEFAULT_MASK) #define LADSPA_IS_HINT_DEFAULT_MINIMUM(x) (((x) & LADSPA_HINT_DEFAULT_MASK) \ == LADSPA_HINT_DEFAULT_MINIMUM) #define LADSPA_IS_HINT_DEFAULT_LOW(x) (((x) & LADSPA_HINT_DEFAULT_MASK) \ == LADSPA_HINT_DEFAULT_LOW) #define LADSPA_IS_HINT_DEFAULT_MIDDLE(x) (((x) & LADSPA_HINT_DEFAULT_MASK) \ == LADSPA_HINT_DEFAULT_MIDDLE) #define LADSPA_IS_HINT_DEFAULT_HIGH(x) (((x) & LADSPA_HINT_DEFAULT_MASK) \ == LADSPA_HINT_DEFAULT_HIGH) #define LADSPA_IS_HINT_DEFAULT_MAXIMUM(x) (((x) & LADSPA_HINT_DEFAULT_MASK) \ == LADSPA_HINT_DEFAULT_MAXIMUM) #define LADSPA_IS_HINT_DEFAULT_0(x) (((x) & LADSPA_HINT_DEFAULT_MASK) \ == LADSPA_HINT_DEFAULT_0) #define LADSPA_IS_HINT_DEFAULT_1(x) (((x) & LADSPA_HINT_DEFAULT_MASK) \ == LADSPA_HINT_DEFAULT_1) #define LADSPA_IS_HINT_DEFAULT_100(x) (((x) & LADSPA_HINT_DEFAULT_MASK) \ == LADSPA_HINT_DEFAULT_100) #define LADSPA_IS_HINT_DEFAULT_440(x) (((x) & LADSPA_HINT_DEFAULT_MASK) \ == LADSPA_HINT_DEFAULT_440) typedef struct _LADSPA_PortRangeHint { /* Hints about the port. */ LADSPA_PortRangeHintDescriptor HintDescriptor; /* Meaningful when hint LADSPA_HINT_BOUNDED_BELOW is active. When LADSPA_HINT_SAMPLE_RATE is also active then this value should be multiplied by the relevant sample rate. */ LADSPA_Data LowerBound; /* Meaningful when hint LADSPA_HINT_BOUNDED_ABOVE is active. When LADSPA_HINT_SAMPLE_RATE is also active then this value should be multiplied by the relevant sample rate. */ LADSPA_Data UpperBound; } LADSPA_PortRangeHint; /*****************************************************************************/ /* Plugin Handles: This plugin handle indicates a particular instance of the plugin concerned. It is valid to compare this to NULL (0 for C++) but otherwise the host should not attempt to interpret it. The plugin may use it to reference internal instance data. */ typedef void * LADSPA_Handle; /*****************************************************************************/ /* Descriptor for a Type of Plugin: This structure is used to describe a plugin type. It provides a number of functions to examine the type, instantiate it, link it to buffers and workspaces and to run it. */ typedef struct _LADSPA_Descriptor { /* This numeric identifier indicates the plugin type uniquely. Plugin programmers may reserve ranges of IDs from a central body to avoid clashes. Hosts may assume that IDs are below 0x1000000. */ unsigned long UniqueID; /* This identifier can be used as a unique, case-sensitive identifier for the plugin type within the plugin file. Plugin types should be identified by file and label rather than by index or plugin name, which may be changed in new plugin versions. Labels must not contain white-space characters. */ const char * Label; /* This indicates a number of properties of the plugin. */ LADSPA_Properties Properties; /* This member points to the null-terminated name of the plugin (e.g. "Sine Oscillator"). */ const char * Name; /* This member points to the null-terminated string indicating the maker of the plugin. This can be an empty string but not NULL. */ const char * Maker; /* This member points to the null-terminated string indicating any copyright applying to the plugin. If no Copyright applies the string "None" should be used. */ const char * Copyright; /* This indicates the number of ports (input AND output) present on the plugin. */ unsigned long PortCount; /* This member indicates an array of port descriptors. Valid indices vary from 0 to PortCount-1. */ const LADSPA_PortDescriptor * PortDescriptors; /* This member indicates an array of null-terminated strings describing ports (e.g. "Frequency (Hz)"). Valid indices vary from 0 to PortCount-1. */ const char * const * PortNames; /* This member indicates an array of range hints for each port (see above). Valid indices vary from 0 to PortCount-1. */ const LADSPA_PortRangeHint * PortRangeHints; /* This may be used by the plugin developer to pass any custom implementation data into an instantiate call. It must not be used or interpreted by the host. It is expected that most plugin writers will not use this facility as LADSPA_Handle should be used to hold instance data. */ void * ImplementationData; /* This member is a function pointer that instantiates a plugin. A handle is returned indicating the new plugin instance. The instantiation function accepts a sample rate as a parameter. The plugin descriptor from which this instantiate function was found must also be passed. This function must return NULL if instantiation fails. Note that instance initialisation should generally occur in activate() rather than here. */ LADSPA_Handle (*instantiate)(const struct _LADSPA_Descriptor * Descriptor, unsigned long SampleRate); /* This member is a function pointer that connects a port on an instantiated plugin to a memory location at which a block of data for the port will be read/written. The data location is expected to be an array of LADSPA_Data for audio ports or a single LADSPA_Data value for control ports. Memory issues will be managed by the host. The plugin must read/write the data at these locations every time run() or run_adding() is called and the data present at the time of this connection call should not be considered meaningful. connect_port() may be called more than once for a plugin instance to allow the host to change the buffers that the plugin is reading or writing. These calls may be made before or after activate() or deactivate() calls. connect_port() must be called at least once for each port before run() or run_adding() is called. When working with blocks of LADSPA_Data the plugin should pay careful attention to the block size passed to the run function as the block allocated may only just be large enough to contain the block of samples. Plugin writers should be aware that the host may elect to use the same buffer for more than one port and even use the same buffer for both input and output (see LADSPA_PROPERTY_INPLACE_BROKEN). However, overlapped buffers or use of a single buffer for both audio and control data may result in unexpected behaviour. */ void (*connect_port)(LADSPA_Handle Instance, unsigned long Port, LADSPA_Data * DataLocation); /* This member is a function pointer that initialises a plugin instance and activates it for use. This is separated from instantiate() to aid real-time support and so that hosts can reinitialise a plugin instance by calling deactivate() and then activate(). In this case the plugin instance must reset all state information dependent on the history of the plugin instance except for any data locations provided by connect_port() and any gain set by set_run_adding_gain(). If there is nothing for activate() to do then the plugin writer may provide a NULL rather than an empty function. When present, hosts must call this function once before run() (or run_adding()) is called for the first time. This call should be made as close to the run() call as possible and indicates to real-time plugins that they are now live. Plugins should not rely on a prompt call to run() after activate(). activate() may not be called again unless deactivate() is called first. Note that connect_port() may be called before or after a call to activate(). */ void (*activate)(LADSPA_Handle Instance); /* This method is a function pointer that runs an instance of a plugin for a block. Two parameters are required: the first is a handle to the particular instance to be run and the second indicates the block size (in samples) for which the plugin instance may run. Note that if an activate() function exists then it must be called before run() or run_adding(). If deactivate() is called for a plugin instance then the plugin instance may not be reused until activate() has been called again. If the plugin has the property LADSPA_PROPERTY_HARD_RT_CAPABLE then there are various things that the plugin should not do within the run() or run_adding() functions (see above). */ void (*run)(LADSPA_Handle Instance, unsigned long SampleCount); /* This method is a function pointer that runs an instance of a plugin for a block. This has identical behaviour to run() except in the way data is output from the plugin. When run() is used, values are written directly to the memory areas associated with the output ports. However when run_adding() is called, values must be added to the values already present in the memory areas. Furthermore, output values written must be scaled by the current gain set by set_run_adding_gain() (see below) before addition. run_adding() is optional. When it is not provided by a plugin, this function pointer must be set to NULL. When it is provided, the function set_run_adding_gain() must be provided also. */ void (*run_adding)(LADSPA_Handle Instance, unsigned long SampleCount); /* This method is a function pointer that sets the output gain for use when run_adding() is called (see above). If this function is never called the gain is assumed to default to 1. Gain information should be retained when activate() or deactivate() are called. This function should be provided by the plugin if and only if the run_adding() function is provided. When it is absent this function pointer must be set to NULL. */ void (*set_run_adding_gain)(LADSPA_Handle Instance, LADSPA_Data Gain); /* This is the counterpart to activate() (see above). If there is nothing for deactivate() to do then the plugin writer may provide a NULL rather than an empty function. Hosts must deactivate all activated units after they have been run() (or run_adding()) for the last time. This call should be made as close to the last run() call as possible and indicates to real-time plugins that they are no longer live. Plugins should not rely on prompt deactivation. Note that connect_port() may be called before or after a call to deactivate(). Deactivation is not similar to pausing as the plugin instance will be reinitialised when activate() is called to reuse it. */ void (*deactivate)(LADSPA_Handle Instance); /* Once an instance of a plugin has been finished with it can be deleted using the following function. The instance handle passed ceases to be valid after this call. If activate() was called for a plugin instance then a corresponding call to deactivate() must be made before cleanup() is called. */ void (*cleanup)(LADSPA_Handle Instance); } LADSPA_Descriptor; /**********************************************************************/ /* Accessing a Plugin: */ /* The exact mechanism by which plugins are loaded is host-dependent, however all most hosts will need to know is the name of shared object file containing the plugin types. To allow multiple hosts to share plugin types, hosts may wish to check for environment variable LADSPA_PATH. If present, this should contain a colon-separated path indicating directories that should be searched (in order) when loading plugin types. A plugin programmer must include a function called "ladspa_descriptor" with the following function prototype within the shared object file. This function will have C-style linkage (if you are using C++ this is taken care of by the `extern "C"' clause at the top of the file). A host will find the plugin shared object file by one means or another, find the ladspa_descriptor() function, call it, and proceed from there. Plugin types are accessed by index (not ID) using values from 0 upwards. Out of range indexes must result in this function returning NULL, so the plugin count can be determined by checking for the least index that results in NULL being returned. */ const LADSPA_Descriptor * ladspa_descriptor(unsigned long Index); /* Datatype corresponding to the ladspa_descriptor() function. */ typedef const LADSPA_Descriptor * (*LADSPA_Descriptor_Function)(unsigned long Index); /**********************************************************************/ #ifdef __cplusplus } #endif #endif /* LADSPA_INCLUDED */ /* EOF */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/filter-graph/meson.build000066400000000000000000000075561511204443500275050ustar00rootroot00000000000000plugin_dependencies = [] if get_option('spa-plugins').allowed() plugin_dependencies += audioconvert_dep endif simd_cargs = [] simd_dependencies = [] if have_sse filter_graph_sse = static_library('filter_graph_sse', ['pffft.c', 'audio-dsp-sse.c' ], include_directories : [configinc], c_args : [sse_args, '-O3', '-DHAVE_SSE'], dependencies : [ spa_dep ], install : false ) simd_cargs += ['-DHAVE_SSE'] simd_dependencies += filter_graph_sse endif if have_avx filter_graph_avx = static_library('filter_graph_avx', ['audio-dsp-avx.c' ], include_directories : [configinc], c_args : [avx_args, fma_args,'-O3', '-DHAVE_AVX'], dependencies : [ spa_dep ], install : false ) simd_cargs += ['-DHAVE_AVX'] simd_dependencies += filter_graph_avx endif if have_neon filter_graph_neon = static_library('filter_graph_neon', ['pffft.c' ], c_args : [neon_args, '-O3', '-DHAVE_NEON'], dependencies : [ spa_dep ], install : false ) simd_cargs += ['-DHAVE_NEON'] simd_dependencies += filter_graph_neon endif filter_graph_c = static_library('filter_graph_c', ['pffft.c', 'audio-dsp.c', 'audio-dsp-c.c' ], include_directories : [configinc], c_args : [simd_cargs, '-O3', '-DPFFFT_SIMD_DISABLE'], dependencies : [ spa_dep, fftw_dep], install : false ) simd_dependencies += filter_graph_c spa_filter_graph = shared_library('spa-filter-graph', ['filter-graph.c' ], include_directories : [configinc], dependencies : [ spa_dep, sndfile_dep, plugin_dependencies, mathlib ], install : true, install_dir : spa_plugindir / 'filter-graph', objects : audioconvert_c.extract_objects('biquad.c'), link_with: simd_dependencies ) filter_graph_dependencies = [ spa_dep, mathlib, sndfile_dep, plugin_dependencies ] spa_filter_graph_plugin_builtin = shared_library('spa-filter-graph-plugin-builtin', [ 'plugin_builtin.c', 'convolver.c' ], include_directories : [configinc], install : true, install_dir : spa_plugindir / 'filter-graph', dependencies : [ filter_graph_dependencies ], objects : audioconvert_c.extract_objects('biquad.c') ) spa_filter_graph_plugin_ladspa = shared_library('spa-filter-graph-plugin-ladspa', [ 'plugin_ladspa.c' ], include_directories : [configinc], install : true, install_dir : spa_plugindir / 'filter-graph', dependencies : [ filter_graph_dependencies, dl_lib ] ) if libmysofa_dep.found() spa_filter_graph_plugin_sofa = shared_library('spa-filter-graph-plugin-sofa', [ 'plugin_sofa.c', 'convolver.c' ], include_directories : [configinc], install : true, install_dir : spa_plugindir / 'filter-graph', dependencies : [ filter_graph_dependencies, libmysofa_dep ] ) endif if lilv_lib.found() spa_filter_graph_plugin_lv2 = shared_library('spa-filter-graph-plugin-lv2', [ 'plugin_lv2.c' ], include_directories : [configinc], install : true, install_dir : spa_plugindir / 'filter-graph', dependencies : [ filter_graph_dependencies, lilv_lib ] ) endif if ebur128_lib.found() spa_filter_graph_plugin_ebur128 = shared_library('spa-filter-graph-plugin-ebur128', [ 'plugin_ebur128.c' ], include_directories : [configinc], install : true, install_dir : spa_plugindir / 'filter-graph', dependencies : [ filter_graph_dependencies, lilv_lib, ebur128_lib ] ) endif if avfilter_dep.found() spa_filter_graph_plugin_ffmpeg = shared_library('spa-filter-graph-plugin-ffmpeg', [ 'plugin_ffmpeg.c' ], include_directories : [configinc], install : true, install_dir : spa_plugindir / 'filter-graph', dependencies : [ filter_graph_dependencies, avfilter_dep, avutil_dep] ) endif if onnxruntime_dep.found() spa_filter_graph_plugin_onnx = shared_library('spa-filter-graph-plugin-onnx', [ 'plugin_onnx.c' ], include_directories : [configinc], install : true, install_dir : spa_plugindir / 'filter-graph', dependencies : [ filter_graph_dependencies, onnxruntime_dep] ) endif pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/filter-graph/pffft.c000066400000000000000000002102741511204443500266050ustar00rootroot00000000000000/* Copyright (c) 2013 Julien Pommier ( pommier@modartt.com ) Based on original fortran 77 code from FFTPACKv4 from NETLIB (http://www.netlib.org/fftpack), authored by Dr Paul Swarztrauber of NCAR, in 1985. As confirmed by the NCAR fftpack software curators, the following FFTPACKv5 license applies to FFTPACKv4 sources. My changes are released under the same terms. FFTPACK license: http://www.cisl.ucar.edu/css/software/fftpack5/ftpk.html Copyright (c) 2004 the University Corporation for Atmospheric Research ("UCAR"). All rights reserved. Developed by NCAR's Computational and Information Systems Laboratory, UCAR, www.cisl.ucar.edu. Redistribution and use of the Software in source and binary forms, with or without modification, is permitted provided that the following conditions are met: - Neither the names of NCAR's Computational and Information Systems Laboratory, the University Corporation for Atmospheric Research, nor the names of its sponsors or contributors may be used to endorse or promote products derived from this Software without specific prior written permission. - Redistributions of source code must retain the above copyright notices, this list of conditions, and the disclaimer below. - Redistributions in binary form must reproduce the above copyright notice, this list of conditions, and the disclaimer below in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE SOFTWARE. PFFFT : a Pretty Fast FFT. This file is largerly based on the original FFTPACK implementation, modified in order to take advantage of SIMD instructions of modern CPUs. */ /* ChangeLog: - 2011/10/02, version 1: This is the very first release of this file. */ #include "pffft.h" #include #include #include #include #include #include /* detect compiler flavour */ #if defined(_MSC_VER) #define COMPILER_MSVC #elif defined(__GNUC__) #define COMPILER_GCC #endif #if defined(COMPILER_GCC) #define ALWAYS_INLINE(return_type) inline return_type __attribute__ ((always_inline)) #define NEVER_INLINE(return_type) return_type __attribute__ ((noinline)) #define RESTRICT __restrict #define VLA_ARRAY_ON_STACK(type__, varname__, size__) type__ varname__[size__]; #elif defined(COMPILER_MSVC) #define ALWAYS_INLINE(return_type) __forceinline return_type #define NEVER_INLINE(return_type) __declspec(noinline) return_type #define RESTRICT __restrict #define VLA_ARRAY_ON_STACK(type__, varname__, size__) type__ *varname__ = (type__*)_alloca(size__ * sizeof(type__)) #endif /* vector support macros: the rest of the code is independent of SSE/Altivec/NEON -- adding support for other platforms with 4-element vectors should be limited to these macros */ // define PFFFT_SIMD_DISABLE if you want to use scalar code instead of simd code //#define PFFFT_SIMD_DISABLE /* Altivec support macros */ #if !defined(PFFFT_SIMD_DISABLE) && (defined(HAVE_ALTIVEC)) typedef vector float v4sf; #define SIMD_SZ 4 #define VZERO() ((vector float) vec_splat_u8(0)) #define VMUL(a,b) vec_madd(a,b, VZERO()) #define VADD(a,b) vec_add(a,b) #define VMADD(a,b,c) vec_madd(a,b,c) #define VSUB(a,b) vec_sub(a,b) inline v4sf ld_ps1(const float *p) { v4sf v = vec_lde(0, p); return vec_splat(vec_perm(v, v, vec_lvsl(0, p)), 0); } #define LD_PS1(p) ld_ps1(&p) #define INTERLEAVE2(in1, in2, out1, out2) { v4sf tmp__ = vec_mergeh(in1, in2); out2 = vec_mergel(in1, in2); out1 = tmp__; } #define UNINTERLEAVE2(in1, in2, out1, out2) { \ vector unsigned char vperm1 = (vector unsigned char)(0,1,2,3,8,9,10,11,16,17,18,19,24,25,26,27); \ vector unsigned char vperm2 = (vector unsigned char)(4,5,6,7,12,13,14,15,20,21,22,23,28,29,30,31); \ v4sf tmp__ = vec_perm(in1, in2, vperm1); out2 = vec_perm(in1, in2, vperm2); out1 = tmp__; \ } #define VTRANSPOSE4(x0,x1,x2,x3) { \ v4sf y0 = vec_mergeh(x0, x2); \ v4sf y1 = vec_mergel(x0, x2); \ v4sf y2 = vec_mergeh(x1, x3); \ v4sf y3 = vec_mergel(x1, x3); \ x0 = vec_mergeh(y0, y2); \ x1 = vec_mergel(y0, y2); \ x2 = vec_mergeh(y1, y3); \ x3 = vec_mergel(y1, y3); \ } #define VSWAPHL(a,b) vec_perm(a,b, (vector unsigned char)(16,17,18,19,20,21,22,23,8,9,10,11,12,13,14,15)) #define VALIGNED(ptr) ((((uintptr_t)(ptr)) & 0xF) == 0) #define pffft_funcs pffft_funcs_altivec #define new_setup_simd new_setup_altivec #define zreorder_simd zreorder_altivec #define zconvolve_accumulate_simd zconvolve_accumulate_altivec #define zconvolve_simd zconvolve_altivec #define transform_simd transform_altivec /* SSE1 support macros */ #elif !defined(PFFFT_SIMD_DISABLE) && (defined(HAVE_SSE)) #include typedef __m128 v4sf; #define SIMD_SZ 4 // 4 floats by simd vector -- this is pretty much hardcoded in the preprocess/finalize functions anyway so you will have to work if you want to enable AVX with its 256-bit vectors. #define VZERO() _mm_setzero_ps() #define VMUL(a,b) _mm_mul_ps(a,b) #define VADD(a,b) _mm_add_ps(a,b) #define VMADD(a,b,c) _mm_add_ps(_mm_mul_ps(a,b), c) #define VSUB(a,b) _mm_sub_ps(a,b) #define LD_PS1(p) _mm_set1_ps(p) #define INTERLEAVE2(in1, in2, out1, out2) { v4sf tmp__ = _mm_unpacklo_ps(in1, in2); out2 = _mm_unpackhi_ps(in1, in2); out1 = tmp__; } #define UNINTERLEAVE2(in1, in2, out1, out2) { v4sf tmp__ = _mm_shuffle_ps(in1, in2, _MM_SHUFFLE(2,0,2,0)); out2 = _mm_shuffle_ps(in1, in2, _MM_SHUFFLE(3,1,3,1)); out1 = tmp__; } #define VTRANSPOSE4(x0,x1,x2,x3) _MM_TRANSPOSE4_PS(x0,x1,x2,x3) #define VSWAPHL(a,b) _mm_shuffle_ps(b, a, _MM_SHUFFLE(3,2,1,0)) #define VALIGNED(ptr) ((((uintptr_t)(ptr)) & 0xF) == 0) #define pffft_funcs pffft_funcs_sse #define new_setup_simd new_setup_sse #define zreorder_simd zreorder_sse #define zconvolve_accumulate_simd zconvolve_accumulate_sse #define zconvolve_simd zconvolve_sse #define transform_simd transform_sse /* ARM NEON support macros */ #elif !defined(PFFFT_SIMD_DISABLE) && (defined(HAVE_NEON)) #include typedef float32x4_t v4sf; #define SIMD_SZ 4 #define VZERO() vdupq_n_f32(0) #define VMUL(a,b) vmulq_f32(a,b) #define VADD(a,b) vaddq_f32(a,b) #define VMADD(a,b,c) vmlaq_f32(c,a,b) #define VSUB(a,b) vsubq_f32(a,b) #define LD_PS1(p) vld1q_dup_f32(&(p)) #define INTERLEAVE2(in1, in2, out1, out2) { float32x4x2_t tmp__ = vzipq_f32(in1,in2); out1=tmp__.val[0]; out2=tmp__.val[1]; } #define UNINTERLEAVE2(in1, in2, out1, out2) { float32x4x2_t tmp__ = vuzpq_f32(in1,in2); out1=tmp__.val[0]; out2=tmp__.val[1]; } #define VTRANSPOSE4(x0,x1,x2,x3) { \ float32x4x2_t t0_ = vzipq_f32(x0, x2); \ float32x4x2_t t1_ = vzipq_f32(x1, x3); \ float32x4x2_t u0_ = vzipq_f32(t0_.val[0], t1_.val[0]); \ float32x4x2_t u1_ = vzipq_f32(t0_.val[1], t1_.val[1]); \ x0 = u0_.val[0]; x1 = u0_.val[1]; x2 = u1_.val[0]; x3 = u1_.val[1]; \ } // marginally faster version //# define VTRANSPOSE4(x0,x1,x2,x3) { asm("vtrn.32 %q0, %q1;\n vtrn.32 %q2,%q3\n vswp %f0,%e2\n vswp %f1,%e3" : "+w"(x0), "+w"(x1), "+w"(x2), "+w"(x3)::); } #define VSWAPHL(a,b) vcombine_f32(vget_low_f32(b), vget_high_f32(a)) #define VALIGNED(ptr) ((((uintptr_t)(ptr)) & 0x3) == 0) #define pffft_funcs pffft_funcs_neon #define new_setup_simd new_setup_neon #define zreorder_simd zreorder_neon #define zconvolve_accumulate_simd zconvolve_accumulate_neon #define zconvolve_simd zconvolve_neon #define transform_simd transform_neon #else #if !defined(PFFFT_SIMD_DISABLE) #warning "building with simd disabled !\n"; #define PFFFT_SIMD_DISABLE // fallback to scalar code #endif #endif // fallback mode for situations where SSE/Altivec are not available, use scalar mode instead #ifdef PFFFT_SIMD_DISABLE typedef float v4sf; #define SIMD_SZ 1 #define VZERO() 0.f #define VMUL(a,b) ((a)*(b)) #define VADD(a,b) ((a)+(b)) #define VMADD(a,b,c) ((a)*(b)+(c)) #define VSUB(a,b) ((a)-(b)) #define LD_PS1(p) (p) #define VALIGNED(ptr) ((((uintptr_t)(ptr)) & 0x3) == 0) #define pffft_funcs pffft_funcs_c #define new_setup_simd new_setup_c #define zreorder_simd zreorder_c #define zconvolve_accumulate_simd zconvolve_accumulate_c #define zconvolve_simd zconvolve_c #define transform_simd transform_c #endif // shortcuts for complex multiplcations #define VCPLXMUL(ar,ai,br,bi) { v4sf tmp; tmp=VMUL(ar,bi); ar=VMUL(ar,br); ar=VSUB(ar,VMUL(ai,bi)); ai=VMUL(ai,br); ai=VADD(ai,tmp); } #define VCPLXMULCONJ(ar,ai,br,bi) { v4sf tmp; tmp=VMUL(ar,bi); ar=VMUL(ar,br); ar=VADD(ar,VMUL(ai,bi)); ai=VMUL(ai,br); ai=VSUB(ai,tmp); } #ifndef SVMUL // multiply a scalar with a vector #define SVMUL(f,v) VMUL(LD_PS1(f),v) #endif #if !defined(PFFFT_SIMD_DISABLE) typedef union v4sf_union { v4sf v; float f[4]; } v4sf_union; #include #define assertv4(v,f0,f1,f2,f3) assert(v.f[0] == (f0) && v.f[1] == (f1) && v.f[2] == (f2) && v.f[3] == (f3)) /* detect bugs with the vector support macros */ static void validate_pffft_simd(void) { float f[16] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }; v4sf_union a0, a1, a2, a3, t, u; memcpy(a0.f, f, 4 * sizeof(float)); memcpy(a1.f, f + 4, 4 * sizeof(float)); memcpy(a2.f, f + 8, 4 * sizeof(float)); memcpy(a3.f, f + 12, 4 * sizeof(float)); t = a0; u = a1; t.v = VZERO(); printf("VZERO=[%2g %2g %2g %2g]\n", t.f[0], t.f[1], t.f[2], t.f[3]); assertv4(t, 0, 0, 0, 0); t.v = VADD(a1.v, a2.v); printf("VADD(4:7,8:11)=[%2g %2g %2g %2g]\n", t.f[0], t.f[1], t.f[2], t.f[3]); assertv4(t, 12, 14, 16, 18); t.v = VMUL(a1.v, a2.v); printf("VMUL(4:7,8:11)=[%2g %2g %2g %2g]\n", t.f[0], t.f[1], t.f[2], t.f[3]); assertv4(t, 32, 45, 60, 77); t.v = VMADD(a1.v, a2.v, a0.v); printf("VMADD(4:7,8:11,0:3)=[%2g %2g %2g %2g]\n", t.f[0], t.f[1], t.f[2], t.f[3]); assertv4(t, 32, 46, 62, 80); INTERLEAVE2(a1.v, a2.v, t.v, u.v); printf("INTERLEAVE2(4:7,8:11)=[%2g %2g %2g %2g] [%2g %2g %2g %2g]\n", t.f[0], t.f[1], t.f[2], t.f[3], u.f[0], u.f[1], u.f[2], u.f[3]); assertv4(t, 4, 8, 5, 9); assertv4(u, 6, 10, 7, 11); UNINTERLEAVE2(a1.v, a2.v, t.v, u.v); printf("UNINTERLEAVE2(4:7,8:11)=[%2g %2g %2g %2g] [%2g %2g %2g %2g]\n", t.f[0], t.f[1], t.f[2], t.f[3], u.f[0], u.f[1], u.f[2], u.f[3]); assertv4(t, 4, 6, 8, 10); assertv4(u, 5, 7, 9, 11); t.v = LD_PS1(f[15]); printf("LD_PS1(15)=[%2g %2g %2g %2g]\n", t.f[0], t.f[1], t.f[2], t.f[3]); assertv4(t, 15, 15, 15, 15); t.v = VSWAPHL(a1.v, a2.v); printf("VSWAPHL(4:7,8:11)=[%2g %2g %2g %2g]\n", t.f[0], t.f[1], t.f[2], t.f[3]); assertv4(t, 8, 9, 6, 7); VTRANSPOSE4(a0.v, a1.v, a2.v, a3.v); printf ("VTRANSPOSE4(0:3,4:7,8:11,12:15)=[%2g %2g %2g %2g] [%2g %2g %2g %2g] [%2g %2g %2g %2g] [%2g %2g %2g %2g]\n", a0.f[0], a0.f[1], a0.f[2], a0.f[3], a1.f[0], a1.f[1], a1.f[2], a1.f[3], a2.f[0], a2.f[1], a2.f[2], a2.f[3], a3.f[0], a3.f[1], a3.f[2], a3.f[3]); assertv4(a0, 0, 4, 8, 12); assertv4(a1, 1, 5, 9, 13); assertv4(a2, 2, 6, 10, 14); assertv4(a3, 3, 7, 11, 15); } #else static void validate_pffft_simd(void) { } // allow test_pffft.c to call this function even when simd is not available.. #endif //!PFFFT_SIMD_DISABLE /* passf2 and passb2 has been merged here, fsign = -1 for passf2, +1 for passb2 */ static NEVER_INLINE(void) passf2_ps(int ido, int l1, const v4sf * cc, v4sf * ch, const float *wa1, float fsign) { int k, i; int l1ido = l1 * ido; if (ido <= 2) { for (k = 0; k < l1ido; k += ido, ch += ido, cc += 2 * ido) { ch[0] = VADD(cc[0], cc[ido + 0]); ch[l1ido] = VSUB(cc[0], cc[ido + 0]); ch[1] = VADD(cc[1], cc[ido + 1]); ch[l1ido + 1] = VSUB(cc[1], cc[ido + 1]); } } else { for (k = 0; k < l1ido; k += ido, ch += ido, cc += 2 * ido) { for (i = 0; i < ido - 1; i += 2) { v4sf tr2 = VSUB(cc[i + 0], cc[i + ido + 0]); v4sf ti2 = VSUB(cc[i + 1], cc[i + ido + 1]); v4sf wr = LD_PS1(wa1[i]), wi = VMUL(LD_PS1(fsign), LD_PS1(wa1[i + 1])); ch[i] = VADD(cc[i + 0], cc[i + ido + 0]); ch[i + 1] = VADD(cc[i + 1], cc[i + ido + 1]); VCPLXMUL(tr2, ti2, wr, wi); ch[i + l1ido] = tr2; ch[i + l1ido + 1] = ti2; } } } } /* passf3 and passb3 has been merged here, fsign = -1 for passf3, +1 for passb3 */ static NEVER_INLINE(void) passf3_ps(int ido, int l1, const v4sf * cc, v4sf * ch, const float *wa1, const float *wa2, float fsign) { static const float taur = -0.5f; float taui = 0.866025403784439f * fsign; int i, k; v4sf tr2, ti2, cr2, ci2, cr3, ci3, dr2, di2, dr3, di3; int l1ido = l1 * ido; float wr1, wi1, wr2, wi2; assert(ido > 2); for (k = 0; k < l1ido; k += ido, cc += 3 * ido, ch += ido) { for (i = 0; i < ido - 1; i += 2) { tr2 = VADD(cc[i + ido], cc[i + 2 * ido]); cr2 = VADD(cc[i], SVMUL(taur, tr2)); ch[i] = VADD(cc[i], tr2); ti2 = VADD(cc[i + ido + 1], cc[i + 2 * ido + 1]); ci2 = VADD(cc[i + 1], SVMUL(taur, ti2)); ch[i + 1] = VADD(cc[i + 1], ti2); cr3 = SVMUL(taui, VSUB(cc[i + ido], cc[i + 2 * ido])); ci3 = SVMUL(taui, VSUB(cc[i + ido + 1], cc[i + 2 * ido + 1])); dr2 = VSUB(cr2, ci3); dr3 = VADD(cr2, ci3); di2 = VADD(ci2, cr3); di3 = VSUB(ci2, cr3); wr1 = wa1[i], wi1 = fsign * wa1[i + 1], wr2 = wa2[i], wi2 = fsign * wa2[i + 1]; VCPLXMUL(dr2, di2, LD_PS1(wr1), LD_PS1(wi1)); ch[i + l1ido] = dr2; ch[i + l1ido + 1] = di2; VCPLXMUL(dr3, di3, LD_PS1(wr2), LD_PS1(wi2)); ch[i + 2 * l1ido] = dr3; ch[i + 2 * l1ido + 1] = di3; } } } /* passf3 */ static NEVER_INLINE(void) passf4_ps(int ido, int l1, const v4sf * cc, v4sf * ch, const float *wa1, const float *wa2, const float *wa3, float fsign) { /* isign == -1 for forward transform and +1 for backward transform */ int i, k; v4sf ci2, ci3, ci4, cr2, cr3, cr4, ti1, ti2, ti3, ti4, tr1, tr2, tr3, tr4; int l1ido = l1 * ido; if (ido == 2) { for (k = 0; k < l1ido; k += ido, ch += ido, cc += 4 * ido) { tr1 = VSUB(cc[0], cc[2 * ido + 0]); tr2 = VADD(cc[0], cc[2 * ido + 0]); ti1 = VSUB(cc[1], cc[2 * ido + 1]); ti2 = VADD(cc[1], cc[2 * ido + 1]); ti4 = VMUL(VSUB(cc[1 * ido + 0], cc[3 * ido + 0]), LD_PS1(fsign)); tr4 = VMUL(VSUB(cc[3 * ido + 1], cc[1 * ido + 1]), LD_PS1(fsign)); tr3 = VADD(cc[ido + 0], cc[3 * ido + 0]); ti3 = VADD(cc[ido + 1], cc[3 * ido + 1]); ch[0 * l1ido + 0] = VADD(tr2, tr3); ch[0 * l1ido + 1] = VADD(ti2, ti3); ch[1 * l1ido + 0] = VADD(tr1, tr4); ch[1 * l1ido + 1] = VADD(ti1, ti4); ch[2 * l1ido + 0] = VSUB(tr2, tr3); ch[2 * l1ido + 1] = VSUB(ti2, ti3); ch[3 * l1ido + 0] = VSUB(tr1, tr4); ch[3 * l1ido + 1] = VSUB(ti1, ti4); } } else { for (k = 0; k < l1ido; k += ido, ch += ido, cc += 4 * ido) { for (i = 0; i < ido - 1; i += 2) { float wr1, wi1, wr2, wi2, wr3, wi3; tr1 = VSUB(cc[i + 0], cc[i + 2 * ido + 0]); tr2 = VADD(cc[i + 0], cc[i + 2 * ido + 0]); ti1 = VSUB(cc[i + 1], cc[i + 2 * ido + 1]); ti2 = VADD(cc[i + 1], cc[i + 2 * ido + 1]); tr4 = VMUL(VSUB (cc[i + 3 * ido + 1], cc[i + 1 * ido + 1]), LD_PS1(fsign)); ti4 = VMUL(VSUB (cc[i + 1 * ido + 0], cc[i + 3 * ido + 0]), LD_PS1(fsign)); tr3 = VADD(cc[i + ido + 0], cc[i + 3 * ido + 0]); ti3 = VADD(cc[i + ido + 1], cc[i + 3 * ido + 1]); ch[i] = VADD(tr2, tr3); cr3 = VSUB(tr2, tr3); ch[i + 1] = VADD(ti2, ti3); ci3 = VSUB(ti2, ti3); cr2 = VADD(tr1, tr4); cr4 = VSUB(tr1, tr4); ci2 = VADD(ti1, ti4); ci4 = VSUB(ti1, ti4); wr1 = wa1[i], wi1 = fsign * wa1[i + 1]; VCPLXMUL(cr2, ci2, LD_PS1(wr1), LD_PS1(wi1)); wr2 = wa2[i], wi2 = fsign * wa2[i + 1]; ch[i + l1ido] = cr2; ch[i + l1ido + 1] = ci2; VCPLXMUL(cr3, ci3, LD_PS1(wr2), LD_PS1(wi2)); wr3 = wa3[i], wi3 = fsign * wa3[i + 1]; ch[i + 2 * l1ido] = cr3; ch[i + 2 * l1ido + 1] = ci3; VCPLXMUL(cr4, ci4, LD_PS1(wr3), LD_PS1(wi3)); ch[i + 3 * l1ido] = cr4; ch[i + 3 * l1ido + 1] = ci4; } } } } /* passf4 */ /* passf5 and passb5 has been merged here, fsign = -1 for passf5, +1 for passb5 */ static NEVER_INLINE(void) passf5_ps(int ido, int l1, const v4sf * cc, v4sf * ch, const float *wa1, const float *wa2, const float *wa3, const float *wa4, float fsign) { static const float tr11 = .309016994374947f; const float ti11 = .951056516295154f * fsign; static const float tr12 = -.809016994374947f; const float ti12 = .587785252292473f * fsign; /* Local variables */ int i, k; v4sf ci2, ci3, ci4, ci5, di3, di4, di5, di2, cr2, cr3, cr5, cr4, ti2, ti3, ti4, ti5, dr3, dr4, dr5, dr2, tr2, tr3, tr4, tr5; float wr1, wi1, wr2, wi2, wr3, wi3, wr4, wi4; #define cc_ref(a_1,a_2) cc[(a_2-1)*ido + a_1 + 1] #define ch_ref(a_1,a_3) ch[(a_3-1)*l1*ido + a_1 + 1] assert(ido > 2); for (k = 0; k < l1; ++k, cc += 5 * ido, ch += ido) { for (i = 0; i < ido - 1; i += 2) { ti5 = VSUB(cc_ref(i, 2), cc_ref(i, 5)); ti2 = VADD(cc_ref(i, 2), cc_ref(i, 5)); ti4 = VSUB(cc_ref(i, 3), cc_ref(i, 4)); ti3 = VADD(cc_ref(i, 3), cc_ref(i, 4)); tr5 = VSUB(cc_ref(i - 1, 2), cc_ref(i - 1, 5)); tr2 = VADD(cc_ref(i - 1, 2), cc_ref(i - 1, 5)); tr4 = VSUB(cc_ref(i - 1, 3), cc_ref(i - 1, 4)); tr3 = VADD(cc_ref(i - 1, 3), cc_ref(i - 1, 4)); ch_ref(i - 1, 1) = VADD(cc_ref(i - 1, 1), VADD(tr2, tr3)); ch_ref(i, 1) = VADD(cc_ref(i, 1), VADD(ti2, ti3)); cr2 = VADD(cc_ref(i - 1, 1), VADD(SVMUL(tr11, tr2), SVMUL(tr12, tr3))); ci2 = VADD(cc_ref(i, 1), VADD(SVMUL(tr11, ti2), SVMUL(tr12, ti3))); cr3 = VADD(cc_ref(i - 1, 1), VADD(SVMUL(tr12, tr2), SVMUL(tr11, tr3))); ci3 = VADD(cc_ref(i, 1), VADD(SVMUL(tr12, ti2), SVMUL(tr11, ti3))); cr5 = VADD(SVMUL(ti11, tr5), SVMUL(ti12, tr4)); ci5 = VADD(SVMUL(ti11, ti5), SVMUL(ti12, ti4)); cr4 = VSUB(SVMUL(ti12, tr5), SVMUL(ti11, tr4)); ci4 = VSUB(SVMUL(ti12, ti5), SVMUL(ti11, ti4)); dr3 = VSUB(cr3, ci4); dr4 = VADD(cr3, ci4); di3 = VADD(ci3, cr4); di4 = VSUB(ci3, cr4); dr5 = VADD(cr2, ci5); dr2 = VSUB(cr2, ci5); di5 = VSUB(ci2, cr5); di2 = VADD(ci2, cr5); wr1 = wa1[i], wi1 = fsign * wa1[i + 1], wr2 = wa2[i], wi2 = fsign * wa2[i + 1]; wr3 = wa3[i], wi3 = fsign * wa3[i + 1], wr4 = wa4[i], wi4 = fsign * wa4[i + 1]; VCPLXMUL(dr2, di2, LD_PS1(wr1), LD_PS1(wi1)); ch_ref(i - 1, 2) = dr2; ch_ref(i, 2) = di2; VCPLXMUL(dr3, di3, LD_PS1(wr2), LD_PS1(wi2)); ch_ref(i - 1, 3) = dr3; ch_ref(i, 3) = di3; VCPLXMUL(dr4, di4, LD_PS1(wr3), LD_PS1(wi3)); ch_ref(i - 1, 4) = dr4; ch_ref(i, 4) = di4; VCPLXMUL(dr5, di5, LD_PS1(wr4), LD_PS1(wi4)); ch_ref(i - 1, 5) = dr5; ch_ref(i, 5) = di5; } } #undef ch_ref #undef cc_ref } static NEVER_INLINE(void) radf2_ps(int ido, int l1, const v4sf * RESTRICT cc, v4sf * RESTRICT ch, const float *wa1) { static const float minus_one = -1.f; int i, k, l1ido = l1 * ido; for (k = 0; k < l1ido; k += ido) { v4sf a = cc[k], b = cc[k + l1ido]; ch[2 * k] = VADD(a, b); ch[2 * (k + ido) - 1] = VSUB(a, b); } if (ido < 2) return; if (ido != 2) { for (k = 0; k < l1ido; k += ido) { for (i = 2; i < ido; i += 2) { v4sf tr2 = cc[i - 1 + k + l1ido], ti2 = cc[i + k + l1ido]; v4sf br = cc[i - 1 + k], bi = cc[i + k]; VCPLXMULCONJ(tr2, ti2, LD_PS1(wa1[i - 2]), LD_PS1(wa1[i - 1])); ch[i + 2 * k] = VADD(bi, ti2); ch[2 * (k + ido) - i] = VSUB(ti2, bi); ch[i - 1 + 2 * k] = VADD(br, tr2); ch[2 * (k + ido) - i - 1] = VSUB(br, tr2); } } if (ido % 2 == 1) return; } for (k = 0; k < l1ido; k += ido) { ch[2 * k + ido] = SVMUL(minus_one, cc[ido - 1 + k + l1ido]); ch[2 * k + ido - 1] = cc[k + ido - 1]; } } /* radf2 */ static NEVER_INLINE(void) radb2_ps(int ido, int l1, const v4sf * cc, v4sf * ch, const float *wa1) { static const float minus_two = -2; int i, k, l1ido = l1 * ido; v4sf a, b, c, d, tr2, ti2; for (k = 0; k < l1ido; k += ido) { a = cc[2 * k]; b = cc[2 * (k + ido) - 1]; ch[k] = VADD(a, b); ch[k + l1ido] = VSUB(a, b); } if (ido < 2) return; if (ido != 2) { for (k = 0; k < l1ido; k += ido) { for (i = 2; i < ido; i += 2) { a = cc[i - 1 + 2 * k]; b = cc[2 * (k + ido) - i - 1]; c = cc[i + 0 + 2 * k]; d = cc[2 * (k + ido) - i + 0]; ch[i - 1 + k] = VADD(a, b); tr2 = VSUB(a, b); ch[i + 0 + k] = VSUB(c, d); ti2 = VADD(c, d); VCPLXMUL(tr2, ti2, LD_PS1(wa1[i - 2]), LD_PS1(wa1[i - 1])); ch[i - 1 + k + l1ido] = tr2; ch[i + 0 + k + l1ido] = ti2; } } if (ido % 2 == 1) return; } for (k = 0; k < l1ido; k += ido) { a = cc[2 * k + ido - 1]; b = cc[2 * k + ido]; ch[k + ido - 1] = VADD(a, a); ch[k + ido - 1 + l1ido] = SVMUL(minus_two, b); } } /* radb2 */ static void radf3_ps(int ido, int l1, const v4sf * RESTRICT cc, v4sf * RESTRICT ch, const float *wa1, const float *wa2) { static const float taur = -0.5f; static const float taui = 0.866025403784439f; int i, k, ic; v4sf ci2, di2, di3, cr2, dr2, dr3, ti2, ti3, tr2, tr3, wr1, wi1, wr2, wi2; for (k = 0; k < l1; k++) { cr2 = VADD(cc[(k + l1) * ido], cc[(k + 2 * l1) * ido]); ch[3 * k * ido] = VADD(cc[k * ido], cr2); ch[(3 * k + 2) * ido] = SVMUL(taui, VSUB(cc[(k + l1 * 2) * ido], cc[(k + l1) * ido])); ch[ido - 1 + (3 * k + 1) * ido] = VADD(cc[k * ido], SVMUL(taur, cr2)); } if (ido == 1) return; for (k = 0; k < l1; k++) { for (i = 2; i < ido; i += 2) { ic = ido - i; wr1 = LD_PS1(wa1[i - 2]); wi1 = LD_PS1(wa1[i - 1]); dr2 = cc[i - 1 + (k + l1) * ido]; di2 = cc[i + (k + l1) * ido]; VCPLXMULCONJ(dr2, di2, wr1, wi1); wr2 = LD_PS1(wa2[i - 2]); wi2 = LD_PS1(wa2[i - 1]); dr3 = cc[i - 1 + (k + l1 * 2) * ido]; di3 = cc[i + (k + l1 * 2) * ido]; VCPLXMULCONJ(dr3, di3, wr2, wi2); cr2 = VADD(dr2, dr3); ci2 = VADD(di2, di3); ch[i - 1 + 3 * k * ido] = VADD(cc[i - 1 + k * ido], cr2); ch[i + 3 * k * ido] = VADD(cc[i + k * ido], ci2); tr2 = VADD(cc[i - 1 + k * ido], SVMUL(taur, cr2)); ti2 = VADD(cc[i + k * ido], SVMUL(taur, ci2)); tr3 = SVMUL(taui, VSUB(di2, di3)); ti3 = SVMUL(taui, VSUB(dr3, dr2)); ch[i - 1 + (3 * k + 2) * ido] = VADD(tr2, tr3); ch[ic - 1 + (3 * k + 1) * ido] = VSUB(tr2, tr3); ch[i + (3 * k + 2) * ido] = VADD(ti2, ti3); ch[ic + (3 * k + 1) * ido] = VSUB(ti3, ti2); } } } /* radf3 */ static void radb3_ps(int ido, int l1, const v4sf * RESTRICT cc, v4sf * RESTRICT ch, const float *wa1, const float *wa2) { static const float taur = -0.5f; static const float taui = 0.866025403784439f; static const float taui_2 = 0.866025403784439f * 2; int i, k, ic; v4sf ci2, ci3, di2, di3, cr2, cr3, dr2, dr3, ti2, tr2; for (k = 0; k < l1; k++) { tr2 = cc[ido - 1 + (3 * k + 1) * ido]; tr2 = VADD(tr2, tr2); cr2 = VMADD(LD_PS1(taur), tr2, cc[3 * k * ido]); ch[k * ido] = VADD(cc[3 * k * ido], tr2); ci3 = SVMUL(taui_2, cc[(3 * k + 2) * ido]); ch[(k + l1) * ido] = VSUB(cr2, ci3); ch[(k + 2 * l1) * ido] = VADD(cr2, ci3); } if (ido == 1) return; for (k = 0; k < l1; k++) { for (i = 2; i < ido; i += 2) { ic = ido - i; tr2 = VADD(cc[i - 1 + (3 * k + 2) * ido], cc[ic - 1 + (3 * k + 1) * ido]); cr2 = VMADD(LD_PS1(taur), tr2, cc[i - 1 + 3 * k * ido]); ch[i - 1 + k * ido] = VADD(cc[i - 1 + 3 * k * ido], tr2); ti2 = VSUB(cc[i + (3 * k + 2) * ido], cc[ic + (3 * k + 1) * ido]); ci2 = VMADD(LD_PS1(taur), ti2, cc[i + 3 * k * ido]); ch[i + k * ido] = VADD(cc[i + 3 * k * ido], ti2); cr3 = SVMUL(taui, VSUB(cc[i - 1 + (3 * k + 2) * ido], cc[ic - 1 + (3 * k + 1) * ido])); ci3 = SVMUL(taui, VADD(cc[i + (3 * k + 2) * ido], cc[ic + (3 * k + 1) * ido])); dr2 = VSUB(cr2, ci3); dr3 = VADD(cr2, ci3); di2 = VADD(ci2, cr3); di3 = VSUB(ci2, cr3); VCPLXMUL(dr2, di2, LD_PS1(wa1[i - 2]), LD_PS1(wa1[i - 1])); ch[i - 1 + (k + l1) * ido] = dr2; ch[i + (k + l1) * ido] = di2; VCPLXMUL(dr3, di3, LD_PS1(wa2[i - 2]), LD_PS1(wa2[i - 1])); ch[i - 1 + (k + 2 * l1) * ido] = dr3; ch[i + (k + 2 * l1) * ido] = di3; } } } /* radb3 */ static NEVER_INLINE(void) radf4_ps(int ido, int l1, const v4sf * RESTRICT cc, v4sf * RESTRICT ch, const float *RESTRICT wa1, const float *RESTRICT wa2, const float *RESTRICT wa3) { static const float minus_hsqt2 = (float)-0.7071067811865475; int i, k, l1ido = l1 * ido; { const v4sf *RESTRICT cc_ = cc, *RESTRICT cc_end = cc + l1ido; v4sf *RESTRICT ch_ = ch; while (cc < cc_end) { // this loop represents between 25% and 40% of total radf4_ps cost ! v4sf a0 = cc[0], a1 = cc[l1ido]; v4sf a2 = cc[2 * l1ido], a3 = cc[3 * l1ido]; v4sf tr1 = VADD(a1, a3); v4sf tr2 = VADD(a0, a2); ch[2 * ido - 1] = VSUB(a0, a2); ch[2 * ido] = VSUB(a3, a1); ch[0] = VADD(tr1, tr2); ch[4 * ido - 1] = VSUB(tr2, tr1); cc += ido; ch += 4 * ido; } cc = cc_; ch = ch_; } if (ido < 2) return; if (ido != 2) { for (k = 0; k < l1ido; k += ido) { const v4sf *RESTRICT pc = (v4sf *) (cc + 1 + k); for (i = 2; i < ido; i += 2, pc += 2) { int ic = ido - i; v4sf wr, wi, cr2, ci2, cr3, ci3, cr4, ci4; v4sf tr1, ti1, tr2, ti2, tr3, ti3, tr4, ti4; cr2 = pc[1 * l1ido + 0]; ci2 = pc[1 * l1ido + 1]; wr = LD_PS1(wa1[i - 2]); wi = LD_PS1(wa1[i - 1]); VCPLXMULCONJ(cr2, ci2, wr, wi); cr3 = pc[2 * l1ido + 0]; ci3 = pc[2 * l1ido + 1]; wr = LD_PS1(wa2[i - 2]); wi = LD_PS1(wa2[i - 1]); VCPLXMULCONJ(cr3, ci3, wr, wi); cr4 = pc[3 * l1ido]; ci4 = pc[3 * l1ido + 1]; wr = LD_PS1(wa3[i - 2]); wi = LD_PS1(wa3[i - 1]); VCPLXMULCONJ(cr4, ci4, wr, wi); /* at this point, on SSE, five of "cr2 cr3 cr4 ci2 ci3 ci4" should be loaded in registers */ tr1 = VADD(cr2, cr4); tr4 = VSUB(cr4, cr2); tr2 = VADD(pc[0], cr3); tr3 = VSUB(pc[0], cr3); ch[i - 1 + 4 * k] = VADD(tr1, tr2); ch[ic - 1 + 4 * k + 3 * ido] = VSUB(tr2, tr1); // at this point tr1 and tr2 can be disposed ti1 = VADD(ci2, ci4); ti4 = VSUB(ci2, ci4); ch[i - 1 + 4 * k + 2 * ido] = VADD(ti4, tr3); ch[ic - 1 + 4 * k + 1 * ido] = VSUB(tr3, ti4); // dispose tr3, ti4 ti2 = VADD(pc[1], ci3); ti3 = VSUB(pc[1], ci3); ch[i + 4 * k] = VADD(ti1, ti2); ch[ic + 4 * k + 3 * ido] = VSUB(ti1, ti2); ch[i + 4 * k + 2 * ido] = VADD(tr4, ti3); ch[ic + 4 * k + 1 * ido] = VSUB(tr4, ti3); } } if (ido % 2 == 1) return; } for (k = 0; k < l1ido; k += ido) { v4sf a = cc[ido - 1 + k + l1ido], b = cc[ido - 1 + k + 3 * l1ido]; v4sf c = cc[ido - 1 + k], d = cc[ido - 1 + k + 2 * l1ido]; v4sf ti1 = SVMUL(minus_hsqt2, VADD(a, b)); v4sf tr1 = SVMUL(minus_hsqt2, VSUB(b, a)); ch[ido - 1 + 4 * k] = VADD(tr1, c); ch[ido - 1 + 4 * k + 2 * ido] = VSUB(c, tr1); ch[4 * k + 1 * ido] = VSUB(ti1, d); ch[4 * k + 3 * ido] = VADD(ti1, d); } } /* radf4 */ static NEVER_INLINE(void) radb4_ps(int ido, int l1, const v4sf * RESTRICT cc, v4sf * RESTRICT ch, const float *RESTRICT wa1, const float *RESTRICT wa2, const float *RESTRICT wa3) { static const float minus_sqrt2 = (float)-1.414213562373095; static const float two = 2.f; int i, k, l1ido = l1 * ido; v4sf ci2, ci3, ci4, cr2, cr3, cr4, ti1, ti2, ti3, ti4, tr1, tr2, tr3, tr4; { const v4sf *RESTRICT cc_ = cc, *RESTRICT ch_end = ch + l1ido; v4sf *ch_ = ch; while (ch < ch_end) { v4sf a = cc[0], b = cc[4 * ido - 1]; v4sf c = cc[2 * ido], d = cc[2 * ido - 1]; tr3 = SVMUL(two, d); tr2 = VADD(a, b); tr1 = VSUB(a, b); tr4 = SVMUL(two, c); ch[0 * l1ido] = VADD(tr2, tr3); ch[2 * l1ido] = VSUB(tr2, tr3); ch[1 * l1ido] = VSUB(tr1, tr4); ch[3 * l1ido] = VADD(tr1, tr4); cc += 4 * ido; ch += ido; } cc = cc_; ch = ch_; } if (ido < 2) return; if (ido != 2) { for (k = 0; k < l1ido; k += ido) { const v4sf *RESTRICT pc = (v4sf *) (cc - 1 + 4 * k); v4sf *RESTRICT ph = (v4sf *) (ch + k + 1); for (i = 2; i < ido; i += 2) { tr1 = VSUB(pc[i], pc[4 * ido - i]); tr2 = VADD(pc[i], pc[4 * ido - i]); ti4 = VSUB(pc[2 * ido + i], pc[2 * ido - i]); tr3 = VADD(pc[2 * ido + i], pc[2 * ido - i]); ph[0] = VADD(tr2, tr3); cr3 = VSUB(tr2, tr3); ti3 = VSUB(pc[2 * ido + i + 1], pc[2 * ido - i + 1]); tr4 = VADD(pc[2 * ido + i + 1], pc[2 * ido - i + 1]); cr2 = VSUB(tr1, tr4); cr4 = VADD(tr1, tr4); ti1 = VADD(pc[i + 1], pc[4 * ido - i + 1]); ti2 = VSUB(pc[i + 1], pc[4 * ido - i + 1]); ph[1] = VADD(ti2, ti3); ph += l1ido; ci3 = VSUB(ti2, ti3); ci2 = VADD(ti1, ti4); ci4 = VSUB(ti1, ti4); VCPLXMUL(cr2, ci2, LD_PS1(wa1[i - 2]), LD_PS1(wa1[i - 1])); ph[0] = cr2; ph[1] = ci2; ph += l1ido; VCPLXMUL(cr3, ci3, LD_PS1(wa2[i - 2]), LD_PS1(wa2[i - 1])); ph[0] = cr3; ph[1] = ci3; ph += l1ido; VCPLXMUL(cr4, ci4, LD_PS1(wa3[i - 2]), LD_PS1(wa3[i - 1])); ph[0] = cr4; ph[1] = ci4; ph = ph - 3 * l1ido + 2; } } if (ido % 2 == 1) return; } for (k = 0; k < l1ido; k += ido) { int i0 = 4 * k + ido; v4sf c = cc[i0 - 1], d = cc[i0 + 2 * ido - 1]; v4sf a = cc[i0 + 0], b = cc[i0 + 2 * ido + 0]; tr1 = VSUB(c, d); tr2 = VADD(c, d); ti1 = VADD(b, a); ti2 = VSUB(b, a); ch[ido - 1 + k + 0 * l1ido] = VADD(tr2, tr2); ch[ido - 1 + k + 1 * l1ido] = SVMUL(minus_sqrt2, VSUB(ti1, tr1)); ch[ido - 1 + k + 2 * l1ido] = VADD(ti2, ti2); ch[ido - 1 + k + 3 * l1ido] = SVMUL(minus_sqrt2, VADD(ti1, tr1)); } } /* radb4 */ static void radf5_ps(int ido, int l1, const v4sf * RESTRICT cc, v4sf * RESTRICT ch, const float *wa1, const float *wa2, const float *wa3, const float *wa4) { static const float tr11 = .309016994374947f; static const float ti11 = .951056516295154f; static const float tr12 = -.809016994374947f; static const float ti12 = .587785252292473f; /* System generated locals */ int cc_offset, ch_offset; /* Local variables */ int i, k, ic; v4sf ci2, di2, ci4, ci5, di3, di4, di5, ci3, cr2, cr3, dr2, dr3, dr4, dr5, cr5, cr4, ti2, ti3, ti5, ti4, tr2, tr3, tr4, tr5; int idp2; #define cc_ref(a_1,a_2,a_3) cc[((a_3)*l1 + (a_2))*ido + a_1] #define ch_ref(a_1,a_2,a_3) ch[((a_3)*5 + (a_2))*ido + a_1] /* Parameter adjustments */ ch_offset = 1 + ido * 6; ch -= ch_offset; cc_offset = 1 + ido * (1 + l1); cc -= cc_offset; /* Function Body */ for (k = 1; k <= l1; ++k) { cr2 = VADD(cc_ref(1, k, 5), cc_ref(1, k, 2)); ci5 = VSUB(cc_ref(1, k, 5), cc_ref(1, k, 2)); cr3 = VADD(cc_ref(1, k, 4), cc_ref(1, k, 3)); ci4 = VSUB(cc_ref(1, k, 4), cc_ref(1, k, 3)); ch_ref(1, 1, k) = VADD(cc_ref(1, k, 1), VADD(cr2, cr3)); ch_ref(ido, 2, k) = VADD(cc_ref(1, k, 1), VADD(SVMUL(tr11, cr2), SVMUL(tr12, cr3))); ch_ref(1, 3, k) = VADD(SVMUL(ti11, ci5), SVMUL(ti12, ci4)); ch_ref(ido, 4, k) = VADD(cc_ref(1, k, 1), VADD(SVMUL(tr12, cr2), SVMUL(tr11, cr3))); ch_ref(1, 5, k) = VSUB(SVMUL(ti12, ci5), SVMUL(ti11, ci4)); //printf("pffft: radf5, k=%d ch_ref=%f, ci4=%f\n", k, ch_ref(1, 5, k), ci4); } if (ido == 1) { return; } idp2 = ido + 2; for (k = 1; k <= l1; ++k) { for (i = 3; i <= ido; i += 2) { ic = idp2 - i; dr2 = LD_PS1(wa1[i - 3]); di2 = LD_PS1(wa1[i - 2]); dr3 = LD_PS1(wa2[i - 3]); di3 = LD_PS1(wa2[i - 2]); dr4 = LD_PS1(wa3[i - 3]); di4 = LD_PS1(wa3[i - 2]); dr5 = LD_PS1(wa4[i - 3]); di5 = LD_PS1(wa4[i - 2]); VCPLXMULCONJ(dr2, di2, cc_ref(i - 1, k, 2), cc_ref(i, k, 2)); VCPLXMULCONJ(dr3, di3, cc_ref(i - 1, k, 3), cc_ref(i, k, 3)); VCPLXMULCONJ(dr4, di4, cc_ref(i - 1, k, 4), cc_ref(i, k, 4)); VCPLXMULCONJ(dr5, di5, cc_ref(i - 1, k, 5), cc_ref(i, k, 5)); cr2 = VADD(dr2, dr5); ci5 = VSUB(dr5, dr2); cr5 = VSUB(di2, di5); ci2 = VADD(di2, di5); cr3 = VADD(dr3, dr4); ci4 = VSUB(dr4, dr3); cr4 = VSUB(di3, di4); ci3 = VADD(di3, di4); ch_ref(i - 1, 1, k) = VADD(cc_ref(i - 1, k, 1), VADD(cr2, cr3)); ch_ref(i, 1, k) = VSUB(cc_ref(i, k, 1), VADD(ci2, ci3)); // tr2 = VADD(cc_ref(i - 1, k, 1), VADD(SVMUL(tr11, cr2), SVMUL(tr12, cr3))); ti2 = VSUB(cc_ref(i, k, 1), VADD(SVMUL(tr11, ci2), SVMUL(tr12, ci3))); // tr3 = VADD(cc_ref(i - 1, k, 1), VADD(SVMUL(tr12, cr2), SVMUL(tr11, cr3))); ti3 = VSUB(cc_ref(i, k, 1), VADD(SVMUL(tr12, ci2), SVMUL(tr11, ci3))); // tr5 = VADD(SVMUL(ti11, cr5), SVMUL(ti12, cr4)); ti5 = VADD(SVMUL(ti11, ci5), SVMUL(ti12, ci4)); tr4 = VSUB(SVMUL(ti12, cr5), SVMUL(ti11, cr4)); ti4 = VSUB(SVMUL(ti12, ci5), SVMUL(ti11, ci4)); ch_ref(i - 1, 3, k) = VSUB(tr2, tr5); ch_ref(ic - 1, 2, k) = VADD(tr2, tr5); ch_ref(i, 3, k) = VADD(ti2, ti5); ch_ref(ic, 2, k) = VSUB(ti5, ti2); ch_ref(i - 1, 5, k) = VSUB(tr3, tr4); ch_ref(ic - 1, 4, k) = VADD(tr3, tr4); ch_ref(i, 5, k) = VADD(ti3, ti4); ch_ref(ic, 4, k) = VSUB(ti4, ti3); } } #undef cc_ref #undef ch_ref } /* radf5 */ static void radb5_ps(int ido, int l1, const v4sf * RESTRICT cc, v4sf * RESTRICT ch, const float *wa1, const float *wa2, const float *wa3, const float *wa4) { static const float tr11 = .309016994374947f; static const float ti11 = .951056516295154f; static const float tr12 = -.809016994374947f; static const float ti12 = .587785252292473f; int cc_offset, ch_offset; /* Local variables */ int i, k, ic; v4sf ci2, ci3, ci4, ci5, di3, di4, di5, di2, cr2, cr3, cr5, cr4, ti2, ti3, ti4, ti5, dr3, dr4, dr5, dr2, tr2, tr3, tr4, tr5; int idp2; #define cc_ref(a_1,a_2,a_3) cc[((a_3)*5 + (a_2))*ido + a_1] #define ch_ref(a_1,a_2,a_3) ch[((a_3)*l1 + (a_2))*ido + a_1] /* Parameter adjustments */ ch_offset = 1 + ido * (1 + l1); ch -= ch_offset; cc_offset = 1 + ido * 6; cc -= cc_offset; /* Function Body */ for (k = 1; k <= l1; ++k) { ti5 = VADD(cc_ref(1, 3, k), cc_ref(1, 3, k)); ti4 = VADD(cc_ref(1, 5, k), cc_ref(1, 5, k)); tr2 = VADD(cc_ref(ido, 2, k), cc_ref(ido, 2, k)); tr3 = VADD(cc_ref(ido, 4, k), cc_ref(ido, 4, k)); ch_ref(1, k, 1) = VADD(cc_ref(1, 1, k), VADD(tr2, tr3)); cr2 = VADD(cc_ref(1, 1, k), VADD(SVMUL(tr11, tr2), SVMUL(tr12, tr3))); cr3 = VADD(cc_ref(1, 1, k), VADD(SVMUL(tr12, tr2), SVMUL(tr11, tr3))); ci5 = VADD(SVMUL(ti11, ti5), SVMUL(ti12, ti4)); ci4 = VSUB(SVMUL(ti12, ti5), SVMUL(ti11, ti4)); ch_ref(1, k, 2) = VSUB(cr2, ci5); ch_ref(1, k, 3) = VSUB(cr3, ci4); ch_ref(1, k, 4) = VADD(cr3, ci4); ch_ref(1, k, 5) = VADD(cr2, ci5); } if (ido == 1) { return; } idp2 = ido + 2; for (k = 1; k <= l1; ++k) { for (i = 3; i <= ido; i += 2) { ic = idp2 - i; ti5 = VADD(cc_ref(i, 3, k), cc_ref(ic, 2, k)); ti2 = VSUB(cc_ref(i, 3, k), cc_ref(ic, 2, k)); ti4 = VADD(cc_ref(i, 5, k), cc_ref(ic, 4, k)); ti3 = VSUB(cc_ref(i, 5, k), cc_ref(ic, 4, k)); tr5 = VSUB(cc_ref(i - 1, 3, k), cc_ref(ic - 1, 2, k)); tr2 = VADD(cc_ref(i - 1, 3, k), cc_ref(ic - 1, 2, k)); tr4 = VSUB(cc_ref(i - 1, 5, k), cc_ref(ic - 1, 4, k)); tr3 = VADD(cc_ref(i - 1, 5, k), cc_ref(ic - 1, 4, k)); ch_ref(i - 1, k, 1) = VADD(cc_ref(i - 1, 1, k), VADD(tr2, tr3)); ch_ref(i, k, 1) = VADD(cc_ref(i, 1, k), VADD(ti2, ti3)); cr2 = VADD(cc_ref(i - 1, 1, k), VADD(SVMUL(tr11, tr2), SVMUL(tr12, tr3))); ci2 = VADD(cc_ref(i, 1, k), VADD(SVMUL(tr11, ti2), SVMUL(tr12, ti3))); cr3 = VADD(cc_ref(i - 1, 1, k), VADD(SVMUL(tr12, tr2), SVMUL(tr11, tr3))); ci3 = VADD(cc_ref(i, 1, k), VADD(SVMUL(tr12, ti2), SVMUL(tr11, ti3))); cr5 = VADD(SVMUL(ti11, tr5), SVMUL(ti12, tr4)); ci5 = VADD(SVMUL(ti11, ti5), SVMUL(ti12, ti4)); cr4 = VSUB(SVMUL(ti12, tr5), SVMUL(ti11, tr4)); ci4 = VSUB(SVMUL(ti12, ti5), SVMUL(ti11, ti4)); dr3 = VSUB(cr3, ci4); dr4 = VADD(cr3, ci4); di3 = VADD(ci3, cr4); di4 = VSUB(ci3, cr4); dr5 = VADD(cr2, ci5); dr2 = VSUB(cr2, ci5); di5 = VSUB(ci2, cr5); di2 = VADD(ci2, cr5); VCPLXMUL(dr2, di2, LD_PS1(wa1[i - 3]), LD_PS1(wa1[i - 2])); VCPLXMUL(dr3, di3, LD_PS1(wa2[i - 3]), LD_PS1(wa2[i - 2])); VCPLXMUL(dr4, di4, LD_PS1(wa3[i - 3]), LD_PS1(wa3[i - 2])); VCPLXMUL(dr5, di5, LD_PS1(wa4[i - 3]), LD_PS1(wa4[i - 2])); ch_ref(i - 1, k, 2) = dr2; ch_ref(i, k, 2) = di2; ch_ref(i - 1, k, 3) = dr3; ch_ref(i, k, 3) = di3; ch_ref(i - 1, k, 4) = dr4; ch_ref(i, k, 4) = di4; ch_ref(i - 1, k, 5) = dr5; ch_ref(i, k, 5) = di5; } } #undef cc_ref #undef ch_ref } /* radb5 */ static NEVER_INLINE(v4sf *) rfftf1_ps(int n, const v4sf * input_readonly, v4sf * work1, v4sf * work2, const float *wa, const int *ifac) { v4sf *in = (v4sf *) input_readonly; v4sf *out = (in == work2 ? work1 : work2); int nf = ifac[1], k1; int l2 = n; int iw = n - 1; assert(in != out && work1 != work2); for (k1 = 1; k1 <= nf; ++k1) { int kh = nf - k1; int ip = ifac[kh + 2]; int l1 = l2 / ip; int ido = n / l2; iw -= (ip - 1) * ido; switch (ip) { case 5:{ int ix2 = iw + ido; int ix3 = ix2 + ido; int ix4 = ix3 + ido; radf5_ps(ido, l1, in, out, &wa[iw], &wa[ix2], &wa[ix3], &wa[ix4]); } break; case 4:{ int ix2 = iw + ido; int ix3 = ix2 + ido; radf4_ps(ido, l1, in, out, &wa[iw], &wa[ix2], &wa[ix3]); } break; case 3:{ int ix2 = iw + ido; radf3_ps(ido, l1, in, out, &wa[iw], &wa[ix2]); } break; case 2: radf2_ps(ido, l1, in, out, &wa[iw]); break; default: assert(0); break; } l2 = l1; if (out == work2) { out = work1; in = work2; } else { out = work2; in = work1; } } return in; /* this is in fact the output .. */ } /* rfftf1 */ static NEVER_INLINE(v4sf *) rfftb1_ps(int n, const v4sf * input_readonly, v4sf * work1, v4sf * work2, const float *wa, const int *ifac) { v4sf *in = (v4sf *) input_readonly; v4sf *out = (in == work2 ? work1 : work2); int nf = ifac[1], k1; int l1 = 1; int iw = 0; assert(in != out); for (k1 = 1; k1 <= nf; k1++) { int ip = ifac[k1 + 1]; int l2 = ip * l1; int ido = n / l2; switch (ip) { case 5:{ int ix2 = iw + ido; int ix3 = ix2 + ido; int ix4 = ix3 + ido; radb5_ps(ido, l1, in, out, &wa[iw], &wa[ix2], &wa[ix3], &wa[ix4]); } break; case 4:{ int ix2 = iw + ido; int ix3 = ix2 + ido; radb4_ps(ido, l1, in, out, &wa[iw], &wa[ix2], &wa[ix3]); } break; case 3:{ int ix2 = iw + ido; radb3_ps(ido, l1, in, out, &wa[iw], &wa[ix2]); } break; case 2: radb2_ps(ido, l1, in, out, &wa[iw]); break; default: assert(0); break; } l1 = l2; iw += (ip - 1) * ido; if (out == work2) { out = work1; in = work2; } else { out = work2; in = work1; } } return in; /* this is in fact the output .. */ } static int decompose(int n, int *ifac, const int *ntryh) { int nl = n, nf = 0, i, j = 0; for (j = 0; ntryh[j]; ++j) { int ntry = ntryh[j]; while (nl != 1) { int nq = nl / ntry; int nr = nl - ntry * nq; if (nr == 0) { ifac[2 + nf++] = ntry; nl = nq; if (ntry == 2 && nf != 1) { for (i = 2; i <= nf; ++i) { int ib = nf - i + 2; ifac[ib + 1] = ifac[ib]; } ifac[2] = 2; } } else break; } } ifac[0] = n; ifac[1] = nf; return nf; } static void rffti1_ps(int n, float *wa, int *ifac) { static const int ntryh[] = { 4, 2, 3, 5, 0 }; int k1, j, ii; int nf = decompose(n, ifac, ntryh); float argh = (2 * (float)M_PI) / n; int is = 0; int nfm1 = nf - 1; int l1 = 1; for (k1 = 1; k1 <= nfm1; k1++) { int ip = ifac[k1 + 1]; int ld = 0; int l2 = l1 * ip; int ido = n / l2; int ipm = ip - 1; for (j = 1; j <= ipm; ++j) { float argld; int i = is, fi = 0; ld += l1; argld = ld * argh; for (ii = 3; ii <= ido; ii += 2) { i += 2; fi += 1; wa[i - 2] = cosf(fi * argld); wa[i - 1] = sinf(fi * argld); } is += ido; } l1 = l2; } } /* rffti1 */ static void cffti1_ps(int n, float *wa, int *ifac) { static const int ntryh[] = { 5, 3, 4, 2, 0 }; int k1, j, ii; int nf = decompose(n, ifac, ntryh); float argh = (2 * (float)M_PI) / (float)n; int i = 1; int l1 = 1; for (k1 = 1; k1 <= nf; k1++) { int ip = ifac[k1 + 1]; int ld = 0; int l2 = l1 * ip; int ido = n / l2; int idot = ido + ido + 2; int ipm = ip - 1; for (j = 1; j <= ipm; j++) { float argld; int i1 = i, fi = 0; wa[i - 1] = 1; wa[i] = 0; ld += l1; argld = ld * argh; for (ii = 4; ii <= idot; ii += 2) { i += 2; fi += 1; wa[i - 1] = cosf(fi * argld); wa[i] = sinf(fi * argld); } if (ip > 5) { wa[i1 - 1] = wa[i - 1]; wa[i1] = wa[i]; } } l1 = l2; } } /* cffti1 */ static v4sf *cfftf1_ps(int n, const v4sf * input_readonly, v4sf * work1, v4sf * work2, const float *wa, const int *ifac, int isign) { v4sf *in = (v4sf *) input_readonly; v4sf *out = (in == work2 ? work1 : work2); int nf = ifac[1], k1; int l1 = 1; int iw = 0; assert(in != out && work1 != work2); for (k1 = 2; k1 <= nf + 1; k1++) { int ip = ifac[k1]; int l2 = ip * l1; int ido = n / l2; int idot = ido + ido; switch (ip) { case 5:{ int ix2 = iw + idot; int ix3 = ix2 + idot; int ix4 = ix3 + idot; passf5_ps(idot, l1, in, out, &wa[iw], &wa[ix2], &wa[ix3], &wa[ix4], isign); } break; case 4:{ int ix2 = iw + idot; int ix3 = ix2 + idot; passf4_ps(idot, l1, in, out, &wa[iw], &wa[ix2], &wa[ix3], isign); } break; case 2:{ passf2_ps(idot, l1, in, out, &wa[iw], isign); } break; case 3:{ int ix2 = iw + idot; passf3_ps(idot, l1, in, out, &wa[iw], &wa[ix2], isign); } break; default: assert(0); } l1 = l2; iw += (ip - 1) * idot; if (out == work2) { out = work1; in = work2; } else { out = work2; in = work1; } } return in; /* this is in fact the output .. */ } struct PFFFT_Setup { int N; int Ncvec; // nb of complex simd vectors (N/4 if PFFFT_COMPLEX, N/8 if PFFFT_REAL) int ifac[15]; pffft_transform_t transform; v4sf *data; // allocated room for twiddle coefs float *e; // points into 'data' , N/4*3 elements float *twiddle; // points into 'data', N/4 elements }; struct funcs { PFFFT_Setup * (*new_setup) (int N, pffft_transform_t transform); void (*transform) (PFFFT_Setup *setup, const float *input, float *output, float *work, pffft_direction_t direction, int ordered); void (*zreorder)(PFFFT_Setup *setup, const float *input, float *output, pffft_direction_t direction); void (*zconvolve_accumulate)(PFFFT_Setup *setup, const float *dft_a, const float *dft_b, const float *dft_c, float *dft_ab, float scaling); void (*zconvolve)(PFFFT_Setup *setup, const float *dft_a, const float *dft_b, float *dft_ab, float scaling); int (*simd_size)(void); void (*validate)(void); }; static PFFFT_Setup *new_setup_simd(int N, pffft_transform_t transform) { PFFFT_Setup *s = (PFFFT_Setup *) malloc(sizeof(PFFFT_Setup)); int k, m; /* unfortunately, the fft size must be a multiple of 16 for complex FFTs and 32 for real FFTs -- a lot of stuff would need to be rewritten to handle other cases (or maybe just switch to a scalar fft, I don't know..) */ if (transform == PFFFT_REAL) { assert((N % (2 * SIMD_SZ * SIMD_SZ)) == 0 && N > 0); } if (transform == PFFFT_COMPLEX) { assert((N % (SIMD_SZ * SIMD_SZ)) == 0 && N > 0); } //assert((N % 32) == 0); s->N = N; s->transform = transform; /* nb of complex simd vectors */ s->Ncvec = (transform == PFFFT_REAL ? N / 2 : N) / SIMD_SZ; s->data = (v4sf *) pffft_aligned_malloc(2 * s->Ncvec * sizeof(v4sf)); s->e = (float *)s->data; s->twiddle = (float *)(s->data + (2 * s->Ncvec * (SIMD_SZ - 1)) / SIMD_SZ); if (transform == PFFFT_REAL) { for (k = 0; k < s->Ncvec; ++k) { int i = k / SIMD_SZ; int j = k % SIMD_SZ; for (m = 0; m < SIMD_SZ - 1; ++m) { float A = -2 * (float)M_PI * (m + 1) * k / N; s->e[(2 * (i * 3 + m) + 0) * SIMD_SZ + j] = cosf(A); s->e[(2 * (i * 3 + m) + 1) * SIMD_SZ + j] = sinf(A); } } rffti1_ps(N / SIMD_SZ, s->twiddle, s->ifac); } else { for (k = 0; k < s->Ncvec; ++k) { int i = k / SIMD_SZ; int j = k % SIMD_SZ; for (m = 0; m < SIMD_SZ - 1; ++m) { float A = -2 * (float)M_PI * (m + 1) * k / N; s->e[(2 * (i * 3 + m) + 0) * SIMD_SZ + j] = cosf(A); s->e[(2 * (i * 3 + m) + 1) * SIMD_SZ + j] = sinf(A); } } cffti1_ps(N / SIMD_SZ, s->twiddle, s->ifac); } /* check that N is decomposable with allowed prime factors */ for (k = 0, m = 1; k < s->ifac[1]; ++k) { m *= s->ifac[2 + k]; } if (m != N / SIMD_SZ) { pffft_destroy_setup(s); s = 0; } return s; } #if !defined(PFFFT_SIMD_DISABLE) /* [0 0 1 2 3 4 5 6 7 8] -> [0 8 7 6 5 4 3 2 1] */ static void reversed_copy(int N, const v4sf * in, int in_stride, v4sf * out) { v4sf g0, g1; int k; INTERLEAVE2(in[0], in[1], g0, g1); in += in_stride; *--out = VSWAPHL(g0, g1); // [g0l, g0h], [g1l g1h] -> [g1l, g0h] for (k = 1; k < N; ++k) { v4sf h0, h1; INTERLEAVE2(in[0], in[1], h0, h1); in += in_stride; *--out = VSWAPHL(g1, h0); *--out = VSWAPHL(h0, h1); g1 = h1; } *--out = VSWAPHL(g1, g0); } static void unreversed_copy(int N, const v4sf * in, v4sf * out, int out_stride) { v4sf g0, g1, h0, h1; int k; g0 = g1 = in[0]; ++in; for (k = 1; k < N; ++k) { h0 = *in++; h1 = *in++; g1 = VSWAPHL(g1, h0); h0 = VSWAPHL(h0, h1); UNINTERLEAVE2(h0, g1, out[0], out[1]); out += out_stride; g1 = h1; } h0 = *in++; h1 = g0; g1 = VSWAPHL(g1, h0); h0 = VSWAPHL(h0, h1); UNINTERLEAVE2(h0, g1, out[0], out[1]); } static void zreorder_simd(PFFFT_Setup * setup, const float *in, float *out, pffft_direction_t direction) { int k, N = setup->N, Ncvec = setup->Ncvec; const v4sf *vin = (const v4sf *)in; v4sf *vout = (v4sf *) out; assert(in != out); if (setup->transform == PFFFT_REAL) { int k, dk = N / 32; if (direction == PFFFT_FORWARD) { for (k = 0; k < dk; ++k) { INTERLEAVE2(vin[k * 8 + 0], vin[k * 8 + 1], vout[2 * (0 * dk + k) + 0], vout[2 * (0 * dk + k) + 1]); INTERLEAVE2(vin[k * 8 + 4], vin[k * 8 + 5], vout[2 * (2 * dk + k) + 0], vout[2 * (2 * dk + k) + 1]); } reversed_copy(dk, vin + 2, 8, (v4sf *) (out + N / 2)); reversed_copy(dk, vin + 6, 8, (v4sf *) (out + N)); } else { for (k = 0; k < dk; ++k) { UNINTERLEAVE2(vin[2 * (0 * dk + k) + 0], vin[2 * (0 * dk + k) + 1], vout[k * 8 + 0], vout[k * 8 + 1]); UNINTERLEAVE2(vin[2 * (2 * dk + k) + 0], vin[2 * (2 * dk + k) + 1], vout[k * 8 + 4], vout[k * 8 + 5]); } unreversed_copy(dk, (v4sf *) (in + N / 4), (v4sf *) (out + N - 6 * SIMD_SZ), -8); unreversed_copy(dk, (v4sf *) (in + 3 * N / 4), (v4sf *) (out + N - 2 * SIMD_SZ), -8); } } else { if (direction == PFFFT_FORWARD) { for (k = 0; k < Ncvec; ++k) { int kk = (k / 4) + (k % 4) * (Ncvec / 4); INTERLEAVE2(vin[k * 2], vin[k * 2 + 1], vout[kk * 2], vout[kk * 2 + 1]); } } else { for (k = 0; k < Ncvec; ++k) { int kk = (k / 4) + (k % 4) * (Ncvec / 4); UNINTERLEAVE2(vin[kk * 2], vin[kk * 2 + 1], vout[k * 2], vout[k * 2 + 1]); } } } } static void pffft_cplx_finalize(int Ncvec, const v4sf * in, v4sf * out, const v4sf * e) { int k, dk = Ncvec / SIMD_SZ; // number of 4x4 matrix blocks v4sf r0, i0, r1, i1, r2, i2, r3, i3; v4sf sr0, dr0, sr1, dr1, si0, di0, si1, di1; assert(in != out); for (k = 0; k < dk; ++k) { r0 = in[8 * k + 0]; i0 = in[8 * k + 1]; r1 = in[8 * k + 2]; i1 = in[8 * k + 3]; r2 = in[8 * k + 4]; i2 = in[8 * k + 5]; r3 = in[8 * k + 6]; i3 = in[8 * k + 7]; VTRANSPOSE4(r0, r1, r2, r3); VTRANSPOSE4(i0, i1, i2, i3); VCPLXMUL(r1, i1, e[k * 6 + 0], e[k * 6 + 1]); VCPLXMUL(r2, i2, e[k * 6 + 2], e[k * 6 + 3]); VCPLXMUL(r3, i3, e[k * 6 + 4], e[k * 6 + 5]); sr0 = VADD(r0, r2); dr0 = VSUB(r0, r2); sr1 = VADD(r1, r3); dr1 = VSUB(r1, r3); si0 = VADD(i0, i2); di0 = VSUB(i0, i2); si1 = VADD(i1, i3); di1 = VSUB(i1, i3); /* transformation for each column is: [1 1 1 1 0 0 0 0] [r0] [1 0 -1 0 0 -1 0 1] [r1] [1 -1 1 -1 0 0 0 0] [r2] [1 0 -1 0 0 1 0 -1] [r3] [0 0 0 0 1 1 1 1] * [i0] [0 1 0 -1 1 0 -1 0] [i1] [0 0 0 0 1 -1 1 -1] [i2] [0 -1 0 1 1 0 -1 0] [i3] */ r0 = VADD(sr0, sr1); i0 = VADD(si0, si1); r1 = VADD(dr0, di1); i1 = VSUB(di0, dr1); r2 = VSUB(sr0, sr1); i2 = VSUB(si0, si1); r3 = VSUB(dr0, di1); i3 = VADD(di0, dr1); *out++ = r0; *out++ = i0; *out++ = r1; *out++ = i1; *out++ = r2; *out++ = i2; *out++ = r3; *out++ = i3; } } static void pffft_cplx_preprocess(int Ncvec, const v4sf * in, v4sf * out, const v4sf * e) { int k, dk = Ncvec / SIMD_SZ; // number of 4x4 matrix blocks v4sf r0, i0, r1, i1, r2, i2, r3, i3; v4sf sr0, dr0, sr1, dr1, si0, di0, si1, di1; assert(in != out); for (k = 0; k < dk; ++k) { r0 = in[8 * k + 0]; i0 = in[8 * k + 1]; r1 = in[8 * k + 2]; i1 = in[8 * k + 3]; r2 = in[8 * k + 4]; i2 = in[8 * k + 5]; r3 = in[8 * k + 6]; i3 = in[8 * k + 7]; sr0 = VADD(r0, r2); dr0 = VSUB(r0, r2); sr1 = VADD(r1, r3); dr1 = VSUB(r1, r3); si0 = VADD(i0, i2); di0 = VSUB(i0, i2); si1 = VADD(i1, i3); di1 = VSUB(i1, i3); r0 = VADD(sr0, sr1); i0 = VADD(si0, si1); r1 = VSUB(dr0, di1); i1 = VADD(di0, dr1); r2 = VSUB(sr0, sr1); i2 = VSUB(si0, si1); r3 = VADD(dr0, di1); i3 = VSUB(di0, dr1); VCPLXMULCONJ(r1, i1, e[k * 6 + 0], e[k * 6 + 1]); VCPLXMULCONJ(r2, i2, e[k * 6 + 2], e[k * 6 + 3]); VCPLXMULCONJ(r3, i3, e[k * 6 + 4], e[k * 6 + 5]); VTRANSPOSE4(r0, r1, r2, r3); VTRANSPOSE4(i0, i1, i2, i3); *out++ = r0; *out++ = i0; *out++ = r1; *out++ = i1; *out++ = r2; *out++ = i2; *out++ = r3; *out++ = i3; } } static ALWAYS_INLINE(void) pffft_real_finalize_4x4(const v4sf * in0, const v4sf * in1, const v4sf * in, const v4sf * e, v4sf * out) { v4sf r0, i0, r1, i1, r2, i2, r3, i3; v4sf sr0, dr0, sr1, dr1, si0, di0, si1, di1; r0 = *in0; i0 = *in1; r1 = *in++; i1 = *in++; r2 = *in++; i2 = *in++; r3 = *in++; i3 = *in++; VTRANSPOSE4(r0, r1, r2, r3); VTRANSPOSE4(i0, i1, i2, i3); /* transformation for each column is: [1 1 1 1 0 0 0 0] [r0] [1 0 -1 0 0 -1 0 1] [r1] [1 0 -1 0 0 1 0 -1] [r2] [1 -1 1 -1 0 0 0 0] [r3] [0 0 0 0 1 1 1 1] * [i0] [0 -1 0 1 -1 0 1 0] [i1] [0 -1 0 1 1 0 -1 0] [i2] [0 0 0 0 -1 1 -1 1] [i3] */ //cerr << "matrix initial, before e , REAL:\n 1: " << r0 << "\n 1: " << r1 << "\n 1: " << r2 << "\n 1: " << r3 << "\n"; //cerr << "matrix initial, before e, IMAG :\n 1: " << i0 << "\n 1: " << i1 << "\n 1: " << i2 << "\n 1: " << i3 << "\n"; VCPLXMUL(r1, i1, e[0], e[1]); VCPLXMUL(r2, i2, e[2], e[3]); VCPLXMUL(r3, i3, e[4], e[5]); //cerr << "matrix initial, real part:\n 1: " << r0 << "\n 1: " << r1 << "\n 1: " << r2 << "\n 1: " << r3 << "\n"; //cerr << "matrix initial, imag part:\n 1: " << i0 << "\n 1: " << i1 << "\n 1: " << i2 << "\n 1: " << i3 << "\n"; sr0 = VADD(r0, r2); dr0 = VSUB(r0, r2); sr1 = VADD(r1, r3); dr1 = VSUB(r3, r1); si0 = VADD(i0, i2); di0 = VSUB(i0, i2); si1 = VADD(i1, i3); di1 = VSUB(i3, i1); r0 = VADD(sr0, sr1); r3 = VSUB(sr0, sr1); i0 = VADD(si0, si1); i3 = VSUB(si1, si0); r1 = VADD(dr0, di1); r2 = VSUB(dr0, di1); i1 = VSUB(dr1, di0); i2 = VADD(dr1, di0); *out++ = r0; *out++ = i0; *out++ = r1; *out++ = i1; *out++ = r2; *out++ = i2; *out++ = r3; *out++ = i3; } static NEVER_INLINE(void) pffft_real_finalize(int Ncvec, const v4sf * in, v4sf * out, const v4sf * e) { int k, dk = Ncvec / SIMD_SZ; // number of 4x4 matrix blocks /* fftpack order is f0r f1r f1i f2r f2i ... f(n-1)r f(n-1)i f(n)r */ v4sf_union cr, ci, *uout = (v4sf_union *) out; v4sf save = in[7], zero = VZERO(); float xr0, xi0, xr1, xi1, xr2, xi2, xr3, xi3; static const float s = (float)M_SQRT2 / 2; cr.v = in[0]; ci.v = in[Ncvec * 2 - 1]; assert(in != out); pffft_real_finalize_4x4(&zero, &zero, in + 1, e, out); /* [cr0 cr1 cr2 cr3 ci0 ci1 ci2 ci3] [Xr(1)] ] [1 1 1 1 0 0 0 0] [Xr(N/4) ] [0 0 0 0 1 s 0 -s] [Xr(N/2) ] [1 0 -1 0 0 0 0 0] [Xr(3N/4)] [0 0 0 0 1 -s 0 s] [Xi(1) ] [1 -1 1 -1 0 0 0 0] [Xi(N/4) ] [0 0 0 0 0 -s -1 -s] [Xi(N/2) ] [0 -1 0 1 0 0 0 0] [Xi(3N/4)] [0 0 0 0 0 -s 1 -s] */ xr0 = (cr.f[0] + cr.f[2]) + (cr.f[1] + cr.f[3]); uout[0].f[0] = xr0; xi0 = (cr.f[0] + cr.f[2]) - (cr.f[1] + cr.f[3]); uout[1].f[0] = xi0; xr2 = (cr.f[0] - cr.f[2]); uout[4].f[0] = xr2; xi2 = (cr.f[3] - cr.f[1]); uout[5].f[0] = xi2; xr1 = ci.f[0] + s * (ci.f[1] - ci.f[3]); uout[2].f[0] = xr1; xi1 = -ci.f[2] - s * (ci.f[1] + ci.f[3]); uout[3].f[0] = xi1; xr3 = ci.f[0] - s * (ci.f[1] - ci.f[3]); uout[6].f[0] = xr3; xi3 = ci.f[2] - s * (ci.f[1] + ci.f[3]); uout[7].f[0] = xi3; for (k = 1; k < dk; ++k) { v4sf save_next = in[8 * k + 7]; pffft_real_finalize_4x4(&save, &in[8 * k + 0], in + 8 * k + 1, e + k * 6, out + k * 8); save = save_next; } } static ALWAYS_INLINE(void) pffft_real_preprocess_4x4(const v4sf * in, const v4sf * e, v4sf * out, int first) { v4sf r0 = in[0], i0 = in[1], r1 = in[2], i1 = in[3], r2 = in[4], i2 = in[5], r3 = in[6], i3 = in[7]; /* transformation for each column is: [1 1 1 1 0 0 0 0] [r0] [1 0 0 -1 0 -1 -1 0] [r1] [1 -1 -1 1 0 0 0 0] [r2] [1 0 0 -1 0 1 1 0] [r3] [0 0 0 0 1 -1 1 -1] * [i0] [0 -1 1 0 1 0 0 1] [i1] [0 0 0 0 1 1 -1 -1] [i2] [0 1 -1 0 1 0 0 1] [i3] */ v4sf sr0 = VADD(r0, r3), dr0 = VSUB(r0, r3); v4sf sr1 = VADD(r1, r2), dr1 = VSUB(r1, r2); v4sf si0 = VADD(i0, i3), di0 = VSUB(i0, i3); v4sf si1 = VADD(i1, i2), di1 = VSUB(i1, i2); r0 = VADD(sr0, sr1); r2 = VSUB(sr0, sr1); r1 = VSUB(dr0, si1); r3 = VADD(dr0, si1); i0 = VSUB(di0, di1); i2 = VADD(di0, di1); i1 = VSUB(si0, dr1); i3 = VADD(si0, dr1); VCPLXMULCONJ(r1, i1, e[0], e[1]); VCPLXMULCONJ(r2, i2, e[2], e[3]); VCPLXMULCONJ(r3, i3, e[4], e[5]); VTRANSPOSE4(r0, r1, r2, r3); VTRANSPOSE4(i0, i1, i2, i3); if (!first) { *out++ = r0; *out++ = i0; } *out++ = r1; *out++ = i1; *out++ = r2; *out++ = i2; *out++ = r3; *out++ = i3; } static NEVER_INLINE(void) pffft_real_preprocess(int Ncvec, const v4sf * in, v4sf * out, const v4sf * e) { int k, dk = Ncvec / SIMD_SZ; // number of 4x4 matrix blocks /* fftpack order is f0r f1r f1i f2r f2i ... f(n-1)r f(n-1)i f(n)r */ v4sf_union Xr, Xi, *uout = (v4sf_union *) out; float cr0, ci0, cr1, ci1, cr2, ci2, cr3, ci3; static const float s = (float)M_SQRT2; assert(in != out); for (k = 0; k < 4; ++k) { Xr.f[k] = ((float *)in)[8 * k]; Xi.f[k] = ((float *)in)[8 * k + 4]; } pffft_real_preprocess_4x4(in, e, out + 1, 1); // will write only 6 values /* [Xr0 Xr1 Xr2 Xr3 Xi0 Xi1 Xi2 Xi3] [cr0] [1 0 2 0 1 0 0 0] [cr1] [1 0 0 0 -1 0 -2 0] [cr2] [1 0 -2 0 1 0 0 0] [cr3] [1 0 0 0 -1 0 2 0] [ci0] [0 2 0 2 0 0 0 0] [ci1] [0 s 0 -s 0 -s 0 -s] [ci2] [0 0 0 0 0 -2 0 2] [ci3] [0 -s 0 s 0 -s 0 -s] */ for (k = 1; k < dk; ++k) { pffft_real_preprocess_4x4(in + 8 * k, e + k * 6, out - 1 + k * 8, 0); } cr0 = (Xr.f[0] + Xi.f[0]) + 2 * Xr.f[2]; uout[0].f[0] = cr0; cr1 = (Xr.f[0] - Xi.f[0]) - 2 * Xi.f[2]; uout[0].f[1] = cr1; cr2 = (Xr.f[0] + Xi.f[0]) - 2 * Xr.f[2]; uout[0].f[2] = cr2; cr3 = (Xr.f[0] - Xi.f[0]) + 2 * Xi.f[2]; uout[0].f[3] = cr3; ci0 = 2 * (Xr.f[1] + Xr.f[3]); uout[2 * Ncvec - 1].f[0] = ci0; ci1 = s * (Xr.f[1] - Xr.f[3]) - s * (Xi.f[1] + Xi.f[3]); uout[2 * Ncvec - 1].f[1] = ci1; ci2 = 2 * (Xi.f[3] - Xi.f[1]); uout[2 * Ncvec - 1].f[2] = ci2; ci3 = -s * (Xr.f[1] - Xr.f[3]) - s * (Xi.f[1] + Xi.f[3]); uout[2 * Ncvec - 1].f[3] = ci3; } static void transform_simd(PFFFT_Setup * setup, const float *finput, float *foutput, float * scratch, pffft_direction_t direction, int ordered) { int k, Ncvec = setup->Ncvec; int nf_odd = (setup->ifac[1] & 1); // temporary buffer is allocated on the stack if the scratch pointer is NULL int stack_allocate = (scratch == 0 ? Ncvec * 2 : 1); VLA_ARRAY_ON_STACK(v4sf, scratch_on_stack, stack_allocate); const v4sf *vinput = (const v4sf *)finput; v4sf *voutput = (v4sf *) foutput; v4sf *buff[2] = { voutput, scratch ? (v4sf*)scratch : scratch_on_stack }; int ib = (nf_odd ^ ordered ? 1 : 0); assert(VALIGNED(finput) && VALIGNED(foutput)); //assert(finput != foutput); if (direction == PFFFT_FORWARD) { ib = !ib; if (setup->transform == PFFFT_REAL) { ib = (rfftf1_ps(Ncvec * 2, vinput, buff[ib], buff[!ib], setup->twiddle, &setup->ifac[0]) == buff[0] ? 0 : 1); pffft_real_finalize(Ncvec, buff[ib], buff[!ib], (v4sf *) setup->e); } else { v4sf *tmp = buff[ib]; for (k = 0; k < Ncvec; ++k) { UNINTERLEAVE2(vinput[k * 2], vinput[k * 2 + 1], tmp[k * 2], tmp[k * 2 + 1]); } ib = (cfftf1_ps(Ncvec, buff[ib], buff[!ib], buff[ib], setup->twiddle, &setup->ifac[0], -1) == buff[0] ? 0 : 1); pffft_cplx_finalize(Ncvec, buff[ib], buff[!ib], (v4sf *) setup->e); } if (ordered) { pffft_zreorder(setup, (float *)buff[!ib], (float *)buff[ib], PFFFT_FORWARD); } else ib = !ib; } else { if (vinput == buff[ib]) { ib = !ib; // may happen when finput == foutput } if (ordered) { pffft_zreorder(setup, (float *)vinput, (float *)buff[ib], PFFFT_BACKWARD); vinput = buff[ib]; ib = !ib; } if (setup->transform == PFFFT_REAL) { pffft_real_preprocess(Ncvec, vinput, buff[ib], (v4sf *) setup->e); ib = (rfftb1_ps (Ncvec * 2, buff[ib], buff[0], buff[1], setup->twiddle, &setup->ifac[0]) == buff[0] ? 0 : 1); } else { pffft_cplx_preprocess(Ncvec, vinput, buff[ib], (v4sf *) setup->e); ib = (cfftf1_ps (Ncvec, buff[ib], buff[0], buff[1], setup->twiddle, &setup->ifac[0], +1) == buff[0] ? 0 : 1); for (k = 0; k < Ncvec; ++k) { INTERLEAVE2(buff[ib][k * 2], buff[ib][k * 2 + 1], buff[ib][k * 2], buff[ib][k * 2 + 1]); } } } if (buff[ib] != voutput) { /* extra copy required -- this situation should only happen when finput == foutput */ assert(finput == foutput); for (k = 0; k < Ncvec; ++k) { v4sf a = buff[ib][2 * k], b = buff[ib][2 * k + 1]; voutput[2 * k] = a; voutput[2 * k + 1] = b; } ib = !ib; } assert(buff[ib] == voutput); } static void zconvolve_accumulate_simd(PFFFT_Setup * s, const float *a, const float *b, const float *c, float *ab, float scaling) { const int Ncvec2 = s->Ncvec * 2; const v4sf *RESTRICT va = (const v4sf *)a; const v4sf *RESTRICT vb = (const v4sf *)b; v4sf *RESTRICT vab = (v4sf *) ab; v4sf *RESTRICT vc = (v4sf *) c; v4sf vscal = LD_PS1(scaling); float ar, ai, br, bi, cr, ci; int i; #ifdef __arm__ __builtin_prefetch(va); __builtin_prefetch(vb); __builtin_prefetch(c); __builtin_prefetch(va + 2); __builtin_prefetch(vb + 2); __builtin_prefetch(c + 2); __builtin_prefetch(va + 4); __builtin_prefetch(vb + 4); __builtin_prefetch(c + 4); __builtin_prefetch(va + 6); __builtin_prefetch(vb + 6); __builtin_prefetch(c + 6); #endif assert(VALIGNED(a) && VALIGNED(b) && VALIGNED(ab)); ar = ((v4sf_union *) va)[0].f[0]; ai = ((v4sf_union *) va)[1].f[0]; br = ((v4sf_union *) vb)[0].f[0]; bi = ((v4sf_union *) vb)[1].f[0]; cr = ((v4sf_union *) vc)[0].f[0]; ci = ((v4sf_union *) vc)[1].f[0]; for (i = 0; i < Ncvec2; i += 4) { v4sf ar, ai, br, bi; ar = va[i + 0]; ai = va[i + 1]; br = vb[i + 0]; bi = vb[i + 1]; VCPLXMUL(ar, ai, br, bi); vab[i + 0] = VMADD(ar, vscal, vc[i + 0]); vab[i + 1] = VMADD(ai, vscal, vc[i + 1]); ar = va[i + 2]; ai = va[i + 3]; br = vb[i + 2]; bi = vb[i + 3]; VCPLXMUL(ar, ai, br, bi); vab[i + 2] = VMADD(ar, vscal, vc[i + 2]); vab[i + 3] = VMADD(ai, vscal, vc[i + 3]); } if (s->transform == PFFFT_REAL) { ((v4sf_union *) vab)[0].f[0] = cr + ar * br * scaling; ((v4sf_union *) vab)[1].f[0] = ci + ai * bi * scaling; } } static void zconvolve_simd(PFFFT_Setup * s, const float *a, const float *b, float *ab, float scaling) { v4sf vscal = LD_PS1(scaling); const v4sf * RESTRICT va = (const v4sf*)a; const v4sf * RESTRICT vb = (const v4sf*)b; v4sf * RESTRICT vab = (v4sf*)ab; float sar, sai, sbr, sbi; const int Ncvec2 = s->Ncvec * 2; int i; #ifdef __arm__ __builtin_prefetch(va); __builtin_prefetch(vb); __builtin_prefetch(vab); __builtin_prefetch(va+2); __builtin_prefetch(vb+2); __builtin_prefetch(vab+2); __builtin_prefetch(va+4); __builtin_prefetch(vb+4); __builtin_prefetch(vab+4); __builtin_prefetch(va+6); __builtin_prefetch(vb+6); __builtin_prefetch(vab+6); #endif assert(VALIGNED(a) && VALIGNED(b) && VALIGNED(ab)); sar = ((v4sf_union*)va)[0].f[0]; sai = ((v4sf_union*)va)[1].f[0]; sbr = ((v4sf_union*)vb)[0].f[0]; sbi = ((v4sf_union*)vb)[1].f[0]; /* default routine, works fine for non-arm cpus with current compilers */ for (i = 0; i < Ncvec2; i += 4) { v4sf var, vai, vbr, vbi; var = va[i + 0]; vai = va[i + 1]; vbr = vb[i + 0]; vbi = vb[i + 1]; VCPLXMUL(var, vai, vbr, vbi); vab[i + 0] = VMUL(var, vscal); vab[i + 1] = VMUL(vai, vscal); var = va[i + 2]; vai = va[i + 3]; vbr = vb[i + 2]; vbi = vb[i + 3]; VCPLXMUL(var, vai, vbr, vbi); vab[i + 2] = VMUL(var, vscal); vab[i + 3] = VMUL(vai, vscal); } if (s->transform == PFFFT_REAL) { ((v4sf_union*)vab)[0].f[0] = sar * sbr * scaling; ((v4sf_union*)vab)[1].f[0] = sai * sbi * scaling; } } #else // defined(PFFFT_SIMD_DISABLE) // standard routine using scalar floats, without SIMD stuff. static void zreorder_simd(PFFFT_Setup * setup, const float *in, float *out, pffft_direction_t direction) { int k, N = setup->N; if (setup->transform == PFFFT_COMPLEX) { for (k = 0; k < 2 * N; ++k) out[k] = in[k]; return; } else if (direction == PFFFT_FORWARD) { float x_N = in[N - 1]; for (k = N - 1; k > 1; --k) out[k] = in[k - 1]; out[0] = in[0]; out[1] = x_N; } else { float x_N = in[1]; for (k = 1; k < N - 1; ++k) out[k] = in[k + 1]; out[0] = in[0]; out[N - 1] = x_N; } } static void transform_simd(PFFFT_Setup * setup, const float *input, float *output, float *scratch, pffft_direction_t direction, int ordered) { int Ncvec = setup->Ncvec; int nf_odd = (setup->ifac[1] & 1); // temporary buffer is allocated on the stack if the scratch pointer is NULL int stack_allocate = (scratch == 0 ? Ncvec * 2 : 1); VLA_ARRAY_ON_STACK(v4sf, scratch_on_stack, stack_allocate); float *buff[2]; int ib; if (scratch == 0) scratch = scratch_on_stack; buff[0] = output; buff[1] = scratch; if (setup->transform == PFFFT_COMPLEX) ordered = 0; // it is always ordered. ib = (nf_odd ^ ordered ? 1 : 0); if (direction == PFFFT_FORWARD) { if (setup->transform == PFFFT_REAL) { ib = (rfftf1_ps(Ncvec * 2, input, buff[ib], buff[!ib], setup->twiddle, &setup->ifac[0]) == buff[0] ? 0 : 1); } else { ib = (cfftf1_ps(Ncvec, input, buff[ib], buff[!ib], setup->twiddle, &setup->ifac[0], -1) == buff[0] ? 0 : 1); } if (ordered) { pffft_zreorder(setup, buff[ib], buff[!ib], PFFFT_FORWARD); ib = !ib; } } else { if (input == buff[ib]) { ib = !ib; // may happen when finput == foutput } if (ordered) { pffft_zreorder(setup, input, buff[!ib], PFFFT_BACKWARD); input = buff[!ib]; } if (setup->transform == PFFFT_REAL) { ib = (rfftb1_ps(Ncvec * 2, input, buff[ib], buff[!ib], setup->twiddle, &setup->ifac[0]) == buff[0] ? 0 : 1); } else { ib = (cfftf1_ps(Ncvec, input, buff[ib], buff[!ib], setup->twiddle, &setup->ifac[0], +1) == buff[0] ? 0 : 1); } } if (buff[ib] != output) { int k; // extra copy required -- this situation should happens only when finput == foutput assert(input == output); for (k = 0; k < Ncvec; ++k) { float a = buff[ib][2 * k], b = buff[ib][2 * k + 1]; output[2 * k] = a; output[2 * k + 1] = b; } ib = !ib; } assert(buff[ib] == output); } static void zconvolve_accumulate_simd(PFFFT_Setup * s, const float *a, const float *b, const float *c, float *ab, float scaling) { int i, Ncvec2 = s->Ncvec * 2; if (s->transform == PFFFT_REAL) { // take care of the fftpack ordering ab[0] = c[0] + a[0] * b[0] * scaling; ab[Ncvec2 - 1] = c[Ncvec2 - 1] + a[Ncvec2 - 1] * b[Ncvec2 - 1] * scaling; ++ab; ++c; ++a; ++b; Ncvec2 -= 2; } for (i = 0; i < Ncvec2; i += 2) { float ar, ai, br, bi; ar = a[i + 0]; ai = a[i + 1]; br = b[i + 0]; bi = b[i + 1]; VCPLXMUL(ar, ai, br, bi); ab[i + 0] = c[i + 0] + ar * scaling; ab[i + 1] = c[i + 1] + ai * scaling; } } static void zconvolve_simd(PFFFT_Setup * s, const float *a, const float *b, float *ab, float scaling) { int i, Ncvec2 = s->Ncvec * 2; if (s->transform == PFFFT_REAL) { // take care of the fftpack ordering ab[0] = a[0] * b[0] * scaling; ab[Ncvec2 - 1] = a[Ncvec2 - 1] * b[Ncvec2 - 1] * scaling; ++ab; ++a; ++b; Ncvec2 -= 2; } for (i = 0; i < Ncvec2; i += 2) { float ar, ai, br, bi; ar = a[i + 0]; ai = a[i + 1]; br = b[i + 0]; bi = b[i + 1]; VCPLXMUL(ar, ai, br, bi); ab[i + 0] = ar * scaling; ab[i + 1] = ai * scaling; } } #endif // defined(PFFFT_SIMD_DISABLE) static int simd_size_simd(void) { return SIMD_SZ; } struct funcs pffft_funcs = { .new_setup = new_setup_simd, .transform = transform_simd, .zreorder = zreorder_simd, .zconvolve_accumulate = zconvolve_accumulate_simd, .zconvolve = zconvolve_simd, .simd_size = simd_size_simd, .validate = validate_pffft_simd, }; #if defined(PFFFT_SIMD_DISABLE) extern struct funcs pffft_funcs_c; #if (defined(HAVE_SSE)) extern struct funcs pffft_funcs_sse; #endif #if (defined(HAVE_ALTIVEC)) extern struct funcs pffft_funcs_altivec; #endif #if (defined(HAVE_NEON)) extern struct funcs pffft_funcs_neon; #endif static struct funcs *funcs = &pffft_funcs_c; /* SSE and co like 16-bytes aligned pointers */ #define MALLOC_V4SF_ALIGNMENT 64 // with a 64-byte alignment, we are even aligned on L2 cache lines... void *pffft_aligned_malloc(size_t nb_bytes) { void *p, *p0 = malloc(nb_bytes + MALLOC_V4SF_ALIGNMENT); if (!p0) return (void *)0; p = (void *)(((size_t)p0 + MALLOC_V4SF_ALIGNMENT) & (~((size_t)(MALLOC_V4SF_ALIGNMENT - 1)))); *((void **)p - 1) = p0; return p; } void pffft_aligned_free(void *p) { if (p) free(*((void **)p - 1)); } int pffft_simd_size(void) { return funcs->simd_size(); } PFFFT_Setup *pffft_new_setup(int N, pffft_transform_t transform) { return funcs->new_setup(N, transform); } void pffft_destroy_setup(PFFFT_Setup * s) { pffft_aligned_free(s->data); free(s); } void pffft_transform(PFFFT_Setup *setup, const float *input, float *output, float *work, pffft_direction_t direction) { return funcs->transform(setup, input, output, work, direction, 0); } void pffft_transform_ordered(PFFFT_Setup *setup, const float *input, float *output, float *work, pffft_direction_t direction) { return funcs->transform(setup, input, output, work, direction, 1); } void pffft_zreorder(PFFFT_Setup *setup, const float *input, float *output, pffft_direction_t direction) { return funcs->zreorder(setup, input, output, direction); } void pffft_zconvolve_accumulate(PFFFT_Setup *setup, const float *dft_a, const float *dft_b, const float *c, float *dft_ab, float scaling) { return funcs->zconvolve_accumulate(setup, dft_a, dft_b, c, dft_ab, scaling); } void pffft_zconvolve(PFFFT_Setup *setup, const float *dft_a, const float *dft_b, float *dft_ab, float scaling) { return funcs->zconvolve(setup, dft_a, dft_b, dft_ab, scaling); } void pffft_select_cpu(int flags) { funcs = &pffft_funcs_c; #if defined(HAVE_SSE) if (flags & SPA_CPU_FLAG_SSE) funcs = &pffft_funcs_sse; #endif #if defined(HAVE_NEON) if (flags & SPA_CPU_FLAG_NEON) funcs = &pffft_funcs_neon; #endif #if defined(HAVE_ALTIVEC) if (flags & SPA_CPU_FLAG_ALTIVEC) funcs = &pffft_funcs_altivec; #endif } #endif pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/filter-graph/pffft.h000066400000000000000000000154471511204443500266170ustar00rootroot00000000000000/* Copyright (c) 2013 Julien Pommier ( pommier@modartt.com ) Based on original fortran 77 code from FFTPACKv4 from NETLIB, authored by Dr Paul Swarztrauber of NCAR, in 1985. As confirmed by the NCAR fftpack software curators, the following FFTPACKv5 license applies to FFTPACKv4 sources. My changes are released under the same terms. FFTPACK license: http://www.cisl.ucar.edu/css/software/fftpack5/ftpk.html Copyright (c) 2004 the University Corporation for Atmospheric Research ("UCAR"). All rights reserved. Developed by NCAR's Computational and Information Systems Laboratory, UCAR, www.cisl.ucar.edu. Redistribution and use of the Software in source and binary forms, with or without modification, is permitted provided that the following conditions are met: - Neither the names of NCAR's Computational and Information Systems Laboratory, the University Corporation for Atmospheric Research, nor the names of its sponsors or contributors may be used to endorse or promote products derived from this Software without specific prior written permission. - Redistributions of source code must retain the above copyright notices, this list of conditions, and the disclaimer below. - Redistributions in binary form must reproduce the above copyright notice, this list of conditions, and the disclaimer below in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE SOFTWARE. */ /* PFFFT : a Pretty Fast FFT. This is basically an adaptation of the single precision fftpack (v4) as found on netlib taking advantage of SIMD instruction found on cpus such as intel x86 (SSE1), powerpc (Altivec), and arm (NEON). For architectures where no SIMD instruction is available, the code falls back to a scalar version. Restrictions: - 1D transforms only, with 32-bit single precision. - supports only transforms for inputs of length N of the form N=(2^a)*(3^b)*(5^c), a >= 5, b >=0, c >= 0 (32, 48, 64, 96, 128, 144, 160, etc are all acceptable lengths). Performance is best for 128<=N<=8192. - all (float*) pointers in the functions below are expected to have an "simd-compatible" alignment, that is 16 bytes on x86 and powerpc CPUs. You can allocate such buffers with the functions pffft_aligned_malloc / pffft_aligned_free (or with stuff like posix_memalign..) */ #ifndef PFFFT_H #define PFFFT_H #include // for size_t #ifdef __cplusplus extern "C" { #endif /* opaque struct holding internal stuff (precomputed twiddle factors) this struct can be shared by many threads as it contains only read-only data. */ typedef struct PFFFT_Setup PFFFT_Setup; /* direction of the transform */ typedef enum { PFFFT_FORWARD, PFFFT_BACKWARD } pffft_direction_t; /* type of transform */ typedef enum { PFFFT_REAL, PFFFT_COMPLEX } pffft_transform_t; /* prepare for performing transforms of size N -- the returned PFFFT_Setup structure is read-only so it can safely be shared by multiple concurrent threads. */ PFFFT_Setup *pffft_new_setup(int N, pffft_transform_t transform); void pffft_destroy_setup(PFFFT_Setup *); /* Perform a Fourier transform , The z-domain data is stored in the most efficient order for transforming it back, or using it for convolution. If you need to have its content sorted in the "usual" way, that is as an array of interleaved complex numbers, either use pffft_transform_ordered , or call pffft_zreorder after the forward fft, and before the backward fft. Transforms are not scaled: PFFFT_BACKWARD(PFFFT_FORWARD(x)) = N*x. Typically you will want to scale the backward transform by 1/N. The 'work' pointer should point to an area of N (2*N for complex fft) floats, properly aligned. If 'work' is NULL, then stack will be used instead (this is probably the best strategy for small FFTs, say for N < 16384). input and output may alias. */ void pffft_transform(PFFFT_Setup *setup, const float *input, float *output, float *work, pffft_direction_t direction); /* Similar to pffft_transform, but makes sure that the output is ordered as expected (interleaved complex numbers). This is similar to calling pffft_transform and then pffft_zreorder. input and output may alias. */ void pffft_transform_ordered(PFFFT_Setup *setup, const float *input, float *output, float *work, pffft_direction_t direction); /* call pffft_zreorder(.., PFFFT_FORWARD) after pffft_transform(..., PFFFT_FORWARD) if you want to have the frequency components in the correct "canonical" order, as interleaved complex numbers. (for real transforms, both 0-frequency and half frequency components, which are real, are assembled in the first entry as F(0)+i*F(n/2+1). Note that the original fftpack did place F(n/2+1) at the end of the arrays). input and output should not alias. */ void pffft_zreorder(PFFFT_Setup *setup, const float *input, float *output, pffft_direction_t direction); /* Perform a multiplication of the frequency components of dft_a and dft_b and accumulate them into dft_ab. The arrays should have been obtained with pffft_transform(.., PFFFT_FORWARD) and should *not* have been reordered with pffft_zreorder (otherwise just perform the operation yourself as the dft coefs are stored as interleaved complex numbers). the operation performed is: dft_ab = dft_c + (dft_a * fdt_b)*scaling The dft_a, dft_b and dft_ab pointers may alias. */ void pffft_zconvolve_accumulate(PFFFT_Setup *setup, const float *dft_a, const float *dft_b, const float *dft_c, float *dft_ab, float scaling); void pffft_zconvolve(PFFFT_Setup *setup, const float *dft_a, const float *dft_b, float *dft_ab, float scaling); /* the float buffers must have the correct alignment (16-byte boundary on intel and powerpc). This function may be used to obtain such correctly aligned buffers. */ void *pffft_aligned_malloc(size_t nb_bytes); void pffft_aligned_free(void *); /* return 4 or 1 depending on whether support for SSE/Altivec instructions was enabled when building pffft.c */ int pffft_simd_size(void); void pffft_select_cpu(int flags); #ifdef __cplusplus } #endif #endif // PFFFT_H pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/filter-graph/plugin_builtin.c000066400000000000000000002340031511204443500305200ustar00rootroot00000000000000/* PipeWire */ /* SPDX-FileCopyrightText: Copyright © 2021 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include "config.h" #include #include #ifdef HAVE_SNDFILE #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include "audio-plugin.h" #include "biquad.h" #include "convolver.h" #include "audio-dsp.h" #define MAX_RATES 32u struct plugin { struct spa_handle handle; struct spa_fga_plugin plugin; struct spa_fga_dsp *dsp; struct spa_log *log; }; struct builtin { struct plugin *plugin; struct spa_fga_dsp *dsp; struct spa_log *log; unsigned long rate; float *port[64]; int type; struct biquad bq; float freq; float Q; float gain; float b0, b1, b2; float a0, a1, a2; float accum; int mode; uint32_t count; float last; float gate; float hold; }; static void *builtin_instantiate(const struct spa_fga_plugin *plugin, const struct spa_fga_descriptor * Descriptor, unsigned long SampleRate, int index, const char *config) { struct plugin *pl = SPA_CONTAINER_OF(plugin, struct plugin, plugin); struct builtin *impl; impl = calloc(1, sizeof(*impl)); if (impl == NULL) return NULL; impl->plugin = pl; impl->rate = SampleRate; impl->dsp = impl->plugin->dsp; impl->log = impl->plugin->log; return impl; } static void builtin_connect_port(void *Instance, unsigned long Port, void * DataLocation) { struct builtin *impl = Instance; impl->port[Port] = DataLocation; } static void builtin_cleanup(void * Instance) { struct builtin *impl = Instance; free(impl); } /** copy */ static void copy_run(void * Instance, unsigned long SampleCount) { struct builtin *impl = Instance; float *in = impl->port[1], *out = impl->port[0]; spa_fga_dsp_copy(impl->dsp, out, in, SampleCount); } static struct spa_fga_port copy_ports[] = { { .index = 0, .name = "Out", .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_AUDIO, }, { .index = 1, .name = "In", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, } }; static const struct spa_fga_descriptor copy_desc = { .name = "copy", .flags = SPA_FGA_DESCRIPTOR_COPY, .n_ports = 2, .ports = copy_ports, .instantiate = builtin_instantiate, .connect_port = builtin_connect_port, .run = copy_run, .cleanup = builtin_cleanup, }; /** mixer */ static void mixer_run(void * Instance, unsigned long SampleCount) { struct builtin *impl = Instance; int i, n_src = 0; float *out = impl->port[0]; const float *src[8]; float gains[8]; bool eq_gain = true; if (out == NULL) return; for (i = 0; i < 8; i++) { float *in = impl->port[1+i]; float gain = impl->port[9+i][0]; if (in == NULL || gain == 0.0f) continue; src[n_src] = in; gains[n_src++] = gain; if (gain != gains[0]) eq_gain = false; } if (eq_gain) spa_fga_dsp_mix_gain(impl->dsp, out, src, n_src, gains, 1, SampleCount); else spa_fga_dsp_mix_gain(impl->dsp, out, src, n_src, gains, n_src, SampleCount); } static struct spa_fga_port mixer_ports[] = { { .index = 0, .name = "Out", .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_AUDIO, }, { .index = 1, .name = "In 1", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, }, { .index = 2, .name = "In 2", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, }, { .index = 3, .name = "In 3", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, }, { .index = 4, .name = "In 4", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, }, { .index = 5, .name = "In 5", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, }, { .index = 6, .name = "In 6", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, }, { .index = 7, .name = "In 7", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, }, { .index = 8, .name = "In 8", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, }, { .index = 9, .name = "Gain 1", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, .def = 1.0f, .min = 0.0f, .max = 10.0f }, { .index = 10, .name = "Gain 2", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, .def = 1.0f, .min = 0.0f, .max = 10.0f }, { .index = 11, .name = "Gain 3", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, .def = 1.0f, .min = 0.0f, .max = 10.0f }, { .index = 12, .name = "Gain 4", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, .def = 1.0f, .min = 0.0f, .max = 10.0f }, { .index = 13, .name = "Gain 5", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, .def = 1.0f, .min = 0.0f, .max = 10.0f }, { .index = 14, .name = "Gain 6", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, .def = 1.0f, .min = 0.0f, .max = 10.0f }, { .index = 15, .name = "Gain 7", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, .def = 1.0f, .min = 0.0f, .max = 10.0f }, { .index = 16, .name = "Gain 8", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, .def = 1.0f, .min = 0.0f, .max = 10.0f }, }; static const struct spa_fga_descriptor mixer_desc = { .name = "mixer", .flags = SPA_FGA_DESCRIPTOR_SUPPORTS_NULL_DATA, .n_ports = 17, .ports = mixer_ports, .instantiate = builtin_instantiate, .connect_port = builtin_connect_port, .run = mixer_run, .cleanup = builtin_cleanup, }; /** biquads */ static int bq_type_from_name(const char *name) { if (spa_streq(name, "bq_lowpass")) return BQ_LOWPASS; if (spa_streq(name, "bq_highpass")) return BQ_HIGHPASS; if (spa_streq(name, "bq_bandpass")) return BQ_BANDPASS; if (spa_streq(name, "bq_lowshelf")) return BQ_LOWSHELF; if (spa_streq(name, "bq_highshelf")) return BQ_HIGHSHELF; if (spa_streq(name, "bq_peaking")) return BQ_PEAKING; if (spa_streq(name, "bq_notch")) return BQ_NOTCH; if (spa_streq(name, "bq_allpass")) return BQ_ALLPASS; if (spa_streq(name, "bq_raw")) return BQ_NONE; return BQ_NONE; } static const char *bq_name_from_type(int type) { switch (type) { case BQ_LOWPASS: return "lowpass"; case BQ_HIGHPASS: return "highpass"; case BQ_BANDPASS: return "bandpass"; case BQ_LOWSHELF: return "lowshelf"; case BQ_HIGHSHELF: return "highshelf"; case BQ_PEAKING: return "peaking"; case BQ_NOTCH: return "notch"; case BQ_ALLPASS: return "allpass"; case BQ_NONE: return "raw"; } return "unknown"; } static void bq_raw_update(struct builtin *impl, float b0, float b1, float b2, float a0, float a1, float a2) { struct biquad *bq = &impl->bq; impl->b0 = b0; impl->b1 = b1; impl->b2 = b2; impl->a0 = a0; impl->a1 = a1; impl->a2 = a2; if (a0 != 0.0f) a0 = 1.0f / a0; bq->b0 = impl->b0 * a0; bq->b1 = impl->b1 * a0; bq->b2 = impl->b2 * a0; bq->a1 = impl->a1 * a0; bq->a2 = impl->a2 * a0; bq->x1 = bq->x2 = 0.0f; bq->type = BQ_RAW; } /* * config = { * coefficients = [ * { rate = 44100, b0=.., b1=.., b2=.., a0=.., a1=.., a2=.. }, * { rate = 48000, b0=.., b1=.., b2=.., a0=.., a1=.., a2=.. }, * { rate = 192000, b0=.., b1=.., b2=.., a0=.., a1=.., a2=.. } * ] * } */ static void *bq_instantiate(const struct spa_fga_plugin *plugin, const struct spa_fga_descriptor * Descriptor, unsigned long SampleRate, int index, const char *config) { struct plugin *pl = SPA_CONTAINER_OF(plugin, struct plugin, plugin); struct builtin *impl; struct spa_json it[3]; const char *val; char key[256]; uint32_t best_rate = 0; int len; impl = calloc(1, sizeof(*impl)); if (impl == NULL) return NULL; impl->plugin = pl; impl->log = impl->plugin->log; impl->dsp = impl->plugin->dsp; impl->rate = SampleRate; impl->b0 = impl->a0 = 1.0f; impl->type = bq_type_from_name(Descriptor->name); if (impl->type != BQ_NONE) return impl; if (config == NULL) { spa_log_error(impl->log, "biquads:bq_raw requires a config section"); goto error; } if (spa_json_begin_object(&it[0], config, strlen(config)) <= 0) { spa_log_error(impl->log, "biquads:config section must be an object"); goto error; } while ((len = spa_json_object_next(&it[0], key, sizeof(key), &val)) > 0) { if (spa_streq(key, "coefficients")) { if (!spa_json_is_array(val, len)) { spa_log_error(impl->log, "biquads:coefficients require an array"); goto error; } spa_json_enter(&it[0], &it[1]); while (spa_json_enter_object(&it[1], &it[2]) > 0) { int32_t rate = 0; float b0 = 1.0f, b1 = 0.0f, b2 = 0.0f; float a0 = 1.0f, a1 = 0.0f, a2 = 0.0f; while ((len = spa_json_object_next(&it[2], key, sizeof(key), &val)) > 0) { if (spa_streq(key, "rate")) { if (spa_json_parse_int(val, len, &rate) <= 0) { spa_log_error(impl->log, "biquads:rate requires a number"); goto error; } } else if (spa_streq(key, "b0")) { if (spa_json_parse_float(val, len, &b0) <= 0) { spa_log_error(impl->log, "biquads:b0 requires a float"); goto error; } } else if (spa_streq(key, "b1")) { if (spa_json_parse_float(val, len, &b1) <= 0) { spa_log_error(impl->log, "biquads:b1 requires a float"); goto error; } } else if (spa_streq(key, "b2")) { if (spa_json_parse_float(val, len, &b2) <= 0) { spa_log_error(impl->log, "biquads:b2 requires a float"); goto error; } } else if (spa_streq(key, "a0")) { if (spa_json_parse_float(val, len, &a0) <= 0) { spa_log_error(impl->log, "biquads:a0 requires a float"); goto error; } } else if (spa_streq(key, "a1")) { if (spa_json_parse_float(val, len, &a1) <= 0) { spa_log_error(impl->log, "biquads:a1 requires a float"); goto error; } } else if (spa_streq(key, "a2")) { if (spa_json_parse_float(val, len, &a2) <= 0) { spa_log_error(impl->log, "biquads:a0 requires a float"); goto error; } } else { spa_log_warn(impl->log, "biquads: ignoring coefficients key: '%s'", key); } } if (labs((long)rate - (long)SampleRate) < labs((long)best_rate - (long)SampleRate)) { best_rate = rate; bq_raw_update(impl, b0, b1, b2, a0, a1, a2); } } } else { spa_log_warn(impl->log, "biquads: ignoring config key: '%s'", key); } } return impl; error: free(impl); errno = EINVAL; return NULL; } #define BQ_NUM_PORTS 11 static struct spa_fga_port bq_ports[] = { { .index = 0, .name = "Out", .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_AUDIO, }, { .index = 1, .name = "In", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, }, { .index = 2, .name = "Freq", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, .hint = SPA_FGA_HINT_SAMPLE_RATE, .def = 0.0f, .min = 0.0f, .max = 1.0f, }, { .index = 3, .name = "Q", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, .def = 0.0f, .min = 0.0f, .max = 10.0f, }, { .index = 4, .name = "Gain", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, .def = 0.0f, .min = -120.0f, .max = 20.0f, }, { .index = 5, .name = "b0", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, .def = 1.0f, .min = -10.0f, .max = 10.0f, }, { .index = 6, .name = "b1", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, .def = 0.0f, .min = -10.0f, .max = 10.0f, }, { .index = 7, .name = "b2", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, .def = 0.0f, .min = -10.0f, .max = 10.0f, }, { .index = 8, .name = "a0", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, .def = 1.0f, .min = -10.0f, .max = 10.0f, }, { .index = 9, .name = "a1", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, .def = 0.0f, .min = -10.0f, .max = 10.0f, }, { .index = 10, .name = "a2", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, .def = 0.0f, .min = -10.0f, .max = 10.0f, }, }; static void bq_freq_update(struct builtin *impl, int type, float freq, float Q, float gain) { struct biquad *bq = &impl->bq; impl->freq = freq; impl->Q = Q; impl->gain = gain; biquad_set(bq, type, freq * 2 / impl->rate, Q, gain); impl->port[5][0] = impl->b0 = bq->b0; impl->port[6][0] = impl->b1 = bq->b1; impl->port[7][0] = impl->b2 = bq->b2; impl->port[8][0] = impl->a0 = 1.0f; impl->port[9][0] = impl->a1 = bq->a1; impl->port[10][0] = impl->a2 = bq->a2; } static void bq_activate(void * Instance) { struct builtin *impl = Instance; if (impl->type == BQ_NONE) { impl->port[5][0] = impl->b0; impl->port[6][0] = impl->b1; impl->port[7][0] = impl->b2; impl->port[8][0] = impl->a0; impl->port[9][0] = impl->a1; impl->port[10][0] = impl->a2; } else { float freq = impl->port[2][0]; float Q = impl->port[3][0]; float gain = impl->port[4][0]; bq_freq_update(impl, impl->type, freq, Q, gain); } } static void bq_run(void *Instance, unsigned long samples) { struct builtin *impl = Instance; struct biquad *bq = &impl->bq; float *out = impl->port[0]; float *in = impl->port[1]; if (impl->type == BQ_NONE) { float b0, b1, b2, a0, a1, a2; b0 = impl->port[5][0]; b1 = impl->port[6][0]; b2 = impl->port[7][0]; a0 = impl->port[8][0]; a1 = impl->port[9][0]; a2 = impl->port[10][0]; if (impl->b0 != b0 || impl->b1 != b1 || impl->b2 != b2 || impl->a0 != a0 || impl->a1 != a1 || impl->a2 != a2) { bq_raw_update(impl, b0, b1, b2, a0, a1, a2); } } else { float freq = impl->port[2][0]; float Q = impl->port[3][0]; float gain = impl->port[4][0]; if (impl->freq != freq || impl->Q != Q || impl->gain != gain) bq_freq_update(impl, impl->type, freq, Q, gain); } spa_fga_dsp_biquad_run(impl->dsp, bq, 1, 0, &out, (const float **)&in, 1, samples); } /** bq_lowpass */ static const struct spa_fga_descriptor bq_lowpass_desc = { .name = "bq_lowpass", .n_ports = BQ_NUM_PORTS, .ports = bq_ports, .instantiate = bq_instantiate, .connect_port = builtin_connect_port, .activate = bq_activate, .run = bq_run, .cleanup = builtin_cleanup, }; /** bq_highpass */ static const struct spa_fga_descriptor bq_highpass_desc = { .name = "bq_highpass", .n_ports = BQ_NUM_PORTS, .ports = bq_ports, .instantiate = bq_instantiate, .connect_port = builtin_connect_port, .activate = bq_activate, .run = bq_run, .cleanup = builtin_cleanup, }; /** bq_bandpass */ static const struct spa_fga_descriptor bq_bandpass_desc = { .name = "bq_bandpass", .n_ports = BQ_NUM_PORTS, .ports = bq_ports, .instantiate = bq_instantiate, .connect_port = builtin_connect_port, .activate = bq_activate, .run = bq_run, .cleanup = builtin_cleanup, }; /** bq_lowshelf */ static const struct spa_fga_descriptor bq_lowshelf_desc = { .name = "bq_lowshelf", .n_ports = BQ_NUM_PORTS, .ports = bq_ports, .instantiate = bq_instantiate, .connect_port = builtin_connect_port, .activate = bq_activate, .run = bq_run, .cleanup = builtin_cleanup, }; /** bq_highshelf */ static const struct spa_fga_descriptor bq_highshelf_desc = { .name = "bq_highshelf", .n_ports = BQ_NUM_PORTS, .ports = bq_ports, .instantiate = bq_instantiate, .connect_port = builtin_connect_port, .activate = bq_activate, .run = bq_run, .cleanup = builtin_cleanup, }; /** bq_peaking */ static const struct spa_fga_descriptor bq_peaking_desc = { .name = "bq_peaking", .n_ports = BQ_NUM_PORTS, .ports = bq_ports, .instantiate = bq_instantiate, .connect_port = builtin_connect_port, .activate = bq_activate, .run = bq_run, .cleanup = builtin_cleanup, }; /** bq_notch */ static const struct spa_fga_descriptor bq_notch_desc = { .name = "bq_notch", .n_ports = BQ_NUM_PORTS, .ports = bq_ports, .instantiate = bq_instantiate, .connect_port = builtin_connect_port, .activate = bq_activate, .run = bq_run, .cleanup = builtin_cleanup, }; /** bq_allpass */ static const struct spa_fga_descriptor bq_allpass_desc = { .name = "bq_allpass", .n_ports = BQ_NUM_PORTS, .ports = bq_ports, .instantiate = bq_instantiate, .connect_port = builtin_connect_port, .activate = bq_activate, .run = bq_run, .cleanup = builtin_cleanup, }; /* bq_raw */ static const struct spa_fga_descriptor bq_raw_desc = { .name = "bq_raw", .n_ports = BQ_NUM_PORTS, .ports = bq_ports, .instantiate = bq_instantiate, .connect_port = builtin_connect_port, .activate = bq_activate, .run = bq_run, .cleanup = builtin_cleanup, }; /** convolve */ struct convolver_impl { struct plugin *plugin; struct spa_log *log; struct spa_fga_dsp *dsp; unsigned long rate; float *port[3]; float latency; struct convolver *conv; }; struct finfo { #define TYPE_INVALID 0 #define TYPE_SNDFILE 1 #define TYPE_HILBERT 2 #define TYPE_DIRAC 3 #define TYPE_IR 4 uint32_t type; const char *filename; #ifdef HAVE_SNDFILE SF_INFO info; SNDFILE *fs; #endif int channels; int def_frames; int max_frames; float latency; /* latency relative to number of samples */ uint32_t rate; const char *error; }; static int finfo_open(const char *filename, struct finfo *info, int rate) { info->filename = filename; if (spa_strstartswith(filename, "/hilbert")) { info->channels = 1; info->rate = rate; info->def_frames = 64; info->max_frames = INT_MAX; info->type = TYPE_HILBERT; info->latency = 0.5f; } else if (spa_strstartswith(filename, "/dirac")) { info->channels = 1; info->def_frames = 1; info->max_frames = 1; info->rate = rate; info->type = TYPE_DIRAC; info->latency = 0.0f; } else if (spa_strstartswith(filename, "/ir:")) { struct spa_json it[1]; float v; int rate; info->channels = 1; info->type = TYPE_IR; info->def_frames = 0; if (spa_json_begin_array_relax(&it[0], filename+4, strlen(filename+4)) <= 0) return -EINVAL; if (spa_json_get_int(&it[0], &rate) <= 0) return -EINVAL; info->rate = rate; while (spa_json_get_float(&it[0], &v) > 0) info->def_frames++; info->max_frames = info->def_frames; info->latency = 0.0f; } else { #ifdef HAVE_SNDFILE info->fs = sf_open(filename, SFM_READ, &info->info); if (info->fs == NULL) { info->error = sf_strerror(NULL); return -ENOENT; } info->channels = info->info.channels; info->def_frames = info->info.frames; info->max_frames = info->def_frames; info->rate = info->info.samplerate; info->type = TYPE_SNDFILE; info->latency = 0.0f; #else info->error = "compiled without sndfile support, can't load samples"; return -ENOTSUP; #endif } return 0; } static float *finfo_read_samples(struct plugin *pl, struct finfo *info, float gain, int delay, int offset, int length, int channel, long unsigned *rate, int *n_samples, int *latency) { float *samples, v; int i, n, h; if (length <= 0) length = info->def_frames; else length = SPA_MIN(length, info->max_frames); length -= SPA_MIN(offset, length); n = delay + length; if (n == 0) return NULL; samples = calloc(n * info->channels, sizeof(float)); if (samples == NULL) return NULL; channel = channel % info->channels; switch (info->type) { case TYPE_SNDFILE: #ifdef HAVE_SNDFILE if (offset > 0) sf_seek(info->fs, offset, SEEK_SET); sf_readf_float(info->fs, samples + (delay * info->channels), length); for (i = 0; i < n; i++) samples[i] = samples[info->channels * i + channel] * gain; #endif break; case TYPE_HILBERT: gain *= 2 / (float)M_PI; h = length / 2; for (i = 1; i < h; i += 2) { v = (gain / i) * (0.43f + 0.57f * cosf(i * (float)M_PI / h)); samples[delay + h + i] = -v; samples[delay + h - i] = v; } spa_log_info(pl->log, "created hilbert function length %d", length); break; case TYPE_DIRAC: samples[delay] = gain; spa_log_info(pl->log, "created dirac function"); break; case TYPE_IR: { struct spa_json it[1]; float v; if (spa_json_begin_array_relax(&it[0], info->filename+4, strlen(info->filename+4)) <= 0) return NULL; if (spa_json_get_int(&it[0], &h) <= 0) return NULL; info->rate = h; i = 0; while (spa_json_get_float(&it[0], &v) > 0) { samples[delay + i] = v * gain; i++; } break; } } *n_samples = n; *rate = info->rate; *latency = (int) (n * info->latency); return samples; } static void finfo_close(struct finfo *info) { #ifdef HAVE_SNDFILE if (info->type == TYPE_SNDFILE && info->fs != NULL) sf_close(info->fs); #endif } static float *read_closest(struct plugin *pl, char **filenames, float gain, float delay_sec, int offset, int length, int channel, long unsigned *rate, int *n_samples, int *latency) { struct finfo finfo[MAX_RATES]; int res, diff = INT_MAX; uint32_t best = SPA_ID_INVALID, i; float *samples = NULL; spa_zero(finfo); for (i = 0; i < MAX_RATES && filenames[i] && filenames[i][0]; i++) { res = finfo_open(filenames[i], &finfo[i], *rate); if (res < 0) continue; if (labs((long)finfo[i].rate - (long)*rate) < diff) { best = i; diff = labs((long)finfo[i].rate - (long)*rate); spa_log_debug(pl->log, "new closest match: %d", finfo[i].rate); } } if (best != SPA_ID_INVALID) { spa_log_info(pl->log, "loading best rate:%u %s", finfo[best].rate, filenames[best]); samples = finfo_read_samples(pl, &finfo[best], gain, (int) (delay_sec * finfo[best].rate), offset, length, channel, rate, n_samples, latency); } else { char buf[PATH_MAX]; spa_log_error(pl->log, "Can't open any sample file (CWD %s):", getcwd(buf, sizeof(buf))); for (i = 0; i < MAX_RATES && filenames[i] && filenames[i][0]; i++) { res = finfo_open(filenames[i], &finfo[i], *rate); if (res < 0) spa_log_error(pl->log, " failed file %s: %s", filenames[i], finfo[i].error); else spa_log_warn(pl->log, " unexpectedly opened file %s", filenames[i]); } } for (i = 0; i < MAX_RATES; i++) finfo_close(&finfo[i]); return samples; } static float *resample_buffer(struct plugin *pl, float *samples, int *n_samples, unsigned long in_rate, unsigned long out_rate, uint32_t quality) { #ifdef HAVE_SPA_PLUGINS uint32_t in_len, out_len, total_out = 0; int out_n_samples; float *out_samples, *out_buf, *in_buf; struct resample r; int res; spa_zero(r); r.channels = 1; r.i_rate = in_rate; r.o_rate = out_rate; r.cpu_flags = pl->dsp->cpu_flags; r.quality = quality; if ((res = resample_native_init(&r)) < 0) { spa_log_error(pl->log, "resampling failed: %s", spa_strerror(res)); errno = -res; return NULL; } out_n_samples = SPA_ROUND_UP(*n_samples * out_rate, in_rate) / in_rate; out_samples = calloc(out_n_samples, sizeof(float)); if (out_samples == NULL) goto error; in_len = *n_samples; in_buf = samples; out_len = out_n_samples; out_buf = out_samples; spa_log_info(pl->log, "Resampling filter: rate: %lu => %lu, n_samples: %u => %u, q:%u", in_rate, out_rate, in_len, out_len, quality); resample_process(&r, (void*)&in_buf, &in_len, (void*)&out_buf, &out_len); spa_log_debug(pl->log, "resampled: %u -> %u samples", in_len, out_len); total_out += out_len; in_len = resample_delay(&r); in_buf = calloc(in_len, sizeof(float)); if (in_buf == NULL) goto error; out_buf = out_samples + total_out; out_len = out_n_samples - total_out; spa_log_debug(pl->log, "flushing resampler: %u in %u out", in_len, out_len); resample_process(&r, (void*)&in_buf, &in_len, (void*)&out_buf, &out_len); spa_log_debug(pl->log, "flushed: %u -> %u samples", in_len, out_len); total_out += out_len; free(in_buf); free(samples); resample_free(&r); *n_samples = total_out; float gain = (float)in_rate / (float)out_rate; for (uint32_t i = 0; i < total_out; i++) out_samples[i] = out_samples[i] * gain; return out_samples; error: resample_free(&r); free(samples); free(out_samples); return NULL; #else spa_log_error(impl->log, "compiled without spa-plugins support, can't resample"); float *out_samples = calloc(*n_samples, sizeof(float)); spa_memcpy(out_samples, samples, *n_samples * sizeof(float)); return out_samples; #endif } static void * convolver_instantiate(const struct spa_fga_plugin *plugin, const struct spa_fga_descriptor * Descriptor, unsigned long SampleRate, int index, const char *config) { struct plugin *pl = SPA_CONTAINER_OF(plugin, struct plugin, plugin); struct convolver_impl *impl; float *samples; int offset = 0, length = 0, channel = index, n_samples = 0, len; uint32_t i = 0; struct spa_json it[2]; const char *val; char key[256]; char *filenames[MAX_RATES] = { 0 }; int blocksize = 0, tailsize = 0; int resample_quality = RESAMPLE_DEFAULT_QUALITY, def_latency; float gain = 1.0f, delay = 0.0f, latency = -1.0f; unsigned long rate; errno = EINVAL; if (config == NULL) { spa_log_error(pl->log, "convolver: requires a config section"); return NULL; } if (spa_json_begin_object(&it[0], config, strlen(config)) <= 0) { spa_log_error(pl->log, "convolver:config must be an object"); return NULL; } while ((len = spa_json_object_next(&it[0], key, sizeof(key), &val)) > 0) { if (spa_streq(key, "blocksize")) { if (spa_json_parse_int(val, len, &blocksize) <= 0) { spa_log_error(pl->log, "convolver:blocksize requires a number"); return NULL; } } else if (spa_streq(key, "tailsize")) { if (spa_json_parse_int(val, len, &tailsize) <= 0) { spa_log_error(pl->log, "convolver:tailsize requires a number"); return NULL; } } else if (spa_streq(key, "gain")) { if (spa_json_parse_float(val, len, &gain) <= 0) { spa_log_error(pl->log, "convolver:gain requires a number"); return NULL; } } else if (spa_streq(key, "delay")) { int delay_i; if (spa_json_parse_int(val, len, &delay_i) > 0) { delay = delay_i / (float)SampleRate; } else if (spa_json_parse_float(val, len, &delay) <= 0) { spa_log_error(pl->log, "convolver:delay requires a number"); return NULL; } } else if (spa_streq(key, "filename")) { if (spa_json_is_array(val, len)) { spa_json_enter(&it[0], &it[1]); while ((len = spa_json_next(&it[1], &val)) > 0 && i < SPA_N_ELEMENTS(filenames)) { filenames[i] = malloc(len+1); if (filenames[i] == NULL) return NULL; spa_json_parse_stringn(val, len, filenames[i], len+1); i++; } } else { filenames[0] = malloc(len+1); if (filenames[0] == NULL) return NULL; spa_json_parse_stringn(val, len, filenames[0], len+1); } } else if (spa_streq(key, "offset")) { if (spa_json_parse_int(val, len, &offset) <= 0) { spa_log_error(pl->log, "convolver:offset requires a number"); return NULL; } } else if (spa_streq(key, "length")) { if (spa_json_parse_int(val, len, &length) <= 0) { spa_log_error(pl->log, "convolver:length requires a number"); return NULL; } } else if (spa_streq(key, "channel")) { if (spa_json_parse_int(val, len, &channel) <= 0) { spa_log_error(pl->log, "convolver:channel requires a number"); return NULL; } } else if (spa_streq(key, "resample_quality")) { if (spa_json_parse_int(val, len, &resample_quality) <= 0) { spa_log_error(pl->log, "convolver:resample_quality requires a number"); return NULL; } } else if (spa_streq(key, "latency")) { if (spa_json_parse_float(val, len, &latency) <= 0) { spa_log_error(pl->log, "convolver:latency requires a number"); return NULL; } } else { spa_log_warn(pl->log, "convolver: ignoring config key: '%s'", key); } } if (filenames[0] == NULL) { spa_log_error(pl->log, "convolver:filename was not given"); return NULL; } if (delay < 0.0f) delay = 0.0f; if (offset < 0) offset = 0; rate = SampleRate; samples = read_closest(pl, filenames, gain, delay, offset, length, channel, &rate, &n_samples, &def_latency); if (samples != NULL && rate != SampleRate) samples = resample_buffer(pl, samples, &n_samples, rate, SampleRate, resample_quality); for (i = 0; i < MAX_RATES; i++) if (filenames[i]) free(filenames[i]); if (samples == NULL) { errno = ENOENT; return NULL; } if (blocksize <= 0) blocksize = SPA_CLAMP(n_samples, 64, 256); if (tailsize <= 0) tailsize = SPA_CLAMP(4096, blocksize, 32768); spa_log_info(pl->log, "using n_samples:%u %d:%d blocksize delay:%f def-latency:%d", n_samples, blocksize, tailsize, delay, def_latency); impl = calloc(1, sizeof(*impl)); if (impl == NULL) goto error; impl->plugin = pl; impl->log = pl->log; impl->dsp = pl->dsp; impl->rate = SampleRate; impl->conv = convolver_new(impl->dsp, blocksize, tailsize, samples, n_samples); if (impl->conv == NULL) goto error; if (latency < 0.0f) impl->latency = def_latency; else impl->latency = latency * impl->rate; free(samples); return impl; error: free(samples); free(impl); return NULL; } static void convolver_connect_port(void * Instance, unsigned long Port, void * DataLocation) { struct convolver_impl *impl = Instance; impl->port[Port] = DataLocation; } static void convolver_cleanup(void * Instance) { struct convolver_impl *impl = Instance; if (impl->conv) convolver_free(impl->conv); free(impl); } static struct spa_fga_port convolve_ports[] = { { .index = 0, .name = "Out", .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_AUDIO, }, { .index = 1, .name = "In", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, }, { .index = 2, .name = "latency", .hint = SPA_FGA_HINT_LATENCY, .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_CONTROL, }, }; static void convolver_activate(void * Instance) { struct convolver_impl *impl = Instance; if (impl->port[2] != NULL) impl->port[2][0] = impl->latency; } static void convolver_deactivate(void * Instance) { struct convolver_impl *impl = Instance; convolver_reset(impl->conv); } static void convolve_run(void * Instance, unsigned long SampleCount) { struct convolver_impl *impl = Instance; if (impl->port[1] != NULL && impl->port[0] != NULL) convolver_run(impl->conv, impl->port[1], impl->port[0], SampleCount); if (impl->port[2] != NULL) impl->port[2][0] = impl->latency; } static const struct spa_fga_descriptor convolve_desc = { .name = "convolver", .flags = SPA_FGA_DESCRIPTOR_SUPPORTS_NULL_DATA, .n_ports = SPA_N_ELEMENTS(convolve_ports), .ports = convolve_ports, .instantiate = convolver_instantiate, .connect_port = convolver_connect_port, .activate = convolver_activate, .deactivate = convolver_deactivate, .run = convolve_run, .cleanup = convolver_cleanup, }; /** delay */ struct delay_impl { struct plugin *plugin; struct spa_fga_dsp *dsp; struct spa_log *log; unsigned long rate; float *port[4]; float delay; uint32_t delay_samples; uint32_t buffer_samples; float *buffer; uint32_t ptr; float latency; }; static void delay_cleanup(void * Instance) { struct delay_impl *impl = Instance; free(impl->buffer); free(impl); } static void *delay_instantiate(const struct spa_fga_plugin *plugin, const struct spa_fga_descriptor * Descriptor, unsigned long SampleRate, int index, const char *config) { struct plugin *pl = SPA_CONTAINER_OF(plugin, struct plugin, plugin); struct delay_impl *impl; struct spa_json it[1]; const char *val; char key[256]; float max_delay = 1.0f, latency = 0.0f; int len; if (config == NULL) { spa_log_error(pl->log, "delay: requires a config section"); errno = EINVAL; return NULL; } if (spa_json_begin_object(&it[0], config, strlen(config)) <= 0) { spa_log_error(pl->log, "delay:config must be an object"); return NULL; } while ((len = spa_json_object_next(&it[0], key, sizeof(key), &val)) > 0) { if (spa_streq(key, "max-delay")) { if (spa_json_parse_float(val, len, &max_delay) <= 0) { spa_log_error(pl->log, "delay:max-delay requires a number"); return NULL; } } else if (spa_streq(key, "latency")) { if (spa_json_parse_float(val, len, &latency) <= 0) { spa_log_error(pl->log, "delay:latency requires a number"); return NULL; } } else { spa_log_warn(pl->log, "delay: ignoring config key: '%s'", key); } } if (max_delay <= 0.0f) max_delay = 1.0f; if (latency <= 0.0f) latency = 0.0f; impl = calloc(1, sizeof(*impl)); if (impl == NULL) return NULL; impl->plugin = pl; impl->dsp = pl->dsp; impl->log = pl->log; impl->rate = SampleRate; impl->buffer_samples = SPA_ROUND_UP_N((uint32_t)(max_delay * impl->rate), 64); impl->latency = latency * impl->rate; spa_log_info(impl->log, "max-delay:%f seconds rate:%lu samples:%d latency:%f", max_delay, impl->rate, impl->buffer_samples, impl->latency); impl->buffer = calloc(impl->buffer_samples * 2 + 64, sizeof(float)); if (impl->buffer == NULL) { delay_cleanup(impl); return NULL; } return impl; } static void delay_connect_port(void * Instance, unsigned long Port, void * DataLocation) { struct delay_impl *impl = Instance; impl->port[Port] = DataLocation; } static void delay_activate(void * Instance) { struct delay_impl *impl = Instance; if (impl->port[3] != NULL) impl->port[3][0] = impl->latency; } static void delay_run(void * Instance, unsigned long SampleCount) { struct delay_impl *impl = Instance; float *in = impl->port[1], *out = impl->port[0]; float delay = impl->port[2][0]; if (delay != impl->delay) { impl->delay_samples = SPA_CLAMP((uint32_t)(delay * impl->rate), 0u, impl->buffer_samples-1); impl->delay = delay; } if (in != NULL && out != NULL) { spa_fga_dsp_delay(impl->dsp, impl->buffer, &impl->ptr, impl->buffer_samples, impl->delay_samples, out, in, SampleCount); } if (impl->port[3] != NULL) impl->port[3][0] = impl->latency; } static struct spa_fga_port delay_ports[] = { { .index = 0, .name = "Out", .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_AUDIO, }, { .index = 1, .name = "In", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, }, { .index = 2, .name = "Delay (s)", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, .def = 0.0f, .min = 0.0f, .max = 100.0f }, { .index = 3, .name = "latency", .hint = SPA_FGA_HINT_LATENCY, .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_CONTROL, }, }; static const struct spa_fga_descriptor delay_desc = { .name = "delay", .flags = SPA_FGA_DESCRIPTOR_SUPPORTS_NULL_DATA, .n_ports = SPA_N_ELEMENTS(delay_ports), .ports = delay_ports, .instantiate = delay_instantiate, .connect_port = delay_connect_port, .activate = delay_activate, .run = delay_run, .cleanup = delay_cleanup, }; /* invert */ static void invert_run(void * Instance, unsigned long SampleCount) { struct builtin *impl = Instance; float *in = impl->port[1], *out = impl->port[0]; unsigned long n; for (n = 0; n < SampleCount; n++) out[n] = -in[n]; } static struct spa_fga_port invert_ports[] = { { .index = 0, .name = "Out", .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_AUDIO, }, { .index = 1, .name = "In", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, }, }; static const struct spa_fga_descriptor invert_desc = { .name = "invert", .n_ports = 2, .ports = invert_ports, .instantiate = builtin_instantiate, .connect_port = builtin_connect_port, .run = invert_run, .cleanup = builtin_cleanup, }; /* clamp */ static void clamp_run(void * Instance, unsigned long SampleCount) { struct builtin *impl = Instance; float min = impl->port[4][0], max = impl->port[5][0]; float *in = impl->port[1], *out = impl->port[0]; float *ctrl = impl->port[3], *notify = impl->port[2]; if (in != NULL && out != NULL) { unsigned long n; for (n = 0; n < SampleCount; n++) out[n] = SPA_CLAMPF(in[n], min, max); } if (ctrl != NULL && notify != NULL) notify[0] = SPA_CLAMPF(ctrl[0], min, max); } static struct spa_fga_port clamp_ports[] = { { .index = 0, .name = "Out", .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_AUDIO, }, { .index = 1, .name = "In", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, }, { .index = 2, .name = "Notify", .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_CONTROL, }, { .index = 3, .name = "Control", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, }, { .index = 4, .name = "Min", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, .def = 0.0f, .min = -100.0f, .max = 100.0f }, { .index = 5, .name = "Max", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, .def = 1.0f, .min = -100.0f, .max = 100.0f }, }; static const struct spa_fga_descriptor clamp_desc = { .name = "clamp", .flags = SPA_FGA_DESCRIPTOR_SUPPORTS_NULL_DATA, .n_ports = SPA_N_ELEMENTS(clamp_ports), .ports = clamp_ports, .instantiate = builtin_instantiate, .connect_port = builtin_connect_port, .run = clamp_run, .cleanup = builtin_cleanup, }; /* linear */ static void linear_run(void * Instance, unsigned long SampleCount) { struct builtin *impl = Instance; float mult = impl->port[4][0], add = impl->port[5][0]; float *in = impl->port[1], *out = impl->port[0]; float *ctrl = impl->port[3], *notify = impl->port[2]; if (in != NULL && out != NULL) spa_fga_dsp_linear(impl->dsp, out, in, mult, add, SampleCount); if (ctrl != NULL && notify != NULL) notify[0] = ctrl[0] * mult + add; } static struct spa_fga_port linear_ports[] = { { .index = 0, .name = "Out", .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_AUDIO, }, { .index = 1, .name = "In", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, }, { .index = 2, .name = "Notify", .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_CONTROL, }, { .index = 3, .name = "Control", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, }, { .index = 4, .name = "Mult", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, .def = 1.0f, .min = -10.0f, .max = 10.0f }, { .index = 5, .name = "Add", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, .def = 0.0f, .min = -10.0f, .max = 10.0f }, }; static const struct spa_fga_descriptor linear_desc = { .name = "linear", .flags = SPA_FGA_DESCRIPTOR_SUPPORTS_NULL_DATA, .n_ports = SPA_N_ELEMENTS(linear_ports), .ports = linear_ports, .instantiate = builtin_instantiate, .connect_port = builtin_connect_port, .run = linear_run, .cleanup = builtin_cleanup, }; /* reciprocal */ static void recip_run(void * Instance, unsigned long SampleCount) { struct builtin *impl = Instance; float *in = impl->port[1], *out = impl->port[0]; float *ctrl = impl->port[3], *notify = impl->port[2]; if (in != NULL && out != NULL) { unsigned long n; for (n = 0; n < SampleCount; n++) { if (in[0] == 0.0f) out[n] = 0.0f; else out[n] = 1.0f / in[n]; } } if (ctrl != NULL && notify != NULL) { if (ctrl[0] == 0.0f) notify[0] = 0.0f; else notify[0] = 1.0f / ctrl[0]; } } static struct spa_fga_port recip_ports[] = { { .index = 0, .name = "Out", .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_AUDIO, }, { .index = 1, .name = "In", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, }, { .index = 2, .name = "Notify", .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_CONTROL, }, { .index = 3, .name = "Control", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, }, }; static const struct spa_fga_descriptor recip_desc = { .name = "recip", .flags = SPA_FGA_DESCRIPTOR_SUPPORTS_NULL_DATA, .n_ports = SPA_N_ELEMENTS(recip_ports), .ports = recip_ports, .instantiate = builtin_instantiate, .connect_port = builtin_connect_port, .run = recip_run, .cleanup = builtin_cleanup, }; /* exp */ static void exp_run(void * Instance, unsigned long SampleCount) { struct builtin *impl = Instance; float base = impl->port[4][0]; float *in = impl->port[1], *out = impl->port[0]; float *ctrl = impl->port[3], *notify = impl->port[2]; if (in != NULL && out != NULL) { unsigned long n; for (n = 0; n < SampleCount; n++) out[n] = powf(base, in[n]); } if (ctrl != NULL && notify != NULL) notify[0] = powf(base, ctrl[0]); } static struct spa_fga_port exp_ports[] = { { .index = 0, .name = "Out", .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_AUDIO, }, { .index = 1, .name = "In", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, }, { .index = 2, .name = "Notify", .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_CONTROL, }, { .index = 3, .name = "Control", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, }, { .index = 4, .name = "Base", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, .def = (float)M_E, .min = -10.0f, .max = 10.0f }, }; static const struct spa_fga_descriptor exp_desc = { .name = "exp", .flags = SPA_FGA_DESCRIPTOR_SUPPORTS_NULL_DATA, .n_ports = SPA_N_ELEMENTS(exp_ports), .ports = exp_ports, .instantiate = builtin_instantiate, .connect_port = builtin_connect_port, .run = exp_run, .cleanup = builtin_cleanup, }; /* log */ static void log_run(void * Instance, unsigned long SampleCount) { struct builtin *impl = Instance; float base = impl->port[4][0]; float m1 = impl->port[5][0]; float m2 = impl->port[6][0]; float *in = impl->port[1], *out = impl->port[0]; float *ctrl = impl->port[3], *notify = impl->port[2]; float lb = log2f(base); if (in != NULL && out != NULL) { unsigned long n; for (n = 0; n < SampleCount; n++) out[n] = m2 * log2f(fabsf(in[n] * m1)) / lb; } if (ctrl != NULL && notify != NULL) notify[0] = m2 * log2f(fabsf(ctrl[0] * m1)) / lb; } static struct spa_fga_port log_ports[] = { { .index = 0, .name = "Out", .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_AUDIO, }, { .index = 1, .name = "In", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, }, { .index = 2, .name = "Notify", .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_CONTROL, }, { .index = 3, .name = "Control", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, }, { .index = 4, .name = "Base", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, .def = (float)M_E, .min = 2.0f, .max = 100.0f }, { .index = 5, .name = "M1", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, .def = 1.0f, .min = -10.0f, .max = 10.0f }, { .index = 6, .name = "M2", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, .def = 1.0f, .min = -10.0f, .max = 10.0f }, }; static const struct spa_fga_descriptor log_desc = { .name = "log", .flags = SPA_FGA_DESCRIPTOR_SUPPORTS_NULL_DATA, .n_ports = SPA_N_ELEMENTS(log_ports), .ports = log_ports, .instantiate = builtin_instantiate, .connect_port = builtin_connect_port, .run = log_run, .cleanup = builtin_cleanup, }; /* mult */ static void mult_run(void * Instance, unsigned long SampleCount) { struct builtin *impl = Instance; int i, n_src = 0; float *out = impl->port[0]; const float *src[8]; if (out == NULL) return; for (i = 0; i < 8; i++) { float *in = impl->port[1+i]; if (in == NULL) continue; src[n_src++] = in; } spa_fga_dsp_mult(impl->dsp, out, src, n_src, SampleCount); } static struct spa_fga_port mult_ports[] = { { .index = 0, .name = "Out", .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_AUDIO, }, { .index = 1, .name = "In 1", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, }, { .index = 2, .name = "In 2", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, }, { .index = 3, .name = "In 3", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, }, { .index = 4, .name = "In 4", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, }, { .index = 5, .name = "In 5", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, }, { .index = 6, .name = "In 6", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, }, { .index = 7, .name = "In 7", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, }, { .index = 8, .name = "In 8", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, }, }; static const struct spa_fga_descriptor mult_desc = { .name = "mult", .flags = SPA_FGA_DESCRIPTOR_SUPPORTS_NULL_DATA, .n_ports = SPA_N_ELEMENTS(mult_ports), .ports = mult_ports, .instantiate = builtin_instantiate, .connect_port = builtin_connect_port, .run = mult_run, .cleanup = builtin_cleanup, }; #define M_PI_M2f (float)(M_PI+M_PI) /* sine */ static void sine_run(void * Instance, unsigned long SampleCount) { struct builtin *impl = Instance; float *out = impl->port[0]; float *notify = impl->port[1]; float freq = impl->port[2][0]; float ampl = impl->port[3][0]; float offs = impl->port[5][0]; unsigned long n; for (n = 0; n < SampleCount; n++) { if (out != NULL) out[n] = sinf(impl->accum) * ampl + offs; if (notify != NULL && n == 0) notify[0] = sinf(impl->accum) * ampl + offs; impl->accum += M_PI_M2f * freq / impl->rate; if (impl->accum >= M_PI_M2f) impl->accum -= M_PI_M2f; } } static struct spa_fga_port sine_ports[] = { { .index = 0, .name = "Out", .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_AUDIO, }, { .index = 1, .name = "Notify", .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_CONTROL, }, { .index = 2, .name = "Freq", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, .def = 440.0f, .min = 0.0f, .max = 1000000.0f }, { .index = 3, .name = "Ampl", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, .def = 1.0, .min = 0.0f, .max = 10.0f }, { .index = 4, .name = "Phase", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, .def = 0.0f, .min = (float)-M_PI, .max = (float)M_PI }, { .index = 5, .name = "Offset", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, .def = 0.0f, .min = -10.0f, .max = 10.0f }, }; static const struct spa_fga_descriptor sine_desc = { .name = "sine", .flags = SPA_FGA_DESCRIPTOR_SUPPORTS_NULL_DATA, .n_ports = SPA_N_ELEMENTS(sine_ports), .ports = sine_ports, .instantiate = builtin_instantiate, .connect_port = builtin_connect_port, .run = sine_run, .cleanup = builtin_cleanup, }; #define PARAM_EQ_MAX 64 struct param_eq_impl { struct plugin *plugin; struct spa_fga_dsp *dsp; struct spa_log *log; unsigned long rate; float *port[8*2]; uint32_t n_bq; struct biquad bq[PARAM_EQ_MAX * 8]; }; static int load_eq_bands(struct plugin *pl, const char *filename, int rate, struct biquad *bq, uint32_t max_bq, uint32_t *n_bq) { FILE *f = NULL; char *line = NULL; ssize_t nread; size_t linelen; uint32_t n = 0; char filter_type[4]; char filter[4]; char freq[9], q[7], gain[7]; float vf, vg, vq; int res = 0; if ((f = fopen(filename, "r")) == NULL) { res = -errno; spa_log_error(pl->log, "failed to open param_eq file '%s': %m", filename); goto exit; } /* * Read the Preamp gain line. * Example: Preamp: -6.8 dB * * When a pre-amp gain is required, which is usually the case when * applying EQ, we need to modify the first EQ band to apply a * bq_highshelf filter at frequency 0 Hz with the provided negative * gain. * * Pre-amp gain is always negative to offset the effect of possible * clipping introduced by the amplification resulting from EQ. */ nread = getline(&line, &linelen, f); if (nread != -1 && sscanf(line, "%*s %6s %*s", gain) == 1) { if (spa_json_parse_float(gain, strlen(gain), &vg)) { spa_log_info(pl->log, "%d %s freq:0 q:1.0 gain:%f", n, bq_name_from_type(BQ_HIGHSHELF), vg); biquad_set(&bq[n++], BQ_HIGHSHELF, 0.0f, 1.0f, vg); } } /* Read the filter bands */ while ((nread = getline(&line, &linelen, f)) != -1) { if (n == PARAM_EQ_MAX) { res = -ENOSPC; goto exit; } /* * On field widths: * - filter can be ON or OFF * - filter type can be PK, LSC, HSC * - freq can be at most 5 decimal digits * - gain can be -xy.z * - Q can be x.y00 * * Use a field width of 6 for gain and Q to account for any * possible zeros. */ if (sscanf(line, "%*s %*d: %3s %3s %*s %8s %*s %*s %6s %*s %*c %6s", filter, filter_type, freq, gain, q) == 5) { if (strcmp(filter, "ON") == 0) { int type; if (spa_streq(filter_type, "PK")) type = BQ_PEAKING; else if (spa_streq(filter_type, "LSC")) type = BQ_LOWSHELF; else if (spa_streq(filter_type, "HSC")) type = BQ_HIGHSHELF; else continue; if (spa_json_parse_float(freq, strlen(freq), &vf) && spa_json_parse_float(gain, strlen(gain), &vg) && spa_json_parse_float(q, strlen(q), &vq)) { spa_log_info(pl->log, "%d %s freq:%f q:%f gain:%f", n, bq_name_from_type(type), vf, vq, vg); biquad_set(&bq[n++], type, vf * 2.0f / rate, vq, vg); } } } } *n_bq = n; exit: if (f) fclose(f); return res; } /* * [ * { type=bq_peaking freq=21 gain=6.7 q=1.100 } * { type=bq_peaking freq=85 gain=6.9 q=3.000 } * { type=bq_peaking freq=110 gain=-2.6 q=2.700 } * { type=bq_peaking freq=210 gain=5.9 q=2.100 } * { type=bq_peaking freq=710 gain=-1.0 q=0.600 } * { type=bq_peaking freq=1600 gain=2.3 q=2.700 } * ] */ static int parse_filters(struct plugin *pl, struct spa_json *iter, int rate, struct biquad *bq, uint32_t max_bq, uint32_t *n_bq) { struct spa_json it[1]; const char *val; char key[256]; char type_str[17]; int len; uint32_t n = 0; while (spa_json_enter_object(iter, &it[0]) > 0) { float freq = 0.0f, gain = 0.0f, q = 1.0f; int type = BQ_NONE; while ((len = spa_json_object_next(&it[0], key, sizeof(key), &val)) > 0) { if (spa_streq(key, "type")) { if (spa_json_parse_stringn(val, len, type_str, sizeof(type_str)) <= 0) { spa_log_error(pl->log, "param_eq:type requires a string"); return -EINVAL; } type = bq_type_from_name(type_str); } else if (spa_streq(key, "freq")) { if (spa_json_parse_float(val, len, &freq) <= 0) { spa_log_error(pl->log, "param_eq:rate requires a number"); return -EINVAL; } } else if (spa_streq(key, "q")) { if (spa_json_parse_float(val, len, &q) <= 0) { spa_log_error(pl->log, "param_eq:q requires a float"); return -EINVAL; } } else if (spa_streq(key, "gain")) { if (spa_json_parse_float(val, len, &gain) <= 0) { spa_log_error(pl->log, "param_eq:gain requires a float"); return -EINVAL; } } else { spa_log_warn(pl->log, "param_eq: ignoring filter key: '%s'", key); } } if (n == max_bq) return -ENOSPC; spa_log_info(pl->log, "%d %s freq:%f q:%f gain:%f", n, bq_name_from_type(type), freq, q, gain); biquad_set(&bq[n++], type, freq * 2 / rate, q, gain); } *n_bq = n; return 0; } /* * { * filename = "...", * filenameX = "...", # to load channel X * filters = [ ... ] * filtersX = [ ... ] # to load channel X * } */ static void *param_eq_instantiate(const struct spa_fga_plugin *plugin, const struct spa_fga_descriptor * Descriptor, unsigned long SampleRate, int index, const char *config) { struct plugin *pl = SPA_CONTAINER_OF(plugin, struct plugin, plugin); struct spa_json it[3]; const char *val; char key[256], filename[PATH_MAX]; int len, res; struct param_eq_impl *impl; uint32_t i, n_bq = 0; if (config == NULL) { spa_log_error(pl->log, "param_eq: requires a config section"); errno = EINVAL; return NULL; } if (spa_json_begin_object(&it[0], config, strlen(config)) <= 0) { spa_log_error(pl->log, "param_eq: config must be an object"); return NULL; } impl = calloc(1, sizeof(*impl)); if (impl == NULL) return NULL; impl->plugin = pl; impl->dsp = pl->dsp; impl->log = pl->log; impl->rate = SampleRate; for (i = 0; i < SPA_N_ELEMENTS(impl->bq); i++) biquad_set(&impl->bq[i], BQ_NONE, 0.0f, 0.0f, 0.0f); while ((len = spa_json_object_next(&it[0], key, sizeof(key), &val)) > 0) { int32_t idx = 0; struct biquad *bq = impl->bq; if (spa_strstartswith(key, "filename")) { if (spa_json_parse_stringn(val, len, filename, sizeof(filename)) <= 0) { spa_log_error(impl->log, "param_eq: filename requires a string"); goto error; } if (spa_atoi32(key+8, &idx, 0)) bq = &impl->bq[(SPA_CLAMP(idx, 1, 8) - 1) * PARAM_EQ_MAX]; res = load_eq_bands(pl, filename, impl->rate, bq, PARAM_EQ_MAX, &n_bq); if (res < 0) { spa_log_error(impl->log, "param_eq: failed to parse configuration from '%s'", filename); goto error; } spa_log_info(impl->log, "loaded %d biquads for channel %d from %s", n_bq, idx, filename); impl->n_bq = SPA_MAX(impl->n_bq, n_bq); } else if (spa_strstartswith(key, "filters")) { if (!spa_json_is_array(val, len)) { spa_log_error(impl->log, "param_eq:filters require an array"); goto error; } spa_json_enter(&it[0], &it[1]); if (spa_atoi32(key+7, &idx, 0)) bq = &impl->bq[(SPA_CLAMP(idx, 1, 8) - 1) * PARAM_EQ_MAX]; res = parse_filters(pl, &it[1], impl->rate, bq, PARAM_EQ_MAX, &n_bq); if (res < 0) { spa_log_error(impl->log, "param_eq: failed to parse configuration"); goto error; } spa_log_info(impl->log, "parsed %d biquads for channel %d", n_bq, idx); impl->n_bq = SPA_MAX(impl->n_bq, n_bq); } else { spa_log_warn(impl->log, "param_eq: ignoring config key: '%s'", key); } if (idx == 0) { for (i = 1; i < 8; i++) spa_memcpy(&impl->bq[i*PARAM_EQ_MAX], impl->bq, sizeof(struct biquad) * PARAM_EQ_MAX); } } return impl; error: free(impl); return NULL; } static void param_eq_connect_port(void * Instance, unsigned long Port, void * DataLocation) { struct param_eq_impl *impl = Instance; impl->port[Port] = DataLocation; } static void param_eq_run(void * Instance, unsigned long SampleCount) { struct param_eq_impl *impl = Instance; spa_fga_dsp_biquad_run(impl->dsp, impl->bq, impl->n_bq, PARAM_EQ_MAX, &impl->port[8], (const float**)impl->port, 8, SampleCount); } static struct spa_fga_port param_eq_ports[] = { { .index = 0, .name = "In 1", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, }, { .index = 1, .name = "In 2", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, }, { .index = 2, .name = "In 3", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, }, { .index = 3, .name = "In 4", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, }, { .index = 4, .name = "In 5", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, }, { .index = 5, .name = "In 6", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, }, { .index = 6, .name = "In 7", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, }, { .index = 7, .name = "In 8", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, }, { .index = 8, .name = "Out 1", .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_AUDIO, }, { .index = 9, .name = "Out 2", .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_AUDIO, }, { .index = 10, .name = "Out 3", .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_AUDIO, }, { .index = 11, .name = "Out 4", .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_AUDIO, }, { .index = 12, .name = "Out 5", .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_AUDIO, }, { .index = 13, .name = "Out 6", .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_AUDIO, }, { .index = 14, .name = "Out 7", .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_AUDIO, }, { .index = 15, .name = "Out 8", .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_AUDIO, }, }; static const struct spa_fga_descriptor param_eq_desc = { .name = "param_eq", .flags = SPA_FGA_DESCRIPTOR_SUPPORTS_NULL_DATA, .n_ports = SPA_N_ELEMENTS(param_eq_ports), .ports = param_eq_ports, .instantiate = param_eq_instantiate, .connect_port = param_eq_connect_port, .run = param_eq_run, .cleanup = free, }; /** max */ static void max_run(void * Instance, unsigned long SampleCount) { struct builtin *impl = Instance; float *out = impl->port[0]; float *src[8]; unsigned long n, p, n_srcs = 0; if (out == NULL) return; for (p = 1; p < 9; p++) { if (impl->port[p] != NULL) src[n_srcs++] = impl->port[p]; } if (n_srcs == 0) { spa_memzero(out, SampleCount * sizeof(float)); } else if (n_srcs == 1) { spa_memcpy(out, src[0], SampleCount * sizeof(float)); } else { for (p = 0; p < n_srcs; p++) { if (p == 0) { for (n = 0; n < SampleCount; n++) out[n] = SPA_MAX(src[p][n], src[p + 1][n]); p++; } else { for (n = 0; n < SampleCount; n++) out[n] = SPA_MAX(out[n], src[p][n]); } } } } static struct spa_fga_port max_ports[] = { { .index = 0, .name = "Out", .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_AUDIO, }, { .index = 1, .name = "In 1", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, }, { .index = 2, .name = "In 2", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, }, { .index = 3, .name = "In 3", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, }, { .index = 4, .name = "In 4", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, }, { .index = 5, .name = "In 5", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, }, { .index = 6, .name = "In 6", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, }, { .index = 7, .name = "In 7", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, }, { .index = 8, .name = "In 8", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, }, }; static const struct spa_fga_descriptor max_desc = { .name = "max", .flags = SPA_FGA_DESCRIPTOR_SUPPORTS_NULL_DATA, .n_ports = SPA_N_ELEMENTS(max_ports), .ports = max_ports, .instantiate = builtin_instantiate, .connect_port = builtin_connect_port, .run = max_run, .cleanup = builtin_cleanup, }; /* DC blocking */ struct dcblock { float xm1; float ym1; }; struct dcblock_impl { struct plugin *plugin; struct spa_fga_dsp *dsp; struct spa_log *log; unsigned long rate; float *port[17]; struct dcblock dc[8]; }; static void *dcblock_instantiate(const struct spa_fga_plugin *plugin, const struct spa_fga_descriptor * Descriptor, unsigned long SampleRate, int index, const char *config) { struct plugin *pl = SPA_CONTAINER_OF(plugin, struct plugin, plugin); struct dcblock_impl *impl; impl = calloc(1, sizeof(*impl)); if (impl == NULL) return NULL; impl->plugin = pl; impl->dsp = pl->dsp; impl->log = pl->log; impl->rate = SampleRate; return impl; } static void dcblock_run_n(struct dcblock dc[], float *dst[], const float *src[], uint32_t n_src, float R, uint32_t n_samples) { float x, y; uint32_t i, n; for (i = 0; i < n_src; i++) { const float *in = src[i]; float *out = dst[i]; float xm1 = dc[i].xm1; float ym1 = dc[i].ym1; if (out == NULL || in == NULL) continue; for (n = 0; n < n_samples; n++) { x = in[n]; y = x - xm1 + R * ym1; xm1 = x; ym1 = y; out[n] = y; } dc[i].xm1 = xm1; dc[i].ym1 = ym1; } } static void dcblock_run(void * Instance, unsigned long SampleCount) { struct dcblock_impl *impl = Instance; float R = impl->port[16][0]; dcblock_run_n(impl->dc, &impl->port[8], (const float**)&impl->port[0], 8, R, SampleCount); } static void dcblock_connect_port(void * Instance, unsigned long Port, void * DataLocation) { struct dcblock_impl *impl = Instance; impl->port[Port] = DataLocation; } static struct spa_fga_port dcblock_ports[] = { { .index = 0, .name = "In 1", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, }, { .index = 1, .name = "In 2", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, }, { .index = 2, .name = "In 3", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, }, { .index = 3, .name = "In 4", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, }, { .index = 4, .name = "In 5", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, }, { .index = 5, .name = "In 6", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, }, { .index = 6, .name = "In 7", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, }, { .index = 7, .name = "In 8", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, }, { .index = 8, .name = "Out 1", .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_AUDIO, }, { .index = 9, .name = "Out 2", .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_AUDIO, }, { .index = 10, .name = "Out 3", .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_AUDIO, }, { .index = 11, .name = "Out 4", .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_AUDIO, }, { .index = 12, .name = "Out 5", .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_AUDIO, }, { .index = 13, .name = "Out 6", .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_AUDIO, }, { .index = 14, .name = "Out 7", .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_AUDIO, }, { .index = 15, .name = "Out 8", .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_AUDIO, }, { .index = 16, .name = "R", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, .def = 0.995f, .min = 0.0f, .max = 1.0f }, }; static const struct spa_fga_descriptor dcblock_desc = { .name = "dcblock", .flags = SPA_FGA_DESCRIPTOR_SUPPORTS_NULL_DATA, .n_ports = SPA_N_ELEMENTS(dcblock_ports), .ports = dcblock_ports, .instantiate = dcblock_instantiate, .connect_port = dcblock_connect_port, .run = dcblock_run, .cleanup = free, }; /* ramp */ static struct spa_fga_port ramp_ports[] = { { .index = 0, .name = "Out", .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_AUDIO, }, { .index = 1, .name = "Start", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, }, { .index = 2, .name = "Stop", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, }, { .index = 3, .name = "Current", .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_CONTROL, }, { .index = 4, .name = "Duration (s)", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, }, }; static void ramp_run(void * Instance, unsigned long SampleCount) { struct builtin *impl = Instance; float *out = impl->port[0]; float start = impl->port[1][0]; float stop = impl->port[2][0], last; float *current = impl->port[3]; float duration = impl->port[4][0]; float inc = (stop - start) / (duration * impl->rate); uint32_t n; last = stop; if (inc < 0.f) SPA_SWAP(start, stop); if (out != NULL) { if (impl->accum == last) { for (n = 0; n < SampleCount; n++) out[n] = last; } else { for (n = 0; n < SampleCount; n++) { out[n] = impl->accum; impl->accum = SPA_CLAMP(impl->accum + inc, start, stop); } } } else { impl->accum = SPA_CLAMP(impl->accum + SampleCount * inc, start, stop); } if (current) current[0] = impl->accum; } static const struct spa_fga_descriptor ramp_desc = { .name = "ramp", .flags = SPA_FGA_DESCRIPTOR_SUPPORTS_NULL_DATA, .n_ports = SPA_N_ELEMENTS(ramp_ports), .ports = ramp_ports, .instantiate = builtin_instantiate, .connect_port = builtin_connect_port, .run = ramp_run, .cleanup = builtin_cleanup, }; /* abs */ static void abs_run(void * Instance, unsigned long SampleCount) { struct builtin *impl = Instance; float *in = impl->port[1], *out = impl->port[0]; if (in != NULL && out != NULL) { unsigned long n; for (n = 0; n < SampleCount; n++) { out[n] = SPA_ABS(in[n]); } } } static struct spa_fga_port abs_ports[] = { { .index = 0, .name = "Out", .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_AUDIO, }, { .index = 1, .name = "In", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, }, }; static const struct spa_fga_descriptor abs_desc = { .name = "abs", .flags = SPA_FGA_DESCRIPTOR_SUPPORTS_NULL_DATA, .n_ports = SPA_N_ELEMENTS(abs_ports), .ports = abs_ports, .instantiate = builtin_instantiate, .connect_port = builtin_connect_port, .run = abs_run, .cleanup = builtin_cleanup, }; /* sqrt */ static void sqrt_run(void * Instance, unsigned long SampleCount) { struct builtin *impl = Instance; float *in = impl->port[1], *out = impl->port[0]; if (in != NULL && out != NULL) { unsigned long n; for (n = 0; n < SampleCount; n++) { if (in[n] <= 0.0f) out[n] = 0.0f; else out[n] = sqrtf(in[n]); } } } static struct spa_fga_port sqrt_ports[] = { { .index = 0, .name = "Out", .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_AUDIO, }, { .index = 1, .name = "In", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, }, }; static const struct spa_fga_descriptor sqrt_desc = { .name = "sqrt", .flags = SPA_FGA_DESCRIPTOR_SUPPORTS_NULL_DATA, .n_ports = SPA_N_ELEMENTS(sqrt_ports), .ports = sqrt_ports, .instantiate = builtin_instantiate, .connect_port = builtin_connect_port, .run = sqrt_run, .cleanup = builtin_cleanup, }; /* debug */ static void debug_run(void * Instance, unsigned long SampleCount) { struct builtin *impl = Instance; float *in = impl->port[0], *out = impl->port[1]; float *control = impl->port[2], *notify = impl->port[3]; if (in != NULL) { spa_debug_log_mem(impl->log, SPA_LOG_LEVEL_INFO, 0, in, SampleCount * sizeof(float)); if (out != NULL) spa_memcpy(out, in, SampleCount * sizeof(float)); } if (control != NULL) { spa_log_info(impl->log, "control: %f", control[0]); if (notify != NULL) notify[0] = control[0]; } } static struct spa_fga_port debug_ports[] = { { .index = 0, .name = "In", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, }, { .index = 1, .name = "Out", .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_AUDIO, }, { .index = 2, .name = "Control", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, }, { .index = 3, .name = "Notify", .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_CONTROL, }, }; static const struct spa_fga_descriptor debug_desc = { .name = "debug", .flags = SPA_FGA_DESCRIPTOR_SUPPORTS_NULL_DATA, .n_ports = SPA_N_ELEMENTS(debug_ports), .ports = debug_ports, .instantiate = builtin_instantiate, .connect_port = builtin_connect_port, .run = debug_run, .cleanup = builtin_cleanup, }; /* pipe */ struct pipe_impl { struct plugin *plugin; struct spa_log *log; struct spa_fga_dsp *dsp; unsigned long rate; float *port[3]; float latency; int write_fd; int read_fd; size_t written; size_t read; }; static int do_exec(struct pipe_impl *impl, const char *command) { int pid, res, len, argc = 0; char *argv[512]; struct spa_json it[2]; const char *value; int stdin_pipe[2]; int stdout_pipe[2]; if (spa_json_begin_array_relax(&it[0], command, strlen(command)) <= 0) return -EINVAL; while ((len = spa_json_next(&it[0], &value)) > 0) { char *s; if ((s = malloc(len+1)) == NULL) return -errno; spa_json_parse_stringn(value, len, s, len+1); argv[argc++] = s; } argv[argc++] = NULL; pipe2(stdin_pipe, 0); pipe2(stdout_pipe, 0); impl->write_fd = stdin_pipe[1]; impl->read_fd = stdout_pipe[0]; pid = fork(); if (pid == 0) { char buf[1024]; char *const *p; struct spa_strbuf s; /* Double fork to avoid zombies; we don't want to set SIGCHLD handler */ pid = fork(); if (pid < 0) { spa_log_error(impl->log, "fork error: %m"); goto done; } else if (pid != 0) { exit(0); } dup2(stdin_pipe[0], 0); dup2(stdout_pipe[1], 1); spa_strbuf_init(&s, buf, sizeof(buf)); for (p = argv; *p; ++p) spa_strbuf_append(&s, " '%s'", *p); spa_log_info(impl->log, "exec%s", s.buffer); res = execvp(argv[0], argv); if (res == -1) { res = -errno; spa_log_error(impl->log, "execvp error '%s': %m", argv[0]); } done: exit(1); } else if (pid < 0) { spa_log_error(impl->log, "fork error: %m"); } else { int status = 0; do { errno = 0; res = waitpid(pid, &status, 0); } while (res < 0 && errno == EINTR); spa_log_debug(impl->log, "exec got pid %d res:%d status:%d", (int)pid, res, status); } return 0; } static void pipe_transfer(struct pipe_impl *impl, float *in, float *out, int count) { ssize_t sz; sz = read(impl->read_fd, out, count * sizeof(float)); if (sz > 0) { impl->read += sz; if (impl->read == (size_t)sz) { while ((sz = read(impl->read_fd, out, count * sizeof(float))) != -1) impl->read += sz; } } else { memset(out, 0, count * sizeof(float)); } if ((sz = write(impl->write_fd, in, count * sizeof(float))) != -1) impl->written += sz; } static void *pipe_instantiate(const struct spa_fga_plugin *plugin, const struct spa_fga_descriptor * Descriptor, unsigned long SampleRate, int index, const char *config) { struct plugin *pl = SPA_CONTAINER_OF(plugin, struct plugin, plugin); struct pipe_impl *impl; struct spa_json it[2]; const char *val; char key[256]; spa_autofree char*command = NULL; int len; errno = EINVAL; if (config == NULL) { spa_log_error(pl->log, "pipe: requires a config section"); return NULL; } if (spa_json_begin_object(&it[0], config, strlen(config)) <= 0) { spa_log_error(pl->log, "pipe: config must be an object"); return NULL; } while ((len = spa_json_object_next(&it[0], key, sizeof(key), &val)) > 0) { if (spa_streq(key, "command")) { if ((command = malloc(len+1)) == NULL) return NULL; if (spa_json_parse_stringn(val, len, command, len+1) <= 0) { spa_log_error(pl->log, "pipe: command requires a string"); return NULL; } } else { spa_log_warn(pl->log, "pipe: ignoring config key: '%s'", key); } } if (command == NULL || command[0] == '\0') { spa_log_error(pl->log, "pipe: command must be given and can not be empty"); return NULL; } impl = calloc(1, sizeof(*impl)); if (impl == NULL) return NULL; impl->plugin = pl; impl->log = pl->log; impl->dsp = pl->dsp; impl->rate = SampleRate; do_exec(impl, command); fcntl(impl->write_fd, F_SETFL, fcntl(impl->write_fd, F_GETFL) | O_NONBLOCK); fcntl(impl->read_fd, F_SETFL, fcntl(impl->read_fd, F_GETFL) | O_NONBLOCK); return impl; } static void pipe_connect_port(void *Instance, unsigned long Port, void * DataLocation) { struct pipe_impl *impl = Instance; impl->port[Port] = DataLocation; } static void pipe_run(void * Instance, unsigned long SampleCount) { struct pipe_impl *impl = Instance; float *in = impl->port[0], *out = impl->port[1]; if (in != NULL && out != NULL) pipe_transfer(impl, in, out, SampleCount); } static void pipe_cleanup(void * Instance) { struct pipe_impl *impl = Instance; close(impl->write_fd); close(impl->read_fd); free(impl); } static struct spa_fga_port pipe_ports[] = { { .index = 0, .name = "In", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, }, { .index = 1, .name = "Out", .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_AUDIO, }, }; static const struct spa_fga_descriptor pipe_desc = { .name = "pipe", .flags = SPA_FGA_DESCRIPTOR_SUPPORTS_NULL_DATA, .n_ports = SPA_N_ELEMENTS(pipe_ports), .ports = pipe_ports, .instantiate = pipe_instantiate, .connect_port = pipe_connect_port, .run = pipe_run, .cleanup = pipe_cleanup, }; /* zeroramp */ static struct spa_fga_port zeroramp_ports[] = { { .index = 0, .name = "In", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, }, { .index = 1, .name = "Out", .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_AUDIO, }, { .index = 2, .name = "Gap (s)", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, .def = 0.000666f, .min = 0.0f, .max = 1.0f }, { .index = 3, .name = "Duration (s)", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, .def = 0.000666f, .min = 0.0f, .max = 1.0f }, }; #ifndef M_PIf # define M_PIf 3.14159265358979323846f /* pi */ #endif static void zeroramp_run(void * Instance, unsigned long SampleCount) { struct builtin *impl = Instance; float *in = impl->port[0]; float *out = impl->port[1]; uint32_t n, i, c; uint32_t gap = (uint32_t)(impl->port[2][0] * impl->rate); uint32_t duration = (uint32_t)(impl->port[3][0] * impl->rate); if (out == NULL) return; if (in == NULL) { memset(out, 0, SampleCount * sizeof(float)); return; } for (n = 0; n < SampleCount; n++) { if (impl->mode == 0) { /* normal mode, finding gaps */ out[n] = in[n]; if (in[n] == 0.0f) { if (++impl->count == gap) { /* we found gap zeroes, fade out last * sample and go into zero mode */ for (c = 1, i = n; c < duration && i > 0; i--, c++) out[i-1] = impl->last * (0.5f + 0.5f * cosf(M_PIf + M_PIf * c / duration)); impl->mode = 1; } } else { /* keep last sample to fade out when needed */ impl->count = 0; impl->last = in[n]; } } if (impl->mode == 1) { /* zero mode */ if (in[n] != 0.0f) { /* gap ended, move to fade-in mode */ impl->mode = 2; impl->count = 0; } else { out[n] = 0.0f; } } if (impl->mode == 2) { /* fade-in mode */ out[n] = in[n] * (0.5f + 0.5f * cosf(M_PIf + (M_PIf * ++impl->count / duration))); if (impl->count == duration) { /* fade in complete, back to normal mode */ impl->count = 0; impl->mode = 0; } } } } static const struct spa_fga_descriptor zeroramp_desc = { .name = "zeroramp", .flags = SPA_FGA_DESCRIPTOR_SUPPORTS_NULL_DATA, .n_ports = SPA_N_ELEMENTS(zeroramp_ports), .ports = zeroramp_ports, .instantiate = builtin_instantiate, .connect_port = builtin_connect_port, .run = zeroramp_run, .cleanup = builtin_cleanup, }; /* noisegate */ static struct spa_fga_port noisegate_ports[] = { { .index = 0, .name = "In", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, }, { .index = 1, .name = "Out", .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_AUDIO, }, { .index = 2, .name = "Level", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, .def = NAN }, { .index = 3, .name = "Open Threshold", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, .def = 0.04f, .min = 0.0f, .max = 1.0f }, { .index = 4, .name = "Close Threshold", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, .def = 0.03f, .min = 0.0f, .max = 1.0f }, { .index = 5, .name = "Attack (s)", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, .def = 0.005f, .min = 0.0f, .max = 1.0f }, { .index = 6, .name = "Hold (s)", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, .def = 0.050f, .min = 0.0f, .max = 1.0f }, { .index = 7, .name = "Release (s)", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, .def = 0.010f, .min = 0.0f, .max = 1.0f }, }; static void noisegate_run(void * Instance, unsigned long SampleCount) { struct builtin *impl = Instance; float *in = impl->port[0]; float *out = impl->port[1]; float in_lev = impl->port[2][0]; unsigned long n; float o_thres = impl->port[3][0]; float c_thres = impl->port[4][0]; float gate, hold, o_rate, c_rate, level; int mode; if (out == NULL) return; if (in == NULL) { memset(out, 0, SampleCount * sizeof(float)); return; } o_rate = 1.0f / (impl->port[5][0] * impl->rate); c_rate = 1.0f / (impl->port[7][0] * impl->rate); gate = impl->gate; hold = impl->hold; mode = impl->mode; level = impl->last; spa_log_trace_fp(impl->log, "%f %d %f", level, mode, gate); for (n = 0; n < SampleCount; n++) { if (isnan(in_lev)) { float lev = fabsf(in[n]); if (lev > level) level = lev; else level = lev * 0.05f + level * 0.95f; } else { level = in_lev; } switch (mode) { case 0: /* closed */ if (level >= o_thres) mode = 1; break; case 1: /* opening */ gate += o_rate; if (gate >= 1.0f) { gate = 1.0f; mode = 2; hold = impl->port[6][0] * impl->rate; } break; case 2: /* hold */ hold -= 1.0f; if (hold <= 0.0f) mode = 3; break; case 3: /* open */ if (level < c_thres) mode = 4; break; case 4: /* closing */ gate -= c_rate; if (level >= o_thres) mode = 1; else if (gate <= 0.0f) { gate = 0.0f; mode = 0; } break; } out[n] = in[n] * gate; } impl->gate = gate; impl->hold = hold; impl->mode = mode; impl->last = level; } static const struct spa_fga_descriptor noisegate_desc = { .name = "noisegate", .flags = SPA_FGA_DESCRIPTOR_SUPPORTS_NULL_DATA, .n_ports = SPA_N_ELEMENTS(noisegate_ports), .ports = noisegate_ports, .instantiate = builtin_instantiate, .connect_port = builtin_connect_port, .run = noisegate_run, .cleanup = builtin_cleanup, }; static const struct spa_fga_descriptor * builtin_descriptor(unsigned long Index) { switch(Index) { case 0: return &mixer_desc; case 1: return &bq_lowpass_desc; case 2: return &bq_highpass_desc; case 3: return &bq_bandpass_desc; case 4: return &bq_lowshelf_desc; case 5: return &bq_highshelf_desc; case 6: return &bq_peaking_desc; case 7: return &bq_notch_desc; case 8: return &bq_allpass_desc; case 9: return ©_desc; case 10: return &convolve_desc; case 11: return &delay_desc; case 12: return &invert_desc; case 13: return &bq_raw_desc; case 14: return &clamp_desc; case 15: return &linear_desc; case 16: return &recip_desc; case 17: return &exp_desc; case 18: return &log_desc; case 19: return &mult_desc; case 20: return &sine_desc; case 21: return ¶m_eq_desc; case 22: return &max_desc; case 23: return &dcblock_desc; case 24: return &ramp_desc; case 25: return &abs_desc; case 26: return &sqrt_desc; case 27: return &debug_desc; case 28: return &pipe_desc; case 29: return &zeroramp_desc; case 30: return &noisegate_desc; } return NULL; } static const struct spa_fga_descriptor *builtin_plugin_make_desc(void *plugin, const char *name) { unsigned long i; for (i = 0; ;i++) { const struct spa_fga_descriptor *d = builtin_descriptor(i); if (d == NULL) break; if (spa_streq(d->name, name)) return d; } return NULL; } static struct spa_fga_plugin_methods impl_plugin = { SPA_VERSION_FGA_PLUGIN_METHODS, .make_desc = builtin_plugin_make_desc, }; static int impl_get_interface(struct spa_handle *handle, const char *type, void **interface) { struct plugin *impl; spa_return_val_if_fail(handle != NULL, -EINVAL); spa_return_val_if_fail(interface != NULL, -EINVAL); impl = (struct plugin *) handle; if (spa_streq(type, SPA_TYPE_INTERFACE_FILTER_GRAPH_AudioPlugin)) *interface = &impl->plugin; else return -ENOENT; return 0; } static int impl_clear(struct spa_handle *handle) { return 0; } static size_t impl_get_size(const struct spa_handle_factory *factory, const struct spa_dict *params) { return sizeof(struct plugin); } static int impl_init(const struct spa_handle_factory *factory, struct spa_handle *handle, const struct spa_dict *info, const struct spa_support *support, uint32_t n_support) { struct plugin *impl; handle->get_interface = impl_get_interface; handle->clear = impl_clear; impl = (struct plugin *) handle; impl->plugin.iface = SPA_INTERFACE_INIT( SPA_TYPE_INTERFACE_FILTER_GRAPH_AudioPlugin, SPA_VERSION_FGA_PLUGIN, &impl_plugin, impl); impl->log = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log); impl->dsp = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_FILTER_GRAPH_AudioDSP); for (uint32_t i = 0; info && i < info->n_items; i++) { const char *k = info->items[i].key; const char *s = info->items[i].value; if (spa_streq(k, "filter.graph.audio.dsp")) sscanf(s, "pointer:%p", &impl->dsp); } if (impl->dsp == NULL) { spa_log_error(impl->log, "%p: could not find DSP functions", impl); return -EINVAL; } return 0; } static const struct spa_interface_info impl_interfaces[] = { {SPA_TYPE_INTERFACE_FILTER_GRAPH_AudioPlugin,}, }; static int impl_enum_interface_info(const struct spa_handle_factory *factory, const struct spa_interface_info **info, uint32_t *index) { spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(info != NULL, -EINVAL); spa_return_val_if_fail(index != NULL, -EINVAL); switch (*index) { case 0: *info = &impl_interfaces[*index]; break; default: return 0; } (*index)++; return 1; } static struct spa_handle_factory spa_fga_plugin_builtin_factory = { SPA_VERSION_HANDLE_FACTORY, "filter.graph.plugin.builtin", NULL, impl_get_size, impl_init, impl_enum_interface_info, }; SPA_EXPORT int spa_handle_factory_enum(const struct spa_handle_factory **factory, uint32_t *index) { spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(index != NULL, -EINVAL); switch (*index) { case 0: *factory = &spa_fga_plugin_builtin_factory; break; default: return 0; } (*index)++; return 1; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/filter-graph/plugin_ebur128.c000066400000000000000000000345451511204443500302530ustar00rootroot00000000000000#include "config.h" #include #include #include #include "audio-plugin.h" #include "audio-dsp.h" #include struct plugin { struct spa_handle handle; struct spa_fga_plugin plugin; struct spa_fga_dsp *dsp; struct spa_log *log; uint32_t quantum_limit; }; enum { PORT_IN_FL, PORT_IN_FR, PORT_IN_FC, PORT_IN_UNUSED, PORT_IN_SL, PORT_IN_SR, PORT_IN_DUAL_MONO, PORT_OUT_FL, PORT_OUT_FR, PORT_OUT_FC, PORT_OUT_UNUSED, PORT_OUT_SL, PORT_OUT_SR, PORT_OUT_DUAL_MONO, PORT_OUT_MOMENTARY, PORT_OUT_SHORTTERM, PORT_OUT_GLOBAL, PORT_OUT_WINDOW, PORT_OUT_RANGE, PORT_OUT_PEAK, PORT_OUT_TRUE_PEAK, PORT_MAX, PORT_IN_START = PORT_IN_FL, PORT_OUT_START = PORT_OUT_FL, PORT_NOTIFY_START = PORT_OUT_MOMENTARY, }; struct ebur128_impl { struct plugin *plugin; struct spa_fga_dsp *dsp; struct spa_log *log; unsigned long rate; float *port[PORT_MAX]; unsigned int max_history; unsigned int max_window; bool use_histogram; ebur128_state *st[7]; }; static void * ebur128_instantiate(const struct spa_fga_plugin *plugin, const struct spa_fga_descriptor * Descriptor, unsigned long SampleRate, int index, const char *config) { struct plugin *pl = SPA_CONTAINER_OF(plugin, struct plugin, plugin); struct ebur128_impl *impl; struct spa_json it[1]; const char *val; char key[256]; int len; float f; impl = calloc(1, sizeof(*impl)); if (impl == NULL) { errno = ENOMEM; return NULL; } impl->plugin = pl; impl->dsp = pl->dsp; impl->log = pl->log; impl->max_history = 10000; impl->max_window = 0; impl->rate = SampleRate; if (config == NULL) return impl; if (spa_json_begin_object(&it[0], config, strlen(config)) <= 0) { spa_log_error(pl->log, "ebur128: expected object in config"); errno = EINVAL; goto error; } while ((len = spa_json_object_next(&it[0], key, sizeof(key), &val)) > 0) { if (spa_streq(key, "max-history")) { if (spa_json_parse_float(val, len, &f) <= 0) { spa_log_error(impl->log, "ebur128:max-history requires a number"); errno = EINVAL; goto error; } impl->max_history = (unsigned int) (f * 1000.0f); } else if (spa_streq(key, "max-window")) { if (spa_json_parse_float(val, len, &f) <= 0) { spa_log_error(impl->log, "ebur128:max-window requires a number"); errno = EINVAL; goto error; } impl->max_window = (unsigned int) (f * 1000.0f); } else if (spa_streq(key, "use-histogram")) { if (spa_json_parse_bool(val, len, &impl->use_histogram) <= 0) { spa_log_error(impl->log, "ebur128:use-histogram requires a boolean"); errno = EINVAL; goto error; } } else { spa_log_warn(impl->log, "ebur128: unknown key %s", key); } } return impl; error: free(impl); return NULL; } static void ebur128_run(void * Instance, unsigned long SampleCount) { struct ebur128_impl *impl = Instance; int i, c; double value; ebur128_state *st[7]; for (i = 0; i < 7; i++) { float *in = impl->port[PORT_IN_START + i]; float *out = impl->port[PORT_OUT_START + i]; st[i] = NULL; if (in == NULL) continue; st[i] = impl->st[i]; if (st[i] != NULL) ebur128_add_frames_float(st[i], in, SampleCount); if (out != NULL) spa_memcpy(out, in, SampleCount * sizeof(float)); } if (impl->port[PORT_OUT_MOMENTARY] != NULL) { double sum = 0.0; for (i = 0, c = 0; i < 7; i++) { if (st[i] != NULL) { ebur128_loudness_momentary(st[i], &value); sum += value; c++; } } impl->port[PORT_OUT_MOMENTARY][0] = (float) (sum / c); } if (impl->port[PORT_OUT_SHORTTERM] != NULL) { double sum = 0.0; for (i = 0, c = 0; i < 7; i++) { if (st[i] != NULL) { ebur128_loudness_shortterm(st[i], &value); sum += value; c++; } } impl->port[PORT_OUT_SHORTTERM][0] = (float) (sum / c); } if (impl->port[PORT_OUT_GLOBAL] != NULL) { ebur128_loudness_global_multiple(st, 7, &value); impl->port[PORT_OUT_GLOBAL][0] = (float)value; } if (impl->port[PORT_OUT_WINDOW] != NULL) { double sum = 0.0; for (i = 0, c = 0; i < 7; i++) { if (st[i] != NULL) { ebur128_loudness_window(st[i], impl->max_window, &value); sum += value; c++; } } impl->port[PORT_OUT_WINDOW][0] = (float) (sum / c); } if (impl->port[PORT_OUT_RANGE] != NULL) { ebur128_loudness_range_multiple(st, 7, &value); impl->port[PORT_OUT_RANGE][0] = (float)value; } if (impl->port[PORT_OUT_PEAK] != NULL) { double max = 0.0; for (i = 0; i < 7; i++) { if (st[i] != NULL) { ebur128_sample_peak(st[i], i, &value); max = SPA_MAX(max, value); } } impl->port[PORT_OUT_PEAK][0] = (float) max; } if (impl->port[PORT_OUT_TRUE_PEAK] != NULL) { double max = 0.0; for (i = 0; i < 7; i++) { if (st[i] != NULL) { ebur128_true_peak(st[i], i, &value); max = SPA_MAX(max, value); } } impl->port[PORT_OUT_TRUE_PEAK][0] = (float) max; } } static void ebur128_connect_port(void * Instance, unsigned long Port, void * DataLocation) { struct ebur128_impl *impl = Instance; impl->port[Port] = DataLocation; } static void ebur128_cleanup(void * Instance) { struct ebur128_impl *impl = Instance; free(impl); } static void ebur128_activate(void * Instance) { struct ebur128_impl *impl = Instance; unsigned long max_window; int major, minor, patch; int mode = 0, i; int modes[] = { EBUR128_MODE_M, EBUR128_MODE_S, EBUR128_MODE_I, 0, EBUR128_MODE_LRA, EBUR128_MODE_SAMPLE_PEAK, EBUR128_MODE_TRUE_PEAK, }; enum channel channels[] = { EBUR128_LEFT, EBUR128_RIGHT, EBUR128_CENTER, EBUR128_UNUSED, EBUR128_LEFT_SURROUND, EBUR128_RIGHT_SURROUND, EBUR128_DUAL_MONO, }; if (impl->use_histogram) mode |= EBUR128_MODE_HISTOGRAM; /* check modes */ for (i = 0; i < 7; i++) { if (impl->port[PORT_NOTIFY_START + i] != NULL) mode |= modes[i]; } ebur128_get_version(&major, &minor, &patch); max_window = impl->max_window; if (major == 1 && minor == 2 && (patch == 5 || patch == 6)) max_window = (max_window + 999) / 1000; for (i = 0; i < 7; i++) { impl->st[i] = ebur128_init(1, impl->rate, mode); if (impl->st[i]) { ebur128_set_channel(impl->st[i], i, channels[i]); ebur128_set_max_history(impl->st[i], impl->max_history); ebur128_set_max_window(impl->st[i], max_window); } } } static void ebur128_deactivate(void * Instance) { struct ebur128_impl *impl = Instance; int i; for (i = 0; i < 7; i++) { if (impl->st[i] != NULL) ebur128_destroy(&impl->st[i]); } } static struct spa_fga_port ebur128_ports[] = { { .index = PORT_IN_FL, .name = "In FL", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, }, { .index = PORT_IN_FR, .name = "In FR", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, }, { .index = PORT_IN_FC, .name = "In FC", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, }, { .index = PORT_IN_UNUSED, .name = "In UNUSED", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, }, { .index = PORT_IN_SL, .name = "In SL", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, }, { .index = PORT_IN_SR, .name = "In SR", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, }, { .index = PORT_IN_DUAL_MONO, .name = "In DUAL MONO", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, }, { .index = PORT_OUT_FL, .name = "Out FL", .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_AUDIO, }, { .index = PORT_OUT_FR, .name = "Out FR", .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_AUDIO, }, { .index = PORT_OUT_FC, .name = "Out FC", .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_AUDIO, }, { .index = PORT_OUT_UNUSED, .name = "Out UNUSED", .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_AUDIO, }, { .index = PORT_OUT_SL, .name = "Out SL", .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_AUDIO, }, { .index = PORT_OUT_SR, .name = "Out SR", .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_AUDIO, }, { .index = PORT_OUT_DUAL_MONO, .name = "Out DUAL MONO", .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_AUDIO, }, { .index = PORT_OUT_MOMENTARY, .name = "Momentary LUFS", .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_CONTROL, }, { .index = PORT_OUT_SHORTTERM, .name = "Shortterm LUFS", .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_CONTROL, }, { .index = PORT_OUT_GLOBAL, .name = "Global LUFS", .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_CONTROL, }, { .index = PORT_OUT_WINDOW, .name = "Window LUFS", .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_CONTROL, }, { .index = PORT_OUT_RANGE, .name = "Range LU", .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_CONTROL, }, { .index = PORT_OUT_PEAK, .name = "Peak", .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_CONTROL, }, { .index = PORT_OUT_TRUE_PEAK, .name = "True Peak", .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_CONTROL, }, }; static const struct spa_fga_descriptor ebur128_desc = { .name = "ebur128", .flags = SPA_FGA_DESCRIPTOR_SUPPORTS_NULL_DATA, .ports = ebur128_ports, .n_ports = SPA_N_ELEMENTS(ebur128_ports), .instantiate = ebur128_instantiate, .connect_port = ebur128_connect_port, .activate = ebur128_activate, .deactivate = ebur128_deactivate, .run = ebur128_run, .cleanup = ebur128_cleanup, }; static struct spa_fga_port lufs2gain_ports[] = { { .index = 0, .name = "LUFS", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, }, { .index = 1, .name = "Gain", .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_CONTROL, }, { .index = 2, .name = "Target LUFS", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, .def = -23.0f, .min = -70.0f, .max = 0.0f }, }; struct lufs2gain_impl { struct plugin *plugin; struct spa_fga_dsp *dsp; struct spa_log *log; unsigned long rate; float *port[3]; }; static void * lufs2gain_instantiate(const struct spa_fga_plugin *plugin, const struct spa_fga_descriptor * Descriptor, unsigned long SampleRate, int index, const char *config) { struct plugin *pl = SPA_CONTAINER_OF(plugin, struct plugin, plugin); struct lufs2gain_impl *impl; impl = calloc(1, sizeof(*impl)); if (impl == NULL) { errno = ENOMEM; return NULL; } impl->plugin = pl; impl->dsp = pl->dsp; impl->log = pl->log; impl->rate = SampleRate; return impl; } static void lufs2gain_connect_port(void * Instance, unsigned long Port, void * DataLocation) { struct lufs2gain_impl *impl = Instance; impl->port[Port] = DataLocation; } static void lufs2gain_run(void * Instance, unsigned long SampleCount) { struct lufs2gain_impl *impl = Instance; float *in = impl->port[0]; float *out = impl->port[1]; float *target = impl->port[2]; float gain; if (in == NULL || out == NULL || target == NULL) return; if (isfinite(in[0])) { float gaindB = target[0] - in[0]; gain = powf(10.0f, gaindB / 20.0f); } else { gain = 1.0f; } out[0] = gain; } static void lufs2gain_cleanup(void * Instance) { struct lufs2gain_impl *impl = Instance; free(impl); } static const struct spa_fga_descriptor lufs2gain_desc = { .name = "lufs2gain", .flags = SPA_FGA_DESCRIPTOR_SUPPORTS_NULL_DATA, .ports = lufs2gain_ports, .n_ports = SPA_N_ELEMENTS(lufs2gain_ports), .instantiate = lufs2gain_instantiate, .connect_port = lufs2gain_connect_port, .run = lufs2gain_run, .cleanup = lufs2gain_cleanup, }; static const struct spa_fga_descriptor * ebur128_descriptor(unsigned long Index) { switch(Index) { case 0: return &ebur128_desc; case 1: return &lufs2gain_desc; } return NULL; } static const struct spa_fga_descriptor *ebur128_plugin_make_desc(void *plugin, const char *name) { unsigned long i; for (i = 0; ;i++) { const struct spa_fga_descriptor *d = ebur128_descriptor(i); if (d == NULL) break; if (spa_streq(d->name, name)) return d; } return NULL; } static struct spa_fga_plugin_methods impl_plugin = { SPA_VERSION_FGA_PLUGIN_METHODS, .make_desc = ebur128_plugin_make_desc, }; static int impl_get_interface(struct spa_handle *handle, const char *type, void **interface) { struct plugin *impl; spa_return_val_if_fail(handle != NULL, -EINVAL); spa_return_val_if_fail(interface != NULL, -EINVAL); impl = (struct plugin *) handle; if (spa_streq(type, SPA_TYPE_INTERFACE_FILTER_GRAPH_AudioPlugin)) *interface = &impl->plugin; else return -ENOENT; return 0; } static int impl_clear(struct spa_handle *handle) { return 0; } static size_t impl_get_size(const struct spa_handle_factory *factory, const struct spa_dict *params) { return sizeof(struct plugin); } static int impl_init(const struct spa_handle_factory *factory, struct spa_handle *handle, const struct spa_dict *info, const struct spa_support *support, uint32_t n_support) { struct plugin *impl; handle->get_interface = impl_get_interface; handle->clear = impl_clear; impl = (struct plugin *) handle; impl->plugin.iface = SPA_INTERFACE_INIT( SPA_TYPE_INTERFACE_FILTER_GRAPH_AudioPlugin, SPA_VERSION_FGA_PLUGIN, &impl_plugin, impl); impl->quantum_limit = 8192u; impl->log = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log); impl->dsp = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_FILTER_GRAPH_AudioDSP); for (uint32_t i = 0; info && i < info->n_items; i++) { const char *k = info->items[i].key; const char *s = info->items[i].value; if (spa_streq(k, "clock.quantum-limit")) spa_atou32(s, &impl->quantum_limit, 0); if (spa_streq(k, "filter.graph.audio.dsp")) sscanf(s, "pointer:%p", &impl->dsp); } if (impl->dsp == NULL) { spa_log_error(impl->log, "%p: could not find DSP functions", impl); return -EINVAL; } return 0; } static const struct spa_interface_info impl_interfaces[] = { {SPA_TYPE_INTERFACE_FILTER_GRAPH_AudioPlugin,}, }; static int impl_enum_interface_info(const struct spa_handle_factory *factory, const struct spa_interface_info **info, uint32_t *index) { spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(info != NULL, -EINVAL); spa_return_val_if_fail(index != NULL, -EINVAL); switch (*index) { case 0: *info = &impl_interfaces[*index]; break; default: return 0; } (*index)++; return 1; } static struct spa_handle_factory spa_fga_ebur128_plugin_factory = { SPA_VERSION_HANDLE_FACTORY, "filter.graph.plugin.ebur128", NULL, impl_get_size, impl_init, impl_enum_interface_info, }; SPA_EXPORT int spa_handle_factory_enum(const struct spa_handle_factory **factory, uint32_t *index) { spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(index != NULL, -EINVAL); switch (*index) { case 0: *factory = &spa_fga_ebur128_plugin_factory; break; default: return 0; } (*index)++; return 1; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/filter-graph/plugin_ffmpeg.c000066400000000000000000000301671511204443500303230ustar00rootroot00000000000000/* PipeWire */ /* SPDX-FileCopyrightText: Copyright © 2021 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "audio-plugin.h" #define MAX_PORTS 256 #define MAX_CTX 64 struct plugin { struct spa_handle handle; struct spa_fga_plugin plugin; struct spa_log *log; }; struct descriptor { struct spa_fga_descriptor desc; struct plugin *p; AVFilterGraph *filter_graph; const AVFilter *format; const AVFilter *buffersrc; const AVFilter *buffersink; AVChannelLayout layout[MAX_CTX]; uint32_t latency_idx; }; struct instance { struct descriptor *desc; AVFilterGraph *filter_graph; uint32_t rate; AVFrame *frame; AVFilterContext *ctx[MAX_CTX]; uint32_t n_ctx; uint32_t n_src; uint32_t n_sink; uint64_t frame_num; float *data[MAX_PORTS]; }; static void layout_from_name(AVChannelLayout *layout, const char *name) { const char *chan; if ((chan = strrchr(name, '_')) != NULL) chan++; else chan = "FC"; if (av_channel_layout_from_string(layout, chan) < 0) av_channel_layout_from_string(layout, "FC"); } static void *ffmpeg_instantiate(const struct spa_fga_plugin *plugin, const struct spa_fga_descriptor *desc, unsigned long SampleRate, int index, const char *config) { struct descriptor *d = (struct descriptor *)desc; struct plugin *p = d->p; struct instance *i; AVFilterInOut *fp, *in, *out; AVFilterContext *cnv, *ctx; int res; char channel[512]; char options_str[1024]; uint32_t n_fp; i = calloc(1, sizeof(*i)); if (i == NULL) return NULL; i->desc = d; i->rate = SampleRate; i->filter_graph = avfilter_graph_alloc(); if (i->filter_graph == NULL) { errno = ENOMEM; return NULL; } res = avfilter_graph_parse2(i->filter_graph, d->desc.name, &in, &out); if (res < 0) { spa_log_error(p->log, "can parse filter graph %s", d->desc.name); errno = EINVAL; return NULL; } for (n_fp = 0, fp = in; fp != NULL; fp = fp->next, n_fp++) { ctx = avfilter_graph_alloc_filter(i->filter_graph, d->buffersrc, "src"); if (ctx == NULL) { spa_log_error(p->log, "can't alloc buffersrc"); return NULL; } av_channel_layout_describe(&d->layout[n_fp], channel, sizeof(channel)); snprintf(options_str, sizeof(options_str), "sample_fmt=%s:sample_rate=%ld:channel_layout=%s", av_get_sample_fmt_name(AV_SAMPLE_FMT_FLTP), SampleRate, channel); spa_log_info(p->log, "%d buffersrc %s", n_fp, options_str); avfilter_init_str(ctx, options_str); avfilter_link(ctx, 0, fp->filter_ctx, fp->pad_idx); i->ctx[n_fp] = ctx; } i->n_src = n_fp; for (fp = out; fp != NULL; fp = fp->next, n_fp++) { cnv = avfilter_graph_alloc_filter(i->filter_graph, d->format, "format"); if (cnv == NULL) { spa_log_error(p->log, "can't alloc format"); return NULL; } av_channel_layout_describe(&d->layout[n_fp], channel, sizeof(channel)); snprintf(options_str, sizeof(options_str), "sample_fmts=%s:sample_rates=%ld:channel_layouts=%s", av_get_sample_fmt_name(AV_SAMPLE_FMT_FLTP), SampleRate, channel); spa_log_info(p->log, "%d format %s", n_fp, options_str); avfilter_init_str(cnv, options_str); avfilter_link(fp->filter_ctx, fp->pad_idx, cnv, 0); ctx = avfilter_graph_alloc_filter(i->filter_graph, d->buffersink, "sink"); if (ctx == NULL) { spa_log_error(p->log, "can't alloc buffersink"); return NULL; } avfilter_init_str(ctx, NULL); avfilter_link(cnv, 0, ctx, 0); i->ctx[n_fp] = ctx; } i->n_sink = n_fp; avfilter_graph_config(i->filter_graph, NULL); i->frame = av_frame_alloc(); #if 0 char *dump = avfilter_graph_dump(i->filter_graph, NULL); spa_log_debug(p->log, "%s", dump); free(dump); #endif return i; } static void ffmpeg_cleanup(void *instance) { struct instance *i = instance; avfilter_graph_free(&i->filter_graph); av_frame_free(&i->frame); free(i); } static void ffmpeg_free(const struct spa_fga_descriptor *desc) { struct descriptor *d = (struct descriptor*)desc; uint32_t i; avfilter_graph_free(&d->filter_graph); for (i = 0; i < d->desc.n_ports; i++) free((void*)d->desc.ports[i].name); free((char*)d->desc.name); free(d->desc.ports); free(d); } static void ffmpeg_connect_port(void *instance, unsigned long port, void *data) { struct instance *i = instance; i->data[port] = data; } static void ffmpeg_run(void *instance, unsigned long SampleCount) { struct instance *i = instance; struct descriptor *desc = i->desc; char buf[1024]; int err, j; uint32_t c, d = 0; float delay; spa_log_trace(i->desc->p->log, "run %ld", SampleCount); for (c = 0; c < i->n_src; c++) { i->frame->nb_samples = SampleCount; i->frame->sample_rate = i->rate; i->frame->format = AV_SAMPLE_FMT_FLTP; i->frame->pts = i->frame_num; av_channel_layout_copy(&i->frame->ch_layout, &desc->layout[c]); for (j = 0; j < desc->layout[c].nb_channels; j++) i->frame->data[j] = (uint8_t*)i->data[d++]; if ((err = av_buffersrc_add_frame_flags(i->ctx[c], i->frame, AV_BUFFERSRC_FLAG_NO_CHECK_FORMAT)) < 0) { av_strerror(err, buf, sizeof(buf)); spa_log_warn(i->desc->p->log, "can't add frame: %s", buf); av_frame_unref(i->frame); continue; } } delay = 0.0f; for (; c < i->n_sink; c++) { if ((err = av_buffersink_get_samples(i->ctx[c], i->frame, SampleCount)) < 0) { av_strerror(err, buf, sizeof(buf)); spa_log_debug(i->desc->p->log, "can't get frame: %s", buf); for (j = 0; j < desc->layout[c].nb_channels; j++) memset(i->data[d++], 0, SampleCount * sizeof(float)); continue; } delay = fmaxf(delay, i->frame_num - i->frame->pts); spa_log_trace(i->desc->p->log, "got frame %d %d %d %s %f", i->frame->nb_samples, i->frame->ch_layout.nb_channels, i->frame->sample_rate, av_get_sample_fmt_name(i->frame->format), delay); for (j = 0; j < desc->layout[c].nb_channels; j++) memcpy(i->data[d++], i->frame->data[j], SampleCount * sizeof(float)); av_frame_unref(i->frame); } i->frame_num += SampleCount; if (i->data[desc->latency_idx] != NULL) i->data[desc->latency_idx][0] = delay; } static const struct spa_fga_descriptor *ffmpeg_plugin_make_desc(void *plugin, const char *name) { struct plugin *p = (struct plugin *)plugin; struct descriptor *desc; uint32_t n_fp, n_p; AVFilterInOut *in = NULL, *out = NULL, *fp; int res, j; spa_log_info(p->log, "%s", name); desc = calloc(1, sizeof(*desc)); if (desc == NULL) return NULL; desc->p = p; desc->filter_graph = avfilter_graph_alloc(); if (desc->filter_graph == NULL) { errno = ENOMEM; return NULL; } res = avfilter_graph_parse2(desc->filter_graph, name, &in, &out); if (res < 0) { spa_log_error(p->log, "can parse filter graph %s", name); errno = EINVAL; return NULL; } desc->desc.n_ports = 0; for (n_fp = 0, fp = in; fp != NULL && n_fp < MAX_CTX; fp = fp->next, n_fp++) { layout_from_name(&desc->layout[n_fp], fp->name); spa_log_info(p->log, "%p: in %s %p:%d channels:%d", fp, fp->name, fp->filter_ctx, fp->pad_idx, desc->layout[n_fp].nb_channels); desc->desc.n_ports += desc->layout[n_fp].nb_channels; } for (fp = out; fp != NULL && n_fp < MAX_CTX; fp = fp->next, n_fp++) { layout_from_name(&desc->layout[n_fp], fp->name); spa_log_info(p->log, "%p: out %s %p:%d channels:%d", fp, fp->name, fp->filter_ctx, fp->pad_idx, desc->layout[n_fp].nb_channels); desc->desc.n_ports += desc->layout[n_fp].nb_channels; } /* one for the latency */ desc->desc.n_ports++; if (n_fp >= MAX_CTX) { spa_log_error(p->log, "%p: too many in/out ports %d > %d", desc, n_fp, MAX_CTX); errno = ENOSPC; return NULL; } if (desc->desc.n_ports >= MAX_PORTS) { spa_log_error(p->log, "%p: too many ports %d > %d", desc, desc->desc.n_ports, MAX_PORTS); errno = ENOSPC; return NULL; } desc->desc.instantiate = ffmpeg_instantiate; desc->desc.cleanup = ffmpeg_cleanup; desc->desc.free = ffmpeg_free; desc->desc.connect_port = ffmpeg_connect_port; desc->desc.run = ffmpeg_run; desc->desc.name = strdup(name); desc->desc.flags = 0; desc->desc.ports = calloc(desc->desc.n_ports, sizeof(struct spa_fga_port)); for (n_fp = 0, n_p = 0, fp = in; fp != NULL; fp = fp->next, n_fp++) { for (j = 0; j < desc->layout[n_fp].nb_channels; j++, n_p++) { desc->desc.ports[n_p].index = n_p; if (desc->layout[n_fp].nb_channels == 1) desc->desc.ports[n_p].name = spa_aprintf("%s", fp->name); else desc->desc.ports[n_p].name = spa_aprintf("%s_%d", fp->name, j); desc->desc.ports[n_p].flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO; } } for (fp = out; fp != NULL; fp = fp->next, n_fp++) { for (j = 0; j < desc->layout[n_fp].nb_channels; j++, n_p++) { desc->desc.ports[n_p].index = n_p; if (desc->layout[n_fp].nb_channels == 1) desc->desc.ports[n_p].name = spa_aprintf("%s", fp->name); else desc->desc.ports[n_p].name = spa_aprintf("%s_%d", fp->name, j); desc->desc.ports[n_p].flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_AUDIO; } } desc->desc.ports[n_p].index = n_p; desc->desc.ports[n_p].name = strdup("latency"); desc->desc.ports[n_p].flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_CONTROL; desc->desc.ports[n_p].hint = SPA_FGA_HINT_LATENCY; desc->latency_idx = n_p++; desc->buffersrc = avfilter_get_by_name("abuffer"); desc->buffersink = avfilter_get_by_name("abuffersink"); desc->format = avfilter_get_by_name("aformat"); return &desc->desc; } static struct spa_fga_plugin_methods impl_plugin = { SPA_VERSION_FGA_PLUGIN_METHODS, .make_desc = ffmpeg_plugin_make_desc, }; static int impl_get_interface(struct spa_handle *handle, const char *type, void **interface) { struct plugin *impl; spa_return_val_if_fail(handle != NULL, -EINVAL); spa_return_val_if_fail(interface != NULL, -EINVAL); impl = (struct plugin *) handle; if (spa_streq(type, SPA_TYPE_INTERFACE_FILTER_GRAPH_AudioPlugin)) *interface = &impl->plugin; else return -ENOENT; return 0; } static int impl_clear(struct spa_handle *handle) { return 0; } static size_t impl_get_size(const struct spa_handle_factory *factory, const struct spa_dict *params) { return sizeof(struct plugin); } static int impl_init(const struct spa_handle_factory *factory, struct spa_handle *handle, const struct spa_dict *info, const struct spa_support *support, uint32_t n_support) { struct plugin *impl; uint32_t i; const char *path = NULL; handle->get_interface = impl_get_interface; handle->clear = impl_clear; impl = (struct plugin *) handle; impl->log = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log); for (i = 0; info && i < info->n_items; i++) { const char *k = info->items[i].key; const char *s = info->items[i].value; if (spa_streq(k, "filter.graph.path")) path = s; } if (!spa_streq(path, "filtergraph")) return -EINVAL; impl->plugin.iface = SPA_INTERFACE_INIT( SPA_TYPE_INTERFACE_FILTER_GRAPH_AudioPlugin, SPA_VERSION_FGA_PLUGIN, &impl_plugin, impl); return 0; } static const struct spa_interface_info impl_interfaces[] = { { SPA_TYPE_INTERFACE_FILTER_GRAPH_AudioPlugin }, }; static int impl_enum_interface_info(const struct spa_handle_factory *factory, const struct spa_interface_info **info, uint32_t *index) { spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(info != NULL, -EINVAL); spa_return_val_if_fail(index != NULL, -EINVAL); switch (*index) { case 0: *info = &impl_interfaces[*index]; break; default: return 0; } (*index)++; return 1; } static struct spa_handle_factory spa_fga_plugin_ffmpeg_factory = { SPA_VERSION_HANDLE_FACTORY, "filter.graph.plugin.ffmpeg", NULL, impl_get_size, impl_init, impl_enum_interface_info, }; SPA_EXPORT int spa_handle_factory_enum(const struct spa_handle_factory **factory, uint32_t *index) { spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(index != NULL, -EINVAL); switch (*index) { case 0: *factory = &spa_fga_plugin_ffmpeg_factory; break; default: return 0; } (*index)++; return 1; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/filter-graph/plugin_ladspa.c000066400000000000000000000222371511204443500303220ustar00rootroot00000000000000/* PipeWire */ /* SPDX-FileCopyrightText: Copyright © 2021 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include "config.h" #include #include #include #include #include #include #include #include #include "audio-plugin.h" #include "ladspa.h" struct plugin { struct spa_handle handle; struct spa_fga_plugin plugin; struct spa_log *log; void *hndl; LADSPA_Descriptor_Function desc_func; }; struct descriptor { struct spa_fga_descriptor desc; const LADSPA_Descriptor *d; }; static void *ladspa_instantiate(const struct spa_fga_plugin *plugin, const struct spa_fga_descriptor *desc, unsigned long SampleRate, int index, const char *config) { struct descriptor *d = (struct descriptor *)desc; return d->d->instantiate(d->d, SampleRate); } static const LADSPA_Descriptor *find_desc(LADSPA_Descriptor_Function desc_func, const char *name) { unsigned long i; for (i = 0; ;i++) { const LADSPA_Descriptor *d = desc_func(i); if (d == NULL) break; if (spa_streq(d->Label, name)) return d; } return NULL; } static float get_default(struct spa_fga_port *port, LADSPA_PortRangeHintDescriptor hint, LADSPA_Data lower, LADSPA_Data upper) { LADSPA_Data def; switch (hint & LADSPA_HINT_DEFAULT_MASK) { case LADSPA_HINT_DEFAULT_MINIMUM: def = lower; break; case LADSPA_HINT_DEFAULT_MAXIMUM: def = upper; break; case LADSPA_HINT_DEFAULT_LOW: if (LADSPA_IS_HINT_LOGARITHMIC(hint)) def = (LADSPA_Data) expf(logf(lower) * 0.75f + logf(upper) * 0.25f); else def = (LADSPA_Data) (lower * 0.75f + upper * 0.25f); break; case LADSPA_HINT_DEFAULT_MIDDLE: if (LADSPA_IS_HINT_LOGARITHMIC(hint)) def = (LADSPA_Data) expf(logf(lower) * 0.5f + logf(upper) * 0.5f); else def = (LADSPA_Data) (lower * 0.5f + upper * 0.5f); break; case LADSPA_HINT_DEFAULT_HIGH: if (LADSPA_IS_HINT_LOGARITHMIC(hint)) def = (LADSPA_Data) expf(logf(lower) * 0.25f + logf(upper) * 0.75f); else def = (LADSPA_Data) (lower * 0.25f + upper * 0.75f); break; case LADSPA_HINT_DEFAULT_0: def = 0.0f; break; case LADSPA_HINT_DEFAULT_1: def = 1.0f; break; case LADSPA_HINT_DEFAULT_100: def = 100.0f; break; case LADSPA_HINT_DEFAULT_440: def = 440.0f; break; default: if (upper == lower) def = upper; else def = SPA_CLAMPF(0.5f * upper, lower, upper); break; } if (LADSPA_IS_HINT_INTEGER(hint)) def = roundf(def); return def; } static void ladspa_port_update_ranges(struct descriptor *dd, struct spa_fga_port *port) { const LADSPA_Descriptor *d = dd->d; unsigned long p = port->index; LADSPA_PortRangeHintDescriptor hint = d->PortRangeHints[p].HintDescriptor; LADSPA_Data lower, upper; lower = d->PortRangeHints[p].LowerBound; upper = d->PortRangeHints[p].UpperBound; port->hint = 0; if (hint & LADSPA_HINT_TOGGLED) port->hint |= SPA_FGA_HINT_BOOLEAN; if (hint & LADSPA_HINT_SAMPLE_RATE) port->hint |= SPA_FGA_HINT_SAMPLE_RATE; if (hint & LADSPA_HINT_INTEGER) port->hint |= SPA_FGA_HINT_INTEGER; if (spa_streq(port->name, "latency")) port->hint |= SPA_FGA_HINT_LATENCY; port->def = get_default(port, hint, lower, upper); port->min = lower; port->max = upper; } static void ladspa_free(const struct spa_fga_descriptor *desc) { struct descriptor *d = (struct descriptor*)desc; free(d->desc.ports); free(d); } static const struct spa_fga_descriptor *ladspa_plugin_make_desc(void *plugin, const char *name) { struct plugin *p = (struct plugin *)plugin; struct descriptor *desc; const LADSPA_Descriptor *d; uint32_t i; d = find_desc(p->desc_func, name); if (d == NULL) return NULL; desc = calloc(1, sizeof(*desc)); desc->d = d; desc->desc.instantiate = ladspa_instantiate; desc->desc.cleanup = d->cleanup; desc->desc.connect_port = (__typeof__(desc->desc.connect_port))d->connect_port; desc->desc.activate = d->activate; desc->desc.deactivate = d->deactivate; desc->desc.run = d->run; desc->desc.free = ladspa_free; desc->desc.name = d->Label; desc->desc.flags = 0; desc->desc.n_ports = d->PortCount; desc->desc.ports = calloc(desc->desc.n_ports, sizeof(struct spa_fga_port)); for (i = 0; i < desc->desc.n_ports; i++) { desc->desc.ports[i].index = i; desc->desc.ports[i].name = d->PortNames[i]; desc->desc.ports[i].flags = d->PortDescriptors[i]; ladspa_port_update_ranges(desc, &desc->desc.ports[i]); } return &desc->desc; } static struct spa_fga_plugin_methods impl_plugin = { SPA_VERSION_FGA_PLUGIN_METHODS, .make_desc = ladspa_plugin_make_desc, }; static int ladspa_handle_load_by_path(struct plugin *impl, const char *path) { int res; void *handle = NULL; LADSPA_Descriptor_Function desc_func; handle = dlopen(path, RTLD_NOW); if (handle == NULL) { spa_log_debug(impl->log, "failed to open '%s': %s", path, dlerror()); res = -ENOENT; goto exit; } spa_log_info(impl->log, "successfully opened '%s'", path); desc_func = (LADSPA_Descriptor_Function) dlsym(handle, "ladspa_descriptor"); if (desc_func == NULL) { spa_log_warn(impl->log, "cannot find descriptor function in '%s': %s", path, dlerror()); res = -ENOSYS; goto exit; } impl->hndl = handle; impl->desc_func = desc_func; return 0; exit: if (handle) dlclose(handle); return res; } static inline const char *split_walk(const char *str, const char *delimiter, size_t * len, const char **state) { const char *s = *state ? *state : str; s += strspn(s, delimiter); if (*s == '\0') return NULL; *len = strcspn(s, delimiter); *state = s + *len; return s; } static int load_ladspa_plugin(struct plugin *impl, const char *path) { int res = -ENOENT; if (path[0] != '/') { const char *search_dirs, *p, *state = NULL; char filename[PATH_MAX]; size_t len; search_dirs = getenv("LADSPA_PATH"); if (!search_dirs) search_dirs = "/usr/lib64/ladspa:/usr/lib/ladspa:" LIBDIR; /* * set the errno for the case when `ladspa_handle_load_by_path()` * is never called, which can only happen if the supplied * LADSPA_PATH contains too long paths */ res = -ENAMETOOLONG; while ((p = split_walk(search_dirs, ":", &len, &state))) { int namelen; if (len >= sizeof(filename)) continue; namelen = snprintf(filename, sizeof(filename), "%.*s/%s.so", (int) len, p, path); if (namelen < 0 || (size_t) namelen >= sizeof(filename)) continue; res = ladspa_handle_load_by_path(impl, filename); if (res >= 0) break; } } else { res = ladspa_handle_load_by_path(impl, path); } return res; } static int impl_get_interface(struct spa_handle *handle, const char *type, void **interface) { struct plugin *impl; spa_return_val_if_fail(handle != NULL, -EINVAL); spa_return_val_if_fail(interface != NULL, -EINVAL); impl = (struct plugin *) handle; if (spa_streq(type, SPA_TYPE_INTERFACE_FILTER_GRAPH_AudioPlugin)) *interface = &impl->plugin; else return -ENOENT; return 0; } static int impl_clear(struct spa_handle *handle) { struct plugin *impl = (struct plugin *)handle; if (impl->hndl) dlclose(impl->hndl); impl->hndl = NULL; return 0; } static size_t impl_get_size(const struct spa_handle_factory *factory, const struct spa_dict *params) { return sizeof(struct plugin); } static int impl_init(const struct spa_handle_factory *factory, struct spa_handle *handle, const struct spa_dict *info, const struct spa_support *support, uint32_t n_support) { struct plugin *impl; uint32_t i; int res; const char *path = NULL; handle->get_interface = impl_get_interface; handle->clear = impl_clear; impl = (struct plugin *) handle; impl->log = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log); for (i = 0; info && i < info->n_items; i++) { const char *k = info->items[i].key; const char *s = info->items[i].value; if (spa_streq(k, "filter.graph.path")) path = s; } if (path == NULL) return -EINVAL; if ((res = load_ladspa_plugin(impl, path)) < 0) { spa_log_error(impl->log, "failed to load plugin '%s': %s", path, spa_strerror(res)); return res; } impl->plugin.iface = SPA_INTERFACE_INIT( SPA_TYPE_INTERFACE_FILTER_GRAPH_AudioPlugin, SPA_VERSION_FGA_PLUGIN, &impl_plugin, impl); return 0; } static const struct spa_interface_info impl_interfaces[] = { { SPA_TYPE_INTERFACE_FILTER_GRAPH_AudioPlugin }, }; static int impl_enum_interface_info(const struct spa_handle_factory *factory, const struct spa_interface_info **info, uint32_t *index) { spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(info != NULL, -EINVAL); spa_return_val_if_fail(index != NULL, -EINVAL); switch (*index) { case 0: *info = &impl_interfaces[*index]; break; default: return 0; } (*index)++; return 1; } static struct spa_handle_factory spa_fga_plugin_ladspa_factory = { SPA_VERSION_HANDLE_FACTORY, "filter.graph.plugin.ladspa", NULL, impl_get_size, impl_init, impl_enum_interface_info, }; SPA_EXPORT int spa_handle_factory_enum(const struct spa_handle_factory **factory, uint32_t *index) { spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(index != NULL, -EINVAL); switch (*index) { case 0: *factory = &spa_fga_plugin_ladspa_factory; break; default: return 0; } (*index)++; return 1; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/filter-graph/plugin_lv2.c000066400000000000000000000457251511204443500275700ustar00rootroot00000000000000/* PipeWire */ /* SPDX-FileCopyrightText: Copyright © 2021 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #include #include #include #include #include #if defined __has_include # if __has_include () #include #include #include #include #include #include #include # else #include #include #include #include #include #include #include # endif #endif #include "audio-plugin.h" static struct context *_context; typedef struct URITable { char **data; size_t alloc; size_t len; } URITable; static void uri_table_init(URITable *table) { table->data = NULL; table->len = table->alloc = 0; } static void uri_table_destroy(URITable *table) { size_t i; for (i = 0; i < table->len; i++) free(table->data[i]); free(table->data); uri_table_init(table); } static LV2_URID uri_table_map(LV2_URID_Map_Handle handle, const char *uri) { URITable *table = (URITable*)handle; size_t i; for (i = 0; i < table->len; i++) if (spa_streq(table->data[i], uri)) return i+1; if (table->len == table->alloc) { table->alloc += 64; table->data = realloc(table->data, table->alloc * sizeof(char *)); } table->data[table->len++] = strdup(uri); return table->len; } static const char *uri_table_unmap(LV2_URID_Map_Handle handle, LV2_URID urid) { URITable *table = (URITable*)handle; if (urid > 0 && urid <= table->len) return table->data[urid-1]; return NULL; } struct context { int ref; LilvWorld *world; LilvNode *lv2_InputPort; LilvNode *lv2_OutputPort; LilvNode *lv2_AudioPort; LilvNode *lv2_ControlPort; LilvNode *lv2_Optional; LilvNode *atom_AtomPort; LilvNode *atom_Sequence; LilvNode *urid_map; LilvNode *powerOf2BlockLength; LilvNode *fixedBlockLength; LilvNode *boundedBlockLength; LilvNode* worker_schedule; LilvNode* worker_iface; LilvNode* state_iface; URITable uri_table; LV2_URID_Map map; LV2_Feature map_feature; LV2_URID_Unmap unmap; LV2_Feature unmap_feature; LV2_URID atom_Int; LV2_URID atom_Float; }; #define context_map(c,uri) ((c)->map.map((c)->map.handle,(uri))) static void context_free(struct context *c) { if (c->world) { lilv_node_free(c->worker_schedule); lilv_node_free(c->powerOf2BlockLength); lilv_node_free(c->fixedBlockLength); lilv_node_free(c->boundedBlockLength); lilv_node_free(c->urid_map); lilv_node_free(c->atom_Sequence); lilv_node_free(c->atom_AtomPort); lilv_node_free(c->lv2_Optional); lilv_node_free(c->lv2_ControlPort); lilv_node_free(c->lv2_AudioPort); lilv_node_free(c->lv2_OutputPort); lilv_node_free(c->lv2_InputPort); lilv_world_free(c->world); } uri_table_destroy(&c->uri_table); free(c); } static const LV2_Feature buf_size_features[3] = { { LV2_BUF_SIZE__powerOf2BlockLength, NULL }, { LV2_BUF_SIZE__fixedBlockLength, NULL }, { LV2_BUF_SIZE__boundedBlockLength, NULL }, }; static struct context *context_new(void) { struct context *c; c = calloc(1, sizeof(*c)); if (c == NULL) return NULL; uri_table_init(&c->uri_table); c->world = lilv_world_new(); if (c->world == NULL) goto error; lilv_world_load_all(c->world); c->lv2_InputPort = lilv_new_uri(c->world, LV2_CORE__InputPort); c->lv2_OutputPort = lilv_new_uri(c->world, LV2_CORE__OutputPort); c->lv2_AudioPort = lilv_new_uri(c->world, LV2_CORE__AudioPort); c->lv2_ControlPort = lilv_new_uri(c->world, LV2_CORE__ControlPort); c->lv2_Optional = lilv_new_uri(c->world, LV2_CORE__connectionOptional); c->atom_AtomPort = lilv_new_uri(c->world, LV2_ATOM__AtomPort); c->atom_Sequence = lilv_new_uri(c->world, LV2_ATOM__Sequence); c->urid_map = lilv_new_uri(c->world, LV2_URID__map); c->powerOf2BlockLength = lilv_new_uri(c->world, LV2_BUF_SIZE__powerOf2BlockLength); c->fixedBlockLength = lilv_new_uri(c->world, LV2_BUF_SIZE__fixedBlockLength); c->boundedBlockLength = lilv_new_uri(c->world, LV2_BUF_SIZE__boundedBlockLength); c->worker_schedule = lilv_new_uri(c->world, LV2_WORKER__schedule); c->worker_iface = lilv_new_uri(c->world, LV2_WORKER__interface); c->state_iface = lilv_new_uri(c->world, LV2_STATE__interface); c->map.handle = &c->uri_table; c->map.map = uri_table_map; c->map_feature.URI = LV2_URID__map; c->map_feature.data = &c->map; c->unmap.handle = &c->uri_table; c->unmap.unmap = uri_table_unmap; c->unmap_feature.URI = LV2_URID__unmap; c->unmap_feature.data = &c->unmap; c->atom_Int = context_map(c, LV2_ATOM__Int); c->atom_Float = context_map(c, LV2_ATOM__Float); return c; error: context_free(c); return NULL; } static struct context *context_ref(void) { if (_context == NULL) { _context = context_new(); if (_context == NULL) return NULL; } _context->ref++; return _context; } static void context_unref(struct context *context) { if (--_context->ref == 0) { context_free(_context); _context = NULL; } } struct plugin { struct spa_handle handle; struct spa_fga_plugin plugin; struct spa_log *log; struct spa_loop *data_loop; struct spa_loop *main_loop; struct context *c; const LilvPlugin *p; }; struct descriptor { struct spa_fga_descriptor desc; struct plugin *p; }; struct instance { struct descriptor *desc; struct plugin *p; LilvInstance *instance; LV2_Worker_Schedule work_schedule; LV2_Feature work_schedule_feature; LV2_Log_Log log; LV2_Feature log_feature; LV2_Options_Option options[6]; LV2_Feature options_feature; const LV2_Feature *features[10]; const LV2_Worker_Interface *work_iface; const LV2_State_Interface *state_iface; int32_t block_length; LV2_Atom empty_atom; }; static int do_respond(struct spa_loop *loop, bool async, uint32_t seq, const void *data, size_t size, void *user_data) { struct instance *i = (struct instance*)user_data; i->work_iface->work_response(i->instance->lv2_handle, size, data); return 0; } /** Called by the plugin to respond to non-RT work. */ static LV2_Worker_Status work_respond(LV2_Worker_Respond_Handle handle, uint32_t size, const void *data) { struct instance *i = (struct instance*)handle; spa_loop_invoke(i->p->data_loop, do_respond, 1, data, size, false, i); return LV2_WORKER_SUCCESS; } static int do_schedule(struct spa_loop *loop, bool async, uint32_t seq, const void *data, size_t size, void *user_data) { struct instance *i = (struct instance*)user_data; i->work_iface->work(i->instance->lv2_handle, work_respond, i, size, data); return 0; } /** Called by the plugin to schedule non-RT work. */ static LV2_Worker_Status work_schedule(LV2_Worker_Schedule_Handle handle, uint32_t size, const void *data) { struct instance *i = (struct instance*)handle; spa_loop_invoke(i->p->main_loop, do_schedule, 1, data, size, false, i); return LV2_WORKER_SUCCESS; } struct state_data { struct instance *i; const char *config; char *tmp; }; static const void *state_retrieve_function(LV2_State_Handle handle, uint32_t key, size_t *size, uint32_t *type, uint32_t *flags) { struct state_data *sd = (struct state_data*)handle; struct plugin *p = sd->i->p; struct context *c = p->c; const char *uri = c->unmap.unmap(c->unmap.handle, key), *val; struct spa_json it[3]; char k[strlen(uri)+3]; int len; if (sd->config == NULL) { spa_log_info(p->log, "lv2: restore %d %s without a config", key, uri); return NULL; } if (spa_json_begin_object(&it[0], sd->config, strlen(sd->config)) <= 0) { spa_log_error(p->log, "lv2: config must be an object"); return NULL; } while ((len = spa_json_object_next(&it[0], k, sizeof(k), &val)) > 0) { if (!spa_streq(k, uri)) continue; if (spa_json_is_container(val, len)) if ((len = spa_json_container_len(&it[0], val, len)) <= 0) return NULL; sd->tmp = realloc(sd->tmp, len+1); spa_json_parse_stringn(val, len, sd->tmp, len+1); spa_log_info(p->log, "lv2: restore %d %s %s", key, uri, sd->tmp); if (size) *size = strlen(sd->tmp); if (type) *type = 0; if (flags) *flags = LV2_STATE_IS_POD | LV2_STATE_IS_PORTABLE; return sd->tmp; } spa_log_info(p->log, "lv2: restore %d %s not found in config", key, uri); return NULL; } SPA_PRINTF_FUNC(3, 0) static int log_vprintf(LV2_Log_Handle handle, LV2_URID type, const char* fmt, va_list ap) { struct instance *i = (struct instance*)handle; spa_log_logv(i->p->log, SPA_LOG_LEVEL_INFO, __FILE__,__LINE__,__func__, fmt, ap); return 0; } SPA_PRINTF_FUNC(3, 4) static int log_printf(LV2_Log_Handle handle, LV2_URID type, const char* fmt, ...) { va_list args; int ret; va_start(args, fmt); ret = log_vprintf(handle, type, fmt, args); va_end(args); return ret; } static void *lv2_instantiate(const struct spa_fga_plugin *plugin, const struct spa_fga_descriptor *desc, unsigned long SampleRate, int index, const char *config) { struct descriptor *d = (struct descriptor*)desc; struct plugin *p = d->p; struct context *c = p->c; struct instance *i; uint32_t n, n_features = 0; static const int32_t min_block_length = 1; static const int32_t max_block_length = 8192; static const int32_t seq_size = 32768; float fsample_rate = SampleRate; i = calloc(1, sizeof(*i)); if (i == NULL) return NULL; i->block_length = 1024; i->desc = d; i->p = p; i->log.handle = i; i->log.printf = log_printf; i->log.vprintf = log_vprintf; i->log_feature.URI = LV2_LOG__log; i->log_feature.data = &i->log; i->features[n_features++] = &i->log_feature; i->features[n_features++] = &c->map_feature; i->features[n_features++] = &c->unmap_feature; i->features[n_features++] = &buf_size_features[0]; i->features[n_features++] = &buf_size_features[1]; i->features[n_features++] = &buf_size_features[2]; if (lilv_plugin_has_feature(p->p, c->worker_schedule)) { i->work_schedule.handle = i; i->work_schedule.schedule_work = work_schedule; i->work_schedule_feature.URI = LV2_WORKER__schedule; i->work_schedule_feature.data = &i->work_schedule; i->features[n_features++] = &i->work_schedule_feature; } i->options[0] = (LV2_Options_Option) { LV2_OPTIONS_INSTANCE, 0, context_map(c, LV2_BUF_SIZE__minBlockLength), sizeof(int32_t), c->atom_Int, &min_block_length }; i->options[1] = (LV2_Options_Option) { LV2_OPTIONS_INSTANCE, 0, context_map(c, LV2_BUF_SIZE__maxBlockLength), sizeof(int32_t), c->atom_Int, &max_block_length }; i->options[2] = (LV2_Options_Option) { LV2_OPTIONS_INSTANCE, 0, context_map(c, LV2_BUF_SIZE__sequenceSize), sizeof(int32_t), c->atom_Int, &seq_size }; i->options[3] = (LV2_Options_Option) { LV2_OPTIONS_INSTANCE, 0, context_map(c, "http://lv2plug.in/ns/ext/buf-size#nominalBlockLength"), sizeof(int32_t), c->atom_Int, &i->block_length }, i->options[4] = (LV2_Options_Option) { LV2_OPTIONS_INSTANCE, 0, context_map(c, LV2_PARAMETERS__sampleRate), sizeof(float), c->atom_Float, &fsample_rate }; i->options[5] = (LV2_Options_Option) { LV2_OPTIONS_INSTANCE, 0, 0, 0, 0, NULL }; i->options_feature.URI = LV2_OPTIONS__options; i->options_feature.data = i->options; i->features[n_features++] = &i->options_feature; i->features[n_features++] = NULL; spa_assert(n_features < SPA_N_ELEMENTS(i->features)); i->instance = lilv_plugin_instantiate(p->p, SampleRate, i->features); if (i->instance == NULL) { free(i); return NULL; } if (lilv_plugin_has_extension_data(p->p, c->worker_iface)) { i->work_iface = (const LV2_Worker_Interface*) lilv_instance_get_extension_data(i->instance, LV2_WORKER__interface); } if (lilv_plugin_has_extension_data(p->p, c->state_iface)) { i->state_iface = (const LV2_State_Interface*) lilv_instance_get_extension_data(i->instance, LV2_STATE__interface); } for (n = 0; n < desc->n_ports; n++) { const LilvPort *port = lilv_plugin_get_port_by_index(p->p, n); if (lilv_port_is_a(p->p, port, c->atom_AtomPort)) { lilv_instance_connect_port(i->instance, n, &i->empty_atom); } } if (i->state_iface && i->state_iface->restore) { struct state_data sd = { .i = i, .config = config, .tmp = NULL }; i->state_iface->restore(i->instance->lv2_handle, state_retrieve_function, &sd, 0, i->features); free(sd.tmp); } return i; } static void lv2_cleanup(void *instance) { struct instance *i = instance; spa_loop_invoke(i->p->data_loop, NULL, 0, NULL, 0, true, NULL); spa_loop_invoke(i->p->main_loop, NULL, 0, NULL, 0, true, NULL); lilv_instance_free(i->instance); free(i); } static void lv2_connect_port(void *instance, unsigned long port, void *data) { struct instance *i = instance; lilv_instance_connect_port(i->instance, port, data); } static void lv2_activate(void *instance) { struct instance *i = instance; lilv_instance_activate(i->instance); } static void lv2_deactivate(void *instance) { struct instance *i = instance; lilv_instance_deactivate(i->instance); } static void lv2_run(void *instance, unsigned long SampleCount) { struct instance *i = instance; lilv_instance_run(i->instance, SampleCount); if (i->work_iface != NULL && i->work_iface->end_run != NULL) i->work_iface->end_run(i->instance); } static void lv2_free(const struct spa_fga_descriptor *desc) { struct descriptor *d = (struct descriptor*)desc; uint32_t i; for (i = 0; i < d->desc.n_ports; i++) free((void*)d->desc.ports[i].name); free((char*)d->desc.name); free(d->desc.ports); free(d); } static const struct spa_fga_descriptor *lv2_plugin_make_desc(void *plugin, const char *name) { struct plugin *p = (struct plugin *)plugin; struct context *c = p->c; struct descriptor *desc; uint32_t i; float *mins, *maxes, *controls; bool latent; uint32_t latency_index; desc = calloc(1, sizeof(*desc)); if (desc == NULL) return NULL; desc->p = p; desc->desc.instantiate = lv2_instantiate; desc->desc.cleanup = lv2_cleanup; desc->desc.connect_port = lv2_connect_port; desc->desc.activate = lv2_activate; desc->desc.deactivate = lv2_deactivate; desc->desc.run = lv2_run; desc->desc.free = lv2_free; desc->desc.name = strdup(name); desc->desc.flags = 0; desc->desc.n_ports = lilv_plugin_get_num_ports(p->p); desc->desc.ports = calloc(desc->desc.n_ports, sizeof(struct spa_fga_port)); mins = alloca(desc->desc.n_ports * sizeof(float)); maxes = alloca(desc->desc.n_ports * sizeof(float)); controls = alloca(desc->desc.n_ports * sizeof(float)); latent = lilv_plugin_has_latency(p->p); latency_index = latent ? lilv_plugin_get_latency_port_index(p->p) : 0; lilv_plugin_get_port_ranges_float(p->p, mins, maxes, controls); for (i = 0; i < desc->desc.n_ports; i++) { const LilvPort *port = lilv_plugin_get_port_by_index(p->p, i); const LilvNode *symbol = lilv_port_get_symbol(p->p, port); struct spa_fga_port *fp = &desc->desc.ports[i]; fp->index = i; fp->name = strdup(lilv_node_as_string(symbol)); fp->flags = 0; if (lilv_port_is_a(p->p, port, c->lv2_InputPort)) fp->flags |= SPA_FGA_PORT_INPUT; if (lilv_port_is_a(p->p, port, c->lv2_OutputPort)) fp->flags |= SPA_FGA_PORT_OUTPUT; if (lilv_port_is_a(p->p, port, c->lv2_ControlPort)) fp->flags |= SPA_FGA_PORT_CONTROL; if (lilv_port_is_a(p->p, port, c->lv2_AudioPort)) fp->flags |= SPA_FGA_PORT_AUDIO; if (lilv_port_has_property(p->p, port, c->lv2_Optional)) fp->flags |= SPA_FGA_PORT_SUPPORTS_NULL_DATA; fp->hint = 0; if (latent && latency_index == i) fp->hint |= SPA_FGA_HINT_LATENCY; fp->min = mins[i]; fp->max = maxes[i]; fp->def = controls[i]; } return &desc->desc; } static struct spa_fga_plugin_methods impl_plugin = { SPA_VERSION_FGA_PLUGIN_METHODS, .make_desc = lv2_plugin_make_desc, }; static int impl_get_interface(struct spa_handle *handle, const char *type, void **interface) { struct plugin *impl; spa_return_val_if_fail(handle != NULL, -EINVAL); spa_return_val_if_fail(interface != NULL, -EINVAL); impl = (struct plugin *) handle; if (spa_streq(type, SPA_TYPE_INTERFACE_FILTER_GRAPH_AudioPlugin)) *interface = &impl->plugin; else return -ENOENT; return 0; } static int impl_clear(struct spa_handle *handle) { struct plugin *p = (struct plugin *)handle; context_unref(p->c); return 0; } static size_t impl_get_size(const struct spa_handle_factory *factory, const struct spa_dict *params) { return sizeof(struct plugin); } static int impl_init(const struct spa_handle_factory *factory, struct spa_handle *handle, const struct spa_dict *info, const struct spa_support *support, uint32_t n_support) { struct plugin *impl; uint32_t i; int res; const char *path = NULL; const LilvPlugins *plugins; LilvNode *uri; handle->get_interface = impl_get_interface; handle->clear = impl_clear; impl = (struct plugin *) handle; impl->log = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log); impl->data_loop = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_DataLoop); impl->main_loop = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Loop); for (i = 0; info && i < info->n_items; i++) { const char *k = info->items[i].key; const char *s = info->items[i].value; if (spa_streq(k, "filter.graph.path")) path = s; } if (path == NULL) return -EINVAL; impl->c = context_ref(); if (impl->c == NULL) return -EINVAL; uri = lilv_new_uri(impl->c->world, path); if (uri == NULL) { spa_log_warn(impl->log, "invalid URI %s", path); res = -EINVAL; goto error_cleanup; } plugins = lilv_world_get_all_plugins(impl->c->world); impl->p = lilv_plugins_get_by_uri(plugins, uri); lilv_node_free(uri); if (impl->p == NULL) { spa_log_warn(impl->log, "can't load plugin %s", path); res = -EINVAL; goto error_cleanup; } impl->plugin.iface = SPA_INTERFACE_INIT( SPA_TYPE_INTERFACE_FILTER_GRAPH_AudioPlugin, SPA_VERSION_FGA_PLUGIN, &impl_plugin, impl); return 0; error_cleanup: if (impl->c) context_unref(impl->c); return res; } static const struct spa_interface_info impl_interfaces[] = { { SPA_TYPE_INTERFACE_FILTER_GRAPH_AudioPlugin }, }; static int impl_enum_interface_info(const struct spa_handle_factory *factory, const struct spa_interface_info **info, uint32_t *index) { spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(info != NULL, -EINVAL); spa_return_val_if_fail(index != NULL, -EINVAL); switch (*index) { case 0: *info = &impl_interfaces[*index]; break; default: return 0; } (*index)++; return 1; } static struct spa_handle_factory spa_fga_plugin_lv2_factory = { SPA_VERSION_HANDLE_FACTORY, "filter.graph.plugin.lv2", NULL, impl_get_size, impl_init, impl_enum_interface_info, }; SPA_EXPORT int spa_handle_factory_enum(const struct spa_handle_factory **factory, uint32_t *index) { spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(index != NULL, -EINVAL); switch (*index) { case 0: *factory = &spa_fga_plugin_lv2_factory; break; default: return 0; } (*index)++; return 1; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/filter-graph/plugin_onnx.c000066400000000000000000000473711511204443500300460ustar00rootroot00000000000000/* PipeWire */ /* SPDX-FileCopyrightText: Copyright © 2021 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include "audio-plugin.h" #define MAX_PORTS 256 #define MAX_CTX 64 const OrtApi* ort = NULL; #define CHECK(expr) \ if ((status = (expr)) != NULL) \ goto error_onnx; struct plugin { struct spa_handle handle; struct spa_fga_plugin plugin; struct spa_log *log; OrtEnv *env; OrtAllocator *allocator; OrtSessionOptions *session_options; }; struct tensor_info { int index; enum spa_direction direction; char *name; enum ONNXTensorElementDataType type; int64_t dimensions[64]; size_t n_dimensions; int retain; #define DATA_NONE 0 #define DATA_PORT 1 #define DATA_CONTROL 2 #define DATA_PARAM_RATE 3 #define DATA_TENSOR 4 uint32_t data_type; char data_name[128]; uint32_t data_index; uint32_t data_size; }; struct descriptor { struct spa_fga_descriptor desc; struct plugin *p; int blocksize; OrtSession *session; struct tensor_info tensors[MAX_PORTS]; size_t n_tensors; }; struct instance { struct descriptor *desc; uint32_t rate; OrtRunOptions *run_options; OrtValue *tensor[MAX_PORTS]; uint32_t offset; float *data[MAX_PORTS]; }; static struct tensor_info *find_tensor(struct descriptor *d, const char *name, enum spa_direction direction) { size_t i; for (i = 0; i < d->n_tensors; i++) { struct tensor_info *ti = &d->tensors[i]; if (spa_streq(ti->name, name) && ti->direction == direction) return ti; } return NULL; } /* * { * dimensions = [ 1, 576 ] * retain = 64 * data = "tensor:"|"param:rate"|"port:"|"control:" * } */ static int parse_tensor_info(struct descriptor *d, struct spa_json *it, struct tensor_info *info) { struct plugin *p = d->p; struct spa_json sub; const char *val; int len; char key[256]; char data[512]; while ((len = spa_json_object_next(it, key, sizeof(key), &val)) > 0) { if (spa_streq(key, "dimensions")) { int64_t dimensions[64]; size_t i, n_dimensions = 0; if (!spa_json_is_array(val, len)) { spa_log_error(p->log, "onnx: %s expects an array", key); return -EINVAL; } spa_json_enter(it, &sub); while (spa_json_get_string(&sub, data, sizeof(data)) > 0 && n_dimensions < 64) dimensions[n_dimensions++] = atoi(data); if (info->n_dimensions == 0) info->n_dimensions = n_dimensions; else if (n_dimensions != info->n_dimensions) { spa_log_error(p->log, "onnx: %s expected %zu dimensions, got %zu", key, info->n_dimensions, n_dimensions); return -EINVAL; } for (i = 0; i < n_dimensions; i++) { if (info->dimensions[i] <= 0) info->dimensions[i] = dimensions[i]; else if (info->dimensions[i] != dimensions[i]) { spa_log_error(p->log, "onnx: %s mismatched %zu dimension, got %" PRIi64" expected %"PRIi64, key, i, dimensions[i], info->dimensions[i]); return -EINVAL; } } } else if (spa_streq(key, "retain")) { if (spa_json_parse_int(val, len, &info->retain) <= 0) { spa_log_error(p->log, "onnx: %s expects an int", key); return -EINVAL; } } else if (spa_streq(key, "data")) { if (spa_json_parse_stringn(val, len, data, sizeof(data)) <= 0) { spa_log_error(p->log, "onnx: %s expects a string", key); return -EINVAL; } if (spa_strstartswith(data, "tensor:")) { struct tensor_info *ti; spa_scnprintf(info->data_name, sizeof(info->data_name), "%s", data+7); ti = find_tensor(d, info->data_name, SPA_DIRECTION_REVERSE(info->direction)); if (ti == NULL) { spa_log_error(p->log, "onnx: unknown tensor %s", info->data_name); return -EINVAL; } info->data_type = DATA_TENSOR; info->data_index = ti->index; } else if (spa_strstartswith(data, "param:rate")) { info->data_type = DATA_PARAM_RATE; } else if (spa_strstartswith(data, "port:")) { info->data_type = DATA_PORT; spa_scnprintf(info->data_name, sizeof(info->data_name), "%s", data+5); } else if (spa_strstartswith(data, "control:")) { info->data_type = DATA_CONTROL; spa_scnprintf(info->data_name, sizeof(info->data_name), "%s", data+8); } else { spa_log_warn(p->log, "onnx: unknown %s value: %s", key, data); } } else { spa_log_warn(p->log, "unexpected onnx tensor-info key '%s'", key); } } return 0; } /* * { * = { * * } * .... * } */ static int parse_tensors(struct descriptor *d, struct spa_json *it, enum spa_direction direction) { struct plugin *p = d->p; struct spa_json sub; const char *val; int len, res; char key[256]; while ((len = spa_json_object_next(&it[0], key, sizeof(key), &val)) > 0) { struct tensor_info *info; if ((info = find_tensor(d, key, direction)) == NULL) { spa_log_error(p->log, "onnx: unknown tensor name %s", key); return -EINVAL; } if (!spa_json_is_object(val, len)) { spa_log_error(p->log, "onnx: tensors %s expects an object", key); return -EINVAL; } spa_json_enter(it, &sub); if ((res = parse_tensor_info(d, &sub, info)) < 0) return res; } return 0; } #define SET_VAL(data,type,val) \ { type _v = (type) (val); memcpy(data, &_v, sizeof(_v)); } \ static int set_value(void *data, enum ONNXTensorElementDataType type, double val) { switch (type) { case ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT8: SET_VAL(data, uint8_t, val); break; case ONNX_TENSOR_ELEMENT_DATA_TYPE_INT8: SET_VAL(data, int8_t, val); break; case ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT16: SET_VAL(data, uint16_t, val); break; case ONNX_TENSOR_ELEMENT_DATA_TYPE_INT16: SET_VAL(data, int16_t, val); break; case ONNX_TENSOR_ELEMENT_DATA_TYPE_INT32: SET_VAL(data, int32_t, val); break; case ONNX_TENSOR_ELEMENT_DATA_TYPE_INT64: SET_VAL(data, int64_t, val); break; case ONNX_TENSOR_ELEMENT_DATA_TYPE_BOOL: SET_VAL(data, bool, val); break; case ONNX_TENSOR_ELEMENT_DATA_TYPE_DOUBLE: SET_VAL(data, double, val); break; case ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT32: SET_VAL(data, uint32_t, val); break; case ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT64: SET_VAL(data, uint64_t, val); break; case ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT: SET_VAL(data, float, val); break; default: return -ENOTSUP; } return 0; } /* * config = { * blocksize = 512 * input-tensors = { * * ... * } * output-tensors = { * * ... * } * } */ static void *onnx_instantiate(const struct spa_fga_plugin *plugin, const struct spa_fga_descriptor *desc, unsigned long SampleRate, int index, const char *config) { struct descriptor *d = (struct descriptor *)desc; struct plugin *p = d->p; struct instance *i; OrtStatus *status; size_t n, j; int res; errno = EINVAL; i = calloc(1, sizeof(*i)); if (i == NULL) return NULL; i->desc = d; i->rate = SampleRate; for (n = 0; n < d->n_tensors; n++) { struct tensor_info *ti = &d->tensors[n]; void *data; spa_log_debug(p->log, "%zd %s %zd", n, ti->name, ti->n_dimensions); ti->data_size = 1; for (j = 0; j < ti->n_dimensions; j++) { spa_log_debug(p->log, "%zd %zd/%zd %"PRIi64, n, j, ti->n_dimensions, ti->dimensions[j]); if (ti->dimensions[j] != -1) ti->data_size *= ti->dimensions[j]; } CHECK(ort->CreateTensorAsOrtValue(p->allocator, ti->dimensions, ti->n_dimensions, ti->type, &i->tensor[n])); CHECK(ort->GetTensorMutableData(i->tensor[n], (void**)&data)); if (ti->data_type == DATA_PARAM_RATE) { if ((res = set_value(data, ti->type, (double)i->rate)) < 0) { errno = -res; goto error; } } } return i; error_onnx: const char* msg = ort->GetErrorMessage(status); spa_log_error(p->log, "%s", msg); ort->ReleaseStatus(status); error: free(i); return NULL; } static void onnx_cleanup(void *instance) { struct instance *i = instance; free(i); } static void onnx_free(const struct spa_fga_descriptor *desc) { struct descriptor *d = (struct descriptor*)desc; free((char*)d->desc.name); free(d->desc.ports); free(d); } static void onnx_connect_port(void *instance, unsigned long port, void *data) { struct instance *i = instance; i->data[port] = data; } static void move_samples(float *dst, uint32_t dst_offs, float *src, uint32_t src_offs, uint32_t n_samples) { memmove(SPA_PTROFF(dst, dst_offs * sizeof(float), void), SPA_PTROFF(src, src_offs * sizeof(float), void), n_samples * sizeof(float)); } static void onnx_run(void *instance, unsigned long SampleCount) { OrtStatus *status; struct instance *i = instance; struct descriptor *d = i->desc; struct plugin *p = d->p; const char *input_names[MAX_PORTS]; const OrtValue *inputs[MAX_PORTS]; const char *output_names[MAX_PORTS]; OrtValue *outputs[MAX_PORTS]; size_t n, n_inputs = 0, n_outputs = 0; float *data; uint32_t offset = i->offset, blocksize = d->blocksize; while (SampleCount > 0) { uint32_t chunk = SPA_MIN(SampleCount, blocksize - offset); uint32_t next_offset; for (n = 0; n < d->n_tensors; n++) { struct tensor_info *ti = &d->tensors[n]; if (ti->direction == SPA_DIRECTION_INPUT) { input_names[n_inputs] = ti->name; inputs[n_inputs++] = i->tensor[ti->index]; if (ti->data_type == DATA_PORT) { CHECK(ort->GetTensorMutableData(i->tensor[ti->index], (void**)&data)); if (ti->retain > 0 && offset == 0) move_samples(data, 0, data, ti->data_size - ti->retain, ti->retain); move_samples(data, ti->retain + offset, i->data[ti->data_index], offset, chunk); } else if (ti->data_type == DATA_TENSOR) { if (offset == 0) { void *src, *dst; CHECK(ort->GetTensorMutableData(i->tensor[ti->data_index], &src)); CHECK(ort->GetTensorMutableData(i->tensor[ti->index], &dst)); move_samples(dst, 0, src, 0, ti->data_size); } } } else { output_names[n_outputs] = ti->name; outputs[n_outputs++] = i->tensor[ti->index]; } } if (offset + chunk >= blocksize) { CHECK(ort->Run(d->session, i->run_options, input_names, (const OrtValue *const*)inputs, n_inputs, output_names, n_outputs, (OrtValue **)outputs)); next_offset = 0; } else { next_offset = offset + chunk; } for (n = 0; n < d->n_tensors; n++) { struct tensor_info *ti = &d->tensors[n]; if (ti->direction != SPA_DIRECTION_OUTPUT) continue; if (ti->data_type == DATA_CONTROL) { if (next_offset == 0) { float *src, *dst; CHECK(ort->GetTensorMutableData(i->tensor[ti->index], (void**)&src)); dst = i->data[ti->data_index]; if (src && dst) dst[0] = src[0]; } } else if (ti->data_type == DATA_PORT) { CHECK(ort->GetTensorMutableData(i->tensor[ti->index], (void**)&data)); move_samples(i->data[ti->data_index], offset, data, offset, chunk); } } SampleCount -= chunk; offset = next_offset; } i->offset = offset; return; error_onnx: const char* msg = ort->GetErrorMessage(status); spa_log_error(p->log, "%s", msg); ort->ReleaseStatus(status); } static const struct spa_fga_descriptor *onnx_plugin_make_desc(void *plugin, const char *name) { OrtStatus *status; struct plugin *p = (struct plugin *)plugin; struct descriptor *desc; size_t i, j, n_inputs, n_outputs; OrtTypeInfo *tinfo; const OrtTensorTypeAndShapeInfo *tt; char path[PATH_MAX]; struct spa_json it[2]; const char *val; int len; char key[256]; if (spa_json_begin_object(&it[0], name, strlen(name)) <= 0) { spa_log_error(p->log, "onnx: expected object in label"); return NULL; } if (spa_json_str_object_find(name, strlen(name), "filename", path, sizeof(path)) <= 0) { spa_log_error(p->log, "onnx: could not find filename in label"); return NULL; } desc = calloc(1, sizeof(*desc)); if (desc == NULL) return NULL; desc->p = p; desc->desc.instantiate = onnx_instantiate; desc->desc.cleanup = onnx_cleanup; desc->desc.free = onnx_free; desc->desc.connect_port = onnx_connect_port; desc->desc.run = onnx_run; desc->desc.name = strdup(name); desc->desc.flags = 0; spa_log_info(p->log, "onnx: loading model %s", path); CHECK(ort->CreateSession(p->env, path, p->session_options, &desc->session)); CHECK(ort->SessionGetInputCount(desc->session, &n_inputs)); CHECK(ort->SessionGetOutputCount(desc->session, &n_outputs)); spa_log_info(p->log, "found %zd input and %zd output tensors", n_inputs, n_outputs); /* first go over all tensors and collect info */ for (i = 0; i < n_inputs; i++) { struct tensor_info *ti = &desc->tensors[i]; ti->index = i; ti->direction = SPA_DIRECTION_INPUT; CHECK(ort->SessionGetInputName(desc->session, i, p->allocator, (char**)&ti->name)); CHECK(ort->SessionGetInputTypeInfo(desc->session, i, &tinfo)); CHECK(ort->CastTypeInfoToTensorInfo(tinfo, &tt)); CHECK(ort->GetTensorElementType(tt, &ti->type)); CHECK(ort->GetDimensionsCount(tt, &ti->n_dimensions)); if (ti->n_dimensions > SPA_N_ELEMENTS(ti->dimensions)) { spa_log_warn(p->log, "too many dimensions"); errno = ENOTSUP; goto error; } CHECK(ort->GetDimensions(tt, ti->dimensions, ti->n_dimensions)); spa_log_debug(p->log, "%zd %s %zd", i, ti->name, ti->n_dimensions); for (j = 0; j < ti->n_dimensions; j++) { spa_log_debug(p->log, "%zd %zd/%zd %"PRIi64, i, j, ti->n_dimensions, ti->dimensions[j]); } } for (i = 0; i < n_outputs; i++) { struct tensor_info *ti = &desc->tensors[i + n_inputs]; ti->index = i + n_inputs; ti->direction = SPA_DIRECTION_OUTPUT; CHECK(ort->SessionGetOutputName(desc->session, i, p->allocator, (char**)&ti->name)); CHECK(ort->SessionGetOutputTypeInfo(desc->session, i, &tinfo)); CHECK(ort->CastTypeInfoToTensorInfo(tinfo, &tt)); CHECK(ort->GetTensorElementType(tt, &ti->type)); CHECK(ort->GetDimensionsCount(tt, &ti->n_dimensions)); if (ti->n_dimensions > SPA_N_ELEMENTS(ti->dimensions)) { spa_log_error(p->log, "too many dimensions"); errno = ENOTSUP; goto error; } CHECK(ort->GetDimensions(tt, ti->dimensions, ti->n_dimensions)); spa_log_debug(p->log, "%zd %s %zd", i, ti->name, ti->n_dimensions); for (j = 0; j < ti->n_dimensions; j++) { spa_log_debug(p->log, "%zd %zd/%zd %"PRIi64, i, j, ti->n_dimensions, ti->dimensions[j]); } } desc->n_tensors = n_inputs + n_outputs; /* enhance the tensor info */ while ((len = spa_json_object_next(&it[0], key, sizeof(key), &val)) > 0) { if (spa_streq(key, "blocksize")) { if (spa_json_parse_int(val, len, &desc->blocksize) <= 0) { spa_log_error(p->log, "onnx:blocksize requires a number"); errno = EINVAL; goto error; } } else if (spa_streq(key, "input-tensors")) { if (!spa_json_is_object(val, len)) { spa_log_error(p->log, "onnx: %s expects an object", key); errno = EINVAL; goto error; } spa_json_enter(&it[0], &it[1]); parse_tensors(desc, &it[1], SPA_DIRECTION_INPUT); } else if (spa_streq(key, "output-tensors")) { if (!spa_json_is_object(val, len)) { spa_log_error(p->log, "onnx: %s expects an object", key); errno = EINVAL; goto error; } spa_json_enter(&it[0], &it[1]); parse_tensors(desc, &it[1], SPA_DIRECTION_OUTPUT); } } desc->desc.ports = calloc(desc->n_tensors, sizeof(struct spa_fga_port)); desc->desc.n_ports = 0; /* make ports */ for (i = 0; i < desc->n_tensors; i++) { struct tensor_info *ti = &desc->tensors[i]; struct spa_fga_port *fp = &desc->desc.ports[desc->desc.n_ports]; fp->flags = 0; fp->index = desc->desc.n_ports; if (ti->type != ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT) continue; if (ti->data_type == DATA_PORT) fp->flags |= SPA_FGA_PORT_AUDIO; else if (ti->data_type == DATA_CONTROL) fp->flags |= SPA_FGA_PORT_CONTROL; else continue; if (ti->direction == SPA_DIRECTION_INPUT) fp->flags |= SPA_FGA_PORT_INPUT; else fp->flags |= SPA_FGA_PORT_OUTPUT; fp->name = ti->data_name; ti->data_index = desc->desc.n_ports; desc->desc.n_ports++; if (desc->desc.n_ports > MAX_PORTS) { spa_log_error(p->log, "too many ports"); errno = -ENOSPC; goto error; } } return &desc->desc; error_onnx: const char* msg = ort->GetErrorMessage(status); spa_log_error(p->log, "%s", msg); ort->ReleaseStatus(status); error: if (desc->session) ort->ReleaseSession(desc->session); onnx_free(&desc->desc); return NULL; } static int load_model(struct plugin *impl, const char *path) { OrtStatus *status; ort = OrtGetApiBase()->GetApi(ORT_API_VERSION); if (ort == NULL) { spa_log_error(impl->log, "Failed to init ONNX Runtime engine"); return -EINVAL; } CHECK(ort->CreateEnv(ORT_LOGGING_LEVEL_WARNING, "onnx-filter-graph", &impl->env)); CHECK(ort->GetAllocatorWithDefaultOptions(&impl->allocator)); CHECK(ort->CreateSessionOptions(&impl->session_options)); CHECK(ort->SetIntraOpNumThreads(impl->session_options, 1)); CHECK(ort->SetInterOpNumThreads(impl->session_options, 1)); CHECK(ort->SetSessionGraphOptimizationLevel(impl->session_options, ORT_ENABLE_ALL)); return 0; error_onnx: const char* msg = ort->GetErrorMessage(status); spa_log_error(impl->log, "%s", msg); ort->ReleaseStatus(status); if (impl->env) ort->ReleaseEnv(impl->env); impl->env = NULL; if (impl->session_options) ort->ReleaseSessionOptions(impl->session_options); impl->session_options = NULL; return -EINVAL; } static struct spa_fga_plugin_methods impl_plugin = { SPA_VERSION_FGA_PLUGIN_METHODS, .make_desc = onnx_plugin_make_desc, }; static int impl_get_interface(struct spa_handle *handle, const char *type, void **interface) { struct plugin *impl; spa_return_val_if_fail(handle != NULL, -EINVAL); spa_return_val_if_fail(interface != NULL, -EINVAL); impl = (struct plugin *) handle; if (spa_streq(type, SPA_TYPE_INTERFACE_FILTER_GRAPH_AudioPlugin)) *interface = &impl->plugin; else return -ENOENT; return 0; } static int impl_clear(struct spa_handle *handle) { return 0; } static size_t impl_get_size(const struct spa_handle_factory *factory, const struct spa_dict *params) { return sizeof(struct plugin); } static int impl_init(const struct spa_handle_factory *factory, struct spa_handle *handle, const struct spa_dict *info, const struct spa_support *support, uint32_t n_support) { struct plugin *impl; uint32_t i; const char *path = NULL; int res; handle->get_interface = impl_get_interface; handle->clear = impl_clear; impl = (struct plugin *) handle; impl->log = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log); for (i = 0; info && i < info->n_items; i++) { const char *k = info->items[i].key; const char *s = info->items[i].value; if (spa_streq(k, "filter.graph.path")) path = s; } if ((res = load_model(impl, path)) < 0) return res; impl->plugin.iface = SPA_INTERFACE_INIT( SPA_TYPE_INTERFACE_FILTER_GRAPH_AudioPlugin, SPA_VERSION_FGA_PLUGIN, &impl_plugin, impl); return 0; } static const struct spa_interface_info impl_interfaces[] = { { SPA_TYPE_INTERFACE_FILTER_GRAPH_AudioPlugin }, }; static int impl_enum_interface_info(const struct spa_handle_factory *factory, const struct spa_interface_info **info, uint32_t *index) { spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(info != NULL, -EINVAL); spa_return_val_if_fail(index != NULL, -EINVAL); switch (*index) { case 0: *info = &impl_interfaces[*index]; break; default: return 0; } (*index)++; return 1; } static struct spa_handle_factory spa_fga_plugin_onnx_factory = { SPA_VERSION_HANDLE_FACTORY, "filter.graph.plugin.onnx", NULL, impl_get_size, impl_init, impl_enum_interface_info, }; SPA_EXPORT int spa_handle_factory_enum(const struct spa_handle_factory **factory, uint32_t *index) { spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(index != NULL, -EINVAL); switch (*index) { case 0: *factory = &spa_fga_plugin_onnx_factory; break; default: return 0; } (*index)++; return 1; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/filter-graph/plugin_sofa.c000066400000000000000000000342401511204443500300030ustar00rootroot00000000000000#include "config.h" #include #include #include #include #include "audio-plugin.h" #include "convolver.h" #include "audio-dsp.h" #include struct plugin { struct spa_handle handle; struct spa_fga_plugin plugin; struct spa_fga_dsp *dsp; struct spa_log *log; struct spa_loop *data_loop; struct spa_loop *main_loop; uint32_t quantum_limit; }; struct spatializer_impl { struct plugin *plugin; struct spa_fga_dsp *dsp; struct spa_log *log; unsigned long rate; float *port[7]; int n_samples, blocksize, tailsize; float *tmp[2]; struct MYSOFA_EASY *sofa; unsigned int interpolate:1; struct convolver *l_conv[3]; struct convolver *r_conv[3]; }; static void * spatializer_instantiate(const struct spa_fga_plugin *plugin, const struct spa_fga_descriptor * Descriptor, unsigned long SampleRate, int index, const char *config) { struct plugin *pl = SPA_CONTAINER_OF(plugin, struct plugin, plugin); struct spatializer_impl *impl; struct spa_json it[1]; const char *val; char key[256]; char filename[PATH_MAX] = ""; int len; errno = EINVAL; if (config == NULL) { spa_log_error(pl->log, "spatializer: no config was given"); return NULL; } if (spa_json_begin_object(&it[0], config, strlen(config)) <= 0) { spa_log_error(pl->log, "spatializer: expected object in config"); return NULL; } impl = calloc(1, sizeof(*impl)); if (impl == NULL) { errno = ENOMEM; return NULL; } impl->plugin = pl; impl->dsp = pl->dsp; impl->log = pl->log; while ((len = spa_json_object_next(&it[0], key, sizeof(key), &val)) > 0) { if (spa_streq(key, "blocksize")) { if (spa_json_parse_int(val, len, &impl->blocksize) <= 0) { spa_log_error(impl->log, "spatializer:blocksize requires a number"); errno = EINVAL; goto error; } } else if (spa_streq(key, "tailsize")) { if (spa_json_parse_int(val, len, &impl->tailsize) <= 0) { spa_log_error(impl->log, "spatializer:tailsize requires a number"); errno = EINVAL; goto error; } } else if (spa_streq(key, "filename")) { if (spa_json_parse_stringn(val, len, filename, sizeof(filename)) <= 0) { spa_log_error(impl->log, "spatializer:filename requires a string"); errno = EINVAL; goto error; } } } if (!filename[0]) { spa_log_error(impl->log, "spatializer:filename was not given"); errno = EINVAL; goto error; } int ret = MYSOFA_OK; impl->sofa = mysofa_open_cached(filename, SampleRate, &impl->n_samples, &ret); if (ret != MYSOFA_OK) { const char *reason; switch (ret) { case MYSOFA_INVALID_FORMAT: reason = "Invalid format"; errno = EINVAL; break; case MYSOFA_UNSUPPORTED_FORMAT: reason = "Unsupported format"; errno = ENOTSUP; break; case MYSOFA_NO_MEMORY: reason = "No memory"; errno = ENOMEM; break; case MYSOFA_READ_ERROR: reason = "Read error"; errno = ENOENT; break; case MYSOFA_INVALID_ATTRIBUTES: reason = "Invalid attributes"; errno = EINVAL; break; case MYSOFA_INVALID_DIMENSIONS: reason = "Invalid dimensions"; errno = EINVAL; break; case MYSOFA_INVALID_DIMENSION_LIST: reason = "Invalid dimension list"; errno = EINVAL; break; case MYSOFA_INVALID_COORDINATE_TYPE: reason = "Invalid coordinate type"; errno = EINVAL; break; case MYSOFA_ONLY_EMITTER_WITH_ECI_SUPPORTED: reason = "Only emitter with ECI supported"; errno = ENOTSUP; break; case MYSOFA_ONLY_DELAYS_WITH_IR_OR_MR_SUPPORTED: reason = "Only delays with IR or MR supported"; errno = ENOTSUP; break; case MYSOFA_ONLY_THE_SAME_SAMPLING_RATE_SUPPORTED: reason = "Only the same sampling rate supported"; errno = ENOTSUP; break; case MYSOFA_RECEIVERS_WITH_RCI_SUPPORTED: reason = "Receivers with RCI supported"; errno = ENOTSUP; break; case MYSOFA_RECEIVERS_WITH_CARTESIAN_SUPPORTED: reason = "Receivers with cartesian supported"; errno = ENOTSUP; break; case MYSOFA_INVALID_RECEIVER_POSITIONS: reason = "Invalid receiver positions"; errno = EINVAL; break; case MYSOFA_ONLY_SOURCES_WITH_MC_SUPPORTED: reason = "Only sources with MC supported"; errno = ENOTSUP; break; default: case MYSOFA_INTERNAL_ERROR: errno = EIO; reason = "Internal error"; break; } spa_log_error(impl->log, "Unable to load HRTF from %s: %s (%d)", filename, reason, ret); goto error; } if (impl->blocksize <= 0) impl->blocksize = SPA_CLAMP(impl->n_samples, 64, 256); if (impl->tailsize <= 0) impl->tailsize = SPA_CLAMP(4096, impl->blocksize, 32768); spa_log_info(impl->log, "using n_samples:%u %d:%d blocksize sofa:%s", impl->n_samples, impl->blocksize, impl->tailsize, filename); impl->tmp[0] = calloc(impl->plugin->quantum_limit, sizeof(float)); impl->tmp[1] = calloc(impl->plugin->quantum_limit, sizeof(float)); impl->rate = SampleRate; return impl; error: if (impl->sofa) mysofa_close_cached(impl->sofa); free(impl); return NULL; } static int do_switch(struct spa_loop *loop, bool async, uint32_t seq, const void *data, size_t size, void *user_data) { struct spatializer_impl *impl = user_data; if (impl->l_conv[0] == NULL) { SPA_SWAP(impl->l_conv[0], impl->l_conv[2]); SPA_SWAP(impl->r_conv[0], impl->r_conv[2]); } else { SPA_SWAP(impl->l_conv[1], impl->l_conv[2]); SPA_SWAP(impl->r_conv[1], impl->r_conv[2]); } impl->interpolate = impl->l_conv[0] && impl->l_conv[1]; return 0; } static void spatializer_reload(void * Instance) { struct spatializer_impl *impl = Instance; float *left_ir = calloc(impl->n_samples, sizeof(float)); float *right_ir = calloc(impl->n_samples, sizeof(float)); float left_delay; float right_delay; float coords[3]; for (uint8_t i = 0; i < 3; i++) coords[i] = impl->port[3 + i][0]; spa_log_info(impl->log, "making spatializer with %f %f %f", coords[0], coords[1], coords[2]); mysofa_s2c(coords); mysofa_getfilter_float( impl->sofa, coords[0], coords[1], coords[2], left_ir, right_ir, &left_delay, &right_delay ); // TODO: make use of delay if ((left_delay != 0.0f || right_delay != 0.0f) && (!isnan(left_delay) || !isnan(right_delay))) spa_log_warn(impl->log, "delay dropped l: %f, r: %f", left_delay, right_delay); if (impl->l_conv[2]) convolver_free(impl->l_conv[2]); if (impl->r_conv[2]) convolver_free(impl->r_conv[2]); impl->l_conv[2] = convolver_new(impl->dsp, impl->blocksize, impl->tailsize, left_ir, impl->n_samples); impl->r_conv[2] = convolver_new(impl->dsp, impl->blocksize, impl->tailsize, right_ir, impl->n_samples); free(left_ir); free(right_ir); if (impl->l_conv[2] == NULL || impl->r_conv[2] == NULL) { spa_log_error(impl->log, "reloading left or right convolver failed"); return; } spa_loop_locked(impl->plugin->data_loop, do_switch, 1, NULL, 0, impl); } struct free_data { void *item[2]; }; static int do_free(struct spa_loop *loop, bool async, uint32_t seq, const void *data, size_t size, void *user_data) { const struct free_data *fd = data; if (fd->item[0]) convolver_free(fd->item[0]); if (fd->item[1]) convolver_free(fd->item[1]); return 0; } static void spatializer_run(void * Instance, unsigned long SampleCount) { struct spatializer_impl *impl = Instance; if (impl->interpolate) { uint32_t len = SPA_MIN(SampleCount, impl->plugin->quantum_limit); struct free_data free_data; float *l = impl->tmp[0], *r = impl->tmp[1]; convolver_run(impl->l_conv[0], impl->port[2], impl->port[0], len); convolver_run(impl->l_conv[1], impl->port[2], l, len); convolver_run(impl->r_conv[0], impl->port[2], impl->port[1], len); convolver_run(impl->r_conv[1], impl->port[2], r, len); for (uint32_t i = 0; i < SampleCount; i++) { float t = (float)i / SampleCount; impl->port[0][i] = impl->port[0][i] * (1.0f - t) + l[i] * t; impl->port[1][i] = impl->port[1][i] * (1.0f - t) + r[i] * t; } free_data.item[0] = impl->l_conv[0]; free_data.item[1] = impl->r_conv[0]; impl->l_conv[0] = impl->l_conv[1]; impl->r_conv[0] = impl->r_conv[1]; impl->l_conv[1] = impl->r_conv[1] = NULL; impl->interpolate = false; spa_loop_invoke(impl->plugin->main_loop, do_free, 1, &free_data, sizeof(free_data), false, impl); } else if (impl->l_conv[0] && impl->r_conv[0]) { convolver_run(impl->l_conv[0], impl->port[2], impl->port[0], SampleCount); convolver_run(impl->r_conv[0], impl->port[2], impl->port[1], SampleCount); } impl->port[6][0] = impl->n_samples; } static void spatializer_connect_port(void * Instance, unsigned long Port, void * DataLocation) { struct spatializer_impl *impl = Instance; impl->port[Port] = DataLocation; } static void spatializer_cleanup(void * Instance) { struct spatializer_impl *impl = Instance; for (uint8_t i = 0; i < 3; i++) { if (impl->l_conv[i]) convolver_free(impl->l_conv[i]); if (impl->r_conv[i]) convolver_free(impl->r_conv[i]); } if (impl->sofa) mysofa_close_cached(impl->sofa); free(impl->tmp[0]); free(impl->tmp[1]); free(impl); } static void spatializer_control_changed(void * Instance) { spatializer_reload(Instance); } static void spatializer_activate(void * Instance) { struct spatializer_impl *impl = Instance; impl->port[6][0] = impl->n_samples; } static void spatializer_deactivate(void * Instance) { struct spatializer_impl *impl = Instance; if (impl->l_conv[0]) convolver_reset(impl->l_conv[0]); if (impl->r_conv[0]) convolver_reset(impl->r_conv[0]); impl->interpolate = false; } static struct spa_fga_port spatializer_ports[] = { { .index = 0, .name = "Out L", .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_AUDIO, }, { .index = 1, .name = "Out R", .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_AUDIO, }, { .index = 2, .name = "In", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, }, { .index = 3, .name = "Azimuth", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, .def = 0.0f, .min = 0.0f, .max = 360.0f }, { .index = 4, .name = "Elevation", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, .def = 0.0f, .min = -90.0f, .max = 90.0f }, { .index = 5, .name = "Radius", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, .def = 1.0f, .min = 0.0f, .max = 100.0f }, { .index = 6, .name = "latency", .hint = SPA_FGA_HINT_LATENCY, .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_CONTROL, }, }; static const struct spa_fga_descriptor spatializer_desc = { .name = "spatializer", .n_ports = SPA_N_ELEMENTS(spatializer_ports), .ports = spatializer_ports, .instantiate = spatializer_instantiate, .connect_port = spatializer_connect_port, .control_changed = spatializer_control_changed, .activate = spatializer_activate, .deactivate = spatializer_deactivate, .run = spatializer_run, .cleanup = spatializer_cleanup, }; static const struct spa_fga_descriptor * sofa_descriptor(unsigned long Index) { switch(Index) { case 0: return &spatializer_desc; } return NULL; } static const struct spa_fga_descriptor *sofa_plugin_make_desc(void *plugin, const char *name) { unsigned long i; for (i = 0; ;i++) { const struct spa_fga_descriptor *d = sofa_descriptor(i); if (d == NULL) break; if (spa_streq(d->name, name)) return d; } return NULL; } static struct spa_fga_plugin_methods impl_plugin = { SPA_VERSION_FGA_PLUGIN_METHODS, .make_desc = sofa_plugin_make_desc, }; static int impl_get_interface(struct spa_handle *handle, const char *type, void **interface) { struct plugin *impl; spa_return_val_if_fail(handle != NULL, -EINVAL); spa_return_val_if_fail(interface != NULL, -EINVAL); impl = (struct plugin *) handle; if (spa_streq(type, SPA_TYPE_INTERFACE_FILTER_GRAPH_AudioPlugin)) *interface = &impl->plugin; else return -ENOENT; return 0; } static int impl_clear(struct spa_handle *handle) { return 0; } static size_t impl_get_size(const struct spa_handle_factory *factory, const struct spa_dict *params) { return sizeof(struct plugin); } static int impl_init(const struct spa_handle_factory *factory, struct spa_handle *handle, const struct spa_dict *info, const struct spa_support *support, uint32_t n_support) { struct plugin *impl; handle->get_interface = impl_get_interface; handle->clear = impl_clear; impl = (struct plugin *) handle; impl->plugin.iface = SPA_INTERFACE_INIT( SPA_TYPE_INTERFACE_FILTER_GRAPH_AudioPlugin, SPA_VERSION_FGA_PLUGIN, &impl_plugin, impl); impl->quantum_limit = 8192u; impl->log = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log); impl->data_loop = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_DataLoop); impl->main_loop = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Loop); impl->dsp = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_FILTER_GRAPH_AudioDSP); for (uint32_t i = 0; info && i < info->n_items; i++) { const char *k = info->items[i].key; const char *s = info->items[i].value; if (spa_streq(k, "clock.quantum-limit")) spa_atou32(s, &impl->quantum_limit, 0); if (spa_streq(k, "filter.graph.audio.dsp")) sscanf(s, "pointer:%p", &impl->dsp); } if (impl->data_loop == NULL || impl->main_loop == NULL) { spa_log_error(impl->log, "%p: could not find a data/main loop", impl); return -EINVAL; } if (impl->dsp == NULL) { spa_log_error(impl->log, "%p: could not find DSP functions", impl); return -EINVAL; } return 0; } static const struct spa_interface_info impl_interfaces[] = { {SPA_TYPE_INTERFACE_FILTER_GRAPH_AudioPlugin,}, }; static int impl_enum_interface_info(const struct spa_handle_factory *factory, const struct spa_interface_info **info, uint32_t *index) { spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(info != NULL, -EINVAL); spa_return_val_if_fail(index != NULL, -EINVAL); switch (*index) { case 0: *info = &impl_interfaces[*index]; break; default: return 0; } (*index)++; return 1; } static struct spa_handle_factory spa_fga_sofa_plugin_factory = { SPA_VERSION_HANDLE_FACTORY, "filter.graph.plugin.sofa", NULL, impl_get_size, impl_init, impl_enum_interface_info, }; SPA_EXPORT int spa_handle_factory_enum(const struct spa_handle_factory **factory, uint32_t *index) { spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(index != NULL, -EINVAL); switch (*index) { case 0: *factory = &spa_fga_sofa_plugin_factory; break; default: return 0; } (*index)++; return 1; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/jack/000077500000000000000000000000001511204443500236525ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/jack/jack-client.c000066400000000000000000000041731511204443500262070ustar00rootroot00000000000000/* Spa JACK Client */ /* SPDX-FileCopyrightText: Copyright © 2019 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include "jack-client.h" static int jack_process(jack_nframes_t nframes, void *arg) { struct spa_jack_client *client = arg; jack_get_cycle_times(client->client, &client->current_frames, &client->current_usecs, &client->next_usecs, &client->period_usecs); jack_transport_query (client->client, &client->pos); client->buffer_size = nframes; spa_log_trace_fp(client->log, "frames %u", nframes); spa_jack_client_emit_process(client); return 0; } static void jack_shutdown(void* arg) { struct spa_jack_client *client = arg; spa_log_warn(client->log, "%p", client); spa_jack_client_emit_shutdown(client); spa_hook_list_init(&client->listener_list); client->client = NULL; } static int status_to_result(jack_status_t status) { int res; if (status & JackInvalidOption) res = -EINVAL; else if (status & JackServerFailed) res = -ECONNREFUSED; else if (status & JackVersionError) res = -EPROTO; else if (status & JackInitFailure) res = -EIO; else res = -EFAULT; return res; } int spa_jack_client_open(struct spa_jack_client *client, const char *client_name, const char *server_name) { jack_status_t status; if (client->client) return 0; client->client = jack_client_open(client_name, JackNoStartServer, &status, NULL); if (client->client == NULL) return status_to_result(status); spa_hook_list_init(&client->listener_list); spa_log_info(client->log, "%p: %s", client, client_name); jack_set_process_callback(client->client, jack_process, client); jack_on_shutdown(client->client, jack_shutdown, client); client->frame_rate = jack_get_sample_rate(client->client); client->buffer_size = jack_get_buffer_size(client->client); return 0; } int spa_jack_client_close(struct spa_jack_client *client) { if (client->client == NULL) return 0; spa_log_info(client->log, "%p:", client); spa_jack_client_emit_destroy(client); if (jack_client_close(client->client) != 0) return -EIO; spa_hook_list_init(&client->listener_list); client->client = NULL; return 0; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/jack/jack-client.h000066400000000000000000000030601511204443500262060ustar00rootroot00000000000000/* Spa JACK Client */ /* SPDX-FileCopyrightText: Copyright © 2019 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef SPA_JACK_CLIENT_H #define SPA_JACK_CLIENT_H #include #include #include #include #ifdef __cplusplus extern "C" { #endif struct spa_jack_client_events { #define SPA_VERSION_JACK_CLIENT_EVENTS 0 uint32_t version; void (*destroy) (void *data); void (*process) (void *data); void (*shutdown) (void *data); }; struct spa_jack_client { struct spa_log *log; jack_client_t *client; jack_nframes_t frame_rate; jack_nframes_t buffer_size; jack_nframes_t current_frames; jack_time_t current_usecs; jack_time_t next_usecs; float period_usecs; jack_position_t pos; struct spa_hook_list listener_list; }; #define spa_jack_client_emit(c,m,v,...) spa_hook_list_call(&(c)->listener_list, \ struct spa_jack_client_events, \ m, v, ##__VA_ARGS__) #define spa_jack_client_emit_destroy(c) spa_jack_client_emit(c, destroy, 0) #define spa_jack_client_emit_process(c) spa_jack_client_emit(c, process, 0) #define spa_jack_client_emit_shutdown(c) spa_jack_client_emit(c, shutdown, 0) #define spa_jack_client_add_listener(c,listener,events,data) \ spa_hook_list_append(&(c)->listener_list, listener, events, data) int spa_jack_client_open(struct spa_jack_client *client, const char *client_name, const char *server_name); int spa_jack_client_close(struct spa_jack_client *client); #ifdef __cplusplus } /* extern "C" */ #endif #endif /* SPA_JACK_CLIENT_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/jack/jack-device.c000066400000000000000000000234271511204443500261730ustar00rootroot00000000000000/* Spa JACK Device */ /* SPDX-FileCopyrightText: Copyright © 2019 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "jack-client.h" #undef SPA_LOG_TOPIC_DEFAULT #define SPA_LOG_TOPIC_DEFAULT &log_topic SPA_LOG_TOPIC_DEFINE_STATIC(log_topic, "spa.jack-device"); #define MAX_DEVICES 64 #define DEFAULT_SERVER "default" struct props { char server[128]; }; static void reset_props(struct props *props) { strncpy(props->server, DEFAULT_SERVER, 64); } struct node { enum spa_direction direction; }; struct impl { struct spa_handle handle; struct spa_device device; struct spa_log *log; struct spa_hook_list hooks; struct props props; struct node nodes[2]; uint32_t n_nodes; uint32_t profile; struct spa_jack_client client; }; static int emit_node(struct impl *this, uint32_t id) { struct spa_dict_item items[6]; struct spa_device_object_info info; char jack_client[64]; info = SPA_DEVICE_OBJECT_INFO_INIT(); info.type = SPA_TYPE_INTERFACE_Node; if (this->nodes[id].direction == SPA_DIRECTION_INPUT) info.factory_name = SPA_NAME_API_JACK_SINK; else info.factory_name = SPA_NAME_API_JACK_SOURCE; info.change_mask = SPA_DEVICE_OBJECT_CHANGE_MASK_PROPS; snprintf(jack_client, sizeof(jack_client), "pointer:%p", &this->client); items[0] = SPA_DICT_ITEM_INIT(SPA_KEY_API_JACK_CLIENT, jack_client); info.props = &SPA_DICT_INIT(items, 1); spa_device_emit_object_info(&this->hooks, id, &info); return 0; } static int activate_profile(struct impl *this, uint32_t id) { int res = 0; uint32_t i, n; const char ** ports; spa_log_debug(this->log, "profile %d", id); if (this->profile == id) return 0; for (i = 0; i < this->n_nodes; i++) spa_device_emit_object_info(&this->hooks, i, NULL); this->n_nodes = 0; spa_jack_client_close(&this->client); if (id == 0) goto done; res = spa_jack_client_open(&this->client, "PipeWire", NULL); if (res < 0) { spa_log_error(this->log, "%p: can't open client: %s", this, spa_strerror(res)); return res; } n = 0; ports = jack_get_ports(this->client.client, NULL, JACK_DEFAULT_AUDIO_TYPE, JackPortIsPhysical|JackPortIsOutput); if (ports) { jack_free(ports); this->nodes[n].direction = SPA_DIRECTION_OUTPUT; emit_node(this, n++); } ports = jack_get_ports(this->client.client, NULL, JACK_DEFAULT_AUDIO_TYPE, JackPortIsPhysical|JackPortIsInput); if (ports) { jack_free(ports); this->nodes[n].direction = SPA_DIRECTION_INPUT; emit_node(this, n++); } this->n_nodes = n; done: this->profile = id; return res; } static int emit_info(struct impl *this, bool full) { int err = 0; struct spa_dict_item items[10]; struct spa_device_info dinfo; struct spa_param_info params[2]; char name[200]; dinfo = SPA_DEVICE_INFO_INIT(); dinfo.change_mask = SPA_DEVICE_CHANGE_MASK_PROPS; items[0] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_API, "jack"); items[1] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_NICK, "jack"); if (spa_streq(this->props.server, "default")) snprintf(name, sizeof(name), "JACK Client"); else snprintf(name, sizeof(name), "JACK Client (%s)", this->props.server); items[2] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_NAME, name); items[3] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_DESCRIPTION, name); items[4] = SPA_DICT_ITEM_INIT(SPA_KEY_API_JACK_SERVER, this->props.server); items[5] = SPA_DICT_ITEM_INIT(SPA_KEY_MEDIA_CLASS, "Audio/Device"); dinfo.props = &SPA_DICT_INIT(items, 6); dinfo.change_mask |= SPA_DEVICE_CHANGE_MASK_PARAMS; params[0] = SPA_PARAM_INFO(SPA_PARAM_EnumProfile, SPA_PARAM_INFO_READ); params[1] = SPA_PARAM_INFO(SPA_PARAM_Profile, SPA_PARAM_INFO_READWRITE); dinfo.n_params = SPA_N_ELEMENTS(params); dinfo.params = params; spa_device_emit_info(&this->hooks, &dinfo); return err; } static int impl_add_listener(void *object, struct spa_hook *listener, const struct spa_device_events *events, void *data) { struct impl *this = object; struct spa_hook_list save; uint32_t i; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(events != NULL, -EINVAL); spa_hook_list_isolate(&this->hooks, &save, listener, events, data); if (events->info) emit_info(this, true); if (events->object_info) { for (i = 0; i < this->n_nodes; i++) emit_node(this, i); } spa_hook_list_join(&this->hooks, &save); return 0; } static int impl_sync(void *object, int seq) { struct impl *this = object; spa_return_val_if_fail(this != NULL, -EINVAL); spa_device_emit_result(&this->hooks, seq, 0, 0, NULL); return 0; } static struct spa_pod *build_profile(struct impl *this, struct spa_pod_builder *b, uint32_t id, uint32_t index) { struct spa_pod_frame f[2]; const char *name, *desc; switch (index) { case 0: name = "off"; desc = "Off"; break; case 1: name = "on"; desc = "On"; break; default: errno = EINVAL; return NULL; } spa_pod_builder_push_object(b, &f[0], SPA_TYPE_OBJECT_ParamProfile, id); spa_pod_builder_add(b, SPA_PARAM_PROFILE_index, SPA_POD_Int(index), SPA_PARAM_PROFILE_name, SPA_POD_String(name), SPA_PARAM_PROFILE_description, SPA_POD_String(desc), 0); return spa_pod_builder_pop(b, &f[0]); } static int impl_enum_params(void *object, int seq, uint32_t id, uint32_t start, uint32_t num, const struct spa_pod *filter) { struct impl *this = object; struct spa_pod *param; struct spa_pod_builder b = { 0 }; uint8_t buffer[1024]; struct spa_result_device_params result; uint32_t count = 0; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(num != 0, -EINVAL); result.id = id; result.next = start; next: result.index = result.next++; spa_pod_builder_init(&b, buffer, sizeof(buffer)); switch (id) { case SPA_PARAM_EnumProfile: { switch (result.index) { case 0: case 1: param = build_profile(this, &b, id, result.index); break; default: return 0; } break; } case SPA_PARAM_Profile: { switch (result.index) { case 0: param = build_profile(this, &b, id, this->profile); break; default: return 0; } break; } default: return -ENOENT; } if (spa_pod_filter(&b, &result.param, param, filter) < 0) goto next; spa_device_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_DEVICE_PARAMS, &result); if (++count != num) goto next; return 0; } static int impl_set_param(void *object, uint32_t id, uint32_t flags, const struct spa_pod *param) { struct impl *this = object; int res; spa_return_val_if_fail(this != NULL, -EINVAL); switch (id) { case SPA_PARAM_Profile: { uint32_t idx; if ((res = spa_pod_parse_object(param, SPA_TYPE_OBJECT_ParamProfile, NULL, SPA_PARAM_PROFILE_index, SPA_POD_Int(&idx))) < 0) { spa_log_warn(this->log, "can't parse profile"); spa_debug_log_pod(this->log, SPA_LOG_LEVEL_DEBUG, 0, NULL, param); return res; } activate_profile(this, idx); break; } default: return -ENOENT; } return 0; } static const struct spa_device_methods impl_device = { SPA_VERSION_DEVICE_METHODS, .add_listener = impl_add_listener, .sync = impl_sync, .enum_params = impl_enum_params, .set_param = impl_set_param, }; static int impl_get_interface(struct spa_handle *handle, const char *type, void **interface) { struct impl *this; spa_return_val_if_fail(handle != NULL, -EINVAL); spa_return_val_if_fail(interface != NULL, -EINVAL); this = (struct impl *) handle; if (spa_streq(type, SPA_TYPE_INTERFACE_Device)) *interface = &this->device; else return -ENOENT; return 0; } static int impl_clear(struct spa_handle *handle) { struct impl *this; spa_return_val_if_fail(handle != NULL, -EINVAL); this = (struct impl *) handle; activate_profile(this, 0); return 0; } static size_t impl_get_size(const struct spa_handle_factory *factory, const struct spa_dict *params) { return sizeof(struct impl); } static int impl_init(const struct spa_handle_factory *factory, struct spa_handle *handle, const struct spa_dict *info, const struct spa_support *support, uint32_t n_support) { struct impl *this; const char *str; spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(handle != NULL, -EINVAL); handle->get_interface = impl_get_interface; handle->clear = impl_clear; this = (struct impl *) handle; this->log = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log); this->client.log = this->log; this->device.iface = SPA_INTERFACE_INIT( SPA_TYPE_INTERFACE_Device, SPA_VERSION_DEVICE, &impl_device, this); spa_hook_list_init(&this->hooks); reset_props(&this->props); if (info && (str = spa_dict_lookup(info, SPA_KEY_API_JACK_SERVER))) snprintf(this->props.server, 64, "%s", str); return 0; } static const struct spa_interface_info impl_interfaces[] = { {SPA_TYPE_INTERFACE_Device,}, }; static int impl_enum_interface_info(const struct spa_handle_factory *factory, const struct spa_interface_info **info, uint32_t *index) { spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(info != NULL, -EINVAL); spa_return_val_if_fail(index != NULL, -EINVAL); if (*index >= SPA_N_ELEMENTS(impl_interfaces)) return 0; *info = &impl_interfaces[(*index)++]; return 1; } const struct spa_handle_factory spa_jack_device_factory = { SPA_VERSION_HANDLE_FACTORY, SPA_NAME_API_JACK_DEVICE, NULL, impl_get_size, impl_init, impl_enum_interface_info, }; pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/jack/jack-sink.c000066400000000000000000000540341511204443500256760ustar00rootroot00000000000000/* Spa jack client */ /* SPDX-FileCopyrightText: Copyright © 2019 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "jack-client.h" #undef SPA_LOG_TOPIC_DEFAULT #define SPA_LOG_TOPIC_DEFAULT &log_topic SPA_LOG_TOPIC_DEFINE_STATIC(log_topic, "spa.jack-sink"); #define MAX_PORTS 128 #define MAX_BUFFERS 8 #define MAX_SAMPLES 8192 struct buffer { uint32_t id; #define BUFFER_FLAG_OUT (1<<0) uint32_t flags; struct spa_buffer *outbuf; struct spa_list link; }; struct port { uint32_t id; uint64_t info_all; struct spa_port_info info; struct spa_dict_item items[4]; struct spa_dict props; struct spa_param_info params[5]; unsigned int have_format:1; struct spa_audio_info current_format; int stride; struct spa_io_buffers *io; struct buffer buffers[MAX_BUFFERS]; uint32_t n_buffers; jack_port_t *jack_port; }; struct impl { struct spa_handle handle; struct spa_node node; struct spa_log *log; uint64_t info_all; struct spa_node_info info; struct spa_param_info params[5]; struct spa_hook_list hooks; struct spa_callbacks callbacks; struct spa_io_clock *clock; struct spa_io_position *position; struct port in_ports[MAX_PORTS]; uint32_t n_in_ports; struct spa_audio_info current_format; struct spa_jack_client *client; struct spa_hook client_listener; unsigned int started:1; }; #define CHECK_IN_PORT(this,p) ((p) < this->n_in_ports) #define CHECK_PORT(this,d,p) (d == SPA_DIRECTION_INPUT && CHECK_IN_PORT(this,p)) #define GET_IN_PORT(this,p) (&this->in_ports[p]) #define GET_PORT(this,d,p) GET_IN_PORT(this,p) static int impl_node_enum_params(void *object, int seq, uint32_t id, uint32_t start, uint32_t num, const struct spa_pod *filter) { struct impl *this = object; struct spa_pod *param; struct spa_pod_builder b = { 0 }; uint8_t buffer[1024]; struct spa_result_node_params result; uint32_t count = 0; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(num != 0, -EINVAL); result.id = id; result.next = start; next: result.index = result.next++; spa_pod_builder_init(&b, buffer, sizeof(buffer)); switch (id) { case SPA_PARAM_PropInfo: return 0; case SPA_PARAM_Props: return 0; case SPA_PARAM_EnumFormat: case SPA_PARAM_Format: switch (result.index) { case 0: param = spa_format_audio_dsp_build(&b, id, &this->current_format.info.dsp); break; default: return 0; } break; case SPA_PARAM_IO: switch (result.index) { case 0: param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamIO, id, SPA_PARAM_IO_id, SPA_POD_Id(SPA_IO_Clock), SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_clock))); break; case 1: param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamIO, id, SPA_PARAM_IO_id, SPA_POD_Id(SPA_IO_Position), SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_position))); break; default: return 0; } break; default: return -ENOENT; } if (spa_pod_filter(&b, &result.param, param, filter) < 0) goto next; spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); if (++count != num) goto next; return 0; } static inline bool is_following(struct impl *impl) { return impl->position && impl->clock && impl->position->clock.id != impl->clock->id; } static int impl_node_set_io(void *object, uint32_t id, void *data, size_t size) { struct impl *this = object; spa_return_val_if_fail(this != NULL, -EINVAL); switch (id) { case SPA_IO_Clock: this->clock = data; break; case SPA_IO_Position: this->position = data; break; default: return -ENOENT; } return 0; } static int impl_node_set_param(void *object, uint32_t id, uint32_t flags, const struct spa_pod *param) { struct impl *this = object; spa_return_val_if_fail(this != NULL, -EINVAL); switch (id) { default: return -ENOENT; } return 0; } static int impl_node_send_command(void *object, const struct spa_command *command) { struct impl *this = object; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(command != NULL, -EINVAL); switch (SPA_NODE_COMMAND_ID(command)) { case SPA_NODE_COMMAND_Start: if (this->started) return 0; this->started = true; break; case SPA_NODE_COMMAND_Pause: if (!this->started) return 0; this->started = false; break; default: return -ENOTSUP; } return 0; } static void emit_node_info(struct impl *this, bool full) { uint64_t old = full ? this->info.change_mask : 0; if (full) this->info.change_mask = this->info_all; if (this->info.change_mask) { struct spa_dict_item items[8]; char latency[64]; snprintf(latency, sizeof(latency), "%d/%d", this->client->buffer_size, this->client->frame_rate); items[0] = SPA_DICT_ITEM_INIT(SPA_KEY_MEDIA_CLASS, "Audio/Sink"); items[1] = SPA_DICT_ITEM_INIT(SPA_KEY_NODE_NAME, "JACK Sink"); items[2] = SPA_DICT_ITEM_INIT(SPA_KEY_NODE_DRIVER, "true"); items[3] = SPA_DICT_ITEM_INIT(SPA_KEY_NODE_PAUSE_ON_IDLE, "false"); items[4] = SPA_DICT_ITEM_INIT(SPA_KEY_NODE_ALWAYS_PROCESS, "true"); items[5] = SPA_DICT_ITEM_INIT("priority.driver", "30001"); items[6] = SPA_DICT_ITEM_INIT("node.group", "jack-group"); items[7] = SPA_DICT_ITEM_INIT(SPA_KEY_NODE_LATENCY, latency); this->info.props = &SPA_DICT_INIT_ARRAY(items); spa_node_emit_info(&this->hooks, &this->info); this->info.change_mask = old; } } static void emit_port_info(struct impl *this, struct port *port, bool full) { uint64_t old = full ? port->info.change_mask : 0; if (full) port->info.change_mask = port->info_all; if (port->info.change_mask) { char* aliases[2]; int n_aliases, n_items; aliases[0] = alloca(jack_port_name_size()); aliases[1] = alloca(jack_port_name_size()); n_aliases = jack_port_get_aliases(port->jack_port, aliases); n_items = 1; port->items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_PORT_NAME, jack_port_short_name(port->jack_port)); if (n_aliases > 0) port->items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_OBJECT_PATH, aliases[0]); if (n_aliases > 1) port->items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_PORT_ALIAS, aliases[1]); port->props = SPA_DICT_INIT(port->items, n_items); spa_node_emit_port_info(&this->hooks, SPA_DIRECTION_INPUT, port->id, &port->info); port->info.change_mask = old; } } static int impl_node_add_listener(void *object, struct spa_hook *listener, const struct spa_node_events *events, void *data) { struct impl *this = object; struct spa_hook_list save; uint32_t i; spa_return_val_if_fail(this != NULL, -EINVAL); spa_hook_list_isolate(&this->hooks, &save, listener, events, data); emit_node_info(this, true); for (i = 0; i < this->n_in_ports; i++) emit_port_info(this, GET_IN_PORT(this, i), true); spa_hook_list_join(&this->hooks, &save); return 0; } static int impl_node_set_callbacks(void *object, const struct spa_node_callbacks *callbacks, void *data) { struct impl *this = object; spa_return_val_if_fail(this != NULL, -EINVAL); this->callbacks = SPA_CALLBACKS_INIT(callbacks, data); return 0; } static void client_process(void *data) { struct impl *this = data; if (is_following(this)) return; if (this->clock) { struct spa_io_clock *c = this->clock; c->nsec = this->client->current_usecs * SPA_NSEC_PER_USEC; c->rate = SPA_FRACTION(1, this->client->frame_rate); c->position = this->client->current_frames; c->duration = this->client->buffer_size; c->delay = 0; c->rate_diff = 1.0; c->next_nsec = this->client->next_usecs * SPA_NSEC_PER_USEC; } if (this->position) { jack_position_t *jp = &this->client->pos; struct spa_io_position *p = this->position; struct spa_io_segment *s; p->n_segments = 1; s = &p->segments[0]; s->flags = 0; s->position = jp->frame; s->rate = 1.0; if (jp->valid & JackPositionBBT) { s->bar.flags = SPA_IO_SEGMENT_BAR_FLAG_VALID; if (jp->valid & JackBBTFrameOffset) s->bar.offset = jp->bbt_offset; else s->bar.offset = 0; s->bar.signature_num = jp->beats_per_bar; s->bar.signature_denom = jp->beat_type; s->bar.bpm = jp->beats_per_minute; s->bar.beat = jp->bar * jp->beats_per_bar + jp->beat; } } spa_node_call_ready(&this->callbacks, SPA_STATUS_NEED_DATA); } static const struct spa_jack_client_events client_events = { SPA_VERSION_JACK_CLIENT_EVENTS, .process = client_process, }; static int init_port(struct impl *this, struct port *port) { port->info_all = SPA_PORT_CHANGE_MASK_FLAGS | SPA_PORT_CHANGE_MASK_PROPS | SPA_PORT_CHANGE_MASK_PARAMS; port->info = SPA_PORT_INFO_INIT(); port->info.flags = SPA_PORT_FLAG_NO_REF; port->items[0] = SPA_DICT_ITEM_INIT(SPA_KEY_FORMAT_DSP, "32 bit float mono audio"); port->props = SPA_DICT_INIT(port->items, 1); port->info.props = &port->props; port->params[0] = SPA_PARAM_INFO(SPA_PARAM_EnumFormat, SPA_PARAM_INFO_READ); port->params[1] = SPA_PARAM_INFO(SPA_PARAM_Meta, SPA_PARAM_INFO_READ); port->params[2] = SPA_PARAM_INFO(SPA_PARAM_IO, SPA_PARAM_INFO_READ); port->params[3] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); port->params[4] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); port->info.params = port->params; port->info.n_params = 5; return 0; } static int init_ports(struct impl *this) { const char **ports; uint32_t i; jack_client_t *client = this->client->client; int res; ports = jack_get_ports(client, NULL, JACK_DEFAULT_AUDIO_TYPE, JackPortIsPhysical|JackPortIsInput); if (ports == NULL) { spa_log_error(this->log, "%p: can't enumerate ports", this); res = -ENODEV; goto exit; } for (i = 0; ports[i]; i++) { struct port *port = GET_IN_PORT(this, i); jack_port_t *p = jack_port_by_name(client, ports[i]); char *aliases[2]; int n_aliases; port->id = i; port->jack_port = jack_port_register(client, jack_port_short_name(p), jack_port_type(p), JackPortIsOutput, 0); if (port->jack_port == NULL) { spa_log_error(this->log, "%p: jack_port_register() %d (%s) failed", this, i, ports[i]); res = -EFAULT; goto exit_free; } aliases[0] = alloca(jack_port_name_size()); aliases[1] = alloca(jack_port_name_size()); n_aliases = jack_port_get_aliases(p, aliases); if (n_aliases > 0) jack_port_set_alias(port->jack_port, aliases[0]); if (n_aliases > 1) jack_port_set_alias(port->jack_port, aliases[1]); init_port(this, port); } this->n_in_ports = i; this->current_format.info.dsp = SPA_AUDIO_INFO_DSP_INIT( .format = SPA_AUDIO_FORMAT_DSP_F32); spa_jack_client_add_listener(this->client, &this->client_listener, &client_events, this); jack_activate(client); for (i = 0; ports[i]; i++) { struct port *port = GET_IN_PORT(this, i); if (jack_connect(client, jack_port_name(port->jack_port), ports[i])) { spa_log_warn(this->log, "%p: Failed to connect %s to %s", this, jack_port_name(port->jack_port), ports[i]); } } res = 0; exit_free: jack_free(ports); exit: return res; } static int impl_node_add_port(void *object, enum spa_direction direction, uint32_t port_id, const struct spa_dict *props) { return -ENOTSUP; } static int impl_node_remove_port(void *object, enum spa_direction direction, uint32_t port_id) { return -ENOTSUP; } static int port_enum_formats(struct impl *this, enum spa_direction direction, uint32_t port_id, uint32_t index, const struct spa_pod *filter, struct spa_pod **param, struct spa_pod_builder *builder) { switch (index) { case 0: *param = spa_pod_builder_add_object(builder, SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_audio), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_dsp), SPA_FORMAT_AUDIO_format, SPA_POD_Id(SPA_AUDIO_FORMAT_DSP_F32)); break; default: return 0; } return 1; } static int impl_node_port_enum_params(void *object, int seq, enum spa_direction direction, uint32_t port_id, uint32_t id, uint32_t start, uint32_t num, const struct spa_pod *filter) { struct impl *this = object; struct port *port; struct spa_pod_builder b = { 0 }; uint8_t buffer[1024]; struct spa_pod *param; struct spa_result_node_params result; uint32_t count = 0; int res; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(num != 0, -EINVAL); spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); port = GET_PORT(this, direction, port_id); result.id = id; result.next = start; next: result.index = result.next++; spa_pod_builder_init(&b, buffer, sizeof(buffer)); switch (id) { case SPA_PARAM_EnumFormat: if ((res = port_enum_formats(this, direction, port_id, result.index, filter, ¶m, &b)) <= 0) return res; break; case SPA_PARAM_Format: if (!port->have_format) return -EIO; if (result.index > 0) return 0; param = spa_format_audio_dsp_build(&b, id, &port->current_format.info.dsp); break; case SPA_PARAM_Buffers: { if (!port->have_format) return -EIO; if (result.index > 0) return 0; param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamBuffers, id, SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(1, 1, MAX_BUFFERS), SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(1), SPA_PARAM_BUFFERS_size, SPA_POD_CHOICE_RANGE_Int( MAX_SAMPLES * port->stride, 16 * port->stride, MAX_SAMPLES * port->stride), SPA_PARAM_BUFFERS_stride, SPA_POD_Int(port->stride)); break; } case SPA_PARAM_IO: switch (result.index) { case 0: param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamIO, id, SPA_PARAM_IO_id, SPA_POD_Id(SPA_IO_Buffers), SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_buffers))); break; default: return 0; } break; default: return -ENOENT; } if (spa_pod_filter(&b, &result.param, param, filter) < 0) goto next; spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); if (++count != num) goto next; return 0; } static int clear_buffers(struct impl *this, struct port *port) { if (port->n_buffers > 0) { spa_log_debug(this->log, "%p: clear buffers", this); port->n_buffers = 0; this->started = false; } return 0; } static int port_set_format(struct impl *this, struct port *port, uint32_t flags, const struct spa_pod *format) { int res; if (format == NULL) { port->have_format = false; clear_buffers(this, port); } else { struct spa_audio_info info = { 0 }; if ((res = spa_format_parse(format, &info.media_type, &info.media_subtype)) < 0) return res; if (info.media_type != SPA_MEDIA_TYPE_audio && info.media_subtype != SPA_MEDIA_SUBTYPE_dsp) return -EINVAL; if (spa_format_audio_dsp_parse(format, &info.info.dsp) < 0) return -EINVAL; if (info.info.dsp.format != SPA_AUDIO_FORMAT_DSP_F32) return -EINVAL; port->stride = 4; port->current_format = info; port->have_format = true; } port->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; if (port->have_format) { port->params[3] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_READWRITE); port->params[4] = SPA_PARAM_INFO(SPA_PARAM_Buffers, SPA_PARAM_INFO_READ); } else { port->params[3] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); port->params[4] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); } emit_port_info(this, port, false); return 0; } static int impl_node_port_set_param(void *object, enum spa_direction direction, uint32_t port_id, uint32_t id, uint32_t flags, const struct spa_pod *param) { struct impl *this = object; struct port *port; int res; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); port = GET_PORT(this, direction, port_id); switch (id) { case SPA_PARAM_Format: res = port_set_format(this, port, flags, param); break; default: return -ENOENT; } return res; } static int impl_node_port_use_buffers(void *object, enum spa_direction direction, uint32_t port_id, uint32_t flags, struct spa_buffer **buffers, uint32_t n_buffers) { struct impl *this = object; struct port *port; uint32_t i; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); port = GET_PORT(this, direction, port_id); clear_buffers(this, port); if (n_buffers > 0 && !port->have_format) return -EIO; if (n_buffers > MAX_BUFFERS) return -ENOSPC; for (i = 0; i < n_buffers; i++) { struct buffer *b; b = &port->buffers[i]; b->id = i; b->outbuf = buffers[i]; b->flags = 0; } port->n_buffers = n_buffers; return 0; } static int impl_node_port_set_io(void *object, enum spa_direction direction, uint32_t port_id, uint32_t id, void *data, size_t size) { struct impl *this = object; struct port *port; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); port = GET_PORT(this, direction, port_id); switch (id) { case SPA_IO_Buffers: port->io = data; break; default: return -ENOENT; } return 0; } static int impl_node_port_reuse_buffer(void *object, uint32_t port_id, uint32_t buffer_id) { return -ENOTSUP; } static int impl_node_process(void *object) { struct impl *this = object; uint32_t i; int res = 0; spa_log_trace(this->log, "%p: process %d", this, this->n_in_ports); for (i = 0; i < this->n_in_ports; i++) { struct port *port = GET_IN_PORT(this, i); struct spa_io_buffers *io = port->io; struct buffer *b; struct spa_data *src; uint32_t n_frames = this->client->buffer_size; void *dst; dst = jack_port_get_buffer(port->jack_port, n_frames); if (io == NULL || io->status != SPA_STATUS_HAVE_DATA || io->buffer_id >= port->n_buffers) { memset(dst, 0, n_frames * sizeof(float)); continue; } spa_log_trace(this->log, "%p: port %d: buffer %d", this, i, io->buffer_id); b = &port->buffers[io->buffer_id]; src = &b->outbuf->datas[0]; spa_memcpy(dst, src->data, n_frames * port->stride); io->status = SPA_STATUS_NEED_DATA; } return res | SPA_STATUS_NEED_DATA; } static const struct spa_node_methods impl_node = { SPA_VERSION_NODE_METHODS, .add_listener = impl_node_add_listener, .set_callbacks = impl_node_set_callbacks, .enum_params = impl_node_enum_params, .set_param = impl_node_set_param, .set_io = impl_node_set_io, .send_command = impl_node_send_command, .add_port = impl_node_add_port, .remove_port = impl_node_remove_port, .port_enum_params = impl_node_port_enum_params, .port_set_param = impl_node_port_set_param, .port_use_buffers = impl_node_port_use_buffers, .port_set_io = impl_node_port_set_io, .port_reuse_buffer = impl_node_port_reuse_buffer, .process = impl_node_process, }; static int impl_get_interface(struct spa_handle *handle, const char *type, void **interface) { struct impl *this; spa_return_val_if_fail(handle != NULL, -EINVAL); spa_return_val_if_fail(interface != NULL, -EINVAL); this = (struct impl *) handle; if (spa_streq(type, SPA_TYPE_INTERFACE_Node)) *interface = &this->node; else return -ENOENT; return 0; } static int impl_clear(struct spa_handle *handle) { return 0; } static size_t impl_get_size(const struct spa_handle_factory *factory, const struct spa_dict *params) { return sizeof(struct impl); } static int impl_init(const struct spa_handle_factory *factory, struct spa_handle *handle, const struct spa_dict *info, const struct spa_support *support, uint32_t n_support) { struct impl *this; const char *str; spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(handle != NULL, -EINVAL); handle->get_interface = impl_get_interface; handle->clear = impl_clear; this = (struct impl *) handle; this->log = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log); if (info && (str = spa_dict_lookup(info, SPA_KEY_API_JACK_CLIENT))) sscanf(str, "pointer:%p", &this->client); if (this->client == NULL) { spa_log_error(this->log, "%p: missing "SPA_KEY_API_JACK_CLIENT " property", this); return -EINVAL; } spa_hook_list_init(&this->hooks); this->node.iface = SPA_INTERFACE_INIT( SPA_TYPE_INTERFACE_Node, SPA_VERSION_NODE, &impl_node, this); this->info_all = SPA_NODE_CHANGE_MASK_FLAGS | SPA_NODE_CHANGE_MASK_PROPS | SPA_NODE_CHANGE_MASK_PARAMS; this->info = SPA_NODE_INFO_INIT(); this->info.max_output_ports = MAX_PORTS; this->info.flags = SPA_NODE_FLAG_RT; this->params[0] = SPA_PARAM_INFO(SPA_PARAM_PropInfo, SPA_PARAM_INFO_READ); this->params[1] = SPA_PARAM_INFO(SPA_PARAM_Props, SPA_PARAM_INFO_READWRITE); this->params[2] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_READ); this->params[3] = SPA_PARAM_INFO(SPA_PARAM_EnumFormat, SPA_PARAM_INFO_READ); this->params[4] = SPA_PARAM_INFO(SPA_PARAM_IO, SPA_PARAM_INFO_READ); this->info.params = this->params; this->info.n_params = 5; init_ports(this); return 0; } static const struct spa_interface_info impl_interfaces[] = { {SPA_TYPE_INTERFACE_Node,}, }; static int impl_enum_interface_info(const struct spa_handle_factory *factory, const struct spa_interface_info **info, uint32_t *index) { spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(info != NULL, -EINVAL); spa_return_val_if_fail(index != NULL, -EINVAL); switch (*index) { case 0: *info = &impl_interfaces[*index]; break; default: return 0; } (*index)++; return 1; } static const struct spa_dict_item info_items[] = { { SPA_KEY_FACTORY_AUTHOR, "Wim Taymans " }, { SPA_KEY_FACTORY_DESCRIPTION, "Play audio with the JACK API" }, }; static const struct spa_dict info = SPA_DICT_INIT_ARRAY(info_items); const struct spa_handle_factory spa_jack_sink_factory = { SPA_VERSION_HANDLE_FACTORY, SPA_NAME_API_JACK_SINK, &info, impl_get_size, impl_init, impl_enum_interface_info, }; pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/jack/jack-source.c000066400000000000000000000546651511204443500262440ustar00rootroot00000000000000/* Spa jack client */ /* SPDX-FileCopyrightText: Copyright © 2019 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "jack-client.h" #undef SPA_LOG_TOPIC_DEFAULT #define SPA_LOG_TOPIC_DEFAULT &log_topic SPA_LOG_TOPIC_DEFINE_STATIC(log_topic, "spa.jack-source"); #define MAX_PORTS 128 #define MAX_BUFFERS 8 #define MAX_SAMPLES 8192 struct buffer { uint32_t id; #define BUFFER_FLAG_OUT (1<<0) uint32_t flags; struct spa_buffer *outbuf; struct spa_list link; }; struct port { uint32_t id; uint64_t info_all; struct spa_port_info info; struct spa_dict_item items[4]; struct spa_dict props; struct spa_param_info params[5]; unsigned int have_format:1; struct spa_audio_info current_format; int stride; struct spa_io_buffers *io; struct buffer buffers[MAX_BUFFERS]; uint32_t n_buffers; struct spa_list empty; jack_port_t *jack_port; }; struct impl { struct spa_handle handle; struct spa_node node; struct spa_log *log; uint64_t info_all; struct spa_node_info info; struct spa_param_info params[5]; struct spa_hook_list hooks; struct spa_callbacks callbacks; struct spa_io_clock *clock; struct spa_io_position *position; struct port out_ports[MAX_PORTS]; uint32_t n_out_ports; struct spa_audio_info current_format; struct spa_jack_client *client; struct spa_hook client_listener; unsigned int started:1; }; #define CHECK_OUT_PORT(this,p) ((p) < this->n_out_ports) #define CHECK_PORT(this,d,p) (d == SPA_DIRECTION_OUTPUT && CHECK_OUT_PORT(this,p)) #define GET_OUT_PORT(this,p) (&this->out_ports[p]) #define GET_PORT(this,d,p) GET_OUT_PORT(this,p) static int impl_node_enum_params(void *object, int seq, uint32_t id, uint32_t start, uint32_t num, const struct spa_pod *filter) { struct impl *this = object; struct spa_pod *param; struct spa_pod_builder b = { 0 }; uint8_t buffer[1024]; struct spa_result_node_params result; uint32_t count = 0; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(num != 0, -EINVAL); result.id = id; result.next = start; next: result.index = result.next++; spa_pod_builder_init(&b, buffer, sizeof(buffer)); switch (id) { case SPA_PARAM_PropInfo: return 0; case SPA_PARAM_Props: return 0; case SPA_PARAM_EnumFormat: case SPA_PARAM_Format: switch (result.index) { case 0: param = spa_format_audio_dsp_build(&b, id, &this->current_format.info.dsp); break; default: return 0; } break; case SPA_PARAM_IO: switch (result.index) { case 0: param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamIO, id, SPA_PARAM_IO_id, SPA_POD_Id(SPA_IO_Clock), SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_clock))); break; case 1: param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamIO, id, SPA_PARAM_IO_id, SPA_POD_Id(SPA_IO_Position), SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_position))); break; default: return 0; } break; default: return -ENOENT; } if (spa_pod_filter(&b, &result.param, param, filter) < 0) goto next; spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); if (++count != num) goto next; return 0; } static int impl_node_set_io(void *object, uint32_t id, void *data, size_t size) { struct impl *this = object; spa_return_val_if_fail(this != NULL, -EINVAL); switch (id) { case SPA_IO_Clock: this->clock = data; break; case SPA_IO_Position: this->position = data; break; default: return -ENOENT; } return 0; } static int impl_node_set_param(void *object, uint32_t id, uint32_t flags, const struct spa_pod *param) { struct impl *this = object; spa_return_val_if_fail(this != NULL, -EINVAL); switch (id) { default: return -ENOENT; } return 0; } static inline void reuse_buffer(struct impl *this, struct port *port, uint32_t id) { struct buffer *b = &port->buffers[id]; if (SPA_FLAG_IS_SET(b->flags, BUFFER_FLAG_OUT)) { spa_log_trace(this->log, "%p: reuse buffer %d", this, id); SPA_FLAG_CLEAR(b->flags, BUFFER_FLAG_OUT); spa_list_append(&port->empty, &b->link); } } static struct buffer *dequeue_buffer(struct impl *this, struct port *port) { struct buffer *b; if (spa_list_is_empty(&port->empty)) return NULL; b = spa_list_first(&port->empty, struct buffer, link); spa_list_remove(&b->link); SPA_FLAG_SET(b->flags, BUFFER_FLAG_OUT); return b; } static int impl_node_send_command(void *object, const struct spa_command *command) { struct impl *this = object; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(command != NULL, -EINVAL); switch (SPA_NODE_COMMAND_ID(command)) { case SPA_NODE_COMMAND_Start: if (this->started) return 0; this->started = true; break; case SPA_NODE_COMMAND_Pause: if (!this->started) return 0; this->started = false; break; default: return -ENOTSUP; } return 0; } static void emit_node_info(struct impl *this, bool full) { uint64_t old = full ? this->info.change_mask : 0; if (full) this->info.change_mask = this->info_all; if (this->info.change_mask) { struct spa_dict_item items[8]; char latency[64]; snprintf(latency, sizeof(latency), "%d/%d", this->client->buffer_size, this->client->frame_rate); items[0] = SPA_DICT_ITEM_INIT(SPA_KEY_MEDIA_CLASS, "Audio/Source"); items[1] = SPA_DICT_ITEM_INIT(SPA_KEY_NODE_NAME, "JACK Source"); items[2] = SPA_DICT_ITEM_INIT(SPA_KEY_NODE_DRIVER, "true"); items[3] = SPA_DICT_ITEM_INIT(SPA_KEY_NODE_PAUSE_ON_IDLE, "false"); items[4] = SPA_DICT_ITEM_INIT(SPA_KEY_NODE_ALWAYS_PROCESS, "true"); items[5] = SPA_DICT_ITEM_INIT("priority.driver", "30000"); items[6] = SPA_DICT_ITEM_INIT("node.group", "jack-group"); items[7] = SPA_DICT_ITEM_INIT(SPA_KEY_NODE_LATENCY, latency); this->info.props = &SPA_DICT_INIT_ARRAY(items); spa_node_emit_info(&this->hooks, &this->info); this->info.change_mask = old; } } static void emit_port_info(struct impl *this, struct port *port, bool full) { uint64_t old = full ? port->info.change_mask : 0; if (full) port->info.change_mask = port->info_all; if (port->info.change_mask) { char* aliases[2]; int n_aliases, n_items; aliases[0] = alloca(jack_port_name_size()); aliases[1] = alloca(jack_port_name_size()); n_aliases = jack_port_get_aliases(port->jack_port, aliases); n_items = 1; port->items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_PORT_NAME, jack_port_short_name(port->jack_port)); if (n_aliases > 0) port->items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_OBJECT_PATH, aliases[0]); if (n_aliases > 1) port->items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_PORT_ALIAS, aliases[1]); port->props = SPA_DICT_INIT(port->items, n_items); spa_node_emit_port_info(&this->hooks, SPA_DIRECTION_OUTPUT, port->id, &port->info); port->info.change_mask = old; } } static int impl_node_add_listener(void *object, struct spa_hook *listener, const struct spa_node_events *events, void *data) { struct impl *this = object; struct spa_hook_list save; uint32_t i; spa_return_val_if_fail(this != NULL, -EINVAL); spa_hook_list_isolate(&this->hooks, &save, listener, events, data); emit_node_info(this, true); for (i = 0; i < this->n_out_ports; i++) emit_port_info(this, GET_OUT_PORT(this, i), true); spa_hook_list_join(&this->hooks, &save); return 0; } static int impl_node_set_callbacks(void *object, const struct spa_node_callbacks *callbacks, void *data) { struct impl *this = object; spa_return_val_if_fail(this != NULL, -EINVAL); this->callbacks = SPA_CALLBACKS_INIT(callbacks, data); return 0; } static inline bool is_following(struct impl *impl) { return impl->position && impl->clock && impl->position->clock.id != impl->clock->id; } static void client_process(void *data) { struct impl *this = data; int res; if (is_following(this)) return; spa_log_trace_fp(this->log, "%p, process", this); res = spa_node_process(&this->node); spa_node_call_ready(&this->callbacks, res); } static const struct spa_jack_client_events client_events = { SPA_VERSION_JACK_CLIENT_EVENTS, .process = client_process, }; static int init_port(struct impl *this, struct port *port) { port->info_all = SPA_PORT_CHANGE_MASK_FLAGS | SPA_PORT_CHANGE_MASK_PROPS | SPA_PORT_CHANGE_MASK_PARAMS; port->info = SPA_PORT_INFO_INIT(); port->info.flags = SPA_PORT_FLAG_NO_REF; port->items[0] = SPA_DICT_ITEM_INIT(SPA_KEY_FORMAT_DSP, "32 bit float mono audio"); port->props = SPA_DICT_INIT(port->items, 1); port->info.props = &port->props; port->params[0] = SPA_PARAM_INFO(SPA_PARAM_EnumFormat, SPA_PARAM_INFO_READ); port->params[1] = SPA_PARAM_INFO(SPA_PARAM_Meta, SPA_PARAM_INFO_READ); port->params[2] = SPA_PARAM_INFO(SPA_PARAM_IO, SPA_PARAM_INFO_READ); port->params[3] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); port->params[4] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); port->info.params = port->params; port->info.n_params = 5; spa_list_init(&port->empty); return 0; } static int init_ports(struct impl *this) { const char **ports; jack_client_t *client = this->client->client; uint32_t i; int res; ports = jack_get_ports(client, NULL, JACK_DEFAULT_AUDIO_TYPE, JackPortIsPhysical|JackPortIsOutput); if (ports == NULL) { spa_log_error(this->log, "%p: can't enumerate ports", this); res = -ENODEV; goto exit; } for (i = 0; ports[i]; i++) { struct port *port = GET_OUT_PORT(this, i); jack_port_t *p = jack_port_by_name(client, ports[i]); char *aliases[2]; int n_aliases; port->id = i; port->jack_port = jack_port_register(client, jack_port_short_name(p), jack_port_type(p), JackPortIsInput, 0); if (port->jack_port == NULL) { spa_log_error(this->log, "%p: jack_port_register() %d (%s) failed", this, i, ports[i]); res = -EFAULT; goto exit_free; } aliases[0] = alloca(jack_port_name_size()); aliases[1] = alloca(jack_port_name_size()); n_aliases = jack_port_get_aliases(p, aliases); if (n_aliases > 0) jack_port_set_alias(port->jack_port, aliases[0]); if (n_aliases > 1) jack_port_set_alias(port->jack_port, aliases[1]); init_port(this, port); } this->n_out_ports = i; this->current_format.info.dsp = SPA_AUDIO_INFO_DSP_INIT( .format = SPA_AUDIO_FORMAT_DSP_F32); spa_jack_client_add_listener(this->client, &this->client_listener, &client_events, this); jack_activate(client); for (i = 0; ports[i]; i++) { struct port *port = GET_OUT_PORT(this, i); if (jack_connect(client, ports[i], jack_port_name(port->jack_port))) { spa_log_warn(this->log, "%p: Failed to connect %s to %s", this, jack_port_name(port->jack_port), ports[i]); } } res = 0; exit_free: jack_free(ports); exit: return res; } static int impl_node_add_port(void *object, enum spa_direction direction, uint32_t port_id, const struct spa_dict *props) { return -ENOTSUP; } static int impl_node_remove_port(void *object, enum spa_direction direction, uint32_t port_id) { return -ENOTSUP; } static int port_enum_formats(struct impl *this, enum spa_direction direction, uint32_t port_id, uint32_t index, const struct spa_pod *filter, struct spa_pod **param, struct spa_pod_builder *builder) { switch (index) { case 0: *param = spa_pod_builder_add_object(builder, SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_audio), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_dsp), SPA_FORMAT_AUDIO_format, SPA_POD_Id(SPA_AUDIO_FORMAT_DSP_F32)); break; default: return 0; } return 1; } static int impl_node_port_enum_params(void *object, int seq, enum spa_direction direction, uint32_t port_id, uint32_t id, uint32_t start, uint32_t num, const struct spa_pod *filter) { struct impl *this = object; struct port *port; struct spa_pod_builder b = { 0 }; uint8_t buffer[1024]; struct spa_pod *param; struct spa_result_node_params result; uint32_t count = 0; int res; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(num != 0, -EINVAL); spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); port = GET_PORT(this, direction, port_id); result.id = id; result.next = start; next: result.index = result.next++; spa_pod_builder_init(&b, buffer, sizeof(buffer)); switch (id) { case SPA_PARAM_EnumFormat: if ((res = port_enum_formats(this, direction, port_id, result.index, filter, ¶m, &b)) <= 0) return res; break; case SPA_PARAM_Format: if (!port->have_format) return -EIO; if (result.index > 0) return 0; param = spa_format_audio_dsp_build(&b, id, &port->current_format.info.dsp); break; case SPA_PARAM_Buffers: { if (!port->have_format) return -EIO; if (result.index > 0) return 0; param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamBuffers, id, SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(1, 1, MAX_BUFFERS), SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(1), SPA_PARAM_BUFFERS_size, SPA_POD_CHOICE_RANGE_Int( MAX_SAMPLES * port->stride, 16 * port->stride, MAX_SAMPLES * port->stride), SPA_PARAM_BUFFERS_stride, SPA_POD_Int(port->stride)); break; } case SPA_PARAM_IO: switch (result.index) { case 0: param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamIO, id, SPA_PARAM_IO_id, SPA_POD_Id(SPA_IO_Buffers), SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_buffers))); break; default: return 0; } break; default: return -ENOENT; } if (spa_pod_filter(&b, &result.param, param, filter) < 0) goto next; spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); if (++count != num) goto next; return 0; } static int clear_buffers(struct impl *this, struct port *port) { if (port->n_buffers > 0) { spa_log_debug(this->log, "%p: clear buffers", this); port->n_buffers = 0; spa_list_init(&port->empty); this->started = false; } return 0; } static int port_set_format(struct impl *this, struct port *port, uint32_t flags, const struct spa_pod *format) { int res; if (format == NULL) { port->have_format = false; clear_buffers(this, port); } else { struct spa_audio_info info = { 0 }; if ((res = spa_format_parse(format, &info.media_type, &info.media_subtype)) < 0) return res; if (info.media_type != SPA_MEDIA_TYPE_audio && info.media_subtype != SPA_MEDIA_SUBTYPE_dsp) return -EINVAL; if (spa_format_audio_dsp_parse(format, &info.info.dsp) < 0) return -EINVAL; if (info.info.dsp.format != SPA_AUDIO_FORMAT_DSP_F32) return -EINVAL; port->stride = 4; port->current_format = info; port->have_format = true; } port->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; if (port->have_format) { port->params[3] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_READWRITE); port->params[4] = SPA_PARAM_INFO(SPA_PARAM_Buffers, SPA_PARAM_INFO_READ); } else { port->params[3] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); port->params[4] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); } emit_port_info(this, port, false); return 0; } static int impl_node_port_set_param(void *object, enum spa_direction direction, uint32_t port_id, uint32_t id, uint32_t flags, const struct spa_pod *param) { struct impl *this = object; struct port *port; int res; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); port = GET_PORT(this, direction, port_id); switch (id) { case SPA_PARAM_Format: res = port_set_format(this, port, flags, param); break; default: return -ENOENT; } return res; } static int impl_node_port_use_buffers(void *object, enum spa_direction direction, uint32_t port_id, uint32_t flags, struct spa_buffer **buffers, uint32_t n_buffers) { struct impl *this = object; struct port *port; uint32_t i; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); port = GET_PORT(this, direction, port_id); clear_buffers(this, port); if (n_buffers > 0 && !port->have_format) return -EIO; if (n_buffers > MAX_BUFFERS) return -ENOSPC; for (i = 0; i < n_buffers; i++) { struct buffer *b; b = &port->buffers[i]; b->id = i; b->outbuf = buffers[i]; b->flags = 0; spa_list_append(&port->empty, &b->link); } port->n_buffers = n_buffers; return 0; } static int impl_node_port_set_io(void *object, enum spa_direction direction, uint32_t port_id, uint32_t id, void *data, size_t size) { struct impl *this = object; struct port *port; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); port = GET_PORT(this, direction, port_id); switch (id) { case SPA_IO_Buffers: port->io = data; break; default: return -ENOENT; } return 0; } static int impl_node_port_reuse_buffer(void *object, uint32_t port_id, uint32_t buffer_id) { struct impl *this = object; struct port *port; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(CHECK_OUT_PORT(this, port_id), -EINVAL); port = GET_OUT_PORT(this, port_id); spa_return_val_if_fail(buffer_id < port->n_buffers, -EINVAL); reuse_buffer(this, port, buffer_id); return 0; } static int impl_node_process(void *object) { struct impl *this = object; uint32_t i; int res = 0; spa_log_trace(this->log, "%p: process %d", this, this->n_out_ports); for (i = 0; i < this->n_out_ports; i++) { struct port *port = GET_OUT_PORT(this, i); struct spa_io_buffers *io = port->io; struct buffer *b; struct spa_data *d; const void *src; uint32_t n_frames = this->client->buffer_size; if (io == NULL || io->status == SPA_STATUS_HAVE_DATA) continue; if (io->buffer_id < port->n_buffers) { reuse_buffer(this, port, io->buffer_id); io->buffer_id = SPA_ID_INVALID; } if ((b = dequeue_buffer(this, port)) == NULL) { spa_log_trace(this->log, "%p: out of buffers", this); io->status = -EPIPE; continue; } src = jack_port_get_buffer(port->jack_port, n_frames); d = &b->outbuf->datas[0]; spa_memcpy(d->data, src, n_frames * port->stride); d->chunk->offset = 0; d->chunk->size = n_frames * port->stride; d->chunk->stride = port->stride; d->chunk->flags = 0; io->buffer_id = b->id; io->status = SPA_STATUS_HAVE_DATA; res |= SPA_STATUS_HAVE_DATA; } return res | SPA_STATUS_HAVE_DATA; } static const struct spa_node_methods impl_node = { SPA_VERSION_NODE_METHODS, .add_listener = impl_node_add_listener, .set_callbacks = impl_node_set_callbacks, .enum_params = impl_node_enum_params, .set_param = impl_node_set_param, .set_io = impl_node_set_io, .send_command = impl_node_send_command, .add_port = impl_node_add_port, .remove_port = impl_node_remove_port, .port_enum_params = impl_node_port_enum_params, .port_set_param = impl_node_port_set_param, .port_use_buffers = impl_node_port_use_buffers, .port_set_io = impl_node_port_set_io, .port_reuse_buffer = impl_node_port_reuse_buffer, .process = impl_node_process, }; static int impl_get_interface(struct spa_handle *handle, const char *type, void **interface) { struct impl *this; spa_return_val_if_fail(handle != NULL, -EINVAL); spa_return_val_if_fail(interface != NULL, -EINVAL); this = (struct impl *) handle; if (spa_streq(type, SPA_TYPE_INTERFACE_Node)) *interface = &this->node; else return -ENOENT; return 0; } static int impl_clear(struct spa_handle *handle) { return 0; } static size_t impl_get_size(const struct spa_handle_factory *factory, const struct spa_dict *params) { return sizeof(struct impl); } static int impl_init(const struct spa_handle_factory *factory, struct spa_handle *handle, const struct spa_dict *info, const struct spa_support *support, uint32_t n_support) { struct impl *this; const char *str; spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(handle != NULL, -EINVAL); handle->get_interface = impl_get_interface; handle->clear = impl_clear; this = (struct impl *) handle; this->log = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log); if (info && (str = spa_dict_lookup(info, SPA_KEY_API_JACK_CLIENT))) sscanf(str, "pointer:%p", &this->client); if (this->client == NULL) { spa_log_error(this->log, "%p: missing "SPA_KEY_API_JACK_CLIENT " property", this); return -EINVAL; } spa_hook_list_init(&this->hooks); this->node.iface = SPA_INTERFACE_INIT( SPA_TYPE_INTERFACE_Node, SPA_VERSION_NODE, &impl_node, this); this->info_all = SPA_NODE_CHANGE_MASK_FLAGS | SPA_NODE_CHANGE_MASK_PROPS | SPA_NODE_CHANGE_MASK_PARAMS; this->info = SPA_NODE_INFO_INIT(); this->info.max_output_ports = MAX_PORTS; this->info.flags = SPA_NODE_FLAG_RT; this->params[0] = SPA_PARAM_INFO(SPA_PARAM_PropInfo, SPA_PARAM_INFO_READ); this->params[1] = SPA_PARAM_INFO(SPA_PARAM_Props, SPA_PARAM_INFO_READWRITE); this->params[2] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_READ); this->params[3] = SPA_PARAM_INFO(SPA_PARAM_EnumFormat, SPA_PARAM_INFO_READ); this->params[4] = SPA_PARAM_INFO(SPA_PARAM_IO, SPA_PARAM_INFO_READ); this->info.params = this->params; this->info.n_params = 5; init_ports(this); return 0; } static const struct spa_interface_info impl_interfaces[] = { {SPA_TYPE_INTERFACE_Node,}, }; static int impl_enum_interface_info(const struct spa_handle_factory *factory, const struct spa_interface_info **info, uint32_t *index) { spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(info != NULL, -EINVAL); spa_return_val_if_fail(index != NULL, -EINVAL); switch (*index) { case 0: *info = &impl_interfaces[*index]; break; default: return 0; } (*index)++; return 1; } static const struct spa_dict_item info_items[] = { { SPA_KEY_FACTORY_AUTHOR, "Wim Taymans " }, { SPA_KEY_FACTORY_DESCRIPTION, "Record audio with the JACK API" }, }; static const struct spa_dict info = SPA_DICT_INIT_ARRAY(info_items); const struct spa_handle_factory spa_jack_source_factory = { SPA_VERSION_HANDLE_FACTORY, SPA_NAME_API_JACK_SOURCE, &info, impl_get_size, impl_init, impl_enum_interface_info, }; pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/jack/meson.build000066400000000000000000000005111511204443500260110ustar00rootroot00000000000000spa_jack_sources = [ 'plugin.c', 'jack-client.c', 'jack-device.c', 'jack-sink.c', 'jack-source.c'] spa_jack = shared_library('spa-jack', spa_jack_sources, dependencies : [ spa_dep, jack_dep, mathlib ], install : true, install_dir : spa_plugindir / 'jack') pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/jack/plugin.c000066400000000000000000000015471511204443500253230ustar00rootroot00000000000000/* Spa jack plugin */ /* SPDX-FileCopyrightText: Copyright © 2019 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include extern const struct spa_handle_factory spa_jack_device_factory; extern const struct spa_handle_factory spa_jack_source_factory; extern const struct spa_handle_factory spa_jack_sink_factory; SPA_LOG_TOPIC_ENUM_DEFINE_REGISTERED; SPA_EXPORT int spa_handle_factory_enum(const struct spa_handle_factory **factory, uint32_t *index) { spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(index != NULL, -EINVAL); switch (*index) { case 0: *factory = &spa_jack_device_factory; break; case 1: *factory = &spa_jack_source_factory; break; case 2: *factory = &spa_jack_sink_factory; break; default: return 0; } (*index)++; return 1; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/libcamera/000077500000000000000000000000001511204443500246615ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/libcamera/libcamera-device.cpp000066400000000000000000000203071511204443500305430ustar00rootroot00000000000000/* Spa libcamera device */ /* SPDX-FileCopyrightText: Copyright © 2020 Collabora Ltd. */ /* @author Raghavendra Rao Sidlagatta */ /* SPDX-FileCopyrightText: Copyright © 2021 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "libcamera.h" #include "libcamera-manager.hpp" #include #include #include using namespace libcamera; namespace { struct impl { struct spa_handle handle; struct spa_device device = {}; struct spa_log *log; std::string device_id; struct spa_hook_list hooks; std::shared_ptr manager; std::shared_ptr camera; impl(spa_log *log, std::shared_ptr manager, std::shared_ptr camera, std::string device_id); }; const libcamera::Span cameraDevice(const Camera& camera) { if (auto devices = camera.properties().get(properties::SystemDevices)) return devices.value(); return {}; } std::string cameraModel(const Camera& camera) { return std::string(camera.properties().get(properties::Model).value_or(camera.id())); } const char *cameraLoc(const Camera& camera) { if (auto location = camera.properties().get(properties::Location)) { switch (location.value()) { case properties::CameraLocationFront: return "front"; case properties::CameraLocationBack: return "back"; case properties::CameraLocationExternal: return "external"; } } return nullptr; } const char *cameraRot(const Camera& camera) { if (auto rotation = camera.properties().get(properties::Rotation)) { switch (rotation.value()) { case 90: return "90"; case 180: return "180"; case 270: return "270"; default: return "0"; } } return nullptr; } int emit_info(struct impl *impl, bool full) { struct spa_dict_item items[10]; struct spa_dict dict; uint32_t n_items = 0; struct spa_device_info info; struct spa_param_info params[2]; Camera& camera = *impl->camera; info = SPA_DEVICE_INFO_INIT(); info.change_mask = SPA_DEVICE_CHANGE_MASK_PROPS; #define ADD_ITEM(key, value) items[n_items++] = SPA_DICT_ITEM_INIT(key, value) const auto path = "libcamera:" + impl->device_id; ADD_ITEM(SPA_KEY_OBJECT_PATH, path.c_str()); ADD_ITEM(SPA_KEY_DEVICE_API, "libcamera"); ADD_ITEM(SPA_KEY_MEDIA_CLASS, "Video/Device"); ADD_ITEM(SPA_KEY_API_LIBCAMERA_PATH, impl->device_id.c_str()); if (auto location = cameraLoc(camera)) ADD_ITEM(SPA_KEY_API_LIBCAMERA_LOCATION, location); if (auto rotation = cameraRot(camera)) ADD_ITEM(SPA_KEY_API_LIBCAMERA_ROTATION, rotation); const auto model = cameraModel(camera); ADD_ITEM(SPA_KEY_DEVICE_PRODUCT_NAME, model.c_str()); ADD_ITEM(SPA_KEY_DEVICE_DESCRIPTION, model.c_str()); const auto name = "libcamera_device." + impl->device_id; ADD_ITEM(SPA_KEY_DEVICE_NAME, name.c_str()); auto device_numbers = cameraDevice(camera); std::string devids; if (!device_numbers.empty()) { std::ostringstream s; /* encode device numbers into a json array */ s << "[ "; for (const auto& devid : device_numbers) s << devid << ' '; s << ']'; devids = std::move(s).str(); ADD_ITEM(SPA_KEY_DEVICE_DEVIDS, devids.c_str()); } #undef ADD_ITEM dict = SPA_DICT_INIT(items, n_items); info.props = &dict; info.change_mask |= SPA_DEVICE_CHANGE_MASK_PARAMS; params[0] = SPA_PARAM_INFO(SPA_PARAM_EnumProfile, SPA_PARAM_INFO_READ); params[1] = SPA_PARAM_INFO(SPA_PARAM_Profile, SPA_PARAM_INFO_WRITE); info.n_params = SPA_N_ELEMENTS(params); info.params = params; spa_device_emit_info(&impl->hooks, &info); if (true) { struct spa_device_object_info oinfo; oinfo = SPA_DEVICE_OBJECT_INFO_INIT(); oinfo.type = SPA_TYPE_INTERFACE_Node; oinfo.factory_name = SPA_NAME_API_LIBCAMERA_SOURCE; oinfo.change_mask = SPA_DEVICE_OBJECT_CHANGE_MASK_PROPS; oinfo.props = &dict; spa_device_emit_object_info(&impl->hooks, 0, &oinfo); } return 0; } int impl_add_listener(void *object, struct spa_hook *listener, const struct spa_device_events *events, void *data) { struct impl *impl = (struct impl*)object; struct spa_hook_list save; int res = 0; spa_return_val_if_fail(impl != nullptr, -EINVAL); spa_return_val_if_fail(events != nullptr, -EINVAL); spa_hook_list_isolate(&impl->hooks, &save, listener, events, data); if (events->info || events->object_info) res = emit_info(impl, true); spa_hook_list_join(&impl->hooks, &save); return res; } int impl_sync(void *object, int seq) { struct impl *impl = (struct impl*) object; spa_return_val_if_fail(impl != nullptr, -EINVAL); spa_device_emit_result(&impl->hooks, seq, 0, 0, nullptr); return 0; } int impl_enum_params(void *object, int seq, uint32_t id, uint32_t start, uint32_t num, const struct spa_pod *filter) { return -ENOTSUP; } int impl_set_param(void *object, uint32_t id, uint32_t flags, const struct spa_pod *param) { return -ENOTSUP; } const struct spa_device_methods impl_device = { .version = SPA_VERSION_DEVICE_METHODS, .add_listener = impl_add_listener, .sync = impl_sync, .enum_params = impl_enum_params, .set_param = impl_set_param, }; int impl_get_interface(struct spa_handle *handle, const char *type, void **interface) { auto *impl = reinterpret_cast(handle); spa_return_val_if_fail(handle != nullptr, -EINVAL); spa_return_val_if_fail(interface != nullptr, -EINVAL); if (spa_streq(type, SPA_TYPE_INTERFACE_Device)) *interface = &impl->device; else return -ENOENT; return 0; } int impl_clear(struct spa_handle *handle) { std::destroy_at(reinterpret_cast(handle)); return 0; } impl::impl(spa_log *log, std::shared_ptr manager, std::shared_ptr camera, std::string device_id) : handle({ SPA_VERSION_HANDLE, impl_get_interface, impl_clear }), log(log), device_id(std::move(device_id)), manager(std::move(manager)), camera(std::move(camera)) { libcamera_log_topic_init(log); spa_hook_list_init(&hooks); device.iface = SPA_INTERFACE_INIT( SPA_TYPE_INTERFACE_Device, SPA_VERSION_DEVICE, &impl_device, this); } size_t impl_get_size(const struct spa_handle_factory *factory, const struct spa_dict *params) { return sizeof(struct impl); } int impl_init(const struct spa_handle_factory *factory, struct spa_handle *handle, const struct spa_dict *info, const struct spa_support *support, uint32_t n_support) { const char *str; int res; spa_return_val_if_fail(factory != nullptr, -EINVAL); spa_return_val_if_fail(handle != nullptr, -EINVAL); auto log = static_cast(spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log)); auto manager = libcamera_manager_acquire(res); if (!manager) { spa_log_error(log, "can't start camera manager: %s", spa_strerror(res)); return res; } std::string device_id; if (info && (str = spa_dict_lookup(info, SPA_KEY_API_LIBCAMERA_PATH))) device_id = str; auto camera = manager->get(device_id); if (!camera) { spa_log_error(log, "unknown camera id %s", device_id.c_str()); return -ENOENT; } new (handle) impl(log, std::move(manager), std::move(camera), std::move(device_id)); return 0; } const struct spa_interface_info impl_interfaces[] = { {SPA_TYPE_INTERFACE_Device,}, }; int impl_enum_interface_info(const struct spa_handle_factory *factory, const struct spa_interface_info **info, uint32_t *index) { spa_return_val_if_fail(factory != nullptr, -EINVAL); spa_return_val_if_fail(info != nullptr, -EINVAL); spa_return_val_if_fail(index != nullptr, -EINVAL); if (*index >= SPA_N_ELEMENTS(impl_interfaces)) return 0; *info = &impl_interfaces[(*index)++]; return 1; } } extern "C" { const struct spa_handle_factory spa_libcamera_device_factory = { SPA_VERSION_HANDLE_FACTORY, SPA_NAME_API_LIBCAMERA_DEVICE, nullptr, impl_get_size, impl_init, impl_enum_interface_info, }; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/libcamera/libcamera-manager.cpp000066400000000000000000000237601511204443500307240ustar00rootroot00000000000000/* Spa libcamera manager */ /* SPDX-FileCopyrightText: Copyright © 2021 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #include #include #include #include using namespace libcamera; #include #include #include #include #include #include #include #include #include #include #include "libcamera.h" #include "libcamera-manager.hpp" namespace { struct device { std::shared_ptr camera; }; struct impl { static constexpr std::size_t max_devices = 64; struct spa_handle handle; struct spa_device device = {}; struct spa_log *log; struct spa_loop_utils *loop_utils; struct spa_hook_list hooks; static constexpr uint64_t info_all = SPA_DEVICE_CHANGE_MASK_FLAGS | SPA_DEVICE_CHANGE_MASK_PROPS; struct spa_device_info info = SPA_DEVICE_INFO_INIT(); std::shared_ptr manager; struct device devices[max_devices]; struct hotplug_event { enum class type { add, remove } type; std::shared_ptr camera; }; std::mutex hotplug_events_lock; std::queue hotplug_events; struct spa_source *hotplug_event_source; impl(spa_log *log, spa_loop_utils *loop_utils, spa_source *hotplug_event_source, std::shared_ptr&& manager); ~impl() { manager->cameraAdded.disconnect(this); manager->cameraRemoved.disconnect(this); spa_loop_utils_destroy_source(loop_utils, hotplug_event_source); } std::uint32_t id_of(const struct device& d) const { spa_assert(std::begin(devices) <= &d && &d < std::end(devices)); return &d - std::begin(devices); } private: void queue_hotplug_event(enum hotplug_event::type type, std::shared_ptr&& camera) { { std::lock_guard guard(hotplug_events_lock); hotplug_events.push({ type, std::move(camera) }); } spa_loop_utils_signal_event(loop_utils, hotplug_event_source); } }; struct device *add_device(struct impl *impl, std::shared_ptr camera) { for (auto& d : impl->devices) { if (!d.camera) { d.camera = std::move(camera); return &d; } } return nullptr; } struct device *find_device(struct impl *impl, const Camera *camera) { for (auto& d : impl->devices) { if (d.camera.get() == camera) return &d; } return nullptr; } void remove_device(struct impl *impl, struct device *device) { *device = {}; } int emit_object_info(struct impl *impl, const struct device *device) { struct spa_device_object_info info; struct spa_dict_item items[20]; struct spa_dict dict; uint32_t n_items = 0; info = SPA_DEVICE_OBJECT_INFO_INIT(); info.type = SPA_TYPE_INTERFACE_Device; info.factory_name = SPA_NAME_API_LIBCAMERA_DEVICE; info.change_mask = SPA_DEVICE_OBJECT_CHANGE_MASK_FLAGS | SPA_DEVICE_OBJECT_CHANGE_MASK_PROPS; info.flags = 0; #define ADD_ITEM(key, value) items[n_items++] = SPA_DICT_ITEM_INIT(key, value) ADD_ITEM(SPA_KEY_DEVICE_ENUM_API,"libcamera.manager"); ADD_ITEM(SPA_KEY_DEVICE_API, "libcamera"); ADD_ITEM(SPA_KEY_MEDIA_CLASS, "Video/Device"); ADD_ITEM(SPA_KEY_API_LIBCAMERA_PATH, device->camera->id().c_str()); #undef ADD_ITEM dict = SPA_DICT_INIT(items, n_items); info.props = &dict; spa_device_emit_object_info(&impl->hooks, impl->id_of(*device), &info); return 1; } void try_add_camera(struct impl *impl, std::shared_ptr camera) { struct device *device; if ((device = find_device(impl, camera.get())) != nullptr) return; if ((device = add_device(impl, std::move(camera))) == nullptr) return; spa_log_info(impl->log, "camera added: id:%" PRIu32 " %s", impl->id_of(*device), device->camera->id().c_str()); emit_object_info(impl, device); } void try_remove_camera(struct impl *impl, const Camera *camera) { struct device *device; if ((device = find_device(impl, camera)) == nullptr) return; auto id = impl->id_of(*device); spa_log_info(impl->log, "camera removed: id:%" PRIu32 " %s", id, device->camera->id().c_str()); spa_device_emit_object_info(&impl->hooks, id, nullptr); remove_device(impl, device); } void consume_hotplug_event(struct impl *impl, impl::hotplug_event& event) { auto& [ type, camera ] = event; switch (type) { case impl::hotplug_event::type::add: spa_log_info(impl->log, "camera appeared: %s", camera->id().c_str()); try_add_camera(impl, std::move(camera)); break; case impl::hotplug_event::type::remove: spa_log_info(impl->log, "camera disappeared: %s", camera->id().c_str()); try_remove_camera(impl, camera.get()); break; } } void on_hotplug_event(void *data, std::uint64_t) { auto impl = static_cast(data); for (;;) { std::optional event; { std::unique_lock guard(impl->hotplug_events_lock); if (!impl->hotplug_events.empty()) { event = std::move(impl->hotplug_events.front()); impl->hotplug_events.pop(); } } if (!event) break; consume_hotplug_event(impl, *event); } } const struct spa_dict_item device_info_items[] = { { SPA_KEY_DEVICE_API, "libcamera" }, { SPA_KEY_DEVICE_NICK, "libcamera-manager" }, }; void emit_device_info(struct impl *impl, bool full) { uint64_t old = full ? impl->info.change_mask : 0; if (full) impl->info.change_mask = impl->info_all; if (impl->info.change_mask) { struct spa_dict dict; dict = SPA_DICT_INIT_ARRAY(device_info_items); impl->info.props = &dict; spa_device_emit_info(&impl->hooks, &impl->info); impl->info.change_mask = old; } } int impl_device_add_listener(void *object, struct spa_hook *listener, const struct spa_device_events *events, void *data) { struct impl *impl = (struct impl*) object; struct spa_hook_list save; spa_return_val_if_fail(impl != nullptr, -EINVAL); spa_return_val_if_fail(events != nullptr, -EINVAL); spa_hook_list_isolate(&impl->hooks, &save, listener, events, data); emit_device_info(impl, true); for (const auto& d : impl->devices) { if (d.camera) emit_object_info(impl, &d); } spa_hook_list_join(&impl->hooks, &save); return 0; } const struct spa_device_methods impl_device = { .version = SPA_VERSION_DEVICE_METHODS, .add_listener = impl_device_add_listener, }; int impl_get_interface(struct spa_handle *handle, const char *type, void **interface) { auto *impl = reinterpret_cast(handle); spa_return_val_if_fail(handle != nullptr, -EINVAL); spa_return_val_if_fail(interface != nullptr, -EINVAL); if (spa_streq(type, SPA_TYPE_INTERFACE_Device)) *interface = &impl->device; else return -ENOENT; return 0; } int impl_clear(struct spa_handle *handle) { auto impl = reinterpret_cast(handle); std::destroy_at(impl); return 0; } impl::impl(spa_log *log, spa_loop_utils *loop_utils, spa_source *hotplug_event_source, std::shared_ptr&& manager) : handle({ SPA_VERSION_HANDLE, impl_get_interface, impl_clear }), log(log), loop_utils(loop_utils), manager(std::move(manager)), hotplug_event_source(hotplug_event_source) { libcamera_log_topic_init(log); spa_hook_list_init(&hooks); device.iface = SPA_INTERFACE_INIT( SPA_TYPE_INTERFACE_Device, SPA_VERSION_DEVICE, &impl_device, this); this->manager->cameraAdded.connect(this, [this](std::shared_ptr camera) { queue_hotplug_event(hotplug_event::type::add, std::move(camera)); }); this->manager->cameraRemoved.connect(this, [this](std::shared_ptr camera) { queue_hotplug_event(hotplug_event::type::remove, std::move(camera)); }); for (auto&& camera : this->manager->cameras()) try_add_camera(this, std::move(camera)); } size_t impl_get_size(const struct spa_handle_factory *factory, const struct spa_dict *params) { return sizeof(struct impl); } int impl_init(const struct spa_handle_factory *factory, struct spa_handle *handle, const struct spa_dict *info, const struct spa_support *support, uint32_t n_support) { spa_return_val_if_fail(factory != nullptr, -EINVAL); spa_return_val_if_fail(handle != nullptr, -EINVAL); auto log = static_cast(spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log)); auto loop_utils = static_cast(spa_support_find(support, n_support, SPA_TYPE_INTERFACE_LoopUtils)); if (!loop_utils) { spa_log_error(log, "a " SPA_TYPE_INTERFACE_LoopUtils " is needed"); return -EINVAL; } auto hotplug_event_source = spa_loop_utils_add_event(loop_utils, on_hotplug_event, handle); if (!hotplug_event_source) { int res = -errno; spa_log_error(log, "failed to create hotplug event: %m"); return res; } int res = 0; auto manager = libcamera_manager_acquire(res); if (!manager) { spa_log_error(log, "failed to start camera manager: %s", spa_strerror(res)); return res; } new (handle) impl(log, loop_utils, hotplug_event_source, std::move(manager)); return 0; } const struct spa_interface_info impl_interfaces[] = { {SPA_TYPE_INTERFACE_Device,}, }; int impl_enum_interface_info(const struct spa_handle_factory *factory, const struct spa_interface_info **info, uint32_t *index) { spa_return_val_if_fail(factory != nullptr, -EINVAL); spa_return_val_if_fail(info != nullptr, -EINVAL); spa_return_val_if_fail(index != nullptr, -EINVAL); if (*index >= SPA_N_ELEMENTS(impl_interfaces)) return 0; *info = &impl_interfaces[(*index)++]; return 1; } } extern "C" { const struct spa_handle_factory spa_libcamera_manager_factory = { SPA_VERSION_HANDLE_FACTORY, SPA_NAME_API_LIBCAMERA_ENUM_MANAGER, nullptr, impl_get_size, impl_init, impl_enum_interface_info, }; } std::shared_ptr libcamera_manager_acquire(int& res) { static std::weak_ptr global_manager; static std::mutex lock; std::lock_guard guard(lock); if (auto manager = global_manager.lock()) return manager; auto manager = std::make_shared(); if ((res = manager->start()) < 0) return {}; global_manager = manager; return manager; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/libcamera/libcamera-manager.hpp000066400000000000000000000004351511204443500307230ustar00rootroot00000000000000/* Spa libcamera support */ /* SPDX-FileCopyrightText: Copyright © 2021 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include std::shared_ptr libcamera_manager_acquire(int& res); pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/libcamera/libcamera-source.cpp000066400000000000000000001645771511204443500306260ustar00rootroot00000000000000/* Spa libcamera source */ /* SPDX-FileCopyrightText: Copyright © 2020 Collabora Ltd. */ /* @author Raghavendra Rao Sidlagatta */ /* SPDX-FileCopyrightText: Copyright © 2021 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "libcamera.h" #include "libcamera-manager.hpp" using namespace libcamera; namespace { #define MAX_BUFFERS 32 #define MASK_BUFFERS 31 #define BUFFER_FLAG_OUTSTANDING (1<<0) struct buffer { uint32_t id; uint32_t flags; struct spa_list link; struct spa_buffer *outbuf; struct spa_meta_header *h; struct spa_meta_videotransform *videotransform; }; struct port { struct impl *impl; std::optional current_format; struct spa_fraction rate = {}; StreamConfiguration streamConfig; uint32_t buffers_blocks = 1; struct buffer buffers[MAX_BUFFERS]; uint32_t n_buffers = 0; struct spa_list queue; static constexpr uint64_t info_all = SPA_PORT_CHANGE_MASK_FLAGS | SPA_PORT_CHANGE_MASK_PROPS | SPA_PORT_CHANGE_MASK_PARAMS; struct spa_port_info info = SPA_PORT_INFO_INIT(); struct spa_io_buffers *io = nullptr; struct spa_io_sequence *control = nullptr; uint32_t control_size; #define PORT_PropInfo 0 #define PORT_EnumFormat 1 #define PORT_Meta 2 #define PORT_IO 3 #define PORT_Format 4 #define PORT_Buffers 5 #define PORT_Latency 6 #define N_PORT_PARAMS 7 struct spa_param_info params[N_PORT_PARAMS]; std::size_t fmt_index = 0; std::size_t size_index = 0; port(struct impl *impl) : impl(impl) { spa_list_init(&queue); params[PORT_PropInfo] = SPA_PARAM_INFO(SPA_PARAM_PropInfo, SPA_PARAM_INFO_READ); params[PORT_EnumFormat] = SPA_PARAM_INFO(SPA_PARAM_EnumFormat, SPA_PARAM_INFO_READ); params[PORT_Meta] = SPA_PARAM_INFO(SPA_PARAM_Meta, SPA_PARAM_INFO_READ); params[PORT_IO] = SPA_PARAM_INFO(SPA_PARAM_IO, SPA_PARAM_INFO_READ); params[PORT_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); params[PORT_Buffers] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); params[PORT_Latency] = SPA_PARAM_INFO(SPA_PARAM_Latency, SPA_PARAM_INFO_READ); info.flags = SPA_PORT_FLAG_LIVE | SPA_PORT_FLAG_PHYSICAL | SPA_PORT_FLAG_TERMINAL; info.params = params; info.n_params = N_PORT_PARAMS; } }; struct impl { struct spa_handle handle; struct spa_node node = {}; struct spa_log *log; struct spa_loop *data_loop; struct spa_system *system; static constexpr uint64_t info_all = SPA_NODE_CHANGE_MASK_FLAGS | SPA_NODE_CHANGE_MASK_PROPS | SPA_NODE_CHANGE_MASK_PARAMS; struct spa_node_info info = SPA_NODE_INFO_INIT(); #define NODE_PropInfo 0 #define NODE_Props 1 #define NODE_EnumFormat 2 #define NODE_Format 3 #define N_NODE_PARAMS 4 struct spa_param_info params[N_NODE_PARAMS]; struct spa_hook_list hooks; struct spa_callbacks callbacks = {}; std::array out_ports; struct spa_io_position *position = nullptr; struct spa_io_clock *clock = nullptr; struct spa_latency_info latency[2]; std::shared_ptr manager; std::shared_ptr camera; const std::unique_ptr config; FrameBufferAllocator allocator; std::vector> requestPool; spa_ringbuffer completed_requests_rb = SPA_RINGBUFFER_INIT(); std::array completed_requests; void requestComplete(libcamera::Request *request); struct spa_source source = {}; ControlList ctrls; ControlList initial_controls; bool active = false; bool acquired = false; impl(spa_log *log, spa_loop *data_loop, spa_system *system, std::shared_ptr manager, std::shared_ptr camera, std::unique_ptr config); struct spa_dll dll; void stop() { spa_loop_locked( data_loop, [](spa_loop *, bool, uint32_t, const void *, size_t, void *user_data) { auto *self = static_cast(user_data); if (self->source.loop) spa_loop_remove_source(self->data_loop, &self->source); return 0; }, 0, nullptr, 0, this ); if (source.fd >= 0) spa_system_close(system, std::exchange(source.fd, -1)); camera->requestCompleted.disconnect(this, &impl::requestComplete); if (int res = camera->stop(); res < 0) { spa_log_warn(log, "failed to stop camera %s: %s", camera->id().c_str(), spa_strerror(res)); } completed_requests_rb = SPA_RINGBUFFER_INIT(); active = false; for (auto& p : out_ports) spa_list_init(&p.queue); } }; #define CHECK_PORT(impl,direction,port_id) ((direction) == SPA_DIRECTION_OUTPUT && (port_id) == 0) #define GET_OUT_PORT(impl,p) (&impl->out_ports[p]) #define GET_PORT(impl,d,p) GET_OUT_PORT(impl,p) void setup_initial_controls(const ControlInfoMap& ctrl_infos, ControlList& ctrls) { /* Libcamera recommends cameras default to manual focus mode, but we don't * expose any focus controls. So, specifically enable autofocus on * cameras which support it. */ auto af_it = ctrl_infos.find(libcamera::controls::AF_MODE); if (af_it != ctrl_infos.end()) { const ControlInfo &ctrl_info = af_it->second; auto is_af_continuous = [](const ControlValue &value) { return value.get() == libcamera::controls::AfModeContinuous; }; if (std::any_of(ctrl_info.values().begin(), ctrl_info.values().end(), is_af_continuous)) { ctrls.set(libcamera::controls::AF_MODE, libcamera::controls::AfModeContinuous); } } auto ae_it = ctrl_infos.find(libcamera::controls::AE_ENABLE); if (ae_it != ctrl_infos.end()) { ctrls.set(libcamera::controls::AE_ENABLE, true); } } int spa_libcamera_open(struct impl *impl) { if (impl->acquired) return 0; spa_log_info(impl->log, "open camera %s", impl->camera->id().c_str()); if (int res = impl->camera->acquire(); res < 0) return res; spa_assert(!impl->allocator.allocated()); const ControlInfoMap &controls = impl->camera->controls(); setup_initial_controls(controls, impl->initial_controls); impl->acquired = true; return 0; } int spa_libcamera_close(struct impl *impl) { struct port *port = &impl->out_ports[0]; if (!impl->acquired) return 0; if (impl->active || port->current_format) return 0; spa_log_info(impl->log, "close camera %s", impl->camera->id().c_str()); spa_assert(!impl->allocator.allocated()); impl->camera->release(); impl->acquired = false; return 0; } int spa_libcamera_buffer_recycle(struct impl *impl, struct port *port, uint32_t buffer_id) { struct buffer *b = &port->buffers[buffer_id]; int res; if (!SPA_FLAG_IS_SET(b->flags, BUFFER_FLAG_OUTSTANDING)) return 0; SPA_FLAG_CLEAR(b->flags, BUFFER_FLAG_OUTSTANDING); if (buffer_id >= impl->requestPool.size()) { spa_log_warn(impl->log, "invalid buffer_id %u >= %zu", buffer_id, impl->requestPool.size()); return -EINVAL; } Request *request = impl->requestPool[buffer_id].get(); if (impl->active) { request->controls().merge(impl->ctrls); impl->ctrls.clear(); if ((res = impl->camera->queueRequest(request)) < 0) { spa_log_warn(impl->log, "can't queue buffer %u: %s", buffer_id, spa_strerror(res)); return res == -EACCES ? -EBUSY : res; } } return 0; } void freeBuffers(struct impl *impl, struct port *port) { impl->requestPool.clear(); std::ignore = impl->allocator.free(port->streamConfig.stream()); } [[nodiscard]] std::size_t count_unique_fds(libcamera::Span planes) { std::size_t c = 0; int fd = -1; for (const auto& plane : planes) { const int current_fd = plane.fd.get(); if (current_fd >= 0 && current_fd != fd) { c += 1; fd = current_fd; } } return c; } int allocBuffers(struct impl *impl, struct port *port, unsigned int count) { libcamera::Stream *stream = port->streamConfig.stream(); int res; if (!impl->requestPool.empty()) return -EBUSY; if ((res = impl->allocator.allocate(stream)) < 0) return res; const auto& bufs = impl->allocator.buffers(stream); if (bufs.empty() || bufs.size() != count) { res = -ENOBUFS; goto err; } for (std::size_t i = 0; i < bufs.size(); i++) { std::unique_ptr request = impl->camera->createRequest(i); if (!request) { res = -ENOMEM; goto err; } res = request->addBuffer(stream, bufs[i].get()); if (res < 0) goto err; impl->requestPool.push_back(std::move(request)); } /* Some devices require data for each output video frame to be * placed in discontiguous memory buffers. In such cases, one * video frame has to be addressed using more than one memory. * address. Therefore, need calculate the number of discontiguous * memory and allocate the specified amount of memory */ port->buffers_blocks = count_unique_fds(bufs.front()->planes()); if (port->buffers_blocks <= 0) { res = -ENOBUFS; goto err; } return 0; err: freeBuffers(impl, port); return res; } int spa_libcamera_clear_buffers(struct port *port) { for (std::size_t i = 0; i < port->n_buffers; i++) { buffer *b = &port->buffers[i]; spa_buffer *sb = b->outbuf; for (std::size_t j = 0; j < sb->n_datas; j++) { auto *d = &sb->datas[j]; d->type = SPA_ID_INVALID; d->data = nullptr; d->fd = -1; } *b = {}; } port->n_buffers = 0; return 0; } struct format_info { PixelFormat pix; spa_video_format format; spa_media_type media_type; spa_media_subtype media_subtype; }; #define MAKE_FMT(pix,fmt,mt,mst) { pix, SPA_VIDEO_FORMAT_ ##fmt, SPA_MEDIA_TYPE_ ##mt, SPA_MEDIA_SUBTYPE_ ##mst } const struct format_info format_info[] = { /* RGB formats */ MAKE_FMT(formats::R8, GRAY8, video, raw), MAKE_FMT(formats::RGB565, RGB16, video, raw), MAKE_FMT(formats::RGB565_BE, RGB16, video, raw), MAKE_FMT(formats::RGB888, BGR, video, raw), MAKE_FMT(formats::BGR888, RGB, video, raw), MAKE_FMT(formats::XRGB8888, BGRx, video, raw), MAKE_FMT(formats::XBGR8888, RGBx, video, raw), MAKE_FMT(formats::RGBX8888, xBGR, video, raw), MAKE_FMT(formats::BGRX8888, xRGB, video, raw), MAKE_FMT(formats::ARGB8888, BGRA, video, raw), MAKE_FMT(formats::ABGR8888, RGBA, video, raw), MAKE_FMT(formats::RGBA8888, ABGR, video, raw), MAKE_FMT(formats::BGRA8888, ARGB, video, raw), MAKE_FMT(formats::YUYV, YUY2, video, raw), MAKE_FMT(formats::YVYU, YVYU, video, raw), MAKE_FMT(formats::UYVY, UYVY, video, raw), MAKE_FMT(formats::VYUY, VYUY, video, raw), MAKE_FMT(formats::NV12, NV12, video, raw), MAKE_FMT(formats::NV21, NV21, video, raw), MAKE_FMT(formats::NV16, NV16, video, raw), MAKE_FMT(formats::NV61, NV61, video, raw), MAKE_FMT(formats::NV24, NV24, video, raw), MAKE_FMT(formats::YUV420, I420, video, raw), MAKE_FMT(formats::YVU420, YV12, video, raw), MAKE_FMT(formats::YUV422, Y42B, video, raw), MAKE_FMT(formats::MJPEG, ENCODED, video, mjpg), #undef MAKE_FMT }; const struct format_info *video_format_to_info(const PixelFormat &pix) { for (const auto& f : format_info) { if (f.pix == pix) return &f; } return nullptr; } const struct format_info *find_format_info_by_media_type( uint32_t type, uint32_t subtype, uint32_t format) { for (const auto& f : format_info) { if (f.media_type == type && f.media_subtype == subtype && f.format == format) return &f; } return nullptr; } int score_size(const Size &a, const Size &b) { int x, y; x = (int)a.width - (int)b.width; y = (int)a.height - (int)b.height; return x * x + y * y; } [[nodiscard]] spa_video_colorimetry color_space_to_colorimetry(const libcamera::ColorSpace& colorspace) { spa_video_colorimetry res = {}; switch (colorspace.range) { case ColorSpace::Range::Full: res.range = SPA_VIDEO_COLOR_RANGE_0_255; break; case ColorSpace::Range::Limited: res.range = SPA_VIDEO_COLOR_RANGE_16_235; break; } switch (colorspace.ycbcrEncoding) { case ColorSpace::YcbcrEncoding::None: res.matrix = SPA_VIDEO_COLOR_MATRIX_RGB; break; case ColorSpace::YcbcrEncoding::Rec601: res.matrix = SPA_VIDEO_COLOR_MATRIX_BT601; break; case ColorSpace::YcbcrEncoding::Rec709: res.matrix = SPA_VIDEO_COLOR_MATRIX_BT709; break; case ColorSpace::YcbcrEncoding::Rec2020: res.matrix = SPA_VIDEO_COLOR_MATRIX_BT2020; break; } switch (colorspace.transferFunction) { case ColorSpace::TransferFunction::Linear: res.transfer = SPA_VIDEO_TRANSFER_GAMMA10; break; case ColorSpace::TransferFunction::Srgb: res.transfer = SPA_VIDEO_TRANSFER_SRGB; break; case ColorSpace::TransferFunction::Rec709: res.transfer = SPA_VIDEO_TRANSFER_BT709; break; } switch (colorspace.primaries) { case ColorSpace::Primaries::Raw: res.primaries = SPA_VIDEO_COLOR_PRIMARIES_UNKNOWN; break; case ColorSpace::Primaries::Smpte170m: res.primaries = SPA_VIDEO_COLOR_PRIMARIES_SMPTE170M; break; case ColorSpace::Primaries::Rec709: res.primaries = SPA_VIDEO_COLOR_PRIMARIES_BT709; break; case ColorSpace::Primaries::Rec2020: res.primaries = SPA_VIDEO_COLOR_PRIMARIES_BT2020; break; } return res; } int spa_libcamera_enum_format(struct impl *impl, struct port *port, int seq, uint32_t start, uint32_t num, const struct spa_pod *filter) { uint8_t buffer[1024]; struct spa_pod_builder b = { 0 }; struct spa_pod_frame f[2]; struct spa_result_node_params result; uint32_t count = 0; const StreamConfiguration& streamConfig = impl->config->at(0); const StreamFormats &formats = streamConfig.formats(); const auto &pixel_formats = formats.pixelformats(); result.id = SPA_PARAM_EnumFormat; result.next = start; if (result.next == 0) { port->fmt_index = 0; port->size_index = 0; } next: result.index = result.next++; next_fmt: if (port->fmt_index >= pixel_formats.size()) return 0; auto format = pixel_formats[port->fmt_index]; spa_log_debug(impl->log, "format: %s", format.toString().c_str()); const auto *info = video_format_to_info(format); if (info == nullptr) { spa_log_debug(impl->log, "unknown format"); port->fmt_index++; goto next_fmt; } const auto& sizes = formats.sizes(format); SizeRange sizeRange; Size frameSize; if (!sizes.empty() && port->size_index <= sizes.size()) { if (port->size_index == 0) { Size wanted = Size(640, 480); int best = std::numeric_limits::max(); for (const auto& test : sizes) { int score = score_size(wanted, test); if (score < best) { best = score; frameSize = test; } } } else { frameSize = sizes[port->size_index - 1]; } } else if (port->size_index < 1) { sizeRange = formats.range(format); if (sizeRange.hStep == 0 || sizeRange.vStep == 0) { port->size_index = 0; port->fmt_index++; goto next_fmt; } } else { port->size_index = 0; port->fmt_index++; goto next_fmt; } port->size_index++; spa_pod_builder_init(&b, buffer, sizeof(buffer)); spa_pod_builder_push_object(&b, &f[0], SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat); spa_pod_builder_add(&b, SPA_FORMAT_mediaType, SPA_POD_Id(info->media_type), SPA_FORMAT_mediaSubtype, SPA_POD_Id(info->media_subtype), 0); if (info->media_subtype == SPA_MEDIA_SUBTYPE_raw) { spa_pod_builder_prop(&b, SPA_FORMAT_VIDEO_format, 0); spa_pod_builder_id(&b, info->format); } if (info->pix.modifier()) { spa_pod_builder_prop(&b, SPA_FORMAT_VIDEO_modifier, 0); spa_pod_builder_long(&b, info->pix.modifier()); } spa_pod_builder_prop(&b, SPA_FORMAT_VIDEO_size, 0); if (sizeRange.hStep != 0 && sizeRange.vStep != 0) { spa_pod_builder_push_choice(&b, &f[1], SPA_CHOICE_Step, 0); spa_pod_builder_frame(&b, &f[1]); spa_pod_builder_rectangle(&b, sizeRange.min.width, sizeRange.min.height); spa_pod_builder_rectangle(&b, sizeRange.min.width, sizeRange.min.height); spa_pod_builder_rectangle(&b, sizeRange.max.width, sizeRange.max.height); spa_pod_builder_rectangle(&b, sizeRange.hStep, sizeRange.vStep); spa_pod_builder_pop(&b, &f[1]); } else { spa_pod_builder_rectangle(&b, frameSize.width, frameSize.height); } if (streamConfig.colorSpace) { auto colorimetry = color_space_to_colorimetry(*streamConfig.colorSpace); spa_pod_builder_add(&b, SPA_FORMAT_VIDEO_colorRange, SPA_POD_Id(colorimetry.range), SPA_FORMAT_VIDEO_colorMatrix, SPA_POD_Id(colorimetry.matrix), SPA_FORMAT_VIDEO_transferFunction, SPA_POD_Id(colorimetry.transfer), SPA_FORMAT_VIDEO_colorPrimaries, SPA_POD_Id(colorimetry.primaries), 0); } const auto *fmt = reinterpret_cast(spa_pod_builder_pop(&b, &f[0])); if (spa_pod_filter(&b, &result.param, fmt, filter) < 0) goto next; spa_node_emit_result(&impl->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); if (++count != num) goto next; return 0; } int spa_libcamera_set_format(struct impl *impl, struct port *port, struct spa_video_info *format, bool try_only) { const struct format_info *info = nullptr; uint32_t video_format; struct spa_rectangle *size = nullptr; CameraConfiguration::Status validation; int res; switch (format->media_subtype) { case SPA_MEDIA_SUBTYPE_raw: video_format = format->info.raw.format; size = &format->info.raw.size; break; case SPA_MEDIA_SUBTYPE_mjpg: case SPA_MEDIA_SUBTYPE_jpeg: video_format = SPA_VIDEO_FORMAT_ENCODED; size = &format->info.mjpg.size; break; case SPA_MEDIA_SUBTYPE_h264: video_format = SPA_VIDEO_FORMAT_ENCODED; size = &format->info.h264.size; break; default: video_format = SPA_VIDEO_FORMAT_ENCODED; break; } info = find_format_info_by_media_type(format->media_type, format->media_subtype, video_format); if (info == nullptr || size == nullptr) { spa_log_error(impl->log, "unknown media type %d %d %d", format->media_type, format->media_subtype, video_format); return -EINVAL; } StreamConfiguration& streamConfig = impl->config->at(0); streamConfig.pixelFormat = info->pix; streamConfig.size.width = size->width; streamConfig.size.height = size->height; streamConfig.bufferCount = 8; validation = impl->config->validate(); if (validation == CameraConfiguration::Invalid) return -EINVAL; if (try_only) return 0; if ((res = spa_libcamera_open(impl)) < 0) return res; res = impl->camera->configure(impl->config.get()); if (res != 0) goto error; port->streamConfig = impl->config->at(0); if ((res = allocBuffers(impl, port, port->streamConfig.bufferCount)) < 0) goto error; port->info.change_mask |= SPA_PORT_CHANGE_MASK_FLAGS | SPA_PORT_CHANGE_MASK_RATE; port->info.flags = SPA_PORT_FLAG_CAN_ALLOC_BUFFERS | SPA_PORT_FLAG_LIVE | SPA_PORT_FLAG_PHYSICAL | SPA_PORT_FLAG_TERMINAL; port->info.rate = SPA_FRACTION(port->rate.num, port->rate.denom); return 0; error: spa_libcamera_close(impl); return res; } const struct { uint32_t id; uint32_t spa_id; } control_map[] = { { libcamera::controls::BRIGHTNESS, SPA_PROP_brightness }, { libcamera::controls::CONTRAST, SPA_PROP_contrast }, { libcamera::controls::SATURATION, SPA_PROP_saturation }, { libcamera::controls::EXPOSURE_TIME, SPA_PROP_exposure }, { libcamera::controls::ANALOGUE_GAIN, SPA_PROP_gain }, { libcamera::controls::SHARPNESS, SPA_PROP_sharpness }, }; uint32_t control_to_prop_id(uint32_t control_id) { for (const auto& c : control_map) { if (c.id == control_id) return c.spa_id; } return SPA_PROP_START_CUSTOM + control_id; } uint32_t prop_id_to_control(uint32_t prop_id) { if (prop_id >= SPA_PROP_START_CUSTOM) return prop_id - SPA_PROP_START_CUSTOM; for (const auto& c : control_map) { if (c.spa_id == prop_id) return c.id; } return SPA_ID_INVALID; } [[nodiscard]] ControlValue control_value_from_pod(const libcamera::ControlId& cid, const spa_pod *value, const void *body) { if (cid.isArray()) return {}; switch (cid.type()) { case libcamera::ControlTypeBool: { bool v; if (spa_pod_body_get_bool(value, body, &v) < 0) return {}; return v; } case libcamera::ControlTypeInteger32: { int32_t v; if (spa_pod_body_get_int(value, body, &v) < 0) return {}; return v; } case libcamera::ControlTypeFloat: { float v; if (spa_pod_body_get_float(value, body, &v) < 0) return {}; return v; } default: return {}; } return {}; } int control_list_update_from_prop(libcamera::ControlList& list, const spa_pod_prop *prop, const void *body) { auto id = prop_id_to_control(prop->key); if (id == SPA_ID_INVALID) return -ENOENT; auto it = list.idMap()->find(id); if (it == list.idMap()->end()) return -ENOENT; if (!list.infoMap()->count(it->second)) return -ENOENT; auto val = control_value_from_pod(*it->second, &prop->value, body); if (val.isNone()) return -EINVAL; list.set(id, std::move(val)); return 0; } [[nodiscard]] bool control_value_to_pod(spa_pod_builder& b, const libcamera::ControlValue& cv) { if (cv.isArray()) return false; switch (cv.type()) { case libcamera::ControlTypeBool: { spa_pod_builder_bool(&b, cv.get()); break; } case libcamera::ControlTypeInteger32: { spa_pod_builder_int(&b, cv.get()); break; } case libcamera::ControlTypeFloat: { spa_pod_builder_float(&b, cv.get()); break; } default: return false; } return true; } template [[nodiscard]] std::array control_info_to_range(const libcamera::ControlInfo& cinfo) { static_assert(std::is_arithmetic_v); auto min = cinfo.min().get(); auto max = cinfo.max().get(); spa_assert(min <= max); auto def = !cinfo.def().isNone() ? cinfo.def().get() : (min + ((max - min) / 2)); return {{ min, max, def }}; } [[nodiscard]] spa_pod *control_details_to_pod(spa_pod_builder& b, const libcamera::ControlId& cid, const libcamera::ControlInfo& cinfo) { if (cid.isArray()) return nullptr; auto id = control_to_prop_id(cid.id()); spa_pod_frame f; spa_pod_builder_push_object(&b, &f, SPA_TYPE_OBJECT_PropInfo, SPA_PARAM_PropInfo); spa_pod_builder_add(&b, SPA_PROP_INFO_id, SPA_POD_Id(id), SPA_PROP_INFO_description, SPA_POD_String(cid.name().c_str()), 0); if (cinfo.values().empty()) { switch (cid.type()) { case ControlTypeBool: { auto min = cinfo.min().get(); auto max = cinfo.max().get(); auto def = !cinfo.def().isNone() ? cinfo.def().get() : min; spa_pod_frame f; spa_pod_builder_prop(&b, SPA_PROP_INFO_type, 0); spa_pod_builder_push_choice(&b, &f, SPA_CHOICE_Enum, 0); spa_pod_builder_bool(&b, def); spa_pod_builder_bool(&b, min); if (max != min) spa_pod_builder_bool(&b, max); spa_pod_builder_pop(&b, &f); break; } case ControlTypeFloat: { auto [ min, max, def ] = control_info_to_range(cinfo); spa_pod_builder_add(&b, SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Float( def, min, max), 0); break; } case ControlTypeInteger32: { auto [ min, max, def ] = control_info_to_range(cinfo); spa_pod_builder_add(&b, SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Int( def, min, max), 0); break; } default: return nullptr; } } else { spa_pod_frame f; spa_pod_builder_prop(&b, SPA_PROP_INFO_type, 0); spa_pod_builder_push_choice(&b, &f, SPA_CHOICE_Enum, 0); if (!control_value_to_pod(b, cinfo.def())) return nullptr; for (const auto& cv : cinfo.values()) { if (!control_value_to_pod(b, cv)) return nullptr; } spa_pod_builder_pop(&b, &f); if (cid.type() == libcamera::ControlTypeInteger32) { spa_pod_builder_prop(&b, SPA_PROP_INFO_labels, 0); spa_pod_builder_push_struct(&b, &f); for (const auto& cv : cinfo.values()) { auto it = cid.enumerators().find(cv.get()); if (it == cid.enumerators().end()) continue; spa_pod_builder_int(&b, it->first); spa_pod_builder_string_len(&b, it->second.data(), it->second.size()); } spa_pod_builder_pop(&b, &f); } } return reinterpret_cast(spa_pod_builder_pop(&b, &f)); } int spa_libcamera_enum_controls(struct impl *impl, struct port *port, int seq, uint32_t start, uint32_t offset, uint32_t num, const struct spa_pod *filter) { const ControlInfoMap &info = impl->camera->controls(); spa_auto(spa_pod_dynamic_builder) b = {}; spa_pod_builder_state state; uint8_t buffer[4096]; spa_result_node_params result = { .id = SPA_PARAM_PropInfo, }; auto it = info.begin(); for (auto skip = start - offset; skip && it != info.end(); skip--) it++; spa_pod_dynamic_builder_init(&b, buffer, sizeof(buffer), 4096); spa_pod_builder_get_state(&b.b, &state); for (result.index = start; num > 0 && it != info.end(); ++it, result.index++) { spa_log_debug(impl->log, "%p: controls[%" PRIu32 "]: %s::%s", impl, result.index, it->first->vendor().c_str(), it->first->name().c_str()); spa_pod_builder_reset(&b.b, &state); const auto *ctrl = control_details_to_pod(b.b, *it->first, it->second); if (!ctrl) continue; if (spa_pod_filter(&b.b, &result.param, ctrl, filter) < 0) continue; result.next = result.index + 1; spa_node_emit_result(&impl->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); num -= 1; } return 0; } int spa_libcamera_apply_controls(struct impl *impl, libcamera::ControlList&& controls) { if (controls.empty()) return 0; struct invoke_data { ControlList *controls; } d = { .controls = &controls, }; return spa_loop_locked( impl->data_loop, [](spa_loop *, bool, uint32_t, const void *data, size_t, void *user_data) { const auto *d = static_cast(data); auto *impl = static_cast(user_data); impl->ctrls.merge(std::move(*d->controls), libcamera::ControlList::MergePolicy::OverwriteExisting); return 0; }, 0, &d, sizeof(d), impl ); } void handle_completed_request(struct impl *impl, libcamera::Request *request) { const auto request_id = request->cookie(); struct port *port = &impl->out_ports[0]; buffer *b = &port->buffers[request_id]; spa_log_trace(impl->log, "%p: request %p[%" PRIu64 "] process status:%u seq:%" PRIu32, impl, request, request_id, static_cast(request->status()), request->sequence()); if (request->status() == libcamera::Request::Status::RequestCancelled) { spa_log_trace(impl->log, "%p: request %p[%" PRIu64 "] cancelled", impl, request, request_id); request->reuse(libcamera::Request::ReuseFlag::ReuseBuffers); SPA_FLAG_SET(b->flags, BUFFER_FLAG_OUTSTANDING); spa_libcamera_buffer_recycle(impl, port, b->id); return; } const FrameBuffer *buffer = request->findBuffer(port->streamConfig.stream()); if (buffer == nullptr) { spa_log_warn(impl->log, "%p: request %p[%" PRIu64 "] has no buffer for stream %p", impl, request, request_id, port->streamConfig.stream()); return; } const FrameMetadata &fmd = buffer->metadata(); if (impl->clock) { double target = (double)port->info.rate.num / port->info.rate.denom; double corr; if (impl->dll.bw == 0.0) { spa_dll_set_bw(&impl->dll, SPA_DLL_BW_MAX, port->info.rate.denom, port->info.rate.denom); impl->clock->next_nsec = fmd.timestamp; corr = 1.0; } else { double diff = ((double)impl->clock->next_nsec - (double)fmd.timestamp) / SPA_NSEC_PER_SEC; double error = port->info.rate.denom * (diff - target); corr = spa_dll_update(&impl->dll, SPA_CLAMPD(error, -128., 128.)); } /* FIXME, we should follow the driver clock and target_ values. * for now we ignore and use our own. */ impl->clock->target_rate = port->rate; impl->clock->target_duration = 1; impl->clock->nsec = fmd.timestamp; impl->clock->rate = port->rate; impl->clock->position = fmd.sequence; impl->clock->duration = 1; impl->clock->delay = 0; impl->clock->rate_diff = corr; impl->clock->next_nsec += (uint64_t) (target * SPA_NSEC_PER_SEC * corr); } if (b->h) { b->h->flags = 0; if (fmd.status != libcamera::FrameMetadata::Status::FrameSuccess) b->h->flags |= SPA_META_HEADER_FLAG_CORRUPTED; b->h->offset = 0; b->h->seq = fmd.sequence; b->h->pts = fmd.timestamp; b->h->dts_offset = 0; } for (std::size_t i = 0; i < b->outbuf->n_datas; i++) { auto *d = &b->outbuf->datas[i]; d->chunk->flags = 0; if (fmd.status != libcamera::FrameMetadata::Status::FrameSuccess) d->chunk->flags |= SPA_CHUNK_FLAG_CORRUPTED; } request->reuse(libcamera::Request::ReuseFlag::ReuseBuffers); spa_list_append(&port->queue, &b->link); spa_io_buffers *io = port->io; if (io == nullptr) { b = spa_list_first(&port->queue, struct buffer, link); spa_list_remove(&b->link); SPA_FLAG_SET(b->flags, BUFFER_FLAG_OUTSTANDING); spa_libcamera_buffer_recycle(impl, port, b->id); } else if (io->status != SPA_STATUS_HAVE_DATA) { if (io->buffer_id < port->n_buffers) spa_libcamera_buffer_recycle(impl, port, io->buffer_id); b = spa_list_first(&port->queue, struct buffer, link); spa_list_remove(&b->link); SPA_FLAG_SET(b->flags, BUFFER_FLAG_OUTSTANDING); io->buffer_id = b->id; io->status = SPA_STATUS_HAVE_DATA; spa_log_trace(impl->log, "%p: now queued %" PRIu32, impl, b->id); } spa_node_call_ready(&impl->callbacks, SPA_STATUS_HAVE_DATA); } void libcamera_on_fd_events(struct spa_source *source) { struct impl *impl = (struct impl*) source->data; uint32_t index; uint64_t cnt; if (source->rmask & SPA_IO_ERR) { spa_log_error(impl->log, "libcamera %p: error %08x", impl, source->rmask); if (impl->source.loop) spa_loop_remove_source(impl->data_loop, &impl->source); return; } if (!(source->rmask & SPA_IO_IN)) { spa_log_warn(impl->log, "libcamera %p: spurious wakeup %d", impl, source->rmask); return; } if (spa_system_eventfd_read(impl->system, impl->source.fd, &cnt) < 0) { spa_log_error(impl->log, "Failed to read on event fd"); return; } auto avail = spa_ringbuffer_get_read_index(&impl->completed_requests_rb, &index); for (; avail > 0; avail--, index++) { auto *request = impl->completed_requests[index & MASK_BUFFERS]; spa_ringbuffer_read_update(&impl->completed_requests_rb, index + 1); handle_completed_request(impl, request); } } int spa_libcamera_use_buffers(struct impl *impl, struct port *port, struct spa_buffer **buffers, uint32_t n_buffers) { return -ENOTSUP; } const struct { Orientation libcamera_orientation; /* clockwise rotation then horizontal mirroring */ uint32_t spa_transform_value; /* horizontal mirroring then counter-clockwise rotation */ } orientation_map[] = { { Orientation::Rotate0, SPA_META_TRANSFORMATION_None }, { Orientation::Rotate0Mirror, SPA_META_TRANSFORMATION_Flipped }, { Orientation::Rotate90, SPA_META_TRANSFORMATION_270 }, { Orientation::Rotate90Mirror, SPA_META_TRANSFORMATION_Flipped90 }, { Orientation::Rotate180, SPA_META_TRANSFORMATION_180 }, { Orientation::Rotate180Mirror, SPA_META_TRANSFORMATION_Flipped180 }, { Orientation::Rotate270, SPA_META_TRANSFORMATION_90 }, { Orientation::Rotate270Mirror, SPA_META_TRANSFORMATION_Flipped270 }, }; uint32_t libcamera_orientation_to_spa_transform_value(Orientation orientation) { for (const auto& t : orientation_map) { if (t.libcamera_orientation == orientation) return t.spa_transform_value; } return SPA_META_TRANSFORMATION_None; } int spa_libcamera_alloc_buffers(struct impl *impl, struct port *port, struct spa_buffer **buffers, uint32_t n_buffers) { if (port->n_buffers > 0) return -EIO; Stream *stream = impl->config->at(0).stream(); const std::vector> &bufs = impl->allocator.buffers(stream); if (n_buffers > 0 && bufs.size() != n_buffers) return -EINVAL; const auto choose_memtype = [](uint32_t t) { if (t != SPA_ID_INVALID && t & (1u << SPA_DATA_DmaBuf)) return SPA_DATA_DmaBuf; if (t & (1u << SPA_DATA_MemFd)) return SPA_DATA_MemFd; return SPA_DATA_Invalid; }; for (uint32_t i = 0; i < n_buffers; i++) { struct buffer *b; if (buffers[i]->n_datas < 1) { spa_log_error(impl->log, "invalid buffer data"); return -EINVAL; } b = &port->buffers[i]; b->id = i; b->outbuf = buffers[i]; b->flags = 0; b->h = (struct spa_meta_header*)spa_buffer_find_meta_data(buffers[i], SPA_META_Header, sizeof(*b->h)); b->videotransform = (struct spa_meta_videotransform*)spa_buffer_find_meta_data( buffers[i], SPA_META_VideoTransform, sizeof(*b->videotransform)); if (b->videotransform) { b->videotransform->transform = libcamera_orientation_to_spa_transform_value(impl->config->orientation); spa_log_debug(impl->log, "Setting videotransform for buffer %u to %u", i, b->videotransform->transform); } const auto& planes = bufs[i]->planes(); spa_data *d = buffers[i]->datas; for(uint32_t j = 0; j < buffers[i]->n_datas; ++j) { const auto memtype = choose_memtype(d[j].type); if (memtype == SPA_DATA_Invalid) { spa_log_error(impl->log, "can't use buffers of type %" PRIu32, d[j].type); return -EINVAL; } d[j].type = memtype; d[j].flags = SPA_DATA_FLAG_READABLE; d[j].fd = -1; d[j].mapoffset = 0; d[j].data = nullptr; d[j].chunk->stride = port->streamConfig.stride; d[j].chunk->flags = 0; /* Update parameters according to the plane information */ unsigned int numPlanes = planes.size(); if (buffers[i]->n_datas < numPlanes) { if (j < buffers[i]->n_datas - 1) { d[j].maxsize = planes[j].length; d[j].chunk->offset = planes[j].offset; d[j].chunk->size = planes[j].length; } else { d[j].chunk->offset = planes[j].offset; for (uint8_t k = j; k < numPlanes; k++) { d[j].maxsize += planes[k].length; d[j].chunk->size += planes[k].length; } } } else if (buffers[i]->n_datas == numPlanes) { d[j].maxsize = planes[j].length; d[j].chunk->offset = planes[j].offset; d[j].chunk->size = planes[j].length; } else { spa_log_warn(impl->log, "buffer index: i: %d, data member " "numbers: %d is greater than plane number: %d", i, buffers[i]->n_datas, numPlanes); d[j].maxsize = port->streamConfig.frameSize; d[j].chunk->offset = 0; d[j].chunk->size = port->streamConfig.frameSize; } switch (memtype) { case SPA_DATA_DmaBuf: case SPA_DATA_MemFd: d[j].flags |= SPA_DATA_FLAG_MAPPABLE; d[j].fd = planes[j].fd.get(); spa_log_debug(impl->log, "Got fd = %" PRId64 " for buffer: #%d", d[j].fd, i); break; default: spa_assert_not_reached(); break; } } } port->n_buffers = n_buffers; spa_log_debug(impl->log, "we have %d buffers", n_buffers); return 0; } void impl::requestComplete(libcamera::Request *request) { struct impl *impl = this; uint32_t index; spa_log_trace(impl->log, "%p: request %p[%" PRIu64 "] completed status:%u seq:%" PRIu32, impl, request, request->cookie(), static_cast(request->status()), request->sequence()); spa_ringbuffer_get_write_index(&impl->completed_requests_rb, &index); impl->completed_requests[index & MASK_BUFFERS] = request; spa_ringbuffer_write_update(&impl->completed_requests_rb, index + 1); if (spa_system_eventfd_write(impl->system, impl->source.fd, 1) < 0) spa_log_error(impl->log, "Failed to write on event fd"); } int spa_libcamera_stream_on(struct impl *impl) { struct port *port = &impl->out_ports[0]; int res; if (!port->current_format) { spa_log_error(impl->log, "Exiting %s with -EIO", __FUNCTION__); return -EIO; } if (impl->active) return 0; spa_log_info(impl->log, "starting camera %s", impl->camera->id().c_str()); if ((res = impl->camera->start(&impl->initial_controls)) < 0) return res == -EACCES ? -EBUSY : res; impl->camera->requestCompleted.connect(impl, &impl::requestComplete); res = spa_system_eventfd_create(impl->system, SPA_FD_CLOEXEC | SPA_FD_NONBLOCK); if (res < 0) goto err_stop; impl->source.fd = res; impl->source.func = libcamera_on_fd_events; impl->source.data = impl; impl->source.mask = SPA_IO_IN | SPA_IO_ERR; impl->source.rmask = 0; for (auto& req : impl->requestPool) { req->reuse(libcamera::Request::ReuseFlag::ReuseBuffers); if ((res = impl->camera->queueRequest(req.get())) < 0) goto err_stop; } impl->dll.bw = 0.0; impl->active = true; res = spa_loop_locked( impl->data_loop, [](spa_loop *, bool, uint32_t, const void *, size_t, void *user_data) { auto *impl = static_cast(user_data); return spa_loop_add_source(impl->data_loop, &impl->source); }, 0, nullptr, 0, impl ); if (res < 0) goto err_stop; return 0; err_stop: impl->stop(); return res; } int spa_libcamera_stream_off(struct impl *impl) { if (!impl->active) return 0; spa_log_info(impl->log, "stopping camera %s", impl->camera->id().c_str()); impl->stop(); return 0; } int port_get_format(struct impl *impl, struct port *port, uint32_t index, const struct spa_pod *filter, struct spa_pod **param, struct spa_pod_builder *builder) { struct spa_pod_frame f; if (!port->current_format) return -EIO; if (index > 0) return 0; spa_pod_builder_push_object(builder, &f, SPA_TYPE_OBJECT_Format, SPA_PARAM_Format); spa_pod_builder_add(builder, SPA_FORMAT_mediaType, SPA_POD_Id(port->current_format->media_type), SPA_FORMAT_mediaSubtype, SPA_POD_Id(port->current_format->media_subtype), 0); switch (port->current_format->media_subtype) { case SPA_MEDIA_SUBTYPE_raw: spa_pod_builder_add(builder, SPA_FORMAT_VIDEO_format, SPA_POD_Id(port->current_format->info.raw.format), SPA_FORMAT_VIDEO_size, SPA_POD_Rectangle(&port->current_format->info.raw.size), 0); break; case SPA_MEDIA_SUBTYPE_mjpg: case SPA_MEDIA_SUBTYPE_jpeg: spa_pod_builder_add(builder, SPA_FORMAT_VIDEO_size, SPA_POD_Rectangle(&port->current_format->info.mjpg.size), 0); break; case SPA_MEDIA_SUBTYPE_h264: spa_pod_builder_add(builder, SPA_FORMAT_VIDEO_size, SPA_POD_Rectangle(&port->current_format->info.h264.size), 0); break; default: return -EIO; } *param = (struct spa_pod*)spa_pod_builder_pop(builder, &f); return 1; } int impl_node_enum_params(void *object, int seq, uint32_t id, uint32_t start, uint32_t num, const struct spa_pod *filter) { struct impl *impl = (struct impl*)object; struct spa_pod *param; struct spa_pod_builder b = { 0 }; uint8_t buffer[1024]; struct spa_result_node_params result; uint32_t count = 0; int res; spa_return_val_if_fail(impl != nullptr, -EINVAL); spa_return_val_if_fail(num != 0, -EINVAL); result.id = id; result.next = start; next: result.index = result.next++; spa_pod_builder_init(&b, buffer, sizeof(buffer)); switch (id) { case SPA_PARAM_PropInfo: { switch (result.index) { default: return spa_libcamera_enum_controls(impl, GET_OUT_PORT(impl, 0), seq, result.index, 0, num, filter); } break; } case SPA_PARAM_Props: { switch (result.index) { default: return 0; } break; } case SPA_PARAM_EnumFormat: return spa_libcamera_enum_format(impl, GET_OUT_PORT(impl, 0), seq, start, num, filter); case SPA_PARAM_Format: if ((res = port_get_format(impl, GET_OUT_PORT(impl, 0), result.index, filter, ¶m, &b)) <= 0) return res; break; default: return -ENOENT; } if (spa_pod_filter(&b, &result.param, param, filter) < 0) goto next; spa_node_emit_result(&impl->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); if (++count != num) goto next; return 0; } int impl_node_set_param(void *object, uint32_t id, uint32_t flags, const struct spa_pod *param) { auto *impl = static_cast(object); spa_return_val_if_fail(impl != nullptr, -EINVAL); switch (id) { case SPA_PARAM_Props: { const auto *obj = reinterpret_cast(param); const struct spa_pod_prop *prop; if (param == nullptr) return 0; libcamera::ControlList controls(impl->camera->controls()); int res; SPA_POD_OBJECT_FOREACH(obj, prop) { switch (prop->key) { default: res = control_list_update_from_prop(controls, prop, SPA_POD_BODY_CONST(&prop->value)); if (res < 0) return res; break; } } res = spa_libcamera_apply_controls(impl, std::move(controls)); if (res < 0) return res; break; } default: return -ENOENT; } return 0; } int impl_node_set_io(void *object, uint32_t id, void *data, size_t size) { struct impl *impl = (struct impl*)object; spa_return_val_if_fail(impl != nullptr, -EINVAL); switch (id) { case SPA_IO_Clock: impl->clock = (struct spa_io_clock*)data; if (impl->clock) SPA_FLAG_SET(impl->clock->flags, SPA_IO_CLOCK_FLAG_NO_RATE); break; case SPA_IO_Position: impl->position = (struct spa_io_position*)data; break; default: return -ENOENT; } return 0; } int impl_node_send_command(void *object, const struct spa_command *command) { struct impl *impl = (struct impl*)object; int res; spa_return_val_if_fail(impl != nullptr, -EINVAL); spa_return_val_if_fail(command != nullptr, -EINVAL); switch (SPA_NODE_COMMAND_ID(command)) { case SPA_NODE_COMMAND_Start: { struct port *port = GET_OUT_PORT(impl, 0); if (!port->current_format) return -EIO; if (port->n_buffers == 0) return -EIO; if ((res = spa_libcamera_stream_on(impl)) < 0) return res; break; } case SPA_NODE_COMMAND_Pause: case SPA_NODE_COMMAND_Suspend: if ((res = spa_libcamera_stream_off(impl)) < 0) return res; break; default: return -ENOTSUP; } return 0; } void emit_node_info(struct impl *impl, bool full) { static const struct spa_dict_item info_items[] = { { SPA_KEY_DEVICE_API, "libcamera" }, { SPA_KEY_MEDIA_CLASS, "Video/Source" }, { SPA_KEY_MEDIA_ROLE, "Camera" }, { SPA_KEY_NODE_DRIVER, "true" }, }; uint64_t old = full ? impl->info.change_mask : 0; if (full) impl->info.change_mask = impl->info_all; if (impl->info.change_mask) { struct spa_dict dict = SPA_DICT_INIT_ARRAY(info_items); impl->info.props = &dict; spa_node_emit_info(&impl->hooks, &impl->info); impl->info.change_mask = old; } } void emit_port_info(struct impl *impl, struct port *port, bool full) { static const struct spa_dict_item info_items[] = { { SPA_KEY_PORT_GROUP, "stream.0" }, }; uint64_t old = full ? port->info.change_mask : 0; if (full) port->info.change_mask = port->info_all; if (port->info.change_mask) { struct spa_dict dict = SPA_DICT_INIT_ARRAY(info_items); port->info.props = &dict; spa_node_emit_port_info(&impl->hooks, SPA_DIRECTION_OUTPUT, 0, &port->info); port->info.change_mask = old; } } int impl_node_add_listener(void *object, struct spa_hook *listener, const struct spa_node_events *events, void *data) { struct impl *impl = (struct impl*)object; struct spa_hook_list save; spa_return_val_if_fail(impl != nullptr, -EINVAL); spa_hook_list_isolate(&impl->hooks, &save, listener, events, data); emit_node_info(impl, true); emit_port_info(impl, GET_OUT_PORT(impl, 0), true); spa_hook_list_join(&impl->hooks, &save); return 0; } int impl_node_set_callbacks(void *object, const struct spa_node_callbacks *callbacks, void *data) { struct impl *impl = (struct impl*)object; spa_return_val_if_fail(impl != nullptr, -EINVAL); impl->callbacks = SPA_CALLBACKS_INIT(callbacks, data); return 0; } int impl_node_sync(void *object, int seq) { struct impl *impl = (struct impl*)object; spa_return_val_if_fail(impl != nullptr, -EINVAL); spa_node_emit_result(&impl->hooks, seq, 0, 0, nullptr); return 0; } int impl_node_add_port(void *object, enum spa_direction direction, uint32_t port_id, const struct spa_dict *props) { return -ENOTSUP; } int impl_node_remove_port(void *object, enum spa_direction direction, uint32_t port_id) { return -ENOTSUP; } int impl_node_port_enum_params(void *object, int seq, enum spa_direction direction, uint32_t port_id, uint32_t id, uint32_t start, uint32_t num, const struct spa_pod *filter) { struct impl *impl = (struct impl*)object; struct port *port; struct spa_pod *param; struct spa_pod_builder b = { 0 }; uint8_t buffer[1024]; struct spa_result_node_params result; uint32_t count = 0; int res; spa_return_val_if_fail(impl != nullptr, -EINVAL); spa_return_val_if_fail(num != 0, -EINVAL); spa_return_val_if_fail(CHECK_PORT(impl, direction, port_id), -EINVAL); port = GET_PORT(impl, direction, port_id); result.id = id; result.next = start; next: result.index = result.next++; spa_pod_builder_init(&b, buffer, sizeof(buffer)); switch (id) { case SPA_PARAM_PropInfo: return spa_libcamera_enum_controls(impl, port, seq, start, 0, num, filter); case SPA_PARAM_EnumFormat: return spa_libcamera_enum_format(impl, port, seq, start, num, filter); case SPA_PARAM_Format: if((res = port_get_format(impl, port, result.index, filter, ¶m, &b)) <= 0) return res; break; case SPA_PARAM_Buffers: { if (!port->current_format) return -EIO; if (result.index > 0) return 0; /* Get the number of buffers to be used from libcamera and send the same to pipewire * so that exact number of buffers are allocated */ uint32_t n_buffers = port->streamConfig.bufferCount; param = (struct spa_pod*)spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamBuffers, id, SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(n_buffers, n_buffers, n_buffers), SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(port->buffers_blocks), SPA_PARAM_BUFFERS_size, SPA_POD_Int(port->streamConfig.frameSize), SPA_PARAM_BUFFERS_stride, SPA_POD_Int(port->streamConfig.stride)); break; } case SPA_PARAM_Meta: switch (result.index) { case 0: param = (struct spa_pod*)spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamMeta, id, SPA_PARAM_META_type, SPA_POD_Id(SPA_META_Header), SPA_PARAM_META_size, SPA_POD_Int(sizeof(struct spa_meta_header))); break; case 1: param = (struct spa_pod*)spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamMeta, id, SPA_PARAM_META_type, SPA_POD_Id(SPA_META_VideoTransform), SPA_PARAM_META_size, SPA_POD_Int(sizeof(struct spa_meta_videotransform))); break; default: return 0; } break; case SPA_PARAM_IO: switch (result.index) { case 0: param = (struct spa_pod*)spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamIO, id, SPA_PARAM_IO_id, SPA_POD_Id(SPA_IO_Buffers), SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_buffers))); break; case 1: param = (struct spa_pod*)spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamIO, id, SPA_PARAM_IO_id, SPA_POD_Id(SPA_IO_Clock), SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_clock))); break; case 2: param = (struct spa_pod*)spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamIO, id, SPA_PARAM_IO_id, SPA_POD_Id(SPA_IO_Control), SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_sequence))); break; default: return 0; } break; case SPA_PARAM_Latency: switch (result.index) { case 0: case 1: param = spa_latency_build(&b, id, &impl->latency[result.index]); break; default: return 0; } break; default: return -ENOENT; } if (spa_pod_filter(&b, &result.param, param, filter) < 0) goto next; spa_node_emit_result(&impl->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); if (++count != num) goto next; return 0; } int port_set_format(struct impl *impl, struct port *port, uint32_t flags, const struct spa_pod *format) { const bool try_only = SPA_FLAG_IS_SET(flags, SPA_NODE_PARAM_FLAG_TEST_ONLY); if (!try_only) { spa_libcamera_stream_off(impl); spa_libcamera_clear_buffers(port); freeBuffers(impl, port); port->current_format.reset(); } if (format == nullptr) { if (!try_only) spa_libcamera_close(impl); } else { spa_video_info info; int res; spa_zero(info); if ((res = spa_format_parse(format, &info.media_type, &info.media_subtype)) < 0) return res; if (info.media_type != SPA_MEDIA_TYPE_video) { spa_log_error(impl->log, "media type must be video"); return -EINVAL; } switch (info.media_subtype) { case SPA_MEDIA_SUBTYPE_raw: if (spa_format_video_raw_parse(format, &info.info.raw) < 0) { spa_log_error(impl->log, "can't parse video raw"); return -EINVAL; } break; case SPA_MEDIA_SUBTYPE_mjpg: if (spa_format_video_mjpg_parse(format, &info.info.mjpg) < 0) return -EINVAL; break; case SPA_MEDIA_SUBTYPE_h264: if (spa_format_video_h264_parse(format, &info.info.h264) < 0) return -EINVAL; break; default: return -EINVAL; } res = spa_libcamera_set_format(impl, port, &info, try_only); if (res < 0) return res; if (!try_only) port->current_format = info; } if (try_only) return 0; impl->info.change_mask |= SPA_NODE_CHANGE_MASK_PARAMS; port->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; if (port->current_format) { impl->params[NODE_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_READWRITE); port->params[PORT_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_READWRITE); port->params[PORT_Buffers] = SPA_PARAM_INFO(SPA_PARAM_Buffers, SPA_PARAM_INFO_READ); } else { impl->params[NODE_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); port->params[PORT_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); port->params[PORT_Buffers] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); } emit_port_info(impl, port, false); emit_node_info(impl, false); return 0; } int impl_node_port_set_param(void *object, enum spa_direction direction, uint32_t port_id, uint32_t id, uint32_t flags, const struct spa_pod *param) { struct impl *impl = (struct impl*)object; struct port *port; int res; spa_return_val_if_fail(object != nullptr, -EINVAL); spa_return_val_if_fail(CHECK_PORT(impl, direction, port_id), -EINVAL); port = GET_PORT(impl, direction, port_id); switch (id) { case SPA_PARAM_Format: res = port_set_format(impl, port, flags, param); break; default: res = -ENOENT; } return res; } int impl_node_port_use_buffers(void *object, enum spa_direction direction, uint32_t port_id, uint32_t flags, struct spa_buffer **buffers, uint32_t n_buffers) { struct impl *impl = (struct impl*)object; struct port *port; int res; spa_return_val_if_fail(impl != nullptr, -EINVAL); spa_return_val_if_fail(CHECK_PORT(impl, direction, port_id), -EINVAL); port = GET_PORT(impl, direction, port_id); if (port->n_buffers) { spa_libcamera_stream_off(impl); if ((res = spa_libcamera_clear_buffers(port)) < 0) return res; } if (n_buffers > 0 && !port->current_format) return -EIO; if (n_buffers > MAX_BUFFERS) return -ENOSPC; if (buffers == nullptr) return 0; if (flags & SPA_NODE_BUFFERS_FLAG_ALLOC) { res = spa_libcamera_alloc_buffers(impl, port, buffers, n_buffers); } else { res = spa_libcamera_use_buffers(impl, port, buffers, n_buffers); } return res; } int impl_node_port_set_io(void *object, enum spa_direction direction, uint32_t port_id, uint32_t id, void *data, size_t size) { struct impl *impl = (struct impl*)object; struct port *port; spa_return_val_if_fail(impl != nullptr, -EINVAL); spa_return_val_if_fail(CHECK_PORT(impl, direction, port_id), -EINVAL); port = GET_PORT(impl, direction, port_id); switch (id) { case SPA_IO_Buffers: port->io = (struct spa_io_buffers*)data; break; case SPA_IO_Control: port->control = (struct spa_io_sequence*)data; port->control_size = size; break; default: return -ENOENT; } return 0; } int impl_node_port_reuse_buffer(void *object, uint32_t port_id, uint32_t buffer_id) { struct impl *impl = (struct impl*)object; struct port *port; int res; spa_return_val_if_fail(impl != nullptr, -EINVAL); spa_return_val_if_fail(port_id == 0, -EINVAL); port = GET_OUT_PORT(impl, port_id); spa_return_val_if_fail(buffer_id < port->n_buffers, -EINVAL); res = spa_libcamera_buffer_recycle(impl, port, buffer_id); return res; } int process_control(struct impl *impl, struct spa_pod_sequence *control, uint32_t size) { libcamera::ControlList controls(impl->camera->controls()); struct spa_pod_parser parser[2]; struct spa_pod_frame frame[2]; struct spa_pod_sequence seq; const void *seq_body, *c_body; struct spa_pod_control c; int res; spa_pod_parser_init_from_data(&parser[0], control, size, 0, size); if (spa_pod_parser_push_sequence_body(&parser[0], &frame[0], &seq, &seq_body) < 0) return 0; while (spa_pod_parser_get_control_body(&parser[0], &c, &c_body) >= 0) { switch (c.type) { case SPA_CONTROL_Properties: { struct spa_pod_object obj; struct spa_pod_prop prop; const void *obj_body, *prop_body; if (spa_pod_parser_init_object_body(&parser[1], &frame[1], &c.value, c_body, &obj, &obj_body) < 0) continue; while (spa_pod_parser_get_prop_body(&parser[1], &prop, &prop_body) >= 0) { res = control_list_update_from_prop(controls, &prop, prop_body); if (res < 0) return res; } break; } default: break; } } res = spa_libcamera_apply_controls(impl, std::move(controls)); if (res < 0) return res; return 0; } int impl_node_process(void *object) { struct impl *impl = (struct impl*)object; int res; struct spa_io_buffers *io; struct port *port; struct buffer *b; spa_return_val_if_fail(impl != nullptr, -EINVAL); port = GET_OUT_PORT(impl, 0); if ((io = port->io) == nullptr) return -EIO; if (port->control) process_control(impl, &port->control->sequence, port->control_size); spa_log_trace(impl->log, "%p: status %d", impl, io->status); if (io->status == SPA_STATUS_HAVE_DATA) { return SPA_STATUS_HAVE_DATA; } if (io->buffer_id < port->n_buffers) { if ((res = spa_libcamera_buffer_recycle(impl, port, io->buffer_id)) < 0) return res; io->buffer_id = SPA_ID_INVALID; } if (spa_list_is_empty(&port->queue)) { return SPA_STATUS_OK; } b = spa_list_first(&port->queue, struct buffer, link); spa_list_remove(&b->link); SPA_FLAG_SET(b->flags, BUFFER_FLAG_OUTSTANDING); spa_log_trace(impl->log, "%p: dequeue buffer %d", impl, b->id); io->buffer_id = b->id; io->status = SPA_STATUS_HAVE_DATA; return SPA_STATUS_HAVE_DATA; } const struct spa_node_methods impl_node = { .version = SPA_VERSION_NODE_METHODS, .add_listener = impl_node_add_listener, .set_callbacks = impl_node_set_callbacks, .sync = impl_node_sync, .enum_params = impl_node_enum_params, .set_param = impl_node_set_param, .set_io = impl_node_set_io, .send_command = impl_node_send_command, .add_port = impl_node_add_port, .remove_port = impl_node_remove_port, .port_enum_params = impl_node_port_enum_params, .port_set_param = impl_node_port_set_param, .port_use_buffers = impl_node_port_use_buffers, .port_set_io = impl_node_port_set_io, .port_reuse_buffer = impl_node_port_reuse_buffer, .process = impl_node_process, }; int impl_get_interface(struct spa_handle *handle, const char *type, void **interface) { auto *impl = reinterpret_cast(handle); spa_return_val_if_fail(handle != nullptr, -EINVAL); spa_return_val_if_fail(interface != nullptr, -EINVAL); if (spa_streq(type, SPA_TYPE_INTERFACE_Node)) *interface = &impl->node; else return -ENOENT; return 0; } int impl_clear(struct spa_handle *handle) { std::destroy_at(reinterpret_cast(handle)); return 0; } impl::impl(spa_log *log, spa_loop *data_loop, spa_system *system, std::shared_ptr manager, std::shared_ptr camera, std::unique_ptr config) : handle({ SPA_VERSION_HANDLE, impl_get_interface, impl_clear }), log(log), data_loop(data_loop), system(system), out_ports{{this}}, manager(std::move(manager)), camera(std::move(camera)), config(std::move(config)), allocator(this->camera) { libcamera_log_topic_init(log); spa_hook_list_init(&hooks); node.iface = SPA_INTERFACE_INIT( SPA_TYPE_INTERFACE_Node, SPA_VERSION_NODE, &impl_node, this); params[NODE_PropInfo] = SPA_PARAM_INFO(SPA_PARAM_PropInfo, SPA_PARAM_INFO_READ); params[NODE_Props] = SPA_PARAM_INFO(SPA_PARAM_Props, SPA_PARAM_INFO_READWRITE); params[NODE_EnumFormat] = SPA_PARAM_INFO(SPA_PARAM_EnumFormat, SPA_PARAM_INFO_READ); params[NODE_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); info.max_output_ports = 1; info.flags = SPA_NODE_FLAG_RT; info.params = params; info.n_params = N_NODE_PARAMS; latency[SPA_DIRECTION_INPUT] = SPA_LATENCY_INFO(SPA_DIRECTION_INPUT); latency[SPA_DIRECTION_OUTPUT] = SPA_LATENCY_INFO(SPA_DIRECTION_OUTPUT); } size_t impl_get_size(const struct spa_handle_factory *factory, const struct spa_dict *params) { return sizeof(struct impl); } int impl_init(const struct spa_handle_factory *factory, struct spa_handle *handle, const struct spa_dict *info, const struct spa_support *support, uint32_t n_support) { int res; spa_return_val_if_fail(factory != nullptr, -EINVAL); spa_return_val_if_fail(handle != nullptr, -EINVAL); auto log = static_cast(spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log)); auto data_loop = static_cast(spa_support_find(support, n_support, SPA_TYPE_INTERFACE_DataLoop)); auto system = static_cast(spa_support_find(support, n_support, SPA_TYPE_INTERFACE_System)); if (!data_loop) { spa_log_error(log, "a data_loop is needed"); return -EINVAL; } if (!system) { spa_log_error(log, "a system is needed"); return -EINVAL; } auto manager = libcamera_manager_acquire(res); if (!manager) { spa_log_error(log, "can't start camera manager: %s", spa_strerror(res)); return res; } const char *device_id = info ? spa_dict_lookup(info, SPA_KEY_API_LIBCAMERA_PATH) : nullptr; auto camera = device_id ? manager->get(device_id) : nullptr; if (!camera) { spa_log_error(log, "unknown camera id: %s", device_id); return -ENOENT; } auto config = camera->generateConfiguration({ libcamera::StreamRole::VideoRecording }); if (!config) { spa_log_error(log, "cannot generate configuration for camera"); return -EINVAL; } new (handle) impl(log, data_loop, system, std::move(manager), std::move(camera), std::move(config)); return 0; } const struct spa_interface_info impl_interfaces[] = { {SPA_TYPE_INTERFACE_Node,}, }; int impl_enum_interface_info(const struct spa_handle_factory *factory, const struct spa_interface_info **info, uint32_t *index) { spa_return_val_if_fail(factory != nullptr, -EINVAL); spa_return_val_if_fail(info != nullptr, -EINVAL); spa_return_val_if_fail(index != nullptr, -EINVAL); if (*index >= SPA_N_ELEMENTS(impl_interfaces)) return 0; *info = &impl_interfaces[(*index)++]; return 1; } } extern "C" { const struct spa_handle_factory spa_libcamera_source_factory = { SPA_VERSION_HANDLE_FACTORY, SPA_NAME_API_LIBCAMERA_SOURCE, nullptr, impl_get_size, impl_init, impl_enum_interface_info, }; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/libcamera/libcamera.c000066400000000000000000000014261511204443500267470ustar00rootroot00000000000000/* Spa libcamera support */ /* SPDX-FileCopyrightText: Copyright © 2020 collabora */ /* SPDX-License-Identifier: MIT */ #include #include #include #include "libcamera.h" SPA_LOG_TOPIC_DEFINE(libcamera_log_topic, "spa.libcamera"); SPA_LOG_TOPIC_ENUM_DEFINE_REGISTERED; SPA_EXPORT int spa_handle_factory_enum(const struct spa_handle_factory **factory, uint32_t *index) { spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(index != NULL, -EINVAL); switch (*index) { case 0: *factory = &spa_libcamera_manager_factory; break; case 1: *factory = &spa_libcamera_device_factory; break; case 2: *factory = &spa_libcamera_source_factory; break; default: return 0; } (*index)++; return 1; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/libcamera/libcamera.h000066400000000000000000000014271511204443500267550ustar00rootroot00000000000000/* Spa libcamera support */ /* SPDX-FileCopyrightText: Copyright © 2020 collabora */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #ifdef __cplusplus extern "C" { #endif /* __cplusplus */ extern const struct spa_handle_factory spa_libcamera_source_factory; extern const struct spa_handle_factory spa_libcamera_manager_factory; extern const struct spa_handle_factory spa_libcamera_device_factory; #undef SPA_LOG_TOPIC_DEFAULT #define SPA_LOG_TOPIC_DEFAULT &libcamera_log_topic extern struct spa_log_topic libcamera_log_topic; static inline void libcamera_log_topic_init(struct spa_log *log) { spa_log_topic_init(log, &libcamera_log_topic); } #ifdef __cplusplus } #endif /* __cplusplus */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/libcamera/meson.build000066400000000000000000000005351511204443500270260ustar00rootroot00000000000000libcamera_sources = [ 'libcamera.c', 'libcamera-manager.cpp', 'libcamera-device.cpp', 'libcamera-source.cpp' ] libcameralib = shared_library('spa-libcamera', libcamera_sources, include_directories : [ configinc ], dependencies : [ spa_dep, libcamera_dep, pthread_lib ], install : true, install_dir : spa_plugindir / 'libcamera') pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/meson.build000066400000000000000000000025461511204443500251130ustar00rootroot00000000000000if alsa_dep.found() and host_machine.system() == 'linux' subdir('alsa') endif if get_option('avb').require(host_machine.system() == 'linux', error_message: 'AVB support is only available on Linux').allowed() subdir('avb') endif if get_option('audioconvert').allowed() subdir('audioconvert') endif if get_option('audiomixer').allowed() subdir('audiomixer') endif if get_option('control').allowed() subdir('control') endif if get_option('audiotestsrc').allowed() subdir('audiotestsrc') endif if bluez_deps_found subdir('bluez5') endif if avcodec_dep.found() subdir('ffmpeg') endif if jack_dep.found() subdir('jack') endif if get_option('support').allowed() subdir('support') endif if get_option('test').allowed() subdir('test') endif if get_option('videoconvert').allowed() subdir('videoconvert') endif if get_option('videotestsrc').allowed() subdir('videotestsrc') endif if get_option('volume').allowed() subdir('volume') endif if have_vulkan subdir('vulkan') endif v4l2_header_found = cc.has_header('linux/videodev2.h', required: get_option('v4l2')) summary({'V4L2 kernel header': v4l2_header_found}, bool_yn: true, section: 'Backend') summary({'V4L2 enabled': v4l2_header_found}, bool_yn: true, section: 'Backend') if v4l2_header_found subdir('v4l2') endif if libcamera_dep.found() subdir('libcamera') endif subdir('aec') subdir('filter-graph') pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/support/000077500000000000000000000000001511204443500244565ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/support/cpu-arm.c000066400000000000000000000046171511204443500261760ustar00rootroot00000000000000/* Spa */ /* SPDX-FileCopyrightText: Copyright © 2019 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #define MAX_BUFFER 4096 static char *get_cpuinfo_line(char *cpuinfo, const char *tag) { char *line, *end, *colon; if (!(line = strstr(cpuinfo, tag))) return NULL; if (!(end = strchr(line, '\n'))) return NULL; if (!(colon = strchr(line, ':'))) return NULL; if (++colon >= end) return NULL; return strndup(colon, end - colon); } static int arm_init(struct impl *impl) { uint32_t flags = 0; char *cpuinfo, *line, buffer[MAX_BUFFER]; int arch; if (!(cpuinfo = spa_cpu_read_file("/proc/cpuinfo", buffer, sizeof(buffer)))) { spa_log_warn(impl->log, "%p: Can't read cpuinfo", impl); return 1; } if ((line = get_cpuinfo_line(cpuinfo, "CPU architecture"))) { arch = strtoul(line, NULL, 0); if (arch >= 6) flags |= SPA_CPU_FLAG_ARMV6; if (arch >= 8) flags |= SPA_CPU_FLAG_ARMV8; free(line); } if ((line = get_cpuinfo_line(cpuinfo, "Features"))) { char *state = NULL; char *current = strtok_r(line, " ", &state); do { #if defined (__aarch64__) if (spa_streq(current, "asimd")) flags |= SPA_CPU_FLAG_NEON; else if (spa_streq(current, "fp")) flags |= SPA_CPU_FLAG_VFPV3 | SPA_CPU_FLAG_VFP; #else if (spa_streq(current, "vfp")) flags |= SPA_CPU_FLAG_VFP; else if (spa_streq(current, "neon")) flags |= SPA_CPU_FLAG_NEON; else if (spa_streq(current, "vfpv3")) flags |= SPA_CPU_FLAG_VFPV3; #endif } while ((current = strtok_r(NULL, " ", &state))); free(line); } impl->flags = flags; return 0; } static int arm_zero_denormals(void *object, bool enable) { #if defined(__aarch64__) uint64_t cw; if (enable) __asm__ __volatile__( "mrs %0, fpcr \n" "orr %0, %0, #0x1000000 \n" "msr fpcr, %0 \n" "isb \n" : "=r"(cw)::"memory"); else __asm__ __volatile__( "mrs %0, fpcr \n" "and %0, %0, #~0x1000000 \n" "msr fpcr, %0 \n" "isb \n" : "=r"(cw)::"memory"); #elif (defined(__VFP_FP__) && !defined(__SOFTFP__)) uint32_t cw; if (enable) __asm__ __volatile__( "vmrs %0, fpscr \n" "orr %0, %0, #0x1000000 \n" "vmsr fpscr, %0 \n" : "=r"(cw)::"memory"); else __asm__ __volatile__( "vmrs %0, fpscr \n" "and %0, %0, #~0x1000000 \n" "vmsr fpscr, %0 \n" : "=r"(cw)::"memory"); #endif return 0; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/support/cpu-riscv.c000066400000000000000000000010771511204443500265420ustar00rootroot00000000000000/* Spa */ /* SPDX-FileCopyrightText: Copyright (c) 2023 Institue of Software Chinese Academy of Sciences (ISCAS). */ /* SPDX-License-Identifier: MIT */ #ifdef HAVE_SYS_AUXV_H #include #define HWCAP_RV(letter) (1ul << ((letter) - 'A')) #endif static int riscv_init(struct impl *impl) { uint32_t flags = 0; #ifdef HAVE_SYS_AUXV_H const unsigned long hwcap = getauxval(AT_HWCAP); if (hwcap & HWCAP_RV('V')) flags |= SPA_CPU_FLAG_RISCV_V; #endif impl->flags = flags; return 0; } static int riscv_zero_denormals(void *object, bool enable) { return 0; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/support/cpu-x86.c000066400000000000000000000103071511204443500260350ustar00rootroot00000000000000/* Spa */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include static int x86_init(struct impl *impl) { uint32_t flags; unsigned int vendor; unsigned int model, family; unsigned int max_level, ext_level, has_osxsave; unsigned int eax, ebx, ecx, edx; max_level = __get_cpuid_max(0, &vendor); if (max_level < 1) return 0; __cpuid(1, eax, ebx, ecx, edx); model = (eax >> 4) & 0x0f; family = (eax >> 8) & 0x0f; if (vendor == signature_INTEL_ebx || vendor == signature_AMD_ebx) { unsigned int extended_model, extended_family; extended_model = (eax >> 12) & 0xf0; extended_family = (eax >> 20) & 0xff; if (family == 0x0f) { family += extended_family; model += extended_model; } else if (family == 0x06) model += extended_model; } (void)model; flags = 0; if (ecx & bit_SSE3) flags |= SPA_CPU_FLAG_SSE3; if (ecx & bit_SSSE3) flags |= SPA_CPU_FLAG_SSSE3; if (ecx & bit_SSE4_1) flags |= SPA_CPU_FLAG_SSE41; if (ecx & bit_SSE4_2) flags |= SPA_CPU_FLAG_SSE42; if (ecx & bit_AVX) flags |= SPA_CPU_FLAG_AVX; has_osxsave = ecx & bit_OSXSAVE; if (ecx & bit_FMA) flags |= SPA_CPU_FLAG_FMA3; if (edx & bit_CMOV) flags |= SPA_CPU_FLAG_CMOV; if (edx & bit_MMX) flags |= SPA_CPU_FLAG_MMX; if (edx & bit_MMXEXT) flags |= SPA_CPU_FLAG_MMXEXT; if (edx & bit_SSE) flags |= SPA_CPU_FLAG_SSE; if (edx & bit_SSE2) flags |= SPA_CPU_FLAG_SSE2; if (max_level >= 7) { __cpuid_count(7, 0, eax, ebx, ecx, edx); if (ebx & bit_BMI) flags |= SPA_CPU_FLAG_BMI1; if (ebx & bit_AVX2) flags |= SPA_CPU_FLAG_AVX2; if (ebx & bit_BMI2) flags |= SPA_CPU_FLAG_BMI2; #define AVX512_BITS (bit_AVX512F | bit_AVX512DQ | bit_AVX512CD | bit_AVX512BW | bit_AVX512VL) if ((ebx & AVX512_BITS) == AVX512_BITS) flags |= SPA_CPU_FLAG_AVX512; } /* Check cpuid level of extended features. */ __cpuid (0x80000000, ext_level, ebx, ecx, edx); if (ext_level >= 0x80000001) { __cpuid (0x80000001, eax, ebx, ecx, edx); if (edx & bit_3DNOW) flags |= SPA_CPU_FLAG_3DNOW; if (edx & bit_3DNOWP) flags |= SPA_CPU_FLAG_3DNOWEXT; if (edx & bit_MMX) flags |= SPA_CPU_FLAG_MMX; if (edx & bit_MMXEXT) flags |= SPA_CPU_FLAG_MMXEXT; if (ecx & bit_FMA4) flags |= SPA_CPU_FLAG_FMA4; if (ecx & bit_XOP) flags |= SPA_CPU_FLAG_XOP; } /* Get XCR_XFEATURE_ENABLED_MASK register with xgetbv. */ #define XCR_XFEATURE_ENABLED_MASK 0x0 #define XSTATE_FP 0x1 #define XSTATE_SSE 0x2 #define XSTATE_YMM 0x4 #define XSTATE_OPMASK 0x20 #define XSTATE_ZMM 0x40 #define XSTATE_HI_ZMM 0x80 #define XCR_AVX_ENABLED_MASK \ (XSTATE_SSE | XSTATE_YMM) #define XCR_AVX512F_ENABLED_MASK \ (XSTATE_SSE | XSTATE_YMM | XSTATE_OPMASK | XSTATE_ZMM | XSTATE_HI_ZMM) if (has_osxsave) asm (".byte 0x0f; .byte 0x01; .byte 0xd0" : "=a" (eax), "=d" (edx) : "c" (XCR_XFEATURE_ENABLED_MASK)); else eax = 0; /* Check if AVX registers are supported. */ if ((eax & XCR_AVX_ENABLED_MASK) != XCR_AVX_ENABLED_MASK) { flags &= ~(SPA_CPU_FLAG_AVX | SPA_CPU_FLAG_AVX2 | SPA_CPU_FLAG_FMA3 | SPA_CPU_FLAG_FMA4 | SPA_CPU_FLAG_XOP); } /* Check if AVX512F registers are supported. */ if ((eax & XCR_AVX512F_ENABLED_MASK) != XCR_AVX512F_ENABLED_MASK) { flags &= ~SPA_CPU_FLAG_AVX512; } if (flags & SPA_CPU_FLAG_AVX512) impl->max_align = 64; else if (flags & (SPA_CPU_FLAG_AVX2 | SPA_CPU_FLAG_AVX | SPA_CPU_FLAG_XOP | SPA_CPU_FLAG_FMA4 | SPA_CPU_FLAG_FMA3)) impl->max_align = 32; else if (flags & (SPA_CPU_FLAG_AESNI | SPA_CPU_FLAG_SSE42 | SPA_CPU_FLAG_SSE41 | SPA_CPU_FLAG_SSSE3 | SPA_CPU_FLAG_SSE3 | SPA_CPU_FLAG_SSE2 | SPA_CPU_FLAG_SSE)) impl->max_align = 16; else impl->max_align = 8; impl->flags = flags; return 0; } #if defined(HAVE_SSE) #include #endif static int x86_zero_denormals(void *object, bool enable) { #if defined(HAVE_SSE) struct impl *impl = object; if (impl->flags & SPA_CPU_FLAG_SSE) { unsigned int mxcsr; mxcsr = _mm_getcsr(); if (enable) mxcsr |= 0x8040; else mxcsr &= ~0x8040; _mm_setcsr(mxcsr); spa_log_debug(impl->log, "%p: zero-denormals:%s", impl, enable ? "on" : "off"); } return 0; #else return -ENOTSUP; #endif } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/support/cpu.c000066400000000000000000000153101511204443500254110ustar00rootroot00000000000000/* Spa */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include "config.h" #include #include #include #include #include #include #include #if defined(__FreeBSD__) || defined(__MidnightBSD__) #include #endif #include #include #include #include #include #include #include SPA_LOG_TOPIC_DEFINE_STATIC(log_topic, "spa.cpu"); #undef SPA_LOG_TOPIC_DEFAULT #define SPA_LOG_TOPIC_DEFAULT &log_topic struct impl { struct spa_handle handle; struct spa_cpu cpu; struct spa_log *log; uint32_t flags; uint32_t force; uint32_t count; uint32_t max_align; uint32_t vm_type; }; static char *spa_cpu_read_file(const char *name, char *buffer, size_t len) { int n, fd; if ((fd = open(name, O_RDONLY | O_CLOEXEC, 0)) < 0) return NULL; if ((n = read(fd, buffer, len-1)) < 0) { close(fd); return NULL; } buffer[n] = '\0'; close(fd); return buffer; } # if defined (__i386__) || defined (__x86_64__) #include "cpu-x86.c" #define init(t) x86_init(t) #define impl_cpu_zero_denormals x86_zero_denormals # elif defined (__arm__) || defined (__aarch64__) #include "cpu-arm.c" #define init(t) arm_init(t) #define impl_cpu_zero_denormals arm_zero_denormals # elif defined (__riscv) #include "cpu-riscv.c" #define init(t) riscv_init(t) #define impl_cpu_zero_denormals riscv_zero_denormals # else #define init(t) #define impl_cpu_zero_denormals NULL #endif static uint32_t impl_cpu_get_flags(void *object) { struct impl *impl = object; if (impl->force != SPA_CPU_FORCE_AUTODETECT) return impl->force; return impl->flags; } static int impl_cpu_force_flags(void *object, uint32_t flags) { struct impl *impl = object; impl->force = flags; return 0; } #ifndef __FreeBSD__ static uint32_t get_count(struct impl *this) { cpu_set_t cpuset; CPU_ZERO(&cpuset); if (sched_getaffinity(0, sizeof(cpuset), &cpuset) == 0) return CPU_COUNT(&cpuset); return 1; } #else static uint32_t get_count(struct impl *this) { static const int mib[] = {CTL_HW, HW_NCPU}; int r; size_t rSize = sizeof(r); if(-1 == sysctl(mib, 2, &r, &rSize, 0, 0)) return 1; return r; } #endif static uint32_t impl_cpu_get_count(void *object) { struct impl *impl = object; return impl->count; } static uint32_t impl_cpu_get_max_align(void *object) { struct impl *impl = object; return impl->max_align; } static uint32_t impl_cpu_get_vm_type(void *object) { struct impl *impl = object; if (impl->vm_type != 0) return impl->vm_type; #if defined(__i386__) || defined(__x86_64__) || defined(__arm__) || defined(__aarch64__) static const char *const dmi_vendors[] = { "/sys/class/dmi/id/product_name", /* Test this before sys_vendor to detect KVM over QEMU */ "/sys/class/dmi/id/sys_vendor", "/sys/class/dmi/id/board_vendor", "/sys/class/dmi/id/bios_vendor" }; static const struct { const char *vendor; int id; } dmi_vendor_table[] = { { "KVM", SPA_CPU_VM_KVM }, { "QEMU", SPA_CPU_VM_QEMU }, { "VMware", SPA_CPU_VM_VMWARE }, /* https://kb.vmware.com/s/article/1009458 */ { "VMW", SPA_CPU_VM_VMWARE }, { "innotek GmbH", SPA_CPU_VM_ORACLE }, { "Oracle Corporation", SPA_CPU_VM_ORACLE }, { "Xen", SPA_CPU_VM_XEN }, { "Bochs", SPA_CPU_VM_BOCHS }, { "Parallels", SPA_CPU_VM_PARALLELS }, /* https://wiki.freebsd.org/bhyve */ { "BHYVE", SPA_CPU_VM_BHYVE }, }; SPA_FOR_EACH_ELEMENT_VAR(dmi_vendors, dv) { char buffer[256], *s; if ((s = spa_cpu_read_file(*dv, buffer, sizeof(buffer))) == NULL) continue; SPA_FOR_EACH_ELEMENT_VAR(dmi_vendor_table, t) { if (spa_strstartswith(s, t->vendor)) { spa_log_debug(impl->log, "Virtualization %s found in DMI (%s)", s, *dv); impl->vm_type = t->id; goto done; } } } done: #endif return impl->vm_type; } static const struct spa_cpu_methods impl_cpu = { SPA_VERSION_CPU_METHODS, .get_flags = impl_cpu_get_flags, .force_flags = impl_cpu_force_flags, .get_count = impl_cpu_get_count, .get_max_align = impl_cpu_get_max_align, .get_vm_type = impl_cpu_get_vm_type, .zero_denormals = impl_cpu_zero_denormals, }; static int impl_get_interface(struct spa_handle *handle, const char *type, void **interface) { struct impl *this; spa_return_val_if_fail(handle != NULL, -EINVAL); spa_return_val_if_fail(interface != NULL, -EINVAL); this = (struct impl *) handle; if (spa_streq(type, SPA_TYPE_INTERFACE_CPU)) *interface = &this->cpu; else return -ENOENT; return 0; } static int impl_clear(struct spa_handle *handle) { return 0; } static size_t impl_get_size(const struct spa_handle_factory *factory, const struct spa_dict *params) { return sizeof(struct impl); } static int impl_init(const struct spa_handle_factory *factory, struct spa_handle *handle, const struct spa_dict *info, const struct spa_support *support, uint32_t n_support) { struct impl *this; const char *str; spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(handle != NULL, -EINVAL); handle->get_interface = impl_get_interface; handle->clear = impl_clear; this = (struct impl *) handle; this->cpu.iface = SPA_INTERFACE_INIT( SPA_TYPE_INTERFACE_CPU, SPA_VERSION_CPU, &impl_cpu, this); this->log = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log); spa_log_topic_init(this->log, &log_topic); this->flags = 0; this->force = SPA_CPU_FORCE_AUTODETECT; this->max_align = 16; this->count = get_count(this); init(this); if (info) { if ((str = spa_dict_lookup(info, SPA_KEY_CPU_FORCE)) != NULL) this->flags = atoi(str); if ((str = spa_dict_lookup(info, SPA_KEY_CPU_VM_TYPE)) != NULL) this->vm_type = atoi(str); if ((str = spa_dict_lookup(info, SPA_KEY_CPU_ZERO_DENORMALS)) != NULL) spa_cpu_zero_denormals(&this->cpu, spa_atob(str)); } spa_log_debug(this->log, "%p: count:%d align:%d flags:%08x", this, this->count, this->max_align, this->flags); return 0; } static const struct spa_interface_info impl_interfaces[] = { {SPA_TYPE_INTERFACE_CPU,}, }; static int impl_enum_interface_info(const struct spa_handle_factory *factory, const struct spa_interface_info **info, uint32_t *index) { spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(info != NULL, -EINVAL); spa_return_val_if_fail(index != NULL, -EINVAL); switch (*index) { case 0: *info = &impl_interfaces[*index]; break; default: return 0; } (*index)++; return 1; } const struct spa_handle_factory spa_support_cpu_factory = { SPA_VERSION_HANDLE_FACTORY, SPA_NAME_SUPPORT_CPU, NULL, impl_get_size, impl_init, impl_enum_interface_info, }; pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/support/dbus.c000066400000000000000000000350601511204443500255630ustar00rootroot00000000000000/* PipeWire */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include SPA_LOG_TOPIC_DEFINE_STATIC(log_topic, "spa.dbus"); #undef SPA_LOG_TOPIC_DEFAULT #define SPA_LOG_TOPIC_DEFAULT &log_topic struct impl { struct spa_handle handle; struct spa_dbus dbus; struct spa_log *log; struct spa_loop_utils *utils; struct spa_list connection_list; }; struct source_data { struct spa_list link; struct spa_source *source; struct connection *conn; }; #define connection_emit(c,m,v,...) spa_hook_list_call(&c->listener_list, struct spa_dbus_connection_events, m, v, ##__VA_ARGS__) #define connection_emit_destroy(c) connection_emit(c, destroy, 0) #define connection_emit_disconnected(c) connection_emit(c, disconnected, 0) struct connection { struct spa_list link; struct spa_dbus_connection this; struct impl *impl; enum spa_dbus_type type; DBusConnection *conn; struct spa_source *dispatch_event; struct spa_list source_list; struct spa_hook_list listener_list; }; static void source_data_free(void *data) { struct source_data *d = data; struct connection *conn = d->conn; struct impl *impl = conn->impl; spa_list_remove(&d->link); spa_loop_utils_destroy_source(impl->utils, d->source); free(d); } static void dispatch_cb(void *userdata) { struct connection *conn = userdata; struct impl *impl = conn->impl; spa_log_debug(impl->log, "impl:%p", impl); if (dbus_connection_dispatch(conn->conn) == DBUS_DISPATCH_COMPLETE) spa_loop_utils_enable_idle(impl->utils, conn->dispatch_event, false); } static void dispatch_status(DBusConnection *conn, DBusDispatchStatus status, void *userdata) { struct connection *c = userdata; struct impl *impl = c->impl; spa_log_debug(impl->log, "impl:%p %d", impl, status); spa_loop_utils_enable_idle(impl->utils, c->dispatch_event, status == DBUS_DISPATCH_COMPLETE ? false : true); } static inline uint32_t dbus_to_io(DBusWatch *watch) { uint32_t mask; unsigned int flags; /* no watch flags for disabled watches */ if (!dbus_watch_get_enabled(watch)) return 0; flags = dbus_watch_get_flags(watch); mask = SPA_IO_HUP | SPA_IO_ERR; if (flags & DBUS_WATCH_READABLE) mask |= SPA_IO_IN; if (flags & DBUS_WATCH_WRITABLE) mask |= SPA_IO_OUT; return mask; } static inline unsigned int io_to_dbus(uint32_t mask) { unsigned int flags = 0; if (mask & SPA_IO_IN) flags |= DBUS_WATCH_READABLE; if (mask & SPA_IO_OUT) flags |= DBUS_WATCH_WRITABLE; if (mask & SPA_IO_HUP) flags |= DBUS_WATCH_HANGUP; if (mask & SPA_IO_ERR) flags |= DBUS_WATCH_ERROR; return flags; } static void handle_io_event(void *userdata, int fd, uint32_t mask) { DBusWatch *watch = userdata; if (!dbus_watch_get_enabled(watch)) { fprintf(stderr, "Asked to handle disabled watch: %p %i", (void *) watch, fd); return; } dbus_watch_handle(watch, io_to_dbus(mask)); } static dbus_bool_t add_watch(DBusWatch *watch, void *userdata) { struct connection *conn = userdata; struct impl *impl = conn->impl; struct source_data *data; spa_log_debug(impl->log, "add watch %p %d", watch, dbus_watch_get_unix_fd(watch)); data = calloc(1, sizeof(struct source_data)); data->conn = conn; /* we dup because dbus tends to add the same fd multiple times and our epoll * implementation does not like that */ data->source = spa_loop_utils_add_io(impl->utils, dup(dbus_watch_get_unix_fd(watch)), dbus_to_io(watch), true, handle_io_event, watch); spa_list_append(&conn->source_list, &data->link); dbus_watch_set_data(watch, data, source_data_free); return TRUE; } static void remove_watch(DBusWatch *watch, void *userdata) { struct connection *conn = userdata; struct impl *impl = conn->impl; spa_log_debug(impl->log, "remove watch %p", watch); dbus_watch_set_data(watch, NULL, NULL); } static void toggle_watch(DBusWatch *watch, void *userdata) { struct connection *conn = userdata; struct impl *impl = conn->impl; struct source_data *data; spa_log_debug(impl->log, "toggle watch %p", watch); if ((data = dbus_watch_get_data(watch)) == NULL) return; spa_loop_utils_update_io(impl->utils, data->source, dbus_to_io(watch)); } static void handle_timer_event(void *userdata, uint64_t expirations) { DBusTimeout *timeout = userdata; uint64_t t; struct timespec ts; struct source_data *data; struct connection *conn; struct impl *impl; if ((data = dbus_timeout_get_data(timeout)) == NULL) return; conn = data->conn; impl = conn->impl; spa_log_debug(impl->log, "timeout %p conn:%p impl:%p", timeout, conn, impl); if (dbus_timeout_get_enabled(timeout)) { t = dbus_timeout_get_interval(timeout) * SPA_NSEC_PER_MSEC; ts.tv_sec = t / SPA_NSEC_PER_SEC; ts.tv_nsec = t % SPA_NSEC_PER_SEC; spa_loop_utils_update_timer(impl->utils, data->source, &ts, NULL, false); dbus_timeout_handle(timeout); } } static dbus_bool_t add_timeout(DBusTimeout *timeout, void *userdata) { struct connection *conn = userdata; struct impl *impl = conn->impl; struct timespec ts; struct source_data *data; uint64_t t; if (!dbus_timeout_get_enabled(timeout)) return FALSE; spa_log_debug(impl->log, "add timeout %p conn:%p impl:%p", timeout, conn, impl); data = calloc(1, sizeof(struct source_data)); data->conn = conn; data->source = spa_loop_utils_add_timer(impl->utils, handle_timer_event, timeout); spa_list_append(&conn->source_list, &data->link); dbus_timeout_set_data(timeout, data, source_data_free); t = dbus_timeout_get_interval(timeout) * SPA_NSEC_PER_MSEC; ts.tv_sec = t / SPA_NSEC_PER_SEC; ts.tv_nsec = t % SPA_NSEC_PER_SEC; spa_loop_utils_update_timer(impl->utils, data->source, &ts, NULL, false); return TRUE; } static void remove_timeout(DBusTimeout *timeout, void *userdata) { struct connection *conn = userdata; struct impl *impl = conn->impl; spa_log_debug(impl->log, "remove timeout %p conn:%p impl:%p", timeout, conn, impl); dbus_timeout_set_data(timeout, NULL, NULL); } static void toggle_timeout(DBusTimeout *timeout, void *userdata) { struct connection *conn = userdata; struct impl *impl = conn->impl; struct source_data *data; struct timespec ts, *tsp; if ((data = dbus_timeout_get_data(timeout)) == NULL) return; spa_log_debug(impl->log, "toggle timeout %p conn:%p impl:%p", timeout, conn, impl); if (dbus_timeout_get_enabled(timeout)) { uint64_t t = dbus_timeout_get_interval(timeout) * SPA_NSEC_PER_MSEC; ts.tv_sec = t / SPA_NSEC_PER_SEC; ts.tv_nsec = t % SPA_NSEC_PER_SEC; tsp = &ts; } else { tsp = NULL; } spa_loop_utils_update_timer(impl->utils, data->source, tsp, NULL, false); } static void wakeup_main(void *userdata) { struct connection *this = userdata; struct impl *impl = this->impl; spa_log_debug(impl->log, "wakeup main impl:%p", impl); spa_loop_utils_enable_idle(impl->utils, this->dispatch_event, true); } static void connection_close(struct connection *this); static DBusHandlerResult filter_message (DBusConnection *connection, DBusMessage *message, void *user_data) { struct connection *this = user_data; struct impl *impl = this->impl; if (dbus_message_is_signal(message, DBUS_INTERFACE_LOCAL, "Disconnected")) { spa_log_debug(impl->log, "dbus connection %p disconnected", this); connection_close(this); connection_emit_disconnected(this); } return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } static const char *type_to_string(enum spa_dbus_type type) { switch (type) { case SPA_DBUS_TYPE_SESSION: return "session"; case SPA_DBUS_TYPE_SYSTEM: return "system"; case SPA_DBUS_TYPE_STARTER: return "starter"; default: return "unknown"; } } static void * impl_connection_get(struct spa_dbus_connection *conn) { struct connection *this = SPA_CONTAINER_OF(conn, struct connection, this); struct impl *impl = this->impl; DBusError error; if (this->conn != NULL) return this->conn; dbus_error_init(&error); this->conn = dbus_bus_get_private((DBusBusType)this->type, &error); if (this->conn == NULL) goto error; dbus_connection_set_exit_on_disconnect(this->conn, false); if (!dbus_connection_add_filter(this->conn, filter_message, this, NULL)) goto error_filter; dbus_connection_set_dispatch_status_function(this->conn, dispatch_status, this, NULL); dbus_connection_set_watch_functions(this->conn, add_watch, remove_watch, toggle_watch, this, NULL); dbus_connection_set_timeout_functions(this->conn, add_timeout, remove_timeout, toggle_timeout, this, NULL); dbus_connection_set_wakeup_main_function(this->conn, wakeup_main, this, NULL); return this->conn; error: spa_log_error(impl->log, "Failed to connect to %s bus: %s", type_to_string(this->type), error.message); dbus_error_free(&error); errno = ECONNREFUSED; return NULL; error_filter: spa_log_error(impl->log, "Failed to create filter"); dbus_connection_close(this->conn); dbus_connection_unref(this->conn); this->conn = NULL; errno = ENOMEM; return NULL; } static void connection_close(struct connection *this) { if (this->conn) { dbus_connection_remove_filter(this->conn, filter_message, this); dbus_connection_close(this->conn); /* Someone may still hold a ref to the handle from get(), so the * unref below may not be the final one. For that case, reset * all callbacks we defined to be sure they are not called. */ dbus_connection_set_dispatch_status_function(this->conn, NULL, NULL, NULL); dbus_connection_set_watch_functions(this->conn, NULL, NULL, NULL, NULL, NULL); dbus_connection_set_timeout_functions(this->conn, NULL, NULL, NULL, NULL, NULL); dbus_connection_set_wakeup_main_function(this->conn, NULL, NULL, NULL); dbus_connection_unref(this->conn); } this->conn = NULL; } static void connection_free(struct connection *conn) { struct impl *impl = conn->impl; struct source_data *data; spa_list_remove(&conn->link); connection_close(conn); spa_list_consume(data, &conn->source_list, link) source_data_free(data); spa_loop_utils_destroy_source(impl->utils, conn->dispatch_event); spa_hook_list_clean(&conn->listener_list); free(conn); } static void impl_connection_destroy(struct spa_dbus_connection *conn) { struct connection *this = SPA_CONTAINER_OF(conn, struct connection, this); struct impl *impl = this->impl; connection_emit_destroy(this); spa_log_debug(impl->log, "destroy conn %p", this); connection_free(this); } static void impl_connection_add_listener(struct spa_dbus_connection *conn, struct spa_hook *listener, const struct spa_dbus_connection_events *events, void *data) { struct connection *this = SPA_CONTAINER_OF(conn, struct connection, this); spa_hook_list_append(&this->listener_list, listener, events, data); } static const struct spa_dbus_connection impl_connection = { SPA_VERSION_DBUS_CONNECTION, impl_connection_get, impl_connection_destroy, impl_connection_add_listener, }; static struct spa_dbus_connection * impl_get_connection(void *object, enum spa_dbus_type type) { struct impl *impl = object; struct connection *conn; int res; conn = calloc(1, sizeof(struct connection)); conn->this = impl_connection; conn->impl = impl; conn->type = type; conn->dispatch_event = spa_loop_utils_add_idle(impl->utils, false, dispatch_cb, conn); if (conn->dispatch_event == NULL) goto no_event; spa_list_init(&conn->source_list); spa_hook_list_init(&conn->listener_list); spa_list_append(&impl->connection_list, &conn->link); spa_log_debug(impl->log, "new conn %p", conn); return &conn->this; no_event: res = -errno; spa_log_error(impl->log, "Failed to create idle event: %m"); free(conn); errno = -res; return NULL; } static const struct spa_dbus_methods impl_dbus = { SPA_VERSION_DBUS_METHODS, .get_connection = impl_get_connection, }; static int impl_get_interface(struct spa_handle *handle, const char *type, void **interface) { struct impl *this; spa_return_val_if_fail(handle != NULL, -EINVAL); spa_return_val_if_fail(interface != NULL, -EINVAL); this = (struct impl *) handle; if (spa_streq(type, SPA_TYPE_INTERFACE_DBus)) *interface = &this->dbus; else return -ENOENT; return 0; } static int impl_clear(struct spa_handle *handle) { struct impl *impl = (struct impl *) handle; struct connection *conn; spa_return_val_if_fail(handle != NULL, -EINVAL); spa_list_consume(conn, &impl->connection_list, link) connection_free(conn); return 0; } static size_t impl_get_size(const struct spa_handle_factory *factory, const struct spa_dict *params) { return sizeof(struct impl); } static int impl_init(const struct spa_handle_factory *factory, struct spa_handle *handle, const struct spa_dict *info, const struct spa_support *support, uint32_t n_support) { struct impl *this; spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(handle != NULL, -EINVAL); handle->get_interface = impl_get_interface; handle->clear = impl_clear; this = (struct impl *) handle; spa_list_init(&this->connection_list); this->dbus.iface = SPA_INTERFACE_INIT( SPA_TYPE_INTERFACE_DBus, SPA_VERSION_DBUS, &impl_dbus, this); this->log = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log); spa_log_topic_init(this->log, &log_topic); this->utils = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_LoopUtils); if (this->utils == NULL) { spa_log_error(this->log, "a LoopUtils is needed"); return -EINVAL; } spa_log_debug(this->log, "%p: initialized", this); return 0; } static const struct spa_interface_info impl_interfaces[] = { {SPA_TYPE_INTERFACE_DBus,}, }; static int impl_enum_interface_info(const struct spa_handle_factory *factory, const struct spa_interface_info **info, uint32_t *index) { spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(info != NULL, -EINVAL); spa_return_val_if_fail(index != NULL, -EINVAL); switch (*index) { case 0: *info = &impl_interfaces[*index]; break; default: return 0; } (*index)++; return 1; } static const struct spa_handle_factory dbus_factory = { SPA_VERSION_HANDLE_FACTORY, SPA_NAME_SUPPORT_DBUS, NULL, impl_get_size, impl_init, impl_enum_interface_info, }; SPA_LOG_TOPIC_ENUM_DEFINE_REGISTERED; SPA_EXPORT int spa_handle_factory_enum(const struct spa_handle_factory **factory, uint32_t *index) { spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(index != NULL, -EINVAL); switch (*index) { case 0: *factory = &dbus_factory; break; default: return 0; } (*index)++; return 1; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/support/evl-plugin.c000066400000000000000000000012351511204443500267050ustar00rootroot00000000000000/* Spa Support plugin */ /* SPDX-FileCopyrightText: Copyright © 2019 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include #include extern const struct spa_handle_factory spa_support_evl_system_factory; SPA_LOG_TOPIC_ENUM_DEFINE_REGISTERED; SPA_EXPORT int spa_handle_factory_enum(const struct spa_handle_factory **factory, uint32_t *index) { spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(index != NULL, -EINVAL); switch (*index) { case 0: *factory = &spa_support_evl_system_factory; break; default: return 0; } (*index)++; return 1; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/support/evl-system.c000066400000000000000000000246771511204443500267520ustar00rootroot00000000000000/* Spa */ /* SPDX-FileCopyrightText: Copyright © 2019 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #undef SPA_LOG_TOPIC_DEFAULT #define SPA_LOG_TOPIC_DEFAULT &log_topic SPA_LOG_TOPIC_DEFINE_STATIC(log_topic, "spa.evl-system"); #define MAX_POLL 512 struct poll_entry { int pfd; int fd; uint32_t events; void *data; unsigned attached:1; }; struct impl { struct spa_handle handle; struct spa_system system; struct spa_log *log; struct poll_entry entries[MAX_POLL]; uint32_t n_entries; uint32_t n_xbuf; int attached; pthread_t thread; int pid; }; static ssize_t impl_read(void *object, int fd, void *buf, size_t count) { return oob_read(fd, buf, count); } static ssize_t impl_write(void *object, int fd, const void *buf, size_t count) { return oob_write(fd, buf, count); } static int impl_ioctl(void *object, int fd, unsigned long request, ...) { int res; va_list ap; long arg; va_start(ap, request); arg = va_arg(ap, long); res = oob_ioctl(fd, request, arg); va_end(ap); return res; } static int impl_close(void *object, int fd) { return close(fd); } static inline int clock_id_to_evl(int clockid) { switch(clockid) { case CLOCK_MONOTONIC: return EVL_CLOCK_MONOTONIC; case CLOCK_REALTIME: return EVL_CLOCK_REALTIME; default: return -clockid; } } /* clock */ static int impl_clock_gettime(void *object, int clockid, struct timespec *value) { return evl_read_clock(clock_id_to_evl(clockid), value); } static int impl_clock_getres(void *object, int clockid, struct timespec *res) { return evl_get_clock_resolution(clock_id_to_evl(clockid), res); } /* poll */ static int impl_pollfd_create(void *object, int flags) { int retval; retval = evl_new_poll(); return retval; } static inline struct poll_entry *find_free(struct impl *impl) { uint32_t i; for (i = 0; i < impl->n_entries; i++) { struct poll_entry *e = &impl->entries[i]; if (e->fd == -1) return e; } if (impl->n_entries == MAX_POLL) { errno = ENOSPC; return NULL; } return &impl->entries[impl->n_entries++]; } static inline struct poll_entry *find_entry(struct impl *impl, int pfd, int fd) { uint32_t i; for (i = 0; i < impl->n_entries; i++) { struct poll_entry *e = &impl->entries[i]; if (e->pfd == pfd && e->fd == fd) return e; } return NULL; } static int attach_entry(struct impl *impl, struct poll_entry *e) { if (!e->attached && e->fd != -1) { int res; res = evl_add_pollfd(e->pfd, e->fd, e->events, evl_nil); if (res < 0) return res; e->attached = true; } return 0; } static int impl_pollfd_add(void *object, int pfd, int fd, uint32_t events, void *data) { struct impl *impl = object; struct poll_entry *e; int res = 0; if ((e = find_free(impl)) == NULL) return -errno; e->pfd = pfd; e->fd = fd; e->events = events; e->data = data; if (impl->attached != 0) attach_entry(impl, e); return res; } static int impl_pollfd_mod(void *object, int pfd, int fd, uint32_t events, void *data) { struct impl *impl = object; struct poll_entry *e; e = find_entry(impl, pfd, fd); if (e == NULL) return -ENOENT; e->events = events; e->data = data; return evl_mod_pollfd(pfd, fd, e->events, evl_nil); } static int impl_pollfd_del(void *object, int pfd, int fd) { struct impl *impl = object; struct poll_entry *e; e = find_entry(impl, pfd, fd); if (e == NULL) return -ENOENT; e->pfd = -1; e->fd = -1; e->attached = false; return evl_del_pollfd(pfd, fd); } static int impl_pollfd_wait(void *object, int pfd, struct spa_poll_event *ev, int n_ev, int timeout) { struct impl *impl = object; struct evl_poll_event pollset[n_ev]; struct timespec tv; int i, j, res; if (impl->attached == 0) { res = evl_attach_self("evl-thread-%d-%p", impl->pid, impl); if (res < 0) return res; impl->attached = res; impl->thread = pthread_self(); for (i = 0; i < (int)impl->n_entries; i++) { struct poll_entry *e = &impl->entries[i]; attach_entry(impl, e); } } if (timeout == -1) { tv.tv_sec = 0; tv.tv_nsec = 0; } else { tv.tv_sec = timeout / SPA_MSEC_PER_SEC; tv.tv_nsec = (timeout % SPA_MSEC_PER_SEC) * SPA_NSEC_PER_MSEC; } res = evl_timedpoll(pfd, pollset, n_ev, &tv); if (SPA_UNLIKELY(res < 0)) return res; for (i = 0, j = 0; i < res; i++) { struct poll_entry *e; e = find_entry(impl, pfd, pollset[i].fd); if (e == NULL) continue; ev[j].events = pollset[i].events; ev[j].data = e->data; j++; } return j; } /* timers */ static int impl_timerfd_create(void *object, int clockid, int flags) { int cid; switch (clockid) { case CLOCK_MONOTONIC: cid = EVL_CLOCK_MONOTONIC; break; default: return -ENOTSUP; } return evl_new_timer(cid); } static int impl_timerfd_settime(void *object, int fd, int flags, const struct itimerspec *new_value, struct itimerspec *old_value) { struct itimerspec val = *new_value; if (!(flags & SPA_FD_TIMER_ABSTIME)) { struct timespec now; evl_read_clock(EVL_CLOCK_MONOTONIC, &now); val.it_value.tv_sec += now.tv_sec; val.it_value.tv_nsec += now.tv_nsec; if (val.it_value.tv_nsec >= 1000000000) { val.it_value.tv_sec++; val.it_value.tv_nsec -= 1000000000; } } return evl_set_timer(fd, &val, old_value); } static int impl_timerfd_gettime(void *object, int fd, struct itimerspec *curr_value) { return evl_get_timer(fd, curr_value); } static int impl_timerfd_read(void *object, int fd, uint64_t *expirations) { if (oob_read(fd, expirations, sizeof(uint64_t)) != sizeof(uint64_t)) return -errno; return 0; } /* events */ static int impl_eventfd_create(void *object, int flags) { struct impl *impl = object; int res, fl; struct evl_flags flg; fl = EVL_CLONE_PUBLIC; if (flags & SPA_FD_NONBLOCK) fl |= EVL_CLONE_NONBLOCK; res = evl_create_flags(&flg, EVL_CLOCK_MONOTONIC, 0, fl, "flags-%d-%p-%d", impl->pid, impl, impl->n_xbuf); if (res < 0) return res; impl->n_xbuf++; return res; } static int impl_eventfd_write(void *object, int fd, uint64_t count) { int res; int flags = count; struct impl *impl = object; pthread_t tid = pthread_self(); if (impl->thread != tid) res = write(fd, &flags, sizeof(flags)); else res = oob_write(fd, &flags, sizeof(flags)); if (res != sizeof(flags)) res = -errno; return res; } static int impl_eventfd_read(void *object, int fd, uint64_t *count) { int res; int flags; struct impl *impl = object; pthread_t tid = pthread_self(); if (impl->thread != tid) res = read(fd, &flags, sizeof(flags)); else res = oob_read(fd, &flags, sizeof(flags)); *count = flags; if (res != sizeof(flags)) return -errno; return 0; } /* signals */ static int impl_signalfd_create(void *object, int signal, int flags) { sigset_t mask; int res, fl = 0; if (flags & SPA_FD_CLOEXEC) fl |= SFD_CLOEXEC; if (flags & SPA_FD_NONBLOCK) fl |= SFD_NONBLOCK; sigemptyset(&mask); sigaddset(&mask, signal); res = signalfd(-1, &mask, fl); sigprocmask(SIG_BLOCK, &mask, NULL); return res; } static int impl_signalfd_read(void *object, int fd, int *signal) { struct signalfd_siginfo signal_info; int len; len = read(fd, &signal_info, sizeof signal_info); if (!(len == -1 && errno == EAGAIN) && len != sizeof signal_info) return -errno; *signal = signal_info.ssi_signo; return 0; } static const struct spa_system_methods impl_system = { SPA_VERSION_SYSTEM_METHODS, .read = impl_read, .write = impl_write, .ioctl = impl_ioctl, .close = impl_close, .clock_gettime = impl_clock_gettime, .clock_getres = impl_clock_getres, .pollfd_create = impl_pollfd_create, .pollfd_add = impl_pollfd_add, .pollfd_mod = impl_pollfd_mod, .pollfd_del = impl_pollfd_del, .pollfd_wait = impl_pollfd_wait, .timerfd_create = impl_timerfd_create, .timerfd_settime = impl_timerfd_settime, .timerfd_gettime = impl_timerfd_gettime, .timerfd_read = impl_timerfd_read, .eventfd_create = impl_eventfd_create, .eventfd_write = impl_eventfd_write, .eventfd_read = impl_eventfd_read, .signalfd_create = impl_signalfd_create, .signalfd_read = impl_signalfd_read, }; static int impl_get_interface(struct spa_handle *handle, const char *type, void **interface) { struct impl *impl; spa_return_val_if_fail(handle != NULL, -EINVAL); spa_return_val_if_fail(interface != NULL, -EINVAL); impl = (struct impl *) handle; if (spa_streq(type, SPA_TYPE_INTERFACE_System)) *interface = &impl->system; else return -ENOENT; return 0; } static int impl_clear(struct spa_handle *handle) { spa_return_val_if_fail(handle != NULL, -EINVAL); return 0; } static size_t impl_get_size(const struct spa_handle_factory *factory, const struct spa_dict *params) { return sizeof(struct impl); } static int impl_init(const struct spa_handle_factory *factory, struct spa_handle *handle, const struct spa_dict *info, const struct spa_support *support, uint32_t n_support) { struct impl *impl; int res; spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(handle != NULL, -EINVAL); handle->get_interface = impl_get_interface; handle->clear = impl_clear; impl = (struct impl *) handle; impl->system.iface = SPA_INTERFACE_INIT( SPA_TYPE_INTERFACE_System, SPA_VERSION_SYSTEM, &impl_system, impl); impl->log = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log); impl->pid = getpid(); if ((res = evl_init()) < 0) { spa_log_error(impl->log, "%p: init failed: %s", impl, spa_strerror(res)); return res; } spa_log_info(impl->log, "%p: initialized", impl); return 0; } static const struct spa_interface_info impl_interfaces[] = { {SPA_TYPE_INTERFACE_System,}, }; static int impl_enum_interface_info(const struct spa_handle_factory *factory, const struct spa_interface_info **info, uint32_t *index) { spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(info != NULL, -EINVAL); spa_return_val_if_fail(index != NULL, -EINVAL); if (*index >= SPA_N_ELEMENTS(impl_interfaces)) return 0; *info = &impl_interfaces[(*index)++]; return 1; } const struct spa_handle_factory spa_support_evl_system_factory = { SPA_VERSION_HANDLE_FACTORY, SPA_NAME_SUPPORT_SYSTEM, NULL, impl_get_size, impl_init, impl_enum_interface_info }; pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/support/journal.c000066400000000000000000000202051511204443500262730ustar00rootroot00000000000000/* Spa */ /* SPDX-FileCopyrightText: Copyright © 2020 Sergey Bugaev */ /* SPDX-License-Identifier: MIT */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #undef SPA_LOG_TOPIC_DEFAULT #define SPA_LOG_TOPIC_DEFAULT &log_topic SPA_LOG_TOPIC_DEFINE_STATIC(log_topic, "spa.journal"); #define DEFAULT_LOG_LEVEL SPA_LOG_LEVEL_INFO struct impl { struct spa_handle handle; struct spa_log log; /* if non-null, we'll additionally forward all logging to there */ struct spa_log *chain_log; }; static SPA_PRINTF_FUNC(7,0) void impl_log_logtv(void *object, enum spa_log_level level, const struct spa_log_topic *topic, const char *file, int line, const char *func, const char *fmt, va_list args) { static const char * const levels[] = { "-", "E", "W", "I", "D", "T", "*T*" }; struct impl *impl = object; char line_buffer[32]; char file_buffer[strlen("CODE_FILE=") + strlen(file) + 1]; char message_buffer[LINE_MAX]; int priority; size_t sz = 0; if (impl->chain_log != NULL) { va_list args_copy; va_copy(args_copy, args); impl->chain_log->level = impl->log.level; spa_log_logtv(impl->chain_log, level, topic, file, line, func, fmt, args_copy); va_end(args_copy); } /* convert SPA log level to syslog priority */ switch (level) { case SPA_LOG_LEVEL_ERROR: priority = LOG_ERR; break; case SPA_LOG_LEVEL_WARN: priority = LOG_WARNING; break; case SPA_LOG_LEVEL_INFO: priority = LOG_INFO; break; case SPA_LOG_LEVEL_DEBUG: case SPA_LOG_LEVEL_TRACE: default: priority = LOG_DEBUG; break; } if (spa_log_level_topic_enabled(&impl->log, topic, SPA_LOG_LEVEL_DEBUG)) { const char *lev = levels[SPA_CLAMP(level, 0u, SPA_N_ELEMENTS(levels) - 1u)]; const char *tp = topic ? topic->topic : ""; if (file && func) { const char *f = strrchr(file, '/'); f = f ? f+1 : file; sz = spa_scnprintf(message_buffer, sizeof(message_buffer), "%s %s%s[%s:%d:%s]: ", lev, tp, topic ? " " : "", f, line, func); } else { sz = spa_scnprintf(message_buffer, sizeof(message_buffer), "%s %s%s", lev, tp, topic ? ": " : ""); } } else if (topic) { sz = spa_scnprintf(message_buffer, sizeof(message_buffer), "%s: ", topic->topic); } /* we'll be using the low-level journal API, which expects us to provide * the location explicitly. line and file are to be passed as preformatted * entries, whereas the function name is passed as-is, and converted into * a field inside sd_journal_send_with_location(). */ snprintf(line_buffer, sizeof(line_buffer), "CODE_LINE=%d", line); snprintf(file_buffer, sizeof(file_buffer), "CODE_FILE=%s", file); vsnprintf(message_buffer + sz, sizeof(message_buffer) - sz, fmt, args); sd_journal_send_with_location(file_buffer, line_buffer, func, "MESSAGE=%s", message_buffer, "PRIORITY=%i", priority, #ifdef HAVE_GETTID "TID=%jd", (intmax_t) gettid(), #endif NULL); } static SPA_PRINTF_FUNC(6,7) void impl_log_log(void *object, enum spa_log_level level, const char *file, int line, const char *func, const char *fmt, ...) { va_list args; va_start(args, fmt); impl_log_logtv(object, level, NULL, file, line, func, fmt, args); va_end(args); } static SPA_PRINTF_FUNC(6,0) void impl_log_logv(void *object, enum spa_log_level level, const char *file, int line, const char *func, const char *fmt, va_list args) { impl_log_logtv(object, level, NULL, file, line, func, fmt, args); } static SPA_PRINTF_FUNC(7,8) void impl_log_logt(void *object, enum spa_log_level level, const struct spa_log_topic *topic, const char *file, int line, const char *func, const char *fmt, ...) { va_list args; va_start(args, fmt); impl_log_logtv(object, level, topic, file, line, func, fmt, args); va_end(args); } static const struct spa_log_methods impl_log = { SPA_VERSION_LOG_METHODS, .log = impl_log_log, .logv = impl_log_logv, .logt = impl_log_logt, .logtv = impl_log_logtv, }; static int impl_get_interface(struct spa_handle *handle, const char *type, void **interface) { struct impl *this; spa_return_val_if_fail(handle != NULL, -EINVAL); spa_return_val_if_fail(interface != NULL, -EINVAL); this = (struct impl *) handle; if (spa_streq(type, SPA_TYPE_INTERFACE_Log)) *interface = &this->log; else return -ENOENT; return 0; } static int impl_clear(struct spa_handle *handle) { struct impl SPA_UNUSED *this; spa_return_val_if_fail(handle != NULL, -EINVAL); this = (struct impl *) handle; return 0; } static size_t impl_get_size(const struct spa_handle_factory *factory, const struct spa_dict *params) { return sizeof(struct impl); } /** Determine if our stderr goes straight to the journal */ static int stderr_is_connected_to_journal(void) { const char *journal_stream; unsigned long long journal_device, journal_inode; struct stat stderr_stat; /* when a service's stderr is connected to the journal, systemd sets * JOURNAL_STREAM in the environment of that service to device:inode * of its stderr. if the variable is not set, clearly our stderr is * not connected to the journal */ journal_stream = getenv("JOURNAL_STREAM"); if (journal_stream == NULL) return 0; /* if it *is* set, that doesn't immediately mean that *our* stderr * is (still) connected to the journal. to know for sure, we have to * compare our actual stderr to the stream systemd has created for * the service we're a part of */ if (sscanf(journal_stream, "%llu:%llu", &journal_device, &journal_inode) != 2) return 0; if (fstat(STDERR_FILENO, &stderr_stat) < 0) return 0; return stderr_stat.st_dev == journal_device && stderr_stat.st_ino == journal_inode; } static int impl_init(const struct spa_handle_factory *factory, struct spa_handle *handle, const struct spa_dict *info, const struct spa_support *support, uint32_t n_support) { struct impl *impl; const char *str; spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(handle != NULL, -EINVAL); handle->get_interface = impl_get_interface; handle->clear = impl_clear; impl = (struct impl *) handle; impl->log.iface = SPA_INTERFACE_INIT( SPA_TYPE_INTERFACE_Log, SPA_VERSION_LOG, &impl_log, impl); impl->log.level = DEFAULT_LOG_LEVEL; if (info) { if ((str = spa_dict_lookup(info, SPA_KEY_LOG_LEVEL)) != NULL) impl->log.level = atoi(str); } /* if our stderr goes to the journal, there's no point in logging both * via the native journal API and by printing to stderr, that would just * result in message duplication */ if (stderr_is_connected_to_journal()) impl->chain_log = NULL; else impl->chain_log = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log); spa_log_debug(&impl->log, "%p: initialized", impl); return 0; } static const struct spa_interface_info impl_interfaces[] = { {SPA_TYPE_INTERFACE_Log,}, }; static int impl_enum_interface_info(const struct spa_handle_factory *factory, const struct spa_interface_info **info, uint32_t *index) { spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(info != NULL, -EINVAL); spa_return_val_if_fail(index != NULL, -EINVAL); switch (*index) { case 0: *info = &impl_interfaces[*index]; break; default: return 0; } (*index)++; return 1; } static const struct spa_handle_factory journal_factory = { SPA_VERSION_HANDLE_FACTORY, .name = SPA_NAME_SUPPORT_LOG, .info = NULL, .get_size = impl_get_size, .init = impl_init, .enum_interface_info = impl_enum_interface_info, }; SPA_LOG_TOPIC_ENUM_DEFINE_REGISTERED; SPA_EXPORT int spa_handle_factory_enum(const struct spa_handle_factory **factory, uint32_t *index) { spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(index != NULL, -EINVAL); switch (*index) { case 0: *factory = &journal_factory; break; default: return 0; } (*index)++; return 1; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/support/logger.c000066400000000000000000000256001511204443500261040ustar00rootroot00000000000000/* Spa */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if defined(__FreeBSD__) || defined(__MidnightBSD__) #define CLOCK_MONOTONIC_RAW CLOCK_MONOTONIC #elif defined(_MSC_VER) static inline void setlinebuf(FILE* stream) { setvbuf(stream, NULL, _IOLBF, 0); } #endif #undef SPA_LOG_TOPIC_DEFAULT #define SPA_LOG_TOPIC_DEFAULT &log_topic SPA_LOG_TOPIC_DEFINE_STATIC(log_topic, "spa.logger"); #define DEFAULT_LOG_LEVEL SPA_LOG_LEVEL_INFO #define TRACE_BUFFER (16*1024) struct impl { struct spa_handle handle; struct spa_log log; FILE *file; bool close_file; struct spa_system *system; struct spa_source source; struct spa_ringbuffer trace_rb; uint8_t trace_data[TRACE_BUFFER]; clockid_t clock_id; unsigned int have_source:1; unsigned int colors:1; unsigned int timestamp:1; unsigned int local_timestamp:1; unsigned int line:1; }; static SPA_PRINTF_FUNC(7,0) void impl_log_logtv(void *object, enum spa_log_level level, const struct spa_log_topic *topic, const char *file, int line, const char *func, const char *fmt, va_list args) { #define RESERVED_LENGTH 24 struct impl *impl = object; char timestamp[18] = {0}; char topicstr[32] = {0}; char filename[64] = {0}; char location[1000 + RESERVED_LENGTH], *p, *s; static const char * const levels[] = { "-", "E", "W", "I", "D", "T", "*T*" }; const char *prefix = "", *suffix = ""; int size, len; bool do_trace; if ((do_trace = (level == SPA_LOG_LEVEL_TRACE && impl->have_source))) level++; if (impl->colors) { if (level <= SPA_LOG_LEVEL_ERROR) prefix = SPA_ANSI_BOLD_RED; else if (level <= SPA_LOG_LEVEL_WARN) prefix = SPA_ANSI_BOLD_YELLOW; else if (level <= SPA_LOG_LEVEL_INFO) prefix = SPA_ANSI_BOLD_GREEN; if (prefix[0]) suffix = SPA_ANSI_RESET; } p = location; len = sizeof(location) - RESERVED_LENGTH; if (impl->local_timestamp) { char buf[64]; struct timespec now; struct tm now_tm; clock_gettime(impl->clock_id, &now); localtime_r(&now.tv_sec, &now_tm); strftime(buf, sizeof(buf), "%H:%M:%S", &now_tm); spa_scnprintf(timestamp, sizeof(timestamp), "[%s.%06d]", buf, (int)(now.tv_nsec / SPA_NSEC_PER_USEC)); } else if (impl->timestamp) { struct timespec now; clock_gettime(impl->clock_id, &now); spa_scnprintf(timestamp, sizeof(timestamp), "[%05jd.%06jd]", (intmax_t) (now.tv_sec & 0x1FFFFFFF) % 100000, (intmax_t) now.tv_nsec / 1000); } if (topic && topic->topic) spa_scnprintf(topicstr, sizeof(topicstr), " %-12s | ", topic->topic); if (impl->line && line != 0) { s = strrchr(file, '/'); spa_scnprintf(filename, sizeof(filename), "[%16.16s:%5i %s()]", s ? s + 1 : file, line, func); } size = spa_scnprintf(p, len, "%s[%s]%s%s%s ", prefix, levels[level], timestamp, topicstr, filename); /* * it is assumed that at this point `size` <= `len`, * which is reasonable as long as file names and function names * don't become very long */ size += spa_vscnprintf(p + size, len - size, fmt, args); /* * `RESERVED_LENGTH` bytes are reserved for printing the suffix * (at the moment it's "... (truncated)\x1B[0m\n" at its longest - 21 bytes), * its length must be less than `RESERVED_LENGTH` (including the null byte), * otherwise a stack buffer overrun could ensue */ /* if the message could not fit entirely... */ if (size >= len - 1) { size = len - 1; /* index of the null byte */ len = sizeof(location); size += spa_scnprintf(p + size, len - size, "... (truncated)"); } else { len = sizeof(location); } size += spa_scnprintf(p + size, len - size, "%s\n", suffix); if (SPA_UNLIKELY(do_trace)) { uint32_t index; spa_ringbuffer_get_write_index(&impl->trace_rb, &index); spa_ringbuffer_write_data(&impl->trace_rb, impl->trace_data, TRACE_BUFFER, index & (TRACE_BUFFER - 1), location, size); spa_ringbuffer_write_update(&impl->trace_rb, index + size); if (spa_system_eventfd_write(impl->system, impl->source.fd, 1) < 0) fprintf(impl->file, "error signaling eventfd: %s\n", strerror(errno)); } else fputs(location, impl->file); #undef RESERVED_LENGTH } static SPA_PRINTF_FUNC(6,0) void impl_log_logv(void *object, enum spa_log_level level, const char *file, int line, const char *func, const char *fmt, va_list args) { impl_log_logtv(object, level, NULL, file, line, func, fmt, args); } static SPA_PRINTF_FUNC(7,8) void impl_log_logt(void *object, enum spa_log_level level, const struct spa_log_topic *topic, const char *file, int line, const char *func, const char *fmt, ...) { va_list args; va_start(args, fmt); impl_log_logtv(object, level, topic, file, line, func, fmt, args); va_end(args); } static SPA_PRINTF_FUNC(6,7) void impl_log_log(void *object, enum spa_log_level level, const char *file, int line, const char *func, const char *fmt, ...) { va_list args; va_start(args, fmt); impl_log_logtv(object, level, NULL, file, line, func, fmt, args); va_end(args); } static void on_trace_event(struct spa_source *source) { struct impl *impl = source->data; int32_t avail; uint32_t index; uint64_t count; if (spa_system_eventfd_read(impl->system, source->fd, &count) < 0) fprintf(impl->file, "failed to read event fd: %s", strerror(errno)); while ((avail = spa_ringbuffer_get_read_index(&impl->trace_rb, &index)) > 0) { int32_t offset, first; if (avail > TRACE_BUFFER) { index += avail - TRACE_BUFFER; avail = TRACE_BUFFER; } offset = index & (TRACE_BUFFER - 1); first = SPA_MIN(avail, TRACE_BUFFER - offset); fwrite(impl->trace_data + offset, first, 1, impl->file); if (SPA_UNLIKELY(avail > first)) { fwrite(impl->trace_data, avail - first, 1, impl->file); } spa_ringbuffer_read_update(&impl->trace_rb, index + avail); } } static const struct spa_log_methods impl_log = { SPA_VERSION_LOG_METHODS, .log = impl_log_log, .logv = impl_log_logv, .logt = impl_log_logt, .logtv = impl_log_logtv, }; static int impl_get_interface(struct spa_handle *handle, const char *type, void **interface) { struct impl *this; spa_return_val_if_fail(handle != NULL, -EINVAL); spa_return_val_if_fail(interface != NULL, -EINVAL); this = (struct impl *) handle; if (spa_streq(type, SPA_TYPE_INTERFACE_Log)) *interface = &this->log; else return -ENOENT; return 0; } static int impl_clear(struct spa_handle *handle) { struct impl *this; spa_return_val_if_fail(handle != NULL, -EINVAL); this = (struct impl *) handle; if (this->close_file && this->file != NULL) fclose(this->file); if (this->have_source) { spa_loop_remove_source(this->source.loop, &this->source); spa_system_close(this->system, this->source.fd); this->have_source = false; } return 0; } static size_t impl_get_size(const struct spa_handle_factory *factory, const struct spa_dict *params) { return sizeof(struct impl); } static int impl_init(const struct spa_handle_factory *factory, struct spa_handle *handle, const struct spa_dict *info, const struct spa_support *support, uint32_t n_support) { struct impl *this; struct spa_loop *loop = NULL; const char *str, *dest = ""; bool linebuf = false; bool force_colors = false; spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(handle != NULL, -EINVAL); handle->get_interface = impl_get_interface; handle->clear = impl_clear; this = (struct impl *) handle; this->log.iface = SPA_INTERFACE_INIT( SPA_TYPE_INTERFACE_Log, SPA_VERSION_LOG, &impl_log, this); this->log.level = DEFAULT_LOG_LEVEL; loop = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Loop); this->system = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_System); if (loop != NULL && this->system != NULL) { this->source.func = on_trace_event; this->source.data = this; this->source.fd = spa_system_eventfd_create(this->system, SPA_FD_CLOEXEC | SPA_FD_NONBLOCK); this->source.mask = SPA_IO_IN; this->source.rmask = 0; if (this->source.fd < 0) { fprintf(stderr, "Warning: failed to create eventfd: %m"); } else { spa_loop_add_source(loop, &this->source); this->have_source = true; } } if (info) { str = spa_dict_lookup(info, SPA_KEY_LOG_TIMESTAMP); if (spa_atob(str) || spa_streq(str, "local")) { this->clock_id = CLOCK_REALTIME; this->local_timestamp = true; } else if (spa_streq(str, "monotonic")) { this->clock_id = CLOCK_MONOTONIC; this->timestamp = true; } else if (spa_streq(str, "monotonic-raw")) { this->clock_id = CLOCK_MONOTONIC_RAW; this->timestamp = true; } else if (spa_streq(str, "realtime")) { this->clock_id = CLOCK_REALTIME; this->timestamp = true; } if ((str = spa_dict_lookup(info, SPA_KEY_LOG_LINE)) != NULL) this->line = spa_atob(str); if ((str = spa_dict_lookup(info, SPA_KEY_LOG_COLORS)) != NULL) { if (spa_streq(str, "force")) { this->colors = true; force_colors = true; } else { this->colors = spa_atob(str); } } if ((str = spa_dict_lookup(info, SPA_KEY_LOG_LEVEL)) != NULL) this->log.level = atoi(str); if ((str = spa_dict_lookup(info, SPA_KEY_LOG_FILE)) != NULL) { dest = str; if (spa_streq(str, "stderr")) this->file = stderr; else if (spa_streq(str, "stdout")) this->file = stdout; else { this->file = fopen(str, "we"); if (this->file == NULL) fprintf(stderr, "Warning: failed to open file %s: (%m)", str); else this->close_file = true; } } } if (this->file == NULL) { this->file = stderr; dest = "stderr"; } else { linebuf = true; } if (linebuf) setlinebuf(this->file); if (this->colors && !force_colors && !isatty(fileno(this->file)) ) { this->colors = false; } spa_ringbuffer_init(&this->trace_rb); spa_log_debug(&this->log, "%p: initialized to %s linebuf:%u", this, dest, linebuf); return 0; } static const struct spa_interface_info impl_interfaces[] = { {SPA_TYPE_INTERFACE_Log,}, }; static int impl_enum_interface_info(const struct spa_handle_factory *factory, const struct spa_interface_info **info, uint32_t *index) { spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(info != NULL, -EINVAL); spa_return_val_if_fail(index != NULL, -EINVAL); switch (*index) { case 0: *info = &impl_interfaces[*index]; break; default: return 0; } (*index)++; return 1; } const struct spa_handle_factory spa_support_logger_factory = { SPA_VERSION_HANDLE_FACTORY, .name = SPA_NAME_SUPPORT_LOG, .info = NULL, .get_size = impl_get_size, .init = impl_init, .enum_interface_info = impl_enum_interface_info, }; pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/support/loop.c000066400000000000000000001115051511204443500255760ustar00rootroot00000000000000/* Spa */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include SPA_LOG_TOPIC_DEFINE_STATIC(log_topic, "spa.loop"); #undef SPA_LOG_TOPIC_DEFAULT #define SPA_LOG_TOPIC_DEFAULT &log_topic #define MAX_ALIGN 8 #define ITEM_ALIGN 8 #define DATAS_SIZE (4096*8) #define MAX_EP 32 /* the number of concurrent queues for invoke. This is also the number * of threads that can concurrently invoke. When there are more, the * retry timeout will be used to retry. */ #define QUEUES_MAX 128 #define DEFAULT_RETRY (1 * SPA_USEC_PER_SEC) /** \cond */ struct invoke_item { size_t item_size; spa_invoke_func_t func; uint32_t seq; uint32_t count; void *data; size_t size; bool block; void *user_data; int res; }; static int loop_signal_event(void *object, struct spa_source *source); struct queue; #define IDX_INVALID ((uint16_t)0xffff) union tag { struct { uint16_t idx; uint16_t count; } t; uint32_t v; }; struct impl { struct spa_handle handle; struct spa_loop loop; struct spa_loop_control control; struct spa_loop_utils utils; struct spa_log *log; struct spa_system *system; struct spa_list source_list; struct spa_list free_list; struct spa_hook_list hooks_list; struct spa_ratelimit rate_limit; int retry_timeout; bool prio_inherit; union tag head; uint32_t n_queues; struct queue *queues[QUEUES_MAX]; pthread_mutex_t lock; pthread_cond_t cond; pthread_cond_t accept_cond; int n_waiting; int n_waiting_for_accept; int poll_fd; pthread_t thread; int enter_count; int recurse; struct spa_source *wakeup; uint32_t count; uint32_t flush_count; uint32_t remove_count; }; struct queue { struct impl *impl; uint16_t idx; uint16_t next; int ack_fd; bool close_fd; struct queue *overflow; struct spa_ringbuffer buffer; uint8_t *buffer_data; uint8_t buffer_mem[DATAS_SIZE + MAX_ALIGN]; }; struct source_impl { struct spa_source source; struct impl *impl; struct spa_list link; union { spa_source_io_func_t io; spa_source_idle_func_t idle; spa_source_event_func_t event; spa_source_timer_func_t timer; spa_source_signal_func_t signal; } func; struct spa_source *fallback; bool close; bool enabled; }; /** \endcond */ static inline uint64_t get_time_ns(struct spa_system *system) { struct timespec ts; spa_system_clock_gettime(system, CLOCK_MONOTONIC, &ts); return SPA_TIMESPEC_TO_NSEC(&ts); } static int loop_add_source(void *object, struct spa_source *source) { struct impl *impl = object; source->loop = &impl->loop; source->priv = NULL; source->rmask = 0; return spa_system_pollfd_add(impl->system, impl->poll_fd, source->fd, source->mask, source); } static int loop_update_source(void *object, struct spa_source *source) { struct impl *impl = object; spa_assert(source->loop == &impl->loop); return spa_system_pollfd_mod(impl->system, impl->poll_fd, source->fd, source->mask, source); } static void detach_source(struct spa_source *source) { struct spa_poll_event *e; source->loop = NULL; source->rmask = 0; if ((e = source->priv)) { /* active in an iteration of the loop, remove it from there */ e->data = NULL; source->priv = NULL; } } static int remove_from_poll(struct impl *impl, struct spa_source *source) { spa_assert(source->loop == &impl->loop); impl->remove_count++; return spa_system_pollfd_del(impl->system, impl->poll_fd, source->fd); } static int loop_remove_source(void *object, struct spa_source *source) { struct impl *impl = object; int res = remove_from_poll(impl, source); detach_source(source); return res; } static void loop_queue_destroy(void *data) { struct queue *queue = data; struct impl *impl = queue->impl; if (queue->close_fd) spa_system_close(impl->system, queue->ack_fd); if (queue->overflow) loop_queue_destroy(queue->overflow); spa_log_info(impl->log, "%p destroyed queue %p idx:%d", impl, queue, queue->idx); free(queue); } static struct queue *loop_create_queue(void *object, bool with_fd) { struct impl *impl = object; struct queue *queue; int res; queue = calloc(1, sizeof(struct queue)); if (queue == NULL) return NULL; queue->idx = IDX_INVALID; queue->next = IDX_INVALID; queue->impl = impl; queue->buffer_data = SPA_PTR_ALIGN(queue->buffer_mem, MAX_ALIGN, uint8_t); spa_ringbuffer_init(&queue->buffer); if (with_fd) { if ((res = spa_system_eventfd_create(impl->system, SPA_FD_EVENT_SEMAPHORE | SPA_FD_CLOEXEC)) < 0) { spa_log_error(impl->log, "%p: can't create ack event: %s", impl, spa_strerror(res)); goto error; } queue->ack_fd = res; queue->close_fd = true; while (true) { uint16_t idx = SPA_ATOMIC_LOAD(impl->n_queues); if (idx >= QUEUES_MAX) { /* this is pretty bad, there are QUEUES_MAX concurrent threads * that are doing an invoke */ spa_log_error(impl->log, "max queues %d exceeded!", idx); res = -ENOSPC; goto error; } queue->idx = idx; if (SPA_ATOMIC_CAS(impl->queues[queue->idx], NULL, queue)) { SPA_ATOMIC_INC(impl->n_queues); break; } } } spa_log_info(impl->log, "%p created queue %p idx:%d %p", impl, queue, queue->idx, (void*)pthread_self()); return queue; error: loop_queue_destroy(queue); errno = -res; return NULL; } static inline struct queue *get_queue(struct impl *impl) { union tag head, next; head.v = SPA_ATOMIC_LOAD(impl->head.v); while (true) { struct queue *queue; if (SPA_UNLIKELY(head.t.idx == IDX_INVALID)) return NULL; queue = impl->queues[head.t.idx]; next.t.idx = queue->next; next.t.count = head.t.count+1; if (SPA_LIKELY(__atomic_compare_exchange_n(&impl->head.v, &head.v, next.v, 0, __ATOMIC_ACQ_REL, __ATOMIC_RELAXED))) { spa_log_trace(impl->log, "%p idx:%d %p", queue, queue->idx, (void*)pthread_self()); return queue; } } return NULL; } static inline void put_queue(struct impl *impl, struct queue *queue) { union tag head, next; spa_log_trace(impl->log, "%p idx:%d %p", queue, queue->idx, (void*)pthread_self()); head.v = SPA_ATOMIC_LOAD(impl->head.v); while (true) { queue->next = head.t.idx; next.t.idx = queue->idx; next.t.count = head.t.count+1; if (SPA_LIKELY(__atomic_compare_exchange_n(&impl->head.v, &head.v, next.v, 0, __ATOMIC_ACQ_REL, __ATOMIC_RELAXED))) break; } } static inline int32_t item_compare(struct invoke_item *a, struct invoke_item *b) { return (int32_t)(a->count - b->count); } static void flush_all_queues(struct impl *impl) { uint32_t flush_count; int res; flush_count = SPA_ATOMIC_INC(impl->flush_count); while (true) { struct queue *cqueue, *queue = NULL; struct invoke_item *citem, *item = NULL; uint32_t cindex, index; spa_invoke_func_t func; bool block; uint32_t i, n_queues; n_queues = SPA_ATOMIC_LOAD(impl->n_queues); for (i = 0; i < n_queues; i++) { /* loop over all queues and overflow queues */ for (cqueue = impl->queues[i]; cqueue != NULL; cqueue = SPA_ATOMIC_LOAD(cqueue->overflow)) { if (spa_ringbuffer_get_read_index(&cqueue->buffer, &cindex) < (int32_t)sizeof(struct invoke_item)) continue; citem = SPA_PTROFF(cqueue->buffer_data, cindex & (DATAS_SIZE - 1), struct invoke_item); if (item == NULL || item_compare(citem, item) < 0) { item = citem; queue = cqueue; index = cindex; } } } if (item == NULL) break; spa_log_trace_fp(impl->log, "%p: flush item %p", queue, item); /* first we remove the function from the item so that recursive * calls don't call the callback again. We can't update the * read index before we call the function because then the item * might get overwritten. */ func = spa_steal_ptr(item->func); if (func) { item->res = func(&impl->loop, true, item->seq, item->data, item->size, item->user_data); } /* if this function did a recursive invoke, it now flushed the * ringbuffer and we can exit */ if (flush_count != SPA_ATOMIC_LOAD(impl->flush_count)) break; index += item->item_size; block = item->block; spa_ringbuffer_read_update(&queue->buffer, index); if (block && queue->ack_fd != -1) { if ((res = spa_system_eventfd_write(impl->system, queue->ack_fd, 1)) < 0) spa_log_warn(impl->log, "%p: failed to write event fd:%d: %s", queue, queue->ack_fd, spa_strerror(res)); } } } static int loop_queue_invoke(void *object, spa_invoke_func_t func, uint32_t seq, const void *data, size_t size, bool block, void *user_data) { struct queue *queue = object, *orig = queue, *overflow; struct impl *impl = queue->impl; struct invoke_item *item; int res; int32_t filled; uint32_t avail, idx, offset, l0; bool in_thread; pthread_t loop_thread, current_thread = pthread_self(); again: loop_thread = impl->thread; in_thread = (loop_thread == 0 || pthread_equal(loop_thread, current_thread)); filled = spa_ringbuffer_get_write_index(&queue->buffer, &idx); spa_assert_se(filled >= 0 && filled <= DATAS_SIZE && "queue xrun"); avail = (uint32_t)(DATAS_SIZE - filled); if (avail < sizeof(struct invoke_item)) goto xrun; offset = idx & (DATAS_SIZE - 1); /* l0 is remaining size in ringbuffer, this should always be larger than * invoke_item, see below */ l0 = DATAS_SIZE - offset; item = SPA_PTROFF(queue->buffer_data, offset, struct invoke_item); item->func = func; item->seq = seq; item->count = SPA_ATOMIC_INC(impl->count); item->size = size; item->block = in_thread ? false : block; item->user_data = user_data; item->res = 0; item->item_size = SPA_ROUND_UP_N(sizeof(struct invoke_item) + size, ITEM_ALIGN); spa_log_trace(impl->log, "%p: add item %p filled:%d block:%d", queue, item, filled, block); if (l0 >= item->item_size) { /* item + size fit in current ringbuffer idx */ item->data = SPA_PTROFF(item, sizeof(struct invoke_item), void); if (l0 < sizeof(struct invoke_item) + item->item_size) { /* not enough space for next invoke_item, fill up till the end * so that the next item will be at the start */ item->item_size = l0; } } else { /* item does not fit, place the invoke_item at idx and start the * data at the start of the ringbuffer */ item->data = queue->buffer_data; item->item_size = SPA_ROUND_UP_N(l0 + size, ITEM_ALIGN); } if (avail < item->item_size) goto xrun; if (data && size > 0) memcpy(item->data, data, size); spa_ringbuffer_write_update(&queue->buffer, idx + item->item_size); if (in_thread) { put_queue(impl, orig); /* when there is no thread running the loop we flush the queues from * this invoking thread but we need to serialize the flushing here with * a mutex */ if (loop_thread == 0) pthread_mutex_lock(&impl->lock); flush_all_queues(impl); if (loop_thread == 0) pthread_mutex_unlock(&impl->lock); res = item->res; } else { loop_signal_event(impl, impl->wakeup); if (block && queue->ack_fd != -1) { uint64_t count = 1; int i, recurse = 0; if (pthread_mutex_trylock(&impl->lock) == 0) { /* we are holding the lock, unlock recurse times */ recurse = impl->recurse; while (impl->recurse > 0) { impl->recurse--; pthread_mutex_unlock(&impl->lock); } pthread_mutex_unlock(&impl->lock); } if ((res = spa_system_eventfd_read(impl->system, queue->ack_fd, &count)) < 0) spa_log_warn(impl->log, "%p: failed to read event fd:%d: %s", queue, queue->ack_fd, spa_strerror(res)); for (i = 0; i < recurse; i++) { pthread_mutex_lock(&impl->lock); impl->recurse++; } res = item->res; } else { if (seq != SPA_ID_INVALID) res = SPA_RESULT_RETURN_ASYNC(seq); else res = 0; } put_queue(impl, orig); } return res; xrun: /* we overflow, make a new queue that shares the same fd * and place it in the overflow array. We hold the queue so there * is only ever one writer to the overflow field. */ overflow = queue->overflow; if (overflow == NULL) { overflow = loop_create_queue(impl, false); if (overflow == NULL) return -errno; overflow->ack_fd = queue->ack_fd; SPA_ATOMIC_STORE(queue->overflow, overflow); } queue = overflow; goto again; } static void wakeup_func(void *data, uint64_t count) { struct impl *impl = data; flush_all_queues(impl); } static int loop_invoke(void *object, spa_invoke_func_t func, uint32_t seq, const void *data, size_t size, bool block, void *user_data) { struct impl *impl = object; struct queue *queue; int res = 0, suppressed; uint64_t nsec; while (true) { queue = get_queue(impl); if (SPA_UNLIKELY(queue == NULL)) queue = loop_create_queue(impl, true); if (SPA_UNLIKELY(queue == NULL)) { if (SPA_UNLIKELY(errno != ENOSPC)) return -errno; /* there was no space for a new queue. This means QUEUE_MAX * threads are concurrently doing an invoke. We can wait a little * and retry to get a queue */ if (impl->retry_timeout == 0) return -EPIPE; nsec = get_time_ns(impl->system); if ((suppressed = spa_ratelimit_test(&impl->rate_limit, nsec)) >= 0) { spa_log_warn(impl->log, "%p: out of queues, retrying (%d suppressed)", impl, suppressed); } usleep(impl->retry_timeout); } else { res = loop_queue_invoke(queue, func, seq, data, size, block, user_data); break; } } return res; } static int loop_locked(void *object, spa_invoke_func_t func, uint32_t seq, const void *data, size_t size, void *user_data) { struct impl *impl = object; int res; pthread_mutex_lock(&impl->lock); res = func(&impl->loop, false, seq, data, size, user_data); pthread_mutex_unlock(&impl->lock); return res; } static int loop_get_fd(void *object) { struct impl *impl = object; return impl->poll_fd; } static void loop_add_hook(void *object, struct spa_hook *hook, const struct spa_loop_control_hooks *hooks, void *data) { struct impl *impl = object; spa_return_if_fail(SPA_CALLBACK_CHECK(hooks, before, 0)); spa_return_if_fail(SPA_CALLBACK_CHECK(hooks, after, 0)); spa_hook_list_append(&impl->hooks_list, hook, hooks, data); } static void loop_enter(void *object) { struct impl *impl = object; pthread_t thread_id = pthread_self(); pthread_mutex_lock(&impl->lock); if (impl->enter_count == 0) { spa_return_if_fail(impl->thread == 0); impl->thread = thread_id; impl->enter_count = 1; } else { spa_return_if_fail(impl->enter_count > 0); spa_return_if_fail(pthread_equal(impl->thread, thread_id)); impl->enter_count++; } spa_log_trace_fp(impl->log, "%p: enter %p", impl, (void *) impl->thread); } static void loop_leave(void *object) { struct impl *impl = object; pthread_t thread_id = pthread_self(); spa_return_if_fail(impl->enter_count > 0); spa_return_if_fail(pthread_equal(impl->thread, thread_id)); spa_log_trace_fp(impl->log, "%p: leave %p", impl, (void *) impl->thread); if (--impl->enter_count == 0) { impl->thread = 0; flush_all_queues(impl); } pthread_mutex_unlock(&impl->lock); } static int loop_check(void *object) { struct impl *impl = object; pthread_t thread_id = pthread_self(); int res; /* we are in the thread running the loop */ if (impl->thread == 0 || pthread_equal(impl->thread, thread_id)) return 1; /* if lock taken by something else, error */ if ((res = pthread_mutex_trylock(&impl->lock)) != 0) return -res; /* we could take the lock, check if we actually locked it somewhere */ res = impl->recurse > 0 ? 1 : -EPERM; pthread_mutex_unlock(&impl->lock); return res; } static int loop_lock(void *object) { struct impl *impl = object; int res; if ((res = pthread_mutex_lock(&impl->lock)) == 0) impl->recurse++; return -res; } static int loop_unlock(void *object) { struct impl *impl = object; int res; spa_return_val_if_fail(impl->recurse > 0, -EIO); impl->recurse--; if ((res = pthread_mutex_unlock(&impl->lock)) != 0) impl->recurse++; return -res; } static int loop_get_time(void *object, struct timespec *abstime, int64_t timeout) { if (clock_gettime(CLOCK_REALTIME, abstime) < 0) return -errno; abstime->tv_sec += timeout / SPA_NSEC_PER_SEC; abstime->tv_nsec += timeout % SPA_NSEC_PER_SEC; if (abstime->tv_nsec >= SPA_NSEC_PER_SEC) { abstime->tv_sec++; abstime->tv_nsec -= SPA_NSEC_PER_SEC; } return 0; } static int loop_wait(void *object, const struct timespec *abstime) { struct impl *impl = object; int res; impl->n_waiting++; impl->recurse--; if (abstime) res = pthread_cond_timedwait(&impl->cond, &impl->lock, abstime); else res = pthread_cond_wait(&impl->cond, &impl->lock); impl->recurse++; impl->n_waiting--; return -res; } static int loop_signal(void *object, bool wait_for_accept) { struct impl *impl = object; int res = 0; if (impl->n_waiting > 0) if ((res = pthread_cond_broadcast(&impl->cond)) != 0) return -res; if (wait_for_accept) { impl->n_waiting_for_accept++; while (impl->n_waiting_for_accept > 0) { if ((res = pthread_cond_wait(&impl->accept_cond, &impl->lock)) != 0) return -res; } } return res; } static int loop_accept(void *object) { struct impl *impl = object; impl->n_waiting_for_accept--; return -pthread_cond_signal(&impl->accept_cond); } struct cancellation_handler_data { struct spa_poll_event *ep; int ep_count; }; static void cancellation_handler(void *closure) { const struct cancellation_handler_data *data = closure; for (int i = 0; i < data->ep_count; i++) { struct spa_source *s = data->ep[i].data; if (SPA_LIKELY(s)) { s->rmask = 0; s->priv = NULL; } } } static int loop_iterate_cancel(void *object, int timeout) { struct impl *impl = object; struct spa_poll_event ep[MAX_EP], *e; int i, nfds; uint32_t remove_count; remove_count = impl->remove_count; spa_loop_control_hook_before(&impl->hooks_list); pthread_mutex_unlock(&impl->lock); nfds = spa_system_pollfd_wait(impl->system, impl->poll_fd, ep, SPA_N_ELEMENTS(ep), timeout); pthread_mutex_lock(&impl->lock); spa_loop_control_hook_after(&impl->hooks_list); if (remove_count != impl->remove_count) nfds = 0; struct cancellation_handler_data cdata = { ep, nfds }; pthread_cleanup_push(cancellation_handler, &cdata); /* first we set all the rmasks, then call the callbacks. The reason is that * some callback might also want to look at other sources it manages and * can then reset the rmask to suppress the callback */ for (i = 0; i < nfds; i++) { struct spa_source *s = ep[i].data; spa_assert(s->loop == &impl->loop); s->rmask = ep[i].events; /* already active in another iteration of the loop, * remove it from that iteration */ if (SPA_UNLIKELY(e = s->priv)) e->data = NULL; s->priv = &ep[i]; } for (i = 0; i < nfds; i++) { struct spa_source *s = ep[i].data; if (SPA_LIKELY(s && s->rmask)) s->func(s); } pthread_cleanup_pop(true); return nfds; } static int loop_iterate(void *object, int timeout) { struct impl *impl = object; struct spa_poll_event ep[MAX_EP], *e; int i, nfds; uint32_t remove_count; remove_count = impl->remove_count; spa_loop_control_hook_before(&impl->hooks_list); pthread_mutex_unlock(&impl->lock); nfds = spa_system_pollfd_wait(impl->system, impl->poll_fd, ep, SPA_N_ELEMENTS(ep), timeout); pthread_mutex_lock(&impl->lock); spa_loop_control_hook_after(&impl->hooks_list); if (remove_count != impl->remove_count) return 0; /* first we set all the rmasks, then call the callbacks. The reason is that * some callback might also want to look at other sources it manages and * can then reset the rmask to suppress the callback */ for (i = 0; i < nfds; i++) { struct spa_source *s = ep[i].data; s->rmask = ep[i].events; /* already active in another iteration of the loop, * remove it from that iteration */ if (SPA_UNLIKELY(e = s->priv)) e->data = NULL; s->priv = &ep[i]; } for (i = 0; i < nfds; i++) { struct spa_source *s = ep[i].data; if (SPA_LIKELY(s && s->rmask)) s->func(s); } for (i = 0; i < nfds; i++) { struct spa_source *s = ep[i].data; if (SPA_LIKELY(s)) { s->rmask = 0; s->priv = NULL; } } return nfds; } static struct source_impl *get_source(struct impl *impl) { struct source_impl *source; if (!spa_list_is_empty(&impl->free_list)) { source = spa_list_first(&impl->free_list, struct source_impl, link); spa_list_remove(&source->link); spa_zero(*source); } else { source = calloc(1, sizeof(struct source_impl)); } if (source != NULL) { source->impl = impl; spa_list_insert(&impl->source_list, &source->link); } return source; } static void source_io_func(struct spa_source *source) { struct source_impl *s = SPA_CONTAINER_OF(source, struct source_impl, source); spa_log_trace_fp(s->impl->log, "%p: io %08x", s, source->rmask); s->func.io(source->data, source->fd, source->rmask); } static struct spa_source *loop_add_io(void *object, int fd, uint32_t mask, bool close, spa_source_io_func_t func, void *data) { struct impl *impl = object; struct source_impl *source; int res; source = get_source(impl); if (source == NULL) goto error_exit; source->source.func = source_io_func; source->source.data = data; source->source.fd = fd; source->source.mask = mask; source->close = close; source->func.io = func; if ((res = loop_add_source(impl, &source->source)) < 0) { if (res != -EPERM) goto error_exit_free; /* file fds (stdin/stdout/...) give EPERM in epoll. Those fds always * return from epoll with the mask set, so we can handle this with * an idle source */ source->source.rmask = mask; source->fallback = spa_loop_utils_add_idle(&impl->utils, mask & (SPA_IO_IN | SPA_IO_OUT) ? true : false, (spa_source_idle_func_t) source_io_func, source); spa_log_trace(impl->log, "%p: adding fallback %p", impl, source->fallback); } return &source->source; error_exit_free: free(source); errno = -res; error_exit: return NULL; } static int loop_update_io(void *object, struct spa_source *source, uint32_t mask) { struct impl *impl = object; struct source_impl *s = SPA_CONTAINER_OF(source, struct source_impl, source); int res; spa_assert(s->impl == object); spa_assert(source->func == source_io_func); spa_log_trace(impl->log, "%p: update %08x -> %08x", s, source->mask, mask); source->mask = mask; if (s->fallback) res = spa_loop_utils_enable_idle(&impl->utils, s->fallback, mask & (SPA_IO_IN | SPA_IO_OUT) ? true : false); else res = loop_update_source(object, source); return res; } static void source_idle_func(struct spa_source *source) { struct source_impl *s = SPA_CONTAINER_OF(source, struct source_impl, source); s->func.idle(source->data); } static int loop_enable_idle(void *object, struct spa_source *source, bool enabled) { struct source_impl *s = SPA_CONTAINER_OF(source, struct source_impl, source); int res = 0; spa_assert(s->impl == object); spa_assert(source->func == source_idle_func); if (enabled && !s->enabled) { if ((res = spa_system_eventfd_write(s->impl->system, source->fd, 1)) < 0) spa_log_warn(s->impl->log, "%p: failed to write idle fd:%d: %s", source, source->fd, spa_strerror(res)); } else if (!enabled && s->enabled) { uint64_t count; if ((res = spa_system_eventfd_read(s->impl->system, source->fd, &count)) < 0) spa_log_warn(s->impl->log, "%p: failed to read idle fd:%d: %s", source, source->fd, spa_strerror(res)); } s->enabled = enabled; return res; } static struct spa_source *loop_add_idle(void *object, bool enabled, spa_source_idle_func_t func, void *data) { struct impl *impl = object; struct source_impl *source; int res; source = get_source(impl); if (source == NULL) goto error_exit; if ((res = spa_system_eventfd_create(impl->system, SPA_FD_CLOEXEC | SPA_FD_NONBLOCK)) < 0) goto error_exit_free; source->source.func = source_idle_func; source->source.data = data; source->source.fd = res; source->close = true; source->source.mask = SPA_IO_IN; source->func.idle = func; if ((res = loop_add_source(impl, &source->source)) < 0) goto error_exit_close; if (enabled) loop_enable_idle(impl, &source->source, true); return &source->source; error_exit_close: spa_system_close(impl->system, source->source.fd); error_exit_free: free(source); errno = -res; error_exit: return NULL; } static void source_event_func(struct spa_source *source) { struct source_impl *s = SPA_CONTAINER_OF(source, struct source_impl, source); uint64_t count = 0; int res; if ((res = spa_system_eventfd_read(s->impl->system, source->fd, &count)) < 0) { if (res != -EAGAIN) spa_log_warn(s->impl->log, "%p: failed to read event fd:%d: %s", source, source->fd, spa_strerror(res)); return; } s->func.event(source->data, count); } static struct spa_source *loop_add_event(void *object, spa_source_event_func_t func, void *data) { struct impl *impl = object; struct source_impl *source; int res; source = get_source(impl); if (source == NULL) goto error_exit; if ((res = spa_system_eventfd_create(impl->system, SPA_FD_CLOEXEC | SPA_FD_NONBLOCK)) < 0) goto error_exit_free; source->source.func = source_event_func; source->source.data = data; source->source.fd = res; source->source.mask = SPA_IO_IN; source->close = true; source->func.event = func; if ((res = loop_add_source(impl, &source->source)) < 0) goto error_exit_close; return &source->source; error_exit_close: spa_system_close(impl->system, source->source.fd); error_exit_free: free(source); errno = -res; error_exit: return NULL; } static int loop_signal_event(void *object, struct spa_source *source) { struct source_impl *s = SPA_CONTAINER_OF(source, struct source_impl, source); int res; spa_assert(s->impl == object); spa_assert(source->func == source_event_func); if (SPA_UNLIKELY((res = spa_system_eventfd_write(s->impl->system, source->fd, 1)) < 0)) spa_log_warn(s->impl->log, "%p: failed to write event fd:%d: %s", source, source->fd, spa_strerror(res)); return res; } static void source_timer_func(struct spa_source *source) { struct source_impl *s = SPA_CONTAINER_OF(source, struct source_impl, source); uint64_t expirations = 0; int res; if (SPA_UNLIKELY((res = spa_system_timerfd_read(s->impl->system, source->fd, &expirations)) < 0)) { if (res != -EAGAIN) spa_log_warn(s->impl->log, "%p: failed to read timer fd:%d: %s", source, source->fd, spa_strerror(res)); return; } s->func.timer(source->data, expirations); } static struct spa_source *loop_add_timer(void *object, spa_source_timer_func_t func, void *data) { struct impl *impl = object; struct source_impl *source; int res; source = get_source(impl); if (source == NULL) goto error_exit; if ((res = spa_system_timerfd_create(impl->system, CLOCK_MONOTONIC, SPA_FD_CLOEXEC | SPA_FD_NONBLOCK)) < 0) goto error_exit_free; source->source.func = source_timer_func; source->source.data = data; source->source.fd = res; source->source.mask = SPA_IO_IN; source->close = true; source->func.timer = func; if ((res = loop_add_source(impl, &source->source)) < 0) goto error_exit_close; return &source->source; error_exit_close: spa_system_close(impl->system, source->source.fd); error_exit_free: free(source); errno = -res; error_exit: return NULL; } static int loop_update_timer(void *object, struct spa_source *source, struct timespec *value, struct timespec *interval, bool absolute) { struct source_impl *s = SPA_CONTAINER_OF(source, struct source_impl, source); struct itimerspec its; int flags = 0, res; spa_assert(s->impl == object); spa_assert(source->func == source_timer_func); spa_zero(its); if (SPA_LIKELY(value)) { its.it_value = *value; } else if (interval) { // timer initially fires after one interval its.it_value = *interval; absolute = false; } if (SPA_UNLIKELY(interval)) its.it_interval = *interval; if (SPA_LIKELY(absolute)) flags |= SPA_FD_TIMER_ABSTIME; if (SPA_UNLIKELY((res = spa_system_timerfd_settime(s->impl->system, source->fd, flags, &its, NULL)) < 0)) return res; return 0; } static void source_signal_func(struct spa_source *source) { struct source_impl *s = SPA_CONTAINER_OF(source, struct source_impl, source); int res, signal_number = 0; if ((res = spa_system_signalfd_read(s->impl->system, source->fd, &signal_number)) < 0) { if (res != -EAGAIN) spa_log_warn(s->impl->log, "%p: failed to read signal fd:%d: %s", source, source->fd, spa_strerror(res)); return; } s->func.signal(source->data, signal_number); } static struct spa_source *loop_add_signal(void *object, int signal_number, spa_source_signal_func_t func, void *data) { struct impl *impl = object; struct source_impl *source; int res; source = get_source(impl); if (source == NULL) goto error_exit; if ((res = spa_system_signalfd_create(impl->system, signal_number, SPA_FD_CLOEXEC | SPA_FD_NONBLOCK)) < 0) goto error_exit_free; source->source.func = source_signal_func; source->source.data = data; source->source.fd = res; source->source.mask = SPA_IO_IN; source->close = true; source->func.signal = func; if ((res = loop_add_source(impl, &source->source)) < 0) goto error_exit_close; return &source->source; error_exit_close: spa_system_close(impl->system, source->source.fd); error_exit_free: free(source); errno = -res; error_exit: return NULL; } static void loop_destroy_source(void *object, struct spa_source *source) { struct source_impl *s = SPA_CONTAINER_OF(source, struct source_impl, source); spa_assert(s->impl == object); spa_log_trace(s->impl->log, "%p ", s); if (s->fallback) loop_destroy_source(s->impl, s->fallback); else remove_from_poll(s->impl, source); if (source->fd != -1 && s->close) { spa_system_close(s->impl->system, source->fd); source->fd = -1; } spa_list_remove(&s->link); detach_source(source); spa_list_insert(&s->impl->free_list, &s->link); } static const struct spa_loop_methods impl_loop = { SPA_VERSION_LOOP_METHODS, .add_source = loop_add_source, .update_source = loop_update_source, .remove_source = loop_remove_source, .invoke = loop_invoke, .locked = loop_locked, }; static const struct spa_loop_control_methods impl_loop_control_cancel = { SPA_VERSION_LOOP_CONTROL_METHODS, .get_fd = loop_get_fd, .add_hook = loop_add_hook, .enter = loop_enter, .leave = loop_leave, .iterate = loop_iterate_cancel, .check = loop_check, .lock = loop_lock, .unlock = loop_unlock, .get_time = loop_get_time, .wait = loop_wait, .signal = loop_signal, .accept = loop_accept, }; static const struct spa_loop_control_methods impl_loop_control = { SPA_VERSION_LOOP_CONTROL_METHODS, .get_fd = loop_get_fd, .add_hook = loop_add_hook, .enter = loop_enter, .leave = loop_leave, .iterate = loop_iterate, .check = loop_check, .lock = loop_lock, .unlock = loop_unlock, .get_time = loop_get_time, .wait = loop_wait, .signal = loop_signal, .accept = loop_accept, }; static const struct spa_loop_utils_methods impl_loop_utils = { SPA_VERSION_LOOP_UTILS_METHODS, .add_io = loop_add_io, .update_io = loop_update_io, .add_idle = loop_add_idle, .enable_idle = loop_enable_idle, .add_event = loop_add_event, .signal_event = loop_signal_event, .add_timer = loop_add_timer, .update_timer = loop_update_timer, .add_signal = loop_add_signal, .destroy_source = loop_destroy_source, }; static int impl_get_interface(struct spa_handle *handle, const char *type, void **interface) { struct impl *impl; spa_return_val_if_fail(handle != NULL, -EINVAL); spa_return_val_if_fail(interface != NULL, -EINVAL); impl = (struct impl *) handle; if (spa_streq(type, SPA_TYPE_INTERFACE_Loop)) *interface = &impl->loop; else if (spa_streq(type, SPA_TYPE_INTERFACE_LoopControl)) *interface = &impl->control; else if (spa_streq(type, SPA_TYPE_INTERFACE_LoopUtils)) *interface = &impl->utils; else return -ENOENT; return 0; } static int impl_clear(struct spa_handle *handle) { struct impl *impl; struct source_impl *source; uint32_t i; spa_return_val_if_fail(handle != NULL, -EINVAL); impl = (struct impl *) handle; spa_log_debug(impl->log, "%p: clear", impl); if (impl->enter_count != 0) spa_log_warn(impl->log, "%p: loop is entered %d times", impl, impl->enter_count); spa_list_consume(source, &impl->source_list, link) loop_destroy_source(impl, &source->source); spa_list_consume(source, &impl->free_list, link) { spa_list_remove(&source->link); free(source); } for (i = 0; i < impl->n_queues; i++) loop_queue_destroy(impl->queues[i]); spa_system_close(impl->system, impl->poll_fd); pthread_cond_destroy(&impl->cond); pthread_mutex_destroy(&impl->lock); return 0; } static size_t impl_get_size(const struct spa_handle_factory *factory, const struct spa_dict *params) { return sizeof(struct impl); } #define CHECK(expression,label) \ do { \ if ((errno = (expression)) != 0) { \ res = -errno; \ spa_log_error(impl->log, #expression ": %s", strerror(errno)); \ goto label; \ } \ } while(false); static int impl_init(const struct spa_handle_factory *factory, struct spa_handle *handle, const struct spa_dict *info, const struct spa_support *support, uint32_t n_support) { struct impl *impl; const char *str; pthread_mutexattr_t attr; pthread_condattr_t cattr; int res; spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(handle != NULL, -EINVAL); handle->get_interface = impl_get_interface; handle->clear = impl_clear; impl = (struct impl *) handle; impl->loop.iface = SPA_INTERFACE_INIT( SPA_TYPE_INTERFACE_Loop, SPA_VERSION_LOOP, &impl_loop, impl); impl->control.iface = SPA_INTERFACE_INIT( SPA_TYPE_INTERFACE_LoopControl, SPA_VERSION_LOOP_CONTROL, &impl_loop_control, impl); impl->utils.iface = SPA_INTERFACE_INIT( SPA_TYPE_INTERFACE_LoopUtils, SPA_VERSION_LOOP_UTILS, &impl_loop_utils, impl); impl->rate_limit.interval = 2 * SPA_NSEC_PER_SEC; impl->rate_limit.burst = 1; impl->retry_timeout = DEFAULT_RETRY; if (info) { if ((str = spa_dict_lookup(info, "loop.cancel")) != NULL && spa_atob(str)) impl->control.iface.cb.funcs = &impl_loop_control_cancel; if ((str = spa_dict_lookup(info, "loop.retry-timeout")) != NULL) impl->retry_timeout = atoi(str); if ((str = spa_dict_lookup(info, "loop.prio-inherit")) != NULL) impl->prio_inherit = spa_atob(str); } CHECK(pthread_mutexattr_init(&attr), error_exit); CHECK(pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE), error_exit_free_attr); if (impl->prio_inherit) CHECK(pthread_mutexattr_setprotocol(&attr, PTHREAD_PRIO_INHERIT), error_exit_free_attr) CHECK(pthread_mutex_init(&impl->lock, &attr), error_exit_free_attr); pthread_mutexattr_destroy(&attr); CHECK(pthread_condattr_init(&cattr), error_exit_free_mutex); CHECK(pthread_condattr_setclock(&cattr, CLOCK_REALTIME), error_exit_free_mutex); CHECK(pthread_cond_init(&impl->cond, &cattr), error_exit_free_mutex); CHECK(pthread_cond_init(&impl->accept_cond, &cattr), error_exit_free_mutex); pthread_condattr_destroy(&cattr); impl->log = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log); spa_log_topic_init(impl->log, &log_topic); impl->system = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_System); if (impl->system == NULL) { spa_log_error(impl->log, "%p: a System is needed", impl); res = -EINVAL; goto error_exit_free_cond; } if ((res = spa_system_pollfd_create(impl->system, SPA_FD_CLOEXEC)) < 0) { spa_log_error(impl->log, "%p: can't create pollfd: %s", impl, spa_strerror(res)); goto error_exit_free_cond; } impl->poll_fd = res; spa_list_init(&impl->source_list); spa_list_init(&impl->free_list); spa_hook_list_init(&impl->hooks_list); impl->wakeup = loop_add_event(impl, wakeup_func, impl); if (impl->wakeup == NULL) { res = -errno; spa_log_error(impl->log, "%p: can't create wakeup event: %m", impl); goto error_exit_free_poll; } impl->head.t.idx = IDX_INVALID; spa_log_debug(impl->log, "%p: initialized", impl); return 0; error_exit_free_poll: spa_system_close(impl->system, impl->poll_fd); error_exit_free_cond: pthread_cond_destroy(&impl->cond); error_exit_free_mutex: pthread_mutex_destroy(&impl->lock); error_exit_free_attr: pthread_mutexattr_destroy(&attr); error_exit: return res; } static const struct spa_interface_info impl_interfaces[] = { {SPA_TYPE_INTERFACE_Loop,}, {SPA_TYPE_INTERFACE_LoopControl,}, {SPA_TYPE_INTERFACE_LoopUtils,}, }; static int impl_enum_interface_info(const struct spa_handle_factory *factory, const struct spa_interface_info **info, uint32_t *index) { spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(info != NULL, -EINVAL); spa_return_val_if_fail(index != NULL, -EINVAL); if (*index >= SPA_N_ELEMENTS(impl_interfaces)) return 0; *info = &impl_interfaces[(*index)++]; return 1; } const struct spa_handle_factory spa_support_loop_factory = { SPA_VERSION_HANDLE_FACTORY, SPA_NAME_SUPPORT_LOOP, NULL, impl_get_size, impl_init, impl_enum_interface_info }; pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/support/meson.build000066400000000000000000000035271511204443500266270ustar00rootroot00000000000000spa_support_sources = [ 'cpu.c', 'logger.c', 'loop.c', 'node-driver.c', 'null-audio-sink.c', 'plugin.c', 'system.c' ] simd_cargs = [] if have_sse simd_cargs += [sse_args, '-DHAVE_SSE'] endif stdthreads_lib = cc.find_library('stdthreads', required: false) spa_support_lib = shared_library('spa-support', spa_support_sources, c_args : [ simd_cargs ], include_directories : [ configinc ], dependencies : [ spa_dep, pthread_lib, epoll_shim_dep, mathlib, stdthreads_lib ], install : true, install_dir : spa_plugindir / 'support') spa_support_dep = declare_dependency(link_with: spa_support_lib) if get_option('evl').allowed() evl_inc = include_directories('/usr/include') evl_lib = cc.find_library('evl', dirs: ['/usr/lib/'], required: get_option('evl')) spa_evl_sources = ['evl-system.c', 'evl-plugin.c'] spa_evl_lib = shared_library('spa-evl', spa_evl_sources, include_directories : [ evl_inc], dependencies : [ spa_dep, pthread_lib, evl_lib ], install : true, install_dir : spa_plugindir / 'support') endif if dbus_dep.found() spa_dbus_sources = ['dbus.c'] spa_dbus_lib = shared_library('spa-dbus', spa_dbus_sources, dependencies : [ spa_dep, dbus_dep ], install : true, install_dir : spa_plugindir / 'support') spa_dbus_dep = declare_dependency(link_with: spa_dbus_lib) else spa_dbus_dep = declare_dependency() endif if systemd_dep.found() spa_journal_sources = [ 'journal.c', ] spa_journal_lib = shared_library('spa-journal', spa_journal_sources, include_directories : [ configinc ], dependencies : [ spa_dep, systemd_dep ], install : true, install_dir : spa_plugindir / 'support') spa_journal_dep = declare_dependency(link_with: spa_journal_lib) else spa_journal_dep = declare_dependency() endif pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/support/node-driver.c000066400000000000000000000713661511204443500270550ustar00rootroot00000000000000/* Spa */ /* SPDX-FileCopyrightText: Copyright © 2020 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #include #include #ifdef __linux__ #include #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include SPA_LOG_TOPIC_DEFINE_STATIC(log_topic, "spa.driver"); #undef SPA_LOG_TOPIC_DEFAULT #define SPA_LOG_TOPIC_DEFAULT &log_topic #define DEFAULT_FREEWHEEL false #define DEFAULT_FREEWHEEL_WAIT 5 #define DEFAULT_CLOCK_PREFIX "clock.system" #define DEFAULT_CLOCK_ID CLOCK_MONOTONIC #define DEFAULT_RESYNC_MS 10 #define CLOCK_OFFSET_NAVG 20 #define CLOCK_OFFSET_MAX_ERR (50 * SPA_NSEC_PER_USEC) #define CLOCKFD 3 #define FD_TO_CLOCKID(fd) ((~(clockid_t) (fd) << 3) | CLOCKFD) #define CLOCKID_TO_FD(clk) ((unsigned int) ~((clk) >> 3)) #define BW_PERIOD (3 * SPA_NSEC_PER_SEC) #define MAX_ERROR_MS 1 #define CLOCK_NAME_MAX 64 struct props { bool freewheel; char clock_name[CLOCK_NAME_MAX]; clockid_t clock_id; uint32_t freewheel_wait; float resync_ms; char clock_device[CLOCK_NAME_MAX]; char clock_interface[CLOCK_NAME_MAX]; }; struct clock_offset { int64_t offset; int64_t err; }; struct impl { struct spa_handle handle; struct spa_node node; struct props props; struct spa_log *log; struct spa_loop *data_loop; struct spa_system *data_system; uint64_t info_all; struct spa_node_info info; #define NODE_PropInfo 0 #define NODE_Props 1 #define N_NODE_PARAMS 2 struct spa_param_info params[N_NODE_PARAMS]; struct spa_hook_list hooks; struct spa_callbacks callbacks; struct spa_io_position *position; struct spa_io_clock *clock; struct spa_source timer_source; struct itimerspec timerspec; int clock_fd; bool started; bool following; bool tracking; clockid_t timer_clockid; uint64_t next_time; uint64_t last_time; uint64_t base_time; struct spa_dll dll; double max_error; double max_resync; struct clock_offset nsec_offset; }; static void reset_props_strings(struct props *props) { spa_zero(props->clock_name); spa_zero(props->clock_device); spa_zero(props->clock_interface); } static void reset_props(struct props *props) { props->freewheel = DEFAULT_FREEWHEEL; props->clock_id = CLOCK_MONOTONIC; props->freewheel_wait = DEFAULT_FREEWHEEL_WAIT; props->resync_ms = DEFAULT_RESYNC_MS; reset_props_strings(props); } static const struct clock_info { const char *name; clockid_t id; } clock_info[] = { { "realtime", CLOCK_REALTIME }, #ifdef CLOCK_TAI { "tai", CLOCK_TAI }, #endif { "monotonic", CLOCK_MONOTONIC }, #ifdef CLOCK_MONOTONIC_RAW { "monotonic-raw", CLOCK_MONOTONIC_RAW }, #endif #ifdef CLOCK_BOOTTIME { "boottime", CLOCK_BOOTTIME }, #endif }; static bool clock_for_timerfd(clockid_t id) { return id == CLOCK_REALTIME || #ifdef CLOCK_BOOTTIME id == CLOCK_BOOTTIME || #endif id == CLOCK_MONOTONIC; } static clockid_t clock_name_to_id(const char *name) { SPA_FOR_EACH_ELEMENT_VAR(clock_info, i) { if (spa_streq(i->name, name)) return i->id; } return -1; } static const char *clock_id_to_name(clockid_t id) { SPA_FOR_EACH_ELEMENT_VAR(clock_info, i) { if (i->id == id) return i->name; } return "custom"; } static void set_timeout(struct impl *this, uint64_t next_time) { /* The realtime system clock may be modified by the user. In such a case, * a scheduled timer must be canceled, since its timeout is no longer * correctly corresponding to the duration of a graph cycle. Worse, if * for example the user resets the realtime clock way back to the past, * then the timeout may now be far in the future, meaning that the next * graph cycle never takes place. The SPA_FD_TIMER_CANCEL_ON_SET flag is * used here to automatically cancel the timer if the user sets the realtime * clock so that the driver can reschedule the cycle. (Timer cancelation * will trigger an on_timeout() invocation with spa_system_timerfd_read() * returning -ECANCELED.) * If timerfd is used with a non-realtime clock, the flag is ignored. * (Note that the flag only works in combination with SPA_FD_TIMER_ABSTIME.) */ spa_log_trace(this->log, "set timeout %"PRIu64, next_time); this->timerspec.it_value.tv_sec = next_time / SPA_NSEC_PER_SEC; this->timerspec.it_value.tv_nsec = next_time % SPA_NSEC_PER_SEC; spa_system_timerfd_settime(this->data_system, this->timer_source.fd, SPA_FD_TIMER_ABSTIME | SPA_FD_TIMER_CANCEL_ON_SET, &this->timerspec, NULL); } static inline uint64_t gettime_nsec(struct impl *this, clockid_t clock_id) { struct timespec now = { 0 }; uint64_t nsec; if (spa_system_clock_gettime(this->data_system, clock_id, &now) < 0) return 0; nsec = SPA_TIMESPEC_TO_NSEC(&now); spa_log_trace(this->log, "%p now:%"PRIu64, this, nsec); return nsec; } static int set_timers(struct impl *this) { this->next_time = gettime_nsec(this, this->timer_clockid); spa_log_debug(this->log, "%p now:%"PRIu64, this, this->next_time); if (this->following || !this->started) { set_timeout(this, 0); } else { set_timeout(this, this->next_time); } return 0; } static inline bool is_following(struct impl *this) { return this->position && this->clock && this->position->clock.id != this->clock->id; } static int do_set_timers(struct spa_loop *loop, bool async, uint32_t seq, const void *data, size_t size, void *user_data) { struct impl *this = user_data; set_timers(this); return 0; } static int64_t get_nsec_offset(struct impl *this, uint64_t *now) { struct timespec ts1, ts2, ts3; int64_t t1, t2, t3; /* Offset between timer clock and monotonic */ if (this->timer_clockid == CLOCK_MONOTONIC) return 0; spa_system_clock_gettime(this->data_system, CLOCK_MONOTONIC, &ts1); spa_system_clock_gettime(this->data_system, this->timer_clockid, &ts2); spa_system_clock_gettime(this->data_system, CLOCK_MONOTONIC, &ts3); t1 = SPA_TIMESPEC_TO_NSEC(&ts1); t2 = SPA_TIMESPEC_TO_NSEC(&ts2); t3 = SPA_TIMESPEC_TO_NSEC(&ts3); if (now) *now = t3; return t1 + (t3 - t1) / 2 - t2; } static int64_t clock_offset_update(struct clock_offset *off, int64_t offset, struct spa_log *log) { const int64_t max_resync = CLOCK_OFFSET_MAX_ERR; const int64_t n = CLOCK_OFFSET_NAVG; int64_t err; /* Moving average smoothing, discarding outliers */ err = offset - off->offset; if (SPA_ABS(err) > max_resync) { /* Clock jump */ spa_log_info(log, "nsec err %"PRIi64" > max_resync %"PRIi64", resetting", err, max_resync); off->offset = offset; off->err = 0; err = 0; } else if (SPA_ABS(err) / 2 <= off->err) { off->offset += err / n; } off->err += (SPA_ABS(err) - off->err) / n; spa_log_trace(log, "clock offset %"PRIi64" err:%"PRIi64" abs-err:%"PRIi64, off->offset, err, off->err); return off->offset; } static int64_t smooth_nsec_offset(struct impl *this, uint64_t *now) { int64_t offset; if (this->timer_clockid == CLOCK_MONOTONIC) return 0; offset = get_nsec_offset(this, now); return clock_offset_update(&this->nsec_offset, offset, this->log); } static int reassign_follower(struct impl *this) { bool following; if (this->clock) SPA_FLAG_UPDATE(this->clock->flags, SPA_IO_CLOCK_FLAG_FREEWHEEL, this->props.freewheel); if (!this->started) return 0; following = is_following(this); if (following != this->following) { spa_log_debug(this->log, "%p: reassign follower %d->%d", this, this->following, following); this->following = following; spa_loop_locked(this->data_loop, do_set_timers, 0, NULL, 0, this); } return 0; } static int impl_node_set_io(void *object, uint32_t id, void *data, size_t size) { struct impl *this = object; spa_return_val_if_fail(this != NULL, -EINVAL); switch (id) { case SPA_IO_Clock: if (size > 0 && size < sizeof(struct spa_io_clock)) return -EINVAL; this->clock = data; if (this->clock) spa_scnprintf(this->clock->name, sizeof(this->clock->name), "%s", this->props.clock_name); break; case SPA_IO_Position: if (size > 0 && size < sizeof(struct spa_io_position)) return -EINVAL; this->position = data; break; default: return -ENOENT; } reassign_follower(this); return 0; } static inline uint64_t scale_u64(uint64_t val, uint32_t num, uint32_t denom) { #if 0 return ((__uint128_t)val * num) / denom; #else return (uint64_t)((double)val / denom * num); #endif } static void on_timeout(struct spa_source *source) { struct impl *this = source->data; uint64_t expirations, nsec, duration, current_time, current_position, position; uint32_t rate; double corr = 1.0, err = 0.0; int res; bool timer_was_canceled = false; /* See set_timeout() for an explanation about timer cancelation. */ if ((res = spa_system_timerfd_read(this->data_system, this->timer_source.fd, &expirations)) < 0) { if (res == -EAGAIN) { return; } else if (res == -ECANCELED) { spa_log_debug(this->log, "%p: timer was canceled; " "rescheduling graph cycle", this); timer_was_canceled = true; } else { spa_log_error(this->log, "%p: timerfd error: %s", this, spa_strerror(res)); return; } } if (SPA_LIKELY(this->position)) { duration = this->position->clock.target_duration; rate = this->position->clock.target_rate.denom; } else { duration = 1024; rate = 48000; } /* In freewheel mode, graph cycles are run as fast as possible, * especially if the "freewheel.wait" period is 0. In such a case, * as soon as the mainloop encounters the scheduled timer timeout, * it will execute it immediately. Since it is not possible to * measure how long it takes the mainloop to do that, it is not * possible to rely on this->next_time as the nsec value in * freewheel mode (this->next_time does not factor in the mainloop * invocation time mentioned earlier). Instead, sample the current * monotonic time when freewheel mode is active, to account for * that invocation time. * * Also, if the timer was canceled, the graph cycle needs to be * rescheduled, and it cannot be assumed that the this->next_time * and this->clock->position values are correct anymore. (Timer * cancellations happen when the realtime clock is being used by * this driver and the user modified the realtime clock for example.) */ if (this->props.freewheel || SPA_UNLIKELY(timer_was_canceled)) nsec = gettime_nsec(this, this->timer_clockid); else nsec = this->next_time; /* "tracking" means that the driver is following a clock that is not * usable by timerfd. It is an entirely separate clock, for example, * a network interface PHC. If tracking is true, timer_clockid is * always the monotonic clock, and this->props.clock_id is that entirely * separate clock. If tracking is false, then this->props.clock_id * equals timer_clockid, so "nsec" can directly be used as the current * driver clock time in that case. */ if (this->tracking) current_time = gettime_nsec(this, this->props.clock_id); else current_time = nsec; current_position = scale_u64(current_time, rate, SPA_NSEC_PER_SEC); if ((this->last_time == 0) || SPA_UNLIKELY(timer_was_canceled)) { spa_dll_set_bw(&this->dll, SPA_DLL_BW_MIN, duration, rate); this->max_error = rate * MAX_ERROR_MS / 1000; this->max_resync = rate * this->props.resync_ms / 1000; position = current_position; /* If the timer was canceled, then it is assumed that a * discontinuity occurred. Accumulated nsec_offset values * cannot be relied upon anymore, and need to be reset. * Also, base_time is set back to 0 to make sure the log line * further below (which prints current stats) continues to * be printed. (If for example the clock was set to an * earlier time, then the base_time might contain a future * timestamp that the clock won't reach for a long while.) */ if (timer_was_canceled) { this->base_time = 0; this->nsec_offset.offset = get_nsec_offset(this, NULL); this->nsec_offset.err = 0; } } else if (SPA_LIKELY(this->clock)) { position = this->clock->position + this->clock->duration; } else { position = current_position; } this->last_time = current_time; if (this->props.freewheel) { corr = 1.0; this->next_time = nsec + this->props.freewheel_wait * SPA_NSEC_PER_SEC; } else if (this->tracking) { /* check the elapsed time of the other clock against * the graph clock elapsed time, feed this error into the * dll and adjust the timeout of our MONOTONIC clock. */ err = (double)position - (double)current_position; if (fabs(err) > this->max_error) { if (fabs(err) > this->max_resync) { spa_log_warn(this->log, "err %f > max_resync %f, resetting", err, this->max_resync); spa_dll_set_bw(&this->dll, SPA_DLL_BW_MIN, duration, rate); position = current_position; err = 0.0; } else { err = SPA_CLAMPD(err, -this->max_error, this->max_error); } } corr = spa_dll_update(&this->dll, err); this->next_time = (uint64_t)(nsec + duration / corr * 1e9 / rate); } else { corr = 1.0; this->next_time = scale_u64(position + duration, SPA_NSEC_PER_SEC, rate); } if (SPA_UNLIKELY((this->next_time - this->base_time) > BW_PERIOD)) { this->base_time = this->next_time; spa_log_debug(this->log, "%p: rate:%f " "bw:%f dur:%"PRIu64" max:%f drift:%f", this, corr, this->dll.bw, duration, this->max_error, err); } if (SPA_LIKELY(this->clock)) { uint64_t nsec_now = nsec; int64_t nsec_offset = smooth_nsec_offset(this, &nsec_now); this->clock->nsec = SPA_MIN(nsec + nsec_offset, nsec_now); this->clock->rate = this->clock->target_rate; this->clock->position = position; this->clock->duration = duration; this->clock->delay = 0; this->clock->rate_diff = corr; this->clock->next_nsec = this->next_time + nsec_offset; SPA_FLAG_UPDATE(this->clock->flags, SPA_IO_CLOCK_FLAG_DISCONT, timer_was_canceled); } spa_node_call_ready(&this->callbacks, SPA_STATUS_HAVE_DATA | SPA_STATUS_NEED_DATA); set_timeout(this, this->next_time); } static int do_start(struct impl *this) { if (this->started) return 0; this->following = is_following(this); this->started = true; this->last_time = 0; spa_loop_locked(this->data_loop, do_set_timers, 0, NULL, 0, this); return 0; } static int do_stop(struct impl *this) { if (!this->started) return 0; this->started = false; spa_loop_locked(this->data_loop, do_set_timers, 0, NULL, 0, this); return 0; } static int impl_node_send_command(void *object, const struct spa_command *command) { struct impl *this = object; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(command != NULL, -EINVAL); switch (SPA_NODE_COMMAND_ID(command)) { case SPA_NODE_COMMAND_Start: do_start(this); break; case SPA_NODE_COMMAND_Suspend: case SPA_NODE_COMMAND_Pause: do_stop(this); break; default: return -ENOTSUP; } return 0; } static void emit_node_info(struct impl *this, bool full) { uint64_t old = full ? this->info.change_mask : 0; if (full) this->info.change_mask = this->info_all; if (this->info.change_mask) { struct spa_dict_item items[3]; items[0] = SPA_DICT_ITEM_INIT(SPA_KEY_NODE_DRIVER, "true"); items[1] = SPA_DICT_ITEM_INIT("clock.id", clock_id_to_name(this->props.clock_id)); items[2] = SPA_DICT_ITEM_INIT("clock.name", this->props.clock_name); this->info.props = &SPA_DICT_INIT(items, 3); spa_node_emit_info(&this->hooks, &this->info); this->info.change_mask = old; } } static int impl_node_add_listener(void *object, struct spa_hook *listener, const struct spa_node_events *events, void *data) { struct impl *this = object; struct spa_hook_list save; spa_return_val_if_fail(this != NULL, -EINVAL); spa_hook_list_isolate(&this->hooks, &save, listener, events, data); emit_node_info(this, true); spa_hook_list_join(&this->hooks, &save); return 0; } static int impl_node_set_callbacks(void *object, const struct spa_node_callbacks *callbacks, void *data) { struct impl *this = object; spa_return_val_if_fail(this != NULL, -EINVAL); this->callbacks = SPA_CALLBACKS_INIT(callbacks, data); return 0; } static int impl_node_process(void *object) { struct impl *this = object; spa_return_val_if_fail(this != NULL, -EINVAL); spa_log_trace(this->log, "process %d", this->props.freewheel); if (this->props.freewheel && !SPA_FLAG_IS_SET(this->position->clock.flags, SPA_IO_CLOCK_FLAG_XRUN_RECOVER)) { this->next_time = gettime_nsec(this, this->timer_clockid); set_timeout(this, this->next_time); } return SPA_STATUS_HAVE_DATA | SPA_STATUS_NEED_DATA; } static int impl_node_enum_params(void *object, int seq, uint32_t id, uint32_t start, uint32_t num, const struct spa_pod *filter) { struct impl *this = object; struct spa_pod *param; struct spa_pod_builder b = { 0 }; uint8_t buffer[4096]; struct spa_result_node_params result; uint32_t count = 0; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(num != 0, -EINVAL); result.id = id; result.next = start; next: result.index = result.next++; spa_pod_builder_init(&b, buffer, sizeof(buffer)); switch (id) { case SPA_PARAM_PropInfo: { struct props *p = &this->props; switch (result.index) { case 0: param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_PropInfo, id, SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_clockId), SPA_PROP_INFO_description, SPA_POD_String("The clock id (monotonic, realtime, etc.)"), SPA_PROP_INFO_type, SPA_POD_String(clock_id_to_name(p->clock_id))); break; case 1: param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_PropInfo, id, SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_clockDevice), SPA_PROP_INFO_description, SPA_POD_String("The clock device (eg. /dev/ptp0)"), SPA_PROP_INFO_type, SPA_POD_Stringn(p->clock_device, sizeof(p->clock_device))); break; case 2: param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_PropInfo, id, SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_clockInterface), SPA_PROP_INFO_description, SPA_POD_String("The clock network interface (eg. eth0)"), SPA_PROP_INFO_type, SPA_POD_Stringn(p->clock_interface, sizeof(p->clock_interface))); break; default: return 0; } break; } case SPA_PARAM_Props: { struct props *p = &this->props; switch (result.index) { case 0: param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_Props, id, SPA_PROP_clockId, SPA_POD_String(clock_id_to_name(p->clock_id)) ); break; case 1: param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_Props, id, SPA_PROP_clockDevice, SPA_POD_Stringn(p->clock_device, sizeof(p->clock_device)) ); break; case 2: param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_Props, id, SPA_PROP_clockInterface, SPA_POD_Stringn(p->clock_interface, sizeof(p->clock_interface)) ); break; default: return 0; } break; } default: return -ENOENT; } if (spa_pod_filter(&b, &result.param, param, filter) < 0) goto next; spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); if (++count != num) goto next; return 0; } static int get_phc_index(struct spa_system *s, const char *name) { #ifdef ETHTOOL_GET_TS_INFO struct ethtool_ts_info info = {0}; struct ifreq ifr = {0}; int fd, err; info.cmd = ETHTOOL_GET_TS_INFO; strncpy(ifr.ifr_name, name, IFNAMSIZ - 1); ifr.ifr_data = (char *) &info; fd = socket(AF_INET, SOCK_DGRAM, 0); if (fd < 0) return -errno; err = spa_system_ioctl(s, fd, SIOCETHTOOL, &ifr); close(fd); if (err < 0) return -errno; return info.phc_index; #else return -ENOTSUP; #endif } static bool parse_clock_id(struct impl *this, const char *s) { int id = clock_name_to_id(s); if (id == -1) { spa_log_info(this->log, "unknown clock id '%s'", s); return false; } this->props.clock_id = id; if (this->clock_fd >= 0) { close(this->clock_fd); this->clock_fd = -1; } return true; } static bool parse_clock_device(struct impl *this, const char *s) { int fd = open(s, O_RDONLY); if (fd == -1) { spa_log_info(this->log, "failed to open clock device '%s': %m", s); return false; } if (this->clock_fd >= 0) { close(this->clock_fd); } this->clock_fd = fd; this->props.clock_id = FD_TO_CLOCKID(this->clock_fd); return true; } static bool parse_clock_interface(struct impl *this, const char *s) { int phc_index = get_phc_index(this->data_system, s); if (phc_index < 0) { spa_log_info(this->log, "failed to get phc device index for interface '%s': %s", s, spa_strerror(phc_index)); return false; } else { char dev[19]; spa_scnprintf(dev, sizeof(dev), "/dev/ptp%d", phc_index); if (!parse_clock_device(this, dev)) { spa_log_info(this->log, "failed to open clock device '%s' " "for interface '%s': %m", dev, s); return false; } } return true; } static void ensure_clock_name(struct impl *this) { struct props *p = &this->props; if (p->clock_name[0] == '\0') { const char *name = clock_id_to_name(p->clock_id); if (p->clock_device[0]) name = p->clock_device; if (p->clock_interface[0]) name = p->clock_interface; spa_scnprintf(p->clock_name, sizeof(p->clock_name), "%s.%s", DEFAULT_CLOCK_PREFIX, name); } } static int impl_node_set_param(void *object, uint32_t id, uint32_t flags, const struct spa_pod *param) { struct impl *this = object; spa_return_val_if_fail(this != NULL, -EINVAL); switch (id) { case SPA_PARAM_Props: { struct props *p = &this->props; bool notify = false; char buffer[CLOCK_NAME_MAX]; int count; if (param == NULL) { return 0; } /* Note that the length passed to SPA_POD_OPT_Stringn() also * includes room for the null terminator, so the content of the * buffer variable is always guaranteed to be null terminated. */ spa_zero(buffer); count = spa_pod_parse_object(param, SPA_TYPE_OBJECT_Props, NULL, SPA_PROP_clockId, SPA_POD_OPT_Stringn(buffer, sizeof(buffer)) ); if (count && parse_clock_id(this, buffer)) { reset_props_strings(p); notify = true; } spa_zero(buffer); count = spa_pod_parse_object(param, SPA_TYPE_OBJECT_Props, NULL, SPA_PROP_clockDevice, SPA_POD_OPT_Stringn(buffer, sizeof(buffer)) ); if (count && parse_clock_device(this, buffer)) { reset_props_strings(p); strncpy(p->clock_device, buffer, sizeof(p->clock_device)); notify = true; } spa_zero(buffer); count = spa_pod_parse_object(param, SPA_TYPE_OBJECT_Props, NULL, SPA_PROP_clockInterface, SPA_POD_OPT_Stringn(buffer, sizeof(buffer)) ); if (count && parse_clock_interface(this, buffer)) { reset_props_strings(p); strncpy(p->clock_interface, buffer, sizeof(p->clock_interface)); notify = true; } if (notify) { ensure_clock_name(this); spa_log_info(this->log, "%p: setting clock to '%s'", this, p->clock_name); if (this->started) { do_stop(this); do_start(this); } emit_node_info(this, true); } break; } default: return -ENOENT; break; } return 0; } static const struct spa_node_methods impl_node = { SPA_VERSION_NODE_METHODS, .add_listener = impl_node_add_listener, .set_callbacks = impl_node_set_callbacks, .enum_params = impl_node_enum_params, .set_param = impl_node_set_param, .set_io = impl_node_set_io, .send_command = impl_node_send_command, .process = impl_node_process, }; static int impl_get_interface(struct spa_handle *handle, const char *type, void **interface) { struct impl *this; spa_return_val_if_fail(handle != NULL, -EINVAL); spa_return_val_if_fail(interface != NULL, -EINVAL); this = (struct impl *) handle; if (spa_streq(type, SPA_TYPE_INTERFACE_Node)) *interface = &this->node; else return -ENOENT; return 0; } static int do_remove_timer(struct spa_loop *loop, bool async, uint32_t seq, const void *data, size_t size, void *user_data) { struct impl *this = user_data; spa_loop_remove_source(this->data_loop, &this->timer_source); return 0; } static int impl_clear(struct spa_handle *handle) { struct impl *this; spa_return_val_if_fail(handle != NULL, -EINVAL); this = (struct impl *) handle; spa_loop_locked(this->data_loop, do_remove_timer, 0, NULL, 0, this); spa_system_close(this->data_system, this->timer_source.fd); if (this->clock_fd != -1) close(this->clock_fd); return 0; } static size_t impl_get_size(const struct spa_handle_factory *factory, const struct spa_dict *params) { return sizeof(struct impl); } static int impl_init(const struct spa_handle_factory *factory, struct spa_handle *handle, const struct spa_dict *info, const struct spa_support *support, uint32_t n_support) { struct impl *this; uint32_t i; spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(handle != NULL, -EINVAL); handle->get_interface = impl_get_interface; handle->clear = impl_clear; this = (struct impl *) handle; this->log = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log); this->data_loop = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_DataLoop); this->data_system = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_DataSystem); this->clock_fd = -1; spa_dll_init(&this->dll); if (this->data_loop == NULL) { spa_log_error(this->log, "a data_loop is needed"); return -EINVAL; } if (this->data_system == NULL) { spa_log_error(this->log, "a data_system is needed"); return -EINVAL; } spa_hook_list_init(&this->hooks); this->node.iface = SPA_INTERFACE_INIT( SPA_TYPE_INTERFACE_Node, SPA_VERSION_NODE, &impl_node, this); this->info_all = SPA_NODE_CHANGE_MASK_FLAGS | SPA_NODE_CHANGE_MASK_PROPS | SPA_NODE_CHANGE_MASK_PARAMS; this->info = SPA_NODE_INFO_INIT(); this->info.max_input_ports = 0; this->info.max_output_ports = 0; this->info.flags = SPA_NODE_FLAG_RT; this->params[NODE_PropInfo] = SPA_PARAM_INFO(SPA_PARAM_PropInfo, SPA_PARAM_INFO_READ); this->params[NODE_Props] = SPA_PARAM_INFO(SPA_PARAM_Props, SPA_PARAM_INFO_READWRITE); this->info.params = this->params; this->info.n_params = N_NODE_PARAMS; reset_props(&this->props); for (i = 0; info && i < info->n_items; i++) { const char *k = info->items[i].key; const char *s = info->items[i].value; if (spa_streq(k, "node.freewheel")) { this->props.freewheel = spa_atob(s); } else if (spa_streq(k, "clock.name") && this->clock_fd < 0) { spa_scnprintf(this->props.clock_name, sizeof(this->props.clock_name), "%s", s); } else if (spa_streq(k, "clock.id") && this->clock_fd < 0) { if (parse_clock_id(this, s)) reset_props_strings(&this->props); } else if (spa_streq(k, "clock.device")) { if (parse_clock_device(this, s)) { reset_props_strings(&this->props); strncpy(this->props.clock_device, s, sizeof(this->props.clock_device)-1); } } else if (spa_streq(k, "clock.interface") && this->clock_fd < 0) { if (parse_clock_interface(this, s)) { reset_props_strings(&this->props); strncpy(this->props.clock_interface, s, sizeof(this->props.clock_interface)-1); } } else if (spa_streq(k, "freewheel.wait")) { this->props.freewheel_wait = atoi(s); } else if (spa_streq(k, "resync.ms")) { this->props.resync_ms = (float)atof(s); } } if (this->props.clock_name[0] == '\0') { spa_scnprintf(this->props.clock_name, sizeof(this->props.clock_name), "%s.%s", DEFAULT_CLOCK_PREFIX, clock_id_to_name(this->props.clock_id)); } ensure_clock_name(this); this->tracking = !clock_for_timerfd(this->props.clock_id); this->timer_clockid = this->tracking ? CLOCK_MONOTONIC : this->props.clock_id; this->max_error = 128; this->nsec_offset.offset = get_nsec_offset(this, NULL); this->nsec_offset.err = 0; this->timer_source.func = on_timeout; this->timer_source.data = this; this->timer_source.fd = spa_system_timerfd_create(this->data_system, this->timer_clockid, SPA_FD_CLOEXEC | SPA_FD_NONBLOCK); this->timer_source.mask = SPA_IO_IN; this->timer_source.rmask = 0; this->timerspec.it_value.tv_sec = 0; this->timerspec.it_value.tv_nsec = 0; this->timerspec.it_interval.tv_sec = 0; this->timerspec.it_interval.tv_nsec = 0; spa_loop_add_source(this->data_loop, &this->timer_source); return 0; } static const struct spa_interface_info impl_interfaces[] = { {SPA_TYPE_INTERFACE_Node,}, }; static int impl_enum_interface_info(const struct spa_handle_factory *factory, const struct spa_interface_info **info, uint32_t *index) { spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(info != NULL, -EINVAL); spa_return_val_if_fail(index != NULL, -EINVAL); switch (*index) { case 0: *info = &impl_interfaces[*index]; break; default: return 0; } (*index)++; return 1; } const struct spa_handle_factory spa_support_node_driver_factory = { SPA_VERSION_HANDLE_FACTORY, SPA_NAME_SUPPORT_NODE_DRIVER, NULL, impl_get_size, impl_init, impl_enum_interface_info, }; pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/support/null-audio-sink.c000066400000000000000000000604351511204443500276450ustar00rootroot00000000000000/* Spa */ /* SPDX-FileCopyrightText: Copyright © 2020 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #undef SPA_LOG_TOPIC_DEFAULT #define SPA_LOG_TOPIC_DEFAULT &log_topic SPA_LOG_TOPIC_DEFINE_STATIC(log_topic, "spa.null-audio-sink"); #define DEFAULT_CLOCK_NAME "clock.system.monotonic" #define MAX_CHANNELS SPA_AUDIO_MAX_CHANNELS struct props { uint32_t format; uint32_t channels; uint32_t rate; uint32_t pos[MAX_CHANNELS]; char clock_name[64]; unsigned int debug:1; unsigned int driver:1; }; static void reset_props(struct props *props) { props->format = 0; props->channels = 0; props->rate = 0; strncpy(props->clock_name, DEFAULT_CLOCK_NAME, sizeof(props->clock_name)); props->debug = false; props->driver = true; } #define DEFAULT_CHANNELS 2 #define DEFAULT_RATE 48000 #define MAX_BUFFERS 16 #define MAX_PORTS 1 struct buffer { uint32_t id; #define BUFFER_FLAG_OUT (1<<0) uint32_t flags; struct spa_buffer *outbuf; }; struct impl; struct port { uint64_t info_all; struct spa_port_info info; struct spa_param_info params[5]; struct spa_io_buffers *io; bool have_format; struct spa_audio_info current_format; uint32_t blocks; size_t bpf; struct buffer buffers[MAX_BUFFERS]; uint32_t n_buffers; }; struct impl { struct spa_handle handle; struct spa_node node; struct spa_log *log; struct spa_loop *data_loop; struct spa_system *data_system; uint32_t quantum_limit; struct props props; uint64_t info_all; struct spa_node_info info; struct spa_param_info params[2]; struct spa_io_clock *clock; struct spa_io_position *position; struct spa_hook_list hooks; struct spa_callbacks callbacks; struct port port; unsigned int started:1; unsigned int following:1; struct spa_source timer_source; struct itimerspec timerspec; uint64_t next_time; }; #define CHECK_PORT(this,d,p) ((d) == SPA_DIRECTION_INPUT && (p) < MAX_PORTS) static int impl_node_enum_params(void *object, int seq, uint32_t id, uint32_t start, uint32_t num, const struct spa_pod *filter) { struct impl *this = object; struct spa_pod *param; struct spa_pod_builder b = { 0 }; uint8_t buffer[1024]; struct spa_result_node_params result; uint32_t count = 0; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(num != 0, -EINVAL); result.id = id; result.next = start; next: result.index = result.next++; spa_pod_builder_init(&b, buffer, sizeof(buffer)); switch (id) { case SPA_PARAM_IO: { switch (result.index) { case 0: param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamIO, id, SPA_PARAM_IO_id, SPA_POD_Id(SPA_IO_Clock), SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_clock))); break; case 1: param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamIO, id, SPA_PARAM_IO_id, SPA_POD_Id(SPA_IO_Position), SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_position))); break; default: return 0; } break; } default: return -ENOENT; } if (spa_pod_filter(&b, &result.param, param, filter) < 0) goto next; spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); if (++count != num) goto next; return 0; } static void set_timeout(struct impl *this, uint64_t next_time) { spa_log_trace(this->log, "set timeout %"PRIu64, next_time); this->timerspec.it_value.tv_sec = next_time / SPA_NSEC_PER_SEC; this->timerspec.it_value.tv_nsec = next_time % SPA_NSEC_PER_SEC; spa_system_timerfd_settime(this->data_system, this->timer_source.fd, SPA_FD_TIMER_ABSTIME, &this->timerspec, NULL); } static int set_timers(struct impl *this) { struct timespec now; int res; if ((res = spa_system_clock_gettime(this->data_system, CLOCK_MONOTONIC, &now)) < 0) return res; this->next_time = SPA_TIMESPEC_TO_NSEC(&now); if (this->following || !this->started) { set_timeout(this, 0); } else { set_timeout(this, this->next_time); } return 0; } static inline bool is_following(struct impl *this) { return this->position && this->clock && this->position->clock.id != this->clock->id; } static int do_set_timers(struct spa_loop *loop, bool async, uint32_t seq, const void *data, size_t size, void *user_data) { struct impl *this = user_data; set_timers(this); return 0; } static int reassign_follower(struct impl *this) { bool following; if (!this->started) return 0; following = is_following(this); if (following != this->following) { spa_log_debug(this->log, "%p: reassign follower %d->%d", this, this->following, following); this->following = following; spa_loop_locked(this->data_loop, do_set_timers, 0, NULL, 0, this); } return 0; } static int impl_node_set_io(void *object, uint32_t id, void *data, size_t size) { struct impl *this = object; spa_return_val_if_fail(this != NULL, -EINVAL); switch (id) { case SPA_IO_Clock: if (size > 0 && size < sizeof(struct spa_io_clock)) return -EINVAL; this->clock = data; if (this->clock != NULL) { spa_scnprintf(this->clock->name, sizeof(this->clock->name), "%s", this->props.clock_name); } break; case SPA_IO_Position: this->position = data; break; default: return -ENOENT; } reassign_follower(this); return 0; } static void on_timeout(struct spa_source *source) { struct impl *this = source->data; uint64_t expirations, nsec, duration = 10; uint32_t rate; int res; spa_log_trace(this->log, "timeout"); if ((res = spa_system_timerfd_read(this->data_system, this->timer_source.fd, &expirations)) < 0) { if (res != -EAGAIN) spa_log_error(this->log, "%p: timerfd error: %s", this, spa_strerror(res)); return; } nsec = this->next_time; if (SPA_LIKELY(this->position)) { duration = this->position->clock.target_duration; rate = this->position->clock.target_rate.denom; } else { duration = 1024; rate = 48000; } this->next_time = nsec + duration * SPA_NSEC_PER_SEC / rate; if (SPA_LIKELY(this->clock)) { this->clock->nsec = nsec; this->clock->rate = this->clock->target_rate; this->clock->position += this->clock->duration; this->clock->duration = duration; this->clock->delay = 0; this->clock->rate_diff = 1.0; this->clock->next_nsec = this->next_time; } spa_node_call_ready(&this->callbacks, SPA_STATUS_NEED_DATA); set_timeout(this, this->next_time); } static int do_start(struct impl *this) { if (this->started) return 0; this->following = is_following(this); this->started = true; spa_loop_locked(this->data_loop, do_set_timers, 0, NULL, 0, this); return 0; } static int do_stop(struct impl *this) { if (!this->started) return 0; this->started = false; spa_loop_locked(this->data_loop, do_set_timers, 0, NULL, 0, this); return 0; } static int impl_node_send_command(void *object, const struct spa_command *command) { struct impl *this = object; struct port *port; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(command != NULL, -EINVAL); port = &this->port; switch (SPA_NODE_COMMAND_ID(command)) { case SPA_NODE_COMMAND_Start: { if (!port->have_format) return -EIO; if (port->n_buffers == 0) return -EIO; do_start(this); break; } case SPA_NODE_COMMAND_Suspend: case SPA_NODE_COMMAND_Pause: do_stop(this); break; default: return -ENOTSUP; } return 0; } static void emit_node_info(struct impl *this, bool full) { uint64_t old = full ? this->info.change_mask : 0; if (full) this->info.change_mask = this->info_all; if (this->info.change_mask) { const struct spa_dict_item node_info_items[] = { { SPA_KEY_NODE_DRIVER, this->props.driver ? "true" : "false" }, }; this->info.props = &SPA_DICT_INIT_ARRAY(node_info_items); spa_node_emit_info(&this->hooks, &this->info); this->info.change_mask = old; } } static void emit_port_info(struct impl *this, struct port *port, bool full) { uint64_t old = full ? port->info.change_mask : 0; if (full) port->info.change_mask = port->info_all; if (port->info.change_mask) { spa_node_emit_port_info(&this->hooks, SPA_DIRECTION_INPUT, 0, &port->info); port->info.change_mask = old; } } static int impl_node_add_listener(void *object, struct spa_hook *listener, const struct spa_node_events *events, void *data) { struct impl *this = object; struct spa_hook_list save; spa_return_val_if_fail(this != NULL, -EINVAL); spa_hook_list_isolate(&this->hooks, &save, listener, events, data); emit_node_info(this, true); emit_port_info(this, &this->port, true); spa_hook_list_join(&this->hooks, &save); return 0; } static int impl_node_set_callbacks(void *object, const struct spa_node_callbacks *callbacks, void *data) { struct impl *this = object; spa_return_val_if_fail(this != NULL, -EINVAL); this->callbacks = SPA_CALLBACKS_INIT(callbacks, data); return 0; } static int port_enum_formats(struct impl *this, enum spa_direction direction, uint32_t port_id, uint32_t index, struct spa_pod **param, struct spa_pod_builder *builder) { struct spa_pod_frame f[1]; switch (index) { case 0: spa_pod_builder_push_object(builder, &f[0], SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat); spa_pod_builder_add(builder, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_audio), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), 0); if (this->props.format != 0) { spa_pod_builder_add(builder, SPA_FORMAT_AUDIO_format, SPA_POD_Id(this->props.format), 0); } else { spa_pod_builder_add(builder, SPA_FORMAT_AUDIO_format, SPA_POD_CHOICE_ENUM_Id(3, SPA_AUDIO_FORMAT_F32P, SPA_AUDIO_FORMAT_F32P, SPA_AUDIO_FORMAT_F32), 0); } if (this->props.rate != 0) { spa_pod_builder_add(builder, SPA_FORMAT_AUDIO_rate, SPA_POD_Int(this->props.rate), 0); } else { spa_pod_builder_add(builder, SPA_FORMAT_AUDIO_rate, SPA_POD_CHOICE_RANGE_Int(DEFAULT_RATE, 1, INT32_MAX), 0); } if (this->props.channels != 0) { spa_pod_builder_add(builder, SPA_FORMAT_AUDIO_channels, SPA_POD_Int(this->props.channels), 0); } else { spa_pod_builder_add(builder, SPA_FORMAT_AUDIO_channels, SPA_POD_CHOICE_RANGE_Int(DEFAULT_CHANNELS, 1, INT32_MAX), 0); } if (this->props.channels != 0) { spa_pod_builder_prop(builder, SPA_FORMAT_AUDIO_position, 0); spa_pod_builder_array(builder, sizeof(uint32_t), SPA_TYPE_Id, this->props.channels, this->props.pos); } *param = spa_pod_builder_pop(builder, &f[0]); break; default: return 0; } return 1; } static int impl_node_port_enum_params(void *object, int seq, enum spa_direction direction, uint32_t port_id, uint32_t id, uint32_t start, uint32_t num, const struct spa_pod *filter) { struct impl *this = object; struct port *port; struct spa_pod_builder b = { 0 }; uint8_t buffer[1024]; struct spa_pod *param; struct spa_result_node_params result; uint32_t count = 0; int res; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(num != 0, -EINVAL); spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); port = &this->port; result.id = id; result.next = start; next: result.index = result.next++; spa_pod_builder_init(&b, buffer, sizeof(buffer)); switch (id) { case SPA_PARAM_EnumFormat: if ((res = port_enum_formats(this, direction, port_id, result.index, ¶m, &b)) <= 0) return res; break; case SPA_PARAM_Format: if (!port->have_format) return -EIO; if (result.index > 0) return 0; param = spa_format_audio_raw_build(&b, id, &port->current_format.info.raw); break; case SPA_PARAM_Buffers: if (!port->have_format) return -EIO; if (result.index > 0) return 0; param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamBuffers, id, SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(1, 1, MAX_BUFFERS), SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(port->blocks), SPA_PARAM_BUFFERS_size, SPA_POD_CHOICE_RANGE_Int( this->quantum_limit * port->bpf, 16 * port->bpf, INT32_MAX), SPA_PARAM_BUFFERS_stride, SPA_POD_Int(port->bpf)); break; case SPA_PARAM_IO: switch (result.index) { case 0: param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamIO, id, SPA_PARAM_IO_id, SPA_POD_Id(SPA_IO_Buffers), SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_buffers))); break; default: return 0; } break; default: return -ENOENT; } if (spa_pod_filter(&b, &result.param, param, filter) < 0) goto next; spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); if (++count != num) goto next; return 0; } static int clear_buffers(struct impl *this, struct port *port) { if (port->n_buffers > 0) { spa_log_info(this->log, "%p: clear buffers", this); port->n_buffers = 0; this->started = false; } return 0; } static int calc_width(struct spa_audio_info *info) { switch (info->info.raw.format) { case SPA_AUDIO_FORMAT_U8: case SPA_AUDIO_FORMAT_U8P: case SPA_AUDIO_FORMAT_S8: case SPA_AUDIO_FORMAT_S8P: case SPA_AUDIO_FORMAT_ULAW: case SPA_AUDIO_FORMAT_ALAW: return 1; case SPA_AUDIO_FORMAT_S16P: case SPA_AUDIO_FORMAT_S16: case SPA_AUDIO_FORMAT_S16_OE: return 2; case SPA_AUDIO_FORMAT_S24P: case SPA_AUDIO_FORMAT_S24: case SPA_AUDIO_FORMAT_S24_OE: return 3; case SPA_AUDIO_FORMAT_F64P: case SPA_AUDIO_FORMAT_F64: case SPA_AUDIO_FORMAT_F64_OE: return 8; default: return 4; } } static int port_set_format(struct impl *this, enum spa_direction direction, uint32_t port_id, uint32_t flags, const struct spa_pod *format) { int res; struct port *port = &this->port; if (format == NULL) { port->have_format = false; clear_buffers(this, port); } else { struct spa_audio_info info = { 0 }; if ((res = spa_format_parse(format, &info.media_type, &info.media_subtype)) < 0) return res; if (info.media_type != SPA_MEDIA_TYPE_audio || info.media_subtype != SPA_MEDIA_SUBTYPE_raw) return -EINVAL; if (spa_format_audio_raw_parse(format, &info.info.raw) < 0) return -EINVAL; if (info.info.raw.rate == 0 || info.info.raw.channels == 0 || info.info.raw.channels > MAX_CHANNELS) return -EINVAL; if (this->props.format != 0) { if (this->props.format != info.info.raw.format) return -EINVAL; } else if (info.info.raw.format != SPA_AUDIO_FORMAT_F32P && info.info.raw.format != SPA_AUDIO_FORMAT_F32) { return -EINVAL; } port->bpf = calc_width(&info); if (SPA_AUDIO_FORMAT_IS_PLANAR(info.info.raw.format)) { port->blocks = info.info.raw.channels; } else { port->blocks = 1; port->bpf *= info.info.raw.channels; } port->current_format = info; port->have_format = true; } port->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; if (port->have_format) { port->info.change_mask |= SPA_PORT_CHANGE_MASK_RATE; port->info.rate = SPA_FRACTION(1, port->current_format.info.raw.rate); port->params[1] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_READWRITE); port->params[3] = SPA_PARAM_INFO(SPA_PARAM_Buffers, SPA_PARAM_INFO_READ); } else { port->params[1] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); port->params[3] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); } emit_port_info(this, port, false); return 0; } static int impl_node_port_set_param(void *object, enum spa_direction direction, uint32_t port_id, uint32_t id, uint32_t flags, const struct spa_pod *param) { struct impl *this = object; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); switch (id) { case SPA_PARAM_Format: return port_set_format(this, direction, port_id, flags, param); default: return -ENOENT; } return 0; } static int impl_node_port_use_buffers(void *object, enum spa_direction direction, uint32_t port_id, uint32_t flags, struct spa_buffer **buffers, uint32_t n_buffers) { struct impl *this = object; struct port *port; uint32_t i; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); port = &this->port; clear_buffers(this, port); if (n_buffers > 0 && !port->have_format) return -EIO; if (n_buffers > MAX_BUFFERS) return -ENOSPC; for (i = 0; i < n_buffers; i++) { struct buffer *b; struct spa_data *d = buffers[i]->datas; b = &port->buffers[i]; b->id = i; b->flags = 0; b->outbuf = buffers[i]; if (d[0].data == NULL) { spa_log_error(this->log, "%p: invalid memory on buffer %p", this, buffers[i]); return -EINVAL; } } port->n_buffers = n_buffers; return 0; } static int impl_node_port_set_io(void *object, enum spa_direction direction, uint32_t port_id, uint32_t id, void *data, size_t size) { struct impl *this = object; struct port *port; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); port = &this->port; switch (id) { case SPA_IO_Buffers: port->io = data; break; default: return -ENOENT; } return 0; } static int impl_node_process(void *object) { struct impl *this = object; struct port *port; struct spa_io_buffers *io; spa_return_val_if_fail(this != NULL, -EINVAL); port = &this->port; if ((io = port->io) == NULL) return -EIO; if (io->status != SPA_STATUS_HAVE_DATA) return io->status; if (io->buffer_id >= port->n_buffers) { io->status = -EINVAL; return io->status; } if (this->props.debug) { struct buffer *b; uint32_t i; b = &port->buffers[io->buffer_id]; for (i = 0; i < b->outbuf->n_datas; i++) { uint32_t offs, size; struct spa_data *d = b->outbuf->datas; offs = SPA_MIN(d->chunk->offset, d->maxsize); size = SPA_MIN(d->maxsize - offs, d->chunk->size); spa_debug_mem(i, SPA_PTROFF(d[i].data, offs, void), SPA_MIN(16u, size));; } } io->status = SPA_STATUS_OK; return SPA_STATUS_HAVE_DATA; } static const struct spa_node_methods impl_node = { SPA_VERSION_NODE_METHODS, .add_listener = impl_node_add_listener, .set_callbacks = impl_node_set_callbacks, .enum_params = impl_node_enum_params, .set_io = impl_node_set_io, .send_command = impl_node_send_command, .port_enum_params = impl_node_port_enum_params, .port_set_param = impl_node_port_set_param, .port_use_buffers = impl_node_port_use_buffers, .port_set_io = impl_node_port_set_io, .process = impl_node_process, }; static int impl_get_interface(struct spa_handle *handle, const char *type, void **interface) { struct impl *this; spa_return_val_if_fail(handle != NULL, -EINVAL); spa_return_val_if_fail(interface != NULL, -EINVAL); this = (struct impl *) handle; if (spa_streq(type, SPA_TYPE_INTERFACE_Node)) *interface = &this->node; else return -ENOENT; return 0; } static int do_remove_timer(struct spa_loop *loop, bool async, uint32_t seq, const void *data, size_t size, void *user_data) { struct impl *this = user_data; spa_loop_remove_source(this->data_loop, &this->timer_source); return 0; } static int impl_clear(struct spa_handle *handle) { struct impl *this; spa_return_val_if_fail(handle != NULL, -EINVAL); this = (struct impl *) handle; spa_loop_locked(this->data_loop, do_remove_timer, 0, NULL, 0, this); spa_system_close(this->data_system, this->timer_source.fd); return 0; } static size_t impl_get_size(const struct spa_handle_factory *factory, const struct spa_dict *params) { return sizeof(struct impl); } static int impl_init(const struct spa_handle_factory *factory, struct spa_handle *handle, const struct spa_dict *info, const struct spa_support *support, uint32_t n_support) { struct impl *this; struct port *port; uint32_t i; spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(handle != NULL, -EINVAL); handle->get_interface = impl_get_interface; handle->clear = impl_clear; this = (struct impl *) handle; this->log = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log); this->data_loop = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_DataLoop); this->data_system = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_DataSystem); if (this->data_loop == NULL) { spa_log_error(this->log, "a data_loop is needed"); return -EINVAL; } if (this->data_system == NULL) { spa_log_error(this->log, "a data_system is needed"); return -EINVAL; } spa_hook_list_init(&this->hooks); this->node.iface = SPA_INTERFACE_INIT( SPA_TYPE_INTERFACE_Node, SPA_VERSION_NODE, &impl_node, this); this->info_all |= SPA_NODE_CHANGE_MASK_FLAGS | SPA_NODE_CHANGE_MASK_PROPS | SPA_NODE_CHANGE_MASK_PARAMS; this->info = SPA_NODE_INFO_INIT(); this->info.max_input_ports = 1; this->info.flags = SPA_NODE_FLAG_RT; this->params[0] = SPA_PARAM_INFO(SPA_PARAM_IO, SPA_PARAM_INFO_READ); this->info.params = this->params; this->info.n_params = 1; reset_props(&this->props); port = &this->port; port->info_all = SPA_PORT_CHANGE_MASK_FLAGS | SPA_PORT_CHANGE_MASK_PARAMS; port->info = SPA_PORT_INFO_INIT(); port->info.flags = SPA_PORT_FLAG_NO_REF | SPA_PORT_FLAG_LIVE; port->params[0] = SPA_PARAM_INFO(SPA_PARAM_EnumFormat, SPA_PARAM_INFO_READ); port->params[1] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); port->params[2] = SPA_PARAM_INFO(SPA_PARAM_IO, SPA_PARAM_INFO_READ); port->params[3] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); port->info.params = port->params; port->info.n_params = 4; this->timer_source.func = on_timeout; this->timer_source.data = this; this->timer_source.fd = spa_system_timerfd_create(this->data_system, CLOCK_MONOTONIC, SPA_FD_CLOEXEC | SPA_FD_NONBLOCK); this->timer_source.mask = SPA_IO_IN; this->timer_source.rmask = 0; this->timerspec.it_value.tv_sec = 0; this->timerspec.it_value.tv_nsec = 0; this->timerspec.it_interval.tv_sec = 0; this->timerspec.it_interval.tv_nsec = 0; spa_loop_add_source(this->data_loop, &this->timer_source); for (i = 0; info && i < info->n_items; i++) { const char *k = info->items[i].key; const char *s = info->items[i].value; if (spa_streq(k, "clock.quantum-limit")) { spa_atou32(s, &this->quantum_limit, 0); } else if (spa_streq(k, SPA_KEY_AUDIO_FORMAT)) { this->props.format = spa_type_audio_format_from_short_name(s); } else if (spa_streq(k, SPA_KEY_AUDIO_CHANNELS)) { this->props.channels = atoi(s); } else if (spa_streq(k, SPA_KEY_AUDIO_RATE)) { this->props.rate = atoi(s); } else if (spa_streq(k, SPA_KEY_NODE_DRIVER)) { this->props.driver = spa_atob(s); } else if (spa_streq(k, SPA_KEY_AUDIO_POSITION)) { spa_audio_parse_position_n(s, strlen(s), this->props.pos, SPA_N_ELEMENTS(this->props.pos), &this->props.channels); } else if (spa_streq(k, SPA_KEY_AUDIO_LAYOUT)) { spa_audio_parse_layout(s, this->props.pos, SPA_N_ELEMENTS(this->props.pos), &this->props.channels); } else if (spa_streq(k, "clock.name")) { spa_scnprintf(this->props.clock_name, sizeof(this->props.clock_name), "%s", s); } } spa_log_info(this->log, "%p: initialized", this); return 0; } static const struct spa_interface_info impl_interfaces[] = { {SPA_TYPE_INTERFACE_Node,}, }; static int impl_enum_interface_info(const struct spa_handle_factory *factory, const struct spa_interface_info **info, uint32_t *index) { spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(info != NULL, -EINVAL); spa_return_val_if_fail(index != NULL, -EINVAL); switch (*index) { case 0: *info = &impl_interfaces[*index]; break; default: return 0; } (*index)++; return 1; } static const struct spa_dict_item info_items[] = { { SPA_KEY_FACTORY_AUTHOR, "Wim Taymans " }, { SPA_KEY_FACTORY_DESCRIPTION, "Consume audio samples" }, }; static const struct spa_dict info = SPA_DICT_INIT_ARRAY(info_items); const struct spa_handle_factory spa_support_null_audio_sink_factory = { SPA_VERSION_HANDLE_FACTORY, "support.null-audio-sink", &info, impl_get_size, impl_init, impl_enum_interface_info, }; pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/support/plugin.c000066400000000000000000000024421511204443500261220ustar00rootroot00000000000000/* Spa Support plugin */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include #include extern const struct spa_handle_factory spa_support_logger_factory; extern const struct spa_handle_factory spa_support_system_factory; extern const struct spa_handle_factory spa_support_cpu_factory; extern const struct spa_handle_factory spa_support_loop_factory; extern const struct spa_handle_factory spa_support_node_driver_factory; extern const struct spa_handle_factory spa_support_null_audio_sink_factory; SPA_LOG_TOPIC_ENUM_DEFINE_REGISTERED; SPA_EXPORT int spa_handle_factory_enum(const struct spa_handle_factory **factory, uint32_t *index) { spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(index != NULL, -EINVAL); switch (*index) { case 0: *factory = &spa_support_logger_factory; break; case 1: *factory = &spa_support_system_factory; break; case 2: *factory = &spa_support_cpu_factory; break; case 3: *factory = &spa_support_loop_factory; break; case 4: *factory = &spa_support_node_driver_factory; break; case 5: *factory = &spa_support_null_audio_sink_factory; break; default: return 0; } (*index)++; return 1; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/support/system.c000066400000000000000000000207621511204443500261550ustar00rootroot00000000000000/* Spa */ /* SPDX-FileCopyrightText: Copyright © 2019 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include SPA_LOG_TOPIC_DEFINE_STATIC(log_topic, "spa.system"); #undef SPA_LOG_TOPIC_DEFAULT #define SPA_LOG_TOPIC_DEFAULT &log_topic #ifndef TFD_TIMER_CANCEL_ON_SET # define TFD_TIMER_CANCEL_ON_SET (1 << 1) #endif struct impl { struct spa_handle handle; struct spa_system system; struct spa_log *log; }; static ssize_t impl_read(void *object, int fd, void *buf, size_t count) { ssize_t res = read(fd, buf, count); return res < 0 ? -errno : res; } static ssize_t impl_write(void *object, int fd, const void *buf, size_t count) { ssize_t res = write(fd, buf, count); return res < 0 ? -errno : res; } static int impl_ioctl(void *object, int fd, unsigned long request, ...) { int res; va_list ap; long arg; va_start(ap, request); arg = va_arg(ap, long); res = ioctl(fd, request, arg); va_end(ap); return res < 0 ? -errno : res; } static int impl_close(void *object, int fd) { struct impl *impl = object; int res = close(fd); spa_log_debug(impl->log, "%p: close fd:%d", impl, fd); return res < 0 ? -errno : res; } /* clock */ static int impl_clock_gettime(void *object, int clockid, struct timespec *value) { int res = clock_gettime(clockid, value); return res < 0 ? -errno : res; } static int impl_clock_getres(void *object, int clockid, struct timespec *res) { int r = clock_getres(clockid, res); return r < 0 ? -errno : r; } /* poll */ static int impl_pollfd_create(void *object, int flags) { struct impl *impl = object; int fl = 0, res; if (flags & SPA_FD_CLOEXEC) fl |= EPOLL_CLOEXEC; res = epoll_create1(fl); spa_log_debug(impl->log, "%p: new fd:%d", impl, res); return res < 0 ? -errno : res; } static int impl_pollfd_add(void *object, int pfd, int fd, uint32_t events, void *data) { struct epoll_event ep; int res; spa_zero(ep); ep.events = events; ep.data.ptr = data; res = epoll_ctl(pfd, EPOLL_CTL_ADD, fd, &ep); return res < 0 ? -errno : res; } static int impl_pollfd_mod(void *object, int pfd, int fd, uint32_t events, void *data) { struct epoll_event ep; int res; spa_zero(ep); ep.events = events; ep.data.ptr = data; res = epoll_ctl(pfd, EPOLL_CTL_MOD, fd, &ep); return res < 0 ? -errno : res; } static int impl_pollfd_del(void *object, int pfd, int fd) { int res = epoll_ctl(pfd, EPOLL_CTL_DEL, fd, NULL); return res < 0 ? -errno : res; } static int impl_pollfd_wait(void *object, int pfd, struct spa_poll_event *ev, int n_ev, int timeout) { struct epoll_event ep[n_ev]; int i, nfds; if (SPA_UNLIKELY((nfds = epoll_wait(pfd, ep, n_ev, timeout)) < 0)) return -errno; for (i = 0; i < nfds; i++) { ev[i].events = ep[i].events; ev[i].data = ep[i].data.ptr; } return nfds; } /* timers */ static int impl_timerfd_create(void *object, int clockid, int flags) { struct impl *impl = object; int fl = 0, res; if (flags & SPA_FD_CLOEXEC) fl |= TFD_CLOEXEC; if (flags & SPA_FD_NONBLOCK) fl |= TFD_NONBLOCK; res = timerfd_create(clockid, fl); spa_log_debug(impl->log, "%p: new fd:%d", impl, res); return res < 0 ? -errno : res; } static int impl_timerfd_settime(void *object, int fd, int flags, const struct itimerspec *new_value, struct itimerspec *old_value) { int fl = 0, res; if (flags & SPA_FD_TIMER_ABSTIME) fl |= TFD_TIMER_ABSTIME; if (flags & SPA_FD_TIMER_CANCEL_ON_SET) fl |= TFD_TIMER_CANCEL_ON_SET; res = timerfd_settime(fd, fl, new_value, old_value); return res < 0 ? -errno : res; } static int impl_timerfd_gettime(void *object, int fd, struct itimerspec *curr_value) { int res = timerfd_gettime(fd, curr_value); return res < 0 ? -errno : res; } static int impl_timerfd_read(void *object, int fd, uint64_t *expirations) { if (read(fd, expirations, sizeof(uint64_t)) != sizeof(uint64_t)) return -errno; return 0; } /* events */ static int impl_eventfd_create(void *object, int flags) { struct impl *impl = object; int fl = 0, res, err; if (flags & SPA_FD_CLOEXEC) fl |= EFD_CLOEXEC; if (flags & SPA_FD_NONBLOCK) fl |= EFD_NONBLOCK; if (flags & SPA_FD_EVENT_SEMAPHORE) fl |= EFD_SEMAPHORE; res = eventfd(0, fl); err = -errno; /* save errno in case it is overwritten before return */ spa_log_debug(impl->log, "%p: new fd:%d", impl, res); return res < 0 ? err : res; } static int impl_eventfd_write(void *object, int fd, uint64_t count) { if (write(fd, &count, sizeof(uint64_t)) != sizeof(uint64_t)) return -errno; return 0; } static int impl_eventfd_read(void *object, int fd, uint64_t *count) { if (read(fd, count, sizeof(uint64_t)) != sizeof(uint64_t)) return -errno; return 0; } /* signals */ static int impl_signalfd_create(void *object, int signal, int flags) { struct impl *impl = object; sigset_t mask; int res, fl = 0; if (flags & SPA_FD_CLOEXEC) fl |= SFD_CLOEXEC; if (flags & SPA_FD_NONBLOCK) fl |= SFD_NONBLOCK; sigemptyset(&mask); sigaddset(&mask, signal); res = signalfd(-1, &mask, fl); sigprocmask(SIG_BLOCK, &mask, NULL); spa_log_debug(impl->log, "%p: new fd:%d", impl, res); return res < 0 ? -errno : res; } static int impl_signalfd_read(void *object, int fd, int *signal) { struct signalfd_siginfo signal_info; int len; len = read(fd, &signal_info, sizeof signal_info); if (!(len == -1 && errno == EAGAIN) && len != sizeof signal_info) return -errno; *signal = signal_info.ssi_signo; return 0; } static const struct spa_system_methods impl_system = { SPA_VERSION_SYSTEM_METHODS, .read = impl_read, .write = impl_write, .ioctl = impl_ioctl, .close = impl_close, .clock_gettime = impl_clock_gettime, .clock_getres = impl_clock_getres, .pollfd_create = impl_pollfd_create, .pollfd_add = impl_pollfd_add, .pollfd_mod = impl_pollfd_mod, .pollfd_del = impl_pollfd_del, .pollfd_wait = impl_pollfd_wait, .timerfd_create = impl_timerfd_create, .timerfd_settime = impl_timerfd_settime, .timerfd_gettime = impl_timerfd_gettime, .timerfd_read = impl_timerfd_read, .eventfd_create = impl_eventfd_create, .eventfd_write = impl_eventfd_write, .eventfd_read = impl_eventfd_read, .signalfd_create = impl_signalfd_create, .signalfd_read = impl_signalfd_read, }; static int impl_get_interface(struct spa_handle *handle, const char *type, void **interface) { struct impl *impl; spa_return_val_if_fail(handle != NULL, -EINVAL); spa_return_val_if_fail(interface != NULL, -EINVAL); impl = (struct impl *) handle; if (spa_streq(type, SPA_TYPE_INTERFACE_System)) *interface = &impl->system; else return -ENOENT; return 0; } static int impl_clear(struct spa_handle *handle) { spa_return_val_if_fail(handle != NULL, -EINVAL); return 0; } static size_t impl_get_size(const struct spa_handle_factory *factory, const struct spa_dict *params) { return sizeof(struct impl); } static int impl_init(const struct spa_handle_factory *factory, struct spa_handle *handle, const struct spa_dict *info, const struct spa_support *support, uint32_t n_support) { struct impl *impl; spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(handle != NULL, -EINVAL); handle->get_interface = impl_get_interface; handle->clear = impl_clear; impl = (struct impl *) handle; impl->system.iface = SPA_INTERFACE_INIT( SPA_TYPE_INTERFACE_System, SPA_VERSION_SYSTEM, &impl_system, impl); impl->log = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log); spa_log_topic_init(impl->log, &log_topic); spa_log_debug(impl->log, "%p: initialized", impl); return 0; } static const struct spa_interface_info impl_interfaces[] = { {SPA_TYPE_INTERFACE_System,}, }; static int impl_enum_interface_info(const struct spa_handle_factory *factory, const struct spa_interface_info **info, uint32_t *index) { spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(info != NULL, -EINVAL); spa_return_val_if_fail(index != NULL, -EINVAL); if (*index >= SPA_N_ELEMENTS(impl_interfaces)) return 0; *info = &impl_interfaces[(*index)++]; return 1; } const struct spa_handle_factory spa_support_system_factory = { SPA_VERSION_HANDLE_FACTORY, SPA_NAME_SUPPORT_SYSTEM, NULL, impl_get_size, impl_init, impl_enum_interface_info }; pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/test/000077500000000000000000000000001511204443500237215ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/test/fakesink.c000066400000000000000000000457611511204443500256750ustar00rootroot00000000000000/* Spa */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #undef SPA_LOG_TOPIC_DEFAULT #define SPA_LOG_TOPIC_DEFAULT &log_topic SPA_LOG_TOPIC_DEFINE_STATIC(log_topic, "spa.fakesink"); struct props { bool live; }; #define MAX_BUFFERS 16 #define MAX_PORTS 1 struct buffer { uint32_t id; struct spa_buffer *outbuf; bool outstanding; struct spa_meta_header *h; struct spa_list link; }; struct port { uint64_t info_all; struct spa_port_info info; struct spa_param_info params[5]; struct spa_io_buffers *io; bool have_format; uint8_t format_buffer[1024]; struct buffer buffers[MAX_BUFFERS]; uint32_t n_buffers; struct spa_list ready; }; struct impl { struct spa_handle handle; struct spa_node node; struct spa_log *log; struct spa_loop *data_loop; struct spa_system *data_system; uint64_t info_all; struct spa_node_info info; struct spa_param_info params[1]; struct props props; struct spa_hook_list hooks; struct spa_callbacks callbacks; struct spa_source timer_source; struct itimerspec timerspec; bool started; uint64_t start_time; uint64_t elapsed_time; uint64_t buffer_count; struct port port; }; #define CHECK_PORT(this,d,p) ((d) == SPA_DIRECTION_INPUT && (p) < MAX_PORTS) #define DEFAULT_LIVE false static void reset_props(struct impl *this, struct props *props) { props->live = DEFAULT_LIVE; } static int impl_node_enum_params(void *object, int seq, uint32_t id, uint32_t start, uint32_t num, const struct spa_pod *filter) { struct impl *this = object; struct spa_pod_builder b = { 0 }; uint8_t buffer[1024]; struct spa_pod *param; struct spa_result_node_params result; uint32_t count = 0; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(num != 0, -EINVAL); result.id = id; result.next = start; next: result.index = result.next++; spa_pod_builder_init(&b, buffer, sizeof(buffer)); switch (id) { case SPA_PARAM_Props: if (result.index > 0) return 0; param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_Props, id, SPA_PROP_live, SPA_POD_Bool(this->props.live)); break; default: return -ENOENT; } if (spa_pod_filter(&b, &result.param, param, filter) < 0) goto next; spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); if (++count != num) goto next; return 0; } static int impl_node_set_io(void *object, uint32_t id, void *data, size_t size) { return -ENOTSUP; } static int impl_node_set_param(void *object, uint32_t id, uint32_t flags, const struct spa_pod *param) { struct impl *this = object; spa_return_val_if_fail(this != NULL, -EINVAL); switch (id) { case SPA_PARAM_Props: { struct port *port = &this->port; if (param == NULL) { reset_props(this, &this->props); return 0; } spa_pod_parse_object(param, SPA_TYPE_OBJECT_Props, NULL, SPA_PROP_live, SPA_POD_OPT_Bool(&this->props.live)); if (this->props.live) port->info.flags |= SPA_PORT_FLAG_LIVE; else port->info.flags &= ~SPA_PORT_FLAG_LIVE; break; } default: return -ENOENT; } return 0; } static void set_timer(struct impl *this, bool enabled) { if (this->callbacks.funcs || this->props.live) { if (enabled) { if (this->props.live) { uint64_t next_time = this->start_time + this->elapsed_time; this->timerspec.it_value.tv_sec = next_time / SPA_NSEC_PER_SEC; this->timerspec.it_value.tv_nsec = next_time % SPA_NSEC_PER_SEC; } else { this->timerspec.it_value.tv_sec = 0; this->timerspec.it_value.tv_nsec = 1; } } else { this->timerspec.it_value.tv_sec = 0; this->timerspec.it_value.tv_nsec = 0; } spa_system_timerfd_settime(this->data_system, this->timer_source.fd, SPA_FD_TIMER_ABSTIME, &this->timerspec, NULL); } } static inline int read_timer(struct impl *this) { uint64_t expirations; int res = 0; if (this->callbacks.funcs || this->props.live) { if ((res = spa_system_timerfd_read(this->data_system, this->timer_source.fd, &expirations)) < 0) { if (res != -EAGAIN) spa_log_error(this->log, "%p: timerfd error: %s", this, spa_strerror(res)); } } return res; } static void render_buffer(struct impl *this, struct buffer *b) { } static int consume_buffer(struct impl *this) { struct port *port = &this->port; struct buffer *b; struct spa_io_buffers *io = port->io; int n_bytes; if (read_timer(this) < 0) return 0; if (spa_list_is_empty(&port->ready)) { io->status = SPA_STATUS_NEED_DATA; spa_node_call_ready(&this->callbacks, SPA_STATUS_NEED_DATA); } if (spa_list_is_empty(&port->ready)) { spa_log_error(this->log, "%p: no buffers", this); return -EPIPE; } b = spa_list_first(&port->ready, struct buffer, link); spa_list_remove(&b->link); n_bytes = b->outbuf->datas[0].maxsize; spa_log_trace(this->log, "%p: dequeue buffer %d", this, b->id); render_buffer(this, b); b->outbuf->datas[0].chunk->offset = 0; b->outbuf->datas[0].chunk->size = n_bytes; b->outbuf->datas[0].chunk->stride = n_bytes; if (b->h) { b->h->seq = this->buffer_count; b->h->pts = this->start_time + this->elapsed_time; b->h->dts_offset = 0; } this->buffer_count++; this->elapsed_time = this->buffer_count; set_timer(this, true); io->buffer_id = b->id; io->status = SPA_STATUS_NEED_DATA; b->outstanding = true; return SPA_STATUS_NEED_DATA; } static void on_input(struct spa_source *source) { struct impl *this = source->data; consume_buffer(this); } static int impl_node_send_command(void *object, const struct spa_command *command) { struct impl *this = object; struct port *port; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(command != NULL, -EINVAL); port = &this->port; switch (SPA_NODE_COMMAND_ID(command)) { case SPA_NODE_COMMAND_Start: { struct timespec now; if (!port->have_format) return -EIO; if (port->n_buffers == 0) return -EIO; if (this->started) return 0; clock_gettime(CLOCK_MONOTONIC, &now); if (this->props.live) this->start_time = SPA_TIMESPEC_TO_NSEC(&now); else this->start_time = 0; this->buffer_count = 0; this->elapsed_time = 0; this->started = true; set_timer(this, true); break; } case SPA_NODE_COMMAND_Pause: if (!port->have_format) return -EIO; if (port->n_buffers == 0) return -EIO; if (!this->started) return 0; this->started = false; set_timer(this, false); break; default: return -ENOTSUP; } return 0; } static void emit_node_info(struct impl *this, bool full) { uint64_t old = full ? this->info.change_mask : 0; if (full) this->info.change_mask = this->info_all; if (this->info.change_mask) { spa_node_emit_info(&this->hooks, &this->info); this->info.change_mask = old; } } static void emit_port_info(struct impl *this, struct port *port, bool full) { uint64_t old = full ? port->info.change_mask : 0; if (full) port->info.change_mask = port->info_all; if (port->info.change_mask) { spa_node_emit_port_info(&this->hooks, SPA_DIRECTION_INPUT, 0, &port->info); port->info.change_mask = old; } } static int impl_node_add_listener(void *object, struct spa_hook *listener, const struct spa_node_events *events, void *data) { struct impl *this = object; struct spa_hook_list save; spa_return_val_if_fail(this != NULL, -EINVAL); spa_hook_list_isolate(&this->hooks, &save, listener, events, data); emit_node_info(this, true); emit_port_info(this, &this->port, true); spa_hook_list_join(&this->hooks, &save); return 0; } static int impl_node_set_callbacks(void *object, const struct spa_node_callbacks *callbacks, void *data) { struct impl *this = object; spa_return_val_if_fail(this != NULL, -EINVAL); if (this->data_loop == NULL && callbacks != NULL) { spa_log_error(this->log, "a data_loop is needed for async operation"); return -EINVAL; } this->callbacks = SPA_CALLBACKS_INIT(callbacks, data); return 0; } static int impl_node_add_port(void *object, enum spa_direction direction, uint32_t port_id, const struct spa_dict *props) { return -ENOTSUP; } static int impl_node_remove_port(void *object, enum spa_direction direction, uint32_t port_id) { return -ENOTSUP; } static int port_enum_formats(struct impl *this, int seq, struct port *port, uint32_t index, const struct spa_pod *filter, struct spa_pod **param, struct spa_pod_builder *builder) { return -ENOTSUP; } static int port_get_format(struct impl *this, struct port *port, uint32_t index, const struct spa_pod *filter, struct spa_pod **param, struct spa_pod_builder *builder) { if (!port->have_format) return -EIO; if (index > 0) return 0; *param = SPA_PTROFF(port->format_buffer, 0, struct spa_pod); return 1; } static int impl_node_port_enum_params(void *object, int seq, enum spa_direction direction, uint32_t port_id, uint32_t id, uint32_t start, uint32_t num, const struct spa_pod *filter) { struct impl *this = object; struct port *port; struct spa_pod_builder b = { 0 }; uint8_t buffer[1024]; struct spa_pod *param; struct spa_result_node_params result; uint32_t count = 0; int res; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(num != 0, -EINVAL); spa_return_val_if_fail(CHECK_PORT(node, direction, port_id), -EINVAL); port = &this->port; result.id = id; result.next = start; next: result.index = result.next++; spa_pod_builder_init(&b, buffer, sizeof(buffer)); switch (id) { case SPA_PARAM_EnumFormat: if ((res = port_enum_formats(this, seq, port, result.index, filter, ¶m, &b)) <= 0) return res; break; case SPA_PARAM_Format: if ((res = port_get_format(this, port, result.index, filter, ¶m, &b)) <= 0) return res; break; case SPA_PARAM_Buffers: if (result.index > 0) return 0; param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamBuffers, id, SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(2, 1, 32), SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(1), SPA_PARAM_BUFFERS_size, SPA_POD_Int(128), SPA_PARAM_BUFFERS_stride, SPA_POD_Int(1)); break; case SPA_PARAM_Meta: switch (result.index) { case 0: param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamMeta, id, SPA_PARAM_META_type, SPA_POD_Id(SPA_META_Header), SPA_PARAM_META_size, SPA_POD_Int(sizeof(struct spa_meta_header))); break; default: return 0; } break; default: return -ENOENT; } if (spa_pod_filter(&b, &result.param, param, filter) < 0) goto next; spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); if (++count != num) goto next; return 0; } static int clear_buffers(struct impl *this, struct port *port) { if (port->n_buffers > 0) { spa_log_debug(this->log, "%p: clear buffers", this); port->n_buffers = 0; spa_list_init(&port->ready); this->started = false; set_timer(this, false); } return 0; } static int port_set_format(struct impl *this, struct port *port, uint32_t flags, const struct spa_pod *format) { if (format == NULL) { port->have_format = false; clear_buffers(this, port); } else { if (SPA_POD_SIZE(format) > sizeof(port->format_buffer)) return -ENOSPC; memcpy(port->format_buffer, format, SPA_POD_SIZE(format)); port->have_format = true; } return 0; } static int impl_node_port_set_param(void *object, enum spa_direction direction, uint32_t port_id, uint32_t id, uint32_t flags, const struct spa_pod *param) { struct impl *this = object; struct port *port; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(CHECK_PORT(node, direction, port_id), -EINVAL); port = &this->port; if (id == SPA_PARAM_Format) { return port_set_format(this, port, flags, param); } else return -ENOENT; } static int impl_node_port_use_buffers(void *object, enum spa_direction direction, uint32_t port_id, uint32_t flags, struct spa_buffer **buffers, uint32_t n_buffers) { struct impl *this = object; struct port *port; uint32_t i; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); port = &this->port; clear_buffers(this, port); if (n_buffers > 0 && !port->have_format) return -EIO; if (n_buffers > MAX_BUFFERS) return -ENOSPC; for (i = 0; i < n_buffers; i++) { struct buffer *b; struct spa_data *d = buffers[i]->datas; b = &port->buffers[i]; b->id = i; b->outbuf = buffers[i]; b->outstanding = true; b->h = spa_buffer_find_meta_data(buffers[i], SPA_META_Header, sizeof(*b->h)); if (d[0].data == NULL) { spa_log_error(this->log, "%p: invalid memory on buffer %p", this, buffers[i]); } } port->n_buffers = n_buffers; return 0; } static int impl_node_port_set_io(void *object, enum spa_direction direction, uint32_t port_id, uint32_t id, void *data, size_t size) { struct impl *this = object; struct port *port; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); port = &this->port; if (id == SPA_IO_Buffers) port->io = data; else return -ENOENT; return 0; } static int impl_node_port_reuse_buffer(void *object, uint32_t port_id, uint32_t buffer_id) { return -ENOTSUP; } static int impl_node_process(void *object) { struct impl *this = object; struct port *port; struct spa_io_buffers *io; spa_return_val_if_fail(this != NULL, -EINVAL); port = &this->port; if ((io = port->io) == NULL) return -EIO; if (io->status == SPA_STATUS_HAVE_DATA && io->buffer_id < port->n_buffers) { struct buffer *b = &port->buffers[io->buffer_id]; if (!b->outstanding) { spa_log_warn(this->log, "%p: buffer %u in use", this, io->buffer_id); io->status = -EINVAL; return -EINVAL; } spa_log_trace(this->log, "%p: queue buffer %u", this, io->buffer_id); spa_list_append(&port->ready, &b->link); b->outstanding = false; io->buffer_id = SPA_ID_INVALID; io->status = SPA_STATUS_OK; } if (this->callbacks.funcs == NULL) return consume_buffer(this); else return SPA_STATUS_OK; } static const struct spa_node_methods impl_node = { SPA_VERSION_NODE_METHODS, .add_listener = impl_node_add_listener, .set_callbacks = impl_node_set_callbacks, .enum_params = impl_node_enum_params, .set_param = impl_node_set_param, .set_io = impl_node_set_io, .send_command = impl_node_send_command, .add_port = impl_node_add_port, .remove_port = impl_node_remove_port, .port_enum_params = impl_node_port_enum_params, .port_set_param = impl_node_port_set_param, .port_use_buffers = impl_node_port_use_buffers, .port_set_io = impl_node_port_set_io, .port_reuse_buffer = impl_node_port_reuse_buffer, .process = impl_node_process, }; static int impl_get_interface(struct spa_handle *handle, const char *type, void **interface) { struct impl *this; spa_return_val_if_fail(handle != NULL, -EINVAL); spa_return_val_if_fail(interface != NULL, -EINVAL); this = (struct impl *) handle; if (spa_streq(type, SPA_TYPE_INTERFACE_Node)) *interface = &this->node; else return -ENOENT; return 0; } static int do_remove_timer(struct spa_loop *loop, bool async, uint32_t seq, const void *data, size_t size, void *user_data) { struct impl *this = user_data; spa_loop_remove_source(this->data_loop, &this->timer_source); return 0; } static int impl_clear(struct spa_handle *handle) { struct impl *this; spa_return_val_if_fail(handle != NULL, -EINVAL); this = (struct impl *) handle; if (this->data_loop) spa_loop_locked(this->data_loop, do_remove_timer, 0, NULL, 0, this); spa_system_close(this->data_system, this->timer_source.fd); return 0; } static size_t impl_get_size(const struct spa_handle_factory *factory, const struct spa_dict *params) { return sizeof(struct impl); } static int impl_init(const struct spa_handle_factory *factory, struct spa_handle *handle, const struct spa_dict *info, const struct spa_support *support, uint32_t n_support) { struct impl *this; struct port *port; spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(handle != NULL, -EINVAL); handle->get_interface = impl_get_interface; handle->clear = impl_clear; this = (struct impl *) handle; this->log = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log); this->data_loop = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_DataLoop); this->data_system = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_DataSystem); spa_hook_list_init(&this->hooks); this->node.iface = SPA_INTERFACE_INIT( SPA_TYPE_INTERFACE_Node, SPA_VERSION_NODE, &impl_node, this); this->info_all = SPA_NODE_CHANGE_MASK_FLAGS | SPA_NODE_CHANGE_MASK_PARAMS; this->info = SPA_NODE_INFO_INIT(); this->info.max_input_ports = 1; this->info.max_output_ports = 0; this->info.flags = SPA_NODE_FLAG_RT; this->params[0] = SPA_PARAM_INFO(SPA_PARAM_Props, SPA_PARAM_INFO_READWRITE); this->info.params = this->params; this->info.n_params = 1; reset_props(this, &this->props); this->timer_source.func = on_input; this->timer_source.data = this; this->timer_source.fd = spa_system_timerfd_create(this->data_system, CLOCK_MONOTONIC, SPA_FD_CLOEXEC | SPA_FD_NONBLOCK); this->timer_source.mask = SPA_IO_IN; this->timer_source.rmask = 0; this->timerspec.it_value.tv_sec = 0; this->timerspec.it_value.tv_nsec = 0; this->timerspec.it_interval.tv_sec = 0; this->timerspec.it_interval.tv_nsec = 0; if (this->data_loop) spa_loop_add_source(this->data_loop, &this->timer_source); port = &this->port; port->info_all = SPA_PORT_CHANGE_MASK_FLAGS | SPA_PORT_CHANGE_MASK_PARAMS; port->info = SPA_PORT_INFO_INIT(); port->info.flags = SPA_PORT_FLAG_NO_REF; if (this->props.live) port->info.flags |= SPA_PORT_FLAG_LIVE; port->params[0] = SPA_PARAM_INFO(SPA_PARAM_Meta, SPA_PARAM_INFO_READ); port->params[1] = SPA_PARAM_INFO(SPA_PARAM_IO, 0); port->params[2] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); port->params[3] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); port->info.params = port->params; port->info.n_params = 4; spa_list_init(&port->ready); return 0; } static const struct spa_interface_info impl_interfaces[] = { {SPA_TYPE_INTERFACE_Node,}, }; static int impl_enum_interface_info(const struct spa_handle_factory *factory, const struct spa_interface_info **info, uint32_t *index) { spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(info != NULL, -EINVAL); spa_return_val_if_fail(index != NULL, -EINVAL); switch (*index) { case 0: *info = &impl_interfaces[*index]; break; default: return 0; } (*index)++; return 1; } const struct spa_handle_factory spa_fakesink_factory = { SPA_VERSION_HANDLE_FACTORY, "fakesink", NULL, impl_get_size, impl_init, impl_enum_interface_info, }; pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/test/fakesrc.c000066400000000000000000000472661511204443500255220ustar00rootroot00000000000000/* Spa */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #undef SPA_LOG_TOPIC_DEFAULT #define SPA_LOG_TOPIC_DEFAULT &log_topic SPA_LOG_TOPIC_DEFINE_STATIC(log_topic, "spa.fakesrc"); struct props { bool live; uint32_t pattern; }; #define MAX_BUFFERS 16 #define MAX_PORTS 1 struct buffer { uint32_t id; struct spa_buffer *outbuf; bool outstanding; struct spa_meta_header *h; struct spa_list link; }; struct port { uint64_t info_all; struct spa_port_info info; struct spa_param_info params[5]; struct spa_io_buffers *io; bool have_format; uint8_t format_buffer[1024]; struct buffer buffers[MAX_BUFFERS]; uint32_t n_buffers; struct spa_list empty; }; struct impl { struct spa_handle handle; struct spa_node node; struct spa_log *log; struct spa_loop *data_loop; struct spa_system *data_system; uint64_t info_all; struct spa_node_info info; struct spa_param_info params[1]; struct props props; struct spa_hook_list hooks; struct spa_callbacks callbacks; struct spa_source timer_source; struct itimerspec timerspec; bool started; uint64_t start_time; uint64_t elapsed_time; uint64_t buffer_count; bool underrun; struct port port; }; #define CHECK_PORT(this,d,p) ((d) == SPA_DIRECTION_OUTPUT && (p) < MAX_PORTS) #define DEFAULT_LIVE false #define DEFAULT_PATTERN 0 static void reset_props(struct impl *this, struct props *props) { props->live = DEFAULT_LIVE; props->pattern = DEFAULT_PATTERN; } static int impl_node_enum_params(void *object, int seq, uint32_t id, uint32_t start, uint32_t num, const struct spa_pod *filter) { struct impl *this = object; struct spa_pod_builder b = { 0 }; uint8_t buffer[1024]; struct spa_pod *param; struct spa_result_node_params result; uint32_t count = 0; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(num != 0, -EINVAL); result.id = id; result.next = start; next: result.index = result.next++; spa_pod_builder_init(&b, buffer, sizeof(buffer)); switch (id) { case SPA_PARAM_Props: { struct props *p = &this->props; if (result.index > 0) return 0; param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_Props, id, SPA_PROP_live, SPA_POD_Bool(p->live), SPA_PROP_patternType, SPA_POD_CHOICE_ENUM_Int(2, p->pattern, p->pattern)); break; } default: return -ENOENT; } if (spa_pod_filter(&b, &result.param, param, filter) < 0) goto next; spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); if (++count != num) goto next; return 0; } static int impl_node_set_io(void *object, uint32_t id, void *data, size_t size) { return -ENOTSUP; } static int impl_node_set_param(void *object, uint32_t id, uint32_t flags, const struct spa_pod *param) { struct impl *this = object; spa_return_val_if_fail(this != NULL, -EINVAL); switch (id) { case SPA_PARAM_Props: { struct props *p = &this->props; struct port *port = &this->port; if (param == NULL) { reset_props(this, p); return 0; } spa_pod_parse_object(param, SPA_TYPE_OBJECT_Props, NULL, SPA_PROP_live, SPA_POD_OPT_Bool(&p->live), SPA_PROP_patternType, SPA_POD_OPT_Int(&p->pattern)); if (p->live) port->info.flags |= SPA_PORT_FLAG_LIVE; else port->info.flags &= ~SPA_PORT_FLAG_LIVE; break; } default: return -ENOENT; } return 0; } static int fill_buffer(struct impl *this, struct buffer *b) { return 0; } static void set_timer(struct impl *this, bool enabled) { if (this->callbacks.funcs || this->props.live) { if (enabled) { if (this->props.live) { uint64_t next_time = this->start_time + this->elapsed_time; this->timerspec.it_value.tv_sec = next_time / SPA_NSEC_PER_SEC; this->timerspec.it_value.tv_nsec = next_time % SPA_NSEC_PER_SEC; } else { this->timerspec.it_value.tv_sec = 0; this->timerspec.it_value.tv_nsec = 1; } } else { this->timerspec.it_value.tv_sec = 0; this->timerspec.it_value.tv_nsec = 0; } spa_system_timerfd_settime(this->data_system, this->timer_source.fd, SPA_FD_TIMER_ABSTIME, &this->timerspec, NULL); } } static inline int read_timer(struct impl *this) { uint64_t expirations; int res = 0; if (this->callbacks.funcs || this->props.live) { if ((res = spa_system_timerfd_read(this->data_system, this->timer_source.fd, &expirations)) < 0) { if (res != -EAGAIN) spa_log_error(this->log, "%p: timerfd error: %s", this, spa_strerror(res)); } } return res; } static int make_buffer(struct impl *this) { struct buffer *b; struct port *port = &this->port; struct spa_io_buffers *io = port->io; int n_bytes; if (read_timer(this) < 0) return 0; if (spa_list_is_empty(&port->empty)) { set_timer(this, false); this->underrun = true; spa_log_error(this->log, "%p: out of buffers", this); return -EPIPE; } b = spa_list_first(&port->empty, struct buffer, link); spa_list_remove(&b->link); b->outstanding = true; n_bytes = b->outbuf->datas[0].maxsize; spa_log_trace(this->log, "%p: dequeue buffer %d", this, b->id); fill_buffer(this, b); b->outbuf->datas[0].chunk->offset = 0; b->outbuf->datas[0].chunk->size = n_bytes; b->outbuf->datas[0].chunk->stride = n_bytes; if (b->h) { b->h->seq = this->buffer_count; b->h->pts = this->start_time + this->elapsed_time; b->h->dts_offset = 0; } this->buffer_count++; this->elapsed_time = this->buffer_count; set_timer(this, true); io->buffer_id = b->id; io->status = SPA_STATUS_HAVE_DATA; return SPA_STATUS_HAVE_DATA; } static void on_output(struct spa_source *source) { struct impl *this = source->data; int res; res = make_buffer(this); if (res == SPA_STATUS_HAVE_DATA && this->callbacks.funcs) spa_node_call_ready(&this->callbacks, res); } static int impl_node_send_command(void *object, const struct spa_command *command) { struct impl *this = object; struct port *port; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(command != NULL, -EINVAL); port = &this->port; switch (SPA_NODE_COMMAND_ID(command)) { case SPA_NODE_COMMAND_Start: { struct timespec now; if (!port->have_format) return -EIO; if (port->n_buffers == 0) return -EIO; if (this->started) return 0; clock_gettime(CLOCK_MONOTONIC, &now); if (this->props.live) this->start_time = SPA_TIMESPEC_TO_NSEC(&now); else this->start_time = 0; this->buffer_count = 0; this->elapsed_time = 0; this->started = true; set_timer(this, true); break; } case SPA_NODE_COMMAND_Pause: if (!port->have_format) return -EIO; if (port->n_buffers == 0) return -EIO; if (!this->started) return 0; this->started = false; set_timer(this, false); break; default: return -ENOTSUP; } return 0; } static void emit_node_info(struct impl *this, bool full) { uint64_t old = full ? this->info.change_mask : 0; if (full) this->info.change_mask = this->info_all; if (this->info.change_mask) { spa_node_emit_info(&this->hooks, &this->info); this->info.change_mask = old; } } static void emit_port_info(struct impl *this, struct port *port, bool full) { uint64_t old = full ? port->info.change_mask : 0; if (full) port->info.change_mask = port->info_all; if (port->info.change_mask) { spa_node_emit_port_info(&this->hooks, SPA_DIRECTION_OUTPUT, 0, &port->info); port->info.change_mask = old; } } static int impl_node_add_listener(void *object, struct spa_hook *listener, const struct spa_node_events *events, void *data) { struct impl *this = object; struct spa_hook_list save; spa_return_val_if_fail(this != NULL, -EINVAL); spa_hook_list_isolate(&this->hooks, &save, listener, events, data); emit_node_info(this, true); emit_port_info(this, &this->port, true); spa_hook_list_join(&this->hooks, &save); return 0; } static int impl_node_set_callbacks(void *object, const struct spa_node_callbacks *callbacks, void *data) { struct impl *this = object; spa_return_val_if_fail(this != NULL, -EINVAL); if (this->data_loop == NULL && callbacks != NULL) { spa_log_error(this->log, "a data_loop is needed for async operation"); return -EINVAL; } this->callbacks = SPA_CALLBACKS_INIT(callbacks, data); return 0; } static int impl_node_add_port(void *object, enum spa_direction direction, uint32_t port_id, const struct spa_dict *props) { return -ENOTSUP; } static int impl_node_remove_port(void *object, enum spa_direction direction, uint32_t port_id) { return -ENOTSUP; } static int port_enum_formats(struct impl *this, struct port *port, uint32_t index, const struct spa_pod *filter, struct spa_pod **param, struct spa_pod_builder *builder) { return 0; } static int port_get_format(struct impl *this, struct port *port, uint32_t index, const struct spa_pod *filter, struct spa_pod **param, struct spa_pod_builder *builder) { if (!port->have_format) return -EIO; if (index > 0) return 0; *param = SPA_PTROFF(port->format_buffer, 0, struct spa_pod); return 1; } static int impl_node_port_enum_params(void *object, int seq, enum spa_direction direction, uint32_t port_id, uint32_t id, uint32_t start, uint32_t num, const struct spa_pod *filter) { struct impl *this = object; struct port *port; struct spa_pod_builder b = { 0 }; uint8_t buffer[1024]; struct spa_pod *param; struct spa_result_node_params result; uint32_t count = 0; int res; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(num != 0, -EINVAL); spa_return_val_if_fail(CHECK_PORT(node, direction, port_id), -EINVAL); port = &this->port; result.id = id; result.next = start; next: result.index = result.next++; spa_pod_builder_init(&b, buffer, sizeof(buffer)); switch (id) { case SPA_PARAM_EnumFormat: if ((res = port_enum_formats(this, port, result.index, filter, ¶m, &b)) <= 0) return res; break; case SPA_PARAM_Format: if ((res = port_get_format(this, port, result.index, filter, ¶m, &b)) <= 0) return res; break; case SPA_PARAM_Buffers: switch (result.index) { case 0: param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamBuffers, id, SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(32, 2, 32), SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(1), SPA_PARAM_BUFFERS_size, SPA_POD_Int(128), SPA_PARAM_BUFFERS_stride, SPA_POD_Int(1)); break; default: return 0; } break; case SPA_PARAM_Meta: switch (result.index) { case 0: param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamMeta, id, SPA_PARAM_META_type, SPA_POD_Id(SPA_META_Header), SPA_PARAM_META_size, SPA_POD_Int(sizeof(struct spa_meta_header))); break; default: return 0; } break; default: return -ENOENT; } if (spa_pod_filter(&b, &result.param, param, filter) < 0) goto next; spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); if (++count != num) goto next; return 0; } static int clear_buffers(struct impl *this, struct port *port) { if (port->n_buffers > 0) { spa_log_debug(this->log, "%p: clear buffers", this); port->n_buffers = 0; spa_list_init(&port->empty); this->started = false; set_timer(this, false); } return 0; } static int port_set_format(struct impl *this, struct port *port, uint32_t flags, const struct spa_pod *format) { if (format == NULL) { port->have_format = false; clear_buffers(this, port); } else { if (SPA_POD_SIZE(format) > sizeof(port->format_buffer)) return -ENOSPC; memcpy(port->format_buffer, format, SPA_POD_SIZE(format)); port->have_format = true; } return 0; } static int impl_node_port_set_param(void *object, enum spa_direction direction, uint32_t port_id, uint32_t id, uint32_t flags, const struct spa_pod *param) { struct impl *this = object; struct port *port; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(CHECK_PORT(node, direction, port_id), -EINVAL); port = &this->port; if (id == SPA_PARAM_Format) { return port_set_format(this, port, flags, param); } else return -ENOENT; } static int impl_node_port_use_buffers(void *object, enum spa_direction direction, uint32_t flags, uint32_t port_id, struct spa_buffer **buffers, uint32_t n_buffers) { struct impl *this = object; struct port *port; uint32_t i; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); port = &this->port; clear_buffers(this, port); if (n_buffers > 0 && !port->have_format) return -EIO; if (n_buffers > MAX_BUFFERS) return -ENOSPC; for (i = 0; i < n_buffers; i++) { struct buffer *b; struct spa_data *d = buffers[i]->datas; b = &port->buffers[i]; b->id = i; b->outbuf = buffers[i]; b->outstanding = false; b->h = spa_buffer_find_meta_data(buffers[i], SPA_META_Header, sizeof(*b->h)); if (d[0].data == NULL) { spa_log_error(this->log, "%p: invalid memory on buffer %p", this, buffers[i]); } spa_list_append(&port->empty, &b->link); } port->n_buffers = n_buffers; this->underrun = false; return 0; } static int impl_node_port_set_io(void *object, enum spa_direction direction, uint32_t port_id, uint32_t id, void *data, size_t size) { struct impl *this = object; struct port *port; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); port = &this->port; if (id == SPA_IO_Buffers) port->io = data; else return -ENOENT; return 0; } static inline void reuse_buffer(struct impl *this, struct port *port, uint32_t id) { struct buffer *b = &port->buffers[id]; spa_return_if_fail(b->outstanding); spa_log_trace(this->log, "%p: reuse buffer %d", this, id); b->outstanding = false; spa_list_append(&port->empty, &b->link); if (this->underrun) { set_timer(this, true); this->underrun = false; } } static int impl_node_port_reuse_buffer(void *object, uint32_t port_id, uint32_t buffer_id) { struct impl *this = object; struct port *port; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(port_id == 0, -EINVAL); port = &this->port; spa_return_val_if_fail(buffer_id < port->n_buffers, -EINVAL); reuse_buffer(this, port, buffer_id); return 0; } static int impl_node_process(void *object) { struct impl *this = object; struct port *port; struct spa_io_buffers *io; spa_return_val_if_fail(this != NULL, -EINVAL); port = &this->port; if ((io = port->io) == NULL) return -EIO; if (io->status == SPA_STATUS_HAVE_DATA) return SPA_STATUS_HAVE_DATA; if (io->buffer_id < port->n_buffers) { reuse_buffer(this, port, io->buffer_id); io->buffer_id = SPA_ID_INVALID; } if (this->callbacks.funcs == NULL) return make_buffer(this); else return SPA_STATUS_OK; } static const struct spa_node_methods impl_node = { SPA_VERSION_NODE_METHODS, .add_listener = impl_node_add_listener, .set_callbacks = impl_node_set_callbacks, .enum_params = impl_node_enum_params, .set_param = impl_node_set_param, .set_io = impl_node_set_io, .send_command = impl_node_send_command, .add_port = impl_node_add_port, .remove_port = impl_node_remove_port, .port_enum_params = impl_node_port_enum_params, .port_set_param = impl_node_port_set_param, .port_use_buffers = impl_node_port_use_buffers, .port_set_io = impl_node_port_set_io, .port_reuse_buffer = impl_node_port_reuse_buffer, .process = impl_node_process, }; static int impl_get_interface(struct spa_handle *handle, const char *type, void **interface) { struct impl *this; spa_return_val_if_fail(handle != NULL, -EINVAL); spa_return_val_if_fail(interface != NULL, -EINVAL); this = (struct impl *) handle; if (spa_streq(type, SPA_TYPE_INTERFACE_Node)) *interface = &this->node; else return -ENOENT; return 0; } static int do_remove_timer(struct spa_loop *loop, bool async, uint32_t seq, const void *data, size_t size, void *user_data) { struct impl *this = user_data; spa_loop_remove_source(this->data_loop, &this->timer_source); return 0; } static int impl_clear(struct spa_handle *handle) { struct impl *this; spa_return_val_if_fail(handle != NULL, -EINVAL); this = (struct impl *) handle; if (this->data_loop) spa_loop_locked(this->data_loop, do_remove_timer, 0, NULL, 0, this); spa_system_close(this->data_system, this->timer_source.fd); return 0; } static size_t impl_get_size(const struct spa_handle_factory *factory, const struct spa_dict *params) { return sizeof(struct impl); } static int impl_init(const struct spa_handle_factory *factory, struct spa_handle *handle, const struct spa_dict *info, const struct spa_support *support, uint32_t n_support) { struct impl *this; struct port *port; spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(handle != NULL, -EINVAL); handle->get_interface = impl_get_interface; handle->clear = impl_clear; this = (struct impl *) handle; this->log = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log); this->data_loop = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_DataLoop); this->data_system = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_DataSystem); spa_hook_list_init(&this->hooks); this->node.iface = SPA_INTERFACE_INIT( SPA_TYPE_INTERFACE_Node, SPA_VERSION_NODE, &impl_node, this); this->info_all = SPA_NODE_CHANGE_MASK_FLAGS | SPA_NODE_CHANGE_MASK_PARAMS; this->info = SPA_NODE_INFO_INIT(); this->info.max_input_ports = 0; this->info.max_output_ports = 1; this->info.flags = SPA_NODE_FLAG_RT; this->params[0] = SPA_PARAM_INFO(SPA_PARAM_Props, SPA_PARAM_INFO_READWRITE); this->info.params = this->params; this->info.n_params = 1; reset_props(this, &this->props); this->timer_source.func = on_output; this->timer_source.data = this; this->timer_source.fd = spa_system_timerfd_create(this->data_system, CLOCK_MONOTONIC, SPA_FD_CLOEXEC | SPA_FD_NONBLOCK); this->timer_source.mask = SPA_IO_IN; this->timer_source.rmask = 0; this->timerspec.it_value.tv_sec = 0; this->timerspec.it_value.tv_nsec = 0; this->timerspec.it_interval.tv_sec = 0; this->timerspec.it_interval.tv_nsec = 0; if (this->data_loop) spa_loop_add_source(this->data_loop, &this->timer_source); port = &this->port; port->info_all = SPA_PORT_CHANGE_MASK_FLAGS | SPA_PORT_CHANGE_MASK_PARAMS; port->info = SPA_PORT_INFO_INIT(); port->info.flags = SPA_PORT_FLAG_NO_REF; if (this->props.live) port->info.flags |= SPA_PORT_FLAG_LIVE; port->params[0] = SPA_PARAM_INFO(SPA_PARAM_Meta, SPA_PARAM_INFO_READ); port->params[1] = SPA_PARAM_INFO(SPA_PARAM_IO, 0); port->params[2] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); port->params[3] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); port->info.params = port->params; port->info.n_params = 4; spa_list_init(&port->empty); return 0; } static const struct spa_interface_info impl_interfaces[] = { {SPA_TYPE_INTERFACE_Node,}, }; static int impl_enum_interface_info(const struct spa_handle_factory *factory, const struct spa_interface_info **info, uint32_t *index) { spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(info != NULL, -EINVAL); spa_return_val_if_fail(index != NULL, -EINVAL); switch (*index) { case 0: *info = &impl_interfaces[*index]; break; default: return 0; } (*index)++; return 1; } const struct spa_handle_factory spa_fakesrc_factory = { SPA_VERSION_HANDLE_FACTORY, "fakesrc", NULL, impl_get_size, impl_init, impl_enum_interface_info, }; pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/test/meson.build000066400000000000000000000004621511204443500260650ustar00rootroot00000000000000test_sources = ['fakesrc.c', 'fakesink.c', 'plugin.c'] testlib = shared_library('spa-test', test_sources, dependencies : [ spa_dep, pthread_lib ], install : true, install_dir : spa_plugindir / 'test') pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/test/plugin.c000066400000000000000000000013441511204443500253650ustar00rootroot00000000000000/* Spa Test plugin */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include extern const struct spa_handle_factory spa_fakesrc_factory; extern const struct spa_handle_factory spa_fakesink_factory; SPA_LOG_TOPIC_ENUM_DEFINE_REGISTERED; SPA_EXPORT int spa_handle_factory_enum(const struct spa_handle_factory **factory, uint32_t *index) { spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(index != NULL, -EINVAL); switch (*index) { case 0: *factory = &spa_fakesrc_factory; break; case 1: *factory = &spa_fakesink_factory; break; default: return 0; } (*index)++; return 1; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/test/test-helper.h000066400000000000000000000044741511204443500263370ustar00rootroot00000000000000#include #include #include #include #include #include static inline const struct spa_handle_factory *get_factory(spa_handle_factory_enum_func_t enum_func, const char *name, uint32_t version) { uint32_t i; int res; const struct spa_handle_factory *factory; for (i = 0;;) { if ((res = enum_func(&factory, &i)) <= 0) { if (res < 0) errno = -res; break; } if (factory->version >= version && !strcmp(factory->name, name)) return factory; } return NULL; } static inline struct spa_handle *load_handle(const struct spa_support *support, uint32_t n_support, const char *lib, const char *name) { int res, len; void *hnd; spa_handle_factory_enum_func_t enum_func; const struct spa_handle_factory *factory; struct spa_handle *handle; const char *str; char *path; if ((str = getenv("SPA_PLUGIN_DIR")) == NULL) str = PLUGINDIR; len = strlen(str) + strlen(lib) + 2; path = alloca(len); snprintf(path, len, "%s/%s", str, lib); if ((hnd = dlopen(path, RTLD_NOW)) == NULL) { fprintf(stderr, "can't load %s: %s\n", lib, dlerror()); res = -ENOENT; goto error; } if ((enum_func = dlsym(hnd, SPA_HANDLE_FACTORY_ENUM_FUNC_NAME)) == NULL) { fprintf(stderr, "can't find enum function\n"); res = -ENXIO; goto error_close; } if ((factory = get_factory(enum_func, name, SPA_VERSION_HANDLE_FACTORY)) == NULL) { fprintf(stderr, "can't find factory\n"); res = -ENOENT; goto error_close; } handle = calloc(1, spa_handle_factory_get_size(factory, NULL)); if ((res = spa_handle_factory_init(factory, handle, NULL, support, n_support)) < 0) { fprintf(stderr, "can't make factory instance: %d\n", res); goto error_close; } return handle; error_close: dlclose(hnd); error: errno = -res; return NULL; } static inline uint32_t get_cpu_flags(void) { struct spa_handle *handle; uint32_t flags; void *iface; int res; handle = load_handle(NULL, 0, "support/libspa-support.so", SPA_NAME_SUPPORT_CPU); if (handle == NULL) return 0; if ((res = spa_handle_get_interface(handle, SPA_TYPE_INTERFACE_CPU, &iface)) < 0) { fprintf(stderr, "can't get CPU interface %s\n", spa_strerror(res)); return 0; } flags = spa_cpu_get_flags((struct spa_cpu*)iface); free(handle); return flags; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/v4l2/000077500000000000000000000000001511204443500235315ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/v4l2/meson.build000066400000000000000000000011711511204443500256730ustar00rootroot00000000000000v4l2_sources = ['v4l2.c', 'v4l2-device.c', 'v4l2-source.c'] v4l2_dependencies = [ spa_dep, libinotify_dep, mathlib ] if libudev_dep.found() v4l2_sources += [ 'v4l2-udev.c' ] v4l2_dependencies += [ libudev_dep ] if logind_dep.found() v4l2_dependencies += [ logind_dep ] endif endif v4l2lib = shared_library('spa-v4l2', v4l2_sources, include_directories : [ configinc ], dependencies : v4l2_dependencies, install : true, install_dir : spa_plugindir / 'v4l2') pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/v4l2/v4l2-device.c000066400000000000000000000167351511204443500257350ustar00rootroot00000000000000/* Spa V4l2 Source */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "v4l2.h" #undef SPA_LOG_TOPIC_DEFAULT #define SPA_LOG_TOPIC_DEFAULT &log_topic SPA_LOG_TOPIC_DEFINE_STATIC(log_topic, "spa.v4l2-device"); static const char default_device[] = "/dev/video0"; struct props { char device[64]; char devnum[32]; char product_id[7]; char vendor_id[7]; int device_fd; }; static void reset_props(struct props *props) { strncpy(props->device, default_device, 64); } struct impl { struct spa_handle handle; struct spa_device device; struct spa_log *log; struct props props; struct spa_hook_list hooks; struct spa_v4l2_device dev; }; static int emit_info(struct impl *this, bool full) { int res; struct spa_dict_item items[13]; uint32_t n_items = 0; struct spa_device_info info; struct spa_param_info params[2]; char path[128], version[16], capabilities[16], device_caps[16], devices_str[16]; struct spa_strbuf buf; if ((res = spa_v4l2_open(&this->dev, this->props.device)) < 0) return res; info = SPA_DEVICE_INFO_INIT(); info.change_mask = SPA_DEVICE_CHANGE_MASK_PROPS; #define ADD_ITEM(key, value) items[n_items++] = SPA_DICT_ITEM_INIT(key, value) snprintf(path, sizeof(path), "v4l2:%s", this->props.device); ADD_ITEM(SPA_KEY_OBJECT_PATH, path); ADD_ITEM(SPA_KEY_DEVICE_API, "v4l2"); ADD_ITEM(SPA_KEY_MEDIA_CLASS, "Video/Device"); if (this->props.product_id[0]) ADD_ITEM(SPA_KEY_DEVICE_PRODUCT_ID, this->props.product_id); if (this->props.vendor_id[0]) ADD_ITEM(SPA_KEY_DEVICE_VENDOR_ID, this->props.vendor_id); ADD_ITEM(SPA_KEY_API_V4L2_PATH, (char *)this->props.device); /* encode device number into a json array */ spa_strbuf_init(&buf, devices_str, sizeof(devices_str)); spa_strbuf_append(&buf, "[ "); spa_strbuf_append(&buf, "%s", this->props.devnum); spa_strbuf_append(&buf, " ]"); ADD_ITEM(SPA_KEY_DEVICE_DEVIDS, devices_str); ADD_ITEM(SPA_KEY_API_V4L2_CAP_DRIVER, (char *)this->dev.cap.driver); ADD_ITEM(SPA_KEY_API_V4L2_CAP_CARD, (char *)this->dev.cap.card); ADD_ITEM(SPA_KEY_API_V4L2_CAP_BUS_INFO, (char *)this->dev.cap.bus_info); snprintf(version, sizeof(version), "%u.%u.%u", (this->dev.cap.version >> 16) & 0xFF, (this->dev.cap.version >> 8) & 0xFF, (this->dev.cap.version) & 0xFF); ADD_ITEM(SPA_KEY_API_V4L2_CAP_VERSION, version); snprintf(capabilities, sizeof(capabilities), "%08x", this->dev.cap.capabilities); ADD_ITEM(SPA_KEY_API_V4L2_CAP_CAPABILITIES, capabilities); snprintf(device_caps, sizeof(device_caps), "%08x", this->dev.cap.device_caps); ADD_ITEM(SPA_KEY_API_V4L2_CAP_DEVICE_CAPS, device_caps); #undef ADD_ITEM info.props = &SPA_DICT_INIT(items, n_items); info.change_mask |= SPA_DEVICE_CHANGE_MASK_PARAMS; params[0] = SPA_PARAM_INFO(SPA_PARAM_EnumProfile, SPA_PARAM_INFO_READ); params[1] = SPA_PARAM_INFO(SPA_PARAM_Profile, SPA_PARAM_INFO_WRITE); info.n_params = 0; info.params = params; spa_device_emit_info(&this->hooks, &info); if (spa_v4l2_is_capture(&this->dev)) { struct spa_device_object_info oinfo; oinfo = SPA_DEVICE_OBJECT_INFO_INIT(); oinfo.type = SPA_TYPE_INTERFACE_Node; oinfo.factory_name = SPA_NAME_API_V4L2_SOURCE; oinfo.change_mask = SPA_DEVICE_OBJECT_CHANGE_MASK_PROPS; oinfo.props = &SPA_DICT_INIT(items, n_items); spa_device_emit_object_info(&this->hooks, 0, &oinfo); } spa_v4l2_close(&this->dev); return 0; } static int impl_add_listener(void *object, struct spa_hook *listener, const struct spa_device_events *events, void *data) { struct impl *this = object; struct spa_hook_list save; int res = 0; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(events != NULL, -EINVAL); spa_hook_list_isolate(&this->hooks, &save, listener, events, data); if (events->info || events->object_info) res = emit_info(this, true); spa_hook_list_join(&this->hooks, &save); return res; } static int impl_sync(void *object, int seq) { struct impl *this = object; spa_return_val_if_fail(this != NULL, -EINVAL); spa_device_emit_result(&this->hooks, seq, 0, 0, NULL); return 0; } static int impl_enum_params(void *object, int seq, uint32_t id, uint32_t start, uint32_t num, const struct spa_pod *filter) { return -ENOTSUP; } static int impl_set_param(void *object, uint32_t id, uint32_t flags, const struct spa_pod *param) { return -ENOTSUP; } static const struct spa_device_methods impl_device = { SPA_VERSION_DEVICE_METHODS, .add_listener = impl_add_listener, .sync = impl_sync, .enum_params = impl_enum_params, .set_param = impl_set_param, }; static int impl_get_interface(struct spa_handle *handle, const char *type, void **interface) { struct impl *this; spa_return_val_if_fail(handle != NULL, -EINVAL); spa_return_val_if_fail(interface != NULL, -EINVAL); this = (struct impl *) handle; if (spa_streq(type, SPA_TYPE_INTERFACE_Device)) *interface = &this->device; else return -ENOENT; return 0; } static int impl_clear(struct spa_handle *handle) { return 0; } static size_t impl_get_size(const struct spa_handle_factory *factory, const struct spa_dict *params) { return sizeof(struct impl); } static int impl_init(const struct spa_handle_factory *factory, struct spa_handle *handle, const struct spa_dict *info, const struct spa_support *support, uint32_t n_support) { struct impl *this; const char *str; spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(handle != NULL, -EINVAL); handle->get_interface = impl_get_interface; handle->clear = impl_clear, this = (struct impl *) handle; this->log = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log); spa_hook_list_init(&this->hooks); this->device.iface = SPA_INTERFACE_INIT( SPA_TYPE_INTERFACE_Device, SPA_VERSION_DEVICE, &impl_device, this); this->dev.log = this->log; this->dev.fd = -1; reset_props(&this->props); if (info && (str = spa_dict_lookup(info, SPA_KEY_API_V4L2_PATH))) strncpy(this->props.device, str, sizeof(this->props.device)-1); if (info && (str = spa_dict_lookup(info, SPA_KEY_DEVICE_DEVIDS))) strncpy(this->props.devnum, str, sizeof(this->props.devnum)-1); if (info && (str = spa_dict_lookup(info, SPA_KEY_DEVICE_PRODUCT_ID))) strncpy(this->props.product_id, str, sizeof(this->props.product_id)-1); if (info && (str = spa_dict_lookup(info, SPA_KEY_DEVICE_VENDOR_ID))) strncpy(this->props.vendor_id, str, sizeof(this->props.vendor_id)-1); return 0; } static const struct spa_interface_info impl_interfaces[] = { {SPA_TYPE_INTERFACE_Device,}, }; static int impl_enum_interface_info(const struct spa_handle_factory *factory, const struct spa_interface_info **info, uint32_t *index) { spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(info != NULL, -EINVAL); spa_return_val_if_fail(index != NULL, -EINVAL); if (*index >= SPA_N_ELEMENTS(impl_interfaces)) return 0; *info = &impl_interfaces[(*index)++]; return 1; } const struct spa_handle_factory spa_v4l2_device_factory = { SPA_VERSION_HANDLE_FACTORY, SPA_NAME_API_V4L2_DEVICE, NULL, impl_get_size, impl_init, impl_enum_interface_info, }; pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/v4l2/v4l2-source.c000066400000000000000000000724141511204443500257720ustar00rootroot00000000000000/* Spa V4l2 Source */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "v4l2.h" static const char default_device[] = "/dev/video0"; static const char default_clock_name[] = "api.v4l2.unknown"; struct props { char device[64]; char device_name[128]; int device_fd; char clock_name[64]; }; static void reset_props(struct props *props) { strncpy(props->device, default_device, sizeof(props->device)); strncpy(props->clock_name, default_clock_name, sizeof(props->clock_name)); } #define MAX_BUFFERS 32 #define BUFFER_FLAG_OUTSTANDING (1<<0) #define BUFFER_FLAG_ALLOCATED (1<<1) #define BUFFER_FLAG_MAPPED (1<<2) struct buffer { uint32_t id; uint32_t flags; struct spa_list link; struct spa_buffer *outbuf; struct spa_meta_header *h; struct spa_meta_videotransform *vt; struct v4l2_buffer v4l2_buffer; void *ptr; void *mmap_ptr; }; #define MAX_CONTROLS 64 struct control { uint32_t id; uint32_t ctrl_id; uint32_t type; int32_t value; }; struct port { struct impl *impl; bool alloc_buffers; bool probed_expbuf; bool have_expbuf; bool first_buffer; uint32_t max_buffers; bool next_fmtdesc; struct v4l2_fmtdesc fmtdesc; bool next_frmsize; struct v4l2_frmsizeenum frmsize; struct v4l2_frmivalenum frmival; bool have_format; bool have_modifier; struct spa_video_info current_format; struct spa_v4l2_device dev; bool have_query_ext_ctrl; struct v4l2_format fmt; enum v4l2_buf_type type; enum v4l2_memory memtype; struct control controls[MAX_CONTROLS]; uint32_t n_controls; struct buffer buffers[MAX_BUFFERS]; uint32_t n_buffers; struct spa_list queue; struct spa_source source; uint64_t info_all; struct spa_port_info info; struct spa_io_buffers *io; struct spa_io_sequence *control; uint32_t control_size; #define PORT_PropInfo 0 #define PORT_EnumFormat 1 #define PORT_Meta 2 #define PORT_IO 3 #define PORT_Format 4 #define PORT_Buffers 5 #define PORT_Latency 6 #define N_PORT_PARAMS 7 struct spa_param_info params[N_PORT_PARAMS]; }; struct impl { struct spa_handle handle; struct spa_node node; struct spa_log *log; struct spa_loop *data_loop; enum spa_meta_videotransform_value transform; uint64_t info_all; struct spa_node_info info; #define NODE_PropInfo 0 #define NODE_Props 1 #define NODE_EnumFormat 2 #define NODE_Format 3 #define N_NODE_PARAMS 4 struct spa_param_info params[N_NODE_PARAMS]; struct props props; struct spa_hook_list hooks; struct spa_callbacks callbacks; struct port out_ports[1]; struct spa_io_position *position; struct spa_io_clock *clock; struct spa_latency_info latency[2]; struct spa_dll dll; }; #define CHECK_PORT(this,direction,port_id) ((direction) == SPA_DIRECTION_OUTPUT && (port_id) == 0) #define GET_OUT_PORT(this,p) (&this->out_ports[p]) #define GET_PORT(this,d,p) GET_OUT_PORT(this,p) #include "v4l2-utils.c" static void emit_node_info(struct impl *this, bool full) { static const struct spa_dict_item info_items[] = { { SPA_KEY_DEVICE_API, "v4l2" }, { SPA_KEY_MEDIA_CLASS, "Video/Source" }, { SPA_KEY_MEDIA_ROLE, "Camera" }, { SPA_KEY_NODE_DRIVER, "true" }, }; uint64_t old = full ? this->info.change_mask : 0; if (full) this->info.change_mask = this->info_all; if (this->info.change_mask) { this->info.props = &SPA_DICT_INIT_ARRAY(info_items); spa_node_emit_info(&this->hooks, &this->info); this->info.change_mask = old; } } static void emit_port_info(struct impl *this, struct port *port, bool full) { static const struct spa_dict_item info_items[] = { { SPA_KEY_PORT_GROUP, "stream.0" }, }; uint64_t old = full ? port->info.change_mask : 0; if (full) port->info.change_mask = port->info_all; if (port->info.change_mask) { port->info.props = &SPA_DICT_INIT_ARRAY(info_items); spa_node_emit_port_info(&this->hooks, SPA_DIRECTION_OUTPUT, 0, &port->info); port->info.change_mask = old; } } static int port_get_format(struct port *port, uint32_t index, const struct spa_pod *filter, struct spa_pod **param, struct spa_pod_builder *builder) { struct spa_pod_frame f; if (!port->have_format) return -EIO; if (index > 0) return 0; spa_pod_builder_push_object(builder, &f, SPA_TYPE_OBJECT_Format, SPA_PARAM_Format); spa_pod_builder_add(builder, SPA_FORMAT_mediaType, SPA_POD_Id(port->current_format.media_type), SPA_FORMAT_mediaSubtype, SPA_POD_Id(port->current_format.media_subtype), 0); switch (port->current_format.media_subtype) { case SPA_MEDIA_SUBTYPE_raw: spa_pod_builder_add(builder, SPA_FORMAT_VIDEO_format, SPA_POD_Id(port->current_format.info.raw.format), 0); if (port->have_modifier) spa_pod_builder_add(builder, SPA_FORMAT_VIDEO_modifier, SPA_POD_Long(0), 0); spa_pod_builder_add(builder, SPA_FORMAT_VIDEO_size, SPA_POD_Rectangle(&port->current_format.info.raw.size), SPA_FORMAT_VIDEO_framerate, SPA_POD_Fraction(&port->current_format.info.raw.framerate), 0); break; case SPA_MEDIA_SUBTYPE_mjpg: case SPA_MEDIA_SUBTYPE_jpeg: spa_pod_builder_add(builder, SPA_FORMAT_VIDEO_size, SPA_POD_Rectangle(&port->current_format.info.mjpg.size), SPA_FORMAT_VIDEO_framerate, SPA_POD_Fraction(&port->current_format.info.mjpg.framerate), 0); break; case SPA_MEDIA_SUBTYPE_h264: spa_pod_builder_add(builder, SPA_FORMAT_VIDEO_size, SPA_POD_Rectangle(&port->current_format.info.h264.size), SPA_FORMAT_VIDEO_framerate, SPA_POD_Fraction(&port->current_format.info.h264.framerate), 0); break; default: return -EIO; } *param = spa_pod_builder_pop(builder, &f); return 1; } static int impl_node_enum_params(void *object, int seq, uint32_t id, uint32_t start, uint32_t num, const struct spa_pod *filter) { struct impl *this = object; struct spa_pod *param; spa_auto(spa_pod_dynamic_builder) b = { 0 }; struct spa_pod_builder_state state; uint8_t buffer[1024]; struct spa_result_node_params result; uint32_t count = 0; int res; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(num != 0, -EINVAL); spa_pod_dynamic_builder_init(&b, buffer, sizeof(buffer), 4096); spa_pod_builder_get_state(&b.b, &state); result.id = id; result.next = start; next: result.index = result.next++; spa_pod_builder_reset(&b.b, &state); switch (id) { case SPA_PARAM_PropInfo: { struct props *p = &this->props; switch (result.index) { case 0: param = spa_pod_builder_add_object(&b.b, SPA_TYPE_OBJECT_PropInfo, id, SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_device), SPA_PROP_INFO_description, SPA_POD_String("The V4L2 device"), SPA_PROP_INFO_type, SPA_POD_String(p->device)); break; case 1: param = spa_pod_builder_add_object(&b.b, SPA_TYPE_OBJECT_PropInfo, id, SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_deviceName), SPA_PROP_INFO_description, SPA_POD_String("The V4L2 device name"), SPA_PROP_INFO_type, SPA_POD_String(p->device_name)); break; case 2: param = spa_pod_builder_add_object(&b.b, SPA_TYPE_OBJECT_PropInfo, id, SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_deviceFd), SPA_PROP_INFO_description, SPA_POD_String("The V4L2 fd"), SPA_PROP_INFO_type, SPA_POD_Int(p->device_fd)); break; default: return spa_v4l2_enum_controls(this, seq, result.index - 3, num, filter); } break; } case SPA_PARAM_Props: { struct props *p = &this->props; struct spa_pod_frame f; struct port *port = &this->out_ports[0]; uint32_t i; if ((res = spa_v4l2_update_controls(this)) < 0) { spa_log_error(this->log, "error: %s", spa_strerror(res)); return res; } switch (result.index) { case 0: spa_pod_builder_push_object(&b.b, &f, SPA_TYPE_OBJECT_Props, id); spa_pod_builder_add(&b.b, SPA_PROP_device, SPA_POD_String(p->device), SPA_PROP_deviceName, SPA_POD_String(p->device_name), SPA_PROP_deviceFd, SPA_POD_Int(p->device_fd), 0); for (i = 0; i < port->n_controls; i++) { struct control *c = &port->controls[i]; spa_pod_builder_prop(&b.b, c->id, 0); switch (c->type) { case SPA_TYPE_Int: spa_pod_builder_int(&b.b, c->value); break; case SPA_TYPE_Bool: spa_pod_builder_bool(&b.b, c->value); break; default: spa_pod_builder_int(&b.b, c->value); break; } } param = spa_pod_builder_pop(&b.b, &f); break; default: return 0; } break; } case SPA_PARAM_EnumFormat: return spa_v4l2_enum_format(this, seq, start, num, filter); case SPA_PARAM_Format: if((res = port_get_format(GET_OUT_PORT(this, 0), result.index, filter, ¶m, &b.b)) <= 0) return res; break; default: return -ENOENT; } if (spa_pod_filter(&b.b, &result.param, param, filter) < 0) goto next; spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); if (++count != num) goto next; return 0; } static int impl_node_set_param(void *object, uint32_t id, uint32_t flags, const struct spa_pod *param) { struct impl *this = object; spa_return_val_if_fail(this != NULL, -EINVAL); switch (id) { case SPA_PARAM_Props: { struct props *p = &this->props; struct spa_pod_object *obj = (struct spa_pod_object *) param; struct spa_pod_prop *prop; if (param == NULL) { reset_props(p); return 0; } SPA_POD_OBJECT_FOREACH(obj, prop) { switch (prop->key) { case SPA_PROP_device: strncpy(p->device, (char *)SPA_POD_CONTENTS(struct spa_pod_string, &prop->value), sizeof(p->device)-1); break; default: spa_v4l2_set_control(this, prop, SPA_POD_BODY_CONST(&prop->value)); break; } } this->info.change_mask |= SPA_NODE_CHANGE_MASK_PARAMS; this->params[NODE_Props].flags ^= SPA_PARAM_INFO_SERIAL; emit_node_info(this, true); break; } default: return -ENOENT; } return 0; } static int impl_node_set_io(void *object, uint32_t id, void *data, size_t size) { struct impl *this = object; spa_return_val_if_fail(this != NULL, -EINVAL); switch (id) { case SPA_IO_Clock: this->clock = data; if (this->clock) { SPA_FLAG_SET(this->clock->flags, SPA_IO_CLOCK_FLAG_NO_RATE); spa_scnprintf(this->clock->name, sizeof(this->clock->name), "%s", this->props.clock_name); } break; case SPA_IO_Position: this->position = data; break; default: return -ENOENT; } return 0; } static int impl_node_send_command(void *object, const struct spa_command *command) { struct impl *this = object; struct port *port; int res; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(command != NULL, -EINVAL); port = GET_OUT_PORT(this, 0); switch (SPA_NODE_COMMAND_ID(command)) { case SPA_NODE_COMMAND_ParamBegin: if ((res = spa_v4l2_open(&port->dev, this->props.device)) < 0) return res; break; case SPA_NODE_COMMAND_ParamEnd: if (port->have_format) return 0; if ((res = spa_v4l2_close(&port->dev)) < 0) return res; break; case SPA_NODE_COMMAND_Start: { if (!port->have_format) { spa_log_error(this->log, "no format"); return -EIO; } if (port->n_buffers == 0) { spa_log_error(this->log, "no buffers"); return -EIO; } if ((res = spa_v4l2_stream_on(this)) < 0) return res; break; } case SPA_NODE_COMMAND_Pause: case SPA_NODE_COMMAND_Suspend: if ((res = spa_v4l2_stream_off(this)) < 0) return res; break; default: return -ENOTSUP; } return 0; } static int impl_node_add_listener(void *object, struct spa_hook *listener, const struct spa_node_events *events, void *data) { struct impl *this = object; struct spa_hook_list save; spa_return_val_if_fail(this != NULL, -EINVAL); spa_hook_list_isolate(&this->hooks, &save, listener, events, data); emit_node_info(this, true); emit_port_info(this, GET_OUT_PORT(this, 0), true); spa_hook_list_join(&this->hooks, &save); return 0; } static int impl_node_set_callbacks(void *object, const struct spa_node_callbacks *callbacks, void *data) { struct impl *this = object; spa_return_val_if_fail(this != NULL, -EINVAL); this->callbacks = SPA_CALLBACKS_INIT(callbacks, data); return 0; } static int impl_node_sync(void *object, int seq) { struct impl *this = object; spa_return_val_if_fail(this != NULL, -EINVAL); spa_node_emit_result(&this->hooks, seq, 0, 0, NULL); return 0; } static int impl_node_add_port(void *object, enum spa_direction direction, uint32_t port_id, const struct spa_dict *props) { return -ENOTSUP; } static int impl_node_remove_port(void *object, enum spa_direction direction, uint32_t port_id) { return -ENOTSUP; } static int impl_node_port_enum_params(void *object, int seq, enum spa_direction direction, uint32_t port_id, uint32_t id, uint32_t start, uint32_t num, const struct spa_pod *filter) { struct impl *this = object; struct port *port; struct spa_pod *param; spa_auto(spa_pod_dynamic_builder) b = { 0 }; struct spa_pod_builder_state state; uint8_t buffer[1024]; struct spa_result_node_params result; uint32_t count = 0; int res; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(num != 0, -EINVAL); spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); spa_pod_dynamic_builder_init(&b, buffer, sizeof(buffer), 4096); spa_pod_builder_get_state(&b.b, &state); port = GET_PORT(this, direction, port_id); result.id = id; result.next = start; next: result.index = result.next++; spa_pod_builder_reset(&b.b, &state); switch (id) { case SPA_PARAM_PropInfo: return spa_v4l2_enum_controls(this, seq, start, num, filter); case SPA_PARAM_EnumFormat: return spa_v4l2_enum_format(this, seq, start, num, filter); case SPA_PARAM_Format: if((res = port_get_format(port, result.index, filter, ¶m, &b.b)) <= 0) return res; break; case SPA_PARAM_Buffers: if (!port->have_format) return -EIO; if (result.index > 0) return 0; if (port->max_buffers == 0) return -EIO; param = spa_pod_builder_add_object(&b.b, SPA_TYPE_OBJECT_ParamBuffers, id, SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(SPA_MIN(4u, port->max_buffers), 1, port->max_buffers), SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(1), SPA_PARAM_BUFFERS_size, SPA_POD_Int(port->fmt.fmt.pix.sizeimage), SPA_PARAM_BUFFERS_stride, SPA_POD_Int(port->fmt.fmt.pix.bytesperline)); break; case SPA_PARAM_Meta: switch (result.index) { case 0: param = spa_pod_builder_add_object(&b.b, SPA_TYPE_OBJECT_ParamMeta, id, SPA_PARAM_META_type, SPA_POD_Id(SPA_META_Header), SPA_PARAM_META_size, SPA_POD_Int(sizeof(struct spa_meta_header))); break; case 1: param = spa_pod_builder_add_object(&b.b, SPA_TYPE_OBJECT_ParamMeta, id, SPA_PARAM_META_type, SPA_POD_Id(SPA_META_VideoTransform), SPA_PARAM_META_size, SPA_POD_Int(sizeof(struct spa_meta_videotransform))); break; default: return 0; } break; case SPA_PARAM_IO: switch (result.index) { case 0: param = spa_pod_builder_add_object(&b.b, SPA_TYPE_OBJECT_ParamIO, id, SPA_PARAM_IO_id, SPA_POD_Id(SPA_IO_Buffers), SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_buffers))); break; case 1: param = spa_pod_builder_add_object(&b.b, SPA_TYPE_OBJECT_ParamIO, id, SPA_PARAM_IO_id, SPA_POD_Id(SPA_IO_Clock), SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_clock))); break; case 2: param = spa_pod_builder_add_object(&b.b, SPA_TYPE_OBJECT_ParamIO, id, SPA_PARAM_IO_id, SPA_POD_Id(SPA_IO_Control), SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_sequence))); break; default: return 0; } break; case SPA_PARAM_Latency: switch (result.index) { case 0: case 1: param = spa_latency_build(&b.b, id, &this->latency[result.index]); break; default: return 0; } break; default: return -ENOENT; } if (spa_pod_filter(&b.b, &result.param, param, filter) < 0) goto next; spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); if (++count != num) goto next; return 0; } static int port_set_format(struct impl *this, struct port *port, uint32_t flags, const struct spa_pod *format) { struct spa_video_info info; int res = 0; spa_zero(info); if (port->have_format) { spa_v4l2_stream_off(this); spa_v4l2_clear_buffers(this); } if (format == NULL) { if (!port->have_format) return 0; port->have_format = false; port->dev.have_format = false; spa_v4l2_close(&port->dev); goto done; } else { if ((res = spa_format_parse(format, &info.media_type, &info.media_subtype)) < 0) return res; if (info.media_type != SPA_MEDIA_TYPE_video) { spa_log_error(this->log, "media type must be video"); return -EINVAL; } switch (info.media_subtype) { case SPA_MEDIA_SUBTYPE_raw: if (spa_format_video_raw_parse(format, &info.info.raw) < 0) { spa_log_error(this->log, "can't parse video raw"); return -EINVAL; } port->have_modifier = info.info.raw.flags & SPA_VIDEO_FLAG_MODIFIER; break; case SPA_MEDIA_SUBTYPE_mjpg: if (spa_format_video_mjpg_parse(format, &info.info.mjpg) < 0) return -EINVAL; break; case SPA_MEDIA_SUBTYPE_h264: if (spa_format_video_h264_parse(format, &info.info.h264) < 0) return -EINVAL; break; default: return -EINVAL; } } if (port->have_format && !SPA_FLAG_IS_SET(flags, SPA_NODE_PARAM_FLAG_TEST_ONLY)) { port->have_format = false; } if ((res = spa_v4l2_set_format(this, &info, flags)) < 0) return res; if (!SPA_FLAG_IS_SET(flags, SPA_NODE_PARAM_FLAG_TEST_ONLY)) { port->current_format = info; port->have_format = true; } done: this->info.change_mask |= SPA_NODE_CHANGE_MASK_PARAMS; port->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; port->params[PORT_Latency].flags ^= SPA_PARAM_INFO_SERIAL; if (port->have_format) { uint64_t latency; latency = port->info.rate.num * SPA_NSEC_PER_SEC / port->info.rate.denom; this->latency[SPA_DIRECTION_OUTPUT] = SPA_LATENCY_INFO(SPA_DIRECTION_OUTPUT, .min_ns = latency, .max_ns = latency); port->params[PORT_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_READWRITE); port->params[PORT_Buffers] = SPA_PARAM_INFO(SPA_PARAM_Buffers, SPA_PARAM_INFO_READ); this->params[NODE_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_READ); } else { this->latency[SPA_DIRECTION_OUTPUT] = SPA_LATENCY_INFO(SPA_DIRECTION_OUTPUT); port->params[PORT_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); port->params[PORT_Buffers] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); this->params[NODE_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, 0); } emit_port_info(this, port, false); emit_node_info(this, false); return res; } static int impl_node_port_set_param(void *object, enum spa_direction direction, uint32_t port_id, uint32_t id, uint32_t flags, const struct spa_pod *param) { int res = 0; struct impl *this = object; struct port *port; spa_return_val_if_fail(object != NULL, -EINVAL); spa_return_val_if_fail(CHECK_PORT(object, direction, port_id), -EINVAL); port = GET_PORT(this, direction, port_id); switch (id) { case SPA_PARAM_Latency: { struct spa_latency_info info; if (param == NULL) info = SPA_LATENCY_INFO(SPA_DIRECTION_REVERSE(direction)); else if ((res = spa_latency_parse(param, &info)) < 0) return res; if (direction == info.direction) return -EINVAL; this->latency[info.direction] = info; port->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; port->params[PORT_Latency].flags ^= SPA_PARAM_INFO_SERIAL; emit_port_info(this, port, false); break; } case SPA_PARAM_Format: res = port_set_format(object, port, flags, param); break; default: res = -ENOENT; } return res; } static int impl_node_port_use_buffers(void *object, enum spa_direction direction, uint32_t port_id, uint32_t flags, struct spa_buffer **buffers, uint32_t n_buffers) { struct impl *this = object; struct port *port; int res; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); port = GET_PORT(this, direction, port_id); if (port->n_buffers) { spa_v4l2_stream_off(this); if ((res = spa_v4l2_clear_buffers(this)) < 0) return res; } if (n_buffers > 0 && !port->have_format) return -EIO; if (n_buffers > MAX_BUFFERS) return -ENOSPC; if (buffers == NULL) return 0; if (flags & SPA_NODE_BUFFERS_FLAG_ALLOC) { res = spa_v4l2_alloc_buffers(this, buffers, n_buffers); } else { res = spa_v4l2_use_buffers(this, buffers, n_buffers); } return res; } static int impl_node_port_set_io(void *object, enum spa_direction direction, uint32_t port_id, uint32_t id, void *data, size_t size) { struct impl *this = object; struct port *port; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); port = GET_PORT(this, direction, port_id); switch (id) { case SPA_IO_Buffers: port->io = data; break; case SPA_IO_Control: port->control = data; port->control_size = size; break; default: return -ENOENT; } return 0; } static int impl_node_port_reuse_buffer(void *object, uint32_t port_id, uint32_t buffer_id) { struct impl *this = object; struct port *port; int res; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(port_id == 0, -EINVAL); port = GET_OUT_PORT(this, port_id); spa_return_val_if_fail(buffer_id < port->n_buffers, -EINVAL); res = spa_v4l2_buffer_recycle(this, buffer_id); return res; } static int process_control(struct impl *this, struct spa_pod_sequence *control, uint32_t size) { struct spa_pod_parser parser[2]; struct spa_pod_frame frame[2]; struct spa_pod_sequence seq; const void *seq_body, *c_body; struct spa_pod_control c; spa_pod_parser_init_from_data(&parser[0], control, size, 0, size); if (spa_pod_parser_push_sequence_body(&parser[0], &frame[0], &seq, &seq_body) < 0) return 0; while (spa_pod_parser_get_control_body(&parser[0], &c, &c_body) >= 0) { switch (c.type) { case SPA_CONTROL_Properties: { struct spa_pod_object obj; struct spa_pod_prop prop; const void *obj_body, *prop_body; if (spa_pod_parser_init_object_body(&parser[1], &frame[1], &c.value, c_body, &obj, &obj_body) < 0) continue; while (spa_pod_parser_get_prop_body(&parser[1], &prop, &prop_body) >= 0) spa_v4l2_set_control(this, &prop, prop_body); break; } default: break; } } return 0; } static int impl_node_process(void *object) { struct impl *this = object; int res; struct spa_io_buffers *io; struct port *port; struct buffer *b; spa_return_val_if_fail(this != NULL, -EINVAL); port = GET_OUT_PORT(this, 0); if ((io = port->io) == NULL) return -EIO; if (port->control) process_control(this, &port->control->sequence, port->control_size); spa_log_trace(this->log, "%p; status %d", this, io->status); if (io->status == SPA_STATUS_HAVE_DATA) return SPA_STATUS_HAVE_DATA; if (io->buffer_id < port->n_buffers) { if ((res = spa_v4l2_buffer_recycle(this, io->buffer_id)) < 0) return res; io->buffer_id = SPA_ID_INVALID; } if (spa_list_is_empty(&port->queue)) return SPA_STATUS_OK; b = spa_list_first(&port->queue, struct buffer, link); spa_list_remove(&b->link); SPA_FLAG_SET(b->flags, BUFFER_FLAG_OUTSTANDING); spa_log_trace(this->log, "%p: dequeue buffer %d", this, b->id); io->buffer_id = b->id; io->status = SPA_STATUS_HAVE_DATA; return SPA_STATUS_HAVE_DATA; } static const struct spa_node_methods impl_node = { SPA_VERSION_NODE_METHODS, .add_listener = impl_node_add_listener, .set_callbacks = impl_node_set_callbacks, .sync = impl_node_sync, .enum_params = impl_node_enum_params, .set_param = impl_node_set_param, .set_io = impl_node_set_io, .send_command = impl_node_send_command, .add_port = impl_node_add_port, .remove_port = impl_node_remove_port, .port_enum_params = impl_node_port_enum_params, .port_set_param = impl_node_port_set_param, .port_use_buffers = impl_node_port_use_buffers, .port_set_io = impl_node_port_set_io, .port_reuse_buffer = impl_node_port_reuse_buffer, .process = impl_node_process, }; static int impl_get_interface(struct spa_handle *handle, const char *type, void **interface) { struct impl *this; spa_return_val_if_fail(handle != NULL, -EINVAL); spa_return_val_if_fail(interface != NULL, -EINVAL); this = (struct impl *) handle; if (spa_streq(type, SPA_TYPE_INTERFACE_Node)) *interface = &this->node; else return -ENOENT; return 0; } static int impl_clear(struct spa_handle *handle) { return 0; } static size_t impl_get_size(const struct spa_handle_factory *factory, const struct spa_dict *params) { return sizeof(struct impl); } static int impl_init(const struct spa_handle_factory *factory, struct spa_handle *handle, const struct spa_dict *info, const struct spa_support *support, uint32_t n_support) { struct impl *this; struct port *port; uint32_t i; int res; bool have_clock = false; spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(handle != NULL, -EINVAL); handle->get_interface = impl_get_interface; handle->clear = impl_clear; this = (struct impl *) handle; this->log = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log); v4l2_log_topic_init(this->log); this->data_loop = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_DataLoop); if (this->data_loop == NULL) { spa_log_error(this->log, "a data_loop is needed"); return -EINVAL; } this->node.iface = SPA_INTERFACE_INIT( SPA_TYPE_INTERFACE_Node, SPA_VERSION_NODE, &impl_node, this); spa_hook_list_init(&this->hooks); this->latency[SPA_DIRECTION_INPUT] = SPA_LATENCY_INFO(SPA_DIRECTION_INPUT); this->latency[SPA_DIRECTION_OUTPUT] = SPA_LATENCY_INFO(SPA_DIRECTION_OUTPUT); this->info_all = SPA_NODE_CHANGE_MASK_FLAGS | SPA_NODE_CHANGE_MASK_PROPS | SPA_NODE_CHANGE_MASK_PARAMS; this->info = SPA_NODE_INFO_INIT(); this->info.max_output_ports = 1; this->info.flags = SPA_NODE_FLAG_RT; this->params[NODE_PropInfo] = SPA_PARAM_INFO(SPA_PARAM_PropInfo, SPA_PARAM_INFO_READ); this->params[NODE_Props] = SPA_PARAM_INFO(SPA_PARAM_Props, SPA_PARAM_INFO_READWRITE); this->params[NODE_EnumFormat] = SPA_PARAM_INFO(SPA_PARAM_EnumFormat, SPA_PARAM_INFO_READ); this->params[NODE_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, 0); this->info.params = this->params; this->info.n_params = N_NODE_PARAMS; reset_props(&this->props); port = GET_OUT_PORT(this, 0); port->impl = this; spa_list_init(&port->queue); port->info_all = SPA_PORT_CHANGE_MASK_FLAGS | SPA_PORT_CHANGE_MASK_PROPS | SPA_PORT_CHANGE_MASK_PARAMS; port->info = SPA_PORT_INFO_INIT(); port->info.flags = SPA_PORT_FLAG_LIVE | SPA_PORT_FLAG_PHYSICAL | SPA_PORT_FLAG_TERMINAL; port->params[PORT_PropInfo] = SPA_PARAM_INFO(SPA_PARAM_PropInfo, SPA_PARAM_INFO_READ); port->params[PORT_EnumFormat] = SPA_PARAM_INFO(SPA_PARAM_EnumFormat, SPA_PARAM_INFO_READ); port->params[PORT_Meta] = SPA_PARAM_INFO(SPA_PARAM_Meta, SPA_PARAM_INFO_READ); port->params[PORT_IO] = SPA_PARAM_INFO(SPA_PARAM_IO, SPA_PARAM_INFO_READ); port->params[PORT_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); port->params[PORT_Buffers] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); port->params[PORT_Latency] = SPA_PARAM_INFO(SPA_PARAM_Latency, SPA_PARAM_INFO_READ); port->info.params = port->params; port->info.n_params = N_PORT_PARAMS; port->probed_expbuf = false; port->have_query_ext_ctrl = true; port->dev.log = this->log; port->dev.fd = -1; for (i = 0; info && i < info->n_items; i++) { const char *k = info->items[i].key; const char *s = info->items[i].value; if (spa_streq(k, SPA_KEY_API_V4L2_PATH)) { strncpy(this->props.device, s, 63); } else if (spa_streq(k, "meta.videotransform.transform")) { this->transform = spa_debug_type_find_type_short(spa_type_meta_videotransform_type, s); } else if (spa_streq(k, "clock.name")) { spa_scnprintf(this->props.clock_name, sizeof(this->props.clock_name), "%s", s); have_clock = true; } } if ((res = spa_v4l2_open(&port->dev, this->props.device)) < 0) return res; spa_v4l2_close(&port->dev); if (!have_clock) { spa_scnprintf(this->props.clock_name, sizeof(this->props.clock_name), "api.v4l2.%s", port->dev.cap.bus_info); } return 0; } static const struct spa_interface_info impl_interfaces[] = { {SPA_TYPE_INTERFACE_Node,}, }; static int impl_enum_interface_info(const struct spa_handle_factory *factory, const struct spa_interface_info **info, uint32_t *index) { spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(info != NULL, -EINVAL); spa_return_val_if_fail(index != NULL, -EINVAL); if (*index >= SPA_N_ELEMENTS(impl_interfaces)) return 0; *info = &impl_interfaces[(*index)++]; return 1; } const struct spa_handle_factory spa_v4l2_source_factory = { SPA_VERSION_HANDLE_FACTORY, SPA_NAME_API_V4L2_SOURCE, NULL, impl_get_size, impl_init, impl_enum_interface_info, }; pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/v4l2/v4l2-udev.c000066400000000000000000000457241511204443500254410ustar00rootroot00000000000000/* Spa V4l2 udev monitor */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "v4l2.h" #ifdef HAVE_LOGIND #include #endif #define MAX_DEVICES 64 enum action { ACTION_CHANGE, ACTION_REMOVE, }; struct device { uint32_t id; struct udev_device *dev; int inotify_wd; unsigned int accessible:1; unsigned int emitted:1; }; struct impl { struct spa_handle handle; struct spa_device device; struct spa_log *log; struct spa_loop *main_loop; struct spa_hook_list hooks; uint64_t info_all; struct spa_device_info info; struct udev *udev; struct udev_monitor *umonitor; struct device devices[MAX_DEVICES]; uint32_t n_devices; struct spa_source source; struct spa_source notify; #ifdef HAVE_LOGIND struct spa_source logind; sd_login_monitor *logind_monitor; #endif }; static int impl_udev_open(struct impl *this) { if (this->udev == NULL) { this->udev = udev_new(); if (this->udev == NULL) return -ENOMEM; } return 0; } static int impl_udev_close(struct impl *this) { if (this->udev != NULL) udev_unref(this->udev); this->udev = NULL; return 0; } static void start_watching_device(struct impl *this, struct device *device) { if (this->notify.fd < 0 || device->inotify_wd >= 0) return; char path[64]; snprintf(path, sizeof(path), "/dev/video%" PRIu32, device->id); device->inotify_wd = inotify_add_watch(this->notify.fd, path, IN_ATTRIB); } static void stop_watching_device(struct impl *this, struct device *device) { if (device->inotify_wd < 0) return; spa_assert(this->notify.fd >= 0); inotify_rm_watch(this->notify.fd, device->inotify_wd); device->inotify_wd = -1; } static struct device *add_device(struct impl *this, uint32_t id, struct udev_device *dev) { struct device *device; if (this->n_devices >= MAX_DEVICES) return NULL; device = &this->devices[this->n_devices++]; spa_zero(*device); device->id = id; udev_device_ref(dev); device->dev = dev; device->inotify_wd = -1; start_watching_device(this, device); return device; } static struct device *find_device(struct impl *this, uint32_t id) { uint32_t i; for (i = 0; i < this->n_devices; i++) { if (this->devices[i].id == id) return &this->devices[i]; } return NULL; } static void remove_device(struct impl *this, struct device *device) { device->dev = udev_device_unref(device->dev); stop_watching_device(this, device); *device = this->devices[--this->n_devices]; } static void clear_devices(struct impl *this) { while (this->n_devices > 0) remove_device(this, &this->devices[0]); } static uint32_t get_device_id(struct impl *this, struct udev_device *dev) { const char *str; if ((str = udev_device_get_devnode(dev)) == NULL) return SPA_ID_INVALID; if (!(str = strrchr(str, '/'))) return SPA_ID_INVALID; if (strlen(str) <= 6 || strncmp(str, "/video", 6) != 0) return SPA_ID_INVALID; return atoi(str + 6); } static int dehex(char x) { if (x >= '0' && x <= '9') return x - '0'; if (x >= 'A' && x <= 'F') return x - 'A' + 10; if (x >= 'a' && x <= 'f') return x - 'a' + 10; return -1; } static void unescape(const char *src, char *dst) { const char *s; char *d; int h1 = 0, h2 = 0; enum { TEXT, BACKSLASH, EX, FIRST } state = TEXT; for (s = src, d = dst; *s; s++) { switch (state) { case TEXT: if (*s == '\\') state = BACKSLASH; else *(d++) = *s; break; case BACKSLASH: if (*s == 'x') state = EX; else { *(d++) = '\\'; *(d++) = *s; state = TEXT; } break; case EX: h1 = dehex(*s); if (h1 < 0) { *(d++) = '\\'; *(d++) = 'x'; *(d++) = *s; state = TEXT; } else state = FIRST; break; case FIRST: h2 = dehex(*s); if (h2 < 0) { *(d++) = '\\'; *(d++) = 'x'; *(d++) = *(s-1); *(d++) = *s; } else *(d++) = (char) (h1 << 4) | h2; state = TEXT; break; } } switch (state) { case TEXT: break; case BACKSLASH: *(d++) = '\\'; break; case EX: *(d++) = '\\'; *(d++) = 'x'; break; case FIRST: *(d++) = '\\'; *(d++) = 'x'; *(d++) = *(s-1); break; } *d = 0; } static int emit_object_info(struct impl *this, struct device *device) { struct spa_device_object_info info; uint32_t id = device->id; struct udev_device *dev = device->dev; const char *str; struct spa_dict_item items[21]; uint32_t n_items = 0; char devnum[32]; info = SPA_DEVICE_OBJECT_INFO_INIT(); info.type = SPA_TYPE_INTERFACE_Device; info.factory_name = SPA_NAME_API_V4L2_DEVICE; info.change_mask = SPA_DEVICE_OBJECT_CHANGE_MASK_FLAGS | SPA_DEVICE_OBJECT_CHANGE_MASK_PROPS; info.flags = 0; items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_ENUM_API,"udev"); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_API, "v4l2"); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_MEDIA_CLASS, "Video/Device"); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_API_V4L2_PATH, udev_device_get_devnode(dev)); snprintf(devnum, sizeof(devnum), "%" PRId64, (int64_t)udev_device_get_devnum(dev)); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_DEVIDS, devnum); if ((str = udev_device_get_property_value(dev, "USEC_INITIALIZED")) && *str) items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_PLUGGED_USEC, str); str = udev_device_get_property_value(dev, "ID_PATH"); if (!(str && *str)) str = udev_device_get_syspath(dev); if (str && *str) { items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_BUS_PATH, str); } if ((str = udev_device_get_devpath(dev)) && *str) { items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_SYSFS_PATH, str); } if ((str = udev_device_get_property_value(dev, "ID_ID")) && *str) { items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_BUS_ID, str); } if ((str = udev_device_get_property_value(dev, "ID_BUS")) && *str) { items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_BUS, str); } if ((str = udev_device_get_property_value(dev, "SUBSYSTEM")) && *str) { items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_SUBSYSTEM, str); } if ((str = udev_device_get_property_value(dev, "ID_VENDOR_ID")) && *str) { int32_t val; if (spa_atoi32(str, &val, 16)) { char *dec = alloca(12); /* 0xffff is max */ snprintf(dec, 12, "0x%04x", val); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_VENDOR_ID, dec); } } str = udev_device_get_property_value(dev, "ID_VENDOR_FROM_DATABASE"); if (!(str && *str)) { str = udev_device_get_property_value(dev, "ID_VENDOR_ENC"); if (!(str && *str)) { str = udev_device_get_property_value(dev, "ID_VENDOR"); } else { char *t = alloca(strlen(str) + 1); unescape(str, t); str = t; } } if (str && *str) { items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_VENDOR_NAME, str); } if ((str = udev_device_get_property_value(dev, "ID_MODEL_ID")) && *str) { int32_t val; if (spa_atoi32(str, &val, 16)) { char *dec = alloca(12); /* 0xffff is max */ snprintf(dec, 12, "0x%04x", val); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_PRODUCT_ID, dec); } } str = udev_device_get_property_value(dev, "ID_MODEL_FROM_DATABASE"); if (!(str && *str)) { str = udev_device_get_property_value(dev, "ID_MODEL_ENC"); if (!(str && *str)) { str = udev_device_get_property_value(dev, "ID_MODEL"); if (!(str && *str)) str = udev_device_get_property_value(dev, "ID_V4L_PRODUCT"); } else { char *t = alloca(strlen(str) + 1); unescape(str, t); str = t; } } if (str && *str) items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_PRODUCT_NAME, str); if ((str = udev_device_get_property_value(dev, "ID_SERIAL")) && *str) { items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_SERIAL, str); } if ((str = udev_device_get_property_value(dev, "ID_V4L_CAPABILITIES")) && *str) { items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_CAPABILITIES, str); } info.props = &SPA_DICT_INIT(items, n_items); spa_device_emit_object_info(&this->hooks, id, &info); device->emitted = true; return 1; } static bool check_access(struct impl *this, struct device *device) { char path[128]; snprintf(path, sizeof(path), "/dev/video%u", device->id); device->accessible = access(path, R_OK|W_OK) >= 0; spa_log_debug(this->log, "%s accessible:%u", path, device->accessible); return device->accessible; } static void process_device(struct impl *this, enum action action, struct device *device) { switch (action) { case ACTION_CHANGE: check_access(this, device); if (device->accessible && !device->emitted) { emit_object_info(this, device); } else if (!device->accessible && device->emitted) { device->emitted = false; spa_device_emit_object_info(&this->hooks, device->id, NULL); } break; case ACTION_REMOVE: { bool emitted = device->emitted; uint32_t id = device->id; remove_device(this, device); if (emitted) spa_device_emit_object_info(&this->hooks, id, NULL); break; } } } static void process_udev_device(struct impl *this, enum action action, struct udev_device *udev_device) { struct device *device; uint32_t id; if ((id = get_device_id(this, udev_device)) == SPA_ID_INVALID) return; device = find_device(this, id); if (action == ACTION_CHANGE && !device) device = add_device(this, id, udev_device); if (!device) return; process_device(this, action, device); } static int stop_inotify(struct impl *this) { if (this->notify.fd == -1) return 0; spa_log_info(this->log, "stop inotify"); for (size_t i = 0; i < this->n_devices; i++) stop_watching_device(this, &this->devices[i]); spa_loop_remove_source(this->main_loop, &this->notify); close(this->notify.fd); this->notify.fd = -1; return 0; } static void impl_on_notify_events(struct spa_source *source) { struct impl *this = source->data; union { unsigned char name[sizeof(struct inotify_event) + NAME_MAX + 1]; struct inotify_event e; /* for appropriate alignment */ } buf; while (true) { ssize_t len; const struct inotify_event *event; void *p, *e; len = read(source->fd, &buf, sizeof(buf)); if (len <= 0) break; e = SPA_PTROFF(&buf, len, void); for (p = &buf; p < e; p = SPA_PTROFF(p, sizeof(struct inotify_event) + event->len, void)) { event = (const struct inotify_event *) p; struct device *device = NULL; for (size_t i = 0; i < this->n_devices; i++) { if (this->devices[i].inotify_wd == event->wd) { device = &this->devices[i]; break; } } if (!device) continue; if (event->mask & IN_ATTRIB) process_device(this, ACTION_CHANGE, device); if (event->mask & IN_IGNORED) device->inotify_wd = -1; } } } static int start_inotify(struct impl *this) { int notify_fd; #ifdef HAVE_LOGIND /* Do not use inotify when using logind session monitoring */ if (this->logind_monitor) return 0; #endif if (this->notify.fd != -1) return 0; if ((notify_fd = inotify_init1(IN_CLOEXEC | IN_NONBLOCK)) < 0) return -errno; spa_log_info(this->log, "start inotify"); this->notify.func = impl_on_notify_events; this->notify.data = this; this->notify.fd = notify_fd; this->notify.mask = SPA_IO_IN | SPA_IO_ERR; spa_loop_add_source(this->main_loop, &this->notify); return 0; } #ifdef HAVE_LOGIND static void impl_on_logind_events(struct spa_source *source) { struct impl *this = source->data; /* Recheck access on all v4l2 devices on logind session changes */ for (size_t i = 0; i < this->n_devices; i++) process_device(this, ACTION_CHANGE, &this->devices[i]); sd_login_monitor_flush(this->logind_monitor); } static int start_logind(struct impl *this) { int res; if (this->logind_monitor) return 0; /* If we are not actually running logind become a NOP */ if (access("/run/systemd/seats/", F_OK) < 0) return 0; res = sd_login_monitor_new("session", &this->logind_monitor); if (res < 0) return res; spa_log_info(this->log, "start logind monitoring"); this->logind.func = impl_on_logind_events; this->logind.data = this; this->logind.fd = sd_login_monitor_get_fd(this->logind_monitor); this->logind.mask = SPA_IO_IN | SPA_IO_ERR; spa_loop_add_source(this->main_loop, &this->logind); return 0; } static void stop_logind(struct impl *this) { if (this->logind_monitor) { spa_loop_remove_source(this->main_loop, &this->logind); sd_login_monitor_unref(this->logind_monitor); this->logind_monitor = NULL; } } #else /* Stubs to avoid more ifdefs below */ static int start_logind(struct impl *this) { return 0; } static void stop_logind(struct impl *this) { } #endif static void impl_on_fd_events(struct spa_source *source) { struct impl *this = source->data; struct udev_device *dev; const char *action; dev = udev_monitor_receive_device(this->umonitor); if (dev == NULL) return; if ((action = udev_device_get_action(dev)) == NULL) action = "change"; spa_log_debug(this->log, "action %s", action); if (spa_streq(action, "add") || spa_streq(action, "change")) { process_udev_device(this, ACTION_CHANGE, dev); } else if (spa_streq(action, "remove")) { process_udev_device(this, ACTION_REMOVE, dev); } udev_device_unref(dev); } static int start_monitor(struct impl *this) { int res; if (this->umonitor != NULL) return 0; this->umonitor = udev_monitor_new_from_netlink(this->udev, "udev"); if (this->umonitor == NULL) return -ENOMEM; udev_monitor_filter_add_match_subsystem_devtype(this->umonitor, "video4linux", NULL); udev_monitor_enable_receiving(this->umonitor); this->source.func = impl_on_fd_events; this->source.data = this; this->source.fd = udev_monitor_get_fd(this->umonitor); this->source.mask = SPA_IO_IN | SPA_IO_ERR; spa_log_debug(this->log, "monitor %p", this->umonitor); spa_loop_add_source(this->main_loop, &this->source); if ((res = start_logind(this)) < 0) return res; if ((res = start_inotify(this)) < 0) return res; return 0; } static int stop_monitor(struct impl *this) { if (this->umonitor == NULL) return 0; clear_devices (this); spa_loop_remove_source(this->main_loop, &this->source); udev_monitor_unref(this->umonitor); this->umonitor = NULL; stop_inotify(this); stop_logind(this); return 0; } static int enum_devices(struct impl *this) { struct udev_enumerate *enumerate; struct udev_list_entry *devices; enumerate = udev_enumerate_new(this->udev); if (enumerate == NULL) return -ENOMEM; udev_enumerate_add_match_subsystem(enumerate, "video4linux"); udev_enumerate_scan_devices(enumerate); for (devices = udev_enumerate_get_list_entry(enumerate); devices; devices = udev_list_entry_get_next(devices)) { struct udev_device *dev; dev = udev_device_new_from_syspath(this->udev, udev_list_entry_get_name(devices)); if (dev == NULL) continue; process_udev_device(this, ACTION_CHANGE, dev); udev_device_unref(dev); } udev_enumerate_unref(enumerate); return 0; } static const struct spa_dict_item device_info_items[] = { { SPA_KEY_DEVICE_API, "udev" }, { SPA_KEY_DEVICE_NICK, "v4l2-udev" }, { SPA_KEY_API_UDEV_MATCH, "video4linux" }, }; static void emit_device_info(struct impl *this, bool full) { uint64_t old = full ? this->info.change_mask : 0; if (full) this->info.change_mask = this->info_all; if (this->info.change_mask) { this->info.props = &SPA_DICT_INIT_ARRAY(device_info_items); spa_device_emit_info(&this->hooks, &this->info); this->info.change_mask = old; } } static void impl_hook_removed(struct spa_hook *hook) { struct impl *this = hook->priv; if (spa_hook_list_is_empty(&this->hooks)) { stop_monitor(this); impl_udev_close(this); } } static int impl_device_add_listener(void *object, struct spa_hook *listener, const struct spa_device_events *events, void *data) { int res; struct impl *this = object; struct spa_hook_list save; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(events != NULL, -EINVAL); if ((res = impl_udev_open(this)) < 0) return res; spa_hook_list_isolate(&this->hooks, &save, listener, events, data); emit_device_info(this, true); if ((res = start_monitor(this)) < 0) return res; if ((res = enum_devices(this)) < 0) return res; spa_hook_list_join(&this->hooks, &save); listener->removed = impl_hook_removed; listener->priv = this; return 0; } static const struct spa_device_methods impl_device = { SPA_VERSION_DEVICE_METHODS, .add_listener = impl_device_add_listener, }; static int impl_get_interface(struct spa_handle *handle, const char *type, void **interface) { struct impl *this; spa_return_val_if_fail(handle != NULL, -EINVAL); spa_return_val_if_fail(interface != NULL, -EINVAL); this = (struct impl *) handle; if (spa_streq(type, SPA_TYPE_INTERFACE_Device)) *interface = &this->device; else return -ENOENT; return 0; } static int impl_clear(struct spa_handle *handle) { struct impl *this = (struct impl *) handle; stop_monitor(this); impl_udev_close(this); return 0; } static size_t impl_get_size(const struct spa_handle_factory *factory, const struct spa_dict *params) { return sizeof(struct impl); } static int impl_init(const struct spa_handle_factory *factory, struct spa_handle *handle, const struct spa_dict *info, const struct spa_support *support, uint32_t n_support) { struct impl *this; spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(handle != NULL, -EINVAL); handle->get_interface = impl_get_interface; handle->clear = impl_clear; this = (struct impl *) handle; this->notify.fd = -1; #ifdef HAVE_LOGIND this->logind_monitor = NULL; #endif this->log = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log); this->main_loop = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Loop); if (this->main_loop == NULL) { spa_log_error(this->log, "a main-loop is needed"); return -EINVAL; } spa_hook_list_init(&this->hooks); this->device.iface = SPA_INTERFACE_INIT( SPA_TYPE_INTERFACE_Device, SPA_VERSION_DEVICE, &impl_device, this); this->info = SPA_DEVICE_INFO_INIT(); this->info_all = SPA_DEVICE_CHANGE_MASK_FLAGS | SPA_DEVICE_CHANGE_MASK_PROPS; this->info.flags = 0; return 0; } static const struct spa_interface_info impl_interfaces[] = { {SPA_TYPE_INTERFACE_Device,}, }; static int impl_enum_interface_info(const struct spa_handle_factory *factory, const struct spa_interface_info **info, uint32_t *index) { spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(info != NULL, -EINVAL); spa_return_val_if_fail(index != NULL, -EINVAL); if (*index >= SPA_N_ELEMENTS(impl_interfaces)) return 0; *info = &impl_interfaces[(*index)++]; return 1; } const struct spa_handle_factory spa_v4l2_udev_factory = { SPA_VERSION_HANDLE_FACTORY, SPA_NAME_API_V4L2_ENUM_UDEV, NULL, impl_get_size, impl_init, impl_enum_interface_info, }; pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/v4l2/v4l2-utils.c000066400000000000000000001725241511204443500256350ustar00rootroot00000000000000/* Spa */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #include #include #include #include #include #include #include #include static int xioctl(int fd, int request, void *arg) { int err; do { err = ioctl(fd, request, arg); } while (err == -1 && errno == EINTR); return err; } int spa_v4l2_open(struct spa_v4l2_device *dev, const char *path) { struct stat st; int err; if (dev->fd != -1) return 0; if (path == NULL) { spa_log_error(dev->log, "Device property not set"); return -EIO; } spa_log_info(dev->log, "device is '%s'", path); dev->fd = open(path, O_RDWR | O_NONBLOCK, 0); if (dev->fd == -1) { err = errno; spa_log_error(dev->log, "Cannot open '%s': %d, %s", path, err, strerror(err)); goto error; } if (fstat(dev->fd, &st) < 0) { err = errno; spa_log_error(dev->log, "Cannot identify '%s': %d, %s", path, err, strerror(err)); goto error_close; } if (!S_ISCHR(st.st_mode)) { spa_log_error(dev->log, "%s is no device", path); err = ENODEV; goto error_close; } if (xioctl(dev->fd, VIDIOC_QUERYCAP, &dev->cap) < 0) { err = errno; spa_log_error(dev->log, "'%s' QUERYCAP: %m", path); goto error_close; } snprintf(dev->path, sizeof(dev->path), "%s", path); return 0; error_close: close(dev->fd); dev->fd = -1; error: return -err; } int spa_v4l2_is_capture(struct spa_v4l2_device *dev) { uint32_t caps = dev->cap.capabilities; if ((caps & V4L2_CAP_DEVICE_CAPS)) caps = dev->cap.device_caps; return (caps & V4L2_CAP_VIDEO_CAPTURE) == V4L2_CAP_VIDEO_CAPTURE; } int spa_v4l2_close(struct spa_v4l2_device *dev) { if (dev->fd == -1) return 0; if (dev->active || dev->have_format) return 0; spa_log_info(dev->log, "close '%s'", dev->path); if (close(dev->fd)) spa_log_warn(dev->log, "close: %m"); dev->fd = -1; return 0; } static int spa_v4l2_buffer_recycle(struct impl *this, uint32_t buffer_id) { struct port *port = &this->out_ports[0]; struct buffer *b = &port->buffers[buffer_id]; struct spa_v4l2_device *dev = &port->dev; int err; if (!SPA_FLAG_IS_SET(b->flags, BUFFER_FLAG_OUTSTANDING)) return 0; SPA_FLAG_CLEAR(b->flags, BUFFER_FLAG_OUTSTANDING); spa_log_trace(this->log, "v4l2 %p: recycle buffer %d", this, buffer_id); if (xioctl(dev->fd, VIDIOC_QBUF, &b->v4l2_buffer) < 0) { err = errno; spa_log_error(this->log, "'%s' VIDIOC_QBUF: %m", this->props.device); return -err; } return 0; } static int spa_v4l2_clear_buffers(struct impl *this) { struct port *port = &this->out_ports[0]; struct v4l2_requestbuffers reqbuf; uint32_t i; if (port->n_buffers == 0) return 0; for (i = 0; i < port->n_buffers; i++) { struct buffer *b; struct spa_data *d; b = &port->buffers[i]; d = b->outbuf->datas; if (SPA_FLAG_IS_SET(b->flags, BUFFER_FLAG_OUTSTANDING)) { spa_log_debug(this->log, "queueing outstanding buffer %p", b); spa_v4l2_buffer_recycle(this, i); } if (SPA_FLAG_IS_SET(b->flags, BUFFER_FLAG_MAPPED)) { munmap(b->ptr, d[0].maxsize); } if (b->mmap_ptr) munmap(b->mmap_ptr, b->v4l2_buffer.length); if (SPA_FLAG_IS_SET(b->flags, BUFFER_FLAG_ALLOCATED)) { spa_log_debug(this->log, "close %d", (int) d[0].fd); close(d[0].fd); } d[0].type = SPA_ID_INVALID; } spa_zero(reqbuf); reqbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; reqbuf.memory = port->memtype; reqbuf.count = 0; if (xioctl(port->dev.fd, VIDIOC_REQBUFS, &reqbuf) < 0) { spa_log_warn(this->log, "VIDIOC_REQBUFS: %m"); } port->n_buffers = 0; return 0; } struct format_info { uint32_t fourcc; uint32_t format; uint32_t media_type; uint32_t media_subtype; }; #define VIDEO SPA_MEDIA_TYPE_video #define IMAGE SPA_MEDIA_TYPE_image #define RAW SPA_MEDIA_SUBTYPE_raw #define BAYER SPA_MEDIA_SUBTYPE_bayer #define MJPG SPA_MEDIA_SUBTYPE_mjpg #define JPEG SPA_MEDIA_SUBTYPE_jpeg #define DV SPA_MEDIA_SUBTYPE_dv #define MPEGTS SPA_MEDIA_SUBTYPE_mpegts #define H264 SPA_MEDIA_SUBTYPE_h264 #define H263 SPA_MEDIA_SUBTYPE_h263 #define MPEG1 SPA_MEDIA_SUBTYPE_mpeg1 #define MPEG2 SPA_MEDIA_SUBTYPE_mpeg2 #define MPEG4 SPA_MEDIA_SUBTYPE_mpeg4 #define XVID SPA_MEDIA_SUBTYPE_xvid #define VC1 SPA_MEDIA_SUBTYPE_vc1 #define VP8 SPA_MEDIA_SUBTYPE_vp8 #define FORMAT_UNKNOWN SPA_VIDEO_FORMAT_UNKNOWN #define FORMAT_ENCODED SPA_VIDEO_FORMAT_ENCODED #define FORMAT_RGB15 SPA_VIDEO_FORMAT_RGB15 #define FORMAT_BGR15 SPA_VIDEO_FORMAT_BGR15 #define FORMAT_RGB16 SPA_VIDEO_FORMAT_RGB16 #define FORMAT_BGR SPA_VIDEO_FORMAT_BGR #define FORMAT_RGB SPA_VIDEO_FORMAT_RGB #define FORMAT_BGRA SPA_VIDEO_FORMAT_BGRA #define FORMAT_BGRx SPA_VIDEO_FORMAT_BGRx #define FORMAT_ARGB SPA_VIDEO_FORMAT_ARGB #define FORMAT_xRGB SPA_VIDEO_FORMAT_xRGB #define FORMAT_GRAY8 SPA_VIDEO_FORMAT_GRAY8 #define FORMAT_GRAY16_LE SPA_VIDEO_FORMAT_GRAY16_LE #define FORMAT_GRAY16_BE SPA_VIDEO_FORMAT_GRAY16_BE #define FORMAT_YVU9 SPA_VIDEO_FORMAT_YVU9 #define FORMAT_YV12 SPA_VIDEO_FORMAT_YV12 #define FORMAT_YUY2 SPA_VIDEO_FORMAT_YUY2 #define FORMAT_YVYU SPA_VIDEO_FORMAT_YVYU #define FORMAT_UYVY SPA_VIDEO_FORMAT_UYVY #define FORMAT_Y42B SPA_VIDEO_FORMAT_Y42B #define FORMAT_Y41B SPA_VIDEO_FORMAT_Y41B #define FORMAT_YUV9 SPA_VIDEO_FORMAT_YUV9 #define FORMAT_I420 SPA_VIDEO_FORMAT_I420 #define FORMAT_NV12 SPA_VIDEO_FORMAT_NV12 #define FORMAT_NV12_64Z32 SPA_VIDEO_FORMAT_NV12_64Z32 #define FORMAT_NV21 SPA_VIDEO_FORMAT_NV21 #define FORMAT_NV16 SPA_VIDEO_FORMAT_NV16 #define FORMAT_NV61 SPA_VIDEO_FORMAT_NV61 #define FORMAT_NV24 SPA_VIDEO_FORMAT_NV24 static const struct format_info format_info[] = { /* RGB formats */ {V4L2_PIX_FMT_RGB332, FORMAT_UNKNOWN, VIDEO, RAW}, {V4L2_PIX_FMT_ARGB555, FORMAT_UNKNOWN, VIDEO, RAW}, {V4L2_PIX_FMT_XRGB555, FORMAT_RGB15, VIDEO, RAW}, {V4L2_PIX_FMT_ARGB555X, FORMAT_UNKNOWN, VIDEO, RAW}, {V4L2_PIX_FMT_XRGB555X, FORMAT_BGR15, VIDEO, RAW}, {V4L2_PIX_FMT_RGB565, FORMAT_RGB16, VIDEO, RAW}, {V4L2_PIX_FMT_RGB565X, FORMAT_UNKNOWN, VIDEO, RAW}, {V4L2_PIX_FMT_BGR666, FORMAT_UNKNOWN, VIDEO, RAW}, {V4L2_PIX_FMT_BGR24, FORMAT_BGR, VIDEO, RAW}, {V4L2_PIX_FMT_RGB24, FORMAT_RGB, VIDEO, RAW}, {V4L2_PIX_FMT_ABGR32, FORMAT_BGRA, VIDEO, RAW}, {V4L2_PIX_FMT_XBGR32, FORMAT_BGRx, VIDEO, RAW}, {V4L2_PIX_FMT_ARGB32, FORMAT_ARGB, VIDEO, RAW}, {V4L2_PIX_FMT_XRGB32, FORMAT_xRGB, VIDEO, RAW}, /* Deprecated Packed RGB Image Formats (alpha ambiguity) */ {V4L2_PIX_FMT_RGB444, FORMAT_UNKNOWN, VIDEO, RAW}, {V4L2_PIX_FMT_RGB555, FORMAT_RGB15, VIDEO, RAW}, {V4L2_PIX_FMT_RGB555X, FORMAT_BGR15, VIDEO, RAW}, {V4L2_PIX_FMT_BGR32, FORMAT_BGRx, VIDEO, RAW}, {V4L2_PIX_FMT_RGB32, FORMAT_xRGB, VIDEO, RAW}, /* Grey formats */ {V4L2_PIX_FMT_GREY, FORMAT_GRAY8, VIDEO, RAW}, {V4L2_PIX_FMT_Y4, FORMAT_UNKNOWN, VIDEO, RAW}, {V4L2_PIX_FMT_Y6, FORMAT_UNKNOWN, VIDEO, RAW}, {V4L2_PIX_FMT_Y10, FORMAT_UNKNOWN, VIDEO, RAW}, {V4L2_PIX_FMT_Y12, FORMAT_UNKNOWN, VIDEO, RAW}, {V4L2_PIX_FMT_Y16, FORMAT_GRAY16_LE, VIDEO, RAW}, {V4L2_PIX_FMT_Y16_BE, FORMAT_GRAY16_BE, VIDEO, RAW}, {V4L2_PIX_FMT_Y10BPACK, FORMAT_UNKNOWN, VIDEO, RAW}, /* Palette formats */ {V4L2_PIX_FMT_PAL8, FORMAT_UNKNOWN, VIDEO, RAW}, /* Chrominance formats */ {V4L2_PIX_FMT_UV8, FORMAT_UNKNOWN, VIDEO, RAW}, /* Luminance+Chrominance formats */ {V4L2_PIX_FMT_YVU410, FORMAT_YVU9, VIDEO, RAW}, {V4L2_PIX_FMT_YVU420, FORMAT_YV12, VIDEO, RAW}, {V4L2_PIX_FMT_YVU420M, FORMAT_UNKNOWN, VIDEO, RAW}, {V4L2_PIX_FMT_YUYV, FORMAT_YUY2, VIDEO, RAW}, {V4L2_PIX_FMT_YYUV, FORMAT_UNKNOWN, VIDEO, RAW}, {V4L2_PIX_FMT_YVYU, FORMAT_YVYU, VIDEO, RAW}, {V4L2_PIX_FMT_UYVY, FORMAT_UYVY, VIDEO, RAW}, {V4L2_PIX_FMT_VYUY, FORMAT_UNKNOWN, VIDEO, RAW}, {V4L2_PIX_FMT_YUV422P, FORMAT_Y42B, VIDEO, RAW}, {V4L2_PIX_FMT_YUV411P, FORMAT_Y41B, VIDEO, RAW}, {V4L2_PIX_FMT_Y41P, FORMAT_UNKNOWN, VIDEO, RAW}, {V4L2_PIX_FMT_YUV444, FORMAT_UNKNOWN, VIDEO, RAW}, {V4L2_PIX_FMT_YUV555, FORMAT_UNKNOWN, VIDEO, RAW}, {V4L2_PIX_FMT_YUV565, FORMAT_UNKNOWN, VIDEO, RAW}, {V4L2_PIX_FMT_YUV32, FORMAT_UNKNOWN, VIDEO, RAW}, {V4L2_PIX_FMT_YUV410, FORMAT_YUV9, VIDEO, RAW}, {V4L2_PIX_FMT_YUV420, FORMAT_I420, VIDEO, RAW}, {V4L2_PIX_FMT_YUV420M, FORMAT_I420, VIDEO, RAW}, {V4L2_PIX_FMT_HI240, FORMAT_UNKNOWN, VIDEO, RAW}, {V4L2_PIX_FMT_HM12, FORMAT_UNKNOWN, VIDEO, RAW}, {V4L2_PIX_FMT_M420, FORMAT_UNKNOWN, VIDEO, RAW}, /* two planes -- one Y, one Cr + Cb interleaved */ {V4L2_PIX_FMT_NV12, FORMAT_NV12, VIDEO, RAW}, {V4L2_PIX_FMT_NV12M, FORMAT_NV12, VIDEO, RAW}, {V4L2_PIX_FMT_NV12MT, FORMAT_NV12_64Z32, VIDEO, RAW}, {V4L2_PIX_FMT_NV12MT_16X16, FORMAT_UNKNOWN, VIDEO, RAW}, {V4L2_PIX_FMT_NV21, FORMAT_NV21, VIDEO, RAW}, {V4L2_PIX_FMT_NV21M, FORMAT_NV21, VIDEO, RAW}, {V4L2_PIX_FMT_NV16, FORMAT_NV16, VIDEO, RAW}, {V4L2_PIX_FMT_NV16M, FORMAT_NV16, VIDEO, RAW}, {V4L2_PIX_FMT_NV61, FORMAT_NV61, VIDEO, RAW}, {V4L2_PIX_FMT_NV61M, FORMAT_NV61, VIDEO, RAW}, {V4L2_PIX_FMT_NV24, FORMAT_NV24, VIDEO, RAW}, {V4L2_PIX_FMT_NV42, FORMAT_UNKNOWN, VIDEO, RAW}, /* Bayer formats - see http://www.siliconimaging.com/RGB%20Bayer.htm */ {V4L2_PIX_FMT_SBGGR8, FORMAT_UNKNOWN, VIDEO, BAYER}, {V4L2_PIX_FMT_SGBRG8, FORMAT_UNKNOWN, VIDEO, BAYER}, {V4L2_PIX_FMT_SGRBG8, FORMAT_UNKNOWN, VIDEO, BAYER}, {V4L2_PIX_FMT_SRGGB8, FORMAT_UNKNOWN, VIDEO, BAYER}, /* compressed formats */ {V4L2_PIX_FMT_MJPEG, FORMAT_ENCODED, VIDEO, MJPG}, {V4L2_PIX_FMT_JPEG, FORMAT_ENCODED, VIDEO, MJPG}, {V4L2_PIX_FMT_PJPG, FORMAT_ENCODED, VIDEO, MJPG}, {V4L2_PIX_FMT_DV, FORMAT_ENCODED, VIDEO, DV}, {V4L2_PIX_FMT_MPEG, FORMAT_ENCODED, VIDEO, MPEGTS}, {V4L2_PIX_FMT_H264, FORMAT_ENCODED, VIDEO, H264}, {V4L2_PIX_FMT_H264_NO_SC, FORMAT_ENCODED, VIDEO, H264}, {V4L2_PIX_FMT_H264_MVC, FORMAT_ENCODED, VIDEO, H264}, {V4L2_PIX_FMT_H263, FORMAT_ENCODED, VIDEO, H263}, {V4L2_PIX_FMT_MPEG1, FORMAT_ENCODED, VIDEO, MPEG1}, {V4L2_PIX_FMT_MPEG2, FORMAT_ENCODED, VIDEO, MPEG2}, {V4L2_PIX_FMT_MPEG4, FORMAT_ENCODED, VIDEO, MPEG4}, {V4L2_PIX_FMT_XVID, FORMAT_ENCODED, VIDEO, XVID}, {V4L2_PIX_FMT_VC1_ANNEX_G, FORMAT_ENCODED, VIDEO, VC1}, {V4L2_PIX_FMT_VC1_ANNEX_L, FORMAT_ENCODED, VIDEO, VC1}, {V4L2_PIX_FMT_VP8, FORMAT_ENCODED, VIDEO, VP8}, /* Vendor-specific formats */ {V4L2_PIX_FMT_WNVA, FORMAT_UNKNOWN, VIDEO, RAW}, {V4L2_PIX_FMT_SN9C10X, FORMAT_UNKNOWN, VIDEO, RAW}, {V4L2_PIX_FMT_PWC1, FORMAT_UNKNOWN, VIDEO, RAW}, {V4L2_PIX_FMT_PWC2, FORMAT_UNKNOWN, VIDEO, RAW}, }; static const struct format_info *fourcc_to_format_info(uint32_t fourcc) { SPA_FOR_EACH_ELEMENT_VAR(format_info, i) { if (i->fourcc == fourcc) return i; } return NULL; } #if 0 static const struct format_info *video_format_to_format_info(uint32_t format) { SPA_FOR_EACH_ELEMENT_VAR(format_info, i) { if (i->format == format) return i; } return NULL; } #endif static const struct format_info *find_format_info_by_media_type(uint32_t type, uint32_t subtype, uint32_t format, int startidx) { size_t i; for (i = startidx; i < SPA_N_ELEMENTS(format_info); i++) { const struct format_info *fi = &format_info[i]; if ((fi->media_type == type) && (fi->media_subtype == subtype) && (format == 0 || fi->format == format)) return fi; } return NULL; } static int enum_filter_format(uint32_t media_type, int32_t media_subtype, const struct spa_pod *filter, uint32_t index) { uint32_t video_format = SPA_VIDEO_FORMAT_UNKNOWN; switch (media_type) { case SPA_MEDIA_TYPE_video: case SPA_MEDIA_TYPE_image: if (media_subtype == SPA_MEDIA_SUBTYPE_raw) { const struct spa_pod_prop *p; const struct spa_pod *val; uint32_t n_values, choice; const uint32_t *values; if (!(p = spa_pod_find_prop(filter, NULL, SPA_FORMAT_VIDEO_format))) return -ENOENT; val = spa_pod_get_values(&p->value, &n_values, &choice); if (val->type != SPA_TYPE_Id || n_values == 0) return SPA_VIDEO_FORMAT_UNKNOWN; values = SPA_POD_BODY(val); if (choice == SPA_CHOICE_None) { if (index == 0) video_format = values[0]; } else { if (index < n_values - 1) video_format = values[index + 1]; } } else { if (index == 0) video_format = SPA_VIDEO_FORMAT_ENCODED; } } return video_format; } static bool filter_framesize(struct v4l2_frmsizeenum *frmsize, const struct spa_rectangle *min, const struct spa_rectangle *max, const struct spa_rectangle *step) { if (frmsize->type == V4L2_FRMSIZE_TYPE_DISCRETE) { if (frmsize->discrete.width < min->width || frmsize->discrete.height < min->height || frmsize->discrete.width > max->width || frmsize->discrete.height > max->height) { return false; } } else if (frmsize->type == V4L2_FRMSIZE_TYPE_CONTINUOUS || frmsize->type == V4L2_FRMSIZE_TYPE_STEPWISE) { /* FIXME, use LCM */ frmsize->stepwise.step_width *= step->width; frmsize->stepwise.step_height *= step->height; if (frmsize->stepwise.max_width < min->width || frmsize->stepwise.max_height < min->height || frmsize->stepwise.min_width > max->width || frmsize->stepwise.min_height > max->height) return false; frmsize->stepwise.min_width = SPA_MAX(frmsize->stepwise.min_width, min->width); frmsize->stepwise.min_height = SPA_MAX(frmsize->stepwise.min_height, min->height); frmsize->stepwise.max_width = SPA_MIN(frmsize->stepwise.max_width, max->width); frmsize->stepwise.max_height = SPA_MIN(frmsize->stepwise.max_height, max->height); } else return false; return true; } static int compare_fraction(struct v4l2_fract *f1, const struct spa_fraction *f2) { uint64_t n1, n2; /* fractions are reduced when set, so we can quickly see if they're equal */ if (f1->denominator == f2->num && f1->numerator == f2->denom) return 0; /* extend to 64 bits */ n1 = ((int64_t) f1->denominator) * f2->denom; n2 = ((int64_t) f1->numerator) * f2->num; if (n1 < n2) return -1; return 1; } static bool filter_framerate(struct v4l2_frmivalenum *frmival, const struct spa_fraction *min, const struct spa_fraction *max, const struct spa_fraction *step) { if (frmival->type == V4L2_FRMIVAL_TYPE_DISCRETE) { if (compare_fraction(&frmival->discrete, min) < 0 || compare_fraction(&frmival->discrete, max) > 0) return false; } else if (frmival->type == V4L2_FRMIVAL_TYPE_CONTINUOUS || frmival->type == V4L2_FRMIVAL_TYPE_STEPWISE) { /* FIXME, use LCM */ frmival->stepwise.step.denominator *= step->num; frmival->stepwise.step.numerator *= step->denom; if (compare_fraction(&frmival->stepwise.min, min) < 0 || compare_fraction(&frmival->stepwise.max, max) > 0) return false; if (compare_fraction(&frmival->stepwise.max, min) < 0) { frmival->stepwise.max.denominator = min->num; frmival->stepwise.max.numerator = min->denom; } if (compare_fraction(&frmival->stepwise.min, max) > 0) { frmival->stepwise.min.denominator = max->num; frmival->stepwise.min.numerator = max->denom; } } else return false; return true; } struct spa_video_colorimetry v4l2_colorimetry_map[] = { { /* V4L2_COLORSPACE_DEFAULT */ .range = SPA_VIDEO_COLOR_RANGE_UNKNOWN, }, { /* V4L2_COLORSPACE_SMPTE170M */ .range = SPA_VIDEO_COLOR_RANGE_16_235, .matrix = SPA_VIDEO_COLOR_MATRIX_BT601, .transfer = SPA_VIDEO_TRANSFER_BT601, .primaries = SPA_VIDEO_COLOR_PRIMARIES_SMPTE170M, }, { /* V4L2_COLORSPACE_SMPTE240M */ .range = SPA_VIDEO_COLOR_RANGE_16_235, .matrix = SPA_VIDEO_COLOR_MATRIX_SMPTE240M, .transfer = SPA_VIDEO_TRANSFER_SMPTE240M, .primaries = SPA_VIDEO_COLOR_PRIMARIES_SMPTE240M, }, { /* V4L2_COLORSPACE_REC709 */ .range = SPA_VIDEO_COLOR_RANGE_16_235, .matrix = SPA_VIDEO_COLOR_MATRIX_BT709, .transfer = SPA_VIDEO_TRANSFER_BT709, .primaries = SPA_VIDEO_COLOR_PRIMARIES_BT709, }, { /* V4L2_COLORSPACE_BT878 (deprecated) */ .range = SPA_VIDEO_COLOR_RANGE_UNKNOWN, }, { /* V4L2_COLORSPACE_470_SYSTEM_M */ .range = SPA_VIDEO_COLOR_RANGE_16_235, .matrix = SPA_VIDEO_COLOR_MATRIX_BT601, .transfer = SPA_VIDEO_TRANSFER_BT709, .primaries = SPA_VIDEO_COLOR_PRIMARIES_BT470M, }, { /* V4L2_COLORSPACE_470_SYSTEM_BG */ .range = SPA_VIDEO_COLOR_RANGE_16_235, .matrix = SPA_VIDEO_COLOR_MATRIX_BT601, .transfer = SPA_VIDEO_TRANSFER_BT709, .primaries = SPA_VIDEO_COLOR_PRIMARIES_BT470BG, }, { /* V4L2_COLORSPACE_JPEG */ .range = SPA_VIDEO_COLOR_RANGE_0_255, .matrix = SPA_VIDEO_COLOR_MATRIX_BT601, .transfer = SPA_VIDEO_TRANSFER_SRGB, .primaries = SPA_VIDEO_COLOR_PRIMARIES_BT709, }, { /* V4L2_COLORSPACE_SRGB */ .range = SPA_VIDEO_COLOR_RANGE_16_235, .matrix = SPA_VIDEO_COLOR_MATRIX_BT601, .transfer = SPA_VIDEO_TRANSFER_SRGB, .primaries = SPA_VIDEO_COLOR_PRIMARIES_BT709, }, { /* V4L2_COLORSPACE_OPRGB */ .range = SPA_VIDEO_COLOR_RANGE_16_235, .matrix = SPA_VIDEO_COLOR_MATRIX_BT601, .transfer = SPA_VIDEO_TRANSFER_ADOBERGB, .primaries = SPA_VIDEO_COLOR_PRIMARIES_ADOBERGB, }, { /* V4L2_COLORSPACE_BT2020 */ .range = SPA_VIDEO_COLOR_RANGE_16_235, .matrix = SPA_VIDEO_COLOR_MATRIX_BT2020, .transfer = SPA_VIDEO_TRANSFER_BT2020_12, .primaries = SPA_VIDEO_COLOR_PRIMARIES_BT2020, }, { /* V4L2_COLORSPACE_RAW */ .range = SPA_VIDEO_COLOR_RANGE_UNKNOWN, } }; enum spa_video_color_range v4l2_color_range_map[] = { SPA_VIDEO_COLOR_RANGE_UNKNOWN, SPA_VIDEO_COLOR_RANGE_0_255, SPA_VIDEO_COLOR_RANGE_16_235 }; enum spa_video_color_matrix v4l2_color_matrix_map[] = { /* V4L2_YCBCR_ENC_DEFAULT */ SPA_VIDEO_COLOR_MATRIX_UNKNOWN, /* V4L2_YCBCR_ENC_601 */ SPA_VIDEO_COLOR_MATRIX_BT601, /* V4L2_YCBCR_ENC_709 */ SPA_VIDEO_COLOR_MATRIX_BT709, /* V4L2_YCBCR_ENC_XV601 */ SPA_VIDEO_COLOR_MATRIX_BT601, /* V4L2_YCBCR_ENC_XV709 */ SPA_VIDEO_COLOR_MATRIX_BT709, /* V4L2_YCBCR_ENC_SYCC */ SPA_VIDEO_COLOR_MATRIX_BT601, /* V4L2_YCBCR_ENC_BT2020 */ SPA_VIDEO_COLOR_MATRIX_BT2020, /* V4L2_YCBCR_ENC_BT2020_CONST_LUM */ SPA_VIDEO_COLOR_MATRIX_BT2020, /* V4L2_YCBCR_ENC_SMPTE240M */ SPA_VIDEO_COLOR_MATRIX_SMPTE240M }; enum spa_video_transfer_function v4l2_transfer_function_map[] = { /* V4L2_XFER_FUNC_DEFAULT */ SPA_VIDEO_TRANSFER_UNKNOWN, /* V4L2_XFER_FUNC_709 */ SPA_VIDEO_TRANSFER_BT709, /* V4L2_XFER_FUNC_SRGB */ SPA_VIDEO_TRANSFER_SRGB, /* V4L2_XFER_FUNC_OPRGB */ SPA_VIDEO_TRANSFER_ADOBERGB, /* V4L2_XFER_FUNC_SMPTE240M */ SPA_VIDEO_TRANSFER_SMPTE240M, /* V4L2_XFER_FUNC_NONE */ SPA_VIDEO_TRANSFER_GAMMA10, /* V4L2_XFER_FUNC_DCI_P3 */ SPA_VIDEO_TRANSFER_UNKNOWN, /* V4L2_XFER_FUNC_SMPTE2084 */ SPA_VIDEO_TRANSFER_SMPTE2084 }; static bool parse_colorimetry(struct impl *this, const struct v4l2_pix_format *pix, bool is_rgb, struct spa_video_colorimetry *colorimetry) { struct spa_video_colorimetry c = { 0 }; if (pix->colorspace < V4L2_COLORSPACE_RAW) c = v4l2_colorimetry_map[pix->colorspace]; if (c.range == SPA_VIDEO_COLOR_RANGE_UNKNOWN) return false; switch (pix->quantization) { case V4L2_QUANTIZATION_FULL_RANGE: case V4L2_QUANTIZATION_LIM_RANGE: c.range = v4l2_color_range_map[pix->quantization]; break; case V4L2_QUANTIZATION_DEFAULT: if (is_rgb) c.range = SPA_VIDEO_COLOR_RANGE_0_255; break; default: spa_log_warn(this->log, "Unknown enum v4l2_quantization value %d", pix->quantization); c.range = SPA_VIDEO_COLOR_RANGE_UNKNOWN; break; } if (pix->ycbcr_enc >= V4L2_YCBCR_ENC_SMPTE240M) spa_log_warn(this->log, "Unknown enum v4l2_ycbcr_encoding value %d", pix->ycbcr_enc); else if (pix->ycbcr_enc > 0) c.matrix = v4l2_color_matrix_map[pix->ycbcr_enc]; if (pix->xfer_func >= V4L2_XFER_FUNC_SMPTE2084) spa_log_warn(this->log, "Unknown enum v4l2_xfer_func value %d", pix->xfer_func); else if (pix->xfer_func > 0) c.transfer = v4l2_transfer_function_map[pix->xfer_func]; *colorimetry = c; return true; } #define FOURCC_ARGS(f) (f)&0x7f,((f)>>8)&0x7f,((f)>>16)&0x7f,((f)>>24)&0x7f static int spa_v4l2_enum_format(struct impl *this, int seq, uint32_t start, uint32_t num, const struct spa_pod *filter) { struct port *port = &this->out_ports[0]; int res, n_fractions; const struct format_info *info; struct spa_pod_choice *choice; uint32_t filter_media_type, filter_media_subtype; struct spa_v4l2_device *dev = &port->dev; uint8_t buffer[1024]; spa_auto(spa_pod_dynamic_builder) b = { 0 }; struct spa_pod_builder_state state; struct spa_pod_frame f[2]; struct spa_result_node_params result; struct v4l2_format fmt; uint32_t count = 0, try_width = 0, try_height = 0; bool with_modifier; if ((res = spa_v4l2_open(dev, this->props.device)) < 0) return res; spa_pod_dynamic_builder_init(&b, buffer, sizeof(buffer), 8192); spa_pod_builder_get_state(&b.b, &state); result.id = SPA_PARAM_EnumFormat; result.next = start; if (result.next == 0) { spa_zero(port->fmtdesc); port->fmtdesc.index = 0; port->fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; port->next_fmtdesc = true; spa_zero(port->frmsize); port->next_frmsize = true; spa_zero(port->frmival); } if (filter) { if ((res = spa_format_parse(filter, &filter_media_type, &filter_media_subtype)) < 0) return res; } with_modifier = !filter || spa_pod_find_prop(filter, NULL, SPA_FORMAT_VIDEO_modifier); if (false) { next_fmtdesc: port->fmtdesc.index++; port->next_fmtdesc = true; } next: result.index = result.next++; while (port->next_fmtdesc) { if (filter) { struct v4l2_format fmt; res = enum_filter_format(filter_media_type, filter_media_subtype, filter, port->fmtdesc.index); if (res == -ENOENT) goto do_enum_fmt; if (res < 0) goto exit; if (res == SPA_VIDEO_FORMAT_UNKNOWN) goto enum_end; info = find_format_info_by_media_type(filter_media_type, filter_media_subtype, res, 0); if (info == NULL) goto next_fmtdesc; port->fmtdesc.pixelformat = info->fourcc; spa_zero(fmt); fmt.type = port->fmtdesc.type; fmt.fmt.pix.pixelformat = info->fourcc; fmt.fmt.pix.field = V4L2_FIELD_ANY; fmt.fmt.pix.width = 0; fmt.fmt.pix.height = 0; if ((res = xioctl(dev->fd, VIDIOC_TRY_FMT, &fmt)) < 0) { spa_log_debug(this->log, "'%s' VIDIOC_TRY_FMT %08x: %m", this->props.device, info->fourcc); goto next_fmtdesc; } if (fmt.fmt.pix.pixelformat != info->fourcc) { spa_log_debug(this->log, "'%s' VIDIOC_TRY_FMT wanted %.4s gave %.4s", this->props.device, (char*)&info->fourcc, (char*)&fmt.fmt.pix.pixelformat); goto next_fmtdesc; } } else { do_enum_fmt: if ((res = xioctl(dev->fd, VIDIOC_ENUM_FMT, &port->fmtdesc)) < 0) { if (errno == EINVAL) goto enum_end; res = -errno; spa_log_error(this->log, "'%s' VIDIOC_ENUM_FMT: %m", this->props.device); goto exit; } } port->next_fmtdesc = false; port->frmsize.index = 0; port->frmsize.pixel_format = port->fmtdesc.pixelformat; port->next_frmsize = true; } if (!(info = fourcc_to_format_info(port->fmtdesc.pixelformat))) goto next_fmtdesc; next_frmsize: while (port->next_frmsize) { if (filter) { const struct spa_pod_prop *p; struct spa_pod *val; uint32_t n_vals, choice; /* check if we have a fixed frame size */ if (!(p = spa_pod_find_prop(filter, NULL, SPA_FORMAT_VIDEO_size))) goto do_frmsize; val = spa_pod_get_values(&p->value, &n_vals, &choice); if (val->type != SPA_TYPE_Rectangle || n_vals == 0) goto enum_end; if (choice == SPA_CHOICE_None) { const struct spa_rectangle *values = SPA_POD_BODY(val); if (port->frmsize.index > 0) goto next_fmtdesc; port->frmsize.type = V4L2_FRMSIZE_TYPE_DISCRETE; port->frmsize.discrete.width = values[0].width; port->frmsize.discrete.height = values[0].height; goto have_size; } } do_frmsize: if ((res = xioctl(dev->fd, VIDIOC_ENUM_FRAMESIZES, &port->frmsize)) < 0) { if (errno == ENOTTY) goto next_fmtdesc; if (errno == EINVAL) { if (port->frmsize.index == 0) { port->frmsize.type = V4L2_FRMSIZE_TYPE_CONTINUOUS; port->frmsize.stepwise.min_width = 16; port->frmsize.stepwise.min_height = 16; port->frmsize.stepwise.max_width = 16384; port->frmsize.stepwise.max_height = 16384; port->frmsize.stepwise.step_width = 16; port->frmsize.stepwise.step_height = 16; port->fmtdesc.index++; port->next_fmtdesc = true; goto do_frmsize_filter; } else goto next_fmtdesc; } res = -errno; spa_log_error(this->log, "'%s' VIDIOC_ENUM_FRAMESIZES: %m", this->props.device); goto exit; } do_frmsize_filter: if (filter) { static const struct spa_rectangle step = {1, 1}; const struct spa_rectangle *values; const struct spa_pod_prop *p; struct spa_pod *val; uint32_t choice, i, n_values; /* check if we have a fixed frame size */ if (!(p = spa_pod_find_prop(filter, NULL, SPA_FORMAT_VIDEO_size))) goto have_size; val = spa_pod_get_values(&p->value, &n_values, &choice); if (val->type != SPA_TYPE_Rectangle || n_values == 0) goto have_size; values = SPA_POD_BODY_CONST(val); if (choice == SPA_CHOICE_Range && n_values > 2) { if (filter_framesize(&port->frmsize, &values[1], &values[2], &step)) goto have_size; } else if (choice == SPA_CHOICE_Step && n_values > 3) { if (filter_framesize(&port->frmsize, &values[1], &values[2], &values[3])) goto have_size; } else if (choice == SPA_CHOICE_Enum) { for (i = 1; i < n_values; i++) { if (filter_framesize(&port->frmsize, &values[i], &values[i], &step)) goto have_size; } } /* nothing matches the filter, get next frame size */ port->frmsize.index++; continue; } have_size: if (port->frmsize.type == V4L2_FRMSIZE_TYPE_DISCRETE) { /* we have a fixed size, use this to get the frame intervals */ port->frmival.index = 0; port->frmival.pixel_format = port->frmsize.pixel_format; port->frmival.width = port->frmsize.discrete.width; port->frmival.height = port->frmsize.discrete.height; port->next_frmsize = false; } else if (port->frmsize.type == V4L2_FRMSIZE_TYPE_CONTINUOUS || port->frmsize.type == V4L2_FRMSIZE_TYPE_STEPWISE) { /* we have a non fixed size, fix to something sensible to get the * framerate */ port->frmival.index = 0; port->frmival.pixel_format = port->frmsize.pixel_format; port->frmival.width = port->frmsize.stepwise.min_width; port->frmival.height = port->frmsize.stepwise.min_height; port->next_frmsize = false; } else { port->frmsize.index++; } } spa_pod_builder_reset(&b.b, &state); spa_pod_builder_push_object(&b.b, &f[0], SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat); spa_pod_builder_add(&b.b, SPA_FORMAT_mediaType, SPA_POD_Id(info->media_type), SPA_FORMAT_mediaSubtype, SPA_POD_Id(info->media_subtype), 0); if (info->media_subtype == SPA_MEDIA_SUBTYPE_raw) { spa_pod_builder_prop(&b.b, SPA_FORMAT_VIDEO_format, 0); spa_pod_builder_id(&b.b, info->format); if (with_modifier) { spa_pod_builder_prop(&b.b, SPA_FORMAT_VIDEO_modifier, SPA_POD_PROP_FLAG_MANDATORY); spa_pod_builder_long(&b.b, 0L); } } spa_pod_builder_prop(&b.b, SPA_FORMAT_VIDEO_size, 0); if (port->frmsize.type == V4L2_FRMSIZE_TYPE_DISCRETE) { spa_pod_builder_rectangle(&b.b, port->frmsize.discrete.width, port->frmsize.discrete.height); try_width = port->frmsize.discrete.width; try_height = port->frmsize.discrete.height; } else if (port->frmsize.type == V4L2_FRMSIZE_TYPE_CONTINUOUS || port->frmsize.type == V4L2_FRMSIZE_TYPE_STEPWISE) { spa_pod_builder_push_choice(&b.b, &f[1], SPA_CHOICE_None, 0); choice = (struct spa_pod_choice*)spa_pod_builder_frame(&b.b, &f[1]); spa_pod_builder_rectangle(&b.b, port->frmsize.stepwise.min_width, port->frmsize.stepwise.min_height); spa_pod_builder_rectangle(&b.b, port->frmsize.stepwise.min_width, port->frmsize.stepwise.min_height); spa_pod_builder_rectangle(&b.b, port->frmsize.stepwise.max_width, port->frmsize.stepwise.max_height); if (port->frmsize.type == V4L2_FRMSIZE_TYPE_CONTINUOUS) { choice->body.type = SPA_CHOICE_Range; } else { choice->body.type = SPA_CHOICE_Step; spa_pod_builder_rectangle(&b.b, port->frmsize.stepwise.max_width, port->frmsize.stepwise.max_height); } spa_pod_builder_pop(&b.b, &f[1]); try_width = port->frmsize.stepwise.min_width; try_height = port->frmsize.stepwise.min_height; } spa_zero(fmt); fmt.type = port->fmtdesc.type; fmt.fmt.pix.pixelformat = info->fourcc; fmt.fmt.pix.field = V4L2_FIELD_ANY; fmt.fmt.pix.width = try_width; fmt.fmt.pix.height = try_height; if ((res = xioctl(dev->fd, VIDIOC_TRY_FMT, &fmt)) < 0) { spa_log_debug(this->log, "'%s' VIDIOC_TRY_FMT %08x: %m", this->props.device, info->fourcc); } else { struct spa_video_colorimetry colorimetry; bool is_rgb = spa_format_video_is_rgb(info->format); if (parse_colorimetry(this, &fmt.fmt.pix, is_rgb, &colorimetry)) { spa_pod_builder_add(&b.b, SPA_FORMAT_VIDEO_colorRange, SPA_POD_Id(colorimetry.range), SPA_FORMAT_VIDEO_colorMatrix, SPA_POD_Id(colorimetry.matrix), SPA_FORMAT_VIDEO_transferFunction, SPA_POD_Id(colorimetry.transfer), SPA_FORMAT_VIDEO_colorPrimaries, SPA_POD_Id(colorimetry.primaries), 0); } } spa_pod_builder_prop(&b.b, SPA_FORMAT_VIDEO_framerate, 0); n_fractions = 0; spa_pod_builder_push_choice(&b.b, &f[1], SPA_CHOICE_None, 0); choice = (struct spa_pod_choice*)spa_pod_builder_frame(&b.b, &f[1]); port->frmival.index = 0; while (true) { if ((res = xioctl(dev->fd, VIDIOC_ENUM_FRAMEINTERVALS, &port->frmival)) < 0) { res = -errno; port->frmsize.index++; port->next_frmsize = true; if (errno == EINVAL || errno == ENOTTY) { if (port->frmival.index == 0) { port->frmival.type = V4L2_FRMIVAL_TYPE_CONTINUOUS; port->frmival.stepwise.min.denominator = 120; port->frmival.stepwise.min.numerator = 1; port->frmival.stepwise.max.denominator = 1; port->frmival.stepwise.max.numerator = 1; goto do_frminterval_filter; } else break; } spa_log_error(this->log, "'%s' VIDIOC_ENUM_FRAMEINTERVALS: %m", this->props.device); goto exit; } do_frminterval_filter: if (filter) { static const struct spa_fraction step = {1, 1}; const struct spa_fraction *values; const struct spa_pod_prop *p; struct spa_pod *val; uint32_t i, n_values, choice; if (!(p = spa_pod_find_prop(filter, NULL, SPA_FORMAT_VIDEO_framerate))) goto have_framerate; val = spa_pod_get_values(&p->value, &n_values, &choice); if (val->type != SPA_TYPE_Fraction || n_values == 0) goto enum_end; values = SPA_POD_BODY(val); switch (choice) { case SPA_CHOICE_None: if (filter_framerate(&port->frmival, &values[0], &values[0], &step)) goto have_framerate; break; case SPA_CHOICE_Range: if (n_values > 2 && filter_framerate(&port->frmival, &values[1], &values[2], &step)) goto have_framerate; break; case SPA_CHOICE_Step: if (n_values > 3 && filter_framerate(&port->frmival, &values[1], &values[2], &values[3])) goto have_framerate; break; case SPA_CHOICE_Enum: for (i = 1; i < n_values; i++) { if (filter_framerate(&port->frmival, &values[i], &values[i], &step)) goto have_framerate; } break; default: break; } port->frmival.index++; continue; } have_framerate: if (port->frmival.type == V4L2_FRMIVAL_TYPE_DISCRETE) { choice->body.type = SPA_CHOICE_Enum; if (n_fractions == 0) spa_pod_builder_fraction(&b.b, port->frmival.discrete.denominator, port->frmival.discrete.numerator); spa_pod_builder_fraction(&b.b, port->frmival.discrete.denominator, port->frmival.discrete.numerator); port->frmival.index++; n_fractions++; } else if (port->frmival.type == V4L2_FRMIVAL_TYPE_CONTINUOUS || port->frmival.type == V4L2_FRMIVAL_TYPE_STEPWISE) { if (n_fractions == 0) { struct spa_fraction f = { 25, 1 }; if (compare_fraction(&port->frmival.stepwise.max, &f) > 0) { f.denom = port->frmival.stepwise.max.numerator; f.num = port->frmival.stepwise.max.denominator; } if (compare_fraction(&port->frmival.stepwise.min, &f) < 0) { f.denom = port->frmival.stepwise.min.numerator; f.num = port->frmival.stepwise.min.denominator; } spa_pod_builder_fraction(&b.b, f.num, f.denom); } spa_pod_builder_fraction(&b.b, port->frmival.stepwise.max.denominator, port->frmival.stepwise.max.numerator); spa_pod_builder_fraction(&b.b, port->frmival.stepwise.min.denominator, port->frmival.stepwise.min.numerator); if (port->frmival.type == V4L2_FRMIVAL_TYPE_CONTINUOUS) { choice->body.type = SPA_CHOICE_Range; n_fractions += 2; } else { choice->body.type = SPA_CHOICE_Step; spa_pod_builder_fraction(&b.b, port->frmival.stepwise.step.denominator, port->frmival.stepwise.step.numerator); n_fractions += 3; } port->frmsize.index++; port->next_frmsize = true; break; } } if (n_fractions == 0) goto next_frmsize; if (n_fractions == 1) choice->body.type = SPA_CHOICE_None; spa_pod_builder_pop(&b.b, &f[1]); result.param = spa_pod_builder_pop(&b.b, &f[0]); spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); if (++count == num) goto enum_end; if (with_modifier && info->media_subtype == SPA_MEDIA_SUBTYPE_raw) { struct spa_pod_object *op = (struct spa_pod_object *) result.param; const struct spa_pod_prop *p; spa_pod_builder_push_object(&b.b, &f[0], op->body.type, op->body.id); SPA_POD_OBJECT_FOREACH(op, p) { if (p->key != SPA_FORMAT_VIDEO_modifier) spa_pod_builder_raw_padded(&b.b, p, SPA_POD_PROP_SIZE(p)); } result.index = result.next++; result.param = spa_pod_builder_pop(&b.b, &f[0]); spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); } if (++count != num) goto next; enum_end: res = 0; exit: spa_v4l2_close(dev); return res; } static int probe_expbuf(struct impl *this) { struct port *port = &this->out_ports[0]; struct spa_v4l2_device *dev = &port->dev; struct v4l2_requestbuffers reqbuf; struct v4l2_exportbuffer expbuf; if (port->probed_expbuf) return 0; port->probed_expbuf = true; spa_zero(reqbuf); reqbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; reqbuf.memory = V4L2_MEMORY_MMAP; reqbuf.count = port->max_buffers = MAX_BUFFERS; if (xioctl(dev->fd, VIDIOC_REQBUFS, &reqbuf) < 0) { spa_log_error(this->log, "'%s' VIDIOC_REQBUFS: %m", this->props.device); return -errno; } port->max_buffers = reqbuf.count; spa_zero(expbuf); expbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; expbuf.index = 0; expbuf.flags = O_CLOEXEC | O_RDONLY; if (xioctl(dev->fd, VIDIOC_EXPBUF, &expbuf) < 0) { spa_log_info(this->log, "'%s' EXPBUF not supported: %m", this->props.device); port->have_expbuf = false; port->alloc_buffers = false; } else { port->have_expbuf = true; port->alloc_buffers = true; } reqbuf.count = 0; if (xioctl(dev->fd, VIDIOC_REQBUFS, &reqbuf) < 0) { spa_log_error(this->log, "'%s' VIDIOC_REQBUFS: %m", this->props.device); return -errno; } return 0; } static int spa_v4l2_set_format(struct impl *this, struct spa_video_info *format, uint32_t flags) { struct port *port = &this->out_ports[0]; struct spa_v4l2_device *dev = &port->dev; int res, cmd; struct v4l2_format reqfmt, fmt; struct v4l2_streamparm streamparm; const struct format_info *info = NULL; uint32_t video_format; struct spa_rectangle *size = NULL; struct spa_fraction *framerate = NULL; bool match; spa_zero(fmt); fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; spa_zero(streamparm); streamparm.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; switch (format->media_subtype) { case SPA_MEDIA_SUBTYPE_raw: video_format = format->info.raw.format; size = &format->info.raw.size; framerate = &format->info.raw.framerate; break; case SPA_MEDIA_SUBTYPE_mjpg: case SPA_MEDIA_SUBTYPE_jpeg: video_format = SPA_VIDEO_FORMAT_ENCODED; size = &format->info.mjpg.size; framerate = &format->info.mjpg.framerate; break; case SPA_MEDIA_SUBTYPE_h264: video_format = SPA_VIDEO_FORMAT_ENCODED; size = &format->info.h264.size; framerate = &format->info.h264.framerate; break; default: video_format = SPA_VIDEO_FORMAT_ENCODED; break; } info = find_format_info_by_media_type(format->media_type, format->media_subtype, video_format, 0); if (info == NULL || size == NULL || framerate == NULL) { spa_log_error(this->log, "%s: unknown media type %d %d %d", this->props.device, format->media_type, format->media_subtype, video_format); return -EINVAL; } fmt.fmt.pix.pixelformat = info->fourcc; fmt.fmt.pix.field = V4L2_FIELD_ANY; fmt.fmt.pix.width = size->width; fmt.fmt.pix.height = size->height; streamparm.parm.capture.timeperframe.numerator = framerate->denom; streamparm.parm.capture.timeperframe.denominator = framerate->num; spa_log_info(this->log, "set %.4s %dx%d %d/%d", (char *)&fmt.fmt.pix.pixelformat, fmt.fmt.pix.width, fmt.fmt.pix.height, streamparm.parm.capture.timeperframe.denominator, streamparm.parm.capture.timeperframe.numerator); reqfmt = fmt; if ((res = spa_v4l2_open(dev, this->props.device)) < 0) return res; cmd = (flags & SPA_NODE_PARAM_FLAG_TEST_ONLY) ? VIDIOC_TRY_FMT : VIDIOC_S_FMT; if (xioctl(dev->fd, cmd, &fmt) < 0) { res = -errno; spa_log_error(this->log, "'%s' VIDIOC_S_FMT: %m", this->props.device); return res; } /* some cheap USB cam's won't accept any change */ if (xioctl(dev->fd, VIDIOC_S_PARM, &streamparm) < 0) spa_log_warn(this->log, "%s: VIDIOC_S_PARM: %m", this->props.device); match = (reqfmt.fmt.pix.pixelformat == fmt.fmt.pix.pixelformat && reqfmt.fmt.pix.width == fmt.fmt.pix.width && reqfmt.fmt.pix.height == fmt.fmt.pix.height); if (!match && !SPA_FLAG_IS_SET(flags, SPA_NODE_PARAM_FLAG_NEAREST)) { spa_log_error(this->log, "%s: wanted %.4s %dx%d, got %.4s %dx%d", this->props.device, (char *)&reqfmt.fmt.pix.pixelformat, reqfmt.fmt.pix.width, reqfmt.fmt.pix.height, (char *)&fmt.fmt.pix.pixelformat, fmt.fmt.pix.width, fmt.fmt.pix.height); return -EINVAL; } if (flags & SPA_NODE_PARAM_FLAG_TEST_ONLY) return match ? 0 : 1; if (streamparm.parm.capture.timeperframe.denominator == 0) streamparm.parm.capture.timeperframe.denominator = 1; spa_log_info(this->log, "'%s' got %.4s %dx%d %d/%d", dev->path, (char *)&fmt.fmt.pix.pixelformat, fmt.fmt.pix.width, fmt.fmt.pix.height, streamparm.parm.capture.timeperframe.denominator, streamparm.parm.capture.timeperframe.numerator); dev->have_format = true; size->width = fmt.fmt.pix.width; size->height = fmt.fmt.pix.height; probe_expbuf(this); port->fmt = fmt; port->info.change_mask |= SPA_PORT_CHANGE_MASK_FLAGS | SPA_PORT_CHANGE_MASK_RATE; port->info.flags = (port->alloc_buffers ? SPA_PORT_FLAG_CAN_ALLOC_BUFFERS : 0) | SPA_PORT_FLAG_LIVE | SPA_PORT_FLAG_PHYSICAL | SPA_PORT_FLAG_TERMINAL; port->info.rate.num = streamparm.parm.capture.timeperframe.numerator; port->info.rate.denom = streamparm.parm.capture.timeperframe.denominator; return match ? 0 : 1; } static int query_ext_ctrl_ioctl(struct port *port, struct v4l2_query_ext_ctrl *qctrl) { struct spa_v4l2_device *dev = &port->dev; struct v4l2_queryctrl qc; int res; if (port->have_query_ext_ctrl) { res = xioctl(dev->fd, VIDIOC_QUERY_EXT_CTRL, qctrl); if (res == 0 || errno != ENOTTY) return res; port->have_query_ext_ctrl = false; } spa_zero(qc); qc.id = qctrl->id; res = xioctl(dev->fd, VIDIOC_QUERYCTRL, &qc); if (res == 0) { qctrl->type = qc.type; memcpy(qctrl->name, qc.name, sizeof(qctrl->name)); qctrl->minimum = qc.minimum; if (qc.type == V4L2_CTRL_TYPE_BITMASK) { qctrl->maximum = (__u32)qc.maximum; qctrl->default_value = (__u32)qc.default_value; } else { qctrl->maximum = qc.maximum; qctrl->default_value = qc.default_value; } qctrl->step = qc.step; qctrl->flags = qc.flags; qctrl->elems = 1; qctrl->nr_of_dims = 0; memset(qctrl->dims, 0, sizeof(qctrl->dims)); switch (qctrl->type) { case V4L2_CTRL_TYPE_INTEGER64: qctrl->elem_size = sizeof(__s64); break; case V4L2_CTRL_TYPE_STRING: qctrl->elem_size = qc.maximum + 1; break; default: qctrl->elem_size = sizeof(__s32); break; } memset(qctrl->reserved, 0, sizeof(qctrl->reserved)); } qctrl->id = qc.id; return res; } static struct { uint32_t v4l2_id; uint32_t spa_id; } control_map[] = { { V4L2_CID_BRIGHTNESS, SPA_PROP_brightness }, { V4L2_CID_CONTRAST, SPA_PROP_contrast }, { V4L2_CID_SATURATION, SPA_PROP_saturation }, { V4L2_CID_HUE, SPA_PROP_hue }, { V4L2_CID_GAMMA, SPA_PROP_gamma }, { V4L2_CID_EXPOSURE_ABSOLUTE, SPA_PROP_exposure }, { V4L2_CID_GAIN, SPA_PROP_gain }, { V4L2_CID_SHARPNESS, SPA_PROP_sharpness }, }; static uint32_t control_to_prop_id(struct impl *impl, uint32_t control_id) { SPA_FOR_EACH_ELEMENT_VAR(control_map, c) { if (c->v4l2_id == control_id) return c->spa_id; } return SPA_PROP_START_CUSTOM + control_id; } static uint32_t prop_id_to_control(struct impl *impl, uint32_t prop_id) { SPA_FOR_EACH_ELEMENT_VAR(control_map, c) { if (c->spa_id == prop_id) return c->v4l2_id; } if (prop_id >= SPA_PROP_START_CUSTOM) return prop_id - SPA_PROP_START_CUSTOM; return SPA_ID_INVALID; } static int spa_v4l2_enum_controls(struct impl *this, int seq, uint32_t start, uint32_t num, const struct spa_pod *filter) { struct port *port = &this->out_ports[0]; struct spa_v4l2_device *dev = &port->dev; struct v4l2_query_ext_ctrl queryctrl; struct spa_pod *param; spa_auto(spa_pod_dynamic_builder) b = { 0 }; struct spa_pod_builder_state state; uint32_t prop_id, ctrl_id; uint8_t buffer[1024]; int res; const unsigned next_fl = V4L2_CTRL_FLAG_NEXT_CTRL | V4L2_CTRL_FLAG_NEXT_COMPOUND; struct spa_pod_frame f[2]; struct spa_result_node_params result; uint32_t count = 0; if ((res = spa_v4l2_open(dev, this->props.device)) < 0) return res; spa_pod_dynamic_builder_init(&b, buffer, sizeof(buffer), 4096); spa_pod_builder_get_state(&b.b, &state); result.id = SPA_PARAM_PropInfo; result.next = start; next: result.index = result.next; spa_zero(queryctrl); if (result.next == 0) { result.next |= next_fl; port->n_controls = 0; } queryctrl.id = result.next; spa_log_debug(this->log, "test control %08x", queryctrl.id); if (query_ext_ctrl_ioctl(port, &queryctrl) != 0) { if (errno == ENOTTY) goto enum_end; if (errno == EINVAL) { if (queryctrl.id != next_fl) goto enum_end; if (result.next & next_fl) result.next = V4L2_CID_USER_BASE; else if (result.next >= V4L2_CID_USER_BASE && result.next < V4L2_CID_LASTP1) result.next++; else if (result.next >= V4L2_CID_LASTP1) result.next = V4L2_CID_PRIVATE_BASE; else goto enum_end; goto next; } res = -errno; spa_log_error(this->log, "'%s' VIDIOC_QUERYCTRL: %m", this->props.device); spa_v4l2_close(dev); return res; } if (result.next & next_fl) result.next = queryctrl.id | next_fl; else result.next++; if (queryctrl.flags & V4L2_CTRL_FLAG_DISABLED) goto next; if (port->n_controls >= MAX_CONTROLS) goto enum_end; ctrl_id = queryctrl.id & ~next_fl; spa_pod_builder_reset(&b.b, &state); prop_id = control_to_prop_id(this, ctrl_id); port->controls[port->n_controls].id = prop_id; port->controls[port->n_controls].ctrl_id = ctrl_id; port->controls[port->n_controls].value = queryctrl.default_value; spa_log_debug(this->log, "Control '%s' %d %d", queryctrl.name, prop_id, ctrl_id); switch (queryctrl.type) { case V4L2_CTRL_TYPE_INTEGER: port->controls[port->n_controls].type = SPA_TYPE_Int; param = spa_pod_builder_add_object(&b.b, SPA_TYPE_OBJECT_PropInfo, SPA_PARAM_PropInfo, SPA_PROP_INFO_id, SPA_POD_Id(prop_id), SPA_PROP_INFO_type, SPA_POD_CHOICE_STEP_Int( (int32_t)queryctrl.default_value, (int32_t)queryctrl.minimum, (int32_t)queryctrl.maximum, (int32_t)queryctrl.step), SPA_PROP_INFO_description, SPA_POD_String(queryctrl.name)); break; case V4L2_CTRL_TYPE_BOOLEAN: port->controls[port->n_controls].type = SPA_TYPE_Bool; param = spa_pod_builder_add_object(&b.b, SPA_TYPE_OBJECT_PropInfo, SPA_PARAM_PropInfo, SPA_PROP_INFO_id, SPA_POD_Id(prop_id), SPA_PROP_INFO_type, SPA_POD_CHOICE_Bool((bool)queryctrl.default_value), SPA_PROP_INFO_description, SPA_POD_String(queryctrl.name)); break; case V4L2_CTRL_TYPE_MENU: { struct v4l2_querymenu querymenu; port->controls[port->n_controls].type = SPA_TYPE_Int; spa_pod_builder_push_object(&b.b, &f[0], SPA_TYPE_OBJECT_PropInfo, SPA_PARAM_PropInfo); spa_pod_builder_add(&b.b, SPA_PROP_INFO_id, SPA_POD_Id(prop_id), SPA_PROP_INFO_type, SPA_POD_Int((int32_t)queryctrl.default_value), SPA_PROP_INFO_description, SPA_POD_String(queryctrl.name), 0); spa_zero(querymenu); querymenu.id = queryctrl.id; spa_pod_builder_prop(&b.b, SPA_PROP_INFO_labels, 0); spa_pod_builder_push_struct(&b.b, &f[1]); for (querymenu.index = queryctrl.minimum; querymenu.index <= queryctrl.maximum; querymenu.index++) { if (xioctl(dev->fd, VIDIOC_QUERYMENU, &querymenu) == 0) { spa_pod_builder_int(&b.b, querymenu.index); spa_pod_builder_string(&b.b, (const char *)querymenu.name); } } spa_pod_builder_pop(&b.b, &f[1]); param = spa_pod_builder_pop(&b.b, &f[0]); break; } case V4L2_CTRL_TYPE_INTEGER_MENU: case V4L2_CTRL_TYPE_BITMASK: case V4L2_CTRL_TYPE_BUTTON: case V4L2_CTRL_TYPE_INTEGER64: case V4L2_CTRL_TYPE_STRING: default: goto next; } port->n_controls++; if (spa_pod_filter(&b.b, &result.param, param, filter) < 0) goto next; spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); if (++count != num) goto next; enum_end: res = 0; spa_v4l2_close(dev); return res; } static int spa_v4l2_update_controls(struct impl *this) { struct port *port = &this->out_ports[0]; struct spa_v4l2_device *dev = &port->dev; int res; uint32_t i; if ((res = spa_v4l2_open(dev, this->props.device)) < 0) return res; for (i = 0; i < port->n_controls; i++) { struct control *c = &port->controls[i]; struct v4l2_control control; spa_zero(control); control.id = c->ctrl_id; if (xioctl(dev->fd, VIDIOC_G_CTRL, &control) < 0) { /* Write only controls like relative pan/tilt return EACCES */ if (errno == EACCES) { c->value = 0; continue; } res = -errno; goto done; } c->value = control.value; } res = 0; done: spa_v4l2_close(dev); return res; } static int spa_v4l2_set_control(struct impl *this, const struct spa_pod_prop *prop, const void *body) { struct port *port = &this->out_ports[0]; struct spa_v4l2_device *dev = &port->dev; struct v4l2_control control; int res; spa_zero(control); control.id = prop_id_to_control(this, prop->key); if (control.id == SPA_ID_INVALID) return -ENOENT; if ((res = spa_v4l2_open(dev, this->props.device)) < 0) return res; switch (prop->value.type) { case SPA_TYPE_Bool: { bool val; if ((res = spa_pod_get_bool(&prop->value, &val)) < 0) goto done; control.value = val; break; } case SPA_TYPE_Float: { float val; if ((res = spa_pod_get_float(&prop->value, &val)) < 0) goto done; control.value = (int32_t) val; break; } case SPA_TYPE_Int: { int32_t val; if ((res = spa_pod_get_int(&prop->value, &val)) < 0) goto done; control.value = val; break; } default: res = -EINVAL; goto done; } if (xioctl(dev->fd, VIDIOC_S_CTRL, &control) < 0) { res = -errno; goto done; } res = 0; done: spa_v4l2_close(dev); return res; } static int mmap_read(struct impl *this) { struct port *port = &this->out_ports[0]; struct spa_v4l2_device *dev = &port->dev; struct v4l2_buffer buf; struct buffer *b; struct spa_data *d; int64_t pts; spa_zero(buf); buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; buf.memory = port->memtype; if (xioctl(dev->fd, VIDIOC_DQBUF, &buf) < 0) return -errno; spa_log_trace(this->log, "v4l2 %p: have output %d/%d", this, buf.index, buf.sequence); /* Drop the first frame in order to work around common firmware * timestamp issues */ if (port->first_buffer) { port->first_buffer = false; if (xioctl(dev->fd, VIDIOC_QBUF, &buf) < 0) spa_log_warn(this->log, "v4l2 %p: error qbuf: %m", this); return 0; } pts = SPA_TIMEVAL_TO_NSEC(&buf.timestamp); if (this->clock) { double target = (double)port->info.rate.num / port->info.rate.denom; double corr; if (this->dll.bw == 0.0) { spa_dll_set_bw(&this->dll, SPA_DLL_BW_MAX, port->info.rate.denom, port->info.rate.denom); this->clock->next_nsec = pts; corr = 1.0; } else { double diff = ((double)this->clock->next_nsec - (double)pts) / SPA_NSEC_PER_SEC; double error = port->info.rate.denom * (diff - target); corr = spa_dll_update(&this->dll, SPA_CLAMPD(error, -128., 128.)); } /* FIXME, we should follow the driver clock and target_ values. * for now we ignore and use our own. */ this->clock->target_rate = port->info.rate; this->clock->target_duration = 1; this->clock->nsec = pts; this->clock->rate = port->info.rate; this->clock->position = buf.sequence; this->clock->duration = 1; this->clock->delay = 0; this->clock->rate_diff = corr; this->clock->next_nsec += (uint64_t) (target * SPA_NSEC_PER_SEC * corr); } b = &port->buffers[buf.index]; if (b->h) { b->h->flags = 0; if (buf.flags & V4L2_BUF_FLAG_ERROR) b->h->flags |= SPA_META_HEADER_FLAG_CORRUPTED; b->h->offset = 0; b->h->seq = buf.sequence; b->h->pts = pts; b->h->dts_offset = 0; } if (b->vt) { b->vt->transform = this->transform; } d = b->outbuf->datas; d[0].chunk->offset = 0; d[0].chunk->size = SPA_MIN(buf.bytesused, d[0].maxsize); d[0].chunk->stride = port->fmt.fmt.pix.bytesperline; d[0].chunk->flags = 0; if (buf.flags & V4L2_BUF_FLAG_ERROR) d[0].chunk->flags |= SPA_CHUNK_FLAG_CORRUPTED; if (b->mmap_ptr && b->ptr) memcpy(b->ptr, b->mmap_ptr, d[0].chunk->size); spa_list_append(&port->queue, &b->link); return 0; } static void v4l2_on_fd_events(struct spa_source *source) { struct impl *this = source->data; struct spa_io_buffers *io; struct port *port = &this->out_ports[0]; struct buffer *b; int res; if (source->rmask & SPA_IO_ERR) { struct port *port = &this->out_ports[0]; spa_log_error(this->log, "'%p' error %08x", this->props.device, source->rmask); if (port->source.loop) spa_loop_remove_source(this->data_loop, &port->source); return; } if (!(source->rmask & SPA_IO_IN)) { spa_log_warn(this->log, "v4l2 %p: spurious wakeup %d", this, source->rmask); return; } if ((res = mmap_read(this)) < 0) { spa_log_warn(this->log, "v4l2 %p: mmap read error:%s", this, spa_strerror(res)); return; } if (spa_list_is_empty(&port->queue)) return; io = port->io; if (io == NULL) { b = spa_list_first(&port->queue, struct buffer, link); spa_list_remove(&b->link); SPA_FLAG_SET(b->flags, BUFFER_FLAG_OUTSTANDING); spa_v4l2_buffer_recycle(this, b->id); } else if (io->status != SPA_STATUS_HAVE_DATA) { if (io->buffer_id < port->n_buffers) spa_v4l2_buffer_recycle(this, io->buffer_id); b = spa_list_first(&port->queue, struct buffer, link); spa_list_remove(&b->link); SPA_FLAG_SET(b->flags, BUFFER_FLAG_OUTSTANDING); io->buffer_id = b->id; io->status = SPA_STATUS_HAVE_DATA; spa_log_trace(this->log, "v4l2 %p: now queued %d", this, b->id); } spa_node_call_ready(&this->callbacks, SPA_STATUS_HAVE_DATA); } static int spa_v4l2_use_buffers(struct impl *this, struct spa_buffer **buffers, uint32_t n_buffers) { struct port *port = &this->out_ports[0]; struct spa_v4l2_device *dev = &port->dev; struct v4l2_requestbuffers reqbuf; unsigned int i; struct spa_data *d; if (n_buffers > 0) { d = buffers[0]->datas; if (d[0].type == SPA_DATA_MemFd || (d[0].type == SPA_DATA_MemPtr && d[0].data != NULL)) { port->memtype = V4L2_MEMORY_USERPTR; } else if (d[0].type == SPA_DATA_DmaBuf) { port->memtype = V4L2_MEMORY_DMABUF; } else { spa_log_error(this->log, "%s: can't use buffers of type %d", this->props.device, d[0].type); return -EINVAL; } } spa_zero(reqbuf); reqbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; reqbuf.memory = port->memtype; reqbuf.count = n_buffers; if (xioctl(dev->fd, VIDIOC_REQBUFS, &reqbuf) < 0) { if (port->memtype != V4L2_MEMORY_USERPTR) { spa_log_error(this->log, "'%s' VIDIOC_REQBUFS %m", this->props.device); return -errno; } /* some drivers (v4l2loopback) don't support USERPTR * and so we need to try again with MMAP and memcpy */ port->memtype = V4L2_MEMORY_MMAP; spa_zero(reqbuf); reqbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; reqbuf.memory = port->memtype; reqbuf.count = n_buffers; if (xioctl(dev->fd, VIDIOC_REQBUFS, &reqbuf) < 0) { spa_log_error(this->log, "'%s' VIDIOC_REQBUFS %m", this->props.device); return -errno; } } spa_log_debug(this->log, "got %d buffers", reqbuf.count); if (reqbuf.count < n_buffers) { spa_log_error(this->log, "'%s' can't allocate enough buffers %d < %d", this->props.device, reqbuf.count, n_buffers); return -ENOMEM; } for (i = 0; i < reqbuf.count; i++) { struct buffer *b; b = &port->buffers[i]; b->id = i; b->outbuf = buffers[i]; b->flags = BUFFER_FLAG_OUTSTANDING; b->h = spa_buffer_find_meta_data(buffers[i], SPA_META_Header, sizeof(*b->h)); b->vt = spa_buffer_find_meta_data(buffers[i], SPA_META_VideoTransform, sizeof(*b->vt)); spa_log_debug(this->log, "%s: import buffer %p", this->props.device, buffers[i]); if (buffers[i]->n_datas < 1) { spa_log_error(this->log, "%s: invalid memory on buffer %p", this->props.device, buffers[i]); return -EINVAL; } d = buffers[i]->datas; spa_zero(b->v4l2_buffer); b->v4l2_buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; b->v4l2_buffer.memory = port->memtype; b->v4l2_buffer.index = i; if (port->memtype == V4L2_MEMORY_USERPTR || port->memtype == V4L2_MEMORY_MMAP) { if (d[0].data == NULL) { void *data; data = mmap(NULL, d[0].maxsize, PROT_READ | PROT_WRITE, MAP_SHARED, d[0].fd, d[0].mapoffset); if (data == MAP_FAILED) return -errno; b->ptr = data; SPA_FLAG_SET(b->flags, BUFFER_FLAG_MAPPED); } else b->ptr = d[0].data; if (port->memtype == V4L2_MEMORY_USERPTR) { b->v4l2_buffer.m.userptr = (unsigned long) b->ptr; b->v4l2_buffer.length = d[0].maxsize; } else { if (xioctl(dev->fd, VIDIOC_QUERYBUF, &b->v4l2_buffer) < 0) { spa_log_error(this->log, "'%s' VIDIOC_QUERYBUF: %m", this->props.device); return -errno; } b->mmap_ptr = mmap(NULL, b->v4l2_buffer.length, PROT_READ, MAP_PRIVATE, dev->fd, b->v4l2_buffer.m.offset); if (b->mmap_ptr == MAP_FAILED) { spa_log_error(this->log, "'%s' mmap: %m", this->props.device); return -errno; } } } else if (port->memtype == V4L2_MEMORY_DMABUF) { b->v4l2_buffer.m.fd = d[0].fd; } else { spa_log_error(this->log, "%s: invalid port memory %d", this->props.device, port->memtype); return -EIO; } spa_v4l2_buffer_recycle(this, i); } port->n_buffers = reqbuf.count; return 0; } static int mmap_init(struct impl *this, struct spa_buffer **buffers, uint32_t n_buffers) { struct port *port = &this->out_ports[0]; struct spa_v4l2_device *dev = &port->dev; struct v4l2_requestbuffers reqbuf; unsigned int i; bool use_expbuf = false; port->memtype = V4L2_MEMORY_MMAP; spa_zero(reqbuf); reqbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; reqbuf.memory = port->memtype; reqbuf.count = n_buffers; if (xioctl(dev->fd, VIDIOC_REQBUFS, &reqbuf) < 0) { spa_log_error(this->log, "'%s' VIDIOC_REQBUFS: %m", this->props.device); return -errno; } spa_log_debug(this->log, "got %d buffers", reqbuf.count); if (reqbuf.count < n_buffers) { spa_log_error(this->log, "'%s' can't allocate enough buffers (%d < %d)", this->props.device, reqbuf.count, n_buffers); return -ENOMEM; } for (i = 0; i < n_buffers; i++) { struct buffer *b; struct spa_data *d; if (buffers[i]->n_datas < 1) { spa_log_error(this->log, "%s: invalid buffer data", this->props.device); return -EINVAL; } b = &port->buffers[i]; b->id = i; b->outbuf = buffers[i]; b->flags = BUFFER_FLAG_OUTSTANDING; b->h = spa_buffer_find_meta_data(buffers[i], SPA_META_Header, sizeof(*b->h)); b->vt = spa_buffer_find_meta_data(buffers[i], SPA_META_VideoTransform, sizeof(*b->vt)); spa_zero(b->v4l2_buffer); b->v4l2_buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; b->v4l2_buffer.memory = port->memtype; b->v4l2_buffer.index = i; if (xioctl(dev->fd, VIDIOC_QUERYBUF, &b->v4l2_buffer) < 0) { spa_log_error(this->log, "'%s' VIDIOC_QUERYBUF: %m", this->props.device); return -errno; } if (b->v4l2_buffer.flags & V4L2_BUF_FLAG_QUEUED) { /* some drivers can give us an already queued buffer. */ spa_log_warn(this->log, "buffer %d was already queued", i); n_buffers = i; break; } d = buffers[i]->datas; d[0].mapoffset = 0; d[0].maxsize = b->v4l2_buffer.length; d[0].chunk->offset = 0; d[0].chunk->size = 0; d[0].chunk->stride = port->fmt.fmt.pix.bytesperline; d[0].chunk->flags = 0; spa_log_debug(this->log, "data types %08x", d[0].type); again: if (port->have_expbuf && d[0].type != SPA_ID_INVALID && (d[0].type & ((1u << SPA_DATA_DmaBuf)|(1u<fd, VIDIOC_EXPBUF, &expbuf) < 0) { if (errno == ENOTTY || errno == EINVAL) { spa_log_debug(this->log, "'%s' VIDIOC_EXPBUF not supported: %m", this->props.device); port->have_expbuf = false; goto again; } spa_log_error(this->log, "'%s' VIDIOC_EXPBUF: %m", this->props.device); return -errno; } if (d[0].type & (1u<flags, BUFFER_FLAG_ALLOCATED); spa_log_debug(this->log, "EXPBUF fd:%d", expbuf.fd); use_expbuf = true; } else if (d[0].type & (1u << SPA_DATA_MemPtr)) { d[0].type = SPA_DATA_MemPtr; d[0].flags = SPA_DATA_FLAG_READABLE; d[0].fd = -1; d[0].mapoffset = b->v4l2_buffer.m.offset; d[0].data = mmap(NULL, b->v4l2_buffer.length, PROT_READ, MAP_SHARED, dev->fd, b->v4l2_buffer.m.offset); if (d[0].data == MAP_FAILED) { spa_log_error(this->log, "'%s' mmap: %m", this->props.device); return -errno; } b->ptr = d[0].data; SPA_FLAG_SET(b->flags, BUFFER_FLAG_MAPPED); spa_log_debug(this->log, "mmap offset:%u data:%p", d[0].mapoffset, b->ptr); use_expbuf = false; } else { spa_log_error(this->log, "%s: unsupported data type:%08x", this->props.device, d[0].type); port->alloc_buffers = false; return -ENOTSUP; } spa_v4l2_buffer_recycle(this, i); } spa_log_info(this->log, "%s: have %u buffers using %s", dev->path, n_buffers, use_expbuf ? "EXPBUF" : "MMAP"); port->n_buffers = n_buffers; return 0; } static int userptr_init(struct impl *this) { return -ENOTSUP; } static int read_init(struct impl *this) { return -ENOTSUP; } static int spa_v4l2_alloc_buffers(struct impl *this, struct spa_buffer **buffers, uint32_t n_buffers) { int res; struct port *port = &this->out_ports[0]; struct spa_v4l2_device *dev = &port->dev; if (port->n_buffers > 0) return -EIO; if (dev->cap.capabilities & V4L2_CAP_STREAMING) { if ((res = mmap_init(this, buffers, n_buffers)) < 0) if ((res = userptr_init(this)) < 0) return res; } else if (dev->cap.capabilities & V4L2_CAP_READWRITE) { if ((res = read_init(this)) < 0) return res; } else { spa_log_error(this->log, "%s: invalid capabilities %08x", this->props.device, dev->cap.capabilities); return -EIO; } return 0; } static int spa_v4l2_stream_on(struct impl *this) { struct port *port = &this->out_ports[0]; struct spa_v4l2_device *dev = &port->dev; enum v4l2_buf_type type; if (dev->fd == -1) return -EIO; if (!dev->have_format) return -EIO; if (dev->active) return 0; spa_log_debug(this->log, "starting"); if (port->current_format.media_subtype == SPA_MEDIA_SUBTYPE_raw || port->current_format.media_subtype == SPA_MEDIA_SUBTYPE_mjpg || port->current_format.media_subtype == SPA_MEDIA_SUBTYPE_jpeg) port->first_buffer = true; else port->first_buffer = false; mmap_read(this); type = V4L2_BUF_TYPE_VIDEO_CAPTURE; if (xioctl(dev->fd, VIDIOC_STREAMON, &type) < 0) { spa_log_error(this->log, "'%s' VIDIOC_STREAMON: %m", this->props.device); return -errno; } this->dll.bw = 0.0; port->source.func = v4l2_on_fd_events; port->source.data = this; port->source.fd = dev->fd; port->source.mask = SPA_IO_IN | SPA_IO_ERR; port->source.rmask = 0; spa_loop_add_source(this->data_loop, &port->source); dev->active = true; return 0; } static int do_remove_source(struct spa_loop *loop, bool async, uint32_t seq, const void *data, size_t size, void *user_data) { struct port *port = user_data; if (port->source.loop) spa_loop_remove_source(loop, &port->source); return 0; } static int spa_v4l2_stream_off(struct impl *this) { struct port *port = &this->out_ports[0]; struct spa_v4l2_device *dev = &port->dev; enum v4l2_buf_type type; uint32_t i; if (!dev->active) return 0; if (dev->fd == -1) return -EIO; spa_log_debug(this->log, "stopping"); spa_loop_locked(this->data_loop, do_remove_source, 0, NULL, 0, port); type = V4L2_BUF_TYPE_VIDEO_CAPTURE; if (xioctl(dev->fd, VIDIOC_STREAMOFF, &type) < 0) { spa_log_error(this->log, "'%s' VIDIOC_STREAMOFF: %m", this->props.device); return -errno; } for (i = 0; i < port->n_buffers; i++) { struct buffer *b; b = &port->buffers[i]; if (!SPA_FLAG_IS_SET(b->flags, BUFFER_FLAG_OUTSTANDING)) { if (xioctl(dev->fd, VIDIOC_QBUF, &b->v4l2_buffer) < 0) spa_log_warn(this->log, "VIDIOC_QBUF: %s", strerror(errno)); } } spa_list_init(&port->queue); dev->active = false; return 0; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/v4l2/v4l2.c000066400000000000000000000020101511204443500244550ustar00rootroot00000000000000/* Spa V4l2 support */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include "config.h" #include #include #include #include "v4l2.h" extern const struct spa_handle_factory spa_v4l2_source_factory; extern const struct spa_handle_factory spa_v4l2_udev_factory; extern const struct spa_handle_factory spa_v4l2_device_factory; SPA_LOG_TOPIC_DEFINE(v4l2_log_topic, "spa.v4l2"); SPA_LOG_TOPIC_ENUM_DEFINE_REGISTERED; SPA_EXPORT int spa_handle_factory_enum(const struct spa_handle_factory **factory, uint32_t *index) { spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(index != NULL, -EINVAL); switch (*index) { case 0: *factory = &spa_v4l2_source_factory; break; case 1: #ifdef HAVE_LIBUDEV *factory = &spa_v4l2_udev_factory; break; #else (*index)++; SPA_FALLTHROUGH; #endif case 2: *factory = &spa_v4l2_device_factory; break; default: return 0; } (*index)++; return 1; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/v4l2/v4l2.h000066400000000000000000000013621511204443500244730ustar00rootroot00000000000000/* Spa V4l2 support */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include #undef SPA_LOG_TOPIC_DEFAULT #define SPA_LOG_TOPIC_DEFAULT &v4l2_log_topic extern struct spa_log_topic v4l2_log_topic; static inline void v4l2_log_topic_init(struct spa_log *log) { spa_log_topic_init(log, &v4l2_log_topic); } struct spa_v4l2_device { struct spa_log *log; int fd; struct v4l2_capability cap; unsigned int active:1; unsigned int have_format:1; char path[64]; }; int spa_v4l2_open(struct spa_v4l2_device *dev, const char *path); int spa_v4l2_close(struct spa_v4l2_device *dev); int spa_v4l2_is_capture(struct spa_v4l2_device *dev); pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/videoconvert/000077500000000000000000000000001511204443500254515ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/videoconvert/meson.build000066400000000000000000000013271511204443500276160ustar00rootroot00000000000000videoconvert_sources = [ 'videoadapter.c', 'videoconvert-dummy.c', 'plugin.c' ] extra_cargs = [] extra_dependencies = [] if avcodec_dep.found() and avutil_dep.found() and swscale_dep.found() videoconvert_ffmpeg = static_library('videoconvert_fmmpeg', ['videoconvert-ffmpeg.c' ], dependencies : [ spa_dep, avcodec_dep, avutil_dep, swscale_dep ], install : false ) extra_cargs += '-D HAVE_VIDEOCONVERT_FFMPEG' extra_dependencies += videoconvert_ffmpeg endif videoconvertlib = shared_library('spa-videoconvert', videoconvert_sources, c_args : extra_cargs, dependencies : [ spa_dep, mathlib ], link_with : extra_dependencies, install : true, install_dir : spa_plugindir / 'videoconvert') pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/videoconvert/plugin.c000066400000000000000000000017331511204443500271170ustar00rootroot00000000000000/* Spa videoconvert plugin */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include extern const struct spa_handle_factory spa_videoadapter_factory; extern const struct spa_handle_factory spa_videoconvert_dummy_factory; #if HAVE_VIDEOCONVERT_FFMPEG extern const struct spa_handle_factory spa_videoconvert_ffmpeg_factory; #endif SPA_LOG_TOPIC_ENUM_DEFINE_REGISTERED; SPA_EXPORT int spa_handle_factory_enum(const struct spa_handle_factory **factory, uint32_t *index) { spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(index != NULL, -EINVAL); switch (*index) { case 0: *factory = &spa_videoadapter_factory; break; case 1: *factory = &spa_videoconvert_dummy_factory; break; #if HAVE_VIDEOCONVERT_FFMPEG case 2: *factory = &spa_videoconvert_ffmpeg_factory; break; #endif default: return 0; } (*index)++; return 1; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/videoconvert/videoadapter.c000066400000000000000000001570211511204443500302720ustar00rootroot00000000000000/* SPA */ /* SPDX-FileCopyrightText: Copyright © 2019 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #undef SPA_LOG_TOPIC_DEFAULT #define SPA_LOG_TOPIC_DEFAULT &log_topic SPA_LOG_TOPIC_DEFINE_STATIC(log_topic, "spa.videoadapter"); #define DEFAULT_ALIGN 16 #define MAX_PORTS (1+1) #define MAX_RETRY 64 /** \cond */ struct impl { struct spa_handle handle; struct spa_node node; struct spa_log *log; struct spa_cpu *cpu; struct spa_plugin_loader *ploader; uint32_t max_align; enum spa_direction direction; struct spa_node *target; struct spa_node *follower; struct spa_hook follower_listener; uint64_t follower_flags; struct spa_video_info follower_current_format; struct spa_video_info default_format; int in_set_param; struct spa_handle *hnd_convert; struct spa_node *convert; struct spa_hook convert_listener; uint64_t convert_port_flags; char *convertname; uint32_t n_buffers; struct spa_buffer **buffers; struct spa_io_buffers io_buffers; struct spa_io_rate_match io_rate_match; struct spa_io_position *io_position; uint64_t info_all; struct spa_node_info info; #define IDX_EnumFormat 0 #define IDX_PropInfo 1 #define IDX_Props 2 #define IDX_Format 3 #define IDX_EnumPortConfig 4 #define IDX_PortConfig 5 #define IDX_Latency 6 #define IDX_ProcessLatency 7 #define IDX_Tag 8 #define N_NODE_PARAMS 9 struct spa_param_info params[N_NODE_PARAMS]; uint32_t convert_params_flags[N_NODE_PARAMS]; uint32_t follower_params_flags[N_NODE_PARAMS]; uint64_t follower_port_flags; struct spa_hook_list hooks; struct spa_callbacks callbacks; unsigned int add_listener:1; unsigned int have_rate_match:1; unsigned int have_format:1; unsigned int recheck_format:1; unsigned int started:1; unsigned int ready:1; unsigned int async:1; enum spa_param_port_config_mode mode; unsigned int follower_removing:1; unsigned int in_recalc; unsigned int warned:1; unsigned int driver:1; int in_enum_sync; }; /** \endcond */ static int node_enum_params_sync(struct impl *impl, struct spa_node *node, uint32_t id, uint32_t *index, const struct spa_pod *filter, struct spa_pod **param, struct spa_pod_builder *builder) { int res; impl->in_enum_sync++; res = spa_node_enum_params_sync(node, id, index, filter, param, builder); impl->in_enum_sync--; return res; } static int node_port_enum_params_sync(struct impl *impl, struct spa_node *node, enum spa_direction direction, uint32_t port_id, uint32_t id, uint32_t *index, const struct spa_pod *filter, struct spa_pod **param, struct spa_pod_builder *builder) { int res; impl->in_enum_sync++; res = spa_node_port_enum_params_sync(node, direction, port_id, id, index, filter, param, builder); impl->in_enum_sync--; return res; } static int follower_enum_params(struct impl *this, uint32_t id, uint32_t idx, struct spa_result_node_params *result, const struct spa_pod *filter, struct spa_pod_builder *builder) { int res; if (result->next < 0x100000) { if (this->follower != this->target && this->convert_params_flags[idx] & SPA_PARAM_INFO_READ) { if ((res = node_enum_params_sync(this, this->target, id, &result->next, filter, &result->param, builder)) == 1) return res; } result->next = 0x100000; } if (result->next < 0x200000) { if (this->follower_params_flags[idx] & SPA_PARAM_INFO_READ) { result->next &= 0xfffff; if ((res = node_enum_params_sync(this, this->follower, id, &result->next, filter, &result->param, builder)) == 1) { result->next |= 0x100000; return res; } } result->next = 0x200000; } return 0; } static int convert_enum_port_config(struct impl *this, int seq, uint32_t id, uint32_t start, uint32_t num, const struct spa_pod *filter, struct spa_pod_builder *builder) { struct spa_pod *f1, *f2 = NULL; int res; if (this->convert == NULL) return 0; f1 = spa_pod_builder_add_object(builder, SPA_TYPE_OBJECT_ParamPortConfig, id, SPA_PARAM_PORT_CONFIG_direction, SPA_POD_Id(this->direction)); if (filter) { if ((res = spa_pod_filter(builder, &f2, f1, filter)) < 0) return res; } else { f2 = f1; } return spa_node_enum_params(this->convert, seq, id, start, num, f2); } static int impl_node_enum_params(void *object, int seq, uint32_t id, uint32_t start, uint32_t num, const struct spa_pod *filter) { struct impl *this = object; uint8_t buffer[4096]; spa_auto(spa_pod_dynamic_builder) b = { 0 }; struct spa_pod_builder_state state; struct spa_result_node_params result; uint32_t count = 0; int res; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(num != 0, -EINVAL); spa_pod_dynamic_builder_init(&b, buffer, sizeof(buffer), 4096); spa_pod_builder_get_state(&b.b, &state); result.id = id; result.next = start; next: result.index = result.next; spa_log_debug(this->log, "%p: %d id:%u", this, seq, id); spa_pod_builder_reset(&b.b, &state); switch (id) { case SPA_PARAM_EnumPortConfig: case SPA_PARAM_PortConfig: if (this->mode == SPA_PARAM_PORT_CONFIG_MODE_passthrough) { switch (result.index) { case 0: result.param = spa_pod_builder_add_object(&b.b, SPA_TYPE_OBJECT_ParamPortConfig, id, SPA_PARAM_PORT_CONFIG_direction, SPA_POD_Id(this->direction), SPA_PARAM_PORT_CONFIG_mode, SPA_POD_Id( SPA_PARAM_PORT_CONFIG_MODE_passthrough)); result.next++; res = 1; break; default: return 0; } } else { return convert_enum_port_config(this, seq, id, start, num, filter, &b.b); } break; case SPA_PARAM_PropInfo: res = follower_enum_params(this, id, IDX_PropInfo, &result, filter, &b.b); break; case SPA_PARAM_Props: res = follower_enum_params(this, id, IDX_Props, &result, filter, &b.b); break; case SPA_PARAM_ProcessLatency: res = follower_enum_params(this, id, IDX_ProcessLatency, &result, filter, &b.b); break; case SPA_PARAM_EnumFormat: case SPA_PARAM_Format: case SPA_PARAM_Latency: case SPA_PARAM_Tag: res = node_port_enum_params_sync(this, this->follower, this->direction, 0, id, &result.next, filter, &result.param, &b.b); break; default: return -ENOENT; } if (res != 1) return res; spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); count++; if (count != num) goto next; return 0; } static int link_io(struct impl *this) { int res; struct spa_io_rate_match *rate_match; size_t rate_match_size; spa_log_debug(this->log, "%p: controls", this); spa_zero(this->io_rate_match); this->io_rate_match.rate = 1.0; if (this->follower == this->target || !this->have_rate_match) { rate_match = NULL; rate_match_size = 0; } else { rate_match = &this->io_rate_match; rate_match_size = sizeof(this->io_rate_match); } if ((res = spa_node_port_set_io(this->follower, this->direction, 0, SPA_IO_RateMatch, rate_match, rate_match_size)) < 0) { spa_log_debug(this->log, "%p: set RateMatch on follower disabled %d %s", this, res, spa_strerror(res)); } else if (this->follower != this->target) { if ((res = spa_node_port_set_io(this->target, SPA_DIRECTION_REVERSE(this->direction), 0, SPA_IO_RateMatch, rate_match, rate_match_size)) < 0) { spa_log_warn(this->log, "%p: set RateMatch on target failed %d %s", this, res, spa_strerror(res)); } } return 0; } static int activate_io(struct impl *this, bool active) { int res; struct spa_io_buffers *data = active ? &this->io_buffers : NULL; uint32_t size = active ? sizeof(this->io_buffers) : 0; if (this->follower == this->target) return 0; if (active) this->io_buffers = SPA_IO_BUFFERS_INIT; if ((res = spa_node_port_set_io(this->follower, this->direction, 0, SPA_IO_Buffers, data, size)) < 0) { spa_log_warn(this->log, "%p: set Buffers on follower failed %d %s", this, res, spa_strerror(res)); return res; } else if ((res = spa_node_port_set_io(this->target, SPA_DIRECTION_REVERSE(this->direction), 0, SPA_IO_Buffers, data, size)) < 0) { spa_log_warn(this->log, "%p: set Buffers on convert failed %d %s", this, res, spa_strerror(res)); return res; } return 0; } static void emit_node_info(struct impl *this, bool full) { uint32_t i; uint64_t old = full ? this->info.change_mask : 0; spa_log_debug(this->log, "%p: info full:%d change:%08"PRIx64, this, full, this->info.change_mask); if (full) this->info.change_mask = this->info_all; if (this->info.change_mask) { struct spa_dict_item *items; uint32_t n_items = 0; if (this->info.props) n_items = this->info.props->n_items; items = alloca((n_items + 2) * sizeof(struct spa_dict_item)); for (i = 0; i < n_items; i++) items[i] = this->info.props->items[i]; items[n_items++] = SPA_DICT_ITEM_INIT("adapter.auto-port-config", NULL); items[n_items++] = SPA_DICT_ITEM_INIT("video.adapt.follower", NULL); this->info.props = &SPA_DICT_INIT(items, n_items); if (this->info.change_mask & SPA_NODE_CHANGE_MASK_PARAMS) { for (i = 0; i < this->info.n_params; i++) { if (this->params[i].user > 0) { this->params[i].flags ^= SPA_PARAM_INFO_SERIAL; this->params[i].user = 0; spa_log_debug(this->log, "param %d flags:%08x", i, this->params[i].flags); } } } spa_node_emit_info(&this->hooks, &this->info); this->info.change_mask = old; spa_zero(this->info.props); } } static int debug_params(struct impl *this, struct spa_node *node, enum spa_direction direction, uint32_t port_id, uint32_t id, struct spa_pod *filter, const char *debug, int err) { struct spa_pod_builder b = { 0 }; uint8_t buffer[4096]; uint32_t state; struct spa_pod *param; int res, count = 0; spa_log_error(this->log, "params %s: %d:%d (%s) %s", spa_debug_type_find_name(spa_type_param, id), direction, port_id, debug, err ? spa_strerror(err) : "no matching params"); if (err == -EBUSY) return 0; if (filter) { spa_log_error(this->log, "with this filter:"); spa_debug_log_pod(this->log, SPA_LOG_LEVEL_ERROR, 2, NULL, filter); } else { spa_log_error(this->log, "there was no filter"); } state = 0; while (true) { spa_pod_builder_init(&b, buffer, sizeof(buffer)); res = node_port_enum_params_sync(this, node, direction, port_id, id, &state, NULL, ¶m, &b); if (res != 1) { if (res < 0) spa_log_error(this->log, " error: %s", spa_strerror(res)); break; } spa_log_error(this->log, "unmatched %s %d:", debug, count); spa_debug_log_pod(this->log, SPA_LOG_LEVEL_ERROR, 2, NULL, param); count++; } if (count == 0) spa_log_error(this->log, "could not get any %s", debug); return 0; } static int negotiate_buffers(struct impl *this) { uint8_t buffer[4096]; struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); uint32_t state; struct spa_pod *param; int res; bool follower_alloc, conv_alloc; uint32_t i, size, buffers, blocks, align, flags, stride = 0, types; uint32_t *aligns, data_flags; struct spa_data *datas; struct spa_meta metas[1]; uint64_t follower_flags, conv_flags; struct spa_node *alloc_node; enum spa_direction alloc_direction; uint32_t alloc_flags; spa_log_debug(this->log, "%p: n_buffers:%d", this, this->n_buffers); if (this->follower == this->target) return 0; if (this->n_buffers > 0) return 0; state = 0; param = NULL; if ((res = node_port_enum_params_sync(this, this->target, SPA_DIRECTION_REVERSE(this->direction), 0, SPA_PARAM_Buffers, &state, param, ¶m, &b)) < 0) { if (res == -ENOENT) param = NULL; else { debug_params(this, this->target, SPA_DIRECTION_REVERSE(this->direction), 0, SPA_PARAM_Buffers, param, "target buffers", res); return res; } } state = 0; if ((res = node_port_enum_params_sync(this, this->follower, this->direction, 0, SPA_PARAM_Buffers, &state, param, ¶m, &b)) != 1) { if (res == -ENOENT) res = 0; else { debug_params(this, this->follower, this->direction, 0, SPA_PARAM_Buffers, param, "follower buffers", res); return res < 0 ? res : -ENOTSUP; } } if (param == NULL) return -ENOTSUP; spa_pod_fixate(param); follower_flags = this->follower_port_flags; conv_flags = this->convert_port_flags; follower_alloc = SPA_FLAG_IS_SET(follower_flags, SPA_PORT_FLAG_CAN_ALLOC_BUFFERS); conv_alloc = SPA_FLAG_IS_SET(conv_flags, SPA_PORT_FLAG_CAN_ALLOC_BUFFERS); flags = alloc_flags = 0; if (conv_alloc || follower_alloc) { flags |= SPA_BUFFER_ALLOC_FLAG_NO_DATA; alloc_flags = SPA_NODE_BUFFERS_FLAG_ALLOC; } align = DEFAULT_ALIGN; types = SPA_ID_INVALID; if ((res = spa_pod_parse_object(param, SPA_TYPE_OBJECT_ParamBuffers, NULL, SPA_PARAM_BUFFERS_buffers, SPA_POD_Int(&buffers), SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(&blocks), SPA_PARAM_BUFFERS_size, SPA_POD_Int(&size), SPA_PARAM_BUFFERS_stride, SPA_POD_Int(&stride), SPA_PARAM_BUFFERS_align, SPA_POD_OPT_Int(&align), SPA_PARAM_BUFFERS_dataType, SPA_POD_OPT_Int(&types))) < 0) return res; if (this->async) buffers = SPA_MAX(2u, buffers); spa_log_info(this->log, "%p: buffers:%d, blocks:%d, size:%d, stride:%d align:%d %d:%d", this, buffers, blocks, size, stride, align, follower_alloc, conv_alloc); align = SPA_MAX(align, this->max_align); datas = alloca(sizeof(struct spa_data) * blocks); memset(datas, 0, sizeof(struct spa_data) * blocks); aligns = alloca(sizeof(uint32_t) * blocks); data_flags = SPA_DATA_FLAG_READWRITE; if (SPA_FLAG_IS_SET(follower_flags, SPA_PORT_FLAG_DYNAMIC_DATA) && SPA_FLAG_IS_SET(conv_flags, SPA_PORT_FLAG_DYNAMIC_DATA)) data_flags |= SPA_DATA_FLAG_DYNAMIC; /* if we allocate, we allocate MemPtr memory */ if (!SPA_FLAG_IS_SET(alloc_flags, SPA_NODE_BUFFERS_FLAG_ALLOC)) types = SPA_DATA_MemPtr; for (i = 0; i < blocks; i++) { datas[i].type = types; datas[i].flags = data_flags; datas[i].maxsize = size; aligns[i] = align; } metas[0].type = SPA_META_Header; metas[0].size = sizeof(struct spa_meta_header); free(this->buffers); this->buffers = spa_buffer_alloc_array(buffers, flags, 1, metas, blocks, datas, aligns); if (this->buffers == NULL) return -errno; this->n_buffers = buffers; /* prefer to let the follower alloc */ if (follower_alloc) { alloc_node = this->follower; alloc_direction = this->direction; } else { alloc_node = this->target; alloc_direction = SPA_DIRECTION_REVERSE(this->direction); } if ((res = spa_node_port_use_buffers(alloc_node, alloc_direction, 0, alloc_flags, this->buffers, this->n_buffers)) < 0) return res; alloc_node = alloc_node == this->follower ? this->target : this->follower; alloc_direction = SPA_DIRECTION_REVERSE(alloc_direction); alloc_flags = 0; if ((res = spa_node_port_use_buffers(alloc_node, alloc_direction, 0, alloc_flags, this->buffers, this->n_buffers)) < 0) return res; activate_io(this, true); return 0; } static void clear_buffers(struct impl *this) { free(this->buffers); this->buffers = NULL; this->n_buffers = 0; } static int configure_format(struct impl *this, uint32_t flags, const struct spa_pod *format) { uint8_t buffer[4096]; int res; spa_log_debug(this->log, "%p: configure format:", this); if (format == NULL) { if (!this->have_format) return 0; activate_io(this, false); } else { spa_debug_log_format(this->log, SPA_LOG_LEVEL_DEBUG, 0, NULL, format); } if ((res = spa_node_port_set_param(this->follower, this->direction, 0, SPA_PARAM_Format, flags, format)) < 0) return res; if (res > 0) { struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); uint32_t state = 0; struct spa_pod *fmt; /* format was changed to nearest compatible format */ if ((res = node_port_enum_params_sync(this, this->follower, this->direction, 0, SPA_PARAM_Format, &state, NULL, &fmt, &b)) != 1) return -EIO; format = fmt; } if (this->target != this->follower) { if ((res = spa_node_port_set_param(this->target, SPA_DIRECTION_REVERSE(this->direction), 0, SPA_PARAM_Format, flags, format)) < 0) return res; } this->have_format = format != NULL; clear_buffers(this); if (format != NULL) res = negotiate_buffers(this); return res; } static int configure_convert(struct impl *this, uint32_t mode) { struct spa_pod_builder b = { 0 }; uint8_t buffer[1024]; struct spa_pod *param; if (this->convert == NULL) return 0; spa_pod_builder_init(&b, buffer, sizeof(buffer)); spa_log_debug(this->log, "%p: configure convert %p", this, this->target); param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamPortConfig, SPA_PARAM_PortConfig, SPA_PARAM_PORT_CONFIG_direction, SPA_POD_Id(this->direction), SPA_PARAM_PORT_CONFIG_mode, SPA_POD_Id(mode)); return spa_node_set_param(this->convert, SPA_PARAM_PortConfig, 0, param); } static const struct spa_node_events follower_node_events; static int recalc_latency(struct impl *this, struct spa_node *src, enum spa_direction direction, uint32_t port_id, struct spa_node *dst) { struct spa_pod_builder b = { 0 }; uint8_t buffer[1024]; struct spa_pod *param; uint32_t index = 0; struct spa_latency_info latency; int res; spa_log_debug(this->log, "%p: %d:%d", this, direction, port_id); if (this->target == this->follower) return 0; while (true) { spa_pod_builder_init(&b, buffer, sizeof(buffer)); if ((res = node_port_enum_params_sync(this, src, direction, port_id, SPA_PARAM_Latency, &index, NULL, ¶m, &b)) != 1) { param = NULL; break; } if ((res = spa_latency_parse(param, &latency)) < 0) return res; if (latency.direction == direction) break; } if ((res = spa_node_port_set_param(dst, SPA_DIRECTION_REVERSE(direction), 0, SPA_PARAM_Latency, 0, param)) < 0) return res; return 0; } static int recalc_tag(struct impl *this, struct spa_node *src, enum spa_direction direction, uint32_t port_id, struct spa_node *dst) { spa_auto(spa_pod_dynamic_builder) b = { 0 }; struct spa_pod_builder_state state; uint8_t buffer[2048]; struct spa_pod *param; uint32_t index = 0; struct spa_tag_info info; int res; spa_log_debug(this->log, "%p: %d:%d", this, direction, port_id); if (this->target == this->follower) return 0; spa_pod_dynamic_builder_init(&b, buffer, sizeof(buffer), 2048); spa_pod_builder_get_state(&b.b, &state); while (true) { void *tag_state = NULL; spa_pod_builder_reset(&b.b, &state); if ((res = node_port_enum_params_sync(this, src, direction, port_id, SPA_PARAM_Tag, &index, NULL, ¶m, &b.b)) != 1) { param = NULL; break; } if ((res = spa_tag_parse(param, &info, &tag_state)) < 0) return res; if (info.direction == direction) break; } return spa_node_port_set_param(dst, SPA_DIRECTION_REVERSE(direction), 0, SPA_PARAM_Tag, 0, param); } static int reconfigure_mode(struct impl *this, enum spa_param_port_config_mode mode, enum spa_direction direction, struct spa_pod *format) { int res = 0; struct spa_hook l; bool passthrough = mode == SPA_PARAM_PORT_CONFIG_MODE_passthrough; bool old_passthrough = this->mode == SPA_PARAM_PORT_CONFIG_MODE_passthrough; spa_log_debug(this->log, "%p: passthrough mode %d", this, passthrough); if (!passthrough && this->convert == NULL) return -ENOTSUP; if (old_passthrough != passthrough) { if (passthrough) { /* remove converter split/merge ports */ configure_convert(this, SPA_PARAM_PORT_CONFIG_MODE_none); } else { /* remove follower ports */ this->follower_removing = true; spa_zero(l); spa_node_add_listener(this->follower, &l, &follower_node_events, this); spa_hook_remove(&l); this->follower_removing = false; } } /* set new target */ this->target = passthrough ? this->follower : this->convert; if ((res = configure_format(this, SPA_NODE_PARAM_FLAG_NEAREST, format)) < 0) return res; this->mode = mode; if (old_passthrough != passthrough && passthrough) { /* add follower ports */ spa_zero(l); spa_node_add_listener(this->follower, &l, &follower_node_events, this); spa_hook_remove(&l); } else { /* add converter ports */ configure_convert(this, mode); } link_io(this); this->info.change_mask |= SPA_NODE_CHANGE_MASK_FLAGS | SPA_NODE_CHANGE_MASK_PARAMS; SPA_FLAG_UPDATE(this->info.flags, SPA_NODE_FLAG_NEED_CONFIGURE, this->mode == SPA_PARAM_PORT_CONFIG_MODE_none); SPA_FLAG_UPDATE(this->info.flags, SPA_NODE_FLAG_ASYNC, this->async && this->follower == this->target); this->params[IDX_Props].user++; emit_node_info(this, false); spa_log_debug(this->log, "%p: passthrough mode %d", this, passthrough); return 0; } static int impl_node_set_param(void *object, uint32_t id, uint32_t flags, const struct spa_pod *param) { int res = 0, res2 = 0; struct impl *this = object; struct spa_video_info info = { 0 }; spa_log_debug(this->log, "%p: set param %d", this, id); switch (id) { case SPA_PARAM_Format: if (this->started) return -EIO; if (param == NULL) return -EINVAL; if (spa_format_video_parse(param, &info) < 0) return -EINVAL; if (info.media_subtype != SPA_MEDIA_SUBTYPE_raw) return -EINVAL; this->follower_current_format = info; break; case SPA_PARAM_PortConfig: { enum spa_direction dir; enum spa_param_port_config_mode mode; struct spa_pod *format = NULL; if (this->started) { spa_log_error(this->log, "was started"); return -EIO; } if (spa_pod_parse_object(param, SPA_TYPE_OBJECT_ParamPortConfig, NULL, SPA_PARAM_PORT_CONFIG_direction, SPA_POD_Id(&dir), SPA_PARAM_PORT_CONFIG_mode, SPA_POD_Id(&mode), SPA_PARAM_PORT_CONFIG_format, SPA_POD_OPT_Pod(&format)) < 0) return -EINVAL; if (format) { struct spa_video_info info; spa_zero(info); if ((res = spa_format_video_parse(format, &info)) < 0) return res; this->default_format = info; } switch (mode) { case SPA_PARAM_PORT_CONFIG_MODE_none: return -ENOTSUP; case SPA_PARAM_PORT_CONFIG_MODE_passthrough: if ((res = reconfigure_mode(this, mode, dir, format)) < 0) return res; break; case SPA_PARAM_PORT_CONFIG_MODE_convert: case SPA_PARAM_PORT_CONFIG_MODE_dsp: if ((res = reconfigure_mode(this, mode, dir, NULL)) < 0) return res; break; default: return -EINVAL; } if (this->target != this->follower) { if ((res = spa_node_set_param(this->target, id, flags, param)) < 0) return res; res = recalc_latency(this, this->follower, this->direction, 0, this->target); } break; } case SPA_PARAM_Props: { int in_set_param = ++this->in_set_param; res = spa_node_set_param(this->follower, id, flags, param); if (this->target != this->follower && this->in_set_param == in_set_param) res2 = spa_node_set_param(this->target, id, flags, param); if (res < 0 && res2 < 0) return res; res = 0; break; } case SPA_PARAM_ProcessLatency: res = spa_node_set_param(this->follower, id, flags, param); break; default: res = -ENOTSUP; break; } return res; } static int impl_node_set_io(void *object, uint32_t id, void *data, size_t size) { struct impl *this = object; int res = 0; spa_return_val_if_fail(this != NULL, -EINVAL); switch (id) { case SPA_IO_Position: this->io_position = data; break; default: break; } if (this->target) res = spa_node_set_io(this->target, id, data, size); if (this->target != this->follower) res = spa_node_set_io(this->follower, id, data, size); return res; } static int update_param_peer_formats(struct impl *impl) { uint8_t buffer[4096]; spa_auto(spa_pod_dynamic_builder) b = { 0 }; uint32_t state = 0; struct spa_pod *param, *p, *str; struct spa_pod_frame f; int res; if (!impl->recheck_format) return 0; spa_log_debug(impl->log, "updating peer formats"); spa_node_send_command(impl->follower, &SPA_NODE_COMMAND_INIT(SPA_NODE_COMMAND_ParamBegin)); spa_pod_dynamic_builder_init(&b, buffer, sizeof(buffer), 4096); spa_pod_builder_push_struct(&b.b, &f); while (true) { res = node_port_enum_params_sync(impl, impl->follower, impl->direction, 0, SPA_PARAM_EnumFormat, &state, NULL, ¶m, &b.b); if (res != 1) break; } param = spa_pod_builder_pop(&b.b, &f); spa_node_send_command(impl->follower, &SPA_NODE_COMMAND_INIT(SPA_NODE_COMMAND_ParamEnd)); spa_pod_simplify(&b.b, ¶m, param); spa_debug_log_pod(impl->log, SPA_LOG_LEVEL_DEBUG, 0, NULL, param); str = spa_pod_copy(param); spa_pod_dynamic_builder_clean(&b); spa_pod_dynamic_builder_init(&b, buffer, sizeof(buffer), 4096); spa_peer_param_build_start(&b.b, &f, SPA_PARAM_PeerEnumFormat); SPA_POD_STRUCT_FOREACH(str, p) { spa_debug_log_pod(impl->log, SPA_LOG_LEVEL_DEBUG, 0, NULL, p); spa_peer_param_build_add_param(&b.b, 1, p); } param = spa_peer_param_build_end(&b.b, &f); spa_debug_log_pod(impl->log, SPA_LOG_LEVEL_DEBUG, 0, NULL, param); res = spa_node_port_set_param(impl->target, SPA_DIRECTION_REVERSE(impl->direction), 0, SPA_PARAM_PeerEnumFormat, 0, param); free(str); impl->recheck_format = false; spa_log_debug(impl->log, "done updating peer formats: %d", res); return 0; } static struct spa_pod *merge_objects(struct impl *this, struct spa_pod_builder *b, uint32_t id, struct spa_pod_object *o1, struct spa_pod_object *o2) { const struct spa_pod_prop *p1, *p2; struct spa_pod_frame f; struct spa_pod_builder_state state; int res = 0; if (o2 == NULL || o1->pod.type != o2->pod.type) return (struct spa_pod*)o1; spa_pod_builder_push_object(b, &f, o1->body.type, o1->body.id); p2 = NULL; SPA_POD_OBJECT_FOREACH(o1, p1) { p2 = spa_pod_object_find_prop(o2, p2, p1->key); if (p2 != NULL) { spa_pod_builder_get_state(b, &state); res = spa_pod_filter_prop(b, p2, p1); if (res < 0) spa_pod_builder_reset(b, &state); } if (p2 == NULL || res < 0) spa_pod_builder_raw_padded(b, p1, SPA_POD_PROP_SIZE(p1)); } p1 = NULL; SPA_POD_OBJECT_FOREACH(o2, p2) { p1 = spa_pod_object_find_prop(o1, p1, p2->key); if (p1 != NULL) continue; spa_pod_builder_raw_padded(b, p2, SPA_POD_PROP_SIZE(p2)); } return spa_pod_builder_pop(b, &f); } static int negotiate_format(struct impl *this) { uint32_t fstate, tstate; struct spa_pod *format, *def; uint8_t buffer[4096]; struct spa_pod_builder b = { 0 }; int res, fres; spa_log_debug(this->log, "%p: have_format:%d recheck:%d", this, this->have_format, this->recheck_format); if (this->target == this->follower) return 0; if (this->have_format && !this->recheck_format) return 0; update_param_peer_formats(this); spa_pod_builder_init(&b, buffer, sizeof(buffer)); spa_node_send_command(this->follower, &SPA_NODE_COMMAND_INIT(SPA_NODE_COMMAND_ParamBegin)); /* The target has been negotiated on its other ports and so it can propose * a passthrough format or an ideal conversion. We use the suggestions of the * target to find the best follower format */ for (tstate = 0;;) { format = NULL; res = node_port_enum_params_sync(this, this->target, SPA_DIRECTION_REVERSE(this->direction), 0, SPA_PARAM_EnumFormat, &tstate, NULL, &format, &b); if (res == -ENOENT) format = NULL; else if (res <= 0) break; if (format != NULL) spa_debug_log_pod(this->log, SPA_LOG_LEVEL_DEBUG, 0, NULL, format); fstate = 0; fres = node_port_enum_params_sync(this, this->follower, this->direction, 0, SPA_PARAM_EnumFormat, &fstate, format, &format, &b); if (fres == 0 && res == 1) continue; if (format != NULL) spa_debug_log_pod(this->log, SPA_LOG_LEVEL_DEBUG, 0, NULL, format); res = fres; break; } if (format == NULL) { debug_params(this, this->follower, this->direction, 0, SPA_PARAM_EnumFormat, format, "follower format", res); debug_params(this, this->target, SPA_DIRECTION_REVERSE(this->direction), 0, SPA_PARAM_EnumFormat, format, "convert format", res); res = -ENOTSUP; goto done; } def = spa_format_video_build(&b, SPA_PARAM_Format, &this->default_format); format = merge_objects(this, &b, SPA_PARAM_Format, (struct spa_pod_object*)format, (struct spa_pod_object*)def); if (format == NULL) return -ENOSPC; spa_pod_fixate(format); res = configure_format(this, SPA_NODE_PARAM_FLAG_NEAREST, format); done: spa_node_send_command(this->follower, &SPA_NODE_COMMAND_INIT(SPA_NODE_COMMAND_ParamEnd)); return res; } static int impl_node_send_command(void *object, const struct spa_command *command) { struct impl *this = object; int res; spa_return_val_if_fail(this != NULL, -EINVAL); spa_log_debug(this->log, "%p: command %d", this, SPA_NODE_COMMAND_ID(command)); switch (SPA_NODE_COMMAND_ID(command)) { case SPA_NODE_COMMAND_Start: spa_log_debug(this->log, "%p: starting %d", this, this->started); if ((res = negotiate_format(this)) < 0) return res; this->ready = true; this->warned = false; break; case SPA_NODE_COMMAND_Suspend: spa_log_debug(this->log, "%p: suspending", this); break; case SPA_NODE_COMMAND_Pause: spa_log_debug(this->log, "%p: pausing", this); break; case SPA_NODE_COMMAND_Flush: spa_log_debug(this->log, "%p: flushing", this); this->io_buffers.status = SPA_STATUS_OK; break; default: break; } res = spa_node_send_command(this->target, command); if (res == -ENOTSUP && this->target != this->follower) res = 0; if (res < 0) { spa_log_error(this->log, "%p: can't send command %d: %s", this, SPA_NODE_COMMAND_ID(command), spa_strerror(res)); } if (res >= 0 && this->target != this->follower) { if ((res = spa_node_send_command(this->follower, command)) < 0) { spa_log_error(this->log, "%p: can't send command %d: %s", this, SPA_NODE_COMMAND_ID(command), spa_strerror(res)); } } switch (SPA_NODE_COMMAND_ID(command)) { case SPA_NODE_COMMAND_Start: if (res < 0) { spa_log_debug(this->log, "%p: start failed", this); this->ready = false; configure_format(this, 0, NULL); } else { this->started = true; spa_log_debug(this->log, "%p: started", this); } break; case SPA_NODE_COMMAND_Suspend: configure_format(this, 0, NULL); this->started = false; this->warned = false; this->ready = false; spa_log_debug(this->log, "%p: suspended", this); break; case SPA_NODE_COMMAND_Pause: this->started = false; this->warned = false; this->ready = false; spa_log_debug(this->log, "%p: paused", this); break; case SPA_NODE_COMMAND_Flush: spa_log_debug(this->log, "%p: flushed", this); break; } return res; } static void convert_node_info(void *data, const struct spa_node_info *info) { struct impl *this = data; uint32_t i; spa_log_debug(this->log, "%p: info change:%08"PRIx64, this, info->change_mask); if (info->change_mask & SPA_NODE_CHANGE_MASK_PARAMS) { for (i = 0; i < info->n_params; i++) { uint32_t idx; switch (info->params[i].id) { case SPA_PARAM_EnumPortConfig: idx = IDX_EnumPortConfig; break; case SPA_PARAM_PortConfig: idx = IDX_PortConfig; break; case SPA_PARAM_PropInfo: idx = IDX_PropInfo; break; case SPA_PARAM_Props: idx = IDX_Props; break; default: continue; } if (!this->add_listener && this->convert_params_flags[idx] == info->params[i].flags) continue; this->info.change_mask |= SPA_NODE_CHANGE_MASK_PARAMS; this->convert_params_flags[idx] = info->params[i].flags; this->params[idx].flags = (this->params[idx].flags & SPA_PARAM_INFO_SERIAL) | (info->params[i].flags & SPA_PARAM_INFO_READWRITE); if (this->add_listener) continue; this->params[idx].user++; spa_log_debug(this->log, "param %d changed", info->params[i].id); } } emit_node_info(this, false); } static void follower_convert_port_info(void *data, enum spa_direction direction, uint32_t port_id, const struct spa_port_info *info) { struct impl *this = data; uint32_t i; int res; if (info == NULL) return; spa_log_debug(this->log, "%p: convert port info %s %p %08"PRIx64, this, this->direction == SPA_DIRECTION_INPUT ? "Input" : "Output", info, info->change_mask); this->convert_port_flags = info->flags; if (info->change_mask & SPA_PORT_CHANGE_MASK_PARAMS) { for (i = 0; i < info->n_params; i++) { uint32_t idx; switch (info->params[i].id) { case SPA_PARAM_Latency: idx = IDX_Latency; break; case SPA_PARAM_Tag: idx = IDX_Tag; break; default: continue; } if (!this->add_listener && this->convert_params_flags[idx] == info->params[i].flags) continue; this->convert_params_flags[idx] = info->params[i].flags; if (this->add_listener) continue; if (idx == IDX_Latency) { this->in_recalc++; res = recalc_latency(this, this->target, direction, port_id, this->follower); this->in_recalc--; spa_log_debug(this->log, "latency: %d (%s)", res, spa_strerror(res)); } if (idx == IDX_Tag) { this->in_recalc++; res = recalc_tag(this, this->target, direction, port_id, this->follower); this->in_recalc--; spa_log_debug(this->log, "tag: %d (%s)", res, spa_strerror(res)); } spa_log_debug(this->log, "param %d changed", info->params[i].id); } } } static void convert_port_info(void *data, enum spa_direction direction, uint32_t port_id, const struct spa_port_info *info) { struct impl *this = data; struct spa_port_info pi; if (direction != this->direction) { if (port_id == 0) { /* handle the converter output port into the follower separately */ follower_convert_port_info(this, direction, port_id, info); return; } else /* the monitor ports are exposed */ port_id--; } else if (info) { pi = *info; pi.flags |= this->follower_port_flags & (SPA_PORT_FLAG_LIVE | SPA_PORT_FLAG_PHYSICAL | SPA_PORT_FLAG_TERMINAL); info = π } spa_log_debug(this->log, "%p: port info %d:%d", this, direction, port_id); if (this->target != this->follower) spa_node_emit_port_info(&this->hooks, direction, port_id, info); } static void convert_result(void *data, int seq, int res, uint32_t type, const void *result) { struct impl *this = data; if (this->target == this->follower || this->in_enum_sync) return; spa_log_trace(this->log, "%p: result %d %d", this, seq, res); spa_node_emit_result(&this->hooks, seq, res, type, result); } static const struct spa_node_events convert_node_events = { SPA_VERSION_NODE_EVENTS, .info = convert_node_info, .port_info = convert_port_info, .result = convert_result, }; static void follower_info(void *data, const struct spa_node_info *info) { struct impl *this = data; uint32_t i; spa_log_debug(this->log, "%p: info change:%08"PRIx64" %d:%d", this, info->change_mask, info->max_input_ports, info->max_output_ports); if (this->follower_removing) return; this->async = (info->flags & SPA_NODE_FLAG_ASYNC) != 0; if (info->max_input_ports > 0) this->direction = SPA_DIRECTION_INPUT; else this->direction = SPA_DIRECTION_OUTPUT; if (this->direction == SPA_DIRECTION_INPUT) { this->info.flags |= SPA_NODE_FLAG_IN_PORT_CONFIG; this->info.max_input_ports = MAX_PORTS; } else { this->info.flags |= SPA_NODE_FLAG_OUT_PORT_CONFIG; this->info.max_output_ports = MAX_PORTS; } SPA_FLAG_UPDATE(this->info.flags, SPA_NODE_FLAG_ASYNC, this->async && this->follower == this->target); spa_log_debug(this->log, "%p: follower info %s", this, this->direction == SPA_DIRECTION_INPUT ? "Input" : "Output"); if (info->change_mask & SPA_NODE_CHANGE_MASK_PROPS) { this->info.change_mask |= SPA_NODE_CHANGE_MASK_PROPS; this->info.props = info->props; } if (info->change_mask & SPA_NODE_CHANGE_MASK_PARAMS) { for (i = 0; i < info->n_params; i++) { uint32_t idx; switch (info->params[i].id) { case SPA_PARAM_PropInfo: idx = IDX_PropInfo; break; case SPA_PARAM_Props: idx = IDX_Props; break; case SPA_PARAM_ProcessLatency: idx = IDX_ProcessLatency; break; default: continue; } if (!this->add_listener && this->follower_params_flags[idx] == info->params[i].flags) continue; this->info.change_mask |= SPA_NODE_CHANGE_MASK_PARAMS; this->follower_params_flags[idx] = info->params[i].flags; this->params[idx].flags = (this->params[idx].flags & SPA_PARAM_INFO_SERIAL) | (info->params[i].flags & SPA_PARAM_INFO_READWRITE); if (this->add_listener) continue; this->params[idx].user++; spa_log_debug(this->log, "param %d changed", info->params[i].id); } } emit_node_info(this, false); spa_zero(this->info.props); this->info.change_mask &= ~SPA_NODE_CHANGE_MASK_PROPS; } static void follower_port_info(void *data, enum spa_direction direction, uint32_t port_id, const struct spa_port_info *info) { struct impl *this = data; uint32_t i; int res; if (info == NULL) return; if (this->follower_removing) { spa_node_emit_port_info(&this->hooks, direction, port_id, NULL); return; } this->follower_port_flags = info->flags; spa_log_debug(this->log, "%p: follower port info %s %p %08"PRIx64" recalc:%u", this, this->direction == SPA_DIRECTION_INPUT ? "Input" : "Output", info, info->change_mask, this->in_recalc); if (info->change_mask & SPA_PORT_CHANGE_MASK_PARAMS) { for (i = 0; i < info->n_params; i++) { uint32_t idx; switch (info->params[i].id) { case SPA_PARAM_EnumFormat: idx = IDX_EnumFormat; break; case SPA_PARAM_Format: idx = IDX_Format; break; case SPA_PARAM_Latency: idx = IDX_Latency; break; case SPA_PARAM_Tag: idx = IDX_Tag; break; default: continue; } if (!this->add_listener && this->follower_params_flags[idx] == info->params[i].flags) continue; this->info.change_mask |= SPA_NODE_CHANGE_MASK_PARAMS; this->follower_params_flags[idx] = info->params[i].flags; this->params[idx].flags = (this->params[idx].flags & SPA_PARAM_INFO_SERIAL) | (info->params[i].flags & SPA_PARAM_INFO_READWRITE); if (this->add_listener) continue; if (idx == IDX_Latency && this->in_recalc == 0) { res = recalc_latency(this, this->follower, direction, port_id, this->target); spa_log_debug(this->log, "latency: %d (%s)", res, spa_strerror(res)); } if (idx == IDX_Tag && this->in_recalc == 0) { res = recalc_tag(this, this->follower, direction, port_id, this->target); spa_log_debug(this->log, "tag: %d (%s)", res, spa_strerror(res)); } if (idx == IDX_EnumFormat) { spa_log_debug(this->log, "new formats"); /* we will renegotiate when restarting */ this->recheck_format = true; } this->params[idx].user++; spa_log_debug(this->log, "param %d changed", info->params[i].id); } } emit_node_info(this, false); if (this->target == this->follower) spa_node_emit_port_info(&this->hooks, direction, port_id, info); } static void follower_result(void *data, int seq, int res, uint32_t type, const void *result) { struct impl *this = data; if (this->target != this->follower || this->in_enum_sync) return; spa_log_trace(this->log, "%p: result %d %d", this, seq, res); spa_node_emit_result(&this->hooks, seq, res, type, result); } static void follower_event(void *data, const struct spa_event *event) { struct impl *this = data; spa_log_trace(this->log, "%p: event %d", this, SPA_EVENT_TYPE(event)); switch (SPA_NODE_EVENT_ID(event)) { case SPA_NODE_EVENT_Error: case SPA_NODE_EVENT_RequestProcess: /* Forward errors and process requests */ spa_node_emit_event(&this->hooks, event); break; default: /* Ignore other events */ break; } } static const struct spa_node_events follower_node_events = { SPA_VERSION_NODE_EVENTS, .info = follower_info, .port_info = follower_port_info, .result = follower_result, .event = follower_event, }; static void follower_probe_info(void *data, const struct spa_node_info *info) { struct impl *this = data; if (info->max_input_ports > 0) this->direction = SPA_DIRECTION_INPUT; else this->direction = SPA_DIRECTION_OUTPUT; } static const struct spa_node_events follower_probe_events = { SPA_VERSION_NODE_EVENTS, .info = follower_probe_info, }; static int follower_ready(void *data, int status) { struct impl *this = data; spa_log_trace_fp(this->log, "%p: ready %d", this, status); if (!this->ready) { spa_log_info(this->log, "%p: ready stopped node", this); return -EIO; } if (this->target != this->follower) { this->driver = true; if (this->direction == SPA_DIRECTION_OUTPUT) { int retry = MAX_RETRY; while (retry--) { status = spa_node_process_fast(this->target); if (status & SPA_STATUS_HAVE_DATA) break; if (status & SPA_STATUS_NEED_DATA) { status = spa_node_process_fast(this->follower); if (!(status & SPA_STATUS_HAVE_DATA)) break; } } } } return spa_node_call_ready(&this->callbacks, status); } static int follower_reuse_buffer(void *data, uint32_t port_id, uint32_t buffer_id) { int res; struct impl *this = data; if (this->target != this->follower) res = spa_node_port_reuse_buffer(this->target, port_id, buffer_id); else res = spa_node_call_reuse_buffer(&this->callbacks, port_id, buffer_id); return res; } static int follower_xrun(void *data, uint64_t trigger, uint64_t delay, struct spa_pod *info) { struct impl *this = data; return spa_node_call_xrun(&this->callbacks, trigger, delay, info); } static const struct spa_node_callbacks follower_node_callbacks = { SPA_VERSION_NODE_CALLBACKS, .ready = follower_ready, .reuse_buffer = follower_reuse_buffer, .xrun = follower_xrun, }; static int impl_node_add_listener(void *object, struct spa_hook *listener, const struct spa_node_events *events, void *data) { struct impl *this = object; struct spa_hook l; struct spa_hook_list save; spa_return_val_if_fail(this != NULL, -EINVAL); spa_log_trace(this->log, "%p: add listener %p", this, listener); spa_hook_list_isolate(&this->hooks, &save, listener, events, data); if (events->info || events->port_info) { this->add_listener = true; spa_zero(l); spa_node_add_listener(this->follower, &l, &follower_node_events, this); spa_hook_remove(&l); if (this->follower != this->target) { spa_zero(l); spa_node_add_listener(this->target, &l, &convert_node_events, this); spa_hook_remove(&l); } this->add_listener = false; emit_node_info(this, true); } spa_hook_list_join(&this->hooks, &save); return 0; } static int impl_node_set_callbacks(void *object, const struct spa_node_callbacks *callbacks, void *data) { struct impl *this = object; spa_return_val_if_fail(this != NULL, -EINVAL); this->callbacks = SPA_CALLBACKS_INIT(callbacks, data); return 0; } static int impl_node_sync(void *object, int seq) { struct impl *this = object; spa_return_val_if_fail(this != NULL, -EINVAL); return spa_node_sync(this->follower, seq); } static int impl_node_add_port(void *object, enum spa_direction direction, uint32_t port_id, const struct spa_dict *props) { struct impl *this = object; spa_return_val_if_fail(this != NULL, -EINVAL); if (direction != this->direction) return -EINVAL; return spa_node_add_port(this->target, direction, port_id, props); } static int impl_node_remove_port(void *object, enum spa_direction direction, uint32_t port_id) { struct impl *this = object; spa_return_val_if_fail(this != NULL, -EINVAL); if (direction != this->direction) return -EINVAL; return spa_node_remove_port(this->target, direction, port_id); } static int impl_node_port_enum_params(void *object, int seq, enum spa_direction direction, uint32_t port_id, uint32_t id, uint32_t start, uint32_t num, const struct spa_pod *filter) { struct impl *this = object; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(num != 0, -EINVAL); if (direction != this->direction) port_id++; spa_log_debug(this->log, "%p: %d %u %u %u", this, seq, id, start, num); return spa_node_port_enum_params(this->target, seq, direction, port_id, id, start, num, filter); } static int impl_node_port_set_param(void *object, enum spa_direction direction, uint32_t port_id, uint32_t id, uint32_t flags, const struct spa_pod *param) { struct impl *this = object; spa_return_val_if_fail(this != NULL, -EINVAL); spa_log_debug(this->log, " %d %d %d %d", port_id, id, direction, this->direction); if (direction != this->direction) port_id++; return spa_node_port_set_param(this->target, direction, port_id, id, flags, param); } static int impl_node_port_set_io(void *object, enum spa_direction direction, uint32_t port_id, uint32_t id, void *data, size_t size) { struct impl *this = object; spa_return_val_if_fail(this != NULL, -EINVAL); spa_log_debug(this->log, "set io %d %d %d %d", port_id, id, direction, this->direction); if (direction != this->direction) port_id++; return spa_node_port_set_io(this->target, direction, port_id, id, data, size); } static int impl_node_port_use_buffers(void *object, enum spa_direction direction, uint32_t port_id, uint32_t flags, struct spa_buffer **buffers, uint32_t n_buffers) { struct impl *this = object; int res; spa_return_val_if_fail(this != NULL, -EINVAL); if (direction != this->direction) port_id++; spa_log_debug(this->log, "%p: %d %d:%d", this, n_buffers, direction, port_id); if ((res = spa_node_port_use_buffers(this->target, direction, port_id, flags, buffers, n_buffers)) < 0) return res; return res; } static int impl_node_port_reuse_buffer(void *object, uint32_t port_id, uint32_t buffer_id) { struct impl *this = object; spa_return_val_if_fail(this != NULL, -EINVAL); return spa_node_port_reuse_buffer(this->target, port_id, buffer_id); } static int impl_node_process(void *object) { struct impl *this = object; int status = 0, fstatus, retry = MAX_RETRY; if (!this->ready) { if (!this->warned) spa_log_warn(this->log, "%p: scheduling stopped node", this); this->warned = true; return -EIO; } spa_log_trace_fp(this->log, "%p: process convert:%p driver:%d", this, this->convert, this->driver); if (this->target == this->follower) { if (this->io_position) this->io_rate_match.size = this->io_position->clock.duration; return spa_node_process_fast(this->follower); } if (this->direction == SPA_DIRECTION_INPUT) { /* an input node (sink). * First we run the converter to process the input for the follower * then if it produced data, we run the follower. */ while (retry--) { status = spa_node_process_fast(this->target); /* schedule the follower when the converter needed * a recycled buffer */ if (status == -EPIPE || status == 0) status = SPA_STATUS_HAVE_DATA; else if (status < 0) break; if (status & (SPA_STATUS_HAVE_DATA | SPA_STATUS_DRAINED)) { /* as long as the converter produced something or * is drained, process the follower. */ fstatus = spa_node_process_fast(this->follower); if (fstatus < 0) { status = fstatus; break; } /* if the follower doesn't need more data or is * drained we can stop */ if ((fstatus & SPA_STATUS_NEED_DATA) == 0 || (fstatus & SPA_STATUS_DRAINED)) break; } /* the converter needs more data */ if ((status & SPA_STATUS_NEED_DATA)) break; } } else if (!this->driver) { bool done = false; while (retry--) { /* output node (source). First run the converter to make * sure we push out any queued data. Then when it needs * more data, schedule the follower. */ status = spa_node_process_fast(this->target); if (status == 0) status = SPA_STATUS_NEED_DATA; else if (status < 0) break; done = (status & (SPA_STATUS_HAVE_DATA | SPA_STATUS_DRAINED)); if (done) break; if (status & SPA_STATUS_NEED_DATA) { /* the converter needs more data, schedule the * follower */ fstatus = spa_node_process_fast(this->follower); if (fstatus < 0) { status = fstatus; break; } /* if the follower didn't produce more data or is * not drained we can stop now */ if ((fstatus & (SPA_STATUS_HAVE_DATA | SPA_STATUS_DRAINED)) == 0) break; } } if (!done) spa_node_call_xrun(&this->callbacks, 0, 0, NULL); } else { status = spa_node_process_fast(this->follower); } spa_log_trace_fp(this->log, "%p: process status:%d", this, status); this->driver = false; return status; } static const struct spa_node_methods impl_node = { SPA_VERSION_NODE_METHODS, .add_listener = impl_node_add_listener, .set_callbacks = impl_node_set_callbacks, .sync = impl_node_sync, .enum_params = impl_node_enum_params, .set_param = impl_node_set_param, .set_io = impl_node_set_io, .send_command = impl_node_send_command, .add_port = impl_node_add_port, .remove_port = impl_node_remove_port, .port_enum_params = impl_node_port_enum_params, .port_set_param = impl_node_port_set_param, .port_use_buffers = impl_node_port_use_buffers, .port_set_io = impl_node_port_set_io, .port_reuse_buffer = impl_node_port_reuse_buffer, .process = impl_node_process, }; static int load_converter(struct impl *this, const struct spa_dict *info, const struct spa_support *support, uint32_t n_support) { const char* factory_name = NULL; struct spa_handle *hnd_convert = NULL; void *iface_conv = NULL; struct spa_dict_item *items; struct spa_dict cinfo; char direction[16]; uint32_t i; items = alloca((info->n_items + 1) * sizeof(struct spa_dict_item)); cinfo = SPA_DICT(items, 0); for (i = 0; i < info->n_items; i++) items[cinfo.n_items++] = info->items[i]; snprintf(direction, sizeof(direction), "%s", SPA_DIRECTION_REVERSE(this->direction) == SPA_DIRECTION_INPUT ? "input" : "output"); items[cinfo.n_items++] = SPA_DICT_ITEM("convert.direction", direction); factory_name = spa_dict_lookup(&cinfo, "video.adapt.converter"); if (factory_name == NULL) return 0; if (this->ploader) { hnd_convert = spa_plugin_loader_load(this->ploader, factory_name, &cinfo); if (!hnd_convert) return -EINVAL; } else { return -ENOTSUP; } spa_handle_get_interface(hnd_convert, SPA_TYPE_INTERFACE_Node, &iface_conv); if (iface_conv == NULL) { spa_plugin_loader_unload(this->ploader, hnd_convert); return -EINVAL; } this->hnd_convert = hnd_convert; this->convert = iface_conv; this->convertname = strdup(factory_name); return 0; } static int impl_get_interface(struct spa_handle *handle, const char *type, void **interface) { struct impl *this; spa_return_val_if_fail(handle != NULL, -EINVAL); spa_return_val_if_fail(interface != NULL, -EINVAL); this = (struct impl *) handle; if (spa_streq(type, SPA_TYPE_INTERFACE_Node)) *interface = &this->node; else return -ENOENT; return 0; } static int impl_clear(struct spa_handle *handle) { struct impl *this; spa_return_val_if_fail(handle != NULL, -EINVAL); this = (struct impl *) handle; spa_hook_remove(&this->follower_listener); spa_node_set_callbacks(this->follower, NULL, NULL); if (this->hnd_convert) { spa_plugin_loader_unload(this->ploader, this->hnd_convert); free(this->convertname); } clear_buffers(this); return 0; } static size_t impl_get_size(const struct spa_handle_factory *factory, const struct spa_dict *params) { size_t size = sizeof(struct impl); return size; } static int impl_init(const struct spa_handle_factory *factory, struct spa_handle *handle, const struct spa_dict *info, const struct spa_support *support, uint32_t n_support) { struct impl *this; const char *str; int ret; struct spa_hook probe_listener; spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(handle != NULL, -EINVAL); handle->get_interface = impl_get_interface; handle->clear = impl_clear; this = (struct impl *) handle; this->log = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log); spa_log_topic_init(this->log, &log_topic); this->cpu = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_CPU); this->ploader = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_PluginLoader); if (info == NULL || (str = spa_dict_lookup(info, "video.adapt.follower")) == NULL) return -EINVAL; sscanf(str, "pointer:%p", &this->follower); if (this->follower == NULL) return -EINVAL; if (this->cpu) this->max_align = spa_cpu_get_max_align(this->cpu); spa_hook_list_init(&this->hooks); this->node.iface = SPA_INTERFACE_INIT( SPA_TYPE_INTERFACE_Node, SPA_VERSION_NODE, &impl_node, this); /* just probe the ports to get the direction */ spa_zero(probe_listener); spa_node_add_listener(this->follower, &probe_listener, &follower_probe_events, this); spa_hook_remove(&probe_listener); ret = load_converter(this, info, support, n_support); spa_log_info(this->log, "%p: loaded converter %s, hnd %p, convert %p", this, this->convertname, this->hnd_convert, this->convert); if (ret < 0) return ret; if (this->convert == NULL) { this->target = this->follower; this->mode = SPA_PARAM_PORT_CONFIG_MODE_passthrough; } else { this->target = this->convert; /* the actual mode is selected below */ this->mode = SPA_PARAM_PORT_CONFIG_MODE_none; } this->info_all = SPA_NODE_CHANGE_MASK_FLAGS | SPA_NODE_CHANGE_MASK_PROPS | SPA_NODE_CHANGE_MASK_PARAMS; this->info = SPA_NODE_INFO_INIT(); this->info.flags = SPA_NODE_FLAG_RT | SPA_NODE_FLAG_NEED_CONFIGURE; this->params[IDX_EnumFormat] = SPA_PARAM_INFO(SPA_PARAM_EnumFormat, SPA_PARAM_INFO_READ); this->params[IDX_PropInfo] = SPA_PARAM_INFO(SPA_PARAM_PropInfo, SPA_PARAM_INFO_READ); this->params[IDX_Props] = SPA_PARAM_INFO(SPA_PARAM_Props, SPA_PARAM_INFO_READWRITE); this->params[IDX_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); this->params[IDX_EnumPortConfig] = SPA_PARAM_INFO(SPA_PARAM_EnumPortConfig, SPA_PARAM_INFO_READ); this->params[IDX_PortConfig] = SPA_PARAM_INFO(SPA_PARAM_PortConfig, SPA_PARAM_INFO_READWRITE); this->params[IDX_Latency] = SPA_PARAM_INFO(SPA_PARAM_Latency, SPA_PARAM_INFO_READWRITE); this->params[IDX_ProcessLatency] = SPA_PARAM_INFO(SPA_PARAM_ProcessLatency, SPA_PARAM_INFO_READWRITE); this->params[IDX_Tag] = SPA_PARAM_INFO(SPA_PARAM_Tag, SPA_PARAM_INFO_READWRITE); this->info.params = this->params; this->info.n_params = N_NODE_PARAMS; spa_node_add_listener(this->follower, &this->follower_listener, &follower_node_events, this); spa_node_set_callbacks(this->follower, &follower_node_callbacks, this); if (this->convert != NULL) update_param_peer_formats(this); // TODO: adapt port bootstrap for arbitrary converter (incl. dummy) if (this->convert) { spa_node_add_listener(this->convert, &this->convert_listener, &convert_node_events, this); if (strcmp(this->convertname, "video.convert.dummy") == 0) { reconfigure_mode(this, SPA_PARAM_PORT_CONFIG_MODE_passthrough, this->direction, NULL); } else { reconfigure_mode(this, SPA_PARAM_PORT_CONFIG_MODE_convert, this->direction, NULL); } } else { reconfigure_mode(this, SPA_PARAM_PORT_CONFIG_MODE_passthrough, this->direction, NULL); } link_io(this); return 0; } static const struct spa_interface_info impl_interfaces[] = { { SPA_TYPE_INTERFACE_Node, }, }; static int impl_enum_interface_info(const struct spa_handle_factory *factory, const struct spa_interface_info **info, uint32_t *index) { spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(info != NULL, -EINVAL); spa_return_val_if_fail(index != NULL, -EINVAL); switch (*index) { case 0: *info = &impl_interfaces[*index]; break; default: return 0; } (*index)++; return 1; } const struct spa_handle_factory spa_videoadapter_factory = { .version = SPA_VERSION_HANDLE_FACTORY, .name = SPA_NAME_VIDEO_ADAPT, .get_size = impl_get_size, .init = impl_init, .enum_interface_info = impl_enum_interface_info, }; videoconvert-dummy.c000066400000000000000000000422671511204443500314110ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/videoconvert/* Spa */ /* SPDX-FileCopyrightText: Copyright © 2019 Wim Taymans */ /* SPDX-FileCopyrightText: Copyright © 2023 columbarius */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #undef SPA_LOG_TOPIC_DEFAULT #define SPA_LOG_TOPIC_DEFAULT &log_topic SPA_LOG_TOPIC_DEFINE_STATIC(log_topic, "spa.videoconvert.dummy"); #define MAX_PORTS 1 struct props { }; struct port { uint32_t direction; uint32_t id; struct spa_io_buffers *io; uint64_t info_all; struct spa_port_info info; #define IDX_EnumFormat 0 #define IDX_Meta 1 #define IDX_IO 2 #define IDX_Format 3 #define IDX_Buffers 4 #define IDX_Latency 5 #define IDX_Tag 6 #define N_PORT_PARAMS 7 struct spa_param_info params[N_PORT_PARAMS]; }; struct dir { struct port ports[MAX_PORTS]; uint32_t n_ports; enum spa_direction direction; enum spa_param_port_config_mode mode; struct spa_video_info format; unsigned int have_profile:1; struct spa_pod *tag; }; struct impl { struct spa_handle handle; struct spa_node node; struct spa_log *log; struct props props; struct spa_io_position *io_position; uint64_t info_all; struct spa_node_info info; #define IDX_EnumPortConfig 0 #define IDX_PortConfig 1 #define IDX_PropInfo 2 #define IDX_Props 3 #define N_NODE_PARAMS 4 struct spa_param_info params[N_NODE_PARAMS]; struct spa_hook_list hooks; struct spa_callbacks callbacks; struct dir dir[2]; }; #define CHECK_PORT(this,d,p) ((p) < this->dir[d].n_ports) static const struct spa_dict_item node_info_items[] = { { SPA_KEY_MEDIA_CLASS, "Video/Filter" }, }; static void emit_node_info(struct impl *this, bool full) { uint64_t old = full ? this->info.change_mask : 0; if (full) this->info.change_mask = this->info_all; if (this->info.change_mask) { this->info.props = &SPA_DICT_INIT_ARRAY(node_info_items); if (this->info.change_mask & SPA_NODE_CHANGE_MASK_PARAMS) { SPA_FOR_EACH_ELEMENT_VAR(this->params, p) { if (p->user > 0) { p->flags ^= SPA_PARAM_INFO_SERIAL; p->user = 0; } } } spa_node_emit_info(&this->hooks, &this->info); this->info.change_mask = old; } } static void emit_port_info(struct impl *this, struct port *port, bool full) { uint64_t old = full ? port->info.change_mask : 0; if (full) port->info.change_mask = port->info_all; if (port->info.change_mask) { struct spa_dict_item items[1]; items[0] = SPA_DICT_ITEM_INIT(SPA_KEY_FORMAT_DSP, "32 bit float RGBA video"); port->info.props = &SPA_DICT_INIT(items, 1); spa_node_emit_port_info(&this->hooks, port->direction, port->id, &port->info); port->info.change_mask = old; } } static int impl_node_enum_params(void *object, int seq, uint32_t id, uint32_t start, uint32_t num, const struct spa_pod *filter) { struct impl *this = object; struct spa_pod *param; struct spa_pod_builder b = { 0 }; uint8_t buffer[1024]; struct spa_result_node_params result; uint32_t count = 0; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(num != 0, -EINVAL); result.id = id; result.next = start; next: result.index = result.next++; spa_pod_builder_init(&b, buffer, sizeof(buffer)); switch (id) { case SPA_PARAM_EnumPortConfig: { struct dir *dir; switch (result.index) { case 0: dir = &this->dir[SPA_DIRECTION_INPUT];; break; case 1: dir = &this->dir[SPA_DIRECTION_OUTPUT];; break; default: return 0; } param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamPortConfig, id, SPA_PARAM_PORT_CONFIG_direction, SPA_POD_Id(dir->direction), SPA_PARAM_PORT_CONFIG_mode, SPA_POD_Id(SPA_PARAM_PORT_CONFIG_MODE_none), SPA_PARAM_PORT_CONFIG_monitor, SPA_POD_Bool(false), SPA_PARAM_PORT_CONFIG_control, SPA_POD_Bool(false)); break; } case SPA_PARAM_PortConfig: { struct dir *dir; struct spa_pod_frame f[1]; switch (result.index) { case 0: dir = &this->dir[SPA_DIRECTION_INPUT];; break; case 1: dir = &this->dir[SPA_DIRECTION_OUTPUT];; break; default: return 0; } spa_pod_builder_push_object(&b, &f[0], SPA_TYPE_OBJECT_ParamPortConfig, id); spa_pod_builder_add(&b, SPA_PARAM_PORT_CONFIG_direction, SPA_POD_Id(dir->direction), SPA_PARAM_PORT_CONFIG_mode, SPA_POD_Id(dir->mode), SPA_PARAM_PORT_CONFIG_monitor, SPA_POD_Bool(false), SPA_PARAM_PORT_CONFIG_control, SPA_POD_Bool(false), 0); param = spa_pod_builder_pop(&b, &f[0]); break; } case SPA_PARAM_PropInfo: { switch (result.index) { case 0: param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_PropInfo, id, SPA_PROP_INFO_name, SPA_POD_String("video.convert.converter"), SPA_PROP_INFO_description, SPA_POD_String("Name of the used videoconverter"), SPA_PROP_INFO_type, SPA_POD_String("dummy"), SPA_PROP_INFO_params, SPA_POD_Bool(true)); break; default: return 0; } break; } default: return 0; } if (spa_pod_filter(&b, &result.param, param, filter) < 0) goto next; spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); if (++count != num) goto next; return 0; } static int impl_node_set_io(void *object, uint32_t id, void *data, size_t size) { struct impl *this = object; spa_return_val_if_fail(this != NULL, -EINVAL); spa_log_debug(this->log, "%p: io %d %p/%zu", this, id, data, size); switch (id) { case SPA_IO_Position: if (size > 0 && size < sizeof(struct spa_io_position)) return -EINVAL; this->io_position = data; break; default: return -ENOENT; } return 0; } static int reconfigure_mode(struct impl *this, enum spa_param_port_config_mode mode, enum spa_direction direction, struct spa_video_info *info) { struct dir *dir; uint32_t i; dir = &this->dir[direction]; if (dir->have_profile && dir->mode == mode && (info == NULL || memcmp(&dir->format, info, sizeof(*info)) == 0)) return 0; spa_log_info(this->log, "%p: port config direction:%d mode:%d %d %p", this, direction, mode, dir->n_ports, info); for (i = 0; i < dir->n_ports; i++) { spa_node_emit_port_info(&this->hooks, direction, i, NULL); } dir->have_profile = true; dir->mode = mode; switch (mode) { case SPA_PARAM_PORT_CONFIG_MODE_none: break; default: return -ENOTSUP; } this->info.change_mask |= SPA_NODE_CHANGE_MASK_FLAGS | SPA_NODE_CHANGE_MASK_PARAMS; this->info.flags &= ~SPA_NODE_FLAG_NEED_CONFIGURE; this->params[IDX_Props].user++; this->params[IDX_PortConfig].user++; return 0; } static int impl_node_set_param(void *object, uint32_t id, uint32_t flags, const struct spa_pod *param) { struct impl *this = object; spa_return_val_if_fail(this != NULL, -EINVAL); if (param == NULL) return 0; switch (id) { case SPA_PARAM_PortConfig: { struct spa_video_info info = { 0, }, *infop = NULL; struct spa_pod *format = NULL; enum spa_direction direction; enum spa_param_port_config_mode mode; bool monitor = false, control = false; int res; if (spa_pod_parse_object(param, SPA_TYPE_OBJECT_ParamPortConfig, NULL, SPA_PARAM_PORT_CONFIG_direction, SPA_POD_Id(&direction), SPA_PARAM_PORT_CONFIG_mode, SPA_POD_Id(&mode), SPA_PARAM_PORT_CONFIG_monitor, SPA_POD_OPT_Bool(&monitor), SPA_PARAM_PORT_CONFIG_control, SPA_POD_OPT_Bool(&control), SPA_PARAM_PORT_CONFIG_format, SPA_POD_OPT_Pod(&format)) < 0) return -EINVAL; if (format) { if (!spa_pod_is_object_type(format, SPA_TYPE_OBJECT_Format)) return -EINVAL; if ((res = spa_format_parse(format, &info.media_type, &info.media_subtype)) < 0) return res; if (info.media_type != SPA_MEDIA_TYPE_video || info.media_subtype != SPA_MEDIA_SUBTYPE_raw) return -EINVAL; if (spa_format_video_raw_parse(format, &info.info.raw) < 0) return -EINVAL; if (info.info.raw.format == 0) return -EINVAL; infop = &info; } if ((res = reconfigure_mode(this, mode, direction, infop)) < 0) return res; emit_node_info(this, false); break; } default: return -ENOENT; } return 0; } static int impl_node_send_command(void *object, const struct spa_command *command) { struct impl *this = object; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(command != NULL, -EINVAL); switch (SPA_NODE_COMMAND_ID(command)) { default: return -ENOTSUP; } return 0; } static int impl_node_add_listener(void *object, struct spa_hook *listener, const struct spa_node_events *events, void *data) { struct impl *this = object; struct spa_hook_list save; spa_return_val_if_fail(this != NULL, -EINVAL); spa_log_trace(this->log, "%p: add listener %p", this, listener); spa_hook_list_isolate(&this->hooks, &save, listener, events, data); emit_node_info(this, true); emit_port_info(this, &this->dir[0].ports[0], true); emit_port_info(this, &this->dir[1].ports[0], true); spa_hook_list_join(&this->hooks, &save); return 0; } static int impl_node_set_callbacks(void *object, const struct spa_node_callbacks *callbacks, void *data) { struct impl *this = object; spa_return_val_if_fail(this != NULL, -EINVAL); this->callbacks = SPA_CALLBACKS_INIT(callbacks, data); return 0; } static int impl_node_add_port(void *object, enum spa_direction direction, uint32_t port_id, const struct spa_dict *props) { return -ENOTSUP; } static int impl_node_remove_port(void *object, enum spa_direction direction, uint32_t port_id) { return -ENOTSUP; } static int port_enum_formats(void *object, enum spa_direction direction, uint32_t port_id, uint32_t index, const struct spa_pod *filter, struct spa_pod **param, struct spa_pod_builder *builder) { return -ENOTSUP; } static int impl_node_port_enum_params(void *object, int seq, enum spa_direction direction, uint32_t port_id, uint32_t id, uint32_t start, uint32_t num, const struct spa_pod *filter) { struct impl *this = object; struct spa_pod *param; struct spa_pod_builder b = { 0 }; uint8_t buffer[4096]; struct spa_result_node_params result; uint32_t count = 0; int res; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(num != 0, -EINVAL); spa_log_debug(this->log, "%p: enum params port %d.%d %d %u", this, direction, port_id, seq, id); spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); result.id = id; result.next = start; next: result.index = result.next++; spa_pod_builder_init(&b, buffer, sizeof(buffer)); switch (id) { case SPA_PARAM_EnumFormat: if ((res = port_enum_formats(this, direction, port_id, result.index, filter, ¶m, &b)) <= 0) return res; break; case SPA_PARAM_Meta: switch (result.index) { case 0: param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamMeta, id, SPA_PARAM_META_type, SPA_POD_Id(SPA_META_Header), SPA_PARAM_META_size, SPA_POD_Int(sizeof(struct spa_meta_header))); break; default: return 0; } break; default: return -ENOENT; } if (spa_pod_filter(&b, &result.param, param, filter) < 0) goto next; spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); if (++count != num) goto next; return 0; } static int port_set_format(struct impl *this, enum spa_direction direction, uint32_t port_id, uint32_t flags, const struct spa_pod *format) { return -ENOTSUP; } static int impl_node_port_set_param(void *object, enum spa_direction direction, uint32_t port_id, uint32_t id, uint32_t flags, const struct spa_pod *param) { struct impl *this = object; spa_return_val_if_fail(this != NULL, -EINVAL); spa_log_debug(this->log, "%p: set param port %d.%d %u", this, direction, port_id, id); spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); switch (id) { case SPA_PARAM_Format: return port_set_format(this, direction, port_id, flags, param); default: return -ENOENT; } } static int impl_node_port_use_buffers(void *object, enum spa_direction direction, uint32_t port_id, uint32_t flags, struct spa_buffer **buffers, uint32_t n_buffers) { struct impl *this = object; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); return -ENOTSUP; } static int impl_node_port_set_io(void *object, enum spa_direction direction, uint32_t port_id, uint32_t id, void *data, size_t size) { struct impl *this = object; spa_return_val_if_fail(this != NULL, -EINVAL); return -ENOTSUP; } static int impl_node_port_reuse_buffer(void *object, uint32_t port_id, uint32_t buffer_id) { struct impl *this = object; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(CHECK_PORT(this, SPA_DIRECTION_OUTPUT, port_id), -EINVAL); return -ENOTSUP; } static int impl_node_process(void *object) { struct impl *this = object; spa_return_val_if_fail(this != NULL, -EINVAL); return -ENOTSUP; } static const struct spa_node_methods impl_node = { SPA_VERSION_NODE_METHODS, .add_listener = impl_node_add_listener, .set_callbacks = impl_node_set_callbacks, .enum_params = impl_node_enum_params, .set_param = impl_node_set_param, .set_io = impl_node_set_io, .send_command = impl_node_send_command, .add_port = impl_node_add_port, .remove_port = impl_node_remove_port, .port_enum_params = impl_node_port_enum_params, .port_set_param = impl_node_port_set_param, .port_use_buffers = impl_node_port_use_buffers, .port_set_io = impl_node_port_set_io, .port_reuse_buffer = impl_node_port_reuse_buffer, .process = impl_node_process, }; static int impl_get_interface(struct spa_handle *handle, const char *type, void **interface) { struct impl *this; spa_return_val_if_fail(handle != NULL, -EINVAL); spa_return_val_if_fail(interface != NULL, -EINVAL); this = (struct impl *) handle; if (spa_streq(type, SPA_TYPE_INTERFACE_Node)) *interface = &this->node; else return -ENOENT; return 0; } static int impl_clear(struct spa_handle *handle) { spa_return_val_if_fail(handle != NULL, -EINVAL); return 0; } static size_t impl_get_size(const struct spa_handle_factory *factory, const struct spa_dict *params) { return sizeof(struct impl); } static int impl_init(const struct spa_handle_factory *factory, struct spa_handle *handle, const struct spa_dict *info, const struct spa_support *support, uint32_t n_support) { struct impl *this; struct dir *dir; spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(handle != NULL, -EINVAL); handle->get_interface = impl_get_interface; handle->clear = impl_clear; this = (struct impl *) handle; this->log = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log); spa_log_topic_init(this->log, &log_topic); // props_reset(&this->props); this->node.iface = SPA_INTERFACE_INIT( SPA_TYPE_INTERFACE_Node, SPA_VERSION_NODE, &impl_node, this); spa_hook_list_init(&this->hooks); this->info_all = SPA_NODE_CHANGE_MASK_FLAGS | SPA_NODE_CHANGE_MASK_PROPS | SPA_NODE_CHANGE_MASK_PARAMS; this->info = SPA_NODE_INFO_INIT(); this->info.max_output_ports = 1; this->info.max_input_ports = 1; this->info.flags = SPA_NODE_FLAG_RT | SPA_NODE_FLAG_IN_PORT_CONFIG | SPA_NODE_FLAG_OUT_PORT_CONFIG | SPA_NODE_FLAG_NEED_CONFIGURE; this->params[IDX_EnumPortConfig] = SPA_PARAM_INFO(SPA_PARAM_EnumPortConfig, SPA_PARAM_INFO_READ); this->params[IDX_PortConfig] = SPA_PARAM_INFO(SPA_PARAM_PortConfig, SPA_PARAM_INFO_READWRITE); this->params[IDX_PropInfo] = SPA_PARAM_INFO(SPA_PARAM_PropInfo, SPA_PARAM_INFO_READ); this->params[IDX_Props] = SPA_PARAM_INFO(SPA_PARAM_Props, SPA_PARAM_INFO_READWRITE); this->info.params = this->params; this->info.n_params = N_NODE_PARAMS; dir = &this->dir[SPA_DIRECTION_INPUT]; dir->direction = SPA_DIRECTION_INPUT; dir = &this->dir[SPA_DIRECTION_OUTPUT]; dir->direction = SPA_DIRECTION_OUTPUT; reconfigure_mode(this, SPA_PARAM_PORT_CONFIG_MODE_none, SPA_DIRECTION_INPUT, NULL); reconfigure_mode(this, SPA_PARAM_PORT_CONFIG_MODE_none, SPA_DIRECTION_OUTPUT, NULL); return 0; } static const struct spa_interface_info impl_interfaces[] = { {SPA_TYPE_INTERFACE_Node,}, }; static int impl_enum_interface_info(const struct spa_handle_factory *factory, const struct spa_interface_info **info, uint32_t *index) { spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(info != NULL, -EINVAL); spa_return_val_if_fail(index != NULL, -EINVAL); switch (*index) { case 0: *info = &impl_interfaces[*index]; break; default: return 0; } (*index)++; return 1; } static const struct spa_dict_item info_items[] = { { SPA_KEY_FACTORY_AUTHOR, "Columbarius " }, { SPA_KEY_FACTORY_DESCRIPTION, "Dummy video convert plugin" }, }; static const struct spa_dict info = SPA_DICT_INIT_ARRAY(info_items); const struct spa_handle_factory spa_videoconvert_dummy_factory = { SPA_VERSION_HANDLE_FACTORY, SPA_NAME_VIDEO_CONVERT_DUMMY, &info, impl_get_size, impl_init, impl_enum_interface_info, }; videoconvert-ffmpeg.c000066400000000000000000002226251511204443500315200ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/videoconvert/* Spa */ /* SPDX-FileCopyrightText: Copyright © 2022 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #undef SPA_LOG_TOPIC_DEFAULT #define SPA_LOG_TOPIC_DEFAULT &log_topic SPA_LOG_TOPIC_DEFINE_STATIC(log_topic, "spa.videoconvert.ffmpeg"); #define MAX_ALIGN 64u #define MAX_BUFFERS 32u #define MAX_DATAS 4 #define MAX_PORTS (1+1) struct props { unsigned int dummy:1; }; static void props_reset(struct props *props) { props->dummy = false; } struct buffer { uint32_t id; #define BUFFER_FLAG_QUEUED (1<<0) #define BUFFER_FLAG_MAPPED (1<<1) uint32_t flags; struct spa_list link; struct spa_buffer *buf; void *datas[MAX_DATAS]; struct spa_meta_header *h; }; struct port { uint32_t direction; uint32_t id; struct spa_io_buffers *io; uint64_t info_all; struct spa_port_info info; #define IDX_EnumFormat 0 #define IDX_Meta 1 #define IDX_IO 2 #define IDX_Format 3 #define IDX_Buffers 4 #define IDX_Latency 5 #define IDX_Tag 6 #define IDX_PeerEnumFormat 7 #define IDX_PeerCapability 8 #define N_PORT_PARAMS 9 struct spa_param_info params[N_PORT_PARAMS]; struct spa_pod *peer_format_pod; const struct spa_pod **peer_formats; uint32_t n_peer_formats; struct buffer buffers[MAX_BUFFERS]; uint32_t n_buffers; struct spa_latency_info latency[2]; unsigned int have_latency:1; struct spa_video_info format; unsigned int valid:1; unsigned int have_format:1; unsigned int is_dsp:1; unsigned int is_monitor:1; unsigned int is_control:1; uint32_t blocks; uint32_t stride; uint32_t size; uint32_t maxsize; struct spa_list queue; }; struct dir { struct port *ports[MAX_PORTS]; uint32_t n_ports; enum spa_direction direction; enum spa_param_port_config_mode mode; struct spa_video_info format; unsigned int have_format:1; unsigned int have_profile:1; struct spa_pod *tag; enum AVPixelFormat pix_fmt; struct spa_rectangle size; struct spa_fraction framerate; ptrdiff_t linesizes[4]; size_t plane_size[4]; unsigned int control:1; }; struct impl { struct spa_handle handle; struct spa_node node; struct spa_log *log; struct spa_cpu *cpu; struct spa_loop *data_loop; uint32_t cpu_flags; uint32_t max_align; uint32_t quantum_limit; enum spa_direction direction; struct spa_ratelimit rate_limit; struct props props; struct spa_io_position *io_position; struct spa_io_rate_match *io_rate_match; uint64_t info_all; struct spa_node_info info; #define IDX_EnumPortConfig 0 #define IDX_PortConfig 1 #define IDX_PropInfo 2 #define IDX_Props 3 #define N_NODE_PARAMS 4 struct spa_param_info params[N_NODE_PARAMS]; struct spa_hook_list hooks; unsigned int monitor:1; struct dir dir[2]; unsigned int started:1; unsigned int setup:1; unsigned int fmt_passthrough:1; unsigned int drained:1; unsigned int port_ignore_latency:1; unsigned int monitor_passthrough:1; char group_name[128]; struct { AVCodecContext *context; AVPacket *packet; AVFrame *frame; } decoder; struct { struct SwsContext *context; AVFrame *frame; } convert; struct { AVCodecContext *context; AVFrame *frame; AVPacket *packet; } encoder; }; #define CHECK_PORT(this,d,p) ((p) < this->dir[d].n_ports) #define GET_PORT(this,d,p) (this->dir[d].ports[p]) #define GET_IN_PORT(this,p) GET_PORT(this,SPA_DIRECTION_INPUT,p) #define GET_OUT_PORT(this,p) GET_PORT(this,SPA_DIRECTION_OUTPUT,p) #define PORT_IS_DSP(this,d,p) (GET_PORT(this,d,p)->is_dsp) #define PORT_IS_CONTROL(this,d,p) (GET_PORT(this,d,p)->is_control) static void emit_node_info(struct impl *this, bool full) { uint64_t old = full ? this->info.change_mask : 0; if (full) this->info.change_mask = this->info_all; if (this->info.change_mask) { if (this->info.change_mask & SPA_NODE_CHANGE_MASK_PARAMS) { SPA_FOR_EACH_ELEMENT_VAR(this->params, p) { if (p->user > 0) { p->flags ^= SPA_PARAM_INFO_SERIAL; p->user = 0; } } } spa_node_emit_info(&this->hooks, &this->info); } this->info.change_mask = old; } static void emit_port_info(struct impl *this, struct port *port, bool full) { uint64_t old = full ? port->info.change_mask : 0; if (full) port->info.change_mask = port->info_all; if (port->info.change_mask) { struct spa_dict_item items[5]; uint32_t n_items = 0; if (PORT_IS_DSP(this, port->direction, port->id)) { items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_FORMAT_DSP, "32 bit float video"); if (port->is_monitor) items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_PORT_MONITOR, "true"); if (this->port_ignore_latency) items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_PORT_IGNORE_LATENCY, "true"); } else if (PORT_IS_CONTROL(this, port->direction, port->id)) { items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_PORT_NAME, "control"); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_FORMAT_DSP, "8 bit raw midi"); } if (this->group_name[0] != '\0') items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_PORT_GROUP, this->group_name); port->info.props = &SPA_DICT_INIT(items, n_items); if (port->info.change_mask & SPA_PORT_CHANGE_MASK_PARAMS) { SPA_FOR_EACH_ELEMENT_VAR(port->params, p) { if (p->user > 0) { p->flags ^= SPA_PARAM_INFO_SERIAL; p->user = 0; } } } spa_node_emit_port_info(&this->hooks, port->direction, port->id, &port->info); } port->info.change_mask = old; } static void emit_info(struct impl *this, bool full) { struct port *p; uint32_t i; emit_node_info(this, full); for (i = 0; i < this->dir[SPA_DIRECTION_INPUT].n_ports; i++) { if ((p = GET_IN_PORT(this, i)) && p->valid) emit_port_info(this, p, full); } for (i = 0; i < this->dir[SPA_DIRECTION_OUTPUT].n_ports; i++) { if ((p = GET_OUT_PORT(this, i)) && p->valid) emit_port_info(this, p, full); } } struct format_info { enum AVPixelFormat pix_fmt; uint32_t format; uint32_t dsp_format; #define FORMAT_DSP (1<<0) #define FORMAT_COMMON (1<<1) uint32_t flags; }; #if defined AV_PIX_FMT_AYUV #define VIDEO_FORMAT_DSP_AYUV SPA_VIDEO_FORMAT_AYUV #else #define VIDEO_FORMAT_DSP_AYUV SPA_VIDEO_FORMAT_Y444 #endif #define VIDEO_FORMAT_DSP_RGBA SPA_VIDEO_FORMAT_RGBA static struct format_info format_info[] = { #if defined AV_PIX_FMT_AYUV { AV_PIX_FMT_AYUV, SPA_VIDEO_FORMAT_AYUV, VIDEO_FORMAT_DSP_AYUV, FORMAT_DSP | FORMAT_COMMON }, #else { AV_PIX_FMT_YUV444P, SPA_VIDEO_FORMAT_Y444, VIDEO_FORMAT_DSP_AYUV, FORMAT_DSP | FORMAT_COMMON }, #endif { AV_PIX_FMT_RGBA, SPA_VIDEO_FORMAT_RGBA, VIDEO_FORMAT_DSP_RGBA, FORMAT_DSP | FORMAT_COMMON }, { AV_PIX_FMT_YUYV422, SPA_VIDEO_FORMAT_YUY2, VIDEO_FORMAT_DSP_AYUV, FORMAT_COMMON }, { AV_PIX_FMT_UYVY422, SPA_VIDEO_FORMAT_UYVY, VIDEO_FORMAT_DSP_AYUV, FORMAT_COMMON }, { AV_PIX_FMT_YVYU422, SPA_VIDEO_FORMAT_YVYU, VIDEO_FORMAT_DSP_AYUV, FORMAT_COMMON }, { AV_PIX_FMT_YUV420P, SPA_VIDEO_FORMAT_I420, VIDEO_FORMAT_DSP_AYUV, FORMAT_COMMON }, { AV_PIX_FMT_BGR0, SPA_VIDEO_FORMAT_BGRx, VIDEO_FORMAT_DSP_RGBA, FORMAT_COMMON }, { AV_PIX_FMT_BGRA, SPA_VIDEO_FORMAT_BGRA, VIDEO_FORMAT_DSP_RGBA, FORMAT_COMMON }, { AV_PIX_FMT_ARGB, SPA_VIDEO_FORMAT_ARGB, VIDEO_FORMAT_DSP_RGBA, FORMAT_COMMON }, { AV_PIX_FMT_ABGR, SPA_VIDEO_FORMAT_ABGR, VIDEO_FORMAT_DSP_RGBA, FORMAT_COMMON }, //{ AV_PIX_FMT_NONE, SPA_VIDEO_FORMAT_YV12 }, { AV_PIX_FMT_RGB0, SPA_VIDEO_FORMAT_RGBx, VIDEO_FORMAT_DSP_RGBA, FORMAT_COMMON }, { AV_PIX_FMT_0RGB, SPA_VIDEO_FORMAT_xRGB, VIDEO_FORMAT_DSP_RGBA, FORMAT_COMMON }, { AV_PIX_FMT_0BGR, SPA_VIDEO_FORMAT_xBGR, VIDEO_FORMAT_DSP_RGBA, FORMAT_COMMON }, //{ AV_PIX_FMT_RGB24, SPA_VIDEO_FORMAT_RGB }, //{ AV_PIX_FMT_BGR24, SPA_VIDEO_FORMAT_BGR }, //{ AV_PIX_FMT_YUV411P, SPA_VIDEO_FORMAT_Y41B }, //{ AV_PIX_FMT_YUV422P, SPA_VIDEO_FORMAT_Y42B }, //{ AV_PIX_FMT_NONE, SPA_VIDEO_FORMAT_v210 }, //{ AV_PIX_FMT_NONE, SPA_VIDEO_FORMAT_v216 }, //{ AV_PIX_FMT_NV12, SPA_VIDEO_FORMAT_NV12 }, //{ AV_PIX_FMT_NV21, SPA_VIDEO_FORMAT_NV21 }, //{ AV_PIX_FMT_GRAY8, SPA_VIDEO_FORMAT_GRAY8 }, //{ AV_PIX_FMT_GRAY16BE, SPA_VIDEO_FORMAT_GRAY16_BE }, //{ AV_PIX_FMT_GRAY16LE, SPA_VIDEO_FORMAT_GRAY16_LE }, //{ AV_PIX_FMT_NONE, SPA_VIDEO_FORMAT_v308 }, //{ AV_PIX_FMT_RGB565, SPA_VIDEO_FORMAT_RGB16 }, //{ AV_PIX_FMT_NONE, SPA_VIDEO_FORMAT_BGR16 }, //{ AV_PIX_FMT_RGB555, SPA_VIDEO_FORMAT_RGB15 }, //{ AV_PIX_FMT_NONE, SPA_VIDEO_FORMAT_BGR15 }, //{ AV_PIX_FMT_NONE, SPA_VIDEO_FORMAT_UYVP }, //{ AV_PIX_FMT_YUVA420P, SPA_VIDEO_FORMAT_A420 }, //{ AV_PIX_FMT_PAL8, SPA_VIDEO_FORMAT_RGB8P }, //{ AV_PIX_FMT_YUV410P, SPA_VIDEO_FORMAT_YUV9 }, //{ AV_PIX_FMT_NONE, SPA_VIDEO_FORMAT_YVU9 }, //{ AV_PIX_FMT_NONE, SPA_VIDEO_FORMAT_IYU1 }, //{ AV_PIX_FMT_NONE, SPA_VIDEO_FORMAT_ARGB64 }, //{ AV_PIX_FMT_NONE, SPA_VIDEO_FORMAT_AYUV64 }, //{ AV_PIX_FMT_NONE, SPA_VIDEO_FORMAT_r210 }, //{ AV_PIX_FMT_YUV420P10BE, SPA_VIDEO_FORMAT_I420_10BE }, //{ AV_PIX_FMT_YUV420P10LE, SPA_VIDEO_FORMAT_I420_10LE }, //{ AV_PIX_FMT_YUV422P10BE, SPA_VIDEO_FORMAT_I422_10BE }, //{ AV_PIX_FMT_YUV422P10LE, SPA_VIDEO_FORMAT_I422_10LE }, //{ AV_PIX_FMT_YUV444P10BE, SPA_VIDEO_FORMAT_Y444_10BE }, //{ AV_PIX_FMT_YUV444P10LE, SPA_VIDEO_FORMAT_Y444_10LE }, //{ AV_PIX_FMT_GBRP, SPA_VIDEO_FORMAT_GBR }, //{ AV_PIX_FMT_GBRP10BE, SPA_VIDEO_FORMAT_GBR_10BE }, //{ AV_PIX_FMT_GBRP10LE, SPA_VIDEO_FORMAT_GBR_10LE }, //{ AV_PIX_FMT_NONE, SPA_VIDEO_FORMAT_NV16 }, //{ AV_PIX_FMT_NONE, SPA_VIDEO_FORMAT_NV24 }, //{ AV_PIX_FMT_NONE, SPA_VIDEO_FORMAT_NV12_64Z32 }, //{ AV_PIX_FMT_YUVA420P10BE, SPA_VIDEO_FORMAT_A420_10BE }, //{ AV_PIX_FMT_YUVA420P10LE, SPA_VIDEO_FORMAT_A420_10LE }, //{ AV_PIX_FMT_YUVA422P10BE, SPA_VIDEO_FORMAT_A422_10BE }, //{ AV_PIX_FMT_YUVA422P10LE, SPA_VIDEO_FORMAT_A422_10LE }, //{ AV_PIX_FMT_YUVA444P10BE, SPA_VIDEO_FORMAT_A444_10BE }, //{ AV_PIX_FMT_YUVA444P10LE, SPA_VIDEO_FORMAT_A444_10LE }, //{ AV_PIX_FMT_NONE, SPA_VIDEO_FORMAT_NV61 }, //{ AV_PIX_FMT_NONE, SPA_VIDEO_FORMAT_P010_10BE }, //{ AV_PIX_FMT_NONE, SPA_VIDEO_FORMAT_P010_10LE }, //{ AV_PIX_FMT_NONE, SPA_VIDEO_FORMAT_IYU2 }, //{ AV_PIX_FMT_NONE, SPA_VIDEO_FORMAT_VYUY }, //{ AV_PIX_FMT_GBRAP, SPA_VIDEO_FORMAT_GBRA }, //{ AV_PIX_FMT_GBRAP10BE, SPA_VIDEO_FORMAT_GBRA_10BE }, //{ AV_PIX_FMT_GBRAP10LE, SPA_VIDEO_FORMAT_GBRA_10LE }, //{ AV_PIX_FMT_GBRP12BE, SPA_VIDEO_FORMAT_GBR_12BE }, //{ AV_PIX_FMT_GBRP12LE, SPA_VIDEO_FORMAT_GBR_12LE }, //{ AV_PIX_FMT_GBRAP12BE, SPA_VIDEO_FORMAT_GBRA_12BE }, //{ AV_PIX_FMT_GBRAP12LE, SPA_VIDEO_FORMAT_GBRA_12LE }, //{ AV_PIX_FMT_YUV420P12BE, SPA_VIDEO_FORMAT_I420_12BE }, //{ AV_PIX_FMT_YUV420P12LE, SPA_VIDEO_FORMAT_I420_12LE }, //{ AV_PIX_FMT_YUV422P12BE, SPA_VIDEO_FORMAT_I422_12BE }, //{ AV_PIX_FMT_YUV422P12LE, SPA_VIDEO_FORMAT_I422_12LE }, //{ AV_PIX_FMT_YUV444P12BE, SPA_VIDEO_FORMAT_Y444_12BE }, //{ AV_PIX_FMT_YUV444P12LE, SPA_VIDEO_FORMAT_Y444_12LE }, //{ AV_PIX_FMT_NONE, SPA_VIDEO_FORMAT_RGBA_F16 }, //{ AV_PIX_FMT_NONE, SPA_VIDEO_FORMAT_RGBA_F32 }, //{ AV_PIX_FMT_X2RGB10LE, SPA_VIDEO_FORMAT_xRGB_210LE }, //{ AV_PIX_FMT_X2BGR10LE, SPA_VIDEO_FORMAT_xBGR_210LE }, //{ AV_PIX_FMT_NONE, SPA_VIDEO_FORMAT_RGBx_102LE }, //{ AV_PIX_FMT_NONE, SPA_VIDEO_FORMAT_BGRx_102LE }, //{ AV_PIX_FMT_NONE, SPA_VIDEO_FORMAT_ARGB_210LE }, //{ AV_PIX_FMT_NONE, SPA_VIDEO_FORMAT_ABGR_210LE }, //{ AV_PIX_FMT_NONE, SPA_VIDEO_FORMAT_RGBA_102LE }, //{ AV_PIX_FMT_NONE, SPA_VIDEO_FORMAT_BGRA_102LE }, }; static struct format_info *format_info_for_format(uint32_t format) { SPA_FOR_EACH_ELEMENT_VAR(format_info, i) { if (i->format == format) return i; } return NULL; } static enum AVPixelFormat format_to_pix_fmt(uint32_t format) { struct format_info *i = format_info_for_format(format); return i ? i->pix_fmt : AV_PIX_FMT_NONE; } static int get_format(struct dir *dir, uint32_t *format, struct spa_rectangle *size, struct spa_fraction *framerate) { if (dir->have_format) { switch (dir->format.media_subtype) { case SPA_MEDIA_SUBTYPE_dsp: *format = dir->format.info.dsp.format; *size = SPA_RECTANGLE(640, 480); *framerate = SPA_FRACTION(30, 1); break; case SPA_MEDIA_SUBTYPE_raw: *format = dir->format.info.raw.format; *size = dir->format.info.raw.size; *framerate = dir->format.info.raw.framerate; break; case SPA_MEDIA_SUBTYPE_mjpg: *format = SPA_VIDEO_FORMAT_I420; *size = dir->format.info.mjpg.size; *framerate = dir->format.info.mjpg.framerate; break; case SPA_MEDIA_SUBTYPE_h264: *format = SPA_VIDEO_FORMAT_I420; *size = dir->format.info.h264.size; *framerate = dir->format.info.h264.framerate; break; default: *format = SPA_VIDEO_FORMAT_I420; *size = SPA_RECTANGLE(640, 480); *framerate = SPA_FRACTION(30, 1); break; } } else { *format = SPA_VIDEO_FORMAT_I420; *size = SPA_RECTANGLE(640, 480); *framerate = SPA_FRACTION(30, 1); } return 0; } static int init_port(struct impl *this, enum spa_direction direction, uint32_t port_id, bool is_dsp, bool is_monitor, bool is_control) { struct port *port = GET_PORT(this, direction, port_id); spa_assert(port_id < MAX_PORTS); if (port == NULL) { port = calloc(1, sizeof(struct port)); if (port == NULL) return -errno; this->dir[direction].ports[port_id] = port; } port->direction = direction; port->id = port_id; port->latency[SPA_DIRECTION_INPUT] = SPA_LATENCY_INFO(SPA_DIRECTION_INPUT); port->latency[SPA_DIRECTION_OUTPUT] = SPA_LATENCY_INFO(SPA_DIRECTION_OUTPUT); port->info = SPA_PORT_INFO_INIT(); port->info.change_mask = port->info_all = SPA_PORT_CHANGE_MASK_FLAGS | SPA_PORT_CHANGE_MASK_PROPS | SPA_PORT_CHANGE_MASK_PARAMS; port->info.flags = SPA_PORT_FLAG_NO_REF | SPA_PORT_FLAG_DYNAMIC_DATA; port->params[IDX_EnumFormat] = SPA_PARAM_INFO(SPA_PARAM_EnumFormat, SPA_PARAM_INFO_READ); port->params[IDX_Meta] = SPA_PARAM_INFO(SPA_PARAM_Meta, SPA_PARAM_INFO_READ); port->params[IDX_IO] = SPA_PARAM_INFO(SPA_PARAM_IO, SPA_PARAM_INFO_READ); port->params[IDX_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); port->params[IDX_Buffers] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); port->params[IDX_Latency] = SPA_PARAM_INFO(SPA_PARAM_Latency, SPA_PARAM_INFO_READWRITE); port->params[IDX_Tag] = SPA_PARAM_INFO(SPA_PARAM_Tag, SPA_PARAM_INFO_READWRITE); port->params[IDX_PeerEnumFormat] = SPA_PARAM_INFO(SPA_PARAM_PeerEnumFormat, SPA_PARAM_INFO_WRITE); port->params[IDX_PeerCapability] = SPA_PARAM_INFO(SPA_PARAM_PeerCapability, SPA_PARAM_INFO_WRITE); port->info.params = port->params; port->info.n_params = N_PORT_PARAMS; port->n_buffers = 0; port->have_format = false; port->is_monitor = is_monitor; port->is_dsp = is_dsp; if (port->is_dsp) { port->format.media_type = SPA_MEDIA_TYPE_video; port->format.media_subtype = SPA_MEDIA_SUBTYPE_dsp; port->format.info.dsp.format = SPA_VIDEO_FORMAT_DSP_F32; port->blocks = 1; port->stride = 16; } port->is_control = is_control; if (port->is_control) { port->format.media_type = SPA_MEDIA_TYPE_application; port->format.media_subtype = SPA_MEDIA_SUBTYPE_control; port->blocks = 1; port->stride = 1; } port->valid = true; spa_list_init(&port->queue); spa_log_debug(this->log, "%p: add port %d:%d %d %d %d", this, direction, port_id, is_dsp, is_monitor, is_control); return 0; } static int deinit_port(struct impl *this, enum spa_direction direction, uint32_t port_id) { struct port *port = GET_PORT(this, direction, port_id); if (port == NULL || !port->valid) return -ENOENT; port->valid = false; free(port->peer_formats); port->peer_formats = NULL; port->n_peer_formats = 0; free(port->peer_format_pod); port->peer_format_pod = NULL; spa_node_emit_port_info(&this->hooks, direction, port_id, NULL); return 0; } static int node_param_enum_port_config(struct impl *this, uint32_t id, uint32_t index, struct spa_pod **param, struct spa_pod_builder *b) { switch (index) { case 0 ... 1: { struct dir *dir = &this->dir[index]; *param = spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_ParamPortConfig, id, SPA_PARAM_PORT_CONFIG_direction, SPA_POD_Id(dir->direction), SPA_PARAM_PORT_CONFIG_mode, SPA_POD_CHOICE_ENUM_Id(4, SPA_PARAM_PORT_CONFIG_MODE_none, SPA_PARAM_PORT_CONFIG_MODE_none, SPA_PARAM_PORT_CONFIG_MODE_dsp, SPA_PARAM_PORT_CONFIG_MODE_convert), SPA_PARAM_PORT_CONFIG_monitor, SPA_POD_CHOICE_Bool(false), SPA_PARAM_PORT_CONFIG_control, SPA_POD_CHOICE_Bool(false)); return 1; } } return 0; } static int node_param_port_config(struct impl *this, uint32_t id, uint32_t index, struct spa_pod **param, struct spa_pod_builder *b) { switch (index) { case 0 ... 1: { struct dir *dir = &this->dir[index];; struct spa_pod_frame f[1]; spa_pod_builder_push_object(b, &f[0], SPA_TYPE_OBJECT_ParamPortConfig, id); spa_pod_builder_add(b, SPA_PARAM_PORT_CONFIG_direction, SPA_POD_Id(dir->direction), SPA_PARAM_PORT_CONFIG_mode, SPA_POD_Id(dir->mode), SPA_PARAM_PORT_CONFIG_monitor, SPA_POD_Bool(this->monitor), SPA_PARAM_PORT_CONFIG_control, SPA_POD_Bool(dir->control), 0); if (dir->have_format) { spa_pod_builder_prop(b, SPA_PARAM_PORT_CONFIG_format, 0); spa_format_video_build(b, id, &dir->format); } *param = spa_pod_builder_pop(b, &f[0]); return 1; } } return 0; } static int impl_node_enum_params(void *object, int seq, uint32_t id, uint32_t start, uint32_t num, const struct spa_pod *filter) { struct impl *this = object; struct spa_pod *param; struct spa_pod_builder b = { 0 }; uint8_t buffer[4096]; struct spa_result_node_params result; uint32_t count = 0; int res = 0; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(num != 0, -EINVAL); result.id = id; result.next = start; next: result.index = result.next++; spa_pod_builder_init(&b, buffer, sizeof(buffer)); param = NULL; switch (id) { case SPA_PARAM_EnumPortConfig: res = node_param_enum_port_config(this, id, result.index, ¶m, &b); break; case SPA_PARAM_PortConfig: res = node_param_port_config(this, id, result.index, ¶m, &b); break; case SPA_PARAM_PropInfo: res = 0; break; case SPA_PARAM_Props: res = 0; break; default: return 0; } if (res <= 0) return res; if (param == NULL || spa_pod_filter(&b, &result.param, param, filter) < 0) goto next; spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); if (++count != num) goto next; return 0; } static int impl_node_set_io(void *object, uint32_t id, void *data, size_t size) { struct impl *this = object; spa_return_val_if_fail(this != NULL, -EINVAL); spa_log_debug(this->log, "%p: io %d %p/%zd", this, id, data, size); switch (id) { case SPA_IO_Position: this->io_position = data; break; default: return -ENOENT; } return 0; } static int videoconvert_set_param(struct impl *this, const char *k, const char *s) { return 0; } static int parse_prop_params(struct impl *this, struct spa_pod *params) { struct spa_pod_parser prs; struct spa_pod_frame f; int changed = 0; spa_pod_parser_pod(&prs, params); if (spa_pod_parser_push_struct(&prs, &f) < 0) return 0; while (true) { const char *name; struct spa_pod *pod; char value[512]; if (spa_pod_parser_get_string(&prs, &name) < 0) break; if (spa_pod_parser_get_pod(&prs, &pod) < 0) break; if (spa_pod_is_string(pod)) { spa_pod_copy_string(pod, sizeof(value), value); } else if (spa_pod_is_float(pod)) { spa_dtoa(value, sizeof(value), SPA_POD_VALUE(struct spa_pod_float, pod)); } else if (spa_pod_is_double(pod)) { spa_dtoa(value, sizeof(value), SPA_POD_VALUE(struct spa_pod_double, pod)); } else if (spa_pod_is_int(pod)) { snprintf(value, sizeof(value), "%d", SPA_POD_VALUE(struct spa_pod_int, pod)); } else if (spa_pod_is_bool(pod)) { snprintf(value, sizeof(value), "%s", SPA_POD_VALUE(struct spa_pod_bool, pod) ? "true" : "false"); } else if (spa_pod_is_none(pod)) { spa_zero(value); } else continue; spa_log_info(this->log, "key:'%s' val:'%s'", name, value); changed += videoconvert_set_param(this, name, value); } return changed; } static int apply_props(struct impl *this, const struct spa_pod *param) { struct spa_pod_prop *prop; struct spa_pod_object *obj = (struct spa_pod_object *) param; int changed = 0; SPA_POD_OBJECT_FOREACH(obj, prop) { switch (prop->key) { case SPA_PROP_params: changed += parse_prop_params(this, &prop->value); break; default: break; } } return changed; } static int reconfigure_mode(struct impl *this, enum spa_param_port_config_mode mode, enum spa_direction direction, bool monitor, bool control, struct spa_video_info *info) { struct dir *dir; uint32_t i; dir = &this->dir[direction]; if (dir->have_profile && this->monitor == monitor && dir->mode == mode && dir->control == control && (info == NULL || memcmp(&dir->format, info, sizeof(*info)) == 0)) return 0; spa_log_debug(this->log, "%p: port config direction:%d monitor:%d " "control:%d mode:%d %d", this, direction, monitor, control, mode, dir->n_ports); for (i = 0; i < dir->n_ports; i++) { deinit_port(this, direction, i); if (this->monitor && direction == SPA_DIRECTION_INPUT) deinit_port(this, SPA_DIRECTION_OUTPUT, i+1); } this->monitor = monitor; this->setup = false; dir->control = control; dir->have_profile = true; dir->mode = mode; switch (mode) { case SPA_PARAM_PORT_CONFIG_MODE_dsp: { dir->n_ports = 1; dir->format.info.dsp = SPA_VIDEO_INFO_DSP_INIT( .format = SPA_VIDEO_FORMAT_DSP_F32); dir->have_format = true; if (this->monitor && direction == SPA_DIRECTION_INPUT) this->dir[SPA_DIRECTION_OUTPUT].n_ports = dir->n_ports + 1; for (i = 0; i < dir->n_ports; i++) { init_port(this, direction, i, true, false, false); if (this->monitor && direction == SPA_DIRECTION_INPUT) init_port(this, SPA_DIRECTION_OUTPUT, i+1, true, true, false); } break; } case SPA_PARAM_PORT_CONFIG_MODE_convert: { dir->n_ports = 1; dir->have_format = false; init_port(this, direction, 0, false, false, false); break; } case SPA_PARAM_PORT_CONFIG_MODE_none: break; default: return -ENOTSUP; } if (direction == SPA_DIRECTION_INPUT && dir->control) { i = dir->n_ports++; init_port(this, direction, i, false, false, true); } /* emit all port info first, then the node props and flags */ emit_info(this, false); this->info.change_mask |= SPA_NODE_CHANGE_MASK_FLAGS | SPA_NODE_CHANGE_MASK_PARAMS; this->info.flags &= ~SPA_NODE_FLAG_NEED_CONFIGURE; this->params[IDX_PortConfig].user++; return 0; } static int node_set_param_port_config(struct impl *this, uint32_t flags, const struct spa_pod *param) { struct spa_video_info info = { 0, }, *infop = NULL; struct spa_pod *format = NULL; enum spa_direction direction; enum spa_param_port_config_mode mode; bool monitor = false, control = false; int res; if (param == NULL) return 0; if (spa_pod_parse_object(param, SPA_TYPE_OBJECT_ParamPortConfig, NULL, SPA_PARAM_PORT_CONFIG_direction, SPA_POD_Id(&direction), SPA_PARAM_PORT_CONFIG_mode, SPA_POD_Id(&mode), SPA_PARAM_PORT_CONFIG_monitor, SPA_POD_OPT_Bool(&monitor), SPA_PARAM_PORT_CONFIG_control, SPA_POD_OPT_Bool(&control), SPA_PARAM_PORT_CONFIG_format, SPA_POD_OPT_Pod(&format)) < 0) return -EINVAL; if (format) { if (!spa_pod_is_object_type(format, SPA_TYPE_OBJECT_Format)) return -EINVAL; if ((res = spa_format_video_parse(format, &info)) < 0) return res; infop = &info; } return reconfigure_mode(this, mode, direction, monitor, control, infop); } static int node_set_param_props(struct impl *this, uint32_t flags, const struct spa_pod *param) { if (param == NULL) return 0; apply_props(this, param); return 0; } static int impl_node_set_param(void *object, uint32_t id, uint32_t flags, const struct spa_pod *param) { struct impl *this = object; int res; spa_return_val_if_fail(this != NULL, -EINVAL); switch (id) { case SPA_PARAM_PortConfig: res = node_set_param_port_config(this, flags, param); break; case SPA_PARAM_Props: res = node_set_param_props(this, flags, param); break; default: return -ENOENT; } emit_info(this, false); return res; } static inline void free_decoder(struct impl *this) { avcodec_free_context(&this->decoder.context); av_packet_free(&this->decoder.packet); } static inline void free_encoder(struct impl *this) { avcodec_free_context(&this->encoder.context); av_packet_free(&this->encoder.packet); av_frame_free(&this->encoder.frame); } static int setup_convert(struct impl *this) { struct dir *in, *out; uint32_t in_format = 0, out_format = 0; int decoder_id = 0, encoder_id = 0; const AVCodec *codec; in = &this->dir[SPA_DIRECTION_INPUT]; out = &this->dir[SPA_DIRECTION_OUTPUT]; spa_log_debug(this->log, "%p: setup:%d in_format:%d out_format:%d", this, this->setup, in->have_format, out->have_format); if (this->setup) return 0; if (!in->have_format || !out->have_format) return -EIO; get_format(in, &in_format, &in->size, &in->framerate); get_format(out, &out_format, &out->size, &out->framerate); switch (in->format.media_subtype) { case SPA_MEDIA_SUBTYPE_dsp: case SPA_MEDIA_SUBTYPE_raw: in->pix_fmt = format_to_pix_fmt(in_format); switch (out->format.media_subtype) { case SPA_MEDIA_SUBTYPE_raw: case SPA_MEDIA_SUBTYPE_dsp: out->pix_fmt = format_to_pix_fmt(out_format); break; case SPA_MEDIA_SUBTYPE_mjpg: encoder_id = AV_CODEC_ID_MJPEG; out->format.media_subtype = SPA_MEDIA_SUBTYPE_raw; out->format.info.raw.format = SPA_VIDEO_FORMAT_I420; out->format.info.raw.size = in->size; out->format.info.raw.framerate = in->framerate; out->pix_fmt = AV_PIX_FMT_YUVJ420P; break; case SPA_MEDIA_SUBTYPE_h264: encoder_id = AV_CODEC_ID_H264; out->format.media_subtype = SPA_MEDIA_SUBTYPE_raw; out->format.info.raw.format = SPA_VIDEO_FORMAT_I420; out->format.info.raw.size = in->size; out->format.info.raw.framerate = in->framerate; out->pix_fmt = AV_PIX_FMT_YUVJ420P; break; default: spa_log_warn(this->log, "%p: in_subtype:%d out_subtype:%d", this, in->format.media_subtype, out->format.media_subtype); return -ENOTSUP; } break; case SPA_MEDIA_SUBTYPE_mjpg: switch (out->format.media_subtype) { case SPA_MEDIA_SUBTYPE_mjpg: /* passthrough if same dimensions or else reencode */ if (in->size.width != out->size.width || in->size.height != out->size.height) { encoder_id = decoder_id = AV_CODEC_ID_MJPEG; out->pix_fmt = AV_PIX_FMT_YUVJ420P; } break; case SPA_MEDIA_SUBTYPE_raw: case SPA_MEDIA_SUBTYPE_dsp: out->pix_fmt = format_to_pix_fmt(out_format); decoder_id = AV_CODEC_ID_MJPEG; break; default: spa_log_warn(this->log, "%p: in_subtype:%d out_subtype:%d", this, in->format.media_subtype, out->format.media_subtype); return -ENOTSUP; } break; case SPA_MEDIA_SUBTYPE_h264: switch (out->format.media_subtype) { case SPA_MEDIA_SUBTYPE_h264: /* passthrough if same dimensions or else reencode */ if (in->size.width != out->size.width || in->size.height != out->size.height) encoder_id = decoder_id = AV_CODEC_ID_H264; break; case SPA_MEDIA_SUBTYPE_raw: case SPA_MEDIA_SUBTYPE_dsp: out->pix_fmt = format_to_pix_fmt(out_format); decoder_id = AV_CODEC_ID_H264; break; default: spa_log_warn(this->log, "%p: in_subtype:%d out_subtype:%d", this, in->format.media_subtype, out->format.media_subtype); return -ENOTSUP; } break; default: spa_log_warn(this->log, "%p: in_subtype:%d out_subtype:%d", this, in->format.media_subtype, out->format.media_subtype); return -ENOTSUP; } if (in->pix_fmt == AV_PIX_FMT_NONE || out->pix_fmt == AV_PIX_FMT_NONE) { spa_log_warn(this->log, "%p: unsupported pixel format", this); return -ENOTSUP; } if (decoder_id) { if ((codec = avcodec_find_decoder(decoder_id)) == NULL) { spa_log_error(this->log, "failed to find %d decoder", decoder_id); return -ENOTSUP; } if ((this->decoder.context = avcodec_alloc_context3(codec)) == NULL) return -EIO; if ((this->decoder.packet = av_packet_alloc()) == NULL) return -EIO; this->decoder.context->flags2 |= AV_CODEC_FLAG2_FAST; if (avcodec_open2(this->decoder.context, codec, NULL) < 0) { spa_log_error(this->log, "failed to open decoder codec"); return -EIO; } spa_log_info(this->log, "%p: using decoder %s", this, codec->name); } else { free_decoder(this); } av_frame_free(&this->decoder.frame); if ((this->decoder.frame = av_frame_alloc()) == NULL) return -EIO; if (encoder_id) { if ((codec = avcodec_find_encoder(encoder_id)) == NULL) { spa_log_error(this->log, "failed to find %d encoder", encoder_id); return -ENOTSUP; } if ((this->encoder.context = avcodec_alloc_context3(codec)) == NULL) return -EIO; if ((this->encoder.packet = av_packet_alloc()) == NULL) return -EIO; if ((this->encoder.frame = av_frame_alloc()) == NULL) return -EIO; this->encoder.context->flags2 |= AV_CODEC_FLAG2_FAST; this->encoder.context->time_base.num = 1; this->encoder.context->width = out->size.width; this->encoder.context->height = out->size.height; this->encoder.context->pix_fmt = out->pix_fmt; if (avcodec_open2(this->encoder.context, codec, NULL) < 0) { spa_log_error(this->log, "failed to open encoder codec"); return -EIO; } spa_log_info(this->log, "%p: using encoder %s", this, codec->name); } else { free_encoder(this); } sws_freeContext(this->convert.context); this->convert.context = NULL; av_frame_free(&this->convert.frame); if ((this->convert.frame = av_frame_alloc()) == NULL) return -EIO; this->setup = true; return 0; } static void reset_node(struct impl *this) { } static int impl_node_send_command(void *object, const struct spa_command *command) { struct impl *this = object; int res; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(command != NULL, -EINVAL); switch (SPA_NODE_COMMAND_ID(command)) { case SPA_NODE_COMMAND_Start: if (this->started) return 0; if ((res = setup_convert(this)) < 0) return res; this->started = true; break; case SPA_NODE_COMMAND_Suspend: this->setup = false; SPA_FALLTHROUGH; case SPA_NODE_COMMAND_Pause: this->started = false; break; case SPA_NODE_COMMAND_Flush: reset_node(this); break; default: return -ENOTSUP; } return 0; } static int impl_node_add_listener(void *object, struct spa_hook *listener, const struct spa_node_events *events, void *data) { struct impl *this = object; struct spa_hook_list save; spa_return_val_if_fail(this != NULL, -EINVAL); spa_log_trace(this->log, "%p: add listener %p", this, listener); spa_hook_list_isolate(&this->hooks, &save, listener, events, data); emit_info(this, true); spa_hook_list_join(&this->hooks, &save); return 0; } static int impl_node_set_callbacks(void *object, const struct spa_node_callbacks *callbacks, void *user_data) { return 0; } static int impl_node_add_port(void *object, enum spa_direction direction, uint32_t port_id, const struct spa_dict *props) { return -ENOTSUP; } static int impl_node_remove_port(void *object, enum spa_direction direction, uint32_t port_id) { return -ENOTSUP; } static void add_video_formats(struct spa_pod_builder *b, uint32_t *vals, uint32_t n_vals, bool is_dsp) { struct spa_pod_frame f[1]; uint32_t ids[SPA_N_ELEMENTS(format_info) + 1], n_ids = 0, i, j, fmt; spa_pod_builder_prop(b, SPA_FORMAT_VIDEO_format, 0); spa_pod_builder_push_choice(b, &f[0], SPA_CHOICE_Enum, 0); if (n_vals == 0) { vals = &format_info[0].format; n_vals = 1; } /* all supported formats */ for (i = 0; i < n_vals; i++) { struct format_info *fi = format_info_for_format(vals[i]); if (fi == NULL) continue; fmt = is_dsp ? fi->dsp_format : fi->format; for (j = 0; j < n_ids; j++) { if (ids[j] == fmt) break; } if (j == n_ids) ids[n_ids++] = fmt; } /* then add all other supported formats */ SPA_FOR_EACH_ELEMENT_VAR(format_info, fi) { if (fi->pix_fmt == AV_PIX_FMT_NONE) continue; fmt = is_dsp ? fi->dsp_format : fi->format; for (j = 0; j < n_ids; j++) { if (fmt == ids[j]) break; } if (j == n_ids) ids[n_ids++] = fmt; } for (i = 0; i < n_ids; i++) { spa_pod_builder_id(b, ids[i]); if (i == 0) spa_pod_builder_id(b, ids[i]); } spa_pod_builder_pop(b, &f[0]); } static struct spa_pod *transform_format(struct impl *this, struct port *port, const struct spa_pod *format, uint32_t id, struct spa_pod_builder *b) { uint32_t media_type, media_subtype, fmt; struct spa_pod_object *obj; const struct spa_pod_prop *prop; struct spa_pod_frame f[2]; if (!spa_format_parse(format, &media_type, &media_subtype) || media_type != SPA_MEDIA_TYPE_video) { return NULL; } obj = (struct spa_pod_object*)format; spa_pod_builder_push_object(b, &f[0], obj->body.type, id); SPA_POD_OBJECT_FOREACH(obj, prop) { switch (prop->key) { case SPA_FORMAT_mediaType: spa_pod_builder_prop(b, prop->key, prop->flags); spa_pod_builder_id(b, SPA_MEDIA_TYPE_video); break; case SPA_FORMAT_mediaSubtype: spa_pod_builder_prop(b, prop->key, prop->flags); spa_pod_builder_id(b, SPA_MEDIA_SUBTYPE_raw); fmt = SPA_VIDEO_FORMAT_I420; switch (media_subtype) { case SPA_MEDIA_SUBTYPE_raw: break; case SPA_MEDIA_SUBTYPE_mjpg: add_video_formats(b, &fmt, 1, port->is_dsp); break; case SPA_MEDIA_SUBTYPE_h264: add_video_formats(b, &fmt, 1, port->is_dsp); break; default: return NULL; } break; case SPA_FORMAT_VIDEO_format: { uint32_t n_vals, choice, *id_vals; struct spa_pod *val = spa_pod_get_values(&prop->value, &n_vals, &choice); if (n_vals < 1) return 0; if (!spa_pod_is_id(val)) return 0; id_vals = SPA_POD_BODY(val); add_video_formats(b, id_vals, n_vals, port->is_dsp); break; } default: spa_pod_builder_raw_padded(b, prop, SPA_POD_PROP_SIZE(prop)); break; } } return spa_pod_builder_pop(b, &f[0]); } static int diff_value(struct impl *impl, uint32_t type, uint32_t size, const void *v1, const void *v2) { switch (type) { case SPA_TYPE_None: return INT_MAX; case SPA_TYPE_Bool: if (size < sizeof(int32_t)) return INT_MAX; return (!!*(int32_t *)v1) - (!!*(int32_t *)v2); case SPA_TYPE_Id: if (size < sizeof(uint32_t)) return INT_MAX; return (*(uint32_t *)v1) != (*(uint32_t *)v2); case SPA_TYPE_Int: if (size < sizeof(int32_t)) return INT_MAX; return *(int32_t *)v1 - *(int32_t *)v2; case SPA_TYPE_Long: if (size < sizeof(int64_t)) return INT_MAX; return *(int64_t *)v1 - *(int64_t *)v2; case SPA_TYPE_Float: if (size < sizeof(float)) return INT_MAX; return (int)(*(float *)v1 - *(float *)v2); case SPA_TYPE_Double: if (size < sizeof(double)) return INT_MAX; return (int)(*(double *)v1 - *(double *)v2); case SPA_TYPE_String: if (size < 1 || ((const char *)v1)[size - 1] != 0 || ((const char *)v2)[size - 1] != 0) return INT_MAX; return strcmp((char *)v1, (char *)v2); case SPA_TYPE_Bytes: return memcmp((char *)v1, (char *)v2, size); case SPA_TYPE_Rectangle: { const struct spa_rectangle *rec1, *rec2; uint64_t n1, n2; if (size < sizeof(*rec1)) return INT_MAX; rec1 = (struct spa_rectangle *) v1; rec2 = (struct spa_rectangle *) v2; n1 = ((uint64_t) rec1->width) * rec1->height; n2 = ((uint64_t) rec2->width) * rec2->height; if (rec1->width == rec2->width && rec1->height == rec2->height) return 0; else if (n1 < n2) return -(n2 - n1); else if (n1 > n2) return n1 - n2; else if (rec1->width == rec2->width) return (int32_t)rec1->height - (int32_t)rec2->height; else return (int32_t)rec1->width - (int32_t)rec2->width; } case SPA_TYPE_Fraction: { const struct spa_fraction *f1, *f2; uint64_t n1, n2; if (size < sizeof(*f1)) return INT_MAX; f1 = (struct spa_fraction *) v1; f2 = (struct spa_fraction *) v2; n1 = ((uint64_t) f1->num) * f2->denom; n2 = ((uint64_t) f2->num) * f1->denom; return (int) (n1 - n2); } default: break; } return 0; } static int diff_prop(struct impl *impl, struct spa_pod_prop *prop, uint32_t type, const void *target, bool fix) { uint32_t i, n_vals, choice, size; struct spa_pod *val = spa_pod_get_values(&prop->value, &n_vals, &choice); void *vals, *v, *best = NULL; int res = INT_MAX; if (n_vals < 1 || val->type != type) return -EINVAL; size = SPA_POD_BODY_SIZE(val); vals = SPA_POD_BODY(val); switch (choice) { case SPA_CHOICE_None: case SPA_CHOICE_Enum: for (i = 0, v = vals; i < n_vals; i++, v = SPA_PTROFF(v, size, void)) { int diff = SPA_ABS(diff_value(impl, type, size, v, target)); if (diff < res) { res = diff; best = v; } } if (fix) { if (best != NULL && best != vals) memcpy(vals, best, size); if (spa_pod_is_choice(&prop->value)) SPA_POD_CHOICE_TYPE(&prop->value) = SPA_CHOICE_None; } break; default: return res; } return res; } static int calc_diff(struct impl *impl, struct spa_pod *param, struct dir *dir, bool fix) { struct spa_pod_object *obj = (struct spa_pod_object*)param; struct spa_pod_prop *prop; struct spa_rectangle size; struct spa_fraction framerate; uint32_t format; int diff = 0; if (!dir->have_format) return -1; get_format(dir, &format, &size, &framerate); SPA_POD_OBJECT_FOREACH(obj, prop) { switch (prop->key) { case SPA_FORMAT_VIDEO_format: diff += diff_prop(impl, prop, SPA_TYPE_Id, &format, fix); break; case SPA_FORMAT_VIDEO_size: diff += diff_prop(impl, prop, SPA_TYPE_Rectangle, &size, fix); break; case SPA_FORMAT_VIDEO_framerate: diff += diff_prop(impl, prop, SPA_TYPE_Fraction, &framerate, fix); break; default: break; } } return diff; } static int all_formats(struct impl *this, struct port *port, uint32_t id, uint32_t index, const struct spa_pod **param, struct spa_pod_builder *b) { struct spa_pod_frame f[1]; switch (index) { case 0: spa_pod_builder_push_object(b, &f[0], SPA_TYPE_OBJECT_Format, id); spa_pod_builder_add(b, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), 0); add_video_formats(b, NULL, 0, port->is_dsp); *param = spa_pod_builder_pop(b, &f[0]); break; case 1: if (this->direction != port->direction) return 0; /* JPEG */ spa_pod_builder_push_object(b, &f[0], SPA_TYPE_OBJECT_Format, id); spa_pod_builder_add(b, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_mjpg), 0); *param = spa_pod_builder_pop(b, &f[0]); break; case 2: if (this->direction != port->direction) return 0; /* H264 */ spa_pod_builder_push_object(b, &f[0], SPA_TYPE_OBJECT_Format, id); spa_pod_builder_add(b, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_h264), 0); *param = spa_pod_builder_pop(b, &f[0]); break; default: return 0; } return 1; } static int port_param_enum_format(struct impl *this, struct port *port, uint32_t id, uint32_t index, const struct spa_pod **param, struct spa_pod_builder *b) { struct dir *other = &this->dir[SPA_DIRECTION_REVERSE(port->direction)]; spa_log_debug(this->log, "%p %d %d %d %d", port, port->valid, this->direction, port->direction, index); if ((port->is_dsp || port->is_control) && index > 0) return 0; if (index == 0) { if (port->is_dsp) { struct spa_video_info_dsp info = SPA_VIDEO_INFO_DSP_INIT( .format = SPA_VIDEO_FORMAT_DSP_F32); *param = spa_format_video_dsp_build(b, id, &info); return 1; } else if (port->is_control) { *param = spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_Format, id, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_application), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_control)); return 1; } else if (other->have_format) { /* peer format */ *param = spa_format_video_build(b, id, &other->format); } return 1; } else if (this->direction != port->direction) { struct port *oport = GET_PORT(this, SPA_DIRECTION_REVERSE(port->direction), port->id); if (oport != NULL && oport->valid && index - 1 < oport->n_peer_formats) { const struct spa_pod *p = oport->peer_formats[index-1]; *param = transform_format(this, port, p, id, b); } else { return all_formats(this, port, id, index - 1 - (oport ? oport->n_peer_formats : 0), param, b); } } else if (index == 1) { const struct spa_pod *best = NULL; int best_diff = INT_MAX; uint32_t i; for (i = 0; i < port->n_peer_formats; i++) { const struct spa_pod *p = port->peer_formats[i]; int diff = calc_diff(this, (struct spa_pod*)p, other, false); if (diff < 0) break; if (diff < best_diff) { best_diff = diff; best = p; } } if (best) { uint32_t offset = b->state.offset; struct spa_pod *p; spa_pod_builder_primitive(b, best); p = spa_pod_builder_deref(b, offset); calc_diff(this, p, other, true); *param = p; } } else { return all_formats(this, port, id, index - 2, param, b); } return 1; } static int port_param_format(struct impl *this, struct port *port, uint32_t id, uint32_t index, const struct spa_pod **param, struct spa_pod_builder *b) { if (!port->have_format) return -EIO; if (index != 0) return 0; if (port->is_dsp) { *param = spa_format_video_dsp_build(b, id, &port->format.info.dsp); } else if (port->is_control) { *param = spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_Format, id, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_application), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_control)); } else { *param = spa_format_video_build(b, id, &port->format); } return 1; } static int port_param_buffers(struct impl *this, struct port *port, uint32_t id, uint32_t index, const struct spa_pod **param, struct spa_pod_builder *b) { uint32_t size, min, max, def; struct port *other; if (!port->have_format) return -EIO; if (index != 0) return 0; if (port->is_dsp) { size = 1024 * 1024 * 16; } else { size = port->size; } other = GET_PORT(this, SPA_DIRECTION_REVERSE(port->direction), port->id); if (other->n_buffers > 0) { min = other->n_buffers; } else { min = 2; } max = MAX_BUFFERS; def = SPA_CLAMP(8u, min, MAX_BUFFERS); *param = spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_ParamBuffers, id, SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(def, min, max), SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(port->blocks), SPA_PARAM_BUFFERS_size, SPA_POD_CHOICE_RANGE_Int( size, 16, INT32_MAX), SPA_PARAM_BUFFERS_stride, SPA_POD_CHOICE_RANGE_Int( port->stride, 0, INT32_MAX)); return 1; } static int port_param_meta(struct impl *this, struct port *port, uint32_t id, uint32_t index, const struct spa_pod **param, struct spa_pod_builder *b) { switch (index) { case 0: *param = spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_ParamMeta, id, SPA_PARAM_META_type, SPA_POD_Id(SPA_META_Header), SPA_PARAM_META_size, SPA_POD_Int(sizeof(struct spa_meta_header))); return 1; } return 0; } static int port_param_io(struct impl *this, struct port *port, uint32_t id, uint32_t index, const struct spa_pod **param, struct spa_pod_builder *b) { switch (index) { case 0: *param = spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_ParamIO, id, SPA_PARAM_IO_id, SPA_POD_Id(SPA_IO_Buffers), SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_buffers))); return 1; } return 0; } static int port_param_latency(struct impl *this, struct port *port, uint32_t id, uint32_t index, const struct spa_pod **param, struct spa_pod_builder *b) { switch (index) { case 0 ... 1: *param = spa_latency_build(b, id, &port->latency[index]); return 1; } return 0; } static int port_param_tag(struct impl *this, struct port *port, uint32_t id, uint32_t index, const struct spa_pod **param, struct spa_pod_builder *b) { switch (index) { case 0 ... 1: if (port->is_monitor) index = index ^ 1; *param = this->dir[index].tag; return 1; } return 0; } static int port_param_peer_enum_format(struct impl *this, struct port *port, uint32_t index, const struct spa_pod **param, struct spa_pod_builder *b) { if (index >= port->n_peer_formats) return 0; *param = port->peer_formats[port->n_peer_formats - index]; return 1; } static int impl_node_port_enum_params(void *object, int seq, enum spa_direction direction, uint32_t port_id, uint32_t id, uint32_t start, uint32_t num, const struct spa_pod *filter) { struct impl *this = object; struct port *port; const struct spa_pod *param; struct spa_pod_builder b = { 0 }; uint8_t buffer[4096]; struct spa_result_node_params result; uint32_t count = 0; int res; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(num != 0, -EINVAL); spa_log_debug(this->log, "%p: enum params port %d.%d %d %u", this, direction, port_id, seq, id); spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); port = GET_PORT(this, direction, port_id); result.id = id; result.next = start; next: result.index = result.next++; spa_pod_builder_init(&b, buffer, sizeof(buffer)); param = NULL; switch (id) { case SPA_PARAM_EnumFormat: res = port_param_enum_format(this, port, id, result.index, ¶m, &b); break; case SPA_PARAM_Format: res = port_param_format(this, port, id, result.index, ¶m, &b); break; case SPA_PARAM_Buffers: res = port_param_buffers(this, port, id, result.index, ¶m, &b); break; case SPA_PARAM_Meta: res = port_param_meta(this, port, id, result.index, ¶m, &b); break; case SPA_PARAM_IO: res = port_param_io(this, port, id, result.index, ¶m, &b); break; case SPA_PARAM_Latency: res = port_param_latency(this, port, id, result.index, ¶m, &b); break; case SPA_PARAM_Tag: res = port_param_tag(this, port, id, result.index, ¶m, &b); break; case SPA_PARAM_PeerEnumFormat: res = port_param_peer_enum_format(this, port, result.index, ¶m, &b); break; default: return -ENOENT; } if (res <= 0) return res; if (param == NULL || spa_pod_filter(&b, &result.param, param, filter) < 0) goto next; spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); if (++count != num) goto next; return 0; } static int clear_buffers(struct impl *this, struct port *port) { uint32_t i, j; spa_log_debug(this->log, "%p: clear buffers %p %d", this, port, port->n_buffers); for (i = 0; i < port->n_buffers; i++) { struct buffer *b = &port->buffers[i]; if (SPA_FLAG_IS_SET(b->flags, BUFFER_FLAG_MAPPED)) { for (j = 0; j < b->buf->n_datas; j++) { if (b->datas[j]) { spa_log_debug(this->log, "%p: unmap buffer %d data %d %p", this, i, j, b->datas[j]); munmap(b->datas[j], b->buf->datas[j].maxsize); b->datas[j] = NULL; } } SPA_FLAG_CLEAR(b->flags, BUFFER_FLAG_MAPPED); } } port->n_buffers = 0; spa_list_init(&port->queue); return 0; } static int port_set_latency(void *object, enum spa_direction direction, uint32_t port_id, uint32_t flags, const struct spa_pod *latency) { struct impl *this = object; struct port *port, *oport; enum spa_direction other = SPA_DIRECTION_REVERSE(direction); struct spa_latency_info info; bool have_latency, emit = false;; uint32_t i; spa_log_debug(this->log, "%p: set latency direction:%d id:%d %p", this, direction, port_id, latency); port = GET_PORT(this, direction, port_id); if (latency == NULL) { info = SPA_LATENCY_INFO(other); have_latency = false; } else { if (spa_latency_parse(latency, &info) < 0 || info.direction != other) return -EINVAL; have_latency = true; } emit = spa_latency_info_compare(&info, &port->latency[other]) != 0 || port->have_latency == have_latency; port->latency[other] = info; port->have_latency = have_latency; spa_log_debug(this->log, "%p: set %s latency %f-%f %d-%d %"PRIu64"-%"PRIu64, this, info.direction == SPA_DIRECTION_INPUT ? "input" : "output", info.min_quantum, info.max_quantum, info.min_rate, info.max_rate, info.min_ns, info.max_ns); if (this->monitor_passthrough) { if (port->is_monitor) oport = GET_PORT(this, other, port_id-1); else if (this->monitor && direction == SPA_DIRECTION_INPUT) oport = GET_PORT(this, other, port_id+1); else return 0; if (oport != NULL && spa_latency_info_compare(&info, &oport->latency[other]) != 0) { oport->latency[other] = info; oport->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; oport->params[IDX_Latency].user++; } } else { spa_latency_info_combine_start(&info, other); for (i = 0; i < this->dir[direction].n_ports; i++) { oport = GET_PORT(this, direction, i); if ((oport->is_monitor) || !oport->have_latency) continue; spa_log_debug(this->log, "%p: combine %d", this, i); spa_latency_info_combine(&info, &oport->latency[other]); } spa_latency_info_combine_finish(&info); spa_log_debug(this->log, "%p: combined %s latency %f-%f %d-%d %"PRIu64"-%"PRIu64, this, info.direction == SPA_DIRECTION_INPUT ? "input" : "output", info.min_quantum, info.max_quantum, info.min_rate, info.max_rate, info.min_ns, info.max_ns); for (i = 0; i < this->dir[other].n_ports; i++) { oport = GET_PORT(this, other, i); if (oport->is_monitor) continue; spa_log_debug(this->log, "%p: change %d", this, i); if (spa_latency_info_compare(&info, &oport->latency[other]) != 0) { oport->latency[other] = info; oport->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; oport->params[IDX_Latency].user++; } } } if (emit) { port->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; port->params[IDX_Latency].user++; } return 0; } static int port_set_tag(void *object, enum spa_direction direction, uint32_t port_id, uint32_t flags, const struct spa_pod *tag) { struct impl *this = object; struct port *port, *oport; enum spa_direction other = SPA_DIRECTION_REVERSE(direction); uint32_t i; spa_log_debug(this->log, "%p: set tag direction:%d id:%d %p", this, direction, port_id, tag); port = GET_PORT(this, direction, port_id); if (port->is_monitor && !this->monitor_passthrough) return 0; if (tag != NULL) { struct spa_tag_info info; void *state = NULL; if (spa_tag_parse(tag, &info, &state) < 0 || info.direction != other) return -EINVAL; } if (spa_tag_compare(tag, this->dir[other].tag) != 0) { free(this->dir[other].tag); this->dir[other].tag = tag ? spa_pod_copy(tag) : NULL; for (i = 0; i < this->dir[other].n_ports; i++) { oport = GET_PORT(this, other, i); oport->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; oport->params[IDX_Tag].user++; } } port->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; port->params[IDX_Tag].user++; return 0; } static int port_set_format(void *object, enum spa_direction direction, uint32_t port_id, uint32_t flags, const struct spa_pod *format) { struct impl *this = object; struct port *port; int res; port = GET_PORT(this, direction, port_id); spa_log_debug(this->log, "%p: %d:%d set format", this, direction, port_id); if (format == NULL) { port->have_format = false; this->setup = false; this->fmt_passthrough = false; clear_buffers(this, port); } else { struct dir *dir = &this->dir[direction]; struct dir *odir = &this->dir[SPA_DIRECTION_REVERSE(direction)]; struct spa_video_info info = { 0 }; if ((res = spa_format_parse(format, &info.media_type, &info.media_subtype)) < 0) { spa_log_error(this->log, "can't parse format %s", spa_strerror(res)); return res; } if (PORT_IS_DSP(this, direction, port_id)) { if (info.media_type != SPA_MEDIA_TYPE_video || info.media_subtype != SPA_MEDIA_SUBTYPE_dsp) { spa_log_error(this->log, "unexpected types %d/%d", info.media_type, info.media_subtype); return -EINVAL; } if ((res = spa_format_video_dsp_parse(format, &info.info.dsp)) < 0) { spa_log_error(this->log, "can't parse format %s", spa_strerror(res)); return res; } if (info.info.dsp.format != SPA_VIDEO_FORMAT_DSP_F32) { spa_log_error(this->log, "unexpected format %d<->%d", info.info.dsp.format, SPA_VIDEO_FORMAT_DSP_F32); return -EINVAL; } port->blocks = 1; port->stride = 16; dir->have_format = true; } else if (PORT_IS_CONTROL(this, direction, port_id)) { if (info.media_type != SPA_MEDIA_TYPE_application || info.media_subtype != SPA_MEDIA_SUBTYPE_control) { spa_log_error(this->log, "unexpected types %d/%d", info.media_type, info.media_subtype); return -EINVAL; } port->blocks = 1; port->stride = 1; dir->have_format = true; } else { enum AVPixelFormat pix_fmt; if (info.media_type != SPA_MEDIA_TYPE_video) { spa_log_error(this->log, "unexpected types %d/%d", info.media_type, info.media_subtype); return -EINVAL; } if ((res = spa_format_video_parse(format, &info)) < 0) { spa_log_error(this->log, "can't parse format %s", spa_strerror(res)); return res; } switch (info.media_subtype) { case SPA_MEDIA_SUBTYPE_raw: { int linesizes[4]; pix_fmt = format_to_pix_fmt(info.info.raw.format); if (pix_fmt == AV_PIX_FMT_NONE) return -EINVAL; av_image_fill_linesizes(linesizes, pix_fmt, info.info.raw.size.width); port->stride = linesizes[0]; port->blocks = 0; for (int i = 0; i < 4; i++) { dir->linesizes[i] = linesizes[i]; if (linesizes[i]) port->blocks++; } av_image_fill_plane_sizes(dir->plane_size, pix_fmt, info.info.raw.size.height, dir->linesizes); port->size = av_image_get_buffer_size(pix_fmt, info.info.raw.size.width, info.info.raw.size.height, this->max_align); break; } case SPA_MEDIA_SUBTYPE_h264: port->stride = 0; port->size = info.info.h264.size.width * info.info.h264.size.height; port->blocks = 1; break; case SPA_MEDIA_SUBTYPE_mjpg: port->stride = 0; port->size = info.info.mjpg.size.width * info.info.mjpg.size.height; port->blocks = 1; break; default: spa_log_error(this->log, "unsupported subtype %d", info.media_subtype); return -ENOTSUP; } dir->format = info; dir->have_format = true; if (odir->have_format) { this->fmt_passthrough = (memcmp(&odir->format, &dir->format, sizeof(dir->format)) == 0); } } port->format = info; port->have_format = true; this->setup = false; spa_log_debug(this->log, "%p: %d %d %d", this, port_id, port->stride, port->blocks); if (dir->have_format && odir->have_format) if ((res = setup_convert(this)) < 0) return res; } port->info.change_mask |= SPA_PORT_CHANGE_MASK_FLAGS; SPA_FLAG_UPDATE(port->info.flags, SPA_PORT_FLAG_CAN_ALLOC_BUFFERS, this->fmt_passthrough); port->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; if (port->have_format) { port->params[IDX_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_READWRITE); port->params[IDX_Buffers] = SPA_PARAM_INFO(SPA_PARAM_Buffers, SPA_PARAM_INFO_READ); } else { port->params[IDX_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); port->params[IDX_Buffers] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); } return 0; } static int port_set_peer_enum_format(void *object, enum spa_direction direction, uint32_t port_id, uint32_t flags, const struct spa_pod *formats) { struct impl *this = object; struct port *port, *oport; int res = 0; uint32_t i; enum spa_direction other = SPA_DIRECTION_REVERSE(direction); static uint32_t subtypes[] = { SPA_MEDIA_SUBTYPE_raw, SPA_MEDIA_SUBTYPE_mjpg, SPA_MEDIA_SUBTYPE_h264 }; struct spa_peer_param_info info; void *state = NULL; spa_return_val_if_fail(spa_pod_is_object(formats), -EINVAL); port = GET_PORT(this, direction, port_id); oport = GET_PORT(this, other, port_id); free(port->peer_formats); port->peer_formats = NULL; free(port->peer_format_pod); port->peer_format_pod = NULL; port->n_peer_formats = 0; if (formats) { uint32_t count = 0; port->peer_format_pod = spa_pod_copy(formats); for (i = 0; i < SPA_N_ELEMENTS(subtypes); i++) { state = NULL; while (spa_peer_param_parse(formats, &info, sizeof(info), &state) > 0) { uint32_t media_type, media_subtype; if (!spa_format_parse(info.param, &media_type, &media_subtype) || media_type != SPA_MEDIA_TYPE_video || media_subtype != subtypes[i]) continue; count++; } } port->peer_formats = calloc(count, sizeof(struct spa_pod *)); for (i = 0; i < SPA_N_ELEMENTS(subtypes); i++) { state = NULL; while (spa_peer_param_parse(port->peer_format_pod, &info, sizeof(info), &state) > 0) { uint32_t media_type, media_subtype; if (!spa_format_parse(info.param, &media_type, &media_subtype) || media_type != SPA_MEDIA_TYPE_video || media_subtype != subtypes[i]) continue; port->peer_formats[port->n_peer_formats++] = info.param; } } } oport->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; oport->params[IDX_EnumFormat].user++; port->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; port->params[IDX_EnumFormat].user++; port->params[IDX_PeerEnumFormat].user++; return res; } static int impl_node_port_set_param(void *object, enum spa_direction direction, uint32_t port_id, uint32_t id, uint32_t flags, const struct spa_pod *param) { struct impl *this = object; int res = 0; spa_return_val_if_fail(this != NULL, -EINVAL); spa_log_debug(this->log, "%p: set param port %d.%d %u", this, direction, port_id, id); spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); switch (id) { case SPA_PARAM_Latency: res = port_set_latency(this, direction, port_id, flags, param); break; case SPA_PARAM_Tag: res = port_set_tag(this, direction, port_id, flags, param); break; case SPA_PARAM_Format: res = port_set_format(this, direction, port_id, flags, param); break; case SPA_PARAM_PeerEnumFormat: res = port_set_peer_enum_format(this, direction, port_id, flags, param); break; break; default: return -ENOENT; } emit_info(this, false); return res; } static inline void queue_buffer(struct impl *this, struct port *port, uint32_t id) { struct buffer *b = &port->buffers[id]; spa_log_trace_fp(this->log, "%p: queue buffer %d on port %d %d", this, id, port->id, b->flags); if (SPA_FLAG_IS_SET(b->flags, BUFFER_FLAG_QUEUED)) return; spa_list_append(&port->queue, &b->link); SPA_FLAG_SET(b->flags, BUFFER_FLAG_QUEUED); } static inline struct buffer *peek_buffer(struct impl *this, struct port *port) { struct buffer *b; if (spa_list_is_empty(&port->queue)) return NULL; b = spa_list_first(&port->queue, struct buffer, link); spa_log_trace_fp(this->log, "%p: peek buffer %d/%d on port %d %u", this, b->id, port->n_buffers, port->id, b->flags); return b; } static inline void dequeue_buffer(struct impl *this, struct port *port, struct buffer *b) { spa_log_trace_fp(this->log, "%p: dequeue buffer %d on port %d %u", this, b->id, port->id, b->flags); if (!SPA_FLAG_IS_SET(b->flags, BUFFER_FLAG_QUEUED)) return; spa_list_remove(&b->link); SPA_FLAG_CLEAR(b->flags, BUFFER_FLAG_QUEUED); } static int impl_node_port_use_buffers(void *object, enum spa_direction direction, uint32_t port_id, uint32_t flags, struct spa_buffer **buffers, uint32_t n_buffers) { struct impl *this = object; struct port *port; uint32_t i, j, maxsize; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); port = GET_PORT(this, direction, port_id); spa_log_debug(this->log, "%p: use buffers %d on port %d:%d flags %08x", this, n_buffers, direction, port_id, flags); clear_buffers(this, port); if (n_buffers > 0 && !port->have_format) return -EIO; if (n_buffers > MAX_BUFFERS) return -ENOSPC; maxsize = this->quantum_limit * sizeof(float); for (i = 0; i < n_buffers; i++) { struct buffer *b; uint32_t n_datas = buffers[i]->n_datas; struct spa_data *d = buffers[i]->datas; b = &port->buffers[i]; b->id = i; b->flags = 0; b->buf = buffers[i]; b->h = spa_buffer_find_meta_data(b->buf, SPA_META_Header, sizeof(struct spa_meta_header)); if (n_datas != port->blocks) { spa_log_error(this->log, "%p: invalid blocks %d on buffer %d, expected %d", this, n_datas, i, port->blocks); return -EINVAL; } if (SPA_FLAG_IS_SET(flags, SPA_NODE_BUFFERS_FLAG_ALLOC)) { struct port *other = GET_PORT(this, SPA_DIRECTION_REVERSE(direction), port_id); if (other->n_buffers <= 0) return -EIO; for (j = 0; j < n_datas; j++) { b->buf->datas[j] = other->buffers[i % other->n_buffers].buf->datas[j]; b->datas[j] = other->buffers[i % other->n_buffers].datas[j]; maxsize = SPA_MAX(maxsize, d[j].maxsize); spa_log_debug(this->log, "buffer %d: mem:%d passthrough:%p maxsize:%d", i, j, b->datas[j], d[j].maxsize); } } else { for (j = 0; j < n_datas; j++) { void *data = d[j].data; if (data == NULL && SPA_FLAG_IS_SET(d[j].flags, SPA_DATA_FLAG_MAPPABLE)) { data = mmap(NULL, d[j].maxsize, PROT_READ, MAP_SHARED, d[j].fd, d[j].mapoffset); if (data == MAP_FAILED) { spa_log_error(this->log, "%p: mmap failed %d on buffer %d %d %p: %m", this, j, i, d[j].type, data); return -EINVAL; } SPA_FLAG_SET(b->flags, BUFFER_FLAG_MAPPED); spa_log_debug(this->log, "%p: mmap %d on buffer %d %d %p %p", this, j, i, d[j].type, data, b); } if (data != NULL && !SPA_IS_ALIGNED(data, this->max_align)) { spa_log_warn(this->log, "%p: memory %d on buffer %d not aligned", this, j, i); } b->datas[j] = data; spa_log_debug(this->log, "buffer %d: mem:%d data:%p maxsize:%d", i, j, data, d[j].maxsize); maxsize = SPA_MAX(maxsize, d[j].maxsize); } } if (direction == SPA_DIRECTION_OUTPUT) queue_buffer(this, port, i); } port->maxsize = maxsize; port->n_buffers = n_buffers; return 0; } struct io_data { struct port *port; void *data; size_t size; }; static int do_set_port_io(struct spa_loop *loop, bool async, uint32_t seq, const void *data, size_t size, void *user_data) { const struct io_data *d = user_data; d->port->io = d->data; return 0; } static int impl_node_port_set_io(void *object, enum spa_direction direction, uint32_t port_id, uint32_t id, void *data, size_t size) { struct impl *this = object; struct port *port; spa_return_val_if_fail(this != NULL, -EINVAL); spa_log_debug(this->log, "%p: set io %d on port %d:%d %p", this, id, direction, port_id, data); spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); port = GET_PORT(this, direction, port_id); switch (id) { case SPA_IO_Buffers: if (this->data_loop) { struct io_data d = { .port = port, .data = data, .size = size }; spa_loop_locked(this->data_loop, do_set_port_io, 0, NULL, 0, &d); } else port->io = data; break; case SPA_IO_RateMatch: this->io_rate_match = data; break; default: return -ENOENT; } return 0; } static int impl_node_port_reuse_buffer(void *object, uint32_t port_id, uint32_t buffer_id) { struct impl *this = object; struct port *port; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(CHECK_PORT(this, SPA_DIRECTION_OUTPUT, port_id), -EINVAL); port = GET_OUT_PORT(this, port_id); queue_buffer(this, port, buffer_id); return 0; } static int impl_node_process(void *object) { struct impl *this = object; struct port *in_port, *out_port; struct spa_io_buffers *input, *output; struct buffer *dbuf, *sbuf; struct dir *in, *out; struct AVFrame *f; void *datas[8]; uint32_t sizes[8], strides[8]; int res; spa_return_val_if_fail(this != NULL, -EINVAL); in = &this->dir[SPA_DIRECTION_INPUT]; out = &this->dir[SPA_DIRECTION_OUTPUT]; out_port = GET_OUT_PORT(this, 0); if ((output = out_port->io) == NULL) return -EIO; if (output->status == SPA_STATUS_HAVE_DATA) return SPA_STATUS_HAVE_DATA; /* recycle */ if (output->buffer_id < out_port->n_buffers) { queue_buffer(this, out_port, output->buffer_id); output->buffer_id = SPA_ID_INVALID; } in_port = GET_IN_PORT(this, 0); if ((input = in_port->io) == NULL) return -EIO; if (input->status != SPA_STATUS_HAVE_DATA) return SPA_STATUS_NEED_DATA; if (input->buffer_id >= in_port->n_buffers) { input->status = -EINVAL; return -EINVAL; } sbuf = &in_port->buffers[input->buffer_id]; input->status = SPA_STATUS_NEED_DATA; if (this->fmt_passthrough) { dbuf = &out_port->buffers[input->buffer_id]; } else if ((dbuf = peek_buffer(this, out_port)) == NULL) { spa_log_error(this->log, "%p: out of buffers", this); return -EPIPE; } spa_log_trace(this->log, "%d %p:%p %d %d %d", input->buffer_id, sbuf->buf->datas[0].chunk, dbuf->buf->datas[0].chunk, sbuf->buf->datas[0].chunk->size, sbuf->id, dbuf->id); /* do decoding */ if (this->decoder.context) { this->decoder.packet->data = sbuf->datas[0]; this->decoder.packet->size = sbuf->buf->datas[0].chunk->size; spa_log_trace(this->log, "decode %p:%d", this->decoder.packet->data, this->decoder.packet->size); if ((res = avcodec_send_packet(this->decoder.context, this->decoder.packet)) < 0) { spa_log_error(this->log, "failed to send frame to codec: %d %p:%d", res, this->decoder.packet->data, this->decoder.packet->size); return -EIO; } f = this->decoder.frame; if (avcodec_receive_frame(this->decoder.context, f) < 0) { spa_log_error(this->log, "failed to receive frame from codec"); return -EIO; } in->pix_fmt = f->format; in->size.width = f->width; in->size.height = f->height; for (uint32_t i = 0; i < 4; ++i) { datas[i] = f->data[i]; strides[i] = f->linesize[i]; sizes[i] = out->plane_size[i]; } } else { f = this->decoder.frame; f->format = in->pix_fmt; f->width = in->size.width; f->height = in->size.height; for (uint32_t i = 0; i < sbuf->buf->n_datas; ++i) { datas[i] = f->data[i] = sbuf->datas[i]; strides[i] = f->linesize[i] = sbuf->buf->datas[i].chunk->stride; sizes[i] = sbuf->buf->datas[i].chunk->size; } } /* do conversion */ if (f->format != out->pix_fmt || f->width != (int)out->size.width || f->height != (int)out->size.height) { if (this->convert.context == NULL) { const AVPixFmtDescriptor *in_fmt = av_pix_fmt_desc_get(f->format); const AVPixFmtDescriptor *out_fmt = av_pix_fmt_desc_get(out->pix_fmt); this->convert.context = sws_getContext( f->width, f->height, f->format, out->size.width, out->size.height, out->pix_fmt, 0, NULL, NULL, NULL); spa_log_info(this->log, "%p: using convert %dx%d:%s -> %dx%d:%s", this, f->width, f->height, in_fmt->name, out->size.width, out->size.height, out_fmt->name); } spa_log_trace(this->log, "convert"); sws_scale_frame(this->convert.context, this->convert.frame, f); f = this->convert.frame; for (uint32_t i = 0; i < 4; ++i) { datas[i] = f->data[i]; strides[i] = f->linesize[i]; sizes[i] = out->plane_size[i]; } } /* do encoding */ if (this->encoder.context) { if ((res = avcodec_send_frame(this->encoder.context, f)) < 0) { spa_log_error(this->log, "failed to send frame to codec: %d", res); return -EIO; } if (avcodec_receive_packet(this->encoder.context, this->encoder.packet) < 0) { spa_log_error(this->log, "failed to receive frame from codec"); return -EIO; } datas[0] = this->encoder.packet->data; sizes[0] = this->encoder.packet->size; strides[0] = 1; spa_log_trace(this->log, "encode %p %d", datas[0], sizes[0]); } /* write to output */ for (uint32_t i = 0; i < dbuf->buf->n_datas; ++i) { if (SPA_FLAG_IS_SET(dbuf->buf->datas[i].flags, SPA_DATA_FLAG_DYNAMIC)) dbuf->buf->datas[i].data = datas[i]; else if (datas[i] && dbuf->datas[i] && dbuf->datas[i] != datas[i]) memcpy(dbuf->datas[i], datas[i], sizes[i]); if (dbuf->buf->datas[i].chunk != sbuf->buf->datas[i].chunk) { dbuf->buf->datas[i].chunk->stride = strides[i]; dbuf->buf->datas[i].chunk->size = sizes[i]; } spa_log_trace(this->log, "out %u %u %p %d", dbuf->id, i, dbuf->buf->datas[i].data, dbuf->buf->datas[i].chunk->size); } dequeue_buffer(this, out_port, dbuf); if (sbuf->h && dbuf->h) *dbuf->h = *sbuf->h; output->buffer_id = dbuf->id; output->status = SPA_STATUS_HAVE_DATA; return SPA_STATUS_HAVE_DATA; } static const struct spa_node_methods impl_node = { SPA_VERSION_NODE_METHODS, .add_listener = impl_node_add_listener, .set_callbacks = impl_node_set_callbacks, .enum_params = impl_node_enum_params, .set_param = impl_node_set_param, .set_io = impl_node_set_io, .send_command = impl_node_send_command, .add_port = impl_node_add_port, .remove_port = impl_node_remove_port, .port_enum_params = impl_node_port_enum_params, .port_set_param = impl_node_port_set_param, .port_use_buffers = impl_node_port_use_buffers, .port_set_io = impl_node_port_set_io, .port_reuse_buffer = impl_node_port_reuse_buffer, .process = impl_node_process, }; static int impl_get_interface(struct spa_handle *handle, const char *type, void **interface) { struct impl *this; spa_return_val_if_fail(handle != NULL, -EINVAL); spa_return_val_if_fail(interface != NULL, -EINVAL); this = (struct impl *) handle; if (spa_streq(type, SPA_TYPE_INTERFACE_Node)) *interface = &this->node; else return -ENOENT; return 0; } static void free_dir(struct dir *dir) { uint32_t i; for (i = 0; i < MAX_PORTS; i++) free(dir->ports[i]); free(dir->tag); } static int impl_clear(struct spa_handle *handle) { struct impl *this; spa_return_val_if_fail(handle != NULL, -EINVAL); this = (struct impl *) handle; free_decoder(this); free_encoder(this); av_frame_free(&this->decoder.frame); av_frame_free(&this->convert.frame); free_dir(&this->dir[SPA_DIRECTION_INPUT]); free_dir(&this->dir[SPA_DIRECTION_OUTPUT]); return 0; } static size_t impl_get_size(const struct spa_handle_factory *factory, const struct spa_dict *params) { return sizeof(struct impl); } static int impl_init(const struct spa_handle_factory *factory, struct spa_handle *handle, const struct spa_dict *info, const struct spa_support *support, uint32_t n_support) { struct impl *this; uint32_t i; spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(handle != NULL, -EINVAL); handle->get_interface = impl_get_interface; handle->clear = impl_clear; this = (struct impl *) handle; this->data_loop = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_DataLoop); this->log = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log); spa_log_topic_init(this->log, &log_topic); this->cpu = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_CPU); if (this->cpu) { this->cpu_flags = spa_cpu_get_flags(this->cpu); this->max_align = SPA_MIN(MAX_ALIGN, spa_cpu_get_max_align(this->cpu)); } props_reset(&this->props); this->rate_limit.interval = 2 * SPA_NSEC_PER_SEC; this->rate_limit.burst = 1; for (i = 0; info && i < info->n_items; i++) { const char *k = info->items[i].key; const char *s = info->items[i].value; if (spa_streq(k, "clock.quantum-limit")) spa_atou32(s, &this->quantum_limit, 0); else if (spa_streq(k, SPA_KEY_PORT_IGNORE_LATENCY)) this->port_ignore_latency = spa_atob(s); else if (spa_streq(k, SPA_KEY_PORT_GROUP)) spa_scnprintf(this->group_name, sizeof(this->group_name), "%s", s); else if (spa_streq(k, "monitor.passthrough")) this->monitor_passthrough = spa_atob(s); else if (spa_streq(k, "convert.direction")) { if (spa_streq(s, "output")) this->direction = SPA_DIRECTION_OUTPUT; else this->direction = SPA_DIRECTION_INPUT; } else videoconvert_set_param(this, k, s); } this->dir[SPA_DIRECTION_INPUT].direction = SPA_DIRECTION_INPUT; this->dir[SPA_DIRECTION_OUTPUT].direction = SPA_DIRECTION_OUTPUT; this->node.iface = SPA_INTERFACE_INIT( SPA_TYPE_INTERFACE_Node, SPA_VERSION_NODE, &impl_node, this); spa_hook_list_init(&this->hooks); this->info_all = SPA_NODE_CHANGE_MASK_FLAGS | SPA_NODE_CHANGE_MASK_PARAMS; this->info = SPA_NODE_INFO_INIT(); this->info.max_input_ports = MAX_PORTS; this->info.max_output_ports = MAX_PORTS; this->info.flags = SPA_NODE_FLAG_RT | SPA_NODE_FLAG_IN_PORT_CONFIG | SPA_NODE_FLAG_OUT_PORT_CONFIG | SPA_NODE_FLAG_NEED_CONFIGURE; this->params[IDX_EnumPortConfig] = SPA_PARAM_INFO(SPA_PARAM_EnumPortConfig, SPA_PARAM_INFO_READ); this->params[IDX_PortConfig] = SPA_PARAM_INFO(SPA_PARAM_PortConfig, SPA_PARAM_INFO_READWRITE); this->params[IDX_PropInfo] = SPA_PARAM_INFO(SPA_PARAM_PropInfo, SPA_PARAM_INFO_READ); this->params[IDX_Props] = SPA_PARAM_INFO(SPA_PARAM_Props, SPA_PARAM_INFO_READWRITE); this->info.params = this->params; this->info.n_params = N_NODE_PARAMS; reconfigure_mode(this, SPA_PARAM_PORT_CONFIG_MODE_convert, SPA_DIRECTION_INPUT, false, false, NULL); reconfigure_mode(this, SPA_PARAM_PORT_CONFIG_MODE_convert, SPA_DIRECTION_OUTPUT, false, false, NULL); return 0; } static const struct spa_interface_info impl_interfaces[] = { {SPA_TYPE_INTERFACE_Node,}, }; static int impl_enum_interface_info(const struct spa_handle_factory *factory, const struct spa_interface_info **info, uint32_t *index) { spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(info != NULL, -EINVAL); spa_return_val_if_fail(index != NULL, -EINVAL); switch (*index) { case 0: *info = &impl_interfaces[*index]; break; default: return 0; } (*index)++; return 1; } const struct spa_handle_factory spa_videoconvert_ffmpeg_factory = { SPA_VERSION_HANDLE_FACTORY, SPA_NAME_VIDEO_CONVERT".ffmpeg", NULL, impl_get_size, impl_init, impl_enum_interface_info, }; pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/videotestsrc/000077500000000000000000000000001511204443500254605ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/videotestsrc/draw.c000066400000000000000000000117161511204443500265670ustar00rootroot00000000000000/* Spa Video Test Source */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include typedef enum { GRAY = 0, YELLOW, CYAN, GREEN, MAGENTA, RED, BLUE, BLACK, NEG_I, WHITE, POS_Q, DARK_BLACK, LIGHT_BLACK, N_COLORS } Color; typedef struct _Pixel Pixel; struct _Pixel { unsigned char R; unsigned char G; unsigned char B; unsigned char Y; unsigned char U; unsigned char V; }; static Pixel colors[N_COLORS] = { {191, 191, 191, 0, 0, 0}, /* GRAY */ {191, 191, 0, 0, 0, 0}, /* YELLOW */ {0, 191, 191, 0, 0, 0}, /* CYAN */ {0, 191, 0, 0, 0, 0}, /* GREEN */ {191, 0, 191, 0, 0, 0}, /* MAGENTA */ {191, 0, 0, 0, 0, 0}, /* RED */ {0, 0, 191, 0, 0, 0}, /* BLUE */ {19, 19, 19, 0, 0, 0}, /* BLACK */ {0, 33, 76, 0, 0, 0}, /* NEGATIVE I */ {255, 255, 255, 0, 0, 0}, /* WHITE */ {49, 0, 107, 0, 0, 0}, /* POSITIVE Q */ {9, 9, 9, 0, 0, 0}, /* DARK BLACK */ {29, 29, 29, 0, 0, 0}, /* LIGHT BLACK */ }; /* YUV values are computed in init_colors() */ typedef struct _DrawingData DrawingData; typedef void (*DrawPixelFunc) (DrawingData * dd, int x, Pixel * pixel); struct _DrawingData { char *line; int width; int height; int stride; DrawPixelFunc draw_pixel; }; static inline void update_yuv(Pixel * pixel) { uint16_t y, u, v; /* see https://en.wikipedia.org/wiki/YUV#Studio_swing_for_BT.601 */ y = 76 * pixel->R + 150 * pixel->G + 29 * pixel->B; u = -43 * pixel->R - 84 * pixel->G + 127 * pixel->B; v = 127 * pixel->R - 106 * pixel->G - 21 * pixel->B; y = (y + 128) >> 8; u = (u + 128) >> 8; v = (v + 128) >> 8; pixel->Y = y; pixel->U = u + 128; pixel->V = v + 128; } static void init_colors(void) { int i; if (colors[WHITE].Y != 0) { /* already computed */ return; } for (i = 0; i < N_COLORS; i++) { update_yuv(&colors[i]); } } static void draw_pixel_rgb(DrawingData * dd, int x, Pixel * color) { dd->line[3 * x + 0] = color->R; dd->line[3 * x + 1] = color->G; dd->line[3 * x + 2] = color->B; } static void draw_pixel_uyvy(DrawingData * dd, int x, Pixel * color) { if (x & 1) { /* odd pixel */ dd->line[2 * (x - 1) + 3] = color->Y; } else { /* even pixel */ dd->line[2 * x + 0] = color->U; dd->line[2 * x + 1] = color->Y; dd->line[2 * x + 2] = color->V; } } static int drawing_data_init(DrawingData * dd, struct impl *this, char *data) { struct port *port = &this->port; struct spa_video_info *format = &port->current_format; struct spa_rectangle *size = &format->info.raw.size; if ((format->media_type != SPA_MEDIA_TYPE_video) || (format->media_subtype != SPA_MEDIA_SUBTYPE_raw)) return -ENOTSUP; if (format->info.raw.format == SPA_VIDEO_FORMAT_RGB) { dd->draw_pixel = draw_pixel_rgb; } else if (format->info.raw.format == SPA_VIDEO_FORMAT_UYVY) { dd->draw_pixel = draw_pixel_uyvy; } else return -ENOTSUP; dd->line = data; dd->width = size->width; dd->height = size->height; dd->stride = port->stride; return 0; } static inline void draw_pixels(DrawingData * dd, int offset, Color color, int length) { int x; for (x = offset; x < offset + length; x++) { dd->draw_pixel(dd, x, &colors[color]); } } static inline void next_line(DrawingData * dd) { dd->line += dd->stride; } static void draw_smpte_snow(DrawingData * dd) { int h, w; int y1, y2; int i, j; w = dd->width; h = dd->height; y1 = 2 * h / 3; y2 = 3 * h / 4; for (i = 0; i < y1; i++) { for (j = 0; j < 7; j++) { int x1 = j * w / 7; int x2 = (j + 1) * w / 7; draw_pixels(dd, x1, j, x2 - x1); } next_line(dd); } for (i = y1; i < y2; i++) { for (j = 0; j < 7; j++) { int x1 = j * w / 7; int x2 = (j + 1) * w / 7; Color c = (j & 1) ? BLACK : BLUE - j; draw_pixels(dd, x1, c, x2 - x1); } next_line(dd); } for (i = y2; i < h; i++) { int x = 0; /* negative I */ draw_pixels(dd, x, NEG_I, w / 6); x += w / 6; /* white */ draw_pixels(dd, x, WHITE, w / 6); x += w / 6; /* positive Q */ draw_pixels(dd, x, POS_Q, w / 6); x += w / 6; /* pluge */ draw_pixels(dd, x, DARK_BLACK, w / 12); x += w / 12; draw_pixels(dd, x, BLACK, w / 12); x += w / 12; draw_pixels(dd, x, LIGHT_BLACK, w / 12); x += w / 12; /* war of the ants (a.k.a. snow) */ for (j = x; j < w; j++) { Pixel p; unsigned char r = rand(); p.R = r; p.G = r; p.B = r; update_yuv(&p); dd->draw_pixel(dd, j, &p); } next_line(dd); } } static void draw_snow(DrawingData * dd) { int x, y; for (y = 0; y < dd->height; y++) { for (x = 0; x < dd->width; x++) { Pixel p; unsigned char r = rand(); p.R = r; p.G = r; p.B = r; update_yuv(&p); dd->draw_pixel(dd, x, &p); } next_line(dd); } } static int draw(struct impl *this, char *data) { DrawingData dd; int res; init_colors(); if ((res = drawing_data_init(&dd, this, data)) < 0) return res; switch (this->props.pattern) { case PATTERN_SMPTE_SNOW: draw_smpte_snow(&dd); break; case PATTERN_SNOW: draw_snow(&dd); break; default: return -ENOTSUP; } return 0; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/videotestsrc/meson.build000066400000000000000000000003611511204443500276220ustar00rootroot00000000000000videotestsrc_sources = ['videotestsrc.c', 'plugin.c'] videotestsrclib = shared_library('spa-videotestsrc', videotestsrc_sources, dependencies : [ spa_dep, pthread_lib ], install : true, install_dir : spa_plugindir / 'videotestsrc') pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/videotestsrc/plugin.c000066400000000000000000000012101511204443500271140ustar00rootroot00000000000000/* Spa Video Test Source plugin */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include extern const struct spa_handle_factory spa_videotestsrc_factory; SPA_LOG_TOPIC_ENUM_DEFINE_REGISTERED; SPA_EXPORT int spa_handle_factory_enum(const struct spa_handle_factory **factory, uint32_t *index) { spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(index != NULL, -EINVAL); switch (*index) { case 0: *factory = &spa_videotestsrc_factory; break; default: return 0; } (*index)++; return 1; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/videotestsrc/videotestsrc.c000066400000000000000000000564131511204443500303530ustar00rootroot00000000000000/* Spa */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #undef SPA_LOG_TOPIC_DEFAULT #define SPA_LOG_TOPIC_DEFAULT &log_topic SPA_LOG_TOPIC_DEFINE_STATIC(log_topic, "spa.videotestsrc"); #define FRAMES_TO_TIME(port,f) ((port->current_format.info.raw.framerate.denom * (f) * SPA_NSEC_PER_SEC) / \ (port->current_format.info.raw.framerate.num)) enum pattern { PATTERN_SMPTE_SNOW, PATTERN_SNOW, }; #define DEFAULT_LIVE true #define DEFAULT_PATTERN PATTERN_SMPTE_SNOW struct props { bool live; uint32_t pattern; }; static void reset_props(struct props *props) { props->live = DEFAULT_LIVE; props->pattern = DEFAULT_PATTERN; } #define MAX_BUFFERS 16 #define MAX_PORTS 1 struct buffer { uint32_t id; struct spa_buffer *outbuf; bool outstanding; struct spa_meta_header *h; struct spa_list link; }; struct port { uint64_t info_all; struct spa_port_info info; struct spa_param_info params[5]; struct spa_io_buffers *io; bool have_format; struct spa_video_info current_format; size_t bpp; int stride; struct buffer buffers[MAX_BUFFERS]; uint32_t n_buffers; struct spa_list empty; }; struct impl { struct spa_handle handle; struct spa_node node; struct spa_log *log; struct spa_loop *data_loop; struct spa_loop_utils *loop_utils; uint64_t info_all; struct spa_node_info info; struct spa_param_info params[2]; struct props props; struct spa_io_clock *clock; struct spa_io_position *position; struct spa_hook_list hooks; struct spa_callbacks callbacks; bool async; struct spa_source *timer_source; struct itimerspec timerspec; bool started; uint64_t start_time; uint64_t elapsed_time; uint64_t frame_count; struct port port; }; #define CHECK_PORT(this,d,p) ((d) == SPA_DIRECTION_OUTPUT && (p) < MAX_PORTS) static int impl_node_enum_params(void *object, int seq, uint32_t id, uint32_t start, uint32_t num, const struct spa_pod *filter) { struct impl *this = object; struct spa_pod *param; struct spa_pod_builder b = { 0 }; uint8_t buffer[1024]; struct spa_result_node_params result; uint32_t count = 0; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(num != 0, -EINVAL); result.id = id; result.next = start; next: result.index = result.next++; spa_pod_builder_init(&b, buffer, sizeof(buffer)); switch (id) { case SPA_PARAM_PropInfo: { struct props *p = &this->props; struct spa_pod_frame f[2]; switch (result.index) { case 0: param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_PropInfo, id, SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_live), SPA_PROP_INFO_description, SPA_POD_String("Configure live mode of the source"), SPA_PROP_INFO_type, SPA_POD_Bool(p->live)); break; case 1: spa_pod_builder_push_object(&b, &f[0], SPA_TYPE_OBJECT_PropInfo, id); spa_pod_builder_add(&b, SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_patternType), SPA_PROP_INFO_description, SPA_POD_String("The pattern"), SPA_PROP_INFO_type, SPA_POD_Int(p->pattern), 0); spa_pod_builder_prop(&b, SPA_PROP_INFO_labels, 0), spa_pod_builder_push_struct(&b, &f[1]); spa_pod_builder_int(&b, PATTERN_SMPTE_SNOW); spa_pod_builder_string(&b, "SMPTE snow"); spa_pod_builder_int(&b, PATTERN_SNOW); spa_pod_builder_string(&b, "Snow"); spa_pod_builder_pop(&b, &f[1]); param = spa_pod_builder_pop(&b, &f[0]); break; default: return 0; } break; } case SPA_PARAM_Props: { struct props *p = &this->props; switch (result.index) { case 0: param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_Props, id, SPA_PROP_live, SPA_POD_Bool(p->live), SPA_PROP_patternType, SPA_POD_Int(p->pattern)); break; default: return 0; } break; } default: return -ENOENT; } if (spa_pod_filter(&b, &result.param, param, filter) < 0) goto next; spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); if (++count != num) goto next; return 0; } static int impl_node_set_io(void *object, uint32_t id, void *data, size_t size) { struct impl *this = object; spa_return_val_if_fail(this != NULL, -EINVAL); switch (id) { case SPA_IO_Clock: if (size > 0 && size < sizeof(struct spa_io_clock)) return -EINVAL; this->clock = data; break; case SPA_IO_Position: this->position = data; break; default: return -ENOENT; } return 0; } static int impl_node_set_param(void *object, uint32_t id, uint32_t flags, const struct spa_pod *param) { struct impl *this = object; spa_return_val_if_fail(this != NULL, -EINVAL); switch (id) { case SPA_PARAM_Props: { struct props *p = &this->props; struct port *port = &this->port; if (param == NULL) { reset_props(p); return 0; } spa_pod_parse_object(param, SPA_TYPE_OBJECT_Props, NULL, SPA_PROP_live, SPA_POD_OPT_Bool(&p->live), SPA_PROP_patternType, SPA_POD_OPT_Int(&p->pattern)); if (p->live) port->info.flags |= SPA_PORT_FLAG_LIVE; else port->info.flags &= ~SPA_PORT_FLAG_LIVE; break; } default: return -ENOENT; } return 0; } #include "draw.c" static int fill_buffer(struct impl *this, struct buffer *b) { return draw(this, b->outbuf->datas[0].data); } static void set_timer(struct impl *this, bool enabled) { if (this->async || this->props.live) { if (enabled) { if (this->props.live) { uint64_t next_time = this->start_time + this->elapsed_time; this->timerspec.it_value.tv_sec = next_time / SPA_NSEC_PER_SEC; this->timerspec.it_value.tv_nsec = next_time % SPA_NSEC_PER_SEC; } else { this->timerspec.it_value.tv_sec = 0; this->timerspec.it_value.tv_nsec = 1; } } else { this->timerspec.it_value.tv_sec = 0; this->timerspec.it_value.tv_nsec = 0; } spa_loop_utils_update_timer(this->loop_utils, this->timer_source, &this->timerspec.it_value, &this->timerspec.it_interval, true); } } static int make_buffer(struct impl *this) { struct buffer *b; struct port *port = &this->port; struct spa_io_buffers *io = port->io; uint32_t n_bytes; if (spa_list_is_empty(&port->empty)) { set_timer(this, false); spa_log_error(this->log, "%p: out of buffers", this); return -EPIPE; } b = spa_list_first(&port->empty, struct buffer, link); spa_list_remove(&b->link); b->outstanding = true; n_bytes = b->outbuf->datas[0].maxsize; spa_log_trace(this->log, "%p: dequeue buffer %d", this, b->id); fill_buffer(this, b); b->outbuf->datas[0].chunk->offset = 0; b->outbuf->datas[0].chunk->size = n_bytes; b->outbuf->datas[0].chunk->stride = port->stride; if (b->h) { b->h->seq = this->frame_count; b->h->pts = this->start_time + this->elapsed_time; b->h->dts_offset = 0; } this->frame_count++; this->elapsed_time = FRAMES_TO_TIME(port, this->frame_count); set_timer(this, true); io->buffer_id = b->id; io->status = SPA_STATUS_HAVE_DATA; return io->status; } static void on_output(void* data, uint64_t expirations) { struct impl *this = data; int res; res = make_buffer(this); if (res == SPA_STATUS_HAVE_DATA) spa_node_call_ready(&this->callbacks, res); } static int impl_node_send_command(void *object, const struct spa_command *command) { struct impl *this = object; struct port *port; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(command != NULL, -EINVAL); port = &this->port; switch (SPA_NODE_COMMAND_ID(command)) { case SPA_NODE_COMMAND_Start: { struct timespec now; if (!port->have_format) return -EIO; if (port->n_buffers == 0) return -EIO; if (this->started) return 0; clock_gettime(CLOCK_MONOTONIC, &now); if (this->props.live) this->start_time = SPA_TIMESPEC_TO_NSEC(&now); else this->start_time = 0; this->frame_count = 0; this->elapsed_time = 0; this->started = true; set_timer(this, true); break; } case SPA_NODE_COMMAND_Suspend: case SPA_NODE_COMMAND_Pause: if (!this->started) return 0; this->started = false; set_timer(this, false); break; default: return -ENOTSUP; } return 0; } static const struct spa_dict_item node_info_items[] = { { SPA_KEY_MEDIA_CLASS, "Video/Source" }, { SPA_KEY_NODE_DRIVER, "true" }, }; static void emit_node_info(struct impl *this, bool full) { uint64_t old = full ? this->info.change_mask : 0; if (full) this->info.change_mask = this->info_all; if (this->info.change_mask) { this->info.props = &SPA_DICT_INIT_ARRAY(node_info_items); spa_node_emit_info(&this->hooks, &this->info); this->info.change_mask = old; } } static void emit_port_info(struct impl *this, struct port *port, bool full) { uint64_t old = full ? port->info.change_mask : 0; if (full) port->info.change_mask = port->info_all; if (port->info.change_mask) { spa_node_emit_port_info(&this->hooks, SPA_DIRECTION_OUTPUT, 0, &port->info); port->info.change_mask = old; } } static int impl_node_add_listener(void *object, struct spa_hook *listener, const struct spa_node_events *events, void *data) { struct impl *this = object; struct spa_hook_list save; spa_return_val_if_fail(this != NULL, -EINVAL); spa_hook_list_isolate(&this->hooks, &save, listener, events, data); emit_node_info(this, true); emit_port_info(this, &this->port, true); spa_hook_list_join(&this->hooks, &save); return 0; } static int impl_node_set_callbacks(void *object, const struct spa_node_callbacks *callbacks, void *data) { struct impl *this = object; spa_return_val_if_fail(this != NULL, -EINVAL); this->callbacks = SPA_CALLBACKS_INIT(callbacks, data); return 0; } static int impl_node_add_port(void *object, enum spa_direction direction, uint32_t port_id, const struct spa_dict *props) { return -ENOTSUP; } static int impl_node_remove_port(void *object, enum spa_direction direction, uint32_t port_id) { return -ENOTSUP; } static int port_enum_formats(void *object, enum spa_direction direction, uint32_t port_id, uint32_t index, const struct spa_pod *filter, struct spa_pod **param, struct spa_pod_builder *builder) { switch (index) { case 0: *param = spa_pod_builder_add_object(builder, SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), SPA_FORMAT_VIDEO_format, SPA_POD_CHOICE_ENUM_Id(3, SPA_VIDEO_FORMAT_RGB, SPA_VIDEO_FORMAT_RGB, SPA_VIDEO_FORMAT_UYVY), SPA_FORMAT_VIDEO_size, SPA_POD_CHOICE_RANGE_Rectangle( &SPA_RECTANGLE(320, 240), &SPA_RECTANGLE(1, 1), &SPA_RECTANGLE(INT32_MAX, INT32_MAX)), SPA_FORMAT_VIDEO_framerate, SPA_POD_CHOICE_RANGE_Fraction( &SPA_FRACTION(25,1), &SPA_FRACTION(0, 1), &SPA_FRACTION(INT32_MAX, 1))); break; default: return 0; } return 1; } static int impl_node_port_enum_params(void *object, int seq, enum spa_direction direction, uint32_t port_id, uint32_t id, uint32_t start, uint32_t num, const struct spa_pod *filter) { struct impl *this = object; struct port *port; struct spa_pod_builder b = { 0 }; uint8_t buffer[1024]; struct spa_pod *param; struct spa_result_node_params result; uint32_t count = 0; int res; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(num != 0, -EINVAL); spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); port = &this->port; result.id = id; result.next = start; next: result.index = result.next++; spa_pod_builder_init(&b, buffer, sizeof(buffer)); switch (id) { case SPA_PARAM_EnumFormat: if ((res = port_enum_formats(this, direction, port_id, result.index, filter, ¶m, &b)) <= 0) return res; break; case SPA_PARAM_Format: if (!port->have_format) return -EIO; if (result.index > 0) return 0; param = spa_format_video_raw_build(&b, id, &port->current_format.info.raw); break; case SPA_PARAM_Buffers: { struct spa_video_info_raw *raw_info = &port->current_format.info.raw; if (!port->have_format) return -EIO; if (result.index > 0) return 0; param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamBuffers, id, SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(2, 1, MAX_BUFFERS), SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(1), SPA_PARAM_BUFFERS_size, SPA_POD_Int(port->stride * raw_info->size.height), SPA_PARAM_BUFFERS_stride, SPA_POD_Int(port->stride)); break; } case SPA_PARAM_Meta: switch (result.index) { case 0: param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamMeta, id, SPA_PARAM_META_type, SPA_POD_Id(SPA_META_Header), SPA_PARAM_META_size, SPA_POD_Int(sizeof(struct spa_meta_header))); break; default: return 0; } break; case SPA_PARAM_IO: switch (result.index) { case 0: param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamIO, id, SPA_PARAM_IO_id, SPA_POD_Id(SPA_IO_Buffers), SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_buffers))); break; default: return 0; } break; default: return -ENOENT; } if (spa_pod_filter(&b, &result.param, param, filter) < 0) goto next; spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); if (++count != num) goto next; return 0; } static int clear_buffers(struct impl *this, struct port *port) { if (port->n_buffers > 0) { spa_log_debug(this->log, "%p: clear buffers", this); port->n_buffers = 0; spa_list_init(&port->empty); this->started = false; set_timer(this, false); } return 0; } static int port_set_format(struct impl *this, struct port *port, uint32_t flags, const struct spa_pod *format) { int res; if (format == NULL) { port->have_format = false; clear_buffers(this, port); } else { struct spa_video_info info = { 0 }; if ((res = spa_format_parse(format, &info.media_type, &info.media_subtype)) < 0) return res; if (info.media_type != SPA_MEDIA_TYPE_video && info.media_subtype != SPA_MEDIA_SUBTYPE_raw) return -EINVAL; if (spa_format_video_raw_parse(format, &info.info.raw) < 0) return -EINVAL; if (info.info.raw.format == SPA_VIDEO_FORMAT_RGB) port->bpp = 3; else if (info.info.raw.format == SPA_VIDEO_FORMAT_UYVY) port->bpp = 2; else return -EINVAL; if (info.info.raw.size.width == 0 || info.info.raw.size.height == 0 || info.info.raw.framerate.num == 0 || info.info.raw.framerate.denom == 0) return -EINVAL; port->current_format = info; port->have_format = true; } port->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; if (port->have_format) { struct spa_video_info_raw *raw_info = &port->current_format.info.raw; port->stride = SPA_ROUND_UP_N(port->bpp * raw_info->size.width, 4); port->params[3] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_READWRITE); port->params[4] = SPA_PARAM_INFO(SPA_PARAM_Buffers, SPA_PARAM_INFO_READ); } else { port->params[3] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); port->params[4] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); } emit_port_info(this, port, false); return 0; } static int impl_node_port_set_param(void *object, enum spa_direction direction, uint32_t port_id, uint32_t id, uint32_t flags, const struct spa_pod *param) { struct impl *this = object; struct port *port; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(CHECK_PORT(node, direction, port_id), -EINVAL); port = &this->port; if (id == SPA_PARAM_Format) { return port_set_format(this, port, flags, param); } else return -ENOENT; } static int impl_node_port_use_buffers(void *object, enum spa_direction direction, uint32_t port_id, uint32_t flags, struct spa_buffer **buffers, uint32_t n_buffers) { struct impl *this = object; struct port *port; uint32_t i; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); port = &this->port; clear_buffers(this, port); if (n_buffers > 0 && !port->have_format) return -EIO; if (n_buffers > MAX_BUFFERS) return -ENOSPC; for (i = 0; i < n_buffers; i++) { struct buffer *b; struct spa_data *d = buffers[i]->datas; b = &port->buffers[i]; b->id = i; b->outbuf = buffers[i]; b->outstanding = false; b->h = spa_buffer_find_meta_data(buffers[i], SPA_META_Header, sizeof(*b->h)); if (d[0].data == NULL) { spa_log_error(this->log, "%p: invalid memory on buffer %p", this, buffers[i]); return -EINVAL; } spa_list_append(&port->empty, &b->link); } port->n_buffers = n_buffers; return 0; } static int impl_node_port_set_io(void *object, enum spa_direction direction, uint32_t port_id, uint32_t id, void *data, size_t size) { struct impl *this = object; struct port *port; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); port = &this->port; switch (id) { case SPA_IO_Buffers: port->io = data; break; default: return -ENOENT; } return 0; } static inline void reuse_buffer(struct impl *this, struct port *port, uint32_t id) { struct buffer *b = &port->buffers[id]; spa_return_if_fail(b->outstanding); spa_log_trace(this->log, "%p: reuse buffer %d", this, id); b->outstanding = false; spa_list_append(&port->empty, &b->link); if (!this->props.live) set_timer(this, true); } static int impl_node_port_reuse_buffer(void *object, uint32_t port_id, uint32_t buffer_id) { struct impl *this = object; struct port *port; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(port_id == 0, -EINVAL); port = &this->port; spa_return_val_if_fail(buffer_id < port->n_buffers, -EINVAL); reuse_buffer(this, port, buffer_id); return 0; } static int impl_node_process(void *object) { struct impl *this = object; struct port *port; struct spa_io_buffers *io; spa_return_val_if_fail(this != NULL, -EINVAL); port = &this->port; if ((io = port->io) == NULL) return -EIO; if (io->status == SPA_STATUS_HAVE_DATA) return SPA_STATUS_HAVE_DATA; if (io->buffer_id < port->n_buffers) { reuse_buffer(this, port, io->buffer_id); io->buffer_id = SPA_ID_INVALID; } if (!this->props.live) return make_buffer(this); else return SPA_STATUS_OK; } static const struct spa_node_methods impl_node = { SPA_VERSION_NODE_METHODS, .add_listener = impl_node_add_listener, .set_callbacks = impl_node_set_callbacks, .enum_params = impl_node_enum_params, .set_param = impl_node_set_param, .set_io = impl_node_set_io, .send_command = impl_node_send_command, .add_port = impl_node_add_port, .remove_port = impl_node_remove_port, .port_enum_params = impl_node_port_enum_params, .port_set_param = impl_node_port_set_param, .port_use_buffers = impl_node_port_use_buffers, .port_set_io = impl_node_port_set_io, .port_reuse_buffer = impl_node_port_reuse_buffer, .process = impl_node_process, }; static int impl_get_interface(struct spa_handle *handle, const char *type, void **interface) { struct impl *this; spa_return_val_if_fail(handle != NULL, -EINVAL); spa_return_val_if_fail(interface != NULL, -EINVAL); this = (struct impl *) handle; if (spa_streq(type, SPA_TYPE_INTERFACE_Node)) *interface = &this->node; else return -ENOENT; return 0; } static int do_remove_timer(struct spa_loop *loop, bool async, uint32_t seq, const void *data, size_t size, void *user_data) { struct impl *this = user_data; spa_loop_remove_source(this->data_loop, this->timer_source); return 0; } static int impl_clear(struct spa_handle *handle) { struct impl *this; spa_return_val_if_fail(handle != NULL, -EINVAL); this = (struct impl *) handle; if (this->data_loop) spa_loop_locked(this->data_loop, do_remove_timer, 0, NULL, 0, this); spa_loop_utils_destroy_source(this->loop_utils, this->timer_source); return 0; } static size_t impl_get_size(const struct spa_handle_factory *factory, const struct spa_dict *params) { return sizeof(struct impl); } static int impl_init(const struct spa_handle_factory *factory, struct spa_handle *handle, const struct spa_dict *info, const struct spa_support *support, uint32_t n_support) { struct impl *this; struct port *port; spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(handle != NULL, -EINVAL); handle->get_interface = impl_get_interface; handle->clear = impl_clear; this = (struct impl *) handle; this->log = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log); this->data_loop = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_DataLoop); this->loop_utils = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_LoopUtils); spa_hook_list_init(&this->hooks); this->node.iface = SPA_INTERFACE_INIT( SPA_TYPE_INTERFACE_Node, SPA_VERSION_NODE, &impl_node, this); this->info_all = SPA_NODE_CHANGE_MASK_FLAGS | SPA_NODE_CHANGE_MASK_PROPS | SPA_NODE_CHANGE_MASK_PARAMS; this->info = SPA_NODE_INFO_INIT(); this->info.max_output_ports = 1; this->info.flags = SPA_NODE_FLAG_RT; this->params[0] = SPA_PARAM_INFO(SPA_PARAM_PropInfo, SPA_PARAM_INFO_READ); this->params[1] = SPA_PARAM_INFO(SPA_PARAM_Props, SPA_PARAM_INFO_READWRITE); this->info.params = this->params; this->info.n_params = 2; reset_props(&this->props); this->timer_source = spa_loop_utils_add_timer(this->loop_utils, on_output, this); this->timerspec.it_value.tv_sec = 0; this->timerspec.it_value.tv_nsec = 0; this->timerspec.it_interval.tv_sec = 0; this->timerspec.it_interval.tv_nsec = 0; port = &this->port; port->info_all = SPA_PORT_CHANGE_MASK_FLAGS | SPA_PORT_CHANGE_MASK_PARAMS; port->info = SPA_PORT_INFO_INIT(); port->info.flags = SPA_PORT_FLAG_NO_REF; if (this->props.live) port->info.flags |= SPA_PORT_FLAG_LIVE; port->params[0] = SPA_PARAM_INFO(SPA_PARAM_EnumFormat, SPA_PARAM_INFO_READ); port->params[1] = SPA_PARAM_INFO(SPA_PARAM_Meta, SPA_PARAM_INFO_READ); port->params[2] = SPA_PARAM_INFO(SPA_PARAM_IO, SPA_PARAM_INFO_READ); port->params[3] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); port->params[4] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); port->info.params = port->params; port->info.n_params = 5; spa_list_init(&port->empty); return 0; } static const struct spa_interface_info impl_interfaces[] = { {SPA_TYPE_INTERFACE_Node,}, }; static int impl_enum_interface_info(const struct spa_handle_factory *factory, const struct spa_interface_info **info, uint32_t *index) { spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(info != NULL, -EINVAL); spa_return_val_if_fail(index != NULL, -EINVAL); switch (*index) { case 0: *info = &impl_interfaces[*index]; break; default: return 0; } (*index)++; return 1; } static const struct spa_dict_item info_items[] = { { SPA_KEY_FACTORY_AUTHOR, "Wim Taymans " }, { SPA_KEY_FACTORY_DESCRIPTION, "Generate a video test pattern" }, }; static const struct spa_dict info = SPA_DICT_INIT_ARRAY(info_items); const struct spa_handle_factory spa_videotestsrc_factory = { SPA_VERSION_HANDLE_FACTORY, "videotestsrc", &info, impl_get_size, impl_init, impl_enum_interface_info, }; pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/volume/000077500000000000000000000000001511204443500242515ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/volume/meson.build000066400000000000000000000003001511204443500264040ustar00rootroot00000000000000volume_sources = ['volume.c', 'plugin.c'] volumelib = shared_library('spa-volume', volume_sources, dependencies : [ spa_dep ], install : true, install_dir : spa_plugindir / 'volume') pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/volume/plugin.c000066400000000000000000000011611511204443500257120ustar00rootroot00000000000000/* Spa Volume plugin */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include extern const struct spa_handle_factory spa_volume_factory; SPA_LOG_TOPIC_ENUM_DEFINE_REGISTERED; SPA_EXPORT int spa_handle_factory_enum(const struct spa_handle_factory **factory, uint32_t *index) { spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(index != NULL, -EINVAL); switch (*index) { case 0: *factory = &spa_volume_factory; break; default: return 0; } (*index)++; return 1; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/volume/volume.c000066400000000000000000000521471511204443500257350ustar00rootroot00000000000000/* Spa */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #include #include #include #include #include #include #include #include #include #undef SPA_LOG_TOPIC_DEFAULT #define SPA_LOG_TOPIC_DEFAULT &log_topic SPA_LOG_TOPIC_DEFINE_STATIC(log_topic, "spa.volume"); #define DEFAULT_RATE 48000 #define DEFAULT_CHANNELS 2 #define DEFAULT_VOLUME 1.0 #define DEFAULT_MUTE false struct props { double volume; bool mute; }; static void reset_props(struct props *props) { props->volume = DEFAULT_VOLUME; props->mute = DEFAULT_MUTE; } #define MAX_BUFFERS 16 struct buffer { uint32_t id; #define BUFFER_FLAG_OUT (1<<0) uint32_t flags; struct spa_buffer *outbuf; struct spa_meta_header *h; void *ptr; size_t size; struct spa_list link; }; struct port { enum spa_direction direction; uint32_t id; bool have_format; uint64_t info_all; struct spa_port_info info; struct spa_param_info params[5]; struct buffer buffers[MAX_BUFFERS]; uint32_t n_buffers; struct spa_io_buffers *io; struct spa_list empty; }; struct impl { struct spa_handle handle; struct spa_node node; struct spa_log *log; uint32_t quantum_limit; uint64_t info_all; struct spa_node_info info; struct spa_param_info params[5]; struct props props; struct spa_hook_list hooks; struct spa_audio_info current_format; int bpf; struct port in_ports[1]; struct port out_ports[1]; bool started; }; #define CHECK_IN_PORT(this,d,p) ((d) == SPA_DIRECTION_INPUT && (p) == 0) #define CHECK_OUT_PORT(this,d,p) ((d) == SPA_DIRECTION_OUTPUT && (p) == 0) #define CHECK_PORT(this,d,p) ((p) == 0) #define GET_IN_PORT(this,p) (&this->in_ports[p]) #define GET_OUT_PORT(this,p) (&this->out_ports[p]) #define GET_PORT(this,d,p) (d == SPA_DIRECTION_INPUT ? GET_IN_PORT(this,p) : GET_OUT_PORT(this,p)) static int impl_node_enum_params(void *object, int seq, uint32_t id, uint32_t start, uint32_t num, const struct spa_pod *filter) { struct impl *this = object; struct spa_pod_builder b = { 0 }; uint8_t buffer[1024]; struct spa_pod *param; struct props *p; struct spa_result_node_params result; uint32_t count = 0; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(num != 0, -EINVAL); p = &this->props; result.id = id; result.next = start; next: result.index = result.next++; spa_pod_builder_init(&b, buffer, sizeof(buffer)); switch (id) { case SPA_PARAM_PropInfo: switch (result.index) { case 0: param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_PropInfo, id, SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_volume), SPA_PROP_INFO_description, SPA_POD_String("The volume"), SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Float(p->volume, 0.0, 10.0)); break; case 1: param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_PropInfo, id, SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_mute), SPA_PROP_INFO_description, SPA_POD_String("Mute"), SPA_PROP_INFO_type, SPA_POD_Bool(p->mute)); break; default: return 0; } break; case SPA_PARAM_Props: switch (result.index) { case 0: param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_Props, id, SPA_PROP_volume, SPA_POD_Float(p->volume), SPA_PROP_mute, SPA_POD_Bool(p->mute)); break; default: return 0; } break; default: return -ENOENT; } if (spa_pod_filter(&b, &result.param, param, filter) < 0) goto next; spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); if (++count != num) goto next; return 0; } static int impl_node_set_io(void *object, uint32_t id, void *data, size_t size) { return -ENOTSUP; } static int impl_node_set_param(void *object, uint32_t id, uint32_t flags, const struct spa_pod *param) { struct impl *this = object; spa_return_val_if_fail(this != NULL, -EINVAL); switch (id) { case SPA_PARAM_Props: { struct props *p = &this->props; if (param == NULL) { reset_props(p); return 0; } spa_pod_parse_object(param, SPA_TYPE_OBJECT_Props, NULL, SPA_PROP_volume, SPA_POD_OPT_Float(&p->volume), SPA_PROP_mute, SPA_POD_OPT_Bool(&p->mute)); break; } default: return -ENOENT; } return 0; } static int impl_node_send_command(void *object, const struct spa_command *command) { struct impl *this = object; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(command != NULL, -EINVAL); switch (SPA_NODE_COMMAND_ID(command)) { case SPA_NODE_COMMAND_Start: this->started = true; break; case SPA_NODE_COMMAND_Pause: this->started = false; break; default: return -ENOTSUP; } return 0; } static void emit_node_info(struct impl *this, bool full) { uint64_t old = full ? this->info.change_mask : 0; if (full) this->info.change_mask = this->info_all; if (this->info.change_mask) { spa_node_emit_info(&this->hooks, &this->info); this->info.change_mask = old; } } static void emit_port_info(struct impl *this, struct port *port, bool full) { uint64_t old = full ? port->info.change_mask : 0; if (full) port->info.change_mask = port->info_all; if (port->info.change_mask) { spa_node_emit_port_info(&this->hooks, port->direction, port->id, &port->info); port->info.change_mask = old; } } static int impl_node_add_listener(void *object, struct spa_hook *listener, const struct spa_node_events *events, void *data) { struct impl *this = object; struct spa_hook_list save; spa_return_val_if_fail(this != NULL, -EINVAL); spa_hook_list_isolate(&this->hooks, &save, listener, events, data); emit_node_info(this, true); emit_port_info(this, GET_IN_PORT(this, 0), true); emit_port_info(this, GET_OUT_PORT(this, 0), true); spa_hook_list_join(&this->hooks, &save); return 0; } static int impl_node_set_callbacks(void *object, const struct spa_node_callbacks *callbacks, void *data) { return 0; } static int impl_node_add_port(void *object, enum spa_direction direction, uint32_t port_id, const struct spa_dict *props) { return -ENOTSUP; } static int impl_node_remove_port(void *object, enum spa_direction direction, uint32_t port_id) { return -ENOTSUP; } static int port_enum_formats(void *object, enum spa_direction direction, uint32_t port_id, uint32_t index, const struct spa_pod *filter, struct spa_pod **param, struct spa_pod_builder *builder) { switch (index) { case 0: *param = spa_pod_builder_add_object(builder, SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_audio), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), SPA_FORMAT_AUDIO_format, SPA_POD_CHOICE_ENUM_Id(2, SPA_AUDIO_FORMAT_S16, SPA_AUDIO_FORMAT_S16), SPA_FORMAT_AUDIO_rate, SPA_POD_CHOICE_RANGE_Int( DEFAULT_RATE, 1, INT32_MAX), SPA_FORMAT_AUDIO_channels, SPA_POD_CHOICE_RANGE_Int( DEFAULT_CHANNELS, 1, INT32_MAX)); break; default: return 0; } return 1; } static int impl_node_port_enum_params(void *object, int seq, enum spa_direction direction, uint32_t port_id, uint32_t id, uint32_t start, uint32_t num, const struct spa_pod *filter) { struct impl *this = object; struct port *port; struct spa_pod_builder b = { 0 }; uint8_t buffer[1024]; struct spa_pod *param; struct spa_result_node_params result; uint32_t count = 0; int res; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(num != 0, -EINVAL); spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); port = GET_PORT(this, direction, port_id); result.id = id; result.next = start; next: result.index = result.next++; spa_pod_builder_init(&b, buffer, sizeof(buffer)); switch (id) { case SPA_PARAM_EnumFormat: if ((res = port_enum_formats(this, direction, port_id, result.index, filter, ¶m, &b)) <= 0) return res; break; case SPA_PARAM_Format: if (!port->have_format) return -EIO; if (result.index > 0) return 0; param = spa_format_audio_raw_build(&b, id, &this->current_format.info.raw); break; case SPA_PARAM_Buffers: if (!port->have_format) return -EIO; if (result.index > 0) return 0; param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamBuffers, id, SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(2, 1, MAX_BUFFERS), SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(1), SPA_PARAM_BUFFERS_size, SPA_POD_CHOICE_RANGE_Int( this->quantum_limit * this->bpf, 16 * this->bpf, INT32_MAX), SPA_PARAM_BUFFERS_stride, SPA_POD_Int(this->bpf)); break; case SPA_PARAM_Meta: switch (result.index) { case 0: param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamMeta, id, SPA_PARAM_META_type, SPA_POD_Id(SPA_META_Header), SPA_PARAM_META_size, SPA_POD_Int(sizeof(struct spa_meta_header))); break; default: return 0; } break; case SPA_PARAM_IO: switch (result.index) { case 0: param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamIO, id, SPA_PARAM_IO_id, SPA_POD_Id(SPA_IO_Buffers), SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_buffers))); break; default: return 0; } break; default: return -ENOENT; } if (spa_pod_filter(&b, &result.param, param, filter) < 0) goto next; spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); if (++count != num) goto next; return 0; } static int clear_buffers(struct impl *this, struct port *port) { if (port->n_buffers > 0) { spa_log_debug(this->log, "%p: clear buffers", this); port->n_buffers = 0; spa_list_init(&port->empty); } return 0; } static int port_set_format(void *object, enum spa_direction direction, uint32_t port_id, uint32_t flags, const struct spa_pod *format) { struct impl *this = object; struct port *port; int res; port = GET_PORT(this, direction, port_id); if (format == NULL) { port->have_format = false; clear_buffers(this, port); } else { struct spa_audio_info info = { 0 }; if ((res = spa_format_parse(format, &info.media_type, &info.media_subtype)) < 0) return res; if (info.media_type != SPA_MEDIA_TYPE_audio || info.media_subtype != SPA_MEDIA_SUBTYPE_raw) return -EINVAL; if (spa_format_audio_raw_parse(format, &info.info.raw) < 0) return -EINVAL; if (info.info.raw.format != SPA_AUDIO_FORMAT_S16 || info.info.raw.channels == 0) return -EINVAL; this->bpf = 2 * info.info.raw.channels; this->current_format = info; port->have_format = true; } port->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; if (port->have_format) { port->params[3] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_READWRITE); port->params[4] = SPA_PARAM_INFO(SPA_PARAM_Buffers, SPA_PARAM_INFO_READ); } else { port->params[3] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); port->params[4] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); } emit_port_info(this, port, false); return 0; } static int impl_node_port_set_param(void *object, enum spa_direction direction, uint32_t port_id, uint32_t id, uint32_t flags, const struct spa_pod *param) { spa_return_val_if_fail(object != NULL, -EINVAL); spa_return_val_if_fail(CHECK_PORT(object, direction, port_id), -EINVAL); if (id == SPA_PARAM_Format) { return port_set_format(object, direction, port_id, flags, param); } else return -ENOENT; } static int impl_node_port_use_buffers(void *object, enum spa_direction direction, uint32_t port_id, uint32_t flags, struct spa_buffer **buffers, uint32_t n_buffers) { struct impl *this = object; struct port *port; uint32_t i; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); port = GET_PORT(this, direction, port_id); clear_buffers(this, port); if (n_buffers > 0 && !port->have_format) return -EIO; if (n_buffers > MAX_BUFFERS) return -ENOSPC; for (i = 0; i < n_buffers; i++) { struct buffer *b; struct spa_data *d = buffers[i]->datas; b = &port->buffers[i]; b->id = i; b->outbuf = buffers[i]; b->flags = direction == SPA_DIRECTION_INPUT ? BUFFER_FLAG_OUT : 0; b->h = spa_buffer_find_meta_data(buffers[i], SPA_META_Header, sizeof(*b->h)); if (d[0].data != NULL) { b->ptr = d[0].data; b->size = d[0].maxsize; } else { spa_log_error(this->log, "%p: invalid memory on buffer %p", this, buffers[i]); return -EINVAL; } if (!SPA_FLAG_IS_SET(b->flags, BUFFER_FLAG_OUT)) spa_list_append(&port->empty, &b->link); } port->n_buffers = n_buffers; return 0; } static int impl_node_port_set_io(void *object, enum spa_direction direction, uint32_t port_id, uint32_t id, void *data, size_t size) { struct impl *this = object; struct port *port; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); port = GET_PORT(this, direction, port_id); switch (id) { case SPA_IO_Buffers: port->io = data; break; default: return -ENOENT; } return 0; } static void recycle_buffer(struct impl *this, uint32_t id) { struct port *port = GET_OUT_PORT(this, 0); struct buffer *b = &port->buffers[id]; if (!SPA_FLAG_IS_SET(b->flags, BUFFER_FLAG_OUT)) { spa_log_warn(this->log, "%p: buffer %d not outstanding", this, id); return; } spa_list_append(&port->empty, &b->link); SPA_FLAG_CLEAR(b->flags, BUFFER_FLAG_OUT); spa_log_trace(this->log, "%p: recycle buffer %d", this, id); } static int impl_node_port_reuse_buffer(void *object, uint32_t port_id, uint32_t buffer_id) { struct impl *this = object; struct port *port; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(CHECK_PORT(this, SPA_DIRECTION_OUTPUT, port_id), -EINVAL); port = GET_OUT_PORT(this, port_id); if (buffer_id >= port->n_buffers) return -EINVAL; recycle_buffer(this, buffer_id); return 0; } static struct buffer *find_free_buffer(struct impl *this, struct port *port) { struct buffer *b; if (spa_list_is_empty(&port->empty)) return NULL; b = spa_list_first(&port->empty, struct buffer, link); spa_list_remove(&b->link); SPA_FLAG_SET(b->flags, BUFFER_FLAG_OUT); return b; } static void do_volume(struct impl *this, struct spa_buffer *dbuf, struct spa_buffer *sbuf) { uint32_t i, n_samples, n_bytes; struct spa_data *sd, *dd; int16_t *src, *dst; double volume; uint32_t written, towrite, savail, davail; uint32_t sindex, dindex; volume = this->props.volume; sd = sbuf->datas; dd = dbuf->datas; savail = SPA_MIN(sd[0].chunk->size, sd[0].maxsize); sindex = sd[0].chunk->offset; davail = 0; dindex = 0; davail = dd[0].maxsize - davail; towrite = SPA_MIN(savail, davail); written = 0; while (written < towrite) { uint32_t soffset = sindex % sd[0].maxsize; uint32_t doffset = dindex % dd[0].maxsize; src = SPA_PTROFF(sd[0].data, soffset, int16_t); dst = SPA_PTROFF(dd[0].data, doffset, int16_t); n_bytes = SPA_MIN(towrite, sd[0].maxsize - soffset); n_bytes = SPA_MIN(n_bytes, dd[0].maxsize - doffset); n_samples = n_bytes / sizeof(int16_t); for (i = 0; i < n_samples; i++) dst[i] = (int16_t)(src[i] * volume); sindex += n_bytes; dindex += n_bytes; written += n_bytes; } dd[0].chunk->offset = 0; dd[0].chunk->size = written; dd[0].chunk->stride = 0; } static int impl_node_process(void *object) { struct impl *this = object; struct port *in_port, *out_port; struct spa_io_buffers *input, *output; struct buffer *dbuf, *sbuf; spa_return_val_if_fail(this != NULL, -EINVAL); out_port = GET_OUT_PORT(this, 0); if ((output = out_port->io) == NULL) return -EIO; if (output->status == SPA_STATUS_HAVE_DATA) return SPA_STATUS_HAVE_DATA; /* recycle */ if (output->buffer_id < out_port->n_buffers) { recycle_buffer(this, output->buffer_id); output->buffer_id = SPA_ID_INVALID; } in_port = GET_IN_PORT(this, 0); if ((input = in_port->io) == NULL) return -EIO; if (input->status != SPA_STATUS_HAVE_DATA) return SPA_STATUS_NEED_DATA; if (input->buffer_id >= in_port->n_buffers) { input->status = -EINVAL; return -EINVAL; } if ((dbuf = find_free_buffer(this, out_port)) == NULL) { spa_log_error(this->log, "%p: out of buffers", this); return -EPIPE; } sbuf = &in_port->buffers[input->buffer_id]; spa_log_trace(this->log, "%p: do volume %d -> %d", this, sbuf->id, dbuf->id); do_volume(this, dbuf->outbuf, sbuf->outbuf); output->buffer_id = dbuf->id; output->status = SPA_STATUS_HAVE_DATA; input->status = SPA_STATUS_NEED_DATA; return SPA_STATUS_HAVE_DATA; } static const struct spa_node_methods impl_node = { SPA_VERSION_NODE_METHODS, .add_listener = impl_node_add_listener, .set_callbacks = impl_node_set_callbacks, .enum_params = impl_node_enum_params, .set_param = impl_node_set_param, .set_io = impl_node_set_io, .send_command = impl_node_send_command, .add_port = impl_node_add_port, .remove_port = impl_node_remove_port, .port_enum_params = impl_node_port_enum_params, .port_set_param = impl_node_port_set_param, .port_use_buffers = impl_node_port_use_buffers, .port_set_io = impl_node_port_set_io, .port_reuse_buffer = impl_node_port_reuse_buffer, .process = impl_node_process, }; static int impl_get_interface(struct spa_handle *handle, const char *type, void **interface) { struct impl *this; spa_return_val_if_fail(handle != NULL, -EINVAL); spa_return_val_if_fail(interface != NULL, -EINVAL); this = (struct impl *) handle; if (spa_streq(type, SPA_TYPE_INTERFACE_Node)) *interface = &this->node; else return -ENOENT; return 0; } static int impl_clear(struct spa_handle *handle) { return 0; } static size_t impl_get_size(const struct spa_handle_factory *factory, const struct spa_dict *params) { return sizeof(struct impl); } static int impl_init(const struct spa_handle_factory *factory, struct spa_handle *handle, const struct spa_dict *info, const struct spa_support *support, uint32_t n_support) { struct impl *this; struct port *port; uint32_t i; spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(handle != NULL, -EINVAL); handle->get_interface = impl_get_interface; handle->clear = impl_clear; this = (struct impl *) handle; this->log = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log); for (i = 0; info && i < info->n_items; i++) { const char *k = info->items[i].key; const char *s = info->items[i].value; if (spa_streq(k, "clock.quantum-limit")) spa_atou32(s, &this->quantum_limit, 0); } spa_hook_list_init(&this->hooks); this->node.iface = SPA_INTERFACE_INIT( SPA_TYPE_INTERFACE_Node, SPA_VERSION_NODE, &impl_node, this); this->info_all = SPA_NODE_CHANGE_MASK_FLAGS | SPA_NODE_CHANGE_MASK_PARAMS; this->info = SPA_NODE_INFO_INIT(); this->info.max_input_ports = 1; this->info.max_output_ports = 1; this->info.flags = SPA_NODE_FLAG_RT; this->params[0] = SPA_PARAM_INFO(SPA_PARAM_PropInfo, SPA_PARAM_INFO_READ); this->params[1] = SPA_PARAM_INFO(SPA_PARAM_Props, SPA_PARAM_INFO_READWRITE); this->info.params = this->params; this->info.n_params = 2; reset_props(&this->props); port = GET_IN_PORT(this, 0); port->direction = SPA_DIRECTION_INPUT; port->id = 0; port->info_all = SPA_PORT_CHANGE_MASK_FLAGS | SPA_PORT_CHANGE_MASK_PARAMS; port->info = SPA_PORT_INFO_INIT(); port->info.flags = SPA_PORT_FLAG_IN_PLACE; port->params[0] = SPA_PARAM_INFO(SPA_PARAM_EnumFormat, SPA_PARAM_INFO_READ); port->params[1] = SPA_PARAM_INFO(SPA_PARAM_Meta, SPA_PARAM_INFO_READ); port->params[2] = SPA_PARAM_INFO(SPA_PARAM_IO, SPA_PARAM_INFO_READ); port->params[3] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); port->params[4] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); port->info.params = port->params; port->info.n_params = 5; spa_list_init(&port->empty); port = GET_OUT_PORT(this, 0); port->direction = SPA_DIRECTION_OUTPUT; port->id = 0; port->info_all = SPA_PORT_CHANGE_MASK_FLAGS | SPA_PORT_CHANGE_MASK_PARAMS; port->info = SPA_PORT_INFO_INIT(); port->info.flags = SPA_PORT_FLAG_NO_REF; port->params[0] = SPA_PARAM_INFO(SPA_PARAM_EnumFormat, SPA_PARAM_INFO_READ); port->params[1] = SPA_PARAM_INFO(SPA_PARAM_Meta, SPA_PARAM_INFO_READ); port->params[2] = SPA_PARAM_INFO(SPA_PARAM_IO, SPA_PARAM_INFO_READ); port->params[3] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); port->params[4] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); port->info.params = port->params; port->info.n_params = 5; spa_list_init(&port->empty); return 0; } static const struct spa_interface_info impl_interfaces[] = { {SPA_TYPE_INTERFACE_Node,}, }; static int impl_enum_interface_info(const struct spa_handle_factory *factory, const struct spa_interface_info **info, uint32_t *index) { spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(info != NULL, -EINVAL); spa_return_val_if_fail(index != NULL, -EINVAL); switch (*index) { case 0: *info = &impl_interfaces[*index]; break; default: return 0; } (*index)++; return 1; } const struct spa_handle_factory spa_volume_factory = { SPA_VERSION_HANDLE_FACTORY, "volume", NULL, impl_get_size, impl_init, impl_enum_interface_info, }; pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/vulkan/000077500000000000000000000000001511204443500242425ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/vulkan/dmabuf.h000066400000000000000000000045771511204443500256660ustar00rootroot00000000000000// Copyright (c) 2023 The wlroots contributors // // Permission is hereby granted, free of charge, to any person obtaining a copy of // this software and associated documentation files (the "Software"), to deal in // the Software without restriction, including without limitation the rights to // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies // of the Software, and to permit persons to whom the Software is furnished to do // so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. // // Obtained from https://gitlab.freedesktop.org/wlroots/wlroots/ /* SPDX-FileCopyrightText: Copyright © 2023 The wlroots contributors */ /* SPDX-License-Identifier: MIT */ #ifndef RENDER_DMABUF_H #define RENDER_DMABUF_H #include #include #include "spa/support/log.h" // Copied from to avoid #ifdef soup #define DMA_BUF_SYNC_READ (1 << 0) #define DMA_BUF_SYNC_WRITE (2 << 0) #define DMA_BUF_SYNC_RW (DMA_BUF_SYNC_READ | DMA_BUF_SYNC_WRITE) /** * Check whether DMA-BUF import/export from/to sync_file is available. * * If this function returns true, dmabuf_import_sync_file() is supported. */ bool dmabuf_check_sync_file_import_export(struct spa_log *log); /** * Import a sync_file into a DMA-BUF with DMA_BUF_IOCTL_IMPORT_SYNC_FILE. * * This can be used to make explicit sync interoperate with implicit sync. */ bool dmabuf_import_sync_file(struct spa_log *log, int dmabuf_fd, uint32_t flags, int sync_file_fd); /** * Export a sync_file from a DMA-BUF with DMA_BUF_IOCTL_EXPORT_SYNC_FILE. * * The sync_file FD is returned on success, -1 is returned on error. * * This can be used to make explicit sync interoperate with implicit sync. */ int dmabuf_export_sync_file(struct spa_log *log, int dmabuf_fd, uint32_t flags); #endif pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/vulkan/dmabuf_fallback.c000066400000000000000000000034461511204443500274720ustar00rootroot00000000000000// Copyright (c) 2023 The wlroots contributors // // Permission is hereby granted, free of charge, to any person obtaining a copy of // this software and associated documentation files (the "Software"), to deal in // the Software without restriction, including without limitation the rights to // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies // of the Software, and to permit persons to whom the Software is furnished to do // so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. // // Obtained from https://gitlab.freedesktop.org/wlroots/wlroots/ /* SPDX-FileCopyrightText: Copyright © 2023 The wlroots contributors */ /* SPDX-License-Identifier: MIT */ #include #include bool dmabuf_check_sync_file_import_export(struct spa_log *log) { return false; } bool dmabuf_import_sync_file(struct spa_log *log, int dmabuf_fd, uint32_t flags, int sync_file_fd) { spa_log_error(log, "DMA-BUF sync_file import IOCTL not available on this system"); return false; } int dmabuf_export_sync_file(struct spa_log *log, int dmabuf_fd, uint32_t flags) { spa_log_error(log, "DMA-BUF sync_file export IOCTL not available on this system"); return false; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/vulkan/dmabuf_linux.c000066400000000000000000000065731511204443500270760ustar00rootroot00000000000000// Copyright (c) 2023 The wlroots contributors // // Permission is hereby granted, free of charge, to any person obtaining a copy of // this software and associated documentation files (the "Software"), to deal in // the Software without restriction, including without limitation the rights to // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies // of the Software, and to permit persons to whom the Software is furnished to do // so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. // // Obtained from https://gitlab.freedesktop.org/wlroots/wlroots/ /* SPDX-FileCopyrightText: Copyright © 2023 The wlroots contributors */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #include #include #include #include #include #include "dmabuf.h" bool dmabuf_check_sync_file_import_export(struct spa_log *log) { /* Unfortunately there's no better way to check the availability of the * IOCTL than to check the kernel version. See the discussion at: * https://lore.kernel.org/dri-devel/20220601161303.64797-1-contact@emersion.fr/ */ struct utsname utsname = {0}; if (uname(&utsname) != 0) { spa_log_warn(log, "uname failed"); return false; } if (strcmp(utsname.sysname, "Linux") != 0) { return false; } unsigned int major, minor, patch; if (sscanf(utsname.release, "%u.%u.%u", &major, &minor, &patch) != 3) return false; return KERNEL_VERSION(major, minor, patch) >= KERNEL_VERSION(5, 20, 0); } // TODO: drop these definitions once widespread #if !defined(DMA_BUF_IOCTL_IMPORT_SYNC_FILE) struct dma_buf_import_sync_file { __u32 flags; __s32 fd; }; #define DMA_BUF_IOCTL_IMPORT_SYNC_FILE _IOW(DMA_BUF_BASE, 3, struct dma_buf_import_sync_file) #endif #if !defined(DMA_BUF_IOCTL_EXPORT_SYNC_FILE) struct dma_buf_export_sync_file { __u32 flags; __s32 fd; }; #define DMA_BUF_IOCTL_EXPORT_SYNC_FILE _IOWR(DMA_BUF_BASE, 2, struct dma_buf_export_sync_file) #endif bool dmabuf_import_sync_file(struct spa_log *log, int dmabuf_fd, uint32_t flags, int sync_file_fd) { struct dma_buf_import_sync_file data = { .flags = flags, .fd = sync_file_fd, }; if (drmIoctl(dmabuf_fd, DMA_BUF_IOCTL_IMPORT_SYNC_FILE, &data) != 0) { spa_log_error(log, "drmIoctl(IMPORT_SYNC_FILE) failed with %d (%s)", errno, spa_strerror(-errno)); return false; } return true; } int dmabuf_export_sync_file(struct spa_log *log, int dmabuf_fd, uint32_t flags) { struct dma_buf_export_sync_file data = { .flags = flags, .fd = -1, }; if (drmIoctl(dmabuf_fd, DMA_BUF_IOCTL_EXPORT_SYNC_FILE, &data) != 0) { spa_log_error(log, "drmIoctl(EXPORT_SYNC_FILE) failed with %d (%s)", errno, spa_strerror(-errno)); return -1; } return data.fd; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/vulkan/meson.build000066400000000000000000000012171511204443500264050ustar00rootroot00000000000000spa_vulkan_sources = [ 'plugin.c', 'pixel-formats.c', 'vulkan-compute-filter.c', 'vulkan-compute-source.c', 'vulkan-compute-utils.c', 'vulkan-blit-filter.c', 'vulkan-blit-dsp-filter.c', 'vulkan-blit-utils.c', 'vulkan-utils.c', 'utils.c', ] drm = dependency('libdrm') if cc.has_header('linux/dma-buf.h') and target_machine.system() == 'linux' spa_vulkan_sources += files('dmabuf_linux.c') else spa_vulkan_sources += files('dmabuf_fallback.c') endif spa_vulkan = shared_library('spa-vulkan', spa_vulkan_sources, dependencies : [ spa_dep, vulkan_dep, mathlib, drm ], install : true, install_dir : spa_plugindir / 'vulkan') pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/vulkan/pixel-formats.c000066400000000000000000000013331511204443500272000ustar00rootroot00000000000000/* Spa */ /* SPDX-FileCopyrightText: Copyright © 2023 columbarius */ /* SPDX-License-Identifier: MIT */ #include "pixel-formats.h" #include #include struct pixel_info { uint32_t format; uint32_t bpp; } pixel_infos[] = { { SPA_VIDEO_FORMAT_RGBA_F32, 16 }, { SPA_VIDEO_FORMAT_BGRA, 4 }, { SPA_VIDEO_FORMAT_RGBA, 4 }, { SPA_VIDEO_FORMAT_BGRx, 4 }, { SPA_VIDEO_FORMAT_RGBx, 4 }, { SPA_VIDEO_FORMAT_BGR, 3 }, { SPA_VIDEO_FORMAT_RGB, 3 }, }; bool get_pixel_format_info(uint32_t format, struct pixel_format_info *info) { struct pixel_info *p; SPA_FOR_EACH_ELEMENT(pixel_infos, p) { if (p->format != format) continue; info->bpp = p->bpp; return true; } return false; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/vulkan/pixel-formats.h000066400000000000000000000004421511204443500272050ustar00rootroot00000000000000/* Spa */ /* SPDX-FileCopyrightText: Copyright © 2023 columbarius */ /* SPDX-License-Identifier: MIT */ #include #include struct pixel_format_info { uint32_t bpp; // bytes per pixel }; bool get_pixel_format_info(uint32_t format, struct pixel_format_info *info); pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/vulkan/plugin.c000066400000000000000000000020621511204443500257040ustar00rootroot00000000000000/* Spa vulkan plugin */ /* SPDX-FileCopyrightText: Copyright © 2019 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include extern const struct spa_handle_factory spa_vulkan_compute_filter_factory; extern const struct spa_handle_factory spa_vulkan_compute_source_factory; extern const struct spa_handle_factory spa_vulkan_blit_filter_factory; extern const struct spa_handle_factory spa_vulkan_blit_dsp_filter_factory; SPA_LOG_TOPIC_ENUM_DEFINE_REGISTERED; SPA_EXPORT int spa_handle_factory_enum(const struct spa_handle_factory **factory, uint32_t *index) { spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(index != NULL, -EINVAL); switch (*index) { case 0: *factory = &spa_vulkan_compute_source_factory; break; case 1: *factory = &spa_vulkan_compute_filter_factory; break; case 2: *factory = &spa_vulkan_blit_filter_factory; break; case 3: *factory = &spa_vulkan_blit_dsp_filter_factory; break; default: return 0; } (*index)++; return 1; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/vulkan/shaders/000077500000000000000000000000001511204443500256735ustar00rootroot00000000000000disk-intersection.comp000066400000000000000000000110431511204443500321310ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/vulkan/shaders// The MIT License // Copyright © 2013 Inigo Quilez // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // Other intersectors: // // Box: https://www.shadertoy.com/view/ld23DV // Triangle: https://www.shadertoy.com/view/MlGcDz // Capsule: https://www.shadertoy.com/view/Xt3SzX // Ellipsoid: https://www.shadertoy.com/view/MlsSzn // Sphere: https://www.shadertoy.com/view/4d2XWV // Capped Cylinder: https://www.shadertoy.com/view/4lcSRn // Disk: https://www.shadertoy.com/view/lsfGDB // Capped Cone: https://www.shadertoy.com/view/llcfRf // Rounded Box: https://www.shadertoy.com/view/WlSXRW // Rounded Cone: https://www.shadertoy.com/view/MlKfzm // Torus: https://www.shadertoy.com/view/4sBGDy // Sphere4: https://www.shadertoy.com/view/3tj3DW // Goursat: https://www.shadertoy.com/view/3lj3DW #define SC 3.0 #if 1 // // Elegant way to intersect a planar coordinate system (3x3 linear system) // vec3 intersectCoordSys(in vec3 o, in vec3 d, vec3 c, vec3 u, vec3 v) { vec3 q = o - c; return vec3(dot(cross(u, v), q), dot(cross(q, u), d), dot(cross(v, q), d)) / dot(cross(v, u), d); } #else // // Ugly (but faster) way to intersect a planar coordinate system: plane + projection // vec3 intersectCoordSys(in vec3 o, in vec3 d, vec3 c, vec3 u, vec3 v) { vec3 q = o - c; vec3 n = cross(u, v); float t = -dot(n, q) / dot(d, n); float r = dot(u, q + d * t); float s = dot(v, q + d * t); return vec3(t, s, r); } #endif vec3 hash3(float n) { return fract(sin(vec3(n, n + 1.0, n + 2.0)) * vec3(43758.5453123, 12578.1459123, 19642.3490423)); } vec3 shade(in vec4 res) { float ra = length(res.yz); float an = atan(res.y, res.z) + 8.0 * iTime; float pa = sin(3.0 * an); vec3 cola = 0.5 + 0.5 * sin((res.w / 64.0) * 3.5 + vec3(0.0, 1.0, 2.0)); vec3 col = vec3(0.0); col += cola * 0.4 * (1.0 - smoothstep(0.90, 1.00, ra)); col += cola * 1.0 * (1.0 - smoothstep(0.00, 0.03, abs(ra - 0.8))) * (0.5 + 0.5 * pa); col += cola * 1.0 * (1.0 - smoothstep(0.00, 0.20, abs(ra - 0.8))) * (0.5 + 0.5 * pa); col += cola * 0.5 * (1.0 - smoothstep(0.05, 0.10, abs(ra - 0.5))) * (0.5 + 0.5 * pa); col += cola * 0.7 * (1.0 - smoothstep(0.00, 0.30, abs(ra - 0.5))) * (0.5 + 0.5 * pa); return col * 0.3; } vec3 render(in vec3 ro, in vec3 rd) { // raytrace vec3 col = vec3(0.0); for (int i = 0; i < 64; i++) { // position disk vec3 r = 2.5 * (-1.0 + 2.0 * hash3(float (i))); r *= SC; // orientate disk vec3 u = normalize(r.zxy); vec3 v = normalize(cross(u, vec3(0.0, 1.0, 0.0))); // intersect coord sys vec3 tmp = intersectCoordSys(ro, rd, r, u, v); tmp /= SC; if (dot(tmp.yz, tmp.yz) < 1.0 && tmp.x > 0.0) { // shade col += shade(vec4(tmp, float (i))); } } return col; } void mainImage(out vec4 fragColor, in vec2 fragCoord) { vec2 q = fragCoord.xy / iResolution.xy; vec2 p = -1.0 + 2.0 * q; p.x *= iResolution.x / iResolution.y; // camera vec3 ro = 2.0 * vec3(cos(0.5 * iTime * 1.1), 0.0, sin(0.5 * iTime * 1.1)); vec3 ta = vec3(0.0, 0.0, 0.0); // camera matrix vec3 ww = normalize(ta - ro); vec3 uu = normalize(cross(ww, vec3(0.0, 1.0, 0.0))); vec3 vv = normalize(cross(uu, ww)); // create view ray vec3 rd = normalize(p.x * uu + p.y * vv + 1.0 * ww); vec3 col = render(ro * SC, rd); fragColor = vec4(col, 1.0); } void mainVR(out vec4 fragColor, in vec2 fragCoord, in vec3 fragRayOri, in vec3 fragRayDir) { vec3 col = render(fragRayOri + vec3(0.0, 0.0, 0.0), fragRayDir); fragColor = vec4(col, 1.0); } filter-color.comp000066400000000000000000000012571511204443500311020ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/vulkan/shadersvoid mainImage( out vec4 fragColor, in vec2 fragCoord ) { vec2 p = fragCoord.xy/iResolution.xy; vec4 col = texture(iChannel0, p); //Desaturate if(p.x<.25) { col = vec4( (col.r+col.g+col.b)/3. ); } //Invert else if (p.x<.5) { col = vec4(1.) - texture(iChannel0, p); } //Chromatic aberration else if (p.x<.75) { vec2 offset = vec2(.01,.0); col.r = texture(iChannel0, p+offset.xy).r; col.g = texture(iChannel0, p ).g; col.b = texture(iChannel0, p+offset.yx).b; } //Color switching else { col.rgb = texture(iChannel0, p).brg; } //Line if( mod(abs(p.x+.5/iResolution.y),.25)<0.5/iResolution.y ) col = vec4(1.); fragColor = col; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/vulkan/shaders/filter.comp000066400000000000000000000021251511204443500300400ustar00rootroot00000000000000#version 450 #extension GL_ARB_separate_shader_objects : enable #define WORKGROUP_SIZE 32 layout (local_size_x = WORKGROUP_SIZE, local_size_y = WORKGROUP_SIZE, local_size_z = 1 ) in; layout(rgba32f, set = 0, binding = 0) uniform image2D resultImage; layout(set = 0, binding = 1) uniform sampler2D iChannel0; layout( push_constant ) uniform Constants { float time; int frame; int width; int height; } PushConstant; float iTime; int iFrame; vec3 iResolution; vec4 iMouse; void mainImage( out vec4 fragColor, in vec2 fragCoord ); void main() { iTime = PushConstant.time; iFrame = PushConstant.frame; iResolution = vec3(float(PushConstant.width), float(PushConstant.height), 0.0); iMouse = vec4(0.0, 0.0, 0.0, 0.0); vec2 coord = vec2(float(gl_GlobalInvocationID.x), iResolution.y - float(gl_GlobalInvocationID.y)); vec4 outColor; if(coord.x >= iResolution.x || coord.y >= iResolution.y) return; mainImage(outColor, coord); imageStore(resultImage, ivec2(gl_GlobalInvocationID.xy), outColor); } //#include "smearcam.comp" #include "filter-color.comp" //#include "filter-ripple.comp" pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/vulkan/shaders/filter.spv000066400000000000000000000114241511204443500277140ustar00rootroot00000000000000#  GLSL.std.450main4  GL_ARB_separate_shader_objects GL_GOOGLE_cpp_style_line_directiveGL_GOOGLE_include_directivemainmainImage(vf4;vf2; fragColor fragCoordiTimeConstantstimeframewidthheightPushConstantiFrame"iResolution.iMouse0coord4gl_GlobalInvocationIDUoutColorVparamWparam]resultImageepjcolniChannel0offsetH#H#H#H# GG4 G]"G]!Gn"Gn!G !      !  ;   ; +   ;+    ! ;!"+#+'++ -;-.,/++++1 21 32;34+15 61+1:B C [ \[;\]_1b klk ml;mn+t>+1}+@@+?+?,+@?+ #<, ++1 ,2:6; 0;U;V; WA=>A=>A$#=%$o&%A('=)(o*)P ,&*+>",>./A6745=187p98A;":=<;A6=4:=1>=p?>@<?P A9@>0AACD05=EDAF"5=GFBHEGBIHKIJKJACL0:=MLAN":=ONBPMOKKBQHPJSQRSRS= X0>WX9YVW=ZV>UZ=[^]=2`4O_a``|bca=dUc^cd86 7 7 ; e;j; = f = g"O hgg ifh>ei=lon= peXqop+>jqACre5=srBustwuvvACxj5=yxACzj:={z|y{AC~j}=~|P>jwACe5=B=ln= eX+>jACe5=B>=ln= e=  X+QACj5>=ln= eX+QACj:>=ln= e= O  X+QACj}>=ln= eX+O ACj5Q>ACj:Q>ACj}Q>wwACe5=A":= tA":=B>j=j> 8pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/vulkan/shaders/main.comp000066400000000000000000000023631511204443500275030ustar00rootroot00000000000000#version 450 #extension GL_ARB_separate_shader_objects : enable #define WORKGROUP_SIZE 32 layout (local_size_x = WORKGROUP_SIZE, local_size_y = WORKGROUP_SIZE, local_size_z = 1 ) in; layout(rgba32f, binding = 0) uniform image2D resultImage; layout( push_constant ) uniform Constants { float time; int frame; int width; int height; } PushConstant; float iTime; int iFrame; vec3 iResolution; vec4 iMouse; void mainImage( out vec4 fragColor, in vec2 fragCoord ); void main() { iTime = PushConstant.time; iFrame = PushConstant.frame; iResolution = vec3(float(PushConstant.width), float(PushConstant.height), 0.0); iMouse = vec4(0.0, 0.0, 0.0, 0.0); vec2 coord = vec2(float(gl_GlobalInvocationID.x), iResolution.y - float(gl_GlobalInvocationID.y)); vec4 outColor; if(coord.x >= iResolution.x || coord.y >= iResolution.y) return; mainImage(outColor, coord); imageStore(resultImage, ivec2(gl_GlobalInvocationID.xy), outColor); } //#include "plasma-globe.comp" //#include "mandelbrot-distance.comp" #include "disk-intersection.comp" //#include "ring-twister.comp" //#include "gears.comp" //#include "protean-clouds.comp" //#include "flame.comp" //#include "shader.comp" //#include "raymarching-primitives.comp" //#include "3d-primitives.comp" pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/vulkan/shaders/main.spv000066400000000000000000000233741511204443500273620ustar00rootroot00000000000000#  GLSL.std.450mainK  GL_ARB_separate_shader_objects GL_GOOGLE_cpp_style_line_directiveGL_GOOGLE_include_directivemain intersectCoordSys(vf3;vf3;vf3;vf3;vf3; o d c uvhash3(f1;nshade(vf4;resrender(vf3;vf3;rord&mainImage(vf4;vf2;$fragColor%fragCoord)iTime+Constants+time+frame+width+height-PushConstant3iFrame9iResolutionEiMouseGcoordKgl_GlobalInvocationIDkoutColorlparammparamsresultImage{qraanpacolacolcoli'r,param4u8v=tmp>param@paramBparamDparamFparamaparamjqop}rotawwuuvvrdcolparamparamH+#H+#H+#H+# G+GK Gs"Gs!G !  !  ! !!! "!!#" (;()* +*** , +;,- +*. /  2*;23+*4 5 * 8;89+*:+*>+B D;DE,FBBBBH IH JI;JK+HL MH+HQY q rq;rsuHx*+?+@+*G+DF+tF,+H+A+@@+?+H+B+`@,B,BBB+>+fff?+<+L?+L>+L=+=+ 333?+ > *+*%@+( @+),:BB+̌?+H ,IQ6;"G;k;l;"mA/0-.=10>)1A56-4=*76>37A5;-:=*<;o=<A5?->=*@?oA@PC=AB>9C>EFAMNKL=HONpPOA(R9Q=SRAMTKQ=HUTpVUWSVP!XPW>GXAZGL=[ZA(\9L=]\Y^[]Y_^a_`a`AbGQ=cbA(d9Q=edYfceaaYg^f`ighihi=!nG>mn9o&lm=pl>kp=qts=IvKOuwvv|xyw=zkctyz86 7 7 7 7 7;{=| =} ~|}>{~= = D={={= D= =={ D= P== D= P867===P    867;;;;;=O! B>AQ=A= =)>=  >A=P  P>>== 1=>==  1B==>==  1B==>==  1==>=  =      1B  ==>= 8677 ;;;';,;4;8;=;>;@;B;D;F;a>>.!"##=*$Y&$%& ! =**o+*>,+9-,.-P/)))0/.10(>'1=2'32>'3=5'O655 7E6>47=94 ;D9: <E;>8<=?>>?=A>@A=C'>BC=E4>DE=G8>FG9 H>@BDF>=H=I=PJKIJ>=K=L=O!MLL=N=O!ONNPMOYQPSQRSRAT=L=UTYVUBSSYWQ VRYWXYX=Z==*[o\[Q]ZQ^ZQ_ZP`]^_\>a`9ba=cdcb>dYY""=*e*fe4>f!=gg86&#7$7"%';"j;"o;};;;;;;;;=!k%=l9O!mll!nkm>jn=!pj!qpP!r))!srq>osA(t9L=utA(v9Q=wvxuwAyoL=zy{zxA|oL>|{=~)~ =)  PB>}>==} E>= D: E>== D E>AoL==AoQ=== E>=}>=>9>=QQQP>$8pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/vulkan/utils.c000066400000000000000000000075421511204443500255560ustar00rootroot00000000000000/* Spa */ /* SPDX-FileCopyrightText: Copyright © 2023 columbarius */ /* SPDX-License-Identifier: MIT */ #include "utils.h" #include #include #include // This function enumerates the available formats in vulkan_state::formats, announcing all formats capable to support DmaBufs // first and then falling back to those supported with SHM buffers. bool find_EnumFormatInfo(struct vulkan_format_infos *fmtInfos, uint32_t index, uint32_t caps, uint32_t *fmt_idx, bool *has_modifier) { int64_t fmtIterator = 0; int64_t maxIterator = 0; if (caps & VULKAN_BUFFER_TYPE_CAP_SHM) maxIterator += fmtInfos->formatCount; if (caps & VULKAN_BUFFER_TYPE_CAP_DMABUF) maxIterator += fmtInfos->formatCount; // Count available formats until index underflows, while fmtIterator indexes the current format. // Iterate twice over formats first time with modifiers, second time without if both caps are supported. while (index < (uint32_t)-1 && fmtIterator < maxIterator) { const struct vulkan_format_info *f_info = &fmtInfos->infos[fmtIterator%fmtInfos->formatCount]; if (caps & VULKAN_BUFFER_TYPE_CAP_DMABUF && fmtIterator < fmtInfos->formatCount) { // First round, check for modifiers if (f_info->modifierCount > 0) { index--; } } else if (caps & VULKAN_BUFFER_TYPE_CAP_SHM) { // Second round, every format should be supported. index--; } fmtIterator++; } if (index != (uint32_t)-1) { // No more formats available return false; } // Undo end of loop increment fmtIterator--; *fmt_idx = fmtIterator%fmtInfos->formatCount; // Loop finished in first round *has_modifier = caps & VULKAN_BUFFER_TYPE_CAP_DMABUF && fmtIterator < fmtInfos->formatCount; return true; } struct spa_pod *build_dsp_EnumFormat(const struct vulkan_format_info *fmt, bool with_modifiers, struct spa_pod_builder *builder) { struct spa_pod_frame f[2]; uint32_t i, c; spa_pod_builder_push_object(builder, &f[0], SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat); spa_pod_builder_add(builder, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video), 0); spa_pod_builder_add(builder, SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_dsp), 0); spa_pod_builder_add(builder, SPA_FORMAT_VIDEO_format, SPA_POD_Id(fmt->spa_format), 0); if (with_modifiers && fmt->modifierCount > 0) { spa_pod_builder_prop(builder, SPA_FORMAT_VIDEO_modifier, SPA_POD_PROP_FLAG_MANDATORY | SPA_POD_PROP_FLAG_DONT_FIXATE); spa_pod_builder_push_choice(builder, &f[1], SPA_CHOICE_Enum, 0); for (i = 0, c = 0; i < fmt->modifierCount; i++) { spa_pod_builder_long(builder, fmt->infos[i].props.drmFormatModifier); if (c++ == 0) spa_pod_builder_long(builder, fmt->infos[i].props.drmFormatModifier); } spa_pod_builder_pop(builder, &f[1]); } return spa_pod_builder_pop(builder, &f[0]); } struct spa_pod *build_raw_EnumFormat(const struct vulkan_format_info *fmt, bool with_modifiers, struct spa_pod_builder *builder) { struct spa_pod_frame f[2]; uint32_t i, c; spa_pod_builder_push_object(builder, &f[0], SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat); spa_pod_builder_add(builder, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video), 0); spa_pod_builder_add(builder, SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), 0); spa_pod_builder_add(builder, SPA_FORMAT_VIDEO_format, SPA_POD_Id(fmt->spa_format), 0); if (with_modifiers && fmt->modifierCount > 0) { spa_pod_builder_prop(builder, SPA_FORMAT_VIDEO_modifier, SPA_POD_PROP_FLAG_MANDATORY | SPA_POD_PROP_FLAG_DONT_FIXATE); spa_pod_builder_push_choice(builder, &f[1], SPA_CHOICE_Enum, 0); for (i = 0, c = 0; i < fmt->modifierCount; i++) { spa_pod_builder_long(builder, fmt->infos[i].props.drmFormatModifier); if (c++ == 0) spa_pod_builder_long(builder, fmt->infos[i].props.drmFormatModifier); } spa_pod_builder_pop(builder, &f[1]); } return spa_pod_builder_pop(builder, &f[0]); } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/vulkan/utils.h000066400000000000000000000010551511204443500255540ustar00rootroot00000000000000/* Spa */ /* SPDX-FileCopyrightText: Copyright © 2023 columbarius */ /* SPDX-License-Identifier: MIT */ #include "vulkan-types.h" #include "spa/pod/builder.h" bool find_EnumFormatInfo(struct vulkan_format_infos *fmtInfos, uint32_t index, uint32_t caps, uint32_t *fmt_idx, bool *has_modifier); struct spa_pod *build_dsp_EnumFormat(const struct vulkan_format_info *fmt, bool with_modifiers, struct spa_pod_builder *builder); struct spa_pod *build_raw_EnumFormat(const struct vulkan_format_info *fmt, bool with_modifiers, struct spa_pod_builder *builder); pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/vulkan/vulkan-blit-dsp-filter.c000066400000000000000000000622221511204443500307110ustar00rootroot00000000000000/* Spa */ /* SPDX-FileCopyrightText: Copyright © 2019 Wim Taymans */ /* SPDX-FileCopyrightText: Copyright © 2023 columbarius */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "pixel-formats.h" #include "vulkan-blit-utils.h" #undef SPA_LOG_TOPIC_DEFAULT #define SPA_LOG_TOPIC_DEFAULT &log_topic SPA_LOG_TOPIC_DEFINE_STATIC(log_topic, "spa.vulkan.blit-dsp-filter"); struct buffer { uint32_t id; #define BUFFER_FLAG_OUT (1<<0) uint32_t flags; struct spa_buffer *outbuf; struct spa_meta_header *h; struct spa_list link; }; struct port { uint64_t info_all; struct spa_port_info info; enum spa_direction direction; #define IDX_EnumFormat 0 #define IDX_Meta 1 #define IDX_IO 2 #define IDX_Format 3 #define IDX_Buffer 4 #define N_PORT_PARAMS 5 struct spa_param_info params[N_PORT_PARAMS]; struct spa_io_buffers *io; bool have_format; struct spa_video_info current_format; struct buffer buffers[MAX_BUFFERS]; uint32_t n_buffers; struct spa_list empty; struct spa_list ready; uint32_t stream_id; }; struct impl { struct spa_handle handle; struct spa_node node; struct spa_log *log; struct spa_io_position *position; uint64_t info_all; struct spa_node_info info; #define IDX_PropInfo 0 #define IDX_Props 1 #define N_NODE_PARAMS 2 struct spa_param_info params[N_NODE_PARAMS]; struct spa_hook_list hooks; struct spa_callbacks callbacks; // Synchronization between main and data thread atomic_bool started; pthread_rwlock_t renderlock; struct vulkan_blit_state state; struct vulkan_pass pass; struct port port[2]; }; #define CHECK_PORT(this,d,p) ((p) < 1) static int lock_init(struct impl *this) { return pthread_rwlock_init(&this->renderlock, NULL); } static void lock_destroy(struct impl *this) { pthread_rwlock_destroy(&this->renderlock); } static int lock_renderer(struct impl *this) { spa_log_info(this->log, "Lock renderer"); return pthread_rwlock_wrlock(&this->renderlock); } static int unlock_renderer(struct impl *this) { spa_log_info(this->log, "Unlock renderer"); return pthread_rwlock_unlock(&this->renderlock); } static int impl_node_enum_params(void *object, int seq, uint32_t id, uint32_t start, uint32_t num, const struct spa_pod *filter) { struct impl *this = object; struct spa_pod *param; struct spa_pod_builder b = { 0 }; uint8_t buffer[1024]; struct spa_result_node_params result; uint32_t count = 0; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(num != 0, -EINVAL); result.id = id; result.next = start; next: result.index = result.next++; spa_pod_builder_init(&b, buffer, sizeof(buffer)); switch (id) { default: return -ENOENT; } if (spa_pod_filter(&b, &result.param, param, filter) < 0) goto next; spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); if (++count != num) goto next; return 0; } static int impl_node_set_io(void *object, uint32_t id, void *data, size_t size) { struct impl *this = object; spa_return_val_if_fail(this != NULL, -EINVAL); switch (id) { case SPA_IO_Position: if (size > 0 && size < sizeof(struct spa_io_position)) return -EINVAL; this->position = data; break; default: return -ENOENT; } return 0; } static int impl_node_set_param(void *object, uint32_t id, uint32_t flags, const struct spa_pod *param) { struct impl *this = object; spa_return_val_if_fail(this != NULL, -EINVAL); switch (id) { default: return -ENOENT; } return 0; } static inline void reuse_buffer(struct impl *this, struct port *port, uint32_t id) { struct buffer *b = &port->buffers[id]; if (SPA_FLAG_IS_SET(b->flags, BUFFER_FLAG_OUT)) { spa_log_debug(this->log, "%p: reuse buffer %d", this, id); SPA_FLAG_CLEAR(b->flags, BUFFER_FLAG_OUT); spa_list_append(&port->empty, &b->link); } } static int impl_node_send_command(void *object, const struct spa_command *command) { struct impl *this = object; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(command != NULL, -EINVAL); switch (SPA_NODE_COMMAND_ID(command)) { case SPA_NODE_COMMAND_Start: if (this->started) return 0; this->started = true; spa_vulkan_blit_start(&this->state); // The main thread needs to lock the renderer before changing its state break; case SPA_NODE_COMMAND_Suspend: case SPA_NODE_COMMAND_Pause: if (!this->started) return 0; lock_renderer(this); spa_vulkan_blit_stop(&this->state); this->started = false; unlock_renderer(this); // Locking the renderer from the renderer is no longer required break; default: return -ENOTSUP; } return 0; } static const struct spa_dict_item node_info_items[] = { { SPA_KEY_MEDIA_CLASS, "Video/Filter" }, }; static void emit_node_info(struct impl *this, bool full) { uint64_t old = full ? this->info.change_mask : 0; if (full) this->info.change_mask = this->info_all; if (this->info.change_mask) { this->info.props = &SPA_DICT_INIT_ARRAY(node_info_items); spa_node_emit_info(&this->hooks, &this->info); this->info.change_mask = old; } } static void emit_port_info(struct impl *this, struct port *port, bool full) { uint64_t old = full ? port->info.change_mask : 0; if (full) port->info.change_mask = port->info_all; if (port->info.change_mask) { struct spa_dict_item items[1]; items[0] = SPA_DICT_ITEM_INIT(SPA_KEY_FORMAT_DSP, "32 bit float RGBA video"); port->info.props = &SPA_DICT_INIT(items, 1); spa_node_emit_port_info(&this->hooks, port->direction, 0, &port->info); port->info.change_mask = old; } } static int impl_node_add_listener(void *object, struct spa_hook *listener, const struct spa_node_events *events, void *data) { struct impl *this = object; struct spa_hook_list save; spa_return_val_if_fail(this != NULL, -EINVAL); spa_hook_list_isolate(&this->hooks, &save, listener, events, data); emit_node_info(this, true); emit_port_info(this, &this->port[0], true); emit_port_info(this, &this->port[1], true); spa_hook_list_join(&this->hooks, &save); return 0; } static int impl_node_set_callbacks(void *object, const struct spa_node_callbacks *callbacks, void *data) { struct impl *this = object; spa_return_val_if_fail(this != NULL, -EINVAL); this->callbacks = SPA_CALLBACKS_INIT(callbacks, data); return 0; } static int impl_node_add_port(void *object, enum spa_direction direction, uint32_t port_id, const struct spa_dict *props) { return -ENOTSUP; } static int impl_node_remove_port(void *object, enum spa_direction direction, uint32_t port_id) { return -ENOTSUP; } static bool port_has_fixated_format(struct port *p) { if (!p->have_format) return false; return p->current_format.info.dsp.flags & SPA_VIDEO_FLAG_MODIFIER && p->current_format.info.dsp.flags ^ SPA_VIDEO_FLAG_MODIFIER_FIXATION_REQUIRED; } static int port_enum_formats(void *object, enum spa_direction direction, uint32_t port_id, uint32_t index, const struct spa_pod *filter, struct spa_pod **param, struct spa_pod_builder *builder) { struct impl *this = object; if (port_has_fixated_format(&this->port[port_id])) { if (index == 0) { spa_log_info(this->log, "enum_formats fixated format idx: %d, format %d, has_modifier 1", index, this->port[port_id].current_format.info.dsp.format); *param = spa_format_video_dsp_build(builder, SPA_PARAM_EnumFormat, &this->port[port_id].current_format.info.dsp); return 1; } return spa_vulkan_blit_enumerate_formats(&this->state, index-1, spa_vulkan_blit_get_buffer_caps(&this->state, direction), param, builder); } else { return spa_vulkan_blit_enumerate_formats(&this->state, index, spa_vulkan_blit_get_buffer_caps(&this->state, direction), param, builder); } } static int port_get_buffer_props(struct impl *this, struct port *port, uint32_t *blocks, uint32_t *size, uint32_t *stride, bool *is_dmabuf) { if (this->position == NULL) return -EIO; spa_log_debug(this->log, "%p: %dx%d stride %d", this, this->position->video.size.width, this->position->video.size.height, this->position->video.stride); if (port->current_format.info.dsp.flags & SPA_VIDEO_FLAG_MODIFIER) { *is_dmabuf = true; struct vulkan_modifier_info *mod_info = spa_vulkan_blit_get_modifier_info(&this->state, &port->current_format); *blocks = mod_info->props.drmFormatModifierPlaneCount; } else { *is_dmabuf = false; *blocks = 1; *size = this->position->video.stride * this->position->video.size.height; *stride = this->position->video.stride; } return 0; } static int impl_node_port_enum_params(void *object, int seq, enum spa_direction direction, uint32_t port_id, uint32_t id, uint32_t start, uint32_t num, const struct spa_pod *filter) { struct impl *this = object; struct port *port; struct spa_pod_builder b = { 0 }; uint8_t buffer[1024]; struct spa_pod *param; struct spa_result_node_params result; uint32_t count = 0; int res; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(num != 0, -EINVAL); spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); port = &this->port[direction]; result.id = id; result.next = start; next: result.index = result.next++; spa_pod_builder_init(&b, buffer, sizeof(buffer)); switch (id) { case SPA_PARAM_EnumFormat: if ((res = port_enum_formats(this, direction, port_id, result.index, filter, ¶m, &b)) <= 0) return res; break; case SPA_PARAM_Format: if (!port->have_format) return -EIO; if (result.index > 0) return 0; param = spa_format_video_dsp_build(&b, id, &port->current_format.info.dsp); break; case SPA_PARAM_Buffers: { if (!port->have_format) return -EIO; if (result.index > 0) return 0; int ret; uint32_t blocks, size, stride; bool is_dmabuf; if ((ret = port_get_buffer_props(this, port, &blocks, &size, &stride, &is_dmabuf)) < 0) return ret; if (is_dmabuf) { param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamBuffers, id, SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(2, 1, MAX_BUFFERS), SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(blocks), SPA_PARAM_BUFFERS_dataType, SPA_POD_CHOICE_FLAGS_Int(1<hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); if (++count != num) goto next; return 0; } static int clear_buffers(struct impl *this, struct port *port) { if (port->n_buffers > 0) { spa_log_debug(this->log, "%p: clear buffers", this); lock_renderer(this); spa_vulkan_blit_use_buffers(&this->state, &this->state.streams[port->stream_id], 0, &port->current_format, 0, NULL); spa_vulkan_blit_clear_pass(&this->state, &this->pass); unlock_renderer(this); port->n_buffers = 0; spa_list_init(&port->empty); spa_list_init(&port->ready); } return 0; } static int port_set_dsp_format(struct impl *this, struct port *port, uint32_t flags, struct spa_video_info *info, bool *has_modifier, bool *modifier_fixed, const struct spa_pod *format) { if (spa_format_video_dsp_parse(format, &info->info.dsp) < 0) return -EINVAL; if (info->info.dsp.format != SPA_VIDEO_FORMAT_DSP_F32) return -EINVAL; this->state.streams[port->stream_id].dim.width = this->position->video.size.width; this->state.streams[port->stream_id].dim.height = this->position->video.size.height; this->state.streams[port->stream_id].bpp = 16; *has_modifier = SPA_FLAG_IS_SET(info->info.dsp.flags, SPA_VIDEO_FLAG_MODIFIER); // fixate modifier if (port->direction == SPA_DIRECTION_OUTPUT && info->info.dsp.flags & SPA_VIDEO_FLAG_MODIFIER && info->info.dsp.flags & SPA_VIDEO_FLAG_MODIFIER_FIXATION_REQUIRED) { const struct spa_pod_prop *mod_prop; if ((mod_prop = spa_pod_find_prop(format, NULL, SPA_FORMAT_VIDEO_modifier)) == NULL) return -EINVAL; const struct spa_pod *mod_pod = &mod_prop->value; uint32_t modifierCount = SPA_POD_CHOICE_N_VALUES(mod_pod); uint64_t *modifiers = SPA_POD_CHOICE_VALUES(mod_pod); if (modifierCount <= 1) return -EINVAL; // SPA_POD_CHOICE carries the "preferred" value at position 0 modifierCount -= 1; modifiers++; uint64_t fixed_modifier; if (spa_vulkan_blit_fixate_modifier(&this->state, &this->state.streams[port->stream_id], info, modifierCount, modifiers, &fixed_modifier) != 0) return -EINVAL; spa_log_info(this->log, "modifier fixated %"PRIu64, fixed_modifier); info->info.dsp.modifier = fixed_modifier; info->info.dsp.flags &= ~SPA_VIDEO_FLAG_MODIFIER_FIXATION_REQUIRED; *modifier_fixed = true; } return 0; } static int port_set_format(struct impl *this, struct port *port, uint32_t flags, const struct spa_pod *format) { int res; if (format == NULL) { port->have_format = false; clear_buffers(this, port); } else { struct spa_video_info info = { 0 }; if ((res = spa_format_parse(format, &info.media_type, &info.media_subtype)) < 0) return res; if (info.media_type != SPA_MEDIA_TYPE_video) return -EINVAL; bool has_modifier = false; bool modifier_fixed = false; if ((res = port_set_dsp_format(this, port, flags, &info, &has_modifier, &modifier_fixed, format)) < 0) return res; if (has_modifier) { SPA_FLAG_SET(port->info.flags, SPA_PORT_FLAG_CAN_ALLOC_BUFFERS); } else { SPA_FLAG_CLEAR(port->info.flags, SPA_PORT_FLAG_CAN_ALLOC_BUFFERS); } port->info.change_mask |= SPA_PORT_CHANGE_MASK_FLAGS; port->current_format = info; port->have_format = true; if (modifier_fixed) { port->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; port->params[IDX_EnumFormat].flags ^= SPA_PARAM_INFO_SERIAL; emit_port_info(this, port, false); return 0; } } port->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; if (port->have_format) { port->params[IDX_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_READWRITE); port->params[IDX_Buffer] = SPA_PARAM_INFO(SPA_PARAM_Buffers, SPA_PARAM_INFO_READ); } else { port->params[IDX_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); port->params[IDX_Buffer] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); } emit_port_info(this, port, false); return 0; } static int impl_node_port_set_param(void *object, enum spa_direction direction, uint32_t port_id, uint32_t id, uint32_t flags, const struct spa_pod *param) { struct impl *this = object; struct port *port; int res; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(CHECK_PORT(node, direction, port_id), -EINVAL); port = &this->port[direction]; switch (id) { case SPA_PARAM_Format: res = port_set_format(this, port, flags, param); break; default: return -ENOENT; } return res; } static int impl_node_port_use_buffers(void *object, enum spa_direction direction, uint32_t port_id, uint32_t flags, struct spa_buffer **buffers, uint32_t n_buffers) { struct impl *this = object; struct port *port; uint32_t i; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); port = &this->port[direction]; clear_buffers(this, port); if (n_buffers > 0 && !port->have_format) return -EIO; if (n_buffers > MAX_BUFFERS) return -ENOSPC; lock_renderer(this); for (i = 0; i < n_buffers; i++) { struct buffer *b; b = &port->buffers[i]; b->id = i; b->outbuf = buffers[i]; b->flags = 0; b->h = spa_buffer_find_meta_data(buffers[i], SPA_META_Header, sizeof(*b->h)); spa_log_info(this->log, "%p: %d:%d add buffer %p", port, direction, port_id, b); spa_list_append(&port->empty, &b->link); } spa_vulkan_blit_use_buffers(&this->state, &this->state.streams[port->stream_id], flags, &port->current_format, n_buffers, buffers); port->n_buffers = n_buffers; if (n_buffers > 0) spa_vulkan_blit_init_pass(&this->state, &this->pass); unlock_renderer(this); return 0; } static int impl_node_port_set_io(void *object, enum spa_direction direction, uint32_t port_id, uint32_t id, void *data, size_t size) { struct impl *this = object; struct port *port; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); port = &this->port[direction]; switch (id) { case SPA_IO_Buffers: port->io = data; break; default: return -ENOENT; } return 0; } static int impl_node_port_reuse_buffer(void *object, uint32_t port_id, uint32_t buffer_id) { struct impl *this = object; struct port *port; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(port_id == 0, -EINVAL); port = &this->port[SPA_DIRECTION_OUTPUT]; spa_return_val_if_fail(buffer_id < port->n_buffers, -EINVAL); reuse_buffer(this, port, buffer_id); return 0; } static int impl_node_process(void *object) { struct impl *this = object; struct port *inport, *outport; struct spa_io_buffers *inio, *outio; struct buffer *b; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(this->started, -EINVAL); inport = &this->port[SPA_DIRECTION_INPUT]; if ((inio = inport->io) == NULL) return -EIO; if (inio->status != SPA_STATUS_HAVE_DATA) return inio->status; if (inio->buffer_id >= inport->n_buffers) { inio->status = -EINVAL; return -EINVAL; } outport = &this->port[SPA_DIRECTION_OUTPUT]; if ((outio = outport->io) == NULL) return -EIO; if (outio->status == SPA_STATUS_HAVE_DATA) return SPA_STATUS_HAVE_DATA; if (outio->buffer_id < outport->n_buffers) { reuse_buffer(this, outport, outio->buffer_id); outio->buffer_id = SPA_ID_INVALID; } if (spa_list_is_empty(&outport->empty)) { spa_log_debug(this->log, "%p: out of buffers", this); return -EPIPE; } if (pthread_rwlock_tryrdlock(&this->renderlock) < 0) { return -EBUSY; } b = &inport->buffers[inio->buffer_id]; this->pass.in_stream_id = SPA_DIRECTION_INPUT; this->pass.in_buffer_id = b->id; inio->status = SPA_STATUS_NEED_DATA; b = spa_list_first(&outport->empty, struct buffer, link); spa_list_remove(&b->link); SPA_FLAG_SET(b->flags, BUFFER_FLAG_OUT); this->pass.out_stream_id = SPA_DIRECTION_OUTPUT; this->pass.out_buffer_id = b->id; spa_log_debug(this->log, "filter into %d", b->id); spa_vulkan_blit_process(&this->state, &this->pass); spa_vulkan_blit_reset_pass(&this->state, &this->pass); b->outbuf->datas[0].chunk->offset = 0; b->outbuf->datas[0].chunk->size = b->outbuf->datas[0].maxsize; b->outbuf->datas[0].chunk->stride = this->position->video.stride; outio->buffer_id = b->id; outio->status = SPA_STATUS_HAVE_DATA; pthread_rwlock_unlock(&this->renderlock); return SPA_STATUS_NEED_DATA | SPA_STATUS_HAVE_DATA; } static const struct spa_node_methods impl_node = { SPA_VERSION_NODE_METHODS, .add_listener = impl_node_add_listener, .set_callbacks = impl_node_set_callbacks, .enum_params = impl_node_enum_params, .set_param = impl_node_set_param, .set_io = impl_node_set_io, .send_command = impl_node_send_command, .add_port = impl_node_add_port, .remove_port = impl_node_remove_port, .port_enum_params = impl_node_port_enum_params, .port_set_param = impl_node_port_set_param, .port_use_buffers = impl_node_port_use_buffers, .port_set_io = impl_node_port_set_io, .port_reuse_buffer = impl_node_port_reuse_buffer, .process = impl_node_process, }; static int impl_get_interface(struct spa_handle *handle, const char *type, void **interface) { struct impl *this; spa_return_val_if_fail(handle != NULL, -EINVAL); spa_return_val_if_fail(interface != NULL, -EINVAL); this = (struct impl *) handle; if (spa_streq(type, SPA_TYPE_INTERFACE_Node)) *interface = &this->node; else return -ENOENT; return 0; } static int impl_clear(struct spa_handle *handle) { struct impl *this; spa_return_val_if_fail(handle != NULL, -EINVAL); this = (struct impl *) handle; spa_vulkan_blit_unprepare(&this->state); spa_vulkan_blit_deinit(&this->state); lock_destroy(this); return 0; } static size_t impl_get_size(const struct spa_handle_factory *factory, const struct spa_dict *params) { return sizeof(struct impl); } static int impl_init(const struct spa_handle_factory *factory, struct spa_handle *handle, const struct spa_dict *info, const struct spa_support *support, uint32_t n_support) { struct impl *this; struct port *port; spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(handle != NULL, -EINVAL); handle->get_interface = impl_get_interface; handle->clear = impl_clear; this = (struct impl *) handle; this->log = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log); this->state.log = this->log; spa_hook_list_init(&this->hooks); this->node.iface = SPA_INTERFACE_INIT( SPA_TYPE_INTERFACE_Node, SPA_VERSION_NODE, &impl_node, this); this->info_all = SPA_NODE_CHANGE_MASK_FLAGS | SPA_NODE_CHANGE_MASK_PROPS | SPA_NODE_CHANGE_MASK_PARAMS; this->info = SPA_NODE_INFO_INIT(); this->info.max_output_ports = 1; this->info.max_input_ports = 1; this->info.flags = SPA_NODE_FLAG_RT; this->params[IDX_PropInfo] = SPA_PARAM_INFO(SPA_PARAM_PropInfo, SPA_PARAM_INFO_READ); this->params[IDX_Props] = SPA_PARAM_INFO(SPA_PARAM_Props, SPA_PARAM_INFO_READWRITE); this->info.params = this->params; this->info.n_params = N_NODE_PARAMS; lock_init(this); port = &this->port[SPA_DIRECTION_INPUT]; port->stream_id = SPA_DIRECTION_INPUT; port->direction = SPA_DIRECTION_INPUT; port->info_all = SPA_PORT_CHANGE_MASK_FLAGS | SPA_PORT_CHANGE_MASK_PARAMS | SPA_PORT_CHANGE_MASK_PROPS; port->info = SPA_PORT_INFO_INIT(); port->info.flags = SPA_PORT_FLAG_NO_REF; port->params[IDX_EnumFormat] = SPA_PARAM_INFO(SPA_PARAM_EnumFormat, SPA_PARAM_INFO_READ); port->params[IDX_Meta] = SPA_PARAM_INFO(SPA_PARAM_Meta, SPA_PARAM_INFO_READ); port->params[IDX_IO] = SPA_PARAM_INFO(SPA_PARAM_IO, SPA_PARAM_INFO_READ); port->params[IDX_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); port->params[IDX_Buffer] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); port->info.params = port->params; port->info.n_params = N_PORT_PARAMS; spa_vulkan_blit_init_stream(&this->state, &this->state.streams[port->stream_id], SPA_DIRECTION_INPUT, NULL); spa_list_init(&port->empty); spa_list_init(&port->ready); port = &this->port[SPA_DIRECTION_OUTPUT]; port->stream_id = SPA_DIRECTION_OUTPUT; port->direction = SPA_DIRECTION_OUTPUT; port->info_all = SPA_PORT_CHANGE_MASK_FLAGS | SPA_PORT_CHANGE_MASK_PARAMS | SPA_PORT_CHANGE_MASK_PROPS; port->info = SPA_PORT_INFO_INIT(); port->info.flags = SPA_PORT_FLAG_NO_REF | SPA_PORT_FLAG_CAN_ALLOC_BUFFERS; port->params[IDX_EnumFormat] = SPA_PARAM_INFO(SPA_PARAM_EnumFormat, SPA_PARAM_INFO_READ); port->params[IDX_Meta] = SPA_PARAM_INFO(SPA_PARAM_Meta, SPA_PARAM_INFO_READ); port->params[IDX_IO] = SPA_PARAM_INFO(SPA_PARAM_IO, SPA_PARAM_INFO_READ); port->params[IDX_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); port->params[IDX_Buffer] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); port->info.params = port->params; port->info.n_params = N_PORT_PARAMS; spa_list_init(&port->empty); spa_list_init(&port->ready); spa_vulkan_blit_init_stream(&this->state, &this->state.streams[port->stream_id], SPA_DIRECTION_OUTPUT, NULL); this->state.n_streams = 2; spa_vulkan_blit_init(&this->state); spa_vulkan_blit_prepare(&this->state); return 0; } static const struct spa_interface_info impl_interfaces[] = { {SPA_TYPE_INTERFACE_Node,}, }; static int impl_enum_interface_info(const struct spa_handle_factory *factory, const struct spa_interface_info **info, uint32_t *index) { spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(info != NULL, -EINVAL); spa_return_val_if_fail(index != NULL, -EINVAL); switch (*index) { case 0: *info = &impl_interfaces[*index]; break; default: return 0; } (*index)++; return 1; } static const struct spa_dict_item info_items[] = { { SPA_KEY_FACTORY_AUTHOR, "Columbarius " }, { SPA_KEY_FACTORY_DESCRIPTION, "Convert video frames using a vulkan blit" }, }; static const struct spa_dict info = SPA_DICT_INIT_ARRAY(info_items); const struct spa_handle_factory spa_vulkan_blit_dsp_filter_factory = { SPA_VERSION_HANDLE_FACTORY, SPA_NAME_API_VULKAN_BLIT_DSP_FILTER, &info, impl_get_size, impl_init, impl_enum_interface_info, }; pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/vulkan/vulkan-blit-filter.c000066400000000000000000000722101511204443500301230ustar00rootroot00000000000000/* Spa */ /* SPDX-FileCopyrightText: Copyright © 2019 Wim Taymans */ /* SPDX-FileCopyrightText: Copyright © 2023 columbarius */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "pixel-formats.h" #include "vulkan-blit-utils.h" #undef SPA_LOG_TOPIC_DEFAULT #define SPA_LOG_TOPIC_DEFAULT &log_topic SPA_LOG_TOPIC_DEFINE_STATIC(log_topic, "spa.vulkan.blit-filter"); struct buffer { uint32_t id; #define BUFFER_FLAG_OUT (1<<0) uint32_t flags; struct spa_buffer *outbuf; struct spa_meta_header *h; struct spa_list link; }; struct port { uint64_t info_all; struct spa_port_info info; enum spa_direction direction; #define IDX_EnumFormat 0 #define IDX_Meta 1 #define IDX_IO 2 #define IDX_Format 3 #define IDX_Buffer 4 #define N_PORT_PARAMS 5 struct spa_param_info params[N_PORT_PARAMS]; struct spa_io_buffers *io; bool have_format; struct spa_video_info current_format; struct buffer buffers[MAX_BUFFERS]; uint32_t n_buffers; struct spa_list empty; struct spa_list ready; uint32_t stream_id; }; struct impl { struct spa_handle handle; struct spa_node node; struct spa_log *log; struct spa_io_position *position; uint64_t info_all; struct spa_node_info info; #define IDX_PropInfo 0 #define IDX_Props 1 #define N_NODE_PARAMS 2 struct spa_param_info params[N_NODE_PARAMS]; struct spa_hook_list hooks; struct spa_callbacks callbacks; // Synchronization between main and data thread atomic_bool started; pthread_rwlock_t renderlock; struct vulkan_blit_state state; struct vulkan_pass pass; struct port port[2]; }; #define CHECK_PORT(this,d,p) ((p) < 1) static int lock_init(struct impl *this) { return pthread_rwlock_init(&this->renderlock, NULL); } static void lock_destroy(struct impl *this) { pthread_rwlock_destroy(&this->renderlock); } static int lock_renderer(struct impl *this) { spa_log_info(this->log, "Lock renderer"); return pthread_rwlock_wrlock(&this->renderlock); } static int unlock_renderer(struct impl *this) { spa_log_info(this->log, "Unlock renderer"); return pthread_rwlock_unlock(&this->renderlock); } static int impl_node_enum_params(void *object, int seq, uint32_t id, uint32_t start, uint32_t num, const struct spa_pod *filter) { struct impl *this = object; struct spa_pod *param; struct spa_pod_builder b = { 0 }; uint8_t buffer[1024]; struct spa_result_node_params result; uint32_t count = 0; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(num != 0, -EINVAL); result.id = id; result.next = start; next: result.index = result.next++; spa_pod_builder_init(&b, buffer, sizeof(buffer)); switch (id) { default: return -ENOENT; } if (spa_pod_filter(&b, &result.param, param, filter) < 0) goto next; spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); if (++count != num) goto next; return 0; } static int impl_node_set_io(void *object, uint32_t id, void *data, size_t size) { struct impl *this = object; spa_return_val_if_fail(this != NULL, -EINVAL); switch (id) { case SPA_IO_Position: if (size > 0 && size < sizeof(struct spa_io_position)) return -EINVAL; this->position = data; break; default: return -ENOENT; } return 0; } static int impl_node_set_param(void *object, uint32_t id, uint32_t flags, const struct spa_pod *param) { struct impl *this = object; spa_return_val_if_fail(this != NULL, -EINVAL); switch (id) { default: return -ENOENT; } return 0; } static inline void reuse_buffer(struct impl *this, struct port *port, uint32_t id) { struct buffer *b = &port->buffers[id]; if (SPA_FLAG_IS_SET(b->flags, BUFFER_FLAG_OUT)) { spa_log_debug(this->log, "%p: reuse buffer %d", this, id); SPA_FLAG_CLEAR(b->flags, BUFFER_FLAG_OUT); spa_list_append(&port->empty, &b->link); } } static int impl_node_send_command(void *object, const struct spa_command *command) { struct impl *this = object; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(command != NULL, -EINVAL); switch (SPA_NODE_COMMAND_ID(command)) { case SPA_NODE_COMMAND_Start: if (this->started) return 0; this->started = true; spa_vulkan_blit_start(&this->state); // The main thread needs to lock the renderer before changing its state break; case SPA_NODE_COMMAND_Suspend: case SPA_NODE_COMMAND_Pause: if (!this->started) return 0; lock_renderer(this); spa_vulkan_blit_stop(&this->state); this->started = false; unlock_renderer(this); // Locking the renderer from the renderer is no longer required break; default: return -ENOTSUP; } return 0; } static const struct spa_dict_item node_info_items[] = { { SPA_KEY_MEDIA_CLASS, "Video/Filter" }, }; static void emit_node_info(struct impl *this, bool full) { uint64_t old = full ? this->info.change_mask : 0; if (full) this->info.change_mask = this->info_all; if (this->info.change_mask) { this->info.props = &SPA_DICT_INIT_ARRAY(node_info_items); spa_node_emit_info(&this->hooks, &this->info); this->info.change_mask = old; } } static void emit_port_info(struct impl *this, struct port *port, bool full) { uint64_t old = full ? port->info.change_mask : 0; if (full) port->info.change_mask = port->info_all; if (port->info.change_mask) { struct spa_dict_item items[1]; items[0] = SPA_DICT_ITEM_INIT(SPA_KEY_FORMAT_DSP, "32 bit float RGBA video"); port->info.props = &SPA_DICT_INIT(items, 1); spa_node_emit_port_info(&this->hooks, port->direction, 0, &port->info); port->info.change_mask = old; } } static int impl_node_add_listener(void *object, struct spa_hook *listener, const struct spa_node_events *events, void *data) { struct impl *this = object; struct spa_hook_list save; spa_return_val_if_fail(this != NULL, -EINVAL); spa_hook_list_isolate(&this->hooks, &save, listener, events, data); emit_node_info(this, true); emit_port_info(this, &this->port[0], true); emit_port_info(this, &this->port[1], true); spa_hook_list_join(&this->hooks, &save); return 0; } static int impl_node_set_callbacks(void *object, const struct spa_node_callbacks *callbacks, void *data) { struct impl *this = object; spa_return_val_if_fail(this != NULL, -EINVAL); this->callbacks = SPA_CALLBACKS_INIT(callbacks, data); return 0; } static int impl_node_add_port(void *object, enum spa_direction direction, uint32_t port_id, const struct spa_dict *props) { return -ENOTSUP; } static int impl_node_remove_port(void *object, enum spa_direction direction, uint32_t port_id) { return -ENOTSUP; } static bool port_has_fixated_format(struct port *p) { if (!p->have_format) return false; switch (p->current_format.media_subtype) { case SPA_MEDIA_SUBTYPE_dsp: return p->current_format.info.dsp.flags & SPA_VIDEO_FLAG_MODIFIER && p->current_format.info.dsp.flags ^ SPA_VIDEO_FLAG_MODIFIER_FIXATION_REQUIRED; case SPA_MEDIA_SUBTYPE_raw: return p->current_format.info.raw.flags & SPA_VIDEO_FLAG_MODIFIER && p->current_format.info.raw.flags ^ SPA_VIDEO_FLAG_MODIFIER_FIXATION_REQUIRED; } return false; } static int port_enum_formats(void *object, enum spa_direction direction, uint32_t port_id, uint32_t index, const struct spa_pod *filter, struct spa_pod **param, struct spa_pod_builder *builder) { struct impl *this = object; if (port_has_fixated_format(&this->port[port_id])) { if (index == 0) { if (this->port[port_id].current_format.media_subtype == SPA_MEDIA_SUBTYPE_dsp) { spa_log_info(this->log, "enum_formats fixated format idx: %d, format %d, has_modifier 1", index, this->port[port_id].current_format.info.dsp.format); *param = spa_format_video_dsp_build(builder, SPA_PARAM_EnumFormat, &this->port[port_id].current_format.info.dsp); } else { spa_log_info(this->log, "enum_formats fixated format idx: %d, format %d, has_modifier 1", index, this->port[port_id].current_format.info.raw.format); *param = spa_format_video_raw_build(builder, SPA_PARAM_EnumFormat, &this->port[port_id].current_format.info.raw); } return 1; } return spa_vulkan_blit_enumerate_formats(&this->state, index-1, spa_vulkan_blit_get_buffer_caps(&this->state, direction), param, builder); } else { return spa_vulkan_blit_enumerate_formats(&this->state, index, spa_vulkan_blit_get_buffer_caps(&this->state, direction), param, builder); } } static int port_get_buffer_props(struct impl *this, struct port *port, uint32_t *blocks, uint32_t *size, uint32_t *stride, bool *is_dmabuf) { if (port->current_format.media_subtype == SPA_MEDIA_SUBTYPE_dsp) { if (this->position == NULL) return -EIO; spa_log_debug(this->log, "%p: %dx%d stride %d", this, this->position->video.size.width, this->position->video.size.height, this->position->video.stride); if (port->current_format.info.dsp.flags & SPA_VIDEO_FLAG_MODIFIER) { *is_dmabuf = true; struct vulkan_modifier_info *mod_info = spa_vulkan_blit_get_modifier_info(&this->state, &port->current_format); *blocks = mod_info->props.drmFormatModifierPlaneCount; } else { *is_dmabuf = false; *blocks = 1; *size = this->position->video.stride * this->position->video.size.height; *stride = this->position->video.stride; } return 0; } else if (port->current_format.media_subtype == SPA_MEDIA_SUBTYPE_raw) { spa_log_debug(this->log, "%p: %dx%d", this, port->current_format.info.raw.size.width, port->current_format.info.raw.size.height); if (port->current_format.info.raw.flags & SPA_VIDEO_FLAG_MODIFIER) { *is_dmabuf = true; struct vulkan_modifier_info *mod_info = spa_vulkan_blit_get_modifier_info(&this->state, &port->current_format); *blocks = mod_info->props.drmFormatModifierPlaneCount; } else { struct pixel_format_info pInfo = {0}; if (!get_pixel_format_info(port->current_format.info.raw.format, &pInfo)) return -EINVAL; uint32_t buffer_stride = pInfo.bpp * port->current_format.info.raw.size.width; *is_dmabuf = false; *blocks = 1; *size = buffer_stride * port->current_format.info.raw.size.height; *stride = buffer_stride; } return 0; } else { return -EINVAL; } } static int impl_node_port_enum_params(void *object, int seq, enum spa_direction direction, uint32_t port_id, uint32_t id, uint32_t start, uint32_t num, const struct spa_pod *filter) { struct impl *this = object; struct port *port; struct spa_pod_builder b = { 0 }; uint8_t buffer[1024]; struct spa_pod *param; struct spa_result_node_params result; uint32_t count = 0; int res; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(num != 0, -EINVAL); spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); port = &this->port[direction]; result.id = id; result.next = start; next: result.index = result.next++; spa_pod_builder_init(&b, buffer, sizeof(buffer)); switch (id) { case SPA_PARAM_EnumFormat: if ((res = port_enum_formats(this, direction, port_id, result.index, filter, ¶m, &b)) <= 0) return res; break; case SPA_PARAM_Format: if (!port->have_format) return -EIO; if (result.index > 0) return 0; if (port->current_format.media_subtype == SPA_MEDIA_SUBTYPE_dsp) { param = spa_format_video_dsp_build(&b, id, &port->current_format.info.dsp); } else if (port->current_format.media_subtype == SPA_MEDIA_SUBTYPE_raw) { param = spa_format_video_raw_build(&b, id, &port->current_format.info.raw); } else { return -EINVAL; } break; case SPA_PARAM_Buffers: { if (!port->have_format) return -EIO; if (result.index > 0) return 0; int ret; uint32_t blocks, size, stride; bool is_dmabuf; if ((ret = port_get_buffer_props(this, port, &blocks, &size, &stride, &is_dmabuf)) < 0) return ret; if (is_dmabuf) { param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamBuffers, id, SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(2, 1, MAX_BUFFERS), SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(blocks), SPA_PARAM_BUFFERS_dataType, SPA_POD_CHOICE_FLAGS_Int(1<hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); if (++count != num) goto next; return 0; } static int clear_buffers(struct impl *this, struct port *port) { if (port->n_buffers > 0) { spa_log_debug(this->log, "%p: clear buffers", this); lock_renderer(this); spa_vulkan_blit_use_buffers(&this->state, &this->state.streams[port->stream_id], 0, &port->current_format, 0, NULL); spa_vulkan_blit_clear_pass(&this->state, &this->pass); unlock_renderer(this); port->n_buffers = 0; spa_list_init(&port->empty); spa_list_init(&port->ready); } return 0; } static int port_set_dsp_format(struct impl *this, struct port *port, uint32_t flags, struct spa_video_info *info, bool *has_modifier, bool *modifier_fixed, const struct spa_pod *format) { if (spa_format_video_dsp_parse(format, &info->info.dsp) < 0) return -EINVAL; if (info->info.dsp.format != SPA_VIDEO_FORMAT_DSP_F32) return -EINVAL; this->state.streams[port->stream_id].dim.width = this->position->video.size.width; this->state.streams[port->stream_id].dim.height = this->position->video.size.height; this->state.streams[port->stream_id].bpp = 16; *has_modifier = SPA_FLAG_IS_SET(info->info.dsp.flags, SPA_VIDEO_FLAG_MODIFIER); // fixate modifier if (port->direction == SPA_DIRECTION_OUTPUT && info->info.dsp.flags & SPA_VIDEO_FLAG_MODIFIER && info->info.dsp.flags & SPA_VIDEO_FLAG_MODIFIER_FIXATION_REQUIRED) { const struct spa_pod_prop *mod_prop; if ((mod_prop = spa_pod_find_prop(format, NULL, SPA_FORMAT_VIDEO_modifier)) == NULL) return -EINVAL; const struct spa_pod *mod_pod = &mod_prop->value; uint32_t modifierCount = SPA_POD_CHOICE_N_VALUES(mod_pod); uint64_t *modifiers = SPA_POD_CHOICE_VALUES(mod_pod); if (modifierCount <= 1) return -EINVAL; // SPA_POD_CHOICE carries the "preferred" value at position 0 modifierCount -= 1; modifiers++; uint64_t fixed_modifier; if (spa_vulkan_blit_fixate_modifier(&this->state, &this->state.streams[port->stream_id], info, modifierCount, modifiers, &fixed_modifier) != 0) return -EINVAL; spa_log_info(this->log, "modifier fixated %"PRIu64, fixed_modifier); info->info.dsp.modifier = fixed_modifier; info->info.dsp.flags &= ~SPA_VIDEO_FLAG_MODIFIER_FIXATION_REQUIRED; *modifier_fixed = true; } return 0; } static int port_set_raw_format(struct impl *this, struct port *port, uint32_t flags, struct spa_video_info *info, bool *has_modifier, bool *modifier_fixed, const struct spa_pod *format) { if (spa_format_video_raw_parse(format, &info->info.raw) < 0) return -EINVAL; struct pixel_format_info pInfo; if (!get_pixel_format_info(info->info.raw.format, &pInfo)) return -EINVAL; this->state.streams[port->stream_id].dim = info->info.raw.size; this->state.streams[port->stream_id].bpp = pInfo.bpp; *has_modifier = SPA_FLAG_IS_SET(info->info.raw.flags, SPA_VIDEO_FLAG_MODIFIER); // fixate modifier if (port->direction == SPA_DIRECTION_OUTPUT && info->info.raw.flags & SPA_VIDEO_FLAG_MODIFIER && info->info.raw.flags & SPA_VIDEO_FLAG_MODIFIER_FIXATION_REQUIRED) { const struct spa_pod_prop *mod_prop; if ((mod_prop = spa_pod_find_prop(format, NULL, SPA_FORMAT_VIDEO_modifier)) == NULL) return -EINVAL; const struct spa_pod *mod_pod = &mod_prop->value; uint32_t modifierCount = SPA_POD_CHOICE_N_VALUES(mod_pod); uint64_t *modifiers = SPA_POD_CHOICE_VALUES(mod_pod); if (modifierCount <= 1) return -EINVAL; // SPA_POD_CHOICE carries the "preferred" value at position 0 modifierCount -= 1; modifiers++; uint64_t fixed_modifier; if (spa_vulkan_blit_fixate_modifier(&this->state, &this->state.streams[port->stream_id], info, modifierCount, modifiers, &fixed_modifier) != 0) return -EINVAL; spa_log_info(this->log, "modifier fixated %"PRIu64, fixed_modifier); info->info.raw.modifier = fixed_modifier; info->info.raw.flags &= ~SPA_VIDEO_FLAG_MODIFIER_FIXATION_REQUIRED; *modifier_fixed = true; } return 0; } static int port_set_format(struct impl *this, struct port *port, uint32_t flags, const struct spa_pod *format) { int res; if (format == NULL) { port->have_format = false; clear_buffers(this, port); } else { struct spa_video_info info = { 0 }; if ((res = spa_format_parse(format, &info.media_type, &info.media_subtype)) < 0) return res; if (info.media_type != SPA_MEDIA_TYPE_video) return -EINVAL; bool has_modifier = false; bool modifier_fixed = false; if (info.media_subtype == SPA_MEDIA_SUBTYPE_dsp) { if ((res = port_set_dsp_format(this, port, flags, &info, &has_modifier, &modifier_fixed, format)) < 0) return res; } else if (info.media_subtype == SPA_MEDIA_SUBTYPE_raw) { if ((res = port_set_raw_format(this, port, flags, &info, &has_modifier, &modifier_fixed, format)) < 0) return res; } else { return -EINVAL; } if (has_modifier) { SPA_FLAG_SET(port->info.flags, SPA_PORT_FLAG_CAN_ALLOC_BUFFERS); } else { SPA_FLAG_CLEAR(port->info.flags, SPA_PORT_FLAG_CAN_ALLOC_BUFFERS); } port->info.change_mask |= SPA_PORT_CHANGE_MASK_FLAGS; port->current_format = info; port->have_format = true; if (modifier_fixed) { port->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; port->params[IDX_EnumFormat].flags ^= SPA_PARAM_INFO_SERIAL; emit_port_info(this, port, false); return 0; } } port->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; if (port->have_format) { port->params[IDX_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_READWRITE); port->params[IDX_Buffer] = SPA_PARAM_INFO(SPA_PARAM_Buffers, SPA_PARAM_INFO_READ); } else { port->params[IDX_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); port->params[IDX_Buffer] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); } emit_port_info(this, port, false); return 0; } static int impl_node_port_set_param(void *object, enum spa_direction direction, uint32_t port_id, uint32_t id, uint32_t flags, const struct spa_pod *param) { struct impl *this = object; struct port *port; int res; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(CHECK_PORT(node, direction, port_id), -EINVAL); port = &this->port[direction]; switch (id) { case SPA_PARAM_Format: res = port_set_format(this, port, flags, param); break; default: return -ENOENT; } return res; } static int impl_node_port_use_buffers(void *object, enum spa_direction direction, uint32_t port_id, uint32_t flags, struct spa_buffer **buffers, uint32_t n_buffers) { struct impl *this = object; struct port *port; uint32_t i; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); port = &this->port[direction]; clear_buffers(this, port); if (n_buffers > 0 && !port->have_format) return -EIO; if (n_buffers > MAX_BUFFERS) return -ENOSPC; lock_renderer(this); for (i = 0; i < n_buffers; i++) { struct buffer *b; b = &port->buffers[i]; b->id = i; b->outbuf = buffers[i]; b->flags = 0; b->h = spa_buffer_find_meta_data(buffers[i], SPA_META_Header, sizeof(*b->h)); spa_log_info(this->log, "%p: %d:%d add buffer %p", port, direction, port_id, b); spa_list_append(&port->empty, &b->link); } spa_vulkan_blit_use_buffers(&this->state, &this->state.streams[port->stream_id], flags, &port->current_format, n_buffers, buffers); port->n_buffers = n_buffers; if (n_buffers > 0) spa_vulkan_blit_init_pass(&this->state, &this->pass); unlock_renderer(this); return 0; } static int impl_node_port_set_io(void *object, enum spa_direction direction, uint32_t port_id, uint32_t id, void *data, size_t size) { struct impl *this = object; struct port *port; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); port = &this->port[direction]; switch (id) { case SPA_IO_Buffers: port->io = data; break; default: return -ENOENT; } return 0; } static int impl_node_port_reuse_buffer(void *object, uint32_t port_id, uint32_t buffer_id) { struct impl *this = object; struct port *port; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(port_id == 0, -EINVAL); port = &this->port[SPA_DIRECTION_OUTPUT]; spa_return_val_if_fail(buffer_id < port->n_buffers, -EINVAL); reuse_buffer(this, port, buffer_id); return 0; } static int impl_node_process(void *object) { struct impl *this = object; struct port *inport, *outport; struct spa_io_buffers *inio, *outio; struct buffer *b; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(this->started, -EINVAL); inport = &this->port[SPA_DIRECTION_INPUT]; if ((inio = inport->io) == NULL) return -EIO; if (inio->status != SPA_STATUS_HAVE_DATA) return inio->status; if (inio->buffer_id >= inport->n_buffers) { inio->status = -EINVAL; return -EINVAL; } outport = &this->port[SPA_DIRECTION_OUTPUT]; if ((outio = outport->io) == NULL) return -EIO; if (outio->status == SPA_STATUS_HAVE_DATA) return SPA_STATUS_HAVE_DATA; if (outio->buffer_id < outport->n_buffers) { reuse_buffer(this, outport, outio->buffer_id); outio->buffer_id = SPA_ID_INVALID; } if (spa_list_is_empty(&outport->empty)) { spa_log_debug(this->log, "%p: out of buffers", this); return -EPIPE; } if (pthread_rwlock_tryrdlock(&this->renderlock) < 0) { return -EBUSY; } b = &inport->buffers[inio->buffer_id]; this->pass.in_stream_id = SPA_DIRECTION_INPUT; this->pass.in_buffer_id = b->id; inio->status = SPA_STATUS_NEED_DATA; b = spa_list_first(&outport->empty, struct buffer, link); spa_list_remove(&b->link); SPA_FLAG_SET(b->flags, BUFFER_FLAG_OUT); this->pass.out_stream_id = SPA_DIRECTION_OUTPUT; this->pass.out_buffer_id = b->id; spa_log_debug(this->log, "filter into %d", b->id); spa_vulkan_blit_process(&this->state, &this->pass); spa_vulkan_blit_reset_pass(&this->state, &this->pass); b->outbuf->datas[0].chunk->offset = 0; b->outbuf->datas[0].chunk->size = b->outbuf->datas[0].maxsize; if (outport->current_format.media_subtype == SPA_MEDIA_SUBTYPE_raw) { b->outbuf->datas[0].chunk->stride = this->state.streams[outport->stream_id].bpp * outport->current_format.info.raw.size.width; } else { b->outbuf->datas[0].chunk->stride = this->position->video.stride; } outio->buffer_id = b->id; outio->status = SPA_STATUS_HAVE_DATA; pthread_rwlock_unlock(&this->renderlock); return SPA_STATUS_NEED_DATA | SPA_STATUS_HAVE_DATA; } static const struct spa_node_methods impl_node = { SPA_VERSION_NODE_METHODS, .add_listener = impl_node_add_listener, .set_callbacks = impl_node_set_callbacks, .enum_params = impl_node_enum_params, .set_param = impl_node_set_param, .set_io = impl_node_set_io, .send_command = impl_node_send_command, .add_port = impl_node_add_port, .remove_port = impl_node_remove_port, .port_enum_params = impl_node_port_enum_params, .port_set_param = impl_node_port_set_param, .port_use_buffers = impl_node_port_use_buffers, .port_set_io = impl_node_port_set_io, .port_reuse_buffer = impl_node_port_reuse_buffer, .process = impl_node_process, }; static int impl_get_interface(struct spa_handle *handle, const char *type, void **interface) { struct impl *this; spa_return_val_if_fail(handle != NULL, -EINVAL); spa_return_val_if_fail(interface != NULL, -EINVAL); this = (struct impl *) handle; if (spa_streq(type, SPA_TYPE_INTERFACE_Node)) *interface = &this->node; else return -ENOENT; return 0; } static int impl_clear(struct spa_handle *handle) { struct impl *this; spa_return_val_if_fail(handle != NULL, -EINVAL); this = (struct impl *) handle; spa_vulkan_blit_unprepare(&this->state); spa_vulkan_blit_deinit(&this->state); lock_destroy(this); return 0; } static size_t impl_get_size(const struct spa_handle_factory *factory, const struct spa_dict *params) { return sizeof(struct impl); } static int impl_init(const struct spa_handle_factory *factory, struct spa_handle *handle, const struct spa_dict *info, const struct spa_support *support, uint32_t n_support) { struct impl *this; struct port *port; spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(handle != NULL, -EINVAL); handle->get_interface = impl_get_interface; handle->clear = impl_clear; this = (struct impl *) handle; this->log = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log); this->state.log = this->log; spa_hook_list_init(&this->hooks); this->node.iface = SPA_INTERFACE_INIT( SPA_TYPE_INTERFACE_Node, SPA_VERSION_NODE, &impl_node, this); this->info_all = SPA_NODE_CHANGE_MASK_FLAGS | SPA_NODE_CHANGE_MASK_PROPS | SPA_NODE_CHANGE_MASK_PARAMS; this->info = SPA_NODE_INFO_INIT(); this->info.max_output_ports = 1; this->info.max_input_ports = 1; this->info.flags = SPA_NODE_FLAG_RT; this->params[IDX_PropInfo] = SPA_PARAM_INFO(SPA_PARAM_PropInfo, SPA_PARAM_INFO_READ); this->params[IDX_Props] = SPA_PARAM_INFO(SPA_PARAM_Props, SPA_PARAM_INFO_READWRITE); this->info.params = this->params; this->info.n_params = N_NODE_PARAMS; lock_init(this); port = &this->port[SPA_DIRECTION_INPUT]; port->stream_id = SPA_DIRECTION_INPUT; port->direction = SPA_DIRECTION_INPUT; port->info_all = SPA_PORT_CHANGE_MASK_FLAGS | SPA_PORT_CHANGE_MASK_PARAMS | SPA_PORT_CHANGE_MASK_PROPS; port->info = SPA_PORT_INFO_INIT(); port->info.flags = SPA_PORT_FLAG_NO_REF; port->params[IDX_EnumFormat] = SPA_PARAM_INFO(SPA_PARAM_EnumFormat, SPA_PARAM_INFO_READ); port->params[IDX_Meta] = SPA_PARAM_INFO(SPA_PARAM_Meta, SPA_PARAM_INFO_READ); port->params[IDX_IO] = SPA_PARAM_INFO(SPA_PARAM_IO, SPA_PARAM_INFO_READ); port->params[IDX_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); port->params[IDX_Buffer] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); port->info.params = port->params; port->info.n_params = N_PORT_PARAMS; spa_vulkan_blit_init_stream(&this->state, &this->state.streams[port->stream_id], SPA_DIRECTION_INPUT, NULL); spa_list_init(&port->empty); spa_list_init(&port->ready); port = &this->port[SPA_DIRECTION_OUTPUT]; port->stream_id = SPA_DIRECTION_OUTPUT; port->direction = SPA_DIRECTION_OUTPUT; port->info_all = SPA_PORT_CHANGE_MASK_FLAGS | SPA_PORT_CHANGE_MASK_PARAMS | SPA_PORT_CHANGE_MASK_PROPS; port->info = SPA_PORT_INFO_INIT(); port->info.flags = SPA_PORT_FLAG_NO_REF | SPA_PORT_FLAG_CAN_ALLOC_BUFFERS; port->params[IDX_EnumFormat] = SPA_PARAM_INFO(SPA_PARAM_EnumFormat, SPA_PARAM_INFO_READ); port->params[IDX_Meta] = SPA_PARAM_INFO(SPA_PARAM_Meta, SPA_PARAM_INFO_READ); port->params[IDX_IO] = SPA_PARAM_INFO(SPA_PARAM_IO, SPA_PARAM_INFO_READ); port->params[IDX_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); port->params[IDX_Buffer] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); port->info.params = port->params; port->info.n_params = N_PORT_PARAMS; spa_list_init(&port->empty); spa_list_init(&port->ready); spa_vulkan_blit_init_stream(&this->state, &this->state.streams[port->stream_id], SPA_DIRECTION_OUTPUT, NULL); this->state.n_streams = 2; spa_vulkan_blit_init(&this->state); spa_vulkan_blit_prepare(&this->state); return 0; } static const struct spa_interface_info impl_interfaces[] = { {SPA_TYPE_INTERFACE_Node,}, }; static int impl_enum_interface_info(const struct spa_handle_factory *factory, const struct spa_interface_info **info, uint32_t *index) { spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(info != NULL, -EINVAL); spa_return_val_if_fail(index != NULL, -EINVAL); switch (*index) { case 0: *info = &impl_interfaces[*index]; break; default: return 0; } (*index)++; return 1; } static const struct spa_dict_item info_items[] = { { SPA_KEY_FACTORY_AUTHOR, "Columbarius " }, { SPA_KEY_FACTORY_DESCRIPTION, "Convert video frames using a vulkan blit" }, }; static const struct spa_dict info = SPA_DICT_INIT_ARRAY(info_items); const struct spa_handle_factory spa_vulkan_blit_filter_factory = { SPA_VERSION_HANDLE_FACTORY, SPA_NAME_API_VULKAN_BLIT_FILTER, &info, impl_get_size, impl_init, impl_enum_interface_info, }; pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/vulkan/vulkan-blit-utils.c000066400000000000000000000516331511204443500300040ustar00rootroot00000000000000/* Spa */ /* SPDX-FileCopyrightText: Copyright © 2019 Wim Taymans */ /* SPDX-FileCopyrightText: Copyright © 2023 columbarius */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #include #include #include #include #if !defined(__FreeBSD__) && !defined(__MidnightBSD__) #include #endif #include #include #include #include #include #include #include #include #include #include "vulkan-blit-utils.h" #include "vulkan-utils.h" #include "utils.h" #define VULKAN_INSTANCE_FUNCTION(name) \ PFN_##name name = (PFN_##name)vkGetInstanceProcAddr(s->base.instance, #name) #define GET_BUFFER_ID_FROM_STREAM(s, pass) \ (s->direction == SPA_DIRECTION_INPUT ? pass->in_buffer_id : pass->out_buffer_id) static int runImportSHMBuffers(struct vulkan_blit_state *s, struct vulkan_pass *pass) { struct vulkan_stream *p = &s->streams[pass->in_stream_id]; if (p->buffer_type == SPA_DATA_MemPtr) { struct spa_buffer *spa_buf = p->spa_buffers[pass->in_buffer_id]; struct vulkan_write_pixels_info writeInfo = { .data = spa_buf->datas[0].data, .offset = 0, .stride = p->bpp * p->dim.width, .bytes_per_pixel = p->bpp, .size.width = p->dim.width, .size.height = p->dim.height, .copies = &pass->in_copy, }; CHECK(vulkan_write_pixels(&s->base, &writeInfo, &pass->in_staging_buffer)); } return 0; } static int runExportSHMBuffers(struct vulkan_blit_state *s, struct vulkan_pass *pass) { struct vulkan_stream *p = &s->streams[pass->out_stream_id]; if (p->buffer_type == SPA_DATA_MemPtr) { struct spa_buffer *spa_buf = p->spa_buffers[pass->out_buffer_id]; struct vulkan_read_pixels_info readInfo = { .data = spa_buf->datas[0].data, .offset = 0, .stride = p->bpp * p->dim.width, .bytes_per_pixel = p->bpp, .size.width = p->dim.width, .size.height = p->dim.height, }; CHECK(vulkan_read_pixels(&s->base, &readInfo, &p->buffers[pass->out_buffer_id])); } return 0; } static int runImportSync(struct vulkan_blit_state *s, struct vulkan_pass *pass) { int ret = 0; for (uint32_t i = 0; i < s->n_streams; i++) { struct vulkan_stream *p = &s->streams[i]; uint32_t current_buffer_id = GET_BUFFER_ID_FROM_STREAM(p, pass); struct vulkan_buffer *current_buffer = &p->buffers[current_buffer_id]; if (p->buffer_type != SPA_DATA_DmaBuf) continue; if (vulkan_buffer_import_implicit_syncfd(&s->base, current_buffer) >= 0) continue; if (vulkan_buffer_wait_dmabuf_fence(&s->base, current_buffer) < 0) { spa_log_warn(s->log, "Failed to wait for foreign buffer DMA-BUF fence"); ret = -1; } } return ret; } static int runExportSync(struct vulkan_blit_state *s, struct vulkan_pass *pass) { int ret = 0; for (uint32_t i = 0; i < s->n_streams; i++) { struct vulkan_stream *p = &s->streams[i]; if (p->buffer_type != SPA_DATA_DmaBuf) continue; if (!vulkan_sync_export_dmabuf(&s->base, &p->buffers[GET_BUFFER_ID_FROM_STREAM(p, pass)], pass->sync_fd)) { ret = -1; } } return ret; } static int runCommandBuffer(struct vulkan_blit_state *s, struct vulkan_pass *pass) { VULKAN_INSTANCE_FUNCTION(vkQueueSubmit2KHR); VULKAN_INSTANCE_FUNCTION(vkGetSemaphoreFdKHR); static const VkCommandBufferBeginInfo beginInfo = { .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT, }; VK_CHECK_RESULT(vkBeginCommandBuffer(pass->commandBuffer, &beginInfo)); uint32_t i; struct vulkan_stream *stream_input = &s->streams[pass->in_stream_id]; struct vulkan_stream *stream_output = &s->streams[pass->out_stream_id]; VkImage src_image = stream_input->buffers[pass->in_buffer_id].image; VkImage dst_image = stream_output->buffers[pass->out_buffer_id].image; if (stream_input->buffer_type == SPA_DATA_MemPtr) { vkCmdCopyBufferToImage(pass->commandBuffer, pass->in_staging_buffer.buffer, src_image, VK_IMAGE_LAYOUT_GENERAL, 1, &pass->in_copy); VkImageMemoryBarrier copy_barrier = { .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, .srcQueueFamilyIndex = s->base.queueFamilyIndex, .dstQueueFamilyIndex = s->base.queueFamilyIndex, .image = src_image, .oldLayout = VK_IMAGE_LAYOUT_GENERAL, .newLayout = VK_IMAGE_LAYOUT_GENERAL, .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT, .dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT, .subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, .subresourceRange.levelCount = 1, .subresourceRange.layerCount = 1, }; vkCmdPipelineBarrier(pass->commandBuffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, NULL, 0, NULL, 1, ©_barrier); } VkImageBlit imageBlitRegion = { .srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, .srcSubresource.layerCount = 1, .srcOffsets[0] = { .x = 0, .y = 0, .z = 0, }, .srcOffsets[1] = { .x = stream_input->dim.width, .y = stream_input->dim.height, .z = 1, }, .dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, .dstSubresource.layerCount = 1, .dstOffsets[1] = { .x = stream_output->dim.width, .y = stream_output->dim.height, .z = 1, } }; spa_log_trace_fp(s->log, "Blitting from (%p, %d, %d) %d,%dx%d,%d to (%p, %d, %d) %d,%dx%d,%d", stream_input, pass->in_buffer_id, stream_input->direction, 0, 0, stream_input->dim.width, stream_input->dim.height, stream_output, pass->out_buffer_id, stream_output->direction, 0, 0, stream_output->dim.width, stream_output->dim.height); vkCmdBlitImage(pass->commandBuffer, src_image, VK_IMAGE_LAYOUT_GENERAL, dst_image, VK_IMAGE_LAYOUT_GENERAL, 1, &imageBlitRegion, VK_FILTER_NEAREST); VkImageMemoryBarrier acquire_barrier[s->n_streams]; VkImageMemoryBarrier release_barrier[s->n_streams]; VkSemaphoreSubmitInfo semaphore_wait_info[s->n_streams]; uint32_t semaphore_wait_info_len = 0; VkSemaphoreSubmitInfo semaphore_signal_info[1]; uint32_t semaphore_signal_info_len = 0; for (i = 0; i < s->n_streams; i++) { struct vulkan_stream *p = &s->streams[i]; struct vulkan_buffer *current_buffer = &p->buffers[GET_BUFFER_ID_FROM_STREAM(p, pass)]; VkAccessFlags access_flags; VkAccessFlags release_flags; if (p->direction == SPA_DIRECTION_INPUT) { access_flags = p->buffer_type == SPA_DATA_DmaBuf ? VK_ACCESS_TRANSFER_READ_BIT : VK_ACCESS_TRANSFER_WRITE_BIT; release_flags = VK_ACCESS_TRANSFER_READ_BIT; } else { access_flags = VK_ACCESS_TRANSFER_WRITE_BIT; release_flags = VK_ACCESS_TRANSFER_WRITE_BIT; } acquire_barrier[i]= (VkImageMemoryBarrier) { .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, .srcQueueFamilyIndex = VK_QUEUE_FAMILY_FOREIGN_EXT, .dstQueueFamilyIndex = s->base.queueFamilyIndex, .image = current_buffer->image, .oldLayout = VK_IMAGE_LAYOUT_UNDEFINED, .newLayout = VK_IMAGE_LAYOUT_GENERAL, .srcAccessMask = 0, .dstAccessMask = access_flags, .subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, .subresourceRange.levelCount = 1, .subresourceRange.layerCount = 1, }; release_barrier[i]= (VkImageMemoryBarrier) { .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, .srcQueueFamilyIndex = s->base.queueFamilyIndex, .dstQueueFamilyIndex = VK_QUEUE_FAMILY_FOREIGN_EXT, .image = current_buffer->image, .oldLayout = VK_IMAGE_LAYOUT_GENERAL, .newLayout = VK_IMAGE_LAYOUT_GENERAL, .srcAccessMask = release_flags, .dstAccessMask = 0, .subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, .subresourceRange.levelCount = 1, .subresourceRange.layerCount = 1, }; if (current_buffer->foreign_semaphore != VK_NULL_HANDLE) { semaphore_wait_info[semaphore_wait_info_len++] = (VkSemaphoreSubmitInfo) { .sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO, .semaphore = current_buffer->foreign_semaphore, .stageMask = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, }; } } vkCmdPipelineBarrier(pass->commandBuffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_2_TRANSFER_BIT, 0, 0, NULL, 0, NULL, s->n_streams, acquire_barrier); vkCmdPipelineBarrier(pass->commandBuffer, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, 0, 0, NULL, 0, NULL, s->n_streams, release_barrier); VK_CHECK_RESULT(vkEndCommandBuffer(pass->commandBuffer)); VK_CHECK_RESULT(vkResetFences(s->base.device, 1, &pass->fence)); semaphore_signal_info[semaphore_signal_info_len++] = (VkSemaphoreSubmitInfo) { .sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO, .semaphore = pass->pipelineSemaphore, }; VkCommandBufferSubmitInfoKHR commandBufferInfo = { .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_SUBMIT_INFO, .commandBuffer = pass->commandBuffer, }; const VkSubmitInfo2KHR submitInfo = { .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO_2_KHR, .commandBufferInfoCount = 1, .pCommandBufferInfos = &commandBufferInfo, .waitSemaphoreInfoCount = semaphore_wait_info_len, .pWaitSemaphoreInfos = semaphore_wait_info, .signalSemaphoreInfoCount = semaphore_signal_info_len, .pSignalSemaphoreInfos = semaphore_signal_info, }; VK_CHECK_RESULT(vkQueueSubmit2KHR(s->base.queue, 1, &submitInfo, pass->fence)); s->started = true; VkSemaphoreGetFdInfoKHR get_fence_fd_info = { .sType = VK_STRUCTURE_TYPE_SEMAPHORE_GET_FD_INFO_KHR, .semaphore = pass->pipelineSemaphore, .handleType = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT, }; VK_CHECK_RESULT(vkGetSemaphoreFdKHR(s->base.device, &get_fence_fd_info, &pass->sync_fd)); return 0; } static void clear_buffers(struct vulkan_blit_state *s, struct vulkan_stream *p) { uint32_t i; for (i = 0; i < p->n_buffers; i++) { vulkan_buffer_clear(&s->base, &p->buffers[i]); p->spa_buffers[i] = NULL; } p->n_buffers = 0; p->buffer_type = SPA_DATA_Invalid; p->maxsize = 0; } static void clear_streams(struct vulkan_blit_state *s) { uint32_t i; for (i = 0; i < s->n_streams; i++) { struct vulkan_stream *p = &s->streams[i]; clear_buffers(s, p); } } int spa_vulkan_blit_fixate_modifier(struct vulkan_blit_state *s, struct vulkan_stream *p, struct spa_video_info *info, uint32_t modifierCount, uint64_t *modifiers, uint64_t *modifier) { VkFormat format; struct spa_rectangle size; switch (info->media_subtype) { case SPA_MEDIA_SUBTYPE_dsp: format = vulkan_id_to_vkformat(info->info.dsp.format); size.width = p->dim.width; size.height = p->dim.height; break; case SPA_MEDIA_SUBTYPE_raw: format = vulkan_id_to_vkformat(info->info.raw.format); size.width = p->dim.width; size.height = p->dim.height; break; default: spa_log_warn(s->log, "Unsupported media subtype %d", info->media_subtype); return -1; } if (format == VK_FORMAT_UNDEFINED) { return -1; } struct dmabuf_fixation_info fixation_info = { .format = format, .modifierCount = modifierCount, .modifiers = modifiers, .size = size, .usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT, }; return vulkan_fixate_modifier(&s->base, &fixation_info, modifier); } int spa_vulkan_blit_use_buffers(struct vulkan_blit_state *s, struct vulkan_stream *p, uint32_t flags, struct spa_video_info *info, uint32_t n_buffers, struct spa_buffer **buffers) { struct external_buffer_info externalBufferInfo = {0}; switch (info->media_subtype) { case SPA_MEDIA_SUBTYPE_dsp: externalBufferInfo.format = vulkan_id_to_vkformat(info->info.dsp.format); externalBufferInfo.size.width = p->dim.width; externalBufferInfo.size.height = p->dim.height; if (info->info.dsp.flags & SPA_VIDEO_FLAG_MODIFIER) externalBufferInfo.modifier = info->info.dsp.modifier; break; case SPA_MEDIA_SUBTYPE_raw: externalBufferInfo.format = vulkan_id_to_vkformat(info->info.raw.format); externalBufferInfo.size.width = p->dim.width; externalBufferInfo.size.height = p->dim.height; if (info->info.raw.flags & SPA_VIDEO_FLAG_MODIFIER) externalBufferInfo.modifier = info->info.raw.modifier; break; default: spa_log_warn(s->log, "Unsupported media subtype %d", info->media_subtype); return -1; } if (externalBufferInfo.format == VK_FORMAT_UNDEFINED) return -1; vulkan_wait_idle(&s->base); clear_buffers(s, p); if (n_buffers == 0) return 0; bool alloc = flags & SPA_NODE_BUFFERS_FLAG_ALLOC; int ret; for (uint32_t i = 0; i < n_buffers; i++) { if (p->buffer_type == SPA_DATA_Invalid) { p->buffer_type = buffers[i]->datas->type; } else { if (p->buffer_type != buffers[i]->datas->type) { spa_log_error(s->log, "Buffers are of different type %d:%d", p->buffer_type, buffers[i]->datas[0].type); return -1; } } p->maxsize = SPA_MAX(p->maxsize, buffers[i]->datas[0].maxsize); externalBufferInfo.usage = p->direction == SPA_DIRECTION_OUTPUT ? VK_IMAGE_USAGE_TRANSFER_DST_BIT : VK_IMAGE_USAGE_TRANSFER_SRC_BIT; externalBufferInfo.spa_buf = buffers[i]; if (alloc) { if (SPA_FLAG_IS_SET(buffers[i]->datas[0].type, 1<base, &externalBufferInfo, &p->buffers[i]); } else { spa_log_error(s->log, "Unsupported buffer type mask %d", buffers[i]->datas[0].type); return -1; } } else { switch (buffers[i]->datas[0].type) { case SPA_DATA_DmaBuf:; ret = vulkan_import_dmabuf(&s->base, &externalBufferInfo, &p->buffers[i]); break; case SPA_DATA_MemPtr:; if (p->direction == SPA_DIRECTION_OUTPUT) { externalBufferInfo.usage |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT; } else { externalBufferInfo.usage |= VK_IMAGE_USAGE_TRANSFER_DST_BIT; } ret = vulkan_import_memptr(&s->base, &externalBufferInfo, &p->buffers[i]); break; default: spa_log_error(s->log, "Unsupported buffer type %d", buffers[i]->datas[0].type); return -1; } } if (ret != 0) { spa_log_error(s->log, "Failed to use buffer %d", i); return ret; } p->spa_buffers[i] = buffers[i]; p->n_buffers++; } return 0; } int spa_vulkan_blit_enumerate_raw_formats(struct vulkan_blit_state *s, uint32_t index, uint32_t caps, struct spa_pod **param, struct spa_pod_builder *builder) { uint32_t fmt_idx; bool has_modifier; if (!find_EnumFormatInfo(&s->formatInfosRaw, index, caps, &fmt_idx, &has_modifier)) return 0; *param = build_raw_EnumFormat(&s->formatInfosRaw.infos[fmt_idx], has_modifier, builder); return 1; } int spa_vulkan_blit_enumerate_dsp_formats(struct vulkan_blit_state *s, uint32_t index, uint32_t caps, struct spa_pod **param, struct spa_pod_builder *builder) { uint32_t fmt_idx; bool has_modifier; if (!find_EnumFormatInfo(&s->formatInfosDSP, index, caps, &fmt_idx, &has_modifier)) return 0; *param = build_dsp_EnumFormat(&s->formatInfosDSP.infos[fmt_idx], has_modifier, builder); return 1; } int spa_vulkan_blit_enumerate_formats(struct vulkan_blit_state *s, uint32_t index, uint32_t caps, struct spa_pod **param, struct spa_pod_builder *builder) { uint32_t fmt_idx; bool has_modifier; uint32_t raw_offset = 0; if ((caps & VULKAN_BUFFER_TYPE_CAP_SHM) > 0) raw_offset += s->formatInfosDSP.formatCount; if ((caps & VULKAN_BUFFER_TYPE_CAP_DMABUF) > 0) raw_offset += s->formatInfosDSP.formatsWithModifiersCount; if (index < raw_offset) { if (find_EnumFormatInfo(&s->formatInfosDSP, index, caps, &fmt_idx, &has_modifier)) { *param = build_dsp_EnumFormat(&s->formatInfosDSP.infos[fmt_idx], has_modifier, builder); return 1; } } else { if (find_EnumFormatInfo(&s->formatInfosRaw, index - raw_offset, caps, &fmt_idx, &has_modifier)) { *param = build_raw_EnumFormat(&s->formatInfosRaw.infos[fmt_idx], has_modifier, builder); return 1; } } return 0; } static int vulkan_stream_init(struct vulkan_stream *stream, enum spa_direction direction, struct spa_dict *props) { spa_zero(*stream); stream->direction = direction; stream->maxsize = 0; return 0; } int spa_vulkan_blit_init_pass(struct vulkan_blit_state *s, struct vulkan_pass *pass) { pass->in_buffer_id = SPA_ID_INVALID; pass->in_stream_id = SPA_ID_INVALID; pass->out_buffer_id = SPA_ID_INVALID; pass->out_stream_id = SPA_ID_INVALID; pass->sync_fd = -1; CHECK(vulkan_fence_create(&s->base, &pass->fence)); CHECK(vulkan_commandBuffer_create(&s->base, s->commandPool, &pass->commandBuffer)); VkExportSemaphoreCreateInfo export_info = { .sType = VK_STRUCTURE_TYPE_EXPORT_SEMAPHORE_CREATE_INFO, .handleTypes = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT, }; VkSemaphoreCreateInfo semaphore_info = { .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, .pNext = &export_info, }; VK_CHECK_RESULT(vkCreateSemaphore(s->base.device, &semaphore_info, NULL, &pass->pipelineSemaphore)); for (uint32_t i = 0; i < s->n_streams; i++) { struct vulkan_stream *p = &s->streams[i]; if (p->direction == SPA_DIRECTION_OUTPUT || p->buffer_type != SPA_DATA_MemPtr) continue; vulkan_staging_buffer_create(&s->base, p->maxsize, &pass->in_staging_buffer); } return 0; } int spa_vulkan_blit_reset_pass(struct vulkan_blit_state *s, struct vulkan_pass *pass) { pass->in_buffer_id = SPA_ID_INVALID; pass->in_stream_id = SPA_ID_INVALID; pass->out_buffer_id = SPA_ID_INVALID; pass->out_stream_id = SPA_ID_INVALID; if (pass->sync_fd != -1) { close(pass->sync_fd); pass->sync_fd = -1; } return 0; } int spa_vulkan_blit_clear_pass(struct vulkan_blit_state *s, struct vulkan_pass *pass) { pass->in_buffer_id = SPA_ID_INVALID; pass->in_stream_id = SPA_ID_INVALID; pass->out_buffer_id = SPA_ID_INVALID; pass->out_stream_id = SPA_ID_INVALID; if (pass->sync_fd != -1) { close(pass->sync_fd); pass->sync_fd = -1; } vkDestroyFence(s->base.device, pass->fence, NULL); pass->fence = VK_NULL_HANDLE; vkFreeCommandBuffers(s->base.device, s->commandPool, 1, &pass->commandBuffer); pass->commandBuffer = VK_NULL_HANDLE; vkDestroySemaphore(s->base.device, pass->pipelineSemaphore, NULL); pass->pipelineSemaphore = VK_NULL_HANDLE; vulkan_staging_buffer_destroy(&s->base, &pass->in_staging_buffer); pass->in_staging_buffer.buffer = VK_NULL_HANDLE; return 0; } int spa_vulkan_blit_init_stream(struct vulkan_blit_state *s, struct vulkan_stream *stream, enum spa_direction direction, struct spa_dict *props) { return vulkan_stream_init(stream, direction, props); } int spa_vulkan_blit_prepare(struct vulkan_blit_state *s) { if (!s->prepared) { CHECK(vulkan_commandPool_create(&s->base, &s->commandPool)); s->prepared = true; } return 0; } int spa_vulkan_blit_unprepare(struct vulkan_blit_state *s) { if (s->prepared) { vkDestroyCommandPool(s->base.device, s->commandPool, NULL); s->prepared = false; } return 0; } int spa_vulkan_blit_start(struct vulkan_blit_state *s) { return 0; } int spa_vulkan_blit_stop(struct vulkan_blit_state *s) { VK_CHECK_RESULT(vkDeviceWaitIdle(s->base.device)); clear_streams(s); s->started = false; return 0; } int spa_vulkan_blit_process(struct vulkan_blit_state *s, struct vulkan_pass *pass) { if (!s->initialized) { spa_log_warn(s->log, "Renderer not initialized"); return -1; } if (!s->prepared) { spa_log_warn(s->log, "Renderer not prepared"); return -1; } CHECK(runImportSync(s, pass)); CHECK(runImportSHMBuffers(s, pass)); CHECK(runCommandBuffer(s, pass)); if (pass->sync_fd != -1) { runExportSync(s, pass); } CHECK(vulkan_wait_idle(&s->base)); CHECK(runExportSHMBuffers(s, pass)); return 0; } int spa_vulkan_blit_get_buffer_caps(struct vulkan_blit_state *s, enum spa_direction direction) { switch (direction) { case SPA_DIRECTION_INPUT: return VULKAN_BUFFER_TYPE_CAP_DMABUF | VULKAN_BUFFER_TYPE_CAP_SHM; case SPA_DIRECTION_OUTPUT: return VULKAN_BUFFER_TYPE_CAP_DMABUF | VULKAN_BUFFER_TYPE_CAP_SHM; } return 0; } struct vulkan_modifier_info *spa_vulkan_blit_get_modifier_info(struct vulkan_blit_state *s, struct spa_video_info *info) { VkFormat format; uint64_t modifier; switch (info->media_subtype) { case SPA_MEDIA_SUBTYPE_dsp: format = vulkan_id_to_vkformat(info->info.dsp.format); modifier = info->info.dsp.modifier; return vulkan_modifierInfo_find(&s->formatInfosDSP, format, modifier); case SPA_MEDIA_SUBTYPE_raw: format = vulkan_id_to_vkformat(info->info.raw.format); modifier = info->info.raw.modifier; return vulkan_modifierInfo_find(&s->formatInfosRaw, format, modifier); default: spa_log_warn(s->log, "Unsupported media subtype %d", info->media_subtype); return NULL; } } int spa_vulkan_blit_init(struct vulkan_blit_state *s) { int ret; s->base.log = s->log; struct vulkan_base_info baseInfo = { .queueFlags = VK_QUEUE_TRANSFER_BIT, }; if ((ret = vulkan_base_init(&s->base, &baseInfo)) < 0) return ret; uint32_t dsp_formats [] = { SPA_VIDEO_FORMAT_DSP_F32 }; vulkan_format_infos_init(&s->base, SPA_N_ELEMENTS(dsp_formats), dsp_formats, &s->formatInfosDSP); uint32_t raw_formats [] = { SPA_VIDEO_FORMAT_BGRA, SPA_VIDEO_FORMAT_RGBA, SPA_VIDEO_FORMAT_BGRx, SPA_VIDEO_FORMAT_RGBx, SPA_VIDEO_FORMAT_BGR, SPA_VIDEO_FORMAT_RGB, }; vulkan_format_infos_init(&s->base, SPA_N_ELEMENTS(raw_formats), raw_formats, &s->formatInfosRaw); s->initialized = true; return 0; } void spa_vulkan_blit_deinit(struct vulkan_blit_state *s) { vulkan_format_infos_deinit(&s->formatInfosRaw); vulkan_format_infos_deinit(&s->formatInfosDSP); vulkan_base_deinit(&s->base); s->initialized = false; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/vulkan/vulkan-blit-utils.h000066400000000000000000000063401511204443500300040ustar00rootroot00000000000000/* Spa */ /* SPDX-FileCopyrightText: Copyright © 2019 Wim Taymans */ /* SPDX-FileCopyrightText: Copyright © 2023 columbarius */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #include #include "vulkan-utils.h" #define MAX_STREAMS 2 struct vulkan_pass { uint32_t in_buffer_id; uint32_t in_stream_id; uint32_t out_buffer_id; uint32_t out_stream_id; VkBufferImageCopy in_copy; struct vulkan_staging_buffer in_staging_buffer; VkCommandBuffer commandBuffer; VkSemaphore pipelineSemaphore; VkFence fence; int sync_fd; }; struct vulkan_stream { enum spa_direction direction; enum spa_data_type buffer_type; struct spa_rectangle dim; uint32_t bpp; uint32_t maxsize; struct vulkan_buffer buffers[MAX_BUFFERS]; struct spa_buffer *spa_buffers[MAX_BUFFERS]; uint32_t n_buffers; }; struct vulkan_blit_state { struct spa_log *log; struct vulkan_base base; struct vulkan_format_infos formatInfosRaw; struct vulkan_format_infos formatInfosDSP; VkCommandPool commandPool; unsigned int initialized:1; unsigned int prepared:1; unsigned int started:1; uint32_t n_streams; struct vulkan_stream streams[MAX_STREAMS]; }; int spa_vulkan_blit_init_pass(struct vulkan_blit_state *s, struct vulkan_pass *pass); int spa_vulkan_blit_reset_pass(struct vulkan_blit_state *s, struct vulkan_pass *pass); int spa_vulkan_blit_clear_pass(struct vulkan_blit_state *s, struct vulkan_pass *pass); int spa_vulkan_blit_init_stream(struct vulkan_blit_state *s, struct vulkan_stream *stream, enum spa_direction, struct spa_dict *props); int spa_vulkan_blit_fixate_modifier(struct vulkan_blit_state *s, struct vulkan_stream *p, struct spa_video_info *info, uint32_t modifierCount, uint64_t *modifiers, uint64_t *modifier); int spa_vulkan_blit_use_buffers(struct vulkan_blit_state *s, struct vulkan_stream *stream, uint32_t flags, struct spa_video_info *info, uint32_t n_buffers, struct spa_buffer **buffers); int spa_vulkan_blit_enumerate_raw_formats(struct vulkan_blit_state *s, uint32_t index, uint32_t caps, struct spa_pod **param, struct spa_pod_builder *builder); int spa_vulkan_blit_enumerate_dsp_formats(struct vulkan_blit_state *s, uint32_t index, uint32_t caps, struct spa_pod **param, struct spa_pod_builder *builder); int spa_vulkan_blit_enumerate_formats(struct vulkan_blit_state *s, uint32_t index, uint32_t caps, struct spa_pod **param, struct spa_pod_builder *builder); int spa_vulkan_blit_prepare(struct vulkan_blit_state *s); int spa_vulkan_blit_unprepare(struct vulkan_blit_state *s); int spa_vulkan_blit_start(struct vulkan_blit_state *s); int spa_vulkan_blit_stop(struct vulkan_blit_state *s); int spa_vulkan_blit_ready(struct vulkan_blit_state *s); int spa_vulkan_blit_process(struct vulkan_blit_state *s, struct vulkan_pass *pass); int spa_vulkan_blit_cleanup(struct vulkan_blit_state *s); int spa_vulkan_blit_get_buffer_caps(struct vulkan_blit_state *s, enum spa_direction direction); struct vulkan_modifier_info *spa_vulkan_blit_get_modifier_info(struct vulkan_blit_state *s, struct spa_video_info *info); int spa_vulkan_blit_init(struct vulkan_blit_state *s); void spa_vulkan_blit_deinit(struct vulkan_blit_state *s); pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/vulkan/vulkan-compute-filter.c000066400000000000000000000557111511204443500306540ustar00rootroot00000000000000/* Spa */ /* SPDX-FileCopyrightText: Copyright © 2019 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "vulkan-compute-utils.h" #undef SPA_LOG_TOPIC_DEFAULT #define SPA_LOG_TOPIC_DEFAULT &log_topic SPA_LOG_TOPIC_DEFINE_STATIC(log_topic, "spa.vulkan.compute-filter"); struct buffer { uint32_t id; #define BUFFER_FLAG_OUT (1<<0) uint32_t flags; struct spa_buffer *outbuf; struct spa_meta_header *h; struct spa_list link; }; struct port { uint64_t info_all; struct spa_port_info info; enum spa_direction direction; #define IDX_EnumFormat 0 #define IDX_Meta 1 #define IDX_IO 2 #define IDX_Format 3 #define IDX_Buffer 4 #define N_PORT_PARAMS 5 struct spa_param_info params[N_PORT_PARAMS]; struct spa_io_buffers *io; bool have_format; struct spa_video_info current_format; struct buffer buffers[MAX_BUFFERS]; uint32_t n_buffers; struct spa_list empty; struct spa_list ready; uint32_t stream_id; }; struct impl { struct spa_handle handle; struct spa_node node; struct spa_log *log; struct spa_io_position *position; uint64_t info_all; struct spa_node_info info; #define IDX_PropInfo 0 #define IDX_Props 1 #define N_NODE_PARAMS 2 struct spa_param_info params[N_NODE_PARAMS]; struct spa_hook_list hooks; struct spa_callbacks callbacks; bool started; struct vulkan_compute_state state; struct port port[2]; }; #define CHECK_PORT(this,d,p) ((p) < 1) static int impl_node_enum_params(void *object, int seq, uint32_t id, uint32_t start, uint32_t num, const struct spa_pod *filter) { struct impl *this = object; struct spa_pod *param; struct spa_pod_builder b = { 0 }; uint8_t buffer[1024]; struct spa_result_node_params result; uint32_t count = 0; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(num != 0, -EINVAL); result.id = id; result.next = start; next: result.index = result.next++; spa_pod_builder_init(&b, buffer, sizeof(buffer)); switch (id) { default: return -ENOENT; } if (spa_pod_filter(&b, &result.param, param, filter) < 0) goto next; spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); if (++count != num) goto next; return 0; } static int impl_node_set_io(void *object, uint32_t id, void *data, size_t size) { struct impl *this = object; spa_return_val_if_fail(this != NULL, -EINVAL); switch (id) { case SPA_IO_Position: if (size > 0 && size < sizeof(struct spa_io_position)) return -EINVAL; this->position = data; break; default: return -ENOENT; } return 0; } static int impl_node_set_param(void *object, uint32_t id, uint32_t flags, const struct spa_pod *param) { struct impl *this = object; spa_return_val_if_fail(this != NULL, -EINVAL); switch (id) { default: return -ENOENT; } return 0; } static inline void reuse_buffer(struct impl *this, struct port *port, uint32_t id) { struct buffer *b = &port->buffers[id]; if (SPA_FLAG_IS_SET(b->flags, BUFFER_FLAG_OUT)) { spa_log_debug(this->log, "%p: reuse buffer %d", this, id); SPA_FLAG_CLEAR(b->flags, BUFFER_FLAG_OUT); spa_list_append(&port->empty, &b->link); } } static int impl_node_send_command(void *object, const struct spa_command *command) { struct impl *this = object; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(command != NULL, -EINVAL); switch (SPA_NODE_COMMAND_ID(command)) { case SPA_NODE_COMMAND_Start: if (this->started) return 0; this->started = true; spa_vulkan_compute_start(&this->state); break; case SPA_NODE_COMMAND_Suspend: case SPA_NODE_COMMAND_Pause: if (!this->started) return 0; this->started = false; spa_vulkan_compute_stop(&this->state); break; default: return -ENOTSUP; } return 0; } static const struct spa_dict_item node_info_items[] = { { SPA_KEY_MEDIA_CLASS, "Video/Filter" }, }; static void emit_node_info(struct impl *this, bool full) { uint64_t old = full ? this->info.change_mask : 0; if (full) this->info.change_mask = this->info_all; if (this->info.change_mask) { this->info.props = &SPA_DICT_INIT_ARRAY(node_info_items); spa_node_emit_info(&this->hooks, &this->info); this->info.change_mask = old; } } static void emit_port_info(struct impl *this, struct port *port, bool full) { uint64_t old = full ? port->info.change_mask : 0; if (full) port->info.change_mask = port->info_all; if (port->info.change_mask) { struct spa_dict_item items[1]; items[0] = SPA_DICT_ITEM_INIT(SPA_KEY_FORMAT_DSP, "32 bit float RGBA video"); port->info.props = &SPA_DICT_INIT(items, 1); spa_node_emit_port_info(&this->hooks, port->direction, 0, &port->info); port->info.change_mask = old; } } static int impl_node_add_listener(void *object, struct spa_hook *listener, const struct spa_node_events *events, void *data) { struct impl *this = object; struct spa_hook_list save; spa_return_val_if_fail(this != NULL, -EINVAL); spa_hook_list_isolate(&this->hooks, &save, listener, events, data); emit_node_info(this, true); emit_port_info(this, &this->port[0], true); emit_port_info(this, &this->port[1], true); spa_hook_list_join(&this->hooks, &save); return 0; } static int impl_node_set_callbacks(void *object, const struct spa_node_callbacks *callbacks, void *data) { struct impl *this = object; spa_return_val_if_fail(this != NULL, -EINVAL); this->callbacks = SPA_CALLBACKS_INIT(callbacks, data); return 0; } static int impl_node_add_port(void *object, enum spa_direction direction, uint32_t port_id, const struct spa_dict *props) { return -ENOTSUP; } static int impl_node_remove_port(void *object, enum spa_direction direction, uint32_t port_id) { return -ENOTSUP; } static int port_enum_formats(void *object, enum spa_direction direction, uint32_t port_id, uint32_t index, const struct spa_pod *filter, struct spa_pod **param, struct spa_pod_builder *builder) { struct impl *this = object; if (this->port[port_id].have_format && this->port[port_id].current_format.info.dsp.flags & SPA_VIDEO_FLAG_MODIFIER && this->port[port_id].current_format.info.dsp.flags ^ SPA_VIDEO_FLAG_MODIFIER_FIXATION_REQUIRED) { if (index == 0) { spa_log_info(this->log, "vulkan-compute-filter: enum_formats fixated format idx: %d, format %d, has_modifier 1", index, this->port[port_id].current_format.info.dsp.format); *param = spa_format_video_dsp_build(builder, SPA_PARAM_EnumFormat, &this->port[port_id].current_format.info.dsp); return 1; } return spa_vulkan_compute_enumerate_formats(&this->state, index-1, spa_vulkan_compute_get_buffer_caps(&this->state, direction), param, builder); } else { return spa_vulkan_compute_enumerate_formats(&this->state, index, spa_vulkan_compute_get_buffer_caps(&this->state, direction), param, builder); } } static int impl_node_port_enum_params(void *object, int seq, enum spa_direction direction, uint32_t port_id, uint32_t id, uint32_t start, uint32_t num, const struct spa_pod *filter) { struct impl *this = object; struct port *port; struct spa_pod_builder b = { 0 }; uint8_t buffer[1024]; struct spa_pod *param; struct spa_result_node_params result; uint32_t count = 0; int res; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(num != 0, -EINVAL); spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); port = &this->port[direction]; result.id = id; result.next = start; next: result.index = result.next++; spa_pod_builder_init(&b, buffer, sizeof(buffer)); switch (id) { case SPA_PARAM_EnumFormat: if ((res = port_enum_formats(this, direction, port_id, result.index, filter, ¶m, &b)) <= 0) return res; break; case SPA_PARAM_Format: if (!port->have_format) return -EIO; if (result.index > 0) return 0; param = spa_format_video_dsp_build(&b, id, &port->current_format.info.dsp); break; case SPA_PARAM_Buffers: { if (!port->have_format) return -EIO; if (this->position == NULL) return -EIO; if (result.index > 0) return 0; spa_log_debug(this->log, "%p: %dx%d stride %d", this, this->position->video.size.width, this->position->video.size.height, this->position->video.stride); if (port->current_format.info.dsp.flags & SPA_VIDEO_FLAG_MODIFIER) { struct vulkan_modifier_info *mod_info = spa_vulkan_compute_get_modifier_info(&this->state, &port->current_format.info.dsp); param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamBuffers, id, SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(2, 1, MAX_BUFFERS), SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(mod_info->props.drmFormatModifierPlaneCount), SPA_PARAM_BUFFERS_dataType, SPA_POD_CHOICE_FLAGS_Int(1<position->video.stride * this->position->video.size.height), SPA_PARAM_BUFFERS_stride, SPA_POD_Int(this->position->video.stride), SPA_PARAM_BUFFERS_dataType, SPA_POD_CHOICE_FLAGS_Int(1<hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); if (++count != num) goto next; return 0; } static int clear_buffers(struct impl *this, struct port *port) { if (port->n_buffers > 0) { spa_log_debug(this->log, "%p: clear buffers", this); spa_vulkan_compute_stop(&this->state); spa_vulkan_compute_use_buffers(&this->state, &this->state.streams[port->stream_id], 0, &port->current_format.info.dsp, 0, NULL); port->n_buffers = 0; spa_list_init(&port->empty); spa_list_init(&port->ready); this->started = false; } return 0; } static int port_set_format(struct impl *this, struct port *port, uint32_t flags, const struct spa_pod *format) { int res; if (format == NULL) { port->have_format = false; clear_buffers(this, port); spa_vulkan_compute_unprepare(&this->state); } else { struct spa_video_info info = { 0 }; if ((res = spa_format_parse(format, &info.media_type, &info.media_subtype)) < 0) return res; if (info.media_type != SPA_MEDIA_TYPE_video && info.media_subtype != SPA_MEDIA_SUBTYPE_dsp) return -EINVAL; if (spa_format_video_dsp_parse(format, &info.info.dsp) < 0) return -EINVAL; if (info.info.dsp.format != SPA_VIDEO_FORMAT_DSP_F32) return -EINVAL; this->state.constants.width = this->position->video.size.width; this->state.constants.height = this->position->video.size.height; bool modifier_fixed = false; if (port->direction == SPA_DIRECTION_OUTPUT && info.info.dsp.flags & SPA_VIDEO_FLAG_MODIFIER && info.info.dsp.flags & SPA_VIDEO_FLAG_MODIFIER_FIXATION_REQUIRED) { const struct spa_pod_prop *mod_prop; if ((mod_prop = spa_pod_find_prop(format, NULL, SPA_FORMAT_VIDEO_modifier)) == NULL) return -EINVAL; const struct spa_pod *mod_pod = &mod_prop->value; uint32_t modifierCount = SPA_POD_CHOICE_N_VALUES(mod_pod); uint64_t *modifiers = SPA_POD_CHOICE_VALUES(mod_pod); if (modifierCount <= 1) return -EINVAL; // SPA_POD_CHOICE carries the "preferred" value at position 0 modifierCount -= 1; modifiers++; uint64_t fixed_modifier; if (spa_vulkan_compute_fixate_modifier(&this->state, &this->state.streams[port->stream_id], &info.info.dsp, modifierCount, modifiers, &fixed_modifier) != 0) return -EINVAL; spa_log_info(this->log, "modifier fixated %"PRIu64, fixed_modifier); info.info.dsp.modifier = fixed_modifier; info.info.dsp.flags &= ~SPA_VIDEO_FLAG_MODIFIER_FIXATION_REQUIRED; modifier_fixed = true; } if (info.info.dsp.flags & SPA_VIDEO_FLAG_MODIFIER) { port->info.flags |= SPA_PORT_FLAG_CAN_ALLOC_BUFFERS; } else { port->info.flags &= ~SPA_PORT_FLAG_CAN_ALLOC_BUFFERS; } port->info.change_mask |= SPA_PORT_CHANGE_MASK_FLAGS; port->current_format = info; port->have_format = true; if (modifier_fixed) { port->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; port->params[IDX_EnumFormat].flags ^= SPA_PARAM_INFO_SERIAL; emit_port_info(this, port, false); return 0; } } port->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; if (port->have_format) { port->params[IDX_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_READWRITE); port->params[IDX_Buffer] = SPA_PARAM_INFO(SPA_PARAM_Buffers, SPA_PARAM_INFO_READ); } else { port->params[IDX_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); port->params[IDX_Buffer] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); } emit_port_info(this, port, false); return 0; } static int impl_node_port_set_param(void *object, enum spa_direction direction, uint32_t port_id, uint32_t id, uint32_t flags, const struct spa_pod *param) { struct impl *this = object; struct port *port; int res; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(CHECK_PORT(node, direction, port_id), -EINVAL); port = &this->port[direction]; switch (id) { case SPA_PARAM_Format: res = port_set_format(this, port, flags, param); break; default: return -ENOENT; } return res; } static int impl_node_port_use_buffers(void *object, enum spa_direction direction, uint32_t port_id, uint32_t flags, struct spa_buffer **buffers, uint32_t n_buffers) { struct impl *this = object; struct port *port; uint32_t i; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); port = &this->port[direction]; clear_buffers(this, port); if (n_buffers > 0 && !port->have_format) return -EIO; if (n_buffers > MAX_BUFFERS) return -ENOSPC; for (i = 0; i < n_buffers; i++) { struct buffer *b; b = &port->buffers[i]; b->id = i; b->outbuf = buffers[i]; b->flags = 0; b->h = spa_buffer_find_meta_data(buffers[i], SPA_META_Header, sizeof(*b->h)); spa_log_info(this->log, "%p: %d:%d add buffer %p", port, direction, port_id, b); spa_list_append(&port->empty, &b->link); } spa_vulkan_compute_use_buffers(&this->state, &this->state.streams[port->stream_id], flags, &port->current_format.info.dsp, n_buffers, buffers); port->n_buffers = n_buffers; return 0; } static int impl_node_port_set_io(void *object, enum spa_direction direction, uint32_t port_id, uint32_t id, void *data, size_t size) { struct impl *this = object; struct port *port; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); port = &this->port[direction]; switch (id) { case SPA_IO_Buffers: port->io = data; break; default: return -ENOENT; } return 0; } static int impl_node_port_reuse_buffer(void *object, uint32_t port_id, uint32_t buffer_id) { struct impl *this = object; struct port *port; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(port_id == 0, -EINVAL); port = &this->port[SPA_DIRECTION_OUTPUT]; spa_return_val_if_fail(buffer_id < port->n_buffers, -EINVAL); reuse_buffer(this, port, buffer_id); return 0; } static int impl_node_process(void *object) { struct impl *this = object; struct port *inport, *outport; struct spa_io_buffers *inio, *outio; struct buffer *b; spa_return_val_if_fail(this != NULL, -EINVAL); inport = &this->port[SPA_DIRECTION_INPUT]; if ((inio = inport->io) == NULL) return -EIO; if (inio->status != SPA_STATUS_HAVE_DATA) return inio->status; if (inio->buffer_id >= inport->n_buffers) { inio->status = -EINVAL; return -EINVAL; } outport = &this->port[SPA_DIRECTION_OUTPUT]; if ((outio = outport->io) == NULL) return -EIO; if (outio->status == SPA_STATUS_HAVE_DATA) return SPA_STATUS_HAVE_DATA; if (outio->buffer_id < outport->n_buffers) { reuse_buffer(this, outport, outio->buffer_id); outio->buffer_id = SPA_ID_INVALID; } if (spa_list_is_empty(&outport->empty)) { spa_log_debug(this->log, "%p: out of buffers", this); return -EPIPE; } b = &inport->buffers[inio->buffer_id]; this->state.streams[inport->stream_id].pending_buffer_id = b->id; inio->status = SPA_STATUS_NEED_DATA; b = spa_list_first(&outport->empty, struct buffer, link); spa_list_remove(&b->link); SPA_FLAG_SET(b->flags, BUFFER_FLAG_OUT); this->state.streams[outport->stream_id].pending_buffer_id = b->id; this->state.constants.time += 0.025f; this->state.constants.frame++; spa_log_debug(this->log, "filter into %d", b->id); spa_vulkan_compute_process(&this->state); b->outbuf->datas[0].chunk->offset = 0; b->outbuf->datas[0].chunk->size = b->outbuf->datas[0].maxsize; b->outbuf->datas[0].chunk->stride = this->position->video.stride; outio->buffer_id = b->id; outio->status = SPA_STATUS_HAVE_DATA; return SPA_STATUS_NEED_DATA | SPA_STATUS_HAVE_DATA; } static const struct spa_node_methods impl_node = { SPA_VERSION_NODE_METHODS, .add_listener = impl_node_add_listener, .set_callbacks = impl_node_set_callbacks, .enum_params = impl_node_enum_params, .set_param = impl_node_set_param, .set_io = impl_node_set_io, .send_command = impl_node_send_command, .add_port = impl_node_add_port, .remove_port = impl_node_remove_port, .port_enum_params = impl_node_port_enum_params, .port_set_param = impl_node_port_set_param, .port_use_buffers = impl_node_port_use_buffers, .port_set_io = impl_node_port_set_io, .port_reuse_buffer = impl_node_port_reuse_buffer, .process = impl_node_process, }; static int impl_get_interface(struct spa_handle *handle, const char *type, void **interface) { struct impl *this; spa_return_val_if_fail(handle != NULL, -EINVAL); spa_return_val_if_fail(interface != NULL, -EINVAL); this = (struct impl *) handle; if (spa_streq(type, SPA_TYPE_INTERFACE_Node)) *interface = &this->node; else return -ENOENT; return 0; } static int impl_clear(struct spa_handle *handle) { struct impl *this; spa_return_val_if_fail(handle != NULL, -EINVAL); this = (struct impl *) handle; spa_vulkan_compute_deinit(&this->state); return 0; } static size_t impl_get_size(const struct spa_handle_factory *factory, const struct spa_dict *params) { return sizeof(struct impl); } static int impl_init(const struct spa_handle_factory *factory, struct spa_handle *handle, const struct spa_dict *info, const struct spa_support *support, uint32_t n_support) { struct impl *this; struct port *port; spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(handle != NULL, -EINVAL); handle->get_interface = impl_get_interface; handle->clear = impl_clear; this = (struct impl *) handle; this->log = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log); this->state.log = this->log; this->state.shaderName = "spa/plugins/vulkan/shaders/filter.spv"; spa_hook_list_init(&this->hooks); this->node.iface = SPA_INTERFACE_INIT( SPA_TYPE_INTERFACE_Node, SPA_VERSION_NODE, &impl_node, this); this->info_all = SPA_NODE_CHANGE_MASK_FLAGS | SPA_NODE_CHANGE_MASK_PROPS | SPA_NODE_CHANGE_MASK_PARAMS; this->info = SPA_NODE_INFO_INIT(); this->info.max_output_ports = 1; this->info.max_input_ports = 1; this->info.flags = SPA_NODE_FLAG_RT; this->params[IDX_PropInfo] = SPA_PARAM_INFO(SPA_PARAM_PropInfo, SPA_PARAM_INFO_READ); this->params[IDX_Props] = SPA_PARAM_INFO(SPA_PARAM_Props, SPA_PARAM_INFO_READWRITE); this->info.params = this->params; this->info.n_params = N_NODE_PARAMS; port = &this->port[0]; port->stream_id = 1; port->direction = SPA_DIRECTION_INPUT; port->info_all = SPA_PORT_CHANGE_MASK_FLAGS | SPA_PORT_CHANGE_MASK_PARAMS | SPA_PORT_CHANGE_MASK_PROPS; port->info = SPA_PORT_INFO_INIT(); port->info.flags = SPA_PORT_FLAG_NO_REF; port->params[IDX_EnumFormat] = SPA_PARAM_INFO(SPA_PARAM_EnumFormat, SPA_PARAM_INFO_READ); port->params[IDX_Meta] = SPA_PARAM_INFO(SPA_PARAM_Meta, SPA_PARAM_INFO_READ); port->params[IDX_IO] = SPA_PARAM_INFO(SPA_PARAM_IO, SPA_PARAM_INFO_READ); port->params[IDX_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); port->params[IDX_Buffer] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); port->info.params = port->params; port->info.n_params = N_PORT_PARAMS; spa_vulkan_compute_init_stream(&this->state, &this->state.streams[port->stream_id], SPA_DIRECTION_INPUT, NULL); spa_list_init(&port->empty); spa_list_init(&port->ready); port = &this->port[1]; port->stream_id = 0; port->direction = SPA_DIRECTION_OUTPUT; port->info_all = SPA_PORT_CHANGE_MASK_FLAGS | SPA_PORT_CHANGE_MASK_PARAMS | SPA_PORT_CHANGE_MASK_PROPS; port->info = SPA_PORT_INFO_INIT(); port->info.flags = SPA_PORT_FLAG_NO_REF | SPA_PORT_FLAG_CAN_ALLOC_BUFFERS; port->params[IDX_EnumFormat] = SPA_PARAM_INFO(SPA_PARAM_EnumFormat, SPA_PARAM_INFO_READ); port->params[IDX_Meta] = SPA_PARAM_INFO(SPA_PARAM_Meta, SPA_PARAM_INFO_READ); port->params[IDX_IO] = SPA_PARAM_INFO(SPA_PARAM_IO, SPA_PARAM_INFO_READ); port->params[IDX_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); port->params[IDX_Buffer] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); port->info.params = port->params; port->info.n_params = N_PORT_PARAMS; spa_list_init(&port->empty); spa_list_init(&port->ready); spa_vulkan_compute_init_stream(&this->state, &this->state.streams[port->stream_id], SPA_DIRECTION_OUTPUT, NULL); this->state.n_streams = 2; spa_vulkan_compute_init(&this->state); spa_vulkan_compute_prepare(&this->state); return 0; } static const struct spa_interface_info impl_interfaces[] = { {SPA_TYPE_INTERFACE_Node,}, }; static int impl_enum_interface_info(const struct spa_handle_factory *factory, const struct spa_interface_info **info, uint32_t *index) { spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(info != NULL, -EINVAL); spa_return_val_if_fail(index != NULL, -EINVAL); switch (*index) { case 0: *info = &impl_interfaces[*index]; break; default: return 0; } (*index)++; return 1; } static const struct spa_dict_item info_items[] = { { SPA_KEY_FACTORY_AUTHOR, "Wim Taymans " }, { SPA_KEY_FACTORY_DESCRIPTION, "Filter video frames using a vulkan compute shader" }, }; static const struct spa_dict info = SPA_DICT_INIT_ARRAY(info_items); const struct spa_handle_factory spa_vulkan_compute_filter_factory = { SPA_VERSION_HANDLE_FACTORY, SPA_NAME_API_VULKAN_COMPUTE_FILTER, &info, impl_get_size, impl_init, impl_enum_interface_info, }; pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/vulkan/vulkan-compute-source.c000066400000000000000000000660701511204443500306670ustar00rootroot00000000000000/* Spa */ /* SPDX-FileCopyrightText: Copyright © 2019 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "vulkan-compute-utils.h" #undef SPA_LOG_TOPIC_DEFAULT #define SPA_LOG_TOPIC_DEFAULT &log_topic SPA_LOG_TOPIC_DEFINE_STATIC(log_topic, "spa.vulkan.compute-source"); #define FRAMES_TO_TIME(this,f) ((this->position->video.framerate.denom * (f) * SPA_NSEC_PER_SEC) / \ (this->position->video.framerate.num)) #define DEFAULT_LIVE true struct props { bool live; }; static void reset_props(struct props *props) { props->live = DEFAULT_LIVE; } struct buffer { uint32_t id; #define BUFFER_FLAG_OUT (1<<0) uint32_t flags; struct spa_buffer *outbuf; struct spa_meta_header *h; struct spa_list link; }; struct port { uint64_t info_all; struct spa_port_info info; enum spa_direction direction; #define IDX_EnumFormat 0 #define IDX_Meta 1 #define IDX_IO 2 #define IDX_Format 3 #define IDX_Buffer 4 #define N_PORT_PARAMS 5 struct spa_param_info params[N_PORT_PARAMS]; struct spa_io_buffers *io; bool have_format; struct spa_video_info current_format; struct buffer buffers[MAX_BUFFERS]; uint32_t n_buffers; struct spa_list empty; struct spa_list ready; }; struct impl { struct spa_handle handle; struct spa_node node; struct spa_log *log; struct spa_loop *data_loop; struct spa_system *data_system; struct spa_io_clock *clock; struct spa_io_position *position; uint64_t info_all; struct spa_node_info info; #define IDX_PropInfo 0 #define IDX_Props 1 #define N_NODE_PARAMS 2 struct spa_param_info params[N_NODE_PARAMS]; struct props props; struct spa_hook_list hooks; struct spa_callbacks callbacks; bool async; struct spa_source timer_source; struct itimerspec timerspec; bool started; uint64_t start_time; uint64_t elapsed_time; uint64_t frame_count; struct vulkan_compute_state state; struct port port; }; #define CHECK_PORT(this,d,p) ((d) == SPA_DIRECTION_OUTPUT && (p) < 1) static int impl_node_enum_params(void *object, int seq, uint32_t id, uint32_t start, uint32_t num, const struct spa_pod *filter) { struct impl *this = object; struct spa_pod *param; struct spa_pod_builder b = { 0 }; uint8_t buffer[1024]; struct spa_result_node_params result; uint32_t count = 0; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(num != 0, -EINVAL); result.id = id; result.next = start; next: result.index = result.next++; spa_pod_builder_init(&b, buffer, sizeof(buffer)); switch (id) { case SPA_PARAM_PropInfo: { struct props *p = &this->props; switch (result.index) { case 0: param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_PropInfo, id, SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_live), SPA_PROP_INFO_description, SPA_POD_String("Configure live mode of the source"), SPA_PROP_INFO_type, SPA_POD_Bool(p->live)); break; default: return 0; } break; } case SPA_PARAM_Props: { struct props *p = &this->props; switch (result.index) { case 0: param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_Props, id, SPA_PROP_live, SPA_POD_Bool(p->live)); break; default: return 0; } break; } default: return -ENOENT; } if (spa_pod_filter(&b, &result.param, param, filter) < 0) goto next; spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); if (++count != num) goto next; return 0; } static int impl_node_set_io(void *object, uint32_t id, void *data, size_t size) { struct impl *this = object; spa_return_val_if_fail(this != NULL, -EINVAL); switch (id) { case SPA_IO_Clock: if (size > 0 && size < sizeof(struct spa_io_clock)) return -EINVAL; this->clock = data; break; case SPA_IO_Position: this->position = data; break; default: return -ENOENT; } return 0; } static int impl_node_set_param(void *object, uint32_t id, uint32_t flags, const struct spa_pod *param) { struct impl *this = object; spa_return_val_if_fail(this != NULL, -EINVAL); switch (id) { case SPA_PARAM_Props: { struct props *p = &this->props; struct port *port = &this->port; if (param == NULL) { reset_props(p); return 0; } spa_pod_parse_object(param, SPA_TYPE_OBJECT_Props, NULL, SPA_PROP_live, SPA_POD_OPT_Bool(&p->live)); if (p->live) port->info.flags |= SPA_PORT_FLAG_LIVE; else port->info.flags &= ~SPA_PORT_FLAG_LIVE; break; } default: return -ENOENT; } return 0; } static void set_timer(struct impl *this, bool enabled) { if (this->async || this->props.live) { if (enabled) { if (this->props.live) { uint64_t next_time = this->start_time + this->elapsed_time; this->timerspec.it_value.tv_sec = next_time / SPA_NSEC_PER_SEC; this->timerspec.it_value.tv_nsec = next_time % SPA_NSEC_PER_SEC; } else { this->timerspec.it_value.tv_sec = 0; this->timerspec.it_value.tv_nsec = 1; } } else { this->timerspec.it_value.tv_sec = 0; this->timerspec.it_value.tv_nsec = 0; } spa_system_timerfd_settime(this->data_system, this->timer_source.fd, SPA_FD_TIMER_ABSTIME, &this->timerspec, NULL); } } static int read_timer(struct impl *this) { uint64_t expirations; int res = 0; if (this->async || this->props.live) { if ((res = spa_system_timerfd_read(this->data_system, this->timer_source.fd, &expirations)) < 0) { if (res != -EAGAIN) spa_log_error(this->log, "%p: timerfd error: %s", this, spa_strerror(res)); } } return res; } static int make_buffer(struct impl *this) { struct buffer *b; struct port *port = &this->port; uint32_t n_bytes; int res; if (read_timer(this) < 0) return 0; if ((res = spa_vulkan_compute_ready(&this->state)) < 0) { res = SPA_STATUS_OK; goto next; } if (spa_list_is_empty(&port->empty)) { set_timer(this, false); spa_log_error(this->log, "%p: out of buffers", this); return -EPIPE; } b = spa_list_first(&port->empty, struct buffer, link); spa_list_remove(&b->link); n_bytes = b->outbuf->datas[0].maxsize; spa_log_trace(this->log, "%p: dequeue buffer %d", this, b->id); this->state.constants.time = this->elapsed_time / (float) SPA_NSEC_PER_SEC; this->state.constants.frame = this->frame_count; this->state.streams[0].pending_buffer_id = b->id; spa_vulkan_compute_process(&this->state); if (this->state.streams[0].ready_buffer_id != SPA_ID_INVALID) { struct buffer *b = &port->buffers[this->state.streams[0].ready_buffer_id]; this->state.streams[0].ready_buffer_id = SPA_ID_INVALID; spa_log_trace(this->log, "%p: ready buffer %d", this, b->id); b->outbuf->datas[0].chunk->offset = 0; b->outbuf->datas[0].chunk->size = n_bytes; b->outbuf->datas[0].chunk->stride = this->position->video.stride; if (b->h) { b->h->seq = this->frame_count; b->h->pts = this->start_time + this->elapsed_time; b->h->dts_offset = 0; } spa_list_append(&port->ready, &b->link); res = SPA_STATUS_HAVE_DATA; } next: this->frame_count++; this->elapsed_time = FRAMES_TO_TIME(this, this->frame_count); set_timer(this, true); return res; } static inline void reuse_buffer(struct impl *this, struct port *port, uint32_t id) { struct buffer *b = &port->buffers[id]; if (SPA_FLAG_IS_SET(b->flags, BUFFER_FLAG_OUT)) { spa_log_trace(this->log, "%p: reuse buffer %d", this, id); SPA_FLAG_CLEAR(b->flags, BUFFER_FLAG_OUT); spa_list_append(&port->empty, &b->link); if (!this->props.live) set_timer(this, true); } } static void on_output(struct spa_source *source) { struct impl *this = source->data; struct port *port = &this->port; struct spa_io_buffers *io = port->io; int res; if (io == NULL) return; if (io->status == SPA_STATUS_HAVE_DATA) return; if (io->buffer_id < port->n_buffers) { reuse_buffer(this, port, io->buffer_id); io->buffer_id = SPA_ID_INVALID; } res = make_buffer(this); if (!spa_list_is_empty(&port->ready)) { struct buffer *b = spa_list_first(&port->ready, struct buffer, link); spa_list_remove(&b->link); SPA_FLAG_SET(b->flags, BUFFER_FLAG_OUT); io->buffer_id = b->id; io->status = SPA_STATUS_HAVE_DATA; } spa_node_call_ready(&this->callbacks, res); } static int impl_node_send_command(void *object, const struct spa_command *command) { struct impl *this = object; struct port *port; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(command != NULL, -EINVAL); port = &this->port; switch (SPA_NODE_COMMAND_ID(command)) { case SPA_NODE_COMMAND_Start: { struct timespec now; if (!port->have_format) return -EIO; if (port->n_buffers == 0) return -EIO; if (this->started) return 0; clock_gettime(CLOCK_MONOTONIC, &now); if (this->props.live) this->start_time = SPA_TIMESPEC_TO_NSEC(&now); else this->start_time = 0; this->frame_count = 0; this->elapsed_time = 0; this->started = true; set_timer(this, true); spa_vulkan_compute_start(&this->state); break; } case SPA_NODE_COMMAND_Suspend: case SPA_NODE_COMMAND_Pause: if (!this->started) return 0; this->started = false; set_timer(this, false); spa_vulkan_compute_stop(&this->state); break; default: return -ENOTSUP; } return 0; } static const struct spa_dict_item node_info_items[] = { { SPA_KEY_MEDIA_CLASS, "Video/Source" }, { SPA_KEY_NODE_DRIVER, "true" }, }; static void emit_node_info(struct impl *this, bool full) { uint64_t old = full ? this->info.change_mask : 0; if (full) this->info.change_mask = this->info_all; if (this->info.change_mask) { this->info.props = &SPA_DICT_INIT_ARRAY(node_info_items); spa_node_emit_info(&this->hooks, &this->info); this->info.change_mask = old; } } static void emit_port_info(struct impl *this, struct port *port, bool full) { uint64_t old = full ? port->info.change_mask : 0; if (full) port->info.change_mask = port->info_all; if (port->info.change_mask) { struct spa_dict_item items[1]; items[0] = SPA_DICT_ITEM_INIT(SPA_KEY_FORMAT_DSP, "32 bit float RGBA video"); port->info.props = &SPA_DICT_INIT(items, 1); spa_node_emit_port_info(&this->hooks, SPA_DIRECTION_OUTPUT, 0, &port->info); port->info.change_mask = old; } } static int impl_node_add_listener(void *object, struct spa_hook *listener, const struct spa_node_events *events, void *data) { struct impl *this = object; struct spa_hook_list save; spa_return_val_if_fail(this != NULL, -EINVAL); spa_hook_list_isolate(&this->hooks, &save, listener, events, data); emit_node_info(this, true); emit_port_info(this, &this->port, true); spa_hook_list_join(&this->hooks, &save); return 0; } static int impl_node_set_callbacks(void *object, const struct spa_node_callbacks *callbacks, void *data) { struct impl *this = object; spa_return_val_if_fail(this != NULL, -EINVAL); this->callbacks = SPA_CALLBACKS_INIT(callbacks, data); return 0; } static int impl_node_add_port(void *object, enum spa_direction direction, uint32_t port_id, const struct spa_dict *props) { return -ENOTSUP; } static int impl_node_remove_port(void *object, enum spa_direction direction, uint32_t port_id) { return -ENOTSUP; } static int port_enum_formats(void *object, enum spa_direction direction, uint32_t port_id, uint32_t index, const struct spa_pod *filter, struct spa_pod **param, struct spa_pod_builder *builder) { struct impl *this = object; if (this->port.have_format && this->port.current_format.info.dsp.flags & SPA_VIDEO_FLAG_MODIFIER && this->port.current_format.info.dsp.flags ^ SPA_VIDEO_FLAG_MODIFIER_FIXATION_REQUIRED) { if (index == 0) { spa_log_info(this->log, "vulkan-compute-source: enum_formats fixated format idx: %d, format %d, has_modifier 1", index, this->port.current_format.info.dsp.format); *param = spa_format_video_dsp_build(builder, SPA_PARAM_EnumFormat, &this->port.current_format.info.dsp); return 1; } return spa_vulkan_compute_enumerate_formats(&this->state, index-1, spa_vulkan_compute_get_buffer_caps(&this->state, direction), param, builder); } else { return spa_vulkan_compute_enumerate_formats(&this->state, index, spa_vulkan_compute_get_buffer_caps(&this->state, direction), param, builder); } } static int impl_node_port_enum_params(void *object, int seq, enum spa_direction direction, uint32_t port_id, uint32_t id, uint32_t start, uint32_t num, const struct spa_pod *filter) { struct impl *this = object; struct port *port; struct spa_pod_builder b = { 0 }; uint8_t buffer[1024]; struct spa_pod *param; struct spa_result_node_params result; uint32_t count = 0; int res; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(num != 0, -EINVAL); spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); port = &this->port; result.id = id; result.next = start; next: result.index = result.next++; spa_pod_builder_init(&b, buffer, sizeof(buffer)); switch (id) { case SPA_PARAM_EnumFormat: if ((res = port_enum_formats(this, direction, port_id, result.index, filter, ¶m, &b)) <= 0) return res; break; case SPA_PARAM_Format: if (!port->have_format) return -EIO; if (result.index > 0) return 0; param = spa_format_video_dsp_build(&b, id, &port->current_format.info.dsp); break; case SPA_PARAM_Buffers: { if (!port->have_format) return -EIO; if (this->position == NULL) return -EIO; if (result.index > 0) return 0; spa_log_debug(this->log, "%p: %dx%d stride %d", this, this->position->video.size.width, this->position->video.size.height, this->position->video.stride); if (port->current_format.info.dsp.flags & SPA_VIDEO_FLAG_MODIFIER) { struct vulkan_modifier_info *mod_info = spa_vulkan_compute_get_modifier_info(&this->state, &port->current_format.info.dsp); param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamBuffers, id, SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(2, 1, MAX_BUFFERS), SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(mod_info->props.drmFormatModifierPlaneCount), SPA_PARAM_BUFFERS_dataType, SPA_POD_CHOICE_FLAGS_Int(1<position->video.stride * this->position->video.size.height), SPA_PARAM_BUFFERS_stride, SPA_POD_Int(this->position->video.stride), SPA_PARAM_BUFFERS_dataType, SPA_POD_CHOICE_FLAGS_Int(1<hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); if (++count != num) goto next; return 0; } static int clear_buffers(struct impl *this, struct port *port) { if (port->n_buffers > 0) { spa_log_debug(this->log, "%p: clear buffers", this); spa_vulkan_compute_use_buffers(&this->state, &this->state.streams[0], 0, &port->current_format.info.dsp, 0, NULL); port->n_buffers = 0; spa_list_init(&port->empty); spa_list_init(&port->ready); this->started = false; set_timer(this, false); } return 0; } static int port_set_format(struct impl *this, struct port *port, uint32_t flags, const struct spa_pod *format) { int res; if (format == NULL) { port->have_format = false; clear_buffers(this, port); spa_vulkan_compute_unprepare(&this->state); } else { struct spa_video_info info = { 0 }; if ((res = spa_format_parse(format, &info.media_type, &info.media_subtype)) < 0) return res; if (info.media_type != SPA_MEDIA_TYPE_video && info.media_subtype != SPA_MEDIA_SUBTYPE_dsp) return -EINVAL; if (spa_format_video_dsp_parse(format, &info.info.dsp) < 0) return -EINVAL; if (info.info.dsp.format != SPA_VIDEO_FORMAT_DSP_F32) return -EINVAL; this->state.constants.width = this->position->video.size.width; this->state.constants.height = this->position->video.size.height; bool modifier_fixed = false; if (info.info.dsp.flags & SPA_VIDEO_FLAG_MODIFIER && info.info.dsp.flags & SPA_VIDEO_FLAG_MODIFIER_FIXATION_REQUIRED) { const struct spa_pod_prop *mod_prop; if ((mod_prop = spa_pod_find_prop(format, NULL, SPA_FORMAT_VIDEO_modifier)) == NULL) return -EINVAL; const struct spa_pod *mod_pod = &mod_prop->value; uint32_t modifierCount = SPA_POD_CHOICE_N_VALUES(mod_pod); uint64_t *modifiers = SPA_POD_CHOICE_VALUES(mod_pod); if (modifierCount <= 1) return -EINVAL; // SPA_POD_CHOICE carries the "preferred" value at position 0 modifierCount -= 1; modifiers++; uint64_t fixed_modifier; if (spa_vulkan_compute_fixate_modifier(&this->state, &this->state.streams[0], &info.info.dsp, modifierCount, modifiers, &fixed_modifier) != 0) return -EINVAL; spa_log_info(this->log, "modifier fixated %"PRIu64, fixed_modifier); info.info.dsp.modifier = fixed_modifier; info.info.dsp.flags &= ~SPA_VIDEO_FLAG_MODIFIER_FIXATION_REQUIRED; modifier_fixed = true; } if (info.info.dsp.flags & SPA_VIDEO_FLAG_MODIFIER) { port->info.flags |= SPA_PORT_FLAG_CAN_ALLOC_BUFFERS; } else { port->info.flags &= ~SPA_PORT_FLAG_CAN_ALLOC_BUFFERS; } port->info.change_mask |= SPA_PORT_CHANGE_MASK_FLAGS; port->current_format = info; port->have_format = true; spa_vulkan_compute_prepare(&this->state); if (modifier_fixed) { port->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; port->params[IDX_EnumFormat].flags ^= SPA_PARAM_INFO_SERIAL; emit_port_info(this, port, false); return 0; } } port->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; if (port->have_format) { port->params[IDX_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_READWRITE); port->params[IDX_Buffer] = SPA_PARAM_INFO(SPA_PARAM_Buffers, SPA_PARAM_INFO_READ); } else { port->params[IDX_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); port->params[IDX_Buffer] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); } emit_port_info(this, port, false); return 0; } static int impl_node_port_set_param(void *object, enum spa_direction direction, uint32_t port_id, uint32_t id, uint32_t flags, const struct spa_pod *param) { struct impl *this = object; struct port *port; int res; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(CHECK_PORT(node, direction, port_id), -EINVAL); port = &this->port; switch (id) { case SPA_PARAM_Format: res = port_set_format(this, port, flags, param); break; default: return -ENOENT; } return res; } static int impl_node_port_use_buffers(void *object, enum spa_direction direction, uint32_t port_id, uint32_t flags, struct spa_buffer **buffers, uint32_t n_buffers) { struct impl *this = object; struct port *port; uint32_t i; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); port = &this->port; clear_buffers(this, port); if (n_buffers > 0 && !port->have_format) return -EIO; if (n_buffers > MAX_BUFFERS) return -ENOSPC; for (i = 0; i < n_buffers; i++) { struct buffer *b; b = &port->buffers[i]; b->id = i; b->outbuf = buffers[i]; b->flags = 0; b->h = spa_buffer_find_meta_data(buffers[i], SPA_META_Header, sizeof(*b->h)); spa_log_info(this->log, "%p: %d:%d add buffer %p", port, direction, port_id, b); spa_list_append(&port->empty, &b->link); } spa_vulkan_compute_use_buffers(&this->state, &this->state.streams[0], flags, &port->current_format.info.dsp, n_buffers, buffers); port->n_buffers = n_buffers; return 0; } static int impl_node_port_set_io(void *object, enum spa_direction direction, uint32_t port_id, uint32_t id, void *data, size_t size) { struct impl *this = object; struct port *port; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); port = &this->port; switch (id) { case SPA_IO_Buffers: port->io = data; break; default: return -ENOENT; } return 0; } static int impl_node_port_reuse_buffer(void *object, uint32_t port_id, uint32_t buffer_id) { struct impl *this = object; struct port *port; spa_return_val_if_fail(this != NULL, -EINVAL); spa_return_val_if_fail(port_id == 0, -EINVAL); port = &this->port; spa_return_val_if_fail(buffer_id < port->n_buffers, -EINVAL); reuse_buffer(this, port, buffer_id); return 0; } static int impl_node_process(void *object) { struct impl *this = object; struct port *port; struct spa_io_buffers *io; spa_return_val_if_fail(this != NULL, -EINVAL); port = &this->port; if ((io = port->io) == NULL) return -EIO; if (io->status == SPA_STATUS_HAVE_DATA) return SPA_STATUS_HAVE_DATA; if (io->buffer_id < port->n_buffers) { reuse_buffer(this, port, io->buffer_id); io->buffer_id = SPA_ID_INVALID; } if (!this->props.live) return make_buffer(this); else return SPA_STATUS_OK; } static const struct spa_node_methods impl_node = { SPA_VERSION_NODE_METHODS, .add_listener = impl_node_add_listener, .set_callbacks = impl_node_set_callbacks, .enum_params = impl_node_enum_params, .set_param = impl_node_set_param, .set_io = impl_node_set_io, .send_command = impl_node_send_command, .add_port = impl_node_add_port, .remove_port = impl_node_remove_port, .port_enum_params = impl_node_port_enum_params, .port_set_param = impl_node_port_set_param, .port_use_buffers = impl_node_port_use_buffers, .port_set_io = impl_node_port_set_io, .port_reuse_buffer = impl_node_port_reuse_buffer, .process = impl_node_process, }; static int impl_get_interface(struct spa_handle *handle, const char *type, void **interface) { struct impl *this; spa_return_val_if_fail(handle != NULL, -EINVAL); spa_return_val_if_fail(interface != NULL, -EINVAL); this = (struct impl *) handle; if (spa_streq(type, SPA_TYPE_INTERFACE_Node)) *interface = &this->node; else return -ENOENT; return 0; } static int do_remove_timer(struct spa_loop *loop, bool async, uint32_t seq, const void *data, size_t size, void *user_data) { struct impl *this = user_data; spa_loop_remove_source(this->data_loop, &this->timer_source); return 0; } static int impl_clear(struct spa_handle *handle) { struct impl *this; spa_return_val_if_fail(handle != NULL, -EINVAL); this = (struct impl *) handle; spa_vulkan_compute_deinit(&this->state); if (this->data_loop) spa_loop_locked(this->data_loop, do_remove_timer, 0, NULL, 0, this); spa_system_close(this->data_system, this->timer_source.fd); return 0; } static size_t impl_get_size(const struct spa_handle_factory *factory, const struct spa_dict *params) { return sizeof(struct impl); } static int impl_init(const struct spa_handle_factory *factory, struct spa_handle *handle, const struct spa_dict *info, const struct spa_support *support, uint32_t n_support) { struct impl *this; struct port *port; spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(handle != NULL, -EINVAL); handle->get_interface = impl_get_interface; handle->clear = impl_clear; this = (struct impl *) handle; this->log = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log); this->data_loop = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_DataLoop); this->data_system = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_DataSystem); spa_hook_list_init(&this->hooks); this->node.iface = SPA_INTERFACE_INIT( SPA_TYPE_INTERFACE_Node, SPA_VERSION_NODE, &impl_node, this); this->info_all = SPA_NODE_CHANGE_MASK_FLAGS | SPA_NODE_CHANGE_MASK_PROPS | SPA_NODE_CHANGE_MASK_PARAMS; this->info = SPA_NODE_INFO_INIT(); this->info.max_output_ports = 1; this->info.flags = SPA_NODE_FLAG_RT; this->params[IDX_PropInfo] = SPA_PARAM_INFO(SPA_PARAM_PropInfo, SPA_PARAM_INFO_READ); this->params[IDX_Props] = SPA_PARAM_INFO(SPA_PARAM_Props, SPA_PARAM_INFO_READWRITE); this->info.params = this->params; this->info.n_params = N_NODE_PARAMS; reset_props(&this->props); this->timer_source.func = on_output; this->timer_source.data = this; this->timer_source.fd = spa_system_timerfd_create(this->data_system, CLOCK_MONOTONIC, SPA_FD_CLOEXEC | SPA_FD_NONBLOCK); this->timer_source.mask = SPA_IO_IN; this->timer_source.rmask = 0; this->timerspec.it_value.tv_sec = 0; this->timerspec.it_value.tv_nsec = 0; this->timerspec.it_interval.tv_sec = 0; this->timerspec.it_interval.tv_nsec = 0; if (this->data_loop) spa_loop_add_source(this->data_loop, &this->timer_source); port = &this->port; port->info_all = SPA_PORT_CHANGE_MASK_FLAGS | SPA_PORT_CHANGE_MASK_PARAMS | SPA_PORT_CHANGE_MASK_PROPS; port->info = SPA_PORT_INFO_INIT(); port->info.flags = SPA_PORT_FLAG_NO_REF; if (this->props.live) port->info.flags |= SPA_PORT_FLAG_LIVE; port->params[IDX_EnumFormat] = SPA_PARAM_INFO(SPA_PARAM_EnumFormat, SPA_PARAM_INFO_READ); port->params[IDX_Meta] = SPA_PARAM_INFO(SPA_PARAM_Meta, SPA_PARAM_INFO_READ); port->params[IDX_IO] = SPA_PARAM_INFO(SPA_PARAM_IO, SPA_PARAM_INFO_READ); port->params[IDX_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); port->params[IDX_Buffer] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); port->info.params = port->params; port->info.n_params = N_PORT_PARAMS; spa_list_init(&port->empty); spa_list_init(&port->ready); this->state.log = this->log; spa_vulkan_compute_init_stream(&this->state, &this->state.streams[0], SPA_DIRECTION_OUTPUT, NULL); this->state.shaderName = "spa/plugins/vulkan/shaders/main.spv"; this->state.n_streams = 1; spa_vulkan_compute_init(&this->state); return 0; } static const struct spa_interface_info impl_interfaces[] = { {SPA_TYPE_INTERFACE_Node,}, }; static int impl_enum_interface_info(const struct spa_handle_factory *factory, const struct spa_interface_info **info, uint32_t *index) { spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(info != NULL, -EINVAL); spa_return_val_if_fail(index != NULL, -EINVAL); switch (*index) { case 0: *info = &impl_interfaces[*index]; break; default: return 0; } (*index)++; return 1; } static const struct spa_dict_item info_items[] = { { SPA_KEY_FACTORY_AUTHOR, "Wim Taymans " }, { SPA_KEY_FACTORY_DESCRIPTION, "Generate video frames using a vulkan compute shader" }, }; static const struct spa_dict info = SPA_DICT_INIT_ARRAY(info_items); const struct spa_handle_factory spa_vulkan_compute_source_factory = { SPA_VERSION_HANDLE_FACTORY, SPA_NAME_API_VULKAN_COMPUTE_SOURCE, &info, impl_get_size, impl_init, impl_enum_interface_info, }; pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/vulkan/vulkan-compute-utils.c000066400000000000000000000563711511204443500305320ustar00rootroot00000000000000/* Spa */ /* SPDX-FileCopyrightText: Copyright © 2019 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #include #include #include #include #if !defined(__FreeBSD__) && !defined(__MidnightBSD__) #include #endif #include #include #include #include #include #include #include #include #include #include "vulkan-compute-utils.h" #include "vulkan-utils.h" #include "utils.h" #include "pixel-formats.h" #define VULKAN_INSTANCE_FUNCTION(name) \ PFN_##name name = (PFN_##name)vkGetInstanceProcAddr(s->base.instance, #name) static int createDescriptors(struct vulkan_compute_state *s) { uint32_t i; VkDescriptorPoolSize descriptorPoolSizes[2] = { { .type = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, .descriptorCount = 1, }, { .type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, .descriptorCount = s->n_streams - 1, }, }; const VkDescriptorPoolCreateInfo descriptorPoolCreateInfo = { .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO, .maxSets = s->n_streams, .poolSizeCount = s->n_streams > 1 ? 2 : 1, .pPoolSizes = descriptorPoolSizes, }; VK_CHECK_RESULT(vkCreateDescriptorPool(s->base.device, &descriptorPoolCreateInfo, NULL, &s->descriptorPool)); VkDescriptorSetLayoutBinding descriptorSetLayoutBinding[s->n_streams]; descriptorSetLayoutBinding[0] = (VkDescriptorSetLayoutBinding) { .binding = 0, .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, .descriptorCount = 1, .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT }; for (i = 1; i < s->n_streams; i++) { descriptorSetLayoutBinding[i] = (VkDescriptorSetLayoutBinding) { .binding = i, .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, .descriptorCount = 1, .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT }; }; const VkDescriptorSetLayoutCreateInfo descriptorSetLayoutCreateInfo = { .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, .bindingCount = s->n_streams, .pBindings = descriptorSetLayoutBinding }; VK_CHECK_RESULT(vkCreateDescriptorSetLayout(s->base.device, &descriptorSetLayoutCreateInfo, NULL, &s->descriptorSetLayout)); const VkDescriptorSetAllocateInfo descriptorSetAllocateInfo = { .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO, .descriptorPool = s->descriptorPool, .descriptorSetCount = 1, .pSetLayouts = &s->descriptorSetLayout }; VK_CHECK_RESULT(vkAllocateDescriptorSets(s->base.device, &descriptorSetAllocateInfo, &s->descriptorSet)); const VkSamplerCreateInfo samplerInfo = { .sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO, .magFilter = VK_FILTER_LINEAR, .minFilter = VK_FILTER_LINEAR, .mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR, .addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, .addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, .addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, .borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_BLACK, .unnormalizedCoordinates = VK_FALSE, .compareEnable = VK_FALSE, .compareOp = VK_COMPARE_OP_ALWAYS, .mipLodBias = 0.0f, .minLod = 0, .maxLod = 5, }; VK_CHECK_RESULT(vkCreateSampler(s->base.device, &samplerInfo, NULL, &s->sampler)); return 0; } static int updateDescriptors(struct vulkan_compute_state *s) { uint32_t i; VkDescriptorImageInfo descriptorImageInfo[s->n_streams]; VkWriteDescriptorSet writeDescriptorSet[s->n_streams]; uint32_t descriptorSetLen = 0; for (i = 0; i < s->n_streams; i++) { struct vulkan_stream *p = &s->streams[i]; if (p->current_buffer_id == p->pending_buffer_id || p->pending_buffer_id == SPA_ID_INVALID) continue; p->current_buffer_id = p->pending_buffer_id; p->busy_buffer_id = p->current_buffer_id; p->pending_buffer_id = SPA_ID_INVALID; descriptorImageInfo[descriptorSetLen] = (VkDescriptorImageInfo) { .sampler = s->sampler, .imageView = p->buffers[p->current_buffer_id].view, .imageLayout = VK_IMAGE_LAYOUT_GENERAL, }; writeDescriptorSet[descriptorSetLen] = (VkWriteDescriptorSet) { .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, .dstSet = s->descriptorSet, .dstBinding = i, .descriptorCount = 1, .descriptorType = i == 0 ? VK_DESCRIPTOR_TYPE_STORAGE_IMAGE : VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, .pImageInfo = &descriptorImageInfo[i], }; descriptorSetLen++; } vkUpdateDescriptorSets(s->base.device, descriptorSetLen, writeDescriptorSet, 0, NULL); return 0; } static VkShaderModule createShaderModule(struct vulkan_compute_state *s, const char* shaderFile) { VkShaderModule shaderModule = VK_NULL_HANDLE; VkResult result; void *data; int fd; struct stat stat; if ((fd = open(shaderFile, 0, O_RDONLY)) == -1) { spa_log_error(s->log, "can't open %s: %m", shaderFile); return VK_NULL_HANDLE; } if (fstat(fd, &stat) < 0) { spa_log_error(s->log, "can't stat %s: %m", shaderFile); close(fd); return VK_NULL_HANDLE; } data = mmap(NULL, stat.st_size, PROT_READ, MAP_PRIVATE, fd, 0); const VkShaderModuleCreateInfo shaderModuleCreateInfo = { .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO, .codeSize = stat.st_size, .pCode = data, }; result = vkCreateShaderModule(s->base.device, &shaderModuleCreateInfo, 0, &shaderModule); munmap(data, stat.st_size); close(fd); if (result != VK_SUCCESS) { spa_log_error(s->log, "can't create shader %s: %m", shaderFile); return VK_NULL_HANDLE; } return shaderModule; } static int createComputePipeline(struct vulkan_compute_state *s, const char *shader_file) { static const VkPushConstantRange range = { .stageFlags = VK_SHADER_STAGE_COMPUTE_BIT, .offset = 0, .size = sizeof(struct push_constants) }; const VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = { .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, .setLayoutCount = 1, .pSetLayouts = &s->descriptorSetLayout, .pushConstantRangeCount = 1, .pPushConstantRanges = &range, }; VK_CHECK_RESULT(vkCreatePipelineLayout(s->base.device, &pipelineLayoutCreateInfo, NULL, &s->pipelineLayout)); s->computeShaderModule = createShaderModule(s, shader_file); if (s->computeShaderModule == VK_NULL_HANDLE) return -ENOENT; const VkPipelineShaderStageCreateInfo shaderStageCreateInfo = { .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, .stage = VK_SHADER_STAGE_COMPUTE_BIT, .module = s->computeShaderModule, .pName = "main", }; const VkComputePipelineCreateInfo pipelineCreateInfo = { .sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO, .stage = shaderStageCreateInfo, .layout = s->pipelineLayout, }; VK_CHECK_RESULT(vkCreateComputePipelines(s->base.device, VK_NULL_HANDLE, 1, &pipelineCreateInfo, NULL, &s->pipeline)); return 0; } static int createCommandBuffer(struct vulkan_compute_state *s) { CHECK(vulkan_commandPool_create(&s->base, &s->commandPool)); CHECK(vulkan_commandBuffer_create(&s->base, s->commandPool, &s->commandBuffer)); return 0; } static int runImportSHMBuffers(struct vulkan_compute_state *s) { for (uint32_t i = 0; i < s->n_streams; i++) { struct vulkan_stream *p = &s->streams[i]; if (p->direction == SPA_DIRECTION_OUTPUT) continue; struct pixel_format_info pixel_info; CHECK(get_pixel_format_info(p->format, &pixel_info)); if (p->spa_buffers[p->current_buffer_id]->datas[0].type == SPA_DATA_MemPtr) { struct vulkan_buffer *vk_buf = &p->buffers[p->current_buffer_id]; struct spa_buffer *spa_buf = p->spa_buffers[p->current_buffer_id]; VkBufferImageCopy copy; struct vulkan_write_pixels_info writeInfo = { .data = spa_buf->datas[0].data, .offset = spa_buf->datas[0].chunk->offset, .stride = spa_buf->datas[0].chunk->stride, .bytes_per_pixel = pixel_info.bpp, .size.width = s->constants.width, .size.height = s->constants.height, .copies = ©, }; CHECK(vulkan_write_pixels(&s->base, &writeInfo, &s->staging_buffer)); vkCmdCopyBufferToImage(s->commandBuffer, s->staging_buffer.buffer, vk_buf->image, VK_IMAGE_LAYOUT_GENERAL, 1, ©); } } return 0; } static int runExportSHMBuffers(struct vulkan_compute_state *s) { for (uint32_t i = 0; i < s->n_streams; i++) { struct vulkan_stream *p = &s->streams[i]; if (p->direction == SPA_DIRECTION_INPUT) continue; struct pixel_format_info pixel_info; CHECK(get_pixel_format_info(p->format, &pixel_info)); if (p->spa_buffers[p->current_buffer_id]->datas[0].type == SPA_DATA_MemPtr) { struct spa_buffer *spa_buf = p->spa_buffers[p->current_buffer_id]; struct vulkan_read_pixels_info readInfo = { .data = spa_buf->datas[0].data, .offset = spa_buf->datas[0].chunk->offset, .stride = spa_buf->datas[0].chunk->stride, .bytes_per_pixel = pixel_info.bpp, .size.width = s->constants.width, .size.height = s->constants.height, }; CHECK(vulkan_read_pixels(&s->base, &readInfo, &p->buffers[p->current_buffer_id])); } } return 0; } /** runCommandBuffer * The return value of this functions means the following: * ret < 0: Error * ret = 0: queueSubmit was successful, but manual synchronization is required * ret = 1: queueSubmit was successful and buffers can be released without synchronization */ static int runCommandBuffer(struct vulkan_compute_state *s) { VULKAN_INSTANCE_FUNCTION(vkQueueSubmit2KHR); VULKAN_INSTANCE_FUNCTION(vkGetSemaphoreFdKHR); static const VkCommandBufferBeginInfo beginInfo = { .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT, }; VK_CHECK_RESULT(vkBeginCommandBuffer(s->commandBuffer, &beginInfo)); CHECK(runImportSHMBuffers(s)); vkCmdBindPipeline(s->commandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, s->pipeline); vkCmdPushConstants (s->commandBuffer, s->pipelineLayout, VK_SHADER_STAGE_COMPUTE_BIT, 0, sizeof(struct push_constants), (const void *) &s->constants); vkCmdBindDescriptorSets(s->commandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, s->pipelineLayout, 0, 1, &s->descriptorSet, 0, NULL); vkCmdDispatch(s->commandBuffer, (uint32_t)ceil(s->constants.width / (float)WORKGROUP_SIZE), (uint32_t)ceil(s->constants.height / (float)WORKGROUP_SIZE), 1); VkImageMemoryBarrier acquire_barrier[s->n_streams]; VkImageMemoryBarrier release_barrier[s->n_streams]; VkSemaphoreSubmitInfo semaphore_wait_info[s->n_streams]; uint32_t semaphore_wait_info_len = 0; VkSemaphoreSubmitInfo semaphore_signal_info[1]; uint32_t semaphore_signal_info_len = 0; uint32_t i; for (i = 0; i < s->n_streams; i++) { struct vulkan_stream *p = &s->streams[i]; struct vulkan_buffer *current_buffer = &p->buffers[p->current_buffer_id]; struct spa_buffer *current_spa_buffer = p->spa_buffers[p->current_buffer_id]; VkAccessFlags access_flags; if (p->direction == SPA_DIRECTION_INPUT) { access_flags = VK_ACCESS_SHADER_READ_BIT; } else { access_flags = VK_ACCESS_SHADER_WRITE_BIT; } acquire_barrier[i]= (VkImageMemoryBarrier) { .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, .srcQueueFamilyIndex = VK_QUEUE_FAMILY_FOREIGN_EXT, .dstQueueFamilyIndex = s->base.queueFamilyIndex, .image = current_buffer->image, .oldLayout = VK_IMAGE_LAYOUT_UNDEFINED, .newLayout = VK_IMAGE_LAYOUT_GENERAL, .srcAccessMask = 0, .dstAccessMask = access_flags, .subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, .subresourceRange.levelCount = 1, .subresourceRange.layerCount = 1, }; release_barrier[i]= (VkImageMemoryBarrier) { .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, .srcQueueFamilyIndex = s->base.queueFamilyIndex, .dstQueueFamilyIndex = VK_QUEUE_FAMILY_FOREIGN_EXT, .image = current_buffer->image, .oldLayout = VK_IMAGE_LAYOUT_GENERAL, .newLayout = VK_IMAGE_LAYOUT_GENERAL, .srcAccessMask = access_flags, .dstAccessMask = 0, .subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, .subresourceRange.levelCount = 1, .subresourceRange.layerCount = 1, }; if (current_spa_buffer->datas[0].type != SPA_DATA_DmaBuf) continue; if (vulkan_sync_foreign_dmabuf(&s->base, current_buffer) < 0) { spa_log_warn(s->log, "Failed to wait for foreign buffer DMA-BUF fence"); } else { if (current_buffer->foreign_semaphore != VK_NULL_HANDLE) { semaphore_wait_info[semaphore_wait_info_len++] = (VkSemaphoreSubmitInfo) { .sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO, .semaphore = current_buffer->foreign_semaphore, .stageMask = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, }; } } } vkCmdPipelineBarrier(s->commandBuffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, 0, NULL, 0, NULL, s->n_streams, acquire_barrier); vkCmdPipelineBarrier(s->commandBuffer, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, 0, 0, NULL, 0, NULL, s->n_streams, release_barrier); VK_CHECK_RESULT(vkEndCommandBuffer(s->commandBuffer)); VK_CHECK_RESULT(vkResetFences(s->base.device, 1, &s->fence)); if (s->pipelineSemaphore == VK_NULL_HANDLE) { VkExportSemaphoreCreateInfo export_info = { .sType = VK_STRUCTURE_TYPE_EXPORT_SEMAPHORE_CREATE_INFO, .handleTypes = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT, }; VkSemaphoreCreateInfo semaphore_info = { .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, .pNext = &export_info, }; VK_CHECK_RESULT(vkCreateSemaphore(s->base.device, &semaphore_info, NULL, &s->pipelineSemaphore)); } semaphore_signal_info[semaphore_signal_info_len++] = (VkSemaphoreSubmitInfo) { .sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO, .semaphore = s->pipelineSemaphore, }; VkCommandBufferSubmitInfoKHR commandBufferInfo = { .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_SUBMIT_INFO, .commandBuffer = s->commandBuffer, }; const VkSubmitInfo2KHR submitInfo = { .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO_2_KHR, .commandBufferInfoCount = 1, .pCommandBufferInfos = &commandBufferInfo, .waitSemaphoreInfoCount = semaphore_wait_info_len, .pWaitSemaphoreInfos = semaphore_wait_info, .signalSemaphoreInfoCount = semaphore_signal_info_len, .pSignalSemaphoreInfos = semaphore_signal_info, }; VK_CHECK_RESULT(vkQueueSubmit2KHR(s->base.queue, 1, &submitInfo, s->fence)); s->started = true; VkSemaphoreGetFdInfoKHR get_fence_fd_info = { .sType = VK_STRUCTURE_TYPE_SEMAPHORE_GET_FD_INFO_KHR, .semaphore = s->pipelineSemaphore, .handleType = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT, }; int sync_file_fd = -1; VK_CHECK_RESULT(vkGetSemaphoreFdKHR(s->base.device, &get_fence_fd_info, &sync_file_fd)); int ret = 1; for (uint32_t i = 0; i < s->n_streams; i++) { struct vulkan_stream *p = &s->streams[i]; struct spa_buffer *current_spa_buffer = p->spa_buffers[p->current_buffer_id]; if (current_spa_buffer->datas[0].type != SPA_DATA_DmaBuf) continue; if (!vulkan_sync_export_dmabuf(&s->base, &p->buffers[p->current_buffer_id], sync_file_fd)) { ret = 0; } } close(sync_file_fd); return ret; } static void clear_buffers(struct vulkan_compute_state *s, struct vulkan_stream *p) { uint32_t i; for (i = 0; i < p->n_buffers; i++) { vulkan_buffer_clear(&s->base, &p->buffers[i]); p->spa_buffers[i] = NULL; } p->n_buffers = 0; if (p->direction == SPA_DIRECTION_INPUT) { vulkan_staging_buffer_destroy(&s->base, &s->staging_buffer); s->staging_buffer.buffer = VK_NULL_HANDLE; } } static void clear_streams(struct vulkan_compute_state *s) { uint32_t i; for (i = 0; i < s->n_streams; i++) { struct vulkan_stream *p = &s->streams[i]; clear_buffers(s, p); } } int spa_vulkan_compute_fixate_modifier(struct vulkan_compute_state *s, struct vulkan_stream *p, struct spa_video_info_dsp *dsp_info, uint32_t modifierCount, uint64_t *modifiers, uint64_t *modifier) { VkFormat format = vulkan_id_to_vkformat(dsp_info->format); if (format == VK_FORMAT_UNDEFINED) { return -1; } struct dmabuf_fixation_info fixation_info = { .format = format, .modifierCount = modifierCount, .modifiers = modifiers, .size.width = s->constants.width, .size.height = s->constants.height, .usage = VK_IMAGE_USAGE_STORAGE_BIT, }; return vulkan_fixate_modifier(&s->base, &fixation_info, modifier); } int spa_vulkan_compute_use_buffers(struct vulkan_compute_state *s, struct vulkan_stream *p, uint32_t flags, struct spa_video_info_dsp *dsp_info, uint32_t n_buffers, struct spa_buffer **buffers) { VkFormat format = vulkan_id_to_vkformat(dsp_info->format); if (format == VK_FORMAT_UNDEFINED) return -1; vulkan_wait_idle(&s->base); clear_buffers(s, p); p->format = SPA_VIDEO_FORMAT_UNKNOWN; if (n_buffers == 0) return 0; bool alloc = flags & SPA_NODE_BUFFERS_FLAG_ALLOC; int ret; for (uint32_t i = 0; i < n_buffers; i++) { if (alloc) { if (SPA_FLAG_IS_SET(buffers[i]->datas[0].type, 1<modifier, .size.width = s->constants.width, .size.height = s->constants.height, .usage = p->direction == SPA_DIRECTION_OUTPUT ? VK_IMAGE_USAGE_STORAGE_BIT : VK_IMAGE_USAGE_SAMPLED_BIT, .spa_buf = buffers[i], }; struct vulkan_modifier_info *modifierInfo = vulkan_modifierInfo_find(&s->formatInfos, format, dsp_info->modifier); CHECK(vulkan_validate_dmabuf_properties(modifierInfo, &dmabufInfo.spa_buf->n_datas, &dmabufInfo.size)); ret = vulkan_create_dmabuf(&s->base, &dmabufInfo, &p->buffers[i]); } else { spa_log_error(s->log, "Unsupported buffer type mask %d", buffers[i]->datas[0].type); return -1; } } else { switch (buffers[i]->datas[0].type) { case SPA_DATA_DmaBuf:; struct external_buffer_info dmabufInfo = { .format = format, .modifier = dsp_info->modifier, .size.width = s->constants.width, .size.height = s->constants.height, .usage = p->direction == SPA_DIRECTION_OUTPUT ? VK_IMAGE_USAGE_STORAGE_BIT : VK_IMAGE_USAGE_SAMPLED_BIT, .spa_buf = buffers[i], }; struct vulkan_modifier_info *modifierInfo = vulkan_modifierInfo_find(&s->formatInfos, format, dsp_info->modifier); CHECK(vulkan_validate_dmabuf_properties(modifierInfo, &dmabufInfo.spa_buf->n_datas, &dmabufInfo.size)); ret = vulkan_import_dmabuf(&s->base, &dmabufInfo, &p->buffers[i]); break; case SPA_DATA_MemPtr:; struct external_buffer_info memptrInfo = { .format = format, .size.width = s->constants.width, .size.height = s->constants.height, .usage = p->direction == SPA_DIRECTION_OUTPUT ? VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT : VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT, .spa_buf = buffers[i], }; ret = vulkan_import_memptr(&s->base, &memptrInfo, &p->buffers[i]); break; default: spa_log_error(s->log, "Unsupported buffer type %d", buffers[i]->datas[0].type); return -1; } } if (ret != 0) { spa_log_error(s->log, "Failed to use buffer %d", i); return ret; } p->spa_buffers[i] = buffers[i]; p->n_buffers++; } if (p->direction == SPA_DIRECTION_INPUT && buffers[0]->datas[0].type == SPA_DATA_MemPtr) { ret = vulkan_staging_buffer_create(&s->base, buffers[0]->datas[0].maxsize, &s->staging_buffer); if (ret < 0) { spa_log_error(s->log, "Failed to create staging buffer"); return ret; } } p->format = dsp_info->format; return 0; } int spa_vulkan_compute_enumerate_formats(struct vulkan_compute_state *s, uint32_t index, uint32_t caps, struct spa_pod **param, struct spa_pod_builder *builder) { uint32_t fmt_idx; bool has_modifier; if (!find_EnumFormatInfo(&s->formatInfos, index, caps, &fmt_idx, &has_modifier)) return 0; *param = build_dsp_EnumFormat(&s->formatInfos.infos[fmt_idx], has_modifier, builder); return 1; } static int vulkan_stream_init(struct vulkan_stream *stream, enum spa_direction direction, struct spa_dict *props) { spa_zero(*stream); stream->direction = direction; stream->current_buffer_id = SPA_ID_INVALID; stream->busy_buffer_id = SPA_ID_INVALID; stream->ready_buffer_id = SPA_ID_INVALID; return 0; } int spa_vulkan_compute_init_stream(struct vulkan_compute_state *s, struct vulkan_stream *stream, enum spa_direction direction, struct spa_dict *props) { return vulkan_stream_init(stream, direction, props); } int spa_vulkan_compute_prepare(struct vulkan_compute_state *s) { if (!s->prepared) { CHECK(vulkan_fence_create(&s->base, &s->fence)); CHECK(createDescriptors(s)); CHECK(createComputePipeline(s, s->shaderName)); CHECK(createCommandBuffer(s)); s->prepared = true; } return 0; } int spa_vulkan_compute_unprepare(struct vulkan_compute_state *s) { if (s->prepared) { vkDestroyShaderModule(s->base.device, s->computeShaderModule, NULL); vkDestroySampler(s->base.device, s->sampler, NULL); vkDestroyDescriptorPool(s->base.device, s->descriptorPool, NULL); vkDestroyDescriptorSetLayout(s->base.device, s->descriptorSetLayout, NULL); vkDestroyPipelineLayout(s->base.device, s->pipelineLayout, NULL); vkDestroyPipeline(s->base.device, s->pipeline, NULL); vkDestroyCommandPool(s->base.device, s->commandPool, NULL); vkDestroyFence(s->base.device, s->fence, NULL); s->prepared = false; } return 0; } int spa_vulkan_compute_start(struct vulkan_compute_state *s) { uint32_t i; for (i = 0; i < s->n_streams; i++) { struct vulkan_stream *p = &s->streams[i]; p->current_buffer_id = SPA_ID_INVALID; p->busy_buffer_id = SPA_ID_INVALID; p->ready_buffer_id = SPA_ID_INVALID; } return 0; } int spa_vulkan_compute_stop(struct vulkan_compute_state *s) { VK_CHECK_RESULT(vkDeviceWaitIdle(s->base.device)); clear_streams(s); s->started = false; return 0; } int spa_vulkan_compute_ready(struct vulkan_compute_state *s) { uint32_t i; VkResult result; if (!s->started) return 0; result = vkGetFenceStatus(s->base.device, s->fence); if (result == VK_NOT_READY) return -EBUSY; VK_CHECK_RESULT(result); s->started = false; for (i = 0; i < s->n_streams; i++) { struct vulkan_stream *p = &s->streams[i]; p->ready_buffer_id = p->busy_buffer_id; p->busy_buffer_id = SPA_ID_INVALID; } return 0; } int spa_vulkan_compute_process(struct vulkan_compute_state *s) { CHECK(updateDescriptors(s)); CHECK(runCommandBuffer(s)); VK_CHECK_RESULT(vkDeviceWaitIdle(s->base.device)); CHECK(runExportSHMBuffers(s)); return 0; } int spa_vulkan_compute_get_buffer_caps(struct vulkan_compute_state *s, enum spa_direction direction) { switch (direction) { case SPA_DIRECTION_INPUT: return VULKAN_BUFFER_TYPE_CAP_DMABUF | VULKAN_BUFFER_TYPE_CAP_SHM; case SPA_DIRECTION_OUTPUT: return VULKAN_BUFFER_TYPE_CAP_DMABUF | VULKAN_BUFFER_TYPE_CAP_SHM; } return 0; } struct vulkan_modifier_info *spa_vulkan_compute_get_modifier_info(struct vulkan_compute_state *s, struct spa_video_info_dsp *info) { VkFormat vk_format = vulkan_id_to_vkformat(info->format); return vulkan_modifierInfo_find(&s->formatInfos, vk_format, info->modifier); } int spa_vulkan_compute_init(struct vulkan_compute_state *s) { int ret; s->base.log = s->log; uint32_t dsp_formats[] = { SPA_VIDEO_FORMAT_DSP_F32 }; struct vulkan_base_info baseInfo = { .queueFlags = VK_QUEUE_COMPUTE_BIT, }; if ((ret = vulkan_base_init(&s->base, &baseInfo)) < 0) return ret; return vulkan_format_infos_init(&s->base, SPA_N_ELEMENTS(dsp_formats), dsp_formats, &s->formatInfos); } void spa_vulkan_compute_deinit(struct vulkan_compute_state *s) { vulkan_format_infos_deinit(&s->formatInfos); vulkan_base_deinit(&s->base); } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/vulkan/vulkan-compute-utils.h000066400000000000000000000057031511204443500305300ustar00rootroot00000000000000/* Spa */ /* SPDX-FileCopyrightText: Copyright © 2019 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #include #include "vulkan-utils.h" #define MAX_STREAMS 2 #define WORKGROUP_SIZE 32 struct pixel { float r, g, b, a; }; struct push_constants { float time; int frame; int width; int height; }; struct vulkan_stream { enum spa_direction direction; uint32_t pending_buffer_id; uint32_t current_buffer_id; uint32_t busy_buffer_id; uint32_t ready_buffer_id; uint32_t format; struct vulkan_buffer buffers[MAX_BUFFERS]; struct spa_buffer *spa_buffers[MAX_BUFFERS]; uint32_t n_buffers; }; struct vulkan_compute_state { struct spa_log *log; struct push_constants constants; struct vulkan_base base; struct vulkan_format_infos formatInfos; VkPipeline pipeline; VkPipelineLayout pipelineLayout; const char *shaderName; VkShaderModule computeShaderModule; VkCommandPool commandPool; VkCommandBuffer commandBuffer; struct vulkan_staging_buffer staging_buffer; VkFence fence; VkSemaphore pipelineSemaphore; unsigned int initialized:1; unsigned int prepared:1; unsigned int started:1; VkDescriptorPool descriptorPool; VkDescriptorSetLayout descriptorSetLayout; VkSampler sampler; uint32_t n_streams; VkDescriptorSet descriptorSet; struct vulkan_stream streams[MAX_STREAMS]; }; int spa_vulkan_compute_init_stream(struct vulkan_compute_state *s, struct vulkan_stream *stream, enum spa_direction, struct spa_dict *props); int spa_vulkan_compute_fixate_modifier(struct vulkan_compute_state *s, struct vulkan_stream *p, struct spa_video_info_dsp *dsp_info, uint32_t modifierCount, uint64_t *modifiers, uint64_t *modifier); int spa_vulkan_compute_use_buffers(struct vulkan_compute_state *s, struct vulkan_stream *stream, uint32_t flags, struct spa_video_info_dsp *dsp_info, uint32_t n_buffers, struct spa_buffer **buffers); int spa_vulkan_compute_enumerate_formats(struct vulkan_compute_state *s, uint32_t index, uint32_t caps, struct spa_pod **param, struct spa_pod_builder *builder); int spa_vulkan_compute_prepare(struct vulkan_compute_state *s); int spa_vulkan_compute_unprepare(struct vulkan_compute_state *s); int spa_vulkan_compute_start(struct vulkan_compute_state *s); int spa_vulkan_compute_stop(struct vulkan_compute_state *s); int spa_vulkan_compute_ready(struct vulkan_compute_state *s); int spa_vulkan_compute_process(struct vulkan_compute_state *s); int spa_vulkan_compute_cleanup(struct vulkan_compute_state *s); int spa_vulkan_compute_get_buffer_caps(struct vulkan_compute_state *s, enum spa_direction direction); struct vulkan_modifier_info *spa_vulkan_compute_get_modifier_info(struct vulkan_compute_state *s, struct spa_video_info_dsp *dsp_info); int spa_vulkan_compute_init(struct vulkan_compute_state *s); void spa_vulkan_compute_deinit(struct vulkan_compute_state *s); pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/vulkan/vulkan-types.h000066400000000000000000000021201511204443500270500ustar00rootroot00000000000000#pragma once #include #include #include #define MAX_BUFFERS 16 #define DMABUF_MAX_PLANES 1 enum buffer_type_caps { VULKAN_BUFFER_TYPE_CAP_SHM = 1<<0, VULKAN_BUFFER_TYPE_CAP_DMABUF = 1<<1, }; struct vulkan_modifier_info { VkDrmFormatModifierPropertiesEXT props; VkExtent2D max_extent; }; struct vulkan_format_info { uint32_t spa_format; VkFormat vk_format; uint32_t modifierCount; struct vulkan_modifier_info *infos; }; struct vulkan_format_infos { uint32_t formatCount; struct vulkan_format_info *infos; uint32_t formatsWithModifiersCount; }; struct vulkan_buffer { int fd; VkImage image; VkImageView view; VkDeviceMemory memory; VkSemaphore foreign_semaphore; }; struct vulkan_staging_buffer { VkBuffer buffer; VkDeviceMemory memory; }; struct vulkan_base_info { uint32_t queueFlags; }; struct vulkan_base { struct spa_log *log; VkInstance instance; VkPhysicalDevice physicalDevice; VkQueue queue; uint32_t queueFamilyIndex; VkDevice device; bool implicit_sync_interop; unsigned int initialized:1; }; pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/vulkan/vulkan-utils.c000066400000000000000000000747441511204443500270640ustar00rootroot00000000000000/* Spa */ /* SPDX-FileCopyrightText: Copyright © 2019 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #include #include #include #include #if !defined(__FreeBSD__) && !defined(__MidnightBSD__) #include #endif #include #include #include #include #include #include #include #include #include #include #include "vulkan-utils.h" #include "dmabuf.h" //#define ENABLE_VALIDATION #define VULKAN_INSTANCE_FUNCTION(name) \ PFN_##name name = (PFN_##name)vkGetInstanceProcAddr(s->instance, #name) static int vkresult_to_errno(VkResult result) { switch (result) { case VK_SUCCESS: case VK_EVENT_SET: case VK_EVENT_RESET: return 0; case VK_INCOMPLETE: return ENOSPC; case VK_NOT_READY: case VK_ERROR_NATIVE_WINDOW_IN_USE_KHR: return EBUSY; case VK_TIMEOUT: return ETIMEDOUT; case VK_ERROR_OUT_OF_HOST_MEMORY: case VK_ERROR_OUT_OF_DEVICE_MEMORY: case VK_ERROR_MEMORY_MAP_FAILED: case VK_ERROR_OUT_OF_POOL_MEMORY: case VK_ERROR_FRAGMENTED_POOL: #ifdef VK_ERROR_FRAGMENTATION_EXT case VK_ERROR_FRAGMENTATION_EXT: #endif return ENOMEM; case VK_ERROR_INITIALIZATION_FAILED: return EIO; case VK_ERROR_DEVICE_LOST: case VK_ERROR_SURFACE_LOST_KHR: #ifdef VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT case VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT: #endif return ENODEV; case VK_ERROR_LAYER_NOT_PRESENT: case VK_ERROR_EXTENSION_NOT_PRESENT: case VK_ERROR_FEATURE_NOT_PRESENT: return ENOENT; case VK_ERROR_INCOMPATIBLE_DRIVER: case VK_ERROR_FORMAT_NOT_SUPPORTED: case VK_ERROR_INCOMPATIBLE_DISPLAY_KHR: return ENOTSUP; case VK_ERROR_TOO_MANY_OBJECTS: return ENFILE; case VK_SUBOPTIMAL_KHR: case VK_ERROR_OUT_OF_DATE_KHR: return EIO; case VK_ERROR_INVALID_EXTERNAL_HANDLE: case VK_ERROR_INVALID_SHADER_NV: #ifdef VK_ERROR_VALIDATION_FAILED_EXT case VK_ERROR_VALIDATION_FAILED_EXT: #endif #ifdef VK_ERROR_INVALID_DRM_FORMAT_MODIFIER_PLANE_LAYOUT_EXT case VK_ERROR_INVALID_DRM_FORMAT_MODIFIER_PLANE_LAYOUT_EXT: #endif #ifdef VK_ERROR_INVALID_DEVICE_ADDRESS_EXT case VK_ERROR_INVALID_DEVICE_ADDRESS_EXT: #endif return EINVAL; #ifdef VK_ERROR_NOT_PERMITTED_EXT case VK_ERROR_NOT_PERMITTED_EXT: return EPERM; #endif default: return EIO; } } static struct { VkFormat format; uint32_t id; } vk_video_format_convs[] = { { VK_FORMAT_R32G32B32A32_SFLOAT, SPA_VIDEO_FORMAT_RGBA_F32 }, { VK_FORMAT_B8G8R8A8_SRGB, SPA_VIDEO_FORMAT_BGRA }, { VK_FORMAT_R8G8B8A8_SRGB, SPA_VIDEO_FORMAT_RGBA }, { VK_FORMAT_B8G8R8A8_SRGB, SPA_VIDEO_FORMAT_BGRx }, { VK_FORMAT_R8G8B8A8_SRGB, SPA_VIDEO_FORMAT_RGBx }, { VK_FORMAT_B8G8R8_SRGB, SPA_VIDEO_FORMAT_BGR }, { VK_FORMAT_R8G8B8_SRGB, SPA_VIDEO_FORMAT_RGB }, }; static int createInstance(struct vulkan_base *s) { static const VkApplicationInfo applicationInfo = { .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO, .pApplicationName = "PipeWire", .applicationVersion = 0, .pEngineName = "PipeWire Vulkan Engine", .engineVersion = 0, .apiVersion = VK_API_VERSION_1_1 }; static const char * const extensions[] = { VK_KHR_EXTERNAL_MEMORY_CAPABILITIES_EXTENSION_NAME }; static const char * const checkLayers[] = { #ifdef ENABLE_VALIDATION "VK_LAYER_KHRONOS_validation", #endif NULL }; uint32_t i, j, layerCount, n_layers = 0; const char *layers[1]; vkEnumerateInstanceLayerProperties(&layerCount, NULL); VkLayerProperties availableLayers[layerCount]; vkEnumerateInstanceLayerProperties(&layerCount, availableLayers); for (i = 0; i < layerCount; i++) { for (j = 0; j < SPA_N_ELEMENTS(checkLayers); j++) { if (spa_streq(availableLayers[i].layerName, checkLayers[j])) layers[n_layers++] = checkLayers[j]; } } const VkInstanceCreateInfo createInfo = { .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, .pApplicationInfo = &applicationInfo, .enabledExtensionCount = 1, .ppEnabledExtensionNames = extensions, .enabledLayerCount = n_layers, .ppEnabledLayerNames = layers, }; VK_CHECK_RESULT(vkCreateInstance(&createInfo, NULL, &s->instance)); return 0; } static int findPhysicalDevice(struct vulkan_base *s) { uint32_t deviceCount; VkPhysicalDevice *devices; vkEnumeratePhysicalDevices(s->instance, &deviceCount, NULL); if (deviceCount == 0) return -ENODEV; devices = alloca(deviceCount * sizeof(VkPhysicalDevice)); vkEnumeratePhysicalDevices(s->instance, &deviceCount, devices); s->physicalDevice = devices[0]; return 0; } static int getComputeQueueFamilyIndex(struct vulkan_base *s, uint32_t queueFlags, uint32_t *queueFamilyIndex) { uint32_t i, queueFamilyCount; VkQueueFamilyProperties *queueFamilies; vkGetPhysicalDeviceQueueFamilyProperties(s->physicalDevice, &queueFamilyCount, NULL); queueFamilies = alloca(queueFamilyCount * sizeof(VkQueueFamilyProperties)); vkGetPhysicalDeviceQueueFamilyProperties(s->physicalDevice, &queueFamilyCount, queueFamilies); for (i = 0; i < queueFamilyCount; i++) { VkQueueFamilyProperties props = queueFamilies[i]; if (props.queueCount > 0 && ((props.queueFlags & queueFlags) == queueFlags)) break; } if (i == queueFamilyCount) return -ENODEV; *queueFamilyIndex = i; return 0; } static int createDevice(struct vulkan_base *s, struct vulkan_base_info *info) { CHECK(getComputeQueueFamilyIndex(s, info->queueFlags, &s->queueFamilyIndex)); const VkDeviceQueueCreateInfo queueCreateInfo = { .sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, .queueFamilyIndex = s->queueFamilyIndex, .queueCount = 1, .pQueuePriorities = (const float[]) { 1.0f } }; const VkPhysicalDeviceSynchronization2FeaturesKHR sync2_features = { .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SYNCHRONIZATION_2_FEATURES_KHR, .synchronization2 = VK_TRUE, }; static const char * const extensions[] = { VK_KHR_EXTERNAL_MEMORY_EXTENSION_NAME, VK_KHR_EXTERNAL_MEMORY_FD_EXTENSION_NAME, VK_KHR_IMAGE_FORMAT_LIST_EXTENSION_NAME, VK_KHR_EXTERNAL_SEMAPHORE_FD_EXTENSION_NAME, VK_KHR_SYNCHRONIZATION_2_EXTENSION_NAME, VK_EXT_IMAGE_DRM_FORMAT_MODIFIER_EXTENSION_NAME, VK_EXT_QUEUE_FAMILY_FOREIGN_EXTENSION_NAME, }; const VkDeviceCreateInfo deviceCreateInfo = { .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, .queueCreateInfoCount = 1, .pQueueCreateInfos = &queueCreateInfo, .enabledExtensionCount = SPA_N_ELEMENTS(extensions), .ppEnabledExtensionNames = extensions, .pNext = &sync2_features, }; VK_CHECK_RESULT(vkCreateDevice(s->physicalDevice, &deviceCreateInfo, NULL, &s->device)); vkGetDeviceQueue(s->device, s->queueFamilyIndex, 0, &s->queue); return 0; } int vulkan_write_pixels(struct vulkan_base *s, struct vulkan_write_pixels_info *info, struct vulkan_staging_buffer *vk_sbuf) { void *vmap; VK_CHECK_RESULT(vkMapMemory(s->device, vk_sbuf->memory, 0, VK_WHOLE_SIZE, 0, &vmap)); // upload data memcpy(vmap, info->data, info->stride * info->size.height); info->copies[0] = (VkBufferImageCopy) { .imageExtent.width = info->size.width, .imageExtent.height = info->size.height, .imageExtent.depth = 1, .imageOffset.x = 0, .imageOffset.y = 0, .imageOffset.z = 0, .bufferOffset = 0, .bufferRowLength = info->size.width, .bufferImageHeight = info->size.height, .imageSubresource.mipLevel = 0, .imageSubresource.baseArrayLayer = 0, .imageSubresource.layerCount = 1, .imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, }; vkUnmapMemory(s->device, vk_sbuf->memory); return 0; } int vulkan_read_pixels(struct vulkan_base *s, struct vulkan_read_pixels_info *info, struct vulkan_buffer *vk_buf) { VkImageSubresource img_sub_res = { .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, .arrayLayer = 0, .mipLevel = 0, }; VkSubresourceLayout img_sub_layout; vkGetImageSubresourceLayout(s->device, vk_buf->image, &img_sub_res, &img_sub_layout); void *v; VK_CHECK_RESULT(vkMapMemory(s->device, vk_buf->memory, 0, VK_WHOLE_SIZE, 0, &v)); const char *d = (const char *)v + img_sub_layout.offset; unsigned char *p = (unsigned char *)info->data + info->offset; uint32_t pack_stride = img_sub_layout.rowPitch; spa_log_trace_fp(s->log, "Read pixels: %p to %p, stride: %d, width %d, height %d, offset %d, pack_stride %d", d, p, info->stride, info->size.width, info->size.height, info->offset, pack_stride); if (pack_stride == info->stride) { memcpy(p, d, info->stride * info->size.height); } else { for (uint32_t i = 0; i < info->size.height; i++) { memcpy(p + i * info->stride, d + i * pack_stride, info->size.width * info->bytes_per_pixel); } } vkUnmapMemory(s->device, vk_buf->memory); return 0; } int vulkan_sync_foreign_dmabuf(struct vulkan_base *s, struct vulkan_buffer *vk_buf) { if (!s->implicit_sync_interop) { return vulkan_buffer_wait_dmabuf_fence(s, vk_buf); } return vulkan_buffer_import_implicit_syncfd(s, vk_buf); } bool vulkan_sync_export_dmabuf(struct vulkan_base *s, struct vulkan_buffer *vk_buf, int sync_file_fd) { return vulkan_buffer_set_implicit_syncfd(s, vk_buf, sync_file_fd); } int vulkan_fence_create(struct vulkan_base *s, VkFence *fence) { VkFenceCreateInfo createInfo = { .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO, .flags = 0, }; VK_CHECK_RESULT(vkCreateFence(s->device, &createInfo, NULL, fence)); return 0; } int vulkan_commandPool_create(struct vulkan_base *s, VkCommandPool *commandPool) { const VkCommandPoolCreateInfo commandPoolCreateInfo = { .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, .flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT, .queueFamilyIndex = s->queueFamilyIndex, }; VK_CHECK_RESULT(vkCreateCommandPool(s->device, &commandPoolCreateInfo, NULL, commandPool)); return 0; } int vulkan_commandBuffer_create(struct vulkan_base *s, VkCommandPool commandPool, VkCommandBuffer *commandBuffer) { const VkCommandBufferAllocateInfo commandBufferAllocateInfo = { .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, .commandPool = commandPool, .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY, .commandBufferCount = 1, }; VK_CHECK_RESULT(vkAllocateCommandBuffers(s->device, &commandBufferAllocateInfo, commandBuffer)); return 0; } uint32_t vulkan_memoryType_find(struct vulkan_base *s, uint32_t memoryTypeBits, VkMemoryPropertyFlags properties) { uint32_t i; VkPhysicalDeviceMemoryProperties memoryProperties; vkGetPhysicalDeviceMemoryProperties(s->physicalDevice, &memoryProperties); for (i = 0; i < memoryProperties.memoryTypeCount; i++) { if ((memoryTypeBits & (1 << i)) && ((memoryProperties.memoryTypes[i].propertyFlags & properties) == properties)) return i; } return -1; } struct vulkan_format_info *vulkan_formatInfo_find(struct vulkan_format_infos *fmtInfo, VkFormat format) { for (uint32_t i = 0; i < fmtInfo->formatCount; i++) { if (fmtInfo->infos[i].vk_format == format) return &fmtInfo->infos[i]; } return NULL; } struct vulkan_modifier_info *vulkan_modifierInfo_find(struct vulkan_format_infos *fmtInfo, VkFormat format, uint64_t mod) { struct vulkan_format_info *f_info = vulkan_formatInfo_find(fmtInfo, format); if (!f_info) return NULL; for (uint32_t i = 0; i < f_info->modifierCount; i++) { if (f_info->infos[i].props.drmFormatModifier == mod) return &f_info->infos[i]; } return NULL; } int vulkan_buffer_get_implicit_syncfd(struct vulkan_base *s, struct vulkan_buffer *vk_buf) { if (!s->implicit_sync_interop) return -1; return dmabuf_export_sync_file(s->log, vk_buf->fd, DMA_BUF_SYNC_READ); } bool vulkan_buffer_set_implicit_syncfd(struct vulkan_base *s, struct vulkan_buffer *vk_buf, int sync_file_fd) { if (!s->implicit_sync_interop) return false; return dmabuf_import_sync_file(s->log, vk_buf->fd, DMA_BUF_SYNC_WRITE, sync_file_fd); } int vulkan_buffer_import_implicit_syncfd(struct vulkan_base *s, struct vulkan_buffer *vk_buf) { int sync_file_fd = vulkan_buffer_get_implicit_syncfd(s, vk_buf); if (sync_file_fd < 0) { spa_log_error(s->log, "Failed to extract for DMA-BUF fence"); return -1; } return vulkan_buffer_import_syncfd(s, vk_buf, sync_file_fd); } int vulkan_buffer_wait_dmabuf_fence(struct vulkan_base *s, struct vulkan_buffer *vk_buf) { struct pollfd pollfd = { .fd = vk_buf->fd, .events = POLLIN, }; int timeout_ms = 1000; int ret = poll(&pollfd, 1, timeout_ms); if (ret < 0) { spa_log_error(s->log, "Failed to wait for DMA-BUF fence"); return -1; } else if (ret == 0) { spa_log_error(s->log, "Timed out waiting for DMA-BUF fence"); return -1; } return 0; } int vulkan_buffer_import_syncfd(struct vulkan_base *s, struct vulkan_buffer *vk_buf, int sync_file_fd) { VULKAN_INSTANCE_FUNCTION(vkImportSemaphoreFdKHR); if (vk_buf->foreign_semaphore == VK_NULL_HANDLE) { VkSemaphoreCreateInfo semaphore_info = { .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, }; VK_CHECK_RESULT_WITH_CLEANUP(vkCreateSemaphore(s->device, &semaphore_info, NULL, &vk_buf->foreign_semaphore), close(sync_file_fd)); } VkImportSemaphoreFdInfoKHR import_info = { .sType = VK_STRUCTURE_TYPE_IMPORT_SEMAPHORE_FD_INFO_KHR, .handleType = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT, .flags = VK_SEMAPHORE_IMPORT_TEMPORARY_BIT_KHR, .semaphore = vk_buf->foreign_semaphore, .fd = sync_file_fd, }; VK_CHECK_RESULT_WITH_CLEANUP(vkImportSemaphoreFdKHR(s->device, &import_info), close(sync_file_fd)); return 0; } void vulkan_buffer_clear(struct vulkan_base *s, struct vulkan_buffer *buffer) { if (buffer->fd != -1) close(buffer->fd); vkFreeMemory(s->device, buffer->memory, NULL); vkDestroyImage(s->device, buffer->image, NULL); vkDestroyImageView(s->device, buffer->view, NULL); } static VkImageAspectFlagBits mem_plane_aspect(uint32_t i) { switch (i) { case 0: return VK_IMAGE_ASPECT_MEMORY_PLANE_0_BIT_EXT; case 1: return VK_IMAGE_ASPECT_MEMORY_PLANE_1_BIT_EXT; case 2: return VK_IMAGE_ASPECT_MEMORY_PLANE_2_BIT_EXT; case 3: return VK_IMAGE_ASPECT_MEMORY_PLANE_3_BIT_EXT; default: abort(); // unreachable } } int vulkan_staging_buffer_create(struct vulkan_base *s, uint32_t size, struct vulkan_staging_buffer *s_buf) { VkBufferCreateInfo buf_info = { .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, .size = size, .usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT, .sharingMode = VK_SHARING_MODE_EXCLUSIVE, }; VK_CHECK_RESULT(vkCreateBuffer(s->device, &buf_info, NULL, &s_buf->buffer)); VkMemoryRequirements memoryRequirements; vkGetBufferMemoryRequirements(s->device, s_buf->buffer, &memoryRequirements); VkMemoryAllocateInfo mem_info = { .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, .allocationSize = memoryRequirements.size, .memoryTypeIndex = vulkan_memoryType_find(s, memoryRequirements.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_COHERENT_BIT | VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT), }; VK_CHECK_RESULT(vkAllocateMemory(s->device, &mem_info, NULL, &s_buf->memory)); VK_CHECK_RESULT(vkBindBufferMemory(s->device, s_buf->buffer, s_buf->memory, 0)); return 0; } void vulkan_staging_buffer_destroy(struct vulkan_base *s, struct vulkan_staging_buffer *s_buf) { if (s_buf->buffer == VK_NULL_HANDLE) return; vkFreeMemory(s->device, s_buf->memory, NULL); vkDestroyBuffer(s->device, s_buf->buffer, NULL); } static int allocate_dmabuf(struct vulkan_base *s, VkFormat format, uint32_t modifierCount, uint64_t *modifiers, VkImageUsageFlags usage, struct spa_rectangle *size, struct vulkan_buffer *vk_buf) { VkImageDrmFormatModifierListCreateInfoEXT imageDrmFormatModifierListCreateInfo = { .sType = VK_STRUCTURE_TYPE_IMAGE_DRM_FORMAT_MODIFIER_LIST_CREATE_INFO_EXT, .drmFormatModifierCount = modifierCount, .pDrmFormatModifiers = modifiers, }; VkExternalMemoryImageCreateInfo extMemoryImageCreateInfo = { .sType = VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO, .handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT, }; extMemoryImageCreateInfo.pNext = &imageDrmFormatModifierListCreateInfo; VkImageCreateInfo imageCreateInfo = { .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, .imageType = VK_IMAGE_TYPE_2D, .format = format, .extent.width = size->width, .extent.height = size->height, .extent.depth = 1, .mipLevels = 1, .arrayLayers = 1, .samples = VK_SAMPLE_COUNT_1_BIT, .tiling = VK_IMAGE_TILING_DRM_FORMAT_MODIFIER_EXT, .usage = usage, .sharingMode = VK_SHARING_MODE_EXCLUSIVE, .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, }; imageCreateInfo.pNext = &extMemoryImageCreateInfo; VK_CHECK_RESULT(vkCreateImage(s->device, &imageCreateInfo, NULL, &vk_buf->image)); VkMemoryRequirements memoryRequirements = {0}; vkGetImageMemoryRequirements(s->device, vk_buf->image, &memoryRequirements); VkExportMemoryAllocateInfo exportAllocateInfo = { .sType = VK_STRUCTURE_TYPE_EXPORT_MEMORY_ALLOCATE_INFO, .handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT, }; VkMemoryAllocateInfo allocateInfo = { .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, .allocationSize = memoryRequirements.size, .memoryTypeIndex = vulkan_memoryType_find(s, memoryRequirements.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT), }; allocateInfo.pNext = &exportAllocateInfo; VK_CHECK_RESULT(vkAllocateMemory(s->device, &allocateInfo, NULL, &vk_buf->memory)); VK_CHECK_RESULT(vkBindImageMemory(s->device, vk_buf->image, vk_buf->memory, 0)); return 0; } int vulkan_validate_dmabuf_properties(const struct vulkan_modifier_info *modInfo, uint32_t *planeCount, struct spa_rectangle *dim) { if (planeCount) { if (*planeCount != modInfo->props.drmFormatModifierPlaneCount) return -1; } if (dim) { if (dim->width > modInfo->max_extent.width || dim->height > modInfo->max_extent.height) return -1; } return 0; } int vulkan_fixate_modifier(struct vulkan_base *s, struct dmabuf_fixation_info *info, uint64_t *modifier) { VULKAN_INSTANCE_FUNCTION(vkGetImageDrmFormatModifierPropertiesEXT); struct vulkan_buffer vk_buf; vk_buf.fd = -1; vk_buf.view = VK_NULL_HANDLE; VK_CHECK_RESULT(allocate_dmabuf(s, info->format, info->modifierCount, info->modifiers, info->usage, &info->size, &vk_buf)); VkImageDrmFormatModifierPropertiesEXT mod_prop = { .sType = VK_STRUCTURE_TYPE_IMAGE_DRM_FORMAT_MODIFIER_PROPERTIES_EXT, }; VK_CHECK_RESULT(vkGetImageDrmFormatModifierPropertiesEXT(s->device, vk_buf.image, &mod_prop)); *modifier = mod_prop.drmFormatModifier; vulkan_buffer_clear(s, &vk_buf); return 0; } int vulkan_create_dmabuf(struct vulkan_base *s, struct external_buffer_info *info, struct vulkan_buffer *vk_buf) { VULKAN_INSTANCE_FUNCTION(vkGetMemoryFdKHR); if (info->spa_buf->n_datas != 1) return -1; VK_CHECK_RESULT(allocate_dmabuf(s, info->format, 1, &info->modifier, info->usage, &info->size, vk_buf)); const VkMemoryGetFdInfoKHR getFdInfo = { .sType = VK_STRUCTURE_TYPE_MEMORY_GET_FD_INFO_KHR, .memory = vk_buf->memory, .handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT }; int fd = -1; VK_CHECK_RESULT(vkGetMemoryFdKHR(s->device, &getFdInfo, &fd)); VkMemoryRequirements memoryRequirements = {0}; vkGetImageMemoryRequirements(s->device, vk_buf->image, &memoryRequirements); spa_log_info(s->log, "export DMABUF %" PRIu64, memoryRequirements.size); for (uint32_t i = 0; i < info->spa_buf->n_datas; i++) { VkImageSubresource subresource = { .aspectMask = mem_plane_aspect(i), }; VkSubresourceLayout subresLayout = {0}; vkGetImageSubresourceLayout(s->device, vk_buf->image, &subresource, &subresLayout); info->spa_buf->datas[i].type = SPA_DATA_DmaBuf; info->spa_buf->datas[i].fd = fd; info->spa_buf->datas[i].flags = SPA_DATA_FLAG_READABLE; info->spa_buf->datas[i].mapoffset = 0; info->spa_buf->datas[i].chunk->offset = subresLayout.offset; info->spa_buf->datas[i].chunk->stride = subresLayout.rowPitch; info->spa_buf->datas[i].chunk->size = subresLayout.size; info->spa_buf->datas[i].maxsize = memoryRequirements.size; } vk_buf->fd = fd; VkImageViewCreateInfo viewInfo = { .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, .image = vk_buf->image, .viewType = VK_IMAGE_VIEW_TYPE_2D, .format = info->format, .components.r = VK_COMPONENT_SWIZZLE_R, .components.g = VK_COMPONENT_SWIZZLE_G, .components.b = VK_COMPONENT_SWIZZLE_B, .components.a = VK_COMPONENT_SWIZZLE_A, .subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, .subresourceRange.levelCount = 1, .subresourceRange.layerCount = 1, }; VK_CHECK_RESULT(vkCreateImageView(s->device, &viewInfo, NULL, &vk_buf->view)); return 0; } int vulkan_import_dmabuf(struct vulkan_base *s, struct external_buffer_info *info, struct vulkan_buffer *vk_buf) { if (info->spa_buf->n_datas == 0 || info->spa_buf->n_datas > DMABUF_MAX_PLANES) return -1; uint32_t planeCount = info->spa_buf->n_datas; VkSubresourceLayout planeLayouts[DMABUF_MAX_PLANES] = {0}; for (uint32_t i = 0; i < planeCount; i++) { planeLayouts[i].offset = info->spa_buf->datas[i].chunk->offset; planeLayouts[i].rowPitch = info->spa_buf->datas[i].chunk->stride; planeLayouts[i].size = 0; } VkImageDrmFormatModifierExplicitCreateInfoEXT modInfo = { .sType = VK_STRUCTURE_TYPE_IMAGE_DRM_FORMAT_MODIFIER_EXPLICIT_CREATE_INFO_EXT, .drmFormatModifierPlaneCount = planeCount, .drmFormatModifier = info->modifier, .pPlaneLayouts = planeLayouts, }; VkExternalMemoryImageCreateInfo extInfo = { .sType = VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO, .handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT, .pNext = &modInfo, }; VkImageCreateInfo imageCreateInfo = { .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, .imageType = VK_IMAGE_TYPE_2D, .format = info->format, .extent.width = info->size.width, .extent.height = info->size.height, .extent.depth = 1, .mipLevels = 1, .arrayLayers = 1, .samples = VK_SAMPLE_COUNT_1_BIT, .tiling = VK_IMAGE_TILING_DRM_FORMAT_MODIFIER_EXT, .usage = info->usage, .sharingMode = VK_SHARING_MODE_EXCLUSIVE, .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, .pNext = &extInfo, }; VK_CHECK_RESULT(vkCreateImage(s->device, &imageCreateInfo, NULL, &vk_buf->image)); VkMemoryRequirements memoryRequirements; vkGetImageMemoryRequirements(s->device, vk_buf->image, &memoryRequirements); vk_buf->fd = fcntl(info->spa_buf->datas[0].fd, F_DUPFD_CLOEXEC, 0); VkImportMemoryFdInfoKHR importInfo = { .sType = VK_STRUCTURE_TYPE_IMPORT_MEMORY_FD_INFO_KHR, .handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT, .fd = fcntl(info->spa_buf->datas[0].fd, F_DUPFD_CLOEXEC, 0), }; VkMemoryAllocateInfo allocateInfo = { .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, .allocationSize = memoryRequirements.size, .memoryTypeIndex = vulkan_memoryType_find(s, memoryRequirements.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT), }; allocateInfo.pNext = &importInfo; spa_log_info(s->log, "import DMABUF"); VK_CHECK_RESULT(vkAllocateMemory(s->device, &allocateInfo, NULL, &vk_buf->memory)); VK_CHECK_RESULT(vkBindImageMemory(s->device, vk_buf->image, vk_buf->memory, 0)); VkImageViewCreateInfo viewInfo = { .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, .image = vk_buf->image, .viewType = VK_IMAGE_VIEW_TYPE_2D, .format = info->format, .components.r = VK_COMPONENT_SWIZZLE_R, .components.g = VK_COMPONENT_SWIZZLE_G, .components.b = VK_COMPONENT_SWIZZLE_B, .components.a = VK_COMPONENT_SWIZZLE_A, .subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, .subresourceRange.levelCount = 1, .subresourceRange.layerCount = 1, }; VK_CHECK_RESULT(vkCreateImageView(s->device, &viewInfo, NULL, &vk_buf->view)); return 0; } int vulkan_import_memptr(struct vulkan_base *s, struct external_buffer_info *info, struct vulkan_buffer *vk_buf) { VkImageCreateInfo imageCreateInfo = { .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, .imageType = VK_IMAGE_TYPE_2D, .format = info->format, .extent.width = info->size.width, .extent.height = info->size.height, .extent.depth = 1, .mipLevels = 1, .arrayLayers = 1, .samples = VK_SAMPLE_COUNT_1_BIT, .tiling = VK_IMAGE_TILING_LINEAR, .usage = info->usage, .sharingMode = VK_SHARING_MODE_EXCLUSIVE, .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, }; VK_CHECK_RESULT(vkCreateImage(s->device, &imageCreateInfo, NULL, &vk_buf->image)); VkMemoryRequirements memoryRequirements; vkGetImageMemoryRequirements(s->device, vk_buf->image, &memoryRequirements); VkMemoryAllocateInfo allocateInfo = { .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, .allocationSize = memoryRequirements.size, .memoryTypeIndex = vulkan_memoryType_find(s, memoryRequirements.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_COHERENT_BIT | VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT), }; vk_buf->fd = -1; spa_log_info(s->log, "import MemPtr"); VK_CHECK_RESULT(vkAllocateMemory(s->device, &allocateInfo, NULL, &vk_buf->memory)); VK_CHECK_RESULT(vkBindImageMemory(s->device, vk_buf->image, vk_buf->memory, 0)); VkImageViewCreateInfo viewInfo = { .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, .image = vk_buf->image, .viewType = VK_IMAGE_VIEW_TYPE_2D, .format = info->format, .components.r = VK_COMPONENT_SWIZZLE_R, .components.g = VK_COMPONENT_SWIZZLE_G, .components.b = VK_COMPONENT_SWIZZLE_B, .components.a = VK_COMPONENT_SWIZZLE_A, .subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, .subresourceRange.levelCount = 1, .subresourceRange.layerCount = 1, }; VK_CHECK_RESULT(vkCreateImageView(s->device, &viewInfo, NULL, &vk_buf->view)); return 0; } uint32_t vulkan_vkformat_to_id(VkFormat format) { SPA_FOR_EACH_ELEMENT_VAR(vk_video_format_convs, f) { if (f->format == format) return f->id; } return SPA_VIDEO_FORMAT_UNKNOWN; } VkFormat vulkan_id_to_vkformat(uint32_t id) { SPA_FOR_EACH_ELEMENT_VAR(vk_video_format_convs, f) { if (f->id == id) return f->format; } return VK_FORMAT_UNDEFINED; } int vulkan_vkresult_to_errno(VkResult result) { return vkresult_to_errno(result); } int vulkan_wait_fence(struct vulkan_base *s, VkFence fence) { VK_CHECK_RESULT(vkWaitForFences(s->device, 1, &fence, VK_TRUE, UINT64_MAX)); return 0; } int vulkan_wait_idle(struct vulkan_base *s) { VK_CHECK_RESULT(vkDeviceWaitIdle(s->device)); return 0; } int vulkan_format_infos_init(struct vulkan_base *s, uint32_t formatCount, uint32_t *formats, struct vulkan_format_infos *info) { if (info->infos) return 0; info->infos = calloc(formatCount, sizeof(struct vulkan_format_info)); if (!info->infos) return -ENOMEM; uint32_t i; for (i = 0; i < formatCount; i++) { VkFormat format = vulkan_id_to_vkformat(formats[i]); if (format == VK_FORMAT_UNDEFINED) continue; struct vulkan_format_info *f_info = &info->infos[info->formatCount++]; f_info->spa_format = formats[i]; f_info->vk_format = format; spa_log_info(s->log, "Adding format %d (spa_format %d)", format, formats[i]); VkDrmFormatModifierPropertiesListEXT modPropsList = { .sType = VK_STRUCTURE_TYPE_DRM_FORMAT_MODIFIER_PROPERTIES_LIST_EXT, }; VkFormatProperties2 fmtProps = { .sType = VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2, .pNext = &modPropsList, }; vkGetPhysicalDeviceFormatProperties2(s->physicalDevice, format, &fmtProps); if (modPropsList.drmFormatModifierCount == 0) { spa_log_info(s->log, "Format has no modifiers"); continue; } modPropsList.pDrmFormatModifierProperties = calloc(modPropsList.drmFormatModifierCount, sizeof(modPropsList.pDrmFormatModifierProperties[0])); if (!modPropsList.pDrmFormatModifierProperties) { spa_log_info(s->log, "Failed to allocate DrmFormatModifierProperties"); continue; } vkGetPhysicalDeviceFormatProperties2(s->physicalDevice, format, &fmtProps); f_info->infos = calloc(modPropsList.drmFormatModifierCount, sizeof(f_info->infos[0])); if (!f_info->infos) { spa_log_info(s->log, "Failed to allocate modifier infos"); free(modPropsList.pDrmFormatModifierProperties); continue; } spa_log_info(s->log, "Found %d modifiers", modPropsList.drmFormatModifierCount); for (uint32_t j = 0; j < modPropsList.drmFormatModifierCount; j++) { VkDrmFormatModifierPropertiesEXT props = modPropsList.pDrmFormatModifierProperties[j]; if (!(props.drmFormatModifierTilingFeatures & VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT)) continue; if (props.drmFormatModifierPlaneCount > DMABUF_MAX_PLANES) continue; VkPhysicalDeviceImageDrmFormatModifierInfoEXT modInfo = { .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_DRM_FORMAT_MODIFIER_INFO_EXT, .drmFormatModifier = props.drmFormatModifier, .sharingMode = VK_SHARING_MODE_EXCLUSIVE, }; VkPhysicalDeviceExternalImageFormatInfo extImgFmtInfo = { .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_IMAGE_FORMAT_INFO, .pNext = &modInfo, .handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT, }; VkPhysicalDeviceImageFormatInfo2 imgFmtInfo = { .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_FORMAT_INFO_2, .pNext = &extImgFmtInfo, .type = VK_IMAGE_TYPE_2D, .format = format, .usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, .tiling = VK_IMAGE_TILING_DRM_FORMAT_MODIFIER_EXT, }; VkExternalImageFormatProperties extImgFmtProps = { .sType = VK_STRUCTURE_TYPE_EXTERNAL_IMAGE_FORMAT_PROPERTIES, }; VkImageFormatProperties2 imgFmtProps = { .sType = VK_STRUCTURE_TYPE_IMAGE_FORMAT_PROPERTIES_2, .pNext = &extImgFmtProps, }; VK_CHECK_RESULT_LOOP(vkGetPhysicalDeviceImageFormatProperties2(s->physicalDevice, &imgFmtInfo, &imgFmtProps)) VkExternalMemoryFeatureFlags extMemFeatures = extImgFmtProps.externalMemoryProperties.externalMemoryFeatures; if (!(extMemFeatures & VK_EXTERNAL_MEMORY_FEATURE_EXPORTABLE_BIT)) { continue; } VkExtent3D max_extent = imgFmtProps.imageFormatProperties.maxExtent; f_info->infos[f_info->modifierCount++] = (struct vulkan_modifier_info){ .props = props, .max_extent = { .width = max_extent.width, .height = max_extent.height }, }; spa_log_info(s->log, "Adding modifier %"PRIu64, props.drmFormatModifier); } free(modPropsList.pDrmFormatModifierProperties); } for (i = 0; i < info->formatCount; i++) { if (info->infos[i].modifierCount > 0) info->formatsWithModifiersCount++; } return 0; } void vulkan_format_infos_deinit(struct vulkan_format_infos *info) { for (uint32_t i = 0; i < info->formatCount; i++) { free(info->infos[i].infos); } free(info->infos); } int vulkan_base_init(struct vulkan_base *s, struct vulkan_base_info *info) { if (!s->initialized) { CHECK(createInstance(s)); CHECK(findPhysicalDevice(s)); CHECK(createDevice(s, info)); s->implicit_sync_interop = dmabuf_check_sync_file_import_export(s->log); s->initialized = true; } return 0; } void vulkan_base_deinit(struct vulkan_base *s) { if (s->initialized) { vkDestroyDevice(s->device, NULL); vkDestroyInstance(s->instance, NULL); s->initialized = false; } } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/plugins/vulkan/vulkan-utils.h000066400000000000000000000113601511204443500270520ustar00rootroot00000000000000/* Spa */ /* SPDX-FileCopyrightText: Copyright © 2019 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #pragma once #include #include #include #include "vulkan-types.h" #define VK_CHECK_RESULT(f) \ { \ VkResult _result = (f); \ int _r = -vulkan_vkresult_to_errno(_result); \ if (_result != VK_SUCCESS) { \ spa_log_error(s->log, "error: %d (%d %s)", _result, _r, spa_strerror(_r)); \ return _r; \ } \ } #define VK_CHECK_RESULT_WITH_CLEANUP(f, c) \ { \ VkResult _result = (f); \ int _r = -vkresult_to_errno(_result); \ if (_result != VK_SUCCESS) { \ spa_log_error(s->log, "error: %d (%d %s)", _result, _r, spa_strerror(_r)); \ (c); \ return _r; \ } \ } #define VK_CHECK_RESULT_LOOP(f) \ { \ VkResult _result = (f); \ int _r = -vkresult_to_errno(_result); \ if (_result != VK_SUCCESS) { \ spa_log_error(s->log, "error: %d (%d %s)", _result, _r, spa_strerror(_r)); \ continue; \ } \ } #define CHECK(f) \ { \ int _res = (f); \ if (_res < 0) \ return _res; \ } struct vulkan_write_pixels_info { struct spa_rectangle size; uint32_t offset; uint32_t stride; uint32_t bytes_per_pixel; VkBufferImageCopy *copies; void *data; }; struct vulkan_read_pixels_info { struct spa_rectangle size; void *data; uint32_t offset; uint32_t stride; uint32_t bytes_per_pixel; }; struct dmabuf_fixation_info { VkFormat format; uint64_t modifierCount; uint64_t *modifiers; struct spa_rectangle size; VkImageUsageFlags usage; }; struct external_buffer_info { VkFormat format; uint64_t modifier; struct spa_rectangle size; VkImageUsageFlags usage; struct spa_buffer *spa_buf; }; int vulkan_write_pixels(struct vulkan_base *s, struct vulkan_write_pixels_info *info, struct vulkan_staging_buffer *vk_sbuf); int vulkan_read_pixels(struct vulkan_base *s, struct vulkan_read_pixels_info *info, struct vulkan_buffer *vk_buf); int vulkan_sync_foreign_dmabuf(struct vulkan_base *s, struct vulkan_buffer *vk_buf); bool vulkan_sync_export_dmabuf(struct vulkan_base *s, struct vulkan_buffer *vk_buf, int sync_file_fd); int vulkan_staging_buffer_create(struct vulkan_base *s, uint32_t size, struct vulkan_staging_buffer *s_buf); void vulkan_staging_buffer_destroy(struct vulkan_base *s, struct vulkan_staging_buffer *s_buf); int vulkan_validate_dmabuf_properties(const struct vulkan_modifier_info *modInfo, uint32_t *planeCount, struct spa_rectangle *dim); int vulkan_fixate_modifier(struct vulkan_base *s, struct dmabuf_fixation_info *info, uint64_t *modifier); int vulkan_create_dmabuf(struct vulkan_base *s, struct external_buffer_info *info, struct vulkan_buffer *vk_buf); int vulkan_import_dmabuf(struct vulkan_base *s, struct external_buffer_info *info, struct vulkan_buffer *vk_buf); int vulkan_import_memptr(struct vulkan_base *s, struct external_buffer_info *info, struct vulkan_buffer *vk_buf); int vulkan_fence_create(struct vulkan_base *s, VkFence *fence); int vulkan_commandPool_create(struct vulkan_base *s, VkCommandPool *commandPool); int vulkan_commandBuffer_create(struct vulkan_base *s, VkCommandPool commandPool, VkCommandBuffer *commandBuffer); uint32_t vulkan_memoryType_find(struct vulkan_base *s, uint32_t memoryTypeBits, VkMemoryPropertyFlags properties); struct vulkan_format_info *vulkan_formatInfo_find(struct vulkan_format_infos *fmtInfo, VkFormat format); struct vulkan_modifier_info *vulkan_modifierInfo_find(struct vulkan_format_infos *fmtInfo, VkFormat format, uint64_t modifier); int vulkan_buffer_get_implicit_syncfd(struct vulkan_base *s, struct vulkan_buffer *vk_buf); bool vulkan_buffer_set_implicit_syncfd(struct vulkan_base *s, struct vulkan_buffer *vk_buf, int sync_file_fd); int vulkan_buffer_import_implicit_syncfd(struct vulkan_base *s, struct vulkan_buffer *vk_buf); int vulkan_buffer_wait_dmabuf_fence(struct vulkan_base *s, struct vulkan_buffer *vk_buf); int vulkan_buffer_import_syncfd(struct vulkan_base *s, struct vulkan_buffer *vk_buf, int sync_file_fd); void vulkan_buffer_clear(struct vulkan_base *s, struct vulkan_buffer *buffer); uint32_t vulkan_vkformat_to_id(VkFormat vkFormat); VkFormat vulkan_id_to_vkformat(uint32_t id); int vulkan_vkresult_to_errno(VkResult result); int vulkan_wait_fence(struct vulkan_base *s, VkFence fence); int vulkan_wait_idle(struct vulkan_base *s); int vulkan_format_infos_init(struct vulkan_base *s, uint32_t formatCount, uint32_t *formats, struct vulkan_format_infos *info); void vulkan_format_infos_deinit(struct vulkan_format_infos *info); int vulkan_base_init(struct vulkan_base *s, struct vulkan_base_info *info); void vulkan_base_deinit(struct vulkan_base *s); pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/tests/000077500000000000000000000000001511204443500224235ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/tests/benchmark-aec.c000066400000000000000000000240641511204443500252550ustar00rootroot00000000000000/* Spa */ /* SPDX-FileCopyrightText: Copyright © 2025 Arun Raghavan */ /* SPDX-License-Identifier: MIT */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static SPA_LOG_IMPL(default_log); struct data { const char *plugin_dir; struct spa_log *log; struct spa_system *system; struct spa_loop *loop; struct spa_loop_control *control; struct spa_loop_utils *loop_utils; struct spa_plugin_loader *plugin_loader; struct spa_support support[6]; uint32_t n_support; struct spa_audio_aec *aec; struct spa_handle *aec_handle; uint32_t aec_samples; }; static int load_handle(struct data *data, struct spa_handle **handle, const char *lib, const char *name) { int res; void *hnd; spa_handle_factory_enum_func_t enum_func; uint32_t i; char *path = NULL; if ((path = spa_aprintf("%s/%s", data->plugin_dir, lib)) == NULL) { return -ENOMEM; } if ((hnd = dlopen(path, RTLD_NOW)) == NULL) { printf("can't load %s: %s\n", path, dlerror()); free(path); return -errno; } free(path); if ((enum_func = dlsym(hnd, SPA_HANDLE_FACTORY_ENUM_FUNC_NAME)) == NULL) { printf("can't find enum function\n"); return -errno; } for (i = 0;;) { const struct spa_handle_factory *factory; if ((res = enum_func(&factory, &i)) <= 0) { if (res != 0) printf("can't enumerate factories: %s\n", spa_strerror(res)); break; } if (!spa_streq(factory->name, name)) continue; *handle = calloc(1, spa_handle_factory_get_size(factory, NULL)); if ((res = spa_handle_factory_init(factory, *handle, NULL, data->support, data->n_support)) < 0) { printf("can't make factory instance: %d\n", res); return res; } return 0; } return -EBADF; } static int init(struct data *data) { int res; const char *str; struct spa_handle *handle = NULL; void *iface; if ((str = getenv("SPA_PLUGIN_DIR")) == NULL) str = PLUGINDIR; data->plugin_dir = str; if ((res = load_handle(data, &handle, "support/libspa-support.so", SPA_NAME_SUPPORT_SYSTEM)) < 0) return res; if ((res = spa_handle_get_interface(handle, SPA_TYPE_INTERFACE_System, &iface)) < 0) { printf("can't get System interface %d\n", res); return res; } data->system = iface; data->support[data->n_support++] = SPA_SUPPORT_INIT(SPA_TYPE_INTERFACE_System, data->system); data->support[data->n_support++] = SPA_SUPPORT_INIT(SPA_TYPE_INTERFACE_DataSystem, data->system); if ((res = load_handle(data, &handle, "support/libspa-support.so", SPA_NAME_SUPPORT_LOOP)) < 0) return res; if ((res = spa_handle_get_interface(handle, SPA_TYPE_INTERFACE_Loop, &iface)) < 0) { printf("can't get interface %d\n", res); return res; } data->loop = iface; if ((res = spa_handle_get_interface(handle, SPA_TYPE_INTERFACE_LoopControl, &iface)) < 0) { printf("can't get interface %d\n", res); return res; } data->control = iface; if ((res = spa_handle_get_interface(handle, SPA_TYPE_INTERFACE_LoopUtils, &iface)) < 0) { printf("can't get interface %d\n", res); return res; } data->loop_utils = iface; data->log = &default_log.log; if ((str = getenv("SPA_DEBUG"))) data->log->level = atoi(str); data->support[data->n_support++] = SPA_SUPPORT_INIT(SPA_TYPE_INTERFACE_Log, data->log); data->support[data->n_support++] = SPA_SUPPORT_INIT(SPA_TYPE_INTERFACE_Loop, data->loop); data->support[data->n_support++] = SPA_SUPPORT_INIT(SPA_TYPE_INTERFACE_DataLoop, data->loop); data->support[data->n_support++] = SPA_SUPPORT_INIT(SPA_TYPE_INTERFACE_LoopUtils, data->loop_utils); /* Use webrtc as default */ if ((res = load_handle(data, &handle, "aec/libspa-aec-webrtc.so", SPA_NAME_AEC)) < 0) return res; if ((res = spa_handle_get_interface(handle, SPA_TYPE_INTERFACE_AUDIO_AEC, &iface)) < 0) { spa_log_error(data->log, "can't get %s interface %d", SPA_TYPE_INTERFACE_AUDIO_AEC, res); return res; } data->aec = iface; data->aec_handle = handle; return 0; } static int spa_dict_from_json(struct spa_dict_item *items, uint32_t n_items, const char *str) { struct spa_json it; int res; char key[1024]; const char *value; uint32_t i, len; struct spa_error_location loc; if ((res = spa_json_begin_object_relax(&it, str, strlen(str))) < 0) { return res; } i = 0; while ((len = spa_json_object_next(&it, key, sizeof(key), &value)) > 0) { if (i > n_items) return -ENOSPC; char *k = malloc(strlen(key) + 1); char *v = malloc(len + 1); memcpy(k, key, strlen(key) + 1); spa_json_parse_stringn(value, len, v, len + 1); items[i++] = SPA_DICT_ITEM_INIT(k, v); } if (spa_json_get_error(&it, str, &loc)) { struct spa_debug_context *c = NULL; spa_debugc(c, "Invalid JSON: %s", loc.reason); spa_debugc_error_location(c, &loc); return -EINVAL; } return i; } static const struct format_info { const char *name; int sf_format; uint32_t spa_format; uint32_t width; } format_info[] = { { "ulaw", SF_FORMAT_ULAW, SPA_AUDIO_FORMAT_ULAW, 1 }, { "alaw", SF_FORMAT_ULAW, SPA_AUDIO_FORMAT_ALAW, 1 }, { "s8", SF_FORMAT_PCM_S8, SPA_AUDIO_FORMAT_S8, 1 }, { "u8", SF_FORMAT_PCM_U8, SPA_AUDIO_FORMAT_U8, 1 }, { "s16", SF_FORMAT_PCM_16, SPA_AUDIO_FORMAT_S16, 2 }, { "s24", SF_FORMAT_PCM_24, SPA_AUDIO_FORMAT_S24, 3 }, { "s32", SF_FORMAT_PCM_32, SPA_AUDIO_FORMAT_S32, 4 }, { "f32", SF_FORMAT_FLOAT, SPA_AUDIO_FORMAT_F32, 4 }, { "f64", SF_FORMAT_DOUBLE, SPA_AUDIO_FORMAT_F32, 8 }, }; static SNDFILE* open_file_read(const struct data *data, const char *name, struct spa_audio_info_raw *info) { SF_INFO sf_info = { 0, }; SNDFILE *file = sf_open(name, SFM_READ, &sf_info); if (!file) { spa_log_error(data->log, "Could not open file: %s", sf_strerror(NULL)); exit(255); } for (unsigned long i = 0; i < SPA_N_ELEMENTS(format_info); i++) { if ((sf_info.format & SF_FORMAT_SUBMASK) == format_info[i].sf_format) { info->format = format_info[i].spa_format; break; } } info->rate = sf_info.samplerate; info->channels = sf_info.channels; return file; } static SNDFILE* open_file_write(const struct data *data, const char *name, struct spa_audio_info_raw *info) { SF_INFO sf_info = { 0, }; for (unsigned long i = 0; i < SPA_N_ELEMENTS(format_info); i++) { if (info->format == format_info[i].spa_format) { sf_info.format = SF_FORMAT_WAV | format_info[i].sf_format; break; } } sf_info.samplerate = info->rate; sf_info.channels = info->channels; SNDFILE *file = sf_open(name, SFM_WRITE, &sf_info); if (!file) { spa_log_error(data->log, "Could not open file: %s", sf_strerror(NULL)); exit(255); } return file; } static void deinterleave(float *data, uint32_t channels, uint32_t samples) { float temp[channels * samples]; for (uint32_t i = 0; i < channels; i++) { for (uint32_t j = 0; j < samples; j++) { temp[i * samples + j] = data[j * channels + i]; } } memcpy(data, temp, sizeof(temp)); } static void interleave(float *data, uint32_t channels, uint32_t samples) { float temp[channels * samples]; for (uint32_t i = 0; i < samples; i++) { for (uint32_t j = 0; j < channels; j++) { temp[i * channels + j] = data[j * samples + i]; } } memcpy(data, temp, sizeof(temp)); } static void usage(char *exe) { printf("Usage: %s rec_file play_file out_file <\"aec args\">\n", basename(exe)); } int main(int argc, char *argv[]) { struct data data = { 0, }; struct spa_dict_item items[16] = { 0, }; int n_items = 0, res; if ((res = init(&data)) < 0) return res; if (argc < 4 || argc > 5) { usage(argv[0]); return -1; } if (argc == 5) { if ((res = spa_dict_from_json(items, SPA_N_ELEMENTS(items), argv[4])) < 0) return res; n_items = res; } struct spa_dict aec_args = SPA_DICT(items, n_items); struct spa_audio_info_raw rec_info = { 0, }; struct spa_audio_info_raw play_info = { 0, }; SNDFILE *rec_file = open_file_read(&data, argv[1], &rec_info); SNDFILE *play_file = open_file_read(&data, argv[2], &play_info); SNDFILE *out_file = open_file_write(&data, argv[3], &rec_info); if ((res = spa_audio_aec_init2(data.aec, &aec_args, &rec_info, &play_info, &rec_info)) < 0) { spa_log_error(data.log, "Could not initialise AEC engine: %s", spa_strerror(res)); return -1; } if (data.aec->latency) { unsigned int num, denom; sscanf(data.aec->latency, "%u/%u", &num, &denom); data.aec_samples = rec_info.rate * num / denom; } else { /* Implementation doesn't care about the block size */ data.aec_samples = 1024; } float rec_data[rec_info.channels * data.aec_samples]; float play_data[play_info.channels * data.aec_samples]; float out_data[rec_info.channels * data.aec_samples]; const float *rec[rec_info.channels]; const float *play[play_info.channels]; float *out[rec_info.channels]; for (uint32_t i = 0; i < rec_info.channels; i++) { rec[i] = &rec_data[i * data.aec_samples]; out[i] = &out_data[i * data.aec_samples]; } for (uint32_t i = 0; i < play_info.channels; i++) { play[i] = &play_data[i * data.aec_samples]; } while (1) { res = sf_readf_float(rec_file, (float *)rec_data, data.aec_samples); if (res != (int) data.aec_samples) break; res = sf_readf_float(play_file, (float *)play_data, data.aec_samples); if (res != (int) data.aec_samples) break; deinterleave((float *)rec_data, rec_info.channels, data.aec_samples); deinterleave((float *)play_data, play_info.channels, data.aec_samples); spa_audio_aec_run(data.aec, rec, play, out, data.aec_samples); interleave((float *)out_data, rec_info.channels, data.aec_samples); res = sf_writef_float(out_file, (const float *)out_data, data.aec_samples); if (res != (int) data.aec_samples) { spa_log_error(data.log, "Failed to write: %s", spa_strerror(res)); break; } } sf_close(rec_file); sf_close(play_file); sf_close(out_file); return 0; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/tests/benchmark-dict.c000066400000000000000000000051421511204443500254440ustar00rootroot00000000000000/* Spa */ /* SPDX-FileCopyrightText: Copyright © 2020 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #include #include #include #include #include #define MAX_COUNT 100000 #define MAX_ITEMS 1000 static struct spa_dict_item items[MAX_ITEMS]; static char values[MAX_ITEMS][32]; static void gen_values(void) { uint32_t i, j, idx; static const char chars[] = "abcdefghijklmnopqrstuvwxyz.:*ABCDEFGHIJKLMNOPQRSTUVWXYZ"; for (i = 0; i < MAX_ITEMS; i++) { for (j = 0; j < 32; j++) { idx = random() % sizeof(chars); values[i][j] = chars[idx]; } idx = random() % 16; values[i][idx + 16] = 0; } } static void gen_dict(struct spa_dict *dict, uint32_t n_items) { uint32_t i, idx; for (i = 0; i < n_items; i++) { idx = random() % MAX_ITEMS; items[i] = SPA_DICT_ITEM_INIT(values[idx], values[idx]); } dict->items = items; dict->n_items = n_items; dict->flags = 0; } static void test_query(const struct spa_dict *dict) { uint32_t i, idx; const char *str; for (i = 0; i < MAX_COUNT; i++) { idx = random() % dict->n_items; str = spa_dict_lookup(dict, dict->items[idx].key); assert(spa_streq(str, dict->items[idx].value)); } } static void test_lookup(struct spa_dict *dict) { struct timespec ts; uint64_t t1, t2, t3, t4; clock_gettime(CLOCK_MONOTONIC, &ts); t1 = SPA_TIMESPEC_TO_NSEC(&ts); test_query(dict); clock_gettime(CLOCK_MONOTONIC, &ts); t2 = SPA_TIMESPEC_TO_NSEC(&ts); fprintf(stderr, "%d elapsed %"PRIu64" count %u = %"PRIu64"/sec\n", dict->n_items, t2 - t1, MAX_COUNT, MAX_COUNT * (uint64_t)SPA_NSEC_PER_SEC / (t2 - t1)); spa_dict_qsort(dict); clock_gettime(CLOCK_MONOTONIC, &ts); t3 = SPA_TIMESPEC_TO_NSEC(&ts); fprintf(stderr, "%d sort elapsed %"PRIu64"\n", dict->n_items, t3 - t2); clock_gettime(CLOCK_MONOTONIC, &ts); t3 = SPA_TIMESPEC_TO_NSEC(&ts); test_query(dict); clock_gettime(CLOCK_MONOTONIC, &ts); t4 = SPA_TIMESPEC_TO_NSEC(&ts); fprintf(stderr, "%d elapsed %"PRIu64" count %u = %"PRIu64"/sec %f speedup\n", dict->n_items, t4 - t3, MAX_COUNT, MAX_COUNT * (uint64_t)SPA_NSEC_PER_SEC / (t4 - t3), (double)(t2 - t1) / (t4 - t2)); } int main(int argc, char *argv[]) { struct spa_dict dict; spa_zero(dict); gen_values(); /* warmup */ gen_dict(&dict, 1000); test_query(&dict); gen_dict(&dict, 10); test_lookup(&dict); gen_dict(&dict, 20); test_lookup(&dict); gen_dict(&dict, 50); test_lookup(&dict); gen_dict(&dict, 100); test_lookup(&dict); gen_dict(&dict, 1000); test_lookup(&dict); return 0; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/tests/benchmark-pod.c000066400000000000000000000204671511204443500253120ustar00rootroot00000000000000/* Spa */ /* SPDX-FileCopyrightText: Copyright © 2019 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #include #include #include #include #include #include #include #define MAX_COUNT 10000000 static void test_builder(void) { uint8_t buffer[1024]; struct spa_pod_builder b = { NULL, }; struct spa_pod_frame f[2]; struct timespec ts; uint64_t t1, t2; uint64_t count = 0; clock_gettime(CLOCK_MONOTONIC, &ts); t1 = SPA_TIMESPEC_TO_NSEC(&ts); fprintf(stderr, "test_builder() : "); for (count = 0; count < MAX_COUNT; count++) { spa_pod_builder_init(&b, buffer, sizeof(buffer)); spa_pod_builder_push_object(&b, &f[0], SPA_TYPE_OBJECT_Format, 0); spa_pod_builder_prop(&b, SPA_FORMAT_mediaType, 0); spa_pod_builder_id(&b, SPA_MEDIA_TYPE_video); spa_pod_builder_prop(&b, SPA_FORMAT_mediaSubtype, 0); spa_pod_builder_id(&b, SPA_MEDIA_SUBTYPE_raw); spa_pod_builder_prop(&b, SPA_FORMAT_VIDEO_format, 0); spa_pod_builder_push_choice(&b, &f[1], SPA_CHOICE_Enum, 0); spa_pod_builder_id(&b, SPA_VIDEO_FORMAT_I420); spa_pod_builder_id(&b, SPA_VIDEO_FORMAT_I420); spa_pod_builder_id(&b, SPA_VIDEO_FORMAT_YUY2); spa_pod_builder_pop(&b, &f[1]); struct spa_rectangle size_min_max[] = { {1, 1}, {INT32_MAX, INT32_MAX} }; spa_pod_builder_prop(&b, SPA_FORMAT_VIDEO_size, 0); spa_pod_builder_push_choice(&b, &f[1], SPA_CHOICE_Range, 0); spa_pod_builder_rectangle(&b, 320, 240); spa_pod_builder_raw(&b, size_min_max, sizeof(size_min_max)); spa_pod_builder_pop(&b, &f[1]); struct spa_fraction rate_min_max[] = { {0, 1}, {INT32_MAX, 1} }; spa_pod_builder_prop(&b, SPA_FORMAT_VIDEO_framerate, 0); spa_pod_builder_push_choice(&b, &f[1], SPA_CHOICE_Range, 0); spa_pod_builder_fraction(&b, 25, 1); spa_pod_builder_raw(&b, rate_min_max, sizeof(rate_min_max)); spa_pod_builder_pop(&b, &f[1]); spa_pod_builder_pop(&b, &f[0]); clock_gettime(CLOCK_MONOTONIC, &ts); t2 = SPA_TIMESPEC_TO_NSEC(&ts); if (t2 - t1 > 1 * SPA_NSEC_PER_SEC) break; } fprintf(stderr, "elapsed %"PRIu64" count %"PRIu64" = %"PRIu64"/sec\n", t2 - t1, count, count * (uint64_t)SPA_NSEC_PER_SEC / (t2 - t1)); } static void test_builder2(void) { uint8_t buffer[1024]; struct spa_pod_builder b = { NULL, }; struct timespec ts; uint64_t t1, t2; uint64_t count = 0; clock_gettime(CLOCK_MONOTONIC, &ts); t1 = SPA_TIMESPEC_TO_NSEC(&ts); fprintf(stderr, "test_builder2() : "); for (count = 0; count < MAX_COUNT; count++) { spa_pod_builder_init(&b, buffer, sizeof(buffer)); spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_Format, 0, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), SPA_FORMAT_VIDEO_format, SPA_POD_CHOICE_ENUM_Id(3, SPA_VIDEO_FORMAT_I420, SPA_VIDEO_FORMAT_I420, SPA_VIDEO_FORMAT_YUY2), SPA_FORMAT_VIDEO_size, SPA_POD_CHOICE_RANGE_Rectangle( &SPA_RECTANGLE(320, 240), &SPA_RECTANGLE(1, 1), &SPA_RECTANGLE(INT32_MAX, INT32_MAX)), SPA_FORMAT_VIDEO_framerate, SPA_POD_CHOICE_RANGE_Fraction( &SPA_FRACTION(25,1), &SPA_FRACTION(0,1), &SPA_FRACTION(INT32_MAX,1))); clock_gettime(CLOCK_MONOTONIC, &ts); t2 = SPA_TIMESPEC_TO_NSEC(&ts); if (t2 - t1 > 1 * SPA_NSEC_PER_SEC) break; } fprintf(stderr, "elapsed %"PRIu64" count %"PRIu64" = %"PRIu64"/sec\n", t2 - t1, count, count * (uint64_t)SPA_NSEC_PER_SEC / (t2 - t1)); } static void test_parse(void) { uint8_t buffer[1024]; struct spa_pod_builder b = { NULL, }; struct timespec ts; uint64_t t1, t2; uint64_t count = 0; struct spa_pod *fmt; spa_pod_builder_init(&b, buffer, sizeof(buffer)); fmt = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_Format, 0, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), SPA_FORMAT_VIDEO_format, SPA_POD_CHOICE_ENUM_Id(3, SPA_VIDEO_FORMAT_I420, SPA_VIDEO_FORMAT_I420, SPA_VIDEO_FORMAT_YUY2), SPA_FORMAT_VIDEO_size, SPA_POD_CHOICE_RANGE_Rectangle( &SPA_RECTANGLE(320, 240), &SPA_RECTANGLE(1, 1), &SPA_RECTANGLE(INT32_MAX, INT32_MAX)), SPA_FORMAT_VIDEO_framerate, SPA_POD_CHOICE_RANGE_Fraction( &SPA_FRACTION(25,1), &SPA_FRACTION(0,1), &SPA_FRACTION(INT32_MAX,1))); spa_pod_fixate(fmt); clock_gettime(CLOCK_MONOTONIC, &ts); t1 = SPA_TIMESPEC_TO_NSEC(&ts); fprintf(stderr, "test_parse() : "); for (count = 0; count < MAX_COUNT; count++) { struct { uint32_t media_type; uint32_t media_subtype; uint32_t format; struct spa_rectangle size; struct spa_fraction framerate; } vals; struct spa_pod_prop *prop; spa_zero(vals); SPA_POD_OBJECT_FOREACH((struct spa_pod_object*)fmt, prop) { uint32_t n_vals, choice; struct spa_pod *pod = spa_pod_get_values(&prop->value, &n_vals, &choice); spa_assert_se(n_vals > 0); switch(prop->key) { case SPA_FORMAT_mediaType: spa_pod_get_id(pod, &vals.media_type); break; case SPA_FORMAT_mediaSubtype: spa_pod_get_id(pod, &vals.media_subtype); break; case SPA_FORMAT_VIDEO_format: spa_pod_get_id(pod, &vals.format); break; case SPA_FORMAT_VIDEO_size: spa_pod_get_rectangle(pod, &vals.size); break; case SPA_FORMAT_VIDEO_framerate: spa_pod_get_fraction(pod, &vals.framerate); break; default: break; } } spa_assert(vals.media_type == SPA_MEDIA_TYPE_video); spa_assert(vals.media_subtype == SPA_MEDIA_SUBTYPE_raw); spa_assert(vals.format == SPA_VIDEO_FORMAT_I420); spa_assert(vals.size.width == 320 && vals.size.height == 240); spa_assert(vals.framerate.num == 25 && vals.framerate.denom == 1); clock_gettime(CLOCK_MONOTONIC, &ts); t2 = SPA_TIMESPEC_TO_NSEC(&ts); if (t2 - t1 > 1 * SPA_NSEC_PER_SEC) break; } fprintf(stderr, "elapsed %"PRIu64" count %"PRIu64" = %"PRIu64"/sec\n", t2 - t1, count, count * (uint64_t)SPA_NSEC_PER_SEC / (t2 - t1)); } static void test_parser(void) { uint8_t buffer[1024]; struct spa_pod_builder b = { NULL, }; struct timespec ts; uint64_t t1, t2; uint64_t count = 0; struct spa_pod *fmt; spa_pod_builder_init(&b, buffer, sizeof(buffer)); fmt = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_Format, 0, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), SPA_FORMAT_VIDEO_format, SPA_POD_CHOICE_ENUM_Id(3, SPA_VIDEO_FORMAT_I420, SPA_VIDEO_FORMAT_I420, SPA_VIDEO_FORMAT_YUY2), SPA_FORMAT_VIDEO_size, SPA_POD_CHOICE_RANGE_Rectangle( &SPA_RECTANGLE(320, 240), &SPA_RECTANGLE(1, 1), &SPA_RECTANGLE(INT32_MAX, INT32_MAX)), SPA_FORMAT_VIDEO_framerate, SPA_POD_CHOICE_RANGE_Fraction( &SPA_FRACTION(25,1), &SPA_FRACTION(0,1), &SPA_FRACTION(INT32_MAX,1))); spa_pod_fixate(fmt); clock_gettime(CLOCK_MONOTONIC, &ts); t1 = SPA_TIMESPEC_TO_NSEC(&ts); fprintf(stderr, "test_parser() : "); for (count = 0; count < MAX_COUNT; count++) { struct { uint32_t media_type; uint32_t media_subtype; uint32_t format; struct spa_rectangle size; struct spa_fraction framerate; } vals; spa_zero(vals); spa_pod_parse_object(fmt, SPA_TYPE_OBJECT_Format, NULL, SPA_FORMAT_mediaType, SPA_POD_Id(&vals.media_type), SPA_FORMAT_mediaSubtype, SPA_POD_Id(&vals.media_subtype), SPA_FORMAT_VIDEO_format, SPA_POD_Id(&vals.format), SPA_FORMAT_VIDEO_size, SPA_POD_Rectangle(&vals.size), SPA_FORMAT_VIDEO_framerate, SPA_POD_Fraction(&vals.framerate)); spa_assert(vals.media_type == SPA_MEDIA_TYPE_video); spa_assert(vals.media_subtype == SPA_MEDIA_SUBTYPE_raw); spa_assert(vals.format == SPA_VIDEO_FORMAT_I420); spa_assert(vals.size.width == 320 && vals.size.height == 240); spa_assert(vals.framerate.num == 25 && vals.framerate.denom == 1); clock_gettime(CLOCK_MONOTONIC, &ts); t2 = SPA_TIMESPEC_TO_NSEC(&ts); if (t2 - t1 > 1 * SPA_NSEC_PER_SEC) break; } fprintf(stderr, "elapsed %"PRIu64" count %"PRIu64" = %"PRIu64"/sec\n", t2 - t1, count, count * (uint64_t)SPA_NSEC_PER_SEC / (t2 - t1)); } int main(int argc, char *argv[]) { test_builder(); test_builder2(); test_parse(); test_parser(); return 0; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/tests/meson.build000066400000000000000000000043631511204443500245730ustar00rootroot00000000000000# Generate a compilation test for each SPA header, excluding the type-info.h # ones which have circular dependencies and take some effort to fix. # Do it for C++ if possible (picks up C++-specific errors), otherwise for C. find = find_program('find', required: false) summary({'find (for header testing)': find.found()}, bool_yn: true, section: 'Optional programs') if find.found() spa_headers = run_command(find, meson.project_source_root() / 'spa' / 'include', '-name', '*.h', '-not', '-name', 'type-info.h', '-type', 'f', '-printf', '%P\n', check: false) foreach spa_header : spa_headers.stdout().split('\n') if spa_header.endswith('.h') # skip empty lines ext = have_cpp ? 'cpp' : 'c' src = configure_file(input: 'spa-include-test-template.c', output: 'spa-include-test-@0@.@1@'.format(spa_header.underscorify(), ext), configuration: { 'INCLUDE': spa_header, }) executable('spa-include-test-@0@'.format(spa_header.underscorify()), src, dependencies: [ spa_dep ], install: false) endif endforeach endif benchmark_apps = [ ['stress-ringbuffer', []], ['benchmark-pod', []], ['benchmark-dict', []], ] if sndfile_dep.found() benchmark_apps += [ ['benchmark-aec', [sndfile_dep]] ] endif foreach a : benchmark_apps benchmark('spa-' + a[0], executable('spa-' + a[0], a[0] + '.c', dependencies : [ spa_dep, dl_lib, pthread_lib, mathlib ] + a[1], include_directories : [configinc], install : installed_tests_enabled, install_dir : installed_tests_execdir, ), env : [ 'SPA_PLUGIN_DIR=@0@'.format(spa_dep.get_variable('plugindir')), ] ) if installed_tests_enabled test_conf = configuration_data() test_conf.set('exec', installed_tests_execdir / 'spa-' + a[0]) configure_file( input: installed_tests_template, output: 'spa-' + a[0] + '.test', install_dir: installed_tests_metadir, configuration: test_conf, ) endif endforeach pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/tests/spa-include-test-template.c000066400000000000000000000000641511204443500275610ustar00rootroot00000000000000#include <@INCLUDE@> int main(void) { return 0; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/tests/stress-ringbuffer.c000066400000000000000000000062011511204443500262400ustar00rootroot00000000000000#include #include #include #include #include #include #include #define DEFAULT_SIZE 0x2000 #define ARRAY_SIZE 63 #define MAX_VALUE 0x10000 #if defined(__FreeBSD__) || defined(__MidnightBSD__) || defined (__GNU__) #include #if (__FreeBSD_version >= 1400000 && __FreeBSD_version < 1400043) \ || (__FreeBSD_version < 1300523) || defined(__MidnightBSD__) \ || defined (__GNU__) static int sched_getcpu(void) { return -1; }; #endif #endif static struct spa_ringbuffer rb; static uint32_t size; static void *data; static sem_t sem; static int fill_int_array(int *array, int start, int count) { int i, j = start; for (i = 0; i < count; i++) { array[i] = j; j = (j + 1) % MAX_VALUE; } return j; } static int cmp_array(int *array1, int *array2, int count) { int i; for (i = 0; i < count; i++) if (array1[i] != array2[i]) { printf("%d != %d at offset %d\n", array1[i], array2[i], i); return 0; } return 1; } static void *reader_start(void *arg) { int i = 0, a[ARRAY_SIZE], b[ARRAY_SIZE]; printf("reader started on cpu: %d\n", sched_getcpu()); i = fill_int_array(a, i, ARRAY_SIZE); while (1) { uint32_t index; int32_t avail; avail = spa_ringbuffer_get_read_index(&rb, &index); if (avail >= (int32_t)(sizeof(b))) { spa_ringbuffer_read_data(&rb, data, size, index % size, b, sizeof(b)); spa_ringbuffer_read_update(&rb, index + sizeof(b)); if (index >= INT32_MAX - sizeof(a)) break; spa_assert(cmp_array(a, b, ARRAY_SIZE)); i = fill_int_array(a, i, ARRAY_SIZE); } } sem_post(&sem); return NULL; } static void *writer_start(void *arg) { int i = 0, a[ARRAY_SIZE]; printf("writer started on cpu: %d\n", sched_getcpu()); i = fill_int_array(a, i, ARRAY_SIZE); while (1) { uint32_t index; int32_t avail; avail = size - spa_ringbuffer_get_write_index(&rb, &index); if (avail >= (int32_t)(sizeof(a))) { spa_ringbuffer_write_data(&rb, data, size, index % size, a, sizeof(a)); spa_ringbuffer_write_update(&rb, index + sizeof(a)); if (index >= INT32_MAX - sizeof(a)) break; i = fill_int_array(a, i, ARRAY_SIZE); } } sem_post(&sem); return NULL; } #define exit_error(msg) \ do { perror(msg); exit(EXIT_FAILURE); } while (0) int main(int argc, char *argv[]) { pthread_t reader_thread, writer_thread; struct timespec ts; printf("starting ringbuffer stress test\n"); if (argc > 1) sscanf(argv[1], "%d", &size); else size = DEFAULT_SIZE; printf("buffer size (bytes): %d\n", size); printf("array size (bytes): %zd\n", sizeof(int) * ARRAY_SIZE); spa_ringbuffer_init(&rb); data = malloc(size); if (sem_init(&sem, 0, 0) != 0) exit_error("init_sem"); pthread_create(&reader_thread, NULL, reader_start, NULL); pthread_create(&writer_thread, NULL, writer_start, NULL); if (clock_gettime(CLOCK_REALTIME, &ts) != 0) exit_error("clock_gettime"); ts.tv_sec += 2; while (sem_timedwait(&sem, &ts) == -1 && errno == EINTR) continue; while (sem_timedwait(&sem, &ts) == -1 && errno == EINTR) continue; printf("read %u, written %u\n", rb.readindex, rb.writeindex); return 0; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/tools/000077500000000000000000000000001511204443500224215ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/tools/meson.build000066400000000000000000000010761511204443500245670ustar00rootroot00000000000000executable('spa-inspect', 'spa-inspect.c', dependencies : [ spa_dep, dl_lib ], install : true) executable('spa-monitor', 'spa-monitor.c', dependencies : [ spa_dep, dl_lib ], install : true) spa_json_dump = executable('spa-json-dump', 'spa-json-dump.c', dependencies : [ spa_dep ], install : true) spa_json_dump_exe = executable('spa-json-dump-native', 'spa-json-dump.c', dependencies : [ spa_inc_dep ], native : true) meson.override_find_program('spa-json-dump', spa_json_dump_exe) pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/tools/spa-inspect.c000066400000000000000000000167471511204443500250320ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static SPA_LOG_IMPL(default_log); struct data { struct spa_support support[4]; uint32_t n_support; struct spa_log *log; struct spa_loop loop; struct spa_node *node; struct spa_hook listener; }; static void print_param(void *data, int seq, int res, uint32_t type, const void *result) { switch (type) { case SPA_RESULT_TYPE_NODE_PARAMS: { const struct spa_result_node_params *r = result; if (spa_pod_is_object_type(r->param, SPA_TYPE_OBJECT_Format)) spa_debug_format(16, NULL, r->param); else spa_debug_pod(16, NULL, r->param); break; } default: break; } } static void inspect_node_params(struct data *data, struct spa_node *node, uint32_t n_params, struct spa_param_info *params) { int res; uint32_t i; struct spa_hook listener; static const struct spa_node_events node_events = { SPA_VERSION_NODE_EVENTS, .result = print_param, }; for (i = 0; i < n_params; i++) { printf("enumerating: %s:\n", spa_debug_type_find_name(spa_type_param, params[i].id)); if (!SPA_FLAG_IS_SET(params[i].flags, SPA_PARAM_INFO_READ)) continue; spa_zero(listener); spa_node_add_listener(node, &listener, &node_events, data); res = spa_node_enum_params(node, 0, params[i].id, 0, UINT32_MAX, NULL); spa_hook_remove(&listener); if (res != 0) { printf("error enum_params %d: %s", params[i].id, spa_strerror(res)); break; } } } static void inspect_port_params(struct data *data, struct spa_node *node, enum spa_direction direction, uint32_t port_id, uint32_t n_params, struct spa_param_info *params) { int res; uint32_t i; struct spa_hook listener; static const struct spa_node_events node_events = { SPA_VERSION_NODE_EVENTS, .result = print_param, }; for (i = 0; i < n_params; i++) { printf("param: %s: flags %c%c\n", spa_debug_type_find_name(spa_type_param, params[i].id), params[i].flags & SPA_PARAM_INFO_READ ? 'r' : '-', params[i].flags & SPA_PARAM_INFO_WRITE ? 'w' : '-'); if (!SPA_FLAG_IS_SET(params[i].flags, SPA_PARAM_INFO_READ)) continue; printf("values:\n"); spa_zero(listener); spa_node_add_listener(node, &listener, &node_events, data); res = spa_node_port_enum_params(node, 0, direction, port_id, params[i].id, 0, UINT32_MAX, NULL); spa_hook_remove(&listener); if (res != 0) { printf("error port_enum_params %d: %s", params[i].id, spa_strerror(res)); break; } } } static void node_info(void *_data, const struct spa_node_info *info) { struct data *data = _data; printf("node info: %08"PRIx64"\n", info->change_mask); printf("max input ports: %u\n", info->max_input_ports); printf("max output ports: %u\n", info->max_output_ports); if (info->change_mask & SPA_NODE_CHANGE_MASK_PROPS) { printf("node properties:\n"); spa_debug_dict(2, info->props); } if (info->change_mask & SPA_NODE_CHANGE_MASK_PARAMS) { inspect_node_params(data, data->node, info->n_params, info->params); } } static void node_port_info(void *_data, enum spa_direction direction, uint32_t id, const struct spa_port_info *info) { struct data *data = _data; printf(" %s port: %08x", direction == SPA_DIRECTION_INPUT ? "input" : "output", id); if (info == NULL) { printf(" removed\n"); } else { printf(" info:\n"); if (info->change_mask & SPA_PORT_CHANGE_MASK_PROPS) { printf("port properties:\n"); spa_debug_dict(2, info->props); } if (info->change_mask & SPA_PORT_CHANGE_MASK_PARAMS) { inspect_port_params(data, data->node, direction, id, info->n_params, info->params); } } } static const struct spa_node_events node_events = { SPA_VERSION_NODE_EVENTS, .info = node_info, .port_info = node_port_info, }; static void inspect_node(struct data *data, struct spa_node *node) { data->node = node; spa_node_add_listener(node, &data->listener, &node_events, data); spa_hook_remove(&data->listener); } static void inspect_factory(struct data *data, const struct spa_handle_factory *factory) { int res; struct spa_handle *handle; void *interface; const struct spa_interface_info *info; uint32_t index; printf("factory version:\t\t%d\n", factory->version); printf("factory name:\t\t'%s'\n", factory->name); if (factory->version < 1) { printf("\tno further info for version %d < 1\n", factory->version); return; } printf("factory info:\n"); if (factory->info) spa_debug_dict(2, factory->info); else printf(" none\n"); printf("factory interfaces:\n"); for (index = 0;;) { if ((res = spa_handle_factory_enum_interface_info(factory, &info, &index)) <= 0) { if (res != 0) printf("error spa_handle_factory_enum_interface_info: %s", spa_strerror(res)); break; } printf(" interface: '%s'\n", info->type); } handle = calloc(1, spa_handle_factory_get_size(factory, NULL)); if ((res = spa_handle_factory_init(factory, handle, NULL, data->support, data->n_support)) < 0) { printf("can't make factory instance: %d\n", res); goto out; } printf("factory instance:\n"); for (index = 0;;) { if ((res = spa_handle_factory_enum_interface_info(factory, &info, &index)) <= 0) { if (res != 0) printf("error spa_handle_factory_enum_interface_info: %s", spa_strerror(res)); break; } printf(" interface: '%s'\n", info->type); if ((res = spa_handle_get_interface(handle, info->type, &interface)) < 0) { printf("can't get interface: %s: %d\n", info->type, res); continue; } if (spa_streq(info->type, SPA_TYPE_INTERFACE_Node)) inspect_node(data, interface); else printf("skipping unknown interface\n"); } if ((res = spa_handle_clear(handle)) < 0) printf("failed to clear handle: %s\n", spa_strerror(res)); out: free(handle); } static const struct spa_loop_methods impl_loop = { SPA_VERSION_LOOP_METHODS, }; int main(int argc, char *argv[]) { struct data data = { 0 }; int res; void *handle; spa_handle_factory_enum_func_t enum_func; uint32_t index; const char *str; if (argc < 2) { printf("usage: %s \n", argv[0]); return -1; } data.log = &default_log.log; data.loop.iface = SPA_INTERFACE_INIT( SPA_TYPE_INTERFACE_Loop, SPA_VERSION_LOOP, &impl_loop, &data); if ((str = getenv("SPA_DEBUG"))) data.log->level = atoi(str); data.support[0] = SPA_SUPPORT_INIT(SPA_TYPE_INTERFACE_Log, data.log); data.support[1] = SPA_SUPPORT_INIT(SPA_TYPE_INTERFACE_Loop, &data.loop); data.support[2] = SPA_SUPPORT_INIT(SPA_TYPE_INTERFACE_DataLoop, &data.loop); data.n_support = 3; if ((handle = dlopen(argv[1], RTLD_NOW)) == NULL) { printf("can't load %s: %s\n", argv[1], dlerror()); return -1; } if ((enum_func = dlsym(handle, SPA_HANDLE_FACTORY_ENUM_FUNC_NAME)) == NULL) { printf("can't find \"%s\" function: %s\n", SPA_HANDLE_FACTORY_ENUM_FUNC_NAME, dlerror()); return -1; } for (index = 0;;) { const struct spa_handle_factory *factory; if ((res = enum_func(&factory, &index)) <= 0) { if (res != 0) printf("error enum_func: %s", spa_strerror(res)); break; } inspect_factory(&data, factory); } return 0; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/tools/spa-json-dump.c000066400000000000000000000150551511204443500252700ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2021 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #include #include #include #include #include #include #include #include #include #define DEFAULT_INDENT 2 struct data { const char *filename; FILE *file; void *data; size_t size; int indent; bool simple_string; const char *comma; const char *key_sep; }; #define OPTIONS "hi:s" static const struct option long_options[] = { { "help", no_argument, NULL, 'h'}, { "indent", required_argument, NULL, 'i' }, { "spa", no_argument, NULL, 's' }, { NULL, 0, NULL, 0 } }; static void show_usage(struct data *d, const char *name, bool is_error) { FILE *fp; fp = is_error ? stderr : stdout; fprintf(fp, "%s [options] [spa-json-file]\n", name); fprintf(fp, " -h, --help Show this help\n" "\n"); fprintf(fp, " -i --indent set indent (default %d)\n" " -s --spa use simplified SPA JSON\n" "\n", DEFAULT_INDENT); } #define REJECT "\"\\'=:,{}[]()#" static bool is_simple_string(const char *val, int len) { int i; for (i = 0; i < len; i++) { if (val[i] < 0x20 || strchr(REJECT, val[i]) != NULL) return false; } return true; } static void encode_string(struct data *d, const char *val, int len) { FILE *f = d->file; int i; if (d->simple_string && is_simple_string(val, len)) { fprintf(f, "%.*s", len, val); return; } fprintf(f, "\""); for (i = 0; i < len; i++) { char v = val[i]; switch (v) { case '\n': fprintf(f, "\\n"); break; case '\r': fprintf(f, "\\r"); break; case '\b': fprintf(f, "\\b"); break; case '\t': fprintf(f, "\\t"); break; case '\f': fprintf(f, "\\f"); break; case '\\': case '"': fprintf(f, "\\%c", v); break; default: if (v > 0 && v < 0x20) fprintf(f, "\\u%04x", v); else fprintf(f, "%c", v); break; } } fprintf(f, "\""); } static int dump(struct data *d, int indent, struct spa_json *it, const char *value, int len) { FILE *file = d->file; struct spa_json sub; bool toplevel = false; int count = 0, res; char key[1024]; if (!value) { toplevel = true; value = "{"; len = 1; } if (spa_json_is_array(value, len)) { fprintf(file, "["); spa_json_enter(it, &sub); while ((len = spa_json_next(&sub, &value)) > 0) { fprintf(file, "%s\n%*s", count++ > 0 ? d->comma : "", indent+d->indent, ""); if ((res = dump(d, indent+d->indent, &sub, value, len)) < 0) return res; } fprintf(file, "%s%*s]", count > 0 ? "\n" : "", count > 0 ? indent : 0, ""); } else if (spa_json_is_object(value, len)) { fprintf(file, "{"); if (!toplevel) spa_json_enter(it, &sub); else sub = *it; while ((len = spa_json_object_next(&sub, key, sizeof(key), &value)) > 0) { fprintf(file, "%s\n%*s", count++ > 0 ? d->comma : "", indent+d->indent, ""); encode_string(d, key, strlen(key)); fprintf(file, "%s ", d->key_sep); res = dump(d, indent+d->indent, &sub, value, len); if (res < 0) { if (toplevel) *it = sub; return res; } } if (toplevel) *it = sub; fprintf(file, "%s%*s}", count > 0 ? "\n" : "", count > 0 ? indent : 0, ""); } else if (spa_json_is_string(value, len) || spa_json_is_null(value, len) || spa_json_is_bool(value, len) || spa_json_is_int(value, len) || spa_json_is_float(value, len)) { fprintf(file, "%.*s", len, value); } else { encode_string(d, value, len); } if (spa_json_get_error(it, NULL, NULL)) return -EINVAL; return 0; } static int process_json(struct data *d) { int len, res; struct spa_json it; const char *value; if ((len = spa_json_begin(&it, d->data, d->size, &value)) <= 0) { fprintf(stderr, "not a valid file '%s': %s\n", d->filename, spa_strerror(len)); return -EINVAL; } if (!spa_json_is_container(value, len)) { spa_json_init(&it, d->data, d->size); value = NULL; len = 0; } res = dump(d, 0, &it, value, len); if (spa_json_next(&it, &value) < 0) res = -EINVAL; fprintf(d->file, "\n"); fflush(d->file); if (res < 0) { struct spa_error_location loc; if (spa_json_get_error(&it, d->data, &loc)) spa_debug_file_error_location(stderr, &loc, "syntax error in file '%s': %s", d->filename, loc.reason); else fprintf(stderr, "error parsing file '%s': %s\n", d->filename, spa_strerror(res)); return -EINVAL; } return 0; } static int process_stdin(struct data *d) { uint8_t *buf = NULL, *p; size_t alloc = 0, size = 0, read_size, res; int err; do { alloc += 1024 + alloc; p = realloc(buf, alloc); if (!p) { fprintf(stderr, "error: %m\n"); goto error; } buf = p; read_size = alloc - size; res = fread(buf + size, 1, read_size, stdin); size += res; } while (res == read_size); if (ferror(stdin)) { fprintf(stderr, "error: %m\n"); goto error; } d->data = buf; d->size = size; err = process_json(d); free(buf); return (err == 0) ? EXIT_SUCCESS : EXIT_FAILURE; error: free(buf); return EXIT_FAILURE; } int main(int argc, char *argv[]) { int c; int longopt_index = 0; int fd, res, exit_code = EXIT_FAILURE; struct data d; struct stat sbuf; spa_zero(d); d.file = stdout; d.filename = "-"; d.simple_string = false; d.comma = ","; d.key_sep = ":"; d.indent = DEFAULT_INDENT; while ((c = getopt_long(argc, argv, OPTIONS, long_options, &longopt_index)) != -1) { switch (c) { case 'h' : show_usage(&d, argv[0], false); return 0; case 'i': d.indent = atoi(optarg); break; case 's': d.simple_string = true; d.comma = ""; d.key_sep = " ="; break; default: show_usage(&d, argv[0], true); return -1; } } if (optind < argc) d.filename = argv[optind++]; if (spa_streq(d.filename, "-")) return process_stdin(&d); if ((fd = open(d.filename, O_CLOEXEC | O_RDONLY)) < 0) { fprintf(stderr, "error opening file '%s': %m\n", d.filename); goto error; } if (fstat(fd, &sbuf) < 0) { fprintf(stderr, "error statting file '%s': %m\n", d.filename); goto error_close; } if ((d.data = mmap(NULL, sbuf.st_size, PROT_READ, MAP_PRIVATE, fd, 0)) == MAP_FAILED) { fprintf(stderr, "error mmapping file '%s': %m\n", d.filename); goto error_close; } d.size = sbuf.st_size; res = process_json(&d); if (res < 0) exit_code = EXIT_FAILURE; else exit_code = EXIT_SUCCESS; munmap(d.data, sbuf.st_size); error_close: close(fd); error: return exit_code; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/spa/tools/spa-monitor.c000066400000000000000000000110431511204443500250340ustar00rootroot00000000000000/* Simple Plugin API */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include static SPA_LOG_IMPL(default_log); struct data { struct spa_log *log; struct spa_loop main_loop; struct spa_support support[3]; uint32_t n_support; unsigned int n_sources; struct spa_source sources[16]; bool rebuild_fds; struct pollfd fds[16]; unsigned int n_fds; }; static void inspect_info(struct data *data, const struct spa_device_object_info *info) { spa_debug_dict(0, info->props); } static void on_device_info(void *_data, const struct spa_device_info *info) { spa_debug_dict(0, info->props); } static void on_device_object_info(void *_data, uint32_t id, const struct spa_device_object_info *info) { struct data *data = _data; if (info == NULL) { fprintf(stderr, "removed: %u\n", id); } else { fprintf(stderr, "added/changed: %u\n", id); inspect_info(data, info); } } static int do_add_source(void *object, struct spa_source *source) { struct data *data = object; data->sources[data->n_sources] = *source; data->n_sources++; data->rebuild_fds = true; return 0; } static const struct spa_loop_methods impl_loop = { SPA_VERSION_LOOP_METHODS, .add_source = do_add_source, }; static const struct spa_device_events impl_device_events = { SPA_VERSION_DEVICE_EVENTS, .info = on_device_info, .object_info = on_device_object_info, }; static void handle_device(struct data *data, struct spa_device *device) { struct spa_hook listener; spa_zero(listener); spa_device_add_listener(device, &listener, &impl_device_events, data); while (true) { int r; uint32_t i; /* rebuild */ if (data->rebuild_fds) { for (i = 0; i < data->n_sources; i++) { struct spa_source *p = &data->sources[i]; data->fds[i].fd = p->fd; data->fds[i].events = p->mask; } data->n_fds = data->n_sources; data->rebuild_fds = false; } r = poll((struct pollfd *) data->fds, data->n_fds, -1); if (r < 0) { if (errno == EINTR) continue; break; } if (r == 0) { fprintf(stderr, "device %p: select timeout", device); break; } /* after */ for (i = 0; i < data->n_sources; i++) { struct spa_source *p = &data->sources[i]; p->func(p); } } spa_hook_remove(&listener); } int main(int argc, char *argv[]) { struct data data = { 0 }; int res; void *handle; spa_handle_factory_enum_func_t enum_func; uint32_t fidx; data.log = &default_log.log; data.main_loop.iface = SPA_INTERFACE_INIT( SPA_TYPE_INTERFACE_Loop, SPA_VERSION_LOOP, &impl_loop, &data); data.support[0] = SPA_SUPPORT_INIT(SPA_TYPE_INTERFACE_Log, data.log); data.support[1] = SPA_SUPPORT_INIT(SPA_TYPE_INTERFACE_Loop, &data.main_loop); data.n_support = 2; if (argc < 2) { printf("usage: %s \n", argv[0]); return -1; } if ((handle = dlopen(argv[1], RTLD_NOW)) == NULL) { printf("can't load %s\n", argv[1]); return -1; } if ((enum_func = dlsym(handle, SPA_HANDLE_FACTORY_ENUM_FUNC_NAME)) == NULL) { printf("can't find function\n"); return -1; } for (fidx = 0;;) { const struct spa_handle_factory *factory; uint32_t iidx; if ((res = enum_func(&factory, &fidx)) <= 0) { if (res != 0) printf("can't enumerate factories: %d\n", res); break; } if (factory->version < 1) { printf("factories version %d < %d not supported\n", factory->version, 1); continue; } for (iidx = 0;;) { const struct spa_interface_info *info; if ((res = spa_handle_factory_enum_interface_info(factory, &info, &iidx)) <= 0) { if (res != 0) printf("can't enumerate interfaces: %d\n", res); break; } if (spa_streq(info->type, SPA_TYPE_INTERFACE_Device)) { struct spa_handle *handle; void *interface; handle = calloc(1, spa_handle_factory_get_size(factory, NULL)); if ((res = spa_handle_factory_init(factory, handle, NULL, data.support, data.n_support)) < 0) { printf("can't make factory instance: %s\n", strerror(res)); continue; } if ((res = spa_handle_get_interface(handle, SPA_TYPE_INTERFACE_Device, &interface)) < 0) { printf("can't get interface: %s\n", strerror(res)); continue; } handle_device(&data, interface); } } } return 0; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/000077500000000000000000000000001511204443500212655ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/daemon/000077500000000000000000000000001511204443500225305ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/daemon/client.conf.avail/000077500000000000000000000000001511204443500260255ustar00rootroot0000000000000020-upmix.conf.in000066400000000000000000000003411511204443500306010ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/daemon/client.conf.avail# Enables upmixing stream.properties = { channelmix.upmix = true channelmix.upmix-method = psd # none, simple channelmix.lfe-cutoff = 150 channelmix.fc-cutoff = 12000 channelmix.rear-delay = 12.0 } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/daemon/client.conf.avail/meson.build000066400000000000000000000005231511204443500301670ustar00rootroot00000000000000conf_files = [ '20-upmix.conf', ] foreach c : conf_files res = configure_file(input : '@0@.in'.format(c), output : c, configuration : conf_config, install_dir : pipewire_confdatadir / 'client.conf.avail') test(f'validate-json-client-@c@', spa_json_dump_exe, args : res) endforeach pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/daemon/client.conf.in000066400000000000000000000107311511204443500252640ustar00rootroot00000000000000# Client config file for PipeWire version @VERSION@ # # # Copy and edit this file in @PIPEWIRE_CONFIG_DIR@ for system-wide changes # or in ~/.config/pipewire for local changes. # # It is also possible to place a file with an updated section in # @PIPEWIRE_CONFIG_DIR@/client.conf.d/ for system-wide changes or in # ~/.config/pipewire/client.conf.d/ for local changes. # context.properties = { ## Configure properties in the system. #mem.warn-mlock = false #mem.allow-mlock = true #mem.mlock-all = false log.level = 0 #default.clock.quantum-limit = 8192 } context.spa-libs = { # = # # Used to find spa factory names. It maps an spa factory name # regular expression to a library name that should contain # that factory. # audio.convert.* = audioconvert/libspa-audioconvert support.* = support/libspa-support video.convert.* = videoconvert/libspa-videoconvert } context.modules = [ #{ name = # ( args = { = ... } ) # ( flags = [ ( ifexists ) ( nofail ) ] ) # ( condition = [ { = ... } ... ] ) #} # # Loads a module with the given parameters. # If ifexists is given, the module is ignored when it is not found. # If nofail is given, module initialization failures are ignored. # # Uses realtime scheduling to boost the audio thread priorities { name = libpipewire-module-rt args = { #rt.prio = @rtprio_client@ #rt.time.soft = -1 #rt.time.hard = -1 } flags = [ ifexists nofail ] condition = [ { module.rt = !false } ] } # The native communication protocol. { name = libpipewire-module-protocol-native } # Allows creating nodes that run in the context of the # client. Is used by all clients that want to provide # data to PipeWire. { name = libpipewire-module-client-node condition = [ { module.client-node = !false } ] } # Allows creating devices that run in the context of the # client. Is used by the session manager. { name = libpipewire-module-client-device condition = [ { module.client-device = !false } ] } # Makes a factory for wrapping nodes in an adapter with a # converter and resampler. { name = libpipewire-module-adapter condition = [ { module.adapter = !false } ] } # Allows applications to create metadata objects. It creates # a factory for Metadata objects. { name = libpipewire-module-metadata condition = [ { module.metadata = !false } ] } # Provides factories to make session manager objects. { name = libpipewire-module-session-manager condition = [ { module.session-manager = !false } ] } ] filter.properties = { #node.latency = 1024/48000 } stream.properties = { #node.latency = 1024/48000 #node.autoconnect = true #resample.quality = 4 #channelmix.normalize = false #channelmix.mix-lfe = true #channelmix.upmix = true #channelmix.upmix-method = psd # none, simple #channelmix.lfe-cutoff = 150 #channelmix.fc-cutoff = 12000 #channelmix.rear-delay = 12.0 #channelmix.stereo-widen = 0.0 #channelmix.hilbert-taps = 0 #dither.noise = 0 } stream.rules = [ { matches = [ { # all keys must match the value. ! negates. ~ starts regex. #application.name = "pw-cat" #node.name = "~Google Chrome$" } ] actions = { update-props = { #node.latency = 512/48000 } } } ] alsa.properties = { #alsa.deny = false # ALSA params take a single value, an array [] of values # or a range { min=.. max=... } #alsa.access = [ MMAP_INTERLEAVED MMAP_NONINTERLEAVED RW_INTERLEAVED RW_NONINTERLEAVED ] #alsa.format = [ FLOAT S32 S24 S24_3 S16 U8 ] #alsa.rate = { min=1 max=384000 } # or [ 44100 48000 .. ] #alsa.channels = { min=1 max=64 } # or [ 2 4 6 .. ] #alsa.period-bytes = { min=128 max=2097152 } # or [ 128 256 1024 .. ] #alsa.buffer-bytes = { min=256 max=4194304 } # or [ 256 512 4096 .. ] #alsa.volume-method = cubic # linear, cubic } # client specific properties alsa.rules = [ { matches = [ { application.process.binary = "resolve" } ] actions = { update-props = { alsa.buffer-bytes = 131072 } } } ] pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/daemon/filter-chain.conf.in000066400000000000000000000036341511204443500263570ustar00rootroot00000000000000# Filter-chain config file for PipeWire version @VERSION@ # # # This is a base config file for running filters. # # Place filter fragments in @PIPEWIRE_CONFIG_DIR@/filter-chain.conf.d/ # for system-wide changes or in ~/.config/pipewire/filter-chain.conf.d/ # for local changes. # # Run the filters with pipewire -c filter-chain.conf # context.properties = { ## Configure properties in the system. #mem.warn-mlock = false #mem.allow-mlock = true #mem.mlock-all = false log.level = 0 } context.spa-libs = { # = # # Used to find spa factory names. It maps an spa factory name # regular expression to a library name that should contain # that factory. # audio.convert.* = audioconvert/libspa-audioconvert support.* = support/libspa-support } context.modules = [ #{ name = # ( args = { = ... } ) # ( flags = [ ( ifexists ) ( nofail ) ] ) # ( condition = [ { = ... } ... ] ) #} # # Loads a module with the given parameters. # If ifexists is given, the module is ignored when it is not found. # If nofail is given, module initialization failures are ignored. # # Uses realtime scheduling to boost the audio thread priorities { name = libpipewire-module-rt args = { #rt.prio = @rtprio_client@ #rt.time.soft = -1 #rt.time.hard = -1 } flags = [ ifexists nofail ] } # The native communication protocol. { name = libpipewire-module-protocol-native } # Allows creating nodes that run in the context of the # client. Is used by all clients that want to provide # data to PipeWire. { name = libpipewire-module-client-node } # Makes a factory for wrapping nodes in an adapter with a # converter and resampler. { name = libpipewire-module-adapter } ] pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/daemon/filter-chain/000077500000000000000000000000001511204443500250755ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/daemon/filter-chain/22-onnx-vad.conf000066400000000000000000000047611511204443500277270ustar00rootroot00000000000000context.modules = [ { name = libpipewire-module-filter-chain flags = [ nofail ] args = { node.description = "ONNX Example" node.name = "neural.example" audio.rate = 16000 node.latency = "512/16000" filter.graph = { nodes = [ { type = builtin name = copy label = copy } { type = onnx name = onnx label = { #filename = "/home/wim/src/silero-vad/src/silero_vad/data/silero_vad_half.onnx" filename = "/home/wim/src/silero-vad/src/silero_vad/data/silero_vad.onnx" blocksize = 512 input-tensors = { "input" = { dimensions = [ 1, 576 ] retain = 64 data = "port:input" } "state" = { dimensions = [ 2, 1, 128 ] data = "tensor:stateN" } "sr" = { dimensions = [ 1 ] data = "param:rate" } } output-tensors = { "output" = { dimensions = [ 1, 1 ] data = "control:speech" } "stateN" = { dimensions = [ 2, 1, 128 ] } } } control = { } config = { } } { type = builtin name = noisegate label = noisegate control = { "Open Threshold" 0.1 "Close Threshold" 0.02 } } ] links = [ { output = "copy:Out" input="onnx:input" } { output = "copy:Out" input="noisegate:In" } { output = "onnx:speech" input="noisegate:Level" } ] inputs = [ "copy:In" ] outputs = [ "noisegate:Out" ] } capture.props = { node.name = "capture.neural" audio.position = [ MONO ] } playback.props = { node.name = "playback.neural" audio.position = [ MONO ] } } } ] pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/daemon/filter-chain/35-ebur128.conf000066400000000000000000000037571511204443500273750ustar00rootroot00000000000000context.modules = [ { name = libpipewire-module-filter-chain args = { node.description = "EBU R128 Normalizer" media.name = "EBU R128 Normalizer" filter.graph = { nodes = [ { name = ebur128 type = ebur128 label = ebur128 } { name = lufsL type = ebur128 label = lufs2gain control = { "Target LUFS" = -16.0 } } { name = lufsR type = ebur128 label = lufs2gain control = { "Target LUFS" = -16.0 } } { name = volumeL type = builtin label = linear } { name = volumeR type = builtin label = linear } ] links = [ { output = "ebur128:Out FL" input = "volumeL:In" } { output = "ebur128:Global LUFS" input = "lufsL:LUFS" } { output = "lufsL:Gain" input = "volumeL:Mult" } { output = "ebur128:Out FR" input = "volumeR:In" } { output = "ebur128:Global LUFS" input = "lufsR:LUFS" } { output = "lufsR:Gain" input = "volumeR:Mult" } ] inputs = [ "ebur128:In FL" "ebur128:In FR" ] outputs = [ "volumeL:Out" "volumeR:Out" ] } capture.props = { node.name = "effect_input.ebur128_normalize" audio.position = [ FL FR ] media.class = Audio/Sink } playback.props = { node.name = "effect_output.ebur128_normalize" audio.position = [ FL FR ] node.passive = true } } } ] pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/daemon/filter-chain/36-dcblock.conf000066400000000000000000000033551511204443500276010ustar00rootroot00000000000000context.modules = [ { name = libpipewire-module-filter-chain args = { node.description = "DCBlock Filter" media.name = "DCBlock Filter" filter.graph = { nodes = [ { name = dcblock type = builtin label = dcblock control = { "R" = 0.995 } } { # add a short 20ms ramp name = ramp type = builtin label = ramp control = { "Start" = 0.0 "Stop" = 1.0 "Duration (s)" = 0.020 } } { name = volumeL type = builtin label = mult } { name = volumeR type = builtin label = mult } ] links = [ { output = "dcblock:Out 1" input = "volumeL:In 1" } { output = "dcblock:Out 2" input = "volumeR:In 1" } { output = "ramp:Out" input = "volumeL:In 2" } { output = "ramp:Out" input = "volumeR:In 2" } ] inputs = [ "dcblock:In 1" "dcblock:In 2" ] outputs = [ "volumeL:Out" "volumeR:Out" ] } capture.props = { node.name = "effect_input.dcblock" audio.position = [ FL FR ] media.class = Audio/Sink } playback.props = { node.name = "effect_output.dcblock" audio.position = [ FL FR ] node.passive = true } } } ] pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/daemon/filter-chain/demonic.conf000066400000000000000000000042471511204443500273710ustar00rootroot00000000000000# filter-chain example config file for PipeWire version @VERSION@ # # # Copy this file into a conf.d/ directory such as # ~/.config/pipewire/filter-chain.conf.d/ # context.modules = [ { name = libpipewire-module-filter-chain flags = [ nofail ] args = { #audio.format = F32 #audio.rate = 48000 audio.channels = 2 audio.position = [ FL FR ] node.description = "Demonic example" media.name = "Demonic example" filter.graph = { nodes = [ { name = rev type = ladspa plugin = revdelay_1605 label = revdelay control = { "Delay Time (s)" = 2.0 } } { name = pitch type = ladspa plugin = am_pitchshift_1433 label = amPitchshift control = { "Pitch shift" = 0.6 } } { name = rev2 type = ladspa plugin = g2reverb label = G2reverb control = { "Reverb tail" = 0.5 "Damping" = 0.9 } } ] links = [ { output = "rev:Output" input = "pitch:Input" } { output = "pitch:Output" input = "rev2:In L" } ] inputs = [ "rev:Input" ] outputs = [ "rev2:Out L" ] } capture.props = { node.name = "effect_input.filter-chain-demonic" #media.class = Audio/Sink } playback.props = { node.name = "effect_output.filter-chain-demonic" #media.class = Audio/Source } } } ] pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/daemon/filter-chain/meson.build000066400000000000000000000020451511204443500272400ustar00rootroot00000000000000conf_files = [ [ 'demonic.conf', 'demonic.conf' ], [ 'source-duplicate-FL.conf', 'source-duplicate-FL.conf' ], [ 'sink-mix-FL-FR.conf', 'sink-mix-FL-FR.conf' ], [ 'sink-make-LFE.conf', 'sink-make-LFE.conf' ], [ 'sink-virtual-surround-5.1-kemar.conf', 'sink-virtual-surround-5.1-kemar.conf' ], [ 'sink-virtual-surround-7.1-hesuvi.conf', 'sink-virtual-surround-7.1-hesuvi.conf' ], [ 'sink-dolby-surround.conf', 'sink-dolby-surround.conf' ], [ 'sink-dolby-pro-logic-ii.conf', 'sink-dolby-pro-logic-ii.conf' ], [ 'sink-eq6.conf', 'sink-eq6.conf' ], [ 'sink-matrix-spatialiser.conf', 'sink-matrix-spatialiser.conf' ], [ 'source-rnnoise.conf', 'source-rnnoise.conf' ], [ 'sink-upmix-5.1-filter.conf', 'sink-upmix-5.1-filter.conf' ], ] foreach c : conf_files res = configure_file(input : c.get(0), output : c.get(1), configuration : conf_config, install_dir : pipewire_confdatadir / 'filter-chain') test('validate-json-filter-chain-' + c.get(0), spa_json_dump_exe, args : res) endforeach sink-dolby-pro-logic-ii.conf000066400000000000000000000072351511204443500322370ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/daemon/filter-chain# Dolby Pro Logic II encoder sink # # Copy this file into a conf.d/ directory such as # ~/.config/pipewire/filter-chain.conf.d/ # { "context.modules": [ { "name": "libpipewire-module-filter-chain", "flags": [ "nofail" ], "args": { "node.description": "Dolby Pro Logic II Sink", "media.name": "Dolby Pro Logic II Sink", "filter.graph": { "nodes": [ { "type": "builtin", "name": "fc_copy", "label": "copy" }, { "type": "builtin", "name": "lfe_copy", "label": "copy" }, { "type": "builtin", "name": "sl_phased", "label": "convolver", "config": { "filename": "/hilbert", "length": 90 } }, { "type": "builtin", "name": "sr_phased", "label": "convolver", "config": { "filename": "/hilbert", "length": 90 } }, { "type": "builtin", "name": "mixer_lt", "label": "mixer", "control": { "Gain 1": 1, "Gain 2": 0, "Gain 3": 0.7071067811865475, "Gain 4": 0.7071067811865475, "Gain 5": -0.8660254037844386, "Gain 6": -0.5 } }, { "type": "builtin", "name": "mixer_rt", "label": "mixer", "control": { "Gain 1": 0, "Gain 2": 1, "Gain 3": 0.7071067811865475, "Gain 4": 0.7071067811865475, "Gain 5": 0.5, "Gain 6": 0.8660254037844386 } } ], "links": [ { "output": "fc_copy:Out", "input": "mixer_lt:In 3" }, { "output": "fc_copy:Out", "input": "mixer_rt:In 3" }, { "output": "lfe_copy:Out", "input": "mixer_lt:In 4" }, { "output": "lfe_copy:Out", "input": "mixer_rt:In 4" }, { "output": "sl_phased:Out", "input": "mixer_lt:In 5" }, { "output": "sl_phased:Out", "input": "mixer_rt:In 5" }, { "output": "sr_phased:Out", "input": "mixer_lt:In 6" }, { "output": "sr_phased:Out", "input": "mixer_rt:In 6" } ], "inputs": [ "mixer_lt:In 1", "mixer_rt:In 2", "fc_copy:In", "lfe_copy:In", "sl_phased:In", "sr_phased:In" ], "outputs": [ "mixer_lt:Out", "mixer_rt:Out" ] }, "capture.props": { "node.name": "effect_input.dolby_pro_logic_ii", "media.class": "Audio/Sink", "audio.channels": 6, "audio.position": [ "FL", "FR", "FC", "LFE", "SL", "SR" ] }, "playback.props": { "node.name": "effect_output.dolby_pro_logic_ii", "node.passive": true, "audio.channels": 2, "audio.position": [ "FL", "FR" ] } } } ] } sink-dolby-surround.conf000066400000000000000000000056671511204443500316350ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/daemon/filter-chain# Dolby Surround encoder sink # # Copy this file into a conf.d/ directory such as # ~/.config/pipewire/filter-chain.conf.d/ # { "context.modules": [ { "name": "libpipewire-module-filter-chain", "flags": [ "nofail" ], "args": { "node.description": "Dolby Surround Sink", "media.name": "Dolby Surround Sink", "filter.graph": { "nodes": [ { "type": "builtin", "name": "mixer_fc", "label": "mixer" }, { "type": "builtin", "name": "mixer_s", "label": "mixer" }, { "type": "builtin", "name": "s_phased", "label": "convolver", "config": { "filename": "/hilbert", "length": 90 } }, { "type": "builtin", "name": "mixer_lt", "label": "mixer", "control": { "Gain 1": 1, "Gain 2": 0, "Gain 3": 0.7071067811865475, "Gain 4": -0.7071067811865475 } }, { "type": "builtin", "name": "mixer_rt", "label": "mixer", "control": { "Gain 1": 0, "Gain 2": 1, "Gain 3": 0.7071067811865475, "Gain 4": 0.7071067811865475 } } ], "links": [ { "output": "mixer_fc:Out", "input": "mixer_lt:In 3" }, { "output": "mixer_fc:Out", "input": "mixer_rt:In 3" }, { "output": "mixer_s:Out", "input": "s_phased:In" }, { "output": "s_phased:Out", "input": "mixer_lt:In 4" }, { "output": "s_phased:Out", "input": "mixer_rt:In 4" } ], "inputs": [ "mixer_lt:In 1", "mixer_rt:In 2", "mixer_fc:In 1", "mixer_fc:In 2", "mixer_s:In 1", "mixer_s:In 2" ], "outputs": [ "mixer_lt:Out", "mixer_rt:Out" ] }, "capture.props": { "node.name": "effect_input.dolby_surround", "media.class": "Audio/Sink", "audio.channels": 6, "audio.position": [ "FL", "FR", "FC", "LFE", "SL", "SR" ] }, "playback.props": { "node.name": "effect_output.dolby_surround", "node.passive": true, "audio.channels": 2, "audio.position": [ "FL", "FR" ] } } } ] } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/daemon/filter-chain/sink-eq6.conf000066400000000000000000000050541511204443500274050ustar00rootroot00000000000000# 6 band sink equalizer # # Copy this file into a conf.d/ directory such as # ~/.config/pipewire/filter-chain.conf.d/ # context.modules = [ { name = libpipewire-module-filter-chain args = { node.description = "Equalizer Sink" media.name = "Equalizer Sink" filter.graph = { nodes = [ { type = builtin name = eq_band_1 label = bq_lowshelf control = { "Freq" = 100.0 "Q" = 1.0 "Gain" = 0.0 } } { type = builtin name = eq_band_2 label = bq_peaking control = { "Freq" = 100.0 "Q" = 1.0 "Gain" = 0.0 } } { type = builtin name = eq_band_3 label = bq_peaking control = { "Freq" = 500.0 "Q" = 1.0 "Gain" = 0.0 } } { type = builtin name = eq_band_4 label = bq_peaking control = { "Freq" = 2000.0 "Q" = 1.0 "Gain" = 0.0 } } { type = builtin name = eq_band_5 label = bq_peaking control = { "Freq" = 5000.0 "Q" = 1.0 "Gain" = 0.0 } } { type = builtin name = eq_band_6 label = bq_highshelf control = { "Freq" = 5000.0 "Q" = 1.0 "Gain" = 0.0 } } ] links = [ { output = "eq_band_1:Out" input = "eq_band_2:In" } { output = "eq_band_2:Out" input = "eq_band_3:In" } { output = "eq_band_3:Out" input = "eq_band_4:In" } { output = "eq_band_4:Out" input = "eq_band_5:In" } { output = "eq_band_5:Out" input = "eq_band_6:In" } ] } audio.channels = 2 audio.position = [ FL FR ] capture.props = { node.name = "effect_input.eq6" media.class = Audio/Sink } playback.props = { node.name = "effect_output.eq6" node.passive = true } } } ] pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/daemon/filter-chain/sink-make-LFE.conf000066400000000000000000000041021511204443500302240ustar00rootroot00000000000000# An example filter chain that makes a stereo sink that mixes # the FL and FR channels to FL, FR, LFE # # Copy this file into a conf.d/ directory # context.modules = [ { name = libpipewire-module-filter-chain args = { node.description = "LFE example" media.name = "LFE example" filter.graph = { nodes = [ { name = copyIL type = builtin label = copy } { name = copyOL type = builtin label = copy } { name = copyIR type = builtin label = copy } { name = copyOR type = builtin label = copy } { name = mix type = builtin label = mixer control = { "Gain 1" = 0.5 "Gain 2" = 0.5 } } { type = builtin name = lpLFE label = bq_lowpass control = { "Freq" = 150.0 } } ] links = [ { output = "copyIL:Out" input = "copyOL:In" } { output = "copyIR:Out" input = "copyOR:In" } { output = "copyIL:Out" input = "mix:In 1" } { output = "copyIR:Out" input = "mix:In 2" } { output = "mix:Out" input = "lpLFE:In" } ] inputs = [ "copyIL:In" "copyIR:In" ] outputs = [ "copyOL:Out" "copyOR:Out" "lpLFE:Out"] } capture.props = { node.name = "input_lfe" audio.position = [ FL FR ] media.class = "Audio/Sink" } playback.props = { node.name = "output_lfe" audio.position = [ FL FR LFE ] stream.dont-remix = true node.passive = true } } } ] sink-matrix-spatialiser.conf000066400000000000000000000024641511204443500324570ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/daemon/filter-chain# Matrix Spatialiser sink # # Copy this file into a conf.d/ directory such as # ~/.config/pipewire/filter-chain.conf.d/ # # ( Jean-Philippe Guillemin ) # context.modules = [ { name = libpipewire-module-filter-chain flags = [ nofail ] args = { node.description = "Matrix Spatialiser" media.name = "Matrix Spatialiser" filter.graph = { nodes = [ { type = ladspa name = matrix plugin = matrix_spatialiser_1422 label = matrixSpatialiser control = { "Width" = 80 } } ] inputs = [ "matrix:Input L" "matrix:Input R" ] outputs = [ "matrix:Output L" "matrix:Output R" ] } audio.channels = 2 audio.position = [ FL FR ] capture.props = { node.name = "effect_input.matrix_spatialiser" media.class = Audio/Sink } playback.props = { node.name = "effect_output.matrix_spatialiser" node.passive = true } } } ] pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/daemon/filter-chain/sink-mix-FL-FR.conf000066400000000000000000000024461511204443500303150ustar00rootroot00000000000000# An example filter chain that makes a stereo sink that mixes # the FL and FR channels to a single FL channel # # Copy this file into a conf.d/ directory such as # ~/.config/pipewire/filter-chain.conf.d/ # context.modules = [ { name = libpipewire-module-filter-chain args = { node.description = "Mix example" media.name = "Mix example" filter.graph = { nodes = [ { name = mix type = builtin label = mixer control = { "Gain 1" = 0.5 "Gain 2" = 0.5 } } ] inputs = [ "mix:In 1" "mix:In 2" ] outputs = [ "mix:Out" ] } capture.props = { node.name = "mix_input.mix-FL-FR-to-FL" audio.position = [ FL FR ] media.class = "Audio/Sink" } playback.props = { node.name = "mix_output.mix-FL-FR-to-FL" audio.position = [ FL ] stream.dont-remix = true node.passive = true } } } ] sink-upmix-5.1-filter.conf000066400000000000000000000136341511204443500315640ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/daemon/filter-chain# Stereo to 5.1 upmix sink # # Copy this file into a conf.d/ directory such as # ~/.config/pipewire/filter-chain.conf.d/ # context.modules = [ { name = libpipewire-module-filter-chain args = { node.description = "Upmix Sink" filter.graph = { nodes = [ { type = builtin name = copyFL label = copy } { type = builtin name = copyFR label = copy } { type = builtin name = copyOFL label = copy } { type = builtin name = copyOFR label = copy } { # this mixes the front left and right together # for filtering the center and subwoofer signal- name = mixF type = builtin label = mixer control = { "Gain 1" = 0.707 "Gain 2" = 0.707 } } { # filtering of the FC and LFE channel. We use a 2 channel # parametric equalizer with custom filters for each channel. # This makes it possible to run the filters in parallel. type = builtin name = eq_FC_LFE label = param_eq config = { filters1 = [ # FC is a crossover filter (with 2 lowpass biquads) { type = bq_lowpass freq = 12000 }, { type = bq_lowpass freq = 12000 }, ] filters2 = [ # LFE is first a gain adjustment (with a highself) and # then a crossover filter (with 2 lowpass biquads) { type = bq_highshelf freq = 0 gain = -20.0 }, # gain -20dB { type = bq_lowpass freq = 120 }, { type = bq_lowpass freq = 120 }, ] } } { # for the rear channels, we subtract the front channels. Do this # with a mixer with negative gain to flip the sign. name = subR type = builtin label = mixer control = { "Gain 1" = 0.707 "Gain 2" = -0.707 } } { # a delay for the rear Left channel. This can be # replaced with the convolver below. */ type = builtin name = delayRL label = delay config = { "max-delay" = 1 } control = { "Delay (s)" = 0.012 } } { # a delay for the rear Right channel. This can be # replaced with the convolver below. */ type = builtin name = delayRR label = delay config = { "max-delay" = 1 } control = { "Delay (s)" = 0.012 } } { # an optional convolver with a hilbert curve to # change the phase. It also has a delay, making the above # left delay filter optional. type = builtin name = convRL label = convolver config = { gain = 1.0 delay = 0.012 filename = "/hilbert" length = 33 latency = 0.0 } } { # an optional convolver with a hilbert curve to # change the phase. It also has a delay, making the above # right delay filter optional. type = builtin name = convRR label = convolver config = { gain = -1.0 delay = 0.012 filename = "/hilbert" length = 33 latency = 0.0 } } ] links = [ { output = "copyFL:Out" input="mixF:In 1" } { output = "copyFR:Out" input="mixF:In 2" } { output = "copyFL:Out" input="copyOFR:In" } { output = "copyFR:Out" input="copyOFL:In" } { output = "mixF:Out" input="eq_FC_LFE:In 1" } { output = "mixF:Out" input="eq_FC_LFE:In 2" } { output = "copyFL:Out" input="subR:In 1" } { output = "copyFR:Out" input="subR:In 2" } # here we can choose to just delay or also convolve # #{ output = "subR:Out" input="delayRL:In" } #{ output = "subR:Out" input="delayRR:In" } { output = "subR:Out" input="convRL:In" } { output = "subR:Out" input="convRR:In" } ] inputs = [ "copyFL:In" "copyFR:In" ] outputs = [ "copyOFL:Out" "copyOFR:Out" "eq_FC_LFE:Out 1" "eq_FC_LFE:Out 2" # here we can choose to just delay or also convolve # #"delayRL:Out" #"delayRR:Out" "convRL:Out" "convRR:Out" ] } capture.props = { node.name = "effect_input.upmix_5.1" media.class = "Audio/Sink" audio.position = [ FL FR ] } playback.props = { node.name = "effect_output.upmix_5.1" audio.position = [ FL FR FC LFE RL RR ] stream.dont-remix = true node.passive = true } } } ] sink-virtual-surround-5.1-kemar.conf000066400000000000000000000153461511204443500336030ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/daemon/filter-chain# Convolver sink # # Copy this file into a conf.d/ directory such as # ~/.config/pipewire/filter-chain.conf.d/ # # Adjust the paths to the convolver files to match your system # context.modules = [ { name = libpipewire-module-filter-chain flags = [ nofail ] args = { node.description = "Virtual Surround Sink" media.name = "Virtual Surround Sink" filter.graph = { nodes = [ { type = builtin label = convolver name = convFL_L config = { filename = "hrir_kemar/hrir-kemar.wav" channel = 0 } } { type = builtin label = convolver name = convFL_R config = { filename = "hrir_kemar/hrir-kemar.wav" channel = 1 } } { type = builtin label = convolver name = convFR_L config = { filename = "hrir_kemar/hrir-kemar.wav" channel = 1 } } { type = builtin label = convolver name = convFR_R config = { filename = "hrir_kemar/hrir-kemar.wav" channel = 0 } } { type = builtin label = convolver name = convFC config = { filename = "hrir_kemar/hrir-kemar.wav" channel = 2 } } { type = builtin label = convolver name = convLFE config = { filename = "hrir_kemar/hrir-kemar.wav" channel = 3 } } { type = builtin label = convolver name = convSL_L config = { filename = "hrir_kemar/hrir-kemar.wav" channel = 4 } } { type = builtin label = convolver name = convSL_R config = { filename = "hrir_kemar/hrir-kemar.wav" channel = 5 } } { type = builtin label = convolver name = convSR_L config = { filename = "hrir_kemar/hrir-kemar.wav" channel = 5 } } { type = builtin label = convolver name = convSR_R config = { filename = "hrir_kemar/hrir-kemar.wav" channel = 4 } } { type = builtin label = mixer name = mixL } { type = builtin label = mixer name = mixR } { type = builtin label = copy name = copyFL } { type = builtin label = copy name = copyFR } { type = builtin label = copy name = copySL } { type = builtin label = copy name = copySR } ] links = [ { output = "copyFL:Out" input = "convFL_L:In" } { output = "copyFL:Out" input = "convFL_R:In" } { output = "copyFR:Out" input = "convFR_R:In" } { output = "copyFR:Out" input = "convFR_L:In" } { output = "copySL:Out" input = "convSL_L:In" } { output = "copySL:Out" input = "convSL_R:In" } { output = "copySR:Out" input = "convSR_R:In" } { output = "copySR:Out" input = "convSR_L:In" } { output = "convFL_L:Out" input = "mixL:In 1" } { output = "convFR_L:Out" input = "mixL:In 2" } { output = "convFC:Out" input = "mixL:In 3" } { output = "convLFE:Out" input = "mixL:In 4" } { output = "convSL_L:Out" input = "mixL:In 5" } { output = "convSR_L:Out" input = "mixL:In 6" } { output = "convFL_R:Out" input = "mixR:In 1" } { output = "convFR_R:Out" input = "mixR:In 2" } { output = "convFC:Out" input = "mixR:In 3" } { output = "convLFE:Out" input = "mixR:In 4" } { output = "convSL_R:Out" input = "mixR:In 5" } { output = "convSR_R:Out" input = "mixR:In 6" } ] inputs = [ "copyFL:In" "copyFR:In" "convFC:In" "convLFE:In" "copySL:In" "copySR:In" ] outputs = [ "mixL:Out" "mixR:Out" ] } capture.props = { node.name = "effect_input.virtual-surround-5.1-kemar" media.class = Audio/Sink audio.channels = 6 audio.position = [ FL FR FC LFE SL SR] } playback.props = { node.name = "effect_output.virtual-surround-5.1-kemar" node.passive = true audio.channels = 2 audio.position = [ FL FR ] } } } ] sink-virtual-surround-7.1-hesuvi.conf000066400000000000000000000145451511204443500340110ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/daemon/filter-chain# Convolver sink # # Copy this file into a conf.d/ directory such as # ~/.config/pipewire/filter-chain.conf.d/ # # Adjust the paths to the convolver files to match your system # context.modules = [ { name = libpipewire-module-filter-chain flags = [ nofail ] args = { node.description = "Virtual Surround Sink" media.name = "Virtual Surround Sink" filter.graph = { nodes = [ # duplicate inputs { type = builtin label = copy name = copyFL } { type = builtin label = copy name = copyFR } { type = builtin label = copy name = copyFC } { type = builtin label = copy name = copyRL } { type = builtin label = copy name = copyRR } { type = builtin label = copy name = copySL } { type = builtin label = copy name = copySR } { type = builtin label = copy name = copyLFE } # apply hrir - HeSuVi 14-channel WAV (not the *-.wav variants) (note: */44/* in HeSuVi are the same, but resampled to 44100) { type = builtin label = convolver name = convFL_L config = { filename = "hrir_hesuvi/hrir.wav" channel = 0 } } { type = builtin label = convolver name = convFL_R config = { filename = "hrir_hesuvi/hrir.wav" channel = 1 } } { type = builtin label = convolver name = convSL_L config = { filename = "hrir_hesuvi/hrir.wav" channel = 2 } } { type = builtin label = convolver name = convSL_R config = { filename = "hrir_hesuvi/hrir.wav" channel = 3 } } { type = builtin label = convolver name = convRL_L config = { filename = "hrir_hesuvi/hrir.wav" channel = 4 } } { type = builtin label = convolver name = convRL_R config = { filename = "hrir_hesuvi/hrir.wav" channel = 5 } } { type = builtin label = convolver name = convFC_L config = { filename = "hrir_hesuvi/hrir.wav" channel = 6 } } { type = builtin label = convolver name = convFR_R config = { filename = "hrir_hesuvi/hrir.wav" channel = 7 } } { type = builtin label = convolver name = convFR_L config = { filename = "hrir_hesuvi/hrir.wav" channel = 8 } } { type = builtin label = convolver name = convSR_R config = { filename = "hrir_hesuvi/hrir.wav" channel = 9 } } { type = builtin label = convolver name = convSR_L config = { filename = "hrir_hesuvi/hrir.wav" channel = 10 } } { type = builtin label = convolver name = convRR_R config = { filename = "hrir_hesuvi/hrir.wav" channel = 11 } } { type = builtin label = convolver name = convRR_L config = { filename = "hrir_hesuvi/hrir.wav" channel = 12 } } { type = builtin label = convolver name = convFC_R config = { filename = "hrir_hesuvi/hrir.wav" channel = 13 } } # treat LFE as FC { type = builtin label = convolver name = convLFE_L config = { filename = "hrir_hesuvi/hrir.wav" channel = 6 } } { type = builtin label = convolver name = convLFE_R config = { filename = "hrir_hesuvi/hrir.wav" channel = 13 } } # stereo output { type = builtin label = mixer name = mixL } { type = builtin label = mixer name = mixR } ] links = [ # input { output = "copyFL:Out" input="convFL_L:In" } { output = "copyFL:Out" input="convFL_R:In" } { output = "copySL:Out" input="convSL_L:In" } { output = "copySL:Out" input="convSL_R:In" } { output = "copyRL:Out" input="convRL_L:In" } { output = "copyRL:Out" input="convRL_R:In" } { output = "copyFC:Out" input="convFC_L:In" } { output = "copyFR:Out" input="convFR_R:In" } { output = "copyFR:Out" input="convFR_L:In" } { output = "copySR:Out" input="convSR_R:In" } { output = "copySR:Out" input="convSR_L:In" } { output = "copyRR:Out" input="convRR_R:In" } { output = "copyRR:Out" input="convRR_L:In" } { output = "copyFC:Out" input="convFC_R:In" } { output = "copyLFE:Out" input="convLFE_L:In" } { output = "copyLFE:Out" input="convLFE_R:In" } # output { output = "convFL_L:Out" input="mixL:In 1" } { output = "convFL_R:Out" input="mixR:In 1" } { output = "convSL_L:Out" input="mixL:In 2" } { output = "convSL_R:Out" input="mixR:In 2" } { output = "convRL_L:Out" input="mixL:In 3" } { output = "convRL_R:Out" input="mixR:In 3" } { output = "convFC_L:Out" input="mixL:In 4" } { output = "convFC_R:Out" input="mixR:In 4" } { output = "convFR_R:Out" input="mixR:In 5" } { output = "convFR_L:Out" input="mixL:In 5" } { output = "convSR_R:Out" input="mixR:In 6" } { output = "convSR_L:Out" input="mixL:In 6" } { output = "convRR_R:Out" input="mixR:In 7" } { output = "convRR_L:Out" input="mixL:In 7" } { output = "convLFE_R:Out" input="mixR:In 8" } { output = "convLFE_L:Out" input="mixL:In 8" } ] inputs = [ "copyFL:In" "copyFR:In" "copyFC:In" "copyLFE:In" "copyRL:In" "copyRR:In", "copySL:In", "copySR:In" ] outputs = [ "mixL:Out" "mixR:Out" ] } capture.props = { node.name = "effect_input.virtual-surround-7.1-hesuvi" media.class = Audio/Sink audio.channels = 8 audio.position = [ FL FR FC LFE RL RR SL SR ] } playback.props = { node.name = "effect_output.virtual-surround-7.1-hesuvi" node.passive = true audio.channels = 2 audio.position = [ FL FR ] } } } ] source-duplicate-FL.conf000066400000000000000000000033641511204443500314420ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/daemon/filter-chain# An example filter chain that makes a source that duplicates the FL channel # to FL and FR. # # Copy this file into a conf.d/ directory such as # ~/.config/pipewire/filter-chain.conf.d/ # context.modules = [ { name = libpipewire-module-filter-chain args = { node.description = "Remap example" media.name = "Remap example" filter.graph = { nodes = [ { name = copyIL type = builtin label = copy } { name = copyOL type = builtin label = copy } { name = copyOR type = builtin label = copy } ] links = [ # we can only tee from nodes, not inputs so we need # to copy the inputs and then tee. { output = "copyIL:Out" input = "copyOL:In" } { output = "copyIL:Out" input = "copyOR:In" } ] inputs = [ "copyIL:In" ] outputs = [ "copyOL:Out" "copyOR:Out" ] } capture.props = { node.name = "remap_input.remap-FL-to-FL-FR" audio.position = [ FL ] stream.dont-remix = true node.passive = true } playback.props = { node.name = "remap_output.remap-FL-to-FL-FR" audio.position = [ FL FR ] media.class = "Audio/Source" } } } ] pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/daemon/filter-chain/source-rnnoise.conf000066400000000000000000000027541511204443500307270ustar00rootroot00000000000000# Noise canceling source # # Copy this file into a conf.d/ directory such as # ~/.config/pipewire/filter-chain.conf.d/ # # Adjust the paths to the rnnoise plugin to match your system # context.modules = [ { name = libpipewire-module-filter-chain flags = [ nofail ] args = { node.description = "Noise Canceling source" media.name = "Noise Canceling source" filter.graph = { nodes = [ { type = ladspa name = rnnoise # The path to the plugin. The suffix .so is appended to # this string and then the file is then located in the directories # listed in the environment variable LADSPA_PATH or # /usr/lib64/ladspa, /usr/lib/ladspa or the system library directory # as a fallback. # You might want to use an absolute path here to avoid problems. plugin = "librnnoise_ladspa" label = noise_suppressor_stereo control = { "VAD Threshold (%)" 50.0 } } ] } audio.position = [ FL FR ] capture.props = { node.name = "effect_input.rnnoise" node.passive = true } playback.props = { node.name = "effect_output.rnnoise" media.class = Audio/Source } } } ] spatializer-7.1.conf000066400000000000000000000144011511204443500305170ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/daemon/filter-chain# Headphone surround sink # # Copy this file into a conf.d/ directory such as # ~/.config/pipewire/filter-chain.conf.d/ # # Adjust the paths to the sofa file to match your system. # context.modules = [ { name = libpipewire-module-filter-chain flags = [ nofail ] args = { node.description = "Spatial Sink" media.name = "Spatial Sink" filter.graph = { nodes = [ { type = sofa label = spatializer name = spFL config = { filename = "~/.config/hrtf-sofa/hrtf b_nh724.sofa" } control = { "Azimuth" = 30.0 "Elevation" = 0.0 "Radius" = 3.0 } } { type = sofa label = spatializer name = spFR config = { filename = "~/.config/hrtf-sofa/hrtf b_nh724.sofa" } control = { "Azimuth" = 330.0 "Elevation" = 0.0 "Radius" = 3.0 } } { type = sofa label = spatializer name = spFC config = { filename = "~/.config/hrtf-sofa/hrtf b_nh724.sofa" } control = { "Azimuth" = 0.0 "Elevation" = 0.0 "Radius" = 3.0 } } { type = sofa label = spatializer name = spRL config = { filename = "~/.config/hrtf-sofa/hrtf b_nh724.sofa" } control = { "Azimuth" = 150.0 "Elevation" = 0.0 "Radius" = 3.0 } } { type = sofa label = spatializer name = spRR config = { filename = "~/.config/hrtf-sofa/hrtf b_nh724.sofa" } control = { "Azimuth" = 210.0 "Elevation" = 0.0 "Radius" = 3.0 } } { type = sofa label = spatializer name = spSL config = { filename = "~/.config/hrtf-sofa/hrtf b_nh724.sofa" } control = { "Azimuth" = 90.0 "Elevation" = 0.0 "Radius" = 3.0 } } { type = sofa label = spatializer name = spSR config = { filename = "~/.config/hrtf-sofa/hrtf b_nh724.sofa" } control = { "Azimuth" = 270.0 "Elevation" = 0.0 "Radius" = 3.0 } } { type = sofa label = spatializer name = spLFE config = { filename = "~/.config/hrtf-sofa/hrtf b_nh724.sofa" } control = { "Azimuth" = 0.0 "Elevation" = -60.0 "Radius" = 3.0 } } { type = builtin label = mixer name = mixL } { type = builtin label = mixer name = mixR } ] links = [ # output { output = "spFL:Out L" input="mixL:In 1" } { output = "spFL:Out R" input="mixR:In 1" } { output = "spFR:Out L" input="mixL:In 2" } { output = "spFR:Out R" input="mixR:In 2" } { output = "spFC:Out L" input="mixL:In 3" } { output = "spFC:Out R" input="mixR:In 3" } { output = "spRL:Out L" input="mixL:In 4" } { output = "spRL:Out R" input="mixR:In 4" } { output = "spRR:Out L" input="mixL:In 5" } { output = "spRR:Out R" input="mixR:In 5" } { output = "spSL:Out L" input="mixL:In 6" } { output = "spSL:Out R" input="mixR:In 6" } { output = "spSR:Out L" input="mixL:In 7" } { output = "spSR:Out R" input="mixR:In 7" } { output = "spLFE:Out L" input="mixL:In 8" } { output = "spLFE:Out R" input="mixR:In 8" } ] inputs = [ "spFL:In" "spFR:In" "spFC:In" "spLFE:In" "spRL:In" "spRR:In", "spSL:In", "spSR:In" ] outputs = [ "mixL:Out" "mixR:Out" ] } capture.props = { node.name = "effect_input.spatializer" media.class = Audio/Sink audio.channels = 8 audio.position = [ FL FR FC LFE RL RR SL SR ] } playback.props = { node.name = "effect_output.spatializer" node.passive = true audio.channels = 2 audio.position = [ FL FR ] } } } ] spatializer-single.conf000066400000000000000000000030451511204443500314750ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/daemon/filter-chain# A virtual sound source sink # Useful for testing spatial effects by moving it around with controls # # Copy this file into a conf.d/ directory such as # ~/.config/pipewire/filter-chain.conf.d/ # # Adjust the paths to the sofa files to match your system # context.modules = [ { name = libpipewire-module-filter-chain flags = [ nofail ] args = { node.description = "3D Sink" media.name = "3D Sink" filter.graph = { nodes = [ { type = sofa label = spatializer name = sp config = { filename = "~/.config/hrtf-sofa/hrtf b_nh724.sofa" } control = { "Azimuth" = 220.0 "Elevation" = 0.0 "Radius" = 3.0 } } ] inputs = [ "sp:In" ] outputs = [ "sp:Out L" "sp:Out R" ] } capture.props = { node.name = "effect_input.3d" media.class = Audio/Sink audio.channels = 1 audio.position = [ FC ] } playback.props = { node.name = "effect_output.3d" node.passive = true audio.channels = 2 audio.position = [ FL FR ] } } } ] pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/daemon/jack.conf.in000066400000000000000000000106701511204443500247200ustar00rootroot00000000000000# JACK client config file for PipeWire version @VERSION@ # # # Copy and edit this file in @PIPEWIRE_CONFIG_DIR@ for system-wide changes # or in ~/.config/pipewire for local changes. # # It is also possible to place a file with an updated section in # @PIPEWIRE_CONFIG_DIR@/jack.conf.d/ for system-wide changes or in # ~/.config/pipewire/jack.conf.d/ for local changes. # context.properties = { ## Configure properties in the system. #mem.warn-mlock = false #mem.allow-mlock = true #mem.mlock-all = false log.level = 0 #default.clock.quantum-limit = 8192 } context.spa-libs = { # = # # Used to find spa factory names. It maps an spa factory name # regular expression to a library name that should contain # that factory. # support.* = support/libspa-support } context.modules = [ #{ name = # ( args = { = ... } ) # ( flags = [ ( ifexists ) ( nofail ) ] ) # ( condition = [ { = ... } ... ] ) #} # # Loads a module with the given parameters. # If ifexists is given, the module is ignored when it is not found. # If nofail is given, module initialization failures are ignored. # # # Boost the data thread priority. { name = libpipewire-module-rt args = { #rt.prio = @rtprio_client@ #rt.time.soft = -1 #rt.time.hard = -1 } flags = [ ifexists nofail ] } # The native communication protocol. { name = libpipewire-module-protocol-native } # Allows creating nodes that run in the context of the # client. Is used by all clients that want to provide # data to PipeWire. { name = libpipewire-module-client-node } # Allows applications to create metadata objects. It creates # a factory for Metadata objects. { name = libpipewire-module-metadata } ] # global properties for all jack clients jack.properties = { #node.latency = 1024/48000 #node.rate = 1/48000 #node.quantum = 1024/48000 #node.lock-quantum = true #node.force-quantum = 0 #jack.show-monitor = true #jack.merge-monitor = true #jack.show-midi = true #jack.short-name = false #jack.filter-name = false #jack.filter-char = " " # # allow: Don't restrict self connect requests # fail-external: Fail self connect requests to external ports only # ignore-external: Ignore self connect requests to external ports only # fail-all: Fail all self connect requests # ignore-all: Ignore all self connect requests #jack.self-connect-mode = allow # # allow: Allow connect request of other ports # fail: Fail connect requests of other ports # ignore: Ignore connect requests of other ports #jack.other-connect-mode = allow #jack.locked-process = true #jack.default-as-system = false #jack.fix-midi-events = true #jack.global-buffer-size = false #jack.max-client-ports = 768 #jack.fill-aliases = false #jack.writable-input = true #jack.flag-midi2 = false } # client specific properties jack.rules = [ { matches = [ { # all keys must match the value. ! negates. ~ starts regex. #client.name = "Carla" #application.process.binary = "jack_simple_client" #application.name = "~jack_simple_client.*" } ] actions = { update-props = { #node.latency = 512/48000 } } } { matches = [ { application.process.binary = "jack_bufsize" } ] actions = { update-props = { jack.global-buffer-size = true # quantum set globally using metadata } } } { matches = [ { application.process.binary = "qsynth" } ] actions = { update-props = { node.always-process = false # makes qsynth idle node.pause-on-idle = false # makes audio fade out when idle node.passive = out # makes the sink and qsynth suspend } } } { matches = [ { client.name = "Mixxx" } ] actions = { update-props = { jack.merge-monitor = false } } } ] pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/daemon/meson.build000066400000000000000000000115351511204443500246770ustar00rootroot00000000000000pipewire_daemon_sources = [ 'pipewire.c', ] conf_config = configuration_data() conf_config.set('VERSION', '"@0@"'.format(pipewire_version)) conf_config.set('PIPEWIRE_CONFIG_DIR', pipewire_configdir) conf_config.set('session_manager_path', pipewire_bindir / 'pipewire-media-session') conf_config.set('session_manager_args', '') conf_config.set('pipewire_path', pipewire_bindir / 'pipewire') conf_config.set('pipewire_pulse_path', pipewire_bindir / 'pipewire-pulse') conf_config.set('sm_comment', '#') conf_config.set('pulse_comment', '#') conf_config.set('rtprio_server', get_option('rtprio-server')) conf_config.set('rtprio_client', get_option('rtprio-client')) conf_config_uninstalled = conf_config conf_config_uninstalled.set('pipewire_path', meson.project_build_root() / 'src' / 'daemon' / 'pipewire') conf_config_uninstalled.set('pipewire_pulse_path', meson.project_build_root() / 'src' / 'daemon' / 'pipewire-pulse') conf_config_uninstalled.set('pulse_comment', '') build_ms = 'media-session' in get_option('session-managers') build_wp = 'wireplumber' in get_option('session-managers') default_sm = get_option('session-managers').get(0, '') build_vk = get_option('vulkan').enabled() summary({'Build media-session': build_ms, 'Build wireplumber': build_wp, 'Default session-manager': default_sm}, section: 'Session managers', bool_yn: true) if build_wp wp_proj = subproject('wireplumber', required : true) endif if build_ms ms_proj = subproject('media-session', required : true) endif if default_sm == '' summary({'No session manager': 'pw-uninstalled.sh will not work out of the box!'}) elif default_sm == 'media-session' ms_bindir = ms_proj.get_variable('media_session_bin_dir', pipewire_bindir) conf_config.set('session_manager_path', ms_bindir / 'pipewire-media-session') ms_uninstalled = ms_proj.get_variable('media_session_uninstalled') conf_config_uninstalled.set('session_manager_path', ms_uninstalled.full_path()) conf_config_uninstalled.set('session_manager_args', 'pipewire-media-session') conf_config_uninstalled.set('sm_comment', '') elif default_sm == 'wireplumber' wp_bindir = wp_proj.get_variable('wireplumber_bin_dir', pipewire_bindir) conf_config.set('session_manager_path', wp_bindir / 'wireplumber') wp_uninstalled = wp_proj.get_variable('wireplumber_uninstalled') conf_config_uninstalled.set('session_manager_path', wp_uninstalled.full_path()) conf_config_uninstalled.set('session_manager_args', 'wireplumber') conf_config_uninstalled.set('sm_comment', '') else conf_config_uninstalled.set('session_manager_path', default_sm) conf_config_uninstalled.set('sm_comment', '') endif conf_files = [ 'pipewire.conf', 'client.conf', 'filter-chain.conf', 'jack.conf', 'minimal.conf', 'pipewire-pulse.conf', 'pipewire-avb.conf', 'pipewire-aes67.conf', ] if build_vk conf_files += 'pipewire-vulkan.conf' endif foreach c : conf_files res = configure_file(input : '@0@.in'.format(c), output : c, configuration : conf_config, install_dir : pipewire_confdatadir) test(f'validate-json-@c@', spa_json_dump_exe, args : res) endforeach res = configure_file(input : 'pipewire.conf.in', output : 'pipewire-uninstalled.conf', configuration : conf_config_uninstalled) test('validate-json-pipewire-uninstalled.conf', spa_json_dump_exe, args : res) conf_avail_folders = [ 'pipewire.conf.avail', 'client.conf.avail', 'pipewire-pulse.conf.avail', ] foreach c : conf_avail_folders subdir(c) endforeach pipewire_exec = executable('pipewire', pipewire_daemon_sources, install: true, include_directories : [ configinc ], dependencies : [ spa_dep, pipewire_dep, ], ) ln = find_program('ln') pipewire_aliases = [ 'pipewire-pulse', 'pipewire-avb', 'pipewire-aes67', ] if build_vk pipewire_aliases += 'pipewire-vulkan' endif foreach alias : pipewire_aliases custom_target( alias, build_by_default: true, install: false, command: [ln, '-sf', meson.project_build_root() + '/@INPUT@', '@OUTPUT@'], input: pipewire_exec, output: alias, ) install_symlink( alias, pointing_to: pipewire_exec.name(), install_dir: pipewire_bindir, ) endforeach custom_target('pipewire-uninstalled', build_by_default: true, install: false, input: pipewire_exec, output: 'pipewire-uninstalled', command: [ln, '-fs', meson.project_build_root() + '/@INPUT@', '@OUTPUT@'], ) #desktop_file = i18n.merge_file( # input : 'pipewire.desktop.in', # output : 'pipewire.desktop', # po_dir : po_dir, # type : 'desktop', # install : true, # install_dir : pipewire_sysconfdir / 'xdg' / 'autostart' #) # #desktop_utils = find_program('desktop-file-validate', required: false) #if desktop_utils.found() # test('Validate desktop file', desktop_utils, # args: [ desktop_file ], # ) #endif subdir('filter-chain') subdir('systemd') pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/daemon/minimal.conf.in000066400000000000000000000435411511204443500254410ustar00rootroot00000000000000# Simple daemon config file for PipeWire version @VERSION@ # # # Copy and edit this file in @PIPEWIRE_CONFIG_DIR@ for system-wide changes # or in ~/.config/pipewire for local changes. # # It is also possible to place a file with an updated section in # @PIPEWIRE_CONFIG_DIR@/minimal.conf.d/ for system-wide changes or in # ~/.config/pipewire/minimal.conf.d/ for local changes. # context.properties = { ## Configure properties in the system. #library.name.system = support/libspa-support #context.data-loop.library.name.system = support/libspa-support #support.dbus = true #link.max-buffers = 64 link.max-buffers = 16 # version < 3 clients can't handle more #mem.warn-mlock = false #mem.allow-mlock = true #mem.mlock-all = false #clock.power-of-two-quantum = true #log.level = 2 #cpu.zero.denormals = false core.daemon = true # listening for socket connections core.name = pipewire-0 # core name and socket name ## Properties for the DSP configuration. #default.clock.rate = 48000 #default.clock.allowed-rates = [ 48000 ] #default.clock.quantum = 1024 #default.clock.min-quantum = 32 #default.clock.max-quantum = 2048 #default.clock.quantum-limit = 8192 #default.clock.quantum-floor = 4 #default.video.width = 640 #default.video.height = 480 #default.video.rate.num = 25 #default.video.rate.denom = 1 # settings.check-quantum = true settings.check-rate = true # This config can use udev or hardcoded ALSA devices. Make sure to # change the alsa device below when disabling udev minimal.use-udev = true # Load the pulseaudio emulation daemon minimal.use-pulse = true # Load the jack-tunnel as a backend minimal.use-jack-tunnel = true } context.properties.rules = [ { matches = [ { cpu.vm.name = !null } ] actions = { update-props = { # These overrides are only applied when running in a vm. default.clock.min-quantum = 1024 } } } ] context.spa-libs = { # = # # Used to find spa factory names. It maps an spa factory name # regular expression to a library name that should contain # that factory. # audio.convert.* = audioconvert/libspa-audioconvert audio.adapt = audioconvert/libspa-audioconvert api.alsa.* = alsa/libspa-alsa support.* = support/libspa-support } context.modules = [ #{ name = # ( args = { = ... } ) # ( flags = [ ( ifexists ) ( nofail ) ] ) # ( condition = [ { = ... } ... ] ) #} # # Loads a module with the given parameters. # If ifexists is given, the module is ignored when it is not found. # If nofail is given, module initialization failures are ignored. # # Uses realtime scheduling to boost the audio thread priorities. This uses # RTKit if the user doesn't have permission to use regular realtime # scheduling. { name = libpipewire-module-rt args = { nice.level = -11 rt.prio = @rtprio_server@ #rt.time.soft = -1 #rt.time.hard = -1 } flags = [ ifexists nofail ] } # The native communication protocol. { name = libpipewire-module-protocol-native } # The profile module. Allows application to access profiler # and performance data. It provides an interface that is used # by pw-top and pw-profiler. { name = libpipewire-module-profiler } # Allows applications to create metadata objects. It creates # a factory for Metadata objects. { name = libpipewire-module-metadata } # Creates a factory for making nodes that run in the # context of the PipeWire server. { name = libpipewire-module-spa-node-factory } { name = libpipewire-module-spa-device-factory } # Allows creating nodes that run in the context of the # client. Is used by all clients that want to provide # data to PipeWire. { name = libpipewire-module-client-node } # The access module can perform access checks and block # new clients. { name = libpipewire-module-access args = { # access.allowed to list an array of paths of allowed # apps. #access.allowed = [ # @session_manager_path@ #] # An array of rejected paths. #access.rejected = [ ] # An array of paths with restricted access. #access.restricted = [ ] # Anything not in the above lists gets assigned the # access.force permission. #access.force = flatpak } } # Makes a factory for wrapping nodes in an adapter with a # converter and resampler. { name = libpipewire-module-adapter } # Makes a factory for creating links between ports. { name = libpipewire-module-link-factory } { name = libpipewire-module-protocol-pulse condition = [ { minimal.use-pulse = true } ] } { name = libpipewire-module-jack-tunnel args = { #jack.library = libjack.so.0 #jack.server = null #jack.client-name = PipeWire #jack.connect = true #jack.connect-audio = [ ] #jack.connect-midi = [ ] #tunnel.mode = duplex #midi.ports = 1 #audio.channels = 2 #audio.position = [ FL FR ] source.props = { # extra sink properties #jack.connect-audio = [ "system:capture_1" ] #jack.connect-midi = [ "system:midi_capture_2" ] # jack-tunnel needs a PortConfig from the # session manager so do this here. node.param.PortConfig = { direction = Output mode = dsp } } sink.props = { # extra sink properties #jack.connect-audio = [ "system:playback_1" ] #jack.connect-midi = [ "system:midi_playback_2" ] node.param.PortConfig = { direction = Input mode = dsp } } } condition = [ { minimal.use-jack-tunnel = true } ] } ] pulse.properties = { # the addresses this server listens on server.address = [ "unix:native" ] } stream.properties = { adapter.auto-port-config = { mode = dsp } } context.objects = [ #{ factory = # ( args = { = ... } ) # ( flags = [ ( nofail ) ] ) # ( condition = [ { = ... } ... ] ) #} # # Creates an object from a PipeWire factory with the given parameters. # If nofail is given, errors are ignored (and no object is created). # #{ factory = spa-node-factory args = { factory.name = videotestsrc node.name = videotestsrc node.description = videotestsrc node.param.Props = { patternType = 1 } } } #{ factory = spa-device-factory args = { factory.name = api.jack.device foo=bar } flags = [ nofail ] } #{ factory = spa-device-factory args = { factory.name = api.alsa.enum.udev } } #{ factory = spa-node-factory args = { factory.name = api.alsa.seq.bridge node.name = Internal-MIDI-Bridge } } #{ factory = adapter args = { factory.name = audiotestsrc node.name = my-test node.description = audiotestsrc node.param.Props = { live = false } } } #{ factory = spa-node-factory args = { factory.name = api.vulkan.compute.source node.name = my-compute-source } } # Make a default metadata store { factory = metadata args = { metadata.name = default # metadata.values = [ # { key = default.audio.sink value = { name = somesink } } # { key = default.audio.source value = { name = somesource } } # ] } } # A default dummy driver. This handles nodes marked with the "node.always-process" # property when no other driver is currently active. JACK clients need this. { factory = spa-node-factory args = { factory.name = support.node.driver node.name = Dummy-Driver node.group = pipewire.dummy priority.driver = 20000 } } { factory = spa-node-factory args = { factory.name = support.node.driver node.name = Freewheel-Driver priority.driver = 19000 node.group = pipewire.freewheel node.freewheel = true #freewheel.wait = 10 } } # This creates a ALSA udev device that will enumerate all # ALSA devices. Because it is using ACP and has the auto-profile # property set, this will enable a profile and create associated # nodes, which will be automatically configured to their best # configuration with the auto-port-config settings. # Extra node and device params can be given with node.param and # device.param prefixes. { factory = spa-device-factory args = { factory.name = api.alsa.enum.udev alsa.use-acp = true device.object.properties = { api.acp.auto-profile = true api.acp.auto-port = true device.object.properties = { node.adapter = audio.adapt resample.disable = false adapter.auto-port-config = { mode = dsp monitor = false control = false position = preserve # unknown, aux } #node.param.Props = { # channelVolumes = [ 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.6 ] #} } #device.param.Profile = { # #idx = 0 # name = pro-audio #} } } condition = [ { minimal.use-udev = true } ] } # This creates a single PCM source device for the given # alsa device path hw:0. You can change source to sink # to make a sink in the same way. { factory = adapter args = { factory.name = api.alsa.pcm.source node.name = "system" node.description = "system" media.class = "Audio/Source" api.alsa.path = "hw:4" #api.alsa.period-size = 0 #api.alsa.period-num = 0 #api.alsa.headroom = 0 #api.alsa.start-delay = 0 #api.alsa.disable-mmap = false #api.alsa.disable-batch = false #api.alsa.use-chmap = false #api.alsa.multirate = true #latency.internal.rate = 0 #latency.internal.ns = 0 #clock.name = api.alsa.0 node.suspend-on-idle = true #audio.format = "S32" #audio.rate = 48000 #audio.allowed-rates = [ ] #audio.channels = 4 #audio.position = [ FL FR RL RR ] #resample.quality = 4 resample.disable = true #monitor.channel-volumes = false #channelmix.normalize = false #channelmix.mix-lfe = true #channelmix.upmix = true #channelmix.upmix-method = psd # none, simple #channelmix.lfe-cutoff = 150 #channelmix.fc-cutoff = 12000 #channelmix.rear-delay = 12.0 #channelmix.stereo-widen = 0.0 #channelmix.hilbert-taps = 0 #channelmix.disable = false #dither.noise = 0 #node.param.Props = { # params = [ # audio.channels 6 # ] #} adapter.auto-port-config = { mode = dsp monitor = false control = false position = unknown # aux, preserve } #node.param.PortConfig = { # direction = Output # mode = dsp # format = { # mediaType = audio # mediaSubtype = raw # format = F32 # rate = 48000 # channels = 4 # position = [ FL FR RL RR ] # } #} #node.param.Props = { # channelVolumes = [ 0.5 0.4 0.3 0.5 ] #} } condition = [ { minimal.use-udev = false } ] } { factory = adapter args = { factory.name = api.alsa.pcm.sink node.name = "system" node.description = "system" media.class = "Audio/Sink" api.alsa.path = "hw:4" #api.alsa.period-size = 0 #api.alsa.period-num = 0 #api.alsa.headroom = 0 #api.alsa.start-delay = 0 #api.alsa.disable-mmap = false #api.alsa.disable-batch = false #api.alsa.use-chmap = false #api.alsa.multirate = true #latency.internal.rate = 0 #latency.internal.ns = 0 #clock.name = api.alsa.0 node.suspend-on-idle = true #audio.format = "S32" #audio.rate = 48000 #audio.allowed-rates = [ ] #audio.channels = 2 #audio.position = "FL,FR" #resample.quality = 4 resample.disable = true #channelmix.normalize = false #channelmix.mix-lfe = true #channelmix.upmix = true #channelmix.upmix-method = psd # none, simple #channelmix.lfe-cutoff = 150 #channelmix.fc-cutoff = 12000 #channelmix.rear-delay = 12.0 #channelmix.stereo-widen = 0.0 #channelmix.hilbert-taps = 0 #channelmix.disable = false #dither.noise = 0 #node.param.Props = { # params = [ # audio.format S16 # ] #} adapter.auto-port-config = { mode = dsp monitor = false control = false position = unknown # aux, preserve } #node.param.PortConfig = { # direction = Input # mode = dsp # monitor = true # format = { # mediaType = audio # mediaSubtype = raw # format = F32 # rate = 48000 # channels = 4 # } #} #node.param.Props = { # channelVolumes = [ 0.5 0.4 0.3 0.5 ] #} } condition = [ { minimal.use-udev = false } ] } # This creates a new Source node. It will have input ports # that you can link, to provide audio for this source. #{ factory = adapter # args = { # factory.name = support.null-audio-sink # node.name = "my-mic" # node.description = "Microphone" # media.class = "Audio/Source/Virtual" # audio.position = "FL,FR" # monitor.passthrough = true # adapter.auto-port-config = { # mode = dsp # monitor = true # position = preserve # unknown, aux, preserve # } # } #} # This creates a new link between the source and the virtual # source ports. #{ factory = link-factory # args = { # link.output.node = system # link.output.port = capture_1 # link.input.node = my-mic # link.input.port = input_FL # } #} #{ factory = link-factory # args = { # link.output.node = system # link.output.port = capture_2 # link.input.node = my-mic # link.input.port = input_FR # } #} ] context.exec = [ #{ path = # ( args = "" | [ ... ] ) # ( condition = [ { = ... } ... ] ) #} # # Execute the given program with arguments. # # You can optionally start the pulseaudio-server here as well # but it is better to start it as a systemd service. # It can be interesting to start another daemon here that listens # on another address with the -a option (eg. -a tcp:4713). # #@pulse_comment@{ path = "@pipewire_path@" args = "-c pipewire-pulse.conf" } ] node.rules = [ { matches = [ { # all keys must match the value. ! negates. ~ starts regex. #alsa.card_name = "ICUSBAUDIO7D" #api.alsa.pcm.stream = "playback" } ] actions = { update-props = { #node.name = "alsa_playback.ICUSBAUDIO7D" } } } ] device.rules = [ { matches = [ { #alsa.card_name = "ICUSBAUDIO7D" } ] actions = { update-props = { #device.name = "alsa_card.ICUSBAUDIO7D" #api.acp.auto-profile = false #api.acp.auto-port = false #device.param.Profile = { # #idx = 0 # name = off #} } } } ] pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/daemon/pipewire-aes67.conf.in000066400000000000000000000143141511204443500265560ustar00rootroot00000000000000# AES67 config file for PipeWire version @VERSION@ # # # Copy and edit this file in @PIPEWIRE_CONFIG_DIR@ for system-wide changes # or in ~/.config/pipewire for local changes. # # It is also possible to place a file with an updated section in # @PIPEWIRE_CONFIG_DIR@/pipewire-aes67.conf.d/ for system-wide changes or in # ~/.config/pipewire/pipewire-aes67.conf.d/ for local changes. # context.properties = { ## Configure properties in the system. #mem.warn-mlock = false #mem.allow-mlock = true #mem.mlock-all = false #log.level = 2 #default.clock.quantum-limit = 8192 } context.spa-libs = { support.* = support/libspa-support } context.objects = [ # An example clock reading from /dev/ptp0. You can also specify the network interface name, # pipewire will query the interface for the current active PHC index. Another option is to # sync the ptp clock to CLOCK_TAI and then set clock.id = tai, keep in mind that tai may # also be synced by a NTP client. # The precedence is: device, interface, id { factory = spa-node-factory args = { factory.name = support.node.driver node.name = PTP0-Driver node.group = pipewire.ptp0 # This driver should only be used for network nodes marked with group priority.driver = 100000 clock.name = "clock.system.ptp0" ### Please select the PTP hardware clock here # Interface name is the preferred method of specifying the PHC clock.interface = "eth0" #clock.device = "/dev/ptp0" #clock.id = tai # Lower this in case of periodic out-of-sync resync.ms = 1.5 object.export = true } } ] context.modules = [ { name = libpipewire-module-rt args = { nice.level = -11 #rt.prio = @rtprio_client@ #rt.time.soft = -1 #rt.time.hard = -1 } flags = [ ifexists nofail ] } { name = libpipewire-module-protocol-native } { name = libpipewire-module-client-node } { name = libpipewire-module-spa-node-factory } { name = libpipewire-module-adapter } { name = libpipewire-module-rtp-sap args = { ### Please select the interface here local.ifname = eth0 sap.ip = 239.255.255.255 sap.port = 9875 net.ttl = 32 net.loop = false # If you use another PTPv2 daemon supporting management # messages over a UNIX socket, specify its path here ptp.management-socket = "/var/run/ptp4lro" stream.rules = [ { matches = [ { rtp.session = "~.*" } ] actions = { create-stream = { node.virtual = false media.class = "Audio/Source" device.api = aes67 # You can adjust the latency buffering here. Use integer values only sess.latency.msec = 3 node.group = pipewire.ptp0 } } }, { matches = [ { sess.sap.announce = true } ] actions = { announce-stream = {} } } ] } }, { name = libpipewire-module-rtp-sink args = { ### Please select the interface here local.ifname = eth0 ### If you want to create multiple output streams, please copy the whole ### module-rtp-sink block, but change this multicast IP to another unused ### one keeping 239.69.x.x range unless you know you need another one destination.ip = 239.69.150.243 destination.port = 5004 net.mtu = 1280 net.ttl = 32 net.loop = false # These should typically be equal # You can customize packet length, but 1 ms should work for every device # Consult receiver documentation to ensure it supports the value you set sess.min-ptime = 1 sess.max-ptime = 1 ### Please change this, especially if you create multiple sinks sess.name = "PipeWire RTP stream" sess.media = "audio" # This property is used if you aren't using ptp4l 4 sess.ts-refclk = "ptp=traceable" sess.ts-offset = 0 # Directly synchronize output against the PTP-synced driver using the RTP timestamps # This can be set to true if the reference clocks are the same; it then makes the # synchronization more robust against transport delay variations and can help lower # latency sess.ts-direct = false # You can adjust the latency buffering here. Use integer values only sess.latency.msec = 3 audio.format = "S24BE" audio.rate = 48000 audio.channels = 2 # These channel names will be visible both to applications and AES67 receivers node.channel-names = ["CH1", "CH2"] # Uncomment this and comment node.group in send/recv stream.props to allow # separate drivers for the RTP sink and PTP sending (i.e. force rate matching on # the AES67 node rather than other nodes) #aes67.driver-group = "pipewire.ptp0" stream.props = { ### Please change the sink name, this is necessary when you create multiple sinks node.name = "rtp-sink" media.class = "Audio/Sink" node.virtual = false device.api = aes67 sess.sap.announce = true node.always-process = true node.group = pipewire.ptp0 rtp.ntp = 0 rtp.fetch-ts-refclk = true } } }, ] pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/daemon/pipewire-avb.conf.in000066400000000000000000000042221511204443500263760ustar00rootroot00000000000000# PulseAudio config file for PipeWire version @VERSION@ # # # Copy and edit this file in @PIPEWIRE_CONFIG_DIR@ for system-wide changes # or in ~/.config/pipewire for local changes. # # It is also possible to place a file with an updated section in # @PIPEWIRE_CONFIG_DIR@/pipewire-pulse.conf.d/ for system-wide changes or in # ~/.config/pipewire/pipewire-pulse.conf.d/ for local changes. # context.properties = { ## Configure properties in the system. #mem.warn-mlock = false #mem.allow-mlock = true #mem.mlock-all = false #log.level = 2 #default.clock.quantum-limit = 8192 } context.spa-libs = { audio.convert.* = audioconvert/libspa-audioconvert support.* = support/libspa-support } context.modules = [ { name = libpipewire-module-rt args = { nice.level = -11 #rt.prio = @rtprio_client@ #rt.time.soft = -1 #rt.time.hard = -1 } flags = [ ifexists nofail ] } { name = libpipewire-module-protocol-native } { name = libpipewire-module-client-node } { name = libpipewire-module-adapter } { name = libpipewire-module-avb args = { # contents of avb.properties can also be placed here # to have config per server. } } ] # Extra modules can be loaded here. Setup in default.pa can be moved here context.exec = [ #{ path = "pactl" args = "load-module module-always-sink" } ] stream.properties = { #node.latency = 1024/48000 #node.autoconnect = true #resample.quality = 4 #channelmix.normalize = false #channelmix.mix-lfe = true #channelmix.upmix = true #channelmix.lfe-cutoff = 120 #channelmix.fc-cutoff = 6000 #channelmix.rear-delay = 12.0 #channelmix.stereo-widen = 0.1 #channelmix.hilbert-taps = 0 } avb.properties = { # the addresses this server listens on #ifname = "eth0.2" ifname = "enp3s0" } avb.properties.rules = [ { matches = [ { cpu.vm.name = !null } ] actions = { update-props = { # These overrides are only applied when running in a vm. } } } ] pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/daemon/pipewire-pulse.conf.avail/000077500000000000000000000000001511204443500275215ustar00rootroot0000000000000020-upmix.conf.in000066400000000000000000000003411511204443500322750ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/daemon/pipewire-pulse.conf.avail# Enables upmixing stream.properties = { channelmix.upmix = true channelmix.upmix-method = psd # none, simple channelmix.lfe-cutoff = 150 channelmix.fc-cutoff = 12000 channelmix.rear-delay = 12.0 } meson.build000066400000000000000000000005321511204443500316040ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/daemon/pipewire-pulse.conf.availconf_files = [ '20-upmix.conf', ] foreach c : conf_files res = configure_file(input : '@0@.in'.format(c), output : c, configuration : conf_config, install_dir : pipewire_confdatadir / 'pipewire-pulse.conf.avail') test(f'validate-json-pulse-@c@', spa_json_dump_exe, args : res) endforeach pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/daemon/pipewire-pulse.conf.in000066400000000000000000000154211511204443500267610ustar00rootroot00000000000000# PulseAudio config file for PipeWire version @VERSION@ # # # Copy and edit this file in @PIPEWIRE_CONFIG_DIR@ for system-wide changes # or in ~/.config/pipewire for local changes. # # It is also possible to place a file with an updated section in # @PIPEWIRE_CONFIG_DIR@/pipewire-pulse.conf.d/ for system-wide changes or in # ~/.config/pipewire/pipewire-pulse.conf.d/ for local changes. # context.properties = { ## Configure properties in the system. #mem.warn-mlock = false #mem.allow-mlock = true #mem.mlock-all = false #log.level = 2 #rlimit.nofile = -1 #default.clock.quantum-limit = 8192 } context.spa-libs = { audio.convert.* = audioconvert/libspa-audioconvert support.* = support/libspa-support } context.modules = [ { name = libpipewire-module-rt args = { nice.level = -11 #rt.prio = @rtprio_client@ #rt.time.soft = -1 #rt.time.hard = -1 #uclamp.min = 0 #uclamp.max = 1024 } flags = [ ifexists nofail ] } { name = libpipewire-module-protocol-native } { name = libpipewire-module-client-node } { name = libpipewire-module-adapter } { name = libpipewire-module-metadata } { name = libpipewire-module-protocol-pulse args = { # contents of pulse.properties can also be placed here # to have config per server. } } ] # Extra scripts can be started here. Setup in default.pa can be moved in # a script or in pulse.cmd below context.exec = [ #{ path = "pactl" args = "load-module module-always-sink" } #{ path = "pactl" args = "upload-sample my-sample.wav my-sample" } #{ path = "/usr/bin/sh" args = "~/.config/pipewire/default.pw" } ] # Extra commands can be executed here. # load-module : loads a module with args and flags # args = " " # ( flags = [ nofail ] ) # ( condition = [ { = , ... } ... ] ) # conditions will check the pulse.properties key/values. pulse.cmd = [ { cmd = "load-module" args = "module-always-sink" flags = [ ] condition = [ { pulse.cmd.always-sink = !false } ] } { cmd = "load-module" args = "module-device-manager" flags = [ ] condition = [ { pulse.cmd.device-manager = !false } ] } { cmd = "load-module" args = "module-device-restore" flags = [ ] condition = [ { pulse.cmd.device-restore = !false } ] } { cmd = "load-module" args = "module-stream-restore" flags = [ ] condition = [ { pulse.cmd.stream-restore = !false } ] } #{ cmd = "load-module" args = "module-switch-on-connect" } #{ cmd = "load-module" args = "module-gsettings" flags = [ nofail ] } ] stream.properties = { #node.latency = 1024/48000 #node.autoconnect = true #resample.quality = 4 #channelmix.normalize = false #channelmix.mix-lfe = true #channelmix.upmix = true #channelmix.upmix-method = psd # none, simple #channelmix.lfe-cutoff = 150 #channelmix.fc-cutoff = 12000 #channelmix.rear-delay = 12.0 #channelmix.stereo-widen = 0.0 #channelmix.hilbert-taps = 0 #dither.noise = 0 } pulse.properties = { # the addresses this server listens on server.address = [ "unix:native" #"unix:/tmp/something" # absolute paths may be used #"tcp:4713" # IPv4 and IPv6 on all addresses #"tcp:[::]:9999" # IPv6 on all addresses #"tcp:127.0.0.1:8888" # IPv4 on a single address # #{ address = "tcp:4713" # address # max-clients = 64 # maximum number of clients # listen-backlog = 32 # backlog in the server listen queue # client.access = "restricted" # permissions for clients #} ] #server.dbus-name = "org.pulseaudio.Server" #pulse.allow-module-loading = true #pulse.min.req = 256/48000 # 5.3ms #pulse.default.req = 960/48000 # 20 milliseconds #pulse.min.frag = 256/48000 # 5.3ms #pulse.default.frag = 96000/48000 # 2 seconds #pulse.default.tlength = 96000/48000 # 2 seconds #pulse.min.quantum = 256/48000 # 5.3ms #pulse.idle.timeout = 0 # don't pause after underruns #pulse.default.format = F32 #pulse.default.position = [ FL FR ] } pulse.properties.rules = [ { matches = [ { cpu.vm.name = !null } ] actions = { update-props = { # These overrides are only applied when running in a vm. pulse.min.quantum = 1024/48000 # 22ms } } } ] # client/stream specific properties pulse.rules = [ { matches = [ { # all keys must match the value. ! negates. ~ starts regex. #client.name = "Firefox" #application.process.binary = "teams" #application.name = "~speech-dispatcher.*" } ] actions = { update-props = { #node.latency = 512/48000 } # Possible quirks:" # force-s16-info forces sink and source info as S16 format # remove-capture-dont-move removes the capture DONT_MOVE flag # block-source-volume blocks updates to source volume # block-sink-volume blocks updates to sink volume #quirks = [ ] } } { # skype does not want to use devices that don't have an S16 sample format. matches = [ { application.process.binary = "teams" } { application.process.binary = "teams-insiders" } { application.process.binary = "teams-for-linux" } { application.process.binary = "skypeforlinux" } ] actions = { quirks = [ force-s16-info ] } } { # firefox marks the capture streams as don't move and then they # can't be moved with pavucontrol or other tools. matches = [ { application.process.binary = "firefox" } ] actions = { quirks = [ remove-capture-dont-move ] } } { # speech dispatcher asks for too small latency and then underruns. matches = [ { application.name = "~speech-dispatcher.*" } ] actions = { update-props = { pulse.min.req = 512/48000 # 10.6ms pulse.min.quantum = 512/48000 # 10.6ms pulse.idle.timeout = 5 # pause after 5 seconds of underrun } } } #{ # matches = [ { application.process.binary = "Discord" } ] # actions = { quirks = [ block-source-volume ] } #} ] pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/daemon/pipewire-vulkan.conf.in000066400000000000000000000103131511204443500271240ustar00rootroot00000000000000# Config file for PipeWire version "0.3.77" # # # This config file should start the vulkan-compute-source/filter as proxied # clients # context.properties = { ## Configure properties in the system. #library.name.system = support/libspa-support #context.data-loop.library.name.system = support/libspa-support #support.dbus = true #mem.warn-mlock = false #mem.allow-mlock = true #mem.mlock-all = false #clock.power-of-two-quantum = true #log.level = 4 #cpu.zero.denormals = false #default.clock.quantum-limit = 8192 } context.spa-libs = { # = # # Used to find spa factory names. It maps an spa factory name # regular expression to a library name that should contain # that factory. # api.vulkan.* = vulkan/libspa-vulkan support.* = support/libspa-support video.convert.* = videoconvert/libspa-videoconvert } context.modules = [ #{ name = # ( args = { = ... } ) # ( flags = [ ( ifexists ) ( nofail ) ] ) # ( condition = [ { = ... } ... ] ) #} # # Loads a module with the given parameters. # If ifexists is given, the module is ignored when it is not found. # If nofail is given, module initialization failures are ignored. # If condition is given, the module is loaded only when the context # properties all match the match rules. # # Uses realtime scheduling to boost the audio thread priorities. This uses # RTKit if the user doesn't have permission to use regular realtime # scheduling. { name = libpipewire-module-rt args = { nice.level = -11 #rt.prio = @rtprio_client@ #rt.time.soft = -1 #rt.time.hard = -1 } flags = [ ifexists nofail ] } # The native communication protocol. { name = libpipewire-module-protocol-native } # Creates a factory for making nodes that run in the # context of the PipeWire server. { name = libpipewire-module-spa-node-factory } # Allows creating nodes that run in the context of the # client. Is used by all clients that want to provide # data to PipeWire. { name = libpipewire-module-client-node } # Makes a factory for wrapping nodes in an adapter with a # converter and resampler. { name = libpipewire-module-adapter } ] context.objects = [ #{ factory = # ( args = { = ... } ) # ( flags = [ ( nofail ) ] ) # ( condition = [ { = ... } ... ] ) #} # # Creates an object from a PipeWire factory with the given parameters. # If nofail is given, errors are ignored (and no object is created). # If condition is given, the object is created only when the context properties # all match the match rules. # #{ factory = spa-node-factory args = { factory.name = videotestsrc node.name = videotestsrc node.description = videotestsrc node.param.Props = { patternType = 1 } } } #{ factory = spa-device-factory args = { factory.name = api.jack.device foo=bar } flags = [ nofail ] } #{ factory = spa-device-factory args = { factory.name = api.alsa.enum.udev } } #{ factory = spa-node-factory args = { factory.name = api.alsa.seq.bridge node.name = Internal-MIDI-Bridge } } #{ factory = adapter args = { factory.name = audiotestsrc node.name = my-test node.description = audiotestsrc node.param.Props = { live = false } } } { factory = spa-node-factory args = { factory.name = api.vulkan.compute.source node.name = vulkan-compute-source object.export = true } } { factory = spa-node-factory args = { factory.name = api.vulkan.compute.filter node.name = vulkan-compute-filter object.export = true } } { factory = spa-node-factory args = { factory.name = api.vulkan.blit.filter node.name = vulkan-blit-filter object.export = true } } { factory = spa-node-factory args = { factory.name = api.vulkan.blit.dsp-filter node.name = vulkan-blit-dsp-filter object.export = true } } ] pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/daemon/pipewire.c000066400000000000000000000066211511204443500245250ustar00rootroot00000000000000/* PipeWire */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include "config.h" #include #include #include #include #include #include #include #include #include #include static void do_quit(void *data, int signal_number) { struct pw_main_loop *loop = data; pw_main_loop_quit(loop); } static void show_help(const char *name, const char *config_name) { fprintf(stdout, _("%s [options]\n" " -h, --help Show this help\n" " -v, --verbose Increase verbosity by one level\n" " --version Show version\n" " -c, --config Load config (Default %s)\n" " -P --properties Set context properties\n"), name, config_name); } int main(int argc, char *argv[]) { struct pw_context *context = NULL; struct pw_main_loop *loop = NULL; struct pw_properties *properties = NULL; static const struct option long_options[] = { { "help", no_argument, NULL, 'h' }, { "version", no_argument, NULL, 'V' }, { "config", required_argument, NULL, 'c' }, { "verbose", no_argument, NULL, 'v' }, { "properties", required_argument, NULL, 'P' }, { NULL, 0, NULL, 0} }; int c, res = 0; char path[PATH_MAX]; const char *config_name; enum spa_log_level level; struct spa_error_location loc; if (setenv("PIPEWIRE_INTERNAL", "1", 1) < 0) fprintf(stderr, "can't set PIPEWIRE_INTERNAL env: %m"); snprintf(path, sizeof(path), "%s.conf", argv[0]); config_name = basename(path); setlocale(LC_ALL, ""); pw_init(&argc, &argv); level = pw_log_level; properties = pw_properties_new( PW_KEY_CONFIG_NAME, config_name, NULL); while ((c = getopt_long(argc, argv, "hVc:vP:", long_options, NULL)) != -1) { switch (c) { case 'v': if (level < SPA_LOG_LEVEL_TRACE) pw_log_set_level(++level); break; case 'h': show_help(argv[0], config_name); return 0; case 'V': fprintf(stdout, "%s\n" "Compiled with libpipewire %s\n" "Linked with libpipewire %s\n", argv[0], pw_get_headers_version(), pw_get_library_version()); return 0; case 'c': config_name = optarg; pw_properties_set(properties, PW_KEY_CONFIG_NAME, config_name); break; case 'P': if (pw_properties_update_string_checked(properties, optarg, strlen(optarg), &loc) < 0) { spa_debug_file_error_location(stderr, &loc, "error: syntax error in --properties: %s", loc.reason); goto done; } break; default: res = -EINVAL; goto done; } } loop = pw_main_loop_new(&properties->dict); if (loop == NULL) { pw_log_error("failed to create main-loop: %m"); res = -errno; goto done; } pw_loop_add_signal(pw_main_loop_get_loop(loop), SIGINT, do_quit, loop); pw_loop_add_signal(pw_main_loop_get_loop(loop), SIGTERM, do_quit, loop); context = pw_context_new(pw_main_loop_get_loop(loop), spa_steal_ptr(properties), 0); if (context == NULL) { pw_log_error("failed to create context: %m"); res = -errno; goto done; } pw_log_info("start main loop"); pw_main_loop_run(loop); pw_log_info("leave main loop"); done: pw_properties_free(properties); if (context) pw_context_destroy(context); if (loop) pw_main_loop_destroy(loop); pw_deinit(); return res; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/daemon/pipewire.conf.avail/000077500000000000000000000000001511204443500263735ustar00rootroot0000000000000010-rates.conf.in000066400000000000000000000001601511204443500311210ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/daemon/pipewire.conf.avail# Adds more common rates context.properties = { default.clock.allowed-rates = [ 44100 48000 88200 96000 ] } 20-upmix.conf.in000066400000000000000000000003411511204443500311470ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/daemon/pipewire.conf.avail# Enables upmixing stream.properties = { channelmix.upmix = true channelmix.upmix-method = psd # none, simple channelmix.lfe-cutoff = 150 channelmix.fc-cutoff = 12000 channelmix.rear-delay = 12.0 } 50-raop.conf.in000066400000000000000000000002561511204443500307560ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/daemon/pipewire.conf.availcontext.modules = [ # Use mDNS to detect and load module-raop-sink { name = libpipewire-module-raop-discover condition = [ { module.raop = !false } ] } ] pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/daemon/pipewire.conf.avail/meson.build000066400000000000000000000005721511204443500305410ustar00rootroot00000000000000conf_files = [ '10-rates.conf', '20-upmix.conf', '50-raop.conf', ] foreach c : conf_files res = configure_file(input : '@0@.in'.format(c), output : c, configuration : conf_config, install_dir : pipewire_confdatadir / 'pipewire.conf.avail') test(f'validate-json-pipewire-@c@', spa_json_dump_exe, args : res) endforeach pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/daemon/pipewire.conf.in000066400000000000000000000337761511204443500256500ustar00rootroot00000000000000# Daemon config file for PipeWire version @VERSION@ # # # Copy and edit this file in @PIPEWIRE_CONFIG_DIR@ for system-wide changes # or in ~/.config/pipewire for local changes. # # It is also possible to place a file with an updated section in # @PIPEWIRE_CONFIG_DIR@/pipewire.conf.d/ for system-wide changes or in # ~/.config/pipewire/pipewire.conf.d/ for local changes. # context.properties = { ## Configure properties in the system. #library.name.system = support/libspa-support #context.data-loop.library.name.system = support/libspa-support #support.dbus = true #link.max-buffers = 64 link.max-buffers = 16 # version < 3 clients can't handle more #mem.warn-mlock = false #mem.allow-mlock = true #mem.mlock-all = false #clock.power-of-two-quantum = true #log.level = 2 #cpu.zero.denormals = false #rlimit.nofile = -1 #loop.rt-prio = -1 # -1 = use module-rt prio, 0 disable rt #loop.class = data.rt #thread.affinity = [ 0 1 ] # optional array of CPUs #context.num-data-loops = 1 # -1 = num-cpus, 0 = no data loops # #context.data-loops = [ # { loop.rt-prio = -1 # loop.class = [ data.rt audio.rt ] # #library.name.system = support/libspa-support # thread.name = data-loop.0 # #thread.affinity = [ 0 1 ] # optional array of CPUs # } #] core.daemon = true # listening for socket connections core.name = pipewire-0 # core name and socket name ## Properties for the DSP configuration. #default.clock.rate = 48000 #default.clock.allowed-rates = [ 48000 ] #default.clock.quantum = 1024 #default.clock.min-quantum = 32 #default.clock.max-quantum = 2048 #default.clock.quantum-limit = 8192 #default.clock.quantum-floor = 4 #default.video.width = 640 #default.video.height = 480 #default.video.rate.num = 25 #default.video.rate.denom = 1 # #settings.check-quantum = false #settings.check-rate = false } context.properties.rules = [ { matches = [ { cpu.vm.name = !null } ] actions = { update-props = { # These overrides are only applied when running in a vm. default.clock.min-quantum = 1024 } } } ] context.spa-libs = { # = # # Used to find spa factory names. It maps an spa factory name # regular expression to a library name that should contain # that factory. # audio.convert.* = audioconvert/libspa-audioconvert avb.* = avb/libspa-avb api.alsa.* = alsa/libspa-alsa api.v4l2.* = v4l2/libspa-v4l2 api.libcamera.* = libcamera/libspa-libcamera api.bluez5.* = bluez5/libspa-bluez5 api.vulkan.* = vulkan/libspa-vulkan api.jack.* = jack/libspa-jack support.* = support/libspa-support video.convert.* = videoconvert/libspa-videoconvert #filter.graph = filter-graph/libspa-filter-graph #videotestsrc = videotestsrc/libspa-videotestsrc #audiotestsrc = audiotestsrc/libspa-audiotestsrc } context.modules = [ #{ name = # ( args = { = ... } ) # ( flags = [ ( ifexists ) ( nofail ) ] ) # ( condition = [ { = ... } ... ] ) #} # # Loads a module with the given parameters. # If ifexists is given, the module is ignored when it is not found. # If nofail is given, module initialization failures are ignored. # If condition is given, the module is loaded only when the context # properties all match the match rules. # # Uses realtime scheduling to boost the audio thread priorities. This uses # RTKit if the user doesn't have permission to use regular realtime # scheduling. You can also clamp utilisation values to improve scheduling # on embedded and heterogeneous systems, e.g. Arm big.LITTLE devices. # use module.rt.args = { ... } to override the arguments. { name = libpipewire-module-rt args = { nice.level = -11 rt.prio = @rtprio_server@ #rt.time.soft = -1 #rt.time.hard = -1 #uclamp.min = 0 #uclamp.max = 1024 } flags = [ ifexists nofail ] condition = [ { module.rt = !false } ] } # The native communication protocol. { name = libpipewire-module-protocol-native args = { # List of server Unix sockets, and optionally permissions #sockets = [ { name = "pipewire-0" }, { name = "pipewire-0-manager" } ] } } # The profile module. Allows application to access profiler # and performance data. It provides an interface that is used # by pw-top and pw-profiler. # use module.profiler.args = { ... } to override the arguments. { name = libpipewire-module-profiler args = { #profile.interval.ms = 0 } condition = [ { module.profiler = !false } ] } # Allows applications to create metadata objects. It creates # a factory for Metadata objects. { name = libpipewire-module-metadata condition = [ { module.metadata = !false } ] } # Creates a factory for making devices that run in the # context of the PipeWire server. { name = libpipewire-module-spa-device-factory condition = [ { module.spa-device-factory = !false } ] } # Creates a factory for making nodes that run in the # context of the PipeWire server. { name = libpipewire-module-spa-node-factory condition = [ { module.spa-node-factory = !false } ] } # Allows creating nodes that run in the context of the # client. Is used by all clients that want to provide # data to PipeWire. { name = libpipewire-module-client-node condition = [ { module.client-node = !false } ] } # Allows creating devices that run in the context of the # client. Is used by the session manager. { name = libpipewire-module-client-device condition = [ { module.client-device = !false } ] } # The portal module monitors the PID of the portal process # and tags connections with the same PID as portal # connections. { name = libpipewire-module-portal flags = [ ifexists nofail ] condition = [ { module.portal = !false } ] } # The access module can perform access checks and block # new clients. { name = libpipewire-module-access args = { # Socket-specific access permissions #access.socket = { pipewire-0 = "default", pipewire-0-manager = "unrestricted" } # Deprecated legacy mode (not socket-based), # for now enabled by default if access.socket is not specified #access.legacy = true } condition = [ { module.access = !false } ] } # Makes a factory for wrapping nodes in an adapter with a # converter and resampler. { name = libpipewire-module-adapter condition = [ { module.adapter = !false } ] } # Makes a factory for creating links between ports. # use module.link-factory.args = { ... } to override the arguments. { name = libpipewire-module-link-factory args = { #allow.link.passive = false } condition = [ { module.link-factory = !false } ] } # Provides factories to make session manager objects. { name = libpipewire-module-session-manager condition = [ { module.session-manager = !false } ] } # Use libcanberra to play X11 Bell { name = libpipewire-module-x11-bell args = { #sink.name = "\@DEFAULT_SINK\@" #sample.name = "bell-window-system" #x11.display = null #x11.xauthority = null } flags = [ ifexists nofail ] condition = [ { module.x11.bell = !false } ] } # The JACK DBus detection module. When jackdbus is started, this # will automatically make PipeWire become a JACK client. # use module.jackdbus-detect.args = { ... } to override the arguments. { name = libpipewire-module-jackdbus-detect args = { #jack.library = libjack.so.0 #jack.server = null #jack.client-name = PipeWire #jack.connect = true #tunnel.mode = duplex # source|sink|duplex source.props = { #audio.channels = 2 #midi.ports = 1 #audio.position = [ FL FR ] # extra sink properties } sink.props = { #audio.channels = 2 #midi.ports = 1 #audio.position = [ FL FR ] # extra sink properties } } flags = [ ifexists nofail ] condition = [ { module.jackdbus-detect = !false } ] } ] context.objects = [ #{ factory = # ( args = { = ... } ) # ( flags = [ ( nofail ) ] ) # ( condition = [ { = ... } ... ] ) #} # # Creates an object from a PipeWire factory with the given parameters. # If nofail is given, errors are ignored (and no object is created). # If condition is given, the object is created only when the context properties # all match the match rules. # #{ factory = spa-node-factory args = { factory.name = videotestsrc node.name = videotestsrc node.description = videotestsrc node.param.Props = { patternType = 1 } } } #{ factory = spa-device-factory args = { factory.name = api.jack.device foo=bar } flags = [ nofail ] } #{ factory = spa-device-factory args = { factory.name = api.alsa.enum.udev } } #{ factory = spa-node-factory args = { factory.name = api.alsa.seq.bridge node.name = Internal-MIDI-Bridge } } #{ factory = adapter args = { factory.name = audiotestsrc node.name = my-test node.description = audiotestsrc node.param.Props = { live = false }} } #{ factory = spa-node-factory args = { factory.name = api.vulkan.compute.source node.name = my-compute-source } } # A default dummy driver. This handles nodes marked with the "node.always-process" # property when no other driver is currently active. JACK clients need this. { factory = spa-node-factory args = { factory.name = support.node.driver node.name = Dummy-Driver node.group = pipewire.dummy node.sync-group = sync.dummy priority.driver = 200000 #clock.id = monotonic # realtime | tai | monotonic-raw | boottime #clock.name = "clock.system.monotonic" } condition = [ { factory.dummy-driver = !false } ] } { factory = spa-node-factory args = { factory.name = support.node.driver node.name = Freewheel-Driver priority.driver = 190000 node.group = pipewire.freewheel node.sync-group = sync.dummy node.freewheel = true #freewheel.wait = 10 } condition = [ { factory.freewheel-driver = !false } ] } # This creates a new Source node. It will have input ports # that you can link, to provide audio for this source. #{ factory = adapter # args = { # factory.name = support.null-audio-sink # node.name = "my-mic" # node.description = "Microphone" # media.class = "Audio/Source/Virtual" # audio.position = "FL,FR" # monitor.passthrough = true # } #} # This creates a single PCM source device for the given # alsa device path hw:0. You can change source to sink # to make a sink in the same way. #{ factory = adapter # args = { # factory.name = api.alsa.pcm.source # node.name = "alsa-source" # node.description = "PCM Source" # media.class = "Audio/Source" # api.alsa.path = "hw:0" # api.alsa.period-size = 1024 # api.alsa.headroom = 0 # api.alsa.disable-mmap = false # api.alsa.disable-batch = false # audio.format = "S16LE" # audio.rate = 48000 # audio.channels = 2 # audio.position = "FL,FR" # } #} # Use the metadata factory to create metadata and some default values. #{ factory = metadata # args = { # metadata.name = my-metadata # metadata.values = [ # { key = default.audio.sink value = { name = somesink } } # { key = default.audio.source value = { name = somesource } } # ] # } #} ] context.exec = [ #{ path = # ( args = "" | [ ... ] ) # ( condition = [ { = ... } ... ] ) #} # # Execute the given program with arguments. # If condition is given, the program is executed only when the context # properties all match the match rules. # # You can optionally start the session manager here, # but it is better to start it as a systemd service. # Run the session manager with -h for options. # @sm_comment@{ path = "@session_manager_path@" args = "@session_manager_args@" @sm_comment@ condition = [ { exec.session-manager = !false } ] } # # You can optionally start the pulseaudio-server here as well # but it is better to start it as a systemd service. # It can be interesting to start another daemon here that listens # on another address with the -a option (eg. -a tcp:4713). # @pulse_comment@{ path = "@pipewire_path@" args = [ "-c" "pipewire-pulse.conf" ] @pulse_comment@ condition = [ { exec.pipewire-pulse = !false } ] } ] pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/daemon/pipewire.desktop.in000066400000000000000000000003141511204443500263520ustar00rootroot00000000000000[Desktop Entry] Version=1.0 Name=PipeWire Media System Comment=Start the PipeWire Media System Exec=pipewire Terminal=false Type=Application X-GNOME-Autostart-Phase=Initialization X-KDE-autostart-phase=1 pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/daemon/systemd/000077500000000000000000000000001511204443500242205ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/daemon/systemd/meson.build000066400000000000000000000000401511204443500263540ustar00rootroot00000000000000subdir('system') subdir('user') pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/daemon/systemd/system/000077500000000000000000000000001511204443500255445ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/daemon/systemd/system/meson.build000066400000000000000000000021161511204443500277060ustar00rootroot00000000000000systemd = dependency('systemd', required : get_option('systemd-system-service')) if not systemd.found() subdir_done() endif systemd_system_services_dir = systemd.get_variable('systemdsystemunitdir', pkgconfig_define : [ 'rootprefix', prefix]) if get_option('systemd-system-unit-dir') != '' systemd_system_services_dir = get_option('systemd-system-unit-dir') endif install_data(sources : ['pipewire.socket', 'pipewire-manager.socket', 'pipewire-pulse.socket' ], install_dir : systemd_system_services_dir) systemd_config = configuration_data() systemd_config.set('PW_BINARY', pipewire_bindir / 'pipewire') systemd_config.set('PW_PULSE_BINARY', pipewire_bindir / 'pipewire-pulse') configure_file(input : 'pipewire.service.in', output : 'pipewire.service', configuration : systemd_config, install_dir : systemd_system_services_dir) configure_file(input : 'pipewire-pulse.service.in', output : 'pipewire-pulse.service', configuration : systemd_config, install_dir : systemd_system_services_dir) pipewire-manager.socket000066400000000000000000000003631511204443500321350ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/daemon/systemd/system[Unit] Description=PipeWire Multimedia System Manager Socket [Socket] Service=pipewire.service Priority=6 ListenStream=%t/pipewire/pipewire-0-manager SocketUser=pipewire SocketGroup=pipewire SocketMode=0600 [Install] WantedBy=sockets.target pipewire-pulse.service.in000066400000000000000000000011141511204443500324230ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/daemon/systemd/system[Unit] Description=PipeWire PulseAudio Service Requires=pipewire-pulse.socket Wants=pipewire.service pipewire-session-manager.service After=pipewire.service pipewire-session-manager.service [Service] LockPersonality=yes MemoryDenyWriteExecute=yes NoNewPrivileges=yes SystemCallArchitectures=native SystemCallFilter=@system-service Type=simple AmbientCapabilities=CAP_SYS_NICE ExecStart=@PW_PULSE_BINARY@ Restart=on-failure User=pipewire Environment=PIPEWIRE_RUNTIME_DIR=%t/pipewire Environment=PULSE_RUNTIME_PATH=%t/pulse [Install] Also=pipewire-pulse.socket WantedBy=pipewire.service pipewire-pulse.socket000066400000000000000000000003031511204443500316450ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/daemon/systemd/system[Unit] Description=PipeWire PulseAudio System Socket [Socket] Priority=6 ListenStream=%t/pulse/native SocketUser=pipewire SocketGroup=pipewire SocketMode=0660 [Install] WantedBy=sockets.target pipewire.service.in000066400000000000000000000021241511204443500312770ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/daemon/systemd/system[Unit] Description=PipeWire Multimedia Service # We require pipewire.socket to be active before starting the daemon, because # while it is possible to use the service without the socket, it is not clear # why it would be desirable. # # Installing pipewire and doing `systemctl start pipewire` will not get the # socket started, which might be confusing and problematic if the server is to # be restarted later on, as the client autospawn feature might kick in. Also, a # start of the socket unit will fail, adding to the confusion. # # After=pipewire.socket is not needed, as it is already implicit in the # socket-service relationship, see systemd.socket(5). Requires=pipewire.socket [Service] LockPersonality=yes MemoryDenyWriteExecute=yes NoNewPrivileges=yes SystemCallArchitectures=native SystemCallFilter=@system-service Type=simple AmbientCapabilities=CAP_SYS_NICE ExecStart=@PW_BINARY@ Restart=on-failure RuntimeDirectory=pipewire RuntimeDirectoryPreserve=yes User=pipewire Environment=PIPEWIRE_RUNTIME_DIR=%t/pipewire [Install] Also=pipewire.socket pipewire-manager.socket WantedBy=default.target pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/daemon/systemd/system/pipewire.socket000066400000000000000000000003121511204443500305760ustar00rootroot00000000000000[Unit] Description=PipeWire Multimedia System Socket [Socket] Priority=6 ListenStream=%t/pipewire/pipewire-0 SocketUser=pipewire SocketGroup=pipewire SocketMode=0660 [Install] WantedBy=sockets.target pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/daemon/systemd/user/000077500000000000000000000000001511204443500251765ustar00rootroot00000000000000filter-chain.service.in000066400000000000000000000006751511204443500314630ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/daemon/systemd/user[Unit] Description=PipeWire filter chain daemon After=pipewire.service pipewire-session-manager.service BindsTo=pipewire.service [Service] LockPersonality=yes MemoryDenyWriteExecute=yes NoNewPrivileges=yes RestrictNamespaces=yes SystemCallArchitectures=native SystemCallFilter=@system-service Type=simple ExecStart=@PW_BINARY@ -c filter-chain.conf Restart=on-failure Slice=session.slice [Install] Also=pipewire.socket WantedBy=default.target pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/daemon/systemd/user/meson.build000066400000000000000000000025661511204443500273510ustar00rootroot00000000000000systemd = dependency('systemd', required : get_option('systemd-user-service')) if not systemd.found() subdir_done() endif systemd_user_services_dir = systemd.get_variable('systemduserunitdir', pkgconfig_define : [ 'prefix', prefix]) if get_option('systemd-user-unit-dir') != '' systemd_user_services_dir = get_option('systemd-user-unit-dir') endif install_data( sources : ['pipewire.socket', 'pipewire-pulse.socket'], install_dir : systemd_user_services_dir) systemd_config = configuration_data() systemd_config.set('PW_BINARY', pipewire_bindir / 'pipewire') systemd_config.set('PW_PULSE_BINARY', pipewire_bindir / 'pipewire-pulse') pw_service_reqs = '' if get_option('dbus').enabled() pw_service_reqs += 'dbus.service ' endif systemd_config.set('PW_SERVICE_REQS', pw_service_reqs) configure_file(input : 'pipewire.service.in', output : 'pipewire.service', configuration : systemd_config, install_dir : systemd_user_services_dir) configure_file(input : 'pipewire-pulse.service.in', output : 'pipewire-pulse.service', configuration : systemd_config, install_dir : systemd_user_services_dir) configure_file(input : 'filter-chain.service.in', output : 'filter-chain.service', configuration : systemd_config, install_dir : systemd_user_services_dir) pipewire-pulse.service.in000066400000000000000000000022721511204443500320630ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/daemon/systemd/user[Unit] Description=PipeWire PulseAudio # We require pipewire-pulse.socket to be active before starting the daemon, because # while it is possible to use the service without the socket, it is not clear # why it would be desirable. # # A user installing pipewire and doing `systemctl --user start pipewire-pulse` # will not get the socket started, which might be confusing and problematic if # the server is to be restarted later on, as the client autospawn feature # might kick in. Also, a start of the socket unit will fail, adding to the # confusion. # # After=pipewire-pulse.socket is not needed, as it is already implicit in the # socket-service relationship, see systemd.socket(5). Requires=pipewire-pulse.socket ConditionUser=!root Wants=pipewire.service pipewire-session-manager.service After=pipewire.service pipewire-session-manager.service BindsTo=pipewire.service Conflicts=pulseaudio.service [Service] LockPersonality=yes MemoryDenyWriteExecute=yes NoNewPrivileges=yes RestrictNamespaces=yes SystemCallArchitectures=native SystemCallFilter=@system-service Type=simple ExecStart=@PW_PULSE_BINARY@ Restart=on-failure Slice=session.slice [Install] Also=pipewire-pulse.socket WantedBy=default.target pipewire-pulse.socket000066400000000000000000000002541511204443500313040ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/daemon/systemd/user[Unit] Description=PipeWire PulseAudio ConditionUser=!root Conflicts=pulseaudio.socket [Socket] Priority=6 ListenStream=%t/pulse/native [Install] WantedBy=sockets.target pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/daemon/systemd/user/pipewire.service.in000066400000000000000000000017731511204443500310210ustar00rootroot00000000000000[Unit] Description=PipeWire Multimedia Service # We require pipewire.socket to be active before starting the daemon, because # while it is possible to use the service without the socket, it is not clear # why it would be desirable. # # A user installing pipewire and doing `systemctl --user start pipewire` # will not get the socket started, which might be confusing and problematic if # the server is to be restarted later on, as the client autospawn feature # might kick in. Also, a start of the socket unit will fail, adding to the # confusion. # # After=pipewire.socket is not needed, as it is already implicit in the # socket-service relationship, see systemd.socket(5). Requires=pipewire.socket @PW_SERVICE_REQS@ ConditionUser=!root [Service] LockPersonality=yes MemoryDenyWriteExecute=yes NoNewPrivileges=yes SystemCallArchitectures=native SystemCallFilter=@system-service mincore Type=simple ExecStart=@PW_BINARY@ Restart=on-failure Slice=session.slice [Install] Also=pipewire.socket WantedBy=default.target pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/daemon/systemd/user/pipewire.socket000066400000000000000000000003001511204443500302250ustar00rootroot00000000000000[Unit] Description=PipeWire Multimedia System Sockets ConditionUser=!root [Socket] Priority=6 ListenStream=%t/pipewire-0 ListenStream=%t/pipewire-0-manager [Install] WantedBy=sockets.target pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/examples/000077500000000000000000000000001511204443500231035ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/examples/audio-capture.c000066400000000000000000000121431511204443500260120ustar00rootroot00000000000000/* PipeWire */ /* SPDX-FileCopyrightText: Copyright © 2022 Wim Taymans */ /* SPDX-License-Identifier: MIT */ /* [title] Audio capture using \ref pw_stream "pw_stream". [title] */ #include #include #include #include #include #include struct data { struct pw_main_loop *loop; struct pw_stream *stream; struct spa_audio_info format; unsigned move:1; }; /* our data processing function is in general: * * struct pw_buffer *b; * b = pw_stream_dequeue_buffer(stream); * * .. consume stuff in the buffer ... * * pw_stream_queue_buffer(stream, b); */ static void on_process(void *userdata) { struct data *data = userdata; struct pw_buffer *b; struct spa_buffer *buf; float *samples, max; uint32_t c, n, n_channels, n_samples, peak; if ((b = pw_stream_dequeue_buffer(data->stream)) == NULL) { pw_log_warn("out of buffers: %m"); return; } buf = b->buffer; if ((samples = buf->datas[0].data) == NULL) return; n_channels = data->format.info.raw.channels; n_samples = buf->datas[0].chunk->size / sizeof(float); /* move cursor up */ if (data->move) fprintf(stdout, "%c[%dA", 0x1b, n_channels + 1); fprintf(stdout, "captured %d samples\n", n_samples / n_channels); for (c = 0; c < data->format.info.raw.channels; c++) { max = 0.0f; for (n = c; n < n_samples; n += n_channels) max = fmaxf(max, fabsf(samples[n])); peak = (uint32_t)SPA_CLAMPF(max * 30, 0.f, 39.f); fprintf(stdout, "channel %d: |%*s%*s| peak:%f\n", c, peak+1, "*", 40 - peak, "", max); } data->move = true; fflush(stdout); pw_stream_queue_buffer(data->stream, b); } /* Be notified when the stream param changes. We're only looking at the * format changes. */ static void on_stream_param_changed(void *_data, uint32_t id, const struct spa_pod *param) { struct data *data = _data; /* NULL means to clear the format */ if (param == NULL || id != SPA_PARAM_Format) return; if (spa_format_parse(param, &data->format.media_type, &data->format.media_subtype) < 0) return; /* only accept raw audio */ if (data->format.media_type != SPA_MEDIA_TYPE_audio || data->format.media_subtype != SPA_MEDIA_SUBTYPE_raw) return; /* call a helper function to parse the format for us. */ spa_format_audio_raw_parse(param, &data->format.info.raw); fprintf(stdout, "capturing rate:%d channels:%d\n", data->format.info.raw.rate, data->format.info.raw.channels); } static const struct pw_stream_events stream_events = { PW_VERSION_STREAM_EVENTS, .param_changed = on_stream_param_changed, .process = on_process, }; static void do_quit(void *userdata, int signal_number) { struct data *data = userdata; pw_main_loop_quit(data->loop); } int main(int argc, char *argv[]) { struct data data = { 0, }; const struct spa_pod *params[1]; uint32_t n_params = 0; uint8_t buffer[1024]; struct pw_properties *props; struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); pw_init(&argc, &argv); /* make a main loop. If you already have another main loop, you can add * the fd of this pipewire mainloop to it. */ data.loop = pw_main_loop_new(NULL); pw_loop_add_signal(pw_main_loop_get_loop(data.loop), SIGINT, do_quit, &data); pw_loop_add_signal(pw_main_loop_get_loop(data.loop), SIGTERM, do_quit, &data); /* Create a simple stream, the simple stream manages the core and remote * objects for you if you don't need to deal with them. * * If you plan to autoconnect your stream, you need to provide at least * media, category and role properties. * * Pass your events and a user_data pointer as the last arguments. This * will inform you about the stream state. The most important event * you need to listen to is the process event where you need to produce * the data. */ props = pw_properties_new(PW_KEY_MEDIA_TYPE, "Audio", PW_KEY_MEDIA_CATEGORY, "Capture", PW_KEY_MEDIA_ROLE, "Music", NULL); if (argc > 1) /* Set stream target if given on command line */ pw_properties_set(props, PW_KEY_TARGET_OBJECT, argv[1]); /* uncomment if you want to capture from the sink monitor ports */ /* pw_properties_set(props, PW_KEY_STREAM_CAPTURE_SINK, "true"); */ data.stream = pw_stream_new_simple( pw_main_loop_get_loop(data.loop), "audio-capture", props, &stream_events, &data); /* Make one parameter with the supported formats. The SPA_PARAM_EnumFormat * id means that this is a format enumeration (of 1 value). * We leave the channels and rate empty to accept the native graph * rate and channels. */ params[n_params++] = spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat, &SPA_AUDIO_INFO_RAW_INIT( .format = SPA_AUDIO_FORMAT_F32)); /* Now connect this stream. We ask that our process function is * called in a realtime thread. */ pw_stream_connect(data.stream, PW_DIRECTION_INPUT, PW_ID_ANY, PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_MAP_BUFFERS | PW_STREAM_FLAG_RT_PROCESS, params, n_params); /* and wait while we let things run */ pw_main_loop_run(data.loop); pw_stream_destroy(data.stream); pw_main_loop_destroy(data.loop); pw_deinit(); return 0; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/examples/audio-dsp-filter.c000066400000000000000000000077151511204443500264310ustar00rootroot00000000000000/* PipeWire */ /* SPDX-FileCopyrightText: Copyright © 2019 Wim Taymans */ /* SPDX-License-Identifier: MIT */ /* [title] Audio filter using \ref pw_filter "pw_filter". [title] */ #include #include #include #include #include #include #include #include struct data; struct port { struct data *data; }; struct data { struct pw_main_loop *loop; struct pw_filter *filter; struct port *in_port; struct port *out_port; }; /* our data processing function is in general: * * struct pw_buffer *b; * in = pw_filter_dequeue_buffer(filter, in_port); * out = pw_filter_dequeue_buffer(filter, out_port); * * .. do stuff with buffers ... * * pw_filter_queue_buffer(filter, in_port, in); * pw_filter_queue_buffer(filter, out_port, out); * * For DSP ports, there is a shortcut to directly dequeue, get * the data and requeue the buffer with pw_filter_get_dsp_buffer(). * * */ static void on_process(void *userdata, struct spa_io_position *position) { struct data *data = userdata; float *in, *out; uint32_t n_samples = position->clock.duration; pw_log_trace("do process %d", n_samples); in = pw_filter_get_dsp_buffer(data->in_port, n_samples); out = pw_filter_get_dsp_buffer(data->out_port, n_samples); if (in == NULL || out == NULL) return; memcpy(out, in, n_samples * sizeof(float)); } static const struct pw_filter_events filter_events = { PW_VERSION_FILTER_EVENTS, .process = on_process, }; static void do_quit(void *userdata, int signal_number) { struct data *data = userdata; pw_main_loop_quit(data->loop); } int main(int argc, char *argv[]) { struct data data = { 0, }; const struct spa_pod *params[1]; uint32_t n_params = 0; uint8_t buffer[1024]; struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); pw_init(&argc, &argv); /* make a main loop. If you already have another main loop, you can add * the fd of this pipewire mainloop to it. */ data.loop = pw_main_loop_new(NULL); pw_loop_add_signal(pw_main_loop_get_loop(data.loop), SIGINT, do_quit, &data); pw_loop_add_signal(pw_main_loop_get_loop(data.loop), SIGTERM, do_quit, &data); /* Create a simple filter, the simple filter manages the core and remote * objects for you if you don't need to deal with them. * * Pass your events and a user_data pointer as the last arguments. This * will inform you about the filter state. The most important event * you need to listen to is the process event where you need to process * the data. */ data.filter = pw_filter_new_simple( pw_main_loop_get_loop(data.loop), "audio-filter", pw_properties_new( PW_KEY_MEDIA_TYPE, "Audio", PW_KEY_MEDIA_CATEGORY, "Filter", PW_KEY_MEDIA_ROLE, "DSP", NULL), &filter_events, &data); /* make an audio DSP input port */ data.in_port = pw_filter_add_port(data.filter, PW_DIRECTION_INPUT, PW_FILTER_PORT_FLAG_MAP_BUFFERS, sizeof(struct port), pw_properties_new( PW_KEY_FORMAT_DSP, "32 bit float mono audio", PW_KEY_PORT_NAME, "input", NULL), NULL, 0); /* make an audio DSP output port */ data.out_port = pw_filter_add_port(data.filter, PW_DIRECTION_OUTPUT, PW_FILTER_PORT_FLAG_MAP_BUFFERS, sizeof(struct port), pw_properties_new( PW_KEY_FORMAT_DSP, "32 bit float mono audio", PW_KEY_PORT_NAME, "output", NULL), NULL, 0); params[n_params++] = spa_process_latency_build(&b, SPA_PARAM_ProcessLatency, &SPA_PROCESS_LATENCY_INFO_INIT( .ns = 10 * SPA_NSEC_PER_MSEC )); /* Now connect this filter. We ask that our process function is * called in a realtime thread. */ if (pw_filter_connect(data.filter, PW_FILTER_FLAG_RT_PROCESS, params, n_params) < 0) { fprintf(stderr, "can't connect\n"); return -1; } /* and wait while we let things run */ pw_main_loop_run(data.loop); pw_filter_destroy(data.filter); pw_main_loop_destroy(data.loop); pw_deinit(); return 0; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/examples/audio-dsp-sink.c000066400000000000000000000123621511204443500261020ustar00rootroot00000000000000/* PipeWire */ /* SPDX-FileCopyrightText: Copyright © 2025 Wim Taymans */ /* SPDX-License-Identifier: MIT */ /* [title] Audio sink using \ref pw_filter "pw_filter" [title] */ #include "config.h" #include #include #include #include #include #include #include #include /* define to make this filter allocate buffer memory */ #define ALLOC_BUFFERS struct data; struct port { struct data *data; }; struct data { struct pw_main_loop *loop; struct pw_filter *filter; struct port *in_port; bool move; uint32_t quantum_limit; }; /* our data processing function is in general: * * struct pw_buffer *b; * out = pw_filter_dequeue_buffer(filter, in_port); * * .. consume data in the buffer ... * * pw_filter_queue_buffer(filter, in_port, out); * * For DSP ports, there is a shortcut to directly dequeue, get * the data and requeue the buffer with pw_filter_get_dsp_buffer(). */ static void on_process(void *userdata, struct spa_io_position *position) { struct data *data = userdata; float *in, max; struct port *in_port = data->in_port; uint32_t i, n_samples = position->clock.duration, peak; pw_log_trace("do process %d", n_samples); in = pw_filter_get_dsp_buffer(in_port, n_samples); if (in == NULL) return; /* move cursor up */ if (data->move) fprintf(stdout, "%c[%dA", 0x1b, 2); fprintf(stdout, "captured %d samples\n", n_samples); max = 0.0f; for (i = 0; i < n_samples; i++) max = fmaxf(max, fabsf(in[i])); peak = (uint32_t)SPA_CLAMPF(max * 30, 0.f, 39.f); fprintf(stdout, "input: |%*s%*s| peak:%f\n", peak+1, "*", 40 - peak, "", max); data->move = true; fflush(stdout); } #ifdef ALLOC_BUFFERS /* close the memfd we set on the buffers here */ static void on_remove_buffer(void *_data, void *_port_data, struct pw_buffer *buffer) { struct spa_buffer *buf = buffer->buffer; struct spa_data *d; d = buf->datas; pw_log_info("remove buffer %p", buffer); close(d[0].fd); } /* we set the PW_STREAM_FLAG_ALLOC_BUFFERS flag when connecting so we need * to provide buffer memory. */ static void on_add_buffer(void *_data, void *_port_data, struct pw_buffer *buffer) { struct data *data = _data; struct spa_buffer *buf = buffer->buffer; struct spa_data *d; pw_log_info("add buffer %p", buffer); d = buf->datas; if ((d[0].type & (1<quantum_limit * sizeof(float); /* truncate to the right size */ if (ftruncate(d[0].fd, d[0].maxsize) < 0) { pw_log_error("can't truncate to %d: %m", d[0].maxsize); return; } } #endif static const struct pw_filter_events filter_events = { PW_VERSION_FILTER_EVENTS, .process = on_process, #ifdef ALLOC_BUFFERS .add_buffer = on_add_buffer, .remove_buffer = on_remove_buffer, #endif }; static void do_quit(void *userdata, int signal_number) { struct data *data = userdata; pw_main_loop_quit(data->loop); } int main(int argc, char *argv[]) { struct data data = { 0, }; uint32_t flags; pw_init(&argc, &argv); data.quantum_limit= 8192; /* make a main loop. If you already have another main loop, you can add * the fd of this pipewire mainloop to it. */ data.loop = pw_main_loop_new(NULL); pw_loop_add_signal(pw_main_loop_get_loop(data.loop), SIGINT, do_quit, &data); pw_loop_add_signal(pw_main_loop_get_loop(data.loop), SIGTERM, do_quit, &data); /* Create a simple filter, the simple filter manages the core and remote * objects for you if you don't need to deal with them. * * Pass your events and a user_data pointer as the last arguments. This * will inform you about the filter state. The most important event * you need to listen to is the process event where you need to process * the data. */ data.filter = pw_filter_new_simple( pw_main_loop_get_loop(data.loop), "audio-dsp-sink", pw_properties_new( PW_KEY_MEDIA_TYPE, "Audio", PW_KEY_MEDIA_CATEGORY, "Sink", PW_KEY_MEDIA_ROLE, "DSP", PW_KEY_MEDIA_CLASS, "Stream/Input/Audio", PW_KEY_NODE_AUTOCONNECT, "true", NULL), &filter_events, &data); flags = PW_FILTER_PORT_FLAG_MAP_BUFFERS; #ifdef ALLOC_BUFFERS flags |= PW_FILTER_PORT_FLAG_ALLOC_BUFFERS; #endif /* make an audio DSP output port */ data.in_port = pw_filter_add_port(data.filter, PW_DIRECTION_INPUT, flags, sizeof(struct port), pw_properties_new( PW_KEY_FORMAT_DSP, "32 bit float mono audio", PW_KEY_PORT_NAME, "input", NULL), NULL, 0); /* Now connect this filter. We ask that our process function is * called in a realtime thread. */ if (pw_filter_connect(data.filter, PW_FILTER_FLAG_RT_PROCESS, NULL, 0) < 0) { fprintf(stderr, "can't connect\n"); return -1; } /* and wait while we let things run */ pw_main_loop_run(data.loop); pw_filter_destroy(data.filter); pw_main_loop_destroy(data.loop); pw_deinit(); return 0; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/examples/audio-dsp-sink2.c000066400000000000000000000112421511204443500261600ustar00rootroot00000000000000/* PipeWire */ /* SPDX-FileCopyrightText: Copyright © 2025 Wim Taymans */ /* SPDX-License-Identifier: MIT */ /* [title] Audio sink using \ref pw_filter "pw_filter" [title] */ #include "config.h" #include #include #include #include #include #include #include #include #include struct data; struct port { struct data *data; }; struct data { struct pw_main_loop *loop; struct pw_filter *filter; struct port *in_port; bool move; uint32_t quantum_limit; }; /* our data processing function is in general: * * struct pw_buffer *b; * out = pw_filter_dequeue_buffer(filter, in_port); * * .. consume data in the buffer ... * * pw_filter_queue_buffer(filter, in_port, out); * * For DSP ports, there is a shortcut to directly dequeue, get * the data and requeue the buffer with pw_filter_get_dsp_buffer(). */ static void on_process(void *userdata, struct spa_io_position *position) { struct data *data = userdata; float *in, max; struct port *in_port = data->in_port; uint32_t i, n_samples = position->clock.duration, peak; pw_log_trace("do process %d", n_samples); in = pw_filter_get_dsp_buffer(in_port, n_samples); if (in == NULL) return; /* move cursor up */ if (data->move) fprintf(stdout, "%c[%dA", 0x1b, 2); fprintf(stdout, "captured %d samples\n", n_samples); max = 0.0f; for (i = 0; i < n_samples; i++) max = fmaxf(max, fabsf(in[i])); peak = (uint32_t)SPA_CLAMPF(max * 30, 0.f, 39.f); fprintf(stdout, "input: |%*s%*s| peak:%f\n", peak+1, "*", 40 - peak, "", max); data->move = true; fflush(stdout); } /* Check the buffer memory */ static void on_add_buffer(void *_data, void *_port_data, struct pw_buffer *buffer) { struct spa_buffer *buf = buffer->buffer; struct spa_data *d; pw_log_info("add buffer %p", buffer); d = buf->datas; if ((d[0].type != SPA_DATA_MemFd)) { pw_log_error("unsupported data type %08x", d[0].type); return; } } static const struct pw_filter_events filter_events = { PW_VERSION_FILTER_EVENTS, .process = on_process, .add_buffer = on_add_buffer, }; static void do_quit(void *userdata, int signal_number) { struct data *data = userdata; pw_main_loop_quit(data->loop); } int main(int argc, char *argv[]) { struct data data = { 0, }; uint32_t flags, n_params = 0; const struct spa_pod *params[1]; uint8_t buffer[1024]; struct spa_pod_builder b; pw_init(&argc, &argv); data.quantum_limit = 8192; /* make a main loop. If you already have another main loop, you can add * the fd of this pipewire mainloop to it. */ data.loop = pw_main_loop_new(NULL); pw_loop_add_signal(pw_main_loop_get_loop(data.loop), SIGINT, do_quit, &data); pw_loop_add_signal(pw_main_loop_get_loop(data.loop), SIGTERM, do_quit, &data); /* Create a simple filter, the simple filter manages the core and remote * objects for you if you don't need to deal with them. * * Pass your events and a user_data pointer as the last arguments. This * will inform you about the filter state. The most important event * you need to listen to is the process event where you need to process * the data. */ data.filter = pw_filter_new_simple( pw_main_loop_get_loop(data.loop), "audio-dsp-sink2", pw_properties_new( PW_KEY_MEDIA_TYPE, "Audio", PW_KEY_MEDIA_CATEGORY, "Sink", PW_KEY_MEDIA_ROLE, "DSP", PW_KEY_MEDIA_CLASS, "Stream/Input/Audio", PW_KEY_NODE_AUTOCONNECT, "true", NULL), &filter_events, &data); flags = PW_FILTER_PORT_FLAG_MAP_BUFFERS; spa_pod_builder_init(&b, buffer, sizeof(buffer)); params[n_params++] = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers, SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(1, 1, 16), SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(1), SPA_PARAM_BUFFERS_size, SPA_POD_Int(sizeof(float) * data.quantum_limit), SPA_PARAM_BUFFERS_stride, SPA_POD_Int(sizeof(float)), SPA_PARAM_BUFFERS_dataType, SPA_POD_CHOICE_FLAGS_Int(1< #include #include #include #include #include #define M_PI_M2f (float)(M_PI+M_PI) #define DEFAULT_RATE 44100 #define DEFAULT_FREQ 440 #define DEFAULT_VOLUME 0.7f struct data; struct port { struct data *data; float accumulator; }; struct data { struct pw_main_loop *loop; struct pw_filter *filter; struct port *out_port; }; /* our data processing function is in general: * * struct pw_buffer *b; * out = pw_filter_dequeue_buffer(filter, out_port); * * .. generate data in the buffer ... * * pw_filter_queue_buffer(filter, out_port, out); * * For DSP ports, there is a shortcut to directly dequeue, get * the data and requeue the buffer with pw_filter_get_dsp_buffer(). */ static void on_process(void *userdata, struct spa_io_position *position) { struct data *data = userdata; float *out; struct port *out_port = data->out_port; uint32_t i, n_samples = position->clock.duration; pw_log_trace("do process %d", n_samples); out = pw_filter_get_dsp_buffer(out_port, n_samples); if (out == NULL) return; for (i = 0; i < n_samples; i++) { out_port->accumulator += M_PI_M2f * DEFAULT_FREQ / DEFAULT_RATE; if (out_port->accumulator >= M_PI_M2f) out_port->accumulator -= M_PI_M2f; *out++ = sinf(out_port->accumulator) * DEFAULT_VOLUME; } } static const struct pw_filter_events filter_events = { PW_VERSION_FILTER_EVENTS, .process = on_process, }; static void do_quit(void *userdata, int signal_number) { struct data *data = userdata; pw_main_loop_quit(data->loop); } int main(int argc, char *argv[]) { struct data data = { 0, }; pw_init(&argc, &argv); /* make a main loop. If you already have another main loop, you can add * the fd of this pipewire mainloop to it. */ data.loop = pw_main_loop_new(NULL); pw_loop_add_signal(pw_main_loop_get_loop(data.loop), SIGINT, do_quit, &data); pw_loop_add_signal(pw_main_loop_get_loop(data.loop), SIGTERM, do_quit, &data); /* Create a simple filter, the simple filter manages the core and remote * objects for you if you don't need to deal with them. * * Pass your events and a user_data pointer as the last arguments. This * will inform you about the filter state. The most important event * you need to listen to is the process event where you need to process * the data. */ data.filter = pw_filter_new_simple( pw_main_loop_get_loop(data.loop), "audio-dsp-src", pw_properties_new( PW_KEY_MEDIA_TYPE, "Audio", PW_KEY_MEDIA_CATEGORY, "Source", PW_KEY_MEDIA_ROLE, "DSP", PW_KEY_MEDIA_CLASS, "Stream/Output/Audio", PW_KEY_NODE_AUTOCONNECT, "true", NULL), &filter_events, &data); /* make an audio DSP output port */ data.out_port = pw_filter_add_port(data.filter, PW_DIRECTION_OUTPUT, PW_FILTER_PORT_FLAG_MAP_BUFFERS, sizeof(struct port), pw_properties_new( PW_KEY_FORMAT_DSP, "32 bit float mono audio", PW_KEY_PORT_NAME, "output", NULL), NULL, 0); /* Now connect this filter. We ask that our process function is * called in a realtime thread. */ if (pw_filter_connect(data.filter, PW_FILTER_FLAG_RT_PROCESS, NULL, 0) < 0) { fprintf(stderr, "can't connect\n"); return -1; } /* and wait while we let things run */ pw_main_loop_run(data.loop); pw_filter_destroy(data.filter); pw_main_loop_destroy(data.loop); pw_deinit(); return 0; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/examples/audio-src-ring.c000066400000000000000000000141711511204443500260760ustar00rootroot00000000000000/* PipeWire */ /* SPDX-FileCopyrightText: Copyright © 2024 Wim Taymans */ /* SPDX-License-Identifier: MIT */ /* [title] Audio source using \ref pw_stream "pw_stream" and ringbuffer. [title] */ #include #include #include #include #include #include #include #define M_PI_M2f (float)(M_PI+M_PI) #define DEFAULT_RATE 44100 #define DEFAULT_CHANNELS 2 #define DEFAULT_VOLUME 0.7f #define BUFFER_SIZE (16*1024) struct data { struct pw_main_loop *main_loop; struct pw_loop *loop; struct pw_stream *stream; float accumulator; struct spa_source *refill_event; struct spa_ringbuffer ring; float buffer[BUFFER_SIZE * DEFAULT_CHANNELS]; }; static void fill_f32(struct data *d, uint32_t offset, int n_frames) { float val; int i, c; for (i = 0; i < n_frames; i++) { d->accumulator += M_PI_M2f * 440 / DEFAULT_RATE; if (d->accumulator >= M_PI_M2f) d->accumulator -= M_PI_M2f; val = sinf(d->accumulator) * DEFAULT_VOLUME; for (c = 0; c < DEFAULT_CHANNELS; c++) d->buffer[((offset + i) % BUFFER_SIZE) * DEFAULT_CHANNELS + c] = val; } } /* this is called from the main-thread when we need to fill up the ringbuffer * with more data */ static void do_refill(void *userdata, uint64_t count) { struct data *data = userdata; int32_t filled; uint32_t index, avail; filled = spa_ringbuffer_get_write_index(&data->ring, &index); /* we xrun, this can not happen because we never read more * than what there is in the ringbuffer and we never write more than * what is left */ spa_assert(filled >= 0); spa_assert(filled <= BUFFER_SIZE); /* this is how much samples we can write */ avail = BUFFER_SIZE - filled; /* write new samples to the ringbuffer from the given index */ fill_f32(data, index, avail); /* and advance the ringbuffer */ spa_ringbuffer_write_update(&data->ring, index + avail); } /* our data processing function is in general: * * struct pw_buffer *b; * b = pw_stream_dequeue_buffer(stream); * * .. generate stuff in the buffer ... * In this case we read samples from a ringbuffer. The ringbuffer is * filled up by another thread. * * pw_stream_queue_buffer(stream, b); */ static void on_process(void *userdata) { struct data *data = userdata; struct pw_buffer *b; struct spa_buffer *buf; uint8_t *p; uint32_t index, to_read, to_silence; int32_t avail, n_frames, stride; if ((b = pw_stream_dequeue_buffer(data->stream)) == NULL) { pw_log_warn("out of buffers: %m"); return; } buf = b->buffer; if ((p = buf->datas[0].data) == NULL) return; /* the amount of space in the ringbuffer and the read index */ avail = spa_ringbuffer_get_read_index(&data->ring, &index); stride = sizeof(float) * DEFAULT_CHANNELS; n_frames = buf->datas[0].maxsize / stride; if (b->requested) n_frames = SPA_MIN((int32_t)b->requested, n_frames); /* we can read if there is something available */ to_read = avail > 0 ? SPA_MIN(avail, n_frames) : 0; /* and fill the remainder with silence */ to_silence = n_frames - to_read; if (to_read > 0) { /* read data into the buffer */ spa_ringbuffer_read_data(&data->ring, data->buffer, BUFFER_SIZE * stride, (index % BUFFER_SIZE) * stride, p, to_read * stride); /* update the read pointer */ spa_ringbuffer_read_update(&data->ring, index + to_read); } if (to_silence > 0) /* set the rest of the buffer to silence */ memset(SPA_PTROFF(p, to_read * stride, void), 0, to_silence * stride); buf->datas[0].chunk->offset = 0; buf->datas[0].chunk->stride = stride; buf->datas[0].chunk->size = n_frames * stride; pw_stream_queue_buffer(data->stream, b); /* signal the main thread to fill the ringbuffer, we can only do this, for * example when the available ringbuffer space falls below a certain * level. */ pw_loop_signal_event(data->loop, data->refill_event); } static const struct pw_stream_events stream_events = { PW_VERSION_STREAM_EVENTS, .process = on_process, }; static void do_quit(void *userdata, int signal_number) { struct data *data = userdata; pw_main_loop_quit(data->main_loop); } int main(int argc, char *argv[]) { struct data data = { 0, }; const struct spa_pod *params[1]; uint32_t n_params = 0; uint8_t buffer[1024]; struct pw_properties *props; struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); pw_init(&argc, &argv); data.main_loop = pw_main_loop_new(NULL); data.loop = pw_main_loop_get_loop(data.main_loop); pw_loop_add_signal(data.loop, SIGINT, do_quit, &data); pw_loop_add_signal(data.loop, SIGTERM, do_quit, &data); /* we're going to refill a ringbuffer from the main loop. Make an * event for this. */ spa_ringbuffer_init(&data.ring); data.refill_event = pw_loop_add_event(data.loop, do_refill, &data); /* prefill the ringbuffer */ do_refill(&data, 0); props = pw_properties_new(PW_KEY_MEDIA_TYPE, "Audio", PW_KEY_MEDIA_CATEGORY, "Playback", PW_KEY_MEDIA_ROLE, "Music", NULL); if (argc > 1) /* Set stream target if given on command line */ pw_properties_set(props, PW_KEY_TARGET_OBJECT, argv[1]); data.stream = pw_stream_new_simple( data.loop, "audio-src-ring", props, &stream_events, &data); /* Make one parameter with the supported formats. The SPA_PARAM_EnumFormat * id means that this is a format enumeration (of 1 value). */ params[n_params++] = spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat, &SPA_AUDIO_INFO_RAW_INIT( .format = SPA_AUDIO_FORMAT_F32, .channels = DEFAULT_CHANNELS, .rate = DEFAULT_RATE )); /* Now connect this stream. We ask that our process function is * called in a realtime thread. */ pw_stream_connect(data.stream, PW_DIRECTION_OUTPUT, PW_ID_ANY, PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_MAP_BUFFERS | PW_STREAM_FLAG_RT_PROCESS, params, n_params); /* and wait while we let things run */ pw_main_loop_run(data.main_loop); pw_stream_destroy(data.stream); pw_loop_destroy_source(data.loop, data.refill_event); pw_main_loop_destroy(data.main_loop); pw_deinit(); return 0; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/examples/audio-src-ring2.c000066400000000000000000000163511511204443500261620ustar00rootroot00000000000000/* PipeWire */ /* SPDX-FileCopyrightText: Copyright © 2024 Wim Taymans */ /* SPDX-License-Identifier: MIT */ /* [title] Audio source using \ref pw_stream "pw_stream" and ringbuffer. This one uses a thread-loop and does a blocking push into a ringbuffer. [title] */ #include #include #include #include #include #include #include #define M_PI_M2f (float)(M_PI+M_PI) #define DEFAULT_RATE 44100 #define DEFAULT_CHANNELS 2 #define DEFAULT_VOLUME 0.7f #define BUFFER_SIZE (16*1024) #define MIN_SIZE 256 #define MAX_SIZE BUFFER_SIZE static float samples[BUFFER_SIZE * DEFAULT_CHANNELS]; struct data { struct pw_thread_loop *thread_loop; struct pw_loop *loop; struct pw_stream *stream; int eventfd; bool running; float accumulator; struct spa_ringbuffer ring; float buffer[BUFFER_SIZE * DEFAULT_CHANNELS]; }; static void fill_f32(struct data *d, float *samples, int n_frames) { float val; int i, c; for (i = 0; i < n_frames; i++) { d->accumulator += M_PI_M2f * 440 / DEFAULT_RATE; if (d->accumulator >= M_PI_M2f) d->accumulator -= M_PI_M2f; val = sinf(d->accumulator) * DEFAULT_VOLUME; for (c = 0; c < DEFAULT_CHANNELS; c++) samples[i * DEFAULT_CHANNELS + c] = val; } } /* this can be called from any thread with a block of samples to write into * the ringbuffer. It will block until all data has been written */ static void push_samples(void *userdata, float *samples, uint32_t n_samples) { struct data *data = userdata; int32_t filled; uint32_t index, avail, stride = sizeof(float) * DEFAULT_CHANNELS; uint64_t count; float *s = samples; while (n_samples > 0) { while (true) { filled = spa_ringbuffer_get_write_index(&data->ring, &index); /* we xrun, this can not happen because we never read more * than what there is in the ringbuffer and we never write more than * what is left */ spa_assert(filled >= 0); spa_assert(filled <= BUFFER_SIZE); /* this is how much samples we can write */ avail = BUFFER_SIZE - filled; if (avail > 0) break; /* no space.. block and wait for free space */ spa_system_eventfd_read(data->loop->system, data->eventfd, &count); if (!data->running) return; } if (avail > n_samples) avail = n_samples; spa_ringbuffer_write_data(&data->ring, data->buffer, BUFFER_SIZE * stride, (index % BUFFER_SIZE) * stride, s, avail * stride); s += avail * DEFAULT_CHANNELS; n_samples -= avail; /* and advance the ringbuffer */ spa_ringbuffer_write_update(&data->ring, index + avail); } } /* our data processing function is in general: * * struct pw_buffer *b; * b = pw_stream_dequeue_buffer(stream); * * .. generate stuff in the buffer ... * In this case we read samples from a ringbuffer. The ringbuffer is * filled up by another thread. * * pw_stream_queue_buffer(stream, b); */ static void on_process(void *userdata) { struct data *data = userdata; struct pw_buffer *b; struct spa_buffer *buf; uint8_t *p; uint32_t index, to_read, to_silence; int32_t avail, n_frames, stride; if ((b = pw_stream_dequeue_buffer(data->stream)) == NULL) { pw_log_warn("out of buffers: %m"); return; } buf = b->buffer; if ((p = buf->datas[0].data) == NULL) return; /* the amount of space in the ringbuffer and the read index */ avail = spa_ringbuffer_get_read_index(&data->ring, &index); stride = sizeof(float) * DEFAULT_CHANNELS; n_frames = buf->datas[0].maxsize / stride; if (b->requested) n_frames = SPA_MIN((int32_t)b->requested, n_frames); /* we can read if there is something available */ to_read = avail > 0 ? SPA_MIN(avail, n_frames) : 0; /* and fill the remainder with silence */ to_silence = n_frames - to_read; if (to_read > 0) { /* read data into the buffer */ spa_ringbuffer_read_data(&data->ring, data->buffer, BUFFER_SIZE * stride, (index % BUFFER_SIZE) * stride, p, to_read * stride); /* update the read pointer */ spa_ringbuffer_read_update(&data->ring, index + to_read); } if (to_silence > 0) /* set the rest of the buffer to silence */ memset(SPA_PTROFF(p, to_read * stride, void), 0, to_silence * stride); buf->datas[0].chunk->offset = 0; buf->datas[0].chunk->stride = stride; buf->datas[0].chunk->size = n_frames * stride; pw_stream_queue_buffer(data->stream, b); /* signal the main thread to fill the ringbuffer, we can only do this, for * example when the available ringbuffer space falls below a certain * level. */ spa_system_eventfd_write(data->loop->system, data->eventfd, 1); } static const struct pw_stream_events stream_events = { PW_VERSION_STREAM_EVENTS, .process = on_process, }; static void do_quit(void *userdata, int signal_number) { struct data *data = userdata; data->running = false; } int main(int argc, char *argv[]) { struct data data = { 0, }; const struct spa_pod *params[1]; uint32_t n_params = 0; uint8_t buffer[1024]; struct pw_properties *props; struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); pw_init(&argc, &argv); data.thread_loop = pw_thread_loop_new("audio-src", NULL); data.loop = pw_thread_loop_get_loop(data.thread_loop); data.running = true; pw_thread_loop_lock(data.thread_loop); pw_loop_add_signal(data.loop, SIGINT, do_quit, &data); pw_loop_add_signal(data.loop, SIGTERM, do_quit, &data); spa_ringbuffer_init(&data.ring); if ((data.eventfd = spa_system_eventfd_create(data.loop->system, SPA_FD_CLOEXEC)) < 0) return data.eventfd; pw_thread_loop_start(data.thread_loop); props = pw_properties_new(PW_KEY_MEDIA_TYPE, "Audio", PW_KEY_MEDIA_CATEGORY, "Playback", PW_KEY_MEDIA_ROLE, "Music", NULL); if (argc > 1) /* Set stream target if given on command line */ pw_properties_set(props, PW_KEY_TARGET_OBJECT, argv[1]); data.stream = pw_stream_new_simple( data.loop, "audio-src-ring", props, &stream_events, &data); /* Make one parameter with the supported formats. The SPA_PARAM_EnumFormat * id means that this is a format enumeration (of 1 value). */ params[n_params++] = spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat, &SPA_AUDIO_INFO_RAW_INIT( .format = SPA_AUDIO_FORMAT_F32, .channels = DEFAULT_CHANNELS, .rate = DEFAULT_RATE )); /* Now connect this stream. We ask that our process function is * called in a realtime thread. */ pw_stream_connect(data.stream, PW_DIRECTION_OUTPUT, PW_ID_ANY, PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_MAP_BUFFERS | PW_STREAM_FLAG_RT_PROCESS, params, n_params); /* prefill the ringbuffer */ fill_f32(&data, samples, BUFFER_SIZE); push_samples(&data, samples, BUFFER_SIZE); srand(time(NULL)); pw_thread_loop_start(data.thread_loop); pw_thread_loop_unlock(data.thread_loop); while (data.running) { uint32_t size = rand() % ((MAX_SIZE - MIN_SIZE + 1) + MIN_SIZE); /* make new random sized block of samples and push */ fill_f32(&data, samples, size); push_samples(&data, samples, size); } pw_thread_loop_lock(data.thread_loop); pw_stream_destroy(data.stream); pw_thread_loop_unlock(data.thread_loop); pw_thread_loop_destroy(data.thread_loop); close(data.eventfd); pw_deinit(); return 0; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/examples/audio-src.c000066400000000000000000000105301511204443500251340ustar00rootroot00000000000000/* PipeWire */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ /* [title] Audio source using \ref pw_stream "pw_stream". [title] */ #include #include #include #include #include #include #define M_PI_M2f (float)(M_PI+M_PI) #define DEFAULT_RATE 44100 #define DEFAULT_CHANNELS 2 #define DEFAULT_VOLUME 0.7f struct data { struct pw_main_loop *loop; struct pw_stream *stream; float accumulator; }; static void fill_f32(struct data *d, void *dest, int n_frames) { float *dst = dest, val; int i, c; for (i = 0; i < n_frames; i++) { d->accumulator += M_PI_M2f * 440 / DEFAULT_RATE; if (d->accumulator >= M_PI_M2f) d->accumulator -= M_PI_M2f; val = sinf(d->accumulator) * DEFAULT_VOLUME; for (c = 0; c < DEFAULT_CHANNELS; c++) *dst++ = val; } } /* our data processing function is in general: * * struct pw_buffer *b; * b = pw_stream_dequeue_buffer(stream); * * .. generate stuff in the buffer ... * * pw_stream_queue_buffer(stream, b); */ static void on_process(void *userdata) { struct data *data = userdata; struct pw_buffer *b; struct spa_buffer *buf; int n_frames, stride; uint8_t *p; if ((b = pw_stream_dequeue_buffer(data->stream)) == NULL) { pw_log_warn("out of buffers: %m"); return; } buf = b->buffer; if ((p = buf->datas[0].data) == NULL) return; stride = sizeof(float) * DEFAULT_CHANNELS; n_frames = buf->datas[0].maxsize / stride; if (b->requested) n_frames = SPA_MIN((int)b->requested, n_frames); fill_f32(data, p, n_frames); buf->datas[0].chunk->offset = 0; buf->datas[0].chunk->stride = stride; buf->datas[0].chunk->size = n_frames * stride; pw_stream_queue_buffer(data->stream, b); } static const struct pw_stream_events stream_events = { PW_VERSION_STREAM_EVENTS, .process = on_process, }; static void do_quit(void *userdata, int signal_number) { struct data *data = userdata; pw_main_loop_quit(data->loop); } int main(int argc, char *argv[]) { struct data data = { 0, }; const struct spa_pod *params[1]; uint32_t n_params = 0; uint8_t buffer[1024]; struct pw_properties *props; struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); pw_init(&argc, &argv); /* make a main loop. If you already have another main loop, you can add * the fd of this pipewire mainloop to it. */ data.loop = pw_main_loop_new(NULL); pw_loop_add_signal(pw_main_loop_get_loop(data.loop), SIGINT, do_quit, &data); pw_loop_add_signal(pw_main_loop_get_loop(data.loop), SIGTERM, do_quit, &data); /* Create a simple stream, the simple stream manages the core and remote * objects for you if you don't need to deal with them. * * If you plan to autoconnect your stream, you need to provide at least * media, category and role properties. * * Pass your events and a user_data pointer as the last arguments. This * will inform you about the stream state. The most important event * you need to listen to is the process event where you need to produce * the data. */ props = pw_properties_new(PW_KEY_MEDIA_TYPE, "Audio", PW_KEY_MEDIA_CATEGORY, "Playback", PW_KEY_MEDIA_ROLE, "Music", NULL); if (argc > 1) /* Set stream target if given on command line */ pw_properties_set(props, PW_KEY_TARGET_OBJECT, argv[1]); data.stream = pw_stream_new_simple( pw_main_loop_get_loop(data.loop), "audio-src", props, &stream_events, &data); /* Make one parameter with the supported formats. The SPA_PARAM_EnumFormat * id means that this is a format enumeration (of 1 value). */ params[n_params++] = spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat, &SPA_AUDIO_INFO_RAW_INIT( .format = SPA_AUDIO_FORMAT_F32, .channels = DEFAULT_CHANNELS, .rate = DEFAULT_RATE )); /* Now connect this stream. We ask that our process function is * called in a realtime thread. */ pw_stream_connect(data.stream, PW_DIRECTION_OUTPUT, PW_ID_ANY, PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_MAP_BUFFERS | PW_STREAM_FLAG_RT_PROCESS, params, n_params); /* and wait while we let things run */ pw_main_loop_run(data.loop); pw_stream_destroy(data.stream); pw_main_loop_destroy(data.loop); pw_deinit(); return 0; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/examples/bluez-session.c000066400000000000000000000176161511204443500260640ustar00rootroot00000000000000/* PipeWire */ /* SPDX-FileCopyrightText: Copyright © 2019 Wim Taymans */ /* SPDX-License-Identifier: MIT */ /* [title] Using the \ref spa_device "SPA Device API", among other things. [title] */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "pipewire/pipewire.h" #define NAME "bluez-session" struct impl; struct object; struct node { struct impl *impl; struct object *object; struct spa_list link; uint32_t id; struct spa_handle *handle; struct pw_proxy *proxy; struct spa_node *node; }; struct object { struct impl *impl; struct spa_list link; uint32_t id; struct spa_handle *handle; struct pw_proxy *proxy; struct spa_device *device; struct spa_hook listener; struct spa_list node_list; }; struct impl { struct pw_main_loop *loop; struct pw_context *context; struct pw_core *core; struct spa_hook core_listener; struct spa_handle *handle; struct spa_device *device; struct spa_hook listener; struct spa_list device_list; struct pw_properties *props; }; static struct node *find_node(struct object *obj, uint32_t id) { struct node *node; spa_list_for_each(node, &obj->node_list, link) { if (node->id == id) return node; } return NULL; } static void update_node(struct object *obj, struct node *node, const struct spa_device_object_info *info) { pw_log_debug("update node %u", node->id); spa_debug_dict(0, info->props); } static struct node *create_node(struct object *obj, uint32_t id, const struct spa_device_object_info *info) { struct node *node; struct impl *impl = obj->impl; struct pw_context *context = impl->context; struct spa_handle *handle; int res; void *iface; pw_log_debug("new node %u", id); if (!spa_streq(info->type, SPA_TYPE_INTERFACE_Node)) return NULL; handle = pw_context_load_spa_handle(context, info->factory_name, info->props); if (handle == NULL) { pw_log_error("can't make factory instance: %m"); goto exit; } if ((res = spa_handle_get_interface(handle, info->type, &iface)) < 0) { pw_log_error("can't get %s interface: %s", info->type, spa_strerror(res)); goto unload_handle; } node = calloc(1, sizeof(*node)); if (node == NULL) goto unload_handle; node->impl = impl; node->object = obj; node->id = id; node->handle = handle; node->node = iface; node->proxy = pw_core_export(impl->core, info->type, info->props, node->node, 0); if (node->proxy == NULL) goto clean_node; spa_list_append(&obj->node_list, &node->link); update_node(obj, node, info); return node; clean_node: free(node); unload_handle: pw_unload_spa_handle(handle); exit: return NULL; } static void remove_node(struct object *obj, struct node *node) { pw_log_debug("remove node %u", node->id); spa_list_remove(&node->link); pw_proxy_destroy(node->proxy); free(node->handle); free(node); } static void device_object_info(void *data, uint32_t id, const struct spa_device_object_info *info) { struct object *obj = data; struct node *node; node = find_node(obj, id); if (info == NULL) { if (node == NULL) { pw_log_warn("object %p: unknown node %u", obj, id); return; } remove_node(obj, node); } else if (node == NULL) { create_node(obj, id, info); } else { update_node(obj, node, info); } } static const struct spa_device_events device_events = { SPA_VERSION_DEVICE_EVENTS, .object_info = device_object_info }; static struct object *find_object(struct impl *impl, uint32_t id) { struct object *obj; spa_list_for_each(obj, &impl->device_list, link) { if (obj->id == id) return obj; } return NULL; } static void update_object(struct impl *impl, struct object *obj, const struct spa_device_object_info *info) { pw_log_debug("update object %u", obj->id); spa_debug_dict(0, info->props); } static struct object *create_object(struct impl *impl, uint32_t id, const struct spa_device_object_info *info) { struct pw_context *context = impl->context; struct object *obj; struct spa_handle *handle; int res; void *iface; pw_log_debug("new object %u", id); if (!spa_streq(info->type, SPA_TYPE_INTERFACE_Device)) return NULL; handle = pw_context_load_spa_handle(context, info->factory_name, info->props); if (handle == NULL) { pw_log_error("can't make factory instance: %m"); goto exit; } if ((res = spa_handle_get_interface(handle, info->type, &iface)) < 0) { pw_log_error("can't get %s interface: %s", info->type, spa_strerror(res)); goto unload_handle; } obj = calloc(1, sizeof(*obj)); if (obj == NULL) goto unload_handle; obj->impl = impl; obj->id = id; obj->handle = handle; obj->device = iface; obj->proxy = pw_core_export(impl->core, info->type, info->props, obj->device, 0); if (obj->proxy == NULL) goto clean_object; spa_list_init(&obj->node_list); spa_device_add_listener(obj->device, &obj->listener, &device_events, obj); spa_list_append(&impl->device_list, &obj->link); update_object(impl, obj, info); return obj; clean_object: free(obj); unload_handle: pw_unload_spa_handle(handle); exit: return NULL; } static void remove_object(struct impl *impl, struct object *obj) { pw_log_debug("remove object %u", obj->id); spa_list_remove(&obj->link); spa_hook_remove(&obj->listener); pw_proxy_destroy(obj->proxy); pw_unload_spa_handle(obj->handle); free(obj); } static void dbus_device_object_info(void *data, uint32_t id, const struct spa_device_object_info *info) { struct impl *impl = data; struct object *obj; obj = find_object(impl, id); if (info == NULL) { if (obj == NULL) return; remove_object(impl, obj); } else if (obj == NULL) { if (create_object(impl, id, info) == NULL) return; } else { update_object(impl, obj, info); } } static const struct spa_device_events dbus_device_events = { SPA_VERSION_DEVICE_EVENTS, .object_info = dbus_device_object_info, }; static int start_monitor(struct impl *impl) { struct spa_handle *handle; int res; void *iface; handle = pw_context_load_spa_handle(impl->context, SPA_NAME_API_BLUEZ5_ENUM_DBUS, &impl->props->dict); if (handle == NULL) { res = -errno; goto out; } if ((res = spa_handle_get_interface(handle, SPA_TYPE_INTERFACE_Device, &iface)) < 0) { pw_log_error("can't get MONITOR interface: %d", res); goto out_unload; } impl->handle = handle; impl->device = iface; spa_device_add_listener(impl->device, &impl->listener, &dbus_device_events, impl); return 0; out_unload: pw_unload_spa_handle(handle); out: return res; } static void on_core_error(void *data, uint32_t id, int seq, int res, const char *message) { struct impl *impl = data; pw_log_error("error id:%u seq:%d res:%d (%s): %s", id, seq, res, spa_strerror(res), message); if (id == PW_ID_CORE && res == -EPIPE) pw_main_loop_quit(impl->loop); } static const struct pw_core_events core_events = { PW_VERSION_CORE_EVENTS, .error = on_core_error, }; int main(int argc, char *argv[]) { struct impl impl = { 0, }; int res; pw_init(&argc, &argv); impl.loop = pw_main_loop_new(NULL); impl.context = pw_context_new(pw_main_loop_get_loop(impl.loop), NULL, 0); spa_list_init(&impl.device_list); impl.core = pw_context_connect(impl.context, NULL, 0); if (impl.core == NULL) { pw_log_error(NAME" %p: can't connect %m", &impl); return -1; } if ((impl.props = pw_properties_new(NULL, NULL)) == NULL) { return -1; } pw_core_add_listener(impl.core, &impl.core_listener, &core_events, &impl); if ((res = start_monitor(&impl)) < 0) { pw_log_error(NAME" %p: error starting monitor: %s", &impl, spa_strerror(res)); return -1; } pw_main_loop_run(impl.loop); pw_context_destroy(impl.context); pw_main_loop_destroy(impl.loop); return 0; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/examples/export-sink.c000066400000000000000000000331441511204443500255370ustar00rootroot00000000000000/* PipeWire */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ /* [title] Exporting and implementing a video sink SPA node, using \ref api_pw_core. [title] */ #include #include #include #include #include #include #include #include #include #include #include #include #define WIDTH 640 #define HEIGHT 480 #define BPP 3 #define RATE 30 #include "sdl.h" #define M_PI_M2f (float)(M_PI+M_PI) #define MAX_BUFFERS 64 #define DEFAULT_PARAM 0.1 struct props { double param; }; static void reset_props(struct props *props) { props->param = DEFAULT_PARAM; } struct data { struct props props; const char *path; SDL_Renderer *renderer; SDL_Window *window; SDL_Texture *texture; struct pw_main_loop *loop; struct pw_context *context; struct pw_core *core; struct spa_hook core_listener; struct spa_node impl_node; struct spa_hook_list hooks; struct spa_io_buffers *io; struct spa_io_sequence *io_notify; uint32_t io_notify_size; float param_accum; uint8_t buffer[1024]; struct spa_video_info_raw format; int32_t stride; struct spa_port_info info; struct spa_param_info params[5]; struct spa_region region; struct spa_buffer *buffers[MAX_BUFFERS]; uint32_t n_buffers; }; static void handle_events(struct data *data) { SDL_Event event; while (SDL_PollEvent(&event)) { switch (event.type) { case SDL_QUIT: pw_main_loop_quit(data->loop); break; } } } static void update_param(struct data *data) { struct spa_pod_builder b = { 0, }; struct spa_pod_frame f[2]; if (data->io_notify == NULL) return; spa_pod_builder_init(&b, data->io_notify, data->io_notify_size); spa_pod_builder_push_sequence(&b, &f[0], 0); spa_pod_builder_control(&b, 0, SPA_CONTROL_Properties); spa_pod_builder_push_object(&b, &f[1], SPA_TYPE_OBJECT_Props, 0); spa_pod_builder_prop(&b, SPA_PROP_contrast, 0); spa_pod_builder_float(&b, (sinf(data->param_accum) * 127.0f) + 127.0f); spa_pod_builder_pop(&b, &f[1]); spa_pod_builder_pop(&b, &f[0]); data->param_accum += M_PI_M2f / 30.0f; if (data->param_accum >= M_PI_M2f) data->param_accum -= M_PI_M2f; } static int impl_send_command(void *object, const struct spa_command *command) { return 0; } static int impl_add_listener(void *object, struct spa_hook *listener, const struct spa_node_events *events, void *data) { struct data *d = object; struct spa_hook_list save; uint64_t old; spa_hook_list_isolate(&d->hooks, &save, listener, events, data); old = d->info.change_mask; d->info.change_mask = SPA_PORT_CHANGE_MASK_FLAGS | SPA_PORT_CHANGE_MASK_PARAMS; spa_node_emit_port_info(&d->hooks, SPA_DIRECTION_INPUT, 0, &d->info); d->info.change_mask = old; spa_hook_list_join(&d->hooks, &save); return 0; } static int impl_set_callbacks(void *object, const struct spa_node_callbacks *callbacks, void *data) { return 0; } static int impl_set_io(void *object, uint32_t id, void *data, size_t size) { return 0; } static int impl_port_set_io(void *object, enum spa_direction direction, uint32_t port_id, uint32_t id, void *data, size_t size) { struct data *d = object; switch (id) { case SPA_IO_Buffers: d->io = data; break; case SPA_IO_Notify: d->io_notify = data; d->io_notify_size = size; break; default: return -ENOENT; } return 0; } static int impl_port_enum_params(void *object, int seq, enum spa_direction direction, uint32_t port_id, uint32_t id, uint32_t start, uint32_t num, const struct spa_pod *filter) { struct data *d = object; struct spa_pod *param; struct spa_pod_builder b = { 0 }; uint8_t buffer[1024]; struct spa_result_node_params result; uint32_t count = 0; result.id = id; result.next = start; next: result.index = result.next++; spa_pod_builder_init(&b, buffer, sizeof(buffer)); switch (id) { case SPA_PARAM_EnumFormat: { SDL_RendererInfo info; if (result.index != 0) return 0; SDL_GetRendererInfo(d->renderer, &info); param = sdl_build_formats(&info, &b); break; } case SPA_PARAM_Format: if (result.index != 0 || d->format.format == 0) return 0; param = spa_format_video_raw_build(&b, id, &d->format); break; case SPA_PARAM_Buffers: if (result.index != 0) return 0; param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamBuffers, id, SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(2, 2, MAX_BUFFERS), SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(1), SPA_PARAM_BUFFERS_size, SPA_POD_Int(d->stride * d->format.size.height), SPA_PARAM_BUFFERS_stride, SPA_POD_Int(d->stride)); break; case SPA_PARAM_Meta: switch (result.index) { case 0: param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamMeta, id, SPA_PARAM_META_type, SPA_POD_Id(SPA_META_Header), SPA_PARAM_META_size, SPA_POD_Int(sizeof(struct spa_meta_header))); break; case 1: param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamMeta, id, SPA_PARAM_META_type, SPA_POD_Id(SPA_META_VideoDamage), SPA_PARAM_META_size, SPA_POD_Int(sizeof(struct spa_meta_region))); break; default: return 0; } break; case SPA_PARAM_IO: switch (result.index) { case 0: param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamIO, id, SPA_PARAM_IO_id, SPA_POD_Id(SPA_IO_Buffers), SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_buffers))); break; case 1: param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamIO, id, SPA_PARAM_IO_id, SPA_POD_Id(SPA_IO_Notify), SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_sequence) + 1024)); break; default: return 0; } break; default: return -ENOENT; } if (spa_pod_filter(&b, &result.param, param, filter) < 0) goto next; spa_node_emit_result(&d->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); if (++count != num) goto next; return 0; } static int port_set_format(void *object, enum spa_direction direction, uint32_t port_id, uint32_t flags, const struct spa_pod *format) { struct data *d = object; Uint32 sdl_format; void *dest; if (format == NULL) { spa_zero(d->format); SDL_DestroyTexture(d->texture); d->texture = NULL; } else { spa_debug_format(0, NULL, format); spa_format_video_raw_parse(format, &d->format); sdl_format = id_to_sdl_format(d->format.format); if (sdl_format == SDL_PIXELFORMAT_UNKNOWN) return -EINVAL; if (d->format.size.width == 0 || d->format.size.height == 0) return -EINVAL; d->texture = SDL_CreateTexture(d->renderer, sdl_format, SDL_TEXTUREACCESS_STREAMING, d->format.size.width, d->format.size.height); if (SDL_LockTexture(d->texture, NULL, &dest, &d->stride) < 0) return -EINVAL; SDL_UnlockTexture(d->texture); } d->info.change_mask = SPA_PORT_CHANGE_MASK_PARAMS; if (format) { d->params[3] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_READWRITE); d->params[4] = SPA_PARAM_INFO(SPA_PARAM_Buffers, SPA_PARAM_INFO_READ); } else { d->params[3] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); d->params[4] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); } spa_node_emit_port_info(&d->hooks, direction, port_id, &d->info); d->info.change_mask = 0; return 0; } static int impl_port_set_param(void *object, enum spa_direction direction, uint32_t port_id, uint32_t id, uint32_t flags, const struct spa_pod *param) { if (id == SPA_PARAM_Format) { return port_set_format(object, direction, port_id, flags, param); } else return -ENOENT; } static int impl_port_use_buffers(void *object, enum spa_direction direction, uint32_t port_id, uint32_t flags, struct spa_buffer **buffers, uint32_t n_buffers) { struct data *d = object; uint32_t i; if (n_buffers > MAX_BUFFERS) return -ENOSPC; for (i = 0; i < n_buffers; i++) d->buffers[i] = buffers[i]; d->n_buffers = n_buffers; return 0; } static int do_render(struct spa_loop *loop, bool async, uint32_t seq, const void *_data, size_t size, void *user_data) { struct data *d = user_data; const struct spa_buffer *buf = *(struct spa_buffer**)_data; uint8_t *map; void *sdata, *ddata; int sstride, dstride, ostride; uint32_t i; uint8_t *src, *dst; struct spa_meta *m; struct spa_meta_region *r; handle_events(d); if (buf->datas[0].type == SPA_DATA_MemFd || buf->datas[0].type == SPA_DATA_DmaBuf) { map = mmap(NULL, buf->datas[0].maxsize, PROT_READ, MAP_PRIVATE, buf->datas[0].fd, buf->datas[0].mapoffset); sdata = map; } else if (buf->datas[0].type == SPA_DATA_MemPtr) { map = NULL; sdata = buf->datas[0].data; } else return -EINVAL; if (SDL_LockTexture(d->texture, NULL, &ddata, &dstride) < 0) { fprintf(stderr, "Couldn't lock texture: %s\n", SDL_GetError()); return -EIO; } if ((m = spa_buffer_find_meta(buf, SPA_META_VideoDamage))) { spa_meta_for_each(r, m) { if (!spa_meta_region_is_valid(r)) break; if (memcmp(&r->region, &d->region, sizeof(struct spa_region)) == 0) break; d->region = r->region; fprintf(stderr, "region %dx%d->%dx%d\n", r->region.position.x, r->region.position.y, r->region.size.width, r->region.size.height); } } sstride = buf->datas[0].chunk->stride; ostride = SPA_MIN(sstride, dstride); src = sdata; dst = ddata; for (i = 0; i < d->format.size.height; i++) { memcpy(dst, src, ostride); src += sstride; dst += dstride; } SDL_UnlockTexture(d->texture); SDL_RenderClear(d->renderer); SDL_RenderCopy(d->renderer, d->texture, NULL, NULL); SDL_RenderPresent(d->renderer); if (map) munmap(map, buf->datas[0].maxsize); return 0; } static int impl_node_process(void *object) { struct data *d = object; struct spa_buffer *buf; int res; if (d->io->status != SPA_STATUS_HAVE_DATA) return SPA_STATUS_NEED_DATA; if (d->io->buffer_id >= d->n_buffers) return SPA_STATUS_NEED_DATA; buf = d->buffers[d->io->buffer_id]; if ((res = pw_loop_invoke(pw_main_loop_get_loop(d->loop), do_render, SPA_ID_INVALID, &buf, sizeof(struct spa_buffer *), false, d)) < 0) return res; update_param(d); return d->io->status = SPA_STATUS_NEED_DATA; } static const struct spa_node_methods impl_node = { SPA_VERSION_NODE_METHODS, .add_listener = impl_add_listener, .set_callbacks = impl_set_callbacks, .set_io = impl_set_io, .send_command = impl_send_command, .port_set_io = impl_port_set_io, .port_enum_params = impl_port_enum_params, .port_set_param = impl_port_set_param, .port_use_buffers = impl_port_use_buffers, .process = impl_node_process, }; static void make_node(struct data *data) { struct pw_properties *props; props = pw_properties_new(PW_KEY_NODE_AUTOCONNECT, "true", NULL); if (data->path) pw_properties_set(props, PW_KEY_TARGET_OBJECT, data->path); pw_properties_set(props, PW_KEY_MEDIA_CLASS, "Stream/Input/Video"); pw_properties_set(props, PW_KEY_MEDIA_TYPE, "Video"); pw_properties_set(props, PW_KEY_MEDIA_CATEGORY, "Capture"); pw_properties_set(props, PW_KEY_MEDIA_ROLE, "Camera"); data->impl_node.iface = SPA_INTERFACE_INIT( SPA_TYPE_INTERFACE_Node, SPA_VERSION_NODE, &impl_node, data); pw_core_export(data->core, SPA_TYPE_INTERFACE_Node, &props->dict, &data->impl_node, 0); pw_properties_free(props); } static void set_permissions(struct data *data) { struct pw_permission permissions[2]; /* an example, set specific permissions on one object, this is the * core object. */ permissions[0] = PW_PERMISSION_INIT(PW_ID_CORE, PW_PERM_R | PW_PERM_X); /* remove WX from all other objects */ permissions[1] = PW_PERMISSION_INIT(PW_ID_ANY, PW_PERM_R); pw_client_update_permissions( pw_core_get_client(data->core), 2, permissions); } static void on_core_error(void *data, uint32_t id, int seq, int res, const char *message) { struct data *d = data; pw_log_error("error id:%u seq:%d res:%d (%s): %s", id, seq, res, spa_strerror(res), message); if (id == PW_ID_CORE) pw_main_loop_quit(d->loop); } static const struct pw_core_events core_events = { PW_VERSION_CORE_EVENTS, .error = on_core_error, }; int main(int argc, char *argv[]) { struct data data = { 0, }; pw_init(&argc, &argv); data.loop = pw_main_loop_new(NULL); data.context = pw_context_new(pw_main_loop_get_loop(data.loop), NULL, 0); data.path = argc > 1 ? argv[1] : NULL; spa_hook_list_init(&data.hooks); data.info = SPA_PORT_INFO_INIT(); data.info.change_mask = SPA_PORT_CHANGE_MASK_FLAGS; data.info.flags = 0; data.info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; data.params[0] = SPA_PARAM_INFO(SPA_PARAM_EnumFormat, SPA_PARAM_INFO_READ); data.params[1] = SPA_PARAM_INFO(SPA_PARAM_Meta, SPA_PARAM_INFO_READ); data.params[2] = SPA_PARAM_INFO(SPA_PARAM_IO, SPA_PARAM_INFO_READ); data.params[3] = SPA_PARAM_INFO(SPA_PARAM_Buffers, SPA_PARAM_INFO_READ); data.params[4] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); data.info.params = data.params; data.info.n_params = 5; reset_props(&data.props); if (SDL_Init(SDL_INIT_VIDEO) < 0) { printf("can't initialize SDL: %s\n", SDL_GetError()); return -1; } if (SDL_CreateWindowAndRenderer (WIDTH, HEIGHT, SDL_WINDOW_RESIZABLE, &data.window, &data.renderer)) { printf("can't create window: %s\n", SDL_GetError()); return -1; } data.core = pw_context_connect(data.context, NULL, 0); if (data.core == NULL) { printf("can't connect: %m\n"); return -1; } pw_core_add_listener(data.core, &data.core_listener, &core_events, &data); set_permissions(&data); make_node(&data); pw_main_loop_run(data.loop); pw_context_destroy(data.context); pw_main_loop_destroy(data.loop); return 0; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/examples/export-source.c000066400000000000000000000330041511204443500260660ustar00rootroot00000000000000/* PipeWire */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ /* [title] Exporting and implementing a video source SPA node, using \ref api_pw_core. [title] */ #include #include #include #include #include #include #include #include #include #include #include #include #define M_PI_M2f (float)(M_PI + M_PI) #define BUFFER_SAMPLES 128 #define MAX_BUFFERS 32 struct buffer { uint32_t id; struct spa_buffer *buffer; struct spa_list link; void *ptr; bool mapped; }; struct data { const char *path; struct pw_main_loop *loop; struct pw_context *context; struct pw_core *core; struct spa_hook core_listener; uint64_t info_all; struct spa_port_info info; struct spa_dict_item items[1]; struct spa_dict dict; struct spa_param_info params[5]; struct spa_node impl_node; struct spa_hook_list hooks; struct spa_io_buffers *io; struct spa_io_control *io_notify; uint32_t io_notify_size; struct spa_audio_info_raw format; struct buffer buffers[MAX_BUFFERS]; uint32_t n_buffers; struct spa_list empty; float accumulator; float volume_accum; }; static void update_volume(struct data *data) { struct spa_pod_builder b = { 0, }; struct spa_pod_frame f[2]; if (data->io_notify == NULL) return; spa_pod_builder_init(&b, data->io_notify, data->io_notify_size); spa_pod_builder_push_sequence(&b, &f[0], 0); spa_pod_builder_control(&b, 0, SPA_CONTROL_Properties); spa_pod_builder_push_object(&b, &f[1], SPA_TYPE_OBJECT_Props, 0); spa_pod_builder_prop(&b, SPA_PROP_volume, 0); spa_pod_builder_float(&b, (sinf(data->volume_accum) / 2.0f) + 0.5f); spa_pod_builder_pop(&b, &f[1]); spa_pod_builder_pop(&b, &f[0]); data->volume_accum += M_PI_M2f / 1000.0f; if (data->volume_accum >= M_PI_M2f) data->volume_accum -= M_PI_M2f; } static int impl_send_command(void *object, const struct spa_command *command) { return 0; } static int impl_add_listener(void *object, struct spa_hook *listener, const struct spa_node_events *events, void *data) { struct data *d = object; struct spa_hook_list save; uint64_t old; spa_hook_list_isolate(&d->hooks, &save, listener, events, data); old = d->info.change_mask; d->info.change_mask = d->info_all; spa_node_emit_port_info(&d->hooks, SPA_DIRECTION_OUTPUT, 0, &d->info); d->info.change_mask = old; spa_hook_list_join(&d->hooks, &save); return 0; } static int impl_set_callbacks(void *object, const struct spa_node_callbacks *callbacks, void *data) { return 0; } static int impl_set_io(void *object, uint32_t id, void *data, size_t size) { return 0; } static int impl_port_set_io(void *object, enum spa_direction direction, uint32_t port_id, uint32_t id, void *data, size_t size) { struct data *d = object; switch (id) { case SPA_IO_Buffers: d->io = data; break; case SPA_IO_Notify: d->io_notify = data; d->io_notify_size = size; break; default: return -ENOENT; } return 0; } static int impl_port_enum_params(void *object, int seq, enum spa_direction direction, uint32_t port_id, uint32_t id, uint32_t start, uint32_t num, const struct spa_pod *filter) { struct data *d = object; struct spa_pod *param; struct spa_pod_builder b = { 0 }; uint8_t buffer[1024]; struct spa_result_node_params result; uint32_t count = 0; result.id = id; result.next = start; next: result.index = result.next++; spa_pod_builder_init(&b, buffer, sizeof(buffer)); switch (id) { case SPA_PARAM_EnumFormat: if (result.index != 0) return 0; param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_audio), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), SPA_FORMAT_AUDIO_format, SPA_POD_CHOICE_ENUM_Id(5, SPA_AUDIO_FORMAT_S16, SPA_AUDIO_FORMAT_S16P, SPA_AUDIO_FORMAT_S16, SPA_AUDIO_FORMAT_F32P, SPA_AUDIO_FORMAT_F32), SPA_FORMAT_AUDIO_channels, SPA_POD_CHOICE_RANGE_Int(2, 1, INT32_MAX), SPA_FORMAT_AUDIO_rate, SPA_POD_CHOICE_RANGE_Int(44100, 1, INT32_MAX)); break; case SPA_PARAM_Format: if (result.index != 0) return 0; if (d->format.format == 0) return 0; param = spa_format_audio_raw_build(&b, id, &d->format); break; case SPA_PARAM_Buffers: if (result.index > 0) return 0; param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamBuffers, id, SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(1, 1, 32), SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(1), SPA_PARAM_BUFFERS_size, SPA_POD_CHOICE_RANGE_Int( BUFFER_SAMPLES * sizeof(float), 32, INT32_MAX), SPA_PARAM_BUFFERS_stride, SPA_POD_Int(sizeof(float))); break; case SPA_PARAM_Meta: switch (result.index) { case 0: param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamMeta, id, SPA_PARAM_META_type, SPA_POD_Id(SPA_META_Header), SPA_PARAM_META_size, SPA_POD_Int(sizeof(struct spa_meta_header))); break; default: return 0; } break; case SPA_PARAM_IO: switch (result.index) { case 0: param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamIO, id, SPA_PARAM_IO_id, SPA_POD_Id(SPA_IO_Buffers), SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_buffers))); break; case 1: param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamIO, id, SPA_PARAM_IO_id, SPA_POD_Id(SPA_IO_Notify), SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_sequence) + 1024)); break; default: return 0; } break; default: return -ENOENT; } if (spa_pod_filter(&b, &result.param, param, filter) < 0) goto next; spa_node_emit_result(&d->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); if (++count != num) goto next; return 0; } static int port_set_format(void *object, enum spa_direction direction, uint32_t port_id, uint32_t flags, const struct spa_pod *format) { struct data *d = object; if (format == NULL) { spa_zero(d->format); } else { spa_debug_format(0, NULL, format); if (spa_format_audio_raw_parse(format, &d->format) < 0) return -EINVAL; if (d->format.format != SPA_AUDIO_FORMAT_S16 && d->format.format != SPA_AUDIO_FORMAT_F32) return -EINVAL; if (d->format.rate == 0 || d->format.channels == 0) return -EINVAL; } d->info.change_mask = SPA_PORT_CHANGE_MASK_PARAMS; if (format) { d->params[3] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_READWRITE); d->params[4] = SPA_PARAM_INFO(SPA_PARAM_Buffers, SPA_PARAM_INFO_READ); } else { d->params[3] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); d->params[4] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); } spa_node_emit_port_info(&d->hooks, SPA_DIRECTION_OUTPUT, 0, &d->info); return 0; } static int impl_port_set_param(void *object, enum spa_direction direction, uint32_t port_id, uint32_t id, uint32_t flags, const struct spa_pod *param) { if (id == SPA_PARAM_Format) { return port_set_format(object, direction, port_id, flags, param); } else return -ENOENT; } static int impl_port_use_buffers(void *object, enum spa_direction direction, uint32_t port_id, uint32_t flags, struct spa_buffer **buffers, uint32_t n_buffers) { struct data *d = object; uint32_t i; if (n_buffers > MAX_BUFFERS) return -ENOSPC; for (i = 0; i < n_buffers; i++) { struct buffer *b = &d->buffers[i]; struct spa_data *datas = buffers[i]->datas; if (datas[0].data != NULL) { b->ptr = datas[0].data; b->mapped = false; } else if (datas[0].type == SPA_DATA_MemFd || datas[0].type == SPA_DATA_DmaBuf) { b->ptr = mmap(NULL, datas[0].maxsize, PROT_WRITE, MAP_SHARED, datas[0].fd, datas[0].mapoffset); if (b->ptr == MAP_FAILED) { pw_log_error("failed to buffer mem"); return -errno; } b->mapped = true; } else { pw_log_error("invalid buffer mem"); return -EINVAL; } b->id = i; b->buffer = buffers[i]; pw_log_debug("got buffer %d size %d", i, datas[0].maxsize); spa_list_append(&d->empty, &b->link); } d->n_buffers = n_buffers; return 0; } static inline void reuse_buffer(struct data *d, uint32_t id) { pw_log_trace("export-source %p: recycle buffer %d", d, id); spa_list_append(&d->empty, &d->buffers[id].link); } static int impl_port_reuse_buffer(void *object, uint32_t port_id, uint32_t buffer_id) { struct data *d = object; reuse_buffer(d, buffer_id); return 0; } static void fill_f32(struct data *d, void *dest, int avail) { float *dst = dest; int n_samples = avail / (sizeof(float) * d->format.channels); int i; uint32_t c; for (i = 0; i < n_samples; i++) { float val; d->accumulator += M_PI_M2f * 440 / d->format.rate; if (d->accumulator >= M_PI_M2f) d->accumulator -= M_PI_M2f; val = sinf(d->accumulator); for (c = 0; c < d->format.channels; c++) *dst++ = val; } } static void fill_s16(struct data *d, void *dest, int avail) { int16_t *dst = dest; int n_samples = avail / (sizeof(int16_t) * d->format.channels); int i; uint32_t c; for (i = 0; i < n_samples; i++) { int16_t val; d->accumulator += M_PI_M2f * 440 / d->format.rate; if (d->accumulator >= M_PI_M2f) d->accumulator -= M_PI_M2f; val = (int16_t) (sinf(d->accumulator) * 32767.0f); for (c = 0; c < d->format.channels; c++) *dst++ = val; } } static int impl_node_process(void *object) { struct data *d = object; struct buffer *b; int avail; struct spa_io_buffers *io = d->io; uint32_t maxsize, index = 0; uint32_t filled, offset; struct spa_data *od; if (io->buffer_id < d->n_buffers) { reuse_buffer(d, io->buffer_id); io->buffer_id = SPA_ID_INVALID; } if (spa_list_is_empty(&d->empty)) { pw_log_error("export-source %p: out of buffers", d); return -EPIPE; } b = spa_list_first(&d->empty, struct buffer, link); spa_list_remove(&b->link); od = b->buffer->datas; maxsize = od[0].maxsize; filled = 0; index = 0; avail = maxsize - filled; offset = index % maxsize; if (offset + avail > maxsize) avail = maxsize - offset; if (d->format.format == SPA_AUDIO_FORMAT_S16) fill_s16(d, SPA_PTROFF(b->ptr, offset, void), avail); else if (d->format.format == SPA_AUDIO_FORMAT_F32) fill_f32(d, SPA_PTROFF(b->ptr, offset, void), avail); od[0].chunk->offset = 0; od[0].chunk->size = avail; od[0].chunk->stride = 0; io->buffer_id = b->id; io->status = SPA_STATUS_HAVE_DATA; update_volume(d); return SPA_STATUS_HAVE_DATA; } static const struct spa_node_methods impl_node = { SPA_VERSION_NODE_METHODS, .add_listener = impl_add_listener, .set_callbacks = impl_set_callbacks, .set_io = impl_set_io, .send_command = impl_send_command, .port_set_io = impl_port_set_io, .port_enum_params = impl_port_enum_params, .port_set_param = impl_port_set_param, .port_use_buffers = impl_port_use_buffers, .port_reuse_buffer = impl_port_reuse_buffer, .process = impl_node_process, }; static void make_node(struct data *data) { struct pw_properties *props; props = pw_properties_new(PW_KEY_NODE_AUTOCONNECT, "true", PW_KEY_NODE_EXCLUSIVE, "true", PW_KEY_MEDIA_TYPE, "Audio", PW_KEY_MEDIA_CATEGORY, "Playback", PW_KEY_MEDIA_ROLE, "Music", NULL); if (data->path) pw_properties_set(props, PW_KEY_TARGET_OBJECT, data->path); data->impl_node.iface = SPA_INTERFACE_INIT( SPA_TYPE_INTERFACE_Node, SPA_VERSION_NODE, &impl_node, data); pw_core_export(data->core, SPA_TYPE_INTERFACE_Node, &props->dict, &data->impl_node, 0); pw_properties_free(props); } static void on_core_error(void *data, uint32_t id, int seq, int res, const char *message) { struct data *d = data; pw_log_error("error id:%u seq:%d res:%d (%s): %s", id, seq, res, spa_strerror(res), message); if (id == PW_ID_CORE) pw_main_loop_quit(d->loop); } static const struct pw_core_events core_events = { PW_VERSION_CORE_EVENTS, .error = on_core_error, }; int main(int argc, char *argv[]) { struct data data = { 0, }; pw_init(&argc, &argv); data.loop = pw_main_loop_new(NULL); data.context = pw_context_new(pw_main_loop_get_loop(data.loop), NULL, 0); data.path = argc > 1 ? argv[1] : NULL; data.info_all = SPA_PORT_CHANGE_MASK_FLAGS | SPA_PORT_CHANGE_MASK_PROPS | SPA_PORT_CHANGE_MASK_PARAMS; data.info = SPA_PORT_INFO_INIT(); data.info.flags = 0; data.items[0] = SPA_DICT_ITEM_INIT(PW_KEY_FORMAT_DSP, "32 bit float mono audio"); data.dict = SPA_DICT_INIT_ARRAY(data.items); data.info.props = &data.dict; data.params[0] = SPA_PARAM_INFO(SPA_PARAM_EnumFormat, SPA_PARAM_INFO_READ); data.params[1] = SPA_PARAM_INFO(SPA_PARAM_Meta, SPA_PARAM_INFO_READ); data.params[2] = SPA_PARAM_INFO(SPA_PARAM_IO, SPA_PARAM_INFO_READ); data.params[3] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); data.params[4] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); data.info.params = data.params; data.info.n_params = 5; spa_list_init(&data.empty); spa_hook_list_init(&data.hooks); if ((data.core = pw_context_connect(data.context, NULL, 0)) == NULL) { printf("can't connect: %m\n"); return -1; } pw_core_add_listener(data.core, &data.core_listener, &core_events, &data); make_node(&data); pw_main_loop_run(data.loop); pw_context_destroy(data.context); pw_main_loop_destroy(data.loop); return 0; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/examples/export-spa-device.c000066400000000000000000000054601511204443500266130ustar00rootroot00000000000000/* PipeWire */ /* SPDX-FileCopyrightText: Copyright © 2019 Wim Taymans */ /* SPDX-License-Identifier: MIT */ /* [title] Exporting and loading a SPA device, using \ref api_pw_core. [title] */ #include #include #include #include #include #include #include struct data { struct pw_main_loop *loop; struct pw_context *context; struct pw_core *core; struct spa_hook core_listener; struct pw_impl_device *device; const char *library; const char *factory; const char *path; }; static int make_device(struct data *data) { struct pw_impl_factory *factory; struct pw_properties *props; factory = pw_context_find_factory(data->context, "spa-device-factory"); if (factory == NULL) return -1; props = pw_properties_new(SPA_KEY_LIBRARY_NAME, data->library, SPA_KEY_FACTORY_NAME, data->factory, NULL); data->device = pw_impl_factory_create_object(factory, NULL, PW_TYPE_INTERFACE_Device, PW_VERSION_DEVICE, props, SPA_ID_INVALID); pw_core_export(data->core, SPA_TYPE_INTERFACE_Device, NULL, pw_impl_device_get_implementation(data->device), 0); return 0; } static void on_core_error(void *data, uint32_t id, int seq, int res, const char *message) { struct data *d = data; pw_log_error("error id:%u seq:%d res:%d (%s): %s", id, seq, res, spa_strerror(res), message); if (id == PW_ID_CORE) pw_main_loop_quit(d->loop); } static const struct pw_core_events core_events = { PW_VERSION_CORE_EVENTS, .error = on_core_error, }; static void do_quit(void *data, int signal_number) { struct data *d = data; pw_main_loop_quit(d->loop); } int main(int argc, char *argv[]) { struct data data = { 0, }; struct pw_loop *l; pw_init(&argc, &argv); if (argc < 3) { fprintf(stderr, "usage: %s \n\n" "\texample: %s v4l2/libspa-v4l2 api.v4l2.device\n\n", argv[0], argv[0]); return -1; } data.loop = pw_main_loop_new(NULL); l = pw_main_loop_get_loop(data.loop); pw_loop_add_signal(l, SIGINT, do_quit, &data); pw_loop_add_signal(l, SIGTERM, do_quit, &data); data.context = pw_context_new(l, NULL, 0); data.library = argv[1]; data.factory = argv[2]; pw_context_load_module(data.context, "libpipewire-module-spa-device-factory", NULL, NULL); data.core = pw_context_connect(data.context, NULL, 0); if (data.core == NULL) { pw_log_error("can't connect %m"); return -1; } pw_core_add_listener(data.core, &data.core_listener, &core_events, &data); if (make_device(&data) < 0) { pw_log_error("can't make device"); return -1; } pw_main_loop_run(data.loop); pw_context_destroy(data.context); pw_main_loop_destroy(data.loop); return 0; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/examples/export-spa.c000066400000000000000000000070751511204443500253620ustar00rootroot00000000000000/* PipeWire */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ /* [title] Exporting and loading a SPA node, using \ref api_pw_core. [title] */ #include #include #include #include #include #include #include struct data { struct pw_main_loop *loop; struct pw_context *context; struct pw_core *core; struct spa_hook core_listener; struct spa_node *node; const char *library; const char *factory; const char *path; struct pw_proxy *proxy; struct spa_hook proxy_listener; uint32_t id; }; static void proxy_event_bound_props(void *_data, uint32_t global_id, const struct spa_dict *props) { struct data *data = _data; if (data->id != global_id) { printf("node id: %u\n", global_id); data->id = global_id; } } static const struct pw_proxy_events proxy_events = { PW_VERSION_PROXY_EVENTS, .bound_props = proxy_event_bound_props, }; static int make_node(struct data *data) { struct pw_properties *props; struct spa_handle *hndl; void *iface; int res; props = pw_properties_new(SPA_KEY_LIBRARY_NAME, data->library, SPA_KEY_FACTORY_NAME, data->factory, NULL); hndl = pw_context_load_spa_handle(data->context, data->factory, &props->dict); if (hndl == NULL) return -errno; if ((res = spa_handle_get_interface(hndl, SPA_TYPE_INTERFACE_Node, &iface)) < 0) return res; data->node = iface; if (data->path) { pw_properties_set(props, PW_KEY_NODE_AUTOCONNECT, "true"); pw_properties_set(props, PW_KEY_TARGET_OBJECT, data->path); } data->proxy = pw_core_export(data->core, SPA_TYPE_INTERFACE_Node, &props->dict, data->node, 0); pw_properties_free(props); if (data->proxy == NULL) return -errno; pw_proxy_add_listener(data->proxy, &data->proxy_listener, &proxy_events, data); return 0; } static void on_core_error(void *data, uint32_t id, int seq, int res, const char *message) { struct data *d = data; pw_log_error("error id:%u seq:%d res:%d (%s): %s", id, seq, res, spa_strerror(res), message); if (id == PW_ID_CORE) pw_main_loop_quit(d->loop); } static const struct pw_core_events core_events = { PW_VERSION_CORE_EVENTS, .error = on_core_error, }; static void do_quit(void *data, int signal_number) { struct data *d = data; pw_main_loop_quit(d->loop); } int main(int argc, char *argv[]) { struct data data = { 0, }; struct pw_loop *l; pw_init(&argc, &argv); if (argc < 3) { fprintf(stderr, "usage: %s [path]\n\n" "\texample: %s v4l2/libspa-v4l2 api.v4l2.source\n\n", argv[0], argv[0]); return -1; } data.loop = pw_main_loop_new(NULL); l = pw_main_loop_get_loop(data.loop); pw_loop_add_signal(l, SIGINT, do_quit, &data); pw_loop_add_signal(l, SIGTERM, do_quit, &data); data.context = pw_context_new(l, NULL, 0); data.library = argv[1]; data.factory = argv[2]; if (argc > 3) data.path = argv[3]; pw_context_load_module(data.context, "libpipewire-module-spa-node-factory", NULL, NULL); data.core = pw_context_connect(data.context, NULL, 0); if (data.core == NULL) { printf("can't connect: %m\n"); return -1; } pw_core_add_listener(data.core, &data.core_listener, &core_events, &data); if (make_node(&data) < 0) { pw_log_error("can't make node"); return -1; } pw_main_loop_run(data.loop); pw_proxy_destroy(data.proxy); pw_core_disconnect(data.core); pw_context_destroy(data.context); pw_main_loop_destroy(data.loop); return 0; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/examples/gmain.c000066400000000000000000000046161511204443500243510ustar00rootroot00000000000000 #include #include #include typedef struct _PipeWireSource { GSource base; struct pw_loop *loop; } PipeWireSource; static gboolean pipewire_loop_source_dispatch (GSource *source, GSourceFunc callback, gpointer user_data) { PipeWireSource *s = (PipeWireSource *) source; int result; result = pw_loop_iterate (s->loop, 0); if (result < 0) g_warning ("pipewire_loop_iterate failed: %s", spa_strerror (result)); return TRUE; } static GSourceFuncs pipewire_source_funcs = { .dispatch = pipewire_loop_source_dispatch, }; static void registry_event_global(void *data, uint32_t id, uint32_t permissions, const char *type, uint32_t version, const struct spa_dict *props) { printf("object: id:%u type:%s/%d\n", id, type, version); } static const struct pw_registry_events registry_events = { PW_VERSION_REGISTRY_EVENTS, .global = registry_event_global, }; int main(int argc, char *argv[]) { GMainLoop *main_loop; PipeWireSource *source; struct pw_loop *loop; struct pw_context *context; struct pw_core *core; struct pw_registry *registry; struct spa_hook registry_listener; main_loop = g_main_loop_new (NULL, FALSE); pw_init(&argc, &argv); loop = pw_loop_new(NULL /* properties */); /* wrap */ source = (PipeWireSource *) g_source_new (&pipewire_source_funcs, sizeof (PipeWireSource)); source->loop = loop; g_source_add_unix_fd (&source->base, pw_loop_get_fd (loop), G_IO_IN | G_IO_ERR); g_source_attach (&source->base, NULL); g_source_unref (&source->base); context = pw_context_new(loop, NULL /* properties */, 0 /* user_data size */); core = pw_context_connect(context, NULL /* properties */, 0 /* user_data size */); registry = pw_core_get_registry(core, PW_VERSION_REGISTRY, 0 /* user_data size */); spa_zero(registry_listener); pw_registry_add_listener(registry, ®istry_listener, ®istry_events, NULL); /* enter and leave must be called from the same thread that runs * the mainloop */ pw_loop_enter(loop); g_main_loop_run(main_loop); pw_loop_leave(loop); pw_proxy_destroy((struct pw_proxy*)registry); pw_core_disconnect(core); pw_context_destroy(context); pw_loop_destroy(loop); g_main_loop_unref(main_loop); return 0; } /* [code] */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/examples/internal.c000066400000000000000000000061221511204443500250640ustar00rootroot00000000000000/* PipeWire */ /* SPDX-FileCopyrightText: Copyright © 2023 Wim Taymans */ /* SPDX-License-Identifier: MIT */ /* [title] In process pipewire graph [title] */ #include #include #include #include #include #include #include struct data { struct pw_main_loop *loop; struct pw_context *context; struct pw_core *core; struct pw_proxy *source; struct pw_proxy *sink; struct pw_proxy *link; int res; }; static void do_quit(void *userdata, int signal_number) { struct data *data = userdata; pw_main_loop_quit(data->loop); } int main(int argc, char *argv[]) { struct data data = { 0, }; struct pw_loop *loop; struct pw_properties *props; const char *dev = "hw:0"; pw_init(&argc, &argv); data.loop = pw_main_loop_new(NULL); loop = pw_main_loop_get_loop(data.loop); if (argc > 1) dev = argv[1]; pw_loop_add_signal(loop, SIGINT, do_quit, &data); pw_loop_add_signal(loop, SIGTERM, do_quit, &data); data.context = pw_context_new(loop, NULL, 0); pw_context_load_module(data.context, "libpipewire-module-spa-node-factory", NULL, NULL); pw_context_load_module(data.context, "libpipewire-module-link-factory", NULL, NULL); data.core = pw_context_connect_self(data.context, NULL, 0); if (data.core == NULL) { fprintf(stderr, "can't connect: %m\n"); data.res = -errno; goto cleanup; } props = pw_properties_new( SPA_KEY_LIBRARY_NAME, "audiotestsrc/libspa-audiotestsrc", SPA_KEY_FACTORY_NAME, "audiotestsrc", PW_KEY_NODE_NAME, "test_source", "node.param.Props", "{ live = false }", NULL); data.source = pw_core_create_object(data.core, "spa-node-factory", PW_TYPE_INTERFACE_Node, PW_VERSION_NODE, &props->dict, 0); pw_properties_free(props); props = pw_properties_new( SPA_KEY_LIBRARY_NAME, "alsa/libspa-alsa", SPA_KEY_FACTORY_NAME, SPA_NAME_API_ALSA_PCM_SINK, PW_KEY_NODE_NAME, "alsa_sink", "api.alsa.path", dev, "priority.driver", "1000", NULL); data.sink = pw_core_create_object(data.core, "spa-node-factory", PW_TYPE_INTERFACE_Node, PW_VERSION_NODE, &props->dict, 0); pw_loop_enter(loop); while (true) { if (pw_proxy_get_bound_id(data.source) != SPA_ID_INVALID && pw_proxy_get_bound_id(data.sink) != SPA_ID_INVALID) break; pw_loop_iterate(loop, -1); } pw_loop_leave(loop); pw_properties_clear(props); pw_properties_setf(props, PW_KEY_LINK_OUTPUT_NODE, "%d", pw_proxy_get_bound_id(data.source)); pw_properties_setf(props, PW_KEY_LINK_INPUT_NODE, "%d", pw_proxy_get_bound_id(data.sink)); data.link = pw_core_create_object(data.core, "link-factory", PW_TYPE_INTERFACE_Link, PW_VERSION_LINK, &props->dict, 0); pw_properties_free(props); pw_main_loop_run(data.loop); cleanup: pw_context_destroy(data.context); pw_main_loop_destroy(data.loop); pw_deinit(); return data.res; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/examples/local-v4l2.c000066400000000000000000000246611511204443500251370ustar00rootroot00000000000000/* PipeWire */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ /* [title] Using libspa-v4l2 [title] */ #include #include #define WIDTH 640 #define HEIGHT 480 #define RATE 30 #define BPP 3 #define MAX_BUFFERS 32 #include "sdl.h" #include #include #include #include #include #include #include #include struct data { SDL_Renderer *renderer; SDL_Window *window; SDL_Texture *texture; struct pw_main_loop *main_loop; struct pw_loop *loop; struct pw_context *context; struct pw_core *core; struct spa_port_info info; struct spa_param_info params[4]; struct spa_node impl_node; struct spa_io_buffers *io; struct spa_hook_list hooks; struct spa_video_info_raw format; int32_t stride; struct spa_buffer *buffers[MAX_BUFFERS]; int n_buffers; struct pw_proxy *out, *in, *link; }; static void handle_events(struct data *data) { SDL_Event event; while (SDL_PollEvent(&event)) { switch (event.type) { case SDL_QUIT: pw_main_loop_quit(data->main_loop); break; } } } static int impl_set_io(void *object, uint32_t id, void *data, size_t size) { return 0; } static int impl_send_command(void *object, const struct spa_command *command) { return 0; } static int impl_add_listener(void *object, struct spa_hook *listener, const struct spa_node_events *events, void *data) { struct data *d = object; struct spa_hook_list save; spa_hook_list_isolate(&d->hooks, &save, listener, events, data); spa_node_emit_port_info(&d->hooks, SPA_DIRECTION_INPUT, 0, &d->info); spa_hook_list_join(&d->hooks, &save); return 0; } static int impl_set_callbacks(void *object, const struct spa_node_callbacks *callbacks, void *data) { return 0; } static int impl_port_set_io(void *object, enum spa_direction direction, uint32_t port_id, uint32_t id, void *data, size_t size) { struct data *d = object; if (id == SPA_IO_Buffers) d->io = data; else return -ENOENT; return 0; } static int impl_port_enum_params(void *object, int seq, enum spa_direction direction, uint32_t port_id, uint32_t id, uint32_t start, uint32_t num, const struct spa_pod *filter) { struct data *d = object; struct spa_pod *param; struct spa_pod_builder b = { 0 }; uint8_t buffer[1024]; struct spa_result_node_params result; uint32_t count = 0; result.id = id; result.next = start; next: result.index = result.next++; spa_pod_builder_init(&b, buffer, sizeof(buffer)); switch (id) { case SPA_PARAM_EnumFormat: { SDL_RendererInfo info; if (result.index > 0) return 0; SDL_GetRendererInfo(d->renderer, &info); param = sdl_build_formats(&info, &b); break; } case SPA_PARAM_Buffers: if (result.index > 0) return 0; param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamBuffers, id, SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(2, 1, 32), SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(1), SPA_PARAM_BUFFERS_size, SPA_POD_Int(d->stride * d->format.size.height), SPA_PARAM_BUFFERS_stride, SPA_POD_Int(d->stride)); break; case SPA_PARAM_Meta: if (result.index > 0) return 0; param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamMeta, id, SPA_PARAM_META_type, SPA_POD_Id(SPA_META_Header), SPA_PARAM_META_size, SPA_POD_Int(sizeof(struct spa_meta_header))); break; default: return -ENOENT; } if (spa_pod_filter(&b, &result.param, param, filter) < 0) goto next; spa_node_emit_result(&d->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); if (++count != num) goto next; return 0; } static int port_set_format(void *object, enum spa_direction direction, uint32_t port_id, uint32_t flags, const struct spa_pod *format) { struct data *d = object; Uint32 sdl_format; void *dest; if (format == NULL) { spa_zero(d->format); SDL_DestroyTexture(d->texture); d->texture = NULL; } else { spa_debug_format(0, NULL, format); spa_format_video_raw_parse(format, &d->format); sdl_format = id_to_sdl_format(d->format.format); if (sdl_format == SDL_PIXELFORMAT_UNKNOWN) return -EINVAL; if (d->format.size.width == 0 || d->format.size.height == 0) return -EINVAL; d->texture = SDL_CreateTexture(d->renderer, sdl_format, SDL_TEXTUREACCESS_STREAMING, d->format.size.width, d->format.size.height); if (SDL_LockTexture(d->texture, NULL, &dest, &d->stride) < 0) return -EINVAL; SDL_UnlockTexture(d->texture); } d->info.change_mask = SPA_PORT_CHANGE_MASK_PARAMS; if (format) { d->params[1] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_READWRITE); d->params[2] = SPA_PARAM_INFO(SPA_PARAM_Buffers, SPA_PARAM_INFO_READ); } else { d->params[1] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); d->params[2] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); } spa_node_emit_port_info(&d->hooks, SPA_DIRECTION_INPUT, 0, &d->info); return 0; } static int impl_port_set_param(void *object, enum spa_direction direction, uint32_t port_id, uint32_t id, uint32_t flags, const struct spa_pod *param) { if (id == SPA_PARAM_Format) { return port_set_format(object, direction, port_id, flags, param); } else return -ENOENT; } static int impl_port_use_buffers(void *object, enum spa_direction direction, uint32_t port_id, uint32_t flags, struct spa_buffer **buffers, uint32_t n_buffers) { struct data *d = object; uint32_t i; if (n_buffers > MAX_BUFFERS) return -ENOSPC; for (i = 0; i < n_buffers; i++) d->buffers[i] = buffers[i]; d->n_buffers = n_buffers; return 0; } static int do_render(struct spa_loop *loop, bool async, uint32_t seq, const void *_data, size_t size, void *user_data) { struct data *d = user_data; struct spa_buffer *buf; uint8_t *map; void *sdata, *ddata; int sstride, dstride, ostride; uint32_t i; uint8_t *src, *dst; buf = d->buffers[d->io->buffer_id]; if (buf->datas[0].type == SPA_DATA_MemFd || buf->datas[0].type == SPA_DATA_DmaBuf) { map = mmap(NULL, buf->datas[0].maxsize, PROT_READ, MAP_PRIVATE, buf->datas[0].fd, buf->datas[0].mapoffset); sdata = map; } else if (buf->datas[0].type == SPA_DATA_MemPtr) { map = NULL; sdata = buf->datas[0].data; } else return -EINVAL; if (SDL_LockTexture(d->texture, NULL, &ddata, &dstride) < 0) { fprintf(stderr, "Couldn't lock texture: %s\n", SDL_GetError()); return -EIO; } sstride = buf->datas[0].chunk->stride; ostride = SPA_MIN(sstride, dstride); src = sdata; dst = ddata; for (i = 0; i < d->format.size.height; i++) { memcpy(dst, src, ostride); src += sstride; dst += dstride; } SDL_UnlockTexture(d->texture); SDL_RenderClear(d->renderer); SDL_RenderCopy(d->renderer, d->texture, NULL, NULL); SDL_RenderPresent(d->renderer); if (map) munmap(map, buf->datas[0].maxsize); return 0; } static int impl_node_process(void *object) { struct data *d = object; int res; if ((res = pw_loop_invoke(d->loop, do_render, SPA_ID_INVALID, NULL, 0, true, d)) < 0) return res; handle_events(d); d->io->status = SPA_STATUS_NEED_DATA; return SPA_STATUS_NEED_DATA; } static const struct spa_node_methods impl_node = { SPA_VERSION_NODE_METHODS, .add_listener = impl_add_listener, .set_callbacks = impl_set_callbacks, .set_io = impl_set_io, .send_command = impl_send_command, .port_set_io = impl_port_set_io, .port_enum_params = impl_port_enum_params, .port_set_param = impl_port_set_param, .port_use_buffers = impl_port_use_buffers, .process = impl_node_process, }; static int make_nodes(struct data *data) { struct pw_properties *props; data->impl_node.iface = SPA_INTERFACE_INIT( SPA_TYPE_INTERFACE_Node, SPA_VERSION_NODE, &impl_node, data); data->info = SPA_PORT_INFO_INIT(); data->info.change_mask = SPA_PORT_CHANGE_MASK_FLAGS | SPA_PORT_CHANGE_MASK_PARAMS; data->info.flags = 0; data->params[0] = SPA_PARAM_INFO(SPA_PARAM_EnumFormat, SPA_PARAM_INFO_READ); data->params[1] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); data->params[2] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0); data->params[3] = SPA_PARAM_INFO(SPA_PARAM_Meta, SPA_PARAM_INFO_READ); data->info.params = data->params; data->info.n_params = SPA_N_ELEMENTS(data->params); data->in = pw_core_export(data->core, SPA_TYPE_INTERFACE_Node, NULL, &data->impl_node, 0); props = pw_properties_new( SPA_KEY_LIBRARY_NAME, "v4l2/libspa-v4l2", SPA_KEY_FACTORY_NAME, SPA_NAME_API_V4L2_SOURCE, NULL); data->out = pw_core_create_object(data->core, "spa-node-factory", PW_TYPE_INTERFACE_Node, PW_VERSION_NODE, &props->dict, 0); pw_loop_enter(data->loop); while (true) { if (pw_proxy_get_bound_id(data->out) != SPA_ID_INVALID && pw_proxy_get_bound_id(data->in) != SPA_ID_INVALID) break; pw_loop_iterate(data->loop, -1); } pw_loop_leave(data->loop); pw_properties_clear(props); pw_properties_setf(props, PW_KEY_LINK_OUTPUT_NODE, "%d", pw_proxy_get_bound_id(data->out)); pw_properties_setf(props, PW_KEY_LINK_INPUT_NODE, "%d", pw_proxy_get_bound_id(data->in)); data->link = pw_core_create_object(data->core, "link-factory", PW_TYPE_INTERFACE_Link, PW_VERSION_LINK, &props->dict, 0); pw_properties_free(props); return 0; } int main(int argc, char *argv[]) { struct data data = { 0, }; pw_init(&argc, &argv); data.main_loop = pw_main_loop_new(NULL); data.loop = pw_main_loop_get_loop(data.main_loop); data.context = pw_context_new( data.loop, pw_properties_new( PW_KEY_CORE_DAEMON, "false", NULL), 0); spa_hook_list_init(&data.hooks); pw_context_load_module(data.context, "libpipewire-module-spa-node-factory", NULL, NULL); pw_context_load_module(data.context, "libpipewire-module-link-factory", NULL, NULL); if (SDL_Init(SDL_INIT_VIDEO) < 0) { printf("can't initialize SDL: %s\n", SDL_GetError()); return -1; } if (SDL_CreateWindowAndRenderer (WIDTH, HEIGHT, SDL_WINDOW_RESIZABLE, &data.window, &data.renderer)) { printf("can't create window: %s\n", SDL_GetError()); return -1; } data.core = pw_context_connect_self(data.context, NULL, 0); if (data.core == NULL) { printf("can't connect to core: %m\n"); return -1; } make_nodes(&data); pw_main_loop_run(data.main_loop); pw_proxy_destroy(data.link); pw_proxy_destroy(data.in); pw_proxy_destroy(data.out); pw_context_destroy(data.context); pw_main_loop_destroy(data.main_loop); pw_deinit(); return 0; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/examples/meson.build000066400000000000000000000025441511204443500252520ustar00rootroot00000000000000# Examples, in order from simple to complicated examples = [ 'audio-src', 'audio-src-ring', 'audio-src-ring2', 'audio-dsp-src', 'audio-dsp-filter', 'audio-dsp-sink', 'audio-dsp-sink2', 'audio-capture', 'video-play', 'video-src', 'video-src-sync', 'video-dsp-play', 'video-dsp-src', 'video-play-pull', 'video-play-reneg', 'video-play-sync', 'video-src-alloc', 'video-src-reneg', 'video-src-fixate', 'video-play-fixate', 'midi-src', 'internal', 'export-sink', 'export-source', 'export-spa', 'export-spa-device', 'bluez-session', 'local-v4l2', 'gmain', ] if not get_option('examples').allowed() subdir_done() endif examples_extra_deps = { 'video-src-fixate': [drm_dep], 'video-play': [sdl_dep], 'video-play-reneg': [sdl_dep], 'video-play-fixate': [sdl_dep, drm_dep], 'video-play-pull': [sdl_dep], 'video-play-sync': [sdl_dep], 'video-dsp-play': [sdl_dep], 'local-v4l2': [sdl_dep], 'export-sink': [sdl_dep], 'gmain': [glib2_dep], } foreach c : examples deps = examples_extra_deps.get(c, []) found = true foreach dep : deps found = found and dep.found() endforeach if found executable( c, c + '.c', install : installed_tests_enabled, install_dir : installed_tests_execdir / 'examples', dependencies : [pipewire_dep, mathlib] + deps, ) endif endforeach pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/examples/midi-src.c000066400000000000000000000163661511204443500247720ustar00rootroot00000000000000/* PipeWire */ /* SPDX-FileCopyrightText: Copyright © 2024 Pauli Virtanen */ /* SPDX-License-Identifier: MIT */ /* [title] MIDI source using \ref pw_filter "pw_filter". [title] */ #include #include #include #include #include #include #include #include #include #define PERIOD_NSEC (SPA_NSEC_PER_SEC/8) struct port { }; struct data { struct pw_main_loop *loop; struct pw_filter *filter; struct port *port; uint32_t clock_id; int64_t offset; uint64_t position; }; static void on_process(void *userdata, struct spa_io_position *position) { struct data *data = userdata; struct port *port = data->port; struct pw_buffer *buf; struct spa_data *d; struct spa_pod_builder builder; struct spa_pod_frame frame; uint64_t sample_offset, sample_period, sample_position, cycle; /* * Use the clock sample position. * * If the playback switches to using a different clock, we reset * playback as the sample position can then be discontinuous. */ if (data->clock_id != position->clock.id) { pw_log_info("switch to clock %u", position->clock.id); data->offset = position->clock.position - data->position; data->clock_id = position->clock.id; } sample_position = position->clock.position - data->offset; data->position = sample_position + position->clock.duration; /* * Produce note on/off every `PERIOD_NSEC` nanoseconds (rounded down to * samples, for simplicity). * * We want to place the notes on the playback timeline, so we use sample * positions (not real time!). */ sample_period = PERIOD_NSEC * position->clock.rate.denom / position->clock.rate.num / SPA_NSEC_PER_SEC; cycle = sample_position / sample_period; if (sample_position % sample_period != 0) ++cycle; sample_offset = cycle*sample_period - sample_position; if (sample_offset >= position->clock.duration) return; /* don't need to produce anything yet */ /* Get output buffer */ if ((buf = pw_filter_dequeue_buffer(port)) == NULL) return; /* Midi buffers always have exactly one data block */ spa_assert(buf->buffer->n_datas == 1); d = &buf->buffer->datas[0]; d->chunk->offset = 0; d->chunk->size = 0; d->chunk->stride = 1; d->chunk->flags = 0; /* * MIDI buffers contain a SPA POD with a sequence of * control messages and their raw MIDI data. */ spa_pod_builder_init(&builder, d->data, d->maxsize); spa_pod_builder_push_sequence(&builder, &frame, 0); while (sample_offset < position->clock.duration) { if (cycle % 2 == 0) { /* MIDI note on, channel 0, middle C, max velocity */ uint32_t event = 0x20903c7f; /* The time position of the message in the graph cycle * is given as offset from the cycle start, in * samples. The cycle has duration of `clock.duration` * samples, and the sample offset should satisfy * 0 <= sample_offset < position->clock.duration. */ spa_pod_builder_control(&builder, sample_offset, SPA_CONTROL_UMP); /* Raw MIDI data for the message */ spa_pod_builder_bytes(&builder, &event, sizeof(event)); pw_log_info("note on at %"PRIu64, sample_position + sample_offset); } else { /* MIDI note off, channel 0, middle C, max velocity */ uint32_t event = 0x20803c7f; spa_pod_builder_control(&builder, sample_offset, SPA_CONTROL_UMP); spa_pod_builder_bytes(&builder, &event, sizeof(event)); pw_log_info("note off at %"PRIu64, sample_position + sample_offset); } sample_offset += sample_period; ++cycle; } /* * Finish the sequence and queue buffer to output. */ spa_pod_builder_pop(&builder, &frame); d->chunk->size = builder.state.offset; pw_log_trace("produced %u/%u bytes", d->chunk->size, d->maxsize); pw_filter_queue_buffer(port, buf); } static void state_changed(void *userdata, enum pw_filter_state old, enum pw_filter_state state, const char *error) { struct data *data = userdata; switch (state) { case PW_FILTER_STATE_STREAMING: /* reset playback position */ pw_log_info("start playback"); data->clock_id = SPA_ID_INVALID; data->offset = 0; data->position = 0; break; default: break; } } static const struct pw_filter_events filter_events = { PW_VERSION_FILTER_EVENTS, .process = on_process, .state_changed = state_changed, }; static void do_quit(void *userdata, int signal_number) { struct data *data = userdata; pw_main_loop_quit(data->loop); } int main(int argc, char *argv[]) { struct data data = {}; uint8_t buffer[1024]; struct spa_pod_builder builder; struct spa_pod *params[1]; uint32_t n_params = 0; pw_init(&argc, &argv); /* make a main loop. If you already have another main loop, you can add * the fd of this pipewire mainloop to it. */ data.loop = pw_main_loop_new(NULL); pw_loop_add_signal(pw_main_loop_get_loop(data.loop), SIGINT, do_quit, &data); pw_loop_add_signal(pw_main_loop_get_loop(data.loop), SIGTERM, do_quit, &data); /* Create a simple filter, the simple filter manages the core and remote * objects for you if you don't need to deal with them. * * Pass your events and a user_data pointer as the last arguments. This * will inform you about the filter state. The most important event * you need to listen to is the process event where you need to process * the data. */ data.filter = pw_filter_new_simple( pw_main_loop_get_loop(data.loop), "midi-src", pw_properties_new( PW_KEY_MEDIA_TYPE, "Midi", PW_KEY_MEDIA_CATEGORY, "Playback", PW_KEY_MEDIA_CLASS, "Midi/Source", NULL), &filter_events, &data); /* Make a midi output port */ data.port = pw_filter_add_port(data.filter, PW_DIRECTION_OUTPUT, PW_FILTER_PORT_FLAG_MAP_BUFFERS, sizeof(struct port), pw_properties_new( PW_KEY_FORMAT_DSP, "32 bit raw UMP", PW_KEY_PORT_NAME, "output", NULL), NULL, 0); /* Update SPA_PARAM_Buffers to request a specific sizes and counts. * This is not mandatory: if you skip this, you'll get default sized * buffers, usually 4k or 32k bytes or so. * * We'll here ask for 4096 bytes as that's enough. */ spa_pod_builder_init(&builder, buffer, sizeof(buffer)); params[n_params++] = spa_pod_builder_add_object(&builder, /* POD Object for the buffer parameter */ SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers, /* Default 1 buffer, minimum of 1, max of 32 buffers. * We can do with 1 buffer as we dequeue and queue in the same * cycle. */ SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(1, 1, 32), /* MIDI buffers always have 1 data block */ SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(1), /* Buffer size: request default 4096 bytes, min 4096, no maximum */ SPA_PARAM_BUFFERS_size, SPA_POD_CHOICE_RANGE_Int(4096, 4096, INT32_MAX), /* MIDI buffers have stride 1 */ SPA_PARAM_BUFFERS_stride, SPA_POD_Int(1)); pw_filter_update_params(data.filter, data.port, (const struct spa_pod **)params, n_params); /* Now connect this filter. We ask that our process function is * called in a realtime thread. */ if (pw_filter_connect(data.filter, PW_FILTER_FLAG_RT_PROCESS, NULL, 0) < 0) { fprintf(stderr, "can't connect\n"); return -1; } /* and wait while we let things run */ pw_main_loop_run(data.loop); pw_filter_destroy(data.filter); pw_main_loop_destroy(data.loop); pw_deinit(); return 0; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/examples/sdl.h000066400000000000000000000160741511204443500240460ustar00rootroot00000000000000/* PipeWire */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ /* [title] SDL2 video format conversions [title] */ #include #include #include #include #include static struct { Uint32 format; uint32_t id; } sdl_video_formats[] = { #if SDL_BYTEORDER == SDL_BIG_ENDIAN { SDL_PIXELFORMAT_UNKNOWN, SPA_VIDEO_FORMAT_UNKNOWN,}, { SDL_PIXELFORMAT_INDEX1LSB, SPA_VIDEO_FORMAT_UNKNOWN,}, { SDL_PIXELFORMAT_UNKNOWN, SPA_VIDEO_FORMAT_UNKNOWN,}, { SDL_PIXELFORMAT_INDEX1LSB, SPA_VIDEO_FORMAT_UNKNOWN,}, { SDL_PIXELFORMAT_INDEX1MSB, SPA_VIDEO_FORMAT_UNKNOWN,}, { SDL_PIXELFORMAT_INDEX4LSB, SPA_VIDEO_FORMAT_UNKNOWN,}, { SDL_PIXELFORMAT_INDEX4MSB, SPA_VIDEO_FORMAT_UNKNOWN,}, { SDL_PIXELFORMAT_INDEX8, SPA_VIDEO_FORMAT_UNKNOWN,}, { SDL_PIXELFORMAT_RGB332, SPA_VIDEO_FORMAT_UNKNOWN,}, { SDL_PIXELFORMAT_RGB444, SPA_VIDEO_FORMAT_UNKNOWN,}, { SDL_PIXELFORMAT_RGB555, SPA_VIDEO_FORMAT_UNKNOWN,}, { SDL_PIXELFORMAT_BGR555, SPA_VIDEO_FORMAT_UNKNOWN,}, { SDL_PIXELFORMAT_ARGB4444, SPA_VIDEO_FORMAT_UNKNOWN,}, { SDL_PIXELFORMAT_RGBA4444, SPA_VIDEO_FORMAT_UNKNOWN,}, { SDL_PIXELFORMAT_ABGR4444, SPA_VIDEO_FORMAT_UNKNOWN,}, { SDL_PIXELFORMAT_BGRA4444, SPA_VIDEO_FORMAT_UNKNOWN,}, { SDL_PIXELFORMAT_ARGB1555, SPA_VIDEO_FORMAT_UNKNOWN,}, { SDL_PIXELFORMAT_RGBA5551, SPA_VIDEO_FORMAT_UNKNOWN,}, { SDL_PIXELFORMAT_ABGR1555, SPA_VIDEO_FORMAT_UNKNOWN,}, { SDL_PIXELFORMAT_BGRA5551, SPA_VIDEO_FORMAT_UNKNOWN,}, { SDL_PIXELFORMAT_RGB565, SPA_VIDEO_FORMAT_UNKNOWN,}, { SDL_PIXELFORMAT_BGR565, SPA_VIDEO_FORMAT_UNKNOWN,}, { SDL_PIXELFORMAT_RGB24, SPA_VIDEO_FORMAT_RGB,}, { SDL_PIXELFORMAT_RGB888, SPA_VIDEO_FORMAT_RGB,}, { SDL_PIXELFORMAT_RGBX8888, SPA_VIDEO_FORMAT_RGBx,}, { SDL_PIXELFORMAT_BGR24, SPA_VIDEO_FORMAT_BGR,}, { SDL_PIXELFORMAT_BGR888, SPA_VIDEO_FORMAT_BGR,}, { SDL_PIXELFORMAT_BGRX8888, SPA_VIDEO_FORMAT_BGRx,}, { SDL_PIXELFORMAT_ARGB2101010, SPA_VIDEO_FORMAT_UNKNOWN,}, { SDL_PIXELFORMAT_RGBA8888, SPA_VIDEO_FORMAT_RGBA,}, { SDL_PIXELFORMAT_ARGB8888, SPA_VIDEO_FORMAT_ARGB,}, { SDL_PIXELFORMAT_BGRA8888, SPA_VIDEO_FORMAT_BGRA,}, { SDL_PIXELFORMAT_ABGR8888, SPA_VIDEO_FORMAT_ABGR,}, { SDL_PIXELFORMAT_YV12, SPA_VIDEO_FORMAT_YV12,}, { SDL_PIXELFORMAT_IYUV, SPA_VIDEO_FORMAT_I420,}, { SDL_PIXELFORMAT_YUY2, SPA_VIDEO_FORMAT_YUY2,}, { SDL_PIXELFORMAT_UYVY, SPA_VIDEO_FORMAT_UYVY,}, { SDL_PIXELFORMAT_YVYU, SPA_VIDEO_FORMAT_YVYU,}, #if SDL_VERSION_ATLEAST(2,0,4) { SDL_PIXELFORMAT_NV12, SPA_VIDEO_FORMAT_NV12,}, { SDL_PIXELFORMAT_NV21, SPA_VIDEO_FORMAT_NV21,}, #endif #else { SDL_PIXELFORMAT_UNKNOWN, SPA_VIDEO_FORMAT_UNKNOWN,}, { SDL_PIXELFORMAT_INDEX1LSB, SPA_VIDEO_FORMAT_UNKNOWN,}, { SDL_PIXELFORMAT_UNKNOWN, SPA_VIDEO_FORMAT_UNKNOWN,}, { SDL_PIXELFORMAT_INDEX1LSB, SPA_VIDEO_FORMAT_UNKNOWN,}, { SDL_PIXELFORMAT_INDEX1MSB, SPA_VIDEO_FORMAT_UNKNOWN,}, { SDL_PIXELFORMAT_INDEX4LSB, SPA_VIDEO_FORMAT_UNKNOWN,}, { SDL_PIXELFORMAT_INDEX4MSB, SPA_VIDEO_FORMAT_UNKNOWN,}, { SDL_PIXELFORMAT_INDEX8, SPA_VIDEO_FORMAT_UNKNOWN,}, { SDL_PIXELFORMAT_RGB332, SPA_VIDEO_FORMAT_UNKNOWN,}, { SDL_PIXELFORMAT_RGB444, SPA_VIDEO_FORMAT_UNKNOWN,}, { SDL_PIXELFORMAT_RGB555, SPA_VIDEO_FORMAT_UNKNOWN,}, { SDL_PIXELFORMAT_BGR555, SPA_VIDEO_FORMAT_UNKNOWN,}, { SDL_PIXELFORMAT_ARGB4444, SPA_VIDEO_FORMAT_UNKNOWN,}, { SDL_PIXELFORMAT_RGBA4444, SPA_VIDEO_FORMAT_UNKNOWN,}, { SDL_PIXELFORMAT_ABGR4444, SPA_VIDEO_FORMAT_UNKNOWN,}, { SDL_PIXELFORMAT_BGRA4444, SPA_VIDEO_FORMAT_UNKNOWN,}, { SDL_PIXELFORMAT_ARGB1555, SPA_VIDEO_FORMAT_UNKNOWN,}, { SDL_PIXELFORMAT_RGBA5551, SPA_VIDEO_FORMAT_UNKNOWN,}, { SDL_PIXELFORMAT_ABGR1555, SPA_VIDEO_FORMAT_UNKNOWN,}, { SDL_PIXELFORMAT_BGRA5551, SPA_VIDEO_FORMAT_UNKNOWN,}, { SDL_PIXELFORMAT_RGB565, SPA_VIDEO_FORMAT_UNKNOWN,}, { SDL_PIXELFORMAT_BGR565, SPA_VIDEO_FORMAT_UNKNOWN,}, { SDL_PIXELFORMAT_RGB24, SPA_VIDEO_FORMAT_BGR,}, { SDL_PIXELFORMAT_RGB888, SPA_VIDEO_FORMAT_BGR,}, { SDL_PIXELFORMAT_RGBX8888, SPA_VIDEO_FORMAT_xBGR,}, { SDL_PIXELFORMAT_BGR24, SPA_VIDEO_FORMAT_RGB,}, { SDL_PIXELFORMAT_BGR888, SPA_VIDEO_FORMAT_RGB,}, { SDL_PIXELFORMAT_BGRX8888, SPA_VIDEO_FORMAT_xRGB,}, { SDL_PIXELFORMAT_ARGB2101010, SPA_VIDEO_FORMAT_UNKNOWN,}, { SDL_PIXELFORMAT_RGBA8888, SPA_VIDEO_FORMAT_ABGR,}, { SDL_PIXELFORMAT_ARGB8888, SPA_VIDEO_FORMAT_BGRA,}, { SDL_PIXELFORMAT_BGRA8888, SPA_VIDEO_FORMAT_ARGB,}, { SDL_PIXELFORMAT_ABGR8888, SPA_VIDEO_FORMAT_RGBA,}, { SDL_PIXELFORMAT_YV12, SPA_VIDEO_FORMAT_YV12,}, { SDL_PIXELFORMAT_IYUV, SPA_VIDEO_FORMAT_I420,}, { SDL_PIXELFORMAT_YUY2, SPA_VIDEO_FORMAT_YUY2,}, { SDL_PIXELFORMAT_UYVY, SPA_VIDEO_FORMAT_UYVY,}, { SDL_PIXELFORMAT_YVYU, SPA_VIDEO_FORMAT_YVYU,}, #if SDL_VERSION_ATLEAST(2,0,4) { SDL_PIXELFORMAT_NV12, SPA_VIDEO_FORMAT_NV12,}, { SDL_PIXELFORMAT_NV21, SPA_VIDEO_FORMAT_NV21,}, #endif #endif }; static inline uint32_t sdl_format_to_id(Uint32 format) { SPA_FOR_EACH_ELEMENT_VAR(sdl_video_formats, f) { if (f->format == format) return f->id; } return SPA_VIDEO_FORMAT_UNKNOWN; } static inline Uint32 id_to_sdl_format(uint32_t id) { SPA_FOR_EACH_ELEMENT_VAR(sdl_video_formats, f) { if (f->id == id) return f->format; } return SDL_PIXELFORMAT_UNKNOWN; } static inline struct spa_pod *sdl_build_formats(SDL_RendererInfo *info, struct spa_pod_builder *b) { uint32_t i, c; struct spa_pod_frame f[2]; /* make an object of type SPA_TYPE_OBJECT_Format and id SPA_PARAM_EnumFormat. * The object type is important because it defines the properties that are * acceptable. The id gives more context about what the object is meant to * contain. In this case we enumerate supported formats. */ spa_pod_builder_push_object(b, &f[0], SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat); /* add media type and media subtype properties */ spa_pod_builder_prop(b, SPA_FORMAT_mediaType, 0); spa_pod_builder_id(b, SPA_MEDIA_TYPE_video); spa_pod_builder_prop(b, SPA_FORMAT_mediaSubtype, 0); spa_pod_builder_id(b, SPA_MEDIA_SUBTYPE_raw); /* build an enumeration of formats */ spa_pod_builder_prop(b, SPA_FORMAT_VIDEO_format, 0); spa_pod_builder_push_choice(b, &f[1], SPA_CHOICE_Enum, 0); /* first the formats supported by the textures */ for (i = 0, c = 0; i < info->num_texture_formats; i++) { uint32_t id = sdl_format_to_id(info->texture_formats[i]); if (id == 0) continue; if (c++ == 0) spa_pod_builder_id(b, SPA_VIDEO_FORMAT_UNKNOWN); spa_pod_builder_id(b, id); } /* then all the other ones SDL can convert from/to */ SPA_FOR_EACH_ELEMENT_VAR(sdl_video_formats, f) { uint32_t id = f->id; if (id != SPA_VIDEO_FORMAT_UNKNOWN) spa_pod_builder_id(b, id); } spa_pod_builder_id(b, SPA_VIDEO_FORMAT_RGBA_F32); spa_pod_builder_pop(b, &f[1]); /* add size and framerate ranges */ spa_pod_builder_add(b, SPA_FORMAT_VIDEO_size, SPA_POD_CHOICE_RANGE_Rectangle( &SPA_RECTANGLE(WIDTH, HEIGHT), &SPA_RECTANGLE(1,1), &SPA_RECTANGLE(info->max_texture_width, info->max_texture_height)), SPA_FORMAT_VIDEO_framerate, SPA_POD_CHOICE_RANGE_Fraction( &SPA_FRACTION(RATE,1), &SPA_FRACTION(0,1), &SPA_FRACTION(30,1)), 0); return spa_pod_builder_pop(b, &f[0]); } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/examples/video-dsp-play.c000066400000000000000000000156211511204443500261110ustar00rootroot00000000000000/* PipeWire */ /* SPDX-FileCopyrightText: Copyright © 2019 Wim Taymans */ /* SPDX-License-Identifier: MIT */ /* [title] Video input stream using \ref pw_filter "pw_filter". [title] */ #include #include #include #include #include #include #include #include #define WIDTH 640 #define HEIGHT 480 #define BPP 3 #define RATE 30 #define MAX_BUFFERS 64 #include "sdl.h" struct pixel { float r, g, b, a; }; struct data { const char *target; SDL_Renderer *renderer; SDL_Window *window; SDL_Texture *texture; SDL_Texture *cursor; struct pw_main_loop *loop; struct pw_filter *filter; struct spa_hook filter_listener; void *in_port; struct spa_io_position *position; struct spa_video_info_dsp format; int counter; SDL_Rect rect; SDL_Rect cursor_rect; }; static void handle_events(struct data *data) { SDL_Event event; while (SDL_PollEvent(&event)) { switch (event.type) { case SDL_QUIT: pw_main_loop_quit(data->loop); break; } } } /* our data processing function is in general: * * struct pw_buffer *b; * b = pw_filter_dequeue_buffer(port); * * .. do stuff with buffer ... * * pw_filter_queue_buffer(port, b); */ static void on_process(void *_data, struct spa_io_position *position) { struct data *data = _data; struct pw_buffer *b; struct spa_buffer *buf; void *sdata, *ddata; int sstride, dstride; uint32_t i, j; uint8_t *src, *dst; b = NULL; while (true) { struct pw_buffer *t; if ((t = pw_filter_dequeue_buffer(data->in_port)) == NULL) break; if (b) pw_filter_queue_buffer(data->in_port, b); b = t; } if (b == NULL) { pw_log_warn("out of buffers: %m"); return; } buf = b->buffer; pw_log_trace("new buffer %p %dx%d", buf, data->position->video.size.width, data->position->video.size.height); handle_events(data); if ((sdata = buf->datas[0].data) == NULL) { pw_log_error("no buffer data"); goto done; } if (SDL_LockTexture(data->texture, NULL, &ddata, &dstride) < 0) { pw_log_error("Couldn't lock texture: %s", SDL_GetError()); goto done; } /* copy video image in texture */ sstride = buf->datas[0].chunk->stride; if (sstride == 0) sstride = buf->datas[0].chunk->size / data->position->video.size.height; src = sdata; dst = ddata; for (i = 0; i < data->position->video.size.height; i++) { struct pixel *p = (struct pixel *) src; for (j = 0; j < data->position->video.size.width; j++) { dst[j * 4 + 0] = SPA_CLAMP((uint8_t)(p[j].r * 255.0f), 0, 255); dst[j * 4 + 1] = SPA_CLAMP((uint8_t)(p[j].g * 255.0f), 0, 255); dst[j * 4 + 2] = SPA_CLAMP((uint8_t)(p[j].b * 255.0f), 0, 255); dst[j * 4 + 3] = SPA_CLAMP((uint8_t)(p[j].a * 255.0f), 0, 255); } src += sstride; dst += dstride; } SDL_UnlockTexture(data->texture); SDL_RenderClear(data->renderer); SDL_RenderCopy(data->renderer, data->texture, &data->rect, NULL); SDL_RenderPresent(data->renderer); done: pw_filter_queue_buffer(data->in_port, b); } static void on_filter_state_changed(void *_data, enum pw_filter_state old, enum pw_filter_state state, const char *error) { struct data *data = _data; fprintf(stderr, "filter state: \"%s\"\n", pw_filter_state_as_string(state)); switch (state) { case PW_FILTER_STATE_UNCONNECTED: pw_main_loop_quit(data->loop); break; default: break; } } static void on_filter_io_changed(void *_data, void *port_data, uint32_t id, void *area, uint32_t size) { struct data *data = _data; switch (id) { case SPA_IO_Position: data->position = area; break; } } static void on_filter_param_changed(void *_data, void *port_data, uint32_t id, const struct spa_pod *param) { struct data *data = _data; struct pw_filter *filter = data->filter; /* NULL means to clear the format */ if (param == NULL || id != SPA_PARAM_Format) return; /* call a helper function to parse the format for us. */ spa_format_video_dsp_parse(param, &data->format); if (data->format.format != SPA_VIDEO_FORMAT_RGBA_F32) { pw_filter_set_error(filter, -EINVAL, "unknown format"); return; } data->texture = SDL_CreateTexture(data->renderer, SDL_PIXELFORMAT_RGBA32, SDL_TEXTUREACCESS_STREAMING, data->position->video.size.width, data->position->video.size.height); if (data->texture == NULL) { pw_filter_set_error(filter, -errno, "can't create texture"); return; } data->rect.x = 0; data->rect.y = 0; data->rect.w = data->position->video.size.width; data->rect.h = data->position->video.size.height; } /* these are the filter events we listen for */ static const struct pw_filter_events filter_events = { PW_VERSION_FILTER_EVENTS, .state_changed = on_filter_state_changed, .io_changed = on_filter_io_changed, .param_changed = on_filter_param_changed, .process = on_process, }; int main(int argc, char *argv[]) { struct data data = { 0, }; pw_init(&argc, &argv); /* create a main loop */ data.loop = pw_main_loop_new(NULL); data.target = argc > 1 ? argv[1] : NULL; /* create a simple filter, the simple filter manages to core and remote * objects for you if you don't need to deal with them * * If you plan to autoconnect your filter, you need to provide at least * media, category and role properties * * Pass your events and a user_data pointer as the last arguments. This * will inform you about the filter state. The most important event * you need to listen to is the process event where you need to consume * the data provided to you. */ data.filter = pw_filter_new_simple( pw_main_loop_get_loop(data.loop), "video-dsp-play", pw_properties_new( PW_KEY_MEDIA_TYPE, "Video", PW_KEY_MEDIA_CATEGORY, "Capture", PW_KEY_MEDIA_ROLE, "DSP", PW_KEY_NODE_AUTOCONNECT, data.target ? "true" : "false", PW_KEY_TARGET_OBJECT, data.target, PW_KEY_MEDIA_CLASS, "Stream/Input/Video", NULL), &filter_events, &data); if (SDL_Init(SDL_INIT_VIDEO) < 0) { fprintf(stderr, "can't initialize SDL: %s\n", SDL_GetError()); return -1; } if (SDL_CreateWindowAndRenderer (WIDTH, HEIGHT, SDL_WINDOW_RESIZABLE, &data.window, &data.renderer)) { fprintf(stderr, "can't create window: %s\n", SDL_GetError()); return -1; } /* Make a new DSP port. This will automatically set up the right * parameters for the port */ data.in_port = pw_filter_add_port(data.filter, PW_DIRECTION_INPUT, PW_FILTER_PORT_FLAG_MAP_BUFFERS, 0, pw_properties_new( PW_KEY_FORMAT_DSP, "32 bit float RGBA video", PW_KEY_PORT_NAME, "input", NULL), NULL, 0); pw_filter_connect(data.filter, 0, /* no flags */ NULL, 0); /* do things until we quit the mainloop */ pw_main_loop_run(data.loop); pw_filter_destroy(data.filter); pw_main_loop_destroy(data.loop); SDL_DestroyTexture(data.texture); if (data.cursor) SDL_DestroyTexture(data.cursor); SDL_DestroyRenderer(data.renderer); SDL_DestroyWindow(data.window); return 0; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/examples/video-dsp-src.c000066400000000000000000000241211511204443500257260ustar00rootroot00000000000000/* PipeWire */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-FileCopyrightText: Copyright © 2023 Columbarius */ /* SPDX-License-Identifier: MIT */ /* [title] Video source using \ref pw_stream. [title] */ #include #include #include #include #include #include #include #include #define BPP 16 #define CURSOR_WIDTH 64 #define CURSOR_HEIGHT 64 #define CURSOR_BPP 4 #define MAX_BUFFERS 64 #define M_PI_M2 ( M_PI + M_PI ) struct pixel { float r, g, b, a; }; struct data { struct pw_main_loop *loop; struct spa_source *timer; struct pw_context *context; struct pw_core *core; struct pw_stream *stream; struct spa_hook stream_listener; struct spa_video_info_dsp format; int32_t stride; struct spa_io_position *position; int counter; uint32_t seq; double crop; double accumulator; int res; }; static void draw_elipse(uint32_t *dst, int width, int height, uint32_t color) { int i, j, r1, r2, r12, r22, r122; r1 = width/2; r12 = r1 * r1; r2 = height/2; r22 = r2 * r2; r122 = r12 * r22; for (i = -r2; i < r2; i++) { for (j = -r1; j < r1; j++) { dst[(i + r2)*width+(j+r1)] = (i * i * r12 + j * j * r22 <= r122) ? color : 0x00000000; } } } static inline float map_value(int value) { return (value%256)/255.0f; } static void on_process(void *userdata) { struct data *data = userdata; struct pw_buffer *b; struct spa_buffer *buf; uint32_t i, j; uint8_t *p; struct pixel *px; struct spa_meta *m; struct spa_meta_header *h; struct spa_meta_region *mc; struct spa_meta_cursor *mcs; if ((b = pw_stream_dequeue_buffer(data->stream)) == NULL) { pw_log_warn("out of buffers: %m"); return; } buf = b->buffer; if ((p = buf->datas[0].data) == NULL) return; if ((h = spa_buffer_find_meta_data(buf, SPA_META_Header, sizeof(*h)))) { #if 0 h->pts = pw_stream_get_nsec(data->stream); #else h->pts = -1; #endif h->flags = 0; h->seq = data->seq++; h->dts_offset = 0; } if ((m = spa_buffer_find_meta(buf, SPA_META_VideoDamage))) { struct spa_meta_region *r = spa_meta_first(m); if (spa_meta_check(r, m)) { r->region.position = SPA_POINT(0,0); r->region.size = data->position->video.size; r++; } if (spa_meta_check(r, m)) r->region = SPA_REGION(0,0,0,0); } if ((mc = spa_buffer_find_meta_data(buf, SPA_META_VideoCrop, sizeof(*mc)))) { data->crop = (sin(data->accumulator) + 1.0) * 32.0; mc->region.position.x = (int32_t)data->crop; mc->region.position.y = (int32_t)data->crop; mc->region.size.width = data->position->video.size.width - (int32_t)(data->crop*2); mc->region.size.height = data->position->video.size.height - (int32_t)(data->crop*2); } if ((mcs = spa_buffer_find_meta_data(buf, SPA_META_Cursor, sizeof(*mcs)))) { struct spa_meta_bitmap *mb; uint32_t *bitmap, color; mcs->id = 1; mcs->position.x = (int32_t)((sin(data->accumulator) + 1.0) * 160.0 + 80); mcs->position.y = (int32_t)((cos(data->accumulator) + 1.0) * 100.0 + 50); mcs->hotspot.x = 0; mcs->hotspot.y = 0; mcs->bitmap_offset = sizeof(struct spa_meta_cursor); mb = SPA_PTROFF(mcs, mcs->bitmap_offset, struct spa_meta_bitmap); mb->format = SPA_VIDEO_FORMAT_ARGB; mb->size.width = CURSOR_WIDTH; mb->size.height = CURSOR_HEIGHT; mb->stride = CURSOR_WIDTH * CURSOR_BPP; mb->offset = sizeof(struct spa_meta_bitmap); bitmap = SPA_PTROFF(mb, mb->offset, uint32_t); color = (uint32_t)((cos(data->accumulator) + 1.0) * (1 << 23)); color |= 0xff000000; draw_elipse(bitmap, mb->size.width, mb->size.height, color); } for (i = 0; i < data->position->video.size.height; i++) { px = (struct pixel *)p; for (j = 0; j < data->position->video.size.width; j++) { px[j] = (struct pixel){map_value(data->counter + j * i), map_value(data->counter + j * (i + 1)), map_value(data->counter + j * (i + 2)), 1.0f}; } p += data->stride; data->counter += 13; } data->accumulator += M_PI_M2 / 50.0; if (data->accumulator >= M_PI_M2) data->accumulator -= M_PI_M2; buf->datas[0].chunk->offset = 0; buf->datas[0].chunk->size = data->position->video.size.height * data->stride; buf->datas[0].chunk->stride = data->stride; pw_stream_queue_buffer(data->stream, b); } static void on_timeout(void *userdata, uint64_t expirations) { struct data *data = userdata; pw_log_trace("timeout"); pw_stream_trigger_process(data->stream); } static void on_stream_state_changed(void *_data, enum pw_stream_state old, enum pw_stream_state state, const char *error) { struct data *data = _data; printf("stream state: \"%s\"\n", pw_stream_state_as_string(state)); switch (state) { case PW_STREAM_STATE_ERROR: case PW_STREAM_STATE_UNCONNECTED: pw_main_loop_quit(data->loop); break; case PW_STREAM_STATE_PAUSED: printf("node id: %d\n", pw_stream_get_node_id(data->stream)); pw_loop_update_timer(pw_main_loop_get_loop(data->loop), data->timer, NULL, NULL, false); break; case PW_STREAM_STATE_STREAMING: { struct timespec timeout, interval; timeout.tv_sec = 0; timeout.tv_nsec = 1; interval.tv_sec = 0; interval.tv_nsec = 40 * SPA_NSEC_PER_MSEC; pw_loop_update_timer(pw_main_loop_get_loop(data->loop), data->timer, &timeout, &interval, false); break; } default: break; } } static void on_stream_io_changed(void *_data, uint32_t id, void *area, uint32_t size) { struct data *data = _data; switch (id) { case SPA_IO_Position: data->position = area; if (data->position) pw_log_info("Position: %ux%u", data->position->video.size.width, data->position->video.size.height); break; } } static void on_stream_param_changed(void *_data, uint32_t id, const struct spa_pod *param) { struct data *data = _data; struct pw_stream *stream = data->stream; uint8_t params_buffer[1024]; struct spa_pod_builder b = SPA_POD_BUILDER_INIT(params_buffer, sizeof(params_buffer)); const struct spa_pod *params[5]; uint32_t n_params = 0; if (param != NULL && id == SPA_PARAM_Tag) { spa_debug_pod(0, NULL, param); return; } if (param == NULL || id != SPA_PARAM_Format) return; spa_format_video_dsp_parse(param, &data->format); data->stride = SPA_ROUND_UP_N(data->position->video.size.width * BPP, 4); params[n_params++] = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers, SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(8, 2, MAX_BUFFERS), SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(1), SPA_PARAM_BUFFERS_size, SPA_POD_Int(data->stride * data->position->video.size.height), SPA_PARAM_BUFFERS_stride, SPA_POD_Int(data->stride)); params[n_params++] = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta, SPA_PARAM_META_type, SPA_POD_Id(SPA_META_Header), SPA_PARAM_META_size, SPA_POD_Int(sizeof(struct spa_meta_header))); params[n_params++] = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta, SPA_PARAM_META_type, SPA_POD_Id(SPA_META_VideoDamage), SPA_PARAM_META_size, SPA_POD_CHOICE_RANGE_Int( sizeof(struct spa_meta_region) * 16, sizeof(struct spa_meta_region) * 1, sizeof(struct spa_meta_region) * 16)); params[n_params++] = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta, SPA_PARAM_META_type, SPA_POD_Id(SPA_META_VideoCrop), SPA_PARAM_META_size, SPA_POD_Int(sizeof(struct spa_meta_region))); #define CURSOR_META_SIZE(w,h) (sizeof(struct spa_meta_cursor) + \ sizeof(struct spa_meta_bitmap) + w * h * CURSOR_BPP) params[n_params++] = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta, SPA_PARAM_META_type, SPA_POD_Id(SPA_META_Cursor), SPA_PARAM_META_size, SPA_POD_Int( CURSOR_META_SIZE(CURSOR_WIDTH,CURSOR_HEIGHT))); pw_stream_update_params(stream, params, n_params); } static void on_trigger_done(void *_data) { pw_log_trace("trigger done"); } static const struct pw_stream_events stream_events = { PW_VERSION_STREAM_EVENTS, .process = on_process, .state_changed = on_stream_state_changed, .param_changed = on_stream_param_changed, .io_changed = on_stream_io_changed, .trigger_done = on_trigger_done, }; static void do_quit(void *userdata, int signal_number) { struct data *data = userdata; pw_main_loop_quit(data->loop); } int main(int argc, char *argv[]) { struct data data = { 0, }; const struct spa_pod *params[2]; uint32_t n_params = 0; uint8_t buffer[1024]; struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); pw_init(&argc, &argv); data.loop = pw_main_loop_new(NULL); pw_loop_add_signal(pw_main_loop_get_loop(data.loop), SIGINT, do_quit, &data); pw_loop_add_signal(pw_main_loop_get_loop(data.loop), SIGTERM, do_quit, &data); data.context = pw_context_new(pw_main_loop_get_loop(data.loop), NULL, 0); data.timer = pw_loop_add_timer(pw_main_loop_get_loop(data.loop), on_timeout, &data); data.core = pw_context_connect(data.context, NULL, 0); if (data.core == NULL) { fprintf(stderr, "can't connect: %m\n"); data.res = -errno; goto cleanup; } data.stream = pw_stream_new(data.core, "video-src", pw_properties_new( PW_KEY_MEDIA_CLASS, "Video/Source", NULL)); params[n_params++] = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_dsp), SPA_FORMAT_VIDEO_format, SPA_POD_Id(SPA_VIDEO_FORMAT_DSP_F32)); { struct spa_pod_frame f; struct spa_dict_item items[1]; /* send a tag, output tags travel downstream */ spa_tag_build_start(&b, &f, SPA_PARAM_Tag, SPA_DIRECTION_OUTPUT); items[0] = SPA_DICT_ITEM_INIT("my-tag-key", "my-special-tag-value"); spa_tag_build_add_dict(&b, &SPA_DICT_INIT(items, 1)); params[n_params++] = spa_tag_build_end(&b, &f); } pw_stream_add_listener(data.stream, &data.stream_listener, &stream_events, &data); pw_stream_connect(data.stream, PW_DIRECTION_OUTPUT, PW_ID_ANY, PW_STREAM_FLAG_DRIVER | PW_STREAM_FLAG_MAP_BUFFERS, params, n_params); pw_main_loop_run(data.loop); cleanup: pw_context_destroy(data.context); pw_main_loop_destroy(data.loop); pw_deinit(); return data.res; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/examples/video-play-fixate.c000066400000000000000000000340261511204443500266030ustar00rootroot00000000000000/* PipeWire */ /* SPDX-FileCopyrightText: Copyright © 2020 Wim Taymans */ /* SPDX-License-Identifier: MIT */ /* [title] Video input stream using \ref pw_stream "pw_stream", with format fixation. [title] */ #include #include #include #include #include #include #include #include #include #include #define WIDTH 640 #define HEIGHT 480 #define RATE 30 #define MAX_BUFFERS 64 #define MAX_MOD 8 #include "sdl.h" struct pixel { float r, g, b, a; }; struct pw_version { int major; int minor; int micro; }; struct modifier_info { uint32_t spa_format; uint32_t n_modifiers; uint64_t modifiers[MAX_MOD]; }; struct data { const char *path; SDL_Renderer *renderer; SDL_Window *window; SDL_Texture *texture; SDL_Texture *cursor; struct pw_main_loop *loop; struct spa_source *reneg; struct pw_stream *stream; struct spa_hook stream_listener; struct spa_video_info format; int32_t stride; struct spa_rectangle size; uint32_t n_mod_info; struct modifier_info mod_info[2]; int counter; }; static struct pw_version parse_pw_version(const char* version) { struct pw_version pw_version; sscanf(version, "%d.%d.%d", &pw_version.major, &pw_version.minor, &pw_version.micro); return pw_version; } static bool has_pw_version(int major, int minor, int micro) { struct pw_version pw_version = parse_pw_version(pw_get_library_version()); printf("PW Version: %d.%d.%d\n", pw_version.major, pw_version.minor, pw_version.micro); return major <= pw_version.major && minor <= pw_version.minor && micro <= pw_version.micro; } static void init_modifiers(struct data *data) { data->n_mod_info = 1; data->mod_info[0].spa_format = SPA_VIDEO_FORMAT_RGB; data->mod_info[0].n_modifiers = 2; data->mod_info[0].modifiers[0] = DRM_FORMAT_MOD_LINEAR; data->mod_info[0].modifiers[1] = DRM_FORMAT_MOD_INVALID; } static void destroy_modifiers(struct data *data) { data->mod_info[0].n_modifiers = 0; } static void strip_modifier(struct data *data, uint32_t spa_format, uint64_t modifier) { if (data->mod_info[0].spa_format != spa_format) return; struct modifier_info *mod_info = &data->mod_info[0]; uint32_t counter = 0; // Dropping of single modifiers is only supported on PipeWire 0.3.40 and newer. // On older PipeWire just dropping all modifiers might work on Versions newer then 0.3.33/35 if (has_pw_version(0,3,40)) { printf("Dropping a single modifier\n"); for (uint32_t i = 0; i < mod_info->n_modifiers; i++) { if (mod_info->modifiers[i] == modifier) continue; mod_info->modifiers[counter++] = mod_info->modifiers[i]; } } else { printf("Dropping all modifiers\n"); counter = 0; } mod_info->n_modifiers = counter; } static void handle_events(struct data *data) { SDL_Event event; while (SDL_PollEvent(&event)) { switch (event.type) { case SDL_QUIT: pw_main_loop_quit(data->loop); break; } } } static struct spa_pod *build_format(struct spa_pod_builder *b, SDL_RendererInfo *info, enum spa_video_format format, uint64_t *modifiers, int modifier_count) { struct spa_pod_frame f[2]; int i, c; spa_pod_builder_push_object(b, &f[0], SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat); spa_pod_builder_add(b, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video), 0); spa_pod_builder_add(b, SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), 0); /* format */ spa_pod_builder_add(b, SPA_FORMAT_VIDEO_format, SPA_POD_Id(format), 0); /* modifiers */ if (modifier_count == 1 && modifiers[0] == DRM_FORMAT_MOD_INVALID) { // we only support implicit modifiers, use shortpath to skip fixation phase spa_pod_builder_prop(b, SPA_FORMAT_VIDEO_modifier, SPA_POD_PROP_FLAG_MANDATORY); spa_pod_builder_long(b, modifiers[0]); } else if (modifier_count > 0) { // build an enumeration of modifiers spa_pod_builder_prop(b, SPA_FORMAT_VIDEO_modifier, SPA_POD_PROP_FLAG_MANDATORY | SPA_POD_PROP_FLAG_DONT_FIXATE); spa_pod_builder_push_choice(b, &f[1], SPA_CHOICE_Enum, 0); // modifiers from the array for (i = 0, c = 0; i < modifier_count; i++) { spa_pod_builder_long(b, modifiers[i]); if (c++ == 0) spa_pod_builder_long(b, modifiers[i]); } spa_pod_builder_pop(b, &f[1]); } spa_pod_builder_add(b, SPA_FORMAT_VIDEO_size, SPA_POD_CHOICE_RANGE_Rectangle( &SPA_RECTANGLE(WIDTH, HEIGHT), &SPA_RECTANGLE(1,1), &SPA_RECTANGLE(info->max_texture_width, info->max_texture_height)), 0); spa_pod_builder_add(b, SPA_FORMAT_VIDEO_framerate, SPA_POD_CHOICE_RANGE_Fraction( &SPA_FRACTION(25,1), &SPA_FRACTION(0,1), &SPA_FRACTION(30,1)), 0); return spa_pod_builder_pop(b, &f[0]); } /* our data processing function is in general: * * struct pw_buffer *b; * b = pw_stream_dequeue_buffer(stream); * * .. do stuff with buffer ... * * pw_stream_queue_buffer(stream, b); */ static void on_process(void *_data) { struct data *data = _data; struct pw_stream *stream = data->stream; struct pw_buffer *b; struct spa_buffer *buf; void *sdata, *ddata; int sstride, dstride, ostride; uint32_t i; uint8_t *src, *dst; b = NULL; /* dequeue and queue old buffers, use the last available * buffer */ while (true) { struct pw_buffer *t; if ((t = pw_stream_dequeue_buffer(stream)) == NULL) break; if (b) pw_stream_queue_buffer(stream, b); b = t; } if (b == NULL) { pw_log_warn("out of buffers: %m"); return; } buf = b->buffer; pw_log_info("new buffer %p", buf); handle_events(data); if (buf->datas[0].type == SPA_DATA_DmaBuf) { // Simulate a failed import of a DmaBuf // We should try another modifier printf("Failed to import dmabuf, stripping modifier %"PRIu64"\n", data->format.info.raw.modifier); strip_modifier(data, data->format.info.raw.format, data->format.info.raw.modifier); pw_loop_signal_event(pw_main_loop_get_loop(data->loop), data->reneg); goto done; } if ((sdata = buf->datas[0].data) == NULL) goto done; if (SDL_LockTexture(data->texture, NULL, &ddata, &dstride) < 0) { fprintf(stderr, "Couldn't lock texture: %s\n", SDL_GetError()); goto done; } /* copy video image in texture */ sstride = buf->datas[0].chunk->stride; if (sstride == 0) sstride = buf->datas[0].chunk->size / data->size.height; ostride = SPA_MIN(sstride, dstride); src = sdata; dst = ddata; for (i = 0; i < data->size.height; i++) { memcpy(dst, src, ostride); src += sstride; dst += dstride; } SDL_UnlockTexture(data->texture); SDL_RenderClear(data->renderer); /* now render the video */ SDL_RenderCopy(data->renderer, data->texture, NULL, NULL); SDL_RenderPresent(data->renderer); done: pw_stream_queue_buffer(stream, b); } static void on_stream_state_changed(void *_data, enum pw_stream_state old, enum pw_stream_state state, const char *error) { struct data *data = _data; fprintf(stderr, "stream state: \"%s\"\n", pw_stream_state_as_string(state)); switch (state) { case PW_STREAM_STATE_UNCONNECTED: pw_main_loop_quit(data->loop); break; case PW_STREAM_STATE_PAUSED: break; case PW_STREAM_STATE_STREAMING: default: break; } } /* Be notified when the stream param changes. We're only looking at the * format changes. * * We are now supposed to call pw_stream_finish_format() with success or * failure, depending on if we can support the format. Because we gave * a list of supported formats, this should be ok. * * As part of pw_stream_finish_format() we can provide parameters that * will control the buffer memory allocation. This includes the metadata * that we would like on our buffer, the size, alignment, etc. */ static void on_stream_param_changed(void *_data, uint32_t id, const struct spa_pod *param) { struct data *data = _data; struct pw_stream *stream = data->stream; uint8_t params_buffer[1024]; struct spa_pod_builder b = SPA_POD_BUILDER_INIT(params_buffer, sizeof(params_buffer)); const struct spa_pod *params[1]; uint32_t n_params = 0; Uint32 sdl_format; void *d; /* NULL means to clear the format */ if (param == NULL || id != SPA_PARAM_Format) return; fprintf(stderr, "got format:\n"); spa_debug_format(2, NULL, param); if (spa_format_parse(param, &data->format.media_type, &data->format.media_subtype) < 0) return; if (data->format.media_type != SPA_MEDIA_TYPE_video || data->format.media_subtype != SPA_MEDIA_SUBTYPE_raw) return; /* call a helper function to parse the format for us. */ spa_format_video_raw_parse(param, &data->format.info.raw); sdl_format = id_to_sdl_format(data->format.info.raw.format); data->size = data->format.info.raw.size; if (sdl_format == SDL_PIXELFORMAT_UNKNOWN) { pw_stream_set_error(stream, -EINVAL, "unknown pixel format"); return; } if (data->size.width == 0 || data->size.height == 0) { pw_stream_set_error(stream, -EINVAL, "invalid size"); return; } data->texture = SDL_CreateTexture(data->renderer, sdl_format, SDL_TEXTUREACCESS_STREAMING, data->size.width, data->size.height); if (SDL_LockTexture(data->texture, NULL, &d, &data->stride) < 0) { pw_stream_set_error(stream, -EINVAL, "invalid texture format"); return; } SDL_UnlockTexture(data->texture); /* a SPA_TYPE_OBJECT_ParamBuffers object defines the acceptable size, * number, stride etc of the buffers */ params[n_params++] = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers, SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(8, 2, MAX_BUFFERS), SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(1), SPA_PARAM_BUFFERS_size, SPA_POD_Int(data->stride * data->size.height), SPA_PARAM_BUFFERS_stride, SPA_POD_Int(data->stride), SPA_PARAM_BUFFERS_dataType, SPA_POD_CHOICE_FLAGS_Int((1<renderer, &info); if (data->mod_info[0].n_modifiers > 0) { params[n_params++] = build_format(b, &info, SPA_VIDEO_FORMAT_RGB, data->mod_info[0].modifiers, data->mod_info[0].n_modifiers); } params[n_params++] = build_format(b, &info, SPA_VIDEO_FORMAT_RGB, NULL, 0); for (int i=0; i < n_params; i++) { spa_debug_format(2, NULL, params[i]); } return n_params; } static void reneg_format(void *_data, uint64_t expiration) { struct data *data = (struct data*) _data; uint8_t buffer[1024]; struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); const struct spa_pod *params[2]; uint32_t n_params; if (data->format.info.raw.format == 0) return; fprintf(stderr, "renegotiate formats:\n"); n_params = build_formats(data, &b, params); pw_stream_update_params(data->stream, params, n_params); } static void do_quit(void *userdata, int signal_number) { struct data *data = userdata; pw_main_loop_quit(data->loop); } int main(int argc, char *argv[]) { struct data data = { 0, }; const struct spa_pod *params[2]; uint8_t buffer[1024]; struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); struct pw_properties *props; int res, n_params; pw_init(&argc, &argv); /* create a main loop */ data.loop = pw_main_loop_new(NULL); pw_loop_add_signal(pw_main_loop_get_loop(data.loop), SIGINT, do_quit, &data); pw_loop_add_signal(pw_main_loop_get_loop(data.loop), SIGTERM, do_quit, &data); /* create a simple stream, the simple stream manages to core and remote * objects for you if you don't need to deal with them * * If you plan to autoconnect your stream, you need to provide at least * media, category and role properties * * Pass your events and a user_data pointer as the last arguments. This * will inform you about the stream state. The most important event * you need to listen to is the process event where you need to consume * the data provided to you. */ props = pw_properties_new(PW_KEY_MEDIA_TYPE, "Video", PW_KEY_MEDIA_CATEGORY, "Capture", PW_KEY_MEDIA_ROLE, "Camera", NULL), data.path = argc > 1 ? argv[1] : NULL; if (data.path) /* Set stream target if given on command line */ pw_properties_set(props, PW_KEY_TARGET_OBJECT, data.path); data.stream = pw_stream_new_simple( pw_main_loop_get_loop(data.loop), "video-play-fixate", props, &stream_events, &data); if (SDL_Init(SDL_INIT_VIDEO) < 0) { fprintf(stderr, "can't initialize SDL: %s\n", SDL_GetError()); return -1; } init_modifiers(&data); if (SDL_CreateWindowAndRenderer (WIDTH, HEIGHT, SDL_WINDOW_RESIZABLE, &data.window, &data.renderer)) { fprintf(stderr, "can't create window: %s\n", SDL_GetError()); return -1; } /* build the extra parameters to connect with. To connect, we can provide * a list of supported formats. We use a builder that writes the param * object to the stack. */ printf("supported formats:\n"); n_params = build_formats(&data, &b, params); /* now connect the stream, we need a direction (input/output), * an optional target node to connect to, some flags and parameters */ if ((res = pw_stream_connect(data.stream, PW_DIRECTION_INPUT, PW_ID_ANY, PW_STREAM_FLAG_AUTOCONNECT | /* try to automatically connect this stream */ PW_STREAM_FLAG_MAP_BUFFERS, /* mmap the buffer data for us */ params, n_params)) /* extra parameters, see above */ < 0) { fprintf(stderr, "can't connect: %s\n", spa_strerror(res)); return -1; } data.reneg = pw_loop_add_event(pw_main_loop_get_loop(data.loop), reneg_format, &data); /* do things until we quit the mainloop */ pw_main_loop_run(data.loop); pw_stream_destroy(data.stream); pw_main_loop_destroy(data.loop); destroy_modifiers(&data); SDL_DestroyTexture(data.texture); if (data.cursor) SDL_DestroyTexture(data.cursor); SDL_DestroyRenderer(data.renderer); SDL_DestroyWindow(data.window); pw_deinit(); return 0; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/examples/video-play-pull.c000066400000000000000000000374661511204443500263120ustar00rootroot00000000000000/* PipeWire */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ /* [title] Video input stream using \ref pw_stream_trigger_process, for pull mode. [title] */ #include #include #include #include #include #include #include #include #define WIDTH 640 #define HEIGHT 480 #define RATE 30 #define MAX_BUFFERS 64 #include "sdl.h" struct pixel { float r, g, b, a; }; struct data { const char *path; SDL_Renderer *renderer; SDL_Window *window; SDL_Texture *texture; SDL_Texture *cursor; struct pw_main_loop *loop; struct spa_source *timer; struct pw_stream *stream; struct spa_hook stream_listener; struct spa_io_position *position; struct spa_video_info format; int32_t stride; struct spa_rectangle size; int counter; SDL_Rect rect; SDL_Rect cursor_rect; bool is_yuv; bool have_request_process; }; static void handle_events(struct data *data) { SDL_Event event; while (SDL_PollEvent(&event)) { switch (event.type) { case SDL_QUIT: pw_main_loop_quit(data->loop); break; } } } /* our data processing function is in general: * * struct pw_buffer *b; * b = pw_stream_dequeue_buffer(stream); * * .. do stuff with buffer ... * * pw_stream_queue_buffer(stream, b); */ static void on_process(void *_data) { struct data *data = _data; struct pw_stream *stream = data->stream; struct pw_buffer *b; struct spa_buffer *buf; void *sdata, *ddata; int sstride, dstride, ostride; struct spa_meta_region *mc; struct spa_meta_cursor *mcs; uint32_t i, j; uint8_t *src, *dst; bool render_cursor = false; b = NULL; while (true) { struct pw_buffer *t; if ((t = pw_stream_dequeue_buffer(stream)) == NULL) break; if (b) pw_stream_queue_buffer(stream, b); b = t; } if (b == NULL) { pw_log_warn("out of buffers: %m"); return; } buf = b->buffer; pw_log_trace("new buffer %p", buf); handle_events(data); if ((sdata = buf->datas[0].data) == NULL) goto done; /* get the videocrop metadata if any */ if ((mc = spa_buffer_find_meta_data(buf, SPA_META_VideoCrop, sizeof(*mc))) && spa_meta_region_is_valid(mc)) { data->rect.x = mc->region.position.x; data->rect.y = mc->region.position.y; data->rect.w = mc->region.size.width; data->rect.h = mc->region.size.height; } /* get cursor metadata */ if ((mcs = spa_buffer_find_meta_data(buf, SPA_META_Cursor, sizeof(*mcs))) && spa_meta_cursor_is_valid(mcs)) { struct spa_meta_bitmap *mb; void *cdata; int cstride; data->cursor_rect.x = mcs->position.x; data->cursor_rect.y = mcs->position.y; mb = SPA_PTROFF(mcs, mcs->bitmap_offset, struct spa_meta_bitmap); data->cursor_rect.w = mb->size.width; data->cursor_rect.h = mb->size.height; if (data->cursor == NULL) { data->cursor = SDL_CreateTexture(data->renderer, id_to_sdl_format(mb->format), SDL_TEXTUREACCESS_STREAMING, mb->size.width, mb->size.height); SDL_SetTextureBlendMode(data->cursor, SDL_BLENDMODE_BLEND); } if (SDL_LockTexture(data->cursor, NULL, &cdata, &cstride) < 0) { fprintf(stderr, "Couldn't lock cursor texture: %s\n", SDL_GetError()); goto done; } /* copy the cursor bitmap into the texture */ src = SPA_PTROFF(mb, mb->offset, uint8_t); dst = cdata; ostride = SPA_MIN(cstride, mb->stride); for (i = 0; i < mb->size.height; i++) { memcpy(dst, src, ostride); dst += cstride; src += mb->stride; } SDL_UnlockTexture(data->cursor); render_cursor = true; } /* copy video image in texture */ if (data->is_yuv) { sstride = data->stride; if (buf->n_datas == 1) { SDL_UpdateTexture(data->texture, NULL, sdata, sstride); } else { SDL_UpdateYUVTexture(data->texture, NULL, sdata, sstride, buf->datas[1].data, sstride / 2, buf->datas[2].data, sstride / 2); } } else { if (SDL_LockTexture(data->texture, NULL, &ddata, &dstride) < 0) { fprintf(stderr, "Couldn't lock texture: %s\n", SDL_GetError()); goto done; } sstride = buf->datas[0].chunk->stride; if (sstride == 0) sstride = buf->datas[0].chunk->size / data->size.height; ostride = SPA_MIN(sstride, dstride); src = sdata; dst = ddata; if (data->format.media_subtype == SPA_MEDIA_SUBTYPE_dsp) { for (i = 0; i < data->size.height; i++) { struct pixel *p = (struct pixel *) src; for (j = 0; j < data->size.width; j++) { dst[j * 4 + 0] = SPA_CLAMP((uint8_t)(p[j].r * 255.0f), 0u, 255u); dst[j * 4 + 1] = SPA_CLAMP((uint8_t)(p[j].g * 255.0f), 0u, 255u); dst[j * 4 + 2] = SPA_CLAMP((uint8_t)(p[j].b * 255.0f), 0u, 255u); dst[j * 4 + 3] = SPA_CLAMP((uint8_t)(p[j].a * 255.0f), 0u, 255u); } src += sstride; dst += dstride; } } else { for (i = 0; i < data->size.height; i++) { memcpy(dst, src, ostride); src += sstride; dst += dstride; } } SDL_UnlockTexture(data->texture); } SDL_RenderClear(data->renderer); /* now render the video and then the cursor if any */ SDL_RenderCopy(data->renderer, data->texture, &data->rect, NULL); if (render_cursor) { SDL_RenderCopy(data->renderer, data->cursor, NULL, &data->cursor_rect); } SDL_RenderPresent(data->renderer); done: pw_stream_queue_buffer(stream, b); } static void enable_timeouts(struct data *data, bool enabled) { struct timespec timeout, interval, *to, *iv; if (!enabled || data->have_request_process) { to = iv = NULL; } else { timeout.tv_sec = 0; timeout.tv_nsec = 1; interval.tv_sec = 0; interval.tv_nsec = 80 * SPA_NSEC_PER_MSEC; to = &timeout; iv = &interval; } pw_loop_update_timer(pw_main_loop_get_loop(data->loop), data->timer, to, iv, false); } static void on_stream_state_changed(void *_data, enum pw_stream_state old, enum pw_stream_state state, const char *error) { struct data *data = _data; fprintf(stderr, "stream state: \"%s\"\n", pw_stream_state_as_string(state)); switch (state) { case PW_STREAM_STATE_UNCONNECTED: pw_main_loop_quit(data->loop); break; case PW_STREAM_STATE_PAUSED: enable_timeouts(data, false); break; case PW_STREAM_STATE_STREAMING: printf("driving:%d lazy:%d\n", pw_stream_is_driving(data->stream), pw_stream_is_lazy(data->stream)); if (pw_stream_is_driving(data->stream) != pw_stream_is_lazy(data->stream)) enable_timeouts(data, true); break; default: break; } } static void on_stream_io_changed(void *_data, uint32_t id, void *area, uint32_t size) { struct data *data = _data; switch (id) { case SPA_IO_Position: data->position = area; break; } } static void on_trigger_done(void *_data) { struct data *data = _data; pw_log_trace("%p trigger done", data); } static void on_timeout(void *userdata, uint64_t expirations) { struct data *data = userdata; pw_stream_trigger_process(data->stream); } static void on_command(void *_data, const struct spa_command *command) { struct data *data = _data; switch (SPA_NODE_COMMAND_ID(command)) { case SPA_NODE_COMMAND_RequestProcess: pw_log_trace("%p trigger", data); pw_stream_trigger_process(data->stream); break; default: break; } } /* Be notified when the stream param changes. We're only looking at the * format changes. * * We are now supposed to call pw_stream_finish_format() with success or * failure, depending on if we can support the format. Because we gave * a list of supported formats, this should be ok. * * As part of pw_stream_finish_format() we can provide parameters that * will control the buffer memory allocation. This includes the metadata * that we would like on our buffer, the size, alignment, etc. */ static void on_stream_param_changed(void *_data, uint32_t id, const struct spa_pod *param) { struct data *data = _data; struct pw_stream *stream = data->stream; uint8_t params_buffer[1024]; struct spa_pod_builder b = SPA_POD_BUILDER_INIT(params_buffer, sizeof(params_buffer)); const struct spa_pod *params[5]; uint32_t n_params = 0; Uint32 sdl_format; void *d; int32_t mult, size, blocks; /* NULL means to clear the format */ if (param == NULL || id != SPA_PARAM_Format) return; fprintf(stderr, "got format:\n"); spa_debug_format(2, NULL, param); if (spa_format_parse(param, &data->format.media_type, &data->format.media_subtype) < 0) return; if (data->format.media_type != SPA_MEDIA_TYPE_video) return; switch (data->format.media_subtype) { case SPA_MEDIA_SUBTYPE_raw: /* call a helper function to parse the format for us. */ spa_format_video_raw_parse(param, &data->format.info.raw); sdl_format = id_to_sdl_format(data->format.info.raw.format); data->size = SPA_RECTANGLE(data->format.info.raw.size.width, data->format.info.raw.size.height); mult = 1; break; case SPA_MEDIA_SUBTYPE_dsp: spa_format_video_dsp_parse(param, &data->format.info.dsp); if (data->format.info.dsp.format != SPA_VIDEO_FORMAT_DSP_F32) return; sdl_format = SDL_PIXELFORMAT_RGBA32; data->size = SPA_RECTANGLE(data->position->video.size.width, data->position->video.size.height); mult = 4; break; default: sdl_format = SDL_PIXELFORMAT_UNKNOWN; break; } if (sdl_format == SDL_PIXELFORMAT_UNKNOWN) { pw_stream_set_error(stream, -EINVAL, "unknown pixel format"); return; } if (data->size.width == 0 || data->size.height == 0) { pw_stream_set_error(stream, -EINVAL, "invalid size"); return; } data->texture = SDL_CreateTexture(data->renderer, sdl_format, SDL_TEXTUREACCESS_STREAMING, data->size.width, data->size.height); switch(sdl_format) { case SDL_PIXELFORMAT_YV12: case SDL_PIXELFORMAT_IYUV: data->stride = data->size.width; size = (data->stride * data->size.height) * 3 / 2; data->is_yuv = true; blocks = 3; break; case SDL_PIXELFORMAT_YUY2: data->stride = data->size.width * 2; size = data->stride * data->size.height; data->is_yuv = true; blocks = 1; break; default: if (SDL_LockTexture(data->texture, NULL, &d, &data->stride) < 0) { data->stride = data->size.width * 2; } else SDL_UnlockTexture(data->texture); size = data->stride * data->size.height; blocks = 1; break; } data->rect.x = 0; data->rect.y = 0; data->rect.w = data->size.width; data->rect.h = data->size.height; /* a SPA_TYPE_OBJECT_ParamBuffers object defines the acceptable size, * number, stride etc of the buffers */ params[n_params++] = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers, SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(8, 2, MAX_BUFFERS), SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(blocks), SPA_PARAM_BUFFERS_size, SPA_POD_Int(size * mult), SPA_PARAM_BUFFERS_stride, SPA_POD_Int(data->stride * mult), SPA_PARAM_BUFFERS_dataType, SPA_POD_CHOICE_FLAGS_Int((1<renderer, &info); params[n_params++] = sdl_build_formats(&info, b); fprintf(stderr, "supported SDL formats:\n"); spa_debug_format(2, NULL, params[0]); params[n_params++] = spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_dsp), SPA_FORMAT_VIDEO_format, SPA_POD_Id(SPA_VIDEO_FORMAT_DSP_F32)); fprintf(stderr, "supported DSP formats:\n"); spa_debug_format(2, NULL, params[1]); return n_params; } static void do_quit(void *userdata, int signal_number) { struct data *data = userdata; pw_main_loop_quit(data->loop); } int main(int argc, char *argv[]) { struct data data = { 0, }; const struct spa_pod *params[2]; uint8_t buffer[1024]; struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); struct pw_properties *props; int res, n_params; pw_init(&argc, &argv); /* create a main loop */ data.loop = pw_main_loop_new(NULL); pw_loop_add_signal(pw_main_loop_get_loop(data.loop), SIGINT, do_quit, &data); pw_loop_add_signal(pw_main_loop_get_loop(data.loop), SIGTERM, do_quit, &data); /* the timer to pull in data */ data.timer = pw_loop_add_timer(pw_main_loop_get_loop(data.loop), on_timeout, &data); /* create a simple stream, the simple stream manages to core and remote * objects for you if you don't need to deal with them * * If you plan to autoconnect your stream, you need to provide at least * media, category and role properties * * Pass your events and a user_data pointer as the last arguments. This * will inform you about the stream state. The most important event * you need to listen to is the process event where you need to consume * the data provided to you. */ props = pw_properties_new(PW_KEY_MEDIA_TYPE, "Video", PW_KEY_MEDIA_CATEGORY, "Capture", PW_KEY_MEDIA_ROLE, "Camera", PW_KEY_NODE_SUPPORTS_LAZY, "1", PW_KEY_NODE_SUPPORTS_REQUEST, "1", NULL), data.path = argc > 1 ? argv[1] : NULL; if (data.path) /* Set stream target if given on command line */ pw_properties_set(props, PW_KEY_TARGET_OBJECT, data.path); data.stream = pw_stream_new_simple( pw_main_loop_get_loop(data.loop), "video-play", props, &stream_events, &data); if (SDL_Init(SDL_INIT_VIDEO) < 0) { fprintf(stderr, "can't initialize SDL: %s\n", SDL_GetError()); return -1; } if (SDL_CreateWindowAndRenderer (WIDTH, HEIGHT, SDL_WINDOW_RESIZABLE, &data.window, &data.renderer)) { fprintf(stderr, "can't create window: %s\n", SDL_GetError()); return -1; } /* build the extra parameters to connect with. To connect, we can provide * a list of supported formats. We use a builder that writes the param * object to the stack. */ n_params = build_format(&data, &b, params); /* now connect the stream, we need a direction (input/output), * an optional target node to connect to, some flags and parameters */ if ((res = pw_stream_connect(data.stream, PW_DIRECTION_INPUT, PW_ID_ANY, PW_STREAM_FLAG_DRIVER | /* we're driver, we pull */ PW_STREAM_FLAG_AUTOCONNECT | /* try to automatically connect this stream */ PW_STREAM_FLAG_MAP_BUFFERS, /* mmap the buffer data for us */ params, n_params)) /* extra parameters, see above */ < 0) { fprintf(stderr, "can't connect: %s\n", spa_strerror(res)); return -1; } /* do things until we quit the mainloop */ pw_main_loop_run(data.loop); pw_stream_destroy(data.stream); pw_main_loop_destroy(data.loop); SDL_DestroyTexture(data.texture); if (data.cursor) SDL_DestroyTexture(data.cursor); SDL_DestroyRenderer(data.renderer); SDL_DestroyWindow(data.window); pw_deinit(); return 0; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/examples/video-play-reneg.c000066400000000000000000000266061511204443500264300ustar00rootroot00000000000000/* PipeWire */ /* SPDX-FileCopyrightText: Copyright © 2020 Wim Taymans */ /* SPDX-License-Identifier: MIT */ /* [title] Video input stream using \ref pw_stream "pw_stream", with format renegotiation. [title] */ #include #include #include #include #include #include #include #include #define WIDTH 640 #define HEIGHT 480 #define RATE 30 #define MAX_BUFFERS 64 #include "sdl.h" struct pixel { float r, g, b, a; }; struct data { const char *path; SDL_Renderer *renderer; SDL_Window *window; SDL_Texture *texture; SDL_Texture *cursor; struct pw_main_loop *loop; struct spa_source *timer; struct pw_stream *stream; struct spa_hook stream_listener; struct spa_video_info format; int32_t stride; struct spa_rectangle size; int counter; }; static void handle_events(struct data *data) { SDL_Event event; while (SDL_PollEvent(&event)) { switch (event.type) { case SDL_QUIT: pw_main_loop_quit(data->loop); break; } } } /* our data processing function is in general: * * struct pw_buffer *b; * b = pw_stream_dequeue_buffer(stream); * * .. do stuff with buffer ... * * pw_stream_queue_buffer(stream, b); */ static void on_process(void *_data) { struct data *data = _data; struct pw_stream *stream = data->stream; struct pw_buffer *b; struct spa_buffer *buf; void *sdata, *ddata; int sstride, dstride, ostride; uint32_t i; uint8_t *src, *dst; b = NULL; /* dequeue and queue old buffers, use the last available * buffer */ while (true) { struct pw_buffer *t; if ((t = pw_stream_dequeue_buffer(stream)) == NULL) break; if (b) pw_stream_queue_buffer(stream, b); b = t; } if (b == NULL) { pw_log_warn("out of buffers: %m"); return; } buf = b->buffer; pw_log_info("new buffer %p", buf); handle_events(data); if ((sdata = buf->datas[0].data) == NULL) goto done; if (SDL_LockTexture(data->texture, NULL, &ddata, &dstride) < 0) { fprintf(stderr, "Couldn't lock texture: %s\n", SDL_GetError()); goto done; } /* copy video image in texture */ sstride = buf->datas[0].chunk->stride; if (sstride == 0) sstride = buf->datas[0].chunk->size / data->size.height; ostride = SPA_MIN(sstride, dstride); src = sdata; dst = ddata; for (i = 0; i < data->size.height; i++) { memcpy(dst, src, ostride); src += sstride; dst += dstride; } SDL_UnlockTexture(data->texture); SDL_RenderClear(data->renderer); /* now render the video */ SDL_RenderCopy(data->renderer, data->texture, NULL, NULL); SDL_RenderPresent(data->renderer); done: pw_stream_queue_buffer(stream, b); } static void on_stream_state_changed(void *_data, enum pw_stream_state old, enum pw_stream_state state, const char *error) { struct data *data = _data; fprintf(stderr, "stream state: \"%s\"\n", pw_stream_state_as_string(state)); switch (state) { case PW_STREAM_STATE_UNCONNECTED: pw_main_loop_quit(data->loop); break; case PW_STREAM_STATE_PAUSED: pw_loop_update_timer(pw_main_loop_get_loop(data->loop), data->timer, NULL, NULL, false); break; case PW_STREAM_STATE_STREAMING: { struct timespec timeout, interval; timeout.tv_sec = 1; timeout.tv_nsec = 0; interval.tv_sec = 1; interval.tv_nsec = 0; pw_loop_update_timer(pw_main_loop_get_loop(data->loop), data->timer, &timeout, &interval, false); break; } default: break; } } /* Be notified when the stream param changes. We're only looking at the * format changes. * * We are now supposed to call pw_stream_finish_format() with success or * failure, depending on if we can support the format. Because we gave * a list of supported formats, this should be ok. * * As part of pw_stream_finish_format() we can provide parameters that * will control the buffer memory allocation. This includes the metadata * that we would like on our buffer, the size, alignment, etc. */ static void on_stream_param_changed(void *_data, uint32_t id, const struct spa_pod *param) { struct data *data = _data; struct pw_stream *stream = data->stream; uint8_t params_buffer[1024]; struct spa_pod_builder b = SPA_POD_BUILDER_INIT(params_buffer, sizeof(params_buffer)); const struct spa_pod *params[1]; uint32_t n_params = 0; Uint32 sdl_format; void *d; /* NULL means to clear the format */ if (param == NULL || id != SPA_PARAM_Format) return; fprintf(stderr, "got format:\n"); spa_debug_format(2, NULL, param); if (spa_format_parse(param, &data->format.media_type, &data->format.media_subtype) < 0) return; if (data->format.media_type != SPA_MEDIA_TYPE_video || data->format.media_subtype != SPA_MEDIA_SUBTYPE_raw) return; /* call a helper function to parse the format for us. */ spa_format_video_raw_parse(param, &data->format.info.raw); sdl_format = id_to_sdl_format(data->format.info.raw.format); data->size = data->format.info.raw.size; if (sdl_format == SDL_PIXELFORMAT_UNKNOWN) { pw_stream_set_error(stream, -EINVAL, "unknown pixel format"); return; } if (data->size.width == 0 || data->size.height == 0) { pw_stream_set_error(stream, -EINVAL, "invalid size"); return; } data->texture = SDL_CreateTexture(data->renderer, sdl_format, SDL_TEXTUREACCESS_STREAMING, data->size.width, data->size.height); if (SDL_LockTexture(data->texture, NULL, &d, &data->stride) < 0) { pw_stream_set_error(stream, -EINVAL, "invalid texture format"); return; } SDL_UnlockTexture(data->texture); /* a SPA_TYPE_OBJECT_ParamBuffers object defines the acceptable size, * number, stride etc of the buffers */ params[n_params++] = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers, SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(8, 2, MAX_BUFFERS), SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(1), SPA_PARAM_BUFFERS_size, SPA_POD_Int(data->stride * data->size.height), SPA_PARAM_BUFFERS_stride, SPA_POD_Int(data->stride), SPA_PARAM_BUFFERS_dataType, SPA_POD_CHOICE_FLAGS_Int((1<renderer, &info); params[n_params++] = sdl_build_formats(&info, b); fprintf(stderr, "supported SDL formats:\n"); spa_debug_format(2, NULL, params[0]); return n_params; } static int reneg_format(struct data *data) { uint8_t buffer[1024]; struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); const struct spa_pod *params[2]; uint32_t n_params = 0; int32_t width, height; if (data->format.info.raw.format == 0) return -EBUSY; width = data->counter & 1 ? 320 : 640; height = data->counter & 1 ? 240 : 480; fprintf(stderr, "renegotiate to %dx%d:\n", width, height); params[n_params++] = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), SPA_FORMAT_VIDEO_format, SPA_POD_Id(data->format.info.raw.format), SPA_FORMAT_VIDEO_size, SPA_POD_Rectangle(&SPA_RECTANGLE(width, height)), SPA_FORMAT_VIDEO_framerate, SPA_POD_Fraction(&data->format.info.raw.framerate)); pw_stream_update_params(data->stream, params, n_params); data->counter++; return 0; } static int reneg_buffers(struct data *data) { uint8_t buffer[1024]; struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); const struct spa_pod *params[2]; uint32_t n_params = 0; fprintf(stderr, "renegotiate buffers\n"); params[n_params++] = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers, SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(8, 2, MAX_BUFFERS), SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(1), SPA_PARAM_BUFFERS_size, SPA_POD_Int(data->stride * data->size.height), SPA_PARAM_BUFFERS_stride, SPA_POD_Int(data->stride)); pw_stream_update_params(data->stream, params, n_params); data->counter++; return 0; } static void on_timeout(void *userdata, uint64_t expirations) { struct data *data = userdata; if (1) reneg_format(data); else reneg_buffers(data); } static void do_quit(void *userdata, int signal_number) { struct data *data = userdata; pw_main_loop_quit(data->loop); } int main(int argc, char *argv[]) { struct data data = { 0, }; const struct spa_pod *params[2]; uint8_t buffer[1024]; struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); struct pw_properties *props; int res, n_params; pw_init(&argc, &argv); /* create a main loop */ data.loop = pw_main_loop_new(NULL); pw_loop_add_signal(pw_main_loop_get_loop(data.loop), SIGINT, do_quit, &data); pw_loop_add_signal(pw_main_loop_get_loop(data.loop), SIGTERM, do_quit, &data); /* create a simple stream, the simple stream manages to core and remote * objects for you if you don't need to deal with them * * If you plan to autoconnect your stream, you need to provide at least * media, category and role properties * * Pass your events and a user_data pointer as the last arguments. This * will inform you about the stream state. The most important event * you need to listen to is the process event where you need to consume * the data provided to you. */ props = pw_properties_new(PW_KEY_MEDIA_TYPE, "Video", PW_KEY_MEDIA_CATEGORY, "Capture", PW_KEY_MEDIA_ROLE, "Camera", NULL), data.path = argc > 1 ? argv[1] : NULL; if (data.path) /* Set stream target if given on command line */ pw_properties_set(props, PW_KEY_TARGET_OBJECT, data.path); data.stream = pw_stream_new_simple( pw_main_loop_get_loop(data.loop), "video-play-reneg", props, &stream_events, &data); if (SDL_Init(SDL_INIT_VIDEO) < 0) { fprintf(stderr, "can't initialize SDL: %s\n", SDL_GetError()); return -1; } if (SDL_CreateWindowAndRenderer (WIDTH, HEIGHT, SDL_WINDOW_RESIZABLE, &data.window, &data.renderer)) { fprintf(stderr, "can't create window: %s\n", SDL_GetError()); return -1; } /* build the extra parameters to connect with. To connect, we can provide * a list of supported formats. We use a builder that writes the param * object to the stack. */ n_params = build_format(&data, &b, params); /* now connect the stream, we need a direction (input/output), * an optional target node to connect to, some flags and parameters */ if ((res = pw_stream_connect(data.stream, PW_DIRECTION_INPUT, PW_ID_ANY, PW_STREAM_FLAG_AUTOCONNECT | /* try to automatically connect this stream */ PW_STREAM_FLAG_MAP_BUFFERS, /* mmap the buffer data for us */ params, n_params)) /* extra parameters, see above */ < 0) { fprintf(stderr, "can't connect: %s\n", spa_strerror(res)); return -1; } data.timer = pw_loop_add_timer(pw_main_loop_get_loop(data.loop), on_timeout, &data); /* do things until we quit the mainloop */ pw_main_loop_run(data.loop); pw_stream_destroy(data.stream); pw_main_loop_destroy(data.loop); SDL_DestroyTexture(data.texture); if (data.cursor) SDL_DestroyTexture(data.cursor); SDL_DestroyRenderer(data.renderer); SDL_DestroyWindow(data.window); pw_deinit(); return 0; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/examples/video-play-sync.c000066400000000000000000000411001511204443500262660ustar00rootroot00000000000000/* PipeWire */ /* SPDX-FileCopyrightText: Copyright © 2025 Wim Taymans */ /* SPDX-License-Identifier: MIT */ /* [title] Video input stream using \ref pw_stream "pw_stream" and sync timeline. [title] */ #include #include #include #include #include #include #include #include #include #include #include #include #define WIDTH 1920 #define HEIGHT 1080 #define RATE 30 #define MAX_BUFFERS 64 #include "sdl.h" struct pixel { float r, g, b, a; }; struct data { const char *path; SDL_Renderer *renderer; SDL_Window *window; SDL_Texture *texture; struct pw_main_loop *loop; struct pw_stream *stream; struct spa_hook stream_listener; struct spa_io_position *position; struct spa_video_info format; int32_t stride; struct spa_rectangle size; int counter; SDL_Rect rect; bool is_yuv; bool with_synctimeline; bool with_synctimeline_release; bool force_synctimeline_release; }; static void handle_events(struct data *data) { SDL_Event event; while (SDL_PollEvent(&event)) { switch (event.type) { case SDL_QUIT: pw_main_loop_quit(data->loop); break; } } } /* our data processing function is in general: * * struct pw_buffer *b; * b = pw_stream_dequeue_buffer(stream); * * .. do stuff with buffer ... * * pw_stream_queue_buffer(stream, b); */ static void on_process(void *_data) { struct data *data = _data; struct pw_stream *stream = data->stream; struct pw_buffer *b; struct spa_buffer *buf; void *sdata, *ddata; int sstride, dstride, ostride; struct spa_meta_header *h; struct spa_meta_sync_timeline *stl = NULL; uint32_t i, j; uint8_t *src, *dst; uint64_t cmd; b = NULL; while (true) { struct pw_buffer *t; if ((t = pw_stream_dequeue_buffer(stream)) == NULL) break; if (b) pw_stream_queue_buffer(stream, b); b = t; } if (b == NULL) { pw_log_warn("out of buffers: %m"); return; } buf = b->buffer; pw_log_trace("new buffer %p", buf); handle_events(data); if ((sdata = buf->datas[0].data) == NULL) goto done; if ((h = spa_buffer_find_meta_data(buf, SPA_META_Header, sizeof(*h)))) { uint64_t now = pw_stream_get_nsec(stream); pw_log_debug("now:%"PRIu64" pts:%"PRIu64" diff:%"PRIi64, now, h->pts, now - h->pts); } if ((stl = spa_buffer_find_meta_data(buf, SPA_META_SyncTimeline, sizeof(*stl))) && stl->acquire_point) { /* wait before we can use the buffer */ if (read(buf->datas[1].fd, &cmd, sizeof(cmd)) < 0) pw_log_warn("acquire_point wait error %m"); pw_log_debug("acquire_point:%"PRIu64, stl->acquire_point); } /* copy video image in texture */ if (data->is_yuv) { void *datas[4]; sstride = data->stride; if (buf->n_datas == 1) { SDL_UpdateTexture(data->texture, NULL, sdata, sstride); } else { datas[0] = sdata; datas[1] = buf->datas[1].data; datas[2] = buf->datas[2].data; SDL_UpdateYUVTexture(data->texture, NULL, datas[0], sstride, datas[1], sstride / 2, datas[2], sstride / 2); } } else { if (SDL_LockTexture(data->texture, NULL, &ddata, &dstride) < 0) { fprintf(stderr, "Couldn't lock texture: %s\n", SDL_GetError()); } sstride = buf->datas[0].chunk->stride; if (sstride == 0) sstride = buf->datas[0].chunk->size / data->size.height; ostride = SPA_MIN(sstride, dstride); src = sdata; dst = ddata; if (data->format.media_subtype == SPA_MEDIA_SUBTYPE_dsp) { for (i = 0; i < data->size.height; i++) { struct pixel *p = (struct pixel *) src; for (j = 0; j < data->size.width; j++) { dst[j * 4 + 0] = SPA_CLAMP((uint8_t)(p[j].r * 255.0f), 0u, 255u); dst[j * 4 + 1] = SPA_CLAMP((uint8_t)(p[j].g * 255.0f), 0u, 255u); dst[j * 4 + 2] = SPA_CLAMP((uint8_t)(p[j].b * 255.0f), 0u, 255u); dst[j * 4 + 3] = SPA_CLAMP((uint8_t)(p[j].a * 255.0f), 0u, 255u); } src += sstride; dst += dstride; } } else { for (i = 0; i < data->size.height; i++) { memcpy(dst, src, ostride); src += sstride; dst += dstride; } } SDL_UnlockTexture(data->texture); } SDL_RenderClear(data->renderer); /* now render the video */ SDL_RenderCopy(data->renderer, data->texture, &data->rect, NULL); SDL_RenderPresent(data->renderer); done: pw_stream_queue_buffer(stream, b); if (stl != NULL && stl->release_point) { /* we promise to signal the release point */ if (data->with_synctimeline_release) SPA_FLAG_CLEAR(stl->flags, SPA_META_SYNC_TIMELINE_UNSCHEDULED_RELEASE); cmd = 1; /* signal buffer release point */ write(buf->datas[2].fd, &cmd, sizeof(cmd)); pw_log_debug("release:%"PRIu64, stl->release_point); } } static void on_stream_state_changed(void *_data, enum pw_stream_state old, enum pw_stream_state state, const char *error) { struct data *data = _data; fprintf(stderr, "stream state: \"%s\"\n", pw_stream_state_as_string(state)); switch (state) { case PW_STREAM_STATE_UNCONNECTED: pw_main_loop_quit(data->loop); break; case PW_STREAM_STATE_PAUSED: /* because we started inactive, activate ourselves now */ pw_stream_set_active(data->stream, true); break; default: break; } } static void on_stream_io_changed(void *_data, uint32_t id, void *area, uint32_t size) { struct data *data = _data; switch (id) { case SPA_IO_Position: data->position = area; break; } } /* Be notified when the stream param changes. We're only looking at the * format changes. * * We are now supposed to call pw_stream_finish_format() with success or * failure, depending on if we can support the format. Because we gave * a list of supported formats, this should be ok. * * As part of pw_stream_finish_format() we can provide parameters that * will control the buffer memory allocation. This includes the metadata * that we would like on our buffer, the size, alignment, etc. */ static void on_stream_param_changed(void *_data, uint32_t id, const struct spa_pod *param) { struct data *data = _data; struct pw_stream *stream = data->stream; uint8_t params_buffer[1024]; struct spa_pod_builder b = SPA_POD_BUILDER_INIT(params_buffer, sizeof(params_buffer)); struct spa_pod_frame f; const struct spa_pod *params[5]; uint32_t n_params = 0; Uint32 sdl_format; void *d; int32_t mult, size, blocks; if (param != NULL && id == SPA_PARAM_Tag) { spa_debug_pod(0, NULL, param); return; } if (param != NULL && id == SPA_PARAM_Latency) { struct spa_latency_info info; if (spa_latency_parse(param, &info) >= 0) fprintf(stderr, "got latency: %"PRIu64"\n", (info.min_ns + info.max_ns) / 2); return; } /* NULL means to clear the format */ if (param == NULL || id != SPA_PARAM_Format) return; fprintf(stderr, "got format:\n"); spa_debug_format(2, NULL, param); if (spa_format_parse(param, &data->format.media_type, &data->format.media_subtype) < 0) return; if (data->format.media_type != SPA_MEDIA_TYPE_video) return; switch (data->format.media_subtype) { case SPA_MEDIA_SUBTYPE_raw: /* call a helper function to parse the format for us. */ spa_format_video_raw_parse(param, &data->format.info.raw); sdl_format = id_to_sdl_format(data->format.info.raw.format); data->size = SPA_RECTANGLE(data->format.info.raw.size.width, data->format.info.raw.size.height); mult = 1; break; case SPA_MEDIA_SUBTYPE_dsp: spa_format_video_dsp_parse(param, &data->format.info.dsp); if (data->format.info.dsp.format != SPA_VIDEO_FORMAT_DSP_F32) return; sdl_format = SDL_PIXELFORMAT_RGBA32; data->size = SPA_RECTANGLE(data->position->video.size.width, data->position->video.size.height); mult = 4; break; default: sdl_format = SDL_PIXELFORMAT_UNKNOWN; break; } if (sdl_format == SDL_PIXELFORMAT_UNKNOWN) { pw_stream_set_error(stream, -EINVAL, "unknown pixel format"); return; } if (data->size.width == 0 || data->size.height == 0) { pw_stream_set_error(stream, -EINVAL, "invalid size"); return; } data->texture = SDL_CreateTexture(data->renderer, sdl_format, SDL_TEXTUREACCESS_STREAMING, data->size.width, data->size.height); switch(sdl_format) { case SDL_PIXELFORMAT_YV12: case SDL_PIXELFORMAT_IYUV: data->stride = data->size.width; size = (data->stride * data->size.height) * 3 / 2; data->is_yuv = true; blocks = 3; break; case SDL_PIXELFORMAT_YUY2: data->is_yuv = true; data->stride = data->size.width * 2; size = (data->stride * data->size.height); blocks = 1; break; default: if (SDL_LockTexture(data->texture, NULL, &d, &data->stride) < 0) { fprintf(stderr, "Couldn't lock texture: %s\n", SDL_GetError()); data->stride = data->size.width * 2; } else SDL_UnlockTexture(data->texture); size = data->stride * data->size.height; blocks = 1; break; } data->rect.x = 0; data->rect.y = 0; data->rect.w = data->size.width; data->rect.h = data->size.height; if (data->with_synctimeline) { /* first add Buffer with 3 blocks (1 data, 2 sync fds). */ spa_pod_builder_push_object(&b, &f, SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers); spa_pod_builder_add(&b, SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(8, 2, MAX_BUFFERS), SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(3), SPA_PARAM_BUFFERS_size, SPA_POD_Int(size * mult), SPA_PARAM_BUFFERS_stride, SPA_POD_Int(data->stride * mult), SPA_PARAM_BUFFERS_dataType, SPA_POD_CHOICE_FLAGS_Int((1<with_synctimeline_release) { uint32_t flags = data->force_synctimeline_release ? /* both sides need compatible features */ SPA_POD_PROP_FLAG_MANDATORY : /* drop features flags if not provided by both sides */ SPA_POD_PROP_FLAG_DROP; spa_pod_builder_prop(&b, SPA_PARAM_META_features, flags); spa_pod_builder_int(&b, SPA_META_FEATURE_SYNC_TIMELINE_RELEASE); } params[n_params++] = spa_pod_builder_pop(&b, &f); } /* fallback for when the synctimeline is not negotiated */ params[n_params++] = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers, SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(8, 2, MAX_BUFFERS), SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(blocks), SPA_PARAM_BUFFERS_size, SPA_POD_Int(size * mult), SPA_PARAM_BUFFERS_stride, SPA_POD_Int(data->stride * mult), SPA_PARAM_BUFFERS_dataType, SPA_POD_CHOICE_FLAGS_Int((1<renderer, &info); params[n_params++] = sdl_build_formats(&info, b); fprintf(stderr, "supported SDL formats:\n"); spa_debug_format(2, NULL, params[0]); params[n_params++] = spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_dsp), SPA_FORMAT_VIDEO_format, SPA_POD_Id(SPA_VIDEO_FORMAT_DSP_F32)); fprintf(stderr, "supported DSP formats:\n"); spa_debug_format(2, NULL, params[1]); return n_params; } static void do_quit(void *userdata, int signal_number) { struct data *data = userdata; pw_main_loop_quit(data->loop); } static void show_help(struct data *data, const char *name, bool is_error) { FILE *fp; fp = is_error ? stderr : stdout; fprintf(fp, "%s [options]\n" " -h, --help Show this help\n" " --version Show version\n" " -r, --remote Remote daemon name\n" " -S, --sync Enable SyncTimeline\n" " -R, --release Enable RELEASE feature\n" " -F, --force-release RELEASE feature needs to be present\n" "\n", name); } int main(int argc, char *argv[]) { struct data data = { 0, }; const struct spa_pod *params[3]; uint8_t buffer[1024]; struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); struct pw_properties *props; int res, n_params; static const struct option long_options[] = { { "help", no_argument, NULL, 'h' }, { "version", no_argument, NULL, 'V' }, { "remote", required_argument, NULL, 'r' }, { "sync", no_argument, NULL, 'S' }, { "release", no_argument, NULL, 'R' }, { "force-release", no_argument, NULL, 'F' }, { NULL, 0, NULL, 0} }; char *opt_remote = NULL; int c; pw_init(&argc, &argv); while ((c = getopt_long(argc, argv, "hVr:SRF", long_options, NULL)) != -1) { switch (c) { case 'h': show_help(&data, argv[0], false); return 0; case 'V': printf("%s\n" "Compiled with libpipewire %s\n" "Linked with libpipewire %s\n", argv[0], pw_get_headers_version(), pw_get_library_version()); return 0; case 'r': opt_remote = optarg; break; case 'F': data.force_synctimeline_release = true; SPA_FALLTHROUGH; case 'R': data.with_synctimeline_release = true; SPA_FALLTHROUGH; case 'S': data.with_synctimeline = true; break; default: show_help(&data, argv[0], true); return -1; } } /* create a main loop */ data.loop = pw_main_loop_new(NULL); pw_loop_add_signal(pw_main_loop_get_loop(data.loop), SIGINT, do_quit, &data); pw_loop_add_signal(pw_main_loop_get_loop(data.loop), SIGTERM, do_quit, &data); /* create a simple stream, the simple stream manages to core and remote * objects for you if you don't need to deal with them * * If you plan to autoconnect your stream, you need to provide at least * media, category and role properties * * Pass your events and a user_data pointer as the last arguments. This * will inform you about the stream state. The most important event * you need to listen to is the process event where you need to consume * the data provided to you. */ props = pw_properties_new(PW_KEY_MEDIA_TYPE, "Video", PW_KEY_MEDIA_CATEGORY, "Capture", PW_KEY_MEDIA_ROLE, "Camera", PW_KEY_REMOTE_NAME, opt_remote, NULL), data.path = optind < argc ? argv[optind++] : "video-src-sync"; if (data.path) pw_properties_set(props, PW_KEY_TARGET_OBJECT, data.path); data.stream = pw_stream_new_simple( pw_main_loop_get_loop(data.loop), "video-play", props, &stream_events, &data); if (SDL_Init(SDL_INIT_VIDEO) < 0) { fprintf(stderr, "can't initialize SDL: %s\n", SDL_GetError()); return -1; } if (SDL_CreateWindowAndRenderer (WIDTH, HEIGHT, SDL_WINDOW_RESIZABLE, &data.window, &data.renderer)) { fprintf(stderr, "can't create window: %s\n", SDL_GetError()); return -1; } /* build the extra parameters to connect with. To connect, we can provide * a list of supported formats. We use a builder that writes the param * object to the stack. */ n_params = build_format(&data, &b, params); /* now connect the stream, we need a direction (input/output), * an optional target node to connect to, some flags and parameters */ if ((res = pw_stream_connect(data.stream, PW_DIRECTION_INPUT, PW_ID_ANY, PW_STREAM_FLAG_AUTOCONNECT | /* try to automatically connect this stream */ PW_STREAM_FLAG_INACTIVE | /* we will activate ourselves */ PW_STREAM_FLAG_MAP_BUFFERS, /* mmap the buffer data for us */ params, n_params)) /* extra parameters, see above */ < 0) { fprintf(stderr, "can't connect: %s\n", spa_strerror(res)); return -1; } /* do things until we quit the mainloop */ pw_main_loop_run(data.loop); pw_stream_destroy(data.stream); pw_main_loop_destroy(data.loop); SDL_DestroyTexture(data.texture); SDL_DestroyRenderer(data.renderer); SDL_DestroyWindow(data.window); pw_deinit(); return 0; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/examples/video-play.c000066400000000000000000000406221511204443500253240ustar00rootroot00000000000000/* PipeWire */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ /* [title] Video input stream using \ref pw_stream "pw_stream". [title] */ #include #include #include #include #include #include #include #include #include #include #include #include #include #define WIDTH 1920 #define HEIGHT 1080 #define RATE 30 #define MAX_BUFFERS 64 #include "sdl.h" struct pixel { float r, g, b, a; }; struct data { const char *path; SDL_Renderer *renderer; SDL_Window *window; SDL_Texture *texture; SDL_Texture *cursor; struct pw_main_loop *loop; struct pw_stream *stream; struct spa_hook stream_listener; struct spa_io_position *position; struct spa_video_info format; int32_t stride; struct spa_rectangle size; int counter; SDL_Rect rect; SDL_Rect cursor_rect; bool is_yuv; }; static void handle_events(struct data *data) { SDL_Event event; while (SDL_PollEvent(&event)) { switch (event.type) { case SDL_QUIT: pw_main_loop_quit(data->loop); break; } } } /* our data processing function is in general: * * struct pw_buffer *b; * b = pw_stream_dequeue_buffer(stream); * * .. do stuff with buffer ... * * pw_stream_queue_buffer(stream, b); */ static void on_process(void *_data) { struct data *data = _data; struct pw_stream *stream = data->stream; struct pw_buffer *b; struct spa_buffer *buf; void *sdata, *ddata; int sstride, dstride, ostride; struct spa_meta_region *mc; struct spa_meta_cursor *mcs; struct spa_meta_header *h; uint32_t i, j; uint8_t *src, *dst; bool render_cursor = false; b = NULL; while (true) { struct pw_buffer *t; if ((t = pw_stream_dequeue_buffer(stream)) == NULL) break; if (b) pw_stream_queue_buffer(stream, b); b = t; } if (b == NULL) { pw_log_warn("out of buffers: %m"); return; } buf = b->buffer; pw_log_trace("new buffer %p", buf); handle_events(data); if ((sdata = buf->datas[0].data) == NULL) goto done; if ((h = spa_buffer_find_meta_data(buf, SPA_META_Header, sizeof(*h)))) { uint64_t now = pw_stream_get_nsec(stream); pw_log_debug("now:%"PRIu64" pts:%"PRIu64" diff:%"PRIi64, now, h->pts, now - h->pts); } /* get the videocrop metadata if any */ if ((mc = spa_buffer_find_meta_data(buf, SPA_META_VideoCrop, sizeof(*mc))) && spa_meta_region_is_valid(mc)) { data->rect.x = mc->region.position.x; data->rect.y = mc->region.position.y; data->rect.w = mc->region.size.width; data->rect.h = mc->region.size.height; } /* get cursor metadata */ if ((mcs = spa_buffer_find_meta_data(buf, SPA_META_Cursor, sizeof(*mcs))) && spa_meta_cursor_is_valid(mcs)) { struct spa_meta_bitmap *mb; void *cdata; int cstride; data->cursor_rect.x = mcs->position.x; data->cursor_rect.y = mcs->position.y; mb = SPA_PTROFF(mcs, mcs->bitmap_offset, struct spa_meta_bitmap); data->cursor_rect.w = mb->size.width; data->cursor_rect.h = mb->size.height; if (data->cursor == NULL) { data->cursor = SDL_CreateTexture(data->renderer, id_to_sdl_format(mb->format), SDL_TEXTUREACCESS_STREAMING, mb->size.width, mb->size.height); SDL_SetTextureBlendMode(data->cursor, SDL_BLENDMODE_BLEND); } if (SDL_LockTexture(data->cursor, NULL, &cdata, &cstride) < 0) { fprintf(stderr, "Couldn't lock cursor texture: %s\n", SDL_GetError()); goto done; } /* copy the cursor bitmap into the texture */ src = SPA_PTROFF(mb, mb->offset, uint8_t); dst = cdata; ostride = SPA_MIN(cstride, mb->stride); for (i = 0; i < mb->size.height; i++) { memcpy(dst, src, ostride); dst += cstride; src += mb->stride; } SDL_UnlockTexture(data->cursor); render_cursor = true; } /* copy video image in texture */ if (data->is_yuv) { void *datas[4]; sstride = data->stride; if (buf->n_datas == 1) { SDL_UpdateTexture(data->texture, NULL, sdata, sstride); } else { datas[0] = sdata; datas[1] = buf->datas[1].data; datas[2] = buf->datas[2].data; SDL_UpdateYUVTexture(data->texture, NULL, datas[0], sstride, datas[1], sstride / 2, datas[2], sstride / 2); } } else { if (SDL_LockTexture(data->texture, NULL, &ddata, &dstride) < 0) { fprintf(stderr, "Couldn't lock texture: %s\n", SDL_GetError()); } sstride = buf->datas[0].chunk->stride; if (sstride == 0) sstride = buf->datas[0].chunk->size / data->size.height; ostride = SPA_MIN(sstride, dstride); src = sdata; dst = ddata; if (data->format.media_subtype == SPA_MEDIA_SUBTYPE_dsp) { for (i = 0; i < data->size.height; i++) { struct pixel *p = (struct pixel *) src; for (j = 0; j < data->size.width; j++) { dst[j * 4 + 0] = SPA_CLAMP((uint8_t)(p[j].r * 255.0f), 0u, 255u); dst[j * 4 + 1] = SPA_CLAMP((uint8_t)(p[j].g * 255.0f), 0u, 255u); dst[j * 4 + 2] = SPA_CLAMP((uint8_t)(p[j].b * 255.0f), 0u, 255u); dst[j * 4 + 3] = SPA_CLAMP((uint8_t)(p[j].a * 255.0f), 0u, 255u); } src += sstride; dst += dstride; } } else { for (i = 0; i < data->size.height; i++) { memcpy(dst, src, ostride); src += sstride; dst += dstride; } } SDL_UnlockTexture(data->texture); } SDL_RenderClear(data->renderer); /* now render the video and then the cursor if any */ SDL_RenderCopy(data->renderer, data->texture, &data->rect, NULL); if (render_cursor) { SDL_RenderCopy(data->renderer, data->cursor, NULL, &data->cursor_rect); } SDL_RenderPresent(data->renderer); done: pw_stream_queue_buffer(stream, b); } static void on_stream_state_changed(void *_data, enum pw_stream_state old, enum pw_stream_state state, const char *error) { struct data *data = _data; fprintf(stderr, "stream state: \"%s\"\n", pw_stream_state_as_string(state)); switch (state) { case PW_STREAM_STATE_UNCONNECTED: pw_main_loop_quit(data->loop); break; case PW_STREAM_STATE_PAUSED: /* because we started inactive, activate ourselves now */ pw_stream_set_active(data->stream, true); break; default: break; } } static void on_stream_io_changed(void *_data, uint32_t id, void *area, uint32_t size) { struct data *data = _data; switch (id) { case SPA_IO_Position: data->position = area; break; } } static void parse_peer_capability(struct data *data, const struct spa_pod *param) { struct spa_peer_param_info info; void *state = NULL; fprintf(stderr, "peer capability\n"); while (spa_peer_param_parse(param, &info, sizeof(info), &state) == 1) { struct spa_param_dict_info di; if (spa_param_dict_parse(info.param, &di, sizeof(di)) > 0) { struct spa_dict dict; struct spa_dict_item *items; const struct spa_dict_item *it; if (spa_param_dict_info_parse(&di, sizeof(di), &dict, NULL) < 0) return; items = alloca(sizeof(struct spa_dict_item) * dict.n_items); if (spa_param_dict_info_parse(&di, sizeof(di), &dict, items) < 0) return; spa_dict_for_each(it, &dict) fprintf(stderr, "peer:%u %s: %s\n", info.peer_id, it->key, it->value); } } } /* Be notified when the stream param changes. We're only looking at the * format changes. * * We are now supposed to call pw_stream_finish_format() with success or * failure, depending on if we can support the format. Because we gave * a list of supported formats, this should be ok. * * As part of pw_stream_finish_format() we can provide parameters that * will control the buffer memory allocation. This includes the metadata * that we would like on our buffer, the size, alignment, etc. */ static void on_stream_param_changed(void *_data, uint32_t id, const struct spa_pod *param) { struct data *data = _data; struct pw_stream *stream = data->stream; uint8_t params_buffer[1024]; struct spa_pod_builder b = SPA_POD_BUILDER_INIT(params_buffer, sizeof(params_buffer)); const struct spa_pod *params[5]; uint32_t n_params = 0; Uint32 sdl_format; void *d; int32_t mult, size, blocks; if (param != NULL && (id == SPA_PARAM_Tag || id == SPA_PARAM_PeerCapability)) { if (id == SPA_PARAM_PeerCapability) parse_peer_capability(data, param); else spa_debug_pod(0, NULL, param); return; } if (param != NULL && id == SPA_PARAM_Latency) { struct spa_latency_info info; if (spa_latency_parse(param, &info) >= 0) fprintf(stderr, "got latency: %"PRIu64"\n", (info.min_ns + info.max_ns) / 2); return; } /* NULL means to clear the format */ if (param == NULL || id != SPA_PARAM_Format) return; fprintf(stderr, "got format:\n"); spa_debug_format(2, NULL, param); if (spa_format_parse(param, &data->format.media_type, &data->format.media_subtype) < 0) return; if (data->format.media_type != SPA_MEDIA_TYPE_video) return; switch (data->format.media_subtype) { case SPA_MEDIA_SUBTYPE_raw: /* call a helper function to parse the format for us. */ spa_format_video_raw_parse(param, &data->format.info.raw); sdl_format = id_to_sdl_format(data->format.info.raw.format); data->size = SPA_RECTANGLE(data->format.info.raw.size.width, data->format.info.raw.size.height); mult = 1; break; case SPA_MEDIA_SUBTYPE_dsp: spa_format_video_dsp_parse(param, &data->format.info.dsp); if (data->format.info.dsp.format != SPA_VIDEO_FORMAT_DSP_F32) return; sdl_format = SDL_PIXELFORMAT_RGBA32; data->size = SPA_RECTANGLE(data->position->video.size.width, data->position->video.size.height); mult = 4; break; default: sdl_format = SDL_PIXELFORMAT_UNKNOWN; break; } if (sdl_format == SDL_PIXELFORMAT_UNKNOWN) { pw_stream_set_error(stream, -EINVAL, "unknown pixel format"); return; } if (data->size.width == 0 || data->size.height == 0) { pw_stream_set_error(stream, -EINVAL, "invalid size"); return; } data->texture = SDL_CreateTexture(data->renderer, sdl_format, SDL_TEXTUREACCESS_STREAMING, data->size.width, data->size.height); switch(sdl_format) { case SDL_PIXELFORMAT_YV12: case SDL_PIXELFORMAT_IYUV: data->stride = data->size.width; size = (data->stride * data->size.height) * 3 / 2; data->is_yuv = true; blocks = 3; break; case SDL_PIXELFORMAT_YUY2: data->is_yuv = true; data->stride = data->size.width * 2; size = (data->stride * data->size.height); blocks = 1; break; default: if (SDL_LockTexture(data->texture, NULL, &d, &data->stride) < 0) { fprintf(stderr, "Couldn't lock texture: %s\n", SDL_GetError()); data->stride = data->size.width * 2; } else SDL_UnlockTexture(data->texture); size = data->stride * data->size.height; blocks = 1; break; } data->rect.x = 0; data->rect.y = 0; data->rect.w = data->size.width; data->rect.h = data->size.height; /* a SPA_TYPE_OBJECT_ParamBuffers object defines the acceptable size, * number, stride etc of the buffers */ params[n_params++] = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers, SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(8, 2, MAX_BUFFERS), SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(blocks), SPA_PARAM_BUFFERS_size, SPA_POD_Int(size * mult), SPA_PARAM_BUFFERS_stride, SPA_POD_Int(data->stride * mult), SPA_PARAM_BUFFERS_dataType, SPA_POD_CHOICE_FLAGS_Int((1<renderer, &info); params[n_params++] = sdl_build_formats(&info, b); fprintf(stderr, "supported SDL formats:\n"); spa_debug_format(2, NULL, params[0]); params[n_params++] = spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_dsp), SPA_FORMAT_VIDEO_format, SPA_POD_Id(SPA_VIDEO_FORMAT_DSP_F32)); fprintf(stderr, "supported DSP formats:\n"); spa_debug_format(2, NULL, params[1]); return n_params; } static void do_quit(void *userdata, int signal_number) { struct data *data = userdata; pw_main_loop_quit(data->loop); } int main(int argc, char *argv[]) { struct data data = { 0, }; const struct spa_pod *params[3]; uint8_t buffer[1024]; struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); struct pw_properties *props; int res, n_params; pw_init(&argc, &argv); /* create a main loop */ data.loop = pw_main_loop_new(NULL); pw_loop_add_signal(pw_main_loop_get_loop(data.loop), SIGINT, do_quit, &data); pw_loop_add_signal(pw_main_loop_get_loop(data.loop), SIGTERM, do_quit, &data); /* create a simple stream, the simple stream manages to core and remote * objects for you if you don't need to deal with them * * If you plan to autoconnect your stream, you need to provide at least * media, category and role properties * * Pass your events and a user_data pointer as the last arguments. This * will inform you about the stream state. The most important event * you need to listen to is the process event where you need to consume * the data provided to you. */ props = pw_properties_new(PW_KEY_MEDIA_TYPE, "Video", PW_KEY_MEDIA_CATEGORY, "Capture", PW_KEY_MEDIA_ROLE, "Camera", NULL), data.path = argc > 1 ? argv[1] : NULL; if (data.path) pw_properties_set(props, PW_KEY_TARGET_OBJECT, data.path); data.stream = pw_stream_new_simple( pw_main_loop_get_loop(data.loop), "video-play", props, &stream_events, &data); if (SDL_Init(SDL_INIT_VIDEO) < 0) { fprintf(stderr, "can't initialize SDL: %s\n", SDL_GetError()); return -1; } if (SDL_CreateWindowAndRenderer (WIDTH, HEIGHT, SDL_WINDOW_RESIZABLE, &data.window, &data.renderer)) { fprintf(stderr, "can't create window: %s\n", SDL_GetError()); return -1; } /* build the extra parameters to connect with. To connect, we can provide * a list of supported formats. We use a builder that writes the param * object to the stack. */ n_params = build_format(&data, &b, params); { struct spa_pod_frame f; struct spa_dict_item items[1]; /* send a tag, input tags travel upstream */ spa_tag_build_start(&b, &f, SPA_PARAM_Tag, SPA_DIRECTION_INPUT); items[0] = SPA_DICT_ITEM_INIT("my-tag-other-key", "my-special-other-tag-value"); spa_tag_build_add_dict(&b, &SPA_DICT_INIT(items, 1)); params[n_params++] = spa_tag_build_end(&b, &f); } /* now connect the stream, we need a direction (input/output), * an optional target node to connect to, some flags and parameters */ if ((res = pw_stream_connect(data.stream, PW_DIRECTION_INPUT, PW_ID_ANY, PW_STREAM_FLAG_AUTOCONNECT | /* try to automatically connect this stream */ PW_STREAM_FLAG_INACTIVE | /* we will activate ourselves */ PW_STREAM_FLAG_MAP_BUFFERS, /* mmap the buffer data for us */ params, n_params)) /* extra parameters, see above */ < 0) { fprintf(stderr, "can't connect: %s\n", spa_strerror(res)); return -1; } /* do things until we quit the mainloop */ pw_main_loop_run(data.loop); pw_stream_destroy(data.stream); pw_main_loop_destroy(data.loop); SDL_DestroyTexture(data.texture); if (data.cursor) SDL_DestroyTexture(data.cursor); SDL_DestroyRenderer(data.renderer); SDL_DestroyWindow(data.window); pw_deinit(); return 0; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/examples/video-src-alloc.c000066400000000000000000000320751511204443500262410ustar00rootroot00000000000000/* PipeWire */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ /* [title] Allocating buffer memory and sending fds to the server. [title] */ #include "config.h" #include #include #include #include #include #include #include #include #include #define BPP 4 #define CURSOR_WIDTH 64 #define CURSOR_HEIGHT 64 #define CURSOR_BPP 4 #define MAX_BUFFERS 64 #define M_PI_M2 ( M_PI + M_PI ) struct data { struct pw_thread_loop *loop; struct spa_source *timer; struct pw_stream *stream; struct spa_hook stream_listener; struct spa_video_info_raw format; int32_t stride; int counter; uint32_t seq; double crop; double accumulator; }; static void draw_elipse(uint32_t *dst, int width, int height, uint32_t color) { int i, j, r1, r2, r12, r22, r122; r1 = width/2; r12 = r1 * r1; r2 = height/2; r22 = r2 * r2; r122 = r12 * r22; for (i = -r2; i < r2; i++) { for (j = -r1; j < r1; j++) { dst[(i + r2)*width+(j+r1)] = (i * i * r12 + j * j * r22 <= r122) ? color : 0x00000000; } } } /* called when we should push a new buffer in the queue */ static void on_process(void *userdata) { struct data *data = userdata; struct pw_buffer *b; struct spa_buffer *buf; uint32_t i, j; uint8_t *p; struct spa_meta *m; struct spa_meta_header *h; struct spa_meta_region *mc; struct spa_meta_cursor *mcs; if ((b = pw_stream_dequeue_buffer(data->stream)) == NULL) { pw_log_warn("out of buffers: %m"); return; } buf = b->buffer; if ((p = buf->datas[0].data) == NULL) return; if ((h = spa_buffer_find_meta_data(buf, SPA_META_Header, sizeof(*h)))) { #if 0 h->pts = pw_stream_get_nsec(data->stream)); #else h->pts = -1; #endif h->flags = 0; h->seq = data->seq++; h->dts_offset = 0; } if ((m = spa_buffer_find_meta(buf, SPA_META_VideoDamage))) { struct spa_meta_region *r = spa_meta_first(m); if (spa_meta_check(r, m)) { r->region.position = SPA_POINT(0,0); r->region.size = data->format.size; r++; } if (spa_meta_check(r, m)) r->region = SPA_REGION(0,0,0,0); } if ((mc = spa_buffer_find_meta_data(buf, SPA_META_VideoCrop, sizeof(*mc)))) { data->crop = (sin(data->accumulator) + 1.0) * 32.0; mc->region.position.x = (int32_t)data->crop; mc->region.position.y = (int32_t)data->crop; mc->region.size.width = data->format.size.width - (int32_t)(data->crop*2); mc->region.size.height = data->format.size.height - (int32_t)(data->crop*2); } if ((mcs = spa_buffer_find_meta_data(buf, SPA_META_Cursor, sizeof(*mcs)))) { struct spa_meta_bitmap *mb; uint32_t *bitmap, color; mcs->id = 1; mcs->position.x = (int32_t)((sin(data->accumulator) + 1.0) * 160.0 + 80); mcs->position.y = (int32_t)((cos(data->accumulator) + 1.0) * 100.0 + 50); mcs->hotspot.x = 0; mcs->hotspot.y = 0; mcs->bitmap_offset = sizeof(struct spa_meta_cursor); mb = SPA_PTROFF(mcs, mcs->bitmap_offset, struct spa_meta_bitmap); mb->format = SPA_VIDEO_FORMAT_ARGB; mb->size.width = CURSOR_WIDTH; mb->size.height = CURSOR_HEIGHT; mb->stride = CURSOR_WIDTH * CURSOR_BPP; mb->offset = sizeof(struct spa_meta_bitmap); bitmap = SPA_PTROFF(mb, mb->offset, uint32_t); color = (uint32_t)((cos(data->accumulator) + 1.0) * (1 << 23)); color |= 0xff000000; draw_elipse(bitmap, mb->size.width, mb->size.height, color); } for (i = 0; i < data->format.size.height; i++) { for (j = 0; j < data->format.size.width * BPP; j++) { p[j] = data->counter + j * i; } p += data->stride; data->counter += 13; } data->accumulator += M_PI_M2 / 50.0; if (data->accumulator >= M_PI_M2) data->accumulator -= M_PI_M2; buf->datas[0].chunk->offset = 0; buf->datas[0].chunk->size = data->format.size.height * data->stride; buf->datas[0].chunk->stride = data->stride; pw_stream_queue_buffer(data->stream, b); } /* trigger the graph when we are a driver */ static void on_timeout(void *userdata, uint64_t expirations) { struct data *data = userdata; pw_log_trace("timeout"); pw_stream_trigger_process(data->stream); } /* when the stream is STREAMING, start the timer at 40ms intervals * to produce and push a frame. In other states we PAUSE the timer. */ static void on_stream_state_changed(void *_data, enum pw_stream_state old, enum pw_stream_state state, const char *error) { struct data *data = _data; printf("stream state: \"%s\"\n", pw_stream_state_as_string(state)); switch (state) { case PW_STREAM_STATE_PAUSED: printf("node id: %d\n", pw_stream_get_node_id(data->stream)); pw_loop_update_timer(pw_thread_loop_get_loop(data->loop), data->timer, NULL, NULL, false); break; case PW_STREAM_STATE_STREAMING: { struct timespec timeout, interval; timeout.tv_sec = 0; timeout.tv_nsec = 1; interval.tv_sec = 0; interval.tv_nsec = 40 * SPA_NSEC_PER_MSEC; if (pw_stream_is_driving(data->stream)) pw_loop_update_timer(pw_thread_loop_get_loop(data->loop), data->timer, &timeout, &interval, false); break; } default: break; } } /* we set the PW_STREAM_FLAG_ALLOC_BUFFERS flag when connecting so we need * to provide buffer memory. */ static void on_stream_add_buffer(void *_data, struct pw_buffer *buffer) { struct data *data = _data; struct spa_buffer *buf = buffer->buffer; struct spa_data *d; #ifdef HAVE_MEMFD_CREATE unsigned int seals; #endif pw_log_info("add buffer %p", buffer); d = buf->datas; if ((d[0].type & (1<stride * data->format.size.height; /* truncate to the right size before we set seals */ if (ftruncate(d[0].fd, d[0].maxsize) < 0) { pw_log_error("can't truncate to %d: %m", d[0].maxsize); return; } #ifdef HAVE_MEMFD_CREATE /* not enforced yet but server might require SEAL_SHRINK later */ seals = F_SEAL_GROW | F_SEAL_SHRINK | F_SEAL_SEAL; if (fcntl(d[0].fd, F_ADD_SEALS, seals) == -1) { pw_log_warn("Failed to add seals: %m"); } #endif /* now mmap so we can write to it in the process function above */ d[0].data = mmap(NULL, d[0].maxsize, PROT_READ|PROT_WRITE, MAP_SHARED, d[0].fd, d[0].mapoffset); if (d[0].data == MAP_FAILED) { pw_log_error("can't mmap memory: %m"); return; } } /* close the memfd we set on the buffers here */ static void on_stream_remove_buffer(void *_data, struct pw_buffer *buffer) { struct spa_buffer *buf = buffer->buffer; struct spa_data *d; d = buf->datas; pw_log_info("remove buffer %p", buffer); munmap(d[0].data, d[0].maxsize); close(d[0].fd); } /* Be notified when the stream param changes. We're only looking at the * format param. * * We are now supposed to call pw_stream_update_params() with success or * failure, depending on if we can support the format. Because we gave * a list of supported formats, this should be ok. * * As part of pw_stream_update_params() we can provide parameters that * will control the buffer memory allocation. This includes the metadata * that we would like on our buffer, the size, alignment, etc. */ static void on_stream_param_changed(void *_data, uint32_t id, const struct spa_pod *param) { struct data *data = _data; struct pw_stream *stream = data->stream; uint8_t params_buffer[1024]; struct spa_pod_builder b = SPA_POD_BUILDER_INIT(params_buffer, sizeof(params_buffer)); const struct spa_pod *params[5]; uint32_t n_params = 0; if (param == NULL || id != SPA_PARAM_Format) return; spa_format_video_raw_parse(param, &data->format); data->stride = SPA_ROUND_UP_N(data->format.size.width * BPP, 4); params[n_params++] = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers, SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(8, 2, MAX_BUFFERS), SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(1), SPA_PARAM_BUFFERS_size, SPA_POD_Int(data->stride * data->format.size.height), SPA_PARAM_BUFFERS_stride, SPA_POD_Int(data->stride), SPA_PARAM_BUFFERS_dataType, SPA_POD_CHOICE_FLAGS_Int(1<loop, false); } int main(int argc, char *argv[]) { struct data data = { 0, }; const struct spa_pod *params[1]; uint32_t n_params = 0; uint8_t buffer[1024]; struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); pw_init(&argc, &argv); /* create a thread loop and start it */ data.loop = pw_thread_loop_new("video-src-alloc", NULL); /* take the lock around all PipeWire functions. In callbacks, the lock * is already taken for you but it's ok to lock again because the lock is * recursive */ pw_thread_loop_lock(data.loop); /* install some handlers to exit nicely */ pw_loop_add_signal(pw_thread_loop_get_loop(data.loop), SIGINT, do_quit, &data); pw_loop_add_signal(pw_thread_loop_get_loop(data.loop), SIGTERM, do_quit, &data); /* start after the signal handlers are set */ pw_thread_loop_start(data.loop); /* create a simple stream, the simple stream manages the core * object for you if you don't want to deal with them. * * We're making a new video provider. We need to set the media-class * property. * * Pass your events and a user_data pointer as the last arguments. This * will inform you about the stream state. The most important event * you need to listen to is the process event where you need to provide * the data. */ data.stream = pw_stream_new_simple( pw_thread_loop_get_loop(data.loop), "video-src-alloc", pw_properties_new( PW_KEY_MEDIA_CLASS, "Video/Source", NULL), &stream_events, &data); /* make a timer to schedule our frames */ data.timer = pw_loop_add_timer(pw_thread_loop_get_loop(data.loop), on_timeout, &data); /* build the extra parameter for the connection. Here we make an * EnumFormat parameter which lists the possible formats we can provide. * The server will select a format that matches and informs us about this * in the stream param_changed event. */ params[n_params++] = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), SPA_FORMAT_VIDEO_format, SPA_POD_Id(SPA_VIDEO_FORMAT_RGBA), SPA_FORMAT_VIDEO_size, SPA_POD_CHOICE_RANGE_Rectangle( &SPA_RECTANGLE(320, 240), &SPA_RECTANGLE(1, 1), &SPA_RECTANGLE(4096, 4096)), SPA_FORMAT_VIDEO_framerate, SPA_POD_Fraction(&SPA_FRACTION(25, 1))); /* now connect the stream, we need a direction (input/output), * an optional target node to connect to, some flags and parameters. * * Here we pass PW_STREAM_FLAG_ALLOC_BUFFERS. We should in the * add_buffer callback configure the buffer memory. This should be * fd backed memory (memfd, dma-buf, ...) that can be shared with * the server. */ pw_stream_connect(data.stream, PW_DIRECTION_OUTPUT, PW_ID_ANY, PW_STREAM_FLAG_DRIVER | PW_STREAM_FLAG_ALLOC_BUFFERS, params, n_params); /* unlock, run the loop and wait, this will trigger the callbacks */ pw_thread_loop_wait(data.loop); /* unlock before stop */ pw_thread_loop_unlock(data.loop); pw_thread_loop_stop(data.loop); pw_stream_destroy(data.stream); /* destroy after dependent objects are destroyed */ pw_thread_loop_destroy(data.loop); pw_deinit(); return 0; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/examples/video-src-fixate.c000066400000000000000000000431621511204443500264260ustar00rootroot00000000000000/* PipeWire */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ /* [title] Fixating negotiated modifiers. [title] */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #define BPP 4 #define CURSOR_WIDTH 64 #define CURSOR_HEIGHT 64 #define CURSOR_BPP 4 #define MAX_BUFFERS 64 #define M_PI_M2 ( M_PI + M_PI ) uint64_t supported_modifiers[] = {DRM_FORMAT_MOD_INVALID, DRM_FORMAT_MOD_LINEAR}; struct data { struct pw_thread_loop *loop; struct spa_source *timer; struct pw_stream *stream; struct spa_hook stream_listener; struct spa_video_info_raw format; int32_t stride; int counter; uint32_t seq; double crop; double accumulator; }; static void draw_elipse(uint32_t *dst, int width, int height, uint32_t color) { int i, j, r1, r2, r12, r22, r122; r1 = width/2; r12 = r1 * r1; r2 = height/2; r22 = r2 * r2; r122 = r12 * r22; for (i = -r2; i < r2; i++) { for (j = -r1; j < r1; j++) { dst[(i + r2)*width+(j+r1)] = (i * i * r12 + j * j * r22 <= r122) ? color : 0x00000000; } } } static struct spa_pod *fixate_format(struct spa_pod_builder *b, enum spa_video_format format, uint64_t *modifier) { struct spa_pod_frame f[1]; spa_pod_builder_push_object(b, &f[0], SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat); spa_pod_builder_add(b, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video), 0); spa_pod_builder_add(b, SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), 0); /* format */ spa_pod_builder_add(b, SPA_FORMAT_VIDEO_format, SPA_POD_Id(format), 0); /* modifiers */ if (modifier) { // we only support implicit modifiers, use shortpath to skip fixation phase spa_pod_builder_prop(b, SPA_FORMAT_VIDEO_modifier, SPA_POD_PROP_FLAG_MANDATORY); spa_pod_builder_long(b, *modifier); } spa_pod_builder_add(b, SPA_FORMAT_VIDEO_size, SPA_POD_CHOICE_RANGE_Rectangle( &SPA_RECTANGLE(320, 240), &SPA_RECTANGLE(1,1), &SPA_RECTANGLE(4096,4096)), 0); // variable framerate spa_pod_builder_add(b, SPA_FORMAT_VIDEO_framerate, SPA_POD_Fraction(&SPA_FRACTION(25, 1)), 0); return spa_pod_builder_pop(b, &f[0]); } static struct spa_pod *build_format(struct spa_pod_builder *b, enum spa_video_format format, uint64_t *modifiers, int modifier_count) { struct spa_pod_frame f[2]; int i, c; spa_pod_builder_push_object(b, &f[0], SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat); spa_pod_builder_add(b, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video), 0); spa_pod_builder_add(b, SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), 0); /* format */ spa_pod_builder_add(b, SPA_FORMAT_VIDEO_format, SPA_POD_Id(format), 0); /* modifiers */ if (modifier_count == 1 && modifiers[0] == DRM_FORMAT_MOD_INVALID) { // we only support implicit modifiers, use shortpath to skip fixation phase spa_pod_builder_prop(b, SPA_FORMAT_VIDEO_modifier, SPA_POD_PROP_FLAG_MANDATORY); spa_pod_builder_long(b, modifiers[0]); } else if (modifier_count > 0) { // build an enumeration of modifiers spa_pod_builder_prop(b, SPA_FORMAT_VIDEO_modifier, SPA_POD_PROP_FLAG_MANDATORY | SPA_POD_PROP_FLAG_DONT_FIXATE); spa_pod_builder_push_choice(b, &f[1], SPA_CHOICE_Enum, 0); // modifiers from the array for (i = 0, c = 0; i < modifier_count; i++) { spa_pod_builder_long(b, modifiers[i]); if (c++ == 0) spa_pod_builder_long(b, modifiers[i]); } spa_pod_builder_pop(b, &f[1]); } spa_pod_builder_add(b, SPA_FORMAT_VIDEO_size, SPA_POD_CHOICE_RANGE_Rectangle( &SPA_RECTANGLE(320, 240), &SPA_RECTANGLE(1,1), &SPA_RECTANGLE(4096,4096)), 0); // variable framerate spa_pod_builder_add(b, SPA_FORMAT_VIDEO_framerate, SPA_POD_Fraction(&SPA_FRACTION(25, 1)), 0); return spa_pod_builder_pop(b, &f[0]); } /* called when we should push a new buffer in the queue */ static void on_process(void *userdata) { struct data *data = userdata; struct pw_buffer *b; struct spa_buffer *buf; uint32_t i, j; uint8_t *p; struct spa_meta *m; struct spa_meta_header *h; struct spa_meta_region *mc; struct spa_meta_cursor *mcs; if ((b = pw_stream_dequeue_buffer(data->stream)) == NULL) { pw_log_warn("out of buffers: %m"); return; } buf = b->buffer; if ((p = buf->datas[0].data) == NULL) { printf("No data ptr\n"); goto done; } if ((h = spa_buffer_find_meta_data(buf, SPA_META_Header, sizeof(*h)))) { #if 0 h->pts = pw_stream_get_nsec(data->stream); #else h->pts = -1; #endif h->flags = 0; h->seq = data->seq++; h->dts_offset = 0; } if ((m = spa_buffer_find_meta(buf, SPA_META_VideoDamage))) { struct spa_meta_region *r = spa_meta_first(m); if (spa_meta_check(r, m)) { r->region.position = SPA_POINT(0,0); r->region.size = data->format.size; r++; } if (spa_meta_check(r, m)) r->region = SPA_REGION(0,0,0,0); } if ((mc = spa_buffer_find_meta_data(buf, SPA_META_VideoCrop, sizeof(*mc)))) { data->crop = (sin(data->accumulator) + 1.0) * 32.0; mc->region.position.x = (int32_t)data->crop; mc->region.position.y = (int32_t)data->crop; mc->region.size.width = data->format.size.width - (int32_t)(data->crop*2); mc->region.size.height = data->format.size.height - (int32_t)(data->crop*2); } if ((mcs = spa_buffer_find_meta_data(buf, SPA_META_Cursor, sizeof(*mcs)))) { struct spa_meta_bitmap *mb; uint32_t *bitmap, color; mcs->id = 1; mcs->position.x = (int32_t)((sin(data->accumulator) + 1.0) * 160.0 + 80); mcs->position.y = (int32_t)((cos(data->accumulator) + 1.0) * 100.0 + 50); mcs->hotspot.x = 0; mcs->hotspot.y = 0; mcs->bitmap_offset = sizeof(struct spa_meta_cursor); mb = SPA_PTROFF(mcs, mcs->bitmap_offset, struct spa_meta_bitmap); mb->format = SPA_VIDEO_FORMAT_ARGB; mb->size.width = CURSOR_WIDTH; mb->size.height = CURSOR_HEIGHT; mb->stride = CURSOR_WIDTH * CURSOR_BPP; mb->offset = sizeof(struct spa_meta_bitmap); bitmap = SPA_PTROFF(mb, mb->offset, uint32_t); color = (uint32_t)((cos(data->accumulator) + 1.0) * (1 << 23)); color |= 0xff000000; draw_elipse(bitmap, mb->size.width, mb->size.height, color); } for (i = 0; i < data->format.size.height; i++) { for (j = 0; j < data->format.size.width * BPP; j++) { p[j] = data->counter + j * i; } p += data->stride; data->counter += 13; } data->accumulator += M_PI_M2 / 50.0; if (data->accumulator >= M_PI_M2) data->accumulator -= M_PI_M2; buf->datas[0].chunk->offset = 0; buf->datas[0].chunk->size = data->format.size.height * data->stride; buf->datas[0].chunk->stride = data->stride; done: pw_stream_queue_buffer(data->stream, b); } /* trigger the graph when we are a driver */ static void on_timeout(void *userdata, uint64_t expirations) { struct data *data = userdata; pw_log_trace("timeout"); pw_stream_trigger_process(data->stream); } /* when the stream is STREAMING, start the timer at 40ms intervals * to produce and push a frame. In other states we PAUSE the timer. */ static void on_stream_state_changed(void *_data, enum pw_stream_state old, enum pw_stream_state state, const char *error) { struct data *data = _data; printf("stream state: \"%s\"\n", pw_stream_state_as_string(state)); switch (state) { case PW_STREAM_STATE_PAUSED: printf("node id: %d\n", pw_stream_get_node_id(data->stream)); pw_loop_update_timer(pw_thread_loop_get_loop(data->loop), data->timer, NULL, NULL, false); break; case PW_STREAM_STATE_STREAMING: { struct timespec timeout, interval; timeout.tv_sec = 0; timeout.tv_nsec = 1; interval.tv_sec = 0; interval.tv_nsec = 40 * SPA_NSEC_PER_MSEC; if (pw_stream_is_driving(data->stream)) pw_loop_update_timer(pw_thread_loop_get_loop(data->loop), data->timer, &timeout, &interval, false); break; } default: break; } } /* we set the PW_STREAM_FLAG_ALLOC_BUFFERS flag when connecting so we need * to provide buffer memory. */ static void on_stream_add_buffer(void *_data, struct pw_buffer *buffer) { printf("add_buffer\n"); struct data *data = _data; struct spa_buffer *buf = buffer->buffer; struct spa_data *d; #ifdef HAVE_MEMFD_CREATE unsigned int seals; #endif pw_log_info("add buffer %p", buffer); d = buf->datas; if ((d[0].type & (1< 0) { printf("pretend to support dmabufs while setting the fd to -1\n"); d[0].type = SPA_DATA_DmaBuf; d[0].fd = -1; d[0].data = NULL; return; } if ((d[0].type & (1<stride * data->format.size.height; /* truncate to the right size before we set seals */ if (ftruncate(d[0].fd, d[0].maxsize) < 0) { pw_log_error("can't truncate to %d: %m", d[0].maxsize); return; } #ifdef HAVE_MEMFD_CREATE /* not enforced yet but server might require SEAL_SHRINK later */ seals = F_SEAL_GROW | F_SEAL_SHRINK | F_SEAL_SEAL; if (fcntl(d[0].fd, F_ADD_SEALS, seals) == -1) { pw_log_warn("Failed to add seals: %m"); } #endif /* now mmap so we can write to it in the process function above */ d[0].data = mmap(NULL, d[0].maxsize, PROT_READ|PROT_WRITE, MAP_SHARED, d[0].fd, d[0].mapoffset); if (d[0].data == MAP_FAILED) { pw_log_error("can't mmap memory: %m"); return; } } /* close the memfd we set on the buffers here */ static void on_stream_remove_buffer(void *_data, struct pw_buffer *buffer) { printf("remove_buffer\n"); struct spa_buffer *buf = buffer->buffer; struct spa_data *d; d = buf->datas; pw_log_info("remove buffer %p", buffer); if ((d[0].type & (1<stream; uint8_t params_buffer[1024]; struct spa_pod_builder b = SPA_POD_BUILDER_INIT(params_buffer, sizeof(params_buffer)); const struct spa_pod *params[5]; uint32_t n_params = 0; int blocks, size, stride, buffertypes; if (param == NULL || id != SPA_PARAM_Format) return; printf("param changed: \n"); spa_debug_format(4, NULL, param); spa_format_video_raw_parse(param, &data->format); data->stride = SPA_ROUND_UP_N(data->format.size.width * BPP, 4); const struct spa_pod_prop *prop_modifier; // check if client supports modifier if ((prop_modifier = spa_pod_find_prop(param, NULL, SPA_FORMAT_VIDEO_modifier)) == NULL) { blocks = 1; size = data->stride * data->format.size.height; stride = data->stride; buffertypes = (1<flags & SPA_POD_PROP_FLAG_DONT_FIXATE) > 0) { const struct spa_pod *pod_modifier = &prop_modifier->value; printf("fixating format\n"); uint32_t n_modifiers = SPA_POD_CHOICE_N_VALUES(pod_modifier); uint64_t *modifiers = SPA_POD_CHOICE_VALUES(pod_modifier); uint64_t modifier; // shortcut for the old gbm allocator path if (n_modifiers == 1 && modifiers[0] == DRM_FORMAT_MOD_INVALID) { modifier = modifiers[0]; } else { // Use the allocator to find the best modifier from the list modifier = modifiers[rand()%n_modifiers]; } params[n_params++] = fixate_format(&b, SPA_VIDEO_FORMAT_RGBA, &modifier); params[n_params++] = build_format(&b, SPA_VIDEO_FORMAT_RGBA, supported_modifiers, SPA_N_ELEMENTS(supported_modifiers)); params[n_params++] = build_format(&b, SPA_VIDEO_FORMAT_RGBA, NULL, 0); printf("announcing fixated EnumFormats\n"); for (unsigned int i=0; i < 3; i++) { spa_debug_format(4, NULL, params[i]); } pw_stream_update_params(stream, params, n_params); return; } printf("no fixation required\n"); blocks = 1; size = data->stride * data->format.size.height; stride = data->stride; buffertypes = (1<loop, false); } int main(int argc, char *argv[]) { struct data data = { 0, }; const struct spa_pod *params[2]; uint32_t n_params = 0; uint8_t buffer[1024]; struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); srand(32); pw_init(&argc, &argv); /* create a thread loop and start it */ data.loop = pw_thread_loop_new("video-src-fixate", NULL); /* take the lock around all PipeWire functions. In callbacks, the lock * is already taken for you but it's ok to lock again because the lock is * recursive */ pw_thread_loop_lock(data.loop); /* install some handlers to exit nicely */ pw_loop_add_signal(pw_thread_loop_get_loop(data.loop), SIGINT, do_quit, &data); pw_loop_add_signal(pw_thread_loop_get_loop(data.loop), SIGTERM, do_quit, &data); /* start after the signal handlers are set */ pw_thread_loop_start(data.loop); /* create a simple stream, the simple stream manages the core * object for you if you don't want to deal with them. * * We're making a new video provider. We need to set the media-class * property. * * Pass your events and a user_data pointer as the last arguments. This * will inform you about the stream state. The most important event * you need to listen to is the process event where you need to provide * the data. */ data.stream = pw_stream_new_simple( pw_thread_loop_get_loop(data.loop), "video-src-fixate", pw_properties_new( PW_KEY_MEDIA_CLASS, "Video/Source", NULL), &stream_events, &data); /* make a timer to schedule our frames */ data.timer = pw_loop_add_timer(pw_thread_loop_get_loop(data.loop), on_timeout, &data); /* build the extra parameter for the connection. Here we make an * EnumFormat parameter which lists the possible formats we can provide. * The server will select a format that matches and informs us about this * in the stream param_changed event. */ params[n_params++] = build_format(&b, SPA_VIDEO_FORMAT_RGBA, supported_modifiers, SPA_N_ELEMENTS(supported_modifiers)); params[n_params++] = build_format(&b, SPA_VIDEO_FORMAT_RGBA, NULL, 0); printf("announcing starting EnumFormats\n"); for (unsigned int i=0; i < 2; i++) { spa_debug_format(4, NULL, params[i]); } /* now connect the stream, we need a direction (input/output), * an optional target node to connect to, some flags and parameters. * * Here we pass PW_STREAM_FLAG_ALLOC_BUFFERS. We should in the * add_buffer callback configure the buffer memory. This should be * fd backed memory (memfd, dma-buf, ...) that can be shared with * the server. */ pw_stream_connect(data.stream, PW_DIRECTION_OUTPUT, PW_ID_ANY, PW_STREAM_FLAG_DRIVER | PW_STREAM_FLAG_ALLOC_BUFFERS, params, n_params); /* unlock, run the loop and wait, this will trigger the callbacks */ pw_thread_loop_wait(data.loop); /* unlock before stop */ pw_thread_loop_unlock(data.loop); pw_thread_loop_stop(data.loop); pw_stream_destroy(data.stream); /* destroy after dependent objects are destroyed */ pw_thread_loop_destroy(data.loop); pw_deinit(); return 0; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/examples/video-src-reneg.c000066400000000000000000000350701511204443500262450ustar00rootroot00000000000000/* PipeWire */ /* SPDX-FileCopyrightText: Copyright © 2020 Wim Taymans */ /* SPDX-License-Identifier: MIT */ /* [title] Renegotiating video producer and consumer formats with \ref pw_stream [title] */ #include "config.h" #include #include #include #include #include #include #include #include #include #define BPP 4 #define CURSOR_WIDTH 64 #define CURSOR_HEIGHT 64 #define CURSOR_BPP 4 #define MAX_BUFFERS 64 #define M_PI_M2 ( M_PI + M_PI ) struct data { struct pw_thread_loop *loop; struct spa_source *timer; struct spa_source *reneg_timer; struct pw_stream *stream; struct spa_hook stream_listener; struct spa_video_info_raw format; int32_t stride; int counter; int cycle; uint32_t seq; double crop; double accumulator; }; static void draw_elipse(uint32_t *dst, int width, int height, uint32_t color) { int i, j, r1, r2, r12, r22, r122; r1 = width/2; r12 = r1 * r1; r2 = height/2; r22 = r2 * r2; r122 = r12 * r22; for (i = -r2; i < r2; i++) { for (j = -r1; j < r1; j++) { dst[(i + r2)*width+(j+r1)] = (i * i * r12 + j * j * r22 <= r122) ? color : 0x00000000; } } } /* called when we should push a new buffer in the queue */ static void on_process(void *userdata) { struct data *data = userdata; struct pw_buffer *b; struct spa_buffer *buf; uint32_t i, j; uint8_t *p; struct spa_meta *m; struct spa_meta_header *h; struct spa_meta_region *mc; struct spa_meta_cursor *mcs; pw_log_trace("timeout"); if ((b = pw_stream_dequeue_buffer(data->stream)) == NULL) { pw_log_warn("out of buffers: %m"); return; } buf = b->buffer; if ((p = buf->datas[0].data) == NULL) return; if ((h = spa_buffer_find_meta_data(buf, SPA_META_Header, sizeof(*h)))) { #if 0 h->pts = pw_stream_get_nsec(data->stream); #else h->pts = -1; #endif h->flags = 0; h->seq = data->seq++; h->dts_offset = 0; } if ((m = spa_buffer_find_meta(buf, SPA_META_VideoDamage))) { struct spa_meta_region *r = spa_meta_first(m); if (spa_meta_check(r, m)) { r->region.position = SPA_POINT(0,0); r->region.size = data->format.size; r++; } if (spa_meta_check(r, m)) r->region = SPA_REGION(0,0,0,0); } if ((mc = spa_buffer_find_meta_data(buf, SPA_META_VideoCrop, sizeof(*mc)))) { data->crop = (sin(data->accumulator) + 1.0) * 32.0; mc->region.position.x = (int32_t)data->crop; mc->region.position.y = (int32_t)data->crop; mc->region.size.width = data->format.size.width - (int32_t)(data->crop*2); mc->region.size.height = data->format.size.height - (int32_t)(data->crop*2); } if ((mcs = spa_buffer_find_meta_data(buf, SPA_META_Cursor, sizeof(*mcs)))) { struct spa_meta_bitmap *mb; uint32_t *bitmap, color; mcs->id = 1; mcs->position.x = (int32_t)((sin(data->accumulator) + 1.0) * 160.0 + 80); mcs->position.y = (int32_t)((cos(data->accumulator) + 1.0) * 100.0 + 50); mcs->hotspot.x = 0; mcs->hotspot.y = 0; mcs->bitmap_offset = sizeof(struct spa_meta_cursor); mb = SPA_PTROFF(mcs, mcs->bitmap_offset, struct spa_meta_bitmap); mb->format = SPA_VIDEO_FORMAT_ARGB; mb->size.width = CURSOR_WIDTH; mb->size.height = CURSOR_HEIGHT; mb->stride = CURSOR_WIDTH * CURSOR_BPP; mb->offset = sizeof(struct spa_meta_bitmap); bitmap = SPA_PTROFF(mb, mb->offset, uint32_t); color = (uint32_t)((cos(data->accumulator) + 1.0) * (1 << 23)); color |= 0xff000000; draw_elipse(bitmap, mb->size.width, mb->size.height, color); } for (i = 0; i < data->format.size.height; i++) { for (j = 0; j < data->format.size.width * BPP; j++) { p[j] = data->counter + j * i; } p += data->stride; data->counter += 13; } data->accumulator += M_PI_M2 / 50.0; if (data->accumulator >= M_PI_M2) data->accumulator -= M_PI_M2; buf->datas[0].chunk->offset = 0; buf->datas[0].chunk->size = data->format.size.height * data->stride; buf->datas[0].chunk->stride = data->stride; pw_stream_queue_buffer(data->stream, b); } /* called on timeout and we should start the graph */ static void on_timeout(void *userdata, uint64_t expirations) { struct data *data = userdata; pw_log_trace("timeout"); pw_stream_trigger_process(data->stream); } /* when the stream is STREAMING, start the timer at 40ms intervals * to produce and push a frame. In other states we PAUSE the timer. */ static void on_stream_state_changed(void *_data, enum pw_stream_state old, enum pw_stream_state state, const char *error) { struct data *data = _data; printf("stream state: \"%s\"\n", pw_stream_state_as_string(state)); switch (state) { case PW_STREAM_STATE_PAUSED: printf("node id: %d\n", pw_stream_get_node_id(data->stream)); pw_loop_update_timer(pw_thread_loop_get_loop(data->loop), data->timer, NULL, NULL, false); pw_loop_update_timer(pw_thread_loop_get_loop(data->loop), data->reneg_timer, NULL, NULL, false); break; case PW_STREAM_STATE_STREAMING: { struct timespec timeout, interval; timeout.tv_sec = 0; timeout.tv_nsec = 1; interval.tv_sec = 0; interval.tv_nsec = 40 * SPA_NSEC_PER_MSEC; if (pw_stream_is_driving(data->stream)) pw_loop_update_timer(pw_thread_loop_get_loop(data->loop), data->timer, &timeout, &interval, false); timeout.tv_sec = 1; timeout.tv_nsec = 0; interval.tv_sec = 1; interval.tv_nsec = 0; pw_loop_update_timer(pw_thread_loop_get_loop(data->loop), data->reneg_timer, &timeout, &interval, false); break; } default: break; } } /* we set the PW_STREAM_FLAG_ALLOC_BUFFERS flag when connecting so we need * to provide buffer memory. */ static void on_stream_add_buffer(void *_data, struct pw_buffer *buffer) { struct data *data = _data; struct spa_buffer *buf = buffer->buffer; struct spa_data *d; #ifdef HAVE_MEMFD_CREATE unsigned int seals; #endif pw_log_info("add buffer %p", buffer); d = buf->datas; if ((d[0].type & (1<stride * data->format.size.height; /* truncate to the right size before we set seals */ if (ftruncate(d[0].fd, d[0].maxsize) < 0) { pw_log_error("can't truncate to %d: %m", d[0].maxsize); return; } #ifdef HAVE_MEMFD_CREATE /* not enforced yet but server might require SEAL_SHRINK later */ seals = F_SEAL_GROW | F_SEAL_SHRINK | F_SEAL_SEAL; if (fcntl(d[0].fd, F_ADD_SEALS, seals) == -1) { pw_log_warn("Failed to add seals: %m"); } #endif /* now mmap so we can write to it in the process function above */ d[0].data = mmap(NULL, d[0].maxsize, PROT_READ|PROT_WRITE, MAP_SHARED, d[0].fd, d[0].mapoffset); if (d[0].data == MAP_FAILED) { pw_log_error("can't mmap memory: %m"); return; } } /* close the memfd we set on the buffers here */ static void on_stream_remove_buffer(void *_data, struct pw_buffer *buffer) { struct spa_buffer *buf = buffer->buffer; struct spa_data *d; d = buf->datas; pw_log_info("remove buffer %p", buffer); munmap(d[0].data, d[0].maxsize); close(d[0].fd); } /* Be notified when the stream param changes. We're only looking at the * format param. * * We are now supposed to call pw_stream_update_params() with success or * failure, depending on if we can support the format. Because we gave * a list of supported formats, this should be ok. * * As part of pw_stream_update_params() we can provide parameters that * will control the buffer memory allocation. This includes the metadata * that we would like on our buffer, the size, alignment, etc. */ static void on_stream_param_changed(void *_data, uint32_t id, const struct spa_pod *param) { struct data *data = _data; struct pw_stream *stream = data->stream; uint8_t params_buffer[1024]; struct spa_pod_builder b = SPA_POD_BUILDER_INIT(params_buffer, sizeof(params_buffer)); const struct spa_pod *params[5]; uint32_t n_params = 0; if (param == NULL || id != SPA_PARAM_Format) return; pw_log_info("format changed"); spa_format_video_raw_parse(param, &data->format); data->stride = SPA_ROUND_UP_N(data->format.size.width * BPP, 4); params[n_params++] = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers, SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(8, 2, MAX_BUFFERS), SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(1), SPA_PARAM_BUFFERS_size, SPA_POD_Int(data->stride * data->format.size.height), SPA_PARAM_BUFFERS_stride, SPA_POD_Int(data->stride), SPA_PARAM_BUFFERS_dataType, SPA_POD_CHOICE_FLAGS_Int(1<cycle & 1 ? 320 : 640; height = data->cycle & 1 ? 240 : 480; fprintf(stderr, "renegotiate to %dx%d:\n", width, height); params[n_params++] = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), SPA_FORMAT_VIDEO_format, SPA_POD_Id(SPA_VIDEO_FORMAT_RGBA), SPA_FORMAT_VIDEO_size, SPA_POD_Rectangle(&SPA_RECTANGLE(width, height)), SPA_FORMAT_VIDEO_framerate, SPA_POD_Fraction(&SPA_FRACTION(25, 1))); pw_stream_update_params(data->stream, params, n_params); data->cycle++; } static void do_quit(void *userdata, int signal_number) { struct data *data = userdata; pw_thread_loop_signal(data->loop, false); } int main(int argc, char *argv[]) { struct data data = { 0, }; const struct spa_pod *params[1]; uint32_t n_params = 0; uint8_t buffer[1024]; struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); pw_init(&argc, &argv); /* create a thread loop and start it */ data.loop = pw_thread_loop_new("video-src-reneg", NULL); /* take the lock around all PipeWire functions. In callbacks, the lock * is already taken for you but it's ok to lock again because the lock is * recursive */ pw_thread_loop_lock(data.loop); /* install some handlers to exit nicely */ pw_loop_add_signal(pw_thread_loop_get_loop(data.loop), SIGINT, do_quit, &data); pw_loop_add_signal(pw_thread_loop_get_loop(data.loop), SIGTERM, do_quit, &data); /* start after the signal handlers are set */ pw_thread_loop_start(data.loop); /* create a simple stream, the simple stream manages the core * object for you if you don't want to deal with them. * * We're making a new video provider. We need to set the media-class * property. * * Pass your events and a user_data pointer as the last arguments. This * will inform you about the stream state. The most important event * you need to listen to is the process event where you need to provide * the data. */ data.stream = pw_stream_new_simple( pw_thread_loop_get_loop(data.loop), "video-src-alloc", pw_properties_new( PW_KEY_MEDIA_CLASS, "Video/Source", NULL), &stream_events, &data); /* make a timer to schedule our frames */ data.timer = pw_loop_add_timer(pw_thread_loop_get_loop(data.loop), on_timeout, &data); /* make a timer to schedule renegotiation */ data.reneg_timer = pw_loop_add_timer(pw_thread_loop_get_loop(data.loop), on_reneg_timeout, &data); /* build the extra parameter for the connection. Here we make an * EnumFormat parameter which lists the possible formats we can provide. * The server will select a format that matches and informs us about this * in the stream param_changed event. */ params[n_params++] = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), SPA_FORMAT_VIDEO_format, SPA_POD_Id(SPA_VIDEO_FORMAT_RGBA), SPA_FORMAT_VIDEO_size, SPA_POD_CHOICE_RANGE_Rectangle( &SPA_RECTANGLE(320, 240), &SPA_RECTANGLE(1, 1), &SPA_RECTANGLE(4096, 4096)), SPA_FORMAT_VIDEO_framerate, SPA_POD_Fraction(&SPA_FRACTION(25, 1))); /* now connect the stream, we need a direction (input/output), * an optional target node to connect to, some flags and parameters. * * Here we pass PW_STREAM_FLAG_ALLOC_BUFFERS. We should in the * add_buffer callback configure the buffer memory. This should be * fd backed memory (memfd, dma-buf, ...) that can be shared with * the server. */ pw_stream_connect(data.stream, PW_DIRECTION_OUTPUT, PW_ID_ANY, PW_STREAM_FLAG_DRIVER | PW_STREAM_FLAG_ALLOC_BUFFERS, params, n_params); /* unlock, run the loop and wait, this will trigger the callbacks */ pw_thread_loop_wait(data.loop); /* unlock before stop */ pw_thread_loop_unlock(data.loop); pw_thread_loop_stop(data.loop); pw_stream_destroy(data.stream); /* destroy after dependent objects are destroyed */ pw_thread_loop_destroy(data.loop); pw_deinit(); return 0; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/examples/video-src-sync.c000066400000000000000000000360361511204443500261240ustar00rootroot00000000000000/* PipeWire */ /* SPDX-FileCopyrightText: Copyright © 2025 Wim Taymans */ /* SPDX-License-Identifier: MIT */ /* [title] Video source using \ref pw_stream and sync_timeline. [title] */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define BPP 4 #define CURSOR_WIDTH 64 #define CURSOR_HEIGHT 64 #define CURSOR_BPP 4 #define MAX_BUFFERS 64 #define M_PI_M2 ( M_PI + M_PI ) struct data { struct pw_main_loop *loop; struct spa_source *timer; struct pw_context *context; struct pw_core *core; struct pw_stream *stream; struct spa_hook stream_listener; struct spa_video_info_raw format; int32_t stride; int counter; uint32_t seq; uint32_t n_buffers; int res; bool with_synctimeline; bool with_synctimeline_release; bool force_synctimeline_release; }; static void on_process(void *userdata) { struct data *data = userdata; struct pw_buffer *b; struct spa_buffer *buf; uint32_t i, j; uint8_t *p; struct spa_meta_header *h; struct spa_meta_sync_timeline *stl; uint64_t cmd; if ((b = pw_stream_dequeue_buffer(data->stream)) == NULL) { pw_log_warn("out of buffers: %m"); return; } buf = b->buffer; if ((p = buf->datas[0].data) == NULL) return; if ((h = spa_buffer_find_meta_data(buf, SPA_META_Header, sizeof(*h)))) { #if 0 h->pts = pw_stream_get_nsec(data->stream); #else h->pts = -1; #endif h->flags = 0; h->seq = data->seq++; h->dts_offset = 0; } if ((stl = spa_buffer_find_meta_data(buf, SPA_META_SyncTimeline, sizeof(*stl))) && stl->release_point) { if (!SPA_FLAG_IS_SET(stl->flags, SPA_META_SYNC_TIMELINE_UNSCHEDULED_RELEASE)) { /* The other end promised to schedule the release point, wait before we * can use the buffer */ if (read(buf->datas[2].fd, &cmd, sizeof(cmd)) < 0) pw_log_warn("release_point wait error %m"); pw_log_debug("release_point:%"PRIu64, stl->release_point); } else if (spa_buffer_has_meta_features(buf, SPA_META_SyncTimeline, SPA_META_FEATURE_SYNC_TIMELINE_RELEASE)) { /* this happens when the other end did not get the buffer or * will not trigger the release point, There is no point waiting, * we can use the buffer right away */ pw_log_warn("release_point not scheduled:%"PRIu64, stl->release_point); } else { /* The other end does not support the RELEASE flag, we don't * know if the buffer was used or not or if the release point will * ever be scheduled, we must assume we can reuse the buffer */ pw_log_debug("assume buffer was released:%"PRIu64, stl->release_point); } } for (i = 0; i < data->format.size.height; i++) { for (j = 0; j < data->format.size.width * BPP; j++) p[j] = data->counter + j * i; p += data->stride; data->counter += 13; } buf->datas[0].chunk->offset = 0; buf->datas[0].chunk->size = data->format.size.height * data->stride; buf->datas[0].chunk->stride = data->stride; if (stl) { /* set the UNSCHEDULED_RELEASE flag, the consumer will clear this if * it promises to signal the release point */ SPA_FLAG_SET(stl->flags, SPA_META_SYNC_TIMELINE_UNSCHEDULED_RELEASE); cmd = 1; stl->acquire_point = data->seq; stl->release_point = data->seq; /* write the acquire point */ write(buf->datas[1].fd, &cmd, sizeof(cmd)); } pw_stream_queue_buffer(data->stream, b); } static void on_timeout(void *userdata, uint64_t expirations) { struct data *data = userdata; pw_log_trace("timeout"); pw_stream_trigger_process(data->stream); } static void on_stream_state_changed(void *_data, enum pw_stream_state old, enum pw_stream_state state, const char *error) { struct data *data = _data; printf("stream state: \"%s\" %s\n", pw_stream_state_as_string(state), error ? error : ""); switch (state) { case PW_STREAM_STATE_ERROR: case PW_STREAM_STATE_UNCONNECTED: pw_main_loop_quit(data->loop); break; case PW_STREAM_STATE_PAUSED: printf("node id: %d\n", pw_stream_get_node_id(data->stream)); pw_loop_update_timer(pw_main_loop_get_loop(data->loop), data->timer, NULL, NULL, false); break; case PW_STREAM_STATE_STREAMING: { struct timespec timeout, interval; timeout.tv_sec = 0; timeout.tv_nsec = 1; interval.tv_sec = 0; interval.tv_nsec = 40 * SPA_NSEC_PER_MSEC; printf("driving:%d\n", pw_stream_is_driving(data->stream)); if (pw_stream_is_driving(data->stream)) pw_loop_update_timer(pw_main_loop_get_loop(data->loop), data->timer, &timeout, &interval, false); break; } default: break; } } static void on_stream_param_changed(void *_data, uint32_t id, const struct spa_pod *param) { struct data *data = _data; struct pw_stream *stream = data->stream; uint8_t params_buffer[1024]; struct spa_pod_builder b = SPA_POD_BUILDER_INIT(params_buffer, sizeof(params_buffer)); const struct spa_pod *params[5]; uint32_t n_params = 0; struct spa_pod_frame f; if (param != NULL && id == SPA_PARAM_Tag) { spa_debug_pod(0, NULL, param); return; } if (param == NULL || id != SPA_PARAM_Format) return; fprintf(stderr, "got format:\n"); spa_debug_format(2, NULL, param); spa_format_video_raw_parse(param, &data->format); data->stride = SPA_ROUND_UP_N(data->format.size.width * BPP, 4); /* first add Buffer with 3 blocks (1 data, 2 sync fds). */ if (data->with_synctimeline) { spa_pod_builder_push_object(&b, &f, SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers); spa_pod_builder_add(&b, SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(8, 2, MAX_BUFFERS), SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(3), SPA_PARAM_BUFFERS_size, SPA_POD_Int(data->stride * data->format.size.height), SPA_PARAM_BUFFERS_stride, SPA_POD_Int(data->stride), SPA_PARAM_BUFFERS_dataType, SPA_POD_CHOICE_FLAGS_Int((1<with_synctimeline_release) { uint32_t flags = data->force_synctimeline_release ? /* both sides need compatible features */ SPA_POD_PROP_FLAG_MANDATORY : /* drop features flags if not provided by both sides */ SPA_POD_PROP_FLAG_DROP; spa_pod_builder_prop(&b, SPA_PARAM_META_features, flags); spa_pod_builder_int(&b, SPA_META_FEATURE_SYNC_TIMELINE_RELEASE); } params[n_params++] = spa_pod_builder_pop(&b, &f); } /* fallback for when the synctimeline is not negotiated */ params[n_params++] = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers, SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(8, 2, MAX_BUFFERS), SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(1), SPA_PARAM_BUFFERS_size, SPA_POD_Int(data->stride * data->format.size.height), SPA_PARAM_BUFFERS_stride, SPA_POD_Int(data->stride), SPA_PARAM_BUFFERS_dataType, SPA_POD_CHOICE_FLAGS_Int((1<buffer; struct spa_data *d; #ifdef HAVE_MEMFD_CREATE unsigned int seals; #endif struct spa_meta_sync_timeline *s; d = buf->datas; pw_log_debug("add buffer %p", buffer); if ((d[0].type & (1<stride * data->format.size.height; /* truncate to the right size before we set seals */ if (ftruncate(d[0].fd, d[0].maxsize) < 0) { pw_log_error("can't truncate to %d: %m", d[0].maxsize); return; } #ifdef HAVE_MEMFD_CREATE /* not enforced yet but server might require SEAL_SHRINK later */ seals = F_SEAL_GROW | F_SEAL_SHRINK | F_SEAL_SEAL; if (fcntl(d[0].fd, F_ADD_SEALS, seals) == -1) { pw_log_warn("Failed to add seals: %m"); } #endif /* now mmap so we can write to it in the process function above */ d[0].data = mmap(NULL, d[0].maxsize, PROT_READ|PROT_WRITE, MAP_SHARED, d[0].fd, d[0].mapoffset); if (d[0].data == MAP_FAILED) { pw_log_error("can't mmap memory: %m"); return; } if ((s = spa_buffer_find_meta_data(buf, SPA_META_SyncTimeline, sizeof(*s))) && buf->n_datas >= 3) { pw_log_debug("got sync timeline"); /* acquire fd (just an example, not really syncobj here) */ d[1].type = SPA_DATA_SyncObj; d[1].flags = SPA_DATA_FLAG_READWRITE; d[1].fd = eventfd(0, EFD_CLOEXEC); d[1].mapoffset = 0; d[1].maxsize = 0; if (d[1].fd == -1) { pw_log_error("can't create acquire fd: %m"); return; } /* release fd (just an example, not really syncobj here) */ d[2].type = SPA_DATA_SyncObj; d[2].flags = SPA_DATA_FLAG_READWRITE; d[2].fd = eventfd(0, EFD_CLOEXEC); d[2].mapoffset = 0; d[2].maxsize = 0; if (d[2].fd == -1) { pw_log_error("can't create release fd: %m"); return; } } if (data->n_buffers++ == 0) { struct spa_dict_item items[2]; uint32_t n_items = 0; bool reliable = false, exclusive = false; if (s != NULL) { /* sync timeline is always exclusive */ exclusive = true; if (spa_buffer_has_meta_features(buf, SPA_META_SyncTimeline, SPA_META_FEATURE_SYNC_TIMELINE_RELEASE)) { pw_log_info("got sync timeline with release"); } else { pw_log_info("got sync timeline"); /* we need reliable transport without release */ reliable = true; } } else { pw_log_info("did not get sync timeline"); } items[n_items++] = SPA_DICT_ITEM(PW_KEY_NODE_EXCLUSIVE, exclusive ? "true" : "false"); items[n_items++] = SPA_DICT_ITEM(PW_KEY_NODE_RELIABLE, reliable ? "true" : "false"); pw_stream_update_properties(data->stream, &SPA_DICT(items, n_items)); } } /* close the memfd we set on the buffers here */ static void on_stream_remove_buffer(void *_data, struct pw_buffer *buffer) { struct data *data = _data; struct spa_buffer *buf = buffer->buffer; struct spa_data *d; d = buf->datas; pw_log_debug("remove buffer %p", buffer); munmap(d[0].data, d[0].maxsize); close(d[0].fd); if (buf->n_datas >= 3) { close(d[1].fd); close(d[2].fd); } data->n_buffers--; } static const struct pw_stream_events stream_events = { PW_VERSION_STREAM_EVENTS, .process = on_process, .state_changed = on_stream_state_changed, .param_changed = on_stream_param_changed, .add_buffer = on_stream_add_buffer, .remove_buffer = on_stream_remove_buffer, }; static void do_quit(void *userdata, int signal_number) { struct data *data = userdata; pw_main_loop_quit(data->loop); } static void show_help(struct data *data, const char *name, bool is_error) { FILE *fp; fp = is_error ? stderr : stdout; fprintf(fp, "%s [options]\n" " -h, --help Show this help\n" " --version Show version\n" " -r, --remote Remote daemon name\n" " -S, --sync Enable SyncTimeline\n" " -R, --release Enable RELEASE feature\n" " -F, --force-release RELEASE feature needs to be present\n" "\n", name); } int main(int argc, char *argv[]) { struct data data = { 0, }; const struct spa_pod *params[2]; uint32_t n_params = 0; uint8_t buffer[1024]; struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); static const struct option long_options[] = { { "help", no_argument, NULL, 'h' }, { "version", no_argument, NULL, 'V' }, { "remote", required_argument, NULL, 'r' }, { "sync", no_argument, NULL, 'S' }, { "release", no_argument, NULL, 'R' }, { "force-release", no_argument, NULL, 'F' }, { NULL, 0, NULL, 0} }; char *opt_remote = NULL; int c; pw_init(&argc, &argv); while ((c = getopt_long(argc, argv, "hVr:SRF", long_options, NULL)) != -1) { switch (c) { case 'h': show_help(&data, argv[0], false); return 0; case 'V': printf("%s\n" "Compiled with libpipewire %s\n" "Linked with libpipewire %s\n", argv[0], pw_get_headers_version(), pw_get_library_version()); return 0; case 'r': opt_remote = optarg; break; case 'F': data.force_synctimeline_release = true; SPA_FALLTHROUGH; case 'R': data.with_synctimeline_release = true; SPA_FALLTHROUGH; case 'S': data.with_synctimeline = true; break; default: show_help(&data, argv[0], true); return -1; } } data.loop = pw_main_loop_new(NULL); pw_loop_add_signal(pw_main_loop_get_loop(data.loop), SIGINT, do_quit, &data); pw_loop_add_signal(pw_main_loop_get_loop(data.loop), SIGTERM, do_quit, &data); data.context = pw_context_new(pw_main_loop_get_loop(data.loop), NULL, 0); data.timer = pw_loop_add_timer(pw_main_loop_get_loop(data.loop), on_timeout, &data); data.core = pw_context_connect(data.context, pw_properties_new( PW_KEY_REMOTE_NAME, opt_remote, NULL), 0); if (data.core == NULL) { fprintf(stderr, "can't connect: %m\n"); data.res = -errno; goto cleanup; } data.stream = pw_stream_new(data.core, "video-src-sync", pw_properties_new( PW_KEY_MEDIA_CLASS, "Video/Source", NULL)); params[n_params++] = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), SPA_FORMAT_VIDEO_format, SPA_POD_Id(SPA_VIDEO_FORMAT_BGRA), SPA_FORMAT_VIDEO_size, SPA_POD_CHOICE_RANGE_Rectangle( &SPA_RECTANGLE(320, 240), &SPA_RECTANGLE(1, 1), &SPA_RECTANGLE(4096, 4096)), SPA_FORMAT_VIDEO_framerate, SPA_POD_Fraction(&SPA_FRACTION(25, 1))); pw_stream_add_listener(data.stream, &data.stream_listener, &stream_events, &data); pw_stream_connect(data.stream, PW_DIRECTION_OUTPUT, PW_ID_ANY, PW_STREAM_FLAG_DRIVER | PW_STREAM_FLAG_ALLOC_BUFFERS | PW_STREAM_FLAG_MAP_BUFFERS, params, n_params); pw_main_loop_run(data.loop); cleanup: pw_context_destroy(data.context); pw_main_loop_destroy(data.loop); pw_deinit(); return data.res; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/examples/video-src.c000066400000000000000000000240671511204443500251530ustar00rootroot00000000000000/* PipeWire */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ /* [title] Video source using \ref pw_stream. [title] */ #include #include #include #include #include #include #include #include #include #include #define BPP 4 #define CURSOR_WIDTH 64 #define CURSOR_HEIGHT 64 #define CURSOR_BPP 4 #define MAX_BUFFERS 64 #define M_PI_M2 ( M_PI + M_PI ) struct data { struct pw_main_loop *loop; struct spa_source *timer; struct pw_context *context; struct pw_core *core; struct pw_stream *stream; struct spa_hook stream_listener; struct spa_video_info_raw format; int32_t stride; int counter; uint32_t seq; double crop; double accumulator; int res; }; static void draw_elipse(uint32_t *dst, int width, int height, uint32_t color) { int i, j, r1, r2, r12, r22, r122; r1 = width/2; r12 = r1 * r1; r2 = height/2; r22 = r2 * r2; r122 = r12 * r22; for (i = -r2; i < r2; i++) { for (j = -r1; j < r1; j++) { dst[(i + r2)*width+(j+r1)] = (i * i * r12 + j * j * r22 <= r122) ? color : 0x00000000; } } } static void on_process(void *userdata) { struct data *data = userdata; struct pw_buffer *b; struct spa_buffer *buf; uint32_t i, j; uint8_t *p; struct spa_meta *m; struct spa_meta_header *h; struct spa_meta_region *mc; struct spa_meta_cursor *mcs; if ((b = pw_stream_dequeue_buffer(data->stream)) == NULL) { pw_log_warn("out of buffers: %m"); return; } buf = b->buffer; if ((p = buf->datas[0].data) == NULL) return; if ((h = spa_buffer_find_meta_data(buf, SPA_META_Header, sizeof(*h)))) { #if 0 h->pts = pw_stream_get_nsec(data->stream); #else h->pts = -1; #endif h->flags = 0; h->seq = data->seq++; h->dts_offset = 0; } if ((m = spa_buffer_find_meta(buf, SPA_META_VideoDamage))) { struct spa_meta_region *r = spa_meta_first(m); if (spa_meta_check(r, m)) { r->region.position = SPA_POINT(0,0); r->region.size = data->format.size; r++; } if (spa_meta_check(r, m)) r->region = SPA_REGION(0,0,0,0); } if ((mc = spa_buffer_find_meta_data(buf, SPA_META_VideoCrop, sizeof(*mc)))) { data->crop = (sin(data->accumulator) + 1.0) * 32.0; mc->region.position.x = (int32_t)data->crop; mc->region.position.y = (int32_t)data->crop; mc->region.size.width = data->format.size.width - (int32_t)(data->crop*2); mc->region.size.height = data->format.size.height - (int32_t)(data->crop*2); } if ((mcs = spa_buffer_find_meta_data(buf, SPA_META_Cursor, sizeof(*mcs)))) { struct spa_meta_bitmap *mb; uint32_t *bitmap, color; mcs->id = 1; mcs->position.x = (int32_t)((sin(data->accumulator) + 1.0) * 160.0 + 80); mcs->position.y = (int32_t)((cos(data->accumulator) + 1.0) * 100.0 + 50); mcs->hotspot.x = 0; mcs->hotspot.y = 0; mcs->bitmap_offset = sizeof(struct spa_meta_cursor); mb = SPA_PTROFF(mcs, mcs->bitmap_offset, struct spa_meta_bitmap); mb->format = SPA_VIDEO_FORMAT_ARGB; mb->size.width = CURSOR_WIDTH; mb->size.height = CURSOR_HEIGHT; mb->stride = CURSOR_WIDTH * CURSOR_BPP; mb->offset = sizeof(struct spa_meta_bitmap); bitmap = SPA_PTROFF(mb, mb->offset, uint32_t); color = (uint32_t)((cos(data->accumulator) + 1.0) * (1 << 23)); color |= 0xff000000; draw_elipse(bitmap, mb->size.width, mb->size.height, color); } for (i = 0; i < data->format.size.height; i++) { for (j = 0; j < data->format.size.width * BPP; j++) { p[j] = data->counter + j * i; } p += data->stride; data->counter += 13; } data->accumulator += M_PI_M2 / 50.0; if (data->accumulator >= M_PI_M2) data->accumulator -= M_PI_M2; buf->datas[0].chunk->offset = 0; buf->datas[0].chunk->size = data->format.size.height * data->stride; buf->datas[0].chunk->stride = data->stride; pw_stream_queue_buffer(data->stream, b); } static void on_timeout(void *userdata, uint64_t expirations) { struct data *data = userdata; pw_log_trace("timeout"); pw_stream_trigger_process(data->stream); } static void on_stream_state_changed(void *_data, enum pw_stream_state old, enum pw_stream_state state, const char *error) { struct data *data = _data; printf("stream state: \"%s\" %s\n", pw_stream_state_as_string(state), error ? error : ""); switch (state) { case PW_STREAM_STATE_ERROR: case PW_STREAM_STATE_UNCONNECTED: pw_main_loop_quit(data->loop); break; case PW_STREAM_STATE_PAUSED: printf("node id: %d\n", pw_stream_get_node_id(data->stream)); pw_loop_update_timer(pw_main_loop_get_loop(data->loop), data->timer, NULL, NULL, false); break; case PW_STREAM_STATE_STREAMING: { struct timespec timeout, interval; timeout.tv_sec = 0; timeout.tv_nsec = 1; interval.tv_sec = 0; interval.tv_nsec = 40 * SPA_NSEC_PER_MSEC; printf("driving:%d lazy:%d\n", pw_stream_is_driving(data->stream), pw_stream_is_lazy(data->stream)); if (pw_stream_is_driving(data->stream) != pw_stream_is_lazy(data->stream)) pw_loop_update_timer(pw_main_loop_get_loop(data->loop), data->timer, &timeout, &interval, false); break; } default: break; } } static void on_stream_param_changed(void *_data, uint32_t id, const struct spa_pod *param) { struct data *data = _data; struct pw_stream *stream = data->stream; uint8_t params_buffer[1024]; struct spa_pod_builder b = SPA_POD_BUILDER_INIT(params_buffer, sizeof(params_buffer)); const struct spa_pod *params[5]; uint32_t n_params = 0; if (param != NULL && (id == SPA_PARAM_Tag || id == SPA_PARAM_PeerCapability)) { spa_debug_pod(0, NULL, param); return; } if (param == NULL || id != SPA_PARAM_Format) return; fprintf(stderr, "got format:\n"); spa_debug_format(2, NULL, param); spa_format_video_raw_parse(param, &data->format); data->stride = SPA_ROUND_UP_N(data->format.size.width * BPP, 4); params[n_params++] = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers, SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(8, 2, MAX_BUFFERS), SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(1), SPA_PARAM_BUFFERS_size, SPA_POD_Int(data->stride * data->format.size.height), SPA_PARAM_BUFFERS_stride, SPA_POD_Int(data->stride)); params[n_params++] = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta, SPA_PARAM_META_type, SPA_POD_Id(SPA_META_Header), SPA_PARAM_META_size, SPA_POD_Int(sizeof(struct spa_meta_header))); params[n_params++] = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta, SPA_PARAM_META_type, SPA_POD_Id(SPA_META_VideoDamage), SPA_PARAM_META_size, SPA_POD_CHOICE_RANGE_Int( sizeof(struct spa_meta_region) * 16, sizeof(struct spa_meta_region) * 1, sizeof(struct spa_meta_region) * 16)); params[n_params++] = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta, SPA_PARAM_META_type, SPA_POD_Id(SPA_META_VideoCrop), SPA_PARAM_META_size, SPA_POD_Int(sizeof(struct spa_meta_region))); #define CURSOR_META_SIZE(w,h) (sizeof(struct spa_meta_cursor) + \ sizeof(struct spa_meta_bitmap) + w * h * CURSOR_BPP) params[n_params++] = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta, SPA_PARAM_META_type, SPA_POD_Id(SPA_META_Cursor), SPA_PARAM_META_size, SPA_POD_Int( CURSOR_META_SIZE(CURSOR_WIDTH,CURSOR_HEIGHT))); pw_stream_update_params(stream, params, n_params); } static void on_trigger_done(void *_data) { pw_log_trace("trigger done"); } static const struct pw_stream_events stream_events = { PW_VERSION_STREAM_EVENTS, .process = on_process, .state_changed = on_stream_state_changed, .param_changed = on_stream_param_changed, .trigger_done = on_trigger_done, }; static void do_quit(void *userdata, int signal_number) { struct data *data = userdata; pw_main_loop_quit(data->loop); } int main(int argc, char *argv[]) { struct data data = { 0, }; const struct spa_pod *params[3]; uint32_t n_params = 0; uint8_t buffer[1024]; struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); pw_init(&argc, &argv); data.loop = pw_main_loop_new(NULL); pw_loop_add_signal(pw_main_loop_get_loop(data.loop), SIGINT, do_quit, &data); pw_loop_add_signal(pw_main_loop_get_loop(data.loop), SIGTERM, do_quit, &data); data.context = pw_context_new(pw_main_loop_get_loop(data.loop), NULL, 0); data.timer = pw_loop_add_timer(pw_main_loop_get_loop(data.loop), on_timeout, &data); data.core = pw_context_connect(data.context, NULL, 0); if (data.core == NULL) { fprintf(stderr, "can't connect: %m\n"); data.res = -errno; goto cleanup; } data.stream = pw_stream_new(data.core, "video-src", pw_properties_new( PW_KEY_MEDIA_CLASS, "Video/Source", PW_KEY_NODE_SUPPORTS_REQUEST, "1", NULL)); params[n_params++] = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), SPA_FORMAT_VIDEO_format, SPA_POD_Id(SPA_VIDEO_FORMAT_BGRA), SPA_FORMAT_VIDEO_size, SPA_POD_CHOICE_RANGE_Rectangle( &SPA_RECTANGLE(320, 240), &SPA_RECTANGLE(1, 1), &SPA_RECTANGLE(4096, 4096)), SPA_FORMAT_VIDEO_framerate, SPA_POD_Fraction(&SPA_FRACTION(25, 1))); { struct spa_pod_frame f; /* send a tag, output tags travel downstream */ spa_tag_build_start(&b, &f, SPA_PARAM_Tag, SPA_DIRECTION_OUTPUT); spa_tag_build_add_dict(&b, &SPA_DICT_ITEMS( SPA_DICT_ITEM("my-tag-key", "my-special-tag-value"))); params[n_params++] = spa_tag_build_end(&b, &f); } { params[n_params++] = spa_param_dict_build_dict(&b, SPA_PARAM_Capability, &SPA_DICT_ITEMS( SPA_DICT_ITEM("my-capability-key", "my-capability-value"))); } pw_stream_add_listener(data.stream, &data.stream_listener, &stream_events, &data); pw_stream_connect(data.stream, PW_DIRECTION_OUTPUT, PW_ID_ANY, PW_STREAM_FLAG_DRIVER | PW_STREAM_FLAG_MAP_BUFFERS, params, n_params); pw_main_loop_run(data.loop); cleanup: pw_context_destroy(data.context); pw_main_loop_destroy(data.loop); pw_deinit(); return data.res; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/gst/000077500000000000000000000000001511204443500220625ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/gst/.editorconfig000066400000000000000000000002061511204443500245350ustar00rootroot00000000000000[*] indent_style = space indent_size = 2 end_of_line = lf charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/gst/gstpipewire.c000066400000000000000000000023411511204443500245700ustar00rootroot00000000000000/* PipeWire GStreamer Elements */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ /** * SECTION:element-pipewiresrc * * * Example launch line * |[ * gst-launch -v pipewiresrc ! ximagesink * ]| Shows PipeWire output in an X window. * */ #include "config.h" #include "gstpipewiresrc.h" #include "gstpipewiresink.h" #include "gstpipewiredeviceprovider.h" GST_DEBUG_CATEGORY (pipewire_debug); static gboolean plugin_init (GstPlugin *plugin) { pw_init (NULL, NULL); gst_element_register (plugin, "pipewiresrc", GST_RANK_PRIMARY + 1, GST_TYPE_PIPEWIRE_SRC); gst_element_register (plugin, "pipewiresink", GST_RANK_NONE, GST_TYPE_PIPEWIRE_SINK); #ifdef HAVE_GSTREAMER_DEVICE_PROVIDER if (!gst_device_provider_register (plugin, "pipewiredeviceprovider", GST_RANK_PRIMARY + 1, GST_TYPE_PIPEWIRE_DEVICE_PROVIDER)) return FALSE; #endif GST_DEBUG_CATEGORY_INIT (pipewire_debug, "pipewire", 0, "PipeWire elements"); return TRUE; } GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, GST_VERSION_MINOR, pipewire, "Uses PipeWire to handle media streams", plugin_init, PACKAGE_VERSION, "MIT/X11", "pipewire", "pipewire.org") pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/gst/gstpipewireclock.c000066400000000000000000000057031511204443500256110ustar00rootroot00000000000000/* GStreamer */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include "config.h" #include #include "gstpipewireclock.h" GST_DEBUG_CATEGORY_STATIC (gst_pipewire_clock_debug_category); #define GST_CAT_DEFAULT gst_pipewire_clock_debug_category G_DEFINE_TYPE (GstPipeWireClock, gst_pipewire_clock, GST_TYPE_SYSTEM_CLOCK); GstClock * gst_pipewire_clock_new (GstPipeWireStream *stream, GstClockTime last_time) { GstPipeWireClock *clock; clock = g_object_new (GST_TYPE_PIPEWIRE_CLOCK, NULL); g_weak_ref_set (&clock->stream, stream); clock->last_time = last_time; clock->time_offset = last_time; return GST_CLOCK_CAST (clock); } static GstClockTime gst_pipewire_clock_get_internal_time (GstClock * clock) { GstPipeWireClock *pclock = (GstPipeWireClock *) clock; g_autoptr (GstPipeWireStream) s = g_weak_ref_get (&pclock->stream); GstClockTime result; uint64_t now; if (G_UNLIKELY (!s)) return pclock->last_time; now = pw_stream_get_nsec(s->pwstream); #if 1 struct pw_time t; if (s->pwstream == NULL || pw_stream_get_time_n (s->pwstream, &t, sizeof(t)) < 0 || t.rate.denom == 0) return pclock->last_time; result = gst_util_uint64_scale (t.ticks, GST_SECOND * t.rate.num, t.rate.denom); result += now - t.now; result += pclock->time_offset; pclock->last_time = result; GST_DEBUG ("%"PRId64", %d/%d %"PRId64" %"PRId64" %"PRId64, t.ticks, t.rate.num, t.rate.denom, t.now, result, now); #else result = now + pclock->time_offset; pclock->last_time = result; #endif return result; } static void gst_pipewire_clock_finalize (GObject * object) { GstPipeWireClock *clock = GST_PIPEWIRE_CLOCK (object); GST_DEBUG_OBJECT (clock, "finalize"); g_weak_ref_set (&clock->stream, NULL); G_OBJECT_CLASS (gst_pipewire_clock_parent_class)->finalize (object); } static void gst_pipewire_clock_class_init (GstPipeWireClockClass * klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); GstClockClass *gstclock_class = GST_CLOCK_CLASS (klass); gobject_class->finalize = gst_pipewire_clock_finalize; gstclock_class->get_internal_time = gst_pipewire_clock_get_internal_time; GST_DEBUG_CATEGORY_INIT (gst_pipewire_clock_debug_category, "pipewireclock", 0, "debug category for pipewireclock object"); } static void gst_pipewire_clock_init (GstPipeWireClock * clock) { GST_OBJECT_FLAG_SET (clock, GST_CLOCK_FLAG_CAN_SET_MASTER); } void gst_pipewire_clock_reset (GstPipeWireClock * clock, GstClockTime time) { #if 0 GstClockTimeDiff time_offset; if (clock->last_time >= time) time_offset = clock->last_time - time; else time_offset = -(time - clock->last_time); clock->time_offset = time_offset; GST_DEBUG_OBJECT (clock, "reset clock to %" GST_TIME_FORMAT ", last %" GST_TIME_FORMAT ", offset %" GST_STIME_FORMAT, GST_TIME_ARGS (time), GST_TIME_ARGS (clock->last_time), GST_STIME_ARGS (time_offset)); #endif } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/gst/gstpipewireclock.h000066400000000000000000000016611511204443500256150ustar00rootroot00000000000000/* GStreamer */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef __GST_PIPEWIRE_CLOCK_H__ #define __GST_PIPEWIRE_CLOCK_H__ #include "config.h" #include "gstpipewirestream.h" #include #include G_BEGIN_DECLS #define GST_TYPE_PIPEWIRE_CLOCK (gst_pipewire_clock_get_type()) G_DECLARE_FINAL_TYPE (GstPipeWireClock, gst_pipewire_clock, GST, PIPEWIRE_CLOCK, GstSystemClock) struct _GstPipeWireClock { GstSystemClock parent; GWeakRef stream; GstClockTime last_time; GstClockTimeDiff time_offset; }; GstClock * gst_pipewire_clock_new (GstPipeWireStream *stream, GstClockTime last_time); void gst_pipewire_clock_reset (GstPipeWireClock *clock, GstClockTime time); G_END_DECLS #endif /* __GST_PIPEWIRE_CLOCK_H__ */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/gst/gstpipewirecore.c000066400000000000000000000112121511204443500254360ustar00rootroot00000000000000/* GStreamer */ /* SPDX-FileCopyrightText: Copyright © 2020 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include "config.h" #include #include #include #include #include "gstpipewirecore.h" /* a list of global cores indexed by fd. */ G_LOCK_DEFINE_STATIC (cores_lock); static GList *cores; static void on_core_error(void *data, uint32_t id, int seq, int res, const char *message) { GstPipeWireCore *core = data; pw_log_warn("error id:%u seq:%d res:%d (%s): %s", id, seq, res, spa_strerror(res), message); if (id == PW_ID_CORE) { core->last_error = res; } pw_thread_loop_signal(core->loop, FALSE); } static void on_core_done (void *data, uint32_t id, int seq) { GstPipeWireCore * core = data; if (id == PW_ID_CORE) { core->last_seq = seq; pw_thread_loop_signal (core->loop, FALSE); } } static const struct pw_core_events core_events = { PW_VERSION_CORE_EVENTS, .done = on_core_done, .error = on_core_error, }; static GstPipeWireCore *make_core (int fd) { GstPipeWireCore *core; core = g_new (GstPipeWireCore, 1); core->refcount = 1; core->fd = fd; core->loop = pw_thread_loop_new ("pipewire-main-loop", NULL); if (core->loop == NULL) goto loop_failed; core->context = pw_context_new (pw_thread_loop_get_loop(core->loop), NULL, 0); if (core->context == NULL) goto context_failed; core->last_seq = -1; core->last_error = 0; GST_DEBUG ("loop %p context %p", core->loop, core->context); if (pw_thread_loop_start (core->loop) < 0) goto mainloop_failed; pw_thread_loop_lock (core->loop); if (fd == -1) core->core = pw_context_connect (core->context, NULL, 0); else core->core = pw_context_connect_fd (core->context, fcntl(fd, F_DUPFD_CLOEXEC, 3), NULL, 0); if (core->core == NULL) goto connection_failed; pw_core_add_listener(core->core, &core->core_listener, &core_events, core); pw_thread_loop_unlock (core->loop); return core; loop_failed: { GST_ERROR ("error creating threadloop"); g_free (core); return NULL; } context_failed: { GST_ERROR ("error creating context"); pw_thread_loop_destroy (core->loop); g_free (core); return NULL; } mainloop_failed: { GST_ERROR ("error starting mainloop"); pw_context_destroy (core->context); pw_thread_loop_destroy (core->loop); g_free (core); return NULL; } connection_failed: { GST_ERROR ("error connect: %s", strerror (errno)); pw_thread_loop_unlock (core->loop); pw_context_destroy (core->context); pw_thread_loop_destroy (core->loop); g_free (core); return NULL; } } typedef struct { int fd; } FindData; static gint core_find (GstPipeWireCore * core, FindData * data) { /* fd's must match */ if (core->fd == data->fd) return 0; return 1; } GstPipeWireCore *gst_pipewire_core_get (int fd) { GstPipeWireCore *core; FindData data; GList *found; data.fd = fd; G_LOCK (cores_lock); found = g_list_find_custom (cores, &data, (GCompareFunc) core_find); if (found != NULL) { core = (GstPipeWireCore *) found->data; core->refcount++; GST_DEBUG ("found core %p", core); } else { core = make_core(fd); if (core != NULL) { GST_DEBUG ("created core %p", core); /* add to list on success */ cores = g_list_prepend (cores, core); } else { GST_WARNING ("could not create core"); } } G_UNLOCK (cores_lock); return core; } static void do_sync(GstPipeWireCore * core) { struct timespec abstime; core->pending_seq = pw_core_sync(core->core, 0, core->pending_seq); pw_thread_loop_get_time (core->loop, &abstime, GST_PIPEWIRE_DEFAULT_TIMEOUT * SPA_NSEC_PER_SEC); while (true) { if (core->last_seq == core->pending_seq || core->last_error < 0) break; if (pw_thread_loop_timed_wait_full (core->loop, &abstime) < 0) break; } } void gst_pipewire_core_release (GstPipeWireCore *core) { gboolean zero; G_LOCK (cores_lock); core->refcount--; if ((zero = (core->refcount == 0))) { GST_DEBUG ("closing core %p", core); /* remove from list, we can release the mutex after removing the connection * from the list because after that, nobody can access the connection anymore. */ cores = g_list_remove (cores, core); } G_UNLOCK (cores_lock); if (zero) { pw_thread_loop_lock (core->loop); do_sync(core); pw_core_disconnect (core->core); pw_thread_loop_unlock (core->loop); pw_thread_loop_stop (core->loop); pw_context_destroy (core->context); pw_thread_loop_destroy (core->loop); free(core); } } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/gst/gstpipewirecore.h000066400000000000000000000014321511204443500254460ustar00rootroot00000000000000/* GStreamer */ /* SPDX-FileCopyrightText: Copyright © 2020 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef __GST_PIPEWIRE_CORE_H__ #define __GST_PIPEWIRE_CORE_H__ #include #include G_BEGIN_DECLS typedef struct _GstPipeWireCore GstPipeWireCore; #define GST_PIPEWIRE_DEFAULT_TIMEOUT 30 /** * GstPipeWireCore: * * Opaque data structure. */ struct _GstPipeWireCore { gint refcount; int fd; struct pw_thread_loop *loop; struct pw_context *context; struct pw_core *core; struct spa_hook core_listener; int last_error; int last_seq; int pending_seq; }; GstPipeWireCore *gst_pipewire_core_get (int fd); void gst_pipewire_core_release (GstPipeWireCore *core); G_END_DECLS #endif /* __GST_PIPEWIRE_CORE_H__ */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/gst/gstpipewiredeviceprovider.c000066400000000000000000000656421511204443500275400ustar00rootroot00000000000000/* GStreamer */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include "config.h" #include #include #include #include #include #include "gstpipewireformat.h" #include "gstpipewiredeviceprovider.h" #include "gstpipewiresrc.h" #include "gstpipewiresink.h" GST_DEBUG_CATEGORY_EXTERN (pipewire_debug); #define GST_CAT_DEFAULT pipewire_debug G_DEFINE_TYPE (GstPipeWireDevice, gst_pipewire_device, GST_TYPE_DEVICE); enum { PROP_ID = 1, PROP_SERIAL, PROP_FD_DEVICE, }; static GstElement * gst_pipewire_device_create_element (GstDevice * device, const gchar * name) { GstPipeWireDevice *pipewire_dev = GST_PIPEWIRE_DEVICE (device); GstElement *elem; gchar *serial_str; elem = gst_element_factory_make (pipewire_dev->element, name); serial_str = g_strdup_printf ("%"PRIu64, pipewire_dev->serial); g_object_set (elem, "target-object", serial_str, "fd", pipewire_dev->fd, NULL); g_free (serial_str); return elem; } static gboolean gst_pipewire_device_reconfigure_element (GstDevice * device, GstElement * element) { GstPipeWireDevice *pipewire_dev = GST_PIPEWIRE_DEVICE (device); gchar *serial_str; if (spa_streq(pipewire_dev->element, "pipewiresrc")) { if (!GST_IS_PIPEWIRE_SRC (element)) return FALSE; } else if (spa_streq(pipewire_dev->element, "pipewiresink")) { if (!GST_IS_PIPEWIRE_SINK (element)) return FALSE; } else { g_assert_not_reached (); } serial_str = g_strdup_printf ("%"PRIu64, pipewire_dev->serial); g_object_set (element, "target-object", serial_str, "fd", pipewire_dev->fd, NULL); g_free (serial_str); return TRUE; } static void gst_pipewire_device_get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec) { GstPipeWireDevice *device; device = GST_PIPEWIRE_DEVICE_CAST (object); switch (prop_id) { case PROP_ID: g_value_set_uint (value, device->id); break; case PROP_SERIAL: g_value_set_uint64 (value, device->serial); break; case PROP_FD_DEVICE: g_value_set_int (value, device->fd); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void gst_pipewire_device_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec) { GstPipeWireDevice *device; device = GST_PIPEWIRE_DEVICE_CAST (object); switch (prop_id) { case PROP_ID: device->id = g_value_get_uint (value); break; case PROP_SERIAL: device->serial = g_value_get_uint64 (value); break; case PROP_FD_DEVICE: device->fd = g_value_get_int (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void gst_pipewire_device_finalize (GObject * object) { G_OBJECT_CLASS (gst_pipewire_device_parent_class)->finalize (object); } static void gst_pipewire_device_class_init (GstPipeWireDeviceClass * klass) { GstDeviceClass *dev_class = GST_DEVICE_CLASS (klass); GObjectClass *object_class = G_OBJECT_CLASS (klass); dev_class->create_element = gst_pipewire_device_create_element; dev_class->reconfigure_element = gst_pipewire_device_reconfigure_element; object_class->get_property = gst_pipewire_device_get_property; object_class->set_property = gst_pipewire_device_set_property; object_class->finalize = gst_pipewire_device_finalize; g_object_class_install_property (object_class, PROP_ID, g_param_spec_uint ("id", "Id", "The internal id of the PipeWire device", 0, G_MAXUINT32, SPA_ID_INVALID, G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); g_object_class_install_property (object_class, PROP_SERIAL, g_param_spec_uint64 ("serial", "Serial", "The internal serial of the PipeWire device", 0, G_MAXUINT64, SPA_ID_INVALID, G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); g_object_class_install_property (object_class, PROP_FD_DEVICE, g_param_spec_int ("fd", "Fd", "The fd to connect with", -1, G_MAXINT, -1, G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); } static void gst_pipewire_device_init (GstPipeWireDevice * device) { } G_DEFINE_TYPE (GstPipeWireDeviceProvider, gst_pipewire_device_provider, GST_TYPE_DEVICE_PROVIDER); enum { PROP_0, PROP_CLIENT_NAME, PROP_FD, PROP_LAST }; struct node_data { struct spa_list link; GstPipeWireDeviceProvider *self; struct pw_node *proxy; struct spa_hook proxy_listener; uint32_t id; uint64_t serial; struct spa_hook node_listener; struct pw_node_info *info; GstCaps *caps; GstDevice *dev; struct spa_list ports; }; struct port_data { struct spa_list link; struct node_data *node_data; struct pw_port *proxy; struct spa_hook proxy_listener; uint32_t id; uint64_t serial; struct spa_hook port_listener; }; static struct node_data *find_node_data(struct spa_list *nodes, uint32_t id) { struct node_data *n; spa_list_for_each(n, nodes, link) { if (n->id == id) return n; } return NULL; } static GstPipeWireDevice * gst_pipewire_device_new (int fd, uint32_t id, uint64_t serial, GstPipeWireDeviceType type, const gchar * element, int priority, const gchar * klass, const gchar * display_name, const GstCaps * caps, const GstStructure * props) { GstPipeWireDevice *gstdev; gstdev = g_object_new (GST_TYPE_PIPEWIRE_DEVICE, "display-name", display_name, "caps", caps, "device-class", klass, "id", id, "serial", serial, "fd", fd, "properties", props, NULL); gstdev->id = id; gstdev->serial = serial; gstdev->type = type; gstdev->element = element; gstdev->priority = priority; return gstdev; } static GstDevice * new_node (GstPipeWireDeviceProvider *self, struct node_data *data) { GstStructure *props; const gchar *klass = NULL, *name = NULL; GstPipeWireDeviceType type; const struct pw_node_info *info = data->info; const gchar *element = NULL; GstPipeWireDevice *gstdev; int priority = 0; if (info->max_input_ports > 0 && info->max_output_ports == 0) { type = GST_PIPEWIRE_DEVICE_TYPE_SINK; element = "pipewiresink"; } else if (info->max_output_ports > 0 && info->max_input_ports == 0) { type = GST_PIPEWIRE_DEVICE_TYPE_SOURCE; element = "pipewiresrc"; } else { return NULL; } props = gst_structure_new ("pipewire-proplist", "is-default", G_TYPE_BOOLEAN, FALSE, NULL); if (info->props) { const struct spa_dict_item *item; const char *str; klass = spa_dict_lookup(info->props, PW_KEY_MEDIA_CLASS); name = spa_dict_lookup(info->props, PW_KEY_NODE_DESCRIPTION); spa_dict_for_each (item, info->props) { gst_structure_set (props, item->key, G_TYPE_STRING, item->value, NULL); if (spa_streq(item->key, "node.name") && klass) { if (spa_streq(klass, "Audio/Source") && spa_streq(item->value, self->default_audio_source_name)) gst_structure_set(props, "is-default", G_TYPE_BOOLEAN, TRUE, NULL); else if (spa_streq(klass, "Audio/Sink") && spa_streq(item->value, self->default_audio_sink_name)) gst_structure_set(props, "is-default", G_TYPE_BOOLEAN, TRUE, NULL); else if (spa_streq(klass, "Video/Source") && spa_streq(item->value, self->default_video_source_name)) gst_structure_set(props, "is-default", G_TYPE_BOOLEAN, TRUE, NULL); } } if ((str = spa_dict_lookup(info->props, PW_KEY_PRIORITY_SESSION))) priority = atoi(str); } if (klass == NULL) klass = "unknown/unknown"; if (name == NULL) name = "unknown"; gstdev = gst_pipewire_device_new (self->fd, data->id, data->serial, type, element, priority, klass, name, data->caps, props); if (props) gst_structure_free (props); return GST_DEVICE (gstdev); } static int compare_device_session_priority (const void *a, const void *b) { const GstPipeWireDevice *dev_a = a; const GstPipeWireDevice *dev_b = b; if (dev_a->priority < dev_b->priority) return 1; else if (dev_a->priority > dev_b->priority) return -1; else return 0; } static void do_add_nodes(GstPipeWireDeviceProvider *self) { struct node_data *nd; g_autoptr (GList) new_devices = NULL; GList *l; spa_list_for_each(nd, &self->nodes, link) { if (nd->dev != NULL) continue; pw_log_info("add node %d", nd->id); nd->dev = new_node (self, nd); if (nd->dev) new_devices = g_list_prepend (new_devices, nd->dev); } if (!new_devices) return; new_devices = g_list_sort (new_devices, compare_device_session_priority); for (l = new_devices; l != NULL; l = l->next) { GstDevice *device = l->data; if(self->list_only) { self->devices = g_list_insert_sorted (self->devices, gst_object_ref (device), compare_device_session_priority); } else { gst_object_ref (device); gst_device_provider_device_add (GST_DEVICE_PROVIDER (self), device); } } } static void resync(GstPipeWireDeviceProvider *self) { self->seq = pw_core_sync(self->core->core, PW_ID_CORE, self->seq); pw_log_debug("resync %d", self->seq); } static void on_core_done (void *data, uint32_t id, int seq) { GstPipeWireDeviceProvider *self = data; pw_log_debug("check %d %d", seq, self->seq); if (id == PW_ID_CORE && seq == self->seq) { do_add_nodes(self); self->end = true; if (self->core) pw_thread_loop_signal (self->core->loop, FALSE); } } static void on_core_error(void *data, uint32_t id, int seq, int res, const char *message) { GstPipeWireDeviceProvider *self = data; pw_log_warn("error id:%u seq:%d res:%d (%s): %s", id, seq, res, spa_strerror(res), message); if (id == PW_ID_CORE) { self->error = res; } pw_thread_loop_signal(self->core->loop, FALSE); } static const struct pw_core_events core_events = { PW_VERSION_CORE_EVENTS, .done = on_core_done, .error = on_core_error, }; static void port_event_info(void *data, const struct pw_port_info *info) { struct port_data *port_data = data; struct node_data *node_data = port_data->node_data; uint32_t i; pw_log_debug("%p", port_data); if (node_data == NULL) return; if (info->change_mask & PW_PORT_CHANGE_MASK_PARAMS) { for (i = 0; i < info->n_params; i++) { uint32_t id = info->params[i].id; if (id == SPA_PARAM_EnumFormat && info->params[i].flags & SPA_PARAM_INFO_READ && node_data->caps == NULL) { node_data->caps = gst_caps_new_empty (); pw_port_enum_params(port_data->proxy, 0, id, 0, UINT32_MAX, NULL); resync(node_data->self); } } } } static void port_event_param(void *data, int seq, uint32_t id, uint32_t index, uint32_t next, const struct spa_pod *param) { struct port_data *port_data = data; struct node_data *node_data = port_data->node_data; GstCaps *c1; if (node_data == NULL) return; c1 = gst_caps_from_format (param); if (c1 && node_data->caps) gst_caps_append (node_data->caps, c1); } static const struct pw_port_events port_events = { PW_VERSION_PORT_EVENTS, .info = port_event_info, .param = port_event_param }; static void node_event_info(void *data, const struct pw_node_info *info) { struct node_data *node_data = data; uint32_t i; pw_log_debug("%p", node_data->proxy); info = node_data->info = pw_node_info_update(node_data->info, info); if (info->change_mask & PW_NODE_CHANGE_MASK_PARAMS) { for (i = 0; i < info->n_params; i++) { uint32_t id = info->params[i].id; if (id == SPA_PARAM_EnumFormat && info->params[i].flags & SPA_PARAM_INFO_READ && node_data->caps == NULL) { node_data->caps = gst_caps_new_empty (); pw_node_enum_params(node_data->proxy, 0, id, 0, UINT32_MAX, NULL); resync(node_data->self); } } } } static void node_event_param(void *data, int seq, uint32_t id, uint32_t index, uint32_t next, const struct spa_pod *param) { struct node_data *node_data = data; GstCaps *c1; c1 = gst_caps_from_format (param); if (c1 && node_data->caps) gst_caps_append (node_data->caps, c1); } static const struct pw_node_events node_events = { PW_VERSION_NODE_EVENTS, .info = node_event_info, .param = node_event_param }; static void removed_node (void *data) { struct node_data *nd = data; pw_proxy_destroy((struct pw_proxy*)nd->proxy); } static void destroy_node (void *data) { struct node_data *nd = data; struct port_data *pd; GstPipeWireDeviceProvider *self = nd->self; GstDeviceProvider *provider = GST_DEVICE_PROVIDER (self); pw_log_debug("destroy %p", nd); spa_list_consume(pd, &nd->ports, link) { spa_list_remove(&pd->link); pd->node_data = NULL; } if (nd->dev != NULL) { gst_device_provider_device_remove (provider, nd->dev); gst_clear_object (&nd->dev); } if (nd->caps) gst_caps_unref(nd->caps); if (nd->info) pw_node_info_free(nd->info); spa_list_remove(&nd->link); } static const struct pw_proxy_events proxy_node_events = { PW_VERSION_PROXY_EVENTS, .removed = removed_node, .destroy = destroy_node, }; static void removed_port (void *data) { struct port_data *pd = data; pw_proxy_destroy((struct pw_proxy*)pd->proxy); } static void destroy_port (void *data) { struct port_data *pd = data; pw_log_debug("destroy %p", pd); if (pd->node_data != NULL) { spa_list_remove(&pd->link); pd->node_data = NULL; } } static const struct pw_proxy_events proxy_port_events = { PW_VERSION_PROXY_EVENTS, .removed = removed_port, .destroy = destroy_port, }; static gboolean is_default_device_name (GstPipeWireDeviceProvider * self, const gchar * name, const gchar * klass, GstPipeWireDeviceType type) { gboolean ret = FALSE; GST_OBJECT_LOCK (self); switch (type) { case GST_PIPEWIRE_DEVICE_TYPE_SINK: if (g_str_has_prefix (klass, "Audio")) ret = !g_strcmp0 (name, self->default_audio_sink_name); break; case GST_PIPEWIRE_DEVICE_TYPE_SOURCE: if (g_str_has_prefix (klass, "Audio")) ret = !g_strcmp0 (name, self->default_audio_source_name); else if (g_str_has_prefix (klass, "Video")) ret = !g_strcmp0 (name, self->default_video_source_name); break; default: GST_ERROR_OBJECT (self, "Unknown pipewire device type!"); break; } GST_OBJECT_UNLOCK (self); return ret; } static void sync_default_devices (GstPipeWireDeviceProvider * self) { GList *tmp, *devices = NULL; for (tmp = GST_DEVICE_PROVIDER_CAST (self)->devices; tmp; tmp = tmp->next) devices = g_list_prepend (devices, gst_object_ref (tmp->data)); for (tmp = devices; tmp; tmp = tmp->next) { GstPipeWireDevice *dev = tmp->data; GstStructure *props = gst_device_get_properties (GST_DEVICE_CAST (dev)); gboolean was_default = FALSE, is_default = FALSE; const gchar *name; gchar *klass = gst_device_get_device_class (GST_DEVICE_CAST (dev)); g_assert (props); gst_structure_get_boolean (props, "is-default", &was_default); name = gst_structure_get_string (props, "node.name"); switch (dev->type) { case GST_PIPEWIRE_DEVICE_TYPE_SINK: is_default = is_default_device_name (self, name, klass, dev->type); break; case GST_PIPEWIRE_DEVICE_TYPE_SOURCE: is_default = is_default_device_name (self, name, klass, dev->type); break; case GST_PIPEWIRE_DEVICE_TYPE_UNKNOWN: break; } if (was_default != is_default) { GstPipeWireDevice *updated_device; gchar *display_name = gst_device_get_display_name (GST_DEVICE_CAST (dev)); GstCaps *caps = gst_device_get_caps (GST_DEVICE_CAST (dev)); gst_structure_set (props, "is-default", G_TYPE_BOOLEAN, is_default, NULL); updated_device = gst_pipewire_device_new (self->fd, dev->id, dev->serial, dev->type, dev->element, dev->priority, klass, display_name, caps, props); gst_device_provider_device_changed (GST_DEVICE_PROVIDER_CAST (self), GST_DEVICE_CAST (updated_device), GST_DEVICE_CAST (dev)); g_free (display_name); gst_caps_unref (caps); } gst_structure_free (props); g_free (klass); } g_list_free_full (devices, gst_object_unref); } static int metadata_property(void *data, uint32_t id, const char *key, const char *type, const char *value) { GstPipeWireDeviceProvider *self = data; char name[1024]; if (value == NULL) return 0; if (spa_streq(key, "default.audio.source")) { if (!spa_streq(type, "Spa:String:JSON")) return 0; g_free(self->default_audio_source_name); if (spa_json_str_object_find(value, strlen(value), "name", name, sizeof(name)) >= 0) self->default_audio_source_name = g_strdup(name); goto sync_devices; } if (spa_streq(key, "default.audio.sink")) { if (!spa_streq(type, "Spa:String:JSON")) return 0; g_free(self->default_audio_sink_name); if (spa_json_str_object_find(value, strlen(value), "name", name, sizeof(name)) >= 0) self->default_audio_sink_name = g_strdup(name); goto sync_devices; } if (spa_streq(key, "default.video.source")) { if (!spa_streq(type, "Spa:String:JSON")) return 0; g_free(self->default_video_source_name); if (spa_json_str_object_find(value, strlen(value), "name", name, sizeof(name)) >= 0) self->default_video_source_name = g_strdup(name); goto sync_devices; } return 0; sync_devices: sync_default_devices (self); return 0; } static const struct pw_metadata_events metadata_events = { PW_VERSION_METADATA_EVENTS, .property = metadata_property}; static void registry_event_global(void *data, uint32_t id, uint32_t permissions, const char *type, uint32_t version, const struct spa_dict *props) { GstPipeWireDeviceProvider *self = data; GstDeviceProvider *provider = (GstDeviceProvider*)self; struct node_data *nd; const char *str; if (spa_streq(type, PW_TYPE_INTERFACE_Node)) { struct pw_node *node; node = pw_registry_bind(self->registry, id, type, PW_VERSION_NODE, sizeof(*nd)); if (node == NULL) goto no_mem; if (props != NULL) { str = spa_dict_lookup(props, PW_KEY_OBJECT_PATH); if (str != NULL) { if (g_str_has_prefix(str, "alsa:")) gst_device_provider_hide_provider (provider, "pulsedeviceprovider"); else if (g_str_has_prefix(str, "v4l2:")) gst_device_provider_hide_provider (provider, "v4l2deviceprovider"); else if (g_str_has_prefix(str, "libcamera:")) gst_device_provider_hide_provider (provider, "libcameraprovider"); } } nd = pw_proxy_get_user_data((struct pw_proxy*)node); nd->self = self; nd->proxy = node; nd->id = id; if (!props || !spa_atou64(spa_dict_lookup(props, PW_KEY_OBJECT_SERIAL), &nd->serial, 0)) nd->serial = SPA_ID_INVALID; spa_list_init(&nd->ports); spa_list_append(&self->nodes, &nd->link); pw_node_add_listener(node, &nd->node_listener, &node_events, nd); pw_proxy_add_listener((struct pw_proxy*)node, &nd->proxy_listener, &proxy_node_events, nd); resync(self); } else if (spa_streq(type, PW_TYPE_INTERFACE_Port)) { struct pw_port *port; struct port_data *pd; if ((str = spa_dict_lookup(props, PW_KEY_NODE_ID)) == NULL) return; if ((nd = find_node_data(&self->nodes, atoi(str))) == NULL) return; port = pw_registry_bind(self->registry, id, type, PW_VERSION_PORT, sizeof(*pd)); if (port == NULL) goto no_mem; pd = pw_proxy_get_user_data((struct pw_proxy*)port); pd->node_data = nd; pd->proxy = port; pd->id = id; if (!props || !spa_atou64(spa_dict_lookup(props, PW_KEY_OBJECT_SERIAL), &pd->serial, 0)) pd->serial = SPA_ID_INVALID; spa_list_append(&nd->ports, &pd->link); pw_port_add_listener(port, &pd->port_listener, &port_events, pd); pw_proxy_add_listener((struct pw_proxy*)port, &pd->proxy_listener, &proxy_port_events, pd); resync(self); } else if (spa_streq(type, PW_TYPE_INTERFACE_Metadata) && props) { const char *name; name = spa_dict_lookup(props, PW_KEY_METADATA_NAME); if (name == NULL) return; if (!spa_streq(name, "default")) return; self->metadata = pw_registry_bind(self->registry, id, type, PW_VERSION_METADATA, 0); pw_metadata_add_listener(self->metadata, &self->metadata_listener, &metadata_events, self); } return; no_mem: GST_ERROR_OBJECT(self, "failed to create proxy"); return; } static void registry_event_global_remove(void *data, uint32_t id) { } static const struct pw_registry_events registry_events = { PW_VERSION_REGISTRY_EVENTS, .global = registry_event_global, .global_remove = registry_event_global_remove, }; static GList * gst_pipewire_device_provider_probe (GstDeviceProvider * provider) { GstPipeWireDeviceProvider *self = GST_PIPEWIRE_DEVICE_PROVIDER (provider); GST_DEBUG_OBJECT (self, "starting probe"); self->core = gst_pipewire_core_get(self->fd); if (self->core == NULL) { GST_ERROR_OBJECT (self, "Failed to connect"); goto failed; } GST_DEBUG_OBJECT (self, "connected"); pw_thread_loop_lock (self->core->loop); spa_list_init(&self->nodes); self->end = FALSE; self->error = 0; self->list_only = TRUE; self->devices = NULL; self->registry = pw_core_get_registry(self->core->core, PW_VERSION_REGISTRY, 0); pw_core_add_listener(self->core->core, &self->core_listener, &core_events, self); pw_registry_add_listener(self->registry, &self->registry_listener, ®istry_events, self); resync(self); for (;;) { if (self->error < 0) break; if (self->end) break; pw_thread_loop_wait (self->core->loop); } GST_DEBUG_OBJECT (self, "disconnect"); g_clear_pointer ((struct pw_proxy**)&self->registry, pw_proxy_destroy); pw_thread_loop_unlock (self->core->loop); g_clear_pointer (&self->core, gst_pipewire_core_release); return self->devices; failed: return NULL; } static gboolean gst_pipewire_device_provider_start (GstDeviceProvider * provider) { GstPipeWireDeviceProvider *self = GST_PIPEWIRE_DEVICE_PROVIDER (provider); GST_DEBUG_OBJECT (self, "starting provider"); self->core = gst_pipewire_core_get(self->fd); if (self->core == NULL) { GST_ERROR_OBJECT (self, "Failed to connect"); goto failed; } GST_DEBUG_OBJECT (self, "connected"); pw_thread_loop_lock (self->core->loop); spa_list_init(&self->nodes); self->end = FALSE; self->error = 0; self->list_only = FALSE; self->registry = pw_core_get_registry(self->core->core, PW_VERSION_REGISTRY, 0); pw_core_add_listener(self->core->core, &self->core_listener, &core_events, self); pw_registry_add_listener(self->registry, &self->registry_listener, ®istry_events, self); resync(self); for (;;) { if (self->error < 0) break; if (self->end) break; pw_thread_loop_wait (self->core->loop); } GST_DEBUG_OBJECT (self, "started"); pw_thread_loop_unlock (self->core->loop); return TRUE; failed: return TRUE; } static void gst_pipewire_device_provider_stop (GstDeviceProvider * provider) { GstPipeWireDeviceProvider *self = GST_PIPEWIRE_DEVICE_PROVIDER (provider); /* core might be NULL if we failed to connect in _start. */ if (self->core != NULL) { pw_thread_loop_lock (self->core->loop); } GST_DEBUG_OBJECT (self, "stopping provider"); if (self->metadata) { spa_hook_remove(&self->metadata_listener); pw_proxy_destroy((struct pw_proxy *)self->metadata); self->metadata = NULL; } g_clear_pointer ((struct pw_proxy**)&self->registry, pw_proxy_destroy); if (self->core != NULL) { pw_thread_loop_unlock (self->core->loop); } g_clear_pointer (&self->core, gst_pipewire_core_release); } static void gst_pipewire_device_provider_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec) { GstPipeWireDeviceProvider *self = GST_PIPEWIRE_DEVICE_PROVIDER (object); switch (prop_id) { case PROP_CLIENT_NAME: g_free (self->client_name); if (!g_value_get_string (value)) { GST_WARNING_OBJECT (self, "Empty PipeWire client name not allowed. " "Resetting to default value"); self->client_name = g_strdup(pw_get_client_name ()); } else self->client_name = g_value_dup_string (value); break; case PROP_FD: self->fd = g_value_get_int (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void gst_pipewire_device_provider_get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec) { GstPipeWireDeviceProvider *self = GST_PIPEWIRE_DEVICE_PROVIDER (object); switch (prop_id) { case PROP_CLIENT_NAME: g_value_set_string (value, self->client_name); break; case PROP_FD: g_value_set_int (value, self->fd); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void gst_pipewire_device_provider_finalize (GObject * object) { GstPipeWireDeviceProvider *self = GST_PIPEWIRE_DEVICE_PROVIDER (object); g_free (self->client_name); g_free (self->default_audio_source_name); g_free (self->default_audio_sink_name); g_free (self->default_video_source_name); G_OBJECT_CLASS (gst_pipewire_device_provider_parent_class)->finalize (object); } static void gst_pipewire_device_provider_class_init (GstPipeWireDeviceProviderClass * klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); GstDeviceProviderClass *dm_class = GST_DEVICE_PROVIDER_CLASS (klass); gobject_class->set_property = gst_pipewire_device_provider_set_property; gobject_class->get_property = gst_pipewire_device_provider_get_property; gobject_class->finalize = gst_pipewire_device_provider_finalize; dm_class->probe = gst_pipewire_device_provider_probe; dm_class->start = gst_pipewire_device_provider_start; dm_class->stop = gst_pipewire_device_provider_stop; g_object_class_install_property (gobject_class, PROP_CLIENT_NAME, g_param_spec_string ("client-name", "Client Name", "The PipeWire client_name_to_use", pw_get_client_name (), G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | GST_PARAM_MUTABLE_READY)); g_object_class_install_property (gobject_class, PROP_FD, g_param_spec_int ("fd", "Fd", "The fd to connect with", -1, G_MAXINT, -1, G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE)); gst_device_provider_class_set_static_metadata (dm_class, "PipeWire Device Provider", "Sink/Source/Audio/Video", "List and provide PipeWire source and sink devices", "Wim Taymans "); } static void gst_pipewire_device_provider_init (GstPipeWireDeviceProvider * self) { self->client_name = g_strdup(pw_get_client_name ()); self->fd = -1; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/gst/gstpipewiredeviceprovider.h000066400000000000000000000035011511204443500275270ustar00rootroot00000000000000/* GStreamer */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef __GST_PIPEWIRE_DEVICE_PROVIDER_H__ #define __GST_PIPEWIRE_DEVICE_PROVIDER_H__ #include "config.h" #include #include #include #include G_BEGIN_DECLS #define GST_TYPE_PIPEWIRE_DEVICE (gst_pipewire_device_get_type()) #define GST_PIPEWIRE_DEVICE_CAST(obj) ((GstPipeWireDevice *)(obj)) G_DECLARE_FINAL_TYPE (GstPipeWireDevice, gst_pipewire_device, GST, PIPEWIRE_DEVICE, GstDevice) typedef enum { GST_PIPEWIRE_DEVICE_TYPE_UNKNOWN, GST_PIPEWIRE_DEVICE_TYPE_SOURCE, GST_PIPEWIRE_DEVICE_TYPE_SINK, } GstPipeWireDeviceType; struct _GstPipeWireDevice { GstDevice parent; GstPipeWireDeviceType type; uint32_t id; uint64_t serial; int fd; const gchar *element; int priority; }; #define GST_TYPE_PIPEWIRE_DEVICE_PROVIDER (gst_pipewire_device_provider_get_type()) #define GST_PIPEWIRE_DEVICE_PROVIDER_CAST(obj) ((GstPipeWireDeviceProvider *)(obj)) G_DECLARE_FINAL_TYPE (GstPipeWireDeviceProvider, gst_pipewire_device_provider, GST, PIPEWIRE_DEVICE_PROVIDER, GstDeviceProvider) struct _GstPipeWireDeviceProvider { GstDeviceProvider parent; gchar *client_name; int fd; GstPipeWireCore *core; struct spa_hook core_listener; struct pw_registry *registry; struct spa_hook registry_listener; struct pw_metadata *metadata; struct spa_hook metadata_listener; gchar *default_audio_source_name; gchar *default_audio_sink_name; gchar *default_video_source_name; struct spa_list nodes; int seq; int error; gboolean end; gboolean list_only; GList *devices; }; G_END_DECLS #endif /* __GST_PIPEWIRE_DEVICE_PROVIDER_H__ */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/gst/gstpipewireformat.c000066400000000000000000001206441511204443500260100ustar00rootroot00000000000000/* GStreamer */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include "gstpipewireformat.h" #ifndef DRM_FORMAT_INVALID #define DRM_FORMAT_INVALID 0 #endif #ifndef DRM_FORMAT_MOD_INVALID #define DRM_FORMAT_MOD_INVALID ((1ULL << 56) - 1) #endif #ifndef DRM_FORMAT_MOD_LINEAR #define DRM_FORMAT_MOD_LINEAR 0 #endif struct media_type { const char *name; uint32_t media_type; uint32_t media_subtype; }; static const struct media_type media_type_map[] = { { "video/x-raw", SPA_MEDIA_TYPE_video, SPA_MEDIA_SUBTYPE_raw }, { "audio/x-raw", SPA_MEDIA_TYPE_audio, SPA_MEDIA_SUBTYPE_raw }, { "image/jpeg", SPA_MEDIA_TYPE_video, SPA_MEDIA_SUBTYPE_mjpg }, { "video/x-jpeg", SPA_MEDIA_TYPE_video, SPA_MEDIA_SUBTYPE_mjpg }, { "video/x-h264", SPA_MEDIA_TYPE_video, SPA_MEDIA_SUBTYPE_h264 }, { "video/x-h265", SPA_MEDIA_TYPE_video, SPA_MEDIA_SUBTYPE_h265 }, { "audio/x-mulaw", SPA_MEDIA_TYPE_audio, SPA_MEDIA_SUBTYPE_raw }, { "audio/x-alaw", SPA_MEDIA_TYPE_audio, SPA_MEDIA_SUBTYPE_raw }, { "audio/mpeg", SPA_MEDIA_TYPE_audio, SPA_MEDIA_SUBTYPE_mp3 }, { "audio/x-flac", SPA_MEDIA_TYPE_audio, SPA_MEDIA_SUBTYPE_flac }, { NULL, } }; static const uint32_t video_format_map[] = { SPA_VIDEO_FORMAT_UNKNOWN, SPA_VIDEO_FORMAT_ENCODED, SPA_VIDEO_FORMAT_I420, SPA_VIDEO_FORMAT_YV12, SPA_VIDEO_FORMAT_YUY2, SPA_VIDEO_FORMAT_UYVY, SPA_VIDEO_FORMAT_AYUV, SPA_VIDEO_FORMAT_RGBx, SPA_VIDEO_FORMAT_BGRx, SPA_VIDEO_FORMAT_xRGB, SPA_VIDEO_FORMAT_xBGR, SPA_VIDEO_FORMAT_RGBA, SPA_VIDEO_FORMAT_BGRA, SPA_VIDEO_FORMAT_ARGB, SPA_VIDEO_FORMAT_ABGR, SPA_VIDEO_FORMAT_RGB, SPA_VIDEO_FORMAT_BGR, SPA_VIDEO_FORMAT_Y41B, SPA_VIDEO_FORMAT_Y42B, SPA_VIDEO_FORMAT_YVYU, SPA_VIDEO_FORMAT_Y444, SPA_VIDEO_FORMAT_v210, SPA_VIDEO_FORMAT_v216, SPA_VIDEO_FORMAT_NV12, SPA_VIDEO_FORMAT_NV21, SPA_VIDEO_FORMAT_GRAY8, SPA_VIDEO_FORMAT_GRAY16_BE, SPA_VIDEO_FORMAT_GRAY16_LE, SPA_VIDEO_FORMAT_v308, SPA_VIDEO_FORMAT_RGB16, SPA_VIDEO_FORMAT_BGR16, SPA_VIDEO_FORMAT_RGB15, SPA_VIDEO_FORMAT_BGR15, SPA_VIDEO_FORMAT_UYVP, SPA_VIDEO_FORMAT_A420, SPA_VIDEO_FORMAT_RGB8P, SPA_VIDEO_FORMAT_YUV9, SPA_VIDEO_FORMAT_YVU9, SPA_VIDEO_FORMAT_IYU1, SPA_VIDEO_FORMAT_ARGB64, SPA_VIDEO_FORMAT_AYUV64, SPA_VIDEO_FORMAT_r210, SPA_VIDEO_FORMAT_I420_10BE, SPA_VIDEO_FORMAT_I420_10LE, SPA_VIDEO_FORMAT_I422_10BE, SPA_VIDEO_FORMAT_I422_10LE, SPA_VIDEO_FORMAT_Y444_10BE, SPA_VIDEO_FORMAT_Y444_10LE, SPA_VIDEO_FORMAT_GBR, SPA_VIDEO_FORMAT_GBR_10BE, SPA_VIDEO_FORMAT_GBR_10LE, SPA_VIDEO_FORMAT_NV16, SPA_VIDEO_FORMAT_NV24, SPA_VIDEO_FORMAT_NV12_64Z32, SPA_VIDEO_FORMAT_A420_10BE, SPA_VIDEO_FORMAT_A420_10LE, SPA_VIDEO_FORMAT_A422_10BE, SPA_VIDEO_FORMAT_A422_10LE, SPA_VIDEO_FORMAT_A444_10BE, SPA_VIDEO_FORMAT_A444_10LE, SPA_VIDEO_FORMAT_NV61, SPA_VIDEO_FORMAT_P010_10BE, SPA_VIDEO_FORMAT_P010_10LE, SPA_VIDEO_FORMAT_IYU2, SPA_VIDEO_FORMAT_VYUY, SPA_VIDEO_FORMAT_GBRA, SPA_VIDEO_FORMAT_GBRA_10BE, SPA_VIDEO_FORMAT_GBRA_10LE, SPA_VIDEO_FORMAT_GBR_12BE, SPA_VIDEO_FORMAT_GBR_12LE, SPA_VIDEO_FORMAT_GBRA_12BE, SPA_VIDEO_FORMAT_GBRA_12LE, SPA_VIDEO_FORMAT_I420_12BE, SPA_VIDEO_FORMAT_I420_12LE, SPA_VIDEO_FORMAT_I422_12BE, SPA_VIDEO_FORMAT_I422_12LE, SPA_VIDEO_FORMAT_Y444_12BE, SPA_VIDEO_FORMAT_Y444_12LE, }; static const uint32_t color_range_map[] = { SPA_VIDEO_COLOR_RANGE_UNKNOWN, SPA_VIDEO_COLOR_RANGE_0_255, SPA_VIDEO_COLOR_RANGE_16_235, }; static const uint32_t color_matrix_map[] = { SPA_VIDEO_COLOR_MATRIX_UNKNOWN, SPA_VIDEO_COLOR_MATRIX_RGB, SPA_VIDEO_COLOR_MATRIX_FCC, SPA_VIDEO_COLOR_MATRIX_BT709, SPA_VIDEO_COLOR_MATRIX_BT601, SPA_VIDEO_COLOR_MATRIX_SMPTE240M, SPA_VIDEO_COLOR_MATRIX_BT2020, }; static const uint32_t transfer_function_map[] = { SPA_VIDEO_TRANSFER_UNKNOWN, SPA_VIDEO_TRANSFER_GAMMA10, SPA_VIDEO_TRANSFER_GAMMA18, SPA_VIDEO_TRANSFER_GAMMA20, SPA_VIDEO_TRANSFER_GAMMA22, SPA_VIDEO_TRANSFER_BT709, SPA_VIDEO_TRANSFER_SMPTE240M, SPA_VIDEO_TRANSFER_SRGB, SPA_VIDEO_TRANSFER_GAMMA28, SPA_VIDEO_TRANSFER_LOG100, SPA_VIDEO_TRANSFER_LOG316, SPA_VIDEO_TRANSFER_BT2020_12, SPA_VIDEO_TRANSFER_ADOBERGB, SPA_VIDEO_TRANSFER_BT2020_10, SPA_VIDEO_TRANSFER_SMPTE2084, SPA_VIDEO_TRANSFER_ARIB_STD_B67, SPA_VIDEO_TRANSFER_BT601, }; static const uint32_t color_primaries_map[] = { SPA_VIDEO_COLOR_PRIMARIES_UNKNOWN, SPA_VIDEO_COLOR_PRIMARIES_BT709, SPA_VIDEO_COLOR_PRIMARIES_BT470M, SPA_VIDEO_COLOR_PRIMARIES_BT470BG, SPA_VIDEO_COLOR_PRIMARIES_SMPTE170M, SPA_VIDEO_COLOR_PRIMARIES_SMPTE240M, SPA_VIDEO_COLOR_PRIMARIES_FILM, SPA_VIDEO_COLOR_PRIMARIES_BT2020, SPA_VIDEO_COLOR_PRIMARIES_ADOBERGB, SPA_VIDEO_COLOR_PRIMARIES_SMPTEST428, SPA_VIDEO_COLOR_PRIMARIES_SMPTERP431, SPA_VIDEO_COLOR_PRIMARIES_SMPTEEG432, SPA_VIDEO_COLOR_PRIMARIES_EBU3213, }; static const uint32_t interlace_mode_map[] = { SPA_VIDEO_INTERLACE_MODE_PROGRESSIVE, SPA_VIDEO_INTERLACE_MODE_INTERLEAVED, SPA_VIDEO_INTERLACE_MODE_MIXED, SPA_VIDEO_INTERLACE_MODE_FIELDS, }; #if __BYTE_ORDER == __BIG_ENDIAN #define _FORMAT_LE(fmt) SPA_AUDIO_FORMAT_ ## fmt ## _OE #define _FORMAT_BE(fmt) SPA_AUDIO_FORMAT_ ## fmt #elif __BYTE_ORDER == __LITTLE_ENDIAN #define _FORMAT_LE(fmt) SPA_AUDIO_FORMAT_ ## fmt #define _FORMAT_BE(fmt) SPA_AUDIO_FORMAT_ ## fmt ## _OE #endif static const uint32_t audio_format_map[] = { SPA_AUDIO_FORMAT_UNKNOWN, SPA_AUDIO_FORMAT_ENCODED, SPA_AUDIO_FORMAT_S8, SPA_AUDIO_FORMAT_U8, _FORMAT_LE (S16), _FORMAT_BE (S16), _FORMAT_LE (U16), _FORMAT_BE (U16), _FORMAT_LE (S24_32), _FORMAT_BE (S24_32), _FORMAT_LE (U24_32), _FORMAT_BE (U24_32), _FORMAT_LE (S32), _FORMAT_BE (S32), _FORMAT_LE (U32), _FORMAT_BE (U32), _FORMAT_LE (S24), _FORMAT_BE (S24), _FORMAT_LE (U24), _FORMAT_BE (U24), _FORMAT_LE (S20), _FORMAT_BE (S20), _FORMAT_LE (U20), _FORMAT_BE (U20), _FORMAT_LE (S18), _FORMAT_BE (S18), _FORMAT_LE (U18), _FORMAT_BE (U18), _FORMAT_LE (F32), _FORMAT_BE (F32), _FORMAT_LE (F64), _FORMAT_BE (F64), }; typedef struct { const struct media_type *type; const GstCapsFeatures *cf; const GstStructure *cs; GPtrArray *array; } ConvertData; static const struct media_type * find_media_types (const char *name) { int i; for (i = 0; media_type_map[i].name; i++) { if (spa_streq(media_type_map[i].name, name)) return &media_type_map[i]; } return NULL; } static int find_index(const uint32_t *items, int n_items, uint32_t id) { int i; for (i = 0; i < n_items; i++) if (items[i] == id) return i; return -1; } static const char * get_nth_string (const GValue *val, int idx) { const GValue *v = NULL; GType type = G_VALUE_TYPE (val); if (type == G_TYPE_STRING && idx == 0) v = val; else if (type == GST_TYPE_LIST) { GArray *array = g_value_peek_pointer (val); if (idx < (int)(array->len + 1)) { v = &g_array_index (array, GValue, SPA_MAX (idx - 1, 0)); } } if (v) return g_value_get_string (v); return NULL; } static bool get_nth_int (const GValue *val, int idx, int *res) { const GValue *v = NULL; GType type = G_VALUE_TYPE (val); if (type == G_TYPE_INT && idx == 0) { v = val; } else if (type == GST_TYPE_INT_RANGE) { if (idx == 0 || idx == 1) { *res = gst_value_get_int_range_min (val); return true; } else if (idx == 2) { *res = gst_value_get_int_range_max (val); return true; } } else if (type == GST_TYPE_LIST) { GArray *array = g_value_peek_pointer (val); if (idx < (int)(array->len + 1)) { v = &g_array_index (array, GValue, SPA_MAX (idx - 1, 0)); } } if (v) { *res = g_value_get_int (v); return true; } return false; } static gboolean get_nth_fraction (const GValue *val, int idx, struct spa_fraction *f) { const GValue *v = NULL; GType type = G_VALUE_TYPE (val); if (type == GST_TYPE_FRACTION && idx == 0) { v = val; } else if (type == GST_TYPE_FRACTION_RANGE) { if (idx == 0 || idx == 1) { v = gst_value_get_fraction_range_min (val); } else if (idx == 2) { v = gst_value_get_fraction_range_max (val); } } else if (type == GST_TYPE_LIST) { GArray *array = g_value_peek_pointer (val); if (idx < (int)(array->len + 1)) { v = &g_array_index (array, GValue, SPA_MAX (idx-1, 0)); } } if (v) { f->num = gst_value_get_fraction_numerator (v); f->denom = gst_value_get_fraction_denominator (v); return true; } return false; } static gboolean get_nth_rectangle (const GValue *width, const GValue *height, int idx, struct spa_rectangle *r) { const GValue *w = NULL, *h = NULL; GType wt = G_VALUE_TYPE (width); GType ht = G_VALUE_TYPE (height); if (wt == G_TYPE_INT && ht == G_TYPE_INT && idx == 0) { w = width; h = height; } else if (wt == GST_TYPE_INT_RANGE && ht == GST_TYPE_INT_RANGE) { if (idx == 0 || idx == 1) { r->width = gst_value_get_int_range_min (width); r->height = gst_value_get_int_range_min (height); return true; } else if (idx == 2) { r->width = gst_value_get_int_range_max (width); r->height = gst_value_get_int_range_max (height); return true; } else if (idx == 3) { r->width = gst_value_get_int_range_step (width); r->height = gst_value_get_int_range_step (height); if (r->width > 1 || r->height > 1) return true; else return false; } } else if (wt == GST_TYPE_LIST && ht == GST_TYPE_LIST) { GArray *wa = g_value_peek_pointer (width); GArray *ha = g_value_peek_pointer (height); if (idx < (int)(wa->len + 1)) w = &g_array_index (wa, GValue, SPA_MAX (idx-1, 0)); if (idx < (int)(ha->len + 1)) h = &g_array_index (ha, GValue, SPA_MAX (idx-1, 0)); } if (w && h) { r->width = g_value_get_int (w); r->height = g_value_get_int (h); return true; } return false; } static uint32_t get_range_type (const GValue *val) { GType type = G_VALUE_TYPE (val); if (type == GST_TYPE_LIST) return SPA_CHOICE_Enum; if (type == GST_TYPE_DOUBLE_RANGE || type == GST_TYPE_FRACTION_RANGE) return SPA_CHOICE_Range; if (type == GST_TYPE_INT_RANGE) { if (gst_value_get_int_range_step (val) == 1) return SPA_CHOICE_Range; else return SPA_CHOICE_Step; } if (type == GST_TYPE_INT64_RANGE) { if (gst_value_get_int64_range_step (val) == 1) return SPA_CHOICE_Range; else return SPA_CHOICE_Step; } return SPA_CHOICE_None; } static uint32_t get_range_type2 (const GValue *v1, const GValue *v2) { uint32_t r1, r2; r1 = get_range_type (v1); r2 = get_range_type (v2); if (r1 == r2) return r1; if (r1 == SPA_CHOICE_Step || r2 == SPA_CHOICE_Step) return SPA_CHOICE_Step; if (r1 == SPA_CHOICE_Range || r2 == SPA_CHOICE_Range) return SPA_CHOICE_Range; return SPA_CHOICE_Range; } static void add_limits (struct spa_pod_dynamic_builder *b, ConvertData *d) { struct spa_pod_choice *choice; struct spa_pod_frame f; const GValue *value, *value2; int i; value = gst_structure_get_value (d->cs, "width"); value2 = gst_structure_get_value (d->cs, "height"); if (value && value2) { struct spa_rectangle v; for (i = 0; get_nth_rectangle (value, value2, i, &v); i++) { if (i == 0) { spa_pod_builder_prop (&b->b, SPA_FORMAT_VIDEO_size, 0); spa_pod_builder_push_choice(&b->b, &f, get_range_type2 (value, value2), 0); } spa_pod_builder_rectangle (&b->b, v.width, v.height); } if (i > 0) { choice = spa_pod_builder_pop(&b->b, &f); if (i == 1) choice->body.type = SPA_CHOICE_None; } } value = gst_structure_get_value (d->cs, "framerate"); if (value) { struct spa_fraction v; for (i = 0; get_nth_fraction (value, i, &v); i++) { if (i == 0) { spa_pod_builder_prop (&b->b, SPA_FORMAT_VIDEO_framerate, 0); spa_pod_builder_push_choice(&b->b, &f, get_range_type (value), 0); } spa_pod_builder_fraction (&b->b, v.num, v.denom); } if (i > 0) { choice = spa_pod_builder_pop(&b->b, &f); if (i == 1) choice->body.type = SPA_CHOICE_None; } } value = gst_structure_get_value (d->cs, "max-framerate"); if (value) { struct spa_fraction v; for (i = 0; get_nth_fraction (value, i, &v); i++) { if (i == 0) { spa_pod_builder_prop (&b->b, SPA_FORMAT_VIDEO_maxFramerate, 0); spa_pod_builder_push_choice(&b->b, &f, get_range_type (value), 0); } spa_pod_builder_fraction (&b->b, v.num, v.denom); } if (i > 0) { choice = spa_pod_builder_pop(&b->b, &f); if (i == 1) choice->body.type = SPA_CHOICE_None; } } } static void add_video_format (gpointer format_ptr, gpointer modifiers_ptr, gpointer user_data) { uint32_t format = GPOINTER_TO_UINT (format_ptr); GHashTable *modifiers = modifiers_ptr; ConvertData *d = user_data; struct spa_pod_dynamic_builder b; struct spa_pod_frame f; int n_mods; spa_pod_dynamic_builder_init (&b, NULL, 0, 1024); spa_pod_builder_push_object (&b.b, &f, SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat); spa_pod_builder_prop (&b.b, SPA_FORMAT_mediaType, 0); spa_pod_builder_id(&b.b, d->type->media_type); spa_pod_builder_prop (&b.b, SPA_FORMAT_mediaSubtype, 0); spa_pod_builder_id(&b.b, d->type->media_subtype); spa_pod_builder_prop (&b.b, SPA_FORMAT_VIDEO_format, 0); spa_pod_builder_id (&b.b, format); n_mods = g_hash_table_size (modifiers); if (n_mods > 0) { struct spa_pod_frame f2; GHashTableIter iter; gpointer key, value; uint32_t flags, choice_type; flags = SPA_POD_PROP_FLAG_MANDATORY; if (n_mods > 1) { choice_type = SPA_CHOICE_Enum; flags |= SPA_POD_PROP_FLAG_DONT_FIXATE; } else { choice_type = SPA_CHOICE_None; } spa_pod_builder_prop (&b.b, SPA_FORMAT_VIDEO_modifier, flags); spa_pod_builder_push_choice (&b.b, &f2, choice_type, 0); g_hash_table_iter_init (&iter, modifiers); g_hash_table_iter_next (&iter, &key, &value); spa_pod_builder_long (&b.b, (uint64_t) key); if (n_mods > 1) { do { spa_pod_builder_long (&b.b, (uint64_t) key); } while (g_hash_table_iter_next (&iter, &key, &value)); } spa_pod_builder_pop (&b.b, &f2); } add_limits (&b, d); g_ptr_array_add (d->array, spa_pod_builder_pop (&b.b, &f)); } static void handle_video_fields (ConvertData *d) { g_autoptr (GHashTable) formats = NULL; const GValue *value; gboolean dmabuf_caps; int i; formats = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify) g_hash_table_unref); dmabuf_caps = (d->cf && gst_caps_features_contains (d->cf, GST_CAPS_FEATURE_MEMORY_DMABUF)); value = gst_structure_get_value (d->cs, "format"); if (value) { const char *v; for (i = 0; (v = get_nth_string (value, i)); i++) { int idx; idx = gst_video_format_from_string (v); #ifdef HAVE_GSTREAMER_DMA_DRM if (dmabuf_caps && idx == GST_VIDEO_FORMAT_DMA_DRM) { const GValue *value2; value2 = gst_structure_get_value (d->cs, "drm-format"); if (value2) { const char *v2; int j; for (j = 0; (v2 = get_nth_string (value2, j)); j++) { uint32_t fourcc; uint64_t mod; int idx2; fourcc = gst_video_dma_drm_fourcc_from_string (v2, &mod); idx2 = gst_video_dma_drm_fourcc_to_format (fourcc); if (idx2 != GST_VIDEO_FORMAT_UNKNOWN && idx2 < (int)SPA_N_ELEMENTS (video_format_map)) { GHashTable *modifiers = g_hash_table_lookup (formats, GINT_TO_POINTER (video_format_map[idx2])); if (!modifiers) { modifiers = g_hash_table_new (NULL, NULL); g_hash_table_insert (formats, GINT_TO_POINTER (video_format_map[idx2]), modifiers); } g_hash_table_add (modifiers, GINT_TO_POINTER (mod)); } } } } else #endif if (idx != GST_VIDEO_FORMAT_UNKNOWN && idx < (int)SPA_N_ELEMENTS (video_format_map)) { GHashTable *modifiers = g_hash_table_lookup (formats, GINT_TO_POINTER (video_format_map[idx])); if (!modifiers) { modifiers = g_hash_table_new (NULL, NULL); g_hash_table_insert (formats, GINT_TO_POINTER (video_format_map[idx]), modifiers); } if (dmabuf_caps) { g_hash_table_add (modifiers, GINT_TO_POINTER (DRM_FORMAT_MOD_LINEAR)); g_hash_table_add (modifiers, GINT_TO_POINTER (DRM_FORMAT_MOD_INVALID)); } } } } if (g_hash_table_size (formats) > 0) { g_hash_table_foreach (formats, add_video_format, d); } else if (!dmabuf_caps) { struct spa_pod_dynamic_builder b; struct spa_pod_frame f; spa_pod_dynamic_builder_init (&b, NULL, 0, 1024); spa_pod_builder_push_object (&b.b, &f, SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat); spa_pod_builder_prop (&b.b, SPA_FORMAT_mediaType, 0); spa_pod_builder_id(&b.b, d->type->media_type); spa_pod_builder_prop (&b.b, SPA_FORMAT_mediaSubtype, 0); spa_pod_builder_id(&b.b, d->type->media_subtype); add_limits (&b, d); g_ptr_array_add (d->array, spa_pod_builder_pop (&b.b, &f)); } } static void set_default_channels (struct spa_pod_builder *b, uint32_t channels) { uint32_t position[8] = {0}; gboolean ok = TRUE; switch (channels) { case 8: position[6] = SPA_AUDIO_CHANNEL_SL; position[7] = SPA_AUDIO_CHANNEL_SR; SPA_FALLTHROUGH case 6: position[5] = SPA_AUDIO_CHANNEL_LFE; SPA_FALLTHROUGH case 5: position[4] = SPA_AUDIO_CHANNEL_FC; SPA_FALLTHROUGH case 4: position[2] = SPA_AUDIO_CHANNEL_RL; position[3] = SPA_AUDIO_CHANNEL_RR; SPA_FALLTHROUGH case 2: position[0] = SPA_AUDIO_CHANNEL_FL; position[1] = SPA_AUDIO_CHANNEL_FR; break; case 1: position[0] = SPA_AUDIO_CHANNEL_MONO; break; default: ok = FALSE; break; } if (ok) spa_pod_builder_add (b, SPA_FORMAT_AUDIO_position, SPA_POD_Array(sizeof(uint32_t), SPA_TYPE_Id, channels, position), 0); } static void handle_audio_fields (ConvertData *d) { const GValue *value; struct spa_pod_dynamic_builder b; struct spa_pod_choice *choice; struct spa_pod_frame f, f0; int i = 0; spa_pod_dynamic_builder_init (&b, NULL, 0, 1024); spa_pod_builder_push_object (&b.b, &f0, SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat); spa_pod_builder_prop (&b.b, SPA_FORMAT_mediaType, 0); spa_pod_builder_id(&b.b, d->type->media_type); spa_pod_builder_prop (&b.b, SPA_FORMAT_mediaSubtype, 0); spa_pod_builder_id(&b.b, d->type->media_subtype); value = gst_structure_get_value (d->cs, "format"); if (value) { const char *v; int idx; for (i = 0; (v = get_nth_string (value, i)); i++) { if (i == 0) { spa_pod_builder_prop (&b.b, SPA_FORMAT_AUDIO_format, 0); spa_pod_builder_push_choice(&b.b, &f, get_range_type (value), 0); } idx = gst_audio_format_from_string (v); if (idx < (int)SPA_N_ELEMENTS (audio_format_map)) spa_pod_builder_id (&b.b, audio_format_map[idx]); } if (i > 0) { choice = spa_pod_builder_pop(&b.b, &f); if (i == 1) choice->body.type = SPA_CHOICE_None; } } else if (strcmp(d->type->name, "audio/x-mulaw") == 0) { spa_pod_builder_prop (&b.b, SPA_FORMAT_AUDIO_format, 0); spa_pod_builder_id (&b.b, SPA_AUDIO_FORMAT_ULAW); } else if (strcmp(d->type->name, "audio/x-alaw") == 0) { spa_pod_builder_prop (&b.b, SPA_FORMAT_AUDIO_format, 0); spa_pod_builder_id (&b.b, SPA_AUDIO_FORMAT_ALAW); } else if (strcmp(d->type->name, "audio/mpeg") == 0) { spa_pod_builder_prop (&b.b, SPA_FORMAT_AUDIO_format, 0); spa_pod_builder_id (&b.b, SPA_AUDIO_FORMAT_ENCODED); } else if (strcmp(d->type->name, "audio/x-flac") == 0) { spa_pod_builder_prop (&b.b, SPA_FORMAT_AUDIO_format, 0); spa_pod_builder_id (&b.b, SPA_AUDIO_FORMAT_ENCODED); } #if 0 value = gst_structure_get_value (d->cs, "layout"); if (value) { const char *v; for (i = 0; (v = get_nth_string (value, i)); i++) { enum spa_audio_layout layout; if (spa_streq(v, "interleaved")) layout = SPA_AUDIO_LAYOUT_INTERLEAVED; else if (spa_streq(v, "non-interleaved")) layout = SPA_AUDIO_LAYOUT_NON_INTERLEAVED; else break; if (i == 0) { spa_pod_builder_prop (&b.b, SPA_FORMAT_AUDIO_layout, 0); spa_pod_builder_push_choice(&b.b, &f, get_range_type (value), 0); } spa_pod_builder_id (&b.b, layout); } if (i > 0) { choice = spa_pod_builder_pop(&b.b, &f); if (i == 1) choice->body.type = SPA_CHOICE_None; } } #endif value = gst_structure_get_value (d->cs, "rate"); if (value) { int v; for (i = 0; get_nth_int (value, i, &v); i++) { if (i == 0) { spa_pod_builder_prop (&b.b, SPA_FORMAT_AUDIO_rate, 0); spa_pod_builder_push_choice(&b.b, &f, get_range_type (value), 0); } spa_pod_builder_int (&b.b, v); } if (i > 0) { choice = spa_pod_builder_pop(&b.b, &f); if (i == 1) choice->body.type = SPA_CHOICE_None; } } value = gst_structure_get_value (d->cs, "channels"); if (value) { int v; for (i = 0; get_nth_int (value, i, &v); i++) { if (i == 0) { spa_pod_builder_prop (&b.b, SPA_FORMAT_AUDIO_channels, 0); spa_pod_builder_push_choice(&b.b, &f, get_range_type (value), 0); } spa_pod_builder_int (&b.b, v); } if (i > 0) { choice = spa_pod_builder_pop(&b.b, &f); if (i == 1) { choice->body.type = SPA_CHOICE_None; set_default_channels (&b.b, v); } } } g_ptr_array_add (d->array, spa_pod_builder_pop (&b.b, &f0)); } static void handle_fields (ConvertData *d) { if (!(d->type = find_media_types (gst_structure_get_name (d->cs)))) return; if (d->type->media_type == SPA_MEDIA_TYPE_video) handle_video_fields (d); else if (d->type->media_type == SPA_MEDIA_TYPE_audio) handle_audio_fields (d); } static gboolean foreach_func_dmabuf (GstCapsFeatures *features, GstStructure *structure, ConvertData *d) { if (!features || !gst_caps_features_contains (features, GST_CAPS_FEATURE_MEMORY_DMABUF)) return TRUE; d->cf = features; d->cs = structure; handle_fields (d); return TRUE; } static gboolean foreach_func_no_dmabuf (GstCapsFeatures *features, GstStructure *structure, ConvertData *d) { if (features && gst_caps_features_contains (features, GST_CAPS_FEATURE_MEMORY_DMABUF)) return TRUE; d->cf = features; d->cs = structure; handle_fields (d); return TRUE; } GPtrArray * gst_caps_to_format_all (GstCaps *caps) { ConvertData d; d.array = g_ptr_array_new_full (gst_caps_get_size (caps), (GDestroyNotify)g_free); gst_caps_foreach (caps, (GstCapsForeachFunc) foreach_func_dmabuf, &d); gst_caps_foreach (caps, (GstCapsForeachFunc) foreach_func_no_dmabuf, &d); return d.array; } typedef const char *(*id_to_string_func)(uint32_t id); static const char *video_id_to_string(uint32_t id) { int idx; if ((idx = find_index(video_format_map, SPA_N_ELEMENTS(video_format_map), id)) == -1) return NULL; return gst_video_format_to_string(idx); } #ifdef HAVE_GSTREAMER_DMA_DRM static char *video_id_to_dma_drm_fourcc(uint32_t id, uint64_t mod) { int idx; guint32 fourcc; if ((idx = find_index(video_format_map, SPA_N_ELEMENTS(video_format_map), id)) == -1) return NULL; fourcc = gst_video_dma_drm_fourcc_from_format(idx); if (fourcc == DRM_FORMAT_INVALID) return NULL; return gst_video_dma_drm_fourcc_to_string(fourcc, mod); } #endif static const char *interlace_mode_id_to_string(uint32_t id) { int idx; if ((idx = find_index(interlace_mode_map, SPA_N_ELEMENTS(interlace_mode_map), id)) == -1) return NULL; return gst_video_interlace_mode_to_string(idx); } static const char *audio_id_to_string(uint32_t id) { int idx; if ((idx = find_index(audio_format_map, SPA_N_ELEMENTS(audio_format_map), id)) == -1) return NULL; return gst_audio_format_to_string(idx); } static GstVideoColorRange color_range_to_gst(uint32_t id) { int idx; if ((idx = find_index(color_range_map, SPA_N_ELEMENTS(color_range_map), id)) == -1) return GST_VIDEO_COLOR_RANGE_UNKNOWN; return idx; } static GstVideoColorMatrix color_matrix_to_gst(uint32_t id) { int idx; if ((idx = find_index(color_matrix_map, SPA_N_ELEMENTS(color_matrix_map), id)) == -1) return GST_VIDEO_COLOR_MATRIX_UNKNOWN; return idx; } static GstVideoTransferFunction transfer_function_to_gst(uint32_t id) { int idx; if ((idx = find_index(transfer_function_map, SPA_N_ELEMENTS(transfer_function_map), id)) == -1) return GST_VIDEO_TRANSFER_UNKNOWN; return idx; } static GstVideoColorPrimaries color_primaries_to_gst(uint32_t id) { int idx; if ((idx = find_index(color_primaries_map, SPA_N_ELEMENTS(color_primaries_map), id)) == -1) return GST_VIDEO_COLOR_PRIMARIES_UNKNOWN; return idx; } static void colorimetry_to_gst_colorimetry(struct spa_video_colorimetry *colorimetry, GstVideoColorimetry *gst_colorimetry) { gst_colorimetry->range = color_range_to_gst(colorimetry->range); gst_colorimetry->matrix = color_matrix_to_gst(colorimetry->matrix); gst_colorimetry->transfer = transfer_function_to_gst(colorimetry->transfer); gst_colorimetry->primaries = color_primaries_to_gst(colorimetry->primaries); } static void handle_id_prop (const struct spa_pod_prop *prop, const char *key, id_to_string_func func, GstCaps *res) { const char * str; struct spa_pod *val; uint32_t *id; uint32_t i, n_items, choice; val = spa_pod_get_values(&prop->value, &n_items, &choice); if (val->type != SPA_TYPE_Id || n_items == 0) return; id = SPA_POD_BODY(val); switch (choice) { case SPA_CHOICE_None: if (!(str = func(id[0]))) return; gst_caps_set_simple (res, key, G_TYPE_STRING, str, NULL); break; case SPA_CHOICE_Enum: { GValue list = { 0 }, v = { 0 }; g_value_init (&list, GST_TYPE_LIST); for (i = 1; i < n_items; i++) { if (!(str = func(id[i]))) continue; g_value_init (&v, G_TYPE_STRING); g_value_set_string (&v, str); gst_value_list_append_and_take_value (&list, &v); } gst_caps_set_value (res, key, &list); g_value_unset (&list); break; } default: break; } } static void handle_dmabuf_prop (const struct spa_pod_prop *prop, const struct spa_pod_prop *prop_modifier, GstCaps *res) { g_autoptr (GPtrArray) fmt_array = NULL; g_autoptr (GPtrArray) drm_fmt_array = NULL; const struct spa_pod *pod_modifier; struct spa_pod *val; uint32_t *id, n_fmts, n_mods, choice, i, j; uint64_t *mods; val = spa_pod_get_values (&prop->value, &n_fmts, &choice); if (val->type != SPA_TYPE_Id || n_fmts == 0) return; if (choice != SPA_CHOICE_None && choice != SPA_CHOICE_Enum) return; id = SPA_POD_BODY (val); if (n_fmts > 1) { n_fmts--; id++; } pod_modifier = spa_pod_get_values (&prop_modifier->value, &n_mods, &choice); if (pod_modifier->type != SPA_TYPE_Long || n_mods == 0) return; if (choice != SPA_CHOICE_None && choice != SPA_CHOICE_Enum) return; mods = SPA_POD_BODY (pod_modifier); if (n_mods > 1) { n_mods--; mods++; } fmt_array = g_ptr_array_new_with_free_func (g_free); drm_fmt_array = g_ptr_array_new_with_free_func (g_free); for (i = 0; i < n_fmts; i++) { for (j = 0; j < n_mods; j++) { gboolean as_drm = FALSE; const char *fmt_str; #ifdef HAVE_GSTREAMER_DMA_DRM if (mods[j] != DRM_FORMAT_MOD_INVALID) { char *drm_str; if ((drm_str = video_id_to_dma_drm_fourcc(id[i], mods[j]))) { g_ptr_array_add(drm_fmt_array, drm_str); as_drm = TRUE; } } #endif if (!as_drm && (mods[j] == DRM_FORMAT_MOD_LINEAR || mods[j] == DRM_FORMAT_MOD_INVALID) && (fmt_str = video_id_to_string(id[i]))) g_ptr_array_add(fmt_array, g_strdup_printf ("%s", fmt_str)); } } #ifdef HAVE_GSTREAMER_DMA_DRM if (drm_fmt_array->len > 0) { g_ptr_array_add (fmt_array, g_strdup_printf ("DMA_DRM")); if (drm_fmt_array->len == 1) { gst_caps_set_simple (res, "drm-format", G_TYPE_STRING, g_ptr_array_index (drm_fmt_array, 0), NULL); } else { GValue list = { 0 }; g_value_init (&list, GST_TYPE_LIST); for (i = 0; i < drm_fmt_array->len; i++) { GValue v = { 0 }; g_value_init (&v, G_TYPE_STRING); g_value_set_string (&v, g_ptr_array_index (drm_fmt_array, i)); gst_value_list_append_and_take_value (&list, &v); } gst_caps_set_value (res, "drm-format", &list); g_value_unset (&list); } } #endif if (fmt_array->len > 0) { gst_caps_set_features_simple (res, gst_caps_features_from_string (GST_CAPS_FEATURE_MEMORY_DMABUF)); if (fmt_array->len == 1) { gst_caps_set_simple (res, "format", G_TYPE_STRING, g_ptr_array_index (fmt_array, 0), NULL); } else { GValue list = { 0 }; g_value_init (&list, GST_TYPE_LIST); for (i = 0; i < fmt_array->len; i++) { GValue v = { 0 }; g_value_init (&v, G_TYPE_STRING); g_value_set_string (&v, g_ptr_array_index (fmt_array, i)); gst_value_list_append_and_take_value (&list, &v); } gst_caps_set_value (res, "format", &list); g_value_unset (&list); } } } static void handle_int_prop (const struct spa_pod_prop *prop, const char *key, GstCaps *res) { struct spa_pod *val; uint32_t *ints; uint32_t i, n_items, choice; val = spa_pod_get_values(&prop->value, &n_items, &choice); if (val->type != SPA_TYPE_Int || n_items == 0) return; ints = SPA_POD_BODY(val); switch (choice) { case SPA_CHOICE_None: gst_caps_set_simple (res, key, G_TYPE_INT, ints[0], NULL); break; case SPA_CHOICE_Range: case SPA_CHOICE_Step: { if (n_items < 3) return; gst_caps_set_simple (res, key, GST_TYPE_INT_RANGE, ints[1], ints[2], NULL); break; } case SPA_CHOICE_Enum: { GValue list = { 0 }, v = { 0 }; g_value_init (&list, GST_TYPE_LIST); for (i = 1; i < n_items; i++) { g_value_init (&v, G_TYPE_INT); g_value_set_int (&v, ints[i]); gst_value_list_append_and_take_value (&list, &v); } gst_caps_set_value (res, key, &list); g_value_unset (&list); break; } default: break; } } static void handle_rect_prop (const struct spa_pod_prop *prop, const char *width, const char *height, GstCaps *res) { struct spa_pod *val; struct spa_rectangle *rect; uint32_t i, n_items, choice; val = spa_pod_get_values(&prop->value, &n_items, &choice); if (val->type != SPA_TYPE_Rectangle || n_items == 0) return; rect = SPA_POD_BODY(val); switch (choice) { case SPA_CHOICE_None: gst_caps_set_simple (res, width, G_TYPE_INT, rect[0].width, height, G_TYPE_INT, rect[0].height, NULL); break; case SPA_CHOICE_Range: case SPA_CHOICE_Step: { if (n_items < 3) return; if (rect[1].width == rect[2].width && rect[1].height == rect[2].height) { gst_caps_set_simple (res, width, G_TYPE_INT, rect[1].width, height, G_TYPE_INT, rect[1].height, NULL); } else { gst_caps_set_simple (res, width, GST_TYPE_INT_RANGE, rect[1].width, rect[2].width, height, GST_TYPE_INT_RANGE, rect[1].height, rect[2].height, NULL); } break; } case SPA_CHOICE_Enum: { GValue l1 = { 0 }, l2 = { 0 }, v1 = { 0 }, v2 = { 0 }; g_value_init (&l1, GST_TYPE_LIST); g_value_init (&l2, GST_TYPE_LIST); for (i = 1; i < n_items; i++) { g_value_init (&v1, G_TYPE_INT); g_value_set_int (&v1, rect[i].width); gst_value_list_append_and_take_value (&l1, &v1); g_value_init (&v2, G_TYPE_INT); g_value_set_int (&v2, rect[i].height); gst_value_list_append_and_take_value (&l2, &v2); } gst_caps_set_value (res, width, &l1); gst_caps_set_value (res, height, &l2); g_value_unset (&l1); g_value_unset (&l2); break; } default: break; } } static void handle_fraction_prop (const struct spa_pod_prop *prop, const char *key, GstCaps *res) { struct spa_pod *val; struct spa_fraction *fract; uint32_t i, n_items, choice; val = spa_pod_get_values(&prop->value, &n_items, &choice); if (val->type != SPA_TYPE_Fraction || n_items == 0) return; fract = SPA_POD_BODY(val); switch (choice) { case SPA_CHOICE_None: gst_caps_set_simple (res, key, GST_TYPE_FRACTION, fract[0].num, fract[0].denom, NULL); break; case SPA_CHOICE_Range: case SPA_CHOICE_Step: { if (n_items < 3) return; if (fract[1].num == fract[2].num && fract[1].denom == fract[2].denom) { gst_caps_set_simple (res, key, GST_TYPE_FRACTION, fract[1].num, fract[1].denom, NULL); } else { gst_caps_set_simple (res, key, GST_TYPE_FRACTION_RANGE, fract[1].num, fract[1].denom, fract[2].num, fract[2].denom, NULL); } break; } case SPA_CHOICE_Enum: { GValue l1 = { 0 }, v1 = { 0 }; g_value_init (&l1, GST_TYPE_LIST); for (i = 1; i < n_items; i++) { g_value_init (&v1, GST_TYPE_FRACTION); gst_value_set_fraction (&v1, fract[i].num, fract[i].denom); gst_value_list_append_and_take_value (&l1, &v1); } gst_caps_set_value (res, key, &l1); g_value_unset (&l1); break; } default: break; } } GstCaps * gst_caps_from_format (const struct spa_pod *format) { GstCaps *res = NULL; uint32_t media_type, media_subtype; struct spa_video_colorimetry colorimetry = { 0 }; const struct spa_pod_prop *prop = NULL; const struct spa_pod_object *obj = (const struct spa_pod_object *) format; if (spa_format_parse(format, &media_type, &media_subtype) < 0) return res; if (media_type == SPA_MEDIA_TYPE_video) { if (media_subtype == SPA_MEDIA_SUBTYPE_raw) { const struct spa_pod_prop *prop_modifier; res = gst_caps_new_empty_simple ("video/x-raw"); if ((prop = spa_pod_object_find_prop (obj, prop, SPA_FORMAT_VIDEO_format)) && (prop_modifier = spa_pod_object_find_prop (obj, NULL, SPA_FORMAT_VIDEO_modifier))) { handle_dmabuf_prop (prop, prop_modifier, res); } else { if ((prop = spa_pod_object_find_prop (obj, prop, SPA_FORMAT_VIDEO_format))) { handle_id_prop (prop, "format", video_id_to_string, res); } } if ((prop = spa_pod_object_find_prop (obj, prop, SPA_FORMAT_VIDEO_interlaceMode))) { handle_id_prop (prop, "interlace-mode", interlace_mode_id_to_string, res); } else { gst_caps_set_simple(res, "interlace-mode", G_TYPE_STRING, "progressive", NULL); } } else if (media_subtype == SPA_MEDIA_SUBTYPE_mjpg) { res = gst_caps_new_empty_simple ("image/jpeg"); } else if (media_subtype == SPA_MEDIA_SUBTYPE_h264) { res = gst_caps_new_simple ("video/x-h264", "stream-format", G_TYPE_STRING, "byte-stream", "alignment", G_TYPE_STRING, "au", NULL); } else if (media_subtype == SPA_MEDIA_SUBTYPE_h265) { res = gst_caps_new_simple ("video/x-h265", "stream-format", G_TYPE_STRING, "byte-stream", "alignment", G_TYPE_STRING, "au", NULL); } else { return NULL; } if ((prop = spa_pod_object_find_prop (obj, prop, SPA_FORMAT_VIDEO_size))) { handle_rect_prop (prop, "width", "height", res); } if ((prop = spa_pod_object_find_prop (obj, prop, SPA_FORMAT_VIDEO_framerate))) { handle_fraction_prop (prop, "framerate", res); } if ((prop = spa_pod_object_find_prop (obj, prop, SPA_FORMAT_VIDEO_maxFramerate))) { handle_fraction_prop (prop, "max-framerate", res); } if (spa_pod_parse_object(format, SPA_TYPE_OBJECT_Format, NULL, SPA_FORMAT_VIDEO_colorRange, SPA_POD_OPT_Id(&colorimetry.range), SPA_FORMAT_VIDEO_colorMatrix, SPA_POD_OPT_Id(&colorimetry.matrix), SPA_FORMAT_VIDEO_transferFunction, SPA_POD_OPT_Id(&colorimetry.transfer), SPA_FORMAT_VIDEO_colorPrimaries, SPA_POD_OPT_Id(&colorimetry.primaries)) > 0) { GstVideoColorimetry gst_colorimetry; char *color; colorimetry_to_gst_colorimetry(&colorimetry, &gst_colorimetry); color = gst_video_colorimetry_to_string(&gst_colorimetry); gst_caps_set_simple(res, "colorimetry", G_TYPE_STRING, color, NULL); g_free(color); } } else if (media_type == SPA_MEDIA_TYPE_audio) { if (media_subtype == SPA_MEDIA_SUBTYPE_raw) { res = gst_caps_new_simple ("audio/x-raw", "layout", G_TYPE_STRING, "interleaved", NULL); if ((prop = spa_pod_object_find_prop (obj, prop, SPA_FORMAT_AUDIO_format))) { handle_id_prop (prop, "format", audio_id_to_string, res); } if ((prop = spa_pod_object_find_prop (obj, prop, SPA_FORMAT_AUDIO_rate))) { handle_int_prop (prop, "rate", res); } if ((prop = spa_pod_object_find_prop (obj, prop, SPA_FORMAT_AUDIO_channels))) { handle_int_prop (prop, "channels", res); } } } return res; } static gboolean filter_dmabuf_caps (GstCapsFeatures *features, GstStructure *structure, gpointer user_data) { const GValue *value; const char *v; if (!gst_caps_features_contains (features, GST_CAPS_FEATURE_MEMORY_DMABUF)) return TRUE; if (!(value = gst_structure_get_value (structure, "format")) || !(v = get_nth_string (value, 0))) return FALSE; #ifdef HAVE_GSTREAMER_DMA_DRM { int idx; idx = gst_video_format_from_string (v); if (idx == GST_VIDEO_FORMAT_UNKNOWN) return FALSE; if (idx == GST_VIDEO_FORMAT_DMA_DRM && !gst_structure_get_value (structure, "drm-format")) return FALSE; } #endif return TRUE; } void gst_caps_sanitize (GstCaps **caps) { g_return_if_fail (GST_IS_CAPS (*caps)); *caps = gst_caps_make_writable (*caps); gst_caps_filter_and_map_in_place (*caps, filter_dmabuf_caps, NULL); } void gst_caps_maybe_fixate_dma_format (GstCaps *caps) { #ifdef HAVE_GSTREAMER_DMA_DRM GstCapsFeatures *features; GstStructure *structure; const GValue *format_value; const GValue *drm_format_value; const char *format_string; const char *drm_format_string; uint32_t fourcc; uint64_t mod; int drm_idx; int i; g_return_if_fail (GST_IS_CAPS (caps)); if (gst_caps_is_fixed (caps) || gst_caps_get_size(caps) != 1) return; features = gst_caps_get_features (caps, 0); if (!gst_caps_features_contains (features, GST_CAPS_FEATURE_MEMORY_DMABUF)) return; structure = gst_caps_get_structure (caps, 0); if (!gst_structure_has_field (structure, "format") || !gst_structure_has_field (structure, "drm-format")) return; format_value = gst_structure_get_value (structure, "format"); drm_format_value = gst_structure_get_value (structure, "drm-format"); if (G_VALUE_TYPE (format_value) != GST_TYPE_LIST || ((GArray *) g_value_peek_pointer (format_value))->len != 2 || G_VALUE_TYPE (drm_format_value) != G_TYPE_STRING) return; drm_format_string = g_value_get_string (drm_format_value); fourcc = gst_video_dma_drm_fourcc_from_string (drm_format_string, &mod); drm_idx = gst_video_dma_drm_fourcc_to_format (fourcc); if (drm_idx == GST_VIDEO_FORMAT_UNKNOWN || mod != DRM_FORMAT_MOD_LINEAR) return; for (i = 0; (format_string = get_nth_string (format_value, i)); i++) { int idx; idx = gst_video_format_from_string (format_string); if (idx != GST_VIDEO_FORMAT_DMA_DRM && idx != drm_idx) return; } gst_caps_set_simple (caps, "format", G_TYPE_STRING, "DMA_DRM", NULL); g_warn_if_fail (gst_caps_is_fixed (caps)); #endif } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/gst/gstpipewireformat.h000066400000000000000000000010111511204443500257770ustar00rootroot00000000000000/* GStreamer */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef _GST_PIPEWIRE_FORMAT_H_ #define _GST_PIPEWIRE_FORMAT_H_ #include #include G_BEGIN_DECLS GPtrArray * gst_caps_to_format_all (GstCaps *caps); GstCaps * gst_caps_from_format (const struct spa_pod *format); void gst_caps_sanitize (GstCaps **caps); void gst_caps_maybe_fixate_dma_format (GstCaps *caps); G_END_DECLS #endif pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/gst/gstpipewirepool.c000066400000000000000000000360731511204443500254730ustar00rootroot00000000000000/* GStreamer */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include "config.h" #include #include #include #include #ifdef HAVE_GSTREAMER_SHM_ALLOCATOR #include #endif #include #include #include "gstpipewirepool.h" #include #include GST_DEBUG_CATEGORY_STATIC (gst_pipewire_pool_debug_category); #define GST_CAT_DEFAULT gst_pipewire_pool_debug_category G_DEFINE_TYPE (GstPipeWirePool, gst_pipewire_pool, GST_TYPE_BUFFER_POOL); enum { ACTIVATED, /* FILL ME */ LAST_SIGNAL }; static guint pool_signals[LAST_SIGNAL] = { 0 }; static GQuark pool_data_quark; GstPipeWirePool * gst_pipewire_pool_new (GstPipeWireStream *stream) { GstPipeWirePool *pool; pool = g_object_new (GST_TYPE_PIPEWIRE_POOL, NULL); g_weak_ref_set (&pool->stream, stream); return pool; } static void pool_data_destroy (gpointer user_data) { GstPipeWirePoolData *data = user_data; gst_object_unref (data->pool); g_free (data); } void gst_pipewire_pool_wrap_buffer (GstPipeWirePool *pool, struct pw_buffer *b) { GstBuffer *buf; uint32_t i; GstPipeWirePoolData *data; /* Default to a large enough value */ gsize plane_0_size = pool->has_rawvideo ? pool->video_info.size : (gsize) pool->video_info.width * pool->video_info.height; gsize plane_sizes[GST_VIDEO_MAX_PLANES] = { plane_0_size, }; GST_DEBUG_OBJECT (pool, "wrap buffer, datas:%d", b->buffer->n_datas); data = g_new0 (GstPipeWirePoolData, 1); buf = gst_buffer_new (); if (pool->add_metavideo) { GstVideoMeta *meta = gst_buffer_add_video_meta_full (buf, GST_VIDEO_FRAME_FLAG_NONE, GST_VIDEO_INFO_FORMAT (&pool->video_info), GST_VIDEO_INFO_WIDTH (&pool->video_info), GST_VIDEO_INFO_HEIGHT (&pool->video_info), GST_VIDEO_INFO_N_PLANES (&pool->video_info), pool->video_info.offset, pool->video_info.stride); gst_video_meta_set_alignment (meta, pool->video_align); if (!gst_video_meta_get_plane_size (meta, plane_sizes)) { GST_ERROR_OBJECT (pool, "could not compute plane sizes"); } /* * We need to set the video meta as pooled, else gst_buffer_pool_release_buffer * will call reset_buffer and the default_reset_buffer implementation for * GstBufferPool removes all metadata without the POOLED flag. */ GST_META_FLAG_SET (meta, GST_META_FLAG_POOLED); } for (i = 0; i < b->buffer->n_datas; i++) { struct spa_data *d = &b->buffer->datas[i]; GstMemory *gmem = NULL; GST_DEBUG_OBJECT (pool, "wrap data (%s %d) %d %d", spa_debug_type_find_short_name(spa_type_data_type, d->type), d->type, d->mapoffset, d->maxsize); if (pool->allocate_memory) { #ifdef HAVE_GSTREAMER_SHM_ALLOCATOR gsize block_size = d->maxsize; if (pool->has_video) { /* For video, we know block sizes from the video info already */ block_size = plane_sizes[i]; } else { /* For audio, reserve space based on the quantum limit and channel count */ g_autoptr (GstPipeWireStream) s = g_weak_ref_get (&pool->stream); struct pw_context *context = pw_core_get_context(pw_stream_get_core(s->pwstream)); const struct pw_properties *props = pw_context_get_properties(context); uint32_t quantum_limit = 8192; /* "reasonable" default */ const char *quantum = spa_dict_lookup(&props->dict, "clock.quantum-limit"); if (!quantum) { quantum = spa_dict_lookup(&props->dict, "default.clock.quantum-limit"); GST_DEBUG_OBJECT (pool, "using default quantum limit %s", quantum); } if (quantum) spa_atou32(quantum, &quantum_limit, 0); GST_DEBUG_OBJECT (pool, "quantum limit %s", quantum); block_size = quantum_limit * pool->audio_info.bpf; } GST_DEBUG_OBJECT (pool, "setting block size %zu", block_size); if (!pool->shm_allocator) pool->shm_allocator = gst_shm_allocator_get(); /* use MemFd only. That is the only supported data type when memory is remote i.e. allocated by the client */ gmem = gst_allocator_alloc (pool->shm_allocator, block_size, NULL); d->fd = gst_fd_memory_get_fd (gmem); d->mapoffset = 0; d->flags = SPA_DATA_FLAG_READWRITE | SPA_DATA_FLAG_MAPPABLE; d->type = SPA_DATA_MemFd; d->maxsize = block_size; d->data = NULL; #endif } else if (d->type == SPA_DATA_MemFd) { gmem = gst_fd_allocator_alloc (pool->fd_allocator, dup(d->fd), d->mapoffset + d->maxsize, GST_FD_MEMORY_FLAG_NONE); gst_memory_resize (gmem, d->mapoffset, d->maxsize); } else if(d->type == SPA_DATA_DmaBuf) { GstMapInfo info = { 0 }; GstFdMemoryFlags fd_flags = GST_FD_MEMORY_FLAG_NONE; if (d->flags & SPA_DATA_FLAG_MAPPABLE && d->flags & SPA_DATA_FLAG_READABLE) fd_flags |= GST_FD_MEMORY_FLAG_KEEP_MAPPED; gmem = gst_fd_allocator_alloc (pool->dmabuf_allocator, dup(d->fd), d->mapoffset + d->maxsize, fd_flags); gst_memory_resize (gmem, d->mapoffset, d->maxsize); if (fd_flags & GST_FD_MEMORY_FLAG_KEEP_MAPPED) { GstMapFlags map_flags = GST_MAP_READ; if (d->flags & SPA_DATA_FLAG_WRITABLE) map_flags |= GST_MAP_WRITE; if (gst_memory_map (gmem, &info, map_flags)) { gst_memory_unmap (gmem, &info); } else { GST_ERROR_OBJECT (pool, "mmaping buffer failed"); } } } else if (d->type == SPA_DATA_MemPtr) { gmem = gst_memory_new_wrapped (0, d->data, d->maxsize, 0, d->maxsize, NULL, NULL); } else { GST_WARNING_OBJECT (pool, "unknown data type (%s %d)", spa_debug_type_find_short_name(spa_type_data_type, d->type), d->type); } if (gmem) gst_buffer_insert_memory (buf, i, gmem); } if (pool->add_metavideo && !pool->allocate_memory) { /* Set memory sizes to expected plane sizes, so we know the valid size, * and the offsets in the meta make sense */ for (i = 0; i < gst_buffer_n_memory (buf); i++) { GstMemory *mem = gst_buffer_peek_memory (buf, i); gst_memory_resize (mem, 0, plane_sizes[i]); } } data->pool = gst_object_ref (pool); data->owner = NULL; data->header = spa_buffer_find_meta_data (b->buffer, SPA_META_Header, sizeof(*data->header)); data->flags = GST_BUFFER_FLAGS (buf); data->b = b; data->buf = buf; data->crop = spa_buffer_find_meta_data (b->buffer, SPA_META_VideoCrop, sizeof(*data->crop)); if (data->crop) gst_buffer_add_video_crop_meta(buf); data->videotransform = spa_buffer_find_meta_data (b->buffer, SPA_META_VideoTransform, sizeof(*data->videotransform)); data->cursor = spa_buffer_find_meta_data (b->buffer, SPA_META_Cursor, sizeof(*data->cursor)); gst_mini_object_set_qdata (GST_MINI_OBJECT_CAST (buf), pool_data_quark, data, pool_data_destroy); b->user_data = data; pool->n_buffers++; } void gst_pipewire_pool_remove_buffer (GstPipeWirePool *pool, struct pw_buffer *b) { GstPipeWirePoolData *data = b->user_data; data->b = NULL; data->header = NULL; data->crop = NULL; data->videotransform = NULL; if (!pool->allocate_memory) gst_buffer_remove_all_memory (data->buf); /* this will also destroy the pool data, if this is the last reference */ gst_clear_buffer (&data->buf); pool->n_buffers--; } GstPipeWirePoolData *gst_pipewire_pool_get_data (GstBuffer *buffer) { return gst_mini_object_get_qdata (GST_MINI_OBJECT_CAST (buffer), pool_data_quark); } static GstFlowReturn acquire_buffer (GstBufferPool * pool, GstBuffer ** buffer, GstBufferPoolAcquireParams * params) { GstPipeWirePool *p = GST_PIPEWIRE_POOL (pool); g_autoptr (GstPipeWireStream) s = g_weak_ref_get (&p->stream); GstPipeWirePoolData *data; struct pw_buffer *b; if (G_UNLIKELY (!s)) return GST_FLOW_ERROR; GST_OBJECT_LOCK (pool); while (TRUE) { if (G_UNLIKELY (GST_BUFFER_POOL_IS_FLUSHING (pool))) goto flushing; if ((b = pw_stream_dequeue_buffer(s->pwstream))) { GST_LOG_OBJECT (pool, "dequeued buffer %p", b); break; } if (params) { if (params->flags & GST_BUFFER_POOL_ACQUIRE_FLAG_DONTWAIT) goto no_more_buffers; if ((params->flags & GST_BUFFER_POOL_ACQUIRE_FLAG_LAST) && p->paused) goto paused; } GST_WARNING_OBJECT (pool, "failed to dequeue buffer: %s", strerror(errno)); g_cond_wait (&p->cond, GST_OBJECT_GET_LOCK (pool)); } data = b->user_data; data->queued = FALSE; *buffer = data->buf; GST_OBJECT_UNLOCK (pool); GST_LOG_OBJECT (pool, "acquired gstbuffer %p", *buffer); return GST_FLOW_OK; flushing: { GST_OBJECT_UNLOCK (pool); return GST_FLOW_FLUSHING; } paused: { GST_OBJECT_UNLOCK (pool); return GST_FLOW_CUSTOM_ERROR_1; } no_more_buffers: { GST_LOG_OBJECT (pool, "no more buffers"); GST_OBJECT_UNLOCK (pool); return GST_FLOW_EOS; } } static const gchar ** get_options (GstBufferPool * pool) { static const gchar *options[] = { GST_BUFFER_POOL_OPTION_VIDEO_META, #ifdef HAVE_GSTREAMER_SHM_ALLOCATOR GST_BUFFER_POOL_OPTION_VIDEO_ALIGNMENT, #endif NULL }; return options; } static gboolean set_config (GstBufferPool * pool, GstStructure * config) { GstPipeWirePool *p = GST_PIPEWIRE_POOL (pool); GstCaps *caps; GstStructure *structure; guint size, min_buffers, max_buffers; gboolean has_videoalign; if (!gst_buffer_pool_config_get_params (config, &caps, &size, &min_buffers, &max_buffers)) { GST_WARNING_OBJECT (pool, "invalid config"); return FALSE; } if (caps == NULL) { GST_WARNING_OBJECT (pool, "no caps in config"); return FALSE; } /* We don't support unlimited buffers */ if (max_buffers == 0) max_buffers = PIPEWIRE_POOL_MAX_BUFFERS; /* Pick a sensible min to avoid starvation */ if (min_buffers == 0) min_buffers = PIPEWIRE_POOL_MIN_BUFFERS; if (min_buffers < PIPEWIRE_POOL_MIN_BUFFERS || max_buffers > PIPEWIRE_POOL_MAX_BUFFERS) return FALSE; structure = gst_caps_get_structure (caps, 0); if (g_str_has_prefix (gst_structure_get_name (structure), "video/") || g_str_has_prefix (gst_structure_get_name (structure), "image/")) { p->has_video = TRUE; gst_video_info_from_caps (&p->video_info, caps); if (GST_VIDEO_FORMAT_INFO_IS_VALID_RAW (p->video_info.finfo) #ifdef HAVE_GSTREAMER_DMA_DRM && GST_VIDEO_FORMAT_INFO_FORMAT (p->video_info.finfo) != GST_VIDEO_FORMAT_DMA_DRM #endif ) p->has_rawvideo = TRUE; else p->has_rawvideo = FALSE; #ifdef HAVE_GSTREAMER_SHM_ALLOCATOR if (p->has_rawvideo) { gst_video_alignment_reset (&p->video_align); gst_video_info_align (&p->video_info, &p->video_align); } #endif } else if (g_str_has_prefix(gst_structure_get_name(structure), "audio/")) { p->has_video = FALSE; gst_audio_info_from_caps(&p->audio_info, caps); } else { g_assert_not_reached (); } p->add_metavideo = p->has_rawvideo && gst_buffer_pool_config_has_option (config, GST_BUFFER_POOL_OPTION_VIDEO_META); #ifdef HAVE_GSTREAMER_SHM_ALLOCATOR has_videoalign = p->has_rawvideo && gst_buffer_pool_config_has_option (config, GST_BUFFER_POOL_OPTION_VIDEO_ALIGNMENT); if (has_videoalign) { gst_buffer_pool_config_get_video_alignment (config, &p->video_align); gst_video_info_align (&p->video_info, &p->video_align); gst_buffer_pool_config_set_video_alignment (config, &p->video_align); GST_LOG_OBJECT (pool, "Set alignment: %u-%ux%u-%u", p->video_align.padding_left, p->video_align.padding_right, p->video_align.padding_top, p->video_align.padding_bottom); } #endif if (p->video_info.size != 0) size = p->video_info.size; gst_buffer_pool_config_set_params (config, caps, size, min_buffers, max_buffers); return GST_BUFFER_POOL_CLASS (gst_pipewire_pool_parent_class)->set_config (pool, config); } void gst_pipewire_pool_set_paused (GstPipeWirePool *pool, gboolean paused) { GST_DEBUG_OBJECT (pool, "pause: %u", paused); GST_OBJECT_LOCK (pool); pool->paused = paused; g_cond_signal (&pool->cond); GST_OBJECT_UNLOCK (pool); } static void flush_start (GstBufferPool * pool) { GstPipeWirePool *p = GST_PIPEWIRE_POOL (pool); GST_DEBUG_OBJECT (pool, "flush start"); GST_OBJECT_LOCK (pool); g_cond_signal (&p->cond); GST_OBJECT_UNLOCK (pool); } static void release_buffer (GstBufferPool * pool, GstBuffer *buffer) { GST_LOG_OBJECT (pool, "release buffer %p", buffer); GstPipeWirePoolData *data = gst_pipewire_pool_get_data(buffer); GST_OBJECT_LOCK (pool); if (!data->queued && data->b != NULL) { GstPipeWirePool *p = GST_PIPEWIRE_POOL (pool); g_autoptr (GstPipeWireStream) s = g_weak_ref_get (&p->stream); int res; pw_thread_loop_lock (s->core->loop); if ((res = pw_stream_return_buffer (s->pwstream, data->b)) < 0) { GST_ERROR_OBJECT (pool,"can't return buffer %p; gstbuffer : %p, %s",data->b, buffer, spa_strerror(res)); } else { data->queued = TRUE; GST_DEBUG_OBJECT (pool, "returned buffer %p; gstbuffer:%p", data->b, buffer); } pw_thread_loop_unlock (s->core->loop); } GST_OBJECT_UNLOCK (pool); } static gboolean do_start (GstBufferPool * pool) { g_signal_emit (pool, pool_signals[ACTIVATED], 0, NULL); return TRUE; } static void gst_pipewire_pool_finalize (GObject * object) { GstPipeWirePool *pool = GST_PIPEWIRE_POOL (object); GST_DEBUG_OBJECT (pool, "finalize"); g_weak_ref_set (&pool->stream, NULL); g_object_unref (pool->fd_allocator); g_object_unref (pool->dmabuf_allocator); #ifdef HAVE_GSTREAMER_SHM_ALLOCATOR if (pool->shm_allocator) g_object_unref (pool->shm_allocator); #endif G_OBJECT_CLASS (gst_pipewire_pool_parent_class)->finalize (object); } static void gst_pipewire_pool_class_init (GstPipeWirePoolClass * klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); GstBufferPoolClass *bufferpool_class = GST_BUFFER_POOL_CLASS (klass); gobject_class->finalize = gst_pipewire_pool_finalize; bufferpool_class->get_options = get_options; bufferpool_class->set_config = set_config; bufferpool_class->start = do_start; bufferpool_class->flush_start = flush_start; bufferpool_class->acquire_buffer = acquire_buffer; bufferpool_class->release_buffer = release_buffer; pool_signals[ACTIVATED] = g_signal_new ("activated", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_generic, G_TYPE_NONE, 0, G_TYPE_NONE); GST_DEBUG_CATEGORY_INIT (gst_pipewire_pool_debug_category, "pipewirepool", 0, "debug category for pipewirepool object"); pool_data_quark = g_quark_from_static_string ("GstPipeWirePoolDataQuark"); } static void gst_pipewire_pool_init (GstPipeWirePool * pool) { pool->fd_allocator = gst_fd_allocator_new (); pool->dmabuf_allocator = gst_dmabuf_allocator_new (); #ifdef HAVE_GSTREAMER_SHM_ALLOCATOR gst_shm_allocator_init_once(); #endif g_cond_init (&pool->cond); } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/gst/gstpipewirepool.h000066400000000000000000000042471511204443500254760ustar00rootroot00000000000000/* GStreamer */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef __GST_PIPEWIRE_POOL_H__ #define __GST_PIPEWIRE_POOL_H__ #include "gstpipewirestream.h" #include #include #include #include G_BEGIN_DECLS #define GST_TYPE_PIPEWIRE_POOL (gst_pipewire_pool_get_type()) G_DECLARE_FINAL_TYPE (GstPipeWirePool, gst_pipewire_pool, GST, PIPEWIRE_POOL, GstBufferPool) #define PIPEWIRE_POOL_MIN_BUFFERS 2u #define PIPEWIRE_POOL_MAX_BUFFERS 16u /* Only available in GStreamer 1.22+ */ #ifndef GST_VIDEO_FORMAT_INFO_IS_VALID_RAW #define GST_VIDEO_FORMAT_INFO_IS_VALID_RAW(info) \ (info != NULL && (info)->format > GST_VIDEO_FORMAT_ENCODED) #endif typedef struct _GstPipeWirePoolData GstPipeWirePoolData; struct _GstPipeWirePoolData { GstPipeWirePool *pool; void *owner; struct spa_meta_header *header; guint flags; struct pw_buffer *b; GstBuffer *buf; gboolean queued; struct spa_meta_region *crop; struct spa_meta_videotransform *videotransform; struct spa_meta_cursor *cursor; }; struct _GstPipeWirePool { GstBufferPool parent; GWeakRef stream; guint n_buffers; gboolean has_video; gboolean has_rawvideo; gboolean add_metavideo; GstAudioInfo audio_info; GstVideoInfo video_info; GstVideoAlignment video_align; GstAllocator *fd_allocator; GstAllocator *dmabuf_allocator; GstAllocator *shm_allocator; GCond cond; gboolean paused; gboolean allocate_memory; }; enum GstPipeWirePoolMode { USE_BUFFERPOOL_NO = 0, USE_BUFFERPOOL_AUTO, USE_BUFFERPOOL_YES }; GstPipeWirePool * gst_pipewire_pool_new (GstPipeWireStream *stream); void gst_pipewire_pool_wrap_buffer (GstPipeWirePool *pool, struct pw_buffer *buffer); void gst_pipewire_pool_remove_buffer (GstPipeWirePool *pool, struct pw_buffer *buffer); static inline gboolean gst_pipewire_pool_has_buffers (GstPipeWirePool *pool) { return pool->n_buffers > 0; } GstPipeWirePoolData *gst_pipewire_pool_get_data (GstBuffer *buffer); void gst_pipewire_pool_set_paused (GstPipeWirePool *pool, gboolean paused); G_END_DECLS #endif /* __GST_PIPEWIRE_POOL_H__ */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/gst/gstpipewiresink.c000066400000000000000000001260111511204443500254560ustar00rootroot00000000000000/* GStreamer */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ /** * SECTION:element-pipewiresink * * * Example launch line * |[ * gst-launch -v videotestsrc ! pipewiresink * ]| Sends a test video source to PipeWire * */ #define PW_ENABLE_DEPRECATED #include "config.h" #include "gstpipewiresink.h" #include #include #include #include #include #include #include #include #include #include "gstpipewireclock.h" #include "gstpipewireformat.h" GST_DEBUG_CATEGORY_STATIC (pipewire_sink_debug); #define GST_CAT_DEFAULT pipewire_sink_debug #define DEFAULT_PROP_MODE GST_PIPEWIRE_SINK_MODE_DEFAULT #define DEFAULT_PROP_SLAVE_METHOD GST_PIPEWIRE_SINK_SLAVE_METHOD_NONE #define DEFAULT_PROP_USE_BUFFERPOOL USE_BUFFERPOOL_AUTO #define MAX_ERROR_MS 1 #define RESYNC_TIMEOUT_MS 10 enum { PROP_0, PROP_PATH, PROP_TARGET_OBJECT, PROP_CLIENT_NAME, PROP_CLIENT_PROPERTIES, PROP_STREAM_PROPERTIES, PROP_MODE, PROP_FD, PROP_SLAVE_METHOD, PROP_USE_BUFFERPOOL, }; GType gst_pipewire_sink_mode_get_type (void) { static gsize mode_type = 0; static const GEnumValue mode[] = { {GST_PIPEWIRE_SINK_MODE_DEFAULT, "GST_PIPEWIRE_SINK_MODE_DEFAULT", "default"}, {GST_PIPEWIRE_SINK_MODE_RENDER, "GST_PIPEWIRE_SINK_MODE_RENDER", "render"}, {GST_PIPEWIRE_SINK_MODE_PROVIDE, "GST_PIPEWIRE_SINK_MODE_PROVIDE", "provide"}, {0, NULL, NULL}, }; if (g_once_init_enter (&mode_type)) { GType tmp = g_enum_register_static ("GstPipeWireSinkMode", mode); g_once_init_leave (&mode_type, tmp); } return (GType) mode_type; } GType gst_pipewire_sink_slave_method_get_type (void) { static gsize method_type = 0; static const GEnumValue method[] = { {GST_PIPEWIRE_SINK_SLAVE_METHOD_NONE, "GST_PIPEWIRE_SINK_SLAVE_METHOD_NONE", "none"}, {GST_PIPEWIRE_SINK_SLAVE_METHOD_RESAMPLE, "GST_PIPEWIRE_SINK_SLAVE_METHOD_RESAMPLE", "resample"}, {0, NULL, NULL}, }; if (g_once_init_enter (&method_type)) { GType tmp = g_enum_register_static ("GstPipeWireSinkSlaveMethod", method); g_once_init_leave (&method_type, tmp); } return (GType) method_type; } static GstStaticPadTemplate gst_pipewire_sink_template = GST_STATIC_PAD_TEMPLATE ("sink", GST_PAD_SINK, GST_PAD_ALWAYS, GST_STATIC_CAPS_ANY ); #define gst_pipewire_sink_parent_class parent_class G_DEFINE_TYPE (GstPipeWireSink, gst_pipewire_sink, GST_TYPE_BASE_SINK); static void gst_pipewire_sink_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec); static void gst_pipewire_sink_get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec); static GstStateChangeReturn gst_pipewire_sink_change_state (GstElement * element, GstStateChange transition); static gboolean gst_pipewire_sink_setcaps (GstBaseSink * bsink, GstCaps * caps); static GstCaps *gst_pipewire_sink_sink_fixate (GstBaseSink * bsink, GstCaps * caps); static GstFlowReturn gst_pipewire_sink_render (GstBaseSink * psink, GstBuffer * buffer); static gboolean gst_pipewire_sink_event (GstBaseSink *sink, GstEvent *event); static GstClock * gst_pipewire_sink_provide_clock (GstElement * elem) { GstPipeWireSink *pwsink = GST_PIPEWIRE_SINK (elem); GstClock *clock; GST_OBJECT_LOCK (pwsink); if (!GST_OBJECT_FLAG_IS_SET (pwsink, GST_ELEMENT_FLAG_PROVIDE_CLOCK)) goto clock_disabled; if (pwsink->stream->clock) clock = GST_CLOCK_CAST (gst_object_ref (pwsink->stream->clock)); else clock = NULL; GST_OBJECT_UNLOCK (pwsink); return clock; /* ERRORS */ clock_disabled: { GST_DEBUG_OBJECT (pwsink, "clock provide disabled"); GST_OBJECT_UNLOCK (pwsink); return NULL; } } static void gst_pipewire_sink_finalize (GObject * object) { GstPipeWireSink *pwsink = GST_PIPEWIRE_SINK (object); gst_clear_object (&pwsink->stream); G_OBJECT_CLASS (parent_class)->finalize (object); } static gboolean gst_pipewire_sink_propose_allocation (GstBaseSink * bsink, GstQuery * query) { GstPipeWireSink *pwsink = GST_PIPEWIRE_SINK (bsink); if (pwsink->use_bufferpool != USE_BUFFERPOOL_NO) gst_query_add_allocation_pool (query, GST_BUFFER_POOL_CAST (pwsink->stream->pool), 0, PIPEWIRE_POOL_MIN_BUFFERS, PIPEWIRE_POOL_MAX_BUFFERS); gst_query_add_allocation_meta (query, GST_VIDEO_META_API_TYPE, NULL); return TRUE; } static void gst_pipewire_sink_class_init (GstPipeWireSinkClass * klass) { GObjectClass *gobject_class; GstElementClass *gstelement_class; GstBaseSinkClass *gstbasesink_class; gobject_class = (GObjectClass *) klass; gstelement_class = (GstElementClass *) klass; gstbasesink_class = (GstBaseSinkClass *) klass; gobject_class->finalize = gst_pipewire_sink_finalize; gobject_class->set_property = gst_pipewire_sink_set_property; gobject_class->get_property = gst_pipewire_sink_get_property; g_object_class_install_property (gobject_class, PROP_PATH, g_param_spec_string ("path", "Path", "The sink path to connect to (NULL = default)", NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_DEPRECATED)); g_object_class_install_property (gobject_class, PROP_TARGET_OBJECT, g_param_spec_string ("target-object", "Target object", "The sink name/serial to connect to (NULL = default)", NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (gobject_class, PROP_CLIENT_NAME, g_param_spec_string ("client-name", "Client Name", "The client name to use (NULL = default)", NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (gobject_class, PROP_CLIENT_PROPERTIES, g_param_spec_boxed ("client-properties", "Client properties", "List of PipeWire client properties", GST_TYPE_STRUCTURE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (gobject_class, PROP_STREAM_PROPERTIES, g_param_spec_boxed ("stream-properties", "Stream properties", "List of PipeWire stream properties", GST_TYPE_STRUCTURE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (gobject_class, PROP_MODE, g_param_spec_enum ("mode", "Mode", "The mode to operate in", GST_TYPE_PIPEWIRE_SINK_MODE, DEFAULT_PROP_MODE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | GST_PARAM_MUTABLE_READY)); g_object_class_install_property (gobject_class, PROP_FD, g_param_spec_int ("fd", "Fd", "The fd to connect with", -1, G_MAXINT, -1, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (gobject_class, PROP_SLAVE_METHOD, g_param_spec_enum ("slave-method", "Slave Method", "Algorithm used to match the rate of the masterclock", GST_TYPE_PIPEWIRE_SINK_SLAVE_METHOD, DEFAULT_PROP_SLAVE_METHOD, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (gobject_class, PROP_USE_BUFFERPOOL, g_param_spec_boolean ("use-bufferpool", "Use bufferpool", "Use bufferpool (default: true for video, false for audio)", DEFAULT_PROP_USE_BUFFERPOOL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); gstelement_class->provide_clock = gst_pipewire_sink_provide_clock; gstelement_class->change_state = gst_pipewire_sink_change_state; gst_element_class_set_static_metadata (gstelement_class, "PipeWire sink", "Sink/Audio/Video", "Send audio/video to PipeWire", "Wim Taymans "); gst_element_class_add_pad_template (gstelement_class, gst_static_pad_template_get (&gst_pipewire_sink_template)); gstbasesink_class->set_caps = gst_pipewire_sink_setcaps; gstbasesink_class->fixate = gst_pipewire_sink_sink_fixate; gstbasesink_class->propose_allocation = gst_pipewire_sink_propose_allocation; gstbasesink_class->render = gst_pipewire_sink_render; gstbasesink_class->event = gst_pipewire_sink_event; GST_DEBUG_CATEGORY_INIT (pipewire_sink_debug, "pipewiresink", 0, "PipeWire Sink"); } static void gst_pipewire_sink_update_params (GstPipeWireSink *sink) { GstPipeWirePool *pool = sink->stream->pool; GstStructure *config; GstCaps *caps; guint size; guint min_buffers; guint max_buffers; const struct spa_pod *port_params[3]; struct spa_pod_builder b = { NULL }; uint8_t buffer[1024]; struct spa_pod_frame f; guint n_params = 0; config = gst_buffer_pool_get_config (GST_BUFFER_POOL (pool)); gst_buffer_pool_config_get_params (config, &caps, &size, &min_buffers, &max_buffers); /* We cannot dynamically grow the pool */ if (max_buffers == 0) { GST_WARNING_OBJECT (sink, "cannot support unlimited buffers in pool"); max_buffers = PIPEWIRE_POOL_MAX_BUFFERS; } spa_pod_builder_init (&b, buffer, sizeof (buffer)); spa_pod_builder_push_object (&b, &f, SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers); spa_pod_builder_add (&b, SPA_PARAM_BUFFERS_size, SPA_POD_CHOICE_RANGE_Int(size, size, INT32_MAX), 0); if (sink->is_rawvideo) { /* MUST have n_datas == n_planes */ spa_pod_builder_add (&b, SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(GST_VIDEO_INFO_N_PLANES (&pool->video_info)), 0); } else { /* Non-planar data, get a single block */ spa_pod_builder_add (&b, SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(1), 0); } spa_pod_builder_add (&b, SPA_PARAM_BUFFERS_stride, SPA_POD_CHOICE_RANGE_Int(0, 0, INT32_MAX), /* At this stage, we will request as many buffers as we _might_ need as * the default, since we can't grow the pool once this is set */ SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int( max_buffers, min_buffers, max_buffers), SPA_PARAM_BUFFERS_dataType, SPA_POD_CHOICE_FLAGS_Int(1<is_rawvideo) { port_params[n_params++] = spa_pod_builder_add_object (&b, SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta, SPA_PARAM_META_type, SPA_POD_Id(SPA_META_VideoCrop), SPA_PARAM_META_size, SPA_POD_Int(sizeof (struct spa_meta_region))); } pw_thread_loop_lock (sink->stream->core->loop); pw_stream_update_params (sink->stream->pwstream, port_params, n_params); pw_thread_loop_unlock (sink->stream->core->loop); gst_structure_free (config); } static void pool_activated (GstPipeWirePool *pool, GstPipeWireSink *sink) { GST_DEBUG_OBJECT (pool, "activated"); g_cond_signal (&sink->stream->pool->cond); } static void gst_pipewire_sink_init (GstPipeWireSink * sink) { sink->stream = gst_pipewire_stream_new (GST_ELEMENT (sink)); sink->mode = DEFAULT_PROP_MODE; sink->use_bufferpool = DEFAULT_PROP_USE_BUFFERPOOL; sink->is_rawvideo = false; sink->first_buffer = true; GST_OBJECT_FLAG_SET (sink, GST_ELEMENT_FLAG_PROVIDE_CLOCK); g_signal_connect (sink->stream->pool, "activated", G_CALLBACK (pool_activated), sink); } static GstCaps * gst_pipewire_sink_sink_fixate (GstBaseSink * bsink, GstCaps * caps) { GstStructure *structure; GstPipeWireSink *pwsink = GST_PIPEWIRE_SINK(bsink); caps = gst_caps_make_writable (caps); structure = gst_caps_get_structure (caps, 0); if (gst_structure_has_name (structure, "video/x-raw")) { pwsink->is_rawvideo = true; gst_structure_fixate_field_nearest_int (structure, "width", 320); gst_structure_fixate_field_nearest_int (structure, "height", 240); gst_structure_fixate_field_nearest_fraction (structure, "framerate", 30, 1); if (gst_structure_has_field (structure, "pixel-aspect-ratio")) gst_structure_fixate_field_nearest_fraction (structure, "pixel-aspect-ratio", 1, 1); else gst_structure_set (structure, "pixel-aspect-ratio", GST_TYPE_FRACTION, 1, 1, NULL); if (gst_structure_has_field (structure, "colorimetry")) gst_structure_fixate_field_string (structure, "colorimetry", "bt601"); if (gst_structure_has_field (structure, "chroma-site")) gst_structure_fixate_field_string (structure, "chroma-site", "mpeg2"); if (gst_structure_has_field (structure, "interlace-mode")) gst_structure_fixate_field_string (structure, "interlace-mode", "progressive"); else gst_structure_set (structure, "interlace-mode", G_TYPE_STRING, "progressive", NULL); } else if (gst_structure_has_name (structure, "audio/x-raw")) { gst_structure_fixate_field_string (structure, "format", "S16LE"); gst_structure_fixate_field_nearest_int (structure, "channels", 2); gst_structure_fixate_field_nearest_int (structure, "rate", 44100); } else if (gst_structure_has_name (structure, "audio/mpeg")) { gst_structure_fixate_field_string (structure, "format", "Encoded"); gst_structure_fixate_field_nearest_int (structure, "channels", 2); gst_structure_fixate_field_nearest_int (structure, "rate", 44100); } else if (gst_structure_has_name (structure, "audio/x-flac")) { gst_structure_fixate_field_string (structure, "format", "Encoded"); gst_structure_fixate_field_nearest_int (structure, "channels", 2); gst_structure_fixate_field_nearest_int (structure, "rate", 44100); } caps = GST_BASE_SINK_CLASS (parent_class)->fixate (bsink, caps); return caps; } static void gst_pipewire_sink_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec) { GstPipeWireSink *pwsink = GST_PIPEWIRE_SINK (object); switch (prop_id) { case PROP_PATH: g_free (pwsink->stream->path); pwsink->stream->path = g_value_dup_string (value); break; case PROP_TARGET_OBJECT: g_free (pwsink->stream->target_object); pwsink->stream->target_object = g_value_dup_string (value); break; case PROP_CLIENT_NAME: g_free (pwsink->stream->client_name); pwsink->stream->client_name = g_value_dup_string (value); break; case PROP_CLIENT_PROPERTIES: if (pwsink->stream->client_properties) gst_structure_free (pwsink->stream->client_properties); pwsink->stream->client_properties = gst_structure_copy (gst_value_get_structure (value)); break; case PROP_STREAM_PROPERTIES: if (pwsink->stream->stream_properties) gst_structure_free (pwsink->stream->stream_properties); pwsink->stream->stream_properties = gst_structure_copy (gst_value_get_structure (value)); break; case PROP_MODE: pwsink->mode = g_value_get_enum (value); break; case PROP_FD: pwsink->stream->fd = g_value_get_int (value); break; case PROP_SLAVE_METHOD: pwsink->slave_method = g_value_get_enum (value); break; case PROP_USE_BUFFERPOOL: if(g_value_get_boolean (value)) pwsink->use_bufferpool = USE_BUFFERPOOL_YES; else pwsink->use_bufferpool = USE_BUFFERPOOL_NO; break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void gst_pipewire_sink_get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec) { GstPipeWireSink *pwsink = GST_PIPEWIRE_SINK (object); switch (prop_id) { case PROP_PATH: g_value_set_string (value, pwsink->stream->path); break; case PROP_TARGET_OBJECT: g_value_set_string (value, pwsink->stream->target_object); break; case PROP_CLIENT_NAME: g_value_set_string (value, pwsink->stream->client_name); break; case PROP_CLIENT_PROPERTIES: gst_value_set_structure (value, pwsink->stream->client_properties); break; case PROP_STREAM_PROPERTIES: gst_value_set_structure (value, pwsink->stream->stream_properties); break; case PROP_MODE: g_value_set_enum (value, pwsink->mode); break; case PROP_FD: g_value_set_int (value, pwsink->stream->fd); break; case PROP_SLAVE_METHOD: g_value_set_enum (value, pwsink->slave_method); break; case PROP_USE_BUFFERPOOL: g_value_set_boolean (value, !!pwsink->use_bufferpool); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void rate_match_resample(GstPipeWireSink *pwsink) { GstPipeWireStream *stream = pwsink->stream; double err, corr; struct pw_time ts; guint64 queued, now, elapsed, target; if (!pwsink->rate_match) return; pw_stream_get_time_n(stream->pwstream, &ts, sizeof(ts)); now = pw_stream_get_nsec(stream->pwstream); if (ts.now != 0) elapsed = gst_util_uint64_scale_int (now - ts.now, ts.rate.denom, GST_SECOND * ts.rate.num); else elapsed = 0; queued = ts.queued - ts.size; target = elapsed; err = ((gint64)queued - ((gint64)target)); corr = spa_dll_update(&stream->dll, SPA_CLAMPD(err, -128.0, 128.0)); stream->err_wdw = (double)ts.rate.denom/ts.size; double avg = (stream->err_avg * stream->err_wdw + (err - stream->err_avg)) / (stream->err_wdw + 1.0); stream->err_var = (stream->err_var * stream->err_wdw + (err - stream->err_avg) * (err - avg)) / (stream->err_wdw + 1.0); stream->err_avg = avg; if (stream->last_ts == 0 || stream->last_ts + SPA_NSEC_PER_SEC < now) { double bw; stream->last_ts = now; if (stream->err_var == 0.0) bw = 0.0; else bw = fabs(stream->err_avg) / sqrt(fabs(stream->err_var)); spa_dll_set_bw(&stream->dll, SPA_CLAMPD(bw, 0.001, SPA_DLL_BW_MAX), ts.size, ts.rate.denom); GST_INFO_OBJECT (pwsink, "q:%"PRIi64"/%"PRIi64" e:%"PRIu64" err:%+03f corr:%f %f %f %f", ts.queued, ts.size, elapsed, err, corr, stream->err_avg, stream->err_var, stream->dll.bw); } pw_stream_set_rate (stream->pwstream, corr); } static void on_add_buffer (void *_data, struct pw_buffer *b) { GstPipeWireSink *pwsink = _data; GST_DEBUG_OBJECT (pwsink, "add pw_buffer %p", b); gst_pipewire_pool_wrap_buffer (pwsink->stream->pool, b); } static void on_remove_buffer (void *_data, struct pw_buffer *b) { GstPipeWireSink *pwsink = _data; GST_DEBUG_OBJECT (pwsink, "remove pw_buffer %p", b); gst_pipewire_pool_remove_buffer (pwsink->stream->pool, b); if (!gst_pipewire_pool_has_buffers (pwsink->stream->pool) && !GST_BUFFER_POOL_IS_FLUSHING (GST_BUFFER_POOL_CAST (pwsink->stream->pool))) { if (pwsink->mode != GST_PIPEWIRE_SINK_MODE_PROVIDE) { GST_ELEMENT_ERROR (pwsink, RESOURCE, NOT_FOUND, ("all buffers have been removed"), ("PipeWire link to remote node was destroyed")); } } } static void do_send_buffer (GstPipeWireSink *pwsink, GstBuffer *buffer) { GstPipeWirePoolData *data; GstPipeWireStream *stream = pwsink->stream; gboolean res; guint i; struct spa_buffer *b; data = gst_pipewire_pool_get_data(buffer); b = data->b->buffer; if (data->header) { data->header->seq = GST_BUFFER_OFFSET (buffer); data->header->pts = GST_BUFFER_PTS (buffer); if (GST_BUFFER_DTS(buffer) != GST_CLOCK_TIME_NONE) data->header->dts_offset = GST_BUFFER_DTS (buffer) - GST_BUFFER_PTS (buffer); else data->header->dts_offset = 0; } if (data->crop) { GstVideoCropMeta *meta = gst_buffer_get_video_crop_meta (buffer); if (meta) { data->crop->region.position.x = meta->x; data->crop->region.position.y = meta->y; data->crop->region.size.width = meta->width; data->crop->region.size.height = meta->width; } } data->b->size = 0; spa_assert(b->n_datas == gst_buffer_n_memory(buffer)); for (i = 0; i < b->n_datas; i++) { struct spa_data *d = &b->datas[i]; GstMemory *mem = gst_buffer_peek_memory (buffer, i); d->chunk->offset = mem->offset; d->chunk->size = mem->size; d->chunk->stride = stream->pool->video_info.stride[i]; data->b->size += mem->size / 4; } GstVideoMeta *meta = gst_buffer_get_video_meta (buffer); if (meta) { if (meta->n_planes == b->n_datas) { uint32_t n_planes = GST_VIDEO_INFO_N_PLANES (&data->pool->video_info); gsize video_size = 0; for (i = 0; i < n_planes; i++) { struct spa_data *d = &b->datas[i]; d->chunk->stride = meta->stride[i]; d->chunk->offset = meta->offset[i] - video_size; video_size += d->chunk->size; } } else { GST_ERROR_OBJECT (pwsink, "plane num not matching, meta:%u buffer:%u", meta->n_planes, b->n_datas); } } if ((res = pw_stream_queue_buffer (stream->pwstream, data->b)) < 0) { GST_WARNING_OBJECT (pwsink, "can't send buffer %s", spa_strerror(res)); } else { data->queued = TRUE; GST_LOG_OBJECT(pwsink, "queued pwbuffer: %p size: %"PRIu64"; gstbuffer %p", data->b, data->b->size, buffer); if (pwsink->first_buffer) { pwsink->first_buffer = false; pwsink->first_buffer_pts = GST_BUFFER_PTS(buffer); } stream->position = gst_util_uint64_scale_int(GST_BUFFER_PTS(buffer) - pwsink->first_buffer_pts, pwsink->rate, 1 * GST_SECOND); // have the buffer duration value minimum as 1, in case of video where rate is 0 (not applicable) stream->buf_duration = SPA_MAX((uint64_t)1, gst_util_uint64_scale_int(GST_BUFFER_DURATION(buffer), pwsink->rate, 1 * GST_SECOND)); } switch (pwsink->slave_method) { case GST_PIPEWIRE_SINK_SLAVE_METHOD_NONE: break; case GST_PIPEWIRE_SINK_SLAVE_METHOD_RESAMPLE: rate_match_resample(pwsink); break; } } static void update_time (GstPipeWireSink *pwsink) { struct spa_io_position *p = pwsink->stream->io_position; double err = 0.0, corr = 1.0; guint64 now; double max_err = pwsink->rate * MAX_ERROR_MS/1000.0; double resync_timeout = pwsink->rate * RESYNC_TIMEOUT_MS/1000.0; if (pwsink->first_buffer) { // use the target duration before the first buffer pwsink->stream->buf_duration = p->clock.target_duration; spa_dll_set_bw(&pwsink->stream->dll, SPA_DLL_BW_MIN, pwsink->stream->buf_duration, pwsink->rate); } now = pw_stream_get_nsec(pwsink->stream->pwstream); err = (double)gst_util_uint64_scale(now, pwsink->rate, 1 * GST_SECOND) - (double)gst_util_uint64_scale(p->clock.next_nsec, pwsink->rate, 1 * GST_SECOND); GST_LOG_OBJECT(pwsink, "err is %f max err is %f now %"PRIu64" next is %"PRIu64"", err, max_err, now, p->clock.next_nsec); if (fabs(err) > max_err) { if (fabs(err) > resync_timeout) { GST_WARNING_OBJECT(pwsink, "err %f exceeds resync timeout, resetting", err); spa_dll_set_bw(&pwsink->stream->dll, SPA_DLL_BW_MIN, pwsink->stream->buf_duration, pwsink->rate); err = 0.0; } else { err = SPA_CLAMPD(err, -max_err, max_err); } } corr = spa_dll_update(&pwsink->stream->dll, err); p->clock.nsec = now; p->clock.position = pwsink->stream->position; p->clock.duration = pwsink->stream->buf_duration; /* we don't have a way to estimate the target (next cycle) buffer duration * so use the current buffer duration */ p->clock.target_duration = pwsink->stream->buf_duration; p->clock.rate = SPA_FRACTION(1, pwsink->rate); // current time plus duration scaled with correlation p->clock.next_nsec = now + (uint64_t)(p->clock.duration / corr * GST_SECOND / pwsink->rate); p->clock.rate_diff = corr; GST_DEBUG_OBJECT(pwsink, "now %"PRIu64", position %"PRIu64", duration %"PRIu64", rate :%d," "next : %"PRIu64", delay is %"PRIi64", rate_diff is %f", p->clock.nsec, p->clock.position, p->clock.duration, pwsink->rate, p->clock.next_nsec, p->clock.delay,p->clock.rate_diff); } static void on_process (void *data) { GstPipeWireSink *pwsink = data; GST_LOG_OBJECT (pwsink, "signal"); g_cond_signal (&pwsink->stream->pool->cond); } static int invoke_trigger_process(struct spa_loop *loop, bool async, uint32_t seq, const void *data, size_t size, void *user_data) { GstPipeWireSink *pwsink = user_data; /* Note: We cannot use the rate for computation of other clock params * in case of video because the rate for video is set as 0 in the _setcaps. * So skip update time for video (i.e. when rate is 0). The video buffers * get timestamp from the SPA_META_Header anyway */ if (pwsink->rate) update_time(pwsink); return pw_stream_trigger_process(pwsink->stream->pwstream); } static void on_state_changed (void *data, enum pw_stream_state old, enum pw_stream_state state, const char *error) { GstPipeWireSink *pwsink = data; GST_DEBUG_OBJECT (pwsink, "got stream state \"%s\" (%d)", pw_stream_state_as_string(state), state); switch (state) { case PW_STREAM_STATE_UNCONNECTED: case PW_STREAM_STATE_CONNECTING: case PW_STREAM_STATE_PAUSED: break; case PW_STREAM_STATE_STREAMING: if (pw_stream_is_driving (pwsink->stream->pwstream)) pw_loop_invoke(pw_stream_get_data_loop(pwsink->stream->pwstream), invoke_trigger_process, 1, NULL, 0 , false, pwsink); break; case PW_STREAM_STATE_ERROR: /* make the error permanent, if it is not already; pw_stream_set_error() will recursively call us again */ if (pw_stream_get_state (pwsink->stream->pwstream, NULL) != PW_STREAM_STATE_ERROR) pw_stream_set_error (pwsink->stream->pwstream, -EPIPE, "%s", error); else GST_ELEMENT_ERROR (pwsink, RESOURCE, FAILED, ("stream error: %s", error), (NULL)); break; } pw_thread_loop_signal (pwsink->stream->core->loop, FALSE); } static void on_param_changed (void *data, uint32_t id, const struct spa_pod *param) { GstPipeWireSink *pwsink = data; GstPipeWirePool *pool = pwsink->stream->pool; if (param == NULL || id != SPA_PARAM_Format) return; GST_OBJECT_LOCK (pool); while (!gst_buffer_pool_is_active (GST_BUFFER_POOL (pool))) { GST_DEBUG_OBJECT (pool, "waiting for pool to become active"); g_cond_wait(&pool->cond, GST_OBJECT_GET_LOCK (pool)); } GST_OBJECT_UNLOCK (pool); gst_pipewire_sink_update_params (pwsink); } static gboolean gst_pipewire_sink_setcaps (GstBaseSink * bsink, GstCaps * caps) { GstPipeWireSink *pwsink; g_autoptr(GPtrArray) possible = NULL; enum pw_stream_state state; const char *error = NULL; GstStructure *config, *s; guint size; guint min_buffers; guint max_buffers; struct timespec abstime; gint rate; pwsink = GST_PIPEWIRE_SINK (bsink); s = gst_caps_get_structure (caps, 0); if (gst_structure_has_name (s, "audio/x-raw")) { gst_structure_get_int (s, "rate", &rate); pwsink->rate = rate; pwsink->rate_match = true; /* Don't provide bufferpool for audio if not requested by the application/user */ if (pwsink->use_bufferpool != USE_BUFFERPOOL_YES) pwsink->use_bufferpool = USE_BUFFERPOOL_NO; } else { GstVideoInfo video_info; pwsink->rate = rate = 0; pwsink->rate_match = false; gst_video_info_from_caps (&video_info, caps); if (GST_VIDEO_FORMAT_INFO_IS_VALID_RAW (video_info.finfo) #ifdef HAVE_GSTREAMER_DMA_DRM && GST_VIDEO_FORMAT_INFO_FORMAT (video_info.finfo) != GST_VIDEO_FORMAT_DMA_DRM #endif ) pwsink->is_rawvideo = TRUE; else pwsink->is_rawvideo = FALSE; } spa_dll_set_bw(&pwsink->stream->dll, SPA_DLL_BW_MIN, 4096, rate); possible = gst_caps_to_format_all (caps); pw_thread_loop_lock (pwsink->stream->core->loop); state = pw_stream_get_state (pwsink->stream->pwstream, &error); if (state == PW_STREAM_STATE_ERROR) goto start_error; if (state == PW_STREAM_STATE_UNCONNECTED) { enum pw_stream_flags flags; uint32_t target_id; struct spa_dict_item items[3]; uint32_t n_items = 0; char buf[64]; flags = PW_STREAM_FLAG_ASYNC; flags |= PW_STREAM_FLAG_EARLY_PROCESS; if (pwsink->mode != GST_PIPEWIRE_SINK_MODE_PROVIDE) flags |= PW_STREAM_FLAG_AUTOCONNECT; else flags |= PW_STREAM_FLAG_DRIVER; #ifdef HAVE_GSTREAMER_SHM_ALLOCATOR flags |= PW_STREAM_FLAG_ALLOC_BUFFERS; pwsink->stream->pool->allocate_memory = true; #endif target_id = pwsink->stream->path ? (uint32_t)atoi(pwsink->stream->path) : PW_ID_ANY; if (pwsink->stream->target_object) { uint64_t serial; items[n_items++] = SPA_DICT_ITEM_INIT(PW_KEY_TARGET_OBJECT, pwsink->stream->target_object); /* If target.object is a name, set it also to node.target */ if (!spa_atou64(pwsink->stream->target_object, &serial, 0)) { target_id = PW_ID_ANY; /* XXX deprecated but the portal and some example apps only * provide the object id */ items[n_items++] = SPA_DICT_ITEM_INIT(PW_KEY_NODE_TARGET, pwsink->stream->target_object); } } if (rate != 0) { snprintf(buf, sizeof(buf), "1/%u", rate); items[n_items++] = SPA_DICT_ITEM_INIT(PW_KEY_NODE_RATE, buf); } if (n_items > 0) pw_stream_update_properties (pwsink->stream->pwstream, &SPA_DICT_INIT(items, n_items)); pw_stream_connect (pwsink->stream->pwstream, PW_DIRECTION_OUTPUT, target_id, flags, (const struct spa_pod **) possible->pdata, possible->len); pw_thread_loop_get_time (pwsink->stream->core->loop, &abstime, GST_PIPEWIRE_DEFAULT_TIMEOUT * SPA_NSEC_PER_SEC); while (TRUE) { state = pw_stream_get_state (pwsink->stream->pwstream, &error); if (state >= PW_STREAM_STATE_PAUSED) break; if (state == PW_STREAM_STATE_ERROR) goto start_error; if (pw_thread_loop_timed_wait_full (pwsink->stream->core->loop, &abstime) < 0) { error = "timeout"; goto start_error; } } } gst_pipewire_clock_reset (GST_PIPEWIRE_CLOCK (pwsink->stream->clock), 0); config = gst_buffer_pool_get_config (GST_BUFFER_POOL_CAST (pwsink->stream->pool)); gst_buffer_pool_config_get_params (config, NULL, &size, &min_buffers, &max_buffers); gst_buffer_pool_config_set_params (config, caps, size, min_buffers, max_buffers); if (pwsink->is_rawvideo) { gst_buffer_pool_config_add_option (config, GST_BUFFER_POOL_OPTION_VIDEO_META); #ifdef HAVE_GSTREAMER_SHM_ALLOCATOR gst_buffer_pool_config_add_option (config, GST_BUFFER_POOL_OPTION_VIDEO_ALIGNMENT); #endif } gst_buffer_pool_set_config (GST_BUFFER_POOL_CAST (pwsink->stream->pool), config); pw_thread_loop_unlock (pwsink->stream->core->loop); pwsink->negotiated = TRUE; return TRUE; start_error: { GST_ERROR_OBJECT (pwsink, "could not start stream: %s", error); pw_thread_loop_unlock (pwsink->stream->core->loop); return FALSE; } } static GstFlowReturn gst_pipewire_sink_render (GstBaseSink * bsink, GstBuffer * buffer) { GstPipeWireSink *pwsink; GstFlowReturn res = GST_FLOW_OK; const char *error = NULL; pwsink = GST_PIPEWIRE_SINK (bsink); if (!pwsink->negotiated) goto not_negotiated; if (buffer->pool != GST_BUFFER_POOL_CAST (pwsink->stream->pool) && !gst_buffer_pool_is_active (GST_BUFFER_POOL_CAST (pwsink->stream->pool))) { GstStructure *config; GstCaps *caps; guint size, min_buffers, max_buffers; config = gst_buffer_pool_get_config (GST_BUFFER_POOL_CAST (pwsink->stream->pool)); gst_buffer_pool_config_get_params (config, &caps, &size, &min_buffers, &max_buffers); if (size == 0) { gsize maxsize; gst_buffer_get_sizes (buffer, NULL, &maxsize); size = maxsize; } gst_buffer_pool_config_set_params (config, caps, size, min_buffers, max_buffers); gst_buffer_pool_set_config (GST_BUFFER_POOL_CAST (pwsink->stream->pool), config); gst_buffer_pool_set_active (GST_BUFFER_POOL_CAST (pwsink->stream->pool), TRUE); } pw_thread_loop_lock (pwsink->stream->core->loop); if (pw_stream_get_state (pwsink->stream->pwstream, &error) != PW_STREAM_STATE_STREAMING) goto done_unlock; if (buffer->pool != GST_BUFFER_POOL_CAST (pwsink->stream->pool)) { gsize offset = 0; gsize buf_size = gst_buffer_get_size (buffer); GST_TRACE_OBJECT(pwsink, "Buffer is not from pipewirepool, copying into our pool"); /* For some streams, the buffer size is changed and may exceed the acquired * buffer size which is acquired from the pool of pipewiresink. Need split * the buffer and send them in turn for this case */ while (buf_size) { GstBuffer *b = NULL; GstMapInfo info = { 0, }; GstBufferPoolAcquireParams params = { 0, }; pw_thread_loop_unlock (pwsink->stream->core->loop); params.flags = GST_BUFFER_POOL_ACQUIRE_FLAG_LAST; res = gst_buffer_pool_acquire_buffer (GST_BUFFER_POOL_CAST (pwsink->stream->pool), &b, ¶ms); if (res == GST_FLOW_CUSTOM_ERROR_1) { res = gst_base_sink_wait_preroll (bsink); if (res != GST_FLOW_OK) goto done; continue; } if (res != GST_FLOW_OK) goto done; if (pwsink->is_rawvideo) { GstVideoFrame src, dst; gboolean copied = FALSE; buf_size = 0; // to break from the loop /* splitting of buffers in the case of video might break the frame layout * and that seems to be causing issues while retrieving the buffers on the receiver * side. Hence use the video_frame_map to copy the buffer of bigger size into the * pipewirepool's buffer */ if (!gst_video_frame_map (&dst, &pwsink->stream->pool->video_info, b, GST_MAP_WRITE)) { GST_ERROR_OBJECT(pwsink, "Failed to map dest buffer"); return GST_FLOW_ERROR; } if (!gst_video_frame_map (&src, &pwsink->stream->pool->video_info, buffer, GST_MAP_READ)) { gst_video_frame_unmap (&dst); GST_ERROR_OBJECT(pwsink, "Failed to map src buffer"); return GST_FLOW_ERROR; } copied = gst_video_frame_copy (&dst, &src); gst_video_frame_unmap (&src); gst_video_frame_unmap (&dst); if (!copied) { GST_ERROR_OBJECT(pwsink, "Failed to copy the frame"); return GST_FLOW_ERROR; } } else { gst_buffer_map (b, &info, GST_MAP_WRITE); gsize extract_size = (buf_size <= info.maxsize) ? buf_size: info.maxsize; gst_buffer_extract (buffer, offset, info.data, info.maxsize); gst_buffer_unmap (b, &info); gst_buffer_resize (b, 0, extract_size); gst_buffer_copy_into(b, buffer, GST_BUFFER_COPY_METADATA, 0, -1); buf_size -= extract_size; offset += extract_size; } pw_thread_loop_lock (pwsink->stream->core->loop); if (pw_stream_get_state (pwsink->stream->pwstream, &error) != PW_STREAM_STATE_STREAMING) { gst_buffer_unref (b); goto done_unlock; } do_send_buffer (pwsink, b); gst_buffer_unref (b); if (pw_stream_is_driving (pwsink->stream->pwstream)) pw_loop_invoke(pw_stream_get_data_loop(pwsink->stream->pwstream), invoke_trigger_process, 1, NULL, 0 , false, pwsink); } } else { GST_TRACE_OBJECT(pwsink, "Buffer is from pipewirepool"); do_send_buffer (pwsink, buffer); if (pw_stream_is_driving (pwsink->stream->pwstream)) pw_loop_invoke(pw_stream_get_data_loop(pwsink->stream->pwstream), invoke_trigger_process, 1, NULL, 0 , false, pwsink); } done_unlock: pw_thread_loop_unlock (pwsink->stream->core->loop); done: return res; not_negotiated: { return GST_FLOW_NOT_NEGOTIATED; } } static void on_io_changed(void *data, uint32_t id, void *area, uint32_t size) { GstPipeWireSink *pwsink = data; switch (id) { case SPA_IO_Position: pwsink->stream->io_position = area; break; } } static const struct pw_stream_events stream_events = { PW_VERSION_STREAM_EVENTS, .state_changed = on_state_changed, .param_changed = on_param_changed, .add_buffer = on_add_buffer, .remove_buffer = on_remove_buffer, .process = on_process, .io_changed = on_io_changed, }; static GstStateChangeReturn gst_pipewire_sink_change_state (GstElement * element, GstStateChange transition) { GstStateChangeReturn ret; GstPipeWireSink *this = GST_PIPEWIRE_SINK_CAST (element); switch (transition) { case GST_STATE_CHANGE_NULL_TO_READY: if (!gst_pipewire_stream_open (this->stream, &stream_events)) goto open_failed; break; case GST_STATE_CHANGE_READY_TO_PAUSED: /* If we are a driver, we shouldn't try to also provide the clock, as we * _are_ the clock for the graph. For that case, we rely on the pipeline * clock to drive the pipeline (and thus the graph). */ if (this->mode == GST_PIPEWIRE_SINK_MODE_PROVIDE) GST_OBJECT_FLAG_UNSET (this, GST_ELEMENT_FLAG_PROVIDE_CLOCK); else GST_OBJECT_FLAG_SET (this, GST_ELEMENT_FLAG_PROVIDE_CLOCK); /* the initial stream state is active, which is needed for linking and * negotiation to happen and the bufferpool to be set up. We don't know * if we'll go to plaing, so we deactivate the stream until that * transition happens. This is janky, but because of how bins propagate * state changes one transition at a time, there may not be a better way * to do this. PAUSED -> READY -> PAUSED transitions, this is a noop */ pw_thread_loop_lock (this->stream->core->loop); pw_stream_set_active(this->stream->pwstream, false); pw_thread_loop_unlock (this->stream->core->loop); gst_pipewire_pool_set_paused(this->stream->pool, TRUE); break; case GST_STATE_CHANGE_PLAYING_TO_PAUSED: /* stop play ASAP by corking */ gst_pipewire_pool_set_paused(this->stream->pool, TRUE); pw_thread_loop_lock (this->stream->core->loop); pw_stream_set_active(this->stream->pwstream, false); pw_thread_loop_unlock (this->stream->core->loop); break; default: break; } ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); switch (transition) { case GST_STATE_CHANGE_PAUSED_TO_PLAYING: /* For some cases, the param_changed event is earlier than the state switch * from paused state to playing state which will wait until buffer pool is ready. * Guarantee to finish preoll if needed to active buffer pool before uncorking and * starting play */ gst_pipewire_pool_set_paused(this->stream->pool, FALSE); pw_thread_loop_lock (this->stream->core->loop); pw_stream_set_active(this->stream->pwstream, true); pw_thread_loop_unlock (this->stream->core->loop); break; case GST_STATE_CHANGE_PLAYING_TO_PAUSED: break; case GST_STATE_CHANGE_PAUSED_TO_READY: gst_buffer_pool_set_active(GST_BUFFER_POOL_CAST(this->stream->pool), FALSE); this->negotiated = FALSE; break; case GST_STATE_CHANGE_READY_TO_NULL: gst_pipewire_stream_close (this->stream); break; default: break; } return ret; /* ERRORS */ open_failed: { return GST_STATE_CHANGE_FAILURE; } } static gboolean gst_pipewire_sink_event (GstBaseSink *sink, GstEvent *event) { GstPipeWireSink *pw_sink = GST_PIPEWIRE_SINK(sink); GstState current_state = GST_ELEMENT(sink)->current_state; switch (GST_EVENT_TYPE (event)) { case GST_EVENT_FLUSH_START: { GST_DEBUG_OBJECT (pw_sink, "flush-start"); pw_thread_loop_lock (pw_sink->stream->core->loop); /* The stream would be already inactive if the sink is not PLAYING */ if (current_state == GST_STATE_PLAYING) pw_stream_set_active(pw_sink->stream->pwstream, false); gst_buffer_pool_set_flushing(GST_BUFFER_POOL_CAST(pw_sink->stream->pool), TRUE); pw_stream_flush(pw_sink->stream->pwstream, false); pw_thread_loop_unlock (pw_sink->stream->core->loop); break; } case GST_EVENT_FLUSH_STOP: { GST_DEBUG_OBJECT (pw_sink, "flush-stop"); pw_thread_loop_lock (pw_sink->stream->core->loop); /* The stream needs to remain inactive if the sink is not PLAYING */ if (current_state == GST_STATE_PLAYING) pw_stream_set_active(pw_sink->stream->pwstream, true); gst_buffer_pool_set_flushing(GST_BUFFER_POOL_CAST(pw_sink->stream->pool), FALSE); pw_thread_loop_unlock (pw_sink->stream->core->loop); break; } default: break; } return GST_BASE_SINK_CLASS (parent_class)->event (sink, event); } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/gst/gstpipewiresink.h000066400000000000000000000037401511204443500254660ustar00rootroot00000000000000/* GStreamer */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef __GST_PIPEWIRE_SINK_H__ #define __GST_PIPEWIRE_SINK_H__ #include "gstpipewirestream.h" #include #include #include #include #include G_BEGIN_DECLS #define GST_TYPE_PIPEWIRE_SINK (gst_pipewire_sink_get_type()) #define GST_PIPEWIRE_SINK_CAST(obj) ((GstPipeWireSink *) (obj)) G_DECLARE_FINAL_TYPE (GstPipeWireSink, gst_pipewire_sink, GST, PIPEWIRE_SINK, GstBaseSink) /** * GstPipeWireSinkMode: * @GST_PIPEWIRE_SINK_MODE_DEFAULT: the default mode as configured in the server * @GST_PIPEWIRE_SINK_MODE_RENDER: try to render the media * @GST_PIPEWIRE_SINK_MODE_PROVIDE: provide the media * * Different modes of operation. */ typedef enum { GST_PIPEWIRE_SINK_MODE_DEFAULT, GST_PIPEWIRE_SINK_MODE_RENDER, GST_PIPEWIRE_SINK_MODE_PROVIDE, } GstPipeWireSinkMode; #define GST_TYPE_PIPEWIRE_SINK_MODE (gst_pipewire_sink_mode_get_type ()) /** * GstPipeWireSinkSlaveMethod: * @GST_PIPEWIRE_SINK_SLAVE_METHOD_NONE: no clock and timestamp slaving * @GST_PIPEWIRE_SINK_SLAVE_METHOD_RESAMPLE: resample audio * * Different clock slaving methods */ typedef enum { GST_PIPEWIRE_SINK_SLAVE_METHOD_NONE, GST_PIPEWIRE_SINK_SLAVE_METHOD_RESAMPLE, } GstPipeWireSinkSlaveMethod; #define GST_TYPE_PIPEWIRE_SINK_SLAVE_METHOD (gst_pipewire_sink_slave_method_get_type ()) /** * GstPipeWireSink: * * Opaque data structure. */ struct _GstPipeWireSink { GstBaseSink element; /*< private >*/ GstPipeWireStream *stream; gboolean use_bufferpool; /* video state */ gboolean negotiated; gboolean rate_match; gint rate; gboolean is_rawvideo; gboolean first_buffer; GstClockTime first_buffer_pts; GstPipeWireSinkMode mode; GstPipeWireSinkSlaveMethod slave_method; }; GType gst_pipewire_sink_mode_get_type (void); G_END_DECLS #endif /* __GST_PIPEWIRE_SINK_H__ */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/gst/gstpipewiresrc.c000066400000000000000000001663311511204443500253120ustar00rootroot00000000000000/* GStreamer */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ /** * SECTION:element-pipewiresrc * * * Example launch line * |[ * gst-launch -v pipewiresrc ! videoconvert ! ximagesink * ]| Shows pipewire output in an X window. * */ #define PW_ENABLE_DEPRECATED #include "gstpipewiresrc.h" #include "gstpipewireformat.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "gstpipewireclock.h" static GQuark process_mem_data_quark; GST_DEBUG_CATEGORY_STATIC (pipewire_src_debug); #define GST_CAT_DEFAULT pipewire_src_debug #define DEFAULT_ALWAYS_COPY false #define DEFAULT_MIN_BUFFERS 1 #define DEFAULT_MAX_BUFFERS INT32_MAX #define DEFAULT_RESEND_LAST false #define DEFAULT_KEEPALIVE_TIME 0 #define DEFAULT_AUTOCONNECT true #define DEFAULT_USE_BUFFERPOOL USE_BUFFERPOOL_AUTO #define DEFAULT_ON_DISCONNECT GST_PIPEWIRE_SRC_ON_DISCONNECT_NONE #define DEFAULT_PROVIDE_CLOCK TRUE enum { PROP_0, PROP_PATH, PROP_TARGET_OBJECT, PROP_CLIENT_NAME, PROP_CLIENT_PROPERTIES, PROP_STREAM_PROPERTIES, PROP_ALWAYS_COPY, PROP_MIN_BUFFERS, PROP_MAX_BUFFERS, PROP_FD, PROP_RESEND_LAST, PROP_KEEPALIVE_TIME, PROP_AUTOCONNECT, PROP_USE_BUFFERPOOL, PROP_ON_DISCONNECT, PROP_PROVIDE_CLOCK, }; GType gst_pipewire_src_on_disconnect_get_type (void) { static gsize on_disconnect_type = 0; static const GEnumValue on_disconnect[] = { {GST_PIPEWIRE_SRC_ON_DISCONNECT_NONE, "GST_PIPEWIRE_SRC_ON_DISCONNECT_NONE", "none"}, {GST_PIPEWIRE_SRC_ON_DISCONNECT_EOS, "GST_PIPEWIRE_SRC_ON_DISCONNECT_EOS", "eos"}, {GST_PIPEWIRE_SRC_ON_DISCONNECT_ERROR, "GST_PIPEWIRE_SRC_ON_DISCONNECT_ERROR", "error"}, {0, NULL, NULL}, }; if (g_once_init_enter (&on_disconnect_type)) { GType tmp = g_enum_register_static ("GstPipeWireSrcOnDisconnect", on_disconnect); g_once_init_leave (&on_disconnect_type, tmp); } return (GType) on_disconnect_type; } static GstStaticPadTemplate gst_pipewire_src_template = GST_STATIC_PAD_TEMPLATE ("src", GST_PAD_SRC, GST_PAD_ALWAYS, GST_STATIC_CAPS_ANY ); #define gst_pipewire_src_parent_class parent_class G_DEFINE_TYPE (GstPipeWireSrc, gst_pipewire_src, GST_TYPE_PUSH_SRC); static GstStateChangeReturn gst_pipewire_src_change_state (GstElement * element, GstStateChange transition); static gboolean gst_pipewire_src_send_event (GstElement * elem, GstEvent * event); static gboolean gst_pipewire_src_negotiate (GstBaseSrc * basesrc); static GstFlowReturn gst_pipewire_src_create (GstPushSrc * psrc, GstBuffer ** buffer); static gboolean gst_pipewire_src_unlock (GstBaseSrc * basesrc); static gboolean gst_pipewire_src_unlock_stop (GstBaseSrc * basesrc); static gboolean gst_pipewire_src_start (GstBaseSrc * basesrc); static gboolean gst_pipewire_src_stop (GstBaseSrc * basesrc); static gboolean gst_pipewire_src_event (GstBaseSrc * src, GstEvent * event); static gboolean gst_pipewire_src_query (GstBaseSrc * src, GstQuery * query); static void gst_pipewire_src_get_times (GstBaseSrc * basesrc, GstBuffer * buffer, GstClockTime * start, GstClockTime * end); static void gst_pipewire_src_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec) { GstPipeWireSrc *pwsrc = GST_PIPEWIRE_SRC (object); switch (prop_id) { case PROP_PATH: g_free (pwsrc->stream->path); pwsrc->stream->path = g_value_dup_string (value); break; case PROP_TARGET_OBJECT: g_free (pwsrc->stream->target_object); pwsrc->stream->target_object = g_value_dup_string (value); break; case PROP_CLIENT_NAME: g_free (pwsrc->stream->client_name); pwsrc->stream->client_name = g_value_dup_string (value); break; case PROP_CLIENT_PROPERTIES: if (pwsrc->stream->client_properties) gst_structure_free (pwsrc->stream->client_properties); pwsrc->stream->client_properties = gst_structure_copy (gst_value_get_structure (value)); break; case PROP_STREAM_PROPERTIES: if (pwsrc->stream->stream_properties) gst_structure_free (pwsrc->stream->stream_properties); pwsrc->stream->stream_properties = gst_structure_copy (gst_value_get_structure (value)); break; case PROP_ALWAYS_COPY: /* don't provide buffer if always copy*/ if (g_value_get_boolean (value)) pwsrc->use_bufferpool = USE_BUFFERPOOL_NO; else pwsrc->use_bufferpool = USE_BUFFERPOOL_YES; break; case PROP_MIN_BUFFERS: pwsrc->min_buffers = g_value_get_int (value); break; case PROP_MAX_BUFFERS: pwsrc->max_buffers = g_value_get_int (value); break; case PROP_FD: pwsrc->stream->fd = g_value_get_int (value); break; case PROP_RESEND_LAST: pwsrc->resend_last = g_value_get_boolean (value); break; case PROP_KEEPALIVE_TIME: pwsrc->keepalive_time = g_value_get_int (value); break; case PROP_AUTOCONNECT: pwsrc->autoconnect = g_value_get_boolean (value); break; case PROP_USE_BUFFERPOOL: if(g_value_get_boolean (value)) pwsrc->use_bufferpool = USE_BUFFERPOOL_YES; else pwsrc->use_bufferpool = USE_BUFFERPOOL_NO; break; case PROP_ON_DISCONNECT: pwsrc->on_disconnect = g_value_get_enum (value); break; case PROP_PROVIDE_CLOCK: gboolean provide = g_value_get_boolean (value); GST_OBJECT_LOCK (pwsrc); if (provide) GST_OBJECT_FLAG_SET (pwsrc, GST_ELEMENT_FLAG_PROVIDE_CLOCK); else GST_OBJECT_FLAG_UNSET (pwsrc, GST_ELEMENT_FLAG_PROVIDE_CLOCK); GST_OBJECT_UNLOCK (pwsrc); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void gst_pipewire_src_get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec) { GstPipeWireSrc *pwsrc = GST_PIPEWIRE_SRC (object); switch (prop_id) { case PROP_PATH: g_value_set_string (value, pwsrc->stream->path); break; case PROP_TARGET_OBJECT: g_value_set_string (value, pwsrc->stream->target_object); break; case PROP_CLIENT_NAME: g_value_set_string (value, pwsrc->stream->client_name); break; case PROP_CLIENT_PROPERTIES: gst_value_set_structure (value, pwsrc->stream->client_properties); break; case PROP_STREAM_PROPERTIES: gst_value_set_structure (value, pwsrc->stream->stream_properties); break; case PROP_ALWAYS_COPY: g_value_set_boolean (value, !pwsrc->use_bufferpool); break; case PROP_MIN_BUFFERS: g_value_set_int (value, pwsrc->min_buffers); break; case PROP_MAX_BUFFERS: g_value_set_int (value, pwsrc->max_buffers); break; case PROP_FD: g_value_set_int (value, pwsrc->stream->fd); break; case PROP_RESEND_LAST: g_value_set_boolean (value, pwsrc->resend_last); break; case PROP_KEEPALIVE_TIME: g_value_set_int (value, pwsrc->keepalive_time); break; case PROP_AUTOCONNECT: g_value_set_boolean (value, pwsrc->autoconnect); break; case PROP_USE_BUFFERPOOL: g_value_set_boolean (value, !!pwsrc->use_bufferpool); break; case PROP_ON_DISCONNECT: g_value_set_enum (value, pwsrc->on_disconnect); break; case PROP_PROVIDE_CLOCK: gboolean result; GST_OBJECT_LOCK (pwsrc); result = GST_OBJECT_FLAG_IS_SET (pwsrc, GST_ELEMENT_FLAG_PROVIDE_CLOCK); GST_OBJECT_UNLOCK (pwsrc); g_value_set_boolean (value, result); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static GstClock * gst_pipewire_src_provide_clock (GstElement * elem) { GstPipeWireSrc *pwsrc = GST_PIPEWIRE_SRC (elem); GstClock *clock; GST_OBJECT_LOCK (pwsrc); if (!GST_OBJECT_FLAG_IS_SET (pwsrc, GST_ELEMENT_FLAG_PROVIDE_CLOCK)) goto clock_disabled; if (pwsrc->stream->clock && pwsrc->is_live) clock = GST_CLOCK_CAST (gst_object_ref (pwsrc->stream->clock)); else clock = NULL; GST_OBJECT_UNLOCK (pwsrc); return clock; /* ERRORS */ clock_disabled: { GST_DEBUG_OBJECT (pwsrc, "clock provide disabled"); GST_OBJECT_UNLOCK (pwsrc); return NULL; } } static void gst_pipewire_src_finalize (GObject * object) { GstPipeWireSrc *pwsrc = GST_PIPEWIRE_SRC (object); gst_clear_object (&pwsrc->stream); G_OBJECT_CLASS (parent_class)->finalize (object); } static void gst_pipewire_src_class_init (GstPipeWireSrcClass * klass) { GObjectClass *gobject_class; GstElementClass *gstelement_class; GstBaseSrcClass *gstbasesrc_class; GstPushSrcClass *gstpushsrc_class; gobject_class = (GObjectClass *) klass; gstelement_class = (GstElementClass *) klass; gstbasesrc_class = (GstBaseSrcClass *) klass; gstpushsrc_class = (GstPushSrcClass *) klass; gobject_class->finalize = gst_pipewire_src_finalize; gobject_class->set_property = gst_pipewire_src_set_property; gobject_class->get_property = gst_pipewire_src_get_property; g_object_class_install_property (gobject_class, PROP_PATH, g_param_spec_string ("path", "Path", "The source path to connect to (NULL = default)", NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_DEPRECATED)); g_object_class_install_property (gobject_class, PROP_TARGET_OBJECT, g_param_spec_string ("target-object", "Target object", "The source name/serial to connect to (NULL = default)", NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (gobject_class, PROP_CLIENT_NAME, g_param_spec_string ("client-name", "Client Name", "The client name to use (NULL = default)", NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (gobject_class, PROP_CLIENT_PROPERTIES, g_param_spec_boxed ("client-properties", "client properties", "list of PipeWire client properties", GST_TYPE_STRUCTURE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (gobject_class, PROP_STREAM_PROPERTIES, g_param_spec_boxed ("stream-properties", "stream properties", "list of PipeWire stream properties", GST_TYPE_STRUCTURE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (gobject_class, PROP_ALWAYS_COPY, g_param_spec_boolean ("always-copy", "Always copy", "Always copy the buffer and data", DEFAULT_ALWAYS_COPY, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_DEPRECATED)); g_object_class_install_property (gobject_class, PROP_MIN_BUFFERS, g_param_spec_int ("min-buffers", "Min Buffers", "Minimum number of buffers to negotiate with PipeWire", 1, G_MAXINT, DEFAULT_MIN_BUFFERS, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (gobject_class, PROP_MAX_BUFFERS, g_param_spec_int ("max-buffers", "Max Buffers", "Maximum number of buffers to negotiate with PipeWire", 1, G_MAXINT, DEFAULT_MAX_BUFFERS, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (gobject_class, PROP_FD, g_param_spec_int ("fd", "Fd", "The fd to connect with", -1, G_MAXINT, -1, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (gobject_class, PROP_RESEND_LAST, g_param_spec_boolean ("resend-last", "Resend last", "Resend last buffer on EOS", DEFAULT_RESEND_LAST, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (gobject_class, PROP_KEEPALIVE_TIME, g_param_spec_int ("keepalive-time", "Keepalive Time", "Periodically send last buffer (in milliseconds, 0 = disabled)", 0, G_MAXINT, DEFAULT_KEEPALIVE_TIME, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (gobject_class, PROP_AUTOCONNECT, g_param_spec_boolean ("autoconnect", "Connect automatically", "Attempt to find a peer to connect to", DEFAULT_AUTOCONNECT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (gobject_class, PROP_USE_BUFFERPOOL, g_param_spec_boolean ("use-bufferpool", "Use bufferpool", "Use bufferpool (default: true for video, false for audio)", DEFAULT_USE_BUFFERPOOL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (gobject_class, PROP_ON_DISCONNECT, g_param_spec_enum ("on-disconnect", "On disconnect", "Action to take on disconnect", GST_TYPE_PIPEWIRE_SRC_ON_DISCONNECT, DEFAULT_ON_DISCONNECT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (gobject_class, PROP_PROVIDE_CLOCK, g_param_spec_boolean ("provide-clock", "Provide Clock", "Provide a clock to be used as the global pipeline clock", DEFAULT_PROVIDE_CLOCK, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); gstelement_class->provide_clock = gst_pipewire_src_provide_clock; gstelement_class->change_state = gst_pipewire_src_change_state; gstelement_class->send_event = gst_pipewire_src_send_event; gst_element_class_set_static_metadata (gstelement_class, "PipeWire source", "Source/Audio/Video", "Uses PipeWire to create audio/video", "Wim Taymans "); gst_element_class_add_pad_template (gstelement_class, gst_static_pad_template_get (&gst_pipewire_src_template)); gstbasesrc_class->negotiate = gst_pipewire_src_negotiate; gstbasesrc_class->unlock = gst_pipewire_src_unlock; gstbasesrc_class->unlock_stop = gst_pipewire_src_unlock_stop; gstbasesrc_class->start = gst_pipewire_src_start; gstbasesrc_class->stop = gst_pipewire_src_stop; gstbasesrc_class->event = gst_pipewire_src_event; gstbasesrc_class->query = gst_pipewire_src_query; gstbasesrc_class->get_times = gst_pipewire_src_get_times; gstpushsrc_class->create = gst_pipewire_src_create; GST_DEBUG_CATEGORY_INIT (pipewire_src_debug, "pipewiresrc", 0, "PipeWire Source"); process_mem_data_quark = g_quark_from_static_string ("GstPipeWireSrcProcessMemQuark"); } static void gst_pipewire_src_init (GstPipeWireSrc * src) { /* we operate in time */ gst_base_src_set_format (GST_BASE_SRC (src), GST_FORMAT_TIME); /* we're a live source, unless explicitly requested not to be */ gst_base_src_set_live (GST_BASE_SRC (src), TRUE); GST_OBJECT_FLAG_SET (src, GST_ELEMENT_FLAG_PROVIDE_CLOCK); src->stream = gst_pipewire_stream_new (GST_ELEMENT (src)); src->use_bufferpool = DEFAULT_USE_BUFFERPOOL; src->min_buffers = DEFAULT_MIN_BUFFERS; src->max_buffers = DEFAULT_MAX_BUFFERS; src->resend_last = DEFAULT_RESEND_LAST; src->keepalive_time = DEFAULT_KEEPALIVE_TIME; src->autoconnect = DEFAULT_AUTOCONNECT; src->min_latency = 0; src->max_latency = GST_CLOCK_TIME_NONE; src->n_buffers = 0; src->flushing_on_remove_buffer = FALSE; src->on_disconnect = DEFAULT_ON_DISCONNECT; src->transform_value = UINT32_MAX; } static gboolean buffer_recycle (GstMiniObject *obj) { GstPipeWirePoolData *data = gst_pipewire_pool_get_data (GST_BUFFER_CAST(obj)); GstPipeWireSrc *src = data->owner; int res; if (src->flushing_on_remove_buffer) { /* * If a flush-start was initiated, this might be called by elements like * queues downstream purging buffers from their internal queues. This can * deadlock if queues use min-threshold-buffers/bytes/time with src_create * trying to take the loop lock and buffer_recycle trying to take the loop * lock down below. We return from here, to prevent deadlock with streaming * thread in a queue thread. * * We will take care of queueing the buffer in on_remove_buffer. */ GstBuffer *buffer = GST_BUFFER_CAST(obj); GST_DEBUG_OBJECT (src, "flush-start initiated, skipping buffer recycle %p", buffer); return TRUE; } GST_OBJECT_LOCK (data->pool); if (!obj->dispose) { GST_OBJECT_UNLOCK (data->pool); return TRUE; } GST_BUFFER_FLAGS (obj) = data->flags; pw_thread_loop_lock (src->stream->core->loop); if (!obj->dispose) { pw_thread_loop_unlock (src->stream->core->loop); GST_OBJECT_UNLOCK (data->pool); return TRUE; } gst_mini_object_ref (obj); data->queued = TRUE; if ((res = pw_stream_queue_buffer (src->stream->pwstream, data->b)) < 0) GST_WARNING_OBJECT (src, "can't queue recycled buffer %p, %s", obj, spa_strerror(res)); else GST_LOG_OBJECT (src, "recycle buffer %p", obj); pw_thread_loop_unlock (src->stream->core->loop); GST_OBJECT_UNLOCK (data->pool); return FALSE; } static void on_add_buffer (void *_data, struct pw_buffer *b) { GstPipeWireSrc *pwsrc = _data; GstPipeWirePoolData *data; gst_pipewire_pool_wrap_buffer (pwsrc->stream->pool, b); data = b->user_data; GST_DEBUG_OBJECT (pwsrc, "add buffer %p", data->buf); data->owner = pwsrc; data->queued = TRUE; GST_MINI_OBJECT_CAST (data->buf)->dispose = buffer_recycle; pwsrc->n_buffers++; } static void on_remove_buffer (void *_data, struct pw_buffer *b) { GstPipeWireSrc *pwsrc = _data; GstPipeWirePoolData *data = b->user_data; GstBuffer *buf = data->buf; gboolean flush_on_remove; int res; GST_DEBUG_OBJECT (pwsrc, "remove buffer %p, queued: %d", buf, data->queued); GST_MINI_OBJECT_CAST (buf)->dispose = NULL; flush_on_remove = pwsrc->on_disconnect == GST_PIPEWIRE_SRC_ON_DISCONNECT_ERROR || pwsrc->on_disconnect == GST_PIPEWIRE_SRC_ON_DISCONNECT_EOS; if (flush_on_remove && !pwsrc->flushing_on_remove_buffer) { pwsrc->flushing_on_remove_buffer = TRUE; GST_DEBUG_OBJECT (pwsrc, "flush-start on remove buffer"); /* * It is possible that when buffers are being removed, a downstream * element can be holding on to a buffer or in the middle of rendering * the same. Former is possible with queues min-threshold-buffers or * similar. Latter can result in a crash during gst_video_frame_copy. * * We send a flush-start event downstream to make elements discard * any buffers they may be holding on to as well as return from their * chain function ASAP. */ gst_pad_push_event (GST_BASE_SRC_PAD (pwsrc), gst_event_new_flush_start ()); } if (data->queued) { gst_buffer_unref (buf); } else { if ((res = pw_stream_queue_buffer (pwsrc->stream->pwstream, b)) < 0) GST_WARNING_OBJECT (pwsrc, "can't queue removed buffer %p, %s", buf, spa_strerror(res)); else GST_DEBUG_OBJECT (pwsrc, "queued buffer %p", buf); } pwsrc->n_buffers--; if (pwsrc->n_buffers == 0) { GST_DEBUG_OBJECT (pwsrc, "removed all buffers"); pwsrc->flushing_on_remove_buffer = FALSE; switch (pwsrc->on_disconnect) { case GST_PIPEWIRE_SRC_ON_DISCONNECT_ERROR: GST_DEBUG_OBJECT (pwsrc, "flush-stop on removing all buffers"); gst_pad_push_event (GST_BASE_SRC_PAD (pwsrc), gst_event_new_flush_stop (FALSE)); GST_ELEMENT_ERROR (pwsrc, RESOURCE, NOT_FOUND, ("all buffers have been removed"), ("PipeWire link to remote node was destroyed")); break; case GST_PIPEWIRE_SRC_ON_DISCONNECT_EOS: GST_DEBUG_OBJECT (pwsrc, "flush-stop on removing all buffers"); gst_pad_push_event (GST_BASE_SRC_PAD (pwsrc), gst_event_new_flush_stop (FALSE)); GST_DEBUG_OBJECT (pwsrc, "sending eos downstream"); gst_pad_push_event (GST_BASE_SRC_PAD (pwsrc), gst_event_new_eos()); break; case GST_PIPEWIRE_SRC_ON_DISCONNECT_NONE: GST_DEBUG_OBJECT (pwsrc, "stream closed or removed"); break; } } } static const char * const transform_map[] = { [SPA_META_TRANSFORMATION_None] = "rotate-0", [SPA_META_TRANSFORMATION_90] = "rotate-90", [SPA_META_TRANSFORMATION_180] = "rotate-180", [SPA_META_TRANSFORMATION_270] = "rotate-270", [SPA_META_TRANSFORMATION_Flipped] = "flip-rotate-0", [SPA_META_TRANSFORMATION_Flipped90] = "flip-rotate-270", [SPA_META_TRANSFORMATION_Flipped180] = "flip-rotate-180", [SPA_META_TRANSFORMATION_Flipped270] = "flip-rotate-90", }; static const char *spa_transform_value_to_gst_image_orientation(uint32_t transform_value) { if (transform_value >= SPA_N_ELEMENTS(transform_map)) transform_value = SPA_META_TRANSFORMATION_None; return transform_map[transform_value]; } static GstBuffer *dequeue_buffer(GstPipeWireSrc *pwsrc) { struct pw_buffer *b; GstBuffer *buf; GstPipeWirePoolData *data; struct spa_meta_header *h; struct spa_meta_region *crop; enum spa_meta_videotransform_value transform_value; struct spa_meta_cursor *cursor; struct pw_time time; guint i; b = pw_stream_dequeue_buffer (pwsrc->stream->pwstream); if (b == NULL) return NULL; data = b->user_data; if (!GST_IS_BUFFER (data->buf)) { GST_ERROR_OBJECT (pwsrc, "stream buffer %p is missing", data->buf); return NULL; } if (!data->queued) { GST_ERROR_OBJECT (pwsrc, "buffer %p was not recycled", data->buf); return NULL; } pw_stream_get_time_n(pwsrc->stream->pwstream, &time, sizeof(time)); if (pwsrc->delay != time.delay && time.rate.denom != 0) { pwsrc->min_latency = time.delay * GST_SECOND * time.rate.num / time.rate.denom; GST_LOG_OBJECT (pwsrc, "latency changed %"PRIi64" -> %"PRIi64" %"PRIu64, pwsrc->delay, time.delay, pwsrc->min_latency); pwsrc->delay = time.delay; gst_element_post_message (GST_ELEMENT_CAST (pwsrc), gst_message_new_latency (GST_OBJECT_CAST (pwsrc))); } GST_LOG_OBJECT (pwsrc, "got new buffer %p", data->buf); buf = gst_buffer_new (); data->queued = FALSE; GST_BUFFER_PTS (buf) = GST_CLOCK_TIME_NONE; GST_BUFFER_DTS (buf) = GST_CLOCK_TIME_NONE; h = data->header; if (h) { GST_LOG_OBJECT (pwsrc, "pts %" G_GUINT64_FORMAT ", dts_offset %" G_GUINT64_FORMAT, h->pts, h->dts_offset); if (GST_CLOCK_TIME_IS_VALID (h->pts)) { GST_BUFFER_PTS (buf) = h->pts; if (GST_BUFFER_PTS (buf) + h->dts_offset > 0) GST_BUFFER_DTS (buf) = GST_BUFFER_PTS (buf) + h->dts_offset; } GST_BUFFER_OFFSET (buf) = h->seq; } else { GST_BUFFER_PTS (buf) = b->time - pwsrc->delay; GST_BUFFER_DTS (buf) = b->time - pwsrc->delay; } if (pwsrc->media_type == SPA_MEDIA_TYPE_video) { if (pwsrc->video_info.fps_n) { GST_BUFFER_DURATION (buf) = gst_util_uint64_scale (GST_SECOND, pwsrc->video_info.fps_d, pwsrc->video_info.fps_n); } } else { GST_BUFFER_DURATION (buf) = gst_util_uint64_scale (GST_SECOND, time.size * time.rate.num, time.rate.denom); } crop = data->crop; if (crop) { GstVideoCropMeta *meta = gst_buffer_get_video_crop_meta(buf); if (meta) { meta->x = crop->region.position.x; meta->y = crop->region.position.y; meta->width = crop->region.size.width; meta->height = crop->region.size.height; } } transform_value = data->videotransform ? data->videotransform->transform : SPA_META_TRANSFORMATION_None; if (transform_value != pwsrc->transform_value) { GstEvent *tag_event; const char* tag_string; tag_string = spa_transform_value_to_gst_image_orientation(transform_value); GST_LOG_OBJECT (pwsrc, "got new videotransform: %u / %s", transform_value, tag_string); tag_event = gst_event_new_tag(gst_tag_list_new(GST_TAG_IMAGE_ORIENTATION, tag_string, NULL)); gst_pad_push_event (GST_BASE_SRC_PAD (pwsrc), tag_event); pwsrc->transform_value = transform_value; } cursor = data->cursor; if (cursor && cursor->id != 0) { /* TODO: at some point, maybe we can figure out width and height from the bitmap, * and even add that to the meta itself */ gst_buffer_add_video_region_of_interest_meta (buf, "cursor", cursor->position.x, cursor->position.y, 0, 0); } if (pwsrc->is_rawvideo) { GstVideoInfo *info = &pwsrc->video_info; uint32_t n_datas = b->buffer->n_datas; uint32_t n_planes = GST_VIDEO_INFO_N_PLANES (info); gsize video_size = 0; GstVideoMeta *meta = gst_buffer_add_video_meta_full (buf, GST_VIDEO_FRAME_FLAG_NONE, GST_VIDEO_INFO_FORMAT (info), GST_VIDEO_INFO_WIDTH (info), GST_VIDEO_INFO_HEIGHT (info), GST_VIDEO_INFO_N_PLANES (info), info->offset, info->stride); for (i = 0; i < MIN (n_datas, n_planes); i++) { struct spa_data *d = &b->buffer->datas[i]; /* don't add the chunk offset here, this is done below when we * share/copy the memory in the target buffer below */ meta->offset[i] = video_size; meta->stride[i] = d->chunk->stride; video_size += d->chunk->size; } } if (b->buffer->n_datas != gst_buffer_n_memory(data->buf)) { GST_ERROR_OBJECT(pwsrc, "n_datas != n_memory, (%d != %d)", b->buffer->n_datas, gst_buffer_n_memory(data->buf)); } for (i = 0; i < b->buffer->n_datas; i++) { struct spa_data *d = &b->buffer->datas[i]; if (d->chunk->size == 0) { // Skip the 0 sized chunk, not adding to the buffer GST_DEBUG_OBJECT(pwsrc, "Chunk size is 0, skipping"); continue; } GstMemory *pmem = gst_buffer_peek_memory (data->buf, i); if (pmem) { GstMemory *mem; if (pwsrc->use_bufferpool != USE_BUFFERPOOL_NO) mem = gst_memory_share (pmem, d->chunk->offset, d->chunk->size); else mem = gst_memory_copy (pmem, d->chunk->offset, d->chunk->size); gst_buffer_insert_memory (buf, i, mem); } if (d->chunk->flags & SPA_CHUNK_FLAG_CORRUPTED) { GST_DEBUG_OBJECT(pwsrc, "Buffer corrupted"); GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_CORRUPTED); } } if (pwsrc->use_bufferpool != USE_BUFFERPOOL_NO) gst_buffer_add_parent_buffer_meta (buf, data->buf); gst_buffer_unref (data->buf); if (gst_buffer_get_size(buf) == 0) { GST_ERROR_OBJECT(pwsrc, "Buffer is empty, dropping this"); gst_buffer_unref(buf); buf = NULL; } return buf; } static void on_process (void *_data) { GstPipeWireSrc *pwsrc = _data; pw_thread_loop_signal (pwsrc->stream->core->loop, FALSE); } static void on_state_changed (void *data, enum pw_stream_state old, enum pw_stream_state state, const char *error) { GstPipeWireSrc *pwsrc = data; GstState current_state = GST_ELEMENT_CAST (pwsrc)->current_state; GST_DEBUG_OBJECT (pwsrc, "got stream state %s", pw_stream_state_as_string (state)); switch (state) { case PW_STREAM_STATE_UNCONNECTED: case PW_STREAM_STATE_CONNECTING: break; case PW_STREAM_STATE_PAUSED: /* * We may see a driver/quantum/clock rate change on switching audio * sources. The same is not applicable for video. * * We post the clock lost message here to take care of a possible * jump or shift in base_time/clock for the pipeline. Application * must handle the clock lost message in it's bus handler by pausing * the pipeline and then setting it back to playing. */ if (current_state == GST_STATE_PLAYING && pwsrc->media_type == SPA_MEDIA_TYPE_audio) gst_element_post_message (GST_ELEMENT_CAST (pwsrc), gst_message_new_clock_lost (GST_OBJECT_CAST (pwsrc), GST_CLOCK_CAST (pwsrc->stream->clock))); break; case PW_STREAM_STATE_STREAMING: break; case PW_STREAM_STATE_ERROR: /* make the error permanent, if it is not already; pw_stream_set_error() will recursively call us again */ if (pw_stream_get_state (pwsrc->stream->pwstream, NULL) != PW_STREAM_STATE_ERROR) pw_stream_set_error (pwsrc->stream->pwstream, -EPIPE, "%s", error); else GST_ELEMENT_ERROR (pwsrc, RESOURCE, FAILED, ("stream error: %s", error), (NULL)); break; } pw_thread_loop_signal (pwsrc->stream->core->loop, FALSE); } static void parse_stream_properties (GstPipeWireSrc *pwsrc, const struct pw_properties *props) { const gchar *var; gboolean is_live; GST_OBJECT_LOCK (pwsrc); var = pw_properties_get (props, PW_KEY_STREAM_IS_LIVE); is_live = pwsrc->is_live = var ? pw_properties_parse_bool(var) : TRUE; GST_OBJECT_UNLOCK (pwsrc); GST_DEBUG_OBJECT (pwsrc, "live %d", is_live); gst_base_src_set_live (GST_BASE_SRC (pwsrc), is_live); } static gboolean gst_pipewire_src_stream_start (GstPipeWireSrc *pwsrc) { const char *error = NULL; struct timespec abstime; pw_thread_loop_lock (pwsrc->stream->core->loop); GST_DEBUG_OBJECT (pwsrc, "doing stream start"); pw_thread_loop_get_time (pwsrc->stream->core->loop, &abstime, GST_PIPEWIRE_DEFAULT_TIMEOUT * SPA_NSEC_PER_SEC); while (TRUE) { enum pw_stream_state state = pw_stream_get_state (pwsrc->stream->pwstream, &error); GST_DEBUG_OBJECT (pwsrc, "waiting for STREAMING, now %s", pw_stream_state_as_string (state)); if (state == PW_STREAM_STATE_STREAMING) break; if (state == PW_STREAM_STATE_ERROR) goto start_error; if (pwsrc->flushing) { error = "flushing"; goto start_error; } if (pw_thread_loop_timed_wait_full (pwsrc->stream->core->loop, &abstime) < 0) { error = "timeout"; goto start_error; } } parse_stream_properties (pwsrc, pw_stream_get_properties (pwsrc->stream->pwstream)); GST_DEBUG_OBJECT (pwsrc, "signal started"); pwsrc->started = TRUE; pw_thread_loop_signal (pwsrc->stream->core->loop, FALSE); pw_thread_loop_unlock (pwsrc->stream->core->loop); return TRUE; start_error: { GST_DEBUG_OBJECT (pwsrc, "error starting stream: %s", error); pwsrc->started = FALSE; pw_thread_loop_signal (pwsrc->stream->core->loop, FALSE); pw_thread_loop_unlock (pwsrc->stream->core->loop); return FALSE; } } static enum pw_stream_state wait_started (GstPipeWireSrc *this) { enum pw_stream_state state, prev_state = PW_STREAM_STATE_UNCONNECTED; const char *error = NULL; struct timespec abstime; gboolean restart = FALSE; pw_thread_loop_lock (this->stream->core->loop); pw_thread_loop_get_time (this->stream->core->loop, &abstime, GST_PIPEWIRE_DEFAULT_TIMEOUT * SPA_NSEC_PER_SEC); /* when started already is true then expects a re-start, so allow prev_state * degrade until turned around. */ if (this->started) { GST_DEBUG_OBJECT (this, "restart in progress"); restart = TRUE; this->started = FALSE; } while (TRUE) { state = pw_stream_get_state (this->stream->pwstream, &error); GST_DEBUG_OBJECT (this, "waiting for started signal, state now %s", pw_stream_state_as_string (state)); if (state == PW_STREAM_STATE_ERROR || (state == PW_STREAM_STATE_UNCONNECTED && prev_state > PW_STREAM_STATE_UNCONNECTED && !restart) || this->flushing) { state = PW_STREAM_STATE_ERROR; break; } if (this->started) break; if (this->autoconnect) { if (pw_thread_loop_timed_wait_full (this->stream->core->loop, &abstime) < 0) { state = PW_STREAM_STATE_ERROR; break; } } else { pw_thread_loop_wait (this->stream->core->loop); } if (restart) restart = state != PW_STREAM_STATE_UNCONNECTED; prev_state = state; } GST_DEBUG_OBJECT (this, "got started signal: %s", pw_stream_state_as_string (state)); pw_thread_loop_unlock (this->stream->core->loop); return state; } static gboolean gst_pipewire_src_negotiate (GstBaseSrc * basesrc) { GstPipeWireSrc *pwsrc = GST_PIPEWIRE_SRC (basesrc); g_autoptr (GstCaps) thiscaps = NULL; g_autoptr (GstCaps) possible_caps = NULL; g_autoptr (GstCaps) negotiated_caps = NULL; g_autoptr (GstCaps) peercaps = NULL; g_autoptr (GPtrArray) possible = NULL; gboolean result = FALSE; const char *error = NULL; struct timespec abstime; uint32_t target_id; /* first see what is possible on our source pad */ thiscaps = gst_pad_query_caps (GST_BASE_SRC_PAD (basesrc), NULL); GST_DEBUG_OBJECT (basesrc, "caps of src: %" GST_PTR_FORMAT, thiscaps); /* nothing or anything is allowed, we're done */ if (thiscaps == NULL) goto no_nego_needed; if (G_UNLIKELY (gst_caps_is_empty (thiscaps))) goto no_caps; /* get the peer caps */ peercaps = gst_pad_peer_query_caps (GST_BASE_SRC_PAD (basesrc), thiscaps); GST_DEBUG_OBJECT (basesrc, "caps of peer: %" GST_PTR_FORMAT, peercaps); if (peercaps) { /* The result is already a subset of our caps */ possible_caps = g_steal_pointer (&peercaps); } else { /* no peer, work with our own caps then */ possible_caps = g_steal_pointer (&thiscaps); } GST_DEBUG_OBJECT (basesrc, "have common caps: %" GST_PTR_FORMAT, possible_caps); gst_caps_sanitize (&possible_caps); if (gst_caps_is_empty (possible_caps)) goto no_common_caps; GST_DEBUG_OBJECT (basesrc, "have common caps (sanitized): %" GST_PTR_FORMAT, possible_caps); if (pw_stream_get_state(pwsrc->stream->pwstream, NULL) == PW_STREAM_STATE_STREAMING) { g_autoptr (GstCaps) current_caps = NULL; g_autoptr (GstCaps) preferred_new_caps = NULL; current_caps = gst_pad_get_current_caps (GST_BASE_SRC_PAD (pwsrc)); preferred_new_caps = gst_caps_copy_nth (possible_caps, 0); if (current_caps && gst_caps_is_equal (current_caps, preferred_new_caps)) { GST_DEBUG_OBJECT (pwsrc, "Stream running and new caps equal current ones. " "Skipping renegotiation."); goto no_nego_needed; } } /* open a connection with these caps */ possible = gst_caps_to_format_all (possible_caps); /* first disconnect */ pw_thread_loop_lock (pwsrc->stream->core->loop); if (pw_stream_get_state(pwsrc->stream->pwstream, &error) != PW_STREAM_STATE_UNCONNECTED) { GST_DEBUG_OBJECT (basesrc, "disconnect capture"); pw_stream_disconnect (pwsrc->stream->pwstream); while (TRUE) { enum pw_stream_state state = pw_stream_get_state (pwsrc->stream->pwstream, &error); GST_DEBUG_OBJECT (basesrc, "waiting for UNCONNECTED, now %s", pw_stream_state_as_string (state)); if (state == PW_STREAM_STATE_UNCONNECTED) break; if (state == PW_STREAM_STATE_ERROR || pwsrc->flushing) goto connect_error; pw_thread_loop_wait (pwsrc->stream->core->loop); } } target_id = pwsrc->stream->path ? (uint32_t)atoi(pwsrc->stream->path) : PW_ID_ANY; if (pwsrc->stream->target_object) { struct spa_dict_item items[2] = { SPA_DICT_ITEM_INIT(PW_KEY_TARGET_OBJECT, pwsrc->stream->target_object), /* XXX deprecated but the portal and some example apps only * provide the object id */ SPA_DICT_ITEM_INIT(PW_KEY_NODE_TARGET, NULL), }; struct spa_dict dict = SPA_DICT_INIT_ARRAY(items); uint64_t serial; /* If target.object is a name, set it also to node.target */ if (spa_atou64(pwsrc->stream->target_object, &serial, 0)) { dict.n_items = 1; } else { target_id = PW_ID_ANY; items[1].value = pwsrc->stream->target_object; } pw_stream_update_properties (pwsrc->stream->pwstream, &dict); } GST_DEBUG_OBJECT (basesrc, "connect capture with path %s, target-object %s", pwsrc->stream->path, pwsrc->stream->target_object); gst_caps_replace (&pwsrc->possible_caps, possible_caps); pwsrc->negotiated = FALSE; enum pw_stream_flags flags; flags = PW_STREAM_FLAG_DONT_RECONNECT | PW_STREAM_FLAG_ASYNC; if (pwsrc->autoconnect) flags |= PW_STREAM_FLAG_AUTOCONNECT; pw_stream_connect (pwsrc->stream->pwstream, PW_DIRECTION_INPUT, target_id, flags, (const struct spa_pod **)possible->pdata, possible->len); pw_thread_loop_get_time (pwsrc->stream->core->loop, &abstime, GST_PIPEWIRE_DEFAULT_TIMEOUT * SPA_NSEC_PER_SEC); while (TRUE) { enum pw_stream_state state = pw_stream_get_state (pwsrc->stream->pwstream, &error); GST_DEBUG_OBJECT (basesrc, "waiting for NEGOTIATED, now %s", pw_stream_state_as_string (state)); if (state == PW_STREAM_STATE_ERROR || pwsrc->flushing) goto connect_error; if (pwsrc->negotiated) break; if (pwsrc->autoconnect) { if (pw_thread_loop_timed_wait_full (pwsrc->stream->core->loop, &abstime) < 0) goto connect_error; } else { pw_thread_loop_wait (pwsrc->stream->core->loop); } } negotiated_caps = g_steal_pointer (&pwsrc->caps); pw_thread_loop_unlock (pwsrc->stream->core->loop); if (negotiated_caps == NULL) goto no_caps; gst_pipewire_clock_reset (GST_PIPEWIRE_CLOCK (pwsrc->stream->clock), 0); GST_INFO_OBJECT (pwsrc, "set format %" GST_PTR_FORMAT, negotiated_caps); result = gst_base_src_set_caps (GST_BASE_SRC (pwsrc), negotiated_caps); if (!result) goto no_caps; result = gst_pipewire_src_stream_start (pwsrc); return result; no_nego_needed: { GST_DEBUG_OBJECT (basesrc, "no negotiation needed"); return TRUE; } no_caps: { const gchar * error_string = "No supported formats found"; GST_ELEMENT_ERROR (basesrc, STREAM, FORMAT, ("%s", error_string), ("This element did not produce valid caps")); pw_thread_loop_lock (pwsrc->stream->core->loop); pw_stream_set_error (pwsrc->stream->pwstream, -EINVAL, "%s", error_string); pw_thread_loop_unlock (pwsrc->stream->core->loop); return FALSE; } no_common_caps: { const gchar * error_string = "No supported formats found"; GST_ELEMENT_ERROR (basesrc, STREAM, FORMAT, ("%s", error_string), ("This element does not have formats in common with the peer")); pw_thread_loop_lock (pwsrc->stream->core->loop); pw_stream_set_error (pwsrc->stream->pwstream, -EPIPE, "%s", error_string); pw_thread_loop_unlock (pwsrc->stream->core->loop); return FALSE; } connect_error: { g_clear_pointer (&pwsrc->caps, gst_caps_unref); pwsrc->possible_caps = NULL; GST_DEBUG_OBJECT (basesrc, "connect error"); pw_thread_loop_unlock (pwsrc->stream->core->loop); return FALSE; } } static void handle_format_change (GstPipeWireSrc *pwsrc, const struct spa_pod *param) { GstStructure *structure; g_autoptr (GstCaps) pw_peer_caps = NULL; g_clear_pointer (&pwsrc->caps, gst_caps_unref); if (param == NULL) { GST_DEBUG_OBJECT (pwsrc, "clear format"); pwsrc->negotiated = FALSE; pwsrc->media_type = SPA_MEDIA_TYPE_unknown; return; } pw_peer_caps = gst_caps_from_format (param); if (pw_peer_caps && pwsrc->possible_caps) { GST_DEBUG_OBJECT (pwsrc, "peer caps %" GST_PTR_FORMAT, pw_peer_caps); GST_DEBUG_OBJECT (pwsrc, "possible caps %" GST_PTR_FORMAT, pwsrc->possible_caps); pwsrc->caps = gst_caps_intersect_full (pw_peer_caps, pwsrc->possible_caps, GST_CAPS_INTERSECT_FIRST); /* * We expect pw_peer_caps to be fixed caps as we receive that from * PipeWire. See pw_context_find_format() and SPA_PARAM_Format. * possible_caps can be non-fixated caps based on what is downstream * in the pipeline. * * The intersection result above might give us non-fixated caps. A * possible scenario for this is the below pipeline. * pipewiresrc ! audioconvert ! audio/x-raw,rate=44100,channels=2 ! .. * * So we fixate the caps explicitly here. */ pwsrc->caps = gst_caps_fixate (pwsrc->caps); gst_caps_maybe_fixate_dma_format (pwsrc->caps); } if (pwsrc->caps) { g_return_if_fail (gst_caps_is_fixed (pwsrc->caps)); pwsrc->negotiated = TRUE; structure = gst_caps_get_structure (pwsrc->caps, 0); if (g_str_has_prefix (gst_structure_get_name (structure), "video/") || g_str_has_prefix (gst_structure_get_name (structure), "image/")) { pwsrc->media_type = SPA_MEDIA_TYPE_video; #ifdef HAVE_GSTREAMER_DMA_DRM if (gst_video_is_dma_drm_caps (pwsrc->caps)) { if (!gst_video_info_dma_drm_from_caps (&pwsrc->drm_info, pwsrc->caps)) { GST_WARNING_OBJECT (pwsrc, "Can't create drm video info from caps"); pw_stream_set_error (pwsrc->stream->pwstream, -EINVAL, "internal error"); return; } if (!gst_video_info_dma_drm_to_video_info (&pwsrc->drm_info, &pwsrc->video_info)) { GST_WARNING_OBJECT (pwsrc, "Can't create video info from drm video info"); pw_stream_set_error (pwsrc->stream->pwstream, -EINVAL, "internal error"); return; } pwsrc->is_rawvideo = TRUE; } else { gst_video_info_dma_drm_init (&pwsrc->drm_info); #endif gst_video_info_from_caps (&pwsrc->video_info, pwsrc->caps); if (GST_VIDEO_FORMAT_INFO_IS_VALID_RAW (pwsrc->video_info.finfo) #ifdef HAVE_GSTREAMER_DMA_DRM && GST_VIDEO_FORMAT_INFO_FORMAT (pwsrc->video_info.finfo) != GST_VIDEO_FORMAT_DMA_DRM #endif ) pwsrc->is_rawvideo = TRUE; else pwsrc->is_rawvideo = FALSE; #ifdef HAVE_GSTREAMER_DMA_DRM } #endif } else { /* Don't provide bufferpool for audio if not requested by the * application/user */ if (pwsrc->use_bufferpool != USE_BUFFERPOOL_YES) pwsrc->use_bufferpool = USE_BUFFERPOOL_NO; pwsrc->media_type = SPA_MEDIA_TYPE_audio; } } else { pwsrc->negotiated = FALSE; pwsrc->media_type = SPA_MEDIA_TYPE_unknown; pwsrc->is_rawvideo = FALSE; } if (pwsrc->caps) { const struct spa_pod *params[10]; struct spa_pod_builder b = { NULL }; uint8_t buffer[16384]; uint32_t buffers = CLAMP (16, pwsrc->min_buffers, pwsrc->max_buffers); int buffertypes, n_params = 0; buffertypes = (1<caps); spa_pod_builder_init (&b, buffer, sizeof (buffer)); params[n_params++] = spa_pod_builder_add_object (&b, SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers, SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(buffers, pwsrc->min_buffers, pwsrc->max_buffers), SPA_PARAM_BUFFERS_blocks, SPA_POD_CHOICE_RANGE_Int(0, 1, INT32_MAX), SPA_PARAM_BUFFERS_size, SPA_POD_CHOICE_RANGE_Int(0, 1, INT32_MAX), SPA_PARAM_BUFFERS_stride, SPA_POD_CHOICE_RANGE_Int(0, 0, INT32_MAX), SPA_PARAM_BUFFERS_dataType, SPA_POD_CHOICE_FLAGS_Int(buffertypes)); params[n_params++] = spa_pod_builder_add_object (&b, SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta, SPA_PARAM_META_type, SPA_POD_Id(SPA_META_Header), SPA_PARAM_META_size, SPA_POD_Int(sizeof (struct spa_meta_header))); params[n_params++] = spa_pod_builder_add_object (&b, SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta, SPA_PARAM_META_type, SPA_POD_Id(SPA_META_VideoCrop), SPA_PARAM_META_size, SPA_POD_Int(sizeof (struct spa_meta_region))); params[n_params++] = spa_pod_builder_add_object (&b, SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta, SPA_PARAM_META_type, SPA_POD_Id(SPA_META_VideoTransform), SPA_PARAM_META_size, SPA_POD_Int(sizeof (struct spa_meta_videotransform))); #define CURSOR_META_SIZE(width, height) \ (sizeof (struct spa_meta_cursor) + \ sizeof (struct spa_meta_bitmap) + width * height * 4) params[n_params++] = spa_pod_builder_add_object (&b, SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta, SPA_PARAM_META_type, SPA_POD_Id(SPA_META_Cursor), SPA_PARAM_META_size, SPA_POD_CHOICE_RANGE_Int (CURSOR_META_SIZE(384, 384), sizeof (struct spa_meta_cursor), CURSOR_META_SIZE(384, 384))); GST_DEBUG_OBJECT (pwsrc, "doing finish format"); pw_stream_update_params (pwsrc->stream->pwstream, params, n_params); } else { GST_WARNING_OBJECT (pwsrc, "finish format with error"); pw_stream_set_error (pwsrc->stream->pwstream, -EINVAL, "unhandled format"); } pw_thread_loop_signal (pwsrc->stream->core->loop, FALSE); } static void on_param_changed (void *data, uint32_t id, const struct spa_pod *param) { GstPipeWireSrc *pwsrc = data; switch (id) { case SPA_PARAM_Format: handle_format_change(pwsrc, param); break; } } static gboolean gst_pipewire_src_unlock (GstBaseSrc * basesrc) { GstPipeWireSrc *pwsrc = GST_PIPEWIRE_SRC (basesrc); pw_thread_loop_lock (pwsrc->stream->core->loop); GST_DEBUG_OBJECT (pwsrc, "setting flushing"); pwsrc->flushing = TRUE; pw_thread_loop_signal (pwsrc->stream->core->loop, FALSE); pw_thread_loop_unlock (pwsrc->stream->core->loop); return TRUE; } static gboolean gst_pipewire_src_unlock_stop (GstBaseSrc * basesrc) { GstPipeWireSrc *pwsrc = GST_PIPEWIRE_SRC (basesrc); pw_thread_loop_lock (pwsrc->stream->core->loop); GST_DEBUG_OBJECT (pwsrc, "unsetting flushing"); pwsrc->flushing = FALSE; pw_thread_loop_unlock (pwsrc->stream->core->loop); return TRUE; } static gboolean gst_pipewire_src_event (GstBaseSrc * src, GstEvent * event) { gboolean res = FALSE; switch (GST_EVENT_TYPE (event)) { case GST_EVENT_CUSTOM_UPSTREAM: if (gst_video_event_is_force_key_unit (event)) { GstClockTime running_time; gboolean all_headers; guint count; gst_video_event_parse_upstream_force_key_unit (event, &running_time, &all_headers, &count); res = TRUE; } else { res = GST_BASE_SRC_CLASS (parent_class)->event (src, event); } break; default: res = GST_BASE_SRC_CLASS (parent_class)->event (src, event); break; } return res; } static gboolean gst_pipewire_src_query (GstBaseSrc * src, GstQuery * query) { gboolean res = FALSE; GstPipeWireSrc *pwsrc; pwsrc = GST_PIPEWIRE_SRC (src); switch (GST_QUERY_TYPE (query)) { case GST_QUERY_LATENCY: GST_OBJECT_LOCK (pwsrc); gst_query_set_latency (query, pwsrc->is_live, pwsrc->min_latency, pwsrc->max_latency); GST_OBJECT_UNLOCK (pwsrc); res = TRUE; break; default: res = GST_BASE_SRC_CLASS (parent_class)->query (src, query); break; } return res; } static void gst_pipewire_src_get_times (GstBaseSrc * basesrc, GstBuffer * buffer, GstClockTime * start, GstClockTime * end) { GstPipeWireSrc *pwsrc = GST_PIPEWIRE_SRC (basesrc); /* for live sources, sync on the timestamp of the buffer */ if (gst_base_src_is_live (basesrc)) { GstClockTime timestamp = GST_BUFFER_PTS (buffer); if (GST_CLOCK_TIME_IS_VALID (timestamp)) { /* get duration to calculate end time */ GstClockTime duration = GST_BUFFER_DURATION (buffer); if (GST_CLOCK_TIME_IS_VALID (duration)) { *end = timestamp + duration; } *start = timestamp; } } else { *start = GST_CLOCK_TIME_NONE; *end = GST_CLOCK_TIME_NONE; } GST_LOG_OBJECT (pwsrc, "start %" GST_TIME_FORMAT " (%" G_GUINT64_FORMAT "), end %" GST_TIME_FORMAT " (%" G_GUINT64_FORMAT ")", GST_TIME_ARGS (*start), *start, GST_TIME_ARGS (*end), *end); } static GstFlowReturn gst_pipewire_src_create (GstPushSrc * psrc, GstBuffer ** buffer) { GstPipeWireSrc *pwsrc; const char *error = NULL; GstBuffer *buf; gboolean update_time = FALSE, timeout = FALSE; GstCaps *caps = NULL; struct timespec abstime = { 0, }; bool have_abstime = false; pwsrc = GST_PIPEWIRE_SRC (psrc); pw_thread_loop_lock (pwsrc->stream->core->loop); if (!pwsrc->negotiated) goto not_negotiated; while (TRUE) { enum pw_stream_state state; if (pwsrc->flushing) goto streaming_stopped; if (pwsrc->stream == NULL) goto streaming_error; state = pw_stream_get_state (pwsrc->stream->pwstream, &error); if (state == PW_STREAM_STATE_ERROR) goto streaming_error; if (state == PW_STREAM_STATE_UNCONNECTED) goto streaming_stopped; if ((caps = pwsrc->caps) != NULL) { pwsrc->caps = NULL; pw_thread_loop_unlock (pwsrc->stream->core->loop); GST_DEBUG_OBJECT (pwsrc, "set format %" GST_PTR_FORMAT, caps); gst_base_src_set_caps (GST_BASE_SRC (pwsrc), caps); gst_caps_unref (caps); pw_thread_loop_lock (pwsrc->stream->core->loop); continue; } if (pwsrc->eos) { if (pwsrc->last_buffer == NULL) goto streaming_eos; buf = pwsrc->last_buffer; pwsrc->last_buffer = NULL; update_time = TRUE; GST_LOG_OBJECT (pwsrc, "EOS, send last buffer"); break; } else if (timeout && pwsrc->last_buffer != NULL) { update_time = TRUE; buf = gst_buffer_ref(pwsrc->last_buffer); GST_LOG_OBJECT (pwsrc, "timeout, send keepalive buffer"); break; } else { buf = dequeue_buffer (pwsrc); GST_LOG_OBJECT (pwsrc, "popped buffer %p", buf); if (buf != NULL) { if (pwsrc->resend_last || pwsrc->keepalive_time > 0) gst_buffer_replace (&pwsrc->last_buffer, buf); break; } } timeout = FALSE; if (pwsrc->keepalive_time > 0) { if (!have_abstime) { /* Record the time we want to timeout at once, for this loop -- the loop might get unrelated signal()s, * and we don't want the keepalive time to get reset by that */ pw_thread_loop_get_time(pwsrc->stream->core->loop, &abstime, pwsrc->keepalive_time * SPA_NSEC_PER_MSEC); have_abstime = TRUE; } if (pw_thread_loop_timed_wait_full (pwsrc->stream->core->loop, &abstime) == -ETIMEDOUT) timeout = TRUE; } else { pw_thread_loop_wait (pwsrc->stream->core->loop); } } pw_thread_loop_unlock (pwsrc->stream->core->loop); *buffer = buf; if (update_time) { GstClock *clock; GstClockTime pts, dts; clock = gst_element_get_clock (GST_ELEMENT_CAST (pwsrc)); if (clock != NULL) { pts = dts = gst_clock_get_time (clock); gst_object_unref (clock); } else { pts = dts = GST_CLOCK_TIME_NONE; } GST_BUFFER_PTS (*buffer) = pts; GST_BUFFER_DTS (*buffer) = dts; GST_LOG_OBJECT (pwsrc, "Sending keepalive buffer pts/dts: %" GST_TIME_FORMAT " (%" G_GUINT64_FORMAT ")", GST_TIME_ARGS (pts), pts); } return GST_FLOW_OK; not_negotiated: { pw_thread_loop_unlock (pwsrc->stream->core->loop); return GST_FLOW_NOT_NEGOTIATED; } streaming_eos: { pw_thread_loop_unlock (pwsrc->stream->core->loop); return GST_FLOW_EOS; } streaming_error: { pw_thread_loop_unlock (pwsrc->stream->core->loop); return GST_FLOW_ERROR; } streaming_stopped: { pw_thread_loop_unlock (pwsrc->stream->core->loop); return GST_FLOW_FLUSHING; } } static gboolean gst_pipewire_src_start (GstBaseSrc * basesrc) { return TRUE; } static gboolean gst_pipewire_src_stop (GstBaseSrc * basesrc) { GstPipeWireSrc *pwsrc; pwsrc = GST_PIPEWIRE_SRC (basesrc); pw_thread_loop_lock (pwsrc->stream->core->loop); pwsrc->eos = false; gst_buffer_replace (&pwsrc->last_buffer, NULL); gst_caps_replace(&pwsrc->caps, NULL); gst_caps_replace(&pwsrc->possible_caps, NULL); pwsrc->transform_value = UINT32_MAX; pw_thread_loop_unlock (pwsrc->stream->core->loop); return TRUE; } static const struct pw_stream_events stream_events = { PW_VERSION_STREAM_EVENTS, .state_changed = on_state_changed, .param_changed = on_param_changed, .add_buffer = on_add_buffer, .remove_buffer = on_remove_buffer, .process = on_process, }; static gboolean gst_pipewire_src_send_event (GstElement * elem, GstEvent * event) { GstPipeWireSrc *this = GST_PIPEWIRE_SRC_CAST (elem); gboolean ret; switch (GST_EVENT_TYPE (event)) { case GST_EVENT_EOS: GST_DEBUG_OBJECT (this, "got EOS"); pw_thread_loop_lock (this->stream->core->loop); this->eos = true; pw_thread_loop_signal (this->stream->core->loop, FALSE); pw_thread_loop_unlock (this->stream->core->loop); ret = TRUE; break; default: ret = GST_ELEMENT_CLASS (parent_class)->send_event (elem, event); break; } return ret; } static GstStateChangeReturn gst_pipewire_src_change_state (GstElement * element, GstStateChange transition) { GstStateChangeReturn ret; GstPipeWireSrc *this = GST_PIPEWIRE_SRC_CAST (element); switch (transition) { case GST_STATE_CHANGE_NULL_TO_READY: if (!gst_pipewire_stream_open (this->stream, &stream_events)) goto open_failed; break; case GST_STATE_CHANGE_READY_TO_PAUSED: break; case GST_STATE_CHANGE_PAUSED_TO_PLAYING: /* uncork and start recording */ pw_thread_loop_lock (this->stream->core->loop); pw_stream_set_active (this->stream->pwstream, true); pw_thread_loop_unlock (this->stream->core->loop); break; case GST_STATE_CHANGE_PLAYING_TO_PAUSED: /* stop recording ASAP by corking */ pw_thread_loop_lock (this->stream->core->loop); pw_stream_set_active (this->stream->pwstream, false); pw_thread_loop_unlock (this->stream->core->loop); break; default: break; } ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); switch (transition) { case GST_STATE_CHANGE_READY_TO_PAUSED: if (wait_started (this) == PW_STREAM_STATE_ERROR) goto open_failed; pw_thread_loop_lock (this->stream->core->loop); /* the initial stream state is active, which is needed for linking and * negotiation to happen and the bufferpool to be set up. We don't know * if we'll go to playing, so we deactivate the stream until that * transition happens. This is janky, but because of how bins propagate * state changes one transition at a time, there may not be a better way * to do this. */ pw_stream_set_active (this->stream->pwstream, false); pw_thread_loop_unlock (this->stream->core->loop); if (gst_base_src_is_live (GST_BASE_SRC (element))) ret = GST_STATE_CHANGE_NO_PREROLL; break; case GST_STATE_CHANGE_PLAYING_TO_PAUSED: break; case GST_STATE_CHANGE_PAUSED_TO_READY: pw_thread_loop_lock (this->stream->core->loop); this->negotiated = FALSE; pw_thread_loop_unlock (this->stream->core->loop); break; case GST_STATE_CHANGE_READY_TO_NULL: gst_pipewire_stream_close (this->stream); break; default: break; } return ret; /* ERRORS */ open_failed: { return GST_STATE_CHANGE_FAILURE; } } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/gst/gstpipewiresrc.h000066400000000000000000000041201511204443500253020ustar00rootroot00000000000000/* GStreamer */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef __GST_PIPEWIRE_SRC_H__ #define __GST_PIPEWIRE_SRC_H__ #include "config.h" #include "gstpipewirestream.h" #include #include #include #include #include #include #include G_BEGIN_DECLS #define GST_TYPE_PIPEWIRE_SRC (gst_pipewire_src_get_type()) #define GST_PIPEWIRE_SRC_CAST(obj) ((GstPipeWireSrc *) (obj)) G_DECLARE_FINAL_TYPE (GstPipeWireSrc, gst_pipewire_src, GST, PIPEWIRE_SRC, GstPushSrc) /** * GstPipeWireSrcOnDisconnect: * @GST_PIPEWIRE_SRC_ON_DISCONNECT_EOS: send EoS downstream * @GST_PIPEWIRE_SRC_ON_DISCONNECT_ERROR: raise pipeline error * @GST_PIPEWIRE_SRC_ON_DISCONNECT_NONE: no action * * Different actions on disconnect. */ typedef enum { GST_PIPEWIRE_SRC_ON_DISCONNECT_NONE, GST_PIPEWIRE_SRC_ON_DISCONNECT_EOS, GST_PIPEWIRE_SRC_ON_DISCONNECT_ERROR, } GstPipeWireSrcOnDisconnect; #define GST_TYPE_PIPEWIRE_SRC_ON_DISCONNECT (gst_pipewire_src_on_disconnect_get_type ()) /** * GstPipeWireSrc: * * Opaque data structure. */ struct _GstPipeWireSrc { GstPushSrc element; GstPipeWireStream *stream; /*< private >*/ gint n_buffers; gint use_bufferpool; gint min_buffers; gint max_buffers; gboolean resend_last; gint keepalive_time; gboolean autoconnect; GstCaps *caps; GstCaps *possible_caps; enum spa_media_type media_type; gboolean is_rawvideo; GstVideoInfo video_info; #ifdef HAVE_GSTREAMER_DMA_DRM GstVideoInfoDmaDrm drm_info; #endif gboolean negotiated; gboolean flushing; gboolean started; gboolean eos; gboolean flushing_on_remove_buffer; gboolean is_live; int64_t delay; GstClockTime min_latency; GstClockTime max_latency; GstBuffer *last_buffer; enum spa_meta_videotransform_value transform_value; GstPipeWireSrcOnDisconnect on_disconnect; }; GType gst_pipewire_src_on_stream_disconnect_get_type (void); G_END_DECLS #endif /* __GST_PIPEWIRE_SRC_H__ */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/gst/gstpipewirestream.c000066400000000000000000000112341511204443500260050ustar00rootroot00000000000000/* GStreamer */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-FileCopyrightText: Copyright © 2024 Collabora Ltd. */ /* SPDX-License-Identifier: MIT */ #include "gstpipewirestream.h" #include "gstpipewirepool.h" #include "gstpipewireclock.h" GST_DEBUG_CATEGORY_STATIC (pipewire_stream_debug); #define GST_CAT_DEFAULT pipewire_stream_debug G_DEFINE_TYPE (GstPipeWireStream, gst_pipewire_stream, GST_TYPE_OBJECT) static void gst_pipewire_stream_init (GstPipeWireStream * self) { self->fd = -1; self->client_name = g_strdup (pw_get_client_name()); self->pool = gst_pipewire_pool_new (self); spa_dll_init(&self->dll); } static void gst_pipewire_stream_finalize (GObject * object) { GstPipeWireStream * self = GST_PIPEWIRE_STREAM (object); g_clear_object (&self->pool); g_free (self->path); g_free (self->target_object); g_free (self->client_name); gst_clear_structure (&self->client_properties); gst_clear_structure (&self->stream_properties); G_OBJECT_CLASS(gst_pipewire_stream_parent_class)->finalize (object); } void gst_pipewire_stream_class_init (GstPipeWireStreamClass * klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); gobject_class->finalize = gst_pipewire_stream_finalize; GST_DEBUG_CATEGORY_INIT (pipewire_stream_debug, "pipewirestream", 0, "PipeWire Stream"); } GstPipeWireStream * gst_pipewire_stream_new (GstElement * element) { GstPipeWireStream *stream; stream = g_object_new (GST_TYPE_PIPEWIRE_STREAM, NULL); stream->element = element; return stream; } static gboolean copy_properties (GQuark field_id, const GValue *value, gpointer user_data) { struct pw_properties *properties = user_data; GValue dst = { 0 }; if (g_value_type_transformable (G_VALUE_TYPE(value), G_TYPE_STRING)) { g_value_init (&dst, G_TYPE_STRING); if (g_value_transform (value, &dst)) { pw_properties_set (properties, g_quark_to_string (field_id), g_value_get_string (&dst)); } g_value_unset (&dst); } return TRUE; } gboolean gst_pipewire_stream_open (GstPipeWireStream * self, const struct pw_stream_events * pwstream_events) { struct pw_properties *props; g_return_val_if_fail (self->core == NULL, FALSE); GST_DEBUG_OBJECT (self, "open"); /* acquire the core */ self->core = gst_pipewire_core_get (self->fd); if (self->core == NULL) goto connect_error; pw_thread_loop_lock (self->core->loop); /* update the client properties */ if (self->client_properties) { props = pw_properties_new (NULL, NULL); gst_structure_foreach (self->client_properties, copy_properties, props); pw_core_update_properties (self->core->core, &props->dict); pw_properties_free (props); } /* create stream */ props = pw_properties_new (NULL, NULL); if (self->client_name) { pw_properties_set (props, PW_KEY_NODE_NAME, self->client_name); pw_properties_set (props, PW_KEY_NODE_DESCRIPTION, self->client_name); } if (self->stream_properties) { gst_structure_foreach (self->stream_properties, copy_properties, props); } if ((self->pwstream = pw_stream_new (self->core->core, self->client_name, props)) == NULL) goto no_stream; pw_stream_add_listener(self->pwstream, &self->pwstream_listener, pwstream_events, self->element); /* create clock */ self->clock = gst_pipewire_clock_new (self, 0); pw_thread_loop_unlock (self->core->loop); return TRUE; /* ERRORS */ connect_error: { GST_ELEMENT_ERROR (self->element, RESOURCE, FAILED, ("Failed to connect"), (NULL)); return FALSE; } no_stream: { GST_ELEMENT_ERROR (self->element, RESOURCE, FAILED, ("can't create stream"), (NULL)); pw_thread_loop_unlock (self->core->loop); return FALSE; } } void gst_pipewire_stream_close (GstPipeWireStream * self) { GST_DEBUG_OBJECT (self, "close"); /* destroy the clock */ gst_element_post_message (GST_ELEMENT (self->element), gst_message_new_clock_lost (GST_OBJECT_CAST (self->element), self->clock)); g_weak_ref_set (&GST_PIPEWIRE_CLOCK (self->clock)->stream, NULL); g_clear_object (&self->clock); /* destroy the pw stream */ pw_thread_loop_lock (self->core->loop); if (self->pwstream) { /* Do not use g_clear_pointer() here as pw_stream_destroy() may chain up to * code requiring the pointer to still be around */ pw_stream_destroy (self->pwstream); self->pwstream = NULL; } pw_thread_loop_unlock (self->core->loop); /* release the core */ g_clear_pointer (&self->core, gst_pipewire_core_release); } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/gst/gstpipewirestream.h000066400000000000000000000027741511204443500260230ustar00rootroot00000000000000/* GStreamer */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-FileCopyrightText: Copyright © 2024 Collabora Ltd. */ /* SPDX-License-Identifier: MIT */ #ifndef __GST_PIPEWIRE_STREAM_H__ #define __GST_PIPEWIRE_STREAM_H__ #include "config.h" #include "gstpipewirecore.h" #include #include #include G_BEGIN_DECLS typedef struct _GstPipeWirePool GstPipeWirePool; #define GST_TYPE_PIPEWIRE_STREAM (gst_pipewire_stream_get_type()) G_DECLARE_FINAL_TYPE(GstPipeWireStream, gst_pipewire_stream, GST, PIPEWIRE_STREAM, GstObject) struct _GstPipeWireStream { GstObject parent; /* relatives */ GstElement *element; GstPipeWireCore *core; GstPipeWirePool *pool; GstClock *clock; guint64 position; guint64 buf_duration; struct spa_dll dll; double err_avg, err_var, err_wdw; guint64 last_ts; guint64 base_buffer_ts; guint64 base_ts; /* the actual pw stream */ struct pw_stream *pwstream; struct spa_hook pwstream_listener; struct spa_io_position *io_position; /* common properties */ int fd; gchar *path; gchar *target_object; gchar *client_name; GstStructure *client_properties; GstStructure *stream_properties; }; GstPipeWireStream * gst_pipewire_stream_new (GstElement * element); gboolean gst_pipewire_stream_open (GstPipeWireStream * self, const struct pw_stream_events * pwstream_events); void gst_pipewire_stream_close (GstPipeWireStream * self); G_END_DECLS #endif /* __GST_PIPEWIRE_STREAM_H__ */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/gst/meson.build000066400000000000000000000015221511204443500242240ustar00rootroot00000000000000pipewire_gst_sources = [ 'gstpipewire.c', 'gstpipewirecore.c', 'gstpipewireclock.c', 'gstpipewireformat.c', 'gstpipewirepool.c', 'gstpipewiresink.c', 'gstpipewiresrc.c', 'gstpipewirestream.c', ] if get_option('gstreamer-device-provider').allowed() pipewire_gst_sources += [ 'gstpipewiredeviceprovider.c' ] endif pipewire_gst_headers = [ 'gstpipewireclock.h', 'gstpipewirecore.h', 'gstpipewiredeviceprovider.h', 'gstpipewireformat.h', 'gstpipewirepool.h', 'gstpipewiresink.h', 'gstpipewiresrc.h', 'gstpipewirestream.h', ] pipewire_gst = shared_library('gstpipewire', pipewire_gst_sources, include_directories : [ configinc ], dependencies : [ spa_dep, gst_dep, pipewire_dep, mathlib ], install : true, install_dir : '@0@/gstreamer-1.0'.format(get_option('libdir')), ) plugins = [pipewire_gst] pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/meson.build000066400000000000000000000003031511204443500234230ustar00rootroot00000000000000 subdir('pipewire') subdir('daemon') subdir('tools') subdir('modules') subdir('examples') if get_option('tests').allowed() subdir('tests') endif if gst_dep.length() != 0 subdir('gst') endif pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/modules/000077500000000000000000000000001511204443500227355ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/modules/flatpak-utils.h000066400000000000000000000070571511204443500256770ustar00rootroot00000000000000/* PipeWire */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef FLATPAK_UTILS_H #define FLATPAK_UTILS_H #include "config.h" #include #include #include #include #include #ifdef HAVE_SYS_VFS_H #include #endif #ifdef HAVE_GLIB2 #include #endif #include #include #include static int pw_check_flatpak_parse_metadata(const char *buf, size_t size, char **app_id, char **instance_id, char **devices) { #ifdef HAVE_GLIB2 /* * See flatpak-metadata(5) * * The .flatpak-info file is in GLib key_file .ini format. */ g_autoptr(GKeyFile) metadata = NULL; char *s; metadata = g_key_file_new(); if (!g_key_file_load_from_data(metadata, buf, size, G_KEY_FILE_NONE, NULL)) return -EINVAL; if (app_id) { s = g_key_file_get_value(metadata, "Application", "name", NULL); *app_id = s ? strdup(s) : NULL; g_free(s); } if (devices) { s = g_key_file_get_value(metadata, "Context", "devices", NULL); *devices = s ? strdup(s) : NULL; g_free(s); } if (instance_id) { s = g_key_file_get_value(metadata, "Instance", "instance-id", NULL); *instance_id = s ? strdup(s) : NULL; g_free(s); } return 0; #else return -ENOTSUP; #endif } static int pw_check_flatpak(pid_t pid, char **app_id, char **instance_id, char **devices) { #if defined(__linux__) char root_path[2048]; struct stat stat_buf; int res; if (app_id) *app_id = NULL; if (instance_id) *instance_id = NULL; if (devices) *devices = NULL; snprintf(root_path, sizeof(root_path), "/proc/%d/root", (int)pid); spa_autoclose int root_fd = openat(AT_FDCWD, root_path, O_RDONLY | O_NONBLOCK | O_DIRECTORY | O_CLOEXEC | O_NOCTTY); if (root_fd < 0) { res = -errno; pw_log_info("failed to open \"%s\": %s", root_path, spa_strerror(res)); if (res == -EACCES) { /* If we can't access the root filesystem, consider not sandboxed. * This should not happen but for now it is a workaround for selinux * where we can't access the gnome-shell root when it connects for * screen sharing. */ return 0; } /* Not able to open the root dir shouldn't happen. Probably the app died and * we're failing due to /proc/$pid not existing. In that case fail instead * of treating this as privileged. */ return res; } spa_autoclose int info_fd = openat(root_fd, ".flatpak-info", O_RDONLY | O_CLOEXEC | O_NOCTTY); if (info_fd < 0) { if (errno == ENOENT) { pw_log_debug("no .flatpak-info, client on the host"); /* No file => on the host */ return 0; } res = -errno; pw_log_error("error opening .flatpak-info: %m"); return res; } if (fstat (info_fd, &stat_buf) != 0 || !S_ISREG (stat_buf.st_mode)) { /* Some weird fd => failure, assume sandboxed */ pw_log_error("error fstat .flatpak-info: %m"); } else if (app_id || instance_id || devices) { /* Parse the application ID if needed */ const size_t size = stat_buf.st_size; if (size > 0) { void *buf = mmap(NULL, size, PROT_READ, MAP_PRIVATE, info_fd, 0); if (buf != MAP_FAILED) { res = pw_check_flatpak_parse_metadata(buf, size, app_id, instance_id, devices); munmap(buf, size); } else { res = -errno; } } else { res = -EINVAL; } if (res == -EINVAL) pw_log_error("PID %d .flatpak-info file is malformed", (int)pid); else if (res < 0) pw_log_error("PID %d .flatpak-info parsing failed: %s", (int)pid, spa_strerror(res)); } return 1; #else return 0; #endif } #endif /* FLATPAK_UTILS_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/modules/meson.build000066400000000000000000000651001511204443500251010ustar00rootroot00000000000000subdir('module-rt') # The list of "main" source files for modules, the ones that have the # doxygen documentation module_sources = [ 'module-access.c', 'module-adapter.c', 'module-avb.c', 'module-client-device.c', 'module-client-node.c', 'module-combine-stream.c', 'module-echo-cancel.c', 'module-example-filter.c', 'module-example-sink.c', 'module-example-source.c', 'module-fallback-sink.c', 'module-ffado-driver.c', 'module-filter-chain.c', 'module-jack-tunnel.c', 'module-jackdbus-detect.c', 'module-link-factory.c', 'module-loopback.c', 'module-metadata.c', 'module-netjack2-driver.c', 'module-netjack2-manager.c', 'module-parametric-equalizer.c', 'module-pipe-tunnel.c', 'module-portal.c', 'module-profiler.c', 'module-protocol-native.c', 'module-protocol-pulse.c', 'module-protocol-simple.c', 'module-pulse-tunnel.c', 'module-rt.c', 'module-raop-discover.c', 'module-raop-sink.c', 'module-rtp-sap.c', 'module-rtp-session.c', 'module-rtp-source.c', 'module-rtp-sink.c', 'module-spa-node.c', 'module-spa-node-factory.c', 'module-spa-device.c', 'module-spa-device-factory.c', 'module-snapcast-discover.c', 'module-vban-recv.c', 'module-vban-send.c', 'module-session-manager.c', 'module-zeroconf-discover.c', 'module-roc-source.c', 'module-roc-sink.c', 'module-x11-bell.c', ] pipewire_module_access_deps = [spa_dep, mathlib, dl_lib, pipewire_dep] if flatpak_support pipewire_module_access_deps += glib2_dep endif pipewire_module_access = shared_library('pipewire-module-access', [ 'module-access.c' ], include_directories : [configinc], install : true, install_dir : modules_install_dir, install_rpath: modules_install_dir, dependencies : pipewire_module_access_deps ) pipewire_module_loopback = shared_library('pipewire-module-loopback', [ 'module-loopback.c' ], include_directories : [configinc], install : true, install_dir : modules_install_dir, install_rpath: modules_install_dir, dependencies : [spa_dep, mathlib, dl_lib, pipewire_dep], ) pipewire_module_filter_chain = shared_library('pipewire-module-filter-chain', [ 'module-filter-chain.c' ], include_directories : [configinc], install : true, install_dir : modules_install_dir, install_rpath: modules_install_dir, dependencies : [spa_dep, mathlib, dl_lib, pipewire_dep], ) pipewire_module_combine_stream = shared_library('pipewire-module-combine-stream', [ 'module-combine-stream.c' ], include_directories : [configinc], install : true, install_dir : modules_install_dir, install_rpath: modules_install_dir, dependencies : [spa_dep, dl_lib, pipewire_dep], ) pipewire_module_echo_cancel = shared_library('pipewire-module-echo-cancel', [ 'module-echo-cancel.c' ], include_directories : [configinc], install : true, install_dir : modules_install_dir, install_rpath: modules_install_dir, dependencies : [mathlib, dl_lib, pipewire_dep, plugin_dependencies], ) build_module_jack_tunnel = jack_dep.found() if build_module_jack_tunnel pipewire_module_jack_tunnel = shared_library('pipewire-module-jack-tunnel', [ 'module-jack-tunnel.c' ], include_directories : [configinc], install : true, install_dir : modules_install_dir, install_rpath: modules_install_dir, dependencies : [mathlib, dl_lib, pipewire_dep], ) build_module_jackdbus_detect = dbus_dep.found() if build_module_jackdbus_detect pipewire_module_jack_tunnel = shared_library('pipewire-module-jackdbus-detect', [ 'module-jackdbus-detect.c' ], include_directories : [configinc], install : true, install_dir : modules_install_dir, install_rpath: modules_install_dir, dependencies : [mathlib, dl_lib, pipewire_dep, dbus_dep], ) endif endif summary({'jack-tunnel': build_module_jack_tunnel}, bool_yn: true, section: 'Optional Modules') build_module_ffado_driver = libffado_dep.found() if build_module_ffado_driver pipewire_module_jack_tunnel = shared_library('pipewire-module-ffado-driver', [ 'module-ffado-driver.c' ], include_directories : [configinc], install : true, install_dir : modules_install_dir, install_rpath: modules_install_dir, dependencies : [mathlib, dl_lib, pipewire_dep, libffado_dep], ) endif summary({'ffado-driver': build_module_ffado_driver}, bool_yn: true, section: 'Optional Modules') opus_custom_h = cc.has_header('opus/opus_custom.h', dependencies: opus_dep) opus_custom_lib = cc.has_function('opus_custom_encoder_ctl', dependencies: opus_dep) # One would imagine that opus_dep is a requirement but for some reason it's not, so we need to manually check that if opus_dep.found() and opus_custom_h and opus_custom_lib opus_custom_dep = declare_dependency(compile_args: ['-DHAVE_OPUS_CUSTOM'], dependencies: opus_dep) else opus_custom_dep = dependency('', required: false) endif summary({'Opus with custom modes for NetJack2': opus_custom_dep}, bool_yn: true, section: 'Streaming between daemons') pipewire_module_netjack2_driver = shared_library('pipewire-module-netjack2-driver', [ 'module-netjack2-driver.c' ], include_directories : [configinc], install : true, install_dir : modules_install_dir, install_rpath: modules_install_dir, dependencies : [spa_dep, mathlib, dl_lib, pipewire_dep, opus_custom_dep], ) pipewire_module_netjack2_manager = shared_library('pipewire-module-netjack2-manager', [ 'module-netjack2-manager.c' ], include_directories : [configinc], install : true, install_dir : modules_install_dir, install_rpath: modules_install_dir, dependencies : [spa_dep, mathlib, dl_lib, pipewire_dep, opus_custom_dep], ) pipewire_module_parametric_equalizer = shared_library('pipewire-module-parametric-equalizer', [ 'module-parametric-equalizer.c' ], include_directories : [configinc], install : true, install_dir : modules_install_dir, install_rpath: modules_install_dir, dependencies : [spa_dep, mathlib, dl_lib, pipewire_dep], ) pipewire_module_profiler = shared_library('pipewire-module-profiler', [ 'module-profiler.c', 'module-profiler/protocol-native.c', ], include_directories : [configinc], install : true, install_dir : modules_install_dir, install_rpath: modules_install_dir, dependencies : [spa_dep, mathlib, dl_lib, pipewire_dep], ) pipewire_module_rt = shared_library('pipewire-module-rt', [ 'module-rt.c' ], include_directories : [configinc], install : true, install_dir : modules_install_dir, install_rpath: modules_install_dir, dependencies : [dbus_dep, mathlib, dl_lib, pipewire_dep], ) build_module_rtkit = dbus_dep.found() and (get_option('legacy-rtkit') == true) if build_module_rtkit pipewire_module_rtkit = shared_library('pipewire-module-rtkit', [ 'module-rt.c' ], include_directories : [configinc], install : true, install_dir : modules_install_dir, install_rpath: modules_install_dir, dependencies : [dbus_dep, mathlib, dl_lib, pipewire_dep], ) endif summary({'rt': '@0@ RTKit'.format(build_module_rtkit ? 'with' : 'without')}, section: 'Optional Modules') pipewire_module_spa_node = shared_library('pipewire-module-spa-node', [ 'module-spa-node.c', 'spa/spa-node.c' ], include_directories : [configinc], install : true, install_dir : modules_install_dir, install_rpath: modules_install_dir, dependencies : [spa_dep, mathlib, dl_lib, pipewire_dep], ) pipewire_module_spa_node_factory = shared_library('pipewire-module-spa-node-factory', [ 'module-spa-node-factory.c', 'spa/spa-node.c' ], include_directories : [configinc], install : true, install_dir : modules_install_dir, install_rpath: modules_install_dir, dependencies : [spa_dep, mathlib, dl_lib, pipewire_dep], ) pipewire_module_spa_device = shared_library('pipewire-module-spa-device', [ 'module-spa-device.c', 'spa/spa-device.c' ], include_directories : [configinc], install : true, install_dir : modules_install_dir, install_rpath: modules_install_dir, dependencies : [spa_dep, mathlib, dl_lib, pipewire_dep], ) pipewire_module_spa_device_factory = shared_library('pipewire-module-spa-device-factory', [ 'module-spa-device-factory.c', 'spa/spa-device.c' ], include_directories : [configinc], install : true, install_dir : modules_install_dir, install_rpath: modules_install_dir, dependencies : [spa_dep, mathlib, dl_lib, pipewire_dep], ) build_module_portal = dbus_dep.found() if build_module_portal pipewire_module_portal = shared_library('pipewire-module-portal', [ 'module-portal.c' ], include_directories : [configinc], install : true, install_dir : modules_install_dir, install_rpath: modules_install_dir, dependencies : [dbus_dep, mathlib, dl_lib, pipewire_dep], ) endif summary({'portal': build_module_portal}, bool_yn: true, section: 'Optional Modules') pipewire_module_client_device = shared_library('pipewire-module-client-device', [ 'module-client-device.c', 'module-client-device/resource-device.c', 'module-client-device/proxy-device.c', 'module-client-device/protocol-native.c', ], include_directories : [configinc], install : true, install_dir : modules_install_dir, install_rpath: modules_install_dir, dependencies : [spa_dep, mathlib, dl_lib, pipewire_dep], ) pipewire_module_link_factory = shared_library('pipewire-module-link-factory', [ 'module-link-factory.c' ], include_directories : [configinc], install : true, install_dir : modules_install_dir, install_rpath: modules_install_dir, dependencies : [spa_dep, mathlib, dl_lib, pipewire_dep], ) pipewire_module_protocol_deps = [mathlib, dl_lib, pipewire_dep] if systemd_dep.found() pipewire_module_protocol_deps += systemd_dep endif if selinux_dep.found() pipewire_module_protocol_deps += selinux_dep endif pipewire_module_protocol_native = shared_library('pipewire-module-protocol-native', [ 'module-protocol-native.c', 'module-protocol-native/local-socket.c', 'module-protocol-native/portal-screencast.c', 'module-protocol-native/protocol-native.c', 'module-protocol-native/protocol-footer.c', 'module-protocol-native/security-context.c', 'module-protocol-native/connection.c' ], include_directories : [configinc], install : true, install_dir : modules_install_dir, install_rpath: modules_install_dir, dependencies : pipewire_module_protocol_deps, ) pipewire_module_protocol_pulse_deps = pipewire_module_protocol_deps pipewire_module_protocol_pulse_sources = [ 'module-protocol-pulse.c', 'module-protocol-pulse/client.c', 'module-protocol-pulse/collect.c', 'module-protocol-pulse/cmd.c', 'module-protocol-pulse/extension.c', 'module-protocol-pulse/format.c', 'module-protocol-pulse/manager.c', 'module-protocol-pulse/message.c', 'module-protocol-pulse/message-handler.c', 'module-protocol-pulse/module.c', 'module-protocol-pulse/operation.c', 'module-protocol-pulse/pending-sample.c', 'module-protocol-pulse/pulse-server.c', 'module-protocol-pulse/quirks.c', 'module-protocol-pulse/remap.c', 'module-protocol-pulse/reply.c', 'module-protocol-pulse/sample.c', 'module-protocol-pulse/sample-play.c', 'module-protocol-pulse/server.c', 'module-protocol-pulse/stream.c', 'module-protocol-pulse/utils.c', 'module-protocol-pulse/volume.c', 'module-protocol-pulse/modules/module-alsa-sink.c', 'module-protocol-pulse/modules/module-alsa-source.c', 'module-protocol-pulse/modules/module-always-sink.c', 'module-protocol-pulse/modules/module-combine-sink.c', 'module-protocol-pulse/modules/module-device-manager.c', 'module-protocol-pulse/modules/module-device-restore.c', 'module-protocol-pulse/modules/module-echo-cancel.c', 'module-protocol-pulse/modules/module-jackdbus-detect.c', 'module-protocol-pulse/modules/module-ladspa-sink.c', 'module-protocol-pulse/modules/module-ladspa-source.c', 'module-protocol-pulse/modules/module-loopback.c', 'module-protocol-pulse/modules/module-native-protocol-tcp.c', 'module-protocol-pulse/modules/module-null-sink.c', 'module-protocol-pulse/modules/module-pipe-source.c', 'module-protocol-pulse/modules/module-pipe-sink.c', 'module-protocol-pulse/modules/module-raop-discover.c', 'module-protocol-pulse/modules/module-remap-sink.c', 'module-protocol-pulse/modules/module-remap-source.c', 'module-protocol-pulse/modules/module-roc-sink.c', 'module-protocol-pulse/modules/module-roc-sink-input.c', 'module-protocol-pulse/modules/module-roc-source.c', 'module-protocol-pulse/modules/module-rtp-recv.c', 'module-protocol-pulse/modules/module-rtp-send.c', 'module-protocol-pulse/modules/module-simple-protocol-tcp.c', 'module-protocol-pulse/modules/module-stream-restore.c', 'module-protocol-pulse/modules/module-switch-on-connect.c', 'module-protocol-pulse/modules/module-tunnel-sink.c', 'module-protocol-pulse/modules/module-tunnel-source.c', 'module-protocol-pulse/modules/module-virtual-sink.c', 'module-protocol-pulse/modules/module-virtual-source.c', 'module-protocol-pulse/modules/module-x11-bell.c', 'module-protocol-pulse/modules/module-zeroconf-discover.c', ] if snap_dep.found() and glib2_snap_dep.found() and gio2_snap_dep.found() and apparmor_snap_dep.found() pipewire_module_protocol_pulse_sources += [ 'module-protocol-pulse/snap-policy.c', ] pipewire_module_protocol_pulse_deps += snap_deps endif if dbus_dep.found() pipewire_module_protocol_pulse_sources += [ 'module-protocol-pulse/dbus-name.c', ] pipewire_module_protocol_pulse_deps += dbus_dep endif if avahi_dep.found() pipewire_module_protocol_pulse_sources += [ 'module-protocol-pulse/modules/module-zeroconf-publish.c', 'module-zeroconf-discover/avahi-poll.c', ] pipewire_module_protocol_pulse_deps += avahi_dep cdata.set('HAVE_AVAHI', true) endif if gsettings_gio_dep.found() pipewire_module_protocol_pulse_sources += [ 'module-protocol-pulse/modules/module-gsettings.c', ] pipewire_module_protocol_pulse_deps += gsettings_gio_dep cdata.set('HAVE_GIO', true) if get_option('gsettings-pulse-schema').enabled() install_data(['module-protocol-pulse/modules/org.freedesktop.pulseaudio.gschema.xml'], install_dir: pipewire_datadir / 'glib-2.0' / 'schemas' ) gnome = import('gnome') gnome.post_install( glib_compile_schemas: true ) endif endif if flatpak_support pipewire_module_protocol_pulse_deps += glib2_dep endif pipewire_module_protocol_pulse = shared_library('pipewire-module-protocol-pulse', pipewire_module_protocol_pulse_sources, include_directories : [configinc], install : true, install_dir : modules_install_dir, install_rpath: modules_install_dir, dependencies : pipewire_module_protocol_pulse_deps, ) build_module_pulse_tunnel = pulseaudio_dep.found() if build_module_pulse_tunnel pipewire_module_pulse_tunnel = shared_library('pipewire-module-pulse-tunnel', [ 'module-pulse-tunnel.c', 'module-protocol-pulse/format.c' ], include_directories : [configinc], install : true, install_dir : modules_install_dir, install_rpath: modules_install_dir, dependencies : [mathlib, dl_lib, pipewire_dep, pulseaudio_dep], ) endif summary({'pulse-tunnel': build_module_pulse_tunnel}, bool_yn: true, section: 'Optional Modules') pipewire_module_pipe_tunnel = shared_library('pipewire-module-pipe-tunnel', [ 'module-pipe-tunnel.c' ], include_directories : [configinc], install : true, install_dir : modules_install_dir, install_rpath: modules_install_dir, dependencies : [mathlib, dl_lib, pipewire_dep], ) pipewire_module_protocol_simple = shared_library('pipewire-module-protocol-simple', [ 'module-protocol-simple.c' ], include_directories : [configinc], install : true, install_dir : modules_install_dir, install_rpath: modules_install_dir, dependencies : pipewire_module_protocol_deps, ) pipewire_module_example_filter = shared_library('pipewire-module-example-filter', [ 'module-example-filter.c' ], include_directories : [configinc], install : false, install_dir : modules_install_dir, install_rpath: modules_install_dir, dependencies : [spa_dep, mathlib, dl_lib, pipewire_dep], ) pipewire_module_example_sink = shared_library('pipewire-module-example-sink', [ 'module-example-sink.c' ], include_directories : [configinc], install : false, install_dir : modules_install_dir, install_rpath: modules_install_dir, dependencies : [spa_dep, mathlib, dl_lib, pipewire_dep], ) pipewire_module_example_sink = shared_library('pipewire-module-example-source', [ 'module-example-source.c' ], include_directories : [configinc], install : false, install_dir : modules_install_dir, install_rpath: modules_install_dir, dependencies : [spa_dep, mathlib, dl_lib, pipewire_dep], ) pipewire_module_client_node = shared_library('pipewire-module-client-node', [ 'module-client-node.c', 'module-client-node/remote-node.c', 'module-client-node/client-node.c', 'module-client-node/protocol-native.c', 'spa/spa-node.c', ], include_directories : [configinc], link_with : pipewire_module_protocol_native, install : true, install_dir : modules_install_dir, install_rpath: modules_install_dir, dependencies : [spa_dep, mathlib, dl_lib, pipewire_dep], ) pipewire_module_metadata = shared_library('pipewire-module-metadata', [ 'module-metadata.c', 'module-metadata/proxy-metadata.c', 'module-metadata/metadata.c', 'module-metadata/protocol-native.c'], include_directories : [configinc], link_with : pipewire_module_protocol_native, install : true, install_dir : modules_install_dir, install_rpath: modules_install_dir, dependencies : [spa_dep, mathlib, dl_lib, pipewire_dep], ) test('pw-test-protocol-native', executable('pw-test-protocol-native', [ 'module-protocol-native/test-connection.c', 'module-protocol-native/connection.c' ], c_args : libpipewire_c_args, include_directories : [configinc ], dependencies : [spa_dep, pipewire_dep], install : installed_tests_enabled, install_dir : installed_tests_execdir, ), env : [ 'SPA_PLUGIN_DIR=@0@'.format(spa_dep.get_variable('plugindir')), 'PIPEWIRE_CONFIG_DIR=@0@'.format(pipewire_dep.get_variable('confdatadir')), 'PIPEWIRE_MODULE_DIR=@0@'.format(pipewire_dep.get_variable('moduledir')), ] ) if installed_tests_enabled test_conf = configuration_data() test_conf.set('exec', installed_tests_execdir / 'pw-test-protocol-native') configure_file( input: installed_tests_template, output: 'pw-test-protocol-native.test', install_dir: installed_tests_metadir, configuration: test_conf ) endif pipewire_module_adapter = shared_library('pipewire-module-adapter', [ 'module-adapter.c', 'module-adapter/adapter.c', 'spa/spa-node.c' ], include_directories : [configinc], install : true, install_dir : modules_install_dir, install_rpath: modules_install_dir, dependencies : [mathlib, dl_lib, rt_lib, pipewire_dep], ) pipewire_module_session_manager = shared_library('pipewire-module-session-manager', [ 'module-session-manager.c', 'module-session-manager/client-endpoint/client-endpoint.c', 'module-session-manager/client-endpoint/endpoint-stream.c', 'module-session-manager/client-endpoint/endpoint.c', 'module-session-manager/client-session/client-session.c', 'module-session-manager/client-session/endpoint-link.c', 'module-session-manager/client-session/session.c', 'module-session-manager/endpoint-link.c', 'module-session-manager/endpoint-stream.c', 'module-session-manager/endpoint.c', 'module-session-manager/protocol-native.c', 'module-session-manager/proxy-session-manager.c', 'module-session-manager/session.c', ], include_directories : [configinc], install : true, install_dir : modules_install_dir, install_rpath: modules_install_dir, dependencies : [spa_dep, mathlib, dl_lib, pipewire_dep], ) build_module_zeroconf_discover = avahi_dep.found() if build_module_zeroconf_discover pipewire_module_zeroconf_discover = shared_library('pipewire-module-zeroconf-discover', [ 'module-zeroconf-discover.c', 'module-protocol-pulse/format.c', 'module-zeroconf-discover/avahi-poll.c' ], include_directories : [configinc], install : true, install_dir : modules_install_dir, install_rpath: modules_install_dir, dependencies : [mathlib, dl_lib, rt_lib, pipewire_dep, avahi_dep], ) endif summary({'zeroconf-discover': build_module_zeroconf_discover}, bool_yn: true, section: 'Optional Modules') build_module_raop_discover = avahi_dep.found() if build_module_raop_discover pipewire_module_raop_discover = shared_library('pipewire-module-raop-discover', [ 'module-raop-discover.c', 'module-zeroconf-discover/avahi-poll.c' ], include_directories : [configinc], install : true, install_dir : modules_install_dir, install_rpath: modules_install_dir, dependencies : [mathlib, dl_lib, rt_lib, pipewire_dep, avahi_dep], ) endif summary({'raop-discover (needs Avahi)': build_module_raop_discover}, bool_yn: true, section: 'Optional Modules') build_module_snapcast_discover = avahi_dep.found() if build_module_snapcast_discover pipewire_module_snapcast_discover = shared_library('pipewire-module-snapcast-discover', [ 'module-snapcast-discover.c', 'module-zeroconf-discover/avahi-poll.c' ], include_directories : [configinc], install : true, install_dir : modules_install_dir, install_rpath: modules_install_dir, dependencies : [mathlib, dl_lib, rt_lib, pipewire_dep, avahi_dep], ) endif summary({'snapcast-discover (needs Avahi)': build_module_snapcast_discover}, bool_yn: true, section: 'Optional Modules') build_module_raop = openssl_lib.found() if build_module_raop pipewire_module_raop_sink = shared_library('pipewire-module-raop-sink', [ 'module-raop-sink.c', 'module-raop/rtsp-client.c', 'module-rtp/stream.c' ], include_directories : [configinc], install : true, install_dir : modules_install_dir, install_rpath: modules_install_dir, dependencies : [mathlib, dl_lib, rt_lib, pipewire_dep, opus_dep, openssl_lib], ) endif summary({'raop-sink (requires OpenSSL)': build_module_raop}, bool_yn: true, section: 'Optional Modules') roc_dep = dependency('roc', version: '>= 0.4.0', required: get_option('roc')) summary({'ROC': roc_dep.found()}, bool_yn: true, section: 'Streaming between daemons') pipewire_module_rtp_source = shared_library('pipewire-module-rtp-source', [ 'module-rtp-source.c', 'module-rtp/stream.c' ], include_directories : [configinc], install : true, install_dir : modules_install_dir, install_rpath: modules_install_dir, dependencies : [mathlib, dl_lib, rt_lib, pipewire_dep, opus_dep], ) pipewire_module_rtp_sink = shared_library('pipewire-module-rtp-sink', [ 'module-rtp-sink.c', 'module-rtp/stream.c' ], include_directories : [configinc], install : true, install_dir : modules_install_dir, install_rpath: modules_install_dir, dependencies : [mathlib, dl_lib, rt_lib, pipewire_dep, opus_dep], ) build_module_rtp_session = avahi_dep.found() if build_module_rtp_session pipewire_module_rtp_session = shared_library('pipewire-module-rtp-session', [ 'module-rtp/stream.c', 'module-zeroconf-discover/avahi-poll.c', 'module-rtp-session.c' ], include_directories : [configinc], install : true, install_dir : modules_install_dir, install_rpath: modules_install_dir, dependencies : [mathlib, dl_lib, rt_lib, pipewire_dep, avahi_dep, opus_dep], ) endif pipewire_module_rtp_sap = shared_library('pipewire-module-rtp-sap', [ 'module-rtp-sap.c' ], include_directories : [configinc], install : true, install_dir : modules_install_dir, install_rpath: modules_install_dir, dependencies : [mathlib, dl_lib, rt_lib, pipewire_dep], ) pipewire_module_vban_send = shared_library('pipewire-module-vban-send', [ 'module-vban-send.c', 'module-vban/stream.c' ], include_directories : [configinc], install : true, install_dir : modules_install_dir, install_rpath: modules_install_dir, dependencies : [mathlib, dl_lib, rt_lib, pipewire_dep], ) pipewire_module_vban_recv = shared_library('pipewire-module-vban-recv', [ 'module-vban-recv.c', 'module-vban/stream.c' ], include_directories : [configinc], install : true, install_dir : modules_install_dir, install_rpath: modules_install_dir, dependencies : [mathlib, dl_lib, rt_lib, pipewire_dep], ) build_module_roc = roc_dep.found() if build_module_roc pipewire_module_roc_sink = shared_library('pipewire-module-roc-sink', [ 'module-roc-sink.c' ], include_directories : [configinc], install : true, install_dir : modules_install_dir, install_rpath: modules_install_dir, dependencies : [mathlib, dl_lib, rt_lib, pipewire_dep, roc_dep], ) pipewire_module_roc_source = shared_library('pipewire-module-roc-source', [ 'module-roc-source.c' ], include_directories : [configinc], install : true, install_dir : modules_install_dir, install_rpath: modules_install_dir, dependencies : [mathlib, dl_lib, rt_lib, pipewire_dep, roc_dep], ) endif summary({'roc-sink': build_module_roc}, bool_yn: true, section: 'Optional Modules') summary({'roc-source': build_module_roc}, bool_yn: true, section: 'Optional Modules') build_module_x11_bell = x11_dep.found() and canberra_dep.found() if build_module_x11_bell pipewire_module_x11_bell = shared_library('pipewire-module-x11-bell', [ 'module-x11-bell.c' ], include_directories : [configinc], install : true, install_dir : modules_install_dir, install_rpath: modules_install_dir, dependencies : [mathlib, dl_lib, rt_lib, pipewire_dep, x11_dep, xfixes_dep, canberra_dep], ) endif summary({'x11-bell': build_module_x11_bell}, bool_yn: true, section: 'Optional Modules') pipewire_module_fallback_sink = shared_library('pipewire-module-fallback-sink', [ 'module-fallback-sink.c' ], include_directories : [configinc], install : true, install_dir : modules_install_dir, install_rpath: modules_install_dir, dependencies : [mathlib, dl_lib, rt_lib, pipewire_dep], ) build_module_avb = get_option('avb').require(host_machine.system() == 'linux', error_message: 'AVB support is only available on Linux').allowed() if build_module_avb pipewire_module_avb = shared_library('pipewire-module-avb', [ 'module-avb.c', 'module-avb/avb.c', 'module-avb/adp.c', 'module-avb/acmp.c', 'module-avb/aecp.c', 'module-avb/aecp-aem.c', 'module-avb/es-builder.c', 'module-avb/avdecc.c', 'module-avb/maap.c', 'module-avb/mmrp.c', 'module-avb/mrp.c', 'module-avb/msrp.c', 'module-avb/mvrp.c', 'module-avb/srp.c', 'module-avb/stream.c' ], include_directories : [configinc], install : true, install_dir : modules_install_dir, install_rpath: modules_install_dir, dependencies : [mathlib, dl_lib, rt_lib, pipewire_dep], ) endif summary({'avb': build_module_avb}, bool_yn: true, section: 'Optional Modules') pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/modules/module-access.c000066400000000000000000000255221511204443500256330ustar00rootroot00000000000000/* PipeWire */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include "config.h" #include #include #include #include #include #include #include #include #ifdef HAVE_SYS_VFS_H #include #endif #ifdef HAVE_SYS_MOUNT_H #include #endif #include #include #include #include #include #include "flatpak-utils.h" /** \page page_module_access Access * * * The `access` module performs access checks on clients. The access check * is only performed once per client, subsequent checks return the same * resolution. * * Permissions assigned to a client are configured as arguments to this * module, see below. Permission management beyond unrestricted access * is delegated to an external agent, usually the session manager. * * This module sets the \ref PW_KEY_ACCESS as follows: * * - If `access.legacy` module option is not enabled: * The value defined for the socket in `access.socket` module option, or * `"default"` if no value is defined. * * - If `access.legacy` is enabled, the value is: * * - `"flatpak"`: if client is a Flatpak client * - `"unrestricted"`: if \ref PW_KEY_CLIENT_ACCESS client property is set to `"allowed"` * - Value of \ref PW_KEY_CLIENT_ACCESS client property, if set * - `"unrestricted"`: otherwise * * If the resulting \ref PW_KEY_ACCESS value is `"unrestricted"`, this module * will give the client all permissions to access all resources. Otherwise, the * client will be forced to wait until an external actor, such as the session * manager, updates the client permissions. * * For connections from applications running inside Flatpak, and not mediated by * other clients (eg. portal or pipewire-pulse), the * `pipewire.access.portal.app_id` property is to the Flatpak application ID, if * found. In addition, `pipewire.sec.flatpak` is set to `true`. * * ## Module Name * * `libpipewire-module-access` * * ## Module Options * * Options specific to the behavior of this module * * - `access.socket = { "socket-name" = "access-value", ... }`: * * Socket-specific access permissions. Has the default value * `{ "CORENAME-manager": "unrestricted" }` * where `CORENAME` is the name of the PipeWire core, usually `pipewire-0`. * * - `access.legacy = true`: enable backward-compatible access mode. Cannot be * enabled when using socket-based permissions. * * If `access.socket` is not specified, has the default value `true` * otherwise `false`. * * \warning The legacy mode is deprecated. The default value is subject to * change and the legacy mode may be removed in future PipeWire * releases. * * ## General options * * Options with well-known behavior: * * - \ref PW_KEY_ACCESS * - \ref PW_KEY_CLIENT_ACCESS * * ## Config override * * A `module.access.args` config section can be added * to override the module arguments. * *\code{.unparsed} * # ~/.config/pipewire/pipewire.conf.d/my-access-args.conf * * module.access.args = { * access.socket = { * pipewire-0 = "default", * pipewire-0-manager = "unrestricted", * } * } *\endcode * * ## Example configuration * *\code{.unparsed} * context.modules = [ * { name = libpipewire-module-access * args = { * # Use separate socket for session manager applications, * # and pipewire-0 for usual applications. * access.socket = { * pipewire-0 = "default", * pipewire-0-manager = "unrestricted", * } * } * } *] *\endcode * * \see pw_resource_error * \see pw_impl_client_update_permissions */ #define NAME "access" PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); #define PW_LOG_TOPIC_DEFAULT mod_topic #define MODULE_USAGE "( access.socket={ =, ... } ) " \ "( access.legacy=true ) " #define ACCESS_UNRESTRICTED "unrestricted" #define ACCESS_FLATPAK "flatpak" #define ACCESS_DEFAULT "default" static const struct spa_dict_item module_props[] = { { PW_KEY_MODULE_AUTHOR, "Wim Taymans " }, { PW_KEY_MODULE_DESCRIPTION, "Perform access check" }, { PW_KEY_MODULE_USAGE, MODULE_USAGE }, { PW_KEY_MODULE_VERSION, PACKAGE_VERSION }, }; struct impl { struct pw_context *context; struct pw_properties *socket_access; struct spa_hook context_listener; struct spa_hook module_listener; unsigned int legacy:1; }; static void context_check_access(void *data, struct pw_impl_client *client) { struct impl *impl = data; struct pw_permission permissions[1]; struct spa_dict_item items[4]; const struct pw_properties *props; const char *str; const char *access; const char *socket; spa_autofree char *flatpak_app_id = NULL; spa_autofree char *flatpak_instance_id = NULL; int nitems = 0; bool sandbox_flatpak; int pid, res; /* Get client properties */ pid = -EINVAL; socket = NULL; sandbox_flatpak = false; if ((props = pw_impl_client_get_properties(client)) != NULL) { if ((str = pw_properties_get(props, PW_KEY_ACCESS)) != NULL) { pw_log_info("client %p: has already access: '%s'", client, str); return; } pw_properties_fetch_int32(props, PW_KEY_SEC_PID, &pid); socket = pw_properties_get(props, PW_KEY_SEC_SOCKET); } if (pid < 0) { pw_log_info("client %p: no trusted pid found, assuming not sandboxed", client); } else { pw_log_info("client %p has trusted pid %d", client, pid); res = pw_check_flatpak(pid, &flatpak_app_id, &flatpak_instance_id, NULL); if (res != 0) { if (res < 0) pw_log_warn("%p: client %p flatpak check failed: %s", impl, client, spa_strerror(res)); pw_log_info("client %p is from flatpak", client); sandbox_flatpak = true; } } /* Apply rules */ if (!impl->legacy) { if ((str = pw_properties_get(impl->socket_access, socket)) != NULL) access = str; else access = ACCESS_DEFAULT; } else { if (sandbox_flatpak) { access = ACCESS_FLATPAK; } else if ((str = pw_properties_get(props, PW_KEY_CLIENT_ACCESS)) != NULL) { if (spa_streq(str, "allowed")) access = ACCESS_UNRESTRICTED; else access = str; } else { access = ACCESS_UNRESTRICTED; } } /* Handle resolution */ if (sandbox_flatpak) { items[nitems++] = SPA_DICT_ITEM_INIT("pipewire.access.portal.app_id", flatpak_app_id); items[nitems++] = SPA_DICT_ITEM_INIT("pipewire.access.portal.instance_id", flatpak_instance_id); items[nitems++] = SPA_DICT_ITEM_INIT("pipewire.sec.flatpak", "true"); } if (spa_streq(access, ACCESS_UNRESTRICTED)) { pw_log_info("%p: client %p '%s' access granted", impl, client, access); items[nitems++] = SPA_DICT_ITEM_INIT(PW_KEY_ACCESS, access); pw_impl_client_update_properties(client, &SPA_DICT_INIT(items, nitems)); permissions[0] = PW_PERMISSION_INIT(PW_ID_ANY, PW_PERM_ALL); pw_impl_client_update_permissions(client, 1, permissions); } else { pw_log_info("%p: client %p wait for '%s' permissions", impl, client, access); items[nitems++] = SPA_DICT_ITEM_INIT(PW_KEY_ACCESS, access); pw_impl_client_update_properties(client, &SPA_DICT_INIT(items, nitems)); } } static const struct pw_context_events context_events = { PW_VERSION_CONTEXT_EVENTS, .check_access = context_check_access, }; static void module_destroy(void *data) { struct impl *impl = data; if (impl->context) { spa_hook_remove(&impl->context_listener); spa_hook_remove(&impl->module_listener); } pw_properties_free(impl->socket_access); free(impl); } static const struct pw_impl_module_events module_events = { PW_VERSION_IMPL_MODULE_EVENTS, .destroy = module_destroy, }; static const char * get_server_name(const struct spa_dict *props) { const char *name = NULL; name = getenv("PIPEWIRE_CORE"); if (name == NULL && props != NULL) name = spa_dict_lookup(props, PW_KEY_CORE_NAME); if (name == NULL) name = PW_DEFAULT_REMOTE; return name; } static int parse_socket_args(struct impl *impl, const char *str) { struct spa_json it[1]; char socket[PATH_MAX]; const char *val; int len; if (spa_json_begin_object(&it[0], str, strlen(str)) <= 0) return -EINVAL; while ((len = spa_json_object_next(&it[0], socket, sizeof(socket), &val)) > 0) { char value[256]; if (spa_json_parse_stringn(val, len, value, sizeof(value)) <= 0) return -EINVAL; pw_properties_set(impl->socket_access, socket, value); } return 0; } static int parse_args(struct impl *impl, const struct pw_properties *props, const struct pw_properties *args) { const char *str; int res; if ((str = pw_properties_get(args, "access.legacy")) != NULL) { impl->legacy = spa_atob(str); } else if (pw_properties_get(args, "access.socket")) { impl->legacy = false; } else { /* When time comes, we should change this to false */ impl->legacy = true; } if (pw_properties_get(args, "access.force") || pw_properties_get(args, "access.allowed") || pw_properties_get(args, "access.rejected") || pw_properties_get(args, "access.restricted")) { pw_log_warn("access.force/allowed/rejected/restricted are deprecated and ignored " "but imply legacy access mode"); impl->legacy = true; } if ((str = pw_properties_get(args, "access.socket")) != NULL) { if (impl->legacy) { pw_log_error("access.socket and legacy mode cannot be both enabled"); return -EINVAL; } if ((res = parse_socket_args(impl, str)) < 0) { pw_log_error("invalid access.socket value"); return res; } } else { char def[PATH_MAX]; spa_scnprintf(def, sizeof(def), "%s-manager", get_server_name(&props->dict)); pw_properties_set(impl->socket_access, def, ACCESS_UNRESTRICTED); } if (impl->legacy) pw_log_info("Using backward-compatible legacy access mode."); return 0; } SPA_EXPORT int pipewire__module_init(struct pw_impl_module *module, const char *args_str) { struct pw_context *context = pw_impl_module_get_context(module); const struct pw_properties *props = pw_context_get_properties(context); spa_autoptr(pw_properties) args = NULL; struct impl *impl; int res; PW_LOG_TOPIC_INIT(mod_topic); impl = calloc(1, sizeof(struct impl)); if (impl == NULL) return -errno; pw_log_debug("module %p: new %s", impl, args_str); if (args_str) args = pw_properties_new_string(args_str); else args = pw_properties_new(NULL, NULL); if (!args) { res = -errno; goto error; } pw_context_conf_update_props(context, "module."NAME".args", args); impl->socket_access = pw_properties_new(NULL, NULL); if ((res = parse_args(impl, props, args)) < 0) goto error; impl->context = context; pw_context_add_listener(context, &impl->context_listener, &context_events, impl); pw_impl_module_add_listener(module, &impl->module_listener, &module_events, impl); pw_impl_module_update_properties(module, &SPA_DICT_INIT_ARRAY(module_props)); return 0; error: module_destroy(impl); return res; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/modules/module-adapter.c000066400000000000000000000232671511204443500260160ustar00rootroot00000000000000/* PipeWire */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include "config.h" #include #include #include #include #include #include #include #include #include "modules/spa/spa-node.h" #include "module-adapter/adapter.h" /** \page page_module_adapter Adapter * * ## Module Name * * `libpipewire-module-adapter` */ #define NAME "adapter" PW_LOG_TOPIC(mod_topic, "mod." NAME); #define PW_LOG_TOPIC_DEFAULT mod_topic #define FACTORY_USAGE SPA_KEY_FACTORY_NAME"= " \ "("SPA_KEY_LIBRARY_NAME"=) " \ ADAPTER_USAGE static const struct spa_dict_item module_props[] = { { PW_KEY_MODULE_AUTHOR, "Wim Taymans " }, { PW_KEY_MODULE_DESCRIPTION, "Manage adapter nodes" }, { PW_KEY_MODULE_VERSION, PACKAGE_VERSION }, }; struct factory_data { struct pw_impl_factory *factory; struct spa_hook factory_listener; struct spa_list node_list; struct pw_context *context; struct pw_impl_module *module; struct spa_hook module_listener; }; struct node_data { struct factory_data *data; struct spa_list link; struct pw_impl_node *adapter; struct pw_impl_node *follower; struct spa_handle *handle; struct spa_hook adapter_listener; struct pw_resource *resource; struct pw_resource *bound_resource; struct spa_hook resource_listener; uint32_t new_id; unsigned int linger; }; static void resource_destroy(void *data) { struct node_data *nd = data; pw_log_debug("%p: destroy %p", nd, nd->adapter); spa_hook_remove(&nd->resource_listener); nd->bound_resource = NULL; if (nd->adapter && !nd->linger) pw_impl_node_destroy(nd->adapter); } static const struct pw_resource_events resource_events = { PW_VERSION_RESOURCE_EVENTS, .destroy = resource_destroy }; static void node_destroy(void *data) { struct node_data *nd = data; pw_log_debug("%p: destroy %p", nd, nd->adapter); spa_list_remove(&nd->link); nd->adapter = NULL; } static void node_free(void *data) { struct node_data *nd = data; pw_log_debug("%p: free %p", nd, nd->follower); if (nd->bound_resource != NULL) spa_hook_remove(&nd->resource_listener); spa_hook_remove(&nd->adapter_listener); if (nd->follower) pw_impl_node_destroy(nd->follower); if (nd->handle) pw_unload_spa_handle(nd->handle); } static void node_initialized(void *data) { struct node_data *nd = data; struct pw_impl_client *client; struct pw_resource *bound_resource; struct pw_global *global; int res; if (nd->resource == NULL) return; client = pw_resource_get_client(nd->resource); global = pw_impl_node_get_global(nd->adapter); res = pw_global_bind(global, client, PW_PERM_ALL, PW_VERSION_NODE, nd->new_id); if (res < 0) goto error_bind; if ((bound_resource = pw_impl_client_find_resource(client, nd->new_id)) == NULL) { res = -EIO; goto error_bind; } nd->bound_resource = bound_resource; pw_resource_add_listener(bound_resource, &nd->resource_listener, &resource_events, nd); return; error_bind: pw_resource_errorf_id(nd->resource, nd->new_id, res, "can't bind adapter node: %s", spa_strerror(res)); return; } static const struct pw_impl_node_events node_events = { PW_VERSION_IMPL_NODE_EVENTS, .destroy = node_destroy, .free = node_free, .initialized = node_initialized, }; struct match { struct pw_properties *props; int count; }; #define MATCH_INIT(p) ((struct match){ .props = (p) }) static int execute_match(void *data, const char *location, const char *action, const char *val, size_t len) { struct match *match = data; if (spa_streq(action, "update-props")) { match->count += pw_properties_update_string(match->props, val, len); } return 1; } static void *create_object(void *_data, struct pw_resource *resource, const char *type, uint32_t version, struct pw_properties *properties, uint32_t new_id) { struct factory_data *d = _data; struct pw_impl_client *client; struct pw_impl_node *adapter, *follower; struct spa_node *spa_follower; const char *str; int res; struct node_data *nd; bool linger, do_register; struct spa_handle *handle = NULL; const struct pw_properties *p; if (properties == NULL) goto error_properties; pw_properties_setf(properties, PW_KEY_FACTORY_ID, "%d", pw_impl_factory_get_info(d->factory)->id); linger = pw_properties_get_bool(properties, PW_KEY_OBJECT_LINGER, false); do_register = pw_properties_get_bool(properties, PW_KEY_OBJECT_REGISTER, true); p = pw_context_get_properties(d->context); pw_properties_set(properties, "clock.quantum-limit", pw_properties_get(p, "default.clock.quantum-limit")); client = resource ? pw_resource_get_client(resource): NULL; if (client && !linger) { pw_properties_setf(properties, PW_KEY_CLIENT_ID, "%d", pw_impl_client_get_info(client)->id); } follower = NULL; spa_follower = NULL; str = pw_properties_get(properties, "adapt.follower.node"); if (str != NULL) { if (sscanf(str, "pointer:%p", &follower) != 1) goto error_properties; spa_follower = pw_impl_node_get_implementation(follower); } str = pw_properties_get(properties, "adapt.follower.spa-node"); if (str != NULL) { if (sscanf(str, "pointer:%p", &spa_follower) != 1) goto error_properties; } if (spa_follower == NULL) { void *iface; const char *factory_name; struct match match; factory_name = pw_properties_get(properties, SPA_KEY_FACTORY_NAME); if (factory_name == NULL) goto error_properties; match = MATCH_INIT(properties); pw_context_conf_section_match_rules(d->context, "node.rules", &properties->dict, execute_match, &match); handle = pw_context_load_spa_handle(d->context, factory_name, &properties->dict); if (handle == NULL) goto error_errno; if ((res = spa_handle_get_interface(handle, SPA_TYPE_INTERFACE_Node, &iface)) < 0) goto error_res; spa_follower = iface; } if (spa_follower == NULL) { res = -EINVAL; goto error_res; } adapter = pw_adapter_new(pw_impl_module_get_context(d->module), spa_follower, properties, sizeof(struct node_data)); properties = NULL; if (adapter == NULL) { if (errno == ENOMEM || errno == EBUSY) goto error_errno; else goto error_usage; } nd = pw_adapter_get_user_data(adapter); nd->data = d; nd->adapter = adapter; nd->follower = follower; nd->handle = handle; nd->resource = resource; nd->new_id = new_id; nd->linger = linger; spa_list_append(&d->node_list, &nd->link); pw_impl_node_add_listener(adapter, &nd->adapter_listener, &node_events, nd); if (do_register) pw_impl_node_register(adapter, NULL); else pw_impl_node_initialized(adapter); return adapter; error_properties: res = -EINVAL; pw_resource_errorf_id(resource, new_id, res, "usage: " FACTORY_USAGE); goto error_cleanup; error_errno: res = -errno; error_res: pw_resource_errorf_id(resource, new_id, res, "can't create node: %s", spa_strerror(res)); goto error_cleanup; error_usage: res = -EINVAL; pw_log_error("usage: "ADAPTER_USAGE); pw_resource_errorf_id(resource, new_id, res, "usage: "ADAPTER_USAGE); goto error_cleanup; error_cleanup: pw_properties_free(properties); if (handle) pw_unload_spa_handle(handle); errno = -res; return NULL; } static const struct pw_impl_factory_implementation impl_factory = { PW_VERSION_IMPL_FACTORY_IMPLEMENTATION, .create_object = create_object, }; static void factory_destroy(void *data) { struct factory_data *d = data; struct node_data *nd; spa_hook_remove(&d->factory_listener); spa_list_consume(nd, &d->node_list, link) pw_impl_node_destroy(nd->adapter); d->factory = NULL; if (d->module) pw_impl_module_destroy(d->module); } static const struct pw_impl_factory_events factory_events = { PW_VERSION_IMPL_FACTORY_EVENTS, .destroy = factory_destroy, }; static void module_destroy(void *data) { struct factory_data *d = data; pw_log_debug("%p: destroy", d); spa_hook_remove(&d->module_listener); d->module = NULL; if (d->factory) pw_impl_factory_destroy(d->factory); } static void module_registered(void *data) { struct factory_data *d = data; struct pw_impl_module *module = d->module; struct pw_impl_factory *factory = d->factory; struct spa_dict_item items[1]; char id[16]; int res; snprintf(id, sizeof(id), "%d", pw_impl_module_get_info(module)->id); items[0] = SPA_DICT_ITEM_INIT(PW_KEY_MODULE_ID, id); pw_impl_factory_update_properties(factory, &SPA_DICT_INIT(items, 1)); if ((res = pw_impl_factory_register(factory, NULL)) < 0) { pw_log_error("%p: can't register factory: %s", factory, spa_strerror(res)); } } static const struct pw_impl_module_events module_events = { PW_VERSION_IMPL_MODULE_EVENTS, .destroy = module_destroy, .registered = module_registered, }; SPA_EXPORT int pipewire__module_init(struct pw_impl_module *module, const char *args) { struct pw_context *context = pw_impl_module_get_context(module); struct pw_impl_factory *factory; struct factory_data *data; PW_LOG_TOPIC_INIT(mod_topic); factory = pw_context_create_factory(context, "adapter", PW_TYPE_INTERFACE_Node, PW_VERSION_NODE, pw_properties_new( PW_KEY_FACTORY_USAGE, FACTORY_USAGE, NULL), sizeof(*data)); if (factory == NULL) return -errno; data = pw_impl_factory_get_user_data(factory); data->factory = factory; data->context = context; data->module = module; spa_list_init(&data->node_list); pw_log_debug("module %p: new", module); pw_impl_factory_add_listener(factory, &data->factory_listener, &factory_events, data); pw_impl_factory_set_implementation(factory, &impl_factory, data); pw_impl_module_update_properties(module, &SPA_DICT_INIT_ARRAY(module_props)); pw_impl_module_add_listener(module, &data->module_listener, &module_events, data); return 0; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/modules/module-adapter/000077500000000000000000000000001511204443500256405ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/modules/module-adapter/adapter.c000066400000000000000000000141121511204443500274230ustar00rootroot00000000000000/* PipeWire */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "pipewire/pipewire.h" #include "modules/spa/spa-node.h" #define NAME "adapter" PW_LOG_TOPIC_EXTERN(mod_topic); #define PW_LOG_TOPIC_DEFAULT mod_topic struct buffer { struct spa_buffer buf; struct spa_data datas[1]; struct spa_chunk chunk[1]; }; struct node { struct pw_context *context; struct pw_impl_node *node; struct spa_hook node_listener; struct spa_node *follower; void *user_data; enum pw_direction direction; struct pw_properties *props; uint32_t media_type; uint32_t media_subtype; struct spa_list ports; }; /** \endcond */ static void node_free(void *data) { struct node *n = data; spa_hook_remove(&n->node_listener); pw_properties_free(n->props); } static const struct pw_impl_node_events node_events = { PW_VERSION_IMPL_NODE_EVENTS, .free = node_free, }; static int find_format(struct spa_node *node, enum pw_direction direction, uint32_t *media_type, uint32_t *media_subtype) { uint32_t state = 0; uint8_t buffer[4096]; struct spa_pod_builder b; int res; struct spa_pod *format; spa_pod_builder_init(&b, buffer, sizeof(buffer)); if ((res = spa_node_port_enum_params_sync(node, direction == PW_DIRECTION_INPUT ? SPA_DIRECTION_INPUT : SPA_DIRECTION_OUTPUT, 0, SPA_PARAM_EnumFormat, &state, NULL, &format, &b)) != 1) { res = res < 0 ? res : -ENOENT; pw_log_warn("%p: can't get format: %s", node, spa_strerror(res)); return res; } if ((res = spa_format_parse(format, media_type, media_subtype)) < 0) return res; pw_log_debug("%p: %s/%s", node, spa_debug_type_find_name(spa_type_media_type, *media_type), spa_debug_type_find_name(spa_type_media_subtype, *media_subtype)); return 0; } struct info_data { struct spa_hook listener; struct spa_node *node; struct pw_properties *props; uint32_t n_input_ports; uint32_t max_input_ports; uint32_t n_output_ports; uint32_t max_output_ports; }; static void info_event(void *data, const struct spa_node_info *info) { struct info_data *d = data; pw_properties_update(d->props, info->props); d->max_input_ports = info->max_input_ports; d->max_output_ports = info->max_output_ports; } static void port_info_event(void *data, enum spa_direction direction, uint32_t port, const struct spa_port_info *info) { struct info_data *d = data; if (direction == SPA_DIRECTION_OUTPUT) d->n_output_ports++; else if (direction == SPA_DIRECTION_INPUT) d->n_input_ports++; } static const struct spa_node_events node_info_events = { .version = SPA_VERSION_NODE_EVENTS, .info = info_event, .port_info = port_info_event, }; struct pw_impl_node *pw_adapter_new(struct pw_context *context, struct spa_node *follower, struct pw_properties *props, size_t user_data_size) { struct pw_impl_node *node; struct node *n; const char *str, *factory_name; enum pw_direction direction; int res; uint32_t media_type, media_subtype; struct info_data info; spa_zero(info); info.node = follower; info.props = props; res = spa_node_add_listener(info.node, &info.listener, &node_info_events, &info); if (res < 0) goto error; spa_hook_remove(&info.listener); pw_log_debug("%p: in %d/%d out %d/%d", info.node, info.n_input_ports, info.max_input_ports, info.n_output_ports, info.max_output_ports); if (info.n_output_ports > 0) { direction = PW_DIRECTION_OUTPUT; } else if (info.n_input_ports > 0) { direction = PW_DIRECTION_INPUT; } else { res = -EINVAL; goto error; } if ((str = pw_properties_get(props, PW_KEY_NODE_ID)) != NULL) pw_properties_set(props, PW_KEY_NODE_SESSION, str); if (pw_properties_get(props, PW_KEY_PORT_GROUP) == NULL) pw_properties_setf(props, PW_KEY_PORT_GROUP, "stream.0"); if ((res = find_format(follower, direction, &media_type, &media_subtype)) < 0) goto error; if (media_type == SPA_MEDIA_TYPE_audio) { pw_properties_setf(props, "audio.adapt.follower", "pointer:%p", follower); pw_properties_set(props, SPA_KEY_LIBRARY_NAME, "audioconvert/libspa-audioconvert"); if (pw_properties_get(props, PW_KEY_MEDIA_CLASS) == NULL) pw_properties_setf(props, PW_KEY_MEDIA_CLASS, "Audio/%s", direction == PW_DIRECTION_INPUT ? "Sink" : "Source"); factory_name = SPA_NAME_AUDIO_ADAPT; } else if (media_type == SPA_MEDIA_TYPE_video) { pw_properties_setf(props, "video.adapt.follower", "pointer:%p", follower); pw_properties_set(props, SPA_KEY_LIBRARY_NAME, "videoconvert/libspa-videoconvert"); if (pw_properties_get(props, PW_KEY_MEDIA_CLASS) == NULL) pw_properties_setf(props, PW_KEY_MEDIA_CLASS, "Video/%s", direction == PW_DIRECTION_INPUT ? "Sink" : "Source"); factory_name = SPA_NAME_VIDEO_ADAPT; } else { res = -ENOTSUP; goto error; } node = pw_spa_node_load(context, factory_name, PW_SPA_NODE_FLAG_ACTIVATE | PW_SPA_NODE_FLAG_NO_REGISTER, pw_properties_copy(props), sizeof(struct node) + user_data_size); if (node == NULL) { res = -errno; pw_log_error("can't load spa node: %m"); goto error; } n = pw_spa_node_get_user_data(node); n->context = context; n->node = node; n->follower = follower; n->direction = direction; n->props = props; n->media_type = media_type; n->media_subtype = media_subtype; spa_list_init(&n->ports); if (user_data_size > 0) n->user_data = SPA_PTROFF(n, sizeof(struct node), void); pw_impl_node_add_listener(node, &n->node_listener, &node_events, n); return node; error: pw_properties_free(props); errno = -res; return NULL; } void *pw_adapter_get_user_data(struct pw_impl_node *node) { struct node *n = pw_spa_node_get_user_data(node); return n->user_data; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/modules/module-adapter/adapter.h000066400000000000000000000010641511204443500274320ustar00rootroot00000000000000/* PipeWire */ /* SPDX-FileCopyrightText: Copyright © 2019 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef PIPEWIRE_ADAPTER_H #define PIPEWIRE_ADAPTER_H #include #ifdef __cplusplus extern "C" { #endif #define ADAPTER_USAGE PW_KEY_NODE_NAME"= " struct pw_impl_node * pw_adapter_new(struct pw_context *context, struct spa_node *follower, struct pw_properties *properties, size_t user_data_size); void *pw_adapter_get_user_data(struct pw_impl_node *node); #ifdef __cplusplus } #endif #endif /* PIPEWIRE_ADAPTER_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/modules/module-avb.c000066400000000000000000000043271511204443500251420ustar00rootroot00000000000000/* PipeWire */ /* SPDX-FileCopyrightText: Copyright © 2022 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "module-avb/avb.h" /** \page page_module_avb AVB * * ## Module Name * * `libpipewire-module-avb` */ #define NAME "avb" PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); #define PW_LOG_TOPIC_DEFAULT mod_topic #define MODULE_USAGE " " static const struct spa_dict_item module_props[] = { { PW_KEY_MODULE_AUTHOR, "Wim Taymans " }, { PW_KEY_MODULE_DESCRIPTION, "Manage an AVB network" }, { PW_KEY_MODULE_USAGE, MODULE_USAGE }, { PW_KEY_MODULE_VERSION, PACKAGE_VERSION }, }; struct impl { struct pw_context *context; struct pw_impl_module *module; struct spa_hook module_listener; struct pw_avb *avb; }; static void impl_free(struct impl *impl) { free(impl); } static void module_destroy(void *data) { struct impl *impl = data; spa_hook_remove(&impl->module_listener); impl_free(impl); } static const struct pw_impl_module_events module_events = { PW_VERSION_IMPL_MODULE_EVENTS, .destroy = module_destroy, }; SPA_EXPORT int pipewire__module_init(struct pw_impl_module *module, const char *args) { struct pw_context *context = pw_impl_module_get_context(module); struct pw_properties *props; struct impl *impl; int res; PW_LOG_TOPIC_INIT(mod_topic); impl = calloc(1, sizeof(struct impl)); if (impl == NULL) goto error_errno; pw_log_debug("module %p: new %s", impl, args); if (args == NULL) args = ""; props = pw_properties_new_string(args); if (props == NULL) goto error_errno; impl->module = module; impl->context = context; impl->avb = pw_avb_new(context, props, 0); if (impl->avb == NULL) goto error_errno; pw_impl_module_add_listener(module, &impl->module_listener, &module_events, impl); pw_impl_module_update_properties(module, &SPA_DICT_INIT_ARRAY(module_props)); return 0; error_errno: res = -errno; if (impl) impl_free(impl); return res; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/modules/module-avb/000077500000000000000000000000001511204443500247705ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/modules/module-avb/aaf.h000066400000000000000000000034371511204443500256770ustar00rootroot00000000000000/* AVB support */ /* SPDX-FileCopyrightText: Copyright © 2022 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef AVB_AAF_H #define AVB_AAF_H struct avb_packet_aaf { uint8_t subtype; #if __BYTE_ORDER == __BIG_ENDIAN unsigned sv:1; unsigned version:3; unsigned mr:1; unsigned _r1:1; unsigned gv:1; unsigned tv:1; uint8_t seq_num; unsigned _r2:7; unsigned tu:1; #elif __BYTE_ORDER == __LITTLE_ENDIAN unsigned tv:1; unsigned gv:1; unsigned _r1:1; unsigned mr:1; unsigned version:3; unsigned sv:1; uint8_t seq_num; unsigned tu:1; unsigned _r2:7; #endif uint64_t stream_id; uint32_t timestamp; #define AVB_AAF_FORMAT_USER 0x00 #define AVB_AAF_FORMAT_FLOAT_32BIT 0x01 #define AVB_AAF_FORMAT_INT_32BIT 0x02 #define AVB_AAF_FORMAT_INT_24BIT 0x03 #define AVB_AAF_FORMAT_INT_16BIT 0x04 #define AVB_AAF_FORMAT_AES3_32BIT 0x05 uint8_t format; #define AVB_AAF_PCM_NSR_USER 0x00 #define AVB_AAF_PCM_NSR_8KHZ 0x01 #define AVB_AAF_PCM_NSR_16KHZ 0x02 #define AVB_AAF_PCM_NSR_32KHZ 0x03 #define AVB_AAF_PCM_NSR_44_1KHZ 0x04 #define AVB_AAF_PCM_NSR_48KHZ 0x05 #define AVB_AAF_PCM_NSR_88_2KHZ 0x06 #define AVB_AAF_PCM_NSR_96KHZ 0x07 #define AVB_AAF_PCM_NSR_176_4KHZ 0x08 #define AVB_AAF_PCM_NSR_192KHZ 0x09 #define AVB_AAF_PCM_NSR_24KHZ 0x0A #if __BYTE_ORDER == __BIG_ENDIAN unsigned nsr:4; unsigned _r3:4; #elif __BYTE_ORDER == __LITTLE_ENDIAN unsigned _r3:4; unsigned nsr:4; #endif uint8_t chan_per_frame; uint8_t bit_depth; uint16_t data_len; #define AVB_AAF_PCM_SP_NORMAL 0x00 #define AVB_AAF_PCM_SP_SPARSE 0x01 #if __BYTE_ORDER == __BIG_ENDIAN unsigned _r4:3; unsigned sp:1; unsigned event:4; #elif __BYTE_ORDER == __LITTLE_ENDIAN unsigned event:4; unsigned sp:1; unsigned _r4:3; #endif uint8_t _r5; uint8_t payload[0]; } __attribute__ ((__packed__)); #endif /* AVB_AAF_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/modules/module-avb/acmp.c000066400000000000000000000312711511204443500260600ustar00rootroot00000000000000/* AVB support */ /* SPDX-FileCopyrightText: Copyright © 2022 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include #include "acmp.h" #include "msrp.h" #include "internal.h" #include "stream.h" static const uint8_t mac[6] = AVB_BROADCAST_MAC; struct pending { struct spa_list link; uint64_t last_time; uint64_t timeout; uint16_t old_sequence_id; uint16_t sequence_id; uint16_t retry; size_t size; void *ptr; }; struct acmp { struct server *server; struct spa_hook server_listener; #define PENDING_TALKER 0 #define PENDING_LISTENER 1 #define PENDING_CONTROLLER 2 struct spa_list pending[3]; uint16_t sequence_id[3]; }; static void *pending_new(struct acmp *acmp, uint32_t type, uint64_t now, uint32_t timeout_ms, const void *m, size_t size) { struct pending *p; struct avb_ethernet_header *h; struct avb_packet_acmp *pm; p = calloc(1, sizeof(*p) + size); if (p == NULL) return NULL; p->last_time = now; p->timeout = timeout_ms * SPA_NSEC_PER_MSEC; p->sequence_id = acmp->sequence_id[type]++; p->size = size; p->ptr = SPA_PTROFF(p, sizeof(*p), void); memcpy(p->ptr, m, size); h = p->ptr; pm = SPA_PTROFF(h, sizeof(*h), void); p->old_sequence_id = ntohs(pm->sequence_id); pm->sequence_id = htons(p->sequence_id); spa_list_append(&acmp->pending[type], &p->link); return p->ptr; } static struct pending *pending_find(struct acmp *acmp, uint32_t type, uint16_t sequence_id) { struct pending *p; spa_list_for_each(p, &acmp->pending[type], link) if (p->sequence_id == sequence_id) return p; return NULL; } static void pending_free(struct acmp *acmp, struct pending *p) { spa_list_remove(&p->link); free(p); } struct msg_info { uint16_t type; const char *name; int (*handle) (struct acmp *acmp, uint64_t now, const void *m, int len); }; static int reply_not_supported(struct acmp *acmp, uint8_t type, const void *m, int len) { struct server *server = acmp->server; uint8_t buf[len]; struct avb_ethernet_header *h = (void*)buf; struct avb_packet_acmp *reply = SPA_PTROFF(h, sizeof(*h), void); memcpy(h, m, len); AVB_PACKET_ACMP_SET_MESSAGE_TYPE(reply, type); AVB_PACKET_ACMP_SET_STATUS(reply, AVB_ACMP_STATUS_NOT_SUPPORTED); return avb_server_send_packet(server, h->src, AVB_TSN_ETH, buf, len); } static int retry_pending(struct acmp *acmp, uint64_t now, struct pending *p) { struct server *server = acmp->server; struct avb_ethernet_header *h = p->ptr; p->retry++; p->last_time = now; return avb_server_send_packet(server, h->dest, AVB_TSN_ETH, p->ptr, p->size); } static int handle_connect_tx_command(struct acmp *acmp, uint64_t now, const void *m, int len) { struct server *server = acmp->server; uint8_t buf[len]; struct avb_ethernet_header *h = (void*)buf; struct avb_packet_acmp *reply = SPA_PTROFF(h, sizeof(*h), void); const struct avb_packet_acmp *p = SPA_PTROFF(m, sizeof(*h), void); int status = AVB_ACMP_STATUS_SUCCESS; struct stream *stream; if (be64toh(p->talker_guid) != server->entity_id) return 0; memcpy(buf, m, len); stream = server_find_stream(server, SPA_DIRECTION_OUTPUT, reply->talker_unique_id); if (stream == NULL) { status = AVB_ACMP_STATUS_TALKER_NO_STREAM_INDEX; goto done; } AVB_PACKET_ACMP_SET_MESSAGE_TYPE(reply, AVB_ACMP_MESSAGE_TYPE_CONNECT_TX_RESPONSE); reply->stream_id = htobe64(stream->id); stream_activate(stream, now); memcpy(reply->stream_dest_mac, stream->addr, 6); reply->connection_count = htons(1); reply->stream_vlan_id = htons(stream->vlan_id); done: AVB_PACKET_ACMP_SET_STATUS(reply, status); return avb_server_send_packet(server, h->dest, AVB_TSN_ETH, buf, len); } static int handle_connect_tx_response(struct acmp *acmp, uint64_t now, const void *m, int len) { struct server *server = acmp->server; struct avb_ethernet_header *h; const struct avb_packet_acmp *resp = SPA_PTROFF(m, sizeof(*h), void); struct avb_packet_acmp *reply; struct pending *pending; uint16_t sequence_id; struct stream *stream; int res; if (be64toh(resp->listener_guid) != server->entity_id) return 0; sequence_id = ntohs(resp->sequence_id); pending = pending_find(acmp, PENDING_TALKER, sequence_id); if (pending == NULL) return 0; h = pending->ptr; pending->size = SPA_MIN((int)pending->size, len); memcpy(h, m, pending->size); reply = SPA_PTROFF(h, sizeof(*h), void); reply->sequence_id = htons(pending->old_sequence_id); AVB_PACKET_ACMP_SET_MESSAGE_TYPE(reply, AVB_ACMP_MESSAGE_TYPE_CONNECT_RX_RESPONSE); stream = server_find_stream(server, SPA_DIRECTION_INPUT, ntohs(reply->listener_unique_id)); if (stream == NULL) return 0; stream->peer_id = be64toh(reply->stream_id); memcpy(stream->addr, reply->stream_dest_mac, 6); stream_activate(stream, now); res = avb_server_send_packet(server, h->dest, AVB_TSN_ETH, h, pending->size); pending_free(acmp, pending); return res; } static int handle_disconnect_tx_command(struct acmp *acmp, uint64_t now, const void *m, int len) { struct server *server = acmp->server; uint8_t buf[len]; struct avb_ethernet_header *h = (void*)buf; struct avb_packet_acmp *reply = SPA_PTROFF(h, sizeof(*h), void); const struct avb_packet_acmp *p = SPA_PTROFF(m, sizeof(*h), void); int status = AVB_ACMP_STATUS_SUCCESS; struct stream *stream; if (be64toh(p->talker_guid) != server->entity_id) return 0; memcpy(buf, m, len); stream = server_find_stream(server, SPA_DIRECTION_OUTPUT, reply->talker_unique_id); if (stream == NULL) { status = AVB_ACMP_STATUS_TALKER_NO_STREAM_INDEX; goto done; } AVB_PACKET_ACMP_SET_MESSAGE_TYPE(reply, AVB_ACMP_MESSAGE_TYPE_DISCONNECT_TX_RESPONSE); stream_deactivate(stream, now); done: AVB_PACKET_ACMP_SET_STATUS(reply, status); return avb_server_send_packet(server, h->dest, AVB_TSN_ETH, buf, len); } static int handle_disconnect_tx_response(struct acmp *acmp, uint64_t now, const void *m, int len) { struct server *server = acmp->server; struct avb_ethernet_header *h; struct avb_packet_acmp *reply; const struct avb_packet_acmp *resp = SPA_PTROFF(m, sizeof(*h), void); struct pending *pending; uint16_t sequence_id; struct stream *stream; int res; if (be64toh(resp->listener_guid) != server->entity_id) return 0; sequence_id = ntohs(resp->sequence_id); pending = pending_find(acmp, PENDING_TALKER, sequence_id); if (pending == NULL) return 0; h = pending->ptr; pending->size = SPA_MIN((int)pending->size, len); memcpy(h, m, pending->size); reply = SPA_PTROFF(h, sizeof(*h), void); reply->sequence_id = htons(pending->old_sequence_id); AVB_PACKET_ACMP_SET_MESSAGE_TYPE(reply, AVB_ACMP_MESSAGE_TYPE_DISCONNECT_RX_RESPONSE); stream = server_find_stream(server, SPA_DIRECTION_INPUT, reply->listener_unique_id); if (stream == NULL) return 0; stream_deactivate(stream, now); res = avb_server_send_packet(server, h->dest, AVB_TSN_ETH, h, pending->size); pending_free(acmp, pending); return res; } static int handle_connect_rx_command(struct acmp *acmp, uint64_t now, const void *m, int len) { struct server *server = acmp->server; struct avb_ethernet_header *h; const struct avb_packet_acmp *p = SPA_PTROFF(m, sizeof(*h), void); struct avb_packet_acmp *cmd; if (be64toh(p->listener_guid) != server->entity_id) return 0; h = pending_new(acmp, PENDING_TALKER, now, AVB_ACMP_TIMEOUT_CONNECT_TX_COMMAND_MS, m, len); if (h == NULL) return -errno; cmd = SPA_PTROFF(h, sizeof(*h), void); AVB_PACKET_ACMP_SET_MESSAGE_TYPE(cmd, AVB_ACMP_MESSAGE_TYPE_CONNECT_TX_COMMAND); AVB_PACKET_ACMP_SET_STATUS(cmd, AVB_ACMP_STATUS_SUCCESS); return avb_server_send_packet(server, h->dest, AVB_TSN_ETH, h, len); } static int handle_ignore(struct acmp *acmp, uint64_t now, const void *m, int len) { return 0; } static int handle_disconnect_rx_command(struct acmp *acmp, uint64_t now, const void *m, int len) { struct server *server = acmp->server; struct avb_ethernet_header *h; const struct avb_packet_acmp *p = SPA_PTROFF(m, sizeof(*h), void); struct avb_packet_acmp *cmd; if (be64toh(p->listener_guid) != server->entity_id) return 0; h = pending_new(acmp, PENDING_TALKER, now, AVB_ACMP_TIMEOUT_DISCONNECT_TX_COMMAND_MS, m, len); if (h == NULL) return -errno; cmd = SPA_PTROFF(h, sizeof(*h), void); AVB_PACKET_ACMP_SET_MESSAGE_TYPE(cmd, AVB_ACMP_MESSAGE_TYPE_DISCONNECT_TX_COMMAND); AVB_PACKET_ACMP_SET_STATUS(cmd, AVB_ACMP_STATUS_SUCCESS); return avb_server_send_packet(server, h->dest, AVB_TSN_ETH, h, len); } static const struct msg_info msg_info[] = { { AVB_ACMP_MESSAGE_TYPE_CONNECT_TX_COMMAND, "connect-tx-command", handle_connect_tx_command, }, { AVB_ACMP_MESSAGE_TYPE_CONNECT_TX_RESPONSE, "connect-tx-response", handle_connect_tx_response, }, { AVB_ACMP_MESSAGE_TYPE_DISCONNECT_TX_COMMAND, "disconnect-tx-command", handle_disconnect_tx_command, }, { AVB_ACMP_MESSAGE_TYPE_DISCONNECT_TX_RESPONSE, "disconnect-tx-response", handle_disconnect_tx_response, }, { AVB_ACMP_MESSAGE_TYPE_GET_TX_STATE_COMMAND, "get-tx-state-command", NULL, }, { AVB_ACMP_MESSAGE_TYPE_GET_TX_STATE_RESPONSE, "get-tx-state-response", handle_ignore, }, { AVB_ACMP_MESSAGE_TYPE_CONNECT_RX_COMMAND, "connect-rx-command", handle_connect_rx_command, }, { AVB_ACMP_MESSAGE_TYPE_CONNECT_RX_RESPONSE, "connect-rx-response", handle_ignore, }, { AVB_ACMP_MESSAGE_TYPE_DISCONNECT_RX_COMMAND, "disconnect-rx-command", handle_disconnect_rx_command, }, { AVB_ACMP_MESSAGE_TYPE_DISCONNECT_RX_RESPONSE, "disconnect-rx-response", handle_ignore, }, { AVB_ACMP_MESSAGE_TYPE_GET_RX_STATE_COMMAND, "get-rx-state-command", NULL, }, { AVB_ACMP_MESSAGE_TYPE_GET_RX_STATE_RESPONSE, "get-rx-state-response", handle_ignore, }, { AVB_ACMP_MESSAGE_TYPE_GET_TX_CONNECTION_COMMAND, "get-tx-connection-command", NULL, }, { AVB_ACMP_MESSAGE_TYPE_GET_TX_CONNECTION_RESPONSE, "get-tx-connection-response", handle_ignore, }, }; static inline const struct msg_info *find_msg_info(uint16_t type, const char *name) { SPA_FOR_EACH_ELEMENT_VAR(msg_info, i) { if ((name == NULL && type == i->type) || (name != NULL && spa_streq(name, i->name))) return i; } return NULL; } static int acmp_message(void *data, uint64_t now, const void *message, int len) { struct acmp *acmp = data; struct server *server = acmp->server; const struct avb_ethernet_header *h = message; const struct avb_packet_acmp *p = SPA_PTROFF(h, sizeof(*h), void); const struct msg_info *info; int message_type; if (ntohs(h->type) != AVB_TSN_ETH) return 0; if (memcmp(h->dest, mac, 6) != 0 && memcmp(h->dest, server->mac_addr, 6) != 0) return 0; if (AVB_PACKET_GET_SUBTYPE(&p->hdr) != AVB_SUBTYPE_ACMP) return 0; message_type = AVB_PACKET_ACMP_GET_MESSAGE_TYPE(p); info = find_msg_info(message_type, NULL); if (info == NULL) return 0; pw_log_info("got ACMP message %s", info->name); if (info->handle == NULL) return reply_not_supported(acmp, message_type | 1, message, len); return info->handle(acmp, now, message, len); } static void acmp_destroy(void *data) { struct acmp *acmp = data; spa_hook_remove(&acmp->server_listener); free(acmp); } static void check_timeout(struct acmp *acmp, uint64_t now, uint16_t type) { struct pending *p, *t; spa_list_for_each_safe(p, t, &acmp->pending[type], link) { if (p->last_time + p->timeout > now) continue; if (p->retry == 0) { pw_log_info("%p: pending timeout, retry", p); retry_pending(acmp, now, p); } else { pw_log_info("%p: pending timeout, fail", p); pending_free(acmp, p); } } } static void acmp_periodic(void *data, uint64_t now) { struct acmp *acmp = data; check_timeout(acmp, now, PENDING_TALKER); check_timeout(acmp, now, PENDING_LISTENER); check_timeout(acmp, now, PENDING_CONTROLLER); } static int do_help(struct acmp *acmp, const char *args, FILE *out) { fprintf(out, "{ \"type\": \"help\"," "\"text\": \"" "/adp/help: this help \\n" "\" }"); return 0; } static int acmp_command(void *data, uint64_t now, const char *command, const char *args, FILE *out) { struct acmp *acmp = data; int res; if (!spa_strstartswith(command, "/acmp/")) return 0; command += strlen("/acmp/"); if (spa_streq(command, "help")) res = do_help(acmp, args, out); else res = -ENOTSUP; return res; } static const struct server_events server_events = { AVB_VERSION_SERVER_EVENTS, .destroy = acmp_destroy, .message = acmp_message, .periodic = acmp_periodic, .command = acmp_command }; struct avb_acmp *avb_acmp_register(struct server *server) { struct acmp *acmp; acmp = calloc(1, sizeof(*acmp)); if (acmp == NULL) return NULL; acmp->server = server; spa_list_init(&acmp->pending[PENDING_TALKER]); spa_list_init(&acmp->pending[PENDING_LISTENER]); spa_list_init(&acmp->pending[PENDING_CONTROLLER]); avdecc_server_add_listener(server, &acmp->server_listener, &server_events, acmp); return (struct avb_acmp*)acmp; } void avb_acmp_unregister(struct avb_acmp *acmp) { acmp_destroy(acmp); } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/modules/module-avb/acmp.h000066400000000000000000000060361511204443500260660ustar00rootroot00000000000000/* AVB support */ /* SPDX-FileCopyrightText: Copyright © 2022 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef AVB_ACMP_H #define AVB_ACMP_H #include "packets.h" #include "internal.h" #define AVB_ACMP_MESSAGE_TYPE_CONNECT_TX_COMMAND 0 #define AVB_ACMP_MESSAGE_TYPE_CONNECT_TX_RESPONSE 1 #define AVB_ACMP_MESSAGE_TYPE_DISCONNECT_TX_COMMAND 2 #define AVB_ACMP_MESSAGE_TYPE_DISCONNECT_TX_RESPONSE 3 #define AVB_ACMP_MESSAGE_TYPE_GET_TX_STATE_COMMAND 4 #define AVB_ACMP_MESSAGE_TYPE_GET_TX_STATE_RESPONSE 5 #define AVB_ACMP_MESSAGE_TYPE_CONNECT_RX_COMMAND 6 #define AVB_ACMP_MESSAGE_TYPE_CONNECT_RX_RESPONSE 7 #define AVB_ACMP_MESSAGE_TYPE_DISCONNECT_RX_COMMAND 8 #define AVB_ACMP_MESSAGE_TYPE_DISCONNECT_RX_RESPONSE 9 #define AVB_ACMP_MESSAGE_TYPE_GET_RX_STATE_COMMAND 10 #define AVB_ACMP_MESSAGE_TYPE_GET_RX_STATE_RESPONSE 11 #define AVB_ACMP_MESSAGE_TYPE_GET_TX_CONNECTION_COMMAND 12 #define AVB_ACMP_MESSAGE_TYPE_GET_TX_CONNECTION_RESPONSE 13 #define AVB_ACMP_STATUS_SUCCESS 0 #define AVB_ACMP_STATUS_LISTENER_UNKNOWN_ID 1 #define AVB_ACMP_STATUS_TALKER_UNKNOWN_ID 2 #define AVB_ACMP_STATUS_TALKER_DEST_MAC_FAIL 3 #define AVB_ACMP_STATUS_TALKER_NO_STREAM_INDEX 4 #define AVB_ACMP_STATUS_TALKER_NO_BANDWIDTH 5 #define AVB_ACMP_STATUS_TALKER_EXCLUSIVE 6 #define AVB_ACMP_STATUS_LISTENER_TALKER_TIMEOUT 7 #define AVB_ACMP_STATUS_LISTENER_EXCLUSIVE 8 #define AVB_ACMP_STATUS_STATE_UNAVAILABLE 9 #define AVB_ACMP_STATUS_NOT_CONNECTED 10 #define AVB_ACMP_STATUS_NO_SUCH_CONNECTION 11 #define AVB_ACMP_STATUS_COULD_NOT_SEND_MESSAGE 12 #define AVB_ACMP_STATUS_TALKER_MISBEHAVING 13 #define AVB_ACMP_STATUS_LISTENER_MISBEHAVING 14 #define AVB_ACMP_STATUS_RESERVED 15 #define AVB_ACMP_STATUS_CONTROLLER_NOT_AUTHORIZED 16 #define AVB_ACMP_STATUS_INCOMPATIBLE_REQUEST 17 #define AVB_ACMP_STATUS_LISTENER_INVALID_CONNECTION 18 #define AVB_ACMP_STATUS_NOT_SUPPORTED 31 #define AVB_ACMP_TIMEOUT_CONNECT_TX_COMMAND_MS 2000 #define AVB_ACMP_TIMEOUT_DISCONNECT_TX_COMMAND_MS 200 #define AVB_ACMP_TIMEOUT_GET_TX_STATE_COMMAND 200 #define AVB_ACMP_TIMEOUT_CONNECT_RX_COMMAND_MS 4500 #define AVB_ACMP_TIMEOUT_DISCONNECT_RX_COMMAND_MS 500 #define AVB_ACMP_TIMEOUT_GET_RX_STATE_COMMAND_MS 200 #define AVB_ACMP_TIMEOUT_GET_TX_CONNECTION_COMMAND 200 struct avb_packet_acmp { struct avb_packet_header hdr; uint64_t stream_id; uint64_t controller_guid; uint64_t talker_guid; uint64_t listener_guid; uint16_t talker_unique_id; uint16_t listener_unique_id; char stream_dest_mac[6]; uint16_t connection_count; uint16_t sequence_id; uint16_t flags; uint16_t stream_vlan_id; uint16_t reserved; } __attribute__ ((__packed__)); #define AVB_PACKET_ACMP_SET_MESSAGE_TYPE(p,v) AVB_PACKET_SET_SUB1(&(p)->hdr, v) #define AVB_PACKET_ACMP_SET_STATUS(p,v) AVB_PACKET_SET_SUB2(&(p)->hdr, v) #define AVB_PACKET_ACMP_GET_MESSAGE_TYPE(p) AVB_PACKET_GET_SUB1(&(p)->hdr) #define AVB_PACKET_ACMP_GET_STATUS(p) AVB_PACKET_GET_SUB2(&(p)->hdr) struct avb_acmp *avb_acmp_register(struct server *server); void avb_acmp_unregister(struct avb_acmp *acmp); #endif /* AVB_ACMP_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/modules/module-avb/adp.c000066400000000000000000000214171511204443500257050ustar00rootroot00000000000000/* AVB support */ /* SPDX-FileCopyrightText: Copyright © 2022 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include "adp.h" #include "aecp-aem-descriptors.h" #include "internal.h" #include "utils.h" static const uint8_t mac[6] = AVB_BROADCAST_MAC; struct entity { struct spa_list link; uint64_t entity_id; uint64_t last_time; int valid_time; unsigned advertise:1; size_t len; uint8_t buf[128]; }; struct adp { struct server *server; struct spa_hook server_listener; struct spa_list entities; uint32_t available_index; }; static struct entity *find_entity_by_id(struct adp *adp, uint64_t id) { struct entity *e; spa_list_for_each(e, &adp->entities, link) if (e->entity_id == id) return e; return NULL; } static void entity_free(struct entity *e) { spa_list_remove(&e->link); free(e); } static int send_departing(struct adp *adp, uint64_t now, struct entity *e) { struct avb_ethernet_header *h = (void*)e->buf; struct avb_packet_adp *p = SPA_PTROFF(h, sizeof(*h), void); AVB_PACKET_ADP_SET_MESSAGE_TYPE(p, AVB_ADP_MESSAGE_TYPE_ENTITY_DEPARTING); p->available_index = htonl(adp->available_index++); avb_server_send_packet(adp->server, mac, AVB_TSN_ETH, e->buf, e->len); e->last_time = now; return 0; } static int send_advertise(struct adp *adp, uint64_t now, struct entity *e) { struct avb_ethernet_header *h = (void*)e->buf; struct avb_packet_adp *p = SPA_PTROFF(h, sizeof(*h), void); AVB_PACKET_ADP_SET_MESSAGE_TYPE(p, AVB_ADP_MESSAGE_TYPE_ENTITY_AVAILABLE); p->available_index = htonl(adp->available_index++); avb_server_send_packet(adp->server, mac, AVB_TSN_ETH, e->buf, e->len); e->last_time = now; return 0; } static int send_discover(struct adp *adp, uint64_t entity_id) { uint8_t buf[128]; struct avb_ethernet_header *h = (void*)buf; struct avb_packet_adp *p = SPA_PTROFF(h, sizeof(*h), void); size_t len = sizeof(*h) + sizeof(*p); spa_memzero(buf, sizeof(buf)); AVB_PACKET_SET_SUBTYPE(&p->hdr, AVB_SUBTYPE_ADP); AVB_PACKET_SET_LENGTH(&p->hdr, AVB_ADP_CONTROL_DATA_LENGTH); AVB_PACKET_ADP_SET_MESSAGE_TYPE(p, AVB_ADP_MESSAGE_TYPE_ENTITY_DISCOVER); p->entity_id = htonl(entity_id); avb_server_send_packet(adp->server, mac, AVB_TSN_ETH, buf, len); return 0; } static int adp_message(void *data, uint64_t now, const void *message, int len) { struct adp *adp = data; struct server *server = adp->server; const struct avb_ethernet_header *h = message; const struct avb_packet_adp *p = SPA_PTROFF(h, sizeof(*h), void); struct entity *e; int message_type; char buf[128]; uint64_t entity_id; if (ntohs(h->type) != AVB_TSN_ETH) return 0; if (memcmp(h->dest, mac, 6) != 0 && memcmp(h->dest, server->mac_addr, 6) != 0) return 0; if (AVB_PACKET_GET_SUBTYPE(&p->hdr) != AVB_SUBTYPE_ADP || AVB_PACKET_GET_LENGTH(&p->hdr) < AVB_ADP_CONTROL_DATA_LENGTH) return 0; message_type = AVB_PACKET_ADP_GET_MESSAGE_TYPE(p); entity_id = be64toh(p->entity_id); e = find_entity_by_id(adp, entity_id); switch (message_type) { case AVB_ADP_MESSAGE_TYPE_ENTITY_AVAILABLE: if (e == NULL) { e = calloc(1, sizeof(*e)); if (e == NULL) return -errno; memcpy(e->buf, message, len); e->len = len; e->valid_time = AVB_PACKET_ADP_GET_VALID_TIME(p); e->entity_id = entity_id; spa_list_append(&adp->entities, &e->link); pw_log_info("entity %s available", avb_utils_format_id(buf, sizeof(buf), entity_id)); } e->last_time = now; break; case AVB_ADP_MESSAGE_TYPE_ENTITY_DEPARTING: if (e != NULL) { pw_log_info("entity %s departing", avb_utils_format_id(buf, sizeof(buf), entity_id)); entity_free(e); } break; case AVB_ADP_MESSAGE_TYPE_ENTITY_DISCOVER: pw_log_info("entity %s advertise", avb_utils_format_id(buf, sizeof(buf), entity_id)); if (entity_id == 0UL) { spa_list_for_each(e, &adp->entities, link) if (e->advertise) send_advertise(adp, now, e); } else if (e != NULL && e->advertise && e->entity_id == entity_id) { send_advertise(adp, now, e); } break; default: return -EINVAL; } return 0; } static void adp_destroy(void *data) { struct adp *adp = data; spa_hook_remove(&adp->server_listener); free(adp); } static void check_timeout(struct adp *adp, uint64_t now) { struct entity *e, *t; char buf[128]; spa_list_for_each_safe(e, t, &adp->entities, link) { if (e->last_time + (e->valid_time + 2) * SPA_NSEC_PER_SEC > now) continue; pw_log_info("entity %s timeout", avb_utils_format_id(buf, sizeof(buf), e->entity_id)); if (e->advertise) send_departing(adp, now, e); entity_free(e); } } static void check_readvertize(struct adp *adp, uint64_t now, struct entity *e) { char buf[128]; if (!e->advertise) return; if (e->last_time + (e->valid_time / 2) * SPA_NSEC_PER_SEC > now) return; pw_log_debug("entity %s readvertise", avb_utils_format_id(buf, sizeof(buf), e->entity_id)); send_advertise(adp, now, e); } static int check_advertise(struct adp *adp, uint64_t now) { struct server *server = adp->server; const struct descriptor *d; struct avb_aem_desc_entity *entity; struct avb_aem_desc_avb_interface *avb_interface; struct entity *e; uint64_t entity_id; struct avb_ethernet_header *h; struct avb_packet_adp *p; char buf[128]; d = server_find_descriptor(server, AVB_AEM_DESC_ENTITY, 0); if (d == NULL) return 0; entity = d->ptr; entity_id = be64toh(entity->entity_id); if ((e = find_entity_by_id(adp, entity_id)) != NULL) { if (e->advertise) check_readvertize(adp, now, e); return 0; } d = server_find_descriptor(server, AVB_AEM_DESC_AVB_INTERFACE, 0); avb_interface = d ? d->ptr : NULL; pw_log_info("entity %s advertise", avb_utils_format_id(buf, sizeof(buf), entity_id)); e = calloc(1, sizeof(*e)); if (e == NULL) return -errno; e->advertise = true; e->valid_time = 10; e->last_time = now; e->entity_id = entity_id; e->len = sizeof(*h) + sizeof(*p); h = (void*)e->buf; p = SPA_PTROFF(h, sizeof(*h), void); AVB_PACKET_SET_LENGTH(&p->hdr, AVB_ADP_CONTROL_DATA_LENGTH); AVB_PACKET_SET_SUBTYPE(&p->hdr, AVB_SUBTYPE_ADP); AVB_PACKET_ADP_SET_MESSAGE_TYPE(p, AVB_ADP_MESSAGE_TYPE_ENTITY_AVAILABLE); AVB_PACKET_ADP_SET_VALID_TIME(p, e->valid_time); p->entity_id = entity->entity_id; p->entity_model_id = entity->entity_model_id; p->entity_capabilities = entity->entity_capabilities; p->talker_stream_sources = entity->talker_stream_sources; p->talker_capabilities = entity->talker_capabilities; p->listener_stream_sinks = entity->listener_stream_sinks; p->listener_capabilities = entity->listener_capabilities; p->controller_capabilities = entity->controller_capabilities; p->available_index = entity->available_index; if (avb_interface) { p->gptp_grandmaster_id = avb_interface->clock_identity; p->gptp_domain_number = avb_interface->domain_number; } p->identify_control_index = 0; p->interface_index = 0; p->association_id = entity->association_id; spa_list_append(&adp->entities, &e->link); return 0; } static void adp_periodic(void *data, uint64_t now) { struct adp *adp = data; check_timeout(adp, now); check_advertise(adp, now); } static int do_help(struct adp *adp, const char *args, FILE *out) { fprintf(out, "{ \"type\": \"help\"," "\"text\": \"" "/adp/help: this help \\n" "/adp/discover [{ \"entity-id\": }] : trigger discover\\n" "\" }"); return 0; } static int do_discover(struct adp *adp, const char *args, FILE *out) { struct spa_json it[1]; char key[128]; uint64_t entity_id = 0ULL; int len; const char *value; if (spa_json_begin_object(&it[0], args, strlen(args)) <= 0) return -EINVAL; while ((len = spa_json_object_next(&it[0], key, sizeof(key), &value)) > 0) { uint64_t id_val; if (spa_json_is_null(value, len)) continue; if (spa_streq(key, "entity-id")) { if (avb_utils_parse_id(value, len, &id_val) >= 0) entity_id = id_val; } } send_discover(adp, entity_id); return 0; } static int adp_command(void *data, uint64_t now, const char *command, const char *args, FILE *out) { struct adp *adp = data; int res; if (!spa_strstartswith(command, "/adp/")) return 0; command += strlen("/adp/"); if (spa_streq(command, "help")) res = do_help(adp, args, out); else if (spa_streq(command, "discover")) res = do_discover(adp, args, out); else res = -ENOTSUP; return res; } static const struct server_events server_events = { AVB_VERSION_SERVER_EVENTS, .destroy = adp_destroy, .message = adp_message, .periodic = adp_periodic, .command = adp_command }; struct avb_adp *avb_adp_register(struct server *server) { struct adp *adp; adp = calloc(1, sizeof(*adp)); if (adp == NULL) return NULL; adp->server = server; spa_list_init(&adp->entities); avdecc_server_add_listener(server, &adp->server_listener, &server_events, adp); return (struct avb_adp*)adp; } void avb_adp_unregister(struct avb_adp *adp) { adp_destroy(adp); } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/modules/module-avb/adp.h000066400000000000000000000070461511204443500257140ustar00rootroot00000000000000/* AVB support */ /* SPDX-FileCopyrightText: Copyright © 2022 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef AVB_ADP_H #define AVB_ADP_H #include "packets.h" #include "internal.h" #define AVB_ADP_MESSAGE_TYPE_ENTITY_AVAILABLE 0 #define AVB_ADP_MESSAGE_TYPE_ENTITY_DEPARTING 1 #define AVB_ADP_MESSAGE_TYPE_ENTITY_DISCOVER 2 #define AVB_ADP_ENTITY_CAPABILITY_EFU_MODE (1u<<0) #define AVB_ADP_ENTITY_CAPABILITY_ADDRESS_ACCESS_SUPPORTED (1u<<1) #define AVB_ADP_ENTITY_CAPABILITY_GATEWAY_ENTITY (1u<<2) #define AVB_ADP_ENTITY_CAPABILITY_AEM_SUPPORTED (1u<<3) #define AVB_ADP_ENTITY_CAPABILITY_LEGACY_AVC (1u<<4) #define AVB_ADP_ENTITY_CAPABILITY_ASSOCIATION_ID_SUPPORTED (1u<<5) #define AVB_ADP_ENTITY_CAPABILITY_ASSOCIATION_ID_VALID (1u<<6) #define AVB_ADP_ENTITY_CAPABILITY_VENDOR_UNIQUE_SUPPORTED (1u<<7) #define AVB_ADP_ENTITY_CAPABILITY_CLASS_A_SUPPORTED (1u<<8) #define AVB_ADP_ENTITY_CAPABILITY_CLASS_B_SUPPORTED (1u<<9) #define AVB_ADP_ENTITY_CAPABILITY_GPTP_SUPPORTED (1u<<10) #define AVB_ADP_ENTITY_CAPABILITY_AEM_AUTHENTICATION_SUPPORTED (1u<<11) #define AVB_ADP_ENTITY_CAPABILITY_AEM_AUTHENTICATION_REQUIRED (1u<<12) #define AVB_ADP_ENTITY_CAPABILITY_AEM_PERSISTENT_ACQUIRE_SUPPORTED (1u<<13) #define AVB_ADP_ENTITY_CAPABILITY_AEM_IDENTIFY_CONTROL_INDEX_VALID (1u<<14) #define AVB_ADP_ENTITY_CAPABILITY_AEM_INTERFACE_INDEX_VALID (1u<<15) #define AVB_ADP_ENTITY_CAPABILITY_GENERAL_CONTROLLER_IGNORE (1u<<16) #define AVB_ADP_ENTITY_CAPABILITY_ENTITY_NOT_READY (1u<<17) #define AVB_ADP_TALKER_CAPABILITY_IMPLEMENTED (1u<<0) #define AVB_ADP_TALKER_CAPABILITY_OTHER_SOURCE (1u<<9) #define AVB_ADP_TALKER_CAPABILITY_CONTROL_SOURCE (1u<<10) #define AVB_ADP_TALKER_CAPABILITY_MEDIA_CLOCK_SOURCE (1u<<11) #define AVB_ADP_TALKER_CAPABILITY_SMPTE_SOURCE (1u<<12) #define AVB_ADP_TALKER_CAPABILITY_MIDI_SOURCE (1u<<13) #define AVB_ADP_TALKER_CAPABILITY_AUDIO_SOURCE (1u<<14) #define AVB_ADP_TALKER_CAPABILITY_VIDEO_SOURCE (1u<<15) #define AVB_ADP_LISTENER_CAPABILITY_IMPLEMENTED (1u<<0) #define AVB_ADP_LISTENER_CAPABILITY_OTHER_SINK (1u<<9) #define AVB_ADP_LISTENER_CAPABILITY_CONTROL_SINK (1u<<10) #define AVB_ADP_LISTENER_CAPABILITY_MEDIA_CLOCK_SINK (1u<<11) #define AVB_ADP_LISTENER_CAPABILITY_SMPTE_SINK (1u<<12) #define AVB_ADP_LISTENER_CAPABILITY_MIDI_SINK (1u<<13) #define AVB_ADP_LISTENER_CAPABILITY_AUDIO_SINK (1u<<14) #define AVB_ADP_LISTENER_CAPABILITY_VIDEO_SINK (1u<<15) #define AVB_ADP_CONTROLLER_CAPABILITY_IMPLEMENTED (1u<<0) #define AVB_ADP_CONTROLLER_CAPABILITY_LAYER3_PROXY (1u<<1) #define AVB_ADP_CONTROL_DATA_LENGTH 56 struct avb_packet_adp { struct avb_packet_header hdr; uint64_t entity_id; uint64_t entity_model_id; uint32_t entity_capabilities; uint16_t talker_stream_sources; uint16_t talker_capabilities; uint16_t listener_stream_sinks; uint16_t listener_capabilities; uint32_t controller_capabilities; uint32_t available_index; uint64_t gptp_grandmaster_id; uint8_t gptp_domain_number; uint8_t reserved0[3]; uint16_t identify_control_index; uint16_t interface_index; uint64_t association_id; uint32_t reserved1; } __attribute__ ((__packed__)); #define AVB_PACKET_ADP_SET_MESSAGE_TYPE(p,v) AVB_PACKET_SET_SUB1(&(p)->hdr, v) #define AVB_PACKET_ADP_SET_VALID_TIME(p,v) AVB_PACKET_SET_SUB2(&(p)->hdr, v) #define AVB_PACKET_ADP_GET_MESSAGE_TYPE(p) AVB_PACKET_GET_SUB1(&(p)->hdr) #define AVB_PACKET_ADP_GET_VALID_TIME(p) AVB_PACKET_GET_SUB2(&(p)->hdr) struct avb_adp *avb_adp_register(struct server *server); void avb_adp_unregister(struct avb_adp *adp); #endif /* AVB_ADP_H */ aecp-aem-descriptors.h000066400000000000000000000170071511204443500310760ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/modules/module-avb/* AVB support */ /* SPDX-FileCopyrightText: Copyright © 2022 Wim Taymans */ /* SPDX-FileCopyrightText: Copyright © 2025 Alexandre Malki (alexandre.malki@kebag-logic.com) */ /* SPDX-License-Identifier: MIT */ #ifndef AVB_AECP_AEM_DESCRIPTORS_H #define AVB_AECP_AEM_DESCRIPTORS_H #include "internal.h" #define AVB_AEM_DESC_ENTITY 0x0000 #define AVB_AEM_DESC_CONFIGURATION 0x0001 #define AVB_AEM_DESC_AUDIO_UNIT 0x0002 #define AVB_AEM_DESC_VIDEO_UNIT 0x0003 #define AVB_AEM_DESC_SENSOR_UNIT 0x0004 #define AVB_AEM_DESC_STREAM_INPUT 0x0005 #define AVB_AEM_DESC_STREAM_OUTPUT 0x0006 #define AVB_AEM_DESC_JACK_INPUT 0x0007 #define AVB_AEM_DESC_JACK_OUTPUT 0x0008 #define AVB_AEM_DESC_AVB_INTERFACE 0x0009 #define AVB_AEM_DESC_CLOCK_SOURCE 0x000a #define AVB_AEM_DESC_MEMORY_OBJECT 0x000b #define AVB_AEM_DESC_LOCALE 0x000c #define AVB_AEM_DESC_STRINGS 0x000d #define AVB_AEM_DESC_STREAM_PORT_INPUT 0x000e #define AVB_AEM_DESC_STREAM_PORT_OUTPUT 0x000f #define AVB_AEM_DESC_EXTERNAL_PORT_INPUT 0x0010 #define AVB_AEM_DESC_EXTERNAL_PORT_OUTPUT 0x0011 #define AVB_AEM_DESC_INTERNAL_PORT_INPUT 0x0012 #define AVB_AEM_DESC_INTERNAL_PORT_OUTPUT 0x0013 #define AVB_AEM_DESC_AUDIO_CLUSTER 0x0014 #define AVB_AEM_DESC_VIDEO_CLUSTER 0x0015 #define AVB_AEM_DESC_SENSOR_CLUSTER 0x0016 #define AVB_AEM_DESC_AUDIO_MAP 0x0017 #define AVB_AEM_DESC_VIDEO_MAP 0x0018 #define AVB_AEM_DESC_SENSOR_MAP 0x0019 #define AVB_AEM_DESC_CONTROL 0x001a #define AVB_AEM_DESC_SIGNAL_SELECTOR 0x001b #define AVB_AEM_DESC_MIXER 0x001c #define AVB_AEM_DESC_MATRIX 0x001d #define AVB_AEM_DESC_MATRIX_SIGNAL 0x001e #define AVB_AEM_DESC_SIGNAL_SPLITTER 0x001f #define AVB_AEM_DESC_SIGNAL_COMBINER 0x0020 #define AVB_AEM_DESC_SIGNAL_DEMULTIPLEXER 0x0021 #define AVB_AEM_DESC_SIGNAL_MULTIPLEXER 0x0022 #define AVB_AEM_DESC_SIGNAL_TRANSCODER 0x0023 #define AVB_AEM_DESC_CLOCK_DOMAIN 0x0024 #define AVB_AEM_DESC_CONTROL_BLOCK 0x0025 /** IEEE 1722.1-2021 Table-7 has up to descriptor 0x0029, reserved for future */ #define AVB_AEM_DESC_LAST_RESERVED_17221 0x0029 #define AVB_AEM_DESC_INVALID 0xffff struct avb_aem_desc_entity { uint64_t entity_id; uint64_t entity_model_id; uint32_t entity_capabilities; uint16_t talker_stream_sources; uint16_t talker_capabilities; uint16_t listener_stream_sinks; uint16_t listener_capabilities; uint32_t controller_capabilities; uint32_t available_index; uint64_t association_id; char entity_name[64]; uint16_t vendor_name_string; uint16_t model_name_string; char firmware_version[64]; char group_name[64]; char serial_number[64]; uint16_t configurations_count; uint16_t current_configuration; } __attribute__ ((__packed__)); struct avb_aem_desc_descriptor_count { uint16_t descriptor_type; uint16_t descriptor_count; } __attribute__ ((__packed__)); struct avb_aem_desc_configuration { char object_name[64]; uint16_t localized_description; uint16_t descriptor_counts_count; uint16_t descriptor_counts_offset; struct avb_aem_desc_descriptor_count descriptor_counts[0]; } __attribute__ ((__packed__)); struct avb_aem_desc_sampling_rate { uint32_t pull_frequency; } __attribute__ ((__packed__)); struct avb_aem_desc_audio_unit { char object_name[64]; uint16_t localized_description; uint16_t clock_domain_index; uint16_t number_of_stream_input_ports; uint16_t base_stream_input_port; uint16_t number_of_stream_output_ports; uint16_t base_stream_output_port; uint16_t number_of_external_input_ports; uint16_t base_external_input_port; uint16_t number_of_external_output_ports; uint16_t base_external_output_port; uint16_t number_of_internal_input_ports; uint16_t base_internal_input_port; uint16_t number_of_internal_output_ports; uint16_t base_internal_output_port; uint16_t number_of_controls; uint16_t base_control; uint16_t number_of_signal_selectors; uint16_t base_signal_selector; uint16_t number_of_mixers; uint16_t base_mixer; uint16_t number_of_matrices; uint16_t base_matrix; uint16_t number_of_splitters; uint16_t base_splitter; uint16_t number_of_combiners; uint16_t base_combiner; uint16_t number_of_demultiplexers; uint16_t base_demultiplexer; uint16_t number_of_multiplexers; uint16_t base_multiplexer; uint16_t number_of_transcoders; uint16_t base_transcoder; uint16_t number_of_control_blocks; uint16_t base_control_block; uint32_t current_sampling_rate; uint16_t sampling_rates_offset; uint16_t sampling_rates_count; struct avb_aem_desc_sampling_rate sampling_rates[0]; } __attribute__ ((__packed__)); #define AVB_AEM_DESC_STREAM_FLAG_SYNC_SOURCE (1u<<0) #define AVB_AEM_DESC_STREAM_FLAG_CLASS_A (1u<<1) #define AVB_AEM_DESC_STREAM_FLAG_CLASS_B (1u<<2) #define AVB_AEM_DESC_STREAM_FLAG_SUPPORTS_ENCRYPTED (1u<<3) #define AVB_AEM_DESC_STREAM_FLAG_PRIMARY_BACKUP_SUPPORTED (1u<<4) #define AVB_AEM_DESC_STREAM_FLAG_PRIMARY_BACKUP_VALID (1u<<5) #define AVB_AEM_DESC_STREAM_FLAG_SECONDARY_BACKUP_SUPPORTED (1u<<6) #define AVB_AEM_DESC_STREAM_FLAG_SECONDARY_BACKUP_VALID (1u<<7) #define AVB_AEM_DESC_STREAM_FLAG_TERTIARY_BACKUP_SUPPORTED (1u<<8) #define AVB_AEM_DESC_STREAM_FLAG_TERTIARY_BACKUP_VALID (1u<<9) struct avb_aem_desc_stream { char object_name[64]; uint16_t localized_description; uint16_t clock_domain_index; uint16_t stream_flags; uint64_t current_format; uint16_t formats_offset; uint16_t number_of_formats; uint64_t backup_talker_entity_id_0; uint16_t backup_talker_unique_id_0; uint64_t backup_talker_entity_id_1; uint16_t backup_talker_unique_id_1; uint64_t backup_talker_entity_id_2; uint16_t backup_talker_unique_id_2; uint64_t backedup_talker_entity_id; uint16_t backedup_talker_unique; uint16_t avb_interface_index; uint32_t buffer_length; uint64_t stream_formats[0]; } __attribute__ ((__packed__)); #define AVB_AEM_DESC_AVB_INTERFACE_FLAG_GPTP_GRANDMASTER_SUPPORTED (1<<0) #define AVB_AEM_DESC_AVB_INTERFACE_FLAG_GPTP_SUPPORTED (1<<1) #define AVB_AEM_DESC_AVB_INTERFACE_FLAG_SRP_SUPPORTED (1<<2) struct avb_aem_desc_avb_interface { char object_name[64]; uint16_t localized_description; uint8_t mac_address[6]; uint16_t interface_flags; uint64_t clock_identity; uint8_t priority1; uint8_t clock_class; uint16_t offset_scaled_log_variance; uint8_t clock_accuracy; uint8_t priority2; uint8_t domain_number; int8_t log_sync_interval; int8_t log_announce_interval; int8_t log_pdelay_interval; uint16_t port_number; } __attribute__ ((__packed__)); #define AVB_AEM_DESC_CLOCK_SOURCE_TYPE_INTERNAL 0x0000 #define AVB_AEM_DESC_CLOCK_SOURCE_TYPE_EXTERNAL 0x0001 #define AVB_AEM_DESC_CLOCK_SOURCE_TYPE_INPUT_STREAM 0x0002 #define AVB_AEM_DESC_CLOCK_SOURCE_TYPE_MEDIA_CLOCK_STREAM 0x0003 #define AVB_AEM_DESC_CLOCK_SOURCE_TYPE_EXPANSION 0xffff struct avb_aem_desc_clock_source { char object_name[64]; uint16_t localized_description; uint16_t clock_source_flags; uint16_t clock_source_type; uint64_t clock_source_identifier; uint16_t clock_source_location_type; uint16_t clock_source_location_index; } __attribute__ ((__packed__)); struct avb_aem_desc_locale { char locale_identifier[64]; uint16_t number_of_strings; uint16_t base_strings; } __attribute__ ((__packed__)); struct avb_aem_desc_strings { char string_0[64]; char string_1[64]; char string_2[64]; char string_3[64]; char string_4[64]; char string_5[64]; char string_6[64]; } __attribute__ ((__packed__)); struct avb_aem_desc_stream_port { uint16_t clock_domain_index; uint16_t port_flags; uint16_t number_of_controls; uint16_t base_control; uint16_t number_of_clusters; uint16_t base_cluster; uint16_t number_of_maps; uint16_t base_map; } __attribute__ ((__packed__)); #endif /* AVB_AECP_AEM_DESCRIPTORS_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/modules/module-avb/aecp-aem.c000066400000000000000000000231211511204443500266030ustar00rootroot00000000000000/* AVB support */ /* SPDX-FileCopyrightText: Copyright © 2022 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include "aecp-aem.h" #include "aecp-aem-descriptors.h" static int reply_status(struct aecp *aecp, int status, const void *m, int len) { struct server *server = aecp->server; uint8_t buf[len]; struct avb_ethernet_header *h = (void*)buf; struct avb_packet_aecp_header *reply = SPA_PTROFF(h, sizeof(*h), void); memcpy(buf, m, len); AVB_PACKET_AECP_SET_MESSAGE_TYPE(reply, AVB_AECP_MESSAGE_TYPE_AEM_RESPONSE); AVB_PACKET_AECP_SET_STATUS(reply, status); return avb_server_send_packet(server, h->src, AVB_TSN_ETH, buf, len); } static int reply_not_implemented(struct aecp *aecp, const void *m, int len) { return reply_status(aecp, AVB_AECP_AEM_STATUS_NOT_IMPLEMENTED, m, len); } static int reply_success(struct aecp *aecp, const void *m, int len) { return reply_status(aecp, AVB_AECP_AEM_STATUS_SUCCESS, m, len); } /* ACQUIRE_ENTITY */ static int handle_acquire_entity(struct aecp *aecp, const void *m, int len) { struct server *server = aecp->server; const struct avb_packet_aecp_aem *p = m; const struct avb_packet_aecp_aem_acquire *ae; const struct descriptor *desc; uint16_t desc_type, desc_id; ae = (const struct avb_packet_aecp_aem_acquire*)p->payload; desc_type = ntohs(ae->descriptor_type); desc_id = ntohs(ae->descriptor_id); desc = server_find_descriptor(server, desc_type, desc_id); if (desc == NULL) return reply_status(aecp, AVB_AECP_AEM_STATUS_NO_SUCH_DESCRIPTOR, p, len); if (desc_type != AVB_AEM_DESC_ENTITY || desc_id != 0) return reply_not_implemented(aecp, m, len); return reply_success(aecp, m, len); } /* LOCK_ENTITY */ static int handle_lock_entity(struct aecp *aecp, const void *m, int len) { struct server *server = aecp->server; const struct avb_packet_aecp_aem *p = m; const struct avb_packet_aecp_aem_acquire *ae; const struct descriptor *desc; uint16_t desc_type, desc_id; ae = (const struct avb_packet_aecp_aem_acquire*)p->payload; desc_type = ntohs(ae->descriptor_type); desc_id = ntohs(ae->descriptor_id); desc = server_find_descriptor(server, desc_type, desc_id); if (desc == NULL) return reply_status(aecp, AVB_AECP_AEM_STATUS_NO_SUCH_DESCRIPTOR, p, len); if (desc_type != AVB_AEM_DESC_ENTITY || desc_id != 0) return reply_not_implemented(aecp, m, len); return reply_success(aecp, m, len); } /* READ_DESCRIPTOR */ static int handle_read_descriptor(struct aecp *aecp, const void *m, int len) { struct server *server = aecp->server; const struct avb_ethernet_header *h = m; const struct avb_packet_aecp_aem *p = SPA_PTROFF(h, sizeof(*h), void); struct avb_packet_aecp_aem *reply; const struct avb_packet_aecp_aem_read_descriptor *rd; uint16_t desc_type, desc_id; const struct descriptor *desc; uint8_t buf[2048]; size_t size, psize; rd = (struct avb_packet_aecp_aem_read_descriptor*)p->payload; desc_type = ntohs(rd->descriptor_type); desc_id = ntohs(rd->descriptor_id); pw_log_info("descriptor type:%04x index:%d", desc_type, desc_id); desc = server_find_descriptor(server, desc_type, desc_id); if (desc == NULL) return reply_status(aecp, AVB_AECP_AEM_STATUS_NO_SUCH_DESCRIPTOR, m, len); memcpy(buf, m, len); psize = sizeof(*rd); size = sizeof(*h) + sizeof(*reply) + psize; memcpy(buf + size, desc->ptr, desc->size); size += desc->size; psize += desc->size; h = (void*)buf; reply = SPA_PTROFF(h, sizeof(*h), void); AVB_PACKET_AECP_SET_MESSAGE_TYPE(&reply->aecp, AVB_AECP_MESSAGE_TYPE_AEM_RESPONSE); AVB_PACKET_AECP_SET_STATUS(&reply->aecp, AVB_AECP_AEM_STATUS_SUCCESS); AVB_PACKET_SET_LENGTH(&reply->aecp.hdr, psize + 12); return avb_server_send_packet(server, h->src, AVB_TSN_ETH, buf, size); } /* GET_AVB_INFO */ static int handle_get_avb_info(struct aecp *aecp, const void *m, int len) { struct server *server = aecp->server; const struct avb_ethernet_header *h = m; const struct avb_packet_aecp_aem *p = SPA_PTROFF(h, sizeof(*h), void); struct avb_packet_aecp_aem *reply; struct avb_packet_aecp_aem_get_avb_info *i; struct avb_aem_desc_avb_interface *avb_interface; uint16_t desc_type, desc_id; const struct descriptor *desc; uint8_t buf[2048]; size_t size, psize; i = (struct avb_packet_aecp_aem_get_avb_info*)p->payload; desc_type = ntohs(i->descriptor_type); desc_id = ntohs(i->descriptor_id); desc = server_find_descriptor(server, desc_type, desc_id); if (desc == NULL) return reply_status(aecp, AVB_AECP_AEM_STATUS_NO_SUCH_DESCRIPTOR, m, len); if (desc_type != AVB_AEM_DESC_AVB_INTERFACE || desc_id != 0) return reply_not_implemented(aecp, m, len); avb_interface = desc->ptr; memcpy(buf, m, len); psize = sizeof(*i); size = sizeof(*h) + sizeof(*reply) + psize; h = (void*)buf; reply = SPA_PTROFF(h, sizeof(*h), void); AVB_PACKET_AECP_SET_MESSAGE_TYPE(&reply->aecp, AVB_AECP_MESSAGE_TYPE_AEM_RESPONSE); AVB_PACKET_AECP_SET_STATUS(&reply->aecp, AVB_AECP_AEM_STATUS_SUCCESS); AVB_PACKET_SET_LENGTH(&reply->aecp.hdr, psize + 12); i = (struct avb_packet_aecp_aem_get_avb_info*)reply->payload; i->gptp_grandmaster_id = avb_interface->clock_identity; i->propagation_delay = htonl(0); i->gptp_domain_number = avb_interface->domain_number; i->flags = 0; i->msrp_mappings_count = htons(0); return avb_server_send_packet(server, h->src, AVB_TSN_ETH, buf, size); } /* AEM_COMMAND */ struct cmd_info { uint16_t type; const char *name; int (*handle) (struct aecp *aecp, const void *p, int len); }; static const struct cmd_info cmd_info[] = { { AVB_AECP_AEM_CMD_ACQUIRE_ENTITY, "acquire-entity", handle_acquire_entity, }, { AVB_AECP_AEM_CMD_LOCK_ENTITY, "lock-entity", handle_lock_entity, }, { AVB_AECP_AEM_CMD_ENTITY_AVAILABLE, "entity-available", NULL, }, { AVB_AECP_AEM_CMD_CONTROLLER_AVAILABLE, "controller-available", NULL, }, { AVB_AECP_AEM_CMD_READ_DESCRIPTOR, "read-descriptor", handle_read_descriptor, }, { AVB_AECP_AEM_CMD_WRITE_DESCRIPTOR, "write-descriptor", NULL, }, { AVB_AECP_AEM_CMD_SET_CONFIGURATION, "set-configuration", NULL, }, { AVB_AECP_AEM_CMD_GET_CONFIGURATION, "get-configuration", NULL, }, { AVB_AECP_AEM_CMD_SET_STREAM_FORMAT, "set-stream-format", NULL, }, { AVB_AECP_AEM_CMD_GET_STREAM_FORMAT, "get-stream-format", NULL, }, { AVB_AECP_AEM_CMD_SET_VIDEO_FORMAT, "set-video-format", NULL, }, { AVB_AECP_AEM_CMD_GET_VIDEO_FORMAT, "get-video-format", NULL, }, { AVB_AECP_AEM_CMD_SET_SENSOR_FORMAT, "set-sensor-format", NULL, }, { AVB_AECP_AEM_CMD_GET_SENSOR_FORMAT, "get-sensor-format", NULL, }, { AVB_AECP_AEM_CMD_SET_STREAM_INFO, "set-stream-info", NULL, }, { AVB_AECP_AEM_CMD_GET_STREAM_INFO, "get-stream-info", NULL, }, { AVB_AECP_AEM_CMD_SET_NAME, "set-name", NULL, }, { AVB_AECP_AEM_CMD_GET_NAME, "get-name", NULL, }, { AVB_AECP_AEM_CMD_SET_ASSOCIATION_ID, "set-association-id", NULL, }, { AVB_AECP_AEM_CMD_GET_ASSOCIATION_ID, "get-association-id", NULL, }, { AVB_AECP_AEM_CMD_SET_SAMPLING_RATE, "set-sampling-rate", NULL, }, { AVB_AECP_AEM_CMD_GET_SAMPLING_RATE, "get-sampling-rate", NULL, }, { AVB_AECP_AEM_CMD_SET_CLOCK_SOURCE, "set-clock-source", NULL, }, { AVB_AECP_AEM_CMD_GET_CLOCK_SOURCE, "get-clock-source", NULL, }, { AVB_AECP_AEM_CMD_SET_CONTROL, "set-control", NULL, }, { AVB_AECP_AEM_CMD_GET_CONTROL, "get-control", NULL, }, { AVB_AECP_AEM_CMD_INCREMENT_CONTROL, "increment-control", NULL, }, { AVB_AECP_AEM_CMD_DECREMENT_CONTROL, "decrement-control", NULL, }, { AVB_AECP_AEM_CMD_SET_SIGNAL_SELECTOR, "set-signal-selector", NULL, }, { AVB_AECP_AEM_CMD_GET_SIGNAL_SELECTOR, "get-signal-selector", NULL, }, { AVB_AECP_AEM_CMD_SET_MIXER, "set-mixer", NULL, }, { AVB_AECP_AEM_CMD_GET_MIXER, "get-mixer", NULL, }, { AVB_AECP_AEM_CMD_SET_MATRIX, "set-matrix", NULL, }, { AVB_AECP_AEM_CMD_GET_MATRIX, "get-matrix", NULL, }, { AVB_AECP_AEM_CMD_START_STREAMING, "start-streaming", NULL, }, { AVB_AECP_AEM_CMD_STOP_STREAMING, "stop-streaming", NULL, }, { AVB_AECP_AEM_CMD_REGISTER_UNSOLICITED_NOTIFICATION, "register-unsolicited-notification", NULL, }, { AVB_AECP_AEM_CMD_DEREGISTER_UNSOLICITED_NOTIFICATION, "deregister-unsolicited-notification", NULL, }, { AVB_AECP_AEM_CMD_IDENTIFY_NOTIFICATION, "identify-notification", NULL, }, { AVB_AECP_AEM_CMD_GET_AVB_INFO, "get-avb-info", handle_get_avb_info, }, { AVB_AECP_AEM_CMD_GET_AS_PATH, "get-as-path", NULL, }, { AVB_AECP_AEM_CMD_GET_COUNTERS, "get-counters", NULL, }, { AVB_AECP_AEM_CMD_REBOOT, "reboot", NULL, }, { AVB_AECP_AEM_CMD_GET_AUDIO_MAP, "get-audio-map", NULL, }, { AVB_AECP_AEM_CMD_ADD_AUDIO_MAPPINGS, "add-audio-mappings", NULL, }, { AVB_AECP_AEM_CMD_REMOVE_AUDIO_MAPPINGS, "remove-audio-mappings", NULL, }, { AVB_AECP_AEM_CMD_GET_VIDEO_MAP, "get-video-map", NULL, }, { AVB_AECP_AEM_CMD_ADD_VIDEO_MAPPINGS, "add-video-mappings", NULL, }, { AVB_AECP_AEM_CMD_REMOVE_VIDEO_MAPPINGS, "remove-video-mappings", NULL, }, { AVB_AECP_AEM_CMD_GET_SENSOR_MAP, "get-sensor-map", NULL, } }; static inline const struct cmd_info *find_cmd_info(uint16_t type, const char *name) { SPA_FOR_EACH_ELEMENT_VAR(cmd_info, i) { if ((name == NULL && type == i->type) || (name != NULL && spa_streq(name, i->name))) return i; } return NULL; } int avb_aecp_aem_handle_command(struct aecp *aecp, const void *m, int len) { const struct avb_ethernet_header *h = m; const struct avb_packet_aecp_aem *p = SPA_PTROFF(h, sizeof(*h), void); uint16_t cmd_type; const struct cmd_info *info; cmd_type = AVB_PACKET_AEM_GET_COMMAND_TYPE(p); info = find_cmd_info(cmd_type, NULL); if (info == NULL) return reply_not_implemented(aecp, m, len); pw_log_info("aem command %s", info->name); if (info->handle == NULL) return reply_not_implemented(aecp, m, len); return info->handle(aecp, m, len); } int avb_aecp_aem_handle_response(struct aecp *aecp, const void *m, int len) { return 0; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/modules/module-avb/aecp-aem.h000066400000000000000000000253641511204443500266230ustar00rootroot00000000000000/* AVB support */ /* SPDX-FileCopyrightText: Copyright © 2022 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef AVB_AEM_H #define AVB_AEM_H #include "aecp.h" #define AVB_AECP_AEM_STATUS_SUCCESS 0 #define AVB_AECP_AEM_STATUS_NOT_IMPLEMENTED 1 #define AVB_AECP_AEM_STATUS_NO_SUCH_DESCRIPTOR 2 #define AVB_AECP_AEM_STATUS_ENTITY_LOCKED 3 #define AVB_AECP_AEM_STATUS_ENTITY_ACQUIRED 4 #define AVB_AECP_AEM_STATUS_NOT_AUTHENTICATED 5 #define AVB_AECP_AEM_STATUS_AUTHENTICATION_DISABLED 6 #define AVB_AECP_AEM_STATUS_BAD_ARGUMENTS 7 #define AVB_AECP_AEM_STATUS_NO_RESOURCES 8 #define AVB_AECP_AEM_STATUS_IN_PROGRESS 9 #define AVB_AECP_AEM_STATUS_ENTITY_MISBEHAVING 10 #define AVB_AECP_AEM_STATUS_NOT_SUPPORTED 11 #define AVB_AECP_AEM_STATUS_STREAM_IS_RUNNING 12 #define AVB_AECP_AEM_CMD_ACQUIRE_ENTITY 0x0000 #define AVB_AECP_AEM_CMD_LOCK_ENTITY 0x0001 #define AVB_AECP_AEM_CMD_ENTITY_AVAILABLE 0x0002 #define AVB_AECP_AEM_CMD_CONTROLLER_AVAILABLE 0x0003 #define AVB_AECP_AEM_CMD_READ_DESCRIPTOR 0x0004 #define AVB_AECP_AEM_CMD_WRITE_DESCRIPTOR 0x0005 #define AVB_AECP_AEM_CMD_SET_CONFIGURATION 0x0006 #define AVB_AECP_AEM_CMD_GET_CONFIGURATION 0x0007 #define AVB_AECP_AEM_CMD_SET_STREAM_FORMAT 0x0008 #define AVB_AECP_AEM_CMD_GET_STREAM_FORMAT 0x0009 #define AVB_AECP_AEM_CMD_SET_VIDEO_FORMAT 0x000a #define AVB_AECP_AEM_CMD_GET_VIDEO_FORMAT 0x000b #define AVB_AECP_AEM_CMD_SET_SENSOR_FORMAT 0x000c #define AVB_AECP_AEM_CMD_GET_SENSOR_FORMAT 0x000d #define AVB_AECP_AEM_CMD_SET_STREAM_INFO 0x000e #define AVB_AECP_AEM_CMD_GET_STREAM_INFO 0x000f #define AVB_AECP_AEM_CMD_SET_NAME 0x0010 #define AVB_AECP_AEM_CMD_GET_NAME 0x0011 #define AVB_AECP_AEM_CMD_SET_ASSOCIATION_ID 0x0012 #define AVB_AECP_AEM_CMD_GET_ASSOCIATION_ID 0x0013 #define AVB_AECP_AEM_CMD_SET_SAMPLING_RATE 0x0014 #define AVB_AECP_AEM_CMD_GET_SAMPLING_RATE 0x0015 #define AVB_AECP_AEM_CMD_SET_CLOCK_SOURCE 0x0016 #define AVB_AECP_AEM_CMD_GET_CLOCK_SOURCE 0x0017 #define AVB_AECP_AEM_CMD_SET_CONTROL 0x0018 #define AVB_AECP_AEM_CMD_GET_CONTROL 0x0019 #define AVB_AECP_AEM_CMD_INCREMENT_CONTROL 0x001a #define AVB_AECP_AEM_CMD_DECREMENT_CONTROL 0x001b #define AVB_AECP_AEM_CMD_SET_SIGNAL_SELECTOR 0x001c #define AVB_AECP_AEM_CMD_GET_SIGNAL_SELECTOR 0x001d #define AVB_AECP_AEM_CMD_SET_MIXER 0x001e #define AVB_AECP_AEM_CMD_GET_MIXER 0x001f #define AVB_AECP_AEM_CMD_SET_MATRIX 0x0020 #define AVB_AECP_AEM_CMD_GET_MATRIX 0x0021 #define AVB_AECP_AEM_CMD_START_STREAMING 0x0022 #define AVB_AECP_AEM_CMD_STOP_STREAMING 0x0023 #define AVB_AECP_AEM_CMD_REGISTER_UNSOLICITED_NOTIFICATION 0x0024 #define AVB_AECP_AEM_CMD_DEREGISTER_UNSOLICITED_NOTIFICATION 0x0025 #define AVB_AECP_AEM_CMD_IDENTIFY_NOTIFICATION 0x0026 #define AVB_AECP_AEM_CMD_GET_AVB_INFO 0x0027 #define AVB_AECP_AEM_CMD_GET_AS_PATH 0x0028 #define AVB_AECP_AEM_CMD_GET_COUNTERS 0x0029 #define AVB_AECP_AEM_CMD_REBOOT 0x002a #define AVB_AECP_AEM_CMD_GET_AUDIO_MAP 0x002b #define AVB_AECP_AEM_CMD_ADD_AUDIO_MAPPINGS 0x002c #define AVB_AECP_AEM_CMD_REMOVE_AUDIO_MAPPINGS 0x002d #define AVB_AECP_AEM_CMD_GET_VIDEO_MAP 0x002e #define AVB_AECP_AEM_CMD_ADD_VIDEO_MAPPINGS 0x002f #define AVB_AECP_AEM_CMD_REMOVE_VIDEO_MAPPINGS 0x0030 #define AVB_AECP_AEM_CMD_GET_SENSOR_MAP 0x0031 #define AVB_AECP_AEM_CMD_ADD_SENSOR_MAPPINGS 0x0032 #define AVB_AECP_AEM_CMD_REMOVE_SENSOR_MAPPINGS 0x0033 #define AVB_AECP_AEM_CMD_START_OPERATION 0x0034 #define AVB_AECP_AEM_CMD_ABORT_OPERATION 0x0035 #define AVB_AECP_AEM_CMD_OPERATION_STATUS 0x0036 #define AVB_AECP_AEM_CMD_AUTH_ADD_KEY 0x0037 #define AVB_AECP_AEM_CMD_AUTH_DELETE_KEY 0x0038 #define AVB_AECP_AEM_CMD_AUTH_GET_KEY_LIST 0x0039 #define AVB_AECP_AEM_CMD_AUTH_GET_KEY 0x003a #define AVB_AECP_AEM_CMD_AUTH_ADD_KEY_TO_CHAIN 0x003b #define AVB_AECP_AEM_CMD_AUTH_DELETE_KEY_FROM_CHAIN 0x003c #define AVB_AECP_AEM_CMD_AUTH_GET_KEYCHAIN_LIST 0x003d #define AVB_AECP_AEM_CMD_AUTH_GET_IDENTITY 0x003e #define AVB_AECP_AEM_CMD_AUTH_ADD_TOKEN 0x003f #define AVB_AECP_AEM_CMD_AUTH_DELETE_TOKEN 0x0040 #define AVB_AECP_AEM_CMD_AUTHENTICATE 0x0041 #define AVB_AECP_AEM_CMD_DEAUTHENTICATE 0x0042 #define AVB_AECP_AEM_CMD_ENABLE_TRANSPORT_SECURITY 0x0043 #define AVB_AECP_AEM_CMD_DISABLE_TRANSPORT_SECURITY 0x0044 #define AVB_AECP_AEM_CMD_ENABLE_STREAM_ENCRYPTION 0x0045 #define AVB_AECP_AEM_CMD_DISABLE_STREAM_ENCRYPTION 0x0046 #define AVB_AECP_AEM_CMD_SET_MEMORY_OBJECT_LENGTH 0x0047 #define AVB_AECP_AEM_CMD_GET_MEMORY_OBJECT_LENGTH 0x0048 #define AVB_AECP_AEM_CMD_SET_STREAM_BACKUP 0x0049 #define AVB_AECP_AEM_CMD_GET_STREAM_BACKUP 0x004a #define AVB_AECP_AEM_CMD_EXPANSION 0x7fff #define AVB_AEM_ACQUIRE_ENTITY_PERSISTENT_FLAG (1<<0) struct avb_packet_aecp_aem_acquire { uint32_t flags; uint64_t owner_guid; uint16_t descriptor_type; uint16_t descriptor_id; } __attribute__ ((__packed__)); struct avb_packet_aecp_aem_lock { uint32_t flags; uint64_t locked_guid; uint16_t descriptor_type; uint16_t descriptor_id; } __attribute__ ((__packed__)); struct avb_packet_aecp_aem_read_descriptor { uint16_t configuration; uint8_t reserved[2]; uint16_t descriptor_type; uint16_t descriptor_id; } __attribute__ ((__packed__)); struct avb_packet_aecp_aem_setget_configuration { uint16_t reserved; uint16_t configuration_index; } __attribute__ ((__packed__)); struct avb_packet_aecp_aem_setget_stream_format { uint16_t descriptor_type; uint16_t descriptor_id; uint64_t stream_format; } __attribute__ ((__packed__)); struct avb_packet_aecp_aem_setget_video_format { uint16_t descriptor_type; uint16_t descriptor_id; uint32_t format_specific; uint16_t aspect_ratio; uint16_t color_space; uint32_t frame_size; } __attribute__ ((__packed__)); struct avb_packet_aecp_aem_setget_sensor_format { uint16_t descriptor_type; uint16_t descriptor_id; uint64_t sensor_format; } __attribute__ ((__packed__)); #define AVB_AEM_STREAM_INFO_FLAG_CLASS_B (1u<<0) #define AVB_AEM_STREAM_INFO_FLAG_FAST_CONNECT (1u<<1) #define AVB_AEM_STREAM_INFO_FLAG_SAVED_STATE (1u<<2) #define AVB_AEM_STREAM_INFO_FLAG_STREAMING_WAIT (1u<<3) #define AVB_AEM_STREAM_INFO_FLAG_ENCRYPTED_PDU (1u<<4) #define AVB_AEM_STREAM_INFO_FLAG_STREAM_VLAN_ID_VALID (1u<<25) #define AVB_AEM_STREAM_INFO_FLAG_CONNECTED (1u<<26) #define AVB_AEM_STREAM_INFO_FLAG_MSRP_FAILURE_VALID (1u<<27) #define AVB_AEM_STREAM_INFO_FLAG_STREAM_DEST_MAC_VALID (1u<<28) #define AVB_AEM_STREAM_INFO_FLAG_MSRP_ACC_LAT_VALID (1u<<29) #define AVB_AEM_STREAM_INFO_FLAG_STREAM_ID_VALID (1u<<30) #define AVB_AEM_STREAM_INFO_FLAG_STREAM_FORMAT_VALID (1u<<31) struct avb_packet_aecp_aem_setget_stream_info { uint16_t descriptor_type; uint16_t descriptor_index; uint32_t aem_stream_info_flags; uint64_t stream_format; uint64_t stream_id; uint32_t msrp_accumulated_latency; uint8_t stream_dest_mac[6]; uint8_t msrp_failure_code; uint8_t reserved; uint64_t msrp_failure_bridge_id; uint16_t stream_vlan_id; uint16_t reserved2; } __attribute__ ((__packed__)); struct avb_packet_aecp_aem_setget_name { uint16_t descriptor_type; uint16_t descriptor_index; uint16_t name_index; uint16_t configuration_index; char name[64]; } __attribute__ ((__packed__)); struct avb_packet_aecp_aem_setget_association_id { uint16_t descriptor_type; uint16_t descriptor_index; uint64_t association_id; } __attribute__ ((__packed__)); struct avb_packet_aecp_aem_setget_sampling_rate { uint16_t descriptor_type; uint16_t descriptor_id; uint32_t sampling_rate; } __attribute__ ((__packed__)); struct avb_packet_aecp_aem_setget_clock_source { uint16_t descriptor_type; uint16_t descriptor_id; uint16_t clock_source_index; uint16_t reserved; } __attribute__ ((__packed__)); struct avb_packet_aecp_aem_setget_control { uint16_t descriptor_type; uint16_t descriptor_id; } __attribute__ ((__packed__)); struct avb_packet_aecp_aem_incdec_control { uint16_t descriptor_type; uint16_t descriptor_id; uint16_t index_count; uint16_t reserved; } __attribute__ ((__packed__)); struct avb_packet_aecp_aem_setget_signal_selector { uint16_t descriptor_type; uint16_t descriptor_id; uint16_t signal_type; uint16_t signal_index; uint16_t signal_output; uint16_t reserved; } __attribute__ ((__packed__)); struct avb_packet_aecp_aem_setget_mixer { uint16_t descriptor_type; uint16_t descriptor_id; } __attribute__ ((__packed__)); struct avb_packet_aecp_aem_setget_matrix { uint16_t descriptor_type; uint16_t descriptor_index; uint16_t matrix_column; uint16_t matrix_row; uint16_t region_width; uint16_t region_height; uint16_t rep_direction_value_count; uint16_t item_offset; } __attribute__ ((__packed__)); struct avb_packet_aecp_aem_startstop_streaming { uint16_t descriptor_type; uint16_t descriptor_id; } __attribute__ ((__packed__)); struct avb_packet_aecp_aem_identify_notification { uint16_t descriptor_type; uint16_t descriptor_id; } __attribute__ ((__packed__)); struct avb_packet_aecp_aem_msrp_mapping { uint8_t traffic_class; uint8_t priority; uint16_t vlan_id; } __attribute__ ((__packed__)); #define AVB_AEM_AVB_INFO_FLAG_GPTP_GRANDMASTER_SUPPORTED (1u<<0) #define AVB_AEM_AVB_INFO_FLAG_GPTP_ENABLED (1u<<1) #define AVB_AEM_AVB_INFO_FLAG_SRP_ENABLED (1u<<2) struct avb_packet_aecp_aem_get_avb_info { uint16_t descriptor_type; uint16_t descriptor_id; uint64_t gptp_grandmaster_id; uint32_t propagation_delay; uint8_t gptp_domain_number; uint8_t flags; uint16_t msrp_mappings_count; uint8_t msrp_mappings[0]; } __attribute__ ((__packed__)); struct avb_packet_aecp_aem_get_as_path { uint16_t descriptor_index; uint16_t reserved; } __attribute__ ((__packed__)); struct avb_packet_aecp_aem_get_counters { uint16_t descriptor_type; uint16_t descriptor_id; uint32_t counters_valid; uint8_t counters_block[0]; } __attribute__ ((__packed__)); struct avb_packet_aecp_aem_reboot { uint16_t descriptor_type; uint16_t descriptor_id; } __attribute__ ((__packed__)); struct avb_packet_aecp_aem_start_operation { uint16_t descriptor_type; uint16_t descriptor_id; uint16_t operation_id; uint16_t operation_type; } __attribute__ ((__packed__)); struct avb_packet_aecp_aem_operation_status { uint16_t descriptor_type; uint16_t descriptor_id; uint16_t operation_id; uint16_t percent_complete; } __attribute__ ((__packed__)); struct avb_packet_aecp_aem { struct avb_packet_aecp_header aecp; #if __BYTE_ORDER == __BIG_ENDIAN unsigned u:1; unsigned cmd1:7; #elif __BYTE_ORDER == __LITTLE_ENDIAN unsigned cmd1:7; unsigned u:1; #endif uint8_t cmd2; uint8_t payload[0]; } __attribute__ ((__packed__)); #define AVB_PACKET_AEM_SET_COMMAND_TYPE(p,v) ((p)->cmd1 = ((v) >> 8),(p)->cmd2 = (v)) #define AVB_PACKET_AEM_GET_COMMAND_TYPE(p) ((p)->cmd1 << 8 | (p)->cmd2) int avb_aecp_aem_handle_command(struct aecp *aecp, const void *m, int len); int avb_aecp_aem_handle_response(struct aecp *aecp, const void *m, int len); #endif /* AVB_AEM_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/modules/module-avb/aecp.c000066400000000000000000000076221511204443500260530ustar00rootroot00000000000000/* AVB support */ /* SPDX-FileCopyrightText: Copyright © 2022 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include #include "aecp.h" #include "aecp-aem.h" #include "internal.h" static const uint8_t mac[6] = AVB_BROADCAST_MAC; struct msg_info { uint16_t type; const char *name; int (*handle) (struct aecp *aecp, const void *p, int len); }; static int reply_not_implemented(struct aecp *aecp, const void *p, int len) { struct server *server = aecp->server; uint8_t buf[len]; struct avb_ethernet_header *h = (void*)buf; struct avb_packet_aecp_header *reply = SPA_PTROFF(h, sizeof(*h), void); memcpy(h, p, len); AVB_PACKET_AECP_SET_STATUS(reply, AVB_AECP_STATUS_NOT_IMPLEMENTED); return avb_server_send_packet(server, h->src, AVB_TSN_ETH, buf, len); } static const struct msg_info msg_info[] = { { AVB_AECP_MESSAGE_TYPE_AEM_COMMAND, "aem-command", avb_aecp_aem_handle_command, }, { AVB_AECP_MESSAGE_TYPE_AEM_RESPONSE, "aem-response", avb_aecp_aem_handle_response, }, { AVB_AECP_MESSAGE_TYPE_ADDRESS_ACCESS_COMMAND, "address-access-command", NULL, }, { AVB_AECP_MESSAGE_TYPE_ADDRESS_ACCESS_RESPONSE, "address-access-response", NULL, }, { AVB_AECP_MESSAGE_TYPE_AVC_COMMAND, "avc-command", NULL, }, { AVB_AECP_MESSAGE_TYPE_AVC_RESPONSE, "avc-response", NULL, }, { AVB_AECP_MESSAGE_TYPE_VENDOR_UNIQUE_COMMAND, "vendor-unique-command", NULL, }, { AVB_AECP_MESSAGE_TYPE_VENDOR_UNIQUE_RESPONSE, "vendor-unique-response", NULL, }, { AVB_AECP_MESSAGE_TYPE_EXTENDED_COMMAND, "extended-command", NULL, }, { AVB_AECP_MESSAGE_TYPE_EXTENDED_RESPONSE, "extended-response", NULL, }, }; static inline const struct msg_info *find_msg_info(uint16_t type, const char *name) { SPA_FOR_EACH_ELEMENT_VAR(msg_info, i) { if ((name == NULL && type == i->type) || (name != NULL && spa_streq(name, i->name))) return i; } return NULL; } static int aecp_message(void *data, uint64_t now, const void *message, int len) { struct aecp *aecp = data; struct server *server = aecp->server; const struct avb_ethernet_header *h = message; const struct avb_packet_aecp_header *p = SPA_PTROFF(h, sizeof(*h), void); const struct msg_info *info; int message_type; if (ntohs(h->type) != AVB_TSN_ETH) return 0; if (memcmp(h->dest, mac, 6) != 0 && memcmp(h->dest, server->mac_addr, 6) != 0) return 0; if (AVB_PACKET_GET_SUBTYPE(&p->hdr) != AVB_SUBTYPE_AECP) return 0; message_type = AVB_PACKET_AECP_GET_MESSAGE_TYPE(p); info = find_msg_info(message_type, NULL); if (info == NULL) return reply_not_implemented(aecp, message, len); pw_log_debug("got AECP message %s", info->name); if (info->handle == NULL) return reply_not_implemented(aecp, message, len); return info->handle(aecp, message, len); } static void aecp_destroy(void *data) { struct aecp *aecp = data; spa_hook_remove(&aecp->server_listener); free(aecp); } static int do_help(struct aecp *aecp, const char *args, FILE *out) { fprintf(out, "{ \"type\": \"help\"," "\"text\": \"" "/adp/help: this help \\n" "\" }"); return 0; } static int aecp_command(void *data, uint64_t now, const char *command, const char *args, FILE *out) { struct aecp *aecp = data; int res; if (!spa_strstartswith(command, "/aecp/")) return 0; command += strlen("/aecp/"); if (spa_streq(command, "help")) res = do_help(aecp, args, out); else res = -ENOTSUP; return res; } static const struct server_events server_events = { AVB_VERSION_SERVER_EVENTS, .destroy = aecp_destroy, .message = aecp_message, .command = aecp_command }; struct avb_aecp *avb_aecp_register(struct server *server) { struct aecp *aecp; aecp = calloc(1, sizeof(*aecp)); if (aecp == NULL) return NULL; aecp->server = server; avdecc_server_add_listener(server, &aecp->server_listener, &server_events, aecp); return (struct avb_aecp*)aecp; } void avb_aecp_unregister(struct avb_aecp *aecp) { aecp_destroy(aecp); } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/modules/module-avb/aecp.h000066400000000000000000000025001511204443500260460ustar00rootroot00000000000000/* AVB support */ /* SPDX-FileCopyrightText: Copyright © 2022 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef AVB_AECP_H #define AVB_AECP_H #include "packets.h" #include "internal.h" #define AVB_AECP_MESSAGE_TYPE_AEM_COMMAND 0 #define AVB_AECP_MESSAGE_TYPE_AEM_RESPONSE 1 #define AVB_AECP_MESSAGE_TYPE_ADDRESS_ACCESS_COMMAND 2 #define AVB_AECP_MESSAGE_TYPE_ADDRESS_ACCESS_RESPONSE 3 #define AVB_AECP_MESSAGE_TYPE_AVC_COMMAND 4 #define AVB_AECP_MESSAGE_TYPE_AVC_RESPONSE 5 #define AVB_AECP_MESSAGE_TYPE_VENDOR_UNIQUE_COMMAND 6 #define AVB_AECP_MESSAGE_TYPE_VENDOR_UNIQUE_RESPONSE 7 #define AVB_AECP_MESSAGE_TYPE_EXTENDED_COMMAND 14 #define AVB_AECP_MESSAGE_TYPE_EXTENDED_RESPONSE 15 #define AVB_AECP_STATUS_SUCCESS 0 #define AVB_AECP_STATUS_NOT_IMPLEMENTED 1 struct avb_packet_aecp_header { struct avb_packet_header hdr; uint64_t target_guid; uint64_t controller_guid; uint16_t sequence_id; } __attribute__ ((__packed__)); #define AVB_PACKET_AECP_SET_MESSAGE_TYPE(p,v) AVB_PACKET_SET_SUB1(&(p)->hdr, v) #define AVB_PACKET_AECP_SET_STATUS(p,v) AVB_PACKET_SET_SUB2(&(p)->hdr, v) #define AVB_PACKET_AECP_GET_MESSAGE_TYPE(p) AVB_PACKET_GET_SUB1(&(p)->hdr) #define AVB_PACKET_AECP_GET_STATUS(p) AVB_PACKET_GET_SUB2(&(p)->hdr) struct avb_aecp *avb_aecp_register(struct server *server); #endif /* AVB_AECP_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/modules/module-avb/avb.c000066400000000000000000000042011511204443500257010ustar00rootroot00000000000000/* PipeWire */ /* SPDX-FileCopyrightText: Copyright © 2022 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include "avb.h" #include "internal.h" #include struct pw_avb *pw_avb_new(struct pw_context *context, struct pw_properties *props, size_t user_data_size) { struct impl *impl; const struct spa_support *support; uint32_t n_support; struct spa_cpu *cpu; const char *str; int res = 0; impl = calloc(1, sizeof(*impl) + user_data_size); if (impl == NULL) goto error_exit; if (props == NULL) props = pw_properties_new(NULL, NULL); if (props == NULL) goto error_free; support = pw_context_get_support(context, &n_support); cpu = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_CPU); pw_context_conf_update_props(context, "avb.properties", props); if ((str = pw_properties_get(props, "vm.overrides")) != NULL) { pw_log_warn("vm.overrides in avb.properties are deprecated, " "use avb.properties.rules instead"); if (cpu != NULL && spa_cpu_get_vm_type(cpu) != SPA_CPU_VM_NONE) pw_properties_update_string(props, str, strlen(str)); pw_properties_set(props, "vm.overrides", NULL); } impl->context = context; impl->loop = pw_context_get_main_loop(context); impl->timer_queue = pw_context_get_timer_queue(context); impl->props = props; impl->core = pw_context_get_object(context, PW_TYPE_INTERFACE_Core); if (impl->core == NULL) { str = pw_properties_get(props, PW_KEY_REMOTE_NAME); impl->core = pw_context_connect(context, pw_properties_new( PW_KEY_REMOTE_NAME, str, NULL), 0); impl->do_disconnect = true; } if (impl->core == NULL) { res = -errno; pw_log_error("can't connect: %m"); goto error_free; } spa_list_init(&impl->servers); avdecc_server_new(impl, &props->dict); return (struct pw_avb*)impl; error_free: free(impl); error_exit: pw_properties_free(props); if (res < 0) errno = -res; return NULL; } static void impl_free(struct impl *impl) { struct server *s; spa_list_consume(s, &impl->servers, link) avdecc_server_free(s); free(impl); } void pw_avb_destroy(struct pw_avb *avb) { struct impl *impl = (struct impl*)avb; impl_free(impl); } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/modules/module-avb/avb.h000066400000000000000000000007661511204443500257220ustar00rootroot00000000000000/* PipeWire */ /* SPDX-FileCopyrightText: Copyright © 2022 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef PIPEWIRE_AVB_H #define PIPEWIRE_AVB_H #include #ifdef __cplusplus extern "C" { #endif struct pw_context; struct pw_properties; struct pw_avb; struct pw_avb *pw_avb_new(struct pw_context *context, struct pw_properties *props, size_t user_data_size); void pw_avb_destroy(struct pw_avb *avb); #ifdef __cplusplus } /* extern "C" */ #endif #endif /* PIPEWIRE_AVB_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/modules/module-avb/avdecc.c000066400000000000000000000206301511204443500263620ustar00rootroot00000000000000/* PipeWire */ /* SPDX-FileCopyrightText: Copyright © 2022 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "avb.h" #include "packets.h" #include "internal.h" #include "stream.h" #include "acmp.h" #include "adp.h" #include "aecp.h" #include "maap.h" #include "mmrp.h" #include "msrp.h" #include "mvrp.h" #include "descriptors.h" #include "utils.h" #define DEFAULT_INTERVAL 1 #define server_emit(s,m,v,...) spa_hook_list_call(&s->listener_list, struct server_events, m, v, ##__VA_ARGS__) #define server_emit_destroy(s) server_emit(s, destroy, 0) #define server_emit_message(s,n,m,l) server_emit(s, message, 0, n, m, l) #define server_emit_periodic(s,n) server_emit(s, periodic, 0, n) #define server_emit_command(s,n,c,a,f) server_emit(s, command, 0, n, c, a, f) static void on_timer_event(void *data) { struct server *server = data; struct impl *impl = server->impl; struct timespec now; clock_gettime(CLOCK_REALTIME, &now); server_emit_periodic(server, SPA_TIMESPEC_TO_NSEC(&now)); pw_timer_queue_add(impl->timer_queue, &server->timer, &server->timer.timeout, DEFAULT_INTERVAL * SPA_NSEC_PER_SEC, on_timer_event, server); } static void on_socket_data(void *data, int fd, uint32_t mask) { struct server *server = data; struct timespec now; if (mask & SPA_IO_IN) { int len; uint8_t buffer[2048]; len = recv(fd, buffer, sizeof(buffer), 0); if (len < 0) { pw_log_warn("got recv error: %m"); } else if (len < (int)sizeof(struct avb_packet_header)) { pw_log_warn("short packet received (%d < %d)", len, (int)sizeof(struct avb_packet_header)); } else { clock_gettime(CLOCK_REALTIME, &now); server_emit_message(server, SPA_TIMESPEC_TO_NSEC(&now), buffer, len); } } } int avb_server_send_packet(struct server *server, const uint8_t dest[6], uint16_t type, void *data, size_t size) { struct avb_ethernet_header *hdr = (struct avb_ethernet_header*)data; int res = 0; memcpy(hdr->dest, dest, ETH_ALEN); memcpy(hdr->src, server->mac_addr, ETH_ALEN); hdr->type = htons(type); if (send(server->source->fd, data, size, 0) < 0) { res = -errno; pw_log_warn("got send error: %m"); } return res; } static int load_filter(int fd, uint16_t eth, const uint8_t dest[6], const uint8_t mac[6]) { struct sock_fprog filter; struct sock_filter bpf_code[] = { BPF_STMT(BPF_LD|BPF_H|BPF_ABS, 12), BPF_JUMP(BPF_JMP|BPF_JEQ, eth, 0, 8), BPF_STMT(BPF_LD|BPF_W|BPF_ABS, 2), BPF_JUMP(BPF_JMP|BPF_JEQ, (dest[2] << 24) | (dest[3] << 16) | (dest[4] << 8) | (dest[5]), 0, 2), BPF_STMT(BPF_LD|BPF_H|BPF_ABS, 0), BPF_JUMP(BPF_JMP|BPF_JEQ, (dest[0] << 8) | (dest[1]), 3, 4), BPF_JUMP(BPF_JMP|BPF_JEQ, (mac[2] << 24) | (mac[3] << 16) | (mac[4] << 8) | (mac[5]), 0, 3), BPF_STMT(BPF_LD|BPF_H|BPF_ABS, 0), BPF_JUMP(BPF_JMP|BPF_JEQ, (mac[0] << 8) | (mac[1]), 0, 1), BPF_STMT(BPF_RET, 0x00040000), BPF_STMT(BPF_RET, 0x00000000), }; filter.len = sizeof(bpf_code) / 8; filter.filter = bpf_code; if (setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &filter, sizeof(filter)) < 0) { pw_log_error("setsockopt(ATTACH_FILTER) failed: %m"); return -errno; } return 0; } int avb_server_make_socket(struct server *server, uint16_t type, const uint8_t mac[6]) { int fd, res; struct ifreq req; struct packet_mreq mreq; struct sockaddr_ll sll; fd = socket(AF_PACKET, SOCK_RAW|SOCK_NONBLOCK, htons(ETH_P_ALL)); if (fd < 0) { pw_log_error("socket() failed: %m"); return -errno; } spa_zero(req); snprintf(req.ifr_name, sizeof(req.ifr_name), "%s", server->ifname); if (ioctl(fd, SIOCGIFINDEX, &req) < 0) { res = -errno; pw_log_error("SIOCGIFINDEX %s failed: %m", server->ifname); goto error_close; } server->ifindex = req.ifr_ifindex; spa_zero(req); snprintf(req.ifr_name, sizeof(req.ifr_name), "%s", server->ifname); if (ioctl(fd, SIOCGIFHWADDR, &req) < 0) { res = -errno; pw_log_error("SIOCGIFHWADDR %s failed: %m", server->ifname); goto error_close; } memcpy(server->mac_addr, req.ifr_hwaddr.sa_data, sizeof(server->mac_addr)); server->entity_id = (uint64_t)server->mac_addr[0] << 56 | (uint64_t)server->mac_addr[1] << 48 | (uint64_t)server->mac_addr[2] << 40 | (uint64_t)0xff << 32 | (uint64_t)0xfe << 24 | (uint64_t)server->mac_addr[3] << 16 | (uint64_t)server->mac_addr[4] << 8 | (uint64_t)server->mac_addr[5]; spa_zero(sll); sll.sll_family = AF_PACKET; sll.sll_protocol = htons(ETH_P_ALL); sll.sll_ifindex = server->ifindex; if (bind(fd, (struct sockaddr *) &sll, sizeof(sll)) < 0) { res = -errno; pw_log_error("bind() failed: %m"); goto error_close; } spa_zero(mreq); mreq.mr_ifindex = server->ifindex; mreq.mr_type = PACKET_MR_MULTICAST; mreq.mr_alen = ETH_ALEN; memcpy(mreq.mr_address, mac, ETH_ALEN); if (setsockopt(fd, SOL_PACKET, PACKET_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) < 0) { res = -errno; pw_log_error("setsockopt(ADD_MEMBERSHIP) failed: %m"); goto error_close; } if ((res = load_filter(fd, type, mac, server->mac_addr)) < 0) goto error_close; return fd; error_close: close(fd); return res; } static int setup_socket(struct server *server) { struct impl *impl = server->impl; int fd, res; static const uint8_t bmac[6] = AVB_BROADCAST_MAC; fd = avb_server_make_socket(server, AVB_TSN_ETH, bmac); if (fd < 0) return fd; pw_log_info("0x%"PRIx64" %d", server->entity_id, server->ifindex); server->source = pw_loop_add_io(impl->loop, fd, SPA_IO_IN, true, on_socket_data, server); if (server->source == NULL) { res = -errno; pw_log_error("server %p: can't create server source: %m", impl); goto error_no_source; } if ((res = pw_timer_queue_add(impl->timer_queue, &server->timer, NULL, DEFAULT_INTERVAL * SPA_NSEC_PER_SEC, on_timer_event, server)) < 0) { pw_log_error("server %p: can't create timer: %s", impl, spa_strerror(res)); goto error_no_timer; } return 0; error_no_timer: pw_loop_destroy_source(impl->loop, server->source); server->source = NULL; error_no_source: close(fd); return res; } struct server *avdecc_server_new(struct impl *impl, struct spa_dict *props) { struct server *server; const char *str; int res = 0; server = calloc(1, sizeof(*server)); if (server == NULL) return NULL; server->impl = impl; spa_list_append(&impl->servers, &server->link); str = spa_dict_lookup(props, "ifname"); server->ifname = str ? strdup(str) : NULL; spa_hook_list_init(&server->listener_list); spa_list_init(&server->descriptors); spa_list_init(&server->streams); server->debug_messages = false; if ((res = setup_socket(server)) < 0) goto error_free; init_descriptors(server); server->mrp = avb_mrp_new(server); if (server->mrp == NULL) goto error_free; avb_aecp_register(server); server->maap = avb_maap_register(server); server->mmrp = avb_mmrp_register(server); server->msrp = avb_msrp_register(server); server->mvrp = avb_mvrp_register(server); avb_adp_register(server); avb_acmp_register(server); server->domain_attr = avb_msrp_attribute_new(server->msrp, AVB_MSRP_ATTRIBUTE_TYPE_DOMAIN); server->domain_attr->attr.domain.sr_class_id = AVB_MSRP_CLASS_ID_DEFAULT; server->domain_attr->attr.domain.sr_class_priority = AVB_MSRP_PRIORITY_DEFAULT; server->domain_attr->attr.domain.sr_class_vid = htons(AVB_DEFAULT_VLAN); avb_mrp_attribute_begin(server->domain_attr->mrp, 0); avb_mrp_attribute_join(server->domain_attr->mrp, 0, true); server_create_stream(server, SPA_DIRECTION_INPUT, 0); server_create_stream(server, SPA_DIRECTION_OUTPUT, 0); avb_maap_reserve(server->maap, 1); return server; error_free: free(server); if (res < 0) errno = -res; return NULL; } void avdecc_server_add_listener(struct server *server, struct spa_hook *listener, const struct server_events *events, void *data) { spa_hook_list_append(&server->listener_list, listener, events, data); } void avdecc_server_free(struct server *server) { struct impl *impl = server->impl; spa_list_remove(&server->link); if (server->source) pw_loop_destroy_source(impl->loop, server->source); pw_timer_queue_cancel(&server->timer); spa_hook_list_clean(&server->listener_list); free(server); } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/modules/module-avb/descriptors.h000066400000000000000000000215361511204443500275110ustar00rootroot00000000000000/* PipeWire */ /* SPDX-FileCopyrightText: Copyright © 2022 Wim Taymans */ /* SPDX-FileCopyrightText: Copyright © 2025 Alexandre Malki (alexandre.malki@kebag-logic.com) */ /* SPDX-License-Identifier: MIT */ #include "adp.h" #include "aecp-aem.h" #include "aecp-aem-descriptors.h" #include "es-builder.h" #include "internal.h" /** * \todo This whole code needs to be re-factore, * configuring the entity using such a "HARDCODED" * header would does not allow an easy way to * adjust parameters. * * Especially for the people involved in the project * and do not have the programming skills to modify * this file. * * \proposition use a YANG model directly derived from this * or use the YAML for simplicity. * * Having the YANG would allow directly to know the * capabilites/limits of the protocol */ static inline void init_descriptors(struct server *server) { es_builder_add_descriptor(server, AVB_AEM_DESC_STRINGS, 0, sizeof(struct avb_aem_desc_strings), &(struct avb_aem_desc_strings) { .string_0 = "PipeWire", .string_1 = "Configuration 1", .string_2 = "Wim Taymans", }); es_builder_add_descriptor(server, AVB_AEM_DESC_LOCALE, 0, sizeof(struct avb_aem_desc_locale), &(struct avb_aem_desc_locale) { .locale_identifier = "en-EN", .number_of_strings = htons(1), .base_strings = htons(0) }); es_builder_add_descriptor(server, AVB_AEM_DESC_ENTITY, 0, sizeof(struct avb_aem_desc_entity), &(struct avb_aem_desc_entity) { .entity_id = htobe64(server->entity_id), .entity_model_id = htobe64(0), .entity_capabilities = htonl( AVB_ADP_ENTITY_CAPABILITY_AEM_SUPPORTED | AVB_ADP_ENTITY_CAPABILITY_CLASS_A_SUPPORTED | AVB_ADP_ENTITY_CAPABILITY_GPTP_SUPPORTED | AVB_ADP_ENTITY_CAPABILITY_AEM_IDENTIFY_CONTROL_INDEX_VALID | AVB_ADP_ENTITY_CAPABILITY_AEM_INTERFACE_INDEX_VALID), .talker_stream_sources = htons(8), .talker_capabilities = htons( AVB_ADP_TALKER_CAPABILITY_IMPLEMENTED | AVB_ADP_TALKER_CAPABILITY_AUDIO_SOURCE), .listener_stream_sinks = htons(8), .listener_capabilities = htons( AVB_ADP_LISTENER_CAPABILITY_IMPLEMENTED | AVB_ADP_LISTENER_CAPABILITY_AUDIO_SINK), .controller_capabilities = htons(0), .available_index = htonl(0), .association_id = htobe64(0), .entity_name = "PipeWire", .vendor_name_string = htons(2), .model_name_string = htons(0), .firmware_version = "0.3.48", .group_name = "", .serial_number = "", .configurations_count = htons(1), .current_configuration = htons(0) }); struct { struct avb_aem_desc_configuration desc; struct avb_aem_desc_descriptor_count descriptor_counts[8]; } __attribute__ ((__packed__)) config = { { .object_name = "Configuration 1", .localized_description = htons(1), .descriptor_counts_count = htons(8), .descriptor_counts_offset = htons( 4 + sizeof(struct avb_aem_desc_configuration)), }, .descriptor_counts = { { htons(AVB_AEM_DESC_AUDIO_UNIT), htons(1) }, { htons(AVB_AEM_DESC_STREAM_INPUT), htons(1) }, { htons(AVB_AEM_DESC_STREAM_OUTPUT), htons(1) }, { htons(AVB_AEM_DESC_AVB_INTERFACE), htons(1) }, { htons(AVB_AEM_DESC_CLOCK_SOURCE), htons(1) }, { htons(AVB_AEM_DESC_CONTROL), htons(2) }, { htons(AVB_AEM_DESC_LOCALE), htons(1) }, { htons(AVB_AEM_DESC_CLOCK_DOMAIN), htons(1) } } }; es_builder_add_descriptor(server, AVB_AEM_DESC_CONFIGURATION, 0, sizeof(config), &config); struct { struct avb_aem_desc_audio_unit desc; struct avb_aem_desc_sampling_rate sampling_rates[6]; } __attribute__ ((__packed__)) audio_unit = { { .object_name = "PipeWire", .localized_description = htons(0), .clock_domain_index = htons(0), .number_of_stream_input_ports = htons(1), .base_stream_input_port = htons(0), .number_of_stream_output_ports = htons(1), .base_stream_output_port = htons(0), .number_of_external_input_ports = htons(8), .base_external_input_port = htons(0), .number_of_external_output_ports = htons(8), .base_external_output_port = htons(0), .number_of_internal_input_ports = htons(0), .base_internal_input_port = htons(0), .number_of_internal_output_ports = htons(0), .base_internal_output_port = htons(0), .number_of_controls = htons(0), .base_control = htons(0), .number_of_signal_selectors = htons(0), .base_signal_selector = htons(0), .number_of_mixers = htons(0), .base_mixer = htons(0), .number_of_matrices = htons(0), .base_matrix = htons(0), .number_of_splitters = htons(0), .base_splitter = htons(0), .number_of_combiners = htons(0), .base_combiner = htons(0), .number_of_demultiplexers = htons(0), .base_demultiplexer = htons(0), .number_of_multiplexers = htons(0), .base_multiplexer = htons(0), .number_of_transcoders = htons(0), .base_transcoder = htons(0), .number_of_control_blocks = htons(0), .base_control_block = htons(0), .current_sampling_rate = htonl(48000), .sampling_rates_offset = htons( 4 + sizeof(struct avb_aem_desc_audio_unit)), .sampling_rates_count = htons(6), }, .sampling_rates = { { .pull_frequency = htonl(44100) }, { .pull_frequency = htonl(48000) }, { .pull_frequency = htonl(88200) }, { .pull_frequency = htonl(96000) }, { .pull_frequency = htonl(176400) }, { .pull_frequency = htonl(192000) }, } }; es_builder_add_descriptor(server, AVB_AEM_DESC_AUDIO_UNIT, 0, sizeof(audio_unit), &audio_unit); struct { struct avb_aem_desc_stream desc; uint64_t stream_formats[6]; } __attribute__ ((__packed__)) stream_input_0 = { { .object_name = "Stream Input 1", .localized_description = htons(0xffff), .clock_domain_index = htons(0), .stream_flags = htons( AVB_AEM_DESC_STREAM_FLAG_SYNC_SOURCE | AVB_AEM_DESC_STREAM_FLAG_CLASS_A), .current_format = htobe64(0x00a0020840000800ULL), .formats_offset = htons( 4 + sizeof(struct avb_aem_desc_stream)), .number_of_formats = htons(6), .backup_talker_entity_id_0 = htobe64(0), .backup_talker_unique_id_0 = htons(0), .backup_talker_entity_id_1 = htobe64(0), .backup_talker_unique_id_1 = htons(0), .backup_talker_entity_id_2 = htobe64(0), .backup_talker_unique_id_2 = htons(0), .backedup_talker_entity_id = htobe64(0), .backedup_talker_unique = htons(0), .avb_interface_index = htons(0), .buffer_length = htons(8) }, .stream_formats = { htobe64(0x00a0010860000800ULL), htobe64(0x00a0020860000800ULL), htobe64(0x00a0030860000800ULL), htobe64(0x00a0040860000800ULL), htobe64(0x00a0050860000800ULL), htobe64(0x00a0060860000800ULL), }, }; es_builder_add_descriptor(server, AVB_AEM_DESC_STREAM_INPUT, 0, sizeof(stream_input_0), &stream_input_0); struct { struct avb_aem_desc_stream desc; uint64_t stream_formats[6]; } __attribute__ ((__packed__)) stream_output_0 = { { .object_name = "Stream Output 1", .localized_description = htons(0xffff), .clock_domain_index = htons(0), .stream_flags = htons( AVB_AEM_DESC_STREAM_FLAG_CLASS_A), .current_format = htobe64(0x00a0020840000800ULL), .formats_offset = htons( 4 + sizeof(struct avb_aem_desc_stream)), .number_of_formats = htons(6), .backup_talker_entity_id_0 = htobe64(0), .backup_talker_unique_id_0 = htons(0), .backup_talker_entity_id_1 = htobe64(0), .backup_talker_unique_id_1 = htons(0), .backup_talker_entity_id_2 = htobe64(0), .backup_talker_unique_id_2 = htons(0), .backedup_talker_entity_id = htobe64(0), .backedup_talker_unique = htons(0), .avb_interface_index = htons(0), .buffer_length = htons(8) }, .stream_formats = { htobe64(0x00a0010860000800ULL), htobe64(0x00a0020860000800ULL), htobe64(0x00a0030860000800ULL), htobe64(0x00a0040860000800ULL), htobe64(0x00a0050860000800ULL), htobe64(0x00a0060860000800ULL), }, }; es_builder_add_descriptor(server, AVB_AEM_DESC_STREAM_OUTPUT, 0, sizeof(stream_output_0), &stream_output_0); struct avb_aem_desc_avb_interface avb_interface = { .localized_description = htons(0xffff), .interface_flags = htons( AVB_AEM_DESC_AVB_INTERFACE_FLAG_GPTP_GRANDMASTER_SUPPORTED), .clock_identity = htobe64(0), .priority1 = 0, .clock_class = 0, .offset_scaled_log_variance = htons(0), .clock_accuracy = 0, .priority2 = 0, .domain_number = 0, .log_sync_interval = 0, .log_announce_interval = 0, .log_pdelay_interval = 0, .port_number = 0, }; strncpy(avb_interface.object_name, server->ifname, 63); memcpy(avb_interface.mac_address, server->mac_addr, 6); es_builder_add_descriptor(server, AVB_AEM_DESC_AVB_INTERFACE, 0, sizeof(avb_interface), &avb_interface); struct avb_aem_desc_clock_source clock_source = { .object_name = "Stream Clock", .localized_description = htons(0xffff), .clock_source_flags = htons(0), .clock_source_type = htons( AVB_AEM_DESC_CLOCK_SOURCE_TYPE_INPUT_STREAM), .clock_source_identifier = htobe64(0), .clock_source_location_type = htons(AVB_AEM_DESC_STREAM_INPUT), .clock_source_location_index = htons(0), }; es_builder_add_descriptor(server, AVB_AEM_DESC_CLOCK_SOURCE, 0, sizeof(clock_source), &clock_source); } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/modules/module-avb/es-builder.c000066400000000000000000000046431511204443500271760ustar00rootroot00000000000000/* PipeWire */ /* SPDX-FileCopyrightText: Copyright © 2025 Alexandre Malki */ /* SPDX-License-Identifier: MIT */ #include "es-builder.h" #include "aecp-aem-descriptors.h" /** * \brief The goal of this modules is to create a an entity and * attache the necessary status or resources to it so they * do no have to be seperated and referenced somewhere else. * * In a sense, it encapsulates the descriptor, and the states * information that will be altered either by a aecp/acmp commands * or internal state changes reflected into the counters. */ /** The callback type used for the different entity descriptor */ typedef void* (*es_builder_cb_t) (struct server *server, uint16_t type, uint16_t index, size_t size, void *ptr); /** Structure holding all necessary cb * \todo for the future of compatibility between milan's version * and plain AVB, add the right callback, that would reduce * code complexity and increase reusability. * As well as having multiple entity model defined using different * entity on the same machine */ struct es_builder_st { es_builder_cb_t build_descriptor_cb; }; /** All callback that needs a status information */ static const struct es_builder_st es_builder[AVB_AEM_DESC_LAST_RESERVED_17221] = { }; /** * \brief, should be called when creating an a descriptor, it will attach * the right state variable that are necessary for counters, stream info * and so on... */ void es_builder_add_descriptor(struct server *server, uint16_t type, uint16_t index, size_t size, void *ptr_aem) { void *desc_ptr; struct descriptor *d; if (!server) { pw_log_error("Invalid server, it is empty %p\n", server); spa_assert(0); } if (type >= AVB_AEM_DESC_LAST_RESERVED_17221) { pw_log_error("Invalid Type %u\n", type); spa_assert(0); } /* Look if the descriptor has a callback to attach more status data */ if (!es_builder[type].build_descriptor_cb) { if (!server_add_descriptor(server, type, index, size, ptr_aem)) { pw_log_error("Could not allocate descriptor %u at " "index %u the avb aem type\n", type, index); spa_assert(0); } } else { desc_ptr = es_builder[type].build_descriptor_cb(server, type, index, size, ptr_aem); if (!desc_ptr) { pw_log_error("Could not allocate specific descriptr " "%u at index %u the avb aem type\n", type, index); spa_assert(0); } d = (struct descriptor *) desc_ptr; d->size = size; } } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/modules/module-avb/es-builder.h000066400000000000000000000007441511204443500272010ustar00rootroot00000000000000/* PipeWire */ /* SPDX-FileCopyrightText: Copyright © 2025 Alexandre Malki */ /* SPDX-License-Identifier: MIT */ #ifndef __ES_BUILDER_H__ #define __ES_BUILDER_H__ #include "internal.h" /** * This is a mandatory feature to add the necessary state information * to create the right entity model **/ void es_builder_add_descriptor(struct server *server, uint16_t type, uint16_t index, size_t size, void *ptr_aem); #endif // __ES_BUILDER_H__ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/modules/module-avb/iec61883.h000066400000000000000000000035771511204443500263270ustar00rootroot00000000000000/* AVB support */ /* SPDX-FileCopyrightText: Copyright © 2022 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef AVB_IEC61883_H #define AVB_IEC61883_H #include "packets.h" struct avb_packet_iec61883 { uint8_t subtype; #if __BYTE_ORDER == __BIG_ENDIAN unsigned sv:1; unsigned version:3; unsigned mr:1; unsigned _r1:1; unsigned gv:1; unsigned tv:1; uint8_t seq_num; unsigned _r2:7; unsigned tu:1; #elif __BYTE_ORDER == __LITTLE_ENDIAN unsigned tv:1; unsigned gv:1; unsigned _r1:1; unsigned mr:1; unsigned version:3; unsigned sv:1; uint8_t seq_num; unsigned tu:1; unsigned _r2:7; #endif uint64_t stream_id; uint32_t timestamp; uint32_t gateway_info; uint16_t data_len; #if __BYTE_ORDER == __BIG_ENDIAN uint8_t tag:2; uint8_t channel:6; uint8_t tcode:4; uint8_t app:4; uint8_t qi1:2; /* CIP Quadlet Indicator 1 */ uint8_t sid:6; /* CIP Source ID */ uint8_t dbs; /* CIP Data Block Size */ uint8_t fn:2; /* CIP Fraction Number */ uint8_t qpc:3; /* CIP Quadlet Padding Count */ uint8_t sph:1; /* CIP Source Packet Header */ uint8_t _r3:2; uint8_t dbc; /* CIP Data Block Continuity */ uint8_t qi2:2; /* CIP Quadlet Indicator 2 */ uint8_t format_id:6; /* CIP Format ID */ #elif __BYTE_ORDER == __LITTLE_ENDIAN uint8_t channel:6; uint8_t tag:2; uint8_t app:4; uint8_t tcode:4; uint8_t sid:6; /* CIP Source ID */ uint8_t qi1:2; /* CIP Quadlet Indicator 1 */ uint8_t dbs; /* CIP Data Block Size */ uint8_t _r3:2; uint8_t sph:1; /* CIP Source Packet Header */ uint8_t qpc:3; /* CIP Quadlet Padding Count */ uint8_t fn:2; /* CIP Fraction Number */ uint8_t dbc; /* CIP Data Block Continuity */ uint8_t format_id:6; /* CIP Format ID */ uint8_t qi2:2; /* CIP Quadlet Indicator 2 */ #endif uint8_t fdf; /* CIP Format Dependent Field */ uint16_t syt; uint8_t payload[0]; } __attribute__ ((__packed__)); #endif /* AVB_IEC61883_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/modules/module-avb/internal.h000066400000000000000000000061731511204443500267640ustar00rootroot00000000000000/* PipeWire */ /* SPDX-FileCopyrightText: Copyright © 2022 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef AVB_INTERNAL_H #define AVB_INTERNAL_H #include #ifdef __cplusplus extern "C" { #endif struct server; struct avb_mrp; #define AVB_TSN_ETH 0x22f0 #define AVB_BROADCAST_MAC { 0x91, 0xe0, 0xf0, 0x01, 0x00, 0x00 }; struct impl { struct pw_loop *loop; struct pw_timer_queue *timer_queue; struct pw_context *context; struct spa_hook context_listener; struct pw_core *core; unsigned do_disconnect:1; struct pw_properties *props; struct spa_list servers; }; struct server_events { #define AVB_VERSION_SERVER_EVENTS 0 uint32_t version; /** the server is destroyed */ void (*destroy) (void *data); int (*message) (void *data, uint64_t now, const void *message, int len); void (*periodic) (void *data, uint64_t now); int (*command) (void *data, uint64_t now, const char *command, const char *args, FILE *out); }; struct descriptor { struct spa_list link; uint16_t type; uint16_t index; uint32_t size; void *ptr; }; struct server { struct spa_list link; struct impl *impl; char *ifname; uint8_t mac_addr[6]; uint64_t entity_id; int ifindex; struct spa_source *source; struct pw_timer timer; struct spa_hook_list listener_list; struct spa_list descriptors; struct spa_list streams; unsigned debug_messages:1; struct avb_mrp *mrp; struct avb_mmrp *mmrp; struct avb_mvrp *mvrp; struct avb_msrp *msrp; struct avb_maap *maap; struct avb_msrp_attribute *domain_attr; }; #include "stream.h" static inline const struct descriptor *server_find_descriptor(struct server *server, uint16_t type, uint16_t index) { struct descriptor *d; spa_list_for_each(d, &server->descriptors, link) { if (d->type == type && d->index == index) return d; } return NULL; } static inline void *server_add_descriptor(struct server *server, uint16_t type, uint16_t index, size_t size, void *ptr) { struct descriptor *d; if ((d = calloc(1, sizeof(struct descriptor) + size)) == NULL) return NULL; d->type = type; d->index = index; d->size = size; d->ptr = SPA_PTROFF(d, sizeof(struct descriptor), void); if (ptr) memcpy(d->ptr, ptr, size); spa_list_append(&server->descriptors, &d->link); return d->ptr; } static inline struct stream *server_find_stream(struct server *server, enum spa_direction direction, uint16_t index) { struct stream *s; spa_list_for_each(s, &server->streams, link) { if (s->direction == direction && s->index == index) return s; } return NULL; } struct server *avdecc_server_new(struct impl *impl, struct spa_dict *props); void avdecc_server_free(struct server *server); void avdecc_server_add_listener(struct server *server, struct spa_hook *listener, const struct server_events *events, void *data); int avb_server_make_socket(struct server *server, uint16_t type, const uint8_t mac[6]); int avb_server_send_packet(struct server *server, const uint8_t dest[6], uint16_t type, void *data, size_t size); struct aecp { struct server *server; struct spa_hook server_listener; }; #ifdef __cplusplus } /* extern "C" */ #endif #endif /* AVB_INTERNAL_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/modules/module-avb/maap.c000066400000000000000000000254731511204443500260650ustar00rootroot00000000000000/* AVB support */ /* SPDX-FileCopyrightText: Copyright © 2022 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include #include "utils.h" #include "maap.h" #define MAAP_ALLOCATION_POOL_SIZE 0xFE00 #define MAAP_ALLOCATION_POOL_BASE { 0x91, 0xe0, 0xf0, 0x00, 0x00, 0x00 } static uint8_t maap_base[6] = MAAP_ALLOCATION_POOL_BASE; #define MAAP_PROBE_RETRANSMITS 3 #define MAAP_PROBE_INTERVAL_MS 500 #define MAAP_PROBE_INTERVAL_VAR_MS 100 #define MAAP_ANNOUNCE_INTERVAL_MS 3000 #define MAAP_ANNOUNCE_INTERVAL_VAR_MS 2000 struct maap { struct server *server; struct spa_hook server_listener; struct pw_properties *props; struct spa_source *source; #define STATE_IDLE 0 #define STATE_PROBE 1 #define STATE_ANNOUNCE 2 uint32_t state; uint64_t timeout; uint32_t probe_count; unsigned short xsubi[3]; uint16_t offset; uint16_t count; }; static const char *message_type_as_string(uint8_t message_type) { switch (message_type) { case AVB_MAAP_MESSAGE_TYPE_PROBE: return "PROBE"; case AVB_MAAP_MESSAGE_TYPE_DEFEND: return "DEFEND"; case AVB_MAAP_MESSAGE_TYPE_ANNOUNCE: return "ANNOUNCE"; } return "INVALID"; } static void maap_message_debug(struct maap *maap, const struct avb_packet_maap *p) { uint32_t v; const uint8_t *addr; v = AVB_PACKET_MAAP_GET_MESSAGE_TYPE(p); pw_log_info("message-type: %d (%s)", v, message_type_as_string(v)); pw_log_info(" maap-version: %d", AVB_PACKET_MAAP_GET_MAAP_VERSION(p)); pw_log_info(" length: %d", AVB_PACKET_GET_LENGTH(&p->hdr)); pw_log_info(" stream-id: 0x%"PRIx64, AVB_PACKET_MAAP_GET_STREAM_ID(p)); addr = AVB_PACKET_MAAP_GET_REQUEST_START(p); pw_log_info(" request-start: %02x:%02x:%02x:%02x:%02x:%02x", addr[0], addr[1], addr[2], addr[3], addr[4], addr[5]); pw_log_info(" request-count: %d", AVB_PACKET_MAAP_GET_REQUEST_COUNT(p)); addr = AVB_PACKET_MAAP_GET_CONFLICT_START(p); pw_log_info(" conflict-start: %02x:%02x:%02x:%02x:%02x:%02x", addr[0], addr[1], addr[2], addr[3], addr[4], addr[5]); pw_log_info(" conflict-count: %d", AVB_PACKET_MAAP_GET_CONFLICT_COUNT(p)); } #define PROBE_TIMEOUT(n) (uint64_t)(((n) + (MAAP_PROBE_INTERVAL_MS + \ drand48() * MAAP_PROBE_INTERVAL_VAR_MS) * SPA_NSEC_PER_MSEC)) #define ANNOUNCE_TIMEOUT(n) (uint64_t)(((n) + (MAAP_ANNOUNCE_INTERVAL_MS + \ drand48() * MAAP_ANNOUNCE_INTERVAL_VAR_MS) * SPA_NSEC_PER_MSEC)) static int make_new_address(struct maap *maap, uint64_t now, int range) { maap->offset = nrand48(maap->xsubi) % (MAAP_ALLOCATION_POOL_SIZE - range); maap->count = range; maap->state = STATE_PROBE; maap->probe_count = MAAP_PROBE_RETRANSMITS; maap->timeout = PROBE_TIMEOUT(now); return 0; } static uint16_t maap_check_conflict(struct maap *maap, const uint8_t request_start[6], uint16_t request_count, uint8_t conflict_start[6]) { uint16_t our_start, our_end; uint16_t req_start, req_end; uint16_t conf_start, conf_count = 0; if (memcmp(request_start, maap_base, 4) != 0) return 0; our_start = maap->offset; our_end = our_start + maap->count; req_start = request_start[4] << 8 | request_start[5]; req_end = req_start + request_count; if (our_start >= req_start && our_start <= req_end) { conf_start = our_start; conf_count = SPA_MIN(our_end, req_end) - our_start; } else if (req_start >= our_start && req_start <= our_end) { conf_start = req_start; conf_count = SPA_MIN(req_end, our_end) - req_start; } if (conf_count == 0) return 0; conflict_start[4] = conf_start >> 8; conflict_start[5] = conf_start; return conf_count; } static int send_packet(struct maap *maap, uint64_t now, uint8_t type, const uint8_t conflict_start[6], uint16_t conflict_count) { struct avb_ethernet_header *h; struct avb_packet_maap *p; uint8_t buf[1024]; uint8_t bmac[6] = AVB_MAAP_MAC; int res = 0; uint8_t start[6]; spa_memzero(buf, sizeof(buf)); h = (void*)buf; p = SPA_PTROFF(h, sizeof(*h), void); memcpy(h->dest, bmac, 6); memcpy(h->src, maap->server->mac_addr, 6); h->type = htons(AVB_TSN_ETH); p->hdr.subtype = AVB_SUBTYPE_MAAP; AVB_PACKET_SET_LENGTH(&p->hdr, sizeof(*p)); AVB_PACKET_MAAP_SET_MAAP_VERSION(p, 1); AVB_PACKET_MAAP_SET_MESSAGE_TYPE(p, type); memcpy(start, maap_base, 4); start[4] = maap->offset >> 8; start[5] = maap->offset; AVB_PACKET_MAAP_SET_REQUEST_START(p, start); AVB_PACKET_MAAP_SET_REQUEST_COUNT(p, maap->count); if (conflict_count) { AVB_PACKET_MAAP_SET_CONFLICT_START(p, conflict_start); AVB_PACKET_MAAP_SET_CONFLICT_COUNT(p, conflict_count); } if (maap->server->debug_messages) { pw_log_info("send: %d (%s)", type, message_type_as_string(type)); maap_message_debug(maap, p); } if (send(maap->source->fd, p, sizeof(*h) + sizeof(*p), 0) < 0) { res = -errno; pw_log_warn("got send error: %m"); } return res; } static int handle_probe(struct maap *maap, uint64_t now, const struct avb_packet_maap *p) { uint8_t conflict_start[6]; uint16_t conflict_count; conflict_count = maap_check_conflict(maap, p->request_start, ntohs(p->request_count), conflict_start); if (conflict_count == 0) return 0; switch (maap->state) { case STATE_PROBE: make_new_address(maap, now, 8); break; case STATE_ANNOUNCE: send_packet(maap, now, AVB_MAAP_MESSAGE_TYPE_DEFEND, conflict_start, conflict_count); break; } return 0; } static int handle_defend(struct maap *maap, uint64_t now, const struct avb_packet_maap *p) { uint8_t conflict_start[6]; uint16_t conflict_count; conflict_count = maap_check_conflict(maap, p->conflict_start, ntohs(p->conflict_count), conflict_start); if (conflict_count != 0) make_new_address(maap, now, 8); return 0; } static int maap_message(struct maap *maap, uint64_t now, const void *message, int len) { const struct avb_packet_maap *p = message; if (AVB_PACKET_GET_SUBTYPE(&p->hdr) != AVB_SUBTYPE_MAAP) return 0; if (maap->server->debug_messages) maap_message_debug(maap, p); switch (AVB_PACKET_MAAP_GET_MESSAGE_TYPE(p)) { case AVB_MAAP_MESSAGE_TYPE_PROBE: handle_probe(maap, now, p); break; case AVB_MAAP_MESSAGE_TYPE_DEFEND: case AVB_MAAP_MESSAGE_TYPE_ANNOUNCE: handle_defend(maap, now, p); break; } return 0; } static void on_socket_data(void *data, int fd, uint32_t mask) { struct maap *maap = data; struct timespec now; if (mask & SPA_IO_IN) { int len; uint8_t buffer[2048]; len = recv(fd, buffer, sizeof(buffer), 0); if (len < 0) { pw_log_warn("got recv error: %m"); } else if (len < (int)sizeof(struct avb_packet_header)) { pw_log_warn("short packet received (%d < %d)", len, (int)sizeof(struct avb_packet_header)); } else { clock_gettime(CLOCK_REALTIME, &now); maap_message(maap, SPA_TIMESPEC_TO_NSEC(&now), buffer, len); } } } static int load_state(struct maap *maap) { const char *str; char key[512]; struct spa_json it[2]; bool have_offset = false; int count = 0, offset = 0, len; const char *val; snprintf(key, sizeof(key), "maap.%s", maap->server->ifname); pw_conf_load_state("module-avb", key, maap->props); if ((str = pw_properties_get(maap->props, "maap.addresses")) == NULL) return 0; if (spa_json_begin_array(&it[0], str, strlen(str)) <= 0) return 0; if (spa_json_enter_object(&it[0], &it[1]) <= 0) return 0; while ((len = spa_json_object_next(&it[1], key, sizeof(key), &val)) > 0) { if (spa_streq(key, "start")) { uint8_t addr[6]; if (avb_utils_parse_addr(val, len, addr) >= 0 && memcmp(addr, maap_base, 4) == 0) { offset = addr[4] << 8 | addr[5]; have_offset = true; } } else if (spa_streq(key, "count")) { spa_json_parse_int(val, len, &count); } } if (count > 0 && have_offset) { maap->count = count; maap->offset = offset; maap->state = STATE_PROBE; maap->probe_count = MAAP_PROBE_RETRANSMITS; maap->timeout = PROBE_TIMEOUT(0); } return 0; } static int save_state(struct maap *maap) { char *ptr; size_t size; FILE *f; char key[512]; uint32_t count; if ((f = open_memstream(&ptr, &size)) == NULL) return -errno; fprintf(f, "[ "); fprintf(f, "{ \"start\": \"%02x:%02x:%02x:%02x:%02x:%02x\", ", maap_base[0], maap_base[1], maap_base[2], maap_base[3], (maap->offset >> 8) & 0xff, maap->offset & 0xff); fprintf(f, " \"count\": %u } ", maap->count); fprintf(f, "]"); fclose(f); count = pw_properties_set(maap->props, "maap.addresses", ptr); free(ptr); if (count > 0) { snprintf(key, sizeof(key), "maap.%s", maap->server->ifname); pw_conf_save_state("module-avb", key, maap->props); } return 0; } static void maap_periodic(void *data, uint64_t now) { struct maap *maap = data; if (now < maap->timeout) return; switch(maap->state) { case STATE_IDLE: break; case STATE_PROBE: send_packet(maap, now, AVB_MAAP_MESSAGE_TYPE_PROBE, NULL, 0); if (--maap->probe_count == 0) { maap->state = STATE_ANNOUNCE; save_state(maap); } maap->timeout = PROBE_TIMEOUT(now); break; case STATE_ANNOUNCE: send_packet(maap, now, AVB_MAAP_MESSAGE_TYPE_ANNOUNCE, NULL, 0); maap->timeout = ANNOUNCE_TIMEOUT(now); break; } } static void maap_free(struct maap *maap) { pw_loop_destroy_source(maap->server->impl->loop, maap->source); spa_hook_remove(&maap->server_listener); pw_properties_free(maap->props); free(maap); } static void maap_destroy(void *data) { struct maap *maap = data; maap_free(maap); } static const struct server_events server_events = { AVB_VERSION_SERVER_EVENTS, .destroy = maap_destroy, .periodic = maap_periodic, }; struct avb_maap *avb_maap_register(struct server *server) { struct maap *maap; uint8_t bmac[6] = AVB_MAAP_MAC; int fd, res; fd = avb_server_make_socket(server, AVB_TSN_ETH, bmac); if (fd < 0) { res = fd; goto error; } maap = calloc(1, sizeof(*maap)); if (maap == NULL) { res = -errno; goto error_close; } maap->props = pw_properties_new(NULL, NULL); if (maap->props == NULL) { res = -errno; goto error_free; } maap->server = server; pw_log_info("0x%"PRIx64" %d", server->entity_id, server->ifindex); pw_random(maap->xsubi, sizeof(maap->xsubi)); load_state(maap); maap->source = pw_loop_add_io(server->impl->loop, fd, SPA_IO_IN, true, on_socket_data, maap); if (maap->source == NULL) { res = -errno; pw_log_error("maap %p: can't create maap source: %m", maap); goto error_free; } avdecc_server_add_listener(server, &maap->server_listener, &server_events, maap); return (struct avb_maap *)maap; error_free: free(maap); error_close: close(fd); error: errno = -res; return NULL; } int avb_maap_reserve(struct avb_maap *m, uint32_t count) { struct maap *maap = (struct maap*)m; if (count > maap->count) make_new_address(maap, 0, count); return 0; } int avb_maap_get_address(struct avb_maap *m, uint8_t addr[6], uint32_t index) { struct maap *maap = (struct maap*)m; uint16_t offset; if (maap->state != STATE_ANNOUNCE) return -EAGAIN; memcpy(addr, maap_base, 6); offset = maap->offset + index; addr[4] = offset >> 8; addr[5] = offset; return 0; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/modules/module-avb/maap.h000066400000000000000000000036221511204443500260620ustar00rootroot00000000000000/* AVB support */ /* SPDX-FileCopyrightText: Copyright © 2022 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef AVB_MAAP_H #define AVB_MAAP_H #include "packets.h" #include "internal.h" #define AVB_TSN_ETH 0x22f0 #define AVB_MAAP_MAC { 0x91, 0xe0, 0xf0, 0x00, 0xff, 0x00 }; #define AVB_MAAP_MESSAGE_TYPE_PROBE 1 #define AVB_MAAP_MESSAGE_TYPE_DEFEND 2 #define AVB_MAAP_MESSAGE_TYPE_ANNOUNCE 3 struct avb_packet_maap { struct avb_packet_header hdr; uint64_t stream_id; uint8_t request_start[6]; uint16_t request_count; uint8_t conflict_start[6]; uint16_t conflict_count; } __attribute__ ((__packed__)); #define AVB_PACKET_MAAP_SET_MESSAGE_TYPE(p,v) AVB_PACKET_SET_SUB1(&(p)->hdr, v) #define AVB_PACKET_MAAP_SET_MAAP_VERSION(p,v) AVB_PACKET_SET_SUB2(&(p)->hdr, v) #define AVB_PACKET_MAAP_SET_STREAM_ID(p,v) ((p)->stream_id = htobe64(v)) #define AVB_PACKET_MAAP_SET_REQUEST_START(p,v) memcpy((p)->request_start, (v), 6) #define AVB_PACKET_MAAP_SET_REQUEST_COUNT(p,v) ((p)->request_count = htons(v)) #define AVB_PACKET_MAAP_SET_CONFLICT_START(p,v) memcpy((p)->conflict_start, (v), 6) #define AVB_PACKET_MAAP_SET_CONFLICT_COUNT(p,v) ((p)->conflict_count = htons(v)) #define AVB_PACKET_MAAP_GET_MESSAGE_TYPE(p) AVB_PACKET_GET_SUB1(&(p)->hdr) #define AVB_PACKET_MAAP_GET_MAAP_VERSION(p) AVB_PACKET_GET_SUB2(&(p)->hdr) #define AVB_PACKET_MAAP_GET_STREAM_ID(p) be64toh((p)->stream_id) #define AVB_PACKET_MAAP_GET_REQUEST_START(p) ((p)->request_start) #define AVB_PACKET_MAAP_GET_REQUEST_COUNT(p) ntohs((p)->request_count) #define AVB_PACKET_MAAP_GET_CONFLICT_START(p) ((p)->conflict_start) #define AVB_PACKET_MAAP_GET_CONFLICT_COUNT(p) ntohs((p)->conflict_count) struct avb_maap; struct avb_maap *avb_maap_register(struct server *server); int avb_maap_reserve(struct avb_maap *maap, uint32_t count); int avb_maap_get_address(struct avb_maap *maap, uint8_t addr[6], uint32_t index); #endif /* AVB_MAAP_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/modules/module-avb/mmrp.c000066400000000000000000000123671511204443500261200ustar00rootroot00000000000000/* AVB support */ /* SPDX-FileCopyrightText: Copyright © 2022 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include "utils.h" #include "mmrp.h" static const uint8_t mmrp_mac[6] = AVB_MMRP_MAC; struct attr { struct avb_mmrp_attribute attr; struct spa_list link; }; struct mmrp { struct server *server; struct spa_hook server_listener; struct spa_source *source; struct spa_list attributes; }; static bool mmrp_check_header(void *data, const void *hdr, size_t *hdr_size, bool *has_params) { const struct avb_packet_mmrp_msg *msg = hdr; uint8_t attr_type = msg->attribute_type; if (!AVB_MMRP_ATTRIBUTE_TYPE_VALID(attr_type)) return false; *hdr_size = sizeof(*msg); *has_params = false; return true; } static int mmrp_attr_event(void *data, uint64_t now, uint8_t attribute_type, uint8_t event) { struct mmrp *mmrp = data; struct attr *a; spa_list_for_each(a, &mmrp->attributes, link) if (a->attr.type == attribute_type) avb_mrp_attribute_update_state(a->attr.mrp, now, event); return 0; } static void debug_service_requirement(const struct avb_packet_mmrp_service_requirement *t) { char buf[128]; pw_log_info("service requirement"); pw_log_info(" %s", avb_utils_format_addr(buf, sizeof(buf), t->addr)); } static int process_service_requirement(struct mmrp *mmrp, uint64_t now, uint8_t attr_type, const void *m, uint8_t event, uint8_t param, int num) { const struct avb_packet_mmrp_service_requirement *t = m; struct attr *a; debug_service_requirement(t); spa_list_for_each(a, &mmrp->attributes, link) if (a->attr.type == attr_type && memcmp(a->attr.attr.service_requirement.addr, t->addr, 6) == 0) avb_mrp_attribute_rx_event(a->attr.mrp, now, event); return 0; } static void debug_process_mac(const struct avb_packet_mmrp_mac *t) { char buf[128]; pw_log_info("mac"); pw_log_info(" %s", avb_utils_format_addr(buf, sizeof(buf), t->addr)); } static int process_mac(struct mmrp *mmrp, uint64_t now, uint8_t attr_type, const void *m, uint8_t event, uint8_t param, int num) { const struct avb_packet_mmrp_mac *t = m; struct attr *a; debug_process_mac(t); spa_list_for_each(a, &mmrp->attributes, link) if (a->attr.type == attr_type && memcmp(a->attr.attr.mac.addr, t->addr, 6) == 0) avb_mrp_attribute_rx_event(a->attr.mrp, now, event); return 0; } static const struct { int (*dispatch) (struct mmrp *mmrp, uint64_t now, uint8_t attr_type, const void *m, uint8_t event, uint8_t param, int num); } dispatch[] = { [AVB_MMRP_ATTRIBUTE_TYPE_SERVICE_REQUIREMENT] = { process_service_requirement, }, [AVB_MMRP_ATTRIBUTE_TYPE_MAC] = { process_mac, }, }; static int mmrp_process(void *data, uint64_t now, uint8_t attribute_type, const void *value, uint8_t event, uint8_t param, int index) { struct mmrp *mmrp = data; return dispatch[attribute_type].dispatch(mmrp, now, attribute_type, value, event, param, index); } static const struct avb_mrp_parse_info info = { AVB_VERSION_MRP_PARSE_INFO, .check_header = mmrp_check_header, .attr_event = mmrp_attr_event, .process = mmrp_process, }; static int mmrp_message(struct mmrp *mmrp, uint64_t now, const void *message, int len) { pw_log_debug("MMRP"); return avb_mrp_parse_packet(mmrp->server->mrp, now, message, len, &info, mmrp); } static void on_socket_data(void *data, int fd, uint32_t mask) { struct mmrp *mmrp = data; struct timespec now; if (mask & SPA_IO_IN) { int len; uint8_t buffer[2048]; len = recv(fd, buffer, sizeof(buffer), 0); if (len < 0) { pw_log_warn("got recv error: %m"); } else if (len < (int)sizeof(struct avb_packet_header)) { pw_log_warn("short packet received (%d < %d)", len, (int)sizeof(struct avb_packet_header)); } else { clock_gettime(CLOCK_REALTIME, &now); mmrp_message(mmrp, SPA_TIMESPEC_TO_NSEC(&now), buffer, len); } } } static void mmrp_destroy(void *data) { struct mmrp *mmrp = data; spa_hook_remove(&mmrp->server_listener); pw_loop_destroy_source(mmrp->server->impl->loop, mmrp->source); free(mmrp); } static const struct server_events server_events = { AVB_VERSION_SERVER_EVENTS, .destroy = mmrp_destroy, }; struct avb_mmrp_attribute *avb_mmrp_attribute_new(struct avb_mmrp *m, uint8_t type) { struct mmrp *mmrp = (struct mmrp*)m; struct avb_mrp_attribute *attr; struct attr *a; attr = avb_mrp_attribute_new(mmrp->server->mrp, sizeof(struct attr)); a = attr->user_data; a->attr.mrp = attr; a->attr.type = type; spa_list_append(&mmrp->attributes, &a->link); return &a->attr; } struct avb_mmrp *avb_mmrp_register(struct server *server) { struct mmrp *mmrp; int fd, res; fd = avb_server_make_socket(server, AVB_MMRP_ETH, mmrp_mac); if (fd < 0) { errno = -fd; return NULL; } mmrp = calloc(1, sizeof(*mmrp)); if (mmrp == NULL) { res = -errno; goto error_close; } mmrp->server = server; spa_list_init(&mmrp->attributes); mmrp->source = pw_loop_add_io(server->impl->loop, fd, SPA_IO_IN, true, on_socket_data, mmrp); if (mmrp->source == NULL) { res = -errno; pw_log_error("mmrp %p: can't create mmrp source: %m", mmrp); goto error_no_source; } avdecc_server_add_listener(server, &mmrp->server_listener, &server_events, mmrp); return (struct avb_mmrp*)mmrp; error_no_source: free(mmrp); error_close: close(fd); errno = -res; return NULL; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/modules/module-avb/mmrp.h000066400000000000000000000022061511204443500261140ustar00rootroot00000000000000/* AVB support */ /* SPDX-FileCopyrightText: Copyright © 2022 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef AVB_MMRP_H #define AVB_MMRP_H #include "mrp.h" #include "internal.h" #define AVB_MMRP_ETH 0x88f6 #define AVB_MMRP_MAC { 0x01, 0x80, 0xc2, 0x00, 0x00, 0x20 } #define AVB_MMRP_ATTRIBUTE_TYPE_SERVICE_REQUIREMENT 1 #define AVB_MMRP_ATTRIBUTE_TYPE_MAC 2 #define AVB_MMRP_ATTRIBUTE_TYPE_VALID(t) ((t)>=1 && (t)<=2) struct avb_packet_mmrp_msg { uint8_t attribute_type; uint8_t attribute_length; uint8_t attribute_list[0]; } __attribute__ ((__packed__)); struct avb_packet_mmrp_service_requirement { unsigned char addr[6]; } __attribute__ ((__packed__)); struct avb_packet_mmrp_mac { unsigned char addr[6]; } __attribute__ ((__packed__)); struct avb_mmrp; struct avb_mmrp_attribute { struct avb_mrp_attribute *mrp; uint8_t type; union { struct avb_packet_mmrp_service_requirement service_requirement; struct avb_packet_mmrp_mac mac; } attr; }; struct avb_mmrp_attribute *avb_mmrp_attribute_new(struct avb_mmrp *mmrp, uint8_t type); struct avb_mmrp *avb_mmrp_register(struct server *server); #endif /* AVB_MMRP_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/modules/module-avb/mrp.c000066400000000000000000000404561511204443500257430ustar00rootroot00000000000000/* AVB support */ /* SPDX-FileCopyrightText: Copyright © 2022 Wim Taymans */ /* SPDX-FileCopyrightText: Copyright © 2025 Kebag-Logic */ /* SPDX-FileCopyrightText: Copyright © 2025 Alexandre Malki */ /* SPDX-License-Identifier: MIT */ #include #include "mrp.h" #define MRP_JOINTIMER_MS 100 #define MRP_LVTIMER_MS 1000 #define MRP_LVATIMER_MS 10000 #define MRP_PERIODTIMER_MS 1000 #define mrp_emit(s,m,v,...) spa_hook_list_call(&s->listener_list, struct avb_mrp_events, m, v, ##__VA_ARGS__) #define mrp_emit_event(s,n,e) mrp_emit(s,event,0,n,e) #define mrp_emit_notify(s,n,a,e) mrp_emit(s,notify,0,n,a,e) #define mrp_attribute_emit(a,m,v,...) spa_hook_list_call(&a->listener_list, struct avb_mrp_attribute_events, m, v, ##__VA_ARGS__) #define mrp_attribute_emit_notify(a,n,e) mrp_attribute_emit(a,notify,0,n,e) struct mrp; struct attribute { struct avb_mrp_attribute attr; struct mrp *mrp; struct spa_list link; uint8_t applicant_state; uint8_t registrar_state; uint64_t leave_timeout; unsigned joined:1; struct spa_hook_list listener_list; }; enum fsm_lva { FSM_LVA_ACTIVE, FSM_LVA_PASSIVE }; struct fsm_leave_all_timer { enum fsm_lva state; uint64_t leave_all_timeout; }; struct mrp { struct server *server; struct spa_hook server_listener; struct spa_hook_list listener_list; struct spa_list attributes; uint64_t periodic_timeout; struct fsm_leave_all_timer lva_timer; uint64_t join_timeout; }; static void mrp_destroy(void *data) { struct mrp *mrp = data; spa_hook_remove(&mrp->server_listener); free(mrp); } static void global_event(struct mrp *mrp, uint64_t now, uint8_t event) { struct attribute *a; spa_list_for_each(a, &mrp->attributes, link) avb_mrp_attribute_update_state(&a->attr, now, event); mrp_emit_event(mrp, now, event); } static void mrp_set_update_lva(struct mrp *mrp, uint64_t now, bool force_send) { if (!force_send) { mrp->lva_timer.leave_all_timeout = now + (MRP_LVATIMER_MS + (pw_rand32() % (MRP_LVATIMER_MS / 2))) * SPA_NSEC_PER_MSEC; } else { mrp->lva_timer.leave_all_timeout = now; } } static void mrp_periodic(void *data, uint64_t now) { struct mrp *mrp = data; bool leave_all = false; struct attribute *a; if (now > mrp->periodic_timeout) { if (mrp->periodic_timeout > 0) global_event(mrp, now, AVB_MRP_EVENT_PERIODIC); mrp->periodic_timeout = now + MRP_PERIODTIMER_MS * SPA_NSEC_PER_MSEC; } if (now > mrp->lva_timer.leave_all_timeout) { /* 802.1Q-2014 Table 10-5 */ mrp->lva_timer.state = FSM_LVA_ACTIVE; if (mrp->lva_timer.leave_all_timeout > 0) { global_event(mrp, now, AVB_MRP_EVENT_RX_LVA); leave_all = true; } } if (now > mrp->join_timeout) { if (mrp->join_timeout > 0) { uint8_t event = leave_all ? AVB_MRP_EVENT_TX_LVA : AVB_MRP_EVENT_TX; global_event(mrp, now, event); } mrp->join_timeout = now + MRP_JOINTIMER_MS * SPA_NSEC_PER_MSEC; } spa_list_for_each(a, &mrp->attributes, link) { // 802.1Q Clause 10.7.4.2 if (a->leave_timeout > 0 && now > a->leave_timeout && a->registrar_state == AVB_MRP_LV) { a->leave_timeout = 0; avb_mrp_attribute_update_state(&a->attr, now, AVB_MRP_EVENT_LV_TIMER); } } } static const struct server_events server_events = { AVB_VERSION_SERVER_EVENTS, .destroy = mrp_destroy, .periodic = mrp_periodic, }; int avb_mrp_parse_packet(struct avb_mrp *mrp, uint64_t now, const void *pkt, int len, const struct avb_mrp_parse_info *info, void *data) { uint8_t *e = SPA_PTROFF(pkt, len, uint8_t); uint8_t *m = SPA_PTROFF(pkt, sizeof(struct avb_packet_mrp), uint8_t); while (m < e && (m[0] != 0 || m[1] != 0)) { const struct avb_packet_mrp_hdr *hdr = (const struct avb_packet_mrp_hdr*)m; uint8_t attr_type = hdr->attribute_type; uint8_t attr_len = hdr->attribute_length; size_t hdr_size; bool has_param; if (!info->check_header(data, hdr, &hdr_size, &has_param)) return -EINVAL; m += hdr_size; while (m < e && (m[0] != 0 || m[1] != 0)) { const struct avb_packet_mrp_vector *v = (const struct avb_packet_mrp_vector*)m; uint16_t i, num_values = AVB_MRP_VECTOR_GET_NUM_VALUES(v); uint8_t event_len = (num_values+2)/3; uint8_t param_len = has_param ? (num_values+3)/4 : 0; int plen = sizeof(*v) + attr_len + event_len + param_len; const uint8_t *first = v->first_value; uint8_t event[3], param[4] = { 0, }; if (m + plen > e) return -EPROTO; if (v->lva) info->attr_event(data, now, attr_type, AVB_MRP_ATTRIBUTE_EVENT_LVA); for (i = 0; i < num_values; i++) { if (i % 3 == 0) { uint8_t ep = first[attr_len + i/3]; event[2] = ep % 6; ep /= 6; event[1] = ep % 6; ep /= 6; event[0] = ep % 6; } if (has_param && (i % 4 == 0)) { uint8_t ep = first[attr_len + event_len + i/4]; param[3] = ep % 4; ep /= 4; param[2] = ep % 4; ep /= 4; param[1] = ep % 4; ep /= 4; param[0] = ep % 4; } info->process(data, now, attr_type, first, event[i%3], param[i%4], i); } m += plen; } m += 2; } return 0; } const char *avb_applicant_state_name(uint8_t state) { switch (state) { case AVB_MRP_VO: return "VO"; case AVB_MRP_VP: return "VP"; case AVB_MRP_VN: return "VN"; case AVB_MRP_AN: return "AN"; case AVB_MRP_AA: return "AA"; case AVB_MRP_QA: return "QA"; case AVB_MRP_LA: return "LA"; case AVB_MRP_AO: return "AO"; case AVB_MRP_QO: return "QO"; case AVB_MRP_AP: return "AP"; case AVB_MRP_QP: return "QP"; case AVB_MRP_LO: return "LO"; } return "unknown-applicant-state"; } const char *avb_registrar_state_name(uint8_t state) { switch (state) { case AVB_MRP_IN: return "IN"; case AVB_MRP_LV: return "LV"; case AVB_MRP_MT: return "MT"; } return "unknown-registrar-state"; } const char *avb_mrp_event_name(uint8_t event) { switch (event) { case AVB_MRP_EVENT_BEGIN: return "begin"; case AVB_MRP_EVENT_NEW: return "new"; case AVB_MRP_EVENT_JOIN: return "join"; case AVB_MRP_EVENT_LV: return "lv"; case AVB_MRP_EVENT_TX: return "tx"; case AVB_MRP_EVENT_TX_LVA: return "tx_lva"; case AVB_MRP_EVENT_TX_LVAF: return "tx_lvaf"; case AVB_MRP_EVENT_RX_NEW: return "rx_new"; case AVB_MRP_EVENT_RX_JOININ: return "rx_joinin"; case AVB_MRP_EVENT_RX_IN: return "rx_in"; case AVB_MRP_EVENT_RX_JOINMT: return "rx_joinmt"; case AVB_MRP_EVENT_RX_MT: return "rx_mt"; case AVB_MRP_EVENT_RX_LV: return "rx_lv"; case AVB_MRP_EVENT_RX_LVA: return "rx_lva"; case AVB_MRP_EVENT_FLUSH: return "flush"; case AVB_MRP_EVENT_REDECLARE: return "redeclare"; case AVB_MRP_EVENT_PERIODIC: return "periodic"; case AVB_MRP_EVENT_LV_TIMER: return "lv_timer"; case AVB_MRP_EVENT_LVA_TIMER: return "lva_timer"; } return "unknown-event"; } const char *avb_mrp_notify_name(uint8_t notify) { switch(notify) { case AVB_MRP_NOTIFY_NEW: return "new"; case AVB_MRP_NOTIFY_JOIN: return "join"; case AVB_MRP_NOTIFY_LEAVE: return "leave"; } return "unknown"; } const char *avb_mrp_send_name(uint8_t send) { switch(send) { case AVB_MRP_SEND_NEW: return "new"; case AVB_MRP_SEND_JOININ: return "joinin"; case AVB_MRP_SEND_IN: return "in"; case AVB_MRP_SEND_JOINMT: return "joinmt"; case AVB_MRP_SEND_MT: return "mt"; case AVB_MRP_SEND_LV: return "leave"; } return "unknown"; } struct avb_mrp_attribute *avb_mrp_attribute_new(struct avb_mrp *m, size_t user_size) { struct mrp *mrp = (struct mrp*)m; struct attribute *a; a = calloc(1, sizeof(*a) + user_size); if (a == NULL) return NULL; a->mrp = mrp; a->attr.user_data = SPA_PTROFF(a, sizeof(*a), void); spa_hook_list_init(&a->listener_list); spa_list_append(&mrp->attributes, &a->link); return &a->attr; } void avb_mrp_attribute_destroy(struct avb_mrp_attribute *attr) { struct attribute *a = SPA_CONTAINER_OF(attr, struct attribute, attr); spa_list_remove(&a->link); free(a); } void avb_mrp_attribute_add_listener(struct avb_mrp_attribute *attr, struct spa_hook *listener, const struct avb_mrp_attribute_events *events, void *data) { struct attribute *a = SPA_CONTAINER_OF(attr, struct attribute, attr); spa_hook_list_append(&a->listener_list, listener, events, data); } void avb_mrp_attribute_update_state(struct avb_mrp_attribute *attr, uint64_t now, int event) { struct attribute *a = SPA_CONTAINER_OF(attr, struct attribute, attr); struct mrp *mrp = a->mrp; uint8_t notify = 0, state; uint8_t send = 0; // Handle the LVA timer FSM switch (event) { case AVB_MRP_EVENT_RX_LVA: mrp_set_update_lva(mrp, now, false); mrp->lva_timer.state = FSM_LVA_PASSIVE; break; case AVB_MRP_EVENT_TX: if (mrp->lva_timer.state == FSM_LVA_ACTIVE) { mrp_set_update_lva(mrp, now, true); } mrp->lva_timer.state = FSM_LVA_PASSIVE; break; default: break; } state = a->registrar_state; switch (event) { case AVB_MRP_EVENT_BEGIN: state = AVB_MRP_MT; break; case AVB_MRP_EVENT_RX_NEW: notify = AVB_MRP_NOTIFY_NEW; switch (state) { case AVB_MRP_LV: a->leave_timeout = INT64_MAX; break; } state = AVB_MRP_IN; break; case AVB_MRP_EVENT_RX_JOININ: case AVB_MRP_EVENT_RX_JOINMT: switch (state) { case AVB_MRP_LV: a->leave_timeout = INT64_MAX; break; case AVB_MRP_MT: notify = AVB_MRP_NOTIFY_JOIN; break; } state = AVB_MRP_IN; break; case AVB_MRP_EVENT_RX_LV: case AVB_MRP_EVENT_RX_LVA: case AVB_MRP_EVENT_TX_LVA: case AVB_MRP_EVENT_REDECLARE: switch (state) { case AVB_MRP_IN: a->leave_timeout = now + MRP_LVTIMER_MS * SPA_NSEC_PER_MSEC; state = AVB_MRP_LV; break; } break; case AVB_MRP_EVENT_FLUSH: switch (state) { case AVB_MRP_LV: notify = AVB_MRP_NOTIFY_LEAVE; break; } state = AVB_MRP_MT; break; case AVB_MRP_EVENT_LV_TIMER: switch (state) { case AVB_MRP_LV: notify = AVB_MRP_NOTIFY_LEAVE; state = AVB_MRP_MT; break; } break; default: break; } if (notify) { mrp_attribute_emit_notify(a, now, notify); mrp_emit_notify(mrp, now, &a->attr, notify); } if (a->registrar_state != state || notify) { pw_log_debug("REG: attr %p: %s %s %s -> %s %s notify? %s", a, a->attr.name, avb_mrp_event_name(event), avb_registrar_state_name(a->registrar_state), avb_registrar_state_name(state), avb_mrp_send_name(notify), notify ? "YES":"NO"); a->registrar_state = state; } state = a->applicant_state; switch (event) { case AVB_MRP_EVENT_BEGIN: state = AVB_MRP_VO; break; case AVB_MRP_EVENT_NEW: switch (state) { case AVB_MRP_VN: case AVB_MRP_AN: break; default: state = AVB_MRP_VN; break; } break; case AVB_MRP_EVENT_JOIN: switch (state) { case AVB_MRP_VO: case AVB_MRP_LO: state = AVB_MRP_VP; break; case AVB_MRP_LA: state = AVB_MRP_AA; break; case AVB_MRP_AO: state = AVB_MRP_AP; break; case AVB_MRP_QO: state = AVB_MRP_QP; break; } break; case AVB_MRP_EVENT_LV: switch (state) { case AVB_MRP_VP: state = AVB_MRP_VO; break; case AVB_MRP_VN: case AVB_MRP_AN: case AVB_MRP_AA: case AVB_MRP_QA: state = AVB_MRP_LA; break; case AVB_MRP_AP: state = AVB_MRP_AO; break; case AVB_MRP_QP: state = AVB_MRP_QO; break; } break; case AVB_MRP_EVENT_RX_JOININ: switch (state) { case AVB_MRP_VO: state = AVB_MRP_AO; break; case AVB_MRP_VP: state = AVB_MRP_AP; break; case AVB_MRP_AA: state = AVB_MRP_QA; break; case AVB_MRP_AO: state = AVB_MRP_QO; break; case AVB_MRP_AP: state = AVB_MRP_QP; break; } SPA_FALLTHROUGH; case AVB_MRP_EVENT_RX_IN: switch (state) { case AVB_MRP_AA: state = AVB_MRP_QA; break; } break; case AVB_MRP_EVENT_RX_JOINMT: case AVB_MRP_EVENT_RX_MT: switch (state) { case AVB_MRP_QA: state = AVB_MRP_AA; break; case AVB_MRP_QO: state = AVB_MRP_AO; break; case AVB_MRP_QP: state = AVB_MRP_AP; break; case AVB_MRP_LO: state = AVB_MRP_VO; break; } break; case AVB_MRP_EVENT_RX_LV: case AVB_MRP_EVENT_RX_LVA: case AVB_MRP_EVENT_REDECLARE: switch (state) { case AVB_MRP_VO: case AVB_MRP_AO: case AVB_MRP_QO: state = AVB_MRP_LO; break; case AVB_MRP_AN: state = AVB_MRP_VN; break; case AVB_MRP_AA: case AVB_MRP_QA: case AVB_MRP_AP: case AVB_MRP_QP: state = AVB_MRP_VP; break; } break; case AVB_MRP_EVENT_PERIODIC: switch (state) { case AVB_MRP_QA: state = AVB_MRP_AA; break; case AVB_MRP_QP: state = AVB_MRP_AP; break; } break; case AVB_MRP_EVENT_TX: switch (state) { case AVB_MRP_VP: case AVB_MRP_AA: case AVB_MRP_AP: if (a->registrar_state == AVB_MRP_IN) send = AVB_MRP_SEND_JOININ; else send = AVB_MRP_SEND_JOINMT; break; case AVB_MRP_VN: case AVB_MRP_AN: send = AVB_MRP_SEND_NEW; break; case AVB_MRP_LA: send = AVB_MRP_SEND_LV; break; case AVB_MRP_LO: if (a->registrar_state == AVB_MRP_IN) send = AVB_MRP_SEND_IN; else send = AVB_MRP_SEND_MT; break; } switch (state) { case AVB_MRP_VP: state = AVB_MRP_AA; break; case AVB_MRP_VN: state = AVB_MRP_AN; break; case AVB_MRP_AN: if(a->registrar_state == AVB_MRP_IN) state = AVB_MRP_QA; else state = AVB_MRP_AA; break; case AVB_MRP_AA: case AVB_MRP_AP: state = AVB_MRP_QA; break; case AVB_MRP_LA: case AVB_MRP_LO: state = AVB_MRP_VO; break; } break; case AVB_MRP_EVENT_TX_LVA: { switch (state) { case AVB_MRP_VP: if (a->registrar_state == AVB_MRP_IN) send = AVB_MRP_SEND_IN; else send = AVB_MRP_SEND_MT; break; case AVB_MRP_VN: case AVB_MRP_AN: send = AVB_MRP_SEND_NEW; break; case AVB_MRP_AA: case AVB_MRP_QA: case AVB_MRP_AP: case AVB_MRP_QP: if (a->registrar_state == AVB_MRP_IN) send = AVB_MRP_SEND_JOININ; else send = AVB_MRP_SEND_JOINMT; break; } switch (state) { case AVB_MRP_VO: case AVB_MRP_LA: case AVB_MRP_AO: case AVB_MRP_QO: state = AVB_MRP_LO; break; case AVB_MRP_VP: state = AVB_MRP_AA; break; case AVB_MRP_VN: state = AVB_MRP_AN; break; case AVB_MRP_AN: case AVB_MRP_AA: case AVB_MRP_AP: case AVB_MRP_QP: state = AVB_MRP_QA; break; } break; } default: break; } if (a->applicant_state != state || send) { pw_log_debug("APP: attr %p: %s %s %s -> %s %d:%s joined? %s", a, a->attr.name, avb_mrp_event_name(event), avb_applicant_state_name(a->applicant_state), avb_registrar_state_name(state), send, avb_mrp_send_name(send), a->joined? "YES" : " NO"); a->applicant_state = state; } if (a->joined) a->attr.pending_send = send; } void avb_mrp_attribute_rx_event(struct avb_mrp_attribute *attr, uint64_t now, uint8_t event) { static const int map[] = { [AVB_MRP_ATTRIBUTE_EVENT_NEW] = AVB_MRP_EVENT_RX_NEW, [AVB_MRP_ATTRIBUTE_EVENT_JOININ] = AVB_MRP_EVENT_RX_JOININ, [AVB_MRP_ATTRIBUTE_EVENT_IN] = AVB_MRP_EVENT_RX_IN, [AVB_MRP_ATTRIBUTE_EVENT_JOINMT] = AVB_MRP_EVENT_RX_JOINMT, [AVB_MRP_ATTRIBUTE_EVENT_MT] = AVB_MRP_EVENT_RX_MT, [AVB_MRP_ATTRIBUTE_EVENT_LV] = AVB_MRP_EVENT_RX_LV, [AVB_MRP_ATTRIBUTE_EVENT_LVA] = AVB_MRP_EVENT_RX_LVA, }; avb_mrp_attribute_update_state(attr, now, map[event]); } void avb_mrp_attribute_begin(struct avb_mrp_attribute *attr, uint64_t now) { struct attribute *a = SPA_CONTAINER_OF(attr, struct attribute, attr); a->leave_timeout = 0; avb_mrp_attribute_update_state(attr, now, AVB_MRP_EVENT_BEGIN); } void avb_mrp_attribute_join(struct avb_mrp_attribute *attr, uint64_t now, bool is_new) { struct attribute *a = SPA_CONTAINER_OF(attr, struct attribute, attr); a->joined = true; int event = is_new ? AVB_MRP_EVENT_NEW : AVB_MRP_EVENT_JOIN; avb_mrp_attribute_update_state(attr, now, event); } void avb_mrp_attribute_leave(struct avb_mrp_attribute *attr, uint64_t now) { struct attribute *a = SPA_CONTAINER_OF(attr, struct attribute, attr); avb_mrp_attribute_update_state(attr, now, AVB_MRP_EVENT_LV); a->joined = false; } void avb_mrp_destroy(struct avb_mrp *mrp) { mrp_destroy(mrp); } struct avb_mrp *avb_mrp_new(struct server *server) { struct mrp *mrp; mrp = calloc(1, sizeof(*mrp)); if (mrp == NULL) return NULL; mrp->server = server; spa_list_init(&mrp->attributes); spa_hook_list_init(&mrp->listener_list); avdecc_server_add_listener(server, &mrp->server_listener, &server_events, mrp); return (struct avb_mrp*)mrp; } void avb_mrp_add_listener(struct avb_mrp *m, struct spa_hook *listener, const struct avb_mrp_events *events, void *data) { struct mrp *mrp = (struct mrp*)m; spa_hook_list_append(&mrp->listener_list, listener, events, data); } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/modules/module-avb/mrp.h000066400000000000000000000117021511204443500257400ustar00rootroot00000000000000/* AVB support */ /* SPDX-FileCopyrightText: Copyright © 2022 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef AVB_MRP_H #define AVB_MRP_H #include "packets.h" #include "internal.h" #define AVB_MRP_PROTOCOL_VERSION 0 struct avb_packet_mrp { struct avb_ethernet_header eth; uint8_t version; } __attribute__ ((__packed__)); struct avb_packet_mrp_hdr { uint8_t attribute_type; uint8_t attribute_length; } __attribute__ ((__packed__)); struct avb_packet_mrp_vector { #if __BYTE_ORDER == __BIG_ENDIAN unsigned lva:3; unsigned nv1:5; #elif __BYTE_ORDER == __LITTLE_ENDIAN unsigned nv1:5; unsigned lva:3; #endif uint8_t nv2; uint8_t first_value[0]; } __attribute__ ((__packed__)); #define AVB_MRP_VECTOR_SET_NUM_VALUES(a,v) ((a)->nv1 = ((v) >> 8),(a)->nv2 = (v)) #define AVB_MRP_VECTOR_GET_NUM_VALUES(a) ((a)->nv1 << 8 | (a)->nv2) struct avb_packet_mrp_footer { uint16_t end_mark; } __attribute__ ((__packed__)); /* applicant states */ #define AVB_MRP_VO 0 /* Very anxious Observer */ #define AVB_MRP_VP 1 /* Very anxious Passive */ #define AVB_MRP_VN 2 /* Very anxious New */ #define AVB_MRP_AN 3 /* Anxious New */ #define AVB_MRP_AA 4 /* Anxious Active */ #define AVB_MRP_QA 5 /* Quiet Active */ #define AVB_MRP_LA 6 /* Leaving Active */ #define AVB_MRP_AO 7 /* Anxious Observer */ #define AVB_MRP_QO 8 /* Quiet Observer */ #define AVB_MRP_AP 9 /* Anxious Passive */ #define AVB_MRP_QP 10 /* Quiet Passive */ #define AVB_MRP_LO 11 /* Leaving Observer */ /* registrar states */ #define AVB_MRP_IN 16 #define AVB_MRP_LV 17 #define AVB_MRP_MT 18 /* events */ #define AVB_MRP_EVENT_BEGIN 0 #define AVB_MRP_EVENT_NEW 1 #define AVB_MRP_EVENT_JOIN 2 #define AVB_MRP_EVENT_LV 3 #define AVB_MRP_EVENT_TX 4 #define AVB_MRP_EVENT_TX_LVA 5 #define AVB_MRP_EVENT_TX_LVAF 6 #define AVB_MRP_EVENT_RX_NEW 7 #define AVB_MRP_EVENT_RX_JOININ 8 #define AVB_MRP_EVENT_RX_IN 9 #define AVB_MRP_EVENT_RX_JOINMT 10 #define AVB_MRP_EVENT_RX_MT 11 #define AVB_MRP_EVENT_RX_LV 12 #define AVB_MRP_EVENT_RX_LVA 13 #define AVB_MRP_EVENT_FLUSH 14 #define AVB_MRP_EVENT_REDECLARE 15 #define AVB_MRP_EVENT_PERIODIC 16 #define AVB_MRP_EVENT_LV_TIMER 17 #define AVB_MRP_EVENT_LVA_TIMER 18 /* attribute events */ #define AVB_MRP_ATTRIBUTE_EVENT_NEW 0 #define AVB_MRP_ATTRIBUTE_EVENT_JOININ 1 #define AVB_MRP_ATTRIBUTE_EVENT_IN 2 #define AVB_MRP_ATTRIBUTE_EVENT_JOINMT 3 #define AVB_MRP_ATTRIBUTE_EVENT_MT 4 #define AVB_MRP_ATTRIBUTE_EVENT_LV 5 #define AVB_MRP_ATTRIBUTE_EVENT_LVA 6 #define AVB_MRP_SEND_NEW 0 #define AVB_MRP_SEND_JOININ 1 #define AVB_MRP_SEND_IN 2 #define AVB_MRP_SEND_JOINMT 3 #define AVB_MRP_SEND_MT 4 #define AVB_MRP_SEND_LV 5 #define AVB_MRP_SEND_LVA 6 #define AVB_MRP_NOTIFY_NEW 1 #define AVB_MRP_NOTIFY_JOIN 2 #define AVB_MRP_NOTIFY_LEAVE 3 const char *avb_applicant_state_name(uint8_t state); const char *avb_registrar_state_name(uint8_t state); const char *avb_mrp_event_name(uint8_t event); const char *avb_mrp_notify_name(uint8_t notify); const char *avb_mrp_send_name(uint8_t send); struct avb_mrp_attribute { uint8_t pending_send; const char *name; void *user_data; }; struct avb_mrp_attribute_events { #define AVB_VERSION_MRP_ATTRIBUTE_EVENTS 0 uint32_t version; void (*notify) (void *data, uint64_t now, uint8_t notify); }; struct avb_mrp_attribute *avb_mrp_attribute_new(struct avb_mrp *mrp, size_t user_size); void avb_mrp_attribute_destroy(struct avb_mrp_attribute *attr); void avb_mrp_attribute_update_state(struct avb_mrp_attribute *attr, uint64_t now, int event); void avb_mrp_attribute_rx_event(struct avb_mrp_attribute *attr, uint64_t now, uint8_t event); void avb_mrp_attribute_begin(struct avb_mrp_attribute *attr, uint64_t now); void avb_mrp_attribute_join(struct avb_mrp_attribute *attr, uint64_t now, bool is_new); void avb_mrp_attribute_leave(struct avb_mrp_attribute *attr, uint64_t now); void avb_mrp_attribute_add_listener(struct avb_mrp_attribute *attr, struct spa_hook *listener, const struct avb_mrp_attribute_events *events, void *data); struct avb_mrp_parse_info { #define AVB_VERSION_MRP_PARSE_INFO 0 uint32_t version; bool (*check_header) (void *data, const void *hdr, size_t *hdr_size, bool *has_params); int (*attr_event) (void *data, uint64_t now, uint8_t attribute_type, uint8_t event); int (*process) (void *data, uint64_t now, uint8_t attribute_type, const void *value, uint8_t event, uint8_t param, int index); }; int avb_mrp_parse_packet(struct avb_mrp *mrp, uint64_t now, const void *pkt, int size, const struct avb_mrp_parse_info *cb, void *data); struct avb_mrp_events { #define AVB_VERSION_MRP_EVENTS 0 uint32_t version; void (*event) (void *data, uint64_t now, uint8_t event); void (*notify) (void *data, uint64_t now, struct avb_mrp_attribute *attr, uint8_t notify); }; struct avb_mrp *avb_mrp_new(struct server *server); void avb_mrp_destroy(struct avb_mrp *mrp); void avb_mrp_add_listener(struct avb_mrp *mrp, struct spa_hook *listener, const struct avb_mrp_events *events, void *data); #endif /* AVB_MRP_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/modules/module-avb/msrp.c000066400000000000000000000310231511204443500261140ustar00rootroot00000000000000/* AVB support */ /* SPDX-FileCopyrightText: Copyright © 2022 Wim Taymans */ /* SPDX-FileCopyrightText: Copyright © 2025 Kebag-Logic */ /* SPDX-FileCopyrightText: Copyright © 2025 Alexandre Malki */ /* SPDX-License-Identifier: MIT */ #include #include #include #include "utils.h" #include "msrp.h" static const uint8_t msrp_mac[6] = AVB_MSRP_MAC; struct attr { struct avb_msrp_attribute attr; struct msrp *msrp; struct spa_hook listener; struct spa_list link; }; struct msrp { struct server *server; struct spa_hook server_listener; struct spa_hook mrp_listener; struct spa_source *source; struct spa_list attributes; }; static void debug_msrp_talker_common(const struct avb_packet_msrp_talker *t) { char buf[128]; pw_log_info(" stream-id: %s", avb_utils_format_id(buf, sizeof(buf), be64toh(t->stream_id))); pw_log_info(" dest-addr: %s", avb_utils_format_addr(buf, sizeof(buf), t->dest_addr)); pw_log_info(" vlan-id: %d", ntohs(t->vlan_id)); pw_log_info(" tspec-max-frame-size: %d", ntohs(t->tspec_max_frame_size)); pw_log_info(" tspec-max-interval-frames: %d", ntohs(t->tspec_max_interval_frames)); pw_log_info(" priority: %d", t->priority); pw_log_info(" rank: %d", t->rank); pw_log_info(" accumulated-latency: %d", ntohl(t->accumulated_latency)); } static void debug_msrp_talker(const struct avb_packet_msrp_talker *t) { pw_log_info("talker"); debug_msrp_talker_common(t); } static void notify_talker(struct msrp *msrp, uint64_t now, struct attr *attr, uint8_t notify) { pw_log_info("> notify talker: %s", avb_mrp_notify_name(notify)); debug_msrp_talker(&attr->attr.attr.talker); } static int process_talker(struct msrp *msrp, uint64_t now, uint8_t attr_type, const void *m, uint8_t event, uint8_t param, int num) { const struct avb_packet_msrp_talker *t = m; struct attr *a; spa_list_for_each(a, &msrp->attributes, link) if (a->attr.type == attr_type && a->attr.attr.talker.stream_id == t->stream_id) { a->attr.attr.talker = *t; avb_mrp_attribute_rx_event(a->attr.mrp, now, event); } return 0; } static int encode_talker(struct msrp *msrp, struct attr *a, void *m) { struct avb_packet_msrp_msg *msg = m; struct avb_packet_mrp_vector *v; struct avb_packet_msrp_talker *t; struct avb_packet_mrp_footer *f; uint8_t *ev; size_t attr_list_length = sizeof(*v) + sizeof(*t) + sizeof(*f) + 1; msg->attribute_type = AVB_MSRP_ATTRIBUTE_TYPE_TALKER_ADVERTISE; msg->attribute_length = sizeof(*t); msg->attribute_list_length = htons(attr_list_length); v = (struct avb_packet_mrp_vector *)msg->attribute_list; v->lva = 0; AVB_MRP_VECTOR_SET_NUM_VALUES(v, 1); t = (struct avb_packet_msrp_talker *)v->first_value; *t = a->attr.attr.talker; ev = SPA_PTROFF(t, sizeof(*t), uint8_t); *ev = a->attr.mrp->pending_send * 6 * 6; f = SPA_PTROFF(ev, sizeof(*ev), struct avb_packet_mrp_footer); f->end_mark = 0; return attr_list_length + sizeof(*msg); } static void debug_msrp_talker_fail(const struct avb_packet_msrp_talker_fail *t) { char buf[128]; pw_log_info("talker fail"); debug_msrp_talker_common(&t->talker); pw_log_info(" bridge-id: %s", avb_utils_format_id(buf, sizeof(buf), be64toh(t->bridge_id))); pw_log_info(" failure-code: %d", t->failure_code); } static int process_talker_fail(struct msrp *msrp, uint64_t now, uint8_t attr_type, const void *m, uint8_t event, uint8_t param, int num) { const struct avb_packet_msrp_talker_fail *t = m; struct attr *a; debug_msrp_talker_fail(t); spa_list_for_each(a, &msrp->attributes, link) if (a->attr.type == attr_type && a->attr.attr.talker_fail.talker.stream_id == t->talker.stream_id) avb_mrp_attribute_rx_event(a->attr.mrp, now, event); return 0; } static void debug_msrp_listener(const struct avb_packet_msrp_listener *l, uint8_t param) { char buf[128]; pw_log_info("listener"); pw_log_info(" %s", avb_utils_format_id(buf, sizeof(buf), be64toh(l->stream_id))); pw_log_info(" %d", param); } static void notify_listener(struct msrp *msrp, uint64_t now, struct attr *attr, uint8_t notify) { pw_log_info("> notify listener: %s", avb_mrp_notify_name(notify)); debug_msrp_listener(&attr->attr.attr.listener, attr->attr.param); } static int process_listener(struct msrp *msrp, uint64_t now, uint8_t attr_type, const void *m, uint8_t event, uint8_t param, int num) { const struct avb_packet_msrp_listener *l = m; struct attr *a; spa_list_for_each(a, &msrp->attributes, link) if (a->attr.type == attr_type && a->attr.attr.listener.stream_id == l->stream_id) avb_mrp_attribute_rx_event(a->attr.mrp, now, event); return 0; } static int encode_listener(struct msrp *msrp, struct attr *a, void *m) { struct avb_packet_msrp_msg *msg = m; struct avb_packet_mrp_vector *v; struct avb_packet_msrp_listener *l; struct avb_packet_mrp_footer *f; uint8_t *ev; size_t attr_list_length = sizeof(*v) + sizeof(*l) + sizeof(*f) + 1 + 1; msg->attribute_type = AVB_MSRP_ATTRIBUTE_TYPE_LISTENER; msg->attribute_length = sizeof(*l); msg->attribute_list_length = htons(attr_list_length); v = (struct avb_packet_mrp_vector *)msg->attribute_list; v->lva = 0; AVB_MRP_VECTOR_SET_NUM_VALUES(v, 1); l = (struct avb_packet_msrp_listener *)v->first_value; *l = a->attr.attr.listener; ev = SPA_PTROFF(l, sizeof(*l), uint8_t); *ev = a->attr.mrp->pending_send * 6 * 6; ev = SPA_PTROFF(ev, sizeof(*ev), uint8_t); *ev = a->attr.param * 4 * 4 * 4; f = SPA_PTROFF(ev, sizeof(*ev), struct avb_packet_mrp_footer); f->end_mark = 0; return attr_list_length + sizeof(*msg); } static void debug_msrp_domain(const struct avb_packet_msrp_domain *d) { pw_log_info("domain"); pw_log_info(" id: %d", d->sr_class_id); pw_log_info(" prio: %d", d->sr_class_priority); pw_log_info(" vid: %d", ntohs(d->sr_class_vid)); } static void notify_domain(struct msrp *msrp, uint64_t now, struct attr *attr, uint8_t notify) { pw_log_info("> notify domain: %s", avb_mrp_notify_name(notify)); debug_msrp_domain(&attr->attr.attr.domain); } static int process_domain(struct msrp *msrp, uint64_t now, uint8_t attr_type, const void *m, uint8_t event, uint8_t param, int num) { struct attr *a; spa_list_for_each(a, &msrp->attributes, link) if (a->attr.type == attr_type) avb_mrp_attribute_rx_event(a->attr.mrp, now, event); return 0; } static int encode_domain(struct msrp *msrp, struct attr *a, void *m) { struct avb_packet_msrp_msg *msg = m; struct avb_packet_mrp_vector *v; struct avb_packet_msrp_domain *d; struct avb_packet_mrp_footer *f; uint8_t *ev; size_t attr_list_length = sizeof(*v) + sizeof(*d) + sizeof(*f) + 1; msg->attribute_type = AVB_MSRP_ATTRIBUTE_TYPE_DOMAIN; msg->attribute_length = sizeof(*d); msg->attribute_list_length = htons(attr_list_length); v = (struct avb_packet_mrp_vector *)msg->attribute_list; v->lva = 0; AVB_MRP_VECTOR_SET_NUM_VALUES(v, 1); d = (struct avb_packet_msrp_domain *)v->first_value; *d = a->attr.attr.domain; ev = SPA_PTROFF(d, sizeof(*d), uint8_t); *ev = a->attr.mrp->pending_send * 36; f = SPA_PTROFF(ev, sizeof(*ev), struct avb_packet_mrp_footer); f->end_mark = 0; return attr_list_length + sizeof(*msg); } static const struct { const char *name; int (*process) (struct msrp *msrp, uint64_t now, uint8_t attr_type, const void *m, uint8_t event, uint8_t param, int num); int (*encode) (struct msrp *msrp, struct attr *attr, void *m); void (*notify) (struct msrp *msrp, uint64_t now, struct attr *attr, uint8_t notify); } dispatch[] = { [AVB_MSRP_ATTRIBUTE_TYPE_TALKER_ADVERTISE] = { "talker", process_talker, encode_talker, notify_talker, }, [AVB_MSRP_ATTRIBUTE_TYPE_TALKER_FAILED] = { "talker-fail", process_talker_fail, NULL, NULL }, [AVB_MSRP_ATTRIBUTE_TYPE_LISTENER] = { "listener", process_listener, encode_listener, notify_listener }, [AVB_MSRP_ATTRIBUTE_TYPE_DOMAIN] = { "domain", process_domain, encode_domain, notify_domain, }, }; static bool msrp_check_header(void *data, const void *hdr, size_t *hdr_size, bool *has_params) { const struct avb_packet_msrp_msg *msg = hdr; uint8_t attr_type = msg->attribute_type; if (!AVB_MSRP_ATTRIBUTE_TYPE_VALID(attr_type)) return false; *hdr_size = sizeof(*msg); *has_params = attr_type == AVB_MSRP_ATTRIBUTE_TYPE_LISTENER; return true; } static int msrp_attr_event(void *data, uint64_t now, uint8_t attribute_type, uint8_t event) { struct msrp *msrp = data; struct attr *a; spa_list_for_each(a, &msrp->attributes, link) if (a->attr.type == attribute_type) avb_mrp_attribute_update_state(a->attr.mrp, now, event); return 0; } static int msrp_process(void *data, uint64_t now, uint8_t attribute_type, const void *value, uint8_t event, uint8_t param, int index) { struct msrp *msrp = data; return dispatch[attribute_type].process(msrp, now, attribute_type, value, event, param, index); } static const struct avb_mrp_parse_info info = { AVB_VERSION_MRP_PARSE_INFO, .check_header = msrp_check_header, .attr_event = msrp_attr_event, .process = msrp_process, }; static int msrp_message(struct msrp *msrp, uint64_t now, const void *message, int len) { return avb_mrp_parse_packet(msrp->server->mrp, now, message, len, &info, msrp); } static void on_socket_data(void *data, int fd, uint32_t mask) { struct msrp *msrp = data; struct timespec now; if (mask & SPA_IO_IN) { int len; uint8_t buffer[2048]; len = recv(fd, buffer, sizeof(buffer), 0); if (len < 0) { pw_log_warn("got recv error: %m"); } else if (len < (int)sizeof(struct avb_packet_header)) { pw_log_warn("short packet received (%d < %d)", len, (int)sizeof(struct avb_packet_header)); } else { clock_gettime(CLOCK_REALTIME, &now); msrp_message(msrp, SPA_TIMESPEC_TO_NSEC(&now), buffer, len); } } } static void msrp_destroy(void *data) { struct msrp *msrp = data; spa_hook_remove(&msrp->server_listener); pw_loop_destroy_source(msrp->server->impl->loop, msrp->source); free(msrp); } static const struct server_events server_events = { AVB_VERSION_SERVER_EVENTS, .destroy = msrp_destroy, }; static void msrp_notify(void *data, uint64_t now, uint8_t notify) { struct attr *a = data; struct msrp *msrp = a->msrp; return dispatch[a->attr.type].notify(msrp, now, a, notify); } static const struct avb_mrp_attribute_events mrp_attr_events = { AVB_VERSION_MRP_ATTRIBUTE_EVENTS, .notify = msrp_notify, }; struct avb_msrp_attribute *avb_msrp_attribute_new(struct avb_msrp *m, uint8_t type) { struct msrp *msrp = (struct msrp*)m; struct avb_mrp_attribute *attr; struct attr *a; attr = avb_mrp_attribute_new(msrp->server->mrp, sizeof(struct attr)); a = attr->user_data; a->msrp = msrp; a->attr.mrp = attr; a->attr.type = type; attr->name = "MSRP"; spa_list_append(&msrp->attributes, &a->link); avb_mrp_attribute_add_listener(attr, &a->listener, &mrp_attr_events, a); return &a->attr; } static void msrp_event(void *data, uint64_t now, uint8_t event) { struct msrp *msrp = data; uint8_t buffer[2048]; struct avb_packet_mrp *p = (struct avb_packet_mrp*)buffer; struct avb_packet_mrp_footer *f; void *msg = SPA_PTROFF(buffer, sizeof(*p), void); struct attr *a; int len, count = 0; size_t total = sizeof(*p) + 2; p->version = AVB_MRP_PROTOCOL_VERSION; spa_list_for_each(a, &msrp->attributes, link) { if (!a->attr.mrp->pending_send) continue; if (dispatch[a->attr.type].encode == NULL) continue; pw_log_debug("send %s %s", dispatch[a->attr.type].name, avb_mrp_send_name(a->attr.mrp->pending_send)); len = dispatch[a->attr.type].encode(msrp, a, msg); if (len < 0) break; count++; msg = SPA_PTROFF(msg, len, void); total += len; } f = (struct avb_packet_mrp_footer *)msg; f->end_mark = 0; if (count > 0) avb_server_send_packet(msrp->server, msrp_mac, AVB_MSRP_ETH, buffer, total); } static const struct avb_mrp_events mrp_events = { AVB_VERSION_MRP_EVENTS, .event = msrp_event, }; struct avb_msrp *avb_msrp_register(struct server *server) { struct msrp *msrp; int fd, res; fd = avb_server_make_socket(server, AVB_MSRP_ETH, msrp_mac); if (fd < 0) { errno = -fd; return NULL; } msrp = calloc(1, sizeof(*msrp)); if (msrp == NULL) { res = -errno; goto error_close; } msrp->server = server; spa_list_init(&msrp->attributes); msrp->source = pw_loop_add_io(server->impl->loop, fd, SPA_IO_IN, true, on_socket_data, msrp); if (msrp->source == NULL) { res = -errno; pw_log_error("msrp %p: can't create msrp source: %m", msrp); goto error_no_source; } avdecc_server_add_listener(server, &msrp->server_listener, &server_events, msrp); avb_mrp_add_listener(server->mrp, &msrp->mrp_listener, &mrp_events, msrp); return (struct avb_msrp*)msrp; error_no_source: free(msrp); error_close: close(fd); errno = -res; return NULL; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/modules/module-avb/msrp.h000066400000000000000000000060531511204443500261260ustar00rootroot00000000000000/* AVB support */ /* SPDX-FileCopyrightText: Copyright © 2022 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef AVB_MSRP_H #define AVB_MSRP_H #include "internal.h" #include "mrp.h" #define AVB_MSRP_ETH 0x22ea #define AVB_MSRP_MAC { 0x01, 0x80, 0xc2, 0x00, 0x00, 0xe }; #define AVB_MSRP_ATTRIBUTE_TYPE_TALKER_ADVERTISE 1 #define AVB_MSRP_ATTRIBUTE_TYPE_TALKER_FAILED 2 #define AVB_MSRP_ATTRIBUTE_TYPE_LISTENER 3 #define AVB_MSRP_ATTRIBUTE_TYPE_DOMAIN 4 #define AVB_MSRP_ATTRIBUTE_TYPE_VALID(t) ((t)>=1 && (t)<=4) struct avb_packet_msrp_msg { uint8_t attribute_type; uint8_t attribute_length; uint16_t attribute_list_length; uint8_t attribute_list[0]; } __attribute__ ((__packed__)); #define AVB_MSRP_TSPEC_MAX_INTERVAL_FRAMES_DEFAULT 1 #define AVB_MSRP_RANK_DEFAULT 1 #define AVB_MSRP_PRIORITY_DEFAULT 3 struct avb_packet_msrp_talker { uint64_t stream_id; uint8_t dest_addr[6]; uint16_t vlan_id; uint16_t tspec_max_frame_size; uint16_t tspec_max_interval_frames; #if __BYTE_ORDER == __BIG_ENDIAN unsigned priority:3; unsigned rank:1; unsigned reserved:4; #elif __BYTE_ORDER == __LITTLE_ENDIAN unsigned reserved:4; unsigned rank:1; unsigned priority:3; #endif uint32_t accumulated_latency; } __attribute__ ((__packed__)); /* failure codes */ #define AVB_MRP_FAIL_BANDWIDTH 1 #define AVB_MRP_FAIL_BRIDGE 2 #define AVB_MRP_FAIL_TC_BANDWIDTH 3 #define AVB_MRP_FAIL_ID_BUSY 4 #define AVB_MRP_FAIL_DSTADDR_BUSY 5 #define AVB_MRP_FAIL_PREEMPTED 6 #define AVB_MRP_FAIL_LATENCY_CHNG 7 #define AVB_MRP_FAIL_PORT_NOT_AVB 8 #define AVB_MRP_FAIL_DSTADDR_FULL 9 #define AVB_MRP_FAIL_AVB_MRP_RESOURCE 10 #define AVB_MRP_FAIL_MMRP_RESOURCE 11 #define AVB_MRP_FAIL_DSTADDR_FAIL 12 #define AVB_MRP_FAIL_PRIO_NOT_SR 13 #define AVB_MRP_FAIL_FRAME_SIZE 14 #define AVB_MRP_FAIL_FANIN_EXCEED 15 #define AVB_MRP_FAIL_STREAM_CHANGE 16 #define AVB_MRP_FAIL_VLAN_BLOCKED 17 #define AVB_MRP_FAIL_VLAN_DISABLED 18 #define AVB_MRP_FAIL_SR_PRIO_ERR 19 struct avb_packet_msrp_talker_fail { struct avb_packet_msrp_talker talker; uint64_t bridge_id; uint8_t failure_code; } __attribute__ ((__packed__)); struct avb_packet_msrp_listener { uint64_t stream_id; } __attribute__ ((__packed__)); /* domain discovery */ #define AVB_MSRP_CLASS_ID_DEFAULT 6 #define AVB_DEFAULT_VLAN 2 struct avb_packet_msrp_domain { uint8_t sr_class_id; uint8_t sr_class_priority; uint16_t sr_class_vid; } __attribute__ ((__packed__)); #define AVB_MSRP_LISTENER_PARAM_IGNORE 0 #define AVB_MSRP_LISTENER_PARAM_ASKING_FAILED 1 #define AVB_MSRP_LISTENER_PARAM_READY 2 #define AVB_MSRP_LISTENER_PARAM_READY_FAILED 3 struct avb_msrp_attribute { struct avb_mrp_attribute *mrp; uint8_t type; uint8_t param; union { struct avb_packet_msrp_talker talker; struct avb_packet_msrp_talker_fail talker_fail; struct avb_packet_msrp_listener listener; struct avb_packet_msrp_domain domain; } attr; }; struct avb_msrp; struct avb_msrp_attribute *avb_msrp_attribute_new(struct avb_msrp *msrp, uint8_t type); struct avb_msrp *avb_msrp_register(struct server *server); #endif /* AVB_MSRP_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/modules/module-avb/mvrp.c000066400000000000000000000160461511204443500261270ustar00rootroot00000000000000/* AVB support */ /* SPDX-FileCopyrightText: Copyright © 2022 Wim Taymans */ /* SPDX-FileCopyrightText: Copyright © 2025 Kebag-Logic */ /* SPDX-FileCopyrightText: Copyright © 2025 Alexandre Malki */ /* SPDX-License-Identifier: MIT */ #include #include #include "mvrp.h" static const uint8_t mvrp_mac[6] = AVB_MVRP_MAC; struct attr { struct avb_mvrp_attribute attr; struct spa_hook listener; struct spa_list link; struct mvrp *mvrp; }; struct mvrp { struct server *server; struct spa_hook server_listener; struct spa_hook mrp_listener; struct spa_source *source; struct spa_list attributes; }; static bool mvrp_check_header(void *data, const void *hdr, size_t *hdr_size, bool *has_params) { const struct avb_packet_mvrp_msg *msg = hdr; uint8_t attr_type = msg->attribute_type; if (!AVB_MVRP_ATTRIBUTE_TYPE_VALID(attr_type)) return false; *hdr_size = sizeof(*msg); *has_params = false; return true; } static int mvrp_attr_event(void *data, uint64_t now, uint8_t attribute_type, uint8_t event) { struct mvrp *mvrp = data; struct attr *a; spa_list_for_each(a, &mvrp->attributes, link) if (a->attr.type == attribute_type) avb_mrp_attribute_rx_event(a->attr.mrp, now, event); return 0; } static void debug_vid(const struct avb_packet_mvrp_vid *t) { pw_log_info("vid"); pw_log_info(" %d", ntohs(t->vlan)); } static int process_vid(struct mvrp *mvrp, uint64_t now, uint8_t attr_type, const void *m, uint8_t event, uint8_t param, int num) { return mvrp_attr_event(mvrp, now, attr_type, event); } static int encode_vid(struct mvrp *mvrp, struct attr *a, void *m) { struct avb_packet_mvrp_msg *msg = m; struct avb_packet_mrp_vector *v; struct avb_packet_mvrp_vid *d; struct avb_packet_mrp_footer *f; uint8_t *ev; size_t attr_list_length = sizeof(*v) + sizeof(*d) + sizeof(*f) + 1; msg->attribute_type = AVB_MVRP_ATTRIBUTE_TYPE_VID; msg->attribute_length = sizeof(*d); v = (struct avb_packet_mrp_vector *)msg->attribute_list; v->lva = 0; AVB_MRP_VECTOR_SET_NUM_VALUES(v, 1); d = (struct avb_packet_mvrp_vid *)v->first_value; *d = a->attr.attr.vid; ev = SPA_PTROFF(d, sizeof(*d), uint8_t); *ev = a->attr.mrp->pending_send * 36; f = SPA_PTROFF(ev, sizeof(*ev), struct avb_packet_mrp_footer); f->end_mark = 0; return attr_list_length + sizeof(*msg); } static void notify_vid(struct mvrp *mvrp, uint64_t now, struct attr *attr, uint8_t notify) { pw_log_info("> notify vid: %s", avb_mrp_notify_name(notify)); debug_vid(&attr->attr.attr.vid); } static const struct { const char *name; int (*process) (struct mvrp *mvrp, uint64_t now, uint8_t attr_type, const void *m, uint8_t event, uint8_t param, int num); int (*encode) (struct mvrp *mvrp, struct attr *attr, void *m); void (*notify) (struct mvrp *mvrp, uint64_t now, struct attr *attr, uint8_t notify); } dispatch[] = { [AVB_MVRP_ATTRIBUTE_TYPE_VID] = { "vid", process_vid, encode_vid, notify_vid }, }; static int mvrp_process(void *data, uint64_t now, uint8_t attribute_type, const void *value, uint8_t event, uint8_t param, int index) { struct mvrp *mvrp = data; return dispatch[attribute_type].process(mvrp, now, attribute_type, value, event, param, index); } static const struct avb_mrp_parse_info info = { AVB_VERSION_MRP_PARSE_INFO, .check_header = mvrp_check_header, .attr_event = mvrp_attr_event, .process = mvrp_process, }; static int mvrp_message(struct mvrp *mvrp, uint64_t now, const void *message, int len) { pw_log_debug("MVRP"); return avb_mrp_parse_packet(mvrp->server->mrp, now, message, len, &info, mvrp); } static void on_socket_data(void *data, int fd, uint32_t mask) { struct mvrp *mvrp = data; struct timespec now; if (mask & SPA_IO_IN) { int len; uint8_t buffer[2048]; len = recv(fd, buffer, sizeof(buffer), 0); if (len < 0) { pw_log_warn("got recv error: %m"); } else if (len < (int)sizeof(struct avb_packet_header)) { pw_log_warn("short packet received (%d < %d)", len, (int)sizeof(struct avb_packet_header)); } else { clock_gettime(CLOCK_REALTIME, &now); mvrp_message(mvrp, SPA_TIMESPEC_TO_NSEC(&now), buffer, len); } } } static void mvrp_destroy(void *data) { struct mvrp *mvrp = data; spa_hook_remove(&mvrp->server_listener); pw_loop_destroy_source(mvrp->server->impl->loop, mvrp->source); free(mvrp); } static const struct server_events server_events = { AVB_VERSION_SERVER_EVENTS, .destroy = mvrp_destroy, }; static void mvrp_notify(void *data, uint64_t now, uint8_t notify) { struct attr *a = data; struct mvrp *mvrp = a->mvrp; return dispatch[a->attr.type].notify(mvrp, now, a, notify); } static const struct avb_mrp_attribute_events mrp_attr_events = { AVB_VERSION_MRP_ATTRIBUTE_EVENTS, .notify = mvrp_notify, }; struct avb_mvrp_attribute *avb_mvrp_attribute_new(struct avb_mvrp *m, uint8_t type) { struct mvrp *mvrp = (struct mvrp*)m; struct avb_mrp_attribute *attr; struct attr *a; attr = avb_mrp_attribute_new(mvrp->server->mrp, sizeof(struct attr)); a = attr->user_data; a->attr.mrp = attr; a->attr.type = type; attr->name = "MVRP"; spa_list_append(&mvrp->attributes, &a->link); avb_mrp_attribute_add_listener(attr, &a->listener, &mrp_attr_events, a); return &a->attr; } static void mvrp_event(void *data, uint64_t now, uint8_t event) { struct mvrp *mvrp = data; uint8_t buffer[2048]; struct avb_packet_mrp *p = (struct avb_packet_mrp*)buffer; struct avb_packet_mrp_footer *f; void *msg = SPA_PTROFF(buffer, sizeof(*p), void); struct attr *a; int len, count = 0; size_t total = sizeof(*p) + 2; p->version = AVB_MRP_PROTOCOL_VERSION; spa_list_for_each(a, &mvrp->attributes, link) { if (!a->attr.mrp->pending_send) continue; if (dispatch[a->attr.type].encode == NULL) continue; pw_log_debug("send %s %s", dispatch[a->attr.type].name, avb_mrp_send_name(a->attr.mrp->pending_send)); len = dispatch[a->attr.type].encode(mvrp, a, msg); if (len < 0) break; count++; msg = SPA_PTROFF(msg, len, void); total += len; } f = (struct avb_packet_mrp_footer *)msg; f->end_mark = 0; if (count > 0) avb_server_send_packet(mvrp->server, mvrp_mac, AVB_MVRP_ETH, buffer, total); } static const struct avb_mrp_events mrp_events = { AVB_VERSION_MRP_EVENTS, .event = mvrp_event, }; struct avb_mvrp *avb_mvrp_register(struct server *server) { struct mvrp *mvrp; int fd, res; fd = avb_server_make_socket(server, AVB_MVRP_ETH, mvrp_mac); if (fd < 0) { errno = -fd; return NULL; } mvrp = calloc(1, sizeof(*mvrp)); if (mvrp == NULL) { res = -errno; goto error_close; } mvrp->server = server; spa_list_init(&mvrp->attributes); mvrp->source = pw_loop_add_io(server->impl->loop, fd, SPA_IO_IN, true, on_socket_data, mvrp); if (mvrp->source == NULL) { res = -errno; pw_log_error("mvrp %p: can't create mvrp source: %m", mvrp); goto error_no_source; } avdecc_server_add_listener(server, &mvrp->server_listener, &server_events, mvrp); avb_mrp_add_listener(server->mrp, &mvrp->mrp_listener, &mrp_events, mvrp); return (struct avb_mvrp*)mvrp; error_no_source: free(mvrp); error_close: close(fd); errno = -res; return NULL; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/modules/module-avb/mvrp.h000066400000000000000000000016271511204443500261330ustar00rootroot00000000000000/* AVB support */ /* SPDX-FileCopyrightText: Copyright © 2022 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef AVB_MVRP_H #define AVB_MVRP_H #include "mrp.h" #include "internal.h" #define AVB_MVRP_ETH 0x88f5 #define AVB_MVRP_MAC { 0x01, 0x80, 0xc2, 0x00, 0x00, 0x21 }; struct avb_packet_mvrp_msg { uint8_t attribute_type; uint8_t attribute_length; uint8_t attribute_list[0]; } __attribute__ ((__packed__)); #define AVB_MVRP_ATTRIBUTE_TYPE_VID 1 #define AVB_MVRP_ATTRIBUTE_TYPE_VALID(t) ((t)==1) struct avb_packet_mvrp_vid { uint16_t vlan; } __attribute__ ((__packed__)); struct avb_mvrp; struct avb_mvrp_attribute { struct avb_mrp_attribute *mrp; uint8_t type; union { struct avb_packet_mvrp_vid vid; } attr; }; struct avb_mvrp_attribute *avb_mvrp_attribute_new(struct avb_mvrp *mvrp, uint8_t type); struct avb_mvrp *avb_mvrp_register(struct server *server); #endif /* AVB_MVRP_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/modules/module-avb/packets.h000066400000000000000000000043661511204443500266040ustar00rootroot00000000000000/* Spa AVB support */ /* SPDX-FileCopyrightText: Copyright © 2022 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef AVB_PACKETS_H #define AVB_PACKETS_H #include #define AVB_SUBTYPE_61883_IIDC 0x00 #define AVB_SUBTYPE_MMA_STREAM 0x01 #define AVB_SUBTYPE_AAF 0x02 #define AVB_SUBTYPE_CVF 0x03 #define AVB_SUBTYPE_CRF 0x04 #define AVB_SUBTYPE_TSCF 0x05 #define AVB_SUBTYPE_SVF 0x06 #define AVB_SUBTYPE_RVF 0x07 #define AVB_SUBTYPE_AEF_CONTINUOUS 0x6E #define AVB_SUBTYPE_VSF_STREAM 0x6F #define AVB_SUBTYPE_EF_STREAM 0x7F #define AVB_SUBTYPE_NTSCF 0x82 #define AVB_SUBTYPE_ESCF 0xEC #define AVB_SUBTYPE_EECF 0xED #define AVB_SUBTYPE_AEF_DISCRETE 0xEE #define AVB_SUBTYPE_ADP 0xFA #define AVB_SUBTYPE_AECP 0xFB #define AVB_SUBTYPE_ACMP 0xFC #define AVB_SUBTYPE_MAAP 0xFE #define AVB_SUBTYPE_EF_CONTROL 0xFF struct avb_ethernet_header { uint8_t dest[6]; uint8_t src[6]; uint16_t type; } __attribute__ ((__packed__)); struct avb_frame_header { uint8_t dest[6]; uint8_t src[6]; uint16_t type; /* 802.1Q Virtual Lan 0x8100 */ uint16_t prio_cfi_id; uint16_t etype; } __attribute__ ((__packed__)); struct avb_packet_header { uint8_t subtype; #if __BYTE_ORDER == __BIG_ENDIAN unsigned sv:1; /* stream_id valid */ unsigned version:3; unsigned subtype_data1:4; unsigned subtype_data2:5; unsigned len1:3; #elif __BYTE_ORDER == __LITTLE_ENDIAN unsigned subtype_data1:4; unsigned version:3; unsigned sv:1; unsigned len1:3; unsigned subtype_data2:5; #elif #error "Unknown byte order" #endif uint8_t len2:8; } __attribute__ ((__packed__)); #define AVB_PACKET_SET_SUBTYPE(p,v) ((p)->subtype = (v)) #define AVB_PACKET_SET_SV(p,v) ((p)->sv = (v)) #define AVB_PACKET_SET_VERSION(p,v) ((p)->version = (v)) #define AVB_PACKET_SET_SUB1(p,v) ((p)->subtype_data1 = (v)) #define AVB_PACKET_SET_SUB2(p,v) ((p)->subtype_data2 = (v)) #define AVB_PACKET_SET_LENGTH(p,v) ((p)->len1 = ((v) >> 8),(p)->len2 = (v)) #define AVB_PACKET_GET_SUBTYPE(p) ((p)->subtype) #define AVB_PACKET_GET_SV(p) ((p)->sv) #define AVB_PACKET_GET_VERSION(p) ((p)->version) #define AVB_PACKET_GET_SUB1(p) ((p)->subtype_data1) #define AVB_PACKET_GET_SUB2(p) ((p)->subtype_data2) #define AVB_PACKET_GET_LENGTH(p) ((p)->len1 << 8 | (p)->len2) #endif /* AVB_PACKETS_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/modules/module-avb/srp.c000066400000000000000000000013171511204443500257420ustar00rootroot00000000000000/* AVB support */ /* SPDX-FileCopyrightText: Copyright © 2022 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include "srp.h" struct srp { struct server *server; struct spa_hook server_listener; }; static void srp_destroy(void *data) { struct srp *srp = data; spa_hook_remove(&srp->server_listener); free(srp); } static const struct server_events server_events = { AVB_VERSION_SERVER_EVENTS, .destroy = srp_destroy, }; int avb_srp_register(struct server *server) { struct srp *srp; srp = calloc(1, sizeof(*srp)); if (srp == NULL) return -errno; srp->server = server; avdecc_server_add_listener(server, &srp->server_listener, &server_events, srp); return 0; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/modules/module-avb/srp.h000066400000000000000000000003631511204443500257470ustar00rootroot00000000000000/* AVB support */ /* SPDX-FileCopyrightText: Copyright © 2022 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef AVB_SRP_H #define AVB_SRP_H #include "internal.h" int avb_srp_register(struct server *server); #endif /* AVB_SRP_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/modules/module-avb/stream.c000066400000000000000000000371431511204443500264370ustar00rootroot00000000000000/* AVB support */ /* SPDX-FileCopyrightText: Copyright © 2022 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #include #include #include #include #include #include "iec61883.h" #include "stream.h" #include "utils.h" #include "aecp-aem-descriptors.h" static void on_stream_destroy(void *d) { struct stream *stream = d; spa_hook_remove(&stream->stream_listener); stream->stream = NULL; } static void on_source_stream_process(void *data) { struct stream *stream = data; struct pw_buffer *buf; struct spa_data *d; uint32_t index, n_bytes; int32_t avail, wanted; if ((buf = pw_stream_dequeue_buffer(stream->stream)) == NULL) { pw_log_debug("out of buffers: %m"); return; } d = buf->buffer->datas; wanted = buf->requested ? buf->requested * stream->stride : d[0].maxsize; n_bytes = SPA_MIN(d[0].maxsize, (uint32_t)wanted); avail = spa_ringbuffer_get_read_index(&stream->ring, &index); if (avail < wanted) { pw_log_debug("capture underrun %d < %d", avail, wanted); memset(d[0].data, 0, n_bytes); } else { spa_ringbuffer_read_data(&stream->ring, stream->buffer_data, stream->buffer_size, index % stream->buffer_size, d[0].data, n_bytes); index += n_bytes; spa_ringbuffer_read_update(&stream->ring, index); } d[0].chunk->size = n_bytes; d[0].chunk->stride = stream->stride; d[0].chunk->offset = 0; buf->size = n_bytes / stream->stride; pw_stream_queue_buffer(stream->stream, buf); } static const struct pw_stream_events source_stream_events = { PW_VERSION_STREAM_EVENTS, .destroy = on_stream_destroy, .process = on_source_stream_process }; static inline void set_iovec(struct spa_ringbuffer *rbuf, void *buffer, uint32_t size, uint32_t offset, struct iovec *iov, uint32_t len) { iov[0].iov_len = SPA_MIN(len, size - offset); iov[0].iov_base = SPA_PTROFF(buffer, offset, void); iov[1].iov_len = len - iov[0].iov_len; iov[1].iov_base = buffer; } static int flush_write(struct stream *stream, uint64_t current_time) { int32_t avail; uint32_t index; uint64_t ptime, txtime; int pdu_count; ssize_t n; struct avb_frame_header *h = (void*)stream->pdu; struct avb_packet_iec61883 *p = SPA_PTROFF(h, sizeof(*h), void); uint8_t dbc; avail = spa_ringbuffer_get_read_index(&stream->ring, &index); pdu_count = (avail / stream->stride) / stream->frames_per_pdu; txtime = current_time + stream->t_uncertainty; ptime = txtime + stream->mtt; dbc = stream->dbc; while (pdu_count--) { *(uint64_t*)CMSG_DATA(stream->cmsg) = txtime; set_iovec(&stream->ring, stream->buffer_data, stream->buffer_size, index % stream->buffer_size, &stream->iov[1], stream->payload_size); p->seq_num = stream->pdu_seq++; p->tv = 1; p->timestamp = ptime; p->dbc = dbc; n = sendmsg(stream->source->fd, &stream->msg, MSG_NOSIGNAL); if (n < 0 || n != (ssize_t)stream->pdu_size) { pw_log_error("sendmsg() failed %zd != %zd: %m", n, stream->pdu_size); } txtime += stream->pdu_period; ptime += stream->pdu_period; index += stream->payload_size; dbc += stream->frames_per_pdu; } stream->dbc = dbc; spa_ringbuffer_read_update(&stream->ring, index); return 0; } static void on_sink_stream_process(void *data) { struct stream *stream = data; struct pw_buffer *buf; struct spa_data *d; int32_t filled; uint32_t index, offs, avail, size; struct timespec now; if ((buf = pw_stream_dequeue_buffer(stream->stream)) == NULL) { pw_log_debug("out of buffers: %m"); return; } d = buf->buffer->datas; offs = SPA_MIN(d[0].chunk->offset, d[0].maxsize); size = SPA_MIN(d[0].chunk->size, d[0].maxsize - offs); avail = size - offs; filled = spa_ringbuffer_get_write_index(&stream->ring, &index); if (filled >= (int32_t)stream->buffer_size) { pw_log_warn("playback overrun %d >= %zd", filled, stream->buffer_size); } else { spa_ringbuffer_write_data(&stream->ring, stream->buffer_data, stream->buffer_size, index % stream->buffer_size, SPA_PTROFF(d[0].data, offs, void), avail); index += avail; spa_ringbuffer_write_update(&stream->ring, index); } pw_stream_queue_buffer(stream->stream, buf); clock_gettime(CLOCK_TAI, &now); flush_write(stream, SPA_TIMESPEC_TO_NSEC(&now)); } static void setup_pdu(struct stream *stream) { struct avb_frame_header *h; struct avb_packet_iec61883 *p; ssize_t payload_size, hdr_size, pdu_size; spa_memzero(stream->pdu, sizeof(stream->pdu)); h = (struct avb_frame_header*)stream->pdu; p = SPA_PTROFF(h, sizeof(*h), void); hdr_size = sizeof(*h) + sizeof(*p); payload_size = stream->stride * stream->frames_per_pdu; pdu_size = hdr_size + payload_size; h->type = htons(0x8100); h->prio_cfi_id = htons((stream->prio << 13) | stream->vlan_id); h->etype = htons(0x22f0); if (stream->direction == SPA_DIRECTION_OUTPUT) { p->subtype = AVB_SUBTYPE_61883_IIDC; p->sv = 1; p->stream_id = htobe64(stream->id); p->data_len = htons(payload_size+8); p->tag = 0x1; p->channel = 0x1f; p->tcode = 0xa; p->sid = 0x3f; p->dbs = stream->info.info.raw.channels; p->qi2 = 0x2; p->format_id = 0x10; p->fdf = 0x2; p->syt = htons(0x0008); } stream->hdr_size = hdr_size; stream->payload_size = payload_size; stream->pdu_size = pdu_size; } static int setup_msg(struct stream *stream) { stream->iov[0].iov_base = stream->pdu; stream->iov[0].iov_len = stream->hdr_size; stream->iov[1].iov_base = SPA_PTROFF(stream->pdu, stream->hdr_size, void); stream->iov[1].iov_len = stream->payload_size; stream->iov[2].iov_base = SPA_PTROFF(stream->pdu, stream->hdr_size, void); stream->iov[2].iov_len = 0; stream->msg.msg_name = &stream->sock_addr; stream->msg.msg_namelen = sizeof(stream->sock_addr); stream->msg.msg_iov = stream->iov; stream->msg.msg_iovlen = 3; stream->msg.msg_control = stream->control; stream->msg.msg_controllen = sizeof(stream->control); stream->cmsg = CMSG_FIRSTHDR(&stream->msg); stream->cmsg->cmsg_level = SOL_SOCKET; stream->cmsg->cmsg_type = SCM_TXTIME; stream->cmsg->cmsg_len = CMSG_LEN(sizeof(__u64)); return 0; } static const struct pw_stream_events sink_stream_events = { PW_VERSION_STREAM_EVENTS, .destroy = on_stream_destroy, .process = on_sink_stream_process }; struct stream *server_create_stream(struct server *server, enum spa_direction direction, uint16_t index) { struct stream *stream; const struct descriptor *desc; uint32_t n_params; const struct spa_pod *params[1]; uint8_t buffer[1024]; struct spa_pod_builder b; int res; desc = server_find_descriptor(server, direction == SPA_DIRECTION_INPUT ? AVB_AEM_DESC_STREAM_INPUT : AVB_AEM_DESC_STREAM_OUTPUT, index); if (desc == NULL) return NULL; stream = calloc(1, sizeof(*stream)); if (stream == NULL) return NULL; stream->server = server; stream->direction = direction; stream->index = index; stream->desc = desc; spa_list_append(&server->streams, &stream->link); stream->prio = AVB_MSRP_PRIORITY_DEFAULT; stream->vlan_id = AVB_DEFAULT_VLAN; stream->id = (uint64_t)server->mac_addr[0] << 56 | (uint64_t)server->mac_addr[1] << 48 | (uint64_t)server->mac_addr[2] << 40 | (uint64_t)server->mac_addr[3] << 32 | (uint64_t)server->mac_addr[4] << 24 | (uint64_t)server->mac_addr[5] << 16 | htons(index); stream->vlan_attr = avb_mvrp_attribute_new(server->mvrp, AVB_MVRP_ATTRIBUTE_TYPE_VID); stream->vlan_attr->attr.vid.vlan = htons(stream->vlan_id); stream->buffer_data = calloc(1, BUFFER_SIZE); stream->buffer_size = BUFFER_SIZE; spa_ringbuffer_init(&stream->ring); if (direction == SPA_DIRECTION_INPUT) { stream->stream = pw_stream_new(server->impl->core, "source", pw_properties_new( PW_KEY_MEDIA_CLASS, "Audio/Source", PW_KEY_NODE_NAME, "avb.source", PW_KEY_NODE_DESCRIPTION, "AVB Source", PW_KEY_NODE_WANT_DRIVER, "true", NULL)); } else { stream->stream = pw_stream_new(server->impl->core, "sink", pw_properties_new( PW_KEY_MEDIA_CLASS, "Audio/Sink", PW_KEY_NODE_NAME, "avb.sink", PW_KEY_NODE_DESCRIPTION, "AVB Sink", PW_KEY_NODE_WANT_DRIVER, "true", NULL)); } if (stream->stream == NULL) goto error_free; pw_stream_add_listener(stream->stream, &stream->stream_listener, direction == SPA_DIRECTION_INPUT ? &source_stream_events : &sink_stream_events, stream); stream->info.info.raw.format = SPA_AUDIO_FORMAT_S24_32_BE; stream->info.info.raw.flags = SPA_AUDIO_FLAG_UNPOSITIONED; stream->info.info.raw.rate = 48000; stream->info.info.raw.channels = 8; stream->stride = stream->info.info.raw.channels * 4; n_params = 0; spa_pod_builder_init(&b, buffer, sizeof(buffer)); params[n_params++] = spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat, &stream->info.info.raw); if ((res = pw_stream_connect(stream->stream, pw_direction_reverse(direction), PW_ID_ANY, PW_STREAM_FLAG_MAP_BUFFERS | PW_STREAM_FLAG_INACTIVE | PW_STREAM_FLAG_RT_PROCESS, params, n_params)) < 0) goto error_free_stream; stream->frames_per_pdu = 6; stream->pdu_period = SPA_NSEC_PER_SEC * stream->frames_per_pdu / stream->info.info.raw.rate; setup_pdu(stream); setup_msg(stream); stream->listener_attr = avb_msrp_attribute_new(server->msrp, AVB_MSRP_ATTRIBUTE_TYPE_LISTENER); stream->talker_attr = avb_msrp_attribute_new(server->msrp, AVB_MSRP_ATTRIBUTE_TYPE_TALKER_ADVERTISE); stream->talker_attr->attr.talker.vlan_id = htons(stream->vlan_id); stream->talker_attr->attr.talker.tspec_max_frame_size = htons(32 + stream->frames_per_pdu * stream->stride); stream->talker_attr->attr.talker.tspec_max_interval_frames = htons(AVB_MSRP_TSPEC_MAX_INTERVAL_FRAMES_DEFAULT); stream->talker_attr->attr.talker.priority = stream->prio; stream->talker_attr->attr.talker.rank = AVB_MSRP_RANK_DEFAULT; stream->talker_attr->attr.talker.accumulated_latency = htonl(95); return stream; error_free_stream: pw_stream_destroy(stream->stream); errno = -res; error_free: free(stream); return NULL; } void stream_destroy(struct stream *stream) { avb_mrp_attribute_destroy(stream->listener_attr->mrp); spa_list_remove(&stream->link); free(stream); } static int setup_socket(struct stream *stream) { struct server *server = stream->server; int fd, res; char buf[128]; struct ifreq req; fd = socket(AF_PACKET, SOCK_RAW | SOCK_NONBLOCK, htons(ETH_P_ALL)); if (fd < 0) { pw_log_error("socket() failed: %m"); return -errno; } spa_zero(req); snprintf(req.ifr_name, sizeof(req.ifr_name), "%s", server->ifname); res = ioctl(fd, SIOCGIFINDEX, &req); if (res < 0) { pw_log_error("SIOCGIFINDEX %s failed: %m", server->ifname); res = -errno; goto error_close; } spa_zero(stream->sock_addr); stream->sock_addr.sll_family = AF_PACKET; stream->sock_addr.sll_protocol = htons(ETH_P_TSN); stream->sock_addr.sll_ifindex = req.ifr_ifindex; if (stream->direction == SPA_DIRECTION_OUTPUT) { struct sock_txtime txtime_cfg; res = setsockopt(fd, SOL_SOCKET, SO_PRIORITY, &stream->prio, sizeof(stream->prio)); if (res < 0) { pw_log_error("setsockopt(SO_PRIORITY %d) failed: %m", stream->prio); res = -errno; goto error_close; } txtime_cfg.clockid = CLOCK_TAI; txtime_cfg.flags = 0; res = setsockopt(fd, SOL_SOCKET, SO_TXTIME, &txtime_cfg, sizeof(txtime_cfg)); if (res < 0) { pw_log_error("setsockopt(SO_TXTIME) failed: %m"); res = -errno; goto error_close; } } else { struct packet_mreq mreq; res = bind(fd, (struct sockaddr *) &stream->sock_addr, sizeof(stream->sock_addr)); if (res < 0) { pw_log_error("bind() failed: %m"); res = -errno; goto error_close; } spa_zero(mreq); mreq.mr_ifindex = req.ifr_ifindex; mreq.mr_type = PACKET_MR_MULTICAST; mreq.mr_alen = ETH_ALEN; memcpy(&mreq.mr_address, stream->addr, ETH_ALEN); res = setsockopt(fd, SOL_PACKET, PACKET_ADD_MEMBERSHIP, &mreq, sizeof(struct packet_mreq)); pw_log_info("join %s", avb_utils_format_addr(buf, 128, stream->addr)); if (res < 0) { pw_log_error("setsockopt(ADD_MEMBERSHIP) failed: %m"); res = -errno; goto error_close; } } return fd; error_close: close(fd); return res; } static void handle_iec61883_packet(struct stream *stream, struct avb_packet_iec61883 *p, int len) { uint32_t index, n_bytes; int32_t filled; filled = spa_ringbuffer_get_write_index(&stream->ring, &index); n_bytes = ntohs(p->data_len) - 8; if (filled + n_bytes > stream->buffer_size) { pw_log_debug("capture overrun"); } else { spa_ringbuffer_write_data(&stream->ring, stream->buffer_data, stream->buffer_size, index % stream->buffer_size, p->payload, n_bytes); index += n_bytes; spa_ringbuffer_write_update(&stream->ring, index); } } static void on_socket_data(void *data, int fd, uint32_t mask) { struct stream *stream = data; if (mask & SPA_IO_IN) { int len; uint8_t buffer[2048]; len = recv(fd, buffer, sizeof(buffer), 0); if (len < 0) { pw_log_warn("got recv error: %m"); } else if (len < (int)sizeof(struct avb_packet_header)) { pw_log_warn("short packet received (%d < %d)", len, (int)sizeof(struct avb_packet_header)); } else { struct avb_ethernet_header *h = (void*)buffer; struct avb_packet_iec61883 *p = SPA_PTROFF(h, sizeof(*h), void); if (memcmp(h->dest, stream->addr, 6) != 0 || p->subtype != AVB_SUBTYPE_61883_IIDC) return; handle_iec61883_packet(stream, p, len - sizeof(*h)); } } } int stream_activate(struct stream *stream, uint64_t now) { struct server *server = stream->server; struct avb_frame_header *h = (void*)stream->pdu; int fd, res; if (stream->source == NULL) { if ((fd = setup_socket(stream)) < 0) return fd; stream->source = pw_loop_add_io(server->impl->loop, fd, SPA_IO_IN, true, on_socket_data, stream); if (stream->source == NULL) { res = -errno; pw_log_error("stream %p: can't create source: %m", stream); close(fd); return res; } } avb_mrp_attribute_begin(stream->vlan_attr->mrp, now); avb_mrp_attribute_join(stream->vlan_attr->mrp, now, true); if (stream->direction == SPA_DIRECTION_INPUT) { stream->listener_attr->attr.listener.stream_id = htobe64(stream->peer_id); stream->listener_attr->param = AVB_MSRP_LISTENER_PARAM_READY; avb_mrp_attribute_begin(stream->listener_attr->mrp, now); avb_mrp_attribute_join(stream->listener_attr->mrp, now, true); stream->talker_attr->attr.talker.stream_id = htobe64(stream->peer_id); avb_mrp_attribute_begin(stream->talker_attr->mrp, now); } else { if ((res = avb_maap_get_address(server->maap, stream->addr, stream->index)) < 0) return res; stream->listener_attr->attr.listener.stream_id = htobe64(stream->id); stream->listener_attr->param = AVB_MSRP_LISTENER_PARAM_IGNORE; avb_mrp_attribute_begin(stream->listener_attr->mrp, now); stream->talker_attr->attr.talker.stream_id = htobe64(stream->id); memcpy(stream->talker_attr->attr.talker.dest_addr, stream->addr, 6); stream->sock_addr.sll_halen = ETH_ALEN; memcpy(&stream->sock_addr.sll_addr, stream->addr, ETH_ALEN); memcpy(h->dest, stream->addr, 6); memcpy(h->src, server->mac_addr, 6); avb_mrp_attribute_begin(stream->talker_attr->mrp, now); avb_mrp_attribute_join(stream->talker_attr->mrp, now, true); } pw_stream_set_active(stream->stream, true); return 0; } int stream_deactivate(struct stream *stream, uint64_t now) { pw_stream_set_active(stream->stream, false); if (stream->source != NULL) { pw_loop_destroy_source(stream->server->impl->loop, stream->source); stream->source = NULL; } avb_mrp_attribute_leave(stream->vlan_attr->mrp, now); if (stream->direction == SPA_DIRECTION_INPUT) { avb_mrp_attribute_leave(stream->listener_attr->mrp, now); } else { avb_mrp_attribute_leave(stream->talker_attr->mrp, now); } return 0; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/modules/module-avb/stream.h000066400000000000000000000032631511204443500264400ustar00rootroot00000000000000/* AVB support */ /* SPDX-FileCopyrightText: Copyright © 2022 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef AVB_STREAM_H #define AVB_STREAM_H #include #include #include #include #include #include #include #define BUFFER_SIZE (1u<<16) #define BUFFER_MASK (BUFFER_SIZE-1) struct stream { struct spa_list link; struct server *server; uint16_t direction; uint16_t index; const struct descriptor *desc; uint64_t id; uint64_t peer_id; struct pw_stream *stream; struct spa_hook stream_listener; uint8_t addr[6]; struct spa_source *source; int prio; int vlan_id; int mtt; int t_uncertainty; uint32_t frames_per_pdu; int ptime_tolerance; uint8_t pdu[2048]; size_t hdr_size; size_t payload_size; size_t pdu_size; int64_t pdu_period; uint8_t pdu_seq; uint8_t prev_seq; uint8_t dbc; struct iovec iov[3]; struct sockaddr_ll sock_addr; struct msghdr msg; char control[CMSG_SPACE(sizeof(uint64_t))]; struct cmsghdr *cmsg; struct spa_ringbuffer ring; void *buffer_data; size_t buffer_size; uint64_t format; uint32_t stride; struct spa_audio_info info; struct avb_msrp_attribute *talker_attr; struct avb_msrp_attribute *listener_attr; struct avb_mvrp_attribute *vlan_attr; }; #include "msrp.h" #include "mvrp.h" #include "maap.h" struct stream *server_create_stream(struct server *server, enum spa_direction direction, uint16_t index); void stream_destroy(struct stream *stream); int stream_activate(struct stream *stream, uint64_t now); int stream_deactivate(struct stream *stream, uint64_t now); #endif /* AVB_STREAM_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/modules/module-avb/utils.h000066400000000000000000000032601511204443500263020ustar00rootroot00000000000000/* AVB support */ /* SPDX-FileCopyrightText: Copyright © 2022 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef AVB_UTILS_H #define AVB_UTILS_H #include #include "internal.h" static inline char *avb_utils_format_id(char *str, size_t size, const uint64_t id) { snprintf(str, size, "%02x:%02x:%02x:%02x:%02x:%02x:%04x", (uint8_t)(id >> 56), (uint8_t)(id >> 48), (uint8_t)(id >> 40), (uint8_t)(id >> 32), (uint8_t)(id >> 24), (uint8_t)(id >> 16), (uint16_t)(id)); return str; } static inline int avb_utils_parse_id(const char *str, int len, uint64_t *id) { char s[64]; uint8_t v[6]; uint16_t unique_id; if (spa_json_parse_stringn(str, len, s, sizeof(s)) <= 0) return -EINVAL; if (sscanf(s, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx:%hx", &v[0], &v[1], &v[2], &v[3], &v[4], &v[5], &unique_id) == 7) { *id = (uint64_t) v[0] << 56 | (uint64_t) v[1] << 48 | (uint64_t) v[2] << 40 | (uint64_t) v[3] << 32 | (uint64_t) v[4] << 24 | (uint64_t) v[5] << 16 | unique_id; } else if (!spa_atou64(str, id, 0)) return -EINVAL; return 0; } static inline char *avb_utils_format_addr(char *str, size_t size, const uint8_t addr[6]) { snprintf(str, size, "%02x:%02x:%02x:%02x:%02x:%02x", addr[0], addr[1], addr[2], addr[3], addr[4], addr[5]); return str; } static inline int avb_utils_parse_addr(const char *str, int len, uint8_t addr[6]) { char s[64]; uint8_t v[6]; if (spa_json_parse_stringn(str, len, s, sizeof(s)) <= 0) return -EINVAL; if (sscanf(s, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx", &v[0], &v[1], &v[2], &v[3], &v[4], &v[5]) != 6) return -EINVAL; memcpy(addr, v, 6); return 0; } #endif /* AVB_UTILS_H */ pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/modules/module-client-device.c000066400000000000000000000173311511204443500271040ustar00rootroot00000000000000/* PipeWire */ /* SPDX-FileCopyrightText: Copyright © 2019 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include "config.h" #include #include #include #include #include #include #include "module-client-device/client-device.h" /** \page page_module_client_device Client Device * * Allow clients to export devices to the PipeWire daemon. * * This module creates an export type for the \ref SPA_TYPE_INTERFACE_Device * interface. * * With \ref pw_core_export(), objects of this type can be exported to the * PipeWire server. All actions performed on the device locally will be visible * to connecteced clients. * * In some cases, it is possible to use this factory directly. * With \ref pw_core_create_object() on the `client-device` * factory will result in a \ref SPA_TYPE_INTERFACE_Device proxy that can be * used to control the server side created \ref pw_impl_device. * * Schematically, the client side \ref spa_device is wrapped in the ClientDevice * proxy and unwrapped by the server side resource so that all actions on the client * side device are reflected on the server side device and server side actions are * reflected in the client. * *\code{.unparsed} * * client side proxy server side resource * .------------------------------. .----------------------------------. * | SPA_TYPE_INTERFACE_Device | | PW_TYPE_INTERFACE_Device | * | | IPC |.--------------------------------.| * | | -----> || SPA_TYPE_INTERFACE_Device || * | | |'--------------------------------'| * '------------------------------' '----------------------------------' *\endcode * * ## Module Name * * `libpipewire-module-client-device` * * ## Module Options * * This module has no options. * * ## Properties for the create_object call * * All properties are passed directly to the \ref pw_context_create_device() call. * * ## Example configuration * * The module is usually added to the config file of the main PipeWire daemon and the * clients. * *\code{.unparsed} * context.modules = [ * { name = libpipewire-module-client-device } * ] *\endcode * * ## See also * * - `module-spa-device-factory`: make nodes from a factory */ #define NAME "client-device" PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); #define PW_LOG_TOPIC_DEFAULT mod_topic static const struct spa_dict_item module_props[] = { { PW_KEY_MODULE_AUTHOR, "Wim Taymans " }, { PW_KEY_MODULE_DESCRIPTION, "Allow clients to create and control remote devices" }, { PW_KEY_MODULE_VERSION, PACKAGE_VERSION }, }; struct pw_proxy *pw_core_spa_device_export(struct pw_core *core, const char *type, const struct spa_dict *props, void *object, size_t user_data_size); struct pw_protocol *pw_protocol_native_ext_client_device_init(struct pw_context *context); struct factory_data { struct pw_impl_factory *factory; struct spa_hook factory_listener; struct pw_impl_module *module; struct spa_hook module_listener; struct pw_export_type export_spadevice; }; static void *create_object(void *_data, struct pw_resource *resource, const char *type, uint32_t version, struct pw_properties *properties, uint32_t new_id) { struct factory_data *data = _data; struct pw_impl_factory *factory = data->factory; void *result; struct pw_resource *device_resource; struct pw_impl_client *client; int res; if (resource == NULL) { res = -EINVAL; goto error_exit; } client = pw_resource_get_client(resource); device_resource = pw_resource_new(client, new_id, PW_PERM_ALL, type, version, 0); if (device_resource == NULL) { res = -errno; goto error_resource; } if (properties == NULL) properties = pw_properties_new(NULL, NULL); if (properties == NULL) { res = -errno; goto error_properties; } pw_properties_setf(properties, PW_KEY_FACTORY_ID, "%d", pw_global_get_id(pw_impl_factory_get_global(factory))); pw_properties_setf(properties, PW_KEY_CLIENT_ID, "%d", pw_global_get_id(pw_impl_client_get_global(client))); result = pw_client_device_new(device_resource, properties); if (result == NULL) { res = -errno; goto error_device; } return result; error_resource: pw_log_error("can't create resource: %s", spa_strerror(res)); pw_resource_errorf_id(resource, new_id, res, "can't create resource: %s", spa_strerror(res)); goto error_exit; error_properties: pw_log_error("can't create properties: %s", spa_strerror(res)); pw_resource_errorf_id(resource, new_id, res, "can't create properties: %s", spa_strerror(res)); goto error_exit_free; error_device: pw_log_error("can't create device: %s", spa_strerror(res)); pw_resource_errorf_id(resource, new_id, res, "can't create device: %s", spa_strerror(res)); goto error_exit_free; error_exit_free: pw_resource_remove(device_resource); error_exit: errno = -res; return NULL; } static const struct pw_impl_factory_implementation impl_factory = { PW_VERSION_IMPL_FACTORY_IMPLEMENTATION, .create_object = create_object, }; static void factory_destroy(void *data) { struct factory_data *d = data; spa_hook_remove(&d->factory_listener); d->factory = NULL; if (d->module) pw_impl_module_destroy(d->module); } static const struct pw_impl_factory_events factory_events = { PW_VERSION_IMPL_FACTORY_EVENTS, .destroy = factory_destroy, }; static void module_destroy(void *data) { struct factory_data *d = data; spa_hook_remove(&d->module_listener); spa_list_remove(&d->export_spadevice.link); d->module = NULL; if (d->factory) pw_impl_factory_destroy(d->factory); } static void module_registered(void *data) { struct factory_data *d = data; struct pw_impl_module *module = d->module; struct pw_impl_factory *factory = d->factory; struct spa_dict_item items[1]; char id[16]; int res; snprintf(id, sizeof(id), "%d", pw_global_get_id(pw_impl_module_get_global(module))); items[0] = SPA_DICT_ITEM_INIT(PW_KEY_MODULE_ID, id); pw_impl_factory_update_properties(factory, &SPA_DICT_INIT(items, 1)); if ((res = pw_impl_factory_register(factory, NULL)) < 0) { pw_log_error("%p: can't register factory: %s", factory, spa_strerror(res)); } } static const struct pw_impl_module_events module_events = { PW_VERSION_IMPL_MODULE_EVENTS, .destroy = module_destroy, .registered = module_registered, }; SPA_EXPORT int pipewire__module_init(struct pw_impl_module *module, const char *args) { struct pw_context *context = pw_impl_module_get_context(module); struct pw_impl_factory *factory; struct factory_data *data; int res; PW_LOG_TOPIC_INIT(mod_topic); factory = pw_context_create_factory(context, "client-device", SPA_TYPE_INTERFACE_Device, SPA_VERSION_DEVICE, pw_properties_new( PW_KEY_FACTORY_USAGE, CLIENT_DEVICE_USAGE, NULL), sizeof(*data)); if (factory == NULL) return -errno; data = pw_impl_factory_get_user_data(factory); data->factory = factory; data->module = module; pw_log_debug("module %p: new", module); pw_impl_factory_set_implementation(factory, &impl_factory, data); data->export_spadevice.type = SPA_TYPE_INTERFACE_Device; data->export_spadevice.func = pw_core_spa_device_export; if ((res = pw_context_register_export_type(context, &data->export_spadevice)) < 0) goto error; pw_protocol_native_ext_client_device_init(context); pw_impl_factory_add_listener(factory, &data->factory_listener, &factory_events, data); pw_impl_module_add_listener(module, &data->module_listener, &module_events, data); pw_impl_module_update_properties(module, &SPA_DICT_INIT_ARRAY(module_props)); return 0; error: pw_impl_factory_destroy(data->factory); return res; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/modules/module-client-device/000077500000000000000000000000001511204443500267335ustar00rootroot00000000000000client-device.h000066400000000000000000000007541511204443500315460ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/modules/module-client-device/* PipeWire */ /* SPDX-FileCopyrightText: Copyright © 2019 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef PIPEWIRE_CLIENT_DEVICE_H #define PIPEWIRE_CLIENT_DEVICE_H #include #ifdef __cplusplus extern "C" { #endif #define CLIENT_DEVICE_USAGE "["PW_KEY_DEVICE_NAME"=]" struct pw_impl_device * pw_client_device_new(struct pw_resource *resource, struct pw_properties *properties); #ifdef __cplusplus } #endif #endif /* PIPEWIRE_CLIENT_DEVICE_H */ protocol-native.c000066400000000000000000000343611511204443500321540ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/modules/module-client-device/* PipeWire */ /* SPDX-FileCopyrightText: Copyright © 2019 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #include #include #define MAX_DICT 1024 #define MAX_PARAM_INFO 128 static inline void push_item(struct spa_pod_builder *b, const struct spa_dict_item *item) { const char *str; spa_pod_builder_string(b, item->key); str = item->value; if (spa_strstartswith(str, "pointer:")) str = ""; spa_pod_builder_string(b, str); } static inline int parse_item(struct spa_pod_parser *prs, struct spa_dict_item *item) { int res; if ((res = spa_pod_parser_get(prs, SPA_POD_String(&item->key), SPA_POD_String(&item->value), NULL)) < 0) return res; if (item->key == NULL || item->value == NULL) return -EINVAL; if (spa_strstartswith(item->value, "pointer:")) item->value = ""; return 0; } #define parse_dict(prs,d) \ do { \ uint32_t i; \ if (spa_pod_parser_get(prs, \ SPA_POD_Int(&(d)->n_items), NULL) < 0) \ return -EINVAL; \ if ((d)->n_items > 0) { \ if ((d)->n_items > MAX_DICT) \ return -ENOSPC; \ (d)->items = alloca((d)->n_items * sizeof(struct spa_dict_item)); \ for (i = 0; i < (d)->n_items; i++) { \ if (parse_item(prs, (struct spa_dict_item *) &(d)->items[i]) < 0) \ return -EINVAL; \ } \ } \ } while(0) #define parse_param_info(prs,n_params,params) \ do { \ uint32_t i; \ if (spa_pod_parser_get(prs, \ SPA_POD_Int(&(n_params)), NULL) < 0) \ return -EINVAL; \ if (n_params > 0) { \ if (n_params > MAX_PARAM_INFO) \ return -ENOSPC; \ params = alloca(n_params * sizeof(struct spa_param_info)); \ for (i = 0; i < n_params; i++) { \ if (spa_pod_parser_get(prs, \ SPA_POD_Id(&(params[i]).id), \ SPA_POD_Int(&(params[i]).flags), NULL) < 0) \ return -EINVAL; \ } \ } \ } while(0) static int device_marshal_add_listener(void *object, struct spa_hook *listener, const struct spa_device_events *events, void *data) { struct pw_resource *resource = object; pw_resource_add_object_listener(resource, listener, events, data); return 0; } static int device_demarshal_add_listener(void *object, const struct pw_protocol_native_message *msg) { return -ENOTSUP; } static int device_marshal_sync(void *object, int seq) { struct pw_protocol_native_message *msg; struct pw_resource *resource = object; struct spa_pod_builder *b; b = pw_protocol_native_begin_resource(resource, SPA_DEVICE_METHOD_SYNC, &msg); spa_pod_builder_add_struct(b, SPA_POD_Int(SPA_RESULT_RETURN_ASYNC(msg->seq))); return pw_protocol_native_end_resource(resource, b); } static int device_demarshal_sync(void *object, const struct pw_protocol_native_message *msg) { struct pw_proxy *proxy = object; struct spa_pod_parser prs; int seq; spa_pod_parser_init(&prs, msg->data, msg->size); if (spa_pod_parser_get_struct(&prs, SPA_POD_Int(&seq)) < 0) return -EINVAL; pw_proxy_notify(proxy, struct spa_device_methods, sync, 0, seq); return 0; } static int device_marshal_enum_params(void *object, int seq, uint32_t id, uint32_t index, uint32_t max, const struct spa_pod *filter) { struct pw_protocol_native_message *msg; struct pw_resource *resource = object; struct spa_pod_builder *b; b = pw_protocol_native_begin_resource(resource, SPA_DEVICE_METHOD_ENUM_PARAMS, &msg); spa_pod_builder_add_struct(b, SPA_POD_Int(SPA_RESULT_RETURN_ASYNC(msg->seq)), SPA_POD_Id(id), SPA_POD_Int(index), SPA_POD_Int(max), SPA_POD_Pod(filter)); return pw_protocol_native_end_resource(resource, b); } static int device_demarshal_enum_params(void *object, const struct pw_protocol_native_message *msg) { struct pw_proxy *proxy = object; struct spa_pod_parser prs; uint32_t id, index, max; int seq; struct spa_pod *filter; spa_pod_parser_init(&prs, msg->data, msg->size); if (spa_pod_parser_get_struct(&prs, SPA_POD_Int(&seq), SPA_POD_Id(&id), SPA_POD_Int(&index), SPA_POD_Int(&max), SPA_POD_Pod(&filter)) < 0) return -EINVAL; pw_proxy_notify(proxy, struct spa_device_methods, enum_params, 0, seq, id, index, max, filter); return 0; } static int device_marshal_set_param(void *object, uint32_t id, uint32_t flags, const struct spa_pod *param) { struct pw_resource *resource = object; struct spa_pod_builder *b; b = pw_protocol_native_begin_resource(resource, SPA_DEVICE_METHOD_SET_PARAM, NULL); spa_pod_builder_add_struct(b, SPA_POD_Id(id), SPA_POD_Int(flags), SPA_POD_Pod(param)); return pw_protocol_native_end_resource(resource, b); } static int device_demarshal_set_param(void *object, const struct pw_protocol_native_message *msg) { struct pw_proxy *proxy = object; struct spa_pod_parser prs; uint32_t id, flags; struct spa_pod *param; spa_pod_parser_init(&prs, msg->data, msg->size); if (spa_pod_parser_get_struct(&prs, SPA_POD_Id(&id), SPA_POD_Int(&flags), SPA_POD_Pod(¶m)) < 0) return -EINVAL; pw_proxy_notify(proxy, struct spa_device_methods, set_param, 0, id, flags, param); return 0; } static void device_marshal_info(void *data, const struct spa_device_info *info) { struct pw_proxy *proxy = data; struct spa_pod_builder *b; struct spa_pod_frame f[2]; uint32_t i, n_items; b = pw_protocol_native_begin_proxy(proxy, SPA_DEVICE_EVENT_INFO, NULL); spa_pod_builder_push_struct(b, &f[0]); if (info) { uint64_t change_mask = info->change_mask; change_mask &= SPA_DEVICE_CHANGE_MASK_FLAGS | SPA_DEVICE_CHANGE_MASK_PROPS | SPA_DEVICE_CHANGE_MASK_PARAMS; n_items = info->props ? info->props->n_items : 0; spa_pod_builder_push_struct(b, &f[1]); spa_pod_builder_add(b, SPA_POD_Long(change_mask), SPA_POD_Long(info->flags), SPA_POD_Int(n_items), NULL); for (i = 0; i < n_items; i++) push_item(b, &info->props->items[i]); spa_pod_builder_add(b, SPA_POD_Int(info->n_params), NULL); for (i = 0; i < info->n_params; i++) { spa_pod_builder_add(b, SPA_POD_Id(info->params[i].id), SPA_POD_Int(info->params[i].flags), NULL); } spa_pod_builder_pop(b, &f[1]); } else { spa_pod_builder_add(b, SPA_POD_Pod(NULL), NULL); } spa_pod_builder_pop(b, &f[0]); pw_protocol_native_end_proxy(proxy, b); } static int device_demarshal_info(void *data, const struct pw_protocol_native_message *msg) { struct pw_resource *resource = data; struct spa_pod_parser prs; struct spa_pod *ipod; struct spa_device_info info = SPA_DEVICE_INFO_INIT(), *infop; struct spa_dict props = SPA_DICT_INIT(NULL, 0); spa_pod_parser_init(&prs, msg->data, msg->size); if (spa_pod_parser_get_struct(&prs, SPA_POD_PodStruct(&ipod)) < 0) return -EINVAL; if (ipod) { struct spa_pod_parser p2; struct spa_pod_frame f2; infop = &info; spa_pod_parser_pod(&p2, ipod); if (spa_pod_parser_push_struct(&p2, &f2) < 0 || spa_pod_parser_get(&p2, SPA_POD_Long(&info.change_mask), SPA_POD_Long(&info.flags), NULL) < 0) return -EINVAL; info.change_mask &= SPA_DEVICE_CHANGE_MASK_FLAGS | SPA_DEVICE_CHANGE_MASK_PROPS | SPA_DEVICE_CHANGE_MASK_PARAMS; parse_dict(&p2, &props); if (props.n_items > 0) info.props = &props; parse_param_info(&p2, info.n_params, info.params); } else { infop = NULL; } pw_resource_notify(resource, struct spa_device_events, info, 0, infop); return 0; } static void device_marshal_result(void *data, int seq, int res, uint32_t type, const void *result) { struct pw_proxy *proxy = data; struct spa_pod_builder *b; struct spa_pod_frame f[2]; b = pw_protocol_native_begin_proxy(proxy, SPA_DEVICE_EVENT_RESULT, NULL); spa_pod_builder_push_struct(b, &f[0]); spa_pod_builder_add(b, SPA_POD_Int(seq), SPA_POD_Int(res), SPA_POD_Id(type), NULL); switch (type) { case SPA_RESULT_TYPE_DEVICE_PARAMS: { const struct spa_result_device_params *r = result; spa_pod_builder_add(b, SPA_POD_Id(r->id), SPA_POD_Int(r->index), SPA_POD_Int(r->next), SPA_POD_Pod(r->param), NULL); break; } default: break; } spa_pod_builder_pop(b, &f[0]); pw_protocol_native_end_proxy(proxy, b); } static int device_demarshal_result(void *data, const struct pw_protocol_native_message *msg) { struct pw_resource *resource = data; struct spa_pod_parser prs; struct spa_pod_frame f[1]; int seq, res; uint32_t type; const void *result; struct spa_result_device_params params; spa_pod_parser_init(&prs, msg->data, msg->size); if (spa_pod_parser_push_struct(&prs, &f[0]) < 0 || spa_pod_parser_get(&prs, SPA_POD_Int(&seq), SPA_POD_Int(&res), SPA_POD_Id(&type), NULL) < 0) return -EINVAL; switch (type) { case SPA_RESULT_TYPE_DEVICE_PARAMS: if (spa_pod_parser_get(&prs, SPA_POD_Id(¶ms.id), SPA_POD_Int(¶ms.index), SPA_POD_Int(¶ms.next), SPA_POD_PodObject(¶ms.param), NULL) < 0) return -EINVAL; result = ¶ms; break; default: result = NULL; break; } pw_resource_notify(resource, struct spa_device_events, result, 0, seq, res, type, result); return 0; } static void device_marshal_event(void *data, const struct spa_event *event) { struct pw_proxy *proxy = data; struct spa_pod_builder *b; b = pw_protocol_native_begin_proxy(proxy, SPA_DEVICE_EVENT_EVENT, NULL); spa_pod_builder_add_struct(b, SPA_POD_Pod(event)); pw_protocol_native_end_proxy(proxy, b); } static int device_demarshal_event(void *data, const struct pw_protocol_native_message *msg) { struct pw_resource *resource = data; struct spa_pod_parser prs; struct spa_event *event; spa_pod_parser_init(&prs, msg->data, msg->size); if (spa_pod_parser_get_struct(&prs, SPA_POD_PodObject(&event)) < 0) return -EINVAL; pw_resource_notify(resource, struct spa_device_events, event, 0, event); return 0; } static void device_marshal_object_info(void *data, uint32_t id, const struct spa_device_object_info *info) { struct pw_proxy *proxy = data; struct spa_pod_builder *b; struct spa_pod_frame f[2]; uint32_t i, n_items; b = pw_protocol_native_begin_proxy(proxy, SPA_DEVICE_EVENT_OBJECT_INFO, NULL); spa_pod_builder_push_struct(b, &f[0]); spa_pod_builder_add(b, SPA_POD_Int(id), NULL); if (info) { uint64_t change_mask = info->change_mask; change_mask &= SPA_DEVICE_OBJECT_CHANGE_MASK_FLAGS | SPA_DEVICE_OBJECT_CHANGE_MASK_PROPS; n_items = info->props ? info->props->n_items : 0; spa_pod_builder_push_struct(b, &f[1]); spa_pod_builder_add(b, SPA_POD_String(info->type), SPA_POD_Long(change_mask), SPA_POD_Long(info->flags), SPA_POD_Int(n_items), NULL); for (i = 0; i < n_items; i++) push_item(b, &info->props->items[i]); spa_pod_builder_pop(b, &f[1]); } else { spa_pod_builder_add(b, SPA_POD_Pod(NULL), NULL); } spa_pod_builder_pop(b, &f[0]); pw_protocol_native_end_proxy(proxy, b); } static int device_demarshal_object_info(void *data, const struct pw_protocol_native_message *msg) { struct pw_resource *resource = data; struct spa_pod_parser prs; struct spa_device_object_info info = SPA_DEVICE_OBJECT_INFO_INIT(), *infop; struct spa_pod *ipod; struct spa_dict props = SPA_DICT_INIT(NULL, 0); uint32_t id; spa_pod_parser_init(&prs, msg->data, msg->size); if (spa_pod_parser_get_struct(&prs, SPA_POD_Int(&id), SPA_POD_PodStruct(&ipod)) < 0) return -EINVAL; if (ipod) { struct spa_pod_parser p2; struct spa_pod_frame f2; infop = &info; spa_pod_parser_pod(&p2, ipod); if (spa_pod_parser_push_struct(&p2, &f2) < 0 || spa_pod_parser_get(&p2, SPA_POD_String(&info.type), SPA_POD_Long(&info.change_mask), SPA_POD_Long(&info.flags), NULL) < 0) return -EINVAL; info.change_mask &= SPA_DEVICE_OBJECT_CHANGE_MASK_FLAGS | SPA_DEVICE_CHANGE_MASK_PROPS; parse_dict(&p2, &props); if (props.n_items > 0) info.props = &props; } else { infop = NULL; } pw_resource_notify(resource, struct spa_device_events, object_info, 0, id, infop); return 0; } static const struct spa_device_methods pw_protocol_native_device_method_marshal = { SPA_VERSION_DEVICE_METHODS, .add_listener = &device_marshal_add_listener, .sync = &device_marshal_sync, .enum_params = &device_marshal_enum_params, .set_param = &device_marshal_set_param }; static const struct pw_protocol_native_demarshal pw_protocol_native_device_method_demarshal[SPA_DEVICE_METHOD_NUM] = { [SPA_DEVICE_METHOD_ADD_LISTENER] = { &device_demarshal_add_listener, 0 }, [SPA_DEVICE_METHOD_SYNC] = { &device_demarshal_sync, 0 }, [SPA_DEVICE_METHOD_ENUM_PARAMS] = { &device_demarshal_enum_params, 0 }, [SPA_DEVICE_METHOD_SET_PARAM] = { &device_demarshal_set_param, 0 }, }; static const struct spa_device_events pw_protocol_native_device_event_marshal = { SPA_VERSION_DEVICE_EVENTS, .info = &device_marshal_info, .result = &device_marshal_result, .event = &device_marshal_event, .object_info = &device_marshal_object_info, }; static const struct pw_protocol_native_demarshal pw_protocol_native_device_event_demarshal[SPA_DEVICE_EVENT_NUM] = { [SPA_DEVICE_EVENT_INFO] = { &device_demarshal_info, 0 }, [SPA_DEVICE_EVENT_RESULT] = { &device_demarshal_result, 0 }, [SPA_DEVICE_EVENT_EVENT] = { &device_demarshal_event, 0 }, [SPA_DEVICE_EVENT_OBJECT_INFO] = { &device_demarshal_object_info, 0 }, }; static const struct pw_protocol_marshal pw_protocol_native_client_device_marshal = { SPA_TYPE_INTERFACE_Device, SPA_VERSION_DEVICE, PW_PROTOCOL_MARSHAL_FLAG_IMPL, SPA_DEVICE_EVENT_NUM, SPA_DEVICE_METHOD_NUM, .client_marshal = &pw_protocol_native_device_event_marshal, .server_demarshal = pw_protocol_native_device_event_demarshal, .server_marshal = &pw_protocol_native_device_method_marshal, .client_demarshal = pw_protocol_native_device_method_demarshal, }; struct pw_protocol *pw_protocol_native_ext_client_device_init(struct pw_context *context) { struct pw_protocol *protocol; protocol = pw_context_find_protocol(context, PW_TYPE_INFO_PROTOCOL_Native); if (protocol == NULL) return NULL; pw_protocol_add_marshal(protocol, &pw_protocol_native_client_device_marshal); return protocol; } proxy-device.c000066400000000000000000000034261511204443500314430ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/modules/module-client-device/* PipeWire */ /* SPDX-FileCopyrightText: Copyright © 2019 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #include #include "pipewire/pipewire.h" struct device_data { struct spa_device *device; struct spa_hook device_listener; struct spa_hook device_methods; struct pw_proxy *proxy; struct spa_hook proxy_listener; }; static void proxy_device_destroy(void *_data) { struct device_data *data = _data; spa_hook_remove(&data->device_listener); spa_hook_remove(&data->device_methods); spa_hook_remove(&data->proxy_listener); } static const struct pw_proxy_events proxy_events = { PW_VERSION_PROXY_EVENTS, .destroy = proxy_device_destroy, }; struct pw_proxy *pw_core_spa_device_export(struct pw_core *core, const char *type, const struct spa_dict *props, void *object, size_t user_data_size) { struct spa_device *device = object; struct spa_interface *iface, *diface; struct pw_proxy *proxy; struct device_data *data; proxy = pw_core_create_object(core, "client-device", SPA_TYPE_INTERFACE_Device, SPA_VERSION_DEVICE, props, user_data_size + sizeof(struct device_data)); if (proxy == NULL) return NULL; data = pw_proxy_get_user_data(proxy); data = SPA_PTROFF(data, user_data_size, struct device_data); data->device = device; data->proxy = proxy; iface = (struct spa_interface*)proxy; diface = (struct spa_interface*)device; pw_proxy_add_listener(proxy, &data->proxy_listener, &proxy_events, data); pw_proxy_add_object_listener(proxy, &data->device_methods, diface->cb.funcs, diface->cb.data); spa_device_add_listener(device, &data->device_listener, iface->cb.funcs, iface->cb.data); return proxy; } resource-device.c000066400000000000000000000065431511204443500321140ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/modules/module-client-device/* PipeWire */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #include #include #include #include #include #include #include #include #include "client-device.h" struct impl { struct pw_context *context; struct pw_impl_device *device; struct spa_hook device_listener; struct pw_resource *resource; struct spa_hook resource_listener; struct spa_hook object_listener; unsigned int registered:1; }; static void device_info(void *data, const struct spa_device_info *info) { struct impl *impl = data; if (!impl->registered) { pw_impl_device_set_implementation(impl->device, (struct spa_device*)impl->resource); pw_impl_device_register(impl->device, NULL); impl->registered = true; } } static const struct spa_device_events object_events = { SPA_VERSION_DEVICE_EVENTS, .info = device_info, }; static void device_resource_destroy(void *data) { struct impl *impl = data; pw_log_debug("client-device %p: destroy", impl); impl->resource = NULL; spa_hook_remove(&impl->device_listener); spa_hook_remove(&impl->resource_listener); spa_hook_remove(&impl->object_listener); pw_impl_device_destroy(impl->device); } static const struct pw_resource_events resource_events = { PW_VERSION_RESOURCE_EVENTS, .destroy = device_resource_destroy, }; static void device_destroy(void *data) { struct impl *impl = data; pw_log_debug("client-device %p: destroy", impl); impl->device = NULL; spa_hook_remove(&impl->device_listener); spa_hook_remove(&impl->resource_listener); spa_hook_remove(&impl->object_listener); pw_resource_destroy(impl->resource); } static void device_initialized(void *data) { struct impl *impl = data; struct pw_impl_device *device = impl->device; struct pw_global *global = pw_impl_device_get_global(device); uint32_t id = pw_global_get_id(global); pw_log_debug("client-device %p: initialized global:%d", impl, id); pw_resource_set_bound_id(impl->resource, id); } static const struct pw_impl_device_events device_events = { PW_VERSION_IMPL_DEVICE_EVENTS, .destroy = device_destroy, .initialized = device_initialized, }; struct pw_impl_device *pw_client_device_new(struct pw_resource *resource, struct pw_properties *properties) { struct impl *impl; struct pw_impl_device *device; struct pw_impl_client *client = pw_resource_get_client(resource); struct pw_context *context = pw_impl_client_get_context(client); if (properties == NULL) properties = pw_properties_new(NULL, NULL); if (properties == NULL) return NULL; pw_properties_setf(properties, PW_KEY_CLIENT_ID, "%d", pw_impl_client_get_info(client)->id); device = pw_context_create_device(context, properties, sizeof(struct impl)); if (device == NULL) return NULL; impl = pw_impl_device_get_user_data(device); impl->device = device; impl->context = context; impl->resource = resource; pw_impl_device_add_listener(impl->device, &impl->device_listener, &device_events, impl); pw_resource_add_listener(impl->resource, &impl->resource_listener, &resource_events, impl); pw_resource_add_object_listener(impl->resource, &impl->object_listener, &object_events, impl); return device; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/modules/module-client-node.c000066400000000000000000000207161511204443500265730ustar00rootroot00000000000000/* PipeWire */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include "config.h" #include #include #include #include #include #include #define PW_API_CLIENT_NODE_IMPL SPA_EXPORT #include "module-client-node/client-node.h" /** \page page_module_client_node Client Node * * Allow clients to export processing nodes to the PipeWire daemon. * * This module creates 2 export types, one for the \ref PW_TYPE_INTERFACE_Node and * another for the \ref SPA_TYPE_INTERFACE_Node interfaces. * * With \ref pw_core_export(), objects of these types can be exported to the * PipeWire server. All actions performed on the node locally will be visible * to connecteced clients and scheduling of the Node will be performed. * * Objects of the \ref PW_TYPE_INTERFACE_Node interface can be made with * \ref pw_context_create_node(), for example. You would manually need to create * and add an object of the \ref SPA_TYPE_INTERFACE_Node interface. Exporting a * \ref SPA_TYPE_INTERFACE_Node directly will first wrap it in a * \ref PW_TYPE_INTERFACE_Node interface. * * Usually this module is not used directly but through the \ref pw_stream and * \ref pw_filter APIs, which provides API to implement the \ref SPA_TYPE_INTERFACE_Node * interface. * * In some cases, it is possible to use this factory directly (the PipeWire JACK * implementation does this). With \ref pw_core_create_object() on the `client-node` * factory will result in a \ref PW_TYPE_INTERFACE_ClientNode proxy that can be * used to control the server side created \ref pw_impl_node. * * Schematically, the client side \ref pw_impl_node is wrapped in the ClientNode * proxy and unwrapped by the server side resource so that all actions on the client * side node are reflected on the server side node and server side actions are * reflected in the client. * *\code{.unparsed} * * client side proxy server side resource * .------------------------------. .----------------------------------. * | PW_TYPE_INTERFACE_ClientNode | | PW_TYPE_INTERFACE_Node | * |.----------------------------.| IPC |.--------------------------------.| * || PW_TYPE_INTERFACE_Node || -----> || SPA_TYPE_INTERFACE_Node || * ||.--------------------------.|| ||.------------------------------.|| * ||| SPA_TYPE_INTERFACE_Node ||| ||| PW_TYPE_INTERFACE_ClientNode ||| * ||| ||| ||| ||| * ||'--------------------------'|| ||'------------------------------'|| * |'----------------------------'| |'--------------------------------'| * '------------------------------' '----------------------------------' *\endcode * * ## Module Name * * `libpipewire-module-client-node` * * ## Module Options * * This module has no options. * * ## Properties for the create_object call * * All properties are passed directly to the \ref pw_context_create_node() call. * * ## Example configuration * * The module is usually added to the config file of the main PipeWire daemon and the * clients. * *\code{.unparsed} * context.modules = [ * { name = libpipewire-module-client-node } * ] *\endcode * * ## See also * * - `module-spa-node-factory`: make nodes from a factory */ #define NAME "client-node" PW_LOG_TOPIC(mod_topic, "mod." NAME); #define PW_LOG_TOPIC_DEFAULT mod_topic static const struct spa_dict_item module_props[] = { { PW_KEY_MODULE_AUTHOR, "Wim Taymans " }, { PW_KEY_MODULE_DESCRIPTION, "Allow clients to create and control remote nodes" }, { PW_KEY_MODULE_VERSION, PACKAGE_VERSION }, }; struct pw_proxy *pw_core_node_export(struct pw_core *core, const char *type, const struct spa_dict *props, void *object, size_t user_data_size); struct pw_proxy *pw_core_spa_node_export(struct pw_core *core, const char *type, const struct spa_dict *props, void *object, size_t user_data_size); struct pw_protocol *pw_protocol_native_ext_client_node_init(struct pw_context *context); struct factory_data { struct pw_impl_factory *factory; struct spa_hook factory_listener; struct pw_impl_module *module; struct spa_hook module_listener; struct pw_export_type export_node; struct pw_export_type export_spanode; }; static void *create_object(void *_data, struct pw_resource *resource, const char *type, uint32_t version, struct pw_properties *properties, uint32_t new_id) { void *result; struct pw_resource *node_resource; struct pw_impl_client *client; int res; if (resource == NULL) { res = -EINVAL; goto error_exit; } client = pw_resource_get_client(resource); node_resource = pw_resource_new(client, new_id, PW_PERM_ALL, type, version, 0); if (node_resource == NULL) { res = -errno; goto error_resource; } if (version == 0) { result = NULL; errno = ENOTSUP; } else { result = pw_impl_client_node_new(node_resource, properties, true); } if (result == NULL) { res = -errno; goto error_node; } return result; error_resource: pw_log_error("can't create resource: %s", spa_strerror(res)); pw_resource_errorf_id(resource, new_id, res, "can't create resource: %s", spa_strerror(res)); goto error_exit; error_node: pw_log_error("can't create node: %s", spa_strerror(res)); pw_resource_errorf_id(resource, new_id, res, "can't create node: %s", spa_strerror(res)); goto error_exit; error_exit: errno = -res; return NULL; } static const struct pw_impl_factory_implementation impl_factory = { PW_VERSION_IMPL_FACTORY_IMPLEMENTATION, .create_object = create_object, }; static void factory_destroy(void *data) { struct factory_data *d = data; spa_hook_remove(&d->factory_listener); d->factory = NULL; if (d->module) pw_impl_module_destroy(d->module); } static const struct pw_impl_factory_events factory_events = { PW_VERSION_IMPL_FACTORY_EVENTS, .destroy = factory_destroy, }; static void module_destroy(void *data) { struct factory_data *d = data; spa_hook_remove(&d->module_listener); spa_list_remove(&d->export_node.link); spa_list_remove(&d->export_spanode.link); d->module = NULL; if (d->factory) pw_impl_factory_destroy(d->factory); } static void module_registered(void *data) { struct factory_data *d = data; struct pw_impl_module *module = d->module; struct pw_impl_factory *factory = d->factory; struct spa_dict_item items[1]; char id[16]; int res; snprintf(id, sizeof(id), "%d", pw_global_get_id(pw_impl_module_get_global(module))); items[0] = SPA_DICT_ITEM_INIT(PW_KEY_MODULE_ID, id); pw_impl_factory_update_properties(factory, &SPA_DICT_INIT(items, 1)); if ((res = pw_impl_factory_register(factory, NULL)) < 0) { pw_log_error("%p: can't register factory: %s", factory, spa_strerror(res)); } } static const struct pw_impl_module_events module_events = { PW_VERSION_IMPL_MODULE_EVENTS, .destroy = module_destroy, .registered = module_registered, }; SPA_EXPORT int pipewire__module_init(struct pw_impl_module *module, const char *args) { struct pw_context *context = pw_impl_module_get_context(module); struct pw_impl_factory *factory; struct factory_data *data; int res; PW_LOG_TOPIC_INIT(mod_topic); factory = pw_context_create_factory(context, "client-node", PW_TYPE_INTERFACE_ClientNode, PW_VERSION_CLIENT_NODE, NULL, sizeof(*data)); if (factory == NULL) return -errno; data = pw_impl_factory_get_user_data(factory); data->factory = factory; data->module = module; pw_log_debug("module %p: new", module); pw_impl_factory_set_implementation(factory, &impl_factory, data); data->export_node.type = PW_TYPE_INTERFACE_Node; data->export_node.func = pw_core_node_export; if ((res = pw_context_register_export_type(context, &data->export_node)) < 0) goto error; data->export_spanode.type = SPA_TYPE_INTERFACE_Node; data->export_spanode.func = pw_core_spa_node_export; if ((res = pw_context_register_export_type(context, &data->export_spanode)) < 0) goto error_remove; pw_protocol_native_ext_client_node_init(context); pw_impl_factory_add_listener(factory, &data->factory_listener, &factory_events, data); pw_impl_module_add_listener(module, &data->module_listener, &module_events, data); pw_impl_module_update_properties(module, &SPA_DICT_INIT_ARRAY(module_props)); return 0; error_remove: spa_list_remove(&data->export_node.link); error: pw_impl_factory_destroy(data->factory); return res; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/modules/module-client-node/000077500000000000000000000000001511204443500264215ustar00rootroot00000000000000client-node.c000066400000000000000000001327761511204443500307270ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/modules/module-client-node/* PipeWire */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "pipewire/private.h" #include "modules/spa/spa-node.h" #include "client-node.h" PW_LOG_TOPIC_EXTERN(mod_topic); #define PW_LOG_TOPIC_DEFAULT mod_topic /** \cond */ #define MAX_BUFFERS 64 #define MAX_METAS 16u #define MAX_DATAS 256u #define AREA_SLOT (sizeof(struct spa_io_async_buffers)) #define AREA_SIZE (4096u / AREA_SLOT) #define MAX_AREAS 32 #define CHECK_FREE_PORT(impl,d,p) (p <= pw_map_get_size(&impl->ports[d]) && !CHECK_PORT(impl,d,p)) #define CHECK_PORT(impl,d,p) (pw_map_lookup(&impl->ports[d], p) != NULL) #define GET_PORT(impl,d,p) (pw_map_lookup(&impl->ports[d], p)) #define CHECK_PORT_BUFFER(impl,b,p) (b < p->n_buffers) struct buffer { struct spa_buffer *outbuf; struct spa_buffer buffer; struct spa_meta metas[MAX_METAS]; struct spa_data datas[MAX_DATAS]; struct pw_memblock *mem; }; struct mix { uint32_t mix_id; struct port *port; uint32_t peer_id; uint32_t n_buffers; uint32_t impl_mix_id; struct buffer buffers[MAX_BUFFERS]; }; struct params { uint32_t n_params; struct spa_pod **params; }; struct port { struct pw_impl_port *port; struct impl *impl; enum spa_direction direction; uint32_t id; struct spa_node mix_node; struct spa_hook_list mix_hooks; struct spa_port_info info; struct pw_properties *properties; struct params params; unsigned int removed:1; unsigned int destroyed:1; struct pw_map mix; }; struct impl { struct pw_impl_client_node this; struct pw_context *context; struct pw_mempool *context_pool; struct spa_node node; struct spa_log *log; struct spa_loop *data_loop; struct spa_system *data_system; struct spa_hook_list hooks; struct spa_callbacks callbacks; struct pw_resource *resource; struct pw_impl_client *client; struct pw_mempool *client_pool; struct spa_source data_source; struct pw_map ports[2]; struct port dummy; struct params params; struct pw_map io_map; struct pw_array io_areas; struct pw_memblock *activation; struct spa_hook node_listener; struct spa_hook resource_listener; struct spa_hook object_listener; uint32_t node_id; uint32_t bind_node_version; uint32_t bind_node_id; }; #define pw_client_node_resource(r,m,v,...) \ pw_resource_call_res(r,struct pw_client_node_events,m,v,__VA_ARGS__) #define pw_client_node_resource_transport(r,...) \ pw_client_node_resource(r,transport,0,__VA_ARGS__) #define pw_client_node_resource_set_param(r,...) \ pw_client_node_resource(r,set_param,0,__VA_ARGS__) #define pw_client_node_resource_set_io(r,...) \ pw_client_node_resource(r,set_io,0,__VA_ARGS__) #define pw_client_node_resource_event(r,...) \ pw_client_node_resource(r,event,0,__VA_ARGS__) #define pw_client_node_resource_command(r,...) \ pw_client_node_resource(r,command,0,__VA_ARGS__) #define pw_client_node_resource_add_port(r,...) \ pw_client_node_resource(r,add_port,0,__VA_ARGS__) #define pw_client_node_resource_remove_port(r,...) \ pw_client_node_resource(r,remove_port,0,__VA_ARGS__) #define pw_client_node_resource_port_set_param(r,...) \ pw_client_node_resource(r,port_set_param,0,__VA_ARGS__) #define pw_client_node_resource_port_use_buffers(r,...) \ pw_client_node_resource(r,port_use_buffers,0,__VA_ARGS__) #define pw_client_node_resource_port_set_io(r,...) \ pw_client_node_resource(r,port_set_io,0,__VA_ARGS__) #define pw_client_node_resource_set_activation(r,...) \ pw_client_node_resource(r,set_activation,0,__VA_ARGS__) #define pw_client_node_resource_port_set_mix_info(r,...) \ pw_client_node_resource(r,port_set_mix_info,1,__VA_ARGS__) static int update_params(struct params *p, uint32_t n_params, const struct spa_pod **params) { uint32_t i; for (i = 0; i < p->n_params; i++) free(p->params[i]); p->n_params = n_params; if (p->n_params == 0) { free(p->params); p->params = NULL; } else { struct spa_pod **np; np = pw_reallocarray(p->params, p->n_params, sizeof(struct spa_pod *)); if (np == NULL) { pw_log_error("%p: can't realloc: %m", p); free(p->params); p->params = NULL; p->n_params = 0; return -errno; } p->params = np; } for (i = 0; i < p->n_params; i++) p->params[i] = params[i] ? spa_pod_copy(params[i]) : NULL; return 0; } static int do_port_use_buffers(struct impl *impl, enum spa_direction direction, uint32_t port_id, uint32_t mix_id, uint32_t flags, struct spa_buffer **buffers, uint32_t n_buffers); /** \endcond */ static struct mix *find_mix(struct port *p, uint32_t mix_id) { if (mix_id == SPA_ID_INVALID) mix_id = 0; else mix_id++; return pw_map_lookup(&p->mix, mix_id); } static struct mix *create_mix(struct port *p, uint32_t mix_id) { struct mix *mix = NULL; size_t len; int res; if (mix_id == SPA_ID_INVALID) mix_id = 0; else mix_id++; if (pw_map_lookup(&p->mix, mix_id) != NULL) { errno = EEXIST; return NULL; } /* pad map size */ for (len = pw_map_get_size(&p->mix); len < mix_id; ++len) if ((res = pw_map_insert_at(&p->mix, len, NULL)) < 0) goto fail; mix = calloc(1, sizeof(struct mix)); if (mix == NULL) return NULL; if ((res = pw_map_insert_at(&p->mix, mix_id, mix)) < 0) goto fail; mix->mix_id = mix_id; mix->port = p; mix->n_buffers = 0; mix->impl_mix_id = SPA_ID_INVALID; return mix; fail: free(mix); errno = -res; return NULL; } static void clear_data(struct impl *impl, struct spa_data *d) { switch ((enum spa_data_type)d->type) { case SPA_DATA_Invalid: case SPA_DATA_MemPtr: case _SPA_DATA_LAST: break; case SPA_DATA_MemId: { uint32_t id; struct pw_memblock *m; id = SPA_PTR_TO_UINT32(d->data); m = pw_mempool_find_id(impl->client_pool, id); if (m) { pw_log_debug("%p: mem %d", impl, m->id); pw_memblock_unref(m); } break; } case SPA_DATA_MemFd: case SPA_DATA_DmaBuf: case SPA_DATA_SyncObj: pw_log_debug("%p: close fd:%d", impl, (int)d->fd); close(d->fd); break; } } static void clear_buffer(struct impl *impl, struct spa_buffer *b) { uint32_t i; for (i = 0; i < b->n_datas; i++) clear_data(impl, &b->datas[i]); } static int clear_buffers(struct impl *impl, struct mix *mix) { uint32_t i; for (i = 0; i < mix->n_buffers; i++) { struct buffer *b = &mix->buffers[i]; spa_log_debug(impl->log, "%p: clear buffer %d", impl, i); clear_buffer(impl, &b->buffer); pw_memblock_unref(b->mem); } mix->n_buffers = 0; return 0; } static void free_mix(struct port *p, struct mix *mix) { struct impl *impl = p->impl; if (mix == NULL) return; if (mix->n_buffers) { /* this shouldn't happen */ spa_log_warn(impl->log, "%p: mix port-id:%u freeing leaked buffers", impl, mix->mix_id - 1u); } clear_buffers(impl, mix); /* never realloc so it's safe to call from pw_map_foreach */ if (mix->mix_id < pw_map_get_size(&p->mix)) pw_map_insert_at(&p->mix, mix->mix_id, NULL); free(mix); } static void mix_clear(struct impl *impl, struct mix *mix) { struct port *port = mix->port; do_port_use_buffers(impl, port->direction, port->id, mix->mix_id, 0, NULL, 0); free_mix(port, mix); } static int impl_node_enum_params(void *object, int seq, uint32_t id, uint32_t start, uint32_t num, const struct spa_pod *filter) { struct impl *impl = object; uint8_t buffer[1024]; struct spa_pod_dynamic_builder b; struct spa_result_node_params result; uint32_t count = 0; bool found = false; spa_return_val_if_fail(impl != NULL, -EINVAL); spa_return_val_if_fail(num != 0, -EINVAL); result.id = id; result.next = 0; while (true) { struct spa_pod *param; result.index = result.next++; if (result.index >= impl->params.n_params) break; param = impl->params.params[result.index]; if (param == NULL || !spa_pod_is_object_id(param, id)) continue; found = true; if (result.index < start) continue; spa_pod_dynamic_builder_init(&b, buffer, sizeof(buffer), 4096); if (spa_pod_filter(&b.b, &result.param, param, filter) == 0) { pw_log_debug("%p: %d param %u", impl, seq, result.index); spa_node_emit_result(&impl->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); count++; } spa_pod_dynamic_builder_clean(&b); if (count == num) break; } return found ? 0 : -ENOENT; } static int impl_node_set_param(void *object, uint32_t id, uint32_t flags, const struct spa_pod *param) { struct impl *impl = object; spa_return_val_if_fail(impl != NULL, -EINVAL); if (impl->resource == NULL) return param == NULL ? 0 : -EIO; return pw_client_node_resource_set_param(impl->resource, id, flags, param); } static int impl_node_set_io(void *object, uint32_t id, void *data, size_t size) { struct impl *impl = object; struct pw_memmap *mm, *old; uint32_t memid, mem_offset, mem_size; uint32_t tag[5] = { impl->node_id, id, }; if (impl->this.flags & 1) return 0; old = pw_mempool_find_tag(impl->client_pool, tag, sizeof(tag)); if (data) { mm = pw_mempool_import_map(impl->client_pool, impl->context_pool, data, size, tag); if (mm == NULL) return -errno; mem_offset = mm->offset; memid = mm->block->id; mem_size = size; } else { memid = SPA_ID_INVALID; mem_offset = mem_size = 0; } pw_memmap_free(old); if (impl->resource == NULL) return data == NULL ? 0 : -EIO; return pw_client_node_resource_set_io(impl->resource, id, memid, mem_offset, mem_size); } static int impl_node_send_command(void *object, const struct spa_command *command) { struct impl *impl = object; uint32_t id; spa_return_val_if_fail(impl != NULL, -EINVAL); spa_return_val_if_fail(command != NULL, -EINVAL); id = SPA_NODE_COMMAND_ID(command); pw_log_debug("%p: send command %d (%s)", impl, id, spa_debug_type_find_name(spa_type_node_command_id, id)); if (impl->resource == NULL) return -EIO; return pw_client_node_resource_command(impl->resource, command); } static void emit_port_info(struct impl *impl, struct port *port) { spa_node_emit_port_info(&impl->hooks, port->direction, port->id, &port->info); } static int impl_node_add_listener(void *object, struct spa_hook *listener, const struct spa_node_events *events, void *data) { struct impl *impl = object; struct spa_hook_list save; union pw_map_item *item; spa_return_val_if_fail(impl != NULL, -EINVAL); spa_hook_list_isolate(&impl->hooks, &save, listener, events, data); pw_array_for_each(item, &impl->ports[SPA_DIRECTION_INPUT].items) { if (item->data) emit_port_info(impl, item->data); } pw_array_for_each(item, &impl->ports[SPA_DIRECTION_OUTPUT].items) { if (item->data) emit_port_info(impl, item->data); } spa_hook_list_join(&impl->hooks, &save); return 0; } static int impl_node_set_callbacks(void *object, const struct spa_node_callbacks *callbacks, void *data) { struct impl *impl = object; spa_return_val_if_fail(impl != NULL, -EINVAL); impl->callbacks = SPA_CALLBACKS_INIT(callbacks, data); return 0; } static int impl_node_sync(void *object, int seq) { struct impl *impl = object; spa_return_val_if_fail(impl != NULL, -EINVAL); pw_log_debug("%p: sync", impl); if (impl->resource == NULL) return -EIO; return pw_resource_ping(impl->resource, seq); } static void do_update_port(struct impl *impl, struct port *port, uint32_t change_mask, uint32_t n_params, const struct spa_pod **params, const struct spa_port_info *info) { if (change_mask & PW_CLIENT_NODE_PORT_UPDATE_PARAMS) { spa_log_debug(impl->log, "%p: port %u update %d params", impl, port->id, n_params); update_params(&port->params, n_params, params); } if (change_mask & PW_CLIENT_NODE_PORT_UPDATE_INFO) { pw_properties_free(port->properties); port->properties = NULL; port->info.props = NULL; port->info.n_params = 0; port->info.params = NULL; if (info) { port->info = *info; if (info->props) { port->properties = pw_properties_new_dict(info->props); port->info.props = &port->properties->dict; } port->info.n_params = 0; port->info.params = NULL; spa_node_emit_port_info(&impl->hooks, port->direction, port->id, info); } } } static int mix_clear_cb(void *item, void *data) { if (item) mix_clear(data, item); return 0; } static void clear_port(struct impl *impl, struct port *port) { spa_log_debug(impl->log, "%p: clear port %p", impl, port); do_update_port(impl, port, PW_CLIENT_NODE_PORT_UPDATE_PARAMS | PW_CLIENT_NODE_PORT_UPDATE_INFO, 0, NULL, NULL); pw_map_for_each(&port->mix, mix_clear_cb, impl); pw_map_clear(&port->mix); pw_map_init(&port->mix, 0, 2); pw_map_insert_at(&impl->ports[port->direction], port->id, NULL); if (!port->removed) spa_node_emit_port_info(&impl->hooks, port->direction, port->id, NULL); } static int impl_node_add_port(void *object, enum spa_direction direction, uint32_t port_id, const struct spa_dict *props) { struct impl *impl = object; spa_return_val_if_fail(impl != NULL, -EINVAL); spa_return_val_if_fail(CHECK_FREE_PORT(impl, direction, port_id), -EINVAL); if (impl->resource == NULL) return -EIO; return pw_client_node_resource_add_port(impl->resource, direction, port_id, props); } static int impl_node_remove_port(void *object, enum spa_direction direction, uint32_t port_id) { struct impl *impl = object; spa_return_val_if_fail(impl != NULL, -EINVAL); spa_return_val_if_fail(CHECK_PORT(impl, direction, port_id), -EINVAL); if (impl->resource == NULL) return -EIO; return pw_client_node_resource_remove_port(impl->resource, direction, port_id); } static int node_port_enum_params(struct impl *impl, int seq, enum spa_direction direction, uint32_t port_id, uint32_t id, uint32_t start, uint32_t num, const struct spa_pod *filter, struct spa_hook_list *hooks) { struct port *port; uint8_t buffer[1024]; struct spa_pod_dynamic_builder b; struct spa_result_node_params result; uint32_t count = 0; bool found = false; spa_return_val_if_fail(impl != NULL, -EINVAL); spa_return_val_if_fail(num != 0, -EINVAL); port = GET_PORT(impl, direction, port_id); spa_return_val_if_fail(port != NULL, -EINVAL); pw_log_debug("%p: seq:%d port %d.%d id:%u start:%u num:%u n_params:%d", impl, seq, direction, port_id, id, start, num, port->params.n_params); result.id = id; result.next = 0; while (true) { struct spa_pod *param; result.index = result.next++; if (result.index >= port->params.n_params) break; param = port->params.params[result.index]; if (param == NULL || !spa_pod_is_object_id(param, id)) continue; found = true; if (result.index < start) continue; spa_pod_dynamic_builder_init(&b, buffer, sizeof(buffer), 4096); if (spa_pod_filter(&b.b, &result.param, param, filter) == 0) { pw_log_debug("%p: %d param %u", impl, seq, result.index); spa_node_emit_result(hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); count++; } spa_pod_dynamic_builder_clean(&b); if (count == num) break; } return found ? 0 : -ENOENT; } static int impl_node_port_enum_params(void *object, int seq, enum spa_direction direction, uint32_t port_id, uint32_t id, uint32_t start, uint32_t num, const struct spa_pod *filter) { struct impl *impl = object; return node_port_enum_params(impl, seq, direction, port_id, id, start, num, filter, &impl->hooks); } static int clear_buffers_cb(void *item, void *data) { if (item) clear_buffers(data, item); return 0; } static int impl_node_port_set_param(void *object, enum spa_direction direction, uint32_t port_id, uint32_t id, uint32_t flags, const struct spa_pod *param) { struct impl *impl = object; struct port *port; spa_return_val_if_fail(impl != NULL, -EINVAL); port = GET_PORT(impl, direction, port_id); if(port == NULL) return param == NULL ? 0 : -EINVAL; pw_log_debug("%p: port %d.%d set param %s %d", impl, direction, port_id, spa_debug_type_find_name(spa_type_param, id), id); if (id == SPA_PARAM_Format) pw_map_for_each(&port->mix, clear_buffers_cb, impl); if (impl->resource == NULL) return param == NULL ? 0 : -EIO; return pw_client_node_resource_port_set_param(impl->resource, direction, port_id, id, flags, param); } static int do_port_set_io(struct impl *impl, enum spa_direction direction, uint32_t port_id, uint32_t mix_id, uint32_t id, void *data, size_t size) { uint32_t memid, mem_offset, mem_size; struct port *port; struct mix *mix; uint32_t tag[5] = { impl->node_id, direction, port_id, mix_id, id }; struct pw_memmap *mm, *old; pw_log_debug("%p: %s port %d.%d set io %p %zd", impl, direction == SPA_DIRECTION_INPUT ? "input" : "output", port_id, mix_id, data, size); old = pw_mempool_find_tag(impl->client_pool, tag, sizeof(tag)); port = GET_PORT(impl, direction, port_id); if (port == NULL) { pw_memmap_free(old); return data == NULL ? 0 : -EINVAL; } if ((mix = find_mix(port, mix_id)) == NULL) { pw_memmap_free(old); return -EINVAL; } if (data) { mm = pw_mempool_import_map(impl->client_pool, impl->context_pool, data, size, tag); if (mm == NULL) return -errno; mem_offset = mm->offset; memid = mm->block->id; mem_size = size; } else { memid = SPA_ID_INVALID; mem_offset = mem_size = 0; } pw_memmap_free(old); if (impl->resource == NULL) return data == NULL ? 0 : -EIO; return pw_client_node_resource_port_set_io(impl->resource, direction, port_id, mix_id, id, memid, mem_offset, mem_size); } static int impl_node_port_set_io(void *object, enum spa_direction direction, uint32_t port_id, uint32_t id, void *data, size_t size) { /* ignore io on the node itself, we only care about the io on the * port mixers, the io on the node ports itself is handled on the * client side */ return -EINVAL; } static int do_port_use_buffers(struct impl *impl, enum spa_direction direction, uint32_t port_id, uint32_t mix_id, uint32_t flags, struct spa_buffer **buffers, uint32_t n_buffers) { struct port *p; struct mix *mix; uint32_t i, j; struct pw_client_node_buffer *mb; p = GET_PORT(impl, direction, port_id); if (p == NULL) return n_buffers == 0 ? 0 : -EINVAL; if (n_buffers > MAX_BUFFERS) return -ENOSPC; spa_log_debug(impl->log, "%p: %s port %d.%d use buffers %p %u flags:%08x", impl, direction == SPA_DIRECTION_INPUT ? "input" : "output", port_id, mix_id, buffers, n_buffers, flags); if (direction == SPA_DIRECTION_OUTPUT) mix_id = SPA_ID_INVALID; if ((mix = find_mix(p, mix_id)) == NULL) return -EINVAL; clear_buffers(impl, mix); if (n_buffers > 0) { mb = alloca(n_buffers * sizeof(struct pw_client_node_buffer)); } else { mb = NULL; } if (impl->resource == NULL) return n_buffers == 0 ? 0 : -EIO; if (p->destroyed) return 0; for (i = 0; i < n_buffers; i++) { struct buffer *b = &mix->buffers[i]; struct pw_memblock *mem, *m; void *baseptr, *endptr; b->outbuf = buffers[i]; memcpy(&b->buffer, buffers[i], sizeof(struct spa_buffer)); b->buffer.datas = b->datas; b->buffer.metas = b->metas; if (buffers[i]->n_metas > 0) baseptr = buffers[i]->metas[0].data; else if (buffers[i]->n_datas > 0) baseptr = buffers[i]->datas[0].chunk; else return -EINVAL; if ((mem = pw_mempool_find_ptr(impl->context_pool, baseptr)) == NULL) return -EINVAL; endptr = SPA_PTROFF(baseptr, buffers[i]->n_datas * sizeof(struct spa_chunk), void); for (j = 0; j < buffers[i]->n_metas; j++) { endptr = SPA_PTROFF(endptr, SPA_ROUND_UP_N(buffers[i]->metas[j].size, 8), void); } for (j = 0; j < buffers[i]->n_datas; j++) { struct spa_data *d = &buffers[i]->datas[j]; if (d->type == SPA_DATA_MemPtr) { if ((m = pw_mempool_find_ptr(impl->context_pool, d->data)) == NULL || m != mem) return -EINVAL; endptr = SPA_MAX(endptr, SPA_PTROFF(d->data, d->maxsize, void)); } } if (endptr > SPA_PTROFF(baseptr, mem->size, void)) return -EINVAL; m = pw_mempool_import_block(impl->client_pool, mem); if (m == NULL) return -errno; b->mem = m; mb[i].buffer = &b->buffer; mb[i].mem_id = m->id; mb[i].offset = SPA_PTRDIFF(baseptr, mem->map->ptr); mb[i].size = SPA_PTRDIFF(endptr, baseptr); spa_log_debug(impl->log, "%p: buffer %d %d %d %d", impl, i, mb[i].mem_id, mb[i].offset, mb[i].size); b->buffer.n_metas = SPA_MIN(buffers[i]->n_metas, MAX_METAS); for (j = 0; j < b->buffer.n_metas; j++) memcpy(&b->buffer.metas[j], &buffers[i]->metas[j], sizeof(struct spa_meta)); b->buffer.n_datas = SPA_MIN(buffers[i]->n_datas, MAX_DATAS); for (j = 0; j < b->buffer.n_datas; j++) { struct spa_data *d = &buffers[i]->datas[j]; memcpy(&b->datas[j], d, sizeof(struct spa_data)); if (flags & SPA_NODE_BUFFERS_FLAG_ALLOC) continue; switch (d->type) { case SPA_DATA_DmaBuf: case SPA_DATA_MemFd: case SPA_DATA_SyncObj: { uint32_t flags = PW_MEMBLOCK_FLAG_DONT_CLOSE; if (!(d->flags & SPA_DATA_FLAG_MAPPABLE)) flags |= PW_MEMBLOCK_FLAG_UNMAPPABLE; if (d->flags & SPA_DATA_FLAG_READABLE) flags |= PW_MEMBLOCK_FLAG_READABLE; if (d->flags & SPA_DATA_FLAG_WRITABLE) flags |= PW_MEMBLOCK_FLAG_WRITABLE; spa_log_debug(impl->log, "mem %d type:%d fd:%d", j, d->type, (int)d->fd); m = pw_mempool_import(impl->client_pool, flags, d->type, d->fd); if (m == NULL) return -errno; b->datas[j].type = SPA_DATA_MemId; b->datas[j].data = SPA_UINT32_TO_PTR(m->id); break; } case SPA_DATA_MemPtr: spa_log_debug(impl->log, "mem %d %zd", j, SPA_PTRDIFF(d->data, baseptr)); b->datas[j].data = SPA_INT_TO_PTR(SPA_PTRDIFF(d->data, baseptr)); break; default: b->datas[j].type = SPA_ID_INVALID; b->datas[j].data = NULL; spa_log_error(impl->log, "invalid memory type %d", d->type); break; } } } mix->n_buffers = n_buffers; return pw_client_node_resource_port_use_buffers(impl->resource, direction, port_id, mix_id, flags, n_buffers, mb); } static int impl_node_port_use_buffers(void *object, enum spa_direction direction, uint32_t port_id, uint32_t flags, struct spa_buffer **buffers, uint32_t n_buffers) { struct impl *impl = object; spa_return_val_if_fail(impl != NULL, -EINVAL); return do_port_use_buffers(impl, direction, port_id, SPA_ID_INVALID, flags, buffers, n_buffers); } static int impl_node_port_reuse_buffer(void *object, uint32_t port_id, uint32_t buffer_id) { struct impl *impl = object; spa_return_val_if_fail(impl != NULL, -EINVAL); spa_return_val_if_fail(CHECK_PORT(impl, SPA_DIRECTION_OUTPUT, port_id), -EINVAL); spa_log_trace_fp(impl->log, "reuse buffer %d", buffer_id); return -ENOTSUP; } static int impl_node_process(void *object) { struct impl *impl = object; struct pw_impl_node *n = impl->this.node; struct timespec ts; /* this should not be called, we call the exported node * directly */ spa_log_warn(impl->log, "exported node activation"); spa_system_clock_gettime(impl->data_system, CLOCK_MONOTONIC, &ts); n->rt.target.activation->status = PW_NODE_ACTIVATION_TRIGGERED; n->rt.target.activation->signal_time = SPA_TIMESPEC_TO_NSEC(&ts); if (SPA_UNLIKELY(spa_system_eventfd_write(n->rt.target.system, n->rt.target.fd, 1) < 0)) pw_log_warn("%p: write failed %m", impl); return SPA_STATUS_OK; } static struct pw_node * client_node_get_node(void *data, uint32_t version, size_t user_data_size) { struct impl *impl = data; uint32_t new_id = user_data_size; pw_log_debug("%p: bind %u/%u", impl, new_id, version); impl->bind_node_version = version; impl->bind_node_id = new_id; pw_map_insert_at(&impl->client->objects, new_id, NULL); return NULL; } static int client_node_update(void *data, uint32_t change_mask, uint32_t n_params, const struct spa_pod **params, const struct spa_node_info *info) { struct impl *impl = data; if (change_mask & PW_CLIENT_NODE_UPDATE_PARAMS) { pw_log_debug("%p: update %d params", impl, n_params); update_params(&impl->params, n_params, params); } if (change_mask & PW_CLIENT_NODE_UPDATE_INFO) { spa_node_emit_info(&impl->hooks, info); } pw_log_debug("%p: got node update", impl); return 0; } static int client_node_port_update(void *data, enum spa_direction direction, uint32_t port_id, uint32_t change_mask, uint32_t n_params, const struct spa_pod **params, const struct spa_port_info *info) { struct impl *impl = data; struct port *port; bool remove; spa_log_debug(impl->log, "%p: got port update change:%08x params:%d", impl, change_mask, n_params); remove = (change_mask == 0); port = GET_PORT(impl, direction, port_id); if (remove) { if (port == NULL) return 0; port->destroyed = true; clear_port(impl, port); } else { struct port *target; if (port == NULL) { if (!CHECK_FREE_PORT(impl, direction, port_id)) return -EINVAL; target = &impl->dummy; spa_zero(impl->dummy); target->direction = direction; target->id = port_id; } else target = port; do_update_port(impl, target, change_mask, n_params, params, info); } return 0; } static int client_node_set_active(void *data, bool active) { struct impl *impl = data; spa_log_debug(impl->log, "%p: active:%d", impl, active); return pw_impl_node_set_active(impl->this.node, active); } static int client_node_event(void *data, const struct spa_event *event) { struct impl *impl = data; spa_node_emit_event(&impl->hooks, event); return 0; } static int client_node_port_buffers(void *data, enum spa_direction direction, uint32_t port_id, uint32_t mix_id, uint32_t n_buffers, struct spa_buffer **buffers) { struct impl *impl = data; struct port *p; struct mix *mix; uint32_t i, j; spa_log_debug(impl->log, "%p: %s port %d.%d buffers %p %u", impl, direction == SPA_DIRECTION_INPUT ? "input" : "output", port_id, mix_id, buffers, n_buffers); p = GET_PORT(impl, direction, port_id); spa_return_val_if_fail(p != NULL, -EINVAL); if (direction == SPA_DIRECTION_OUTPUT) mix_id = SPA_ID_INVALID; if ((mix = find_mix(p, mix_id)) == NULL) goto invalid; if (mix->n_buffers != n_buffers) goto invalid; for (i = 0; i < n_buffers; i++) { if (mix->buffers[i].outbuf->n_datas != buffers[i]->n_datas) goto invalid; } for (i = 0; i < n_buffers; i++) { struct spa_buffer *oldbuf, *newbuf; struct buffer *b = &mix->buffers[i]; oldbuf = b->outbuf; newbuf = buffers[i]; spa_log_debug(impl->log, "buffer %d n_datas:%d", i, newbuf->n_datas); for (j = 0; j < b->buffer.n_datas; j++) { struct spa_chunk *oldchunk = oldbuf->datas[j].chunk; struct spa_data *d = &newbuf->datas[j]; uint32_t flags = d->flags; if (d->type == SPA_DATA_MemFd && !SPA_FLAG_IS_SET(flags, SPA_DATA_FLAG_MAPPABLE)) { spa_log_debug(impl->log, "buffer:%d data:%d has non mappable MemFd, " "fixing to ensure backwards compatibility.", i, j); flags |= SPA_DATA_FLAG_MAPPABLE; } /* overwrite everything except the chunk */ oldbuf->datas[j] = *d; oldbuf->datas[j].flags = flags; oldbuf->datas[j].chunk = oldchunk; b->datas[j].type = d->type; b->datas[j].flags = flags; b->datas[j].fd = d->fd; spa_log_debug(impl->log, " data %d type:%d fl:%08x fd:%d, offs:%d max:%d", j, d->type, flags, (int) d->fd, d->mapoffset, d->maxsize); } } return 0; invalid: for (i = 0; i < n_buffers; i++) clear_buffer(impl, buffers[i]); return -EINVAL; } static const struct pw_client_node_methods client_node_methods = { PW_VERSION_CLIENT_NODE_METHODS, .get_node = client_node_get_node, .update = client_node_update, .port_update = client_node_port_update, .set_active = client_node_set_active, .event = client_node_event, .port_buffers = client_node_port_buffers, }; static void node_on_data_fd_events(struct spa_source *source) { struct impl *impl = source->data; if (SPA_UNLIKELY(source->rmask & (SPA_IO_ERR | SPA_IO_HUP))) { spa_log_warn(impl->log, "%p: got error", impl); return; } if (SPA_LIKELY(source->rmask & SPA_IO_IN)) { uint64_t cmd; struct pw_impl_node *node = impl->this.node; if (SPA_UNLIKELY(spa_system_eventfd_read(impl->data_system, impl->data_source.fd, &cmd) < 0)) pw_log_warn("%p: read failed %m", impl); else if (SPA_UNLIKELY(cmd > 1)) pw_log_info("(%s-%u) client missed %"PRIu64" wakeups", node->name, node->info.id, cmd - 1); if (impl->resource && impl->resource->version < 5) { struct pw_node_activation *a = node->rt.target.activation; int status = a->state[0].status; spa_log_trace_fp(impl->log, "%p: got ready %d", impl, status); spa_node_call_ready(&impl->callbacks, status); } else { spa_log_trace_fp(impl->log, "%p: got complete", impl); pw_impl_node_rt_emit_complete(node); } } } static const struct spa_node_methods impl_node = { SPA_VERSION_NODE_METHODS, .add_listener = impl_node_add_listener, .set_callbacks = impl_node_set_callbacks, .sync = impl_node_sync, .enum_params = impl_node_enum_params, .set_param = impl_node_set_param, .set_io = impl_node_set_io, .send_command = impl_node_send_command, .add_port = impl_node_add_port, .remove_port = impl_node_remove_port, .port_enum_params = impl_node_port_enum_params, .port_set_param = impl_node_port_set_param, .port_use_buffers = impl_node_port_use_buffers, .port_set_io = impl_node_port_set_io, .port_reuse_buffer = impl_node_port_reuse_buffer, .process = impl_node_process, }; static int impl_init(struct impl *impl, struct spa_dict *info) { impl->node.iface = SPA_INTERFACE_INIT( SPA_TYPE_INTERFACE_Node, SPA_VERSION_NODE, &impl_node, impl); spa_hook_list_init(&impl->hooks); impl->data_source.func = node_on_data_fd_events; impl->data_source.data = impl; impl->data_source.fd = -1; impl->data_source.mask = SPA_IO_IN | SPA_IO_ERR | SPA_IO_HUP; impl->data_source.rmask = 0; return 0; } static int impl_clear(struct impl *impl) { update_params(&impl->params, 0, NULL); return 0; } static int do_remove_source(struct spa_loop *loop, bool async, uint32_t seq, const void *data, size_t size, void *user_data) { struct spa_source *source = user_data; spa_loop_remove_source(loop, source); return 0; } static void client_node_resource_destroy(void *data) { struct impl *impl = data; struct pw_impl_client_node *this = &impl->this; pw_log_debug("%p: destroy", impl); impl->resource = this->resource = NULL; spa_hook_remove(&impl->resource_listener); spa_hook_remove(&impl->object_listener); if (impl->data_source.fd != -1) { spa_loop_locked(impl->data_loop, do_remove_source, SPA_ID_INVALID, NULL, 0, &impl->data_source); } if (this->node) pw_impl_node_destroy(this->node); } static void client_node_resource_error(void *data, int seq, int res, const char *message) { struct impl *impl = data; struct spa_result_node_error result; pw_log_error("%p: error seq:%d %d (%s)", impl, seq, res, message); result.message = message; spa_node_emit_result(&impl->hooks, seq, res, SPA_RESULT_TYPE_NODE_ERROR, &result); } static void client_node_resource_pong(void *data, int seq) { struct impl *impl = data; pw_log_debug("%p: got pong, emit result %d", impl, seq); spa_node_emit_result(&impl->hooks, seq, 0, 0, NULL); } static void node_peer_added(void *data, struct pw_impl_node *peer) { struct impl *impl = data; struct pw_memblock *m; m = pw_mempool_import_block(impl->client_pool, peer->activation); if (m == NULL) { pw_log_warn("%p: can't ensure mem: %m", impl); return; } pw_log_debug("%p: peer %p/%p id:%u added mem_id:%u %p %d", impl, peer, impl->this.node, peer->info.id, m->id, m, m->ref); if (impl->resource == NULL) return; pw_client_node_resource_set_activation(impl->resource, peer->info.id, peer->source.fd, m->id, 0, sizeof(struct pw_node_activation)); } static void node_peer_removed(void *data, struct pw_impl_node *peer) { struct impl *impl = data; struct pw_memblock *m; m = pw_mempool_find_fd(impl->client_pool, peer->activation->fd); if (m == NULL) { pw_log_warn("%p: unknown peer %p fd:%d", impl, peer, peer->source.fd); return; } pw_log_debug("%p: peer %p/%p id:%u removed mem_id:%u", impl, peer, impl->this.node, peer->info.id, m->id); if (impl->resource != NULL) { pw_client_node_resource_set_activation(impl->resource, peer->info.id, -1, SPA_ID_INVALID, 0, 0); } pw_memblock_unref(m); } void pw_impl_client_node_registered(struct pw_impl_client_node *this, struct pw_global *global) { struct impl *impl = SPA_CONTAINER_OF(this, struct impl, this); struct pw_impl_node *node = this->node; struct pw_impl_client *client = impl->client; uint32_t node_id = global->id; pw_log_debug("%p: %d", &impl->node, node_id); impl->activation = pw_mempool_import_block(impl->client_pool, node->activation); if (impl->activation == NULL) { pw_log_debug("%p: can't import block: %m", &impl->node); return; } impl->node_id = node_id; if (impl->resource == NULL) return; pw_resource_set_bound_id(impl->resource, node_id); pw_client_node_resource_transport(impl->resource, this->node->source.fd, impl->data_source.fd, impl->activation->id, 0, sizeof(struct pw_node_activation)); if (impl->bind_node_id) { pw_global_bind(global, client, PW_PERM_ALL, impl->bind_node_version, impl->bind_node_id); } } static int add_area(struct impl *impl) { size_t size; struct pw_memblock *area; size = AREA_SLOT * AREA_SIZE; area = pw_mempool_alloc(impl->context_pool, PW_MEMBLOCK_FLAG_READWRITE | PW_MEMBLOCK_FLAG_SEAL | PW_MEMBLOCK_FLAG_MAP, SPA_DATA_MemFd, size); if (area == NULL) return -errno; pw_log_debug("%p: io area %u %p", impl, (unsigned)pw_array_get_len(&impl->io_areas, struct pw_memblock*), area->map->ptr); pw_array_add_ptr(&impl->io_areas, area); return 0; } static void node_initialized(void *data) { struct impl *impl = data; struct pw_impl_client_node *this = &impl->this; struct pw_global *global; struct spa_system *data_system = impl->data_system; impl->data_source.fd = spa_system_eventfd_create(data_system, SPA_FD_CLOEXEC | SPA_FD_NONBLOCK); spa_loop_add_source(impl->data_loop, &impl->data_source); pw_log_debug("%p: transport read-fd:%d write-fd:%d", impl, impl->data_source.fd, this->node->source.fd); if (add_area(impl) < 0) return; if ((global = pw_impl_node_get_global(this->node)) != NULL) pw_impl_client_node_registered(this, global); } static void node_free(void *data) { struct impl *impl = data; struct pw_impl_client_node *this = &impl->this; struct spa_system *data_system = impl->data_system; uint32_t tag[5] = { impl->node_id, }; struct pw_memmap *mm; struct pw_memblock **area; this->node = NULL; pw_log_debug("%p: free", impl); impl_clear(impl); spa_hook_remove(&impl->node_listener); while ((mm = pw_mempool_find_tag(impl->client_pool, tag, sizeof(uint32_t))) != NULL) pw_memmap_free(mm); if (impl->activation) pw_memblock_free(impl->activation); pw_array_for_each(area, &impl->io_areas) { if (*area) pw_memblock_unref(*area); } pw_array_clear(&impl->io_areas); if (impl->resource) pw_resource_destroy(impl->resource); pw_map_clear(&impl->ports[0]); pw_map_clear(&impl->ports[1]); pw_map_clear(&impl->io_map); if (impl->data_source.fd != -1) spa_system_close(data_system, impl->data_source.fd); free(impl); } static int port_init_mix(void *data, struct pw_impl_port_mix *mix) { struct port *port = data; struct impl *impl = port->impl; struct mix *m; uint32_t idx, pos, len; struct pw_memblock *area; struct spa_io_async_buffers *ab; if ((m = create_mix(port, mix->port.port_id)) == NULL) return -ENOMEM; mix->id = pw_map_insert_new(&impl->io_map, NULL); if (mix->id == SPA_ID_INVALID) { free_mix(port, m); return -errno; } idx = mix->id / AREA_SIZE; pos = mix->id % AREA_SIZE; len = pw_array_get_len(&impl->io_areas, struct pw_memblock *); if (idx > len) goto no_mem; if (idx == len) { pw_log_debug("%p: extend area idx:%u pos:%u", impl, idx, pos); if (add_area(impl) < 0) goto no_mem; } area = *pw_array_get_unchecked(&impl->io_areas, idx, struct pw_memblock*); ab = SPA_PTROFF(area->map->ptr, pos * AREA_SLOT, void); mix->io_data = ab; mix->io[0] = &ab->buffers[0]; mix->io[1] = &ab->buffers[1]; *mix->io[0] = SPA_IO_BUFFERS_INIT; *mix->io[1] = SPA_IO_BUFFERS_INIT; m->peer_id = mix->peer_id; m->impl_mix_id = mix->id; if (impl->resource && impl->resource->version >= 4) pw_client_node_resource_port_set_mix_info(impl->resource, mix->port.direction, mix->p->port_id, mix->port.port_id, mix->peer_id, NULL); pw_log_debug("%p: init mix id:%d io:%p/%p base:%p", impl, mix->id, mix->io[0], mix->io[1], area->map->ptr); return 0; no_mem: pw_map_remove(&impl->io_map, mix->id); free_mix(port, m); return -ENOMEM; } static int port_release_mix(void *data, struct pw_impl_port_mix *mix) { struct port *port = data; struct impl *impl = port->impl; struct mix *m; pw_log_debug("%p: remove mix id:%d io:%p", impl, mix->id, mix->io); if (!pw_map_has_item(&impl->io_map, mix->id)) return -EINVAL; if (impl->resource && impl->resource->version >= 4 && !port->destroyed) pw_client_node_resource_port_set_mix_info(impl->resource, mix->port.direction, mix->p->port_id, mix->port.port_id, SPA_ID_INVALID, NULL); pw_map_remove(&impl->io_map, mix->id); m = find_mix(port, mix->port.port_id); if (m && m->impl_mix_id == mix->id) free_mix(port, m); else pw_log_debug("%p: already cleared mix id:%d port-id:%d", impl, mix->id, mix->port.port_id); return 0; } static const struct pw_impl_port_implementation port_impl = { PW_VERSION_PORT_IMPLEMENTATION, .init_mix = port_init_mix, .release_mix = port_release_mix, }; static int impl_mix_add_listener(void *object, struct spa_hook *listener, const struct spa_node_events *events, void *data) { struct port *port = object; spa_hook_list_append(&port->mix_hooks, listener, events, data); return 0; } static int impl_mix_port_enum_params(void *object, int seq, enum spa_direction direction, uint32_t port_id, uint32_t id, uint32_t start, uint32_t num, const struct spa_pod *filter) { struct port *port = object; if (port->direction != direction) return -ENOTSUP; return node_port_enum_params(port->impl, seq, direction, port->id, id, start, num, filter, &port->mix_hooks); } static int impl_mix_port_set_param(void *object, enum spa_direction direction, uint32_t port_id, uint32_t id, uint32_t flags, const struct spa_pod *param) { return -ENOTSUP; } static int impl_mix_add_port(void *object, enum spa_direction direction, uint32_t mix_id, const struct spa_dict *props) { struct port *port = object; pw_log_debug("%p: add port %d:%d.%d", object, direction, port->id, mix_id); return 0; } static int impl_mix_remove_port(void *object, enum spa_direction direction, uint32_t mix_id) { struct port *port = object; pw_log_debug("%p: remove port %d:%d.%d", object, direction, port->id, mix_id); return 0; } static int impl_mix_port_use_buffers(void *object, enum spa_direction direction, uint32_t mix_id, uint32_t flags, struct spa_buffer **buffers, uint32_t n_buffers) { struct port *port = object; struct impl *impl = port->impl; return do_port_use_buffers(impl, direction, port->id, mix_id, flags, buffers, n_buffers); } static int impl_mix_port_set_io(void *object, enum spa_direction direction, uint32_t mix_id, uint32_t id, void *data, size_t size) { struct port *p = object; struct pw_impl_port *port = p->port; struct impl *impl = port->owner_data; struct pw_impl_port_mix *mix; mix = pw_map_lookup(&port->mix_port_map, mix_id); if (mix == NULL) return -EINVAL; switch (id) { case SPA_IO_Buffers: if (data && size >= sizeof(struct spa_io_buffers)) mix->io[0] = mix->io[1] = data; else mix->io[0] = mix->io[1] = NULL; break; case SPA_IO_AsyncBuffers: if (data && size >= sizeof(struct spa_io_async_buffers)) { struct spa_io_async_buffers *ab = data; mix->io[0] = &ab->buffers[0]; mix->io[1] = &ab->buffers[1]; } else mix->io[0] = mix->io[1] = NULL; break; default: break; } return do_port_set_io(impl, direction, port->port_id, mix->port.port_id, id, data, size); } static int impl_mix_port_reuse_buffer(void *object, uint32_t port_id, uint32_t buffer_id) { struct port *p = object; return impl_node_port_reuse_buffer(p->impl, p->id, buffer_id); } static int impl_mix_process(void *object) { return SPA_STATUS_HAVE_DATA; } static const struct spa_node_methods impl_port_mix = { SPA_VERSION_NODE_METHODS, .add_listener = impl_mix_add_listener, .port_enum_params = impl_mix_port_enum_params, .port_set_param = impl_mix_port_set_param, .add_port = impl_mix_add_port, .remove_port = impl_mix_remove_port, .port_use_buffers = impl_mix_port_use_buffers, .port_set_io = impl_mix_port_set_io, .port_reuse_buffer = impl_mix_port_reuse_buffer, .process = impl_mix_process, }; static void node_port_init(void *data, struct pw_impl_port *port) { struct impl *impl = data; struct port *p = pw_impl_port_get_user_data(port); pw_log_debug("%p: port %p init", impl, port); *p = impl->dummy; p->port = port; p->impl = impl; p->direction = port->direction; p->id = port->port_id; p->impl = impl; pw_map_init(&p->mix, 2, 2); spa_hook_list_init(&p->mix_hooks); p->mix_node.iface = SPA_INTERFACE_INIT( SPA_TYPE_INTERFACE_Node, SPA_VERSION_NODE, &impl_port_mix, p); create_mix(p, SPA_ID_INVALID); pw_map_insert_at(&impl->ports[p->direction], p->id, p); return; } static void node_port_added(void *data, struct pw_impl_port *port) { struct impl *impl = data; struct port *p = pw_impl_port_get_user_data(port); port->flags |= PW_IMPL_PORT_FLAG_NO_MIXER; port->impl = SPA_CALLBACKS_INIT(&port_impl, p); port->owner_data = impl; pw_impl_port_set_mix(port, &p->mix_node, PW_IMPL_PORT_MIX_FLAG_MULTI | PW_IMPL_PORT_MIX_FLAG_MIX_ONLY); } static void node_port_removed(void *data, struct pw_impl_port *port) { struct impl *impl = data; struct port *p = pw_impl_port_get_user_data(port); pw_log_debug("%p: port %p remove", impl, port); p->removed = true; clear_port(impl, p); } static const struct pw_impl_node_events node_events = { PW_VERSION_IMPL_NODE_EVENTS, .free = node_free, .initialized = node_initialized, .port_init = node_port_init, .port_added = node_port_added, .port_removed = node_port_removed, .peer_added = node_peer_added, .peer_removed = node_peer_removed, }; static const struct pw_resource_events resource_events = { PW_VERSION_RESOURCE_EVENTS, .destroy = client_node_resource_destroy, .error = client_node_resource_error, .pong = client_node_resource_pong, }; /** Create a new client node * \param client an owner \ref pw_client * \param id an id * \param name a name * \param properties extra properties * \return a newly allocated client node * * Create a new \ref pw_impl_node. * * \memberof pw_impl_client_node */ struct pw_impl_client_node *pw_impl_client_node_new(struct pw_resource *resource, struct pw_properties *properties, bool do_register) { struct impl *impl; struct pw_impl_client_node *this; struct pw_impl_client *client = pw_resource_get_client(resource); struct pw_context *context = pw_impl_client_get_context(client); int res; impl = calloc(1, sizeof(struct impl)); if (impl == NULL) { res = -errno; goto error_exit_cleanup; } if (properties == NULL) properties = pw_properties_new(NULL, NULL); if (properties == NULL) { res = -errno; goto error_exit_free; } pw_properties_setf(properties, PW_KEY_CLIENT_ID, "%d", client->global->id); this = &impl->this; impl->context = context; impl->context_pool = pw_context_get_mempool(context); impl->data_source.fd = -1; pw_log_debug("%p: new", &impl->node); impl_init(impl, NULL); impl->log = pw_log_get(); impl->resource = resource; impl->client = client; impl->client_pool = pw_impl_client_get_mempool(client); this->flags = do_register ? 0 : 1; pw_map_init(&impl->ports[0], 64, 64); pw_map_init(&impl->ports[1], 64, 64); pw_map_init(&impl->io_map, 64, 64); pw_array_init(&impl->io_areas, 64 * sizeof(struct pw_memblock*)); this->resource = resource; this->node = pw_spa_node_new(context, PW_SPA_NODE_FLAG_ASYNC | (do_register ? 0 : PW_SPA_NODE_FLAG_NO_REGISTER), (struct spa_node *)&impl->node, NULL, properties, 0); if (this->node == NULL) goto error_no_node; if (this->node->data_loop == NULL) { errno = EIO; goto error_no_node; } impl->data_loop = this->node->data_loop->loop; impl->data_system = this->node->data_loop->system; this->node->remote = true; this->flags = 0; if (resource->version < PW_VERSION_CLIENT_NODE) { pw_log_warn("detected old client version %d", resource->version); if (resource->version < 6) this->node->rt.target.activation->client_version = 0; } pw_resource_add_listener(this->resource, &impl->resource_listener, &resource_events, impl); pw_resource_add_object_listener(this->resource, &impl->object_listener, &client_node_methods, impl); this->node->port_user_data_size = sizeof(struct port); pw_impl_node_add_listener(this->node, &impl->node_listener, &node_events, impl); return this; error_no_node: res = -errno; impl_clear(impl); properties = NULL; goto error_exit_free; error_exit_free: free(impl); error_exit_cleanup: if (resource) pw_resource_destroy(resource); pw_properties_free(properties); errno = -res; return NULL; } /** Destroy a client node * \param node the client node to destroy * \memberof pw_impl_client_node */ void pw_impl_client_node_destroy(struct pw_impl_client_node *node) { pw_resource_destroy(node->resource); } client-node.h000066400000000000000000000015201511204443500307120ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/modules/module-client-node/* PipeWire */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #ifndef PIPEWIRE_CLIENT_NODE_H #define PIPEWIRE_CLIENT_NODE_H #include #include #ifdef __cplusplus extern "C" { #endif /** \class pw_impl_client_node * * PipeWire client node interface */ struct pw_impl_client_node { struct pw_impl_node *node; struct pw_resource *resource; uint32_t flags; }; struct pw_impl_client_node * pw_impl_client_node_new(struct pw_resource *resource, struct pw_properties *properties, bool do_register); void pw_impl_client_node_destroy(struct pw_impl_client_node *node); void pw_impl_client_node_registered(struct pw_impl_client_node *node, struct pw_global *global); #ifdef __cplusplus } #endif #endif /* PIPEWIRE_CLIENT_NODE_H */ protocol-native.c000066400000000000000000001054121511204443500316360ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/modules/module-client-node/* PipeWire */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #include #include #include #define MAX_DICT 1024 #define MAX_PARAMS 4096 #define MAX_PARAM_INFO 128 #define MAX_BUFFERS 64 #define MAX_METAS 16u #define MAX_DATAS 256u PW_LOG_TOPIC_EXTERN(mod_topic); #define PW_LOG_TOPIC_DEFAULT mod_topic static inline void push_item(struct spa_pod_builder *b, const struct spa_dict_item *item) { const char *str; spa_pod_builder_string(b, item->key); str = item->value; if (spa_strstartswith(str, "pointer:")) str = ""; spa_pod_builder_string(b, str); } static void push_dict(struct spa_pod_builder *b, const struct spa_dict *dict) { uint32_t i, n_items; struct spa_pod_frame f; n_items = dict ? dict->n_items : 0; spa_pod_builder_push_struct(b, &f); spa_pod_builder_int(b, n_items); for (i = 0; i < n_items; i++) push_item(b, &dict->items[i]); spa_pod_builder_pop(b, &f); } static inline int parse_item(struct spa_pod_parser *prs, struct spa_dict_item *item) { int res; if ((res = spa_pod_parser_get(prs, SPA_POD_String(&item->key), SPA_POD_String(&item->value), NULL)) < 0) return res; if (spa_strstartswith(item->value, "pointer:")) item->value = ""; return 0; } #define parse_dict(prs,d) \ do { \ uint32_t i; \ if (spa_pod_parser_get(prs, \ SPA_POD_Int(&(d)->n_items), NULL) < 0) \ return -EINVAL; \ (d)->items = NULL; \ if ((d)->n_items > 0) { \ if ((d)->n_items > MAX_DICT) \ return -ENOSPC; \ (d)->items = alloca((d)->n_items * sizeof(struct spa_dict_item)); \ for (i = 0; i < (d)->n_items; i++) { \ if (parse_item(prs, (struct spa_dict_item *) &(d)->items[i]) < 0) \ return -EINVAL; \ } \ } \ } while(0) #define parse_dict_struct(prs,f,dict) \ do { \ if (spa_pod_parser_push_struct(prs, f) < 0) \ return -EINVAL; \ parse_dict(prs, dict); \ spa_pod_parser_pop(prs, f); \ } while(0) #define parse_params(prs,n_params,params) \ do { \ uint32_t i; \ if (spa_pod_parser_get(prs, \ SPA_POD_Int(&n_params), NULL) < 0) \ return -EINVAL; \ params = NULL; \ if (n_params > 0) { \ if (n_params > MAX_PARAMS) \ return -ENOSPC; \ params = alloca(n_params * sizeof(struct spa_pod *)); \ for (i = 0; i < n_params; i++) { \ if (spa_pod_parser_get(prs, \ SPA_POD_PodObject(¶ms[i]), NULL) < 0) \ return -EINVAL; \ } \ } \ } while(0) #define parse_param_info(prs,n_params,params) \ do { \ uint32_t i; \ if (spa_pod_parser_get(prs, \ SPA_POD_Int(&(n_params)), NULL) < 0) \ return -EINVAL; \ params = NULL; \ if (n_params > 0) { \ if (n_params > MAX_PARAM_INFO) \ return -ENOSPC; \ params = alloca(n_params * sizeof(struct spa_param_info)); \ for (i = 0; i < n_params; i++) { \ if (spa_pod_parser_get(prs, \ SPA_POD_Id(&(params[i]).id), \ SPA_POD_Int(&(params[i]).flags), NULL) < 0) \ return -EINVAL; \ } \ } \ } while(0) static int client_node_marshal_add_listener(void *object, struct spa_hook *listener, const struct pw_client_node_events *events, void *data) { struct pw_proxy *proxy = object; pw_proxy_add_object_listener(proxy, listener, events, data); return 0; } static struct pw_node * client_node_marshal_get_node(void *object, uint32_t version, size_t user_data_size) { struct pw_proxy *proxy = object; struct spa_pod_builder *b; struct pw_proxy *res; uint32_t new_id; res = pw_proxy_new(object, PW_TYPE_INTERFACE_Node, version, user_data_size); if (res == NULL) return NULL; new_id = pw_proxy_get_id(res); b = pw_protocol_native_begin_proxy(proxy, PW_CLIENT_NODE_METHOD_GET_NODE, NULL); spa_pod_builder_add_struct(b, SPA_POD_Int(version), SPA_POD_Int(new_id)); pw_protocol_native_end_proxy(proxy, b); return (struct pw_node *) res; } static int client_node_marshal_update(void *object, uint32_t change_mask, uint32_t n_params, const struct spa_pod **params, const struct spa_node_info *info) { struct pw_proxy *proxy = object; struct spa_pod_builder *b; struct spa_pod_frame f[2]; uint32_t i, n_items, n_info_params; b = pw_protocol_native_begin_proxy(proxy, PW_CLIENT_NODE_METHOD_UPDATE, NULL); spa_pod_builder_push_struct(b, &f[0]); spa_pod_builder_add(b, SPA_POD_Int(change_mask), SPA_POD_Int(n_params), NULL); for (i = 0; i < n_params; i++) spa_pod_builder_add(b, SPA_POD_Pod(params[i]), NULL); if (info) { uint64_t change_mask = info->change_mask; change_mask &= SPA_NODE_CHANGE_MASK_FLAGS | SPA_NODE_CHANGE_MASK_PROPS | SPA_NODE_CHANGE_MASK_PARAMS; n_items = info->props && (change_mask & SPA_NODE_CHANGE_MASK_PROPS) ? info->props->n_items : 0; n_info_params = (change_mask & SPA_NODE_CHANGE_MASK_PARAMS) ? info->n_params : 0; spa_pod_builder_push_struct(b, &f[1]); spa_pod_builder_add(b, SPA_POD_Int(info->max_input_ports), SPA_POD_Int(info->max_output_ports), SPA_POD_Long(change_mask), SPA_POD_Long(info->flags), SPA_POD_Int(n_items), NULL); for (i = 0; i < n_items; i++) push_item(b, &info->props->items[i]); spa_pod_builder_add(b, SPA_POD_Int(n_info_params), NULL); for (i = 0; i < n_info_params; i++) { spa_pod_builder_add(b, SPA_POD_Id(info->params[i].id), SPA_POD_Int(info->params[i].flags), NULL); } spa_pod_builder_pop(b, &f[1]); } else { spa_pod_builder_add(b, SPA_POD_Pod(NULL), NULL); } spa_pod_builder_pop(b, &f[0]); return pw_protocol_native_end_proxy(proxy, b); } static int client_node_marshal_port_update(void *object, enum spa_direction direction, uint32_t port_id, uint32_t change_mask, uint32_t n_params, const struct spa_pod **params, const struct spa_port_info *info) { struct pw_proxy *proxy = object; struct spa_pod_builder *b; struct spa_pod_frame f[2]; uint32_t i, n_items; b = pw_protocol_native_begin_proxy(proxy, PW_CLIENT_NODE_METHOD_PORT_UPDATE, NULL); spa_pod_builder_push_struct(b, &f[0]); spa_pod_builder_add(b, SPA_POD_Int(direction), SPA_POD_Int(port_id), SPA_POD_Int(change_mask), SPA_POD_Int(n_params), NULL); for (i = 0; i < n_params; i++) spa_pod_builder_add(b, SPA_POD_Pod(params[i]), NULL); if (info) { uint64_t change_mask = info->change_mask; n_items = info->props ? info->props->n_items : 0; change_mask &= SPA_PORT_CHANGE_MASK_FLAGS | SPA_PORT_CHANGE_MASK_RATE | SPA_PORT_CHANGE_MASK_PROPS | SPA_PORT_CHANGE_MASK_PARAMS; spa_pod_builder_push_struct(b, &f[1]); spa_pod_builder_add(b, SPA_POD_Long(change_mask), SPA_POD_Long(info->flags), SPA_POD_Int(info->rate.num), SPA_POD_Int(info->rate.denom), SPA_POD_Int(n_items), NULL); for (i = 0; i < n_items; i++) push_item(b, &info->props->items[i]); spa_pod_builder_add(b, SPA_POD_Int(info->n_params), NULL); for (i = 0; i < info->n_params; i++) { spa_pod_builder_add(b, SPA_POD_Id(info->params[i].id), SPA_POD_Int(info->params[i].flags), NULL); } spa_pod_builder_pop(b, &f[1]); } else { spa_pod_builder_add(b, SPA_POD_Pod(NULL), NULL); } spa_pod_builder_pop(b, &f[0]); return pw_protocol_native_end_proxy(proxy, b); } static int client_node_marshal_set_active(void *object, bool active) { struct pw_proxy *proxy = object; struct spa_pod_builder *b; b = pw_protocol_native_begin_proxy(proxy, PW_CLIENT_NODE_METHOD_SET_ACTIVE, NULL); spa_pod_builder_add_struct(b, SPA_POD_Bool(active)); return pw_protocol_native_end_proxy(proxy, b); } static int client_node_marshal_event_method(void *object, const struct spa_event *event) { struct pw_proxy *proxy = object; struct spa_pod_builder *b; b = pw_protocol_native_begin_proxy(proxy, PW_CLIENT_NODE_METHOD_EVENT, NULL); spa_pod_builder_add_struct(b, SPA_POD_Pod(event)); return pw_protocol_native_end_proxy(proxy, b); } static int client_node_marshal_port_buffers(void *object, enum spa_direction direction, uint32_t port_id, uint32_t mix_id, uint32_t n_buffers, struct spa_buffer **buffers) { struct pw_proxy *proxy = object; struct spa_pod_builder *b; struct spa_pod_frame f[2]; uint32_t i, j; b = pw_protocol_native_begin_proxy(proxy, PW_CLIENT_NODE_METHOD_PORT_BUFFERS, NULL); spa_pod_builder_push_struct(b, &f[0]); spa_pod_builder_add(b, SPA_POD_Int(direction), SPA_POD_Int(port_id), SPA_POD_Int(mix_id), SPA_POD_Int(n_buffers), NULL); for (i = 0; i < n_buffers; i++) { struct spa_buffer *buf = buffers[i]; spa_pod_builder_add(b, SPA_POD_Int(buf->n_datas), NULL); for (j = 0; j < buf->n_datas; j++) { struct spa_data *d = &buf->datas[j]; spa_pod_builder_add(b, SPA_POD_Id(d->type), SPA_POD_Fd(pw_protocol_native_add_proxy_fd(proxy, d->fd)), SPA_POD_Int(d->flags), SPA_POD_Int(d->mapoffset), SPA_POD_Int(d->maxsize), NULL); } } spa_pod_builder_pop(b, &f[0]); return pw_protocol_native_end_proxy(proxy, b); } static int client_node_demarshal_transport(void *data, const struct pw_protocol_native_message *msg) { struct pw_proxy *proxy = data; struct spa_pod_parser prs; uint32_t mem_id, offset, sz; int64_t ridx, widx; int readfd, writefd; spa_pod_parser_init(&prs, msg->data, msg->size); if (spa_pod_parser_get_struct(&prs, SPA_POD_Fd(&ridx), SPA_POD_Fd(&widx), SPA_POD_Int(&mem_id), SPA_POD_Int(&offset), SPA_POD_Int(&sz)) < 0) return -EINVAL; readfd = pw_protocol_native_get_proxy_fd(proxy, ridx); writefd = pw_protocol_native_get_proxy_fd(proxy, widx); if (readfd < 0 || writefd < 0) return -EINVAL; pw_proxy_notify(proxy, struct pw_client_node_events, transport, 0, readfd, writefd, mem_id, offset, sz); return 0; } static int client_node_demarshal_set_param(void *data, const struct pw_protocol_native_message *msg) { struct pw_proxy *proxy = data; struct spa_pod_parser prs; uint32_t id, flags; const struct spa_pod *param = NULL; spa_pod_parser_init(&prs, msg->data, msg->size); if (spa_pod_parser_get_struct(&prs, SPA_POD_Id(&id), SPA_POD_Int(&flags), SPA_POD_PodObject(¶m)) < 0) return -EINVAL; pw_proxy_notify(proxy, struct pw_client_node_events, set_param, 0, id, flags, param); return 0; } static int client_node_demarshal_event_event(void *data, const struct pw_protocol_native_message *msg) { struct pw_proxy *proxy = data; struct spa_pod_parser prs; const struct spa_event *event; spa_pod_parser_init(&prs, msg->data, msg->size); if (spa_pod_parser_get_struct(&prs, SPA_POD_PodObject(&event)) < 0) return -EINVAL; if (event == NULL) return -EINVAL; pw_proxy_notify(proxy, struct pw_client_node_events, event, 0, event); return 0; } static int client_node_demarshal_command(void *data, const struct pw_protocol_native_message *msg) { struct pw_proxy *proxy = data; struct spa_pod_parser prs; const struct spa_command *command; spa_pod_parser_init(&prs, msg->data, msg->size); if (spa_pod_parser_get_struct(&prs, SPA_POD_PodObject(&command)) < 0) return -EINVAL; if (command == NULL) return -EINVAL; pw_proxy_notify(proxy, struct pw_client_node_events, command, 0, command); return 0; } static int client_node_demarshal_add_port(void *data, const struct pw_protocol_native_message *msg) { struct pw_proxy *proxy = data; struct spa_pod_parser prs; struct spa_pod_frame f[2]; int32_t direction, port_id; struct spa_dict props = SPA_DICT_INIT(NULL, 0); spa_pod_parser_init(&prs, msg->data, msg->size); if (spa_pod_parser_push_struct(&prs, &f[0]) < 0) return -EINVAL; if (spa_pod_parser_get(&prs, SPA_POD_Int(&direction), SPA_POD_Int(&port_id), NULL) < 0) return -EINVAL; parse_dict_struct(&prs, &f[1], &props); pw_proxy_notify(proxy, struct pw_client_node_events, add_port, 0, direction, port_id, props.n_items ? &props : NULL); return 0; } static int client_node_demarshal_remove_port(void *data, const struct pw_protocol_native_message *msg) { struct pw_proxy *proxy = data; struct spa_pod_parser prs; int32_t direction, port_id; spa_pod_parser_init(&prs, msg->data, msg->size); if (spa_pod_parser_get_struct(&prs, SPA_POD_Int(&direction), SPA_POD_Int(&port_id)) < 0) return -EINVAL; pw_proxy_notify(proxy, struct pw_client_node_events, remove_port, 0, direction, port_id); return 0; } static int client_node_demarshal_port_set_param(void *data, const struct pw_protocol_native_message *msg) { struct pw_proxy *proxy = data; struct spa_pod_parser prs; uint32_t direction, port_id, id, flags; const struct spa_pod *param = NULL; spa_pod_parser_init(&prs, msg->data, msg->size); if (spa_pod_parser_get_struct(&prs, SPA_POD_Int(&direction), SPA_POD_Int(&port_id), SPA_POD_Id(&id), SPA_POD_Int(&flags), SPA_POD_PodObject(¶m)) < 0) return -EINVAL; pw_proxy_notify(proxy, struct pw_client_node_events, port_set_param, 0, direction, port_id, id, flags, param); return 0; } static int client_node_demarshal_port_use_buffers(void *data, const struct pw_protocol_native_message *msg) { struct pw_proxy *proxy = data; struct spa_pod_parser prs; struct spa_pod_frame f; uint32_t direction, port_id, mix_id, flags, n_buffers, data_id; struct pw_client_node_buffer *buffers; uint32_t i, j; spa_pod_parser_init(&prs, msg->data, msg->size); if (spa_pod_parser_push_struct(&prs, &f) < 0 || spa_pod_parser_get(&prs, SPA_POD_Int(&direction), SPA_POD_Int(&port_id), SPA_POD_Int(&mix_id), SPA_POD_Int(&flags), SPA_POD_Int(&n_buffers), NULL) < 0) return -EINVAL; if (n_buffers > MAX_BUFFERS) return -ENOSPC; buffers = alloca(sizeof(struct pw_client_node_buffer) * n_buffers); for (i = 0; i < n_buffers; i++) { struct spa_buffer *buf = buffers[i].buffer = alloca(sizeof(struct spa_buffer)); if (spa_pod_parser_get(&prs, SPA_POD_Int(&buffers[i].mem_id), SPA_POD_Int(&buffers[i].offset), SPA_POD_Int(&buffers[i].size), SPA_POD_Int(&buf->n_metas), NULL) < 0) return -EINVAL; if (buf->n_metas > MAX_METAS) return -ENOSPC; buf->metas = alloca(sizeof(struct spa_meta) * buf->n_metas); for (j = 0; j < buf->n_metas; j++) { struct spa_meta *m = &buf->metas[j]; if (spa_pod_parser_get(&prs, SPA_POD_Id(&m->type), SPA_POD_Int(&m->size), NULL) < 0) return -EINVAL; m->data = NULL; } if (spa_pod_parser_get(&prs, SPA_POD_Int(&buf->n_datas), NULL) < 0) return -EINVAL; if (buf->n_datas > MAX_DATAS) return -ENOSPC; buf->datas = alloca(sizeof(struct spa_data) * buf->n_datas); for (j = 0; j < buf->n_datas; j++) { struct spa_data *d = &buf->datas[j]; if (spa_pod_parser_get(&prs, SPA_POD_Id(&d->type), SPA_POD_Int(&data_id), SPA_POD_Int(&d->flags), SPA_POD_Int(&d->mapoffset), SPA_POD_Int(&d->maxsize), NULL) < 0) return -EINVAL; d->fd = -1; d->data = SPA_UINT32_TO_PTR(data_id); d->chunk = NULL; } } pw_proxy_notify(proxy, struct pw_client_node_events, port_use_buffers, 0, direction, port_id, mix_id, flags, n_buffers, buffers); return 0; } static int client_node_demarshal_port_set_io(void *data, const struct pw_protocol_native_message *msg) { struct pw_proxy *proxy = data; struct spa_pod_parser prs; uint32_t direction, port_id, mix_id, id, memid, off, sz; spa_pod_parser_init(&prs, msg->data, msg->size); if (spa_pod_parser_get_struct(&prs, SPA_POD_Int(&direction), SPA_POD_Int(&port_id), SPA_POD_Int(&mix_id), SPA_POD_Id(&id), SPA_POD_Int(&memid), SPA_POD_Int(&off), SPA_POD_Int(&sz)) < 0) return -EINVAL; pw_proxy_notify(proxy, struct pw_client_node_events, port_set_io, 0, direction, port_id, mix_id, id, memid, off, sz); return 0; } static int client_node_demarshal_set_activation(void *data, const struct pw_protocol_native_message *msg) { struct pw_proxy *proxy = data; struct spa_pod_parser prs; uint32_t node_id, memid, off, sz; int64_t sigidx; int signalfd; spa_pod_parser_init(&prs, msg->data, msg->size); if (spa_pod_parser_get_struct(&prs, SPA_POD_Int(&node_id), SPA_POD_Fd(&sigidx), SPA_POD_Int(&memid), SPA_POD_Int(&off), SPA_POD_Int(&sz)) < 0) return -EINVAL; signalfd = pw_protocol_native_get_proxy_fd(proxy, sigidx); pw_proxy_notify(proxy, struct pw_client_node_events, set_activation, 0, node_id, signalfd, memid, off, sz); return 0; } static int client_node_demarshal_port_set_mix_info(void *data, const struct pw_protocol_native_message *msg) { struct pw_proxy *proxy = data; struct spa_pod_parser prs; uint32_t direction, port_id, mix_id, peer_id; struct spa_pod_frame f[2]; struct spa_dict props = SPA_DICT_INIT(NULL, 0); spa_pod_parser_init(&prs, msg->data, msg->size); if (spa_pod_parser_push_struct(&prs, &f[0]) < 0 || spa_pod_parser_get(&prs, SPA_POD_Int(&direction), SPA_POD_Int(&port_id), SPA_POD_Int(&mix_id), SPA_POD_Int(&peer_id), NULL) < 0) return -EINVAL; parse_dict_struct(&prs, &f[1], &props); pw_proxy_notify(proxy, struct pw_client_node_events, port_set_mix_info, 1, direction, port_id, mix_id, peer_id, &props); return 0; } static int client_node_demarshal_set_io(void *data, const struct pw_protocol_native_message *msg) { struct pw_proxy *proxy = data; struct spa_pod_parser prs; uint32_t id, memid, off, sz; spa_pod_parser_init(&prs, msg->data, msg->size); if (spa_pod_parser_get_struct(&prs, SPA_POD_Id(&id), SPA_POD_Int(&memid), SPA_POD_Int(&off), SPA_POD_Int(&sz)) < 0) return -EINVAL; pw_proxy_notify(proxy, struct pw_client_node_events, set_io, 0, id, memid, off, sz); return 0; } static int client_node_marshal_transport(void *data, int readfd, int writefd, uint32_t mem_id, uint32_t offset, uint32_t size) { struct pw_protocol_native_message *msg; struct pw_resource *resource = data; struct spa_pod_builder *b; b = pw_protocol_native_begin_resource(resource, PW_CLIENT_NODE_EVENT_TRANSPORT, &msg); spa_pod_builder_add_struct(b, SPA_POD_Fd(pw_protocol_native_add_resource_fd(resource, readfd)), SPA_POD_Fd(pw_protocol_native_add_resource_fd(resource, writefd)), SPA_POD_Int(mem_id), SPA_POD_Int(offset), SPA_POD_Int(size)); return pw_protocol_native_end_resource(resource, b); } static int client_node_marshal_set_param(void *data, uint32_t id, uint32_t flags, const struct spa_pod *param) { struct pw_resource *resource = data; struct spa_pod_builder *b; b = pw_protocol_native_begin_resource(resource, PW_CLIENT_NODE_EVENT_SET_PARAM, NULL); spa_pod_builder_add_struct(b, SPA_POD_Id(id), SPA_POD_Int(flags), SPA_POD_Pod(param)); return pw_protocol_native_end_resource(resource, b); } static int client_node_marshal_event_event(void *data, const struct spa_event *event) { struct pw_resource *resource = data; struct spa_pod_builder *b; b = pw_protocol_native_begin_resource(resource, PW_CLIENT_NODE_EVENT_EVENT, NULL); spa_pod_builder_add_struct(b, SPA_POD_Pod(event)); return pw_protocol_native_end_resource(resource, b); } static int client_node_marshal_command(void *data, const struct spa_command *command) { struct pw_resource *resource = data; struct spa_pod_builder *b; b = pw_protocol_native_begin_resource(resource, PW_CLIENT_NODE_EVENT_COMMAND, NULL); spa_pod_builder_add_struct(b, SPA_POD_Pod(command)); return pw_protocol_native_end_resource(resource, b); } static int client_node_marshal_add_port(void *data, enum spa_direction direction, uint32_t port_id, const struct spa_dict *props) { struct pw_resource *resource = data; struct spa_pod_builder *b; struct spa_pod_frame f; b = pw_protocol_native_begin_resource(resource, PW_CLIENT_NODE_EVENT_ADD_PORT, NULL); spa_pod_builder_push_struct(b, &f); spa_pod_builder_add(b, SPA_POD_Int(direction), SPA_POD_Int(port_id), NULL); push_dict(b, props); spa_pod_builder_pop(b, &f); return pw_protocol_native_end_resource(resource, b); } static int client_node_marshal_remove_port(void *data, enum spa_direction direction, uint32_t port_id) { struct pw_resource *resource = data; struct spa_pod_builder *b; b = pw_protocol_native_begin_resource(resource, PW_CLIENT_NODE_EVENT_REMOVE_PORT, NULL); spa_pod_builder_add_struct(b, SPA_POD_Int(direction), SPA_POD_Int(port_id)); return pw_protocol_native_end_resource(resource, b); } static int client_node_marshal_port_set_param(void *data, enum spa_direction direction, uint32_t port_id, uint32_t id, uint32_t flags, const struct spa_pod *param) { struct pw_resource *resource = data; struct spa_pod_builder *b; b = pw_protocol_native_begin_resource(resource, PW_CLIENT_NODE_EVENT_PORT_SET_PARAM, NULL); spa_pod_builder_add_struct(b, SPA_POD_Int(direction), SPA_POD_Int(port_id), SPA_POD_Id(id), SPA_POD_Int(flags), SPA_POD_Pod(param)); return pw_protocol_native_end_resource(resource, b); } static int client_node_marshal_port_use_buffers(void *data, enum spa_direction direction, uint32_t port_id, uint32_t mix_id, uint32_t flags, uint32_t n_buffers, struct pw_client_node_buffer *buffers) { struct pw_resource *resource = data; struct spa_pod_builder *b; struct spa_pod_frame f; uint32_t i, j; b = pw_protocol_native_begin_resource(resource, PW_CLIENT_NODE_EVENT_PORT_USE_BUFFERS, NULL); spa_pod_builder_push_struct(b, &f); spa_pod_builder_add(b, SPA_POD_Int(direction), SPA_POD_Int(port_id), SPA_POD_Int(mix_id), SPA_POD_Int(flags), SPA_POD_Int(n_buffers), NULL); for (i = 0; i < n_buffers; i++) { struct spa_buffer *buf = buffers[i].buffer; spa_pod_builder_add(b, SPA_POD_Int(buffers[i].mem_id), SPA_POD_Int(buffers[i].offset), SPA_POD_Int(buffers[i].size), SPA_POD_Int(buf->n_metas), NULL); for (j = 0; j < buf->n_metas; j++) { struct spa_meta *m = &buf->metas[j]; spa_pod_builder_add(b, SPA_POD_Id(m->type), SPA_POD_Int(m->size), NULL); } spa_pod_builder_add(b, SPA_POD_Int(buf->n_datas), NULL); for (j = 0; j < buf->n_datas; j++) { struct spa_data *d = &buf->datas[j]; spa_pod_builder_add(b, SPA_POD_Id(d->type), SPA_POD_Int(SPA_PTR_TO_UINT32(d->data)), SPA_POD_Int(d->flags), SPA_POD_Int(d->mapoffset), SPA_POD_Int(d->maxsize), NULL); } } spa_pod_builder_pop(b, &f); return pw_protocol_native_end_resource(resource, b); } static int client_node_marshal_port_set_io(void *data, uint32_t direction, uint32_t port_id, uint32_t mix_id, uint32_t id, uint32_t memid, uint32_t offset, uint32_t size) { struct pw_resource *resource = data; struct spa_pod_builder *b; b = pw_protocol_native_begin_resource(resource, PW_CLIENT_NODE_EVENT_PORT_SET_IO, NULL); spa_pod_builder_add_struct(b, SPA_POD_Int(direction), SPA_POD_Int(port_id), SPA_POD_Int(mix_id), SPA_POD_Id(id), SPA_POD_Int(memid), SPA_POD_Int(offset), SPA_POD_Int(size)); return pw_protocol_native_end_resource(resource, b); } static int client_node_marshal_set_activation(void *data, uint32_t node_id, int signalfd, uint32_t memid, uint32_t offset, uint32_t size) { struct pw_protocol_native_message *msg; struct pw_resource *resource = data; struct spa_pod_builder *b; b = pw_protocol_native_begin_resource(resource, PW_CLIENT_NODE_EVENT_SET_ACTIVATION, &msg); spa_pod_builder_add_struct(b, SPA_POD_Int(node_id), SPA_POD_Fd(pw_protocol_native_add_resource_fd(resource, signalfd)), SPA_POD_Int(memid), SPA_POD_Int(offset), SPA_POD_Int(size)); return pw_protocol_native_end_resource(resource, b); } static int client_node_marshal_set_io(void *data, uint32_t id, uint32_t memid, uint32_t offset, uint32_t size) { struct pw_resource *resource = data; struct spa_pod_builder *b; b = pw_protocol_native_begin_resource(resource, PW_CLIENT_NODE_EVENT_SET_IO, NULL); spa_pod_builder_add_struct(b, SPA_POD_Id(id), SPA_POD_Int(memid), SPA_POD_Int(offset), SPA_POD_Int(size)); return pw_protocol_native_end_resource(resource, b); } static int client_node_marshal_port_set_mix_info(void *data, uint32_t direction, uint32_t port_id, uint32_t mix_id, uint32_t peer_id, const struct spa_dict *props) { struct pw_resource *resource = data; struct spa_pod_builder *b; struct spa_pod_frame f; b = pw_protocol_native_begin_resource(resource, PW_CLIENT_NODE_EVENT_PORT_SET_MIX_INFO, NULL); spa_pod_builder_push_struct(b, &f); spa_pod_builder_add(b, SPA_POD_Int(direction), SPA_POD_Int(port_id), SPA_POD_Int(mix_id), SPA_POD_Int(peer_id), NULL); push_dict(b, props); spa_pod_builder_pop(b, &f); return pw_protocol_native_end_resource(resource, b); } static int client_node_demarshal_get_node(void *object, const struct pw_protocol_native_message *msg) { struct pw_resource *resource = object; struct spa_pod_parser prs; int32_t version, new_id; spa_pod_parser_init(&prs, msg->data, msg->size); if (spa_pod_parser_get_struct(&prs, SPA_POD_Int(&version), SPA_POD_Int(&new_id)) < 0) return -EINVAL; return pw_resource_notify(resource, struct pw_client_node_methods, get_node, 0, version, new_id); } static int client_node_demarshal_update(void *object, const struct pw_protocol_native_message *msg) { struct pw_resource *resource = object; struct spa_pod_parser prs; struct spa_pod_frame f[2]; uint32_t change_mask, n_params; const struct spa_pod **params = NULL; struct spa_node_info info = SPA_NODE_INFO_INIT(), *infop = NULL; struct spa_pod *ipod; struct spa_dict props = SPA_DICT_INIT(NULL, 0); spa_pod_parser_init(&prs, msg->data, msg->size); if (spa_pod_parser_push_struct(&prs, &f[0]) < 0 || spa_pod_parser_get(&prs, SPA_POD_Int(&change_mask), NULL) < 0) return -EINVAL; parse_params(&prs, n_params, params); if (spa_pod_parser_get(&prs, SPA_POD_PodStruct(&ipod), NULL) < 0) return -EINVAL; if (ipod) { struct spa_pod_parser p2; struct spa_pod_frame f2; infop = &info; spa_pod_parser_pod(&p2, ipod); if (spa_pod_parser_push_struct(&p2, &f2) < 0 || spa_pod_parser_get(&p2, SPA_POD_Int(&info.max_input_ports), SPA_POD_Int(&info.max_output_ports), SPA_POD_Long(&info.change_mask), SPA_POD_Long(&info.flags), NULL) < 0) return -EINVAL; info.change_mask &= SPA_NODE_CHANGE_MASK_FLAGS | SPA_NODE_CHANGE_MASK_PROPS | SPA_NODE_CHANGE_MASK_PARAMS; parse_dict(&p2, &props); if (props.n_items > 0) info.props = &props; parse_param_info(&p2, info.n_params, info.params); } pw_resource_notify(resource, struct pw_client_node_methods, update, 0, change_mask, n_params, params, infop); return 0; } static int client_node_demarshal_port_update(void *object, const struct pw_protocol_native_message *msg) { struct pw_resource *resource = object; struct spa_pod_parser prs; struct spa_pod_frame f; uint32_t direction, port_id, change_mask, n_params; const struct spa_pod **params = NULL; struct spa_port_info info = SPA_PORT_INFO_INIT(), *infop = NULL; struct spa_pod *ipod; struct spa_dict props = SPA_DICT_INIT(NULL, 0); spa_pod_parser_init(&prs, msg->data, msg->size); if (spa_pod_parser_push_struct(&prs, &f) < 0 || spa_pod_parser_get(&prs, SPA_POD_Int(&direction), SPA_POD_Int(&port_id), SPA_POD_Int(&change_mask), NULL) < 0) return -EINVAL; parse_params(&prs, n_params, params); if (spa_pod_parser_get(&prs, SPA_POD_PodStruct(&ipod), NULL) < 0) return -EINVAL; if (ipod) { struct spa_pod_parser p2; struct spa_pod_frame f2; infop = &info; spa_pod_parser_pod(&p2, ipod); if (spa_pod_parser_push_struct(&p2, &f2) < 0 || spa_pod_parser_get(&p2, SPA_POD_Long(&info.change_mask), SPA_POD_Long(&info.flags), SPA_POD_Int(&info.rate.num), SPA_POD_Int(&info.rate.denom), NULL) < 0) return -EINVAL; info.change_mask &= SPA_PORT_CHANGE_MASK_FLAGS | SPA_PORT_CHANGE_MASK_RATE | SPA_PORT_CHANGE_MASK_PROPS | SPA_PORT_CHANGE_MASK_PARAMS; parse_dict(&p2, &props); if (props.n_items > 0) info.props = &props; parse_param_info(&p2, info.n_params, info.params); } pw_resource_notify(resource, struct pw_client_node_methods, port_update, 0, direction, port_id, change_mask, n_params, params, infop); return 0; } static int client_node_demarshal_set_active(void *object, const struct pw_protocol_native_message *msg) { struct pw_resource *resource = object; struct spa_pod_parser prs; bool active; spa_pod_parser_init(&prs, msg->data, msg->size); if (spa_pod_parser_get_struct(&prs, SPA_POD_Bool(&active)) < 0) return -EINVAL; pw_resource_notify(resource, struct pw_client_node_methods, set_active, 0, active); return 0; } static int client_node_demarshal_event_method(void *object, const struct pw_protocol_native_message *msg) { struct pw_resource *resource = object; struct spa_pod_parser prs; const struct spa_event *event; spa_pod_parser_init(&prs, msg->data, msg->size); if (spa_pod_parser_get_struct(&prs, SPA_POD_PodObject(&event)) < 0) return -EINVAL; if (event == NULL) return -EINVAL; pw_resource_notify(resource, struct pw_client_node_methods, event, 0, event); return 0; } static int client_node_demarshal_port_buffers(void *object, const struct pw_protocol_native_message *msg) { struct pw_resource *resource = object; struct spa_pod_parser prs; struct spa_pod_frame f; uint32_t i, j, direction, port_id, mix_id, n_buffers; int64_t data_fd; struct spa_buffer **buffers = NULL; spa_pod_parser_init(&prs, msg->data, msg->size); if (spa_pod_parser_push_struct(&prs, &f) < 0 || spa_pod_parser_get(&prs, SPA_POD_Int(&direction), SPA_POD_Int(&port_id), SPA_POD_Int(&mix_id), SPA_POD_Int(&n_buffers), NULL) < 0) return -EINVAL; if (n_buffers > MAX_BUFFERS) return -ENOSPC; buffers = alloca(sizeof(struct spa_buffer*) * n_buffers); for (i = 0; i < n_buffers; i++) { struct spa_buffer *buf = buffers[i] = alloca(sizeof(struct spa_buffer)); spa_zero(*buf); if (spa_pod_parser_get(&prs, SPA_POD_Int(&buf->n_datas), NULL) < 0) return -EINVAL; if (buf->n_datas > MAX_DATAS) return -ENOSPC; buf->datas = alloca(sizeof(struct spa_data) * buf->n_datas); for (j = 0; j < buf->n_datas; j++) { struct spa_data *d = &buf->datas[j]; if (spa_pod_parser_get(&prs, SPA_POD_Id(&d->type), SPA_POD_Fd(&data_fd), SPA_POD_Int(&d->flags), SPA_POD_Int(&d->mapoffset), SPA_POD_Int(&d->maxsize), NULL) < 0) return -EINVAL; d->fd = pw_protocol_native_get_resource_fd(resource, data_fd); } } pw_resource_notify(resource, struct pw_client_node_methods, port_buffers, 0, direction, port_id, mix_id, n_buffers, buffers); return 0; } static const struct pw_client_node_methods pw_protocol_native_client_node_method_marshal = { PW_VERSION_CLIENT_NODE_METHODS, .add_listener = &client_node_marshal_add_listener, .get_node = &client_node_marshal_get_node, .update = &client_node_marshal_update, .port_update = &client_node_marshal_port_update, .set_active = &client_node_marshal_set_active, .event = &client_node_marshal_event_method, .port_buffers = &client_node_marshal_port_buffers }; static const struct pw_protocol_native_demarshal pw_protocol_native_client_node_method_demarshal[PW_CLIENT_NODE_METHOD_NUM] = { [PW_CLIENT_NODE_METHOD_ADD_LISTENER] = { NULL, 0 }, [PW_CLIENT_NODE_METHOD_GET_NODE] = { &client_node_demarshal_get_node, 0 }, [PW_CLIENT_NODE_METHOD_UPDATE] = { &client_node_demarshal_update, 0 }, [PW_CLIENT_NODE_METHOD_PORT_UPDATE] = { &client_node_demarshal_port_update, 0 }, [PW_CLIENT_NODE_METHOD_SET_ACTIVE] = { &client_node_demarshal_set_active, 0 }, [PW_CLIENT_NODE_METHOD_EVENT] = { &client_node_demarshal_event_method, 0 }, [PW_CLIENT_NODE_METHOD_PORT_BUFFERS] = { &client_node_demarshal_port_buffers, 0 } }; static const struct pw_client_node_events pw_protocol_native_client_node_event_marshal = { PW_VERSION_CLIENT_NODE_EVENTS, .transport = &client_node_marshal_transport, .set_param = &client_node_marshal_set_param, .set_io = &client_node_marshal_set_io, .event = &client_node_marshal_event_event, .command = &client_node_marshal_command, .add_port = &client_node_marshal_add_port, .remove_port = &client_node_marshal_remove_port, .port_set_param = &client_node_marshal_port_set_param, .port_use_buffers = &client_node_marshal_port_use_buffers, .port_set_io = &client_node_marshal_port_set_io, .set_activation = &client_node_marshal_set_activation, .port_set_mix_info = &client_node_marshal_port_set_mix_info, }; static const struct pw_protocol_native_demarshal pw_protocol_native_client_node_event_demarshal[PW_CLIENT_NODE_EVENT_NUM] = { [PW_CLIENT_NODE_EVENT_TRANSPORT] = { &client_node_demarshal_transport, 0 }, [PW_CLIENT_NODE_EVENT_SET_PARAM] = { &client_node_demarshal_set_param, 0 }, [PW_CLIENT_NODE_EVENT_SET_IO] = { &client_node_demarshal_set_io, 0 }, [PW_CLIENT_NODE_EVENT_EVENT] = { &client_node_demarshal_event_event, 0 }, [PW_CLIENT_NODE_EVENT_COMMAND] = { &client_node_demarshal_command, 0 }, [PW_CLIENT_NODE_EVENT_ADD_PORT] = { &client_node_demarshal_add_port, 0 }, [PW_CLIENT_NODE_EVENT_REMOVE_PORT] = { &client_node_demarshal_remove_port, 0 }, [PW_CLIENT_NODE_EVENT_PORT_SET_PARAM] = { &client_node_demarshal_port_set_param, 0 }, [PW_CLIENT_NODE_EVENT_PORT_USE_BUFFERS] = { &client_node_demarshal_port_use_buffers, 0 }, [PW_CLIENT_NODE_EVENT_PORT_SET_IO] = { &client_node_demarshal_port_set_io, 0 }, [PW_CLIENT_NODE_EVENT_SET_ACTIVATION] = { &client_node_demarshal_set_activation, 0 }, [PW_CLIENT_NODE_EVENT_PORT_SET_MIX_INFO] = { &client_node_demarshal_port_set_mix_info, 0 } }; static const struct pw_protocol_marshal pw_protocol_native_client_node_marshal = { PW_TYPE_INTERFACE_ClientNode, PW_VERSION_CLIENT_NODE, 0, PW_CLIENT_NODE_METHOD_NUM, PW_CLIENT_NODE_EVENT_NUM, .client_marshal = &pw_protocol_native_client_node_method_marshal, .server_demarshal = &pw_protocol_native_client_node_method_demarshal, .server_marshal = &pw_protocol_native_client_node_event_marshal, .client_demarshal = pw_protocol_native_client_node_event_demarshal, }; struct pw_protocol *pw_protocol_native_ext_client_node_init(struct pw_context *context) { struct pw_protocol *protocol; protocol = pw_context_find_protocol(context, PW_TYPE_INFO_PROTOCOL_Native); if (protocol == NULL) return NULL; pw_protocol_add_marshal(protocol, &pw_protocol_native_client_node_marshal); return protocol; } remote-node.c000066400000000000000000001034341511204443500307310ustar00rootroot00000000000000pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/modules/module-client-node/* PipeWire */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include #include #include #include #include #include #include #include #include #include #include #include #include "pipewire/pipewire.h" #include "pipewire/private.h" #include "pipewire/extensions/protocol-native.h" #include "pipewire/extensions/client-node.h" #define MAX_BUFFERS 64 PW_LOG_TOPIC_EXTERN(mod_topic); #define PW_LOG_TOPIC_DEFAULT mod_topic /** \cond */ static bool mlock_warned = false; struct buffer { uint32_t id; struct spa_buffer *buf; struct pw_memmap *mem; }; struct mix { struct spa_list link; struct pw_impl_port *port; struct pw_impl_port_mix mix; struct pw_array buffers; }; struct node_data { struct pw_context *context; struct pw_loop *data_loop; struct spa_system *data_system; struct pw_mempool *pool; uint32_t remote_id; int rtwritefd; struct pw_memmap *activation; struct spa_list mix[2]; struct spa_list free_mix; struct pw_impl_node *node; struct spa_hook node_listener; struct spa_hook node_rt_listener; unsigned int do_free:1; unsigned int have_transport:1; unsigned int allow_mlock:1; unsigned int warn_mlock:1; struct pw_client_node *client_node; struct spa_hook client_node_listener; struct spa_hook proxy_client_node_listener; struct spa_list links; }; struct link { struct spa_list link; struct node_data *data; struct pw_memmap *map; struct pw_node_target target; uint32_t node_id; }; /** \endcond */ static struct link *find_activation(struct spa_list *links, uint32_t node_id) { struct link *l; spa_list_for_each(l, links, link) { if (l->target.id == node_id) return l; } return NULL; } static void clear_link(struct node_data *data, struct link *link) { pw_log_debug("link %p", link); pw_impl_node_remove_target(data->node, &link->target); pw_memmap_free(link->map); spa_system_close(link->target.system, link->target.fd); spa_list_remove(&link->link); free(link); } static void clean_transport(struct node_data *data) { struct link *l; uint32_t tag[5] = { data->remote_id, }; struct pw_impl_node *node = data->node; struct pw_memmap *mm; if (!data->have_transport) return; spa_list_consume(l, &data->links, link) clear_link(data, l); while ((mm = pw_mempool_find_tag(data->pool, tag, sizeof(uint32_t))) != NULL) { if (mm->tag[1] == SPA_ID_INVALID) spa_node_set_io(node->node, mm->tag[2], NULL, 0); pw_memmap_free(mm); } pw_memmap_free(data->activation); node->rt.target.activation = node->activation->map->ptr; spa_system_close(data->data_system, data->rtwritefd); data->have_transport = false; } static void mix_init(struct mix *mix, struct pw_impl_port *port, uint32_t mix_id, uint32_t peer_id) { pw_log_debug("port %p: mix init %d.%d", port, port->port_id, mix_id); mix->port = port; mix->mix.id = mix_id; mix->mix.peer_id = peer_id; if (mix_id != SPA_ID_INVALID) pw_impl_port_init_mix(port, &mix->mix); pw_array_init(&mix->buffers, 32); pw_array_ensure_size(&mix->buffers, sizeof(struct buffer) * 64); } static struct mix *find_mix(struct node_data *data, enum spa_direction direction, uint32_t port_id, uint32_t mix_id) { struct mix *mix; spa_list_for_each(mix, &data->mix[direction], link) { if (mix->port->port_id == port_id && mix->mix.id == mix_id) { pw_log_debug("port %p: found mix %d:%d.%d", mix->port, direction, port_id, mix_id); return mix; } } return NULL; } static struct mix *create_mix(struct node_data *data, struct pw_impl_port *port, uint32_t mix_id, uint32_t peer_id) { struct mix *mix; if (spa_list_is_empty(&data->free_mix)) { if ((mix = calloc(1, sizeof(*mix))) == NULL) return NULL; } else { mix = spa_list_first(&data->free_mix, struct mix, link); spa_list_remove(&mix->link); } mix_init(mix, port, mix_id, peer_id); spa_list_append(&data->mix[port->direction], &mix->link); return mix; } static int client_node_transport(void *_data, int readfd, int writefd, uint32_t mem_id, uint32_t offset, uint32_t size) { struct node_data *data = _data; struct pw_impl_node *node = data->node; struct pw_proxy *proxy = (struct pw_proxy*)data->client_node; clean_transport(data); data->activation = pw_mempool_map_id(data->pool, mem_id, PW_MEMMAP_FLAG_READWRITE, offset, size, NULL); if (data->activation == NULL) { pw_log_warn("remote-node %p: can't map activation: %m", proxy); return -errno; } node->rt.target.activation = data->activation->ptr; pw_impl_node_set_io(node, SPA_IO_Clock, &node->rt.target.activation->position.clock, sizeof(struct spa_io_clock)); pw_impl_node_set_io(node, SPA_IO_Position, &node->rt.target.activation->position, sizeof(struct spa_io_position)); pw_log_debug("remote-node %p: fds:%d %d node:%u activation:%p", proxy, readfd, writefd, data->remote_id, data->activation->ptr); data->rtwritefd = writefd; spa_system_close(node->rt.target.system, node->source.fd); node->rt.target.fd = node->source.fd = readfd; node->rt.target.activation->client_version = PW_VERSION_NODE_ACTIVATION; data->have_transport = true; if (node->active) pw_client_node_set_active(data->client_node, true); return 0; } static int add_node_update(struct node_data *data, uint32_t change_mask, uint32_t info_mask) { struct pw_impl_node *node = data->node; struct spa_node_info ni = SPA_NODE_INFO_INIT(); uint32_t n_params = 0; struct spa_pod **params = NULL; int res; if (change_mask & PW_CLIENT_NODE_UPDATE_PARAMS) { uint32_t i, idx, id; uint8_t buf[4096]; struct spa_pod_dynamic_builder b; for (i = 0; i < node->info.n_params; i++) { struct spa_pod *param; id = node->info.params[i].id; if (id == SPA_PARAM_Invalid) continue; for (idx = 0;;) { spa_pod_dynamic_builder_init(&b, buf, sizeof(buf), 4096); res = spa_node_enum_params_sync(node->node, id, &idx, NULL, ¶m, &b.b); if (res == 1) { void *p; p = pw_reallocarray(params, n_params + 1, sizeof(struct spa_pod *)); if (p == NULL) { res = -errno; pw_log_error("realloc failed: %m"); } else { params = p; params[n_params++] = spa_pod_copy(param); } } spa_pod_dynamic_builder_clean(&b); if (res != 1) break; } } } if (change_mask & PW_CLIENT_NODE_UPDATE_INFO) { ni.max_input_ports = node->info.max_input_ports; ni.max_output_ports = node->info.max_output_ports; ni.change_mask = info_mask; ni.flags = node->spa_flags; ni.props = node->info.props; ni.params = node->info.params; ni.n_params = node->info.n_params; } res = pw_client_node_update(data->client_node, change_mask, n_params, (const struct spa_pod **)params, &ni); if (params) { while (n_params > 0) free(params[--n_params]); free(params); } return res; } static int add_port_update(struct node_data *data, struct pw_impl_port *port, uint32_t change_mask) { struct spa_port_info pi = SPA_PORT_INFO_INIT(); uint32_t n_params = 0; struct spa_pod **params = NULL; int res; if (change_mask & PW_CLIENT_NODE_PORT_UPDATE_PARAMS) { uint32_t i, idx, id; uint8_t buf[4096]; struct spa_pod_dynamic_builder b; for (i = 0; i < port->info.n_params; i++) { struct spa_pod *param; id = port->info.params[i].id; if (id == SPA_PARAM_Invalid) continue; for (idx = 0;;) { struct spa_node *qnode; uint32_t qport; spa_pod_dynamic_builder_init(&b, buf, sizeof(buf), 4096); switch (id) { case SPA_PARAM_IO: qnode = port->mix; qport = SPA_ID_INVALID; break; default: qnode = port->node->node; qport = port->port_id; break; } res = spa_node_port_enum_params_sync(qnode, port->direction, qport, id, &idx, NULL, ¶m, &b.b); if (res == 1) { void *p; p = pw_reallocarray(params, n_params + 1, sizeof(struct spa_pod*)); if (p == NULL) { res = -errno; pw_log_error("realloc failed: %m"); } else { params = p; params[n_params++] = spa_pod_copy(param); } } spa_pod_dynamic_builder_clean(&b); if (res != 1) break; } } } if (change_mask & PW_CLIENT_NODE_PORT_UPDATE_INFO) { pi.change_mask = SPA_PORT_CHANGE_MASK_FLAGS | SPA_PORT_CHANGE_MASK_RATE | SPA_PORT_CHANGE_MASK_PROPS | SPA_PORT_CHANGE_MASK_PARAMS; pi.flags = port->spa_flags; pi.rate = SPA_FRACTION(0, 1); pi.props = &port->properties->dict; SPA_FLAG_CLEAR(pi.flags, SPA_PORT_FLAG_DYNAMIC_DATA); pi.n_params = port->info.n_params; pi.params = port->info.params; } res = pw_client_node_port_update(data->client_node, port->direction, port->port_id, change_mask, n_params, (const struct spa_pod **)params, &pi); if (params) { while (n_params > 0) free(params[--n_params]); free(params); } return res; } static int client_node_set_param(void *_data, uint32_t id, uint32_t flags, const struct spa_pod *param) { struct node_data *data = _data; struct pw_proxy *proxy = (struct pw_proxy*)data->client_node; int res; pw_log_debug("node %p: set_param %s:", proxy, spa_debug_type_find_name(spa_type_param, id)); res = spa_node_set_param(data->node->node, id, flags, param); if (res < 0) { pw_log_error("node %p: set_param %s (%d) %p: %s", proxy, spa_debug_type_find_name(spa_type_param, id), id, param, spa_strerror(res)); pw_proxy_errorf(proxy, res, "node_set_param(%s) failed: %s", spa_debug_type_find_name(spa_type_param, id), spa_strerror(res)); } return res; } static int client_node_set_io(void *_data, uint32_t id, uint32_t memid, uint32_t offset, uint32_t size) { struct node_data *data = _data; struct pw_impl_node *node = data->node; struct pw_proxy *proxy = (struct pw_proxy*)data->client_node; struct pw_memmap *old, *mm; void *ptr; uint32_t tag[5] = { data->remote_id, SPA_ID_INVALID, id, }; int res; old = pw_mempool_find_tag(data->pool, tag, sizeof(tag)); if (memid == SPA_ID_INVALID) { mm = ptr = NULL; size = 0; } else { mm = pw_mempool_map_id(data->pool, memid, PW_MEMMAP_FLAG_READWRITE, offset, size, tag); if (mm == NULL) { pw_log_warn("can't map memory id %u: %m", memid); res = -errno; goto exit; } ptr = mm->ptr; } pw_log_debug("node %p: set io %s %p", proxy, spa_debug_type_find_name(spa_type_io, id), ptr); res = pw_impl_node_set_io(node, id, ptr, size); pw_memmap_free(old); exit: if (res < 0) { pw_log_error("node %p: set_io: %s", proxy, spa_strerror(res)); pw_proxy_errorf(proxy, res, "node_set_io failed: %s", spa_strerror(res)); } return res; } static int client_node_event(void *data, const struct spa_event *event) { uint32_t id = SPA_NODE_EVENT_ID(event); pw_log_warn("unhandled node event %d (%s)", id, spa_debug_type_find_name(spa_type_node_event_id, id)); return -ENOTSUP; } static int client_node_command(void *_data, const struct spa_command *command) { struct node_data *data = _data; struct pw_impl_node *node = data->node; struct pw_proxy *proxy = (struct pw_proxy*)data->client_node; int res; uint32_t id = SPA_NODE_COMMAND_ID(command); pw_log_debug("%p: got command %d (%s)", proxy, id, spa_debug_type_find_name(spa_type_node_command_id, id)); switch (id) { case SPA_NODE_COMMAND_Pause: if ((res = pw_impl_node_set_state(node, PW_NODE_STATE_IDLE)) < 0) { pw_log_warn("node %p: pause failed", proxy); pw_proxy_error(proxy, res, "pause failed"); } break; case SPA_NODE_COMMAND_Start: if ((res = pw_impl_node_set_state(node, PW_NODE_STATE_RUNNING)) < 0) { pw_log_warn("node %p: start failed", proxy); pw_proxy_error(proxy, res, "start failed"); } break; case SPA_NODE_COMMAND_Suspend: if ((res = pw_impl_node_set_state(node, PW_NODE_STATE_SUSPENDED)) < 0) { pw_log_warn("node %p: suspend failed", proxy); pw_proxy_error(proxy, res, "suspend failed"); } break; default: res = pw_impl_node_send_command(node, command); if (res < 0) { pw_log_warn("node command %d (%s) error: %s (%d)", id, spa_debug_type_find_name(spa_type_node_command_id, id), spa_strerror(res), res); pw_proxy_errorf(proxy, res, "command %d (%s) error: %s (%d)", id, spa_debug_type_find_name(spa_type_node_command_id, id), spa_strerror(res), res); } } return res; } static int client_node_add_port(void *_data, enum spa_direction direction, uint32_t port_id, const struct spa_dict *props) { struct node_data *data = _data; struct pw_proxy *proxy = (struct pw_proxy*)data->client_node; pw_log_warn("add port not supported"); pw_proxy_error(proxy, -ENOTSUP, "add port not supported"); return -ENOTSUP; } static int client_node_remove_port(void *_data, enum spa_direction direction, uint32_t port_id) { struct node_data *data = _data; struct pw_proxy *proxy = (struct pw_proxy*)data->client_node; pw_log_warn("remove port not supported"); pw_proxy_error(proxy, -ENOTSUP, "remove port not supported"); return -ENOTSUP; } static int clear_buffers(struct node_data *data, struct mix *mix) { struct pw_impl_port *port = mix->port; struct buffer *b; int res; pw_log_debug("port %p: clear %zd buffers mix:%d", port, pw_array_get_len(&mix->buffers, struct buffer), mix->mix.id); if ((res = pw_impl_port_use_buffers(port, &mix->mix, 0, NULL, 0)) < 0) { pw_log_error("port %p: error clear buffers %s", port, spa_strerror(res)); return res; } pw_array_for_each(b, &mix->buffers) { pw_log_debug("port %p: clear buffer %d map %p %p", port, b->id, b->mem, b->buf); pw_memmap_free(b->mem); free(b->buf); } mix->buffers.size = 0; return 0; } static int client_node_port_set_param(void *_data, enum spa_direction direction, uint32_t port_id, uint32_t id, uint32_t flags, const struct spa_pod *param) { struct node_data *data = _data; struct pw_proxy *proxy = (struct pw_proxy*)data->client_node; struct pw_impl_port *port; int res; port = pw_impl_node_find_port(data->node, direction, port_id); if (port == NULL) { res = -EINVAL; goto error_exit; } pw_log_debug("port %p: set_param %s %p", port, spa_debug_type_find_name(spa_type_param, id), param); res = pw_impl_port_set_param(port, id, flags, param); if (res < 0) goto error_exit; if (id == SPA_PARAM_Format) { struct mix *mix; spa_list_for_each(mix, &data->mix[direction], link) { if (mix->port->port_id == port_id) clear_buffers(data, mix); } } return res; error_exit: pw_log_error("port %p: set_param %d %p: %s", port, id, param, spa_strerror(res)); pw_proxy_errorf(proxy, res, "port_set_param(%s) failed: %s", spa_debug_type_find_name(spa_type_param, id), spa_strerror(res)); return res; } static int client_node_port_use_buffers(void *_data, enum spa_direction direction, uint32_t port_id, uint32_t mix_id, uint32_t flags, uint32_t n_buffers, struct pw_client_node_buffer *buffers) { struct node_data *data = _data; struct pw_proxy *proxy = (struct pw_proxy*)data->client_node; struct buffer *bid; uint32_t i, j; struct spa_buffer *b, **bufs; struct mix *mix; int res, prot; mix = find_mix(data, direction, port_id, mix_id); if (mix == NULL) { res = -ENOENT; goto error_exit; } if (n_buffers > MAX_BUFFERS) return -ENOSPC; prot = PW_MEMMAP_FLAG_READWRITE; /* clear previous buffers */ clear_buffers(data, mix); bufs = alloca(n_buffers * sizeof(struct spa_buffer *)); for (i = 0; i < n_buffers; i++) { size_t size; off_t offset; struct pw_memmap *mm; mm = pw_mempool_map_id(data->pool, buffers[i].mem_id, prot, buffers[i].offset, buffers[i].size, NULL); if (mm == NULL) { res = -errno; goto error_exit_cleanup; } bid = pw_array_add(&mix->buffers, sizeof(struct buffer)); if (bid == NULL) { res = -errno; goto error_exit_cleanup; } bid->id = i; bid->mem = mm; if (data->allow_mlock && mlock(mm->ptr, mm->size) < 0) if (errno != ENOMEM || !mlock_warned) { pw_log(data->warn_mlock ? SPA_LOG_LEVEL_WARN : SPA_LOG_LEVEL_DEBUG, "Failed to mlock memory %p %u: %s", mm->ptr, mm->size, errno == ENOMEM ? "This is not a problem but for best performance, " "consider increasing RLIMIT_MEMLOCK" : strerror(errno)); mlock_warned |= errno == ENOMEM; } size = sizeof(struct spa_buffer); for (j = 0; j < buffers[i].buffer->n_metas; j++) size += sizeof(struct spa_meta); for (j = 0; j < buffers[i].buffer->n_datas; j++) size += sizeof(struct spa_data); b = bid->buf = malloc(size); if (b == NULL) { res = -errno; goto error_exit_cleanup; } memcpy(b, buffers[i].buffer, sizeof(struct spa_buffer)); b->metas = SPA_PTROFF(b, sizeof(struct spa_buffer), struct spa_meta); b->datas = SPA_PTROFF(b->metas, sizeof(struct spa_meta) * b->n_metas, struct spa_data); pw_log_debug("add buffer mem:%d id:%d offset:%u size:%u %p", mm->block->id, bid->id, buffers[i].offset, buffers[i].size, bid->buf); offset = 0; for (j = 0; j < b->n_metas; j++) { struct spa_meta *m = &b->metas[j]; memcpy(m, &buffers[i].buffer->metas[j], sizeof(struct spa_meta)); m->data = SPA_PTROFF(mm->ptr, offset, void); offset += SPA_ROUND_UP_N(m->size, 8); } for (j = 0; j < b->n_datas; j++) { struct spa_data *d = &b->datas[j]; memcpy(d, &buffers[i].buffer->datas[j], sizeof(struct spa_data)); d->chunk = SPA_PTROFF(mm->ptr, offset + sizeof(struct spa_chunk) * j, struct spa_chunk); if (flags & SPA_NODE_BUFFERS_FLAG_ALLOC) continue; if (d->type == SPA_DATA_MemId) { uint32_t mem_id = SPA_PTR_TO_UINT32(d->data); struct pw_memblock *bm; bm = pw_mempool_find_id(data->pool, mem_id); if (bm == NULL) { pw_log_error("unknown buffer mem %u", mem_id); res = -ENODEV; goto error_exit_cleanup; } d->fd = bm->fd; d->type = bm->type; d->data = NULL; pw_log_debug(" data %d %u -> fd %d maxsize %d flags:%08x", j, bm->id, bm->fd, d->maxsize, d->flags); } else if (d->type == SPA_DATA_MemPtr) { int offs = SPA_PTR_TO_INT(d->data); d->data = SPA_PTROFF(mm->ptr, offs, void); d->fd = -1; pw_log_debug(" data %d id:%u -> mem:%p offs:%d maxsize:%d flags:%08x", j, bid->id, d->data, offs, d->maxsize, d->flags); } else { pw_log_warn("unknown buffer data type %d", d->type); } } bufs[i] = b; } if ((res = pw_impl_port_use_buffers(mix->port, &mix->mix, flags, bufs, n_buffers)) < 0) goto error_exit_cleanup; if (flags & SPA_NODE_BUFFERS_FLAG_ALLOC) { pw_client_node_port_buffers(data->client_node, direction, port_id, mix_id, n_buffers, bufs); } return res; error_exit_cleanup: clear_buffers(data, mix); error_exit: pw_log_error("port %p: use_buffers(%u:%u:%d): %d %s", mix, direction, port_id, mix_id, res, spa_strerror(res)); pw_proxy_errorf(proxy, res, "port_use_buffers(%u:%u:%d) error: %s", direction, port_id, mix_id, spa_strerror(res)); return res; } static int client_node_port_set_io(void *_data, uint32_t direction, uint32_t port_id, uint32_t mix_id, uint32_t id, uint32_t memid, uint32_t offset, uint32_t size) { struct node_data *data = _data; struct pw_proxy *proxy = (struct pw_proxy*)data->client_node; struct mix *mix; struct pw_memmap *mm, *old; void *ptr; int res = 0; uint32_t tag[5] = { data->remote_id, direction, port_id, mix_id, id }; mix = find_mix(data, direction, port_id, mix_id); if (mix == NULL) { res = -ENOENT; goto exit; } old = pw_mempool_find_tag(data->pool, tag, sizeof(tag)); if (memid == SPA_ID_INVALID) { mm = ptr = NULL; size = 0; } else { mm = pw_mempool_map_id(data->pool, memid, PW_MEMMAP_FLAG_READWRITE, offset, size, tag); if (mm == NULL) { pw_log_warn("can't map memory id %u: %m", memid); res = -errno; goto exit; } ptr = mm->ptr; } pw_log_debug("port %p: set io:%s new:%p old:%p", mix->port, spa_debug_type_find_name(spa_type_io, id), ptr, mix->mix.io); if ((res = spa_node_port_set_io(mix->port->mix, direction, mix->mix.port.port_id, id, ptr, size)) < 0) { if (res == -ENOTSUP) res = 0; else goto exit_free; } exit_free: pw_memmap_free(old); exit: if (res < 0) { pw_log_error("port %p: set_io: %s", mix, spa_strerror(res)); pw_proxy_errorf(proxy, res, "port_set_io failed: %s", spa_strerror(res)); } return res; } static int client_node_set_activation(void *_data, uint32_t node_id, int signalfd, uint32_t memid, uint32_t offset, uint32_t size) { struct node_data *data = _data; struct pw_proxy *proxy = (struct pw_proxy*)data->client_node; struct pw_impl_node *node = data->node; struct pw_memmap *mm; void *ptr; struct link *link; int res = 0; if (memid == SPA_ID_INVALID) { mm = ptr = NULL; size = 0; } else { mm = pw_mempool_map_id(data->pool, memid, PW_MEMMAP_FLAG_READWRITE, offset, size, NULL); if (mm == NULL) { res = -errno; goto error_exit; } ptr = mm->ptr; } if (data->remote_id == node_id) { pw_log_debug("node %p: our activation %u: %u %p %u %u", node, node_id, memid, ptr, offset, size); } else { pw_log_debug("node %p: set activation %u: %u %p %u %u", node, node_id, memid, ptr, offset, size); } if (ptr) { link = calloc(1, sizeof(struct link)); if (link == NULL) { res = -errno; goto error_exit; } link->data = data; link->map = mm; link->target.id = node_id; link->target.activation = ptr; link->target.system = data->data_system; link->target.fd = signalfd; link->target.trigger = link->target.activation->server_version < 1 ? trigger_target_v0 : trigger_target_v1; spa_list_append(&data->links, &link->link); pw_impl_node_add_target(node, &link->target); pw_log_debug("node %p: add link %p: memid:%u fd:%d id:%u state:%p pending:%d/%d", node, link, memid, signalfd, node_id, &link->target.activation->state[0], link->target.activation->state[0].pending, link->target.activation->state[0].required); } else { link = find_activation(&data->links, node_id); if (link == NULL) { res = -ENOENT; goto error_exit; } pw_log_debug("node %p: remove link %p: id:%u state:%p pending:%d/%d", node, link, node_id, &link->target.activation->state[0], link->target.activation->state[0].pending, link->target.activation->state[0].required); clear_link(data, link); } return res; error_exit: pw_log_error("node %p: set activation %d: %s", node, node_id, spa_strerror(res)); pw_proxy_errorf(proxy, res, "set_activation: %s", spa_strerror(res)); return res; } static void clear_mix(struct node_data *data, struct mix *mix) { pw_log_debug("port %p: mix clear %d.%d", mix->port, mix->port->port_id, mix->mix.id); if (mix->mix.id != SPA_ID_INVALID) spa_node_port_set_io(mix->port->mix, mix->mix.port.direction, mix->mix.port.port_id, SPA_IO_Buffers, NULL, 0); spa_list_remove(&mix->link); clear_buffers(data, mix); pw_array_clear(&mix->buffers); spa_list_append(&data->free_mix, &mix->link); if (mix->mix.id != SPA_ID_INVALID) pw_impl_port_release_mix(mix->port, &mix->mix); } static int client_node_port_set_mix_info(void *_data, enum spa_direction direction, uint32_t port_id, uint32_t mix_id, uint32_t peer_id, const struct spa_dict *props) { struct node_data *data = _data; struct mix *mix; pw_log_debug("%p: %d:%d:%d peer:%d", data, direction, port_id, mix_id, peer_id); mix = find_mix(data, direction, port_id, mix_id); if (peer_id == SPA_ID_INVALID) { if (mix == NULL) return -EINVAL; clear_mix(data, mix); } else { struct pw_impl_port *port; if (mix != NULL) return -EEXIST; port = pw_impl_node_find_port(data->node, direction, port_id); if (port == NULL) return -ENOENT; mix = create_mix(data, port, mix_id, peer_id); if (mix == NULL) return -errno; } return 0; } static const struct pw_client_node_events client_node_events = { PW_VERSION_CLIENT_NODE_EVENTS, .transport = client_node_transport, .set_param = client_node_set_param, .set_io = client_node_set_io, .event = client_node_event, .command = client_node_command, .add_port = client_node_add_port, .remove_port = client_node_remove_port, .port_set_param = client_node_port_set_param, .port_use_buffers = client_node_port_use_buffers, .port_set_io = client_node_port_set_io, .set_activation = client_node_set_activation, .port_set_mix_info = client_node_port_set_mix_info, }; static void do_node_init(struct node_data *data) { struct pw_impl_node *node = data->node; struct pw_impl_port *port; struct mix *mix; pw_log_debug("%p: node %p init", data, node); add_node_update(data, PW_CLIENT_NODE_UPDATE_PARAMS | PW_CLIENT_NODE_UPDATE_INFO, SPA_NODE_CHANGE_MASK_FLAGS | SPA_NODE_CHANGE_MASK_PROPS | SPA_NODE_CHANGE_MASK_PARAMS); spa_list_for_each(port, &node->input_ports, link) { mix = create_mix(data, port, SPA_ID_INVALID, SPA_ID_INVALID); if (mix == NULL) pw_log_error("%p: failed to create port mix: %m", node); add_port_update(data, port, PW_CLIENT_NODE_PORT_UPDATE_PARAMS | PW_CLIENT_NODE_PORT_UPDATE_INFO); } spa_list_for_each(port, &node->output_ports, link) { mix = create_mix(data, port, SPA_ID_INVALID, SPA_ID_INVALID); if (mix == NULL) pw_log_error("%p: failed to create port mix: %m", node); add_port_update(data, port, PW_CLIENT_NODE_PORT_UPDATE_PARAMS | PW_CLIENT_NODE_PORT_UPDATE_INFO); } } static void clean_node(struct node_data *d) { struct mix *mix; if (d->have_transport) { spa_list_consume(mix, &d->mix[SPA_DIRECTION_INPUT], link) clear_mix(d, mix); spa_list_consume(mix, &d->mix[SPA_DIRECTION_OUTPUT], link) clear_mix(d, mix); } spa_list_consume(mix, &d->free_mix, link) { spa_list_remove(&mix->link); free(mix); } clean_transport(d); } static void node_destroy(void *data) { struct node_data *d = data; pw_log_debug("%p: destroy", d); clean_node(d); } static void node_free(void *data) { struct node_data *d = data; pw_log_debug("%p: free", d); d->node = NULL; } static void node_info_changed(void *data, const struct pw_node_info *info) { struct node_data *d = data; uint32_t change_mask, info_mask; pw_log_debug("info changed %p", d); if (d->client_node == NULL) return; change_mask = PW_CLIENT_NODE_UPDATE_INFO; info_mask = SPA_NODE_CHANGE_MASK_FLAGS; if (info->change_mask & PW_NODE_CHANGE_MASK_PROPS) { info_mask |= SPA_NODE_CHANGE_MASK_PROPS; } if (info->change_mask & PW_NODE_CHANGE_MASK_PARAMS) { change_mask |= PW_CLIENT_NODE_UPDATE_PARAMS; info_mask |= SPA_NODE_CHANGE_MASK_PARAMS; } add_node_update(d, change_mask, info_mask); } static void node_port_info_changed(void *data, struct pw_impl_port *port, const struct pw_port_info *info) { struct node_data *d = data; uint32_t change_mask = 0; pw_log_debug("info changed %p", d); if (d->client_node == NULL) return; if (info->change_mask & PW_PORT_CHANGE_MASK_PROPS) change_mask |= PW_CLIENT_NODE_PORT_UPDATE_INFO; if (info->change_mask & PW_PORT_CHANGE_MASK_PARAMS) { change_mask |= PW_CLIENT_NODE_PORT_UPDATE_PARAMS; change_mask |= PW_CLIENT_NODE_PORT_UPDATE_INFO; } add_port_update(d, port, change_mask); } static void node_port_added(void *data, struct pw_impl_port *port) { struct node_data *d = data; struct mix *mix; pw_log_debug("added %p", d); if (d->client_node == NULL) return; mix = create_mix(d, port, SPA_ID_INVALID, SPA_ID_INVALID); if (mix == NULL) pw_log_error("%p: failed to create port mix: %m", d->node); } static void node_port_removed(void *data, struct pw_impl_port *port) { struct node_data *d = data; struct mix *mix, *tmp; pw_log_debug("removed %p", d); if (d->client_node == NULL) return; pw_client_node_port_update(d->client_node, port->direction, port->port_id, 0, 0, NULL, NULL); spa_list_for_each_safe(mix, tmp, &d->mix[port->direction], link) { if (mix->port == port) clear_mix(d, mix); } } static void node_active_changed(void *data, bool active) { struct node_data *d = data; pw_log_debug("active %d", active); if (d->client_node == NULL) return; pw_client_node_set_active(d->client_node, active); } static void node_event(void *data, const struct spa_event *event) { struct node_data *d = data; pw_log_debug("%p", d); if (d->client_node == NULL) return; pw_client_node_event(d->client_node, event); } static const struct pw_impl_node_events node_events = { PW_VERSION_IMPL_NODE_EVENTS, .destroy = node_destroy, .free = node_free, .info_changed = node_info_changed, .port_info_changed = node_port_info_changed, .port_added = node_port_added, .port_removed = node_port_removed, .active_changed = node_active_changed, .event = node_event, }; static void client_node_removed(void *_data) { struct node_data *data = _data; struct pw_impl_node *node = data->node; pw_log_debug("%p: removed", data); spa_hook_remove(&data->proxy_client_node_listener); spa_hook_remove(&data->client_node_listener); if (node) { spa_hook_remove(&data->node_listener); pw_impl_node_remove_rt_listener(node, &data->node_rt_listener); pw_impl_node_set_state(node, PW_NODE_STATE_SUSPENDED); clean_node(data); if (data->do_free) pw_impl_node_destroy(node); } data->client_node = NULL; } static void client_node_destroy(void *_data) { struct node_data *data = _data; pw_log_debug("%p: destroy", data); client_node_removed(_data); } static void client_node_bound_props(void *_data, uint32_t global_id, const struct spa_dict *props) { struct node_data *data = _data; pw_log_debug("%p: bound %u", data, global_id); data->remote_id = global_id; if (props) pw_properties_update(data->node->properties, props); } static const struct pw_proxy_events proxy_client_node_events = { PW_VERSION_PROXY_EVENTS, .removed = client_node_removed, .destroy = client_node_destroy, .bound_props = client_node_bound_props, }; static void node_rt_complete(void *data) { struct node_data *d = data; struct pw_impl_node *node = d->node; struct spa_system *data_system = d->data_system; if (!node->driving || !SPA_FLAG_IS_SET(node->rt.target.activation->flags, PW_NODE_ACTIVATION_FLAG_PROFILER)) return; if (SPA_UNLIKELY(spa_system_eventfd_write(data_system, d->rtwritefd, 1) < 0)) pw_log_warn("node %p: write failed %m", node); } static const struct pw_impl_node_rt_events node_rt_events = { PW_VERSION_IMPL_NODE_RT_EVENTS, .complete = node_rt_complete, }; static struct pw_proxy *node_export(struct pw_core *core, void *object, bool do_free, size_t user_data_size) { struct pw_impl_node *node = object; struct pw_proxy *client_node; struct node_data *data; if (node->data_loop == NULL) goto error; pw_log_debug("%p: export node %p", core, object); user_data_size = SPA_ROUND_UP_N(user_data_size, __alignof__(struct node_data)); client_node = pw_core_create_object(core, "client-node", PW_TYPE_INTERFACE_ClientNode, PW_VERSION_CLIENT_NODE, &node->properties->dict, user_data_size + sizeof(struct node_data)); if (client_node == NULL) goto error; data = pw_proxy_get_user_data(client_node); data = SPA_PTROFF(data, user_data_size, struct node_data); data->pool = pw_core_get_mempool(core); data->node = node; data->do_free = do_free; data->context = pw_impl_node_get_context(node); data->data_loop = node->data_loop; data->data_system = data->data_loop->system; data->client_node = (struct pw_client_node *)client_node; data->remote_id = SPA_ID_INVALID; /* the node might have been registered and added to a driver. When we export, * we will be assigned a new driver target from the server and we can forget our * local ones. */ pw_node_peer_unref(spa_steal_ptr(node->from_driver_peer)); pw_node_peer_unref(spa_steal_ptr(node->to_driver_peer)); data->allow_mlock = pw_properties_get_bool(node->properties, "mem.allow-mlock", data->context->settings.mem_allow_mlock); data->warn_mlock = pw_properties_get_bool(node->properties, "mem.warn-mlock", data->context->settings.mem_warn_mlock); node->exported = true; spa_list_init(&data->free_mix); spa_list_init(&data->mix[0]); spa_list_init(&data->mix[1]); spa_list_init(&data->links); pw_proxy_add_listener(client_node, &data->proxy_client_node_listener, &proxy_client_node_events, data); pw_impl_node_add_listener(node, &data->node_listener, &node_events, data); pw_impl_node_add_rt_listener(node, &data->node_rt_listener, &node_rt_events, data); pw_client_node_add_listener(data->client_node, &data->client_node_listener, &client_node_events, data); do_node_init(data); return client_node; error: if (do_free) pw_impl_node_destroy(node); return NULL; } struct pw_proxy *pw_core_node_export(struct pw_core *core, const char *type, const struct spa_dict *props, void *object, size_t user_data_size) { struct pw_impl_node *node = object; if (props) pw_impl_node_update_properties(node, props); return node_export(core, object, false, user_data_size); } struct pw_proxy *pw_core_spa_node_export(struct pw_core *core, const char *type, const struct spa_dict *props, void *object, size_t user_data_size) { struct pw_impl_node *node; struct pw_proxy *proxy; const char *str; bool do_register; str = props ? spa_dict_lookup(props, PW_KEY_OBJECT_REGISTER) : NULL; do_register = str ? pw_properties_parse_bool(str) : true; node = pw_context_create_node(pw_core_get_context(core), props ? pw_properties_new_dict(props) : NULL, 0); if (node == NULL) return NULL; pw_impl_node_set_implementation(node, (struct spa_node*)object); if (do_register) pw_impl_node_register(node, NULL); proxy = node_export(core, node, true, user_data_size); if (proxy) pw_impl_node_set_active(node, true); return proxy; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/modules/module-combine-stream.c000066400000000000000000001326121511204443500272760ustar00rootroot00000000000000/* PipeWire */ /* SPDX-FileCopyrightText: Copyright © 2023 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /** \page page_module_combine_stream Combine Stream * * The combine stream can make: * * - a new virtual sink that forwards audio to other sinks * - a new virtual source that combines audio from other sources * * The sources and sink that need to be combined can be selected using generic match * rules. This makes it possible to combine static nodes or nodes based on certain * properties. * * ## Module Name * * `libpipewire-module-combine-stream` * * ## Module Options * * - `node.name`: a unique name for the stream * - `node.description`: a human readable name for the stream * - `combine.mode` = capture | playback | sink | source, default sink * - `combine.latency-compensate`: use delay buffers to match stream latencies * - `combine.on-demand-streams`: use metadata to create streams on demand * - `combine.props = {}`: properties to be passed to the sink/source * - `stream.props = {}`: properties to be passed to the streams * - `stream.rules = {}`: rules for matching streams, use create-stream actions * * ## General options * * Options with well-known behavior. * * - \ref PW_KEY_REMOTE_NAME * - \ref PW_KEY_AUDIO_CHANNELS * - \ref SPA_KEY_AUDIO_LAYOUT * - \ref SPA_KEY_AUDIO_POSITION * - \ref PW_KEY_MEDIA_NAME * - \ref PW_KEY_NODE_LATENCY * - \ref PW_KEY_NODE_NAME * - \ref PW_KEY_NODE_DESCRIPTION * - \ref PW_KEY_NODE_GROUP * - \ref PW_KEY_NODE_VIRTUAL * - \ref PW_KEY_MEDIA_CLASS * * ## Stream options * * - `audio.position`: Set the stream channel map. By default this is the same channel * map as the combine stream. You can also use audio.layout * - `combine.audio.position`: map the combine audio positions to the stream positions. * combine input channels are mapped one-by-one to stream output channels. * You can also use combine.audio.layout. * * ## Example configuration * *\code{.unparsed} * # ~/.config/pipewire/pipewire.conf.d/my-combine-stream-1.conf * * context.modules = [ * { name = libpipewire-module-combine-stream * args = { * combine.mode = sink * node.name = "combine_sink" * node.description = "My Combine Sink" * combine.latency-compensate = false * combine.props = { * audio.position = [ FL FR ] * } * stream.props = { * } * stream.rules = [ * { * matches = [ * # any of the items in matches needs to match, if one does, * # actions are emitted. * { * # all keys must match the value. ! negates. ~ starts regex. * #node.name = "~alsa_input.*" * media.class = "Audio/Sink" * } * ] * actions = { * create-stream = { * #combine.audio.position = [ FL FR ] * #audio.position = [ FL FR ] * } * } * } * ] * } * } * ] *\endcode * * Below is an example configuration that makes a 5.1 virtual audio sink * from 3 separate stereo sinks. * *\code{.unparsed} * # ~/.config/pipewire/pipewire.conf.d/my-combine-stream-2.conf * * context.modules = [ * { name = libpipewire-module-combine-stream * args = { * combine.mode = sink * node.name = "combine_sink_5_1" * node.description = "My 5.1 Combine Sink" * combine.latency-compensate = false * combine.props = { * audio.position = [ FL FR FC LFE SL SR ] * } * stream.props = { * stream.dont-remix = true # link matching channels without remixing * } * stream.rules = [ * { matches = [ * { media.class = "Audio/Sink" * node.name = "alsa_output.usb-Topping_E30-00.analog-stereo" * } ] * actions = { create-stream = { * combine.audio.position = [ FL FR ] * audio.position = [ FL FR ] * } } } * { matches = [ * { media.class = "Audio/Sink" * node.name = "alsa_output.usb-BEHRINGER_UMC404HD_192k-00.pro-output-0" * } ] * actions = { create-stream = { * combine.audio.position = [ FC LFE ] * audio.position = [ AUX0 AUX1 ] * } } } * { matches = [ * { media.class = "Audio/Sink" * node.name = "alsa_output.pci-0000_00_1b.0.analog-stereo" * } ] * actions = { create-stream = { * combine.audio.position = [ SL SR ] * audio.position = [ FL FR ] * } } } * ] * } * } * ] *\endcode * * Below is an example configuration that makes a 4.0 virtual audio source * from 2 separate stereo sources. * *\code{.unparsed} * # ~/.config/pipewire/pipewire.conf.d/my-combine-stream-3.conf * * context.modules = [ * { name = libpipewire-module-combine-stream * args = { * combine.mode = source * node.name = "combine_source_4_0" * node.description = "My 4.0 Combine Source" * combine.props = { * audio.position = [ FL FR SL SR ] * } * stream.props = { * stream.dont-remix = true * } * stream.rules = [ * { matches = [ * { media.class = "Audio/Source" * node.name = "alsa_input.usb-046d_HD_Pro_Webcam_C920_09D53E1F-02.analog-stereo" * } ] * actions = { create-stream = { * audio.position = [ FL FR ] * combine.audio.position = [ FL FR ] * } } } * { matches = [ * { media.class = "Audio/Source" * node.name = "alsa_input.usb-046d_0821_9534DE90-00.analog-stereo" * } ] * actions = { create-stream = { * audio.position = [ FL FR ] * combine.audio.position = [ SL SR ] * } } } * ] * } * } * ] *\endcode */ #define NAME "combine-stream" PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); #define PW_LOG_TOPIC_DEFAULT mod_topic #define DEFAULT_CHANNELS 2 #define DEFAULT_POSITION "[ FL FR ]" #define MODULE_USAGE "( node.latency= ) " \ "( combine.mode=, default:sink ) " \ "( node.name= ) " \ "( node.description= ) " \ "( audio.channels= ) " \ "( audio.position= ) " \ "( combine.props= ) " \ "( stream.props= ) " \ "( stream.rules= ) " #define MAX_CHANNELS SPA_AUDIO_MAX_CHANNELS #define DELAYBUF_MAX_SIZE (20 * sizeof(float) * 96000) static const struct spa_dict_item module_props[] = { { PW_KEY_MODULE_AUTHOR, "Wim Taymans " }, { PW_KEY_MODULE_DESCRIPTION, "Combine multiple streams into a single stream" }, { PW_KEY_MODULE_USAGE, MODULE_USAGE }, { PW_KEY_MODULE_VERSION, PACKAGE_VERSION }, }; struct impl { struct pw_context *context; struct pw_loop *main_loop; struct pw_loop *data_loop; struct pw_properties *props; #define MODE_SINK 0 #define MODE_SOURCE 1 #define MODE_CAPTURE 2 #define MODE_PLAYBACK 3 uint32_t mode; struct pw_impl_module *module; struct spa_hook module_listener; struct pw_core *core; struct spa_hook core_proxy_listener; struct spa_hook core_listener; struct pw_registry *registry; struct spa_hook registry_listener; struct pw_metadata *metadata; struct spa_hook metadata_listener; uint32_t metadata_id; struct spa_source *update_delay_event; struct pw_properties *combine_props; struct pw_stream *combine; struct spa_hook combine_listener; struct pw_stream_events combine_events; uint32_t combine_id; struct pw_properties *stream_props; struct spa_latency_info latency; int64_t latency_offset; struct spa_audio_info_raw info; unsigned int do_disconnect:1; unsigned int latency_compensate:1; unsigned int on_demand_streams:1; struct spa_list streams; uint32_t n_streams; }; struct ringbuffer { void *buf; uint32_t idx; uint32_t size; }; struct stream { uint32_t id; char *on_demand_id; struct impl *impl; struct spa_list link; struct pw_stream *stream; struct spa_hook stream_listener; struct pw_stream_events stream_events; struct spa_latency_info latency; struct spa_audio_info_raw info; uint32_t remap[MAX_CHANNELS]; void *delaybuf; struct ringbuffer delay[MAX_CHANNELS]; int64_t delay_samples; /* for main loop */ int64_t data_delay_samples; /* for data loop */ int64_t compensate_samples; /* for main loop */ unsigned int ready:1; unsigned int added:1; unsigned int have_latency:1; }; static int parse_audio_info(const struct pw_properties *props, struct spa_audio_info_raw *info) { return spa_audio_info_raw_init_dict_keys(info, &SPA_DICT_ITEMS( SPA_DICT_ITEM(SPA_KEY_AUDIO_FORMAT, "F32P"), SPA_DICT_ITEM(SPA_KEY_AUDIO_POSITION, DEFAULT_POSITION)), &props->dict, SPA_KEY_AUDIO_CHANNELS, SPA_KEY_AUDIO_LAYOUT, SPA_KEY_AUDIO_POSITION, NULL); } static void ringbuffer_init(struct ringbuffer *r, void *buf, uint32_t size) { r->buf = buf; r->idx = 0; r->size = size; } static void ringbuffer_memcpy(struct ringbuffer *r, void *dst, void *src, uint32_t size) { uint32_t avail; avail = SPA_MIN(size, r->size); /* buf to dst */ if (dst && avail > 0) { spa_ringbuffer_read_data(NULL, r->buf, r->size, r->idx, dst, avail); dst = SPA_PTROFF(dst, avail, void); } /* src to dst */ if (size > avail) { if (dst) memcpy(dst, src, size - avail); src = SPA_PTROFF(src, size - avail, void); } /* src to buf */ if (avail > 0) { spa_ringbuffer_write_data(NULL, r->buf, r->size, r->idx, src, avail); r->idx = (r->idx + avail) % r->size; } } static void mix_f32(float *dst, float *src, uint32_t size) { uint32_t i, s = size / sizeof(float); for (i = 0; i < s; i++) dst[i] += src[i]; } static void ringbuffer_mix(struct ringbuffer *r, void *dst, void *src, uint32_t size) { uint32_t avail; avail = SPA_MIN(size, r->size); /* buf to dst */ if (dst && avail > 0) { uint32_t l0 = SPA_MIN(avail, r->size - r->idx), l1 = avail - l0; mix_f32(dst, SPA_PTROFF(r->buf, r->idx, void), l0); if (SPA_UNLIKELY(l1 > 0)) mix_f32(SPA_PTROFF(dst, l0, void), r->buf, l1); dst = SPA_PTROFF(dst, avail, void); } /* src to dst */ if (size > avail) { if (dst) mix_f32(dst, src, size - avail); src = SPA_PTROFF(src, size - avail, void); } /* src to buf */ if (avail > 0) { spa_ringbuffer_write_data(NULL, r->buf, r->size, r->idx, src, avail); r->idx = (r->idx + avail) % r->size; } } static void ringbuffer_copy(struct ringbuffer *dst, struct ringbuffer *src) { uint32_t l0, l1; if (dst->size == 0 || src->size == 0) return; l0 = src->size - src->idx; l1 = src->idx; ringbuffer_memcpy(dst, NULL, SPA_PTROFF(src->buf, src->idx, void), l0); ringbuffer_memcpy(dst, NULL, src->buf, l1); } static struct stream *find_stream(struct impl *impl, uint32_t id) { struct stream *s; spa_list_for_each(s, &impl->streams, link) if (s->id == id) return s; return NULL; } static struct stream *find_on_demand_stream(struct impl *impl, const char *on_demand_id) { struct stream *s; spa_list_for_each(s, &impl->streams, link) if (spa_streq(s->on_demand_id, on_demand_id)) return s; return NULL; } static enum pw_direction get_combine_direction(struct impl *impl) { if (impl->mode == MODE_SINK || impl->mode == MODE_CAPTURE) return PW_DIRECTION_INPUT; else return PW_DIRECTION_OUTPUT; } static void apply_latency_offset(struct spa_latency_info *latency, int64_t offset) { latency->min_ns += SPA_MAX(offset, -(int64_t)latency->min_ns); latency->max_ns += SPA_MAX(offset, -(int64_t)latency->max_ns); } static int64_t get_stream_delay(struct stream *s) { struct pw_time t; if (pw_stream_get_time_n(s->stream, &t, sizeof(t)) < 0) return INT64_MIN; return t.delay; /* samples at graph rate */ } static void update_latency(struct impl *impl) { struct spa_latency_info latency; struct stream *s; if (impl->combine == NULL) return; if (!impl->latency_compensate) { spa_latency_info_combine_start(&latency, get_combine_direction(impl)); spa_list_for_each(s, &impl->streams, link) if (s->have_latency) spa_latency_info_combine(&latency, &s->latency); spa_latency_info_combine_finish(&latency); } else { int64_t max_delay = INT64_MIN; latency = SPA_LATENCY_INFO(get_combine_direction(impl)); spa_list_for_each(s, &impl->streams, link) { int64_t delay = get_stream_delay(s); if (delay > max_delay && s->have_latency) { latency = s->latency; max_delay = delay; } } } apply_latency_offset(&latency, impl->latency_offset); if (spa_latency_info_compare(&latency, &impl->latency) != 0) { struct spa_pod_builder b = { 0 }; uint8_t buffer[1024]; const struct spa_pod *param; impl->latency = latency; spa_pod_builder_init(&b, buffer, sizeof(buffer)); param = spa_latency_build(&b, SPA_PARAM_Latency, &latency); pw_stream_update_params(impl->combine, ¶m, 1); } } struct replace_delay_info { struct stream *stream; void *buf; struct ringbuffer delay[MAX_CHANNELS]; }; static int do_replace_delay(struct spa_loop *loop, bool async, uint32_t seq, const void *data, size_t size, void *user_data) { struct replace_delay_info *info = user_data; unsigned int i; for (i = 0; i < SPA_N_ELEMENTS(info->stream->delay); ++i) { ringbuffer_copy(&info->delay[i], &info->stream->delay[i]); info->stream->delay[i] = info->delay[i]; } SPA_SWAP(info->stream->delaybuf, info->buf); return 0; } static void resize_delay(struct stream *stream, uint32_t size) { struct replace_delay_info info; uint32_t channels = stream->info.channels; unsigned int i; size = SPA_MIN(size, DELAYBUF_MAX_SIZE); for (i = 0; i < channels; ++i) if (stream->delay[i].size != size) break; if (i == channels) return; pw_log_info("stream %d latency compensation samples:%u", stream->id, (unsigned int)(size / sizeof(float))); spa_zero(info); info.stream = stream; if (size > 0) info.buf = calloc(channels, size); if (!info.buf) size = 0; for (i = 0; i < channels; ++i) ringbuffer_init(&info.delay[i], SPA_PTROFF(info.buf, i*size, void), size); pw_loop_locked(stream->impl->data_loop, do_replace_delay, 0, NULL, 0, &info); free(info.buf); } static void update_delay(struct impl *impl) { struct stream *s; int64_t max_delay = INT64_MIN; if (!impl->latency_compensate) return; spa_list_for_each(s, &impl->streams, link) { int64_t delay = get_stream_delay(s); if (delay != s->delay_samples && delay != INT64_MIN) pw_log_debug("stream %d delay:%"PRIi64" samples", s->id, delay); max_delay = SPA_MAX(max_delay, delay); s->delay_samples = delay; s->compensate_samples = 0; } spa_list_for_each(s, &impl->streams, link) { uint32_t size = 0; if (s->delay_samples != INT64_MIN) { int64_t delay = max_delay - s->delay_samples; s->compensate_samples = delay; size = delay * sizeof(float); } resize_delay(s, size); } update_latency(impl); } static void update_delay_event(void *data, uint64_t count) { struct impl *impl = data; /* in main loop */ update_delay(impl); } static int do_clear_delaybuf(struct spa_loop *loop, bool async, uint32_t seq, const void *data, size_t size, void *user_data) { struct impl *impl = user_data; struct stream *s; unsigned int i; spa_list_for_each(s, &impl->streams, link) { for (i = 0; i < SPA_N_ELEMENTS(s->delay); ++i) if (s->delay[i].size) memset(s->delay[i].buf, 0, s->delay[i].size); } return 0; } static void clear_delaybuf(struct impl *impl) { pw_loop_locked(impl->data_loop, do_clear_delaybuf, 0, NULL, 0, impl); } static int do_add_stream(struct spa_loop *loop, bool async, uint32_t seq, const void *data, size_t size, void *user_data) { struct stream *s = user_data; struct impl *impl = s->impl; if (!s->added) { spa_list_append(&impl->streams, &s->link); impl->n_streams++; s->added = true; } return 0; } static void param_tag_changed(struct impl *impl, const struct spa_pod *param) { if (param == NULL) return; pw_log_debug("tag update"); struct stream *s; struct spa_tag_info tag; const struct spa_pod *params[1] = { param }; void *state = NULL; if (spa_tag_parse(param, &tag, &state) < 0) return; spa_list_for_each(s, &impl->streams, link) { if (s->stream == NULL) continue; pw_log_debug("updating stream %d", s->id); pw_stream_update_params(s->stream, params, 1); } } static void param_latency_changed(struct impl *impl, const struct spa_pod *param) { if (param == NULL) return; pw_log_debug("latency update"); struct stream *s; struct spa_latency_info info; const struct spa_pod *params[1]; if (spa_latency_parse(param, &info) < 0) return; spa_list_for_each(s, &impl->streams, link) { uint8_t buffer[1024]; struct spa_pod_builder b; if (s->stream == NULL) continue; pw_log_debug("updating stream %d", s->id); if (impl->latency_compensate) { struct spa_latency_info other = info; other.min_rate += s->compensate_samples; other.max_rate += s->compensate_samples; spa_pod_builder_init(&b, buffer, sizeof(buffer)); params[0] = spa_latency_build(&b, SPA_PARAM_Latency, &other); } else { params[0] = param; } pw_stream_update_params(s->stream, params, 1); } } static int do_remove_stream(struct spa_loop *loop, bool async, uint32_t seq, const void *data, size_t size, void *user_data) { struct stream *s = user_data; if (s->added) { spa_list_remove(&s->link); s->impl->n_streams--; s->added = false; } return 0; } static void remove_stream(struct stream *s, bool destroy) { pw_log_debug("destroy stream %d", s->id); pw_loop_locked(s->impl->data_loop, do_remove_stream, 0, NULL, 0, s); if (destroy && s->stream) { spa_hook_remove(&s->stream_listener); pw_stream_destroy(s->stream); } free(s->on_demand_id); free(s->delaybuf); free(s); } static void destroy_stream(struct stream *s) { remove_stream(s, true); } static void destroy_all_on_demand_streams(struct impl *impl) { struct stream *s, *tmp; spa_list_for_each_safe(s, tmp, &impl->streams, link) if (s->on_demand_id) destroy_stream(s); } static void stream_destroy(void *d) { struct stream *s = d; spa_hook_remove(&s->stream_listener); remove_stream(s, false); } static void stream_input_process(void *d) { struct stream *s = d, *t; struct impl *impl = s->impl; bool ready = true; s->ready = true; pw_log_debug("stream ready %p", s); spa_list_for_each(t, &impl->streams, link) { if (!t->ready) { ready = false; break; } } if (ready) { pw_log_debug("do trigger"); pw_stream_trigger_process(impl->combine); } } static void stream_state_changed(void *d, enum pw_stream_state old, enum pw_stream_state state, const char *error) { struct stream *s = d; switch (state) { case PW_STREAM_STATE_ERROR: case PW_STREAM_STATE_UNCONNECTED: stream_destroy(s); break; case PW_STREAM_STATE_STREAMING: update_latency(s->impl); break; default: break; } } static void stream_param_changed(void *d, uint32_t id, const struct spa_pod *param) { struct stream *s = d; struct spa_latency_info latency; switch (id) { case SPA_PARAM_Format: update_delay(s->impl); break; case SPA_PARAM_Latency: if (param == NULL) { s->have_latency = false; } else if (spa_latency_parse(param, &latency) == 0 && latency.direction == get_combine_direction(s->impl)) { s->have_latency = true; s->latency = latency; } update_latency(s->impl); update_delay(s->impl); break; default: break; } } static const struct pw_stream_events stream_events = { PW_VERSION_STREAM_EVENTS, .destroy = stream_destroy, .state_changed = stream_state_changed, .param_changed = stream_param_changed, }; struct stream_info { struct impl *impl; uint32_t id; const char *on_demand_id; const struct spa_dict *props; struct pw_properties *stream_props; }; static int create_stream(struct stream_info *info) { struct impl *impl = info->impl; int res; uint32_t n_params, i, j; const struct spa_pod *params[1]; const char *str, *node_name, *dir_name; uint8_t buffer[1024]; struct spa_pod_builder b; struct spa_audio_info_raw remap_info, tmp_info; struct stream *s; enum pw_stream_flags flags; enum pw_direction direction; if (info->on_demand_id) { node_name = info->on_demand_id; pw_log_info("create on demand stream: %s", node_name); } else { node_name = spa_dict_lookup(info->props, PW_KEY_NODE_NAME); if (node_name == NULL) node_name = spa_dict_lookup(info->props, PW_KEY_OBJECT_SERIAL); if (node_name == NULL) return -EIO; pw_log_info("create stream for %d %s", info->id, node_name); } s = calloc(1, sizeof(*s)); if (s == NULL) goto error_errno; s->id = info->id; s->impl = impl; s->stream_events = stream_events; flags = PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_MAP_BUFFERS | PW_STREAM_FLAG_RT_PROCESS | PW_STREAM_FLAG_ASYNC; if (impl->mode == MODE_SINK || impl->mode == MODE_CAPTURE) { direction = PW_DIRECTION_OUTPUT; flags |= PW_STREAM_FLAG_TRIGGER; dir_name = "output"; } else { direction = PW_DIRECTION_INPUT; s->stream_events.process = stream_input_process; dir_name = "input"; } s->info = impl->info; if ((str = pw_properties_get(info->stream_props, SPA_KEY_AUDIO_POSITION)) != NULL) spa_audio_parse_position_n(str, strlen(str), s->info.position, SPA_N_ELEMENTS(s->info.position), &s->info.channels); if ((str = pw_properties_get(info->stream_props, SPA_KEY_AUDIO_LAYOUT)) != NULL) spa_audio_parse_layout(str, s->info.position, SPA_N_ELEMENTS(s->info.position), &s->info.channels); if (s->info.channels == 0) s->info = impl->info; spa_zero(remap_info); if ((str = pw_properties_get(info->stream_props, "combine.audio.position")) != NULL) spa_audio_parse_position_n(str, strlen(str), remap_info.position, SPA_N_ELEMENTS(remap_info.position), &remap_info.channels); if ((str = pw_properties_get(info->stream_props, "combine.audio.layout")) != NULL) spa_audio_parse_layout(str, remap_info.position, SPA_N_ELEMENTS(remap_info.position), &remap_info.channels); if (remap_info.channels == 0) remap_info = s->info; tmp_info = impl->info; for (i = 0; i < remap_info.channels; i++) { s->remap[i] = i; for (j = 0; j < tmp_info.channels; j++) { uint32_t pj, pi; pj = tmp_info.position[j]; pi = remap_info.position[i]; if (pj == pi) { s->remap[i] = j; break; } } pw_log_info("remap %d -> %d", i, s->remap[i]); } str = pw_properties_get(impl->props, PW_KEY_NODE_DESCRIPTION); if (str == NULL) str = pw_properties_get(impl->props, PW_KEY_NODE_NAME); if (str == NULL) str = node_name; if (pw_properties_get(info->stream_props, PW_KEY_MEDIA_NAME) == NULL) pw_properties_setf(info->stream_props, PW_KEY_MEDIA_NAME, "%s %s", str, dir_name); if (pw_properties_get(info->stream_props, PW_KEY_NODE_DESCRIPTION) == NULL) pw_properties_setf(info->stream_props, PW_KEY_NODE_DESCRIPTION, "%s %s", str, dir_name); str = pw_properties_get(impl->props, PW_KEY_NODE_NAME); if (str == NULL) str = "combine_stream"; if (pw_properties_get(info->stream_props, PW_KEY_NODE_NAME) == NULL) pw_properties_setf(info->stream_props, PW_KEY_NODE_NAME, "%s.%s_%s", dir_name, str, node_name); if (info->on_demand_id) { s->on_demand_id = strdup(info->on_demand_id); pw_properties_set(info->stream_props, "combine.on-demand-id", s->on_demand_id); } else { if (pw_properties_get(info->stream_props, PW_KEY_TARGET_OBJECT) == NULL) pw_properties_set(info->stream_props, PW_KEY_TARGET_OBJECT, node_name); } s->stream = pw_stream_new(impl->core, "Combine stream", info->stream_props); info->stream_props = NULL; if (s->stream == NULL) goto error_errno; pw_stream_add_listener(s->stream, &s->stream_listener, &s->stream_events, s); n_params = 0; spa_pod_builder_init(&b, buffer, sizeof(buffer)); params[n_params++] = spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat, &s->info); if ((res = pw_stream_connect(s->stream, direction, PW_ID_ANY, flags, params, n_params)) < 0) goto error; pw_loop_locked(impl->data_loop, do_add_stream, 0, NULL, 0, s); update_delay(impl); return 0; error_errno: res = -errno; error: if (s) destroy_stream(s); return res; } static int rule_matched(void *data, const char *location, const char *action, const char *str, size_t len) { struct stream_info *i = data; struct impl *impl = i->impl; int res = 0; if (spa_streq(action, "create-stream")) { i->stream_props = pw_properties_copy(impl->stream_props); pw_properties_update_string(i->stream_props, str, len); res = create_stream(i); pw_properties_free(i->stream_props); } return res; } static int metadata_property(void *data, uint32_t id, const char *key, const char *type, const char *value) { struct impl *impl = data; const char *on_demand_id; struct stream *s; if (id != impl->combine_id) return 0; if (!key) { destroy_all_on_demand_streams(impl); goto out; } if (!spa_strstartswith(key, "combine.on-demand-stream.")) return 0; on_demand_id = key + strlen("combine.on-demand-stream."); if (*on_demand_id == '\0') return 0; if (value) { struct stream_info info; s = find_on_demand_stream(impl, on_demand_id); if (s) destroy_stream(s); spa_zero(info); info.impl = impl; info.id = SPA_ID_INVALID; info.on_demand_id = on_demand_id; info.stream_props = pw_properties_copy(impl->stream_props); pw_properties_update_string(info.stream_props, value, strlen(value)); create_stream(&info); pw_properties_free(info.stream_props); } else { s = find_on_demand_stream(impl, on_demand_id); if (s) destroy_stream(s); } out: update_delay(impl); return 0; } static const struct pw_metadata_events metadata_events = { PW_VERSION_METADATA_EVENTS, .property = metadata_property }; static void registry_event_global(void *data, uint32_t id, uint32_t permissions, const char *type, uint32_t version, const struct spa_dict *props) { struct impl *impl = data; const char *str; struct stream_info info; if (impl->on_demand_streams && spa_streq(type, PW_TYPE_INTERFACE_Metadata)) { if (!props) return; if (!spa_streq(spa_dict_lookup(props, "metadata.name"), "default")) return; impl->metadata = pw_registry_bind(impl->registry, id, type, PW_VERSION_METADATA, 0); pw_metadata_add_listener(impl->metadata, &impl->metadata_listener, &metadata_events, impl); impl->metadata_id = id; return; } if (!spa_streq(type, PW_TYPE_INTERFACE_Node) || props == NULL) return; if (id == impl->combine_id) return; spa_zero(info); info.impl = impl; info.id = id; info.props = props; str = pw_properties_get(impl->props, "stream.rules"); if (str == NULL) { if (impl->mode == MODE_CAPTURE || impl->mode == MODE_SINK) str = "[ { matches = [ { media.class = \"Audio/Sink\" } ] " " actions = { create-stream = {} } } ]"; else str = "[ { matches = [ { media.class = \"Audio/Source\" } ] " " actions = { create-stream = {} } } ]"; } pw_conf_match_rules(str, strlen(str), NAME, props, rule_matched, &info); } static void registry_event_global_remove(void *data, uint32_t id) { struct impl *impl = data; struct stream *s; if (impl->metadata && id == impl->metadata_id) { destroy_all_on_demand_streams(impl); update_delay(impl); spa_hook_remove(&impl->metadata_listener); pw_proxy_destroy((struct pw_proxy*)impl->metadata); impl->metadata = NULL; return; } s = find_stream(impl, id); if (s == NULL) return; destroy_stream(s); update_delay(impl); } static const struct pw_registry_events registry_events = { PW_VERSION_REGISTRY_EVENTS, .global = registry_event_global, .global_remove = registry_event_global_remove, }; static void combine_destroy(void *d) { struct impl *impl = d; spa_hook_remove(&impl->combine_listener); impl->combine = NULL; } static void combine_state_changed(void *d, enum pw_stream_state old, enum pw_stream_state state, const char *error) { struct impl *impl = d; struct stream *s; switch (state) { case PW_STREAM_STATE_ERROR: case PW_STREAM_STATE_UNCONNECTED: pw_impl_module_schedule_destroy(impl->module); break; case PW_STREAM_STATE_PAUSED: clear_delaybuf(impl); spa_list_for_each(s, &impl->streams, link) { pw_stream_flush(s->stream, false); } pw_stream_flush(impl->combine, false); impl->combine_id = pw_stream_get_node_id(impl->combine); pw_log_info("got combine id %d", impl->combine_id); break; case PW_STREAM_STATE_STREAMING: break; default: break; } } static bool check_stream_delay(struct stream *s) { int64_t delay; if (!s->impl->latency_compensate) return false; delay = get_stream_delay(s); if (delay == INT64_MIN || delay == s->data_delay_samples) return false; s->data_delay_samples = delay; return true; } static void combine_input_process(void *d) { struct impl *impl = d; struct pw_buffer *in, *out; struct stream *s; bool delay_changed = false; in = NULL; while (true) { struct pw_buffer *t; if ((t = pw_stream_dequeue_buffer(impl->combine)) == NULL) break; if (in) pw_stream_queue_buffer(impl->combine, in); in = t; } if (in == NULL) { pw_log_debug("%p: out of input buffers: %m", impl); return; } spa_list_for_each(s, &impl->streams, link) { uint32_t j; if (s->stream == NULL) continue; if (check_stream_delay(s)) delay_changed = true; if ((out = pw_stream_dequeue_buffer(s->stream)) == NULL) { pw_log_warn("%p: out of playback buffers: %m", s); goto do_trigger; } for (j = 0; j < out->buffer->n_datas; j++) { struct spa_data *ds, *dd; uint32_t outsize = 0, remap; int32_t stride = 0; dd = &out->buffer->datas[j]; remap = s->remap[j]; if (remap < in->buffer->n_datas) { uint32_t offs, size; ds = &in->buffer->datas[remap]; offs = SPA_MIN(ds->chunk->offset, ds->maxsize); size = SPA_MIN(ds->chunk->size, ds->maxsize - offs); ringbuffer_memcpy(&s->delay[j], dd->data, SPA_PTROFF(ds->data, offs, void), size); outsize = SPA_MAX(outsize, size); stride = SPA_MAX(stride, ds->chunk->stride); } else { memset(dd->data, 0, outsize); } dd->chunk->offset = 0; dd->chunk->size = outsize; dd->chunk->stride = stride; } pw_stream_queue_buffer(s->stream, out); do_trigger: pw_stream_trigger_process(s->stream); } pw_stream_queue_buffer(impl->combine, in); /* Update delay if quantum etc. has changed. * This should be rare enough so that doing it via main loop doesn't matter. */ if (impl->latency_compensate && delay_changed) pw_loop_signal_event(impl->main_loop, impl->update_delay_event); } static void combine_output_process(void *d) { struct impl *impl = d; struct pw_buffer *in, *out; struct stream *s; bool delay_changed = false; bool mix[MAX_CHANNELS]; if ((out = pw_stream_dequeue_buffer(impl->combine)) == NULL) { pw_log_debug("%p: out of output buffers: %m", impl); return; } for (uint32_t i = 0; i < out->buffer->n_datas; i++) mix[i] = false; spa_list_for_each(s, &impl->streams, link) { uint32_t j; if (s->stream == NULL) continue; if (check_stream_delay(s)) delay_changed = true; in = NULL; while (true) { struct pw_buffer *t; if ((t = pw_stream_dequeue_buffer(s->stream)) == NULL) break; if (in) pw_stream_queue_buffer(s->stream, in); in = t; } if (in == NULL) { pw_log_debug("%p: out of input buffers: %m", s); continue; } s->ready = false; for (j = 0; j < in->buffer->n_datas; j++) { struct spa_data *ds, *dd; uint32_t outsize = 0, remap; int32_t stride = 0; ds = &in->buffer->datas[j]; remap = s->remap[j]; if (remap < out->buffer->n_datas) { uint32_t offs, size; dd = &out->buffer->datas[remap]; offs = SPA_MIN(ds->chunk->offset, ds->maxsize); size = SPA_MIN(ds->chunk->size, ds->maxsize - offs); size = SPA_MIN(size, dd->maxsize); if (mix[remap]) { ringbuffer_mix(&s->delay[j], dd->data, SPA_PTROFF(ds->data, offs, void), size); } else { ringbuffer_memcpy(&s->delay[j], dd->data, SPA_PTROFF(ds->data, offs, void), size); mix[remap] = true; } outsize = SPA_MAX(outsize, size); stride = SPA_MAX(stride, ds->chunk->stride); dd->chunk->offset = 0; dd->chunk->size = outsize; dd->chunk->stride = stride; } } pw_stream_queue_buffer(s->stream, in); } pw_stream_queue_buffer(impl->combine, out); if (impl->latency_compensate && delay_changed) pw_loop_signal_event(impl->main_loop, impl->update_delay_event); } static void combine_param_changed(void *d, uint32_t id, const struct spa_pod *param) { struct impl *impl = d; switch (id) { case SPA_PARAM_Props: { int64_t latency_offset; uint8_t buffer[1024]; struct spa_pod_builder b; const struct spa_pod *p; if (!param) latency_offset = 0; else if (spa_pod_parse_object(param, SPA_TYPE_OBJECT_Props, NULL, SPA_PROP_latencyOffsetNsec, SPA_POD_Long(&latency_offset)) < 0) break; if (latency_offset == impl->latency_offset) break; impl->latency_offset = latency_offset; spa_pod_builder_init(&b, buffer, sizeof(buffer)); p = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_Props, SPA_PARAM_Props, SPA_PROP_latencyOffsetNsec, SPA_POD_Long(impl->latency_offset)); pw_stream_update_params(impl->combine, &p, 1); update_latency(impl); break; } case SPA_PARAM_Tag: param_tag_changed(impl, param); break; case SPA_PARAM_Latency: param_latency_changed(impl, param); break; default: break; } } static const struct pw_stream_events combine_events = { PW_VERSION_STREAM_EVENTS, .destroy = combine_destroy, .state_changed = combine_state_changed, .param_changed = combine_param_changed, }; static int create_combine(struct impl *impl) { int res; uint32_t n_params; const struct spa_pod *params[3]; uint8_t buffer[1024]; struct spa_pod_builder b; enum pw_direction direction; enum pw_stream_flags flags; impl->combine = pw_stream_new(impl->core, "Combine stream", impl->combine_props); impl->combine_props = NULL; if (impl->combine == NULL) return -errno; flags = PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_MAP_BUFFERS | PW_STREAM_FLAG_RT_PROCESS; impl->combine_events = combine_events; if (impl->mode == MODE_SINK || impl->mode == MODE_CAPTURE) { direction = PW_DIRECTION_INPUT; impl->combine_events.process = combine_input_process; } else { direction = PW_DIRECTION_OUTPUT; impl->combine_events.process = combine_output_process; flags |= PW_STREAM_FLAG_TRIGGER; } pw_stream_add_listener(impl->combine, &impl->combine_listener, &impl->combine_events, impl); n_params = 0; spa_pod_builder_init(&b, buffer, sizeof(buffer)); params[n_params++] = spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat, &impl->info); params[n_params++] = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_PropInfo, SPA_PARAM_PropInfo, SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_latencyOffsetNsec), SPA_PROP_INFO_description, SPA_POD_String("Latency offset (ns)"), SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Long(0LL, INT64_MIN, INT64_MAX)); params[n_params++] = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_Props, SPA_PARAM_Props, SPA_PROP_latencyOffsetNsec, SPA_POD_Long(impl->latency_offset)); if ((res = pw_stream_connect(impl->combine, direction, PW_ID_ANY, flags, params, n_params)) < 0) return res; return 0; } static void core_error(void *data, uint32_t id, int seq, int res, const char *message) { struct impl *impl = data; pw_log_error("error id:%u seq:%d res:%d (%s): %s", id, seq, res, spa_strerror(res), message); if (id == PW_ID_CORE && res == -EPIPE) pw_impl_module_schedule_destroy(impl->module); } static const struct pw_core_events core_events = { PW_VERSION_CORE_EVENTS, .error = core_error, }; static void core_removed(void *d) { struct impl *impl = d; if (impl->core) { spa_hook_remove(&impl->core_listener); impl->core = NULL; } if (impl->registry) { spa_hook_remove(&impl->registry_listener); pw_proxy_destroy((struct pw_proxy*)impl->registry); impl->registry = NULL; } if (impl->metadata) { spa_hook_remove(&impl->metadata_listener); pw_proxy_destroy((struct pw_proxy*)impl->metadata); impl->metadata = NULL; } pw_impl_module_schedule_destroy(impl->module); } static const struct pw_proxy_events core_proxy_events = { PW_VERSION_PROXY_EVENTS, .removed = core_removed, }; static void impl_destroy(struct impl *impl) { struct stream *s; spa_list_consume(s, &impl->streams, link) destroy_stream(s); if (impl->combine) pw_stream_destroy(impl->combine); if (impl->update_delay_event) pw_loop_destroy_source(impl->main_loop, impl->update_delay_event); if (impl->metadata) { spa_hook_remove(&impl->metadata_listener); pw_proxy_destroy((struct pw_proxy*)impl->metadata); impl->metadata = NULL; } if (impl->registry) { spa_hook_remove(&impl->registry_listener); pw_proxy_destroy((struct pw_proxy*)impl->registry); impl->registry = NULL; } if (impl->core) { spa_hook_remove(&impl->core_listener); if (impl->do_disconnect) pw_core_disconnect(impl->core); impl->core = NULL; } if (impl->data_loop) pw_context_release_loop(impl->context, impl->data_loop); pw_properties_free(impl->stream_props); pw_properties_free(impl->combine_props); pw_properties_free(impl->props); free(impl); } static void module_destroy(void *data) { struct impl *impl = data; spa_hook_remove(&impl->module_listener); impl_destroy(impl); } static const struct pw_impl_module_events module_events = { PW_VERSION_IMPL_MODULE_EVENTS, .destroy = module_destroy, }; static void copy_props(const struct pw_properties *props, struct pw_properties *target, const char *key) { const char *str; if ((str = pw_properties_get(props, key)) != NULL) { if (pw_properties_get(target, key) == NULL) pw_properties_set(target, key, str); } } SPA_EXPORT int pipewire__module_init(struct pw_impl_module *module, const char *args) { struct pw_context *context = pw_impl_module_get_context(module); struct pw_properties *props = NULL; uint32_t id = pw_global_get_id(pw_impl_module_get_global(module)); uint32_t pid = getpid(); struct impl *impl; const char *str, *prefix; int res; struct spa_error_location loc = {}; PW_LOG_TOPIC_INIT(mod_topic); impl = calloc(1, sizeof(struct impl)); if (impl == NULL) return -errno; pw_log_debug("module %p: new %s", impl, args); impl->module = module; impl->context = context; spa_list_init(&impl->streams); if (args == NULL) args = "{}"; props = pw_properties_new_string_checked(args, strlen(args), &loc); if (props == NULL) { res = -errno; if (loc.reason) spa_debug_log_error_location(pw_log_get(), SPA_LOG_LEVEL_ERROR, &loc, "invalid module arguments: %s", loc.reason); else pw_log_error("can't create properties: %m"); goto error; } impl->props = props; impl->main_loop = pw_context_get_main_loop(context); impl->data_loop = pw_context_acquire_loop(context, &props->dict); if ((str = pw_properties_get(props, "combine.mode")) == NULL) str = "sink"; if (spa_streq(str, "sink")) { impl->mode = MODE_SINK; prefix = "sink"; } else if (spa_streq(str, "capture")) { impl->mode = MODE_CAPTURE; prefix = "capture"; } else if (spa_streq(str, "source")) { impl->mode = MODE_SOURCE; prefix = "source"; } else if (spa_streq(str, "playback")) { impl->mode = MODE_PLAYBACK; prefix = "playback"; } else { pw_log_warn("unknown combine.mode '%s', using 'sink'", str); impl->mode = MODE_SINK; prefix = "sink"; } if ((str = pw_properties_get(props, "combine.latency-compensate")) != NULL) impl->latency_compensate = spa_atob(str); if ((str = pw_properties_get(props, "combine.on-demand-streams")) != NULL) impl->on_demand_streams = spa_atob(str); impl->combine_props = pw_properties_new(NULL, NULL); impl->stream_props = pw_properties_new(NULL, NULL); if (impl->combine_props == NULL || impl->stream_props == NULL) { res = -errno; pw_log_error( "can't create properties: %m"); goto error; } pw_properties_set(props, PW_KEY_NODE_LOOP_NAME, impl->data_loop->name); if (pw_properties_get(props, PW_KEY_NODE_GROUP) == NULL) pw_properties_setf(props, PW_KEY_NODE_GROUP, "combine-%s-%u-%u", prefix, pid, id); if (pw_properties_get(props, PW_KEY_NODE_LINK_GROUP) == NULL) pw_properties_setf(props, PW_KEY_NODE_LINK_GROUP, "combine-%s-%u-%u", prefix, pid, id); if (pw_properties_get(props, PW_KEY_NODE_VIRTUAL) == NULL) pw_properties_set(props, PW_KEY_NODE_VIRTUAL, "true"); if (pw_properties_get(props, "resample.prefill") == NULL) pw_properties_set(props, "resample.prefill", "true"); if (pw_properties_get(props, "resample.disable") == NULL) pw_properties_set(props, "resample.disable", "true"); if (pw_properties_get(props, PW_KEY_MEDIA_CLASS) == NULL) { if (impl->mode == MODE_SINK) pw_properties_set(props, PW_KEY_MEDIA_CLASS, "Audio/Sink"); else if (impl->mode == MODE_SOURCE) pw_properties_set(props, PW_KEY_MEDIA_CLASS, "Audio/Source"); } if (pw_properties_get(props, PW_KEY_NODE_NAME) == NULL) pw_properties_setf(props, PW_KEY_NODE_NAME, "combine-%s-%u-%u", prefix, pid, id); if (pw_properties_get(props, PW_KEY_NODE_DESCRIPTION) == NULL) pw_properties_setf(props, PW_KEY_NODE_DESCRIPTION, "Combine %s", prefix); if ((str = pw_properties_get(props, "combine.props")) != NULL) pw_properties_update_string(impl->combine_props, str, strlen(str)); if ((str = pw_properties_get(props, "stream.props")) != NULL) pw_properties_update_string(impl->stream_props, str, strlen(str)); copy_props(props, impl->combine_props, PW_KEY_NODE_LOOP_NAME); copy_props(props, impl->combine_props, PW_KEY_AUDIO_CHANNELS); copy_props(props, impl->combine_props, SPA_KEY_AUDIO_LAYOUT); copy_props(props, impl->combine_props, SPA_KEY_AUDIO_POSITION); copy_props(props, impl->combine_props, PW_KEY_NODE_NAME); copy_props(props, impl->combine_props, PW_KEY_NODE_DESCRIPTION); copy_props(props, impl->combine_props, PW_KEY_NODE_GROUP); copy_props(props, impl->combine_props, PW_KEY_NODE_LINK_GROUP); copy_props(props, impl->combine_props, PW_KEY_NODE_LATENCY); copy_props(props, impl->combine_props, PW_KEY_NODE_VIRTUAL); copy_props(props, impl->combine_props, PW_KEY_MEDIA_CLASS); copy_props(props, impl->combine_props, "resample.prefill"); copy_props(props, impl->combine_props, "resample.disable"); if ((res = parse_audio_info(impl->combine_props, &impl->info)) < 0) { pw_log_error( "can't create format: %s", spa_strerror(res)); goto error; } copy_props(props, impl->stream_props, PW_KEY_NODE_LOOP_NAME); copy_props(props, impl->stream_props, PW_KEY_NODE_GROUP); copy_props(props, impl->stream_props, PW_KEY_NODE_VIRTUAL); copy_props(props, impl->stream_props, PW_KEY_NODE_LINK_GROUP); copy_props(props, impl->stream_props, "resample.prefill"); copy_props(props, impl->stream_props, "resample.disable"); if (pw_properties_get(impl->stream_props, PW_KEY_MEDIA_ROLE) == NULL) pw_properties_set(props, PW_KEY_MEDIA_ROLE, "filter"); if (pw_properties_get(impl->stream_props, PW_KEY_NODE_PASSIVE) == NULL) pw_properties_set(impl->stream_props, PW_KEY_NODE_PASSIVE, "true"); if (pw_properties_get(impl->stream_props, PW_KEY_NODE_DONT_RECONNECT) == NULL) pw_properties_set(impl->stream_props, PW_KEY_NODE_DONT_RECONNECT, "true"); if (impl->latency_compensate) { impl->update_delay_event = pw_loop_add_event(impl->main_loop, update_delay_event, impl); if (impl->update_delay_event == NULL) { res = -errno; pw_log_error("can't create event source: %m"); goto error; } } impl->core = pw_context_get_object(impl->context, PW_TYPE_INTERFACE_Core); if (impl->core == NULL) { str = pw_properties_get(props, PW_KEY_REMOTE_NAME); impl->core = pw_context_connect(impl->context, pw_properties_new( PW_KEY_REMOTE_NAME, str, NULL), 0); impl->do_disconnect = true; } if (impl->core == NULL) { res = -errno; pw_log_error("can't connect: %m"); goto error; } pw_proxy_add_listener((struct pw_proxy*)impl->core, &impl->core_proxy_listener, &core_proxy_events, impl); pw_core_add_listener(impl->core, &impl->core_listener, &core_events, impl); if ((res = create_combine(impl)) < 0) goto error; impl->registry = pw_core_get_registry(impl->core, PW_VERSION_REGISTRY, 0); pw_registry_add_listener(impl->registry, &impl->registry_listener, ®istry_events, impl); pw_impl_module_add_listener(module, &impl->module_listener, &module_events, impl); pw_impl_module_update_properties(module, &SPA_DICT_INIT_ARRAY(module_props)); return 0; error: impl_destroy(impl); return res; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/modules/module-echo-cancel.c000066400000000000000000001455361511204443500265430ustar00rootroot00000000000000/* PipeWire */ /* SPDX-FileCopyrightText: Copyright © 2021 Wim Taymans */ /* SPDX-FileCopyrightText: Copyright © 2021 Arun Raghavan */ /* SPDX-License-Identifier: MIT */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /** \page page_module_echo_cancel Echo Cancel * * The `echo-cancel` module performs echo cancellation. The module creates * virtual `echo-cancel-capture` source and `echo-cancel-playback` sink * nodes and the associated streams. * * The echo-cancel module is mostly used in video or audio conference * applications. When the other participants talk and the audio is going out to * the speakers, the signal will be picked up again by the microphone and sent * back to the other participants (along with your talking), resulting in an * echo. This is annoying because the other participants will hear their own * echo from you. * * Conceptually the echo-canceler is composed of 4 streams: * *\code{.unparsed} * .--------. .---------. .--------. .----------. .-------. * | mic | --> | capture | --> | | --> | source | --> | app | * '--------' '---------' | echo | '----------' '-------' * | cancel | * .--------. .---------. | | .----------. .---------. * | app | --> | sink | --> | | --> | playback | --> | speaker | * '--------' '---------' '--------' '----------' '---------' *\endcode * - A capture stream that captures audio from a microphone. * - A Sink that takes the signal containing the data that should be canceled * out from the capture stream. This is where the application (video conference * application) send the audio to and it contains the signal from the other * participants that are speaking and that needs to be cancelled out. * - A playback stream that just passes the signal from the Sink to the speaker. * This is so that you can hear the other participants. It is also the signal * that gets picked up by the microphone and that eventually needs to be * removed again. * - A Source that exposes the echo-canceled data captured from the capture * stream. The data from the sink stream and capture stream are correlated and * the signal from the sink stream is removed from the capture stream data. * This data then goes into the application (the conference application) and * does not contain the echo from the other participants anymore. * * ## Module Name * * `libpipewire-module-echo-cancel` * * ## Module Options * * Options specific to the behavior of this module * * - `capture.props = {}`: properties to be passed to the capture stream * - `source.props = {}`: properties to be passed to the source stream * - `sink.props = {}`: properties to be passed to the sink stream * - `playback.props = {}`: properties to be passed to the playback stream * - `library.name = `: the echo cancellation library Currently supported: * `aec/libspa-aec-webrtc`. Leave unset to use the default method (`aec/libspa-aec-webrtc`). * - `aec.args = `: arguments to pass to the echo cancellation method * - `monitor.mode`: Instead of making a sink, make a stream that captures from * the monitor ports of the default sink. * * ## General options * * Options with well-known behavior: * * - \ref PW_KEY_AUDIO_RATE * - \ref PW_KEY_AUDIO_CHANNELS * - \ref SPA_KEY_AUDIO_LAYOUT * - \ref SPA_KEY_AUDIO_POSITION * - \ref PW_KEY_MEDIA_CLASS * - \ref PW_KEY_NODE_LATENCY * - \ref PW_KEY_NODE_NAME * - \ref PW_KEY_NODE_DESCRIPTION * - \ref PW_KEY_NODE_GROUP * - \ref PW_KEY_NODE_LINK_GROUP * - \ref PW_KEY_NODE_VIRTUAL * - \ref PW_KEY_NODE_LATENCY * - \ref PW_KEY_REMOTE_NAME * * ## Example configuration *\code{.unparsed} * # ~/.config/pipewire/pipewire.conf.d/my-echo-cancel.conf * * context.modules = [ * { name = libpipewire-module-echo-cancel * args = { * # library.name = aec/libspa-aec-webrtc * # node.latency = 1024/48000 * # monitor.mode = false * capture.props = { * node.name = "Echo Cancellation Capture" * } * source.props = { * node.name = "Echo Cancellation Source" * } * sink.props = { * node.name = "Echo Cancellation Sink" * } * playback.props = { * node.name = "Echo Cancellation Playback" * } * } * } *] *\endcode * */ /** */ #define NAME "echo-cancel" PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); #define PW_LOG_TOPIC_DEFAULT mod_topic #define DEFAULT_RATE 48000 #define DEFAULT_POSITION "[ FL FR ]" #define MAX_CHANNELS SPA_AUDIO_MAX_CHANNELS /* Hopefully this is enough for any combination of AEC engine and resampler * input requirement for rate matching */ #define MAX_BUFSIZE_MS 100 #define DELAY_MS 0 static const struct spa_dict_item module_props[] = { { PW_KEY_MODULE_AUTHOR, "Wim Taymans " }, { PW_KEY_MODULE_DESCRIPTION, "Echo Cancellation" }, { PW_KEY_MODULE_USAGE, " ( remote.name= ) " "( node.latency= ) " "( audio.rate= ) " "( audio.channels= ) " "( audio.position= ) " "( buffer.max_size= ) " "( buffer.play_delay= ) " "( library.name = ) " "( aec.args= ) " "( capture.props= ) " "( source.props= ) " "( sink.props= ) " "( playback.props= ) " }, { PW_KEY_MODULE_VERSION, PACKAGE_VERSION }, }; struct impl { struct pw_context *context; struct pw_impl_module *module; struct spa_hook module_listener; struct pw_core *core; struct spa_hook core_proxy_listener; struct spa_hook core_listener; struct spa_audio_info_raw rec_info; struct spa_audio_info_raw out_info; struct spa_audio_info_raw play_info; struct pw_properties *capture_props; struct pw_stream *capture; struct spa_hook capture_listener; struct spa_audio_info_raw capture_info; struct pw_properties *source_props; struct pw_stream *source; struct spa_hook source_listener; struct spa_audio_info_raw source_info; void *rec_buffer[MAX_CHANNELS]; uint32_t rec_ringsize; struct spa_ringbuffer rec_ring; struct pw_properties *playback_props; struct pw_stream *playback; struct spa_hook playback_listener; struct spa_audio_info_raw playback_info; struct pw_properties *sink_props; struct pw_stream *sink; struct spa_hook sink_listener; void *play_buffer[MAX_CHANNELS]; uint32_t play_ringsize; struct spa_ringbuffer play_ring; struct spa_ringbuffer play_delayed_ring; struct spa_audio_info_raw sink_info; void *out_buffer[MAX_CHANNELS]; uint32_t out_ringsize; struct spa_ringbuffer out_ring; struct spa_audio_aec *aec; uint32_t aec_blocksize; struct spa_io_position *capture_position; struct spa_io_position *sink_position; uint32_t capture_cycle; uint32_t sink_cycle; unsigned int do_disconnect:1; uint32_t max_buffer_size; uint32_t buffer_delay; uint32_t current_delay; struct spa_handle *spa_handle; struct spa_plugin_loader *loader; bool monitor_mode; char wav_path[512]; struct wav_file *wav_file; }; static inline void aec_run(struct impl *impl, const float *rec[], const float *play[], float *out[], uint32_t n_samples) { spa_audio_aec_run(impl->aec, rec, play, out, n_samples); #ifdef HAVE_SPA_PLUGINS if (SPA_UNLIKELY(impl->wav_path[0])) { if (impl->wav_file == NULL) { struct wav_file_info info; spa_zero(info); info.info.media_type = SPA_MEDIA_TYPE_audio; info.info.media_subtype = SPA_MEDIA_SUBTYPE_raw; info.info.info.raw.format = SPA_AUDIO_FORMAT_F32P; info.info.info.raw.rate = impl->rec_info.rate; info.info.info.raw.channels = impl->play_info.channels + impl->rec_info.channels + impl->out_info.channels; impl->wav_file = wav_file_open(impl->wav_path, "w", &info); if (impl->wav_file == NULL) pw_log_warn("can't open wav path '%s': %m", impl->wav_path); } if (impl->wav_file) { uint32_t i, n, c = impl->play_info.channels + impl->rec_info.channels + impl->out_info.channels; const float *data[c]; for (i = n = 0; i < impl->play_info.channels; i++) data[n++] = play[i]; for (i = 0; i < impl->rec_info.channels; i++) data[n++] = rec[i]; for (i = 0; i < impl->out_info.channels; i++) data[n++] = out[i]; wav_file_write(impl->wav_file, (void*)data, n_samples); } else { spa_zero(impl->wav_path); } } else if (impl->wav_file != NULL) { wav_file_close(impl->wav_file); impl->wav_file = NULL; } #endif } static void process(struct impl *impl) { struct pw_buffer *cout; struct pw_buffer *pout = NULL; float rec_buf[impl->rec_info.channels][impl->aec_blocksize / sizeof(float)]; float play_buf[impl->play_info.channels][impl->aec_blocksize / sizeof(float)]; float play_delayed_buf[impl->play_info.channels][impl->aec_blocksize / sizeof(float)]; float out_buf[impl->out_info.channels][impl->aec_blocksize / sizeof(float)]; const float *rec[impl->rec_info.channels]; const float *play[impl->play_info.channels]; const float *play_delayed[impl->play_info.channels]; float *out[impl->out_info.channels]; struct spa_data *dd; uint32_t i; uint32_t rindex, pindex, oindex, pdindex, size; int32_t avail, pavail, pdavail; size = impl->aec_blocksize; /* First read a block from the capture ring buffer */ avail = spa_ringbuffer_get_read_index(&impl->rec_ring, &rindex); while (avail >= (int32_t)size * 2) { /* drop samples that are not needed this or next cycle. Note * that samples are kept in the ringbuffer until next cycle if * size is not equal to or divisible by quantum, to avoid * discontinuity */ pw_log_debug("avail %d", avail); spa_ringbuffer_read_update(&impl->rec_ring, rindex + size); avail = spa_ringbuffer_get_read_index(&impl->rec_ring, &rindex); pw_log_debug("new avail %d, size %u", avail, size); } for (i = 0; i < impl->rec_info.channels; i++) { /* captured samples, with echo from sink */ rec[i] = &rec_buf[i][0]; spa_ringbuffer_read_data(&impl->rec_ring, impl->rec_buffer[i], impl->rec_ringsize, rindex % impl->rec_ringsize, (void*)rec[i], size); } spa_ringbuffer_read_update(&impl->rec_ring, rindex + size); for (i = 0; i < impl->out_info.channels; i++) { /* filtered samples, without echo from sink */ out[i] = &out_buf[i][0]; } pavail = spa_ringbuffer_get_read_index(&impl->play_ring, &pindex); pdavail = spa_ringbuffer_get_read_index(&impl->play_delayed_ring, &pdindex); if (impl->playback != NULL && (pout = pw_stream_dequeue_buffer(impl->playback)) == NULL) { pw_log_debug("out of playback buffers: %m"); /* playback stream may not yet be in streaming state, drop play * data to avoid introducing additional playback latency */ spa_ringbuffer_read_update(&impl->play_ring, pindex + pavail); spa_ringbuffer_read_update(&impl->play_delayed_ring, pdindex + pdavail); goto done; } if (pavail > avail) { /* drop too old samples from previous graph cycles */ pw_log_debug("pavail %d, dropping %d", pavail, pavail - avail); spa_ringbuffer_read_update(&impl->play_ring, pindex + pavail - avail); pavail = spa_ringbuffer_get_read_index(&impl->play_ring, &pindex); pw_log_debug("new pavail %d, avail %d", pavail, avail); } if (pdavail > avail) { /* drop too old samples from previous graph cycles */ pw_log_debug("pdavail %d, dropping %d", pdavail, pdavail - avail); spa_ringbuffer_read_update(&impl->play_delayed_ring, pdindex + pdavail - avail); pdavail = spa_ringbuffer_get_read_index(&impl->play_delayed_ring, &pdindex); pw_log_debug("new pdavail %d, avail %d", pdavail, avail); } for (i = 0; i < impl->play_info.channels; i++) { /* echo from sink */ play[i] = &play_buf[i][0]; /* echo from sink delayed */ play_delayed[i] = &play_delayed_buf[i][0]; spa_ringbuffer_read_data(&impl->play_ring, impl->play_buffer[i], impl->play_ringsize, pindex % impl->play_ringsize, (void *)play[i], size); spa_ringbuffer_read_data(&impl->play_delayed_ring, impl->play_buffer[i], impl->play_ringsize, pdindex % impl->play_ringsize, (void *)play_delayed[i], size); if (pout != NULL) { /* output to sink, just copy */ dd = &pout->buffer->datas[i]; memcpy(dd->data, play[i], size); dd->chunk->offset = 0; dd->chunk->size = size; dd->chunk->stride = sizeof(float); } } spa_ringbuffer_read_update(&impl->play_ring, pindex + size); spa_ringbuffer_read_update(&impl->play_delayed_ring, pdindex + size); if (impl->playback != NULL) pw_stream_queue_buffer(impl->playback, pout); if (SPA_UNLIKELY (impl->current_delay < impl->buffer_delay)) { uint32_t delay_left = impl->buffer_delay - impl->current_delay; uint32_t silence_size; /* don't run the canceller until play_buffer has been filled, * copy silence to output in the meantime */ silence_size = SPA_MIN(size, delay_left * sizeof(float)); for (i = 0; i < impl->out_info.channels; i++) memset(out[i], 0, silence_size); impl->current_delay += silence_size / sizeof(float); pw_log_debug("current_delay %d", impl->current_delay); if (silence_size != size) { const float *pd[impl->play_info.channels]; float *o[impl->out_info.channels]; for (i = 0; i < impl->play_info.channels; i++) pd[i] = play_delayed[i] + delay_left; for (i = 0; i < impl->out_info.channels; i++) o[i] = out[i] + delay_left; aec_run(impl, rec, pd, o, size / sizeof(float) - delay_left); } } else { /* run the canceller */ aec_run(impl, rec, play_delayed, out, size / sizeof(float)); } /* Next, copy over the output to the output ringbuffer */ avail = spa_ringbuffer_get_write_index(&impl->out_ring, &oindex); if (avail + size > impl->out_ringsize) { uint32_t rindex, drop; /* Drop enough so we have size bytes left */ drop = avail + size - impl->out_ringsize; pw_log_debug("output ringbuffer xrun %d + %u > %u, dropping %u", avail, size, impl->out_ringsize, drop); spa_ringbuffer_get_read_index(&impl->out_ring, &rindex); spa_ringbuffer_read_update(&impl->out_ring, rindex + drop); avail += drop; } for (i = 0; i < impl->out_info.channels; i++) { /* captured samples, with echo from sink */ spa_ringbuffer_write_data(&impl->out_ring, impl->out_buffer[i], impl->out_ringsize, oindex % impl->out_ringsize, (void *)out[i], size); } spa_ringbuffer_write_update(&impl->out_ring, oindex + size); /* And finally take data from the output ringbuffer and make it * available on the source */ avail = spa_ringbuffer_get_read_index(&impl->out_ring, &oindex); while (avail >= (int32_t)size) { if ((cout = pw_stream_dequeue_buffer(impl->source)) != NULL) { for (i = 0; i < impl->out_info.channels; i++) { dd = &cout->buffer->datas[i]; spa_ringbuffer_read_data(&impl->out_ring, impl->out_buffer[i], impl->out_ringsize, oindex % impl->out_ringsize, (void *)dd->data, size); dd->chunk->offset = 0; dd->chunk->size = size; dd->chunk->stride = sizeof(float); } pw_stream_queue_buffer(impl->source, cout); } else { /* drop data as to not cause delay */ pw_log_debug("out of source buffers: %m"); } oindex += size; spa_ringbuffer_read_update(&impl->out_ring, oindex); avail -= size; } done: impl->capture_cycle = 0; impl->sink_cycle = 0; } static void reset_buffers(struct impl *impl) { uint32_t index, i; spa_ringbuffer_init(&impl->rec_ring); spa_ringbuffer_init(&impl->play_ring); spa_ringbuffer_init(&impl->play_delayed_ring); spa_ringbuffer_init(&impl->out_ring); for (i = 0; i < impl->rec_info.channels; i++) memset(impl->rec_buffer[i], 0, impl->rec_ringsize); for (i = 0; i < impl->play_info.channels; i++) memset(impl->play_buffer[i], 0, impl->play_ringsize); for (i = 0; i < impl->out_info.channels; i++) memset(impl->out_buffer[i], 0, impl->out_ringsize); spa_ringbuffer_get_write_index(&impl->play_ring, &index); spa_ringbuffer_write_update(&impl->play_ring, index + (sizeof(float) * (impl->buffer_delay))); spa_ringbuffer_get_read_index(&impl->play_ring, &index); spa_ringbuffer_read_update(&impl->play_ring, index + (sizeof(float) * (impl->buffer_delay))); impl->capture_cycle = 0; impl->sink_cycle = 0; } static void capture_destroy(void *d) { struct impl *impl = d; spa_hook_remove(&impl->capture_listener); impl->capture = NULL; } static void capture_process(void *data) { struct impl *impl = data; struct pw_buffer *buf; struct spa_data *d; uint32_t i, index, offs, size; int32_t avail; if ((buf = pw_stream_dequeue_buffer(impl->capture)) == NULL) { pw_log_debug("out of capture buffers: %m"); return; } d = &buf->buffer->datas[0]; offs = SPA_MIN(d->chunk->offset, d->maxsize); size = SPA_MIN(d->chunk->size, d->maxsize - offs); avail = spa_ringbuffer_get_write_index(&impl->rec_ring, &index); if (avail + size > impl->rec_ringsize) { uint32_t rindex, drop; /* Drop enough so we have size bytes left */ drop = avail + size - impl->rec_ringsize; pw_log_debug("capture ringbuffer xrun %d + %u > %u, dropping %u", avail, size, impl->rec_ringsize, drop); spa_ringbuffer_get_read_index(&impl->rec_ring, &rindex); spa_ringbuffer_read_update(&impl->rec_ring, rindex + drop); avail += drop; } /* If we don't know what size to push yet, use the canceller blocksize * if it has a specific requirement, else keep the block size the same * on input and output or what the resampler needs */ if (impl->aec_blocksize == 0) { impl->aec_blocksize = size; pw_log_debug("Setting AEC block size to %u", impl->aec_blocksize); } for (i = 0; i < impl->rec_info.channels; i++) { /* captured samples, with echo from sink */ d = &buf->buffer->datas[i]; offs = SPA_MIN(d->chunk->offset, d->maxsize); size = SPA_MIN(d->chunk->size, d->maxsize - offs); spa_ringbuffer_write_data(&impl->rec_ring, impl->rec_buffer[i], impl->rec_ringsize, index % impl->rec_ringsize, SPA_PTROFF(d->data, offs, void), size); } spa_ringbuffer_write_update(&impl->rec_ring, index + size); if (avail + size >= impl->aec_blocksize) { if (impl->capture_position) impl->capture_cycle = impl->capture_position->clock.cycle; else pw_log_warn("no capture position"); if (impl->capture_cycle == impl->sink_cycle) process(impl); } pw_stream_queue_buffer(impl->capture, buf); } static void capture_state_changed(void *data, enum pw_stream_state old, enum pw_stream_state state, const char *error) { struct impl *impl = data; int res; switch (state) { case PW_STREAM_STATE_PAUSED: pw_stream_flush(impl->source, false); pw_stream_flush(impl->capture, false); if (old == PW_STREAM_STATE_STREAMING) { if (pw_stream_get_state(impl->sink, NULL) != PW_STREAM_STATE_STREAMING) { reset_buffers(impl); pw_log_debug("%p: deactivate %s", impl, impl->aec->name); res = spa_audio_aec_deactivate(impl->aec); if (res < 0 && res != -EOPNOTSUPP) { pw_log_error("aec plugin %s deactivate failed: %s", impl->aec->name, spa_strerror(res)); } } } break; case PW_STREAM_STATE_STREAMING: if (pw_stream_get_state(impl->sink, NULL) == PW_STREAM_STATE_STREAMING) { pw_log_debug("%p: activate %s", impl, impl->aec->name); res = spa_audio_aec_activate(impl->aec); if (res < 0 && res != -EOPNOTSUPP) { pw_log_error("aec plugin %s activate failed: %s", impl->aec->name, spa_strerror(res)); } } break; case PW_STREAM_STATE_UNCONNECTED: pw_log_info("%p: capture unconnected", impl); pw_impl_module_schedule_destroy(impl->module); break; case PW_STREAM_STATE_ERROR: pw_log_info("%p: capture error: %s", impl, error); break; default: break; } } static void source_state_changed(void *data, enum pw_stream_state old, enum pw_stream_state state, const char *error) { struct impl *impl = data; switch (state) { case PW_STREAM_STATE_PAUSED: pw_stream_flush(impl->source, false); pw_stream_flush(impl->capture, false); break; case PW_STREAM_STATE_UNCONNECTED: pw_log_info("%p: source unconnected", impl); pw_impl_module_schedule_destroy(impl->module); break; case PW_STREAM_STATE_ERROR: pw_log_info("%p: source error: %s", impl, error); break; default: break; } } static void input_param_latency_changed(struct impl *impl, const struct spa_pod *param) { struct spa_latency_info latency; uint8_t buffer[1024]; struct spa_pod_builder b; const struct spa_pod *params[1]; if (param == NULL || spa_latency_parse(param, &latency) < 0) return; spa_pod_builder_init(&b, buffer, sizeof(buffer)); params[0] = spa_latency_build(&b, SPA_PARAM_Latency, &latency); if (latency.direction == SPA_DIRECTION_INPUT) pw_stream_update_params(impl->capture, params, 1); else pw_stream_update_params(impl->source, params, 1); } static struct spa_pod* get_props_param(struct impl* impl, struct spa_pod_builder* b) { struct spa_pod_frame f[2]; spa_pod_builder_push_object( b, &f[0], SPA_TYPE_OBJECT_Props, SPA_PARAM_Props); spa_pod_builder_prop(b, SPA_PROP_params, 0); spa_pod_builder_push_struct(b, &f[1]); spa_pod_builder_string(b, "debug.aec.wav-path"); spa_pod_builder_string(b, impl->wav_path); if (spa_audio_aec_get_params(impl->aec, NULL) > 0) spa_audio_aec_get_params(impl->aec, b); spa_pod_builder_pop(b, &f[1]); return spa_pod_builder_pop(b, &f[0]); } static int set_params(struct impl* impl, const struct spa_pod *params) { struct spa_pod_parser prs; struct spa_pod_frame f; spa_pod_parser_pod(&prs, params); if (spa_pod_parser_push_struct(&prs, &f) < 0) return 0; while (true) { const char *name; struct spa_pod *pod; char value[512]; if (spa_pod_parser_get_string(&prs, &name) < 0) break; if (spa_pod_parser_get_pod(&prs, &pod) < 0) break; if (spa_pod_is_string(pod)) { spa_pod_copy_string(pod, sizeof(value), value); } else if (spa_pod_is_none(pod)) { spa_zero(value); } else continue; pw_log_info("key:'%s' val:'%s'", name, value); if (spa_streq(name, "debug.aec.wav-path")) { spa_scnprintf(impl->wav_path, sizeof(impl->wav_path), "%s", value); } } spa_audio_aec_set_params(impl->aec, params); return 1; } static void props_changed(struct impl* impl, const struct spa_pod *param) { uint8_t buffer[1024]; struct spa_pod_dynamic_builder b; const struct spa_pod* params[1]; const struct spa_pod_prop* prop; struct spa_pod_object* obj = (struct spa_pod_object*)param; if (param == NULL) return; SPA_POD_OBJECT_FOREACH(obj, prop) { if (prop->key == SPA_PROP_params) set_params(impl, &prop->value); } spa_pod_dynamic_builder_init(&b, buffer, sizeof(buffer), 4096); params[0] = get_props_param(impl, &b.b); if (params[0]) { pw_stream_update_params(impl->capture, params, 1); if (impl->playback != NULL) pw_stream_update_params(impl->playback, params, 1); } spa_pod_dynamic_builder_clean(&b); } static void input_param_changed(void *data, uint32_t id, const struct spa_pod* param) { struct impl* impl = data; switch (id) { case SPA_PARAM_Format: if (param == NULL) reset_buffers(impl); break; case SPA_PARAM_Latency: input_param_latency_changed(impl, param); break; case SPA_PARAM_Props: props_changed(impl, param); break; } } static void capture_io_changed(void *data, uint32_t id, void *area, uint32_t size) { struct impl *impl = data; switch (id) { case SPA_IO_Position: impl->capture_position = area; break; default: break; } } static const struct pw_stream_events capture_events = { PW_VERSION_STREAM_EVENTS, .destroy = capture_destroy, .state_changed = capture_state_changed, .process = capture_process, .param_changed = input_param_changed, .io_changed = capture_io_changed }; static void source_destroy(void *d) { struct impl *impl = d; spa_hook_remove(&impl->source_listener); impl->source = NULL; } static const struct pw_stream_events source_events = { PW_VERSION_STREAM_EVENTS, .destroy = source_destroy, .state_changed = source_state_changed, .param_changed = input_param_changed }; static void playback_state_changed(void *data, enum pw_stream_state old, enum pw_stream_state state, const char *error) { struct impl *impl = data; switch (state) { case PW_STREAM_STATE_PAUSED: pw_stream_flush(impl->sink, false); if (impl->playback != NULL) pw_stream_flush(impl->playback, false); if (old == PW_STREAM_STATE_STREAMING) { impl->current_delay = 0; } break; case PW_STREAM_STATE_UNCONNECTED: pw_log_info("%p: playback unconnected", impl); pw_impl_module_schedule_destroy(impl->module); break; case PW_STREAM_STATE_ERROR: pw_log_info("%p: playback error: %s", impl, error); break; default: break; } } static void sink_state_changed(void *data, enum pw_stream_state old, enum pw_stream_state state, const char *error) { struct impl *impl = data; int res; switch (state) { case PW_STREAM_STATE_PAUSED: pw_stream_flush(impl->sink, false); if (impl->playback != NULL) pw_stream_flush(impl->playback, false); if (old == PW_STREAM_STATE_STREAMING) { impl->current_delay = 0; if (pw_stream_get_state(impl->capture, NULL) != PW_STREAM_STATE_STREAMING) { reset_buffers(impl); pw_log_debug("%p: deactivate %s", impl, impl->aec->name); res = spa_audio_aec_deactivate(impl->aec); if (res < 0 && res != -EOPNOTSUPP) { pw_log_error("aec plugin %s deactivate failed: %s", impl->aec->name, spa_strerror(res)); } } } break; case PW_STREAM_STATE_STREAMING: if (pw_stream_get_state(impl->capture, NULL) == PW_STREAM_STATE_STREAMING) { pw_log_debug("%p: activate %s", impl, impl->aec->name); res = spa_audio_aec_activate(impl->aec); if (res < 0 && res != -EOPNOTSUPP) { pw_log_error("aec plugin %s activate failed: %s", impl->aec->name, spa_strerror(res)); } } break; case PW_STREAM_STATE_UNCONNECTED: pw_log_info("%p: sink unconnected", impl); pw_impl_module_schedule_destroy(impl->module); break; case PW_STREAM_STATE_ERROR: pw_log_info("%p: sink error: %s", impl, error); break; default: break; } } static void output_param_latency_changed(struct impl *impl, const struct spa_pod *param) { struct spa_latency_info latency; uint8_t buffer[1024]; struct spa_pod_builder b; const struct spa_pod *params[1]; if (param == NULL || spa_latency_parse(param, &latency) < 0) return; spa_pod_builder_init(&b, buffer, sizeof(buffer)); params[0] = spa_latency_build(&b, SPA_PARAM_Latency, &latency); if (latency.direction == SPA_DIRECTION_INPUT) pw_stream_update_params(impl->sink, params, 1); else if (impl->playback != NULL) pw_stream_update_params(impl->playback, params, 1); } static void output_param_changed(void *data, uint32_t id, const struct spa_pod *param) { struct impl *impl = data; switch (id) { case SPA_PARAM_Format: if (param == NULL) reset_buffers(impl); break; case SPA_PARAM_Latency: output_param_latency_changed(impl, param); break; case SPA_PARAM_Props: props_changed(impl, param); break; } } static void sink_destroy(void *d) { struct impl *impl = d; spa_hook_remove(&impl->sink_listener); impl->sink = NULL; } static void sink_process(void *data) { struct impl *impl = data; struct pw_buffer *buf; struct spa_data *d; uint32_t i, index, offs, size; int32_t avail; if ((buf = pw_stream_dequeue_buffer(impl->sink)) == NULL) { pw_log_debug("out of sink buffers: %m"); return; } d = &buf->buffer->datas[0]; offs = SPA_MIN(d->chunk->offset, d->maxsize); size = SPA_MIN(d->chunk->size, d->maxsize - offs); avail = spa_ringbuffer_get_write_index(&impl->play_ring, &index); if (avail + size > impl->play_ringsize) { uint32_t rindex, drop; /* Drop enough so we have size bytes left */ drop = avail + size - impl->play_ringsize; pw_log_debug("sink ringbuffer xrun %d + %u > %u, dropping %u", avail, size, impl->play_ringsize, drop); spa_ringbuffer_get_read_index(&impl->play_ring, &rindex); spa_ringbuffer_read_update(&impl->play_ring, rindex + drop); spa_ringbuffer_get_read_index(&impl->play_delayed_ring, &rindex); spa_ringbuffer_read_update(&impl->play_delayed_ring, rindex + drop); avail += drop; } if (impl->aec_blocksize == 0) { impl->aec_blocksize = size; pw_log_debug("Setting AEC block size to %u", impl->aec_blocksize); } for (i = 0; i < impl->play_info.channels; i++) { /* echo from sink */ d = &buf->buffer->datas[i]; offs = SPA_MIN(d->chunk->offset, d->maxsize); size = SPA_MIN(d->chunk->size, d->maxsize - offs); spa_ringbuffer_write_data(&impl->play_ring, impl->play_buffer[i], impl->play_ringsize, index % impl->play_ringsize, SPA_PTROFF(d->data, offs, void), size); } spa_ringbuffer_write_update(&impl->play_ring, index + size); spa_ringbuffer_get_write_index(&impl->play_delayed_ring, &index); spa_ringbuffer_write_update(&impl->play_delayed_ring, index + size); if (avail + size >= impl->aec_blocksize) { if (impl->sink_position) impl->sink_cycle = impl->sink_position->clock.cycle; else pw_log_warn("no sink position"); if (impl->capture_cycle == impl->sink_cycle) process(impl); } pw_stream_queue_buffer(impl->sink, buf); } static void playback_destroy(void *d) { struct impl *impl = d; if (impl->playback != NULL) { spa_hook_remove(&impl->playback_listener); impl->playback = NULL; } } static const struct pw_stream_events playback_events = { PW_VERSION_STREAM_EVENTS, .destroy = playback_destroy, .state_changed = playback_state_changed, .param_changed = output_param_changed }; static void sink_io_changed(void *data, uint32_t id, void *area, uint32_t size) { struct impl *impl = data; switch (id) { case SPA_IO_Position: impl->sink_position = area; break; default: break; } } static const struct pw_stream_events sink_events = { PW_VERSION_STREAM_EVENTS, .destroy = sink_destroy, .process = sink_process, .state_changed = sink_state_changed, .param_changed = output_param_changed, .io_changed = sink_io_changed }; #define MAX_PARAMS 512u static int setup_streams(struct impl *impl) { int res; uint32_t n_params, i; uint32_t offsets[MAX_PARAMS]; const struct spa_pod *params[MAX_PARAMS]; struct spa_pod_dynamic_builder b; impl->capture = pw_stream_new(impl->core, "Echo-Cancel Capture", impl->capture_props); impl->capture_props = NULL; if (impl->capture == NULL) return -errno; pw_stream_add_listener(impl->capture, &impl->capture_listener, &capture_events, impl); impl->source = pw_stream_new(impl->core, "Echo-Cancel Source", impl->source_props); impl->source_props = NULL; if (impl->source == NULL) return -errno; pw_stream_add_listener(impl->source, &impl->source_listener, &source_events, impl); if (impl->monitor_mode) { impl->playback = NULL; } else { impl->playback = pw_stream_new(impl->core, "Echo-Cancel Playback", impl->playback_props); impl->playback_props = NULL; if (impl->playback == NULL) return -errno; pw_stream_add_listener(impl->playback, &impl->playback_listener, &playback_events, impl); } impl->sink = pw_stream_new(impl->core, "Echo-Cancel Sink", impl->sink_props); impl->sink_props = NULL; if (impl->sink == NULL) return -errno; pw_stream_add_listener(impl->sink, &impl->sink_listener, &sink_events, impl); n_params = 0; spa_pod_dynamic_builder_init(&b, NULL, 0, 4096); if (n_params < MAX_PARAMS) { offsets[n_params++] = b.b.state.offset; spa_format_audio_raw_build(&b.b, SPA_PARAM_EnumFormat, &impl->capture_info); } int nbr_of_external_props = spa_audio_aec_enum_props(impl->aec, 0, NULL); for (int i = 0; i < nbr_of_external_props; i++) { if (n_params < MAX_PARAMS) { offsets[n_params++] = b.b.state.offset; spa_audio_aec_enum_props(impl->aec, i, &b.b); } } if (n_params < MAX_PARAMS) { offsets[n_params++] = b.b.state.offset; spa_pod_builder_add_object(&b.b, SPA_TYPE_OBJECT_PropInfo, SPA_PARAM_PropInfo, SPA_PROP_INFO_name, SPA_POD_String("debug.aec.wav-path"), SPA_PROP_INFO_description, SPA_POD_String("Path to WAV file"), SPA_PROP_INFO_type, SPA_POD_String(impl->wav_path), SPA_PROP_INFO_params, SPA_POD_Bool(true)); } if (n_params < MAX_PARAMS) { offsets[n_params++] = b.b.state.offset; get_props_param(impl, &b.b); } for (i = 0; i < n_params; i++) params[i] = spa_pod_builder_deref(&b.b, offsets[i]); if ((res = pw_stream_connect(impl->capture, PW_DIRECTION_INPUT, PW_ID_ANY, PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_MAP_BUFFERS | PW_STREAM_FLAG_RT_PROCESS, params, n_params)) < 0) { spa_pod_dynamic_builder_clean(&b); return res; } offsets[0] = b.b.state.offset; spa_format_audio_raw_build(&b.b, SPA_PARAM_EnumFormat, &impl->source_info); for (i = 0; i < n_params; i++) params[i] = spa_pod_builder_deref(&b.b, offsets[i]); if ((res = pw_stream_connect(impl->source, PW_DIRECTION_OUTPUT, PW_ID_ANY, PW_STREAM_FLAG_MAP_BUFFERS | PW_STREAM_FLAG_RT_PROCESS | PW_STREAM_FLAG_ASYNC, params, n_params)) < 0) { spa_pod_dynamic_builder_clean(&b); return res; } offsets[0] = b.b.state.offset; spa_format_audio_raw_build(&b.b, SPA_PARAM_EnumFormat, &impl->sink_info); for (i = 0; i < n_params; i++) params[i] = spa_pod_builder_deref(&b.b, offsets[i]); if ((res = pw_stream_connect(impl->sink, PW_DIRECTION_INPUT, PW_ID_ANY, impl->playback != NULL ? PW_STREAM_FLAG_MAP_BUFFERS | PW_STREAM_FLAG_RT_PROCESS : PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_MAP_BUFFERS | PW_STREAM_FLAG_RT_PROCESS, params, n_params)) < 0) { spa_pod_dynamic_builder_clean(&b); return res; } offsets[0] = b.b.state.offset; spa_format_audio_raw_build(&b.b, SPA_PARAM_EnumFormat, &impl->playback_info); for (i = 0; i < n_params; i++) params[i] = spa_pod_builder_deref(&b.b, offsets[i]); if (impl->playback != NULL && (res = pw_stream_connect(impl->playback, PW_DIRECTION_OUTPUT, PW_ID_ANY, PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_MAP_BUFFERS | PW_STREAM_FLAG_RT_PROCESS | PW_STREAM_FLAG_ASYNC, params, n_params)) < 0) { spa_pod_dynamic_builder_clean(&b); return res; } spa_pod_dynamic_builder_clean(&b); impl->rec_ringsize = sizeof(float) * impl->max_buffer_size * impl->rec_info.rate / 1000; impl->play_ringsize = sizeof(float) * ((impl->max_buffer_size * impl->play_info.rate / 1000) + impl->buffer_delay); impl->out_ringsize = sizeof(float) * impl->max_buffer_size * impl->out_info.rate / 1000; for (i = 0; i < impl->rec_info.channels; i++) impl->rec_buffer[i] = malloc(impl->rec_ringsize); for (i = 0; i < impl->play_info.channels; i++) impl->play_buffer[i] = malloc(impl->play_ringsize); for (i = 0; i < impl->out_info.channels; i++) impl->out_buffer[i] = malloc(impl->out_ringsize); reset_buffers(impl); return 0; } static void core_error(void *data, uint32_t id, int seq, int res, const char *message) { struct impl *impl = data; if (res == -ENOENT) { pw_log_info("id:%u seq:%d res:%d (%s): %s", id, seq, res, spa_strerror(res), message); } else { pw_log_warn("error id:%u seq:%d res:%d (%s): %s", id, seq, res, spa_strerror(res), message); } if (id == PW_ID_CORE && res == -EPIPE) pw_impl_module_schedule_destroy(impl->module); } static const struct pw_core_events core_events = { PW_VERSION_CORE_EVENTS, .error = core_error, }; static void core_destroy(void *d) { struct impl *impl = d; spa_hook_remove(&impl->core_listener); impl->core = NULL; pw_impl_module_schedule_destroy(impl->module); } static const struct pw_proxy_events core_proxy_events = { .destroy = core_destroy, }; static void impl_destroy(struct impl *impl) { uint32_t i; if (impl->capture) pw_stream_destroy(impl->capture); if (impl->source) pw_stream_destroy(impl->source); if (impl->playback) pw_stream_destroy(impl->playback); if (impl->sink) pw_stream_destroy(impl->sink); if (impl->core && impl->do_disconnect) pw_core_disconnect(impl->core); if (impl->spa_handle) spa_plugin_loader_unload(impl->loader, impl->spa_handle); pw_properties_free(impl->capture_props); pw_properties_free(impl->source_props); pw_properties_free(impl->playback_props); pw_properties_free(impl->sink_props); for (i = 0; i < impl->rec_info.channels; i++) free(impl->rec_buffer[i]); for (i = 0; i < impl->play_info.channels; i++) free(impl->play_buffer[i]); for (i = 0; i < impl->out_info.channels; i++) free(impl->out_buffer[i]); free(impl); } static void module_destroy(void *data) { struct impl *impl = data; spa_hook_remove(&impl->module_listener); impl_destroy(impl); } static const struct pw_impl_module_events module_events = { PW_VERSION_IMPL_MODULE_EVENTS, .destroy = module_destroy, }; static int parse_audio_info(struct pw_properties *props, struct spa_audio_info_raw *info) { return spa_audio_info_raw_init_dict_keys(info, &SPA_DICT_ITEMS( SPA_DICT_ITEM(SPA_KEY_AUDIO_FORMAT, "F32P"), SPA_DICT_ITEM(SPA_KEY_AUDIO_RATE, SPA_STRINGIFY(DEFAULT_RATE)), SPA_DICT_ITEM(SPA_KEY_AUDIO_POSITION, DEFAULT_POSITION)), &props->dict, SPA_KEY_AUDIO_RATE, SPA_KEY_AUDIO_CHANNELS, SPA_KEY_AUDIO_LAYOUT, SPA_KEY_AUDIO_POSITION, NULL); } static void copy_props(struct impl *impl, struct pw_properties *props, const char *key) { const char *str; if ((str = pw_properties_get(props, key)) != NULL) { if (pw_properties_get(impl->capture_props, key) == NULL) pw_properties_set(impl->capture_props, key, str); if (pw_properties_get(impl->source_props, key) == NULL) pw_properties_set(impl->source_props, key, str); if (pw_properties_get(impl->playback_props, key) == NULL) pw_properties_set(impl->playback_props, key, str); if (pw_properties_get(impl->sink_props, key) == NULL) pw_properties_set(impl->sink_props, key, str); } } SPA_EXPORT int pipewire__module_init(struct pw_impl_module *module, const char *args) { struct pw_context *context = pw_impl_module_get_context(module); struct pw_properties *props, *aec_props; struct spa_audio_info_raw info; struct impl *impl; uint32_t id = pw_global_get_id(pw_impl_module_get_global(module)); uint32_t pid = getpid(); const char *str; const char *path; int res = 0; struct spa_handle *handle = NULL; void *iface; PW_LOG_TOPIC_INIT(mod_topic); impl = calloc(1, sizeof(struct impl)); if (impl == NULL) return -errno; pw_log_debug("module %p: new %s", impl, args); if (args) props = pw_properties_new_string(args); else props = pw_properties_new(NULL, NULL); if (props == NULL) { res = -errno; pw_log_error( "can't create properties: %m"); goto error; } impl->capture_props = pw_properties_new(NULL, NULL); impl->source_props = pw_properties_new(NULL, NULL); impl->playback_props = pw_properties_new(NULL, NULL); impl->sink_props = pw_properties_new(NULL, NULL); if (impl->source_props == NULL || impl->sink_props == NULL || impl->capture_props == NULL || impl->playback_props == NULL) { res = -errno; pw_log_error( "can't create properties: %m"); goto error; } impl->monitor_mode = false; if ((str = pw_properties_get(props, "monitor.mode")) != NULL) impl->monitor_mode = pw_properties_parse_bool(str); impl->module = module; impl->context = context; if (pw_properties_get(props, PW_KEY_NODE_GROUP) == NULL) pw_properties_setf(props, PW_KEY_NODE_GROUP, "echo-cancel-%u-%u", pid, id); if (pw_properties_get(props, PW_KEY_NODE_LINK_GROUP) == NULL) pw_properties_setf(props, PW_KEY_NODE_LINK_GROUP, "echo-cancel-%u-%u", pid, id); if (pw_properties_get(props, PW_KEY_NODE_VIRTUAL) == NULL) pw_properties_set(props, PW_KEY_NODE_VIRTUAL, "true"); if (pw_properties_get(props, "resample.prefill") == NULL) pw_properties_set(props, "resample.prefill", "true"); if ((res = parse_audio_info(props, &info)) < 0) { pw_log_error( "can't parse format: %s", spa_strerror(res)); goto error; } impl->capture_info = info; impl->source_info = info; impl->sink_info = info; impl->playback_info = info; if ((str = pw_properties_get(props, "capture.props")) != NULL) pw_properties_update_string(impl->capture_props, str, strlen(str)); if ((str = pw_properties_get(props, "source.props")) != NULL) pw_properties_update_string(impl->source_props, str, strlen(str)); if ((str = pw_properties_get(props, "sink.props")) != NULL) pw_properties_update_string(impl->sink_props, str, strlen(str)); if ((str = pw_properties_get(props, "playback.props")) != NULL) pw_properties_update_string(impl->playback_props, str, strlen(str)); if (pw_properties_get(impl->capture_props, PW_KEY_NODE_NAME) == NULL) pw_properties_set(impl->capture_props, PW_KEY_NODE_NAME, "echo-cancel-capture"); if (pw_properties_get(impl->capture_props, PW_KEY_NODE_DESCRIPTION) == NULL) pw_properties_set(impl->capture_props, PW_KEY_NODE_DESCRIPTION, "Echo-Cancel Capture"); if (pw_properties_get(impl->capture_props, PW_KEY_NODE_PASSIVE) == NULL) pw_properties_set(impl->capture_props, PW_KEY_NODE_PASSIVE, "true"); if (pw_properties_get(impl->source_props, PW_KEY_NODE_NAME) == NULL) pw_properties_set(impl->source_props, PW_KEY_NODE_NAME, "echo-cancel-source"); if (pw_properties_get(impl->source_props, PW_KEY_NODE_DESCRIPTION) == NULL) pw_properties_set(impl->source_props, PW_KEY_NODE_DESCRIPTION, "Echo-Cancel Source"); if (pw_properties_get(impl->source_props, PW_KEY_MEDIA_CLASS) == NULL) pw_properties_set(impl->source_props, PW_KEY_MEDIA_CLASS, "Audio/Source"); if (pw_properties_get(impl->playback_props, PW_KEY_NODE_NAME) == NULL) pw_properties_set(impl->playback_props, PW_KEY_NODE_NAME, "echo-cancel-playback"); if (pw_properties_get(impl->playback_props, PW_KEY_NODE_DESCRIPTION) == NULL) pw_properties_set(impl->playback_props, PW_KEY_NODE_DESCRIPTION, "Echo-Cancel Playback"); if (pw_properties_get(impl->playback_props, PW_KEY_NODE_PASSIVE) == NULL) pw_properties_set(impl->playback_props, PW_KEY_NODE_PASSIVE, "true"); if (pw_properties_get(impl->sink_props, PW_KEY_NODE_NAME) == NULL) pw_properties_set(impl->sink_props, PW_KEY_NODE_NAME, "echo-cancel-sink"); if (pw_properties_get(impl->sink_props, PW_KEY_NODE_DESCRIPTION) == NULL) pw_properties_set(impl->sink_props, PW_KEY_NODE_DESCRIPTION, "Echo-Cancel Sink"); if (pw_properties_get(impl->sink_props, PW_KEY_MEDIA_CLASS) == NULL) pw_properties_set(impl->sink_props, PW_KEY_MEDIA_CLASS, impl->monitor_mode ? "Stream/Input/Audio" : "Audio/Sink"); if (impl->monitor_mode) { if (pw_properties_get(impl->sink_props, PW_KEY_NODE_PASSIVE) == NULL) pw_properties_set(impl->sink_props, PW_KEY_NODE_PASSIVE, "true"); if (pw_properties_get(impl->sink_props, PW_KEY_STREAM_MONITOR) == NULL) pw_properties_set(impl->sink_props, PW_KEY_STREAM_MONITOR, "true"); if (pw_properties_get(impl->sink_props, PW_KEY_STREAM_CAPTURE_SINK) == NULL) pw_properties_set(impl->sink_props, PW_KEY_STREAM_CAPTURE_SINK, "true"); } copy_props(impl, props, PW_KEY_NODE_GROUP); copy_props(impl, props, PW_KEY_NODE_LINK_GROUP); copy_props(impl, props, PW_KEY_NODE_VIRTUAL); copy_props(impl, props, SPA_KEY_AUDIO_CHANNELS); copy_props(impl, props, SPA_KEY_AUDIO_LAYOUT); copy_props(impl, props, SPA_KEY_AUDIO_POSITION); copy_props(impl, props, "resample.prefill"); impl->max_buffer_size = pw_properties_get_uint32(props,"buffer.max_size", MAX_BUFSIZE_MS); if ((str = pw_properties_get(props, "buffer.play_delay")) != NULL) { int req_num, req_denom; if (sscanf(str, "%u/%u", &req_num, &req_denom) == 2) { if (req_denom != 0) { impl->buffer_delay = (info.rate * req_num) / req_denom; } else { impl->buffer_delay = DELAY_MS * info.rate / 1000; pw_log_warn("Sample rate for buffer.play_delay is 0 using default"); } } else { impl->buffer_delay = DELAY_MS * info.rate / 1000; pw_log_warn("Wrong value/format for buffer.play_delay using default"); } } else { impl->buffer_delay = DELAY_MS * info.rate / 1000; } if ((str = pw_properties_get(impl->capture_props, SPA_KEY_AUDIO_POSITION)) != NULL) { spa_audio_parse_position_n(str, strlen(str), impl->capture_info.position, SPA_N_ELEMENTS(impl->capture_info.position), &impl->capture_info.channels); } if ((str = pw_properties_get(impl->capture_props, SPA_KEY_AUDIO_LAYOUT)) != NULL) { spa_audio_parse_layout(str, impl->capture_info.position, SPA_N_ELEMENTS(impl->capture_info.position), &impl->capture_info.channels); } if ((str = pw_properties_get(impl->source_props, SPA_KEY_AUDIO_POSITION)) != NULL) { spa_audio_parse_position_n(str, strlen(str), impl->source_info.position, SPA_N_ELEMENTS(impl->source_info.position), &impl->source_info.channels); } if ((str = pw_properties_get(impl->source_props, SPA_KEY_AUDIO_LAYOUT)) != NULL) { spa_audio_parse_layout(str, impl->source_info.position, SPA_N_ELEMENTS(impl->source_info.position), &impl->source_info.channels); } if ((str = pw_properties_get(impl->sink_props, SPA_KEY_AUDIO_POSITION)) != NULL) { spa_audio_parse_position_n(str, strlen(str), impl->sink_info.position, SPA_N_ELEMENTS(impl->sink_info.position), &impl->sink_info.channels); impl->playback_info = impl->sink_info; } if ((str = pw_properties_get(impl->sink_props, SPA_KEY_AUDIO_LAYOUT)) != NULL) { spa_audio_parse_layout(str, impl->sink_info.position, SPA_N_ELEMENTS(impl->sink_info.position), &impl->sink_info.channels); impl->playback_info = impl->sink_info; } if ((str = pw_properties_get(impl->playback_props, SPA_KEY_AUDIO_POSITION)) != NULL) { spa_audio_parse_position_n(str, strlen(str), impl->playback_info.position, SPA_N_ELEMENTS(impl->playback_info.position), &impl->playback_info.channels); } if ((str = pw_properties_get(impl->playback_props, SPA_KEY_AUDIO_LAYOUT)) != NULL) { spa_audio_parse_layout(str, impl->playback_info.position, SPA_N_ELEMENTS(impl->playback_info.position), &impl->playback_info.channels); } if (impl->playback_info.channels != impl->sink_info.channels) impl->playback_info = impl->sink_info; if ((str = pw_properties_get(props, "aec.method")) != NULL) pw_log_warn("aec.method is not supported anymore use library.name"); /* Use webrtc as default */ if ((path = pw_properties_get(props, "library.name")) == NULL) path = "aec/libspa-aec-webrtc"; const struct spa_support *support; uint32_t n_support; support = pw_context_get_support(context, &n_support); impl->loader = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_PluginLoader); if (impl->loader == NULL) { pw_log_error("a plugin loader is needed"); return -EINVAL; } struct spa_dict_item dict_items[] = { { SPA_KEY_LIBRARY_NAME, path }, }; struct spa_dict dict = SPA_DICT_INIT_ARRAY(dict_items); handle = spa_plugin_loader_load(impl->loader, SPA_NAME_AEC, &dict); if (handle == NULL) { pw_log_error("aec plugin %s not available library.name %s", SPA_NAME_AEC, path); return -ENOENT; } if ((res = spa_handle_get_interface(handle, SPA_TYPE_INTERFACE_AUDIO_AEC, &iface)) < 0) { pw_log_error("can't get %s interface %d", SPA_TYPE_INTERFACE_AUDIO_AEC, res); return res; } impl->aec = iface; impl->spa_handle = handle; if (impl->aec->iface.version > SPA_VERSION_AUDIO_AEC) { pw_log_error("codec plugin %s has incompatible ABI version (%d > %d)", SPA_NAME_AEC, impl->aec->iface.version, SPA_VERSION_AUDIO_AEC); res = -ENOENT; goto error; } pw_log_info("Using plugin AEC %s with version %d", impl->aec->name, impl->aec->iface.version); if ((str = pw_properties_get(props, "aec.args")) != NULL) aec_props = pw_properties_new_string(str); else aec_props = pw_properties_new(NULL, NULL); if (spa_interface_callback_check(&impl->aec->iface, struct spa_audio_aec_methods, init2, 3)) { impl->rec_info = impl->capture_info; impl->out_info = impl->source_info; impl->play_info = impl->sink_info; res = spa_audio_aec_init2(impl->aec, &aec_props->dict, &impl->rec_info, &impl->out_info, &impl->play_info); if (impl->sink_info.channels != impl->play_info.channels) impl->sink_info = impl->play_info; if (impl->playback_info.channels != impl->play_info.channels) impl->playback_info = impl->play_info; if (impl->capture_info.channels != impl->rec_info.channels) impl->capture_info = impl->rec_info; if (impl->source_info.channels != impl->out_info.channels) impl->source_info = impl->out_info; } else { if (impl->source_info.channels != impl->sink_info.channels) impl->source_info = impl->sink_info; if (impl->capture_info.channels != impl->source_info.channels) impl->capture_info = impl->source_info; if (impl->playback_info.channels != impl->sink_info.channels) impl->playback_info = impl->sink_info; info = impl->playback_info; res = spa_audio_aec_init(impl->aec, &aec_props->dict, &info); impl->rec_info = info; impl->out_info = info; impl->play_info = info; } pw_properties_free(aec_props); if (res < 0) { pw_log_error("aec plugin %s create failed: %s", impl->aec->name, spa_strerror(res)); goto error; } if (impl->aec->latency) { unsigned int num, denom, req_num, req_denom; unsigned int factor = 0; unsigned int new_num = 0; spa_assert_se(sscanf(impl->aec->latency, "%u/%u", &num, &denom) == 2); if ((str = pw_properties_get(props, PW_KEY_NODE_LATENCY)) != NULL) { sscanf(str, "%u/%u", &req_num, &req_denom); factor = (req_num * denom) / (req_denom * num); new_num = req_num / factor * factor; } if (factor == 0 || new_num == 0) { pw_log_info("Setting node latency to %s", impl->aec->latency); pw_properties_set(props, PW_KEY_NODE_LATENCY, impl->aec->latency); impl->aec_blocksize = sizeof(float) * info.rate * num / denom; } else { pw_log_info("Setting node latency to %u/%u", new_num, req_denom); pw_properties_setf(props, PW_KEY_NODE_LATENCY, "%u/%u", new_num, req_denom); impl->aec_blocksize = sizeof(float) * info.rate * num / denom * factor; } } else { /* Implementation doesn't care about the block size */ impl->aec_blocksize = 0; } copy_props(impl, props, PW_KEY_NODE_LATENCY); impl->core = pw_context_get_object(impl->context, PW_TYPE_INTERFACE_Core); if (impl->core == NULL) { str = pw_properties_get(props, PW_KEY_REMOTE_NAME); impl->core = pw_context_connect(impl->context, pw_properties_new( PW_KEY_REMOTE_NAME, str, NULL), 0); impl->do_disconnect = true; } if (impl->core == NULL) { res = -errno; pw_log_error("can't connect: %m"); goto error; } pw_properties_free(props); pw_proxy_add_listener((struct pw_proxy*)impl->core, &impl->core_proxy_listener, &core_proxy_events, impl); pw_core_add_listener(impl->core, &impl->core_listener, &core_events, impl); setup_streams(impl); pw_impl_module_add_listener(module, &impl->module_listener, &module_events, impl); pw_impl_module_update_properties(module, &SPA_DICT_INIT_ARRAY(module_props)); return 0; error: pw_properties_free(props); impl_destroy(impl); return res; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/modules/module-example-filter.c000066400000000000000000000443471511204443500273160ustar00rootroot00000000000000/* PipeWire */ /* SPDX-FileCopyrightText: Copyright © 2023 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /** \page page_module_example_filter Example Filter * * The example filter is a good starting point for writing a custom * filter. We refer to the source code for more information. * * ## Module Name * * `libpipewire-module-example-filter` * * ## Module Options * * - `node.description`: a human readable name for the filter streams * - `capture.props = {}`: properties to be passed to the input stream * - `playback.props = {}`: properties to be passed to the output stream * * ## General options * * Options with well-known behavior. Most options can be added to the global * configuration or the individual streams: * * - \ref PW_KEY_REMOTE_NAME * - \ref PW_KEY_AUDIO_RATE * - \ref PW_KEY_AUDIO_CHANNELS * - \ref SPA_KEY_AUDIO_LAYOUT * - \ref SPA_KEY_AUDIO_POSITION * - \ref PW_KEY_MEDIA_NAME * - \ref PW_KEY_NODE_LATENCY * - \ref PW_KEY_NODE_DESCRIPTION * - \ref PW_KEY_NODE_GROUP * - \ref PW_KEY_NODE_LINK_GROUP * - \ref PW_KEY_NODE_VIRTUAL * - \ref PW_KEY_NODE_NAME : See notes below. If not specified, defaults to * 'filter-PID-MODULEID'. * * Stream only properties: * * - \ref PW_KEY_MEDIA_CLASS * - \ref PW_KEY_NODE_NAME : if not given per stream, the global node.name will be * prefixed with 'input.' and 'output.' to generate a capture and playback * stream node.name respectively. * * ## Example configuration of a virtual source * *\code{.unparsed} * # ~/.config/pipewire/pipewire.conf.d/my-example-filter.conf * * context.modules = [ * { name = libpipewire-module-example-filter * args = { * node.description = "Example Filter" * capture.props = { * audio.position = [ FL FR ] * node.passive = true * } * playback.props = { * node.name = "Example Filter" * media.class = "Audio/Source" * audio.position = [ FL FR ] * } * } * } * ] *\endcode * *\code{.unparsed} * pw-cli -m lm libpipewire-module-example-filter '{ audio.position=[FL FR] }' *\endcode * */ #define NAME "example-filter" PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); #define PW_LOG_TOPIC_DEFAULT mod_topic #define DEFAULT_POSITION "[ FL FR ]" static const struct spa_dict_item module_props[] = { { PW_KEY_MODULE_AUTHOR, "Wim Taymans " }, { PW_KEY_MODULE_DESCRIPTION, "Create example filter streams" }, { PW_KEY_MODULE_USAGE, " ( remote.name= ) " "( node.latency= ) " "( node.description= ) " "( audio.rate= ) " "( audio.channels= ) " "( audio.position= ) " "( capture.props= ) " "( playback.props= ) " }, { PW_KEY_MODULE_VERSION, PACKAGE_VERSION }, }; #include #include #include #include #include #include #include #include #include struct impl { struct pw_context *context; struct pw_impl_module *module; struct spa_hook module_listener; struct pw_core *core; struct spa_hook core_proxy_listener; struct spa_hook core_listener; struct pw_properties *capture_props; struct pw_stream *capture; struct spa_hook capture_listener; struct spa_audio_info_raw capture_info; struct pw_properties *playback_props; struct pw_stream *playback; struct spa_hook playback_listener; struct spa_audio_info_raw playback_info; struct spa_latency_info latency[2]; struct spa_process_latency_info process_latency; unsigned int do_disconnect:1; }; static void capture_destroy(void *d) { struct impl *impl = d; spa_hook_remove(&impl->capture_listener); impl->capture = NULL; } static void capture_process(void *d) { struct impl *impl = d; int res; if ((res = pw_stream_trigger_process(impl->playback)) < 0) { while (true) { struct pw_buffer *t; if ((t = pw_stream_dequeue_buffer(impl->capture)) == NULL) break; pw_stream_queue_buffer(impl->capture, t); } } } static void playback_process(void *d) { struct impl *impl = d; struct pw_buffer *in, *out; uint32_t i; in = NULL; while (true) { struct pw_buffer *t; if ((t = pw_stream_dequeue_buffer(impl->capture)) == NULL) break; if (in) pw_stream_queue_buffer(impl->capture, in); in = t; } if (in == NULL) pw_log_debug("%p: out of capture buffers: %m", impl); if ((out = pw_stream_dequeue_buffer(impl->playback)) == NULL) pw_log_debug("%p: out of playback buffers: %m", impl); if (in != NULL && out != NULL) { uint32_t outsize = UINT32_MAX; int32_t stride = 0; struct spa_data *d; const void *src[in->buffer->n_datas]; void *dst[out->buffer->n_datas]; for (i = 0; i < in->buffer->n_datas; i++) { uint32_t offs, size; d = &in->buffer->datas[i]; offs = SPA_MIN(d->chunk->offset, d->maxsize); size = SPA_MIN(d->chunk->size, d->maxsize - offs); src[i] = SPA_PTROFF(d->data, offs, void); outsize = SPA_MIN(outsize, size); stride = SPA_MAX(stride, d->chunk->stride); } for (i = 0; i < out->buffer->n_datas; i++) { d = &out->buffer->datas[i]; outsize = SPA_MIN(outsize, d->maxsize); dst[i] = d->data; if (i < in->buffer->n_datas) { /* do filtering here, samples are a single * channel float */ memcpy(dst[i], src[i], outsize); } else { memset(dst[i], 0, outsize); } d->chunk->offset = 0; d->chunk->size = outsize; d->chunk->stride = stride; } } if (in != NULL) pw_stream_queue_buffer(impl->capture, in); if (out != NULL) pw_stream_queue_buffer(impl->playback, out); } static void update_latency(struct impl *impl, enum spa_direction direction) { struct spa_latency_info latency; uint8_t buffer[1024]; struct spa_pod_builder b; const struct spa_pod *params[1]; struct pw_stream *s = direction == SPA_DIRECTION_OUTPUT ? impl->playback : impl->capture; spa_pod_builder_init(&b, buffer, sizeof(buffer)); latency = impl->latency[direction]; spa_process_latency_info_add(&impl->process_latency, &latency); params[0] = spa_latency_build(&b, SPA_PARAM_Latency, &latency); pw_stream_update_params(s, params, 1); } static void param_latency_changed(struct impl *impl, const struct spa_pod *param) { struct spa_latency_info latency; if (param == NULL || spa_latency_parse(param, &latency) < 0) return; impl->latency[latency.direction] = latency; update_latency(impl, latency.direction); } static void stream_state_changed(void *data, enum pw_stream_state old, enum pw_stream_state state, const char *error) { struct impl *impl = data; switch (state) { case PW_STREAM_STATE_PAUSED: pw_stream_flush(impl->playback, false); pw_stream_flush(impl->capture, false); break; case PW_STREAM_STATE_UNCONNECTED: pw_log_info("module %p: unconnected", impl); pw_impl_module_schedule_destroy(impl->module); break; case PW_STREAM_STATE_ERROR: pw_log_info("module %p: error: %s", impl, error); break; default: break; } } static void capture_param_changed(void *data, uint32_t id, const struct spa_pod *param) { struct impl *impl = data; switch (id) { case SPA_PARAM_Format: { struct spa_audio_info_raw info; if (param == NULL) return; if (spa_format_audio_raw_parse(param, &info) < 0) return; if (info.rate == 0 || info.channels == 0) return; break; } case SPA_PARAM_Latency: param_latency_changed(impl, param); break; } } static const struct pw_stream_events in_stream_events = { PW_VERSION_STREAM_EVENTS, .destroy = capture_destroy, .process = capture_process, .state_changed = stream_state_changed, .param_changed = capture_param_changed, }; static void playback_destroy(void *d) { struct impl *impl = d; spa_hook_remove(&impl->playback_listener); impl->playback = NULL; } static void playback_param_changed(void *data, uint32_t id, const struct spa_pod *param) { struct impl *impl = data; switch (id) { case SPA_PARAM_Latency: param_latency_changed(impl, param); break; } } static const struct pw_stream_events out_stream_events = { PW_VERSION_STREAM_EVENTS, .destroy = playback_destroy, .process = playback_process, .state_changed = stream_state_changed, .param_changed = playback_param_changed, }; static int setup_streams(struct impl *impl) { int res; uint32_t n_params; const struct spa_pod *params[1]; uint8_t buffer[1024]; struct spa_pod_builder b; impl->capture = pw_stream_new(impl->core, "filter capture", impl->capture_props); impl->capture_props = NULL; if (impl->capture == NULL) return -errno; pw_stream_add_listener(impl->capture, &impl->capture_listener, &in_stream_events, impl); impl->playback = pw_stream_new(impl->core, "filter playback", impl->playback_props); impl->playback_props = NULL; if (impl->playback == NULL) return -errno; pw_stream_add_listener(impl->playback, &impl->playback_listener, &out_stream_events, impl); /* connect playback first to activate it before capture triggers it */ n_params = 0; spa_pod_builder_init(&b, buffer, sizeof(buffer)); params[n_params++] = spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat, &impl->playback_info); if ((res = pw_stream_connect(impl->playback, PW_DIRECTION_OUTPUT, PW_ID_ANY, PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_MAP_BUFFERS | PW_STREAM_FLAG_RT_PROCESS | PW_STREAM_FLAG_TRIGGER, params, n_params)) < 0) return res; n_params = 0; spa_pod_builder_init(&b, buffer, sizeof(buffer)); params[n_params++] = spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat, &impl->capture_info); if ((res = pw_stream_connect(impl->capture, PW_DIRECTION_INPUT, PW_ID_ANY, PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_MAP_BUFFERS | PW_STREAM_FLAG_RT_PROCESS | PW_STREAM_FLAG_ASYNC, params, n_params)) < 0) return res; return 0; } static void core_error(void *data, uint32_t id, int seq, int res, const char *message) { struct impl *impl = data; if (res == -ENOENT) { pw_log_info("message id:%u seq:%d res:%d (%s): %s", id, seq, res, spa_strerror(res), message); } else { pw_log_warn("error id:%u seq:%d res:%d (%s): %s", id, seq, res, spa_strerror(res), message); } if (id == PW_ID_CORE && res == -EPIPE) pw_impl_module_schedule_destroy(impl->module); } static const struct pw_core_events core_events = { PW_VERSION_CORE_EVENTS, .error = core_error, }; static void core_destroy(void *d) { struct impl *impl = d; spa_hook_remove(&impl->core_listener); impl->core = NULL; pw_impl_module_schedule_destroy(impl->module); } static const struct pw_proxy_events core_proxy_events = { .destroy = core_destroy, }; static void impl_destroy(struct impl *impl) { /* deactivate both streams before destroying any of them */ if (impl->capture) pw_stream_set_active(impl->capture, false); if (impl->playback) pw_stream_set_active(impl->playback, false); if (impl->capture) pw_stream_destroy(impl->capture); if (impl->playback) pw_stream_destroy(impl->playback); if (impl->core && impl->do_disconnect) pw_core_disconnect(impl->core); pw_properties_free(impl->capture_props); pw_properties_free(impl->playback_props); free(impl); } static void module_destroy(void *data) { struct impl *impl = data; spa_hook_remove(&impl->module_listener); impl_destroy(impl); } static const struct pw_impl_module_events module_events = { PW_VERSION_IMPL_MODULE_EVENTS, .destroy = module_destroy, }; static int parse_audio_info(struct pw_properties *props, struct spa_audio_info_raw *info) { return spa_audio_info_raw_init_dict_keys(info, &SPA_DICT_ITEMS( SPA_DICT_ITEM(SPA_KEY_AUDIO_FORMAT, "F32P"), SPA_DICT_ITEM(SPA_KEY_AUDIO_POSITION, DEFAULT_POSITION)), &props->dict, SPA_KEY_AUDIO_RATE, SPA_KEY_AUDIO_CHANNELS, SPA_KEY_AUDIO_LAYOUT, SPA_KEY_AUDIO_POSITION, NULL); } static void copy_props(struct impl *impl, struct pw_properties *props, const char *key) { const char *str; if ((str = pw_properties_get(props, key)) != NULL) { if (pw_properties_get(impl->capture_props, key) == NULL) pw_properties_set(impl->capture_props, key, str); if (pw_properties_get(impl->playback_props, key) == NULL) pw_properties_set(impl->playback_props, key, str); } } SPA_EXPORT int pipewire__module_init(struct pw_impl_module *module, const char *args) { struct pw_context *context = pw_impl_module_get_context(module); struct pw_properties *props; struct impl *impl; uint32_t id = pw_global_get_id(pw_impl_module_get_global(module)); uint32_t pid = getpid(); const char *str; int res; PW_LOG_TOPIC_INIT(mod_topic); impl = calloc(1, sizeof(struct impl)); if (impl == NULL) return -errno; pw_log_debug("module %p: new %s", impl, args); if (args) props = pw_properties_new_string(args); else props = pw_properties_new(NULL, NULL); if (props == NULL) { res = -errno; pw_log_error( "can't create properties: %m"); goto error; } impl->capture_props = pw_properties_new(NULL, NULL); impl->playback_props = pw_properties_new(NULL, NULL); if (impl->capture_props == NULL || impl->playback_props == NULL) { res = -errno; pw_log_error( "can't create properties: %m"); goto error; } impl->module = module; impl->context = context; impl->latency[SPA_DIRECTION_INPUT] = SPA_LATENCY_INFO(SPA_DIRECTION_INPUT); impl->latency[SPA_DIRECTION_OUTPUT] = SPA_LATENCY_INFO(SPA_DIRECTION_OUTPUT); if (pw_properties_get(props, PW_KEY_NODE_GROUP) == NULL) pw_properties_setf(props, PW_KEY_NODE_GROUP, "filter-%u-%u", pid, id); if (pw_properties_get(props, PW_KEY_NODE_LINK_GROUP) == NULL) pw_properties_setf(props, PW_KEY_NODE_LINK_GROUP, "filter-%u-%u", pid, id); if (pw_properties_get(props, PW_KEY_NODE_VIRTUAL) == NULL) pw_properties_set(props, PW_KEY_NODE_VIRTUAL, "true"); if (pw_properties_get(props, "resample.prefill") == NULL) pw_properties_set(props, "resample.prefill", "true"); if ((str = pw_properties_get(props, "capture.props")) != NULL) pw_properties_update_string(impl->capture_props, str, strlen(str)); if ((str = pw_properties_get(props, "playback.props")) != NULL) pw_properties_update_string(impl->playback_props, str, strlen(str)); copy_props(impl, props, PW_KEY_AUDIO_RATE); copy_props(impl, props, PW_KEY_AUDIO_CHANNELS); copy_props(impl, props, SPA_KEY_AUDIO_LAYOUT); copy_props(impl, props, SPA_KEY_AUDIO_POSITION); copy_props(impl, props, PW_KEY_NODE_DESCRIPTION); copy_props(impl, props, PW_KEY_NODE_GROUP); copy_props(impl, props, PW_KEY_NODE_LINK_GROUP); copy_props(impl, props, PW_KEY_NODE_LATENCY); copy_props(impl, props, PW_KEY_NODE_VIRTUAL); copy_props(impl, props, PW_KEY_MEDIA_NAME); copy_props(impl, props, "resample.prefill"); if ((str = pw_properties_get(props, PW_KEY_NODE_NAME)) == NULL) { pw_properties_setf(props, PW_KEY_NODE_NAME, "filter-%u-%u", pid, id); str = pw_properties_get(props, PW_KEY_NODE_NAME); } if (pw_properties_get(impl->capture_props, PW_KEY_NODE_NAME) == NULL) pw_properties_setf(impl->capture_props, PW_KEY_NODE_NAME, "input.%s", str); if (pw_properties_get(impl->playback_props, PW_KEY_NODE_NAME) == NULL) pw_properties_setf(impl->playback_props, PW_KEY_NODE_NAME, "output.%s", str); if (pw_properties_get(impl->capture_props, PW_KEY_NODE_DESCRIPTION) == NULL) pw_properties_set(impl->capture_props, PW_KEY_NODE_DESCRIPTION, str); if (pw_properties_get(impl->playback_props, PW_KEY_NODE_DESCRIPTION) == NULL) pw_properties_set(impl->playback_props, PW_KEY_NODE_DESCRIPTION, str); if ((res = parse_audio_info(impl->capture_props, &impl->capture_info)) < 0 || (res = parse_audio_info(impl->playback_props, &impl->playback_info)) < 0) { pw_log_error( "can't parse formats: %s", spa_strerror(res)); goto error; } if (!impl->capture_info.rate && !impl->playback_info.rate) { if (pw_properties_get(impl->playback_props, "resample.disable") == NULL) pw_properties_set(impl->playback_props, "resample.disable", "true"); if (pw_properties_get(impl->capture_props, "resample.disable") == NULL) pw_properties_set(impl->capture_props, "resample.disable", "true"); } else if (impl->capture_info.rate && !impl->playback_info.rate) impl->playback_info.rate = impl->capture_info.rate; else if (impl->playback_info.rate && !impl->capture_info.rate) impl->capture_info.rate = !impl->playback_info.rate; else if (impl->capture_info.rate != impl->playback_info.rate) { pw_log_warn("Both capture and playback rate are set, but" " they are different. Using the highest of two. This behaviour" " is deprecated, please use equal rates in the module config"); impl->playback_info.rate = impl->capture_info.rate = SPA_MAX(impl->playback_info.rate, impl->capture_info.rate); } if (pw_properties_get(impl->capture_props, PW_KEY_MEDIA_NAME) == NULL) pw_properties_setf(impl->capture_props, PW_KEY_MEDIA_NAME, "%s input", pw_properties_get(impl->capture_props, PW_KEY_NODE_DESCRIPTION)); if (pw_properties_get(impl->playback_props, PW_KEY_MEDIA_NAME) == NULL) pw_properties_setf(impl->playback_props, PW_KEY_MEDIA_NAME, "%s output", pw_properties_get(impl->playback_props, PW_KEY_NODE_DESCRIPTION)); impl->core = pw_context_get_object(impl->context, PW_TYPE_INTERFACE_Core); if (impl->core == NULL) { str = pw_properties_get(props, PW_KEY_REMOTE_NAME); impl->core = pw_context_connect(impl->context, pw_properties_new( PW_KEY_REMOTE_NAME, str, NULL), 0); impl->do_disconnect = true; } if (impl->core == NULL) { res = -errno; pw_log_error("can't connect: %m"); goto error; } pw_properties_free(props); pw_proxy_add_listener((struct pw_proxy*)impl->core, &impl->core_proxy_listener, &core_proxy_events, impl); pw_core_add_listener(impl->core, &impl->core_listener, &core_events, impl); setup_streams(impl); pw_impl_module_add_listener(module, &impl->module_listener, &module_events, impl); pw_impl_module_update_properties(module, &SPA_DICT_INIT_ARRAY(module_props)); return 0; error: pw_properties_free(props); impl_destroy(impl); return res; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/modules/module-example-sink.c000066400000000000000000000265131511204443500267700ustar00rootroot00000000000000/* PipeWire */ /* SPDX-FileCopyrightText: Copyright © 2021 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /** \page page_module_example_sink Example Sink * * The example sink is a good starting point for writing a custom * sink. We refer to the source code for more information. * * ## Module Name * * `libpipewire-module-example-sink` * * ## Module Options * * - `node.name`: a unique name for the stream * - `node.description`: a human readable name for the stream * - `stream.props = {}`: properties to be passed to the stream * * ## General options * * Options with well-known behavior. * * - \ref PW_KEY_REMOTE_NAME * - \ref PW_KEY_AUDIO_FORMAT * - \ref PW_KEY_AUDIO_RATE * - \ref PW_KEY_AUDIO_CHANNELS * - \ref SPA_KEY_AUDIO_LAYOUT * - \ref SPA_KEY_AUDIO_POSITION * - \ref PW_KEY_MEDIA_NAME * - \ref PW_KEY_NODE_LATENCY * - \ref PW_KEY_NODE_NAME * - \ref PW_KEY_NODE_DESCRIPTION * - \ref PW_KEY_NODE_GROUP * - \ref PW_KEY_NODE_VIRTUAL * - \ref PW_KEY_MEDIA_CLASS * * ## Example configuration * *\code{.unparsed} * # ~/.config/pipewire/pipewire.conf.d/my-example-sink.conf * * context.modules = [ * { name = libpipewire-module-example-sink * args = { * node.name = "example_sink" * node.description = "My Example Sink" * stream.props = { * audio.position = [ FL FR ] * } * } * } * ] *\endcode */ #define NAME "example-sink" PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); #define PW_LOG_TOPIC_DEFAULT mod_topic #define DEFAULT_FORMAT "S16" #define DEFAULT_RATE 48000 #define DEFAULT_CHANNELS 2 #define DEFAULT_POSITION "[ FL FR ]" #define MODULE_USAGE "( node.latency= ) " \ "( node.name= ) " \ "( node.description= ) " \ "( audio.format= ) " \ "( audio.rate= ) " \ "( audio.channels= ) " \ "( audio.position= ] " \ "( stream.props= ) " static const struct spa_dict_item module_props[] = { { PW_KEY_MODULE_AUTHOR, "Wim Taymans " }, { PW_KEY_MODULE_DESCRIPTION, "An example audio sink" }, { PW_KEY_MODULE_USAGE, MODULE_USAGE }, { PW_KEY_MODULE_VERSION, PACKAGE_VERSION }, }; struct impl { struct pw_context *context; struct pw_properties *props; struct pw_impl_module *module; struct spa_hook module_listener; struct pw_core *core; struct spa_hook core_proxy_listener; struct spa_hook core_listener; struct pw_properties *stream_props; struct pw_stream *stream; struct spa_hook stream_listener; struct spa_audio_info_raw info; uint32_t frame_size; unsigned int do_disconnect:1; }; static void stream_destroy(void *d) { struct impl *impl = d; spa_hook_remove(&impl->stream_listener); impl->stream = NULL; } static void stream_state_changed(void *d, enum pw_stream_state old, enum pw_stream_state state, const char *error) { struct impl *impl = d; switch (state) { case PW_STREAM_STATE_ERROR: case PW_STREAM_STATE_UNCONNECTED: pw_impl_module_schedule_destroy(impl->module); break; case PW_STREAM_STATE_PAUSED: case PW_STREAM_STATE_STREAMING: break; default: break; } } static void playback_stream_process(void *d) { struct impl *impl = d; struct pw_buffer *buf; struct spa_data *bd; void *data; uint32_t offs, size; if ((buf = pw_stream_dequeue_buffer(impl->stream)) == NULL) { pw_log_debug("out of buffers: %m"); return; } bd = &buf->buffer->datas[0]; offs = SPA_MIN(bd->chunk->offset, bd->maxsize); size = SPA_MIN(bd->chunk->size, bd->maxsize - offs); data = SPA_PTROFF(bd->data, offs, void); /* write buffer contents here */ pw_log_info("got buffer of size %d and data %p", size, data); pw_stream_queue_buffer(impl->stream, buf); } static const struct pw_stream_events playback_stream_events = { PW_VERSION_STREAM_EVENTS, .destroy = stream_destroy, .state_changed = stream_state_changed, .process = playback_stream_process }; static int create_stream(struct impl *impl) { int res; uint32_t n_params; const struct spa_pod *params[1]; uint8_t buffer[1024]; struct spa_pod_builder b; impl->stream = pw_stream_new(impl->core, "example sink", impl->stream_props); impl->stream_props = NULL; if (impl->stream == NULL) return -errno; pw_stream_add_listener(impl->stream, &impl->stream_listener, &playback_stream_events, impl); n_params = 0; spa_pod_builder_init(&b, buffer, sizeof(buffer)); params[n_params++] = spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat, &impl->info); if ((res = pw_stream_connect(impl->stream, PW_DIRECTION_INPUT, PW_ID_ANY, PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_MAP_BUFFERS | PW_STREAM_FLAG_RT_PROCESS, params, n_params)) < 0) return res; return 0; } static void core_error(void *data, uint32_t id, int seq, int res, const char *message) { struct impl *impl = data; pw_log_error("error id:%u seq:%d res:%d (%s): %s", id, seq, res, spa_strerror(res), message); if (id == PW_ID_CORE && res == -EPIPE) pw_impl_module_schedule_destroy(impl->module); } static const struct pw_core_events core_events = { PW_VERSION_CORE_EVENTS, .error = core_error, }; static void core_destroy(void *d) { struct impl *impl = d; spa_hook_remove(&impl->core_listener); impl->core = NULL; pw_impl_module_schedule_destroy(impl->module); } static const struct pw_proxy_events core_proxy_events = { .destroy = core_destroy, }; static void impl_destroy(struct impl *impl) { if (impl->stream) pw_stream_destroy(impl->stream); if (impl->core && impl->do_disconnect) pw_core_disconnect(impl->core); pw_properties_free(impl->stream_props); pw_properties_free(impl->props); free(impl); } static void module_destroy(void *data) { struct impl *impl = data; spa_hook_remove(&impl->module_listener); impl_destroy(impl); } static const struct pw_impl_module_events module_events = { PW_VERSION_IMPL_MODULE_EVENTS, .destroy = module_destroy, }; static int parse_audio_info(const struct pw_properties *props, struct spa_audio_info_raw *info) { return spa_audio_info_raw_init_dict_keys(info, &SPA_DICT_ITEMS( SPA_DICT_ITEM(SPA_KEY_AUDIO_FORMAT, DEFAULT_FORMAT), SPA_DICT_ITEM(SPA_KEY_AUDIO_RATE, SPA_STRINGIFY(DEFAULT_RATE)), SPA_DICT_ITEM(SPA_KEY_AUDIO_POSITION, DEFAULT_POSITION)), &props->dict, SPA_KEY_AUDIO_FORMAT, SPA_KEY_AUDIO_RATE, SPA_KEY_AUDIO_CHANNELS, SPA_KEY_AUDIO_LAYOUT, SPA_KEY_AUDIO_POSITION, NULL); } static int calc_frame_size(const struct spa_audio_info_raw *info) { int res = info->channels; switch (info->format) { case SPA_AUDIO_FORMAT_U8: case SPA_AUDIO_FORMAT_S8: case SPA_AUDIO_FORMAT_ALAW: case SPA_AUDIO_FORMAT_ULAW: return res; case SPA_AUDIO_FORMAT_S16: case SPA_AUDIO_FORMAT_S16_OE: case SPA_AUDIO_FORMAT_U16: return res * 2; case SPA_AUDIO_FORMAT_S24: case SPA_AUDIO_FORMAT_S24_OE: case SPA_AUDIO_FORMAT_U24: return res * 3; case SPA_AUDIO_FORMAT_S24_32: case SPA_AUDIO_FORMAT_S24_32_OE: case SPA_AUDIO_FORMAT_S32: case SPA_AUDIO_FORMAT_S32_OE: case SPA_AUDIO_FORMAT_U32: case SPA_AUDIO_FORMAT_U32_OE: case SPA_AUDIO_FORMAT_F32: case SPA_AUDIO_FORMAT_F32_OE: return res * 4; case SPA_AUDIO_FORMAT_F64: case SPA_AUDIO_FORMAT_F64_OE: return res * 8; default: return 0; } } static void copy_props(struct impl *impl, struct pw_properties *props, const char *key) { const char *str; if ((str = pw_properties_get(props, key)) != NULL) { if (pw_properties_get(impl->stream_props, key) == NULL) pw_properties_set(impl->stream_props, key, str); } } SPA_EXPORT int pipewire__module_init(struct pw_impl_module *module, const char *args) { struct pw_context *context = pw_impl_module_get_context(module); struct pw_properties *props = NULL; uint32_t id = pw_global_get_id(pw_impl_module_get_global(module)); uint32_t pid = getpid(); struct impl *impl; const char *str; int res; PW_LOG_TOPIC_INIT(mod_topic); impl = calloc(1, sizeof(struct impl)); if (impl == NULL) return -errno; pw_log_debug("module %p: new %s", impl, args); if (args == NULL) args = ""; props = pw_properties_new_string(args); if (props == NULL) { res = -errno; pw_log_error( "can't create properties: %m"); goto error; } impl->props = props; impl->stream_props = pw_properties_new(NULL, NULL); if (impl->stream_props == NULL) { res = -errno; pw_log_error( "can't create properties: %m"); goto error; } impl->module = module; impl->context = context; if (pw_properties_get(props, PW_KEY_NODE_VIRTUAL) == NULL) pw_properties_set(props, PW_KEY_NODE_VIRTUAL, "true"); if (pw_properties_get(props, PW_KEY_MEDIA_CLASS) == NULL) pw_properties_set(props, PW_KEY_MEDIA_CLASS, "Audio/Sink"); if (pw_properties_get(props, PW_KEY_NODE_NAME) == NULL) pw_properties_setf(props, PW_KEY_NODE_NAME, "example-sink-%u-%u", pid, id); if (pw_properties_get(props, PW_KEY_NODE_DESCRIPTION) == NULL) pw_properties_set(props, PW_KEY_NODE_DESCRIPTION, pw_properties_get(props, PW_KEY_NODE_NAME)); if ((str = pw_properties_get(props, "stream.props")) != NULL) pw_properties_update_string(impl->stream_props, str, strlen(str)); copy_props(impl, props, PW_KEY_AUDIO_RATE); copy_props(impl, props, PW_KEY_AUDIO_CHANNELS); copy_props(impl, props, SPA_KEY_AUDIO_LAYOUT); copy_props(impl, props, SPA_KEY_AUDIO_POSITION); copy_props(impl, props, PW_KEY_NODE_NAME); copy_props(impl, props, PW_KEY_NODE_DESCRIPTION); copy_props(impl, props, PW_KEY_NODE_GROUP); copy_props(impl, props, PW_KEY_NODE_LATENCY); copy_props(impl, props, PW_KEY_NODE_VIRTUAL); copy_props(impl, props, PW_KEY_MEDIA_CLASS); if ((res = parse_audio_info(impl->stream_props, &impl->info)) < 0) { pw_log_error( "can't parse format: %s", spa_strerror(res)); goto error; } impl->frame_size = calc_frame_size(&impl->info); if (impl->frame_size == 0) { res = -EINVAL; pw_log_error( "can't parse audio format"); goto error; } impl->core = pw_context_get_object(impl->context, PW_TYPE_INTERFACE_Core); if (impl->core == NULL) { str = pw_properties_get(props, PW_KEY_REMOTE_NAME); impl->core = pw_context_connect(impl->context, pw_properties_new( PW_KEY_REMOTE_NAME, str, NULL), 0); impl->do_disconnect = true; } if (impl->core == NULL) { res = -errno; pw_log_error("can't connect: %m"); goto error; } pw_proxy_add_listener((struct pw_proxy*)impl->core, &impl->core_proxy_listener, &core_proxy_events, impl); pw_core_add_listener(impl->core, &impl->core_listener, &core_events, impl); if ((res = create_stream(impl)) < 0) goto error; pw_impl_module_add_listener(module, &impl->module_listener, &module_events, impl); pw_impl_module_update_properties(module, &SPA_DICT_INIT_ARRAY(module_props)); return 0; error: impl_destroy(impl); return res; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/modules/module-example-source.c000066400000000000000000000267441511204443500273320ustar00rootroot00000000000000/* PipeWire */ /* SPDX-FileCopyrightText: Copyright © 2021 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /** \page page_module_example_source Example Source * * The example source is a good starting point for writing a custom * source. We refer to the source code for more information. * * ## Module Name * * `libpipewire-module-example-source` * * ## Module Options * * - `node.name`: a unique name for the stream * - `node.description`: a human readable name for the stream * - `stream.props = {}`: properties to be passed to the stream * * ## General options * * Options with well-known behavior. * * - \ref PW_KEY_REMOTE_NAME * - \ref PW_KEY_AUDIO_FORMAT * - \ref PW_KEY_AUDIO_RATE * - \ref PW_KEY_AUDIO_CHANNELS * - \ref SPA_KEY_AUDIO_LAYOUT * - \ref SPA_KEY_AUDIO_POSITION * - \ref PW_KEY_MEDIA_NAME * - \ref PW_KEY_NODE_LATENCY * - \ref PW_KEY_NODE_NAME * - \ref PW_KEY_NODE_DESCRIPTION * - \ref PW_KEY_NODE_GROUP * - \ref PW_KEY_NODE_VIRTUAL * - \ref PW_KEY_MEDIA_CLASS * * ## Example configuration * *\code{.unparsed} * # ~/.config/pipewire/pipewire.conf.d/my-example-source.conf * * context.modules = [ * { name = libpipewire-module-example-source * args = { * node.name = "example_source" * node.description = "My Example Source" * stream.props = { * audio.position = [ FL FR ] * } * } * } * ] *\endcode */ #define NAME "example-source" PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); #define PW_LOG_TOPIC_DEFAULT mod_topic #define DEFAULT_FORMAT "S16" #define DEFAULT_RATE 48000 #define DEFAULT_CHANNELS 2 #define DEFAULT_POSITION "[ FL FR ]" #define MODULE_USAGE "( node.latency= ) " \ "( node.name= ) " \ "( node.description= ) " \ "( audio.format= ) " \ "( audio.rate= ) " \ "( audio.channels= ) " \ "( audio.position= ) " \ "( stream.props= ) " static const struct spa_dict_item module_props[] = { { PW_KEY_MODULE_AUTHOR, "Wim Taymans " }, { PW_KEY_MODULE_DESCRIPTION, "An example audio source" }, { PW_KEY_MODULE_USAGE, MODULE_USAGE }, { PW_KEY_MODULE_VERSION, PACKAGE_VERSION }, }; struct impl { struct pw_context *context; struct pw_properties *props; struct pw_impl_module *module; struct spa_hook module_listener; struct pw_core *core; struct spa_hook core_proxy_listener; struct spa_hook core_listener; struct pw_properties *stream_props; struct pw_stream *stream; struct spa_hook stream_listener; struct spa_audio_info_raw info; uint32_t frame_size; unsigned int do_disconnect:1; unsigned int unloading:1; }; static void stream_destroy(void *d) { struct impl *impl = d; spa_hook_remove(&impl->stream_listener); impl->stream = NULL; } static void stream_state_changed(void *d, enum pw_stream_state old, enum pw_stream_state state, const char *error) { struct impl *impl = d; switch (state) { case PW_STREAM_STATE_ERROR: case PW_STREAM_STATE_UNCONNECTED: pw_impl_module_schedule_destroy(impl->module); break; case PW_STREAM_STATE_PAUSED: case PW_STREAM_STATE_STREAMING: break; default: break; } } static void capture_stream_process(void *d) { struct impl *impl = d; struct pw_buffer *buf; struct spa_data *bd; void *data; uint32_t size; if ((buf = pw_stream_dequeue_buffer(impl->stream)) == NULL) { pw_log_debug("out of buffers: %m"); return; } bd = &buf->buffer->datas[0]; data = bd->data; size = buf->requested ? buf->requested * impl->frame_size : bd->maxsize; /* fill buffer contents here */ pw_log_info("fill buffer data %p with up to %u bytes", data, size); bd->chunk->size = size; bd->chunk->stride = impl->frame_size; bd->chunk->offset = 0; buf->size = size / impl->frame_size; pw_stream_queue_buffer(impl->stream, buf); } static const struct pw_stream_events capture_stream_events = { PW_VERSION_STREAM_EVENTS, .destroy = stream_destroy, .state_changed = stream_state_changed, .process = capture_stream_process }; static int create_stream(struct impl *impl) { int res; uint32_t n_params; const struct spa_pod *params[1]; uint8_t buffer[1024]; struct spa_pod_builder b; impl->stream = pw_stream_new(impl->core, "example source", impl->stream_props); impl->stream_props = NULL; if (impl->stream == NULL) return -errno; pw_stream_add_listener(impl->stream, &impl->stream_listener, &capture_stream_events, impl); n_params = 0; spa_pod_builder_init(&b, buffer, sizeof(buffer)); params[n_params++] = spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat, &impl->info); if ((res = pw_stream_connect(impl->stream, PW_DIRECTION_OUTPUT, PW_ID_ANY, PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_MAP_BUFFERS | PW_STREAM_FLAG_RT_PROCESS, params, n_params)) < 0) return res; return 0; } static void core_error(void *data, uint32_t id, int seq, int res, const char *message) { struct impl *impl = data; pw_log_error("error id:%u seq:%d res:%d (%s): %s", id, seq, res, spa_strerror(res), message); if (id == PW_ID_CORE && res == -EPIPE) pw_impl_module_schedule_destroy(impl->module); } static const struct pw_core_events core_events = { PW_VERSION_CORE_EVENTS, .error = core_error, }; static void core_destroy(void *d) { struct impl *impl = d; spa_hook_remove(&impl->core_listener); impl->core = NULL; pw_impl_module_schedule_destroy(impl->module); } static const struct pw_proxy_events core_proxy_events = { .destroy = core_destroy, }; static void impl_destroy(struct impl *impl) { if (impl->stream) pw_stream_destroy(impl->stream); if (impl->core && impl->do_disconnect) pw_core_disconnect(impl->core); pw_properties_free(impl->stream_props); pw_properties_free(impl->props); free(impl); } static void module_destroy(void *data) { struct impl *impl = data; impl->unloading = true; spa_hook_remove(&impl->module_listener); impl_destroy(impl); } static const struct pw_impl_module_events module_events = { PW_VERSION_IMPL_MODULE_EVENTS, .destroy = module_destroy, }; static int parse_audio_info(const struct pw_properties *props, struct spa_audio_info_raw *info) { return spa_audio_info_raw_init_dict_keys(info, &SPA_DICT_ITEMS( SPA_DICT_ITEM(SPA_KEY_AUDIO_FORMAT, DEFAULT_FORMAT), SPA_DICT_ITEM(SPA_KEY_AUDIO_RATE, SPA_STRINGIFY(DEFAULT_RATE)), SPA_DICT_ITEM(SPA_KEY_AUDIO_POSITION, DEFAULT_POSITION)), &props->dict, SPA_KEY_AUDIO_FORMAT, SPA_KEY_AUDIO_RATE, SPA_KEY_AUDIO_CHANNELS, SPA_KEY_AUDIO_LAYOUT, SPA_KEY_AUDIO_POSITION, NULL); } static int calc_frame_size(const struct spa_audio_info_raw *info) { int res = info->channels; switch (info->format) { case SPA_AUDIO_FORMAT_U8: case SPA_AUDIO_FORMAT_S8: case SPA_AUDIO_FORMAT_ALAW: case SPA_AUDIO_FORMAT_ULAW: return res; case SPA_AUDIO_FORMAT_S16: case SPA_AUDIO_FORMAT_S16_OE: case SPA_AUDIO_FORMAT_U16: return res * 2; case SPA_AUDIO_FORMAT_S24: case SPA_AUDIO_FORMAT_S24_OE: case SPA_AUDIO_FORMAT_U24: return res * 3; case SPA_AUDIO_FORMAT_S24_32: case SPA_AUDIO_FORMAT_S24_32_OE: case SPA_AUDIO_FORMAT_S32: case SPA_AUDIO_FORMAT_S32_OE: case SPA_AUDIO_FORMAT_U32: case SPA_AUDIO_FORMAT_U32_OE: case SPA_AUDIO_FORMAT_F32: case SPA_AUDIO_FORMAT_F32_OE: return res * 4; case SPA_AUDIO_FORMAT_F64: case SPA_AUDIO_FORMAT_F64_OE: return res * 8; default: return 0; } } static void copy_props(struct impl *impl, struct pw_properties *props, const char *key) { const char *str; if ((str = pw_properties_get(props, key)) != NULL) { if (pw_properties_get(impl->stream_props, key) == NULL) pw_properties_set(impl->stream_props, key, str); } } SPA_EXPORT int pipewire__module_init(struct pw_impl_module *module, const char *args) { struct pw_context *context = pw_impl_module_get_context(module); uint32_t id = pw_global_get_id(pw_impl_module_get_global(module)); uint32_t pid = getpid(); struct pw_properties *props = NULL; struct impl *impl; const char *str; int res; PW_LOG_TOPIC_INIT(mod_topic); impl = calloc(1, sizeof(struct impl)); if (impl == NULL) return -errno; pw_log_debug("module %p: new %s", impl, args); if (args == NULL) args = ""; props = pw_properties_new_string(args); if (props == NULL) { res = -errno; pw_log_error( "can't create properties: %m"); goto error; } impl->props = props; impl->stream_props = pw_properties_new(NULL, NULL); if (impl->stream_props == NULL) { res = -errno; pw_log_error( "can't create properties: %m"); goto error; } impl->module = module; impl->context = context; if (pw_properties_get(props, PW_KEY_NODE_VIRTUAL) == NULL) pw_properties_set(props, PW_KEY_NODE_VIRTUAL, "true"); if (pw_properties_get(props, PW_KEY_MEDIA_CLASS) == NULL) pw_properties_set(props, PW_KEY_MEDIA_CLASS, "Audio/Source"); if (pw_properties_get(props, PW_KEY_NODE_NAME) == NULL) pw_properties_setf(props, PW_KEY_NODE_NAME, "example-source-%u-%u", pid, id); if (pw_properties_get(props, PW_KEY_NODE_DESCRIPTION) == NULL) pw_properties_set(props, PW_KEY_NODE_DESCRIPTION, pw_properties_get(props, PW_KEY_NODE_NAME)); if ((str = pw_properties_get(props, "stream.props")) != NULL) pw_properties_update_string(impl->stream_props, str, strlen(str)); copy_props(impl, props, PW_KEY_AUDIO_RATE); copy_props(impl, props, PW_KEY_AUDIO_CHANNELS); copy_props(impl, props, SPA_KEY_AUDIO_LAYOUT); copy_props(impl, props, SPA_KEY_AUDIO_POSITION); copy_props(impl, props, PW_KEY_NODE_NAME); copy_props(impl, props, PW_KEY_NODE_DESCRIPTION); copy_props(impl, props, PW_KEY_NODE_GROUP); copy_props(impl, props, PW_KEY_NODE_LATENCY); copy_props(impl, props, PW_KEY_NODE_VIRTUAL); copy_props(impl, props, PW_KEY_MEDIA_CLASS); if ((res = parse_audio_info(impl->stream_props, &impl->info)) < 0) { pw_log_error( "can't parse format: %s", spa_strerror(res)); goto error; } impl->frame_size = calc_frame_size(&impl->info); if (impl->frame_size == 0) { res = -EINVAL; pw_log_error( "can't parse audio format"); goto error; } impl->core = pw_context_get_object(impl->context, PW_TYPE_INTERFACE_Core); if (impl->core == NULL) { str = pw_properties_get(props, PW_KEY_REMOTE_NAME); impl->core = pw_context_connect(impl->context, pw_properties_new( PW_KEY_REMOTE_NAME, str, NULL), 0); impl->do_disconnect = true; } if (impl->core == NULL) { res = -errno; pw_log_error("can't connect: %m"); goto error; } pw_proxy_add_listener((struct pw_proxy*)impl->core, &impl->core_proxy_listener, &core_proxy_events, impl); pw_core_add_listener(impl->core, &impl->core_listener, &core_events, impl); if ((res = create_stream(impl)) < 0) goto error; pw_impl_module_add_listener(module, &impl->module_listener, &module_events, impl); pw_impl_module_update_properties(module, &SPA_DICT_INIT_ARRAY(module_props)); return 0; error: impl_destroy(impl); return res; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/modules/module-fallback-sink.c000066400000000000000000000240001511204443500270610ustar00rootroot00000000000000/* PipeWire */ /* SPDX-FileCopyrightText: Copyright © 2021 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include /** \page page_module_fallback_sink Fallback Sink * * Fallback sink, which appear dynamically when no other sinks are * present. This is only useful for Pulseaudio compatibility. * * ## Module Name * * `libpipewire-module-fallback-sink` * * ## Module Options * * - `sink.name`: sink name * - `sink.description`: sink description */ #define NAME "fallback-sink" #define DEFAULT_SINK_NAME "auto_null" #define DEFAULT_SINK_DESCRIPTION _("Dummy Output") PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); #define PW_LOG_TOPIC_DEFAULT mod_topic #define MODULE_USAGE ("( sink.name= ) " \ "( sink.description= ) ") static const struct spa_dict_item module_props[] = { { PW_KEY_MODULE_AUTHOR, "Pauli Virtanen " }, { PW_KEY_MODULE_DESCRIPTION, "Dynamically appearing fallback sink" }, { PW_KEY_MODULE_USAGE, MODULE_USAGE }, { PW_KEY_MODULE_VERSION, PACKAGE_VERSION }, }; struct bitmap { uint8_t *data; size_t size; size_t items; }; struct impl { struct pw_context *context; struct pw_impl_module *module; struct spa_hook module_listener; struct pw_core *core; struct pw_registry *registry; struct pw_proxy *sink; struct spa_hook core_listener; struct spa_hook core_proxy_listener; struct spa_hook registry_listener; struct spa_hook sink_listener; struct pw_properties *properties; struct bitmap sink_ids; struct bitmap fallback_sink_ids; int check_seq; unsigned int do_disconnect:1; unsigned int scheduled:1; }; static int bitmap_add(struct bitmap *map, uint32_t i) { const uint32_t pos = (i >> 3); const uint8_t mask = 1 << (i & 0x7); if (pos >= map->size) { size_t new_size = map->size + pos + 16; void *p; p = realloc(map->data, new_size); if (!p) return -errno; memset((uint8_t*)p + map->size, 0, new_size - map->size); map->data = p; map->size = new_size; } if (map->data[pos] & mask) return 1; map->data[pos] |= mask; ++map->items; return 0; } static bool bitmap_remove(struct bitmap *map, uint32_t i) { const uint32_t pos = (i >> 3); const uint8_t mask = 1 << (i & 0x7); if (pos >= map->size) return false; if (!(map->data[pos] & mask)) return false; map->data[pos] &= ~mask; --map->items; return true; } static void bitmap_free(struct bitmap *map) { free(map->data); spa_zero(*map); } static int add_id(struct bitmap *map, uint32_t id) { int res; if (id == SPA_ID_INVALID) return -EINVAL; if ((res = bitmap_add(map, id)) < 0) pw_log_error("%s", spa_strerror(res)); return res; } static void reschedule_check(struct impl *impl) { if (!impl->scheduled) return; impl->check_seq = pw_core_sync(impl->core, 0, impl->check_seq); } static void schedule_check(struct impl *impl) { if (impl->scheduled) return; impl->scheduled = true; impl->check_seq = pw_core_sync(impl->core, 0, impl->check_seq); } static void sink_proxy_removed(void *data) { struct impl *impl = data; pw_proxy_destroy(impl->sink); } static void sink_proxy_bound_props(void *data, uint32_t id, const struct spa_dict *props) { struct impl *impl = data; add_id(&impl->sink_ids, id); add_id(&impl->fallback_sink_ids, id); reschedule_check(impl); schedule_check(impl); } static void sink_proxy_destroy(void *data) { struct impl *impl = data; pw_log_debug("fallback dummy sink destroyed"); spa_hook_remove(&impl->sink_listener); impl->sink = NULL; } static const struct pw_proxy_events sink_proxy_events = { PW_VERSION_PROXY_EVENTS, .removed = sink_proxy_removed, .bound_props = sink_proxy_bound_props, .destroy = sink_proxy_destroy, }; static int sink_create(struct impl *impl) { if (impl->sink) return 0; pw_log_info("creating fallback dummy sink"); impl->sink = pw_core_create_object(impl->core, "adapter", PW_TYPE_INTERFACE_Node, PW_VERSION_NODE, impl->properties ? &impl->properties->dict : NULL, 0); if (impl->sink == NULL) return -errno; pw_proxy_add_listener(impl->sink, &impl->sink_listener, &sink_proxy_events, impl); return 0; } static void sink_destroy(struct impl *impl) { if (!impl->sink) return; pw_log_info("removing fallback dummy sink"); pw_proxy_destroy(impl->sink); } static void check_sinks(struct impl *impl) { int res; pw_log_debug("seeing %zu sink(s), %zu fallback sink(s)", impl->sink_ids.items, impl->fallback_sink_ids.items); if (impl->sink_ids.items > impl->fallback_sink_ids.items) { sink_destroy(impl); } else { if ((res = sink_create(impl)) < 0) pw_log_error("error creating sink: %s", spa_strerror(res)); } } static void registry_event_global(void *data, uint32_t id, uint32_t permissions, const char *type, uint32_t version, const struct spa_dict *props) { struct impl *impl = data; const char *str; reschedule_check(impl); if (!props) return; if (!spa_streq(type, PW_TYPE_INTERFACE_Node)) return; str = spa_dict_lookup(props, PW_KEY_MEDIA_CLASS); if (!(spa_streq(str, "Audio/Sink") || spa_streq(str, "Audio/Sink/Virtual"))) return; add_id(&impl->sink_ids, id); schedule_check(impl); } static void registry_event_global_remove(void *data, uint32_t id) { struct impl *impl = data; reschedule_check(impl); bitmap_remove(&impl->fallback_sink_ids, id); if (bitmap_remove(&impl->sink_ids, id)) schedule_check(impl); } static const struct pw_registry_events registry_events = { PW_VERSION_REGISTRY_EVENTS, .global = registry_event_global, .global_remove = registry_event_global_remove, }; static void core_done(void *data, uint32_t id, int seq) { struct impl *impl = data; if (seq == impl->check_seq) { impl->scheduled = false; check_sinks(impl); } } static const struct pw_core_events core_events = { PW_VERSION_CORE_EVENTS, .done = core_done, }; static void core_proxy_removed(void *data) { struct impl *impl = data; if (impl->registry) { spa_hook_remove(&impl->registry_listener); pw_proxy_destroy((struct pw_proxy*)impl->registry); impl->registry = NULL; } } static void core_proxy_destroy(void *data) { struct impl *impl = data; spa_hook_remove(&impl->core_listener); spa_hook_remove(&impl->core_proxy_listener); impl->core = NULL; } static const struct pw_proxy_events core_proxy_events = { PW_VERSION_PROXY_EVENTS, .destroy = core_proxy_destroy, .removed = core_proxy_removed, }; static void impl_destroy(struct impl *impl) { sink_destroy(impl); if (impl->registry) { spa_hook_remove(&impl->registry_listener); pw_proxy_destroy((struct pw_proxy*)impl->registry); impl->registry = NULL; } if (impl->core) { spa_hook_remove(&impl->core_listener); spa_hook_remove(&impl->core_proxy_listener); if (impl->do_disconnect) pw_core_disconnect(impl->core); impl->core = NULL; } if (impl->properties) { pw_properties_free(impl->properties); impl->properties = NULL; } bitmap_free(&impl->sink_ids); bitmap_free(&impl->fallback_sink_ids); free(impl); } static void module_destroy(void *data) { struct impl *impl = data; spa_hook_remove(&impl->module_listener); impl_destroy(impl); } static const struct pw_impl_module_events module_events = { PW_VERSION_IMPL_MODULE_EVENTS, .destroy = module_destroy, }; SPA_EXPORT int pipewire__module_init(struct pw_impl_module *module, const char *args) { struct pw_context *context = pw_impl_module_get_context(module); struct pw_properties *props = NULL; struct impl *impl = NULL; const char *str; int res; PW_LOG_TOPIC_INIT(mod_topic); impl = calloc(1, sizeof(struct impl)); if (impl == NULL) goto error_errno; pw_log_debug("module %p: new %s", impl, args); if (args == NULL) args = ""; impl->module = module; impl->context = context; props = pw_properties_new_string(args); if (props == NULL) goto error_errno; impl->properties = pw_properties_new(NULL, NULL); if (impl->properties == NULL) goto error_errno; if ((str = pw_properties_get(props, "sink.name")) == NULL) str = DEFAULT_SINK_NAME; pw_properties_set(impl->properties, PW_KEY_NODE_NAME, str); if ((str = pw_properties_get(props, "sink.description")) == NULL) str = DEFAULT_SINK_DESCRIPTION; pw_properties_set(impl->properties, PW_KEY_NODE_DESCRIPTION, str); pw_properties_setf(impl->properties, SPA_KEY_AUDIO_RATE, "%u", 48000); pw_properties_setf(impl->properties, SPA_KEY_AUDIO_CHANNELS, "%u", 2); pw_properties_set(impl->properties, SPA_KEY_AUDIO_POSITION, "FL,FR"); pw_properties_set(impl->properties, PW_KEY_MEDIA_CLASS, "Audio/Sink"); pw_properties_set(impl->properties, PW_KEY_FACTORY_NAME, "support.null-audio-sink"); pw_properties_set(impl->properties, PW_KEY_NODE_VIRTUAL, "true"); pw_properties_set(impl->properties, "monitor.channel-volumes", "true"); impl->core = pw_context_get_object(impl->context, PW_TYPE_INTERFACE_Core); if (impl->core == NULL) { str = pw_properties_get(props, PW_KEY_REMOTE_NAME); impl->core = pw_context_connect(impl->context, pw_properties_new( PW_KEY_REMOTE_NAME, str, NULL), 0); impl->do_disconnect = true; } if (impl->core == NULL) { res = -errno; pw_log_error("can't connect: %m"); goto error; } pw_proxy_add_listener((struct pw_proxy*)impl->core, &impl->core_proxy_listener, &core_proxy_events, impl); pw_core_add_listener(impl->core, &impl->core_listener, &core_events, impl); impl->registry = pw_core_get_registry(impl->core, PW_VERSION_REGISTRY, 0); if (impl->registry == NULL) goto error_errno; pw_registry_add_listener(impl->registry, &impl->registry_listener, ®istry_events, impl); pw_impl_module_add_listener(module, &impl->module_listener, &module_events, impl); pw_impl_module_update_properties(module, &SPA_DICT_INIT_ARRAY(module_props)); schedule_check(impl); pw_properties_free(props); return 0; error_errno: res = -errno; error: if (props) pw_properties_free(props); if (impl) impl_destroy(impl); return res; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/modules/module-ffado-driver.c000066400000000000000000001252171511204443500267440ustar00rootroot00000000000000/* PipeWire */ /* SPDX-FileCopyrightText: Copyright © 2021 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /** \page page_module_ffado_driver FFADO firewire audio driver * * The ffado-driver module provides a source or sink using the libffado library for * reading and writing to firewire audio devices. * * ## Module Name * * `libpipewire-module-ffado-driver` * * ## Module Options * * - `driver.mode`: the driver mode, sink|source|duplex, default duplex * - `ffado.devices`: array of devices to open, default "hw:0" * - `ffado.period-size`: period size,default 1024. A value of 0 will use the graph duration. * - `ffado.period-num`: period number,default 3 * - `ffado.sample-rate`: sample-rate, default 48000. A value of 0 will use the graph rate. * - `ffado.slave-mode`: slave mode * - `ffado.snoop-mode`: snoop mode * - `ffado.verbose`: ffado verbose level * - `ffado.rtprio`: ffado realtime priority, this is by default the PipeWire server * priority + 5 * - `ffado.realtime`: ffado realtime mode. this requires correctly configured rlimits * to acquire FIFO scheduling at the ffado.rtprio priority * - `latency.internal.input`: extra input latency in frames * - `latency.internal.output`: extra output latency in frames * - `source.props`: Extra properties for the source filter * - `sink.props`: Extra properties for the sink filter * * ## General options * * Options with well-known behavior. * * - \ref PW_KEY_REMOTE_NAME * - \ref SPA_KEY_AUDIO_LAYOUT * - \ref SPA_KEY_AUDIO_POSITION * - \ref PW_KEY_NODE_NAME * - \ref PW_KEY_NODE_DESCRIPTION * - \ref PW_KEY_NODE_GROUP * - \ref PW_KEY_NODE_VIRTUAL * - \ref PW_KEY_MEDIA_CLASS * - \ref PW_KEY_TARGET_OBJECT to specify the remote node.name or serial.id to link to * * ## Example configuration of a duplex sink/source * *\code{.unparsed} * # ~/.config/pipewire/pipewire.conf.d/my-ffado-driver.conf * * context.modules = [ * { name = libpipewire-module-ffado-driver * args = { * #driver.mode = duplex * #ffado.devices = [ "hw:0" ] * #ffado.period-size = 1024 * #ffado.period-num = 3 * #ffado.sample-rate = 48000 * #ffado.slave-mode = false * #ffado.snoop-mode = false * #ffado.verbose = 0 * #ffado.rtprio = 65 * #ffado.realtime = true * #latency.internal.input = 0 * #latency.internal.output = 0 * #audio.position = [ FL FR ] * source.props = { * # extra sink properties * } * sink.props = { * # extra sink properties * } * } * } * ] *\endcode */ #define NAME "ffado-driver" PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); #define PW_LOG_TOPIC_DEFAULT mod_topic #define MAX_CHANNELS SPA_AUDIO_MAX_CHANNELS #define MAX_PORTS 128 #define FFADO_RT_PRIORITY_PACKETIZER_RELATIVE 5 #define DEFAULT_DEVICES "[ \"hw:0\" ]" #define DEFAULT_PERIOD_SIZE 1024 #define DEFAULT_PERIOD_NUM 3 #define DEFAULT_SAMPLE_RATE 48000 #define DEFAULT_SLAVE_MODE false #define DEFAULT_SNOOP_MODE false #define DEFAULT_VERBOSE 0 #define DEFAULT_RTPRIO (RTPRIO_SERVER + FFADO_RT_PRIORITY_PACKETIZER_RELATIVE) #define DEFAULT_REALTIME true #define DEFAULT_POSITION "[ FL FR ]" #define DEFAULT_MIDI_PORTS 1 #define MODULE_USAGE "( remote.name= ) " \ "( driver.mode= ) " \ "( ffado.devices= ) " \ "( ffado.period-size= ) " \ "( ffado.period-num= ) " \ "( ffado.sample-rate= ) " \ "( ffado.slave-mode= ) " \ "( ffado.snoop-mode= ) " \ "( ffado.verbose= ) " \ "( ffado.rtprio= ) " \ "( ffado.realtime= ) " \ "( audio.position= ) " \ "( source.props= ) " \ "( sink.props= ) " static const struct spa_dict_item module_props[] = { { PW_KEY_MODULE_AUTHOR, "Wim Taymans " }, { PW_KEY_MODULE_DESCRIPTION, "Create an FFADO based driver" }, { PW_KEY_MODULE_USAGE, MODULE_USAGE }, { PW_KEY_MODULE_VERSION, PACKAGE_VERSION }, }; struct port_data { struct port *port; }; struct port { enum spa_direction direction; ffado_streaming_stream_type stream_type; char name[280]; struct spa_latency_info latency[2]; bool latency_changed[2]; unsigned int is_midi:1; unsigned int cleared:1; void *buffer; uint8_t event_byte; uint8_t event_type; uint32_t event_time; uint8_t event_buffer[512]; uint32_t event_pos; int event_pending; struct port_data *data; }; struct volume { bool mute; uint32_t n_volumes; float volumes[MAX_CHANNELS]; }; struct stream { struct impl *impl; enum spa_direction direction; struct pw_properties *props; struct pw_filter *filter; struct spa_hook listener; struct spa_audio_info_raw info; uint32_t n_ports; struct port *ports[MAX_PORTS]; struct volume volume; unsigned int ready:1; unsigned int running:1; struct { unsigned int transfered:1; } rt; }; struct impl { struct pw_context *context; struct pw_loop *main_loop; struct pw_loop *data_loop; struct spa_system *system; struct spa_source *ffado_timer; ffado_device_info_t device_info; ffado_options_t device_options; ffado_device_t *dev; #define MODE_SINK (1<<0) #define MODE_SOURCE (1<<1) #define MODE_DUPLEX (MODE_SINK|MODE_SOURCE) uint32_t mode; struct pw_properties *props; struct pw_impl_module *module; struct spa_hook module_listener; struct pw_core *core; struct spa_hook core_proxy_listener; struct spa_hook core_listener; uint32_t reset_work_id; struct spa_io_position *position; uint32_t latency[2]; struct stream source; struct stream sink; char *devices[FFADO_MAX_SPECSTRINGS]; uint32_t n_devices; int32_t sample_rate; int32_t period_size; int32_t n_periods; bool slave_mode; bool snoop_mode; uint32_t verbose; int32_t rtprio; bool realtime; uint32_t input_latency; uint32_t output_latency; uint32_t quantum_limit; uint32_t frame_time; unsigned int do_disconnect:1; unsigned int fix_midi:1; unsigned int started:1; unsigned int freewheel:1; pthread_t thread; struct { unsigned int done:1; unsigned int triggered:1; unsigned int new_xrun:1; uint32_t pw_xrun; uint32_t ffado_xrun; } rt; }; static int stop_ffado_device(struct impl *impl); static int start_ffado_device(struct impl *impl); static void schedule_reset_ffado_device(struct impl *impl); static void reset_volume(struct volume *vol, uint32_t n_volumes) { uint32_t i; vol->mute = false; vol->n_volumes = n_volumes; for (i = 0; i < n_volumes; i++) vol->volumes[i] = 1.0f; } static inline void do_volume(float *dst, const float *src, struct volume *vol, uint32_t ch, uint32_t n_samples) { float v = vol->mute ? 0.0f : vol->volumes[ch]; if (v == 0.0f || src == NULL) memset(dst, 0, n_samples * sizeof(float)); else if (v == 1.0f) memcpy(dst, src, n_samples * sizeof(float)); else { uint32_t i; for (i = 0; i < n_samples; i++) dst[i] = src[i] * v; } } static void clear_port_buffer(struct port *p, uint32_t n_samples) { if (!p->cleared) { if (p->buffer) memset(p->buffer, 0, n_samples * sizeof(uint32_t)); p->cleared = true; } } static inline void fix_midi_event(uint8_t *data, size_t size) { /* fixup NoteOn with vel 0 */ if (size > 2 && (data[0] & 0xF0) == 0x90 && data[2] == 0x00) { data[0] = 0x80 + (data[0] & 0x0F); data[2] = 0x40; } } static void midi_to_ffado(struct port *p, float *src, uint32_t n_samples) { struct spa_pod_parser parser; struct spa_pod_frame frame; struct spa_pod_sequence seq; struct spa_pod_control c; const void *seq_body, *c_body; uint32_t i, index = 0, unhandled = 0; uint32_t *dst = p->buffer; if (src == NULL) return; spa_pod_parser_init_from_data(&parser, src, n_samples * sizeof(float), 0, n_samples * sizeof(float)); if (spa_pod_parser_push_sequence_body(&parser, &frame, &seq, &seq_body) < 0) return; clear_port_buffer(p, n_samples); /* first leftovers from previous cycle, always start at offset 0 */ for (i = 0; i < p->event_pos; i++) { dst[index] = 0x01000000 | (uint32_t) p->event_buffer[i]; index += 8; } p->event_pos = 0; while (spa_pod_parser_get_control_body(&parser, &c, &c_body) >= 0) { uint8_t data[16]; int j, size; size_t c_size = c.value.size; uint64_t state = 0; if (c.type != SPA_CONTROL_UMP) continue; if (index < c.offset) index = SPA_ROUND_UP_N(c.offset, 8); while (c_size > 0) { size = spa_ump_to_midi((const uint32_t**)&c_body, &c_size, data, sizeof(data), &state); if (size <= 0) break; for (j = 0; j < size; j++) { if (index >= n_samples) { /* keep events that don't fit for the next cycle */ if (p->event_pos < sizeof(p->event_buffer)) p->event_buffer[p->event_pos++] = data[j]; else unhandled++; } else dst[index] = 0x01000000 | (uint32_t) data[j]; index += 8; } } } if (unhandled > 0) pw_log_warn("%u MIDI events dropped (index %d)", unhandled, index); else if (p->event_pos > 0) pw_log_debug("%u MIDI events saved (index %d)", p->event_pos, index); } static int take_bytes(struct port *p, uint32_t *frame, uint8_t **bytes, size_t *size) { if (p->event_pos == 0) return 0; *frame = p->event_time; *bytes = p->event_buffer; *size = p->event_pos; return 1; } static const int status_len[] = { 2, /* noteoff */ 2, /* noteon */ 2, /* keypress */ 2, /* controller */ 1, /* pgmchange */ 1, /* chanpress */ 2, /* pitchbend */ -1, /* invalid */ 1, /* sysex 0xf0 */ 1, /* qframe 0xf1 */ 2, /* songpos 0xf2 */ 1, /* songsel 0xf3 */ -1, /* none 0xf4 */ -1, /* none 0xf5 */ 0, /* tune request 0xf6 */ -1, /* none 0xf7 */ 0, /* clock 0xf8 */ -1, /* none 0xf9 */ 0, /* start 0xfa */ 0, /* continue 0xfb */ 0, /* stop 0xfc */ -1, /* none 0xfd */ 0, /* sensing 0xfe */ 0, /* reset 0xff */ }; static int process_byte(struct port *p, uint32_t time, uint8_t byte, uint32_t *frame, uint8_t **bytes, size_t *size) { int res = 0; if (byte >= 0xf8) { if (byte == 0xfd) { pw_log_warn("dropping invalid MIDI status bytes %08x", byte); return false; } p->event_byte = byte; *frame = time; *bytes = &p->event_byte; *size = 1; return 1; } if ((byte & 0x80) && (byte != 0xf7 || p->event_type != 8)) { if (p->event_pending > 0) pw_log_warn("incomplete MIDI message %02x dropped %u time:%u", p->event_type, p->event_pending, time); /* new command */ p->event_buffer[0] = byte; p->event_time = time; if ((byte & 0xf0) == 0xf0) /* system message */ p->event_type = (byte & 0x0f) + 8; else p->event_type = (byte >> 4) & 0x07; p->event_pos = 1; p->event_pending = status_len[p->event_type]; } else { if (p->event_pending > 0) { /* rest of command */ if (p->event_pos < sizeof(p->event_buffer)) p->event_buffer[p->event_pos++] = byte; if (p->event_type != 8) p->event_pending--; } else { /* running status */ p->event_buffer[1] = byte; p->event_time = time; p->event_pending = status_len[p->event_type] - 1; p->event_pos = 2; } } if (p->event_pending == 0) { res = take_bytes(p, frame, bytes, size); if (p->event_type >= 8) p->event_type = 7; } else if (p->event_type == 8) { if (byte == 0xf7 || p->event_pos >= sizeof(p->event_buffer)) { res = take_bytes(p, frame, bytes, size); p->event_pos = 0; if (byte == 0xf7) { p->event_pending = 0; p->event_type = 7; } } } return res; } static void ffado_to_midi(struct port *p, float *dst, uint32_t *src, uint32_t size) { struct spa_pod_builder b = { 0, }; uint32_t i, count; struct spa_pod_frame f; count = src ? size : 0; spa_pod_builder_init(&b, dst, size); spa_pod_builder_push_sequence(&b, &f, 0); for (i = 0; i < count; i++) { uint32_t data = src[i], frame; uint8_t *bytes; size_t size; if ((data & 0xff000000) == 0) continue; if (process_byte(p, i, data & 0xff, &frame, &bytes, &size)) { uint64_t state = 0; while (size > 0) { uint32_t ev[4]; int ev_size = spa_ump_from_midi(&bytes, &size, ev, sizeof(ev), 0, &state); if (ev_size <= 0) break; spa_pod_builder_control(&b, frame, SPA_CONTROL_UMP); spa_pod_builder_bytes(&b, ev, ev_size); } } } spa_pod_builder_pop(&b, &f); if (p->event_pending > 0) /* make sure the rest of the MIDI message is sent first in the next cycle */ p->event_time = 0; } static inline uint64_t get_time_ns(struct impl *impl) { uint64_t nsec; if (impl->sink.filter) nsec = pw_filter_get_nsec(impl->sink.filter); else if (impl->source.filter) nsec = pw_filter_get_nsec(impl->source.filter); else nsec = 0; return nsec; } static int set_timeout(struct impl *impl, uint64_t time) { struct timespec timeout, interval; timeout.tv_sec = time / SPA_NSEC_PER_SEC; timeout.tv_nsec = time % SPA_NSEC_PER_SEC; interval.tv_sec = 0; interval.tv_nsec = 0; pw_loop_update_timer(impl->data_loop, impl->ffado_timer, &timeout, &interval, true); return 0; } static void stream_destroy(void *d) { struct stream *s = d; uint32_t i; for (i = 0; i < s->n_ports; i++) { struct port *p = s->ports[i]; if (p != NULL) { s->ports[i] = NULL; free(p->buffer); free(p); } } s->n_ports = 0; spa_hook_remove(&s->listener); s->filter = NULL; s->ready = false; s->running = false; } static void check_start(struct impl *impl) { if ((!(impl->mode & MODE_SINK) || (impl->sink.ready && impl->sink.running)) && (!(impl->mode & MODE_SOURCE) || (impl->source.ready && impl->source.running))) start_ffado_device(impl); } static void stream_state_changed(void *d, enum pw_filter_state old, enum pw_filter_state state, const char *error) { struct stream *s = d; struct impl *impl = s->impl; switch (state) { case PW_FILTER_STATE_ERROR: pw_log_warn("filter state %d error: %s", state, error); break; case PW_FILTER_STATE_UNCONNECTED: pw_impl_module_schedule_destroy(impl->module); break; case PW_FILTER_STATE_PAUSED: s->running = false; if (!impl->sink.running && !impl->source.running) stop_ffado_device(impl); break; case PW_FILTER_STATE_STREAMING: s->running = true; check_start(impl); break; default: break; } } static void sink_process(void *d, struct spa_io_position *position) { struct stream *s = d; struct impl *impl = s->impl; uint32_t i, n_samples = position->clock.duration; pw_log_trace_fp("process %d", impl->rt.triggered); if (impl->mode == MODE_SINK && impl->rt.triggered) { impl->rt.triggered = false; return; } for (i = 0; i < s->n_ports; i++) { struct port *p = s->ports[i]; float *src; if (p == NULL || p->data == NULL) continue; src = pw_filter_get_dsp_buffer(p->data, n_samples); if (src == NULL) { clear_port_buffer(p, n_samples); continue; } if (SPA_UNLIKELY(p->is_midi)) midi_to_ffado(p, src, n_samples); else do_volume(p->buffer, src, &s->volume, i, n_samples); p->cleared = false; } ffado_streaming_transfer_playback_buffers(impl->dev); s->rt.transfered = true; if (impl->mode == MODE_SINK) { pw_log_trace_fp("done %u", impl->frame_time); impl->rt.done = true; set_timeout(impl, position->clock.nsec); } } static void silence_playback(struct impl *impl) { uint32_t i; struct stream *s = &impl->sink; for (i = 0; i < s->n_ports; i++) { struct port *p = s->ports[i]; if (p != NULL) clear_port_buffer(p, impl->device_options.period_size); } ffado_streaming_transfer_playback_buffers(impl->dev); s->rt.transfered = true; } static void source_process(void *d, struct spa_io_position *position) { struct stream *s = d; struct impl *impl = s->impl; uint32_t i, n_samples = position->clock.duration; pw_log_trace_fp("process %d", impl->rt.triggered); if (SPA_FLAG_IS_SET(impl->position->clock.flags, SPA_IO_CLOCK_FLAG_XRUN_RECOVER)) return; if (!impl->rt.triggered) { pw_log_trace_fp("done %u", impl->frame_time); impl->rt.done = true; if (!impl->sink.rt.transfered) silence_playback(impl); set_timeout(impl, position->clock.nsec); return; } impl->rt.triggered = false; ffado_streaming_transfer_capture_buffers(impl->dev); s->rt.transfered = true; for (i = 0; i < s->n_ports; i++) { struct port *p = s->ports[i]; float *dst; if (p == NULL || p->data == NULL || p->buffer == NULL) continue; dst = pw_filter_get_dsp_buffer(p->data, n_samples); if (dst == NULL) continue; if (SPA_UNLIKELY(p->is_midi)) ffado_to_midi(p, dst, p->buffer, n_samples); else do_volume(dst, p->buffer, &s->volume, i, n_samples); } } static void stream_io_changed(void *data, void *port_data, uint32_t id, void *area, uint32_t size) { struct stream *s = data; struct impl *impl = s->impl; bool freewheel; if (port_data == NULL) { switch (id) { case SPA_IO_Position: impl->position = area; freewheel = impl->position != NULL && SPA_FLAG_IS_SET(impl->position->clock.flags, SPA_IO_CLOCK_FLAG_FREEWHEEL); if (impl->freewheel != freewheel) { pw_log_info("freewheel: %d -> %d", impl->freewheel, freewheel); impl->freewheel = freewheel; if (impl->started) { if (freewheel) { set_timeout(impl, 0); ffado_streaming_stop(impl->dev); } else { ffado_streaming_start(impl->dev); impl->rt.done = true; set_timeout(impl, get_time_ns(impl)); } } } break; default: break; } } } static void param_latency_changed(struct stream *s, const struct spa_pod *param, struct port_data *data) { struct port *port = data->port; struct spa_latency_info latency; bool update = false; enum spa_direction direction = port->direction; if (param == NULL || spa_latency_parse(param, &latency) < 0) return; if (spa_latency_info_compare(&port->latency[direction], &latency)) { port->latency[direction] = latency; port->latency_changed[direction] = update = true; } } static int make_stream_ports(struct stream *s) { struct impl *impl = s->impl; struct pw_properties *props; uint8_t buffer[1024]; struct spa_pod_builder b; struct spa_latency_info latency; const struct spa_pod *params[2]; uint32_t i, n_params = 0, n_channels = 0; bool is_midi; for (i = 0; i < s->n_ports; i++) { struct port *port = s->ports[i]; if (port->data != NULL) { free(port->buffer); pw_filter_remove_port(port->data); port->data = NULL; } } for (i = 0; i < s->n_ports; i++) { struct port *port = s->ports[i]; char channel[32]; snprintf(channel, sizeof(channel), "AUX%u", n_channels); switch (port->stream_type) { case ffado_stream_type_audio: props = pw_properties_new( PW_KEY_FORMAT_DSP, "32 bit float mono audio", PW_KEY_PORT_PHYSICAL, "true", PW_KEY_PORT_TERMINAL, "true", PW_KEY_PORT_NAME, port->name, PW_KEY_AUDIO_CHANNEL, channel, NULL); is_midi = false; n_channels++; break; case ffado_stream_type_midi: props = pw_properties_new( PW_KEY_FORMAT_DSP, "8 bit raw midi", PW_KEY_PORT_NAME, port->name, PW_KEY_PORT_PHYSICAL, "true", PW_KEY_PORT_TERMINAL, "true", PW_KEY_PORT_CONTROL, "true", NULL); is_midi = true; break; default: pw_log_info("not registering unknown stream %d %s (type %d)", i, port->name, port->stream_type); continue; } latency = SPA_LATENCY_INFO(s->direction, .min_quantum = 1, .max_quantum = 1, .min_rate = impl->latency[s->direction], .max_rate = impl->latency[s->direction]); spa_pod_builder_init(&b, buffer, sizeof(buffer)); n_params = 0; params[n_params++] = spa_latency_build(&b, SPA_PARAM_Latency, &latency); port->data = pw_filter_add_port(s->filter, s->direction, PW_FILTER_PORT_FLAG_MAP_BUFFERS, sizeof(struct port_data), props, params, n_params); if (port->data == NULL) { pw_log_error("Can't create port: %m"); return -errno; } port->data->port = port; port->latency[s->direction] = latency; port->is_midi = is_midi; port->buffer = calloc(impl->quantum_limit, sizeof(float)); if (port->buffer == NULL) { pw_log_error("Can't create port buffer: %m"); return -errno; } } return 0; } static void setup_stream_ports(struct stream *s) { struct impl *impl = s->impl; uint32_t i; for (i = 0; i < s->n_ports; i++) { struct port *port = s->ports[i]; if (s->direction == PW_DIRECTION_INPUT) { if (ffado_streaming_set_playback_stream_buffer(impl->dev, i, port->buffer)) pw_log_error("cannot configure port buffer for %s", port->name); if (ffado_streaming_playback_stream_onoff(impl->dev, i, 1)) pw_log_error("cannot enable port %s", port->name); } else { if (ffado_streaming_set_capture_stream_buffer(impl->dev, i, port->buffer)) pw_log_error("cannot configure port buffer for %s", port->name); if (ffado_streaming_capture_stream_onoff(impl->dev, i, 1)) pw_log_error("cannot enable port %s", port->name); } } } static struct spa_pod *make_props_param(struct spa_pod_builder *b, struct volume *vol) { return spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_Props, SPA_PARAM_Props, SPA_PROP_mute, SPA_POD_Bool(vol->mute), SPA_PROP_channelVolumes, SPA_POD_Array(sizeof(float), SPA_TYPE_Float, vol->n_volumes, vol->volumes)); } static void parse_props(struct stream *s, const struct spa_pod *param) { struct spa_pod_object *obj = (struct spa_pod_object *) param; struct spa_pod_prop *prop; uint8_t buffer[1024]; struct spa_pod_builder b; const struct spa_pod *params[1]; SPA_POD_OBJECT_FOREACH(obj, prop) { switch (prop->key) { case SPA_PROP_mute: { bool mute; if (spa_pod_get_bool(&prop->value, &mute) == 0) s->volume.mute = mute; break; } case SPA_PROP_channelVolumes: { uint32_t n; float vols[MAX_CHANNELS]; if ((n = spa_pod_copy_array(&prop->value, SPA_TYPE_Float, vols, SPA_N_ELEMENTS(vols))) > 0) { s->volume.n_volumes = n; for (n = 0; n < s->volume.n_volumes; n++) s->volume.volumes[n] = vols[n]; } break; } default: break; } } spa_pod_builder_init(&b, buffer, sizeof(buffer)); params[0] = make_props_param(&b, &s->volume); pw_filter_update_params(s->filter, NULL, params, 1); } static void stream_param_changed(void *data, void *port_data, uint32_t id, const struct spa_pod *param) { struct stream *s = data; if (port_data != NULL) { switch (id) { case SPA_PARAM_Latency: param_latency_changed(s, param, port_data); break; } } else { switch (id) { case SPA_PARAM_PortConfig: pw_log_debug("PortConfig"); if (make_stream_ports(s) >= 0) { s->ready = true; check_start(s->impl); } break; case SPA_PARAM_Props: pw_log_debug("Props"); parse_props(s, param); break; } } } static const struct pw_filter_events sink_events = { PW_VERSION_FILTER_EVENTS, .destroy = stream_destroy, .state_changed = stream_state_changed, .param_changed = stream_param_changed, .io_changed = stream_io_changed, .process = sink_process }; static const struct pw_filter_events source_events = { PW_VERSION_FILTER_EVENTS, .destroy = stream_destroy, .state_changed = stream_state_changed, .param_changed = stream_param_changed, .io_changed = stream_io_changed, .process = source_process, }; static int update_stream_format(struct stream *s, uint32_t samplerate) { uint8_t buffer[1024]; struct spa_pod_builder b; uint32_t n_params; const struct spa_pod *params[2]; if (s->info.rate == samplerate) return 0; s->info.rate = samplerate; if (s->filter == NULL) return 0; n_params = 0; spa_pod_builder_init(&b, buffer, sizeof(buffer)); params[n_params++] = spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat, &s->info); params[n_params++] = spa_format_audio_raw_build(&b, SPA_PARAM_Format, &s->info); return pw_filter_update_params(s->filter, NULL, params, n_params); } static int make_stream(struct stream *s, const char *name) { struct impl *impl = s->impl; uint32_t n_params; const struct spa_pod *params[4]; uint8_t buffer[1024]; struct spa_pod_builder b; s->filter = pw_filter_new(impl->core, name, pw_properties_copy(s->props)); if (s->filter == NULL) return -errno; spa_zero(s->listener); if (s->direction == PW_DIRECTION_INPUT) { pw_filter_add_listener(s->filter, &s->listener, &sink_events, s); } else { pw_filter_add_listener(s->filter, &s->listener, &source_events, s); } reset_volume(&s->volume, s->info.channels); spa_pod_builder_init(&b, buffer, sizeof(buffer)); n_params = 0; params[n_params++] = spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat, &s->info); params[n_params++] = spa_format_audio_raw_build(&b, SPA_PARAM_Format, &s->info); params[n_params++] = make_props_param(&b, &s->volume); return pw_filter_connect(s->filter, PW_FILTER_FLAG_DRIVER | PW_FILTER_FLAG_RT_PROCESS | PW_FILTER_FLAG_CUSTOM_LATENCY, params, n_params); } static void destroy_stream(struct stream *s) { if (s->filter) pw_filter_destroy(s->filter); } static void on_ffado_timeout(void *data, uint64_t expirations) { struct impl *impl = data; bool source_running, sink_running; uint64_t nsec; ffado_wait_response response; pw_log_trace_fp("wakeup %d", impl->rt.done); if (impl->freewheel) return; if (!impl->rt.done) { impl->rt.pw_xrun++; impl->rt.new_xrun = true; ffado_streaming_reset(impl->dev); } again: pw_log_trace_fp("FFADO wait"); response = ffado_streaming_wait(impl->dev); nsec = get_time_ns(impl); switch (response) { case ffado_wait_ok: break; case ffado_wait_xrun: pw_log_debug("FFADO xrun"); impl->rt.ffado_xrun++; impl->rt.new_xrun = true; goto again; case ffado_wait_shutdown: pw_log_info("FFADO shutdown"); return; case ffado_wait_error: default: pw_log_error("FFADO error"); return; } source_running = impl->source.running && impl->sink.ready; sink_running = impl->sink.running && impl->source.ready; impl->source.rt.transfered = false; impl->sink.rt.transfered = false; if (!source_running) { ffado_streaming_transfer_capture_buffers(impl->dev); impl->source.rt.transfered = true; } if (!sink_running) silence_playback(impl); pw_log_trace_fp("process %d %u %u %p %d %"PRIu64, impl->device_options.period_size, source_running, sink_running, impl->position, impl->frame_time, nsec); if (impl->rt.new_xrun) { pw_log_warn("Xrun FFADO:%u PipeWire:%u source:%d sink:%d", impl->rt.ffado_xrun, impl->rt.pw_xrun, source_running, sink_running); impl->rt.new_xrun = false; } if (impl->position) { struct spa_io_clock *c = &impl->position->clock; #if 0 if (c->target_duration != (uint64_t) impl->device_options.period_size) { ffado_streaming_transfer_capture_buffers(impl->dev); silence_playback(impl); if (ffado_streaming_set_period_size(impl->dev, c->target_duration) != 0) { pw_log_warn("can't change period size"); } else { sleep(1); impl->device_options.period_size = c->target_duration; } goto again; } #endif c->nsec = nsec; c->rate = SPA_FRACTION(1, impl->device_options.sample_rate); c->position += impl->device_options.period_size; c->duration = impl->device_options.period_size; c->delay = 0; c->rate_diff = 1.0; c->next_nsec = nsec + (c->duration * SPA_NSEC_PER_SEC) / impl->device_options.sample_rate; c->target_rate = c->rate; c->target_duration = c->duration; } if (impl->mode & MODE_SOURCE && source_running) { impl->rt.done = false; impl->rt.triggered = true; set_timeout(impl, nsec + SPA_NSEC_PER_SEC); pw_filter_trigger_process(impl->source.filter); } else if (impl->mode == MODE_SINK && sink_running) { impl->rt.done = false; impl->rt.triggered = true; set_timeout(impl, nsec + SPA_NSEC_PER_SEC); pw_filter_trigger_process(impl->sink.filter); } else { impl->rt.done = true; set_timeout(impl, nsec); } } static void close_ffado_device(struct impl *impl) { if (impl->dev == NULL) return; stop_ffado_device(impl); ffado_streaming_finish(impl->dev); impl->dev = NULL; pw_log_info("closed FFADO device %s", impl->devices[0]); } static int open_ffado_device(struct impl *impl) { int32_t target_rate, target_period; if (impl->dev != NULL) return 0; target_rate = impl->sample_rate; target_period = impl->period_size; if (impl->position) { struct spa_io_clock *c = &impl->position->clock; if (target_rate == 0) target_rate = c->target_rate.denom; if (target_period == 0) target_period = c->target_duration; } if (target_rate == 0) target_rate = DEFAULT_SAMPLE_RATE; if (target_period == 0) target_period = DEFAULT_PERIOD_SIZE; spa_zero(impl->device_info); impl->device_info.device_spec_strings = impl->devices; impl->device_info.nb_device_spec_strings = impl->n_devices; spa_zero(impl->device_options); impl->device_options.sample_rate = target_rate; impl->device_options.period_size = target_period; impl->device_options.nb_buffers = impl->n_periods; impl->device_options.realtime = impl->realtime; impl->device_options.packetizer_priority = impl->rtprio; impl->device_options.verbose = impl->verbose; impl->device_options.slave_mode = impl->slave_mode; impl->device_options.snoop_mode = impl->snoop_mode; impl->dev = ffado_streaming_init(impl->device_info, impl->device_options); if (impl->dev == NULL) { pw_log_error("can't open FFADO device %s", impl->devices[0]); return -EIO; } if (impl->device_options.realtime) { pw_log_info("Streaming thread running with Realtime scheduling, priority %d", impl->device_options.packetizer_priority); } else { pw_log_info("Streaming thread running without Realtime scheduling"); } ffado_streaming_set_audio_datatype(impl->dev, ffado_audio_datatype_float); impl->source.n_ports = ffado_streaming_get_nb_capture_streams(impl->dev); impl->sink.n_ports = ffado_streaming_get_nb_playback_streams(impl->dev); if (impl->source.n_ports == 0 && impl->sink.n_ports == 0) { close_ffado_device(impl); return -EIO; } update_stream_format(&impl->source, impl->device_options.sample_rate); update_stream_format(&impl->sink, impl->device_options.sample_rate); pw_log_info("opened FFADO device %s source:%d sink:%d rate:%d period:%d %p", impl->devices[0], impl->source.n_ports, impl->sink.n_ports, impl->device_options.sample_rate, impl->device_options.period_size, impl->position); return 0; } static int probe_ffado_device(struct impl *impl) { int res; uint32_t i, n_channels; struct port *port; char name[256]; if ((res = open_ffado_device(impl)) < 0) return res; n_channels = 0; for (i = 0; i < impl->source.n_ports; i++) { port = calloc(1, sizeof(struct port)); if (port == NULL) return -errno; port->direction = impl->source.direction; port->stream_type = ffado_streaming_get_capture_stream_type(impl->dev, i); ffado_streaming_get_capture_stream_name(impl->dev, i, name, sizeof(name)); snprintf(port->name, sizeof(port->name), "%s_out", name); switch (port->stream_type) { case ffado_stream_type_audio: n_channels++; break; default: break; } impl->source.ports[i] = port; } if (impl->source.info.channels != n_channels) { uint32_t n_pos = SPA_MIN(n_channels, SPA_N_ELEMENTS(impl->source.info.position)); impl->source.info.channels = n_pos; for (i = 0; i < n_pos; i++) impl->source.info.position[i] = SPA_AUDIO_CHANNEL_AUX0 + i; } n_channels = 0; for (i = 0; i < impl->sink.n_ports; i++) { port = calloc(1, sizeof(struct port)); if (port == NULL) return -errno; port->direction = impl->sink.direction; port->stream_type = ffado_streaming_get_playback_stream_type(impl->dev, i); ffado_streaming_get_playback_stream_name(impl->dev, i, name, sizeof(name)); snprintf(port->name, sizeof(port->name), "%s_in", name); switch (port->stream_type) { case ffado_stream_type_audio: n_channels++; break; default: break; } impl->sink.ports[i] = port; } if (impl->sink.info.channels != n_channels) { uint32_t n_pos = SPA_MIN(n_channels, SPA_N_ELEMENTS(impl->sink.info.position)); impl->sink.info.channels = n_pos; for (i = 0; i < n_pos; i++) impl->sink.info.position[i] = SPA_AUDIO_CHANNEL_AUX0 + i; } if (impl->mode & MODE_SINK) { if ((res = make_stream(&impl->sink, "FFADO Sink")) < 0) goto exit; } if (impl->mode & MODE_SOURCE) { if ((res = make_stream(&impl->source, "FFADO Source")) < 0) goto exit; } exit: close_ffado_device(impl); return res; } static int start_ffado_device(struct impl *impl) { int res; if (impl->started) return 0; if ((res = open_ffado_device(impl)) < 0) return res; setup_stream_ports(&impl->source); setup_stream_ports(&impl->sink); if (ffado_streaming_prepare(impl->dev)) { pw_log_error("Could not prepare streaming"); schedule_reset_ffado_device(impl); return -EIO; } if (ffado_streaming_start(impl->dev)) { pw_log_warn("Could not start FFADO streaming, try reset"); schedule_reset_ffado_device(impl); return -EIO; } pw_log_info("FFADO started streaming"); impl->started = true; impl->rt.done = true; set_timeout(impl, get_time_ns(impl)); return 0; } static int stop_ffado_device(struct impl *impl) { if (!impl->started) return 0; impl->started = false; set_timeout(impl, 0); if (ffado_streaming_stop(impl->dev)) pw_log_error("Could not stop FFADO streaming"); else pw_log_info("FFADO stopped streaming"); close_ffado_device(impl); return 0; } static void do_reset_ffado(void *obj, void *data, int res, uint32_t id) { struct impl *impl = obj; impl->reset_work_id = SPA_ID_INVALID; close_ffado_device(impl); open_ffado_device(impl); } static void schedule_reset_ffado_device(struct impl *impl) { if (impl->reset_work_id != SPA_ID_INVALID) return; impl->reset_work_id = pw_work_queue_add(pw_context_get_work_queue(impl->context), impl, 0, do_reset_ffado, NULL); } static void core_error(void *data, uint32_t id, int seq, int res, const char *message) { struct impl *impl = data; pw_log_error("error id:%u seq:%d res:%d (%s): %s", id, seq, res, spa_strerror(res), message); if (id == PW_ID_CORE && res == -EPIPE) pw_impl_module_schedule_destroy(impl->module); } static const struct pw_core_events core_events = { PW_VERSION_CORE_EVENTS, .error = core_error, }; static void core_destroy(void *d) { struct impl *impl = d; spa_hook_remove(&impl->core_listener); impl->core = NULL; pw_impl_module_schedule_destroy(impl->module); } static const struct pw_proxy_events core_proxy_events = { .destroy = core_destroy, }; static void impl_destroy(struct impl *impl) { uint32_t i; if (impl->reset_work_id != SPA_ID_INVALID) pw_work_queue_cancel(pw_context_get_work_queue(impl->context), impl, SPA_ID_INVALID); close_ffado_device(impl); destroy_stream(&impl->source); destroy_stream(&impl->sink); if (impl->core && impl->do_disconnect) pw_core_disconnect(impl->core); if (impl->ffado_timer) pw_loop_destroy_source(impl->data_loop, impl->ffado_timer); if (impl->data_loop) pw_context_release_loop(impl->context, impl->data_loop); pw_properties_free(impl->sink.props); pw_properties_free(impl->source.props); pw_properties_free(impl->props); for (i = 0; i < impl->n_devices; i++) free(impl->devices[i]); free(impl); } static void module_destroy(void *data) { struct impl *impl = data; spa_hook_remove(&impl->module_listener); impl_destroy(impl); } static const struct pw_impl_module_events module_events = { PW_VERSION_IMPL_MODULE_EVENTS, .destroy = module_destroy, }; static void parse_devices(struct impl *impl, const char *val, size_t len) { struct spa_json it[1]; char v[FFADO_MAX_SPECSTRING_LENGTH]; if (spa_json_begin_array_relax(&it[0], val, len) <= 0) return; impl->n_devices = 0; while (spa_json_get_string(&it[0], v, sizeof(v)) > 0 && impl->n_devices < FFADO_MAX_SPECSTRINGS) { impl->devices[impl->n_devices++] = strdup(v); } } static int parse_audio_info(const struct pw_properties *props, struct spa_audio_info_raw *info) { return spa_audio_info_raw_init_dict_keys(info, &SPA_DICT_ITEMS( SPA_DICT_ITEM(SPA_KEY_AUDIO_FORMAT, "F32P"), SPA_DICT_ITEM(SPA_KEY_AUDIO_POSITION, DEFAULT_POSITION)), &props->dict, SPA_KEY_AUDIO_CHANNELS, SPA_KEY_AUDIO_LAYOUT, SPA_KEY_AUDIO_POSITION, NULL); } static void copy_props(struct impl *impl, struct pw_properties *props, const char *key) { const char *str; if ((str = pw_properties_get(props, key)) != NULL) { if (pw_properties_get(impl->sink.props, key) == NULL) pw_properties_set(impl->sink.props, key, str); if (pw_properties_get(impl->source.props, key) == NULL) pw_properties_set(impl->source.props, key, str); } } SPA_EXPORT int pipewire__module_init(struct pw_impl_module *module, const char *args) { struct pw_context *context = pw_impl_module_get_context(module); struct pw_properties *props = NULL; struct impl *impl; const char *str; int res; PW_LOG_TOPIC_INIT(mod_topic); impl = calloc(1, sizeof(struct impl)); if (impl == NULL) return -errno; pw_log_debug("module %p: new %s", impl, args); if (args == NULL) args = ""; props = pw_properties_new_string(args); if (props == NULL) { res = -errno; pw_log_error( "can't create properties: %m"); goto error; } impl->props = props; str = pw_properties_get(props, "ffado.devices"); if (str == NULL) str = DEFAULT_DEVICES; parse_devices(impl, str, strlen(str)); impl->period_size = pw_properties_get_int32(props, "ffado.period-size", DEFAULT_PERIOD_SIZE); impl->n_periods = pw_properties_get_int32(props, "ffado.period-num", DEFAULT_PERIOD_NUM); impl->sample_rate = pw_properties_get_int32(props, "ffado.sample-rate", DEFAULT_SAMPLE_RATE); impl->slave_mode = pw_properties_get_bool(props, "ffado.slave-mode", DEFAULT_SLAVE_MODE); impl->snoop_mode = pw_properties_get_bool(props, "ffado.snoop-mode", DEFAULT_SNOOP_MODE); impl->verbose = pw_properties_get_uint32(props, "ffado.verbose", DEFAULT_VERBOSE); impl->rtprio = pw_properties_get_uint32(props, "ffado.rtprio", DEFAULT_RTPRIO); impl->realtime = pw_properties_get_bool(props, "ffado.realtime", DEFAULT_REALTIME); impl->input_latency = pw_properties_get_uint32(props, "latency.internal.input", 0); impl->output_latency = pw_properties_get_uint32(props, "latency.internal.output", 0); impl->quantum_limit = pw_properties_get_uint32( pw_context_get_properties(context), "default.clock.quantum-limit", 8192u); impl->sink.props = pw_properties_new(NULL, NULL); impl->source.props = pw_properties_new(NULL, NULL); if (impl->source.props == NULL || impl->sink.props == NULL) { res = -errno; pw_log_error( "can't create properties: %m"); goto error; } impl->module = module; impl->context = context; impl->main_loop = pw_context_get_main_loop(context); impl->data_loop = pw_context_acquire_loop(context, &props->dict); impl->system = impl->main_loop->system; impl->reset_work_id = SPA_ID_INVALID; impl->source.impl = impl; impl->source.direction = PW_DIRECTION_OUTPUT; impl->sink.impl = impl; impl->sink.direction = PW_DIRECTION_INPUT; impl->mode = MODE_DUPLEX; if ((str = pw_properties_get(props, "driver.mode")) != NULL) { if (spa_streq(str, "source")) { impl->mode = MODE_SOURCE; } else if (spa_streq(str, "sink")) { impl->mode = MODE_SINK; } else if (spa_streq(str, "duplex")) { impl->mode = MODE_DUPLEX; } else { pw_log_error("invalid driver.mode '%s'", str); res = -EINVAL; goto error; } } impl->ffado_timer = pw_loop_add_timer(impl->data_loop, on_ffado_timeout, impl); if (impl->ffado_timer == NULL) { pw_log_error("can't create ffado timer: %m"); res = -errno; goto error; } pw_properties_set(props, PW_KEY_NODE_LOOP_NAME, impl->data_loop->name); if (pw_properties_get(props, PW_KEY_NODE_VIRTUAL) == NULL) pw_properties_set(props, PW_KEY_NODE_VIRTUAL, "true"); if (pw_properties_get(props, PW_KEY_NODE_GROUP) == NULL) pw_properties_set(props, PW_KEY_NODE_GROUP, "ffado-group"); if (pw_properties_get(props, PW_KEY_NODE_LINK_GROUP) == NULL) pw_properties_set(props, PW_KEY_NODE_LINK_GROUP, "ffado-group"); if (pw_properties_get(props, PW_KEY_NODE_PAUSE_ON_IDLE) == NULL) pw_properties_set(props, PW_KEY_NODE_PAUSE_ON_IDLE, "false"); pw_properties_set(impl->sink.props, PW_KEY_MEDIA_CLASS, "Audio/Sink"); pw_properties_set(impl->sink.props, PW_KEY_PRIORITY_DRIVER, "35000"); pw_properties_set(impl->sink.props, PW_KEY_PRIORITY_SESSION, "2000"); pw_properties_set(impl->sink.props, PW_KEY_NODE_NAME, "ffado_sink"); pw_properties_set(impl->sink.props, PW_KEY_NODE_DESCRIPTION, "FFADO Sink"); pw_properties_set(impl->source.props, PW_KEY_MEDIA_CLASS, "Audio/Source"); pw_properties_set(impl->source.props, PW_KEY_PRIORITY_DRIVER, "35001"); pw_properties_set(impl->source.props, PW_KEY_PRIORITY_SESSION, "2001"); pw_properties_set(impl->source.props, PW_KEY_NODE_NAME, "ffado_source"); pw_properties_set(impl->source.props, PW_KEY_NODE_DESCRIPTION, "FFADO Source"); if ((str = pw_properties_get(props, "sink.props")) != NULL) pw_properties_update_string(impl->sink.props, str, strlen(str)); if ((str = pw_properties_get(props, "source.props")) != NULL) pw_properties_update_string(impl->source.props, str, strlen(str)); copy_props(impl, props, PW_KEY_NODE_LOOP_NAME); copy_props(impl, props, PW_KEY_NODE_LINK_GROUP); copy_props(impl, props, PW_KEY_NODE_GROUP); copy_props(impl, props, PW_KEY_NODE_VIRTUAL); copy_props(impl, props, PW_KEY_NODE_PAUSE_ON_IDLE); if ((res = parse_audio_info(impl->source.props, &impl->source.info)) < 0 || (res = parse_audio_info(impl->sink.props, &impl->sink.info)) < 0) { pw_log_error( "can't parse format: %s", spa_strerror(res)); goto error; } impl->core = pw_context_get_object(impl->context, PW_TYPE_INTERFACE_Core); if (impl->core == NULL) { str = pw_properties_get(props, PW_KEY_REMOTE_NAME); impl->core = pw_context_connect(impl->context, pw_properties_new( PW_KEY_REMOTE_NAME, str, NULL), 0); impl->do_disconnect = true; } if (impl->core == NULL) { res = -errno; pw_log_error("can't connect: %m"); goto error; } pw_proxy_add_listener((struct pw_proxy*)impl->core, &impl->core_proxy_listener, &core_proxy_events, impl); pw_core_add_listener(impl->core, &impl->core_listener, &core_events, impl); if ((res = probe_ffado_device(impl)) < 0) goto error; pw_impl_module_add_listener(module, &impl->module_listener, &module_events, impl); pw_impl_module_update_properties(module, &SPA_DICT_INIT_ARRAY(module_props)); return 0; error: impl_destroy(impl); return res; } pipewire-1.5.84-98b46935250a4b74005123abef4a28604c9a1365/src/modules/module-filter-chain.c000066400000000000000000002031701511204443500267340ustar00rootroot00000000000000/* PipeWire */ /* SPDX-FileCopyrightText: Copyright © 2021 Wim Taymans */ /* SPDX-License-Identifier: MIT */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define NAME "filter-chain" PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); #define PW_LOG_TOPIC_DEFAULT mod_topic extern struct spa_handle_factory spa_filter_graph_factory; /** * \page page_module_filter_chain Filter-Chain * * The filter-chain allows you to create an arbitrary processing graph * from LADSPA, LV2, sofa, ffmpeg and builtin filters. This filter can be * made into a virtual sink/source or between any 2 nodes in the graph. * * The filter chain is built with 2 streams, a capture stream providing * the input to the filter chain and a playback stream sending out the * filtered stream to the next nodes in the graph. * * Because both ends of the filter-chain are built with streams, the session * manager can manage the configuration and connection with the sinks and * sources automatically. * * ## Module Name * * `libpipewire-module-filter-chain` * * ## Module Options * * - `node.description`: a human readable name for the filter chain * - `filter.graph = []`: a description of the filter graph to run, see below * - `capture.props = {}`: properties to be passed to the input stream * - `playback.props = {}`: properties to be passed to the output stream * * ## Filter graph description * * The general structure of the graph description is as follows: * *\code{.unparsed} * filter.graph = { * nodes = [ * { * type = * name = * plugin = * label =